Skip to content

Commit

Permalink
WIP: Signed DistanceField font rendering in swrenderer
Browse files Browse the repository at this point in the history
  • Loading branch information
ogoffart committed Oct 17, 2024
1 parent dd74112 commit 9585dcd
Show file tree
Hide file tree
Showing 13 changed files with 244 additions and 138 deletions.
10 changes: 7 additions & 3 deletions internal/compiler/embedded_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,14 @@ pub struct BitmapGlyph {
pub width: i16,
pub height: i16,
pub x_advance: i16,
pub data: Vec<u8>, // 8bit alpha map or SDF if `BitMapGlyphs`'s pixel_size is 0.
/// 8bit alpha map or SDF if `BitMapGlyphs`'s `sdf` is `true`.
pub data: Vec<u8>,
}

#[cfg(feature = "software-renderer")]
#[derive(Debug, Clone)]
pub struct BitmapGlyphs {
pub pixel_size: i16, // pixel size 0 means it's a scalable font (via SDF).
pub pixel_size: i16,
pub glyph_data: Vec<BitmapGlyph>,
}

Expand All @@ -74,7 +75,8 @@ pub struct CharacterMapEntry {
#[derive(Debug, Clone)]
pub struct BitmapFont {
pub family_name: String,
pub character_map: Vec<CharacterMapEntry>, // map of available glyphs, sorted by char
/// map of available glyphs, sorted by char
pub character_map: Vec<CharacterMapEntry>,
pub units_per_em: f32,
pub ascent: f32,
pub descent: f32,
Expand All @@ -83,6 +85,8 @@ pub struct BitmapFont {
pub glyphs: Vec<BitmapGlyphs>,
pub weight: u16,
pub italic: bool,
/// true when the font is represented as a signed distance field
pub sdf: bool,
}

#[derive(Debug, Clone)]
Expand Down
2 changes: 2 additions & 0 deletions internal/compiler/generator/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,7 @@ fn embed_resource(
glyphs,
weight,
italic,
sdf,
},
) => {
let family_name_var = format!("slint_embedded_resource_{}_family_name", resource.id);
Expand Down Expand Up @@ -1042,6 +1043,7 @@ fn embed_resource(
.glyphs = slint::cbindgen_private::Slice<slint::cbindgen_private::BitmapGlyphs>{{ {glyphsets_var}, {glyphsets_size} }},
.weight = {weight},
.italic = {italic},
.sdf = {sdf},
}}"
);

Expand Down
3 changes: 2 additions & 1 deletion internal/compiler/generator/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3005,7 +3005,7 @@ fn generate_resources(doc: &Document) -> Vec<TokenStream> {
)
},
#[cfg(feature = "software-renderer")]
crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(crate::embedded_resources::BitmapFont { family_name, character_map, units_per_em, ascent, descent, x_height, cap_height, glyphs, weight, italic }) => {
crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(crate::embedded_resources::BitmapFont { family_name, character_map, units_per_em, ascent, descent, x_height, cap_height, glyphs, weight, italic, sdf }) => {

let character_map_size = character_map.len();

Expand Down Expand Up @@ -3066,6 +3066,7 @@ fn generate_resources(doc: &Document) -> Vec<TokenStream> {
}),
weight: #weight,
italic: #italic,
sdf: #sdf,
};
)
},
Expand Down
44 changes: 27 additions & 17 deletions internal/compiler/passes/embed_glyphs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ fn embed_font(
glyphs,
weight: face_info.weight.0,
italic: face_info.style != fontdb::Style::Normal,
sdf: cfg!(feature = "embed-glyphs-as-sdf"),
}
}

