From e6c15d0f6f285bc604c0d93381b9b1d3957cb5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Fri, 10 Feb 2017 16:57:48 -0800 Subject: [PATCH] Upright CJK characters in vertically-oriented labels (#7114) CJK characters and adjacent punctuation now remain upright in vertically oriented labels that have line placement. Fixes #1682. --- mapbox-gl-js | 2 +- package.json | 2 +- platform/ios/CHANGELOG.md | 1 + platform/macos/CHANGELOG.md | 1 + src/mbgl/layout/symbol_instance.cpp | 39 +++-- src/mbgl/layout/symbol_instance.hpp | 4 +- src/mbgl/layout/symbol_layout.cpp | 82 ++++++--- src/mbgl/layout/symbol_layout.hpp | 5 +- src/mbgl/text/glyph.hpp | 39 ++++- src/mbgl/text/glyph_set.cpp | 28 ++- src/mbgl/text/glyph_set.hpp | 9 +- src/mbgl/text/quads.cpp | 21 ++- src/mbgl/text/quads.hpp | 6 +- src/mbgl/util/i18n.cpp | 254 +++++++++++++++++++++++++--- src/mbgl/util/i18n.hpp | 50 +++++- test/text/quads.test.cpp | 2 +- 16 files changed, 450 insertions(+), 95 deletions(-) diff --git a/mapbox-gl-js b/mapbox-gl-js index 3c52ddba231..69de96f53ee 160000 --- a/mapbox-gl-js +++ b/mapbox-gl-js @@ -1 +1 @@ -Subproject commit 3c52ddba23124a1844162a7fc2417ac8671fa911 +Subproject commit 69de96f53ee41f0e2b8804491d236f0b1fe1a3cd diff --git a/package.json b/package.json index 71450c68b78..19acd188c12 100644 --- a/package.json +++ b/package.json @@ -46,4 +46,4 @@ "remote_path": "./{name}/v{version}", "package_name": "{node_abi}-{platform}-{arch}.tar.gz" } -} \ No newline at end of file +} diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 3db7548fe5b..87b2b8c05f4 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -8,6 +8,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Added support for right-to-left text and Arabic ligatures in labels. ([#6984](https://github.com/mapbox/mapbox-gl-native/pull/6984), [#7123](https://github.com/mapbox/mapbox-gl-native/pull/7123)) * Improved the line wrapping behavior of point-placed labels, especially labels written in Chinese and Japanese. ([#6828](https://github.com/mapbox/mapbox-gl-native/pull/6828), [#7446](https://github.com/mapbox/mapbox-gl-native/pull/7446)) +* CJK characters now remain upright in vertically oriented labels that have line placement, such as road labels. ([#7114](https://github.com/mapbox/mapbox-gl-native/issues/7114)) * Added Chinese (Simplified and Traditional), French, German, Japanese, Portuguese (Brazilian), Swedish, and Vietnamese localizations. ([#7316](https://github.com/mapbox/mapbox-gl-native/pull/7316), [#7899](https://github.com/mapbox/mapbox-gl-native/pull/7899), [#7999](https://github.com/mapbox/mapbox-gl-native/pull/7999)) ### Styles diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index efca47f435d..b52d1d70553 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -6,6 +6,7 @@ * Added support for right-to-left text and Arabic ligatures in labels. ([#6984](https://github.com/mapbox/mapbox-gl-native/pull/6984), [#7123](https://github.com/mapbox/mapbox-gl-native/pull/7123)) * Improved the line wrapping behavior of point-placed labels, especially labels written in Chinese and Japanese. ([#6828](https://github.com/mapbox/mapbox-gl-native/pull/6828), [#7446](https://github.com/mapbox/mapbox-gl-native/pull/7446)) +* CJK characters now remain upright in vertically oriented labels that have line placement, such as road labels. ([#7114](https://github.com/mapbox/mapbox-gl-native/issues/7114)) * Added Chinese (Simplified and Traditional), French, German, Japanese, Portuguese (Brazilian), Swedish, and Vietnamese localizations. ([#7316](https://github.com/mapbox/mapbox-gl-native/pull/7316), [#7503](https://github.com/mapbox/mapbox-gl-native/pull/7503), [#7899](https://github.com/mapbox/mapbox-gl-native/pull/7899), [#7999](https://github.com/mapbox/mapbox-gl-native/pull/7999)) ### Styles diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index fafcc7c15d1..2935fddd910 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -6,29 +6,46 @@ namespace mbgl { using namespace style; SymbolInstance::SymbolInstance(Anchor& anchor, const GeometryCoordinates& line, - const Shaping& shapedText, const PositionedIcon& shapedIcon, + const std::pair& shapedTextOrientations, const PositionedIcon& shapedIcon, const SymbolLayoutProperties::Evaluated& layout, const bool addToBuffers, const uint32_t index_, const float textBoxScale, const float textPadding, const SymbolPlacementType textPlacement, const float iconBoxScale, const float iconPadding, const SymbolPlacementType iconPlacement, const GlyphPositions& face, const IndexedSubfeature& indexedFeature) : point(anchor.point), index(index_), - hasText(shapedText), + hasText(shapedTextOrientations.first || shapedTextOrientations.second), hasIcon(shapedIcon), - // Create the quads used for rendering the glyphs. - glyphQuads(addToBuffers && shapedText ? - getGlyphQuads(anchor, shapedText, textBoxScale, line, layout, textPlacement, face) : - SymbolQuads()), - // Create the quad used for rendering the icon. iconQuads(addToBuffers && shapedIcon ? - getIconQuads(anchor, shapedIcon, line, layout, iconPlacement, shapedText) : + getIconQuads(anchor, shapedIcon, line, layout, iconPlacement, shapedTextOrientations.first) : SymbolQuads()), // Create the collision features that will be used to check whether this symbol instance can be placed - textCollisionFeature(line, anchor, shapedText, textBoxScale, textPadding, textPlacement, indexedFeature), - iconCollisionFeature(line, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature) - {} + textCollisionFeature(line, anchor, shapedTextOrientations.second ?: shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature), + iconCollisionFeature(line, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature) { + + // Create the quads used for rendering the glyphs. + if (addToBuffers) { + if (shapedTextOrientations.first) { + auto quads = getGlyphQuads(anchor, shapedTextOrientations.first, textBoxScale, line, layout, textPlacement, face); + glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); + } + if (shapedTextOrientations.second) { + auto quads = getGlyphQuads(anchor, shapedTextOrientations.second, textBoxScale, line, layout, textPlacement, face); + glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); + } + } + + if (shapedTextOrientations.first && shapedTextOrientations.second) { + writingModes = WritingModeType::Horizontal | WritingModeType::Vertical; + } else if (shapedTextOrientations.first) { + writingModes = WritingModeType::Horizontal; + } else if (shapedTextOrientations.second) { + writingModes = WritingModeType::Vertical; + } else { + writingModes = WritingModeType::None; + } +} } // namespace mbgl diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index 508c11a3940..014e34cb785 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -12,7 +13,7 @@ class IndexedSubfeature; class SymbolInstance { public: explicit SymbolInstance(Anchor& anchor, const GeometryCoordinates& line, - const Shaping& shapedText, const PositionedIcon& shapedIcon, + const std::pair& shapedTextOrientations, const PositionedIcon& shapedIcon, const style::SymbolLayoutProperties::Evaluated&, const bool inside, const uint32_t index, const float textBoxScale, const float textPadding, style::SymbolPlacementType textPlacement, const float iconBoxScale, const float iconPadding, style::SymbolPlacementType iconPlacement, @@ -26,6 +27,7 @@ class SymbolInstance { SymbolQuads iconQuads; CollisionFeature textCollisionFeature; CollisionFeature iconCollisionFeature; + WritingModeType writingModes; }; } // namespace mbgl diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 665806cb0ee..11c26fa661a 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -124,6 +125,9 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, // Loop through all characters of this text and collect unique codepoints. for (char16_t chr : *ft.text) { ranges.insert(getGlyphRange(chr)); + if (char16_t verticalChr = util::i18n::verticalizePunctuation(chr)) { + ranges.insert(getGlyphRange(verticalChr)); + } } } @@ -206,30 +210,46 @@ void SymbolLayout::prepare(uintptr_t tileUID, auto glyphSet = glyphAtlas.getGlyphSet(layout.get()); + const bool textAlongLine = layout.get() == AlignmentType::Map && + layout.get() == SymbolPlacementType::Line; + for (const auto& feature : features) { if (feature.geometry.empty()) continue; - Shaping shapedText; + std::pair shapedTextOrientations; PositionedIcon shapedIcon; GlyphPositions face; // if feature has text, shape the text if (feature.text) { - shapedText = glyphSet->getShaping( - /* string */ *feature.text, - /* maxWidth: ems */ layout.get() != SymbolPlacementType::Line ? - layout.get() * 24 : 0, - /* lineHeight: ems */ layout.get() * 24, - /* horizontalAlign */ horizontalAlign, - /* verticalAlign */ verticalAlign, - /* justify */ justify, - /* spacing: ems */ layout.get() * 24, - /* translate */ Point(layout.get()[0], layout.get()[1]), - /* bidirectional algorithm object */ bidi); - - // Add the glyphs we need for this label to the glyph atlas. - if (shapedText) { - glyphAtlas.addGlyphs(tileUID, *feature.text, layout.get(), **glyphSet, face); + auto getShaping = [&] (const std::u16string& text, WritingModeType writingMode) { + const float oneEm = 24.0f; + const Shaping result = glyphSet->getShaping( + /* string */ text, + /* maxWidth: ems */ layout.get() != SymbolPlacementType::Line ? + layout.get() * oneEm : 0, + /* lineHeight: ems */ layout.get() * oneEm, + /* horizontalAlign */ horizontalAlign, + /* verticalAlign */ verticalAlign, + /* justify */ justify, + /* spacing: ems */ layout.get() * oneEm, + /* translate */ Point(layout.get()[0], layout.get()[1]), + /* verticalHeight */ oneEm, + /* writingMode */ writingMode, + /* bidirectional algorithm object */ bidi); + + // Add the glyphs we need for this label to the glyph atlas. + if (result) { + glyphAtlas.addGlyphs(tileUID, text, layout.get(), **glyphSet, face); + } + + return result; + }; + + shapedTextOrientations.first = getShaping(*feature.text, WritingModeType::Horizontal); + + if (util::i18n::allowsVerticalWritingMode(*feature.text) && textAlongLine) { + shapedTextOrientations.second = getShaping(util::i18n::verticalizePunctuation(*feature.text), WritingModeType::Vertical); } } @@ -251,8 +271,8 @@ void SymbolLayout::prepare(uintptr_t tileUID, } // if either shapedText or icon position is present, add the feature - if (shapedText || shapedIcon) { - addFeature(feature, shapedText, shapedIcon, face); + if (shapedTextOrientations.first || shapedIcon) { + addFeature(feature, shapedTextOrientations, shapedIcon, face); } } @@ -260,7 +280,7 @@ void SymbolLayout::prepare(uintptr_t tileUID, } void SymbolLayout::addFeature(const SymbolFeature& feature, - const Shaping& shapedText, + const std::pair& shapedTextOrientations, const PositionedIcon& shapedIcon, const GlyphPositions& face) { const float minScale = 0.5f; @@ -305,7 +325,7 @@ void SymbolLayout::addFeature(const SymbolFeature& feature, const bool addToBuffers = mode == MapMode::Still || withinPlus0; - symbolInstances.emplace_back(anchor, line, shapedText, shapedIcon, layout, addToBuffers, symbolInstances.size(), + symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, layout, addToBuffers, symbolInstances.size(), textBoxScale, textPadding, textPlacement, iconBoxScale, iconPadding, iconPlacement, face, indexedFeature); @@ -317,8 +337,8 @@ void SymbolLayout::addFeature(const SymbolFeature& feature, Anchors anchors = getAnchors(line, symbolSpacing, textMaxAngle, - shapedText.left, - shapedText.right, + (shapedTextOrientations.second ?: shapedTextOrientations.first).left, + (shapedTextOrientations.second ?: shapedTextOrientations.first).right, shapedIcon.left, shapedIcon.right, glyphSize, @@ -326,7 +346,7 @@ void SymbolLayout::addFeature(const SymbolFeature& feature, overscaling); for (auto& anchor : anchors) { - if (!shapedText || !anchorIsTooClose(shapedText.text, textRepeatDistance, anchor)) { + if (!shapedTextOrientations.first || !anchorIsTooClose(shapedTextOrientations.first.text, textRepeatDistance, anchor)) { addSymbolInstance(line, anchor); } } @@ -448,7 +468,7 @@ std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) if (glyphScale < collisionTile.maxScale) { addSymbols( bucket->text, symbolInstance.glyphQuads, glyphScale, - layout.get(), textPlacement, collisionTile.config.angle); + layout.get(), textPlacement, collisionTile.config.angle, symbolInstance.writingModes); } } @@ -457,7 +477,7 @@ std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) if (iconScale < collisionTile.maxScale) { addSymbols( bucket->icon, symbolInstance.iconQuads, iconScale, - layout.get(), iconPlacement, collisionTile.config.angle); + layout.get(), iconPlacement, collisionTile.config.angle, symbolInstance.writingModes); } } } @@ -470,7 +490,7 @@ std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) } template -void SymbolLayout::addSymbols(Buffer &buffer, const SymbolQuads &symbols, float scale, const bool keepUpright, const style::SymbolPlacementType placement, const float placementAngle) { +void SymbolLayout::addSymbols(Buffer &buffer, const SymbolQuads &symbols, float scale, const bool keepUpright, const style::SymbolPlacementType placement, const float placementAngle, WritingModeType writingModes) { constexpr const uint16_t vertexLength = 4; const float placementZoom = util::max(util::log2(scale) + zoom, 0.0f); @@ -485,9 +505,15 @@ void SymbolLayout::addSymbols(Buffer &buffer, const SymbolQuads &symbols, float float maxZoom = util::min(zoom + util::log2(symbol.maxScale), util::MAX_ZOOM_F); const auto &anchorPoint = symbol.anchorPoint; - // drop upside down versions of glyphs + // drop incorrectly oriented glyphs const float a = std::fmod(symbol.anchorAngle + placementAngle + M_PI, M_PI * 2); - if (keepUpright && placement == style::SymbolPlacementType::Line && + if (writingModes & WritingModeType::Vertical) { + if (placement == style::SymbolPlacementType::Line && symbol.writingMode == WritingModeType::Vertical) { + if (keepUpright && placement == style::SymbolPlacementType::Line && (a <= (M_PI * 5 / 4) || a > (M_PI * 7 / 4))) + continue; + } else if (keepUpright && placement == style::SymbolPlacementType::Line && (a <= (M_PI * 3 / 4) || a > (M_PI * 5 / 4))) + continue; + } else if (keepUpright && placement == style::SymbolPlacementType::Line && (a <= M_PI / 2 || a > M_PI * 3 / 2)) { continue; } diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index cb31bb3b292..0784c496144 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -55,7 +55,7 @@ class SymbolLayout { private: void addFeature(const SymbolFeature&, - const Shaping& shapedText, + const std::pair& shapedTextOrientations, const PositionedIcon& shapedIcon, const GlyphPositions& face); @@ -67,7 +67,8 @@ class SymbolLayout { // Adds placed items to the buffer. template void addSymbols(Buffer&, const SymbolQuads&, float scale, - const bool keepUpright, const style::SymbolPlacementType, const float placementAngle); + const bool keepUpright, const style::SymbolPlacementType, const float placementAngle, + WritingModeType writingModes); const std::string sourceLayerName; const std::string bucketName; diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp index 2bf1448492a..c89d045dfca 100644 --- a/src/mbgl/text/glyph.hpp +++ b/src/mbgl/text/glyph.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -52,25 +53,29 @@ typedef std::map GlyphPositions; class PositionedGlyph { public: - explicit PositionedGlyph(uint32_t glyph_, float x_, float y_) - : glyph(glyph_), x(x_), y(y_) {} + explicit PositionedGlyph(uint32_t glyph_, float x_, float y_, float angle_) + : glyph(glyph_), x(x_), y(y_), angle(angle_) {} uint32_t glyph = 0; float x = 0; float y = 0; + float angle = 0; }; +enum class WritingModeType : uint8_t; + class Shaping { public: explicit Shaping() : top(0), bottom(0), left(0), right(0) {} - explicit Shaping(float x, float y, std::u16string text_) - : text(std::move(text_)), top(y), bottom(y), left(x), right(x) {} + explicit Shaping(float x, float y, std::u16string text_, WritingModeType writingMode_) + : text(std::move(text_)), top(y), bottom(y), left(x), right(x), writingMode(writingMode_) {} std::vector positionedGlyphs; std::u16string text; int32_t top; int32_t bottom; int32_t left; int32_t right; + WritingModeType writingMode; explicit operator bool() const { return !positionedGlyphs.empty(); } }; @@ -90,4 +95,30 @@ class SDFGlyph { GlyphMetrics metrics; }; +enum class WritingModeType : uint8_t { + None = 0, + Horizontal = 1 << 0, + Vertical = 1 << 1, +}; + +constexpr WritingModeType operator|(WritingModeType a, WritingModeType b) { + return WritingModeType(mbgl::underlying_type(a) | mbgl::underlying_type(b)); +} + +constexpr WritingModeType& operator|=(WritingModeType& a, WritingModeType b) { + return (a = a | b); +} + +constexpr bool operator&(WritingModeType lhs, WritingModeType rhs) { + return mbgl::underlying_type(lhs) & mbgl::underlying_type(rhs); +} + +constexpr WritingModeType& operator&=(WritingModeType& lhs, WritingModeType rhs) { + return (lhs = WritingModeType(mbgl::underlying_type(lhs) & mbgl::underlying_type(rhs))); +} + +constexpr WritingModeType operator~(WritingModeType value) { + return WritingModeType(~mbgl::underlying_type(value)); +} + } // end namespace mbgl diff --git a/src/mbgl/text/glyph_set.cpp b/src/mbgl/text/glyph_set.cpp index b4b31954869..61d5cac9262 100644 --- a/src/mbgl/text/glyph_set.cpp +++ b/src/mbgl/text/glyph_set.cpp @@ -42,18 +42,19 @@ const Shaping GlyphSet::getShaping(const std::u16string& logicalInput, const float justify, const float spacing, const Point& translate, + const float verticalHeight, + const WritingModeType writingMode, BiDi& bidi) const { - // The string stored in shaping.text is used for finding duplicates, but may end up quite // different from the glyphs that get shown - Shaping shaping(translate.x * 24, translate.y * 24, logicalInput); + Shaping shaping(translate.x * 24, translate.y * 24, logicalInput, writingMode); std::vector reorderedLines = bidi.processText(logicalInput, - determineLineBreaks(logicalInput, spacing, maxWidth)); + determineLineBreaks(logicalInput, spacing, maxWidth, writingMode)); shapeLines(shaping, reorderedLines, spacing, lineHeight, horizontalAlign, verticalAlign, - justify, translate); + justify, translate, verticalHeight, writingMode); return shaping; } @@ -198,8 +199,9 @@ std::set leastBadBreaks(const PotentialBreak& lastLineBreak) { // more intuitive, but we can't do that because the visual order may be changed by line breaks! std::set GlyphSet::determineLineBreaks(const std::u16string& logicalInput, const float spacing, - float maxWidth) const { - if (!maxWidth) { + float maxWidth, + const WritingModeType writingMode) const { + if (!maxWidth || writingMode != WritingModeType::Horizontal) { return {}; } @@ -239,7 +241,9 @@ void GlyphSet::shapeLines(Shaping& shaping, const float horizontalAlign, const float verticalAlign, const float justify, - const Point& translate) const { + const Point& translate, + const float verticalHeight, + const WritingModeType writingMode) const { // the y offset *should* be part of the font metadata const int32_t yOffset = -17; @@ -266,8 +270,14 @@ void GlyphSet::shapeLines(Shaping& shaping, } const SDFGlyph& glyph = it->second; - shaping.positionedGlyphs.emplace_back(chr, x, y); - x += glyph.metrics.advance + spacing; + + if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(chr)) { + shaping.positionedGlyphs.emplace_back(chr, x, y, 0); + x += glyph.metrics.advance + spacing; + } else { + shaping.positionedGlyphs.emplace_back(chr, x, 0, -M_PI_2); + x += verticalHeight + spacing; + } } // Only justify if we placed at least one glyph diff --git a/src/mbgl/text/glyph_set.hpp b/src/mbgl/text/glyph_set.hpp index 3037cefca01..0342c82eb5c 100644 --- a/src/mbgl/text/glyph_set.hpp +++ b/src/mbgl/text/glyph_set.hpp @@ -18,6 +18,8 @@ class GlyphSet { float justify, float spacing, const Point& translate, + float verticalHeight, + const WritingModeType, BiDi& bidi) const; private: @@ -26,7 +28,8 @@ class GlyphSet { float maxWidth) const; std::set determineLineBreaks(const std::u16string& logicalInput, const float spacing, - float maxWidth) const; + float maxWidth, + const WritingModeType) const; void shapeLines(Shaping& shaping, const std::vector& lines, @@ -35,7 +38,9 @@ class GlyphSet { float horizontalAlign, float verticalAlign, float justify, - const Point& translate) const; + const Point& translate, + float verticalHeight, + const WritingModeType) const; std::map sdfs; }; diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp index 10c4dfea90a..b4a3ea09a4d 100644 --- a/src/mbgl/text/quads.cpp +++ b/src/mbgl/text/quads.cpp @@ -89,7 +89,7 @@ SymbolQuads getIconQuads(Anchor& anchor, const PositionedIcon& shapedIcon, } SymbolQuads quads; - quads.emplace_back(tl, tr, bl, br, image.pos, 0, 0, anchor.point, globalMinScale, std::numeric_limits::infinity()); + quads.emplace_back(tl, tr, bl, br, image.pos, 0, 0, anchor.point, globalMinScale, std::numeric_limits::infinity(), shapedText.writingMode); return quads; } @@ -207,10 +207,19 @@ SymbolQuads getGlyphQuads(Anchor& anchor, const Shaping& shapedText, const float x2 = x1 + rect.w; const float y2 = y1 + rect.h; - const Point otl{x1, y1}; - const Point otr{x2, y1}; - const Point obl{x1, y2}; - const Point obr{x2, y2}; + const Point center{positionedGlyph.x, static_cast(static_cast(glyph.metrics.advance) / 2.0)}; + + Point otl{x1, y1}; + Point otr{x2, y1}; + Point obl{x1, y2}; + Point obr{x2, y2}; + + if (positionedGlyph.angle != 0) { + otl = util::rotate(otl - center, positionedGlyph.angle) + center; + otr = util::rotate(otr - center, positionedGlyph.angle) + center; + obl = util::rotate(obl - center, positionedGlyph.angle) + center; + obr = util::rotate(obr - center, positionedGlyph.angle) + center; + } for (const GlyphInstance &instance : glyphInstances) { @@ -236,7 +245,7 @@ SymbolQuads getGlyphQuads(Anchor& anchor, const Shaping& shapedText, const float anchorAngle = std::fmod((anchor.angle + instance.offset + 2 * M_PI), (2 * M_PI)); const float glyphAngle = std::fmod((instance.angle + instance.offset + 2 * M_PI), (2 * M_PI)); - quads.emplace_back(tl, tr, bl, br, rect, anchorAngle, glyphAngle, instance.anchorPoint, glyphMinScale, instance.maxScale); + quads.emplace_back(tl, tr, bl, br, rect, anchorAngle, glyphAngle, instance.anchorPoint, glyphMinScale, instance.maxScale, shapedText.writingMode); } diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp index 75fb53aaded..760015340ee 100644 --- a/src/mbgl/text/quads.hpp +++ b/src/mbgl/text/quads.hpp @@ -15,7 +15,7 @@ class PositionedIcon; struct SymbolQuad { explicit SymbolQuad(Point tl_, Point tr_, Point bl_, Point br_, Rect tex_, float anchorAngle_, float glyphAngle_, Point anchorPoint_, - float minScale_, float maxScale_) + float minScale_, float maxScale_, WritingModeType writingMode_) : tl(std::move(tl_)), tr(std::move(tr_)), bl(std::move(bl_)), @@ -25,13 +25,15 @@ struct SymbolQuad { glyphAngle(glyphAngle_), anchorPoint(std::move(anchorPoint_)), minScale(minScale_), - maxScale(maxScale_) {} + maxScale(maxScale_), + writingMode(writingMode_) {} Point tl, tr, bl, br; Rect tex; float anchorAngle, glyphAngle; Point anchorPoint; float minScale, maxScale; + WritingModeType writingMode; }; typedef std::vector SymbolQuads; diff --git a/src/mbgl/util/i18n.cpp b/src/mbgl/util/i18n.cpp index 33ce5e22de7..8e56877a64d 100644 --- a/src/mbgl/util/i18n.cpp +++ b/src/mbgl/util/i18n.cpp @@ -1,5 +1,7 @@ #include "i18n.hpp" +#include + namespace { /** Defines a function that returns true if a codepoint is in a named block. @@ -8,7 +10,7 @@ namespace { @param last The last codepoint in the block, inclusive. */ #define DEFINE_IS_IN_UNICODE_BLOCK(name, first, last) \ - inline bool isIn##name(uint16_t codepoint) { \ + inline bool isIn##name(char16_t codepoint) { \ return codepoint >= first && codepoint <= last; \ } @@ -16,7 +18,7 @@ namespace { // Keep it synchronized with . // DEFINE_IS_IN_UNICODE_BLOCK(BasicLatin, 0x0000, 0x007F) -// DEFINE_IS_IN_UNICODE_BLOCK(Latin1Supplement, 0x0080, 0x00FF) +DEFINE_IS_IN_UNICODE_BLOCK(Latin1Supplement, 0x0080, 0x00FF) // DEFINE_IS_IN_UNICODE_BLOCK(LatinExtendedA, 0x0100, 0x017F) // DEFINE_IS_IN_UNICODE_BLOCK(LatinExtendedB, 0x0180, 0x024F) // DEFINE_IS_IN_UNICODE_BLOCK(IPAExtensions, 0x0250, 0x02AF) @@ -50,11 +52,11 @@ namespace { // DEFINE_IS_IN_UNICODE_BLOCK(Tibetan, 0x0F00, 0x0FFF) // DEFINE_IS_IN_UNICODE_BLOCK(Myanmar, 0x1000, 0x109F) // DEFINE_IS_IN_UNICODE_BLOCK(Georgian, 0x10A0, 0x10FF) -// DEFINE_IS_IN_UNICODE_BLOCK(HangulJamo, 0x1100, 0x11FF) +DEFINE_IS_IN_UNICODE_BLOCK(HangulJamo, 0x1100, 0x11FF) // DEFINE_IS_IN_UNICODE_BLOCK(Ethiopic, 0x1200, 0x137F) // DEFINE_IS_IN_UNICODE_BLOCK(EthiopicSupplement, 0x1380, 0x139F) // DEFINE_IS_IN_UNICODE_BLOCK(Cherokee, 0x13A0, 0x13FF) -// DEFINE_IS_IN_UNICODE_BLOCK(UnifiedCanadianAboriginalSyllabics, 0x1400, 0x167F) +DEFINE_IS_IN_UNICODE_BLOCK(UnifiedCanadianAboriginalSyllabics, 0x1400, 0x167F) // DEFINE_IS_IN_UNICODE_BLOCK(Ogham, 0x1680, 0x169F) // DEFINE_IS_IN_UNICODE_BLOCK(Runic, 0x16A0, 0x16FF) // DEFINE_IS_IN_UNICODE_BLOCK(Tagalog, 0x1700, 0x171F) @@ -63,7 +65,7 @@ namespace { // DEFINE_IS_IN_UNICODE_BLOCK(Tagbanwa, 0x1760, 0x177F) // DEFINE_IS_IN_UNICODE_BLOCK(Khmer, 0x1780, 0x17FF) // DEFINE_IS_IN_UNICODE_BLOCK(Mongolian, 0x1800, 0x18AF) -// DEFINE_IS_IN_UNICODE_BLOCK(UnifiedCanadianAboriginalSyllabicsExtended, 0x18B0, 0x18FF) +DEFINE_IS_IN_UNICODE_BLOCK(UnifiedCanadianAboriginalSyllabicsExtended, 0x18B0, 0x18FF) // DEFINE_IS_IN_UNICODE_BLOCK(Limbu, 0x1900, 0x194F) // DEFINE_IS_IN_UNICODE_BLOCK(TaiLe, 0x1950, 0x197F) // DEFINE_IS_IN_UNICODE_BLOCK(NewTaiLue, 0x1980, 0x19DF) @@ -84,22 +86,22 @@ namespace { // DEFINE_IS_IN_UNICODE_BLOCK(CombiningDiacriticalMarksSupplement, 0x1DC0, 0x1DFF) // DEFINE_IS_IN_UNICODE_BLOCK(LatinExtendedAdditional, 0x1E00, 0x1EFF) // DEFINE_IS_IN_UNICODE_BLOCK(GreekExtended, 0x1F00, 0x1FFF) -// DEFINE_IS_IN_UNICODE_BLOCK(GeneralPunctuation, 0x2000, 0x206F) +DEFINE_IS_IN_UNICODE_BLOCK(GeneralPunctuation, 0x2000, 0x206F) // DEFINE_IS_IN_UNICODE_BLOCK(SuperscriptsandSubscripts, 0x2070, 0x209F) // DEFINE_IS_IN_UNICODE_BLOCK(CurrencySymbols, 0x20A0, 0x20CF) // DEFINE_IS_IN_UNICODE_BLOCK(CombiningDiacriticalMarksforSymbols, 0x20D0, 0x20FF) -// DEFINE_IS_IN_UNICODE_BLOCK(LetterlikeSymbols, 0x2100, 0x214F) -// DEFINE_IS_IN_UNICODE_BLOCK(NumberForms, 0x2150, 0x218F) +DEFINE_IS_IN_UNICODE_BLOCK(LetterlikeSymbols, 0x2100, 0x214F) +DEFINE_IS_IN_UNICODE_BLOCK(NumberForms, 0x2150, 0x218F) // DEFINE_IS_IN_UNICODE_BLOCK(Arrows, 0x2190, 0x21FF) // DEFINE_IS_IN_UNICODE_BLOCK(MathematicalOperators, 0x2200, 0x22FF) -// DEFINE_IS_IN_UNICODE_BLOCK(MiscellaneousTechnical, 0x2300, 0x23FF) -// DEFINE_IS_IN_UNICODE_BLOCK(ControlPictures, 0x2400, 0x243F) -// DEFINE_IS_IN_UNICODE_BLOCK(OpticalCharacterRecognition, 0x2440, 0x245F) -// DEFINE_IS_IN_UNICODE_BLOCK(EnclosedAlphanumerics, 0x2460, 0x24FF) +DEFINE_IS_IN_UNICODE_BLOCK(MiscellaneousTechnical, 0x2300, 0x23FF) +DEFINE_IS_IN_UNICODE_BLOCK(ControlPictures, 0x2400, 0x243F) +DEFINE_IS_IN_UNICODE_BLOCK(OpticalCharacterRecognition, 0x2440, 0x245F) +DEFINE_IS_IN_UNICODE_BLOCK(EnclosedAlphanumerics, 0x2460, 0x24FF) // DEFINE_IS_IN_UNICODE_BLOCK(BoxDrawing, 0x2500, 0x257F) // DEFINE_IS_IN_UNICODE_BLOCK(BlockElements, 0x2580, 0x259F) -// DEFINE_IS_IN_UNICODE_BLOCK(GeometricShapes, 0x25A0, 0x25FF) -// DEFINE_IS_IN_UNICODE_BLOCK(MiscellaneousSymbols, 0x2600, 0x26FF) +DEFINE_IS_IN_UNICODE_BLOCK(GeometricShapes, 0x25A0, 0x25FF) +DEFINE_IS_IN_UNICODE_BLOCK(MiscellaneousSymbols, 0x2600, 0x26FF) // DEFINE_IS_IN_UNICODE_BLOCK(Dingbats, 0x2700, 0x27BF) // DEFINE_IS_IN_UNICODE_BLOCK(MiscellaneousMathematicalSymbolsA, 0x27C0, 0x27EF) // DEFINE_IS_IN_UNICODE_BLOCK(SupplementalArrowsA, 0x27F0, 0x27FF) @@ -123,15 +125,15 @@ DEFINE_IS_IN_UNICODE_BLOCK(CJKSymbolsandPunctuation, 0x3000, 0x303F) DEFINE_IS_IN_UNICODE_BLOCK(Hiragana, 0x3040, 0x309F) DEFINE_IS_IN_UNICODE_BLOCK(Katakana, 0x30A0, 0x30FF) DEFINE_IS_IN_UNICODE_BLOCK(Bopomofo, 0x3100, 0x312F) -// DEFINE_IS_IN_UNICODE_BLOCK(HangulCompatibilityJamo, 0x3130, 0x318F) -// DEFINE_IS_IN_UNICODE_BLOCK(Kanbun, 0x3190, 0x319F) +DEFINE_IS_IN_UNICODE_BLOCK(HangulCompatibilityJamo, 0x3130, 0x318F) +DEFINE_IS_IN_UNICODE_BLOCK(Kanbun, 0x3190, 0x319F) DEFINE_IS_IN_UNICODE_BLOCK(BopomofoExtended, 0x31A0, 0x31BF) DEFINE_IS_IN_UNICODE_BLOCK(CJKStrokes, 0x31C0, 0x31EF) DEFINE_IS_IN_UNICODE_BLOCK(KatakanaPhoneticExtensions, 0x31F0, 0x31FF) DEFINE_IS_IN_UNICODE_BLOCK(EnclosedCJKLettersandMonths, 0x3200, 0x32FF) DEFINE_IS_IN_UNICODE_BLOCK(CJKCompatibility, 0x3300, 0x33FF) DEFINE_IS_IN_UNICODE_BLOCK(CJKUnifiedIdeographsExtensionA, 0x3400, 0x4DBF) -// DEFINE_IS_IN_UNICODE_BLOCK(YijingHexagramSymbols, 0x4DC0, 0x4DFF) +DEFINE_IS_IN_UNICODE_BLOCK(YijingHexagramSymbols, 0x4DC0, 0x4DFF) DEFINE_IS_IN_UNICODE_BLOCK(CJKUnifiedIdeographs, 0x4E00, 0x9FFF) DEFINE_IS_IN_UNICODE_BLOCK(YiSyllables, 0xA000, 0xA48F) DEFINE_IS_IN_UNICODE_BLOCK(YiRadicals, 0xA490, 0xA4CF) @@ -148,7 +150,7 @@ DEFINE_IS_IN_UNICODE_BLOCK(YiRadicals, 0xA490, 0xA4CF) // DEFINE_IS_IN_UNICODE_BLOCK(DevanagariExtended, 0xA8E0, 0xA8FF) // DEFINE_IS_IN_UNICODE_BLOCK(KayahLi, 0xA900, 0xA92F) // DEFINE_IS_IN_UNICODE_BLOCK(Rejang, 0xA930, 0xA95F) -// DEFINE_IS_IN_UNICODE_BLOCK(HangulJamoExtendedA, 0xA960, 0xA97F) +DEFINE_IS_IN_UNICODE_BLOCK(HangulJamoExtendedA, 0xA960, 0xA97F) // DEFINE_IS_IN_UNICODE_BLOCK(Javanese, 0xA980, 0xA9DF) // DEFINE_IS_IN_UNICODE_BLOCK(MyanmarExtendedB, 0xA9E0, 0xA9FF) // DEFINE_IS_IN_UNICODE_BLOCK(Cham, 0xAA00, 0xAA5F) @@ -159,12 +161,12 @@ DEFINE_IS_IN_UNICODE_BLOCK(YiRadicals, 0xA490, 0xA4CF) // DEFINE_IS_IN_UNICODE_BLOCK(LatinExtendedE, 0xAB30, 0xAB6F) // DEFINE_IS_IN_UNICODE_BLOCK(CherokeeSupplement, 0xAB70, 0xABBF) // DEFINE_IS_IN_UNICODE_BLOCK(MeeteiMayek, 0xABC0, 0xABFF) -// DEFINE_IS_IN_UNICODE_BLOCK(HangulSyllables, 0xAC00, 0xD7AF) -// DEFINE_IS_IN_UNICODE_BLOCK(HangulJamoExtendedB, 0xD7B0, 0xD7FF) +DEFINE_IS_IN_UNICODE_BLOCK(HangulSyllables, 0xAC00, 0xD7AF) +DEFINE_IS_IN_UNICODE_BLOCK(HangulJamoExtendedB, 0xD7B0, 0xD7FF) // DEFINE_IS_IN_UNICODE_BLOCK(HighSurrogates, 0xD800, 0xDB7F) // DEFINE_IS_IN_UNICODE_BLOCK(HighPrivateUseSurrogates, 0xDB80, 0xDBFF) // DEFINE_IS_IN_UNICODE_BLOCK(LowSurrogates, 0xDC00, 0xDFFF) -// DEFINE_IS_IN_UNICODE_BLOCK(PrivateUseArea, 0xE000, 0xF8FF) +DEFINE_IS_IN_UNICODE_BLOCK(PrivateUseArea, 0xE000, 0xF8FF) DEFINE_IS_IN_UNICODE_BLOCK(CJKCompatibilityIdeographs, 0xF900, 0xFAFF) // DEFINE_IS_IN_UNICODE_BLOCK(AlphabeticPresentationForms, 0xFB00, 0xFB4F) // DEFINE_IS_IN_UNICODE_BLOCK(ArabicPresentationFormsA, 0xFB50, 0xFDFF) @@ -172,7 +174,7 @@ DEFINE_IS_IN_UNICODE_BLOCK(CJKCompatibilityIdeographs, 0xF900, 0xFAFF) DEFINE_IS_IN_UNICODE_BLOCK(VerticalForms, 0xFE10, 0xFE1F) // DEFINE_IS_IN_UNICODE_BLOCK(CombiningHalfMarks, 0xFE20, 0xFE2F) DEFINE_IS_IN_UNICODE_BLOCK(CJKCompatibilityForms, 0xFE30, 0xFE4F) -// DEFINE_IS_IN_UNICODE_BLOCK(SmallFormVariants, 0xFE50, 0xFE6F) +DEFINE_IS_IN_UNICODE_BLOCK(SmallFormVariants, 0xFE50, 0xFE6F) // DEFINE_IS_IN_UNICODE_BLOCK(ArabicPresentationFormsB, 0xFE70, 0xFEFF) DEFINE_IS_IN_UNICODE_BLOCK(HalfwidthandFullwidthForms, 0xFF00, 0xFFEF) // DEFINE_IS_IN_UNICODE_BLOCK(Specials, 0xFFF0, 0xFFFF) @@ -288,13 +290,33 @@ DEFINE_IS_IN_UNICODE_BLOCK(HalfwidthandFullwidthForms, 0xFF00, 0xFFEF) // DEFINE_IS_IN_UNICODE_BLOCK(VariationSelectorsSupplement, 0xE0100, 0xE01EF) // DEFINE_IS_IN_UNICODE_BLOCK(SupplementaryPrivateUseAreaA, 0xF0000, 0xFFFFF) // DEFINE_IS_IN_UNICODE_BLOCK(SupplementaryPrivateUseAreaB, 0x100000, 0x10FFFF) + +const std::map verticalPunctuation = { + { u'!', u'︕' }, { u'#', u'#' }, { u'$', u'$' }, { u'%', u'%' }, { u'&', u'&' }, + { u'(', u'︵' }, { u')', u'︶' }, { u'*', u'*' }, { u'+', u'+' }, { u',', u'︐' }, + { u'-', u'︲' }, { u'.', u'・' }, { u'/', u'/' }, { u':', u'︓' }, { u';', u'︔' }, + { u'<', u'︿' }, { u'=', u'=' }, { u'>', u'﹀' }, { u'?', u'︖' }, { u'@', u'@' }, + { u'[', u'﹇' }, { u'\\', u'\' }, { u']', u'﹈' }, { u'^', u'^' }, { u'_', u'︳' }, + { u'`', u'`' }, { u'{', u'︷' }, { u'|', u'―' }, { u'}', u'︸' }, { u'~', u'~' }, + { u'¢', u'¢' }, { u'£', u'£' }, { u'¥', u'¥' }, { u'¦', u'¦' }, { u'¬', u'¬' }, + { u'¯', u' ̄' }, { u'–', u'︲' }, { u'—', u'︱' }, { u'‘', u'﹃' }, { u'’', u'﹄' }, + { u'“', u'﹁' }, { u'”', u'﹂' }, { u'…', u'︙' }, { u'‧', u'・' }, { u'₩', u'₩' }, + { u'、', u'︑' }, { u'。', u'︒' }, { u'〈', u'︿' }, { u'〉', u'﹀' }, { u'《', u'︽' }, + { u'》', u'︾' }, { u'「', u'﹁' }, { u'」', u'﹂' }, { u'『', u'﹃' }, { u'』', u'﹄' }, + { u'【', u'︻' }, { u'】', u'︼' }, { u'〔', u'︹' }, { u'〕', u'︺' }, { u'〖', u'︗' }, + { u'〗', u'︘' }, { u'!', u'︕' }, { u'(', u'︵' }, { u')', u'︶' }, { u',', u'︐' }, + { u'-', u'︲' }, { u'.', u'・' }, { u':', u'︓' }, { u';', u'︔' }, { u'<', u'︿' }, + { u'>', u'﹀' }, { u'?', u'︖' }, { u'[', u'﹇' }, { u']', u'﹈' }, { u'_', u'︳' }, + { u'{', u'︷' }, { u'|', u'―' }, { u'}', u'︸' }, { u'⦅', u'︵' }, { u'⦆', u'︶' }, + { u'。', u'︒' }, { u'「', u'﹁' }, { u'」', u'﹂' }, +}; } namespace mbgl { namespace util { namespace i18n { -bool allowsWordBreaking(uint16_t chr) { +bool allowsWordBreaking(char16_t chr) { return (chr == 0x0a /* newline */ || chr == 0x20 /* space */ || chr == 0x26 /* ampersand */ @@ -311,7 +333,7 @@ bool allowsWordBreaking(uint16_t chr) { } bool allowsIdeographicBreaking(const std::u16string& string) { - for (uint16_t chr : string) { + for (char16_t chr : string) { if (!allowsIdeographicBreaking(chr)) { return false; } @@ -319,7 +341,7 @@ bool allowsIdeographicBreaking(const std::u16string& string) { return true; } -bool allowsIdeographicBreaking(uint16_t chr) { +bool allowsIdeographicBreaking(char16_t chr) { // Allow U+2027 "Interpunct" for hyphenation of Chinese words if (chr == 0x2027) return true; @@ -352,6 +374,188 @@ bool allowsIdeographicBreaking(uint16_t chr) { // || isInCJKCompatibilityIdeographsSupplement(chr)); } +bool allowsVerticalWritingMode(const std::u16string& string) { + for (char32_t chr : string) { + if (hasUprightVerticalOrientation(chr)) { + return true; + } + } + return false; +} + +// The following logic comes from +// . +// The data file denotes with “U” or “Tu” any codepoint that may be drawn +// upright in vertical text but does not distinguish between upright and +// “neutral” characters. + +bool hasUprightVerticalOrientation(char16_t chr) { + if (chr == u'˪' || chr == u'˫') + return true; + + // Return early for characters outside all ranges whose characters remain + // upright in vertical writing mode. + if (chr < 0x1100) + return false; + + if (isInBopomofo(chr) || isInBopomofoExtended(chr)) + return true; + if (isInCJKCompatibilityForms(chr)) { + if (!(chr >= u'﹉' && chr <= u'﹏')) + return true; + } + if (isInCJKCompatibility(chr) || isInCJKCompatibilityIdeographs(chr) || + isInCJKRadicalsSupplement(chr) || isInCJKStrokes(chr)) + return true; + if (isInCJKSymbolsandPunctuation(chr)) { + if (!(chr >= u'〈' && chr <= u'】') && !(chr >= u'〔' && chr <= u'〟') && chr != u'〰') + return true; + } + if (isInCJKUnifiedIdeographs(chr) || isInCJKUnifiedIdeographsExtensionA(chr) || + isInEnclosedCJKLettersandMonths(chr) || isInHangulCompatibilityJamo(chr) || + isInHangulJamo(chr) || isInHangulJamoExtendedA(chr) || isInHangulJamoExtendedB(chr) || + isInHangulSyllables(chr) || isInHiragana(chr) || + isInIdeographicDescriptionCharacters(chr) || isInKanbun(chr) || isInKangxiRadicals(chr)) + return true; + if (isInKatakana(chr)) { + if (chr != u'ー') + return true; + } + if (isInKatakanaPhoneticExtensions(chr)) + return true; + if (isInHalfwidthandFullwidthForms(chr)) { + if (chr != u'(' && chr != u')' && chr != u'-' && !(chr >= u':' && chr <= u'>') && + chr != u'[' && chr != u']' && chr != u'_' && !(chr >= u'{' && chr <= 0xFFDF) && + chr != u' ̄' && !(chr >= u'│' && chr <= 0xFFEF)) + return true; + } + if (isInSmallFormVariants(chr)) { + if (!(chr >= u'﹘' && chr <= u'﹞') && !(chr >= u'﹣' && chr <= u'﹦')) + return true; + } + if (isInUnifiedCanadianAboriginalSyllabics(chr) || + isInUnifiedCanadianAboriginalSyllabicsExtended(chr) || isInVerticalForms(chr) || + isInYijingHexagramSymbols(chr) || isInYiSyllables(chr) || isInYiRadicals(chr)) + return true; + + // https://github.com/mapbox/mapbox-gl/issues/29 + + // if (isInMeroiticHieroglyphs(chr)) return true; + // if (isInSiddham(chr)) return true; + // if (isInEgyptianHieroglyphs(chr)) return true; + // if (isInAnatolianHieroglyphs(chr)) return true; + // if (isInIdeographicSymbolsandPunctuation(chr)) return true; + // if (isInTangut(chr)) return true; + // if (isInTangutComponents(chr)) return true; + // if (isInKanaSupplement(chr)) return true; + // if (isInByzantineMusicalSymbols(chr)) return true; + // if (isInMusicalSymbols(chr)) return true; + // if (isInTaiXuanJingSymbols(chr)) return true; + // if (isInCountingRodNumerals(chr)) return true; + // if (isInSuttonSignWriting(chr)) return true; + // if (isInMahjongTiles(chr)) return true; + // if (isInDominoTiles(chr)) return true; + // if (isInPlayingCards(chr)) return true; + // if (isInEnclosedAlphanumericSupplement(chr)) return true; + // if (isInEnclosedIdeographicSupplement(chr)) return true; + // if (isInMiscellaneousSymbolsandPictographs(chr)) return true; + // if (isInEmoticons(chr)) return true; + // if (isInOrnamentalDingbats(chr)) return true; + // if (isInTransportandMapSymbols(chr)) return true; + // if (isInAlchemicalSymbols(chr)) return true; + // if (isInGeometricShapesExtended(chr)) return true; + // if (isInSupplementalSymbolsandPictographs(chr)) return true; + // if (isInCJKUnifiedIdeographsExtensionB(chr)) return true; + // if (isInCJKUnifiedIdeographsExtensionC(chr)) return true; + // if (isInCJKUnifiedIdeographsExtensionD(chr)) return true; + // if (isInCJKUnifiedIdeographsExtensionE(chr)) return true; + // if (isInCJKCompatibilityIdeographsSupplement(chr)) return true; + + return false; +} + +bool hasNeutralVerticalOrientation(char16_t chr) { + if (isInLatin1Supplement(chr)) { + if (chr == u'§' || chr == u'©' || chr == u'®' || chr == u'±' || chr == u'¼' || + chr == u'½' || chr == u'¾' || chr == u'×' || chr == u'÷') { + return true; + } + } + if (isInGeneralPunctuation(chr)) { + if (chr == u'‖' || chr == u'†' || chr == u'‡' || chr == u'‰' || chr == u'‱' || + chr == u'※' || chr == u'‼' || chr == u'⁂' || chr == u'⁇' || chr == u'⁈' || + chr == u'⁉' || chr == u'⁑') { + return true; + } + } + if (isInLetterlikeSymbols(chr) || isInNumberForms(chr)) { + return true; + } + if (isInMiscellaneousTechnical(chr)) { + if ((chr >= u'⌀' && chr <= u'⌇') || (chr >= u'⌌' && chr <= u'⌟') || + (chr >= u'⌤' && chr <= u'⌨') || chr == u'⌫' || (chr >= u'⍽' && chr <= u'⎚') || + (chr >= u'⎾' && chr <= u'⏍') || chr == u'⏏' || (chr >= u'⏑' && chr <= u'⏛') || + (chr >= u'⏢' && chr <= 0x23FF)) { + return true; + } + } + if (isInControlPictures(chr) || isInOpticalCharacterRecognition(chr) || + isInEnclosedAlphanumerics(chr) || isInGeometricShapes(chr)) { + return true; + } + if (isInMiscellaneousSymbols(chr)) { + if ((chr >= u'⬒' && chr <= u'⬯') || + (chr >= u'⭐' && chr <= 0x2B59 /* heavy circled saltire */) || + (chr >= 0x2BB8 /* upwards white arrow from bar with horizontal bar */ && + chr <= 0x2BEB)) { + return true; + } + } + if (isInCJKSymbolsandPunctuation(chr) || isInKatakana(chr) || isInPrivateUseArea(chr) || + isInCJKCompatibilityForms(chr) || isInSmallFormVariants(chr) || + isInHalfwidthandFullwidthForms(chr)) { + return true; + } + if (chr == u'∞' || chr == u'∴' || chr == u'∵' || + (chr >= 0x2700 /* black safety scissors */ && chr <= u'❧') || + (chr >= u'❶' && chr <= u'➓') || chr == 0xFFFC /* object replacement character */ || + chr == 0xFFFD /* replacement character */) { + return true; + } + return false; +} + +bool hasRotatedVerticalOrientation(char16_t chr) { + return !(hasUprightVerticalOrientation(chr) || hasNeutralVerticalOrientation(chr)); +} + +std::u16string verticalizePunctuation(const std::u16string& input) { + std::u16string output; + + for (size_t i = 0; i < input.size(); i++) { + char16_t nextCharCode = i < input.size() ? input[i + 1] : 0; + char16_t prevCharCode = i ? input[i - 1] : 0; + + bool canReplacePunctuation = + ((!nextCharCode || !hasRotatedVerticalOrientation(nextCharCode) || + verticalPunctuation.count(input[i + 1])) && + (!prevCharCode || !hasRotatedVerticalOrientation(prevCharCode) || + verticalPunctuation.count(input[i - 1]))); + + if (char16_t repl = canReplacePunctuation ? verticalizePunctuation(input[i]) : 0) { + output += repl; + } else { + output += input[i]; + } + } + + return output; +} + +char16_t verticalizePunctuation(char16_t chr) { + return verticalPunctuation.count(chr) ? verticalPunctuation.at(chr) : 0; +} + } // namespace i18n } // namespace util } // namespace mbgl diff --git a/src/mbgl/util/i18n.hpp b/src/mbgl/util/i18n.hpp index f1d3f53f729..186212f50d2 100644 --- a/src/mbgl/util/i18n.hpp +++ b/src/mbgl/util/i18n.hpp @@ -8,7 +8,7 @@ namespace i18n { /** Returns whether a line break can be inserted after the character indicated by the given Unicode codepoint due to word breaking. */ -bool allowsWordBreaking(uint16_t chr); +bool allowsWordBreaking(char16_t chr); /** Returns whether a line break can be inserted after any character in the given string. If false, line breaking should occur on word boundaries @@ -17,7 +17,53 @@ bool allowsIdeographicBreaking(const std::u16string& string); /** Returns whether a line break can be inserted after the character indicated by the given Unicode codepoint due to ideographic breaking. */ -bool allowsIdeographicBreaking(uint16_t chr); +bool allowsIdeographicBreaking(char16_t chr); + +/** Returns whether any substring of the given string can be drawn as vertical + text with upright glyphs. */ +bool allowsVerticalWritingMode(const std::u16string& string); + +/** Returns true if the given Unicode codepoint identifies a character with + upright orientation. + + A character has upright orientation if it is drawn upright (unrotated) + whether the line is oriented horizontally or vertically, even if both + adjacent characters can be rotated. For example, a Chinese character is + always drawn upright. An uprightly oriented character causes an adjacent + “neutral” character to be drawn upright as well. */ +bool hasUprightVerticalOrientation(char16_t chr); + +/** Returns true if the given Unicode codepoint identifies a character with + neutral orientation. + + A character has neutral orientation if it may be drawn rotated or unrotated + when the line is oriented vertically, depending on the orientation of the + adjacent characters. For example, along a verticlly oriented line, the + vulgar fraction ½ is drawn upright among Chinese characters but rotated + among Latin letters. A neutrally oriented character does not influence + whether an adjacent character is drawn upright or rotated. + */ +bool hasNeutralVerticalOrientation(char16_t chr); + +/** Returns true if the given Unicode codepoint identifies a character with + rotated orientation. + + A character has rotated orientation if it is drawn rotated when the line is + oriented vertically, even if both adjacent characters are upright. For + example, a Latin letter is drawn rotated along a vertical line. A rotated + character causes an adjacent “neutral” character to be drawn rotated as + well. + */ +bool hasRotatedVerticalOrientation(char16_t chr); + +/** Returns a copy of the given string with punctuation characters replaced with + their vertical forms wherever applicable. */ +std::u16string verticalizePunctuation(const std::u16string& input); + +/** Returns the form of the given character appropriate for vertical text. + + @return The character’s specialized vertical form; 0 if not applicable. */ +char16_t verticalizePunctuation(char16_t chr); } // namespace i18n } // namespace util diff --git a/test/text/quads.test.cpp b/test/text/quads.test.cpp index 0bc29613443..cc4a4be8e18 100644 --- a/test/text/quads.test.cpp +++ b/test/text/quads.test.cpp @@ -54,7 +54,7 @@ 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)); + shapedText.positionedGlyphs.emplace_back(PositionedGlyph(32, 0.0f, 0.0f, 0)); // none {