maplibre/sdf/
tessellation_new.rs

1//! Tessellation for lines and polygons is implemented here.
2
3use std::collections::HashMap;
4
5use geo_types::Geometry;
6use geozero::{
7    geo_types::GeoWriter, ColumnValue, FeatureProcessor, GeomProcessor, PropertyProcessor,
8};
9use lyon::{
10    geom::euclid::{Box2D, Point2D},
11    tessellation::VertexBuffers,
12};
13use widestring::U16String;
14
15use crate::{
16    euclid::{Rect, Size2D},
17    legacy::{
18        bidi::{apply_arabic_shaping, Char16},
19        buckets::symbol_bucket::SymbolBucketBuffer,
20        font_stack::FontStackHasher,
21        geometry_tile_data::{GeometryCoordinates, SymbolGeometryTileLayer},
22        glyph::{Glyph, GlyphDependencies, GlyphMap, GlyphMetrics, Glyphs},
23        glyph_atlas::{GlyphPosition, GlyphPositionMap, GlyphPositions},
24        image::ImageMap,
25        image_atlas::ImagePositions,
26        layout::{
27            layout::{BucketParameters, LayerTypeInfo, LayoutParameters},
28            symbol_feature::{SymbolGeometryTileFeature, VectorGeometryTileFeature},
29            symbol_layout::{FeatureIndex, LayerProperties, SymbolLayer, SymbolLayout},
30        },
31        style_types::SymbolLayoutProperties_Unevaluated,
32        tagged_string::TaggedString,
33        CanonicalTileID, MapMode, OverscaledTileID, TileSpace,
34    },
35    render::shaders::ShaderSymbolVertexNew,
36    sdf::{tessellation::IndexDataType, text::GlyphSet, Feature},
37};
38
39type GeoResult<T> = geozero::error::Result<T>;
40
41/// Build tessellations with vectors.
42pub struct TextTessellatorNew {
43    geo_writer: GeoWriter,
44
45    // configuration
46    text_field: String,
47
48    // output
49    pub quad_buffer: VertexBuffers<ShaderSymbolVertexNew, IndexDataType>,
50    pub features: Vec<Feature>,
51
52    // collected feature data from tile processing
53    collected_features: Vec<(String, f64, f64)>,
54
55    // iteration variables
56    current_index: usize,
57    current_text: Option<String>,
58    current_origin: Option<Box2D<f32, TileSpace>>,
59    current_point: Option<(f64, f64)>,
60}
61
62impl TextTessellatorNew {
63    pub fn finish(&mut self) {
64        let data = include_bytes!("../../../data/0-255.pbf");
65        let glyphs = GlyphSet::try_from(data.as_slice()).unwrap();
66
67        let font_stack = vec![
68            "Open Sans Regular".to_string(),
69            "Arial Unicode MS Regular".to_string(),
70        ];
71
72        let layer_name = "layer".to_string();
73
74        let mut glyph_dependencies = GlyphDependencies::new();
75
76        let tile_id = OverscaledTileID {
77            canonical: CanonicalTileID { x: 0, y: 0, z: 0 },
78            overscaled_z: 0,
79        };
80        let mut parameters = BucketParameters {
81            tile_id: tile_id,
82            mode: MapMode::Continuous,
83            pixel_ratio: 1.0,
84            layer_type: LayerTypeInfo,
85        };
86
87        // Build SymbolGeometryTileFeatures from the tile data collected during processing.
88        // Pre-populate formatted_text so symbol_layout uses actual names instead of defaults.
89        let features: Vec<SymbolGeometryTileFeature> = self
90            .collected_features
91            .iter()
92            .map(|(text, x, y)| {
93                let geometry = vec![GeometryCoordinates(vec![Point2D::new(
94                    *x as i16, *y as i16,
95                )])];
96                let mut feature =
97                    SymbolGeometryTileFeature::new(Box::new(VectorGeometryTileFeature {
98                        geometry,
99                    }));
100                let mut tagged_string = TaggedString::default();
101                tagged_string.add_text_section(
102                    &apply_arabic_shaping(&U16String::from(text.as_str())),
103                    1.0,
104                    font_stack.clone(),
105                    None,
106                );
107                feature.formatted_text = Some(tagged_string);
108                feature
109            })
110            .collect();
111
112        if features.is_empty() {
113            return;
114        }
115
116        let layer_data = SymbolGeometryTileLayer {
117            name: layer_name.clone(),
118            features,
119        };
120        let layer_properties = vec![LayerProperties {
121            id: layer_name.clone(),
122            layer: SymbolLayer {
123                layout: SymbolLayoutProperties_Unevaluated,
124            },
125        }];
126
127        let image_positions = ImagePositions::new();
128
129        let glyph_map =
130            GlyphPositionMap::from_iter(glyphs.glyphs.iter().map(|(unicode_point, glyph)| {
131                (
132                    *unicode_point as Char16,
133                    GlyphPosition {
134                        rect: Rect::new(
135                            Point2D::new(
136                                glyph.tex_origin_x as u16 + 3,
137                                glyph.tex_origin_y as u16 + 3,
138                            ),
139                            Size2D::new(
140                                glyph.buffered_dimensions().0 as u16,
141                                glyph.buffered_dimensions().1 as u16,
142                            ),
143                        ), // FIXME: verify if this mapping is correct
144                        metrics: GlyphMetrics {
145                            width: glyph.width,
146                            height: glyph.height,
147                            left: glyph.left_bearing,
148                            top: glyph.top_bearing,
149                            advance: glyph.h_advance,
150                        },
151                    },
152                )
153            }));
154
155        let glyph_positions: GlyphPositions =
156            GlyphPositions::from([(FontStackHasher::new(&font_stack), glyph_map)]);
157
158        let glyphs: GlyphMap = GlyphMap::from([(
159            FontStackHasher::new(&font_stack),
160            Glyphs::from_iter(glyphs.glyphs.iter().map(|(unicode_point, glyph)| {
161                (
162                    *unicode_point as Char16,
163                    Some(Glyph {
164                        id: *unicode_point as Char16,
165                        bitmap: Default::default(),
166                        metrics: GlyphMetrics {
167                            width: glyph.width,
168                            height: glyph.height,
169                            left: glyph.left_bearing,
170                            top: glyph.top_bearing,
171                            advance: glyph.h_advance,
172                        },
173                    }),
174                )
175            })),
176        )]);
177
178        let mut layout = SymbolLayout::new(
179            &parameters,
180            &layer_properties,
181            Box::new(layer_data),
182            &mut LayoutParameters {
183                bucket_parameters: &mut parameters.clone(),
184                glyph_dependencies: &mut glyph_dependencies,
185                image_dependencies: &mut Default::default(),
186                available_images: &mut Default::default(),
187            },
188        )
189        .unwrap();
190
191        let empty_image_map = ImageMap::new();
192        layout.prepare_symbols(
193            &glyphs,
194            &glyph_positions,
195            &empty_image_map,
196            &image_positions,
197        );
198
199        let mut output = HashMap::new();
200        layout.create_bucket(
201            image_positions,
202            Box::new(FeatureIndex),
203            &mut output,
204            false,
205            false,
206            &tile_id.canonical,
207        );
208
209        let new_buffer = output.remove(&layer_name).unwrap();
210
211        let mut buffer = VertexBuffers::new();
212        let text_buffer = new_buffer.bucket.text;
213        let SymbolBucketBuffer {
214            shared_vertices,
215            triangles,
216            ..
217        } = text_buffer;
218        buffer.vertices = shared_vertices
219            .iter()
220            .map(|v| ShaderSymbolVertexNew::new(v))
221            .collect();
222        buffer.indices = triangles.indices.iter().map(|i| *i as u32).collect();
223
224        self.quad_buffer = buffer;
225    }
226}
227
228impl TextTessellatorNew {
229    pub fn new(text_field: String) -> Self {
230        Self {
231            text_field,
232            ..Default::default()
233        }
234    }
235}
236
237impl Default for TextTessellatorNew {
238    fn default() -> Self {
239        Self {
240            geo_writer: Default::default(),
241            text_field: "name".to_string(),
242            quad_buffer: VertexBuffers::new(),
243            features: vec![],
244            collected_features: vec![],
245            current_index: 0,
246            current_text: None,
247            current_origin: None,
248            current_point: None,
249        }
250    }
251}
252
253impl GeomProcessor for TextTessellatorNew {
254    fn xy(&mut self, x: f64, y: f64, idx: usize) -> GeoResult<()> {
255        self.current_point = Some((x, y));
256        self.geo_writer.xy(x, y, idx)
257    }
258    fn point_begin(&mut self, idx: usize) -> GeoResult<()> {
259        self.geo_writer.point_begin(idx)
260    }
261    fn point_end(&mut self, idx: usize) -> GeoResult<()> {
262        self.geo_writer.point_end(idx)
263    }
264    fn multipoint_begin(&mut self, size: usize, idx: usize) -> GeoResult<()> {
265        self.geo_writer.multipoint_begin(size, idx)
266    }
267    fn linestring_begin(&mut self, tagged: bool, size: usize, idx: usize) -> GeoResult<()> {
268        self.geo_writer.linestring_begin(tagged, size, idx)
269    }
270    fn linestring_end(&mut self, tagged: bool, idx: usize) -> GeoResult<()> {
271        self.geo_writer.linestring_end(tagged, idx)
272    }
273    fn multilinestring_begin(&mut self, size: usize, idx: usize) -> GeoResult<()> {
274        self.geo_writer.multilinestring_begin(size, idx)
275    }
276    fn multilinestring_end(&mut self, idx: usize) -> GeoResult<()> {
277        self.geo_writer.multilinestring_end(idx)
278    }
279    fn polygon_begin(&mut self, tagged: bool, size: usize, idx: usize) -> GeoResult<()> {
280        self.geo_writer.polygon_begin(tagged, size, idx)
281    }
282    fn polygon_end(&mut self, tagged: bool, idx: usize) -> GeoResult<()> {
283        self.geo_writer.polygon_end(tagged, idx)
284    }
285    fn multipolygon_begin(&mut self, size: usize, idx: usize) -> GeoResult<()> {
286        self.geo_writer.multipolygon_begin(size, idx)
287    }
288    fn multipolygon_end(&mut self, idx: usize) -> GeoResult<()> {
289        self.geo_writer.multipolygon_end(idx)
290    }
291}
292
293impl PropertyProcessor for TextTessellatorNew {
294    fn property(
295        &mut self,
296        _idx: usize,
297        name: &str,
298        value: &ColumnValue,
299    ) -> geozero::error::Result<bool> {
300        if name == self.text_field {
301            match value {
302                ColumnValue::String(str) => {
303                    self.current_text = Some(str.to_string());
304                }
305                _ => {}
306            }
307        }
308        Ok(true)
309    }
310}
311
312impl FeatureProcessor for TextTessellatorNew {
313    fn feature_end(&mut self, _idx: u64) -> geozero::error::Result<()> {
314        let geometry = self.geo_writer.take_geometry();
315
316        // Collect features that have both a name and a point geometry
317        if let (Some(text), Some((x, y))) = (self.current_text.take(), self.current_point.take()) {
318            self.collected_features.push((text, x, y));
319        } else {
320            self.current_text = None;
321            self.current_point = None;
322        }
323
324        match geometry {
325            Some(Geometry::Point(_point)) => {}
326            Some(Geometry::Polygon(_polygon)) => {}
327            Some(Geometry::LineString(_linestring)) => {}
328            Some(Geometry::Line(_))
329            | Some(Geometry::MultiPoint(_))
330            | Some(Geometry::MultiLineString(_))
331            | Some(Geometry::MultiPolygon(_))
332            | Some(Geometry::GeometryCollection(_))
333            | Some(Geometry::Rect(_))
334            | Some(Geometry::Triangle(_)) => {
335                log::debug!("Unsupported geometry in text tesselation")
336            }
337            None => {
338                log::debug!("No geometry in feature")
339            }
340        };
341
342        Ok(())
343    }
344}