M12: doc comments for the functions around this change

This commit is contained in:
Eric Kaden
2026-10-20 09:24:47 -04:00
parent 15ab8c7a2c
commit 15eef03efc
4 changed files with 28 additions and 0 deletions
+15
View File
@@ -1004,6 +1004,9 @@ impl Layouter<'_> {
}
}
/// The height a value takes at width `w` in the context's font and paragraph: its text as
/// measured, a picture scaled to `w` keeping its shape, nothing for a line, a rectangle or
/// no value.
fn value_height(&self, v: &Value, w: f64, ctx: &Ctx) -> f64 {
match v {
Value::Text(t) => text::measure_height(&text::plain(t), w, &ctx.para, &Face::of(&ctx.font), &|id| self.embed(id)),
@@ -1013,6 +1016,10 @@ impl Layouter<'_> {
}
}
/// Where a field's caption goes and the room it takes there: its reserve, else the height
/// of its text above or below, or beside it its text's width plus 2pt, at most 60% of
/// `inner_w`. An inline caption goes on the left. `None` for a button, a caption that takes
/// no space, or one with neither text nor a reserve.
fn caption_reserve(&self, f: &Field, inner_w: f64, ctx: &Ctx) -> Option<(Placement, f64)> {
let cap = f.caption.as_ref()?;
if !cap.presence.occupies() || f.ui == Ui::Button {
@@ -1037,6 +1044,9 @@ impl Layouter<'_> {
Some((placement, reserve))
}
/// The height of a field at `inner_w`: its widget (a check box's size, or its lines of text,
/// grown to hold a long value, plus padding and the widget margin), plus a caption above or
/// below it. A caption beside it makes it at least as tall as the caption's text.
fn field_height(&self, f: &Field, inner_w: f64, ctx: &Ctx) -> f64 {
let font = ctx.font.apply(&f.common.font);
let lines = if f.multiline { 3.0 } else { 1.0 };
@@ -1089,6 +1099,8 @@ impl Layouter<'_> {
text::measure_height(&text::plain(value.trim_end()), w, &ctx.para, &Self::field_text_face(f, &font), &|_| String::new())
}
/// The height of an exclusion group's fields at `inner_w`, by the group's layout: stacked
/// top to bottom, at their own positions, or packed in rows.
fn group_height(&self, g: &ExclGroup, inner_w: f64, ctx: &Ctx, depth: usize) -> f64 {
let nodes: Vec<Node> = g.fields.iter().map(|f| Node::Field(Box::new(f.clone()))).collect();
match g.layout {
@@ -1457,6 +1469,9 @@ impl Layouter<'_> {
self.push(Item::Image { rect: fitted, data: Arc::new(data.to_vec()), content_type: content_type.to_string() })
}
/// The widget name for a field or group named `base`, cleaned (dots become underscores,
/// control characters go): the first use is the name itself, later ones `name_2`, `name_3`
/// and so on.
fn unique_name(&mut self, base: &str) -> String {
let base = clean_name(base);
let n = self.names.entry(base.clone()).or_insert(0);
+3
View File
@@ -40,6 +40,8 @@ pub fn measure(s: &str) -> Option<f64> {
Some((v * k).clamp(-100_000.0, 100_000.0))
}
/// The attribute `name` of `n` read as a measurement in points (see [`measure`]); `None`
/// when it is missing or unreadable.
fn attr_measure(n: XmlNode, name: &str) -> Option<f64> {
n.attribute(name).and_then(measure)
}
@@ -51,6 +53,7 @@ fn scale(v: &str) -> Option<f64> {
(v.is_finite() && v > 0.0).then(|| (v / 100.0).clamp(0.01, 10.0))
}
/// The first child element of `n` named `name` (by local name, in any namespace).
fn child<'a, 'd>(n: XmlNode<'a, 'd>, name: &str) -> Option<XmlNode<'a, 'd>> {
n.children().find(|c| c.is_element() && c.tag_name().name() == name)
}
+3
View File
@@ -509,6 +509,9 @@ impl Emitter<'_> {
Some(r)
}
/// Write a new image XObject for a picture, tagged with its `hash`: a JPEG as it is, a PNG
/// or GIF decoded to samples with a soft mask for any transparency. `None`, with a warning,
/// when the picture can't be read or is another kind.
fn new_image_xobject(&mut self, data: &[u8], content_type: &str, hash: &[u8; 32]) -> Option<ObjRef> {
let what = if content_type.is_empty() { "picture" } else { content_type };
let mut d = Dict::new();
+7
View File
@@ -13,14 +13,18 @@ use crate::layout::WidgetKind;
use crate::model::*;
use crate::script::{NodeKind, Overrides, apply_overrides, fields_by_som, form_tree, overrides, rerender, set_overrides};
/// The widgets laid out on `page`, in order.
fn widgets(page: &Page) -> Vec<&Widget> {
page.items.iter().filter_map(|i| if let Item::Widget(w) = i { Some(w.as_ref()) } else { None }).collect()
}
/// The text of each text span laid out on `page`, in order.
fn texts(page: &Page) -> Vec<String> {
page.items.iter().filter_map(|i| if let Item::Text(s) = i { Some(s.text.clone()) } else { None }).collect()
}
/// Measurements in points, inches, millimeters and centimeters convert to points, a bare
/// number is points, and an `em` length, a non-number or a NaN is `None`.
#[test]
fn measurements_convert_to_points() {
assert_eq!(measure("72pt"), Some(72.0));
@@ -137,6 +141,9 @@ fn horizontally_scaled_text_is_written_with_tz_inside_its_own_graphics_state() {
assert!(ops.lines().filter(|l| l.contains(" Tj ") && !l.contains(" Tz ")).all(|l| l.starts_with("BT ")), "{ops}");
}
/// The sample template parses without warnings into what the layout uses: its page areas,
/// page-number draws, rich-text title, field font, caption and widget border, check box,
/// date field, radio group, table columns and page break all come through.
#[test]
fn the_template_parses_with_fonts_captions_and_rich_text() {
let (tpl, warnings) = parse(&template(2)).unwrap();