Skip to main content

appcore_filemaker/
source_text.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: source_text.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/08/30 05:00:00 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/08/30 05:00:00 by dnettoRaw
8//      ###########      S: 1.0.2-rc
9// =============================================================================
10
11use serde::{Deserialize, Serialize};
12
13use crate::{
14    Alignment, ErrorCode, FileMakerError, Length, Result, TextIr, TextOverflow, Unit, WritingMode,
15};
16
17/// Declarative text measurement and overflow options.
18#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
19#[serde(default, deny_unknown_fields)]
20pub struct TextSourceOptions {
21    /// Overflow behavior.
22    pub overflow: TextOverflow,
23    /// Optional maximum number of resolved lines.
24    pub max_lines: Option<usize>,
25    /// Minimum size used by `shrink`; must be absolute.
26    pub min_font_size: Option<Length>,
27    /// Line-height multiplier in millionths.
28    pub line_height: u32,
29    /// Horizontal lines or top-to-bottom right-to-left vertical columns.
30    pub writing_mode: WritingMode,
31    /// Inline alignment applied after wrapping and shrink.
32    pub align_x: Alignment,
33    /// Symmetric inline-axis padding applied to every line.
34    pub padding_inline: Length,
35    /// Per-side padding around the measured text block.
36    pub padding: crate::TextBlockPadding,
37}
38
39impl Default for TextSourceOptions {
40    fn default() -> Self {
41        Self {
42            overflow: TextOverflow::Wrap,
43            max_lines: None,
44            min_font_size: Some(Length::Absolute(Unit::from_raw(6_000_000))),
45            line_height: 1_200_000,
46            writing_mode: WritingMode::Horizontal,
47            align_x: Alignment::Start,
48            padding_inline: Length::Absolute(Unit::ZERO),
49            padding: crate::TextBlockPadding::default(),
50        }
51    }
52}
53
54pub(crate) fn validate_text_options(options: &TextSourceOptions) -> Result<()> {
55    let minimum = options
56        .min_font_size
57        .map(|value| value.resolve(Unit::ZERO, Unit::ZERO))
58        .transpose()?
59        .flatten();
60    let invalid_padding = match options.padding_inline {
61        Length::Absolute(value) => value < Unit::ZERO,
62        Length::Logical(value) => value < 0,
63        Length::Percent(value) => !(0..500_000).contains(&value),
64        Length::Auto => true,
65    };
66    let block_padding = [
67        options.padding.top,
68        options.padding.right,
69        options.padding.bottom,
70        options.padding.left,
71    ];
72    let invalid_block_padding = block_padding.iter().any(|value| match value {
73        Length::Absolute(unit) => *unit < Unit::ZERO,
74        Length::Logical(units) => *units < 0,
75        Length::Percent(units) => !(0..500_000).contains(units),
76        Length::Auto => true,
77    });
78    if options.max_lines == Some(0)
79        || !(500_000..=4_000_000).contains(&options.line_height)
80        || minimum.is_some_and(|value| value <= Unit::ZERO)
81        || options
82            .min_font_size
83            .is_some_and(|value| !matches!(value, Length::Absolute(_)))
84        || invalid_padding
85        || invalid_block_padding
86    {
87        return Err(FileMakerError::new(
88            ErrorCode::SchemaField,
89            "text options contain an invalid line, font, or inline-padding constraint",
90        ));
91    }
92    Ok(())
93}
94
95pub(crate) fn convert_text_options(options: TextSourceOptions) -> TextIr {
96    TextIr {
97        overflow: options.overflow,
98        max_lines: options.max_lines,
99        min_font_size: options.min_font_size,
100        line_height: options.line_height,
101        writing_mode: options.writing_mode,
102        align_x: options.align_x,
103        padding_inline: options.padding_inline,
104        padding: options.padding,
105    }
106}