Skip to content
This repository was archived by the owner on Aug 8, 2023. It is now read-only.

Commit da7a67b

Browse files
committed
[core] Fix multiple line vertical text shaping
1 parent 1b97cc9 commit da7a67b

File tree

6 files changed

+144
-120
lines changed

6 files changed

+144
-120
lines changed

src/mbgl/text/glyph.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class Shaping {
8383
Shaping() = default;
8484
explicit Shaping(float x, float y, WritingModeType writingMode_, std::size_t lineCount_)
8585
: top(y), bottom(y), left(x), right(x), writingMode(writingMode_), lineCount(lineCount_) {}
86-
std::vector<PositionedGlyph> positionedGlyphs;
86+
std::unordered_map<uint32_t, std::vector<PositionedGlyph>> positionedGlyphs;
8787
float top = 0;
8888
float bottom = 0;
8989
float left = 0;

src/mbgl/text/glyph_manager.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ void GlyphManager::notify(GlyphRequestor& requestor, const GlyphDependencies& gl
146146
if (it != entry.glyphs.end()) {
147147
glyphs.glyphs.emplace(*it);
148148
} else {
149-
glyphs.glyphs.emplace(glyphID, std::experimental::nullopt);
149+
glyphs.glyphs.emplace(glyphID, nullopt);
150150
}
151151
}
152152
}

src/mbgl/text/quads.cpp

Lines changed: 89 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -68,91 +68,96 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
6868

6969
SymbolQuads quads;
7070

