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.table.core; 18 19 import com.google.typography.font.sfntly.data.ReadableFontData; 20 import com.google.typography.font.sfntly.data.WritableFontData; 21 import com.google.typography.font.sfntly.table.Header; 22 import com.google.typography.font.sfntly.table.Table; 23 import com.google.typography.font.sfntly.table.TableBasedTableBuilder; 24 import com.google.typography.font.sfntly.table.truetype.LocaTable; 25 26 import java.util.EnumSet; 27 28 /** 29 * A Font Header table. 30 * 31 * @author Stuart Gill 32 */ 33 public final class FontHeaderTable extends Table { 34 35 /** 36 * Checksum adjustment base value. To compute the checksum adjustment: 37 * 1) set it to 0; 2) sum the entire font as ULONG, 3) then store 0xB1B0AFBA - sum. 38 */ 39 public static final long CHECKSUM_ADJUSTMENT_BASE = 0xB1B0AFBAL; 40 41 /** 42 * Magic number value stored in the magic number field. 43 */ 44 public static final long MAGIC_NUMBER = 0x5F0F3CF5L; 45 46 /** 47 * The ranges to use for checksum calculation. 48 */ 49 private static final int[] CHECKSUM_RANGES = 50 new int[] {0, Offset.checkSumAdjustment.offset, Offset.magicNumber.offset}; 51 52 /** 53 * Offsets to specific elements in the underlying data. These offsets are relative to the 54 * start of the table or the start of sub-blocks within the table. 55 */ 56 private enum Offset { 57 tableVersion(0), 58 fontRevision(4), 59 checkSumAdjustment(8), 60 magicNumber(12), 61 flags(16), 62 unitsPerEm(18), 63 created(20), 64 modified(28), 65 xMin(36), 66 yMin(38), 67 xMax(40), 68 yMax(42), 69 macStyle(44), 70 lowestRecPPEM(46), 71 fontDirectionHint(48), 72 indexToLocFormat(50), 73 glyphDataFormat(52); 74 75 private final int offset; 76 Offset(int offset)77 private Offset(int offset) { 78 this.offset = offset; 79 } 80 } 81 82 /** 83 * Constructor. 84 * 85 * @param header the table header 86 * @param data the readable data for the table 87 */ FontHeaderTable(Header header, ReadableFontData data)88 private FontHeaderTable(Header header, ReadableFontData data) { 89 super(header, data); 90 data.setCheckSumRanges(0, Offset.checkSumAdjustment.offset, Offset.magicNumber.offset); 91 } 92 93 /** 94 * Get the table version. 95 * 96 * @return the table version 97 */ tableVersion()98 public int tableVersion() { 99 return this.data.readFixed(Offset.tableVersion.offset); 100 } 101 102 /** 103 * Get the font revision. 104 * 105 * @return the font revision 106 */ fontRevision()107 public int fontRevision() { 108 return this.data.readFixed(Offset.fontRevision.offset); 109 } 110 111 /** 112 * Get the checksum adjustment. To compute: set it to 0, sum the entire font 113 * as ULONG, then store 0xB1B0AFBA - sum. 114 * 115 * @return checksum adjustment 116 */ checkSumAdjustment()117 public long checkSumAdjustment() { 118 return this.data.readULong(Offset.checkSumAdjustment.offset); 119 } 120 121 /** 122 * Get the magic number. Set to 0x5F0F3CF5. 123 * 124 * @return the magic number 125 */ magicNumber()126 public long magicNumber() { 127 return this.data.readULong(Offset.magicNumber.offset); 128 } 129 130 /** 131 * Flag values in the font header table. 132 * 133 */ 134 public enum Flags { 135 BaselineAtY0, 136 LeftSidebearingAtX0, 137 InstructionsDependOnPointSize, 138 ForcePPEMToInteger, 139 InstructionsAlterAdvanceWidth, 140 //Apple Flags 141 Apple_Vertical, 142 Apple_Zero, 143 Apple_RequiresLayout, 144 Apple_GXMetamorphosis, 145 Apple_StrongRTL, 146 Apple_IndicRearrangement, 147 148 FontDataLossless, 149 FontConverted, 150 OptimizedForClearType, 151 Reserved14, 152 Reserved15; 153 mask()154 public int mask() { 155 return 1 << this.ordinal(); 156 } 157 asSet(int value)158 public static EnumSet<Flags> asSet(int value) { 159 EnumSet<Flags> set = EnumSet.noneOf(Flags.class); 160 for (Flags flag : Flags.values()) { 161 if ((value & flag.mask()) == flag.mask()) { 162 set.add(flag); 163 } 164 } 165 return set; 166 } 167 value(EnumSet<Flags> set)168 static public int value(EnumSet<Flags> set) { 169 int value = 0; 170 for (Flags flag : set) { 171 value |= flag.mask(); 172 } 173 return value; 174 } 175 cleanValue(EnumSet<Flags> set)176 static public int cleanValue(EnumSet<Flags> set) { 177 EnumSet<Flags> clean = EnumSet.copyOf(set); 178 clean.remove(Flags.Reserved14); 179 clean.remove(Flags.Reserved15); 180 return value(clean); 181 } 182 } 183 184 /** 185 * Get the flags as an int value. 186 * 187 * @return the flags 188 */ flagsAsInt()189 public int flagsAsInt() { 190 return this.data.readUShort(Offset.flags.offset); 191 } 192 193 /** 194 * Get the flags as an enum set. 195 * 196 * @return the enum set of the flags 197 */ flags()198 public EnumSet<Flags> flags() { 199 return Flags.asSet(this.flagsAsInt()); 200 } 201 202 /** 203 * Get the units per em. 204 * 205 * @return the units per em 206 */ unitsPerEm()207 public int unitsPerEm() { 208 return this.data.readUShort(Offset.unitsPerEm.offset); 209 } 210 211 /** 212 * Get the created date. Number of seconds since 12:00 midnight, January 1, 213 * 1904. 64-bit integer. 214 * 215 * @return created date 216 */ created()217 public long created() { 218 return this.data.readDateTimeAsLong(Offset.created.offset); 219 } 220 221 /** 222 * Get the modified date. Number of seconds since 12:00 midnight, January 1, 223 * 1904. 64-bit integer. 224 * 225 * @return created date 226 */ modified()227 public long modified() { 228 return this.data.readDateTimeAsLong(Offset.modified.offset); 229 } 230 231 /** 232 * Get the x min. For all glyph bounding boxes. 233 * 234 * @return the x min 235 */ xMin()236 public int xMin() { 237 return this.data.readShort(Offset.xMin.offset); 238 } 239 240 /** 241 * Get the y min. For all glyph bounding boxes. 242 * 243 * @return the y min 244 */ yMin()245 public int yMin() { 246 return this.data.readShort(Offset.yMin.offset); 247 } 248 249 /** 250 * Get the x max. For all glyph bounding boxes. 251 * 252 * @return the xmax 253 */ xMax()254 public int xMax() { 255 return this.data.readShort(Offset.xMax.offset); 256 } 257 258 /** 259 * Get the y max. For all glyph bounding boxes. 260 * 261 * @return the ymax 262 */ yMax()263 public int yMax() { 264 return this.data.readShort(Offset.yMax.offset); 265 } 266 267 /** 268 * Mac style bits set in the font header table. 269 * 270 */ 271 public enum MacStyle { 272 Bold, 273 Italic, 274 Underline, 275 Outline, 276 Shadow, 277 Condensed, 278 Extended, 279 Reserved7, 280 Reserved8, 281 Reserved9, 282 Reserved10, 283 Reserved11, 284 Reserved12, 285 Reserved13, 286 Reserved14, 287 Reserved15; 288 mask()289 public int mask() { 290 return 1 << this.ordinal(); 291 } 292 asSet(int value)293 public static EnumSet<MacStyle> asSet(int value) { 294 EnumSet<MacStyle> set = EnumSet.noneOf(MacStyle.class); 295 for (MacStyle style : MacStyle.values()) { 296 if ((value & style.mask()) == style.mask()) { 297 set.add(style); 298 } 299 } 300 return set; 301 } 302 value(EnumSet<MacStyle> set)303 public static int value(EnumSet<MacStyle> set) { 304 int value = 0; 305 for (MacStyle style : set) { 306 value |= style.mask(); 307 } 308 return value; 309 } 310 cleanValue(EnumSet<MacStyle> set)311 public static int cleanValue(EnumSet<MacStyle> set) { 312 EnumSet<MacStyle> clean = EnumSet.copyOf(set); 313 clean.removeAll(reserved); 314 return value(clean); 315 } 316 317 private static final EnumSet<MacStyle> reserved = 318 EnumSet.range(MacStyle.Reserved7, MacStyle.Reserved15); 319 } 320 321 /** 322 * Get the Mac style bits as an int. 323 * 324 * @return the Mac style bits 325 */ macStyleAsInt()326 public int macStyleAsInt() { 327 return this.data.readUShort(Offset.macStyle.offset); 328 } 329 330 /** 331 * Get the Mac style bits as an enum set. 332 * 333 * @return the Mac style bits 334 */ macStyle()335 public EnumSet<MacStyle> macStyle() { 336 return MacStyle.asSet(this.macStyleAsInt()); 337 } 338 lowestRecPPEM()339 public int lowestRecPPEM() { 340 return this.data.readUShort(Offset.lowestRecPPEM.offset); 341 } 342 343 /** 344 * Font direction hint values in the font header table. 345 * 346 */ 347 public enum FontDirectionHint { 348 FullyMixed(0), 349 OnlyStrongLTR(1), 350 StrongLTRAndNeutral(2), 351 OnlyStrongRTL(-1), 352 StrongRTLAndNeutral(-2); 353 354 private final int value; 355 FontDirectionHint(int value)356 private FontDirectionHint(int value) { 357 this.value = value; 358 } 359 value()360 public int value() { 361 return this.value; 362 } 363 equals(int value)364 public boolean equals(int value) { 365 return value == this.value; 366 } 367 valueOf(int value)368 public static FontDirectionHint valueOf(int value) { 369 for (FontDirectionHint hint : FontDirectionHint.values()) { 370 if (hint.equals(value)) { 371 return hint; 372 } 373 } 374 return null; 375 } 376 } 377 fontDirectionHintAsInt()378 public int fontDirectionHintAsInt() { 379 return this.data.readShort(Offset.fontDirectionHint.offset); 380 } 381 fontDirectionHint()382 public FontDirectionHint fontDirectionHint() { 383 return FontDirectionHint.valueOf(this.fontDirectionHintAsInt()); 384 } 385 386 /** 387 * The index to location format used in the LocaTable. 388 * 389 * @see LocaTable 390 */ 391 public enum IndexToLocFormat { 392 shortOffset(0), 393 longOffset(1); 394 395 private final int value; 396 IndexToLocFormat(int value)397 private IndexToLocFormat(int value) { 398 this.value = value; 399 } 400 value()401 public int value() { 402 return this.value; 403 } 404 equals(int value)405 public boolean equals(int value) { 406 return value == this.value; 407 } 408 valueOf(int value)409 public static IndexToLocFormat valueOf(int value) { 410 for (IndexToLocFormat format : IndexToLocFormat.values()) { 411 if (format.equals(value)) { 412 return format; 413 } 414 } 415 return null; 416 } 417 } 418 indexToLocFormatAsInt()419 public int indexToLocFormatAsInt() { 420 return this.data.readShort(Offset.indexToLocFormat.offset); 421 } 422 indexToLocFormat()423 public IndexToLocFormat indexToLocFormat() { 424 return IndexToLocFormat.valueOf(this.indexToLocFormatAsInt()); 425 } 426 glyphdataFormat()427 public int glyphdataFormat() { 428 return this.data.readShort(Offset.glyphDataFormat.offset); 429 } 430 431 public static class Builder extends TableBasedTableBuilder<FontHeaderTable> { 432 private boolean fontChecksumSet = false; 433 private long fontChecksum = 0; 434 435 /** 436 * Create a new builder using the header information and data provided. 437 * 438 * @param header the header information 439 * @param data the data holding the table 440 * @return a new builder 441 */ createBuilder(Header header, WritableFontData data)442 public static Builder createBuilder(Header header, WritableFontData data) { 443 return new Builder(header, data); 444 } 445 Builder(Header header, WritableFontData data)446 protected Builder(Header header, WritableFontData data) { 447 super(header, data); 448 data.setCheckSumRanges(0, Offset.checkSumAdjustment.offset, Offset.magicNumber.offset); 449 } 450 Builder(Header header, ReadableFontData data)451 protected Builder(Header header, ReadableFontData data) { 452 super(header, data); 453 data.setCheckSumRanges(FontHeaderTable.CHECKSUM_RANGES); 454 } 455 456 @Override subReadyToSerialize()457 protected boolean subReadyToSerialize() { 458 if (this.dataChanged()) { 459 ReadableFontData data = this.internalReadData(); 460 data.setCheckSumRanges(FontHeaderTable.CHECKSUM_RANGES); 461 } 462 if (this.fontChecksumSet) { 463 ReadableFontData data = this.internalReadData(); 464 data.setCheckSumRanges(FontHeaderTable.CHECKSUM_RANGES); 465 long checksumAdjustment = 466 FontHeaderTable.CHECKSUM_ADJUSTMENT_BASE - (this.fontChecksum + data.checksum()); 467 this.setCheckSumAdjustment(checksumAdjustment); 468 } 469 return super.subReadyToSerialize(); 470 } 471 472 @Override subBuildTable(ReadableFontData data)473 protected FontHeaderTable subBuildTable(ReadableFontData data) { 474 return new FontHeaderTable(this.header(), data); 475 } 476 477 /** 478 * Sets the font checksum to be used when calculating the the checksum 479 * adjustment for the header table during build time. 480 * 481 * The font checksum is the sum value of all tables but the font header 482 * table. If the font checksum has been set then further setting will be 483 * ignored until the font check sum has been cleared with 484 * {@link #clearFontChecksum()}. Most users will never need to set this. It 485 * is used when the font is being built. If set by a client it can interfere 486 * with that process. 487 * 488 * @param checksum 489 * the font checksum 490 */ setFontChecksum(long checksum)491 public void setFontChecksum(long checksum) { 492 if (this.fontChecksumSet) { 493 return; 494 } 495 this.fontChecksumSet = true; 496 this.fontChecksum = checksum; 497 } 498 499 /** 500 * Clears the font checksum to be used when calculating the the checksum 501 * adjustment for the header table during build time. 502 * 503 * The font checksum is the sum value of all tables but the font header 504 * table. If the font checksum has been set then further setting will be 505 * ignored until the font check sum has been cleared. 506 * 507 */ clearFontChecksum()508 public void clearFontChecksum() { 509 this.fontChecksumSet = false; 510 } 511 tableVersion()512 public int tableVersion() { 513 return this.table().tableVersion(); 514 } 515 setTableVersion(int version)516 public void setTableVersion(int version) { 517 this.internalWriteData().writeFixed(Offset.tableVersion.offset, version); 518 } 519 fontRevision()520 public int fontRevision() { 521 return this.table().fontRevision(); 522 } 523 setFontRevision(int revision)524 public void setFontRevision(int revision) { 525 this.internalWriteData().writeFixed(Offset.fontRevision.offset, revision); 526 } 527 checkSumAdjustment()528 public long checkSumAdjustment() { 529 return this.table().checkSumAdjustment(); 530 } 531 setCheckSumAdjustment(long adjustment)532 public void setCheckSumAdjustment(long adjustment) { 533 this.internalWriteData().writeULong(Offset.checkSumAdjustment.offset, adjustment); 534 } 535 magicNumber()536 public long magicNumber() { 537 return this.table().magicNumber(); 538 } 539 setMagicNumber(long magicNumber)540 public void setMagicNumber(long magicNumber) { 541 this.internalWriteData().writeULong(Offset.magicNumber.offset, magicNumber); 542 } 543 flagsAsInt()544 public int flagsAsInt() { 545 return this.table().flagsAsInt(); 546 } 547 flags()548 public EnumSet<Flags> flags() { 549 return this.table().flags(); 550 } 551 setFlagsAsInt(int flags)552 public void setFlagsAsInt(int flags) { 553 this.internalWriteData().writeUShort(Offset.flags.offset, flags); 554 } 555 setFlags(EnumSet<Flags> flags)556 public void setFlags(EnumSet<Flags> flags) { 557 setFlagsAsInt(Flags.cleanValue(flags)); 558 } 559 unitsPerEm()560 public int unitsPerEm() { 561 return this.table().unitsPerEm(); 562 } 563 setUnitsPerEm(int units)564 public void setUnitsPerEm(int units) { 565 this.internalWriteData().writeUShort(Offset.unitsPerEm.offset, units); 566 } 567 created()568 public long created() { 569 return this.table().created(); 570 } 571 setCreated(long date)572 public void setCreated(long date) { 573 this.internalWriteData().writeDateTime(Offset.created.offset, date); 574 } 575 modified()576 public long modified() { 577 return this.table().modified(); 578 } 579 setModified(long date)580 public void setModified(long date) { 581 this.internalWriteData().writeDateTime(Offset.modified.offset, date); 582 } 583 xMin()584 public int xMin() { 585 return this.table().xMin(); 586 } 587 setXMin(int xmin)588 public void setXMin(int xmin) { 589 this.internalWriteData().writeShort(Offset.xMin.offset, xmin); 590 } 591 yMin()592 public int yMin() { 593 return this.table().yMin(); 594 } 595 setYMin(int ymin)596 public void setYMin(int ymin) { 597 this.internalWriteData().writeShort(Offset.yMin.offset, ymin); 598 } 599 xMax()600 public int xMax() { 601 return this.table().xMax(); 602 } 603 setXMax(int xmax)604 public void setXMax(int xmax) { 605 this.internalWriteData().writeShort(Offset.xMax.offset, xmax); 606 } 607 yMax()608 public int yMax() { 609 return this.table().yMax(); 610 } 611 setYMax(int ymax)612 public void setYMax(int ymax) { 613 this.internalWriteData().writeShort(Offset.yMax.offset, ymax); 614 } 615 macStyleAsInt()616 public int macStyleAsInt() { 617 return this.table().macStyleAsInt(); 618 } 619 setMacStyleAsInt(int style)620 public void setMacStyleAsInt(int style) { 621 this.internalWriteData().writeUShort(Offset.macStyle.offset, style); 622 } 623 macStyle()624 public EnumSet<MacStyle> macStyle() { 625 return this.table().macStyle(); 626 } 627 macStyle(EnumSet<MacStyle> style)628 public void macStyle(EnumSet<MacStyle> style) { 629 this.setMacStyleAsInt(MacStyle.cleanValue(style)); 630 } 631 lowestRecPPEM()632 public int lowestRecPPEM() { 633 return this.table().lowestRecPPEM(); 634 } 635 setLowestRecPPEM(int size)636 public void setLowestRecPPEM(int size) { 637 this.internalWriteData().writeUShort(Offset.lowestRecPPEM.offset, size); 638 } 639 fontDirectionHintAsInt()640 public int fontDirectionHintAsInt() { 641 return this.table().fontDirectionHintAsInt(); 642 } 643 setFontDirectionHintAsInt(int hint)644 public void setFontDirectionHintAsInt(int hint) { 645 this.internalWriteData().writeShort(Offset.fontDirectionHint.offset, hint); 646 } 647 fontDirectionHint()648 public FontDirectionHint fontDirectionHint() { 649 return this.table().fontDirectionHint(); 650 } 651 setFontDirectionHint(FontDirectionHint hint)652 public void setFontDirectionHint(FontDirectionHint hint) { 653 this.setFontDirectionHintAsInt(hint.value()); 654 } 655 indexToLocFormatAsInt()656 public int indexToLocFormatAsInt() { 657 return this.table().indexToLocFormatAsInt(); 658 } 659 setIndexToLocFormatAsInt(int format)660 public void setIndexToLocFormatAsInt(int format) { 661 this.internalWriteData().writeShort(Offset.indexToLocFormat.offset, format); 662 } 663 indexToLocFormat()664 public IndexToLocFormat indexToLocFormat() { 665 return this.table().indexToLocFormat(); 666 } 667 setIndexToLocFormat(IndexToLocFormat format)668 public void setIndexToLocFormat(IndexToLocFormat format) { 669 this.setIndexToLocFormatAsInt(format.value()); 670 } 671 glyphdataFormat()672 public int glyphdataFormat() { 673 return this.table().glyphdataFormat(); 674 } 675 setGlyphdataFormat(int format)676 public void setGlyphdataFormat(int format) { 677 this.internalWriteData().writeShort(Offset.glyphDataFormat.offset, format); 678 } 679 } 680 } 681