1 package com.google.typography.font.sfntly.table.truetype; 2 3 import com.google.typography.font.sfntly.data.ReadableFontData; 4 import com.google.typography.font.sfntly.data.WritableFontData; 5 import com.google.typography.font.sfntly.table.SubTable; 6 import com.google.typography.font.sfntly.table.truetype.GlyphTable.Offset; 7 8 public abstract class Glyph extends SubTable { 9 10 public enum GlyphType { 11 Simple, 12 Composite; 13 } 14 15 protected volatile boolean initialized = false; 16 // TOO(stuartg): should we replace this with a shared lock? more contention 17 // but less space 18 protected final Object initializationLock = new Object(); 19 20 private final Glyph.GlyphType glyphType; 21 private final int numberOfContours; 22 Glyph(ReadableFontData data, Glyph.GlyphType glyphType)23 protected Glyph(ReadableFontData data, Glyph.GlyphType glyphType) { 24 super(data); 25 this.glyphType = glyphType; 26 27 if (this.data.length() == 0) { 28 this.numberOfContours = 0; 29 } else { 30 // -1 if composite 31 this.numberOfContours = this.data.readShort(Offset.numberOfContours.offset); 32 } 33 } 34 Glyph(ReadableFontData data, int offset, int length, Glyph.GlyphType glyphType)35 protected Glyph(ReadableFontData data, int offset, int length, Glyph.GlyphType glyphType) { 36 super(data, offset, length); 37 this.glyphType = glyphType; 38 39 if (this.data.length() == 0) { 40 this.numberOfContours = 0; 41 } else { 42 // -1 if composite 43 this.numberOfContours = this.data.readShort(Offset.numberOfContours.offset); 44 } 45 } 46 glyphType(ReadableFontData data, int offset, int length)47 private static Glyph.GlyphType glyphType(ReadableFontData data, int offset, int length) { 48 if (offset > data.length()) { 49 throw new IndexOutOfBoundsException(); 50 } 51 if (length == 0) { 52 return GlyphType.Simple; 53 } 54 int numberOfContours = data.readShort(offset); 55 if (numberOfContours >= 0) { 56 return GlyphType.Simple; 57 } 58 return GlyphType.Composite; 59 } 60 61 // @SuppressWarnings("unchecked") 62 // static <T extends Glyph> T getGlyph( 63 // GlyphTable table, ReadableFontData data, int offset, int length) { 64 // Glyph.GlyphType type = Glyph.glyphType(data, offset, length); 65 // if (type == GlyphType.Simple) { 66 // return (T) new SimpleGlyph(data, offset, length); 67 // } 68 // return (T) new CompositeGlyph(data, offset, length); 69 // } 70 getGlyph( GlyphTable table, ReadableFontData data, int offset, int length)71 static Glyph getGlyph( 72 GlyphTable table, ReadableFontData data, int offset, int length) { 73 Glyph.GlyphType type = Glyph.glyphType(data, offset, length); 74 if (type == GlyphType.Simple) { 75 return new SimpleGlyph(data, offset, length); 76 } 77 return new CompositeGlyph(data, offset, length); 78 } 79 initialize()80 protected abstract void initialize(); 81 82 83 @Override padding()84 public int padding() { 85 this.initialize(); 86 return super.padding(); 87 } 88 glyphType()89 public Glyph.GlyphType glyphType() { 90 return this.glyphType; 91 } 92 93 /** 94 * Gets the number of contours in the glyph. If this returns a number greater 95 * than or equal to zero it is the actual number of contours and this is a 96 * simple glyph. If there are zero contours in the glyph then none of the 97 * other data operations will return usable values. If it -1 then the glyph is 98 * a composite glyph. 99 * 100 * @return number of contours 101 */ numberOfContours()102 public int numberOfContours() { 103 return this.numberOfContours; 104 } 105 xMin()106 public int xMin() { 107 return this.data.readShort(Offset.xMin.offset); 108 } 109 xMax()110 public int xMax() { 111 return this.data.readShort(Offset.xMax.offset); 112 } 113 yMin()114 public int yMin() { 115 return this.data.readShort(Offset.yMin.offset); 116 } 117 yMax()118 public int yMax() { 119 return this.data.readShort(Offset.yMax.offset); 120 } 121 instructionSize()122 public abstract int instructionSize(); 123 instructions()124 public abstract ReadableFontData instructions(); 125 126 @Override toString()127 public String toString() { 128 return this.toString(0); 129 } 130 toString(int length)131 public String toString(int length) { 132 StringBuilder sb = new StringBuilder(); 133 sb.append(this.glyphType()); 134 sb.append(", contours="); 135 sb.append(this.numberOfContours()); 136 sb.append(", [xmin="); 137 sb.append(this.xMin()); 138 sb.append(", ymin="); 139 sb.append(this.yMin()); 140 sb.append(", xmax="); 141 sb.append(this.xMax()); 142 sb.append(", ymax="); 143 sb.append(this.yMax()); 144 sb.append("]"); 145 sb.append("\n"); 146 return sb.toString(); 147 } 148 149 // TODO(stuartg): interface? need methods from Composite? 150 public abstract static class Contour { Contour()151 protected Contour() { 152 } 153 } 154 155 public abstract static class Builder<T extends Glyph> extends SubTable.Builder<T> { 156 protected int format; 157 Builder(WritableFontData data)158 protected Builder(WritableFontData data) { 159 super(data); 160 } 161 Builder(ReadableFontData data)162 protected Builder(ReadableFontData data) { 163 super(data); 164 } 165 166 /** 167 * @param data 168 * @param offset 169 * @param length 170 */ Builder(WritableFontData data, int offset, int length)171 protected Builder(WritableFontData data, int offset, int length) { 172 this(data.slice(offset, length)); 173 } 174 getBuilder( GlyphTable.Builder tableBuilder, ReadableFontData data)175 static Glyph.Builder<? extends Glyph> getBuilder( 176 GlyphTable.Builder tableBuilder, ReadableFontData data) { 177 return Glyph.Builder.getBuilder(tableBuilder, data, 0, data.length()); 178 } 179 getBuilder( GlyphTable.Builder tableBuilder, ReadableFontData data, int offset, int length)180 static Glyph.Builder<? extends Glyph> getBuilder( 181 GlyphTable.Builder tableBuilder, ReadableFontData data, int offset, int length) { 182 Glyph.GlyphType type = Glyph.glyphType(data, offset, length); 183 if (type == GlyphType.Simple) { 184 return new SimpleGlyph.SimpleGlyphBuilder(data, offset, length); 185 } 186 return new CompositeGlyph.CompositeGlyphBuilder(data, offset, length); 187 } 188 189 @Override subDataSet()190 protected void subDataSet() { 191 // NOP 192 } 193 194 @Override subDataSizeToSerialize()195 protected int subDataSizeToSerialize() { 196 return this.internalReadData().length(); 197 } 198 199 @Override subReadyToSerialize()200 protected boolean subReadyToSerialize() { 201 return true; 202 } 203 204 @Override subSerialize(WritableFontData newData)205 protected int subSerialize(WritableFontData newData) { 206 return this.internalReadData().copyTo(newData); 207 } 208 } 209 }