1use super::{
4 DeterministicTextMeasurer, FLOWCHART_DEFAULT_FONT_KEY, TextMeasurer, TextMetrics, TextStyle,
5 WrapMode, flowchart_default_bold_delta_em, flowchart_default_bold_kern_delta_em,
6 flowchart_default_bold_svg_right_overhang_em, font_key_uses_courier_metrics,
7 is_flowchart_default_font, overrides, round_to_1_64_px, style_requests_bold_font_weight,
8 svg_wrapped_first_line_bbox_height_px,
9};
10
11#[derive(Debug, Clone, Default)]
12pub struct VendoredFontMetricsTextMeasurer {
13 fallback: DeterministicTextMeasurer,
14}
15
16#[derive(Clone, Copy)]
17struct FontMetricProfile<'a> {
18 entries: &'a [(char, f64)],
19 default_em: f64,
20 kern_pairs: &'a [(u32, u32, f64)],
21 space_trigrams: &'a [(u32, u32, f64)],
22 trigrams: &'a [(u32, u32, u32, f64)],
23 missing_v_comma_kern_em: f64,
24 missing_t_o_kern_em: f64,
25 missing_t_r_kern_em: f64,
26 missing_space_before_capital_a_em: f64,
27 missing_space_after_capital_a_before_open_paren_em: f64,
28}
29
30impl VendoredFontMetricsTextMeasurer {
31 fn metric_profile(
32 table: &crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables,
33 ) -> FontMetricProfile<'_> {
34 FontMetricProfile {
35 entries: table.entries,
36 default_em: table.default_em.max(0.1),
37 kern_pairs: table.kern_pairs,
38 space_trigrams: table.space_trigrams,
39 trigrams: table.trigrams,
40 missing_v_comma_kern_em: if table.font_key == FLOWCHART_DEFAULT_FONT_KEY {
41 -140.0 / 1024.0
42 } else {
43 0.0
44 },
45 missing_t_o_kern_em: if table.font_key == FLOWCHART_DEFAULT_FONT_KEY {
46 -128.0 / 1024.0
47 } else {
48 0.0
49 },
50 missing_t_r_kern_em: if table.font_key == FLOWCHART_DEFAULT_FONT_KEY {
51 -113.0 / 1024.0
52 } else {
53 0.0
54 },
55 missing_space_before_capital_a_em: if table.font_key
56 == "trebuchetms,verdana,arial,sans-serif"
57 {
58 -57.0 / 1024.0
59 } else {
60 0.0
61 },
62 missing_space_after_capital_a_before_open_paren_em: if table.font_key
63 == "trebuchetms,verdana,arial,sans-serif"
64 {
65 -57.0 / 1024.0
66 } else {
67 0.0
68 },
69 }
70 }
71
72 pub(super) fn quantize_svg_bbox_px_nearest(v: f64) -> f64 {
73 if !(v.is_finite() && v >= 0.0) {
74 return 0.0;
75 }
76 let x = v * 1024.0;
80 let f = x.floor();
81 let frac = x - f;
82 let i = if frac < 0.5 {
83 f
84 } else if frac > 0.5 {
85 f + 1.0
86 } else {
87 let fi = f as i64;
88 if fi % 2 == 0 { f } else { f + 1.0 }
89 };
90 i / 1024.0
91 }
92
93 fn quantize_svg_half_px_nearest(half_px: f64) -> f64 {
94 if !(half_px.is_finite() && half_px >= 0.0) {
95 return 0.0;
96 }
97 (half_px * 256.0).floor() / 256.0
101 }
102
103 fn normalize_font_key(s: &str) -> String {
104 s.chars()
105 .filter_map(|ch| {
106 if ch.is_whitespace() || ch == '"' || ch == '\'' || ch == ';' {
110 None
111 } else {
112 Some(ch.to_ascii_lowercase())
113 }
114 })
115 .collect()
116 }
117
118 fn lookup_table(
119 &self,
120 style: &TextStyle,
121 ) -> Option<&'static crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables> {
122 let key = style
123 .font_family
124 .as_deref()
125 .map(Self::normalize_font_key)
126 .unwrap_or_default();
127 let key = if key.is_empty() {
128 FLOWCHART_DEFAULT_FONT_KEY
131 } else {
132 key.as_str()
133 };
134 if let Some(t) = crate::generated::font_metrics_flowchart_11_12_2::lookup_font_metrics(key)
135 {
136 return Some(t);
137 }
138
139 let key_lower = key;
142 if font_key_uses_courier_metrics(key_lower) {
143 return crate::generated::font_metrics_flowchart_11_12_2::lookup_font_metrics(
144 "courier",
145 );
146 }
147 if key_lower.contains("sans-serif") {
151 return crate::generated::font_metrics_flowchart_11_12_2::lookup_font_metrics(
152 "sans-serif",
153 );
154 }
155 None
156 }
157
158 fn lookup_char_em(entries: &[(char, f64)], default_em: f64, ch: char) -> f64 {
159 fn find_entry_em(entries: &[(char, f64)], ch: char) -> Option<f64> {
160 let mut lo = 0usize;
161 let mut hi = entries.len();
162 while lo < hi {
163 let mid = (lo + hi) / 2;
164 match entries[mid].0.cmp(&ch) {
165 std::cmp::Ordering::Equal => return Some(entries[mid].1),
166 std::cmp::Ordering::Less => lo = mid + 1,
167 std::cmp::Ordering::Greater => hi = mid,
168 }
169 }
170 None
171 }
172
173 if let Some(em) = find_entry_em(entries, ch) {
174 return em;
175 }
176
177 let paired = match ch {
182 '(' => Some(')'),
183 ')' => Some('('),
184 '[' => Some(']'),
185 ']' => Some('['),
186 '{' => Some('}'),
187 '}' => Some('{'),
188 _ => None,
189 };
190 if let Some(other) = paired
191 && let Some(other_em) = find_entry_em(entries, other)
192 {
193 return other_em;
194 }
195 if ch.is_ascii() {
196 return default_em;
197 }
198
199 if ('\u{80}'..='\u{9f}').contains(&ch) {
200 return 0.598_7;
205 }
206
207 Self::lookup_non_ascii_fallback_em(default_em, ch)
208 }
209
210 fn lookup_non_ascii_fallback_em(default_em: f64, ch: char) -> f64 {
211 let code = ch as u32;
212
213 if unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1) == 0
219 || (0x1f3fb..=0x1f3ff).contains(&code)
220 {
221 return 0.0;
222 }
223 if (0x0590..=0x05ff).contains(&code) {
224 return 0.479_980_468_75;
225 }
226 if (0x1f300..=0x1faff).contains(&code) || (0x2600..=0x27bf).contains(&code) {
227 return 1.249_67;
228 }
229 if (0xac00..=0xd7af).contains(&code) {
230 return 0.864_257_812_5;
231 }
232
233 match unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1) {
234 2.. => 1.0,
235 _ => default_em,
236 }
237 }
238
239 fn lookup_kern_em(kern_pairs: &[(u32, u32, f64)], a: char, b: char) -> f64 {
240 let key_a = a as u32;
241 let key_b = b as u32;
242 let mut lo = 0usize;
243 let mut hi = kern_pairs.len();
244 while lo < hi {
245 let mid = (lo + hi) / 2;
246 let (ma, mb, v) = kern_pairs[mid];
247 match (ma.cmp(&key_a), mb.cmp(&key_b)) {
248 (std::cmp::Ordering::Equal, std::cmp::Ordering::Equal) => return v,
249 (std::cmp::Ordering::Less, _) => lo = mid + 1,
250 (std::cmp::Ordering::Equal, std::cmp::Ordering::Less) => lo = mid + 1,
251 _ => hi = mid,
252 }
253 }
254 0.0
255 }
256
257 fn lookup_profile_kern_em(profile: FontMetricProfile<'_>, a: char, b: char) -> f64 {
258 let explicit = Self::lookup_kern_em(profile.kern_pairs, a, b);
259 if explicit != 0.0 {
260 return explicit;
261 }
262
263 if a == 'v' && b == ',' {
264 return profile.missing_v_comma_kern_em;
269 }
270 if a == 'T' && b == 'o' {
271 return profile.missing_t_o_kern_em;
272 }
273 if a == 'T' && b == 'r' {
274 return profile.missing_t_r_kern_em;
275 }
276
277 0.0
278 }
279
280 fn lookup_space_trigram_em(space_trigrams: &[(u32, u32, f64)], a: char, b: char) -> f64 {
281 let key_a = a as u32;
282 let key_b = b as u32;
283 let mut lo = 0usize;
284 let mut hi = space_trigrams.len();
285 while lo < hi {
286 let mid = (lo + hi) / 2;
287 let (ma, mb, v) = space_trigrams[mid];
288 match (ma.cmp(&key_a), mb.cmp(&key_b)) {
289 (std::cmp::Ordering::Equal, std::cmp::Ordering::Equal) => return v,
290 (std::cmp::Ordering::Less, _) => lo = mid + 1,
291 (std::cmp::Ordering::Equal, std::cmp::Ordering::Less) => lo = mid + 1,
292 _ => hi = mid,
293 }
294 }
295 0.0
296 }
297
298 fn lookup_trigram_em(trigrams: &[(u32, u32, u32, f64)], a: char, b: char, c: char) -> f64 {
299 let key_a = a as u32;
300 let key_b = b as u32;
301 let key_c = c as u32;
302 let mut lo = 0usize;
303 let mut hi = trigrams.len();
304 while lo < hi {
305 let mid = (lo + hi) / 2;
306 let (ma, mb, mc, v) = trigrams[mid];
307 match (ma.cmp(&key_a), mb.cmp(&key_b), mc.cmp(&key_c)) {
308 (
309 std::cmp::Ordering::Equal,
310 std::cmp::Ordering::Equal,
311 std::cmp::Ordering::Equal,
312 ) => return v,
313 (std::cmp::Ordering::Less, _, _) => lo = mid + 1,
314 (std::cmp::Ordering::Equal, std::cmp::Ordering::Less, _) => lo = mid + 1,
315 (
316 std::cmp::Ordering::Equal,
317 std::cmp::Ordering::Equal,
318 std::cmp::Ordering::Less,
319 ) => lo = mid + 1,
320 _ => hi = mid,
321 }
322 }
323 0.0
324 }
325
326 fn is_tiny_lattice_residual_em(v: f64) -> bool {
327 v.abs() <= (1.0 / 1024.0) + 1e-12
332 }
333
334 fn same_glyph_pair_kern_em(
335 profile: FontMetricProfile<'_>,
336 a: char,
337 b: char,
338 same_run_len_after: usize,
339 ) -> f64 {
340 let kern = Self::lookup_profile_kern_em(profile, a, b);
341 if a == b && Self::is_tiny_lattice_residual_em(kern) && same_run_len_after % 2 == 1 {
342 0.0
343 } else {
344 kern
345 }
346 }
347
348 fn same_glyph_trigram_em(profile: FontMetricProfile<'_>, a: char, b: char, c: char) -> f64 {
349 let delta = Self::lookup_trigram_em(profile.trigrams, a, b, c);
350 if a == b && b == c && Self::is_tiny_lattice_residual_em(delta) {
351 0.0
352 } else {
353 delta
354 }
355 }
356
357 fn lookup_html_override_em(overrides: &[(&'static str, f64)], text: &str) -> Option<f64> {
358 let mut lo = 0usize;
359 let mut hi = overrides.len();
360 while lo < hi {
361 let mid = (lo + hi) / 2;
362 let (k, v) = overrides[mid];
363 match k.cmp(text) {
364 std::cmp::Ordering::Equal => return Some(v),
365 std::cmp::Ordering::Less => lo = mid + 1,
366 std::cmp::Ordering::Greater => hi = mid,
367 }
368 }
369 None
370 }
371
372 fn lookup_svg_override_em(
373 overrides: &[(&'static str, f64, f64)],
374 text: &str,
375 ) -> Option<(f64, f64)> {
376 let mut lo = 0usize;
377 let mut hi = overrides.len();
378 while lo < hi {
379 let mid = (lo + hi) / 2;
380 let (k, l, r) = overrides[mid];
381 match k.cmp(text) {
382 std::cmp::Ordering::Equal => return Some((l, r)),
383 std::cmp::Ordering::Less => lo = mid + 1,
384 std::cmp::Ordering::Greater => hi = mid,
385 }
386 }
387 None
388 }
389
390 fn lookup_overhang_em(entries: &[(char, f64)], default_em: f64, ch: char) -> f64 {
391 let mut lo = 0usize;
392 let mut hi = entries.len();
393 while lo < hi {
394 let mid = (lo + hi) / 2;
395 match entries[mid].0.cmp(&ch) {
396 std::cmp::Ordering::Equal => return entries[mid].1,
397 std::cmp::Ordering::Less => lo = mid + 1,
398 std::cmp::Ordering::Greater => hi = mid,
399 }
400 }
401 default_em
402 }
403
404 fn line_svg_bbox_extents_px(
405 table: &crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables,
406 text: &str,
407 font_size: f64,
408 ) -> (f64, f64) {
409 let profile = Self::metric_profile(table);
410 let t = text.trim_end();
411 if t.is_empty() {
412 return (0.0, 0.0);
413 }
414
415 if let Some((left_em, right_em)) = Self::lookup_svg_override_em(table.svg_overrides, t) {
416 let left = Self::quantize_svg_bbox_px_nearest((left_em * font_size).max(0.0));
417 let right = Self::quantize_svg_bbox_px_nearest((right_em * font_size).max(0.0));
418 return (left, right);
419 }
420
421 if let Some((left, right)) =
422 overrides::lookup_flowchart_svg_bbox_x_px(table.font_key, font_size, t)
423 {
424 return (left, right);
425 }
426
427 let first = t.chars().next().unwrap_or(' ');
428 let last = t.chars().last().unwrap_or(' ');
429
430 let advance_px_unscaled = {
437 let words: Vec<&str> = t.split_whitespace().filter(|s| !s.is_empty()).collect();
438 if words.len() >= 2 {
439 let mut sum_px = 0.0f64;
440 for (idx, w) in words.iter().enumerate() {
441 if idx == 0 {
442 sum_px += Self::line_width_px(profile, w, false, font_size);
443 } else {
444 let seg = format!(" {w}");
445 sum_px += Self::line_width_px(profile, &seg, false, font_size);
446 }
447 }
448 sum_px
449 } else {
450 Self::line_width_px(profile, t, false, font_size)
451 }
452 };
453
454 let advance_px = advance_px_unscaled * table.svg_scale;
455 let half = Self::quantize_svg_half_px_nearest((advance_px / 2.0).max(0.0));
456 let left_oh_em = if first.is_ascii() && !matches!(first, '[' | '(' | '{') {
464 0.0
465 } else {
466 Self::lookup_overhang_em(
467 table.svg_bbox_overhang_left,
468 table.svg_bbox_overhang_left_default_em,
469 first,
470 )
471 };
472 let right_oh_em = if last.is_ascii() && !matches!(last, ']' | ')' | '}') {
473 0.0
474 } else {
475 Self::lookup_overhang_em(
476 table.svg_bbox_overhang_right,
477 table.svg_bbox_overhang_right_default_em,
478 last,
479 )
480 };
481
482 let left = (half + left_oh_em * font_size).max(0.0);
483 let right = (half + right_oh_em * font_size).max(0.0);
484 (left, right)
485 }
486
487 fn line_svg_bbox_extents_px_single_run(
488 table: &crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables,
489 text: &str,
490 font_size: f64,
491 ) -> (f64, f64) {
492 let profile = Self::metric_profile(table);
493 let t = text.trim_end();
494 if t.is_empty() {
495 return (0.0, 0.0);
496 }
497
498 if let Some((left_em, right_em)) = Self::lookup_svg_override_em(table.svg_overrides, t) {
499 let left = Self::quantize_svg_bbox_px_nearest((left_em * font_size).max(0.0));
500 let right = Self::quantize_svg_bbox_px_nearest((right_em * font_size).max(0.0));
501 return (left, right);
502 }
503
504 let first = t.chars().next().unwrap_or(' ');
505 let last = t.chars().last().unwrap_or(' ');
506
507 let advance_px_unscaled = Self::line_width_px(profile, t, false, font_size);
510
511 let advance_px = advance_px_unscaled * table.svg_scale;
512 let half = Self::quantize_svg_half_px_nearest((advance_px / 2.0).max(0.0));
513
514 let left_oh_em = if first.is_ascii() && !matches!(first, '[' | '(' | '{') {
515 0.0
516 } else {
517 Self::lookup_overhang_em(
518 table.svg_bbox_overhang_left,
519 table.svg_bbox_overhang_left_default_em,
520 first,
521 )
522 };
523 let right_oh_em = if last.is_ascii() && !matches!(last, ']' | ')' | '}') {
524 0.0
525 } else {
526 Self::lookup_overhang_em(
527 table.svg_bbox_overhang_right,
528 table.svg_bbox_overhang_right_default_em,
529 last,
530 )
531 };
532
533 let left = (half + left_oh_em * font_size).max(0.0);
534 let right = (half + right_oh_em * font_size).max(0.0);
535 (left, right)
536 }
537
538 fn line_svg_bbox_extents_px_single_run_with_ascii_overhang(
539 table: &crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables,
540 text: &str,
541 font_size: f64,
542 ) -> (f64, f64) {
543 Self::line_svg_bbox_extents_px_single_run_with_ascii_overhang_and_weight(
544 table, text, font_size, false,
545 )
546 }
547
548 fn line_svg_bbox_extents_px_single_run_with_ascii_overhang_and_weight(
549 table: &crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables,
550 text: &str,
551 font_size: f64,
552 bold: bool,
553 ) -> (f64, f64) {
554 let profile = Self::metric_profile(table);
555 let t = text.trim_end();
556 if t.is_empty() {
557 return (0.0, 0.0);
558 }
559
560 if let Some((left_em, right_em)) = Self::lookup_svg_override_em(table.svg_overrides, t) {
561 let left = Self::quantize_svg_bbox_px_nearest((left_em * font_size).max(0.0));
562 let right = Self::quantize_svg_bbox_px_nearest((right_em * font_size).max(0.0));
563 return (left, right);
564 }
565
566 let first = t.chars().next().unwrap_or(' ');
567 let last = t.chars().last().unwrap_or(' ');
568
569 let advance_px_unscaled = Self::line_width_px(profile, t, bold, font_size);
570
571 let advance_px = advance_px_unscaled * table.svg_scale;
572 let half = Self::quantize_svg_half_px_nearest((advance_px / 2.0).max(0.0));
573
574 let left_oh_em = Self::lookup_overhang_em(
575 table.svg_bbox_overhang_left,
576 table.svg_bbox_overhang_left_default_em,
577 first,
578 );
579 let mut right_oh_em = Self::lookup_overhang_em(
580 table.svg_bbox_overhang_right,
581 table.svg_bbox_overhang_right_default_em,
582 last,
583 );
584 if bold && table.font_key == FLOWCHART_DEFAULT_FONT_KEY {
585 right_oh_em = right_oh_em.max(flowchart_default_bold_svg_right_overhang_em(last));
586 }
587
588 let left = (half + left_oh_em * font_size).max(0.0);
589 let right = (half + right_oh_em * font_size).max(0.0);
590 (left, right)
591 }
592
593 fn line_svg_bbox_width_px(
594 table: &crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables,
595 text: &str,
596 font_size: f64,
597 ) -> f64 {
598 let (l, r) = Self::line_svg_bbox_extents_px(table, text, font_size);
599 (l + r).max(0.0)
600 }
601
602 fn line_svg_bbox_width_single_run_px(
603 table: &crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables,
604 text: &str,
605 font_size: f64,
606 ) -> f64 {
607 let t = text.trim_end();
608 if !t.is_empty()
609 && let Some((left_em, right_em)) =
610 overrides::lookup_sequence_svg_override_em(table.font_key, t)
611 {
612 let left = Self::quantize_svg_bbox_px_nearest((left_em * font_size).max(0.0));
613 let right = Self::quantize_svg_bbox_px_nearest((right_em * font_size).max(0.0));
614 return (left + right).max(0.0);
615 }
616
617 let (l, r) = Self::line_svg_bbox_extents_px_single_run(table, text, font_size);
618 (l + r).max(0.0)
619 }
620
621 fn line_svg_title_bbox_extents_px(
622 table: &crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables,
623 text: &str,
624 font_size: f64,
625 ) -> (f64, f64) {
626 let profile = Self::metric_profile(table);
627 let t = text.trim_end();
628 if t.is_empty() {
629 return (0.0, 0.0);
630 }
631
632 let advance_px = if let Some(em) = Self::lookup_html_override_em(table.html_overrides, t) {
637 em * font_size
638 } else {
639 Self::line_width_px(profile, t, false, font_size) * table.svg_scale
640 };
641 let half = Self::quantize_svg_half_px_nearest((advance_px / 2.0).max(0.0));
642 (half, half)
643 }
644
645 fn split_token_to_svg_bbox_width_px(
646 table: &crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables,
647 tok: &str,
648 max_width_px: f64,
649 font_size: f64,
650 ) -> (String, String) {
651 if max_width_px <= 0.0 {
652 return (tok.to_string(), String::new());
653 }
654 let chars = tok.chars().collect::<Vec<_>>();
655 if chars.is_empty() {
656 return (String::new(), String::new());
657 }
658
659 let first = chars[0];
660 let left_oh_em = if first.is_ascii() {
661 0.0
662 } else {
663 Self::lookup_overhang_em(
664 table.svg_bbox_overhang_left,
665 table.svg_bbox_overhang_left_default_em,
666 first,
667 )
668 };
669
670 let mut em = 0.0;
671 let mut prev: Option<char> = None;
672 let mut split_at = 1usize;
673 for (idx, ch) in chars.iter().enumerate() {
674 em += Self::lookup_char_em(table.entries, table.default_em.max(0.1), *ch);
675 if let Some(p) = prev {
676 em += Self::lookup_kern_em(table.kern_pairs, p, *ch);
677 }
678 prev = Some(*ch);
679
680 let right_oh_em = if ch.is_ascii() {
681 0.0
682 } else {
683 Self::lookup_overhang_em(
684 table.svg_bbox_overhang_right,
685 table.svg_bbox_overhang_right_default_em,
686 *ch,
687 )
688 };
689 let half_px = Self::quantize_svg_half_px_nearest(
690 (em * font_size * table.svg_scale / 2.0).max(0.0),
691 );
692 let w_px = 2.0 * half_px + (left_oh_em + right_oh_em) * font_size;
693 if w_px.is_finite() && w_px <= max_width_px {
694 split_at = idx + 1;
695 } else if idx > 0 {
696 break;
697 }
698 }
699 let head = chars[..split_at].iter().collect::<String>();
700 let tail = chars[split_at..].iter().collect::<String>();
701 (head, tail)
702 }
703
704 fn wrap_text_lines_svg_bbox_px(
705 table: &crate::generated::font_metrics_flowchart_11_12_2::FontMetricsTables,
706 text: &str,
707 max_width_px: Option<f64>,
708 font_size: f64,
709 tokenize_whitespace: bool,
710 ) -> Vec<String> {
711 const EPS_PX: f64 = 0.125;
712 let max_width_px = max_width_px.filter(|w| w.is_finite() && *w > 0.0);
713 let width_fn = if tokenize_whitespace {
714 Self::line_svg_bbox_width_px
715 } else {
716 Self::line_svg_bbox_width_single_run_px
717 };
718
719 let mut lines = Vec::new();
720 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
721 let Some(w) = max_width_px else {
722 lines.push(line);
723 continue;
724 };
725
726 let mut tokens = std::collections::VecDeque::from(
727 DeterministicTextMeasurer::split_line_to_words(&line),
728 );
729 let mut out: Vec<String> = Vec::new();
730 let mut cur = String::new();
731
732 while let Some(tok) = tokens.pop_front() {
733 if cur.is_empty() && tok == " " {
734 continue;
735 }
736
737 let candidate = format!("{cur}{tok}");
738 let candidate_trimmed = candidate.trim_end();
739 if width_fn(table, candidate_trimmed, font_size) <= w + EPS_PX {
740 cur = candidate;
741 continue;
742 }
743
744 if !cur.trim().is_empty() {
745 out.push(cur.trim_end().to_string());
746 cur.clear();
747 tokens.push_front(tok);
748 continue;
749 }
750
751 if tok == " " {
752 continue;
753 }
754
755 if width_fn(table, tok.as_str(), font_size) <= w + EPS_PX {
756 cur = tok;
757 continue;
758 }
759
760 let (head, tail) =
762 Self::split_token_to_svg_bbox_width_px(table, &tok, w + EPS_PX, font_size);
763 out.push(head);
764 if !tail.is_empty() {
765 tokens.push_front(tail);
766 }
767 }
768
769 if !cur.trim().is_empty() {
770 out.push(cur.trim_end().to_string());
771 }
772
773 if out.is_empty() {
774 lines.push("".to_string());
775 } else {
776 lines.extend(out);
777 }
778 }
779
780 if lines.is_empty() {
781 vec!["".to_string()]
782 } else {
783 lines
784 }
785 }
786
787 fn line_width_px(
788 profile: FontMetricProfile<'_>,
789 text: &str,
790 bold: bool,
791 font_size: f64,
792 ) -> f64 {
793 fn normalize_whitespace_like(ch: char) -> (char, f64) {
794 const NBSP_DELTA_EM: f64 = -1.0 / 3072.0;
802 if ch == '\u{00A0}' {
803 (' ', NBSP_DELTA_EM)
804 } else {
805 (ch, 0.0)
806 }
807 }
808
809 let mut em = 0.0;
810 let mut prevprev: Option<char> = None;
811 let mut prev: Option<char> = None;
812 let mut same_run_len = 0usize;
813 for ch in text.chars() {
814 let (ch, delta_em) = normalize_whitespace_like(ch);
815 let next_same_run_len = if prev == Some(ch) {
816 same_run_len + 1
817 } else {
818 1
819 };
820 em += Self::lookup_char_em(profile.entries, profile.default_em, ch) + delta_em;
821 if let Some(p) = prev {
822 em += Self::same_glyph_pair_kern_em(profile, p, ch, next_same_run_len);
823 }
824 if bold {
825 if let Some(p) = prev {
826 em += flowchart_default_bold_kern_delta_em(p, ch);
827 }
828 em += flowchart_default_bold_delta_em(ch);
829 }
830 if let (Some(a), Some(b)) = (prevprev, prev) {
831 if b == ' ' {
832 if !(a.is_whitespace() || ch.is_whitespace()) {
833 let space_delta =
834 Self::lookup_space_trigram_em(profile.space_trigrams, a, ch);
835 if space_delta != 0.0 {
836 em += space_delta;
837 } else if a == 'A' && ch == '(' {
838 em += profile.missing_space_after_capital_a_before_open_paren_em;
839 } else if ch == 'A' && a.is_ascii_alphanumeric() {
840 em += profile.missing_space_before_capital_a_em;
845 }
846 }
847 } else if !(a.is_whitespace() || b.is_whitespace() || ch.is_whitespace()) {
848 em += Self::same_glyph_trigram_em(profile, a, b, ch);
849 }
850 }
851 prevprev = prev;
852 prev = Some(ch);
853 same_run_len = next_same_run_len;
854 }
855 em * font_size
856 }
857
858 fn split_token_to_width_px(
859 profile: FontMetricProfile<'_>,
860 tok: &str,
861 max_width_px: f64,
862 bold: bool,
863 font_size: f64,
864 ) -> (String, String) {
865 fn normalize_whitespace_like(ch: char) -> (char, f64) {
866 const NBSP_DELTA_EM: f64 = -1.0 / 3072.0;
867 if ch == '\u{00A0}' {
868 (' ', NBSP_DELTA_EM)
869 } else {
870 (ch, 0.0)
871 }
872 }
873
874 if max_width_px <= 0.0 {
875 return (tok.to_string(), String::new());
876 }
877 let max_em = max_width_px / font_size.max(1.0);
878 let mut em = 0.0;
879 let mut prevprev: Option<char> = None;
880 let mut prev: Option<char> = None;
881 let mut same_run_len = 0usize;
882 let chars = tok.chars().collect::<Vec<_>>();
883 let mut split_at = 0usize;
884 for (idx, ch) in chars.iter().enumerate() {
885 let (ch_norm, delta_em) = normalize_whitespace_like(*ch);
886 let next_same_run_len = if prev == Some(ch_norm) {
887 same_run_len + 1
888 } else {
889 1
890 };
891 em += Self::lookup_char_em(profile.entries, profile.default_em, ch_norm) + delta_em;
892 if let Some(p) = prev {
893 em += Self::same_glyph_pair_kern_em(profile, p, ch_norm, next_same_run_len);
894 }
895 if bold {
896 if let Some(p) = prev {
897 em += flowchart_default_bold_kern_delta_em(p, ch_norm);
898 }
899 em += flowchart_default_bold_delta_em(ch_norm);
900 }
901 if let (Some(a), Some(b)) = (prevprev, prev)
902 && !(a.is_whitespace() || b.is_whitespace() || ch_norm.is_whitespace())
903 {
904 em += Self::same_glyph_trigram_em(profile, a, b, ch_norm);
905 }
906 prevprev = prev;
907 prev = Some(ch_norm);
908 same_run_len = next_same_run_len;
909 if em > max_em && idx > 0 {
910 break;
911 }
912 split_at = idx + 1;
913 if em >= max_em {
914 break;
915 }
916 }
917 if split_at == 0 {
918 split_at = 1.min(chars.len());
919 }
920 let head = chars.iter().take(split_at).collect::<String>();
921 let tail = chars.iter().skip(split_at).collect::<String>();
922 (head, tail)
923 }
924
925 fn wrap_line_to_width_px(
926 profile: FontMetricProfile<'_>,
927 line: &str,
928 max_width_px: f64,
929 font_size: f64,
930 break_long_words: bool,
931 bold: bool,
932 ) -> Vec<String> {
933 fn split_html_breakable_segments(tok: &str) -> Vec<String> {
934 let hyphen_count = tok.chars().filter(|ch| *ch == '-').count();
942 let char_count = tok.chars().count();
943 let is_hyphenated_compound = hyphen_count >= 2 && char_count >= 16;
944 let is_url_like = tok.starts_with("http://") || tok.starts_with("https://");
945 let is_path_like = is_hyphenated_compound
946 || is_url_like
947 || tok.len() >= 24
948 && tok
949 .chars()
950 .filter(|ch| {
951 matches!(ch, '/' | '\\' | '-' | ':' | '?' | '&' | '#' | '[' | ']')
952 })
953 .count()
954 >= 2;
955 if !is_path_like {
956 return vec![tok.to_string()];
957 }
958
959 fn is_break_after(ch: char, is_url_like: bool) -> bool {
960 matches!(ch, '/' | '-' | ':' | '?' | '&' | '#' | ')' | ']' | '}')
961 || (is_url_like && ch == '.')
962 }
963
964 let mut out: Vec<String> = Vec::new();
965 let mut cur = String::new();
966 for ch in tok.chars() {
967 cur.push(ch);
968 if is_break_after(ch, is_url_like) && !cur.is_empty() {
969 out.push(std::mem::take(&mut cur));
970 }
971 }
972 if !cur.is_empty() {
973 out.push(cur);
974 }
975 if out.len() <= 1 {
976 vec![tok.to_string()]
977 } else {
978 out
979 }
980 }
981
982 let max_width_px = if break_long_words {
987 max_width_px
988 } else {
989 max_width_px + (1.0 / 64.0)
990 };
991
992 let mut tokens =
993 std::collections::VecDeque::from(DeterministicTextMeasurer::split_line_to_words(line));
994 let mut out: Vec<String> = Vec::new();
995 let mut cur = String::new();
996
997 while let Some(tok) = tokens.pop_front() {
998 if cur.is_empty() && tok == " " {
999 continue;
1000 }
1001
1002 let candidate = format!("{cur}{tok}");
1003 let candidate_trimmed = candidate.trim_end();
1004 if Self::line_width_px(profile, candidate_trimmed, bold, font_size) <= max_width_px {
1005 cur = candidate;
1006 continue;
1007 }
1008
1009 if !break_long_words && tok != " " && !cur.trim().is_empty() {
1010 let segments = split_html_breakable_segments(&tok);
1014 if segments.len() > 1 {
1015 let mut cur_candidate = cur.clone();
1016 let mut consumed = 0usize;
1017 for seg in &segments {
1018 let candidate = format!("{cur_candidate}{seg}");
1019 let candidate_trimmed = candidate.trim_end();
1020 if Self::line_width_px(profile, candidate_trimmed, bold, font_size)
1021 <= max_width_px
1022 {
1023 cur_candidate = candidate;
1024 consumed += 1;
1025 } else {
1026 break;
1027 }
1028 }
1029 if consumed > 0 {
1030 cur = cur_candidate;
1031 for seg in segments.into_iter().skip(consumed).rev() {
1032 tokens.push_front(seg);
1033 }
1034 continue;
1035 }
1036 }
1037 }
1038
1039 if !cur.trim().is_empty() {
1040 out.push(cur.trim_end().to_string());
1041 cur.clear();
1042 }
1043
1044 if tok == " " {
1045 continue;
1046 }
1047
1048 if Self::line_width_px(profile, tok.as_str(), bold, font_size) <= max_width_px {
1049 cur = tok;
1050 continue;
1051 }
1052
1053 if !break_long_words {
1054 let segments = split_html_breakable_segments(&tok);
1055 if segments.len() > 1 {
1056 for seg in segments.into_iter().rev() {
1057 tokens.push_front(seg);
1058 }
1059 continue;
1060 }
1061 out.push(tok);
1062 continue;
1063 }
1064
1065 let (head, tail) =
1066 Self::split_token_to_width_px(profile, &tok, max_width_px, bold, font_size);
1067 out.push(head);
1068 if !tail.is_empty() {
1069 tokens.push_front(tail);
1070 }
1071 }
1072
1073 if !cur.trim().is_empty() {
1074 out.push(cur.trim_end().to_string());
1075 }
1076
1077 if out.is_empty() {
1078 vec!["".to_string()]
1079 } else {
1080 out
1081 }
1082 }
1083
1084 fn wrap_text_lines_px(
1085 profile: FontMetricProfile<'_>,
1086 text: &str,
1087 style: &TextStyle,
1088 bold: bool,
1089 max_width_px: Option<f64>,
1090 wrap_mode: WrapMode,
1091 ) -> Vec<String> {
1092 let font_size = style.font_size.max(1.0);
1093 let max_width_px = max_width_px.filter(|w| w.is_finite() && *w > 0.0);
1094 let break_long_words = wrap_mode == WrapMode::SvgLike;
1095
1096 let mut lines = Vec::new();
1097 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
1098 if let Some(w) = max_width_px {
1099 lines.extend(Self::wrap_line_to_width_px(
1100 profile,
1101 &line,
1102 w,
1103 font_size,
1104 break_long_words,
1105 bold,
1106 ));
1107 } else {
1108 lines.push(line);
1109 }
1110 }
1111
1112 if lines.is_empty() {
1113 vec!["".to_string()]
1114 } else {
1115 lines
1116 }
1117 }
1118}
1119
1120fn vendored_measure_wrapped_impl(
1121 measurer: &VendoredFontMetricsTextMeasurer,
1122 text: &str,
1123 style: &TextStyle,
1124 max_width: Option<f64>,
1125 wrap_mode: WrapMode,
1126 use_html_overrides: bool,
1127) -> (TextMetrics, Option<f64>) {
1128 let Some(table) = measurer.lookup_table(style) else {
1129 return measurer
1130 .fallback
1131 .measure_wrapped_with_raw_width(text, style, max_width, wrap_mode);
1132 };
1133
1134 let bold = is_flowchart_default_font(style) && style_requests_bold_font_weight(style);
1135 let font_size = style.font_size.max(1.0);
1136 let max_width = max_width.filter(|w| w.is_finite() && *w > 0.0);
1137 let line_height_factor = match wrap_mode {
1138 WrapMode::SvgLike | WrapMode::SvgLikeSingleRun => 1.1,
1139 WrapMode::HtmlLike => 1.5,
1140 };
1141
1142 let html_overrides: &[(&'static str, f64)] = if use_html_overrides && !bold {
1143 table.html_overrides
1144 } else {
1145 &[]
1146 };
1147 let profile = VendoredFontMetricsTextMeasurer::metric_profile(table);
1148
1149 let html_override_px = |em: f64| -> f64 {
1150 if (font_size - table.base_font_size_px).abs() < 0.01 {
1158 em * font_size
1159 } else {
1160 em * table.base_font_size_px
1161 }
1162 };
1163
1164 let html_width_override_px = |line: &str| -> Option<f64> {
1165 overrides::lookup_flowchart_html_width_px(table.font_key, font_size, line)
1168 };
1169
1170 let raw_width_unscaled = if wrap_mode == WrapMode::HtmlLike {
1179 let mut raw_w: f64 = 0.0;
1180 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
1181 if let Some(w) = html_width_override_px(&line) {
1182 raw_w = raw_w.max(w);
1183 continue;
1184 }
1185 if let Some(em) =
1186 VendoredFontMetricsTextMeasurer::lookup_html_override_em(html_overrides, &line)
1187 {
1188 raw_w = raw_w.max(html_override_px(em));
1189 } else {
1190 raw_w = raw_w.max(VendoredFontMetricsTextMeasurer::line_width_px(
1191 profile, &line, bold, font_size,
1192 ));
1193 }
1194 }
1195 Some(raw_w)
1196 } else {
1197 None
1198 };
1199
1200 fn split_html_min_content_segments(tok: &str) -> Vec<String> {
1213 fn is_break_after(ch: char) -> bool {
1219 matches!(ch, '-' | '?' | '&' | '#')
1220 }
1221
1222 let mut out: Vec<String> = Vec::new();
1223 let mut cur = String::new();
1224 for ch in tok.chars() {
1225 cur.push(ch);
1226 if is_break_after(ch) && !cur.is_empty() {
1227 out.push(std::mem::take(&mut cur));
1228 }
1229 }
1230 if !cur.is_empty() {
1231 out.push(cur);
1232 }
1233 if out.len() <= 1 {
1234 vec![tok.to_string()]
1235 } else {
1236 out
1237 }
1238 }
1239
1240 let html_min_content_width = if wrap_mode == WrapMode::HtmlLike && max_width.is_some() {
1241 let mut max_word_w: f64 = 0.0;
1242 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
1243 for part in line.split(' ') {
1244 let part = part.trim();
1245 if part.is_empty() {
1246 continue;
1247 }
1248 for seg in split_html_min_content_segments(part) {
1249 let seg_w = html_width_override_px(&seg).unwrap_or_else(|| {
1250 VendoredFontMetricsTextMeasurer::line_width_px(
1251 profile,
1252 seg.as_str(),
1253 bold,
1254 font_size,
1255 )
1256 });
1257 max_word_w = max_word_w.max(seg_w);
1258 }
1259 }
1260 }
1261 if max_word_w.is_finite() && max_word_w > 0.0 {
1262 Some(max_word_w)
1263 } else {
1264 None
1265 }
1266 } else {
1267 None
1268 };
1269
1270 let lines = match wrap_mode {
1271 WrapMode::HtmlLike => VendoredFontMetricsTextMeasurer::wrap_text_lines_px(
1272 profile, text, style, bold, max_width, wrap_mode,
1273 ),
1274 WrapMode::SvgLike => VendoredFontMetricsTextMeasurer::wrap_text_lines_svg_bbox_px(
1275 table, text, max_width, font_size, true,
1276 ),
1277 WrapMode::SvgLikeSingleRun => VendoredFontMetricsTextMeasurer::wrap_text_lines_svg_bbox_px(
1278 table, text, max_width, font_size, false,
1279 ),
1280 };
1281
1282 let mut width: f64 = 0.0;
1283 match wrap_mode {
1284 WrapMode::HtmlLike => {
1285 for line in &lines {
1286 if let Some(w) = html_width_override_px(line) {
1287 width = width.max(w);
1288 continue;
1289 }
1290 if let Some(em) =
1291 VendoredFontMetricsTextMeasurer::lookup_html_override_em(html_overrides, line)
1292 {
1293 width = width.max(html_override_px(em));
1294 } else {
1295 width = width.max(VendoredFontMetricsTextMeasurer::line_width_px(
1296 profile, line, bold, font_size,
1297 ));
1298 }
1299 }
1300 }
1301 WrapMode::SvgLike => {
1302 for line in &lines {
1303 width = width.max(VendoredFontMetricsTextMeasurer::line_svg_bbox_width_px(
1304 table, line, font_size,
1305 ));
1306 }
1307 }
1308 WrapMode::SvgLikeSingleRun => {
1309 for line in &lines {
1310 width = width.max(
1311 VendoredFontMetricsTextMeasurer::line_svg_bbox_width_single_run_px(
1312 table, line, font_size,
1313 ),
1314 );
1315 }
1316 }
1317 }
1318
1319 if wrap_mode == WrapMode::HtmlLike {
1323 let needs_wrap = max_width.is_some_and(|w| raw_width_unscaled.is_some_and(|rw| rw > w));
1324 if let Some(w) = max_width {
1325 if needs_wrap {
1326 width = width.max(w);
1327 } else {
1328 width = width.min(w);
1329 }
1330 }
1331 if needs_wrap && let Some(w) = html_min_content_width {
1332 width = width.max(w);
1333 }
1334 width = round_to_1_64_px(width);
1337 if let Some(w) = max_width {
1338 width = if needs_wrap {
1339 width.max(w)
1340 } else {
1341 width.min(w)
1342 };
1343 }
1344 }
1345
1346 let height = match wrap_mode {
1347 WrapMode::HtmlLike => lines.len() as f64 * font_size * line_height_factor,
1348 WrapMode::SvgLike | WrapMode::SvgLikeSingleRun => {
1349 if lines.is_empty() {
1350 0.0
1351 } else {
1352 let first_line_h = svg_wrapped_first_line_bbox_height_px(style);
1358 let additional = (lines.len().saturating_sub(1)) as f64 * font_size * 1.1;
1359 first_line_h + additional
1360 }
1361 }
1362 };
1363
1364 let metrics = TextMetrics {
1365 width,
1366 height,
1367 line_count: lines.len(),
1368 };
1369 let raw_width_px = if wrap_mode == WrapMode::HtmlLike {
1370 raw_width_unscaled
1371 } else {
1372 None
1373 };
1374 (metrics, raw_width_px)
1375}
1376
1377impl TextMeasurer for VendoredFontMetricsTextMeasurer {
1378 fn measure(&self, text: &str, style: &TextStyle) -> TextMetrics {
1379 self.measure_wrapped(text, style, None, WrapMode::SvgLike)
1380 }
1381
1382 fn measure_svg_text_computed_length_px(&self, text: &str, style: &TextStyle) -> f64 {
1383 let Some(table) = self.lookup_table(style) else {
1384 return self
1385 .fallback
1386 .measure_svg_text_computed_length_px(text, style);
1387 };
1388
1389 let bold = is_flowchart_default_font(style) && style_requests_bold_font_weight(style);
1390 let font_size = style.font_size.max(1.0);
1391 let profile = VendoredFontMetricsTextMeasurer::metric_profile(table);
1392 let mut width: f64 = 0.0;
1393 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
1394 width = width.max(VendoredFontMetricsTextMeasurer::line_width_px(
1395 profile, &line, bold, font_size,
1396 ));
1397 }
1398 if width.is_finite() && width >= 0.0 {
1399 width
1400 } else {
1401 0.0
1402 }
1403 }
1404
1405 fn measure_svg_text_bbox_x(&self, text: &str, style: &TextStyle) -> (f64, f64) {
1406 let Some(table) = self.lookup_table(style) else {
1407 return self.fallback.measure_svg_text_bbox_x(text, style);
1408 };
1409
1410 let font_size = style.font_size.max(1.0);
1411 let mut left: f64 = 0.0;
1412 let mut right: f64 = 0.0;
1413 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
1414 let (l, r) = Self::line_svg_bbox_extents_px(table, &line, font_size);
1415 left = left.max(l);
1416 right = right.max(r);
1417 }
1418 (left, right)
1419 }
1420
1421 fn measure_svg_text_bbox_x_with_ascii_overhang(
1422 &self,
1423 text: &str,
1424 style: &TextStyle,
1425 ) -> (f64, f64) {
1426 let Some(table) = self.lookup_table(style) else {
1427 return self
1428 .fallback
1429 .measure_svg_text_bbox_x_with_ascii_overhang(text, style);
1430 };
1431
1432 let font_size = style.font_size.max(1.0);
1433 let mut left: f64 = 0.0;
1434 let mut right: f64 = 0.0;
1435 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
1436 let (l, r) = Self::line_svg_bbox_extents_px_single_run_with_ascii_overhang(
1437 table, &line, font_size,
1438 );
1439 left = left.max(l);
1440 right = right.max(r);
1441 }
1442 (left, right)
1443 }
1444
1445 fn measure_svg_title_bbox_x(&self, text: &str, style: &TextStyle) -> (f64, f64) {
1446 let Some(table) = self.lookup_table(style) else {
1447 return self.fallback.measure_svg_title_bbox_x(text, style);
1448 };
1449
1450 let font_size = style.font_size.max(1.0);
1451 let mut left: f64 = 0.0;
1452 let mut right: f64 = 0.0;
1453 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
1454 let (l, r) = Self::line_svg_title_bbox_extents_px(table, &line, font_size);
1455 left = left.max(l);
1456 right = right.max(r);
1457 }
1458 (left, right)
1459 }
1460
1461 fn measure_svg_simple_text_bbox_width_px(&self, text: &str, style: &TextStyle) -> f64 {
1462 let Some(table) = self.lookup_table(style) else {
1463 return self
1464 .fallback
1465 .measure_svg_simple_text_bbox_width_px(text, style);
1466 };
1467
1468 let font_size = style.font_size.max(1.0);
1469 let t = text.trim_end();
1470 if !t.is_empty()
1471 && let Some((left_em, right_em)) =
1472 overrides::lookup_sequence_svg_override_em(table.font_key, t)
1473 {
1474 let left = Self::quantize_svg_bbox_px_nearest((left_em * font_size).max(0.0));
1475 let right = Self::quantize_svg_bbox_px_nearest((right_em * font_size).max(0.0));
1476 return (left + right).max(0.0);
1477 }
1478
1479 let mut width: f64 = 0.0;
1480 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
1481 let (l, r) = Self::line_svg_bbox_extents_px_single_run_with_ascii_overhang(
1482 table, &line, font_size,
1483 );
1484 width = width.max((l + r).max(0.0));
1485 }
1486 width
1487 }
1488
1489 fn measure_svg_simple_text_bbox_width_for_wrap_px(&self, text: &str, style: &TextStyle) -> f64 {
1490 let Some(table) = self.lookup_table(style) else {
1491 return self
1492 .fallback
1493 .measure_svg_simple_text_bbox_width_for_wrap_px(text, style);
1494 };
1495
1496 let font_size = style.font_size.max(1.0);
1497 let mut width: f64 = 0.0;
1498 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
1499 let (l, r) = Self::line_svg_bbox_extents_px_single_run_with_ascii_overhang(
1500 table, &line, font_size,
1501 );
1502 width = width.max((l + r).max(0.0));
1503 }
1504 width
1505 }
1506
1507 fn measure_svg_raw_text_bbox_width_px(&self, text: &str, style: &TextStyle) -> f64 {
1508 let Some(table) = self.lookup_table(style) else {
1509 return self
1510 .fallback
1511 .measure_svg_raw_text_bbox_width_px(text, style);
1512 };
1513
1514 let font_size = style.font_size.max(1.0);
1515 let bold = is_flowchart_default_font(style) && style_requests_bold_font_weight(style);
1516 let mut width: f64 = 0.0;
1517 for line in DeterministicTextMeasurer::normalized_text_lines(text) {
1518 let (l, r) = Self::line_svg_bbox_extents_px_single_run_with_ascii_overhang_and_weight(
1519 table, &line, font_size, bold,
1520 );
1521 width = width.max((l + r).max(0.0));
1522 }
1523 width
1524 }
1525
1526 fn measure_svg_simple_text_bbox_height_px(&self, text: &str, style: &TextStyle) -> f64 {
1527 let t = text.trim_end();
1528 if t.is_empty() {
1529 return 0.0;
1530 }
1531 let font_size = style.font_size.max(1.0);
1534 (font_size * 1.1).max(0.0)
1535 }
1536
1537 fn measure_wrapped(
1538 &self,
1539 text: &str,
1540 style: &TextStyle,
1541 max_width: Option<f64>,
1542 wrap_mode: WrapMode,
1543 ) -> TextMetrics {
1544 vendored_measure_wrapped_impl(self, text, style, max_width, wrap_mode, true).0
1545 }
1546
1547 fn measure_wrapped_with_raw_width(
1548 &self,
1549 text: &str,
1550 style: &TextStyle,
1551 max_width: Option<f64>,
1552 wrap_mode: WrapMode,
1553 ) -> (TextMetrics, Option<f64>) {
1554 vendored_measure_wrapped_impl(self, text, style, max_width, wrap_mode, true)
1555 }
1556
1557 fn measure_wrapped_raw(
1558 &self,
1559 text: &str,
1560 style: &TextStyle,
1561 max_width: Option<f64>,
1562 wrap_mode: WrapMode,
1563 ) -> TextMetrics {
1564 vendored_measure_wrapped_impl(self, text, style, max_width, wrap_mode, false).0
1565 }
1566}