Expand Down Expand Up @@ -441,7 +442,7 @@ fn embed_sdf_glyphs(
font: &Font,
fallback_fonts: &[Font],
) -> Vec<BitmapGlyphs> {
let Some(target_pixel_size) = pixel_sizes.iter().max().map(|max_size| max_size / 2) else {
let Some(target_pixel_size) = pixel_sizes.iter().max().map(|max_size| max_size * 2 / 3) else {
return vec![];
};

Expand Down Expand Up @@ -471,7 +472,7 @@ fn embed_sdf_glyphs(
glyph_data[*glyph_index as usize] = glyph;
}

vec![BitmapGlyphs { pixel_size: 0 /* indicates SDF */, glyph_data }]
vec![BitmapGlyphs { pixel_size: target_pixel_size, glyph_data }]
}

#[cfg(all(not(target_arch = "wasm32"), feature = "embed-glyphs-as-sdf"))]
Expand All @@ -493,19 +494,16 @@ fn generate_sdf_for_glyph(

let metrics = font.metrics();

const RANGE: f64 = 256.0;
const RANGE: f64 = 2.;
let target_pixel_size = target_pixel_size as f64;
let scale = target_pixel_size / metrics.units_per_em as f64;
let width = ((bbox.x_max as f64 - bbox.x_min as f64) * scale + 2. * RANGE).ceil() as u32;
let height = ((bbox.y_max as f64 - bbox.y_min as f64) * scale + 2. * RANGE).ceil() as u32;
let transformation = nalgebra::convert::<_, Affine2<f64>>(Similarity2::new(
Vector2::new(
target_pixel_size - (bbox.x_min as f64 * scale),
target_pixel_size - (bbox.y_min as f64 * scale),
),
0.0,
Vector2::new(RANGE - bbox.x_min as f64 * scale, RANGE - bbox.y_min as f64 * scale),
0.,
scale,
));
let width = ((bbox.x_max as f64 - bbox.x_min as f64) * scale).ceil() as u32;
let height = ((bbox.y_max as f64 - bbox.y_min as f64) * scale).ceil() as u32;

// Unlike msdfgen, the transformation is not passed into the
// `generate_msdf` function – the coordinates of the control points
Expand All @@ -526,17 +524,29 @@ fn generate_sdf_for_glyph(
fdsm::bezier::scanline::FillRule::Nonzero,
);

let glyph_data = sdf.into_raw();
let mut glyph_data = sdf.into_raw();

// normalize around 0
for x in &mut glyph_data {
*x = x.wrapping_sub(128);
}

// invert the y coordinate (as the fsdm crate has the y axis inverted)
let (w, h) = (width as usize, height as usize);
for idx in 0..glyph_data.len() / 2 {
glyph_data.swap(idx, (h - idx / w - 1) * w + idx % w);
}

// Add a "0" so that we can always access pos+1 without going out of bound
// (so that the last row will look like `data[len-1]*1 + data[len]*0`)
glyph_data.push(0);

let metrics = fontdue::Metrics {
// NOTE! This is the x pos in font design space, so it needs to be multiplied by the target size and divided by units per em.
xmin: bbox.x_min as _,
// NOTE! This is the y pos in font design space, so it needs to be multiplied by the target size and divided by units per em.
ymin: bbox.y_min as _,
xmin: -(RANGE - bbox.x_min as f64 * scale).floor() as i32,
ymin: -(RANGE - bbox.y_min as f64 * scale).floor() as i32,
width: width as usize,
height: height as usize,
// NOTE! This is the advance in font design space, so it needs to be multiplied by the target size and divided by units per em.
advance_width: face.glyph_hor_advance(glyph_id).unwrap() as _,
advance_width: (face.glyph_hor_advance(glyph_id).unwrap() as f64 * scale) as f32,
advance_height: 0., /*unused */
bounds: Default::default(), /*unused */
};
Expand Down
3 changes: 3 additions & 0 deletions internal/core/graphics/bitmapfont.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct BitmapGlyph {
/// The horizontal distance to the next glyph
pub x_advance: i16,
/// The 8-bit alpha map that's to be blended with the current text color
/// or 8-bit signed distance field depending on `BitmapFont::sdf`
pub data: Slice<'static, u8>,
}

Expand Down Expand Up @@ -67,4 +68,6 @@ pub struct BitmapFont {
pub weight: u16,
/// Whether the type-face is rendered italic.
pub italic: bool,
/// Whether the format of the font is a signed distance field
pub sdf: bool,
}
6 changes: 6 additions & 0 deletions internal/core/graphics/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ pub enum PixelFormat {
RgbaPremultiplied,
/// Alpha map. 8bits. Each pixel is an alpha value. The color is specified separately.
AlphaMap,
/// Distance field. 8bit interpreted as i8
SignedDistanceField,
}

impl PixelFormat {
Expand All @@ -244,6 +246,7 @@ impl PixelFormat {
PixelFormat::Rgba => 4,
PixelFormat::RgbaPremultiplied => 4,
PixelFormat::AlphaMap => 1,
PixelFormat::SignedDistanceField => 1,
}
}
}
Expand Down Expand Up @@ -473,6 +476,9 @@ impl ImageInner {
});
slice.fill_with(|| iter.next().unwrap());
}
PixelFormat::SignedDistanceField => {
todo!("converting from a signed distance field to an image")
}
};
}
}
Expand Down
151 changes: 88 additions & 63 deletions internal/core/software_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1499,7 +1499,7 @@ impl<'a, T: ProcessScene> SceneBuilder<'a, T> {
for positioned_glyph in glyphs {
let glyph = paragraph.layout.font.render_glyph(positioned_glyph.glyph_id);

let src_rect = PhysicalRect::new(
let target_rect = PhysicalRect::new(
PhysicalPoint::from_lengths(
line_x + positioned_glyph.x + glyph.x,
baseline_y - glyph.y - glyph.height,
Expand All @@ -1515,71 +1515,96 @@ impl<'a, T: ProcessScene> SceneBuilder<'a, T> {
_ => color,
};

if let Some(clipped_src) = src_rect.intersection(&physical_clip) {
let geometry = clipped_src.translate(offset).round();
let origin = (geometry.origin - offset.round()).round().cast::<i16>();
let actual_x = (origin.x - src_rect.origin.x as i16) as usize;
let actual_y = (origin.y - src_rect.origin.y as i16) as usize;
let pixel_stride = glyph.width.get() as u16;
let mut geometry = geometry.cast();
if geometry.size.width > glyph.width.get() - (actual_x as i16) {
geometry.size.width = glyph.width.get() - (actual_x as i16)
}
if geometry.size.height > glyph.height.get() - (actual_y as i16) {
geometry.size.height = glyph.height.get() - (actual_y as i16)
}
let source_size = geometry.size;
if source_size.is_empty() {
continue;
let Some(clipped_target) = physical_clip.intersection(&target_rect) else {
continue;
};
let geometry = clipped_target.translate(offset).round();
let origin = (geometry.origin - offset.round()).round().cast::<i16>();
let actual_x = (origin.x - target_rect.origin.x as i16) as usize;
let actual_y = (origin.y - target_rect.origin.y as i16) as usize;
let pixel_stride = glyph.pixel_stride;
let mut geometry = geometry.cast();
if geometry.size.width > glyph.width.get() - (actual_x as i16) {
geometry.size.width = glyph.width.get() - (actual_x as i16)
}
if geometry.size.height > glyph.height.get() - (actual_y as i16) {
geometry.size.height = glyph.height.get() - (actual_y as i16)
}
let source_size = geometry.size;
if source_size.is_empty() {
continue;
}
match &glyph.alpha_map {
fonts::GlyphAlphaMap::Static(data) => {
let texture = if !glyph.sdf {
SceneTexture {
data: &data[actual_x + actual_y * pixel_stride as usize..],
pixel_stride,
format: PixelFormat::AlphaMap,
extra: SceneTextureExtra {
colorize: color,
// color already is mixed with global alpha
alpha: color.alpha(),
rotation: self.rotation.orientation,
dx: Fixed::from_integer(1),
dy: Fixed::from_integer(1),
off_x: Fixed::from_integer(0),
off_y: Fixed::from_integer(0),
},
}
} else {
let dx = Fixed::from_integer(pixel_stride - 1)
/ glyph.width.get() as u16;
let dy = Fixed::from_integer((data.len() as u16 - 1) / pixel_stride - 1)
/ glyph.height.get() as u16;
let off_x = Fixed::<i32, 8>::from_fixed(dx)
* (clipped_target.origin.x - target_rect.origin.x) as i32;
let off_y = Fixed::<i32, 8>::from_fixed(dy)
* (clipped_target.origin.y - target_rect.origin.y) as i32;
SceneTexture {
data: data,
pixel_stride,
format: PixelFormat::SignedDistanceField,
extra: SceneTextureExtra {
colorize: color,
// color already is mixed with global alpha
alpha: color.alpha(),
rotation: self.rotation.orientation,
dx,
dy,
off_x: Fixed::try_from_fixed(off_x).unwrap(),
off_y: Fixed::try_from_fixed(off_y).unwrap(),
},
}
};
self.processor
.process_texture(geometry.transformed(self.rotation), texture);
}
match &glyph.alpha_map {
fonts::GlyphAlphaMap::Static(data) => {
self.processor.process_texture(
geometry.transformed(self.rotation),
SceneTexture {
data: &data
[actual_x + actual_y * pixel_stride as usize..],
pixel_stride,
format: PixelFormat::AlphaMap,
extra: SceneTextureExtra {
colorize: color,
// color already is mixed with global alpha
alpha: color.alpha(),
rotation: self.rotation.orientation,
dx: Fixed::from_integer(1),
dy: Fixed::from_integer(1),
off_x: Fixed::from_integer(0),
off_y: Fixed::from_integer(0),
},
fonts::GlyphAlphaMap::Shared(data) => {
self.processor.process_shared_image_buffer(
geometry.transformed(self.rotation),
SharedBufferCommand {
buffer: SharedBufferData::AlphaMap {
data: data.clone(),
width: pixel_stride,
},
);
}
fonts::GlyphAlphaMap::Shared(data) => {
self.processor.process_shared_image_buffer(
geometry.transformed(self.rotation),
SharedBufferCommand {
buffer: SharedBufferData::AlphaMap {
data: data.clone(),
width: pixel_stride,
},
source_rect: PhysicalRect::new(
PhysicalPoint::new(actual_x as _, actual_y as _),
source_size,
),
extra: SceneTextureExtra {
colorize: color,
// color already is mixed with global alpha
alpha: color.alpha(),
rotation: self.rotation.orientation,
dx: Fixed::from_integer(1),
dy: Fixed::from_integer(1),
off_x: Fixed::from_integer(0),
off_y: Fixed::from_integer(0),
},
source_rect: PhysicalRect::new(
PhysicalPoint::new(actual_x as _, actual_y as _),
source_size,
),
extra: SceneTextureExtra {
colorize: color,
// color already is mixed with global alpha
alpha: color.alpha(),
rotation: self.rotation.orientation,
dx: Fixed::from_integer(1),
dy: Fixed::from_integer(1),
off_x: Fixed::from_integer(0),
off_y: Fixed::from_integer(0),
},
);
}
};
},
);
}
}
}
core::ops::ControlFlow::Continue(())
Expand Down
Loading

0 comments on commit 9585dcd

Please sign in to comment.