Committed by
David PHAM-VAN
Improve outlines containing non-sequential level increments
Showing
3 changed files
with
153 additions
and
12 deletions
@@ -6,6 +6,7 @@ | @@ -6,6 +6,7 @@ | ||
6 | - Depreciate Font.stringSize | 6 | - Depreciate Font.stringSize |
7 | - Implement fallback font | 7 | - Implement fallback font |
8 | - Implement Emoji support | 8 | - Implement Emoji support |
9 | +- Improve outlines containing non-sequential level increments [Roel Spilker] | ||
9 | 10 | ||
10 | ## 3.6.6 | 11 | ## 3.6.6 |
11 | 12 |
@@ -623,24 +623,28 @@ class Outline extends Anchor { | @@ -623,24 +623,28 @@ class Outline extends Anchor { | ||
623 | color: color, | 623 | color: color, |
624 | style: style, | 624 | style: style, |
625 | page: context.pageNumber, | 625 | page: context.pageNumber, |
626 | - ); | 626 | + )..effectiveLevel = level; |
627 | 627 | ||
628 | - var parent = context.document.outline; | ||
629 | - var l = level; | 628 | + final root = context.document.outline; |
630 | 629 | ||
631 | - while (l > 0) { | ||
632 | - if (parent.effectiveLevel == l) { | ||
633 | - break; | ||
634 | - } | 630 | + // find the most recently added outline |
631 | + var actualLevel= -1; | ||
632 | + var candidate = root; | ||
633 | + while (candidate.outlines.isNotEmpty) { | ||
634 | + candidate = candidate.outlines.last; | ||
635 | + actualLevel++; | ||
636 | + } | ||
635 | 637 | ||
636 | - if (parent.outlines.isEmpty) { | ||
637 | - parent.effectiveLevel = level; | 638 | + // find the latest added outline with a level lower than ours |
639 | + while (candidate != root) { | ||
640 | + final candidateLevel = candidate.effectiveLevel ?? actualLevel; | ||
641 | + if (candidateLevel < level) { | ||
638 | break; | 642 | break; |
639 | } | 643 | } |
640 | - parent = parent.outlines.last; | ||
641 | - l--; | 644 | + candidate = candidate.parent!; |
645 | + actualLevel--; | ||
642 | } | 646 | } |
643 | 647 | ||
644 | - parent.add(_outline!); | 648 | + candidate.add(_outline!); |
645 | } | 649 | } |
646 | } | 650 | } |
@@ -83,8 +83,144 @@ void main() { | @@ -83,8 +83,144 @@ void main() { | ||
83 | ); | 83 | ); |
84 | }); | 84 | }); |
85 | 85 | ||
86 | + group('Levels', () { | ||
87 | + test('Well-formed', () { | ||
88 | + final generated = _OutlineBuilder() | ||
89 | + .add('Part 1', 0) | ||
90 | + .add('Chapter 1', 1) | ||
91 | + .add('Paragraph 1.1', 2) | ||
92 | + .add('Paragraph 1.2', 2) | ||
93 | + .add('Paragraph 1.3', 2) | ||
94 | + .add('Chapter 2', 1) | ||
95 | + .add('Paragraph 2.1', 2) | ||
96 | + .add('Paragraph 2.2', 2) | ||
97 | + .render() | ||
98 | + .join('\n'); | ||
99 | + const expected = '''null | ||
100 | + Part 1 | ||
101 | + Chapter 1 | ||
102 | + Paragraph 1.1 | ||
103 | + Paragraph 1.2 | ||
104 | + Paragraph 1.3 | ||
105 | + Chapter 2 | ||
106 | + Paragraph 2.1 | ||
107 | + Paragraph 2.2'''; | ||
108 | + | ||
109 | + expect(generated, expected); | ||
110 | + }); | ||
111 | + | ||
112 | + test('Does not start with level 0', () { | ||
113 | + final generated = _OutlineBuilder() | ||
114 | + .add('Part 1', 1) | ||
115 | + .add('Chapter 1', 2) | ||
116 | + .add('Chapter 2', 2) | ||
117 | + .render() | ||
118 | + .join('\n'); | ||
119 | + const expected = '''null | ||
120 | + Part 1 | ||
121 | + Chapter 1 | ||
122 | + Chapter 2'''; | ||
123 | + | ||
124 | + expect(generated, expected); | ||
125 | + }); | ||
126 | + | ||
127 | + test('Contains non-sequential level increment', () { | ||
128 | + final generated = _OutlineBuilder() | ||
129 | + .add('Part 1', 0) | ||
130 | + .add('Chapter 1', 2) | ||
131 | + .add('Paragraph 1.1', 4) | ||
132 | + .add('Paragraph 1.2', 4) | ||
133 | + .add('Paragraph 1.3', 4) | ||
134 | + .add('Chapter 2', 2) | ||
135 | + .add('Paragraph 2.1', 4) | ||
136 | + .add('Paragraph 2.2', 4) | ||
137 | + .render() | ||
138 | + .join('\n'); | ||
139 | + | ||
140 | + const expected = '''null | ||
141 | + Part 1 | ||
142 | + Chapter 1 | ||
143 | + Paragraph 1.1 | ||
144 | + Paragraph 1.2 | ||
145 | + Paragraph 1.3 | ||
146 | + Chapter 2 | ||
147 | + Paragraph 2.1 | ||
148 | + Paragraph 2.2'''; | ||
149 | + | ||
150 | + expect(generated, expected); | ||
151 | + }); | ||
152 | + | ||
153 | + test('Reverse leveling', () { | ||
154 | + final generated = _OutlineBuilder() | ||
155 | + .add('Paragraph 2.2', 2) | ||
156 | + .add('Paragraph 2.1', 2) | ||
157 | + .add('Chapter 2', 1) | ||
158 | + .add('Paragraph 1.3', 2) | ||
159 | + .add('Paragraph 1.2', 2) | ||
160 | + .add('Paragraph 1.1', 2) | ||
161 | + .add('Chapter 1', 1) | ||
162 | + .add('Part 1', 0) | ||
163 | + .render() | ||
164 | + .join('\n'); | ||
165 | + | ||
166 | + const expected = '''null | ||
167 | + Paragraph 2.2 | ||
168 | + Paragraph 2.1 | ||
169 | + Chapter 2 | ||
170 | + Paragraph 1.3 | ||
171 | + Paragraph 1.2 | ||
172 | + Paragraph 1.1 | ||
173 | + Chapter 1 | ||
174 | + Part 1'''; | ||
175 | + | ||
176 | + expect(generated, expected); | ||
177 | + }); | ||
178 | + }); | ||
179 | + | ||
86 | tearDownAll(() async { | 180 | tearDownAll(() async { |
87 | final file = File('widgets-outline.pdf'); | 181 | final file = File('widgets-outline.pdf'); |
88 | await file.writeAsBytes(await pdf.save()); | 182 | await file.writeAsBytes(await pdf.save()); |
89 | }); | 183 | }); |
90 | } | 184 | } |
185 | + | ||
186 | +class _OutlineBuilder { | ||
187 | + final List<String> _titles = <String>[]; | ||
188 | + final List<int> _levels = <int>[]; | ||
189 | + | ||
190 | + _OutlineBuilder add(String text, int level) { | ||
191 | + _titles.add(text); | ||
192 | + _levels.add(level); | ||
193 | + return this; | ||
194 | + } | ||
195 | + | ||
196 | + List<String> render() { | ||
197 | + final pdf = Document(); | ||
198 | + pdf.addPage( | ||
199 | + MultiPage( | ||
200 | + build: (Context context) => <Widget>[ | ||
201 | + for (int i = 0; i < _titles.length; i++) | ||
202 | + Outline( | ||
203 | + name: 'anchor$i', | ||
204 | + title: _titles[i], | ||
205 | + level: _levels[i], | ||
206 | + ) | ||
207 | + ], | ||
208 | + ), | ||
209 | + ); | ||
210 | + return _collectOutlines(pdf.document.catalog.outlines!); | ||
211 | + } | ||
212 | +} | ||
213 | + | ||
214 | +List<String> _collectOutlines( | ||
215 | + PdfOutline outline, [ | ||
216 | + List<String>? output, | ||
217 | + int indent = 0, | ||
218 | +]) { | ||
219 | + final result = output ?? <String>[]; | ||
220 | + final intentation = List.filled(indent, ' ').join(); | ||
221 | + result.add('$intentation${outline.title}'); | ||
222 | + for (var child in outline.outlines) { | ||
223 | + _collectOutlines(child, result, indent + 1); | ||
224 | + } | ||
225 | + return result; | ||
226 | +} |
-
Please register or login to post a comment