Showing
9 changed files
with
150 additions
and
18 deletions
| 1 | # Changelog | 1 | # Changelog |
| 2 | 2 | ||
| 3 | -## 3.1.1 | 3 | +## 3.2.0 |
| 4 | 4 | ||
| 5 | - Fix documentation | 5 | - Fix documentation |
| 6 | - Add Positioned.fill() | 6 | - Add Positioned.fill() |
| 7 | - Improve GraphicState | 7 | - Improve GraphicState |
| 8 | - Add SVG Color filter | 8 | - Add SVG Color filter |
| 9 | +- Implement Compressed XREF | ||
| 9 | 10 | ||
| 10 | ## 3.1.0 | 11 | ## 3.1.0 |
| 11 | 12 |
| @@ -57,7 +57,7 @@ class PdfCatalog extends PdfObjectDict { | @@ -57,7 +57,7 @@ class PdfCatalog extends PdfObjectDict { | ||
| 57 | super.prepare(); | 57 | super.prepare(); |
| 58 | 58 | ||
| 59 | /// the PDF specification version, overrides the header version starting from 1.4 | 59 | /// the PDF specification version, overrides the header version starting from 1.4 |
| 60 | - params['/Version'] = PdfName('/${pdfDocument.version}'); | 60 | + params['/Version'] = PdfName('/${pdfDocument.versionString}'); |
| 61 | 61 | ||
| 62 | params['/Pages'] = pdfPageList.ref(); | 62 | params['/Pages'] = pdfPageList.ref(); |
| 63 | 63 |
| @@ -600,6 +600,7 @@ class PdfDictStream extends PdfDict<PdfDataType> { | @@ -600,6 +600,7 @@ class PdfDictStream extends PdfDict<PdfDataType> { | ||
| 600 | Map<String, PdfDataType> values = const <String, PdfDataType>{}, | 600 | Map<String, PdfDataType> values = const <String, PdfDataType>{}, |
| 601 | required this.data, | 601 | required this.data, |
| 602 | this.isBinary = false, | 602 | this.isBinary = false, |
| 603 | + this.encrypt = true, | ||
| 603 | }) : super.values(values); | 604 | }) : super.values(values); |
| 604 | 605 | ||
| 605 | final Uint8List data; | 606 | final Uint8List data; |
| @@ -608,6 +609,8 @@ class PdfDictStream extends PdfDict<PdfDataType> { | @@ -608,6 +609,8 @@ class PdfDictStream extends PdfDict<PdfDataType> { | ||
| 608 | 609 | ||
| 609 | final bool isBinary; | 610 | final bool isBinary; |
| 610 | 611 | ||
| 612 | + final bool encrypt; | ||
| 613 | + | ||
| 611 | @override | 614 | @override |
| 612 | void output(PdfStream s) { | 615 | void output(PdfStream s) { |
| 613 | final _values = PdfDict(values); | 616 | final _values = PdfDict(values); |
| @@ -638,7 +641,7 @@ class PdfDictStream extends PdfDict<PdfDataType> { | @@ -638,7 +641,7 @@ class PdfDictStream extends PdfDict<PdfDataType> { | ||
| 638 | } | 641 | } |
| 639 | } | 642 | } |
| 640 | 643 | ||
| 641 | - if (object.pdfDocument.encryption != null) { | 644 | + if (encrypt && object.pdfDocument.encryption != null) { |
| 642 | _data = object.pdfDocument.encryption!.encrypt(_data, object); | 645 | _data = object.pdfDocument.encryption!.encrypt(_data, object); |
| 643 | } | 646 | } |
| 644 | 647 |
| @@ -35,6 +35,15 @@ import 'page_list.dart'; | @@ -35,6 +35,15 @@ import 'page_list.dart'; | ||
| 35 | import 'signature.dart'; | 35 | import 'signature.dart'; |
| 36 | import 'stream.dart'; | 36 | import 'stream.dart'; |
| 37 | 37 | ||
| 38 | +/// PDF version to generate | ||
| 39 | +enum PdfVersion { | ||
| 40 | + /// PDF 1.4 | ||
| 41 | + pdf_1_4, | ||
| 42 | + | ||
| 43 | + /// PDF 1.5 to 1.7 | ||
| 44 | + pdf_1_5, | ||
| 45 | +} | ||
| 46 | + | ||
| 38 | /// Display hint for the PDF viewer | 47 | /// Display hint for the PDF viewer |
| 39 | enum PdfPageMode { | 48 | enum PdfPageMode { |
| 40 | /// This page mode indicates that the document | 49 | /// This page mode indicates that the document |
| @@ -69,6 +78,7 @@ class PdfDocument { | @@ -69,6 +78,7 @@ class PdfDocument { | ||
| 69 | PdfPageMode pageMode = PdfPageMode.none, | 78 | PdfPageMode pageMode = PdfPageMode.none, |
| 70 | DeflateCallback? deflate, | 79 | DeflateCallback? deflate, |
| 71 | bool compress = true, | 80 | bool compress = true, |
| 81 | + this.version = PdfVersion.pdf_1_4, | ||
| 72 | }) : deflate = compress ? (deflate ?? defaultDeflate) : null, | 82 | }) : deflate = compress ? (deflate ?? defaultDeflate) : null, |
| 73 | prev = null, | 83 | prev = null, |
| 74 | _objser = 1 { | 84 | _objser = 1 { |
| @@ -84,7 +94,8 @@ class PdfDocument { | @@ -84,7 +94,8 @@ class PdfDocument { | ||
| 84 | DeflateCallback? deflate, | 94 | DeflateCallback? deflate, |
| 85 | bool compress = true, | 95 | bool compress = true, |
| 86 | }) : deflate = compress ? (deflate ?? defaultDeflate) : null, | 96 | }) : deflate = compress ? (deflate ?? defaultDeflate) : null, |
| 87 | - _objser = prev!.size { | 97 | + _objser = prev!.size, |
| 98 | + version = prev.version { | ||
| 88 | // Now create some standard objects | 99 | // Now create some standard objects |
| 89 | pdfPageList = PdfPageList(this); | 100 | pdfPageList = PdfPageList(this); |
| 90 | pdfNames = PdfNames(this); | 101 | pdfNames = PdfNames(this); |
| @@ -107,6 +118,9 @@ class PdfDocument { | @@ -107,6 +118,9 @@ class PdfDocument { | ||
| 107 | /// This is the Catalog object, which is required by each Pdf Document | 118 | /// This is the Catalog object, which is required by each Pdf Document |
| 108 | late PdfCatalog catalog; | 119 | late PdfCatalog catalog; |
| 109 | 120 | ||
| 121 | + /// PDF version to generate | ||
| 122 | + final PdfVersion version; | ||
| 123 | + | ||
| 110 | /// This is the info object. Although this is an optional object, we | 124 | /// This is the info object. Although this is an optional object, we |
| 111 | /// include it. | 125 | /// include it. |
| 112 | PdfInfo? info; | 126 | PdfInfo? info; |
| @@ -139,7 +153,7 @@ class PdfDocument { | @@ -139,7 +153,7 @@ class PdfDocument { | ||
| 139 | PdfGraphicStates? _graphicStates; | 153 | PdfGraphicStates? _graphicStates; |
| 140 | 154 | ||
| 141 | /// The PDF specification version | 155 | /// The PDF specification version |
| 142 | - final String version = '1.7'; | 156 | + final String versionString = '1.7'; |
| 143 | 157 | ||
| 144 | /// This holds the current fonts | 158 | /// This holds the current fonts |
| 145 | final Set<PdfFont> fonts = <PdfFont>{}; | 159 | final Set<PdfFont> fonts = <PdfFont>{}; |
| @@ -188,7 +202,7 @@ class PdfDocument { | @@ -188,7 +202,7 @@ class PdfDocument { | ||
| 188 | 202 | ||
| 189 | /// This writes the document to an OutputStream. | 203 | /// This writes the document to an OutputStream. |
| 190 | Future<void> _write(PdfStream os) async { | 204 | Future<void> _write(PdfStream os) async { |
| 191 | - final pos = PdfOutput(os); | 205 | + final pos = PdfOutput(os, version); |
| 192 | 206 | ||
| 193 | // Write each object to the [PdfStream]. We call via the output | 207 | // Write each object to the [PdfStream]. We call via the output |
| 194 | // as that builds the xref table | 208 | // as that builds the xref table |
| @@ -32,6 +32,8 @@ abstract class PdfDocumentParserBase { | @@ -32,6 +32,8 @@ abstract class PdfDocumentParserBase { | ||
| 32 | /// The offset of the previous cross reference table | 32 | /// The offset of the previous cross reference table |
| 33 | int get xrefOffset; | 33 | int get xrefOffset; |
| 34 | 34 | ||
| 35 | + PdfVersion get version => PdfVersion.pdf_1_4; | ||
| 36 | + | ||
| 35 | /// Import the existing objects into the new PDF document | 37 | /// Import the existing objects into the new PDF document |
| 36 | void mergeDocument(PdfDocument pdfDocument); | 38 | void mergeDocument(PdfDocument pdfDocument); |
| 37 | } | 39 | } |
| @@ -16,6 +16,7 @@ | @@ -16,6 +16,7 @@ | ||
| 16 | 16 | ||
| 17 | import 'catalog.dart'; | 17 | import 'catalog.dart'; |
| 18 | import 'data_types.dart'; | 18 | import 'data_types.dart'; |
| 19 | +import 'document.dart'; | ||
| 19 | import 'encryption.dart'; | 20 | import 'encryption.dart'; |
| 20 | import 'info.dart'; | 21 | import 'info.dart'; |
| 21 | import 'object.dart'; | 22 | import 'object.dart'; |
| @@ -26,11 +27,24 @@ import 'xref.dart'; | @@ -26,11 +27,24 @@ import 'xref.dart'; | ||
| 26 | /// PDF document writer | 27 | /// PDF document writer |
| 27 | class PdfOutput { | 28 | class PdfOutput { |
| 28 | /// This creates a Pdf [PdfStream] | 29 | /// This creates a Pdf [PdfStream] |
| 29 | - PdfOutput(this.os) { | ||
| 30 | - os.putString('%PDF-1.4\n'); | 30 | + PdfOutput(this.os, this.version) { |
| 31 | + String v; | ||
| 32 | + switch (version) { | ||
| 33 | + case PdfVersion.pdf_1_4: | ||
| 34 | + v = '1.4'; | ||
| 35 | + break; | ||
| 36 | + case PdfVersion.pdf_1_5: | ||
| 37 | + v = '1.5'; | ||
| 38 | + break; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + os.putString('%PDF-$v\n'); | ||
| 31 | os.putBytes(const <int>[0x25, 0xC2, 0xA5, 0xC2, 0xB1, 0xC3, 0xAB, 0x0A]); | 42 | os.putBytes(const <int>[0x25, 0xC2, 0xA5, 0xC2, 0xB1, 0xC3, 0xAB, 0x0A]); |
| 32 | } | 43 | } |
| 33 | 44 | ||
| 45 | + /// Pdf version to output | ||
| 46 | + final PdfVersion version; | ||
| 47 | + | ||
| 34 | /// This is the actual [PdfStream] used to write to. | 48 | /// This is the actual [PdfStream] used to write to. |
| 35 | final PdfStream os; | 49 | final PdfStream os; |
| 36 | 50 | ||
| @@ -49,6 +63,9 @@ class PdfOutput { | @@ -49,6 +63,9 @@ class PdfOutput { | ||
| 49 | /// This is used to track the /Sign object (signature) | 63 | /// This is used to track the /Sign object (signature) |
| 50 | PdfSignature? signatureID; | 64 | PdfSignature? signatureID; |
| 51 | 65 | ||
| 66 | + /// Generate a compressed cross reference table | ||
| 67 | + bool get isCompressed => version.index > PdfVersion.pdf_1_4.index; | ||
| 68 | + | ||
| 52 | /// This method writes a [PdfObject] to the stream. | 69 | /// This method writes a [PdfObject] to the stream. |
| 53 | void write(PdfObject ob) { | 70 | void write(PdfObject ob) { |
| 54 | // Check the object to see if it's one that is needed later | 71 | // Check the object to see if it's one that is needed later |
| @@ -73,12 +90,6 @@ class PdfOutput { | @@ -73,12 +90,6 @@ class PdfOutput { | ||
| 73 | throw Exception('Root object is not present in document'); | 90 | throw Exception('Root object is not present in document'); |
| 74 | } | 91 | } |
| 75 | 92 | ||
| 76 | - final _xref = os.offset; | ||
| 77 | - xref.output(os); | ||
| 78 | - | ||
| 79 | - // now the trailer object | ||
| 80 | - os.putString('trailer\n'); | ||
| 81 | - | ||
| 82 | final params = PdfDict(); | 93 | final params = PdfDict(); |
| 83 | 94 | ||
| 84 | // the number of entries (REQUIRED) | 95 | // the number of entries (REQUIRED) |
| @@ -104,9 +115,22 @@ class PdfOutput { | @@ -104,9 +115,22 @@ class PdfOutput { | ||
| 104 | params['/Prev'] = PdfNum(rootID!.pdfDocument.prev!.xrefOffset); | 115 | params['/Prev'] = PdfNum(rootID!.pdfDocument.prev!.xrefOffset); |
| 105 | } | 116 | } |
| 106 | 117 | ||
| 107 | - // end the trailer object | ||
| 108 | - params.output(os); | ||
| 109 | - os.putString('\nstartxref\n$_xref\n%%EOF\n'); | 118 | + final _xref = os.offset; |
| 119 | + if (isCompressed) { | ||
| 120 | + xref.outputCompressed(rootID!, os, params); | ||
| 121 | + } else { | ||
| 122 | + xref.output(os); | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + if (!isCompressed) { | ||
| 126 | + // the trailer object | ||
| 127 | + os.putString('trailer\n'); | ||
| 128 | + params.output(os); | ||
| 129 | + os.putByte(0x0a); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + // the reference to the xref object | ||
| 133 | + os.putString('startxref\n$_xref\n%%EOF\n'); | ||
| 110 | 134 | ||
| 111 | if (signatureID != null) { | 135 | if (signatureID != null) { |
| 112 | await signatureID!.writeSignature(os); | 136 | await signatureID!.writeSignature(os); |
| @@ -14,6 +14,8 @@ | @@ -14,6 +14,8 @@ | ||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | +import 'dart:typed_data'; | ||
| 18 | + | ||
| 17 | import 'package:pdf/src/pdf/stream.dart'; | 19 | import 'package:pdf/src/pdf/stream.dart'; |
| 18 | 20 | ||
| 19 | import 'data_types.dart'; | 21 | import 'data_types.dart'; |
| @@ -45,6 +47,24 @@ class PdfXref { | @@ -45,6 +47,24 @@ class PdfXref { | ||
| 45 | return rs + ' n '; | 47 | return rs + ' n '; |
| 46 | } | 48 | } |
| 47 | 49 | ||
| 50 | + /// The xref in the format of the compressed xref section in the Pdf file | ||
| 51 | + int cref(ByteData o, int ofs, List<int> w) { | ||
| 52 | + assert(w.length >= 3); | ||
| 53 | + | ||
| 54 | + void setVal(int l, int v) { | ||
| 55 | + for (var n = 0; n < l; n++) { | ||
| 56 | + o.setUint8(ofs, (v >> (l - n - 1) * 8) & 0xff); | ||
| 57 | + ofs++; | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + setVal(w[0], 1); | ||
| 62 | + setVal(w[1], offset); | ||
| 63 | + setVal(w[2], generation); | ||
| 64 | + | ||
| 65 | + return ofs; | ||
| 66 | + } | ||
| 67 | + | ||
| 48 | @override | 68 | @override |
| 49 | bool operator ==(Object other) { | 69 | bool operator ==(Object other) { |
| 50 | if (other is PdfXref) { | 70 | if (other is PdfXref) { |
| @@ -113,4 +133,70 @@ class PdfXrefTable extends PdfDataType { | @@ -113,4 +133,70 @@ class PdfXrefTable extends PdfDataType { | ||
| 113 | // now write the last block | 133 | // now write the last block |
| 114 | _writeblock(s, firstid, block); | 134 | _writeblock(s, firstid, block); |
| 115 | } | 135 | } |
| 136 | + | ||
| 137 | + /// Output a compressed cross-reference table | ||
| 138 | + void outputCompressed(PdfObject object, PdfStream s, PdfDict params) { | ||
| 139 | + // Write this object too | ||
| 140 | + final id = offsets.last.id + 1; | ||
| 141 | + final offset = s.offset; | ||
| 142 | + offsets.add(PdfXref(id, offset)); | ||
| 143 | + | ||
| 144 | + // Sort all references | ||
| 145 | + offsets.sort((a, b) => a.id.compareTo(b.id)); | ||
| 146 | + | ||
| 147 | + s.putString('$id 0 obj\n'); | ||
| 148 | + | ||
| 149 | + params['/Type'] = const PdfName('/XRef'); | ||
| 150 | + params['/Size'] = PdfNum(id + 1); | ||
| 151 | + | ||
| 152 | + var firstid = 0; // First id in block | ||
| 153 | + var lastid = 0; // The last id used | ||
| 154 | + final blocks = <int>[]; // xrefs in this block first, count | ||
| 155 | + | ||
| 156 | + // We need block 0 to exist | ||
| 157 | + blocks.add(firstid); | ||
| 158 | + | ||
| 159 | + for (var x in offsets) { | ||
| 160 | + // check to see if block is in range | ||
| 161 | + if (x.id != (lastid + 1)) { | ||
| 162 | + // no, so store this block, and reset | ||
| 163 | + blocks.add(lastid - firstid + 1); | ||
| 164 | + firstid = x.id; | ||
| 165 | + blocks.add(firstid); | ||
| 166 | + } | ||
| 167 | + lastid = x.id; | ||
| 168 | + } | ||
| 169 | + blocks.add(lastid - firstid + 1); | ||
| 170 | + | ||
| 171 | + if (!(blocks.length == 2 && blocks[0] == 0 && blocks[1] == id + 1)) { | ||
| 172 | + params['/Index'] = PdfArray.fromNum(blocks); | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + var bytes = 2; // A pdf less than 256 bytes is unlikely | ||
| 176 | + while (1 << (bytes * 8) < offset) { | ||
| 177 | + bytes++; | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + final w = [1, bytes, 1]; | ||
| 181 | + params['/W'] = PdfArray.fromNum(w); | ||
| 182 | + final wl = w.reduce((a, b) => a + b); | ||
| 183 | + | ||
| 184 | + final o = ByteData((offsets.length + 2) * wl); | ||
| 185 | + var ofs = 0; | ||
| 186 | + // Write offset zero, all zeros | ||
| 187 | + ofs += wl; | ||
| 188 | + | ||
| 189 | + for (var x in offsets) { | ||
| 190 | + ofs = x.cref(o, ofs, w); | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + // Write the object | ||
| 194 | + PdfDictStream( | ||
| 195 | + object: object, | ||
| 196 | + data: o.buffer.asUint8List(), | ||
| 197 | + isBinary: true, | ||
| 198 | + encrypt: false, | ||
| 199 | + values: params.values, | ||
| 200 | + ).output(s); | ||
| 201 | + } | ||
| 116 | } | 202 | } |
| @@ -26,6 +26,7 @@ class Document { | @@ -26,6 +26,7 @@ class Document { | ||
| 26 | PdfPageMode pageMode = PdfPageMode.none, | 26 | PdfPageMode pageMode = PdfPageMode.none, |
| 27 | DeflateCallback? deflate, | 27 | DeflateCallback? deflate, |
| 28 | bool compress = true, | 28 | bool compress = true, |
| 29 | + PdfVersion version = PdfVersion.pdf_1_4, | ||
| 29 | this.theme, | 30 | this.theme, |
| 30 | String? title, | 31 | String? title, |
| 31 | String? author, | 32 | String? author, |
| @@ -37,6 +38,7 @@ class Document { | @@ -37,6 +38,7 @@ class Document { | ||
| 37 | pageMode: pageMode, | 38 | pageMode: pageMode, |
| 38 | deflate: deflate, | 39 | deflate: deflate, |
| 39 | compress: compress, | 40 | compress: compress, |
| 41 | + version: version, | ||
| 40 | ) { | 42 | ) { |
| 41 | if (title != null || | 43 | if (title != null || |
| 42 | author != null || | 44 | author != null || |
| @@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl | @@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl | ||
| 4 | homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf | 4 | homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf |
| 5 | repository: https://github.com/DavBfr/dart_pdf | 5 | repository: https://github.com/DavBfr/dart_pdf |
| 6 | issue_tracker: https://github.com/DavBfr/dart_pdf/issues | 6 | issue_tracker: https://github.com/DavBfr/dart_pdf/issues |
| 7 | -version: 3.1.1 | 7 | +version: 3.2.0 |
| 8 | 8 | ||
| 9 | environment: | 9 | environment: |
| 10 | sdk: ">=2.12.0-0 <3.0.0" | 10 | sdk: ">=2.12.0-0 <3.0.0" |
-
Please register or login to post a comment