text_layout.rs

  1use crate::{
  2    color::ColorU,
  3    fonts::{FontCache, FontId, GlyphId},
  4    geometry::rect::RectF,
  5    platform, scene, PaintContext,
  6};
  7use core_foundation::{
  8    attributed_string::CFMutableAttributedString,
  9    base::{CFRange, TCFType},
 10    string::CFString,
 11};
 12use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
 13use ordered_float::OrderedFloat;
 14use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
 15use pathfinder_geometry::vector::{vec2f, Vector2F};
 16use smallvec::SmallVec;
 17use std::{
 18    borrow::Borrow,
 19    char,
 20    collections::HashMap,
 21    convert::TryFrom,
 22    hash::{Hash, Hasher},
 23    ops::Range,
 24    sync::Arc,
 25};
 26
 27pub struct TextLayoutCache {
 28    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<Line>>>,
 29    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<Line>>>,
 30    fonts: Arc<dyn platform::FontSystem>,
 31}
 32
 33impl TextLayoutCache {
 34    pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
 35        Self {
 36            prev_frame: Mutex::new(HashMap::new()),
 37            curr_frame: RwLock::new(HashMap::new()),
 38            fonts,
 39        }
 40    }
 41
 42    pub fn finish_frame(&self) {
 43        let mut prev_frame = self.prev_frame.lock();
 44        let mut curr_frame = self.curr_frame.write();
 45        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
 46        curr_frame.clear();
 47    }
 48
 49    pub fn layout_str<'a>(
 50        &'a self,
 51        text: &'a str,
 52        font_size: f32,
 53        runs: &'a [(Range<usize>, FontId)],
 54    ) -> Arc<Line> {
 55        let key = &CacheKeyRef {
 56            text,
 57            font_size: OrderedFloat(font_size),
 58            runs,
 59        } as &dyn CacheKey;
 60        let curr_frame = self.curr_frame.upgradable_read();
 61        if let Some(line) = curr_frame.get(key) {
 62            return line.clone();
 63        }
 64
 65        let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
 66        if let Some((key, line)) = self.prev_frame.lock().remove_entry(key) {
 67            curr_frame.insert(key, line.clone());
 68            line.clone()
 69        } else {
 70            let line = Arc::new(self.fonts.layout_str(text, font_size, runs));
 71            let key = CacheKeyValue {
 72                text: text.into(),
 73                font_size: OrderedFloat(font_size),
 74                runs: SmallVec::from(runs),
 75            };
 76            curr_frame.insert(key, line.clone());
 77            line
 78        }
 79    }
 80}
 81
 82trait CacheKey {
 83    fn key<'a>(&'a self) -> CacheKeyRef<'a>;
 84}
 85
 86impl<'a> PartialEq for (dyn CacheKey + 'a) {
 87    fn eq(&self, other: &dyn CacheKey) -> bool {
 88        self.key() == other.key()
 89    }
 90}
 91
 92impl<'a> Eq for (dyn CacheKey + 'a) {}
 93
 94impl<'a> Hash for (dyn CacheKey + 'a) {
 95    fn hash<H: Hasher>(&self, state: &mut H) {
 96        self.key().hash(state)
 97    }
 98}
 99
100#[derive(Eq, PartialEq)]
101struct CacheKeyValue {
102    text: String,
103    font_size: OrderedFloat<f32>,
104    runs: SmallVec<[(Range<usize>, FontId); 1]>,
105}
106
107impl CacheKey for CacheKeyValue {
108    fn key<'a>(&'a self) -> CacheKeyRef<'a> {
109        CacheKeyRef {
110            text: &self.text.as_str(),
111            font_size: self.font_size,
112            runs: self.runs.as_slice(),
113        }
114    }
115}
116
117impl Hash for CacheKeyValue {
118    fn hash<H: Hasher>(&self, state: &mut H) {
119        self.key().hash(state);
120    }
121}
122
123impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
124    fn borrow(&self) -> &(dyn CacheKey + 'a) {
125        self as &dyn CacheKey
126    }
127}
128
129#[derive(Copy, Clone, PartialEq, Eq, Hash)]
130struct CacheKeyRef<'a> {
131    text: &'a str,
132    font_size: OrderedFloat<f32>,
133    runs: &'a [(Range<usize>, FontId)],
134}
135
136impl<'a> CacheKey for CacheKeyRef<'a> {
137    fn key<'b>(&'b self) -> CacheKeyRef<'b> {
138        *self
139    }
140}
141
142#[derive(Default, Debug)]
143pub struct Line {
144    pub width: f32,
145    pub runs: Vec<Run>,
146    pub len: usize,
147    pub font_size: f32,
148}
149
150#[derive(Debug)]
151pub struct Run {
152    pub font_id: FontId,
153    pub glyphs: Vec<Glyph>,
154}
155
156#[derive(Debug)]
157pub struct Glyph {
158    pub id: GlyphId,
159    pub position: Vector2F,
160    pub index: usize,
161}
162
163impl Line {
164    pub fn x_for_index(&self, index: usize) -> f32 {
165        for run in &self.runs {
166            for glyph in &run.glyphs {
167                if glyph.index == index {
168                    return glyph.position.x();
169                }
170            }
171        }
172        self.width
173    }
174
175    pub fn index_for_x(&self, x: f32) -> Option<usize> {
176        if x >= self.width {
177            None
178        } else {
179            for run in self.runs.iter().rev() {
180                for glyph in run.glyphs.iter().rev() {
181                    if glyph.position.x() <= x {
182                        return Some(glyph.index);
183                    }
184                }
185            }
186            Some(0)
187        }
188    }
189
190    pub fn paint(&self, bounds: RectF, colors: &[(Range<usize>, ColorU)], ctx: &mut PaintContext) {
191        let mut colors = colors.iter().peekable();
192        let mut color = ColorU::black();
193
194        for run in &self.runs {
195            let bounding_box = ctx.font_cache.bounding_box(run.font_id, self.font_size);
196            let max_glyph_width = bounding_box.x();
197            for glyph in &run.glyphs {
198                let glyph_origin = bounds.origin() + glyph.position;
199                if glyph_origin.x() + max_glyph_width < bounds.origin().x() {
200                    continue;
201                }
202                if glyph_origin.x() > bounds.upper_right().x() {
203                    break;
204                }
205
206                while let Some((range, next_color)) = colors.peek() {
207                    if glyph.index >= range.end {
208                        colors.next();
209                    } else {
210                        color = *next_color;
211                        break;
212                    }
213                }
214
215                ctx.scene.push_glyph(scene::Glyph {
216                    font_id: run.font_id,
217                    font_size: self.font_size,
218                    id: glyph.id,
219                    origin: glyph_origin,
220                    color,
221                });
222            }
223        }
224    }
225}