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