diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9a0dc32..4fabdfcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Install Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - name: Download the CFF dump utility + run: wget https://github.com/janpe2/CFFDump/releases/download/v1.3.0/CFFDump_bin_cli_1.3.0.jar -O CFFDump_bin_cli_1.3.0.jar + - name: Set CFF_DUMP_BIN environment variable + run: echo "CFF_DUMP_BIN=$PWD/CFFDump_bin_cli_1.3.0.jar" >> $GITHUB_ENV - name: Set up Python uses: actions/setup-python@v5 with: @@ -14,9 +23,9 @@ jobs: - name: Install fonttools run: pip install fonttools==4.50 - uses: dtolnay/rust-toolchain@stable - - run: cargo build --release + - run: cargo build name: Build - - run: cargo test --release + - run: cargo test name: Run tests checks: diff --git a/.gitignore b/.gitignore index dfae216e..102dfa24 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ debug # Tests tests/subsets tests/ttx/*.otf +tests/cff/*.otf tests/ttx/*_ref.ttx diff --git a/tests/README.md b/tests/README.md index 942fa886..c2ff7866 100644 --- a/tests/README.md +++ b/tests/README.md @@ -4,15 +4,17 @@ You need to have `fonttools 4.50` installed on your system and in your PATH. Note that you need to have that exact version, otherwise the tests will fail. +In addition to that, you need Java installed on your system, install the [CFF dump utility](https://github.com/janpe2/CFFDump/releases/tag/v1.3.0) and point the `CFF_DUMP_BIN` environment variable to it. + ## Generating tests -In order to create new fonttools tests, you can edit `data/fonttools.tests`. For subset tests, you can edit -`data/subsets.tests` +In order to create new fonttools tests, you can edit `data/fonttools.tests`. +For CFF tests, you can edit `data/cff.tests`. For subset tests, you can edit `data/subsets.tests` In order to generate the tests, run `scripts/gen-tests.py`. ## Description Testing is very important, as having errors in the subsetting logic could have fatal consequences. -Because of this, we have three different testing approaches that cover 4 different +Because of this, we have four different testing approaches that cover 4 different font readers and 7 different PDF readers in total. ### Subset tests @@ -32,6 +34,12 @@ dump small subsets of fonts and compare the output to how fonttools would subset to identify other kinds of potential issues in the implementation. And it conveniently also allows us to have a fourth implementation to test against. +### CFF tests +A problem with CFF tests is that fonttools abstracts away the exact structure of the CFF table, +and stuff like the order of operators in DICTs as well as missing entries are not preserved. Because of +this, we use the above-mentioned CFF dump utility, which provides a much more detailed insight into the +structure of the CFF table, and allows us to detect regressions in CFF subsetting more easily. + ### Fuzzing tests In `examples`, we have a binary that takes an environment variable `FONT_DIR` and recursively iterates over all fonts in that directory and basically performs the same test as in #1, but on a randomly selected sets of glyphs. We currently diff --git a/tests/cff/LatinModernRoman-Regular_1.txt b/tests/cff/LatinModernRoman-Regular_1.txt new file mode 100644 index 00000000..032413e5 --- /dev/null +++ b/tests/cff/LatinModernRoman-Regular_1.txt @@ -0,0 +1,209 @@ +% CFF Dump Output +% File: LatinModernRoman-Regular_1.otf +% Dumping an OpenType font file. +% CFF data starts at 0x7C and its length is 674 bytes. +% All dumped offsets are relative to 0x7C. + + +-------------------------------------------------------------------------------- + +Header (0x00000000): + major: 1 + minor: 0 + hdrSize: 4 + offSize: 4 + +-------------------------------------------------------------------------------- + +Name INDEX (0x00000004): + count: 1, offSize: 1 + Offsets of INDEX (relative to 8): + [0] = 1 + [1] = 18 + Data: + [0]: (LMRoman10-Regular) + +-------------------------------------------------------------------------------- + +Top DICT INDEX (0x0000001a): + count: 1, offSize: 1 + Offsets of INDEX (relative to 30): + [0] = 1 + [1] = 68 + Data: + [0] (0x0000001f): + << + /ROS << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> + /Notice (Copyright 2003, 2009 B. Jackowski and J. M. Nowacki (on behalf of TeX users groups). This work is released under the GUST Font License -- see http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt for details.) % SID 393 + /FontMatrix [0.001 0 0 0.001 0 0] + /FontBBox [-430 -290 1417 1127] + /charset 327 % offset + /CharStrings 417 % offset + /CIDCount 65535 + /FDArray 393 % offset + /FDSelect 385 % offset + % ----- Following entries are missing, so they get default values: ----- + /isFixedPitch false % default + /ItalicAngle 0 % default + /UnderlinePosition -100 % default + /UnderlineThickness 50 % default + /PaintType 0 % default + /CharstringType 2 % default + /StrokeWidth 0 % default + /CIDFontVersion 0 % default + /CIDFontRevision 0 % default + /CIDFontType 0 % default + >> + +-------------------------------------------------------------------------------- + +String INDEX (0x00000062): + count: 3, offSize: 1 + Offsets of INDEX (relative to 104): + [0] = 1 + [1] = 6 + [2] = 14 + [3] = 221 + Data: + [0](SID = 391): (Adobe) + [1](SID = 392): (Identity) + [2](SID = 393): (Copyright 2003, 2009 B. Jackowski and J. M. Nowacki (on behalf of TeX users groups). This work is released under the GUST Font License -- see http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt for details.) + +-------------------------------------------------------------------------------- + +Global Subr INDEX (0x00000145): + count: 0 + + +-------------------------------------------------------------------------------- + +Charset (0x00000147): + (format 2; [GID] = ): + ([0] = CID 0), + [1] = CID 1, + [2] = CID 2, + +-------------------------------------------------------------------------------- + +FDSelect (0x00000181): + (format 3) + FD #0 for GIDs 0...2 + + +-------------------------------------------------------------------------------- + +Font DICT INDEX (a.k.a. FDArray) (0x00000189): + count: 1, offSize: 1 + Offsets of INDEX (relative to 397): + [0] = 1 + [1] = 20 + Data: + [0] (0x0000018e): + << + /FontMatrix [1 0 0 1 0 0] + /Private [53 332] % [size offset] + % ----- Following entries are missing, so they get default values: ----- + /isFixedPitch false % default + /ItalicAngle 0 % default + /UnderlinePosition -100 % default + /UnderlineThickness 50 % default + /PaintType 0 % default + /CharstringType 2 % default + /FontBBox [0 0 0 0] % default + /StrokeWidth 0 % default + >> + +-------------------------------------------------------------------------------- + +CharStrings INDEX (0x000001a1): + count: 3, offSize: 1 + Offsets of INDEX (relative to 423): + [0] = 1 + [1] = 4 + [2] = 120 + [3] = 251 + Data: + ([GID] (offset): ) + [0] CID 0 (0x000001a8): + 280 endchar % glyph width = 280 + nominalWidthX = 280 + [1] CID 1 (0x000001ab): + 681 0 31 307 31 86 31 163 31 hstem % glyph width = 681 + nominalWidthX = 681 + 33 25 3 25 151 25 198 89 vstem + 652 hmoveto + 31 -24 vlineto + -77 -2 11 36 hvcurveto + 524 vlineto + 36 2 11 77 vhcurveto + 24 31 -563 hlineto + -28 -225 rlineto + 25 hlineto + 139 16 27 55 153 hhcurveto + 129 hlineto + 47 2 -7 -33 hvcurveto + -240 -90 vlineto + -97 -11 31 86 hvcurveto + -25 -265 25 hlineto + 85 11 32 97 vhcurveto + 90 -267 hlineto + -33 -2 -7 -47 vhcurveto + -133 hlineto + -172 -23 73 154 -25 hvcurveto + -25 hlineto + 42 -258 rlineto + endchar + [2] CID 2 (0x0000021f): + 676 -10 25 330 21 312 23 hstem % glyph width = 676 + nominalWidthX = 676 + 28 103 421 95 vstem + 647 121 rmoveto + 6 -7 5 -4 -15 -14 -36 -15 -15 vhcurveto + -55 -57 -90 -11 -73 hhcurveto + -90 -73 58 77 -40 hvcurveto + -30 60 -8 69 66 vvcurveto + 500 hlineto + 14 2 7 12 92 -24 106 -69 65 hvcurveto + 50 -53 -71 24 -72 hhcurveto + -188 -142 -164 -191 -171 118 -170 204 -15 hvcurveto + 14 hlineto + 97 125 17 96 53 hvcurveto + 3 5 5 7 6 vvcurveto + -95 245 rmoveto + -421 hlineto + 133 3 57 179 161 hhcurveto + 72 58 -43 -69 33 hvcurveto + 29 -62 8 -70 -68 vvcurveto + endchar + +-------------------------------------------------------------------------------- + +CIDFont's Private DICTs: + Private DICT for Font DICT #0 (0x0000014c): + << + /BlueValues [-22 0 431 448 666 677 683 705] % Original delta values: [-22 22 431 17 218 11 6 22] + /BlueScale 0.04546 + /BlueFuzz 0 + /StdHW 31 + /StdVW 69 + /StemSnapH [22 23 25 26 28 30 31 38 40 42 45 106] % Original delta values: [22 1 2 1 2 2 1 7 2 2 3 61] + /StemSnapV [25 66 69 75 77 83 86 89 92 97 103 107] % Original delta values: [25 41 3 6 2 6 3 3 3 5 6 4] + % ----- Following entries are missing, so they get default values: ----- + /BlueShift 7 % default + /ForceBold false % default + /LanguageGroup 0 % default + /ExpansionFactor 0.06 % default + /initialRandomSeed 0 % default + /defaultWidthX 0 % default + /nominalWidthX 0 % default + >> + +-------------------------------------------------------------------------------- + +Local Subr INDEX for Font DICT #0: + + +-------------------------------------------------------------------------------- + +Info messages: +Info: This is a CIDFont. + +% End of dump + diff --git a/tests/cff/LatinModernRoman-Regular_2.txt b/tests/cff/LatinModernRoman-Regular_2.txt new file mode 100644 index 00000000..7c7cfb4a --- /dev/null +++ b/tests/cff/LatinModernRoman-Regular_2.txt @@ -0,0 +1,193 @@ +% CFF Dump Output +% File: LatinModernRoman-Regular_2.otf +% Dumping an OpenType font file. +% CFF data starts at 0x7C and its length is 635 bytes. +% All dumped offsets are relative to 0x7C. + + +-------------------------------------------------------------------------------- + +Header (0x00000000): + major: 1 + minor: 0 + hdrSize: 4 + offSize: 4 + +-------------------------------------------------------------------------------- + +Name INDEX (0x00000004): + count: 1, offSize: 1 + Offsets of INDEX (relative to 8): + [0] = 1 + [1] = 18 + Data: + [0]: (LMRoman10-Regular) + +-------------------------------------------------------------------------------- + +Top DICT INDEX (0x0000001a): + count: 1, offSize: 1 + Offsets of INDEX (relative to 30): + [0] = 1 + [1] = 68 + Data: + [0] (0x0000001f): + << + /ROS << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> + /Notice (Copyright 2003, 2009 B. Jackowski and J. M. Nowacki (on behalf of TeX users groups). This work is released under the GUST Font License -- see http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt for details.) % SID 393 + /FontMatrix [0.001 0 0 0.001 0 0] + /FontBBox [-430 -290 1417 1127] + /charset 327 % offset + /CharStrings 417 % offset + /CIDCount 65535 + /FDArray 393 % offset + /FDSelect 385 % offset + % ----- Following entries are missing, so they get default values: ----- + /isFixedPitch false % default + /ItalicAngle 0 % default + /UnderlinePosition -100 % default + /UnderlineThickness 50 % default + /PaintType 0 % default + /CharstringType 2 % default + /StrokeWidth 0 % default + /CIDFontVersion 0 % default + /CIDFontRevision 0 % default + /CIDFontType 0 % default + >> + +-------------------------------------------------------------------------------- + +String INDEX (0x00000062): + count: 3, offSize: 1 + Offsets of INDEX (relative to 104): + [0] = 1 + [1] = 6 + [2] = 14 + [3] = 221 + Data: + [0](SID = 391): (Adobe) + [1](SID = 392): (Identity) + [2](SID = 393): (Copyright 2003, 2009 B. Jackowski and J. M. Nowacki (on behalf of TeX users groups). This work is released under the GUST Font License -- see http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt for details.) + +-------------------------------------------------------------------------------- + +Global Subr INDEX (0x00000145): + count: 0 + + +-------------------------------------------------------------------------------- + +Charset (0x00000147): + (format 2; [GID] = ): + ([0] = CID 0), + [1] = CID 1, + +-------------------------------------------------------------------------------- + +FDSelect (0x00000181): + (format 3) + FD #0 for GIDs 0...1 + + +-------------------------------------------------------------------------------- + +Font DICT INDEX (a.k.a. FDArray) (0x00000189): + count: 1, offSize: 1 + Offsets of INDEX (relative to 397): + [0] = 1 + [1] = 20 + Data: + [0] (0x0000018e): + << + /FontMatrix [1 0 0 1 0 0] + /Private [53 332] % [size offset] + % ----- Following entries are missing, so they get default values: ----- + /isFixedPitch false % default + /ItalicAngle 0 % default + /UnderlinePosition -100 % default + /UnderlineThickness 50 % default + /PaintType 0 % default + /CharstringType 2 % default + /FontBBox [0 0 0 0] % default + /StrokeWidth 0 % default + >> + +-------------------------------------------------------------------------------- + +CharStrings INDEX (0x000001a1): + count: 2, offSize: 1 + Offsets of INDEX (relative to 422): + [0] = 1 + [1] = 4 + [2] = 213 + Data: + ([GID] (offset): ) + [0] CID 0 (0x000001a7): + 280 endchar % glyph width = 280 + nominalWidthX = 280 + [1] CID 1 (0x000001aa): + 750 -22 31 565 31 47 31 53 95 83 -21 163 -20 hstemhm % glyph width = 750 + nominalWidthX = 750 + 136 89 20 97 48 89 -24 97 30 31 hintmask 0xE2 0xA0 + 716 652 rmoveto + 31 vlineto + -118 -3 -119 3 rlineto + -31 vlineto + 103 0 -47 -27 hvcurveto + -347 vlineto + -142 -97 -80 -95 -47 -118 25 190 vhcurveto + 381 vlineto + 36 2 11 77 vhcurveto + 24 31 hlineto + -3 -35 -74 0 -38 hhcurveto + -38 -75 0 3 -35 hvcurveto + -31 24 vlineto + 77 2 -11 -36 hvcurveto + -377 vlineto + -141 116 -109 136 115 90 93 114 17 vhcurveto + 3 20 0 9 40 vvcurveto + 320 vlineto + 33 0 45 103 vhcurveto + hintmask 0x1D 0x40 + -374 132 rmoveto + 24 -21 23 -27 -31 -18 -25 -22 -25 21 -23 27 31 18 25 23 vhcurveto + 210 hmoveto + 24 -21 23 -27 -31 -18 -25 -22 -25 21 -23 27 31 18 25 23 vhcurveto + -17 230 rmoveto + 22 4 -17 20 -22 hhcurveto + -12 -10 -8 -4 -4 hvcurveto + -145 -130 14 -21 170 94 11 5 9 10 2 12 rlinecurve + endchar + +-------------------------------------------------------------------------------- + +CIDFont's Private DICTs: + Private DICT for Font DICT #0 (0x0000014c): + << + /BlueValues [-22 0 431 448 666 677 683 705] % Original delta values: [-22 22 431 17 218 11 6 22] + /BlueScale 0.04546 + /BlueFuzz 0 + /StdHW 31 + /StdVW 69 + /StemSnapH [22 23 25 26 28 30 31 38 40 42 45 106] % Original delta values: [22 1 2 1 2 2 1 7 2 2 3 61] + /StemSnapV [25 66 69 75 77 83 86 89 92 97 103 107] % Original delta values: [25 41 3 6 2 6 3 3 3 5 6 4] + % ----- Following entries are missing, so they get default values: ----- + /BlueShift 7 % default + /ForceBold false % default + /LanguageGroup 0 % default + /ExpansionFactor 0.06 % default + /initialRandomSeed 0 % default + /defaultWidthX 0 % default + /nominalWidthX 0 % default + >> + +-------------------------------------------------------------------------------- + +Local Subr INDEX for Font DICT #0: + + +-------------------------------------------------------------------------------- + +Info messages: +Info: This is a CIDFont. + +% End of dump + diff --git a/tests/cff/NewCMMath-Regular_1.txt b/tests/cff/NewCMMath-Regular_1.txt new file mode 100644 index 00000000..ee0f9cf8 --- /dev/null +++ b/tests/cff/NewCMMath-Regular_1.txt @@ -0,0 +1,213 @@ +% CFF Dump Output +% File: NewCMMath-Regular_1.otf +% Dumping an OpenType font file. +% CFF data starts at 0x7C and its length is 785 bytes. +% All dumped offsets are relative to 0x7C. + + +-------------------------------------------------------------------------------- + +Header (0x00000000): + major: 1 + minor: 0 + hdrSize: 4 + offSize: 4 + +-------------------------------------------------------------------------------- + +Name INDEX (0x00000004): + count: 1, offSize: 1 + Offsets of INDEX (relative to 8): + [0] = 1 + [1] = 18 + Data: + [0]: (NewCMMath-Regular) + +-------------------------------------------------------------------------------- + +Top DICT INDEX (0x0000001a): + count: 1, offSize: 1 + Offsets of INDEX (relative to 30): + [0] = 1 + [1] = 70 + Data: + [0] (0x0000001f): + << + /ROS << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> + /Notice ((C) 2019-2021 Antonis Tsolomitis. +This work is released under the GUST Font License -- see http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt for details.) % SID 393 + /FontMatrix [0.001 0 0 0.001 0 0] + /FontBBox [-1042 -3060 4082 3560] + /charset 278 % offset + /CharStrings 375 % offset + /CIDCount 65535 + /FDArray 351 % offset + /FDSelect 343 % offset + % ----- Following entries are missing, so they get default values: ----- + /isFixedPitch false % default + /ItalicAngle 0 % default + /UnderlinePosition -100 % default + /UnderlineThickness 50 % default + /PaintType 0 % default + /CharstringType 2 % default + /StrokeWidth 0 % default + /CIDFontVersion 0 % default + /CIDFontRevision 0 % default + /CIDFontType 0 % default + >> + +-------------------------------------------------------------------------------- + +String INDEX (0x00000064): + count: 3, offSize: 1 + Offsets of INDEX (relative to 106): + [0] = 1 + [1] = 6 + [2] = 14 + [3] = 170 + Data: + [0](SID = 391): (Adobe) + [1](SID = 392): (Identity) + [2](SID = 393): ((C) 2019-2021 Antonis Tsolomitis. +This work is released under the GUST Font License -- see http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt for details.) + +-------------------------------------------------------------------------------- + +Global Subr INDEX (0x00000114): + count: 0 + + +-------------------------------------------------------------------------------- + +Charset (0x00000116): + (format 2; [GID] = ): + ([0] = CID 0), + [1] = CID 1, + [2] = CID 2, + +-------------------------------------------------------------------------------- + +FDSelect (0x00000157): + (format 3) + FD #0 for GIDs 0...2 + + +-------------------------------------------------------------------------------- + +Font DICT INDEX (a.k.a. FDArray) (0x0000015f): + count: 1, offSize: 1 + Offsets of INDEX (relative to 355): + [0] = 1 + [1] = 20 + Data: + [0] (0x00000164): + << + /FontMatrix [1 0 0 1 0 0] + /Private [60 283] % [size offset] + % ----- Following entries are missing, so they get default values: ----- + /isFixedPitch false % default + /ItalicAngle 0 % default + /UnderlinePosition -100 % default + /UnderlineThickness 50 % default + /PaintType 0 % default + /CharstringType 2 % default + /FontBBox [0 0 0 0] % default + /StrokeWidth 0 % default + >> + +-------------------------------------------------------------------------------- + +CharStrings INDEX (0x00000177): + count: 3, offSize: 2 + Offsets of INDEX (relative to 385): + [0] = 1 + [1] = 4 + [2] = 188 + [3] = 400 + Data: + ([GID] (offset): ) + [0] CID 0 (0x00000182): + -326 endchar % glyph width = -326 + nominalWidthX = 280 + [1] CID 1 (0x00000185): + 196 -60 106 184 40 184 106 hstem % glyph width = 196 + nominalWidthX = 802 + 356 106 vstem + cntrmask 0xE0 + 740 -81 rmoveto + 7 8 0 13 -7 8 rrcurveto + -283 282 245 0 rlineto + 11 9 9 11 11 -9 9 -11 hvcurveto + -245 0 283 282 rlineto + 7 8 0 13 -7 8 -8 7 -13 0 -8 -7 rrcurveto + -302 -303 -302 303 rlineto + -8 7 -13 0 -8 -7 -7 -8 0 -13 7 -8 rrcurveto + 283 -282 -285 0 rlineto + -11 -9 -9 -11 -11 9 -9 11 hvcurveto + 285 0 -283 -282 rlineto + -7 -8 0 -13 7 -8 8 -7 13 0 8 7 rrcurveto + 302 303 302 -303 rlineto + 8 -7 13 0 8 7 rrcurveto + -278 74 rmoveto + 29 -24 24 -29 -29 -24 -24 -29 -30 24 -23 29 29 24 23 30 vhcurveto + 514 vmoveto + 30 -24 23 -29 -29 -24 -23 -30 -29 24 -24 29 29 24 24 29 vhcurveto + endchar + [2] CID 2 (0x0000023d): + -153 -10 28 404 24 252 -20 hstemhm % glyph width = -153 + nominalWidthX = 453 + 45 55 -55 35 265 34 -27 55 hintmask 0xF2 + 407 131 rmoveto + 0 38 -17 30 -26 24 -38 35 -46 8 -35 6 -80 14 -65 12 0 53 0 32 27 39 95 0 rrcurveto + hintmask 0xF4 + 116 0 5 -81 2 -29 1 -11 12 0 4 0 rrcurveto + 17 0 7 19 hvcurveto + 93 vlineto + 17 0 9 -14 vhcurveto + -5 0 -2 0 -13 -12 -2 -1 -10 -9 -6 -5 -30 21 -38 6 -37 0 -143 0 -34 -75 0 -50 0 -32 14 -26 24 -20 38 -33 38 -7 62 -10 rrcurveto + hintmask 0xEA + 50 -9 81 -14 0 -67 0 -39 -27 -46 -96 0 -96 0 -35 63 -18 68 -3 13 -1 4 -14 0 rrcurveto + -17 0 -7 -20 hvcurveto + -123 vlineto + -17 0 -9 14 vhcurveto + 9 0 19 21 20 22 44 -41 54 -2 24 0 rrcurveto + 130 48 70 71 hvcurveto + -43 526 rmoveto + 19 -17 22 -24 vhcurveto + -16 0 -8 -9 -10 -11 rrcurveto + -131 -146 23 -26 158 114 rlineto + 16 11 9 7 0 19 rrcurveto + endchar + +-------------------------------------------------------------------------------- + +CIDFont's Private DICTs: + Private DICT for Font DICT #0 (0x0000011b): + << + /defaultWidthX 778 + /nominalWidthX 606 + /BlueValues [-22 0 431 448 666 666 677 705] % Original delta values: [-22 22 431 17 218 0 11 28] + /OtherBlues [-206 -205] % Original delta values: [-206 1] + /BlueScale 0.04546 + /BlueShift 6 + /BlueFuzz 0 + /StdHW 40 + /StemSnapH [22 31 36 40 44 56 61 67] % Original delta values: [22 9 5 4 4 12 5 6] + /StdVW 40 + /StemSnapV [40 46 60 64 69 75 79 83 89 95] % Original delta values: [40 6 14 4 5 6 4 4 6 6] + % ----- Following entries are missing, so they get default values: ----- + /ForceBold false % default + /LanguageGroup 0 % default + /ExpansionFactor 0.06 % default + /initialRandomSeed 0 % default + >> + +-------------------------------------------------------------------------------- + +Local Subr INDEX for Font DICT #0: + + +-------------------------------------------------------------------------------- + +Info messages: +Info: This is a CIDFont. + +% End of dump + diff --git a/tests/cff/NotoSansCJKsc-Regular_1.txt b/tests/cff/NotoSansCJKsc-Regular_1.txt new file mode 100644 index 00000000..aacebf2d --- /dev/null +++ b/tests/cff/NotoSansCJKsc-Regular_1.txt @@ -0,0 +1,278 @@ +% CFF Dump Output +% File: NotoSansCJKsc-Regular_1.otf +% Dumping an OpenType font file. +% CFF data starts at 0x7C and its length is 786 bytes. +% All dumped offsets are relative to 0x7C. + + +-------------------------------------------------------------------------------- + +Header (0x00000000): + major: 1 + minor: 0 + hdrSize: 4 + offSize: 4 + +-------------------------------------------------------------------------------- + +Name INDEX (0x00000004): + count: 1, offSize: 1 + Offsets of INDEX (relative to 8): + [0] = 1 + [1] = 22 + Data: + [0]: (NotoSansCJKsc-Regular) + +-------------------------------------------------------------------------------- + +Top DICT INDEX (0x0000001e): + count: 1, offSize: 1 + Offsets of INDEX (relative to 34): + [0] = 1 + [1] = 69 + Data: + [0] (0x00000023): + << + /ROS << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> + /Notice (Copyright 2014-2021 Adobe (http://www.adobe.com/). Noto is a trademark of Google Inc.) % SID 393 + /FontMatrix [0.001 0 0 0.001 0 0] + /FontBBox [-1002 -1048 2928 1808] + /charset 275 % offset + /CharStrings 398 % offset + /CIDCount 65535 + /FDArray 346 % offset + /FDSelect 341 % offset + % ----- Following entries are missing, so they get default values: ----- + /isFixedPitch false % default + /ItalicAngle 0 % default + /UnderlinePosition -100 % default + /UnderlineThickness 50 % default + /PaintType 0 % default + /CharstringType 2 % default + /StrokeWidth 0 % default + /CIDFontVersion 0 % default + /CIDFontRevision 0 % default + /CIDFontType 0 % default + >> + +-------------------------------------------------------------------------------- + +String INDEX (0x00000067): + count: 5, offSize: 1 + Offsets of INDEX (relative to 111): + [0] = 1 + [1] = 6 + [2] = 14 + [3] = 99 + [4] = 128 + [5] = 162 + Data: + [0](SID = 391): (Adobe) + [1](SID = 392): (Identity) + [2](SID = 393): (Copyright 2014-2021 Adobe (http://www.adobe.com/). Noto is a trademark of Google Inc.) + [3](SID = 394): (NotoSansCJKsc-Regular-Generic) + [4](SID = 395): (NotoSansCJKsc-Regular-Proportional) + +-------------------------------------------------------------------------------- + +Global Subr INDEX (0x00000111): + count: 0 + + +-------------------------------------------------------------------------------- + +Charset (0x00000113): + (format 2; [GID] = ): + ([0] = CID 0), + [1] = CID 1, + [2] = CID 2, + [3] = CID 3, + +-------------------------------------------------------------------------------- + +FDSelect (0x00000155): + (format 0; [GID] = ): + [0] = 0, + [1] = 1, + [2] = 1, + [3] = 1, + +-------------------------------------------------------------------------------- + +Font DICT INDEX (a.k.a. FDArray) (0x0000015a): + count: 2, offSize: 1 + Offsets of INDEX (relative to 351): + [0] = 1 + [1] = 24 + [2] = 47 + Data: + [0] (0x00000160): + << + /FontMatrix [1 0 0 1 0 0] + /Private [28 280] % [size offset] + /FontName (NotoSansCJKsc-Regular-Generic) % SID 394 + % ----- Following entries are missing, so they get default values: ----- + /isFixedPitch false % default + /ItalicAngle 0 % default + /UnderlinePosition -100 % default + /UnderlineThickness 50 % default + /PaintType 0 % default + /CharstringType 2 % default + /FontBBox [0 0 0 0] % default + /StrokeWidth 0 % default + >> + [1] (0x00000177): + << + /FontMatrix [1 0 0 1 0 0] + /Private [33 308] % [size offset] + /FontName (NotoSansCJKsc-Regular-Proportional) % SID 395 + % ----- Following entries are missing, so they get default values: ----- + /isFixedPitch false % default + /ItalicAngle 0 % default + /UnderlinePosition -100 % default + /UnderlineThickness 50 % default + /PaintType 0 % default + /CharstringType 2 % default + /FontBBox [0 0 0 0] % default + /StrokeWidth 0 % default + >> + +-------------------------------------------------------------------------------- + +CharStrings INDEX (0x0000018e): + count: 4, offSize: 2 + Offsets of INDEX (relative to 410): + [0] = 1 + [1] = 84 + [2] = 182 + [3] = 265 + [4] = 376 + Data: + ([GID] (offset): ) + [0] CID 0 (0x0000019b): + -120 50 859 91 -50 50 hstemhm % glyph width = defaultWidthX = 1000 + 100 50 700 50 hintmask 0xB8 + 100 -120 rmoveto + 800 1000 -800 hlineto + 400 -459 rmoveto + -318 409 rlineto + 636 hlineto + -286 -450 rmoveto + hintmask 0xD8 + 318 409 rlineto + -818 vlineto + -668 -41 rmoveto + 318 409 318 -409 rlineto + -668 859 rmoveto + 318 -409 -318 -409 rlineto + endchar + [1] CID 1 (0x000001ee): + -11 -13 76 417 77 85 65 hstem % glyph width = -11 + nominalWidthX = 606 + 52 94 315 93 vstem + 303 -13 rmoveto + 133 118 104 180 181 -118 105 -133 -133 -118 -105 -181 -180 118 -104 133 hvcurveto + 76 vmoveto + -94 -63 83 125 125 63 84 94 94 64 -84 -125 -125 -64 -83 -94 hvcurveto + -46 579 rmoveto + 92 hlineto + 127 155 -39 36 -132 -126 rlineto + -5 hlineto + -131 126 -39 -36 rlineto + endchar + [2] CID 2 (0x00000250): + 104 -13 81 665 -20 76 52 hstem % glyph width = 104 + nominalWidthX = 721 + 98 92 345 89 vstem + 361 -13 rmoveto + 149 114 80 235 hvcurveto + 431 -89 -433 vlineto + -176 -77 -56 -97 -96 -75 56 176 vhcurveto + 433 -92 -431 vlineto + -235 113 -80 150 vhcurveto + -50 802 rmoveto + 97 hlineto + 117 126 -41 29 -121 -103 rlineto + -5 hlineto + -123 103 -40 -29 rlineto + endchar + [3] CID 3 (0x000002a3): + -10 -13 79 -45 -21 543 -20 119 65 hstemhm % glyph width = -10 + nominalWidthX = 607 + 84 92 249 91 -79.5 79.5 hintmask 0xBC + 251 -13 rmoveto + hintmask 0xBA + 74 54 39 59 51 hvcurveto + 3 hlineto + hintmask 0x7A + 7 -85 rlineto + hintmask 0x7C + 76 543 -91 hlineto + hintmask 0xBC + -385 vlineto + -64 -52 -39 -28 -56 hhcurveto + -72 -30 43 101 hvcurveto + 333 -92 -344 vlineto + -139 52 -73 115 vhcurveto + 6 655 rmoveto + 91 hlineto + 128 155 -40 36 -131 -126 rlineto + -4 hlineto + -132 126 -39 -36 rlineto + endchar + +-------------------------------------------------------------------------------- + +CIDFont's Private DICTs: + Private DICT for Font DICT #0 (0x00000118): + << + /BlueValues [-250 -250 1100 1100] % Original delta values: [-250 0 1350 0] + /StdHW 40 + /StdVW 40 + /StemSnapH [40 120] % Original delta values: [40 80] + /StemSnapV [40 120] % Original delta values: [40 80] + /LanguageGroup 1 + /defaultWidthX 1000 + /nominalWidthX 107 + % ----- Following entries are missing, so they get default values: ----- + /BlueScale 0.039625 % default + /BlueShift 7 % default + /BlueFuzz 1 % default + /ForceBold false % default + /ExpansionFactor 0.06 % default + /initialRandomSeed 0 % default + >> + Private DICT for Font DICT #1 (0x00000134): + << + /BlueValues [-13 0 543 557 733 747] % Original delta values: [-13 13 543 14 176 14] + /OtherBlues [-250 -229] % Original delta values: [-250 21] + /StdHW 69 + /StdVW 85 + /StemSnapH [69 79 88] % Original delta values: [69 10 9] + /StemSnapV [85 95 111] % Original delta values: [85 10 16] + /defaultWidthX 742 + /nominalWidthX 617 + % ----- Following entries are missing, so they get default values: ----- + /BlueScale 0.039625 % default + /BlueShift 7 % default + /BlueFuzz 1 % default + /ForceBold false % default + /LanguageGroup 0 % default + /ExpansionFactor 0.06 % default + /initialRandomSeed 0 % default + >> + +-------------------------------------------------------------------------------- + +Local Subr INDEX for Font DICT #0: + + +-------------------------------------------------------------------------------- + +Local Subr INDEX for Font DICT #1: + + +-------------------------------------------------------------------------------- + +Info messages: +Info: This is a CIDFont. + +% End of dump + diff --git a/tests/data/cff.tests b/tests/data/cff.tests new file mode 100644 index 00000000..b73de2a6 --- /dev/null +++ b/tests/data/cff.tests @@ -0,0 +1,7 @@ +// These tests act as regression tests to prevent regressions in CFF subsetting + +LatinModernRoman-Regular.otf;290,292 +LatinModernRoman-Regular.otf;580 +NewCMMath-Regular.otf;1034,4789 +NotoSansCJKsc-Regular.otf;230-232 + diff --git a/tests/scripts/gen-tests.py b/tests/scripts/gen-tests.py index e80a7585..9faf5c36 100755 --- a/tests/scripts/gen-tests.py +++ b/tests/scripts/gen-tests.py @@ -9,20 +9,28 @@ FONT_DIR = ROOT / ".." / "fonts" SUBSETS_PATH = ROOT / "src" / "subsets.rs" FONT_TOOLS_PATH = ROOT / "src" / "font_tools.rs" +CFF_PATH = ROOT / "src" / "cff.rs" def main(): gen_font_tools_tests() + gen_cff_tests() gen_subset_tests() def gen_font_tools_tests(): + cff_fonttools_impl("fonttools.tests", Path(FONT_TOOLS_PATH), "test_font_tools") + +def gen_cff_tests(): + cff_fonttools_impl("cff.tests", Path(CFF_PATH), "test_cff_dump") + +def cff_fonttools_impl(test_src, out_path, fn_name): test_string = f"// This file was auto-generated by `{Path(__file__).name}`, do not edit manually.\n\n" test_string += "#[allow(non_snake_case)]\n\n" test_string += f"use crate::*;\n\n" counters = {} - with open(DATA_DIR / "fonttools.tests") as file: + with open(DATA_DIR / test_src) as file: content = file.read().splitlines() for line in content: if line.startswith("//") or len(line.strip()) == 0: @@ -42,9 +50,9 @@ def gen_font_tools_tests(): function_name = f"{font_name_to_function(font_file)}_{counter}" test_string += "#[test] " - test_string += f'fn {function_name}() {{test_font_tools("{font_file}", "{gids}", {counter})}}\n' + test_string += f'fn {function_name}() {{{fn_name}("{font_file}", "{gids}", {counter})}}\n' - with open(Path(FONT_TOOLS_PATH), "w+") as file: + with open(out_path, "w+") as file: file.write(test_string) diff --git a/tests/src/cff.rs b/tests/src/cff.rs new file mode 100644 index 00000000..60c86ca3 --- /dev/null +++ b/tests/src/cff.rs @@ -0,0 +1,21 @@ +// This file was auto-generated by `gen-tests.py`, do not edit manually. + +#[allow(non_snake_case)] +use crate::*; + +#[test] +fn latin_modern_roman_regular_1() { + test_cff_dump("LatinModernRoman-Regular.otf", "290,292", 1) +} +#[test] +fn latin_modern_roman_regular_2() { + test_cff_dump("LatinModernRoman-Regular.otf", "580", 2) +} +#[test] +fn new_c_m_math_regular_1() { + test_cff_dump("NewCMMath-Regular.otf", "1034,4789", 1) +} +#[test] +fn noto_sans_c_j_ksc_regular_1() { + test_cff_dump("NotoSansCJKsc-Regular.otf", "230-232", 1) +} diff --git a/tests/src/main.rs b/tests/src/main.rs index 401a2231..5409892e 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -15,6 +15,7 @@ mod subsets; #[rustfmt::skip] mod font_tools; +mod cff; type Result = std::result::Result>; @@ -28,10 +29,63 @@ struct TestContext { gids: Vec, } +fn test_cff_dump(font_file: &str, gids: &str, num: u16) { + let mut cff_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + cff_path.push("tests/cff"); + let _ = std::fs::create_dir_all(&cff_path); + + let name = Path::new(font_file); + let stem = name.file_stem().unwrap().to_string_lossy().to_string(); + let dump_name = format!("{}_{}.txt", stem, num); + let dump_path: PathBuf = [cff_path.to_string_lossy().to_string(), dump_name.clone()] + .iter() + .collect(); + let otf_name = format!("{}_{}.otf", stem, num); + let otf_path: PathBuf = [cff_path.to_string_lossy().to_string(), otf_name.clone()] + .iter() + .collect(); + + let data = read_file(font_file); + let face = ttf_parser::Face::parse(&data, 0).unwrap(); + let gids_vec: Vec<_> = parse_gids(gids, face.number_of_glyphs()); + let remapper = GlyphRemapper::new_from_glyphs(gids_vec.as_slice()); + let subset = subset(&data, 0, &remapper).unwrap(); + + std::fs::write(otf_path.clone(), subset).unwrap(); + + let cff_dump_util = env!("CFF_DUMP_BIN"); + + let output = Command::new("java") + .args([ + "-jar", + cff_dump_util, + "-otf", + "-c", + "-long", + "-offsets", + otf_path.clone().to_str().unwrap(), + ]) + .output() + .unwrap() + .stdout; + + if !dump_path.exists() || OVERWRITE_REFS { + std::fs::write(dump_path, &output).unwrap(); + panic!("reference file was created/overwritten."); + } else { + let reference = std::fs::read(dump_path).unwrap(); + + assert!( + reference.iter().zip(output.iter()).all(|(a, b)| a == b), + "CFF dump output didn't match." + ); + } +} + fn test_font_tools(font_file: &str, gids: &str, num: u16) { - let mut font_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - font_path.push("tests/ttx"); - let _ = std::fs::create_dir_all(&font_path); + let mut ttx_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + ttx_path.push("tests/ttx"); + let _ = std::fs::create_dir_all(&ttx_path); let name = Path::new(font_file); let stem = name.file_stem().unwrap().to_string_lossy().to_string(); @@ -39,18 +93,18 @@ fn test_font_tools(font_file: &str, gids: &str, num: u16) { let ttx_ref_name = format!("{}_{}_ref.ttx", stem, num); let otf_name = format!("{}_{}.otf", stem, num); let otf_ref_name = format!("{}_{}_ref.otf", stem, num); - let otf_path: PathBuf = [font_path.to_string_lossy().to_string(), otf_name.clone()] + let otf_path: PathBuf = [ttx_path.to_string_lossy().to_string(), otf_name.clone()] .iter() .collect(); let otf_ref_path: PathBuf = - [font_path.to_string_lossy().to_string(), otf_ref_name.clone()] + [ttx_path.to_string_lossy().to_string(), otf_ref_name.clone()] .iter() .collect(); - let ttx_path: PathBuf = [font_path.to_string_lossy().to_string(), ttx_name.clone()] + let ttx_path: PathBuf = [ttx_path.to_string_lossy().to_string(), ttx_name.clone()] .iter() .collect(); let ttx_ref_path: PathBuf = - [font_path.to_string_lossy().to_string(), ttx_ref_name.clone()] + [ttx_path.to_string_lossy().to_string(), ttx_ref_name.clone()] .iter() .collect(); @@ -62,6 +116,7 @@ fn test_font_tools(font_file: &str, gids: &str, num: u16) { std::fs::write(otf_path.clone(), subset).unwrap(); + // Optionally create the subset via fonttools, so that we can compare it to our subset. if FONT_TOOLS_REF { let font_path = get_font_path(font_file); Command::new("fonttools") @@ -103,6 +158,7 @@ fn test_font_tools(font_file: &str, gids: &str, num: u16) { ]) .output() .unwrap(); + panic!("reference file was created/overwritten."); } else { let output = Command::new("fonttools") .args(["ttx", "-f", "-o", "-", otf_path.clone().to_str().unwrap()])