maplibre/legacy/layout/
symbol_layout.rs

1//! Translated from https://github.com/maplibre/maplibre-native/blob/4add9ea/src/mbgl/layout/symbol_layout.cpp
2
3use std::{
4    collections::{BTreeMap, BTreeSet, HashMap},
5    f64::consts::PI,
6    ops::Range,
7    rc::Rc,
8};
9
10use lyon::geom::euclid::Point2D;
11use widestring::U16String;
12
13use crate::{
14    coords::{EXTENT, TILE_SIZE},
15    legacy::{
16        bidi::{apply_arabic_shaping, BiDi, Char16},
17        buckets::symbol_bucket::{
18            DynamicVertex, OpacityVertex, PlacedSymbol, Segment, SymbolBucket, SymbolBucketBuffer,
19            SymbolVertex,
20        },
21        geometry::{
22            anchor::{Anchor, Anchors},
23            feature_index::{IndexedSubfeature, RefIndexedSubfeature},
24        },
25        geometry_tile_data::{FeatureType, GeometryCoordinates, SymbolGeometryTileLayer},
26        glyph::{GlyphIDs, GlyphMap, Shaping, WritingModeType},
27        glyph_atlas::GlyphPositions,
28        image::{ImageMap, ImageType},
29        image_atlas::ImagePositions,
30        layout::{
31            layout::{BucketParameters, LayoutParameters},
32            symbol_feature::SymbolGeometryTileFeature,
33            symbol_instance::{
34                ShapedTextOrientations, SymbolContent, SymbolInstance, SymbolInstanceSharedData,
35            },
36        },
37        quads::{SymbolQuad, SymbolQuads},
38        shaping::{get_anchor_justification, get_shaping, PositionedIcon},
39        style_types::*,
40        tagged_string::{SectionOptions, TaggedString},
41        util::{constants::ONE_EM, i18n, lower_bound, math::deg2radf},
42        CanonicalTileID, MapMode,
43    },
44};
45
46// TODO
47/// maplibre/maplibre-native#4add9ea original name: SymbolLayer
48#[derive(Clone, Debug)]
49pub struct SymbolLayer {
50    pub layout: SymbolLayoutProperties_Unevaluated,
51}
52/// maplibre/maplibre-native#4add9ea original name: SymbolLayer_Impl
53pub type SymbolLayer_Impl = SymbolLayer;
54// TODO
55/// maplibre/maplibre-native#4add9ea original name: LayerProperties
56#[derive(Clone, Debug)]
57pub struct LayerProperties {
58    pub id: String,
59    pub layer: SymbolLayer_Impl,
60}
61/// maplibre/maplibre-native#4add9ea original name: SymbolLayerProperties
62pub type SymbolLayerProperties = LayerProperties;
63impl LayerProperties {
64    /// maplibre/maplibre-native#4add9ea original name: layerImpl
65    pub fn layer_impl(&self) -> &SymbolLayer_Impl {
66        // TODO
67        &self.layer
68    }
69
70    /// maplibre/maplibre-native#4add9ea original name: baseImpl
71    pub fn base_impl(&self) -> &Self {
72        self
73    }
74}
75/// maplibre/maplibre-native#4add9ea original name: Bucket
76pub type Bucket = SymbolBucket;
77
78/// maplibre/maplibre-native#4add9ea original name: LayerRenderData
79#[derive(Debug)]
80pub struct LayerRenderData {
81    pub bucket: Bucket,
82    pub layer_properties: LayerProperties,
83}
84
85/// maplibre/maplibre-native#4add9ea original name: SortKeyRange
86#[derive(Clone, Copy, Debug)]
87pub struct SortKeyRange {
88    sort_key: f64,
89    start: usize,
90    end: usize,
91}
92
93impl SortKeyRange {
94    /// maplibre/maplibre-native#4add9ea original name: isFirstRange
95    pub fn is_first_range(&self) -> bool {
96        self.start == 0
97    }
98}
99
100// index
101/// maplibre/maplibre-native#4add9ea original name: FeatureIndex
102pub struct FeatureIndex;
103
104/// maplibre/maplibre-native#4add9ea original name: sectionOptionsToValue
105fn section_options_to_value(options: &SectionOptions) -> expression::Value {
106    let mut result: HashMap<String, expression::Value> = Default::default();
107    // TODO: Data driven properties that can be overridden on per section basis.
108    // TextOpacity
109    // TextHaloColor
110    // TextHaloWidth
111    // TextHaloBlur
112    if let Some(text_color) = &(options.text_color) {
113        result.insert(
114            expression::K_FORMATTED_SECTION_TEXT_COLOR.to_string(),
115            expression::Value::Color(text_color.clone()),
116        );
117    }
118    expression::Value::Object(result)
119}
120
121/// maplibre/maplibre-native#4add9ea original name: toSymbolLayerProperties
122fn to_symbol_layer_properties(layer: &LayerProperties) -> &SymbolLayerProperties {
123    // TODO
124    //return static_cast<const SymbolLayerProperties&>(*layer);
125    layer
126}
127
128/// maplibre/maplibre-native#4add9ea original name: createLayout
129fn create_layout(
130    unevaluated: &SymbolLayoutProperties_Unevaluated,
131    zoom: f64,
132) -> SymbolLayoutProperties_PossiblyEvaluated {
133    let mut layout = unevaluated.evaluate(PropertyEvaluationParameters(zoom));
134
135    if layout.get::<IconRotationAlignment>() == AlignmentType::Auto {
136        if layout.get::<SymbolPlacement>() != SymbolPlacementType::Point {
137            layout.set::<IconRotationAlignment>(AlignmentType::Map);
138        } else {
139            layout.set::<IconRotationAlignment>(AlignmentType::Viewport);
140        }
141    }
142
143    if layout.get::<TextRotationAlignment>() == AlignmentType::Auto {
144        if layout.get::<SymbolPlacement>() != SymbolPlacementType::Point {
145            layout.set::<TextRotationAlignment>(AlignmentType::Map);
146        } else {
147            layout.set::<TextRotationAlignment>(AlignmentType::Viewport);
148        }
149    }
150
151    // If unspecified `*-pitch-alignment` inherits `*-rotation-alignment`
152    if layout.get::<TextPitchAlignment>() == AlignmentType::Auto {
153        layout.set::<TextPitchAlignment>(layout.get::<TextRotationAlignment>());
154    }
155    if layout.get::<IconPitchAlignment>() == AlignmentType::Auto {
156        layout.set::<IconPitchAlignment>(layout.get::<IconRotationAlignment>());
157    }
158
159    layout
160}
161
162// The radial offset is to the edge of the text box
163// In the horizontal direction, the edge of the text box is where glyphs start
164// But in the vertical direction, the glyphs appear to "start" at the baseline
165// We don't actually load baseline data, but we assume an offset of ONE_EM - 17
166// (see "yOffset" in shaping.js)
167const BASELINE_OFFSET: f64 = 7.0;
168
169// We don't care which shaping we get because this is used for collision
170// purposes and all the justifications have the same collision box.
171/// maplibre/maplibre-native#4add9ea original name: getDefaultHorizontalShaping
172fn get_default_horizontal_shaping(shaped_text_orientations: &ShapedTextOrientations) -> &Shaping {
173    if shaped_text_orientations.right().is_any_line_not_empty() {
174        return shaped_text_orientations.right();
175    }
176    if shaped_text_orientations.center().is_any_line_not_empty() {
177        return shaped_text_orientations.center();
178    }
179    if shaped_text_orientations.left().is_any_line_not_empty() {
180        return shaped_text_orientations.left();
181    }
182    return shaped_text_orientations.horizontal();
183}
184
185/// maplibre/maplibre-native#4add9ea original name: shapingForTextJustifyType
186fn shaping_for_text_justify_type(
187    shaped_text_orientations: &ShapedTextOrientations,
188    type_: TextJustifyType,
189) -> &Shaping {
190    match type_ {
191        TextJustifyType::Right => {
192            return shaped_text_orientations.right();
193        }
194
195        TextJustifyType::Left => {
196            return shaped_text_orientations.left();
197        }
198
199        TextJustifyType::Center => {
200            return shaped_text_orientations.center();
201        }
202        _ => {
203            assert!(false);
204            return shaped_text_orientations.horizontal();
205        }
206    }
207}
208
209/// maplibre/maplibre-native#4add9ea original name: evaluateRadialOffset
210fn evaluate_radial_offset(anchor: SymbolAnchorType, mut radial_offset: f64) -> [f64; 2] {
211    let mut result = [0.0, 0.0];
212    if radial_offset < 0.0 {
213        radial_offset = 0.0; // Ignore negative offset.
214    }
215    // solve for r where r^2 + r^2 = radialOffset^2
216    let sqrt2 = 1.41421356237;
217    let hypotenuse = radial_offset / sqrt2;
218
219    match anchor {
220        SymbolAnchorType::TopRight | SymbolAnchorType::TopLeft => {
221            result[1] = hypotenuse - BASELINE_OFFSET;
222        }
223
224        SymbolAnchorType::BottomRight | SymbolAnchorType::BottomLeft => {
225            result[1] = -hypotenuse + BASELINE_OFFSET;
226        }
227        SymbolAnchorType::Bottom => {
228            result[1] = -radial_offset + BASELINE_OFFSET;
229        }
230        SymbolAnchorType::Top => {
231            result[1] = radial_offset - BASELINE_OFFSET;
232        }
233
234        _ => {}
235    }
236
237    match anchor {
238        SymbolAnchorType::TopRight | SymbolAnchorType::BottomRight => {
239            result[0] = -hypotenuse;
240        }
241        SymbolAnchorType::TopLeft | SymbolAnchorType::BottomLeft => {
242            result[0] = hypotenuse;
243        }
244        SymbolAnchorType::Left => {
245            result[0] = radial_offset;
246        }
247        SymbolAnchorType::Right => {
248            result[0] = -radial_offset;
249        }
250
251        _ => {}
252    }
253
254    result
255}
256
257/// maplibre/maplibre-native#4add9ea original name: SymbolLayout
258pub struct SymbolLayout {
259    pub layer_paint_properties: BTreeMap<String, LayerProperties>,
260    pub bucket_leader_id: String,
261    pub symbol_instances: Vec<SymbolInstance>,
262    pub sort_key_ranges: Vec<SortKeyRange>,
263
264    // Stores the layer so that we can hold on to GeometryTileFeature instances
265    // in SymbolFeature, which may reference data from this object.
266    source_layer: Box<SymbolGeometryTileLayer>,
267    overscaling: f64,
268    zoom: f64,
269    canonical_id: CanonicalTileID,
270    mode: MapMode,
271    pixel_ratio: f64,
272
273    tile_size: u32,
274    tile_pixel_ratio: f64,
275
276    icons_need_linear: bool,
277    sort_features_by_y: bool,
278    sort_features_by_key: bool,
279    allow_vertical_placement: bool,
280    icons_in_text: bool,
281    placement_modes: Vec<TextWritingModeType>,
282
283    text_size: <TextSize as DataDrivenLayoutProperty>::UnevaluatedType,
284    icon_size: <IconSize as DataDrivenLayoutProperty>::UnevaluatedType,
285    text_radial_offset: <TextRadialOffset as DataDrivenLayoutProperty>::UnevaluatedType,
286    layout: SymbolLayoutProperties_PossiblyEvaluated,
287    features: Vec<SymbolGeometryTileFeature>,
288
289    bidi: BiDi, // Consider moving this up to geometry tile worker to reduce
290    // reinstantiation costs, use of BiDi/ubiditransform object must
291    // be rained to one thread
292    compare_text: BTreeMap<U16String, Vec<Anchor>>,
293}
294
295impl SymbolLayout {
296    /// maplibre/maplibre-native#4add9ea original name: new
297    pub fn new(
298        parameters: &BucketParameters,
299        layers: &Vec<LayerProperties>,
300        source_layer: Box<SymbolGeometryTileLayer>,
301        layout_parameters: &mut LayoutParameters, // TODO is this output?
302    ) -> Option<Self> {
303        let overscaling = parameters.tile_id.overscale_factor() as f64;
304        let zoom = parameters.tile_id.overscaled_z as f64;
305        let tile_size = (TILE_SIZE * overscaling) as u32;
306
307        let leader: &SymbolLayer_Impl =
308            to_symbol_layer_properties(layers.first().unwrap()).layer_impl();
309
310        let mut self_ = Self {
311            bucket_leader_id: layers.first().unwrap().base_impl().id.clone(),
312
313            source_layer,
314            overscaling,
315            zoom,
316            canonical_id: parameters.tile_id.canonical,
317            mode: parameters.mode,
318            pixel_ratio: parameters.pixel_ratio,
319            tile_size: tile_size,
320            tile_pixel_ratio: EXTENT / tile_size as f64,
321            layout: create_layout(
322                &to_symbol_layer_properties(layers.first().unwrap())
323                    .layer_impl()
324                    .layout,
325                zoom,
326            ),
327            text_size: leader.layout.get_dynamic::<TextSize>(),
328            icon_size: leader.layout.get_dynamic::<IconSize>(),
329            text_radial_offset: leader.layout.get_dynamic::<TextRadialOffset>(),
330
331            // default values
332            layer_paint_properties: Default::default(),
333            symbol_instances: vec![],
334            sort_key_ranges: vec![],
335            icons_need_linear: false,
336            sort_features_by_y: false,
337            sort_features_by_key: false,
338            allow_vertical_placement: false,
339            icons_in_text: false,
340            placement_modes: vec![],
341            features: vec![],
342            bidi: BiDi,
343            compare_text: Default::default(),
344        };
345
346        let has_text = self_.layout.has::<TextField>() && self_.layout.has::<TextFont>();
347        let has_icon = self_.layout.has::<IconImage>();
348
349        if !has_text && !has_icon {
350            return None;
351        }
352
353        let has_symbol_sort_key = !leader.layout.get_dynamic::<SymbolSortKey>().is_undefined();
354        let symbol_zorder = self_.layout.get::<SymbolZOrder>();
355        self_.sort_features_by_key =
356            symbol_zorder != SymbolZOrderType::ViewportY && has_symbol_sort_key;
357        let z_order_by_viewport_y = symbol_zorder == SymbolZOrderType::ViewportY
358            || (symbol_zorder == SymbolZOrderType::Auto && !self_.sort_features_by_key);
359        self_.sort_features_by_y = z_order_by_viewport_y
360            && (self_.layout.get::<TextAllowOverlap>()
361                || self_.layout.get::<IconAllowOverlap>()
362                || self_.layout.get::<TextIgnorePlacement>()
363                || self_.layout.get::<IconIgnorePlacement>());
364        if self_.layout.get::<SymbolPlacement>() == SymbolPlacementType::Point {
365            let mut modes = self_.layout.get::<TextWritingMode>();
366
367            // Remove duplicates and preserve order.
368            // TODO Verify if this is correct. Maybe make this better.
369            let mut seen: BTreeSet<TextWritingModeType> = BTreeSet::new();
370            modes = modes
371                .iter()
372                .filter(|placement_mode| {
373                    self_.allow_vertical_placement = self_.allow_vertical_placement
374                        || **placement_mode == TextWritingModeType::Vertical;
375                    seen.insert(**placement_mode)
376                })
377                .cloned()
378                .collect();
379
380            self_.placement_modes = modes;
381        }
382
383        for layer in layers {
384            self_
385                .layer_paint_properties
386                .insert(layer.base_impl().id.clone(), layer.clone());
387        }
388
389        // Determine glyph dependencies
390        let feature_count = self_.source_layer.feature_count();
391        for i in 0..feature_count {
392            let feature = self_.source_layer.get_feature(i);
393
394            // TODO
395            //if (!leader.filter(expression::EvaluationContext::new(self_.zoom, feature.get()).withCanonicalTileID(&parameters.tileID.canonical), )) {
396            //    continue;
397            //}
398
399            let mut ft: SymbolGeometryTileFeature = *feature.clone();
400
401            ft.index = i;
402
403            if has_text {
404                // If formatted_text is not pre-populated (e.g. from feature data),
405                // evaluate the text expression (currently a stub returning a default value).
406                if ft.formatted_text.is_none() {
407                    let formatted = self_.layout.evaluate4::<TextField>(
408                        self_.zoom,
409                        &ft,
410                        layout_parameters.available_images,
411                        self_.canonical_id,
412                    );
413                    let text_transform =
414                        self_
415                            .layout
416                            .evaluate::<TextTransform>(self_.zoom, &ft, self_.canonical_id);
417                    let base_font_stack =
418                        self_
419                            .layout
420                            .evaluate::<TextFont>(self_.zoom, &ft, self_.canonical_id);
421
422                    ft.formatted_text = Some(TaggedString::default());
423                    let ft_formatted_text = ft.formatted_text.as_mut().unwrap();
424                    for section in &formatted.sections {
425                        if let Some(image) = &section.image {
426                            layout_parameters
427                                .image_dependencies
428                                .insert(image.image_id.clone(), ImageType::Icon);
429                            ft_formatted_text.add_image_section(image.image_id.clone());
430                        } else {
431                            let mut u8string = section.text.clone();
432                            if text_transform == TextTransformType::Uppercase {
433                                u8string = u8string.to_uppercase();
434                            } else if text_transform == TextTransformType::Lowercase {
435                                u8string = u8string.to_lowercase();
436                            }
437
438                            // TODO seems like invalid UTF-8 can not be in a tile? if let Err(e) =
439                            ft_formatted_text.add_text_section(
440                                &apply_arabic_shaping(&U16String::from(u8string.as_str())),
441                                if let Some(font_scale) = section.font_scale {
442                                    font_scale
443                                } else {
444                                    1.0
445                                },
446                                if let Some(font_stack) = &section.font_stack {
447                                    font_stack.clone()
448                                } else {
449                                    base_font_stack.clone()
450                                },
451                                section.text_color.clone(),
452                            )
453                            //{
454                            //    log::error!("Encountered section with invalid UTF-8 in tile, source: {} z: {} x: {} y: {}", self_.sourceLayer.getName(), self_.canonicalID.z, self_.canonicalID.x, self_.canonicalID.y);
455                            //    continue; // skip section
456                            //}
457                        }
458                    }
459                }
460
461                // Collect glyph dependencies using the TaggedString sections directly.
462                // Works for both pre-populated text and expression-evaluated text.
463                let ft_formatted_text = ft.formatted_text.as_mut().unwrap();
464                let can_verticalize_text = self_.layout.get::<TextRotationAlignment>()
465                    == AlignmentType::Map
466                    && self_.layout.get::<SymbolPlacement>() != SymbolPlacementType::Point
467                    && ft_formatted_text.allows_vertical_writing_mode();
468
469                // Loop through all characters of this text and collect unique codepoints.
470                for j in 0..ft_formatted_text.length() {
471                    let section_idx = ft_formatted_text.get_section_index(j) as usize;
472                    let section = ft_formatted_text.section_at(section_idx);
473                    if section.image_id.is_some() {
474                        continue;
475                    }
476
477                    let dependencies: &mut GlyphIDs = layout_parameters
478                        .glyph_dependencies
479                        .entry(section.font_stack.clone())
480                        .or_default();
481                    let code_point: Char16 = ft_formatted_text.get_char_code_at(j);
482                    dependencies.insert(code_point);
483                    if can_verticalize_text
484                        || (self_.allow_vertical_placement
485                            && ft_formatted_text.allows_vertical_writing_mode())
486                    {
487                        let vertical_chr: Char16 = i18n::verticalize_punctuation(code_point);
488                        if vertical_chr != 0 {
489                            dependencies.insert(vertical_chr);
490                        }
491                    }
492                }
493            }
494
495            if has_icon {
496                ft.icon = Some(self_.layout.evaluate4::<IconImage>(
497                    self_.zoom,
498                    &ft,
499                    layout_parameters.available_images,
500                    self_.canonical_id,
501                )); // TODO it might be that this is None?
502                layout_parameters
503                    .image_dependencies
504                    .insert(ft.icon.as_ref().unwrap().image_id.clone(), ImageType::Icon);
505            }
506
507            if ft.formatted_text.is_some() || ft.icon.is_some() {
508                if self_.sort_features_by_key {
509                    ft.sort_key =
510                        self_
511                            .layout
512                            .evaluate::<SymbolSortKey>(self_.zoom, &ft, self_.canonical_id);
513
514                    let lower_bound = lower_bound(&self_.features, &ft);
515                    self_.features.insert(lower_bound, ft);
516                } else {
517                    self_.features.push(ft);
518                }
519            }
520        }
521
522        if self_.layout.get::<SymbolPlacement>() == SymbolPlacementType::Line {
523            todo!()
524            // TODO mergeLines(self_.features);
525        }
526
527        Some(self_)
528    }
529    /// maplibre/maplibre-native#4add9ea original name: prepareSymbols
530    pub fn prepare_symbols(
531        &mut self,
532        glyph_map: &GlyphMap,
533        glyph_positions: &GlyphPositions,
534        image_map: &ImageMap,
535        image_positions: &ImagePositions,
536    ) {
537        let is_point_placement: bool =
538            self.layout.get::<SymbolPlacement>() == SymbolPlacementType::Point;
539        let text_along_line: bool =
540            self.layout.get::<TextRotationAlignment>() == AlignmentType::Map && !is_point_placement;
541
542        let mut to_process_features = Vec::new();
543
544        for (feature_index, feature) in self.features.iter_mut().enumerate() {
545            // TODO expensive clone
546            if feature.geometry.is_empty() {
547                continue;
548            }
549
550            let mut shaped_text_orientations: ShapedTextOrientations =
551                ShapedTextOrientations::default();
552            let mut shaped_icon: Option<PositionedIcon> = None;
553            let mut text_offset = [0.0, 0.0];
554            let layout_text_size: f64 =
555                self.layout
556                    .evaluate::<TextSize>(self.zoom + 1., feature, self.canonical_id);
557            let layout_text_size_at_bucket_zoom_level: f64 =
558                self.layout
559                    .evaluate::<TextSize>(self.zoom, feature, self.canonical_id);
560            let layout_icon_size: f64 =
561                self.layout
562                    .evaluate::<IconSize>(self.zoom + 1., feature, self.canonical_id);
563
564            // if feature has text, shape the text
565            if let Some(mut feature_formatted_text) = feature.formatted_text.clone() {
566                if layout_text_size > 0.0 {
567                    let line_height: f64 = self.layout.get::<TextLineHeight>() * ONE_EM;
568                    let spacing: f64 =
569                        if i18n::allows_letter_spacing(feature_formatted_text.raw_text()) {
570                            self.layout.evaluate::<TextLetterSpacing>(
571                                self.zoom,
572                                feature,
573                                self.canonical_id,
574                            ) * ONE_EM
575                        } else {
576                            0.0
577                        };
578
579                    let apply_shaping = |formatted_text: &TaggedString,
580                                         writing_mode: WritingModeType,
581                                         text_anchor: SymbolAnchorType,
582                                         text_justify: TextJustifyType,
583                                         text_offset: &[f64; 2]|
584                     -> Shaping {
585                        get_shaping(
586                            /* string */ formatted_text,
587                            /* maxWidth: ems */
588                            if is_point_placement {
589                                self.layout.evaluate::<TextMaxWidth>(
590                                    self.zoom,
591                                    feature,
592                                    self.canonical_id,
593                                ) * ONE_EM
594                            } else {
595                                0.0
596                            },
597                            /* ems */ line_height,
598                            text_anchor,
599                            text_justify,
600                            /* ems */ spacing,
601                            /* translate */ text_offset,
602                            /* writingMode */ writing_mode,
603                            /* bidirectional algorithm object */ &self.bidi,
604                            glyph_map,
605                            /* glyphs */ glyph_positions,
606                            /* images */ image_positions,
607                            layout_text_size,
608                            layout_text_size_at_bucket_zoom_level,
609                            self.allow_vertical_placement,
610                        )
611                    };
612
613                    let variable_text_anchor: Vec<TextVariableAnchorType> =
614                        self.layout.evaluate_static::<TextVariableAnchor>(
615                            self.zoom,
616                            feature,
617                            self.canonical_id,
618                        );
619                    let text_anchor: SymbolAnchorType =
620                        self.layout
621                            .evaluate::<TextAnchor>(self.zoom, feature, self.canonical_id);
622                    if variable_text_anchor.is_empty() {
623                        // Layers with variable anchors use the `text-radial-offset`
624                        // property and the [x, y] offset vector is calculated at
625                        // placement time instead of layout time
626                        let radial_offset: f64 = self.layout.evaluate::<TextRadialOffset>(
627                            self.zoom,
628                            feature,
629                            self.canonical_id,
630                        );
631                        if radial_offset > 0.0 {
632                            // The style spec says don't use `text-offset` and
633                            // `text-radial-offset` together but doesn't actually
634                            // specify what happens if you use both. We go with the
635                            // radial offset.
636                            text_offset =
637                                evaluate_radial_offset(text_anchor, radial_offset * ONE_EM);
638                        } else {
639                            text_offset = [
640                                self.layout.evaluate::<TextOffset>(
641                                    self.zoom,
642                                    feature,
643                                    self.canonical_id,
644                                )[0] * ONE_EM,
645                                self.layout.evaluate::<TextOffset>(
646                                    self.zoom,
647                                    feature,
648                                    self.canonical_id,
649                                )[1] * ONE_EM,
650                            ];
651                        }
652                    }
653                    let mut text_justify = if text_along_line {
654                        TextJustifyType::Center
655                    } else {
656                        self.layout
657                            .evaluate::<TextJustify>(self.zoom, feature, self.canonical_id)
658                    };
659
660                    let add_vertical_shaping_for_point_label_if_needed =
661                        |shaped_text_orientations: &mut ShapedTextOrientations,
662                         feature_formatted_text: &mut TaggedString| {
663                            if self.allow_vertical_placement
664                                && feature_formatted_text.allows_vertical_writing_mode()
665                            {
666                                feature_formatted_text.verticalize_punctuation();
667                                // Vertical POI label placement is meant to be used for
668                                // scripts that support vertical writing mode, thus, default
669                                // TextJustifyType::Left justification is used. If
670                                // Latin scripts would need to be supported, this should
671                                // take into account other justifications.
672                                shaped_text_orientations.set_vertical(apply_shaping(
673                                    feature_formatted_text,
674                                    WritingModeType::Vertical,
675                                    text_anchor,
676                                    TextJustifyType::Left,
677                                    &text_offset,
678                                ))
679                            }
680                        };
681
682                    // If this layer uses text-variable-anchor, generate shapings for
683                    // all justification possibilities.
684                    if !text_along_line && !variable_text_anchor.is_empty() {
685                        let mut justifications: Vec<TextJustifyType> = Vec::new();
686                        if text_justify != TextJustifyType::Auto {
687                            justifications.push(text_justify);
688                        } else {
689                            for anchor in &variable_text_anchor {
690                                justifications.push(get_anchor_justification(anchor));
691                            }
692                        }
693                        for justification in justifications {
694                            let mut shaping_for_justification = shaping_for_text_justify_type(
695                                &shaped_text_orientations,
696                                justification,
697                            );
698                            if shaping_for_justification.is_any_line_not_empty() {
699                                continue;
700                            }
701                            // If using text-variable-anchor for the layer, we use a
702                            // center anchor for all shapings and apply the offsets for
703                            // the anchor in the placement step.
704                            let shaping = apply_shaping(
705                                &feature_formatted_text,
706                                WritingModeType::Horizontal,
707                                SymbolAnchorType::Center,
708                                justification,
709                                &text_offset,
710                            );
711                            if shaping.is_any_line_not_empty() {
712                                shaping_for_justification = &shaping;
713                                if shaping_for_justification.positioned_lines.len() == 1 {
714                                    shaped_text_orientations.single_line = true;
715                                    break;
716                                }
717                            }
718                        }
719
720                        // Vertical point label shaping if allowVerticalPlacement is enabled.
721                        add_vertical_shaping_for_point_label_if_needed(
722                            &mut shaped_text_orientations,
723                            &mut feature_formatted_text,
724                        );
725                    } else {
726                        if text_justify == TextJustifyType::Auto {
727                            text_justify = get_anchor_justification(&text_anchor);
728                        }
729
730                        // Horizontal point or line label.
731                        let shaping = apply_shaping(
732                            &feature_formatted_text,
733                            WritingModeType::Horizontal,
734                            text_anchor,
735                            text_justify,
736                            &text_offset,
737                        );
738                        if shaping.is_any_line_not_empty() {
739                            shaped_text_orientations.set_horizontal(shaping)
740                        }
741
742                        // Vertical point label shaping if allowVerticalPlacement is enabled.
743                        add_vertical_shaping_for_point_label_if_needed(
744                            &mut shaped_text_orientations,
745                            &mut feature_formatted_text,
746                        );
747
748                        // Verticalized line label.
749                        if text_along_line && feature_formatted_text.allows_vertical_writing_mode()
750                        {
751                            feature_formatted_text.verticalize_punctuation();
752                            shaped_text_orientations.set_vertical(apply_shaping(
753                                &feature_formatted_text,
754                                WritingModeType::Vertical,
755                                text_anchor,
756                                text_justify,
757                                &text_offset,
758                            ));
759                        }
760                    }
761                }
762
763                feature.formatted_text = Some(feature_formatted_text);
764            }
765
766            // if feature has icon, get sprite atlas position
767            let mut icon_type: SymbolContent = SymbolContent::None;
768            if let Some(icon) = &feature.icon {
769                let image = image_map.get(&icon.image_id);
770                if let Some(image) = image {
771                    icon_type = SymbolContent::IconRGBA;
772                    shaped_icon = Some(PositionedIcon::shape_icon(
773                        image_positions.get(&icon.image_id).unwrap().clone(),
774                        &self
775                            .layout
776                            .evaluate::<IconOffset>(self.zoom, feature, self.canonical_id),
777                        self.layout
778                            .evaluate::<IconAnchor>(self.zoom, feature, self.canonical_id),
779                    ));
780                    if image.sdf {
781                        icon_type = SymbolContent::IconSDF;
782                    }
783                    if image.pixel_ratio != self.pixel_ratio {
784                        self.icons_need_linear = true;
785                    } else if self.layout.get_dynamic::<IconRotate>().constant_or(1.0) != 0.0 {
786                        self.icons_need_linear = true;
787                    }
788                }
789            }
790
791            // if either shapedText or icon position is present, add the feature
792            let default_shaping = get_default_horizontal_shaping(&shaped_text_orientations);
793            self.icons_in_text = if default_shaping.is_any_line_not_empty() {
794                default_shaping.icons_in_text
795            } else {
796                false
797            };
798            if default_shaping.is_any_line_not_empty() || shaped_icon.is_some() {
799                // TODO borrow conflict with self.features
800                to_process_features.push((
801                    feature_index,
802                    shaped_text_orientations,
803                    shaped_icon,
804                    image_map,
805                    text_offset,
806                    layout_text_size,
807                    layout_icon_size,
808                    icon_type,
809                ));
810            }
811        }
812
813        for (
814            feature_index,
815            shaped_text_orientations,
816            shaped_icon,
817            imageMap,
818            text_offset,
819            layout_text_size,
820            layout_icon_size,
821            icon_type,
822        ) in to_process_features
823        {
824            self.add_feature(
825                feature_index,
826                &self.features[feature_index].clone(), // TODO likely wrong clone
827                &shaped_text_orientations,
828                shaped_icon,
829                imageMap,
830                text_offset,
831                layout_text_size,
832                layout_icon_size,
833                icon_type,
834            );
835
836            self.features[feature_index].geometry.clear();
837        }
838
839        self.compare_text.clear();
840    }
841
842    /// maplibre/maplibre-native#4add9ea original name: createBucket
843    pub fn create_bucket(
844        &self,
845        _image_positions: ImagePositions,
846        _feature_index: Box<FeatureIndex>,
847        render_data: &mut HashMap<String, LayerRenderData>,
848        first_load: bool,
849        show_collision_boxes: bool,
850        canonical: &CanonicalTileID,
851    ) {
852        let mut symbol_instances = self.symbol_instances.clone(); // TODO should we clone or modify the symbol instances?
853        let mut bucket: SymbolBucket = SymbolBucket::new(
854            self.layout.clone(),
855            &self.layer_paint_properties,
856            &self.text_size,
857            &self.icon_size,
858            self.zoom,
859            self.icons_need_linear,
860            self.sort_features_by_y,
861            self.bucket_leader_id.clone(),
862            self.symbol_instances.clone(), // TODO should we clone or modify the symbol instances?
863            self.sort_key_ranges.clone(),
864            self.tile_pixel_ratio,
865            self.allow_vertical_placement,
866            self.placement_modes.clone(),
867            self.icons_in_text,
868        );
869
870        for symbol_instance in &mut symbol_instances {
871            let has_text = symbol_instance.has_text();
872            let has_icon = symbol_instance.has_icon();
873            let single_line = symbol_instance.single_line;
874
875            let feature = self
876                .features
877                .get(symbol_instance.layout_feature_index)
878                .unwrap();
879
880            // Insert final placement into collision tree and add glyphs/icons to buffers
881
882            // Process icon first, so that text symbols would have reference to
883            // iconIndex which is used when dynamic vertices for icon-text-fit image
884            // have to be updated.
885            if has_icon {
886                let size_data: Range<f64> = bucket.icon_size_binder.get_vertex_size_data(feature); // TODO verify usage of range
887                let icon_buffer = if symbol_instance.has_sdf_icon() {
888                    &mut bucket.sdf_icon
889                } else {
890                    &mut bucket.icon
891                };
892                let mut place_icon =
893                    |icon_quads: &SymbolQuads, mut index: usize, writing_mode: WritingModeType| {
894                        let mut icon_symbol = PlacedSymbol {
895                            anchor_point: symbol_instance.anchor.point,
896                            segment: symbol_instance.anchor.segment.unwrap_or(0),
897                            lower_size: size_data.start,
898                            upper_size: size_data.end,
899                            line_offset: symbol_instance.icon_offset,
900                            writing_modes: writing_mode,
901                            line: symbol_instance.line().clone(),
902                            tile_distances: Vec::new(),
903                            glyph_offsets: vec![],
904                            hidden: false,
905                            vertex_start_index: 0,
906                            cross_tile_id: 0,
907                            placed_orientation: None,
908                            angle: if self.allow_vertical_placement
909                                && writing_mode == WritingModeType::Vertical
910                            {
911                                PI / 2.
912                            } else {
913                                0.0
914                            },
915                            placed_icon_index: None,
916                        };
917
918                        icon_symbol.vertex_start_index = self.add_symbols(
919                            icon_buffer,
920                            size_data.clone(),
921                            icon_quads,
922                            &symbol_instance.anchor,
923                            &mut icon_symbol,
924                            feature.sort_key,
925                        );
926
927                        icon_buffer.placed_symbols.push(icon_symbol);
928                        index = icon_buffer.placed_symbols.len() - 1; // TODO we receive an index but always overwrite it
929                    };
930
931                place_icon(
932                    symbol_instance.icon_quads().as_ref().unwrap(),
933                    symbol_instance.placed_icon_index.unwrap(),
934                    WritingModeType::None,
935                );
936                if let Some(vertical_icon_quads) = symbol_instance.vertical_icon_quads() {
937                    place_icon(
938                        vertical_icon_quads,
939                        symbol_instance.placed_vertical_icon_index.unwrap(),
940                        WritingModeType::Vertical,
941                    );
942                }
943
944                // TODO
945                assert!(bucket.paint_properties.is_empty())
946                //for pair in bucket.paintProperties {
947                //    pair.1.iconBinders.populateVertexVectors(
948                //        feature,
949                //        iconBuffer.sharedVertices.elements(),
950                //        symbolInstance.dataFeatureIndex,
951                //        {},
952                //        {},
953                //        canonical,
954                //    );
955                //}
956            }
957
958            if has_text && feature.formatted_text.is_some() {
959                let mut last_added_section: Option<usize> = None;
960                if single_line {
961                    let mut placed_text_index: Option<usize> = None;
962                    let (new_last_added_section, new_placed_index) = self.add_symbol_glyph_quads(
963                        &mut bucket,
964                        symbol_instance,
965                        feature,
966                        symbol_instance.writing_modes,
967                        placed_text_index,
968                        symbol_instance.right_justified_glyph_quads(),
969                        canonical,
970                        last_added_section,
971                    );
972                    last_added_section = Some(new_last_added_section);
973                    placed_text_index = new_placed_index;
974                    symbol_instance.placed_right_text_index = placed_text_index;
975                    symbol_instance.placed_center_text_index = placed_text_index;
976                    symbol_instance.placed_left_text_index = placed_text_index;
977                } else {
978                    if symbol_instance.right_justified_glyph_quads_size != 0 {
979                        let (new_last_added_section, new_placed_index) = self
980                            .add_symbol_glyph_quads(
981                                &mut bucket,
982                                symbol_instance,
983                                feature,
984                                symbol_instance.writing_modes,
985                                symbol_instance.placed_right_text_index,
986                                symbol_instance.right_justified_glyph_quads(),
987                                canonical,
988                                last_added_section,
989                            );
990                        last_added_section = Some(new_last_added_section);
991                        symbol_instance.placed_right_text_index = new_placed_index
992                    }
993                    if symbol_instance.center_justified_glyph_quads_size != 0 {
994                        let (new_last_added_section, new_placed_index) = self
995                            .add_symbol_glyph_quads(
996                                &mut bucket,
997                                symbol_instance,
998                                feature,
999                                symbol_instance.writing_modes,
1000                                symbol_instance.placed_center_text_index,
1001                                symbol_instance.center_justified_glyph_quads(),
1002                                canonical,
1003                                last_added_section,
1004                            );
1005                        last_added_section = Some(new_last_added_section);
1006                        symbol_instance.placed_center_text_index = new_placed_index
1007                    }
1008                    if symbol_instance.left_justified_glyph_quads_size != 0 {
1009                        let (new_last_added_section, new_placed_index) = self
1010                            .add_symbol_glyph_quads(
1011                                &mut bucket,
1012                                symbol_instance,
1013                                feature,
1014                                symbol_instance.writing_modes,
1015                                symbol_instance.placed_left_text_index,
1016                                symbol_instance.left_justified_glyph_quads(),
1017                                canonical,
1018                                last_added_section,
1019                            );
1020                        last_added_section = Some(new_last_added_section);
1021                        symbol_instance.placed_left_text_index = new_placed_index
1022                    }
1023                }
1024                if symbol_instance.writing_modes.contains(WritingModeType::Vertical) // TODO is bitset op correct?
1025                    && symbol_instance.vertical_glyph_quads_size != 0
1026                {
1027                    let (new_last_added_section, new_placed_index) = self.add_symbol_glyph_quads(
1028                        &mut bucket,
1029                        symbol_instance,
1030                        feature,
1031                        WritingModeType::Vertical,
1032                        symbol_instance.placed_vertical_text_index,
1033                        symbol_instance.vertical_glyph_quads(),
1034                        canonical,
1035                        last_added_section,
1036                    );
1037                    last_added_section = Some(new_last_added_section);
1038                    symbol_instance.placed_vertical_text_index = new_placed_index
1039                }
1040                assert!(last_added_section.is_some()); // True, as hasText == true;
1041                self.update_paint_properties_for_section(
1042                    &mut bucket,
1043                    feature,
1044                    last_added_section.unwrap(),
1045                    canonical,
1046                );
1047            }
1048
1049            symbol_instance.release_shared_data();
1050        }
1051
1052        if show_collision_boxes {
1053            self.add_to_debug_buffers(&mut bucket);
1054        }
1055        if bucket.has_data() {
1056            for pair in &self.layer_paint_properties {
1057                if !first_load {
1058                    bucket.just_reloaded = true;
1059                }
1060                render_data.insert(
1061                    pair.0.clone(),
1062                    LayerRenderData {
1063                        bucket: bucket.clone(), // TODO is cloning intended here?
1064                        layer_properties: pair.1.clone(),
1065                    },
1066                );
1067            }
1068        }
1069    }
1070
1071    /// maplibre/maplibre-native#4add9ea original name: hasSymbolInstances
1072    fn has_symbol_instances(&self) -> bool {
1073        !self.symbol_instances.is_empty()
1074    }
1075    /// maplibre/maplibre-native#4add9ea original name: hasDependencies
1076    fn has_dependencies(&self) -> bool {
1077        !self.features.is_empty()
1078    }
1079
1080    pub const INVALID_OFFSET_VALUE: f64 = f64::MAX;
1081    /**
1082     * @brief Calculates variable text offset.
1083     *
1084     * @param anchor text anchor
1085     * @param textOffset Either `text-offset` or [ `text-radial-offset`,
1086     * INVALID_OFFSET_VALUE ]
1087     * @return std::array<f64, 2> offset along x- and y- axis correspondingly.
1088     */
1089    /// maplibre/maplibre-native#4add9ea original name: evaluateVariableOffset
1090    pub fn evaluate_variable_offset(anchor: SymbolAnchorType, mut offset: [f64; 2]) -> [f64; 2] {
1091        if offset[1] == Self::INVALID_OFFSET_VALUE {
1092            return evaluate_radial_offset(anchor, offset[0]);
1093        }
1094        let mut result = [0.0, 0.0];
1095        offset[0] = (offset[0]).abs();
1096        offset[1] = (offset[1]).abs();
1097
1098        match anchor {
1099            SymbolAnchorType::TopRight | SymbolAnchorType::TopLeft | SymbolAnchorType::Top => {
1100                result[1] = offset[1] - BASELINE_OFFSET;
1101            }
1102
1103            SymbolAnchorType::BottomRight
1104            | SymbolAnchorType::BottomLeft
1105            | SymbolAnchorType::Bottom => {
1106                result[1] = -offset[1] + BASELINE_OFFSET;
1107            }
1108
1109            SymbolAnchorType::Center | SymbolAnchorType::Left | SymbolAnchorType::Right => {}
1110        }
1111
1112        match anchor {
1113            SymbolAnchorType::TopRight
1114            | SymbolAnchorType::BottomRight
1115            | SymbolAnchorType::Right => {
1116                result[0] = -offset[0];
1117            }
1118            SymbolAnchorType::TopLeft | SymbolAnchorType::BottomLeft | SymbolAnchorType::Left => {
1119                result[0] = offset[0];
1120            }
1121            SymbolAnchorType::Center | SymbolAnchorType::Top | SymbolAnchorType::Bottom => {}
1122        }
1123
1124        result
1125    }
1126
1127    // Analog of `addToLineVertexArray` in JS. This version doesn't need to build up
1128    // a line array like the JS version does, but it uses the same logic to
1129    // calculate tile distances.
1130    /// maplibre/maplibre-native#4add9ea original name: calculateTileDistances
1131    pub fn calculate_tile_distances(line: &GeometryCoordinates, anchor: &Anchor) -> Vec<f64> {
1132        let mut tile_distances: Vec<f64> = vec![0.0; line.len()];
1133        if let Some(segment) = anchor.segment {
1134            assert!(segment < line.len());
1135            let mut sum_forward_length = if segment + 1 < line.len() {
1136                anchor.point.distance_to(line[segment + 1].cast::<f64>())
1137            } else {
1138                0.0
1139            };
1140            let mut sum_backward_length = anchor.point.distance_to(line[segment].cast::<f64>());
1141            for i in segment + 1..line.len() {
1142                tile_distances[i] = sum_forward_length;
1143                if i < line.len() - 1 {
1144                    sum_forward_length +=
1145                        line[i + 1].cast::<f64>().distance_to(line[i].cast::<f64>());
1146                }
1147            }
1148
1149            let mut i = segment;
1150            loop {
1151                tile_distances[i] = sum_backward_length;
1152                if i != 0 {
1153                    sum_backward_length +=
1154                        line[i - 1].cast::<f64>().distance_to(line[i].cast::<f64>());
1155                } else {
1156                    break; // Add break to avoid unsigned integer overflow when i==0
1157                }
1158                i -= 1;
1159            }
1160        }
1161        tile_distances
1162    }
1163}
1164
1165impl SymbolLayout {
1166    /// maplibre/maplibre-native#4add9ea original name: addFeature
1167    fn add_feature(
1168        &mut self,
1169        layout_feature_index: usize,
1170        feature: &SymbolGeometryTileFeature,
1171        shaped_text_orientations: &ShapedTextOrientations,
1172        mut shaped_icon: Option<PositionedIcon>, // TODO should this be an output?
1173        image_map: &ImageMap,
1174        text_offset: [f64; 2],
1175        layout_text_size: f64,
1176        layout_icon_size: f64,
1177        icon_type: SymbolContent,
1178    ) {
1179        let min_scale = 0.5;
1180        let glyph_size = 24.0;
1181
1182        let icon_offset: [f64; 2] =
1183            self.layout
1184                .evaluate::<IconOffset>(self.zoom, feature, self.canonical_id);
1185
1186        // To reduce the number of labels that jump around when zooming we need
1187        // to use a text-size value that is the same for all zoom levels.
1188        // This calculates text-size at a high zoom level so that all tiles can
1189        // use the same value when calculating anchor positions.
1190        let text_max_size = self
1191            .layout
1192            .evaluate::<TextSize>(18., feature, self.canonical_id);
1193
1194        let font_scale = layout_text_size / glyph_size;
1195        let text_box_scale = self.tile_pixel_ratio * font_scale;
1196        let text_max_box_scale = self.tile_pixel_ratio * text_max_size / glyph_size;
1197        let icon_box_scale = self.tile_pixel_ratio * layout_icon_size;
1198        let symbol_spacing = self.tile_pixel_ratio * self.layout.get::<SymbolSpacing>();
1199        let text_padding = self.layout.get::<TextPadding>() * self.tile_pixel_ratio;
1200        let icon_padding = self.layout.get::<IconPadding>() * self.tile_pixel_ratio;
1201        let text_max_angle = deg2radf(self.layout.get::<TextMaxAngle>());
1202        let icon_rotation =
1203            self.layout
1204                .evaluate::<IconRotate>(self.zoom, feature, self.canonical_id);
1205        let text_rotation =
1206            self.layout
1207                .evaluate::<TextRotate>(self.zoom, feature, self.canonical_id);
1208        let variable_text_offset: [f64; 2];
1209        if !self.text_radial_offset.is_undefined() {
1210            variable_text_offset = [
1211                self.layout
1212                    .evaluate::<TextRadialOffset>(self.zoom, feature, self.canonical_id)
1213                    * ONE_EM,
1214                Self::INVALID_OFFSET_VALUE,
1215            ];
1216        } else {
1217            variable_text_offset = [
1218                self.layout
1219                    .evaluate::<TextOffset>(self.zoom, feature, self.canonical_id)[0]
1220                    * ONE_EM,
1221                self.layout
1222                    .evaluate::<TextOffset>(self.zoom, feature, self.canonical_id)[1]
1223                    * ONE_EM,
1224            ];
1225        }
1226
1227        let text_placement: SymbolPlacementType =
1228            if self.layout.get::<TextRotationAlignment>() != AlignmentType::Map {
1229                SymbolPlacementType::Point
1230            } else {
1231                self.layout.get::<SymbolPlacement>()
1232            };
1233
1234        let text_repeat_distance: f64 = symbol_spacing / 2.;
1235        let evaluated_layout_properties: SymbolLayoutProperties_Evaluated =
1236            self.layout.evaluate_feature(self.zoom, feature);
1237        let indexed_feature = IndexedSubfeature {
1238            ref_: RefIndexedSubfeature {
1239                index: feature.index,
1240                sort_index: self.symbol_instances.len(),
1241                source_layer_name: self.source_layer.get_name().to_string(),
1242                bucket_leader_id: self.bucket_leader_id.clone(),
1243                bucket_instance_id: 0,
1244                collision_group_id: 0,
1245            },
1246            source_layer_name_copy: self.source_layer.get_name().to_string(),
1247            bucket_leader_idcopy: self.bucket_leader_id.clone(),
1248        };
1249
1250        let icon_text_fit = evaluated_layout_properties.get::<IconTextFit>();
1251        let has_icon_text_fit = icon_text_fit != IconTextFitType::None;
1252        // Adjust shaped icon size when icon-text-fit is used.
1253        let mut vertically_shaped_icon: Option<PositionedIcon> = None;
1254
1255        if let Some(shaped_icon) = &mut shaped_icon {
1256            if has_icon_text_fit {
1257                // Create vertically shaped icon for vertical writing mode if needed.
1258                if self.allow_vertical_placement
1259                    && shaped_text_orientations.vertical().is_any_line_not_empty()
1260                {
1261                    vertically_shaped_icon = Some(shaped_icon.clone());
1262                    vertically_shaped_icon.as_mut().unwrap().fit_icon_to_text(
1263                        shaped_text_orientations.vertical(),
1264                        icon_text_fit,
1265                        &self.layout.get::<IconTextFitPadding>(),
1266                        &icon_offset,
1267                        font_scale,
1268                    );
1269                }
1270                let shaped_text = get_default_horizontal_shaping(shaped_text_orientations);
1271                if shaped_text.is_any_line_not_empty() {
1272                    shaped_icon.fit_icon_to_text(
1273                        shaped_text,
1274                        icon_text_fit,
1275                        &self.layout.get::<IconTextFitPadding>(),
1276                        &icon_offset,
1277                        font_scale,
1278                    );
1279                }
1280            }
1281        }
1282
1283        let mut add_symbol_instance =
1284            |anchor: &Anchor, shared_data: Rc<SymbolInstanceSharedData>| {
1285                // assert!(sharedData); TODO
1286                let anchor_inside_tile = anchor.point.x >= 0.
1287                    && anchor.point.x < EXTENT
1288                    && anchor.point.y >= 0.
1289                    && anchor.point.y < EXTENT;
1290
1291                if self.mode == MapMode::Tile || anchor_inside_tile {
1292                    // For static/continuous rendering, only add symbols anchored within this tile:
1293                    //  neighboring symbols will be added as part of the neighboring tiles.
1294                    // In tiled rendering mode, add all symbols in the buffers so that we can:
1295                    //  (1) render symbols that overlap into this tile
1296                    //  (2) approximate collision detection effects from neighboring symbols
1297                    self.symbol_instances.push(SymbolInstance::new(
1298                        *anchor,
1299                        shared_data,
1300                        shaped_text_orientations,
1301                        &shaped_icon,
1302                        &vertically_shaped_icon,
1303                        text_box_scale,
1304                        text_padding,
1305                        text_placement,
1306                        text_offset,
1307                        icon_box_scale,
1308                        icon_padding,
1309                        icon_offset,
1310                        indexed_feature.clone(),
1311                        layout_feature_index,
1312                        feature.index,
1313                        if let Some(formatted_text) = &feature.formatted_text {
1314                            formatted_text.raw_text().clone()
1315                        } else {
1316                            U16String::new()
1317                        },
1318                        self.overscaling,
1319                        icon_rotation,
1320                        text_rotation,
1321                        variable_text_offset,
1322                        self.allow_vertical_placement,
1323                        icon_type,
1324                    ));
1325
1326                    if self.sort_features_by_key {
1327                        if !self.sort_key_ranges.is_empty()
1328                            && self.sort_key_ranges.last().unwrap().sort_key == feature.sort_key
1329                        {
1330                            self.sort_key_ranges.last_mut().unwrap().end =
1331                                self.symbol_instances.len();
1332                        } else {
1333                            self.sort_key_ranges.push(SortKeyRange {
1334                                sort_key: feature.sort_key,
1335                                start: self.symbol_instances.len() - 1,
1336                                end: self.symbol_instances.len(),
1337                            });
1338                        }
1339                    }
1340                }
1341            };
1342
1343        let create_symbol_instance_shared_data = |line: GeometryCoordinates| {
1344            Rc::new(SymbolInstanceSharedData::new(
1345                line,
1346                shaped_text_orientations,
1347                shaped_icon.clone(),
1348                vertically_shaped_icon.clone(),
1349                &evaluated_layout_properties,
1350                text_placement,
1351                text_offset,
1352                image_map,
1353                icon_rotation,
1354                icon_type,
1355                has_icon_text_fit,
1356                self.allow_vertical_placement,
1357            ))
1358        };
1359
1360        let type_ = feature.get_type();
1361
1362        if self.layout.get::<SymbolPlacement>() == SymbolPlacementType::Line {
1363            todo!()
1364            /*let clippedLines = clipLines(feature.geometry, 0, 0, EXTENT, EXTENT);
1365            for line in clippedLines {
1366                let anchors: Anchors = getAnchors(
1367                    line,
1368                    symbolSpacing,
1369                    textMaxAngle,
1370                    (if shapedTextOrientations.vertical {
1371                        &shapedTextOrientations.vertical
1372                    } else {
1373                        getDefaultHorizontalShaping(shapedTextOrientations)
1374                    })
1375                    .left,
1376                    (if shapedTextOrientations.vertical {
1377                        &shapedTextOrientations.vertical
1378                    } else {
1379                        getDefaultHorizontalShaping(shapedTextOrientations)
1380                    })
1381                    .right,
1382                    (if shapedIcon { shapedIcon.left() } else { 0 }),
1383                    (if shapedIcon { shapedIcon.right() } else { 0 }),
1384                    glyphSize,
1385                    textMaxBoxScale,
1386                    self.overscaling,
1387                );
1388                let sharedData = createSymbolInstanceSharedData(line);
1389                for anchor in anchors {
1390                    if (!feature.formattedText
1391                        || !self.anchorIsTooClose(
1392                            feature.formattedText.rawText(),
1393                            textRepeatDistance,
1394                            anchor,
1395                        ))
1396                    {
1397                        addSymbolInstance(anchor, sharedData);
1398                    }
1399                }
1400            }*/
1401        } else if self.layout.get::<SymbolPlacement>() == SymbolPlacementType::LineCenter {
1402            todo!()
1403            /*
1404            // No clipping, multiple lines per feature are allowed
1405            // "lines" with only one point are ignored as in clipLines
1406            for line in feature.geometry {
1407                if (line.len() > 1) {
1408                    let anchor: Option<Anchor> = getCenterAnchor(
1409                        line,
1410                        textMaxAngle,
1411                        (if shapedTextOrientations.vertical {
1412                            shapedTextOrientations.vertical
1413                        } else {
1414                            getDefaultHorizontalShaping(shapedTextOrientations)
1415                        })
1416                        .left,
1417                        (if shapedTextOrientations.vertical {
1418                            shapedTextOrientations.vertical
1419                        } else {
1420                            getDefaultHorizontalShaping(shapedTextOrientations)
1421                        })
1422                        .right,
1423                        (if shapedIcon { shapedIcon.left() } else { 0 }),
1424                        (if shapedIcon { shapedIcon.right() } else { 0 }),
1425                        glyphSize,
1426                        textMaxBoxScale,
1427                    );
1428                    if (anchor) {
1429                        addSymbolInstance(*anchor, createSymbolInstanceSharedData(line));
1430                    }
1431                }
1432            }*/
1433        } else if type_ == FeatureType::Polygon {
1434            todo!()
1435            /*for polygon in classifyRings(feature.geometry) {
1436                let poly: Polygon<f64>;
1437                for ring in polygon {
1438                    let r: LinearRing<f64>;
1439                    for p in ring {
1440                        r.push(convertPoint::<double>(p));
1441                    }
1442                    poly.push(r);
1443                }
1444
1445                // 1 pixel worth of precision, in tile coordinates
1446                let poi = mapbox::polylabel(poly, EXTENT / TILE_SIZE);
1447                let anchor = Anchor::new((poi.x) as f64, (poi.y) as f64, 0.0, (minScale) as usize);
1448                addSymbolInstance(anchor, createSymbolInstanceSharedData(polygon[0]));
1449            }*/
1450        } else if type_ == FeatureType::LineString {
1451            for line in &feature.geometry {
1452                // Skip invalid LineStrings.
1453                if line.0.is_empty() {
1454                    continue;
1455                }
1456
1457                let anchor = Anchor {
1458                    point: Point2D::new((line[0].x) as f64, (line[0].y) as f64),
1459                    angle: 0.0,
1460                    segment: Some((min_scale) as usize),
1461                };
1462                add_symbol_instance(&anchor, create_symbol_instance_shared_data(line.clone()));
1463            }
1464        } else if type_ == FeatureType::Point {
1465            for points in &feature.geometry {
1466                for point in &points.0 {
1467                    let anchor = Anchor {
1468                        point: Point2D::new((point.x) as f64, (point.y) as f64),
1469                        angle: 0.0,
1470                        segment: Some((min_scale) as usize),
1471                    };
1472                    add_symbol_instance(
1473                        &anchor,
1474                        create_symbol_instance_shared_data(GeometryCoordinates(vec![*point])),
1475                    );
1476                }
1477            }
1478        }
1479    }
1480
1481    /// maplibre/maplibre-native#4add9ea original name: anchorIsTooClose
1482    fn anchor_is_too_close(
1483        &mut self,
1484        text: &U16String,
1485        repeat_distance: f64,
1486        anchor: &Anchor,
1487    ) -> bool {
1488        if let Some(other_anchors) = self.compare_text.get(text) {
1489            for other_anchor in other_anchors {
1490                if anchor.point.distance_to(other_anchor.point) < repeat_distance {
1491                    return true;
1492                }
1493            }
1494        } else {
1495            self.compare_text.insert(text.clone(), Anchors::new());
1496        }
1497
1498        let anchors = self.compare_text.get_mut(text).unwrap();
1499        anchors.push(*anchor);
1500        false
1501    }
1502
1503    /// maplibre/maplibre-native#4add9ea original name: addToDebugBuffers
1504    fn add_to_debug_buffers(&self, bucket: &mut SymbolBucket) {
1505        todo!()
1506    }
1507
1508    // Adds placed items to the buffer.
1509    /// maplibre/maplibre-native#4add9ea original name: addSymbol
1510    fn add_symbol(
1511        &self,
1512        buffer: &mut SymbolBucketBuffer,
1513        size_data: Range<f64>,
1514        symbol: &SymbolQuad,
1515        label_anchor: &Anchor,
1516        sort_key: f64,
1517    ) -> usize {
1518        let vertex_length: u16 = 4;
1519
1520        let tl = symbol.tl;
1521        let tr = symbol.tr;
1522        let bl = symbol.bl;
1523        let br = symbol.br;
1524        let tex = symbol.tex;
1525        let pixel_offset_tl = symbol.pixel_offset_tl;
1526        let pixel_offset_br = symbol.pixel_offset_br;
1527        let min_font_scale = symbol.min_font_scale;
1528
1529        if buffer.segments.is_empty()
1530            || buffer.segments.last().unwrap().vertex_length + vertex_length as usize
1531                > u16::MAX as usize
1532            || (buffer.segments.last().unwrap().sort_key - sort_key).abs() > f64::EPSILON
1533        {
1534            buffer.segments.push(Segment {
1535                vertex_offset: buffer.shared_vertices.len(),
1536                index_offset: buffer.triangles.len(),
1537                vertex_length: 0,
1538                index_length: 0,
1539                sort_key,
1540                _phandom_data: Default::default(),
1541            });
1542        }
1543
1544        // We're generating triangle fans, so we always start with the first
1545        // coordinate in this polygon.
1546        let segment = buffer.segments.last_mut().unwrap();
1547        assert!(segment.vertex_length <= u16::MAX as usize);
1548        let index = (segment.vertex_length) as u16;
1549
1550        // coordinates (2 triangles)
1551        let vertices = &mut buffer.shared_vertices;
1552        vertices.push(SymbolVertex::new(
1553            label_anchor.point,
1554            tl,
1555            symbol.glyph_offset.y,
1556            tex.origin.x,
1557            tex.origin.y,
1558            size_data.clone(),
1559            symbol.is_sdf,
1560            pixel_offset_tl,
1561            min_font_scale,
1562        ));
1563        vertices.push(SymbolVertex::new(
1564            label_anchor.point,
1565            tr,
1566            symbol.glyph_offset.y,
1567            tex.origin.x + tex.width(),
1568            tex.origin.y,
1569            size_data.clone(),
1570            symbol.is_sdf,
1571            Point2D::new(pixel_offset_br.x, pixel_offset_tl.y),
1572            min_font_scale,
1573        ));
1574        vertices.push(SymbolVertex::new(
1575            label_anchor.point,
1576            bl,
1577            symbol.glyph_offset.y,
1578            tex.origin.x,
1579            tex.origin.y + tex.height(),
1580            size_data.clone(),
1581            symbol.is_sdf,
1582            Point2D::new(pixel_offset_tl.x, pixel_offset_br.y),
1583            min_font_scale,
1584        ));
1585        vertices.push(SymbolVertex::new(
1586            label_anchor.point,
1587            br,
1588            symbol.glyph_offset.y,
1589            tex.origin.x + tex.width(),
1590            tex.origin.y + tex.height(),
1591            size_data.clone(),
1592            symbol.is_sdf,
1593            pixel_offset_br,
1594            min_font_scale,
1595        ));
1596
1597        // Dynamic/Opacity vertices are initialized so that the vertex count always
1598        // agrees with the layout vertex buffer, but they will always be updated
1599        // before rendering happens
1600        let dynamic_vertex = DynamicVertex::new(label_anchor.point, 0.);
1601        buffer.shared_dynamic_vertices.push(dynamic_vertex);
1602        buffer.shared_dynamic_vertices.push(dynamic_vertex);
1603        buffer.shared_dynamic_vertices.push(dynamic_vertex);
1604        buffer.shared_dynamic_vertices.push(dynamic_vertex);
1605
1606        let opacity_vertex = OpacityVertex::new(true, 1.0);
1607        buffer.shared_opacity_vertices.push(opacity_vertex);
1608        buffer.shared_opacity_vertices.push(opacity_vertex);
1609        buffer.shared_opacity_vertices.push(opacity_vertex);
1610        buffer.shared_opacity_vertices.push(opacity_vertex);
1611
1612        // add the two triangles, referencing the four coordinates we just inserted.
1613        buffer.triangles.push(index, index + 1, index + 2);
1614        buffer.triangles.push(index + 1, index + 2, index + 3);
1615
1616        segment.vertex_length += vertex_length as usize;
1617        segment.index_length += 6;
1618
1619        index as usize
1620    }
1621    /// maplibre/maplibre-native#4add9ea original name: addSymbols
1622    fn add_symbols(
1623        &self,
1624        buffer: &mut SymbolBucketBuffer,
1625        size_data: Range<f64>,
1626        symbols: &SymbolQuads,
1627        label_anchor: &Anchor,
1628        placed_symbol: &mut PlacedSymbol,
1629        sort_key: f64,
1630    ) -> usize {
1631        let mut first_symbol = true;
1632        let mut first_index = 0;
1633        for symbol in symbols {
1634            let index = self.add_symbol(buffer, size_data.clone(), symbol, label_anchor, sort_key);
1635            placed_symbol.glyph_offsets.push(symbol.glyph_offset.x);
1636            if first_symbol {
1637                first_index = index;
1638                first_symbol = false;
1639            }
1640        }
1641        first_index
1642    }
1643
1644    // Adds symbol quads to bucket and returns formatted section index of last
1645    // added quad.
1646    /// maplibre/maplibre-native#4add9ea original name: addSymbolGlyphQuads
1647    fn add_symbol_glyph_quads(
1648        &self,
1649        bucket: &mut SymbolBucket,
1650        symbol_instance: &SymbolInstance,
1651        feature: &SymbolGeometryTileFeature,
1652        writing_mode: WritingModeType,
1653        placed_index: Option<usize>, // TODO should this be an output?
1654        glyph_quads: &SymbolQuads,
1655        canonical: &CanonicalTileID,
1656        mut last_added_section: Option<usize>, // TODO should this be an output?
1657    ) -> (usize, Option<usize>) {
1658        let mut output_placed_index = placed_index;
1659        let size_data: Range<f64> = bucket.text_size_binder.get_vertex_size_data(feature); // TODO verify if usage of range is oke, empty ranges, reverse
1660        let has_format_section_overrides: bool = bucket.has_format_section_overrides();
1661        let placed_icon_index = if writing_mode == WritingModeType::Vertical {
1662            symbol_instance.placed_vertical_icon_index
1663        } else {
1664            symbol_instance.placed_icon_index
1665        };
1666
1667        // TODO is this PlacedSymbol correct?
1668        let mut newly_placed_symbol = PlacedSymbol {
1669            anchor_point: symbol_instance.anchor.point,
1670            segment: symbol_instance.anchor.segment.unwrap_or(0),
1671            lower_size: size_data.start,
1672            upper_size: size_data.end,
1673            line_offset: symbol_instance.text_offset,
1674            writing_modes: writing_mode,
1675            line: symbol_instance.line().clone(),
1676            tile_distances: Self::calculate_tile_distances(
1677                symbol_instance.line(),
1678                &symbol_instance.anchor,
1679            ),
1680
1681            glyph_offsets: vec![],
1682            hidden: false,
1683            vertex_start_index: 0,
1684            cross_tile_id: 0,
1685            placed_orientation: None,
1686            angle: if self.allow_vertical_placement && writing_mode == WritingModeType::Vertical {
1687                PI / 2.
1688            } else {
1689                0.0
1690            },
1691            placed_icon_index,
1692        };
1693
1694        let mut first_symbol = true;
1695        for symbol_quad in glyph_quads {
1696            if has_format_section_overrides {
1697                if let Some(last_added_section) = last_added_section {
1698                    if last_added_section != symbol_quad.section_index {
1699                        self.update_paint_properties_for_section(
1700                            bucket,
1701                            feature,
1702                            last_added_section,
1703                            canonical,
1704                        );
1705                    }
1706                }
1707
1708                last_added_section = Some(symbol_quad.section_index);
1709            }
1710            let index = self.add_symbol(
1711                &mut bucket.text,
1712                size_data.clone(),
1713                symbol_quad,
1714                &symbol_instance.anchor,
1715                feature.sort_key,
1716            );
1717
1718            newly_placed_symbol
1719                .glyph_offsets
1720                .push(symbol_quad.glyph_offset.x);
1721
1722            if first_symbol {
1723                newly_placed_symbol.vertex_start_index = index;
1724                first_symbol = false;
1725            }
1726        }
1727
1728        bucket.text.placed_symbols.push(newly_placed_symbol);
1729        output_placed_index = Some(bucket.text.placed_symbols.len() - 1);
1730
1731        if let Some(last_added_section) = last_added_section {
1732            (last_added_section, output_placed_index)
1733        } else {
1734            (0, output_placed_index)
1735        }
1736    }
1737
1738    /// maplibre/maplibre-native#4add9ea original name: updatePaintPropertiesForSection
1739    fn update_paint_properties_for_section(
1740        &self,
1741        bucket: &SymbolBucket,
1742        feature: &SymbolGeometryTileFeature,
1743        section_index: usize,
1744        canonical: &CanonicalTileID,
1745    ) -> usize {
1746        let formatted_section = section_options_to_value(
1747            feature
1748                .formatted_text
1749                .as_ref()
1750                .unwrap()
1751                .section_at(section_index),
1752        );
1753        // todo!()
1754        // for pair in bucket.paintProperties {
1755        //     pair.1.textBinders.populateVertexVectors(
1756        //         feature,
1757        //         bucket.text.vertices().elements(),
1758        //         feature.index,
1759        //         {},
1760        //         {},
1761        //         canonical,
1762        //         formattedSection,
1763        //     );
1764        // }
1765        0
1766    }
1767}
1768
1769#[cfg(test)]
1770mod tests {
1771    use std::collections::HashMap;
1772
1773    use crate::{
1774        euclid::{Point2D, Rect, Size2D},
1775        legacy::{
1776            bidi::Char16,
1777            font_stack::FontStackHasher,
1778            geometry_tile_data::{GeometryCoordinates, SymbolGeometryTileLayer},
1779            glyph::{Glyph, GlyphDependencies, GlyphMap, GlyphMetrics, Glyphs},
1780            glyph_atlas::{GlyphPosition, GlyphPositionMap, GlyphPositions},
1781            image::ImageMap,
1782            image_atlas::ImagePositions,
1783            layout::{
1784                layout::{BucketParameters, LayerTypeInfo, LayoutParameters},
1785                symbol_feature::{SymbolGeometryTileFeature, VectorGeometryTileFeature},
1786                symbol_layout::{FeatureIndex, LayerProperties, SymbolLayer, SymbolLayout},
1787            },
1788            style_types::SymbolLayoutProperties_Unevaluated,
1789            tagged_string::SectionOptions,
1790            CanonicalTileID, MapMode, OverscaledTileID,
1791        },
1792    };
1793
1794    #[test]
1795    /// maplibre/maplibre-native#4add9ea original name: test
1796    fn test() {
1797        let font_stack = vec![
1798            "Open Sans Regular".to_string(),
1799            "Arial Unicode MS Regular".to_string(),
1800        ];
1801
1802        let section_options = SectionOptions::new(1.0, font_stack.clone(), None);
1803
1804        let mut glyph_dependencies = GlyphDependencies::new();
1805
1806        let tile_id = OverscaledTileID {
1807            canonical: CanonicalTileID { x: 0, y: 0, z: 0 },
1808            overscaled_z: 0,
1809        };
1810        let mut parameters = BucketParameters {
1811            tile_id: tile_id,
1812            mode: MapMode::Continuous,
1813            pixel_ratio: 1.0,
1814            layer_type: LayerTypeInfo,
1815        };
1816        let layer_data = SymbolGeometryTileLayer {
1817            name: "layer".to_string(),
1818            features: vec![SymbolGeometryTileFeature::new(Box::new(
1819                VectorGeometryTileFeature {
1820                    geometry: vec![GeometryCoordinates(vec![Point2D::new(1024, 1024)])],
1821                },
1822            ))],
1823        };
1824        let layer_properties = vec![LayerProperties {
1825            id: "layer".to_string(),
1826            layer: SymbolLayer {
1827                layout: SymbolLayoutProperties_Unevaluated,
1828            },
1829        }];
1830
1831        let image_positions = ImagePositions::new();
1832
1833        let mut glyph_position = GlyphPosition {
1834            rect: Rect::new(Point2D::new(0, 0), Size2D::new(10, 10)),
1835            metrics: GlyphMetrics {
1836                width: 18,
1837                height: 18,
1838                left: 2,
1839                top: -8,
1840                advance: 21,
1841            },
1842        };
1843        let glyph_positions: GlyphPositions = GlyphPositions::from([(
1844            FontStackHasher::new(&font_stack),
1845            GlyphPositionMap::from([('中' as Char16, glyph_position)]),
1846        )]);
1847
1848        let mut glyph = Glyph::default();
1849        glyph.id = '中' as Char16;
1850        glyph.metrics = glyph_position.metrics;
1851
1852        let glyphs: GlyphMap = GlyphMap::from([(
1853            FontStackHasher::new(&font_stack),
1854            Glyphs::from([('中' as Char16, Some(glyph))]),
1855        )]);
1856
1857        let mut layout = SymbolLayout::new(
1858            &parameters,
1859            &layer_properties,
1860            Box::new(layer_data),
1861            &mut LayoutParameters {
1862                bucket_parameters: &mut parameters.clone(),
1863                glyph_dependencies: &mut glyph_dependencies,
1864                image_dependencies: &mut Default::default(),
1865                available_images: &mut Default::default(),
1866            },
1867        )
1868        .unwrap();
1869
1870        assert_eq!(glyph_dependencies.len(), 1);
1871
1872        let empty_image_map = ImageMap::new();
1873        layout.prepare_symbols(
1874            &glyphs,
1875            &glyph_positions,
1876            &empty_image_map,
1877            &image_positions,
1878        );
1879
1880        let mut output = HashMap::new();
1881        layout.create_bucket(
1882            image_positions,
1883            Box::new(FeatureIndex),
1884            &mut output,
1885            false,
1886            false,
1887            &tile_id.canonical,
1888        );
1889
1890        println!(
1891            "{:#?}",
1892            output.get("layer").unwrap().bucket.text.shared_vertices
1893        )
1894    }
1895}