Align glyphs correctly using font-kit's raster_bounds

This commit is contained in:
Antonio Scandurra 2021-03-24 11:45:11 +01:00
parent 7523df05cf
commit e0e4cff815
4 changed files with 104 additions and 69 deletions

View file

@ -1,17 +1,20 @@
use crate::geometry::{ use crate::geometry::{
rect::RectI, rect::RectI,
vector::{vec2f, Vector2F, Vector2I}, transform2d::Transform2F,
vector::{vec2f, Vector2F},
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use cocoa::appkit::CGPoint; use cocoa::appkit::{CGFloat, CGPoint};
use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext}; use core_graphics::{
use parking_lot::{RwLock, RwLockUpgradableReadGuard}; base::CGGlyph, color_space::CGColorSpace, context::CGContext, geometry::CGAffineTransform,
};
pub use font_kit::properties::{Properties, Weight}; pub use font_kit::properties::{Properties, Weight};
use font_kit::{ use font_kit::{
font::Font, loaders::core_text::NativeFont, metrics::Metrics, source::SystemSource, canvas::RasterizationOptions, font::Font, hinting::HintingOptions,
loaders::core_text::NativeFont, metrics::Metrics, source::SystemSource,
}; };
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
@ -193,35 +196,51 @@ impl FontCache {
font_size: f32, font_size: f32,
glyph_id: GlyphId, glyph_id: GlyphId,
scale_factor: f32, scale_factor: f32,
) -> Option<(Vector2I, Vec<u8>)> { ) -> Option<(RectI, Vec<u8>)> {
let native_font = self.native_font(font_id, font_size); let font = self.font(font_id);
let glyph_id = glyph_id as CGGlyph; let scale = Transform2F::from_scale(scale_factor);
let glyph_bounds = let bounds = font
native_font.get_bounding_rects_for_glyphs(Default::default(), &[glyph_id]); .raster_bounds(
let position = CGPoint::new(-glyph_bounds.origin.x, -glyph_bounds.origin.y); glyph_id,
let width = (glyph_bounds.size.width * scale_factor as f64).ceil() as usize; font_size,
let height = (glyph_bounds.size.height * scale_factor as f64).ceil() as usize; scale,
HintingOptions::None,
RasterizationOptions::GrayscaleAa,
)
.ok()?;
if width == 0 || height == 0 { if bounds.width() == 0 || bounds.height() == 0 {
None None
} else { } else {
let mut ctx = CGContext::create_bitmap_context( let mut pixels = vec![0; bounds.width() as usize * bounds.height() as usize];
None, let ctx = CGContext::create_bitmap_context(
width, Some(pixels.as_mut_ptr() as *mut _),
height, bounds.width() as usize,
bounds.height() as usize,
8, 8,
width, bounds.width() as usize,
&CGColorSpace::create_device_gray(), &CGColorSpace::create_device_gray(),
kCGImageAlphaOnly, kCGImageAlphaOnly,
); );
ctx.scale(scale_factor as f64, scale_factor as f64);
native_font.draw_glyphs(&[glyph_id], &[position], ctx.clone());
ctx.flush();
Some(( // Move the origin to bottom left and account for scaling, this
Vector2I::new(width as i32, height as i32), // makes drawing text consistent with the font-kit's raster_bounds.
Vec::from(ctx.data()), ctx.translate(0.0, bounds.height() as CGFloat);
)) let transform = scale.translate(-bounds.origin().to_f32());
ctx.set_text_matrix(&CGAffineTransform {
a: transform.matrix.m11() as CGFloat,
b: -transform.matrix.m21() as CGFloat,
c: -transform.matrix.m12() as CGFloat,
d: transform.matrix.m22() as CGFloat,
tx: transform.vector.x() as CGFloat,
ty: -transform.vector.y() as CGFloat,
});
ctx.set_font(&font.native_font().copy_to_CGFont());
ctx.set_font_size(font_size as CGFloat);
ctx.show_glyphs_at_positions(&[glyph_id as CGGlyph], &[CGPoint::new(0.0, 0.0)]);
Some((bounds, pixels))
} }
} }
@ -290,30 +309,28 @@ fn push_font(state: &mut FontCacheState, font: Font) -> FontId {
font_id font_id
} }
#[cfg(test)] // #[cfg(test)]
mod tests { // mod tests {
use std::{fs::File, io::BufWriter, path::Path}; // use std::{fs::File, io::BufWriter, path::Path};
use super::*; // use super::*;
#[test] // #[test]
fn test_render_glyph() { // fn test_render_glyph() {
let cache = FontCache::new(); // let cache = FontCache::new();
let family_id = cache.load_family(&["Fira Code"]).unwrap(); // let family_id = cache.load_family(&["Fira Code"]).unwrap();
let font_id = cache.select_font(family_id, &Default::default()).unwrap(); // let font_id = cache.select_font(family_id, &Default::default()).unwrap();
let glyph_id = cache.font(font_id).glyph_for_char('m').unwrap(); // let glyph_id = cache.font(font_id).glyph_for_char('G').unwrap();
let (size, bytes) = cache.render_glyph(font_id, 16.0, glyph_id, 1.).unwrap(); // let (bounds, bytes) = cache.render_glyph(font_id, 16.0, glyph_id, 1.).unwrap();
let path = Path::new(r"/Users/as-cii/Desktop/image.png"); // let path = Path::new(r"/Users/as-cii/Desktop/image.png");
let file = File::create(path).unwrap(); // let file = File::create(path).unwrap();
let ref mut w = BufWriter::new(file); // let ref mut w = BufWriter::new(file);
let mut encoder = png::Encoder::new(w, size.x() as u32, size.y() as u32); // let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
encoder.set_color(png::ColorType::Grayscale); // encoder.set_color(png::ColorType::Grayscale);
encoder.set_depth(png::BitDepth::Eight); // encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header().unwrap(); // let mut writer = encoder.write_header().unwrap();
// writer.write_image_data(&bytes).unwrap();
writer.write_image_data(&bytes).unwrap(); // Save // }
dbg!(size, bytes); // }
}
}

