summaryrefslogtreecommitdiff
path: root/rust/crates/text/src/layout.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/crates/text/src/layout.rs')
-rw-r--r--rust/crates/text/src/layout.rs163
1 files changed, 163 insertions, 0 deletions
diff --git a/rust/crates/text/src/layout.rs b/rust/crates/text/src/layout.rs
new file mode 100644
index 00000000..c0e3a198
--- /dev/null
+++ b/rust/crates/text/src/layout.rs
@@ -0,0 +1,163 @@
+use crate::params::{
+ FONT_SIZE_SCALE_REFERENCE, TextAlign, TextBackground, TextDecoration, TextLayoutParams,
+};
+
+pub const TEXT_DECORATION_THICKNESS_RATIO: f64 = 0.07;
+pub const STRIKETHROUGH_VERTICAL_RATIO: f64 = 0.35;
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct ResolvedTextLayout {
+ pub scaled_font_size: f64,
+ pub letter_spacing: f64,
+ pub line_height_px: f64,
+ pub font_size_ratio: f64,
+ pub text_align: TextAlign,
+ pub text_decoration: TextDecoration,
+}
+
+pub fn resolve_text_layout(text: &TextLayoutParams, canvas_height: f64) -> ResolvedTextLayout {
+ let scaled_font_size = text.font_size * (canvas_height / FONT_SIZE_SCALE_REFERENCE);
+ ResolvedTextLayout {
+ scaled_font_size,
+ letter_spacing: text.letter_spacing,
+ line_height_px: scaled_font_size * text.line_height,
+ font_size_ratio: text.font_size / 15.0,
+ text_align: text.text_align,
+ text_decoration: text.text_decoration,
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct LineMetrics {
+ pub width: f64,
+ pub ascent: f64,
+ pub descent: f64,
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct TextBlockMeasurement {
+ pub visual_center_offset: f64,
+ pub height: f64,
+ pub max_width: f64,
+}
+
+pub fn measure_text_block(
+ line_metrics: &[LineMetrics],
+ line_height_px: f64,
+) -> TextBlockMeasurement {
+ let max_width = line_metrics
+ .iter()
+ .fold(0.0_f64, |max, metrics| max.max(metrics.width));
+ let line_count = line_metrics.len() as f64;
+ TextBlockMeasurement {
+ visual_center_offset: ((line_count - 1.0) * line_height_px) / 2.0,
+ height: line_count * line_height_px,
+ max_width,
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct Rect {
+ pub left: f64,
+ pub top: f64,
+ pub width: f64,
+ pub height: f64,
+}
+
+impl Rect {
+ pub fn right(self) -> f64 {
+ self.left + self.width
+ }
+
+ pub fn bottom(self) -> f64 {
+ self.top + self.height
+ }
+}
+
+pub fn align_offset(text_align: TextAlign, line_width: f64) -> f64 {
+ match text_align {
+ TextAlign::Left => 0.0,
+ TextAlign::Center => -line_width / 2.0,
+ TextAlign::Right => -line_width,
+ }
+}
+
+pub fn text_rect(text_align: TextAlign, block: &TextBlockMeasurement) -> Rect {
+ Rect {
+ left: align_offset(text_align, block.max_width),
+ top: -block.height / 2.0,
+ width: block.max_width,
+ height: block.height,
+ }
+}
+
+fn background_visible(background: &TextBackground) -> bool {
+ background.enabled && !background.color.is_empty() && background.color != "transparent"
+}
+
+pub fn text_background_rect(
+ text_align: TextAlign,
+ block: &TextBlockMeasurement,
+ background: &TextBackground,
+ font_size_ratio: f64,
+) -> Option<Rect> {
+ if !background_visible(background) {
+ return None;
+ }
+ let rect = text_rect(text_align, block);
+ let padding_x = background.padding_x * font_size_ratio;
+ let padding_y = background.padding_y * font_size_ratio;
+ Some(Rect {
+ left: rect.left - padding_x + background.offset_x,
+ top: rect.top - padding_y + background.offset_y,
+ width: rect.width + padding_x * 2.0,
+ height: rect.height + padding_y * 2.0,
+ })
+}
+
+pub fn text_visual_rect(
+ text_align: TextAlign,
+ block: &TextBlockMeasurement,
+ background: &TextBackground,
+ font_size_ratio: f64,
+) -> Rect {
+ let rect = text_rect(text_align, block);
+ let Some(background_rect) = text_background_rect(text_align, block, background, font_size_ratio)
+ else {
+ return rect;
+ };
+ let left = rect.left.min(background_rect.left);
+ let top = rect.top.min(background_rect.top);
+ Rect {
+ left,
+ top,
+ width: rect.right().max(background_rect.right()) - left,
+ height: rect.bottom().max(background_rect.bottom()) - top,
+ }
+}
+
+pub fn decoration_rect(
+ text_decoration: TextDecoration,
+ text_align: TextAlign,
+ line_width: f64,
+ line_y: f64,
+ ascent: f64,
+ descent: f64,
+ scaled_font_size: f64,
+) -> Option<Rect> {
+ if text_decoration == TextDecoration::None {
+ return None;
+ }
+ let thickness = (scaled_font_size * TEXT_DECORATION_THICKNESS_RATIO).max(1.0);
+ let top = match text_decoration {
+ TextDecoration::Underline => line_y + descent + thickness,
+ TextDecoration::LineThrough => line_y - (ascent - descent) * STRIKETHROUGH_VERTICAL_RATIO,
+ TextDecoration::None => unreachable!(),
+ };
+ Some(Rect {
+ left: align_offset(text_align, line_width),
+ top,
+ width: line_width,
+ height: thickness,
+ })
+}