• 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.data;
18 
19 import java.io.IOException;
20 import java.io.OutputStream;
21 import java.math.BigDecimal;
22 import java.util.Arrays;
23 import java.util.Date;
24 
25 
26 /**
27  * Writable font data wrapper. Supports reading of data primitives in the
28  * TrueType / OpenType spec.
29  *
30  * <p>The data types used are as listed:
31  * <table>
32  * <table>
33  * <tr>
34  * <td>BYTE</td>
35  * <td>8-bit unsigned integer.</td>
36  * </tr>
37  * <tr>
38  * <td>CHAR</td>
39  * <td>8-bit signed integer.</td>
40  * </tr>
41  * <tr>
42  * <td>USHORT</td>
43  * <td>16-bit unsigned integer.</td>
44  * </tr>
45  * <tr>
46  * <td>SHORT</td>
47  * <td>16-bit signed integer.</td>
48  * </tr>
49  * <tr>
50  * <td>UINT24</td>
51  * <td>24-bit unsigned integer.</td>
52  * </tr>
53  * <tr>
54  * <td>ULONG</td>
55  * <td>32-bit unsigned integer.</td>
56  * </tr>
57  * <tr>
58  * <td>LONG</td>
59  * <td>32-bit signed integer.</td>
60  * </tr>
61  * <tr>
62  * <td>Fixed</td>
63  * <td>32-bit signed fixed-point number (16.16)</td>
64  * </tr>
65  * <tr>
66  * <td>FUNIT</td>
67  * <td>Smallest measurable distance in the em space.</td>
68  * </tr>
69  * <tr>
70  * <td>FWORD</td>
71  * <td>16-bit signed integer (SHORT) that describes a quantity in FUnits.</td>
72  * </tr>
73  * <tr>
74  * <td>UFWORD</td>
75  * <td>16-bit unsigned integer (USHORT) that describes a quantity in FUnits.
76  * </td>
77  * </tr>
78  * <tr>
79  * <td>F2DOT14</td>
80  * <td>16-bit signed fixed number with the low 14 bits of fraction (2.14).</td>
81  * </tr>
82  * <tr>
83  * <td>LONGDATETIME</td>
84  * <td>Date represented in number of seconds since 12:00 midnight, January 1,
85  * 1904. The value is represented as a signed 64-bit integer.</td>
86  * </tr>
87  * </table>
88  *
89  * @author Stuart Gill
90  * @see WritableFontData
91  */
92 public class ReadableFontData extends FontData {
93 
createReadableFontData(byte[] b)94   public static ReadableFontData createReadableFontData(byte[] b) {
95     ByteArray<?> ba = new MemoryByteArray(b);
96     return new ReadableFontData(ba);
97   }
98 
99 
100   /**
101    * Flag on whether the checksum has been set.
102    */
103   private volatile boolean checksumSet = false;
104   /**
105    * Lock on all operations that will affect the value of the checksum.
106    */
107   private final Object checksumLock = new Object();
108   private volatile long checksum;
109   private volatile int[] checksumRange;
110 
111   /**
112    * Constructor.
113    *
114    * @param array byte array to wrap
115    */
ReadableFontData(ByteArray<? extends ByteArray<?>> array)116   protected ReadableFontData(ByteArray<? extends ByteArray<?>> array) {
117     super(array);
118   }
119 
120   /**
121    * Constructor. Creates a bounded wrapper of another ReadableFontData from the
122    * given offset until the end of the original ReadableFontData.
123    *
124    * @param data data to wrap
125    * @param offset the start of this data's view of the original data
126    */
ReadableFontData(ReadableFontData data, int offset)127   protected ReadableFontData(ReadableFontData data, int offset) {
128     super(data, offset);
129   }
130 
131   /**
132    * Constructor. Creates a bounded wrapper of another ReadableFontData from the
133    * given offset until the end of the original ReadableFontData.
134    *
135    * @param data data to wrap
136    * @param offset the start of this data's view of the original data
137    * @param length the length of the other FontData to use
138    */
ReadableFontData(ReadableFontData data, int offset, int length)139   protected ReadableFontData(ReadableFontData data, int offset, int length) {
140     super(data, offset, length);
141   }
142 
143   /**
144    * Makes a slice of this FontData. The returned slice will share the data with
145    * the original <code>FontData</code>.
146    *
147    * @param offset the start of the slice
148    * @param length the number of bytes in the slice
149    * @return a slice of the original FontData
150    */
151   @Override
slice(int offset, int length)152   public ReadableFontData slice(int offset, int length) {
153     if (offset < 0 || length < 0 || offset > Integer.MAX_VALUE - length ||
154         (offset + length) > this.size()) {
155       throw new IndexOutOfBoundsException("Attempt to bind data outside of its limits.");
156     }
157     ReadableFontData slice = new ReadableFontData(this, offset, length);
158     return slice;
159   }
160 
161   /**
162    * Makes a bottom bound only slice of this array. The returned slice will
163    * share the data with the original <code>FontData</code>.
164    *
165    * @param offset the start of the slice
166    * @return a slice of the original FontData
167    */
168   @Override
slice(int offset)169   public ReadableFontData slice(int offset) {
170     if (offset < 0 || offset > this.size()) {
171       throw new IndexOutOfBoundsException("Attempt to bind data outside of its limits.");
172     }
173     ReadableFontData slice = new ReadableFontData(this, offset);
174     return slice;
175   }
176 
177   /**
178    * Generates a String representation of the object with a certain number of
179    * data bytes.
180    *
181    * @param length number of bytes of the data to include in the String
182    * @return String representation of the object
183    */
toString(int length)184   public String toString(int length) {
185     StringBuilder sb = new StringBuilder();
186     sb.append("[l=" + this.length() + ", cs=" + this.checksum() + "]\n");
187     sb.append(this.array.toString(this.boundOffset(0), this.boundLength(0, length)));
188     return sb.toString();
189   }
190 
191   @Override
toString()192   public String toString() {
193     return toString(0);
194   }
195 
196   /**
197    * Gets a computed checksum for the data. This checksum uses the OpenType spec
198    * calculation. Every ULong value (32 bit unsigned) in the data is summed and
199    * the resulting value is truncated to 32 bits. If the data length in bytes is
200    * not an integral multiple of 4 then any remaining bytes are treated as the
201    * start of a 4 byte sequence whose remaining bytes are zero.
202    *
203    * @return the checksum
204    */
checksum()205   public long checksum() {
206     if (!this.checksumSet) {
207       computeChecksum();
208     }
209     return this.checksum;
210   }
211 
212   /**
213    * Computes the checksum for the font data using any ranges set for the
214    * calculation. Updates the internal state of this object in a threadsafe way.
215    */
computeChecksum()216   private void computeChecksum() {
217     synchronized (this.checksumLock) {
218       if (this.checksumSet) {
219         // another thread computed the checksum while were waiting to do so
220         return;
221       }
222       long sum = 0;
223       if (this.checksumRange == null) {
224         sum = computeCheckSum(0, this.length());
225       } else {
226         for (int lowBoundIndex = 0; lowBoundIndex < this.checksumRange.length; lowBoundIndex += 2) {
227           int lowBound = this.checksumRange[lowBoundIndex];
228           int highBound =
229               (lowBoundIndex == this.checksumRange.length - 1) ? this.length() : this.checksumRange[
230                   lowBoundIndex + 1];
231           sum += computeCheckSum(lowBound, highBound);
232         }
233       }
234       this.checksum = sum & 0xffffffffL;
235       this.checksumSet = true;
236     }
237   }
238 
239   /**
240    * Do the actual computation of the checksum for a range using the
241    * TrueType/OpenType checksum algorithm. The range used is from the low bound
242    * to the high bound in steps of four bytes. If any of the bytes within that 4
243    * byte segment are not readable then it will considered a zero for
244    * calculation.
245    *
246    * <p>Only called from within a synchronized method so it does not need to be
247    * synchronized itself.
248    *
249    * @param lowBound first position to start a 4 byte segment on
250    * @param highBound last possible position to start a 4 byte segment on
251    * @return the checksum for the total range
252    */
computeCheckSum(int lowBound, int highBound)253   private long computeCheckSum(int lowBound, int highBound) {
254     long sum = 0;
255     // checksum all whole 4-byte chunks
256     for (int i = lowBound; i <= highBound - 4; i += 4) {
257       sum += this.readULong(i);
258     }
259     // add last fragment if not 4-byte multiple
260     int off = highBound & -4;
261     if (off < highBound) {
262       int b3 = this.readUByte(off);
263       int b2 = (off + 1 < highBound) ? this.readUByte(off + 1) : 0;
264       int b1 = (off + 2 < highBound) ? this.readUByte(off + 2) : 0;
265       int b0 = 0;
266       sum += (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
267     }
268     return sum;
269   }
270 
271   /**
272    * Sets the ranges to use for computing the checksum. These ranges are in
273    * begin and end pairs. If an odd number is given then the final range is
274    * assumed to extend to the end of the data. The lengths of each range must be
275    * a multiple of 4.
276    *
277    * @param ranges the range bounds to use for the checksum
278    */
setCheckSumRanges(int... ranges)279   public void setCheckSumRanges(int... ranges) {
280     synchronized (this.checksumLock) {
281       if (ranges != null && ranges.length > 0) {
282         this.checksumRange = Arrays.copyOf(ranges, ranges.length);
283       } else {
284         this.checksumRange = null;
285       }
286       this.checksumSet = false;
287     }
288   }
289 
290   /**
291    * Gets the ranges that are used for computing the checksum. These ranges are in
292    * begin and end pairs. If an odd number is given then the final range is
293    * assumed to extend to the end of the data. The lengths of each range must be
294    * a multiple of 4.
295    *
296    * @return the range bounds used for the checksum
297    */
checkSumRange()298   public int[] checkSumRange() {
299     synchronized (this.checksumLock) {
300       if (this.checksumRange != null && checksumRange.length > 0) {
301         return Arrays.copyOf(this.checksumRange, this.checksumRange.length);
302       }
303       return new int[0];
304     }
305   }
306 
307   /**
308    * Reads the UBYTE at the given index.
309    *
310    * @param index index into the font data
311    * @return the UBYTE; -1 if outside the bounds of the font data
312    * @throws IndexOutOfBoundsException if index is outside the FontData's range
313    */
readUByte(int index)314   public int readUByte(int index) {
315     if (!this.boundsCheck(index, 1)) {
316       throw new IndexOutOfBoundsException(
317           "Index attempted to be read from is out of bounds: " + Integer.toHexString(index));
318     }
319     int b = this.array.get(this.boundOffset(index));
320     if (b < 0) {
321       throw new IndexOutOfBoundsException(
322           "Index attempted to be read from is out of bounds: " + Integer.toHexString(index));
323     }
324     return b;
325     // return this.array.get(this.boundOffset(index));
326   }
327 
328   /**
329    * Reads the BYTE at the given index.
330    *
331    * @param index index into the font data
332    * @return the BYTE
333    * @throws IndexOutOfBoundsException if index is outside the FontData's range
334    */
readByte(int index)335   public int readByte(int index) {
336     if (!this.boundsCheck(index, 1)) {
337       throw new IndexOutOfBoundsException(
338           "Index attempted to be read from is out of bounds: " + Integer.toHexString(index));
339     }
340     int b = this.array.get(this.boundOffset(index));
341     if (b < 0) {
342       throw new IndexOutOfBoundsException(
343           "Index attempted to be read from is out of bounds: " + Integer.toHexString(index));
344     }
345     return (b << 24) >> 24;
346     // return (this.array.get(this.boundOffset(index)) << 24) >> 24;
347   }
348 
349   /**
350    * Reads the bytes at the given index into the array.
351    *
352    * @param index index into the font data
353    * @param b the destination for the bytes read
354    * @param offset offset in the byte array to place the bytes
355    * @param length the length of bytes to read
356    * @return the number of bytes actually read; -1 if the index is outside the
357    *         bounds of the font data
358    */
readBytes(int index, byte[] b, int offset, int length)359   public int readBytes(int index, byte[] b, int offset, int length) {
360     int bytesRead =
361         this.array.get(this.boundOffset(index), b, offset, this.boundLength(index, length));
362     if (bytesRead < 0) {
363       throw new IndexOutOfBoundsException(
364           "Index attempted to be read from is out of bounds: " + Integer.toHexString(index));
365     }
366     return bytesRead;
367   }
368 
369   /**
370    * Reads the CHAR at the given index.
371    *
372    * @param index index into the font data
373    * @return the CHAR
374    * @throws IndexOutOfBoundsException if index is outside the FontData's range
375    */
readChar(int index)376   public int readChar(int index) {
377     return this.readUByte(index);
378   }
379 
380   /**
381    * Reads the USHORT at the given index.
382    *
383    * @param index index into the font data
384    * @return the USHORT
385    * @throws IndexOutOfBoundsException if index is outside the FontData's range
386    */
readUShort(int index)387   public int readUShort(int index) {
388     return 0xffff & (this.readUByte(index) << 8 | this.readUByte(index + 1));
389   }
390 
391   /**
392    * Reads the SHORT at the given index.
393    *
394    * @param index index into the font data
395    * @return the SHORT
396    * @throws IndexOutOfBoundsException if index is outside the FontData's range
397    */
readShort(int index)398   public int readShort(int index) {
399     return ((this.readByte(index) << 8 | this.readUByte(index + 1)) << 16) >> 16;
400   }
401 
402   /**
403    * Reads the UINT24 at the given index.
404    *
405    * @param index index into the font data
406    * @return the UINT24
407    * @throws IndexOutOfBoundsException if index is outside the FontData's range
408    */
readUInt24(int index)409   public int readUInt24(int index) {
410     return 0xffffff & (this.readUByte(index) << 16 | this.readUByte(index + 1) << 8
411         | this.readUByte(index + 2));
412   }
413 
414   /**
415    * Reads the ULONG at the given index.
416    *
417    * @param index index into the font data
418    * @return the ULONG
419    * @throws IndexOutOfBoundsException if index is outside the FontData's range
420    */
readULong(int index)421   public long readULong(int index) {
422     return 0xffffffffL & (this.readUByte(index) << 24 | this.readUByte(index + 1) << 16
423         | this.readUByte(index + 2) << 8 | this.readUByte(index + 3));
424   }
425 
426   /**
427    * Reads the ULONG at the given index as an int.
428    *
429    * @param index index into the font data
430    * @return the ULONG
431    * @throws IndexOutOfBoundsException if index is outside the FontData's range
432    * @throws ArithmeticException if the value will not fit into an integer
433    */
readULongAsInt(int index)434   public int readULongAsInt(int index) {
435     long ulong = this.readULong(index);
436     if ((ulong & 0x80000000) == 0x80000000) {
437       throw new ArithmeticException("Long value too large to fit into an integer.");
438     }
439     return (int) ulong;
440   }
441 
442   /**
443    * Reads the ULONG at the given index, little-endian variant.
444    *
445    * @param index index into the font data
446    * @return the ULONG
447    * @throws IndexOutOfBoundsException if index is outside the FontData's range
448    */
readULongLE(int index)449   public long readULongLE(int index) {
450     return 0xffffffffL & (this.readUByte(index) | this.readUByte(index + 1) << 8
451         | this.readUByte(index + 2) << 16 | this.readUByte(index + 3) << 24);
452   }
453 
454   /**
455    * Reads the LONG at the given index.
456    *
457    * @param index index into the font data
458    * @return the LONG
459    * @throws IndexOutOfBoundsException if index is outside the FontData's range
460    */
readLong(int index)461   public int readLong(int index) {
462     return this.readByte(index) << 24 | this.readUByte(index + 1) << 16 |
463     this.readUByte(index + 2) << 8 | this.readUByte(index + 3);
464   }
465 
466   /**
467    * Reads the Fixed at the given index.
468    *
469    * @param index index into the font data
470    * @return the Fixed
471    * @throws IndexOutOfBoundsException if index is outside the FontData's range
472    */
readFixed(int index)473   public int readFixed(int index) {
474     return this.readLong(index);
475   }
476 
477   /**
478    * Reads the F2DOT14 at the given index.
479    *
480    * @param index index into the font data
481    * @return the F2DOT14
482    * @throws IndexOutOfBoundsException if index is outside the FontData's range
483    */
readF2Dot14(int index)484   public BigDecimal readF2Dot14(int index) {
485     throw new UnsupportedOperationException();
486   }
487 
488   /**
489    * Reads the LONGDATETIME at the given index.
490    *
491    * @param index index into the font data
492    * @return the LONGDATETIME
493    * @throws IndexOutOfBoundsException if index is outside the FontData's range
494    */
readDateTimeAsLong(int index)495   public long readDateTimeAsLong(int index) {
496     return this.readULong(index) << 32 | this.readULong(index + 4);
497   }
498 
499   /**
500    * Reads the LONGDATETIME at the given index.
501    *
502    * @param index index into the font data
503    * @return the F2DOT14
504    * @throws IndexOutOfBoundsException if index is outside the FontData's range
505    */
readLongDateTime(int index)506   public Date readLongDateTime(int index) {
507     throw new UnsupportedOperationException();
508   }
509 
510   /**
511    * Reads the FUNIT at the given index.
512    *
513    * @param index index into the font data
514    * @return the FUNIT
515    * @throws IndexOutOfBoundsException if index is outside the FontData's range
516    */
readFUnit(int index)517   public int readFUnit(int index) {
518     throw new UnsupportedOperationException();
519   }
520 
521   /**
522    * Reads the FWORD at the given index.
523    *
524    * @param index index into the font data
525    * @return the FWORD
526    * @throws IndexOutOfBoundsException if index is outside the FontData's range
527    */
readFWord(int index)528   public int readFWord(int index) {
529     return this.readShort(index);
530   }
531 
532   /**
533    * Reads the UFWORD at the given index.
534    *
535    * @param index index into the font data
536    * @return the UFWORD
537    * @throws IndexOutOfBoundsException if index is outside the FontData's range
538    */
readUFWord(int index)539   public int readUFWord(int index) {
540     return this.readUShort(index);
541   }
542 
543   /**
544    * Copy the FontData to an OutputStream.
545    *
546    * @param os the destination
547    * @return number of bytes copied
548    * @throws IOException
549    */
copyTo(OutputStream os)550   public int copyTo(OutputStream os) throws IOException {
551     return this.array.copyTo(os, this.boundOffset(0), this.length());
552   }
553 
554   /**
555    * Copies the FontData to a WritableFontData.
556    *
557    * @param wfd the destination
558    * @return number of bytes copied
559    */
copyTo(WritableFontData wfd)560   public int copyTo(WritableFontData wfd) {
561     return this.array.copyTo(wfd.boundOffset(0), wfd.array, this.boundOffset(0), this.length());
562   }
563 
564   /**
565    * Search for the key value in the range tables provided.
566    *
567    *  The search looks through the start-end pairs looking for the key value. It
568    * is assumed that the start-end pairs are both represented by UShort values,
569    * ranges do not overlap, and are monotonically increasing.
570    *
571    * @param startIndex the position to read the first start value from
572    * @param startOffset the offset between subsequent start values
573    * @param endIndex the position to read the first end value from
574    * @param endOffset the offset between subsequent end values
575    * @param length the number of start-end pairs
576    * @param key the value to search for
577    * @return the index of the start-end pairs in which the key was found; -1
578    *         otherwise
579    */
searchUShort(int startIndex, int startOffset, int endIndex, int endOffset, int length, int key)580   public int searchUShort(int startIndex,
581       int startOffset,
582       int endIndex,
583       int endOffset,
584       int length,
585       int key) {
586     int location = 0;
587     int bottom = 0;
588     int top = length;
589     while (top != bottom) {
590       location = (top + bottom) / 2;
591       int locationStart = this.readUShort(startIndex + location * startOffset);
592       if (key < locationStart) {
593         // location is below current location
594         top = location;
595       } else {
596         // is key below the upper bound?
597         int locationEnd = this.readUShort(endIndex + location * endOffset);
598         if (key <= locationEnd) {
599           return location;
600         }
601         // location is above the current location
602         bottom = location + 1;
603       }
604     }
605     return -1;
606   }
607 
608   /**
609    * Search for the key value in the range tables provided.
610    *
611    *  The search looks through the start-end pairs looking for the key value. It
612    * is assumed that the start-end pairs are both represented by ULong values
613    * that can be represented within 31 bits, ranges do not overlap, and are
614    * monotonically increasing.
615    *
616    * @param startIndex the position to read the first start value from
617    * @param startOffset the offset between subsequent start values
618    * @param endIndex the position to read the first end value from
619    * @param endOffset the offset between subsequent end values
620    * @param length the number of start-end pairs
621    * @param key the value to search for
622    * @return the index of the start-end pairs in which the key was found; -1
623    *         otherwise
624    */
searchULong(int startIndex, int startOffset, int endIndex, int endOffset, int length, int key)625   public int searchULong(int startIndex,
626       int startOffset,
627       int endIndex,
628       int endOffset,
629       int length,
630       int key) {
631     int location = 0;
632     int bottom = 0;
633     int top = length;
634     while (top != bottom) {
635       location = (top + bottom) / 2;
636       int locationStart = this.readULongAsInt(startIndex + location * startOffset);
637       if (key < locationStart) {
638         // location is below current location
639         top = location;
640       } else {
641         // is key below the upper bound?
642         int locationEnd = this.readULongAsInt(endIndex + location * endOffset);
643         if (key <= locationEnd) {
644           return location;
645         }
646         // location is above the current location
647         bottom = location + 1;
648       }
649     }
650     return -1;
651   }
652 
653   /**
654    * Search for the key value in the table provided.
655    *
656    *  The search looks through the values looking for the key value. It is
657    * assumed that the are represented by UShort values and are monotonically
658    * increasing.
659    *
660    * @param startIndex the position to read the first start value from
661    * @param startOffset the offset between subsequent start values
662    * @param length the number of start-end pairs
663    * @param key the value to search for
664    * @return the index of the start-end pairs in which the key was found; -1
665    *         otherwise
666    */
searchUShort(int startIndex, int startOffset, int length, int key)667   public int searchUShort(int startIndex, int startOffset, int length, int key) {
668     int location = 0;
669     int bottom = 0;
670     int top = length;
671     while (top != bottom) {
672       location = (top + bottom) / 2;
673       int locationStart = this.readUShort(startIndex + location * startOffset);
674       if (key < locationStart) {
675         // location is below current location
676         top = location;
677       } else if (key > locationStart) {
678         // location is above current location
679         bottom = location + 1;
680       } else {
681         return location;
682       }
683     }
684     return -1;
685   }
686 }
687