//! Page geometry and text-width estimation. //! //! We use approximate Helvetica metrics rather than pulling real AFM tables //! or a font-shaping crate. The approximation slightly overcounts width so //! we err on the side of shorter lines rather than clipping the right edge. use printpdf::Mm; pub(crate) const PAGE_W: Mm = Mm(148.0); pub(crate) const PAGE_H: Mm = Mm(210.0); pub(crate) const MARGIN: Mm = Mm(14.0); pub(crate) const BODY_PT: f32 = 11.0; pub(crate) const H1_PT: f32 = 20.0; pub(crate) const H2_PT: f32 = 16.0; pub(crate) const H3_PT: f32 = 13.0; pub(crate) const MONO_PT: f32 = 10.0; pub(crate) const LINE_GAP: f32 = 1.35; pub(crate) const BLOCK_GAP_MM: f32 = 3.5; pub(crate) const HEADING_GAP_MM: f32 = 5.5; pub(crate) const LIST_INDENT_MM: f32 = 6.0; pub(crate) const QUOTE_INDENT_MM: f32 = 5.0; pub(crate) fn content_width_mm() -> f32 { PAGE_W.0 - MARGIN.0 * 2.0 } pub(crate) fn content_top_mm() -> f32 { PAGE_H.0 - MARGIN.0 } pub(crate) fn content_bottom_mm() -> f32 { MARGIN.0 } /// Approximate width in mm of `text` rendered in Helvetica at `pt`. /// /// Uses a per-family average advance width of 0.52em (bold slightly wider). /// 1pt = 25.4/72 mm. pub(crate) fn text_width_mm(text: &str, pt: f32, bold: bool, mono: bool) -> f32 { let avg_em = if mono { 0.60 } else if bold { 0.56 } else { 0.52 }; let mm_per_pt = 25.4 / 72.0; text.chars().count() as f32 * pt * avg_em * mm_per_pt } pub(crate) fn line_height_mm(pt: f32) -> f32 { pt * LINE_GAP * 25.4 / 72.0 } #[cfg(test)] mod tests { use super::*; #[test] fn width_grows_with_length_and_size() { let a = text_width_mm("hello", BODY_PT, false, false); let b = text_width_mm("hello world", BODY_PT, false, false); let c = text_width_mm("hello", H1_PT, false, false); assert!(b > a); assert!(c > a); } #[test] fn bold_wider_than_regular() { let r = text_width_mm("hello", BODY_PT, false, false); let b = text_width_mm("hello", BODY_PT, true, false); assert!(b > r); } }