Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions crates/paged-compose/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@ pub fn emit_glyph_slice_blend<O, F>(
// into the glyph affine (P-08); the breaker already accounted
// for the advance, so glyphs are merely stretched in place.
let sx = scale * g.x_scale;
let transform = Transform([sx, 0.0, 0.0, -scale, gx, gy]);
// `y_scale` folds IDML `VerticalScale` into the glyph affine's
// y-axis (the `-scale` y-flip term), scaling glyph height about
// the baseline (`gy`) without touching the advance or leading.
let sy = scale * g.y_scale;
let transform = Transform([sx, 0.0, 0.0, -sy, gx, gy]);
if normal {
list.push(DisplayCommand::FillPath {
path_id,
Expand Down Expand Up @@ -225,7 +229,11 @@ pub fn emit_glyph_slice_stroke<O, S>(
// than transforming through `scale`. `x_scale` mirrors the fill
// path so a stretched run keeps its stroke aligned (P-08).
let sx = scale * g.x_scale;
let transform = Transform([sx, 0.0, 0.0, -scale, gx, gy]);
// `y_scale` folds IDML `VerticalScale` into the glyph affine's
// y-axis (the `-scale` y-flip term), scaling glyph height about
// the baseline (`gy`) without touching the advance or leading.
let sy = scale * g.y_scale;
let transform = Transform([sx, 0.0, 0.0, -sy, gx, gy]);
list.push(DisplayCommand::StrokePath {
path_id,
paint,
Expand Down Expand Up @@ -399,6 +407,47 @@ mod tests {
assert!(m[3] < 0.0, "y-scale not flipped: {:?}", m);
}

#[test]
fn vertical_scale_scales_glyph_affine_y_axis() {
// `y_scale` folds IDML VerticalScale into the affine's d term
// (the y-flip), independent of `x_scale` (HorizontalScale → a).
let glyph = |x_scale: f32, y_scale: f32| PositionedGlyph {
glyph_id: 65,
cluster: 0,
x: 0,
y: 0,
x_advance: 0,
font_id: 0,
point_size: 0.0,
underline: false,
strikethru: false,
x_scale,
y_scale,
};
let mut list = DisplayList::new();
for g in [glyph(1.0, 1.0), glyph(1.0, 2.0)] {
emit_glyph_slice(
&[g],
1,
12.0,
|_| Paint::Solid(Color::BLACK),
(0.0, 0.0),
&UnitSquareOutliner::default(),
&mut list,
);
}
let aff = |i: usize| match &list.commands[i] {
DisplayCommand::FillPath { transform, .. } => transform.0,
other => panic!("expected FillPath, got {other:?}"),
};
let (m1, m2) = (aff(0), aff(1));
// d (y-scale) doubles with VerticalScale=200%; a (x-scale) is
// untouched. Both d's stay negative (the page-down y-flip).
assert!(m1[3] < 0.0 && m2[3] < 0.0);
assert!((m2[3] - 2.0 * m1[3]).abs() < 1e-4, "d1={} d2={}", m1[3], m2[3]);
assert!((m1[0] - m2[0]).abs() < 1e-4, "x-scale must not change");
}

#[test]
fn emit_glyph_slice_caches_per_font_id() {
// Two glyph slices with the same glyph_id but different
Expand All @@ -416,6 +465,7 @@ mod tests {
underline: false,
strikethru: false,
x_scale: 1.0,
y_scale: 1.0,
}];
let glyphs_b = vec![PositionedGlyph {
glyph_id: 65,
Expand All @@ -428,6 +478,7 @@ mod tests {
underline: false,
strikethru: false,
x_scale: 1.0,
y_scale: 1.0,
}];
emit_glyph_slice(
&glyphs_a,
Expand Down
1 change: 1 addition & 0 deletions crates/paged-introspect/src/testutil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ pub fn document_with_one_textframe(self_id: &str) -> Document {
applied_master: None,
master_page_transform: None,
override_list: Vec::new(),
show_master_items: None,
});
Document {
container: Container {
Expand Down
23 changes: 23 additions & 0 deletions crates/paged-parse/src/designmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ pub struct DesignMap {
pub spreads: Vec<SpreadRef>,
pub stories: Vec<StoryRef>,
pub master_spreads: Vec<String>,
/// `DOMVersion` attribute on the root `<Document>` element (e.g.
/// `"18.5"` for InDesign 2023). Surfaced read-only so tooling can
/// report the authoring DOM; the parser is version-agnostic and
/// does **not** branch on it yet (no version negotiation).
pub dom_version: Option<String>,
/// Document-level color management settings, extracted from the
/// root `<Document>` element. Drives ICC transform construction —
/// the renderer matches `color_settings.cmyk_profile` against its
Expand Down Expand Up @@ -199,6 +204,7 @@ impl DesignMap {
match reader.read_event_into(&mut buf)? {
Event::Start(e) | Event::Empty(e) => {
if e.name().as_ref() == b"Document" {
out.dom_version = attr(&e, b"DOMVersion");
out.color_settings = ColorSettings {
cmyk_profile: attr(&e, b"CMYKProfile"),
rgb_profile: attr(&e, b"RGBProfile"),
Expand Down Expand Up @@ -360,4 +366,21 @@ mod tests {
let visible: Vec<bool> = dm.layers.iter().map(|l| l.visible).collect();
assert_eq!(visible, vec![true, true, false, true]);
}

#[test]
fn reads_dom_version_when_present() {
let xml = br#"<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging" DOMVersion="18.5">
<idPkg:Spread src="Spreads/Spread_u1.xml"/>
</Document>"#;
let dm = DesignMap::parse(xml).unwrap();
assert_eq!(dm.dom_version.as_deref(), Some("18.5"));
}

#[test]
fn dom_version_absent_is_none() {
// SAMPLE's <Document> carries no DOMVersion attribute.
let dm = DesignMap::parse(SAMPLE).unwrap();
assert_eq!(dm.dom_version, None);
}
}
25 changes: 25 additions & 0 deletions crates/paged-parse/src/spread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,12 @@ pub struct Page {
/// auto-page-number markers; if absent, it falls back to the
/// 1-based body page index.
pub name: Option<String>,
/// `ShowMasterItems` attribute on the `<Page>` element. When
/// `Some(false)` the page hides **all** master-spread overlay
/// items (InDesign's "Hide Master Items" per-page toggle); the
/// renderer skips stamping master frames/lines/text onto it.
/// `None`/`Some(true)` ⇒ stamp as usual.
pub show_master_items: Option<bool>,
}

#[derive(Debug, Clone, Serialize)]
Expand Down Expand Up @@ -1690,6 +1696,8 @@ impl Spread {
.map(|s| s.split_whitespace().map(str::to_string).collect())
.unwrap_or_default(),
name: attr(&e, b"Name"),
show_master_items: attr(&e, b"ShowMasterItems")
.and_then(|s| s.parse().ok()),
});
}
}
Expand Down Expand Up @@ -3160,6 +3168,23 @@ mod tests {
assert_eq!(s.text_frames[1].item_transform, None);
}

#[test]
fn parses_show_master_items_flag() {
let xml =
br#"<idPkg:Spread xmlns:idPkg="http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging">
<Spread Self="s">
<Page Self="p1" GeometricBounds="0 0 792 612" ShowMasterItems="false"/>
<Page Self="p2" GeometricBounds="0 0 792 612" ShowMasterItems="true"/>
<Page Self="p3" GeometricBounds="0 0 792 612"/>
</Spread>
</idPkg:Spread>"#;
let s = Spread::parse(xml).unwrap();
assert_eq!(s.pages.len(), 3);
assert_eq!(s.pages[0].show_master_items, Some(false));
assert_eq!(s.pages[1].show_master_items, Some(true));
assert_eq!(s.pages[2].show_master_items, None, "absent ⇒ stamp as usual");
}

#[test]
fn lifts_frames_out_of_groups_with_composed_transform() {
// Two levels of nesting: outer group translates by (10, 20),
Expand Down
15 changes: 15 additions & 0 deletions crates/paged-renderer/src/bin/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ fn main() -> Result<()> {
if !args.json {
println!("file {}", args.file.display());
println!("mimetype {}", document.container.mimetype);
if let Some(v) = document.container.designmap.dom_version.as_deref() {
println!("DOMVersion {v}");
}
println!(
"manifest {} spread(s), {} story ref(s), {} master(s)",
document.container.designmap.spreads.len(),
Expand Down Expand Up @@ -457,6 +460,16 @@ fn main() -> Result<()> {
g = built.stats.glyphs,
l = built.stats.lines,
);
// Overset signal: lines that fell past the last frame in a chain
// are dropped (matching InDesign's clipped PDF), but silently
// dropping them hides genuine overflow. Surface the count so the
// caller can tell "fit exactly" from "text was clipped".
if built.stats.dropped_overflow_lines > 0 {
println!(
" overset: {} line(s) dropped past the last frame (text clipped, not lost)",
built.stats.dropped_overflow_lines,
);
}
if want_display_list {
println!(
" display-list: {} command(s) total across {} page(s), {} path(s) total",
Expand Down Expand Up @@ -558,6 +571,7 @@ fn build_json_report(
json!({
"file": args.file,
"mimetype": document.container.mimetype,
"dom_version": document.container.designmap.dom_version,
"manifest": {
"spreads": document.container.designmap.spreads.len(),
"stories": document.container.designmap.stories.len(),
Expand All @@ -580,6 +594,7 @@ fn build_json_report(
"glyphs": built.stats.glyphs,
"lines": built.stats.lines,
"decoded_images": built.stats.decoded_images,
"dropped_overflow_lines": built.stats.dropped_overflow_lines,
"commands": total_cmds,
"unique_paths": total_paths,
},
Expand Down
Loading
Loading