@@ -157,9 +157,175 @@ public function testProcessFormat13AddsGlyphsToSubset(): void
157157 }
158158
159159
160+ public function testProcessFormat14NonDefaultUVSMapsGlyphs (): void
161+ {
162+ // Format 14 subtable with 1 VariationSelector record that has a Non-Default UVS table.
163+ //
164+ // Byte layout (subtable starts at offset 0):
165+ // 0- 1: format = 14 (0x00,0x0E) — consumed by getCIDToGIDMap before this call
166+ // 2- 5: length = 30 (0x00,0x00,0x00,0x1E)
167+ // 6- 9: numVarSelectorRecs = 1 (0x00,0x00,0x00,0x01)
168+ // 10-12: varSelector = 0x0E0100 (3 bytes)
169+ // 13-16: defaultUVSOffset = 0 (absent)
170+ // 17-20: nonDefaultUVSOffset = 21 (0x00,0x00,0x00,0x15) — relative to subtable start
171+ // 21-24: numUVSMappings = 1 (0x00,0x00,0x00,0x01)
172+ // 25-27: unicodeValue = U+0082A6 (0x00,0x82,0xA6)
173+ // 28-29: glyphID = 1142 (0x04,0x76)
174+ $ font = "\x00\x0e" // format = 14
175+ . "\x00\x00\x00\x1e" // length = 30
176+ . "\x00\x00\x00\x01" // numVarSelectorRecords = 1
177+ . "\x0e\x01\x00" // varSelector = U+E0100 (uint24)
178+ . "\x00\x00\x00\x00" // defaultUVSOffset = 0 (absent)
179+ . "\x00\x00\x00\x15" // nonDefaultUVSOffset = 21
180+ . "\x00\x00\x00\x01" // numUVSMappings = 1
181+ . "\x00\x82\xa6" // unicodeValue = U+0082A6 (uint24)
182+ . "\x04\x76" ; // glyphID = 1142
183+
184+ $ instance = $ this ->buildTrueType ($ font , [
185+ 'encodingTables ' => [
186+ [
187+ 'platformID ' => 3 ,
188+ 'encodingID ' => 1 ,
189+ 'offset ' => 0 ,
190+ ],
191+ ],
192+ 'platform_id ' => 3 ,
193+ 'encoding_id ' => 1 ,
194+ 'table ' => [
195+ 'cmap ' => [
196+ 'offset ' => 0 ,
197+ ],
198+ ],
199+ 'type ' => 'TrueTypeUnicode ' ,
200+ ]);
201+
202+ $ this ->invokeMethod ($ instance , 'getCIDToGIDMap ' );
203+ $ fontData = $ this ->getFontData ($ instance );
204+
205+ // Non-default UVS mapping: U+0082A6 → glyph 1142
206+ $ this ->assertSame (1142 , $ fontData ['ctgdata ' ][0x0082A6 ]);
207+ // .notdef fallback must still be set
208+ $ this ->assertSame (0 , $ fontData ['ctgdata ' ][0 ]);
209+ }
210+
211+ public function testProcessFormat14NonDefaultUVSTracksSubglyphs (): void
212+ {
213+ // Same layout as above but with a second mapping and subchars tracking.
214+ //
215+ // Byte layout (subtable starts at offset 0):
216+ // 0- 1: format = 14 (0x00,0x0E)
217+ // 2- 5: length = 35
218+ // 6- 9: numVarSelectorRecs = 1
219+ // 10-12: varSelector = 0x0E0101 (3 bytes)
220+ // 13-16: defaultUVSOffset = 0
221+ // 17-20: nonDefaultUVSOffset = 21
222+ // 21-24: numUVSMappings = 2
223+ // 25-27: unicodeValue[0] = U+0082A6 → glyphID 7961 (0x00,0x82,0xA6 + 0x1F,0x19)
224+ // 30-32: unicodeValue[1] = U+004E4D → glyphID 42 (0x00,0x4E,0x4D + 0x00,0x2A)
225+ $ font = "\x00\x0e" // format = 14
226+ . "\x00\x00\x00\x25" // length = 37
227+ . "\x00\x00\x00\x01" // numVarSelectorRecords = 1
228+ . "\x0e\x01\x01" // varSelector = U+E0101 (uint24)
229+ . "\x00\x00\x00\x00" // defaultUVSOffset = 0
230+ . "\x00\x00\x00\x15" // nonDefaultUVSOffset = 21
231+ . "\x00\x00\x00\x02" // numUVSMappings = 2
232+ . "\x00\x82\xa6" // unicodeValue[0] = U+0082A6
233+ . "\x1f\x19" // glyphID[0] = 7961
234+ . "\x00\x4e\x4d" // unicodeValue[1] = U+004E4D
235+ . "\x00\x2a" ; // glyphID[1] = 42
236+
237+ $ instance = $ this ->buildTrueType ($ font , [
238+ 'encodingTables ' => [
239+ [
240+ 'platformID ' => 3 ,
241+ 'encodingID ' => 1 ,
242+ 'offset ' => 0 ,
243+ ],
244+ ],
245+ 'platform_id ' => 3 ,
246+ 'encoding_id ' => 1 ,
247+ 'table ' => [
248+ 'cmap ' => [
249+ 'offset ' => 0 ,
250+ ],
251+ ],
252+ 'type ' => 'TrueTypeUnicode ' ,
253+ ]);
254+
255+ // Mark U+0082A6 as a subset char to verify subglyphs tracking
256+ $ this ->setProperty ($ instance , 'subchars ' , [0x0082A6 => true ]);
257+
258+ $ this ->invokeMethod ($ instance , 'getCIDToGIDMap ' );
259+ $ fontData = $ this ->getFontData ($ instance );
260+ $ subGlyphs = $ this ->getProperty ($ instance , 'subglyphs ' );
261+
262+ $ this ->assertSame (7961 , $ fontData ['ctgdata ' ][0x0082A6 ]);
263+ $ this ->assertSame (42 , $ fontData ['ctgdata ' ][0x004E4D ]);
264+ // Glyph 7961 must be in the subset (0x0082A6 was a subchar)
265+ $ this ->assertArrayHasKey (7961 , $ subGlyphs );
266+ $ this ->assertTrue ($ subGlyphs [7961 ]);
267+ // Glyph 42 must NOT be in the subset (U+004E4D was not a subchar)
268+ $ this ->assertArrayNotHasKey (42 , $ subGlyphs );
269+ }
270+
271+ public function testProcessFormat14DefaultUVSOnlyAddsNoCtgEntries (): void
272+ {
273+ // Format 14 subtable with 1 VariationSelector record that has only a Default UVS table.
274+ // Default UVS sequences use the standard cmap glyph — no ctgdata entries should be added.
275+ //
276+ // Byte layout (subtable starts at offset 0):
277+ // 0- 1: format = 14
278+ // 2- 5: length = 26
279+ // 6- 9: numVarSelectorRecs = 1
280+ // 10-12: varSelector = 0x0E0100
281+ // 13-16: defaultUVSOffset = 21 (has a Default UVS table)
282+ // 17-20: nonDefaultUVSOffset = 0 (absent)
283+ // 21-24: numUnicodeValueRanges = 1
284+ // 25-27: startUnicodeValue = U+004E4D (uint24)
285+ // 28: additionalCount = 2
286+ $ font = "\x00\x0e" // format = 14
287+ . "\x00\x00\x00\x1d" // length = 29
288+ . "\x00\x00\x00\x01" // numVarSelectorRecords = 1
289+ . "\x0e\x01\x00" // varSelector = U+E0100 (uint24)
290+ . "\x00\x00\x00\x15" // defaultUVSOffset = 21
291+ . "\x00\x00\x00\x00" // nonDefaultUVSOffset = 0 (absent)
292+ . "\x00\x00\x00\x01" // numUnicodeValueRanges = 1
293+ . "\x00\x4e\x4d" // startUnicodeValue = U+004E4D (uint24)
294+ . "\x02" ; // additionalCount = 2
295+
296+ $ instance = $ this ->buildTrueType ($ font , [
297+ 'encodingTables ' => [
298+ [
299+ 'platformID ' => 3 ,
300+ 'encodingID ' => 1 ,
301+ 'offset ' => 0 ,
302+ ],
303+ ],
304+ 'platform_id ' => 3 ,
305+ 'encoding_id ' => 1 ,
306+ 'table ' => [
307+ 'cmap ' => [
308+ 'offset ' => 0 ,
309+ ],
310+ ],
311+ 'type ' => 'TrueTypeUnicode ' ,
312+ ]);
313+
314+ $ this ->invokeMethod ($ instance , 'getCIDToGIDMap ' );
315+ $ fontData = $ this ->getFontData ($ instance );
316+
317+ // Default UVS adds no explicit ctgdata entries beyond .notdef
318+ $ this ->assertSame ([0 => 0 ], $ fontData ['ctgdata ' ]);
319+ }
320+
160321 public function testGetCIDToGIDMapFormat14SetsNotDefGlyph (): void
161322 {
162- $ instance = $ this ->buildTrueType ("\x00\x0e" , [
323+ // Format 14 subtable with numVarSelectorRecords=0: no mappings → only .notdef fallback added.
324+ $ font = "\x00\x0e" // format = 14
325+ . "\x00\x00\x00\x0a" // length = 10
326+ . "\x00\x00\x00\x00" ; // numVarSelectorRecords = 0
327+
328+ $ instance = $ this ->buildTrueType ($ font , [
163329 'encodingTables ' => [
164330 [
165331 'platformID ' => 3 ,
0 commit comments