1use std::{
2 borrow::Borrow,
3 cell::RefCell,
4 collections::HashMap,
5 ffi::OsStr,
6 fs,
7 hash::{Hash, Hasher},
8 ops::Range,
9 path::Path as FilePath,
10 rc::Rc,
11};
12
13use fnv::{FnvBuildHasher, FnvHashMap, FnvHasher};
14use lru::LruCache;
15use rustybuzz::ttf_parser;
16use slotmap::{DefaultKey, SlotMap};
17
18use unicode_bidi::BidiInfo;
19use unicode_segmentation::UnicodeSegmentation;
20
21use crate::{
22 paint::{PaintFlavor, StrokeSettings, TextSettings},
23 Canvas, Color, ErrorKind, FillRule, ImageFlags, ImageId, ImageInfo, Paint, PixelFormat, RenderTarget, Renderer,
24};
25
26mod atlas;
27pub use atlas::Atlas;
28
29mod font;
30pub use font::FontMetrics;
31use font::{Font, GlyphRendering};
32
33// This padding is an empty border around the glyph’s pixels but inside the
34// sampled area (texture coordinates) for the quad in render_atlas().
35const GLYPH_PADDING: u32 = 1;
36// We add an additional margin of 1 pixel outside of the sampled area,
37// to deal with the linear interpolation of texels at the edge of that area
38// which mixes in the texels just outside of the edge.
39// This manifests as noise around the glyph, outside of the padding.
40const GLYPH_MARGIN: u32 = 1;
41
42const TEXTURE_SIZE: usize = 512;
43const DEFAULT_LRU_CACHE_CAPACITY: usize = 1000;
44
45/// A font handle.
46#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
47pub struct FontId(DefaultKey);
48
49/// Text baseline vertical alignment:
50/// `Top`, `Middle`, `Alphabetic` (default), `Bottom`.
51#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
52#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
53pub enum Baseline {
54 /// The text baseline is the top of the em square.
55 Top,
56 /// The text baseline is the middle of the em square.
57 Middle,
58 /// The text baseline is the normal alphabetic baseline. Default value.
59 Alphabetic,
60 // The text baseline is the bottom of the bounding box.
61 Bottom,
62}
63
64impl Default for Baseline {
65 fn default() -> Self {
66 Self::Alphabetic
67 }
68}
69
70/// Text horizontal alignment: `Left` (default), `Center`, `Right`.
71#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
72#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
73pub enum Align {
74 /// The text is left-aligned.
75 Left,
76 /// The text is centered.
77 Center,
78 /// The text is right-aligned.
79 Right,
80}
81
82impl Default for Align {
83 fn default() -> Self {
84 Self::Left
85 }
86}
87
88#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
89pub enum RenderMode {
90 Fill,
91 Stroke,
92}
93
94impl Default for RenderMode {
95 fn default() -> Self {
96 Self::Fill
97 }
98}
99
100#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
101pub(crate) struct RenderedGlyphId {
102 glyph_index: u32,
103 font_id: FontId,
104 size: u32,
105 line_width: u32,
106 render_mode: RenderMode,
107 subpixel_location: u8,
108}
109
110impl RenderedGlyphId {
111 fn new(
112 glyph_index: u32,
113 font_id: FontId,
114 font_size: f32,
115 line_width: f32,
116 mode: RenderMode,
117 subpixel_location: u8,
118 ) -> Self {
119 Self {
120 glyph_index,
121 font_id,
122 size: (font_size * 10.0).trunc() as u32,
123 line_width: (line_width * 10.0).trunc() as u32,
124 render_mode: mode,
125 subpixel_location,
126 }
127 }
128}
129
130#[derive(Copy, Clone, Debug)]
131pub(crate) struct RenderedGlyph {
132 texture_index: usize,
133 width: u32,
134 height: u32,
135 bearing_y: i32,
136 atlas_x: u32,
137 atlas_y: u32,
138 color_glyph: bool,
139}
140
141#[derive(Copy, Clone, Debug)]
142pub struct ShapedGlyph {
143 pub x: f32,
144 pub y: f32,
145 pub c: char,
146 pub byte_index: usize,
147 pub font_id: FontId,
148 pub codepoint: u32,
149 pub width: f32,
150 pub height: f32,
151 pub advance_x: f32,
152 pub advance_y: f32,
153 pub offset_x: f32,
154 pub offset_y: f32,
155 pub bearing_x: f32,
156 pub bearing_y: f32,
157 pub bitmap_glyph: bool,
158}
159
160#[derive(Clone, Debug, Default)]
161struct ShapedWord {
162 glyphs: Vec<ShapedGlyph>,
163 width: f32,
164}
165
166#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
167struct ShapingId {
168 size: u32,
169 word_hash: u64,
170 font_ids: [Option<FontId>; 8],
171}
172
173impl ShapingId {
174 fn new(font_size: f32, font_ids: [Option<FontId>; 8], word: &str, max_width: Option<f32>) -> Self {
175 let mut hasher: FnvHasher = FnvHasher::default();
176 word.hash(&mut hasher);
177 if let Some(max_width: f32) = max_width {
178 (max_width.trunc() as i32).hash(&mut hasher);
179 }
180
181 Self {
182 size: (font_size * 10.0).trunc() as u32,
183 word_hash: hasher.finish(),
184 font_ids,
185 }
186 }
187}
188
189type ShapedWordsCache<H> = LruCache<ShapingId, Result<ShapedWord, ErrorKind>, H>;
190type ShapingRunCache<H> = LruCache<ShapingId, TextMetrics, H>;
191
192pub(crate) struct FontTexture {
193 pub atlas: Atlas,
194 pub(crate) image_id: ImageId,
195}
196
197/// TextContext provides functionality for text processing in femtovg. You can
198/// add fonts using the [`Self::add_font_file()`], [`Self::add_font_mem()`] and
199/// [`Self::add_font_dir()`] functions. For each registered font a [`FontId`] is
200/// returned.
201///
202/// The [`FontId`] can be supplied to [`crate::Paint`] along with additional parameters
203/// such as the font size.
204///
205/// The paint is needed when using TextContext's measurement functions such as
206/// [`Self::measure_text()`].
207///
208/// Note that the measurements are done entirely with the supplied sizes in the paint
209/// parameter. If you need measurements that take a [`crate::Canvas`]'s transform or dpi into
210/// account (see [`crate::Canvas::set_size()`]), you need to use the measurement functions
211/// on the canvas.
212#[derive(Clone, Default)]
213pub struct TextContext(pub(crate) Rc<RefCell<TextContextImpl>>);
214
215impl TextContext {
216 /// Registers all .ttf files from a directory with this text context. If successful, the
217 /// font ids of all registered fonts are returned.
218 pub fn add_font_dir<T: AsRef<FilePath>>(&self, path: T) -> Result<Vec<FontId>, ErrorKind> {
219 self.0.borrow_mut().add_font_dir(path)
220 }
221
222 /// Registers the .ttf file from the specified path with this text context. If successful,
223 /// the font id is returned.
224 pub fn add_font_file<T: AsRef<FilePath>>(&self, path: T) -> Result<FontId, ErrorKind> {
225 self.0.borrow_mut().add_font_file(path)
226 }
227
228 /// Registers the in-memory representation of a TrueType font pointed to by the data
229 /// parameter with this text context. If successful, the font id is returned.
230 pub fn add_font_mem(&self, data: &[u8]) -> Result<FontId, ErrorKind> {
231 self.0.borrow_mut().add_font_mem(data)
232 }
233
234 /// Registers the in-memory representation of a TrueType font pointed to by the shared data
235 /// parameter with this text context. If successful, the font id is returned. The face_index
236 /// specifies the face index if the font data is a true type font collection. For plain true
237 /// type fonts, use 0 as index.
238 pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
239 &self,
240 data: T,
241 face_index: u32,
242 ) -> Result<FontId, ErrorKind> {
243 self.0.borrow_mut().add_shared_font_with_index(data, face_index)
244 }
245
246 /// Returns information on how the provided text will be drawn with the specified paint.
247 pub fn measure_text<S: AsRef<str>>(
248 &self,
249 x: f32,
250 y: f32,
251 text: S,
252 paint: &Paint,
253 ) -> Result<TextMetrics, ErrorKind> {
254 self.0.borrow_mut().measure_text(x, y, text, &paint.text)
255 }
256
257 /// Returns the maximum index-th byte of text that will fit inside max_width.
258 ///
259 /// The retuned index will always lie at the start and/or end of a UTF-8 code point sequence or at the start or end of the text
260 pub fn break_text<S: AsRef<str>>(&self, max_width: f32, text: S, paint: &Paint) -> Result<usize, ErrorKind> {
261 self.0.borrow_mut().break_text(max_width, text, &paint.text)
262 }
263
264 /// Returnes a list of ranges representing each line of text that will fit inside max_width
265 pub fn break_text_vec<S: AsRef<str>>(
266 &self,
267 max_width: f32,
268 text: S,
269 paint: &Paint,
270 ) -> Result<Vec<Range<usize>>, ErrorKind> {
271 self.0.borrow_mut().break_text_vec(max_width, text, &paint.text)
272 }
273
274 /// Returns font metrics for a particular Paint.
275 pub fn measure_font(&self, paint: &Paint) -> Result<FontMetrics, ErrorKind> {
276 self.0
277 .borrow_mut()
278 .measure_font(paint.text.font_size, &paint.text.font_ids)
279 }
280
281 /// Adjusts the capacity of the shaping run cache. This is a cache for measurements of whole
282 /// strings.
283 pub fn resize_shaping_run_cache(&self, capacity: std::num::NonZeroUsize) {
284 self.0.borrow_mut().resize_shaping_run_cache(capacity)
285 }
286
287 /// Adjusts the capacity of the shaped words cache. This is a cache for measurements of
288 /// individual words. Words are separated by
289 /// [UAX#29 word boundaries](http://www.unicode.org/reports/tr29/#Word_Boundaries).
290 pub fn resize_shaped_words_cache(&self, capacity: std::num::NonZeroUsize) {
291 self.0.borrow_mut().resize_shaped_words_cache(capacity)
292 }
293}
294
295pub(crate) struct TextContextImpl {
296 fonts: SlotMap<DefaultKey, Font>,
297 shaping_run_cache: ShapingRunCache<FnvBuildHasher>,
298 shaped_words_cache: ShapedWordsCache<FnvBuildHasher>,
299}
300
301impl Default for TextContextImpl {
302 fn default() -> Self {
303 let fnv_run: BuildHasherDefault = FnvBuildHasher::default();
304 let fnv_words: BuildHasherDefault = FnvBuildHasher::default();
305
306 Self {
307 fonts: Default::default(),
308 shaping_run_cache: LruCache::with_hasher(
309 cap:std::num::NonZeroUsize::new(DEFAULT_LRU_CACHE_CAPACITY).unwrap(),
310 hash_builder:fnv_run,
311 ),
312 shaped_words_cache: LruCache::with_hasher(
313 cap:std::num::NonZeroUsize::new(DEFAULT_LRU_CACHE_CAPACITY).unwrap(),
314 hash_builder:fnv_words,
315 ),
316 }
317 }
318}
319
320impl TextContextImpl {
321 pub fn resize_shaping_run_cache(&mut self, capacity: std::num::NonZeroUsize) {
322 self.shaping_run_cache.resize(capacity);
323 }
324
325 pub fn resize_shaped_words_cache(&mut self, capacity: std::num::NonZeroUsize) {
326 self.shaped_words_cache.resize(capacity);
327 }
328
329 pub fn add_font_dir<T: AsRef<FilePath>>(&mut self, path: T) -> Result<Vec<FontId>, ErrorKind> {
330 let path = path.as_ref();
331 let mut fonts = Vec::new();
332
333 if path.is_dir() {
334 for entry in fs::read_dir(path)? {
335 let entry = entry?;
336 let path = entry.path();
337
338 if path.is_dir() {
339 self.add_font_dir(&path)?;
340 } else if let Some("ttf") = path.extension().and_then(OsStr::to_str) {
341 fonts.push(self.add_font_file(path)?);
342 } else if let Some("ttc") = path.extension().and_then(OsStr::to_str) {
343 fonts.extend(self.add_font_file_collection(path)?);
344 }
345 }
346 }
347
348 Ok(fonts)
349 }
350
351 pub fn add_font_file<T: AsRef<FilePath>>(&mut self, path: T) -> Result<FontId, ErrorKind> {
352 let data = std::fs::read(path)?;
353
354 self.add_font_mem(&data)
355 }
356
357 pub fn add_font_file_collection<T: AsRef<FilePath>>(
358 &mut self,
359 path: T,
360 ) -> Result<impl Iterator<Item = FontId> + '_, ErrorKind> {
361 let data = std::fs::read(path)?;
362
363 let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
364 Ok((0..count).filter_map(move |index| self.add_font_mem_with_index(&data, index).ok()))
365 }
366
367 pub fn add_font_mem(&mut self, data: &[u8]) -> Result<FontId, ErrorKind> {
368 self.add_font_mem_with_index(data, 0)
369 }
370
371 pub fn add_font_mem_with_index(&mut self, data: &[u8], face_index: u32) -> Result<FontId, ErrorKind> {
372 self.clear_caches();
373
374 let data_copy = data.to_owned();
375 let font = Font::new_with_data(data_copy, face_index)?;
376 Ok(FontId(self.fonts.insert(font)))
377 }
378
379 pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
380 &mut self,
381 data: T,
382 face_index: u32,
383 ) -> Result<FontId, ErrorKind> {
384 self.clear_caches();
385
386 let font = Font::new_with_data(data, face_index)?;
387 Ok(FontId(self.fonts.insert(font)))
388 }
389
390 pub fn font(&self, id: FontId) -> Option<&Font> {
391 self.fonts.get(id.0)
392 }
393
394 pub fn font_mut(&mut self, id: FontId) -> Option<&mut Font> {
395 self.fonts.get_mut(id.0)
396 }
397
398 pub fn find_font<F, T>(&mut self, font_ids: &[Option<FontId>; 8], mut callback: F) -> Result<T, ErrorKind>
399 where
400 F: FnMut((FontId, &mut Font)) -> (bool, T),
401 {
402 // Try each font in the paint
403 for maybe_font_id in font_ids {
404 if let &Some(font_id) = maybe_font_id {
405 if let Some(font) = self.fonts.get_mut(font_id.0) {
406 let (has_missing, result) = callback((font_id, font));
407
408 if !has_missing {
409 return Ok(result);
410 }
411 }
412 } else {
413 break;
414 }
415 }
416
417 // Try each registered font
418 // An optimisation here would be to skip fonts that were tried by the paint
419 for (id, font) in &mut self.fonts {
420 let (has_missing, result) = callback((FontId(id), font));
421
422 if !has_missing {
423 return Ok(result);
424 }
425 }
426
427 // Just return the first font at this point and let it render .nodef glyphs
428 if let Some((id, font)) = self.fonts.iter_mut().next() {
429 return Ok(callback((FontId(id), font)).1);
430 }
431
432 Err(ErrorKind::NoFontFound)
433 }
434
435 fn clear_caches(&mut self) {
436 self.shaped_words_cache.clear();
437 }
438
439 pub fn measure_text<S: AsRef<str>>(
440 &mut self,
441 x: f32,
442 y: f32,
443 text: S,
444 text_settings: &TextSettings,
445 ) -> Result<TextMetrics, ErrorKind> {
446 shape(x, y, self, text_settings, text.as_ref(), None)
447 }
448
449 pub fn break_text<S: AsRef<str>>(
450 &mut self,
451 max_width: f32,
452 text: S,
453 text_settings: &TextSettings,
454 ) -> Result<usize, ErrorKind> {
455 let layout = shape(0.0, 0.0, self, text_settings, text.as_ref(), Some(max_width))?;
456
457 Ok(layout.final_byte_index)
458 }
459
460 pub fn break_text_vec<S: AsRef<str>>(
461 &mut self,
462 max_width: f32,
463 text: S,
464 text_settings: &TextSettings,
465 ) -> Result<Vec<Range<usize>>, ErrorKind> {
466 let text = text.as_ref();
467
468 let mut res = Vec::new();
469 let mut start = 0;
470
471 while start < text.len() {
472 if let Ok(index) = self.break_text(max_width, &text[start..], text_settings) {
473 if index == 0 {
474 break;
475 }
476
477 let index = start + index;
478 res.push(start..index);
479 start += &text[start..index].len();
480 } else {
481 break;
482 }
483 }
484
485 Ok(res)
486 }
487
488 pub fn measure_font(&mut self, font_size: f32, font_ids: &[Option<FontId>; 8]) -> Result<FontMetrics, ErrorKind> {
489 if let Some(Some(id)) = font_ids.first() {
490 if let Some(font) = self.font(*id) {
491 return Ok(font.metrics(font_size));
492 }
493 }
494
495 Err(ErrorKind::NoFontFound)
496 }
497}
498
499/// Result of a shaping run.
500#[derive(Clone, Default, Debug)]
501pub struct TextMetrics {
502 pub x: f32,
503 pub y: f32,
504 width: f32,
505 height: f32,
506 pub glyphs: Vec<ShapedGlyph>,
507 pub(crate) final_byte_index: usize,
508}
509
510impl TextMetrics {
511 pub(crate) fn scale(&mut self, scale: f32) {
512 self.x *= scale;
513 self.y *= scale;
514 self.width *= scale;
515 self.height *= scale;
516
517 for glyph in &mut self.glyphs {
518 glyph.x *= scale;
519 glyph.y *= scale;
520 glyph.width *= scale;
521 glyph.height *= scale;
522 }
523 }
524
525 /// width of the glyphs as drawn
526 pub fn width(&self) -> f32 {
527 self.width
528 }
529
530 /// height of the glyphs as drawn
531 pub fn height(&self) -> f32 {
532 self.height
533 }
534
535 pub(crate) fn has_bitmap_glyphs(&self) -> bool {
536 self.glyphs.iter().any(|g| g.bitmap_glyph)
537 }
538}
539
540// Shaper
541
542pub(crate) fn shape(
543 x: f32,
544 y: f32,
545 context: &mut TextContextImpl,
546 text_settings: &TextSettings,
547 text: &str,
548 max_width: Option<f32>,
549) -> Result<TextMetrics, ErrorKind> {
550 let id: ShapingId = ShapingId::new(text_settings.font_size, text_settings.font_ids, word:text, max_width);
551
552 if !context.shaping_run_cache.contains(&id) {
553 let metrics: TextMetrics = shape_run(
554 context,
555 text_settings.font_size,
556 text_settings.font_ids,
557 text_settings.letter_spacing,
558 text,
559 max_width,
560 )?;
561 context.shaping_run_cache.put(k:id, v:metrics);
562 }
563
564 if let Some(mut metrics: TextMetrics) = context.shaping_run_cache.get(&id).cloned() {
565 layout(x, y, context, &mut metrics, text_settings)?;
566
567 return Ok(metrics);
568 }
569
570 Err(ErrorKind::UnknownError)
571}
572
573fn shape_run(
574 context: &mut TextContextImpl,
575 font_size: f32,
576 font_ids: [Option<FontId>; 8],
577 letter_spacing: f32,
578 text: &str,
579 max_width: Option<f32>,
580) -> Result<TextMetrics, ErrorKind> {
581 let mut result = TextMetrics {
582 x: 0.0,
583 y: 0.0,
584 width: 0.0,
585 height: 0.0,
586 glyphs: Vec::with_capacity(text.len()),
587 final_byte_index: 0,
588 };
589
590 let bidi_info = BidiInfo::new(text, Some(unicode_bidi::Level::ltr()));
591
592 // this controls whether we should break within words
593 let mut first_word_in_paragraph = true;
594
595 if let Some(paragraph) = bidi_info.paragraphs.first() {
596 let line = paragraph.range.clone();
597
598 let (levels, runs) = bidi_info.visual_runs(paragraph, line);
599
600 for run in runs.iter() {
601 let sub_text = &text[run.clone()];
602
603 if sub_text.is_empty() {
604 continue;
605 }
606
607 let hb_direction = if levels[run.start].is_rtl() {
608 rustybuzz::Direction::RightToLeft
609 } else {
610 rustybuzz::Direction::LeftToRight
611 };
612
613 let mut words = Vec::new();
614 let mut word_break_reached = false;
615 let mut byte_index = run.start;
616
617 for mut word_txt in sub_text.split_word_bounds() {
618 let id = ShapingId::new(font_size, font_ids, word_txt, max_width);
619
620 if !context.shaped_words_cache.contains(&id) {
621 let word = shape_word(word_txt, hb_direction, context, font_size, &font_ids, letter_spacing);
622 context.shaped_words_cache.put(id, word);
623 }
624
625 if let Some(Ok(word)) = context.shaped_words_cache.get(&id) {
626 let mut word = word.clone();
627
628 if let Some(max_width) = max_width {
629 if result.width + word.width >= max_width {
630 word_break_reached = true;
631 if first_word_in_paragraph {
632 // search for the largest prefix of the word that can fit
633 let mut bytes_included = 0;
634 let mut subword_width = 0.0;
635 let target_width = max_width - result.width;
636 for glyph in word.glyphs.iter() {
637 bytes_included = glyph.byte_index;
638 let glyph_width = glyph.advance_x + letter_spacing;
639
640 // nuance: we want to include the first glyph even if it breaks
641 // the bounds. this is to allow pathologically small bounds to
642 // at least complete rendering
643 if subword_width + glyph_width >= target_width && bytes_included != 0 {
644 break;
645 } else {
646 subword_width += glyph_width;
647 }
648 }
649
650 if bytes_included == 0 {
651 // just in case - never mind!
652 break;
653 }
654
655 let subword_txt = &word_txt[..bytes_included];
656 let id = ShapingId::new(font_size, font_ids, subword_txt, Some(max_width));
657 if !context.shaped_words_cache.contains(&id) {
658 let subword = shape_word(
659 subword_txt,
660 hb_direction,
661 context,
662 font_size,
663 &font_ids,
664 letter_spacing,
665 );
666 context.shaped_words_cache.put(id, subword);
667 }
668
669 if let Some(Ok(subword)) = context.shaped_words_cache.get(&id) {
670 // replace the outer variables so we can continue normally
671 word = subword.clone();
672 word_txt = subword_txt;
673 } else {
674 break;
675 }
676 } else if word.glyphs.iter().all(|g| g.c.is_whitespace()) {
677 // the last word we've broken in the middle of is whitespace.
678 // include this word for now, but we will discard its metrics in a moment.
679 } else {
680 // we are not breaking up words - discard this word
681 break;
682 }
683 }
684 }
685
686 // if we have broken in the middle of whitespace, do not include this word in metrics
687 if !word_break_reached || !word.glyphs.iter().all(|g| g.c.is_whitespace()) {
688 result.width += word.width;
689 }
690
691 for glyph in &mut word.glyphs {
692 glyph.byte_index += byte_index;
693 debug_assert!(text.get(glyph.byte_index..).is_some());
694 }
695 words.push(word);
696 first_word_in_paragraph = false;
697 }
698
699 byte_index += word_txt.len();
700
701 if word_break_reached {
702 break;
703 }
704 }
705
706 if levels[run.start].is_rtl() {
707 words.reverse();
708 }
709
710 for word in words {
711 result.glyphs.extend(word.glyphs.clone());
712 }
713
714 result.final_byte_index = byte_index;
715
716 if word_break_reached {
717 break;
718 }
719 }
720 }
721
722 Ok(result)
723}
724
725fn shape_word(
726 word: &str,
727 hb_direction: rustybuzz::Direction,
728 context: &mut TextContextImpl,
729 font_size: f32,
730 font_ids: &[Option<FontId>; 8],
731 letter_spacing: f32,
732) -> Result<ShapedWord, ErrorKind> {
733 // find_font will call the closure with each font matching the provided style
734 // until a font capable of shaping the word is found
735 context.find_font(font_ids, |(font_id, font)| {
736 let face = font.face_ref();
737 // Call harfbuzz
738 let output = {
739 let mut buffer = rustybuzz::UnicodeBuffer::new();
740 buffer.push_str(word);
741 buffer.set_direction(hb_direction);
742
743 rustybuzz::shape(&face, &[], buffer)
744 };
745
746 let positions = output.glyph_positions();
747 let infos = output.glyph_infos();
748
749 let mut shaped_word = ShapedWord {
750 glyphs: Vec::with_capacity(positions.len()),
751 width: 0.0,
752 };
753
754 let mut has_missing = false;
755
756 for (position, (info, c)) in positions.iter().zip(infos.iter().zip(word.chars())) {
757 if info.glyph_id == 0 {
758 has_missing = true;
759 }
760
761 let scale = font.scale(font_size);
762
763 let mut g = ShapedGlyph {
764 x: 0.0,
765 y: 0.0,
766 c,
767 byte_index: info.cluster as usize,
768 font_id,
769 codepoint: info.glyph_id,
770 width: 0.0,
771 height: 0.0,
772 advance_x: position.x_advance as f32 * scale,
773 advance_y: position.y_advance as f32 * scale,
774 offset_x: position.x_offset as f32 * scale,
775 offset_y: position.y_offset as f32 * scale,
776 bearing_x: 0.0,
777 bearing_y: 0.0,
778 bitmap_glyph: false,
779 };
780
781 if let Some(glyph) = font.glyph(&face, info.glyph_id as u16) {
782 g.width = glyph.metrics.width * scale;
783 g.height = glyph.metrics.height * scale;
784 g.bearing_x = glyph.metrics.bearing_x * scale;
785 g.bearing_y = glyph.metrics.bearing_y * scale;
786 g.bitmap_glyph = glyph.path.is_none();
787 }
788
789 shaped_word.width += g.advance_x + letter_spacing;
790 shaped_word.glyphs.push(g);
791 }
792
793 (has_missing, shaped_word)
794 })
795}
796
797// Calculates the x,y coordinates for each glyph based on their advances. Calculates total width and height of the shaped text run
798fn layout(
799 x: f32,
800 y: f32,
801 context: &mut TextContextImpl,
802 res: &mut TextMetrics,
803 text_settings: &TextSettings,
804) -> Result<(), ErrorKind> {
805 let mut cursor_x = x;
806 let mut cursor_y = y;
807
808 // Horizontal alignment
809 match text_settings.text_align {
810 Align::Center => cursor_x -= res.width / 2.0,
811 Align::Right => cursor_x -= res.width,
812 _ => (),
813 }
814
815 res.x = cursor_x;
816
817 let mut min_y = cursor_y;
818 let mut max_y = cursor_y;
819
820 let mut ascender: f32 = 0.;
821 let mut descender: f32 = 0.;
822
823 for glyph in &mut res.glyphs {
824 let font = context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?;
825 let metrics = font.metrics(text_settings.font_size);
826 ascender = ascender.max(metrics.ascender());
827 descender = descender.min(metrics.descender());
828 }
829
830 let primary_metrics = context.find_font(&text_settings.font_ids, |(_, font)| {
831 (false, font.metrics(text_settings.font_size))
832 })?;
833 if ascender.abs() < std::f32::EPSILON {
834 ascender = primary_metrics.ascender();
835 }
836 if descender.abs() < std::f32::EPSILON {
837 descender = primary_metrics.descender();
838 }
839
840 // Baseline alignment
841 let alignment_offset_y = match text_settings.text_baseline {
842 Baseline::Top => ascender,
843 Baseline::Middle => (ascender + descender) / 2.0,
844 Baseline::Alphabetic => 0.0,
845 Baseline::Bottom => descender,
846 };
847
848 for glyph in &mut res.glyphs {
849 glyph.x = cursor_x + glyph.offset_x + glyph.bearing_x;
850 glyph.y = (cursor_y + alignment_offset_y).round() + glyph.offset_y - glyph.bearing_y;
851
852 min_y = min_y.min(glyph.y);
853 max_y = max_y.max(glyph.y + glyph.height);
854
855 cursor_x += glyph.advance_x + text_settings.letter_spacing;
856 cursor_y += glyph.advance_y;
857 }
858
859 res.y = min_y;
860 res.height = max_y - min_y;
861
862 Ok(())
863}
864
865// Renderer
866
867#[derive(Clone, Debug)]
868pub struct DrawCommand {
869 pub image_id: ImageId,
870 pub quads: Vec<Quad>,
871}
872
873#[derive(Copy, Clone, Default, Debug)]
874pub struct Quad {
875 pub x0: f32,
876 pub y0: f32,
877 pub s0: f32,
878 pub t0: f32,
879 pub x1: f32,
880 pub y1: f32,
881 pub s1: f32,
882 pub t1: f32,
883}
884
885pub struct GlyphDrawCommands {
886 pub alpha_glyphs: Vec<DrawCommand>,
887 pub color_glyphs: Vec<DrawCommand>,
888}
889
890#[derive(Default)]
891pub(crate) struct GlyphAtlas {
892 pub rendered_glyphs: RefCell<FnvHashMap<RenderedGlyphId, RenderedGlyph>>,
893 pub glyph_textures: RefCell<Vec<FontTexture>>,
894}
895
896impl GlyphAtlas {
897 pub(crate) fn render_atlas<T: Renderer>(
898 &self,
899 canvas: &mut Canvas<T>,
900 text_layout: &TextMetrics,
901 font_size: f32,
902 line_width: f32,
903 mode: RenderMode,
904 ) -> Result<GlyphDrawCommands, ErrorKind> {
905 let mut alpha_cmd_map = FnvHashMap::default();
906 let mut color_cmd_map = FnvHashMap::default();
907
908 let line_width_offset = if mode == RenderMode::Stroke {
909 (line_width / 2.0).ceil()
910 } else {
911 0.0
912 };
913
914 let initial_render_target = canvas.current_render_target;
915
916 for glyph in &text_layout.glyphs {
917 let subpixel_location = crate::geometry::quantize(glyph.x.fract(), 0.1) * 10.0;
918
919 let id = RenderedGlyphId::new(
920 glyph.codepoint,
921 glyph.font_id,
922 font_size,
923 line_width,
924 mode,
925 subpixel_location as u8,
926 );
927
928 if !self.rendered_glyphs.borrow().contains_key(&id) {
929 let glyph = self.render_glyph(canvas, font_size, line_width, mode, glyph)?;
930
931 self.rendered_glyphs.borrow_mut().insert(id, glyph);
932 }
933
934 let rendered_glyphs = self.rendered_glyphs.borrow();
935 let rendered = rendered_glyphs.get(&id).unwrap();
936
937 if let Some(texture) = self.glyph_textures.borrow().get(rendered.texture_index) {
938 let image_id = texture.image_id;
939 let size = texture.atlas.size();
940 let itw = 1.0 / size.0 as f32;
941 let ith = 1.0 / size.1 as f32;
942
943 let cmd_map = if rendered.color_glyph {
944 &mut color_cmd_map
945 } else {
946 &mut alpha_cmd_map
947 };
948
949 let cmd = cmd_map.entry(rendered.texture_index).or_insert_with(|| DrawCommand {
950 image_id,
951 quads: Vec::new(),
952 });
953
954 let mut q = Quad::default();
955
956 let line_width_offset = if rendered.color_glyph { 0. } else { line_width_offset };
957
958 q.x0 = glyph.x.trunc() - line_width_offset - GLYPH_PADDING as f32;
959 q.y0 = (glyph.y + glyph.bearing_y).round()
960 - rendered.bearing_y as f32
961 - line_width_offset
962 - GLYPH_PADDING as f32;
963 q.x1 = q.x0 + rendered.width as f32;
964 q.y1 = q.y0 + rendered.height as f32;
965
966 q.s0 = rendered.atlas_x as f32 * itw;
967 q.t0 = rendered.atlas_y as f32 * ith;
968 q.s1 = (rendered.atlas_x + rendered.width) as f32 * itw;
969 q.t1 = (rendered.atlas_y + rendered.height) as f32 * ith;
970
971 cmd.quads.push(q);
972 }
973 }
974
975 canvas.set_render_target(initial_render_target);
976
977 Ok(GlyphDrawCommands {
978 alpha_glyphs: alpha_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
979 color_glyphs: color_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
980 })
981 }
982
983 fn render_glyph<T: Renderer>(
984 &self,
985 canvas: &mut Canvas<T>,
986 font_size: f32,
987 line_width: f32,
988 mode: RenderMode,
989 glyph: &ShapedGlyph,
990 ) -> Result<RenderedGlyph, ErrorKind> {
991 let padding = GLYPH_PADDING + GLYPH_MARGIN;
992
993 let text_context = canvas.text_context.clone();
994 let mut text_context = text_context.borrow_mut();
995
996 let (mut maybe_glyph_representation, scale) = {
997 let font = text_context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?;
998 let face = font.face_ref();
999 let scale = font.scale(font_size);
1000
1001 let maybe_glyph_representation =
1002 font.glyph_rendering_representation(&face, glyph.codepoint as u16, font_size as u16);
1003 (maybe_glyph_representation, scale)
1004 };
1005
1006 #[cfg(feature = "image-loading")]
1007 let color_glyph = matches!(maybe_glyph_representation, Some(GlyphRendering::RenderAsImage(..)));
1008 #[cfg(not(feature = "image-loading"))]
1009 let color_glyph = false;
1010
1011 let line_width = if color_glyph || mode != RenderMode::Stroke {
1012 0.0
1013 } else {
1014 line_width
1015 };
1016
1017 let line_width_offset = (line_width / 2.0).ceil();
1018
1019 let width = glyph.width.ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
1020 let height = glyph.height.ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
1021
1022 let (dst_index, dst_image_id, (dst_x, dst_y)) =
1023 self.find_texture_or_alloc(canvas, width as usize, height as usize)?;
1024
1025 // render glyph to image
1026 canvas.save();
1027 canvas.reset();
1028
1029 let rendered_bearing_y = glyph.bearing_y.round();
1030 let x_quant = crate::geometry::quantize(glyph.x.fract(), 0.1);
1031 let x = dst_x as f32 - glyph.bearing_x + line_width_offset + padding as f32 + x_quant;
1032 let y = TEXTURE_SIZE as f32 - dst_y as f32 - rendered_bearing_y - line_width_offset - padding as f32;
1033
1034 let rendered_glyph = RenderedGlyph {
1035 width: width - 2 * GLYPH_MARGIN,
1036 height: height - 2 * GLYPH_MARGIN,
1037 bearing_y: rendered_bearing_y as i32,
1038 atlas_x: dst_x as u32 + GLYPH_MARGIN,
1039 atlas_y: dst_y as u32 + GLYPH_MARGIN,
1040 texture_index: dst_index,
1041 color_glyph,
1042 };
1043
1044 match maybe_glyph_representation.as_mut() {
1045 Some(GlyphRendering::RenderAsPath(ref mut path)) => {
1046 canvas.translate(x, y);
1047
1048 canvas.set_render_target(RenderTarget::Image(dst_image_id));
1049 canvas.clear_rect(
1050 dst_x as u32,
1051 TEXTURE_SIZE as u32 - dst_y as u32 - height,
1052 width,
1053 height,
1054 Color::black(),
1055 );
1056 let factor = 1.0 / 8.0;
1057
1058 let mask_color = Color::rgbf(factor, factor, factor);
1059
1060 let mut line_width = line_width;
1061
1062 if mode == RenderMode::Stroke {
1063 line_width /= scale;
1064 }
1065
1066 canvas.global_composite_blend_func(crate::BlendFactor::SrcAlpha, crate::BlendFactor::One);
1067
1068 // 4x
1069 // let points = [
1070 // (-3.0/8.0, 1.0/8.0),
1071 // (1.0/8.0, 3.0/8.0),
1072 // (3.0/8.0, -1.0/8.0),
1073 // (-1.0/8.0, -3.0/8.0),
1074 // ];
1075
1076 // 8x
1077 let points = [
1078 (-7.0 / 16.0, -1.0 / 16.0),
1079 (-1.0 / 16.0, -5.0 / 16.0),
1080 (3.0 / 16.0, -7.0 / 16.0),
1081 (5.0 / 16.0, -3.0 / 16.0),
1082 (7.0 / 16.0, 1.0 / 16.0),
1083 (1.0 / 16.0, 5.0 / 16.0),
1084 (-3.0 / 16.0, 7.0 / 16.0),
1085 (-5.0 / 16.0, 3.0 / 16.0),
1086 ];
1087
1088 for point in &points {
1089 canvas.save();
1090 canvas.translate(point.0, point.1);
1091
1092 canvas.scale(scale, scale);
1093
1094 if mode == RenderMode::Stroke {
1095 canvas.stroke_path_internal(
1096 path,
1097 &PaintFlavor::Color(mask_color),
1098 false,
1099 &StrokeSettings {
1100 line_width,
1101 ..Default::default()
1102 },
1103 );
1104 } else {
1105 canvas.fill_path_internal(path, &PaintFlavor::Color(mask_color), false, FillRule::EvenOdd);
1106 }
1107
1108 canvas.restore();
1109 }
1110 }
1111 #[cfg(feature = "image-loading")]
1112 Some(GlyphRendering::RenderAsImage(image_buffer)) => {
1113 let target_x = rendered_glyph.atlas_x as usize;
1114 let target_y = rendered_glyph.atlas_y as usize;
1115 let target_width = rendered_glyph.width;
1116 let target_height = rendered_glyph.height;
1117
1118 let image_buffer =
1119 image_buffer.resize(target_width, target_height, image::imageops::FilterType::Nearest);
1120 if let Ok(image) = crate::image::ImageSource::try_from(&image_buffer) {
1121 canvas.update_image(dst_image_id, image, target_x, target_y).unwrap();
1122 }
1123 }
1124 _ => {}
1125 }
1126
1127 canvas.restore();
1128
1129 Ok(rendered_glyph)
1130 }
1131
1132 // Returns (texture index, image id, glyph padding box)
1133 fn find_texture_or_alloc<T: Renderer>(
1134 &self,
1135 canvas: &mut Canvas<T>,
1136 width: usize,
1137 height: usize,
1138 ) -> Result<(usize, ImageId, (usize, usize)), ErrorKind> {
1139 // Find a free location in one of the atlases
1140 let mut texture_search_result = {
1141 let mut glyph_textures = self.glyph_textures.borrow_mut();
1142 let mut textures = glyph_textures.iter_mut().enumerate();
1143 textures.find_map(|(index, texture)| {
1144 texture
1145 .atlas
1146 .add_rect(width, height)
1147 .map(|loc| (index, texture.image_id, loc))
1148 })
1149 };
1150
1151 if texture_search_result.is_none() {
1152 // All atlases are exausted and a new one must be created
1153 let mut atlas = Atlas::new(TEXTURE_SIZE, TEXTURE_SIZE);
1154
1155 let loc = atlas
1156 .add_rect(width, height)
1157 .ok_or(ErrorKind::FontSizeTooLargeForAtlas)?;
1158
1159 // Using PixelFormat::Gray8 works perfectly and takes less VRAM.
1160 // We keep Rgba8 for now because it might be useful for sub-pixel
1161 // anti-aliasing (ClearType®), and the atlas debug display is much
1162 // clearer with different colors. Also, Rgba8 is required for color
1163 // fonts (typically used for emojis).
1164 let info = ImageInfo::new(ImageFlags::empty(), atlas.size().0, atlas.size().1, PixelFormat::Rgba8);
1165 let image_id = canvas.images.alloc(&mut canvas.renderer, info)?;
1166
1167 #[cfg(feature = "debug_inspector")]
1168 if cfg!(debug_assertions) {
1169 // Fill the texture with red pixels only in debug builds.
1170 if let Ok(size) = canvas.image_size(image_id) {
1171 // With image-loading we then subsequently support color fonts, where
1172 // the color glyphs are uploaded directly. Since that's immediately and
1173 // the clear_rect() is run much later, it would overwrite any uploaded
1174 // glyphs. So then when for the debug-inspector, use an image to clear.
1175 #[cfg(feature = "image-loading")]
1176 {
1177 use rgb::FromSlice;
1178 let clear_image = image::RgbaImage::from_pixel(
1179 size.0 as u32,
1180 size.1 as u32,
1181 image::Rgba::<u8>([255, 0, 0, 0]),
1182 );
1183 canvas
1184 .update_image(
1185 image_id,
1186 crate::image::ImageSource::from(imgref::Img::new(
1187 clear_image.as_rgba(),
1188 clear_image.width() as usize,
1189 clear_image.height() as usize,
1190 )),
1191 0,
1192 0,
1193 )
1194 .unwrap();
1195 }
1196 #[cfg(not(feature = "image-loading"))]
1197 {
1198 canvas.save();
1199 canvas.reset();
1200 canvas.set_render_target(RenderTarget::Image(image_id));
1201 canvas.clear_rect(
1202 0,
1203 0,
1204 size.0 as u32,
1205 size.1 as u32,
1206 Color::rgb(255, 0, 0), // Shown as white if using Gray8.,
1207 );
1208 canvas.restore();
1209 }
1210 }
1211 }
1212
1213 self.glyph_textures.borrow_mut().push(FontTexture { atlas, image_id });
1214
1215 let index = self.glyph_textures.borrow().len() - 1;
1216 texture_search_result = Some((index, image_id, loc));
1217 }
1218
1219 texture_search_result.ok_or(ErrorKind::UnknownError)
1220 }
1221
1222 pub(crate) fn clear<T: Renderer>(&self, canvas: &mut Canvas<T>) {
1223 let image_ids = std::mem::take(&mut *self.glyph_textures.borrow_mut())
1224 .into_iter()
1225 .map(|font_texture| font_texture.image_id);
1226 image_ids.for_each(|id| canvas.delete_image(id));
1227
1228 self.rendered_glyphs.borrow_mut().clear();
1229 }
1230}
1231
1232pub(crate) fn render_direct<T: Renderer>(
1233 canvas: &mut Canvas<T>,
1234 text_layout: &TextMetrics,
1235 paint_flavor: &PaintFlavor,
1236 anti_alias: bool,
1237 stroke: &StrokeSettings,
1238 font_size: f32,
1239 mode: RenderMode,
1240 invscale: f32,
1241) -> Result<(), ErrorKind> {
1242 let text_context = canvas.text_context.clone();
1243 let text_context = text_context.borrow_mut();
1244
1245 let mut scaled = false;
1246 let mut face_cache: HashMap<FontId, rustybuzz::Face> = HashMap::default();
1247
1248 for glyph in &text_layout.glyphs {
1249 let (glyph_rendering, scale) = {
1250 let font = text_context.font(glyph.font_id).ok_or(ErrorKind::NoFontFound)?;
1251 let face = face_cache.entry(glyph.font_id).or_insert_with(|| font.face_ref());
1252
1253 let scale = font.scale(font_size);
1254
1255 let glyph_rendering = if let Some(glyph_rendering) =
1256 font.glyph_rendering_representation(face, glyph.codepoint as u16, font_size as u16)
1257 {
1258 glyph_rendering
1259 } else {
1260 continue;
1261 };
1262
1263 (glyph_rendering, scale)
1264 };
1265
1266 canvas.save();
1267
1268 let mut line_width = stroke.line_width;
1269
1270 if mode == RenderMode::Stroke && !scaled {
1271 line_width /= scale;
1272 scaled = true;
1273 }
1274
1275 canvas.translate(
1276 (glyph.x - glyph.bearing_x) * invscale,
1277 (glyph.y + glyph.bearing_y) * invscale,
1278 );
1279 canvas.scale(scale * invscale, -scale * invscale);
1280
1281 match glyph_rendering {
1282 GlyphRendering::RenderAsPath(path) => {
1283 if mode == RenderMode::Stroke {
1284 canvas.stroke_path_internal(
1285 path.borrow(),
1286 paint_flavor,
1287 anti_alias,
1288 &StrokeSettings {
1289 line_width,
1290 ..stroke.clone()
1291 },
1292 );
1293 } else {
1294 canvas.fill_path_internal(path.borrow(), paint_flavor, anti_alias, FillRule::EvenOdd);
1295 }
1296 }
1297 #[cfg(feature = "image-loading")]
1298 GlyphRendering::RenderAsImage(_) => unreachable!(),
1299 }
1300
1301 canvas.restore();
1302 }
1303
1304 Ok(())
1305}
1306