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

Commit

Permalink
[core] Upright CJK characters in vertically-oriented labels
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 committed Nov 18, 2016
1 parent 38fcbe2 commit 7e1e6a4
Show file tree
Hide file tree
Showing 15 changed files with 450 additions and 104 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"lodash": "^4.16.4",
"mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#597115a1e1bd982944b068f8accde34eada74fc2",
"mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#7f62a4fc9f21e619824d68abbc4b03cbc1685572",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#c32d0c5ac80e3b7393bc17b8944e64fa5cffd90a",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#5c768c1f001b70f23c83ff4b9a01260d2db2a6dd",
"mkdirp": "^0.5.1",
"node-cmake": "^1.2.1",
"request": "^2.72.0",
Expand Down
1 change: 1 addition & 0 deletions platform/ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
* TileJSON manifests can now specify `"scheme": "tms"` to indicate the use of [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) coordinates. ([#2270](https://github.com/mapbox/mapbox-gl-native/pull/2270))
* Fixed an issue causing abstract MGLMultiPointFeature objects to be returned in feature query results. Now concrete MGLPointCollectionFeature objects are returned. ([#6742](https://github.com/mapbox/mapbox-gl-native/pull/6742))
* Fixed rendering artifacts and missing glyphs that occurred after viewing a large number of CJK characters on the map. ([#5908](https://github.com/mapbox/mapbox-gl-native/pull/5908))
* 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))
* Improved the line wrapping behavior of point-placed labels written in Chinese, Japanese, and Yi. ([#6828](https://github.com/mapbox/mapbox-gl-native/pull/6828))
* `-[MGLMapView resetPosition]` now resets to the current style’s default center coordinates, zoom level, direction, and pitch, if specified. ([#6127](https://github.com/mapbox/mapbox-gl-native/pull/6127))
* Fixed an issue where feature querying sometimes failed to return the expected features when the map was tilted. ([#6773](https://github.com/mapbox/mapbox-gl-native/pull/6773))
Expand Down
1 change: 1 addition & 0 deletions platform/macos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* TileJSON manifests can now specify `"scheme": "tms"` to indicate the use of [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) coordinates. ([#2270](https://github.com/mapbox/mapbox-gl-native/pull/2270))
* Fixed an issue causing abstract `MGLMultiPointFeature` objects to be returned in feature query results. Now concrete `MGLPointCollectionFeature` objects are returned. ([#6742](https://github.com/mapbox/mapbox-gl-native/pull/6742))
* Fixed rendering artifacts and missing glyphs that occurred after viewing a large number of CJK characters on the map. ([#5908](https://github.com/mapbox/mapbox-gl-native/pull/5908))
* 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))
* Improved the line wrapping behavior of point-placed labels written in Chinese, Japanese, and Yi. ([#6828](https://github.com/mapbox/mapbox-gl-native/pull/6828))
* Fixed an issue where the style zoom levels were not respected when deciding when to render a layer. ([#5811](https://github.com/mapbox/mapbox-gl-native/issues/5811))
* Fixed an issue where feature querying sometimes failed to return the expected features when the map was tilted. ([#6773](https://github.com/mapbox/mapbox-gl-native/pull/6773))
Expand Down
37 changes: 26 additions & 11 deletions src/mbgl/layout/symbol_instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,44 @@ 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;
}
}

} // 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
71 changes: 52 additions & 19 deletions src/mbgl/layout/symbol_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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 @@ -99,6 +100,9 @@ SymbolLayout::SymbolLayout(std::string bucketName_,
// Loop through all characters of this text and collect unique codepoints.
for (char16_t chr : *ft.text) {
ranges.insert(getGlyphRange(chr));
if (char32_t verticalChr = util::i18n::verticalizePunctuation(chr)) {
ranges.insert(getGlyphRange(verticalChr));
}
}
}

Expand Down Expand Up @@ -187,29 +191,50 @@ 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(
const float oneEm = 24.0f;
shapedTextOrientations.first = glyphSet->getShaping(
/* string */ *feature.text,
/* base direction of text */ *feature.writingDirection,
/* maxWidth: ems */ layout.get<SymbolPlacement>() != SymbolPlacementType::Line ?
layout.get<TextMaxWidth>() * 24 : 0,
/* lineHeight: ems */ layout.get<TextLineHeight>() * 24,
layout.get<TextMaxWidth>() * oneEm : 0,
/* lineHeight: ems */ layout.get<TextLineHeight>() * oneEm,
/* horizontalAlign */ horizontalAlign,
/* verticalAlign */ verticalAlign,
/* justify */ justify,
/* spacing: ems */ layout.get<TextLetterSpacing>() * 24,
/* translate */ Point<float>(layout.get<TextOffset>()[0], layout.get<TextOffset>()[1]));
/* spacing: ems */ layout.get<TextLetterSpacing>() * oneEm,
/* translate */ Point<float>(layout.get<TextOffset>()[0], layout.get<TextOffset>()[1]),
/* verticalHeight */ oneEm,
/* writingMode */ WritingModeType::Horizontal);
if (util::i18n::allowsVerticalWritingMode(*feature.text) && textAlongLine) {
shapedTextOrientations.second = glyphSet->getShaping(
/* string */ *feature.text,
/* base direction of text */ *feature.writingDirection,
/* 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 */ WritingModeType::Vertical);
}

// Add the glyphs we need for this label to the glyph atlas.
if (shapedText) {
if (shapedTextOrientations.first) {
glyphAtlas.addGlyphs(tileUID, *feature.text, layout.get<TextFont>(), **glyphSet, face);
}
}
Expand All @@ -232,8 +257,8 @@ void SymbolLayout::prepare(uintptr_t tileUID,
}

// if either shapedText or icon position is present, add the feature
if (shapedText || shapedIcon) {
addFeature(feature.geometry, shapedText, shapedIcon, face, feature.index);
if (shapedTextOrientations.first || shapedIcon) {
addFeature(feature.geometry, shapedTextOrientations, shapedIcon, face, feature.index);
}
}

Expand All @@ -242,7 +267,9 @@ void SymbolLayout::prepare(uintptr_t tileUID,


void SymbolLayout::addFeature(const GeometryCollection &lines,
const Shaping &shapedText, const PositionedIcon &shapedIcon, const GlyphPositions &face, const size_t index) {
const std::pair<Shaping, Shaping>& shapedTextOrientations,
const PositionedIcon &shapedIcon, const GlyphPositions &face,
const size_t index) {

const float minScale = 0.5f;
const float glyphSize = 24.0f;
Expand Down Expand Up @@ -278,13 +305,13 @@ void SymbolLayout::addFeature(const GeometryCollection &lines,

// Calculate the anchor points around which you want to place labels
Anchors anchors = isLine ?
getAnchors(line, symbolSpacing, textMaxAngle, shapedText.left, shapedText.right, shapedIcon.left, shapedIcon.right, glyphSize, textMaxBoxScale, overscaling) :
getAnchors(line, symbolSpacing, textMaxAngle, (shapedTextOrientations.second ?: shapedTextOrientations.first).left, (shapedTextOrientations.second ?: shapedTextOrientations.first).right, shapedIcon.left, shapedIcon.right, glyphSize, textMaxBoxScale, overscaling) :
Anchors({ Anchor(float(line[0].x), float(line[0].y), 0, minScale) });

// For each potential label, create the placement features used to check for collisions, and the quads use for rendering.
for (Anchor &anchor : anchors) {
if (shapedText && isLine) {
if (anchorIsTooClose(shapedText.text, textRepeatDistance, anchor)) {
if (shapedTextOrientations.first && isLine) {
if (anchorIsTooClose(shapedTextOrientations.first.text, textRepeatDistance, anchor)) {
continue;
}
}
Expand All @@ -306,7 +333,7 @@ void SymbolLayout::addFeature(const GeometryCollection &lines,
// TODO remove the `&& false` when is #1673 implemented
const bool addToBuffers = (mode == MapMode::Still) || inside || (mayOverlap && false);

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 Down Expand Up @@ -400,7 +427,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 @@ -409,7 +436,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 @@ -422,7 +449,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 @@ -437,9 +464,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 @@ -60,7 +60,7 @@ class SymbolLayout {

private:
void addFeature(const GeometryCollection&,
const Shaping& shapedText,
const std::pair<Shaping, Shaping>& shapedTextOrientations,
const PositionedIcon& shapedIcon,
const GlyphPositions& face,
const size_t index);
Expand All @@ -73,7 +73,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 float overscaling;
const float zoom;
Expand Down
38 changes: 34 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 @@ -86,4 +91,29 @@ class SDFGlyph {
GlyphMetrics metrics;
};

enum class WritingModeType : uint8_t {
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 7e1e6a4

Please sign in to comment.