David PHAM-VAN

Simplify PDF generation

@@ -20,8 +20,11 @@ import 'dart:typed_data'; @@ -20,8 +20,11 @@ import 'dart:typed_data';
20 import 'package:crypto/crypto.dart'; 20 import 'package:crypto/crypto.dart';
21 21
22 import 'document_parser.dart'; 22 import 'document_parser.dart';
  23 +import 'format/array.dart';
  24 +import 'format/num.dart';
23 import 'format/object_base.dart'; 25 import 'format/object_base.dart';
24 import 'format/stream.dart'; 26 import 'format/stream.dart';
  27 +import 'format/string.dart';
25 import 'format/xref.dart'; 28 import 'format/xref.dart';
26 import 'graphic_state.dart'; 29 import 'graphic_state.dart';
27 import 'io/vm.dart' if (dart.library.js) 'io/js.dart'; 30 import 'io/vm.dart' if (dart.library.js) 'io/js.dart';
@@ -36,7 +39,6 @@ import 'obj/page.dart'; @@ -36,7 +39,6 @@ import 'obj/page.dart';
36 import 'obj/page_label.dart'; 39 import 'obj/page_label.dart';
37 import 'obj/page_list.dart'; 40 import 'obj/page_list.dart';
38 import 'obj/signature.dart'; 41 import 'obj/signature.dart';
39 -import 'output.dart';  
40 42
41 /// Display hint for the PDF viewer 43 /// Display hint for the PDF viewer
42 enum PdfPageMode { 44 enum PdfPageMode {
@@ -200,24 +202,36 @@ class PdfDocument { @@ -200,24 +202,36 @@ class PdfDocument {
200 202
201 /// This writes the document to an OutputStream. 203 /// This writes the document to an OutputStream.
202 Future<void> _write(PdfStream os) async { 204 Future<void> _write(PdfStream os) async {
203 - final pos = PdfOutput(os, version, verbose);  
204 -  
205 - // Write each object to the [PdfStream]. We call via the output  
206 - // as that builds the xref table  
207 - objects.where((e) => e.inUse).forEach(pos.write);  
208 - var lastFree = 0;  
209 - for (final obj in objects.where((e) => !e.inUse)) {  
210 - pos.xref.add(PdfXref(  
211 - obj.objser,  
212 - lastFree,  
213 - generation: obj.objgen,  
214 - type: PdfCrossRefEntryType.free,  
215 - ));  
216 - lastFree = obj.objser; 205 + PdfSignature? signature;
  206 +
  207 + final xref = PdfXrefTable();
  208 +
  209 + for (final ob in objects.where((e) => e.inUse)) {
  210 + ob.prepare();
  211 + if (ob is PdfInfo) {
  212 + xref.params['/Info'] = ob.ref();
  213 + } else if (ob is PdfEncryption) {
  214 + xref.params['/Encrypt'] = ob.ref();
  215 + } else if (ob is PdfSignature) {
  216 + assert(signature == null, 'Only one document signature is allowed');
  217 + signature = ob;
  218 + }
  219 + xref.objects.add(ob);
217 } 220 }
218 221
219 - // Finally close the output, which writes the xref table.  
220 - await pos.close(); 222 + final id =
  223 + PdfString(documentID, format: PdfStringFormat.binary, encrypted: false);
  224 + xref.params['/ID'] = PdfArray([id, id]);
  225 +
  226 + if (prev != null) {
  227 + xref.params['/Prev'] = PdfNum(prev!.xrefOffset);
  228 + }
  229 +
  230 + xref.output(catalog, os);
  231 +
  232 + if (signature != null) {
  233 + await signature.writeSignature(os);
  234 + }
221 } 235 }
222 236
223 /// Generate the PDF document as a memory file 237 /// Generate the PDF document as a memory file
@@ -30,7 +30,7 @@ abstract class PdfDataType { @@ -30,7 +30,7 @@ abstract class PdfDataType {
30 30
31 PdfStream _toStream() { 31 PdfStream _toStream() {
32 final s = PdfStream(); 32 final s = PdfStream();
33 - output(const PdfObjectBase(objser: 0), s); 33 + output(PdfObjectBase(objser: 0, params: this), s);
34 return s; 34 return s;
35 } 35 }
36 36
@@ -2,7 +2,7 @@ import 'dart:math' as math; @@ -2,7 +2,7 @@ import 'dart:math' as math;
2 2
3 import 'package:meta/meta.dart'; 3 import 'package:meta/meta.dart';
4 4
5 -import '../format/stream.dart'; 5 +import 'stream.dart';
6 6
7 mixin PdfDiagnostic { 7 mixin PdfDiagnostic {
8 static const _maxSize = 300; 8 static const _maxSize = 300;
@@ -15,6 +15,8 @@ mixin PdfDiagnostic { @@ -15,6 +15,8 @@ mixin PdfDiagnostic {
15 15
16 int get elapsedStopwatch => _stopwatch?.elapsedMicroseconds ?? 0; 16 int get elapsedStopwatch => _stopwatch?.elapsedMicroseconds ?? 0;
17 17
  18 + int size = 0;
  19 +
18 @protected 20 @protected
19 @mustCallSuper 21 @mustCallSuper
20 void debugFill(String value) { 22 void debugFill(String value) {
@@ -29,10 +31,11 @@ mixin PdfDiagnostic { @@ -29,10 +31,11 @@ mixin PdfDiagnostic {
29 }()); 31 }());
30 } 32 }
31 33
32 - void setInsertion(PdfStream os) { 34 + void setInsertion(PdfStream os, [int size = _maxSize]) {
33 assert(() { 35 assert(() {
  36 + this.size = size;
34 _offset = os.offset; 37 _offset = os.offset;
35 - os.putComment(' ' * _maxSize); 38 + os.putComment(' ' * size);
36 return true; 39 return true;
37 }()); 40 }());
38 } 41 }
@@ -45,7 +48,7 @@ mixin PdfDiagnostic { @@ -45,7 +48,7 @@ mixin PdfDiagnostic {
45 final b = o.output(); 48 final b = o.output();
46 os.setBytes( 49 os.setBytes(
47 _offset!, 50 _offset!,
48 - b.sublist(0, math.min(_maxSize + 2, b.lengthInBytes - 1)), 51 + b.sublist(0, math.min(size + 2, b.lengthInBytes - 1)),
49 ); 52 );
50 } 53 }
51 return true; 54 return true;
@@ -99,6 +99,6 @@ class PdfDictStream extends PdfDict<PdfDataType> { @@ -99,6 +99,6 @@ class PdfDictStream extends PdfDict<PdfDataType> {
99 } 99 }
100 s.putString('stream\n'); 100 s.putString('stream\n');
101 s.putBytes(_data); 101 s.putBytes(_data);
102 - s.putString('\nendstream\n'); 102 + s.putString('\nendstream');
103 } 103 }
104 } 104 }
@@ -16,7 +16,10 @@ @@ -16,7 +16,10 @@
16 16
17 import 'dart:typed_data'; 17 import 'dart:typed_data';
18 18
  19 +import 'base.dart';
  20 +import 'diagnostic.dart';
19 import 'indirect.dart'; 21 import 'indirect.dart';
  22 +import 'stream.dart';
20 23
21 /// Callback used to compress the data 24 /// Callback used to compress the data
22 typedef DeflateCallback = List<int> Function(List<int> data); 25 typedef DeflateCallback = List<int> Function(List<int> data);
@@ -34,10 +37,11 @@ enum PdfVersion { @@ -34,10 +37,11 @@ enum PdfVersion {
34 pdf_1_5, 37 pdf_1_5,
35 } 38 }
36 39
37 -class PdfObjectBase {  
38 - const PdfObjectBase({ 40 +class PdfObjectBase<T extends PdfDataType> with PdfDiagnostic {
  41 + PdfObjectBase({
39 required this.objser, 42 required this.objser,
40 this.objgen = 0, 43 this.objgen = 0,
  44 + required this.params,
41 }); 45 });
42 46
43 /// This is the unique serial number for this object. 47 /// This is the unique serial number for this object.
@@ -46,6 +50,8 @@ class PdfObjectBase { @@ -46,6 +50,8 @@ class PdfObjectBase {
46 /// This is the generation number for this object. 50 /// This is the generation number for this object.
47 final int objgen; 51 final int objgen;
48 52
  53 + final T params;
  54 +
49 /// Callback used to compress the data 55 /// Callback used to compress the data
50 DeflateCallback? get deflate => null; 56 DeflateCallback? get deflate => null;
51 57
@@ -59,4 +65,15 @@ class PdfObjectBase { @@ -59,4 +65,15 @@ class PdfObjectBase {
59 65
60 /// Returns the unique serial number in Pdf format 66 /// Returns the unique serial number in Pdf format
61 PdfIndirect ref() => PdfIndirect(objser, objgen); 67 PdfIndirect ref() => PdfIndirect(objser, objgen);
  68 +
  69 + void output(PdfStream s) {
  70 + s.putString('$objser $objgen obj\n');
  71 + writeContent(s);
  72 + s.putString('endobj\n');
  73 + }
  74 +
  75 + void writeContent(PdfStream s) {
  76 + params.output(this, s, verbose ? 0 : null);
  77 + s.putByte(0x0a);
  78 + }
62 } 79 }
@@ -19,6 +19,7 @@ import 'dart:typed_data'; @@ -19,6 +19,7 @@ import 'dart:typed_data';
19 19
20 import 'array.dart'; 20 import 'array.dart';
21 import 'base.dart'; 21 import 'base.dart';
  22 +import 'diagnostic.dart';
22 import 'dict.dart'; 23 import 'dict.dart';
23 import 'dict_stream.dart'; 24 import 'dict_stream.dart';
24 import 'indirect.dart'; 25 import 'indirect.dart';
@@ -95,16 +96,17 @@ class PdfXref { @@ -95,16 +96,17 @@ class PdfXref {
95 int get hashCode => offset; 96 int get hashCode => offset;
96 } 97 }
97 98
98 -class PdfXrefTable extends PdfDataType { 99 +class PdfXrefTable extends PdfDataType with PdfDiagnostic {
99 PdfXrefTable(); 100 PdfXrefTable();
100 101
101 - /// Contains offsets of each object  
102 - final offsets = <PdfXref>[]; 102 + /// Document root point
  103 + final params = PdfDict();
103 104
104 - /// Add a cross reference element to the set  
105 - void add(PdfXref xref) {  
106 - offsets.add(xref);  
107 - } 105 + /// List of objects to write
  106 + final objects = <PdfObjectBase>{};
  107 +
  108 + /// Contains the offset of each objects
  109 + final _offsets = <PdfXref>[];
108 110
109 /// Writes a block of references to the Pdf file 111 /// Writes a block of references to the Pdf file
110 void _writeBlock(PdfStream s, int firstId, List<PdfXref> block) { 112 void _writeBlock(PdfStream s, int firstId, List<PdfXref> block) {
@@ -117,26 +119,109 @@ class PdfXrefTable extends PdfDataType { @@ -117,26 +119,109 @@ class PdfXrefTable extends PdfDataType {
117 } 119 }
118 120
119 @override 121 @override
120 - void output(PdfObjectBase o, PdfStream s, [int? indent]) {} 122 + void output(PdfObjectBase o, PdfStream s, [int? indent]) {
  123 + String v;
  124 + switch (o.version) {
  125 + case PdfVersion.pdf_1_4:
  126 + v = '1.4';
  127 + break;
  128 + case PdfVersion.pdf_1_5:
  129 + v = '1.5';
  130 + break;
  131 + }
  132 +
  133 + s.putString('%PDF-$v\n');
  134 + s.putBytes(const <int>[0x25, 0xC2, 0xA5, 0xC2, 0xB1, 0xC3, 0xAB, 0x0A]);
  135 + assert(() {
  136 + if (o.verbose) {
  137 + setInsertion(s);
  138 + startStopwatch();
  139 + debugFill('Verbose dart_pdf');
  140 + debugFill('Producer https://github.com/DavBfr/dart_pdf');
  141 + debugFill('Creation date: ${DateTime.now()}');
  142 + }
  143 + return true;
  144 + }());
  145 +
  146 + for (final ob in objects) {
  147 + assert(() {
  148 + if (ob.verbose) {
  149 + ob.setInsertion(s, 150);
  150 + ob.startStopwatch();
  151 + }
  152 + return true;
  153 + }());
  154 + _offsets.add(PdfXref(ob.objser, s.offset, generation: ob.objgen));
  155 + ob.output(s);
  156 + assert(() {
  157 + if (ob.verbose) {
  158 + ob.stopStopwatch();
  159 + ob.debugFill(
  160 + 'Creation time: ${ob.elapsedStopwatch / Duration.microsecondsPerSecond} seconds');
  161 + ob.writeDebug(s);
  162 + }
  163 + return true;
  164 + }());
  165 + }
  166 +
  167 + final int xrefOffset;
  168 +
  169 + params['/Root'] = o.ref();
  170 +
  171 + switch (o.version) {
  172 + case PdfVersion.pdf_1_4:
  173 + xrefOffset = outputLegacy(o, s);
  174 + break;
  175 + case PdfVersion.pdf_1_5:
  176 + xrefOffset = outputCompressed(o, s);
  177 + break;
  178 + }
  179 +
  180 + assert(() {
  181 + if (o.verbose) {
  182 + s.putComment('');
  183 + s.putComment('-' * 78);
  184 + s.putComment('$runtimeType');
  185 + }
  186 + return true;
  187 + }());
  188 +
  189 + // the reference to the xref object
  190 + s.putString('startxref\n$xrefOffset\n%%EOF\n');
  191 +
  192 + assert(() {
  193 + if (o.verbose) {
  194 + stopStopwatch();
  195 + debugFill(
  196 + 'Creation time: ${elapsedStopwatch / Duration.microsecondsPerSecond} seconds');
  197 + debugFill('File size: ${s.offset} bytes');
  198 + // debugFill('Pages: ${rootID!.pdfDocument.pdfPageList.pages.length}');
  199 + debugFill('Objects: ${objects.length}');
  200 + writeDebug(s);
  201 + }
  202 + return true;
  203 + }());
  204 + }
121 205
122 @override 206 @override
123 String toString() { 207 String toString() {
124 final s = StringBuffer(); 208 final s = StringBuffer();
125 - for (final x in offsets) { 209 + for (final x in _offsets) {
126 s.writeln(' $x'); 210 s.writeln(' $x');
127 } 211 }
128 return s.toString(); 212 return s.toString();
129 } 213 }
130 214
131 - int outputLegacy(PdfObjectBase object, PdfStream s, PdfDict params) { 215 + int outputLegacy(PdfObjectBase o, PdfStream s) {
132 // Now scan through the offsets list. They should be in sequence. 216 // Now scan through the offsets list. They should be in sequence.
133 - offsets.sort((a, b) => a.id.compareTo(b.id)); 217 + _offsets.sort((a, b) => a.id.compareTo(b.id));
  218 + final size = _offsets.last.id + 1;
134 219
135 assert(() { 220 assert(() {
136 - if (object.verbose) { 221 + if (o.verbose) {
137 s.putComment(''); 222 s.putComment('');
138 s.putComment('-' * 78); 223 s.putComment('-' * 78);
139 - s.putComment('$runtimeType ${object.version.name}\n$this'); 224 + s.putComment('$runtimeType ${o.version.name}\n$this');
140 } 225 }
141 return true; 226 return true;
142 }()); 227 }());
@@ -156,7 +241,7 @@ class PdfXrefTable extends PdfDataType { @@ -156,7 +241,7 @@ class PdfXrefTable extends PdfDataType {
156 final objOffset = s.offset; 241 final objOffset = s.offset;
157 s.putString('xref\n'); 242 s.putString('xref\n');
158 243
159 - for (final x in offsets) { 244 + for (final x in _offsets) {
160 // check to see if block is in range 245 // check to see if block is in range
161 if (x.id != (lastId + 1)) { 246 if (x.id != (lastId + 1)) {
162 // no, so write this block, and reset 247 // no, so write this block, and reset
@@ -175,31 +260,33 @@ class PdfXrefTable extends PdfDataType { @@ -175,31 +260,33 @@ class PdfXrefTable extends PdfDataType {
175 260
176 // the trailer object 261 // the trailer object
177 assert(() { 262 assert(() {
178 - if (object.verbose) { 263 + if (o.verbose) {
179 s.putComment(''); 264 s.putComment('');
180 } 265 }
181 return true; 266 return true;
182 }()); 267 }());
183 s.putString('trailer\n'); 268 s.putString('trailer\n');
184 - params.output(object, s, object.verbose ? 0 : null); 269 + params['/Size'] = PdfNum(size);
  270 + params.output(o, s, o.verbose ? 0 : null);
185 s.putByte(0x0a); 271 s.putByte(0x0a);
186 272
187 return objOffset; 273 return objOffset;
188 } 274 }
189 275
190 /// Output a compressed cross-reference table 276 /// Output a compressed cross-reference table
191 - int outputCompressed(PdfObjectBase object, PdfStream s, PdfDict params) { 277 + int outputCompressed(PdfObjectBase o, PdfStream s) {
192 final offset = s.offset; 278 final offset = s.offset;
193 279
194 // Sort all references 280 // Sort all references
195 - offsets.sort((a, b) => a.id.compareTo(b.id)); 281 + _offsets.sort((a, b) => a.id.compareTo(b.id));
196 282
197 // Write this object too 283 // Write this object too
198 - final id = offsets.last.id + 1;  
199 - offsets.add(PdfXref(id, offset)); 284 + final id = _offsets.last.id + 1;
  285 + final size = id + 1;
  286 + _offsets.add(PdfXref(id, offset));
200 287
201 params['/Type'] = const PdfName('/XRef'); 288 params['/Type'] = const PdfName('/XRef');
202 - params['/Size'] = PdfNum(id + 1); 289 + params['/Size'] = PdfNum(size);
203 290
204 var firstId = 0; // First id in block 291 var firstId = 0; // First id in block
205 var lastId = 0; // The last id used 292 var lastId = 0; // The last id used
@@ -208,7 +295,7 @@ class PdfXrefTable extends PdfDataType { @@ -208,7 +295,7 @@ class PdfXrefTable extends PdfDataType {
208 // We need block 0 to exist 295 // We need block 0 to exist
209 blocks.add(firstId); 296 blocks.add(firstId);
210 297
211 - for (final x in offsets) { 298 + for (final x in _offsets) {
212 // check to see if block is in range 299 // check to see if block is in range
213 if (x.id != (lastId + 1)) { 300 if (x.id != (lastId + 1)) {
214 // no, so store this block, and reset 301 // no, so store this block, and reset
@@ -220,7 +307,7 @@ class PdfXrefTable extends PdfDataType { @@ -220,7 +307,7 @@ class PdfXrefTable extends PdfDataType {
220 } 307 }
221 blocks.add(lastId - firstId + 1); 308 blocks.add(lastId - firstId + 1);
222 309
223 - if (!(blocks.length == 2 && blocks[0] == 0 && blocks[1] == id + 1)) { 310 + if (!(blocks.length == 2 && blocks[0] == 0 && blocks[1] == size)) {
224 params['/Index'] = PdfArray.fromNum(blocks); 311 params['/Index'] = PdfArray.fromNum(blocks);
225 } 312 }
226 313
@@ -229,21 +316,21 @@ class PdfXrefTable extends PdfDataType { @@ -229,21 +316,21 @@ class PdfXrefTable extends PdfDataType {
229 params['/W'] = PdfArray.fromNum(w); 316 params['/W'] = PdfArray.fromNum(w);
230 final wl = w.reduce((a, b) => a + b); 317 final wl = w.reduce((a, b) => a + b);
231 318
232 - final o = ByteData((offsets.length + 1) * wl); 319 + final binOffsets = ByteData((_offsets.length + 1) * wl);
233 var ofs = 0; 320 var ofs = 0;
234 // Write offset zero, all zeros 321 // Write offset zero, all zeros
235 ofs += wl; 322 ofs += wl;
236 323
237 - for (final x in offsets) {  
238 - ofs = x._compressedRef(o, ofs, w); 324 + for (final x in _offsets) {
  325 + ofs = x._compressedRef(binOffsets, ofs, w);
239 } 326 }
240 327
241 // Write the object 328 // Write the object
242 assert(() { 329 assert(() {
243 - if (object.verbose) { 330 + if (o.verbose) {
244 s.putComment(''); 331 s.putComment('');
245 s.putComment('-' * 78); 332 s.putComment('-' * 78);
246 - s.putComment('$runtimeType ${object.version.name}\n$this'); 333 + s.putComment('$runtimeType ${o.version.name}\n$this');
247 } 334 }
248 return true; 335 return true;
249 }()); 336 }());
@@ -253,13 +340,13 @@ class PdfXrefTable extends PdfDataType { @@ -253,13 +340,13 @@ class PdfXrefTable extends PdfDataType {
253 s.putString('$id 0 obj\n'); 340 s.putString('$id 0 obj\n');
254 341
255 PdfDictStream( 342 PdfDictStream(
256 - data: o.buffer.asUint8List(), 343 + data: binOffsets.buffer.asUint8List(),
257 isBinary: false, 344 isBinary: false,
258 encrypt: false, 345 encrypt: false,
259 values: params.values, 346 values: params.values,
260 - ).output(object, s, object.verbose ? 0 : null); 347 + ).output(o, s, o.verbose ? 0 : null);
261 348
262 - s.putString('endobj\n'); 349 + s.putString('\nendobj\n');
263 return objOffset; 350 return objOffset;
264 } 351 }
265 } 352 }
@@ -19,26 +19,24 @@ import 'package:meta/meta.dart'; @@ -19,26 +19,24 @@ import 'package:meta/meta.dart';
19 import '../document.dart'; 19 import '../document.dart';
20 import '../format/base.dart'; 20 import '../format/base.dart';
21 import '../format/object_base.dart'; 21 import '../format/object_base.dart';
22 -import '../format/stream.dart';  
23 -import 'diagnostic.dart';  
24 22
25 /// Base Object used in the PDF file 23 /// Base Object used in the PDF file
26 -abstract class PdfObject<T extends PdfDataType> extends PdfObjectBase  
27 - with PdfDiagnostic { 24 +abstract class PdfObject<T extends PdfDataType> extends PdfObjectBase<T> {
28 /// This is usually called by extensors to this class, and sets the 25 /// This is usually called by extensors to this class, and sets the
29 /// Pdf Object Type 26 /// Pdf Object Type
30 PdfObject( 27 PdfObject(
31 this.pdfDocument, { 28 this.pdfDocument, {
32 - required this.params, 29 + required T params,
33 int objgen = 0, 30 int objgen = 0,
34 int? objser, 31 int? objser,
35 - }) : super(objser: objser ?? pdfDocument.genSerial(), objgen: objgen) { 32 + }) : super(
  33 + objser: objser ?? pdfDocument.genSerial(),
  34 + objgen: objgen,
  35 + params: params,
  36 + ) {
36 pdfDocument.objects.add(this); 37 pdfDocument.objects.add(this);
37 } 38 }
38 39
39 - /// This is the object parameters.  
40 - final T params;  
41 -  
42 /// This allows any Pdf object to refer to the document being constructed. 40 /// This allows any Pdf object to refer to the document being constructed.
43 final PdfDocument pdfDocument; 41 final PdfDocument pdfDocument;
44 42
@@ -56,35 +54,10 @@ abstract class PdfObject<T extends PdfDataType> extends PdfObjectBase @@ -56,35 +54,10 @@ abstract class PdfObject<T extends PdfDataType> extends PdfObjectBase
56 @override 54 @override
57 PdfVersion get version => pdfDocument.version; 55 PdfVersion get version => pdfDocument.version;
58 56
59 - /// Writes the object to the output stream.  
60 - void write(PdfStream os) {  
61 - prepare();  
62 - _writeStart(os);  
63 - writeContent(os);  
64 - _writeEnd(os);  
65 - }  
66 -  
67 /// Prepare the object to be written to the stream 57 /// Prepare the object to be written to the stream
68 @mustCallSuper 58 @mustCallSuper
69 void prepare() {} 59 void prepare() {}
70 60
71 - /// The write method should call this before writing anything to the  
72 - /// OutputStream. This will send the standard header for each object.  
73 - void _writeStart(PdfStream os) {  
74 - os.putString('$objser $objgen obj\n');  
75 - }  
76 -  
77 - void writeContent(PdfStream os) {  
78 - params.output(this, os, verbose ? 0 : null);  
79 - os.putByte(0x0a);  
80 - }  
81 -  
82 - /// The write method should call this after writing anything to the  
83 - /// OutputStream. This will send the standard footer for each object.  
84 - void _writeEnd(PdfStream os) {  
85 - os.putString('endobj\n');  
86 - }  
87 -  
88 @override 61 @override
89 String toString() => '$runtimeType $params'; 62 String toString() => '$runtimeType $params';
90 } 63 }
@@ -36,10 +36,10 @@ class PdfObjectDict extends PdfObject<PdfDict> { @@ -36,10 +36,10 @@ class PdfObjectDict extends PdfObject<PdfDict> {
36 } 36 }
37 37
38 @override 38 @override
39 - void writeContent(PdfStream os) { 39 + void writeContent(PdfStream s) {
40 if (params.isNotEmpty) { 40 if (params.isNotEmpty) {
41 - params.output(this, os, pdfDocument.verbose ? 0 : null);  
42 - os.putByte(0x0a); 41 + params.output(this, s, pdfDocument.verbose ? 0 : null);
  42 + s.putByte(0x0a);
43 } 43 }
44 } 44 }
45 } 45 }
@@ -35,11 +35,12 @@ class PdfObjectStream extends PdfObjectDict { @@ -35,11 +35,12 @@ class PdfObjectStream extends PdfObjectDict {
35 final bool isBinary; 35 final bool isBinary;
36 36
37 @override 37 @override
38 - void writeContent(PdfStream os) { 38 + void writeContent(PdfStream s) {
39 PdfDictStream.values( 39 PdfDictStream.values(
40 isBinary: isBinary, 40 isBinary: isBinary,
41 values: params.values, 41 values: params.values,
42 data: buf.output(), 42 data: buf.output(),
43 - ).output(this, os, pdfDocument.verbose ? 0 : null); 43 + ).output(this, s, pdfDocument.verbose ? 0 : null);
  44 + s.putByte(0x0a);
44 } 45 }
45 } 46 }
@@ -82,12 +82,12 @@ class PdfSignature extends PdfObjectDict { @@ -82,12 +82,12 @@ class PdfSignature extends PdfObjectDict {
82 int? _offsetEnd; 82 int? _offsetEnd;
83 83
84 @override 84 @override
85 - void write(PdfStream os) { 85 + void output(PdfStream s) {
86 value.preSign(this, params); 86 value.preSign(this, params);
87 87
88 - _offsetStart = os.offset + '$objser $objgen obj\n'.length;  
89 - super.write(os);  
90 - _offsetEnd = os.offset; 88 + _offsetStart = s.offset + '$objser $objgen obj\n'.length;
  89 + super.output(s);
  90 + _offsetEnd = s.offset;
91 } 91 }
92 92
93 Future<void> writeSignature(PdfStream os) async { 93 Future<void> writeSignature(PdfStream os) async {
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 -import 'format/array.dart';  
18 -import 'format/dict.dart';  
19 -import 'format/num.dart';  
20 -import 'format/object_base.dart';  
21 -import 'format/stream.dart';  
22 -import 'format/string.dart';  
23 -import 'format/xref.dart';  
24 -import 'obj/catalog.dart';  
25 -import 'obj/diagnostic.dart';  
26 -import 'obj/encryption.dart';  
27 -import 'obj/info.dart';  
28 -import 'obj/object.dart';  
29 -import 'obj/signature.dart';  
30 -  
31 -/// PDF document writer  
32 -class PdfOutput with PdfDiagnostic {  
33 - /// This creates a Pdf [PdfStream]  
34 - PdfOutput(this.os, this.version, this.verbose) {  
35 - String v;  
36 - switch (version) {  
37 - case PdfVersion.pdf_1_4:  
38 - v = '1.4';  
39 - break;  
40 - case PdfVersion.pdf_1_5:  
41 - v = '1.5';  
42 - break;  
43 - }  
44 -  
45 - os.putString('%PDF-$v\n');  
46 - os.putBytes(const <int>[0x25, 0xC2, 0xA5, 0xC2, 0xB1, 0xC3, 0xAB, 0x0A]);  
47 - assert(() {  
48 - if (verbose) {  
49 - setInsertion(os);  
50 - startStopwatch();  
51 - debugFill('Verbose dart_pdf');  
52 - debugFill('Producer https://github.com/DavBfr/dart_pdf');  
53 - debugFill('Creation date: ${DateTime.now()}');  
54 - }  
55 - return true;  
56 - }());  
57 - }  
58 -  
59 - /// Pdf version to output  
60 - final PdfVersion version;  
61 -  
62 - /// This is the actual [PdfStream] used to write to.  
63 - final PdfStream os;  
64 -  
65 - /// Cross reference table  
66 - final xref = PdfXrefTable();  
67 -  
68 - /// This is used to track the /Root object (catalog)  
69 - PdfCatalog? rootID;  
70 -  
71 - /// This is used to track the /Info object (info)  
72 - PdfInfo? infoID;  
73 -  
74 - /// This is used to track the /Encrypt object (encryption)  
75 - PdfEncryption? encryptID;  
76 -  
77 - /// This is used to track the /Sign object (signature)  
78 - PdfSignature? signatureID;  
79 -  
80 - /// Generate a compressed cross reference table  
81 - bool get isCompressed => version.index > PdfVersion.pdf_1_4.index;  
82 -  
83 - /// Verbose output  
84 - final bool verbose;  
85 -  
86 - /// This method writes a [PdfObject] to the stream.  
87 - void write(PdfObject ob) {  
88 - // Check the object to see if it's one that is needed later  
89 - if (ob is PdfCatalog) {  
90 - rootID = ob;  
91 - } else if (ob is PdfInfo) {  
92 - infoID = ob;  
93 - } else if (ob is PdfEncryption) {  
94 - encryptID = ob;  
95 - } else if (ob is PdfSignature) {  
96 - assert(signatureID == null, 'Only one document signature is allowed');  
97 - signatureID = ob;  
98 - }  
99 -  
100 - assert(() {  
101 - if (verbose) {  
102 - ob.setInsertion(os);  
103 - ob.startStopwatch();  
104 - }  
105 - return true;  
106 - }());  
107 - xref.add(PdfXref(ob.objser, os.offset, generation: ob.objgen));  
108 - ob.write(os);  
109 - assert(() {  
110 - if (verbose) {  
111 - ob.stopStopwatch();  
112 - ob.debugFill(  
113 - 'Creation time: ${ob.elapsedStopwatch / Duration.microsecondsPerSecond} seconds');  
114 - ob.writeDebug(os);  
115 - }  
116 - return true;  
117 - }());  
118 - }  
119 -  
120 - /// This closes the Stream, writing the xref table  
121 - Future<void> close() async {  
122 - if (rootID == null) {  
123 - throw Exception('Root object is not present in document');  
124 - }  
125 -  
126 - final params = PdfDict();  
127 -  
128 - // the number of entries (REQUIRED)  
129 - params['/Size'] = PdfNum(rootID!.pdfDocument.objser);  
130 -  
131 - // the /Root catalog indirect reference (REQUIRED)  
132 - params['/Root'] = rootID!.ref();  
133 - final id = PdfString(rootID!.pdfDocument.documentID,  
134 - format: PdfStringFormat.binary, encrypted: false);  
135 - params['/ID'] = PdfArray([id, id]);  
136 -  
137 - // the /Info reference (OPTIONAL)  
138 - if (infoID != null) {  
139 - params['/Info'] = infoID!.ref();  
140 - }  
141 -  
142 - // the /Encrypt reference (OPTIONAL)  
143 - if (encryptID != null) {  
144 - params['/Encrypt'] = encryptID!.ref();  
145 - }  
146 -  
147 - if (rootID!.pdfDocument.prev != null) {  
148 - params['/Prev'] = PdfNum(rootID!.pdfDocument.prev!.xrefOffset);  
149 - }  
150 -  
151 - final _xref = isCompressed  
152 - ? xref.outputCompressed(rootID!, os, params)  
153 - : xref.outputLegacy(rootID!, os, params);  
154 -  
155 - assert(() {  
156 - if (verbose) {  
157 - os.putComment('');  
158 - os.putComment('-' * 78);  
159 - os.putComment('$runtimeType');  
160 - }  
161 - return true;  
162 - }());  
163 -  
164 - // the reference to the xref object  
165 - os.putString('startxref\n$_xref\n%%EOF\n');  
166 -  
167 - assert(() {  
168 - if (verbose) {  
169 - stopStopwatch();  
170 - debugFill(  
171 - 'Creation time: ${elapsedStopwatch / Duration.microsecondsPerSecond} seconds');  
172 - debugFill('File size: ${os.offset} bytes');  
173 - debugFill('Pages: ${rootID!.pdfDocument.pdfPageList.pages.length}');  
174 - debugFill('Objects: ${xref.offsets.length}');  
175 - writeDebug(os);  
176 - }  
177 - return true;  
178 - }());  
179 -  
180 - if (signatureID != null) {  
181 - await signatureID!.writeSignature(os);  
182 - }  
183 - }  
184 -}  
@@ -18,6 +18,7 @@ export 'pdf/format/array.dart'; @@ -18,6 +18,7 @@ export 'pdf/format/array.dart';
18 export 'pdf/format/ascii85.dart'; 18 export 'pdf/format/ascii85.dart';
19 export 'pdf/format/base.dart'; 19 export 'pdf/format/base.dart';
20 export 'pdf/format/bool.dart'; 20 export 'pdf/format/bool.dart';
  21 +export 'pdf/format/diagnostic.dart';
21 export 'pdf/format/dict.dart'; 22 export 'pdf/format/dict.dart';
22 export 'pdf/format/dict_stream.dart'; 23 export 'pdf/format/dict_stream.dart';
23 export 'pdf/format/indirect.dart'; 24 export 'pdf/format/indirect.dart';
@@ -17,78 +17,74 @@ @@ -17,78 +17,74 @@
17 import 'dart:convert'; 17 import 'dart:convert';
18 import 'dart:io'; 18 import 'dart:io';
19 19
  20 +import 'package:pdf/pdf.dart';
20 import 'package:pdf/src/priv.dart'; 21 import 'package:pdf/src/priv.dart';
21 import 'package:test/test.dart'; 22 import 'package:test/test.dart';
22 23
23 -class BasicObject extends PdfObjectBase {  
24 - const BasicObject(int objser) : super(objser: objser); 24 +class BasicObject<T extends PdfDataType> extends PdfObjectBase<T> {
  25 + BasicObject({required super.objser, required super.params});
25 26
26 @override 27 @override
27 bool get verbose => true; 28 bool get verbose => true;
28 29
29 - void write(PdfStream os, PdfDataType value) {  
30 - os.putString('$objser $objgen obj\n');  
31 - value.output(this, os, verbose ? 0 : null);  
32 - os.putByte(0x0a);  
33 - os.putString('endobj\n');  
34 - } 30 + @override
  31 + PdfVersion get version => PdfVersion.pdf_1_4;
  32 +
  33 + @override
  34 + DeflateCallback? get deflate => zlib.encode;
35 } 35 }
36 36
37 void main() { 37 void main() {
38 test('Pdf Minimal', () async { 38 test('Pdf Minimal', () async {
39 - final pages = PdfDict({  
40 - '/Type': const PdfName('/Pages'),  
41 - '/Count': const PdfNum(1),  
42 - });  
43 -  
44 - final page = PdfDict({  
45 - '/Type': const PdfName('/Page'),  
46 - '/Parent': const PdfIndirect(2, 0),  
47 - '/MediaBox': PdfArray.fromNum([0, 0, 595.27559, 841.88976]),  
48 - '/Resources': PdfDict({  
49 - '/ProcSet': PdfArray([  
50 - const PdfName('/PDF'),  
51 - ]),  
52 - }),  
53 - '/Contents': const PdfIndirect(4, 0),  
54 - });  
55 -  
56 - final content = PdfDictStream(  
57 - data: latin1.encode('30 811.88976 m 200 641.88976 l S'),  
58 - );  
59 -  
60 - pages['/Kids'] = PdfArray([const PdfIndirect(3, 0)]);  
61 -  
62 - final catalog = PdfDict({  
63 - '/Type': const PdfName('/Catalog'),  
64 - '/Pages': const PdfIndirect(2, 0),  
65 - }); 39 + var objser = 1;
66 40
67 - final os = PdfStream(); 41 + final pages = BasicObject(
  42 + objser: objser++,
  43 + params: PdfDict({
  44 + '/Type': const PdfName('/Pages'),
  45 + '/Count': const PdfNum(1),
  46 + }));
68 47
69 - final xref = PdfXrefTable(); 48 + final content = BasicObject(
  49 + objser: objser++,
  50 + params: PdfDictStream(
  51 + data: latin1.encode('30 811.88976 m 200 641.88976 l S'),
  52 + ));
  53 +
  54 + final page = BasicObject(
  55 + objser: objser++,
  56 + params: PdfDict({
  57 + '/Type': const PdfName('/Page'),
  58 + '/Parent': pages.ref(),
  59 + '/MediaBox': PdfArray.fromNum([0, 0, 595.27559, 841.88976]),
  60 + '/Resources': PdfDict({
  61 + '/ProcSet': PdfArray([
  62 + const PdfName('/PDF'),
  63 + ]),
  64 + }),
  65 + '/Contents': content.ref(),
  66 + }));
  67 +
  68 + pages.params['/Kids'] = PdfArray([page.ref()]);
70 69
71 - os.putString('%PDF-1.4\n');  
72 - os.putBytes(const <int>[0x25, 0xC2, 0xA5, 0xC2, 0xB1, 0xC3, 0xAB, 0x0A]);  
73 -  
74 - xref.add(PdfXref(1, os.offset));  
75 - final cat = const BasicObject(1)..write(os, catalog);  
76 - xref.add(PdfXref(2, os.offset));  
77 - const BasicObject(2).write(os, pages);  
78 - xref.add(PdfXref(3, os.offset));  
79 - const BasicObject(3).write(os, page);  
80 - xref.add(PdfXref(4, os.offset));  
81 - const BasicObject(4).write(os, content);  
82 -  
83 - final xrefOffset = xref.outputLegacy(  
84 - cat,  
85 - os,  
86 - PdfDict({  
87 - '/Size': PdfNum(xref.offsets.length + 1),  
88 - '/Root': const PdfIndirect(1, 0), 70 + final catalog = BasicObject(
  71 + objser: objser++,
  72 + params: PdfDict({
  73 + '/Type': const PdfName('/Catalog'),
  74 + '/Pages': pages.ref(),
89 })); 75 }));
90 76
91 - os.putString('startxref\n$xrefOffset\n%%EOF\n'); 77 + final os = PdfStream();
  78 +
  79 + final xref = PdfXrefTable();
  80 + xref.objects.addAll([
  81 + catalog,
  82 + pages,
  83 + page,
  84 + content,
  85 + ]);
  86 +
  87 + xref.output(catalog, os);
92 88
93 final file = File('minimal.pdf'); 89 final file = File('minimal.pdf');
94 await file.writeAsBytes(os.output()); 90 await file.writeAsBytes(os.output());