Showing
9 changed files
with
312 additions
and
29 deletions
@@ -33,6 +33,7 @@ export 'src/pdf/obj/info.dart'; | @@ -33,6 +33,7 @@ export 'src/pdf/obj/info.dart'; | ||
33 | export 'src/pdf/obj/metadata.dart'; | 33 | export 'src/pdf/obj/metadata.dart'; |
34 | export 'src/pdf/obj/outline.dart'; | 34 | export 'src/pdf/obj/outline.dart'; |
35 | export 'src/pdf/obj/page.dart'; | 35 | export 'src/pdf/obj/page.dart'; |
36 | +export 'src/pdf/obj/page_label.dart'; | ||
36 | export 'src/pdf/obj/pattern.dart'; | 37 | export 'src/pdf/obj/pattern.dart'; |
37 | export 'src/pdf/obj/shading.dart'; | 38 | export 'src/pdf/obj/shading.dart'; |
38 | export 'src/pdf/obj/signature.dart'; | 39 | export 'src/pdf/obj/signature.dart'; |
@@ -30,6 +30,7 @@ import 'obj/names.dart'; | @@ -30,6 +30,7 @@ import 'obj/names.dart'; | ||
30 | import 'obj/object.dart'; | 30 | import 'obj/object.dart'; |
31 | import 'obj/outline.dart'; | 31 | import 'obj/outline.dart'; |
32 | import 'obj/page.dart'; | 32 | import 'obj/page.dart'; |
33 | +import 'obj/page_label.dart'; | ||
33 | import 'obj/page_list.dart'; | 34 | import 'obj/page_list.dart'; |
34 | import 'obj/signature.dart'; | 35 | import 'obj/signature.dart'; |
35 | import 'output.dart'; | 36 | import 'output.dart'; |
@@ -83,10 +84,8 @@ class PdfDocument { | @@ -83,10 +84,8 @@ class PdfDocument { | ||
83 | }) : deflate = compress ? (deflate ?? defaultDeflate) : null, | 84 | }) : deflate = compress ? (deflate ?? defaultDeflate) : null, |
84 | prev = null, | 85 | prev = null, |
85 | _objser = 1 { | 86 | _objser = 1 { |
86 | - // Now create some standard objects | ||
87 | - pdfPageList = PdfPageList(this); | ||
88 | - pdfNames = PdfNames(this); | ||
89 | - catalog = PdfCatalog(this, pdfPageList, pageMode, pdfNames); | 87 | + // create the catalog |
88 | + catalog = PdfCatalog(this, PdfPageList(this), pageMode); | ||
90 | } | 89 | } |
91 | 90 | ||
92 | PdfDocument.load( | 91 | PdfDocument.load( |
@@ -99,9 +98,7 @@ class PdfDocument { | @@ -99,9 +98,7 @@ class PdfDocument { | ||
99 | _objser = prev!.size, | 98 | _objser = prev!.size, |
100 | version = prev.version { | 99 | version = prev.version { |
101 | // Now create some standard objects | 100 | // Now create some standard objects |
102 | - pdfPageList = PdfPageList(this); | ||
103 | - pdfNames = PdfNames(this); | ||
104 | - catalog = PdfCatalog(this, pdfPageList, pageMode, pdfNames); | 101 | + catalog = PdfCatalog(this, PdfPageList(this), pageMode); |
105 | 102 | ||
106 | // Import the existing document | 103 | // Import the existing document |
107 | prev!.mergeDocument(this); | 104 | prev!.mergeDocument(this); |
@@ -118,7 +115,7 @@ class PdfDocument { | @@ -118,7 +115,7 @@ class PdfDocument { | ||
118 | final Set<PdfObject> objects = <PdfObject>{}; | 115 | final Set<PdfObject> objects = <PdfObject>{}; |
119 | 116 | ||
120 | /// This is the Catalog object, which is required by each Pdf Document | 117 | /// This is the Catalog object, which is required by each Pdf Document |
121 | - late PdfCatalog catalog; | 118 | + late final PdfCatalog catalog; |
122 | 119 | ||
123 | /// PDF version to generate | 120 | /// PDF version to generate |
124 | final PdfVersion version; | 121 | final PdfVersion version; |
@@ -129,13 +126,13 @@ class PdfDocument { | @@ -129,13 +126,13 @@ class PdfDocument { | ||
129 | PdfInfo? info; | 126 | PdfInfo? info; |
130 | 127 | ||
131 | /// This is the Pages object, which is required by each Pdf Document | 128 | /// This is the Pages object, which is required by each Pdf Document |
132 | - late PdfPageList pdfPageList; | ||
133 | - | ||
134 | - /// The name dictionary | ||
135 | - late PdfNames pdfNames; | 129 | + PdfPageList get pdfPageList => catalog.pdfPageList; |
136 | 130 | ||
137 | - /// This is the Outline object, which is optional | ||
138 | - PdfOutline? _outline; | 131 | + /// The anchor names dictionary |
132 | + PdfNames get pdfNames { | ||
133 | + catalog.names ??= PdfNames(this); | ||
134 | + return catalog.names!; | ||
135 | + } | ||
139 | 136 | ||
140 | /// This holds a [PdfObject] describing the default border for annotations. | 137 | /// This holds a [PdfObject] describing the default border for annotations. |
141 | /// It's only used when the document is being written. | 138 | /// It's only used when the document is being written. |
@@ -191,11 +188,14 @@ class PdfDocument { | @@ -191,11 +188,14 @@ class PdfDocument { | ||
191 | 188 | ||
192 | /// The root outline | 189 | /// The root outline |
193 | PdfOutline get outline { | 190 | PdfOutline get outline { |
194 | - if (_outline == null) { | ||
195 | - _outline = PdfOutline(this); | ||
196 | - catalog.outlines = _outline; | ||
197 | - } | ||
198 | - return _outline!; | 191 | + catalog.outlines ??= PdfOutline(this); |
192 | + return catalog.outlines!; | ||
193 | + } | ||
194 | + | ||
195 | + /// The root page labels | ||
196 | + PdfPageLabels get pageLabels { | ||
197 | + catalog.pageLabels ??= PdfPageLabels(this); | ||
198 | + return catalog.pageLabels!; | ||
199 | } | 199 | } |
200 | 200 | ||
201 | /// Graphic states for opacity and transfer modes | 201 | /// Graphic states for opacity and transfer modes |
@@ -21,6 +21,7 @@ import 'metadata.dart'; | @@ -21,6 +21,7 @@ import 'metadata.dart'; | ||
21 | import 'names.dart'; | 21 | import 'names.dart'; |
22 | import 'object_dict.dart'; | 22 | import 'object_dict.dart'; |
23 | import 'outline.dart'; | 23 | import 'outline.dart'; |
24 | +import 'page_label.dart'; | ||
24 | import 'page_list.dart'; | 25 | import 'page_list.dart'; |
25 | 26 | ||
26 | /// Pdf Catalog object | 27 | /// Pdf Catalog object |
@@ -30,7 +31,6 @@ class PdfCatalog extends PdfObjectDict { | @@ -30,7 +31,6 @@ class PdfCatalog extends PdfObjectDict { | ||
30 | PdfDocument pdfDocument, | 31 | PdfDocument pdfDocument, |
31 | this.pdfPageList, | 32 | this.pdfPageList, |
32 | this.pageMode, | 33 | this.pageMode, |
33 | - this.names, | ||
34 | ) : super(pdfDocument, type: '/Catalog'); | 34 | ) : super(pdfDocument, type: '/Catalog'); |
35 | 35 | ||
36 | /// The pages of the document | 36 | /// The pages of the document |
@@ -45,8 +45,11 @@ class PdfCatalog extends PdfObjectDict { | @@ -45,8 +45,11 @@ class PdfCatalog extends PdfObjectDict { | ||
45 | /// The initial page mode | 45 | /// The initial page mode |
46 | final PdfPageMode pageMode; | 46 | final PdfPageMode pageMode; |
47 | 47 | ||
48 | - /// The initial page mode | ||
49 | - final PdfNames names; | 48 | + /// The anchor names |
49 | + PdfNames? names; | ||
50 | + | ||
51 | + /// The page labels of the document | ||
52 | + PdfPageLabels? pageLabels; | ||
50 | 53 | ||
51 | /// These map the page modes just defined to the pagemodes setting of Pdf. | 54 | /// These map the page modes just defined to the pagemodes setting of Pdf. |
52 | static const List<String> _pdfPageModes = <String>[ | 55 | static const List<String> _pdfPageModes = <String>[ |
@@ -75,7 +78,14 @@ class PdfCatalog extends PdfObjectDict { | @@ -75,7 +78,14 @@ class PdfCatalog extends PdfObjectDict { | ||
75 | } | 78 | } |
76 | 79 | ||
77 | // the Names object | 80 | // the Names object |
78 | - params['/Names'] = names.ref(); | 81 | + if (names != null) { |
82 | + params['/Names'] = names!.ref(); | ||
83 | + } | ||
84 | + | ||
85 | + // the PageLabels object | ||
86 | + if (pageLabels != null && pageLabels!.labels.isNotEmpty) { | ||
87 | + params['/PageLabels'] = pageLabels!.ref(); | ||
88 | + } | ||
79 | 89 | ||
80 | // the /PageMode setting | 90 | // the /PageMode setting |
81 | params['/PageMode'] = PdfName(_pdfPageModes[pageMode.index]); | 91 | params['/PageMode'] = PdfName(_pdfPageModes[pageMode.index]); |
@@ -58,7 +58,7 @@ class PdfOutline extends PdfObjectDict { | @@ -58,7 +58,7 @@ class PdfOutline extends PdfObjectDict { | ||
58 | this.color, | 58 | this.color, |
59 | this.destMode = PdfOutlineMode.fitPage, | 59 | this.destMode = PdfOutlineMode.fitPage, |
60 | this.style = PdfOutlineStyle.normal, | 60 | this.style = PdfOutlineStyle.normal, |
61 | - int? page, | 61 | + String? page, |
62 | }) : assert(anchor == null || (dest == null && rect == null)), | 62 | }) : assert(anchor == null || (dest == null && rect == null)), |
63 | _page = page, | 63 | _page = page, |
64 | super(pdfDocument); | 64 | super(pdfDocument); |
@@ -76,10 +76,12 @@ class PdfOutline extends PdfObjectDict { | @@ -76,10 +76,12 @@ class PdfOutline extends PdfObjectDict { | ||
76 | PdfPage? dest; | 76 | PdfPage? dest; |
77 | 77 | ||
78 | /// Page number | 78 | /// Page number |
79 | - int? get page => | 79 | + String? get page => |
80 | _page ?? | 80 | _page ?? |
81 | - (dest != null ? pdfDocument.pdfPageList.pages.indexOf(dest!) + 1 : null); | ||
82 | - final int? _page; | 81 | + (dest != null |
82 | + ? (pdfDocument.pdfPageList.pages.indexOf(dest!) + 1).toString() | ||
83 | + : null); | ||
84 | + final String? _page; | ||
83 | 85 | ||
84 | /// The region on the destination page | 86 | /// The region on the destination page |
85 | final PdfRect? rect; | 87 | final PdfRect? rect; |
pdf/lib/src/pdf/obj/page_label.dart
0 → 100644
1 | +import '../../priv.dart'; | ||
2 | +import '../document.dart'; | ||
3 | +import 'object_dict.dart'; | ||
4 | + | ||
5 | +enum PdfPageLabelStyle { | ||
6 | + arabic, | ||
7 | + romanUpper, | ||
8 | + romanLower, | ||
9 | + lettersUpper, | ||
10 | + lettersLower | ||
11 | +} | ||
12 | + | ||
13 | +class PdfPageLabel { | ||
14 | + PdfPageLabel(this.prefix, {this.style, this.subsequent}); | ||
15 | + | ||
16 | + PdfPageLabel.arabic({this.prefix, this.subsequent}) | ||
17 | + : style = PdfPageLabelStyle.arabic; | ||
18 | + | ||
19 | + PdfPageLabel.romanUpper({this.prefix, this.subsequent}) | ||
20 | + : style = PdfPageLabelStyle.romanUpper; | ||
21 | + | ||
22 | + PdfPageLabel.romanLower({this.prefix, this.subsequent}) | ||
23 | + : style = PdfPageLabelStyle.romanLower; | ||
24 | + | ||
25 | + PdfPageLabel.lettersUpper({this.prefix, this.subsequent}) | ||
26 | + : style = PdfPageLabelStyle.lettersUpper; | ||
27 | + | ||
28 | + PdfPageLabel.lettersLower({this.prefix, this.subsequent}) | ||
29 | + : style = PdfPageLabelStyle.lettersLower; | ||
30 | + | ||
31 | + final PdfPageLabelStyle? style; | ||
32 | + final String? prefix; | ||
33 | + final int? subsequent; | ||
34 | + | ||
35 | + PdfDict toDict(PdfObject obj) { | ||
36 | + final PdfName? s; | ||
37 | + switch (style) { | ||
38 | + case PdfPageLabelStyle.arabic: | ||
39 | + s = const PdfName('/D'); | ||
40 | + break; | ||
41 | + case PdfPageLabelStyle.romanUpper: | ||
42 | + s = const PdfName('/R'); | ||
43 | + break; | ||
44 | + case PdfPageLabelStyle.romanLower: | ||
45 | + s = const PdfName('/r'); | ||
46 | + break; | ||
47 | + case PdfPageLabelStyle.lettersUpper: | ||
48 | + s = const PdfName('/A'); | ||
49 | + break; | ||
50 | + case PdfPageLabelStyle.lettersLower: | ||
51 | + s = const PdfName('/a'); | ||
52 | + break; | ||
53 | + case null: | ||
54 | + s = null; | ||
55 | + } | ||
56 | + return PdfDict({ | ||
57 | + if (s != null) '/S': s, | ||
58 | + if (prefix != null && prefix!.isNotEmpty) | ||
59 | + '/P': PdfSecString.fromString(obj, prefix!), | ||
60 | + if (subsequent != null) '/St': PdfNum(subsequent!) | ||
61 | + }); | ||
62 | + } | ||
63 | + | ||
64 | + String _toRoman(int decimal) { | ||
65 | + const dictionary = { | ||
66 | + 1000: 'M', | ||
67 | + 900: 'CM', | ||
68 | + 500: 'D', | ||
69 | + 400: 'CD,', | ||
70 | + 100: 'C', | ||
71 | + 90: 'XC', | ||
72 | + 50: 'L', | ||
73 | + 40: 'XL', | ||
74 | + 10: 'X', | ||
75 | + 9: 'IX', | ||
76 | + 5: 'V', | ||
77 | + 4: 'IV', | ||
78 | + 1: 'I' | ||
79 | + }; | ||
80 | + | ||
81 | + assert(decimal > 0 && decimal < 3999, | ||
82 | + 'Roman numerals are limited to the inclusive range of 1 to 3999.'); | ||
83 | + | ||
84 | + var result = ''; | ||
85 | + dictionary.forEach((k, v) { | ||
86 | + while (decimal >= k) { | ||
87 | + decimal -= k; | ||
88 | + result += v; | ||
89 | + } | ||
90 | + }); | ||
91 | + return result; | ||
92 | + } | ||
93 | + | ||
94 | + String _toLetters(int decimal) { | ||
95 | + final n = String.fromCharCode(0x41 + decimal % 26); | ||
96 | + final r = decimal ~/ 26 + 1; | ||
97 | + return n * r; | ||
98 | + } | ||
99 | + | ||
100 | + String asString([int index = 0]) { | ||
101 | + final i = subsequent == null ? index : index + subsequent!; | ||
102 | + | ||
103 | + final String suffix; | ||
104 | + switch (style) { | ||
105 | + case PdfPageLabelStyle.arabic: | ||
106 | + suffix = (i + 1).toString(); | ||
107 | + break; | ||
108 | + case PdfPageLabelStyle.romanUpper: | ||
109 | + suffix = _toRoman(i + 1); | ||
110 | + break; | ||
111 | + case PdfPageLabelStyle.romanLower: | ||
112 | + suffix = _toRoman(i + 1).toLowerCase(); | ||
113 | + break; | ||
114 | + case PdfPageLabelStyle.lettersUpper: | ||
115 | + suffix = _toLetters(i); | ||
116 | + break; | ||
117 | + case PdfPageLabelStyle.lettersLower: | ||
118 | + suffix = _toLetters(i).toLowerCase(); | ||
119 | + break; | ||
120 | + case null: | ||
121 | + suffix = ''; | ||
122 | + } | ||
123 | + return '${prefix ?? ''}$suffix'; | ||
124 | + } | ||
125 | +} | ||
126 | + | ||
127 | +/// Pdf PageLabels object | ||
128 | +class PdfPageLabels extends PdfObjectDict { | ||
129 | + /// Constructs a Pdf PageLabels object. | ||
130 | + PdfPageLabels(PdfDocument pdfDocument) : super(pdfDocument); | ||
131 | + | ||
132 | + final labels = <int, PdfPageLabel>{}; | ||
133 | + | ||
134 | + String pageLabel(int index) { | ||
135 | + final n = labels.keys.toList()..sort(); | ||
136 | + var current = PdfPageLabel.arabic(); | ||
137 | + var s = 0; | ||
138 | + for (final i in n) { | ||
139 | + if (index >= i) { | ||
140 | + current = labels[i]!; | ||
141 | + s = i; | ||
142 | + } | ||
143 | + } | ||
144 | + | ||
145 | + return current.asString(index - s); | ||
146 | + } | ||
147 | + | ||
148 | + Iterable<String> get names sync* { | ||
149 | + final n = labels.keys.toList()..sort(); | ||
150 | + var l = PdfPageLabel.arabic(); | ||
151 | + final len = pdfDocument.pdfPageList.pages.length; | ||
152 | + var c = 0; | ||
153 | + var b = c < n.length ? n[c] : len; | ||
154 | + var s = 0; | ||
155 | + for (var i = 0; i < len; i++) { | ||
156 | + if (i >= b) { | ||
157 | + l = labels[b]!; | ||
158 | + c++; | ||
159 | + b = c < n.length ? n[c] : len; | ||
160 | + s = i; | ||
161 | + } | ||
162 | + yield l.asString(i - s); | ||
163 | + } | ||
164 | + } | ||
165 | + | ||
166 | + @override | ||
167 | + void prepare() { | ||
168 | + super.prepare(); | ||
169 | + | ||
170 | + final nums = PdfArray(); | ||
171 | + for (final entry in labels.entries) { | ||
172 | + nums.add(PdfNum(entry.key)); | ||
173 | + nums.add(entry.value.toDict(this)); | ||
174 | + } | ||
175 | + | ||
176 | + params['/Nums'] = nums; | ||
177 | + } | ||
178 | +} |
@@ -622,7 +622,7 @@ class Outline extends Anchor { | @@ -622,7 +622,7 @@ class Outline extends Anchor { | ||
622 | anchor: name, | 622 | anchor: name, |
623 | color: color, | 623 | color: color, |
624 | style: style, | 624 | style: style, |
625 | - page: context.pageNumber, | 625 | + page: context.pageLabel, |
626 | )..effectiveLevel = level; | 626 | )..effectiveLevel = level; |
627 | 627 | ||
628 | final root = context.document.outline; | 628 | final root = context.document.outline; |
@@ -62,7 +62,16 @@ class Context { | @@ -62,7 +62,16 @@ class Context { | ||
62 | 62 | ||
63 | final PdfDocument document; | 63 | final PdfDocument document; |
64 | 64 | ||
65 | - int get pageNumber => document.pdfPageList.pages.indexOf(page) + 1; | 65 | + int get _pageNumber => document.pdfPageList.pages.indexOf(page); |
66 | + | ||
67 | + int get pageNumber => _pageNumber + 1; | ||
68 | + | ||
69 | + String get pageLabel => document.catalog.pageLabels == null | ||
70 | + ? pageNumber.toString() | ||
71 | + : document.pageLabels.pageLabel(_pageNumber); | ||
72 | + | ||
73 | + set pageLabel(String value) => | ||
74 | + document.pageLabels.labels[_pageNumber] = PdfPageLabel(value); | ||
66 | 75 | ||
67 | /// Number of pages in the document. | 76 | /// Number of pages in the document. |
68 | /// This value is not available in a MultiPage body and will be equal to pageNumber. | 77 | /// This value is not available in a MultiPage body and will be equal to pageNumber. |
pdf/test/page_labels_test.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 'package:pdf/pdf.dart'; | ||
18 | +import 'package:test/test.dart'; | ||
19 | + | ||
20 | +void main() { | ||
21 | + test('Page label', () async { | ||
22 | + final pdf = PdfDocument(); | ||
23 | + | ||
24 | + for (var i = 0; i < 5; i++) { | ||
25 | + PdfPage(pdf); | ||
26 | + } | ||
27 | + | ||
28 | + pdf.pageLabels.labels[0] = PdfPageLabel('Hello'); | ||
29 | + pdf.pageLabels.labels[1] = PdfPageLabel.lettersUpper(); | ||
30 | + pdf.pageLabels.labels[3] = PdfPageLabel.romanLower(prefix: 'appendix '); | ||
31 | + | ||
32 | + expect(pdf.pageLabels.pageLabel(0), 'Hello'); | ||
33 | + expect(pdf.pageLabels.pageLabel(1), 'A'); | ||
34 | + expect(pdf.pageLabels.pageLabel(2), 'B'); | ||
35 | + expect(pdf.pageLabels.pageLabel(3), 'appendix i'); | ||
36 | + expect(pdf.pageLabels.pageLabel(4), 'appendix ii'); | ||
37 | + }); | ||
38 | + | ||
39 | + test('Roman labels', () async { | ||
40 | + final pdf = PdfDocument(); | ||
41 | + | ||
42 | + for (var i = 0; i < 500; i++) { | ||
43 | + PdfPage(pdf); | ||
44 | + } | ||
45 | + | ||
46 | + pdf.pageLabels.labels[3] = PdfPageLabel.romanLower(); | ||
47 | + | ||
48 | + expect(pdf.pageLabels.pageLabel(0), '1'); | ||
49 | + expect(pdf.pageLabels.pageLabel(300), 'ccxcviii'); | ||
50 | + expect(pdf.pageLabels.names.toList()[300], 'ccxcviii'); | ||
51 | + }); | ||
52 | + | ||
53 | + test('No of labels', () async { | ||
54 | + final pdf = PdfDocument(); | ||
55 | + | ||
56 | + for (var i = 0; i < 500; i++) { | ||
57 | + PdfPage(pdf); | ||
58 | + } | ||
59 | + | ||
60 | + expect(pdf.pageLabels.pageLabel(0), '1'); | ||
61 | + expect(pdf.pageLabels.pageLabel(300), '301'); | ||
62 | + expect(pdf.pageLabels.names.toList()[300], '301'); | ||
63 | + }); | ||
64 | + | ||
65 | + test('Letter labels', () async { | ||
66 | + final pdf = PdfDocument(); | ||
67 | + | ||
68 | + for (var i = 0; i < 500; i++) { | ||
69 | + PdfPage(pdf); | ||
70 | + } | ||
71 | + | ||
72 | + pdf.pageLabels.labels[30] = PdfPageLabel.lettersLower(); | ||
73 | + | ||
74 | + expect(pdf.pageLabels.pageLabel(0), '1'); | ||
75 | + expect(pdf.pageLabels.pageLabel(30), 'a'); | ||
76 | + expect(pdf.pageLabels.names.toList()[30], 'a'); | ||
77 | + expect(pdf.pageLabels.pageLabel(40), 'k'); | ||
78 | + expect(pdf.pageLabels.names.toList()[40], 'k'); | ||
79 | + expect(pdf.pageLabels.pageLabel(60), 'ee'); | ||
80 | + expect(pdf.pageLabels.names.toList()[60], 'ee'); | ||
81 | + }); | ||
82 | +} |
-
Please register or login to post a comment