Showing
17 changed files
with
359 additions
and
55 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); | 100 | writeblock(firstid, block); |
| 105 | - } | ||
| 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,10 +27,17 @@ import 'page_format.dart'; | @@ -27,10 +27,17 @@ 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') { | 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 { | ||
| 32 | pdfDocument.pdfPageList.pages.add(this); | 38 | pdfDocument.pdfPageList.pages.add(this); |
| 33 | } | 39 | } |
| 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 |
| 36 | PdfPageFormat pageFormat; | 43 | PdfPageFormat pageFormat; |
| @@ -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(); | 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 { | 86 | } else { |
| 87 | - params['/Contents'] = PdfArray.fromObjects(contents); | 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) { |
| 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 { | ||
| 98 | params['/Annots'] = PdfArray.fromObjects(annotations); | 108 | params['/Annots'] = PdfArray.fromObjects(annotations); |
| 99 | } | 109 | } |
| 100 | } | 110 | } |
| 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,8 +22,8 @@ import 'page.dart'; | @@ -22,8 +22,8 @@ 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, | 25 | + Document({ |
| 26 | + PdfPageMode pageMode = PdfPageMode.none, | ||
| 27 | DeflateCallback deflate, | 27 | DeflateCallback deflate, |
| 28 | bool compress = true, | 28 | bool compress = true, |
| 29 | this.theme, | 29 | this.theme, |
| @@ -32,8 +32,8 @@ class Document { | @@ -32,8 +32,8 @@ class Document { | ||
| 32 | String creator, | 32 | String creator, |
| 33 | String subject, | 33 | String subject, |
| 34 | String keywords, | 34 | String keywords, |
| 35 | - String producer}) | ||
| 36 | - : document = PdfDocument( | 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, | 47 | + document.info = PdfInfo( |
| 48 | + document, | ||
| 48 | title: title, | 49 | title: title, |
| 49 | author: author, | 50 | author: author, |
| 50 | creator: creator, | 51 | creator: creator, |
| 51 | subject: subject, | 52 | subject: subject, |
| 52 | keywords: keywords, | 53 | keywords: keywords, |
| 53 | - producer: producer); | 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,9 +96,18 @@ class Page { | @@ -96,9 +96,18 @@ class Page { | ||
| 96 | ..fillPath(); | 96 | ..fillPath(); |
| 97 | } | 97 | } |
| 98 | 98 | ||
| 99 | - void generate(Document document) { | 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 { | ||
| 100 | _pdfPage = PdfPage(document.document, pageFormat: pageFormat); | 108 | _pdfPage = PdfPage(document.document, pageFormat: pageFormat); |
| 101 | } | 109 | } |
| 110 | + } | ||
| 102 | 111 | ||
| 103 | void postProcess(Document document) { | 112 | void postProcess(Document document) { |
| 104 | final canvas = _pdfPage.getGraphics(); | 113 | final canvas = _pdfPage.getGraphics(); |
| @@ -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