text.rs

  1use crate::{
  2    color::Color,
  3    font_cache::FamilyId,
  4    fonts::TextStyle,
  5    geometry::{
  6        rect::RectF,
  7        vector::{vec2f, Vector2F},
  8    },
  9    json::{ToJson, Value},
 10    text_layout::{Line, ShapedBoundary},
 11    DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
 12};
 13use serde_json::json;
 14
 15pub struct Text {
 16    text: String,
 17    family_id: FamilyId,
 18    font_size: f32,
 19    style: TextStyle,
 20}
 21
 22pub struct LayoutState {
 23    lines: Vec<(Line, Vec<ShapedBoundary>)>,
 24    line_height: f32,
 25}
 26
 27impl Text {
 28    pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self {
 29        Self {
 30            text,
 31            family_id,
 32            font_size,
 33            style: Default::default(),
 34        }
 35    }
 36
 37    pub fn with_style(mut self, style: &TextStyle) -> Self {
 38        self.style = style.clone();
 39        self
 40    }
 41
 42    pub fn with_default_color(mut self, color: Color) -> Self {
 43        self.style.color = color;
 44        self
 45    }
 46}
 47
 48impl Element for Text {
 49    type LayoutState = LayoutState;
 50    type PaintState = ();
 51
 52    fn layout(
 53        &mut self,
 54        constraint: SizeConstraint,
 55        cx: &mut LayoutContext,
 56    ) -> (Vector2F, Self::LayoutState) {
 57        let font_id = cx
 58            .font_cache
 59            .select_font(self.family_id, &self.style.font_properties)
 60            .unwrap();
 61        let line_height = cx.font_cache.line_height(font_id, self.font_size);
 62
 63        let mut wrapper = cx.font_cache.line_wrapper(font_id, self.font_size);
 64        let mut lines = Vec::new();
 65        let mut line_count = 0;
 66        let mut max_line_width = 0_f32;
 67        for line in self.text.lines() {
 68            let shaped_line = cx.text_layout_cache.layout_str(
 69                line,
 70                self.font_size,
 71                &[(line.len(), font_id, self.style.color)],
 72            );
 73            let wrap_boundaries = wrapper
 74                .wrap_shaped_line(line, &shaped_line, constraint.max.x())
 75                .collect::<Vec<_>>();
 76
 77            max_line_width = max_line_width.max(shaped_line.width());
 78            line_count += wrap_boundaries.len() + 1;
 79            lines.push((shaped_line, wrap_boundaries));
 80        }
 81
 82        let size = vec2f(
 83            max_line_width
 84                .ceil()
 85                .max(constraint.min.x())
 86                .min(constraint.max.x()),
 87            (line_height * line_count as f32).ceil(),
 88        );
 89        (size, LayoutState { lines, line_height })
 90    }
 91
 92    fn paint(
 93        &mut self,
 94        bounds: RectF,
 95        layout: &mut Self::LayoutState,
 96        cx: &mut PaintContext,
 97    ) -> Self::PaintState {
 98        let mut origin = bounds.origin();
 99        for (line, wrap_boundaries) in &layout.lines {
100            line.paint_wrapped(
101                origin,
102                layout.line_height,
103                wrap_boundaries.iter().copied(),
104                cx,
105            );
106            origin.set_y(origin.y() + (wrap_boundaries.len() + 1) as f32 * layout.line_height);
107        }
108    }
109
110    fn dispatch_event(
111        &mut self,
112        _: &Event,
113        _: RectF,
114        _: &mut Self::LayoutState,
115        _: &mut Self::PaintState,
116        _: &mut EventContext,
117    ) -> bool {
118        false
119    }
120
121    fn debug(
122        &self,
123        bounds: RectF,
124        _: &Self::LayoutState,
125        _: &Self::PaintState,
126        cx: &DebugContext,
127    ) -> Value {
128        json!({
129            "type": "Label",
130            "bounds": bounds.to_json(),
131            "text": &self.text,
132            "font_family": cx.font_cache.family_name(self.family_id).unwrap(),
133            "font_size": self.font_size,
134            "style": self.style.to_json(),
135        })
136    }
137}