From 2bb2a40b6d13d4fe44e8879003a3e53416033a34 Mon Sep 17 00:00:00 2001 From: Alexander Shalamov Date: Wed, 13 Nov 2019 18:04:47 +0200 Subject: [core] Shape images in labels and create image quads --- src/mbgl/layout/layout.hpp | 3 +- src/mbgl/layout/symbol_instance.cpp | 13 +- src/mbgl/layout/symbol_instance.hpp | 16 +- src/mbgl/layout/symbol_layout.cpp | 27 ++- src/mbgl/layout/symbol_layout.hpp | 8 +- src/mbgl/renderer/image_atlas.hpp | 8 + src/mbgl/text/glyph.hpp | 46 ++++- src/mbgl/text/quads.cpp | 182 ++++++++++---------- src/mbgl/text/quads.hpp | 7 +- src/mbgl/text/shaping.cpp | 260 +++++++++++++++++++---------- src/mbgl/text/shaping.hpp | 10 +- src/mbgl/tile/geometry_tile_worker.cpp | 3 +- test/text/cross_tile_symbol_index.test.cpp | 7 +- test/text/quads.test.cpp | 5 +- test/text/shaping.test.cpp | 24 +-- 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& 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& shapedIcon, - const optional& verticallyShapedIcon, - const style::SymbolLayoutProperties::Evaluated& layout, - const style::SymbolPlacementType textPlacement, - const std::array& textOffset, - const GlyphPositions& positions, - bool allowVerticalPlacement); + const ShapedTextOrientations& shapedTextOrientations, + const optional& shapedIcon, + const optional& verticallyShapedIcon, + const style::SymbolLayoutProperties::Evaluated& layout, + const style::SymbolPlacementType textPlacement, + const std::array& 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 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() == SymbolPlacementType::Point; const bool textAlongLine = layout->get() == 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 shapedIcon, - const GlyphPositions& glyphPositions, + const ImageMap& imageMap, std::array 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(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&, std::unordered_map&, const bool firstLoad, const bool showCollisionBoxes) override; @@ -60,7 +62,7 @@ private: const SymbolFeature&, const ShapedTextOrientations& shapedTextOrientations, optional shapedIcon, - const GlyphPositions&, + const ImageMap&, std::array 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 textureRect; uint32_t version; @@ -50,6 +51,13 @@ public: textureRect.h / pixelRatio, }}; } + + Rect paddedTextureRect() const { + return {static_cast(textureRect.x - padding), + static_cast(textureRect.y - padding), + static_cast(textureRect.w + padding * 2), + static_cast(textureRect.h + padding * 2)}; + } }; using ImagePositions = std::map; 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; 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 rect_, + GlyphMetrics metrics_, + optional 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 rect; + GlyphMetrics metrics; + optional imageID; // Maps positioned glyph to TaggedString section std::size_t sectionIndex; }; enum class WritingModeType : uint8_t; +struct PositionedLine { + std::vector 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 positionedGlyphs; + explicit Shaping(float x, float y, WritingModeType writingMode_) + : top(y), bottom(y), left(x), right(x), writingMode(writingMode_) {} + std::vector 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 textOffset, const SymbolLayoutProperties::Evaluated& layout, const style::SymbolPlacementType placement, - const GlyphPositions& positions, + const ImageMap& imageMap, bool allowVerticalPlacement) { const float textRotate = layout.get() * util::DEG2RAD; const bool alongLine = layout.get() == 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& 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 glyphOffset = alongLine ? - Point{ positionedGlyph.x + halfAdvance, positionedGlyph.y } : - Point{ 0.0f, 0.0f }; - - Point builtInOffset = alongLine ? - Point{ 0.0f, 0.0f } : - Point{ positionedGlyph.x + halfAdvance + textOffset[0], positionedGlyph.y + textOffset[1] }; - - Point 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& 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 glyphOffset = + alongLine ? Point{positionedGlyph.x + halfAdvance, positionedGlyph.y} : Point{0.0f, 0.0f}; + + Point builtInOffset = alongLine ? Point{0.0f, 0.0f} + : Point{positionedGlyph.x + halfAdvance + textOffset[0], + positionedGlyph.y + textOffset[1] - lineOffset}; + + Point 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 tl{x1, y1}; + Point tr{x2, y1}; + Point bl{x1, y2}; + Point 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 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 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 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 tl{x1, y1}; - Point tr{x2, y1}; - Point bl{x1, y2}; - Point 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 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 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 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 -#include +#include #include +#include +#include #include #include @@ -50,7 +51,7 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText, const std::array 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& positionedGlyphs, - const GlyphMap& glyphMap, - std::size_t start, - std::size_t end, - float justify) { - if (!justify) { +void justifyLine(std::vector& 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 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& 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 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 -#include #include -#include #include +#include +#include +#include +#include namespace mbgl { @@ -68,7 +69,8 @@ const Shaping getShaping(const TaggedString& string, const std::array& 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 variableTextOffset{{0.0f, 0.0f}}; style::SymbolPlacementType placementType = style::SymbolPlacementType::Point; - auto sharedData = std::make_shared(std::move(line), - shaping, nullopt, nullopt, layout_, placementType, - textOffset, positions, false); + auto sharedData = std::make_shared( + 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(makeMutable(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); -- cgit v1.2.1