1 #include <mbgl/text/quads.hpp>
2 #include <mbgl/text/shaping.hpp>
3 #include <mbgl/tile/geometry_tile_data.hpp>
4 #include <mbgl/geometry/anchor.hpp>
5 #include <mbgl/style/layers/symbol_layer_properties.hpp>
6 #include <mbgl/util/math.hpp>
7 #include <mbgl/util/constants.hpp>
8 #include <mbgl/util/optional.hpp>
9 
10 #include <cassert>
11 
12 namespace mbgl {
13 
14 using namespace style;
15 
getIconQuad(const PositionedIcon & shapedIcon,const SymbolLayoutProperties::Evaluated & layout,const float layoutTextSize,const Shaping & shapedText)16 SymbolQuad getIconQuad(const PositionedIcon& shapedIcon,
17                        const SymbolLayoutProperties::Evaluated& layout,
18                        const float layoutTextSize,
19                        const Shaping& shapedText) {
20     const ImagePosition& image = shapedIcon.image();
21 
22     // If you have a 10px icon that isn't perfectly aligned to the pixel grid it will cover 11 actual
23     // pixels. The quad needs to be padded to account for this, otherwise they'll look slightly clipped
24     // on one edge in some cases.
25     const float border = 1.0;
26 
27     float top = shapedIcon.top() - border / image.pixelRatio;
28     float left = shapedIcon.left() - border / image.pixelRatio;
29     float bottom = shapedIcon.bottom() + border / image.pixelRatio;
30     float right = shapedIcon.right() + border / image.pixelRatio;
31     Point<float> tl;
32     Point<float> tr;
33     Point<float> br;
34     Point<float> bl;
35 
36     if (layout.get<IconTextFit>() != IconTextFitType::None && shapedText) {
37         auto iconWidth = right - left;
38         auto iconHeight = bottom - top;
39         auto size = layoutTextSize / 24.0f;
40         auto textLeft = shapedText.left * size;
41         auto textRight = shapedText.right * size;
42         auto textTop = shapedText.top * size;
43         auto textBottom = shapedText.bottom * size;
44         auto textWidth = textRight - textLeft;
45         auto textHeight = textBottom - textTop;
46         auto padT = layout.get<IconTextFitPadding>()[0];
47         auto padR = layout.get<IconTextFitPadding>()[1];
48         auto padB = layout.get<IconTextFitPadding>()[2];
49         auto padL = layout.get<IconTextFitPadding>()[3];
50         auto offsetY = layout.get<IconTextFit>() == IconTextFitType::Width ? (textHeight - iconHeight) * 0.5 : 0;
51         auto offsetX = layout.get<IconTextFit>() == IconTextFitType::Height ? (textWidth - iconWidth) * 0.5 : 0;
52         auto width = layout.get<IconTextFit>() == IconTextFitType::Width || layout.get<IconTextFit>() == IconTextFitType::Both ? textWidth : iconWidth;
53         auto height = layout.get<IconTextFit>() == IconTextFitType::Height || layout.get<IconTextFit>() == IconTextFitType::Both ? textHeight : iconHeight;
54         left = textLeft + offsetX - padL;
55         top = textTop + offsetY - padT;
56         right = textLeft + offsetX + padR + width;
57         bottom = textTop + offsetY + padB + height;
58         tl = {left, top};
59         tr = {right, top};
60         br = {right, bottom};
61         bl = {left, bottom};
62     } else {
63         tl = {left, top};
64         tr = {right, top};
65         br = {right, bottom};
66         bl = {left, bottom};
67     }
68 
69     const float angle = shapedIcon.angle();
70 
71     if (angle) {
72         // Compute the transformation matrix.
73         float angle_sin = std::sin(angle);
74         float angle_cos = std::cos(angle);
75         std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}};
76 
77         tl = util::matrixMultiply(matrix, tl);
78         tr = util::matrixMultiply(matrix, tr);
79         bl = util::matrixMultiply(matrix, bl);
80         br = util::matrixMultiply(matrix, br);
81     }
82 
83     // Icon quad is padded, so texture coordinates also need to be padded.
84     Rect<uint16_t> textureRect {
85         static_cast<uint16_t>(image.textureRect.x - border),
86         static_cast<uint16_t>(image.textureRect.y - border),
87         static_cast<uint16_t>(image.textureRect.w + border * 2),
88         static_cast<uint16_t>(image.textureRect.h + border * 2)
89     };
90 
91     return SymbolQuad { tl, tr, bl, br, textureRect, shapedText.writingMode, { 0.0f, 0.0f } };
92 }
93 
getGlyphQuads(const Shaping & shapedText,const SymbolLayoutProperties::Evaluated & layout,const style::SymbolPlacementType placement,const GlyphPositionMap & positions)94 SymbolQuads getGlyphQuads(const Shaping& shapedText,
95                           const SymbolLayoutProperties::Evaluated& layout,
96                           const style::SymbolPlacementType placement,
97                           const GlyphPositionMap& positions) {
98     const float textRotate = layout.get<TextRotate>() * util::DEG2RAD;
99 
100     const float oneEm = 24.0;
101     std::array<float, 2> textOffset = layout.get<TextOffset>();
102     textOffset[0] *= oneEm;
103     textOffset[1] *= oneEm;
104 
105     SymbolQuads quads;
106 
107     for (const PositionedGlyph &positionedGlyph: shapedText.positionedGlyphs) {
108         auto positionsIt = positions.find(positionedGlyph.glyph);
109         if (positionsIt == positions.end())
110             continue;
111 
112         const GlyphPosition& glyph = positionsIt->second;
113         const Rect<uint16_t>& rect = glyph.rect;
114 
115         // The rects have an addditional buffer that is not included in their size;
116         const float glyphPadding = 1.0f;
117         const float rectBuffer = 3.0f + glyphPadding;
118 
119         const float halfAdvance = glyph.metrics.advance / 2.0;
120         const bool alongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map && placement != SymbolPlacementType::Point;
121 
122         const Point<float> glyphOffset = alongLine ?
123             Point<float>{ positionedGlyph.x + halfAdvance, positionedGlyph.y } :
124             Point<float>{ 0.0f, 0.0f };
125 
126         const Point<float> builtInOffset = alongLine ?
127             Point<float>{ 0.0f, 0.0f } :
128             Point<float>{ positionedGlyph.x + halfAdvance + textOffset[0], positionedGlyph.y + textOffset[1] };
129 
130 
131         const float x1 = glyph.metrics.left - rectBuffer - halfAdvance + builtInOffset.x;
132         const float y1 = -glyph.metrics.top - rectBuffer + builtInOffset.y;
133         const float x2 = x1 + rect.w;
134         const float y2 = y1 + rect.h;
135 
136         Point<float> tl{x1, y1};
137         Point<float> tr{x2, y1};
138         Point<float> bl{x1, y2};
139         Point<float> br{x2, y2};
140 
141         if (alongLine && positionedGlyph.vertical) {
142             // Vertical-supporting glyphs are laid out in 24x24 point boxes (1 square em)
143             // In horizontal orientation, the y values for glyphs are below the midline
144             // and we use a "yOffset" of -17 to pull them up to the middle.
145             // By rotating counter-clockwise around the point at the center of the left
146             // edge of a 24x24 layout box centered below the midline, we align the center
147             // of the glyphs with the horizontal midline, so the yOffset is no longer
148             // necessary, but we also pull the glyph to the left along the x axis
149             const Point<float> center{-halfAdvance, halfAdvance};
150             const float verticalRotation = -M_PI_2;
151             const Point<float> xOffsetCorrection{5, 0};
152 
153             tl = util::rotate(tl - center, verticalRotation) + center + xOffsetCorrection;
154             tr = util::rotate(tr - center, verticalRotation) + center + xOffsetCorrection;
155             bl = util::rotate(bl - center, verticalRotation) + center + xOffsetCorrection;
156             br = util::rotate(br - center, verticalRotation) + center + xOffsetCorrection;
157         }
158 
159         if (textRotate) {
160             // Compute the transformation matrix.
161             float angle_sin = std::sin(textRotate);
162             float angle_cos = std::cos(textRotate);
163             std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}};
164 
165             tl = util::matrixMultiply(matrix, tl);
166             tr = util::matrixMultiply(matrix, tr);
167             bl = util::matrixMultiply(matrix, bl);
168             br = util::matrixMultiply(matrix, br);
169         }
170 
171         quads.emplace_back(tl, tr, bl, br, rect, shapedText.writingMode, glyphOffset);
172     }
173 
174     return quads;
175 }
176 } // namespace mbgl
177