Automatically determine whether to use oversampling.
Expand glyph capacity slightly.
Draw a range of font sizes.
use narcissus_app::{create_app, Event, Key, WindowDesc};
use narcissus_core::{default, obj, rand::Pcg64};
-use narcissus_font::{CachedGlyph, CachedGlyphIndex, FontCollection, GlyphCache, Oversample};
+use narcissus_font::{CachedGlyph, CachedGlyphIndex, FontCollection, GlyphCache};
use narcissus_gpu::{
create_device, Access, Bind, Buffer, BufferDesc, BufferImageCopy, BufferUsageFlags, ClearValue,
Device, Extent2d, Extent3d, Image, ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension,
const NUM_SHARKS: usize = 50;
const MAX_GLYPH_INSTANCES: usize = 262_144;
-const MAX_GLYPHS: usize = 1024;
+const MAX_GLYPHS: usize = 8192;
/// Marker trait indicates it's safe to convert a given type directly to an array of bytes.
///
let text_pipeline = TextPipeline::new(device.as_ref());
let fonts = Fonts::new();
- let mut glyph_cache = GlyphCache::new(&fonts, 512, 512, 1, Oversample::X2, Oversample::X2);
+ let mut glyph_cache = GlyphCache::new(&fonts, 1024, 512, 1);
let blåhaj_image = load_image("bins/narcissus/data/blåhaj.png");
let (blåhaj_vertices, blåhaj_indices) = load_obj("bins/narcissus/data/blåhaj.obj");
basic_uniforms.write(BasicUniforms { clip_from_model });
// Do some Font Shit.'
- let line0 = "Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½ Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½";
- let line1 = "加盟国は、国際連合と協力して";
+ let line0 = "Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½ Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½ Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½";
+ let line1 = "加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して";
let mut glyph_instances = Vec::new();
- let mut glyphs = Vec::new();
+ let mut glyph_indices = Vec::new();
+ let mut x;
let mut y = 0.0;
let mut rng = Pcg64::new();
- for line in 0..100 {
- let font_family = if line & 1 == 0 {
- FontFamily::RobotoRegular
+ for line in 0..34 {
+ let (font_family, font_size_px, text) = if line & 1 == 0 {
+ (FontFamily::RobotoRegular, 10.0, line0)
} else {
- FontFamily::NotoSansJapanese
+ (FontFamily::NotoSansJapanese, 20.0, line1)
};
- let font = fonts.font(font_family);
-
- let v_metrics = font.vertical_metrics();
- let font_scale = font.scale_for_pixel_height(if line & 1 == 0 { 25.0 } else { 40.0 });
- y += v_metrics.ascent * font_scale - v_metrics.descent * font_scale
- + v_metrics.line_gap * font_scale;
- y = y.trunc();
+ let font_size_px = font_size_px + (line / 2) as f32 * 2.0;
- let mut x = 0.0;
-
- glyphs.clear();
+ let font = fonts.font(font_family);
+ let scale = font.scale_for_size_px(font_size_px);
- let text = if line & 1 == 0 { line0 } else { line1 };
+ x = 0.0;
+ y += (font.ascent() - font.descent() + font.line_gap()) * scale;
+ y = y.trunc();
- glyphs.extend(text.chars().map(|c| {
- font.glyph_id(c)
- .unwrap_or_else(|| font.glyph_id('□').unwrap())
+ glyph_indices.clear();
+ glyph_indices.extend(text.chars().map(|c| {
+ font.glyph_index(c)
+ .unwrap_or_else(|| font.glyph_index('□').unwrap())
}));
let mut prev_glyph_index = None;
- for glyph_index in glyphs.iter().copied() {
- let cached_glyph_index =
- glyph_cache.cache_glyph(font_family, glyph_index, font_scale);
-
- if let Some(prev_glyph_index) = prev_glyph_index {
- x += font.kerning_advance(prev_glyph_index, glyph_index) * font_scale;
+ for glyph_index in glyph_indices.iter().copied() {
+ if let Some(prev_glyph_index) = prev_glyph_index.replace(glyph_index) {
+ x += font.kerning_advance(prev_glyph_index, glyph_index) * scale;
}
+ let cached_glyph_index =
+ glyph_cache.cache_glyph(font_family, glyph_index, font_size_px);
+
const COLOR_SERIES: [u32; 4] = [0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63];
+ let color = COLOR_SERIES[rng.next_bound_u64(4) as usize];
glyph_instances.push(GlyphInstance {
cached_glyph_index,
x,
y,
- color: COLOR_SERIES[rng.next_bound_u64(4) as usize],
+ color,
});
let h_metrics = font.horizontal_metrics(glyph_index);
- x += h_metrics.advance_width * font_scale;
- prev_glyph_index = Some(glyph_index);
+ x += h_metrics.advance_width * scale;
}
}
struct GlyphKey<Family> {
family: Family,
glyph_index: GlyphIndex,
- scale: FiniteF32,
+ size_px: FiniteF32,
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Glyph<F> {
family: F,
glyph_index: GlyphIndex,
- scale: FiniteF32,
+ size_px: FiniteF32,
cached_glyph_index: CachedGlyphIndex,
}
fonts: &'a F,
padding: usize,
- oversample_h: Oversample,
- oversample_v: Oversample,
next_cached_glyph_index: u32,
cached_glyph_lookup: FxHashMap<GlyphKey<F::Family>, CachedGlyphIndex>,
where
F: FontCollection<'a>,
{
- pub fn new(
- fonts: &'a F,
- width: usize,
- height: usize,
- padding: usize,
- oversample_h: Oversample,
- oversample_v: Oversample,
- ) -> Self {
+ pub fn new(fonts: &'a F, width: usize, height: usize, padding: usize) -> Self {
Self {
fonts,
padding,
- oversample_h,
- oversample_v,
glyphs: Vec::new(),
self.height
}
+ fn oversample_for_size(size_px: f32) -> Oversample {
+ if size_px <= 25.0 {
+ Oversample::X2
+ } else {
+ Oversample::None
+ }
+ }
+
pub fn cache_glyph(
&mut self,
family: F::Family,
glyph_index: GlyphIndex,
- scale: f32,
+ size_px: f32,
) -> CachedGlyphIndex {
let key = GlyphKey {
family,
glyph_index,
- scale: FiniteF32::new(scale).unwrap(),
+ size_px: FiniteF32::new(size_px).unwrap(),
};
*self.cached_glyph_lookup.entry(key).or_insert_with(|| {
|(glyph_key, &cached_glyph_index)| Glyph {
family: glyph_key.family,
glyph_index: glyph_key.glyph_index,
- scale: glyph_key.scale,
+ size_px: glyph_key.size_px,
cached_glyph_index,
},
));
self.glyphs.sort_unstable();
let padding = self.padding as i32;
- let oversample_h = self.oversample_h.as_i32();
- let oversample_v = self.oversample_v.as_i32();
self.rects.clear();
self.rects.extend(self.glyphs.iter().map(|glyph| {
- let scale = glyph.scale.get();
+ let font = self.fonts.font(glyph.family);
+ let size_px = glyph.size_px.get();
+ let scale = font.scale_for_size_px(size_px);
+ let oversample = Self::oversample_for_size(size_px);
- let bitmap_box = self.fonts.font(glyph.family).glyph_bitmap_box(
+ let bitmap_box = font.glyph_bitmap_box(
glyph.glyph_index,
- scale * oversample_h as f32,
- scale * oversample_v as f32,
+ scale * oversample.as_f32(),
+ scale * oversample.as_f32(),
0.0,
0.0,
);
- let w = bitmap_box.x1 - bitmap_box.x0 + padding + oversample_h - 1;
- let h = bitmap_box.y1 - bitmap_box.y0 + padding + oversample_v - 1;
+ let w = bitmap_box.x1 - bitmap_box.x0 + padding + oversample.as_i32() - 1;
+ let h = bitmap_box.y1 - bitmap_box.y0 + padding + oversample.as_i32() - 1;
Rect {
id: glyph.cached_glyph_index.0 as i32,
}));
self.packer.clear();
- self.packer.pack(&mut self.rects);
+ assert!(self.packer.pack(&mut self.rects));
self.texture.fill(0);
self.cached_glyphs
.resize(self.glyphs.len(), CachedGlyph::default());
let padding = self.padding as i32;
- let oversample_h = oversample_h as f32;
- let oversample_v = oversample_v as f32;
for (glyph, rect) in self.glyphs.iter().zip(self.rects.iter_mut()) {
let font = self.fonts.font(glyph.family);
rect.w -= padding;
rect.h -= padding;
- let scale = glyph.scale.get();
- let scale_x = scale * oversample_h;
- let scale_y = scale * oversample_v;
+ let size_px = glyph.size_px.get();
+ let scale = font.scale_for_size_px(size_px);
+ let oversample = Self::oversample_for_size(size_px);
+
+ let scale_x = scale * oversample.as_f32();
+ let scale_y = scale * oversample.as_f32();
let (sub_x, sub_y) = font.render_glyph_bitmap(
&mut self.texture,
scale_y,
0.0,
0.0,
- self.oversample_h,
- self.oversample_v,
+ oversample,
+ oversample,
glyph.glyph_index,
);
y1: _,
} = font.glyph_bitmap_box(
glyph.glyph_index,
- scale * oversample_h,
- scale * oversample_v,
+ scale * oversample.as_f32(),
+ scale * oversample.as_f32(),
0.0,
0.0,
);
- cached_glyph.offset_x0 = x0 as f32 / oversample_h + sub_x;
- cached_glyph.offset_y0 = y0 as f32 / oversample_v + sub_y;
- cached_glyph.offset_x1 = (x0 + rect.w) as f32 / oversample_h + sub_x;
- cached_glyph.offset_y1 = (y0 + rect.h) as f32 / oversample_v + sub_y;
+ cached_glyph.offset_x0 = x0 as f32 / oversample.as_f32() + sub_x;
+ cached_glyph.offset_y0 = y0 as f32 / oversample.as_f32() + sub_y;
+ cached_glyph.offset_x1 = (x0 + rect.w) as f32 / oversample.as_f32() + sub_x;
+ cached_glyph.offset_y1 = (y0 + rect.h) as f32 / oversample.as_f32() + sub_y;
}
(&self.cached_glyphs, &self.texture)
use stb_truetype_sys::{
stbtt_FindGlyphIndex, stbtt_GetFontOffsetForIndex, stbtt_GetFontVMetrics,
stbtt_GetGlyphBitmapBoxSubpixel, stbtt_GetGlyphHMetrics, stbtt_GetGlyphKernAdvance,
- stbtt_InitFont, stbtt_MakeGlyphBitmapSubpixelPrefilter, stbtt_ScaleForPixelHeight, truetype,
+ stbtt_InitFont, stbtt_MakeGlyphBitmapSubpixelPrefilter, truetype,
};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Oversample {
- X1,
+ None,
X2,
X3,
X4,
pub fn as_i32(self) -> i32 {
self as i32 + 1
}
+
+ pub fn as_f32(self) -> f32 {
+ (self as i32) as f32 + 1.0
+ }
}
/// Font vertical metrics in unscaled coordinates.
pub struct Font<'a> {
info: truetype::FontInfo,
phantom: PhantomData<&'a [u8]>,
+ vertical_metrics: VerticalMetrics,
}
impl<'a> Font<'a> {
info.assume_init()
};
+ let vertical_metrics = {
+ let mut ascent = 0;
+ let mut descent = 0;
+ let mut line_gap = 0;
+ // Safety: We've just initialized the font info above.
+ unsafe { stbtt_GetFontVMetrics(&info, &mut ascent, &mut descent, &mut line_gap) };
+ VerticalMetrics {
+ ascent: ascent as f32,
+ descent: descent as f32,
+ line_gap: line_gap as f32,
+ }
+ };
+
Self {
info,
phantom: PhantomData,
+ vertical_metrics,
}
}
- pub fn scale_for_pixel_height(&self, height: f32) -> f32 {
- unsafe { stbtt_ScaleForPixelHeight(&self.info, height) }
+ /// Returns a scale factor to produce a font whose "height" is `size_px` pixels tall.
+ ///
+ /// Height is measured as the distance from the highest ascender to the lowest descender.
+ pub fn scale_for_size_px(&self, size_px: f32) -> f32 {
+ size_px / (self.vertical_metrics.ascent - self.vertical_metrics.descent)
}
- pub fn vertical_metrics(&self) -> VerticalMetrics {
- let mut ascent = 0;
- let mut descent = 0;
- let mut line_gap = 0;
- unsafe {
- stbtt_GetFontVMetrics(&self.info, &mut ascent, &mut descent, &mut line_gap);
- }
- VerticalMetrics {
- ascent: ascent as f32,
- descent: descent as f32,
- line_gap: line_gap as f32,
- }
+ /// Return the font's vertical ascent in unscaled coordinates.
+ pub fn ascent(&self) -> f32 {
+ self.vertical_metrics.ascent
+ }
+
+ /// Return the font's vertical descent in unscaled coordinates.
+ pub fn descent(&self) -> f32 {
+ self.vertical_metrics.descent
+ }
+
+ /// Return the font's line gap in unscaled coordinates.
+ pub fn line_gap(&self) -> f32 {
+ self.vertical_metrics.line_gap
}
- pub fn glyph_id(&self, c: char) -> Option<GlyphIndex> {
- let glyph_id = unsafe { stbtt_FindGlyphIndex(&self.info, c as i32) };
- NonZeroI32::new(glyph_id).map(|glyph_id| GlyphIndex(glyph_id))
+ /// Return the `GlyphIndex` for the character, or `None` if the font has no matching glyph.
+ pub fn glyph_index(&self, c: char) -> Option<GlyphIndex> {
+ let glyph_index = unsafe { stbtt_FindGlyphIndex(&self.info, c as i32) };
+ NonZeroI32::new(glyph_index).map(|glyph_index| GlyphIndex(glyph_index))
}
pub fn glyph_bitmap_box(