diff --git a/vendor/hayro-syntax/src/object/number.rs b/vendor/hayro-syntax/src/object/number.rs index bd37b19c3..9b89ed709 100644 --- a/vendor/hayro-syntax/src/object/number.rs +++ b/vendor/hayro-syntax/src/object/number.rs @@ -70,6 +70,13 @@ impl Number { impl Skippable for Number { fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> { let has_sign = r.forward_if(|b| b == b'+' || b == b'-').is_some(); + // PdfCraft patch: one more sign, so that skipping a number covers what `read_inner` + // reads. `--280` was already covered by the `b'-'` arm below, but `-+280` and `++280` + // were not, and a skip that fails where a read succeeds puts the reader back where it + // started (#3630 review). + if has_sign { + r.forward_if(|b| b == b'+' || b == b'-'); + } // Some PDFs have weird trailing minuses, so try to accept those as well. match r.peek_byte()? { @@ -113,17 +120,24 @@ impl Readable<'_> for Number { #[inline(always)] fn read_inner(r: &mut Reader<'_>) -> Option { - let negative = match r.peek_byte()? { - b'-' => { - r.forward(); - true - } - b'+' => { - r.forward(); - false - } - _ => false, - }; + // PdfCraft patch: a doubled sign is one number, not two. `--280` left `negative` set with no + // digits read, so it returned 0 and the reader stopped on `-280`, which became a second + // number: a `cm` with it got seven operands, kept the last six, and the matrix + // `0 0 1 0 -280 10` hid everything drawn under it (#3565). `Number::skip` above already + // consumes the whole token, so read and skip disagreed about where the number ended. + // + // One extra sign is absorbed and the value is negative if either sign was a minus, which is + // what the COS lexer does since #3299 (`--280` and `+-280` are both -280). A third sign is + // left to the digit loop's trailing-minus arm, as before. + let mut negative = false; + let mut signs = 0; + while signs < 2 + && let Some(b @ (b'-' | b'+')) = r.peek_byte() + { + r.forward(); + negative |= b == b'-'; + signs += 1; + } // PdfCraft patch: high-precision float mantissa overflow (jsPDF double-precision coordinates). // Digits that don't fit in `mantissa` are dropped: past the dot they are only precision; @@ -348,6 +362,30 @@ mod tests { use crate::reader::Reader; use crate::reader::ReaderExt; + /// PdfCraft patch: a doubled sign is one number. `--280` returned 0 and left `-280` to be + /// read as a second number, so a `cm` carrying it got seven operands and a degenerate matrix + /// (#3565). The COS lexer reads these the same way since #3299. + #[test] + fn a_doubled_sign_is_one_number() { + for (src, want) in [("--280", -280), ("+-280", -280), ("-+280", -280), ("++280", 280), ("-280", -280), ("+280", 280)] { + let mut r = Reader::new(src.as_bytes()); + assert_eq!(r.read_without_context::().unwrap(), want, "{src}"); + assert!(r.at_end(), "{src}: the whole number was read, leaving nothing for a second one"); + } + // The fraction too, and a lone sign is still zero. + assert_eq!(Reader::new("--280.5".as_bytes()).read_without_context::().unwrap(), -280.5); + assert_eq!(Reader::new("-".as_bytes()).read_without_context::().unwrap(), 0); + + // Skipping covers what reading accepts: a skip that fails where a read succeeds puts the + // reader back where it started, so `Object::skip` would leave the number in place + // (#3630 review). + for src in ["--280", "+-280", "-+280", "++280", "-280", "+280", "--280.5"] { + let mut r = Reader::new(src.as_bytes()); + assert!(r.skip::(false).is_some(), "{src}: not skipped"); + assert!(r.at_end(), "{src}: only part of the number was skipped"); + } + } + #[test] fn int_1() { assert_eq!(