Skip to content

Instantly share code, notes, and snippets.

@fundon
Last active June 27, 2026 10:04
Show Gist options
  • Select an option

  • Save fundon/2fbf7f6b5d685bb8ed87174fcdafe867 to your computer and use it in GitHub Desktop.

Select an option

Save fundon/2fbf7f6b5d685bb8ed87174fcdafe867 to your computer and use it in GitHub Desktop.
glifo + parley + vello_cpu example
[package]
name = "glifo-example"
version = "0.1.0"
edition = "2024"
[dependencies]
skrifa = { version = "0.42.0", default-features = false, features = ["autohint_shaping"] }
parley = { version = "0.10.0", default-features = false, features = ["system", "accesskit"] }
glifo = { version = "0.1.1", default-features = false, git = "https://github.com/fundon/vello.git", branch = "vello_cpu_supports_custom_atlas_size" }
vello_cpu = { version = "0.0.9", default-features = false, features = ["png", "text", "f32_pipeline", "multithreading"], git = "https://github.com/fundon/vello.git", branch = "vello_cpu_supports_custom_atlas_size" }
smallvec = { version = "1.15.1", features = ["const_new"] }
use std::sync::Arc;
use glifo::atlas::key::quantize_subpixel;
use glifo::{GlyphCacheKey, GlyphRenderer};
use parley::{
Alignment, AlignmentOptions, FontFamily, GenericFamily, Layout, LineHeight,
PositionedLayoutItem, StyleProperty,
};
use parley::{FontContext, FontFamilyName, LayoutContext};
use skrifa::metrics::GlyphMetrics;
use skrifa::prelude::{LocationRef, NormalizedCoord, Size};
use skrifa::raw::TableProvider;
use skrifa::{FontRef, MetadataProvider};
use smallvec::SmallVec;
use vello_cpu::color::palette::css::WHITE;
use vello_cpu::kurbo::{Affine, Rect, Vec2};
use vello_cpu::peniko::{Color, Extend, ImageSampler};
use vello_cpu::{
Glyph, Image, ImageSource, Level, Pixmap, RasterizerSettings, RenderContext, RenderMode,
RenderSettings, Resources,
};
const TEXT: &str = "Hello 😂🤣😭😜🤪😇👁👄🫦👅🧔🕴💃 world\nHello 😂🤣😭😜🤪😇👁👄🫦👅🧔🕴💃 world";
const FONT_SIZE: f32 = 32.0;
const OUTPUT_PATH: &str = "glifo_colr_position.png";
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ColorBrush {
pub color: Color,
}
impl Default for ColorBrush {
fn default() -> Self {
Self {
color: Color::BLACK,
}
}
}
fn main() {
let width = 800;
let height = 400;
let mut ctx = RenderContext::new_with(
width,
height,
RenderSettings {
level: Level::new(),
num_threads: 0,
},
);
let mut resources = Resources::new();
ctx.set_paint(WHITE);
ctx.fill_rect(&Rect::new(0.0, 0.0, f64::from(width), f64::from(height)));
let mut atlas_ctx = RenderContext::new_with(
2048,
2048,
RenderSettings {
level: Level::new(),
num_threads: 0,
},
);
let mut layout_ctx = LayoutContext::<ColorBrush>::new();
let mut font_ctx = FontContext::new();
font_ctx.collection.load_system_fonts();
draw_text(
&mut ctx,
&mut atlas_ctx,
&mut resources,
&mut layout_ctx,
&mut font_ctx,
TEXT,
);
ctx.flush();
let mut pixmap = Pixmap::new(width, height);
ctx.render_with(
&mut pixmap,
&mut Resources::default(),
RasterizerSettings {
render_mode: RenderMode::OptimizeSpeed,
..Default::default()
},
);
std::fs::write(OUTPUT_PATH, pixmap.into_png().unwrap()).expect("write output PNG");
println!("wrote {OUTPUT_PATH}");
println!("text: {TEXT}");
let Some(pixmaps) = resources.pixmaps() else {
return;
};
for (index, pixmap) in pixmaps.iter().enumerate() {
{
let dir = std::env::current_dir().unwrap();
let dir = dir.to_string_lossy();
let atlas_pixmap =
Pixmap::from_parts(pixmap.data().to_vec(), pixmap.width(), pixmap.height());
let data = atlas_pixmap.into_png().unwrap();
std::fs::write(format!("{dir}/target/atlas-{}.png", index), data).unwrap();
}
}
}
fn draw_text(
ctx: &mut RenderContext,
atlas_ctx: &mut RenderContext,
resources: &mut Resources,
layout_ctx: &mut LayoutContext<ColorBrush>,
font_ctx: &mut FontContext,
text: &str,
) {
let mut builder = layout_ctx.ranged_builder(font_ctx, text, 1.0, true);
builder.push_default(FontFamily::from(
[
GenericFamily::SystemUi.into(),
FontFamilyName::named("Fluent Emoji Color"),
// FontFamilyName::named("Noto Color Emoji"),
]
.as_slice(),
));
builder.push_default(LineHeight::FontSizeRelative(1.2));
builder.push_default(StyleProperty::FontSize(FONT_SIZE));
let mut layout: Layout<ColorBrush> = builder.build(text);
layout.break_all_lines(Some(ctx.width() as f32));
layout.align(Alignment::Start, AlignmentOptions::default());
for line in layout.lines() {
for item in line.items() {
match item {
PositionedLayoutItem::GlyphRun(glyph_run) => {
let run = glyph_run.run();
let font = run.font();
let font_data = &font.data;
let font_id = font_data.id();
let font_index = font.index;
let font_ref = FontRef::from_index(font_data.data(), font_index).unwrap();
let is_color = font_ref.colr().is_ok();
let scale = FONT_SIZE / font_ref.head().unwrap().units_per_em() as f32;
let normalized_coords = run
.normalized_coords()
.iter()
.copied()
.map(NormalizedCoord::from_bits)
.collect::<Vec<_>>();
let location_ref = LocationRef::new(&normalized_coords);
let positioned_glyphs = glyph_run.positioned_glyphs();
let mut temp_glyph_cache_key = GlyphCacheKey {
font_id,
font_index,
hinted: !is_color,
size_bits: FONT_SIZE.to_bits(),
var_coords: SmallVec::from_slice(&normalized_coords),
..GlyphCacheKey::DEFAULT
};
if is_color {
temp_glyph_cache_key = temp_glyph_cache_key.to_colr();
}
let glifo_run = atlas_ctx
.glyph_run(resources, font)
.font_size(FONT_SIZE)
.hint(!is_color)
.atlas_cache(true);
glifo_run.fill_glyphs(positioned_glyphs.clone().map(|glyph| Glyph {
id: glyph.id,
x: glyph.x,
y: glyph.y,
}));
// Upload glyphs into the atlas cache.
resources.prepare_glyph_cache(RenderMode::OptimizeSpeed);
resources.maintain_glyph_cache();
// Clear the tick so the old glyphs are not replaced with the new ones.
// Keep the old glyphs in the cache.
if let Some(glyph_atlas) = resources.glyph_atlas_mut() {
glyph_atlas.clear_tick();
}
for glyph in positioned_glyphs {
let mut glyph_cache_key = GlyphCacheKey {
glyph_id: glyph.id,
..temp_glyph_cache_key.clone()
};
if !is_color {
glyph_cache_key.subpixel_x = quantize_subpixel(glyph.x.fract());
}
let Some(pixmaps) = resources.pixmaps() else {
return;
};
let Some(glyph_atlas) = resources.glyph_atlas_mut() else {
continue;
};
let Some(atlas_slot) = glyph_atlas.get(&glyph_cache_key) else {
continue;
};
let Some(pixmap) = pixmaps.get(atlas_slot.page_index as usize) else {
continue;
};
let mut glyph_pixmap = Pixmap::new(atlas_slot.width, atlas_slot.height);
copy_pixmap_from_atlas(
&pixmap,
&mut glyph_pixmap,
atlas_slot.x,
atlas_slot.y,
);
#[cfg(debug_assertions)]
{
let dir = std::env::current_dir().unwrap();
let dir = dir.to_string_lossy();
if let Ok(png) = glyph_pixmap.clone().into_png() {
std::fs::write(format!("{dir}/target/glyph-{}.png", glyph.id), png)
.unwrap();
}
}
let image = Image {
image: ImageSource::Pixmap(Arc::new(glyph_pixmap)),
sampler: ImageSampler {
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: vello_cpu::peniko::ImageQuality::Medium,
alpha: 1.0,
},
};
let pos = Vec2::new(glyph.x as f64, glyph.y as f64);
let bearing =
Vec2::new(atlas_slot.bearing_x as f64, atlas_slot.bearing_y as f64);
let transform = Affine::translate(pos + bearing);
let state = ctx.save_state();
if is_color {
let color_glyphs = font_ref.color_glyphs();
let bounds = color_glyphs
.get(glyph.id.into())
.unwrap()
// For COLRv1 glyphs
.bounding_box(location_ref, Size::unscaled())
.or_else(|| {
// For COLRv0 glyphs
let metrics = GlyphMetrics::new(
&font_ref,
Size::unscaled(),
location_ref,
);
metrics.bounds(glyph.id.into())
})
.unwrap()
.scale(scale);
let tx = bounds.x_min as f64;
let ty = bounds.y_min as f64;
ctx.set_transform(
transform
.then_translate(Vec2::new(tx, -ty))
.pre_scale_non_uniform(1.0, -1.0),
);
} else {
ctx.set_transform(transform);
}
ctx.set_paint_image(image);
ctx.fill_rect(&Rect {
x0: 0.0,
y0: 0.0,
x1: atlas_slot.width as f64,
y1: atlas_slot.height as f64,
});
ctx.restore_state(state);
}
}
_ => {}
}
}
}
}
fn copy_pixmap_from_atlas(src: &Pixmap, dst: &mut Pixmap, src_x: u16, src_y: u16) {
let src_stride = src.width() as usize;
let copy_width = dst.width() as usize;
let copy_height = dst.height() as usize;
let dst_stride = copy_width;
let src_data = src.data_as_u8_slice();
let dst_data = dst.data_as_u8_slice_mut();
for y in 0..copy_height {
let dst_row_start = y * dst_stride * 4;
let dst_row_end = dst_row_start + copy_width * 4;
let src_row_start = ((src_y as usize + y) * src_stride + src_x as usize) * 4;
let src_row_end = src_row_start + copy_width * 4;
dst_data[dst_row_start..dst_row_end].copy_from_slice(&src_data[src_row_start..src_row_end]);
}
}
use std::sync::Arc;
use glifo::atlas::key::quantize_subpixel;
use glifo::{GlyphCacheKey, GlyphRenderer};
use parley::{
Alignment, AlignmentOptions, FontFamily, GenericFamily, Layout, LineHeight,
PositionedLayoutItem, StyleProperty,
};
use parley::{FontContext, FontFamilyName, LayoutContext};
use skrifa::FontRef;
use skrifa::prelude::NormalizedCoord;
use skrifa::raw::TableProvider;
use smallvec::SmallVec;
use vello_cpu::color::palette::css::WHITE;
use vello_cpu::kurbo::{Affine, Rect, Vec2};
use vello_cpu::peniko::{Color, Extend, ImageSampler};
use vello_cpu::{
Glyph, Image, ImageSource, Level, Pixmap, RasterizerSettings, RenderContext, RenderMode,
RenderSettings, Resources,
};
const TEXT: &str = "Hello 😂🤣😭😜🤪😇👁👄🫦👅🧔🕴💃 world\nHello 😂🤣😭😜🤪😇👁👄🫦👅🧔🕴💃 world";
const FONT_SIZE: f32 = 32.0;
const OUTPUT_PATH: &str = "glifo_colr_position.png";
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ColorBrush {
pub color: Color,
}
impl Default for ColorBrush {
fn default() -> Self {
Self {
color: Color::BLACK,
}
}
}
fn main() {
let width = 800;
let height = 400;
let mut ctx = RenderContext::new_with(
width,
height,
RenderSettings {
level: Level::new(),
num_threads: 0,
},
);
let mut resources = Resources::new();
ctx.set_paint(WHITE);
ctx.fill_rect(&Rect::new(0.0, 0.0, f64::from(width), f64::from(height)));
let mut atlas_ctx = RenderContext::new_with(
2048,
2048,
RenderSettings {
level: Level::new(),
num_threads: 0,
},
);
let mut layout_ctx = LayoutContext::<ColorBrush>::new();
let mut font_ctx = FontContext::new();
font_ctx.collection.load_system_fonts();
draw_text(
&mut ctx,
&mut atlas_ctx,
&mut resources,
&mut layout_ctx,
&mut font_ctx,
TEXT,
);
ctx.flush();
let mut pixmap = Pixmap::new(width, height);
ctx.render_with(
&mut pixmap,
&mut Resources::default(),
RasterizerSettings {
render_mode: RenderMode::OptimizeSpeed,
..Default::default()
},
);
std::fs::write(OUTPUT_PATH, pixmap.into_png().unwrap()).expect("write output PNG");
println!("wrote {OUTPUT_PATH}");
println!("text: {TEXT}");
let Some(pixmaps) = resources.pixmaps() else {
return;
};
for (index, pixmap) in pixmaps.iter().enumerate() {
{
let dir = std::env::current_dir().unwrap();
let dir = dir.to_string_lossy();
let atlas_pixmap =
Pixmap::from_parts(pixmap.data().to_vec(), pixmap.width(), pixmap.height());
let data = atlas_pixmap.into_png().unwrap();
std::fs::write(format!("{dir}/target/atlas-{}.png", index), data).unwrap();
}
}
}
fn draw_text(
ctx: &mut RenderContext,
atlas_ctx: &mut RenderContext,
resources: &mut Resources,
layout_ctx: &mut LayoutContext<ColorBrush>,
font_ctx: &mut FontContext,
text: &str,
) {
let mut builder = layout_ctx.ranged_builder(font_ctx, text, 1.0, true);
builder.push_default(FontFamily::from(
[
GenericFamily::SystemUi.into(),
// FontFamilyName::named("Fluent Emoji Color"),
FontFamilyName::named("Noto Color Emoji"),
]
.as_slice(),
));
builder.push_default(LineHeight::FontSizeRelative(1.2));
builder.push_default(StyleProperty::FontSize(FONT_SIZE));
let mut layout: Layout<ColorBrush> = builder.build(text);
layout.break_all_lines(Some(ctx.width() as f32));
layout.align(Alignment::Start, AlignmentOptions::default());
for line in layout.lines() {
// let line_height = line.metrics().line_height as f64;
for item in line.items() {
match item {
PositionedLayoutItem::GlyphRun(glyph_run) => {
let run = glyph_run.run();
let font = run.font();
let font_data = &font.data;
let font_id = font_data.id();
let font_index = font.index;
let font_ref = FontRef::from_index(font_data.data(), font_index).unwrap();
let is_color = font_ref.colr().is_ok();
let normalized_coords = run
.normalized_coords()
.iter()
.copied()
.map(NormalizedCoord::from_bits)
.collect::<Vec<_>>();
let positioned_glyphs = glyph_run.positioned_glyphs();
let mut temp_glyph_cache_key = GlyphCacheKey {
font_id,
font_index,
hinted: !is_color,
size_bits: FONT_SIZE.to_bits(),
var_coords: SmallVec::from_slice(&normalized_coords),
..GlyphCacheKey::DEFAULT
};
if is_color {
temp_glyph_cache_key = temp_glyph_cache_key.to_colr();
}
atlas_ctx
.glyph_run(resources, font)
.font_size(FONT_SIZE)
.hint(!is_color)
.atlas_cache(true)
.fill_glyphs(positioned_glyphs.clone().map(|glyph| Glyph {
id: glyph.id,
x: glyph.x,
y: glyph.y,
}));
// Upload glyphs into the atlas cache.
resources.prepare_glyph_cache(RenderMode::OptimizeSpeed);
resources.maintain_glyph_cache();
// Clear the tick so the old glyphs are not replaced with the new ones.
// Keep the old glyphs in the cache.
if let Some(glyph_atlas) = resources.glyph_atlas_mut() {
glyph_atlas.clear_tick();
}
for glyph in positioned_glyphs {
let mut glyph_cache_key = GlyphCacheKey {
glyph_id: glyph.id,
..temp_glyph_cache_key.clone()
};
if !is_color {
glyph_cache_key.subpixel_x = quantize_subpixel(glyph.x.fract());
}
let Some(pixmaps) = resources.pixmaps() else {
return;
};
let Some(glyph_atlas) = resources.glyph_atlas_mut() else {
continue;
};
let Some(atlas_slot) = glyph_atlas.get(&glyph_cache_key) else {
continue;
};
let Some(pixmap) = pixmaps.get(atlas_slot.page_index as usize) else {
continue;
};
let mut glyph_pixmap = Pixmap::new(atlas_slot.width, atlas_slot.height);
copy_pixmap_from_atlas(
&pixmap,
&mut glyph_pixmap,
atlas_slot.x,
atlas_slot.y,
);
#[cfg(debug_assertions)]
{
let dir = std::env::current_dir().unwrap();
let dir = dir.to_string_lossy();
if let Ok(png) = glyph_pixmap.clone().into_png() {
std::fs::write(format!("{dir}/target/glyph-{}.png", glyph.id), png)
.unwrap();
}
}
let image = Image {
image: ImageSource::Pixmap(Arc::new(glyph_pixmap)),
sampler: ImageSampler {
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: vello_cpu::peniko::ImageQuality::Medium,
alpha: 1.0,
},
};
let pos = Vec2::new(glyph.x as f64, glyph.y as f64);
let bearing =
Vec2::new(atlas_slot.bearing_x as f64, atlas_slot.bearing_y as f64);
let transform = Affine::translate(pos + bearing);
let state = ctx.save_state();
if is_color {
ctx.set_transform(
transform
* Affine::scale_non_uniform(1.0, -1.0)
* Affine::translate(Vec2::new(
0.0,
atlas_slot.height as f64 * -1.0,
)),
);
} else {
ctx.set_transform(transform);
}
ctx.set_paint_image(image);
ctx.fill_rect(&Rect {
x0: 0.0,
y0: 0.0,
x1: atlas_slot.width as f64,
y1: atlas_slot.height as f64,
});
ctx.restore_state(state);
}
}
_ => {}
}
}
}
}
fn copy_pixmap_from_atlas(src: &Pixmap, dst: &mut Pixmap, src_x: u16, src_y: u16) {
let src_stride = src.width() as usize;
let copy_width = dst.width() as usize;
let copy_height = dst.height() as usize;
let dst_stride = copy_width;
let src_data = src.data_as_u8_slice();
let dst_data = dst.data_as_u8_slice_mut();
for y in 0..copy_height {
let dst_row_start = y * dst_stride * 4;
let dst_row_end = dst_row_start + copy_width * 4;
let src_row_start = ((src_y as usize + y) * src_stride + src_x as usize) * 4;
let src_row_end = src_row_start + copy_width * 4;
dst_data[dst_row_start..dst_row_end].copy_from_slice(&src_data[src_row_start..src_row_end]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment