1use super::*;
2
3impl TextShaper {
5 pub fn new(measurer: TextMeasurer, atlas: GlyphAtlas) -> Self {
12 Self { measurer, atlas }
13 }
14
15 pub fn measurer(&self) -> &TextMeasurer {
21 self.get_measurer()
22 }
23
24 pub fn atlas(&self) -> &GlyphAtlas {
30 self.get_atlas()
31 }
32
33 pub fn atlas_mut(&mut self) -> &mut GlyphAtlas {
39 self.get_mut_atlas()
40 }
41
42 pub fn shape(&mut self, text: &str, style: &TextStyle, x: f64, y: f64) -> TextRun {
62 if !style.rich.is_empty() && text.contains('{') {
63 return self.shape_rich(text, style, x, y);
64 }
65 self.shape_plain(text, style, x, y)
66 }
67 fn shape_plain(&mut self, text: &str, style: &TextStyle, x: f64, y: f64) -> TextRun {
79 let lines: Vec<&str> = text.split('\n').collect();
80 let font: String = style.font();
81 let mut quads: Vec<GlyphQuad> = Vec::new();
82 let mut widths: Vec<f64> = Vec::with_capacity(lines.len());
83 let mut ascent: f64 = 0.0_f64;
84 let mut descent: f64 = 0.0_f64;
85 let line_height: f64 = style.line_height_px();
86 for line in &lines {
87 let metrics: TextMetrics = self.get_measurer().measure(line, &font);
88 widths.push(metrics.width);
89 ascent = ascent.max(metrics.ascent);
90 descent = descent.max(metrics.descent);
91 }
92 let block_height: f64 = line_height * lines.len() as f64;
93 let top: f64 = match style.vertical_align {
94 TextVerticalAlign::Top => y,
95 TextVerticalAlign::Middle => y - block_height * 0.5,
96 TextVerticalAlign::Bottom => y - block_height,
97 TextVerticalAlign::Baseline => y - ascent,
98 };
99 let mut shaped_lines: Vec<TextLine> = Vec::with_capacity(lines.len());
100 for (index, line) in lines.iter().enumerate() {
101 let line_width: f64 = widths[index];
102 let left: f64 = match style.align {
103 TextAlign::Left => x,
104 TextAlign::Center => x - line_width * 0.5,
105 TextAlign::Right => x - line_width,
106 };
107 let baseline: f64 = top + line_height * index as f64 + ascent;
108 self.shape_line(line, &font, left, baseline, 0, &mut quads);
109 shaped_lines.push(TextLine {
110 text: (*line).to_string(),
111 left,
112 baseline,
113 width: line_width,
114 });
115 }
116 TextRun {
117 quads,
118 lines: shaped_lines,
119 width: widths.iter().copied().fold(0.0_f64, f64::max),
120 height: block_height,
121 ascent,
122 descent,
123 line_height,
124 line_count: lines.len(),
125 }
126 }
127 fn shape_rich(&mut self, text: &str, style: &TextStyle, x: f64, y: f64) -> TextRun {
143 let runs: Vec<(Option<String>, String)> = split_rich_text(text);
144 let mut quads: Vec<GlyphQuad> = Vec::new();
145 let mut shaped_lines: Vec<TextLine> = Vec::new();
146 let mut total_width: f64 = 0.0_f64;
147 let mut ascent: f64 = 0.0_f64;
148 let mut descent: f64 = 0.0_f64;
149 let mut pen_x: f64 = x;
150 for (name, content) in runs {
151 let mut run_style: TextStyle = style.clone();
152 if let Some(name) = &name
153 && let Some((_, rich)) = style
154 .rich
155 .iter()
156 .find(|(key, _): &&(String, RichTextStyle)| key == name)
157 {
158 if let Some(family) = &rich.family {
159 run_style.family = family.clone();
160 }
161 if let Some(size) = rich.size {
162 run_style.size = size;
163 }
164 if let Some(weight) = &rich.weight {
165 run_style.weight = weight.clone();
166 }
167 if let Some(value) = &rich.style {
168 run_style.style = value.clone();
169 }
170 if let Some(color) = rich.color {
171 run_style.color = color;
172 }
173 }
174 let color: u32 = run_style.color.to_u32();
175 let font: String = run_style.font();
176 let metrics: TextMetrics = self.get_measurer().measure(&content, &font);
177 ascent = ascent.max(metrics.ascent);
178 descent = descent.max(metrics.descent);
179 total_width += metrics.width;
180 let baseline: f64 = y + metrics.ascent;
181 self.shape_line(&content, &font, pen_x, baseline, color, &mut quads);
182 shaped_lines.push(TextLine {
183 text: content,
184 left: pen_x,
185 baseline,
186 width: metrics.width,
187 });
188 pen_x += metrics.width;
189 }
190 let line_count: usize = shaped_lines.len();
191 TextRun {
192 quads,
193 lines: shaped_lines,
194 width: total_width,
195 height: ascent + descent,
196 ascent,
197 descent,
198 line_height: style.line_height_px(),
199 line_count,
200 }
201 }
202 fn shape_line(
211 &mut self,
212 line: &str,
213 font: &str,
214 left: f64,
215 baseline: f64,
216 color: u32,
217 quads: &mut Vec<GlyphQuad>,
218 ) {
219 let mut pen: f32 = left as f32;
220 for character in line.chars() {
221 let entry: GlyphEntry = self.get_mut_atlas().glyph(character, font);
222 if entry.width > 0.0 && entry.height > 0.0 {
223 quads.push(GlyphQuad {
224 x: pen + entry.bearing_x,
225 y: baseline as f32 - entry.bearing_y,
226 width: entry.width,
227 height: entry.height,
228 u: entry.u,
229 v: entry.v,
230 u_size: entry.width / self.get_atlas().width() as f32,
231 v_size: entry.height / self.get_atlas().height() as f32,
232 color,
233 });
234 }
235 pen += entry.advance;
236 }
237 }
238}
239
240impl GlyphAtlas {
242 pub fn new(width: u32, height: u32) -> Option<Self> {
254 let document: Document = web_sys::window()?.document()?;
255 let canvas: HtmlCanvasElement = document
256 .create_element(CANVAS_TAG)
257 .ok()?
258 .dyn_into::<HtmlCanvasElement>()
259 .ok()?;
260 canvas.set_width(width);
261 canvas.set_height(height);
262 let context: CanvasRenderingContext2d = canvas
263 .get_context("2d")
264 .ok()
265 .flatten()?
266 .dyn_into::<CanvasRenderingContext2d>()
267 .ok()?;
268 Some(Self {
269 canvas,
270 context,
271 entries: HashMap::new(),
272 cursor_x: 0.0,
273 cursor_y: 0.0,
274 shelf_height: 0.0,
275 width,
276 height,
277 dirty: false,
278 })
279 }
280
281 pub fn canvas(&self) -> &HtmlCanvasElement {
289 self.get_canvas()
290 }
291
292 pub fn width(&self) -> u32 {
298 self.get_width()
299 }
300
301 pub fn height(&self) -> u32 {
307 self.get_height()
308 }
309
310 pub fn is_dirty(&self) -> bool {
316 self.get_dirty()
317 }
318
319 pub fn mark_clean(&mut self) {
321 self.set_dirty(false);
322 }
323
324 pub fn len(&self) -> usize {
330 self.get_entries().len()
331 }
332
333 pub fn is_empty(&self) -> bool {
339 self.get_entries().is_empty()
340 }
341
342 pub fn glyph(&mut self, character: char, font: &str) -> GlyphEntry {
354 let key: GlyphKey = GlyphKey {
355 character,
356 font: font.to_string(),
357 };
358 if let Some(entry) = self.get_entries().get(&key) {
359 return *entry;
360 }
361 let entry: GlyphEntry = self.rasterize(character, font);
362 self.get_mut_entries().insert(key, entry);
363 entry
364 }
365
366 fn rasterize(&mut self, character: char, font: &str) -> GlyphEntry {
377 self.get_context().set_font(font);
378 let Ok(metrics) = self.get_context().measure_text(&character.to_string()) else {
379 return GlyphEntry {
380 u: 0.0,
381 v: 0.0,
382 width: 0.0,
383 height: 0.0,
384 bearing_x: 0.0,
385 bearing_y: 0.0,
386 advance: 0.0,
387 };
388 };
389 let advance: f32 = metrics.width() as f32;
390 let left: f32 = metrics.actual_bounding_box_left() as f32;
391 let right: f32 = metrics.actual_bounding_box_right() as f32;
392 let ascent: f32 = metrics.actual_bounding_box_ascent() as f32;
393 let descent: f32 = metrics.actual_bounding_box_descent() as f32;
394 let glyph_width: f32 = (left + right).ceil().max(0.0);
395 let glyph_height: f32 = (ascent + descent).ceil().max(0.0);
396 if glyph_width <= 0.0 || glyph_height <= 0.0 {
398 return GlyphEntry {
399 u: 0.0,
400 v: 0.0,
401 width: 0.0,
402 height: 0.0,
403 bearing_x: 0.0,
404 bearing_y: 0.0,
405 advance,
406 };
407 }
408 let padding: f32 = GLYPH_PADDING;
409 let cell_width: f32 = glyph_width + padding * 2.0;
410 let cell_height: f32 = glyph_height + padding * 2.0;
411 if self.get_cursor_x() + cell_width > self.get_width() as f32 {
412 self.set_cursor_x(0.0);
413 self.set_cursor_y(self.get_cursor_y() + self.get_shelf_height());
414 self.set_shelf_height(0.0);
415 }
416 if self.get_cursor_y() + cell_height > self.get_height() as f32 {
417 self.reset();
422 }
423 let x: f32 = self.get_cursor_x();
424 let y: f32 = self.get_cursor_y();
425 self.set_cursor_x(self.get_cursor_x() + cell_width);
426 self.set_shelf_height(self.get_shelf_height().max(cell_height));
427 let pen_x: f32 = x + padding + left;
430 let baseline: f32 = y + padding + ascent;
431 self.get_context().set_font(font);
432 self.get_context().set_fill_style_str("#ffffff");
433 let _: Result<(), JsValue> =
434 self.get_context()
435 .fill_text(&character.to_string(), pen_x as f64, baseline as f64);
436 self.set_dirty(true);
437 GlyphEntry {
438 u: x / self.get_width() as f32,
439 v: y / self.get_height() as f32,
440 width: glyph_width,
441 height: glyph_height,
442 bearing_x: -(left + padding),
443 bearing_y: ascent + padding,
444 advance,
445 }
446 }
447
448 fn reset(&mut self) {
450 self.get_context()
451 .clear_rect(0.0, 0.0, self.get_width() as f64, self.get_height() as f64);
452 self.get_mut_entries().clear();
453 self.set_cursor_x(0.0);
454 self.set_cursor_y(0.0);
455 self.set_shelf_height(0.0);
456 self.set_dirty(true);
457 }
458}
459
460impl Debug for TextMeasurer {
462 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472 formatter
473 .debug_struct("TextMeasurer")
474 .field("cached", &self.get_cache().borrow().len())
475 .finish()
476 }
477}
478
479impl TextMeasurer {
481 pub fn new() -> Option<Self> {
490 let document: Document = web_sys::window()?.document()?;
491 let canvas: HtmlCanvasElement = document
492 .create_element(CANVAS_TAG)
493 .ok()?
494 .dyn_into::<HtmlCanvasElement>()
495 .ok()?;
496 canvas.set_width(1);
497 canvas.set_height(1);
498 let context: CanvasRenderingContext2d = canvas
499 .get_context("2d")
500 .ok()
501 .flatten()?
502 .dyn_into::<CanvasRenderingContext2d>()
503 .ok()?;
504 Some(Self {
505 context: Some(context),
506 cache: Rc::new(RefCell::new(HashMap::new())),
507 })
508 }
509
510 pub fn approximate() -> Self {
517 Self {
518 context: None,
519 cache: Rc::new(RefCell::new(HashMap::new())),
520 }
521 }
522
523 pub fn measure(&self, text: &str, font: &str) -> TextMetrics {
533 if text.is_empty() {
534 return TextMetrics::default();
535 }
536 let key: (String, String) = (text.to_string(), font.to_string());
537 if let Some(metrics) = self.get_cache().borrow().get(&key) {
538 return *metrics;
539 }
540 let metrics: TextMetrics = self.measure_uncached(text, font);
541 self.get_cache().borrow_mut().insert(key, metrics);
542 metrics
543 }
544
545 fn measure_uncached(&self, text: &str, font: &str) -> TextMetrics {
555 let size: f64 = font_size_of(font);
556 let Some(context) = self.try_get_context() else {
557 return approximate_metrics(text, size);
558 };
559 context.set_font(font);
560 let Ok(metrics) = context.measure_text(text) else {
561 return approximate_metrics(text, size);
562 };
563 let ascent: f64 = metrics.actual_bounding_box_ascent();
564 let descent: f64 = metrics.actual_bounding_box_descent();
565 TextMetrics {
566 width: metrics.width(),
567 height: ascent + descent,
568 ascent,
569 descent,
570 line_height: size * 1.2,
571 }
572 }
573
574 pub fn cache_size(&self) -> usize {
580 self.get_cache().borrow().len()
581 }
582
583 pub fn clear_cache(&self) {
585 self.get_cache().borrow_mut().clear();
586 }
587}