@@ -31,6 +31,16 @@ public void Discover(FontSubsettingContext context)
3131 {
3232 ushort oldGid ;
3333 if ( context . OriginalFont . CmapTable . TryGetGlyphId ( codePoint , out oldGid ) )
34+ {
35+ // DEBUGGA: Skriv ut mappningen
36+ Console . WriteLine ( $ "Code point 0x{ codePoint : X} → Glyph ID { oldGid } ") ;
37+ }
38+ else
39+ {
40+ // VIKTIGT: Om detta körs för emoji betyder det att fonten inte har den!
41+ Console . WriteLine ( $ "Code point 0x{ codePoint : X} NOT FOUND in font!") ;
42+ }
43+ if ( context . OriginalFont . CmapTable . TryGetGlyphId ( codePoint , out oldGid ) )
3444 {
3545 if ( ! context . IncludedGlyphs . Contains ( oldGid ) )
3646 {
@@ -48,21 +58,15 @@ public void Discover(FontSubsettingContext context)
4858
4959 public void Rewrite ( FontSubsettingContext context )
5060 {
51- // --- PHASE 3: REWRITE ---
52- // Now context.OldToNewGlyphId IS populated. We can safely map
53- // Unicode -> OldGID -> NewGID.
54-
5561 // Build mapping: Unicode code point → NEW glyph ID in subset
5662 Dictionary < uint , ushort > cmapMapping = new Dictionary < uint , ushort > ( ) ;
5763
58-
5964 foreach ( uint codePoint in context . UsedCodePoints )
6065 {
6166 ushort oldGid ;
6267 if ( context . OriginalFont . CmapTable . TryGetGlyphId ( codePoint , out oldGid ) )
6368 {
6469 ushort newGid ;
65- // Map the old ID to the new dense ID (0, 1, 2...)
6670 if ( context . OldToNewGlyphId . TryGetValue ( oldGid , out newGid ) )
6771 {
6872 cmapMapping [ codePoint ] = newGid ;
@@ -74,55 +78,126 @@ public void Rewrite(FontSubsettingContext context)
7478 }
7579 }
7680
77- // DEBUG: Visa vad som faktiskt läggs in
78- Console . WriteLine ( "=== cmapMapping innehåll ===" ) ;
79- Console . WriteLine ( $ "'T' (84) i cmapMapping: { ( cmapMapping . ContainsKey ( 84 ) ? cmapMapping [ 84 ] . ToString ( ) : "SAKNAS" ) } ") ;
80- Console . WriteLine ( $ "'A' (65) i cmapMapping: { ( cmapMapping . ContainsKey ( 65 ) ? cmapMapping [ 65 ] . ToString ( ) : "SAKNAS" ) } ") ;
81-
82- // Visa också OldToNewGlyphId för 'T'
83- ushort tOldGid ;
84- context . OriginalFont . CmapTable . TryGetGlyphId ( 84 , out tOldGid ) ;
85- Console . WriteLine ( $ "'T' OldGID: { tOldGid } ") ;
86- Console . WriteLine ( $ "OldToNewGlyphId[{ tOldGid } ]: { ( context . OldToNewGlyphId . ContainsKey ( tOldGid ) ? context . OldToNewGlyphId [ tOldGid ] . ToString ( ) : "SAKNAS" ) } ") ;
87-
88-
8981 // Always map code point 0 to .notdef (required by spec)
9082 cmapMapping [ 0 ] = 0 ;
9183
92- // Create format 4 subtable using the provided class names
93- // This creates the internal segment structure (Start/EndCount, IdDelta, etc.)
94- CmapSubtable4 format4 = CmapFormat4 . CreateFromMappings ( cmapMapping ) ;
84+ // Check if we need Format 12 (for code points > 0xFFFF like emoji)
85+ bool needsFormat12 = cmapMapping . Keys . Any ( cp => cp > 0xFFFF ) ;
9586
9687 // Build new cmap table
9788 CmapTable newCmap = new CmapTable ( ) ;
9889 newCmap . Version = 0 ;
99- // Note: NumTables is usually updated automatically when records are added,
100- // but we set it to be explicit.
101- newCmap . NumTables = 2 ;
10290
103- // (3,1) – Windows Unicode BMP
104- EncodingRecord winRecord = new EncodingRecord ( Platforms . Windows , 1 , 0 ) ;
105- winRecord . Subtable = format4 ;
91+ if ( needsFormat12 )
92+ {
93+ // Create Format 12 subtable for full Unicode support
94+ var format12 = CreateFormat12Subtable ( cmapMapping ) ;
95+
96+ // Also create Format 4 for BMP characters (backwards compatibility)
97+ var bmpMapping = cmapMapping . Where ( kvp => kvp . Key <= 0xFFFF )
98+ . ToDictionary ( kvp => kvp . Key , kvp => kvp . Value ) ;
99+ var format4 = CmapFormat4 . CreateFromMappings ( bmpMapping ) ;
100+
101+ // Add Format 12 record (3,10) – Windows Unicode UCS-4 (full range)
102+ EncodingRecord format12Record = new EncodingRecord ( Platforms . Windows , 10 , 0 ) ;
103+ format12Record . Subtable = format12 ;
104+ newCmap . EncodingRecords . Add ( format12Record ) ;
105+ newCmap . SubTables . Add ( format12 ) ;
106+
107+ // Add Format 4 record (3,1) – Windows Unicode BMP (backwards compatibility)
108+ EncodingRecord format4Record = new EncodingRecord ( Platforms . Windows , 1 , 0 ) ;
109+ format4Record . Subtable = format4 ;
110+ newCmap . EncodingRecords . Add ( format4Record ) ;
111+ newCmap . SubTables . Add ( format4 ) ;
112+
113+ newCmap . NumTables = 2 ;
114+ }
115+ else
116+ {
117+ // Only BMP characters - Format 4 is sufficient
118+ CmapSubtable4 format4 = CmapFormat4 . CreateFromMappings ( cmapMapping ) ;
106119
107- // (0,3 ) – Unicode BMP
108- EncodingRecord unicodeRecord = new EncodingRecord ( Platforms . Unicode , 3 , 0 ) ;
109- unicodeRecord . Subtable = format4 ;
120+ // (3,1 ) – Windows Unicode BMP
121+ EncodingRecord winRecord = new EncodingRecord ( Platforms . Windows , 1 , 0 ) ;
122+ winRecord . Subtable = format4 ;
110123
111- newCmap . EncodingRecords . Add ( winRecord ) ;
112- newCmap . EncodingRecords . Add ( unicodeRecord ) ;
124+ // (0,3) – Unicode BMP
125+ EncodingRecord unicodeRecord = new EncodingRecord ( Platforms . Unicode , 3 , 0 ) ;
126+ unicodeRecord . Subtable = format4 ;
113127
114- // Add the subtable to the table's internal list
115- newCmap . SubTables . Add ( format4 ) ;
128+ newCmap . EncodingRecords . Add ( winRecord ) ;
129+ newCmap . EncodingRecords . Add ( unicodeRecord ) ;
130+ newCmap . SubTables . Add ( format4 ) ;
131+ newCmap . NumTables = 2 ;
132+ }
116133
117- // Replace cmap in subset font.
118- // Now GetMinCharCode() will return the correct value to the validator.
119134 context . SubsetFont . AddOrReplaceTable ( newCmap ) ;
135+ }
136+
137+ private CmapSubtable12 CreateFormat12Subtable ( Dictionary < uint , ushort > mapping )
138+ {
139+ var subtable = new CmapSubtable12 ( ) ;
140+
141+ // Sort by code point
142+ var sortedMappings = mapping . OrderBy ( kvp => kvp . Key ) . ToList ( ) ;
143+
144+ if ( sortedMappings . Count == 0 )
145+ {
146+ subtable . NumGroups = 0 ;
147+ subtable . Length = 16 ; // Header only
148+ return subtable ;
149+ }
150+
151+ // Build sequential groups
152+ uint currentStart = sortedMappings [ 0 ] . Key ;
153+ uint currentStartGid = sortedMappings [ 0 ] . Value ;
154+ uint currentEnd = currentStart ;
155+
156+ for ( int i = 1 ; i < sortedMappings . Count ; i ++ )
157+ {
158+ uint codePoint = sortedMappings [ i ] . Key ;
159+ ushort glyphId = sortedMappings [ i ] . Value ;
160+
161+ // Check if this continues the current sequential group
162+ bool isSequential = ( codePoint == currentEnd + 1 ) &&
163+ ( glyphId == currentStartGid + ( codePoint - currentStart ) ) ;
164+
165+ if ( isSequential )
166+ {
167+ // Extend current group
168+ currentEnd = codePoint ;
169+ }
170+ else
171+ {
172+ // Save current group and start new one
173+ subtable . Groups . Add ( new SequencialMapGroup
174+ {
175+ StartCharCode = currentStart ,
176+ EndCharCode = currentEnd ,
177+ StartGlyphId = currentStartGid
178+ } ) ;
179+
180+ currentStart = codePoint ;
181+ currentStartGid = glyphId ;
182+ currentEnd = codePoint ;
183+ }
184+ }
185+
186+ // Add final group
187+ subtable . Groups . Add ( new SequencialMapGroup
188+ {
189+ StartCharCode = currentStart ,
190+ EndCharCode = currentEnd ,
191+ StartGlyphId = currentStartGid
192+ } ) ;
193+
194+ // Update metadata
195+ subtable . NumGroups = ( uint ) subtable . Groups . Count ;
196+
197+ // Calculate length: header (16 bytes) + groups (12 bytes each)
198+ subtable . Length = 16 + ( subtable . NumGroups * 12 ) ;
120199
121- // DEBUG: Verifiera direkt efter att tabellen lagts till
122- Console . WriteLine ( "=== CmapSubsetProcessor.Rewrite DONE ===" ) ;
123- Console . WriteLine ( $ " SubsetFont hash: { context . SubsetFont . GetHashCode ( ) } ") ;
124- Console . WriteLine ( $ " CmapTable hash: { context . SubsetFont . CmapTable . GetHashCode ( ) } ") ;
125- Console . WriteLine ( $ " MapCharToGlyph('T'): { context . SubsetFont . CmapTable . MapCharToGlyph ( 'T' ) } ") ;
200+ return subtable ;
126201 }
127202 }
128203}
0 commit comments