com.adobe.fontengine.font.cff.Dict Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
*
* File: Dict.java
*
*
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2004-2005 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains the property of
* Adobe Systems Incorporated and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe Systems
* Incorporated and its suppliers and may be covered by U.S. and Foreign
* Patents, patents in process, and are protected by trade secret or
* copyright law. Dissemination of this information or reproduction of this
* material is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
*
*/
package com.adobe.fontengine.font.cff;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.adobe.fontengine.font.InvalidFontException;
import com.adobe.fontengine.font.Matrix;
import com.adobe.fontengine.font.ROS;
import com.adobe.fontengine.font.Rect;
import com.adobe.fontengine.font.cff.CFFByteArray.CFFByteArrayBuilder;
import com.adobe.fontengine.font.postscript.PostscriptTokenParser;
import com.adobe.fontengine.font.OrigFontType;
/**
* Represents a DICT.
*
* Keys and values
*
*
* The inner classes {@link IntegerValue}, {@link StringValue},
* {@link NumbersValue}, {@link OffsetValue},
* {@link OffsetSizeValue}, and {@link ROSValue} must be used for
* values.
*
*
*
* The inner class {@link Key} provide objects which must be used for the
* keys. Those objects are actually subtypes of {@link Key}, with one
* class per type of value: {@link IntegerKey}, {@link StringKey},
* {@link NumbersKey}, {@link OffsetKey},
* {@link OffsetSizeKey}, and {@link ROSKey}.
*
*
*
* The various get
methods take one of the {@link Key} objects
* and return the corresponding {@link Value} object from the dictionary.
* Optionally, if the key is not present in the dictionary
* but a default value exists, that value is returned.
*
*
* Streaming a dictionary
*
*
* Streaming a dictionary is a little bit involved, because some values are
* actually offsets to other portions of the stream. Our strategy is to first
* stream all the key/value pairs which are not offsets, using the
* {@link #stream stream} method of the dictionary, and then to stream
* immediately dummy values for each key/value pair which contains an offset.
* Streaming each such key/value pair gives back a marker. For example, for a
* dictionary that contains a {@link Key#FDSelect} entry and a
* {@link Key#CharStrings} entry:
*
*
*
* dict.stream (...);
* int fdSelectMarker = Key.FDSelect.streamDummyValue (...);
* int charstringsMarker = Key.CharStrings.streamDummyValue (...);
*
*
*
* Later, when the actual offset is known (e.g. just before streaming the
* portion that is pointed to), the offsets can be fixed:
*
*
*
* Key.FDSelect.fixOffset (bb, fdSelectMarker, offsetForFdSelect);
* ...
* Key.CharStrings.fixOffset (bb, charstringsMarker, offsetForCharStrings);
*
*
*
* Values which are a combination of an offset and a size are handled similarly:
*
*
*
* int privateMarker = Key.Private.streamDummyValue (...);
* ...
* Key.Private.fixOffset (bb, marker2, offsetForPrivate);
* ...
* Key.Private.fixSize (bb, marker2, sizeOfPrivate);
*
*
*
* Note that the offsets being passed are stored directly. In particular the
* value passed to fixOffset
must account for the base of the
* offset.
*
*
*
* Since the values that contains offsets are streamed by separate calls, it is
* necessary for the {@link #stream stream} call to not stream those values,
* even if they are present in the dictionary. Another situation to solve is
* that some dictionaries must be streamed with some key first. To solve both
* problems, the {@link #stream stream} method takes a list of {@link Key Key},
* and streams precisely those keys which are present in the
* dictionary, in the order in which they appear in the list. This mechanism is
* also useful to stream only part of an existing dictionary.
*
*
* Synchronization
*
* Dict, Key and Value and their subclasses are immutable.
*/
public class Dict {
//----------------------------------------------------------------- values ---
/** The Value class represents a dictionary value.
*
* Value objects are immutable.
*/
static interface Value {
/** Collects the strings used by this value for the FontSet string index.
* @param strings if this value needs strings that are not already
* present in strings
, then add them to this list
*/
abstract public void collectStrings (List /**/ strings);
/** Stream this value in a CFFByteArrayBuilder.
* @param bb the destination for the bytes
* @param strings (a representation of) the string index for the fontset;
* the string at index i in the list has sid i
*/
abstract public void stream (CFFByteArrayBuilder bb, List strings) throws InvalidFontException;
}
/** Represent a value which is a string. */
static class StringValue implements Value {
final public String value;
public StringValue (ValueStack s, StringIndex stringIndex) throws InvalidFontException {
value = stringIndex.getString (s.popInt ());
}
public StringValue (String s) {
value = s;
}
public void collectStrings (List /**/ strings) {
if (strings.indexOf (value) == -1) {
strings.add (value); }
}
public void stream (CFFByteArrayBuilder bb, List strings) {
streamInt (bb, strings.indexOf (value));
}
}
/** Represent a value which is an array of numbers. */
static class NumbersValue implements Value {
final public Object[] values;
public NumbersValue (ValueStack s, int numberOfElements) {
if (numberOfElements == -1) {
numberOfElements = s.count; }
values = new Object [numberOfElements];
try {
for (int i = numberOfElements - 1; i >= 0; i--) {
values [i] = s.popValue (); }}
catch (InvalidFontException e) {
// cannot happen, since we pop as many elements
// as there are on the stack
}
}
public NumbersValue (Object [] values) throws InvalidFontException {
this.values = new Object [values.length];
for (int i = 0; i < values.length; i++) {
if (! (values [i] instanceof Integer) && ! (values [i] instanceof String) && !(values[i] instanceof Double)) {
throw new InvalidFontException ("invalid value"); }
this.values [i] = values [i]; }
}
public NumbersValue (int[] values) {
this.values = new Object [values.length];
for (int i = 0; i < values.length; i++) {
this.values[i] = new Integer(values[i]); }
}
public NumbersValue (double[] values) {
this.values = new Object [values.length];
for (int i = 0; i < values.length; i++) {
this.values[i] = new Double(values[i]); }
}
public NumbersValue (Double d) {
this.values = new Object[] {d};
}
public NumbersValue (Integer i) {
this.values = new Object[] {i};
}
public NumbersValue (int i) {
this.values = new Object[] {new Integer (i)};
}
public NumbersValue (String s) {
this.values = new Object[] {s};
}
public NumbersValue (Matrix m) {
this.values = new Object[] {new Double(m.a),
new Double(m.b),
new Double(m.c),
new Double(m.d),
new Double(m.tx),
new Double(m.ty)};
}
public NumbersValue (Rect bbox) {
this.values = new Object[] { new Double(bbox.xmin),
new Double(bbox.ymin),
new Double(bbox.xmax),
new Double(bbox.ymax)};
}
public void collectStrings (List /**/ strings) {
// this value does not depend on strings in the string index
}
public void stream (CFFByteArrayBuilder bb, List strings)
throws InvalidFontException {
for (int i = 0; i < values.length; i++) {
if (values [i] instanceof Integer) {
streamInt (bb, ((Integer) values [i]).intValue ()); }
else if (values[i] instanceof String) {
streamDouble (bb, (String) values [i]); }
else {
double d = ((Double)values[i]).doubleValue();
int val = (int)d;
if (val == d) {
streamInt (bb, val); }
else {
streamDouble (bb, (Double)values[i]); }}}
}
public int getCount () {
return values.length;
}
/** Return the values are an array of double
.
*/
public double[] getValuesAsDouble () {
double[] result = new double [values.length];
for (int i = 0; i < values.length; i++) {
if (values [i] instanceof Integer) {
result [i] = ((Integer) values [i]).intValue (); }
else if (values[i] instanceof String){
result [i] = Double.parseDouble ((String) values [i]); }
else
result[i] = ((Double)values[i]).doubleValue(); }
return result;
}
/** Return values as an array of int
.
*/
public int[] getValuesAsInt() {
int[] result = new int [values.length];
for (int i = 0; i < values.length; i++) {
if (values [i] instanceof Integer) {
result [i] = ((Integer) values [i]).intValue (); }
else if (values[i] instanceof String){
result [i] = Integer.parseInt((String) values [i]); }
else {
result[i] = ((Double)values[i]).intValue(); }}
return result;
}
public double getFirstValueAsDouble () {
if (values [0] instanceof Integer) {
return ((Integer) values [0]).intValue (); }
else if (values[0] instanceof String){
return Double.parseDouble ((String) values [0]); }
else {
return ((Double)values[0]).doubleValue(); }
}
}
/** Represent a value which is an offset. */
static class OffsetValue implements Value {
final public int offset;
public OffsetValue (ValueStack s) throws InvalidFontException {
offset = s.popInt ();
}
public OffsetValue (int offset) {
this.offset = offset;
}
public void collectStrings (List /**/ strings) {
// this value does not depend on strings in the string index
}
public void stream (CFFByteArrayBuilder bb, List strings) {
// streaming is performed by the pair streamDummy/fixOffset
}
}
/** Represent a value which is an offset/size pair. */
static class OffsetSizeValue implements Value {
final public int size;
public int offset;
public OffsetSizeValue (ValueStack s) throws InvalidFontException {
offset = s.popInt ();
size = s.popInt ();
}
public void collectStrings (List /**/ strings) {
// this value does not depend on strings in the string index
}
public void stream (CFFByteArrayBuilder bb, List strings) {
// streaming is performed by the pair streamDummy/fixOffset
}
}
/** Represent a value which is an ROS. */
static class ROSValue implements Value {
public final ROS ros;
public ROSValue (ValueStack s, StringIndex stringIndex) throws InvalidFontException {
int supplement = s.popInt ();
String ordering = stringIndex.getString (s.popInt ());
String registry = stringIndex.getString (s.popInt ());
ros = new ROS(registry, ordering, supplement);
}
public ROSValue (String registry, String ordering, int supplement) {
ros = new ROS(registry, ordering, supplement);
}
public void collectStrings (List /**/ strings) {
if (strings.indexOf (ros.registry) == -1) {
strings.add (ros.registry); }
if (strings.indexOf (ros.ordering) == -1) {
strings.add (ros.ordering); }
}
public void stream (CFFByteArrayBuilder bb, List strings) {
streamInt (bb, strings.indexOf (ros.registry));
streamInt (bb, strings.indexOf (ros.ordering));
streamInt (bb, ros.supplement);
}
}
/** Represent a key value which is an integer. */
static class IntegerValue implements Value {
final public int value;
public IntegerValue (ValueStack s) throws InvalidFontException {
value = s.popInt ();
}
public IntegerValue (int n) {
value = n;
}
public void collectStrings (List /**/ strings) {
// this value does not depend on strings in the string index
}
public void stream (CFFByteArrayBuilder bb, List strings) {
streamInt (bb, value);
}
}
//------------------------------------------------------------------- keys ---
/** Base class for dictionaries keys.
*/
static abstract class Key {
private final String name;
private final int opcode1;
private final int opcode2;
private final static Key[] nonExtendedKeys = new Key [27];
private final static Key[] extendedKeys = new Key [39];
// our constructors are private, so that only the Key objects
// defined in this class are used.
private Key (int opcode, String name) {
this.opcode1 = opcode;
this.opcode2 = -1;
this.name = name;
nonExtendedKeys [opcode] = this;
}
private Key (int opcode1, int opcode2, String name) {
this.opcode1 = opcode1;
this.opcode2 = opcode2;
this.name = name;
extendedKeys [opcode2] = this;
}
public boolean equals (Object obj) {
if (obj == null) {
return false; }
if (this == obj) {
return true; }
if (!(obj instanceof Key)) {
return false; }
Key otherKey = (Key) obj;
if ( (this.name.equals(otherKey.name))
&& (this.opcode1 == otherKey.opcode1)
&& (this.opcode2 == otherKey.opcode2)) {
return true; }
return false;
}
public int hashCode () {
return this.name.hashCode();
}
public String toString () {
return name;
}
int opCodeLength () {
if (opcode2 == -1) {
return 1; }
return 2;
}
void stream (CFFByteArrayBuilder bb) {
bb.addCard8 (opcode1);
if (opcode2 != -1) {
bb.addCard8 (opcode2); }
}
abstract Value parse (ValueStack s, StringIndex stringIndex)
throws InvalidFontException;
abstract boolean compatibleValue (Object o);
final static StringKey version = new StringKey (0, "version");
final static StringKey Notice = new StringKey (1, "Notice");
final static StringKey FullName = new StringKey (2, "FullName");
final static StringKey FamilyName = new StringKey (3, "FamilyName");
final static StringKey Weight = new StringKey (4, "Weight");
final static NumbersKey FontBBox = new NumbersKey (5, "FontBBox", 4);
final static NumbersKey BlueValues = new NumbersKey (6, "BlueValues", -1);
final static NumbersKey OtherBlues = new NumbersKey (7, "OtherBlues", -1);
final static NumbersKey FamilyBlues = new NumbersKey (8, "FamilyBlues", -1);
final static NumbersKey FamilyOtherBlues = new NumbersKey (9, "FamilyOtherBlues", -1);
final static NumbersKey StdHW = new NumbersKey (10, "StdHW", 1);
final static NumbersKey StdVW = new NumbersKey (11, "StdVW", 1);
final static NumbersKey UniqueID = new NumbersKey (13, "UniqueID", 1);
final static NumbersKey XUID = new NumbersKey (14, "XUID", -1);
final static OffsetKey charset = new OffsetKey (15, "charset");
final static OffsetKey Encoding = new OffsetKey (16, "Encoding");
final static OffsetKey CharStrings = new OffsetKey (17, "CharStrings");
final static OffsetSizeKey Private = new OffsetSizeKey (18, "Private");
final static OffsetKey Subrs = new OffsetKey (19, "Subrs");
final static NumbersKey defaultWidthX = new NumbersKey (20, "defaultWidthX", 1);
final static NumbersKey nominalWidthX = new NumbersKey (21, "nominalWidthX", 1);
final static StringKey Copyright = new StringKey (12, 0, "Copyright");
final static NumbersKey isFixedPitch = new NumbersKey (12, 1, "isFixedPitch", 1);
final static NumbersKey ItalicAngle = new NumbersKey (12, 2, "ItalicAngle", 1);
final static NumbersKey UnderlinePosition = new NumbersKey (12, 3, "UnderlinePosition", 1);
final static NumbersKey UnderlineThickness = new NumbersKey (12, 4, "UnderlineThickness", 1);
final static NumbersKey PaintType = new NumbersKey (12, 5, "PainType", 1);
final static NumbersKey CharstringType = new NumbersKey (12, 6, "CharstringType", 1);
final static NumbersKey FontMatrix = new NumbersKey (12, 7, "FontMatrix", 6);
final static NumbersKey StrokeWidth = new NumbersKey (12, 8, "StrokeWidth", 1);
final static NumbersKey BlueScale = new NumbersKey (12, 9, "BlueScale", 1);
final static NumbersKey BlueShift = new NumbersKey (12, 10, "BlueShift", 1);
final static NumbersKey BlueFuzz = new NumbersKey (12, 11, "BlueFuzz", 1);
final static NumbersKey StemSnapH = new NumbersKey (12, 12, "StemSnapH", -1);
final static NumbersKey StemSnapV = new NumbersKey (12, 13, "StemSnapV", -1);
final static NumbersKey ForceBold = new NumbersKey (12, 14, "ForceBold", 1);
final static NumbersKey ForceBoldThreshold = new NumbersKey (12, 15, "ForceBoldThreshold", 1);
final static NumbersKey LanguageGroup = new NumbersKey (12, 17, "LanguageGroup", 1);
final static NumbersKey ExpansionFactor = new NumbersKey (12, 18, "ExpansionFactor", 1);
final static NumbersKey initialRandomSeed = new NumbersKey (12, 19, "initialRandomSeed", 1);
final static IntegerKey SyntheticBase = new IntegerKey (12, 20, "SyntheticBase");
final static StringKey PostScript = new StringKey (12, 21, "PostScript");
final static StringKey BaseFontName = new StringKey (12, 22, "BaseFontName");
final static NumbersKey BaseFontBlend = new NumbersKey (12, 23, "BaseFontBlend", -1);
final static ROSKey ROS = new ROSKey (12, 30, "ROS");
final static NumbersKey CIDFontVersion = new NumbersKey (12, 31, "CIDFontVersion", 1);
final static NumbersKey CIDFontRevision = new NumbersKey (12, 32, "CIDFontRevision", 1);
final static NumbersKey CIDFontType = new NumbersKey (12, 33, "CIDFontType", 1);
final static IntegerKey CIDCount = new IntegerKey (12, 34, "CIDCount");
final static NumbersKey UIDBase = new NumbersKey (12, 35, "UIDBase", 1);
final static OffsetKey FDArray = new OffsetKey (12, 36, "FDArray");
final static OffsetKey FDSelect = new OffsetKey (12, 37, "FDSelect");
final static StringKey FontName = new StringKey (12, 38, "FontName");
}
static class IntegerKey extends Key {
IntegerKey (int a, int b, String s) {
super (a, b, s);
}
IntegerKey (int a, String s) {
super (a, s);
}
public Value parse (ValueStack s, StringIndex stringIndex)
throws InvalidFontException {
return new IntegerValue (s);
}
public boolean compatibleValue (Object o) {
return o instanceof IntegerValue;
}
}
static class NumbersKey extends Key {
final int stackDepth;
NumbersKey (int a, int b, String s, int stackDepth) {
super (a, b, s);
this.stackDepth = stackDepth;
}
NumbersKey (int a, String s, int stackDepth) {
super (a, s);
this.stackDepth = stackDepth;
}
public Value parse (ValueStack s, StringIndex stringIndex)
throws InvalidFontException {
return new NumbersValue (s, stackDepth);
}
public boolean compatibleValue (Object o) {
if (! (o instanceof NumbersValue)) {
return false; }
if (stackDepth != -1) {
NumbersValue v = (NumbersValue) o;
return v.values.length == stackDepth; }
return true;
}
}
static class OffsetKey extends Key {
OffsetKey (int a, int b, String s) {
super (a, b, s);
}
OffsetKey (int a, String s) {
super (a, s);
}
public Value parse (ValueStack s, StringIndex stringIndex)
throws InvalidFontException {
return new OffsetValue (s);
}
public int streamDummyValue (CFFByteArrayBuilder bb) {
int marker = bb.getSize();
bb.addCard8 (29);
bb.addCard32 (0);
stream (bb);
return marker;
}
public void fixOffset (CFFByteArrayBuilder bb, int marker, int offset) {
bb.setCard32 (marker + 1, offset);
}
public boolean compatibleValue (Object o) {
return o instanceof OffsetValue;
}
}
static class OffsetSizeKey extends Key {
OffsetSizeKey (int a, int b, String s) {
super (a, b, s);
}
OffsetSizeKey (int a, String s) {
super (a, s);
}
public Value parse (ValueStack s, StringIndex stringIndex)
throws InvalidFontException {
return new OffsetSizeValue (s);
}
public int streamDummyValue (CFFByteArrayBuilder bb) {
int marker = bb.getSize();
bb.addCard8 (29);
bb.addCard32 (0);
bb.addCard8 (29);
bb.addCard32 (0);
super.stream (bb);
return marker;
}
public void fixOffset (CFFByteArrayBuilder bb, int marker, int offset) {
bb.setCard32 (marker + 6, offset);
}
public void fixSize (CFFByteArrayBuilder bb, int marker, int size) {
bb.setCard32 (marker + 1, size);
}
public boolean compatibleValue (Object o) {
return o instanceof OffsetSizeValue;
}
}
static class ROSKey extends Key {
ROSKey (int a, int b, String s) {
super (a, b, s);
}
ROSKey (int a, String s) {
super (a, s);
}
public Value parse (ValueStack stack, StringIndex stringIndex)
throws InvalidFontException {
return new ROSValue (stack, stringIndex);
}
public boolean compatibleValue (Object o) {
return o instanceof ROSValue;
}
}
static class StringKey extends Key {
StringKey (int a, int b, String s) {
super (a, b, s);
}
StringKey (int a, String s) {
super (a, s);
}
public Value parse (ValueStack s, StringIndex stringIndex)
throws InvalidFontException {
return new StringValue (s, stringIndex);
}
public boolean compatibleValue (Object o) {
return o instanceof StringValue;
}
}
//--------------------------------------------------------------- members ---
/* Because taking apart a Dict is rather expensive, we don't
* really want to do that each time we look a value up. Our
* strategy is to parse a Dict at construction time, and to
* keep a map of Keys to Values.
*/
private final Map /* */ m;
//----------------------------------------------------------- constructors ---
/** Construct a Dict
from a CFFByteArray
.
* @param data the CFFByteArray to get data from
* @param offset the offset of the first byte in data
* @param size the number of bytes for this Dict
* @param stringIndex the StringIndex to get strings from
*/
Dict (CFFByteArray data, int offset, int size, StringIndex stringIndex)
throws InvalidFontException {
this.m = new LinkedHashMap ();
ValueStack s = new ValueStack ();
int o = offset;
int limit = offset + size;
while (o < limit) {
int b0 = data.getcard8 (o);
o++;
switch (b0) {
case 12: {
b0 = data.getcard8 (o);
o++;
if (b0 < Key.extendedKeys.length && Key.extendedKeys [b0] != null) {
this.m.put (Key.extendedKeys [b0], Key.extendedKeys [b0].parse (s, stringIndex)); }
else {
s.count = 0; }
break; }
case 28: {
s.pushInt (data.getint16 (o));
o += 2;
break; }
case 29: {
s.pushInt (data.getint32 (o));
o += 4;
break; }
case 30: {
StringBuffer sb = new StringBuffer ();
boolean done = false;
while (! done) {
int b1 = data.getcard8 (o);
o++;
if (b1 == 0xff) {
done = true; }
else if ((b1 & 0xf) == 0xf) {
sb.append (floatNibbles [b1 >> 4]);
done = true; }
else {
if ((b1 >> 4) == 0xf)
throw new InvalidFontException ("CFF/Dict bad floating point number format");
sb.append (floatNibbles [b1 >> 4]);
sb.append (floatNibbles [b1 & 0xf]); }}
s.pushDouble (sb.toString ());
break; }
default: {
if (b0 < Key.nonExtendedKeys.length && Key.nonExtendedKeys [b0] != null) {
this.m.put (Key.nonExtendedKeys [b0], Key.nonExtendedKeys [b0].parse (s, stringIndex));
break; }
if (32 <= b0 && b0 <= 246) {
s.pushInt (b0 - 139);
break; }
if (247 <= b0 && b0 <= 250) {
int b1 = data.getcard8 (o);
o++;
s.pushInt ((b0 - 247) * 256 + b1 + 108);
break; }
if (241 <= b0 && b0 <= 254) {
int b1 = data.getcard8 (o);
o++;
s.pushInt (-(b0 - 251) * 256 - b1 - 108);
break; }
s.count = 0; }}}
if (s.count != 0) {
throw new InvalidFontException ("CFF/Dict stack not empty"); }
}
/** Construct a Dict
from a map of key/values.
* @param m a map of {@link Key} to {@link Value}
*/
Dict (Map /**/ m) throws InvalidFontException {
this.m = new LinkedHashMap (m.size(), 1);
for (Iterator it = m.keySet ().iterator (); it.hasNext (); ) {
Object o = it.next ();
if (! (o instanceof Key)) {
throw new InvalidFontException ("Invalid key " + o); }
Key key = (Key) o;
o = m.get (o);
if (! key.compatibleValue (o)) {
throw new InvalidFontException ("Invalid value for " + key); }
this.m.put (key, o); }
}
private double[] deltaEncode(double[] original) {
double[] delta = new double[original.length];
double diff = 0;
for (int i = 0; i < delta.length; i++) {
delta[i] = original[i] - diff;
diff = original[i]; }
return delta;
}
/**
* Construct a Dict
with the supplied values. No scaling is applied
* to the given values. They are assumed to be scaled in the cff's font units.
*
* @param blues If non-null, an array of blue values. If null, no blue values will be used.
* @param otherBlues If non-null, an array of other blue values. They are not delta encoded. If null, no
* other blue values will be used.
* @param familyBlues If non-null, an array of family blue values. They are not delta encoded. If null, no
* family blue values will be used.
* @param familyOtherBlues If non-null, an array of family other blue values. They are not delta encoded.
* If null, no family other blue values will be used.
* @param blueScale If non-null, the blue scale to be used. If null, the cff default (0.039625) is used.
* @param blueShift If non-null, the blue shift to be used. If null, the cff default (7) is used.
* @param bluefuzz If non-null, the blue fuzz to be used. If null, the cff default (1) is used.
* @param stdHW If non-null, the stdHW to be used. If null, no value will be used.
* @param stdVW If non-null, the stdVW to be used. If null, no value will be used.
* @param stemSnapH If non-null, the horizontal stemsnaps to be used. They are not delta encoded. If null, no value will be used.
* @param stemSnapV If non-null, the vertical stemsnaps to be used. They are not delta encoded. If null, no value will be used.
* @param forceBold If non-null and true, forceBold will be set to 1. Else, it will be set to 0.
* @param langGroup If non-null, the language group to be used. Else, the cff default (0) is used.
* @param expansionFactor If non-null, the expansionFactor to be used. Else, the cff default (0.06) is used.
* @param initRandSeed If non-null, the initial random seed to be used. Otherwise, the cff default (0) is used.
*/
public Dict(double[] blues, double[] otherBlues, double[] familyBlues, double[] familyOtherBlues,
Double blueScale, Double blueShift, Integer bluefuzz, Double stdHW,
Double stdVW, double[] stemSnapH, double[] stemSnapV,
Boolean forceBold, Integer langGroup, Double expansionFactor, Integer initRandSeed,
Double nominalWidth, Double defaultWidth)
{
this.m = new LinkedHashMap();
if (nominalWidth != null && nominalWidth.doubleValue() != 0) {
this.m.put(Key.nominalWidthX, new NumbersValue(nominalWidth));
if (defaultWidth != null && defaultWidth.doubleValue() != 0)
this.m.put(Key.defaultWidthX, new NumbersValue(defaultWidth)); }
if (blues != null) {
this.m.put(Key.BlueValues, new NumbersValue(deltaEncode(blues))); }
if (forceBold != null && forceBold.equals(Boolean.TRUE)) {
this.m.put(Key.ForceBold, new NumbersValue(1)); }
if (langGroup != null && langGroup.doubleValue() != 0) {
this.m.put(Key.LanguageGroup, new NumbersValue(langGroup)); }
if (expansionFactor != null && expansionFactor.doubleValue() != 0.06) {
this.m.put(Key.ExpansionFactor, new NumbersValue(expansionFactor)); }
if (initRandSeed != null && initRandSeed.doubleValue() != 0) {
this.m.put(Key.initialRandomSeed, new NumbersValue(initRandSeed)); }
if (otherBlues != null) {
this.m.put(Key.OtherBlues, new NumbersValue(deltaEncode(otherBlues))); }
if (familyBlues != null) {
this.m.put(Key.FamilyBlues, new NumbersValue(deltaEncode(familyBlues))); }
if (familyOtherBlues != null) {
this.m.put(Key.FamilyOtherBlues, new NumbersValue(deltaEncode(familyOtherBlues))); }
if (blueScale != null) {
this.m.put(Key.BlueScale, new NumbersValue(blueScale));}
if (blueShift != null && blueShift.doubleValue() != 7) {
this.m.put(Key.BlueShift, new NumbersValue(blueShift)); }
if (bluefuzz != null && bluefuzz.doubleValue() != 1) {
this.m.put(Key.BlueFuzz, new NumbersValue(bluefuzz)); }
if (stdHW != null) {
this.m.put(Key.StdHW, new NumbersValue(stdHW)); }
if (stdVW != null) {
this.m.put(Key.StdVW, new NumbersValue(stdVW)); }
if (stemSnapH != null) {
this.m.put(Key.StemSnapH, new NumbersValue(deltaEncode(stemSnapH))); }
if (stemSnapV != null) {
this.m.put(Key.StemSnapV, new NumbersValue(deltaEncode(stemSnapV))); }
}
/** Construct a Dict
with the supplied values.
*
* @param matrix If non-null, the font matrix associated with the cff. If null, the cff
* default (0.001 0 0 0.001 0 0) is used.
*/
public Dict (Matrix matrix) {
this.m = new LinkedHashMap();
if (matrix != null) {
this.m.put(Key.FontMatrix, new NumbersValue(matrix)); }
}
/**
* Construct a Dict
with the supplied values. No scaling is applied
* to the given values. They are assumed to be scaled in the cff's font units.
* @param version If non-null, the version string associated with the cff. If null, no version string
* is added.
* @param notice If non-null, the notice string associated with the cff. If null, no notice string
* is added.
* @param copyright If non-null, the copyright string associated with the cff. If null, no
* copyright is added.
* @param fullname If non-null, the fullname string associated with the cff. If null, no
* fullname is added.
* @param fontname If non-null, the fontname associated with the cff. If null, no fontname
* is added.
* @param familyName If non-null, the familyname string associated with the cff. If null, no
* familyname is added.
* @param weight If non-null, the weight string associated with the cff. If null, no
* weight is added.
* @param fixedPitch If non-null and true, isFixedPitch is set to true. Otherwise, it is false.
* @param italicAngle If non-null, the italicangle associated with the cff. If null, the cff
* default (0) is used.
* @param underlinePosition If non-null, the underline position associated with the cff. If null, the cff
* default (-100) is used.
* @param underlineThickness If non-null, the underline thickness associated with the cff. If null, the cff
* default (50) is used.
* @param paintType If non-null, the painttype associated with the cff. If null, the cff
* default (0) is used.
* @param uniqueID If non-null, the uniqueID associated with the cff. If null, no uid is used.
* @param bbox If non-null, the font bounding box associated with the cff. If null, no bbox is used.
* @param strokewidth If non-null, the strokewidth associated with the cff. If null, the default
* strokewidth (0) is used.
* @param xuid If non-null, the xuid associated with the cff. If null, no xuid is used.
* @param postscript If non-null, the postscript string associated with the cff.
* @param fsType If non-null, the fsType associated with the cff. This exact value will be added to the postscript
* string associated with the cff. If null, the postscript string is not modified.
* @param baseFontBlend If non-null, the base font blend associated with the cff. If null, no base font
* blend is used.
* @param baseFontName If non-null, the base fontname associated with the cff. If null, no base font
* name is used.
* @throws InvalidFontException
*/
public Dict(String registry, String ordering, int supplement,
String version, String notice, String copyright, String fullname,
String fontname, String familyName, String weight, Boolean fixedPitch,
Double italicAngle, Double underlinePosition, Double underlineThickness,
Integer paintType, Integer uniqueID,
Rect bbox, Double strokewidth, int[] xuid,
String postscript, Integer fsType, int[] baseFontBlend,
String baseFontName, Matrix fontMatrix) throws InvalidFontException {
this.m = new LinkedHashMap();
if (registry != null && ordering != null) {
this.m.put(Key.ROS, new ROSValue(registry, ordering, supplement)); }
if (fontname != null) {
this.m.put(Key.FontName, new StringValue(fontname)); }
if (fsType != null) {
String fsString = "/FSType " + fsType.toString() + " def";
if (postscript != null) {
this.m.put(Key.PostScript, new StringValue(postscript + " " + fsString)); }
else {
this.m.put(Key.PostScript, new StringValue(fsString)); }}
else if (postscript != null) {
this.m.put(Key.PostScript, new StringValue(postscript)); }
if (version != null) {
this.m.put(Key.version, new StringValue(version)); }
if (notice != null) {
this.m.put(Key.Notice, new StringValue(notice)); }
if (copyright != null) {
this.m.put(Key.Copyright, new StringValue(copyright)); }
if (xuid != null) {
this.m.put(Key.XUID, new NumbersValue(xuid)); }
if (baseFontBlend != null) {
this.m.put(Key.BaseFontBlend, new NumbersValue(baseFontBlend)); }
if (bbox != null) {
this.m.put(Key.FontBBox, new NumbersValue(bbox)); }
if (fullname != null) {
this.m.put(Key.FullName, new StringValue(fullname)); }
if (familyName != null) {
this.m.put(Key.FamilyName, new StringValue(familyName)); }
if (weight != null) {
this.m.put(Key.Weight, new StringValue(weight)); }
if (italicAngle != null && italicAngle.doubleValue() != 0) {
this.m.put(Key.ItalicAngle, new NumbersValue(italicAngle)); }
if (underlinePosition != null && underlinePosition.doubleValue() != -100) {
this.m.put(Key.UnderlinePosition, new NumbersValue(underlinePosition)); }
if (underlineThickness != null && underlineThickness.doubleValue() != 50) {
this.m.put(Key.UnderlineThickness, new NumbersValue(underlineThickness)); }
if (paintType != null && paintType.doubleValue() != 0) {
this.m.put(Key.PaintType, new NumbersValue(paintType)); }
if (uniqueID != null) {
this.m.put(Key.UniqueID, new NumbersValue(uniqueID)); }
if (strokewidth != null && strokewidth.doubleValue() != 0) {
this.m.put(Key.StrokeWidth, new NumbersValue(strokewidth)); }
if (baseFontName != null) {
this.m.put(Key.BaseFontName, new StringValue(baseFontName)); }
if (fontMatrix != null) {
this.m.put(Key.FontMatrix, new NumbersValue(fontMatrix)); }
}
/* During parsing, we need to keep a stack of the operands.
*
* Each stack element is either an integer (represented as an Integer)
* or a floating point (represented as a String - with nibbles interpreted).
* The motivation for this representation is that we can then stream the
* values out without loss - a round-trip through any floating point
* representation would loose some bit(s).
*/
private static class ValueStack {
/** The number of stack elements. */
int count = 0;
/** Each stack element is either an integer (represented as an Integer)
* or a floating point (represented as a String - with nibbles interpreted).
* The motivation for not converting floating point numbers to
* float or double is to allow streaming of a font without changing
* the values.
*
* A CFF Dict stack should not be larger than 48, according to the
* CFF specification.
*/
Object[] values = new Object [48];
/** Push an integer on the stack.
* @param i the integer to be pushed
* @throws InvalidFontException if the stack is full.
*/
void pushInt (int i) throws InvalidFontException {
if (count == values.length) {
throw new InvalidFontException ("CFF Dict stack overflow"); }
values [count] = new Integer (i);
count++; }
/** Push a floating point on the stack.
* @param s the floating point to be pushed
* @throws InvalidFontException if the stack is full.
*/
void pushDouble (String s) throws InvalidFontException {
if (count == values.length) {
throw new InvalidFontException ("CFF Dict stack overflow"); }
values [count] = s;
count++;
}
/** Pop the top stack element as an int.
* @throws InvalidFontException if the stack is empty, or the
* top element is not an integral value.
*/
int popInt () throws InvalidFontException {
if (count == 0) {
throw new InvalidFontException ("empty CFF Dict stack"); }
count--;
if (values [count] instanceof Integer) {
return ((Integer) values [count]).intValue (); }
else {
try {
return (int) Double.parseDouble ((String) values [count]); }
catch (NumberFormatException e) {
throw new InvalidFontException("Invalid number on stack", e); } }
}
/** Pop the top stack element a {@link Value}
* @throws InvalidFontException if the stack is empty.
*/
Object popValue () throws InvalidFontException {
if (count == 0) {
throw new InvalidFontException ("empty CFF Dict statck"); }
count--;
return values [count];
}
}
/** Stack operands representing floating point numbers are encoded
* using pieces of strings:
*/
private static final String[] floatNibbles
= {"0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", ".", "E", "E-", "", "-"};
//------------------------------------------------------ accessing values ---
/** The default values for dictionary entries. */
private static final Map /**/ defaultDict;
static {
NumbersValue numberZero = new NumbersValue (0);
NumbersValue numberOne = new NumbersValue (1);
NumbersValue numberTwo = new NumbersValue (2);
OffsetValue offsetZero = new OffsetValue (0);
NumbersValue booleanFalse = new NumbersValue (0);
defaultDict = new HashMap /**/ ();
defaultDict.put (Key.isFixedPitch, booleanFalse);
defaultDict.put (Key.ItalicAngle, numberZero);
defaultDict.put (Key.UnderlinePosition, new NumbersValue (-100));
defaultDict.put (Key.UnderlineThickness, new NumbersValue (50));
defaultDict.put (Key.PaintType, numberTwo);
defaultDict.put (Key.CharstringType, numberTwo);
try {
defaultDict.put (Key.FontMatrix,
new NumbersValue (new Object[] {"0.001", "0", "0", "0.001", "0", "0"}));
defaultDict.put (Key.FontBBox,
new NumbersValue (new Object[] {"0", "0", "0", "0"})); }
catch (InvalidFontException e) {
// we know that the values above are ok.
}
defaultDict.put (Key.StrokeWidth, numberZero);
defaultDict.put (Key.charset, offsetZero);
defaultDict.put (Key.Encoding, offsetZero);
defaultDict.put (Key.BlueScale, new NumbersValue ("0.039625"));
defaultDict.put (Key.BlueShift, new NumbersValue (7));
defaultDict.put (Key.BlueFuzz, numberOne);
defaultDict.put (Key.ForceBold, booleanFalse);
defaultDict.put (Key.LanguageGroup, numberZero);
defaultDict.put (Key.ExpansionFactor, new NumbersValue ("0.06"));
defaultDict.put (Key.initialRandomSeed, numberZero);
defaultDict.put (Key.defaultWidthX, numberZero);
defaultDict.put (Key.nominalWidthX, numberZero);
defaultDict.put (Key.CIDFontVersion, numberZero);
defaultDict.put (Key.CIDFontRevision, numberZero);
defaultDict.put (Key.CIDFontType, numberZero);
defaultDict.put (Key.CIDCount, new NumbersValue (8720));
defaultDict.put (Key.ForceBoldThreshold, numberZero);
}
OrigFontType getOrigFontType () {
StringValue v = get(Key.PostScript, true);
if (v != null) {
return PostscriptTokenParser.getOrigFontType(v.value); }
return null;
}
Integer getFSType () {
StringValue v = get(Key.PostScript, true);
if (v != null) {
return PostscriptTokenParser.getFSType (v.value); }
return null;
}
ROS getROS () {
ROSValue v = get (Key.ROS, true);
if (v != null) {
return v.ros; }
return null;
}
/** Return the value associated with a {@link IntegerKey}.
*
* @param key the {@link IntegerKey} being looked up.
* @param useDefault if true
and the key is not present
* in the dictionary, return the default value for that key.
*/
IntegerValue get (IntegerKey key, boolean useDefault) {
IntegerValue v = (IntegerValue) m.get (key);
if (v == null && useDefault) {
v = (IntegerValue) defaultDict.get (key); }
return v;
}
/** Return the value associated with a {@link StringKey}.
*
* @param key the {@link StringKey} being looked up.
* @param useDefault if true
and the key is not present
* in the dictionary, return the default value for that key.
*/
StringValue get (StringKey key, boolean useDefault) {
StringValue v = (StringValue) m.get (key);
if (v == null && useDefault) {
v = (StringValue) defaultDict.get (key); }
return v;
}
/** Return the value associated with a {@link NumbersKey}.
*
* @param key the {@link NumbersKey} being looked up.
* @param useDefault if true
and the key is not present
* in the dictionary, return the default value for that key.
*/
NumbersValue get (NumbersKey key, boolean useDefault) {
NumbersValue v = (NumbersValue) m.get (key);
if (v == null && useDefault) {
v = (NumbersValue) defaultDict.get (key); }
return v;
}
/** Return the value associated with a {@link ROSKey}.
*
* @param key the {@link ROSKey} being looked up.
* @param useDefault if true
and the key is not present
* in the dictionary, return the default value for that key.
*/
ROSValue get (ROSKey key, boolean useDefault) {
ROSValue v = (ROSValue) m.get (key);
if (v == null && useDefault) {
v = (ROSValue) defaultDict.get (key); }
return v;
}
/** Return the value associated with a {@link OffsetKey}.
*
* @param key the {@link OffsetKey} being looked up.
* @param useDefault if true
and the key is not present
* in the dictionary, return the default value for that key.
*/
OffsetValue get (OffsetKey key, boolean useDefault) {
OffsetValue v = (OffsetValue) m.get (key);
if (v == null && useDefault) {
v = (OffsetValue) defaultDict.get (key); }
return v;
}
/** Return the value associated with a {@link OffsetSizeKey}.
*
* @param key the {@link OffsetSizeKey} being looked up.
* @param useDefault if true
and the key is not present
* in the dictionary, return the default value for that key.
*/
OffsetSizeValue get (OffsetSizeKey key, boolean useDefault) {
OffsetSizeValue v = (OffsetSizeValue) m.get (key);
if (v == null && useDefault) {
v = (OffsetSizeValue) defaultDict.get (key); }
return v;
}
//-------------------------------------------------------------- streaming ---
/** Collect the strings used by this dictionary.
* @param strings a list in which unique strings are collected.
*/
void collectStrings (List /**/ strings) {
for (Iterator it = m.values ().iterator (); it.hasNext (); ) {
Value v = (Value) it.next ();
v.collectStrings (strings); }
}
/** Stream the dictionary to a CFFByteArrayBuilder.
*
* @param bb the CFFByteArrayBuilder in which to stream the dictionary
* @param strings a list ordered by SID of the strings in the StringINDEX
* @param keys stream only the keys in this array, in the order in which
* they appear.
*/
void stream (CFFByteArrayBuilder bb, List strings, Key[] keys)
throws InvalidFontException {
for (int i = 0; i < keys.length; i++) {
Value v = (Value) m.get (keys [i]);
if (v != null) {
v.stream (bb, strings);
keys [i].stream (bb); }}
}
/** Stream the value associated with key into a CFFByteArrayBuilder.
*
* @param bb the CFFByteArrayBuilder in which to stream the dictionary
* @param strings a list ordered by SID of the strings in the StringINDEX
* @param key the key to be streamed
* @throws InvalidFontException
*/
void streamValue(CFFByteArrayBuilder bb, List strings, Key key)
throws InvalidFontException {
Value v = (Value) m.get (key);
if (v != null) {
v.stream (bb, strings);
key.stream (bb); }
}
static void streamKeyVal(CFFByteArrayBuilder bb, IntegerKey key, int value) {
streamInt(bb, value);
key.stream(bb);
}
static void streamKeyVal(CFFByteArrayBuilder bb, StringKey key, String value, List strings) {
streamInt (bb, strings.indexOf (value));
key.stream(bb);
}
/** Stream an integer to a CFFByteArrayBuilder.
*
* @param bb the CFFByteArrayBuilder in which to stream the integer
* @param n the integer to steam
*/
private static void streamInt (CFFByteArrayBuilder bb, int n) {
if (-107 <= n && n <= 107) {
bb.addCard8 (n + 139); }
else if (108 <= n && n <= 1131) {
bb.addCard8 ((n - 108) / 256 + 247);
bb.addCard8 ((n - 108) % 256); }
else if (-1131 <= n && n <= -108) {
bb.addCard8 ((-108 - n) / 256 + 251);
bb.addCard8 ((-108 - n) % 256); }
else if (-32768 <= n && n <= 32767) {
bb.addCard8 (28);
bb.addCard16 (n); }
else {
bb.addCard8 (29);
bb.addCard32 (n); }
}
private static void streamDouble(CFFByteArrayBuilder bb, Double d)
throws InvalidFontException {
int i = d.intValue();
// if it really was an integer, write that out.
if (i == d.doubleValue()) {
streamInt(bb, i);
return; }
streamDouble(bb, d.toString());
}
/** Stream a floating point number to a CFFByteArrayBuilder.
*
* @param bb the CFFByteArrayBuilder in which to stream the integer
* @param s the floating point number to steam, represented as
* a string of nibbles
*/
private static void streamDouble (CFFByteArrayBuilder bb, String s)
throws InvalidFontException {
bb.addCard8 (30);
byte b = 0;
boolean topNibble = true;
for (int i = 0; i < s.length (); i++) {
char c = s.charAt (i);
switch (c) {
case '0': b |= (0x0 << (topNibble ? 4 : 0)); break;
case '1': b |= (0x1 << (topNibble ? 4 : 0)); break;
case '2': b |= (0x2 << (topNibble ? 4 : 0)); break;
case '3': b |= (0x3 << (topNibble ? 4 : 0)); break;
case '4': b |= (0x4 << (topNibble ? 4 : 0)); break;
case '5': b |= (0x5 << (topNibble ? 4 : 0)); break;
case '6': b |= (0x6 << (topNibble ? 4 : 0)); break;
case '7': b |= (0x7 << (topNibble ? 4 : 0)); break;
case '8': b |= (0x8 << (topNibble ? 4 : 0)); break;
case '9': b |= (0x9 << (topNibble ? 4 : 0)); break;
case '.': b |= (0xa << (topNibble ? 4 : 0)); break;
case 'E':
byte code = 0xb;
if (i < s.length() - 1 && s.charAt(i + 1) == '-') {
code = 0xc;
i++;
}
b |= (code << (topNibble ? 4 : 0));
break;
case '-': b |= (0xe << (topNibble ? 4 : 0)); break;
default: {
throw new InvalidFontException ("CFF: invalid float number"); }}
if (! topNibble) {
bb.addCard8 (b);
b = 0; }
topNibble = ! topNibble; }
if (topNibble) {
bb.addCard8 (0xff); }
else {
bb.addCard8 (b | 0xf); }
}
}