maplibre/sdf/
text.rs

1use std::{collections::BTreeMap, convert::TryFrom};
2
3use image::{GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma};
4use lyon::tessellation::{FillVertex, FillVertexConstructor};
5use prost::{DecodeError, Message};
6
7use crate::render::shaders::ShaderSymbolVertex;
8
9pub mod sdf_glyphs {
10    include!(concat!(env!("OUT_DIR"), "/glyphs.rs"));
11}
12
13pub type UnicodePoint = char;
14
15#[derive(Debug)]
16pub struct Glyph {
17    pub codepoint: UnicodePoint,
18    pub width: u32,
19    pub height: u32,
20    pub left_bearing: i32,
21    pub top_bearing: i32,
22    pub h_advance: u32,
23
24    /// x origin coordinate within the packed texture
25    pub tex_origin_x: u32,
26    /// y origin coordinate within the packed texture
27    pub tex_origin_y: u32,
28}
29
30impl Glyph {
31    fn from_pbf(g: sdf_glyphs::Glyph, origin_x: u32, origin_y: u32) -> Self {
32        Self {
33            codepoint: char::try_from(g.id).unwrap(),
34            width: g.width,
35            height: g.height,
36            left_bearing: g.left,
37            top_bearing: g.top,
38            h_advance: g.advance,
39            tex_origin_x: origin_x,
40            tex_origin_y: origin_y,
41        }
42    }
43
44    pub fn buffered_dimensions(&self) -> (u32, u32) {
45        (self.width + 3 * 2, self.height + 3 * 2)
46    }
47    pub fn origin_offset(&self) -> (u32, u32) {
48        (self.tex_origin_x, self.tex_origin_y)
49    }
50    pub fn advance(&self) -> u32 {
51        self.h_advance
52    }
53}
54
55pub struct GlyphSet {
56    texture_bytes: Vec<u8>,
57    texture_dimensions: (usize, usize),
58    pub glyphs: BTreeMap<UnicodePoint, Glyph>,
59}
60
61impl TryFrom<&[u8]> for GlyphSet {
62    type Error = DecodeError;
63
64    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
65        Ok(GlyphSet::from(sdf_glyphs::Glyphs::decode(value)?))
66    }
67}
68
69impl From<sdf_glyphs::Glyphs> for GlyphSet {
70    fn from(pbf_glyphs: sdf_glyphs::Glyphs) -> Self {
71        let stacks = pbf_glyphs.stacks;
72        let mut texture: GrayImage = ImageBuffer::new(4096, 4096);
73        let mut last_position = (0, 0);
74        let mut max_height = 0;
75
76        let glyphs = stacks
77            .into_iter()
78            .flat_map(|stack| {
79                stack
80                    .glyphs
81                    .into_iter()
82                    .filter_map(|mut glyph| {
83                        // Save an extra copy operation by taking the bits out directly.
84                        let bitmap = glyph.bitmap.take()?;
85
86                        let glyph = Glyph::from_pbf(glyph, last_position.0, last_position.1);
87
88                        let buffered_width = glyph.width + 3 * 2;
89                        let buffered_height = glyph.height + 3 * 2;
90
91                        let glyph_texture = ImageBuffer::<Luma<u8>, _>::from_vec(
92                            buffered_width,
93                            buffered_height,
94                            bitmap,
95                        )?;
96                        assert_eq!(buffered_height, glyph_texture.height());
97                        assert_eq!(buffered_width, glyph_texture.width());
98
99                        // TODO: wraparound on texture width overflow
100                        texture
101                            .copy_from(&glyph_texture, last_position.0, last_position.1)
102                            .expect("Unable to copy glyph texture.");
103
104                        last_position.0 += glyph_texture.width();
105                        max_height = max_height.max(glyph_texture.height());
106
107                        Some((glyph.codepoint, glyph))
108                    })
109                    .collect::<Vec<_>>()
110            })
111            .collect();
112
113        Self {
114            texture_bytes: texture
115                .view(0, 0, last_position.0, max_height)
116                .pixels()
117                .map(|(_x, _y, p)| p[0])
118                .collect(),
119            texture_dimensions: (last_position.0 as _, max_height as _),
120            glyphs,
121        }
122    }
123}
124
125impl GlyphSet {
126    pub fn get_texture_dimensions(&self) -> (usize, usize) {
127        self.texture_dimensions
128    }
129
130    pub fn get_texture_bytes(&self) -> &[u8] {
131        self.texture_bytes.as_slice()
132    }
133}
134
135pub struct SymbolVertexBuilder {
136    /// Where is the top-left anchor of the glyph box
137    pub glyph_anchor: [f32; 3],
138    /// Where is the top-left anchor of the text box
139    pub text_anchor: [f32; 3],
140    /// Size of sprite-sheet * font_scale
141    pub texture_dimensions: (f32, f32),
142    /// Size of individual glyph * font_scale
143    pub sprite_dimensions: (f32, f32),
144    /// where in the sheet is the sprite * font_scale
145    pub sprite_offset: (f32, f32),
146    pub glyph: bool,
147    pub color: [u8; 4],
148}
149
150impl FillVertexConstructor<ShaderSymbolVertex> for SymbolVertexBuilder {
151    fn new_vertex(&mut self, vertex: FillVertex) -> ShaderSymbolVertex {
152        let vertex_position = vertex.position();
153
154        let sprite_ratio_x = self.sprite_dimensions.0 / self.texture_dimensions.0;
155        let sprite_ratio_y = self.sprite_dimensions.1 / self.texture_dimensions.1;
156
157        let x_offset = self.sprite_offset.0 / self.texture_dimensions.0;
158        let y_offset = self.sprite_offset.1 / self.texture_dimensions.1;
159
160        let tex_coords = [
161            x_offset
162                + ((vertex_position.x - self.glyph_anchor[0]) / self.sprite_dimensions.0)
163                    * sprite_ratio_x,
164            y_offset
165                + ((vertex_position.y - self.glyph_anchor[1]) / self.sprite_dimensions.1)
166                    * sprite_ratio_y,
167        ];
168
169        ShaderSymbolVertex {
170            position: [vertex_position.x, vertex_position.y, 0.],
171            text_anchor: self.text_anchor,
172            is_glyph: if self.glyph { 1 } else { 0 },
173            color: self.color,
174            tex_coords,
175        }
176    }
177}
178
179#[derive(Debug, Copy, Clone)]
180pub enum Anchor {
181    Center,
182    Left,
183    Right,
184    Top,
185    Bottom,
186    TopLeft,
187    TopRight,
188    BottomLeft,
189    BottomRight,
190}