1use alloc::vec::Vec;
8
9use super::{
10 ast::{Font, Graphic, Section, Span},
11 position_tracker::PositionTracker,
12};
13
14pub(super) struct BinaryDataParser<'a, T> {
16 tracker: PositionTracker<'a>,
18 entry_key: &'static str,
20 constructor: fn(&'a str, Vec<&'a str>, Span) -> T,
22}
23
24impl<'a, T> BinaryDataParser<'a, T> {
25 pub fn new(
27 source: &'a str,
28 position: usize,
29 line: usize,
30 entry_key: &'static str,
31 constructor: fn(&'a str, Vec<&'a str>, Span) -> T,
32 ) -> Self {
33 Self {
34 tracker: PositionTracker::new_at(
35 source,
36 position,
37 u32::try_from(line).unwrap_or(u32::MAX),
38 1,
39 ),
40 entry_key,
41 constructor,
42 }
43 }
44
45 pub fn parse(mut self) -> (Vec<T>, usize, usize) {
49 let mut entries = Vec::new();
50
51 while !self.tracker.is_at_end() && !self.at_next_section() {
52 self.skip_whitespace_and_comments();
53
54 if self.tracker.is_at_end() || self.at_next_section() {
55 break;
56 }
57
58 if let Some(entry) = self.parse_entry() {
59 entries.push(entry);
60 }
61 }
62
63 let final_position = self.tracker.offset();
64 let final_line = self.tracker.line() as usize;
65 (entries, final_position, final_line)
66 }
67
68 fn parse_entry(&mut self) -> Option<T> {
70 let entry_start = self.tracker.checkpoint();
71 let line = self.current_line();
72
73 if let Some(colon_pos) = line.find(':') {
74 let key = line[..colon_pos].trim();
75 if key == self.entry_key {
76 let filename = line[colon_pos + 1..].trim();
77 self.tracker.skip_line();
78
79 let data_lines = self.collect_data_lines();
80
81 let entry_end = self.tracker.checkpoint();
83 let span = entry_end.span_from(&entry_start);
84
85 return Some((self.constructor)(filename, data_lines, span));
86 }
87 }
88
89 self.tracker.skip_line();
90 None
91 }
92
93 fn collect_data_lines(&mut self) -> Vec<&'a str> {
95 let mut data_lines = Vec::new();
96
97 while !self.tracker.is_at_end() && !self.at_next_section() {
98 let data_line = self.current_line();
99 let trimmed = data_line.trim();
100
101 if trimmed.is_empty() || trimmed.starts_with('[') {
102 break;
103 }
104
105 if trimmed.starts_with(';') || trimmed.starts_with('!') {
107 self.tracker.skip_line();
108 continue;
109 }
110
111 if trimmed.starts_with("# ") || trimmed == "#" {
114 break;
115 }
116
117 data_lines.push(data_line);
118 self.tracker.skip_line();
119 }
120
121 data_lines
122 }
123
124 fn at_next_section(&self) -> bool {
126 self.tracker.remaining().trim_start().starts_with('[')
127 }
128
129 fn current_line(&self) -> &'a str {
131 let remaining = self.tracker.remaining();
132 let end = remaining.find('\n').unwrap_or(remaining.len());
133 &remaining[..end]
134 }
135
136 fn skip_whitespace_and_comments(&mut self) {
138 loop {
139 self.tracker.skip_whitespace();
140
141 let remaining = self.tracker.remaining();
142 if remaining.is_empty() {
143 break;
144 }
145
146 if remaining.starts_with(';') || remaining.starts_with("!:") {
147 self.tracker.skip_line();
148 continue;
149 }
150
151 if remaining.starts_with('\n') {
153 self.tracker.advance(1);
154 continue;
155 }
156
157 break;
158 }
159 }
160}
161
162pub(super) struct FontsParser;
164
165impl FontsParser {
166 pub fn parse(source: &str, position: usize, line: usize) -> (Section<'_>, usize, usize) {
170 let parser = BinaryDataParser::new(
171 source,
172 position,
173 line,
174 "fontname",
175 |filename, data_lines, span| Font {
176 filename,
177 data_lines,
178 span,
179 },
180 );
181 let (fonts, final_position, final_line) = parser.parse();
182 (Section::Fonts(fonts), final_position, final_line)
183 }
184}
185
186pub(super) struct GraphicsParser;
188
189impl GraphicsParser {
190 pub fn parse(source: &str, position: usize, line: usize) -> (Section<'_>, usize, usize) {
194 let parser = BinaryDataParser::new(
195 source,
196 position,
197 line,
198 "filename",
199 |filename, data_lines, span| Graphic {
200 filename,
201 data_lines,
202 span,
203 },
204 );
205 let (graphics, final_position, final_line) = parser.parse();
206 (Section::Graphics(graphics), final_position, final_line)
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn fonts_parser_empty_section() {
216 let source = "";
217 let (section, _, _) = FontsParser::parse(source, 0, 1);
218
219 if let Section::Fonts(fonts) = section {
220 assert!(fonts.is_empty());
221 } else {
222 panic!("Expected Fonts section");
223 }
224 }
225
226 #[test]
227 fn fonts_parser_single_font() {
228 let source = "fontname: arial.ttf\ndata1\ndata2\n";
229 let (section, _, _) = FontsParser::parse(source, 0, 1);
230
231 if let Section::Fonts(fonts) = section {
232 assert_eq!(fonts.len(), 1);
233 assert_eq!(fonts[0].filename, "arial.ttf");
234 assert_eq!(fonts[0].data_lines.len(), 2);
235 assert_eq!(fonts[0].data_lines[0], "data1");
236 assert_eq!(fonts[0].data_lines[1], "data2");
237 } else {
238 panic!("Expected Fonts section");
239 }
240 }
241
242 #[test]
243 fn fonts_parser_multiple_fonts() {
244 let source = "fontname: font1.ttf\ndata1\ndata2\n\nfontname: font2.ttf\ndata3\ndata4\n";
245 let (section, _, _) = FontsParser::parse(source, 0, 1);
246
247 if let Section::Fonts(fonts) = section {
248 assert_eq!(fonts.len(), 2);
249
250 assert_eq!(fonts[0].filename, "font1.ttf");
251 assert_eq!(fonts[0].data_lines.len(), 2);
252 assert_eq!(fonts[0].data_lines[0], "data1");
253 assert_eq!(fonts[0].data_lines[1], "data2");
254
255 assert_eq!(fonts[1].filename, "font2.ttf");
256 assert_eq!(fonts[1].data_lines.len(), 2);
257 assert_eq!(fonts[1].data_lines[0], "data3");
258 assert_eq!(fonts[1].data_lines[1], "data4");
259 } else {
260 panic!("Expected Fonts section");
261 }
262 }
263
264 #[test]
265 fn fonts_parser_with_comments() {
266 let source = "; This is a comment\nfontname: test.ttf\n!: Another comment\ndata1\ndata2\n";
267 let (section, _, _) = FontsParser::parse(source, 0, 1);
268
269 if let Section::Fonts(fonts) = section {
270 assert_eq!(fonts.len(), 1);
271 assert_eq!(fonts[0].filename, "test.ttf");
272 assert_eq!(fonts[0].data_lines.len(), 2);
273 } else {
274 panic!("Expected Fonts section");
275 }
276 }
277
278 #[test]
279 fn fonts_parser_with_whitespace() {
280 let source = " fontname: arial.ttf \n data1 \n data2 \n";
281 let (section, _, _) = FontsParser::parse(source, 0, 1);
282
283 if let Section::Fonts(fonts) = section {
284 assert_eq!(fonts.len(), 1);
285 assert_eq!(fonts[0].filename, "arial.ttf");
286 assert_eq!(fonts[0].data_lines.len(), 2);
287 assert_eq!(fonts[0].data_lines[0], " data1 ");
288 assert_eq!(fonts[0].data_lines[1], " data2 ");
289 } else {
290 panic!("Expected Fonts section");
291 }
292 }
293
294 #[test]
295 fn fonts_parser_stops_at_next_section() {
296 let source = "fontname: test.ttf\ndata1\ndata2\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n";
297 let (section, _, _) = FontsParser::parse(source, 0, 1);
298
299 if let Section::Fonts(fonts) = section {
300 assert_eq!(fonts.len(), 1);
301 assert_eq!(fonts[0].filename, "test.ttf");
302 assert_eq!(fonts[0].data_lines.len(), 2);
303 } else {
304 panic!("Expected Fonts section");
305 }
306 }
307
308 #[test]
309 fn fonts_parser_malformed_entry() {
310 let source = "invalid_line\nfontname: valid.ttf\ndata1\n";
311 let (section, _, _) = FontsParser::parse(source, 0, 1);
312
313 if let Section::Fonts(fonts) = section {
314 assert_eq!(fonts.len(), 1);
315 assert_eq!(fonts[0].filename, "valid.ttf");
316 assert_eq!(fonts[0].data_lines.len(), 1);
317 } else {
318 panic!("Expected Fonts section");
319 }
320 }
321
322 #[test]
323 fn fonts_parser_no_data_lines() {
324 let source = "fontname: empty.ttf\n[Events]\n";
325 let (section, _, _) = FontsParser::parse(source, 0, 1);
326
327 if let Section::Fonts(fonts) = section {
328 assert_eq!(fonts.len(), 1);
329 assert_eq!(fonts[0].filename, "empty.ttf");
330 assert!(fonts[0].data_lines.is_empty());
331 } else {
332 panic!("Expected Fonts section");
333 }
334 }
335
336 #[test]
337 fn graphics_parser_empty_section() {
338 let source = "";
339 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
340
341 if let Section::Graphics(graphics) = section {
342 assert!(graphics.is_empty());
343 } else {
344 panic!("Expected Graphics section");
345 }
346 }
347
348 #[test]
349 fn graphics_parser_single_graphic() {
350 let source = "filename: logo.png\nimage_data1\nimage_data2\n";
351 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
352
353 if let Section::Graphics(graphics) = section {
354 assert_eq!(graphics.len(), 1);
355 assert_eq!(graphics[0].filename, "logo.png");
356 assert_eq!(graphics[0].data_lines.len(), 2);
357 assert_eq!(graphics[0].data_lines[0], "image_data1");
358 assert_eq!(graphics[0].data_lines[1], "image_data2");
359 } else {
360 panic!("Expected Graphics section");
361 }
362 }
363
364 #[test]
365 fn graphics_parser_multiple_graphics() {
366 let source = "filename: img1.png\ndata1\ndata2\n\nfilename: img2.jpg\ndata3\ndata4\n";
367 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
368
369 if let Section::Graphics(graphics) = section {
370 assert_eq!(graphics.len(), 2);
371
372 assert_eq!(graphics[0].filename, "img1.png");
373 assert_eq!(graphics[0].data_lines.len(), 2);
374 assert_eq!(graphics[0].data_lines[0], "data1");
375 assert_eq!(graphics[0].data_lines[1], "data2");
376
377 assert_eq!(graphics[1].filename, "img2.jpg");
378 assert_eq!(graphics[1].data_lines.len(), 2);
379 assert_eq!(graphics[1].data_lines[0], "data3");
380 assert_eq!(graphics[1].data_lines[1], "data4");
381 } else {
382 panic!("Expected Graphics section");
383 }
384 }
385
386 #[test]
387 fn graphics_parser_with_comments() {
388 let source = "; Image section comment\nfilename: test.png\n!: Another comment\nimg_data1\nimg_data2\n";
389 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
390
391 if let Section::Graphics(graphics) = section {
392 assert_eq!(graphics.len(), 1);
393 assert_eq!(graphics[0].filename, "test.png");
394 assert_eq!(graphics[0].data_lines.len(), 2);
395 } else {
396 panic!("Expected Graphics section");
397 }
398 }
399
400 #[test]
401 fn graphics_parser_with_whitespace() {
402 let source = " filename: logo.png \n img_data1 \n img_data2 \n";
403 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
404
405 if let Section::Graphics(graphics) = section {
406 assert_eq!(graphics.len(), 1);
407 assert_eq!(graphics[0].filename, "logo.png");
408 assert_eq!(graphics[0].data_lines.len(), 2);
409 assert_eq!(graphics[0].data_lines[0], " img_data1 ");
410 assert_eq!(graphics[0].data_lines[1], " img_data2 ");
411 } else {
412 panic!("Expected Graphics section");
413 }
414 }
415
416 #[test]
417 fn graphics_parser_stops_at_next_section() {
418 let source = "filename: test.png\nimg_data1\nimg_data2\n[Styles]\nFormat: Name, Fontname, Fontsize\n";
419 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
420
421 if let Section::Graphics(graphics) = section {
422 assert_eq!(graphics.len(), 1);
423 assert_eq!(graphics[0].filename, "test.png");
424 assert_eq!(graphics[0].data_lines.len(), 2);
425 } else {
426 panic!("Expected Graphics section");
427 }
428 }
429
430 #[test]
431 fn graphics_parser_malformed_entry() {
432 let source = "invalid_line_without_colon\nfilename: valid.png\nimg_data1\n";
433 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
434
435 if let Section::Graphics(graphics) = section {
436 assert_eq!(graphics.len(), 1);
437 assert_eq!(graphics[0].filename, "valid.png");
438 assert_eq!(graphics[0].data_lines.len(), 1);
439 } else {
440 panic!("Expected Graphics section");
441 }
442 }
443
444 #[test]
445 fn graphics_parser_no_data_lines() {
446 let source = "filename: empty.png\n[Fonts]\n";
447 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
448
449 if let Section::Graphics(graphics) = section {
450 assert_eq!(graphics.len(), 1);
451 assert_eq!(graphics[0].filename, "empty.png");
452 assert!(graphics[0].data_lines.is_empty());
453 } else {
454 panic!("Expected Graphics section");
455 }
456 }
457
458 #[test]
459 fn fonts_parser_colon_in_filename() {
460 let source = "fontname: C:\\Fonts\\arial.ttf\ndata1\n";
461 let (section, _, _) = FontsParser::parse(source, 0, 1);
462
463 if let Section::Fonts(fonts) = section {
464 assert_eq!(fonts.len(), 1);
465 assert_eq!(fonts[0].filename, "C:\\Fonts\\arial.ttf");
466 } else {
467 panic!("Expected Fonts section");
468 }
469 }
470
471 #[test]
472 fn graphics_parser_colon_in_filename() {
473 let source = "filename: D:\\Images\\logo.png\nimg_data1\n";
474 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
475
476 if let Section::Graphics(graphics) = section {
477 assert_eq!(graphics.len(), 1);
478 assert_eq!(graphics[0].filename, "D:\\Images\\logo.png");
479 } else {
480 panic!("Expected Graphics section");
481 }
482 }
483
484 #[test]
485 fn fonts_parser_malformed_entry_no_colon() {
486 let source = "invalid_font_entry\ndata1\ndata2\n";
487 let (section, _, _) = FontsParser::parse(source, 0, 1);
488
489 if let Section::Fonts(fonts) = section {
490 assert!(fonts.is_empty());
492 } else {
493 panic!("Expected Fonts section");
494 }
495 }
496
497 #[test]
498 fn fonts_parser_empty_filename() {
499 let source = "fontname: \ndata1\ndata2\n";
500 let (section, _, _) = FontsParser::parse(source, 0, 1);
501
502 if let Section::Fonts(fonts) = section {
503 assert_eq!(fonts.len(), 1);
504 assert_eq!(fonts[0].filename, "");
505 assert_eq!(fonts[0].data_lines.len(), 2);
506 } else {
507 panic!("Expected Fonts section");
508 }
509 }
510
511 #[test]
512 fn fonts_parser_whitespace_only_filename() {
513 let source = "fontname: \ndata1\ndata2\n";
514 let (section, _, _) = FontsParser::parse(source, 0, 1);
515
516 if let Section::Fonts(fonts) = section {
517 assert_eq!(fonts.len(), 1);
518 assert_eq!(fonts[0].filename, "");
519 assert_eq!(fonts[0].data_lines.len(), 2);
520 } else {
521 panic!("Expected Fonts section");
522 }
523 }
524
525 #[test]
526 fn fonts_parser_comments_between_data_lines() {
527 let source =
528 "fontname: arial.ttf\ndata1\n; Comment line\ndata2\n! Another comment\ndata3\n";
529 let (section, _, _) = FontsParser::parse(source, 0, 1);
530
531 if let Section::Fonts(fonts) = section {
532 assert_eq!(fonts.len(), 1);
533 assert_eq!(fonts[0].filename, "arial.ttf");
534 assert_eq!(fonts[0].data_lines.len(), 3);
536 assert_eq!(fonts[0].data_lines[0], "data1");
537 assert_eq!(fonts[0].data_lines[1], "data2");
538 assert_eq!(fonts[0].data_lines[2], "data3");
539 } else {
540 panic!("Expected Fonts section");
541 }
542 }
543
544 #[test]
545 fn fonts_parser_empty_lines_between_data() {
546 let source = "fontname: arial.ttf\ndata1\n\n\ndata2\n \ndata3\n";
547 let (section, _, _) = FontsParser::parse(source, 0, 1);
548
549 if let Section::Fonts(fonts) = section {
550 assert_eq!(fonts.len(), 1);
551 assert_eq!(fonts[0].filename, "arial.ttf");
552 assert_eq!(fonts[0].data_lines.len(), 1);
554 assert_eq!(fonts[0].data_lines[0], "data1");
555 } else {
556 panic!("Expected Fonts section");
557 }
558 }
559
560 #[test]
561 fn fonts_parser_entry_at_end_of_file() {
562 let source = "fontname: arial.ttf\ndata1\ndata2";
563 let (section, _, _) = FontsParser::parse(source, 0, 1);
564
565 if let Section::Fonts(fonts) = section {
566 assert_eq!(fonts.len(), 1);
567 assert_eq!(fonts[0].filename, "arial.ttf");
568 assert_eq!(fonts[0].data_lines.len(), 2);
569 } else {
570 panic!("Expected Fonts section");
571 }
572 }
573
574 #[test]
575 fn fonts_parser_mixed_comment_styles() {
576 let source = "fontname: arial.ttf\ndata1\n; Semicolon comment\ndata2\n! Exclamation comment\ndata3\n# Hash comment\ndata4\n";
577 let (section, _, _) = FontsParser::parse(source, 0, 1);
578
579 if let Section::Fonts(fonts) = section {
580 assert_eq!(fonts.len(), 1);
581 assert_eq!(fonts[0].data_lines.len(), 3);
583 } else {
584 panic!("Expected Fonts section");
585 }
586 }
587
588 #[test]
589 fn graphics_parser_malformed_entry_no_colon() {
590 let source = "invalid_graphic_entry\nimg_data1\nimg_data2\n";
591 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
592
593 if let Section::Graphics(graphics) = section {
594 assert!(graphics.is_empty());
596 } else {
597 panic!("Expected Graphics section");
598 }
599 }
600
601 #[test]
602 fn graphics_parser_empty_filename() {
603 let source = "filename: \nimg_data1\nimg_data2\n";
604 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
605
606 if let Section::Graphics(graphics) = section {
607 assert_eq!(graphics.len(), 1);
608 assert_eq!(graphics[0].filename, "");
609 assert_eq!(graphics[0].data_lines.len(), 2);
610 } else {
611 panic!("Expected Graphics section");
612 }
613 }
614
615 #[test]
616 fn graphics_parser_whitespace_only_filename() {
617 let source = "filename: \nimg_data1\nimg_data2\n";
618 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
619
620 if let Section::Graphics(graphics) = section {
621 assert_eq!(graphics.len(), 1);
622 assert_eq!(graphics[0].filename, "");
623 assert_eq!(graphics[0].data_lines.len(), 2);
624 } else {
625 panic!("Expected Graphics section");
626 }
627 }
628
629 #[test]
630 fn graphics_parser_comments_between_data_lines() {
631 let source = "filename: logo.png\nimg_data1\n; Comment line\nimg_data2\n! Another comment\nimg_data3\n";
632 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
633
634 if let Section::Graphics(graphics) = section {
635 assert_eq!(graphics.len(), 1);
636 assert_eq!(graphics[0].filename, "logo.png");
637 assert_eq!(graphics[0].data_lines.len(), 3);
639 assert_eq!(graphics[0].data_lines[0], "img_data1");
640 assert_eq!(graphics[0].data_lines[1], "img_data2");
641 assert_eq!(graphics[0].data_lines[2], "img_data3");
642 } else {
643 panic!("Expected Graphics section");
644 }
645 }
646
647 #[test]
648 fn graphics_parser_empty_lines_between_data() {
649 let source = "filename: logo.png\nimg_data1\n\n\nimg_data2\n \nimg_data3\n";
650 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
651
652 if let Section::Graphics(graphics) = section {
653 assert_eq!(graphics.len(), 1);
654 assert_eq!(graphics[0].filename, "logo.png");
655 assert_eq!(graphics[0].data_lines.len(), 1);
657 assert_eq!(graphics[0].data_lines[0], "img_data1");
658 } else {
659 panic!("Expected Graphics section");
660 }
661 }
662
663 #[test]
664 fn graphics_parser_entry_at_end_of_file() {
665 let source = "filename: logo.png\nimg_data1\nimg_data2";
666 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
667
668 if let Section::Graphics(graphics) = section {
669 assert_eq!(graphics.len(), 1);
670 assert_eq!(graphics[0].filename, "logo.png");
671 assert_eq!(graphics[0].data_lines.len(), 2);
672 } else {
673 panic!("Expected Graphics section");
674 }
675 }
676
677 #[test]
678 fn graphics_parser_mixed_comment_styles() {
679 let source = "filename: logo.png\nimg_data1\n; Semicolon comment\nimg_data2\n! Exclamation comment\nimg_data3\n# Hash comment\nimg_data4\n";
680 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
681
682 if let Section::Graphics(graphics) = section {
683 assert_eq!(graphics.len(), 1);
684 assert_eq!(graphics[0].data_lines.len(), 3);
686 } else {
687 panic!("Expected Graphics section");
688 }
689 }
690
691 #[test]
692 fn fonts_parser_multiple_entries_with_edge_cases() {
693 let source = "fontname: font1.ttf\ndata1_1\ndata1_2\n\ninvalid_entry_no_colon\n\nfontname: font2.ttf\n; Comment\ndata2_1\n\nfontname: \ndata3_1\n";
694 let (section, _, _) = FontsParser::parse(source, 0, 1);
695
696 if let Section::Fonts(fonts) = section {
697 assert_eq!(fonts.len(), 3); assert_eq!(fonts[0].filename, "font1.ttf");
700 assert_eq!(fonts[0].data_lines.len(), 2);
701
702 assert_eq!(fonts[1].filename, "font2.ttf");
703 assert_eq!(fonts[1].data_lines.len(), 1);
704
705 assert_eq!(fonts[2].filename, "");
706 assert_eq!(fonts[2].data_lines.len(), 1);
707 } else {
708 panic!("Expected Fonts section");
709 }
710 }
711
712 #[test]
713 fn graphics_parser_multiple_entries_with_edge_cases() {
714 let source = "filename: image1.png\nimg1_1\nimg1_2\n\ninvalid_entry_no_colon\n\nfilename: image2.png\n; Comment\nimg2_1\n\nfilename: \nimg3_1\n";
715 let (section, _, _) = GraphicsParser::parse(source, 0, 1);
716
717 if let Section::Graphics(graphics) = section {
718 assert_eq!(graphics.len(), 3); assert_eq!(graphics[0].filename, "image1.png");
721 assert_eq!(graphics[0].data_lines.len(), 2);
722
723 assert_eq!(graphics[1].filename, "image2.png");
724 assert_eq!(graphics[1].data_lines.len(), 1);
725
726 assert_eq!(graphics[2].filename, "");
727 assert_eq!(graphics[2].data_lines.len(), 1);
728 } else {
729 panic!("Expected Graphics section");
730 }
731 }
732}