Showing
11 changed files
with
554 additions
and
57 deletions
@@ -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 | } |
pdf/lib/src/ttf_writer.dart
0 → 100644
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 | } |
pdf/lib/src/unicode_cmap.dart
0 → 100644
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()); |
-
Please register or login to post a comment