From 150dfc6131155294e8e407bd1f2cfe8f416224f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 26 Oct 2016 03:37:26 -0700 Subject: [PATCH] [core] Line-break ideographic text by character Allow a line break to be inserted after any supported Chinese, Japanese, or Yi character in a point-placed label. Balance the lines unless non-ideographic text such as Latin letters are present. Fixes #1223. --- cmake/core-files.cmake | 2 ++ package.json | 2 +- platform/ios/CHANGELOG.md | 1 + platform/macos/CHANGELOG.md | 1 + src/mbgl/text/glyph_set.cpp | 25 ++++++++++----- src/mbgl/text/glyph_set.hpp | 3 +- src/mbgl/util/i18n.cpp | 62 +++++++++++++++++++++++++++++++++++++ src/mbgl/util/i18n.hpp | 18 +++++++++++ 8 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 src/mbgl/util/i18n.cpp create mode 100644 src/mbgl/util/i18n.hpp diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 4d5cddbc49f..82d7cfa7550 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -451,6 +451,8 @@ set(MBGL_CORE_FILES src/mbgl/util/http_header.hpp src/mbgl/util/http_timeout.cpp src/mbgl/util/http_timeout.hpp + src/mbgl/util/i18n.cpp + src/mbgl/util/i18n.hpp src/mbgl/util/interpolate.hpp src/mbgl/util/intersection_tests.cpp src/mbgl/util/intersection_tests.hpp diff --git a/package.json b/package.json index de2f6ff39e2..75bd421417e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "lodash": "^4.16.4", "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#98a56d538b11fb331aa67a6d632d6ecd6821b007", "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#7f62a4fc9f21e619824d68abbc4b03cbc1685572", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#844ecd23c3314eec509dbcdaf0f956d5504e6fef", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#4a508b6da9bad0fe267b0ccd4440fa061da3b4f8", "mkdirp": "^0.5.1", "node-cmake": "^1.2.1", "request": "^2.72.0", diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 4d8ca723c25..3ebfde05365 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -19,6 +19,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Added [quadkey](https://msdn.microsoft.com/en-us/library/bb259689.aspx) support and limited WMS support in raster tile URL templates. ([#5628](https://github.com/mapbox/mapbox-gl-native/pull/5628)) * 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 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)) +* 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)) * The `text-pitch-alignment` property is now supported in stylesheets for improved street label legibility on a tilted map. ([#5288](https://github.com/mapbox/mapbox-gl-native/pull/5288)) * The `icon-text-fit` and `icon-text-fit-padding` properties are now supported in stylesheets, allowing the background of a shield to automatically resize to fit the shield’s text. ([#5334](https://github.com/mapbox/mapbox-gl-native/pull/5334)) diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 5d58bafd799..33fd4db8347 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -15,6 +15,7 @@ * GeoJSON sources specified by the stylesheet at design time now support `cluster`, `clusterMaxZoom`, and `clusterRadius` attributes for clustering point features on the base map. ([#5724](https://github.com/mapbox/mapbox-gl-native/pull/5724)) * 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 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)) +* 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)) * If MGLMapView is unable to obtain or parse a style, it now calls its delegate’s `-mapViewDidFailLoadingMap:withError:` method. ([#6145](https://github.com/mapbox/mapbox-gl-native/pull/6145)) * Added the `-[MGLMapViewDelegate mapView:didFinishLoadingStyle:]` delegate method, which offers the earliest opportunity to modify the layout or appearance of the current style before the map view is displayed to the user. ([#6636](https://github.com/mapbox/mapbox-gl-native/pull/6636)) diff --git a/src/mbgl/text/glyph_set.cpp b/src/mbgl/text/glyph_set.cpp index 0875a83850d..06e22168451 100644 --- a/src/mbgl/text/glyph_set.cpp +++ b/src/mbgl/text/glyph_set.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -54,7 +55,8 @@ const Shaping GlyphSet::getShaping(const std::u32string &string, const float max if (shaping.positionedGlyphs.empty()) return shaping; - lineWrap(shaping, lineHeight, maxWidth, horizontalAlign, verticalAlign, justify, translate); + lineWrap(shaping, lineHeight, maxWidth, horizontalAlign, verticalAlign, justify, translate, + util::i18n::allowsIdeographicBreaking(string)); return shaping; } @@ -85,9 +87,10 @@ void justifyLine(std::vector &positionedGlyphs, const std::map< } } -void GlyphSet::lineWrap(Shaping &shaping, const float lineHeight, const float maxWidth, - const float horizontalAlign, const float verticalAlign, - const float justify, const Point &translate) const { +void GlyphSet::lineWrap(Shaping &shaping, const float lineHeight, float maxWidth, + const float horizontalAlign, const float verticalAlign, + const float justify, const Point &translate, + bool useBalancedIdeographicBreaking) const { uint32_t lastSafeBreak = 0; uint32_t lengthBeforeCurrentLine = 0; @@ -99,6 +102,12 @@ void GlyphSet::lineWrap(Shaping &shaping, const float lineHeight, const float ma std::vector &positionedGlyphs = shaping.positionedGlyphs; if (maxWidth) { + if (useBalancedIdeographicBreaking) { + auto lastPositionedGlyph = positionedGlyphs[positionedGlyphs.size() - 1]; + uint32_t estimatedLineCount = std::fmax(1, std::ceil(lastPositionedGlyph.x / maxWidth)); + maxWidth = lastPositionedGlyph.x / estimatedLineCount; + } + for (uint32_t i = 0; i < positionedGlyphs.size(); i++) { PositionedGlyph &shape = positionedGlyphs[i]; @@ -133,8 +142,9 @@ void GlyphSet::lineWrap(Shaping &shaping, const float lineHeight, const float ma line++; } - // Spaces, plus word-breaking punctuation that often appears without surrounding spaces. - if (shape.glyph == 0x20 /* space */ + // Ideographic characters, spaces, and word-breaking punctuation that often appear without surrounding spaces. + if (useBalancedIdeographicBreaking + || shape.glyph == 0x20 /* space */ || shape.glyph == 0x26 /* ampersand */ || shape.glyph == 0x2b /* plus sign */ || shape.glyph == 0x2d /* hyphen-minus */ @@ -143,7 +153,8 @@ void GlyphSet::lineWrap(Shaping &shaping, const float lineHeight, const float ma || shape.glyph == 0xb7 /* middle dot */ || shape.glyph == 0x200b /* zero-width space */ || shape.glyph == 0x2010 /* hyphen */ - || shape.glyph == 0x2013 /* en dash */) { + || shape.glyph == 0x2013 /* en dash */ + || util::i18n::allowsIdeographicBreaking(shape.glyph)) { lastSafeBreak = i; } } diff --git a/src/mbgl/text/glyph_set.hpp b/src/mbgl/text/glyph_set.hpp index 37ffdb070a2..fed7960a5f7 100644 --- a/src/mbgl/text/glyph_set.hpp +++ b/src/mbgl/text/glyph_set.hpp @@ -13,7 +13,8 @@ class GlyphSet { float horizontalAlign, float verticalAlign, float justify, float spacing, const Point &translate) const; void lineWrap(Shaping &shaping, float lineHeight, float maxWidth, float horizontalAlign, - float verticalAlign, float justify, const Point &translate) const; + float verticalAlign, float justify, const Point &translate, + bool useBalancedIdeographicBreaking) const; private: std::map sdfs; diff --git a/src/mbgl/util/i18n.cpp b/src/mbgl/util/i18n.cpp new file mode 100644 index 00000000000..1fee899eeb6 --- /dev/null +++ b/src/mbgl/util/i18n.cpp @@ -0,0 +1,62 @@ +#include "i18n.hpp" + +namespace mbgl { +namespace util { +namespace i18n { + +bool allowsIdeographicBreaking(const std::u32string& string) { + for (uint32_t chr : string) { + if (!allowsIdeographicBreaking(chr)) { + return false; + } + } + return true; +} + +bool allowsIdeographicBreaking(uint32_t chr) { + // Return early for characters outside all ideographic ranges. + if (chr < 0x2E80) return false; + + // CJK Radicals Supplement, Kangxi Radicals, Ideographic Description Characters, CJK Symbols and Punctuation: “⺀” to “〿” + if (chr >= 0x2E80 && chr <= 0x303F) return true; + + // Hiragana: before “ぁ” to “ゟ” + if (chr >= 0x3040 && chr <= 0x309F) return true; + + // Katakana: “゠” to “ヿ” + if (chr >= 0x30A0 && chr <= 0x30FF) return true; + + // CJK Strokes: “㇀” to past “㇣” + if (chr >= 0x31C0 && chr <= 0x31EF) return true; + + // Katakana Phonetic Extensions: “ㇰ” to “ㇿ” + if (chr >= 0x31F0 && chr <= 0x31FF) return true; + + // Enclosed CJK Letters and Months, CJK Compatibility: “㈀” to “㏿” + if (chr >= 0x3200 && chr <= 0x33FF) return true; + + // CJK Unified Ideographs Extension A: “㐀” to past “䶵” + if (chr >= 0x3400 && chr <= 0x4DBF) return true; + + // CJK Unified Ideographs: “一” to past “鿕” + if (chr >= 0x4E00 && chr <= 0x9FFF) return true; + + // Yi Syllables, Yi Radicals: “ꀀ” to past “꓆” + if (chr >= 0xA000 && chr <= 0xA4CF) return true; + + // CJK Compatibility Forms: “︰” to “﹏” + if (chr >= 0xFE30 && chr <= 0xFE4F) return true; + + // CJK Compatibility Ideographs: “豈” to past “龎” + if (chr >= 0xF900 && chr <= 0xFAFF) return true; + + // Halfwidth and Fullwidth Forms: before “!” to past “○” + if (chr >= 0xFF00 && chr <= 0xFFEF) return true; + + return false; +} + +} // namespace i18n +} // namespace util +} // namespace mbgl + diff --git a/src/mbgl/util/i18n.hpp b/src/mbgl/util/i18n.hpp new file mode 100644 index 00000000000..d1a7454f66e --- /dev/null +++ b/src/mbgl/util/i18n.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace mbgl { +namespace util { +namespace i18n { + +/// Returns whether a line break can be inserted after any character in the given string. +/// If false, line breaking should occur on word boundaries instead. +bool allowsIdeographicBreaking(const std::u32string& string); + +/// Returns whether a line break can be inserted after the character indicated by the given Unicode codepoint. +bool allowsIdeographicBreaking(uint32_t chr); + +} // namespace i18n +} // namespace util +} // namespace mbgl