1use alloc::string::String;
47use alloc::vec::Vec;
48
49use crate::font::{FN_FAMILY, FN_FULL, FN_STYLE, FN_TYPO_FAMILY, FS_SYN_BOLD, FS_SYN_ITALIC};
50use crate::fontconf::{is_generic, FontConf};
51use crate::ttf::{sfnt_name, Tb, ERR_FAIL, NAME_CAP};
52use crate::var::{Var, VarAxis, VarInstance};
53use crate::Text;
54
55pub const FDB_RESCAN: u32 = 1;
56pub const FDFF_ITALIC: u16 = 1;
57pub const FDFF_BOLD: u16 = 2;
58pub const FDFF_OBLIQUE: u16 = 4;
59pub const FDFF_CFF: u16 = 8;
60pub const FDFF_COLOR: u16 = 16;
61pub const FDFF_VAR: u16 = 32;
62pub const FDFF_SYMBOL: u16 = 64;
63pub const FDFF_CFF2: u16 = 128;
65pub const FDFF_NOLATIN: u16 = 256;
67
68const DEPTH_MAX: u32 = 8;
69const PATH_CAP: usize = 4096;
70const HDR_READ: usize = 4096;
71const TTC_MAX: u32 = 64;
72const NT_MAX: u32 = 1024;
73const NAME_MAX: u32 = 1 << 20;
74const CMAP_MAX: u32 = 1 << 20;
75const FILES_MAX: usize = 20000;
76const LOADED_MAX: usize = 256;
77const CACHE_MAGIC: u32 = 0x3142_4446; const CACHE_HDR: usize = 28;
79const CACHE_VERSION: u32 = 4;
80const VISIT_MAX: usize = 4096;
81const TAG_TTCF: u32 = 0x7474_6366;
82const TAG_TRUE: u32 = 0x7472_7565;
83const TAG_OTTO: u32 = 0x4F54_544F;
84const TAG_V1: u32 = 0x0001_0000;
85const T_NAME: u32 = 0x6E61_6D65;
86const T_OS2: u32 = 0x4F53_2F32;
87const T_HEAD: u32 = 0x6865_6164;
88const T_GLYF: u32 = 0x676C_7966;
89const T_CFF: u32 = 0x4346_4620;
90const T_CFF2: u32 = 0x4346_4632;
91const T_COLR: u32 = 0x434F_4C52;
92const T_CBDT: u32 = 0x4342_4454;
93const T_FVAR: u32 = 0x6676_6172;
94const T_CMAP: u32 = 0x636D_6170;
95const FVAR_MAX: u32 = 1 << 20;
96static NAME_WANT: [u32; 4] = [FN_FAMILY, FN_STYLE, FN_FULL, FN_TYPO_FAMILY];
97
98#[derive(Clone, Default)]
100pub struct FdFace {
101 pub path: String,
102 pub style: String, pub full: String, pub family: String, pub tfamily: String, pub face: u32, pub weight: u16, pub flags: u16, }
110
111#[derive(Clone, Debug, Default, PartialEq)]
113pub struct FdVar {
114 pub axes: Vec<VarAxis>,
116 pub instances: Vec<VarInstance>,
118}
119
120#[derive(Clone, Copy)]
122struct FileRec {
123 path: u32,
124 size: u32,
125 first: u32,
126 n: u32,
127}
128
129#[derive(Clone, Copy, Default)]
131struct FaceRec {
132 file: u32,
133 face: u32,
134 weight: u16,
135 flags: u16,
136 fam: u32,
137 style: u32,
138 full: u32,
139 tfam: u32,
140}
141
142struct Family {
143 first: u32,
144 n: u32,
145}
146
147#[derive(Default)]
148struct Cache {
149 files: Vec<FileRec>,
150 faces: Vec<FaceRec>,
151 pool: Vec<u8>,
152 hash: Vec<u64>,
153 by_hash: Vec<(u64, u32)>, }
155
156#[derive(Default)]
157pub struct FontDb {
158 pool: Vec<u8>,
159 files: Vec<FileRec>,
160 fi: Vec<FaceRec>,
161 faces: Vec<FdFace>,
162 fam: Vec<Family>,
163 loaded: Vec<(usize, u32, i32)>, vars: Vec<(usize, Option<FdVar>)>, cache: Option<Cache>,
166 use_cache: bool,
167 visited: Vec<(u64, u64)>,
168}
169
170fn read_var(path: &str, face: u32) -> Option<FdVar> {
172 let f = plat::File::open(path, plat::FO_READ)?;
173 let mut h = [0u8; 16];
174 if read_at(&f, 0, &mut h) < 12 {
175 return None;
176 }
177 let dir = if be32(&h, 0) == TAG_TTCF {
178 let mut o = [0u8; 4];
179 if read_at(&f, 12 + face as u64 * 4, &mut o) != 4 {
180 return None;
181 }
182 be32(&o, 0) as u64
183 } else {
184 0
185 };
186 let mut d = [0u8; 12];
187 if read_at(&f, dir, &mut d) != 12 {
188 return None;
189 }
190 let nt = be16(&d, 4).min(NT_MAX);
191 let mut recs = alloc::vec![0u8; (nt * 16) as usize];
192 if read_at(&f, dir + 12, &mut recs) != (nt * 16) as i64 {
193 return None;
194 }
195 let (mut fvar, mut name) = ((0u32, 0u32), (0u32, 0u32));
196 for i in 0..nt as usize {
197 let r = &recs[i * 16..];
198 match be32(r, 0) {
199 T_FVAR => fvar = (be32(r, 8), be32(r, 12)),
200 T_NAME => name = (be32(r, 8), be32(r, 12)),
201 _ => {}
202 }
203 }
204 let table = |(o, l): (u32, u32), max: u32| -> Vec<u8> {
205 let mut b = alloc::vec![0u8; l.min(max) as usize];
206 let n = read_at(&f, o as u64, &mut b);
207 b.truncate(n.max(0) as usize);
208 b
209 };
210 let fv = table(fvar, FVAR_MAX);
211 let nm = table(name, NAME_MAX);
212 let v = Var::parse(Tb(&fv), Tb(&[]), Tb(&[]), Tb(&[]), Tb(&[]), Tb(&[]), Tb(&nm))?;
213 Some(FdVar { axes: v.axes, instances: v.instances })
214}
215
216pub fn fnv64(d: &[u8]) -> u64 {
218 let mut h: u64 = 0xCBF2_9CE4_8422_2325;
219 for &b in d {
220 h ^= b as u64;
221 h = h.wrapping_mul(0x0000_0100_0000_01B3);
222 }
223 h
224}
225
226fn ci_cmp(a: &[u8], b: &[u8]) -> i32 {
228 let mut i = 0;
229 loop {
230 let mut x = a.get(i).copied().unwrap_or(0) as i32;
231 let mut y = b.get(i).copied().unwrap_or(0) as i32;
232 if (x - b'A' as i32) as u32 <= 25 {
233 x += 32;
234 }
235 if (y - b'A' as i32) as u32 <= 25 {
236 y += 32;
237 }
238 let d = x - y;
239 if d != 0 || y == 0 {
240 return d;
241 }
242 i += 1;
243 }
244}
245
246fn pstr(pool: &[u8], off: u32) -> &[u8] {
248 let s = &pool[off as usize..];
249 &s[..s.iter().position(|&c| c == 0).unwrap_or(s.len())]
250}
251
252fn pool_put(pool: &mut Vec<u8>, s: &[u8]) -> u32 {
253 let off = pool.len() as u32;
254 pool.extend_from_slice(s);
255 pool.push(0);
256 off
257}
258
259fn ends_ci(s: &str, ext: &str) -> bool {
260 s.len() >= ext.len() && s.as_bytes()[s.len() - ext.len()..].eq_ignore_ascii_case(ext.as_bytes())
261}
262
263fn be16(d: &[u8], o: usize) -> u32 {
264 u16::from_be_bytes([d[o], d[o + 1]]) as u32
265}
266
267fn be32(d: &[u8], o: usize) -> u32 {
268 u32::from_be_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]])
269}
270
271fn cmap_latin(f: &plat::File, off: u32, len: u32) -> bool {
275 let mut b = alloc::vec![0u8; len.min(CMAP_MAX) as usize];
276 let got = read_at(f, off as u64, &mut b);
277 if got < 4 {
278 return true;
279 }
280 b.truncate(got as usize);
281 for mac in [false, true] {
282 for i in 0..be16(&b, 2) as usize {
283 let r = 4 + i * 8;
284 if r + 8 > b.len() {
285 break;
286 }
287 let (pid, eid, s) = (be16(&b, r), be16(&b, r + 2), be32(&b, r + 4) as usize);
288 let kind = if mac { pid == 1 && eid == 0 } else { pid == 0 || (pid == 3 && (eid == 1 || eid == 10)) };
289 if !kind || s.saturating_add(16) > b.len() {
290 continue;
291 }
292 let mapped = |cp: u32| match be16(&b, s) {
293 0 if mac => Some(b.get(s + 6 + cp as usize).is_some_and(|&g| g != 0)),
294 6 if mac => {
295 let (first, n) = (be16(&b, s + 6), be16(&b, s + 8));
296 let g = s + 10 + cp.wrapping_sub(first) as usize * 2;
297 Some(cp >= first && cp - first < n && g + 2 <= b.len() && be16(&b, g) != 0)
298 }
299 4 if !mac => {
300 let seg = be16(&b, s + 6) as usize / 2;
301 (0..seg).map(|k| (s + 14 + k * 2, s + 16 + seg * 2 + k * 2)).take_while(|&(_, st)| st + 2 <= b.len())
302 .find(|&(e, _)| be16(&b, e) >= cp).map(|(_, st)| be16(&b, st) <= cp)
303 }
304 12 if !mac => {
305 let n = be32(&b, s + 12) as usize;
306 Some((0..n).map(|k| s + 16 + k * 12).take_while(|&g| g + 8 <= b.len())
307 .any(|g| be32(&b, g) <= cp && cp <= be32(&b, g + 4)))
308 }
309 _ => None,
310 };
311 if let (Some(upper), Some(lower)) = (mapped(0x41), mapped(0x61)) {
312 return upper && lower;
313 }
314 }
315 }
316 true
317}
318
319fn le32(d: &[u8], o: usize) -> u32 {
320 u32::from_le_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]])
321}
322
323fn read_at(f: &plat::File, off: u64, buf: &mut [u8]) -> i64 {
324 match f.read_at(off, buf) {
325 Some(n) => n as i64,
326 None => -1,
327 }
328}
329
330impl FontDb {
331 pub fn new() -> FontDb {
332 FontDb::default()
333 }
334
335 pub fn clear(&mut self) {
337 self.pool = Vec::new();
338 self.files = Vec::new();
339 self.fi = Vec::new();
340 self.faces = Vec::new();
341 self.fam = Vec::new();
342 self.loaded.clear();
343 self.vars.clear();
344 }
345
346 pub fn family_count(&self) -> usize {
347 self.fam.len()
348 }
349
350 pub fn family_name(&self, i: usize) -> &str {
352 match self.fam.get(i) {
353 Some(f) => &self.faces[f.first as usize].family,
354 None => "",
355 }
356 }
357
358 pub fn family_range(&self, i: usize) -> core::ops::Range<usize> {
360 match self.fam.get(i) {
361 Some(f) => f.first as usize..(f.first + f.n) as usize,
362 None => 0..0,
363 }
364 }
365
366 pub fn family_faces(&self, i: usize) -> &[FdFace] {
368 match self.fam.get(i) {
369 Some(f) => &self.faces[f.first as usize..(f.first + f.n) as usize],
370 None => &[],
371 }
372 }
373
374 pub fn face_count(&self) -> usize {
375 self.faces.len()
376 }
377
378 pub fn face(&self, i: usize) -> Option<&FdFace> {
379 self.faces.get(i)
380 }
381
382 pub fn scan(&mut self, flags: u32) -> usize {
386 let mut ws: Vec<(String, u32)> = Vec::new();
387 Self::roots(&mut ws);
388 self.scan_from(ws, flags)
389 }
390
391 pub fn scan_dirs(&mut self, dirs: &[&str], flags: u32) -> usize {
393 let ws = dirs.iter().filter(|d| !d.is_empty()).map(|d| (String::from(*d), 0)).collect();
394 self.scan_from(ws, flags)
395 }
396
397 fn scan_from(&mut self, mut ws: Vec<(String, u32)>, flags: u32) -> usize {
398 self.clear();
399 self.visited.clear();
400 self.use_cache = flags & FDB_RESCAN == 0 && self.cache.as_ref().is_some_and(|c| !c.files.is_empty());
401 while let Some((path, depth)) = ws.pop() {
402 if !self.visit_once(&path) {
403 continue;
404 }
405 let mut ents: Vec<(String, bool, u32)> = Vec::new();
407 if !plat::dir_list(&path, &mut |name, is_dir, size| ents.push((String::from(name), is_dir, size))) {
408 continue;
409 }
410 for (name, is_dir, size) in ents {
411 let mut p2 = path.clone();
413 if p2.len() >= PATH_CAP - 2 {
414 continue; }
416 if !p2.ends_with('/') && !p2.ends_with('\\') {
417 p2.push('/');
418 }
419 p2.push_str(&name);
420 if p2.len() >= PATH_CAP - 2 {
421 continue; }
423 if is_dir {
424 if depth < DEPTH_MAX {
425 ws.push((p2, depth + 1));
426 }
427 continue;
428 }
429 if ![".ttf", ".otf", ".ttc", ".otc"].iter().any(|e| ends_ci(&p2, e)) {
430 continue;
431 }
432 if self.files.len() >= FILES_MAX {
433 continue;
434 }
435 self.add_file(&p2, size);
436 }
437 }
438 self.finalize();
439 self.faces.len()
440 }
441
442 fn visit_once(&mut self, path: &str) -> bool {
444 let Some(id) = plat::dev_ino(path) else { return true };
445 if self.visited.contains(&id) {
446 return false;
447 }
448 if self.visited.len() < VISIT_MAX {
449 self.visited.push(id);
450 }
451 true
452 }
453
454 fn roots(ws: &mut Vec<(String, u32)>) {
457 for d in plat::policy::font_dirs().into_iter().rev() {
458 ws.push((d, 0));
459 }
460 }
461
462 fn add_file(&mut self, path: &str, size: u32) {
464 let poff = pool_put(&mut self.pool, path.as_bytes());
465 let first = self.fi.len() as u32;
466 self.files.push(FileRec { path: poff, size, first, n: 0 });
467 let file = self.files.len() as u32 - 1;
468 if !(self.use_cache && self.from_cache(path, size, file)) {
469 self.parse(path, file);
470 }
471 let n = self.fi.len() as u32 - first;
473 self.files[file as usize].n = n;
474 }
475
476 fn from_cache(&mut self, path: &str, size: u32, file: u32) -> bool {
478 let Some(c) = &self.cache else { return false };
479 let h = fnv64(path.as_bytes());
480 let start = c.by_hash.partition_point(|e| e.0 < h);
481 for &(eh, i) in &c.by_hash[start..] {
482 if eh != h {
483 break;
484 }
485 let (i, f) = (i as usize, &c.files[i as usize]);
486 if c.hash[i] != h || f.size != size || pstr(&c.pool, f.path) != path.as_bytes() {
487 continue;
488 }
489 for k in f.first..f.first + f.n {
490 let mut r = c.faces[k as usize];
491 r.file = file;
492 r.fam = pool_put(&mut self.pool, pstr(&c.pool, r.fam));
494 r.style = pool_put(&mut self.pool, pstr(&c.pool, r.style));
495 r.full = pool_put(&mut self.pool, pstr(&c.pool, r.full));
496 r.tfam = pool_put(&mut self.pool, pstr(&c.pool, r.tfam));
497 self.fi.push(r);
498 }
499 return true;
500 }
501 false
502 }
503
504 fn parse(&mut self, path: &str, file: u32) {
506 let Some(f) = plat::File::open(path, plat::FO_READ) else { return };
507 let mut hdr = alloc::vec![0u8; HDR_READ];
508 let n = read_at(&f, 0, &mut hdr);
509 if n < 12 {
510 return;
511 }
512 match be32(&hdr, 0) {
513 TAG_TTCF => {
514 let nf = be32(&hdr, 8).min(TTC_MAX);
515 if (nf * 4 + 12) as i64 > n {
517 return;
518 }
519 for i in 0..nf {
520 let dir = be32(&hdr, (12 + i * 4) as usize);
521 self.face_rec(&f, dir, i, file);
522 }
523 }
524 TAG_V1 | TAG_TRUE | TAG_OTTO => self.face_rec(&f, 0, 0, file),
525 _ => {}
526 }
527 }
528
529 fn face_rec(&mut self, f: &plat::File, dir: u32, face: u32, file: u32) {
531 let (mut name, mut os2, mut head, mut cmap) = ((0u32, 0u32), (0u32, 0u32), (0u32, 0u32), (0u32, 0u32));
532 let mut flags = 0u16;
533 let mut outl = false;
534 let mut d = [0u8; 12];
536 if read_at(f, dir as u64, &mut d) != 12 {
537 return;
538 }
539 let nt = be16(&d, 4).min(NT_MAX);
540 let mut recs = alloc::vec![0u8; (nt * 16) as usize];
541 if read_at(f, dir as u64 + 12, &mut recs) != (nt * 16) as i64 {
542 return;
543 }
544 for i in 0..nt as usize {
545 let r = &recs[i * 16..];
546 let (tag, off, len) = (be32(r, 0), be32(r, 8), be32(r, 12));
547 match tag {
548 T_NAME => name = (off, len),
549 T_OS2 => os2 = (off, len),
550 T_HEAD => head = (off, len),
551 T_CMAP => cmap = (off, len),
552 T_GLYF => outl = true,
553 T_CFF => {
554 flags |= FDFF_CFF;
555 outl = true;
556 }
557 T_CFF2 => {
558 flags |= FDFF_CFF | FDFF_CFF2;
559 outl = true;
560 }
561 T_COLR => flags |= FDFF_COLOR,
562 T_CBDT => {
563 outl = true;
564 flags |= FDFF_COLOR;
565 }
566 T_FVAR => flags |= FDFF_VAR,
567 _ => {}
568 }
569 }
570 if !outl || name.1 < 6 {
571 return; }
573 let mut mac_style = 0u32;
575 if head.1 >= 54 {
576 let mut h = [0u8; 54];
577 if read_at(f, head.0 as u64, &mut h) == 54 {
578 mac_style = be16(&h, 44);
579 }
580 }
581 let mut o = [0u8; 96];
583 let mut os2n = os2.1.min(96) as i64;
584 if os2n != 0 {
585 os2n = read_at(f, os2.0 as u64, &mut o[..os2n as usize]);
586 if os2n < 0 {
587 return;
588 }
589 }
590 let nl = name.1.min(NAME_MAX) as usize;
592 let mut nb = alloc::vec![0u8; nl];
593 let got = read_at(f, name.0 as u64, &mut nb);
594 if got <= 0 {
595 return;
596 }
597 let tb = Tb(&nb[..got as usize]);
598 let mut names: [Vec<u8>; 4] = Default::default();
599 for (i, &id) in NAME_WANT.iter().enumerate() {
600 names[i] = sfnt_name(tb, id, NAME_CAP);
601 }
602 if names[0].is_empty() {
603 return; }
605 if names[3].is_empty() {
606 names[3] = names[0].clone();
607 }
608 let mut weight = 400u32;
610 if os2n >= 64 {
611 weight = be16(&o, 4);
612 if weight < 10 {
613 weight *= 100;
614 }
615 if weight == 0 {
616 weight = 400;
617 }
618 if weight > 1000 {
619 weight = 1000;
620 }
621 let s = be16(&o, 62);
622 if s & 1 != 0 {
623 flags |= FDFF_ITALIC;
624 }
625 if s & 0x20 != 0 {
626 flags |= FDFF_BOLD;
627 }
628 if s & 0x200 != 0 {
629 flags |= FDFF_OBLIQUE;
630 }
631 if os2n >= 82 && be32(&o, 78) & 0x8000_0000 != 0 {
632 flags |= FDFF_SYMBOL; }
634 } else {
635 if mac_style & 1 != 0 {
636 flags |= FDFF_BOLD;
637 weight = 700;
638 }
639 if mac_style & 2 != 0 {
640 flags |= FDFF_ITALIC;
641 }
642 }
643 if cmap.1 != 0 && !cmap_latin(f, cmap.0, cmap.1) {
644 flags |= FDFF_NOLATIN;
645 }
646 let mut r = FaceRec { file, face, weight: weight as u16, flags, ..Default::default() };
647 r.fam = pool_put(&mut self.pool, &names[0]);
648 r.style = pool_put(&mut self.pool, &names[1]);
649 r.full = pool_put(&mut self.pool, &names[2]);
650 r.tfam = pool_put(&mut self.pool, &names[3]);
651 self.fi.push(r);
652 }
653
654 fn cmp(&self, i: usize, j: usize) -> i32 {
658 let (a, b) = (&self.fi[i], &self.fi[j]);
659 let p = &self.pool;
660 let c = ci_cmp(pstr(p, a.fam), pstr(p, b.fam));
661 if c != 0 {
662 return c;
663 }
664 let c = a.weight as i32 - b.weight as i32;
665 if c != 0 {
666 return c;
667 }
668 let m = FDFF_ITALIC | FDFF_OBLIQUE;
669 let c = (a.flags & m) as i32 - (b.flags & m) as i32;
670 if c != 0 {
671 return c;
672 }
673 let c = ci_cmp(pstr(p, a.style), pstr(p, b.style));
674 if c != 0 {
675 return c;
676 }
677 let c = a.file.wrapping_sub(b.file) as i32;
678 if c != 0 {
679 return c;
680 }
681 a.face.wrapping_sub(b.face) as i32
682 }
683
684 fn same(&self, i: usize, j: usize) -> bool {
686 let (a, b) = (&self.fi[i], &self.fi[j]);
687 let p = &self.pool;
688 a.weight == b.weight
689 && a.flags == b.flags
690 && ci_cmp(pstr(p, a.fam), pstr(p, b.fam)) == 0
691 && ci_cmp(pstr(p, a.full), pstr(p, b.full)) == 0
692 }
693
694 fn finalize(&mut self) {
697 let n = self.fi.len();
698 if n == 0 {
699 return;
700 }
701 let mut idx: Vec<usize> = (0..n).collect();
702 for gap in [701usize, 301, 132, 57, 23, 10, 4, 1] {
703 for i in gap..n {
704 let t = idx[i];
705 let mut j = i;
706 while j >= gap && self.cmp(idx[j - gap], t) > 0 {
707 idx[j] = idx[j - gap];
708 j -= gap;
709 }
710 idx[j] = t;
711 }
712 }
713 let mut prev: Option<usize> = None;
714 let p = |o: u32| String::from_utf8_lossy(pstr(&self.pool, o)).into_owned();
715 let mut faces: Vec<FdFace> = Vec::new();
716 let mut fam: Vec<Family> = Vec::new();
717 for &k in &idx {
718 if let Some(pk) = prev
719 && self.same(pk, k)
720 {
721 continue;
722 }
723 prev = Some(k);
724 let r = self.fi[k];
725 let fdf = FdFace {
726 path: p(self.files[r.file as usize].path),
727 style: p(r.style),
728 full: p(r.full),
729 family: p(r.fam),
730 tfamily: p(r.tfam),
731 face: r.face,
732 weight: r.weight,
733 flags: r.flags,
734 };
735 match fam.last_mut() {
737 Some(f) if ci_cmp(faces[f.first as usize].family.as_bytes(), fdf.family.as_bytes()) == 0 => f.n += 1,
738 _ => fam.push(Family { first: faces.len() as u32, n: 1 }),
739 }
740 faces.push(fdf);
741 }
742 self.faces = faces;
743 self.fam = fam;
744 }
745
746 pub fn find(&self, family: &str, weight: u32, italic: bool) -> Option<(usize, u32)> {
750 let w = if weight == 0 { 400 } else { weight.min(1000) as i32 };
751 for pass in 0..2 {
752 let mut best = i32::MAX;
753 let mut bi = None;
754 for (i, f) in self.faces.iter().enumerate() {
755 let name = if pass == 0 { &f.family } else { &f.tfamily };
756 if ci_cmp(name.as_bytes(), family.as_bytes()) != 0 {
757 continue;
758 }
759 let fi = f.flags & (FDFF_ITALIC | FDFF_OBLIQUE) != 0;
761 let mut s = if fi != italic { 10000 } else { 0 };
762 let fw = f.weight as i32;
763 s += (fw - w).abs() * 2;
764 if (w >= 500 && fw < w) || (w < 500 && fw > w) {
766 s += 1;
767 }
768 if s < best {
769 best = s;
770 bi = Some(i);
771 }
772 }
773 if let Some(i) = bi {
774 let f = &self.faces[i];
775 let mut syn = 0;
776 if italic && f.flags & (FDFF_ITALIC | FDFF_OBLIQUE) == 0 {
777 syn |= FS_SYN_ITALIC;
778 }
779 if w >= 600 && f.weight < 600 {
780 syn |= FS_SYN_BOLD;
781 }
782 return Some((i, syn));
783 }
784 }
785 None
786 }
787
788 pub fn resolve(&self, conf: &FontConf, family: &str, lang: &str, weight: u32, italic: bool) -> Option<(usize, u32, String)> {
795 let mut tried = conf.candidates(family, lang);
796 if !is_generic(family) {
797 tried.extend(conf.candidates("sans-serif", lang));
798 }
799 let any_script = ["emoji", "math"].iter().any(|g| family.trim().eq_ignore_ascii_case(g));
800 let mut spare = None;
801 for f in tried.iter().filter(|f| !is_generic(f)) {
802 if let Some((i, syn)) = self.find(f, weight, italic) {
803 if any_script || self.faces[i].flags & FDFF_NOLATIN == 0 || f.trim().eq_ignore_ascii_case(family.trim()) {
804 return Some((i, syn, self.faces[i].family.clone()));
805 }
806 spare = spare.or(Some((i, syn)));
807 }
808 }
809 spare.map(|(i, syn)| (i, syn, self.faces[i].family.clone()))
810 }
811
812 pub fn load_resolved(&mut self, text: &mut Text, conf: &FontConf, family: &str, lang: &str, weight: u32, italic: bool) -> (i32, String) {
814 match self.resolve(conf, family, lang, weight, italic) {
815 Some((fi, syn, fam)) => (self.load_var(text, fi, syn, weight, italic, None, &[]), fam),
816 None => (ERR_FAIL, String::new()),
817 }
818 }
819
820 pub fn load(&mut self, text: &mut Text, family: &str, weight: u32, italic: bool) -> i32 {
822 let Some((fi, syn)) = self.find(family, weight, italic) else { return ERR_FAIL };
823 self.load_var(text, fi, syn, weight, italic, None, &[])
824 }
825
826 pub fn load_face(&mut self, text: &mut Text, fi: usize, syn: u32) -> i32 {
829 if fi >= self.faces.len() {
830 return ERR_FAIL;
831 }
832 for &(f, s, id) in &self.loaded {
834 if f == fi && s == syn && text.valid(id as u32) {
835 return id;
836 }
837 }
838 let face = &self.faces[fi];
839 let mut id = text.load_file(&face.path, face.face);
840 if id < 0 {
841 return id;
842 }
843 if syn != 0 {
844 id = text.style(id as u32, syn);
845 if id < 0 {
846 return id;
847 }
848 }
849 if self.loaded.len() < LOADED_MAX {
850 self.loaded.push((fi, syn, id));
851 }
852 id
853 }
854
855 pub fn variations(&mut self, i: usize) -> Option<&FdVar> {
860 let f = self.faces.get(i)?;
861 if f.flags & FDFF_VAR == 0 {
862 return None;
863 }
864 let k = match self.vars.iter().position(|v| v.0 == i) {
865 Some(k) => k,
866 None => {
867 let v = read_var(&f.path, f.face);
868 self.vars.push((i, v));
869 self.vars.len() - 1
870 }
871 };
872 self.vars[k].1.as_ref()
873 }
874
875 pub fn var_values(&mut self, i: usize, weight: u32, italic: bool, size_pt: Option<f32>, axes: &[([u8; 4], f32)]) -> Option<(Vec<f32>, u32)> {
878 let face_weight = self.faces.get(i)?.weight as u32;
879 let face_italic = self.faces.get(i)?.flags & (FDFF_ITALIC | FDFF_OBLIQUE) != 0;
880 let v = self.variations(i)?;
881 let w = if weight == 0 { 400 } else { weight.min(1000) };
882 let mut vals: Vec<f32> = v.axes.iter().map(|a| a.def).collect();
883 let mut syn = 0;
884 let find = |t: &[u8; 4]| v.axes.iter().position(|a| &a.tag == t);
885 match find(b"wght") {
887 Some(k) => {
888 let a = &v.axes[k];
889 vals[k] = (w as f32).clamp(a.min, a.max);
890 if w >= 600 && a.max < 600.0 {
891 syn |= FS_SYN_BOLD;
892 }
893 }
894 None if w >= 600 && face_weight < 600 => syn |= FS_SYN_BOLD,
895 None => {}
896 }
897 if italic && !face_italic {
899 match (find(b"ital"), find(b"slnt")) {
900 (Some(k), _) if v.axes[k].max >= 1.0 => vals[k] = 1.0,
901 (_, Some(k)) if v.axes[k].min < 0.0 => vals[k] = v.axes[k].min,
902 _ => syn |= FS_SYN_ITALIC,
903 }
904 }
905 if let (Some(k), Some(pt)) = (find(b"opsz"), size_pt) {
907 vals[k] = pt.clamp(v.axes[k].min, v.axes[k].max);
908 }
909 for &(t, x) in axes {
911 if let Some(k) = find(&t) {
912 vals[k] = if x.is_nan() { v.axes[k].def } else { x.clamp(v.axes[k].min, v.axes[k].max) };
913 if &t == b"wght" {
914 syn &= !FS_SYN_BOLD;
915 }
916 if &t == b"ital" || &t == b"slnt" {
917 syn &= !FS_SYN_ITALIC;
918 }
919 }
920 }
921 Some((vals, syn))
922 }
923
924 #[allow(clippy::too_many_arguments)]
928 pub fn load_var(&mut self, text: &mut Text, fi: usize, syn: u32, weight: u32, italic: bool, size_pt: Option<f32>, axes: &[([u8; 4], f32)]) -> i32 {
929 let Some((vals, vsyn)) = self.var_values(fi, weight, italic, size_pt, axes) else { return self.load_face(text, fi, syn) };
930 let syn = (syn & !(FS_SYN_BOLD | FS_SYN_ITALIC)) | vsyn;
933 let id = self.load_face(text, fi, 0);
934 if id < 0 {
935 return id;
936 }
937 let tags: Vec<([u8; 4], f32)> = match self.variations(fi) {
938 Some(v) => v.axes.iter().zip(&vals).map(|(a, &x)| (a.tag, x)).collect(),
939 None => Vec::new(),
940 };
941 let id = text.vary(id as u32, &tags);
942 if id < 0 || syn == 0 { id } else { text.style(id as u32, syn) }
943 }
944
945 pub fn save_cache(&self, path: &str) -> i32 {
949 let mut b = Vec::with_capacity(CACHE_HDR + self.files.len() * 16 + self.fi.len() * 32 + self.pool.len());
950 b.extend_from_slice(&CACHE_MAGIC.to_le_bytes());
951 b.extend_from_slice(&CACHE_VERSION.to_le_bytes());
952 b.extend_from_slice(&(self.files.len() as u32).to_le_bytes());
953 b.extend_from_slice(&(self.fi.len() as u32).to_le_bytes());
954 b.extend_from_slice(&(self.pool.len() as u32).to_le_bytes());
955 b.extend_from_slice(&[0u8; 8]);
956 for f in &self.files {
957 for v in [f.path, f.size, f.first, f.n] {
958 b.extend_from_slice(&v.to_le_bytes());
959 }
960 }
961 for r in &self.fi {
962 b.extend_from_slice(&r.file.to_le_bytes());
963 b.extend_from_slice(&r.face.to_le_bytes());
964 b.extend_from_slice(&r.weight.to_le_bytes());
965 b.extend_from_slice(&r.flags.to_le_bytes());
966 for v in [r.fam, r.style, r.full, r.tfam, 0] {
967 b.extend_from_slice(&v.to_le_bytes());
968 }
969 }
970 b.extend_from_slice(&self.pool);
971 let h = fnv64(&b[CACHE_HDR..]);
972 b[20..28].copy_from_slice(&h.to_le_bytes());
973 if plat::write_file(path, &b) { 0 } else { -1 }
974 }
975
976 pub fn load_cache(&mut self, path: &str) -> i32 {
978 self.cache = None;
979 let Some(d) = plat::read_file(path) else { return -1 };
980 if d.len() < CACHE_HDR || le32(&d, 0) != CACHE_MAGIC || le32(&d, 4) != CACHE_VERSION {
981 return -1;
982 }
983 if fnv64(&d[CACHE_HDR..]) != u64::from_le_bytes(d[20..28].try_into().unwrap()) {
984 return -1;
985 }
986 let nf = le32(&d, 8) as usize;
987 let nfa = le32(&d, 12) as usize;
988 let np = le32(&d, 16) as usize;
989 if nf > FILES_MAX || nfa > FILES_MAX * 8 || np == 0 {
990 return -1;
991 }
992 if nf * 16 + nfa * 32 + np + CACHE_HDR > d.len() {
993 return -1;
994 }
995 let fo = CACHE_HDR;
996 let ao = fo + nf * 16;
997 let po = ao + nfa * 32;
998 let pool = &d[po..po + np];
999 if pool[np - 1] != 0 {
1000 return -1;
1001 }
1002 let mut c = Cache::default();
1004 for i in 0..nf {
1005 let o = fo + i * 16;
1006 let f = FileRec { path: le32(&d, o), size: le32(&d, o + 4), first: le32(&d, o + 8), n: le32(&d, o + 12) };
1007 let (end, carry) = f.first.overflowing_add(f.n);
1008 if f.path as usize >= np || carry || end as usize > nfa {
1009 return -1;
1010 }
1011 c.files.push(f);
1012 }
1013 for i in 0..nfa {
1014 let o = ao + i * 32;
1015 let r = FaceRec {
1016 file: le32(&d, o),
1017 face: le32(&d, o + 4),
1018 weight: u16::from_le_bytes([d[o + 8], d[o + 9]]),
1019 flags: u16::from_le_bytes([d[o + 10], d[o + 11]]),
1020 fam: le32(&d, o + 12),
1021 style: le32(&d, o + 16),
1022 full: le32(&d, o + 20),
1023 tfam: le32(&d, o + 24),
1024 };
1025 if r.file as usize >= nf
1026 || r.fam as usize >= np
1027 || r.style as usize >= np
1028 || r.full as usize >= np
1029 || r.tfam as usize >= np
1030 {
1031 return -1;
1032 }
1033 c.faces.push(r);
1034 }
1035 c.pool = pool.to_vec();
1036 c.hash = c.files.iter().map(|f| fnv64(pstr(&c.pool, f.path))).collect();
1038 c.by_hash = c.hash.iter().enumerate().map(|(i, &h)| (h, i as u32)).collect();
1039 c.by_hash.sort_unstable();
1040 self.cache = Some(c);
1041 0
1042 }
1043}