1 package com.google.typography.font.sfntly.table.truetype; 2 3 import com.google.typography.font.sfntly.data.FontData; 4 import com.google.typography.font.sfntly.data.ReadableFontData; 5 import com.google.typography.font.sfntly.data.WritableFontData; 6 7 import java.util.LinkedList; 8 import java.util.List; 9 10 public final class CompositeGlyph extends Glyph { 11 public static final int FLAG_ARG_1_AND_2_ARE_WORDS = 0x01; 12 public static final int FLAG_ARGS_ARE_XY_VALUES = 0x01 << 1; 13 public static final int FLAG_ROUND_XY_TO_GRID = 0x01 << 2; 14 public static final int FLAG_WE_HAVE_A_SCALE = 0x01 << 3; 15 public static final int FLAG_RESERVED = 0x01 << 4; 16 public static final int FLAG_MORE_COMPONENTS = 0x01 << 5; 17 public static final int FLAG_WE_HAVE_AN_X_AND_Y_SCALE = 0x01 << 6; 18 public static final int FLAG_WE_HAVE_A_TWO_BY_TWO = 0x01 << 7; 19 public static final int FLAG_WE_HAVE_INSTRUCTIONS = 0x01 << 8; 20 public static final int FLAG_USE_MY_METRICS = 0x01 << 9; 21 public static final int FLAG_OVERLAP_COMPOUND = 0x01 << 10; 22 public static final int FLAG_SCALED_COMPONENT_OFFSET = 0x01 << 11; 23 public static final int FLAG_UNSCALED_COMPONENT_OFFSET = 0x01 << 12; 24 25 private final List<Integer> contourIndex = new LinkedList<Integer>(); 26 private int instructionsOffset; 27 private int instructionSize; 28 CompositeGlyph(ReadableFontData data, int offset, int length)29 protected CompositeGlyph(ReadableFontData data, int offset, int length) { 30 super(data, offset, length, GlyphType.Composite); 31 initialize(); 32 } 33 CompositeGlyph(ReadableFontData data)34 protected CompositeGlyph(ReadableFontData data) { 35 super(data, GlyphType.Composite); 36 initialize(); 37 } 38 39 @Override initialize()40 protected void initialize() { 41 if (this.initialized) { 42 return; 43 } 44 synchronized (this.initializationLock) { 45 if (this.initialized) { 46 return; 47 } 48 49 int index = 5 * FontData.DataSize.USHORT.size(); // header 50 int flags = FLAG_MORE_COMPONENTS; 51 while ((flags & FLAG_MORE_COMPONENTS) == FLAG_MORE_COMPONENTS) { 52 contourIndex.add(index); 53 flags = this.data.readUShort(index); 54 index += 2 * FontData.DataSize.USHORT.size(); // flags and 55 // glyphIndex 56 if ((flags & FLAG_ARG_1_AND_2_ARE_WORDS) == FLAG_ARG_1_AND_2_ARE_WORDS) { 57 index += 2 * FontData.DataSize.SHORT.size(); 58 } else { 59 index += 2 * FontData.DataSize.BYTE.size(); 60 } 61 if ((flags & FLAG_WE_HAVE_A_SCALE) == FLAG_WE_HAVE_A_SCALE) { 62 index += FontData.DataSize.F2DOT14.size(); 63 } else if ((flags & FLAG_WE_HAVE_AN_X_AND_Y_SCALE) == FLAG_WE_HAVE_AN_X_AND_Y_SCALE) { 64 index += 2 * FontData.DataSize.F2DOT14.size(); 65 } else if ((flags & FLAG_WE_HAVE_A_TWO_BY_TWO) == FLAG_WE_HAVE_A_TWO_BY_TWO) { 66 index += 4 * FontData.DataSize.F2DOT14.size(); 67 } 68 } 69 int nonPaddedDataLength = index; 70 if ((flags & FLAG_WE_HAVE_INSTRUCTIONS) == FLAG_WE_HAVE_INSTRUCTIONS) { 71 this.instructionSize = this.data.readUShort(index); 72 index += FontData.DataSize.USHORT.size(); 73 this.instructionsOffset = index; 74 nonPaddedDataLength = index + (this.instructionSize * FontData.DataSize.BYTE.size()); 75 } 76 this.setPadding(this.dataLength() - nonPaddedDataLength); 77 } 78 } 79 flags(int contour)80 public int flags(int contour) { 81 return this.data.readUShort(this.contourIndex.get(contour)); 82 } 83 numGlyphs()84 public int numGlyphs() { 85 return this.contourIndex.size(); 86 } 87 glyphIndex(int contour)88 public int glyphIndex(int contour) { 89 return this.data.readUShort(FontData.DataSize.USHORT.size() + this.contourIndex.get(contour)); 90 } 91 argument1(int contour)92 public int argument1(int contour) { 93 int index = 2 * FontData.DataSize.USHORT.size() + this.contourIndex.get(contour); 94 int flags = this.flags(contour); 95 if ((flags & FLAG_ARG_1_AND_2_ARE_WORDS) == FLAG_ARG_1_AND_2_ARE_WORDS) { 96 return this.data.readUShort(index); 97 } 98 return this.data.readByte(index); 99 } 100 argument2(int contour)101 public int argument2(int contour) { 102 int index = 2 * FontData.DataSize.USHORT.size() + this.contourIndex.get(contour); 103 int flags = this.flags(contour); 104 if ((flags & FLAG_ARG_1_AND_2_ARE_WORDS) == FLAG_ARG_1_AND_2_ARE_WORDS) { 105 return this.data.readUShort(index + FontData.DataSize.USHORT.size()); 106 } 107 return this.data.readByte(index + FontData.DataSize.BYTE.size()); 108 } 109 transformationSize(int contour)110 public int transformationSize(int contour) { 111 int flags = this.flags(contour); 112 if ((flags & FLAG_WE_HAVE_A_SCALE) == FLAG_WE_HAVE_A_SCALE) { 113 return FontData.DataSize.F2DOT14.size(); 114 } else if ((flags & FLAG_WE_HAVE_AN_X_AND_Y_SCALE) == FLAG_WE_HAVE_AN_X_AND_Y_SCALE) { 115 return 2 * FontData.DataSize.F2DOT14.size(); 116 } else if ((flags & FLAG_WE_HAVE_A_TWO_BY_TWO) == FLAG_WE_HAVE_A_TWO_BY_TWO) { 117 return 4 * FontData.DataSize.F2DOT14.size(); 118 } 119 return 0; 120 } 121 transformation(int contour)122 public byte[] transformation(int contour) { 123 int flags = this.flags(contour); 124 int index = this.contourIndex.get(contour) + 2 * FontData.DataSize.USHORT.size(); 125 if ((flags & FLAG_ARG_1_AND_2_ARE_WORDS) == FLAG_ARG_1_AND_2_ARE_WORDS) { 126 index += 2 * FontData.DataSize.SHORT.size(); 127 } else { 128 index += 2 * FontData.DataSize.BYTE.size(); 129 } 130 131 int tsize = transformationSize(contour); 132 byte[] transformation = new byte[tsize]; 133 this.data.readBytes(index, transformation, 0, tsize); 134 return transformation; 135 } 136 137 @Override instructionSize()138 public int instructionSize() { 139 return this.instructionSize; 140 } 141 142 @Override instructions()143 public ReadableFontData instructions() { 144 return this.data.slice(this.instructionsOffset, this.instructionSize()); 145 } 146 147 @Override toString()148 public String toString() { 149 StringBuilder sb = new StringBuilder(super.toString()); 150 sb.append("\ncontourOffset.length = "); 151 sb.append(this.contourIndex.size()); 152 sb.append("\ninstructionSize = "); 153 sb.append(this.instructionSize); 154 sb.append("\n\tcontour index = ["); 155 for (int contour = 0; contour < this.contourIndex.size(); contour++) { 156 if (contour != 0) { 157 sb.append(", "); 158 } 159 sb.append(this.contourIndex.get(contour)); 160 } 161 sb.append("]\n"); 162 for (int contour = 0; contour < this.contourIndex.size(); contour++) { 163 sb.append("\t" + contour + " = [gid = " + this.glyphIndex(contour) + ", arg1 = " 164 + this.argument1(contour) + ", arg2 = " + this.argument2(contour) + "]\n"); 165 } 166 return sb.toString(); 167 } 168 169 public static class CompositeGlyphBuilder extends Glyph.Builder<CompositeGlyph> { CompositeGlyphBuilder(WritableFontData data, int offset, int length)170 protected CompositeGlyphBuilder(WritableFontData data, int offset, int length) { 171 super(data.slice(offset, length)); 172 } 173 CompositeGlyphBuilder(ReadableFontData data, int offset, int length)174 protected CompositeGlyphBuilder(ReadableFontData data, int offset, int length) { 175 super(data.slice(offset, length)); 176 } 177 178 @Override subBuildTable(ReadableFontData data)179 protected CompositeGlyph subBuildTable(ReadableFontData data) { 180 return new CompositeGlyph(data); 181 } 182 } 183 }