string_atlas.rs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use crate::{
  19. gfx::{DebugTag, Rectangle, RectangleUnion, Renderer},
  20. mesh::COLOR_WHITE,
  21. };
  22. use super::{
  23. atlas::{Atlas, RenderedAtlas},
  24. make_layout,
  25. render::push_glyphs,
  26. };
  27. /// One rasterized glyph of a string, placed relative to the string's
  28. /// layout origin and mapped into the shared atlas texture.
  29. pub struct StringAtlasGlyph {
  30. /// Glyph rect in virtual units relative to the string layout origin
  31. pub rect: Rectangle,
  32. /// UV rect within the shared atlas texture
  33. pub uv_rect: Rectangle,
  34. }
  35. /// Atlas-backed geometry for one input string.
  36. pub struct StringAtlasEntry {
  37. pub glyphs: Vec<StringAtlasGlyph>,
  38. /// Union of the glyph rects in virtual units relative to the string
  39. /// layout origin
  40. pub ink_bounds: Rectangle,
  41. }
  42. /// A single shared atlas texture covering a fixed list of strings.
  43. pub struct StringAtlas {
  44. /// The rendered atlas holding every glyph of every input string
  45. pub rendered: RenderedAtlas,
  46. /// One entry per input string, index-aligned with the input
  47. pub entries: Vec<StringAtlasEntry>,
  48. }
  49. /// Rasterize every glyph of every string once and pack them into a
  50. /// single shared atlas texture. Returns per-string glyph geometry so
  51. /// callers can draw each string as textured quads referencing the
  52. /// shared texture instead of allocating per-string GPU resources.
  53. pub fn make_string_atlas(
  54. strings: &[&str],
  55. font_size: f32,
  56. window_scale: f32,
  57. max_row_width: usize,
  58. renderer: &Renderer,
  59. tag: DebugTag,
  60. ) -> StringAtlas {
  61. let layouts: Vec<_> = strings
  62. .iter()
  63. .map(|string| make_layout(string, COLOR_WHITE, font_size, 1., window_scale, None, &[]))
  64. .collect();
  65. let mut atlas = Atlas::with_max_row_width(renderer, tag, max_row_width);
  66. let mut scale_ctx = swash::scale::ScaleContext::new();
  67. let mut run_idx = 0;
  68. for layout in &layouts {
  69. for line in layout.lines() {
  70. for item in line.items() {
  71. let parley::PositionedLayoutItem::GlyphRun(glyph_run) = item else { continue };
  72. push_glyphs(&mut atlas, &glyph_run, run_idx, &mut scale_ctx);
  73. run_idx += 1;
  74. }
  75. }
  76. }
  77. let rendered = atlas.make();
  78. let mut entries = Vec::with_capacity(layouts.len());
  79. let mut run_idx = 0;
  80. for layout in &layouts {
  81. let mut glyphs = vec![];
  82. let mut bounds = RectangleUnion::new();
  83. for line in layout.lines() {
  84. for item in line.items() {
  85. let parley::PositionedLayoutItem::GlyphRun(glyph_run) = item else { continue };
  86. let mut run_x = glyph_run.offset();
  87. let run_y = glyph_run.baseline();
  88. for glyph in glyph_run.glyphs() {
  89. let glyph_inf =
  90. rendered.fetch_uv(glyph.id as u16, run_idx).expect("missing glyph UV rect");
  91. let glyph_x = run_x + glyph.x;
  92. let glyph_y = run_y - glyph.y;
  93. run_x += glyph.advance;
  94. let rect = Rectangle::new(
  95. (glyph_x + glyph_inf.place.left as f32) / layout.scale(),
  96. (glyph_y - glyph_inf.place.top as f32) / layout.scale(),
  97. glyph_inf.place.width as f32 / layout.scale(),
  98. glyph_inf.place.height as f32 / layout.scale(),
  99. );
  100. bounds.add(rect);
  101. let uv_rect = glyph_inf.uv_rect;
  102. glyphs.push(StringAtlasGlyph { rect, uv_rect });
  103. }
  104. run_idx += 1;
  105. }
  106. }
  107. let ink_bounds = bounds.get().unwrap_or(Rectangle::zero());
  108. entries.push(StringAtlasEntry { glyphs, ink_bounds });
  109. }
  110. StringAtlas { rendered, entries }
  111. }
  112. #[cfg(test)]
  113. mod tests {
  114. use super::*;
  115. #[test]
  116. fn test_make_string_atlas() {
  117. let (method_send, _method_recv) = async_channel::unbounded();
  118. let renderer = Renderer::new(method_send);
  119. let strings = ["a", ":", "\u{1F600}", "\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}"];
  120. let atlas = make_string_atlas(&strings, 40., 1., 4096, &renderer, None);
  121. assert_eq!(atlas.entries.len(), 4);
  122. for entry in &atlas.entries {
  123. assert!(!entry.glyphs.is_empty());
  124. assert!(entry.ink_bounds.w > 0.);
  125. assert!(entry.ink_bounds.h > 0.);
  126. for glyph in &entry.glyphs {
  127. assert!(glyph.rect.w > 0.);
  128. assert!(glyph.rect.h > 0.);
  129. assert!((0. ..=1.).contains(&glyph.uv_rect.x));
  130. assert!((0. ..=1.).contains(&glyph.uv_rect.y));
  131. assert!((0. ..=1.).contains(&(glyph.uv_rect.x + glyph.uv_rect.w)));
  132. assert!((0. ..=1.).contains(&(glyph.uv_rect.y + glyph.uv_rect.h)));
  133. }
  134. }
  135. }
  136. }