Showing
13 changed files
with
247 additions
and
339 deletions
@@ -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 { |
pdf/lib/src/pdf/output.dart
deleted
100644 → 0
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()); |
-
Please register or login to post a comment