David PHAM-VAN

Uses an internal TTF parser

@@ -9,3 +9,5 @@ pubspec.lock @@ -9,3 +9,5 @@ pubspec.lock
9 # Directory created by dartdoc 9 # Directory created by dartdoc
10 # If you don't generate documentation locally you can remove this line. 10 # If you don't generate documentation locally you can remove this line.
11 doc/api/ 11 doc/api/
  12 +
  13 +*.pdf
  1 +# 1.0.3
  2 +* Remove dependency to ttf_parser
  3 +
1 # 1.0.2 4 # 1.0.2
2 * Update sdk support for 2.0.0 5 * Update sdk support for 2.0.0
  6 +
3 # 1.0.1 7 # 1.0.1
4 * Add example 8 * Add example
5 * Lower vector_math dependency version 9 * Lower vector_math dependency version
6 * Uses better page format object 10 * Uses better page format object
7 -  
1 # Pdf creation library for dart / flutter 1 # Pdf creation library for dart / flutter
2 2
  3 +This is a low-level Pdf creation library.
  4 +It can create a full multi-pages document with graphics,
  5 +images and text using TrueType fonts.
  6 +
  7 +The coordinate system is using the internal Pdf system:
  8 + * (0.0, 0.0) is bottom-left
  9 + * 1.0 is defined as 1 / 72.0 inch
  10 + * you can use the constants for centimeters, milimeters and inch defined in PDFPageFormat
  11 +
3 Example: 12 Example:
4 ```dart 13 ```dart
5 final pdf = new PDFDocument(); 14 final pdf = new PDFDocument();
@@ -12,8 +21,30 @@ g.drawRect(50.0, 30.0, 100.0, 50.0); @@ -12,8 +21,30 @@ g.drawRect(50.0, 30.0, 100.0, 50.0);
12 g.fillPath(); 21 g.fillPath();
13 22
14 g.setColor(new PDFColor(0.3, 0.3, 0.3)); 23 g.setColor(new PDFColor(0.3, 0.3, 0.3));
15 -g.drawString(font, 12.0, "Hello World!", 50.0, 300.0); 24 +g.drawString(font, 12.0, "Hello World!", 5.0 * PDFPageFormat.MM, 300.0);
16 25
17 var file = new File('file.pdf'); 26 var file = new File('file.pdf');
18 file.writeAsBytesSync(pdf.save()); 27 file.writeAsBytesSync(pdf.save());
19 ``` 28 ```
  29 +
  30 +To load an image it is possible to use the dart library `image`
  31 +
  32 +```dart
  33 +Image image = decodeImage(new Io.File('test.webp').readAsBytesSync());
  34 +PDFImage image = new PDFImage(
  35 + pdf,
  36 + image: img.data.buffer.asUint8List(),
  37 + width: img.width,
  38 + height: img.height);
  39 +g.drawImage(image, 100.0, 100.0, 80.0);
  40 +```
  41 +
  42 +To use a TrueType font:
  43 +
  44 +```dart
  45 +PDFTTFFont ttf = new PDFTTFFont(
  46 + pdf,
  47 + (new File("open-sans.ttf").readAsBytesSync() as Uint8List).buffer.asByteData());
  48 +g.setColor(new PDFColor(0.3, 0.3, 0.3));
  49 +g.drawString(ttf, 20.0, "Dart is awesome", 50.0, 30.0);
  50 +```
@@ -23,7 +23,6 @@ import 'dart:io'; @@ -23,7 +23,6 @@ import 'dart:io';
23 import 'dart:typed_data'; 23 import 'dart:typed_data';
24 24
25 import 'package:meta/meta.dart'; 25 import 'package:meta/meta.dart';
26 -import 'package:ttf_parser/ttf_parser.dart';  
27 import 'package:vector_math/vector_math_64.dart'; 26 import 'package:vector_math/vector_math_64.dart';
28 27
29 part 'src/annotation.dart'; 28 part 'src/annotation.dart';
@@ -50,6 +49,7 @@ part 'src/point.dart'; @@ -50,6 +49,7 @@ part 'src/point.dart';
50 part 'src/polygon.dart'; 49 part 'src/polygon.dart';
51 part 'src/rect.dart'; 50 part 'src/rect.dart';
52 part 'src/stream.dart'; 51 part 'src/stream.dart';
  52 +part 'src/ttf_parser.dart';
