AI import: non-native art stays upright unless its placement mirrors it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
EricKuck
2026-10-16 02:21:47 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent 0a4e9ea8c8
commit 710b6058ca
2 changed files with 39 additions and 8 deletions
+9 -6
View File
@@ -1283,12 +1283,15 @@ impl<'a> Reader<'a> {
if bbox.width() <= 0.0 || bbox.height() <= 0.0 {
return Ok(());
}
// The art's own box onto the object's box (its measure of the same art), upside down:
// the object's space has y down, and the PDF's content is kept in its own y-up
// coordinates, so the PDF's bottom is the top of the box.
let fit = Affine::translate((target.x0, target.y1))
* Affine::scale_non_uniform(target.width() / bbox.width(), -target.height() / bbox.height())
* Affine::translate((-bbox.x0, -bbox.y0));
// The art's own box onto the object's box (its measure of the same art). The PDF importer
// hands the art back as the page shows it, and it keeps that orientation unless its
// placement mirrors it (a negative determinant): the usual `/RTransform` `1 0 0 -1 …` with
// the origin's flip doesn't, and the art then shows as the file's own PDF part draws it.
let fit = if place.determinant() < 0.0 {
Affine::translate((target.x0, target.y1)) * Affine::scale_non_uniform(target.width() / bbox.width(), -target.height() / bbox.height())
} else {
Affine::translate((target.x0, target.y0)) * Affine::scale_non_uniform(target.width() / bbox.width(), target.height() / bbox.height())
} * Affine::translate((-bbox.x0, -bbox.y0));
for n in &mut art {
n.transform(fit, true);
// The PDF's swatches stay with it: its colours keep their values, unlinked.
+30 -2
View File
@@ -718,8 +718,8 @@ fn ascii85_comment_lines(bytes: &[u8]) -> String {
/// `/Bounds` in a space measured from `/Origin` with y down, then `/RTransform`. Two squares at
/// 1..5 × 2..8 of a 10-point page, blue below red: bounds (1, 2)–(5, 8) → from the origin
/// (10, 20), y down, (11, 18)–(15, 12) → twice that plus (100, 50): x 122..130, y 74..86 in art
/// space, which is y 14..26 in the document (crop marks 100 high). The space has y down, so the
/// PDF's bottom (blue) is the top of the box.
/// space, which is y 14..26 in the document (crop marks 100 high). That placement mirrors (`2 0 0 2`
/// with the origin's flip), so the PDF's bottom (blue) is the top of the box.
#[test]
fn non_native_art_is_drawn_from_its_pdf_fitted_to_its_bounds() {
let pdf = tiny_pdf(10.0, "0 0 1 rg 1 2 4 3 re f 1 0 0 rg 1 5 4 3 re f");
@@ -756,6 +756,34 @@ fn non_native_art_is_drawn_from_its_pdf_fitted_to_its_bounds() {
assert_ne!(paths[0].id, paths[1].id);
}
/// The usual placement, `/RTransform` `1 0 0 -1 …`, doesn't mirror (with the origin's flip its
/// determinant is positive), so the art shows upright, as the file's own PDF part draws it: the
/// PDF's top (red) at the top. Bounds
/// (1, 2)–(5, 8) from the origin (10, 20), y down, → (11, 18)–(15, 12) → x 111..115, y 32..38 in
/// art space, y 62..68 in the document (crop marks 100 high).
#[test]
fn non_native_art_placed_without_a_mirror_stays_upright() {
let pdf = tiny_pdf(10.0, "0 0 1 rg 1 2 4 3 re f 1 0 0 rg 1 5 4 3 re f");
let foreign = format!(
"q\n/ForeignObject :\n1 /Version ,\n1 0 0 -1 100 50 /RTransform ,\n10 20 /Origin ,\n1 2 5 8 /Bounds ,\n/Data ,\n{};\n0 0 Xd\n6 () XW\n0 Ae\n{}h\nW\nn\nQ\n",
ascii85_comment_lines(&pdf),
rect_path(110.0, 30.0, 116.0, 40.0)
);
let s = read_ok(&layer(&foreign));
assert!(s.warnings.is_empty(), "{:?}", s.warnings);
let objects = art(&s.doc);
let NodeKind::Group { children, clip: true } = &objects[0].kind else { panic!("{:?}", objects[0].kind) };
let group = &children[1];
let bounds = group.geometric_bounds().unwrap();
for (got, want) in [(bounds.x0, 111.0), (bounds.y0, 62.0), (bounds.x1, 115.0), (bounds.y1, 68.0)] {
assert!((got - want).abs() < 1e-6, "{bounds:?}");
}
let paths = group.children().unwrap();
let (blue, red) = (paths[0].geometric_bounds().unwrap(), paths[1].geometric_bounds().unwrap());
assert!((red.y0 - 62.0).abs() < 1e-6 && (red.y1 - 65.0).abs() < 1e-6, "red {red:?}");
assert!((blue.y0 - 65.0).abs() < 1e-6 && (blue.y1 - 68.0).abs() < 1e-6, "blue {blue:?}");
}
#[test]
fn cmyk_documents() {
let doc = read_ok(&format!("%AI9_ColorModel: 2\n{}", layer(""))).doc;