maplibre/legacy/
quads.rs

1//! Translated from https://github.com/maplibre/maplibre-native/blob/4add9ea/src/mbgl/text/quads.cpp
2
3use std::f64::consts::PI;
4
5use crate::{
6    euclid::{Point2D, Rect, Size2D, Vector2D},
7    legacy::{
8        glyph::{Shaping, WritingModeType},
9        image::{ImageMap, ImageStretches},
10        image_atlas::ImagePosition,
11        layout::symbol_instance::SymbolContent,
12        shaping::PositionedIcon,
13        style_types::{
14            AlignmentType, SymbolLayoutProperties_Evaluated, SymbolPlacementType, TextRotate,
15            TextRotationAlignment,
16        },
17        util::{
18            constants::ONE_EM,
19            math::{deg2radf, rotate},
20        },
21        TileSpace,
22    },
23};
24
25/// maplibre/maplibre-native#4add9ea original name: SymbolQuad
26pub struct SymbolQuad {
27    pub tl: Point2D<f64, TileSpace>,
28    pub tr: Point2D<f64, TileSpace>,
29    pub bl: Point2D<f64, TileSpace>,
30    pub br: Point2D<f64, TileSpace>,
31    pub tex: Rect<u16, TileSpace>,
32    pub pixel_offset_tl: Point2D<f64, TileSpace>,
33    pub pixel_offset_br: Point2D<f64, TileSpace>,
34    pub glyph_offset: Point2D<f64, TileSpace>,
35    pub writing_mode: WritingModeType,
36    pub is_sdf: bool,
37    pub section_index: usize,
38    pub min_font_scale: Point2D<f64, TileSpace>,
39}
40
41/// maplibre/maplibre-native#4add9ea original name: SymbolQuads
42pub type SymbolQuads = Vec<SymbolQuad>;
43
44const BORDER: u16 = ImagePosition::PADDING;
45
46/// maplibre/maplibre-native#4add9ea original name: computeStretchSum
47fn compute_stretch_sum(stretches: &ImageStretches) -> f64 {
48    let mut sum = 0.;
49    for stretch in stretches {
50        sum += stretch.1 - stretch.0;
51    }
52    sum
53}
54
55/// maplibre/maplibre-native#4add9ea original name: sumWithinRange
56fn sum_within_range(stretches: &ImageStretches, min: f64, max: f64) -> f64 {
57    let mut sum = 0.;
58    for stretch in stretches {
59        sum += min.max(max.min(stretch.1)) - min.max(max.min(stretch.0));
60    }
61    sum
62}
63
64/// maplibre/maplibre-native#4add9ea original name: getEmOffset
65fn get_em_offset(stretch_offset: f64, stretch_size: f64, icon_size: f64, icon_offset: f64) -> f64 {
66    icon_offset + icon_size * stretch_offset / stretch_size
67}
68
69/// maplibre/maplibre-native#4add9ea original name: getPxOffset
70fn get_px_offset(
71    fixed_offset: f64,
72    fixed_size: f64,
73    stretch_offset: f64,
74    stretch_size: f64,
75) -> f64 {
76    fixed_offset - fixed_size * stretch_offset / stretch_size
77}
78
79/// maplibre/maplibre-native#4add9ea original name: Cut
80struct Cut {
81    fixed: f64,
82    stretch: f64,
83}
84
85/// maplibre/maplibre-native#4add9ea original name: Cuts
86type Cuts = Vec<Cut>;
87
88/// maplibre/maplibre-native#4add9ea original name: stretchZonesToCuts
89fn stretch_zones_to_cuts(
90    stretch_zones: &ImageStretches,
91    fixed_size: f64,
92    stretch_size: f64,
93) -> Cuts {
94    let mut cuts = vec![Cut {
95        fixed: -(BORDER as f64),
96        stretch: 0.0,
97    }];
98
99    for zone in stretch_zones {
100        let c1 = zone.0;
101        let c2 = zone.1;
102        let last_stretch = cuts.last().unwrap().stretch;
103        cuts.push(Cut {
104            fixed: c1 - last_stretch,
105            stretch: last_stretch,
106        });
107        cuts.push(Cut {
108            fixed: c1 - last_stretch,
109            stretch: last_stretch + (c2 - c1),
110        });
111    }
112    cuts.push(Cut {
113        fixed: fixed_size + BORDER as f64,
114        stretch: stretch_size,
115    });
116    cuts
117}
118
119/// maplibre/maplibre-native#4add9ea original name: matrixMultiply
120fn matrix_multiply<U>(m: &[f64; 4], p: Point2D<f64, U>) -> Point2D<f64, U> {
121    Point2D::<f64, U>::new(m[0] * p.x + m[1] * p.y, m[2] * p.x + m[3] * p.y)
122}
123
124/// maplibre/maplibre-native#4add9ea original name: getIconQuads
125pub fn get_icon_quads(
126    shaped_icon: &PositionedIcon,
127    icon_rotate: f64,
128    icon_type: SymbolContent,
129    has_icon_text_fit: bool,
130) -> SymbolQuads {
131    let mut quads = Vec::new();
132
133    let image = &shaped_icon.image;
134    let pixel_ratio = image.pixel_ratio;
135    let image_width: u16 = image.padded_rect.width() - 2 * BORDER;
136    let image_height: u16 = image.padded_rect.height() - 2 * BORDER;
137
138    let icon_width = shaped_icon.right - shaped_icon.left;
139    let icon_height = shaped_icon.bottom - shaped_icon.top;
140
141    let stretch_xfull: ImageStretches = vec![(0.0, image_width as f64)];
142    let stretch_yfull: ImageStretches = vec![(0.0, image_height as f64)];
143    let stretch_x: &ImageStretches = if !image.stretch_x.is_empty() {
144        &image.stretch_x
145    } else {
146        &stretch_xfull
147    };
148    let stretch_y: &ImageStretches = if !image.stretch_y.is_empty() {
149        &image.stretch_y
150    } else {
151        &stretch_yfull
152    };
153
154    let stretch_width = compute_stretch_sum(stretch_x);
155    let stretch_height = compute_stretch_sum(stretch_y);
156    let fixed_width = image_width as f64 - stretch_width;
157    let fixed_height = image_height as f64 - stretch_height;
158
159    let mut stretch_offset_x = 0.;
160    let mut stretch_content_width = stretch_width;
161    let mut stretch_offset_y = 0.;
162    let mut stretch_content_height = stretch_height;
163    let mut fixed_offset_x = 0.;
164    let mut fixed_content_width = fixed_width;
165    let mut fixed_offset_y = 0.;
166    let mut fixed_content_height = fixed_height;
167
168    if has_icon_text_fit {
169        if let Some(content) = &image.content {
170            stretch_offset_x = sum_within_range(stretch_x, 0., content.left);
171            stretch_offset_y = sum_within_range(stretch_y, 0., content.top);
172            stretch_content_width = sum_within_range(stretch_x, content.left, content.right);
173            stretch_content_height = sum_within_range(stretch_y, content.top, content.bottom);
174            fixed_offset_x = content.left - stretch_offset_x;
175            fixed_offset_y = content.top - stretch_offset_y;
176            fixed_content_width = content.right - content.left - stretch_content_width;
177            fixed_content_height = content.bottom - content.top - stretch_content_height;
178        }
179    }
180
181    let mut matrix: Option<[f64; 4]> = None;
182    if icon_rotate != 0.0 {
183        // TODO is this correct?
184        let angle = deg2radf(icon_rotate);
185        let angle_sin = angle.sin();
186        let angle_cos = angle.cos();
187        matrix = Some([angle_cos, -angle_sin, angle_sin, angle_cos]);
188    }
189
190    let mut make_box = |left: &Cut, top: &Cut, right: &Cut, bottom: &Cut| {
191        let left_em = get_em_offset(
192            left.stretch - stretch_offset_x,
193            stretch_content_width,
194            icon_width,
195            shaped_icon.left,
196        );
197        let left_px = get_px_offset(
198            left.fixed - fixed_offset_x,
199            fixed_content_width,
200            left.stretch,
201            stretch_width,
202        );
203
204        let top_em = get_em_offset(
205            top.stretch - stretch_offset_y,
206            stretch_content_height,
207            icon_height,
208            shaped_icon.top,
209        );
210        let top_px = get_px_offset(
211            top.fixed - fixed_offset_y,
212            fixed_content_height,
213            top.stretch,
214            stretch_height,
215        );
216
217        let right_em = get_em_offset(
218            right.stretch - stretch_offset_x,
219            stretch_content_width,
220            icon_width,
221            shaped_icon.left,
222        );
223        let right_px = get_px_offset(
224            right.fixed - fixed_offset_x,
225            fixed_content_width,
226            right.stretch,
227            stretch_width,
228        );
229
230        let bottom_em = get_em_offset(
231            bottom.stretch - stretch_offset_y,
232            stretch_content_height,
233            icon_height,
234            shaped_icon.top,
235        );
236        let bottom_px = get_px_offset(
237            bottom.fixed - fixed_offset_y,
238            fixed_content_height,
239            bottom.stretch,
240            stretch_height,
241        );
242
243        let mut tl = Point2D::<f64, TileSpace>::new(left_em, top_em);
244        let mut tr = Point2D::<f64, TileSpace>::new(right_em, top_em);
245        let mut br = Point2D::<f64, TileSpace>::new(right_em, bottom_em);
246        let mut bl = Point2D::<f64, TileSpace>::new(left_em, bottom_em);
247        let pixel_offset_tl =
248            Point2D::<f64, TileSpace>::new(left_px / pixel_ratio, top_px / pixel_ratio);
249        let pixel_offset_br =
250            Point2D::<f64, TileSpace>::new(right_px / pixel_ratio, bottom_px / pixel_ratio);
251
252        if let Some(matrix) = matrix {
253            tl = matrix_multiply(&matrix, tl);
254            tr = matrix_multiply(&matrix, tr);
255            bl = matrix_multiply(&matrix, bl);
256            br = matrix_multiply(&matrix, br);
257        }
258
259        let x1 = left.stretch + left.fixed;
260        let x2 = right.stretch + right.fixed;
261        let y1 = top.stretch + top.fixed;
262        let y2 = bottom.stretch + bottom.fixed;
263
264        // TODO: consider making texture coordinates f64 instead of uint16_t
265        let sub_rect: Rect<u16, TileSpace> = Rect::new(
266            Point2D::new(
267                (image.padded_rect.origin.x as f64 + BORDER as f64 + x1) as u16,
268                (image.padded_rect.origin.y as f64 + BORDER as f64 + y1) as u16,
269            ),
270            Size2D::new((x2 - x1) as u16, (y2 - y1) as u16),
271        );
272
273        let min_font_scale_x = fixed_content_width / pixel_ratio / icon_width;
274        let min_font_scale_y = fixed_content_height / pixel_ratio / icon_height;
275
276        // Icon quad is padded, so texture coordinates also need to be padded.
277        quads.push(SymbolQuad {
278            tl,
279            tr,
280            bl,
281            br,
282            tex: sub_rect,
283            pixel_offset_tl,
284            pixel_offset_br,
285            glyph_offset: Point2D::new(0.0, 0.0),
286            writing_mode: WritingModeType::None,
287            is_sdf: icon_type == SymbolContent::IconSDF,
288            section_index: 0,
289            min_font_scale: Point2D::new(min_font_scale_x, min_font_scale_y),
290        });
291    };
292
293    if !has_icon_text_fit || (image.stretch_x.is_empty() && image.stretch_y.is_empty()) {
294        make_box(
295            &Cut {
296                fixed: 0.,
297                stretch: -1.,
298            },
299            &Cut {
300                fixed: 0.,
301                stretch: -1.,
302            },
303            &Cut {
304                fixed: 0.,
305                stretch: (image_width + 1) as f64,
306            },
307            &Cut {
308                fixed: 0.,
309                stretch: (image_height + 1) as f64,
310            },
311        );
312    } else {
313        let x_cuts = stretch_zones_to_cuts(stretch_x, fixed_width, stretch_width);
314        let y_cuts = stretch_zones_to_cuts(stretch_y, fixed_height, stretch_height);
315
316        for xi in 0..x_cuts.len() - 1 {
317            let x1 = &x_cuts[xi];
318            let x2 = &x_cuts[xi + 1];
319            for yi in 0..y_cuts.len() - 1 {
320                let y1 = &y_cuts[yi];
321                let y2 = &y_cuts[yi + 1];
322                make_box(x1, y1, x2, y2);
323            }
324        }
325    }
326
327    quads
328}
329
330/// maplibre/maplibre-native#4add9ea original name: getGlyphQuads
331pub fn get_glyph_quads(
332    shaped_text: &Shaping,
333    text_offset: [f64; 2],
334    layout: &SymbolLayoutProperties_Evaluated,
335    placement: SymbolPlacementType,
336    image_map: &ImageMap,
337    allow_vertical_placement: bool,
338) -> SymbolQuads {
339    let text_rotate: f64 = deg2radf(layout.get_eval::<TextRotate>());
340    let along_line: bool = layout.get::<TextRotationAlignment>() == AlignmentType::Map
341        && placement != SymbolPlacementType::Point;
342
343    let mut quads = Vec::new();
344
345    for line in &shaped_text.positioned_lines {
346        for positioned_glyph in &line.positioned_glyphs {
347            if positioned_glyph.rect.is_empty() {
348                continue;
349            }
350
351            // The rects have an addditional buffer that is not included in their size;
352            let glyph_padding = 1.0;
353            let mut rect_buffer = 3.0 + glyph_padding;
354            let mut pixel_ratio = 1.0;
355            let mut line_offset = 0.0;
356            let rotate_vertical_glyph =
357                (along_line || allow_vertical_placement) && positioned_glyph.vertical;
358            let half_advance =
359                positioned_glyph.metrics.advance as f64 * positioned_glyph.scale / 2.0;
360            let rect = positioned_glyph.rect;
361            let mut is_sdf = true;
362
363            // Align images and scaled glyphs in the middle of a vertical line.
364            if allow_vertical_placement && shaped_text.verticalizable {
365                let scaled_glyph_offset = (positioned_glyph.scale - 1.) * ONE_EM;
366                let image_offset =
367                    (ONE_EM - positioned_glyph.metrics.width as f64 * positioned_glyph.scale) / 2.0;
368                line_offset = line.line_offset / 2.0
369                    - (if positioned_glyph.image_id.is_some() {
370                        -image_offset
371                    } else {
372                        scaled_glyph_offset
373                    });
374            }
375
376            if let Some(image_id) = &positioned_glyph.image_id {
377                let image = image_map.get(image_id);
378                if let Some(image) = image {
379                    pixel_ratio = image.pixel_ratio;
380                    rect_buffer = ImagePosition::PADDING as f64 / pixel_ratio;
381                    is_sdf = image.sdf;
382                }
383            }
384
385            let glyph_offset = if along_line {
386                Point2D::new(positioned_glyph.x + half_advance, positioned_glyph.y)
387            } else {
388                Point2D::new(0.0, 0.0)
389            };
390
391            let mut built_in_offset = if along_line {
392                Vector2D::new(0.0, 0.0)
393            } else {
394                Vector2D::new(
395                    positioned_glyph.x + half_advance + text_offset[0],
396                    positioned_glyph.y + text_offset[1] - line_offset,
397                )
398            };
399
400            let mut verticalized_label_offset = Vector2D::<f64, TileSpace>::new(0.0, 0.0);
401            if rotate_vertical_glyph {
402                // Vertical POI labels, that are rotated 90deg CW and whose
403                // glyphs must preserve upright orientation need to be rotated
404                // 90deg CCW. After quad is rotated, it is translated to the
405                // original built-in offset.
406                verticalized_label_offset = built_in_offset;
407                built_in_offset = Vector2D::new(0.0, 0.0);
408            }
409
410            let x1 = (positioned_glyph.metrics.left as f64 - rect_buffer) * positioned_glyph.scale
411                - half_advance
412                + built_in_offset.x;
413            let y1 = (-positioned_glyph.metrics.top as f64 - rect_buffer) * positioned_glyph.scale
414                + built_in_offset.y;
415            let x2 = x1 + rect.width() as f64 * positioned_glyph.scale / pixel_ratio;
416            let y2 = y1 + rect.height() as f64 * positioned_glyph.scale / pixel_ratio;
417
418            let mut tl: Point2D<f64, TileSpace> = Point2D::new(x1, y1);
419            let mut tr: Point2D<f64, TileSpace> = Point2D::new(x2, y1);
420            let mut bl: Point2D<f64, TileSpace> = Point2D::new(x1, y2);
421            let mut br: Point2D<f64, TileSpace> = Point2D::new(x2, y2);
422
423            if rotate_vertical_glyph {
424                // Vertical-supporting glyphs are laid out in 24x24 point boxes
425                // (1 square em) In horizontal orientation, the y values for
426                // glyphs are below the midline and we use a "yOffset" of -17 to
427                // pull them up to the middle. By rotating counter-clockwise
428                // around the point at the center of the left edge of a 24x24
429                // layout box centered below the midline, we align the center of
430                // the glyphs with the horizontal midline, so the yOffset is no
431                // longer necessary, but we also pull the glyph to the left
432                // along the x axis. The y coordinate includes baseline yOffset,
433                // therefore, needs to be accounted for when glyph is rotated
434                // and translated.
435
436                let center = Point2D::new(-half_advance, half_advance - Shaping::Y_OFFSET as f64);
437                let vertical_rotation = -PI / 2.;
438
439                // xHalfWidhtOffsetcorrection is a difference between full-width
440                // and half-width advance, should be 0 for full-width glyphs and
441                // will pull up half-width glyphs.
442                let x_half_widht_offsetcorrection = ONE_EM / 2. - half_advance;
443                let y_image_offset_correction = if positioned_glyph.image_id.is_some() {
444                    x_half_widht_offsetcorrection
445                } else {
446                    0.0
447                };
448
449                let x_offset_correction = Vector2D::<f64, TileSpace>::new(
450                    5.0 - Shaping::Y_OFFSET as f64 - x_half_widht_offsetcorrection,
451                    -y_image_offset_correction,
452                );
453
454                tl = center
455                    + rotate(&(tl - center), vertical_rotation)
456                    + x_offset_correction
457                    + verticalized_label_offset;
458                tr = center
459                    + rotate(&(tr - center), vertical_rotation)
460                    + x_offset_correction
461                    + verticalized_label_offset;
462                bl = center
463                    + rotate(&(bl - center), vertical_rotation)
464                    + x_offset_correction
465                    + verticalized_label_offset;
466                br = center
467                    + rotate(&(br - center), vertical_rotation)
468                    + x_offset_correction
469                    + verticalized_label_offset;
470            }
471
472            if text_rotate != 0.0 {
473                // TODO is this correct?
474                // Compute the transformation matrix.
475                let angle_sin = text_rotate.sin();
476                let angle_cos = text_rotate.cos();
477                let matrix = [angle_cos, -angle_sin, angle_sin, angle_cos];
478
479                tl = matrix_multiply(&matrix, tl);
480                tr = matrix_multiply(&matrix, tr);
481                bl = matrix_multiply(&matrix, bl);
482                br = matrix_multiply(&matrix, br);
483            }
484
485            let pixel_offset_tl = Point2D::default();
486            let pixel_offset_br = Point2D::default();
487            let min_font_scale = Point2D::default();
488
489            quads.push(SymbolQuad {
490                tl,
491                tr,
492                bl,
493                br,
494                tex: rect,
495                pixel_offset_tl,
496                pixel_offset_br,
497                glyph_offset,
498                writing_mode: shaped_text.writing_mode,
499                is_sdf,
500                section_index: positioned_glyph.section_index,
501                min_font_scale,
502            });
503        }
504    }
505
506    quads
507}
508#[cfg(test)]
509mod tests {
510    use cgmath::ulps_eq;
511
512    use crate::{
513        euclid::{Point2D, Rect, Size2D},
514        legacy::{
515            geometry::anchor::Anchor,
516            geometry_tile_data::GeometryCoordinates,
517            glyph::{PositionedGlyph, PositionedLine, Shaping, WritingModeType},
518            image_atlas::ImagePosition,
519            layout::symbol_instance::SymbolContent,
520            quads::get_icon_quads,
521            shaping::PositionedIcon,
522            style_types::{IconTextFitType, SymbolAnchorType, SymbolLayoutProperties_Evaluated},
523        },
524    };
525
526    #[test]
527    /// maplibre/maplibre-native#4add9ea original name: getIconQuads_normal
528    pub fn get_icon_quads_normal() {
529        let layout = SymbolLayoutProperties_Evaluated;
530        let anchor = Anchor {
531            point: Point2D::new(2.0, 3.0),
532            angle: 0.0,
533            segment: Some(0),
534        };
535        let image: ImagePosition = ImagePosition {
536            pixel_ratio: 1.0,
537            padded_rect: Rect::new(Point2D::origin(), Size2D::new(15, 11)),
538            version: 0,
539            stretch_x: vec![],
540            stretch_y: vec![],
541            content: None,
542        };
543
544        let shaped_icon =
545            PositionedIcon::shape_icon(image.clone(), &[-6.5, -4.5], SymbolAnchorType::Center);
546
547        let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
548
549        assert_eq!(quads.len(), 1);
550        let quad = &quads[0];
551        ulps_eq!(quad.tl.x, -14.);
552        ulps_eq!(quad.tl.y, -10.);
553        ulps_eq!(quad.tr.x, 1.);
554        ulps_eq!(quad.tr.y, -10.);
555        ulps_eq!(quad.bl.x, -14.);
556        ulps_eq!(quad.bl.y, 1.);
557        ulps_eq!(quad.br.x, 1.);
558        ulps_eq!(quad.br.y, 1.);
559    }
560
561    #[test]
562    /// maplibre/maplibre-native#4add9ea original name: getIconQuads_style
563    pub fn get_icon_quads_style() {
564        let anchor = Anchor {
565            point: Point2D::new(0.0, 0.0),
566            angle: 0.0,
567            segment: Some(0),
568        };
569
570        let image: ImagePosition = ImagePosition {
571            pixel_ratio: 1.0,
572            padded_rect: Rect::new(Point2D::origin(), Size2D::new(20, 20)),
573            version: 0,
574            stretch_x: vec![],
575            stretch_y: vec![],
576            content: None,
577        };
578
579        let line = GeometryCoordinates::default();
580        let mut shaped_text: Shaping = Shaping {
581            top: -10.,
582            bottom: 30.0,
583            left: -60.,
584            right: 20.0,
585
586            positioned_lines: vec![],
587            writing_mode: WritingModeType::None,
588            verticalizable: false,
589            icons_in_text: false,
590        };
591
592        // shapedText.positionedGlyphs.emplace_back(PositionedGlyph(32, 0.0, 0.0, false, 0, 1.0));
593
594        shaped_text.positioned_lines.push(PositionedLine::default());
595        shaped_text
596            .positioned_lines
597            .last_mut()
598            .unwrap()
599            .positioned_glyphs
600            .push(PositionedGlyph {
601                glyph: 32,
602                x: 0.0,
603                y: 0.0,
604                vertical: false,
605                font: 0,
606                scale: 0.0,
607                rect: Default::default(),
608                metrics: Default::default(),
609                image_id: None,
610                section_index: 0,
611            });
612
613        // none
614        {
615            let shaped_icon =
616                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
617
618            ulps_eq!(-18.5, shaped_icon.top);
619            ulps_eq!(-0.5, shaped_icon.right);
620            ulps_eq!(-0.5, shaped_icon.bottom);
621            ulps_eq!(-18.5, shaped_icon.left);
622
623            let layout = SymbolLayoutProperties_Evaluated;
624            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
625
626            assert_eq!(quads.len(), 1);
627            let quad = &quads[0];
628
629            ulps_eq!(quad.tl.x, -19.5);
630            ulps_eq!(quad.tl.y, -19.5);
631            ulps_eq!(quad.tr.x, 0.5);
632            ulps_eq!(quad.tr.y, -19.5);
633            ulps_eq!(quad.bl.x, -19.5);
634            ulps_eq!(quad.bl.y, 0.5);
635            ulps_eq!(quad.br.x, 0.5);
636            ulps_eq!(quad.br.y, 0.5);
637        }
638
639        // width
640        {
641            let mut shaped_icon =
642                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
643            shaped_icon.fit_icon_to_text(
644                &shaped_text,
645                IconTextFitType::Width,
646                &[0., 0., 0., 0.],
647                &[0., 0.],
648                24.0 / 24.0,
649            );
650            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
651
652            assert_eq!(quads.len(), 1);
653            let quad = &quads[0];
654
655            ulps_eq!(quad.tl.x, -64.4444427);
656            ulps_eq!(quad.tl.y, 0.);
657            ulps_eq!(quad.tr.x, 24.4444427);
658            ulps_eq!(quad.tr.y, 0.);
659            ulps_eq!(quad.bl.x, -64.4444427);
660            ulps_eq!(quad.bl.y, 20.);
661            ulps_eq!(quad.br.x, 24.4444427);
662            ulps_eq!(quad.br.y, 20.);
663        }
664
665        // width x textSize
666        {
667            let mut shaped_icon =
668                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
669            shaped_icon.fit_icon_to_text(
670                &shaped_text,
671                IconTextFitType::Width,
672                &[0., 0., 0., 0.],
673                &[0., 0.],
674                12.0 / 24.0,
675            );
676            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
677
678            assert_eq!(quads.len(), 1);
679            let quad = &quads[0];
680
681            ulps_eq!(quad.tl.x, -32.2222214);
682            ulps_eq!(quad.tl.y, -5.);
683            ulps_eq!(quad.tr.x, 12.2222214);
684            ulps_eq!(quad.tr.y, -5.);
685            ulps_eq!(quad.bl.x, -32.2222214);
686            ulps_eq!(quad.bl.y, 15.);
687            ulps_eq!(quad.br.x, 12.2222214);
688            ulps_eq!(quad.br.y, 15.);
689        }
690
691        // width x textSize + padding
692        {
693            let mut shaped_icon =
694                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
695            shaped_icon.fit_icon_to_text(
696                &shaped_text,
697                IconTextFitType::Width,
698                &[5., 10., 5., 10.],
699                &[0., 0.],
700                12.0 / 24.0,
701            );
702            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
703
704            assert_eq!(quads.len(), 1);
705            let quad = &quads[0];
706
707            ulps_eq!(quad.tl.x, -43.3333321);
708            ulps_eq!(quad.tl.y, -5.);
709            ulps_eq!(quad.tr.x, 23.3333321);
710            ulps_eq!(quad.tr.y, -5.);
711            ulps_eq!(quad.bl.x, -43.3333321);
712            ulps_eq!(quad.bl.y, 15.);
713            ulps_eq!(quad.br.x, 23.3333321);
714            ulps_eq!(quad.br.y, 15.);
715        }
716
717        // height
718        {
719            let mut shaped_icon =
720                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
721            shaped_icon.fit_icon_to_text(
722                &shaped_text,
723                IconTextFitType::Height,
724                &[0., 0., 0., 0.],
725                &[0., 0.],
726                24.0 / 24.0,
727            );
728            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
729
730            assert_eq!(quads.len(), 1);
731            let quad = &quads[0];
732
733            ulps_eq!(quad.tl.x, -30.);
734            ulps_eq!(quad.tl.y, -12.2222214);
735            ulps_eq!(quad.tr.x, -10.);
736            ulps_eq!(quad.tr.y, -12.2222214);
737            ulps_eq!(quad.bl.x, -30.);
738            ulps_eq!(quad.bl.y, 32.2222214);
739            ulps_eq!(quad.br.x, -10.);
740            ulps_eq!(quad.br.y, 32.2222214);
741        }
742
743        // height x textSize
744        {
745            let layout = SymbolLayoutProperties_Evaluated;
746            let mut shaped_icon =
747                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
748            shaped_icon.fit_icon_to_text(
749                &shaped_text,
750                IconTextFitType::Height,
751                &[0., 0., 0., 0.],
752                &[0., 0.],
753                12.0 / 24.0,
754            );
755            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
756
757            assert_eq!(quads.len(), 1);
758            let quad = &quads[0];
759
760            ulps_eq!(quad.tl.x, -20.);
761            ulps_eq!(quad.tl.y, -6.11111069);
762            ulps_eq!(quad.tr.x, 0.);
763            ulps_eq!(quad.tr.y, -6.11111069);
764            ulps_eq!(quad.bl.x, -20.);
765            ulps_eq!(quad.bl.y, 16.1111107);
766            ulps_eq!(quad.br.x, 0.);
767            ulps_eq!(quad.br.y, 16.1111107);
768        }
769
770        // height x textSize + padding
771        {
772            let mut shaped_icon =
773                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
774            shaped_icon.fit_icon_to_text(
775                &shaped_text,
776                IconTextFitType::Height,
777                &[5., 10., 5., 20.],
778                &[0., 0.],
779                12.0 / 24.0,
780            );
781            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
782
783            assert_eq!(quads.len(), 1);
784            let quad = &quads[0];
785
786            ulps_eq!(quad.tl.x, -20.);
787            ulps_eq!(quad.tl.y, -11.666666);
788            ulps_eq!(quad.tr.x, 0.);
789            ulps_eq!(quad.tr.y, -11.666666);
790            ulps_eq!(quad.bl.x, -20.);
791            ulps_eq!(quad.bl.y, 21.666666);
792            ulps_eq!(quad.br.x, 0.);
793            ulps_eq!(quad.br.y, 21.666666);
794        }
795
796        // both
797        {
798            let mut shaped_icon =
799                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
800            shaped_icon.fit_icon_to_text(
801                &shaped_text,
802                IconTextFitType::Both,
803                &[0., 0., 0., 0.],
804                &[0., 0.],
805                24.0 / 24.0,
806            );
807            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
808
809            assert_eq!(quads.len(), 1);
810            let quad = &quads[0];
811
812            ulps_eq!(quad.tl.x, -64.4444427);
813            ulps_eq!(quad.tl.y, -12.2222214);
814            ulps_eq!(quad.tr.x, 24.4444427);
815            ulps_eq!(quad.tr.y, -12.2222214);
816            ulps_eq!(quad.bl.x, -64.4444427);
817            ulps_eq!(quad.bl.y, 32.2222214);
818            ulps_eq!(quad.br.x, 24.4444427);
819            ulps_eq!(quad.br.y, 32.2222214);
820        }
821
822        // both x textSize
823        {
824            let mut shaped_icon =
825                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
826            shaped_icon.fit_icon_to_text(
827                &shaped_text,
828                IconTextFitType::Both,
829                &[0., 0., 0., 0.],
830                &[0., 0.],
831                12.0 / 24.0,
832            );
833            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
834
835            assert_eq!(quads.len(), 1);
836            let quad = &quads[0];
837
838            ulps_eq!(quad.tl.x, -32.2222214);
839            ulps_eq!(quad.tl.y, -6.11111069);
840            ulps_eq!(quad.tr.x, 12.2222214);
841            ulps_eq!(quad.tr.y, -6.11111069);
842            ulps_eq!(quad.bl.x, -32.2222214);
843            ulps_eq!(quad.bl.y, 16.1111107);
844            ulps_eq!(quad.br.x, 12.2222214);
845            ulps_eq!(quad.br.y, 16.1111107);
846        }
847
848        // both x textSize + padding
849        {
850            let mut shaped_icon =
851                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
852            shaped_icon.fit_icon_to_text(
853                &shaped_text,
854                IconTextFitType::Both,
855                &[5., 10., 5., 10.],
856                &[0., 0.],
857                12.0 / 24.0,
858            );
859            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
860
861            assert_eq!(quads.len(), 1);
862            let quad = &quads[0];
863
864            ulps_eq!(quad.tl.x, -43.3333321);
865            ulps_eq!(quad.tl.y, -11.666666);
866            ulps_eq!(quad.tr.x, 23.3333321);
867            ulps_eq!(quad.tr.y, -11.666666);
868            ulps_eq!(quad.bl.x, -43.3333321);
869            ulps_eq!(quad.bl.y, 21.666666);
870            ulps_eq!(quad.br.x, 23.3333321);
871            ulps_eq!(quad.br.y, 21.666666);
872        }
873
874        // both x textSize + padding t/r/b/l
875        {
876            let layout = SymbolLayoutProperties_Evaluated;
877            // FIXME add layout.get::<TextSize>() = 12.0; this test also works without this, which makes sense because text size does not affect glyph quads
878            let mut shaped_icon =
879                PositionedIcon::shape_icon(image.clone(), &[-9.5, -9.5], SymbolAnchorType::Center);
880            shaped_icon.fit_icon_to_text(
881                &shaped_text,
882                IconTextFitType::Both,
883                &[0., 5., 10., 15.],
884                &[0., 0.],
885                12.0 / 24.0,
886            );
887            let quads = get_icon_quads(&shaped_icon, 0., SymbolContent::IconRGBA, false);
888
889            assert_eq!(quads.len(), 1);
890            let quad = &quads[0];
891
892            ulps_eq!(quad.tl.x, -48.3333321);
893            ulps_eq!(quad.tl.y, -6.66666603);
894            ulps_eq!(quad.tr.x, 18.3333321);
895            ulps_eq!(quad.tr.y, -6.66666603);
896            ulps_eq!(quad.bl.x, -48.3333321);
897            ulps_eq!(quad.bl.y, 26.666666);
898            ulps_eq!(quad.br.x, 18.3333321);
899            ulps_eq!(quad.br.y, 26.666666);
900        }
901    }
902}