summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2017-02-10 16:57:48 -0800
committerJohn Firebaugh <john.firebaugh@gmail.com>2017-02-10 18:57:48 -0600
commite6c15d0f6f285bc604c0d93381b9b1d3957cb5c9 (patch)
treefefef07141b72a96d12f6ff788c8727b20420bab
parent0bbc6b814cbec44be7026a0bac83d56e4d71a287 (diff)
downloadqtlocation-mapboxgl-e6c15d0f6f285bc604c0d93381b9b1d3957cb5c9.tar.gz
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.
m---------mapbox-gl-js0
-rw-r--r--package.json2
-rw-r--r--platform/ios/CHANGELOG.md1
-rw-r--r--platform/macos/CHANGELOG.md1
-rw-r--r--src/mbgl/layout/symbol_instance.cpp39
-rw-r--r--src/mbgl/layout/symbol_instance.hpp4
-rw-r--r--src/mbgl/layout/symbol_layout.cpp82
-rw-r--r--src/mbgl/layout/symbol_layout.hpp5
-rw-r--r--src/mbgl/text/glyph.hpp39
-rw-r--r--src/mbgl/text/glyph_set.cpp28
-rw-r--r--src/mbgl/text/glyph_set.hpp9
-rw-r--r--src/mbgl/text/quads.cpp21
-rw-r--r--src/mbgl/text/quads.hpp6
-rw-r--r--src/mbgl/util/i18n.cpp254
-rw-r--r--src/mbgl/util/i18n.hpp50
-rw-r--r--test/text/quads.test.cpp2
16 files changed, 449 insertions, 94 deletions
diff --git a/mapbox-gl-js b/mapbox-gl-js
-Subproject 3c52ddba23124a1844162a7fc2417ac8671fa91
+Subproject 69de96f53ee41f0e2b8804491d236f0b1fe1a3c
diff --git a/package.json b/package.json
index 71450c68b7..19acd188c1 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 3db7548fe5..87b2b8c05f 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 efca47f435..b52d1d7055 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 fafcc7c15d..2935fddd91 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<Shaping, Shaping>& 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 508c11a394..014e34cb78 100644
--- a/src/mbgl/layout/symbol_instance.hpp
+++ b/src/mbgl/layout/symbol_instance.hpp
@@ -1,6 +1,7 @@
#pragma once
#include <mbgl/text/quads.hpp>
+#include <mbgl/text/glyph.hpp>
#include <mbgl/text/collision_feature.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
@@ -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<Shaping, Shaping>& 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 @@ public:
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 665806cb0e..11c26fa661 100644
--- a/src/mbgl/layout/symbol_layout.cpp
+++ b/src/mbgl/layout/symbol_layout.cpp
@@ -17,6 +17,7 @@
#include <mbgl/util/std.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/string.hpp>
+#include <mbgl/util/i18n.hpp>
#include <mbgl/math/clamp.hpp>
#include <mbgl/math/minmax.hpp>
#include <mbgl/math/log2.hpp>
@@ -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<TextFont>());
+ const bool textAlongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map &&
+ layout.get<SymbolPlacement>() == SymbolPlacementType::Line;
+
for (const auto& feature : features) {
if (feature.geometry.empty()) continue;
- Shaping shapedText;
+ std::pair<Shaping, Shaping> 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<SymbolPlacement>() != SymbolPlacementType::Line ?
- layout.get<TextMaxWidth>() * 24 : 0,
- /* lineHeight: ems */ layout.get<TextLineHeight>() * 24,
- /* horizontalAlign */ horizontalAlign,
- /* verticalAlign */ verticalAlign,
- /* justify */ justify,
- /* spacing: ems */ layout.get<TextLetterSpacing>() * 24,
- /* translate */ Point<float>(layout.get<TextOffset>()[0], layout.get<TextOffset>()[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<TextFont>(), **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<SymbolPlacement>() != SymbolPlacementType::Line ?
+ layout.get<TextMaxWidth>() * oneEm : 0,
+ /* lineHeight: ems */ layout.get<TextLineHeight>() * oneEm,
+ /* horizontalAlign */ horizontalAlign,
+ /* verticalAlign */ verticalAlign,
+ /* justify */ justify,
+ /* spacing: ems */ layout.get<TextLetterSpacing>() * oneEm,
+ /* translate */ Point<float>(layout.get<TextOffset>()[0], layout.get<TextOffset>()[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<TextFont>(), **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<Shaping, Shaping>& 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<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile)
if (glyphScale < collisionTile.maxScale) {
addSymbols(
bucket->text, symbolInstance.glyphQuads, glyphScale,
- layout.get<TextKeepUpright>(), textPlacement, collisionTile.config.angle);
+ layout.get<TextKeepUpright>(), textPlacement, collisionTile.config.angle, symbolInstance.writingModes);
}
}
@@ -457,7 +477,7 @@ std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile)
if (iconScale < collisionTile.maxScale) {
addSymbols(
bucket->icon, symbolInstance.iconQuads, iconScale,
- layout.get<IconKeepUpright>(), iconPlacement, collisionTile.config.angle);
+ layout.get<IconKeepUpright>(), iconPlacement, collisionTile.config.angle, symbolInstance.writingModes);
}
}
}
@@ -470,7 +490,7 @@ std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile)
}
template <typename Buffer>
-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 cb31bb3b29..0784c49614 100644
--- a/src/mbgl/layout/symbol_layout.hpp
+++ b/src/mbgl/layout/symbol_layout.hpp
@@ -55,7 +55,7 @@ public:
private:
void addFeature(const SymbolFeature&,
- const Shaping& shapedText,
+ const std::pair<Shaping, Shaping>& shapedTextOrientations,
const PositionedIcon& shapedIcon,
const GlyphPositions& face);
@@ -67,7 +67,8 @@ private:
// Adds placed items to the buffer.
template <typename Buffer>
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 2bf1448492..c89d045dfc 100644
--- a/src/mbgl/text/glyph.hpp
+++ b/src/mbgl/text/glyph.hpp
@@ -2,6 +2,7 @@
#include <mbgl/text/glyph_range.hpp>
#include <mbgl/util/rect.hpp>
+#include <mbgl/util/traits.hpp>
#include <cstdint>
#include <vector>
@@ -52,25 +53,29 @@ typedef std::map<uint32_t, Glyph> 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<PositionedGlyph> 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 @@ public:
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 b4b3195486..61d5cac926 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<float>& 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<std::u16string> 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<std::size_t> leastBadBreaks(const PotentialBreak& lastLineBreak) {
// more intuitive, but we can't do that because the visual order may be changed by line breaks!
std::set<std::size_t> 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<float>& translate) const {
+ const Point<float>& 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 3037cefca0..0342c82eb5 100644
--- a/src/mbgl/text/glyph_set.hpp
+++ b/src/mbgl/text/glyph_set.hpp
@@ -18,6 +18,8 @@ public:
float justify,
float spacing,
const Point<float>& translate,
+ float verticalHeight,
+ const WritingModeType,
BiDi& bidi) const;
private:
@@ -26,7 +28,8 @@ private:
float maxWidth) const;
std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput,
const float spacing,
- float maxWidth) const;
+ float maxWidth,
+ const WritingModeType) const;
void shapeLines(Shaping& shaping,
const std::vector<std::u16string>& lines,
@@ -35,7 +38,9 @@ private:
float horizontalAlign,
float verticalAlign,
float justify,
- const Point<float>& translate) const;
+ const Point<float>& translate,
+ float verticalHeight,
+ const WritingModeType) const;
std::map<uint32_t, SDFGlyph> sdfs;
};
diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp
index 10c4dfea90..b4a3ea09a4 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<float>::infinity());
+ quads.emplace_back(tl, tr, bl, br, image.pos, 0, 0, anchor.point, globalMinScale, std::numeric_limits<float>::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<float> otl{x1, y1};
- const Point<float> otr{x2, y1};
- const Point<float> obl{x1, y2};
- const Point<float> obr{x2, y2};
+ const Point<float> center{positionedGlyph.x, static_cast<float>(static_cast<float>(glyph.metrics.advance) / 2.0)};
+
+ Point<float> otl{x1, y1};
+ Point<float> otr{x2, y1};
+ Point<float> obl{x1, y2};
+ Point<float> 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 75fb53aade..760015340e 100644
--- a/src/mbgl/text/quads.hpp
+++ b/src/mbgl/text/quads.hpp
@@ -15,7 +15,7 @@ class PositionedIcon;
struct SymbolQuad {
explicit SymbolQuad(Point<float> tl_, Point<float> tr_, Point<float> bl_, Point<float> br_,
Rect<uint16_t> tex_, float anchorAngle_, float glyphAngle_, Point<float> 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<float> tl, tr, bl, br;
Rect<uint16_t> tex;
float anchorAngle, glyphAngle;
Point<float> anchorPoint;
float minScale, maxScale;
+ WritingModeType writingMode;
};
typedef std::vector<SymbolQuad> SymbolQuads;
diff --git a/src/mbgl/util/i18n.cpp b/src/mbgl/util/i18n.cpp
index 33ce5e22de..8e56877a64 100644
--- a/src/mbgl/util/i18n.cpp
+++ b/src/mbgl/util/i18n.cpp
@@ -1,5 +1,7 @@
#include "i18n.hpp"
+#include <map>
+
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 <http://www.unicode.org/Public/UCD/latest/ucd/Blocks.txt>.
// 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<char16_t, char16_t> 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
+// <http://www.unicode.org/Public/vertical/revision-16/VerticalOrientation-16.txt>.
+// 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 f1d3f53f72..186212f50d 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 0bc2961344..cc4a4be8e1 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
{