53 part 'src/ttffont.dart'; 53 part 'src/ttffont.dart';
54 part 'src/xobject.dart'; 54 part 'src/xobject.dart';
55 part 'src/xref.dart'; 55 part 'src/xref.dart';
@@ -20,11 +20,9 @@ part of pdf; @@ -20,11 +20,9 @@ part of pdf;
20 20
21 class PDFFontDescriptor extends PDFObject { 21 class PDFFontDescriptor extends PDFObject {
22 final PDFObjectStream file; 22 final PDFObjectStream file;
23 - final TtfFont font;  
24 final PDFTTFFont ttfFont; 23 final PDFTTFFont ttfFont;
25 24
26 - PDFFontDescriptor(this.ttfFont, this.file, this.font)  
27 - : super(ttfFont.pdfDocument, "/FontDescriptor"); 25 + PDFFontDescriptor(this.ttfFont, this.file) : super(ttfFont.pdfDocument, "/FontDescriptor");
28 26
29 @override 27 @override
30 void prepare() { 28 void prepare() {
@@ -34,9 +32,10 @@ class PDFFontDescriptor extends PDFObject { @@ -34,9 +32,10 @@ class PDFFontDescriptor extends PDFObject {
34 params["/FontFile2"] = file.ref(); 32 params["/FontFile2"] = file.ref();
35 params["/Flags"] = PDFStream.intNum(32); 33 params["/Flags"] = PDFStream.intNum(32);
36 params["/FontBBox"] = new PDFStream() 34 params["/FontBBox"] = new PDFStream()
37 - ..putStringArray([font.head.xMin, font.head.yMin, font.head.xMax, font.head.yMax]);  
38 - params["/Ascent"] = PDFStream.intNum(font.hhea.ascent);  
39 - params["/Descent"] = PDFStream.intNum(font.hhea.descent); 35 + ..putStringArray(
  36 + [ttfFont.font.xMin, ttfFont.font.yMin, ttfFont.font.xMax, ttfFont.font.yMax]);
  37 + params["/Ascent"] = PDFStream.intNum(ttfFont.font.ascent);
  38 + params["/Descent"] = PDFStream.intNum(ttfFont.font.descent);
40 params["/ItalicAngle"] = PDFStream.intNum(0); 39 params["/ItalicAngle"] = PDFStream.intNum(0);
41 params["/CapHeight"] = PDFStream.intNum(10); 40 params["/CapHeight"] = PDFStream.intNum(10);
42 params["/StemV"] = PDFStream.intNum(79); 41 params["/StemV"] = PDFStream.intNum(79);
  1 +/*
  2 + * Copyright (C) 2018, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * This library is free software; you can redistribute it and/or
  5 + * modify it under the terms of the GNU Lesser General Public
  6 + * License as published by the Free Software Foundation; either
  7 + * version 2.1 of the License, or (at your option) any later version.
  8 + *
  9 + * This library is distributed in the hope that it will be useful,
  10 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12 + * Lesser General Public License for more details.
  13 + *
  14 + * You should have received a copy of the GNU Lesser General Public
  15 + * License along with this library; if not, write to the Free Software
  16 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17 + */
  18 +
  19 +part of pdf;
  20 +
  21 +class TTFParser {
  22 + static const _HEAD = "head";
  23 + static const _NAME = "name";
  24 + static const _HMTX = "hmtx";
  25 + static const _HHEA = "hhea";
  26 + static const _CMAP = "cmap";
  27 + static const _MAXP = "maxp";
  28 + static const _LOCA = "loca";
  29 + static const _GLYF = "glyf";
  30 +
  31 + final ByteData bytes;
  32 + final _tableOffsets = new Map<String, int>();
  33 + String _fontName;
  34 + final advanceWidth = new List<double>();
  35 + final charToGlyphIndexMap = new Map<int, int>();
  36 + final glyphOffsets = new List<int>();
  37 + final glyphInfoMap = new Map<int, PDFRect>();
  38 +
  39 + TTFParser(this.bytes) {
  40 + final numTables = bytes.getUint16(4);
  41 +
  42 + for (var i = 0; i < numTables; i++) {
  43 + final name = utf8.decode(bytes.buffer.asUint8List(i * 16 + 12, 4));
  44 + final offset = bytes.getUint32(i * 16 + 20);
  45 + _tableOffsets[name] = offset;
  46 + }
  47 +
  48 + _parseFontName();
  49 + _parseHmtx();
  50 + _parseCMap();
  51 + _parseIndexes();
  52 + _parseGlyf();
  53 + }
  54 +
  55 + get unitsPerEm => bytes.getUint16(_tableOffsets[_HEAD] + 18);
  56 +
  57 + get xMin => bytes.getInt16(_tableOffsets[_HEAD] + 36);
  58 +
  59 + get yMin => bytes.getInt16(_tableOffsets[_HEAD] + 38);
  60 +
  61 + get xMax => bytes.getInt16(_tableOffsets[_HEAD] + 40);
  62 +
  63 + get yMax => bytes.getInt16(_tableOffsets[_HEAD] + 42);
  64 +
  65 + get indexToLocFormat => bytes.getInt16(_tableOffsets[_HEAD] + 50);
  66 +
  67 + get ascent => bytes.getInt16(_tableOffsets[_HHEA] + 4);
  68 +
  69 + get descent => bytes.getInt16(_tableOffsets[_HHEA] + 6);
  70 +
  71 + get numOfLongHorMetrics => bytes.getInt16(_tableOffsets[_HHEA] + 34);
  72 +
  73 + get numGlyphs => bytes.getInt16(_tableOffsets[_MAXP] + 4);
  74 +
  75 + get fontName => _fontName;
  76 +
  77 + void _parseFontName() {
  78 + final basePosition = _tableOffsets[_NAME];
  79 + final count = bytes.getUint16(basePosition + 2);
  80 + final stringOffset = bytes.getUint16(basePosition + 4);
  81 + int pos = basePosition + 6;
  82 + for (var i = 0; i < count; i++) {
  83 + int platformID = bytes.getUint16(pos);
  84 + int nameID = bytes.getUint16(pos + 6);
  85 + int length = bytes.getUint16(pos + 8);
  86 + int offset = bytes.getUint16(pos + 10);
  87 + pos += 12;
  88 + if (platformID == 1 && nameID == 6) {
  89 + _fontName = utf8
  90 + .decode(bytes.buffer.asUint8List(basePosition + stringOffset + offset, length));
  91 + }
  92 + }
  93 + }
  94 +
  95 + void _parseHmtx() {
  96 + final offset = _tableOffsets[_HMTX];
  97 + final unitsPerEm = this.unitsPerEm;
  98 + for (var i = 0; i < numOfLongHorMetrics; i++) {
  99 + advanceWidth.add(bytes.getInt16(offset + i * 4).toDouble() / unitsPerEm);
  100 + }
  101 + }
  102 +
  103 + void _parseCMap() {
  104 + final basePosition = _tableOffsets[_CMAP];
  105 + final numSubTables = bytes.getUint16(basePosition + 2);
  106 + for (var i = 0; i < numSubTables; i++) {
  107 + final offset = bytes.getUint32(basePosition + i * 8 + 8);
  108 + final format = bytes.getUint16(basePosition + offset);
  109 + final length = bytes.getUint16(basePosition + offset + 2);
  110 +
  111 + switch (format) {
  112 + case 0:
  113 + _parseCMapFormat0(basePosition + offset + 4, length);
  114 + break;
  115 +
  116 + case 4:
  117 + _parseCMapFormat4(basePosition + offset + 4, length);
  118 + break;
  119 + case 6:
  120 + _parseCMapFormat6(basePosition + offset + 4, length);
  121 + break;
  122 + }
  123 + }
  124 + }
  125 +
  126 + void _parseCMapFormat0(int basePosition, int length) {
  127 + assert(length == 262);
  128 + for (var i = 0; i < 256; i++) {
  129 + int charCode = i;
  130 + int glyphIndex = bytes.getUint8(basePosition + i);
  131 + if (glyphIndex > 0) {
  132 + charToGlyphIndexMap[charCode] = glyphIndex;
  133 + }
  134 + }
  135 + }
  136 +
  137 + void _parseCMapFormat4(int basePosition, int length) {
  138 + final segCount = bytes.getUint16(basePosition + 2) ~/ 2;
  139 + final endCodes = new List<int>();
  140 + for (var i = 0; i < segCount; i++) {
  141 + endCodes.add(bytes.getUint16(basePosition + i * 2 + 10));
  142 + }
  143 + final startCodes = new List<int>();
  144 + for (var i = 0; i < segCount; i++) {
  145 + startCodes.add(bytes.getUint16(basePosition + (segCount + i) * 2 + 12));
  146 + }
  147 + final idDeltas = new List<int>();
  148 + for (var i = 0; i < segCount; i++) {
  149 + idDeltas.add(bytes.getUint16(basePosition + (segCount * 2 + i) * 2 + 12));
  150 + }
  151 + final idRangeOffsetBasePos = basePosition + segCount * 6 + 12;
  152 + final idRangeOffsets = new List<int>();
  153 + for (var i = 0; i < segCount; i++) {
  154 + idRangeOffsets.add(bytes.getUint16(idRangeOffsetBasePos + i * 2));
  155 + }
  156 + for (var s = 0; s < segCount - 1; s++) {
  157 + final startCode = startCodes[s];
  158 + final endCode = endCodes[s];
  159 + final idDelta = idDeltas[s];
  160 + final idRangeOffset = idRangeOffsets[s];
  161 + final idRangeOffsetAddress = idRangeOffsetBasePos + s * 2;
  162 + for (var c = startCode; c <= endCode; c++) {
  163 + var glyphIndex;
  164 + if (idRangeOffset == 0) {
  165 + glyphIndex = (idDelta + c) % 65536;
  166 + } else {
  167 + final glyphIndexAddress = idRangeOffset + 2 * (c - startCode) + idRangeOffsetAddress;
  168 + glyphIndex = bytes.getUint16(glyphIndexAddress);
  169 + }
  170 + charToGlyphIndexMap[c] = glyphIndex;
  171 + }
  172 + }
  173 + }
  174 +
  175 + void _parseCMapFormat6(int basePosition, int length) {
  176 + final firstCode = bytes.getUint16(basePosition + 2);
  177 + final entryCount = bytes.getUint16(basePosition + 4);
  178 + for (var i = 0; i < entryCount; i++) {
  179 + final charCode = firstCode + i;
  180 + final glyphIndex = bytes.getUint16(basePosition + i * 2 + 6);
  181 + if (glyphIndex > 0) {
  182 + charToGlyphIndexMap[charCode] = glyphIndex;
  183 + }
  184 + }
  185 + }
  186 +
  187 + void _parseIndexes() {
  188 + final basePosition = _tableOffsets[_LOCA];
  189 + final numGlyphs = this.numGlyphs;
  190 + if (indexToLocFormat == 0) {
  191 + for (var i = 0; i < numGlyphs; i++) {
  192 + glyphOffsets.add(bytes.getUint16(basePosition + i * 2) * 2);
  193 + }
  194 + } else {
  195 + for (var i = 0; i < numGlyphs; i++) {
  196 + glyphOffsets.add(bytes.getUint32(basePosition + i * 4));
  197 + }
  198 + }
  199 + }
  200 +
  201 + void _parseGlyf() {
  202 + final baseOffset = _tableOffsets[_GLYF];
  203 + final unitsPerEm = this.unitsPerEm;
  204 + int glyphIndex = 0;
  205 + for (var offset in glyphOffsets) {
  206 + final xMin = bytes.getInt16(baseOffset + offset + 2); // 2
  207 + final yMin = bytes.getInt16(baseOffset + offset + 4); // 4
  208 + final xMax = bytes.getInt16(baseOffset + offset + 6); // 6
  209 + final yMax = bytes.getInt16(baseOffset + offset + 8); // 8
  210 + glyphInfoMap[glyphIndex] = new PDFRect(
  211 + xMin.toDouble() / unitsPerEm,
  212 + yMin.toDouble() / unitsPerEm,
  213 + xMax.toDouble() / unitsPerEm,
  214 + yMax.toDouble() / unitsPerEm);
  215 + glyphIndex++;
  216 + }
  217 + }
  218 +}
@@ -23,19 +23,20 @@ class PDFTTFFont extends PDFFont { @@ -23,19 +23,20 @@ class PDFTTFFont extends PDFFont {
23 PDFFontDescriptor descriptor; 23 PDFFontDescriptor descriptor;
24 PDFArrayObject widthsObject; 24 PDFArrayObject widthsObject;
25 final widths = new List<String>(); 25 final widths = new List<String>();
26 - TtfFont _font; 26 + final TTFParser font;
27 int _charMin; 27 int _charMin;
28 int _charMax; 28 int _charMax;
29 29
30 /// Constructs a PDFTTFFont 30 /// Constructs a PDFTTFFont
31 - PDFTTFFont(PDFDocument pdfDocument, Uint8List bytes)  
32 - : super(pdfDocument, subtype: "/TrueType") {  
33 - _font = new TtfParser().parse(bytes);  
34 - baseFont = "/" + _font.name.fontName.replaceAll(" ", ""); 31 + PDFTTFFont(PDFDocument pdfDocument, ByteData bytes)
  32 + : font = new TTFParser(bytes),
  33 + super(pdfDocument, subtype: "/TrueType") {
  34 + baseFont = "/" + font.fontName.replaceAll(" ", "");
35 35
36 PDFObjectStream file = new PDFObjectStream(pdfDocument, isBinary: true); 36 PDFObjectStream file = new PDFObjectStream(pdfDocument, isBinary: true);
37 - file.buf.putBytes(bytes);  
38 - file.params["/Length1"] = PDFStream.intNum(bytes.length); 37 + final data = bytes.buffer.asUint8List();
  38 + file.buf.putBytes(data);
  39 + file.params["/Length1"] = PDFStream.intNum(data.length);
39 40
40 _charMin = 32; 41 _charMin = 32;
41 _charMax = 255; 42 _charMax = 255;
@@ -45,35 +46,30 @@ class PDFTTFFont extends PDFFont { @@ -45,35 +46,30 @@ class PDFTTFFont extends PDFFont {
45 } 46 }
46 47
47 unicodeCMap = new PDFObject(pdfDocument); 48 unicodeCMap = new PDFObject(pdfDocument);
48 - descriptor = new PDFFontDescriptor(this, file, _font); 49 + descriptor = new PDFFontDescriptor(this, file);
49 widthsObject = new PDFArrayObject(pdfDocument, widths); 50 widthsObject = new PDFArrayObject(pdfDocument, widths);
50 } 51 }
51 52
52 @override 53 @override
53 double glyphAdvance(int charCode) { 54 double glyphAdvance(int charCode) {
54 - var g = _font.cmap.charToGlyphIndexMap[charCode]; 55 + var g = font.charToGlyphIndexMap[charCode];
55 56
56 if (g == null) { 57 if (g == null) {
57 return super.glyphAdvance(charCode); 58 return super.glyphAdvance(charCode);
58 } 59 }
59 60
60 - return _font.hmtx.metrics[g].advanceWidth / _font.head.unitsPerEm; 61 + return (font.advanceWidth[g]) ?? super.glyphAdvance(charCode);
61 } 62 }
62 63
63 @override 64 @override
64 PDFRect glyphBounds(int charCode) { 65 PDFRect glyphBounds(int charCode) {
65 - var g = _font.cmap.charToGlyphIndexMap[charCode]; 66 + var g = font.charToGlyphIndexMap[charCode];
66 67
67 if (g == null) { 68 if (g == null) {
68 return super.glyphBounds(charCode); 69 return super.glyphBounds(charCode);
69 } 70 }
70 71
71 - var info = _font.glyf.glyphInfoMap[g];  
72 - return new PDFRect(  
73 - info.xMin.toDouble() / _font.head.unitsPerEm,  
74 - info.yMin.toDouble() / _font.head.unitsPerEm,  
75 - (info.xMax - info.xMin).toDouble() / _font.head.unitsPerEm,  
76 - (info.yMax - info.yMin).toDouble() / _font.head.unitsPerEm); 72 + return font.glyphInfoMap[g] ?? super.glyphBounds(charCode);
77 } 73 }
78 74
79 @override 75 @override
@@ -2,14 +2,13 @@ name: pdf @@ -2,14 +2,13 @@ name: pdf
2 author: David PHAM-VAN <dev.nfet.net@gmail.com> 2 author: David PHAM-VAN <dev.nfet.net@gmail.com>
3 description: A pdf producer for Dart. It can create pdf files for both web or flutter. 3 description: A pdf producer for Dart. It can create pdf files for both web or flutter.
4 homepage: https://github.com/davbfr/dart_pdf 4 homepage: https://github.com/davbfr/dart_pdf
5 -version: 1.0.2 5 +version: 1.0.3
6 6
7 environment: 7 environment:
8 sdk: ">=1.8.0 <3.0.0" 8 sdk: ">=1.8.0 <3.0.0"
9 9
10 dependencies: 10 dependencies:
11 meta: "^1.1.5" 11 meta: "^1.1.5"
12 - ttf_parser: "^1.0.0"  
13 vector_math: "^2.0.0" 12 vector_math: "^2.0.0"
14 13
15 dev_dependencies: 14 dev_dependencies:
1 import 'dart:io'; 1 import 'dart:io';
2 import 'dart:math'; 2 import 'dart:math';
  3 +import 'dart:typed_data';
3 4
4 import 'package:pdf/pdf.dart'; 5 import 'package:pdf/pdf.dart';
5 import 'package:test/test.dart'; 6 import 'package:test/test.dart';
@@ -27,8 +28,11 @@ void main() { @@ -27,8 +28,11 @@ void main() {
27 g.restoreContext(); 28 g.restoreContext();
28 var font1 = new PDFFont(pdf); 29 var font1 = new PDFFont(pdf);
29 30
30 - var font2 =  
31 - new PDFTTFFont(pdf, new File("../assets/Nunito-Regular.ttf").readAsBytesSync()); 31 + var font2 = new PDFTTFFont(
  32 + pdf,
  33 + (new File("../assets/Nunito-Regular.ttf").readAsBytesSync() as Uint8List)
  34 + .buffer
  35 + .asByteData());
32 var s = "Hello World!"; 36 var s = "Hello World!";
33 var r = font2.stringBounds(s); 37 var r = font2.stringBounds(s);
34 const FS = 20.0; 38 const FS = 20.0;
  1 +import 'dart:io';
  2 +import 'dart:typed_data';
  3 +
  4 +import 'package:pdf/pdf.dart';
  5 +import 'package:test/test.dart';
  6 +
  7 +void main() {
  8 + test('Pdf', () {
  9 + var pdf = new PDFDocument(deflate: false);
  10 + var i = pdf.info;
  11 + i.author = "David PHAM-VAN";
  12 + i.creator = i.author;
  13 + i.title = "My Title";
  14 + i.subject = "My Subject";
  15 + var page = new PDFPage(pdf, pageFormat: const PDFPageFormat(500.0, 300.0));
  16 +
  17 + var g = page.getGraphics();
  18 + var ttf = new PDFTTFFont(
  19 + pdf,
  20 + (new File("../assets/Nunito-Regular.ttf").readAsBytesSync() as Uint8List)
  21 + .buffer
  22 + .asByteData());
  23 + var s = "Hello World!";
  24 + var r = ttf.stringBounds(s);
  25 + print(r);
  26 + const FS = 20.0;
  27 + g.setColor(new PDFColor(0.0, 1.0, 1.0));
  28 + g.drawRect(50.0 + r.x * FS, 30.0 + r.y * FS, r.w * FS, r.h * FS);
  29 + g.fillPath();
  30 + g.setColor(new PDFColor(0.3, 0.3, 0.3));
  31 + g.drawString(ttf, FS, s, 50.0, 30.0);
  32 +
  33 + var file = new File('file.pdf');
  34 + file.writeAsBytesSync(pdf.save());
  35 + });
  36 +}