Skip to content

makeSubset() and makeSubsetSIP() write a cmap glyphIdArray that idRangeOffset puts out of reach #156

Description

@jakejackson1

Follow-up to #150, which dropped the unreachable glyphIdArray from repackageTTF() and left the
other two builders alone.

All three write the same format 4 subtable in the same shape, and all three write idRangeOffset = 0
for every segment - which is exactly what tells a reader to resolve that segment through its
idDelta and to look no further. Each then appends a glyphIdArray holding every glyph of every
segment, plus a trailing zero. Nothing can reach it. #140 corrected the length field to count it,
#150 established that it need not be there at all, and makeSubset() and makeSubsetSIP() still
write it.

Measured

tests/data/ttf, cleared font cache, the subtable taken out of the emitted cmap and measured
against the 16 + (8 * segCount) bytes a header and four segment arrays occupy. The subset is the
63-character pangram of #151 - a space, the digits, both cases of the alphabet - or everything the
font maps, where the row says so.

font builder subset subtable segments unreachable
NotoSans-Regular makeSubset pangram 184 5 128 (70%)
Poppins-Regular makeSubset pangram 184 5 128 (70%)
Manjari-Regular makeSubset pangram 184 5 128 (70%)
angerthas makeSubset pangram 212 10 116 (55%)
NotoSans-Regular makeSubset 3,054 characters 12,356 779 6,108 (49%)
Poppins-Regular makeSubset 472 characters 1,696 92 944 (56%)
Manjari-Regular makeSubset 404 characters 2,514 211 810 (32%)
NotoSans-Regular makeSubsetSIP pangram 160 2 128 (80%)
angerthas makeSubsetSIP pangram 148 2 116 (78%)

Over all 31 fonts, makeSubset to the pangram: 2,164 bytes of subtable between them, 852 of it
unreachable. To everything each font maps, which is what the golden masters capture: 219,660 bytes,
207,004 of it unreachable - 6,678 dead bytes per embedded font, and 94% of the subtable.

So a document embedding one subsetted text font carries somewhere between a hundred bytes and six
kilobytes that no reader opens. Small beside the glyf table next to it, which is why this is an
enhancement and not a bug.

It is not the overflow

Neither builder is near the ceiling a uint16 length imposes. The widest subtable either writes over
this corpus is makeSubset on NotoSans-Regular's whole character set, 12,356 bytes, and without the
array it would be 6,248. #150 was repackageTTF() running to 133,080 bytes and the field keeping the
low sixteen bits of it; nothing here does that. The guard #150 added - measure the packed subtable and
raise a FontException rather than let pack('n') wrap - belongs in these two as well when they are
touched.

What a fix looks like

The loop and the trailing zero after $cmap[] = 0; // idRangeOffset of last Segment, removed from
each: src/Fonts/FontSubsetter.php:384 and :965. Six lines.

What it costs

Which is why #150 stopped where it did. Both builders' output is pinned twice over:

Every one of those has to be refreshed and read.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions