[core] Shape images in labels and create image quads

This commit is contained in:
Alexander Shalamov
2019-11-13 18:04:47 +02:00
parent f4f652063d
commit 2bb2a40b6d
15 changed files with 383 additions and 224 deletions

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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.

View File

@@ -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();

View File

@@ -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,

View File

@@ -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>;

View File

@@ -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 {

View File

@@ -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;
for (const auto& line : shapedText.positionedLines) {
for (const auto& positionedGlyph : line.positionedGlyphs) {
if (!positionedGlyph.rect.hasArea()) 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;
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;
// The rects have an addditional buffer that is not included in their size;
const float glyphPadding = 1.0f;
const float rectBuffer = 3.0f + glyphPadding;
// 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);
}
const float halfAdvance = glyph.metrics.advance * positionedGlyph.scale / 2.0;
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 };
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> 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 };
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 };
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;

View File

@@ -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

View File

@@ -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;
}
// 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;
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);
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;
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 {
shaping.positionedGlyphs.emplace_back(codePoint, x, y + baselineOffset, true, section.fontStackHash, section.scale, sectionIndex);
x += util::ONE_EM * section.scale + spacing;
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;
}
}
if (!vertical) {
positionedGlyphs.emplace_back(codePoint,
x,
y + baselineOffset,
vertical,
section.fontStackHash,
sectionScale,
rect,
metrics,
section.imageID,
sectionIndex);
x += advance * sectionScale + spacing;
} else {
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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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
{

View File

@@ -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);