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