keep digits and short codes upright in CJK line labels (#4566)
@@ -29,6 +29,7 @@
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- *...Add new stuff here...*
|
||||
- [core] Draw numbers and short uppercase codes upright in vertical CJK line labels ([#4565](https://github.com/maplibre/maplibre-native/issues/4565)), compat to [maplibre-gl-js#8205](https://github.com/maplibre/maplibre-gl-js/pull/8205).
|
||||
- [core] Fix `ImageSource` not rendering across world copies ([#4508](https://github.com/maplibre/maplibre-native/issues/4508)).
|
||||
- [core] Repaint data-driven symbol paint properties after feature-state updates.
|
||||
- [core] Accept alpha values in legacy comma-separated `hsl()` colors ([#4434](https://github.com/maplibre/maplibre-native/issues/4434)).
|
||||
|
||||
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,514 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"height": 400,
|
||||
"width": 900
|
||||
}
|
||||
},
|
||||
"zoom": 3.5,
|
||||
"center": [
|
||||
1,
|
||||
3
|
||||
],
|
||||
"sources": {
|
||||
"maplibre": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "札幌21タワー"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-17.5,
|
||||
3
|
||||
],
|
||||
[
|
||||
-9.5,
|
||||
3
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "札幌21タワー"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-17.6464,
|
||||
3.3536
|
||||
],
|
||||
[
|
||||
-9.8682,
|
||||
11.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "札幌21タワー"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-18,
|
||||
3.5
|
||||
],
|
||||
[
|
||||
-18,
|
||||
11.5
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "札幌21タワー"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-18.3536,
|
||||
3.3536
|
||||
],
|
||||
[
|
||||
-26.1318,
|
||||
11.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "札幌21タワー"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-18.5,
|
||||
3
|
||||
],
|
||||
[
|
||||
-26.5,
|
||||
3
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "札幌21タワー"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-18.3536,
|
||||
2.6464
|
||||
],
|
||||
[
|
||||
-26.1318,
|
||||
-5.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "東京21タワー"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-18,
|
||||
2.5
|
||||
],
|
||||
[
|
||||
-18,
|
||||
-5.5
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "東京21タワー"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-17.6464,
|
||||
2.6464
|
||||
],
|
||||
[
|
||||
-9.8682,
|
||||
-5.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "南京路21号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
1.5,
|
||||
3
|
||||
],
|
||||
[
|
||||
9.5,
|
||||
3
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "南京路21号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
1.3536,
|
||||
3.3536
|
||||
],
|
||||
[
|
||||
9.1318,
|
||||
11.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "南京路21号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
1,
|
||||
3.5
|
||||
],
|
||||
[
|
||||
1,
|
||||
11.5
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "南京路21号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
0.6464,
|
||||
3.3536
|
||||
],
|
||||
[
|
||||
-7.1318,
|
||||
11.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "南京路21号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
0.5,
|
||||
3
|
||||
],
|
||||
[
|
||||
-7.5,
|
||||
3
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "南京路21号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
0.6464,
|
||||
2.6464
|
||||
],
|
||||
[
|
||||
-7.1318,
|
||||
-5.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "南京路21号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
1,
|
||||
2.5
|
||||
],
|
||||
[
|
||||
1,
|
||||
-5.5
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "南京路21号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
1.3536,
|
||||
2.6464
|
||||
],
|
||||
[
|
||||
9.1318,
|
||||
-5.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Street 123"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
20.5,
|
||||
3
|
||||
],
|
||||
[
|
||||
28.5,
|
||||
3
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Street 123"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
20.3536,
|
||||
3.3536
|
||||
],
|
||||
[
|
||||
28.1318,
|
||||
11.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Street 123"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
20,
|
||||
3.5
|
||||
],
|
||||
[
|
||||
20,
|
||||
11.5
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Street 123"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
19.6464,
|
||||
3.3536
|
||||
],
|
||||
[
|
||||
11.8682,
|
||||
11.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Street 123"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
19.5,
|
||||
3
|
||||
],
|
||||
[
|
||||
11.5,
|
||||
3
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Street 123"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
19.6464,
|
||||
2.6464
|
||||
],
|
||||
[
|
||||
11.8682,
|
||||
-5.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Street 123"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
20,
|
||||
2.5
|
||||
],
|
||||
[
|
||||
20,
|
||||
-5.5
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Street 123"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
20.3536,
|
||||
2.6464
|
||||
],
|
||||
[
|
||||
28.1318,
|
||||
-5.1318
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lines-symbol",
|
||||
"type": "symbol",
|
||||
"source": "maplibre",
|
||||
"layout": {
|
||||
"text-field": "{name}",
|
||||
"text-size": 12,
|
||||
"symbol-placement": "line-center",
|
||||
"symbol-spacing": 100,
|
||||
"text-allow-overlap": true,
|
||||
"text-ignore-placement": true,
|
||||
"text-font": [
|
||||
"NotoCJK"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lines",
|
||||
"type": "line",
|
||||
"source": "maplibre",
|
||||
"paint": {
|
||||
"line-opacity": 0.25
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,208 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"height": 512,
|
||||
"width": 900
|
||||
}
|
||||
},
|
||||
"zoom": 2,
|
||||
"center": [
|
||||
0,
|
||||
57
|
||||
],
|
||||
"sources": {
|
||||
"maplibre": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "身什戰1-2号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-70,
|
||||
39
|
||||
],
|
||||
[
|
||||
-70,
|
||||
75
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "身什戰3.5号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-50,
|
||||
39
|
||||
],
|
||||
[
|
||||
-50,
|
||||
75
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "身什戰1〜2号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-30,
|
||||
39
|
||||
],
|
||||
[
|
||||
-30,
|
||||
75
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "身什戰A16号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-10,
|
||||
39
|
||||
],
|
||||
[
|
||||
-10,
|
||||
75
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "身什戰ABC号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
10,
|
||||
39
|
||||
],
|
||||
[
|
||||
10,
|
||||
75
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "身什戰ABCD号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
30,
|
||||
39
|
||||
],
|
||||
[
|
||||
30,
|
||||
75
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "身什戰what号"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
50,
|
||||
39
|
||||
],
|
||||
[
|
||||
50,
|
||||
75
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "ヴィラ ISHIKAWA"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
70,
|
||||
39
|
||||
],
|
||||
[
|
||||
70,
|
||||
75
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lines-symbol",
|
||||
"type": "symbol",
|
||||
"source": "maplibre",
|
||||
"layout": {
|
||||
"text-field": "{name}",
|
||||
"symbol-placement": "line",
|
||||
"symbol-spacing": 400,
|
||||
"text-allow-overlap": true,
|
||||
"text-font": [
|
||||
"NotoCJK"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lines",
|
||||
"type": "line",
|
||||
"source": "maplibre",
|
||||
"paint": {
|
||||
"line-opacity": 0.25
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 28 KiB |
@@ -70,6 +70,7 @@ target_sources(
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/text/bidi.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/compression.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/filesystem.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/i18n.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/monotonic_timer.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/png_writer.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/thread_local.cpp
|
||||
|
||||
@@ -80,6 +80,7 @@ target_sources(
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/text/bidi.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/compression.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/filesystem.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/i18n.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/monotonic_timer.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/png_writer.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/thread_local.cpp
|
||||
|
||||
@@ -63,6 +63,7 @@ cc_library(
|
||||
"src/mln/text/bidi.cpp",
|
||||
"src/mln/util/compression.cpp",
|
||||
"src/mln/util/filesystem.cpp",
|
||||
"src/mln/util/i18n.cpp",
|
||||
"src/mln/util/monotonic_timer.cpp",
|
||||
"src/mln/util/png_writer.cpp",
|
||||
"src/mln/util/thread_local.cpp",
|
||||
|
||||
23
platform/default/src/mln/util/i18n.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <mln/util/i18n.hpp>
|
||||
|
||||
#include <unicode/uchar.h>
|
||||
|
||||
namespace mln {
|
||||
namespace util {
|
||||
namespace i18n {
|
||||
|
||||
bool isDigit(char16_t chr) {
|
||||
return u_charType(chr) == U_DECIMAL_DIGIT_NUMBER;
|
||||
}
|
||||
|
||||
bool isUppercase(char16_t chr) {
|
||||
return u_charType(chr) == U_UPPERCASE_LETTER;
|
||||
}
|
||||
|
||||
bool isPunctuationOrSymbol(char16_t chr) {
|
||||
return (U_GET_GC_MASK(chr) & (U_GC_P_MASK | U_GC_S_MASK)) != 0;
|
||||
}
|
||||
|
||||
} // namespace i18n
|
||||
} // namespace util
|
||||
} // namespace mln
|
||||
@@ -57,6 +57,7 @@ target_sources(
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/async_task.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/compression.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/filesystem.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/i18n.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/image.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/jpeg_reader.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/webp_reader.cpp
|
||||
|
||||
@@ -112,6 +112,7 @@ target_sources(
|
||||
${PROJECT_SOURCE_DIR}/platform/qt/src/mln/http_file_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/platform/qt/src/mln/http_request.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/qt/src/mln/http_request.hpp
|
||||
${PROJECT_SOURCE_DIR}/platform/qt/src/mln/i18n.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/qt/src/mln/image.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/qt/src/mln/number_format.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/qt/src/mln/local_glyph_rasterizer.cpp
|
||||
|
||||
23
platform/qt/src/mln/i18n.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <mln/util/i18n.hpp>
|
||||
|
||||
#include <QChar>
|
||||
|
||||
namespace mln {
|
||||
namespace util {
|
||||
namespace i18n {
|
||||
|
||||
bool isDigit(char16_t chr) {
|
||||
return QChar(chr).isDigit();
|
||||
}
|
||||
|
||||
bool isUppercase(char16_t chr) {
|
||||
return QChar(chr).isUpper();
|
||||
}
|
||||
|
||||
bool isPunctuationOrSymbol(char16_t chr) {
|
||||
return QChar(chr).isPunct() || QChar(chr).isSymbol();
|
||||
}
|
||||
|
||||
} // namespace i18n
|
||||
} // namespace util
|
||||
} // namespace mln
|
||||
@@ -78,6 +78,7 @@ target_sources(
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/async_task.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/compression.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/filesystem.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/i18n.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/image.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/jpeg_reader.cpp
|
||||
${PROJECT_SOURCE_DIR}/platform/default/src/mln/util/webp_reader.cpp
|
||||
|
||||
@@ -405,6 +405,74 @@ std::set<std::size_t> determineLineBreaks(const TaggedString& logicalInput,
|
||||
return leastBadBreaks(evaluateBreak(logicalInput.length(), currentX, targetWidth, potentialBreaks, 0, true));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t maxUprightLetterRun = 3;
|
||||
|
||||
// Longer uppercase runs are words, while short runs such as "JR" and "A1" are codes.
|
||||
bool runIsUpright(const TaggedString& line, std::size_t start, std::size_t end) {
|
||||
bool hasDigit = false;
|
||||
bool isNumber = true;
|
||||
bool isShortUppercaseCode = end - start <= maxUprightLetterRun;
|
||||
for (std::size_t i = start; i < end; ++i) {
|
||||
const auto chr = line.getCharCodeAt(i);
|
||||
const bool isDigit = util::i18n::isDigit(chr);
|
||||
hasDigit |= isDigit;
|
||||
isNumber &= isDigit || util::i18n::isPunctuationOrSymbol(chr);
|
||||
isShortUppercaseCode &= isDigit || util::i18n::isUppercase(chr);
|
||||
}
|
||||
return (hasDigit && isNumber) || isShortUppercaseCode;
|
||||
}
|
||||
|
||||
// Punctuation such as the hyphen in "1-2" needs a vertical presentation form
|
||||
// now that its neighbors are upright. The glyph is replaced during shaping.
|
||||
void markSurroundedPunctuationUpright(const TaggedString& line, std::vector<bool>& verticals) {
|
||||
const auto isText = [&](std::size_t i) {
|
||||
const auto& section = line.getSection(i);
|
||||
return section.type == GlyphIDType::FontPBF && !section.imageID;
|
||||
};
|
||||
for (std::size_t i = 0; i < line.length(); ++i) {
|
||||
if (!verticals[i] && isText(i) && util::i18n::verticalizePunctuation(line.getCharCodeAt(i)) &&
|
||||
(i == 0 || verticals[i - 1]) && (i + 1 == line.length() || verticals[i + 1])) {
|
||||
verticals[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep words and combining marks together; images and shaped glyph IDs delimit runs.
|
||||
std::vector<bool> determineLineVerticals(const TaggedString& line) {
|
||||
std::vector<bool> verticals(line.length(), false);
|
||||
const auto isText = [&](std::size_t i) {
|
||||
const auto& section = line.getSection(i);
|
||||
return section.type == GlyphIDType::FontPBF && !section.imageID;
|
||||
};
|
||||
for (std::size_t i = 0; i < line.length(); ++i) {
|
||||
verticals[i] = isText(i) && util::i18n::hasUprightVerticalOrientation(line.getCharCodeAt(i));
|
||||
}
|
||||
|
||||
const auto isRunCharacter = [&](std::size_t i) {
|
||||
return isText(i) && !verticals[i] && !util::i18n::isWhitespace(line.getCharCodeAt(i));
|
||||
};
|
||||
for (std::size_t start = 0; start < line.length(); ++start) {
|
||||
if (!isRunCharacter(start)) continue;
|
||||
std::size_t end = start + 1;
|
||||
while (end < line.length() && isRunCharacter(end)) ++end;
|
||||
if (runIsUpright(line, start, end)) {
|
||||
for (std::size_t i = start; i < end; ++i) {
|
||||
const auto chr = line.getCharCodeAt(i);
|
||||
verticals[i] = (util::i18n::isUppercase(chr) || util::i18n::isDigit(chr)) &&
|
||||
!util::i18n::isCharInComplexShapingScript(chr);
|
||||
}
|
||||
}
|
||||
start = end - 1;
|
||||
}
|
||||
|
||||
markSurroundedPunctuationUpright(line, verticals);
|
||||
return verticals;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void shapeLines(Shaping& shaping,
|
||||
std::vector<TaggedString>& lines,
|
||||
const float spacing,
|
||||
@@ -443,6 +511,10 @@ void shapeLines(Shaping& shaping,
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto lineVerticals = writingMode == WritingModeType::Vertical && !allowVerticalPlacement
|
||||
? determineLineVerticals(line)
|
||||
: std::vector<bool>{};
|
||||
|
||||
for (std::size_t i = 0; i < line.length(); i++) {
|
||||
const std::size_t sectionIndex = line.getSectionIndex(i);
|
||||
const SectionOptions& section = line.sectionAt(sectionIndex);
|
||||
@@ -465,15 +537,19 @@ void shapeLines(Shaping& shaping,
|
||||
double sectionScale = section.scale;
|
||||
assert(sectionScale);
|
||||
|
||||
const bool vertical = !(
|
||||
writingMode == WritingModeType::Horizontal || codePoint.complex.type != GlyphIDType::FontPBF ||
|
||||
// Don't verticalize glyphs that have no upright orientation
|
||||
// if vertical placement is disabled.
|
||||
(!allowVerticalPlacement && !util::i18n::hasUprightVerticalOrientation(codePoint)) ||
|
||||
// If vertical placement is ebabled, don't verticalize glyphs
|
||||
// that are from complex text layout script, or whitespaces.
|
||||
(allowVerticalPlacement &&
|
||||
(util::i18n::isWhitespace(codePoint) || util::i18n::isCharInComplexShapingScript(codePoint))));
|
||||
const bool vertical = !lineVerticals.empty()
|
||||
? lineVerticals[i]
|
||||
: writingMode != WritingModeType::Horizontal &&
|
||||
codePoint.complex.type == GlyphIDType::FontPBF &&
|
||||
(allowVerticalPlacement
|
||||
? !util::i18n::isWhitespace(codePoint) &&
|
||||
!util::i18n::isCharInComplexShapingScript(codePoint)
|
||||
: util::i18n::hasUprightVerticalOrientation(codePoint));
|
||||
if (!lineVerticals.empty() && vertical && !util::i18n::hasUprightVerticalOrientation(codePoint)) {
|
||||
if (const char16_t replacement = util::i18n::verticalizePunctuation(codePoint)) {
|
||||
codePoint = GlyphID(replacement, section.type);
|
||||
}
|
||||
}
|
||||
|
||||
if (!section.imageID) {
|
||||
auto glyphPositionMap = glyphPositions.find(section.fontStackHash);
|
||||
|
||||
@@ -87,6 +87,12 @@ bool isCharInComplexShapingScript(char16_t chr);
|
||||
|
||||
bool isWhitespace(char16_t chr);
|
||||
|
||||
bool isDigit(char16_t chr);
|
||||
|
||||
bool isUppercase(char16_t chr);
|
||||
|
||||
bool isPunctuationOrSymbol(char16_t chr);
|
||||
|
||||
bool isVariationSelector1(char16_t chr);
|
||||
|
||||
} // namespace i18n
|
||||
|
||||
BIN
test/fixtures/local_glyphs/no_local/expected.png
vendored
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
BIN
test/fixtures/local_glyphs/ping_fang/expected.png
vendored
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@@ -5,10 +5,103 @@
|
||||
#include <mln/text/tagged_string.hpp>
|
||||
#include <mln/text/shaping.hpp>
|
||||
#include <mln/util/constants.hpp>
|
||||
#include <mln/util/i18n.hpp>
|
||||
|
||||
#include <optional>
|
||||
|
||||
using namespace mln;
|
||||
using namespace util;
|
||||
|
||||
namespace {
|
||||
|
||||
void addStubGlyph(GlyphMap& glyphs,
|
||||
GlyphPositions& glyphPositions,
|
||||
const FontStackHash fontStackHash,
|
||||
const char16_t codePoint,
|
||||
const GlyphIDType type) {
|
||||
const GlyphID glyphID(codePoint, type);
|
||||
GlyphMetrics metrics;
|
||||
metrics.width = 10;
|
||||
metrics.height = 18;
|
||||
metrics.left = 1;
|
||||
metrics.top = -8;
|
||||
metrics.advance = 12;
|
||||
|
||||
Glyph glyph;
|
||||
glyph.id = glyphID;
|
||||
glyph.metrics = metrics;
|
||||
glyphs[fontStackHash][glyphID] = Immutable<Glyph>(makeMutable<Glyph>(std::move(glyph)));
|
||||
|
||||
GlyphPosition position;
|
||||
position.metrics = metrics;
|
||||
glyphPositions[fontStackHash][glyphID] = position;
|
||||
}
|
||||
|
||||
void addStubGlyphs(const TaggedString& string, GlyphMap& glyphs, GlyphPositions& glyphPositions) {
|
||||
for (std::size_t i = 0; i < string.length(); ++i) {
|
||||
const auto codePoint = string.getCharCodeAt(i);
|
||||
|
||||
const auto& section = string.getSection(i);
|
||||
if (section.imageID) continue;
|
||||
|
||||
addStubGlyph(glyphs, glyphPositions, section.fontStackHash, codePoint, section.type);
|
||||
if (section.type == GlyphIDType::FontPBF) {
|
||||
if (const auto verticalCodePoint = util::i18n::verticalizePunctuation(codePoint)) {
|
||||
addStubGlyph(glyphs, glyphPositions, section.fontStackHash, verticalCodePoint, section.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shaping shapeTaggedText(const TaggedString& string,
|
||||
const WritingModeType writingMode = WritingModeType::Vertical,
|
||||
const bool allowVerticalPlacement = false,
|
||||
ImagePositions imagePositions = {}) {
|
||||
GlyphMap glyphs;
|
||||
GlyphPositions glyphPositions;
|
||||
addStubGlyphs(string, glyphs, glyphPositions);
|
||||
|
||||
BiDi bidi;
|
||||
return getShaping(string,
|
||||
4096.0f,
|
||||
ONE_EM,
|
||||
style::SymbolAnchorType::TopLeft,
|
||||
style::TextJustifyType::Left,
|
||||
0.0f,
|
||||
{{0.0f, 0.0f}},
|
||||
writingMode,
|
||||
bidi,
|
||||
glyphs,
|
||||
glyphPositions,
|
||||
imagePositions,
|
||||
ONE_EM,
|
||||
ONE_EM,
|
||||
allowVerticalPlacement);
|
||||
}
|
||||
|
||||
Shaping shapeLineLabel(const std::u16string& text,
|
||||
const WritingModeType writingMode = WritingModeType::Vertical,
|
||||
const bool allowVerticalPlacement = false) {
|
||||
const FontStack fontStack{{"font-stack"}};
|
||||
return shapeTaggedText(TaggedString(text, SectionOptions(1.0, fontStack, GlyphIDType::FontPBF, 0)),
|
||||
writingMode,
|
||||
allowVerticalPlacement);
|
||||
}
|
||||
|
||||
using GlyphOrientation = std::pair<char16_t, std::string>;
|
||||
|
||||
std::vector<GlyphOrientation> getGlyphOrientations(const Shaping& shaping) {
|
||||
std::vector<GlyphOrientation> result;
|
||||
for (const auto& line : shaping.positionedLines) {
|
||||
for (const auto& glyph : line.positionedGlyphs) {
|
||||
result.emplace_back(glyph.glyph.complex.code, glyph.vertical ? "upright" : "along-line");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Shaping, ZWSP) {
|
||||
GlyphPosition glyphPosition;
|
||||
glyphPosition.metrics.width = 18;
|
||||
@@ -104,6 +197,485 @@ TEST(Shaping, ZWSP) {
|
||||
}
|
||||
}
|
||||
|
||||
// Regression cases ported from maplibre-gl-js#8205 for maplibre-native#4565.
|
||||
TEST(Shaping, VerticalDigitsBetweenCJKCharacters) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"반포대로21길");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'반', "upright"},
|
||||
{u'포', "upright"},
|
||||
{u'대', "upright"},
|
||||
{u'로', "upright"},
|
||||
{u'2', "upright"},
|
||||
{u'1', "upright"},
|
||||
{u'길', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalTrailingDigits) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"身什戰33");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'身', "upright"},
|
||||
{u'什', "upright"},
|
||||
{u'戰', "upright"},
|
||||
{u'3', "upright"},
|
||||
{u'3', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalDigitsOfAnyLength) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道1234号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'1', "upright"},
|
||||
{u'2', "upright"},
|
||||
{u'3', "upright"},
|
||||
{u'4', "upright"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalWhitespaceSeparatedDigit) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"身什戰 1");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'身', "upright"},
|
||||
{u'什', "upright"},
|
||||
{u'戰', "upright"},
|
||||
{u' ', "along-line"},
|
||||
{u'1', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalSingleUppercaseLetter) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道A号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'A', "upright"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalDecomposedLatinLetter) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道e\u0301号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'e', "along-line"},
|
||||
{u'\u0301', "along-line"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalLowercaseGreekLetter) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道α号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'α', "along-line"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalNonLatinUppercaseCode) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道\u041C1号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'\u041C', "upright"},
|
||||
{u'1', "upright"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalComplexScript) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道ب号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'ب', "along-line"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalProlongedSoundMark) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"札幌タワー");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'札', "upright"},
|
||||
{u'幌', "upright"},
|
||||
{u'タ', "upright"},
|
||||
{u'ワ', "upright"},
|
||||
{u'ー', "along-line"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalWaveDashBetweenDigits) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"身什戰1〜2");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'身', "upright"},
|
||||
{u'什', "upright"},
|
||||
{u'戰', "upright"},
|
||||
{u'1', "upright"},
|
||||
{u'〜', "along-line"},
|
||||
{u'2', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalLowercaseLatinWord) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"two 身什戰");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u't', "along-line"},
|
||||
{u'w', "along-line"},
|
||||
{u'o', "along-line"},
|
||||
{u' ', "along-line"},
|
||||
{u'身', "upright"},
|
||||
{u'什', "upright"},
|
||||
{u'戰', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalMixedAlphanumericCode) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"身什戰A1");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'身', "upright"},
|
||||
{u'什', "upright"},
|
||||
{u'戰', "upright"},
|
||||
{u'A', "upright"},
|
||||
{u'1', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalLatinWordAdjoiningCJK) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"銀座Ginza通り");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'銀', "upright"},
|
||||
{u'座', "upright"},
|
||||
{u'G', "along-line"},
|
||||
{u'i', "along-line"},
|
||||
{u'n', "along-line"},
|
||||
{u'z', "along-line"},
|
||||
{u'a', "along-line"},
|
||||
{u'通', "upright"},
|
||||
{u'り', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalLongUppercaseWord) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"ヴィラ ISHIKAWA");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'ヴ', "upright"},
|
||||
{u'ィ', "upright"},
|
||||
{u'ラ', "upright"},
|
||||
{u' ', "along-line"},
|
||||
{u'I', "along-line"},
|
||||
{u'S', "along-line"},
|
||||
{u'H', "along-line"},
|
||||
{u'I', "along-line"},
|
||||
{u'K', "along-line"},
|
||||
{u'A', "along-line"},
|
||||
{u'W', "along-line"},
|
||||
{u'A', "along-line"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalShortUppercaseCode) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"JR山手線");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'J', "upright"},
|
||||
{u'R', "upright"},
|
||||
{u'山', "upright"},
|
||||
{u'手', "upright"},
|
||||
{u'線', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalPunctuationBetweenDigits) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道1-2号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'1', "upright"},
|
||||
{u'︲', "upright"},
|
||||
{u'2', "upright"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalPunctuationInRotatedRun) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道3.5km");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'3', "along-line"},
|
||||
{u'.', "along-line"},
|
||||
{u'5', "along-line"},
|
||||
{u'k', "along-line"},
|
||||
{u'm', "along-line"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalMissingGlyphPreservesIndices) {
|
||||
const FontStack fontStack{{"font-stack"}};
|
||||
const auto fontStackHash = FontStackHasher()(fontStack);
|
||||
const TaggedString text(u"what 国21号", SectionOptions(1.0, fontStack, GlyphIDType::FontPBF, 0));
|
||||
GlyphMap glyphs;
|
||||
GlyphPositions glyphPositions;
|
||||
addStubGlyphs(text, glyphs, glyphPositions);
|
||||
// Missing "w" must not shift the character index used for the remaining glyphs.
|
||||
glyphs.at(fontStackHash).erase(u'w');
|
||||
glyphPositions.at(fontStackHash).erase(u'w');
|
||||
|
||||
BiDi bidi;
|
||||
const auto shapedLineLabel = getShaping(text,
|
||||
4096.0f,
|
||||
ONE_EM,
|
||||
style::SymbolAnchorType::TopLeft,
|
||||
style::TextJustifyType::Left,
|
||||
0.0f,
|
||||
{{0.0f, 0.0f}},
|
||||
WritingModeType::Vertical,
|
||||
bidi,
|
||||
glyphs,
|
||||
glyphPositions,
|
||||
{},
|
||||
ONE_EM,
|
||||
ONE_EM,
|
||||
false);
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'h', "along-line"},
|
||||
{u'a', "along-line"},
|
||||
{u't', "along-line"},
|
||||
{u' ', "along-line"},
|
||||
{u'国', "upright"},
|
||||
{u'2', "upright"},
|
||||
{u'1', "upright"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, HorizontalGlyphOrientation) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"身什戰33", WritingModeType::Horizontal);
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'身', "along-line"},
|
||||
{u'什', "along-line"},
|
||||
{u'戰', "along-line"},
|
||||
{u'3', "along-line"},
|
||||
{u'3', "along-line"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, AllowedVerticalGlyphOrientation) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"two 身什戰", WritingModeType::Vertical, true);
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u't', "upright"},
|
||||
{u'w', "upright"},
|
||||
{u'o', "upright"},
|
||||
{u' ', "along-line"},
|
||||
{u'身', "upright"},
|
||||
{u'什', "upright"},
|
||||
{u'戰', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalThreeCharacterUppercaseCode) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道ABC号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'A', "upright"},
|
||||
{u'B', "upright"},
|
||||
{u'C', "upright"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalFourCharacterUppercaseWord) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道ABCD号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'A', "along-line"},
|
||||
{u'B', "along-line"},
|
||||
{u'C', "along-line"},
|
||||
{u'D', "along-line"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalBengaliDigits) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道১২号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'১', "upright"},
|
||||
{u'২', "upright"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalArabicIndicDigits) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道١٢号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'١', "along-line"},
|
||||
{u'٢', "along-line"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalDigitWithCombiningMark) {
|
||||
const auto shapedLineLabel = shapeLineLabel(u"国道1\u0301号");
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'国', "upright"},
|
||||
{u'道', "upright"},
|
||||
{u'1', "along-line"},
|
||||
{u'\u0301', "along-line"},
|
||||
{u'号', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalGlyphOrientationRespectsFormattedSections) {
|
||||
const FontStack fontStack{{"font-stack"}};
|
||||
TaggedString text;
|
||||
text.addTextSection(u"AB", 1.0, fontStack, GlyphIDType::FontPBF);
|
||||
text.addTextSection(u"CD", 0.5, fontStack, GlyphIDType::FontPBF);
|
||||
|
||||
const auto shapedLineLabel = shapeTaggedText(text);
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'A', "along-line"},
|
||||
{u'B', "along-line"},
|
||||
{u'C', "along-line"},
|
||||
{u'D', "along-line"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalGlyphOrientationDelimitsInlineImages) {
|
||||
const FontStack fontStack{{"font-stack"}};
|
||||
TaggedString text;
|
||||
text.addTextSection(u"AB", 1.0, fontStack, GlyphIDType::FontPBF);
|
||||
text.addImageSection("image");
|
||||
text.addTextSection(u"CD", 1.0, fontStack, GlyphIDType::FontPBF);
|
||||
|
||||
const style::Image::Impl image("image", PremultipliedImage({12, 24}), 1.0f);
|
||||
ImagePositions imagePositions;
|
||||
imagePositions.emplace("image", ImagePosition(Rect<uint16_t>(0, 0, 14, 26), image));
|
||||
|
||||
const auto shapedLineLabel = shapeTaggedText(text, WritingModeType::Vertical, false, std::move(imagePositions));
|
||||
ASSERT_EQ(shapedLineLabel.positionedLines.size(), 1u);
|
||||
ASSERT_EQ(shapedLineLabel.positionedLines.front().positionedGlyphs.size(), 5u);
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'A', "upright"},
|
||||
{u'B', "upright"},
|
||||
{u'\uE000', "along-line"}, // Inline image placeholder.
|
||||
{u'C', "upright"},
|
||||
{u'D', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
EXPECT_EQ(shapedLineLabel.positionedLines.front().positionedGlyphs[2].imageID, std::optional<std::string>("image"));
|
||||
EXPECT_TRUE(shapedLineLabel.iconsInText);
|
||||
}
|
||||
|
||||
TEST(Shaping, VerticalGlyphOrientationDelimitsNativeGlyphSections) {
|
||||
const FontStack fontStack{{"font-stack"}};
|
||||
const auto nativeType = genNewGlyphIDType("shaping-test-native", fontStack, {{0, 0xffff}});
|
||||
TaggedString text;
|
||||
text.addTextSection(u"AB", 1.0, fontStack, GlyphIDType::FontPBF);
|
||||
text.addTextSection(u"C", 1.0, fontStack, nativeType);
|
||||
text.addTextSection(u"D", 1.0, fontStack, GlyphIDType::FontPBF);
|
||||
|
||||
const auto shapedLineLabel = shapeTaggedText(text);
|
||||
const auto orientations = getGlyphOrientations(shapedLineLabel);
|
||||
const std::vector<GlyphOrientation> expected = {
|
||||
{u'A', "upright"},
|
||||
{u'B', "upright"},
|
||||
{u'C', "along-line"},
|
||||
{u'D', "upright"},
|
||||
};
|
||||
EXPECT_EQ(orientations, expected);
|
||||
}
|
||||
|
||||
void setupShapedText(Shaping& shapedText, float textSize) {
|
||||
const auto glyph = PositionedGlyph(32,
|
||||
0.0f,
|
||||
|
||||