maplibre/legacy/layout/
symbol_instance.rs

1//! Translated from https://github.com/maplibre/maplibre-native/blob/4add9ea/src/mbgl/layout/symbol_instance.cpp
2
3use std::rc::Rc;
4
5use bitflags::bitflags;
6use widestring::U16String;
7
8use crate::legacy::{
9    collision_feature::CollisionFeature,
10    geometry::{anchor::Anchor, feature_index::IndexedSubfeature},
11    geometry_tile_data::GeometryCoordinates,
12    glyph::{Shaping, WritingModeType},
13    image::ImageMap,
14    quads::{get_glyph_quads, get_icon_quads, SymbolQuads},
15    shaping::PositionedIcon,
16    style_types::{SymbolLayoutProperties_Evaluated, SymbolPlacementType},
17};
18
19/// maplibre/maplibre-native#4add9ea original name: getAnyShaping
20fn get_any_shaping(shaped_text_orientations: &ShapedTextOrientations) -> &Shaping {
21    if shaped_text_orientations.right().is_any_line_not_empty() {
22        return shaped_text_orientations.right();
23    }
24    if shaped_text_orientations.center.is_any_line_not_empty() {
25        return &(shaped_text_orientations.center);
26    }
27    if shaped_text_orientations.left.is_any_line_not_empty() {
28        return &(shaped_text_orientations.left);
29    }
30    if shaped_text_orientations.vertical.is_any_line_not_empty() {
31        return &(shaped_text_orientations.vertical);
32    }
33    &shaped_text_orientations.horizontal
34}
35
36/// maplibre/maplibre-native#4add9ea original name: ShapedTextOrientations
37#[derive(Default)]
38pub struct ShapedTextOrientations {
39    horizontal: Shaping,
40    vertical: Shaping,
41    // The following are used with variable text placement on, including right()
42    center: Shaping,
43    left: Shaping,
44    pub single_line: bool,
45}
46
47impl ShapedTextOrientations {
48    /// maplibre/maplibre-native#4add9ea original name: new
49    pub fn new(
50        horizontal: Shaping,
51        vertical: Shaping,
52        right: Option<Shaping>,
53        center: Shaping,
54        left: Shaping,
55        single_line: bool,
56    ) -> Self {
57        Self {
58            horizontal: (horizontal),
59            vertical: (vertical),
60            center: (center),
61            left: (left),
62            single_line: single_line,
63        }
64    }
65
66    /// maplibre/maplibre-native#4add9ea original name: horizontal
67    pub fn horizontal(&self) -> &Shaping {
68        &self.horizontal
69    }
70    /// maplibre/maplibre-native#4add9ea original name: vertical
71    pub fn vertical(&self) -> &Shaping {
72        &self.vertical
73    }
74    /// maplibre/maplibre-native#4add9ea original name: right
75    pub fn right(&self) -> &Shaping {
76        &self.horizontal
77    }
78    /// maplibre/maplibre-native#4add9ea original name: center
79    pub fn center(&self) -> &Shaping {
80        &self.center
81    }
82    /// maplibre/maplibre-native#4add9ea original name: left
83    pub fn left(&self) -> &Shaping {
84        &self.left
85    }
86
87    /// maplibre/maplibre-native#4add9ea original name: set_horizontal
88    pub fn set_horizontal(&mut self, horizontal: Shaping) {
89        self.horizontal = horizontal;
90    }
91    /// maplibre/maplibre-native#4add9ea original name: set_vertical
92    pub fn set_vertical(&mut self, vertical: Shaping) {
93        self.vertical = vertical;
94    }
95}
96
97bitflags! {
98    /// maplibre/maplibre-native#4add9ea original name: SymbolContent:
99#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
100    pub struct SymbolContent: u8 {
101         const None = 0;
102         const Text = 1 << 0;
103         const IconRGBA = 1 << 1;
104         const IconSDF = 1 << 2;
105    }
106}
107
108/// maplibre/maplibre-native#4add9ea original name: SymbolInstanceSharedData
109#[derive(Default)]
110pub struct SymbolInstanceSharedData {
111    line: GeometryCoordinates,
112    // Note: When singleLine == true, only `rightJustifiedGlyphQuads` is populated.
113    right_justified_glyph_quads: SymbolQuads,
114    center_justified_glyph_quads: SymbolQuads,
115    left_justified_glyph_quads: SymbolQuads,
116    vertical_glyph_quads: SymbolQuads,
117    icon_quads: Option<SymbolQuads>,
118    vertical_icon_quads: Option<SymbolQuads>,
119}
120
121impl SymbolInstanceSharedData {
122    /// maplibre/maplibre-native#4add9ea original name: new
123    pub fn new(
124        line_: GeometryCoordinates,
125        shaped_text_orientations: &ShapedTextOrientations,
126        shaped_icon: Option<PositionedIcon>,
127        vertically_shaped_icon: Option<PositionedIcon>,
128        layout: &SymbolLayoutProperties_Evaluated,
129        text_placement: SymbolPlacementType,
130        text_offset: [f64; 2],
131        image_map: &ImageMap,
132        icon_rotation: f64,
133        icon_type: SymbolContent,
134        has_icon_text_fit: bool,
135        allow_vertical_placement: bool,
136    ) -> Self {
137        let mut self_ = Self {
138            line: line_,
139            ..Self::default()
140        };
141        // Create the quads used for rendering the icon and glyphs.
142        if let Some(shaped_icon) = &shaped_icon {
143            self_.icon_quads = Some(get_icon_quads(
144                shaped_icon,
145                icon_rotation,
146                icon_type,
147                has_icon_text_fit,
148            ));
149            if let Some(vertically_shaped_icon) = &vertically_shaped_icon {
150                self_.vertical_icon_quads = Some(get_icon_quads(
151                    vertically_shaped_icon,
152                    icon_rotation,
153                    icon_type,
154                    has_icon_text_fit,
155                ));
156            }
157        }
158
159        // todo is this translation correct?
160        if !shaped_text_orientations.single_line {
161            if shaped_text_orientations.right().is_any_line_not_empty() {
162                self_.right_justified_glyph_quads = get_glyph_quads(
163                    shaped_text_orientations.right(),
164                    text_offset,
165                    layout,
166                    text_placement,
167                    image_map,
168                    allow_vertical_placement,
169                );
170            }
171
172            if shaped_text_orientations.center.is_any_line_not_empty() {
173                self_.center_justified_glyph_quads = get_glyph_quads(
174                    &shaped_text_orientations.center,
175                    text_offset,
176                    layout,
177                    text_placement,
178                    image_map,
179                    allow_vertical_placement,
180                );
181            }
182
183            if shaped_text_orientations.left.is_any_line_not_empty() {
184                self_.left_justified_glyph_quads = get_glyph_quads(
185                    &shaped_text_orientations.left,
186                    text_offset,
187                    layout,
188                    text_placement,
189                    image_map,
190                    allow_vertical_placement,
191                );
192            }
193        } else {
194            let shape = if shaped_text_orientations.right().is_any_line_not_empty() {
195                Some(shaped_text_orientations.right())
196            } else if shaped_text_orientations.center.is_any_line_not_empty() {
197                Some(&shaped_text_orientations.center)
198            } else if shaped_text_orientations.left.is_any_line_not_empty() {
199                Some(&shaped_text_orientations.left)
200            } else {
201                None
202            };
203
204            if let Some(shape) = shape {
205                self_.right_justified_glyph_quads = get_glyph_quads(
206                    shape,
207                    text_offset,
208                    layout,
209                    text_placement,
210                    image_map,
211                    allow_vertical_placement,
212                );
213            }
214        }
215
216        if shaped_text_orientations.vertical.is_any_line_not_empty() {
217            self_.vertical_glyph_quads = get_glyph_quads(
218                &shaped_text_orientations.vertical,
219                text_offset,
220                layout,
221                text_placement,
222                image_map,
223                allow_vertical_placement,
224            );
225        }
226        self_
227    }
228    /// maplibre/maplibre-native#4add9ea original name: empty
229    fn empty(&self) -> bool {
230        self.right_justified_glyph_quads.is_empty()
231            && self.center_justified_glyph_quads.is_empty()
232            && self.left_justified_glyph_quads.is_empty()
233            && self.vertical_glyph_quads.is_empty()
234    }
235}
236
237/// maplibre/maplibre-native#4add9ea original name: SymbolInstance
238#[derive(Clone)]
239pub struct SymbolInstance {
240    shared_data: Rc<SymbolInstanceSharedData>,
241
242    pub anchor: Anchor,
243    pub symbol_content: SymbolContent,
244
245    pub right_justified_glyph_quads_size: usize,
246    pub center_justified_glyph_quads_size: usize,
247    pub left_justified_glyph_quads_size: usize,
248    pub vertical_glyph_quads_size: usize,
249    pub icon_quads_size: usize,
250
251    pub text_collision_feature: CollisionFeature,
252    pub icon_collision_feature: CollisionFeature,
253    pub vertical_text_collision_feature: Option<CollisionFeature>,
254    pub vertical_icon_collision_feature: Option<CollisionFeature>,
255    pub writing_modes: WritingModeType,
256    pub layout_feature_index: usize, // Index into the set of features included at layout time
257    pub data_feature_index: usize,   // Index into the underlying tile data feature set
258    pub text_offset: [f64; 2],
259    pub icon_offset: [f64; 2],
260    pub key: U16String,
261    pub placed_right_text_index: Option<usize>,
262    pub placed_center_text_index: Option<usize>,
263    pub placed_left_text_index: Option<usize>,
264    pub placed_vertical_text_index: Option<usize>,
265    pub placed_icon_index: Option<usize>,
266    pub placed_vertical_icon_index: Option<usize>,
267    pub text_box_scale: f64,
268    pub variable_text_offset: [f64; 2],
269    pub single_line: bool,
270    pub cross_tile_id: u32,
271}
272
273impl SymbolInstance {
274    /// maplibre/maplibre-native#4add9ea original name: new
275    pub fn new(
276        anchor_: Anchor,
277        shared_data: Rc<SymbolInstanceSharedData>,
278        shaped_text_orientations: &ShapedTextOrientations,
279        shaped_icon: &Option<PositionedIcon>,
280        vertically_shaped_icon: &Option<PositionedIcon>,
281        text_box_scale: f64,
282        text_padding: f64,
283        text_placement: SymbolPlacementType,
284        text_offset: [f64; 2],
285        icon_box_scale: f64,
286        icon_padding: f64,
287        icon_offset: [f64; 2],
288        indexed_feature: IndexedSubfeature,
289        layout_feature_index: usize,
290        data_feature_index: usize,
291        key_: U16String,
292        overscaling: f64,
293        icon_rotation: f64,
294        text_rotation: f64,
295        variable_text_offset: [f64; 2],
296        allow_vertical_placement: bool,
297        icon_type: SymbolContent,
298    ) -> Self {
299        let mut self_ = Self {
300            symbol_content: icon_type,
301            // Create the collision features that will be used to check whether this
302            // symbol instance can be placed As a collision approximation, we can use
303            // either the vertical or any of the horizontal versions of the feature
304            text_collision_feature: CollisionFeature::new_from_text(
305                &shared_data.line,
306                &anchor_,
307                get_any_shaping(shaped_text_orientations).clone(),
308                text_box_scale,
309                text_padding,
310                text_placement,
311                indexed_feature.clone(),
312                overscaling,
313                text_rotation,
314            ),
315            icon_collision_feature: CollisionFeature::new_from_icon(
316                &shared_data.line,
317                &anchor_,
318                shaped_icon,
319                icon_box_scale,
320                icon_padding,
321                indexed_feature.clone(),
322                icon_rotation,
323            ),
324
325            shared_data: shared_data,
326            anchor: anchor_,
327            writing_modes: WritingModeType::None,
328            layout_feature_index: layout_feature_index,
329            data_feature_index: data_feature_index,
330            text_offset: text_offset,
331            icon_offset: icon_offset,
332            key: key_,
333
334            text_box_scale: text_box_scale,
335            variable_text_offset: variable_text_offset,
336            single_line: shaped_text_orientations.single_line,
337
338            right_justified_glyph_quads_size: 0,
339            center_justified_glyph_quads_size: 0,
340            left_justified_glyph_quads_size: 0,
341            vertical_glyph_quads_size: 0,
342            icon_quads_size: 0,
343
344            vertical_text_collision_feature: None,
345            placed_right_text_index: None,
346            placed_center_text_index: None,
347            placed_left_text_index: None,
348            placed_vertical_text_index: None,
349            placed_icon_index: None,
350            placed_vertical_icon_index: None,
351
352            vertical_icon_collision_feature: None,
353            cross_tile_id: 0,
354        };
355
356        // 'hasText' depends on finding at least one glyph in the shaping that's also in the GlyphPositionMap
357        if !self_.shared_data.empty() {
358            self_.symbol_content |= SymbolContent::Text;
359        }
360        if allow_vertical_placement && shaped_text_orientations.vertical.is_any_line_not_empty() {
361            let vertical_point_label_angle = 90.0;
362            self_.vertical_text_collision_feature = Some(CollisionFeature::new_from_text(
363                self_.line(),
364                &self_.anchor,
365                shaped_text_orientations.vertical.clone(),
366                text_box_scale,
367                text_padding,
368                text_placement,
369                indexed_feature.clone(),
370                overscaling,
371                text_rotation + vertical_point_label_angle,
372            ));
373            if vertically_shaped_icon.is_some() {
374                self_.vertical_icon_collision_feature = Some(CollisionFeature::new_from_icon(
375                    &self_.shared_data.line,
376                    &self_.anchor,
377                    vertically_shaped_icon,
378                    icon_box_scale,
379                    icon_padding,
380                    indexed_feature,
381                    icon_rotation + vertical_point_label_angle,
382                ));
383            }
384        }
385
386        self_.right_justified_glyph_quads_size =
387            self_.shared_data.right_justified_glyph_quads.len();
388        self_.center_justified_glyph_quads_size =
389            self_.shared_data.center_justified_glyph_quads.len();
390        self_.left_justified_glyph_quads_size = self_.shared_data.left_justified_glyph_quads.len();
391        self_.vertical_glyph_quads_size = self_.shared_data.vertical_glyph_quads.len();
392
393        self_.icon_quads_size = if let Some(icon_quads) = &self_.shared_data.icon_quads {
394            icon_quads.len()
395        } else {
396            0
397        };
398
399        if self_.right_justified_glyph_quads_size != 0
400            || self_.center_justified_glyph_quads_size != 0
401            || self_.left_justified_glyph_quads_size != 0
402        {
403            self_.writing_modes |= WritingModeType::Horizontal;
404        }
405
406        if self_.vertical_glyph_quads_size != 0 {
407            self_.writing_modes |= WritingModeType::Vertical;
408        }
409
410        self_
411    }
412    /// maplibre/maplibre-native#4add9ea original name: getDefaultHorizontalPlacedTextIndex
413    pub fn get_default_horizontal_placed_text_index(&self) -> Option<usize> {
414        if let Some(index) = self.placed_right_text_index {
415            return Some(index);
416        }
417        if let Some(index) = self.placed_center_text_index {
418            return Some(index);
419        }
420        if let Some(index) = self.placed_left_text_index {
421            return Some(index);
422        }
423        None
424    }
425    /// maplibre/maplibre-native#4add9ea original name: line
426    pub fn line(&self) -> &GeometryCoordinates {
427        &self.shared_data.line
428    }
429    /// maplibre/maplibre-native#4add9ea original name: rightJustifiedGlyphQuads
430    pub fn right_justified_glyph_quads(&self) -> &SymbolQuads {
431        &self.shared_data.right_justified_glyph_quads
432    }
433    /// maplibre/maplibre-native#4add9ea original name: leftJustifiedGlyphQuads
434    pub fn left_justified_glyph_quads(&self) -> &SymbolQuads {
435        &self.shared_data.left_justified_glyph_quads
436    }
437    /// maplibre/maplibre-native#4add9ea original name: centerJustifiedGlyphQuads
438    pub fn center_justified_glyph_quads(&self) -> &SymbolQuads {
439        &self.shared_data.center_justified_glyph_quads
440    }
441    /// maplibre/maplibre-native#4add9ea original name: verticalGlyphQuads
442    pub fn vertical_glyph_quads(&self) -> &SymbolQuads {
443        &self.shared_data.vertical_glyph_quads
444    }
445    /// maplibre/maplibre-native#4add9ea original name: hasText
446    pub fn has_text(&self) -> bool {
447        self.symbol_content.contains(SymbolContent::Text) // TODO Is this correct?
448    }
449    /// maplibre/maplibre-native#4add9ea original name: hasIcon
450    pub fn has_icon(&self) -> bool {
451        self.symbol_content.contains(SymbolContent::IconRGBA) || self.has_sdf_icon()
452    }
453    /// maplibre/maplibre-native#4add9ea original name: hasSdfIcon
454    pub fn has_sdf_icon(&self) -> bool {
455        self.symbol_content.contains(SymbolContent::IconSDF)
456    }
457    /// maplibre/maplibre-native#4add9ea original name: iconQuads
458    pub fn icon_quads(&self) -> &Option<SymbolQuads> {
459        &self.shared_data.icon_quads
460    }
461    /// maplibre/maplibre-native#4add9ea original name: verticalIconQuads
462    pub fn vertical_icon_quads(&self) -> &Option<SymbolQuads> {
463        &self.shared_data.vertical_icon_quads
464    }
465    /// maplibre/maplibre-native#4add9ea original name: releaseSharedData
466    pub fn release_shared_data(&self) {
467        // todo!()
468        // TODO not sure how to do this self.sharedData.reset();
469    }
470
471    /// maplibre/maplibre-native#4add9ea original name: invalidCrossTileID
472    fn invalid_cross_tile_id() -> u32 {
473        u32::MAX
474    }
475}
476
477/// maplibre/maplibre-native#4add9ea original name: SymbolInstanceReferences
478type SymbolInstanceReferences = Vec<SymbolInstance>;