1#![allow(dead_code)]
5
6extern crate alloc;
7use alloc::collections::BTreeMap;
8use alloc::string::String;
9use alloc::vec::Vec;
10
11struct HeapBuf<T>(Vec<T>);
33
34impl<T> Default for HeapBuf<T> {
35 fn default() -> Self {
36 Self(Vec::new())
37 }
38}
39
40impl<T> alloc_no_stdlib::SliceWrapper<T> for HeapBuf<T> {
41 fn slice(&self) -> &[T] {
42 &self.0
43 }
44}
45
46impl<T> alloc_no_stdlib::SliceWrapperMut<T> for HeapBuf<T> {
47 fn slice_mut(&mut self) -> &mut [T] {
48 &mut self.0
49 }
50}
51
52struct HeapAllocator;
53
54impl<T: Clone + Default> alloc_no_stdlib::Allocator<T> for HeapAllocator {
55 type AllocatedMemory = HeapBuf<T>;
56
57 fn alloc_cell(&mut self, len: usize) -> HeapBuf<T> {
59 HeapBuf(alloc::vec![T::default(); len])
60 }
61
62 fn free_cell(&mut self, _data: HeapBuf<T>) {}
64}
65
66pub fn decompress_brotli(compressed: &[u8], decompressed_size: usize) -> Option<Vec<u8>> {
72 let mut s = brotli_decompressor::BrotliState::new(
73 HeapAllocator,
74 HeapAllocator,
75 HeapAllocator,
76 );
77
78 let mut out_vec = alloc::vec![0u8; decompressed_size.max(16)];
82
83 let mut available_in: usize = compressed.len();
84 let mut input_offset: usize = 0;
85 let mut available_out: usize = out_vec.len();
86 let mut output_offset: usize = 0;
87 let mut total_out: usize = 0;
88
89 let result = brotli_decompressor::BrotliDecompressStream(
90 &mut available_in,
91 &mut input_offset,
92 compressed,
93 &mut available_out,
94 &mut output_offset,
95 &mut out_vec,
96 &mut total_out,
97 &mut s,
98 );
99
100 if matches!(result, brotli_decompressor::BrotliResult::ResultSuccess) {
101 out_vec.truncate(total_out);
102 Some(out_vec)
103 } else {
104 crate::warn!(
105 "[FONT][BR] Brotli 展開に失敗 result={:?} in={} total_out={} in_left={}",
106 result,
107 compressed.len(),
108 total_out,
109 available_in
110 );
111 None
112 }
113}
114
115static WOFF2_KNOWN_TAGS: [&[u8; 4]; 63] = [
116 b"cmap", b"head", b"hhea", b"hmtx", b"maxp", b"name", b"OS/2", b"post",
117 b"cvt ", b"fpgm", b"glyf", b"loca", b"prep", b"CFF ", b"VORG", b"EBDT",
118 b"EBLC", b"gasp", b"hdmx", b"kern", b"LTSH", b"PCLT", b"VDMX", b"vhea",
119 b"vmtx", b"BASE", b"GDEF", b"GPOS", b"GSUB", b"JSTF", b"DSIG", b"MATH",
120 b"CBDT", b"CBLC", b"COLR", b"CPAL", b"SVG ", b"sbix", b"acnt", b"avar",
121 b"bdat", b"bloc", b"bsln", b"cvar", b"fdsc", b"fmtx", b"fvar", b"gvar",
122 b"hsty", b"just", b"lcar", b"mort", b"morx", b"opbd", b"prop", b"trak",
123 b"Zapf", b"Silf", b"Gloc", b"Glat", b"Feat", b"Sill", b"SVG ",
124];
125
126fn read_woff2_uint16(data: &[u8], offset: &mut usize) -> Option<u16> {
127 if *offset + 2 > data.len() {
128 return None;
129 }
130 let val = u16::from_be_bytes([data[*offset], data[*offset + 1]]);
131 *offset += 2;
132 Some(val)
133}
134
135fn read_woff2_uint32(data: &[u8], offset: &mut usize) -> Option<u32> {
136 if *offset + 4 > data.len() {
137 return None;
138 }
139 let val = u32::from_be_bytes([
140 data[*offset],
141 data[*offset + 1],
142 data[*offset + 2],
143 data[*offset + 3],
144 ]);
145 *offset += 4;
146 Some(val)
147}
148
149fn read_woff2_base128(data: &[u8], offset: &mut usize) -> Option<u32> {
150 let mut accum: u32 = 0;
151 for _ in 0..5 {
152 if *offset >= data.len() {
153 return None;
154 }
155 let code = data[*offset];
156 *offset += 1;
157 if accum & 0xFE00_0000 != 0 {
158 return None;
159 }
160 accum = (accum << 7) | ((code & 0x7F) as u32);
161 if (code & 0x80) == 0 {
162 return Some(accum);
163 }
164 }
165 None
166}
167
168struct Woff2Table {
169 tag: [u8; 4],
170 orig_length: u32,
171 transform_length: Option<u32>,
172 flags: u8,
173}
174
175pub fn decompress_woff2(bytes: &[u8]) -> Option<Vec<u8>> {
177 if bytes.len() < 48 || &bytes[0..4] != b"wOF2" {
178 return None;
179 }
180 let mut offset = 4;
181 let flavor = read_woff2_uint32(bytes, &mut offset)?;
182 let _length = read_woff2_uint32(bytes, &mut offset)?;
183 let num_tables = read_woff2_uint16(bytes, &mut offset)? as usize;
184 let _reserved = read_woff2_uint16(bytes, &mut offset)?;
185 let total_sfnt_size = read_woff2_uint32(bytes, &mut offset)? as usize;
186 let total_compressed_size = read_woff2_uint32(bytes, &mut offset)? as usize;
187 let _major_version = read_woff2_uint16(bytes, &mut offset)?;
188 let _minor_version = read_woff2_uint16(bytes, &mut offset)?;
189 let _meta_offset = read_woff2_uint32(bytes, &mut offset)?;
190 let _meta_length = read_woff2_uint32(bytes, &mut offset)?;
191 let _meta_orig_length = read_woff2_uint32(bytes, &mut offset)?;
192 let _priv_offset = read_woff2_uint32(bytes, &mut offset)?;
193 let _priv_length = read_woff2_uint32(bytes, &mut offset)?;
194
195 let mut tables = Vec::with_capacity(num_tables);
196 let mut total_uncompressed_stream_len: usize = 0;
197
198 for _ in 0..num_tables {
199 if offset >= bytes.len() {
200 return None;
201 }
202 let flags = bytes[offset];
203 offset += 1;
204 let tag_idx = flags & 0x3F;
205
206 let tag = if tag_idx == 63 {
207 if offset + 4 > bytes.len() {
208 return None;
209 }
210 let t = [bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]];
211 offset += 4;
212 t
213 } else if (tag_idx as usize) < WOFF2_KNOWN_TAGS.len() {
214 *WOFF2_KNOWN_TAGS[tag_idx as usize]
215 } else {
216 return None;
217 };
218
219 let orig_length = read_woff2_base128(bytes, &mut offset)?;
220 let transform_version = (flags >> 6) & 0x03;
221
222 let is_glyf_or_loca = &tag == b"glyf" || &tag == b"loca";
240 let transformed = if is_glyf_or_loca {
241 transform_version == 0
242 } else {
243 transform_version != 0
244 };
245
246 let transform_length = if transformed {
247 let tl = read_woff2_base128(bytes, &mut offset)?;
248 total_uncompressed_stream_len += tl as usize;
249 Some(tl)
250 } else {
251 total_uncompressed_stream_len += orig_length as usize;
252 None
253 };
254
255 tables.push(Woff2Table {
256 tag,
257 orig_length,
258 transform_length,
259 flags,
260 });
261 }
262
263 if offset + total_compressed_size > bytes.len() {
264 return None;
265 }
266
267 let compressed_data = &bytes[offset..offset + total_compressed_size];
268 crate::warn!(
271 "[WOFF2] decompress compressed={} expected_uncompressed={}",
272 compressed_data.len(),
273 total_uncompressed_stream_len
274 );
275 let decompressed_stream = decompress_brotli(compressed_data, total_uncompressed_stream_len)?;
276
277 let mut sfnt = Vec::with_capacity(total_sfnt_size.max(12 + num_tables * 16));
279
280 sfnt.extend_from_slice(&flavor.to_be_bytes());
282 sfnt.extend_from_slice(&(num_tables as u16).to_be_bytes());
283
284 let mut max_p2 = 1u16;
285 while max_p2 * 2 <= (num_tables as u16) {
286 max_p2 *= 2;
287 }
288 let search_range = max_p2 * 16;
289 let entry_selector = (16 - max_p2.leading_zeros() - 1) as u16;
290 let range_shift = (num_tables as u16) * 16 - search_range;
291
292 sfnt.extend_from_slice(&search_range.to_be_bytes());
293 sfnt.extend_from_slice(&entry_selector.to_be_bytes());
294 sfnt.extend_from_slice(&range_shift.to_be_bytes());
295
296 let table_dir_offset = 12;
298 let data_start_offset = table_dir_offset + num_tables * 16;
299
300 let mut sorted_indices: Vec<usize> = (0..num_tables).collect();
301 sorted_indices.sort_by_key(|&idx| tables[idx].tag);
302
303 sfnt.resize(data_start_offset, 0);
305
306 let mut stream_offset = 0usize;
317 let mut table_ranges: Vec<(usize, usize)> = Vec::with_capacity(num_tables);
318 for table in tables.iter().take(num_tables) {
319 let len = table.orig_length as usize;
320 let read_len = table.transform_length.map(|l| l as usize).unwrap_or(len);
321 if stream_offset + read_len > decompressed_stream.len() {
322 crate::warn!(
323 "[FONT][WOFF2] 展開ストリームが短い tag={:?} 必要={} 残り={}",
324 core::str::from_utf8(&table.tag).unwrap_or("????"),
325 read_len,
326 decompressed_stream.len().saturating_sub(stream_offset)
327 );
328 return None;
329 }
330 table_ranges.push((stream_offset, stream_offset + read_len));
331 stream_offset += read_len;
332 }
333
334 let mut rebuilt: alloc::collections::BTreeMap<usize, Vec<u8>> =
343 alloc::collections::BTreeMap::new();
344 {
345 let glyf_idx = tables.iter().take(num_tables).position(|t| {
346 &t.tag == b"glyf" && t.transform_length.is_some()
347 });
348 if let Some(gi) = glyf_idx {
349 let (rs, re) = table_ranges[gi];
350 let raw = decompressed_stream.get(rs..re)?;
351 match crate::kernel::woff2_glyf::reconstruct_glyf(raw) {
352 Some((glyf_data, loca_data, _fmt)) => {
353 crate::warn!(
354 "[FONT][WOFF2] glyf 変換を復元 {} -> {} バイト(loca {} バイト)",
355 raw.len(),
356 glyf_data.len(),
357 loca_data.len()
358 );
359 rebuilt.insert(gi, glyf_data);
360 if let Some(li) = tables
361 .iter()
362 .take(num_tables)
363 .position(|t| &t.tag == b"loca")
364 {
365 rebuilt.insert(li, loca_data);
366 }
367 }
368 None => {
369 crate::warn!(
370 "[FONT][WOFF2] glyf 変換の復元に失敗({} バイト)",
371 raw.len()
372 );
373 return None;
374 }
375 }
376 }
377 }
378
379 let mut current_data_offset = data_start_offset;
380
381 for &idx in &sorted_indices {
382 let table = &tables[idx];
383 let len = table.orig_length as usize;
384 let (rs, re) = table_ranges[idx];
385 let table_raw: &[u8] = match rebuilt.get(&idx) {
387 Some(v) => v.as_slice(),
388 None => &decompressed_stream[rs..re],
389 };
390 let len = if rebuilt.contains_key(&idx) {
391 table_raw.len()
392 } else {
393 len
394 };
395
396 let mut sum: u32 = 0;
398 let mut i = 0;
399 while i < table_raw.len() {
400 let b0 = table_raw[i] as u32;
401 let b1 = table_raw.get(i + 1).copied().unwrap_or(0) as u32;
402 let b2 = table_raw.get(i + 2).copied().unwrap_or(0) as u32;
403 let b3 = table_raw.get(i + 3).copied().unwrap_or(0) as u32;
404 sum = sum.wrapping_add((b0 << 24) | (b1 << 16) | (b2 << 8) | b3);
405 i += 4;
406 }
407
408 let dir_entry_pos = table_dir_offset + sorted_indices.iter().position(|&x| x == idx)? * 16;
409 sfnt[dir_entry_pos..dir_entry_pos + 4].copy_from_slice(&table.tag);
410 sfnt[dir_entry_pos + 4..dir_entry_pos + 8].copy_from_slice(&sum.to_be_bytes());
411 sfnt[dir_entry_pos + 8..dir_entry_pos + 12].copy_from_slice(&(current_data_offset as u32).to_be_bytes());
412 sfnt[dir_entry_pos + 12..dir_entry_pos + 16].copy_from_slice(&(len as u32).to_be_bytes());
413
414 sfnt.extend_from_slice(table_raw);
415 let padding = (4 - (table_raw.len() % 4)) % 4;
416 sfnt.extend(core::iter::repeat_n(0u8, padding));
417 current_data_offset += table_raw.len() + padding;
418 }
419
420 Some(sfnt)
421}
422
423use spin::Mutex;
424use ttf_parser::{Face, OutlineBuilder};
425
426fn f32_floor(x: f32) -> i32 {
427 if x >= 0.0 {
428 x as i32
429 } else {
430 (x - 1.0) as i32
431 }
432}
433
434fn f32_ceil(x: f32) -> i32 {
435 if x >= 0.0 {
436 (x + 1.0) as i32
437 } else {
438 x as i32
439 }
440}
441
442#[derive(Debug, Clone, Copy, PartialEq)]
443struct Point {
444 x: f32,
445 y: f32,
446}
447
448#[derive(Debug, Clone)]
449enum Segment {
450 Line(Point, Point),
451}
452
453struct GlyphOutlineBuilder {
455 segments: Vec<Segment>,
456 current: Point,
457 start: Point,
458 scale: f32,
459}
460
461impl GlyphOutlineBuilder {
462 fn new(scale: f32) -> Self {
463 Self {
464 segments: Vec::new(),
465 current: Point { x: 0.0, y: 0.0 },
466 start: Point { x: 0.0, y: 0.0 },
467 scale,
468 }
469 }
470
471 fn flatten_quad(&mut self, p0: Point, p1: Point, p2: Point) {
473 let steps = 6; let mut prev = p0;
475 for i in 1..=steps {
476 let t = (i as f32) / (steps as f32);
477 let mt = 1.0 - t;
478 let x = mt * mt * p0.x + 2.0 * mt * t * p1.x + t * t * p2.x;
480 let y = mt * mt * p0.y + 2.0 * mt * t * p1.y + t * t * p2.y;
481 let current = Point { x, y };
482 self.segments.push(Segment::Line(prev, current));
483 prev = current;
484 }
485 }
486}
487
488impl OutlineBuilder for GlyphOutlineBuilder {
489 fn move_to(&mut self, x: f32, y: f32) {
490 let p = Point {
491 x: x * self.scale,
492 y: y * self.scale,
493 };
494 self.current = p;
495 self.start = p;
496 }
497
498 fn line_to(&mut self, x: f32, y: f32) {
499 let p = Point {
500 x: x * self.scale,
501 y: y * self.scale,
502 };
503 self.segments.push(Segment::Line(self.current, p));
504 self.current = p;
505 }
506
507 fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
508 let p1 = Point {
509 x: x1 * self.scale,
510 y: y1 * self.scale,
511 };
512 let p2 = Point {
513 x: x * self.scale,
514 y: y * self.scale,
515 };
516 self.flatten_quad(self.current, p1, p2);
517 self.current = p2;
518 }
519
520 fn curve_to(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, x: f32, y: f32) {
521 let p2 = Point {
524 x: x * self.scale,
525 y: y * self.scale,
526 };
527 self.segments.push(Segment::Line(self.current, p2));
528 self.current = p2;
529 }
530
531 fn close(&mut self) {
532 if self.current != self.start {
533 self.segments.push(Segment::Line(self.current, self.start));
534 }
535 }
536}
537
538#[derive(Clone)]
540pub struct CachedGlyph {
541 pub width: u32,
542 pub height: u32,
543 pub x_offset: i32,
544 pub y_offset: i32,
545 pub advance: u32,
546 pub data: Vec<u8>, }
548
549#[derive(Clone)]
551pub struct CachedColorGlyph {
552 pub width: u32,
553 pub height: u32,
554 pub x_offset: i32,
555 pub y_offset: i32,
556 pub advance: u32,
557 pub data: Vec<u32>, }
559
560pub struct VectorFont {
562 face: Face<'static>,
563 fallbacks: Vec<Face<'static>>,
565 cache: BTreeMap<(char, u32), CachedGlyph>, color_cache: BTreeMap<(char, u32, u32, u32), CachedColorGlyph>, advance_cache: BTreeMap<(char, u32), u32>, }
569
570fn fallback_glyph_char(c: char) -> Option<char> {
573 let sub = match c {
574 '␣' => '_', '⇥' => '>', '↵' => '<', '⇠' => '<', '–' | '—' | '―' | '‐' | '‑' | '−' => '-',
581 '\u{2018}' | '\u{2019}' | '‚' | '‛' | '`' => '\'',
583 '\u{201C}' | '\u{201D}' | '„' | '‟' => '"',
584 '•' | '◦' | '‣' | '·' | '∙' => '*',
586 '×' => 'x',
588 '÷' => '/',
589 '…' => '.',
591 '\u{00A0}' | '\u{202F}' | '\u{2009}' | '\u{200A}' => ' ',
593 '\u{f060}' => '\u{2190}', '\u{f061}' => '\u{2192}', '\u{f062}' => '\u{2191}', '\u{f063}' => '\u{2193}', '\u{f053}' => '\u{2039}', '\u{f054}' => '\u{203A}', '\u{f077}' => '^', '\u{f078}' => 'v', '\u{f104}' => '\u{2039}', '\u{f105}' => '\u{203A}', '\u{f106}' => '^', '\u{f107}' => 'v', '\u{f00c}' => '\u{2713}', '\u{f00d}' => '\u{2715}', '\u{f05a}' => 'i', '\u{f35d}' => '↗', '\u{f10d}' => '"', '\u{f10e}' => '"', _ => return None,
643 };
644 Some(sub)
645}
646
647impl VectorFont {
648 pub fn new(font_bytes: &'static [u8]) -> Result<Self, ttf_parser::FaceParsingError> {
649 let face = Face::parse(font_bytes, 0)?;
650 Ok(Self {
651 face,
652 fallbacks: Vec::new(),
653 cache: BTreeMap::new(),
654 color_cache: BTreeMap::new(),
655 advance_cache: BTreeMap::new(),
656 })
657 }
658
659 pub fn new_with_fallbacks(
663 font_bytes: &'static [u8],
664 fallback_bytes: &[&'static [u8]],
665 ) -> Result<Self, ttf_parser::FaceParsingError> {
666 let face = Face::parse(font_bytes, 0)?;
667 let mut fallbacks = Vec::new();
668 for fb in fallback_bytes {
669 if let Ok(f) = Face::parse(fb, 0) {
670 fallbacks.push(f);
671 }
672 }
673 Ok(Self {
674 face,
675 fallbacks,
676 cache: BTreeMap::new(),
677 color_cache: BTreeMap::new(),
678 advance_cache: BTreeMap::new(),
679 })
680 }
681
682 pub fn set_primary(&mut self, font_bytes: &'static [u8]) -> Result<(), ttf_parser::FaceParsingError> {
688 let new_face = Face::parse(font_bytes, 0)?;
689 let old_primary = core::mem::replace(&mut self.face, new_face);
690 self.fallbacks.insert(0, old_primary);
691 self.cache.clear();
692 self.color_cache.clear();
693 self.advance_cache.clear();
694 Ok(())
695 }
696
697 pub fn primary_has_glyph(&self, c: char) -> bool {
708 self.face.glyph_index(c).is_some()
709 }
710
711 pub fn has_glyph(&self, c: char) -> bool {
712 if ('\u{e000}'..='\u{f8ff}').contains(&c) {
717 return self.face.glyph_index(c).is_some();
718 }
719 if self.face.glyph_index(c).is_some() {
720 return true;
721 }
722 self.fallbacks.iter().any(|f| f.glyph_index(c).is_some())
723 }
724
725 pub fn get_glyph(&mut self, c: char, size_px: u32) -> Option<&CachedGlyph> {
726 let key = (c, size_px);
727 if self.cache.contains_key(&key) {
728 return self.cache.get(&key);
729 }
730
731 if self.cache.len() >= 8000 {
732 crate::info!(
733 "[vector_font] Cache limit (8000) reached. Clearing glyph cache to free memory."
734 );
735 self.cache.clear();
736 }
737
738 let (face_idx, glyph_id) = {
741 let find = |ch: char| -> Option<(usize, ttf_parser::GlyphId)> {
742 if let Some(id) = self.face.glyph_index(ch) {
743 return Some((0, id));
744 }
745 if ('\u{e000}'..='\u{f8ff}').contains(&ch) {
758 return None;
759 }
760 for (i, f) in self.fallbacks.iter().enumerate() {
761 if let Some(id) = f.glyph_index(ch) {
762 return Some((i + 1, id));
763 }
764 }
765 None
766 };
767 match find(c) {
768 Some(x) => x,
769 None => {
770 fallback_glyph_char(c).and_then(find)?
772 }
773 }
774 };
775 let face: &Face = if face_idx == 0 {
776 &self.face
777 } else {
778 &self.fallbacks[face_idx - 1]
779 };
780
781 let units_per_em = face.units_per_em() as f32;
782 let scale = (size_px as f32) / units_per_em;
783
784 let advance = (face.glyph_hor_advance(glyph_id)? as f32 * scale) as u32;
786
787 let mut builder = GlyphOutlineBuilder::new(scale);
790 face.outline_glyph(glyph_id, &mut builder);
791
792 if builder.segments.is_empty() {
795 let empty_glyph = CachedGlyph {
796 width: 0,
797 height: 0,
798 x_offset: 0,
799 y_offset: 0,
800 advance,
801 data: Vec::new(),
802 };
803 self.cache.insert(key, empty_glyph);
804 return self.cache.get(&key);
805 }
806
807 let bounding_box = face.glyph_bounding_box(glyph_id)?;
809
810 let scale_y = -scale;
812
813 let mut segments = builder.segments;
815 for seg in &mut segments {
816 match seg {
817 Segment::Line(p1, p2) => {
818 p1.y = -p1.y;
819 p2.y = -p2.y;
820 }
821 }
822 }
823
824 let bx0 = bounding_box.x_min as f32 * scale;
826 let by0 = bounding_box.y_max as f32 * scale_y; let bx1 = bounding_box.x_max as f32 * scale;
828 let by1 = bounding_box.y_min as f32 * scale_y; let gx0 = f32_floor(bx0) - 1;
831 let gy0 = f32_floor(by0) - 1;
832 let gx1 = f32_ceil(bx1) + 1;
833 let gy1 = f32_ceil(by1) + 1;
834
835 let width = (gx1 - gx0).max(1) as u32;
836 let height = (gy1 - gy0).max(1) as u32;
837 let x_offset = gx0;
838 let y_offset = gy0;
839
840 if width > 500 || height > 500 || width == 0 || height == 0 {
841 crate::warn!(
842 "[NET][DIAG] glyph c={:?} units_per_em={} scale={} bbox=({},{},{},{}) w={} h={} face_idx={}",
843 c,
844 units_per_em,
845 scale,
846 bounding_box.x_min,
847 bounding_box.y_min,
848 bounding_box.x_max,
849 bounding_box.y_max,
850 width,
851 height,
852 face_idx
853 );
854 }
855
856 let mut data = alloc::vec![0u8; (width * height) as usize];
861 let mut coverage = alloc::vec![0u8; width as usize];
862 let mut xs: alloc::vec::Vec<f32> = alloc::vec::Vec::new();
863
864 for py in 0..height {
865 let screen_y = (gy0 + py as i32) as f32;
866 for c in coverage.iter_mut() {
867 *c = 0;
868 }
869
870 for sy in 0..4 {
872 let sub_y = screen_y + (sy as f32 + 0.5) / 4.0;
873
874 xs.clear();
876 for seg in &segments {
877 match seg {
878 Segment::Line(p1, p2) => {
879 let y_min = p1.y.min(p2.y);
880 let y_max = p1.y.max(p2.y);
881 if sub_y >= y_min && sub_y < y_max && p1.y != p2.y {
882 let t = (sub_y - p1.y) / (p2.y - p1.y);
883 xs.push(p1.x + t * (p2.x - p1.x));
884 }
885 }
886 }
887 }
888 if xs.is_empty() {
889 continue;
890 }
891 xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
893
894 let mut i = 0;
897 while i + 1 < xs.len() {
898 let span_start = xs[i];
899 let span_end = xs[i + 1];
900 i += 2;
901 if span_end <= span_start {
902 continue;
903 }
904 let px_lo = (f32_floor(span_start) - gx0 - 1).max(0) as u32;
908 let px_hi = ((f32_ceil(span_end) - gx0 + 1).max(0) as u32).min(width);
909 for px in px_lo..px_hi {
910 let screen_x = (gx0 + px as i32) as f32;
911 for sx in 0..4 {
912 let sub_x = screen_x + (sx as f32 + 0.5) / 4.0;
913 if sub_x >= span_start && sub_x < span_end {
914 coverage[px as usize] += 1;
915 }
916 }
917 }
918 }
919 }
920
921 for px in 0..width {
922 let alpha = ((coverage[px as usize] as u32 * 255) / 16).min(255) as u8;
923 data[(py * width + px) as usize] = alpha;
924 }
925 }
926
927 let cached = CachedGlyph {
928 width,
929 height,
930 x_offset,
931 y_offset,
932 advance,
933 data,
934 };
935
936 self.cache.insert(key, cached);
937 self.cache.get(&key)
938 }
939
940 pub fn get_color_glyph(
942 &mut self,
943 c: char,
944 size_px: u32,
945 fg_color: u32,
946 bg_color: u32,
947 ) -> Option<&CachedColorGlyph> {
948 let key = (c, size_px, fg_color, bg_color);
949 if self.color_cache.contains_key(&key) {
950 return self.color_cache.get(&key);
951 }
952
953 if self.color_cache.len() >= 16000 {
954 self.color_cache.clear();
955 }
956
957 let glyph = self.get_glyph(c, size_px)?.clone();
958
959 let mut color_data = Vec::with_capacity(glyph.data.len());
960 let fg_r = (fg_color >> 16) & 0xFF;
961 let fg_g = (fg_color >> 8) & 0xFF;
962 let fg_b = fg_color & 0xFF;
963 let bg_r = (bg_color >> 16) & 0xFF;
964 let bg_g = (bg_color >> 8) & 0xFF;
965 let bg_b = bg_color & 0xFF;
966
967 for &alpha in &glyph.data {
968 if alpha == 0 {
969 color_data.push(0x00000000); } else if alpha == 255 {
971 color_data.push(fg_color | 0xFF000000); } else {
973 let a = alpha as u32;
974 let inv_a = 255 - a;
975 let r = ((fg_r * a + bg_r * inv_a) / 255) & 0xFF;
976 let g = ((fg_g * a + bg_g * inv_a) / 255) & 0xFF;
977 let b = ((fg_b * a + bg_b * inv_a) / 255) & 0xFF;
978 color_data.push(0xFF000000 | (r << 16) | (g << 8) | b);
979 }
980 }
981
982 let cached = CachedColorGlyph {
983 width: glyph.width,
984 height: glyph.height,
985 x_offset: glyph.x_offset,
986 y_offset: glyph.y_offset,
987 advance: glyph.advance,
988 data: color_data,
989 };
990
991 self.color_cache.insert(key, cached);
992 self.color_cache.get(&key)
993 }
994
995 pub fn get_glyph_advance(&mut self, c: char, size_px: u32) -> u32 {
998 let key = (c, size_px);
999 if let Some(glyph) = self.cache.get(&key) {
1000 return glyph.advance;
1001 }
1002 if let Some(&adv) = self.advance_cache.get(&key) {
1003 return adv;
1004 }
1005
1006 let (face_idx, glyph_id) = {
1007 let find = |ch: char| -> Option<(usize, ttf_parser::GlyphId)> {
1008 if let Some(id) = self.face.glyph_index(ch) {
1009 return Some((0, id));
1010 }
1011 if ('\u{e000}'..='\u{f8ff}').contains(&ch) {
1024 return None;
1025 }
1026 for (i, f) in self.fallbacks.iter().enumerate() {
1027 if let Some(id) = f.glyph_index(ch) {
1028 return Some((i + 1, id));
1029 }
1030 }
1031 None
1032 };
1033 match find(c) {
1034 Some(x) => x,
1035 None => {
1036 if let Some(alt) = fallback_glyph_char(c).and_then(find) {
1037 alt
1038 } else {
1039 let default_adv = size_px / 2;
1040 self.advance_cache.insert(key, default_adv);
1041 return default_adv;
1042 }
1043 }
1044 }
1045 };
1046
1047 let face: &Face = if face_idx == 0 {
1048 &self.face
1049 } else {
1050 &self.fallbacks[face_idx - 1]
1051 };
1052 let units_per_em = face.units_per_em() as f32;
1053 let scale = (size_px as f32) / units_per_em;
1054 let advance = if let Some(adv) = face.glyph_hor_advance(glyph_id) {
1055 (adv as f32 * scale) as u32
1056 } else {
1057 size_px / 2
1058 };
1059
1060 if self.advance_cache.len() >= 10000 {
1061 self.advance_cache.clear();
1062 }
1063 self.advance_cache.insert(key, advance);
1064 advance
1065 }
1066
1067 pub fn get_string_width(&mut self, s: &str, size_px: u32) -> u32 {
1069 self.get_string_width_ls(s, size_px, 0)
1070 }
1071
1072 pub fn get_string_width_ls(&mut self, s: &str, size_px: u32, ls: i32) -> u32 {
1074 self.get_string_width_ls_ws(s, size_px, ls, 0)
1075 }
1076
1077 pub fn get_string_width_ls_ws(&mut self, s: &str, size_px: u32, ls: i32, ws: i32) -> u32 {
1079 let mut width = 0i32;
1080 for c in s.chars() {
1081 width += self.get_glyph_advance(c, size_px) as i32 + ls + if c == ' ' { ws } else { 0 };
1082 }
1083 width.max(0) as u32
1084 }
1085
1086 pub fn get_string_wrapped_size(&mut self, s: &str, size_px: u32, max_width: u32) -> (u32, u32) {
1094 self.get_string_wrapped_size_ls_ws(s, size_px, max_width, 0, 0)
1095 }
1096
1097 pub fn get_string_wrapped_size_ls(
1099 &mut self,
1100 s: &str,
1101 size_px: u32,
1102 max_width: u32,
1103 ls: i32,
1104 ) -> (u32, u32) {
1105 self.get_string_wrapped_size_ls_ws(s, size_px, max_width, ls, 0)
1106 }
1107
1108 pub fn get_string_wrapped_size_ls_ws(
1109 &mut self,
1110 s: &str,
1111 size_px: u32,
1112 max_width: u32,
1113 ls: i32,
1114 ws: i32,
1115 ) -> (u32, u32) {
1116 self.get_string_wrapped_size_ext(s, size_px, max_width, ls, ws, false, false)
1117 }
1118
1119 pub fn get_string_wrapped_size_ext(
1121 &mut self,
1122 s: &str,
1123 size_px: u32,
1124 max_width: u32,
1125 ls: i32,
1126 ws: i32,
1127 word_break_all: bool,
1128 _white_space_pre_wrap: bool,
1129 ) -> (u32, u32) {
1130 let line_height = size_px + 4;
1135 let chars: alloc::vec::Vec<char> = s.chars().collect();
1136 let advances: alloc::vec::Vec<i32> = chars
1137 .iter()
1138 .map(|&c| {
1139 if c == '\n' {
1140 0
1141 } else {
1142 let extra = ls + if c == ' ' { ws } else { 0 };
1143 (self.get_glyph_advance(c, size_px) as i32 + extra).max(0)
1144 }
1145 })
1146 .collect();
1147 let breaks = crate::os_lib::layout::line_break::break_positions(
1148 &chars,
1149 &advances,
1150 max_width.min(i32::MAX as u32) as i32,
1151 word_break_all,
1152 |c| !word_break_all && is_line_start_forbidden(c),
1153 |c| !word_break_all && is_line_end_forbidden(c),
1154 );
1155 let mut max_w = 0u32;
1156 let mut emitted_lines = 0u32;
1157 let mut line_w = 0i32;
1158 let mut line_w_no_trailing = 0i32;
1160 let mut seg_has_content = false;
1161 let mut bi = 0usize;
1162 for (i, &c) in chars.iter().enumerate() {
1163 if c == '\n' {
1164 max_w = max_w.max(line_w_no_trailing.max(0) as u32);
1165 emitted_lines += 1;
1166 line_w = 0;
1167 line_w_no_trailing = 0;
1168 seg_has_content = false;
1169 continue;
1170 }
1171 if bi < breaks.len() && breaks[bi] == i {
1172 bi += 1;
1173 max_w = max_w.max(line_w_no_trailing.max(0) as u32);
1174 emitted_lines += 1;
1175 line_w = 0;
1176 line_w_no_trailing = 0;
1177 }
1178 line_w += advances[i];
1179 if !c.is_whitespace() {
1180 line_w_no_trailing = line_w;
1181 }
1182 seg_has_content = true;
1183 }
1184 max_w = max_w.max(line_w_no_trailing.max(0) as u32);
1185 if seg_has_content {
1186 emitted_lines += 1;
1187 }
1188 (max_w, emitted_lines.max(1) * line_height)
1189 }
1190}
1191
1192pub static GLOBAL_VECTOR_FONT: Mutex<Option<VectorFont>> = Mutex::new(None);
1194pub static GLOBAL_TERMINAL_FONT: Mutex<Option<VectorFont>> = Mutex::new(None);
1195
1196pub static CUSTOM_FONTS: Mutex<BTreeMap<String, VectorFont>> = Mutex::new(BTreeMap::new());
1213
1214fn looks_like_ttf_or_otf(bytes: &[u8]) -> bool {
1216 if bytes.len() < 4 {
1217 return false;
1218 }
1219 matches!(
1220 &bytes[0..4],
1221 [0x00, 0x01, 0x00, 0x00] | [b'O', b'T', b'T', b'O'] | [b't', b'r', b'u', b'e']
1222 )
1223}
1224
1225fn looks_like_woff2(bytes: &[u8]) -> bool {
1246 bytes.len() >= 4 && &bytes[0..4] == b"wOF2"
1247}
1248
1249pub fn font_key(family: &str, weight: u16) -> alloc::string::String {
1261 alloc::format!("{}|{}", family.trim().to_lowercase(), weight)
1262}
1263
1264pub fn register_custom_font_weighted(family: &str, weight: u16, bytes: Vec<u8>) -> bool {
1266 register_custom_font_inner(family, weight, bytes)
1267}
1268
1269pub fn register_custom_font(family: &str, bytes: Vec<u8>) -> bool {
1270 register_custom_font_inner(family, 400, bytes)
1271}
1272
1273fn register_custom_font_inner(family: &str, weight: u16, bytes: Vec<u8>) -> bool {
1274 crate::warn!(
1291 "[FONT] 登録開始 family={} bytes={}",
1292 family,
1293 bytes.len()
1294 );
1295 let fallback_chain: Vec<&'static [u8]> = DEFAULT_FONT_CHAIN
1299 .iter()
1300 .filter_map(|n| font_bytes(n))
1301 .collect();
1302 let font_bytes = if looks_like_woff2(&bytes) {
1303 match decompress_woff2(&bytes) {
1304 Some(sfnt) => sfnt,
1305 None => {
1306 crate::warn!(
1307 "[FONT] WOFF2 の展開に失敗 family={} bytes={}",
1308 family,
1309 bytes.len()
1310 );
1311 return false;
1312 }
1313 }
1314 } else if looks_like_ttf_or_otf(&bytes) {
1315 bytes
1316 } else {
1317 crate::warn!(
1318 "[FONT] 未知のフォント形式 family={} bytes={} head={:02x?}",
1319 family,
1320 bytes.len(),
1321 &bytes[..bytes.len().min(4)]
1322 );
1323 return false;
1324 };
1325
1326 let leaked: &'static [u8] = Vec::leak(font_bytes);
1327 let _pf = crate::os_lib::web_engine::perf::start();
1328 crate::warn!("[FONT] 解析開始 family={} bytes={}", family, leaked.len());
1329 let _parsed = VectorFont::new_with_fallbacks(leaked, &fallback_chain);
1339 crate::os_lib::web_engine::perf::add(
1340 crate::os_lib::web_engine::perf::Slot::FontParse,
1341 _pf,
1342 );
1343 match _parsed {
1344 Ok(font) => {
1345 {
1346 let mut map = CUSTOM_FONTS.lock();
1347 map.insert(font_key(family, weight), font);
1349 let plain = family.trim().to_lowercase();
1353 if !map.contains_key(&plain) {
1354 if let Ok(f2) = VectorFont::new_with_fallbacks(leaked, &fallback_chain) {
1355 map.insert(plain, f2);
1356 }
1357 }
1358 }
1359 let probe = |c: char| -> bool {
1383 CUSTOM_FONTS
1384 .lock()
1385 .get(&family.trim().to_lowercase())
1386 .map(|f| f.has_glyph(c))
1387 .unwrap_or(false)
1388 };
1389 crate::warn!(
1390 "[FONT] 登録完了 family={} bytes={} 字形F02D={} 字形F015={}",
1391 family,
1392 leaked.len(),
1393 probe('\u{f02d}'),
1394 probe('\u{f015}')
1395 );
1396 true
1397 }
1398 Err(e) => {
1399 crate::warn!(
1400 "[FONT] フォントのパースに失敗 family={} bytes={} err={:?}",
1401 family,
1402 leaked.len(),
1403 e
1404 );
1405 false
1406 }
1407 }
1408}
1409
1410pub fn has_custom_font_weighted(family: &str, weight: u16) -> bool {
1420 CUSTOM_FONTS.lock().contains_key(&font_key(family, weight))
1421}
1422
1423pub fn has_custom_font(family: &str) -> bool {
1424 CUSTOM_FONTS.lock().contains_key(&family.trim().to_lowercase())
1425}
1426
1427pub const AVAILABLE_FONTS: &[&str] = &[
1435 "Mplus1Code-Regular",
1436 "HackGenConsoleNF-Regular",
1437 "ipam",
1438 "ipag",
1439 "MPLUS2-Regular",
1440 "MPLUS2-Bold",
1441];
1442
1443pub const DEFAULT_FONT_CHAIN: [&str; 3] =
1445 ["Mplus1Code-Regular", "HackGenConsoleNF-Regular", "ipam"];
1446
1447pub fn font_bytes(name: &str) -> Option<&'static [u8]> {
1449 Some(match name {
1450 "Mplus1Code-Regular" => include_bytes!("../../fonts/Mplus1Code/Mplus1Code-Regular.ttf"),
1451 "HackGenConsoleNF-Regular" => {
1452 include_bytes!("../../fonts/HackGen/HackGenConsoleNF-Regular.ttf")
1453 }
1454 "ipam" => include_bytes!("../../fonts/ipa/ipam.ttf"),
1455 "ipag" | "IPAg" => include_bytes!("../../fonts/ipa/ipag.ttf"),
1456 "MPLUS2-Regular" => include_bytes!("../../fonts/MPLUS2/MPLUS2-Regular.ttf"),
1457 "MPLUS2-Bold" => include_bytes!("../../fonts/MPLUS2/MPLUS2-Bold.ttf"),
1458 _ => return None,
1459 })
1460}
1461
1462pub fn init_global_font_chain(names: &[&str]) {
1466 let mut bytes: Vec<&'static [u8]> = names.iter().filter_map(|n| font_bytes(n)).collect();
1467 if bytes.is_empty() {
1468 bytes = DEFAULT_FONT_CHAIN
1469 .iter()
1470 .filter_map(|n| font_bytes(n))
1471 .collect();
1472 }
1473 if bytes.is_empty() {
1474 crate::info!("[vector_font] No embedded fonts available for chain init!");
1475 return;
1476 }
1477 let primary = bytes[0];
1478 let fallbacks: Vec<&'static [u8]> = bytes[1..].to_vec();
1479 match VectorFont::new_with_fallbacks(primary, &fallbacks) {
1480 Ok(font) => {
1481 crate::info!(
1482 "[vector_font] Global font chain initialized ({} fonts).",
1483 bytes.len()
1484 );
1485 *GLOBAL_VECTOR_FONT.lock() = Some(font);
1486 }
1487 Err(e) => {
1488 crate::info!("[vector_font] Failed to init font chain: {:?}", e);
1489 }
1490 }
1491}
1492
1493pub fn init_global_font(font_bytes: &'static [u8]) {
1495 match VectorFont::new(font_bytes) {
1496 Ok(mut font) => {
1497 crate::info!("[vector_font] Successfully initialized global vector font.");
1498 crate::info!("[vector_font] Running test get_glyph('o', 16) for global font...");
1499 if font.get_glyph('o', 16).is_some() {
1500 crate::info!("[vector_font] Test get_glyph('o', 16) succeeded for global font!");
1501 } else {
1502 crate::info!("[vector_font] Test get_glyph('o', 16) failed for global font!");
1503 }
1504 *GLOBAL_VECTOR_FONT.lock() = Some(font);
1505 }
1506 Err(e) => {
1507 crate::info!(
1508 "[vector_font] Failed to initialize global vector font: {:?}",
1509 e
1510 );
1511 }
1512 }
1513}
1514
1515pub fn init_terminal_font() {
1517 let font_bytes = include_bytes!("../../fonts/Mplus1Code/Mplus1Code-Regular.ttf");
1518 match VectorFont::new(font_bytes) {
1519 Ok(mut font) => {
1520 crate::info!("[vector_font] Successfully initialized terminal monospace font.");
1521 crate::info!("[vector_font] Running test get_glyph('o', 16)...");
1522 if font.get_glyph('o', 16).is_some() {
1523 crate::info!("[vector_font] Test get_glyph('o', 16) succeeded!");
1524 } else {
1525 crate::info!("[vector_font] Test get_glyph('o', 16) failed!");
1526 }
1527 *GLOBAL_TERMINAL_FONT.lock() = Some(font);
1528 }
1529 Err(e) => {
1530 crate::info!(
1531 "[vector_font] Failed to initialize terminal monospace font: {:?}",
1532 e
1533 );
1534 }
1535 }
1536}
1537
1538pub fn with_family_font<R>(
1543 s: &str,
1544 family_list: &str,
1545 bold: bool,
1546 f: impl FnOnce(&mut VectorFont) -> R,
1547) -> Option<R> {
1548 if family_list.is_empty() {
1549 return None;
1550 }
1551 let want: &[u16] = if bold { &[900, 700] } else { &[400] };
1552 let mut fonts = CUSTOM_FONTS.lock();
1553 for fam in family_list.split(',') {
1554 let key = fam.trim().trim_matches('"').trim_matches('\'').to_lowercase();
1555 if key.is_empty() {
1556 continue;
1557 }
1558 let prefix = alloc::format!("{}|", key);
1559 let available: Vec<(u16, bool)> = fonts
1560 .range(prefix.clone()..)
1561 .take_while(|(k, _)| k.starts_with(&prefix))
1562 .filter_map(|(k, font)| {
1563 let w = k.get(prefix.len()..)?.parse::<u16>().ok()?;
1564 Some((w, crate::kernel::font_select::covers_all(s, |c| font.primary_has_glyph(c))))
1565 })
1566 .collect();
1567 let hit = crate::kernel::font_select::pick_weight(want, &available)
1568 .map(|w| font_key(&key, w))
1569 .unwrap_or(key.clone());
1570 if fonts.contains_key(&hit) {
1571 if let Some(font) = fonts.get_mut(&hit) {
1572 return Some(f(font));
1573 }
1574 }
1575 for cand in crate::kernel::font_select::generic_family_candidates(&key) {
1578 let prefix = alloc::format!("{}|", cand);
1579 let available: Vec<(u16, bool)> = fonts
1580 .range(prefix.clone()..)
1581 .take_while(|(k, _)| k.starts_with(&prefix))
1582 .filter_map(|(k, font)| {
1583 let w = k.get(prefix.len()..)?.parse::<u16>().ok()?;
1584 Some((w, crate::kernel::font_select::covers_all(s, |c| font.primary_has_glyph(c))))
1585 })
1586 .collect();
1587 if let Some(w) = crate::kernel::font_select::pick_weight(want, &available) {
1588 if let Some(font) = fonts.get_mut(&font_key(cand, w)) {
1589 return Some(f(font));
1590 }
1591 }
1592 }
1593 }
1594 None
1595}
1596
1597pub fn get_vector_string_width_family(
1605 s: &str,
1606 size_px: u32,
1607 family_list: &str,
1608 bold: bool,
1609 ls: i32,
1610 ws: i32,
1611) -> u32 {
1612 with_family_font(s, family_list, bold, |f| f.get_string_width_ls_ws(s, size_px, ls, ws))
1613 .unwrap_or_else(|| get_vector_string_width_ls_ws(s, size_px, ls, ws))
1614}
1615
1616#[allow(clippy::too_many_arguments)]
1619pub fn get_vector_string_wrapped_size_family(
1620 s: &str,
1621 size_px: u32,
1622 max_width: u32,
1623 ls: i32,
1624 ws: i32,
1625 word_break_all: bool,
1626 white_space_pre_wrap: bool,
1627 family_list: &str,
1628 bold: bool,
1629) -> (u32, u32) {
1630 with_family_font(s, family_list, bold, |f| {
1631 f.get_string_wrapped_size_ext(s, size_px, max_width, ls, ws, word_break_all, white_space_pre_wrap)
1632 })
1633 .unwrap_or_else(|| {
1634 get_vector_string_wrapped_size_ext(s, size_px, max_width, ls, ws, word_break_all, white_space_pre_wrap)
1635 })
1636}
1637
1638pub fn get_vector_string_width(s: &str, size_px: u32) -> u32 {
1639 if let Some(ref mut font) = *GLOBAL_VECTOR_FONT.lock() {
1640 font.get_string_width(s, size_px)
1641 } else {
1642 s.len() as u32 * (size_px / 2) }
1644}
1645
1646pub fn get_vector_string_width_ls(s: &str, size_px: u32, ls: i32) -> u32 {
1648 get_vector_string_width_ls_ws(s, size_px, ls, 0)
1649}
1650
1651pub fn get_vector_string_width_ls_ws(s: &str, size_px: u32, ls: i32, ws: i32) -> u32 {
1653 if let Some(ref mut font) = *GLOBAL_VECTOR_FONT.lock() {
1654 font.get_string_width_ls_ws(s, size_px, ls, ws)
1655 } else {
1656 let space_count = s.chars().filter(|&c| c == ' ').count() as i32;
1657 let base = s.len() as i32 * (size_px / 2) as i32
1658 + s.chars().count() as i32 * ls
1659 + space_count * ws;
1660 base.max(0) as u32
1661 }
1662}
1663
1664pub fn get_vector_char_width(c: char, size_px: u32) -> u32 {
1667 if let Some(ref mut font) = *GLOBAL_VECTOR_FONT.lock() {
1668 font.get_glyph_advance(c, size_px)
1669 } else {
1670 size_px / 2
1671 }
1672}
1673
1674pub fn get_vector_string_wrapped_size(s: &str, size_px: u32, max_width: u32) -> (u32, u32) {
1675 if let Some(ref mut font) = *GLOBAL_VECTOR_FONT.lock() {
1676 font.get_string_wrapped_size(s, size_px, max_width)
1677 } else {
1678 let char_w = size_px / 2;
1679 let max_chars = (max_width / char_w).max(1);
1680 let chars_count = s.chars().count() as u32;
1681 let lines = chars_count.div_ceil(max_chars);
1682 (
1683 max_width.min(chars_count * char_w),
1684 lines.max(1) * (size_px + 4),
1685 )
1686 }
1687}
1688
1689pub fn get_vector_string_wrapped_size_ls(
1691 s: &str,
1692 size_px: u32,
1693 max_width: u32,
1694 ls: i32,
1695) -> (u32, u32) {
1696 get_vector_string_wrapped_size_ls_ws(s, size_px, max_width, ls, 0)
1697}
1698
1699pub fn get_vector_string_wrapped_size_ls_ws(
1701 s: &str,
1702 size_px: u32,
1703 max_width: u32,
1704 ls: i32,
1705 ws: i32,
1706) -> (u32, u32) {
1707 if let Some(ref mut font) = *GLOBAL_VECTOR_FONT.lock() {
1708 font.get_string_wrapped_size_ls_ws(s, size_px, max_width, ls, ws)
1709 } else {
1710 let char_w = ((size_px / 2) as i32 + ls).max(1) as u32;
1711 let max_chars = (max_width / char_w).max(1);
1712 let chars_count = s.chars().count() as u32;
1713 let lines = chars_count.div_ceil(max_chars);
1714 (
1715 max_width.min(chars_count * char_w),
1716 lines.max(1) * (size_px + 4),
1717 )
1718 }
1719}
1720
1721pub fn get_vector_string_wrapped_size_ext(
1722 s: &str,
1723 size_px: u32,
1724 max_width: u32,
1725 ls: i32,
1726 ws: i32,
1727 word_break_all: bool,
1728 white_space_pre_wrap: bool,
1729) -> (u32, u32) {
1730 if let Some(ref mut font) = *GLOBAL_VECTOR_FONT.lock() {
1731 font.get_string_wrapped_size_ext(s, size_px, max_width, ls, ws, word_break_all, white_space_pre_wrap)
1732 } else {
1733 let char_w = ((size_px / 2) as i32 + ls).max(1) as u32;
1734 let max_chars = (max_width / char_w).max(1);
1735 let chars_count = s.chars().count() as u32;
1736 let lines = chars_count.div_ceil(max_chars);
1737 (
1738 max_width.min(chars_count * char_w),
1739 lines.max(1) * (size_px + 4),
1740 )
1741 }
1742}
1743
1744pub fn is_line_start_forbidden(c: char) -> bool {
1745 matches!(
1746 c,
1747 '、' | '。'
1748 | ','
1749 | '.'
1750 | '・'
1751 | ':'
1752 | ';'
1753 | '?'
1754 | '!'
1755 | ')'
1756 | ']'
1757 | '}'
1758 | '〉'
1759 | '》'
1760 | '』'
1761 | '」'
1762 | '〕'
1763 | 'ー'
1764 | '〜'
1765 | '々'
1766 | '〆'
1767 | 'ぁ'
1768 | 'ぃ'
1769 | 'ぅ'
1770 | 'ぇ'
1771 | 'ぉ'
1772 | 'っ'
1773 | 'ゃ'
1774 | 'ゅ'
1775 | 'ょ'
1776 | 'ゎ'
1777 | 'ァ'
1778 | 'ィ'
1779 | 'ゥ'
1780 | 'ェ'
1781 | 'ォ'
1782 | 'ッ'
1783 | 'ャ'
1784 | 'ュ'
1785 | 'ョ'
1786 | 'ヮ'
1787 | 'ヶ'
1788 | 'ヵ'
1789 )
1790}
1791
1792pub fn is_line_end_forbidden(c: char) -> bool {
1793 matches!(c, '(' | '[' | '{' | '〈' | '《' | '『' | '「' | '〔')
1794}