1use crate::{
2 point, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle,
3 FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams,
4 ShapedGlyph, SharedString, Size,
5};
6use anyhow::{anyhow, Context, Ok, Result};
7use collections::HashMap;
8use cosmic_text::{
9 fontdb::Query, Attrs, AttrsList, BufferLine, CacheKey, Family, Font as CosmicTextFont,
10 FontSystem, SwashCache,
11};
12use parking_lot::{RwLock, RwLockUpgradableReadGuard};
13use pathfinder_geometry::{
14 rect::{RectF, RectI},
15 vector::{Vector2F, Vector2I},
16};
17use smallvec::SmallVec;
18use std::{borrow::Cow, sync::Arc};
19
20pub(crate) struct LinuxTextSystem(RwLock<LinuxTextSystemState>);
21
22struct LinuxTextSystemState {
23 swash_cache: SwashCache,
24 font_system: FontSystem,
25 fonts: Vec<Arc<CosmicTextFont>>,
26 font_selections: HashMap<Font, FontId>,
27 font_ids_by_family_name: HashMap<SharedString, SmallVec<[FontId; 4]>>,
28 postscript_names_by_font_id: HashMap<FontId, String>,
29}
30
31impl LinuxTextSystem {
32 pub(crate) fn new() -> Self {
33 let mut font_system = FontSystem::new();
34
35 // todo!(linux) make font loading non-blocking
36 font_system.db_mut().load_system_fonts();
37
38 Self(RwLock::new(LinuxTextSystemState {
39 font_system,
40 swash_cache: SwashCache::new(),
41 fonts: Vec::new(),
42 font_selections: HashMap::default(),
43 // font_ids_by_postscript_name: HashMap::default(),
44 font_ids_by_family_name: HashMap::default(),
45 postscript_names_by_font_id: HashMap::default(),
46 }))
47 }
48}
49
50impl Default for LinuxTextSystem {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56#[allow(unused)]
57impl PlatformTextSystem for LinuxTextSystem {
58 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
59 self.0.write().add_fonts(fonts)
60 }
61
62 // todo!(linux) ensure that this integrates with platform font loading
63 // do we need to do more than call load_system_fonts()?
64 fn all_font_names(&self) -> Vec<String> {
65 self.0
66 .read()
67 .font_system
68 .db()
69 .faces()
70 .map(|face| face.post_script_name.clone())
71 .collect()
72 }
73
74 // todo!(linux)
75 fn all_font_families(&self) -> Vec<String> {
76 Vec::new()
77 }
78
79 fn font_id(&self, font: &Font) -> Result<FontId> {
80 // todo!(linux): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit?
81 let lock = self.0.upgradable_read();
82 if let Some(font_id) = lock.font_selections.get(font) {
83 Ok(*font_id)
84 } else {
85 let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
86 let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family)
87 {
88 font_ids.as_slice()
89 } else {
90 let font_ids = lock.load_family(&font.family, font.features)?;
91 lock.font_ids_by_family_name
92 .insert(font.family.clone(), font_ids);
93 lock.font_ids_by_family_name[&font.family].as_ref()
94 };
95
96 let id = lock
97 .font_system
98 .db()
99 .query(&Query {
100 families: &[Family::Name(&font.family)],
101 weight: font.weight.into(),
102 style: font.style.into(),
103 stretch: Default::default(),
104 })
105 .context("no font")?;
106
107 let font_id = if let Some(font_id) = lock.fonts.iter().position(|font| font.id() == id)
108 {
109 FontId(font_id)
110 } else {
111 // Font isn't in fonts so add it there, this is because we query all the fonts in the db
112 // and maybe we haven't loaded it yet
113 let font_id = FontId(lock.fonts.len());
114 let font = lock.font_system.get_font(id).unwrap();
115 lock.fonts.push(font);
116 font_id
117 };
118
119 lock.font_selections.insert(font.clone(), font_id);
120 Ok(font_id)
121 }
122 }
123
124 fn font_metrics(&self, font_id: FontId) -> FontMetrics {
125 let metrics = self.0.read().fonts[font_id.0].as_swash().metrics(&[]);
126
127 FontMetrics {
128 units_per_em: metrics.units_per_em as u32,
129 ascent: metrics.ascent,
130 descent: -metrics.descent, // todo!(linux) confirm this is correct
131 line_gap: metrics.leading,
132 underline_position: metrics.underline_offset,
133 underline_thickness: metrics.stroke_size,
134 cap_height: metrics.cap_height,
135 x_height: metrics.x_height,
136 // todo!(linux): Compute this correctly
137 bounding_box: Bounds {
138 origin: point(0.0, 0.0),
139 size: size(metrics.max_width, metrics.ascent + metrics.descent),
140 },
141 }
142 }
143
144 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
145 let lock = self.0.read();
146 let metrics = lock.fonts[font_id.0].as_swash().metrics(&[]);
147 let glyph_metrics = lock.fonts[font_id.0].as_swash().glyph_metrics(&[]);
148 let glyph_id = glyph_id.0 as u16;
149 // todo!(linux): Compute this correctly
150 // see https://github.com/servo/font-kit/blob/master/src/loaders/freetype.rs#L614-L620
151 Ok(Bounds {
152 origin: point(0.0, 0.0),
153 size: size(
154 glyph_metrics.advance_width(glyph_id),
155 glyph_metrics.advance_height(glyph_id),
156 ),
157 })
158 }
159
160 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
161 self.0.read().advance(font_id, glyph_id)
162 }
163
164 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
165 self.0.read().glyph_for_char(font_id, ch)
166 }
167
168 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
169 self.0.write().raster_bounds(params)
170 }
171
172 fn rasterize_glyph(
173 &self,
174 params: &RenderGlyphParams,
175 raster_bounds: Bounds<DevicePixels>,
176 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
177 self.0.write().rasterize_glyph(params, raster_bounds)
178 }
179
180 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout {
181 self.0.write().layout_line(text, font_size, runs)
182 }
183
184 // todo!(linux) Confirm that this has been superseded by the LineWrapper
185 fn wrap_line(
186 &self,
187 text: &str,
188 font_id: FontId,
189 font_size: Pixels,
190 width: Pixels,
191 ) -> Vec<usize> {
192 unimplemented!()
193 }
194}
195
196impl LinuxTextSystemState {
197 #[profiling::function]
198 fn add_fonts(&mut self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
199 let db = self.font_system.db_mut();
200 for bytes in fonts {
201 match bytes {
202 Cow::Borrowed(embedded_font) => {
203 db.load_font_data(embedded_font.to_vec());
204 }
205 Cow::Owned(bytes) => {
206 db.load_font_data(bytes);
207 }
208 }
209 }
210 Ok(())
211 }
212
213 #[profiling::function]
214 fn load_family(
215 &mut self,
216 name: &SharedString,
217 _features: FontFeatures,
218 ) -> Result<SmallVec<[FontId; 4]>> {
219 let mut font_ids = SmallVec::new();
220 let family = self
221 .font_system
222 .get_font_matches(Attrs::new().family(cosmic_text::Family::Name(name)));
223 for font in family.as_ref() {
224 let font = self.font_system.get_font(*font).unwrap();
225 if font.as_swash().charmap().map('m') == 0 {
226 self.font_system.db_mut().remove_face(font.id());
227 continue;
228 };
229
230 let font_id = FontId(self.fonts.len());
231 font_ids.push(font_id);
232 self.fonts.push(font);
233 }
234 Ok(font_ids)
235 }
236
237 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
238 let width = self.fonts[font_id.0]
239 .as_swash()
240 .glyph_metrics(&[])
241 .advance_width(glyph_id.0 as u16);
242 let height = self.fonts[font_id.0]
243 .as_swash()
244 .glyph_metrics(&[])
245 .advance_height(glyph_id.0 as u16);
246 Ok(Size { width, height })
247 }
248
249 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
250 let glyph_id = self.fonts[font_id.0].as_swash().charmap().map(ch);
251 if glyph_id == 0 {
252 None
253 } else {
254 Some(GlyphId(glyph_id.into()))
255 }
256 }
257
258 fn is_emoji(&self, font_id: FontId) -> bool {
259 // todo!(linux): implement this correctly
260 self.postscript_names_by_font_id
261 .get(&font_id)
262 .map_or(false, |postscript_name| {
263 postscript_name == "AppleColorEmoji"
264 })
265 }
266
267 // todo!(linux) both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system
268 fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
269 let font = &self.fonts[params.font_id.0];
270 let font_system = &mut self.font_system;
271 let image = self
272 .swash_cache
273 .get_image(
274 font_system,
275 CacheKey::new(
276 font.id(),
277 params.glyph_id.0 as u16,
278 (params.font_size * params.scale_factor).into(),
279 (0.0, 0.0),
280 )
281 .0,
282 )
283 .clone()
284 .unwrap();
285 Ok(Bounds {
286 origin: point(image.placement.left.into(), (-image.placement.top).into()),
287 size: size(image.placement.width.into(), image.placement.height.into()),
288 })
289 }
290
291 #[profiling::function]
292 fn rasterize_glyph(
293 &mut self,
294 params: &RenderGlyphParams,
295 glyph_bounds: Bounds<DevicePixels>,
296 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
297 if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
298 Err(anyhow!("glyph bounds are empty"))
299 } else {
300 // todo!(linux) handle subpixel variants
301 let bitmap_size = glyph_bounds.size;
302 let font = &self.fonts[params.font_id.0];
303 let font_system = &mut self.font_system;
304 let image = self
305 .swash_cache
306 .get_image(
307 font_system,
308 CacheKey::new(
309 font.id(),
310 params.glyph_id.0 as u16,
311 (params.font_size * params.scale_factor).into(),
312 (0.0, 0.0),
313 )
314 .0,
315 )
316 .clone()
317 .unwrap();
318
319 Ok((bitmap_size, image.data))
320 }
321 }
322
323 // todo!(linux) This is all a quick first pass, maybe we should be using cosmic_text::Buffer
324 #[profiling::function]
325 fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
326 let mut attrs_list = AttrsList::new(Attrs::new());
327 let mut offs = 0;
328 for run in font_runs {
329 // todo!(linux) We need to check we are doing utf properly
330 let font = &self.fonts[run.font_id.0];
331 let font = self.font_system.db().face(font.id()).unwrap();
332 attrs_list.add_span(
333 offs..offs + run.len,
334 Attrs::new()
335 .family(Family::Name(&font.families.first().unwrap().0))
336 .stretch(font.stretch)
337 .style(font.style)
338 .weight(font.weight),
339 );
340 offs += run.len;
341 }
342 let mut line = BufferLine::new(text, attrs_list, cosmic_text::Shaping::Advanced);
343 let layout = line.layout(
344 &mut self.font_system,
345 font_size.0,
346 f32::MAX, // todo!(linux) we don't have a width cause this should technically not be wrapped I believe
347 cosmic_text::Wrap::None,
348 );
349 let mut runs = Vec::new();
350 // todo!(linux) what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering
351 let layout = layout.first().unwrap();
352 for glyph in &layout.glyphs {
353 let font_id = glyph.font_id;
354 let font_id = FontId(
355 self.fonts
356 .iter()
357 .position(|font| font.id() == font_id)
358 .unwrap(),
359 );
360 let mut glyphs = SmallVec::new();
361 // todo!(linux) this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction
362 glyphs.push(ShapedGlyph {
363 id: GlyphId(glyph.glyph_id as u32),
364 position: point((glyph.x).into(), glyph.y.into()),
365 index: glyph.start,
366 is_emoji: self.is_emoji(font_id),
367 });
368 runs.push(crate::ShapedRun { font_id, glyphs });
369 }
370 LineLayout {
371 font_size,
372 width: layout.w.into(),
373 ascent: layout.max_ascent.into(),
374 descent: layout.max_descent.into(),
375 runs,
376 len: text.len(),
377 }
378 }
379}
380
381impl From<RectF> for Bounds<f32> {
382 fn from(rect: RectF) -> Self {
383 Bounds {
384 origin: point(rect.origin_x(), rect.origin_y()),
385 size: size(rect.width(), rect.height()),
386 }
387 }
388}
389
390impl From<RectI> for Bounds<DevicePixels> {
391 fn from(rect: RectI) -> Self {
392 Bounds {
393 origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
394 size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
395 }
396 }
397}
398
399impl From<Vector2I> for Size<DevicePixels> {
400 fn from(value: Vector2I) -> Self {
401 size(value.x().into(), value.y().into())
402 }
403}
404
405impl From<RectI> for Bounds<i32> {
406 fn from(rect: RectI) -> Self {
407 Bounds {
408 origin: point(rect.origin_x(), rect.origin_y()),
409 size: size(rect.width(), rect.height()),
410 }
411 }
412}
413
414impl From<Point<u32>> for Vector2I {
415 fn from(size: Point<u32>) -> Self {
416 Vector2I::new(size.x as i32, size.y as i32)
417 }
418}
419
420impl From<Vector2F> for Size<f32> {
421 fn from(vec: Vector2F) -> Self {
422 size(vec.x(), vec.y())
423 }
424}
425
426impl From<FontWeight> for cosmic_text::Weight {
427 fn from(value: FontWeight) -> Self {
428 cosmic_text::Weight(value.0 as u16)
429 }
430}
431
432impl From<FontStyle> for cosmic_text::Style {
433 fn from(style: FontStyle) -> Self {
434 match style {
435 FontStyle::Normal => cosmic_text::Style::Normal,
436 FontStyle::Italic => cosmic_text::Style::Italic,
437 FontStyle::Oblique => cosmic_text::Style::Oblique,
438 }
439 }
440}