Showing
2 changed files
with
430 additions
and
0 deletions
test/github_social_preview.dart
0 → 100644
1 | +import 'dart:io'; | ||
2 | + | ||
3 | +import 'package:pdf/pdf.dart'; | ||
4 | +import 'package:pdf/widgets.dart'; | ||
5 | +import 'package:string_scanner/string_scanner.dart'; | ||
6 | + | ||
7 | +const dpi = 72.0; | ||
8 | +const px = dpi / PdfPageFormat.inch * PdfPageFormat.point; | ||
9 | + | ||
10 | +void main() { | ||
11 | + // Open self | ||
12 | + final source = File('../test/github_social_preview.dart').readAsStringSync(); | ||
13 | + final code = DartSyntaxHighlighter( | ||
14 | + SyntaxHighlighterStyle.dark(), | ||
15 | + ).format( | ||
16 | + source.substring( | ||
17 | + source.lastIndexOf('// START') + 9, | ||
18 | + source.lastIndexOf('// END') - 3, | ||
19 | + ), | ||
20 | + ); | ||
21 | + | ||
22 | + // START | ||
23 | + // Enable debug painting | ||
24 | + Document.debug = true; | ||
25 | + | ||
26 | + // Create a pdf document | ||
27 | + final pdf = Document( | ||
28 | + title: 'Github Social Preview', | ||
29 | + author: 'David PHAM-VAN', | ||
30 | + ); | ||
31 | + | ||
32 | + // New page | ||
33 | + pdf.addPage( | ||
34 | + Page( | ||
35 | + pageFormat: PdfPageFormat( | ||
36 | + 1280 * px, | ||
37 | + 640 * px, | ||
38 | + marginAll: 78 * px, | ||
39 | + ), | ||
40 | + build: (context) => Row( | ||
41 | + mainAxisSize: MainAxisSize.max, | ||
42 | + children: [ | ||
43 | + // Display the source code on the first half of the page | ||
44 | + Flexible( | ||
45 | + fit: FlexFit.tight, | ||
46 | + child: Container( | ||
47 | + color: PdfColors.grey800, | ||
48 | + padding: EdgeInsets.all(10 * px), | ||
49 | + child: ClipRect(child: RichText(text: code)), | ||
50 | + ), | ||
51 | + ), | ||
52 | + // Add a vertical separator | ||
53 | + Container(width: 5 * px, color: PdfColors.lightBlue), | ||
54 | + // Show "Hello World!" centered and rotated on the second half of the page | ||
55 | + Flexible( | ||
56 | + fit: FlexFit.tight, | ||
57 | + child: Center( | ||
58 | + child: Transform.rotateBox( | ||
59 | + angle: .2, | ||
60 | + child: Text( | ||
61 | + 'Hello World!', | ||
62 | + style: TextStyle( | ||
63 | + font: Font.helveticaBold(), | ||
64 | + fontSize: 50 * px, | ||
65 | + ), | ||
66 | + ), | ||
67 | + ), | ||
68 | + ), | ||
69 | + ), | ||
70 | + ], | ||
71 | + ), | ||
72 | + ), | ||
73 | + ); | ||
74 | + // END | ||
75 | + | ||
76 | + // Save the file | ||
77 | + File('social_preview.pdf').writeAsBytesSync(pdf.save()); | ||
78 | + | ||
79 | + // Convert to png | ||
80 | + Process.runSync('pdftocairo', | ||
81 | + ['social_preview.pdf', '-png', '-r', '72', 'social_preview.png']); | ||
82 | +} | ||
83 | + | ||
84 | +class SyntaxHighlighterStyle { | ||
85 | + const SyntaxHighlighterStyle( | ||
86 | + {this.baseStyle, | ||
87 | + this.numberStyle, | ||
88 | + this.commentStyle, | ||
89 | + this.keywordStyle, | ||
90 | + this.stringStyle, | ||
91 | + this.punctuationStyle, | ||
92 | + this.classStyle, | ||
93 | + this.constantStyle}); | ||
94 | + | ||
95 | + final TextStyle baseStyle; | ||
96 | + final TextStyle numberStyle; | ||
97 | + final TextStyle commentStyle; | ||
98 | + final TextStyle keywordStyle; | ||
99 | + final TextStyle stringStyle; | ||
100 | + final TextStyle punctuationStyle; | ||
101 | + final TextStyle classStyle; | ||
102 | + final TextStyle constantStyle; | ||
103 | + | ||
104 | + factory SyntaxHighlighterStyle.dark() => SyntaxHighlighterStyle( | ||
105 | + baseStyle: TextStyle( | ||
106 | + font: Font.courierBold(), | ||
107 | + color: PdfColors.white, | ||
108 | + fontSize: 10 * px, | ||
109 | + ), | ||
110 | + numberStyle: TextStyle(color: PdfColors.purple300), | ||
111 | + commentStyle: TextStyle(color: PdfColors.green600), | ||
112 | + keywordStyle: TextStyle(color: PdfColors.blue600), | ||
113 | + stringStyle: TextStyle(color: PdfColors.orange400), | ||
114 | + punctuationStyle: TextStyle(color: PdfColors.pink), | ||
115 | + classStyle: TextStyle(color: PdfColors.cyan), | ||
116 | + constantStyle: TextStyle(color: PdfColors.pink), | ||
117 | + ); | ||
118 | +} | ||
119 | + | ||
120 | +class DartSyntaxHighlighter { | ||
121 | + DartSyntaxHighlighter(this._style) { | ||
122 | + _spans = <_HighlightSpan>[]; | ||
123 | + } | ||
124 | + | ||
125 | + final SyntaxHighlighterStyle _style; | ||
126 | + | ||
127 | + static const List<String> _keywords = <String>[ | ||
128 | + 'abstract', | ||
129 | + 'as', | ||
130 | + 'assert', | ||
131 | + 'async', | ||
132 | + 'await', | ||
133 | + 'break', | ||
134 | + 'case', | ||
135 | + 'catch', | ||
136 | + 'class', | ||
137 | + 'const', | ||
138 | + 'continue', | ||
139 | + 'default', | ||
140 | + 'deferred', | ||
141 | + 'do', | ||
142 | + 'dynamic', | ||
143 | + 'else', | ||
144 | + 'enum', | ||
145 | + 'export', | ||
146 | + 'external', | ||
147 | + 'extends', | ||
148 | + 'factory', | ||
149 | + 'false', | ||
150 | + 'final', | ||
151 | + 'finally', | ||
152 | + 'for', | ||
153 | + 'get', | ||
154 | + 'if', | ||
155 | + 'implements', | ||
156 | + 'import', | ||
157 | + 'in', | ||
158 | + 'is', | ||
159 | + 'library', | ||
160 | + 'new', | ||
161 | + 'null', | ||
162 | + 'operator', | ||
163 | + 'part', | ||
164 | + 'rethrow', | ||
165 | + 'return', | ||
166 | + 'set', | ||
167 | + 'static', | ||
168 | + 'super', | ||
169 | + 'switch', | ||
170 | + 'sync', | ||
171 | + 'this', | ||
172 | + 'throw', | ||
173 | + 'true', | ||
174 | + 'try', | ||
175 | + 'typedef', | ||
176 | + 'var', | ||
177 | + 'void', | ||
178 | + 'while', | ||
179 | + 'with', | ||
180 | + 'yield' | ||
181 | + ]; | ||
182 | + | ||
183 | + static const List<String> _builtInTypes = <String>[ | ||
184 | + 'int', | ||
185 | + 'double', | ||
186 | + 'num', | ||
187 | + 'bool' | ||
188 | + ]; | ||
189 | + | ||
190 | + String _src; | ||
191 | + StringScanner _scanner; | ||
192 | + | ||
193 | + List<_HighlightSpan> _spans; | ||
194 | + | ||
195 | + TextSpan format(String source) { | ||
196 | + _src = source; | ||
197 | + | ||
198 | + _scanner = StringScanner(_src); | ||
199 | + | ||
200 | + if (_generateSpans()) { | ||
201 | + // Successfully parsed the code | ||
202 | + final List<TextSpan> formattedText = <TextSpan>[]; | ||
203 | + int currentPosition = 0; | ||
204 | + | ||
205 | + for (_HighlightSpan span in _spans) { | ||
206 | + if (currentPosition != span.start) | ||
207 | + formattedText | ||
208 | + .add(TextSpan(text: _src.substring(currentPosition, span.start))); | ||
209 | + | ||
210 | + formattedText.add(TextSpan( | ||
211 | + style: span.textStyle(_style), text: span.textForSpan(_src))); | ||
212 | + | ||
213 | + currentPosition = span.end; | ||
214 | + } | ||
215 | + | ||
216 | + if (currentPosition != _src.length) | ||
217 | + formattedText | ||
218 | + .add(TextSpan(text: _src.substring(currentPosition, _src.length))); | ||
219 | + _spans.clear(); | ||
220 | + return TextSpan(style: _style.baseStyle, children: formattedText); | ||
221 | + } else { | ||
222 | + // Parsing failed, return with only basic formatting | ||
223 | + return TextSpan(style: _style.baseStyle, text: source); | ||
224 | + } | ||
225 | + } | ||
226 | + | ||
227 | + bool _generateSpans() { | ||
228 | + int lastLoopPosition = _scanner.position; | ||
229 | + | ||
230 | + while (!_scanner.isDone) { | ||
231 | + // Skip White space | ||
232 | + _scanner.scan(RegExp(r'\s+')); | ||
233 | + | ||
234 | + // Block comments | ||
235 | + if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) { | ||
236 | + _spans.add(_HighlightSpan(_HighlightType.comment, | ||
237 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
238 | + continue; | ||
239 | + } | ||
240 | + | ||
241 | + // Line comments | ||
242 | + if (_scanner.scan('//')) { | ||
243 | + final int startComment = _scanner.lastMatch.start; | ||
244 | + | ||
245 | + bool eof = false; | ||
246 | + int endComment; | ||
247 | + if (_scanner.scan(RegExp(r'.*\n'))) { | ||
248 | + endComment = _scanner.lastMatch.end - 1; | ||
249 | + } else { | ||
250 | + eof = true; | ||
251 | + endComment = _src.length; | ||
252 | + } | ||
253 | + | ||
254 | + _spans.add( | ||
255 | + _HighlightSpan(_HighlightType.comment, startComment, endComment)); | ||
256 | + | ||
257 | + if (eof) { | ||
258 | + break; | ||
259 | + } | ||
260 | + | ||
261 | + continue; | ||
262 | + } | ||
263 | + | ||
264 | + // Raw r"String" | ||
265 | + if (_scanner.scan(RegExp(r'r".*"'))) { | ||
266 | + _spans.add(_HighlightSpan(_HighlightType.string, | ||
267 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
268 | + continue; | ||
269 | + } | ||
270 | + | ||
271 | + // Raw r'String' | ||
272 | + if (_scanner.scan(RegExp(r"r'.*'"))) { | ||
273 | + _spans.add(_HighlightSpan(_HighlightType.string, | ||
274 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
275 | + continue; | ||
276 | + } | ||
277 | + | ||
278 | + // Multiline """String""" | ||
279 | + if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) { | ||
280 | + _spans.add(_HighlightSpan(_HighlightType.string, | ||
281 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
282 | + continue; | ||
283 | + } | ||
284 | + | ||
285 | + // Multiline '''String''' | ||
286 | + if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) { | ||
287 | + _spans.add(_HighlightSpan(_HighlightType.string, | ||
288 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
289 | + continue; | ||
290 | + } | ||
291 | + | ||
292 | + // "String" | ||
293 | + if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) { | ||
294 | + _spans.add(_HighlightSpan(_HighlightType.string, | ||
295 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
296 | + continue; | ||
297 | + } | ||
298 | + | ||
299 | + // 'String' | ||
300 | + if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) { | ||
301 | + _spans.add(_HighlightSpan(_HighlightType.string, | ||
302 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
303 | + continue; | ||
304 | + } | ||
305 | + | ||
306 | + // Double | ||
307 | + if (_scanner.scan(RegExp(r'\d+\.\d+'))) { | ||
308 | + _spans.add(_HighlightSpan(_HighlightType.number, | ||
309 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
310 | + continue; | ||
311 | + } | ||
312 | + | ||
313 | + // Integer | ||
314 | + if (_scanner.scan(RegExp(r'\d+'))) { | ||
315 | + _spans.add(_HighlightSpan(_HighlightType.number, | ||
316 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
317 | + continue; | ||
318 | + } | ||
319 | + | ||
320 | + // Punctuation | ||
321 | + if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) { | ||
322 | + _spans.add(_HighlightSpan(_HighlightType.punctuation, | ||
323 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
324 | + continue; | ||
325 | + } | ||
326 | + | ||
327 | + // Meta data | ||
328 | + if (_scanner.scan(RegExp(r'@\w+'))) { | ||
329 | + _spans.add(_HighlightSpan(_HighlightType.keyword, | ||
330 | + _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
331 | + continue; | ||
332 | + } | ||
333 | + | ||
334 | + // Words | ||
335 | + if (_scanner.scan(RegExp(r'\w+'))) { | ||
336 | + _HighlightType type; | ||
337 | + | ||
338 | + String word = _scanner.lastMatch[0]; | ||
339 | + if (word.startsWith('_')) { | ||
340 | + word = word.substring(1); | ||
341 | + } | ||
342 | + | ||
343 | + if (_keywords.contains(word)) | ||
344 | + type = _HighlightType.keyword; | ||
345 | + else if (_builtInTypes.contains(word)) | ||
346 | + type = _HighlightType.keyword; | ||
347 | + else if (_firstLetterIsUpperCase(word)) | ||
348 | + type = _HighlightType.klass; | ||
349 | + else if (word.length >= 2 && | ||
350 | + word.startsWith('k') && | ||
351 | + _firstLetterIsUpperCase(word.substring(1))) | ||
352 | + type = _HighlightType.constant; | ||
353 | + | ||
354 | + if (type != null) { | ||
355 | + _spans.add(_HighlightSpan( | ||
356 | + type, _scanner.lastMatch.start, _scanner.lastMatch.end)); | ||
357 | + } | ||
358 | + } | ||
359 | + | ||
360 | + // Check if this loop did anything | ||
361 | + if (lastLoopPosition == _scanner.position) { | ||
362 | + // Failed to parse this file, abort gracefully | ||
363 | + return false; | ||
364 | + } | ||
365 | + lastLoopPosition = _scanner.position; | ||
366 | + } | ||
367 | + | ||
368 | + _simplify(); | ||
369 | + return true; | ||
370 | + } | ||
371 | + | ||
372 | + void _simplify() { | ||
373 | + for (int i = _spans.length - 2; i >= 0; i -= 1) { | ||
374 | + if (_spans[i].type == _spans[i + 1].type && | ||
375 | + _spans[i].end == _spans[i + 1].start) { | ||
376 | + _spans[i] = | ||
377 | + _HighlightSpan(_spans[i].type, _spans[i].start, _spans[i + 1].end); | ||
378 | + _spans.removeAt(i + 1); | ||
379 | + } | ||
380 | + } | ||
381 | + } | ||
382 | + | ||
383 | + bool _firstLetterIsUpperCase(String str) { | ||
384 | + if (str.isNotEmpty) { | ||
385 | + final String first = str.substring(0, 1); | ||
386 | + return first == first.toUpperCase(); | ||
387 | + } | ||
388 | + return false; | ||
389 | + } | ||
390 | +} | ||
391 | + | ||
392 | +enum _HighlightType { | ||
393 | + number, | ||
394 | + comment, | ||
395 | + keyword, | ||
396 | + string, | ||
397 | + punctuation, | ||
398 | + klass, | ||
399 | + constant | ||
400 | +} | ||
401 | + | ||
402 | +class _HighlightSpan { | ||
403 | + _HighlightSpan(this.type, this.start, this.end); | ||
404 | + final _HighlightType type; | ||
405 | + final int start; | ||
406 | + final int end; | ||
407 | + | ||
408 | + String textForSpan(String src) { | ||
409 | + return src.substring(start, end); | ||
410 | + } | ||
411 | + | ||
412 | + TextStyle textStyle(SyntaxHighlighterStyle style) { | ||
413 | + if (type == _HighlightType.number) | ||
414 | + return style.numberStyle; | ||
415 | + else if (type == _HighlightType.comment) | ||
416 | + return style.commentStyle; | ||
417 | + else if (type == _HighlightType.keyword) | ||
418 | + return style.keywordStyle; | ||
419 | + else if (type == _HighlightType.string) | ||
420 | + return style.stringStyle; | ||
421 | + else if (type == _HighlightType.punctuation) | ||
422 | + return style.punctuationStyle; | ||
423 | + else if (type == _HighlightType.klass) | ||
424 | + return style.classStyle; | ||
425 | + else if (type == _HighlightType.constant) | ||
426 | + return style.constantStyle; | ||
427 | + else | ||
428 | + return style.baseStyle; | ||
429 | + } | ||
430 | +} |
-
Please register or login to post a comment