1use super::{ParseError, ParseResult};
57use crate::objects::Object;
58use std::collections::HashMap;
59
60#[derive(Debug, Clone, PartialEq)]
74pub enum MarkedContentValue {
75 String(Vec<u8>),
79 Integer(i64),
81 Real(f64),
83 Name(String),
85 Array(Vec<MarkedContentValue>),
87 Dict(HashMap<String, MarkedContentValue>),
89}
90
91#[derive(Debug, Clone, PartialEq)]
101pub enum MarkedContentProps {
102 Inline(HashMap<String, MarkedContentValue>),
103 ResourceRef(String),
104}
105
106#[derive(Debug, Clone, PartialEq)]
140pub enum ContentOperation {
141 BeginText,
145
146 EndText,
149
150 SetCharSpacing(f32),
154
155 SetWordSpacing(f32),
158
159 SetHorizontalScaling(f32),
162
163 SetLeading(f32),
166
167 SetFont(String, f32),
170
171 SetTextRenderMode(i32),
174
175 SetTextRise(f32),
178
179 MoveText(f32, f32),
183
184 MoveTextSetLeading(f32, f32),
187
188 SetTextMatrix(f32, f32, f32, f32, f32, f32),
191
192 NextLine,
195
196 ShowText(Vec<u8>),
200
201 ShowTextArray(Vec<TextElement>),
204
205 NextLineShowText(Vec<u8>),
208
209 SetSpacingNextLineShowText(f32, f32, Vec<u8>),
212
213 SaveGraphicsState,
217
218 RestoreGraphicsState,
221
222 SetTransformMatrix(f32, f32, f32, f32, f32, f32),
225
226 SetLineWidth(f32),
228
229 SetLineCap(i32),
232
233 SetLineJoin(i32),
236
237 SetMiterLimit(f32),
240
241 SetDashPattern(Vec<f32>, f32),
244
245 SetIntent(String),
248
249 SetFlatness(f32),
252
253 SetGraphicsStateParams(String),
256
257 MoveTo(f32, f32),
260
261 LineTo(f32, f32),
263
264 CurveTo(f32, f32, f32, f32, f32, f32),
267
268 CurveToV(f32, f32, f32, f32),
270
271 CurveToY(f32, f32, f32, f32),
273
274 ClosePath,
277
278 Rectangle(f32, f32, f32, f32),
281
282 Stroke,
285
286 CloseStroke,
289
290 Fill,
292
293 FillEvenOdd,
295
296 FillStroke,
299
300 FillStrokeEvenOdd,
302
303 CloseFillStroke,
306
307 CloseFillStrokeEvenOdd,
309
310 EndPath,
313
314 Clip, ClipEvenOdd, SetStrokingColorSpace(String),
322
323 SetNonStrokingColorSpace(String),
326
327 SetStrokingColor(Vec<f32>),
330
331 SetNonStrokingColor(Vec<f32>),
334
335 SetStrokingGray(f32),
338
339 SetNonStrokingGray(f32),
341
342 SetStrokingRGB(f32, f32, f32),
345
346 SetNonStrokingRGB(f32, f32, f32),
348
349 SetStrokingCMYK(f32, f32, f32, f32),
351
352 SetNonStrokingCMYK(f32, f32, f32, f32),
354
355 ShadingFill(String), BeginInlineImage,
361 InlineImage {
363 params: HashMap<String, Object>,
365 data: Vec<u8>,
367 },
368
369 PaintXObject(String),
373
374 BeginMarkedContent(String), BeginMarkedContentWithProps(String, MarkedContentProps), EndMarkedContent, DefineMarkedContentPoint(String), DefineMarkedContentPointWithProps(String, MarkedContentProps), BeginCompatibility, EndCompatibility, }
385
386#[derive(Debug, Clone, PartialEq)]
405pub enum TextElement {
406 Text(Vec<u8>),
408 Spacing(f32),
411}
412
413#[derive(Debug, Clone, PartialEq)]
415pub(super) enum Token {
416 Number(f32),
417 Integer(i32),
418 String(Vec<u8>),
419 HexString(Vec<u8>),
420 Name(String),
421 Operator(String),
422 ArrayStart,
423 ArrayEnd,
424 DictStart,
425 DictEnd,
426 InlineImageData(Vec<u8>),
430}
431
432pub struct ContentTokenizer<'a> {
434 input: &'a [u8],
435 position: usize,
436 in_inline_image: bool,
439}
440
441impl<'a> ContentTokenizer<'a> {
442 pub fn new(input: &'a [u8]) -> Self {
444 Self {
445 input,
446 position: 0,
447 in_inline_image: false,
448 }
449 }
450
451 pub(super) fn next_token(&mut self) -> ParseResult<Option<Token>> {
453 if self.in_inline_image {
455 self.in_inline_image = false;
456 return self.read_inline_image_data();
457 }
458
459 self.skip_whitespace();
460
461 if self.position >= self.input.len() {
462 return Ok(None);
463 }
464
465 let ch = self.input[self.position];
466
467 match ch {
468 b'+' | b'-' | b'.' | b'0'..=b'9' => self.read_number(),
470
471 b'(' => self.read_literal_string(),
473 b'<' => {
474 if self.peek_next() == Some(b'<') {
475 self.position += 2;
476 Ok(Some(Token::DictStart))
477 } else {
478 self.read_hex_string()
479 }
480 }
481 b'>' => {
482 if self.peek_next() == Some(b'>') {
483 self.position += 2;
484 Ok(Some(Token::DictEnd))
485 } else {
486 Err(ParseError::SyntaxError {
487 position: self.position,
488 message: "Unexpected '>'".to_string(),
489 })
490 }
491 }
492
493 b'[' => {
495 self.position += 1;
496 Ok(Some(Token::ArrayStart))
497 }
498 b']' => {
499 self.position += 1;
500 Ok(Some(Token::ArrayEnd))
501 }
502
503 b'/' => self.read_name(),
505
506 b';' | b')' | b'{' | b'}' => {
511 self.position += 1;
512 self.next_token() }
514
515 _ => {
517 let token = self.read_operator()?;
518 if let Some(Token::Operator(ref op)) = token {
520 if op == "ID" {
521 self.in_inline_image = true;
522 }
523 }
524 Ok(token)
525 }
526 }
527 }
528
529 fn skip_whitespace(&mut self) {
530 while self.position < self.input.len() {
531 match self.input[self.position] {
532 b' ' | b'\t' | b'\r' | b'\n' | b'\x0C' => self.position += 1,
533 b'%' => self.skip_comment(),
534 _ => break,
535 }
536 }
537 }
538
539 fn skip_comment(&mut self) {
540 while self.position < self.input.len() && self.input[self.position] != b'\n' {
541 self.position += 1;
542 }
543 }
544
545 fn peek_next(&self) -> Option<u8> {
546 if self.position + 1 < self.input.len() {
547 Some(self.input[self.position + 1])
548 } else {
549 None
550 }
551 }
552
553 fn read_number(&mut self) -> ParseResult<Option<Token>> {
554 let start = self.position;
555 let mut has_dot = false;
556
557 if self.position < self.input.len()
559 && (self.input[self.position] == b'+' || self.input[self.position] == b'-')
560 {
561 self.position += 1;
562 }
563
564 while self.position < self.input.len() {
566 match self.input[self.position] {
567 b'0'..=b'9' => self.position += 1,
568 b'.' if !has_dot => {
569 has_dot = true;
570 self.position += 1;
571 }
572 _ => break,
573 }
574 }
575
576 let num_str = std::str::from_utf8(&self.input[start..self.position]).map_err(|_| {
577 ParseError::SyntaxError {
578 position: start,
579 message: "Invalid number format".to_string(),
580 }
581 })?;
582
583 if has_dot {
584 let value = num_str
585 .parse::<f32>()
586 .map_err(|_| ParseError::SyntaxError {
587 position: start,
588 message: "Invalid float number".to_string(),
589 })?;
590 Ok(Some(Token::Number(value)))
591 } else {
592 let value = num_str
593 .parse::<i32>()
594 .map_err(|_| ParseError::SyntaxError {
595 position: start,
596 message: "Invalid integer number".to_string(),
597 })?;
598 Ok(Some(Token::Integer(value)))
599 }
600 }
601
602 fn read_literal_string(&mut self) -> ParseResult<Option<Token>> {
603 self.position += 1; let mut result = Vec::new();
605 let mut paren_depth = 1;
606 let mut escape = false;
607
608 while self.position < self.input.len() && paren_depth > 0 {
609 let ch = self.input[self.position];
610 self.position += 1;
611
612 if escape {
613 match ch {
614 b'n' => result.push(b'\n'),
615 b'r' => result.push(b'\r'),
616 b't' => result.push(b'\t'),
617 b'b' => result.push(b'\x08'),
618 b'f' => result.push(b'\x0C'),
619 b'(' => result.push(b'('),
620 b')' => result.push(b')'),
621 b'\\' => result.push(b'\\'),
622 b'0'..=b'7' => {
623 self.position -= 1;
625 let octal_value = self.read_octal_escape()?;
626 result.push(octal_value);
627 }
628 _ => result.push(ch), }
630 escape = false;
631 } else {
632 match ch {
633 b'\\' => escape = true,
634 b'(' => {
635 paren_depth += 1;
636 result.push(ch);
637 }
638 b')' => {
639 paren_depth -= 1;
640 if paren_depth > 0 {
641 result.push(ch);
642 }
643 }
644 _ => result.push(ch),
645 }
646 }
647 }
648
649 Ok(Some(Token::String(result)))
650 }
651
652 fn read_octal_escape(&mut self) -> ParseResult<u8> {
653 let mut value = 0u16;
656 let mut count = 0;
657
658 while count < 3 && self.position < self.input.len() {
659 match self.input[self.position] {
660 b'0'..=b'7' => {
661 value = value * 8 + u16::from(self.input[self.position] - b'0');
662 self.position += 1;
663 count += 1;
664 }
665 _ => break,
666 }
667 }
668
669 Ok(value as u8)
670 }
671
672 fn read_hex_string(&mut self) -> ParseResult<Option<Token>> {
673 self.position += 1; let mut result = Vec::new();
675 let mut nibble = None;
676
677 while self.position < self.input.len() {
678 let ch = self.input[self.position];
679
680 match ch {
681 b'>' => {
682 self.position += 1;
683 if let Some(n) = nibble {
685 result.push(n << 4);
686 }
687 return Ok(Some(Token::HexString(result)));
688 }
689 b'0'..=b'9' | b'A'..=b'F' | b'a'..=b'f' => {
690 let digit = if ch <= b'9' {
691 ch - b'0'
692 } else if ch <= b'F' {
693 ch - b'A' + 10
694 } else {
695 ch - b'a' + 10
696 };
697
698 if let Some(n) = nibble {
699 result.push((n << 4) | digit);
700 nibble = None;
701 } else {
702 nibble = Some(digit);
703 }
704 self.position += 1;
705 }
706 b' ' | b'\t' | b'\r' | b'\n' | b'\x0C' => {
707 self.position += 1;
709 }
710 _ => {
711 return Err(ParseError::SyntaxError {
712 position: self.position,
713 message: format!("Invalid character in hex string: {:?}", ch as char),
714 });
715 }
716 }
717 }
718
719 Err(ParseError::SyntaxError {
720 position: self.position,
721 message: "Unterminated hex string".to_string(),
722 })
723 }
724
725 fn read_name(&mut self) -> ParseResult<Option<Token>> {
726 self.position += 1; let start = self.position;
728
729 while self.position < self.input.len() {
730 let ch = self.input[self.position];
731 match ch {
732 b' ' | b'\t' | b'\r' | b'\n' | b'\x0C' | b'(' | b')' | b'<' | b'>' | b'['
733 | b']' | b'{' | b'}' | b'/' | b'%' => break,
734 b'#' => {
735 self.position += 1;
737 if self.position + 1 < self.input.len() {
738 self.position += 2;
739 }
740 }
741 _ => self.position += 1,
742 }
743 }
744
745 let name_bytes = &self.input[start..self.position];
746 let name = self.decode_name(name_bytes)?;
747 Ok(Some(Token::Name(name)))
748 }
749
750 fn decode_name(&self, bytes: &[u8]) -> ParseResult<String> {
751 let mut result = Vec::new();
752 let mut i = 0;
753
754 while i < bytes.len() {
755 if bytes[i] == b'#' && i + 2 < bytes.len() {
756 let hex_str = std::str::from_utf8(&bytes[i + 1..i + 3]).map_err(|_| {
758 ParseError::SyntaxError {
759 position: self.position,
760 message: "Invalid hex escape in name".to_string(),
761 }
762 })?;
763 let value =
764 u8::from_str_radix(hex_str, 16).map_err(|_| ParseError::SyntaxError {
765 position: self.position,
766 message: "Invalid hex escape in name".to_string(),
767 })?;
768 result.push(value);
769 i += 3;
770 } else {
771 result.push(bytes[i]);
772 i += 1;
773 }
774 }
775
776 String::from_utf8(result).map_err(|_| ParseError::SyntaxError {
777 position: self.position,
778 message: "Invalid UTF-8 in name".to_string(),
779 })
780 }
781
782 fn read_operator(&mut self) -> ParseResult<Option<Token>> {
783 let start = self.position;
784
785 while self.position < self.input.len() {
786 let ch = self.input[self.position];
787 match ch {
788 b' ' | b'\t' | b'\r' | b'\n' | b'\x0C' | b'(' | b')' | b'<' | b'>' | b'['
789 | b']' | b'{' | b'}' | b'/' | b'%' | b';' => break,
790 _ => self.position += 1,
791 }
792 }
793
794 let op_bytes = &self.input[start..self.position];
795 let op = std::str::from_utf8(op_bytes).map_err(|_| ParseError::SyntaxError {
796 position: start,
797 message: "Invalid operator".to_string(),
798 })?;
799
800 Ok(Some(Token::Operator(op.to_string())))
801 }
802
803 fn read_inline_image_data(&mut self) -> ParseResult<Option<Token>> {
809 if self.position < self.input.len() {
811 let ch = self.input[self.position];
812 if ch == b' ' || ch == b'\n' || ch == b'\r' || ch == b'\t' {
813 self.position += 1;
814 if ch == b'\r'
816 && self.position < self.input.len()
817 && self.input[self.position] == b'\n'
818 {
819 self.position += 1;
820 }
821 }
822 }
823
824 let start = self.position;
825
826 while self.position + 1 < self.input.len() {
828 let preceded_by_whitespace = self.position == start
829 || matches!(
830 self.input[self.position - 1],
831 b' ' | b'\t' | b'\r' | b'\n' | b'\x0C'
832 );
833
834 if preceded_by_whitespace
835 && self.input[self.position] == b'E'
836 && self.input[self.position + 1] == b'I'
837 {
838 let after_ei = self.position + 2;
839 let followed_by_boundary = after_ei >= self.input.len()
840 || matches!(
841 self.input[after_ei],
842 b' ' | b'\t' | b'\r' | b'\n' | b'\x0C' | b'/' | b'<' | b'(' | b'[' | b'%'
843 );
844
845 if followed_by_boundary {
846 let mut end = self.position;
848 if end > start
849 && matches!(self.input[end - 1], b' ' | b'\t' | b'\r' | b'\n' | b'\x0C')
850 {
851 end -= 1;
852 }
853 let data = self.input[start..end].to_vec();
854 self.position = after_ei; return Ok(Some(Token::InlineImageData(data)));
856 }
857 }
858 self.position += 1;
859 }
860
861 let data = self.input[start..].to_vec();
863 self.position = self.input.len();
864 Ok(Some(Token::InlineImageData(data)))
865 }
866}
867
868pub struct ContentParser {
887 tokens: Vec<Token>,
888 position: usize,
889}
890
891pub(crate) struct ParsedType3CharProc {
892 pub(crate) width: (f64, f64),
893 pub(crate) bbox: Option<[f64; 4]>,
894 pub(crate) operations: Vec<ContentOperation>,
895}
896
897impl ContentParser {
898 pub fn new(_content: &[u8]) -> Self {
900 Self {
901 tokens: Vec::new(),
902 position: 0,
903 }
904 }
905
906 pub fn parse(content: &[u8]) -> ParseResult<Vec<ContentOperation>> {
940 Self::parse_content(content)
941 }
942
943 pub fn parse_strict(content: &[u8]) -> ParseResult<Vec<ContentOperation>> {
950 let mut tokenizer = ContentTokenizer::new(content);
951 let mut tokens = Vec::new();
952 while let Some(token) = tokenizer.next_token()? {
953 tokens.push(token);
954 }
955 Self {
956 tokens,
957 position: 0,
958 }
959 .parse_operators_strict()
960 }
961
962 pub(crate) fn parse_type3_charproc(content: &[u8]) -> ParseResult<ParsedType3CharProc> {
963 let mut tokenizer = ContentTokenizer::new(content);
964 let mut tokens = Vec::new();
965 while let Some(token) = tokenizer.next_token()? {
966 tokens.push(token);
967 }
968 let mut parser = Self {
969 tokens,
970 position: 0,
971 };
972 let mut operations = Vec::new();
973 let mut operands = Vec::new();
974 let mut metrics = None;
975 while parser.position < parser.tokens.len() {
976 let token = parser.tokens[parser.position].clone();
977 parser.position += 1;
978 match token {
979 Token::Operator(op) if op == "d0" => {
980 if metrics.is_some() || !operations.is_empty() {
981 return Err(ParseError::SyntaxError {
982 position: parser.position,
983 message: "d0/d1 must be the first Type 3 CharProc operator".into(),
984 });
985 }
986 let wy = parser.pop_number(&mut operands)?;
987 let wx = parser.pop_number(&mut operands)?;
988 if !operands.is_empty() {
989 return Err(ParseError::SyntaxError {
990 position: parser.position,
991 message: "d0 has extra operands".into(),
992 });
993 }
994 metrics = Some(((f64::from(wx), f64::from(wy)), None));
995 }
996 Token::Operator(op) if op == "d1" => {
997 if metrics.is_some() || !operations.is_empty() {
998 return Err(ParseError::SyntaxError {
999 position: parser.position,
1000 message: "d0/d1 must be the first Type 3 CharProc operator".into(),
1001 });
1002 }
1003 let ury = parser.pop_number(&mut operands)?;
1004 let urx = parser.pop_number(&mut operands)?;
1005 let lly = parser.pop_number(&mut operands)?;
1006 let llx = parser.pop_number(&mut operands)?;
1007 let wy = parser.pop_number(&mut operands)?;
1008 let wx = parser.pop_number(&mut operands)?;
1009 if !operands.is_empty() {
1010 return Err(ParseError::SyntaxError {
1011 position: parser.position,
1012 message: "d1 has extra operands".into(),
1013 });
1014 }
1015 metrics = Some((
1016 (f64::from(wx), f64::from(wy)),
1017 Some([
1018 f64::from(llx),
1019 f64::from(lly),
1020 f64::from(urx),
1021 f64::from(ury),
1022 ]),
1023 ));
1024 }
1025 Token::Operator(op) => {
1026 operations.push(parser.parse_operator(&op, &mut operands)?);
1027 }
1028 operand => operands.push(operand),
1029 }
1030 }
1031 if !operands.is_empty() {
1032 return Err(ParseError::SyntaxError {
1033 position: parser.position,
1034 message: "trailing operands without an operator".into(),
1035 });
1036 }
1037 let (width, bbox) = metrics.ok_or_else(|| ParseError::SyntaxError {
1038 position: 0,
1039 message: "Type 3 CharProc must begin with d0 or d1".into(),
1040 })?;
1041 Ok(ParsedType3CharProc {
1042 width,
1043 bbox,
1044 operations,
1045 })
1046 }
1047
1048 pub fn parse_content(content: &[u8]) -> ParseResult<Vec<ContentOperation>> {
1053 let mut tokenizer = ContentTokenizer::new(content);
1054 let mut tokens = Vec::new();
1055
1056 loop {
1063 match tokenizer.next_token() {
1064 Ok(Some(token)) => tokens.push(token),
1065 Ok(None) => break,
1066 Err(_e) => {
1067 tracing::debug!("content tokenizer stopped early: {_e}");
1068 break;
1069 }
1070 }
1071 }
1072
1073 let mut parser = Self {
1074 tokens,
1075 position: 0,
1076 };
1077
1078 parser.parse_operators()
1079 }
1080
1081 fn parse_operators(&mut self) -> ParseResult<Vec<ContentOperation>> {
1082 let mut operators = Vec::new();
1083 let mut operand_stack: Vec<Token> = Vec::new();
1084
1085 while self.position < self.tokens.len() {
1086 let token = self.tokens[self.position].clone();
1087 self.position += 1;
1088
1089 match &token {
1090 Token::Operator(op) => {
1091 match self.parse_operator(op, &mut operand_stack) {
1101 Ok(operator) => operators.push(operator),
1102 Err(_e) => {
1103 tracing::debug!("skipping malformed content operator '{op}': {_e}");
1104 operand_stack.clear();
1105 }
1106 }
1107 }
1108 _ => {
1109 operand_stack.push(token);
1111 }
1112 }
1113 }
1114
1115 Ok(operators)
1116 }
1117
1118 fn parse_operators_strict(&mut self) -> ParseResult<Vec<ContentOperation>> {
1119 let mut operators = Vec::new();
1120 let mut operand_stack = Vec::new();
1121 while self.position < self.tokens.len() {
1122 let token = self.tokens[self.position].clone();
1123 self.position += 1;
1124 match token {
1125 Token::Operator(op) => {
1126 operators.push(self.parse_operator(&op, &mut operand_stack)?);
1127 }
1128 operand => operand_stack.push(operand),
1129 }
1130 }
1131 if operand_stack.is_empty() {
1132 Ok(operators)
1133 } else {
1134 Err(ParseError::SyntaxError {
1135 position: self.position,
1136 message: "trailing operands without an operator".to_string(),
1137 })
1138 }
1139 }
1140
1141 fn parse_operator(
1142 &mut self,
1143 op: &str,
1144 operands: &mut Vec<Token>,
1145 ) -> ParseResult<ContentOperation> {
1146 let operator = match op {
1147 "BT" => ContentOperation::BeginText,
1149 "ET" => ContentOperation::EndText,
1150
1151 "Tc" => {
1153 let spacing = self.pop_number(operands)?;
1154 ContentOperation::SetCharSpacing(spacing)
1155 }
1156 "Tw" => {
1157 let spacing = self.pop_number(operands)?;
1158 ContentOperation::SetWordSpacing(spacing)
1159 }
1160 "Tz" => {
1161 let scale = self.pop_number(operands)?;
1162 ContentOperation::SetHorizontalScaling(scale)
1163 }
1164 "TL" => {
1165 let leading = self.pop_number(operands)?;
1166 ContentOperation::SetLeading(leading)
1167 }
1168 "Tf" => {
1169 let size = self.pop_number(operands)?;
1170 let font = self.pop_name(operands)?;
1171 ContentOperation::SetFont(font, size)
1172 }
1173 "Tr" => {
1174 let mode = self.pop_integer(operands)?;
1175 ContentOperation::SetTextRenderMode(mode)
1176 }
1177 "Ts" => {
1178 let rise = self.pop_number(operands)?;
1179 ContentOperation::SetTextRise(rise)
1180 }
1181
1182 "Td" => {
1184 let ty = self.pop_number(operands)?;
1185 let tx = self.pop_number(operands)?;
1186 ContentOperation::MoveText(tx, ty)
1187 }
1188 "TD" => {
1189 let ty = self.pop_number(operands)?;
1190 let tx = self.pop_number(operands)?;
1191 ContentOperation::MoveTextSetLeading(tx, ty)
1192 }
1193 "Tm" => {
1194 let f = self.pop_number(operands)?;
1195 let e = self.pop_number(operands)?;
1196 let d = self.pop_number(operands)?;
1197 let c = self.pop_number(operands)?;
1198 let b = self.pop_number(operands)?;
1199 let a = self.pop_number(operands)?;
1200 ContentOperation::SetTextMatrix(a, b, c, d, e, f)
1201 }
1202 "T*" => ContentOperation::NextLine,
1203
1204 "Tj" => {
1206 let text = self.pop_string(operands)?;
1207 ContentOperation::ShowText(text)
1208 }
1209 "TJ" => {
1210 let array = self.pop_array(operands)?;
1211 let elements = self.parse_text_array(array)?;
1212 ContentOperation::ShowTextArray(elements)
1213 }
1214 "'" => {
1215 let text = self.pop_string(operands)?;
1216 ContentOperation::NextLineShowText(text)
1217 }
1218 "\"" => {
1219 let text = self.pop_string(operands)?;
1225 let ac = self.pop_number(operands)?;
1226 let aw = self.pop_number(operands)?;
1227 ContentOperation::SetSpacingNextLineShowText(aw, ac, text)
1228 }
1229
1230 "q" => ContentOperation::SaveGraphicsState,
1232 "Q" => ContentOperation::RestoreGraphicsState,
1233 "cm" => {
1234 let f = self.pop_number(operands)?;
1235 let e = self.pop_number(operands)?;
1236 let d = self.pop_number(operands)?;
1237 let c = self.pop_number(operands)?;
1238 let b = self.pop_number(operands)?;
1239 let a = self.pop_number(operands)?;
1240 ContentOperation::SetTransformMatrix(a, b, c, d, e, f)
1241 }
1242 "w" => {
1243 let width = self.pop_number(operands)?;
1244 ContentOperation::SetLineWidth(width)
1245 }
1246 "J" => {
1247 let cap = self.pop_integer(operands)?;
1248 ContentOperation::SetLineCap(cap)
1249 }
1250 "j" => {
1251 let join = self.pop_integer(operands)?;
1252 ContentOperation::SetLineJoin(join)
1253 }
1254 "M" => {
1255 let limit = self.pop_number(operands)?;
1256 ContentOperation::SetMiterLimit(limit)
1257 }
1258 "d" => {
1259 let phase = self.pop_number(operands)?;
1260 let array = self.pop_array(operands)?;
1261 let pattern = self.parse_dash_array(array)?;
1262 ContentOperation::SetDashPattern(pattern, phase)
1263 }
1264 "ri" => {
1265 let intent = self.pop_name(operands)?;
1266 ContentOperation::SetIntent(intent)
1267 }
1268 "i" => {
1269 let flatness = self.pop_number(operands)?;
1270 ContentOperation::SetFlatness(flatness)
1271 }
1272 "gs" => {
1273 let name = self.pop_name(operands)?;
1274 ContentOperation::SetGraphicsStateParams(name)
1275 }
1276
1277 "m" => {
1279 let y = self.pop_number(operands)?;
1280 let x = self.pop_number(operands)?;
1281 ContentOperation::MoveTo(x, y)
1282 }
1283 "l" => {
1284 let y = self.pop_number(operands)?;
1285 let x = self.pop_number(operands)?;
1286 ContentOperation::LineTo(x, y)
1287 }
1288 "c" => {
1289 let y3 = self.pop_number(operands)?;
1290 let x3 = self.pop_number(operands)?;
1291 let y2 = self.pop_number(operands)?;
1292 let x2 = self.pop_number(operands)?;
1293 let y1 = self.pop_number(operands)?;
1294 let x1 = self.pop_number(operands)?;
1295 ContentOperation::CurveTo(x1, y1, x2, y2, x3, y3)
1296 }
1297 "v" => {
1298 let y3 = self.pop_number(operands)?;
1299 let x3 = self.pop_number(operands)?;
1300 let y2 = self.pop_number(operands)?;
1301 let x2 = self.pop_number(operands)?;
1302 ContentOperation::CurveToV(x2, y2, x3, y3)
1303 }
1304 "y" => {
1305 let y3 = self.pop_number(operands)?;
1306 let x3 = self.pop_number(operands)?;
1307 let y1 = self.pop_number(operands)?;
1308 let x1 = self.pop_number(operands)?;
1309 ContentOperation::CurveToY(x1, y1, x3, y3)
1310 }
1311 "h" => ContentOperation::ClosePath,
1312 "re" => {
1313 let height = self.pop_number(operands)?;
1314 let width = self.pop_number(operands)?;
1315 let y = self.pop_number(operands)?;
1316 let x = self.pop_number(operands)?;
1317 ContentOperation::Rectangle(x, y, width, height)
1318 }
1319
1320 "S" => ContentOperation::Stroke,
1322 "s" => ContentOperation::CloseStroke,
1323 "f" | "F" => ContentOperation::Fill,
1324 "f*" => ContentOperation::FillEvenOdd,
1325 "B" => ContentOperation::FillStroke,
1326 "B*" => ContentOperation::FillStrokeEvenOdd,
1327 "b" => ContentOperation::CloseFillStroke,
1328 "b*" => ContentOperation::CloseFillStrokeEvenOdd,
1329 "n" => ContentOperation::EndPath,
1330
1331 "W" => ContentOperation::Clip,
1333 "W*" => ContentOperation::ClipEvenOdd,
1334
1335 "CS" => {
1337 let name = self.pop_name(operands)?;
1338 ContentOperation::SetStrokingColorSpace(name)
1339 }
1340 "cs" => {
1341 let name = self.pop_name(operands)?;
1342 ContentOperation::SetNonStrokingColorSpace(name)
1343 }
1344 "SC" | "SCN" => {
1345 let components = self.pop_color_components(operands)?;
1346 ContentOperation::SetStrokingColor(components)
1347 }
1348 "sc" | "scn" => {
1349 let components = self.pop_color_components(operands)?;
1350 ContentOperation::SetNonStrokingColor(components)
1351 }
1352 "G" => {
1353 let gray = self.pop_number(operands)?;
1354 ContentOperation::SetStrokingGray(gray)
1355 }
1356 "g" => {
1357 let gray = self.pop_number(operands)?;
1358 ContentOperation::SetNonStrokingGray(gray)
1359 }
1360 "RG" => {
1361 let b = self.pop_number(operands)?;
1362 let g = self.pop_number(operands)?;
1363 let r = self.pop_number(operands)?;
1364 ContentOperation::SetStrokingRGB(r, g, b)
1365 }
1366 "rg" => {
1367 let b = self.pop_number(operands)?;
1368 let g = self.pop_number(operands)?;
1369 let r = self.pop_number(operands)?;
1370 ContentOperation::SetNonStrokingRGB(r, g, b)
1371 }
1372 "K" => {
1373 let k = self.pop_number(operands)?;
1374 let y = self.pop_number(operands)?;
1375 let m = self.pop_number(operands)?;
1376 let c = self.pop_number(operands)?;
1377 ContentOperation::SetStrokingCMYK(c, m, y, k)
1378 }
1379 "k" => {
1380 let k = self.pop_number(operands)?;
1381 let y = self.pop_number(operands)?;
1382 let m = self.pop_number(operands)?;
1383 let c = self.pop_number(operands)?;
1384 ContentOperation::SetNonStrokingCMYK(c, m, y, k)
1385 }
1386
1387 "sh" => {
1389 let name = self.pop_name(operands)?;
1390 ContentOperation::ShadingFill(name)
1391 }
1392
1393 "Do" => {
1395 let name = self.pop_name(operands)?;
1396 ContentOperation::PaintXObject(name)
1397 }
1398
1399 "BMC" => {
1401 let tag = self.pop_name(operands)?;
1402 ContentOperation::BeginMarkedContent(tag)
1403 }
1404 "BDC" => {
1405 let props = self.pop_dict_or_name(operands)?;
1406 let tag = self.pop_name(operands)?;
1407 ContentOperation::BeginMarkedContentWithProps(tag, props)
1408 }
1409 "EMC" => ContentOperation::EndMarkedContent,
1410 "MP" => {
1411 let tag = self.pop_name(operands)?;
1412 ContentOperation::DefineMarkedContentPoint(tag)
1413 }
1414 "DP" => {
1415 let props = self.pop_dict_or_name(operands)?;
1416 let tag = self.pop_name(operands)?;
1417 ContentOperation::DefineMarkedContentPointWithProps(tag, props)
1418 }
1419
1420 "BX" => ContentOperation::BeginCompatibility,
1422 "EX" => ContentOperation::EndCompatibility,
1423
1424 "BI" => {
1426 operands.clear(); self.parse_inline_image()?
1428 }
1429
1430 _ => {
1431 return Err(ParseError::SyntaxError {
1432 position: self.position,
1433 message: format!("Unknown operator: {op}"),
1434 });
1435 }
1436 };
1437
1438 operands.clear(); Ok(operator)
1440 }
1441
1442 fn pop_number(&self, operands: &mut Vec<Token>) -> ParseResult<f32> {
1444 match operands.pop() {
1445 Some(Token::Number(n)) => Ok(n),
1446 Some(Token::Integer(i)) => Ok(i as f32),
1447 _ => Err(ParseError::SyntaxError {
1448 position: self.position,
1449 message: "Expected number operand".to_string(),
1450 }),
1451 }
1452 }
1453
1454 fn pop_integer(&self, operands: &mut Vec<Token>) -> ParseResult<i32> {
1455 match operands.pop() {
1456 Some(Token::Integer(i)) => Ok(i),
1457 _ => Err(ParseError::SyntaxError {
1458 position: self.position,
1459 message: "Expected integer operand".to_string(),
1460 }),
1461 }
1462 }
1463
1464 fn pop_name(&self, operands: &mut Vec<Token>) -> ParseResult<String> {
1465 match operands.pop() {
1466 Some(Token::Name(n)) => Ok(n),
1467 _ => Err(ParseError::SyntaxError {
1468 position: self.position,
1469 message: "Expected name operand".to_string(),
1470 }),
1471 }
1472 }
1473
1474 fn pop_string(&self, operands: &mut Vec<Token>) -> ParseResult<Vec<u8>> {
1475 match operands.pop() {
1476 Some(Token::String(s)) => Ok(s),
1477 Some(Token::HexString(s)) => Ok(s),
1478 _ => Err(ParseError::SyntaxError {
1479 position: self.position,
1480 message: "Expected string operand".to_string(),
1481 }),
1482 }
1483 }
1484
1485 fn pop_array(&self, operands: &mut Vec<Token>) -> ParseResult<Vec<Token>> {
1486 let has_array_end = matches!(operands.last(), Some(Token::ArrayEnd));
1488 if has_array_end {
1489 operands.pop(); }
1491
1492 let mut array = Vec::new();
1493 let mut found_start = false;
1494
1495 while let Some(token) = operands.pop() {
1497 match token {
1498 Token::ArrayStart => {
1499 found_start = true;
1500 break;
1501 }
1502 Token::ArrayEnd => {
1503 continue;
1505 }
1506 _ => array.push(token),
1507 }
1508 }
1509
1510 if !found_start {
1511 return Err(ParseError::SyntaxError {
1512 position: self.position,
1513 message: "Expected array".to_string(),
1514 });
1515 }
1516
1517 array.reverse(); Ok(array)
1519 }
1520
1521 fn pop_dict_or_name(&self, operands: &mut Vec<Token>) -> ParseResult<MarkedContentProps> {
1522 let token = operands.pop().ok_or_else(|| ParseError::SyntaxError {
1523 position: self.position,
1524 message: "Expected dict or name operand for BDC/DP".to_string(),
1525 })?;
1526
1527 match token {
1528 Token::Name(name) => Ok(MarkedContentProps::ResourceRef(name)),
1529 Token::DictEnd => {
1530 let mut map: HashMap<String, MarkedContentValue> = HashMap::new();
1534 loop {
1535 let next = operands.pop().ok_or_else(|| ParseError::SyntaxError {
1536 position: self.position,
1537 message: "Unterminated inline dict in BDC/DP".to_string(),
1538 })?;
1539 if matches!(next, Token::DictStart) {
1540 break;
1541 }
1542 let value = Self::token_to_mc_value(next, operands)?;
1543 let key = match operands.pop() {
1544 Some(Token::Name(k)) => k,
1545 Some(other) => {
1546 return Err(ParseError::SyntaxError {
1547 position: self.position,
1548 message: format!(
1549 "Expected Name as inline dict key, got {:?}",
1550 other
1551 ),
1552 });
1553 }
1554 None => {
1555 return Err(ParseError::SyntaxError {
1556 position: self.position,
1557 message: "Unterminated inline dict (missing key)".to_string(),
1558 });
1559 }
1560 };
1561 map.insert(key, value);
1562 }
1563 Ok(MarkedContentProps::Inline(map))
1564 }
1565 other => Err(ParseError::SyntaxError {
1566 position: self.position,
1567 message: format!("Expected name or inline dict for BDC/DP, got {:?}", other),
1568 }),
1569 }
1570 }
1571
1572 fn token_to_mc_value(
1576 token: Token,
1577 operands: &mut Vec<Token>,
1578 ) -> ParseResult<MarkedContentValue> {
1579 match token {
1580 Token::String(b) | Token::HexString(b) => Ok(MarkedContentValue::String(b)),
1581 Token::Integer(i) => Ok(MarkedContentValue::Integer(i as i64)),
1582 Token::Number(f) => Ok(MarkedContentValue::Real(f as f64)),
1583 Token::Name(n) => Ok(MarkedContentValue::Name(n)),
1584 Token::ArrayEnd => {
1585 let mut items: Vec<MarkedContentValue> = Vec::new();
1586 loop {
1587 let next = operands.pop().ok_or_else(|| ParseError::SyntaxError {
1588 position: 0,
1589 message: "Unterminated array in marked-content props".to_string(),
1590 })?;
1591 if matches!(next, Token::ArrayStart) {
1592 break;
1593 }
1594 items.push(Self::token_to_mc_value(next, operands)?);
1595 }
1596 items.reverse();
1597 Ok(MarkedContentValue::Array(items))
1598 }
1599 Token::DictEnd => {
1600 let mut nested: HashMap<String, MarkedContentValue> = HashMap::new();
1601 loop {
1602 let next = operands.pop().ok_or_else(|| ParseError::SyntaxError {
1603 position: 0,
1604 message: "Unterminated nested dict in marked-content props".to_string(),
1605 })?;
1606 if matches!(next, Token::DictStart) {
1607 break;
1608 }
1609 let value = Self::token_to_mc_value(next, operands)?;
1610 let key = match operands.pop() {
1611 Some(Token::Name(k)) => k,
1612 _ => {
1613 return Err(ParseError::SyntaxError {
1614 position: 0,
1615 message: "Expected name key in nested dict".to_string(),
1616 });
1617 }
1618 };
1619 nested.insert(key, value);
1620 }
1621 Ok(MarkedContentValue::Dict(nested))
1622 }
1623 other => Err(ParseError::SyntaxError {
1624 position: 0,
1625 message: format!("Unexpected token type in marked-content value: {:?}", other),
1626 }),
1627 }
1628 }
1629
1630 fn pop_color_components(&self, operands: &mut Vec<Token>) -> ParseResult<Vec<f32>> {
1631 let mut components = Vec::new();
1632
1633 while let Some(token) = operands.last() {
1635 match token {
1636 Token::Number(n) => {
1637 components.push(*n);
1638 operands.pop();
1639 }
1640 Token::Integer(i) => {
1641 components.push(*i as f32);
1642 operands.pop();
1643 }
1644 _ => break,
1645 }
1646 }
1647
1648 components.reverse();
1649 Ok(components)
1650 }
1651
1652 fn parse_text_array(&self, tokens: Vec<Token>) -> ParseResult<Vec<TextElement>> {
1653 let mut elements = Vec::new();
1654
1655 for token in tokens {
1656 match token {
1657 Token::String(s) | Token::HexString(s) => {
1658 elements.push(TextElement::Text(s));
1659 }
1660 Token::Number(n) => {
1661 elements.push(TextElement::Spacing(n));
1662 }
1663 Token::Integer(i) => {
1664 elements.push(TextElement::Spacing(i as f32));
1665 }
1666 _ => {
1667 return Err(ParseError::SyntaxError {
1668 position: self.position,
1669 message: "Invalid element in text array".to_string(),
1670 });
1671 }
1672 }
1673 }
1674
1675 Ok(elements)
1676 }
1677
1678 fn parse_dash_array(&self, tokens: Vec<Token>) -> ParseResult<Vec<f32>> {
1679 let mut pattern = Vec::new();
1680
1681 for token in tokens {
1682 match token {
1683 Token::Number(n) => pattern.push(n),
1684 Token::Integer(i) => pattern.push(i as f32),
1685 _ => {
1686 return Err(ParseError::SyntaxError {
1687 position: self.position,
1688 message: "Invalid element in dash array".to_string(),
1689 });
1690 }
1691 }
1692 }
1693
1694 Ok(pattern)
1695 }
1696
1697 fn parse_inline_image(&mut self) -> ParseResult<ContentOperation> {
1698 let mut params = HashMap::new();
1700
1701 while self.position < self.tokens.len() {
1702 if let Token::Operator(op) = &self.tokens[self.position] {
1704 if op == "ID" {
1705 self.position += 1;
1706 break;
1707 }
1708 }
1709
1710 if let Token::Name(key) = &self.tokens[self.position] {
1715 self.position += 1;
1716 if self.position >= self.tokens.len() {
1717 break;
1718 }
1719
1720 let value = match &self.tokens[self.position] {
1722 Token::Integer(n) => Object::Integer(*n as i64),
1723 Token::Number(n) => Object::Real(*n as f64),
1724 Token::Name(s) => Object::Name(expand_inline_name(s)),
1725 Token::String(s) => Object::String(String::from_utf8_lossy(s).to_string()),
1726 Token::HexString(s) => Object::String(String::from_utf8_lossy(s).to_string()),
1727 _ => Object::Null,
1728 };
1729
1730 let full_key = expand_inline_key(key);
1732 params.insert(full_key, value);
1733 self.position += 1;
1734 } else {
1735 self.position += 1;
1736 }
1737 }
1738
1739 let data = if self.position < self.tokens.len() {
1742 if let Token::InlineImageData(bytes) = &self.tokens[self.position] {
1743 let d = bytes.clone();
1744 self.position += 1;
1745 d
1746 } else {
1747 self.collect_inline_image_data_from_tokens()?
1749 }
1750 } else {
1751 Vec::new()
1752 };
1753
1754 Ok(ContentOperation::InlineImage { params, data })
1755 }
1756
1757 fn collect_inline_image_data_from_tokens(&mut self) -> ParseResult<Vec<u8>> {
1760 let mut data = Vec::new();
1761 while self.position < self.tokens.len() {
1762 if let Token::Operator(op) = &self.tokens[self.position] {
1763 if op == "EI" {
1764 self.position += 1;
1765 break;
1766 }
1767 }
1768 match &self.tokens[self.position] {
1769 Token::String(bytes) | Token::HexString(bytes) => {
1770 data.extend_from_slice(bytes);
1771 }
1772 Token::Integer(n) => data.extend_from_slice(n.to_string().as_bytes()),
1773 Token::Number(n) => data.extend_from_slice(n.to_string().as_bytes()),
1774 Token::Name(s) | Token::Operator(s) => data.extend_from_slice(s.as_bytes()),
1775 _ => {}
1776 }
1777 self.position += 1;
1778 }
1779 Ok(data)
1780 }
1781}
1782
1783fn expand_inline_key(key: &str) -> String {
1785 match key {
1786 "W" => "Width".to_string(),
1787 "H" => "Height".to_string(),
1788 "CS" | "ColorSpace" => "ColorSpace".to_string(),
1789 "BPC" | "BitsPerComponent" => "BitsPerComponent".to_string(),
1790 "F" => "Filter".to_string(),
1791 "DP" | "DecodeParms" => "DecodeParms".to_string(),
1792 "IM" => "ImageMask".to_string(),
1793 "I" => "Interpolate".to_string(),
1794 "Intent" => "Intent".to_string(),
1795 "D" => "Decode".to_string(),
1796 _ => key.to_string(),
1797 }
1798}
1799
1800fn expand_inline_name(name: &str) -> String {
1802 match name {
1803 "G" => "DeviceGray".to_string(),
1804 "RGB" => "DeviceRGB".to_string(),
1805 "CMYK" => "DeviceCMYK".to_string(),
1806 "I" => "Indexed".to_string(),
1807 "AHx" => "ASCIIHexDecode".to_string(),
1808 "A85" => "ASCII85Decode".to_string(),
1809 "LZW" => "LZWDecode".to_string(),
1810 "Fl" => "FlateDecode".to_string(),
1811 "RL" => "RunLengthDecode".to_string(),
1812 "DCT" => "DCTDecode".to_string(),
1813 "CCF" => "CCITTFaxDecode".to_string(),
1814 _ => name.to_string(),
1815 }
1816}
1817
1818#[cfg(test)]
1819mod tests {
1820 use super::*;
1821
1822 #[test]
1823 fn test_tokenize_numbers() {
1824 let input = b"123 -45 3.14159 -0.5 .5";
1825 let mut tokenizer = ContentTokenizer::new(input);
1826
1827 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Integer(123)));
1828 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Integer(-45)));
1829 assert_eq!(
1830 tokenizer.next_token().unwrap(),
1831 Some(Token::Number(3.14159))
1832 );
1833 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Number(-0.5)));
1834 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Number(0.5)));
1835 assert_eq!(tokenizer.next_token().unwrap(), None);
1836 }
1837
1838 #[test]
1839 fn test_tokenize_strings() {
1840 let input = b"(Hello World) (Hello\\nWorld) (Nested (paren))";
1841 let mut tokenizer = ContentTokenizer::new(input);
1842
1843 assert_eq!(
1844 tokenizer.next_token().unwrap(),
1845 Some(Token::String(b"Hello World".to_vec()))
1846 );
1847 assert_eq!(
1848 tokenizer.next_token().unwrap(),
1849 Some(Token::String(b"Hello\nWorld".to_vec()))
1850 );
1851 assert_eq!(
1852 tokenizer.next_token().unwrap(),
1853 Some(Token::String(b"Nested (paren)".to_vec()))
1854 );
1855 }
1856
1857 #[test]
1858 fn test_tokenize_hex_strings() {
1859 let input = b"<48656C6C6F> <48 65 6C 6C 6F>";
1860 let mut tokenizer = ContentTokenizer::new(input);
1861
1862 assert_eq!(
1863 tokenizer.next_token().unwrap(),
1864 Some(Token::HexString(b"Hello".to_vec()))
1865 );
1866 assert_eq!(
1867 tokenizer.next_token().unwrap(),
1868 Some(Token::HexString(b"Hello".to_vec()))
1869 );
1870 }
1871
1872 #[test]
1873 fn test_tokenize_names() {
1874 let input = b"/Name /Name#20with#20spaces /A#42C";
1875 let mut tokenizer = ContentTokenizer::new(input);
1876
1877 assert_eq!(
1878 tokenizer.next_token().unwrap(),
1879 Some(Token::Name("Name".to_string()))
1880 );
1881 assert_eq!(
1882 tokenizer.next_token().unwrap(),
1883 Some(Token::Name("Name with spaces".to_string()))
1884 );
1885 assert_eq!(
1886 tokenizer.next_token().unwrap(),
1887 Some(Token::Name("ABC".to_string()))
1888 );
1889 }
1890
1891 #[test]
1892 fn test_tokenize_operators() {
1893 let input = b"BT Tj ET q Q";
1894 let mut tokenizer = ContentTokenizer::new(input);
1895
1896 assert_eq!(
1897 tokenizer.next_token().unwrap(),
1898 Some(Token::Operator("BT".to_string()))
1899 );
1900 assert_eq!(
1901 tokenizer.next_token().unwrap(),
1902 Some(Token::Operator("Tj".to_string()))
1903 );
1904 assert_eq!(
1905 tokenizer.next_token().unwrap(),
1906 Some(Token::Operator("ET".to_string()))
1907 );
1908 assert_eq!(
1909 tokenizer.next_token().unwrap(),
1910 Some(Token::Operator("q".to_string()))
1911 );
1912 assert_eq!(
1913 tokenizer.next_token().unwrap(),
1914 Some(Token::Operator("Q".to_string()))
1915 );
1916 }
1917
1918 #[test]
1919 fn test_parse_text_operators() {
1920 let content = b"BT /F1 12 Tf 100 200 Td (Hello World) Tj ET";
1921 let operators = ContentParser::parse(content).unwrap();
1922
1923 assert_eq!(operators.len(), 5);
1924 assert_eq!(operators[0], ContentOperation::BeginText);
1925 assert_eq!(
1926 operators[1],
1927 ContentOperation::SetFont("F1".to_string(), 12.0)
1928 );
1929 assert_eq!(operators[2], ContentOperation::MoveText(100.0, 200.0));
1930 assert_eq!(
1931 operators[3],
1932 ContentOperation::ShowText(b"Hello World".to_vec())
1933 );
1934 assert_eq!(operators[4], ContentOperation::EndText);
1935 }
1936
1937 #[test]
1938 fn test_parse_graphics_operators() {
1939 let content = b"q 1 0 0 1 50 50 cm 2 w 0 0 100 100 re S Q";
1940 let operators = ContentParser::parse(content).unwrap();
1941
1942 assert_eq!(operators.len(), 6);
1943 assert_eq!(operators[0], ContentOperation::SaveGraphicsState);
1944 assert_eq!(
1945 operators[1],
1946 ContentOperation::SetTransformMatrix(1.0, 0.0, 0.0, 1.0, 50.0, 50.0)
1947 );
1948 assert_eq!(operators[2], ContentOperation::SetLineWidth(2.0));
1949 assert_eq!(
1950 operators[3],
1951 ContentOperation::Rectangle(0.0, 0.0, 100.0, 100.0)
1952 );
1953 assert_eq!(operators[4], ContentOperation::Stroke);
1954 assert_eq!(operators[5], ContentOperation::RestoreGraphicsState);
1955 }
1956
1957 #[test]
1958 fn test_parse_color_operators() {
1959 let content = b"0.5 g 1 0 0 rg 0 0 0 1 k";
1960 let operators = ContentParser::parse(content).unwrap();
1961
1962 assert_eq!(operators.len(), 3);
1963 assert_eq!(operators[0], ContentOperation::SetNonStrokingGray(0.5));
1964 assert_eq!(
1965 operators[1],
1966 ContentOperation::SetNonStrokingRGB(1.0, 0.0, 0.0)
1967 );
1968 assert_eq!(
1969 operators[2],
1970 ContentOperation::SetNonStrokingCMYK(0.0, 0.0, 0.0, 1.0)
1971 );
1972 }
1973
1974 mod comprehensive_tests {
1976 use super::*;
1977
1978 #[test]
1979 fn test_all_text_operators() {
1980 let content = b"BT 5 Tc 10 Tw 120 Tz 15 TL /F1 12 Tf 1 Tr 5 Ts 100 200 Td 50 150 TD T* (Hello) Tj ET";
1982 let operators = ContentParser::parse(content).unwrap();
1983
1984 assert_eq!(operators[0], ContentOperation::BeginText);
1985 assert_eq!(operators[1], ContentOperation::SetCharSpacing(5.0));
1986 assert_eq!(operators[2], ContentOperation::SetWordSpacing(10.0));
1987 assert_eq!(operators[3], ContentOperation::SetHorizontalScaling(120.0));
1988 assert_eq!(operators[4], ContentOperation::SetLeading(15.0));
1989 assert_eq!(
1990 operators[5],
1991 ContentOperation::SetFont("F1".to_string(), 12.0)
1992 );
1993 assert_eq!(operators[6], ContentOperation::SetTextRenderMode(1));
1994 assert_eq!(operators[7], ContentOperation::SetTextRise(5.0));
1995 assert_eq!(operators[8], ContentOperation::MoveText(100.0, 200.0));
1996 assert_eq!(
1997 operators[9],
1998 ContentOperation::MoveTextSetLeading(50.0, 150.0)
1999 );
2000 assert_eq!(operators[10], ContentOperation::NextLine);
2001 assert_eq!(operators[11], ContentOperation::ShowText(b"Hello".to_vec()));
2002 assert_eq!(operators[12], ContentOperation::EndText);
2003 }
2004
2005 #[test]
2006 fn test_all_graphics_state_operators() {
2007 let content = b"q Q 1 0 0 1 50 50 cm 2 w 1 J 2 j 10 M /GS1 gs 0.5 i /Perceptual ri";
2009 let operators = ContentParser::parse(content).unwrap();
2010
2011 assert_eq!(operators[0], ContentOperation::SaveGraphicsState);
2012 assert_eq!(operators[1], ContentOperation::RestoreGraphicsState);
2013 assert_eq!(
2014 operators[2],
2015 ContentOperation::SetTransformMatrix(1.0, 0.0, 0.0, 1.0, 50.0, 50.0)
2016 );
2017 assert_eq!(operators[3], ContentOperation::SetLineWidth(2.0));
2018 assert_eq!(operators[4], ContentOperation::SetLineCap(1));
2019 assert_eq!(operators[5], ContentOperation::SetLineJoin(2));
2020 assert_eq!(operators[6], ContentOperation::SetMiterLimit(10.0));
2021 assert_eq!(
2022 operators[7],
2023 ContentOperation::SetGraphicsStateParams("GS1".to_string())
2024 );
2025 assert_eq!(operators[8], ContentOperation::SetFlatness(0.5));
2026 assert_eq!(
2027 operators[9],
2028 ContentOperation::SetIntent("Perceptual".to_string())
2029 );
2030 }
2031
2032 #[test]
2033 fn test_all_path_construction_operators() {
2034 let content = b"100 200 m 150 200 l 200 200 250 250 300 200 c 250 180 300 200 v 200 180 300 200 y h 50 50 100 100 re";
2035 let operators = ContentParser::parse(content).unwrap();
2036
2037 assert_eq!(operators[0], ContentOperation::MoveTo(100.0, 200.0));
2038 assert_eq!(operators[1], ContentOperation::LineTo(150.0, 200.0));
2039 assert_eq!(
2040 operators[2],
2041 ContentOperation::CurveTo(200.0, 200.0, 250.0, 250.0, 300.0, 200.0)
2042 );
2043 assert_eq!(
2044 operators[3],
2045 ContentOperation::CurveToV(250.0, 180.0, 300.0, 200.0)
2046 );
2047 assert_eq!(
2048 operators[4],
2049 ContentOperation::CurveToY(200.0, 180.0, 300.0, 200.0)
2050 );
2051 assert_eq!(operators[5], ContentOperation::ClosePath);
2052 assert_eq!(
2053 operators[6],
2054 ContentOperation::Rectangle(50.0, 50.0, 100.0, 100.0)
2055 );
2056 }
2057
2058 #[test]
2059 fn test_all_path_painting_operators() {
2060 let content = b"S s f F f* B B* b b* n W W*";
2061 let operators = ContentParser::parse(content).unwrap();
2062
2063 assert_eq!(operators[0], ContentOperation::Stroke);
2064 assert_eq!(operators[1], ContentOperation::CloseStroke);
2065 assert_eq!(operators[2], ContentOperation::Fill);
2066 assert_eq!(operators[3], ContentOperation::Fill); assert_eq!(operators[4], ContentOperation::FillEvenOdd);
2068 assert_eq!(operators[5], ContentOperation::FillStroke);
2069 assert_eq!(operators[6], ContentOperation::FillStrokeEvenOdd);
2070 assert_eq!(operators[7], ContentOperation::CloseFillStroke);
2071 assert_eq!(operators[8], ContentOperation::CloseFillStrokeEvenOdd);
2072 assert_eq!(operators[9], ContentOperation::EndPath);
2073 assert_eq!(operators[10], ContentOperation::Clip);
2074 assert_eq!(operators[11], ContentOperation::ClipEvenOdd);
2075 }
2076
2077 #[test]
2078 fn test_all_color_operators() {
2079 let content = b"/DeviceRGB CS /DeviceGray cs 0.7 G 0.4 g 1 0 0 RG 0 1 0 rg 0 0 0 1 K 0.2 0.3 0.4 0.5 k /Shade1 sh";
2081 let operators = ContentParser::parse(content).unwrap();
2082
2083 assert_eq!(
2084 operators[0],
2085 ContentOperation::SetStrokingColorSpace("DeviceRGB".to_string())
2086 );
2087 assert_eq!(
2088 operators[1],
2089 ContentOperation::SetNonStrokingColorSpace("DeviceGray".to_string())
2090 );
2091 assert_eq!(operators[2], ContentOperation::SetStrokingGray(0.7));
2092 assert_eq!(operators[3], ContentOperation::SetNonStrokingGray(0.4));
2093 assert_eq!(
2094 operators[4],
2095 ContentOperation::SetStrokingRGB(1.0, 0.0, 0.0)
2096 );
2097 assert_eq!(
2098 operators[5],
2099 ContentOperation::SetNonStrokingRGB(0.0, 1.0, 0.0)
2100 );
2101 assert_eq!(
2102 operators[6],
2103 ContentOperation::SetStrokingCMYK(0.0, 0.0, 0.0, 1.0)
2104 );
2105 assert_eq!(
2106 operators[7],
2107 ContentOperation::SetNonStrokingCMYK(0.2, 0.3, 0.4, 0.5)
2108 );
2109 assert_eq!(
2110 operators[8],
2111 ContentOperation::ShadingFill("Shade1".to_string())
2112 );
2113 }
2114
2115 #[test]
2116 fn test_xobject_and_marked_content_operators() {
2117 let content = b"/Image1 Do /MC1 BMC EMC /MP1 MP BX EX";
2119 let operators = ContentParser::parse(content).unwrap();
2120
2121 assert_eq!(
2122 operators[0],
2123 ContentOperation::PaintXObject("Image1".to_string())
2124 );
2125 assert_eq!(
2126 operators[1],
2127 ContentOperation::BeginMarkedContent("MC1".to_string())
2128 );
2129 assert_eq!(operators[2], ContentOperation::EndMarkedContent);
2130 assert_eq!(
2131 operators[3],
2132 ContentOperation::DefineMarkedContentPoint("MP1".to_string())
2133 );
2134 assert_eq!(operators[4], ContentOperation::BeginCompatibility);
2135 assert_eq!(operators[5], ContentOperation::EndCompatibility);
2136 }
2137
2138 #[test]
2139 fn test_complex_content_stream() {
2140 let content = b"q 0.5 0 0 0.5 100 100 cm BT /F1 12 Tf 0 0 Td (Complex) Tj ET Q";
2141 let operators = ContentParser::parse(content).unwrap();
2142
2143 assert_eq!(operators.len(), 8);
2144 assert_eq!(operators[0], ContentOperation::SaveGraphicsState);
2145 assert_eq!(
2146 operators[1],
2147 ContentOperation::SetTransformMatrix(0.5, 0.0, 0.0, 0.5, 100.0, 100.0)
2148 );
2149 assert_eq!(operators[2], ContentOperation::BeginText);
2150 assert_eq!(
2151 operators[3],
2152 ContentOperation::SetFont("F1".to_string(), 12.0)
2153 );
2154 assert_eq!(operators[4], ContentOperation::MoveText(0.0, 0.0));
2155 assert_eq!(
2156 operators[5],
2157 ContentOperation::ShowText(b"Complex".to_vec())
2158 );
2159 assert_eq!(operators[6], ContentOperation::EndText);
2160 assert_eq!(operators[7], ContentOperation::RestoreGraphicsState);
2161 }
2162
2163 #[test]
2164 fn test_tokenizer_whitespace_handling() {
2165 let input = b" \t\n\r BT \t\n /F1 12.5 \t Tf \n\r ET ";
2166 let mut tokenizer = ContentTokenizer::new(input);
2167
2168 assert_eq!(
2169 tokenizer.next_token().unwrap(),
2170 Some(Token::Operator("BT".to_string()))
2171 );
2172 assert_eq!(
2173 tokenizer.next_token().unwrap(),
2174 Some(Token::Name("F1".to_string()))
2175 );
2176 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Number(12.5)));
2177 assert_eq!(
2178 tokenizer.next_token().unwrap(),
2179 Some(Token::Operator("Tf".to_string()))
2180 );
2181 assert_eq!(
2182 tokenizer.next_token().unwrap(),
2183 Some(Token::Operator("ET".to_string()))
2184 );
2185 assert_eq!(tokenizer.next_token().unwrap(), None);
2186 }
2187
2188 #[test]
2189 fn test_tokenizer_edge_cases() {
2190 let input = b"0 .5 -.5 +.5 123. .123 1.23 -1.23";
2192 let mut tokenizer = ContentTokenizer::new(input);
2193
2194 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Integer(0)));
2195 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Number(0.5)));
2196 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Number(-0.5)));
2197 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Number(0.5)));
2198 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Number(123.0)));
2199 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Number(0.123)));
2200 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Number(1.23)));
2201 assert_eq!(tokenizer.next_token().unwrap(), Some(Token::Number(-1.23)));
2202 }
2203
2204 #[test]
2205 fn test_string_parsing_edge_cases() {
2206 let input = b"(Simple) (With\\\\backslash) (With\\)paren) (With\\newline) (With\\ttab) (With\\rcarriage) (With\\bbackspace) (With\\fformfeed) (With\\(leftparen) (With\\)rightparen) (With\\377octal) (With\\dddoctal)";
2207 let mut tokenizer = ContentTokenizer::new(input);
2208
2209 assert_eq!(
2210 tokenizer.next_token().unwrap(),
2211 Some(Token::String(b"Simple".to_vec()))
2212 );
2213 assert_eq!(
2214 tokenizer.next_token().unwrap(),
2215 Some(Token::String(b"With\\backslash".to_vec()))
2216 );
2217 assert_eq!(
2218 tokenizer.next_token().unwrap(),
2219 Some(Token::String(b"With)paren".to_vec()))
2220 );
2221 assert_eq!(
2222 tokenizer.next_token().unwrap(),
2223 Some(Token::String(b"With\newline".to_vec()))
2224 );
2225 assert_eq!(
2226 tokenizer.next_token().unwrap(),
2227 Some(Token::String(b"With\ttab".to_vec()))
2228 );
2229 assert_eq!(
2230 tokenizer.next_token().unwrap(),
2231 Some(Token::String(b"With\rcarriage".to_vec()))
2232 );
2233 assert_eq!(
2234 tokenizer.next_token().unwrap(),
2235 Some(Token::String(b"With\x08backspace".to_vec()))
2236 );
2237 assert_eq!(
2238 tokenizer.next_token().unwrap(),
2239 Some(Token::String(b"With\x0Cformfeed".to_vec()))
2240 );
2241 assert_eq!(
2242 tokenizer.next_token().unwrap(),
2243 Some(Token::String(b"With(leftparen".to_vec()))
2244 );
2245 assert_eq!(
2246 tokenizer.next_token().unwrap(),
2247 Some(Token::String(b"With)rightparen".to_vec()))
2248 );
2249 }
2250
2251 #[test]
2252 fn test_hex_string_parsing() {
2253 let input = b"<48656C6C6F> <48 65 6C 6C 6F> <48656C6C6F57> <48656C6C6F5>";
2254 let mut tokenizer = ContentTokenizer::new(input);
2255
2256 assert_eq!(
2257 tokenizer.next_token().unwrap(),
2258 Some(Token::HexString(b"Hello".to_vec()))
2259 );
2260 assert_eq!(
2261 tokenizer.next_token().unwrap(),
2262 Some(Token::HexString(b"Hello".to_vec()))
2263 );
2264 assert_eq!(
2265 tokenizer.next_token().unwrap(),
2266 Some(Token::HexString(b"HelloW".to_vec()))
2267 );
2268 assert_eq!(
2269 tokenizer.next_token().unwrap(),
2270 Some(Token::HexString(b"Hello\x50".to_vec()))
2271 );
2272 }
2273
2274 #[test]
2275 fn test_name_parsing_edge_cases() {
2276 let input = b"/Name /Name#20with#20spaces /Name#23with#23hash /Name#2Fwith#2Fslash /#45mptyName";
2277 let mut tokenizer = ContentTokenizer::new(input);
2278
2279 assert_eq!(
2280 tokenizer.next_token().unwrap(),
2281 Some(Token::Name("Name".to_string()))
2282 );
2283 assert_eq!(
2284 tokenizer.next_token().unwrap(),
2285 Some(Token::Name("Name with spaces".to_string()))
2286 );
2287 assert_eq!(
2288 tokenizer.next_token().unwrap(),
2289 Some(Token::Name("Name#with#hash".to_string()))
2290 );
2291 assert_eq!(
2292 tokenizer.next_token().unwrap(),
2293 Some(Token::Name("Name/with/slash".to_string()))
2294 );
2295 assert_eq!(
2296 tokenizer.next_token().unwrap(),
2297 Some(Token::Name("EmptyName".to_string()))
2298 );
2299 }
2300
2301 #[test]
2302 fn test_operator_parsing_edge_cases() {
2303 let content = b"q q q Q Q Q BT BT ET ET";
2304 let operators = ContentParser::parse(content).unwrap();
2305
2306 assert_eq!(operators.len(), 10);
2307 assert_eq!(operators[0], ContentOperation::SaveGraphicsState);
2308 assert_eq!(operators[1], ContentOperation::SaveGraphicsState);
2309 assert_eq!(operators[2], ContentOperation::SaveGraphicsState);
2310 assert_eq!(operators[3], ContentOperation::RestoreGraphicsState);
2311 assert_eq!(operators[4], ContentOperation::RestoreGraphicsState);
2312 assert_eq!(operators[5], ContentOperation::RestoreGraphicsState);
2313 assert_eq!(operators[6], ContentOperation::BeginText);
2314 assert_eq!(operators[7], ContentOperation::BeginText);
2315 assert_eq!(operators[8], ContentOperation::EndText);
2316 assert_eq!(operators[9], ContentOperation::EndText);
2317 }
2318
2319 #[test]
2320 fn test_error_handling_insufficient_operands() {
2321 let content = b"100 Td (kept) Tj";
2326 let ops = ContentParser::parse(content).expect("recovers from bad Td");
2327 assert!(
2328 ops.iter()
2329 .any(|op| matches!(op, ContentOperation::ShowText(t) if t == b"kept")),
2330 "valid Tj after the malformed Td must survive: {ops:?}"
2331 );
2332 }
2333
2334 #[test]
2335 fn test_error_handling_invalid_operator() {
2336 let content = b"100 200 INVALID 10 20 m";
2339 let ops = ContentParser::parse(content).expect("recovers from unknown operator");
2340 assert!(
2341 ops.iter()
2342 .any(|op| matches!(op, ContentOperation::MoveTo(_, _))),
2343 "valid MoveTo after the unknown operator must survive: {ops:?}"
2344 );
2345 }
2346
2347 #[test]
2348 fn test_error_handling_malformed_string() {
2349 let input = b"(Unclosed string";
2351 let mut tokenizer = ContentTokenizer::new(input);
2352 let result = tokenizer.next_token();
2353 assert!(result.is_ok() || result.is_err());
2356 }
2357
2358 #[test]
2359 fn test_error_handling_malformed_hex_string() {
2360 let input = b"<48656C6C6G>";
2361 let mut tokenizer = ContentTokenizer::new(input);
2362 let result = tokenizer.next_token();
2363 assert!(result.is_err());
2364 }
2365
2366 #[test]
2367 fn test_error_handling_malformed_name() {
2368 let input = b"/Name#GG";
2369 let mut tokenizer = ContentTokenizer::new(input);
2370 let result = tokenizer.next_token();
2371 assert!(result.is_err());
2372 }
2373
2374 #[test]
2375 fn test_empty_content_stream() {
2376 let content = b"";
2377 let operators = ContentParser::parse(content).unwrap();
2378 assert_eq!(operators.len(), 0);
2379 }
2380
2381 #[test]
2382 fn test_whitespace_only_content_stream() {
2383 let content = b" \t\n\r ";
2384 let operators = ContentParser::parse(content).unwrap();
2385 assert_eq!(operators.len(), 0);
2386 }
2387
2388 #[test]
2389 fn test_mixed_integer_and_real_operands() {
2390 let content = b"100 200 m 150 200 l";
2392 let operators = ContentParser::parse(content).unwrap();
2393
2394 assert_eq!(operators.len(), 2);
2395 assert_eq!(operators[0], ContentOperation::MoveTo(100.0, 200.0));
2396 assert_eq!(operators[1], ContentOperation::LineTo(150.0, 200.0));
2397 }
2398
2399 #[test]
2400 fn test_negative_operands() {
2401 let content = b"-100 -200 Td -50.5 -75.2 TD";
2402 let operators = ContentParser::parse(content).unwrap();
2403
2404 assert_eq!(operators.len(), 2);
2405 assert_eq!(operators[0], ContentOperation::MoveText(-100.0, -200.0));
2406 assert_eq!(
2407 operators[1],
2408 ContentOperation::MoveTextSetLeading(-50.5, -75.2)
2409 );
2410 }
2411
2412 #[test]
2413 fn test_large_numbers() {
2414 let content = b"999999.999999 -999999.999999 m";
2415 let operators = ContentParser::parse(content).unwrap();
2416
2417 assert_eq!(operators.len(), 1);
2418 assert_eq!(
2419 operators[0],
2420 ContentOperation::MoveTo(999999.999999, -999999.999999)
2421 );
2422 }
2423
2424 #[test]
2425 fn test_scientific_notation() {
2426 let content = b"123.45 -456.78 m";
2428 let operators = ContentParser::parse(content).unwrap();
2429
2430 assert_eq!(operators.len(), 1);
2431 assert_eq!(operators[0], ContentOperation::MoveTo(123.45, -456.78));
2432 }
2433
2434 #[test]
2435 fn test_show_text_array_complex() {
2436 let content = b"(Hello) TJ (kept) Tj";
2440 let ops = ContentParser::parse(content).expect("recovers from malformed TJ");
2441 assert!(
2442 ops.iter()
2443 .any(|op| matches!(op, ContentOperation::ShowText(t) if t == b"kept")),
2444 "valid Tj after the malformed TJ must survive: {ops:?}"
2445 );
2446 }
2447
2448 #[test]
2449 fn test_dash_pattern_empty() {
2450 let content = b"0 d 10 20 m";
2453 let ops = ContentParser::parse(content).expect("recovers from malformed d");
2454 assert!(
2455 ops.iter()
2456 .any(|op| matches!(op, ContentOperation::MoveTo(_, _))),
2457 "valid MoveTo after the malformed dash op must survive: {ops:?}"
2458 );
2459 }
2460
2461 #[test]
2462 fn test_dash_pattern_complex() {
2463 let content = b"2.5 d 10 20 m";
2465 let ops = ContentParser::parse(content).expect("recovers from malformed d");
2466 assert!(
2467 ops.iter()
2468 .any(|op| matches!(op, ContentOperation::MoveTo(_, _))),
2469 "valid MoveTo after the malformed dash op must survive: {ops:?}"
2470 );
2471 }
2472
2473 #[test]
2474 fn test_pop_array_removes_array_end() {
2475 let parser = ContentParser::new(b"");
2477
2478 let mut operands = vec![
2480 Token::ArrayStart,
2481 Token::Integer(1),
2482 Token::Integer(2),
2483 Token::Integer(3),
2484 Token::ArrayEnd,
2485 ];
2486 let result = parser.pop_array(&mut operands).unwrap();
2487 assert_eq!(result.len(), 3);
2488 assert!(operands.is_empty());
2489
2490 let mut operands = vec![Token::ArrayStart, Token::Number(1.5), Token::Number(2.5)];
2492 let result = parser.pop_array(&mut operands).unwrap();
2493 assert_eq!(result.len(), 2);
2494 assert!(operands.is_empty());
2495 }
2496
2497 #[test]
2498 fn test_dash_array_parsing_valid() {
2499 let parser = ContentParser::new(b"");
2501
2502 let valid_tokens = vec![Token::Number(3.0), Token::Integer(2)];
2504 let result = parser.parse_dash_array(valid_tokens).unwrap();
2505 assert_eq!(result, vec![3.0, 2.0]);
2506
2507 let empty_tokens = vec![];
2509 let result = parser.parse_dash_array(empty_tokens).unwrap();
2510 let expected: Vec<f32> = vec![];
2511 assert_eq!(result, expected);
2512 }
2513
2514 #[test]
2515 fn test_text_array_parsing_valid() {
2516 let parser = ContentParser::new(b"");
2518
2519 let valid_tokens = vec![
2521 Token::String(b"Hello".to_vec()),
2522 Token::Number(-100.0),
2523 Token::String(b"World".to_vec()),
2524 ];
2525 let result = parser.parse_text_array(valid_tokens).unwrap();
2526 assert_eq!(result.len(), 3);
2527 }
2528
2529 #[test]
2530 fn test_inline_image_handling() {
2531 let content = b"BI /W 100 /H 100 /BPC 8 /CS /RGB ID some_image_data EI";
2532 let operators = ContentParser::parse(content).unwrap();
2533
2534 assert_eq!(operators.len(), 1);
2535 match &operators[0] {
2536 ContentOperation::InlineImage { params, data: _ } => {
2537 assert_eq!(params.get("Width"), Some(&Object::Integer(100)));
2539 assert_eq!(params.get("Height"), Some(&Object::Integer(100)));
2540 assert_eq!(params.get("BitsPerComponent"), Some(&Object::Integer(8)));
2541 assert_eq!(
2542 params.get("ColorSpace"),
2543 Some(&Object::Name("DeviceRGB".to_string()))
2544 );
2545 }
2547 _ => panic!("Expected InlineImage operation"),
2548 }
2549 }
2550
2551 #[test]
2552 fn test_inline_image_with_filter() {
2553 let content = b"BI /W 50 /H 50 /CS /G /BPC 1 /F /AHx ID 00FF00FF EI";
2554 let operators = ContentParser::parse(content).unwrap();
2555
2556 assert_eq!(operators.len(), 1);
2557 match &operators[0] {
2558 ContentOperation::InlineImage { params, data: _ } => {
2559 assert_eq!(params.get("Width"), Some(&Object::Integer(50)));
2560 assert_eq!(params.get("Height"), Some(&Object::Integer(50)));
2561 assert_eq!(
2562 params.get("ColorSpace"),
2563 Some(&Object::Name("DeviceGray".to_string()))
2564 );
2565 assert_eq!(params.get("BitsPerComponent"), Some(&Object::Integer(1)));
2566 assert_eq!(
2567 params.get("Filter"),
2568 Some(&Object::Name("ASCIIHexDecode".to_string()))
2569 );
2570 }
2571 _ => panic!("Expected InlineImage operation"),
2572 }
2573 }
2574
2575 #[test]
2576 fn test_content_parser_performance() {
2577 let mut content = Vec::new();
2578 for i in 0..1000 {
2579 content.extend_from_slice(format!("{} {} m ", i, i + 1).as_bytes());
2580 }
2581
2582 let start = std::time::Instant::now();
2583 let operators = ContentParser::parse(&content).unwrap();
2584 let duration = start.elapsed();
2585
2586 assert_eq!(operators.len(), 1000);
2587 assert!(duration.as_millis() < 100); }
2589
2590 #[test]
2591 fn test_tokenizer_performance() {
2592 let mut input = Vec::new();
2593 for i in 0..1000 {
2594 input.extend_from_slice(format!("{} {} ", i, i + 1).as_bytes());
2595 }
2596
2597 let start = std::time::Instant::now();
2598 let mut tokenizer = ContentTokenizer::new(&input);
2599 let mut count = 0;
2600 while tokenizer.next_token().unwrap().is_some() {
2601 count += 1;
2602 }
2603 let duration = start.elapsed();
2604
2605 assert_eq!(count, 2000); assert!(duration.as_millis() < 50); }
2608
2609 #[test]
2610 fn test_memory_usage_large_content() {
2611 let mut content = Vec::new();
2612 for i in 0..10000 {
2613 content.extend_from_slice(
2614 format!("{} {} {} {} {} {} c ", i, i + 1, i + 2, i + 3, i + 4, i + 5)
2615 .as_bytes(),
2616 );
2617 }
2618
2619 let operators = ContentParser::parse(&content).unwrap();
2620 assert_eq!(operators.len(), 10000);
2621
2622 for op in operators {
2624 matches!(op, ContentOperation::CurveTo(_, _, _, _, _, _));
2625 }
2626 }
2627
2628 #[test]
2629 fn test_concurrent_parsing() {
2630 use std::sync::Arc;
2631 use std::thread;
2632
2633 let content = Arc::new(b"BT /F1 12 Tf 100 200 Td (Hello) Tj ET".to_vec());
2634 let handles: Vec<_> = (0..10)
2635 .map(|_| {
2636 let content_clone = content.clone();
2637 thread::spawn(move || ContentParser::parse(&content_clone).unwrap())
2638 })
2639 .collect();
2640
2641 for handle in handles {
2642 let operators = handle.join().unwrap();
2643 assert_eq!(operators.len(), 5);
2644 assert_eq!(operators[0], ContentOperation::BeginText);
2645 assert_eq!(operators[4], ContentOperation::EndText);
2646 }
2647 }
2648
2649 #[test]
2652 fn test_tokenizer_hex_string_edge_cases() {
2653 let mut tokenizer = ContentTokenizer::new(b"<>");
2654 let token = tokenizer.next_token().unwrap().unwrap();
2655 match token {
2656 Token::HexString(data) => assert!(data.is_empty()),
2657 _ => panic!("Expected empty hex string"),
2658 }
2659
2660 let mut tokenizer = ContentTokenizer::new(b"<123>");
2662 let token = tokenizer.next_token().unwrap().unwrap();
2663 match token {
2664 Token::HexString(data) => assert_eq!(data, vec![0x12, 0x30]),
2665 _ => panic!("Expected hex string with odd digits"),
2666 }
2667
2668 let mut tokenizer = ContentTokenizer::new(b"<12 34\t56\n78>");
2670 let token = tokenizer.next_token().unwrap().unwrap();
2671 match token {
2672 Token::HexString(data) => assert_eq!(data, vec![0x12, 0x34, 0x56, 0x78]),
2673 _ => panic!("Expected hex string with whitespace"),
2674 }
2675 }
2676
2677 #[test]
2678 fn test_tokenizer_literal_string_escape_sequences() {
2679 let mut tokenizer = ContentTokenizer::new(b"(\\n\\r\\t\\b\\f\\(\\)\\\\)");
2681 let token = tokenizer.next_token().unwrap().unwrap();
2682 match token {
2683 Token::String(data) => {
2684 assert_eq!(
2685 data,
2686 vec![b'\n', b'\r', b'\t', 0x08, 0x0C, b'(', b')', b'\\']
2687 );
2688 }
2689 _ => panic!("Expected string with escapes"),
2690 }
2691
2692 let mut tokenizer = ContentTokenizer::new(b"(\\101\\040\\377)");
2694 let token = tokenizer.next_token().unwrap().unwrap();
2695 match token {
2696 Token::String(data) => assert_eq!(data, vec![b'A', b' ', 255]),
2697 _ => panic!("Expected string with octal escapes"),
2698 }
2699 }
2700
2701 #[test]
2702 fn test_tokenizer_nested_parentheses() {
2703 let mut tokenizer = ContentTokenizer::new(b"(outer (inner) text)");
2704 let token = tokenizer.next_token().unwrap().unwrap();
2705 match token {
2706 Token::String(data) => {
2707 assert_eq!(data, b"outer (inner) text");
2708 }
2709 _ => panic!("Expected string with nested parentheses"),
2710 }
2711
2712 let mut tokenizer = ContentTokenizer::new(b"(level1 (level2 (level3) back2) back1)");
2714 let token = tokenizer.next_token().unwrap().unwrap();
2715 match token {
2716 Token::String(data) => {
2717 assert_eq!(data, b"level1 (level2 (level3) back2) back1");
2718 }
2719 _ => panic!("Expected string with deep nesting"),
2720 }
2721 }
2722
2723 #[test]
2724 fn test_tokenizer_name_hex_escapes() {
2725 let mut tokenizer = ContentTokenizer::new(b"/Name#20With#20Spaces");
2726 let token = tokenizer.next_token().unwrap().unwrap();
2727 match token {
2728 Token::Name(name) => assert_eq!(name, "Name With Spaces"),
2729 _ => panic!("Expected name with hex escapes"),
2730 }
2731
2732 let mut tokenizer = ContentTokenizer::new(b"/Special#2F#28#29#3C#3E");
2734 let token = tokenizer.next_token().unwrap().unwrap();
2735 match token {
2736 Token::Name(name) => assert_eq!(name, "Special/()<>"),
2737 _ => panic!("Expected name with special character escapes"),
2738 }
2739 }
2740
2741 #[test]
2742 fn test_tokenizer_number_edge_cases() {
2743 let mut tokenizer = ContentTokenizer::new(b"2147483647");
2745 let token = tokenizer.next_token().unwrap().unwrap();
2746 match token {
2747 Token::Integer(n) => assert_eq!(n, 2147483647),
2748 _ => panic!("Expected large integer"),
2749 }
2750
2751 let mut tokenizer = ContentTokenizer::new(b"0.00001");
2753 let token = tokenizer.next_token().unwrap().unwrap();
2754 match token {
2755 Token::Number(n) => assert!((n - 0.00001).abs() < f32::EPSILON),
2756 _ => panic!("Expected small float"),
2757 }
2758
2759 let mut tokenizer = ContentTokenizer::new(b".5");
2761 let token = tokenizer.next_token().unwrap().unwrap();
2762 match token {
2763 Token::Number(n) => assert!((n - 0.5).abs() < f32::EPSILON),
2764 _ => panic!("Expected float starting with dot"),
2765 }
2766 }
2767
2768 #[test]
2769 fn test_parser_complex_path_operations() {
2770 let content = b"100 200 m 150 200 l 150 250 l 100 250 l h f";
2771 let operators = ContentParser::parse(content).unwrap();
2772
2773 assert_eq!(operators.len(), 6);
2774 assert_eq!(operators[0], ContentOperation::MoveTo(100.0, 200.0));
2775 assert_eq!(operators[1], ContentOperation::LineTo(150.0, 200.0));
2776 assert_eq!(operators[2], ContentOperation::LineTo(150.0, 250.0));
2777 assert_eq!(operators[3], ContentOperation::LineTo(100.0, 250.0));
2778 assert_eq!(operators[4], ContentOperation::ClosePath);
2779 assert_eq!(operators[5], ContentOperation::Fill);
2780 }
2781
2782 #[test]
2783 fn test_parser_bezier_curves() {
2784 let content = b"100 100 150 50 200 150 c";
2785 let operators = ContentParser::parse(content).unwrap();
2786
2787 assert_eq!(operators.len(), 1);
2788 match &operators[0] {
2789 ContentOperation::CurveTo(x1, y1, x2, y2, x3, y3) => {
2790 assert!(x1.is_finite() && y1.is_finite());
2794 assert!(x2.is_finite() && y2.is_finite());
2795 assert!(x3.is_finite() && y3.is_finite());
2796 assert!(*x1 >= 50.0 && *x1 <= 200.0);
2798 assert!(*y1 >= 50.0 && *y1 <= 200.0);
2799 }
2800 _ => panic!("Expected CurveTo operation"),
2801 }
2802 }
2803
2804 #[test]
2805 fn test_parser_color_operations() {
2806 let content = b"0.5 g 1 0 0 rg 0 1 0 1 k /DeviceRGB cs 0.2 0.4 0.6 sc";
2807 let operators = ContentParser::parse(content).unwrap();
2808
2809 assert_eq!(operators.len(), 5);
2810 match &operators[0] {
2811 ContentOperation::SetNonStrokingGray(gray) => assert_eq!(*gray, 0.5),
2812 _ => panic!("Expected SetNonStrokingGray"),
2813 }
2814 match &operators[1] {
2815 ContentOperation::SetNonStrokingRGB(r, g, b) => {
2816 assert_eq!((*r, *g, *b), (1.0, 0.0, 0.0));
2817 }
2818 _ => panic!("Expected SetNonStrokingRGB"),
2819 }
2820 }
2821
2822 #[test]
2823 fn test_parser_text_positioning_advanced() {
2824 let content = b"BT 1 0 0 1 100 200 Tm 0 TL 10 TL (Line 1) ' (Line 2) ' ET";
2825 let operators = ContentParser::parse(content).unwrap();
2826
2827 assert_eq!(operators.len(), 7);
2828 assert_eq!(operators[0], ContentOperation::BeginText);
2829 match &operators[1] {
2830 ContentOperation::SetTextMatrix(a, b, c, d, e, f) => {
2831 assert_eq!((*a, *b, *c, *d, *e, *f), (1.0, 0.0, 0.0, 1.0, 100.0, 200.0));
2832 }
2833 _ => panic!("Expected SetTextMatrix"),
2834 }
2835 assert_eq!(operators[6], ContentOperation::EndText);
2836 }
2837
2838 #[test]
2839 fn test_parser_graphics_state_operations() {
2840 let content = b"q 2 0 0 2 100 100 cm 5 w 1 J 2 j 10 M Q";
2841 let operators = ContentParser::parse(content).unwrap();
2842
2843 assert_eq!(operators.len(), 7);
2844 assert_eq!(operators[0], ContentOperation::SaveGraphicsState);
2845 match &operators[1] {
2846 ContentOperation::SetTransformMatrix(a, b, c, d, e, f) => {
2847 assert_eq!((*a, *b, *c, *d, *e, *f), (2.0, 0.0, 0.0, 2.0, 100.0, 100.0));
2848 }
2849 _ => panic!("Expected SetTransformMatrix"),
2850 }
2851 assert_eq!(operators[6], ContentOperation::RestoreGraphicsState);
2852 }
2853
2854 #[test]
2855 fn test_parser_xobject_operations() {
2856 let content = b"/Image1 Do /Form2 Do /Pattern3 Do";
2857 let operators = ContentParser::parse(content).unwrap();
2858
2859 assert_eq!(operators.len(), 3);
2860 for (i, expected_name) in ["Image1", "Form2", "Pattern3"].iter().enumerate() {
2861 match &operators[i] {
2862 ContentOperation::PaintXObject(name) => assert_eq!(name, expected_name),
2863 _ => panic!("Expected PaintXObject"),
2864 }
2865 }
2866 }
2867
2868 #[test]
2869 fn test_parser_marked_content_operations() {
2870 let content = b"/P BMC (Tagged content) Tj EMC";
2871 let operators = ContentParser::parse(content).unwrap();
2872
2873 assert_eq!(operators.len(), 3);
2874 match &operators[0] {
2875 ContentOperation::BeginMarkedContent(tag) => assert_eq!(tag, "P"),
2876 _ => panic!("Expected BeginMarkedContent"),
2877 }
2878 assert_eq!(operators[2], ContentOperation::EndMarkedContent);
2879 }
2880
2881 #[test]
2882 fn test_parser_error_handling_invalid_operators() {
2883 let content = b"m 10 20 l";
2888 let ops = ContentParser::parse(content).expect("recovers from operand-less m");
2889 assert!(
2890 ops.iter()
2891 .any(|op| matches!(op, ContentOperation::LineTo(_, _))),
2892 "valid LineTo after the operand-less m must survive: {ops:?}"
2893 );
2894
2895 let content = b"(kept) Tj <ABC DEF";
2899 let ops = ContentParser::parse(content).expect("recovers, keeping pre-error tokens");
2900 assert!(
2901 ops.iter()
2902 .any(|op| matches!(op, ContentOperation::ShowText(t) if t == b"kept")),
2903 "text before the unterminated hex must survive: {ops:?}"
2904 );
2905
2906 let content = b"100 200 300";
2908 assert!(ContentParser::parse(content).is_ok());
2909 }
2910
2911 #[test]
2912 fn test_parser_whitespace_tolerance() {
2913 let content = b" \n\t 100 \r\n 200 \t m \n";
2914 let operators = ContentParser::parse(content).unwrap();
2915
2916 assert_eq!(operators.len(), 1);
2917 assert_eq!(operators[0], ContentOperation::MoveTo(100.0, 200.0));
2918 }
2919
2920 #[test]
2921 fn test_tokenizer_comment_handling() {
2922 let content = b"100 % This is a comment\n200 m % Another comment";
2923 let operators = ContentParser::parse(content).unwrap();
2924
2925 assert_eq!(operators.len(), 1);
2926 assert_eq!(operators[0], ContentOperation::MoveTo(100.0, 200.0));
2927 }
2928
2929 #[test]
2930 fn test_parser_stream_with_binary_data() {
2931 let content = b"100 200 m % Comment with \xFF binary\n150 250 l";
2933
2934 let operators = ContentParser::parse(content).unwrap();
2935 assert_eq!(operators.len(), 2);
2936 assert_eq!(operators[0], ContentOperation::MoveTo(100.0, 200.0));
2937 assert_eq!(operators[1], ContentOperation::LineTo(150.0, 250.0));
2938 }
2939
2940 #[test]
2941 fn test_tokenizer_array_parsing() {
2942 let content = b"100 200 m 150 250 l";
2944 let operators = ContentParser::parse(content).unwrap();
2945
2946 assert_eq!(operators.len(), 2);
2947 assert_eq!(operators[0], ContentOperation::MoveTo(100.0, 200.0));
2948 assert_eq!(operators[1], ContentOperation::LineTo(150.0, 250.0));
2949 }
2950
2951 #[test]
2952 fn test_parser_rectangle_operations() {
2953 let content = b"10 20 100 50 re 0 0 200 300 re";
2954 let operators = ContentParser::parse(content).unwrap();
2955
2956 assert_eq!(operators.len(), 2);
2957 match &operators[0] {
2958 ContentOperation::Rectangle(x, y, width, height) => {
2959 assert_eq!((*x, *y, *width, *height), (10.0, 20.0, 100.0, 50.0));
2960 }
2961 _ => panic!("Expected Rectangle operation"),
2962 }
2963 match &operators[1] {
2964 ContentOperation::Rectangle(x, y, width, height) => {
2965 assert_eq!((*x, *y, *width, *height), (0.0, 0.0, 200.0, 300.0));
2966 }
2967 _ => panic!("Expected Rectangle operation"),
2968 }
2969 }
2970
2971 #[test]
2972 fn test_parser_clipping_operations() {
2973 let content = b"100 100 50 50 re W n 200 200 75 75 re W* n";
2974 let operators = ContentParser::parse(content).unwrap();
2975
2976 assert_eq!(operators.len(), 6);
2977 assert_eq!(operators[1], ContentOperation::Clip);
2978 assert_eq!(operators[2], ContentOperation::EndPath);
2979 assert_eq!(operators[4], ContentOperation::ClipEvenOdd);
2980 assert_eq!(operators[5], ContentOperation::EndPath);
2981 }
2982
2983 #[test]
2984 fn test_parser_painting_operations() {
2985 let content = b"S s f f* B B* b b*";
2986 let operators = ContentParser::parse(content).unwrap();
2987
2988 assert_eq!(operators.len(), 8);
2989 assert_eq!(operators[0], ContentOperation::Stroke);
2990 assert_eq!(operators[1], ContentOperation::CloseStroke);
2991 assert_eq!(operators[2], ContentOperation::Fill);
2992 assert_eq!(operators[3], ContentOperation::FillEvenOdd);
2993 assert_eq!(operators[4], ContentOperation::FillStroke);
2994 assert_eq!(operators[5], ContentOperation::FillStrokeEvenOdd);
2995 assert_eq!(operators[6], ContentOperation::CloseFillStroke);
2996 assert_eq!(operators[7], ContentOperation::CloseFillStrokeEvenOdd);
2997 }
2998
2999 #[test]
3000 fn test_parser_line_style_operations() {
3001 let content = b"5 w 1 J 2 j 10 M [ 3 2 ] 0 d";
3002 let operators = ContentParser::parse(content).unwrap();
3003
3004 assert_eq!(operators.len(), 5);
3005 assert_eq!(operators[0], ContentOperation::SetLineWidth(5.0));
3006 assert_eq!(operators[1], ContentOperation::SetLineCap(1));
3007 assert_eq!(operators[2], ContentOperation::SetLineJoin(2));
3008 assert_eq!(operators[3], ContentOperation::SetMiterLimit(10.0));
3009 }
3011
3012 #[test]
3013 fn test_parser_text_state_operations() {
3014 let content = b"12 Tc 3 Tw 100 Tz 1 Tr 2 Ts";
3015 let operators = ContentParser::parse(content).unwrap();
3016
3017 assert_eq!(operators.len(), 5);
3018 assert_eq!(operators[0], ContentOperation::SetCharSpacing(12.0));
3019 assert_eq!(operators[1], ContentOperation::SetWordSpacing(3.0));
3020 assert_eq!(operators[2], ContentOperation::SetHorizontalScaling(100.0));
3021 assert_eq!(operators[3], ContentOperation::SetTextRenderMode(1));
3022 assert_eq!(operators[4], ContentOperation::SetTextRise(2.0));
3023 }
3024
3025 #[test]
3026 fn test_parser_unicode_text() {
3027 let content = b"BT (Hello \xC2\xA9 World \xE2\x9C\x93) Tj ET";
3028 let operators = ContentParser::parse(content).unwrap();
3029
3030 assert_eq!(operators.len(), 3);
3031 assert_eq!(operators[0], ContentOperation::BeginText);
3032 match &operators[1] {
3033 ContentOperation::ShowText(text) => {
3034 assert!(text.len() > 5); }
3036 _ => panic!("Expected ShowText operation"),
3037 }
3038 assert_eq!(operators[2], ContentOperation::EndText);
3039 }
3040
3041 #[test]
3042 fn test_parser_stress_test_large_coordinates() {
3043 let content = b"999999.999 -999999.999 999999.999 -999999.999 999999.999 -999999.999 c";
3044 let operators = ContentParser::parse(content).unwrap();
3045
3046 assert_eq!(operators.len(), 1);
3047 match &operators[0] {
3048 ContentOperation::CurveTo(_x1, _y1, _x2, _y2, _x3, _y3) => {
3049 assert!((*_x1 - 999999.999).abs() < 0.1);
3050 assert!((*_y1 - (-999999.999)).abs() < 0.1);
3051 assert!((*_x3 - 999999.999).abs() < 0.1);
3052 }
3053 _ => panic!("Expected CurveTo operation"),
3054 }
3055 }
3056
3057 #[test]
3058 fn test_parser_empty_content_stream() {
3059 let content = b"";
3060 let operators = ContentParser::parse(content).unwrap();
3061 assert!(operators.is_empty());
3062
3063 let content = b" \n\t\r ";
3064 let operators = ContentParser::parse(content).unwrap();
3065 assert!(operators.is_empty());
3066 }
3067
3068 #[test]
3069 fn test_tokenizer_error_recovery() {
3070 let content = b"100 200 m % Comment with\xFFbinary\n150 250 l";
3074 let ops = ContentParser::parse(content).expect("recovers around binary comment");
3075 assert!(
3076 ops.iter()
3077 .any(|op| matches!(op, ContentOperation::MoveTo(_, _))),
3078 "MoveTo before the comment must survive: {ops:?}"
3079 );
3080 assert!(
3081 ops.iter()
3082 .any(|op| matches!(op, ContentOperation::LineTo(_, _))),
3083 "LineTo after the comment must survive: {ops:?}"
3084 );
3085 }
3086
3087 #[test]
3088 fn malformed_operator_does_not_discard_surrounding_text() {
3089 let content = b"BT /F1 12 Tf 72 700 Td (First line) Tj Td (Second line) Tj ET";
3097 let ops = ContentParser::parse_content(content)
3098 .expect("malformed operator must not fail the whole stream");
3099 let shown: Vec<&Vec<u8>> = ops
3100 .iter()
3101 .filter_map(|op| match op {
3102 ContentOperation::ShowText(t) => Some(t),
3103 _ => None,
3104 })
3105 .collect();
3106 assert_eq!(
3107 shown.len(),
3108 2,
3109 "both valid Tj operators must survive the malformed Td"
3110 );
3111 assert_eq!(shown[0], b"First line");
3112 assert_eq!(shown[1], b"Second line");
3113 }
3114
3115 #[test]
3116 fn test_parser_optimization_repeated_operations() {
3117 let mut content = Vec::new();
3119 for i in 0..1000 {
3120 content.extend_from_slice(format!("{} {} m ", i, i * 2).as_bytes());
3121 }
3122
3123 let start = std::time::Instant::now();
3124 let operators = ContentParser::parse(&content).unwrap();
3125 let duration = start.elapsed();
3126
3127 assert_eq!(operators.len(), 1000);
3128 assert!(duration.as_millis() < 200); }
3130
3131 #[test]
3132 fn test_parser_memory_efficiency_large_strings() {
3133 let large_text = "A".repeat(10000);
3135 let content = format!("BT ({}) Tj ET", large_text);
3136 let operators = ContentParser::parse(content.as_bytes()).unwrap();
3137
3138 assert_eq!(operators.len(), 3);
3139 match &operators[1] {
3140 ContentOperation::ShowText(text) => {
3141 assert_eq!(text.len(), 10000);
3142 }
3143 _ => panic!("Expected ShowText operation"),
3144 }
3145 }
3146 }
3147
3148 #[test]
3149 fn test_content_stream_too_large() {
3150 let mut large_content = Vec::new();
3152
3153 for i in 0..10000 {
3155 large_content.extend_from_slice(format!("{} {} m ", i, i).as_bytes());
3156 }
3157 large_content.extend_from_slice(b"S");
3158
3159 let result = ContentParser::parse_content(&large_content);
3161 assert!(result.is_ok());
3162
3163 let operations = result.unwrap();
3164 assert!(operations.len() > 10000);
3166 }
3167
3168 #[test]
3169 fn test_invalid_operator_handling() {
3170 let content = b"100 200 INVALID_OP 300 400 m";
3172 let result = ContentParser::parse_content(content);
3173
3174 if let Ok(operations) = result {
3176 assert!(operations
3178 .iter()
3179 .any(|op| matches!(op, ContentOperation::MoveTo(_, _))));
3180 }
3181 }
3182
3183 #[test]
3184 fn test_nested_arrays_malformed() {
3185 let content = b"[[(Hello] [World)]] TJ";
3187 let result = ContentParser::parse_content(content);
3188
3189 assert!(result.is_ok() || result.is_err());
3191 }
3192
3193 #[test]
3194 fn test_escape_sequences_in_strings() {
3195 let test_cases = vec![
3197 (b"(\\n\\r\\t)".as_slice(), b"\n\r\t".as_slice()),
3198 (b"(\\\\)".as_slice(), b"\\".as_slice()),
3199 (b"(\\(\\))".as_slice(), b"()".as_slice()),
3200 (b"(\\123)".as_slice(), b"S".as_slice()), (b"(\\0)".as_slice(), b"\0".as_slice()),
3202 ];
3203
3204 for (input, expected) in test_cases {
3205 let mut content = Vec::new();
3206 content.extend_from_slice(input);
3207 content.extend_from_slice(b" Tj");
3208
3209 let result = ContentParser::parse_content(&content);
3210 assert!(result.is_ok());
3211
3212 let operations = result.unwrap();
3213 if let ContentOperation::ShowText(text) = &operations[0] {
3214 assert_eq!(text, expected, "Failed for input: {:?}", input);
3215 } else {
3216 panic!("Expected ShowText operation");
3217 }
3218 }
3219 }
3220
3221 #[test]
3222 fn test_content_with_inline_images() {
3223 let content = b"BI /W 10 /H 10 /CS /RGB ID \x00\x01\x02\x03 EI";
3225 let result = ContentParser::parse_content(content);
3226
3227 assert!(result.is_ok() || result.is_err());
3229 }
3230
3231 #[test]
3232 fn test_operator_with_missing_operands() {
3233 let test_cases = vec![
3235 b"Tj" as &[u8], b"m", b"rg", b"Tf", ];
3240
3241 for content in test_cases {
3242 let result = ContentParser::parse_content(content);
3243 assert!(result.is_ok() || result.is_err());
3245 }
3246 }
3247
3248 #[test]
3251 fn test_tokenizer_handles_curly_braces() {
3252 let input = b"q { } Q";
3255 let mut tokenizer = ContentTokenizer::new(input);
3256
3257 let mut tokens = Vec::new();
3258 while let Some(token) = tokenizer.next_token().unwrap() {
3259 tokens.push(token);
3260 }
3261
3262 assert!(tokens.contains(&Token::Operator("q".to_string())));
3264 assert!(tokens.contains(&Token::Operator("Q".to_string())));
3265 }
3266
3267 #[test]
3268 fn test_tokenizer_handles_closing_paren() {
3269 let input = b"q ) Q";
3271 let mut tokenizer = ContentTokenizer::new(input);
3272
3273 let mut tokens = Vec::new();
3274 while let Some(token) = tokenizer.next_token().unwrap() {
3275 tokens.push(token);
3276 }
3277
3278 assert!(tokens.contains(&Token::Operator("q".to_string())));
3279 assert!(tokens.contains(&Token::Operator("Q".to_string())));
3280 }
3281
3282 #[test]
3283 fn test_inline_image_binary_with_curly_braces() {
3284 let content = b"BI /W 2 /H 2 /BPC 8 /CS /G ID \x7B\x7D\x00\xFF EI Q";
3287 let result = ContentParser::parse_content(content);
3288 assert!(
3289 result.is_ok(),
3290 "Parsing inline image with curly braces failed: {:?}",
3291 result.err()
3292 );
3293
3294 let ops = result.unwrap();
3295 let has_inline = ops
3297 .iter()
3298 .any(|op| matches!(op, ContentOperation::InlineImage { .. }));
3299 let has_q = ops
3300 .iter()
3301 .any(|op| matches!(op, ContentOperation::RestoreGraphicsState));
3302 assert!(has_inline, "Expected InlineImage operation");
3303 assert!(has_q, "Expected RestoreGraphicsState after EI");
3304 }
3305
3306 #[test]
3307 fn test_inline_image_binary_with_all_byte_values() {
3308 let mut content = Vec::new();
3310 content.extend_from_slice(b"BI /W 16 /H 16 /BPC 8 /CS /G ID ");
3311 for b in 0u8..=255 {
3313 content.push(b);
3314 }
3315 content.extend_from_slice(b" EI Q");
3316
3317 let result = ContentParser::parse_content(&content);
3318 assert!(
3319 result.is_ok(),
3320 "Parsing inline image with all byte values failed: {:?}",
3321 result.err()
3322 );
3323 }
3324
3325 #[test]
3326 fn test_inline_image_ei_detection() {
3327 let content = b"BI /W 2 /H 1 /BPC 8 /CS /G ID \x45\x49\x00\n EI Q";
3330 let result = ContentParser::parse_content(content);
3332 assert!(result.is_ok(), "EI detection failed: {:?}", result.err());
3333
3334 let ops = result.unwrap();
3335 let has_inline = ops
3336 .iter()
3337 .any(|op| matches!(op, ContentOperation::InlineImage { .. }));
3338 assert!(has_inline, "Expected InlineImage operation");
3339 }
3340
3341 #[test]
3342 fn test_tokenizer_no_infinite_loop_on_consecutive_delimiters() {
3343 let input = b"q {{{}}})))) Q";
3345 let mut tokenizer = ContentTokenizer::new(input);
3346
3347 let mut tokens = Vec::new();
3348 while let Some(token) = tokenizer.next_token().unwrap() {
3349 tokens.push(token);
3350 if tokens.len() > 100 {
3351 panic!("Tokenizer produced too many tokens — possible infinite loop");
3352 }
3353 }
3354
3355 assert!(tokens.contains(&Token::Operator("q".to_string())));
3356 assert!(tokens.contains(&Token::Operator("Q".to_string())));
3357 }
3358
3359 #[test]
3360 fn test_content_parser_inline_image_produces_correct_operation() {
3361 let content = b"BI /W 4 /H 4 /BPC 8 /CS /G ID \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F EI";
3363 let result = ContentParser::parse_content(content);
3364 assert!(result.is_ok(), "Parse failed: {:?}", result.err());
3365
3366 let ops = result.unwrap();
3367 assert_eq!(
3368 ops.len(),
3369 1,
3370 "Expected exactly 1 operation, got {}",
3371 ops.len()
3372 );
3373
3374 if let ContentOperation::InlineImage { params, data } = &ops[0] {
3375 assert_eq!(params.get("Width"), Some(&Object::Integer(4)));
3376 assert_eq!(params.get("Height"), Some(&Object::Integer(4)));
3377 assert_eq!(params.get("BitsPerComponent"), Some(&Object::Integer(8)));
3378 assert!(!data.is_empty(), "Image data should not be empty");
3379 } else {
3380 panic!("Expected InlineImage operation, got {:?}", ops[0]);
3381 }
3382 }
3383
3384 #[test]
3385 fn test_octal_escape_overflow_777() {
3386 let mut tokenizer = ContentTokenizer::new(b"(\\777)");
3390 let token = tokenizer.next_token().unwrap().unwrap();
3391 match token {
3392 Token::String(data) => assert_eq!(data, vec![0xFF]),
3393 _ => panic!("Expected string token"),
3394 }
3395 }
3396
3397 #[test]
3398 fn test_octal_escape_overflow_400() {
3399 let mut tokenizer = ContentTokenizer::new(b"(\\400)");
3402 let token = tokenizer.next_token().unwrap().unwrap();
3403 match token {
3404 Token::String(data) => assert_eq!(data, vec![0x00]),
3405 _ => panic!("Expected string token"),
3406 }
3407 }
3408
3409 #[test]
3410 fn test_octal_escape_overflow_577() {
3411 let mut tokenizer = ContentTokenizer::new(b"(\\577)");
3414 let token = tokenizer.next_token().unwrap().unwrap();
3415 match token {
3416 Token::String(data) => assert_eq!(data, vec![0x7F]),
3417 _ => panic!("Expected string token"),
3418 }
3419 }
3420
3421 #[test]
3422 fn test_octal_escape_max_valid_377() {
3423 let mut tokenizer = ContentTokenizer::new(b"(\\377)");
3425 let token = tokenizer.next_token().unwrap().unwrap();
3426 match token {
3427 Token::String(data) => assert_eq!(data, vec![0xFF]),
3428 _ => panic!("Expected string token"),
3429 }
3430 }
3431
3432 #[test]
3433 fn test_octal_escape_overflow_mixed_with_valid() {
3434 let mut tokenizer = ContentTokenizer::new(b"(A\\777B\\101C)");
3436 let token = tokenizer.next_token().unwrap().unwrap();
3437 match token {
3438 Token::String(data) => {
3439 assert_eq!(data, vec![b'A', 0xFF, b'B', b'A', b'C']);
3440 }
3441 _ => panic!("Expected string token"),
3442 }
3443 }
3444}