diff --git a/crates/xfa/src/layout.rs b/crates/xfa/src/layout.rs index 6de989bf4a..feb8a6fde3 100644 --- a/crates/xfa/src/layout.rs +++ b/crates/xfa/src/layout.rs @@ -1464,6 +1464,9 @@ impl Layouter<'_> { if *n == 1 { base } else { format!("{base}_{n}") } } + /// Draw a field in `rect`: its border and caption, then its value as text (non-interactive + /// fields, barcodes, text longer than a page) or as a widget. `radio` puts it in an + /// exclusion group; `sib` is its index in its SOM path (`name[sib]`). fn draw_field( &mut self, f: &Field, diff --git a/crates/xfa/src/model.rs b/crates/xfa/src/model.rs index 8ea85ccbcc..1c456a916e 100644 --- a/crates/xfa/src/model.rs +++ b/crates/xfa/src/model.rs @@ -180,6 +180,8 @@ impl Default for Font { } impl Font { + /// This font with the properties `spec` sets in place of its own; those `spec` leaves + /// `None` are inherited. pub fn apply(&self, spec: &FontSpec) -> Font { Font { typeface: spec.typeface.clone().unwrap_or_else(|| self.typeface.clone()), diff --git a/crates/xfa/src/parse.rs b/crates/xfa/src/parse.rs index 2996b4ffbc..3ebdc92c73 100644 --- a/crates/xfa/src/parse.rs +++ b/crates/xfa/src/parse.rs @@ -146,6 +146,8 @@ fn parse_border(n: Option) -> Option { Some(Border { presence: Presence::parse(n.attribute("presence")), edges: four, fill: parse_fill(child(n, "fill")) }) } +/// ``: typeface, size, weight, posture, underline, fill color and horizontal scale. +/// What the element doesn't set, or a missing element, is left `None` to inherit. fn parse_font(n: Option) -> FontSpec { let Some(n) = n else { return FontSpec::default() }; FontSpec { @@ -206,6 +208,8 @@ fn parse_rich(body: XmlNode) -> RichText { size: Option, h_scale: Option, } + /// `base` with the CSS declarations in `css` applied: weight, style, underline, size and + /// `xfa-font-horizontal-scale`. Anything else is ignored. fn styled(base: &Style, css: Option<&str>) -> Style { let mut s = base.clone(); for decl in css.unwrap_or("").split(';') { @@ -224,6 +228,9 @@ fn parse_rich(body: XmlNode) -> RichText { } // Paragraph boundaries from `p` are implicit (dropped when empty); `br` makes an explicit // line break that stays even when the line is blank. + /// Add the text under `n` to `out` as runs in `style`, as the elements it meets restyle it; + /// `p` and `br` start paragraphs (`explicit` marks the `br` ones) and `embed` spans add + /// embedded fields. Nothing deeper than `MAX_DEPTH` is read. fn walk(n: XmlNode, style: &Style, out: &mut RichText, explicit: &mut Vec, depth: usize) { if depth > MAX_DEPTH { return; diff --git a/crates/xfa/src/pdf.rs b/crates/xfa/src/pdf.rs index 593c78a80d..00d473f0da 100644 --- a/crates/xfa/src/pdf.rs +++ b/crates/xfa/src/pdf.rs @@ -570,6 +570,9 @@ impl Emitter<'_> { } } + /// Write one laid-out page under `pages_ref`: its fills, lines, text and pictures in a + /// content stream, the fonts and images it uses as resources, and its widgets as + /// annotations. Returns the page object. fn page(&mut self, page: &Page, pages_ref: ObjRef) -> Result { let page_ref = self.doc.add(Object::Dict(Dict::new())); let mut content: Vec = Vec::new(); diff --git a/crates/xfa/src/tests.rs b/crates/xfa/src/tests.rs index 41a6781f43..3d01245abd 100644 --- a/crates/xfa/src/tests.rs +++ b/crates/xfa/src/tests.rs @@ -51,6 +51,9 @@ fn scaled_text_template() -> (String, &'static str) { (xdp, TEXT) } +/// `fontHorizontalScale` is read as a percentage and text is measured at it: the 80% draw +/// fits its box on one line where the full-width one takes two, and a span's own scale +/// applies to that span alone. #[test] fn horizontally_scaled_text_is_measured_and_drawn_narrower() { let (xdp, text) = scaled_text_template(); @@ -80,6 +83,8 @@ fn horizontally_scaled_text_is_measured_and_drawn_narrower() { assert!((half.face.width("half") - full.face.width("half") / 2.0).abs() < 1e-9); } +/// A growing non-interactive field is measured in the face its text is drawn in: condensed, +/// its text takes fewer lines and the field grows only to hold them. #[test] fn a_non_interactive_field_is_measured_at_the_scale_its_text_is_drawn() { // A growing, non-interactive text field (drawn as text by the layout, not as a widget), @@ -111,6 +116,8 @@ fn a_non_interactive_field_is_measured_at_the_scale_its_text_is_drawn() { assert!((narrow_gap - full_gap).abs() < 0.01, "the field shrinks with its text: gap {narrow_gap} vs {full_gap}"); } +/// Condensed text is written with `Tz` inside a `q`/`Q` pair of its own, and full-width text +/// sets no scale, so none carries over from the text before it. #[test] fn horizontally_scaled_text_is_written_with_tz_inside_its_own_graphics_state() { let (xdp, _) = scaled_text_template(); diff --git a/crates/xfa/src/text.rs b/crates/xfa/src/text.rs index f880a945cf..05071a5418 100644 --- a/crates/xfa/src/text.rs +++ b/crates/xfa/src/text.rs @@ -81,6 +81,8 @@ pub struct Face { } impl Face { + /// The face a resolved font is drawn in: the standard-14 family its typeface maps to, + /// with its size and horizontal scale clamped to a drawable range. pub fn of(font: &Font) -> Face { Face { family: Family::of(&font.typeface), @@ -93,6 +95,8 @@ impl Face { } } + /// This face with what a rich-text run sets in place of its own: weight, posture, + /// underline, size and horizontal scale. The family and color stay. pub fn with_run(&self, run: &Run) -> Face { Face { family: self.family, @@ -111,6 +115,8 @@ impl Face { Face { h_scale: 1.0, ..self } } + /// The resource name of this face's standard font, as content streams and `/DA` strings + /// refer to it. pub fn resource_name(&self) -> &'static str { self.family.resource_name(self.bold, self.italic) }