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