1use crate::error::BackendError;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum Operand {
12 Integer(i64),
14 Real(f64),
16 Name(String),
18 LiteralString(Vec<u8>),
20 HexString(Vec<u8>),
22 Array(Vec<Operand>),
24 Boolean(bool),
26 Null,
28}
29
30#[derive(Debug, Clone, PartialEq)]
32pub struct Operator {
33 pub name: String,
35 pub operands: Vec<Operand>,
37}
38
39#[derive(Debug, Clone, PartialEq)]
41pub struct InlineImageData {
42 pub dict: Vec<(String, Operand)>,
44 pub data: Vec<u8>,
46}
47
48pub fn tokenize(input: &[u8]) -> Result<Vec<Operator>, BackendError> {
58 let mut ops = Vec::new();
59 let mut operand_stack: Vec<Operand> = Vec::new();
60 let mut pos = 0;
61
62 while pos < input.len() {
63 skip_whitespace_and_comments(input, &mut pos);
64 if pos >= input.len() {
65 break;
66 }
67
68 let b = input[pos];
69
70 match b {
71 b'(' => {
73 let s = parse_literal_string(input, &mut pos)?;
74 operand_stack.push(Operand::LiteralString(s));
75 }
76 b'<' => {
78 if pos + 1 < input.len() && input[pos + 1] == b'<' {
79 return Err(BackendError::Interpreter(
82 "unexpected '<<' in content stream".to_string(),
83 ));
84 }
85 let s = parse_hex_string(input, &mut pos)?;
86 operand_stack.push(Operand::HexString(s));
87 }
88 b'[' => {
90 pos += 1; let arr = parse_array(input, &mut pos)?;
92 operand_stack.push(Operand::Array(arr));
93 }
94 b'/' => {
96 let name = parse_name(input, &mut pos);
97 operand_stack.push(Operand::Name(name));
98 }
99 b'0'..=b'9' | b'+' | b'-' | b'.' => {
101 let num = parse_number(input, &mut pos)?;
102 operand_stack.push(num);
103 }
104 b'a'..=b'z' | b'A'..=b'Z' | b'*' | b'\'' | b'"' => {
106 let keyword = parse_keyword(input, &mut pos);
107 match keyword.as_str() {
108 "true" => operand_stack.push(Operand::Boolean(true)),
109 "false" => operand_stack.push(Operand::Boolean(false)),
110 "null" => operand_stack.push(Operand::Null),
111 "BI" => {
112 let (dict, data) = parse_inline_image(input, &mut pos)?;
114 ops.push(Operator {
115 name: "BI".to_string(),
116 operands: vec![
117 Operand::Array(
118 dict.into_iter()
119 .flat_map(|(k, v)| vec![Operand::Name(k), v])
120 .collect(),
121 ),
122 Operand::LiteralString(data),
123 ],
124 });
125 }
126 _ => {
127 ops.push(Operator {
129 name: keyword,
130 operands: std::mem::take(&mut operand_stack),
131 });
132 }
133 }
134 }
135 b']' => {
137 return Err(BackendError::Interpreter(
138 "unexpected ']' outside array".to_string(),
139 ));
140 }
141 _ => {
142 pos += 1;
144 }
145 }
146 }
147
148 Ok(ops)
149}
150
151fn is_whitespace(b: u8) -> bool {
153 matches!(b, b' ' | b'\t' | b'\r' | b'\n' | 0x0C | 0x00)
154}
155
156fn is_delimiter(b: u8) -> bool {
158 matches!(
159 b,
160 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
161 )
162}
163
164fn skip_whitespace_and_comments(input: &[u8], pos: &mut usize) {
166 while *pos < input.len() {
167 if is_whitespace(input[*pos]) {
168 *pos += 1;
169 } else if input[*pos] == b'%' {
170 while *pos < input.len() && input[*pos] != b'\n' && input[*pos] != b'\r' {
172 *pos += 1;
173 }
174 } else {
175 break;
176 }
177 }
178}
179
180fn parse_literal_string(input: &[u8], pos: &mut usize) -> Result<Vec<u8>, BackendError> {
182 debug_assert_eq!(input[*pos], b'(');
183 *pos += 1; let mut result = Vec::new();
186 let mut depth = 1u32;
187
188 while *pos < input.len() {
189 let b = input[*pos];
190 match b {
191 b'(' => {
192 depth += 1;
193 result.push(b'(');
194 *pos += 1;
195 }
196 b')' => {
197 depth -= 1;
198 if depth == 0 {
199 *pos += 1; return Ok(result);
201 }
202 result.push(b')');
203 *pos += 1;
204 }
205 b'\\' => {
206 *pos += 1;
207 if *pos >= input.len() {
208 return Err(BackendError::Interpreter(
209 "unterminated escape in literal string".to_string(),
210 ));
211 }
212 let escaped = input[*pos];
213 match escaped {
214 b'n' => result.push(b'\n'),
215 b'r' => result.push(b'\r'),
216 b't' => result.push(b'\t'),
217 b'b' => result.push(0x08),
218 b'f' => result.push(0x0C),
219 b'(' => result.push(b'('),
220 b')' => result.push(b')'),
221 b'\\' => result.push(b'\\'),
222 b'\r' => {
223 *pos += 1;
225 if *pos < input.len() && input[*pos] == b'\n' {
226 *pos += 1;
227 }
228 continue;
229 }
230 b'\n' => {
231 *pos += 1;
233 continue;
234 }
235 b'0'..=b'7' => {
236 let mut val = escaped - b'0';
238 for _ in 0..2 {
239 if *pos + 1 < input.len()
240 && input[*pos + 1] >= b'0'
241 && input[*pos + 1] <= b'7'
242 {
243 *pos += 1;
244 val = val * 8 + (input[*pos] - b'0');
245 } else {
246 break;
247 }
248 }
249 result.push(val);
250 *pos += 1;
251 continue;
252 }
253 _ => {
254 result.push(escaped);
256 }
257 }
258 *pos += 1;
259 }
260 _ => {
261 result.push(b);
262 *pos += 1;
263 }
264 }
265 }
266
267 Err(BackendError::Interpreter(
268 "unterminated literal string".to_string(),
269 ))
270}
271
272fn parse_hex_string(input: &[u8], pos: &mut usize) -> Result<Vec<u8>, BackendError> {
274 debug_assert_eq!(input[*pos], b'<');
275 *pos += 1; let mut hex_chars = Vec::new();
278 while *pos < input.len() {
279 let b = input[*pos];
280 if b == b'>' {
281 *pos += 1; break;
283 }
284 if is_whitespace(b) {
285 *pos += 1;
286 continue;
287 }
288 hex_chars.push(b);
289 *pos += 1;
290 }
291
292 if hex_chars.len() % 2 != 0 {
294 hex_chars.push(b'0');
295 }
296
297 let mut result = Vec::with_capacity(hex_chars.len() / 2);
298 for chunk in hex_chars.chunks(2) {
299 let hi = hex_digit(chunk[0])?;
300 let lo = hex_digit(chunk[1])?;
301 result.push((hi << 4) | lo);
302 }
303
304 Ok(result)
305}
306
307fn hex_digit(b: u8) -> Result<u8, BackendError> {
309 match b {
310 b'0'..=b'9' => Ok(b - b'0'),
311 b'a'..=b'f' => Ok(b - b'a' + 10),
312 b'A'..=b'F' => Ok(b - b'A' + 10),
313 _ => Err(BackendError::Interpreter(format!(
314 "invalid hex digit: {:?}",
315 b as char
316 ))),
317 }
318}
319
320fn parse_array(input: &[u8], pos: &mut usize) -> Result<Vec<Operand>, BackendError> {
322 let mut elements = Vec::new();
323
324 loop {
325 skip_whitespace_and_comments(input, pos);
326 if *pos >= input.len() {
327 return Err(BackendError::Interpreter("unterminated array".to_string()));
328 }
329
330 if input[*pos] == b']' {
331 *pos += 1; return Ok(elements);
333 }
334
335 let b = input[*pos];
336 match b {
337 b'(' => {
338 let s = parse_literal_string(input, pos)?;
339 elements.push(Operand::LiteralString(s));
340 }
341 b'<' => {
342 let s = parse_hex_string(input, pos)?;
343 elements.push(Operand::HexString(s));
344 }
345 b'[' => {
346 *pos += 1;
347 let arr = parse_array(input, pos)?;
348 elements.push(Operand::Array(arr));
349 }
350 b'/' => {
351 let name = parse_name(input, pos);
352 elements.push(Operand::Name(name));
353 }
354 b'0'..=b'9' | b'+' | b'-' | b'.' => {
355 let num = parse_number(input, pos)?;
356 elements.push(num);
357 }
358 b'a'..=b'z' | b'A'..=b'Z' => {
359 let keyword = parse_keyword(input, pos);
360 match keyword.as_str() {
361 "true" => elements.push(Operand::Boolean(true)),
362 "false" => elements.push(Operand::Boolean(false)),
363 "null" => elements.push(Operand::Null),
364 _ => {
365 elements.push(Operand::Name(keyword));
367 }
368 }
369 }
370 _ => {
371 return Err(BackendError::Interpreter(format!(
372 "unexpected byte in array: 0x{:02X}",
373 b
374 )));
375 }
376 }
377 }
378}
379
380fn parse_name(input: &[u8], pos: &mut usize) -> String {
382 debug_assert_eq!(input[*pos], b'/');
383 *pos += 1; let start = *pos;
386 while *pos < input.len() && !is_whitespace(input[*pos]) && !is_delimiter(input[*pos]) {
387 *pos += 1;
388 }
389
390 let raw = &input[start..*pos];
392 let mut name = Vec::with_capacity(raw.len());
393 let mut i = 0;
394 while i < raw.len() {
395 if raw[i] == b'#' && i + 2 < raw.len() {
396 if let (Ok(hi), Ok(lo)) = (hex_digit(raw[i + 1]), hex_digit(raw[i + 2])) {
397 name.push((hi << 4) | lo);
398 i += 3;
399 continue;
400 }
401 }
402 name.push(raw[i]);
403 i += 1;
404 }
405
406 String::from_utf8_lossy(&name).into_owned()
407}
408
409fn parse_number(input: &[u8], pos: &mut usize) -> Result<Operand, BackendError> {
411 let start = *pos;
412 let mut has_dot = false;
413
414 if *pos < input.len() && (input[*pos] == b'+' || input[*pos] == b'-') {
416 *pos += 1;
417 }
418
419 while *pos < input.len() {
421 let b = input[*pos];
422 if b == b'.' {
423 if has_dot {
424 break; }
426 has_dot = true;
427 *pos += 1;
428 } else if b.is_ascii_digit() {
429 *pos += 1;
430 } else {
431 break;
432 }
433 }
434
435 let token = &input[start..*pos];
436 let s = std::str::from_utf8(token)
437 .map_err(|_| BackendError::Interpreter("invalid UTF-8 in number token".to_string()))?;
438
439 if has_dot {
440 let val: f64 = s
441 .parse()
442 .map_err(|_| BackendError::Interpreter(format!("invalid real number: {s}")))?;
443 Ok(Operand::Real(val))
444 } else {
445 let val: i64 = s
446 .parse()
447 .map_err(|_| BackendError::Interpreter(format!("invalid integer: {s}")))?;
448 Ok(Operand::Integer(val))
449 }
450}
451
452fn parse_keyword(input: &[u8], pos: &mut usize) -> String {
454 let start = *pos;
455 while *pos < input.len() {
456 let b = input[*pos];
457 if b.is_ascii_alphabetic() || b == b'*' || b == b'\'' || b == b'"' {
458 *pos += 1;
459 } else {
460 break;
461 }
462 }
463 String::from_utf8_lossy(&input[start..*pos]).into_owned()
464}
465
466type InlineImageDict = Vec<(String, Operand)>;
468
469fn parse_inline_image(
472 input: &[u8],
473 pos: &mut usize,
474) -> Result<(InlineImageDict, Vec<u8>), BackendError> {
475 let mut dict = Vec::new();
477
478 loop {
479 skip_whitespace_and_comments(input, pos);
480 if *pos >= input.len() {
481 return Err(BackendError::Interpreter(
482 "unterminated inline image (missing ID)".to_string(),
483 ));
484 }
485
486 if *pos + 1 < input.len()
488 && input[*pos] == b'I'
489 && input[*pos + 1] == b'D'
490 && (*pos + 2 >= input.len() || is_whitespace(input[*pos + 2]))
491 {
492 *pos += 2; if *pos < input.len() && is_whitespace(input[*pos]) {
495 *pos += 1;
496 }
497 break;
498 }
499
500 if input[*pos] != b'/' {
502 return Err(BackendError::Interpreter(
503 "expected name key in inline image dictionary".to_string(),
504 ));
505 }
506 let key = parse_name(input, pos);
507
508 skip_whitespace_and_comments(input, pos);
510 if *pos >= input.len() {
511 return Err(BackendError::Interpreter(
512 "unterminated inline image dictionary".to_string(),
513 ));
514 }
515
516 let value = parse_inline_image_value(input, pos)?;
517 dict.push((key, value));
518 }
519
520 let data_start = *pos;
522 while *pos < input.len() {
524 if *pos + 2 <= input.len()
525 && (*pos == data_start || is_whitespace(input[*pos - 1]))
526 && input[*pos] == b'E'
527 && input[*pos + 1] == b'I'
528 && (*pos + 2 >= input.len()
529 || is_whitespace(input[*pos + 2])
530 || is_delimiter(input[*pos + 2]))
531 {
532 let data = input[data_start..*pos].to_vec();
533 let data = if data.last().is_some_and(|&b| is_whitespace(b)) {
535 data[..data.len() - 1].to_vec()
536 } else {
537 data
538 };
539 *pos += 2; return Ok((dict, data));
541 }
542 *pos += 1;
543 }
544
545 Err(BackendError::Interpreter(
546 "unterminated inline image (missing EI)".to_string(),
547 ))
548}
549
550fn parse_inline_image_value(input: &[u8], pos: &mut usize) -> Result<Operand, BackendError> {
552 let b = input[*pos];
553 match b {
554 b'/' => Ok(Operand::Name(parse_name(input, pos))),
555 b'(' => Ok(Operand::LiteralString(parse_literal_string(input, pos)?)),
556 b'<' => Ok(Operand::HexString(parse_hex_string(input, pos)?)),
557 b'[' => {
558 *pos += 1;
559 Ok(Operand::Array(parse_array(input, pos)?))
560 }
561 b'0'..=b'9' | b'+' | b'-' | b'.' => parse_number(input, pos),
562 b'a'..=b'z' | b'A'..=b'Z' => {
563 let kw = parse_keyword(input, pos);
564 match kw.as_str() {
565 "true" => Ok(Operand::Boolean(true)),
566 "false" => Ok(Operand::Boolean(false)),
567 "null" => Ok(Operand::Null),
568 _ => Ok(Operand::Name(kw)),
569 }
570 }
571 _ => Err(BackendError::Interpreter(format!(
572 "unexpected byte in inline image value: 0x{:02X}",
573 b
574 ))),
575 }
576}
577
578#[cfg(test)]
579mod tests {
580 use super::*;
581
582 #[test]
585 fn parse_integer() {
586 let ops = tokenize(b"42 m").unwrap();
587 assert_eq!(ops.len(), 1);
588 assert_eq!(ops[0].name, "m");
589 assert_eq!(ops[0].operands, vec![Operand::Integer(42)]);
590 }
591
592 #[test]
593 fn parse_negative_integer() {
594 let ops = tokenize(b"-7 Td").unwrap();
595 assert_eq!(ops.len(), 1);
596 assert_eq!(ops[0].operands, vec![Operand::Integer(-7)]);
597 }
598
599 #[test]
600 fn parse_real_number() {
601 let ops = tokenize(b"3.14 w").unwrap();
602 assert_eq!(ops.len(), 1);
603 assert_eq!(ops[0].operands, vec![Operand::Real(3.14)]);
604 }
605
606 #[test]
607 fn parse_real_leading_dot() {
608 let ops = tokenize(b".5 w").unwrap();
609 assert_eq!(ops.len(), 1);
610 assert_eq!(ops[0].operands, vec![Operand::Real(0.5)]);
611 }
612
613 #[test]
614 fn parse_negative_real() {
615 let ops = tokenize(b"-.002 w").unwrap();
616 assert_eq!(ops.len(), 1);
617 assert_eq!(ops[0].operands, vec![Operand::Real(-0.002)]);
618 }
619
620 #[test]
621 fn parse_name_operand() {
622 let ops = tokenize(b"/F1 12 Tf").unwrap();
623 assert_eq!(ops.len(), 1);
624 assert_eq!(ops[0].name, "Tf");
625 assert_eq!(
626 ops[0].operands,
627 vec![Operand::Name("F1".to_string()), Operand::Integer(12)]
628 );
629 }
630
631 #[test]
632 fn parse_name_with_hex_escape() {
633 let ops = tokenize(b"/F#231 12 Tf").unwrap();
634 assert_eq!(ops[0].operands[0], Operand::Name("F#1".to_string()));
635 }
636
637 #[test]
638 fn parse_literal_string_simple() {
639 let ops = tokenize(b"(Hello) Tj").unwrap();
640 assert_eq!(ops.len(), 1);
641 assert_eq!(ops[0].name, "Tj");
642 assert_eq!(
643 ops[0].operands,
644 vec![Operand::LiteralString(b"Hello".to_vec())]
645 );
646 }
647
648 #[test]
649 fn parse_literal_string_escaped_chars() {
650 let ops = tokenize(b"(line1\\nline2) Tj").unwrap();
651 assert_eq!(
652 ops[0].operands,
653 vec![Operand::LiteralString(b"line1\nline2".to_vec())]
654 );
655 }
656
657 #[test]
658 fn parse_literal_string_balanced_parens() {
659 let ops = tokenize(b"(a(b)c) Tj").unwrap();
660 assert_eq!(
661 ops[0].operands,
662 vec![Operand::LiteralString(b"a(b)c".to_vec())]
663 );
664 }
665
666 #[test]
667 fn parse_literal_string_octal_escape() {
668 let ops = tokenize(b"(\\101) Tj").unwrap();
670 assert_eq!(ops[0].operands, vec![Operand::LiteralString(vec![65])]);
671 }
672
673 #[test]
674 fn parse_hex_string() {
675 let ops = tokenize(b"<48656C6C6F> Tj").unwrap();
676 assert_eq!(ops.len(), 1);
677 assert_eq!(ops[0].operands, vec![Operand::HexString(b"Hello".to_vec())]);
678 }
679
680 #[test]
681 fn parse_hex_string_odd_digits() {
682 let ops = tokenize(b"<ABC> Tj").unwrap();
684 assert_eq!(ops[0].operands, vec![Operand::HexString(vec![0xAB, 0xC0])]);
685 }
686
687 #[test]
688 fn parse_hex_string_with_whitespace() {
689 let ops = tokenize(b"<48 65 6C 6C 6F> Tj").unwrap();
690 assert_eq!(ops[0].operands, vec![Operand::HexString(b"Hello".to_vec())]);
691 }
692
693 #[test]
694 fn parse_array_operand() {
695 let ops = tokenize(b"[1 2 3] re").unwrap();
696 assert_eq!(ops.len(), 1);
697 assert_eq!(
698 ops[0].operands,
699 vec![Operand::Array(vec![
700 Operand::Integer(1),
701 Operand::Integer(2),
702 Operand::Integer(3),
703 ])]
704 );
705 }
706
707 #[test]
708 fn parse_boolean_true() {
709 let ops = tokenize(b"true m").unwrap();
710 assert_eq!(ops[0].operands, vec![Operand::Boolean(true)]);
711 }
712
713 #[test]
714 fn parse_boolean_false() {
715 let ops = tokenize(b"false m").unwrap();
716 assert_eq!(ops[0].operands, vec![Operand::Boolean(false)]);
717 }
718
719 #[test]
720 fn parse_null_operand() {
721 let ops = tokenize(b"null m").unwrap();
722 assert_eq!(ops[0].operands, vec![Operand::Null]);
723 }
724
725 #[test]
728 fn parse_bt_et() {
729 let ops = tokenize(b"BT ET").unwrap();
730 assert_eq!(ops.len(), 2);
731 assert_eq!(ops[0].name, "BT");
732 assert!(ops[0].operands.is_empty());
733 assert_eq!(ops[1].name, "ET");
734 assert!(ops[1].operands.is_empty());
735 }
736
737 #[test]
738 fn parse_tf_operator() {
739 let ops = tokenize(b"/F1 12 Tf").unwrap();
740 assert_eq!(ops.len(), 1);
741 assert_eq!(ops[0].name, "Tf");
742 assert_eq!(
743 ops[0].operands,
744 vec![Operand::Name("F1".to_string()), Operand::Integer(12)]
745 );
746 }
747
748 #[test]
749 fn parse_td_operator() {
750 let ops = tokenize(b"72 700 Td").unwrap();
751 assert_eq!(ops.len(), 1);
752 assert_eq!(ops[0].name, "Td");
753 assert_eq!(
754 ops[0].operands,
755 vec![Operand::Integer(72), Operand::Integer(700)]
756 );
757 }
758
759 #[test]
760 fn parse_tj_operator() {
761 let ops = tokenize(b"(Hello World) Tj").unwrap();
762 assert_eq!(ops.len(), 1);
763 assert_eq!(ops[0].name, "Tj");
764 assert_eq!(
765 ops[0].operands,
766 vec![Operand::LiteralString(b"Hello World".to_vec())]
767 );
768 }
769
770 #[test]
771 fn parse_tj_array_with_kerning() {
772 let ops = tokenize(b"[(H) -20 (ello)] TJ").unwrap();
773 assert_eq!(ops.len(), 1);
774 assert_eq!(ops[0].name, "TJ");
775 assert_eq!(
776 ops[0].operands,
777 vec![Operand::Array(vec![
778 Operand::LiteralString(b"H".to_vec()),
779 Operand::Integer(-20),
780 Operand::LiteralString(b"ello".to_vec()),
781 ])]
782 );
783 }
784
785 #[test]
786 fn parse_path_operators() {
787 let ops = tokenize(b"100 200 m 300 400 l S").unwrap();
788 assert_eq!(ops.len(), 3);
789 assert_eq!(ops[0].name, "m");
790 assert_eq!(
791 ops[0].operands,
792 vec![Operand::Integer(100), Operand::Integer(200)]
793 );
794 assert_eq!(ops[1].name, "l");
795 assert_eq!(
796 ops[1].operands,
797 vec![Operand::Integer(300), Operand::Integer(400)]
798 );
799 assert_eq!(ops[2].name, "S");
800 assert!(ops[2].operands.is_empty());
801 }
802
803 #[test]
804 fn parse_re_operator() {
805 let ops = tokenize(b"10 20 100 50 re f").unwrap();
806 assert_eq!(ops.len(), 2);
807 assert_eq!(ops[0].name, "re");
808 assert_eq!(
809 ops[0].operands,
810 vec![
811 Operand::Integer(10),
812 Operand::Integer(20),
813 Operand::Integer(100),
814 Operand::Integer(50),
815 ]
816 );
817 assert_eq!(ops[1].name, "f");
818 }
819
820 #[test]
821 fn parse_f_star_operator() {
822 let ops = tokenize(b"f*").unwrap();
823 assert_eq!(ops.len(), 1);
824 assert_eq!(ops[0].name, "f*");
825 }
826
827 #[test]
830 fn skip_comments() {
831 let ops = tokenize(b"% this is a comment\nBT ET").unwrap();
832 assert_eq!(ops.len(), 2);
833 assert_eq!(ops[0].name, "BT");
834 assert_eq!(ops[1].name, "ET");
835 }
836
837 #[test]
838 fn inline_comment_between_operators() {
839 let ops = tokenize(b"BT % begin text\n/F1 12 Tf\nET").unwrap();
840 assert_eq!(ops.len(), 3);
841 assert_eq!(ops[0].name, "BT");
842 assert_eq!(ops[1].name, "Tf");
843 assert_eq!(ops[2].name, "ET");
844 }
845
846 #[test]
849 fn parse_typical_text_stream() {
850 let stream = b"BT\n/F1 12 Tf\n72 700 Td\n(Hello World) Tj\nET";
851 let ops = tokenize(stream).unwrap();
852 assert_eq!(ops.len(), 5);
853 assert_eq!(ops[0].name, "BT");
854 assert_eq!(ops[1].name, "Tf");
855 assert_eq!(ops[1].operands.len(), 2);
856 assert_eq!(ops[2].name, "Td");
857 assert_eq!(ops[2].operands.len(), 2);
858 assert_eq!(ops[3].name, "Tj");
859 assert_eq!(ops[3].operands.len(), 1);
860 assert_eq!(ops[4].name, "ET");
861 }
862
863 #[test]
864 fn parse_mixed_text_and_graphics() {
865 let stream = b"q\n1 0 0 1 72 720 cm\nBT\n/F1 12 Tf\n(Test) Tj\nET\n100 200 300 400 re S\nQ";
866 let ops = tokenize(stream).unwrap();
867 assert_eq!(ops[0].name, "q");
868 assert_eq!(ops[1].name, "cm");
869 assert_eq!(ops[1].operands.len(), 6);
870 assert_eq!(ops[2].name, "BT");
871 assert_eq!(ops[3].name, "Tf");
872 assert_eq!(ops[4].name, "Tj");
873 assert_eq!(ops[5].name, "ET");
874 assert_eq!(ops[6].name, "re");
875 assert_eq!(ops[7].name, "S");
876 assert_eq!(ops[8].name, "Q");
877 }
878
879 #[test]
880 fn parse_color_operators() {
881 let ops = tokenize(b"0.5 g\n1 0 0 RG").unwrap();
882 assert_eq!(ops.len(), 2);
883 assert_eq!(ops[0].name, "g");
884 assert_eq!(ops[0].operands, vec![Operand::Real(0.5)]);
885 assert_eq!(ops[1].name, "RG");
886 assert_eq!(
887 ops[1].operands,
888 vec![
889 Operand::Integer(1),
890 Operand::Integer(0),
891 Operand::Integer(0),
892 ]
893 );
894 }
895
896 #[test]
897 fn parse_quote_operator() {
898 let ops = tokenize(b"(text) '").unwrap();
899 assert_eq!(ops.len(), 1);
900 assert_eq!(ops[0].name, "'");
901 assert_eq!(
902 ops[0].operands,
903 vec![Operand::LiteralString(b"text".to_vec())]
904 );
905 }
906
907 #[test]
908 fn parse_double_quote_operator() {
909 let ops = tokenize(b"1 2 (text) \"").unwrap();
910 assert_eq!(ops.len(), 1);
911 assert_eq!(ops[0].name, "\"");
912 assert_eq!(
913 ops[0].operands,
914 vec![
915 Operand::Integer(1),
916 Operand::Integer(2),
917 Operand::LiteralString(b"text".to_vec()),
918 ]
919 );
920 }
921
922 #[test]
923 fn parse_empty_stream() {
924 let ops = tokenize(b"").unwrap();
925 assert!(ops.is_empty());
926 }
927
928 #[test]
929 fn parse_whitespace_only() {
930 let ops = tokenize(b" \t\n\r ").unwrap();
931 assert!(ops.is_empty());
932 }
933
934 #[test]
935 fn parse_inline_image() {
936 let stream = b"BI\n/W 2 /H 2 /CS /G /BPC 8\nID \x00\xFF\x00\xFF\nEI";
938 let ops = tokenize(stream).unwrap();
939 assert_eq!(ops.len(), 1);
940 assert_eq!(ops[0].name, "BI");
941 if let Operand::Array(ref entries) = ops[0].operands[0] {
943 assert_eq!(entries.len(), 8);
945 assert_eq!(entries[0], Operand::Name("W".to_string()));
946 assert_eq!(entries[1], Operand::Integer(2));
947 } else {
948 panic!("expected array operand for BI dict");
949 }
950 if let Operand::LiteralString(ref data) = ops[0].operands[1] {
952 assert_eq!(data, &[0x00, 0xFF, 0x00, 0xFF]);
953 } else {
954 panic!("expected literal string operand for BI data");
955 }
956 }
957
958 #[test]
961 fn parse_positive_sign_number() {
962 let ops = tokenize(b"+5 m").unwrap();
963 assert_eq!(ops[0].operands, vec![Operand::Integer(5)]);
964 }
965
966 #[test]
967 fn parse_zero() {
968 let ops = tokenize(b"0 m").unwrap();
969 assert_eq!(ops[0].operands, vec![Operand::Integer(0)]);
970 }
971
972 #[test]
973 fn parse_zero_real() {
974 let ops = tokenize(b"0.0 m").unwrap();
975 assert_eq!(ops[0].operands, vec![Operand::Real(0.0)]);
976 }
977
978 #[test]
979 fn parse_multiple_operators_no_operands() {
980 let ops = tokenize(b"q Q n W").unwrap();
981 assert_eq!(ops.len(), 4);
982 assert_eq!(ops[0].name, "q");
983 assert_eq!(ops[1].name, "Q");
984 assert_eq!(ops[2].name, "n");
985 assert_eq!(ops[3].name, "W");
986 }
987
988 #[test]
989 fn parse_text_matrix() {
990 let ops = tokenize(b"1 0 0 1 72 700 Tm").unwrap();
991 assert_eq!(ops.len(), 1);
992 assert_eq!(ops[0].name, "Tm");
993 assert_eq!(ops[0].operands.len(), 6);
994 }
995
996 #[test]
997 fn unterminated_literal_string_error() {
998 let result = tokenize(b"(unclosed");
999 assert!(result.is_err());
1000 }
1001
1002 #[test]
1003 fn unterminated_array_error() {
1004 let result = tokenize(b"[1 2 3");
1005 assert!(result.is_err());
1006 }
1007
1008 #[test]
1009 fn unexpected_array_close_error() {
1010 let result = tokenize(b"]");
1011 assert!(result.is_err());
1012 }
1013
1014 #[test]
1015 fn parse_do_operator() {
1016 let ops = tokenize(b"/Im0 Do").unwrap();
1017 assert_eq!(ops.len(), 1);
1018 assert_eq!(ops[0].name, "Do");
1019 assert_eq!(ops[0].operands, vec![Operand::Name("Im0".to_string())]);
1020 }
1021
1022 #[test]
1023 fn parse_scn_operator() {
1024 let ops = tokenize(b"0.5 0.2 0.8 scn").unwrap();
1025 assert_eq!(ops.len(), 1);
1026 assert_eq!(ops[0].name, "scn");
1027 assert_eq!(ops[0].operands.len(), 3);
1028 }
1029
1030 #[test]
1031 fn parse_dash_pattern() {
1032 let ops = tokenize(b"[3 5] 6 d").unwrap();
1033 assert_eq!(ops.len(), 1);
1034 assert_eq!(ops[0].name, "d");
1035 assert_eq!(
1036 ops[0].operands,
1037 vec![
1038 Operand::Array(vec![Operand::Integer(3), Operand::Integer(5)]),
1039 Operand::Integer(6),
1040 ]
1041 );
1042 }
1043
1044 #[test]
1045 fn parse_consecutive_strings() {
1046 let ops = tokenize(b"(abc) (def) Tj").unwrap();
1047 assert_eq!(ops.len(), 1);
1048 assert_eq!(ops[0].name, "Tj");
1049 assert_eq!(ops[0].operands.len(), 2);
1050 }
1051}