From 81dedbd04237e181c2118931c5f37d341aeb6837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 20 Jun 2023 06:08:41 +0200 Subject: [PATCH] Implement subpixel glyph positioning and scaling The latest `cosmic-text` release performs bucketing calculations after layouting. This allows us to provide proper subpixel offsets, as well as scale any buffer thanks to its linear layout properties. See https://github.com/pop-os/cosmic-text/pull/143. --- Cargo.toml | 2 +- examples/hello-world.rs | 11 ++++++----- src/lib.rs | 9 ++++++--- src/text_render.rs | 31 +++++++++++++++++++++---------- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eadf439..71e5a01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0 OR Zlib" [dependencies] wgpu = "0.16" etagere = "0.2.8" -cosmic-text = "0.8" +cosmic-text = "0.9" lru = "0.9" [dev-dependencies] diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 1156551..b3cde3f 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -1,6 +1,6 @@ use glyphon::{ - Attrs, Buffer, Color, Family, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas, - TextBounds, TextRenderer, + Attrs, Buffer, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache, TextArea, + TextAtlas, TextBounds, TextRenderer, }; use wgpu::{ CommandEncoderDescriptor, CompositeAlphaMode, DeviceDescriptor, Features, Instance, @@ -73,7 +73,7 @@ async fn run() { let physical_height = (height as f64 * scale_factor) as f32; buffer.set_size(&mut font_system, physical_width, physical_height); - buffer.set_text(&mut font_system, "Hello world! 👋\nThis is rendered with 🦅 glyphon 🦁\nThe text below should be partially clipped.\na b c d e f g h i j k l m n o p q r s t u v w x y z", Attrs::new().family(Family::SansSerif)); + buffer.set_text(&mut font_system, "Hello world! 👋\nThis is rendered with 🦅 glyphon 🦁\nThe text below should be partially clipped.\na b c d e f g h i j k l m n o p q r s t u v w x y z", Attrs::new().family(Family::SansSerif), Shaping::Advanced); buffer.shape_until_scroll(&mut font_system); event_loop.run(move |event, _, control_flow| { @@ -103,8 +103,9 @@ async fn run() { }, [TextArea { buffer: &buffer, - left: 10, - top: 10, + left: 10.0, + top: 10.0, + scale: 1.0, bounds: TextBounds { left: 0, top: 0, diff --git a/src/lib.rs b/src/lib.rs index 85b232f..938c01c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,8 @@ pub use cosmic_text::{ self, fontdb, Action, Affinity, Attrs, AttrsList, AttrsOwned, Buffer, BufferLine, CacheKey, Color, Command, Cursor, Edit, Editor, Family, FamilyOwned, Font, FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, LayoutRun, LayoutRunIter, Metrics, ShapeGlyph, ShapeLine, ShapeSpan, - ShapeWord, Stretch, Style, SubpixelBin, SwashCache, SwashContent, SwashImage, Weight, Wrap, + ShapeWord, Shaping, Stretch, Style, SubpixelBin, SwashCache, SwashContent, SwashImage, Weight, + Wrap, }; use etagere::AllocId; @@ -101,9 +102,11 @@ pub struct TextArea<'a> { /// The buffer containing the text to be rendered. pub buffer: &'a Buffer, /// The left edge of the buffer. - pub left: i32, + pub left: f32, /// The top edge of the buffer. - pub top: i32, + pub top: f32, + /// The scaling to apply to the buffer. + pub scale: f32, /// The visible bounds of the text area. This is used to clip the text and doesn't have to /// match the `left` and `top` values. pub bounds: TextBounds, diff --git a/src/text_render.rs b/src/text_render.rs index 20411a3..ccf1951 100644 --- a/src/text_render.rs +++ b/src/text_render.rs @@ -93,13 +93,24 @@ impl TextRenderer { for text_area in text_areas { for run in text_area.buffer.layout_runs() { for glyph in run.glyphs.iter() { - if atlas.mask_atlas.glyph_cache.contains(&glyph.cache_key) { - atlas.mask_atlas.promote(glyph.cache_key); - } else if atlas.color_atlas.glyph_cache.contains(&glyph.cache_key) { - atlas.color_atlas.promote(glyph.cache_key); + let physical_glyph = + glyph.physical((text_area.left, text_area.top), text_area.scale); + + if atlas + .mask_atlas + .glyph_cache + .contains(&physical_glyph.cache_key) + { + atlas.mask_atlas.promote(physical_glyph.cache_key); + } else if atlas + .color_atlas + .glyph_cache + .contains(&physical_glyph.cache_key) + { + atlas.color_atlas.promote(physical_glyph.cache_key); } else { let Some(image) = cache - .get_image_uncached(font_system, glyph.cache_key) else { continue }; + .get_image_uncached(font_system, physical_glyph.cache_key) else { continue }; let content_type = match image.content { SwashContent::Color => ContentType::Color, @@ -178,7 +189,7 @@ impl TextRenderer { }; inner.put( - glyph.cache_key, + physical_glyph.cache_key, GlyphDetails { width: width as u16, height: height as u16, @@ -190,11 +201,11 @@ impl TextRenderer { ); } - let details = atlas.glyph(&glyph.cache_key).unwrap(); + let details = atlas.glyph(&physical_glyph.cache_key).unwrap(); - let mut x = glyph.x_int + details.left as i32 + text_area.left; - let mut y = - run.line_y as i32 + glyph.y_int - details.top as i32 + text_area.top; + let mut x = physical_glyph.x + details.left as i32; + let mut y = (run.line_y * text_area.scale).round() as i32 + physical_glyph.y + - details.top as i32; let (mut atlas_x, mut atlas_y, content_type) = match details.gpu_cache { GpuCacheStatus::InAtlas { x, y, content_type } => (x, y, content_type),