Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
Upright CJK characters in vertically-oriented labels (#7114)
Browse files Browse the repository at this point in the history
CJK characters and adjacent punctuation now remain upright in vertically oriented labels that have line placement.

Fixes #1682.
  • Loading branch information
1ec5 authored and jfirebaugh committed Feb 11, 2017
1 parent 0bbc6b8 commit e6c15d0
Show file tree
Hide file tree
Showing 16 changed files with 450 additions and 95 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@
"remote_path": "./{name}/v{version}",
"package_name": "{node_abi}-{platform}-{arch}.tar.gz"
}
}
}
1 change: 1 addition & 0 deletions platform/ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions platform/macos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 28 additions & 11 deletions src/mbgl/layout/symbol_instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 3 additions & 1 deletion src/mbgl/layout/symbol_instance.hpp
Original file line number Diff line number Diff line change
@@ -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>

Expand All @@ -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,
Expand All @@ -26,6 +27,7 @@ class SymbolInstance {
SymbolQuads iconQuads;
CollisionFeature textCollisionFeature;
CollisionFeature iconCollisionFeature;
WritingModeType writingModes;
};

} // namespace mbgl
82 changes: 54 additions & 28 deletions src/mbgl/layout/symbol_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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));
}
}
}

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

Expand All @@ -251,16 +271,16 @@ 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);
}
}

features.clear();
}

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;
Expand Down Expand Up @@ -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);
Expand All @@ -317,16 +337,16 @@ 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,
textMaxBoxScale,
overscaling);

for (auto& anchor : anchors) {
if (!shapedText || !anchorIsTooClose(shapedText.text, textRepeatDistance, anchor)) {
if (!shapedTextOrientations.first || !anchorIsTooClose(shapedTextOrientations.first.text, textRepeatDistance, anchor)) {
addSymbolInstance(line, anchor);
}
}
Expand Down Expand Up @@ -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);
}
}

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

Expand All @@ -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;
}
Expand Down
5 changes: 3 additions & 2 deletions src/mbgl/layout/symbol_layout.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class SymbolLayout {

private:
void addFeature(const SymbolFeature&,
const Shaping& shapedText,
const std::pair<Shaping, Shaping>& shapedTextOrientations,
const PositionedIcon& shapedIcon,
const GlyphPositions& face);

Expand All @@ -67,7 +67,8 @@ class SymbolLayout {
// 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;
Expand Down
39 changes: 35 additions & 4 deletions src/mbgl/text/glyph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <mbgl/text/glyph_range.hpp>
#include <mbgl/util/rect.hpp>
#include <mbgl/util/traits.hpp>

#include <cstdint>
#include <vector>
Expand Down Expand Up @@ -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(); }
};
Expand All @@ -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
Loading

0 comments on commit e6c15d0

Please sign in to comment.