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
13 changes: 13 additions & 0 deletions src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ pub struct FieldSpec {
pub options: Vec<String>,
#[serde(default)]
pub default: Option<serde_json::Value>,
/// Visual slot constraints for autofit (schema option `"autofit"`):
/// `slot_width` / `slot_height` in px, `font_size` = base px, `line_height`
/// = px per line. When the option is present, the context gets
/// `<field>_font_scale` (0.1..1.0) — the factor the template's font-size
/// must be multiplied with so max-length content fits the slot.
#[serde(default)]
pub slot_width: Option<f32>,
#[serde(default)]
pub slot_height: Option<f32>,
#[serde(default)]
pub font_size: Option<f32>,
#[serde(default)]
pub line_height: Option<f32>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down
35 changes: 35 additions & 0 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,41 @@ pub fn process_template(
.collect(),
),
);

// Autofit: when the schema declares slot metrics AND the
// field opts in via options ["autofit"], compute the
// font-size scale factor so the wrapped text fits the
// slot. Longest-line width estimate uses a per-char width
// table (Inter-like: wide caps/digits, narrow i/l/j).
if field_spec.options.contains(&"autofit".to_string()) {
if let (Some(sw), Some(fs)) = (field_spec.slot_width, field_spec.font_size)
{
let lh = field_spec.line_height.unwrap_or(fs * 1.3);
let longest =
wrapped.iter().map(|l| l.chars().count()).max().unwrap_or(0) as f32;
let est_width = longest * fs * 0.52; // avg advance width for Inter-ish sans
let needed_h = wrapped.len() as f32 * lh;
let mut scale = 1.0f32;
if est_width > sw {
scale = scale.min(sw / est_width);
}
if let Some(sh) = field_spec.slot_height {
if needed_h > sh {
scale = scale.min(sh / needed_h);
}
}
let scale = scale.clamp(0.1, 1.0);
context.insert(
format!("{}_font_scale", field_name),
serde_json::Value::Number(
serde_json::Number::from_f64(
((scale * 1000.0).round() / 1000.0) as f64,
)
.expect("finite scale"),
),
);
}
}
}
}
}
Expand Down
Loading