Align glyphs correctly using font-kit's raster_bounds
This commit is contained in:
parent
7523df05cf
commit
e0e4cff815
4 changed files with 104 additions and 69 deletions
|
@ -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);
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue