Showing
17 changed files
with
378 additions
and
74 deletions
@@ -92,9 +92,10 @@ final file = File("example.pdf"); | @@ -92,9 +92,10 @@ final file = File("example.pdf"); | ||
92 | await file.writeAsBytes(pdf.save()); | 92 | await file.writeAsBytes(pdf.save()); |
93 | ``` | 93 | ``` |
94 | 94 | ||
95 | -## Encryption and Digital Signature | 95 | +## Encryption, Digital Signature, and loading a PDF Document |
96 | 96 | ||
97 | Encryption using RC4-40, RC4-128, AES-128, and AES-256 is fully supported using a separate library. | 97 | Encryption using RC4-40, RC4-128, AES-128, and AES-256 is fully supported using a separate library. |
98 | 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. | 98 | 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. |
99 | +It implememts a PDF parser to load an existing document and add pages, change pages, and add a signature. | ||
99 | 100 | ||
100 | Drop me an email for availability and more information. | 101 | Drop me an email for availability and more information. |
@@ -20,6 +20,7 @@ export 'src/border.dart'; | @@ -20,6 +20,7 @@ export 'src/border.dart'; | ||
20 | export 'src/color.dart'; | 20 | export 'src/color.dart'; |
21 | export 'src/colors.dart'; | 21 | export 'src/colors.dart'; |
22 | export 'src/document.dart'; | 22 | export 'src/document.dart'; |
23 | +export 'src/document_parser.dart'; | ||
23 | export 'src/encryption.dart'; | 24 | export 'src/encryption.dart'; |
24 | export 'src/exif.dart'; | 25 | export 'src/exif.dart'; |
25 | export 'src/font.dart'; | 26 | export 'src/font.dart'; |
@@ -14,6 +14,7 @@ | @@ -14,6 +14,7 @@ | ||
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | 16 | ||
17 | +import 'dart:collection'; | ||
17 | import 'dart:convert'; | 18 | import 'dart:convert'; |
18 | import 'dart:typed_data'; | 19 | import 'dart:typed_data'; |
19 | 20 | ||
@@ -54,6 +55,18 @@ class PdfBool extends PdfDataType { | @@ -54,6 +55,18 @@ class PdfBool extends PdfDataType { | ||
54 | void output(PdfStream s) { | 55 | void output(PdfStream s) { |
55 | s.putString(value ? 'true' : 'false'); | 56 | s.putString(value ? 'true' : 'false'); |
56 | } | 57 | } |
58 | + | ||
59 | + @override | ||
60 | + bool operator ==(Object other) { | ||
61 | + if (other is PdfBool) { | ||
62 | + return value == other.value; | ||
63 | + } | ||
64 | + | ||
65 | + return false; | ||
66 | + } | ||
67 | + | ||
68 | + @override | ||
69 | + int get hashCode => value.hashCode; | ||
57 | } | 70 | } |
58 | 71 | ||
59 | class PdfNum extends PdfDataType { | 72 | class PdfNum extends PdfDataType { |
@@ -86,6 +99,18 @@ class PdfNum extends PdfDataType { | @@ -86,6 +99,18 @@ class PdfNum extends PdfDataType { | ||
86 | s.putString(r); | 99 | s.putString(r); |
87 | } | 100 | } |
88 | } | 101 | } |
102 | + | ||
103 | + @override | ||
104 | + bool operator ==(Object other) { | ||
105 | + if (other is PdfNum) { | ||
106 | + return value == other.value; | ||
107 | + } | ||
108 | + | ||
109 | + return false; | ||
110 | + } | ||
111 | + | ||
112 | + @override | ||
113 | + int get hashCode => value.hashCode; | ||
89 | } | 114 | } |
90 | 115 | ||
91 | class PdfNumList extends PdfDataType { | 116 | class PdfNumList extends PdfDataType { |
@@ -102,6 +127,18 @@ class PdfNumList extends PdfDataType { | @@ -102,6 +127,18 @@ class PdfNumList extends PdfDataType { | ||
102 | PdfNum(values[n]).output(s); | 127 | PdfNum(values[n]).output(s); |
103 | } | 128 | } |
104 | } | 129 | } |
130 | + | ||
131 | + @override | ||
132 | + bool operator ==(Object other) { | ||
133 | + if (other is PdfNumList) { | ||
134 | + return values == other.values; | ||
135 | + } | ||
136 | + | ||
137 | + return false; | ||
138 | + } | ||
139 | + | ||
140 | + @override | ||
141 | + int get hashCode => values.hashCode; | ||
105 | } | 142 | } |
106 | 143 | ||
107 | enum PdfStringFormat { binary, litteral } | 144 | enum PdfStringFormat { binary, litteral } |
@@ -255,6 +292,18 @@ class PdfString extends PdfDataType { | @@ -255,6 +292,18 @@ class PdfString extends PdfDataType { | ||
255 | void output(PdfStream s) { | 292 | void output(PdfStream s) { |
256 | _output(s, value); | 293 | _output(s, value); |
257 | } | 294 | } |
295 | + | ||
296 | + @override | ||
297 | + bool operator ==(Object other) { | ||
298 | + if (other is PdfString) { | ||
299 | + return value == other.value; | ||
300 | + } | ||
301 | + | ||
302 | + return false; | ||
303 | + } | ||
304 | + | ||
305 | + @override | ||
306 | + int get hashCode => value.hashCode; | ||
258 | } | 307 | } |
259 | 308 | ||
260 | class PdfSecString extends PdfString { | 309 | class PdfSecString extends PdfString { |
@@ -337,6 +386,18 @@ class PdfName extends PdfDataType { | @@ -337,6 +386,18 @@ class PdfName extends PdfDataType { | ||
337 | } | 386 | } |
338 | s.putBytes(bytes); | 387 | s.putBytes(bytes); |
339 | } | 388 | } |
389 | + | ||
390 | + @override | ||
391 | + bool operator ==(Object other) { | ||
392 | + if (other is PdfName) { | ||
393 | + return value == other.value; | ||
394 | + } | ||
395 | + | ||
396 | + return false; | ||
397 | + } | ||
398 | + | ||
399 | + @override | ||
400 | + int get hashCode => value.hashCode; | ||
340 | } | 401 | } |
341 | 402 | ||
342 | class PdfNull extends PdfDataType { | 403 | class PdfNull extends PdfDataType { |
@@ -346,6 +407,14 @@ class PdfNull extends PdfDataType { | @@ -346,6 +407,14 @@ class PdfNull extends PdfDataType { | ||
346 | void output(PdfStream s) { | 407 | void output(PdfStream s) { |
347 | s.putString('null'); | 408 | s.putString('null'); |
348 | } | 409 | } |
410 | + | ||
411 | + @override | ||
412 | + bool operator ==(Object other) { | ||
413 | + return other is PdfNull; | ||
414 | + } | ||
415 | + | ||
416 | + @override | ||
417 | + int get hashCode => null.hashCode; | ||
349 | } | 418 | } |
350 | 419 | ||
351 | class PdfIndirect extends PdfDataType { | 420 | class PdfIndirect extends PdfDataType { |
@@ -359,6 +428,18 @@ class PdfIndirect extends PdfDataType { | @@ -359,6 +428,18 @@ class PdfIndirect extends PdfDataType { | ||
359 | void output(PdfStream s) { | 428 | void output(PdfStream s) { |
360 | s.putString('$ser $gen R'); | 429 | s.putString('$ser $gen R'); |
361 | } | 430 | } |
431 | + | ||
432 | + @override | ||
433 | + bool operator ==(Object other) { | ||
434 | + if (other is PdfIndirect) { | ||
435 | + return ser == other.ser && gen == other.gen; | ||
436 | + } | ||
437 | + | ||
438 | + return false; | ||
439 | + } | ||
440 | + | ||
441 | + @override | ||
442 | + int get hashCode => ser.hashCode + gen.hashCode; | ||
362 | } | 443 | } |
363 | 444 | ||
364 | class PdfArray extends PdfDataType { | 445 | class PdfArray extends PdfDataType { |
@@ -401,6 +482,33 @@ class PdfArray extends PdfDataType { | @@ -401,6 +482,33 @@ class PdfArray extends PdfDataType { | ||
401 | } | 482 | } |
402 | s.putString(']'); | 483 | s.putString(']'); |
403 | } | 484 | } |
485 | + | ||
486 | + /// Make all values unique, preserving the order | ||
487 | + void uniq() { | ||
488 | + if (values.length <= 1) { | ||
489 | + return; | ||
490 | + } | ||
491 | + | ||
492 | + // ignore: prefer_collection_literals | ||
493 | + final uniques = LinkedHashMap<PdfDataType, bool>(); | ||
494 | + for (final s in values) { | ||
495 | + uniques[s] = true; | ||
496 | + } | ||
497 | + values.clear(); | ||
498 | + values.addAll(uniques.keys); | ||
499 | + } | ||
500 | + | ||
501 | + @override | ||
502 | + bool operator ==(Object other) { | ||
503 | + if (other is PdfArray) { | ||
504 | + return values == other.values; | ||
505 | + } | ||
506 | + | ||
507 | + return false; | ||
508 | + } | ||
509 | + | ||
510 | + @override | ||
511 | + int get hashCode => values.hashCode; | ||
404 | } | 512 | } |
405 | 513 | ||
406 | class PdfDict extends PdfDataType { | 514 | class PdfDict extends PdfDataType { |
@@ -427,6 +535,10 @@ class PdfDict extends PdfDataType { | @@ -427,6 +535,10 @@ class PdfDict extends PdfDataType { | ||
427 | values[k] = v; | 535 | values[k] = v; |
428 | } | 536 | } |
429 | 537 | ||
538 | + PdfDataType operator [](String k) { | ||
539 | + return values[k]; | ||
540 | + } | ||
541 | + | ||
430 | @override | 542 | @override |
431 | void output(PdfStream s) { | 543 | void output(PdfStream s) { |
432 | s.putBytes(const <int>[0x3c, 0x3c]); | 544 | s.putBytes(const <int>[0x3c, 0x3c]); |
@@ -443,6 +555,39 @@ class PdfDict extends PdfDataType { | @@ -443,6 +555,39 @@ class PdfDict extends PdfDataType { | ||
443 | bool containsKey(String key) { | 555 | bool containsKey(String key) { |
444 | return values.containsKey(key); | 556 | return values.containsKey(key); |
445 | } | 557 | } |
558 | + | ||
559 | + void merge(PdfDict other) { | ||
560 | + for (final key in other.values.keys) { | ||
561 | + final value = other[key]; | ||
562 | + final current = values[key]; | ||
563 | + if (current == null) { | ||
564 | + values[key] = value; | ||
565 | + } else if (value is PdfArray && current is PdfArray) { | ||
566 | + current.values.addAll(value.values); | ||
567 | + current.uniq(); | ||
568 | + } else if (value is PdfDict && current is PdfDict) { | ||
569 | + current.merge(value); | ||
570 | + } else { | ||
571 | + values[key] = value; | ||
572 | + } | ||
573 | + } | ||
574 | + } | ||
575 | + | ||
576 | + void addAll(PdfDict other) { | ||
577 | + values.addAll(other.values); | ||
578 | + } | ||
579 | + | ||
580 | + @override | ||
581 | + bool operator ==(Object other) { | ||
582 | + if (other is PdfDict) { | ||
583 | + return values == other.values; | ||
584 | + } | ||
585 | + | ||
586 | + return false; | ||
587 | + } | ||
588 | + | ||
589 | + @override | ||
590 | + int get hashCode => values.hashCode; | ||
446 | } | 591 | } |
447 | 592 | ||
448 | class PdfColorType extends PdfDataType { | 593 | class PdfColorType extends PdfDataType { |
@@ -468,4 +613,16 @@ class PdfColorType extends PdfDataType { | @@ -468,4 +613,16 @@ class PdfColorType extends PdfDataType { | ||
468 | ]).output(s); | 613 | ]).output(s); |
469 | } | 614 | } |
470 | } | 615 | } |
616 | + | ||
617 | + @override | ||
618 | + bool operator ==(Object other) { | ||
619 | + if (other is PdfColorType) { | ||
620 | + return color == other.color; | ||
621 | + } | ||
622 | + | ||
623 | + return false; | ||
624 | + } | ||
625 | + | ||
626 | + @override | ||
627 | + int get hashCode => color.hashCode; | ||
471 | } | 628 | } |
@@ -23,6 +23,7 @@ import '../io/interface.dart' | @@ -23,6 +23,7 @@ import '../io/interface.dart' | ||
23 | if (dart.library.io) '../io/vm.dart' | 23 | if (dart.library.io) '../io/vm.dart' |
24 | if (dart.library.js) '../io/js.dart'; | 24 | if (dart.library.js) '../io/js.dart'; |
25 | import 'catalog.dart'; | 25 | import 'catalog.dart'; |
26 | +import 'document_parser.dart'; | ||
26 | import 'encryption.dart'; | 27 | import 'encryption.dart'; |
27 | import 'font.dart'; | 28 | import 'font.dart'; |
28 | import 'graphic_state.dart'; | 29 | import 'graphic_state.dart'; |
@@ -70,7 +71,8 @@ class PdfDocument { | @@ -70,7 +71,8 @@ class PdfDocument { | ||
70 | PdfPageMode pageMode = PdfPageMode.none, | 71 | PdfPageMode pageMode = PdfPageMode.none, |
71 | DeflateCallback deflate, | 72 | DeflateCallback deflate, |
72 | bool compress = true, | 73 | bool compress = true, |
73 | - }) : deflate = compress ? (deflate ?? defaultDeflate) : null { | 74 | + }) : deflate = compress ? (deflate ?? defaultDeflate) : null, |
75 | + prev = null { | ||
74 | _objser = 1; | 76 | _objser = 1; |
75 | 77 | ||
76 | // Now create some standard objects | 78 | // Now create some standard objects |
@@ -79,9 +81,30 @@ class PdfDocument { | @@ -79,9 +81,30 @@ class PdfDocument { | ||
79 | catalog = PdfCatalog(this, pdfPageList, pageMode, pdfNames); | 81 | catalog = PdfCatalog(this, pdfPageList, pageMode, pdfNames); |
80 | } | 82 | } |
81 | 83 | ||
84 | + PdfDocument.load( | ||
85 | + this.prev, { | ||
86 | + PdfPageMode pageMode = PdfPageMode.none, | ||
87 | + DeflateCallback deflate, | ||
88 | + bool compress = true, | ||
89 | + }) : deflate = compress ? (deflate ?? defaultDeflate) : null { | ||
90 | + _objser = prev.size; | ||
91 | + | ||
92 | + // Now create some standard objects | ||
93 | + pdfPageList = PdfPageList(this); | ||
94 | + pdfNames = PdfNames(this); | ||
95 | + catalog = PdfCatalog(this, pdfPageList, pageMode, pdfNames); | ||
96 | + | ||
97 | + // Import the existing document | ||
98 | + prev.mergeDocument(this); | ||
99 | + } | ||
100 | + | ||
101 | + final PdfDocumentParserBase prev; | ||
102 | + | ||
82 | /// This is used to allocate objects a unique serial number in the document. | 103 | /// This is used to allocate objects a unique serial number in the document. |
83 | int _objser; | 104 | int _objser; |
84 | 105 | ||
106 | + int get objser => _objser; | ||
107 | + | ||
85 | /// This vector contains each indirect object within the document. | 108 | /// This vector contains each indirect object within the document. |
86 | final Set<PdfObject> objects = <PdfObject>{}; | 109 | final Set<PdfObject> objects = <PdfObject>{}; |
87 | 110 | ||
@@ -146,7 +169,7 @@ class PdfDocument { | @@ -146,7 +169,7 @@ class PdfDocument { | ||
146 | /// This returns a specific page. It's used mainly when using a | 169 | /// This returns a specific page. It's used mainly when using a |
147 | /// Serialized template file. | 170 | /// Serialized template file. |
148 | PdfPage page(int page) { | 171 | PdfPage page(int page) { |
149 | - return pdfPageList.getPage(page); | 172 | + return pdfPageList.pages[page]; |
150 | } | 173 | } |
151 | 174 | ||
152 | /// The root outline | 175 | /// The root outline |
@@ -182,6 +205,9 @@ class PdfDocument { | @@ -182,6 +205,9 @@ class PdfDocument { | ||
182 | /// Generate the PDF document as a memory file | 205 | /// Generate the PDF document as a memory file |
183 | Uint8List save() { | 206 | Uint8List save() { |
184 | final os = PdfStream(); | 207 | final os = PdfStream(); |
208 | + if (prev != null) { | ||
209 | + os.putBytes(prev.bytes); | ||
210 | + } | ||
185 | _write(os); | 211 | _write(os); |
186 | return os.output(); | 212 | return os.output(); |
187 | } | 213 | } |
pdf/lib/src/document_parser.dart
0 → 100644
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 'dart:typed_data'; | ||
18 | + | ||
19 | +import 'package:pdf/src/document.dart'; | ||
20 | + | ||
21 | +/// Base class for loading an existing PDF document. | ||
22 | +abstract class PdfDocumentParserBase { | ||
23 | + /// Create a Document loader instance | ||
24 | + PdfDocumentParserBase(this.bytes); | ||
25 | + | ||
26 | + /// The existing PDF document content | ||
27 | + final Uint8List bytes; | ||
28 | + | ||
29 | + /// The objects size of the existing PDF document | ||
30 | + int get size; | ||
31 | + | ||
32 | + /// The offset of the previous cross reference table | ||
33 | + int get xrefOffset; | ||
34 | + | ||
35 | + /// Import the existing objects into the new PDF document | ||
36 | + void mergeDocument(PdfDocument pdfDocument); | ||
37 | +} |
@@ -138,6 +138,14 @@ mixin PdfGraphicStream on PdfObject { | @@ -138,6 +138,14 @@ mixin PdfGraphicStream on PdfObject { | ||
138 | resources['/ExtGState'] = pdfDocument.graphicStates.ref(); | 138 | resources['/ExtGState'] = pdfDocument.graphicStates.ref(); |
139 | } | 139 | } |
140 | 140 | ||
141 | + if (params.containsKey('/Resources')) { | ||
142 | + final res = params['/Resources']; | ||
143 | + if (res is PdfDict) { | ||
144 | + res.merge(resources); | ||
145 | + return; | ||
146 | + } | ||
147 | + } | ||
148 | + | ||
141 | params['/Resources'] = resources; | 149 | params['/Resources'] = resources; |
142 | } | 150 | } |
143 | } | 151 | } |
@@ -83,4 +83,7 @@ class PdfObject { | @@ -83,4 +83,7 @@ class PdfObject { | ||
83 | 83 | ||
84 | /// Returns the unique serial number in Pdf format | 84 | /// Returns the unique serial number in Pdf format |
85 | PdfIndirect ref() => PdfIndirect(objser, objgen); | 85 | PdfIndirect ref() => PdfIndirect(objser, objgen); |
86 | + | ||
87 | + @override | ||
88 | + String toString() => '$runtimeType $params'; | ||
86 | } | 89 | } |
@@ -72,26 +72,23 @@ class PdfOutput { | @@ -72,26 +72,23 @@ class PdfOutput { | ||
72 | final xref = os.offset; | 72 | final xref = os.offset; |
73 | os.putString('xref\n'); | 73 | os.putString('xref\n'); |
74 | 74 | ||
75 | - // Now scan through the offsets list. The should be in sequence, | ||
76 | - // but just in case: | 75 | + // Now scan through the offsets list. They should be in sequence. |
76 | + offsets.sort((a, b) => a.id.compareTo(b.id)); | ||
77 | + | ||
77 | var firstid = 0; // First id in block | 78 | var firstid = 0; // First id in block |
78 | - var lastid = -1; // The last id used | 79 | + var lastid = 0; // The last id used |
79 | final block = <PdfXref>[]; // xrefs in this block | 80 | final block = <PdfXref>[]; // xrefs in this block |
80 | 81 | ||
81 | // We need block 0 to exist | 82 | // We need block 0 to exist |
82 | block.add(PdfXref(0, 0, generation: 65535)); | 83 | block.add(PdfXref(0, 0, generation: 65535)); |
83 | 84 | ||
84 | for (var x in offsets) { | 85 | for (var x in offsets) { |
85 | - if (firstid == -1) { | ||
86 | - firstid = x.id; | ||
87 | - } | ||
88 | - | ||
89 | - // check to see if block is in range (-1 means empty) | ||
90 | - if (lastid > -1 && x.id != (lastid + 1)) { | 86 | + // check to see if block is in range |
87 | + if (lastid != null && x.id != (lastid + 1)) { | ||
91 | // no, so write this block, and reset | 88 | // no, so write this block, and reset |
92 | writeblock(firstid, block); | 89 | writeblock(firstid, block); |
93 | block.clear(); | 90 | block.clear(); |
94 | - firstid = -1; | 91 | + firstid = x.id; |
95 | } | 92 | } |
96 | 93 | ||
97 | // now add to block | 94 | // now add to block |
@@ -100,9 +97,7 @@ class PdfOutput { | @@ -100,9 +97,7 @@ class PdfOutput { | ||
100 | } | 97 | } |
101 | 98 | ||
102 | // now write the last block | 99 | // now write the last block |
103 | - if (firstid > -1) { | ||
104 | - writeblock(firstid, block); | ||
105 | - } | 100 | + writeblock(firstid, block); |
106 | 101 | ||
107 | // now the trailer object | 102 | // now the trailer object |
108 | os.putString('trailer\n'); | 103 | os.putString('trailer\n'); |
@@ -110,7 +105,7 @@ class PdfOutput { | @@ -110,7 +105,7 @@ class PdfOutput { | ||
110 | final params = PdfDict(); | 105 | final params = PdfDict(); |
111 | 106 | ||
112 | // the number of entries (REQUIRED) | 107 | // the number of entries (REQUIRED) |
113 | - params['/Size'] = PdfNum(offsets.length + 1); | 108 | + params['/Size'] = PdfNum(rootID.pdfDocument.objser); |
114 | 109 | ||
115 | // the /Root catalog indirect reference (REQUIRED) | 110 | // the /Root catalog indirect reference (REQUIRED) |
116 | if (rootID != null) { | 111 | if (rootID != null) { |
@@ -132,6 +127,10 @@ class PdfOutput { | @@ -132,6 +127,10 @@ class PdfOutput { | ||
132 | params['/Encrypt'] = encryptID.ref(); | 127 | params['/Encrypt'] = encryptID.ref(); |
133 | } | 128 | } |
134 | 129 | ||
130 | + if (rootID.pdfDocument.prev != null) { | ||
131 | + params['/Prev'] = PdfNum(rootID.pdfDocument.prev.xrefOffset); | ||
132 | + } | ||
133 | + | ||
135 | // end the trailer object | 134 | // end the trailer object |
136 | params.output(os); | 135 | params.output(os); |
137 | os.putString('\nstartxref\n$xref\n%%EOF\n'); | 136 | os.putString('\nstartxref\n$xref\n%%EOF\n'); |
@@ -27,9 +27,16 @@ import 'page_format.dart'; | @@ -27,9 +27,16 @@ import 'page_format.dart'; | ||
27 | class PdfPage extends PdfObject with PdfGraphicStream { | 27 | class PdfPage extends PdfObject with PdfGraphicStream { |
28 | /// This constructs a Page object, which will hold any contents for this | 28 | /// This constructs a Page object, which will hold any contents for this |
29 | /// page. | 29 | /// page. |
30 | - PdfPage(PdfDocument pdfDocument, {this.pageFormat = PdfPageFormat.standard}) | ||
31 | - : super(pdfDocument, type: '/Page') { | ||
32 | - pdfDocument.pdfPageList.pages.add(this); | 30 | + PdfPage( |
31 | + PdfDocument pdfDocument, { | ||
32 | + this.pageFormat = PdfPageFormat.standard, | ||
33 | + int index, | ||
34 | + }) : super(pdfDocument, type: '/Page') { | ||
35 | + if (index != null) { | ||
36 | + pdfDocument.pdfPageList.pages.insert(index, this); | ||
37 | + } else { | ||
38 | + pdfDocument.pdfPageList.pages.add(this); | ||
39 | + } | ||
33 | } | 40 | } |
34 | 41 | ||
35 | /// This is this page format, ie the size of the page, margins, and rotation | 42 | /// This is this page format, ie the size of the page, margins, and rotation |
@@ -38,10 +45,6 @@ class PdfPage extends PdfObject with PdfGraphicStream { | @@ -38,10 +45,6 @@ class PdfPage extends PdfObject with PdfGraphicStream { | ||
38 | /// This holds the contents of the page. | 45 | /// This holds the contents of the page. |
39 | List<PdfObjectStream> contents = <PdfObjectStream>[]; | 46 | List<PdfObjectStream> contents = <PdfObjectStream>[]; |
40 | 47 | ||
41 | - /// Object ID that contains a thumbnail sketch of the page. | ||
42 | - /// -1 indicates no thumbnail. | ||
43 | - PdfObject thumbnail; | ||
44 | - | ||
45 | /// This holds any Annotations contained within this page. | 48 | /// This holds any Annotations contained within this page. |
46 | List<PdfAnnot> annotations = <PdfAnnot>[]; | 49 | List<PdfAnnot> annotations = <PdfAnnot>[]; |
47 | 50 | ||
@@ -72,30 +75,38 @@ class PdfPage extends PdfObject with PdfGraphicStream { | @@ -72,30 +75,38 @@ class PdfPage extends PdfObject with PdfGraphicStream { | ||
72 | params['/MediaBox'] = | 75 | params['/MediaBox'] = |
73 | PdfArray.fromNum(<double>[0, 0, pageFormat.width, pageFormat.height]); | 76 | PdfArray.fromNum(<double>[0, 0, pageFormat.width, pageFormat.height]); |
74 | 77 | ||
75 | - // Rotation (if not zero) | ||
76 | -// if(rotate!=0) { | ||
77 | -// os.write("/Rotate "); | ||
78 | -// os.write(Integer.toString(rotate).getBytes()); | ||
79 | -// os.write("\n"); | ||
80 | -// } | ||
81 | - | ||
82 | - // the /Contents pages object | 78 | + // The graphic operations to draw the page |
83 | if (contents.isNotEmpty) { | 79 | if (contents.isNotEmpty) { |
84 | - if (contents.length == 1) { | ||
85 | - params['/Contents'] = contents.first.ref(); | ||
86 | - } else { | ||
87 | - params['/Contents'] = PdfArray.fromObjects(contents); | 80 | + final contentList = PdfArray.fromObjects(contents); |
81 | + | ||
82 | + if (params.containsKey('/Contents')) { | ||
83 | + final prevContent = params['/Contents']; | ||
84 | + if (prevContent is PdfArray) { | ||
85 | + contentList.values.insertAll(0, prevContent.values); | ||
86 | + } else { | ||
87 | + contentList.values.insert(0, prevContent); | ||
88 | + } | ||
88 | } | 89 | } |
89 | - } | ||
90 | 90 | ||
91 | - // The thumbnail | ||
92 | - if (thumbnail != null) { | ||
93 | - params['/Thumb'] = thumbnail.ref(); | 91 | + contentList.uniq(); |
92 | + | ||
93 | + if (contentList.values.length == 1) { | ||
94 | + params['/Contents'] = contentList.values.first; | ||
95 | + } else { | ||
96 | + params['/Contents'] = contentList; | ||
97 | + } | ||
94 | } | 98 | } |
95 | 99 | ||
96 | // The /Annots object | 100 | // The /Annots object |
97 | if (annotations.isNotEmpty) { | 101 | if (annotations.isNotEmpty) { |
98 | - params['/Annots'] = PdfArray.fromObjects(annotations); | 102 | + if (params.containsKey('/Annots')) { |
103 | + final annotsList = params['/Annots']; | ||
104 | + if (annotsList is PdfArray) { | ||
105 | + annotsList.values.addAll(PdfArray.fromObjects(annotations).values); | ||
106 | + } | ||
107 | + } else { | ||
108 | + params['/Annots'] = PdfArray.fromObjects(annotations); | ||
109 | + } | ||
99 | } | 110 | } |
100 | } | 111 | } |
101 | } | 112 | } |
@@ -17,10 +17,10 @@ | @@ -17,10 +17,10 @@ | ||
17 | */ | 17 | */ |
18 | 18 | ||
19 | import 'package:meta/meta.dart'; | 19 | import 'package:meta/meta.dart'; |
20 | -import 'package:pdf/pdf.dart'; | ||
21 | 20 | ||
22 | import 'data_types.dart'; | 21 | import 'data_types.dart'; |
23 | import 'document.dart'; | 22 | import 'document.dart'; |
23 | +import 'function.dart'; | ||
24 | import 'graphic_stream.dart'; | 24 | import 'graphic_stream.dart'; |
25 | import 'graphics.dart'; | 25 | import 'graphics.dart'; |
26 | import 'rect.dart'; | 26 | import 'rect.dart'; |
@@ -22,18 +22,18 @@ import 'page.dart'; | @@ -22,18 +22,18 @@ import 'page.dart'; | ||
22 | import 'theme.dart'; | 22 | import 'theme.dart'; |
23 | 23 | ||
24 | class Document { | 24 | class Document { |
25 | - Document( | ||
26 | - {PdfPageMode pageMode = PdfPageMode.none, | ||
27 | - DeflateCallback deflate, | ||
28 | - bool compress = true, | ||
29 | - this.theme, | ||
30 | - String title, | ||
31 | - String author, | ||
32 | - String creator, | ||
33 | - String subject, | ||
34 | - String keywords, | ||
35 | - String producer}) | ||
36 | - : document = PdfDocument( | 25 | + Document({ |
26 | + PdfPageMode pageMode = PdfPageMode.none, | ||
27 | + DeflateCallback deflate, | ||
28 | + bool compress = true, | ||
29 | + this.theme, | ||
30 | + String title, | ||
31 | + String author, | ||
32 | + String creator, | ||
33 | + String subject, | ||
34 | + String keywords, | ||
35 | + String producer, | ||
36 | + }) : document = PdfDocument( | ||
37 | pageMode: pageMode, | 37 | pageMode: pageMode, |
38 | deflate: deflate, | 38 | deflate: deflate, |
39 | compress: compress, | 39 | compress: compress, |
@@ -44,13 +44,51 @@ class Document { | @@ -44,13 +44,51 @@ class Document { | ||
44 | subject != null || | 44 | subject != null || |
45 | keywords != null || | 45 | keywords != null || |
46 | producer != null) { | 46 | producer != null) { |
47 | - document.info = PdfInfo(document, | ||
48 | - title: title, | ||
49 | - author: author, | ||
50 | - creator: creator, | ||
51 | - subject: subject, | ||
52 | - keywords: keywords, | ||
53 | - producer: producer); | 47 | + document.info = PdfInfo( |
48 | + document, | ||
49 | + title: title, | ||
50 | + author: author, | ||
51 | + creator: creator, | ||
52 | + subject: subject, | ||
53 | + keywords: keywords, | ||
54 | + producer: producer, | ||
55 | + ); | ||
56 | + } | ||
57 | + } | ||
58 | + | ||
59 | + Document.load( | ||
60 | + PdfDocumentParserBase parser, { | ||
61 | + PdfPageMode pageMode = PdfPageMode.none, | ||
62 | + DeflateCallback deflate, | ||
63 | + bool compress = true, | ||
64 | + this.theme, | ||
65 | + String title, | ||
66 | + String author, | ||
67 | + String creator, | ||
68 | + String subject, | ||
69 | + String keywords, | ||
70 | + String producer, | ||
71 | + }) : document = PdfDocument.load( | ||
72 | + parser, | ||
73 | + pageMode: pageMode, | ||
74 | + deflate: deflate, | ||
75 | + compress: compress, | ||
76 | + ) { | ||
77 | + if (title != null || | ||
78 | + author != null || | ||
79 | + creator != null || | ||
80 | + subject != null || | ||
81 | + keywords != null || | ||
82 | + producer != null) { | ||
83 | + document.info = PdfInfo( | ||
84 | + document, | ||
85 | + title: title, | ||
86 | + author: author, | ||
87 | + creator: creator, | ||
88 | + subject: subject, | ||
89 | + keywords: keywords, | ||
90 | + producer: producer, | ||
91 | + ); | ||
54 | } | 92 | } |
55 | } | 93 | } |
56 | 94 | ||
@@ -64,8 +102,13 @@ class Document { | @@ -64,8 +102,13 @@ class Document { | ||
64 | 102 | ||
65 | bool _paint = false; | 103 | bool _paint = false; |
66 | 104 | ||
67 | - void addPage(Page page) { | ||
68 | - page.generate(this); | 105 | + void addPage(Page page, {int index}) { |
106 | + page.generate(this, index: index); | ||
107 | + _pages.add(page); | ||
108 | + } | ||
109 | + | ||
110 | + void editPage(int index, Page page) { | ||
111 | + page.generate(this, index: index, insert: false); | ||
69 | _pages.add(page); | 112 | _pages.add(page); |
70 | } | 113 | } |
71 | 114 |
@@ -150,7 +150,7 @@ class MultiPage extends Page { | @@ -150,7 +150,7 @@ class MultiPage extends Page { | ||
150 | } | 150 | } |
151 | 151 | ||
152 | @override | 152 | @override |
153 | - void generate(Document document) { | 153 | + void generate(Document document, {bool insert = true, int index}) { |
154 | if (_buildList == null) { | 154 | if (_buildList == null) { |
155 | return; | 155 | return; |
156 | } | 156 | } |
@@ -178,7 +178,7 @@ class MultiPage extends Page { | @@ -178,7 +178,7 @@ class MultiPage extends Page { | ||
178 | Context context; | 178 | Context context; |
179 | double offsetEnd; | 179 | double offsetEnd; |
180 | double offsetStart; | 180 | double offsetStart; |
181 | - var index = 0; | 181 | + var _index = 0; |
182 | var sameCount = 0; | 182 | var sameCount = 0; |
183 | final baseContext = | 183 | final baseContext = |
184 | Context(document: document.document).inheritFromAll(<Inherited>[ | 184 | Context(document: document.document).inheritFromAll(<Inherited>[ |
@@ -189,8 +189,8 @@ class MultiPage extends Page { | @@ -189,8 +189,8 @@ class MultiPage extends Page { | ||
189 | final children = _buildList(baseContext); | 189 | final children = _buildList(baseContext); |
190 | WidgetContext widgetContext; | 190 | WidgetContext widgetContext; |
191 | 191 | ||
192 | - while (index < children.length) { | ||
193 | - final child = children[index]; | 192 | + while (_index < children.length) { |
193 | + final child = children[_index]; | ||
194 | var canSpan = false; | 194 | var canSpan = false; |
195 | if (child is SpanningWidget) { | 195 | if (child is SpanningWidget) { |
196 | canSpan = child.canSpan; | 196 | canSpan = child.canSpan; |
@@ -207,7 +207,11 @@ class MultiPage extends Page { | @@ -207,7 +207,11 @@ class MultiPage extends Page { | ||
207 | 207 | ||
208 | // Create a new page if we don't already have one | 208 | // Create a new page if we don't already have one |
209 | if (context == null || child is NewPage) { | 209 | if (context == null || child is NewPage) { |
210 | - final pdfPage = PdfPage(document.document, pageFormat: pageFormat); | 210 | + final pdfPage = PdfPage( |
211 | + document.document, | ||
212 | + pageFormat: pageFormat, | ||
213 | + index: index == null ? null : (index++), | ||
214 | + ); | ||
211 | context = | 215 | context = |
212 | baseContext.copyWith(page: pdfPage, canvas: pdfPage.getGraphics()); | 216 | baseContext.copyWith(page: pdfPage, canvas: pdfPage.getGraphics()); |
213 | 217 | ||
@@ -293,7 +297,7 @@ class MultiPage extends Page { | @@ -293,7 +297,7 @@ class MultiPage extends Page { | ||
293 | // Has it finished spanning? | 297 | // Has it finished spanning? |
294 | if (!span.hasMoreWidgets) { | 298 | if (!span.hasMoreWidgets) { |
295 | sameCount = 0; | 299 | sameCount = 0; |
296 | - index++; | 300 | + _index++; |
297 | } | 301 | } |
298 | 302 | ||
299 | // Schedule a new page | 303 | // Schedule a new page |
@@ -313,7 +317,7 @@ class MultiPage extends Page { | @@ -313,7 +317,7 @@ class MultiPage extends Page { | ||
313 | 317 | ||
314 | offsetStart -= child.box.height; | 318 | offsetStart -= child.box.height; |
315 | sameCount = 0; | 319 | sameCount = 0; |
316 | - index++; | 320 | + _index++; |
317 | } | 321 | } |
318 | } | 322 | } |
319 | 323 |
@@ -96,8 +96,17 @@ class Page { | @@ -96,8 +96,17 @@ class Page { | ||
96 | ..fillPath(); | 96 | ..fillPath(); |
97 | } | 97 | } |
98 | 98 | ||
99 | - void generate(Document document) { | ||
100 | - _pdfPage = PdfPage(document.document, pageFormat: pageFormat); | 99 | + void generate(Document document, {bool insert = true, int index}) { |
100 | + if (index != null) { | ||
101 | + if (insert) { | ||
102 | + _pdfPage = | ||
103 | + PdfPage(document.document, pageFormat: pageFormat, index: index); | ||
104 | + } else { | ||
105 | + _pdfPage = document.document.page(index); | ||
106 | + } | ||
107 | + } else { | ||
108 | + _pdfPage = PdfPage(document.document, pageFormat: pageFormat); | ||
109 | + } | ||
101 | } | 110 | } |
102 | 111 | ||
103 | void postProcess(Document document) { | 112 | void postProcess(Document document) { |
@@ -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: 1.13.0 | 7 | +version: 1.14.0 |
8 | 8 | ||
9 | environment: | 9 | environment: |
10 | sdk: ">=2.3.0 <3.0.0" | 10 | sdk: ">=2.3.0 <3.0.0" |
@@ -183,9 +183,10 @@ final title = 'Flutter Demo'; | @@ -183,9 +183,10 @@ final title = 'Flutter Demo'; | ||
183 | await Printing.layoutPdf(onLayout: (format) => _generatePdf(format, title)); | 183 | await Printing.layoutPdf(onLayout: (format) => _generatePdf(format, title)); |
184 | ``` | 184 | ``` |
185 | 185 | ||
186 | -## Encryption and Digital Signature | 186 | +## Encryption, Digital Signature, and loading a PDF Document |
187 | 187 | ||
188 | Encryption using RC4-40, RC4-128, AES-128, and AES-256 is fully supported using a separate library. | 188 | Encryption using RC4-40, RC4-128, AES-128, and AES-256 is fully supported using a separate library. |
189 | 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. | 189 | 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. |
190 | +It implememts a PDF parser to load an existing document and add pages, change pages, and add a signature. | ||
190 | 191 | ||
191 | Drop me an email for availability and more information. | 192 | Drop me an email for availability and more information. |
-
Please register or login to post a comment