1use csscolorparser::Color;
4use geozero::{ColumnValue, FeatureProcessor, GeomProcessor, PropertyProcessor};
5use lyon::{
6 geom::euclid::{Box2D, Point2D},
7 tessellation::{
8 geometry_builder::MaxIndex, BuffersBuilder, FillOptions, FillTessellator, VertexBuffers,
9 },
10};
11
12use crate::{
13 legacy::TileSpace,
14 render::shaders::ShaderSymbolVertex,
15 sdf::{
16 text::{Anchor, Glyph, GlyphSet, SymbolVertexBuilder},
17 Feature,
18 },
19};
20
21pub type IndexDataType = u32; type GeoResult<T> = geozero::error::Result<T>;
25
26pub struct TextTessellator<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> {
28 glyphs: GlyphSet,
29
30 pub quad_buffer: VertexBuffers<ShaderSymbolVertex, I>,
32 pub features: Vec<Feature>,
33
34 current_index: usize,
36 current_text: Option<String>,
37 current_origin: Option<Box2D<f32, TileSpace>>,
38}
39
40impl<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> Default
41 for TextTessellator<I>
42{
43 fn default() -> Self {
44 let data = include_bytes!("../../../data/0-255.pbf");
45 let glyphs = GlyphSet::try_from(data.as_slice()).unwrap();
46 Self {
47 glyphs,
48 quad_buffer: VertexBuffers::new(),
49 features: Vec::new(),
50 current_index: 0,
51 current_text: None,
52 current_origin: None,
53 }
54 }
55}
56
57enum StringGlyph<'a> {
58 Char(char),
59 Glyph(&'a Glyph),
60}
61
62impl<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> TextTessellator<I> {
63 pub fn tessellate_glyph_quads(
64 &mut self,
65 origin: [f32; 2],
66 label_text: &str,
67 color: Color,
68 ) -> Option<Box2D<f32, TileSpace>> {
69 let mut tessellator = FillTessellator::new();
70
71 let mut next_origin = origin;
72
73 let texture_dimensions = self.glyphs.get_texture_dimensions();
74 let texture_dimensions = (texture_dimensions.0 as f32, texture_dimensions.1 as f32);
75
76 let mut bbox = None;
78 for str_glyph in label_text
79 .chars()
80 .map(|c| {
81 self.glyphs
82 .glyphs
83 .get(&c)
84 .map(StringGlyph::Glyph)
85 .unwrap_or_else(|| StringGlyph::Char(c))
86 })
87 .collect::<Vec<_>>()
88 {
89 let glyph = match str_glyph {
90 StringGlyph::Glyph(glyph) => glyph,
91 StringGlyph::Char(c) => match c {
92 ' ' => {
93 next_origin[0] += 10.0; continue;
95 }
96 _ => {
97 log::error!("unhandled char {}", c);
98 continue;
99 }
100 },
101 };
102
103 let glyph_dims = glyph.buffered_dimensions();
104 let width = glyph_dims.0 as f32;
105 let height = glyph_dims.1 as f32;
106
107 let glyph_anchor = [
108 next_origin[0] + glyph.left_bearing as f32,
109 next_origin[1] - glyph.top_bearing as f32,
110 0.,
111 ];
112
113 let glyph_bbox = Box2D::new(
114 (glyph_anchor[0], glyph_anchor[1]).into(),
115 (glyph_anchor[0] + width, glyph_anchor[1] + height).into(),
116 );
117
118 bbox = bbox.map_or_else(
119 || Some(glyph_bbox),
120 |bbox: Box2D<_, TileSpace>| Some(bbox.union(&glyph_bbox)),
121 );
122
123 tessellator
124 .tessellate_rectangle(
125 &glyph_bbox.cast_unit(),
126 &FillOptions::default(),
127 &mut BuffersBuilder::new(
128 &mut self.quad_buffer,
129 SymbolVertexBuilder {
130 glyph_anchor,
131 text_anchor: [origin[0], origin[1], 0.0],
132 texture_dimensions,
133 sprite_dimensions: (width, height),
134 sprite_offset: (
135 glyph.origin_offset().0 as f32,
136 glyph.origin_offset().1 as f32,
137 ),
138 color: color.to_rgba8(), glyph: true, },
141 ),
142 )
143 .ok()?;
144
145 next_origin[0] += glyph.advance() as f32 + 1.0;
146 }
147
148 bbox
149 }
150}
151
152impl<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> GeomProcessor
153 for TextTessellator<I>
154{
155 fn xy(&mut self, x: f64, y: f64, _idx: usize) -> GeoResult<()> {
156 if self.current_origin.is_some() {
157 unreachable!("Text labels have only a single origin point")
159 } else {
160 self.current_origin = Some(Box2D::new(
161 Point2D::new(x as f32, y as f32),
162 Point2D::new(x as f32, y as f32),
163 ))
164 }
165
166 Ok(())
167 }
168
169 fn point_begin(&mut self, _idx: usize) -> GeoResult<()> {
170 Ok(())
171 }
172
173 fn point_end(&mut self, _idx: usize) -> GeoResult<()> {
174 Ok(())
175 }
176
177 fn multipoint_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> {
178 Ok(())
179 }
180
181 fn multipoint_end(&mut self, _idx: usize) -> GeoResult<()> {
182 Ok(())
183 }
184
185 fn linestring_begin(&mut self, _tagged: bool, _size: usize, _idx: usize) -> GeoResult<()> {
186 Ok(())
187 }
188
189 fn linestring_end(&mut self, _tagged: bool, _idx: usize) -> GeoResult<()> {
190 Ok(())
191 }
192
193 fn multilinestring_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> {
194 Ok(())
195 }
196
197 fn multilinestring_end(&mut self, _idx: usize) -> GeoResult<()> {
198 Ok(())
199 }
200
201 fn polygon_begin(&mut self, _tagged: bool, _size: usize, _idx: usize) -> GeoResult<()> {
202 Ok(())
203 }
204
205 fn polygon_end(&mut self, _tagged: bool, _idx: usize) -> GeoResult<()> {
206 Ok(())
207 }
208
209 fn multipolygon_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> {
210 Ok(())
211 }
212
213 fn multipolygon_end(&mut self, _idx: usize) -> GeoResult<()> {
214 Ok(())
215 }
216}
217
218impl<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> PropertyProcessor
219 for TextTessellator<I>
220{
221 fn property(
222 &mut self,
223 _idx: usize,
224 name: &str,
225 value: &ColumnValue,
226 ) -> geozero::error::Result<bool> {
227 if name == "name" {
229 match value {
230 ColumnValue::String(str) => {
231 self.current_text = Some(str.to_string());
232 }
233 _ => {}
234 }
235 }
236 Ok(true)
237 }
238}
239
240impl<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> FeatureProcessor
241 for TextTessellator<I>
242{
243 fn feature_end(&mut self, _idx: u64) -> geozero::error::Result<()> {
244 if let (Some(origin), Some(text)) = (&self.current_origin, self.current_text.clone()) {
245 if text.is_empty() {
246 panic!("dud")
247 }
248 let anchor = Anchor::BottomLeft;
249 let origin = match anchor {
251 Anchor::Center => origin.center(), Anchor::BottomLeft => origin.min,
253 _ => unimplemented!("no support for this anchor"),
254 };
255 let bbox = self.tessellate_glyph_quads(
256 origin.to_array(),
257 text.as_str(),
258 Color::from_linear_rgba(1.0, 0., 0., 1.),
259 );
260
261 let next_index = self.quad_buffer.indices.len();
262 let start = self.current_index;
263 let end = next_index;
264 self.current_index = next_index;
265
266 self.features.push(Feature {
267 bbox: bbox.unwrap_or(Box2D::new(origin, origin)),
268 indices: start..end,
269 text_anchor: origin.cast(),
270 str: text,
271 });
272
273 self.current_origin = None;
274 self.current_text = None;
275 }
276 Ok(())
277 }
278}