David PHAM-VAN

Add Unicode support

@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 DART_SRC=$(shell find . -name '*.dart') 15 DART_SRC=$(shell find . -name '*.dart')
16 CLNG_SRC=$(shell find printing/ios -name '*.java' -o -name '*.m' -o -name '*.h') $(shell find printing/android -name '*.java' -o -name '*.m' -o -name '*.h') 16 CLNG_SRC=$(shell find printing/ios -name '*.java' -o -name '*.m' -o -name '*.h') $(shell find printing/android -name '*.java' -o -name '*.m' -o -name '*.h')
17 SWFT_SRC=$(shell find . -name '*.swift') 17 SWFT_SRC=$(shell find . -name '*.swift')
18 - FONTS=pdf/open-sans.ttf pdf/roboto.ttf 18 + FONTS=pdf/open-sans.ttf pdf/roboto.ttf pdf/noto-sans.ttf
19 19
20 all: $(FONTS) format 20 all: $(FONTS) format
21 21
@@ -25,6 +25,9 @@ pdf/open-sans.ttf: @@ -25,6 +25,9 @@ pdf/open-sans.ttf:
25 pdf/roboto.ttf: 25 pdf/roboto.ttf:
26 curl -L "https://github.com/google/fonts/raw/master/apache/robotomono/RobotoMono-Regular.ttf" > $@ 26 curl -L "https://github.com/google/fonts/raw/master/apache/robotomono/RobotoMono-Regular.ttf" > $@
27 27
  28 +pdf/noto-sans.ttf:
  29 + curl -L "https://raw.githubusercontent.com/google/fonts/master/ofl/notosans/NotoSans-Regular.ttf" > $@
  30 +
28 format: format-dart format-clang format-swift 31 format: format-dart format-clang format-swift
29 32
30 format-dart: $(DART_SRC) 33 format-dart: $(DART_SRC)
1 # 1.3.5 1 # 1.3.5
2 * Add some color functions 2 * Add some color functions
3 * Remove color constants from PdfColor, use PdfColors 3 * Remove color constants from PdfColor, use PdfColors
  4 +* Add TTF Font SubSetting
  5 +* Add Unicode support for TTF Fonts
4 6
5 # 1.3.4 7 # 1.3.4
6 * Add available dimensions for PdfPageFormat 8 * Add available dimensions for PdfPageFormat
@@ -54,8 +54,10 @@ part 'src/polygon.dart'; @@ -54,8 +54,10 @@ part 'src/polygon.dart';
54 part 'src/rect.dart'; 54 part 'src/rect.dart';
55 part 'src/stream.dart'; 55 part 'src/stream.dart';
56 part 'src/ttf_parser.dart'; 56 part 'src/ttf_parser.dart';
  57 +part 'src/ttf_writer.dart';
57 part 'src/ttffont.dart'; 58 part 'src/ttffont.dart';
58 part 'src/type1_font.dart'; 59 part 'src/type1_font.dart';
59 part 'src/type1_fonts.dart'; 60 part 'src/type1_fonts.dart';
  61 +part 'src/unicode_cmap.dart';
