• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2010 Google Inc. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.typography.font.sfntly;
18 
19 import com.google.typography.font.sfntly.data.FontInputStream;
20 import com.google.typography.font.sfntly.data.FontOutputStream;
21 import com.google.typography.font.sfntly.data.ReadableFontData;
22 import com.google.typography.font.sfntly.data.WritableFontData;
23 import com.google.typography.font.sfntly.math.Fixed1616;
24 import com.google.typography.font.sfntly.math.FontMath;
25 import com.google.typography.font.sfntly.table.FontDataTable;
26 import com.google.typography.font.sfntly.table.Header;
27 import com.google.typography.font.sfntly.table.Table;
28 import com.google.typography.font.sfntly.table.core.CMapTable;
29 import com.google.typography.font.sfntly.table.core.FontHeaderTable;
30 import com.google.typography.font.sfntly.table.core.HorizontalDeviceMetricsTable;
31 import com.google.typography.font.sfntly.table.core.HorizontalHeaderTable;
32 import com.google.typography.font.sfntly.table.core.HorizontalMetricsTable;
33 import com.google.typography.font.sfntly.table.core.MaximumProfileTable;
34 import com.google.typography.font.sfntly.table.core.NameTable;
35 import com.google.typography.font.sfntly.table.truetype.LocaTable;
36 
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collections;
43 import java.util.HashMap;
44 import java.util.Iterator;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48 import java.util.SortedSet;
49 import java.util.TreeMap;
50 import java.util.TreeSet;
51 import java.util.logging.Logger;
52 
53 /**
54  * An sfnt container font object. This object is immutable and thread safe. To
55  * construct one use an instance of {@link Font.Builder}.
56  *
57  * @author Stuart Gill
58  */
59 public class Font {
60 
61   private static final Logger logger =
62     Logger.getLogger(Font.class.getCanonicalName());
63 
64   /**
65    * Offsets to specific elements in the underlying data. These offsets are relative to the
66    * start of the table or the start of sub-blocks within the table.
67    */
68   private enum Offset {
69     // Offsets within the main directory
70     sfntVersion(0),
71     numTables(4),
72     searchRange(6),
73     entrySelector(8),
74     rangeShift(10),
75     tableRecordBegin(12),
76     sfntHeaderSize(12),
77 
78     // Offsets within a specific table record
79     tableTag(0),
80     tableCheckSum(4),
81     tableOffset(8),
82     tableLength(12),
83     tableRecordSize(16);
84 
85     private final int offset;
86 
Offset(int offset)87     private Offset(int offset) {
88       this.offset = offset;
89     }
90   }
91 
92   /**
93    * Ordering of tables for different font types.
94    */
95   private final static List<Integer> CFF_TABLE_ORDERING;
96   private final static List<Integer> TRUE_TYPE_TABLE_ORDERING;
97   static {
98     Integer[] cffArray = new Integer[] {Tag.head,
99         Tag.hhea,
100         Tag.maxp,
101         Tag.OS_2,
102         Tag.name,
103         Tag.cmap,
104         Tag.post,
105         Tag.CFF};
106     List<Integer> cffList = new ArrayList<Integer>(cffArray.length);
Collections.addAll(cffList, cffArray)107     Collections.addAll(cffList, cffArray);
108     CFF_TABLE_ORDERING = Collections.unmodifiableList(cffList);
109 
110     Integer[] ttArray = new Integer[] {Tag.head,
111         Tag.hhea,
112         Tag.maxp,
113         Tag.OS_2,
114         Tag.hmtx,
115         Tag.LTSH,
116         Tag.VDMX,
117         Tag.hdmx,
118         Tag.cmap,
119         Tag.fpgm,
120         Tag.prep,
121         Tag.cvt,
122         Tag.loca,
123         Tag.glyf,
124         Tag.kern,
125         Tag.name,
126         Tag.post,
127         Tag.gasp,
128         Tag.PCLT,
129         Tag.DSIG};
130     List<Integer> ttList = new ArrayList<Integer>(ttArray.length);
Collections.addAll(ttList, ttArray)131     Collections.addAll(ttList, ttArray);
132     TRUE_TYPE_TABLE_ORDERING = Collections.unmodifiableList(ttList);
133   }
134 
135   /**
136    * Platform ids. These are used in a number of places within the font whenever
137    * the platform needs to be specified.
138    *
139    * @see NameTable
140    * @see CMapTable
141    */
142   public enum PlatformId {
143     Unknown(-1), Unicode(0), Macintosh(1), ISO(2), Windows(3), Custom(4);
144 
145     private final int value;
146 
PlatformId(int value)147     private PlatformId(int value) {
148       this.value = value;
149     }
150 
value()151     public int value() {
152       return this.value;
153     }
154 
equals(int value)155     public boolean equals(int value) {
156       return value == this.value;
157     }
158 
valueOf(int value)159     public static PlatformId valueOf(int value) {
160       for (PlatformId platform : PlatformId.values()) {
161         if (platform.equals(value)) {
162           return platform;
163         }
164       }
165       return Unknown;
166     }
167   }
168 
169   /**
170    * Unicode encoding ids. These are used in a number of places within the font
171    * whenever character encodings need to be specified.
172    *
173    * @see NameTable
174    * @see CMapTable
175    */
176   public enum UnicodeEncodingId {
177     // Unicode Platform Encodings
178     Unknown(-1),
179     Unicode1_0(0),
180     Unicode1_1(1),
181     ISO10646(2),
182     Unicode2_0_BMP(3),
183     Unicode2_0(4),
184     UnicodeVariationSequences(5);
185 
186     private final int value;
187 
UnicodeEncodingId(int value)188     private UnicodeEncodingId(int value) {
189       this.value = value;
190     }
191 
value()192     public int value() {
193       return this.value;
194     }
195 
equals(int value)196     public boolean equals(int value) {
197       return value == this.value;
198     }
199 
valueOf(int value)200     public static UnicodeEncodingId valueOf(int value) {
201       for (UnicodeEncodingId encoding : UnicodeEncodingId.values()) {
202         if (encoding.equals(value)) {
203           return encoding;
204         }
205       }
206       return Unknown;
207     }
208   }
209 
210   /**
211    * Windows encoding ids. These are used in a number of places within the font
212    * whenever character encodings need to be specified.
213    *
214    * @see NameTable
215    * @see CMapTable
216    */
217   public enum WindowsEncodingId {
218     // Windows Platform Encodings
219     Unknown(-1),
220     Symbol(0),
221     UnicodeUCS2(1),
222     ShiftJIS(2),
223     PRC(3),
224     Big5(4),
225     Wansung(5),
226     Johab(6),
227     UnicodeUCS4(10);
228 
229     private final int value;
230 
WindowsEncodingId(int value)231     private WindowsEncodingId(int value) {
232       this.value = value;
233     }
234 
value()235     public int value() {
236       return this.value;
237     }
238 
equals(int value)239     public boolean equals(int value) {
240       return value == this.value;
241     }
242 
valueOf(int value)243     public static WindowsEncodingId valueOf(int value) {
244       for (WindowsEncodingId encoding : WindowsEncodingId.values()) {
245         if (encoding.equals(value)) {
246           return encoding;
247         }
248       }
249       return Unknown;
250     }
251   }
252 
253   /**
254    * Macintosh encoding ids. These are used in a number of places within the
255    * font whenever character encodings need to be specified.
256    *
257    * @see NameTable
258    * @see CMapTable
259    */
260   public enum MacintoshEncodingId {
261     // Macintosh Platform Encodings
262     Unknown(-1),
263     Roman(0),
264     Japanese(1),
265     ChineseTraditional(2),
266     Korean(3),
267     Arabic(4),
268     Hebrew(5),
269     Greek(6),
270     Russian(7),
271     RSymbol(8),
272     Devanagari(9),
273     Gurmukhi(10),
274     Gujarati(11),
275     Oriya(12),
276     Bengali(13),
277     Tamil(14),
278     Telugu(15),
279     Kannada(16),
280     Malayalam(17),
281     Sinhalese(18),
282     Burmese(19),
283     Khmer(20),
284     Thai(21),
285     Laotian(22),
286     Georgian(23),
287     Armenian(24),
288     ChineseSimplified(25),
289     Tibetan(26),
290     Mongolian(27),
291     Geez(28),
292     Slavic(29),
293     Vietnamese(30),
294     Sindhi(31),
295     Uninterpreted(32);
296 
297     private final int value;
298 
MacintoshEncodingId(int value)299     private MacintoshEncodingId(int value) {
300       this.value = value;
301     }
302 
value()303     public int value() {
304       return this.value;
305     }
306 
equals(int value)307     public boolean equals(int value) {
308       return value == this.value;
309     }
310 
valueOf(int value)311     public static MacintoshEncodingId valueOf(int value) {
312       for (MacintoshEncodingId encoding : MacintoshEncodingId.values()) {
313         if (encoding.equals(value)) {
314           return encoding;
315         }
316       }
317       return Unknown;
318     }
319   }
320 
321   public static final int SFNTVERSION_1 = Fixed1616.fixed(1, 0);
322 
323   private final int sfntVersion;
324   private final byte[] digest;
325   private long checksum;
326 
327   private Map<Integer, ? extends Table> tables; // these get set in the builder
328 
329   /**
330    * Constructor.
331    *
332    * @param sfntVersion the sfnt version
333    * @param digest the computed digest for the font; null if digest was not
334    *        computed
335    */
Font(int sfntVersion, byte[] digest)336   private Font(int sfntVersion, byte[] digest) {
337     this.sfntVersion = sfntVersion;
338     this.digest = digest;
339   }
340 
341   /**
342    * Gets the sfnt version set in the sfnt wrapper of the font.
343    *
344    * @return the sfnt version
345    */
sfntVersion()346   public int sfntVersion() {
347     return this.sfntVersion;
348   }
349 
350   /**
351    * Gets a copy of the fonts digest that was created when the font was read. If
352    * no digest was set at creation time then the return result will be null.
353    *
354    * @return a copy of the digest array or <code>null</code> if one wasn't set
355    *         at creation time
356    */
digest()357   public byte[] digest() {
358     if (this.digest == null) {
359       return null;
360     }
361     return Arrays.copyOf(this.digest, this.digest.length);
362   }
363 
364   /**
365    * Get the checksum for this font.
366    *
367    * @return the font checksum
368    */
checksum()369   public long checksum() {
370     return this.checksum;
371   }
372 
373   /**
374    * Get the number of tables in this font.
375    *
376    * @return the number of tables
377    */
numTables()378   public int numTables() {
379     return this.tables.size();
380   }
381 
382   /**
383    * Get an iterator over all the tables in the font.
384    *
385    * @return a table iterator
386    */
iterator()387   public Iterator<? extends Table> iterator() {
388     return this.tables.values().iterator();
389   }
390 
391   /**
392    * Does the font have a particular table.
393    *
394    * @param tag the table identifier
395    * @return true if the table is in the font; false otherwise
396    */
hasTable(int tag)397   public boolean hasTable(int tag) {
398     return this.tables.containsKey(tag);
399   }
400 
401   /**
402    * Get the table in this font with the specified id.
403    *
404    * @param <T> the type of the table
405    * @param tag the identifier of the table
406    * @return the table specified if it exists; null otherwise
407    */
408   @SuppressWarnings("unchecked")
getTable(int tag)409   public <T extends Table> T getTable(int tag) {
410     return (T) this.tables.get(tag);
411   }
412 
413   /**
414    * Get a map of the tables in this font accessed by table tag.
415    *
416    * @return an unmodifiable view of the tables in this font
417    */
tableMap()418   public Map<Integer, ? extends Table> tableMap() {
419     return Collections.unmodifiableMap(this.tables);
420   }
421 
422   @Override
toString()423   public String toString() {
424     StringBuilder sb = new StringBuilder();
425     sb.append("digest = ");
426     byte[] digest = this.digest();
427     if (digest != null) {
428       for (int i = 0; i < digest.length; i++) {
429         int d = 0xff & digest[i];
430         if (d < 0x10) {
431           sb.append("0");
432         }
433         sb.append(Integer.toHexString(d));
434       }
435     }
436     sb.append("\n[");
437     sb.append(Fixed1616.toString(sfntVersion));
438     sb.append(", ");
439     sb.append(this.numTables());
440     sb.append("]\n");
441     Iterator<? extends Table> iter = this.iterator();
442     while (iter.hasNext()) {
443       FontDataTable table = iter.next();
444       sb.append("\t");
445       sb.append(table);
446       sb.append("\n");
447     }
448     return sb.toString();
449   }
450 
451   /**
452    * Serialize the font to the output stream.
453    *
454    * @param os the destination for the font serialization
455    * @param tableOrdering the table ordering to apply
456    * @throws IOException
457    */
serialize(OutputStream os, List<Integer> tableOrdering)458   void serialize(OutputStream os, List<Integer> tableOrdering) throws IOException {
459     List<Integer> finalTableOrdering = this.generateTableOrdering(tableOrdering);
460     List<Header> tableRecords = buildTableHeadersForSerialization(finalTableOrdering);
461     FontOutputStream fos = new FontOutputStream(os);
462     this.serializeHeader(fos, tableRecords);
463     this.serializeTables(fos, tableRecords);
464   }
465 
466   /**
467    * Build the table headers to be used for serialization. These headers will be
468    * filled out with the data required for serialization. The headers will be
469    * sorted in the order specified and only those specified will have headers
470    * generated.
471    *
472    * @param tableOrdering the tables to generate headers for and the order to
473    *        sort them
474    * @return a list of table headers ready for serialization
475    */
buildTableHeadersForSerialization(List<Integer> tableOrdering)476   private List<Header> buildTableHeadersForSerialization(List<Integer> tableOrdering) {
477     List<Integer> finalTableOrdering = this.generateTableOrdering(tableOrdering);
478 
479     List<Header> tableHeaders = new ArrayList<Header>(this.numTables());
480     int tableOffset =
481         Offset.tableRecordBegin.offset + this.numTables() * Offset.tableRecordSize.offset;
482     for (Integer tag : finalTableOrdering) {
483       Table table = this.tables.get(tag);
484       if (table != null) {
485         tableHeaders.add(new Header(
486             tag, table.calculatedChecksum(), tableOffset, table.header().length()));
487         // write on boundary of 4 bytes
488         tableOffset += (table.dataLength() + 3) & ~3;
489       }
490     }
491     return tableHeaders;
492   }
493 
494   /**
495    * Searialize the headers.
496    *
497    * @param fos the destination stream for the headers
498    * @param tableHeaders the headers to serialize
499    * @throws IOException
500    */
serializeHeader(FontOutputStream fos, List<Header> tableHeaders)501   private void serializeHeader(FontOutputStream fos, List<Header> tableHeaders)
502       throws IOException {
503     fos.writeFixed(this.sfntVersion);
504     fos.writeUShort(tableHeaders.size());
505     int log2OfMaxPowerOf2 = FontMath.log2(tableHeaders.size());
506     int searchRange = 2 << (log2OfMaxPowerOf2 - 1 + 4);
507     fos.writeUShort(searchRange);
508     fos.writeUShort(log2OfMaxPowerOf2);
509     fos.writeUShort((tableHeaders.size() * 16) - searchRange);
510 
511     List<Header> sortedHeaders = new ArrayList<Header>(tableHeaders);
512     Collections.sort(sortedHeaders, Header.COMPARATOR_BY_TAG);
513 
514     for (Header record : sortedHeaders) {
515       fos.writeULong(record.tag());
516       fos.writeULong(record.checksum());
517       fos.writeULong(record.offset());
518       fos.writeULong(record.length());
519     }
520   }
521 
522   /**
523    * Serialize the tables.
524    *
525    * @param fos the destination stream for the headers
526    * @param tableHeaders the headers for the tables to serialize
527    * @throws IOException
528    */
serializeTables(FontOutputStream fos, List<Header> tableHeaders)529   private void serializeTables(FontOutputStream fos, List<Header> tableHeaders)
530       throws IOException {
531 
532     for (Header record : tableHeaders) {
533       Table table = this.getTable(record.tag());
534       if (table == null) {
535         throw new IOException("Table out of sync with font header.");
536       }
537       int tableSize = table.serialize(fos);
538       int fillerSize = ((tableSize + 3) & ~3) - tableSize;
539       for (int i = 0; i < fillerSize; i++) {
540         fos.write(0);
541       }
542     }
543   }
544 
545   /**
546    * Generate the full table ordering to used for serialization. The full
547    * ordering uses the partial ordering as a seed and then adds all remaining
548    * tables in the font in an undefined order.
549    *
550    * @param defaultTableOrdering the partial ordering to be used as a seed for
551    *        the full ordering
552    * @return the full ordering for serialization
553    */
generateTableOrdering(List<Integer> defaultTableOrdering)554   private List<Integer> generateTableOrdering(List<Integer> defaultTableOrdering) {
555     List<Integer> tableOrdering = new ArrayList<Integer>(this.tables.size());
556     if (defaultTableOrdering == null) {
557       defaultTableOrdering = defaultTableOrdering();
558     }
559 
560     Set<Integer> tablesInFont = new TreeSet<Integer>(this.tables.keySet());
561 
562     // add all the default ordering
563     for (Integer tag : defaultTableOrdering) {
564       if (this.hasTable(tag)) {
565         tableOrdering.add(tag);
566         tablesInFont.remove(tag);
567       }
568     }
569 
570     // add all the rest
571     for (Integer tag : tablesInFont) {
572       tableOrdering.add(tag);
573     }
574 
575     return tableOrdering;
576   }
577 
578   /**
579    * Get the default table ordering based on the type of the font.
580    *
581    * @return the default table ordering
582    */
defaultTableOrdering()583   private List<Integer> defaultTableOrdering() {
584     if (this.hasTable(Tag.CFF)) {
585       return Font.CFF_TABLE_ORDERING;
586     }
587     return Font.TRUE_TYPE_TABLE_ORDERING;
588   }
589 
590   /**
591    * A builder for a font object. The builder allows the for the creation of
592    * immutable {@link Font} objects. The builder is a one use non-thread safe
593    * object and cnce the {@link Font} object has been created it is no longer
594    * usable. To create a further {@link Font} object new builder will be
595    * required.
596    *
597    * @author Stuart Gill
598    *
599    */
600   public static final class Builder {
601 
602     private Map<Integer, Table.Builder<? extends Table>> tableBuilders;
603     private FontFactory factory;
604     private int sfntVersion = SFNTVERSION_1;
605     private int numTables;
606     @SuppressWarnings("unused")
607     private int searchRange;
608     @SuppressWarnings("unused")
609     private int entrySelector;
610     @SuppressWarnings("unused")
611     private int rangeShift;
612     private Map<Header, WritableFontData> dataBlocks;
613     private byte[] digest;
614 
Builder(FontFactory factory)615     private Builder(FontFactory factory) {
616       this.factory = factory;
617       this.tableBuilders = new HashMap<Integer, Table.Builder<? extends Table>>();
618     }
619 
loadFont(InputStream is)620     private void loadFont(InputStream is) throws IOException {
621       if (is == null) {
622         throw new IOException("No input stream for font.");
623       }
624       FontInputStream fontIS = null;
625       try {
626         fontIS = new FontInputStream(is);
627         SortedSet<Header> records = readHeader(fontIS);
628         this.dataBlocks = loadTableData(records, fontIS);
629         this.tableBuilders = buildAllTableBuilders(this.dataBlocks);
630       } finally {
631         fontIS.close();
632       }
633     }
634 
loadFont(WritableFontData wfd, int offsetToOffsetTable)635     private void loadFont(WritableFontData wfd, int offsetToOffsetTable) throws IOException {
636       if (wfd == null) {
637         throw new IOException("No data for font.");
638       }
639       SortedSet<Header> records = readHeader(wfd, offsetToOffsetTable);
640       this.dataBlocks = loadTableData(records, wfd);
641       this.tableBuilders = buildAllTableBuilders(this.dataBlocks);
642     }
643 
644     static final Builder
getOTFBuilder(FontFactory factory, InputStream is)645     getOTFBuilder(FontFactory factory, InputStream is) throws IOException {
646       Builder builder = new Builder(factory);
647       builder.loadFont(is);
648       return builder;
649     }
650 
getOTFBuilder( FontFactory factory, WritableFontData wfd, int offsetToOffsetTable)651     static final Builder getOTFBuilder(
652         FontFactory factory, WritableFontData wfd, int offsetToOffsetTable) throws IOException {
653       Builder builder = new Builder(factory);
654       builder.loadFont(wfd, offsetToOffsetTable);
655       return builder;
656     }
657 
getOTFBuilder(FontFactory factory)658     static final Builder getOTFBuilder(FontFactory factory) {
659       return new Builder(factory);
660     }
661 
662     /**
663      * Get the font factory that created this font builder.
664      *
665      * @return the font factory
666      */
getFontFactory()667     public FontFactory getFontFactory() {
668       return this.factory;
669     }
670 
671     /**
672      * Is the font ready to build?
673      *
674      * @return true if ready to build; false otherwise
675      */
readyToBuild()676     public boolean readyToBuild() {
677       // just read in data with no manipulation
678       if (this.tableBuilders == null && this.dataBlocks != null && this.dataBlocks.size() > 0) {
679         return true;
680       }
681 
682       for (Table.Builder<? extends Table> tableBuilder : this.tableBuilders.values()) {
683         if (tableBuilder.readyToBuild() == false) {
684           return false;
685         }
686       }
687       return true;
688     }
689 
690     /**
691      * Build the {@link Font}. After this call this builder will no longer be
692      * usable.
693      *
694      * @return a {@link Font}
695      */
build()696     public Font build() {
697       Map<Integer, ? extends Table> tables = null;
698 
699       Font font = new Font(this.sfntVersion, this.digest);
700 
701       if (this.tableBuilders.size() > 0) {
702         tables = buildTablesFromBuilders(font, this.tableBuilders);
703       }
704       font.tables = tables;
705       this.tableBuilders = null;
706       this.dataBlocks = null;
707       return font;
708     }
709 
710     /**
711      * Set a unique fingerprint for the font object.
712      *
713      * @param digest a unique identifier for the font
714      */
setDigest(byte[] digest)715     public void setDigest(byte[] digest) {
716       this.digest = digest;
717     }
718 
719     /**
720      * Clear all table builders.
721      */
clearTableBuilders()722     public void clearTableBuilders() {
723       this.tableBuilders.clear();
724     }
725 
726     /**
727      * Does this font builder have the specified table builder.
728      *
729      * @param tag the table builder tag
730      * @return true if there is a builder for that table; false otherwise
731      */
hasTableBuilder(int tag)732     public boolean hasTableBuilder(int tag) {
733       return this.tableBuilders.containsKey(tag);
734     }
735 
736     /**
737      * Get the table builder for the given tag. If there is no builder for that
738      * tag then return a null.
739      *
740      * @param tag the table builder tag
741      * @return the builder for the tag; null if there is no builder for that tag
742      */
getTableBuilder(int tag)743     public Table.Builder<? extends Table> getTableBuilder(int tag) {
744       Table.Builder<? extends Table> builder = this.tableBuilders.get(tag);
745       return builder;
746     }
747 
748     /**
749      * Creates a new empty table builder for the table type given by the table
750      * id tag.
751      *
752      *  This new table will be added to the font and will replace any existing
753      * builder for that table.
754      *
755      * @param tag
756      * @return new empty table of the type specified by tag; if tag is not known
757      *         then a generic OpenTypeTable is returned
758      */
newTableBuilder(int tag)759     public Table.Builder<? extends Table> newTableBuilder(int tag) {
760       Header header = new Header(tag);
761       Table.Builder<? extends Table> builder = Table.Builder.getBuilder(header, null);
762       this.tableBuilders.put(header.tag(), builder);
763 
764       return builder;
765     }
766 
767     /**
768      * Creates a new table builder for the table type given by the table id tag.
769      * It makes a copy of the data provided and uses that copy for the table.
770      *
771      *  This new table has been added to the font and will replace any existing
772      * builder for that table.
773      *
774      * @param tag
775      * @param srcData
776      * @return new empty table of the type specified by tag; if tag is not known
777      *         then a generic OpenTypeTable is returned
778      */
newTableBuilder(int tag, ReadableFontData srcData)779     public Table.Builder<? extends Table> newTableBuilder(int tag, ReadableFontData srcData) {
780       WritableFontData data;
781       data = WritableFontData.createWritableFontData(srcData.length());
782       // TODO(stuartg): take over original data instead?
783       srcData.copyTo(data);
784 
785       Header header = new Header(tag, data.length());
786       Table.Builder<? extends Table> builder = Table.Builder.getBuilder(header, data);
787 
788       this.tableBuilders.put(tag, builder);
789 
790       return builder;
791     }
792 
793     /**
794      * Get a map of the table builders in this font builder accessed by table
795      * tag.
796      *
797      * @return an unmodifiable view of the table builders in this font builder
798      */
tableBuilderMap()799     public Map<Integer, Table.Builder<? extends Table>> tableBuilderMap() {
800       return Collections.unmodifiableMap(this.tableBuilders);
801     }
802 
803     /**
804      * Remove the specified table builder from the font builder.
805      *
806      * @param tag the table builder to remove
807      * @return the table builder removed
808      */
removeTableBuilder(int tag)809     public Table.Builder<? extends Table> removeTableBuilder(int tag) {
810       return this.tableBuilders.remove(tag);
811     }
812 
813     /**
814      * Get the number of table builders in the font builder.
815      *
816      * @return the number of table builders
817      */
tableBuilderCount()818     public int tableBuilderCount() {
819       return this.tableBuilders.size();
820     }
821 
822     @SuppressWarnings("unused")
sfntWrapperSize()823     private int sfntWrapperSize() {
824       return Offset.sfntHeaderSize.offset +
825       (Offset.tableRecordSize.offset * this.tableBuilders.size());
826     }
827 
buildAllTableBuilders( Map<Header, WritableFontData> tableData)828     private Map<Integer, Table.Builder<? extends Table>> buildAllTableBuilders(
829         Map<Header, WritableFontData> tableData) {
830       Map<Integer, Table.Builder<? extends Table>> builderMap =
831         new HashMap<Integer, Table.Builder<? extends Table>>();
832       Set<Header> records = tableData.keySet();
833       for (Header record : records) {
834         Table.Builder<? extends Table> builder = getTableBuilder(record, tableData.get(record));
835         builderMap.put(record.tag(), builder);
836       }
837       interRelateBuilders(builderMap);
838       return builderMap;
839     }
840 
getTableBuilder(Header header, WritableFontData data)841     private Table.Builder<? extends Table> getTableBuilder(Header header, WritableFontData data) {
842       Table.Builder<? extends Table> builder = Table.Builder.getBuilder(header, data);
843       return builder;
844     }
845 
buildTablesFromBuilders(Font font, Map<Integer, Table.Builder<? extends Table>> builderMap)846     private static Map<Integer, Table> buildTablesFromBuilders(Font font,
847         Map<Integer, Table.Builder<? extends Table>> builderMap) {
848       Map<Integer, Table> tableMap = new TreeMap<Integer, Table>();
849 
850       interRelateBuilders(builderMap);
851 
852       long fontChecksum = 0;
853       boolean tablesChanged = false;
854       FontHeaderTable.Builder headerTableBuilder = null;
855 
856       // now build all the tables
857       for (Table.Builder<? extends Table> builder : builderMap.values()) {
858         Table table = null;
859         if (Tag.isHeaderTable(builder.header().tag())) {
860           headerTableBuilder = (FontHeaderTable.Builder) builder;
861           continue;
862         }
863         if (builder.readyToBuild()) {
864           tablesChanged |= builder.changed();
865           table = builder.build();
866         }
867         if (table == null) {
868           throw new RuntimeException("Unable to build table - " + builder);
869         }
870         long tableChecksum = table.calculatedChecksum();
871         fontChecksum += tableChecksum;
872         tableMap.put(table.header().tag(), table);
873       }
874 
875       // now fix up the header table
876       Table headerTable = null;
877       if (headerTableBuilder != null) {
878         if (tablesChanged) {
879           headerTableBuilder.setFontChecksum(fontChecksum);
880         }
881         if (headerTableBuilder.readyToBuild()) {
882           tablesChanged |= headerTableBuilder.changed();
883           headerTable = headerTableBuilder.build();
884         }
885         if (headerTable == null) {
886           throw new RuntimeException("Unable to build table - " + headerTableBuilder);
887         }
888         fontChecksum += headerTable.calculatedChecksum();
889         tableMap.put(headerTable.header().tag(), headerTable);
890       }
891 
892       font.checksum = fontChecksum & 0xffffffffL;
893       return tableMap;
894     }
895 
896     private static void
interRelateBuilders(Map<Integer, Table.Builder<? extends Table>> builderMap)897     interRelateBuilders(Map<Integer, Table.Builder<? extends Table>> builderMap) {
898       FontHeaderTable.Builder headerTableBuilder =
899         (FontHeaderTable.Builder) builderMap.get(Tag.head);
900       HorizontalHeaderTable.Builder horizontalHeaderBuilder =
901         (HorizontalHeaderTable.Builder) builderMap.get(Tag.hhea);
902       MaximumProfileTable.Builder maxProfileBuilder =
903         (MaximumProfileTable.Builder) builderMap.get(Tag.maxp);
904       LocaTable.Builder locaTableBuilder =
905         (LocaTable.Builder) builderMap.get(Tag.loca);
906       HorizontalMetricsTable.Builder horizontalMetricsBuilder =
907         (HorizontalMetricsTable.Builder) builderMap.get(Tag.hmtx);
908       HorizontalDeviceMetricsTable.Builder hdmxTableBuilder =
909         (HorizontalDeviceMetricsTable.Builder) builderMap.get(Tag.hdmx);
910 
911       // set the inter table data required to build certain tables
912       if (horizontalMetricsBuilder != null) {
913         if (maxProfileBuilder != null) {
914           horizontalMetricsBuilder.setNumGlyphs(maxProfileBuilder.numGlyphs());
915         }
916         if (horizontalHeaderBuilder != null) {
917           horizontalMetricsBuilder.setNumberOfHMetrics(
918               horizontalHeaderBuilder.numberOfHMetrics());
919         }
920       }
921 
922       if (locaTableBuilder != null) {
923         if (maxProfileBuilder != null) {
924           locaTableBuilder.setNumGlyphs(maxProfileBuilder.numGlyphs());
925         }
926         if (headerTableBuilder != null) {
927           locaTableBuilder.setFormatVersion(headerTableBuilder.indexToLocFormat());
928         }
929       }
930 
931       if (hdmxTableBuilder != null) {
932         if (maxProfileBuilder != null) {
933           hdmxTableBuilder.setNumGlyphs(maxProfileBuilder.numGlyphs());
934         }
935       }
936     }
937 
readHeader(FontInputStream is)938     private SortedSet<Header> readHeader(FontInputStream is) throws IOException {
939       SortedSet<Header> records =
940           new TreeSet<Header>(Header.COMPARATOR_BY_OFFSET);
941 
942       this.sfntVersion = is.readFixed();
943       this.numTables = is.readUShort();
944       this.searchRange = is.readUShort();
945       this.entrySelector = is.readUShort();
946       this.rangeShift = is.readUShort();
947 
948       for (int tableNumber = 0; tableNumber < this.numTables; tableNumber++) {
949         Header table = new Header(is.readULongAsInt(), // safe since the tag is ASCII
950             is.readULong(),         // checksum
951             is.readULongAsInt(),    // offset
952             is.readULongAsInt());   // length
953         records.add(table);
954       }
955       return records;
956     }
957 
loadTableData( SortedSet<Header> headers, FontInputStream is)958     private Map<Header, WritableFontData> loadTableData(
959         SortedSet<Header> headers, FontInputStream is) throws IOException {
960       Map<Header, WritableFontData> tableData =
961           new HashMap<Header, WritableFontData>(headers.size());
962       logger.fine("########  Reading Table Data");
963       for (Header tableHeader : headers) {
964         is.skip(tableHeader.offset() - is.position());
965         logger.finer("\t" + tableHeader);
966         logger.finest("\t\tStream Position = " + Integer.toHexString((int) is.position()));
967         // don't close this or the whole stream is gone
968         FontInputStream tableIS = new FontInputStream(is, tableHeader.length());
969         // TODO(stuartg): start tracking bad tables and other errors
970         WritableFontData data = WritableFontData.createWritableFontData(tableHeader.length());
971         data.copyFrom(tableIS, tableHeader.length());
972         tableData.put(tableHeader, data);
973       }
974       return tableData;
975     }
976 
readHeader(ReadableFontData fd, int offset)977     private SortedSet<Header> readHeader(ReadableFontData fd, int offset) {
978       SortedSet<Header> records =
979           new TreeSet<Header>(Header.COMPARATOR_BY_OFFSET);
980 
981       this.sfntVersion = fd.readFixed(offset + Offset.sfntVersion.offset);
982       this.numTables = fd.readUShort(offset + Offset.numTables.offset);
983       this.searchRange = fd.readUShort(offset + Offset.searchRange.offset);
984       this.entrySelector = fd.readUShort(offset + Offset.entrySelector.offset);
985       this.rangeShift = fd.readUShort(offset + Offset.rangeShift.offset);
986 
987       int tableOffset = offset + Offset.tableRecordBegin.offset;
988       for (int tableNumber = 0;
989       tableNumber < this.numTables;
990       tableNumber++, tableOffset += Offset.tableRecordSize.offset) {
991         Header table =
992         // safe since the tag is ASCII
993             new Header(fd.readULongAsInt(tableOffset + Offset.tableTag.offset),
994                 fd.readULong(tableOffset + Offset.tableCheckSum.offset), // checksum
995             fd.readULongAsInt(tableOffset + Offset.tableOffset.offset), // offset
996             fd.readULongAsInt(tableOffset + Offset.tableLength.offset)); // length
997         records.add(table);
998       }
999       return records;
1000     }
1001 
loadTableData( SortedSet<Header> headers, WritableFontData fd)1002     private Map<Header, WritableFontData> loadTableData(
1003         SortedSet<Header> headers, WritableFontData fd) {
1004       Map<Header, WritableFontData> tableData =
1005           new HashMap<Header, WritableFontData>(headers.size());
1006       logger.fine("########  Reading Table Data");
1007       for (Header tableHeader : headers) {
1008         WritableFontData data = fd.slice(tableHeader.offset(), tableHeader.length());
1009         tableData.put(tableHeader, data);
1010       }
1011       return tableData;
1012     }
1013   }
1014 }
1015