From 5a1e4c6b816ef495e53e54f563ecfcef3c5839aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 10 Dec 2019 17:38:21 +0100 Subject: [core] implement stretchable icons for icon-text-fit --- include/mbgl/style/image.hpp | 2 +- metrics/ignores/platform-all.json | 10 -- src/mbgl/layout/symbol_instance.cpp | 5 +- src/mbgl/layout/symbol_instance.hpp | 1 + src/mbgl/layout/symbol_layout.cpp | 4 +- src/mbgl/renderer/image_atlas.hpp | 2 +- src/mbgl/style/image.cpp | 2 +- src/mbgl/style/image_impl.cpp | 6 +- src/mbgl/style/image_impl.hpp | 2 +- src/mbgl/text/collision_feature.cpp | 19 ++- src/mbgl/text/collision_feature.hpp | 23 ++- src/mbgl/text/quads.cpp | 224 ++++++++++++++++++++++------- src/mbgl/text/quads.hpp | 5 +- src/mbgl/text/shaping.cpp | 12 +- src/mbgl/text/shaping.hpp | 26 +++- test/text/cross_tile_symbol_index.test.cpp | 1 + test/text/quads.test.cpp | 40 +++--- 17 files changed, 279 insertions(+), 105 deletions(-) diff --git a/include/mbgl/style/image.hpp b/include/mbgl/style/image.hpp index bbea081c71..0d9c863e85 100644 --- a/include/mbgl/style/image.hpp +++ b/include/mbgl/style/image.hpp @@ -65,7 +65,7 @@ public: const ImageStretches& getStretchY() const; // The space where text can be fit into this image. - const ImageContent& getContent() const; + const optional& getContent() const; class Impl; Immutable baseImpl; diff --git a/metrics/ignores/platform-all.json b/metrics/ignores/platform-all.json index e1b5c57fc0..b92310a756 100644 --- a/metrics/ignores/platform-all.json +++ b/metrics/ignores/platform-all.json @@ -82,16 +82,6 @@ "render-tests/fill-extrusion-pattern/tile-buffer": "https://github.com/mapbox/mapbox-gl-js/issues/3327", "render-tests/fill-pattern/update-feature-state": "https://github.com/mapbox/mapbox-gl-native/issues/15895", "render-tests/geojson/inline-linestring-fill": "current behavior is arbitrary", - "render-tests/icon-text-fit/stretch-fifteen-part": "https://github.com/mapbox/mapbox-gl-native/issues/16017", - "render-tests/icon-text-fit/stretch-nine-part": "https://github.com/mapbox/mapbox-gl-native/issues/16017", - "render-tests/icon-text-fit/stretch-nine-part-@2x": "https://github.com/mapbox/mapbox-gl-native/issues/16017", - "render-tests/icon-text-fit/stretch-nine-part-content": "https://github.com/mapbox/mapbox-gl-native/issues/16017", - "render-tests/icon-text-fit/stretch-nine-part-content-collision": "https://github.com/mapbox/mapbox-gl-native/issues/16017", - "render-tests/icon-text-fit/stretch-nine-part-just-height": "https://github.com/mapbox/mapbox-gl-native/issues/16017", - "render-tests/icon-text-fit/stretch-nine-part-just-width": "https://github.com/mapbox/mapbox-gl-native/issues/16017", - "render-tests/icon-text-fit/stretch-three-part": "https://github.com/mapbox/mapbox-gl-native/issues/16017", - "render-tests/icon-text-fit/stretch-two-part": "https://github.com/mapbox/mapbox-gl-native/issues/16017", - "render-tests/icon-text-fit/stretch-underscale": "https://github.com/mapbox/mapbox-gl-native/issues/16017", "render-tests/icon-text-fit/text-variable-anchor-overlap": "https://github.com/mapbox/mapbox-gl-native/issues/15809", "render-tests/mixed-zoom/z10-z11": "https://github.com/mapbox/mapbox-gl-native/issues/10397", "render-tests/raster-masking/overlapping-zoom": "https://github.com/mapbox/mapbox-gl-native/issues/10195", diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index 01ba3e26a6..9eda80d4ea 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -28,13 +28,14 @@ SymbolInstanceSharedData::SymbolInstanceSharedData(GeometryCoordinates line_, const ImageMap& imageMap, float iconRotation, SymbolContent iconType, + bool hasIconTextFit, bool allowVerticalPlacement) : line(std::move(line_)) { // Create the quads used for rendering the icon and glyphs. if (shapedIcon) { - iconQuads = getIconQuads(*shapedIcon, iconRotation, iconType); + iconQuads = getIconQuads(*shapedIcon, iconRotation, iconType, hasIconTextFit); if (verticallyShapedIcon) { - verticalIconQuads = getIconQuads(*verticallyShapedIcon, iconRotation, iconType); + verticalIconQuads = getIconQuads(*verticallyShapedIcon, iconRotation, iconType, hasIconTextFit); } } diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index 56dd82240e..2a25c655aa 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -34,6 +34,7 @@ struct SymbolInstanceSharedData { const ImageMap& imageMap, float iconRotation, SymbolContent iconType, + bool hasIconTextFit, bool allowVerticalPlacement); bool empty() const; GeometryCoordinates line; diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 1772d1ef91..1f447ddc9f 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -555,9 +555,10 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, IndexedSubfeature indexedFeature(feature.index, sourceLayer->getName(), bucketLeaderID, symbolInstances.size()); const auto iconTextFit = evaluatedLayoutProperties.get(); + const bool hasIconTextFit = iconTextFit != IconTextFitType::None; // Adjust shaped icon size when icon-text-fit is used. optional verticallyShapedIcon; - if (shapedIcon && iconTextFit != IconTextFitType::None) { + if (shapedIcon && hasIconTextFit) { // Create vertically shaped icon for vertical writing mode if needed. if (allowVerticalPlacement && shapedTextOrientations.vertical) { verticallyShapedIcon = shapedIcon; @@ -602,6 +603,7 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, imageMap, iconRotation, iconType, + hasIconTextFit, allowVerticalPlacement); }; diff --git a/src/mbgl/renderer/image_atlas.hpp b/src/mbgl/renderer/image_atlas.hpp index 8be975e98c..690d6e34b8 100644 --- a/src/mbgl/renderer/image_atlas.hpp +++ b/src/mbgl/renderer/image_atlas.hpp @@ -26,7 +26,7 @@ public: uint32_t version; style::ImageStretches stretchX; style::ImageStretches stretchY; - style::ImageContent content; + optional content; std::array tl() const { return {{static_cast(paddedRect.x + padding), static_cast(paddedRect.y + padding)}}; diff --git a/src/mbgl/style/image.cpp b/src/mbgl/style/image.cpp index 269f5f9d1a..be7e52abfa 100644 --- a/src/mbgl/style/image.cpp +++ b/src/mbgl/style/image.cpp @@ -41,7 +41,7 @@ const ImageStretches& Image::getStretchY() const { return baseImpl->stretchY; } -const ImageContent& Image::getContent() const { +const optional& Image::getContent() const { return baseImpl->content; } diff --git a/src/mbgl/style/image_impl.cpp b/src/mbgl/style/image_impl.cpp index c43bb552ee..939b1c130c 100644 --- a/src/mbgl/style/image_impl.cpp +++ b/src/mbgl/style/image_impl.cpp @@ -45,9 +45,7 @@ Image::Impl::Impl(std::string id_, sdf(sdf_), stretchX(std::move(stretchX_)), stretchY(std::move(stretchY_)), - content(content_ - ? std::move(*content_) - : ImageContent{0, 0, static_cast(image.size.width), static_cast(image.size.height)}) { + content(std::move(content_)) { if (!image.valid()) { throw util::StyleImageException("dimensions may not be zero"); } else if (pixelRatio <= 0) { @@ -56,7 +54,7 @@ Image::Impl::Impl(std::string id_, throw util::StyleImageException("stretchX is out of bounds or overlapping"); } else if (!validateStretch(stretchY, image.size.height)) { throw util::StyleImageException("stretchY is out of bounds or overlapping"); - } else if (!validateContent(content, image.size)) { + } else if (content && !validateContent(*content, image.size)) { throw util::StyleImageException("content area is invalid"); } } diff --git a/src/mbgl/style/image_impl.hpp b/src/mbgl/style/image_impl.hpp index 683cd3347b..76a68017e2 100644 --- a/src/mbgl/style/image_impl.hpp +++ b/src/mbgl/style/image_impl.hpp @@ -34,7 +34,7 @@ public: const ImageStretches stretchY; // The space where text can be fit into this image. - const ImageContent content; + const optional content; }; } // namespace style diff --git a/src/mbgl/text/collision_feature.cpp b/src/mbgl/text/collision_feature.cpp index 744ba916c6..d2fdd2da59 100644 --- a/src/mbgl/text/collision_feature.cpp +++ b/src/mbgl/text/collision_feature.cpp @@ -10,20 +10,27 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line, const float bottom, const float left, const float right, + const optional& collisionPadding, const float boxScale, const float padding, const style::SymbolPlacementType placement, IndexedSubfeature indexedFeature_, const float overscaling, const float rotate) - : indexedFeature(std::move(indexedFeature_)) - , alongLine(placement != style::SymbolPlacementType::Point) { + : indexedFeature(std::move(indexedFeature_)), alongLine(placement != style::SymbolPlacementType::Point) { if (top == 0 && bottom == 0 && left == 0 && right == 0) return; - const float y1 = top * boxScale - padding; - const float y2 = bottom * boxScale + padding; - const float x1 = left * boxScale - padding; - const float x2 = right * boxScale + padding; + float y1 = top * boxScale - padding; + float y2 = bottom * boxScale + padding; + float x1 = left * boxScale - padding; + float x2 = right * boxScale + padding; + + if (collisionPadding) { + x1 -= collisionPadding->left * boxScale; + y1 -= collisionPadding->top * boxScale; + x2 += collisionPadding->right * boxScale; + y2 += collisionPadding->bottom * boxScale; + } if (alongLine) { float height = y2 - y1; diff --git a/src/mbgl/text/collision_feature.hpp b/src/mbgl/text/collision_feature.hpp index ebda4a5673..af9931a031 100644 --- a/src/mbgl/text/collision_feature.hpp +++ b/src/mbgl/text/collision_feature.hpp @@ -79,7 +79,19 @@ public: const IndexedSubfeature& indexedFeature_, const float overscaling, const float rotate) - : CollisionFeature(line, anchor, shapedText.top, shapedText.bottom, shapedText.left, shapedText.right, boxScale, padding, placement, indexedFeature_, overscaling, rotate) {} + : CollisionFeature(line, + anchor, + shapedText.top, + shapedText.bottom, + shapedText.left, + shapedText.right, + nullopt, + boxScale, + padding, + placement, + indexedFeature_, + overscaling, + rotate) {} // for icons // Icons collision features are always SymbolPlacementType::Point, which means the collision feature @@ -94,15 +106,19 @@ public: const float padding, const IndexedSubfeature& indexedFeature_, const float rotate) - : CollisionFeature(line, anchor, + : CollisionFeature(line, + anchor, (shapedIcon ? shapedIcon->top() : 0), (shapedIcon ? shapedIcon->bottom() : 0), (shapedIcon ? shapedIcon->left() : 0), (shapedIcon ? shapedIcon->right() : 0), + (shapedIcon ? shapedIcon->collisionPadding() : optional{nullopt}), boxScale, padding, style::SymbolPlacementType::Point, - indexedFeature_, 1, rotate) {} + indexedFeature_, + 1, + rotate) {} CollisionFeature(const GeometryCoordinates& line, const Anchor&, @@ -110,6 +126,7 @@ public: const float bottom, const float left, const float right, + const optional& collisionPadding, const float boxScale, const float padding, const style::SymbolPlacementType, diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp index b6427d0d04..26b26c5566 100644 --- a/src/mbgl/text/quads.cpp +++ b/src/mbgl/text/quads.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -14,63 +15,184 @@ namespace mbgl { using namespace style; -SymbolQuads getIconQuads(const PositionedIcon& shapedIcon, const float iconRotate, const SymbolContent iconType) { - const ImagePosition& image = shapedIcon.image(); +constexpr const auto border = ImagePosition::padding; - // 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 auto border = ImagePosition::padding; +float computeStretchSum(const ImageStretches& stretches) { + float sum = 0; + for (auto& stretch : stretches) { + sum += stretch.second - stretch.first; + } + return sum; +} - // 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 - // displaySize uses the logical dimensions, not the physical pixel dimensions. - // Unlike the JavaScript version, we're _not_ including the padding in the texture rect, so the - // logic "dimension * padded / non-padded - dimension" is swapped. - const float iconWidth = shapedIcon.right() - shapedIcon.left(); - const float expandX = (iconWidth * image.paddedRect.w / (image.paddedRect.w - 2 * border) - iconWidth) / 2.0f; - const float left = shapedIcon.left() - expandX; - const float right = shapedIcon.right() + expandX; +float sumWithinRange(const ImageStretches& stretches, const float min, const float max) { + float sum = 0; + for (auto& stretch : stretches) { + sum += util::max(min, util::min(max, stretch.second)) - util::max(min, util::min(max, stretch.first)); + } + return sum; +} + +inline float getEmOffset(float stretchOffset, float stretchSize, float iconSize, float iconOffset) { + return iconOffset + iconSize * stretchOffset / stretchSize; +} + +inline float getPxOffset(float fixedOffset, float fixedSize, float stretchOffset, float stretchSize) { + return fixedOffset - fixedSize * stretchOffset / stretchSize; +} + +struct Cut { + float fixed; + float stretch; +}; + +using Cuts = std::vector; + +Cuts stretchZonesToCuts(const ImageStretches& stretchZones, const float fixedSize, const float stretchSize) { + Cuts cuts{{-border, 0}}; + + for (auto& zone : stretchZones) { + const auto c1 = zone.first; + const auto c2 = zone.second; + const auto lastStretch = cuts.back().stretch; + cuts.emplace_back(Cut{c1 - lastStretch, lastStretch}); + cuts.emplace_back(Cut{c1 - lastStretch, lastStretch + (c2 - c1)}); + } + cuts.emplace_back(Cut{fixedSize + border, stretchSize}); + return cuts; +} +SymbolQuads getIconQuads(const PositionedIcon& shapedIcon, + const float iconRotate, + const SymbolContent iconType, + const bool hasIconTextFit) { + SymbolQuads quads; + + const ImagePosition& image = shapedIcon.image(); + const float pixelRatio = image.pixelRatio; + const uint16_t imageWidth = image.paddedRect.w - 2 * border; + const uint16_t imageHeight = image.paddedRect.h - 2 * border; + + const float iconWidth = shapedIcon.right() - shapedIcon.left(); const float iconHeight = shapedIcon.bottom() - shapedIcon.top(); - const float expandY = (iconHeight * image.paddedRect.h / (image.paddedRect.h - 2 * border) - iconHeight) / 2.0f; - const float top = shapedIcon.top() - expandY; - const float bottom = shapedIcon.bottom() + expandY; - - Point tl{left, top}; - Point tr{right, top}; - Point br{right, bottom}; - Point bl{left, bottom}; - - const float angle = iconRotate * util::DEG2RAD; - - if (angle) { - // Compute the transformation matrix. - float angle_sin = std::sin(angle); - float angle_cos = std::cos(angle); - 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); + + const ImageStretches stretchXFull{{0, imageWidth}}; + const ImageStretches stretchYFull{{0, imageHeight}}; + const ImageStretches& stretchX = !image.stretchX.empty() ? image.stretchX : stretchXFull; + const ImageStretches& stretchY = !image.stretchY.empty() ? image.stretchY : stretchYFull; + + const float stretchWidth = computeStretchSum(stretchX); + const float stretchHeight = computeStretchSum(stretchY); + const float fixedWidth = imageWidth - stretchWidth; + const float fixedHeight = imageHeight - stretchHeight; + + float stretchOffsetX = 0; + float stretchContentWidth = stretchWidth; + float stretchOffsetY = 0; + float stretchContentHeight = stretchHeight; + float fixedOffsetX = 0; + float fixedContentWidth = fixedWidth; + float fixedOffsetY = 0; + float fixedContentHeight = fixedHeight; + + if (hasIconTextFit && image.content) { + auto& content = *image.content; + stretchOffsetX = sumWithinRange(stretchX, 0, content.left); + stretchOffsetY = sumWithinRange(stretchY, 0, content.top); + stretchContentWidth = sumWithinRange(stretchX, content.left, content.right); + stretchContentHeight = sumWithinRange(stretchY, content.top, content.bottom); + fixedOffsetX = content.left - stretchOffsetX; + fixedOffsetY = content.top - stretchOffsetY; + fixedContentWidth = content.right - content.left - stretchContentWidth; + fixedContentHeight = content.bottom - content.top - stretchContentHeight; + } + + optional> matrix{nullopt}; + if (iconRotate) { + const float angle = iconRotate * util::DEG2RAD; + const float angle_sin = std::sin(angle); + const float angle_cos = std::cos(angle); + matrix = std::array{{angle_cos, -angle_sin, angle_sin, angle_cos}}; } - Point pixelOffsetTL; - Point pixelOffsetBR; - Point minFontScale; - - return {SymbolQuad{tl, - tr, - bl, - br, - image.paddedRect, - WritingModeType::None, - {0.0f, 0.0f}, - iconType == SymbolContent::IconSDF, - pixelOffsetTL, - pixelOffsetBR, - minFontScale}}; + auto makeBox = [&](Cut left, Cut top, Cut right, Cut bottom) { + const float leftEm = + getEmOffset(left.stretch - stretchOffsetX, stretchContentWidth, iconWidth, shapedIcon.left()); + const float leftPx = getPxOffset(left.fixed - fixedOffsetX, fixedContentWidth, left.stretch, stretchWidth); + + const float topEm = + getEmOffset(top.stretch - stretchOffsetY, stretchContentHeight, iconHeight, shapedIcon.top()); + const float topPx = getPxOffset(top.fixed - fixedOffsetY, fixedContentHeight, top.stretch, stretchHeight); + + const float rightEm = + getEmOffset(right.stretch - stretchOffsetX, stretchContentWidth, iconWidth, shapedIcon.left()); + const float rightPx = getPxOffset(right.fixed - fixedOffsetX, fixedContentWidth, right.stretch, stretchWidth); + + const float bottomEm = + getEmOffset(bottom.stretch - stretchOffsetY, stretchContentHeight, iconHeight, shapedIcon.top()); + const float bottomPx = + getPxOffset(bottom.fixed - fixedOffsetY, fixedContentHeight, bottom.stretch, stretchHeight); + + Point tl(leftEm, topEm); + Point tr(rightEm, topEm); + Point br(rightEm, bottomEm); + Point bl(leftEm, bottomEm); + const Point pixelOffsetTL(leftPx / pixelRatio, topPx / pixelRatio); + const Point pixelOffsetBR(rightPx / pixelRatio, bottomPx / pixelRatio); + + if (matrix) { + tl = util::matrixMultiply(*matrix, tl); + tr = util::matrixMultiply(*matrix, tr); + bl = util::matrixMultiply(*matrix, bl); + br = util::matrixMultiply(*matrix, br); + } + + const float x1 = left.stretch + left.fixed; + const float x2 = right.stretch + right.fixed; + const float y1 = top.stretch + top.fixed; + const float y2 = bottom.stretch + bottom.fixed; + + // TODO: consider making texture coordinates float instead of uint16_t + const Rect subRect{static_cast(image.paddedRect.x + border + x1), + static_cast(image.paddedRect.y + border + y1), + static_cast(x2 - x1), + static_cast(y2 - y1)}; + + const float minFontScaleX = fixedContentWidth / pixelRatio / iconWidth; + const float minFontScaleY = fixedContentHeight / pixelRatio / iconHeight; + + // Icon quad is padded, so texture coordinates also need to be padded. + quads.emplace_back(tl, + tr, + bl, + br, + subRect, + WritingModeType::None, + Point{0.0f, 0.0f}, + iconType == SymbolContent::IconSDF, + pixelOffsetTL, + pixelOffsetBR, + Point{minFontScaleX, minFontScaleY}); + }; + + if (!hasIconTextFit || (image.stretchX.empty() && image.stretchY.empty())) { + makeBox({0, -1}, {0, -1}, {0, static_cast(imageWidth + 1)}, {0, static_cast(imageHeight + 1)}); + } else { + const auto xCuts = stretchZonesToCuts(stretchX, fixedWidth, stretchWidth); + const auto yCuts = stretchZonesToCuts(stretchY, fixedHeight, stretchHeight); + + for (size_t xi = 0; xi < xCuts.size() - 1; xi++) { + const auto& x1 = xCuts[xi]; + const auto& x2 = xCuts[xi + 1]; + for (size_t yi = 0; yi < yCuts.size() - 1; yi++) { + const auto& y1 = yCuts[yi]; + const auto& y2 = yCuts[yi + 1]; + makeBox(x1, y1, x2, y2); + } + } + } + + return quads; } SymbolQuads getGlyphQuads(const Shaping& shapedText, diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp index ef058989a5..18e6ce53c5 100644 --- a/src/mbgl/text/quads.hpp +++ b/src/mbgl/text/quads.hpp @@ -57,7 +57,10 @@ public: using SymbolQuads = std::vector; -SymbolQuads getIconQuads(const PositionedIcon& shapedIcon, float iconRotate, SymbolContent iconType); +SymbolQuads getIconQuads(const PositionedIcon& shapedIcon, + float iconRotate, + SymbolContent iconType, + bool hasIconTextFit); SymbolQuads getGlyphQuads(const Shaping& shapedText, const std::array textOffset, diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index 033cbdf4e5..725a93555b 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -79,7 +79,17 @@ PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, float top = dy - image.displaySize()[1] * anchorAlign.verticalAlign; float bottom = top + image.displaySize()[1]; - return PositionedIcon{image, top, bottom, left, right}; + Padding collisionPadding; + if (image.content) { + auto& content = *image.content; + const auto pixelRatio = image.pixelRatio; + collisionPadding.left = content.left / pixelRatio; + collisionPadding.top = content.top / pixelRatio; + collisionPadding.right = image.displaySize()[0] - content.right / pixelRatio; + collisionPadding.bottom = image.displaySize()[1] - content.bottom / pixelRatio; + } + + return PositionedIcon{image, top, bottom, left, right, collisionPadding}; } void PositionedIcon::fitIconToText(const Shaping& shapedText, diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp index 0c238d36b0..5343d4c3b2 100644 --- a/src/mbgl/text/shaping.hpp +++ b/src/mbgl/text/shaping.hpp @@ -26,16 +26,37 @@ style::TextJustifyType getAnchorJustification(style::SymbolAnchorType anchor); class SymbolFeature; class BiDi; +class Padding { +public: + float left = 0; + float top = 0; + float right = 0; + float bottom = 0; + + explicit operator bool() const { return left != 0 || top != 0 || right != 0 || bottom != 0; } + + bool operator==(const Padding& rhs) const { + return left == rhs.left && top == rhs.top && right == rhs.right && bottom == rhs.bottom; + } +}; + class PositionedIcon { private: - PositionedIcon(ImagePosition image_, float top_, float bottom_, float left_, float right_) - : _image(image_), _top(top_), _bottom(bottom_), _left(left_), _right(right_) {} + PositionedIcon( + ImagePosition image_, float top_, float bottom_, float left_, float right_, const Padding& collisionPadding_) + : _image(image_), + _top(top_), + _bottom(bottom_), + _left(left_), + _right(right_), + _collisionPadding(collisionPadding_) {} ImagePosition _image; float _top; float _bottom; float _left; float _right; + Padding _collisionPadding; public: static PositionedIcon shapeIcon(const ImagePosition&, @@ -55,6 +76,7 @@ public: float bottom() const { return _bottom; } float left() const { return _left; } float right() const { return _right; } + const Padding& collisionPadding() const { return _collisionPadding; } }; const Shaping getShaping(const TaggedString& string, diff --git a/test/text/cross_tile_symbol_index.test.cpp b/test/text/cross_tile_symbol_index.test.cpp index 3b02a90422..fe1375c66d 100644 --- a/test/text/cross_tile_symbol_index.test.cpp +++ b/test/text/cross_tile_symbol_index.test.cpp @@ -26,6 +26,7 @@ SymbolInstance makeSymbolInstance(float x, float y, std::u16string key) { imageMap, 0, SymbolContent::IconSDF, + false, 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 4068fe643a..a95379a5d7 100644 --- a/test/text/quads.test.cpp +++ b/test/text/quads.test.cpp @@ -22,18 +22,18 @@ TEST(getIconQuads, normal) { GeometryCoordinates line; - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; - EXPECT_EQ(quad.tl.x, -14); - EXPECT_EQ(quad.tl.y, -10); - EXPECT_EQ(quad.tr.x, 1); - EXPECT_EQ(quad.tr.y, -10); - EXPECT_EQ(quad.bl.x, -14); - EXPECT_EQ(quad.bl.y, 1); - EXPECT_EQ(quad.br.x, 1); - EXPECT_EQ(quad.br.y, 1); + EXPECT_FLOAT_EQ(quad.tl.x, -14); + EXPECT_FLOAT_EQ(quad.tl.y, -10); + EXPECT_FLOAT_EQ(quad.tr.x, 1); + EXPECT_FLOAT_EQ(quad.tr.y, -10); + EXPECT_FLOAT_EQ(quad.bl.x, -14); + EXPECT_FLOAT_EQ(quad.bl.y, 1); + EXPECT_FLOAT_EQ(quad.br.x, 1); + EXPECT_FLOAT_EQ(quad.br.y, 1); } TEST(getIconQuads, style) { @@ -62,7 +62,7 @@ TEST(getIconQuads, style) { EXPECT_FLOAT_EQ(-18.5f, shapedIcon.left()); SymbolLayoutProperties::Evaluated layout; - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; @@ -81,7 +81,7 @@ TEST(getIconQuads, style) { { auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center); shapedIcon.fitIconToText(shapedText, IconTextFitType::Width, {{0, 0, 0, 0}}, {{0, 0}}, 24.0f / 24.0f); - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; @@ -100,7 +100,7 @@ TEST(getIconQuads, style) { { auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center); shapedIcon.fitIconToText(shapedText, IconTextFitType::Width, {{0, 0, 0, 0}}, {{0, 0}}, 12.0f / 24.0f); - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; @@ -119,7 +119,7 @@ TEST(getIconQuads, style) { { auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center); shapedIcon.fitIconToText(shapedText, IconTextFitType::Width, {{5, 10, 5, 10}}, {{0, 0}}, 12.0f / 24.0f); - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; @@ -138,7 +138,7 @@ TEST(getIconQuads, style) { { auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center); shapedIcon.fitIconToText(shapedText, IconTextFitType::Height, {{0, 0, 0, 0}}, {{0, 0}}, 24.0f / 24.0f); - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; @@ -158,7 +158,7 @@ TEST(getIconQuads, style) { SymbolLayoutProperties::Evaluated layout; auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center); shapedIcon.fitIconToText(shapedText, IconTextFitType::Height, {{0, 0, 0, 0}}, {{0, 0}}, 12.0f / 24.0f); - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; @@ -177,7 +177,7 @@ TEST(getIconQuads, style) { { auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center); shapedIcon.fitIconToText(shapedText, IconTextFitType::Height, {{5, 10, 5, 20}}, {{0, 0}}, 12.0f / 24.0f); - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; @@ -196,7 +196,7 @@ TEST(getIconQuads, style) { { auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center); shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{0, 0, 0, 0}}, {{0, 0}}, 24.0f / 24.0f); - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; @@ -215,7 +215,7 @@ TEST(getIconQuads, style) { { auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center); shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{0, 0, 0, 0}}, {{0, 0}}, 12.0f / 24.0f); - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; @@ -234,7 +234,7 @@ TEST(getIconQuads, style) { { auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center); shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{5, 10, 5, 10}}, {{0, 0}}, 12.0f / 24.0f); - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; @@ -255,7 +255,7 @@ TEST(getIconQuads, style) { layout.get() = 12.0f; auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center); shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{0, 5, 10, 15}}, {{0, 0}}, 12.0f / 24.0f); - SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA); + SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false); ASSERT_EQ(quads.size(), 1); const auto& quad = quads[0]; -- cgit v1.2.1