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