1use std::cell::RefCell;
7
8use blockworx_geom::WorldPx;
9use blockworx_paint::text::{Glyph, Layout, Row, TextLayout};
10use blockworx_paint::{Color, Font, FontChoice};
11use egui::epaint::{
12 Galley,
13 text::{Fonts, TextOptions},
14};
15
16use crate::convert::{IntoEgui as _, IntoGeom as _};
17use crate::font::build_fonts;
18
19const PIXELS_PER_POINT: f32 = 1.0;
22
23pub struct EpaintLayout {
26 typeface: FontChoice,
27 fonts: RefCell<Fonts>,
30}
31
32impl EpaintLayout {
33 #[must_use]
34 pub fn new(typeface: FontChoice) -> Self {
35 Self {
36 typeface,
37 fonts: RefCell::new(Fonts::new(TextOptions::default(), build_fonts(typeface))),
38 }
39 }
40}
41
42impl TextLayout for EpaintLayout {
43 fn typeface(&self) -> FontChoice {
44 self.typeface
45 }
46
47 fn layout(&self, text: &str, font: &Font, wrap: WorldPx) -> Layout {
48 let galley = self
49 .fonts
50 .borrow_mut()
51 .with_pixels_per_point(PIXELS_PER_POINT)
52 .layout(
53 text.to_owned(),
54 font.egui(),
55 Color::PLACEHOLDER.egui(),
56 wrap.get(),
57 );
58 layout_of(&galley)
59 }
60}
61
62pub struct ContextLayout {
67 ctx: egui::Context,
68 typeface: FontChoice,
69}
70
71impl ContextLayout {
72 #[must_use]
75 pub fn new(ctx: &egui::Context, typeface: FontChoice) -> Self {
76 Self {
77 ctx: ctx.clone(),
78 typeface,
79 }
80 }
81}
82
83impl TextLayout for ContextLayout {
84 fn typeface(&self) -> FontChoice {
85 self.typeface
86 }
87
88 fn layout(&self, text: &str, font: &Font, wrap: WorldPx) -> Layout {
89 let galley = self.ctx.fonts_mut(|fonts| {
90 fonts.layout(
91 text.to_owned(),
92 font.egui(),
93 Color::PLACEHOLDER.egui(),
94 wrap.get(),
95 )
96 });
97 layout_of(&galley)
98 }
99}
100
101fn layout_of(galley: &Galley) -> Layout {
105 Layout {
106 size: galley.size().geom(),
107 rows: galley
108 .rows
109 .iter()
110 .map(|row| Row {
111 pos: row.pos.to_vec2().geom(),
112 height: row.size.y,
113 ends_with_newline: row.ends_with_newline,
114 glyphs: row
115 .glyphs
116 .iter()
117 .map(|glyph| Glyph {
118 chr: glyph.chr,
119 id: None,
122 pos: glyph.pos.to_vec2().geom(),
123 advance: glyph.advance_width,
124 ascent: glyph.font_ascent,
125 })
126 .collect(),
127 })
128 .collect(),
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
139 fn an_unbounded_wrap_is_the_no_wrap_layout() {
140 let engine = EpaintLayout::new(FontChoice::Basic);
141 let font = Font::canvas(12.0);
142 let unbounded = engine.layout("the quick brown fox", &font, WorldPx::UNBOUNDED);
143 let no_wrap = engine
144 .fonts
145 .borrow_mut()
146 .with_pixels_per_point(PIXELS_PER_POINT)
147 .layout_no_wrap(
148 "the quick brown fox".to_owned(),
149 (&font).egui(),
150 Color::PLACEHOLDER.egui(),
151 );
152 assert_eq!(unbounded.size, no_wrap.size().geom());
153 assert_eq!(
154 unbounded.rows.len(),
155 no_wrap.rows.len(),
156 "one row, unwrapped, either way",
157 );
158 }
159}