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