1#[cfg(feature = "alloc")]
7use alloc::{vec, vec::Vec};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FontError {
12 InvalidFont,
14 TableNotFound,
16 GlyphNotFound,
18 UnsupportedFormat,
20 DataTruncated,
22 BufferTooSmall,
24}
25
26impl core::fmt::Display for FontError {
27 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
28 match self {
29 Self::InvalidFont => write!(f, "invalid font"),
30 Self::TableNotFound => write!(f, "table not found"),
31 Self::GlyphNotFound => write!(f, "glyph not found"),
32 Self::UnsupportedFormat => write!(f, "unsupported format"),
33 Self::DataTruncated => write!(f, "data truncated"),
34 Self::BufferTooSmall => write!(f, "buffer too small"),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
41pub enum SubpixelMode {
42 #[default]
44 None,
45 Rgb,
47 Bgr,
49 VerticalRgb,
51 VerticalBgr,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct OutlinePoint {
58 pub x: i16,
60 pub y: i16,
62 pub on_curve: bool,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68#[cfg(feature = "alloc")]
69pub struct GlyphContour {
70 pub points: Vec<OutlinePoint>,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76#[cfg(feature = "alloc")]
77pub struct GlyphOutline {
78 pub contours: Vec<GlyphContour>,
80 pub x_min: i16,
82 pub y_min: i16,
84 pub x_max: i16,
86 pub y_max: i16,
88 pub advance_width: u16,
90 pub lsb: i16,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub struct TableTag(pub [u8; 4]);
97
98impl TableTag {
99 pub const CMAP: Self = Self(*b"cmap");
100 pub const GLYF: Self = Self(*b"glyf");
101 pub const HEAD: Self = Self(*b"head");
102 pub const HHEA: Self = Self(*b"hhea");
103 pub const HMTX: Self = Self(*b"hmtx");
104 pub const LOCA: Self = Self(*b"loca");
105 pub const MAXP: Self = Self(*b"maxp");
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct TableEntry {
111 pub tag: TableTag,
112 pub offset: u32,
113 pub length: u32,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct HeadTable {
119 pub units_per_em: u16,
121 pub index_to_loc_format: i16,
123 pub x_min: i16,
125 pub y_min: i16,
126 pub x_max: i16,
127 pub y_max: i16,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub struct HheaTable {
133 pub ascent: i16,
135 pub descent: i16,
137 pub line_gap: i16,
139 pub num_h_metrics: u16,
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub struct MaxpTable {
146 pub num_glyphs: u16,
147}
148
149pub(crate) fn read_u16_be(data: &[u8], offset: usize) -> Option<u16> {
151 if offset + 2 > data.len() {
152 return None;
153 }
154 Some(u16::from_be_bytes([data[offset], data[offset + 1]]))
155}
156
157pub(crate) fn read_i16_be(data: &[u8], offset: usize) -> Option<i16> {
159 read_u16_be(data, offset).map(|v| v as i16)
160}
161
162pub(crate) fn read_u32_be(data: &[u8], offset: usize) -> Option<u32> {
164 if offset + 4 > data.len() {
165 return None;
166 }
167 Some(u32::from_be_bytes([
168 data[offset],
169 data[offset + 1],
170 data[offset + 2],
171 data[offset + 3],
172 ]))
173}
174
175#[derive(Debug)]
180pub struct TtfParser<'a> {
181 data: &'a [u8],
183 num_tables: u16,
185}
186
187impl<'a> TtfParser<'a> {
188 pub fn new(data: &'a [u8]) -> Result<Self, FontError> {
190 if data.len() < 12 {
191 return Err(FontError::InvalidFont);
192 }
193
194 let version = read_u32_be(data, 0).ok_or(FontError::DataTruncated)?;
196 if version != 0x00010000 && version != 0x4F54544F {
197 return Err(FontError::InvalidFont);
198 }
199
200 let num_tables = read_u16_be(data, 4).ok_or(FontError::DataTruncated)?;
201
202 Ok(Self { data, num_tables })
203 }
204
205 pub fn find_table(&self, tag: TableTag) -> Option<TableEntry> {
207 let header_size = 12;
208 let entry_size = 16;
209
210 for i in 0..self.num_tables as usize {
211 let offset = header_size + i * entry_size;
212 if offset + entry_size > self.data.len() {
213 break;
214 }
215
216 let t = [
217 self.data[offset],
218 self.data[offset + 1],
219 self.data[offset + 2],
220 self.data[offset + 3],
221 ];
222
223 if t == tag.0 {
224 let table_offset = read_u32_be(self.data, offset + 8)?;
225 let length = read_u32_be(self.data, offset + 12)?;
226 return Some(TableEntry {
227 tag,
228 offset: table_offset,
229 length,
230 });
231 }
232 }
233 None
234 }
235
236 pub fn table_data(&self, entry: &TableEntry) -> Result<&'a [u8], FontError> {
238 let start = entry.offset as usize;
239 let end = start + entry.length as usize;
240 if end > self.data.len() {
241 return Err(FontError::DataTruncated);
242 }
243 Ok(&self.data[start..end])
244 }
245
246 pub fn parse_head(&self) -> Result<HeadTable, FontError> {
248 let entry = self
249 .find_table(TableTag::HEAD)
250 .ok_or(FontError::TableNotFound)?;
251 let d = self.table_data(&entry)?;
252 if d.len() < 54 {
253 return Err(FontError::DataTruncated);
254 }
255
256 Ok(HeadTable {
257 units_per_em: read_u16_be(d, 18).ok_or(FontError::DataTruncated)?,
258 x_min: read_i16_be(d, 36).ok_or(FontError::DataTruncated)?,
259 y_min: read_i16_be(d, 38).ok_or(FontError::DataTruncated)?,
260 x_max: read_i16_be(d, 40).ok_or(FontError::DataTruncated)?,
261 y_max: read_i16_be(d, 42).ok_or(FontError::DataTruncated)?,
262 index_to_loc_format: read_i16_be(d, 50).ok_or(FontError::DataTruncated)?,
263 })
264 }
265
266 pub fn parse_hhea(&self) -> Result<HheaTable, FontError> {
268 let entry = self
269 .find_table(TableTag::HHEA)
270 .ok_or(FontError::TableNotFound)?;
271 let d = self.table_data(&entry)?;
272 if d.len() < 36 {
273 return Err(FontError::DataTruncated);
274 }
275
276 Ok(HheaTable {
277 ascent: read_i16_be(d, 4).ok_or(FontError::DataTruncated)?,
278 descent: read_i16_be(d, 6).ok_or(FontError::DataTruncated)?,
279 line_gap: read_i16_be(d, 8).ok_or(FontError::DataTruncated)?,
280 num_h_metrics: read_u16_be(d, 34).ok_or(FontError::DataTruncated)?,
281 })
282 }
283
284 pub fn parse_maxp(&self) -> Result<MaxpTable, FontError> {
286 let entry = self
287 .find_table(TableTag::MAXP)
288 .ok_or(FontError::TableNotFound)?;
289 let d = self.table_data(&entry)?;
290 if d.len() < 6 {
291 return Err(FontError::DataTruncated);
292 }
293
294 Ok(MaxpTable {
295 num_glyphs: read_u16_be(d, 4).ok_or(FontError::DataTruncated)?,
296 })
297 }
298
299 pub fn char_to_glyph(&self, ch: u32) -> Result<u16, FontError> {
302 let entry = self
303 .find_table(TableTag::CMAP)
304 .ok_or(FontError::TableNotFound)?;
305 let d = self.table_data(&entry)?;
306 if d.len() < 4 {
307 return Err(FontError::DataTruncated);
308 }
309
310 let num_subtables = read_u16_be(d, 2).ok_or(FontError::DataTruncated)?;
311
312 for i in 0..num_subtables as usize {
314 let rec_off = 4 + i * 8;
315 if rec_off + 8 > d.len() {
316 break;
317 }
318 let platform = read_u16_be(d, rec_off).ok_or(FontError::DataTruncated)?;
319 let sub_offset = read_u32_be(d, rec_off + 4).ok_or(FontError::DataTruncated)? as usize;
320
321 if platform != 0 && platform != 3 {
322 continue;
323 }
324
325 if sub_offset + 6 > d.len() {
326 continue;
327 }
328
329 let format = read_u16_be(d, sub_offset).ok_or(FontError::DataTruncated)?;
330
331 if format == 4 {
332 return self.cmap_format4_lookup(d, sub_offset, ch);
333 }
334 }
335
336 Err(FontError::GlyphNotFound)
337 }
338
339 fn cmap_format4_lookup(
341 &self,
342 cmap_data: &[u8],
343 offset: usize,
344 ch: u32,
345 ) -> Result<u16, FontError> {
346 if ch > 0xFFFF {
347 return Err(FontError::GlyphNotFound);
348 }
349 let ch = ch as u16;
350
351 let seg_count_x2 =
352 read_u16_be(cmap_data, offset + 6).ok_or(FontError::DataTruncated)? as usize;
353 let seg_count = seg_count_x2 / 2;
354
355 let end_codes_off = offset + 14;
356 let start_codes_off = end_codes_off + seg_count_x2 + 2;
358 let id_delta_off = start_codes_off + seg_count_x2;
359 let id_range_off = id_delta_off + seg_count_x2;
360
361 for seg in 0..seg_count {
362 let end_code =
363 read_u16_be(cmap_data, end_codes_off + seg * 2).ok_or(FontError::DataTruncated)?;
364
365 if ch > end_code {
366 continue;
367 }
368
369 let start_code = read_u16_be(cmap_data, start_codes_off + seg * 2)
370 .ok_or(FontError::DataTruncated)?;
371
372 if ch < start_code {
373 return Err(FontError::GlyphNotFound);
374 }
375
376 let id_delta =
377 read_i16_be(cmap_data, id_delta_off + seg * 2).ok_or(FontError::DataTruncated)?;
378 let id_range =
379 read_u16_be(cmap_data, id_range_off + seg * 2).ok_or(FontError::DataTruncated)?;
380
381 if id_range == 0 {
382 return Ok((ch as i16).wrapping_add(id_delta) as u16);
383 }
384
385 let glyph_offset =
386 id_range_off + seg * 2 + id_range as usize + (ch - start_code) as usize * 2;
387 let glyph_id = read_u16_be(cmap_data, glyph_offset).ok_or(FontError::DataTruncated)?;
388
389 if glyph_id == 0 {
390 return Err(FontError::GlyphNotFound);
391 }
392
393 return Ok((glyph_id as i16).wrapping_add(id_delta) as u16);
394 }
395
396 Err(FontError::GlyphNotFound)
397 }
398
399 pub fn glyph_offset(&self, glyph_id: u16, head: &HeadTable) -> Result<(u32, u32), FontError> {
401 let entry = self
402 .find_table(TableTag::LOCA)
403 .ok_or(FontError::TableNotFound)?;
404 let d = self.table_data(&entry)?;
405
406 if head.index_to_loc_format == 0 {
407 let idx = glyph_id as usize * 2;
409 let off1 = read_u16_be(d, idx).ok_or(FontError::DataTruncated)? as u32 * 2;
410 let off2 = read_u16_be(d, idx + 2).ok_or(FontError::DataTruncated)? as u32 * 2;
411 Ok((off1, off2))
412 } else {
413 let idx = glyph_id as usize * 4;
415 let off1 = read_u32_be(d, idx).ok_or(FontError::DataTruncated)?;
416 let off2 = read_u32_be(d, idx + 4).ok_or(FontError::DataTruncated)?;
417 Ok((off1, off2))
418 }
419 }
420
421 #[cfg(feature = "alloc")]
423 pub fn parse_glyph(&self, glyph_id: u16) -> Result<GlyphOutline, FontError> {
424 let head = self.parse_head()?;
425 let (off1, off2) = self.glyph_offset(glyph_id, &head)?;
426
427 if off1 == off2 {
428 return Ok(GlyphOutline {
430 contours: Vec::new(),
431 x_min: 0,
432 y_min: 0,
433 x_max: 0,
434 y_max: 0,
435 advance_width: 0,
436 lsb: 0,
437 });
438 }
439
440 let glyf_entry = self
441 .find_table(TableTag::GLYF)
442 .ok_or(FontError::TableNotFound)?;
443 let glyf_data = self.table_data(&glyf_entry)?;
444
445 let glyph_start = off1 as usize;
446 if glyph_start + 10 > glyf_data.len() {
447 return Err(FontError::DataTruncated);
448 }
449
450 let num_contours = read_i16_be(glyf_data, glyph_start).ok_or(FontError::DataTruncated)?;
451 let x_min = read_i16_be(glyf_data, glyph_start + 2).ok_or(FontError::DataTruncated)?;
452 let y_min = read_i16_be(glyf_data, glyph_start + 4).ok_or(FontError::DataTruncated)?;
453 let x_max = read_i16_be(glyf_data, glyph_start + 6).ok_or(FontError::DataTruncated)?;
454 let y_max = read_i16_be(glyf_data, glyph_start + 8).ok_or(FontError::DataTruncated)?;
455
456 if num_contours < 0 {
457 return Ok(GlyphOutline {
459 contours: Vec::new(),
460 x_min,
461 y_min,
462 x_max,
463 y_max,
464 advance_width: 0,
465 lsb: 0,
466 });
467 }
468
469 let num_contours = num_contours as usize;
470 let mut cursor = glyph_start + 10;
471
472 let mut end_pts = Vec::with_capacity(num_contours);
474 for _ in 0..num_contours {
475 let ep = read_u16_be(glyf_data, cursor).ok_or(FontError::DataTruncated)?;
476 end_pts.push(ep);
477 cursor += 2;
478 }
479
480 let num_points = if let Some(&last) = end_pts.last() {
481 last as usize + 1
482 } else {
483 return Ok(GlyphOutline {
484 contours: Vec::new(),
485 x_min,
486 y_min,
487 x_max,
488 y_max,
489 advance_width: 0,
490 lsb: 0,
491 });
492 };
493
494 let instruction_length =
496 read_u16_be(glyf_data, cursor).ok_or(FontError::DataTruncated)? as usize;
497 cursor += 2 + instruction_length;
498
499 let mut flags = Vec::with_capacity(num_points);
501 while flags.len() < num_points {
502 if cursor >= glyf_data.len() {
503 return Err(FontError::DataTruncated);
504 }
505 let flag = glyf_data[cursor];
506 cursor += 1;
507 flags.push(flag);
508
509 if flag & 0x08 != 0 {
511 if cursor >= glyf_data.len() {
512 return Err(FontError::DataTruncated);
513 }
514 let repeat = glyf_data[cursor] as usize;
515 cursor += 1;
516 for _ in 0..repeat {
517 if flags.len() < num_points {
518 flags.push(flag);
519 }
520 }
521 }
522 }
523
524 let mut x_coords = Vec::with_capacity(num_points);
526 let mut x: i16 = 0;
527 for flag in &flags {
528 let short = flag & 0x02 != 0;
529 let same_or_positive = flag & 0x10 != 0;
530
531 if short {
532 if cursor >= glyf_data.len() {
533 return Err(FontError::DataTruncated);
534 }
535 let dx = glyf_data[cursor] as i16;
536 cursor += 1;
537 x += if same_or_positive { dx } else { -dx };
538 } else if !same_or_positive {
539 let dx = read_i16_be(glyf_data, cursor).ok_or(FontError::DataTruncated)?;
540 cursor += 2;
541 x += dx;
542 }
543 x_coords.push(x);
545 }
546
547 let mut y_coords = Vec::with_capacity(num_points);
549 let mut y: i16 = 0;
550 for flag in &flags {
551 let short = flag & 0x04 != 0;
552 let same_or_positive = flag & 0x20 != 0;
553
554 if short {
555 if cursor >= glyf_data.len() {
556 return Err(FontError::DataTruncated);
557 }
558 let dy = glyf_data[cursor] as i16;
559 cursor += 1;
560 y += if same_or_positive { dy } else { -dy };
561 } else if !same_or_positive {
562 let dy = read_i16_be(glyf_data, cursor).ok_or(FontError::DataTruncated)?;
563 cursor += 2;
564 y += dy;
565 }
566 y_coords.push(y);
567 }
568
569 let mut contours = Vec::with_capacity(num_contours);
571 let mut start = 0usize;
572 for &end in &end_pts {
573 let end = end as usize;
574 let mut points = Vec::new();
575 for idx in start..=end {
576 if idx < num_points {
577 points.push(OutlinePoint {
578 x: x_coords[idx],
579 y: y_coords[idx],
580 on_curve: flags[idx] & 0x01 != 0,
581 });
582 }
583 }
584 contours.push(GlyphContour { points });
585 start = end + 1;
586 }
587
588 Ok(GlyphOutline {
589 contours,
590 x_min,
591 y_min,
592 x_max,
593 y_max,
594 advance_width: 0,
595 lsb: 0,
596 })
597 }
598}
599
600#[derive(Debug, Clone, PartialEq, Eq)]
602#[cfg(feature = "alloc")]
603pub struct GlyphBitmap {
604 pub data: Vec<u8>,
606 pub width: u32,
608 pub height: u32,
610 pub bearing_x: i32,
612 pub bearing_y: i32,
614 pub advance: u32,
616}
617
618#[derive(Debug, Clone)]
620#[cfg(feature = "alloc")]
621struct GlyphCacheEntry {
622 ch: u32,
624 size_px: u16,
626 bitmap: GlyphBitmap,
628 access_count: u32,
630}
631
632pub(crate) const GLYPH_CACHE_SIZE: usize = 256;
634
635#[derive(Debug)]
637#[cfg(feature = "alloc")]
638pub struct GlyphCache {
639 entries: Vec<GlyphCacheEntry>,
640 total_lookups: u64,
641 cache_hits: u64,
642}
643
644#[cfg(feature = "alloc")]
645impl Default for GlyphCache {
646 fn default() -> Self {
647 Self::new()
648 }
649}
650
651#[cfg(feature = "alloc")]
652impl GlyphCache {
653 pub fn new() -> Self {
655 Self {
656 entries: Vec::new(),
657 total_lookups: 0,
658 cache_hits: 0,
659 }
660 }
661
662 pub fn get(&mut self, ch: u32, size_px: u16) -> Option<&GlyphBitmap> {
664 self.total_lookups += 1;
665
666 let idx = self
667 .entries
668 .iter()
669 .position(|e| e.ch == ch && e.size_px == size_px);
670
671 if let Some(i) = idx {
672 self.cache_hits += 1;
673 self.entries[i].access_count += 1;
674 Some(&self.entries[i].bitmap)
675 } else {
676 None
677 }
678 }
679
680 pub fn insert(&mut self, ch: u32, size_px: u16, bitmap: GlyphBitmap) {
682 if self.entries.len() >= GLYPH_CACHE_SIZE {
684 let min_idx = self
686 .entries
687 .iter()
688 .enumerate()
689 .min_by_key(|(_, e)| e.access_count)
690 .map(|(i, _)| i)
691 .unwrap_or(0);
692 self.entries.swap_remove(min_idx);
693 }
694
695 self.entries.push(GlyphCacheEntry {
696 ch,
697 size_px,
698 bitmap,
699 access_count: 1,
700 });
701 }
702
703 pub fn hit_rate_percent(&self) -> u32 {
705 if self.total_lookups == 0 {
706 return 0;
707 }
708 ((self.cache_hits * 100) / self.total_lookups) as u32
709 }
710
711 pub fn clear(&mut self) {
713 self.entries.clear();
714 self.total_lookups = 0;
715 self.cache_hits = 0;
716 }
717
718 pub fn len(&self) -> usize {
720 self.entries.len()
721 }
722
723 pub fn is_empty(&self) -> bool {
725 self.entries.is_empty()
726 }
727}
728
729#[cfg(feature = "alloc")]
735pub fn rasterize_outline(outline: &GlyphOutline, scale_num: u32, scale_den: u32) -> GlyphBitmap {
736 if outline.contours.is_empty() || scale_den == 0 {
737 return GlyphBitmap {
738 data: Vec::new(),
739 width: 0,
740 height: 0,
741 bearing_x: 0,
742 bearing_y: 0,
743 advance: 0,
744 };
745 }
746
747 let scale = |v: i16| -> i32 { (v as i32 * scale_num as i32) / scale_den as i32 };
749
750 let x_min = scale(outline.x_min);
751 let y_min = scale(outline.y_min);
752 let x_max = scale(outline.x_max);
753 let y_max = scale(outline.y_max);
754
755 let width = (x_max - x_min + 1).max(1) as u32;
756 let height = (y_max - y_min + 1).max(1) as u32;
757
758 let width = width.min(512);
760 let height = height.min(512);
761
762 let mut data = vec![0u8; (width * height) as usize];
763
764 for contour in &outline.contours {
766 let points = &contour.points;
767 if points.len() < 2 {
768 continue;
769 }
770
771 let num = points.len();
772 for i in 0..num {
773 let p0 = &points[i];
774 let p1 = &points[(i + 1) % num];
775
776 let x0 = scale(p0.x) - x_min;
777 let y0 = y_max - scale(p0.y);
778 let x1 = scale(p1.x) - x_min;
779 let y1 = y_max - scale(p1.y);
780
781 if p0.on_curve && p1.on_curve {
782 draw_line(&mut data, width, height, x0, y0, x1, y1);
784 } else if !p1.on_curve && (i + 2) <= num {
785 let p2 = &points[(i + 2) % num];
787 let x2 = scale(p2.x) - x_min;
788 let y2 = y_max - scale(p2.y);
789 draw_quadratic_bezier(&mut data, width, height, x0, y0, x1, y1, x2, y2);
790 }
791 }
792 }
793
794 GlyphBitmap {
795 data,
796 width,
797 height,
798 bearing_x: x_min,
799 bearing_y: y_max,
800 advance: width,
801 }
802}
803
804#[cfg(feature = "alloc")]
806fn draw_line(buf: &mut [u8], w: u32, h: u32, x0: i32, y0: i32, x1: i32, y1: i32) {
807 let mut x0 = x0;
808 let mut y0 = y0;
809
810 let dx = (x1 - x0).abs();
811 let dy = -(y1 - y0).abs();
812 let sx: i32 = if x0 < x1 { 1 } else { -1 };
813 let sy: i32 = if y0 < y1 { 1 } else { -1 };
814 let mut err = dx + dy;
815
816 loop {
817 if x0 >= 0 && (x0 as u32) < w && y0 >= 0 && (y0 as u32) < h {
819 let idx = y0 as u32 * w + x0 as u32;
820 if (idx as usize) < buf.len() {
821 buf[idx as usize] = 255;
822 }
823 }
824
825 if x0 == x1 && y0 == y1 {
826 break;
827 }
828
829 let e2 = 2 * err;
830 if e2 >= dy {
831 err += dy;
832 x0 += sx;
833 }
834 if e2 <= dx {
835 err += dx;
836 y0 += sy;
837 }
838 }
839}
840
841#[cfg(feature = "alloc")]
843#[allow(clippy::too_many_arguments)]
844fn draw_quadratic_bezier(
845 buf: &mut [u8],
846 w: u32,
847 h: u32,
848 x0: i32,
849 y0: i32,
850 cx: i32,
851 cy: i32,
852 x2: i32,
853 y2: i32,
854) {
855 let mx = (x0 + x2) / 2;
858 let my = (y0 + y2) / 2;
859 let dist = (cx - mx).abs() + (cy - my).abs();
860
861 if dist <= 1 {
862 draw_line(buf, w, h, x0, y0, x2, y2);
863 return;
864 }
865
866 let ax = (x0 + cx) / 2;
868 let ay = (y0 + cy) / 2;
869 let bx = (cx + x2) / 2;
870 let by = (cy + y2) / 2;
871 let midx = (ax + bx) / 2;
872 let midy = (ay + by) / 2;
873
874 draw_quadratic_bezier(buf, w, h, x0, y0, ax, ay, midx, midy);
875 draw_quadratic_bezier(buf, w, h, midx, midy, bx, by, x2, y2);
876}
877
878#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
880pub enum HintingMode {
881 #[default]
883 None,
884 Light,
886 Full,
888 Auto,
890}
891
892#[cfg(feature = "alloc")]
894pub fn apply_hinting(outline: &GlyphOutline, _mode: HintingMode) -> GlyphOutline {
895 outline.clone()
898}
899
900#[cfg(feature = "alloc")]
907pub fn render_glyph(
908 parser: &TtfParser<'_>,
909 ch: char,
910 pixel_size: u16,
911) -> Result<GlyphBitmap, FontError> {
912 let head = parser.parse_head()?;
913 let glyph_id = parser.char_to_glyph(ch as u32)?;
914 let outline = parser.parse_glyph(glyph_id)?;
915 let bitmap = rasterize_outline(&outline, pixel_size as u32, head.units_per_em as u32);
916 Ok(bitmap)
917}