/*
* Copyright 2006 The FOray Project.
* http://www.foray.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This work is in part derived from the following work(s), used with the
* permission of the licensor:
* Apache FOP, licensed by the Apache Software Foundation
*
*/
/*
* $LastChangedRevision: 10482 $
* $LastChangedDate: 2008-03-23 02:59:39 +1100 (Sun, 23 Mar 2008) $
* $LastChangedBy$
*/
/* NOTICE: This file has been changed by Plutext Pty Ltd for use in docx4j.
*
* This notice is included to meet the condition in clause 4(b) of the License.
*/
//package org.foray.font.format;
package org.docx4j.fonts.foray.font.format;
import java.io.Serializable;
/**
* A PANOSE-1 classification number.
*
* <p>References:</p>
* <ul>
* <li><a href="http://fonts.apple.com/TTRefMan/RM06/Chap6OS2.html">The TTF OS/2 Table
* doc</a></li>
* <li><a href="http://www.w3.org/Fonts/Panose/pan2.html#StaticDigits">Panose 2.0 White
* Paper</a></li>
* <li><a href="http://www.byte.com/art/9405/sec12/art1.htm">The Panose Typeface-Matching
* System</a></li>
* <li>Michael S. De Laurentis, PANOSE 1.0 Core Mapper Services, Hewlett-Packard Document
* EWC-93-0023b, Hewlett-Packard Corporation, 101 Stewart, Suite 700, Seattle, WA 98101 (1993).</li>
* <li><a href="www.fonts.com/hp/panose/greybook">The "Grey Book"</a></li>
* </ul>
*/
public final class Panose implements Serializable {
private static final long serialVersionUID = -6678392067062344333L;
/**
* Enumeration of the fields that comprise a PANOSE description.
* @see "http://fonts.apple.com/TTRefMan/RM06/Chap6OS2.html"
*/
public enum Field {
/** The bFamilyType field. */
FAMILY_TYPE ((byte) 0, (byte) 5),
/** The bSerifStyle field. */
SERIF_STYLE ((byte) 1, (byte) 15),
/** The bWeight field. */
WEIGHT ((byte) 2, (byte) 11),
/** The bProportion field. */
PROPORTION ((byte) 3, (byte) 9),
/** The bContrast field. */
CONTRAST ((byte) 4, (byte) 9),
/** The bStrokeVariatoon field. */
STROKE_VARIATION ((byte) 5, (byte) 8),
/** The bArmStyle field. */
ARM_STYLE ((byte) 6, (byte) 11),
/** The bLetterform field. */
LETTERFORM ((byte) 7, (byte) 15),
/** The bMidline field. */
MIDLINE ((byte) 8, (byte) 13),
/** The bXHeight field. */
X_HEIGHT ((byte) 9, (byte) 7);
/** The 0-based index of this element in the PANOSE array. */
private byte index;
/** The maximum value that is permissible for this element. */
private byte maxValue;
/**
* Private Constructor.
* @param index The 0-based index of this element in the PANOSE array.
* @param maxValue The maximum value that is permissible for this element.
*/
private Field(final byte index, final byte maxValue) {
this.index = index;
this.maxValue = maxValue;
}
/**
* Returns the 0-based index of this element in the PANOSE array.
* @return The 0-based index of this element in the PANOSE array.
*/
public byte getIndex() {
return this.index;
}
/**
* Returns the maximum valid value for this field.
* @return The maximum valid value for this field.
*/
public byte getMaxValue() {
return this.maxValue;
}
}
/**
* Constant indicating the minimum italic value for the letterform field.
* This is based on an analysis of the MS ClearType Collection fonts:
* <ul>
* <li>consolas [ 2 11 6 9 2 2 4 3 2 4 ] bold 6 ital 3</li>
* <li>consolas-bold [ 2 11 7 9 2 2 4 3 2 4 ] bold 7 ital 3</li>
* <li>consolas-italic [ 2 11 6 9 2 2 4 10 2 4 ] bold 6 ital 10</li>
* <li>consolas-bolditalic [ 2 11 7 9 2 2 4 10 2 4 ] bold 7 ital 10</li>
*
* <li>cordianew [ 2 11 3 4 2 2 2 2 2 4 ] bold 3 ital 2</li>
* <li>cordianew-bolditalic [ 2 11 6 4 2 2 2 9 2 4 ] bold 6 ital 9</li>
*
* <li>calibri [ 2 15 5 2 2 2 4 3 2 4 ] bold 5 ital 3</li>
* <li>calibri-bold [ 2 15 7 2 3 4 4 3 2 4 ] bold 7 ital 3</li>
* <li>calibri-bolditalic [ 2 15 7 2 3 4 4 10 2 4 ] bold 7 ital 10</li>
*
* <li>constantia [ 2 3 6 2 5 3 6 3 3 3 ] bold 6 ital 3</li>
* <li>constantia-bold [ 2 3 7 2 6 3 6 3 3 3 ] bold 7 ital 3 (note the 6 at index 4)</li>
* <li>constantia-italic [ 2 3 6 2 5 3 6 10 3 3 ] bold 6 ital 10</li>
*
* <li>candara-bold [ 2 14 7 2 3 3 3 2 2 4 ] bold 7 ital 2</li>
* <li>candara-italic [ 2 14 5 2 3 3 3 9 2 4 ] bold 5 ital 9</li>
* <li>candara-bolditalic [ 2 14 7 2 3 3 3 9 2 4 ] bold 7 ital 9</li>
*
* <li>cambria-bold [ 2 4 8 3 5 4 6 3 2 4 ] bold 8 ital 3</li>
* <li>cambria-italic [ 2 4 5 3 5 4 6 10 2 4 ] bold 5 ital 10</li>
* </ul>
*/
private static final byte LETTERFORM_MIN_ITALIC = 9;
/**
* Constant indicating the minimum bold value for the weight field.
* This is based on an analysis of the MS ClearType Collection fonts, which can be found at
* {@link #LETTERFORM_MIN_ITALIC}.
*/
private static final byte WEIGHT_MIN_BOLD = 7;
/** An array of weights indicating that all elements in a comparison between two PANOSE values
* shall be considered to be of the same weight. */
private static final byte[] NEUTRAL_WEIGHTS = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
/* Caveat: It is tempting to make NEUTRAL_WEIGHTS public and allow client apps to use it as a
* parameter. However, there is no way to protect the data inside it from corruption, so we
* have elected to keep it private and to allow "null" to be interpreted as the same value. */
/** The encapsulated array of PANOSE numbers. */
private byte[] panoseArray;
/**
* Private Constructor. Use {@link #makeInstance(byte[])} to create an instance of this class.
* @param panoseArray The array of bytes recording the PANOSE classification.
*/
private Panose(final byte[] panoseArray) {
/* Clone the incoming array to protect our data from subsequent changes made to the original
* array. */
this.panoseArray = panoseArray.clone();
}
/**
* Creates a new Panose instance, first checking it for validity.
* @param panoseArray The array of bytes recording the PANOSE
* classification.
* @return The newly-created instance.
* @throws FontException If <code>panoseArray</code> contains an illegal value.
* @see #forceInstance(byte[])
*/
public static Panose makeInstance(final byte[] panoseArray) {
final String panoseValidationMessage = Panose.validPanose(panoseArray);
if (panoseValidationMessage != null) {
throw new IllegalArgumentException("Illegal Panose Array: " + panoseValidationMessage);
}
return new Panose(panoseArray);
}
/**
* Creates a new Panose instance without any error checking.
* @param panoseArray The array of bytes recording the PANOSE classification.
* @return The newly-created instance.
* @see #makeInstance(byte[])
*/
public static Panose forceInstance(final byte[] panoseArray) {
return new Panose(panoseArray);
}
/**
* Returns a clone of the the array of bytes representing the PANOSE number.
* To avoid the cost of this cloning operation, use {@link #getElement(int)} to obtain the
* value of individual elements in the array.
* @return The PANOSE array.
*/
public byte[] getPanoseArray() {
return this.panoseArray.clone();
}
/**
* Returns a given element from the underlying Panose array.
* @param index The index to the element desired.
* @return The value of the element at <code>index</code>.
*/
public byte getElement(final int index) {
return this.panoseArray[index];
}
/**
* Returns a given element from the underlying Panose array.
* @param field The field for which the value is desired.
* @return The value of the element at <code>field</code>.
*/
public byte getElement(final Panose.Field field) {
final int index = field.getIndex();
return getElement(index);
}
/**
* Computes the weighted "closeness" of another Panose to this value.
* @param otherPanose Another Panose instance which is being compared to this.
* @param weights 10-element byte array of weights that should be used for each of the elements
* in the comparison.
* Values in this array must be between 0 and 127 inclusive.
* (This constant is documented at http://www.w3.org/Fonts/Panose/pan2.html#StaticDigits).
* Use null if all elements are to be weighted equally.
* @return The weighted difference between the two Panose values.
* A smaller value indicates that the two values are closer, and a larger
* value indicates that they are farther apart.
*/
public long difference(final Panose otherPanose, final byte[] weights) {
/* This is a partial implementation of the "PANOSE Matching Heuristic" documented at:
* http://www.w3.org/Fonts/Panose/pan2.html#Heuristic. **/
byte[] weightsToUse = null;
if (weights == null) {
weightsToUse = Panose.NEUTRAL_WEIGHTS;
} else {
validateWeights(weights);
weightsToUse = weights;
}
long difference = 0;
for (int i = 0; i < Panose.Field.values().length; i++) {
// final int digit = panoseDescription.length - i;
// final int weight = (int) Math.round(Math.pow(2, digit - 1));
final int weight = weightsToUse[i];
final int thisDifference = this.getElement(i) - otherPanose.getElement(i);
difference += weight * thisDifference * thisDifference;
}
return difference;
}
/**
* Examines an array of weights, throwing various unchecked exceptions if the data is not valid.
* @param weights The array of weights to be tested.
*/
private static void validateWeights(final byte[] weights) {
if (weights == null) {
throw new NullPointerException("Weights may not be null");
}
if (weights.length != Panose.Field.values().length) {
throw new IllegalArgumentException("Weights size expected: "
+ Panose.Field.values().length + ", actual: " + weights.length);
}
for (int i = 0; i < weights.length; i++) {
final byte weight = weights[i];
if (weight < 0
|| weight > Byte.MAX_VALUE) {
throw new IllegalArgumentException("Weight element " + i + " is outside the range "
+ "of 0 thru 127.");
}
}
}
/**
* Tests the validity of a panose description.
* @param panoseDescription The panose values to be tested.
* @return Null for a valid PANOSE description. For an invalid PANOSE description, returns a
* descriptive message indicating which element is invalid.
*/
public static String validPanose(final byte[] panoseDescription) {
if (panoseDescription == null) {
return "Panose description cannot be null.";
}
if (panoseDescription.length != Panose.Field.values().length) {
return "Illegal Panose description size: " + panoseDescription.length;
}
for (int i = 0; i < panoseDescription.length; i++) {
final byte theByte = panoseDescription[i];
final Panose.Field panoseField = Panose.Field.values()[i];
final byte maxValue = panoseField.getMaxValue();
if (theByte < 0
|| theByte > maxValue) {
return "Invalid value " + theByte + " > " + maxValue + " in position " + i
+ " of " + toString(panoseDescription);
}
}
return null;
}
/**
* {@inheritDoc}
*/
public String toString() {
return toString(this.panoseArray);
}
/**
* Returns a String representation of a Panose array.
* @param panoseArray The Panose array to be expressed as a String.
* @return The String representation of <code>panoseArray</code>.
*/
private static String toString(final byte[] panoseArray) {
final StringBuilder sb = new StringBuilder(30);
sb.append("[ ");
for (int i = 0; i < panoseArray.length; i++) {
final byte theByte = panoseArray[i];
sb.append(theByte + " ");
}
sb.append("]");
return sb.toString();
}
/**
* Returns the bold version of this Panose instance.
* @return If this already describes a bold font, returns this. Otherwise, returns a new
* Panose instance that is identical to this, except describing a bold font.
*/
public Panose getBold() {
final byte weightValue = this.getElement(Panose.Field.WEIGHT);
if (weightValue >= Panose.WEIGHT_MIN_BOLD) {
/* This Panose value is already bold. */
return this;
}
final byte[] newArray = this.panoseArray.clone();
newArray[Panose.Field.WEIGHT.getIndex()] = Panose.WEIGHT_MIN_BOLD;
return Panose.forceInstance(newArray);
}
/**
* Returns the italic version of this Panose instance.
* @return If this already describes an italic font, returns this. Otherwise, returns a new
* Panose instance that is identical to this, except describing an italic font.
*/
public Panose getItalic() {
final byte letterformValue = this.getElement(Panose.Field.LETTERFORM);
if (letterformValue >= Panose.LETTERFORM_MIN_ITALIC) {
/* This Panose value is already italic. */
return this;
}
final byte[] newArray = this.panoseArray.clone();
newArray[Panose.Field.LETTERFORM.getIndex()] = Panose.LETTERFORM_MIN_ITALIC;
return Panose.forceInstance(newArray);
}
}