Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cosmic-text as an option for the text renderer #16

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@ tiny-skia = { version = "0.11", default-features = false, features = [
"simd",
] }
smithay-client-toolkit = { version = "0.18.0", default_features = false }
once_cell = { version = "1.16.0", optional = true }

# Draw title text using crossfont `--features crossfont`
crossfont = { version = "0.7.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.10.0", optional = true }

[features]
default = ["ab_glyph"]
crossfont = ["dep:crossfont"]
ab_glyph = ["dep:ab_glyph", "memmap2"]
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
141 changes: 141 additions & 0 deletions src/title/cosmic_text_renderer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use crate::title::{config, font_preference::FontPreference};
use cosmic_text::{
Attrs, AttrsList, BufferLine, Color as CosmicColor, Family, FontSystem, Metrics, Shaping,
SwashCache, Wrap,
};
use tiny_skia::{Color, ColorU8, Pixmap, PremultipliedColorU8};

// Use arbitrarily large width, then calculate max/min x from resulting glyphs
const MAX_WIDTH: f32 = 1024.0 * 1024.0;

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,
color: Color,
scale: u32,
pixmap: Option<Pixmap>,
font_pref: FontPreference,
font_system: FontSystem,
}

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()), Shaping::Advanced);
let cache = SwashCache::new();
let font_pref = config::titlebar_font().unwrap_or_default();
Self {
buffer_line,
cache,
color,
scale: 1,
pixmap: None,
font_pref,
font_system: FontSystem::new(),
}
}

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(&mut self.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 as f32, MAX_WIDTH, Wrap::Word, None);
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 physical_glyph = glyph.physical((0., 0.), 1.); // TODO scale
self.cache.with_pixels(
&mut self.font_system,
physical_glyph.cache_key,
color,
|pixel_x, pixel_y, color| {
if color.a() == 0 {
return;
}

let y = height + physical_glyph.y + pixel_y;
let x = physical_glyph.x + 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()
}