60 part 'src/xobject.dart'; 62 part 'src/xobject.dart';
61 part 'src/xref.dart'; 63 part 'src/xref.dart';
@@ -153,4 +153,11 @@ abstract class PdfFont extends PdfObject { @@ -153,4 +153,11 @@ abstract class PdfFont extends PdfObject {
153 153
154 @override 154 @override
155 String toString() => 'Font($fontName)'; 155 String toString() => 'Font($fontName)';
  156 +
  157 + PdfStream putText(String text) {
  158 + return PdfStream()
  159 + ..putBytes(latin1.encode('('))
  160 + ..putTextBytes(latin1.encode(text))
  161 + ..putBytes(latin1.encode(')'));
  162 + }
156 } 163 }
@@ -30,7 +30,7 @@ class PdfFontDescriptor extends PdfObject { @@ -30,7 +30,7 @@ class PdfFontDescriptor extends PdfObject {
30 30
31 params['/FontName'] = PdfStream.string('/' + ttfFont.fontName); 31 params['/FontName'] = PdfStream.string('/' + ttfFont.fontName);
32 params['/FontFile2'] = file.ref(); 32 params['/FontFile2'] = file.ref();
33 - params['/Flags'] = PdfStream.intNum(32); 33 + params['/Flags'] = PdfStream.intNum(ttfFont.font.unicode ? 4 : 32);
34 params['/FontBBox'] = PdfStream() 34 params['/FontBBox'] = PdfStream()
35 ..putIntArray(<int>[ 35 ..putIntArray(<int>[
36 (ttfFont.font.xMin / ttfFont.font.unitsPerEm * 1000).toInt(), 36 (ttfFont.font.xMin / ttfFont.font.unitsPerEm * 1000).toInt(),
@@ -195,7 +195,7 @@ class PdfGraphics { @@ -195,7 +195,7 @@ class PdfGraphics {
195 buf.putString(' Td ${font.name} '); 195 buf.putString(' Td ${font.name} ');
196 buf.putNum(size); 196 buf.putNum(size);
197 buf.putString(' Tf '); 197 buf.putString(' Tf ');
198 - buf.putText(s); 198 + buf.putStream(font.putText(s));
199 buf.putString(' Tj ET\n'); 199 buf.putString(' Tj ET\n');
200 } 200 }
201 201
@@ -16,6 +16,17 @@ @@ -16,6 +16,17 @@
16 16
17 part of pdf; 17 part of pdf;
18 18
  19 +class TtfGlyphInfo {
  20 + TtfGlyphInfo(this.index, this.data, this.compounds);
  21 +
  22 + final int index;
  23 + final Uint8List data;
  24 + final List<int> compounds;
  25 +
  26 + @override
  27 + String toString() => 'Glyph $index $compounds';
  28 +}
  29 +
19 class TtfParser { 30 class TtfParser {
20 TtfParser(this.bytes) { 31 TtfParser(this.bytes) {
21 final int numTables = bytes.getUint16(4); 32 final int numTables = bytes.getUint16(4);
@@ -23,56 +34,61 @@ class TtfParser { @@ -23,56 +34,61 @@ class TtfParser {
23 for (int i = 0; i < numTables; i++) { 34 for (int i = 0; i < numTables; i++) {
24 final String name = utf8.decode(bytes.buffer.asUint8List(i * 16 + 12, 4)); 35 final String name = utf8.decode(bytes.buffer.asUint8List(i * 16 + 12, 4));
25 final int offset = bytes.getUint32(i * 16 + 20); 36 final int offset = bytes.getUint32(i * 16 + 20);
26 - _tableOffsets[name] = offset; 37 + final int size = bytes.getUint32(i * 16 + 24);
  38 + tableOffsets[name] = offset;
  39 + tableSize[name] = size;
27 } 40 }
28 41
29 _parseFontName(); 42 _parseFontName();
30 _parseCMap(); 43 _parseCMap();
31 _parseIndexes(); 44 _parseIndexes();
32 - _parseGlyf(); 45 + _parseGlyphs();
33 } 46 }
34 47
35 - static const String _HEAD = 'head';  
36 - static const String _NAME = 'name';  
37 - static const String _HMTX = 'hmtx';  
38 - static const String _HHEA = 'hhea';  
39 - static const String _CMAP = 'cmap';  
40 - static const String _MAXP = 'maxp';  
41 - static const String _LOCA = 'loca';  
42 - static const String _GLYF = 'glyf'; 48 + static const String head_table = 'head';
  49 + static const String name_table = 'name';
  50 + static const String hmtx_table = 'hmtx';
  51 + static const String hhea_table = 'hhea';
  52 + static const String cmap_table = 'cmap';
  53 + static const String maxp_table = 'maxp';
  54 + static const String loca_table = 'loca';
  55 + static const String glyf_table = 'glyf';
43 56
44 final ByteData bytes; 57 final ByteData bytes;
45 - final Map<String, int> _tableOffsets = <String, int>{}; 58 + final Map<String, int> tableOffsets = <String, int>{};
  59 + final Map<String, int> tableSize = <String, int>{};
46 String _fontName; 60 String _fontName;
47 final Map<int, int> charToGlyphIndexMap = <int, int>{}; 61 final Map<int, int> charToGlyphIndexMap = <int, int>{};
48 final List<int> glyphOffsets = <int>[]; 62 final List<int> glyphOffsets = <int>[];
49 final Map<int, PdfFontMetrics> glyphInfoMap = <int, PdfFontMetrics>{}; 63 final Map<int, PdfFontMetrics> glyphInfoMap = <int, PdfFontMetrics>{};
50 64
51 - int get unitsPerEm => bytes.getUint16(_tableOffsets[_HEAD] + 18); 65 + int get unitsPerEm => bytes.getUint16(tableOffsets[head_table] + 18);
52 66
53 - int get xMin => bytes.getInt16(_tableOffsets[_HEAD] + 36); 67 + int get xMin => bytes.getInt16(tableOffsets[head_table] + 36);
54 68
55 - int get yMin => bytes.getInt16(_tableOffsets[_HEAD] + 38); 69 + int get yMin => bytes.getInt16(tableOffsets[head_table] + 38);
56 70
57 - int get xMax => bytes.getInt16(_tableOffsets[_HEAD] + 40); 71 + int get xMax => bytes.getInt16(tableOffsets[head_table] + 40);
58 72
59 - int get yMax => bytes.getInt16(_tableOffsets[_HEAD] + 42); 73 + int get yMax => bytes.getInt16(tableOffsets[head_table] + 42);
60 74
61 - int get indexToLocFormat => bytes.getInt16(_tableOffsets[_HEAD] + 50); 75 + int get indexToLocFormat => bytes.getInt16(tableOffsets[head_table] + 50);
62 76
63 - int get ascent => bytes.getInt16(_tableOffsets[_HHEA] + 4); 77 + int get ascent => bytes.getInt16(tableOffsets[hhea_table] + 4);
64 78
65 - int get descent => bytes.getInt16(_tableOffsets[_HHEA] + 6); 79 + int get descent => bytes.getInt16(tableOffsets[hhea_table] + 6);
66 80
67 - int get numOfLongHorMetrics => bytes.getInt16(_tableOffsets[_HHEA] + 34); 81 + int get numOfLongHorMetrics => bytes.getInt16(tableOffsets[hhea_table] + 34);
68 82
69 - int get numGlyphs => bytes.getInt16(_tableOffsets[_MAXP] + 4); 83 + int get numGlyphs => bytes.getInt16(tableOffsets[maxp_table] + 4);
70 84
71 String get fontName => _fontName; 85 String get fontName => _fontName;
72 86
  87 + bool get unicode => bytes.getUint32(0) == 0x10000;
  88 +
73 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html 89 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
74 void _parseFontName() { 90 void _parseFontName() {
75 - final int basePosition = _tableOffsets[_NAME]; 91 + final int basePosition = tableOffsets[name_table];
76 final int count = bytes.getUint16(basePosition + 2); 92 final int count = bytes.getUint16(basePosition + 2);
77 final int stringOffset = bytes.getUint16(basePosition + 4); 93 final int stringOffset = bytes.getUint16(basePosition + 4);
78 int pos = basePosition + 6; 94 int pos = basePosition + 6;
@@ -105,7 +121,7 @@ class TtfParser { @@ -105,7 +121,7 @@ class TtfParser {
105 } 121 }
106 122
107 void _parseCMap() { 123 void _parseCMap() {
108 - final int basePosition = _tableOffsets[_CMAP]; 124 + final int basePosition = tableOffsets[cmap_table];
109 final int numSubTables = bytes.getUint16(basePosition + 2); 125 final int numSubTables = bytes.getUint16(basePosition + 2);
110 for (int i = 0; i < numSubTables; i++) { 126 for (int i = 0; i < numSubTables; i++) {
111 final int offset = bytes.getUint32(basePosition + i * 8 + 8); 127 final int offset = bytes.getUint32(basePosition + i * 8 + 8);
@@ -190,7 +206,7 @@ class TtfParser { @@ -190,7 +206,7 @@ class TtfParser {
190 } 206 }
191 207
192 void _parseIndexes() { 208 void _parseIndexes() {
193 - final int basePosition = _tableOffsets[_LOCA]; 209 + final int basePosition = tableOffsets[loca_table];
194 final int numGlyphs = this.numGlyphs; 210 final int numGlyphs = this.numGlyphs;
195 if (indexToLocFormat == 0) { 211 if (indexToLocFormat == 0) {
196 for (int i = 0; i < numGlyphs; i++) { 212 for (int i = 0; i < numGlyphs; i++) {
@@ -204,9 +220,9 @@ class TtfParser { @@ -204,9 +220,9 @@ class TtfParser {
204 } 220 }
205 221
206 /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html 222 /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html
207 - void _parseGlyf() {  
208 - final int baseOffset = _tableOffsets[_GLYF];  
209 - final int hmtxOffset = _tableOffsets[_HMTX]; 223 + void _parseGlyphs() {
  224 + final int baseOffset = tableOffsets[glyf_table];
  225 + final int hmtxOffset = tableOffsets[hmtx_table];
210 final int unitsPerEm = this.unitsPerEm; 226 final int unitsPerEm = this.unitsPerEm;
211 int glyphIndex = 0; 227 int glyphIndex = 0;
212 for (int offset in glyphOffsets) { 228 for (int offset in glyphOffsets) {
@@ -228,4 +244,117 @@ class TtfParser { @@ -228,4 +244,117 @@ class TtfParser {
228 glyphIndex++; 244 glyphIndex++;
229 } 245 }
230 } 246 }
  247 +
  248 + /// http://stevehanov.ca/blog/?id=143
  249 + TtfGlyphInfo readGlyph(int index) {
  250 + assert(index != null);
  251 + assert(index < glyphOffsets.length);
  252 +
  253 + final int start = tableOffsets[glyf_table] + glyphOffsets[index];
  254 +
  255 + final int numberOfContours = bytes.getInt16(start);
  256 + assert(numberOfContours >= -1);
  257 +
  258 + if (numberOfContours == -1) {
  259 + return _readCompoundGlyph(index, start, start + 10);
  260 + } else {
  261 + return _readSimpleGlyph(index, start, start + 10, numberOfContours);
  262 + }
  263 + }
  264 +
  265 + TtfGlyphInfo _readSimpleGlyph(
  266 + int glyph, int start, int offset, int numberOfContours) {
  267 + const int X_IS_BYTE = 2;
  268 + const int Y_IS_BYTE = 4;
  269 + const int REPEAT = 8;
  270 + const int X_DELTA = 16;
  271 + const int Y_DELTA = 32;
  272 +
  273 + int numPoints = 1;
  274 +
  275 + for (int i = 0; i < numberOfContours; i++) {
  276 + numPoints = math.max(numPoints, bytes.getUint16(offset) + 1);
  277 + offset += 2;
  278 + }
  279 +
  280 + // skip over intructions
  281 + offset += bytes.getUint16(offset) + 2;
  282 +
  283 + if (numberOfContours == 0) {
  284 + return TtfGlyphInfo(
  285 + glyph,
  286 + Uint8List.view(bytes.buffer, start, offset - start),
  287 + <int>[],
  288 + );
  289 + }
  290 +
  291 + final List<int> flags = <int>[];
  292 +
  293 + for (int i = 0; i < numPoints; i++) {
  294 + final int flag = bytes.getUint8(offset++);
  295 + flags.add(flag);
  296 +
  297 + if (flag & REPEAT != 0) {
  298 + int repeatCount = bytes.getUint8(offset++);
  299 + assert(repeatCount > 0);
  300 + i += repeatCount;
  301 + while (repeatCount-- > 0) {
  302 + flags.add(flag);
  303 + }
  304 + }
  305 + }
  306 +
  307 + int byteFlag = X_IS_BYTE;
  308 + int deltaFlag = X_DELTA;
  309 + for (int a = 0; a < 2; a++) {
  310 + for (int i = 0; i < numPoints; i++) {
  311 + final int flag = flags[i];
  312 + if (flag & byteFlag != 0) {
  313 + offset++;
  314 + } else if (~flag & deltaFlag != 0) {
  315 + offset += 2;
  316 + }
  317 + }
  318 + byteFlag = Y_IS_BYTE;
  319 + deltaFlag = Y_DELTA;
  320 + }
  321 +
  322 + return TtfGlyphInfo(
  323 + glyph,
  324 + Uint8List.view(bytes.buffer, start, offset - start),
  325 + <int>[],
  326 + );
  327 + }
  328 +
  329 + TtfGlyphInfo _readCompoundGlyph(int glyph, int start, int offset) {
  330 + const int ARG_1_AND_2_ARE_WORDS = 1;
  331 + const int MORE_COMPONENTS = 32;
  332 + const int WE_HAVE_INSTRUCTIONS = 256;
  333 +
  334 + final List<int> components = <int>[];
  335 + bool hasInstructions = false;
  336 + int flags = MORE_COMPONENTS;
  337 +
  338 + while (flags & MORE_COMPONENTS != 0) {
  339 + flags = bytes.getUint16(offset);
  340 + final int glyphIndex = bytes.getUint16(offset + 2);
  341 + offset += (flags & ARG_1_AND_2_ARE_WORDS != 0) ? 8 : 6;
  342 +
  343 + components.add(glyphIndex);
  344 + if (flags & WE_HAVE_INSTRUCTIONS != 0) {
  345 + assert(!hasInstructions); // Not already set
  346 + hasInstructions = true;
  347 + }
  348 + }
  349 +
  350 + if (hasInstructions) {
  351 + offset += bytes.getUint16(offset) + 2;
  352 + }
  353 +
  354 + return TtfGlyphInfo(
  355 + glyph,
  356 + Uint8List.view(bytes.buffer, start, offset - start),
  357 + components,
  358 + );
  359 + }
231 } 360 }
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of pdf;
  18 +
  19 +/// https://opentype.js.org/
  20 +class TtfWriter {
  21 + TtfWriter(this.ttf);
  22 +
  23 + final TtfParser ttf;
  24 +
  25 + int _calcTableChecksum(ByteData table) {
  26 + assert(table.lengthInBytes % 4 == 0);
  27 + int sum = 0;
  28 + for (int i = 0; i < table.lengthInBytes - 3; i += 4) {
  29 + sum = (sum + table.getUint32(i)) & 0xffffffff;
  30 + }
  31 + return sum;
  32 + }
  33 +
  34 + void _updateCompoundGlyph(TtfGlyphInfo glyph, Map<int, int> compoundMap) {
  35 + const int ARG_1_AND_2_ARE_WORDS = 1;
  36 + const int MORE_COMPONENTS = 32;
  37 +
  38 + int offset = 10;
  39 + final ByteData bytes = glyph.data.buffer
  40 + .asByteData(glyph.data.offsetInBytes, glyph.data.lengthInBytes);
  41 + int flags = MORE_COMPONENTS;
  42 +
  43 + while (flags & MORE_COMPONENTS != 0) {
  44 + flags = bytes.getUint16(offset);
  45 + final int glyphIndex = bytes.getUint16(offset + 2);
  46 + bytes.setUint16(offset + 2, compoundMap[glyphIndex]);
  47 + offset += (flags & ARG_1_AND_2_ARE_WORDS != 0) ? 8 : 6;
  48 + }
  49 + }
  50 +
  51 + int _wordAlign(int offset, [int align = 2]) {
  52 + return offset + ((align - (offset % align)) % align);
  53 + }
  54 +
  55 + Uint8List withChars(List<int> chars) {
  56 + final Map<String, Uint8List> tables = <String, Uint8List>{};
  57 + final Map<String, int> tablesLength = <String, int>{};
  58 +
  59 + // Create the glyphs table
  60 + final List<TtfGlyphInfo> glyphsInfo = <TtfGlyphInfo>[];
  61 + final Map<int, int> compounds = <int, int>{};
  62 +
  63 + for (int index = 0; index < chars.length; index++) {
  64 + if (chars[index] == 32) {
  65 + final TtfGlyphInfo glyph = TtfGlyphInfo(32, Uint8List(0), <int>[]);
  66 + glyphsInfo.add(glyph);
  67 + continue;
  68 + }
  69 +
  70 + final TtfGlyphInfo glyph = ttf.readGlyph(
  71 + chars[index] == 0 ? 0 : ttf.charToGlyphIndexMap[chars[index]]);
  72 + for (int g in glyph.compounds) {
  73 + compounds[g] = null;
  74 + }
  75 + glyphsInfo.add(glyph);
  76 + }
  77 +
  78 + // Add compound glyphs
  79 + for (int compound in compounds.keys) {
  80 + final int index = chars.indexOf(compound);
  81 + if (index >= 0) {
  82 + compounds[compound] = index;
  83 + } else {
  84 + compounds[compound] = glyphsInfo.length;
  85 + final TtfGlyphInfo glyph = ttf.readGlyph(compound);
  86 + assert(glyph.compounds.isEmpty); // This is a simple glyph
  87 + glyphsInfo.add(glyph);
  88 + }
  89 + }
  90 +
  91 + // Add one last empty glyph
  92 + final TtfGlyphInfo glyph = TtfGlyphInfo(32, Uint8List(0), <int>[]);
  93 + glyphsInfo.add(glyph);
  94 +
  95 + // update compound indices
  96 + for (TtfGlyphInfo glyph in glyphsInfo) {
  97 + if (glyph.compounds.isNotEmpty) {
  98 + _updateCompoundGlyph(glyph, compounds);
  99 + }
  100 + }
  101 +
  102 + int glyphsTableLength = 0;
  103 + for (TtfGlyphInfo glyph in glyphsInfo) {
  104 + glyphsTableLength =
  105 + _wordAlign(glyphsTableLength + glyph.data.lengthInBytes);
  106 + }
  107 + int offset = 0;
  108 + final Uint8List glyphsTable = Uint8List(_wordAlign(glyphsTableLength, 4));
  109 + tables[TtfParser.glyf_table] = glyphsTable;
  110 + tablesLength[TtfParser.glyf_table] = glyphsTableLength;
  111 +
  112 + // Loca
  113 + if (ttf.indexToLocFormat == 0) {
  114 + tables[TtfParser.loca_table] =
  115 + Uint8List(_wordAlign(glyphsInfo.length * 2, 4)); // uint16
  116 + tablesLength[TtfParser.loca_table] = glyphsInfo.length * 2;
  117 + } else {
  118 + tables[TtfParser.loca_table] =
  119 + Uint8List(_wordAlign(glyphsInfo.length * 4, 4)); // uint32
  120 + tablesLength[TtfParser.loca_table] = glyphsInfo.length * 4;
  121 + }
  122 +
  123 + {
  124 + final ByteData loca = tables[TtfParser.loca_table].buffer.asByteData();
  125 + int index = 0;
  126 + for (TtfGlyphInfo glyph in glyphsInfo) {
  127 + if (ttf.indexToLocFormat == 0) {
  128 + loca.setUint16(index, offset ~/ 2);
  129 + index += 2;
  130 + } else {
  131 + loca.setUint32(index, offset);
  132 + index += 4;
  133 + }
  134 + glyphsTable.setAll(offset, glyph.data);
  135 + offset = _wordAlign(offset + glyph.data.lengthInBytes);
  136 + }
  137 + }
  138 +
  139 + {
  140 + // Head table
  141 + final int start = ttf.tableOffsets[TtfParser.head_table];
  142 + final int len = ttf.tableSize[TtfParser.head_table];
  143 + final Uint8List head = Uint8List.fromList(
  144 + ttf.bytes.buffer.asUint8List(start, _wordAlign(len, 4)));
  145 + head.buffer.asByteData().setUint32(8, 0); // checkSumAdjustment
  146 + tables[TtfParser.head_table] = head;
  147 + tablesLength[TtfParser.head_table] = len;
  148 + }
  149 +
  150 + {
  151 + // MaxP table
  152 + final int start = ttf.tableOffsets[TtfParser.maxp_table];
  153 + final int len = ttf.tableSize[TtfParser.maxp_table];
  154 + final Uint8List maxp = Uint8List.fromList(
  155 + ttf.bytes.buffer.asUint8List(start, _wordAlign(len, 4)));
  156 + maxp.buffer.asByteData().setUint16(4, glyphsInfo.length);
  157 + tables[TtfParser.maxp_table] = maxp;
  158 + tablesLength[TtfParser.maxp_table] = len;
  159 + }
  160 +
  161 + {
  162 + // HHEA table
  163 + final int start = ttf.tableOffsets[TtfParser.hhea_table];
  164 + final int len = ttf.tableSize[TtfParser.hhea_table];
  165 + final Uint8List hhea = Uint8List.fromList(
  166 + ttf.bytes.buffer.asUint8List(start, _wordAlign(len, 4)));
  167 + hhea.buffer
  168 + .asByteData()
  169 + .setUint16(34, glyphsInfo.length); // numOfLongHorMetrics
  170 +
  171 + tables[TtfParser.hhea_table] = hhea;
  172 + tablesLength[TtfParser.hhea_table] = len;
  173 + }
  174 +
  175 + {
  176 + // HMTX table
  177 + final int len = 4 * glyphsInfo.length;
  178 + final Uint8List hmtx = Uint8List(_wordAlign(len, 4));
  179 + final int hmtxOffset = ttf.tableOffsets[TtfParser.hmtx_table];
  180 + final ByteData hmtxData = hmtx.buffer.asByteData();
  181 + int index = 0;
  182 + for (TtfGlyphInfo glyph in glyphsInfo) {
  183 + hmtxData.setUint32(
  184 + index, ttf.bytes.getInt32(hmtxOffset + glyph.index * 4));
  185 + index += 4;
  186 + }
  187 + tables[TtfParser.hmtx_table] = hmtx;
  188 + tablesLength[TtfParser.hmtx_table] = len;
  189 + }
  190 +
  191 + {
  192 + // CMAP table
  193 + final Uint8List cmap = Uint8List(_wordAlign(0x112, 4));
  194 + cmap.setAll(3, <int>[1, 0, 1, 0, 0, 0, 0, 0, 12, 0, 0, 1, 6]);
  195 + final ByteData cmapData = cmap.buffer.asByteData();
  196 + for (int i = 1; i < chars.length; i++) {
  197 + cmapData.setUint8(i + 18, i);
  198 + }
  199 + tables[TtfParser.cmap_table] = cmap;
  200 + tablesLength[TtfParser.cmap_table] = 0x112;
  201 + }
  202 +
  203 + {
  204 + final List<int> bytes = <int>[];
  205 +
  206 + final int numTables = tables.length;
  207 +
  208 + // Create the file header
  209 + final ByteData start = ByteData(12 + numTables * 16);
  210 + start.setUint32(0, 0x00010000);
  211 + start.setUint16(4, numTables);
  212 + int pot = numTables;
  213 + while (pot & (pot - 1) != 0) {
  214 + pot++;
  215 + }
  216 + start.setUint16(6, pot * 16);
  217 + start.setUint16(8, math.log(pot).toInt());
  218 + start.setUint16(10, pot * 16 - numTables * 16);
  219 +
  220 + // Create the table directory
  221 + int count = 0;
  222 + int offset = 12 + numTables * 16;
  223 + tables.forEach((String name, Uint8List data) {
  224 + final List<int> runes = name.runes.toList();
  225 + start.setUint8(12 + count * 16, runes[0]);
  226 + start.setUint8(12 + count * 16 + 1, runes[1]);
  227 + start.setUint8(12 + count * 16 + 2, runes[2]);
  228 + start.setUint8(12 + count * 16 + 3, runes[3]);
  229 + start.setUint32(12 + count * 16 + 4,
  230 + _calcTableChecksum(data.buffer.asByteData())); // checkSum
  231 + start.setUint32(12 + count * 16 + 8, offset); // offset
  232 + start.setUint32(12 + count * 16 + 12, tablesLength[name]); // length
  233 + offset += data.lengthInBytes;
  234 + count++;
  235 + });
  236 + bytes.addAll(start.buffer.asUint8List());
  237 +
  238 + tables.forEach((String name, Uint8List data) {
  239 + bytes.addAll(data.buffer.asUint8List());
  240 + });
  241 +
  242 + return Uint8List.fromList(bytes);
  243 + }
  244 + }
  245 +}
@@ -21,37 +21,22 @@ class PdfTtfFont extends PdfFont { @@ -21,37 +21,22 @@ class PdfTtfFont extends PdfFont {
21 PdfTtfFont(PdfDocument pdfDocument, ByteData bytes) 21 PdfTtfFont(PdfDocument pdfDocument, ByteData bytes)
22 : font = TtfParser(bytes), 22 : font = TtfParser(bytes),
23 super._create(pdfDocument, subtype: '/TrueType') { 23 super._create(pdfDocument, subtype: '/TrueType') {
24 - final PdfObjectStream file = PdfObjectStream(pdfDocument, isBinary: true);  
25 - final Uint8List data = bytes.buffer.asUint8List();  
26 - file.buf.putBytes(data);  
27 - file.params['/Length1'] = PdfStream.intNum(data.length);  
28 -  
29 - _charMin = 32;  
30 - _charMax = 255;  
31 -  
32 - final List<String> widths = <String>[];  
33 -  
34 - for (int i = _charMin; i <= _charMax; i++) {  
35 - widths.add((glyphMetrics(i).advanceWidth * 1000.0).toInt().toString());  
36 - }  
37 -  
38 - unicodeCMap = PdfObject(pdfDocument); 24 + file = PdfObjectStream(pdfDocument, isBinary: true);
  25 + unicodeCMap = PdfUnicodeCmap(pdfDocument);
39 descriptor = PdfFontDescriptor(this, file); 26 descriptor = PdfFontDescriptor(this, file);
40 - widthsObject = PdfArrayObject(pdfDocument, widths); 27 + widthsObject = PdfArrayObject(pdfDocument, <String>[]);
41 } 28 }
42 29
43 - PdfObject unicodeCMap; 30 + PdfUnicodeCmap unicodeCMap;
44 31
45 PdfFontDescriptor descriptor; 32 PdfFontDescriptor descriptor;
46 33
  34 + PdfObjectStream file;
  35 +
47 PdfArrayObject widthsObject; 36 PdfArrayObject widthsObject;
48 37
49 final TtfParser font; 38 final TtfParser font;
50 39
51 - int _charMin;  
52 -  
53 - int _charMax;  
54 -  
55 @override 40 @override
56 String get fontName => font.fontName; 41 String get fontName => font.fontName;
57 42
@@ -74,14 +59,80 @@ class PdfTtfFont extends PdfFont { @@ -74,14 +59,80 @@ class PdfTtfFont extends PdfFont {
74 59
75 @override 60 @override
76 void _prepare() { 61 void _prepare() {
  62 + int charMin;
  63 + int charMax;
  64 +
77 super._prepare(); 65 super._prepare();
78 66
  67 + final TtfWriter ttfWriter = TtfWriter(font);
  68 + final Uint8List data = ttfWriter.withChars(unicodeCMap.cmap);
  69 +
  70 + file.buf.putBytes(data);
  71 + file.params['/Length1'] = PdfStream.intNum(data.length);
  72 +
79 params['/BaseFont'] = PdfStream.string('/' + fontName); 73 params['/BaseFont'] = PdfStream.string('/' + fontName);
80 - params['/FirstChar'] = PdfStream.intNum(_charMin);  
81 - params['/LastChar'] = PdfStream.intNum(_charMax);  
82 - params['/Widths'] = widthsObject.ref();  
83 params['/FontDescriptor'] = descriptor.ref(); 74 params['/FontDescriptor'] = descriptor.ref();
84 -// params['/Encoding'] = PdfStream.string('/Identity-H');  
85 -// params['/ToUnicode'] = unicodeCMap.ref(); 75 + if (font.unicode) {
  76 + if (params.containsKey('/Encoding')) {
  77 + params.remove('/Encoding');
  78 + }
  79 + params['/ToUnicode'] = unicodeCMap.ref();
  80 + charMin = 0;
  81 + charMax = unicodeCMap.cmap.length - 1;
  82 + for (int i = charMin; i <= charMax; i++) {
  83 + widthsObject.values.add(
  84 + (glyphMetrics(unicodeCMap.cmap[i]).advanceWidth * 1000.0)
  85 + .toInt()
  86 + .toString());
  87 + }
  88 + } else {
  89 + charMin = 32;
  90 + charMax = 255;
  91 + for (int i = charMin; i <= charMax; i++) {
  92 + widthsObject.values
  93 + .add((glyphMetrics(i).advanceWidth * 1000.0).toInt().toString());
  94 + }
  95 + }
  96 + params['/FirstChar'] = PdfStream.intNum(charMin);
  97 + params['/LastChar'] = PdfStream.intNum(charMax);
  98 + params['/Widths'] = widthsObject.ref();
  99 + }
  100 +
  101 + @override
  102 + PdfStream putText(String text) {
  103 + if (!font.unicode) {
  104 + return super.putText(text);
  105 + }
  106 +
  107 + final Runes runes = text.runes;
  108 + final List<int> bytes = List<int>();
  109 + for (int rune in runes) {
  110 + int char = unicodeCMap.cmap.indexOf(rune);
  111 + if (char == -1) {
  112 + char = unicodeCMap.cmap.length;
  113 + unicodeCMap.cmap.add(rune);
  114 + }
  115 +
  116 + bytes.add(char);
  117 + }
  118 +
  119 + return PdfStream()
  120 + ..putBytes(latin1.encode('('))
  121 + ..putTextBytes(bytes)
  122 + ..putBytes(latin1.encode(')'));
  123 + }
  124 +
  125 + @override
  126 + PdfFontMetrics stringMetrics(String s) {
  127 + if (s.isEmpty || !font.unicode) {
  128 + return super.stringMetrics(s);
  129 + }
  130 +
  131 + final Runes runes = s.runes;
  132 + final List<int> bytes = List<int>();
  133 + runes.forEach(bytes.add);
  134 +
  135 + final Iterable<PdfFontMetrics> metrics = bytes.map(glyphMetrics);
  136 + return PdfFontMetrics.append(metrics);
86 } 137 }
87 } 138 }
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of pdf;
  18 +
  19 +class PdfUnicodeCmap extends PdfObjectStream {
  20 + PdfUnicodeCmap(PdfDocument pdfDocument) : super(pdfDocument);
  21 +
  22 + final List<int> cmap = <int>[0];
  23 +
  24 + @override
  25 + void _prepare() {
  26 + buf.putString('/CIDInit/ProcSet findresource begin\n'
  27 + '12 dict begin\n'
  28 + 'begincmap\n'
  29 + '/CIDSystemInfo<<\n'
  30 + '/Registry (Adobe)\n'
  31 + '/Ordering (UCS)\n'
  32 + '/Supplement 0\n'
  33 + '>> def\n'
  34 + '/CMapName/Adobe-Identity-UCS def\n'
  35 + '/CMapType 2 def\n'
  36 + '1 begincodespacerange\n'
  37 + '<00> <FF>\n'
  38 + 'endcodespacerange\n'
  39 + '${cmap.length} beginbfchar\n');
  40 +
  41 + for (int key = 0; key < cmap.length; key++) {
  42 + final int value = cmap[key];
  43 + buf.putString('<' +
  44 + key.toRadixString(16).toUpperCase().padLeft(2, '0') +
  45 + '> <' +
  46 + value.toRadixString(16).toUpperCase().padLeft(4, '0') +
  47 + '>\n');
  48 + }
  49 +
  50 + buf.putString('endbfchar\n'
  51 + 'endcmap\n'
  52 + 'CMapName currentdict /CMap defineresource pop\n'
  53 + 'end\n'
  54 + 'end');
  55 + super._prepare();
  56 + }
  57 +}
@@ -65,10 +65,11 @@ void main() { @@ -65,10 +65,11 @@ void main() {
65 65
66 final PdfGraphics g = page.getGraphics(); 66 final PdfGraphics g = page.getGraphics();
67 int top = 0; 67 int top = 0;
68 - const String s = 'Hello '; 68 + const String s = 'Hello Lukáča ';
69 69
70 printTextTtf(g, s, File('open-sans.ttf'), 30.0 + 30.0 * top++); 70 printTextTtf(g, s, File('open-sans.ttf'), 30.0 + 30.0 * top++);
71 printTextTtf(g, s, File('roboto.ttf'), 30.0 + 30.0 * top++); 71 printTextTtf(g, s, File('roboto.ttf'), 30.0 + 30.0 * top++);
  72 + printTextTtf(g, s, File('noto-sans.ttf'), 30.0 + 30.0 * top++);
72 73
73 final File file = File('ttf.pdf'); 74 final File file = File('ttf.pdf');
74 file.writeAsBytesSync(pdf.save()); 75 file.writeAsBytesSync(pdf.save());