M2: a /Rotate that isn't a quarter turn counts as 0, as the page is drawn (#1863)

Page::rotation rounded a /Rotate that isn't a multiple of 90 down to a
quarter turn (135 became 90), while the renderer draws such a page
upright. Display size and the display-to-user-space matrix (header and
footer, watermarks, measuring, printing, image signatures) then
disagreed with the page on screen. ISO 32000-2 §7.7.3.3 requires a
multiple of 90; read any other value as 0, as the renderer does.

Page import and image stamps, which read /Rotate themselves, already
treat such a value as 0. Test: rotate_off_a_quarter_turn_counts_as_upright
(/Rotate 135, 30, -45, 359).
This commit is contained in:
pauliusiv123-rgb
2026-10-15 02:27:02 -04:00
committed by GitHub
parent e337a49631
commit 4d358d4fa4
2 changed files with 21 additions and 3 deletions
+19 -2
View File
@@ -37,9 +37,11 @@ impl Page {
}
}
/// Clockwise rotation when displayed: 0, 90, 180 or 270.
/// Clockwise rotation when displayed: 0, 90, 180 or 270. A `/Rotate` that isn't a multiple
/// of 90 (ISO 32000-2 §7.7.3.3 requires one) counts as 0, as the renderer draws that page.
pub fn rotation(&self, doc: &Document) -> i64 {
self.dict.get(b"Rotate").and_then(|o| doc.resolve(o).as_int()).unwrap_or(0).rem_euclid(360) / 90 * 90
let r = self.dict.get(b"Rotate").and_then(|o| doc.resolve(o).as_int()).unwrap_or(0).rem_euclid(360);
if r % 90 == 0 { r } else { 0 }
}
/// The size of the page's user-space unit in points (`/UserUnit`; it isn't inherited), read
@@ -184,6 +186,21 @@ mod tests {
}
}
/// A `/Rotate` that isn't a quarter turn counts as 0, as the renderer draws the page
/// upright, so display space matches what's on screen.
#[test]
fn rotate_off_a_quarter_turn_counts_as_upright() {
for rotate in [135, 30, -45, 359] {
let d = doc(rotate);
let p = &pages(&d)[0];
assert_eq!(p.rotation(&d), 0, "{rotate}");
assert_eq!(p.display_size(&d), (200.0, 300.0), "{rotate}");
assert_eq!(p.view_matrix(&d), [1.0, 0.0, 0.0, 1.0, 10.0, 20.0], "{rotate}");
}
let d = doc(-90);
assert_eq!(pages(&d)[0].rotation(&d), 270);
}
/// Display space is in points: `/UserUnit 2` doubles it. As in Acrobat, the value isn't
/// inherited, values below 1 or not numbers count as 1, and values over 75,000 as 75,000.
#[test]