David PHAM-VAN

Implement Compressed XREF

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"