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