David PHAM-VAN

Add document loading

... ... @@ -7,6 +7,7 @@
- Add GridPaper widget
- Improve internal sructure
- Add some asserts on the TtfParser
- Add document loading
## 1.13.0
... ...
... ... @@ -92,9 +92,10 @@ final file = File("example.pdf");
await file.writeAsBytes(pdf.save());
```
## Encryption and Digital Signature
## Encryption, Digital Signature, and loading a PDF Document
Encryption using RC4-40, RC4-128, AES-128, and AES-256 is fully supported using a separate library.
This library also provides SHA1 or SHA-256 Digital Signature using your x509 certificate. The graphic signature is represented by a clickable widget that shows Digital Signature information.
It implememts a PDF parser to load an existing document and add pages, change pages, and add a signature.
Drop me an email for availability and more information.
... ...
... ... @@ -20,6 +20,7 @@ export 'src/border.dart';
export 'src/color.dart';
export 'src/colors.dart';
export 'src/document.dart';
export 'src/document_parser.dart';
export 'src/encryption.dart';
export 'src/exif.dart';
export 'src/font.dart';
... ...
... ... @@ -14,6 +14,7 @@
* limitations under the License.
*/
import 'dart:collection';
import 'dart:convert';
import 'dart:typed_data';
... ... @@ -54,6 +55,18 @@ class PdfBool extends PdfDataType {
void output(PdfStream s) {
s.putString(value ? 'true' : 'false');
}
@override
bool operator ==(Object other) {
if (other is PdfBool) {
return value == other.value;
}
return false;
}
@override
int get hashCode => value.hashCode;
}
class PdfNum extends PdfDataType {
... ... @@ -86,6 +99,18 @@ class PdfNum extends PdfDataType {
s.putString(r);
}
}
@override
bool operator ==(Object other) {
if (other is PdfNum) {
return value == other.value;
}
return false;
}
@override
int get hashCode => value.hashCode;
}
class PdfNumList extends PdfDataType {
... ... @@ -102,6 +127,18 @@ class PdfNumList extends PdfDataType {
PdfNum(values[n]).output(s);
}
}
@override
bool operator ==(Object other) {
if (other is PdfNumList) {
return values == other.values;
}
return false;
}
@override
int get hashCode => values.hashCode;
}
enum PdfStringFormat { binary, litteral }
... ... @@ -255,6 +292,18 @@ class PdfString extends PdfDataType {
void output(PdfStream s) {
_output(s, value);
}
@override
bool operator ==(Object other) {
if (other is PdfString) {
return value == other.value;
}
return false;
}
@override
int get hashCode => value.hashCode;
}
class PdfSecString extends PdfString {
... ... @@ -337,6 +386,18 @@ class PdfName extends PdfDataType {
}
s.putBytes(bytes);
}
@override
bool operator ==(Object other) {
if (other is PdfName) {
return value == other.value;
}
return false;
}
@override
int get hashCode => value.hashCode;
}
class PdfNull extends PdfDataType {
... ... @@ -346,6 +407,14 @@ class PdfNull extends PdfDataType {
void output(PdfStream s) {
s.putString('null');
}
@override
bool operator ==(Object other) {
return other is PdfNull;
}
@override
int get hashCode => null.hashCode;
}
class PdfIndirect extends PdfDataType {
... ... @@ -359,6 +428,18 @@ class PdfIndirect extends PdfDataType {
void output(PdfStream s) {
s.putString('$ser $gen R');
}
@override
bool operator ==(Object other) {
if (other is PdfIndirect) {
return ser == other.ser && gen == other.gen;
}
return false;
}
@override
int get hashCode => ser.hashCode + gen.hashCode;
}
class PdfArray extends PdfDataType {
... ... @@ -401,6 +482,33 @@ class PdfArray extends PdfDataType {
}
s.putString(']');
}
/// Make all values unique, preserving the order
void uniq() {
if (values.length <= 1) {
return;
}
// ignore: prefer_collection_literals
final uniques = LinkedHashMap<PdfDataType, bool>();
for (final s in values) {
uniques[s] = true;
}
values.clear();
values.addAll(uniques.keys);
}
@override
bool operator ==(Object other) {
if (other is PdfArray) {
return values == other.values;
}
return false;
}
@override
int get hashCode => values.hashCode;
}
class PdfDict extends PdfDataType {
... ... @@ -427,6 +535,10 @@ class PdfDict extends PdfDataType {
values[k] = v;
}
PdfDataType operator [](String k) {
return values[k];
}
@override
void output(PdfStream s) {
s.putBytes(const <int>[0x3c, 0x3c]);
... ... @@ -443,6 +555,39 @@ class PdfDict extends PdfDataType {
bool containsKey(String key) {
return values.containsKey(key);
}
void merge(PdfDict other) {
for (final key in other.values.keys) {
final value = other[key];
final current = values[key];
if (current == null) {
values[key] = value;
} else if (value is PdfArray && current is PdfArray) {
current.values.addAll(value.values);
current.uniq();
} else if (value is PdfDict && current is PdfDict) {
current.merge(value);
} else {
values[key] = value;
}
}
}
void addAll(PdfDict other) {
values.addAll(other.values);
}
@override
bool operator ==(Object other) {
if (other is PdfDict) {
return values == other.values;
}
return false;
}
@override
int get hashCode => values.hashCode;
}
class PdfColorType extends PdfDataType {
... ... @@ -468,4 +613,16 @@ class PdfColorType extends PdfDataType {
]).output(s);
}
}
@override
bool operator ==(Object other) {
if (other is PdfColorType) {
return color == other.color;
}
return false;
}
@override
int get hashCode => color.hashCode;
}
... ...
... ... @@ -23,6 +23,7 @@ import '../io/interface.dart'
if (dart.library.io) '../io/vm.dart'
if (dart.library.js) '../io/js.dart';
import 'catalog.dart';
import 'document_parser.dart';
import 'encryption.dart';
import 'font.dart';
import 'graphic_state.dart';
... ... @@ -70,7 +71,8 @@ class PdfDocument {
PdfPageMode pageMode = PdfPageMode.none,
DeflateCallback deflate,
bool compress = true,
}) : deflate = compress ? (deflate ?? defaultDeflate) : null {
}) : deflate = compress ? (deflate ?? defaultDeflate) : null,
prev = null {
_objser = 1;
// Now create some standard objects
... ... @@ -79,9 +81,30 @@ class PdfDocument {
catalog = PdfCatalog(this, pdfPageList, pageMode, pdfNames);
}
PdfDocument.load(
this.prev, {
PdfPageMode pageMode = PdfPageMode.none,
DeflateCallback deflate,
bool compress = true,
}) : deflate = compress ? (deflate ?? defaultDeflate) : null {
_objser = prev.size;
// Now create some standard objects
pdfPageList = PdfPageList(this);
pdfNames = PdfNames(this);
catalog = PdfCatalog(this, pdfPageList, pageMode, pdfNames);
// Import the existing document
prev.mergeDocument(this);
}
final PdfDocumentParserBase prev;
/// This is used to allocate objects a unique serial number in the document.
int _objser;
int get objser => _objser;
/// This vector contains each indirect object within the document.
final Set<PdfObject> objects = <PdfObject>{};
... ... @@ -146,7 +169,7 @@ class PdfDocument {
/// This returns a specific page. It's used mainly when using a
/// Serialized template file.
PdfPage page(int page) {
return pdfPageList.getPage(page);
return pdfPageList.pages[page];
}
/// The root outline
... ... @@ -182,6 +205,9 @@ class PdfDocument {
/// Generate the PDF document as a memory file
Uint8List save() {
final os = PdfStream();
if (prev != null) {
os.putBytes(prev.bytes);
}
_write(os);
return os.output();
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'dart:typed_data';
import 'package:pdf/src/document.dart';
/// Base class for loading an existing PDF document.
abstract class PdfDocumentParserBase {
/// Create a Document loader instance
PdfDocumentParserBase(this.bytes);
/// The existing PDF document content
final Uint8List bytes;
/// The objects size of the existing PDF document
int get size;
/// The offset of the previous cross reference table
int get xrefOffset;
/// Import the existing objects into the new PDF document
void mergeDocument(PdfDocument pdfDocument);
}
... ...
... ... @@ -138,6 +138,14 @@ mixin PdfGraphicStream on PdfObject {
resources['/ExtGState'] = pdfDocument.graphicStates.ref();
}
if (params.containsKey('/Resources')) {
final res = params['/Resources'];
if (res is PdfDict) {
res.merge(resources);
return;
}
}
params['/Resources'] = resources;
}
}
... ...
... ... @@ -83,4 +83,7 @@ class PdfObject {
/// Returns the unique serial number in Pdf format
PdfIndirect ref() => PdfIndirect(objser, objgen);
@override
String toString() => '$runtimeType $params';
}
... ...
... ... @@ -72,26 +72,23 @@ class PdfOutput {
final xref = os.offset;
os.putString('xref\n');
// Now scan through the offsets list. The should be in sequence,
// but just in case:
// Now scan through the offsets list. They should be in sequence.
offsets.sort((a, b) => a.id.compareTo(b.id));
var firstid = 0; // First id in block
var lastid = -1; // The last id used
var lastid = 0; // The last id used
final block = <PdfXref>[]; // xrefs in this block
// We need block 0 to exist
block.add(PdfXref(0, 0, generation: 65535));
for (var x in offsets) {
if (firstid == -1) {
firstid = x.id;
}
// check to see if block is in range (-1 means empty)
if (lastid > -1 && x.id != (lastid + 1)) {
// check to see if block is in range
if (lastid != null && x.id != (lastid + 1)) {
// no, so write this block, and reset
writeblock(firstid, block);
block.clear();
firstid = -1;
firstid = x.id;
}
// now add to block
... ... @@ -100,9 +97,7 @@ class PdfOutput {
}
// now write the last block
if (firstid > -1) {
writeblock(firstid, block);
}
writeblock(firstid, block);
// now the trailer object
os.putString('trailer\n');
... ... @@ -110,7 +105,7 @@ class PdfOutput {
final params = PdfDict();
// the number of entries (REQUIRED)
params['/Size'] = PdfNum(offsets.length + 1);
params['/Size'] = PdfNum(rootID.pdfDocument.objser);
// the /Root catalog indirect reference (REQUIRED)
if (rootID != null) {
... ... @@ -132,6 +127,10 @@ class PdfOutput {
params['/Encrypt'] = encryptID.ref();
}
if (rootID.pdfDocument.prev != null) {
params['/Prev'] = PdfNum(rootID.pdfDocument.prev.xrefOffset);
}
// end the trailer object
params.output(os);
os.putString('\nstartxref\n$xref\n%%EOF\n');
... ...
... ... @@ -27,9 +27,16 @@ import 'page_format.dart';
class PdfPage extends PdfObject with PdfGraphicStream {
/// This constructs a Page object, which will hold any contents for this
/// page.
PdfPage(PdfDocument pdfDocument, {this.pageFormat = PdfPageFormat.standard})
: super(pdfDocument, type: '/Page') {
pdfDocument.pdfPageList.pages.add(this);
PdfPage(
PdfDocument pdfDocument, {
this.pageFormat = PdfPageFormat.standard,
int index,
}) : super(pdfDocument, type: '/Page') {
if (index != null) {
pdfDocument.pdfPageList.pages.insert(index, this);
} else {
pdfDocument.pdfPageList.pages.add(this);
}
}
/// This is this page format, ie the size of the page, margins, and rotation
... ... @@ -38,10 +45,6 @@ class PdfPage extends PdfObject with PdfGraphicStream {
/// This holds the contents of the page.
List<PdfObjectStream> contents = <PdfObjectStream>[];
/// Object ID that contains a thumbnail sketch of the page.
/// -1 indicates no thumbnail.
PdfObject thumbnail;
/// This holds any Annotations contained within this page.
List<PdfAnnot> annotations = <PdfAnnot>[];
... ... @@ -72,30 +75,38 @@ class PdfPage extends PdfObject with PdfGraphicStream {
params['/MediaBox'] =
PdfArray.fromNum(<double>[0, 0, pageFormat.width, pageFormat.height]);
// Rotation (if not zero)
// if(rotate!=0) {
// os.write("/Rotate ");
// os.write(Integer.toString(rotate).getBytes());
// os.write("\n");
// }
// the /Contents pages object
// The graphic operations to draw the page
if (contents.isNotEmpty) {
if (contents.length == 1) {
params['/Contents'] = contents.first.ref();
} else {
params['/Contents'] = PdfArray.fromObjects(contents);
final contentList = PdfArray.fromObjects(contents);
if (params.containsKey('/Contents')) {
final prevContent = params['/Contents'];
if (prevContent is PdfArray) {
contentList.values.insertAll(0, prevContent.values);
} else {
contentList.values.insert(0, prevContent);
}
}
}
// The thumbnail
if (thumbnail != null) {
params['/Thumb'] = thumbnail.ref();
contentList.uniq();
if (contentList.values.length == 1) {
params['/Contents'] = contentList.values.first;
} else {
params['/Contents'] = contentList;
}
}
// The /Annots object
if (annotations.isNotEmpty) {
params['/Annots'] = PdfArray.fromObjects(annotations);
if (params.containsKey('/Annots')) {
final annotsList = params['/Annots'];
if (annotsList is PdfArray) {
annotsList.values.addAll(PdfArray.fromObjects(annotations).values);
}
} else {
params['/Annots'] = PdfArray.fromObjects(annotations);
}
}
}
}
... ...
... ... @@ -17,10 +17,10 @@
*/
import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';
import 'data_types.dart';
import 'document.dart';
import 'function.dart';
import 'graphic_stream.dart';
import 'graphics.dart';
import 'rect.dart';
... ...
... ... @@ -39,4 +39,7 @@ class PdfXref {
}
return rs + ' n ';
}
@override
String toString() => '$runtimeType $id $generation $offset';
}
... ...
... ... @@ -22,18 +22,18 @@ import 'page.dart';
import 'theme.dart';
class Document {
Document(
{PdfPageMode pageMode = PdfPageMode.none,
DeflateCallback deflate,
bool compress = true,
this.theme,
String title,
String author,
String creator,
String subject,
String keywords,
String producer})
: document = PdfDocument(
Document({
PdfPageMode pageMode = PdfPageMode.none,
DeflateCallback deflate,
bool compress = true,
this.theme,
String title,
String author,
String creator,
String subject,
String keywords,
String producer,
}) : document = PdfDocument(
pageMode: pageMode,
deflate: deflate,
compress: compress,
... ... @@ -44,13 +44,51 @@ class Document {
subject != null ||
keywords != null ||
producer != null) {
document.info = PdfInfo(document,
title: title,
author: author,
creator: creator,
subject: subject,
keywords: keywords,
producer: producer);
document.info = PdfInfo(
document,
title: title,
author: author,
creator: creator,
subject: subject,
keywords: keywords,
producer: producer,
);
}
}
Document.load(
PdfDocumentParserBase parser, {
PdfPageMode pageMode = PdfPageMode.none,
DeflateCallback deflate,
bool compress = true,
this.theme,
String title,
String author,
String creator,
String subject,
String keywords,
String producer,
}) : document = PdfDocument.load(
parser,
pageMode: pageMode,
deflate: deflate,
compress: compress,
) {
if (title != null ||
author != null ||
creator != null ||
subject != null ||
keywords != null ||
producer != null) {
document.info = PdfInfo(
document,
title: title,
author: author,
creator: creator,
subject: subject,
keywords: keywords,
producer: producer,
);
}
}
... ... @@ -64,8 +102,13 @@ class Document {
bool _paint = false;
void addPage(Page page) {
page.generate(this);
void addPage(Page page, {int index}) {
page.generate(this, index: index);
_pages.add(page);
}
void editPage(int index, Page page) {
page.generate(this, index: index, insert: false);
_pages.add(page);
}
... ...
... ... @@ -150,7 +150,7 @@ class MultiPage extends Page {
}
@override
void generate(Document document) {
void generate(Document document, {bool insert = true, int index}) {
if (_buildList == null) {
return;
}
... ... @@ -178,7 +178,7 @@ class MultiPage extends Page {
Context context;
double offsetEnd;
double offsetStart;
var index = 0;
var _index = 0;
var sameCount = 0;
final baseContext =
Context(document: document.document).inheritFromAll(<Inherited>[
... ... @@ -189,8 +189,8 @@ class MultiPage extends Page {
final children = _buildList(baseContext);
WidgetContext widgetContext;
while (index < children.length) {
final child = children[index];
while (_index < children.length) {
final child = children[_index];
var canSpan = false;
if (child is SpanningWidget) {
canSpan = child.canSpan;
... ... @@ -207,7 +207,11 @@ class MultiPage extends Page {
// Create a new page if we don't already have one
if (context == null || child is NewPage) {
final pdfPage = PdfPage(document.document, pageFormat: pageFormat);
final pdfPage = PdfPage(
document.document,
pageFormat: pageFormat,
index: index == null ? null : (index++),
);
context =
baseContext.copyWith(page: pdfPage, canvas: pdfPage.getGraphics());
... ... @@ -293,7 +297,7 @@ class MultiPage extends Page {
// Has it finished spanning?
if (!span.hasMoreWidgets) {
sameCount = 0;
index++;
_index++;
}
// Schedule a new page
... ... @@ -313,7 +317,7 @@ class MultiPage extends Page {
offsetStart -= child.box.height;
sameCount = 0;
index++;
_index++;
}
}
... ...
... ... @@ -96,8 +96,17 @@ class Page {
..fillPath();
}
void generate(Document document) {
_pdfPage = PdfPage(document.document, pageFormat: pageFormat);
void generate(Document document, {bool insert = true, int index}) {
if (index != null) {
if (insert) {
_pdfPage =
PdfPage(document.document, pageFormat: pageFormat, index: index);
} else {
_pdfPage = document.document.page(index);
}
} else {
_pdfPage = PdfPage(document.document, pageFormat: pageFormat);
}
}
void postProcess(Document document) {
... ...
... ... @@ -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: 1.13.0
version: 1.14.0
environment:
sdk: ">=2.3.0 <3.0.0"
... ...
... ... @@ -183,9 +183,10 @@ final title = 'Flutter Demo';
await Printing.layoutPdf(onLayout: (format) => _generatePdf(format, title));
```
## Encryption and Digital Signature
## Encryption, Digital Signature, and loading a PDF Document
Encryption using RC4-40, RC4-128, AES-128, and AES-256 is fully supported using a separate library.
This library also provides SHA1 or SHA-256 Digital Signature using your x509 certificate. The graphic signature is represented by a clickable widget that shows Digital Signature information.
It implememts a PDF parser to load an existing document and add pages, change pages, and add a signature.
Drop me an email for availability and more information.
... ...