71-
for (const PositionedGlyph &positionedGlyph: shapedText.positionedGlyphs) {
72-
auto fontPositions = positions.find(positionedGlyph.font);
73-
if (fontPositions == positions.end())
74-
continue;
75-
76-
auto positionsIt = fontPositions->second.glyphPositionMap.find(positionedGlyph.glyph);
77-
if (positionsIt == fontPositions->second.glyphPositionMap.end()) {
78-
continue;
71+
if (shapedText.lineCount == 0) return quads;
72+
const float lineHeight = (std::fabs(shapedText.bottom) + std::fabs(shapedText.top)) / shapedText.lineCount;
73+
for (const auto& positionedGlyphs : shapedText.positionedGlyphs) {
74+
const float currentHeight = lineHeight * positionedGlyphs.first;
75+
for (const PositionedGlyph& positionedGlyph : positionedGlyphs.second) {
76+
auto fontPositions = positions.find(positionedGlyph.font);
77+
if (fontPositions == positions.end()) continue;
78+
79+
auto positionsIt = fontPositions->second.glyphPositionMap.find(positionedGlyph.glyph);
80+
if (positionsIt == fontPositions->second.glyphPositionMap.end()) {
81+
continue;
82+
}
83+
84+
const GlyphPosition& glyph = positionsIt->second;
85+
const Rect<uint16_t>& rect = glyph.rect;
86+
87+
// The rects have an additional buffer that is not included in their size;
88+
const float glyphPadding = 1.0f;
89+
const float rectBuffer = 3.0f + glyphPadding;
90+
91+
const float halfAdvance = glyph.metrics.advance * positionedGlyph.scale / 2.0;
92+
93+
const Point<float> glyphOffset =
94+
alongLine ? Point<float>{positionedGlyph.x + halfAdvance, positionedGlyph.y} : Point<float>{0.0f, 0.0f};
95+
96+
Point<float> builtInOffset = alongLine ? Point<float>{0.0f, 0.0f}
97+
: Point<float>{positionedGlyph.x + halfAdvance + textOffset[0],
98+
positionedGlyph.y + textOffset[1]};
99+
100+
Point<float> verticalizedLabelOffset = {0.0f, 0.0f};
101+
const bool rotateVerticalGlyph = (alongLine || allowVerticalPlacement) && positionedGlyph.vertical;
102+
if (rotateVerticalGlyph) {
103+
// Vertical POI labels, that are rotated 90deg CW and whose glyphs must preserve upright orientation
104+
// need to be rotated 90deg CCW. After quad is rotated, it is translated to the original built-in
105+
// offset.
106+
verticalizedLabelOffset = builtInOffset;
107+
builtInOffset = {0.0f, 0.0f};
108+
}
109+
110+
const float x1 = (glyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset.x;
111+
const float y1 = (-glyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset.y;
112+
const float x2 = x1 + rect.w * positionedGlyph.scale;
113+
const float y2 = y1 + rect.h * positionedGlyph.scale;
114+
115+
Point<float> tl{x1, y1};
116+
Point<float> tr{x2, y1};
117+
Point<float> bl{x1, y2};
118+
Point<float> br{x2, y2};
119+
120+
if (rotateVerticalGlyph) {
121+
// Vertical-supporting glyphs are laid out in 24x24 point boxes (1 square em)
122+
// In horizontal orientation, the y values for glyphs are below the midline.
123+
// If the glyph's baseline is applicable, we take the relative y offset of the
124+
// glyph, which needs to erase out current line height that added to the glyphs.
125+
// Otherwise, we use a "yOffset" of -17 to pull them up to the middle.
126+
// By rotating counter-clockwise around the point at the center of the left
127+
// edge of a 24x24 layout box centered below the midline, we align the center
128+
// of the glyphs with the horizontal midline, so the yOffset is no longer
129+
// necessary, but we also pull the glyph to the left along the x axis.
130+
// The y coordinate includes baseline yOffset, therefore, needs to be accounted
131+
// for when glyph is rotated and translated.
132+
const float yShift = (shapedText.hasBaseline ? (positionedGlyph.y - currentHeight) : Shaping::yOffset);
133+
const Point<float> center{-halfAdvance, halfAdvance - yShift};
134+
const float verticalRotation = -M_PI_2;
135+
136+
// xHalfWidhtOffsetcorrection is a difference between full-width and half-width
137+
// advance, should be 0 for full-width glyphs and will pull up half-width glyphs.
138+
const float xHalfWidhtOffsetcorrection = util::ONE_EM / 2 - halfAdvance;
139+
const Point<float> xOffsetCorrection{5.0f - yShift - xHalfWidhtOffsetcorrection, 0.0f};
140+
141+
tl = util::rotate(tl - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
142+
tr = util::rotate(tr - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
143+
bl = util::rotate(bl - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
144+
br = util::rotate(br - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
145+
}
146+
147+
if (textRotate) {
148+
// Compute the transformation matrix.
149+
float angle_sin = std::sin(textRotate);
150+
float angle_cos = std::cos(textRotate);
151+
std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}};
152+
153+
tl = util::matrixMultiply(matrix, tl);
154+
tr = util::matrixMultiply(matrix, tr);
155+
bl = util::matrixMultiply(matrix, bl);
156+
br = util::matrixMultiply(matrix, br);
157+
}
158+
159+
quads.emplace_back(tl, tr, bl, br, rect, shapedText.writingMode, glyphOffset, positionedGlyph.sectionIndex);
79160
}
80-
81-
const GlyphPosition& glyph = positionsIt->second;
82-
const Rect<uint16_t>& rect = glyph.rect;
83-
84-
// The rects have an additional buffer that is not included in their size;
85-
const float glyphPadding = 1.0f;
86-
const float rectBuffer = 3.0f + glyphPadding;
87-
88-
const float halfAdvance = glyph.metrics.advance * positionedGlyph.scale / 2.0;
89-
90-
const Point<float> glyphOffset = alongLine ?
91-
Point<float>{ positionedGlyph.x + halfAdvance, positionedGlyph.y } :
92-
Point<float>{ 0.0f, 0.0f };
93-
94-
Point<float> builtInOffset = alongLine ?
95-
Point<float>{ 0.0f, 0.0f } :
96-
Point<float>{ positionedGlyph.x + halfAdvance + textOffset[0], positionedGlyph.y + textOffset[1] };
97-
98-
Point<float> verticalizedLabelOffset = { 0.0f, 0.0f };
99-
const bool rotateVerticalGlyph = (alongLine || allowVerticalPlacement) && positionedGlyph.vertical;
100-
if (rotateVerticalGlyph) {
101-
// Vertical POI labels, that are rotated 90deg CW and whose glyphs must preserve upright orientation
102-
// need to be rotated 90deg CCW. After quad is rotated, it is translated to the original built-in offset.
103-
verticalizedLabelOffset = builtInOffset;
104-
builtInOffset = { 0.0f, 0.0f };
105-
}
106-
107-
const float x1 = (glyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset.x;
108-
const float y1 = (-glyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset.y;
109-
const float x2 = x1 + rect.w * positionedGlyph.scale;
110-
const float y2 = y1 + rect.h * positionedGlyph.scale;
111-
112-
Point<float> tl{x1, y1};
113-
Point<float> tr{x2, y1};
114-
Point<float> bl{x1, y2};
115-
Point<float> br{x2, y2};
116-
117-
if (rotateVerticalGlyph) {
118-
// Vertical-supporting glyphs are laid out in 24x24 point boxes (1 square em)
119-
// In horizontal orientation, the y values for glyphs are below the midline.
120-
// If the glyph's baseline is applicable, we take the y value of the glyph.
121-
// Otherwise, we use a "yOffset" of -17 to pull them up to the middle.
122-
// By rotating counter-clockwise around the point at the center of the left
123-
// edge of a 24x24 layout box centered below the midline, we align the center
124-
// of the glyphs with the horizontal midline, so the yOffset is no longer
125-
// necessary, but we also pull the glyph to the left along the x axis.
126-
// The y coordinate includes baseline yOffset, therefore, needs to be accounted
127-
// for when glyph is rotated and translated.
128-
const float yShift = (shapedText.hasBaseline ? positionedGlyph.y : Shaping::yOffset);
129-
const Point<float> center{-halfAdvance, halfAdvance - yShift};
130-
const float verticalRotation = -M_PI_2;
131-
132-
// xHalfWidhtOffsetcorrection is a difference between full-width and half-width
133-
// advance, should be 0 for full-width glyphs and will pull up half-width glyphs.
134-
const float xHalfWidhtOffsetcorrection = util::ONE_EM / 2 - halfAdvance;
135-
const Point<float> xOffsetCorrection{5.0f - yShift - xHalfWidhtOffsetcorrection, 0.0f};
136-
137-
tl = util::rotate(tl - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
138-
tr = util::rotate(tr - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
139-
bl = util::rotate(bl - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
140-
br = util::rotate(br - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
141-
}
142-
143-
if (textRotate) {
144-
// Compute the transformation matrix.
145-
float angle_sin = std::sin(textRotate);
146-
float angle_cos = std::cos(textRotate);
147-
std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}};
148-
149-
tl = util::matrixMultiply(matrix, tl);
150-
tr = util::matrixMultiply(matrix, tr);
151-
bl = util::matrixMultiply(matrix, bl);
152-
br = util::matrixMultiply(matrix, br);
153-
}
154-
155-
quads.emplace_back(tl, tr, bl, br, rect, shapedText.writingMode, glyphOffset, positionedGlyph.sectionIndex);
156161
}
157162

158163
return quads;

src/mbgl/text/shaping.cpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,12 @@ void align(Shaping& shaping,
122122
const std::size_t lineCount) {
123123
const float shiftX = (justify - horizontalAlign) * maxLineLength;
124124
const float shiftY = (-verticalAlign * lineCount + 0.5) * lineHeight;
125-
126-
for (auto& glyph : shaping.positionedGlyphs) {
127-
glyph.x += shiftX;
128-
glyph.y += shiftY;
125+
126+
for (auto& glyphs : shaping.positionedGlyphs) {
127+
for (auto& glyph : glyphs.second) {
128+
glyph.x += shiftX;
129+
glyph.y += shiftY;
130+
}
129131
}
130132
}
131133

@@ -149,7 +151,7 @@ void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
149151
if (it != glyphs->second.glyphs.end() && it->second) {
150152
const float lastAdvance = (*it->second)->metrics.advance * glyph.scale;
151153
const float lineIndent = float(glyph.x + lastAdvance) * justify;
152-
154+
153155
for (std::size_t j = start; j <= end; j++) {
154156
positionedGlyphs[j].x -= lineIndent;
155157
positionedGlyphs[j].y += baselineOffset;
@@ -349,6 +351,7 @@ void shapeLines(Shaping& shaping,
349351
textJustify == style::TextJustifyType::Left ? 0 :
350352
0.5;
351353

354+
uint32_t lineIndex{0};
352355
for (TaggedString& line : lines) {
353356
// Collapse whitespace so it doesn't throw off justification
354357
line.trim();
@@ -357,11 +360,11 @@ void shapeLines(Shaping& shaping,
357360

358361
if (line.empty()) {
359362
y += lineHeight; // Still need a line feed after empty line
363+
++lineIndex;
360364
continue;
361365
}
362366

363367
float biggestHeight{0.0f}, baselineOffset{0.0f};
364-
std::size_t lineStartIndex = shaping.positionedGlyphs.size();
365368
for (std::size_t i = 0; i < line.length(); i++) {
366369
const std::size_t sectionIndex = line.getSectionIndex(i);
367370
const SectionOptions& section = line.sectionAt(sectionIndex);
@@ -409,31 +412,32 @@ void shapeLines(Shaping& shaping,
409412
// are from complex text layout script, or whitespaces.
410413
(allowVerticalPlacement &&
411414
(util::i18n::isWhitespace(codePoint) || util::i18n::isCharInComplexShapingScript(codePoint)))) {
412-
shaping.positionedGlyphs.emplace_back(
415+
shaping.positionedGlyphs[lineIndex].emplace_back(
413416
codePoint, x, y + glyphOffset, false, section.fontStackHash, section.scale, sectionIndex);
414417
x += glyph.metrics.advance * section.scale + spacing;
415418
} else {
416-
shaping.positionedGlyphs.emplace_back(
419+
shaping.positionedGlyphs[lineIndex].emplace_back(
417420
codePoint, x, y + glyphOffset, true, section.fontStackHash, section.scale, sectionIndex);
418421
x += util::ONE_EM * section.scale + spacing;
419422
}
420423
}
421424

422425
// Only justify if we placed at least one glyph
423-
if (shaping.positionedGlyphs.size() != lineStartIndex) {
426+
if (!shaping.positionedGlyphs[lineIndex].empty()) {
424427
float lineLength = x - spacing; // Don't count trailing spacing
425428
maxLineLength = util::max(lineLength, maxLineLength);
426429

427-
justifyLine(shaping.positionedGlyphs,
430+
justifyLine(shaping.positionedGlyphs[lineIndex],
428431
glyphMap,
429-
lineStartIndex,
430-
shaping.positionedGlyphs.size() - 1,
432+
0,
433+
shaping.positionedGlyphs[lineIndex].size() - 1,
431434
justify,
432435
baselineOffset);
433436
}
434-
437+
435438
x = 0;
436439
y += lineHeight * lineMaxScale;
440+
++lineIndex;
437441
}
438442

439443
auto anchorAlign = AnchorAlignment::getAnchorAlignment(textAnchor);

test/text/quads.test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ TEST(getIconQuads, style) {
4747
shapedText.bottom = 30.0f;
4848
shapedText.left = -60.0f;
4949
shapedText.right = 20.0f;
50-
shapedText.positionedGlyphs.emplace_back(PositionedGlyph(32, 0.0f, 0.0f, false, 0, 1.0));
50+
shapedText.positionedGlyphs[0].emplace_back(PositionedGlyph(32, 0.0f, 0.0f, false, 0, 1.0));
5151

5252
// none
5353
{

0 commit comments

Comments
 (0)