View file

@ -1,5 +1,6 @@
use cocoa::foundation::{NSPoint, NSRect, NSSize}; use cocoa::foundation::{NSPoint, NSRect, NSSize};
use pathfinder_geometry::{rect::RectF, vector::Vector2F}; use pathfinder_geometry::{rect::RectF, vector::Vector2F};
pub trait Vector2FExt { pub trait Vector2FExt {
fn to_ns_point(&self) -> NSPoint; fn to_ns_point(&self) -> NSPoint;
fn to_ns_size(&self) -> NSSize; fn to_ns_size(&self) -> NSSize;

View file

@ -267,19 +267,20 @@ impl Renderer {
let mut sprites_by_atlas = HashMap::new(); let mut sprites_by_atlas = HashMap::new();
for glyph in layer.glyphs() { for glyph in layer.glyphs() {
if let Some((atlas, bounds)) = self.sprite_cache.render_glyph( if let Some(sprite) = self.sprite_cache.render_glyph(
glyph.font_id, glyph.font_id,
glyph.font_size, glyph.font_size,
glyph.id, glyph.id,
scene.scale_factor(), scene.scale_factor(),
) { ) {
sprites_by_atlas sprites_by_atlas
.entry(atlas) .entry(sprite.atlas_id)
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(shaders::GPUISprite { .push(shaders::GPUISprite {
origin: (glyph.origin * scene.scale_factor()).to_float2(), origin: (glyph.origin * scene.scale_factor() + sprite.offset.to_f32())
size: bounds.size().to_float2(), .to_float2(),
atlas_origin: bounds.origin().to_float2(), size: sprite.size.to_float2(),
atlas_origin: sprite.atlas_origin.to_float2(),
color: glyph.color.to_uchar4(), color: glyph.color.to_uchar4(),
}); });
} }

View file

@ -19,12 +19,20 @@ struct GlyphDescriptor {
glyph_id: GlyphId, glyph_id: GlyphId,
} }
#[derive(Clone)]
pub struct GlyphSprite {
pub atlas_id: usize,
pub atlas_origin: Vector2I,
pub offset: Vector2I,
pub size: Vector2I,
}
pub struct SpriteCache { pub struct SpriteCache {
device: metal::Device, device: metal::Device,
atlas_size: Vector2I, atlas_size: Vector2I,
font_cache: Arc<FontCache>, font_cache: Arc<FontCache>,
atlasses: Vec<Atlas>, atlasses: Vec<Atlas>,
glyphs: HashMap<GlyphDescriptor, Option<(usize, RectI)>>, glyphs: HashMap<GlyphDescriptor, Option<GlyphSprite>>,
} }
impl SpriteCache { impl SpriteCache {
@ -49,7 +57,7 @@ impl SpriteCache {
font_size: f32, font_size: f32,
glyph_id: GlyphId, glyph_id: GlyphId,
scale_factor: f32, scale_factor: f32,
) -> Option<(usize, RectI)> { ) -> Option<GlyphSprite> {
let font_cache = &self.font_cache; let font_cache = &self.font_cache;
let atlasses = &mut self.atlasses; let atlasses = &mut self.atlasses;
let atlas_size = self.atlas_size; let atlas_size = self.atlas_size;
@ -61,20 +69,28 @@ impl SpriteCache {
glyph_id, glyph_id,
}) })
.or_insert_with(|| { .or_insert_with(|| {
let (size, mask) = let (glyph_bounds, mask) =
font_cache.render_glyph(font_id, font_size, glyph_id, scale_factor)?; font_cache.render_glyph(font_id, font_size, glyph_id, scale_factor)?;
assert!(size.x() < atlas_size.x()); assert!(glyph_bounds.width() < atlas_size.x());
assert!(size.y() < atlas_size.y()); assert!(glyph_bounds.height() < atlas_size.y());
let atlas = atlasses.last_mut().unwrap(); let atlas_bounds = atlasses
if let Some(bounds) = atlas.try_insert(size, &mask) { .last_mut()
Some((atlasses.len() - 1, RectI::new(bounds.origin(), size))) .unwrap()
} else { .try_insert(glyph_bounds.size(), &mask)
let mut atlas = Atlas::new(device, atlas_size); .unwrap_or_else(|| {
let bounds = atlas.try_insert(size, &mask).unwrap(); let mut atlas = Atlas::new(device, atlas_size);
atlasses.push(atlas); let bounds = atlas.try_insert(glyph_bounds.size(), &mask).unwrap();
Some((atlasses.len() - 1, RectI::new(bounds.origin(), size))) atlasses.push(atlas);
} bounds
});
Some(GlyphSprite {
atlas_id: atlasses.len() - 1,
atlas_origin: atlas_bounds.origin(),
offset: glyph_bounds.origin(),
size: glyph_bounds.size(),
})
}) })
.clone() .clone()
} }