#include #include #include #include #include #include #include #include #include #include #include #include using namespace std::numbers; namespace mbgl { using namespace style; constexpr const auto border = ImagePosition::padding; float computeStretchSum(const ImageStretches& stretches) { float sum = 0; for (auto& stretch : stretches) { sum += stretch.second - stretch.first; } return sum; } 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 ImageStretches stretchXFull{{0.0f, imageWidth}}; const ImageStretches stretchYFull{{0.0f, 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; auto icon = shapedIcon; if (hasIconTextFit && image.content) { const auto& content = *image.content; const auto contentWidth = content.right - content.left; const auto contentHeight = content.bottom - content.top; if (image.textFitWidth || image.textFitHeight) { icon = icon.applyTextFit(); } 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 = contentWidth - stretchContentWidth; fixedContentHeight = contentHeight - stretchContentHeight; } std::optional> matrix{std::nullopt}; if (iconRotate) { const float angle = util::deg2radf(iconRotate); 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}}; } const float iconLeft = icon.left(); const float iconTop = icon.top(); const float iconWidth = icon.right() - iconLeft; const float iconHeight = icon.bottom() - iconTop; auto makeBox = [&](Cut left, Cut top, Cut right, Cut bottom) { const float leftEm = getEmOffset(left.stretch - stretchOffsetX, stretchContentWidth, iconWidth, iconLeft); const float leftPx = getPxOffset(left.fixed - fixedOffsetX, fixedContentWidth, left.stretch, stretchWidth); const float topEm = getEmOffset(top.stretch - stretchOffsetY, stretchContentHeight, iconHeight, iconTop); const float topPx = getPxOffset(top.fixed - fixedOffsetY, fixedContentHeight, top.stretch, stretchHeight); const float rightEm = getEmOffset(right.stretch - stretchOffsetX, stretchContentWidth, iconWidth, iconLeft); const float rightPx = getPxOffset(right.fixed - fixedOffsetX, fixedContentWidth, right.stretch, stretchWidth); const float bottomEm = getEmOffset(bottom.stretch - stretchOffsetY, stretchContentHeight, iconHeight, iconTop); 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, const std::array textOffset, const SymbolLayoutProperties::Evaluated& layout, const style::SymbolPlacementType placement, const ImageMap& imageMap, bool allowVerticalPlacement) { const float textRotate = util::deg2radf(layout.get()); const bool alongLine = layout.get() == AlignmentType::Map && placement != SymbolPlacementType::Point; SymbolQuads quads; 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.0f; const Rect& rect = positionedGlyph.rect; bool isSDF = true; // 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; isSDF = image->second->sdf; } 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 = -pi_v / 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.f - 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); } Point pixelOffsetTL; Point pixelOffsetBR; Point minFontScale; quads.emplace_back(tl, tr, bl, br, rect, shapedText.writingMode, glyphOffset, isSDF, pixelOffsetTL, pixelOffsetBR, minFontScale, positionedGlyph.sectionIndex); } } return quads; } } // namespace mbgl