Skip to content

Commit

Permalink
Add cosmic-text as an option for the text renderer
Browse files Browse the repository at this point in the history
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
ids1024 committed Mar 2, 2023
1 parent 59ae0e9 commit 469fe18
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 6 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ smithay-client-toolkit = "0.16"
tiny-skia = { version = "0.8", features = ["std", "simd"] }
log = "0.4"
memmap2 = "0.5.8"
once_cell = { version = "1.16.0", optional = true }

# Draw title text using crossfont `--features crossfont`
crossfont = { version = "0.5.0", features = ["force_system_fontconfig"], optional = true }
# Draw title text using ab_glyph `--features ab_glyph`
ab_glyph = { version = "0.2.17", optional = true }
# Draw title text using cosmic-text `--features cosmic-text`
cosmic-text = { version = "0.7.0", optional = true }

[features]
default = ["ab_glyph"]
cosmic-text = ["once_cell", "dep:cosmic-text"]
46 changes: 40 additions & 6 deletions src/title.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,40 @@ mod font_preference;
#[cfg(feature = "crossfont")]
mod crossfont_renderer;

#[cfg(all(not(feature = "crossfont"), feature = "ab_glyph"))]
#[cfg(feature = "cosmic-text")]
mod cosmic_text_renderer;

#[cfg(all(
not(feature = "crossfont"),
not(feature = "cosmic-text"),
feature = "ab_glyph"
))]
mod ab_glyph_renderer;

#[cfg(all(not(feature = "crossfont"), not(feature = "ab_glyph")))]
#[cfg(all(
not(feature = "crossfont"),
not(feature = "cosmic-text"),
not(feature = "ab_glyph")
))]
mod dumb;

#[derive(Debug)]
pub struct TitleText {
#[cfg(feature = "crossfont")]
imp: crossfont_renderer::CrossfontTitleText,
#[cfg(all(not(feature = "crossfont"), feature = "ab_glyph"))]
#[cfg(feature = "cosmic-text")]
imp: cosmic_text_renderer::CosmicTextTitleText,
#[cfg(all(
not(feature = "crossfont"),
not(feature = "cosmic-text"),
feature = "ab_glyph"
))]
imp: ab_glyph_renderer::AbGlyphTitleText,
#[cfg(all(not(feature = "crossfont"), not(feature = "ab_glyph")))]
#[cfg(all(
not(feature = "crossfont"),
not(feature = "cosmic-text"),
not(feature = "ab_glyph")
))]
imp: dumb::DumbTitleText,
}

Expand All @@ -31,12 +52,25 @@ impl TitleText {
.ok()
.map(|imp| Self { imp });

#[cfg(all(not(feature = "crossfont"), feature = "ab_glyph"))]
#[cfg(feature = "cosmic-text")]
return Some(Self {
imp: cosmic_text_renderer::CosmicTextTitleText::new(color),
});

#[cfg(all(
not(feature = "crossfont"),
not(feature = "cosmic-text"),
feature = "ab_glyph"
))]
return Some(Self {
imp: ab_glyph_renderer::AbGlyphTitleText::new(color),
});

#[cfg(all(not(feature = "crossfont"), not(feature = "ab_glyph")))]
#[cfg(all(
not(feature = "crossfont"),
not(feature = "cosmic-text"),
not(feature = "ab_glyph")
))]
{
let _ = color;
return None;
Expand Down
139 changes: 139 additions & 0 deletions src/title/cosmic_text_renderer.rs
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()
}

0 comments on commit 469fe18

Please sign in to comment.