David PHAM-VAN

Improve TTF Writer compatibility

1 # Changelog 1 # Changelog
2 2
  3 +## 3.8.5
  4 +
  5 +- Improve TTF Writer compatibility
  6 +
3 ## 3.8.4 7 ## 3.8.4
4 8
5 - Improve Multi-Page layout 9 - Improve Multi-Page layout
@@ -372,13 +372,13 @@ class TtfParser { @@ -372,13 +372,13 @@ class TtfParser {
372 final hmtxOffset = tableOffsets[hmtx_table]!; 372 final hmtxOffset = tableOffsets[hmtx_table]!;
373 final unitsPerEm = this.unitsPerEm; 373 final unitsPerEm = this.unitsPerEm;
374 final numOfLongHorMetrics = this.numOfLongHorMetrics; 374 final numOfLongHorMetrics = this.numOfLongHorMetrics;
375 - final defaultadvanceWidth = 375 + final defaultAdvanceWidth =
376 bytes.getUint16(hmtxOffset + (numOfLongHorMetrics - 1) * 4); 376 bytes.getUint16(hmtxOffset + (numOfLongHorMetrics - 1) * 4);
377 377
378 for (var glyphIndex = 0; glyphIndex < numGlyphs; glyphIndex++) { 378 for (var glyphIndex = 0; glyphIndex < numGlyphs; glyphIndex++) {
379 final advanceWidth = glyphIndex < numOfLongHorMetrics 379 final advanceWidth = glyphIndex < numOfLongHorMetrics
380 ? bytes.getUint16(hmtxOffset + glyphIndex * 4) 380 ? bytes.getUint16(hmtxOffset + glyphIndex * 4)
381 - : defaultadvanceWidth; 381 + : defaultAdvanceWidth;
382 final leftBearing = glyphIndex < numOfLongHorMetrics 382 final leftBearing = glyphIndex < numOfLongHorMetrics
383 ? bytes.getInt16(hmtxOffset + glyphIndex * 4 + 2) 383 ? bytes.getInt16(hmtxOffset + glyphIndex * 4 + 2)
384 : bytes.getInt16(hmtxOffset + 384 : bytes.getInt16(hmtxOffset +
@@ -500,7 +500,7 @@ class TtfParser { @@ -500,7 +500,7 @@ class TtfParser {
500 const HAS_SCALE = 0x008; 500 const HAS_SCALE = 0x008;
501 const MORE_COMPONENTS = 0x0020; 501 const MORE_COMPONENTS = 0x0020;
502 const HAS_X_Y_SCALE = 0x0040; 502 const HAS_X_Y_SCALE = 0x0040;
503 - const HAS_TRANFORMATION_MATRIX = 0x0080; 503 + const HAS_TRANSFORMATION_MATRIX = 0x0080;
504 const WE_HAVE_INSTRUCTIONS = 0x0100; 504 const WE_HAVE_INSTRUCTIONS = 0x0100;
505 505
506 final components = <int>[]; 506 final components = <int>[];
@@ -515,7 +515,7 @@ class TtfParser { @@ -515,7 +515,7 @@ class TtfParser {
515 offset += 2; 515 offset += 2;
516 } else if (flags & HAS_X_Y_SCALE != 0) { 516 } else if (flags & HAS_X_Y_SCALE != 0) {
517 offset += 4; 517 offset += 4;
518 - } else if (flags & HAS_TRANFORMATION_MATRIX != 0) { 518 + } else if (flags & HAS_TRANSFORMATION_MATRIX != 0) {
519 offset += 8; 519 offset += 8;
520 } 520 }
521 521
@@ -19,15 +19,16 @@ import 'dart:typed_data'; @@ -19,15 +19,16 @@ import 'dart:typed_data';
19 19
20 import 'ttf_parser.dart'; 20 import 'ttf_parser.dart';
21 21
22 -/// Generate a TTF font copy with the minimal number of glyph to embedd 22 +/// Generate a TTF font copy with the minimal number of glyph to embed
23 /// into the PDF document 23 /// into the PDF document
24 /// 24 ///
25 /// https://opentype.js.org/ 25 /// https://opentype.js.org/
  26 +/// https://github.com/HinTak/Font-Validator
26 class TtfWriter { 27 class TtfWriter {
27 - /// Create a Truetype Writer object 28 + /// Create a TrueType Writer object
28 TtfWriter(this.ttf); 29 TtfWriter(this.ttf);
29 30
30 - /// original truetype file 31 + /// Original TrueType file
31 final TtfParser ttf; 32 final TtfParser ttf;
32 33
33 int _calcTableChecksum(ByteData table) { 34 int _calcTableChecksum(ByteData table) {
@@ -56,7 +57,7 @@ class TtfWriter { @@ -56,7 +57,7 @@ class TtfWriter {
56 } 57 }
57 } 58 }
58 59
59 - int _wordAlign(int offset, [int align = 2]) { 60 + int _wordAlign(int offset, [int align = 4]) {
60 return offset + ((align - (offset % align)) % align); 61 return offset + ((align - (offset % align)) % align);
61 } 62 }
62 63
@@ -105,10 +106,6 @@ class TtfWriter { @@ -105,10 +106,6 @@ class TtfWriter {
105 assert(compounds[compound]! >= 0, 'Unable to find the glyph'); 106 assert(compounds[compound]! >= 0, 'Unable to find the glyph');
106 } 107 }
107 108
108 - // Add one last empty glyph  
109 - final glyph = TtfGlyphInfo(32, Uint8List(0), const <int>[]);  
110 - glyphsInfo.add(glyph);  
111 -  
112 // update compound indices 109 // update compound indices
113 for (final glyph in glyphsInfo) { 110 for (final glyph in glyphsInfo) {
114 if (glyph.compounds.isNotEmpty) { 111 if (glyph.compounds.isNotEmpty) {
@@ -122,19 +119,19 @@ class TtfWriter { @@ -122,19 +119,19 @@ class TtfWriter {
122 _wordAlign(glyphsTableLength + glyph.data.lengthInBytes); 119 _wordAlign(glyphsTableLength + glyph.data.lengthInBytes);
123 } 120 }
124 var offset = 0; 121 var offset = 0;
125 - final glyphsTable = Uint8List(_wordAlign(glyphsTableLength, 4)); 122 + final glyphsTable = Uint8List(_wordAlign(glyphsTableLength));
126 tables[TtfParser.glyf_table] = glyphsTable; 123 tables[TtfParser.glyf_table] = glyphsTable;
127 tablesLength[TtfParser.glyf_table] = glyphsTableLength; 124 tablesLength[TtfParser.glyf_table] = glyphsTableLength;
128 125
129 // Loca 126 // Loca
130 if (ttf.indexToLocFormat == 0) { 127 if (ttf.indexToLocFormat == 0) {
131 tables[TtfParser.loca_table] = 128 tables[TtfParser.loca_table] =
132 - Uint8List(_wordAlign(glyphsInfo.length * 2, 4)); // uint16  
133 - tablesLength[TtfParser.loca_table] = glyphsInfo.length * 2; 129 + Uint8List(_wordAlign((glyphsInfo.length + 1) * 2)); // uint16
  130 + tablesLength[TtfParser.loca_table] = (glyphsInfo.length + 1) * 2;
134 } else { 131 } else {
135 tables[TtfParser.loca_table] = 132 tables[TtfParser.loca_table] =
136 - Uint8List(_wordAlign(glyphsInfo.length * 4, 4)); // uint32  
137 - tablesLength[TtfParser.loca_table] = glyphsInfo.length * 4; 133 + Uint8List(_wordAlign((glyphsInfo.length + 1) * 4)); // uint32
  134 + tablesLength[TtfParser.loca_table] = (glyphsInfo.length + 1) * 4;
138 } 135 }
139 136
140 { 137 {
@@ -151,6 +148,11 @@ class TtfWriter { @@ -151,6 +148,11 @@ class TtfWriter {
151 glyphsTable.setAll(offset, glyph.data); 148 glyphsTable.setAll(offset, glyph.data);
152 offset = _wordAlign(offset + glyph.data.lengthInBytes); 149 offset = _wordAlign(offset + glyph.data.lengthInBytes);
153 } 150 }
  151 + if (ttf.indexToLocFormat == 0) {
  152 + loca.setUint16(index, offset ~/ 2);
  153 + } else {
  154 + loca.setUint32(index, offset);
  155 + }
154 } 156 }
155 157
156 { 158 {
@@ -158,7 +160,7 @@ class TtfWriter { @@ -158,7 +160,7 @@ class TtfWriter {
158 final start = ttf.tableOffsets[TtfParser.head_table]!; 160 final start = ttf.tableOffsets[TtfParser.head_table]!;
159 final len = ttf.tableSize[TtfParser.head_table]!; 161 final len = ttf.tableSize[TtfParser.head_table]!;
160 final head = Uint8List.fromList( 162 final head = Uint8List.fromList(
161 - ttf.bytes.buffer.asUint8List(start, _wordAlign(len, 4))); 163 + ttf.bytes.buffer.asUint8List(start, _wordAlign(len)));
162 head.buffer.asByteData().setUint32(8, 0); // checkSumAdjustment 164 head.buffer.asByteData().setUint32(8, 0); // checkSumAdjustment
163 tables[TtfParser.head_table] = head; 165 tables[TtfParser.head_table] = head;
164 tablesLength[TtfParser.head_table] = len; 166 tablesLength[TtfParser.head_table] = len;
@@ -169,7 +171,7 @@ class TtfWriter { @@ -169,7 +171,7 @@ class TtfWriter {
169 final start = ttf.tableOffsets[TtfParser.maxp_table]!; 171 final start = ttf.tableOffsets[TtfParser.maxp_table]!;
170 final len = ttf.tableSize[TtfParser.maxp_table]!; 172 final len = ttf.tableSize[TtfParser.maxp_table]!;
171 final maxp = Uint8List.fromList( 173 final maxp = Uint8List.fromList(
172 - ttf.bytes.buffer.asUint8List(start, _wordAlign(len, 4))); 174 + ttf.bytes.buffer.asUint8List(start, _wordAlign(len)));
173 maxp.buffer.asByteData().setUint16(4, glyphsInfo.length); 175 maxp.buffer.asByteData().setUint16(4, glyphsInfo.length);
174 tables[TtfParser.maxp_table] = maxp; 176 tables[TtfParser.maxp_table] = maxp;
175 tablesLength[TtfParser.maxp_table] = len; 177 tablesLength[TtfParser.maxp_table] = len;
@@ -180,7 +182,7 @@ class TtfWriter { @@ -180,7 +182,7 @@ class TtfWriter {
180 final start = ttf.tableOffsets[TtfParser.hhea_table]!; 182 final start = ttf.tableOffsets[TtfParser.hhea_table]!;
181 final len = ttf.tableSize[TtfParser.hhea_table]!; 183 final len = ttf.tableSize[TtfParser.hhea_table]!;
182 final hhea = Uint8List.fromList( 184 final hhea = Uint8List.fromList(
183 - ttf.bytes.buffer.asUint8List(start, _wordAlign(len, 4))); 185 + ttf.bytes.buffer.asUint8List(start, _wordAlign(len)));
184 hhea.buffer 186 hhea.buffer
185 .asByteData() 187 .asByteData()
186 .setUint16(34, glyphsInfo.length); // numOfLongHorMetrics 188 .setUint16(34, glyphsInfo.length); // numOfLongHorMetrics
@@ -192,17 +194,17 @@ class TtfWriter { @@ -192,17 +194,17 @@ class TtfWriter {
192 { 194 {
193 // HMTX table 195 // HMTX table
194 final len = 4 * glyphsInfo.length; 196 final len = 4 * glyphsInfo.length;
195 - final hmtx = Uint8List(_wordAlign(len, 4)); 197 + final hmtx = Uint8List(_wordAlign(len));
196 final hmtxOffset = ttf.tableOffsets[TtfParser.hmtx_table]!; 198 final hmtxOffset = ttf.tableOffsets[TtfParser.hmtx_table]!;
197 final hmtxData = hmtx.buffer.asByteData(); 199 final hmtxData = hmtx.buffer.asByteData();
198 final numOfLongHorMetrics = ttf.numOfLongHorMetrics; 200 final numOfLongHorMetrics = ttf.numOfLongHorMetrics;
199 - final defaultadvanceWidth = 201 + final defaultAdvanceWidth =
200 ttf.bytes.getUint16(hmtxOffset + (numOfLongHorMetrics - 1) * 4); 202 ttf.bytes.getUint16(hmtxOffset + (numOfLongHorMetrics - 1) * 4);
201 var index = 0; 203 var index = 0;
202 for (final glyph in glyphsInfo) { 204 for (final glyph in glyphsInfo) {
203 final advanceWidth = glyph.index < numOfLongHorMetrics 205 final advanceWidth = glyph.index < numOfLongHorMetrics
204 ? ttf.bytes.getUint16(hmtxOffset + glyph.index * 4) 206 ? ttf.bytes.getUint16(hmtxOffset + glyph.index * 4)
205 - : defaultadvanceWidth; 207 + : defaultAdvanceWidth;
206 final leftBearing = glyph.index < numOfLongHorMetrics 208 final leftBearing = glyph.index < numOfLongHorMetrics
207 ? ttf.bytes.getInt16(hmtxOffset + glyph.index * 4 + 2) 209 ? ttf.bytes.getInt16(hmtxOffset + glyph.index * 4 + 2)
208 : ttf.bytes.getInt16(hmtxOffset + 210 : ttf.bytes.getInt16(hmtxOffset +
@@ -219,12 +221,12 @@ class TtfWriter { @@ -219,12 +221,12 @@ class TtfWriter {
219 { 221 {
220 // CMAP table 222 // CMAP table
221 const len = 40; 223 const len = 40;
222 - final cmap = Uint8List(_wordAlign(len, 4)); 224 + final cmap = Uint8List(_wordAlign(len));
223 final cmapData = cmap.buffer.asByteData(); 225 final cmapData = cmap.buffer.asByteData();
224 cmapData.setUint16(0, 0); // Table version number 226 cmapData.setUint16(0, 0); // Table version number
225 cmapData.setUint16(2, 1); // Number of encoding tables that follow. 227 cmapData.setUint16(2, 1); // Number of encoding tables that follow.
226 cmapData.setUint16(4, 3); // Platform ID 228 cmapData.setUint16(4, 3); // Platform ID
227 - cmapData.setUint16(6, 1); // Platform-specific encoding ID 229 + cmapData.setUint16(6, 10); // Platform-specific encoding ID
228 cmapData.setUint32(8, 12); // Offset from beginning of table 230 cmapData.setUint32(8, 12); // Offset from beginning of table
229 cmapData.setUint16(12, 12); // Table format 231 cmapData.setUint16(12, 12); // Table format
230 cmapData.setUint32(16, 28); // Table length 232 cmapData.setUint32(16, 28); // Table length
@@ -239,7 +241,7 @@ class TtfWriter { @@ -239,7 +241,7 @@ class TtfWriter {
239 } 241 }
240 242
241 { 243 {
242 - final bytes = <int>[]; 244 + final bytes = BytesBuilder();
243 245
244 final numTables = tables.length; 246 final numTables = tables.length;
245 247
@@ -258,7 +260,20 @@ class TtfWriter { @@ -258,7 +260,20 @@ class TtfWriter {
258 // Create the table directory 260 // Create the table directory
259 var count = 0; 261 var count = 0;
260 var offset = 12 + numTables * 16; 262 var offset = 12 + numTables * 16;
261 - tables.forEach((String name, Uint8List data) { 263 + var headOffset = 0;
  264 +
  265 + final tableKeys = [
  266 + TtfParser.head_table,
  267 + TtfParser.hhea_table,
  268 + TtfParser.maxp_table,
  269 + TtfParser.hmtx_table,
  270 + TtfParser.cmap_table,
  271 + TtfParser.loca_table,
  272 + TtfParser.glyf_table,
  273 + ];
  274 +
  275 + for (final name in tableKeys) {
  276 + final data = tables[name]!;
262 final runes = name.runes.toList(); 277 final runes = name.runes.toList();
263 start.setUint8(12 + count * 16, runes[0]); 278 start.setUint8(12 + count * 16, runes[0]);
264 start.setUint8(12 + count * 16 + 1, runes[1]); 279 start.setUint8(12 + count * 16 + 1, runes[1]);
@@ -268,16 +283,28 @@ class TtfWriter { @@ -268,16 +283,28 @@ class TtfWriter {
268 _calcTableChecksum(data.buffer.asByteData())); // checkSum 283 _calcTableChecksum(data.buffer.asByteData())); // checkSum
269 start.setUint32(12 + count * 16 + 8, offset); // offset 284 start.setUint32(12 + count * 16 + 8, offset); // offset
270 start.setUint32(12 + count * 16 + 12, tablesLength[name]!); // length 285 start.setUint32(12 + count * 16 + 12, tablesLength[name]!); // length
  286 +
  287 + if (name == 'head') {
  288 + headOffset = offset;
  289 + }
271 offset += data.lengthInBytes; 290 offset += data.lengthInBytes;
272 count++; 291 count++;
273 - });  
274 - bytes.addAll(start.buffer.asUint8List()); 292 + }
  293 + bytes.add(start.buffer.asUint8List());
275 294
276 - tables.forEach((String name, Uint8List data) {  
277 - bytes.addAll(data.buffer.asUint8List());  
278 - }); 295 + for (final name in tableKeys) {
  296 + final data = tables[name]!;
  297 + bytes.add(data.buffer.asUint8List());
  298 + }
  299 +
  300 + final output = bytes.toBytes();
  301 +
  302 + final crc = 0xB1B0AFBA - _calcTableChecksum(output.buffer.asByteData());
  303 + output.buffer
  304 + .asByteData()
  305 + .setUint32(headOffset + 8, crc & 0xffffffff); // checkSumAdjustment
279 306
280 - return Uint8List.fromList(bytes); 307 + return output;
281 } 308 }
282 } 309 }
283 } 310 }
@@ -3,7 +3,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl @@ -3,7 +3,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl
3 homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf 3 homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf
4 repository: https://github.com/DavBfr/dart_pdf 4 repository: https://github.com/DavBfr/dart_pdf
5 issue_tracker: https://github.com/DavBfr/dart_pdf/issues 5 issue_tracker: https://github.com/DavBfr/dart_pdf/issues
6 -version: 3.8.4 6 +version: 3.8.5
7 7
8 environment: 8 environment:
9 sdk: ">=2.12.0 <3.0.0" 9 sdk: ">=2.12.0 <3.0.0"