Skip to content

Commit

Permalink
Merge pull request #8 from alexheretic/ab-glyph
Browse files Browse the repository at this point in the history
Add `ab_glyph` feature for drawing title text
  • Loading branch information
PolyMeilex authored Aug 14, 2022
2 parents d3d33af + cdf781f commit 59720f0
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 8 deletions.
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ smithay-client-toolkit = "0.16"
tiny-skia = { version = "0.7.0", features = ["std", "simd"] }
log = "0.4"

crossfont = { version = "0.5.0", features = [
"force_system_fontconfig",
], 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.15", optional = true }

[features]
# default = ["title"]
title = ["crossfont"]
default = ["ab_glyph"]
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@

### Dark mode:
![image](https://user-images.githubusercontent.com/20758186/169424673-3b9fa022-f112-4928-8360-305a714ba979.png)

## Title text: ab_glyph
By default title text is drawn with _ab_glyph_ crate. This can be disabled by disabling default features.

## Title text: crossfont
Alternatively title text may be drawn with _crossfont_ crate. This adds a requirement on _freetype_.

```toml
sctk-adwaita = { default-features = false, features = ["crossfont"] }
```
16 changes: 13 additions & 3 deletions src/title.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ use tiny_skia::{Color, Pixmap};
#[cfg(feature = "crossfont")]
mod crossfont_renderer;

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

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

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

Expand All @@ -21,7 +26,12 @@ impl TitleText {
.ok()
.map(|imp| Self { imp });

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

#[cfg(all(not(feature = "crossfont"), not(feature = "ab_glyph")))]
{
let _ = color;
return None;
Expand Down
Binary file added src/title/Cantarell-Regular.ttf
Binary file not shown.
123 changes: 123 additions & 0 deletions src/title/ab_glyph_renderer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//! Title renderer using ab_glyph & Cantarell-Regular.ttf (SIL Open Font Licence v1.1).
//!
//! Uses embedded font & requires no dynamically linked dependencies.
use ab_glyph::{point, Font, FontRef, Glyph, PxScale, ScaleFont};
use tiny_skia::{Color, Pixmap, PremultipliedColorU8};

const CANTARELL: &[u8] = include_bytes!("Cantarell-Regular.ttf");
/// Equivalent to ~10.5pt size, should match the crossfont version fairly well.
const DEFAULT_PX_SIZE: f32 = 19.0;

#[derive(Debug)]
pub struct AbGlyphTitleText {
title: String,
font: FontRef<'static>,
size: PxScale,
color: Color,
pixmap: Option<Pixmap>,
}

impl AbGlyphTitleText {
pub fn new(color: Color) -> Self {
// TODO Try to pick system default font & size then fallback?
let font = FontRef::try_from_slice(CANTARELL).unwrap();
let size = PxScale::from(DEFAULT_PX_SIZE);

Self {
title: <_>::default(),
font,
size,
color,
pixmap: None,
}
}

pub fn update_scale(&mut self, scale: u32) {
let new_scale = PxScale::from(DEFAULT_PX_SIZE * scale as f32);
if (self.size.x - new_scale.x).abs() > f32::EPSILON {
self.size = new_scale;
self.pixmap = self.render();
}
}

pub fn update_title(&mut self, title: impl Into<String>) {
let new_title = title.into();
if new_title != self.title {
self.title = new_title;
self.pixmap = self.render();
}
}

pub fn update_color(&mut self, color: Color) {
if color != self.color {
self.color = color;
self.pixmap = self.render();
}
}

pub fn pixmap(&self) -> Option<&Pixmap> {
self.pixmap.as_ref()
}

/// Render returning the new `Pixmap`.
fn render(&self) -> Option<Pixmap> {
let font = self.font.as_scaled(self.size);

let glyphs = self.layout();
let last_glyph = glyphs.last()?;
let width = (last_glyph.position.x + font.h_advance(last_glyph.id)).ceil() as u32;
let height = font.height().ceil() as u32;

let mut pixmap = Pixmap::new(width, height)?;

let pixels = pixmap.pixels_mut();

for glyph in glyphs {
if let Some(outline) = self.font.outline_glyph(glyph) {
let bounds = outline.px_bounds();
let left = bounds.min.x as u32;
let top = bounds.min.y as u32;
outline.draw(|x, y, c| {
let p_idx = (top + y) * width + (left + x);
let old_alpha_u8 = pixels[p_idx as usize].alpha();
let new_alpha = c + (old_alpha_u8 as f32 / 255.0);
if let Some(px) = PremultipliedColorU8::from_rgba(
(self.color.red() * new_alpha * 255.0) as _,
(self.color.green() * new_alpha * 255.0) as _,
(self.color.blue() * new_alpha * 255.0) as _,
(new_alpha * 255.0) as _,
) {
pixels[p_idx as usize] = px;
}
})
}
}

Some(pixmap)
}

/// Simple single-line glyph layout.
fn layout(&self) -> Vec<Glyph> {
let font = self.font.as_scaled(self.size);

let mut caret = point(0.0, font.ascent());
let mut last_glyph: Option<Glyph> = None;
let mut target = Vec::new();
for c in self.title.chars() {
if c.is_control() {
continue;
}
let mut glyph = font.scaled_glyph(c);
if let Some(previous) = last_glyph.take() {
caret.x += font.kern(previous.id, glyph.id);
}
glyph.position = caret;

last_glyph = Some(glyph.clone());
caret.x += font.h_advance(glyph.id);

target.push(glyph);
}
target
}
}

0 comments on commit 59720f0

Please sign in to comment.