| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use crate::{
- gfx::{DebugTag, Rectangle, RectangleUnion, Renderer},
- mesh::COLOR_WHITE,
- };
- use super::{
- atlas::{Atlas, RenderedAtlas},
- make_layout,
- render::push_glyphs,
- };
- /// One rasterized glyph of a string, placed relative to the string's
- /// layout origin and mapped into the shared atlas texture.
- pub struct StringAtlasGlyph {
- /// Glyph rect in virtual units relative to the string layout origin
- pub rect: Rectangle,
- /// UV rect within the shared atlas texture
- pub uv_rect: Rectangle,
- }
- /// Atlas-backed geometry for one input string.
- pub struct StringAtlasEntry {
- pub glyphs: Vec<StringAtlasGlyph>,
- /// Union of the glyph rects in virtual units relative to the string
- /// layout origin
- pub ink_bounds: Rectangle,
- }
- /// A single shared atlas texture covering a fixed list of strings.
- pub struct StringAtlas {
- /// The rendered atlas holding every glyph of every input string
- pub rendered: RenderedAtlas,
- /// One entry per input string, index-aligned with the input
- pub entries: Vec<StringAtlasEntry>,
- }
- /// Rasterize every glyph of every string once and pack them into a
- /// single shared atlas texture. Returns per-string glyph geometry so
- /// callers can draw each string as textured quads referencing the
- /// shared texture instead of allocating per-string GPU resources.
- pub fn make_string_atlas(
- strings: &[&str],
- font_size: f32,
- window_scale: f32,
- max_row_width: usize,
- renderer: &Renderer,
- tag: DebugTag,
- ) -> StringAtlas {
- let layouts: Vec<_> = strings
- .iter()
- .map(|string| make_layout(string, COLOR_WHITE, font_size, 1., window_scale, None, &[]))
- .collect();
- let mut atlas = Atlas::with_max_row_width(renderer, tag, max_row_width);
- let mut scale_ctx = swash::scale::ScaleContext::new();
- let mut run_idx = 0;
- for layout in &layouts {
- for line in layout.lines() {
- for item in line.items() {
- let parley::PositionedLayoutItem::GlyphRun(glyph_run) = item else { continue };
- push_glyphs(&mut atlas, &glyph_run, run_idx, &mut scale_ctx);
- run_idx += 1;
- }
- }
- }
- let rendered = atlas.make();
- let mut entries = Vec::with_capacity(layouts.len());
- let mut run_idx = 0;
- for layout in &layouts {
- let mut glyphs = vec![];
- let mut bounds = RectangleUnion::new();
- for line in layout.lines() {
- for item in line.items() {
- let parley::PositionedLayoutItem::GlyphRun(glyph_run) = item else { continue };
- let mut run_x = glyph_run.offset();
- let run_y = glyph_run.baseline();
- for glyph in glyph_run.glyphs() {
- let glyph_inf =
- rendered.fetch_uv(glyph.id as u16, run_idx).expect("missing glyph UV rect");
- let glyph_x = run_x + glyph.x;
- let glyph_y = run_y - glyph.y;
- run_x += glyph.advance;
- let rect = Rectangle::new(
- (glyph_x + glyph_inf.place.left as f32) / layout.scale(),
- (glyph_y - glyph_inf.place.top as f32) / layout.scale(),
- glyph_inf.place.width as f32 / layout.scale(),
- glyph_inf.place.height as f32 / layout.scale(),
- );
- bounds.add(rect);
- let uv_rect = glyph_inf.uv_rect;
- glyphs.push(StringAtlasGlyph { rect, uv_rect });
- }
- run_idx += 1;
- }
- }
- let ink_bounds = bounds.get().unwrap_or(Rectangle::zero());
- entries.push(StringAtlasEntry { glyphs, ink_bounds });
- }
- StringAtlas { rendered, entries }
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- #[test]
- fn test_make_string_atlas() {
- let (method_send, _method_recv) = async_channel::unbounded();
- let renderer = Renderer::new(method_send);
- let strings = ["a", ":", "\u{1F600}", "\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}"];
- let atlas = make_string_atlas(&strings, 40., 1., 4096, &renderer, None);
- assert_eq!(atlas.entries.len(), 4);
- for entry in &atlas.entries {
- assert!(!entry.glyphs.is_empty());
- assert!(entry.ink_bounds.w > 0.);
- assert!(entry.ink_bounds.h > 0.);
- for glyph in &entry.glyphs {
- assert!(glyph.rect.w > 0.);
- assert!(glyph.rect.h > 0.);
- assert!((0. ..=1.).contains(&glyph.uv_rect.x));
- assert!((0. ..=1.).contains(&glyph.uv_rect.y));
- assert!((0. ..=1.).contains(&(glyph.uv_rect.x + glyph.uv_rect.w)));
- assert!((0. ..=1.).contains(&(glyph.uv_rect.y + glyph.uv_rect.h)));
- }
- }
- }
- }
|