David PHAM-VAN

Implement Compressed XREF

# Changelog
## 3.1.1
## 3.2.0
- Fix documentation
- Add Positioned.fill()
- Improve GraphicState
- Add SVG Color filter
- Implement Compressed XREF
## 3.1.0
... ...
... ... @@ -57,7 +57,7 @@ class PdfCatalog extends PdfObjectDict {
super.prepare();
/// the PDF specification version, overrides the header version starting from 1.4
params['/Version'] = PdfName('/${pdfDocument.version}');
params['/Version'] = PdfName('/${pdfDocument.versionString}');
params['/Pages'] = pdfPageList.ref();
... ...
... ... @@ -600,6 +600,7 @@ class PdfDictStream extends PdfDict<PdfDataType> {
Map<String, PdfDataType> values = const <String, PdfDataType>{},
required this.data,
this.isBinary = false,
this.encrypt = true,
}) : super.values(values);
final Uint8List data;
... ... @@ -608,6 +609,8 @@ class PdfDictStream extends PdfDict<PdfDataType> {
final bool isBinary;
final bool encrypt;
@override
void output(PdfStream s) {
final _values = PdfDict(values);
... ... @@ -638,7 +641,7 @@ class PdfDictStream extends PdfDict<PdfDataType> {
}
}
if (object.pdfDocument.encryption != null) {
if (encrypt && object.pdfDocument.encryption != null) {
_data = object.pdfDocument.encryption!.encrypt(_data, object);
}
... ...
... ... @@ -35,6 +35,15 @@ import 'page_list.dart';
import 'signature.dart';
import 'stream.dart';
/// PDF version to generate
enum PdfVersion {
/// PDF 1.4
pdf_1_4,
/// PDF 1.5 to 1.7
pdf_1_5,
}
/// Display hint for the PDF viewer
enum PdfPageMode {
/// This page mode indicates that the document
... ... @@ -69,6 +78,7 @@ class PdfDocument {
PdfPageMode pageMode = PdfPageMode.none,
DeflateCallback? deflate,
bool compress = true,
this.version = PdfVersion.pdf_1_4,
}) : deflate = compress ? (deflate ?? defaultDeflate) : null,
prev = null,
_objser = 1 {
... ... @@ -84,7 +94,8 @@ class PdfDocument {
DeflateCallback? deflate,
bool compress = true,
}) : deflate = compress ? (deflate ?? defaultDeflate) : null,
_objser = prev!.size {
_objser = prev!.size,
version = prev.version {
// Now create some standard objects
pdfPageList = PdfPageList(this);
pdfNames = PdfNames(this);
... ... @@ -107,6 +118,9 @@ class PdfDocument {
/// This is the Catalog object, which is required by each Pdf Document
late PdfCatalog catalog;
/// PDF version to generate
final PdfVersion version;
/// This is the info object. Although this is an optional object, we
/// include it.
PdfInfo? info;
... ... @@ -139,7 +153,7 @@ class PdfDocument {
PdfGraphicStates? _graphicStates;
/// The PDF specification version
final String version = '1.7';
final String versionString = '1.7';
/// This holds the current fonts
final Set<PdfFont> fonts = <PdfFont>{};
... ... @@ -188,7 +202,7 @@ class PdfDocument {
/// This writes the document to an OutputStream.
Future<void> _write(PdfStream os) async {
final pos = PdfOutput(os);
final pos = PdfOutput(os, version);
// Write each object to the [PdfStream]. We call via the output
// as that builds the xref table
... ...
... ... @@ -32,6 +32,8 @@ abstract class PdfDocumentParserBase {
/// The offset of the previous cross reference table
int get xrefOffset;
PdfVersion get version => PdfVersion.pdf_1_4;
/// Import the existing objects into the new PDF document
void mergeDocument(PdfDocument pdfDocument);
}
... ...
... ... @@ -16,6 +16,7 @@
import 'catalog.dart';
import 'data_types.dart';
import 'document.dart';
import 'encryption.dart';
import 'info.dart';
import 'object.dart';
... ... @@ -26,11 +27,24 @@ import 'xref.dart';
/// PDF document writer
class PdfOutput {
/// This creates a Pdf [PdfStream]
PdfOutput(this.os) {
os.putString('%PDF-1.4\n');
PdfOutput(this.os, this.version) {
String v;
switch (version) {
case PdfVersion.pdf_1_4:
v = '1.4';
break;
case PdfVersion.pdf_1_5:
v = '1.5';
break;
}
os.putString('%PDF-$v\n');
os.putBytes(const <int>[0x25, 0xC2, 0xA5, 0xC2, 0xB1, 0xC3, 0xAB, 0x0A]);
}
/// Pdf version to output
final PdfVersion version;
/// This is the actual [PdfStream] used to write to.
final PdfStream os;
... ... @@ -49,6 +63,9 @@ class PdfOutput {
/// This is used to track the /Sign object (signature)
PdfSignature? signatureID;
/// Generate a compressed cross reference table
bool get isCompressed => version.index > PdfVersion.pdf_1_4.index;
/// This method writes a [PdfObject] to the stream.
void write(PdfObject ob) {
// Check the object to see if it's one that is needed later
... ... @@ -73,12 +90,6 @@ class PdfOutput {
throw Exception('Root object is not present in document');
}
final _xref = os.offset;
xref.output(os);
// now the trailer object
os.putString('trailer\n');
final params = PdfDict();
// the number of entries (REQUIRED)
... ... @@ -104,9 +115,22 @@ class PdfOutput {
params['/Prev'] = PdfNum(rootID!.pdfDocument.prev!.xrefOffset);
}
// end the trailer object
params.output(os);
os.putString('\nstartxref\n$_xref\n%%EOF\n');
final _xref = os.offset;
if (isCompressed) {
xref.outputCompressed(rootID!, os, params);
} else {
xref.output(os);
}
if (!isCompressed) {
// the trailer object
os.putString('trailer\n');
params.output(os);
os.putByte(0x0a);
}
// the reference to the xref object
os.putString('startxref\n$_xref\n%%EOF\n');
if (signatureID != null) {
await signatureID!.writeSignature(os);
... ...
... ... @@ -14,6 +14,8 @@
* limitations under the License.
*/
import 'dart:typed_data';
import 'package:pdf/src/pdf/stream.dart';
import 'data_types.dart';
... ... @@ -45,6 +47,24 @@ class PdfXref {
return rs + ' n ';
}
/// The xref in the format of the compressed xref section in the Pdf file
int cref(ByteData o, int ofs, List<int> w) {
assert(w.length >= 3);
void setVal(int l, int v) {
for (var n = 0; n < l; n++) {
o.setUint8(ofs, (v >> (l - n - 1) * 8) & 0xff);
ofs++;
}
}
setVal(w[0], 1);
setVal(w[1], offset);
setVal(w[2], generation);
return ofs;
}
@override
bool operator ==(Object other) {
if (other is PdfXref) {
... ... @@ -113,4 +133,70 @@ class PdfXrefTable extends PdfDataType {
// now write the last block
_writeblock(s, firstid, block);
}
/// Output a compressed cross-reference table
void outputCompressed(PdfObject object, PdfStream s, PdfDict params) {
// Write this object too
final id = offsets.last.id + 1;
final offset = s.offset;
offsets.add(PdfXref(id, offset));
// Sort all references
offsets.sort((a, b) => a.id.compareTo(b.id));
s.putString('$id 0 obj\n');
params['/Type'] = const PdfName('/XRef');
params['/Size'] = PdfNum(id + 1);
var firstid = 0; // First id in block
var lastid = 0; // The last id used
final blocks = <int>[]; // xrefs in this block first, count
// We need block 0 to exist
blocks.add(firstid);
for (var x in offsets) {
// check to see if block is in range
if (x.id != (lastid + 1)) {
// no, so store this block, and reset
blocks.add(lastid - firstid + 1);
firstid = x.id;
blocks.add(firstid);
}
lastid = x.id;
}
blocks.add(lastid - firstid + 1);
if (!(blocks.length == 2 && blocks[0] == 0 && blocks[1] == id + 1)) {
params['/Index'] = PdfArray.fromNum(blocks);
}
var bytes = 2; // A pdf less than 256 bytes is unlikely
while (1 << (bytes * 8) < offset) {
bytes++;
}
final w = [1, bytes, 1];
params['/W'] = PdfArray.fromNum(w);
final wl = w.reduce((a, b) => a + b);
final o = ByteData((offsets.length + 2) * wl);
var ofs = 0;
// Write offset zero, all zeros
ofs += wl;
for (var x in offsets) {
ofs = x.cref(o, ofs, w);
}
// Write the object
PdfDictStream(
object: object,
data: o.buffer.asUint8List(),
isBinary: true,
encrypt: false,
values: params.values,
).output(s);
}
}
... ...
... ... @@ -26,6 +26,7 @@ class Document {
PdfPageMode pageMode = PdfPageMode.none,
DeflateCallback? deflate,
bool compress = true,
PdfVersion version = PdfVersion.pdf_1_4,
this.theme,
String? title,
String? author,
... ... @@ -37,6 +38,7 @@ class Document {
pageMode: pageMode,
deflate: deflate,
compress: compress,
version: version,
) {
if (title != null ||
author != null ||
... ...
... ... @@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl
homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf
repository: https://github.com/DavBfr/dart_pdf
issue_tracker: https://github.com/DavBfr/dart_pdf/issues
version: 3.1.1
version: 3.2.0
environment:
sdk: ">=2.12.0-0 <3.0.0"
... ...