summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Shalamov <alexander.shalamov@mapbox.com>2019-11-13 18:04:47 +0200
committerAlexander Shalamov <alexander.shalamov@mapbox.com>2019-12-02 17:11:49 +0200
commit2bb2a40b6d13d4fe44e8879003a3e53416033a34 (patch)
tree19b184fc5faef7a2bfaba579f61c59e8e3e1db7b
parentf4f652063d9a9903d96bf6f32257a5e6fd6dbfe2 (diff)
downloadqtlocation-mapboxgl-2bb2a40b6d13d4fe44e8879003a3e53416033a34.tar.gz
[core] Shape images in labels and create image quads
-rw-r--r--src/mbgl/layout/layout.hpp3
-rw-r--r--src/mbgl/layout/symbol_instance.cpp13
-rw-r--r--src/mbgl/layout/symbol_instance.hpp16
-rw-r--r--src/mbgl/layout/symbol_layout.cpp27
-rw-r--r--src/mbgl/layout/symbol_layout.hpp8
-rw-r--r--src/mbgl/renderer/image_atlas.hpp8
-rw-r--r--src/mbgl/text/glyph.hpp46
-rw-r--r--src/mbgl/text/quads.cpp182
-rw-r--r--src/mbgl/text/quads.hpp7
-rw-r--r--src/mbgl/text/shaping.cpp260
-rw-r--r--src/mbgl/text/shaping.hpp10
-rw-r--r--src/mbgl/tile/geometry_tile_worker.cpp3
-rw-r--r--test/text/cross_tile_symbol_index.test.cpp7
-rw-r--r--test/text/quads.test.cpp5
-rw-r--r--test/text/shaping.test.cpp24
15 files changed, 389 insertions, 230 deletions
diff --git a/src/mbgl/layout/layout.hpp b/src/mbgl/layout/layout.hpp
index 91d3e3f596..4e7c0c7aca 100644
--- a/src/mbgl/layout/layout.hpp
+++ b/src/mbgl/layout/layout.hpp
@@ -23,8 +23,7 @@ public:
const bool,
const bool) = 0;
- virtual void prepareSymbols(const GlyphMap&, const GlyphPositions&,
- const ImageMap&, const ImagePositions&) {};
+ virtual void prepareSymbols(const GlyphMap&, const GlyphPositions&, const ImageMap&, const ImagePositions&){};
virtual bool hasSymbolInstances() const {
return true;
diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp
index 41a00e0131..d48a3af388 100644
--- a/src/mbgl/layout/symbol_instance.cpp
+++ b/src/mbgl/layout/symbol_instance.cpp
@@ -25,8 +25,9 @@ SymbolInstanceSharedData::SymbolInstanceSharedData(GeometryCoordinates line_,
const style::SymbolLayoutProperties::Evaluated& layout,
const style::SymbolPlacementType textPlacement,
const std::array<float, 2>& textOffset,
- const GlyphPositions& positions,
- bool allowVerticalPlacement) : line(std::move(line_)) {
+ const ImageMap& imageMap,
+ bool allowVerticalPlacement)
+ : line(std::move(line_)) {
// Create the quads used for rendering the icon and glyphs.
if (shapedIcon) {
iconQuad = getIconQuad(*shapedIcon, getAnyShaping(shapedTextOrientations).writingMode);
@@ -38,11 +39,12 @@ SymbolInstanceSharedData::SymbolInstanceSharedData(GeometryCoordinates line_,
bool singleLineInitialized = false;
const auto initHorizontalGlyphQuads = [&] (SymbolQuads& quads, const Shaping& shaping) {
if (!shapedTextOrientations.singleLine) {
- quads = getGlyphQuads(shaping, textOffset, layout, textPlacement, positions, allowVerticalPlacement);
+ quads = getGlyphQuads(shaping, textOffset, layout, textPlacement, imageMap, allowVerticalPlacement);
return;
}
if (!singleLineInitialized) {
- rightJustifiedGlyphQuads = getGlyphQuads(shaping, textOffset, layout, textPlacement, positions, allowVerticalPlacement);
+ rightJustifiedGlyphQuads =
+ getGlyphQuads(shaping, textOffset, layout, textPlacement, imageMap, allowVerticalPlacement);
singleLineInitialized = true;
}
};
@@ -60,7 +62,8 @@ SymbolInstanceSharedData::SymbolInstanceSharedData(GeometryCoordinates line_,
}
if (shapedTextOrientations.vertical) {
- verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.vertical, textOffset, layout, textPlacement, positions, allowVerticalPlacement);
+ verticalGlyphQuads = getGlyphQuads(
+ shapedTextOrientations.vertical, textOffset, layout, textPlacement, imageMap, allowVerticalPlacement);
}
}
diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp
index 4a57b527f7..bc2ff48d33 100644
--- a/src/mbgl/layout/symbol_instance.hpp
+++ b/src/mbgl/layout/symbol_instance.hpp
@@ -23,14 +23,14 @@ struct ShapedTextOrientations {
struct SymbolInstanceSharedData {
SymbolInstanceSharedData(GeometryCoordinates line,
- const ShapedTextOrientations& shapedTextOrientations,
- const optional<PositionedIcon>& shapedIcon,
- const optional<PositionedIcon>& verticallyShapedIcon,
- const style::SymbolLayoutProperties::Evaluated& layout,
- const style::SymbolPlacementType textPlacement,
- const std::array<float, 2>& textOffset,
- const GlyphPositions& positions,
- bool allowVerticalPlacement);
+ const ShapedTextOrientations& shapedTextOrientations,
+ const optional<PositionedIcon>& shapedIcon,
+ const optional<PositionedIcon>& verticallyShapedIcon,
+ const style::SymbolLayoutProperties::Evaluated& layout,
+ const style::SymbolPlacementType textPlacement,
+ const std::array<float, 2>& textOffset,
+ const ImageMap& imageMap,
+ bool allowVerticalPlacement);
bool empty() const;
GeometryCoordinates line;
// Note: When singleLine == true, only `rightJustifiedGlyphQuads` is populated.
diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp
index 55de0d2a2b..1207c1c668 100644
--- a/src/mbgl/layout/symbol_layout.cpp
+++ b/src/mbgl/layout/symbol_layout.cpp
@@ -340,8 +340,10 @@ std::array<float, 2> SymbolLayout::evaluateVariableOffset(style::SymbolAnchorTyp
return result;
}
-void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions& glyphPositions,
- const ImageMap& imageMap, const ImagePositions& imagePositions) {
+void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap,
+ const GlyphPositions& glyphPositions,
+ const ImageMap& imageMap,
+ const ImagePositions& imagePositions) {
const bool isPointPlacement = layout->get<SymbolPlacement>() == SymbolPlacementType::Point;
const bool textAlongLine = layout->get<TextRotationAlignment>() == AlignmentType::Map && !isPointPlacement;
@@ -375,7 +377,8 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions
/* translate */ textOffset,
/* writingMode */ writingMode,
/* bidirectional algorithm object */ bidi,
- /* glyphs */ glyphMap,
+ glyphMap,
+ /* glyphs */ glyphPositions,
/* images */ imagePositions,
layoutTextSize,
allowVerticalPlacement);
@@ -430,7 +433,7 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions
Shaping shaping = applyShaping(*feature.formattedText, WritingModeType::Horizontal, SymbolAnchorType::Center, justification);
if (shaping) {
shapingForJustification = std::move(shaping);
- if (shapingForJustification.lineCount == 1u) {
+ if (shapingForJustification.positionedLines.size() == 1u) {
shapedTextOrientations.singleLine = true;
break;
}
@@ -488,7 +491,7 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions
feature,
shapedTextOrientations,
std::move(shapedIcon),
- glyphPositions,
+ imageMap,
textOffset,
layoutTextSize,
layoutIconSize,
@@ -505,7 +508,7 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
const SymbolFeature& feature,
const ShapedTextOrientations& shapedTextOrientations,
optional<PositionedIcon> shapedIcon,
- const GlyphPositions& glyphPositions,
+ const ImageMap& imageMap,
std::array<float, 2> textOffset,
float layoutTextSize,
float layoutIconSize,
@@ -585,10 +588,16 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
}
};
- const auto createSymbolInstanceSharedData = [&] (GeometryCoordinates line) {
+ const auto createSymbolInstanceSharedData = [&](GeometryCoordinates line) {
return std::make_shared<SymbolInstanceSharedData>(std::move(line),
- shapedTextOrientations, shapedIcon, verticallyShapedIcon, evaluatedLayoutProperties,
- textPlacement, textOffset, glyphPositions, allowVerticalPlacement);
+ shapedTextOrientations,
+ shapedIcon,
+ verticallyShapedIcon,
+ evaluatedLayoutProperties,
+ textPlacement,
+ textOffset,
+ imageMap,
+ allowVerticalPlacement);
};
const auto& type = feature.getType();
diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp
index 804bc39741..86bfd46a31 100644
--- a/src/mbgl/layout/symbol_layout.hpp
+++ b/src/mbgl/layout/symbol_layout.hpp
@@ -31,8 +31,10 @@ public:
~SymbolLayout() final = default;
- void prepareSymbols(const GlyphMap&, const GlyphPositions&,
- const ImageMap&, const ImagePositions&) override;
+ void prepareSymbols(const GlyphMap& glyphMap,
+ const GlyphPositions&,
+ const ImageMap&,
+ const ImagePositions&) override;
void createBucket(const ImagePositions&, std::unique_ptr<FeatureIndex>&, std::unordered_map<std::string, LayerRenderData>&, const bool firstLoad, const bool showCollisionBoxes) override;
@@ -60,7 +62,7 @@ private:
const SymbolFeature&,
const ShapedTextOrientations& shapedTextOrientations,
optional<PositionedIcon> shapedIcon,
- const GlyphPositions&,
+ const ImageMap&,
std::array<float, 2> textOffset,
float layoutTextSize,
float layoutIconSize,
diff --git a/src/mbgl/renderer/image_atlas.hpp b/src/mbgl/renderer/image_atlas.hpp
index 56d7406a0a..e4d7d0f009 100644
--- a/src/mbgl/renderer/image_atlas.hpp
+++ b/src/mbgl/renderer/image_atlas.hpp
@@ -20,6 +20,7 @@ class ImagePosition {
public:
ImagePosition(const mapbox::Bin&, const style::Image::Impl&, uint32_t version = 0);
+ static constexpr const uint16_t padding = 1u;
float pixelRatio;
Rect<uint16_t> textureRect;
uint32_t version;
@@ -50,6 +51,13 @@ public:
textureRect.h / pixelRatio,
}};
}
+
+ Rect<uint16_t> paddedTextureRect() const {
+ return {static_cast<uint16_t>(textureRect.x - padding),
+ static_cast<uint16_t>(textureRect.y - padding),
+ static_cast<uint16_t>(textureRect.w + padding * 2),
+ static_cast<uint16_t>(textureRect.h + padding * 2)};
+ }
};
using ImagePositions = std::map<std::string, ImagePosition>;
diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp
index ba9c521f77..19a0d037a9 100644
--- a/src/mbgl/text/glyph.hpp
+++ b/src/mbgl/text/glyph.hpp
@@ -60,38 +60,66 @@ using GlyphMap = std::map<FontStackHash, Glyphs>;
class PositionedGlyph {
public:
- explicit PositionedGlyph(GlyphID glyph_, float x_, float y_, bool vertical_, FontStackHash font_, float scale_, std::size_t sectionIndex_ = 0)
- : glyph(glyph_), x(x_), y(y_), vertical(vertical_), font(font_), scale(scale_), sectionIndex(sectionIndex_)
- {}
+ explicit PositionedGlyph(GlyphID glyph_,
+ float x_,
+ float y_,
+ bool vertical_,
+ FontStackHash font_,
+ float scale_,
+ Rect<uint16_t> rect_,
+ GlyphMetrics metrics_,
+ optional<std::string> imageID_,
+ std::size_t sectionIndex_ = 0)
+ : glyph(glyph_),
+ x(x_),
+ y(y_),
+ vertical(vertical_),
+ font(font_),
+ scale(scale_),
+ rect(std::move(rect_)),
+ metrics(std::move(metrics_)),
+ imageID(std::move(imageID_)),
+ sectionIndex(sectionIndex_) {}
GlyphID glyph = 0;
float x = 0;
float y = 0;
bool vertical = false;
-
FontStackHash font = 0;
float scale = 0.0;
+ Rect<uint16_t> rect;
+ GlyphMetrics metrics;
+ optional<std::string> imageID;
// Maps positioned glyph to TaggedString section
std::size_t sectionIndex;
};
enum class WritingModeType : uint8_t;
+struct PositionedLine {
+ std::vector<PositionedGlyph> positionedGlyphs;
+ float lineOffset = 0.0;
+};
+
class Shaping {
public:
Shaping() = default;
- explicit Shaping(float x, float y, WritingModeType writingMode_, std::size_t lineCount_)
- : top(y), bottom(y), left(x), right(x), writingMode(writingMode_), lineCount(lineCount_) {}
- std::vector<PositionedGlyph> positionedGlyphs;
+ explicit Shaping(float x, float y, WritingModeType writingMode_)
+ : top(y), bottom(y), left(x), right(x), writingMode(writingMode_) {}
+ std::vector<PositionedLine> positionedLines;
float top = 0;
float bottom = 0;
float left = 0;
float right = 0;
WritingModeType writingMode;
- std::size_t lineCount = 0u;
- explicit operator bool() const { return !positionedGlyphs.empty(); }
+ explicit operator bool() const {
+ return std::any_of(positionedLines.begin(), positionedLines.end(), [](const auto& line) {
+ return !line.positionedGlyphs.empty();
+ });
+ }
// The y offset *should* be part of the font metadata.
static constexpr int32_t yOffset = -17;
+ bool verticalizable = false;
};
enum class WritingModeType : uint8_t {
diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp
index a94bfee336..ee17510c35 100644
--- a/src/mbgl/text/quads.cpp
+++ b/src/mbgl/text/quads.cpp
@@ -20,7 +20,7 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon,
// If you have a 10px icon that isn't perfectly aligned to the pixel grid it will cover 11 actual
// pixels. The quad needs to be padded to account for this, otherwise they'll look slightly clipped
// on one edge in some cases.
- constexpr const float border = 1.0f;
+ const float border = ImagePosition::padding;
// Expand the box to respect the 1 pixel border in the atlas image. We're using `image.paddedRect - border`
// instead of image.displaySize because we only pad with one pixel for retina images as well, and the
@@ -77,96 +77,110 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
const std::array<float, 2> textOffset,
const SymbolLayoutProperties::Evaluated& layout,
const style::SymbolPlacementType placement,
- const GlyphPositions& positions,
+ const ImageMap& imageMap,
bool allowVerticalPlacement) {
const float textRotate = layout.get<TextRotate>() * util::DEG2RAD;
const bool alongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map && placement != SymbolPlacementType::Point;
SymbolQuads quads;
- for (const PositionedGlyph &positionedGlyph: shapedText.positionedGlyphs) {
- auto fontPositions = positions.find(positionedGlyph.font);
- if (fontPositions == positions.end())
- continue;
-
- auto positionsIt = fontPositions->second.find(positionedGlyph.glyph);
- if (positionsIt == fontPositions->second.end())
- continue;
-
- const GlyphPosition& glyph = positionsIt->second;
- const Rect<uint16_t>& rect = glyph.rect;
-
- // The rects have an addditional buffer that is not included in their size;
- const float glyphPadding = 1.0f;
- const float rectBuffer = 3.0f + glyphPadding;
-
- const float halfAdvance = glyph.metrics.advance * positionedGlyph.scale / 2.0;
-
- const Point<float> glyphOffset = alongLine ?
- Point<float>{ positionedGlyph.x + halfAdvance, positionedGlyph.y } :
- Point<float>{ 0.0f, 0.0f };
-
- Point<float> builtInOffset = alongLine ?
- Point<float>{ 0.0f, 0.0f } :
- Point<float>{ positionedGlyph.x + halfAdvance + textOffset[0], positionedGlyph.y + textOffset[1] };
-
- Point<float> verticalizedLabelOffset = { 0.0f, 0.0f };
- const bool rotateVerticalGlyph = (alongLine || allowVerticalPlacement) && positionedGlyph.vertical;
- if (rotateVerticalGlyph) {
- // Vertical POI labels, that are rotated 90deg CW and whose glyphs must preserve upright orientation
- // need to be rotated 90deg CCW. After quad is rotated, it is translated to the original built-in offset.
- verticalizedLabelOffset = builtInOffset;
- builtInOffset = { 0.0f, 0.0f };
+ for (const auto& line : shapedText.positionedLines) {
+ for (const auto& positionedGlyph : line.positionedGlyphs) {
+ if (!positionedGlyph.rect.hasArea()) continue;
+
+ // The rects have an addditional buffer that is not included in their size;
+ const float glyphPadding = 1.0f;
+ float rectBuffer = 3.0f + glyphPadding;
+ float pixelRatio = 1.0f;
+ float lineOffset = 0.0f;
+ const bool rotateVerticalGlyph = (alongLine || allowVerticalPlacement) && positionedGlyph.vertical;
+ const float halfAdvance = positionedGlyph.metrics.advance * positionedGlyph.scale / 2.0;
+ const Rect<uint16_t>& rect = positionedGlyph.rect;
+
+ // Align images and scaled glyphs in the middle of a vertical line.
+ if (allowVerticalPlacement && shapedText.verticalizable) {
+ const float scaledGlyphOffset = (positionedGlyph.scale - 1) * util::ONE_EM;
+ const float imageOffset = (util::ONE_EM - positionedGlyph.metrics.width * positionedGlyph.scale) / 2.0f;
+ lineOffset = line.lineOffset / 2.0f - (positionedGlyph.imageID ? -imageOffset : scaledGlyphOffset);
+ }
+
+ if (positionedGlyph.imageID) {
+ auto image = imageMap.find(*positionedGlyph.imageID);
+ if (image == imageMap.end()) {
+ continue;
+ }
+ pixelRatio = image->second->pixelRatio;
+ rectBuffer = ImagePosition::padding / pixelRatio;
+ }
+
+ const Point<float> glyphOffset =
+ alongLine ? Point<float>{positionedGlyph.x + halfAdvance, positionedGlyph.y} : Point<float>{0.0f, 0.0f};
+
+ Point<float> builtInOffset = alongLine ? Point<float>{0.0f, 0.0f}
+ : Point<float>{positionedGlyph.x + halfAdvance + textOffset[0],
+ positionedGlyph.y + textOffset[1] - lineOffset};
+
+ Point<float> verticalizedLabelOffset = {0.0f, 0.0f};
+ if (rotateVerticalGlyph) {
+ // Vertical POI labels, that are rotated 90deg CW and whose glyphs must preserve upright orientation
+ // need to be rotated 90deg CCW. After quad is rotated, it is translated to the original built-in
+ // offset.
+ verticalizedLabelOffset = builtInOffset;
+ builtInOffset = {0.0f, 0.0f};
+ }
+
+ const float x1 =
+ (positionedGlyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset.x;
+ const float y1 = (-positionedGlyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset.y;
+ const float x2 = x1 + rect.w * positionedGlyph.scale / pixelRatio;
+ const float y2 = y1 + rect.h * positionedGlyph.scale / pixelRatio;
+
+ Point<float> tl{x1, y1};
+ Point<float> tr{x2, y1};
+ Point<float> bl{x1, y2};
+ Point<float> br{x2, y2};
+
+ if (rotateVerticalGlyph) {
+ // Vertical-supporting glyphs are laid out in 24x24 point boxes (1 square em)
+ // In horizontal orientation, the y values for glyphs are below the midline
+ // and we use a "yOffset" of -17 to pull them up to the middle.
+ // By rotating counter-clockwise around the point at the center of the left
+ // edge of a 24x24 layout box centered below the midline, we align the center
+ // of the glyphs with the horizontal midline, so the yOffset is no longer
+ // necessary, but we also pull the glyph to the left along the x axis.
+ // The y coordinate includes baseline yOffset, therefore, needs to be accounted
+ // for when glyph is rotated and translated.
+
+ const Point<float> center{-halfAdvance, halfAdvance - Shaping::yOffset};
+ const float verticalRotation = -M_PI_2;
+
+ // xHalfWidhtOffsetcorrection is a difference between full-width and half-width
+ // advance, should be 0 for full-width glyphs and will pull up half-width glyphs.
+ const float xHalfWidhtOffsetcorrection = util::ONE_EM / 2 - halfAdvance;
+ const float yImageOffsetCorrection = positionedGlyph.imageID ? xHalfWidhtOffsetcorrection : 0.0f;
+ const Point<float> xOffsetCorrection{5.0f - Shaping::yOffset - xHalfWidhtOffsetcorrection,
+ -yImageOffsetCorrection};
+
+ tl = util::rotate(tl - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
+ tr = util::rotate(tr - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
+ bl = util::rotate(bl - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
+ br = util::rotate(br - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
+ }
+
+ if (textRotate) {
+ // Compute the transformation matrix.
+ float angle_sin = std::sin(textRotate);
+ float angle_cos = std::cos(textRotate);
+ std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}};
+
+ tl = util::matrixMultiply(matrix, tl);
+ tr = util::matrixMultiply(matrix, tr);
+ bl = util::matrixMultiply(matrix, bl);
+ br = util::matrixMultiply(matrix, br);
+ }
+
+ quads.emplace_back(tl, tr, bl, br, rect, shapedText.writingMode, glyphOffset, positionedGlyph.sectionIndex);
}
-
- const float x1 = (glyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset.x;
- const float y1 = (-glyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset.y;
- const float x2 = x1 + rect.w * positionedGlyph.scale;
- const float y2 = y1 + rect.h * positionedGlyph.scale;
-
- Point<float> tl{x1, y1};
- Point<float> tr{x2, y1};
- Point<float> bl{x1, y2};
- Point<float> br{x2, y2};
-
- if (rotateVerticalGlyph) {
- // Vertical-supporting glyphs are laid out in 24x24 point boxes (1 square em)
- // In horizontal orientation, the y values for glyphs are below the midline
- // and we use a "yOffset" of -17 to pull them up to the middle.
- // By rotating counter-clockwise around the point at the center of the left
- // edge of a 24x24 layout box centered below the midline, we align the center
- // of the glyphs with the horizontal midline, so the yOffset is no longer
- // necessary, but we also pull the glyph to the left along the x axis.
- // The y coordinate includes baseline yOffset, therefore, needs to be accounted
- // for when glyph is rotated and translated.
-
- const Point<float> center{ -halfAdvance, halfAdvance - Shaping::yOffset };
- const float verticalRotation = -M_PI_2;
-
- // xHalfWidhtOffsetcorrection is a difference between full-width and half-width
- // advance, should be 0 for full-width glyphs and will pull up half-width glyphs.
- const float xHalfWidhtOffsetcorrection = util::ONE_EM / 2 - halfAdvance;
- const Point<float> xOffsetCorrection{ 5.0f - Shaping::yOffset - xHalfWidhtOffsetcorrection, 0.0f };
-
- tl = util::rotate(tl - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
- tr = util::rotate(tr - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
- bl = util::rotate(bl - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
- br = util::rotate(br - center, verticalRotation) + center + xOffsetCorrection + verticalizedLabelOffset;
- }
-
- if (textRotate) {
- // Compute the transformation matrix.
- float angle_sin = std::sin(textRotate);
- float angle_cos = std::cos(textRotate);
- std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}};
-
- tl = util::matrixMultiply(matrix, tl);
- tr = util::matrixMultiply(matrix, tr);
- bl = util::matrixMultiply(matrix, bl);
- br = util::matrixMultiply(matrix, br);
- }
-
- quads.emplace_back(tl, tr, bl, br, rect, shapedText.writingMode, glyphOffset, positionedGlyph.sectionIndex);
}
return quads;
diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp
index 4435c9aab8..f67266b1ec 100644
--- a/src/mbgl/text/quads.hpp
+++ b/src/mbgl/text/quads.hpp
@@ -1,8 +1,9 @@
#pragma once
-#include <mbgl/text/glyph_atlas.hpp>
-#include <mbgl/style/types.hpp>
+#include <mbgl/style/image_impl.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
+#include <mbgl/style/types.hpp>
+#include <mbgl/text/glyph_atlas.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
#include <vector>
@@ -50,7 +51,7 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
const std::array<float, 2> textOffset,
const style::SymbolLayoutProperties::Evaluated&,
style::SymbolPlacementType placement,
- const GlyphPositions& positions,
+ const ImageMap& imageMap,
bool allowVerticalPlacement);
} // namespace mbgl
diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp
index 9714a54c28..55e46179b0 100644
--- a/src/mbgl/text/shaping.cpp
+++ b/src/mbgl/text/shaping.cpp
@@ -122,44 +122,43 @@ void PositionedIcon::fitIconToText(const Shaping& shapedText,
}
void align(Shaping& shaping,
- const float justify,
- const float horizontalAlign,
- const float verticalAlign,
- const float maxLineLength,
- const float lineHeight,
- const std::size_t lineCount) {
+ float justify,
+ float horizontalAlign,
+ float verticalAlign,
+ float maxLineLength,
+ float maxLineHeight,
+ float lineHeight,
+ float blockHeight,
+ std::size_t lineCount) {
const float shiftX = (justify - horizontalAlign) * maxLineLength;
- const float shiftY = (-verticalAlign * lineCount + 0.5) * lineHeight;
-
- for (auto& glyph : shaping.positionedGlyphs) {
- glyph.x += shiftX;
- glyph.y += shiftY;
+ float shiftY = 0.0f;
+
+ if (maxLineHeight != lineHeight) {
+ shiftY = -blockHeight * verticalAlign - Shaping::yOffset;
+ } else {
+ shiftY = (-verticalAlign * lineCount + 0.5) * lineHeight;
+ }
+
+ for (auto& line : shaping.positionedLines) {
+ for (auto& positionedGlyph : line.positionedGlyphs) {
+ positionedGlyph.x += shiftX;
+ positionedGlyph.y += shiftY;
+ }
}
}
// justify left = 0, right = 1, center = .5
-void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
- const GlyphMap& glyphMap,
- std::size_t start,
- std::size_t end,
- float justify) {
- if (!justify) {
+void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs, float justify, float lineOffset) {
+ if (!justify && !lineOffset) {
return;
}
-
- PositionedGlyph& glyph = positionedGlyphs[end];
- auto glyphs = glyphMap.find(glyph.font);
- if (glyphs == glyphMap.end()) {
- return;
- }
- auto it = glyphs->second.find(glyph.glyph);
- if (it != glyphs->second.end() && it->second) {
- const float lastAdvance = (*it->second)->metrics.advance * glyph.scale;
- const float lineIndent = float(glyph.x + lastAdvance) * justify;
-
- for (std::size_t j = start; j <= end; j++) {
- positionedGlyphs[j].x -= lineIndent;
- }
+
+ PositionedGlyph& lastGlyph = positionedGlyphs.back();
+ const float lastAdvance = lastGlyph.metrics.advance * lastGlyph.scale;
+ const float lineIndent = float(lastGlyph.x + lastAdvance) * justify;
+ for (auto& positionedGlyph : positionedGlyphs) {
+ positionedGlyph.x -= lineIndent;
+ positionedGlyph.y += lineOffset;
}
}
@@ -172,17 +171,17 @@ float getGlyphAdvance(char16_t codePoint,
if (!section.imageID) {
auto glyphs = glyphMap.find(section.fontStackHash);
if (glyphs == glyphMap.end()) {
- return 0.0;
+ return 0.0f;
}
auto it = glyphs->second.find(codePoint);
if (it == glyphs->second.end() || !it->second) {
- return 0.0;
+ return 0.0f;
}
return (*it->second)->metrics.advance * section.scale + spacing;
} else {
auto image = imagePositions.find(*section.imageID);
if (image == imagePositions.end()) {
- return 0.0;
+ return 0.0f;
}
return image->second.displaySize()[0] * section.scale * util::ONE_EM / layoutTextSize + spacing;
}
@@ -348,83 +347,169 @@ void shapeLines(Shaping& shaping,
const style::TextJustifyType textJustify,
const WritingModeType writingMode,
const GlyphMap& glyphMap,
- const ImagePositions& /*imagePositions*/,
- float /*layoutTextSize*/,
+ const GlyphPositions& glyphPositions,
+ const ImagePositions& imagePositions,
+ float layoutTextSize,
bool allowVerticalPlacement) {
- float x = 0;
+ float x = 0.0f;
float y = Shaping::yOffset;
-
- float maxLineLength = 0;
+ float maxLineLength = 0.0f;
+ double maxLineHeight = 0.0f;
+
+ const float justify =
+ textJustify == style::TextJustifyType::Right ? 1.0f : textJustify == style::TextJustifyType::Left ? 0.0f : 0.5f;
- const float justify = textJustify == style::TextJustifyType::Right ? 1 :
- textJustify == style::TextJustifyType::Left ? 0 :
- 0.5;
-
for (TaggedString& line : lines) {
// Collapse whitespace so it doesn't throw off justification
line.trim();
const double lineMaxScale = line.getMaxScale();
-
+ const double maxLineOffset = (lineMaxScale - 1.0) * util::ONE_EM;
+ double lineOffset = 0.0;
+ shaping.positionedLines.emplace_back();
+ auto& positionedLine = shaping.positionedLines.back();
+ auto& positionedGlyphs = positionedLine.positionedGlyphs;
+
if (line.empty()) {
y += lineHeight; // Still need a line feed after empty line
continue;
}
- std::size_t lineStartIndex = shaping.positionedGlyphs.size();
for (std::size_t i = 0; i < line.length(); i++) {
const std::size_t sectionIndex = line.getSectionIndex(i);
const SectionOptions& section = line.sectionAt(sectionIndex);
char16_t codePoint = line.getCharCodeAt(i);
- auto glyphs = glyphMap.find(section.fontStackHash);
- if (glyphs == glyphMap.end()) {
- continue;
- }
- auto it = glyphs->second.find(codePoint);
- if (it == glyphs->second.end() || !it->second) {
- continue;
+ double baselineOffset = 0.0;
+ Rect<uint16_t> rect;
+ GlyphMetrics metrics;
+ float advance = 0.0f;
+ float verticalAdvance = util::ONE_EM;
+ double sectionScale = section.scale;
+ assert(sectionScale);
+
+ const bool vertical =
+ !(writingMode == WritingModeType::Horizontal ||
+ // Don't verticalize glyphs that have no upright orientation if vertical placement is disabled.
+ (!allowVerticalPlacement && !util::i18n::hasUprightVerticalOrientation(codePoint)) ||
+ // If vertical placement is ebabled, don't verticalize glyphs that
+ // are from complex text layout script, or whitespaces.
+ (allowVerticalPlacement &&
+ (util::i18n::isWhitespace(codePoint) || util::i18n::isCharInComplexShapingScript(codePoint))));
+
+ if (!section.imageID) {
+ auto glyphPositionMap = glyphPositions.find(section.fontStackHash);
+ if (glyphPositionMap == glyphPositions.end()) {
+ continue;
+ }
+
+ auto glyphPosition = glyphPositionMap->second.find(codePoint);
+ if (glyphPosition != glyphPositionMap->second.end()) {
+ rect = glyphPosition->second.rect;
+ metrics = glyphPosition->second.metrics;
+ } else {
+ auto glyphs = glyphMap.find(section.fontStackHash);
+ if (glyphs == glyphMap.end()) {
+ continue;
+ }
+
+ auto glyph = glyphs->second.find(codePoint);
+ if (glyph == glyphs->second.end() || !glyph->second) {
+ continue;
+ }
+ metrics = (*glyph->second)->metrics;
+ }
+ advance = metrics.advance;
+ // We don't know the baseline, but since we're laying out
+ // at 24 points, we can calculate how much it will move when
+ // we scale up or down.
+ baselineOffset = (lineMaxScale - sectionScale) * util::ONE_EM;
+ } else {
+ auto image = imagePositions.find(*section.imageID);
+ if (image == imagePositions.end()) {
+ continue;
+ }
+ const auto& displaySize = image->second.displaySize();
+ metrics.width = displaySize[0];
+ metrics.height = displaySize[1];
+ metrics.left = ImagePosition::padding;
+ metrics.top = -Glyph::borderSize;
+ metrics.advance = vertical ? displaySize[1] : displaySize[0];
+ rect = image->second.paddedTextureRect();
+
+ // If needed, allow to set scale factor for an image using
+ // alias "image-scale" that could be alias for "font-scale"
+ // when FormattedSection is an image section.
+ sectionScale = sectionScale * util::ONE_EM / layoutTextSize;
+
+ // Difference between one EM and an image size.
+ // Aligns bottom of an image to a baseline level.
+ float imageOffset = util::ONE_EM - displaySize[1] * sectionScale;
+ baselineOffset = maxLineOffset + imageOffset;
+ advance = verticalAdvance = metrics.advance;
+
+ // Difference between height of an image and one EM at max line scale.
+ // Pushes current line down if an image size is over 1 EM at max line scale.
+ double offset =
+ (vertical ? displaySize[0] : displaySize[1]) * sectionScale - util::ONE_EM * lineMaxScale;
+ if (offset > 0.0 && offset > lineOffset) {
+ lineOffset = offset;
+ }
}
-
- // We don't know the baseline, but since we're laying out
- // at 24 points, we can calculate how much it will move when
- // we scale up or down.
- const double baselineOffset = (lineMaxScale - section.scale) * util::ONE_EM;
-
- const Glyph& glyph = **it->second;
-
- if (writingMode == WritingModeType::Horizontal ||
- // Don't verticalize glyphs that have no upright orientation if vertical placement is disabled.
- (!allowVerticalPlacement && !util::i18n::hasUprightVerticalOrientation(codePoint)) ||
- // If vertical placement is ebabled, don't verticalize glyphs that
- // are from complex text layout script, or whitespaces.
- (allowVerticalPlacement && (util::i18n::isWhitespace(codePoint) || util::i18n::isCharInComplexShapingScript(codePoint)))) {
- shaping.positionedGlyphs.emplace_back(codePoint, x, y + baselineOffset, false, section.fontStackHash, section.scale, sectionIndex);
- x += glyph.metrics.advance * section.scale + spacing;
+
+ if (!vertical) {
+ positionedGlyphs.emplace_back(codePoint,
+ x,
+ y + baselineOffset,
+ vertical,
+ section.fontStackHash,
+ sectionScale,
+ rect,
+ metrics,
+ section.imageID,
+ sectionIndex);
+ x += advance * sectionScale + spacing;
} else {
- shaping.positionedGlyphs.emplace_back(codePoint, x, y + baselineOffset, true, section.fontStackHash, section.scale, sectionIndex);
- x += util::ONE_EM * section.scale + spacing;
+ positionedGlyphs.emplace_back(codePoint,
+ x,
+ y + baselineOffset,
+ vertical,
+ section.fontStackHash,
+ sectionScale,
+ rect,
+ metrics,
+ section.imageID,
+ sectionIndex);
+ x += verticalAdvance * sectionScale + spacing;
+ shaping.verticalizable |= true;
}
}
-
+
// Only justify if we placed at least one glyph
- if (shaping.positionedGlyphs.size() != lineStartIndex) {
+ if (positionedGlyphs.size() != 0) {
float lineLength = x - spacing; // Don't count trailing spacing
maxLineLength = util::max(lineLength, maxLineLength);
-
- justifyLine(shaping.positionedGlyphs, glyphMap, lineStartIndex,
- shaping.positionedGlyphs.size() - 1, justify);
+ justifyLine(positionedGlyphs, justify, lineOffset);
}
-
- x = 0;
- y += lineHeight * lineMaxScale;
+
+ double currentLineHeight = lineHeight * lineMaxScale + lineOffset;
+ x = 0.0f;
+ y += currentLineHeight;
+ positionedLine.lineOffset = std::max(lineOffset, maxLineOffset);
+ maxLineHeight = std::max(currentLineHeight, maxLineHeight);
}
auto anchorAlign = AnchorAlignment::getAnchorAlignment(textAnchor);
-
- align(shaping, justify, anchorAlign.horizontalAlign, anchorAlign.verticalAlign, maxLineLength,
- lineHeight, lines.size());
const float height = y - Shaping::yOffset;
+ align(shaping,
+ justify,
+ anchorAlign.horizontalAlign,
+ anchorAlign.verticalAlign,
+ maxLineLength,
+ maxLineHeight,
+ lineHeight,
+ height,
+ lines.size());
// Calculate the bounding box
shaping.top += -anchorAlign.verticalAlign * height;
@@ -442,27 +527,29 @@ const Shaping getShaping(const TaggedString& formattedString,
const std::array<float, 2>& translate,
const WritingModeType writingMode,
BiDi& bidi,
- const GlyphMap& glyphs,
+ const GlyphMap& glyphMap,
+ const GlyphPositions& glyphPositions,
const ImagePositions& imagePositions,
float layoutTextSize,
bool allowVerticalPlacement) {
+ assert(layoutTextSize);
std::vector<TaggedString> reorderedLines;
if (formattedString.sectionCount() == 1) {
auto untaggedLines = bidi.processText(
formattedString.rawText(),
- determineLineBreaks(formattedString, spacing, maxWidth, glyphs, imagePositions, layoutTextSize));
+ determineLineBreaks(formattedString, spacing, maxWidth, glyphMap, imagePositions, layoutTextSize));
for (const auto& line : untaggedLines) {
reorderedLines.emplace_back(line, formattedString.sectionAt(0));
}
} else {
auto processedLines = bidi.processStyledText(
formattedString.getStyledText(),
- determineLineBreaks(formattedString, spacing, maxWidth, glyphs, imagePositions, layoutTextSize));
+ determineLineBreaks(formattedString, spacing, maxWidth, glyphMap, imagePositions, layoutTextSize));
for (const auto& line : processedLines) {
reorderedLines.emplace_back(line, formattedString.getSections());
}
}
- Shaping shaping(translate[0], translate[1], writingMode, reorderedLines.size());
+ Shaping shaping(translate[0], translate[1], writingMode);
shapeLines(shaping,
reorderedLines,
spacing,
@@ -470,7 +557,8 @@ const Shaping getShaping(const TaggedString& formattedString,
textAnchor,
textJustify,
writingMode,
- glyphs,
+ glyphMap,
+ glyphPositions,
imagePositions,
layoutTextSize,
allowVerticalPlacement);
diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp
index ac608563ff..c170642e82 100644
--- a/src/mbgl/text/shaping.hpp
+++ b/src/mbgl/text/shaping.hpp
@@ -1,10 +1,11 @@
#pragma once
-#include <mbgl/text/glyph.hpp>
-#include <mbgl/text/tagged_string.hpp>
#include <mbgl/renderer/image_atlas.hpp>
-#include <mbgl/style/types.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
+#include <mbgl/style/types.hpp>
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/text/glyph_atlas.hpp>
+#include <mbgl/text/tagged_string.hpp>
namespace mbgl {
@@ -68,7 +69,8 @@ const Shaping getShaping(const TaggedString& string,
const std::array<float, 2>& translate,
const WritingModeType,
BiDi& bidi,
- const GlyphMap& glyphs,
+ const GlyphMap& glyphMap,
+ const GlyphPositions& glyphPositions,
const ImagePositions& imagePositions,
float layoutTextSize,
bool allowVerticalPlacement);
diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp
index a61321b363..c38464bb04 100644
--- a/src/mbgl/tile/geometry_tile_worker.cpp
+++ b/src/mbgl/tile/geometry_tile_worker.cpp
@@ -460,8 +460,7 @@ void GeometryTileWorker::finalizeLayout() {
return;
}
- layout->prepareSymbols(glyphMap, glyphAtlas.positions,
- imageMap, iconAtlas.iconPositions);
+ layout->prepareSymbols(glyphMap, glyphAtlas.positions, imageMap, iconAtlas.iconPositions);
if (!layout->hasSymbolInstances()) {
continue;
diff --git a/test/text/cross_tile_symbol_index.test.cpp b/test/text/cross_tile_symbol_index.test.cpp
index 4ff84063f9..a02055e70f 100644
--- a/test/text/cross_tile_symbol_index.test.cpp
+++ b/test/text/cross_tile_symbol_index.test.cpp
@@ -6,7 +6,7 @@ using namespace mbgl;
SymbolInstance makeSymbolInstance(float x, float y, std::u16string key) {
GeometryCoordinates line;
- GlyphPositions positions;
+ ImageMap imageMap;
const ShapedTextOrientations shaping{};
style::SymbolLayoutProperties::Evaluated layout_;
IndexedSubfeature subfeature(0, "", "", 0);
@@ -16,9 +16,8 @@ SymbolInstance makeSymbolInstance(float x, float y, std::u16string key) {
std::array<float, 2> variableTextOffset{{0.0f, 0.0f}};
style::SymbolPlacementType placementType = style::SymbolPlacementType::Point;
- auto sharedData = std::make_shared<SymbolInstanceSharedData>(std::move(line),
- shaping, nullopt, nullopt, layout_, placementType,
- textOffset, positions, false);
+ auto sharedData = std::make_shared<SymbolInstanceSharedData>(
+ std::move(line), shaping, nullopt, nullopt, layout_, placementType, textOffset, imageMap, false);
return SymbolInstance(anchor, std::move(sharedData), shaping, nullopt, nullopt, 0, 0, placementType, textOffset, 0, 0, iconOffset, subfeature, 0, 0, key, 0.0f, 0.0f, 0.0f, variableTextOffset, false);
}
diff --git a/test/text/quads.test.cpp b/test/text/quads.test.cpp
index b04617a40b..4d7f254efa 100644
--- a/test/text/quads.test.cpp
+++ b/test/text/quads.test.cpp
@@ -45,7 +45,10 @@ TEST(getIconQuads, style) {
shapedText.bottom = 30.0f;
shapedText.left = -60.0f;
shapedText.right = 20.0f;
- shapedText.positionedGlyphs.emplace_back(PositionedGlyph(32, 0.0f, 0.0f, false, 0, 1.0));
+ // shapedText.positionedGlyphs.emplace_back(PositionedGlyph(32, 0.0f, 0.0f, false, 0, 1.0));
+ shapedText.positionedLines.emplace_back();
+ shapedText.positionedLines.back().positionedGlyphs.emplace_back(
+ PositionedGlyph(32, 0.0f, 0.0f, false, 0, 1.0, /*texRect*/ {}, /*metrics*/ {}, /*imageID*/ nullopt));
// none
{
diff --git a/test/text/shaping.test.cpp b/test/text/shaping.test.cpp
index c4d2ef7fc4..53f8505393 100644
--- a/test/text/shaping.test.cpp
+++ b/test/text/shaping.test.cpp
@@ -10,13 +10,16 @@ using namespace mbgl;
using namespace util;
TEST(Shaping, ZWSP) {
+ GlyphPosition glyphPosition;
+ glyphPosition.metrics.width = 18;
+ glyphPosition.metrics.height = 18;
+ glyphPosition.metrics.left = 2;
+ glyphPosition.metrics.top = -8;
+ glyphPosition.metrics.advance = 21;
+
Glyph glyph;
glyph.id = u'中';
- glyph.metrics.width = 18;
- glyph.metrics.height = 18;
- glyph.metrics.left = 2;
- glyph.metrics.top = -8;
- glyph.metrics.advance = 21;
+ glyph.metrics = glyphPosition.metrics;
BiDi bidi;
auto immutableGlyph = Immutable<Glyph>(makeMutable<Glyph>(std::move(glyph)));
@@ -25,7 +28,7 @@ TEST(Shaping, ZWSP) {
GlyphMap glyphs = {
{ FontStackHasher()(fontStack), {{u'中', std::move(immutableGlyph)}} }
};
-
+ GlyphPositions glyphPositions = {{FontStackHasher()(fontStack), {{u'中', std::move(glyphPosition)}}}};
ImagePositions imagePositions;
const auto testGetShaping = [&](const TaggedString& string, unsigned maxWidthInChars) {
@@ -39,6 +42,7 @@ TEST(Shaping, ZWSP) {
WritingModeType::Horizontal,
bidi,
glyphs,
+ glyphPositions,
imagePositions,
16.0,
/*allowVerticalPlacement*/ false);
@@ -51,7 +55,7 @@ TEST(Shaping, ZWSP) {
{
TaggedString string(u"中中\u200b中中\u200b中中\u200b中中中中中中\u200b中中", sectionOptions);
auto shaping = testGetShaping(string, 5);
- ASSERT_EQ(shaping.lineCount, 3);
+ ASSERT_EQ(shaping.positionedLines.size(), 3);
ASSERT_EQ(shaping.top, -36);
ASSERT_EQ(shaping.bottom, 36);
ASSERT_EQ(shaping.left, -63);
@@ -65,7 +69,7 @@ TEST(Shaping, ZWSP) {
{
TaggedString string(u"中中\u200b中", sectionOptions);
auto shaping = testGetShaping(string, 1);
- ASSERT_EQ(shaping.lineCount, 2);
+ ASSERT_EQ(shaping.positionedLines.size(), 2);
ASSERT_EQ(shaping.top, -24);
ASSERT_EQ(shaping.bottom, 24);
ASSERT_EQ(shaping.left, -21);
@@ -78,7 +82,7 @@ TEST(Shaping, ZWSP) {
{
TaggedString string(u"中中\u200b", sectionOptions);
auto shaping = testGetShaping(string, 2);
- ASSERT_EQ(shaping.lineCount, 1);
+ ASSERT_EQ(shaping.positionedLines.size(), 1);
ASSERT_EQ(shaping.top, -12);
ASSERT_EQ(shaping.bottom, 12);
ASSERT_EQ(shaping.left, -21);
@@ -90,7 +94,7 @@ TEST(Shaping, ZWSP) {
{
TaggedString string(u"\u200b\u200b\u200b\u200b\u200b", sectionOptions);
auto shaping = testGetShaping(string, 1);
- ASSERT_EQ(shaping.lineCount, 5);
+ ASSERT_EQ(shaping.positionedLines.size(), 5);
ASSERT_EQ(shaping.top, -60);
ASSERT_EQ(shaping.bottom, 60);
ASSERT_EQ(shaping.left, 0);