diff --git a/README.md b/README.md index 62172db..8704f3a 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | TAR | `application/x-tar` | [`patterns/tar.hexpat`](patterns/tar.hexpat) | Tar file format | | TIFF | `image/tiff` | [`patterns/tiff.hexpat`](patterns/tiff.hexpat) | Tag Image File Format | | TGA | `image/tga` | [`patterns/tga.hexpat`](patterns/tga.hexpat) | Truevision TGA/TARGA image | +| TTF | `font/ttf`, `font/otf` | [`patterns/ttf.hexpat`](patterns/ttf.hexpat) | TrueType and OpenType font format | | Ubiquiti | | [`patterns/ubiquiti.hexpat`](patterns/ubiquiti.hexpat) | Ubiquiti Firmware (update) image | | UEFI | | [`patterns/uefi.hexpat`](patterns/uefi.hexpat)` | UEFI structs for parsing efivars | | UF2 | | [`patterns/uf2.hexpat`](patterns/uf2.hexpat) | [USB Flashing Format](https://github.com/microsoft/uf2) | diff --git a/patterns/ttf.hexpat b/patterns/ttf.hexpat new file mode 100644 index 0000000..f0f7513 --- /dev/null +++ b/patterns/ttf.hexpat @@ -0,0 +1,726 @@ +#pragma author Shimogawa (aka Rebuild) +#pragma description TrueType and OpenType font format +#pragma endian big +#pragma MIME font/ttf +#pragma MIME font/otf + +#pragma pattern_limit 1000000 + +import std.mem; +import std.string; +import std.time; +import std.io; + +struct TableDirectory { + char tag[4]; + u32 checkSum; + u32 offset; + u32 length; +}; + +fn format_fixed32(auto fp32) { + return fp32.integer + fp32.fraction / 65536.0; +}; + +fn format_f2dot14(auto f) { + return f.integer + f.fraction / 16384.0; +}; + +using FWord = s16; +using UFWord = u16; + +struct FixedPoint32 { + u16 integer; + u16 fraction; +} [[format("format_fixed32")]]; + +bitfield F2Dot14 { + integer : 2; + fraction : 14; +} [[format("format_f2dot14")]]; + +fn format_longdatetime(auto t) { + return std::time::format(std::time::to_utc(u32(t.time - 2082844800))); +}; + +struct LongDatetime { + s64 time; +} [[format("format_longdatetime")]]; + +using HeadTable; + +u64 currentOffset; +u64 currentLength; +u64 glyfStart; +bool hasPostScriptOutlines = false; +u16 numHMetrics; +u16 gNumGlyphs; +HeadTable headTable; + +// begin avar + +struct AxisValueMap { + F2Dot14 fromCoord; + F2Dot14 toCoord; +}; + +struct ShortFracSegment { + u16 positionMapCount; + AxisValueMap axisValueMaps[positionMapCount]; +}; + +struct AvarTable { + FixedPoint32 version; + s32 axisCount; + ShortFracSegment segments[axisCount]; +}; + +// end avar + +// begin cmap + +u64 startOfCmapTable = 0; + +struct BaseCmapSubtable { + u64 start = $; + u16 format; +}; + +struct CmapSubtable0 : BaseCmapSubtable { + u16 length; + u16 language; + u8 glyphIndexArr[256]; +}; + +struct CmapSubtable2 : BaseCmapSubtable { + u16 length; + u16 language; + u16 subHeaderKeys[256]; + u8 data[length - 516]; +}; + +struct CmapSubtable4 : BaseCmapSubtable { + u16 length; + u64 end = start + length; + u16 language; + u16 segCountX2; + u16 segCount = segCountX2 / 2; + u16 searchRange; + u16 entrySelector; + u16 rangeShift; + u16 endCode[segCount]; + u16 reserved; + u16 startCode[segCount]; + s16 idDelta[segCount]; + u16 idRangeOffset[segCount]; + u16 glyphIdArr[(end - $) / sizeof(u16)]; +}; + +struct CmapSubtable6 : BaseCmapSubtable { + u16 length; + u16 language; + u16 firstCode; + u16 entryCount; + u16 glyphIdArr[entryCount]; +}; + +struct SequentialMapGroup { + u32 startCharCode; + u32 endCharCode; + u32 startGlyphId; +}; + +struct CmapSubtable8 : BaseCmapSubtable { + u16 reserved; + u32 length; + u32 language; + u8 is32[8192]; + u32 numGroups; + SequentialMapGroup groups[numGroups]; +}; + +struct CmapSubtable10 : BaseCmapSubtable { + u16 reserved; + u32 length; + u64 end = start + length; + u32 language; + u32 startCharCode; + u32 numChars; + u16 glyphIdArr[(end - $) / sizeof(u16)]; +}; + +struct CmapSubtable12 : BaseCmapSubtable { + u16 reserved; + u32 length; + u32 language; + u32 numGroups; + SequentialMapGroup groups[numGroups]; +}; + +struct CmapSubtable { + u16 format = std::mem::read_unsigned($, 2, std::mem::Endian::Big); + + match (format) { + (0): CmapSubtable0 table [[inline]]; + (2): CmapSubtable2 table [[inline]]; + (4): CmapSubtable4 table [[inline]]; + (6): CmapSubtable6 table [[inline]]; + (8): CmapSubtable8 table [[inline]]; + (10): CmapSubtable10 table [[inline]]; + (12): CmapSubtable12 table [[inline]]; + (_): BaseCmapSubtable table [[inline]]; + } +} [[name(std::format("Subtable Format {}", format))]]; + +enum CmapPlatform : u16 { + Unicode = 0, + Macintosh = 1, + ISO = 2, + Microsoft = 3, + Custom = 4, +}; + +struct EncodingRecord { + CmapPlatform platformId; + u16 encodingId; + u32 offset; + + u64 endEncodingRecord = $; + $ = startOfCmapTable + offset; + + CmapSubtable subtable; + + $ = endEncodingRecord; +}; + +struct CmapTable { + startOfCmapTable = $; + u16 version; + u16 numSubtables; + EncodingRecord encodingRecords[numSubtables]; +}; + +// end cmap + +// begin cvt + +struct CvtTable { + u64 size = currentLength / sizeof(FWord); + FWord controlValues[size]; +}; + +// end cvt + +// begin gasp + +bitfield RangeGaspBehavior { + padding : 12; + symmetricSmoothing : 1; + symmetricGridfit : 1; + doGray : 1; + gridFit : 1; +}; + +struct GaspRange { + u16 rangeMaxPPEM; + RangeGaspBehavior rangeGaspBehavior; +}; + +struct GaspTable { + u16 version; + u16 numRanges; + GaspRange gaspRanges[numRanges]; +}; + +// end gasp + +// begin glyf + +struct GlyphTableHeader { + u64 startOffset = $; + s16 numContours; + FWord xMin; + FWord yMin; + FWord xMax; + FWord yMax; +}; + +bitfield OutlineFlag { + padding : 2; + ySameOrPositive : 1; + xSameOrPositive : 1; + repeat : 1; + yShort : 1; + xShort : 1; + onCurve : 1; +}; + +u32 pIdx = 0; +u32 curGlyfSize; + +struct SimpleGlyphFlag { + pIdx += 1; + OutlineFlag flag; + if (flag.repeat) { + pIdx += 1; + u8 repeatTimes; + } +}; + +struct SimpleGlyphTable : GlyphTableHeader { + u16 endPtsOfContours[numContours]; + u32 numPoints = numContours == 0 ? 0 : endPtsOfContours[numContours - 1]; + u16 instructionLength; + u8 instructions[instructionLength]; + pIdx = 0; + SimpleGlyphFlag flags[while(pIdx < numPoints)] [[single_color]]; + u8 data[startOffset + curGlyfSize - $]; +}; + +bitfield CompositeGlyphFlag { + padding : 3; + unscaledComponentOffset : 1; + scaledComponentOffset : 1; + overlapCompound : 1; + useMyMetrics : 1; + weHaveInstructions : 1; + weHaveA2x2 : 1; + weHaveAnXAndYScale : 1; + moreComponents : 1; + padding : 1; + weHaveAScale : 1; + roundXYToGrid : 1; + argsAreXYValues : 1; + arg1And2AreWords : 1; +}; + +bool componentGlyphHasMoreComponents; +bool componentGlyphHasInstruction; + +struct ComponentGlyphRecord { + CompositeGlyphFlag flags; + u16 glyphIndex; + if (flags.arg1And2AreWords) { + FWord arg1; + FWord arg2; + } else { + u8 arg1; + u8 arg2; + } + if (flags.weHaveAScale) { + F2Dot14 scale; + } else if (flags.weHaveAnXAndYScale) { + F2Dot14 xScale; + F2Dot14 yScale; + } else if (flags.weHaveA2x2) { + F2Dot14 xScale; + F2Dot14 scale01; + F2Dot14 scale10; + F2Dot14 yScale; + } + componentGlyphHasMoreComponents = flags.moreComponents; + componentGlyphHasInstruction = flags.weHaveInstructions; +}; + +struct CompositeGlyphTable : GlyphTableHeader { + componentGlyphHasMoreComponents = true; + componentGlyphHasInstruction = false; + ComponentGlyphRecord records[while (componentGlyphHasMoreComponents)]; + if (componentGlyphHasInstruction) { + u16 instructionLength; + u8 instructions[instructionLength]; + } +}; + +struct GlyfTable { + s16 type = std::mem::read_signed($, 2, std::mem::Endian::Big); + + if (type >= 0) { + SimpleGlyphTable table [[inline]]; + } else { + CompositeGlyphTable table [[inline]]; + } +} [[name(std::format("glyf ({})", table.numContours < 0 ? "Composite" : "Simple"))]]; + +// end glyf + +// begin hdmx + +struct DeviceRecord { + u8 pixelSize; + u8 maxWidth; + u8 widths[gNumGlyphs]; +}; + +struct HdmxTable { + u16 version; + u16 numRecords; + u32 sizeDeviceRecord; + DeviceRecord records[numRecords]; +}; + +// end hdmx + +// begin head + +bitfield HeadFlag { + padding : 1; + lastResort : 1; + clearTypeOptimized : 1; + converted : 1; + lossless : 1; + indicStyleRearr : 1; + RTLGlyph : 1; + AATFont : 1; + linguisticRender : 1; + padding : 1; + verticalLayout : 1; + instrMayAlterAW : 1; + integerScaling : 1; + differentPointSize : 1; + xLBlackBitIsLSB : 1; + yZeroIsBaseline : 1; +}; + +bitfield MacStyle { + padding : 9; + extended : 1; + condensed : 1; + shadow : 1; + outline : 1; + underline : 1; + italic : 1; + bold : 1; +}; + +struct HeadTable { + FixedPoint32 version; + FixedPoint32 fontRevision; + u32 checksumAdjustment; + u32 magic; + HeadFlag flags; + u16 unitsPerEm; + LongDatetime created; + LongDatetime modified; + FWord xMin; + FWord yMin; + FWord xMax; + FWord yMax; + MacStyle macStyle; + u16 lowestRecPPEM; + s16 fontDirectionHint; + s16 indexToLocFormat; + s16 glyphDataFormat; +}; + +// end head + +// begin hhea + +struct HheaTable { + FixedPoint32 version; + FWord ascent; + FWord descent; + FWord lineGap; + UFWord advanceWidthMax; + FWord minLeftSideBearing; + FWord minRightSideBearing; + FWord xMaxExtent; + s16 caretSlopeRise; + s16 caretSlopeRun; + s16 caretOffset; + s16 reserved[4]; + s16 metricDataFormat; + u16 numberOfHMetrics; + numHMetrics = numberOfHMetrics; +}; + +// end hhea + +// begin hmtx + +struct LongHorMetric { + UFWord advanceWidth; + FWord lsb; +}; + +struct HmtxTable { + LongHorMetric hMetrics[numHMetrics]; + FWord leftSideBearings[gNumGlyphs - numHMetrics]; +}; + +// end hmtx + +// begin loca + +// loca contains offsets for glyf tables, so +// we put glyf arrays here also. + +struct GlyfWithOffset { + if (longOffset) { + u32 offset; + u32 realOffset = offset; + u32 nextOff = std::mem::read_unsigned($, 4, std::mem::Endian::Big); + } else { + u16 offset; + u32 realOffset = u32(offset) * 2; + u32 nextOff = u32(std::mem::read_unsigned($, 2, std::mem::Endian::Big)) * 2; + } + curGlyfSize = nextOff - realOffset; + u64 prev = $; + $ = glyfStart + realOffset; + if (curGlyfSize != 0) { + GlyfTable glyf; + } + $ = prev; +}; + +struct LocaTable { + if (headTable.indexToLocFormat == 1) { + //u32 offsets[gNumGlyphs + 1]; + GlyfWithOffset glyfs[gNumGlyphs]; + } else { + // u16 offsets[gNumGlyphs + 1]; + GlyfWithOffset glyfs[gNumGlyphs]; + } +}; + +// end loca + +// begin maxp + +struct MaxpTable { + FixedPoint32 version; + u16 numGlyphs; + gNumGlyphs = numGlyphs; + if (!hasPostScriptOutlines) { + u16 maxPoints; + u16 maxContours; + u16 maxComponentPoints; + u16 maxComponentContours; + u16 maxZones; + u16 maxTwilightPoints; + u16 maxStorage; + u16 maxFunctionDefs; + u16 maxInstructionDefs; + u16 maxStackElements; + u16 maxSizeOfElements; + u16 maxComponentDepth; + } +}; + +// end maxp + +// begin name + +u64 curStorageOffset; + +struct NameRecord { + u16 platformId; + u16 encodingId; + u16 languageId; + u16 nameId; + u16 length; + u16 stringOffset; + u64 cur = $; + $ = curStorageOffset + stringOffset; + char16 string[length / 2]; + $ = cur; +}; + +struct LangTagRecord { + u16 length; + u16 langTagOffset; + u64 cur = $; + $ = curStorageOffset + langTagOffset; + char16 langTag[length / 2]; + $ = cur; +}; + +struct NameTableV0 { + u64 startOfNameTable = $; + u16 version; + u16 count; + u16 storageOffset; + curStorageOffset = startOfNameTable + storageOffset; + NameRecord nameRecords[count]; +}; + +struct NameTableV1 : NameTableV0 { + u16 langTagCount; + LangTagRecord langTagRecords[langTagCount]; +}; + +struct NameTable { + u16 version = std::mem::read_unsigned($, 2, std::mem::Endian::Big); + + match (version) { + (0): NameTableV0 table [[inline]]; + (_): NameTableV1 table [[inline]]; + } +}; + +// end name + +// begin OS/2 + +struct OS2TableV0 { + u16 version; + FWord xAvgCharWidth; + u16 usWeightClass; + u16 usWidthClass; + u16 fsType; + FWord ySubscriptXSize; + FWord ySubscriptYSize; + FWord SubscriptXOffset; + FWord SubscriptYOffset; + FWord ySuperscriptXSize; + FWord ySuperscriptYSize; + FWord SuperscriptXOffset; + FWord SuperscriptYOffset; + FWord yStrikeoutSize; + FWord yStrikeoutPosition; + s16 sFamilyClass; + u8 panose[10]; + u32 ulUnicodeRange[4]; + u8 achVendID[4]; + u16 fsSelection; + u16 usFirstCharIndex; + u16 usLastCharIndex; + FWord sTypoAscender; + FWord sTypoDescender; + FWord sTypoLineGap; + UFWord usWinAscent; + UFWord usWinDescent; +}; + +struct OS2TableV1 : OS2TableV0 { + u32 ulCodePageRange[2]; +}; + +struct OS2TableV4 : OS2TableV1 { + FWord sxHeight; + FWord sCapHeight; + u16 usDefaultChar; + u16 usBreakChar; + u16 usMaxContent; +}; + +struct OS2TableV5 : OS2TableV4 { + u16 usLowerOpticalPointSize; + u16 usUpperOpticalPointSize; +}; + +struct OS2Table { + u16 version = std::mem::read_unsigned($, 2, std::mem::Endian::Big); + + match (version) { + (0): OS2TableV0 table [[inline]]; + (1): OS2TableV1 table [[inline]]; + (2 | 3 | 4): OS2TableV4 table [[inline]]; + (_): OS2TableV5 table [[inline]]; + } +}; + +// end OS/2 + +// begin post + +struct PostTableV1 { + u64 startOfTable = $; + FixedPoint32 version; + FixedPoint32 italicAngle; + FWord underlinePosition; + FWord underlineThickness; + u32 isFixedPitch; + u32 minMemType42; + u32 maxMemType42; + u32 minMemType1; + u32 maxMemType1; +}; + +struct PostTableV2 : PostTableV1 { + u16 numGlyphs; + u16 glyphNameIndex[numGlyphs]; + std::string::SizedString stringData[while ($ < startOfTable + currentLength)]; +}; + +struct PostTable { + u16 major = std::mem::read_unsigned($, 2, std::mem::Endian::Big); + u16 minor = std::mem::read_unsigned($ + 2, 2, std::mem::Endian::Big); + + match (major, minor) { + (2, 0): PostTableV2 table [[inline]]; + (_, _): PostTableV1 table [[inline]]; + } +}; + +// end post + +struct HiddenForPreprocessing { + u64 defStart = $; + TableDirectory td; + $ = td.offset; + currentOffset = td.offset; + currentLength = td.length; + match (td.tag) { + ("CFF "): { + hasPostScriptOutlines = true; + } + ("maxp"): MaxpTable table; + ("head"): { + HeadTable table; + headTable = table; + } + ("glyf"): { + glyfStart = $; + } + } + $ = defStart + sizeof(TableDirectory); +} [[hidden]]; + +struct Table { + u64 defStart = $; + TableDirectory td [[inline]]; + $ = td.offset; + currentOffset = td.offset; + currentLength = td.length; + match (td.tag) { + ("avar"): AvarTable table; + ("bhed"): HeadTable table; + ("cmap"): CmapTable table; + ("cvt "): CvtTable table; + ("gasp"): GaspTable table; + // ("glyf"): GlyfTable table; + // glyf will be contained in loca. + ("glyf"): {} + ("hdmx"): HdmxTable table; + ("head"): HeadTable table; + ("hhea"): HheaTable table; + ("hmtx"): HmtxTable table; + ("loca"): LocaTable table; + ("maxp"): MaxpTable table; + ("name"): NameTable table; + ("OS/2"): OS2Table table; + ("post"): PostTable table; + (_): u8 data[td.length]; + } + $ = defStart + sizeof(TableDirectory); +} [[name(std::format("Table({})", td.tag))]]; + + +struct TTF { + u32 scalarType; + u16 numTables; + u16 searchRange; + u16 entrySelector; + u16 rangeShift; + + u64 start = $; + HiddenForPreprocessing hidden[numTables] [[hidden]]; + $ = start; + + Table tables[numTables]; +}; + +TTF ttf @ 0x0; diff --git a/tests/patterns/test_data/ttf.hexpat.ttf b/tests/patterns/test_data/ttf.hexpat.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/tests/patterns/test_data/ttf.hexpat.ttf differ