-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add cosmic-text as an option for the text renderer
Cosmic-text is still changing somewhat rapidly, but should provide a way to handle RTL/bidir/etc text with proper shaping. So I thought I'd try adding support for it here. This isn't quite working right when I try it with Arabic text (maybe partly that it needs alpha blending when compositing glyphs?). And it needs to request the right font.
- Loading branch information
Showing
3 changed files
with
183 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
use crate::title::{config, font_preference::FontPreference}; | ||
use cosmic_text::{ | ||
Attrs, AttrsList, BufferLine, Color as CosmicColor, Family, FontSystem, Metrics, SwashCache, | ||
Wrap, | ||
}; | ||
use once_cell::sync::Lazy; | ||
use tiny_skia::{Color, ColorU8, Pixmap, PremultipliedColorU8}; | ||
|
||
// Use arbitrarily large width, then calculate max/min x from resulting glyphs | ||
const MAX_WIDTH: i32 = 1024 * 1024; | ||
|
||
// Needs static lifetime due to limitations in cosmic-text/rustybuzz | ||
static FONT_SYSTEM: Lazy<FontSystem> = Lazy::new(|| FontSystem::new()); | ||
|
||
fn attrs_from_font_pref(font_preference: &FontPreference) -> Attrs { | ||
let attrs = Attrs::new().family(Family::Name(&font_preference.name)); | ||
if let Some(style) = &font_preference.style { | ||
// TODO | ||
} | ||
attrs | ||
} | ||
|
||
pub struct CosmicTextTitleText { | ||
buffer_line: BufferLine, | ||
cache: SwashCache<'static>, | ||
color: Color, | ||
scale: u32, | ||
pixmap: Option<Pixmap>, | ||
font_pref: FontPreference, | ||
} | ||
|
||
impl std::fmt::Debug for CosmicTextTitleText { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
f.debug_struct("TitleText") | ||
.field("color", &self.color) | ||
.field("scale", &self.scale) | ||
.field("pixmap", &self.pixmap) | ||
.field("font_pref", &self.font_pref) | ||
.finish() | ||
} | ||
} | ||
|
||
impl CosmicTextTitleText { | ||
pub fn new(color: Color) -> Self { | ||
let buffer_line = BufferLine::new("", AttrsList::new(Attrs::new())); | ||
let cache = SwashCache::new(&FONT_SYSTEM); | ||
let font_pref = config::titlebar_font().unwrap_or_default(); | ||
Self { | ||
buffer_line, | ||
cache, | ||
color, | ||
scale: 1, | ||
pixmap: None, | ||
font_pref, | ||
} | ||
} | ||
|
||
pub fn update_scale(&mut self, scale: u32) { | ||
self.scale = scale; | ||
self.update_pixmap(); | ||
} | ||
|
||
pub fn update_title<S: Into<String>>(&mut self, title: S) { | ||
let attrs = attrs_from_font_pref(&self.font_pref); | ||
self.buffer_line | ||
.set_text(title.into(), AttrsList::new(attrs)); | ||
self.update_pixmap(); | ||
} | ||
|
||
pub fn update_color(&mut self, color: Color) { | ||
self.color = color; | ||
self.update_pixmap(); | ||
} | ||
|
||
pub fn pixmap(&self) -> Option<&Pixmap> { | ||
self.pixmap.as_ref() | ||
} | ||
|
||
fn update_pixmap(&mut self) { | ||
self.pixmap = None; | ||
|
||
let shape_line = self.buffer_line.shape(&FONT_SYSTEM); | ||
|
||
let height = (1.4 * self.scale as f32 * self.font_pref.pt_size * 96.0 / 72.0).ceil() as i32; // ? | ||
let layout_lines = shape_line.layout(height, MAX_WIDTH, Wrap::Word); | ||
let layout_line = &layout_lines[0]; | ||
let min_x = layout_line | ||
.glyphs | ||
.iter() | ||
.map(|i| i.x.floor() as u32) | ||
.min() | ||
.unwrap_or(0); | ||
let width = layout_line.w.ceil() as u32; | ||
|
||
let mut pixmap = match Pixmap::new(width, height as u32) { | ||
Some(pixmap) => pixmap, | ||
None => return, | ||
}; | ||
let pixels = pixmap.pixels_mut(); | ||
|
||
let color = self.color.to_color_u8(); | ||
let color = CosmicColor::rgba(color.red(), color.green(), color.blue(), color.alpha()); | ||
for glyph in &layout_line.glyphs { | ||
let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int); | ||
self.cache | ||
.with_pixels(cache_key, color, |pixel_x, pixel_y, color| { | ||
if color.a() == 0 { | ||
return; | ||
} | ||
|
||
let y = height + y_int + pixel_y; | ||
let x = x_int + pixel_x - min_x as i32; | ||
let idx = y * width as i32 + x; | ||
if idx >= 0 && (idx as usize) < pixels.len() { | ||
let idx = idx as usize; | ||
let color = ColorU8::from_rgba(color.r(), color.g(), color.b(), color.a()); | ||
pixels[idx] = alpha_blend(color.premultiply(), pixels[idx]); | ||
} | ||
}); | ||
} | ||
|
||
self.pixmap = Some(pixmap); | ||
} | ||
} | ||
|
||
// `a` over `b` | ||
// This should be correct but not especially efficient. | ||
fn alpha_blend(a: PremultipliedColorU8, b: PremultipliedColorU8) -> PremultipliedColorU8 { | ||
let blend_channel = |channel_a: u8, channel_b: u8| -> u8 { | ||
channel_a.saturating_add(channel_b.saturating_mul(255 - a.alpha())) | ||
}; | ||
PremultipliedColorU8::from_rgba( | ||
blend_channel(a.red(), b.red()), | ||
blend_channel(a.green(), b.green()), | ||
blend_channel(a.blue(), b.blue()), | ||
blend_channel(a.alpha(), b.alpha()), | ||
) | ||
.unwrap() | ||
} |