From f8678421764c39d87fa7d372ee8fdb412e5def37 Mon Sep 17 00:00:00 2001 From: Anjana Sofia Vakil Date: Thu, 8 Aug 2019 00:28:37 +0700 Subject: [PATCH] Enable vertical point label placement via 'text-writing-mode' style property (#8399) * Add text-writing-mode layout property * Add render tests for text-writing-mode layout property * Skip render tests for text-writing-mode property * Revert "Skip render tests for text-writing-mode property" This reverts commit 069c6f01a9eee86ef2f2d4f74691ff0cf1bbc891. * Add 'text-writing-mode' style spec type & symbol layer prop * Add attributes to SymbolInstance & PlacedSymbol structs for vertical placement - Add placedOrientation attribute to PlacedSymbol - Add verticalTextBox attributes to SymbolInstance alongside textBox & iconBox * Rotate glyphs for vertical point labels if needed & correct for y-offset Port changes from gl-native@alexshalamov_wip_vertical_labels (shaping.cpp, quads.cpp glyphs.hpp) * Track writing modes and enable vertical placement in SymbolBucket - Add writingModes and allowVerticalPlacement properties to SymbolBucket - Compute writingModes from `text-writing-mode` style property - Determine glyph dependencies based on whether vertical placement is allowed - Rotate symbols as needed * Track vertical collision boxes in SymbolBucket, if needed * Add vertical text shaping during symbol layout, if needed * Add vertical text collision feature during symbol layout, if needed * Hide unused orientation glyphs when drawing symbols - Add updateVerticalLabels to hide glyphs for unused orientations - Modify updateVariableAnchors to hide unused orientation glyphs when using vertical labels and variable anchors * Place correct orientation variant per chosen writing modes - Attempt to place vertical/horizontal boxes for each symbol according to writing mode order of preference - Integrate with variable-anchor placement to try each anchor/orientation combination possible - Prefer previously placed orientations & anchors - Hide unplaced orientations * Fix symbol unit tests Add yOffset property to shaping objects * Organize text-writing-mode render tests to separate line/point tests * Add more render tests for vertical CJK point labels Add tests for vertical placement of point labels with: - CJK text with punctuation - multiline mixed CJK/latin text - CJK with embedded Arabic RTL text * Tiny clean up * Minor fixes per reviewer comments * Revert "Update yarn.lock" per reviewer comment This reverts commit 3ff7480f44a683ce772d35cb6a5631972e1ff5c0. * Deduplicate values during SymbolStyleLayer creation (not SymbolBucket) * Add type annotations for variable anchor box placement * Use .indexOf instead of .includes for IE compat; lint fix * Render half-width glyphs in upright orientation This change forces glyphs whose natural orientation in vertical writing mode is 'sideways' to be rendered in upright orientation (only for non complex text layouts). This is different compared to W3C / browser behavior that is by default, renders glyphs in their respective natural orientation. In the future, there might need to add a new layout property that would control glyph orientation separately (e.g., text-glyph-orientation: natural | upright). * Update mixed-multiline-vertical-horizontal-mode expected results --- src/data/array_types.js | 106 +++++---- src/data/bucket/symbol_attributes.js | 3 + src/data/bucket/symbol_bucket.js | 38 ++- src/render/draw_symbol.js | 33 ++- src/style-spec/reference/v8.json | 42 ++++ src/style-spec/types.js | 1 + src/style/style_layer/symbol_style_layer.js | 14 ++ .../symbol_style_layer_properties.js | 2 + src/symbol/placement.js | 219 +++++++++++++++--- src/symbol/quads.js | 41 +++- src/symbol/shaping.js | 32 ++- src/symbol/symbol_layout.js | 40 +++- src/util/script_detection.js | 8 + test/expected/text-shaping-default.json | 1 + test/expected/text-shaping-linebreak.json | 1 + test/expected/text-shaping-newline.json | 1 + .../text-shaping-newlines-in-middle.json | 1 + test/expected/text-shaping-null.json | 1 + test/expected/text-shaping-spacing.json | 1 + .../text-shaping-zero-width-space.json | 3 +- .../chinese-punctuation/expected.png | Bin .../chinese-punctuation/style.json | 0 .../{ => line_label}/chinese/expected.png | Bin .../{ => line_label}/chinese/style.json | 0 .../{ => line_label}/latin/expected.png | Bin .../{ => line_label}/latin/style.json | 0 .../{ => line_label}/mixed/expected.png | Bin .../{ => line_label}/mixed/style.json | 0 .../cjk-arabic-vertical-mode/expected.png | Bin 0 -> 6427 bytes .../cjk-arabic-vertical-mode/style.json | 68 ++++++ .../cjk-horizontal-vertical-mode/expected.png | Bin 0 -> 3409 bytes .../cjk-horizontal-vertical-mode/style.json | 68 ++++++ .../expected.png | Bin 0 -> 3044 bytes .../style.json | 58 +++++ .../expected.png | Bin 0 -> 3888 bytes .../cjk-punctuation-vertical-mode/style.json | 65 ++++++ .../expected.png | Bin 0 -> 3570 bytes .../style.json | 80 +++++++ .../expected.png | Bin 0 -> 3321 bytes .../style.json | 70 ++++++ .../cjk-vertical-horizontal-mode/expected.png | Bin 0 -> 3269 bytes .../cjk-vertical-horizontal-mode/style.json | 68 ++++++ .../cjk-vertical-mode/expected.png | Bin 0 -> 2275 bytes .../point_label/cjk-vertical-mode/style.json | 68 ++++++ .../latin-vertical-mode/expected.png | Bin 0 -> 902 bytes .../latin-vertical-mode/style.json | 48 ++++ .../expected.png | Bin 0 -> 4205 bytes .../style.json | 58 +++++ yarn.lock | 2 +- 49 files changed, 1125 insertions(+), 116 deletions(-) rename test/integration/render-tests/text-writing-mode/{ => line_label}/chinese-punctuation/expected.png (100%) rename test/integration/render-tests/text-writing-mode/{ => line_label}/chinese-punctuation/style.json (100%) rename test/integration/render-tests/text-writing-mode/{ => line_label}/chinese/expected.png (100%) rename test/integration/render-tests/text-writing-mode/{ => line_label}/chinese/style.json (100%) rename test/integration/render-tests/text-writing-mode/{ => line_label}/latin/expected.png (100%) rename test/integration/render-tests/text-writing-mode/{ => line_label}/latin/style.json (100%) rename test/integration/render-tests/text-writing-mode/{ => line_label}/mixed/expected.png (100%) rename test/integration/render-tests/text-writing-mode/{ => line_label}/mixed/style.json (100%) create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-arabic-vertical-mode/expected.png create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-arabic-vertical-mode/style.json create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-horizontal-vertical-mode/expected.png create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-horizontal-vertical-mode/style.json create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-multiline-vertical-horizontal-mode/expected.png create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-multiline-vertical-horizontal-mode/style.json create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-punctuation-vertical-mode/expected.png create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-punctuation-vertical-mode/style.json create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-horizontal-mode/expected.png create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-horizontal-mode/style.json create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-mode/expected.png create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-mode/style.json create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-vertical-horizontal-mode/expected.png create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-vertical-horizontal-mode/style.json create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-vertical-mode/expected.png create mode 100644 test/integration/render-tests/text-writing-mode/point_label/cjk-vertical-mode/style.json create mode 100644 test/integration/render-tests/text-writing-mode/point_label/latin-vertical-mode/expected.png create mode 100644 test/integration/render-tests/text-writing-mode/point_label/latin-vertical-mode/style.json create mode 100644 test/integration/render-tests/text-writing-mode/point_label/mixed-multiline-vertical-horizontal-mode/expected.png create mode 100644 test/integration/render-tests/text-writing-mode/point_label/mixed-multiline-vertical-horizontal-mode/style.json diff --git a/src/data/array_types.js b/src/data/array_types.js index 6a215e5574a..a63a0bd5575 100644 --- a/src/data/array_types.js +++ b/src/data/array_types.js @@ -420,12 +420,12 @@ register('StructArrayLayout2ub2f12', StructArrayLayout2ub2f12); * [8]: Uint32[3] * [20]: Uint16[3] * [28]: Float32[2] - * [36]: Uint8[2] + * [36]: Uint8[3] * [40]: Uint32[1] * * @private */ -class StructArrayLayout2i2ui3ul3ui2f2ub1ul44 extends StructArray { +class StructArrayLayout2i2ui3ul3ui2f3ub1ul44 extends StructArray { uint8: Uint8Array; int16: Int16Array; uint16: Uint16Array; @@ -440,13 +440,13 @@ class StructArrayLayout2i2ui3ul3ui2f2ub1ul44 extends StructArray { this.float32 = new Float32Array(this.arrayBuffer); } - emplaceBack(v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number) { + emplaceBack(v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number) { const i = this.length; this.resize(i + 1); - return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14); + return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); } - emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number) { + emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number) { const o2 = i * 22; const o4 = i * 11; const o1 = i * 44; @@ -464,24 +464,25 @@ class StructArrayLayout2i2ui3ul3ui2f2ub1ul44 extends StructArray { this.float32[o4 + 8] = v11; this.uint8[o1 + 36] = v12; this.uint8[o1 + 37] = v13; - this.uint32[o4 + 10] = v14; + this.uint8[o1 + 38] = v14; + this.uint32[o4 + 10] = v15; return i; } } -StructArrayLayout2i2ui3ul3ui2f2ub1ul44.prototype.bytesPerElement = 44; -register('StructArrayLayout2i2ui3ul3ui2f2ub1ul44', StructArrayLayout2i2ui3ul3ui2f2ub1ul44); +StructArrayLayout2i2ui3ul3ui2f3ub1ul44.prototype.bytesPerElement = 44; +register('StructArrayLayout2i2ui3ul3ui2f3ub1ul44', StructArrayLayout2i2ui3ul3ui2f3ub1ul44); /** * Implementation of the StructArray layout: * [0]: Int16[6] - * [12]: Uint16[9] - * [32]: Uint32[1] - * [36]: Float32[2] + * [12]: Uint16[11] + * [36]: Uint32[1] + * [40]: Float32[2] * * @private */ -class StructArrayLayout6i9ui1ul2f44 extends StructArray { +class StructArrayLayout6i11ui1ul2f48 extends StructArray { uint8: Uint8Array; int16: Int16Array; uint16: Uint16Array; @@ -496,15 +497,15 @@ class StructArrayLayout6i9ui1ul2f44 extends StructArray { this.float32 = new Float32Array(this.arrayBuffer); } - emplaceBack(v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number, v16: number, v17: number) { + emplaceBack(v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number, v16: number, v17: number, v18: number, v19: number) { const i = this.length; this.resize(i + 1); - return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17); + return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19); } - emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number, v16: number, v17: number) { - const o2 = i * 22; - const o4 = i * 11; + emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number, v16: number, v17: number, v18: number, v19: number) { + const o2 = i * 24; + const o4 = i * 12; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; this.int16[o2 + 2] = v2; @@ -520,15 +521,17 @@ class StructArrayLayout6i9ui1ul2f44 extends StructArray { this.uint16[o2 + 12] = v12; this.uint16[o2 + 13] = v13; this.uint16[o2 + 14] = v14; - this.uint32[o4 + 8] = v15; - this.float32[o4 + 9] = v16; - this.float32[o4 + 10] = v17; + this.uint16[o2 + 15] = v15; + this.uint16[o2 + 16] = v16; + this.uint32[o4 + 9] = v17; + this.float32[o4 + 10] = v18; + this.float32[o4 + 11] = v19; return i; } } -StructArrayLayout6i9ui1ul2f44.prototype.bytesPerElement = 44; -register('StructArrayLayout6i9ui1ul2f44', StructArrayLayout6i9ui1ul2f44); +StructArrayLayout6i11ui1ul2f48.prototype.bytesPerElement = 48; +register('StructArrayLayout6i11ui1ul2f48', StructArrayLayout6i11ui1ul2f48); /** * Implementation of the StructArray layout: @@ -867,6 +870,7 @@ class PlacedSymbolStruct extends Struct { lineOffsetX: number; lineOffsetY: number; writingMode: number; + placedOrientation: number; hidden: number; crossTileID: number; get anchorX() { return this._structArray.int16[this._pos2 + 0]; } @@ -895,8 +899,10 @@ class PlacedSymbolStruct extends Struct { set lineOffsetY(x: number) { this._structArray.float32[this._pos4 + 8] = x; } get writingMode() { return this._structArray.uint8[this._pos1 + 36]; } set writingMode(x: number) { this._structArray.uint8[this._pos1 + 36] = x; } - get hidden() { return this._structArray.uint8[this._pos1 + 37]; } - set hidden(x: number) { this._structArray.uint8[this._pos1 + 37] = x; } + get placedOrientation() { return this._structArray.uint8[this._pos1 + 37]; } + set placedOrientation(x: number) { this._structArray.uint8[this._pos1 + 37] = x; } + get hidden() { return this._structArray.uint8[this._pos1 + 38]; } + set hidden(x: number) { this._structArray.uint8[this._pos1 + 38] = x; } get crossTileID() { return this._structArray.uint32[this._pos4 + 10]; } set crossTileID(x: number) { this._structArray.uint32[this._pos4 + 10] = x; } } @@ -908,7 +914,7 @@ export type PlacedSymbol = PlacedSymbolStruct; /** * @private */ -export class PlacedSymbolArray extends StructArrayLayout2i2ui3ul3ui2f2ub1ul44 { +export class PlacedSymbolArray extends StructArrayLayout2i2ui3ul3ui2f3ub1ul44 { /** * Return the PlacedSymbolStruct at the given location in the array. * @param {number} index The index of the element. @@ -932,6 +938,8 @@ class SymbolInstanceStruct extends Struct { key: number; textBoxStartIndex: number; textBoxEndIndex: number; + verticalTextBoxStartIndex: number; + verticalTextBoxEndIndex: number; iconBoxStartIndex: number; iconBoxEndIndex: number; featureIndex: number; @@ -959,34 +967,38 @@ class SymbolInstanceStruct extends Struct { set textBoxStartIndex(x: number) { this._structArray.uint16[this._pos2 + 7] = x; } get textBoxEndIndex() { return this._structArray.uint16[this._pos2 + 8]; } set textBoxEndIndex(x: number) { this._structArray.uint16[this._pos2 + 8] = x; } - get iconBoxStartIndex() { return this._structArray.uint16[this._pos2 + 9]; } - set iconBoxStartIndex(x: number) { this._structArray.uint16[this._pos2 + 9] = x; } - get iconBoxEndIndex() { return this._structArray.uint16[this._pos2 + 10]; } - set iconBoxEndIndex(x: number) { this._structArray.uint16[this._pos2 + 10] = x; } - get featureIndex() { return this._structArray.uint16[this._pos2 + 11]; } - set featureIndex(x: number) { this._structArray.uint16[this._pos2 + 11] = x; } - get numHorizontalGlyphVertices() { return this._structArray.uint16[this._pos2 + 12]; } - set numHorizontalGlyphVertices(x: number) { this._structArray.uint16[this._pos2 + 12] = x; } - get numVerticalGlyphVertices() { return this._structArray.uint16[this._pos2 + 13]; } - set numVerticalGlyphVertices(x: number) { this._structArray.uint16[this._pos2 + 13] = x; } - get numIconVertices() { return this._structArray.uint16[this._pos2 + 14]; } - set numIconVertices(x: number) { this._structArray.uint16[this._pos2 + 14] = x; } - get crossTileID() { return this._structArray.uint32[this._pos4 + 8]; } - set crossTileID(x: number) { this._structArray.uint32[this._pos4 + 8] = x; } - get textBoxScale() { return this._structArray.float32[this._pos4 + 9]; } - set textBoxScale(x: number) { this._structArray.float32[this._pos4 + 9] = x; } - get radialTextOffset() { return this._structArray.float32[this._pos4 + 10]; } - set radialTextOffset(x: number) { this._structArray.float32[this._pos4 + 10] = x; } + get verticalTextBoxStartIndex() { return this._structArray.uint16[this._pos2 + 9]; } + set verticalTextBoxStartIndex(x: number) { this._structArray.uint16[this._pos2 + 9] = x; } + get verticalTextBoxEndIndex() { return this._structArray.uint16[this._pos2 + 10]; } + set verticalTextBoxEndIndex(x: number) { this._structArray.uint16[this._pos2 + 10] = x; } + get iconBoxStartIndex() { return this._structArray.uint16[this._pos2 + 11]; } + set iconBoxStartIndex(x: number) { this._structArray.uint16[this._pos2 + 11] = x; } + get iconBoxEndIndex() { return this._structArray.uint16[this._pos2 + 12]; } + set iconBoxEndIndex(x: number) { this._structArray.uint16[this._pos2 + 12] = x; } + get featureIndex() { return this._structArray.uint16[this._pos2 + 13]; } + set featureIndex(x: number) { this._structArray.uint16[this._pos2 + 13] = x; } + get numHorizontalGlyphVertices() { return this._structArray.uint16[this._pos2 + 14]; } + set numHorizontalGlyphVertices(x: number) { this._structArray.uint16[this._pos2 + 14] = x; } + get numVerticalGlyphVertices() { return this._structArray.uint16[this._pos2 + 15]; } + set numVerticalGlyphVertices(x: number) { this._structArray.uint16[this._pos2 + 15] = x; } + get numIconVertices() { return this._structArray.uint16[this._pos2 + 16]; } + set numIconVertices(x: number) { this._structArray.uint16[this._pos2 + 16] = x; } + get crossTileID() { return this._structArray.uint32[this._pos4 + 9]; } + set crossTileID(x: number) { this._structArray.uint32[this._pos4 + 9] = x; } + get textBoxScale() { return this._structArray.float32[this._pos4 + 10]; } + set textBoxScale(x: number) { this._structArray.float32[this._pos4 + 10] = x; } + get radialTextOffset() { return this._structArray.float32[this._pos4 + 11]; } + set radialTextOffset(x: number) { this._structArray.float32[this._pos4 + 11] = x; } } -SymbolInstanceStruct.prototype.size = 44; +SymbolInstanceStruct.prototype.size = 48; export type SymbolInstance = SymbolInstanceStruct; /** * @private */ -export class SymbolInstanceArray extends StructArrayLayout6i9ui1ul2f44 { +export class SymbolInstanceArray extends StructArrayLayout6i11ui1ul2f48 { /** * Return the SymbolInstanceStruct at the given location in the array. * @param {number} index The index of the element. @@ -1108,8 +1120,8 @@ export { StructArrayLayout6i1ul2ui2i24, StructArrayLayout2i2i2i12, StructArrayLayout2ub2f12, - StructArrayLayout2i2ui3ul3ui2f2ub1ul44, - StructArrayLayout6i9ui1ul2f44, + StructArrayLayout2i2ui3ul3ui2f3ub1ul44, + StructArrayLayout6i11ui1ul2f48, StructArrayLayout1f4, StructArrayLayout3i6, StructArrayLayout1ul2ui8, diff --git a/src/data/bucket/symbol_attributes.js b/src/data/bucket/symbol_attributes.js index dfe10cbdaed..0700270077f 100644 --- a/src/data/bucket/symbol_attributes.js +++ b/src/data/bucket/symbol_attributes.js @@ -71,6 +71,7 @@ export const placement = createLayout([ { type: 'Float32', name: 'lineOffsetX' }, { type: 'Float32', name: 'lineOffsetY' }, { type: 'Uint8', name: 'writingMode' }, + { type: 'Uint8', name: 'placedOrientation' }, { type: 'Uint8', name: 'hidden' }, { type: 'Uint32', name: 'crossTileID'} ]); @@ -85,6 +86,8 @@ export const symbolInstance = createLayout([ { type: 'Uint16', name: 'key' }, { type: 'Uint16', name: 'textBoxStartIndex' }, { type: 'Uint16', name: 'textBoxEndIndex' }, + { type: 'Uint16', name: 'verticalTextBoxStartIndex' }, + { type: 'Uint16', name: 'verticalTextBoxEndIndex' }, { type: 'Uint16', name: 'iconBoxStartIndex' }, { type: 'Uint16', name: 'iconBoxEndIndex' }, { type: 'Uint16', name: 'featureIndex' }, diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index 8ce7a9a8def..12c13c2afdc 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -26,6 +26,7 @@ import { TriangleIndexArray, LineIndexArray } from '../index_array_type'; import transformText from '../../symbol/transform_text'; import mergeLines from '../../symbol/mergelines'; import {allowsVerticalWritingMode} from '../../util/script_detection'; +import { WritingMode } from '../../symbol/shaping'; import loadGeometry from '../load_geometry'; import mvt from '@mapbox/vector-tile'; const vectorTileFeatureTypes = mvt.VectorTileFeature.types; @@ -64,9 +65,11 @@ export type SingleCollisionBox = { export type CollisionArrays = { textBox?: SingleCollisionBox; + verticalTextBox?: SingleCollisionBox; iconBox?: SingleCollisionBox; textCircles?: Array; textFeatureIndex?: number; + verticalTextFeatureIndex?: number; iconFeatureIndex?: number; }; @@ -292,6 +295,8 @@ class SymbolBucket implements Bucket { sourceLayerIndex: number; sourceID: string; symbolInstanceIndexes: Array; + writingModes: Array; + allowVerticalPlacement: boolean; constructor(options: BucketParameters) { this.collisionBoxArray = options.collisionBoxArray; @@ -318,6 +323,10 @@ class SymbolBucket implements Bucket { this.sortFeaturesByY = zOrderByViewportY && (layout.get('text-allow-overlap') || layout.get('icon-allow-overlap') || layout.get('text-ignore-placement') || layout.get('icon-ignore-placement')); + if (layout.get('symbol-placement') === 'point') { + this.writingModes = layout.get('text-writing-mode').map(wm => WritingMode[wm]); + } + this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); this.sourceID = options.sourceID; @@ -335,10 +344,10 @@ class SymbolBucket implements Bucket { this.symbolInstances = new SymbolInstanceArray(); } - calculateGlyphDependencies(text: string, stack: {[number]: boolean}, textAlongLine: boolean, doesAllowVerticalWritingMode: boolean) { + calculateGlyphDependencies(text: string, stack: {[number]: boolean}, textAlongLine: boolean, allowVerticalPlacement: boolean, doesAllowVerticalWritingMode: boolean) { for (let i = 0; i < text.length; i++) { stack[text.charCodeAt(i)] = true; - if (textAlongLine && doesAllowVerticalWritingMode) { + if ((textAlongLine || allowVerticalPlacement) && doesAllowVerticalWritingMode) { const verticalChar = verticalizedCharacterMap[text.charAt(i)]; if (verticalChar) { stack[verticalChar.charCodeAt(0)] = true; @@ -422,11 +431,12 @@ class SymbolBucket implements Bucket { if (text) { const fontStack = textFont.evaluate(feature, {}).join(','); const textAlongLine = layout.get('text-rotation-alignment') === 'map' && layout.get('symbol-placement') !== 'point'; + this.allowVerticalPlacement = this.writingModes && this.writingModes.indexOf(WritingMode.vertical) >= 0; for (const section of text.sections) { const doesAllowVerticalWritingMode = allowsVerticalWritingMode(text.toString()); const sectionFont = section.fontStack || fontStack; const sectionStack = stacks[sectionFont] = stacks[sectionFont] || {}; - this.calculateGlyphDependencies(section.text, sectionStack, textAlongLine, doesAllowVerticalWritingMode); + this.calculateGlyphDependencies(section.text, sectionStack, textAlongLine, this.allowVerticalPlacement, doesAllowVerticalWritingMode); } } } @@ -523,6 +533,8 @@ class SymbolBucket implements Bucket { const glyphOffsetArrayStart = this.glyphOffsetArray.length; const vertexStartIndex = segment.vertexLength; + const angle = (this.allowVerticalPlacement && writingMode === WritingMode.vertical) ? Math.PI / 2 : 0; + for (const symbol of quads) { const tl = symbol.tl, @@ -539,7 +551,7 @@ class SymbolBucket implements Bucket { addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, bl.x, y + bl.y, tex.x, tex.y + tex.h, sizeVertex); addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, br.x, y + br.y, tex.x + tex.w, tex.y + tex.h, sizeVertex); - addDynamicAttributes(dynamicLayoutVertexArray, labelAnchor, 0); + addDynamicAttributes(dynamicLayoutVertexArray, labelAnchor, angle); indexArray.emplaceBack(index, index + 1, index + 2); indexArray.emplaceBack(index + 1, index + 2, index + 3); @@ -555,7 +567,10 @@ class SymbolBucket implements Bucket { lineStartIndex, lineLength, (labelAnchor.segment: any), sizeVertex ? sizeVertex[0] : 0, sizeVertex ? sizeVertex[1] : 0, lineOffset[0], lineOffset[1], - writingMode, (false: any), + writingMode, + // placedOrientation is null initially; will be updated to horizontal(1)/vertical(2) if placed + 0, + (false: any), // The crossTileID is only filled/used on the foreground for dynamic text anchors 0); @@ -628,13 +643,14 @@ class SymbolBucket implements Bucket { for (let i = 0; i < this.symbolInstances.length; i++) { const symbolInstance = this.symbolInstances.get(i); this.addDebugCollisionBoxes(symbolInstance.textBoxStartIndex, symbolInstance.textBoxEndIndex, symbolInstance); + this.addDebugCollisionBoxes(symbolInstance.verticalTextBoxStartIndex, symbolInstance.verticalTextBoxEndIndex, symbolInstance); this.addDebugCollisionBoxes(symbolInstance.iconBoxStartIndex, symbolInstance.iconBoxEndIndex, symbolInstance); } } // These flat arrays are meant to be quicker to iterate over than the source // CollisionBoxArray - _deserializeCollisionBoxesForSymbol(collisionBoxArray: CollisionBoxArray, textStartIndex: number, textEndIndex: number, iconStartIndex: number, iconEndIndex: number): CollisionArrays { + _deserializeCollisionBoxesForSymbol(collisionBoxArray: CollisionBoxArray, textStartIndex: number, textEndIndex: number, verticalTextStartIndex: number, verticalTextEndIndex: number, iconStartIndex: number, iconEndIndex: number): CollisionArrays { const collisionArrays = {}; for (let k = textStartIndex; k < textEndIndex; k++) { const box: CollisionBox = (collisionBoxArray.get(k): any); @@ -651,6 +667,14 @@ class SymbolBucket implements Bucket { collisionArrays.textCircles.push(box.anchorPointX, box.anchorPointY, box.radius, box.signedDistanceFromAnchor, used); } } + for (let k = verticalTextStartIndex; k < verticalTextEndIndex; k++) { + const box: CollisionBox = (collisionBoxArray.get(k): any); + if (box.radius === 0) { + collisionArrays.verticalTextBox = { x1: box.x1, y1: box.y1, x2: box.x2, y2: box.y2, anchorPointX: box.anchorPointX, anchorPointY: box.anchorPointY }; + collisionArrays.verticalTextFeatureIndex = box.featureIndex; + break; // Only one box allowed per instance + } + } for (let k = iconStartIndex; k < iconEndIndex; k++) { // An icon can only have one box now, so this indexing is a bit vestigial... const box: CollisionBox = (collisionBoxArray.get(k): any); @@ -671,6 +695,8 @@ class SymbolBucket implements Bucket { collisionBoxArray, symbolInstance.textBoxStartIndex, symbolInstance.textBoxEndIndex, + symbolInstance.verticalTextBoxStartIndex, + symbolInstance.verticalTextBoxEndIndex, symbolInstance.iconBoxStartIndex, symbolInstance.iconBoxEndIndex )); diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js index ed2578eb8f4..2c4e26f892e 100644 --- a/src/render/draw_symbol.js +++ b/src/render/draw_symbol.js @@ -14,7 +14,7 @@ import DepthMode from '../gl/depth_mode'; import CullFaceMode from '../gl/cull_face_mode'; import {addDynamicAttributes} from '../data/bucket/symbol_bucket'; -import { getAnchorAlignment } from '../symbol/shaping'; +import { getAnchorAlignment, WritingMode } from '../symbol/shaping'; import ONE_EM from '../symbol/one_em'; import { evaluateRadialOffset } from '../symbol/symbol_layout'; @@ -101,7 +101,8 @@ function updateVariableAnchors(bucket, rotateWithMap, pitchWithMap, variableOffs dynamicLayoutVertexArray.clear(); for (let s = 0; s < placedSymbols.length; s++) { const symbol: any = placedSymbols.get(s); - const variableOffset = (!symbol.hidden && symbol.crossTileID) ? variableOffsets[symbol.crossTileID] : null; + const skipOrientation = bucket.allowVerticalPlacement && !symbol.placedOrientation; + const variableOffset = (!symbol.hidden && symbol.crossTileID && !skipOrientation) ? variableOffsets[symbol.crossTileID] : null; if (!variableOffset) { // These symbols are from a justification that is not being used, or a label that wasn't placed // so we don't need to do the extra math to figure out what incremental shift to apply. @@ -130,8 +131,32 @@ function updateVariableAnchors(bucket, rotateWithMap, pitchWithMap, variableOffs shift.rotate(-transform.angle) : shift); + const angle = (bucket.allowVerticalPlacement && symbol.placedOrientation === WritingMode.vertical) ? Math.PI / 2 : 0; for (let g = 0; g < symbol.numGlyphs; g++) { - addDynamicAttributes(dynamicLayoutVertexArray, shiftedAnchor, 0); + addDynamicAttributes(dynamicLayoutVertexArray, shiftedAnchor, angle); + } + } + } + bucket.text.dynamicLayoutVertexBuffer.updateData(dynamicLayoutVertexArray); +} + +function updateVerticalLabels(bucket) { + const placedSymbols = bucket.text.placedSymbolArray; + const dynamicLayoutVertexArray = bucket.text.dynamicLayoutVertexArray; + dynamicLayoutVertexArray.clear(); + for (let s = 0; s < placedSymbols.length; s++) { + const symbol: any = placedSymbols.get(s); + const shouldHide = symbol.hidden || !symbol.placedOrientation; + if (shouldHide) { + // These symbols are from an orientation that is not being used, or a label that wasn't placed + // so we don't need to do the extra math to figure out what incremental shift to apply. + symbolProjection.hideGlyphs(symbol.numGlyphs, dynamicLayoutVertexArray); + } else { + const tileAnchor = new Point(symbol.anchorX, symbol.anchorY); + const angle = (bucket.allowVerticalPlacement && symbol.placedOrientation === WritingMode.vertical) ? Math.PI / 2 : 0; + + for (let g = 0; g < symbol.numGlyphs; g++) { + addDynamicAttributes(dynamicLayoutVertexArray, tileAnchor, angle); } } } @@ -211,6 +236,8 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate const tileScale = Math.pow(2, tr.zoom - tile.tileID.overscaledZ); updateVariableAnchors(bucket, rotateWithMap, pitchWithMap, variableOffsets, symbolSize, tr, labelPlaneMatrix, coord.posMatrix, tileScale, size); + } else if (isText && size && bucket.allowVerticalPlacement) { + updateVerticalLabels(bucket); } const matrix = painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor), diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 59f2ab361be..0d65c34cc32 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -2038,6 +2038,48 @@ }, "property-type": "data-constant" }, + "text-writing-mode": { + "type": "array", + "value": "enum", + "values": { + "horizontal": { + "doc": "If a text's language supports horizontal writing mode, symbols with point placement would be laid out horizontally." + }, + "vertical": { + "doc": "If a text's language supports vertical writing mode, symbols with point placement would be laid out vertically." + } + }, + "doc": "The property allows control over a symbol's orientation. Note that the property values act as a hint, so that a symbol whose language doesn’t support the provided orientation will be laid out in its natural orientation. Example: English point symbol will be rendered horizontally even if array value contains single 'vertical' enum value. The order of elements in an array define priority order for the placement of an orientation variant.", + "requires": [ + "text-field", + { + "symbol-placement": [ + "point" + ] + } + ], + "sdk-support": { + "basic functionality": { + "js": "next", + "android": "next", + "ios": "next", + "macos": "next" + }, + "data-driven styling": { + "js": "Not yet supported", + "android": "Not yet supported", + "ios": "Not yet supported", + "macos": "Not yet supported" + } + }, + "expression": { + "interpolated": false, + "parameters": [ + "zoom" + ] + }, + "property-type": "data-constant" + }, "text-rotate": { "type": "number", "default": 0, diff --git a/src/style-spec/types.js b/src/style-spec/types.js index 8dc4ce57e29..efddf753d72 100644 --- a/src/style-spec/types.js +++ b/src/style-spec/types.js @@ -244,6 +244,7 @@ export type SymbolLayerSpecification = {| "text-variable-anchor"?: PropertyValueSpecification>, "text-anchor"?: DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">, "text-max-angle"?: PropertyValueSpecification, + "text-writing-mode"?: PropertyValueSpecification>, "text-rotate"?: DataDrivenPropertyValueSpecification, "text-padding"?: PropertyValueSpecification, "text-keep-upright"?: PropertyValueSpecification, diff --git a/src/style/style_layer/symbol_style_layer.js b/src/style/style_layer/symbol_style_layer.js index a1a245a1a21..4392741825f 100644 --- a/src/style/style_layer/symbol_style_layer.js +++ b/src/style/style_layer/symbol_style_layer.js @@ -53,6 +53,20 @@ class SymbolStyleLayer extends StyleLayer { if (this.layout.get('icon-pitch-alignment') === 'auto') { this.layout._values['icon-pitch-alignment'] = this.layout.get('icon-rotation-alignment'); } + + if (this.layout.get('symbol-placement') === 'point') { + const writingModes = this.layout.get('text-writing-mode'); + if (writingModes) { + // remove duplicates, preserving order + const deduped = []; + for (const m of writingModes) { + if (deduped.indexOf(m) < 0) deduped.push(m); + } + this.layout._values['text-writing-mode'] = deduped; + } else { + this.layout._values['text-writing-mode'] = ['horizontal']; + } + } } getValueAndResolveTokens(name: *, feature: Feature) { diff --git a/src/style/style_layer/symbol_style_layer_properties.js b/src/style/style_layer/symbol_style_layer_properties.js index d5cdeb55f4b..b92b2d212c4 100644 --- a/src/style/style_layer/symbol_style_layer_properties.js +++ b/src/style/style_layer/symbol_style_layer_properties.js @@ -50,6 +50,7 @@ export type LayoutProps = {| "text-variable-anchor": DataConstantProperty>, "text-anchor": DataDrivenProperty<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">, "text-max-angle": DataConstantProperty, + "text-writing-mode": DataConstantProperty>, "text-rotate": DataDrivenProperty, "text-padding": DataConstantProperty, "text-keep-upright": DataConstantProperty, @@ -93,6 +94,7 @@ const layout: Properties = new Properties({ "text-variable-anchor": new DataConstantProperty(styleSpec["layout_symbol"]["text-variable-anchor"]), "text-anchor": new DataDrivenProperty(styleSpec["layout_symbol"]["text-anchor"]), "text-max-angle": new DataConstantProperty(styleSpec["layout_symbol"]["text-max-angle"]), + "text-writing-mode": new DataConstantProperty(styleSpec["layout_symbol"]["text-writing-mode"]), "text-rotate": new DataDrivenProperty(styleSpec["layout_symbol"]["text-rotate"]), "text-padding": new DataConstantProperty(styleSpec["layout_symbol"]["text-padding"]), "text-keep-upright": new DataConstantProperty(styleSpec["layout_symbol"]["text-keep-upright"]), diff --git a/src/symbol/placement.js b/src/symbol/placement.js index 6be908f2122..a7dec283b21 100644 --- a/src/symbol/placement.js +++ b/src/symbol/placement.js @@ -5,7 +5,7 @@ import EXTENT from '../data/extent'; import * as symbolSize from './symbol_size'; import * as projection from './projection'; import { getAnchorJustification, evaluateRadialOffset } from './symbol_layout'; -import { getAnchorAlignment } from './shaping'; +import { getAnchorAlignment, WritingMode } from './shaping'; import assert from 'assert'; import pixelsToTileUnits from '../source/pixels_to_tile_units'; import Point from '@mapbox/point-geometry'; @@ -165,6 +165,7 @@ export class Placement { placements: { [CrossTileID]: JointPlacement }; opacities: { [CrossTileID]: JointOpacityState }; variableOffsets: {[CrossTileID]: VariableOffset }; + placedOrientations: {[CrossTileID]: number }; commitTime: number; lastPlacementChangeTime: number; stale: boolean; @@ -189,6 +190,8 @@ export class Placement { if (prevPlacement) { prevPlacement.prevPlacement = undefined; // Only hold on to one placement back } + + this.placedOrientations = {}; } placeLayerTile(styleLayer: StyleLayer, tile: Tile, showCollisionBoxes: boolean, seenCrossTileIDs: { [string | number]: boolean }) { @@ -234,7 +237,7 @@ export class Placement { attemptAnchorPlacement(anchor: TextAnchor, textBox: SingleCollisionBox, width: number, height: number, radialTextOffset: number, textBoxScale: number, rotateWithMap: boolean, pitchWithMap: boolean, textPixelRatio: number, posMatrix: mat4, collisionGroup: CollisionGroup, - textAllowOverlap: boolean, symbolInstance: SymbolInstance, bucket: SymbolBucket) { + textAllowOverlap: boolean, symbolInstance: SymbolInstance, bucket: SymbolBucket, orientation: number): ?{ box: Array, offscreen: boolean } { const shift = calculateVariableLayoutOffset(anchor, width, height, radialTextOffset, textBoxScale); @@ -263,7 +266,13 @@ export class Placement { textBoxScale, prevAnchor }; - this.markUsedJustification(bucket, anchor, symbolInstance); + this.markUsedJustification(bucket, anchor, symbolInstance, orientation); + + if (bucket.allowVerticalPlacement) { + this.markUsedOrientation(bucket, orientation, symbolInstance); + this.placedOrientations[symbolInstance.crossTileID] = orientation; + } + return placedGlyphBoxes; } } @@ -317,26 +326,82 @@ export class Placement { let placeIcon = false; let offscreen = true; + let placed = { box: null, offscreen: null }; + let placedVertical = { box: null, offscreen: null }; + let placedGlyphBoxes = null; let placedGlyphCircles = null; let placedIconBoxes = null; let textFeatureIndex = 0; + let verticalTextFeatureIndex = 0; let iconFeatureIndex = 0; if (collisionArrays.textFeatureIndex) { textFeatureIndex = collisionArrays.textFeatureIndex; } + if (collisionArrays.verticalTextFeatureIndex) { + verticalTextFeatureIndex = collisionArrays.verticalTextFeatureIndex; + } const textBox = collisionArrays.textBox; if (textBox) { + + const updatePreviousOrientationIfNotPlaced = (isPlaced) => { + let previousOrientation = WritingMode.horizontal; + if (bucket.allowVerticalPlacement && !isPlaced && this.prevPlacement) { + const prevPlacedOrientation = this.prevPlacement.placedOrientations[symbolInstance.crossTileID]; + if (prevPlacedOrientation) { + this.placedOrientations[symbolInstance.crossTileID] = prevPlacedOrientation; + previousOrientation = prevPlacedOrientation; + this.markUsedOrientation(bucket, previousOrientation, symbolInstance); + } + } + return previousOrientation; + }; + + const placeTextForPlacementModes = (placeHorizontalFn, placeVerticalFn) => { + if (bucket.allowVerticalPlacement && symbolInstance.numVerticalGlyphVertices > 0 && collisionArrays.verticalTextBox) { + for (const placementMode of bucket.writingModes) { + if (placementMode === WritingMode.vertical) { + placed = placeVerticalFn(); + placedVertical = placed; + } else { + placed = placeHorizontalFn(); + } + if (placed && placed.box && placed.box.length) break; + } + } else { + placed = placeHorizontalFn(); + } + }; + if (!layout.get('text-variable-anchor')) { - placedGlyphBoxes = this.collisionIndex.placeCollisionBox(textBox, - layout.get('text-allow-overlap'), textPixelRatio, posMatrix, collisionGroup.predicate); - placeText = placedGlyphBoxes.box.length > 0; + const placeBox = (collisionTextBox, orientation) => { + const placedFeature = this.collisionIndex.placeCollisionBox(collisionTextBox, layout.get('text-allow-overlap'), + textPixelRatio, posMatrix, collisionGroup.predicate); + if (placedFeature && placedFeature.box && placedFeature.box.length) { + this.markUsedOrientation(bucket, orientation, symbolInstance); + this.placedOrientations[symbolInstance.crossTileID] = orientation; + } + return placedFeature; + }; + + const placeHorizontal = () => { + return placeBox(textBox, WritingMode.horizontal); + }; + + const placeVertical = () => { + const verticalTextBox = collisionArrays.verticalTextBox; + if (bucket.allowVerticalPlacement && symbolInstance.numVerticalGlyphVertices > 0 && verticalTextBox) { + return placeBox(verticalTextBox, WritingMode.vertical); + } + return { box: null, offscreen: null }; + }; + + placeTextForPlacementModes(placeHorizontal, placeVertical); + updatePreviousOrientationIfNotPlaced(placed && placed.box && placed.box.length); + } else { - const width = textBox.x2 - textBox.x1; - const height = textBox.y2 - textBox.y1; - const textBoxScale = symbolInstance.textBoxScale; let anchors = layout.get('text-variable-anchor'); // If this symbol was in the last placement, shift the previously used @@ -350,29 +415,66 @@ export class Placement { } } - for (const anchor of anchors) { - placedGlyphBoxes = this.attemptAnchorPlacement( - anchor, textBox, width, height, symbolInstance.radialTextOffset, - textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, posMatrix, - collisionGroup, textAllowOverlap, symbolInstance, bucket); - if (placedGlyphBoxes) { - placeText = true; - break; + const placeBoxForVariableAnchors = (collisionTextBox, orientation) => { + const width = collisionTextBox.x2 - collisionTextBox.x1; + const height = collisionTextBox.y2 - collisionTextBox.y1; + const textBoxScale = symbolInstance.textBoxScale; + + let placedBox: ?{ box: Array, offscreen: boolean } = { box: [], offscreen: false }; + + for (const anchor of anchors) { + placedBox = this.attemptAnchorPlacement( + anchor, collisionTextBox, width, height, symbolInstance.radialTextOffset, + textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, posMatrix, + collisionGroup, textAllowOverlap, symbolInstance, bucket, orientation); + + if (placedBox && placedBox.box && placedBox.box.length) { + placeText = true; + break; + } + } + + return placedBox; + }; + + const placeHorizontal = () => { + return placeBoxForVariableAnchors(textBox, WritingMode.horizontal); + }; + + const placeVertical = () => { + const verticalTextBox = collisionArrays.verticalTextBox; + const wasPlaced = placed && placed.box && placed.box.length; + if (bucket.allowVerticalPlacement && !wasPlaced && symbolInstance.numVerticalGlyphVertices > 0 && verticalTextBox) { + return placeBoxForVariableAnchors(verticalTextBox, WritingMode.vertical); } + return { box: null, offscreen: null }; + }; + + placeTextForPlacementModes(placeHorizontal, placeVertical); + + if (placed) { + placeText = placed.box; + offscreen = placed.offscreen; } + const prevOrientation = updatePreviousOrientationIfNotPlaced(placed && placed.box); + // If we didn't get placed, we still need to copy our position from the last placement for // fade animations - if (!this.variableOffsets[symbolInstance.crossTileID] && this.prevPlacement) { + if (!placeText && this.prevPlacement) { const prevOffset = this.prevPlacement.variableOffsets[symbolInstance.crossTileID]; if (prevOffset) { this.variableOffsets[symbolInstance.crossTileID] = prevOffset; - this.markUsedJustification(bucket, prevOffset.anchor, symbolInstance); + this.markUsedJustification(bucket, prevOffset.anchor, symbolInstance, prevOrientation); } } + } } + placedGlyphBoxes = placed; + placeText = placedGlyphBoxes && placedGlyphBoxes.box && placedGlyphBoxes.box.length > 0; + offscreen = placedGlyphBoxes && placedGlyphBoxes.offscreen; const textCircles = collisionArrays.textCircles; if (textCircles) { @@ -422,9 +524,15 @@ export class Placement { placeIcon = placeIcon && placeText; } - if (placeText && placedGlyphBoxes) { - this.collisionIndex.insertCollisionBox(placedGlyphBoxes.box, layout.get('text-ignore-placement'), + if (placeText && placedGlyphBoxes && placedGlyphBoxes.box) { + if (placedVertical && placedVertical.box && verticalTextFeatureIndex) { + this.collisionIndex.insertCollisionBox(placedGlyphBoxes.box, layout.get('text-ignore-placement'), + bucket.bucketInstanceId, verticalTextFeatureIndex, collisionGroup.ID); + } else { + this.collisionIndex.insertCollisionBox(placedGlyphBoxes.box, layout.get('text-ignore-placement'), bucket.bucketInstanceId, textFeatureIndex, collisionGroup.ID); + } + } if (placeIcon && placedIconBoxes) { this.collisionIndex.insertCollisionBox(placedIconBoxes.box, layout.get('icon-ignore-placement'), @@ -457,16 +565,28 @@ export class Placement { bucket.justReloaded = false; } - markUsedJustification(bucket: SymbolBucket, placedAnchor: TextAnchor, symbolInstance: SymbolInstance) { + markUsedJustification(bucket: SymbolBucket, placedAnchor: TextAnchor, symbolInstance: SymbolInstance, orientation: number) { const justifications = { "left": symbolInstance.leftJustifiedTextSymbolIndex, "center": symbolInstance.centerJustifiedTextSymbolIndex, "right": symbolInstance.rightJustifiedTextSymbolIndex }; - const autoIndex = justifications[getAnchorJustification(placedAnchor)]; - for (const justification in justifications) { - const index = justifications[justification]; + let autoIndex; + if (orientation === WritingMode.vertical) { + autoIndex = symbolInstance.verticalPlacedTextSymbolIndex; + } else { + autoIndex = justifications[getAnchorJustification(placedAnchor)]; + } + + const indexes = [ + symbolInstance.leftJustifiedTextSymbolIndex, + symbolInstance.centerJustifiedTextSymbolIndex, + symbolInstance.rightJustifiedTextSymbolIndex, + symbolInstance.verticalPlacedTextSymbolIndex + ]; + + for (const index of indexes) { if (index >= 0) { if (autoIndex >= 0 && index !== autoIndex) { // There are multiple justifications and this one isn't it: shift offscreen @@ -479,6 +599,25 @@ export class Placement { } } + markUsedOrientation(bucket: SymbolBucket, orientation: number, symbolInstance: SymbolInstance) { + const horizontal = (orientation === WritingMode.horizontal || orientation === WritingMode.horizontalOnly) ? orientation : 0; + const vertical = orientation === WritingMode.vertical ? orientation : 0; + + const horizontalIndexes = [ + symbolInstance.leftJustifiedTextSymbolIndex, + symbolInstance.centerJustifiedTextSymbolIndex, + symbolInstance.rightJustifiedTextSymbolIndex + ]; + + for (const index of horizontalIndexes) { + bucket.text.placedSymbolArray.get(index).placedOrientation = horizontal; + } + + if (symbolInstance.verticalPlacedTextSymbolIndex) { + bucket.text.placedSymbolArray.get(symbolInstance.verticalPlacedTextSymbolIndex).placedOrientation = vertical; + } + } + commit(now: number): void { this.commitTime = now; @@ -491,6 +630,8 @@ export class Placement { const prevOpacities = prevPlacement ? prevPlacement.opacities : {}; const prevOffsets = prevPlacement ? prevPlacement.variableOffsets : {}; + const prevOrientations = prevPlacement ? prevPlacement.placedOrientations : {}; + // add the opacities from the current placement, and copy their current values from the previous placement for (const crossTileID in this.placements) { const jointPlacement = this.placements[crossTileID]; @@ -523,6 +664,12 @@ export class Placement { } } + for (const crossTileID in prevOrientations) { + if (!this.placedOrientations[crossTileID] && this.opacities[crossTileID] && !this.opacities[crossTileID].isHidden()) { + this.placedOrientations[crossTileID] = prevOrientations[crossTileID]; + } + } + // this.lastPlacementChangeTime is the time of the last commit() that // resulted in a placement change -- in other words, the start time of // the last symbol fade animation @@ -606,21 +753,33 @@ export class Placement { // its position at render time. If this layer has variable placement, shift the various // symbol instances appropriately so that symbols from buckets that have yet to be placed // offset appropriately. - const hidden = opacityState.text.isHidden() ? 1 : 0; + const symbolHidden = opacityState.text.isHidden() ? 1 : 0; + const placedOrientation = this.placedOrientations[symbolInstance.crossTileID]; + const verticalHidden = (placedOrientation === WritingMode.horizontal || placedOrientation === WritingMode.horizontalOnly) ? 1 : 0; + const horizontalHidden = placedOrientation === WritingMode.vertical ? 1 : 0; [ symbolInstance.rightJustifiedTextSymbolIndex, symbolInstance.centerJustifiedTextSymbolIndex, - symbolInstance.leftJustifiedTextSymbolIndex, - symbolInstance.verticalPlacedTextSymbolIndex + symbolInstance.leftJustifiedTextSymbolIndex ].forEach(index => { if (index >= 0) { - bucket.text.placedSymbolArray.get(index).hidden = hidden; + bucket.text.placedSymbolArray.get(index).hidden = symbolHidden || horizontalHidden; } }); + if (symbolInstance.verticalPlacedTextSymbolIndex >= 0) { + bucket.text.placedSymbolArray.get(symbolInstance.verticalPlacedTextSymbolIndex).hidden = symbolHidden || verticalHidden; + } + const prevOffset = this.variableOffsets[symbolInstance.crossTileID]; if (prevOffset) { - this.markUsedJustification(bucket, prevOffset.anchor, symbolInstance); + this.markUsedJustification(bucket, prevOffset.anchor, symbolInstance, placedOrientation); + } + + const prevOrientation = this.placedOrientations[symbolInstance.crossTileID]; + if (prevOrientation) { + this.markUsedJustification(bucket, 'left', symbolInstance, prevOrientation); + this.markUsedOrientation(bucket, prevOrientation, symbolInstance); } } diff --git a/src/symbol/quads.js b/src/symbol/quads.js index 633352038e9..7ac39d50218 100644 --- a/src/symbol/quads.js +++ b/src/symbol/quads.js @@ -9,6 +9,7 @@ import type {PositionedIcon, Shaping} from './shaping'; import type SymbolStyleLayer from '../style/style_layer/symbol_style_layer'; import type {Feature} from '../style-spec/expression'; import type {GlyphPosition} from '../render/glyph_atlas'; +import ONE_EM from './one_em'; /** * A textured quad for rendering a single icon or glyph. @@ -120,7 +121,8 @@ export function getGlyphQuads(anchor: Anchor, layer: SymbolStyleLayer, alongLine: boolean, feature: Feature, - positions: {[string]: {[number]: GlyphPosition}}): Array { + positions: {[string]: {[number]: GlyphPosition}}, + allowVerticalPlacement: boolean): Array { const textRotate = layer.layout.get('text-rotate').evaluate(feature, {}) * Math.PI / 180; @@ -146,10 +148,20 @@ export function getGlyphQuads(anchor: Anchor, [positionedGlyph.x + halfAdvance, positionedGlyph.y] : [0, 0]; - const builtInOffset = alongLine ? + let builtInOffset = alongLine ? [0, 0] : [positionedGlyph.x + halfAdvance + textOffset[0], positionedGlyph.y + textOffset[1]]; + const rotateVerticalGlyph = (alongLine || allowVerticalPlacement) && positionedGlyph.vertical; + + let verticalizedLabelOffset = [0, 0]; + if (rotateVerticalGlyph) { + // Vertical POI labels that are rotated 90deg CW and whose glyphs must preserve upright orientation + // need to be rotated 90deg CCW. After a quad is rotated, it is translated to the original built-in offset. + verticalizedLabelOffset = builtInOffset; + builtInOffset = [0, 0]; + } + const x1 = (glyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset[0]; const y1 = (-glyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset[1]; const x2 = x1 + rect.w * positionedGlyph.scale; @@ -157,24 +169,31 @@ export function getGlyphQuads(anchor: Anchor, const tl = new Point(x1, y1); const tr = new Point(x2, y1); - const bl = new Point(x1, y2); + const bl = new Point(x1, y2); const br = new Point(x2, y2); - if (alongLine && positionedGlyph.vertical) { + if (rotateVerticalGlyph) { // Vertical-supporting glyphs are laid out in 24x24 point boxes (1 square em) // In horizontal orientation, the y values for glyphs are below the midline // and we use a "yOffset" of -17 to pull them up to the middle. // By rotating counter-clockwise around the point at the center of the left // edge of a 24x24 layout box centered below the midline, we align the center // of the glyphs with the horizontal midline, so the yOffset is no longer - // necessary, but we also pull the glyph to the left along the x axis - const center = new Point(-halfAdvance, halfAdvance); + // necessary, but we also pull the glyph to the left along the x axis. + // The y coordinate includes baseline yOffset, thus needs to be accounted + // for when glyph is rotated and translated. + const center = new Point(-halfAdvance, halfAdvance - shaping.yOffset); const verticalRotation = -Math.PI / 2; - const xOffsetCorrection = new Point(5, 0); - tl._rotateAround(verticalRotation, center)._add(xOffsetCorrection); - tr._rotateAround(verticalRotation, center)._add(xOffsetCorrection); - bl._rotateAround(verticalRotation, center)._add(xOffsetCorrection); - br._rotateAround(verticalRotation, center)._add(xOffsetCorrection); + + // xHalfWidhtOffsetcorrection is a difference between full-width and half-width + // advance, should be 0 for full-width glyphs and will pull up half-width glyphs. + const xHalfWidhtOffsetcorrection = ONE_EM / 2 - halfAdvance; + const xOffsetCorrection = new Point(5 - shaping.yOffset - xHalfWidhtOffsetcorrection, 0); + const verticalOffsetCorrection = new Point(...verticalizedLabelOffset); + tl._rotateAround(verticalRotation, center)._add(xOffsetCorrection)._add(verticalOffsetCorrection); + tr._rotateAround(verticalRotation, center)._add(xOffsetCorrection)._add(verticalOffsetCorrection); + bl._rotateAround(verticalRotation, center)._add(xOffsetCorrection)._add(verticalOffsetCorrection); + br._rotateAround(verticalRotation, center)._add(xOffsetCorrection)._add(verticalOffsetCorrection); } if (textRotate) { diff --git a/src/symbol/shaping.js b/src/symbol/shaping.js index 0386a3feb88..a7c60a99a1e 100644 --- a/src/symbol/shaping.js +++ b/src/symbol/shaping.js @@ -2,7 +2,8 @@ import { charHasUprightVerticalOrientation, - charAllowsIdeographicBreaking + charAllowsIdeographicBreaking, + charInComplexShapingScript } from '../util/script_detection'; import verticalizePunctuation from '../util/verticalize_punctuation'; import { plugin as rtlTextPlugin } from '../source/rtl_text_plugin'; @@ -39,7 +40,8 @@ export type Shaping = { right: number, writingMode: 1 | 2, lineCount: number, - text: string + text: string, + yOffset: number, }; export type SymbolAnchor = 'center' | 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; @@ -146,7 +148,8 @@ function shapeText(text: Formatted, textJustify: TextJustify, spacing: number, translate: [number, number], - writingMode: 1 | 2): Shaping | false { + writingMode: 1 | 2, + allowVerticalPlacement: boolean): Shaping | false { const logicalInput = TaggedString.fromFeature(text, defaultFontStack); if (writingMode === WritingMode.vertical) { @@ -199,10 +202,11 @@ function shapeText(text: Formatted, left: translate[0], right: translate[0], writingMode, - lineCount: lines.length + lineCount: lines.length, + yOffset: -17 // the y offset *should* be part of the font metadata }; - shapeLines(shaping, glyphs, lines, lineHeight, textAnchor, textJustify, writingMode, spacing); + shapeLines(shaping, glyphs, lines, lineHeight, textAnchor, textJustify, writingMode, spacing, allowVerticalPlacement); if (!positionedGlyphs.length) return false; return shaping; @@ -439,12 +443,11 @@ function shapeLines(shaping: Shaping, textAnchor: SymbolAnchor, textJustify: TextJustify, writingMode: 1 | 2, - spacing: number) { - // the y offset *should* be part of the font metadata - const yOffset = -17; + spacing: number, + allowVerticalPlacement: boolean) { let x = 0; - let y = yOffset; + let y = shaping.yOffset; let maxLineLength = 0; const positionedGlyphs = shaping.positionedGlyphs; @@ -476,11 +479,16 @@ function shapeLines(shaping: Shaping, if (!glyph) continue; - if (!charHasUprightVerticalOrientation(codePoint) || writingMode === WritingMode.horizontal) { + if (writingMode === WritingMode.horizontal || + // Don't verticalize glyphs that have no upright orientation if vertical placement is disabled. + (!allowVerticalPlacement && !charHasUprightVerticalOrientation(codePoint)) || + // If vertical placement is ebabled, don't verticalize glyphs that + // are from complex text layout script, or whitespaces. + (allowVerticalPlacement && (whitespace[codePoint] || charInComplexShapingScript(codePoint)))) { positionedGlyphs.push({glyph: codePoint, x, y: y + baselineOffset, vertical: false, scale: section.scale, fontStack: section.fontStack}); x += glyph.metrics.advance * section.scale + spacing; } else { - positionedGlyphs.push({glyph: codePoint, x, y: baselineOffset, vertical: true, scale: section.scale, fontStack: section.fontStack}); + positionedGlyphs.push({glyph: codePoint, x, y: y + baselineOffset, vertical: true, scale: section.scale, fontStack: section.fontStack}); x += ONE_EM * section.scale + spacing; } } @@ -501,7 +509,7 @@ function shapeLines(shaping: Shaping, align(positionedGlyphs, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, lines.length); // Calculate the bounding box - const height = y - yOffset; + const height = y - shaping.yOffset; shaping.top += -verticalAlign * height; shaping.bottom = shaping.top + height; diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index 498faa87d01..b85310ef051 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -187,6 +187,16 @@ export function performSymbolLayout(bucket: SymbolBucket, layout.get('text-max-width').evaluate(feature, {}) * ONE_EM : 0; + const addVerticalShapingForPointLabelIfNeeded = () => { + if (bucket.allowVerticalPlacement && allowsVerticalWritingMode(unformattedText)) { + // Vertical POI label placement is meant to be used for scripts that support vertical + // writing mode, thus, default left justification is used. If Latin + // scripts would need to be supported, this should take into account other justifications. + shapedTextOrientations.vertical = shapeText(text, glyphMap, fontstack, maxWidth, lineHeight, textAnchor, + 'left', spacingIfAllowed, textOffset, WritingMode.vertical, true); + } + }; + // If this layer uses text-variable-anchor, generate shapings for all justification possibilities. if (!textAlongLine && variableTextAnchor) { const justifications = textJustify === "auto" ? @@ -205,24 +215,32 @@ export function performSymbolLayout(bucket: SymbolBucket, // If using text-variable-anchor for the layer, we use a center anchor for all shapings and apply // the offsets for the anchor in the placement step. const shaping = shapeText(text, glyphMap, fontstack, maxWidth, lineHeight, 'center', - justification, spacingIfAllowed, textOffset, WritingMode.horizontal); + justification, spacingIfAllowed, textOffset, WritingMode.horizontal, false); if (shaping) { shapedTextOrientations.horizontal[justification] = shaping; singleLine = shaping.lineCount === 1; } } } + + addVerticalShapingForPointLabelIfNeeded(); } else { if (textJustify === "auto") { textJustify = getAnchorJustification(textAnchor); } + + // Horizontal point or line label. const shaping = shapeText(text, glyphMap, fontstack, maxWidth, lineHeight, textAnchor, textJustify, spacingIfAllowed, - textOffset, WritingMode.horizontal); + textOffset, WritingMode.horizontal, false); if (shaping) shapedTextOrientations.horizontal[textJustify] = shaping; + // Vertical point label (if allowVerticalPlacement is enabled). + addVerticalShapingForPointLabelIfNeeded(); + + // Verticalized line label. if (allowsVerticalWritingMode(unformattedText) && textAlongLine && keepUpright) { shapedTextOrientations.vertical = shapeText(text, glyphMap, fontstack, maxWidth, lineHeight, textAnchor, textJustify, - spacingIfAllowed, textOffset, WritingMode.vertical); + spacingIfAllowed, textOffset, WritingMode.vertical, false); } } @@ -405,7 +423,7 @@ function addTextVertices(bucket: SymbolBucket, glyphPositionMap: {[string]: {[number]: GlyphPosition}}, sizes: Sizes) { const glyphQuads = getGlyphQuads(anchor, shapedText, textOffset, - layer, textAlongLine, feature, glyphPositionMap); + layer, textAlongLine, feature, glyphPositionMap, bucket.allowVerticalPlacement); const sizeData = bucket.textSizeData; let textSizeData = null; @@ -485,7 +503,7 @@ function addSymbol(bucket: SymbolBucket, sizes: Sizes) { const lineArray = bucket.addToLineVertexArray(anchor, line); - let textCollisionFeature, iconCollisionFeature; + let textCollisionFeature, iconCollisionFeature, verticalTextCollisionFeature; let numIconVertices = 0; let numHorizontalGlyphVertices = 0; @@ -494,6 +512,13 @@ function addSymbol(bucket: SymbolBucket, let key = murmur3(''); const radialTextOffset = (layer.layout.get('text-radial-offset').evaluate(feature, {}) || 0) * ONE_EM; + if (bucket.allowVerticalPlacement && shapedTextOrientations.vertical) { + const textRotation = layer.layout.get('text-rotate').evaluate(feature, {}); + const verticalTextRotation = textRotation + 90.0; + const verticalShaping = shapedTextOrientations.vertical; + verticalTextCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, verticalShaping, textBoxScale, textPadding, textAlongLine, bucket.overscaling, verticalTextRotation); + } + for (const justification: any in shapedTextOrientations.horizontal) { const shaping = shapedTextOrientations.horizontal[justification]; @@ -526,6 +551,9 @@ function addSymbol(bucket: SymbolBucket, const textBoxStartIndex = textCollisionFeature ? textCollisionFeature.boxStartIndex : bucket.collisionBoxArray.length; const textBoxEndIndex = textCollisionFeature ? textCollisionFeature.boxEndIndex : bucket.collisionBoxArray.length; + const verticalTextBoxStartIndex = verticalTextCollisionFeature ? verticalTextCollisionFeature.boxStartIndex : bucket.collisionBoxArray.length; + const verticalTextBoxEndIndex = verticalTextCollisionFeature ? verticalTextCollisionFeature.boxEndIndex : bucket.collisionBoxArray.length; + if (shapedIcon) { const iconQuads = getIconQuads(anchor, shapedIcon, layer, iconAlongLine, getDefaultHorizontalShaping(shapedTextOrientations.horizontal), @@ -585,6 +613,8 @@ function addSymbol(bucket: SymbolBucket, key, textBoxStartIndex, textBoxEndIndex, + verticalTextBoxStartIndex, + verticalTextBoxEndIndex, iconBoxStartIndex, iconBoxEndIndex, featureIndex, diff --git a/src/util/script_detection.js b/src/util/script_detection.js index 60932e70869..5ae5b933ef5 100644 --- a/src/util/script_detection.js +++ b/src/util/script_detection.js @@ -269,6 +269,14 @@ export function charHasRotatedVerticalOrientation(char: number) { charHasNeutralVerticalOrientation(char)); } +export function charInComplexShapingScript(char: number) { + return isChar['Arabic'](char) || + isChar['Arabic Supplement'](char) || + isChar['Arabic Extended-A'](char) || + isChar['Arabic Presentation Forms-A'](char) || + isChar['Arabic Presentation Forms-B'](char); +} + export function charInSupportedScript(char: number, canRenderRTL: boolean) { // This is a rough heuristic: whether we "can render" a script // actually depends on the properties of the font being used diff --git a/test/expected/text-shaping-default.json b/test/expected/text-shaping-default.json index c682db02896..f103d4aa98c 100644 --- a/test/expected/text-shaping-default.json +++ b/test/expected/text-shaping-default.json @@ -47,5 +47,6 @@ "left": -32.5, "right": 32.5, "writingMode": 1, + "yOffset": -17, "lineCount": 1 } diff --git a/test/expected/text-shaping-linebreak.json b/test/expected/text-shaping-linebreak.json index 0739d684711..9ba4b8298e3 100644 --- a/test/expected/text-shaping-linebreak.json +++ b/test/expected/text-shaping-linebreak.json @@ -87,5 +87,6 @@ "left": -32.5, "right": 32.5, "writingMode": 1, + "yOffset": -17, "lineCount": 2 } diff --git a/test/expected/text-shaping-newline.json b/test/expected/text-shaping-newline.json index 8c0bdc2197b..77fa41532d8 100644 --- a/test/expected/text-shaping-newline.json +++ b/test/expected/text-shaping-newline.json @@ -87,5 +87,6 @@ "left": -32.5, "right": 32.5, "writingMode": 1, + "yOffset": -17, "lineCount": 2 } diff --git a/test/expected/text-shaping-newlines-in-middle.json b/test/expected/text-shaping-newlines-in-middle.json index fc0c28e9769..e532bcb53a0 100644 --- a/test/expected/text-shaping-newlines-in-middle.json +++ b/test/expected/text-shaping-newlines-in-middle.json @@ -87,5 +87,6 @@ "left": -32.5, "right": 32.5, "writingMode": 1, + "yOffset": -17, "lineCount": 3 } diff --git a/test/expected/text-shaping-null.json b/test/expected/text-shaping-null.json index b54d9a56abb..a9c08690b57 100644 --- a/test/expected/text-shaping-null.json +++ b/test/expected/text-shaping-null.json @@ -23,5 +23,6 @@ "left": -10, "right": 10, "writingMode": 1, + "yOffset": -17, "lineCount": 1 } diff --git a/test/expected/text-shaping-spacing.json b/test/expected/text-shaping-spacing.json index b65ce97d03d..9c1f2a704b2 100644 --- a/test/expected/text-shaping-spacing.json +++ b/test/expected/text-shaping-spacing.json @@ -47,5 +47,6 @@ "left": -38.5, "right": 38.5, "writingMode": 1, + "yOffset": -17, "lineCount": 1 } diff --git a/test/expected/text-shaping-zero-width-space.json b/test/expected/text-shaping-zero-width-space.json index 4145103857e..15fe489357c 100644 --- a/test/expected/text-shaping-zero-width-space.json +++ b/test/expected/text-shaping-zero-width-space.json @@ -119,5 +119,6 @@ "left": -63, "right": 63, "writingMode": 1, + "yOffset": -17, "lineCount": 3 -} \ No newline at end of file +} diff --git a/test/integration/render-tests/text-writing-mode/chinese-punctuation/expected.png b/test/integration/render-tests/text-writing-mode/line_label/chinese-punctuation/expected.png similarity index 100% rename from test/integration/render-tests/text-writing-mode/chinese-punctuation/expected.png rename to test/integration/render-tests/text-writing-mode/line_label/chinese-punctuation/expected.png diff --git a/test/integration/render-tests/text-writing-mode/chinese-punctuation/style.json b/test/integration/render-tests/text-writing-mode/line_label/chinese-punctuation/style.json similarity index 100% rename from test/integration/render-tests/text-writing-mode/chinese-punctuation/style.json rename to test/integration/render-tests/text-writing-mode/line_label/chinese-punctuation/style.json diff --git a/test/integration/render-tests/text-writing-mode/chinese/expected.png b/test/integration/render-tests/text-writing-mode/line_label/chinese/expected.png similarity index 100% rename from test/integration/render-tests/text-writing-mode/chinese/expected.png rename to test/integration/render-tests/text-writing-mode/line_label/chinese/expected.png diff --git a/test/integration/render-tests/text-writing-mode/chinese/style.json b/test/integration/render-tests/text-writing-mode/line_label/chinese/style.json similarity index 100% rename from test/integration/render-tests/text-writing-mode/chinese/style.json rename to test/integration/render-tests/text-writing-mode/line_label/chinese/style.json diff --git a/test/integration/render-tests/text-writing-mode/latin/expected.png b/test/integration/render-tests/text-writing-mode/line_label/latin/expected.png similarity index 100% rename from test/integration/render-tests/text-writing-mode/latin/expected.png rename to test/integration/render-tests/text-writing-mode/line_label/latin/expected.png diff --git a/test/integration/render-tests/text-writing-mode/latin/style.json b/test/integration/render-tests/text-writing-mode/line_label/latin/style.json similarity index 100% rename from test/integration/render-tests/text-writing-mode/latin/style.json rename to test/integration/render-tests/text-writing-mode/line_label/latin/style.json diff --git a/test/integration/render-tests/text-writing-mode/mixed/expected.png b/test/integration/render-tests/text-writing-mode/line_label/mixed/expected.png similarity index 100% rename from test/integration/render-tests/text-writing-mode/mixed/expected.png rename to test/integration/render-tests/text-writing-mode/line_label/mixed/expected.png diff --git a/test/integration/render-tests/text-writing-mode/mixed/style.json b/test/integration/render-tests/text-writing-mode/line_label/mixed/style.json similarity index 100% rename from test/integration/render-tests/text-writing-mode/mixed/style.json rename to test/integration/render-tests/text-writing-mode/line_label/mixed/style.json diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-arabic-vertical-mode/expected.png b/test/integration/render-tests/text-writing-mode/point_label/cjk-arabic-vertical-mode/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..b2d3a24d59b9e7c2538ee93f7e81959c52e12149 GIT binary patch literal 6427 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 Z(;rnqkZ-{l>8FQGfKM>`l2>xk{{W~VZP@?- literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-arabic-vertical-mode/style.json b/test/integration/render-tests/text-writing-mode/point_label/cjk-arabic-vertical-mode/style.json new file mode 100644 index 00000000000..003974de573 --- /dev/null +++ b/test/integration/render-tests/text-writing-mode/point_label/cjk-arabic-vertical-mode/style.json @@ -0,0 +1,68 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 150, + "width": 150 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name_cjk": "نشاط التدويل ボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ -30, 0 ] + } + }, + { + "type": "Feature", + "properties": { + "name_cjk": "マップボックス نشاط التدويل" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, 0] + } + }, + { + "type": "Feature", + "properties": { + "name_cjk": "マップボックス نشاط التدويل ボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ 30, 0 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "{name_cjk}", + "text-writing-mode": ["vertical"], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-size": 14 + } + } + ] +} diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-horizontal-vertical-mode/expected.png b/test/integration/render-tests/text-writing-mode/point_label/cjk-horizontal-vertical-mode/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..ca20330459acd413e3ef36491ce5229d1a601d7d GIT binary patch literal 3409 zcmZvfdpr~R|HtJr48zzgh8_324Mjw*xlPSD5@NPQB9vIS#W0uI6vK)xrq+bo%4J=d z-TFoyD@PY}W~)QrI9YW{(mCIq-#@>{@A3OS9-qtO^M1S^@5kr;c|1O^_w!wk*%567 zv;=BsXc)!DL}Jx@#XnOIpk9R`+2d+EEH*MMnYdwDaI|ykh(JT03rf|V(y5LVzrkc0 zkA4KARHM62wtU>aLBBA8=kSIDi877U3=&~J5rZ+l#>feC<3~sbEgx@}+fld88#{8^ z=l)`T{dD%1d%m}PZ$16K_UywaJu>3IjF2WgnUhC;(s0YX1M-t=UBeHR-?`o_@J=Og z$_3$6*AP6pn-rTYe-Eqp)^OT9Qe3v4K>AS(JSaF3)nLF{bnt60Gj~&^qZ7&1i2P%6 zQmHV*PvXrr5^|&T!DP_6;Q^V+0Oj>YmV_}F zUEWVZ%h6I4_L#gq@0zySv=mvMQU5cu)&N5p;*5bD>kL?zLB?Iyf!P31crKhe!b;Is zkU2-Se0di8(wKEM}qrQ-5{K?!TE zm7wn>)jW&GK4{3lSHljlG*F!x&_aTaKPu9_ApVwG<^L8Mz0tA@cuMN8(skHNjs50! zO$9xt$ny|IfBf$C%Dx`ubH8r)(>u6-${OKf{{W!_3(zimjHtnr%bWn3katqcn-|vZI0{D|Ic^k zEMM=FEP;J;C1;}f%hrAWf=ZF_s$ugFu3H_4f2`<{YLBE|jfvfV;7z#$MY&640C4!BC#3^raUjvzlU~%7!Jb~E75dCMKl{Ia%Xw&#JB;%9DQMJ} z>HI9Epmn*pv+gc$?hl!@E{+0r%n5=Qs%|pcU{wjQ+C4Y#({~3LoAKb*GKekVjKaF^ z0^jXl<)#E^7q(oB7k}&n7j!_ zOF490^Xt5QU$o96RMf#DJfiJk*=D+g01N{A+tr&*%C44MKB$$Vc4>)nX2`Nt2n7ea zfVnpfYcfEYesJI6JlnX17(>#)YXop$mLqx%}rp zzZ;`R-A)*PCjjZw#kunfM5`7*O!&bgM5DKhHGdK#ew^#_mnd8s$BG~esp~LLq;~Jy zX`op#l2mad=F+w{+~$-^u50t2eW!ai9wT_r$6VG^RF5Vt{1rto+MbW^>s58xdVkez zn7+W1Ov9=+!RWgnZIM~%F=syTRC|^| zsdSEL6zdmZ>mev`rB3RPt1h7XQGf$T_zkxm@Hv!C#EQ8DcWc%%8{sr>%eRcbQXuju zjnIMOZjbE{Mt0U@%?B2Ib`_aVgN?KGw_i#l74o2i!1vX>08?*eS|2tQK8W=ahyygu zP`lGMUVAeQJ&@QNkbv5F73WB5c*ki;51MUIA|V8B>`)tD_js4)hu*B*bwgvkTmBE* zca)P$q^iWqU_Z3Qy7}{N+Nm3ve9iA}Pc%Af`D@l>g87Zxh*et}&jOECELOj6V3YS1&d!8BQ+q8Eih}@+uo}ZAE*tOE}g9 zGKb6<^6tX2PbnZ#b^0up&Lf`gE1dl~yw7%=fg+Wf))z_BRLkY6_BdC^zL+)82CEJs z+Kywo^-98~3`OlLodZ$K`ol1~@5@>o5Pl;czFrUtKZCo^hucW%eNK3+ycAvE_ACU{994pExkN>&$HP zDl~Hxok4NRt0M4b|9EEocl}Hveb3X)JxB7<5>2tY@aT(*exmt;(tEM;QweIw z6^l)?zBct???AZySw(<(N3rW`aq>Wz<34%R{=ya5m-ckSnMtc$r9ac%(d(Jb)BKgK z0Gv-|yDL3{r{AvFgO0LF#se}L>4(?#bO}hn_-EL`Gbt47LkAj*6wYzC%;sbuA=21F zo-hyvDRT+KzJh&QroB(3%3dIzU4d1MW9S>0bf3MiA77<*x#~-MinM`As zEz(*p8-)|lt!1tNxetS&!R|J6#+{qR!y3E^$ZMCyGULL%=P3JcF4MlFw8j~1kn#M% z(lNtM%98--Crqz)>}@f|c{zQNRYp@pxnO@~ z$Wqe>-1)V~?PUlF-jvi&Y*(Zjh7Yb^4^KA~L_cM-tSEACru)o^{knJ$==}3TGnAm5 zc$}Vsd5t?hY1>4=<5BPe8s2FOqeWRo+tz-=cvl(;$_`Zh3 z*D88AV7*P4mdv_6iw`g`?lx3n`iVIlk&16K{>DP6kx`{vg327tGYrdkw?nA6uJc2c zYs5JH8`eYmHm|82V?Uxqf~t^K5j<@6akZw`Efo9aznL~GR}{|H0vd&g2??ez`Cd25CRe$@bQ<}c589awAk@6(~LPe!N^63-adg1geA6^-a z((f=|C_Z7_Jqg<;O8OWC-X`hcLs~HQS3svE$4=PNQ0ev+;)JXWay>CL4IW&g1$D<_O&os{W6;t$PTM<&K7rjo zsBA}{W>%s&Z9N8Fl5S4Q<;w`SeAI@LC59_Ko~P%_KzP+PTP`t{eLBhMyhQzGvdfGa zgb!~~1v$n6SuT}HNJCu(X|{iZ8rZjhTLZxED8P;UOP3M(Q2fSBQ4u$6rN+!m`)&l< z#4(zau9)Koon($=%&B0vq!OLm>q{>4Tn*ik$22tYD_v@cZWi|9S)=gVr+$1hgNnAEtf zRLjOlgNd*~J2lKdg}3r8f5AI!?goA00e7+N_u<<$)jhy!Ac@ja;_P%Ojy=qSLh`Dg xYhSd|m6JOAxPL7oR%c^@|FtzCBV6QJJNx5G&(8f+uWpqzVxx9MUJu8S{{!%C384T0 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-horizontal-vertical-mode/style.json b/test/integration/render-tests/text-writing-mode/point_label/cjk-horizontal-vertical-mode/style.json new file mode 100644 index 00000000000..cd90cadaf3c --- /dev/null +++ b/test/integration/render-tests/text-writing-mode/point_label/cjk-horizontal-vertical-mode/style.json @@ -0,0 +1,68 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ -10, 10 ] + } + }, + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ 34, 0 ] + } + }, + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ -10, -10 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "{name_jp}", + "text-writing-mode": ["horizontal", "vertical"], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-size": 14 + } + } + ] +} diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-multiline-vertical-horizontal-mode/expected.png b/test/integration/render-tests/text-writing-mode/point_label/cjk-multiline-vertical-horizontal-mode/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4e4a10c5dfa90bc3d67aa3eb4132144c8e19155a GIT binary patch literal 3044 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 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-multiline-vertical-horizontal-mode/style.json b/test/integration/render-tests/text-writing-mode/point_label/cjk-multiline-vertical-horizontal-mode/style.json new file mode 100644 index 00000000000..134da23622d --- /dev/null +++ b/test/integration/render-tests/text-writing-mode/point_label/cjk-multiline-vertical-horizontal-mode/style.json @@ -0,0 +1,58 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス\nボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, 14 ] + } + }, + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス\nボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, -28 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "{name_jp}", + "text-writing-mode": ["vertical", "horizontal"], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-size": 12 + } + } + ] +} diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-punctuation-vertical-mode/expected.png b/test/integration/render-tests/text-writing-mode/point_label/cjk-punctuation-vertical-mode/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..5714c413f5b49f8e437803e154aec733c38130d3 GIT binary patch literal 3888 zcmaJ^dpHyN|7V76SQ{I|$laRjT$fwOTqBd*l58TO7`Yv(wqY|f$=y*lD=B1-6j5PV zopW+H?j>iajv|-Sg$JF!>-qkk=lA^n`F)<}bNS=-`8@CEeR;h;@4~(cO-c&7<>TQNoF)CSKPP1(pDHCz&b$mRadHDtfvlr90a!h3 zNZ$@Nev|Sgx6`;{s`-X}#0oO!ON(GEkSUB7Go8TMU(>8(W<3YFNT+ER3}#Gu4{!{4F)f$NiI3Y zMEMtyS+#jLd5#ggqjS|!4=+>21$>N0LSRD~PI*Z0XT7(snG8G*f<040ZbRZ0z;pR8 znEEtcHB%u)GeD#~@oS6HHHDj)cYF(4EQ-@cKA6Eh&Ip-U%x)QUs$&_Seu!}?vd!{| zdf3SK_@iP0HEY4LAV2BV{+Sjo$M7Sbm6A>M16D2BqfENDVy94%oGSR(0*QYV@Y$H`A!5q%*Bui+uae9zQ#SwKonnmP*|rmtN^b3I?3m)NfO_S&mr z)tWTz;iW^CzFW#oRSnF!z$-poaNO%xbj>lHwBd!M-zy`{?o~BZ&y8|C(bU~dgSdr= z-hAvum1CP$wWkC0Yl*aE9@LRy@|7&3J>cYoB@nyyIe*1SM_8iu+og^+sJ4Pm&qvrS zoW9SOnyfCl-7b^AEw=WzW#!ju13`+x_1i@i;I)aA(^DvXQ+9R0(7T z4UQVgQY9uO@SZL@A)+;vn^bpT$HPq0e#HeWTU66uhZRD2aW-6(QV^_6^DD{uLA2cm zoVK(bS@vI6Dw-X^?uWh)rGJ^BrwODPWY7&^(F`+Y!_KGyZnmRilv%#Ce?#t4arU-d zz(7)OpdLQHxpVB zvDh#xAhn|%wJzTJGZAx7IR2->sI}SxF;}JkcosZ45*)fTUi%#}LT_eWqppj@ZVCHpKA6&Bm18j*jjUG4IqH`@d4NMvdK^CvbT z`Z3jb6B|d?@+50aE&1$06LE4PZgA8~pxivM3-+CpfLOYm;v7Og7_tCki!58pt5k_H zD+zTa>m)k#M2d=Mv|h*}-lP3^R7ef;`6CDD9k<~1Ub^3= ztc|8qEs<`uI_OYG)eoe*E0$_K;LCX}Tpr6)DD9KScm*FS{P5Ub@LYVs<;IZ99^ed# zwR()^6PB;Ft#ZE4@|)>s&fA%yuKpPFxJ8{v_8)wu7u$|4mCpA(WeXPU5W1X@$UF72 zKi!6W&-V{ws?vpiVf482MU6S?>#!?9vrD+u!{}p{vWJj6XTF)<&HUORmmY7OWkYhT z70AcH4kMmcoSgU4T8tN34(3(9KSQ0rGZY(txTik`wKh$%d1CTeH{>^MW3dJgR6uSV z#s08oSJ8zMsT<7dp_nZ)#i_F~$Pn<0zQS#nm+ z@T4-MdoC$2Ul(pVCkdTw)SU{riYhQ+EJlRcQ(lQ>fC^%Oo;*#e;*~M62D%?jb$z_tXBXAn6L9kNwxACs- zU-4)LS8H8-vIg$vo@&fTQF_^NxroxpcIZbYq%#_5MAFh2Gqn{4GUc+*sw_v@uJt7Of<$g@|*WZwH5o*vQ)p{ zY(BMw+mp54KwK&oZJ?gy`DUw2aOS3i<~C$=j%4Kl!7Y6O!5>;wZMX52e0aSZVn0_A zE`d!xj@SdA*nm7BDLjZ)X(SOrCX{GWvQ5?AT?4j0TNISpp_Y8 z)wuN6)xIBZRYWRGkxgm^P=AM{Ayi$V*M;MBhruz{0tnWh`;V3R)kw`@J`i`rl?P3v zXcR2ip42K@NyiLa;8nfVdWLcyr~Q-*TaL1>gdj=#=Y=_0rwCmVU!D5r@eB%@~JAIBMRrSpK&f&HE0!=#Xyv?sRxNb#U zh*wP*K-ZNpHUT$8hZYIFypCJU7?lTN+(qW~csInSQ5G?N^1EIb4X8>g9#Lk-D4iGa zx3O!3dIoRq3~ag|$Bc&~M+m`Z;PQ3~XGl<%68k-7EG-uA`L$@VsM@0KsA_cGs+thV zl$6n&bx;#5tzAw(y{SyAfZR1YhF8`fGb(p|j3B(J9P5%hkq5Z{!BYA!*PnlPl&^4? zowAJ3jsg*V!$7dw)c^6-N;Z0u}EQ~@yP*Kk2v-~ z=*LsKs$#Q=XT1ly!b1u1gB9D!2SA?#R5S!CIf?^2Wp^Jjb8-RuwuY;=kx@p4=sC*PH9OJ3Z zB628Mnor9bs^^=%_!CZahxcg)90GVM#{L0G%a>wP$ya_#aDP`v_gx*zm>{Xl_mfbZ zGeoM|n=d}VGK$tc&x60FIFf(y(+wCR*U>QngZ>g{JVcP?0^C%zMV>sp*<0w_B)O0U zmx&dL(=C<$Uewu>KIGwyrC?ve4m&DaB}$LDn%xjikFjbn?wWVU?(X+^Q$Q{!|2zWO z@ZsfVscFVe8T6K%bl6345D5c`GqEt%qA&2zGyhlfb-F?3t^-u(0=r#%Ai;gVNQ6^v zb(l~`!6VrTru}Kxel8x$l7Ctib(o@cqtb}_MlUSyvK#-KNndPMu`>2*ZuGI4X=>HCy z{`?WvJaGWAElBE5W+-%3((;1X07x;F{9R#GiSehF4uHsMPghPacEYW0C2fd zqUbV-eX`ybHq45hG=pox_a z)=cug48C;nOi>51u%ept>TPT*e^ZG)%7fT1U2qO%&W5pNnG5)f{!yMlXp&2uxTk4) z!P#`Eib!Q#Xj7C#_Fwic(b!^1?@?%hg&sAhefnQQilJ{x$;ilX%}n9n8|bejBi3kv zFPh!+CboiH$NhU`d=^+ovpQKU3dbwl=0?tQ)A1Zn>WC_t*W04%{i4cJt*2ehzQJdn z3!lu4NrB%J>3O8Edfg1~u1CP#a0Of#C7bTcUC$6p&#_-`OICFl17EE{Wj&wM%x~FqFM3IIIFO_Ru1NB zwYp0;%y1Y20J!F1a4MlI6awhx0Z#RsI#GKB>J$%4gbGFayzqS(EuyB=EtQHOntzzZZwP)^ zMs10nRvh`4Rirr8<8wz7wL`NfwjS37%t^-s7|)}QrPwI%gzIe8F}%jGIYWVZh#@B@ zD%^W{lyg$j5t)_Y;D;(-H%9ADfo;jy#t$%p;+D6YPA0GmVmo8JYb)M;lxh0Tm#C8& zS@6(Ou4*|6@429?H81?+vIyv27{{Y5q7_3{lAsynPN4zI7yy zE1eiyGS=m}MgGxWtlDi~WbT#WDn_7AmoQEVSCLx zhIwwOnqyc3y!+1o78WD=V6EYQ3yayCr2OB1bChJ%f7@>(!?V7qM%$Kj!csnj0FQg_ H(X{^nwPn-7 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-punctuation-vertical-mode/style.json b/test/integration/render-tests/text-writing-mode/point_label/cjk-punctuation-vertical-mode/style.json new file mode 100644 index 00000000000..21df85e4f5c --- /dev/null +++ b/test/integration/render-tests/text-writing-mode/point_label/cjk-punctuation-vertical-mode/style.json @@ -0,0 +1,65 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name_cjk": "氣—到–身_什…戰.只。" + }, + "geometry": { + "type": "Point", + "coordinates": [ -30, 0 ] + } + }, + { + "type": "Feature", + "properties": { + "name_cjk": "(氣)到(身)(什)" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, 0] + } + }, + { + "type": "Feature", + "properties": { + "name_cjk": "“氣”到“身”“白”" + }, + "geometry": { + "type": "Point", + "coordinates": [ 30, 0 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "{name_cjk}", + "text-writing-mode": ["vertical"], + "text-font": [ "NotoCJK" ], + "text-size": 14 + } + } + ] +} diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-horizontal-mode/expected.png b/test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-horizontal-mode/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..f1ef07dea79039430de259a31eff65cdce00088b GIT binary patch literal 3570 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 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-horizontal-mode/style.json b/test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-horizontal-mode/style.json new file mode 100644 index 00000000000..25d953c108d --- /dev/null +++ b/test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-horizontal-mode/style.json @@ -0,0 +1,80 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, 8 ] + } + }, + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, 8 ] + } + }, + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, 8 ] + } + }, + { + "type": "Feature", + "properties": { + "name_jp": "マップ" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, -34 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "{name_jp}", + "text-writing-mode": ["vertical", "horizontal"], + "text-radial-offset": 1, + "text-variable-anchor": ["center", "left", "right", "bottom"], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-size": 14 + } + } + ] +} diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-mode/expected.png b/test/integration/render-tests/text-writing-mode/point_label/cjk-variable-anchors-vertical-mode/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..402d88f3c7627f9f98d98e1cfdfc109cc6744a93 GIT binary patch literal 3321 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 D)>x|_ literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-vertical-horizontal-mode/style.json b/test/integration/render-tests/text-writing-mode/point_label/cjk-vertical-horizontal-mode/style.json new file mode 100644 index 00000000000..a84a132044f --- /dev/null +++ b/test/integration/render-tests/text-writing-mode/point_label/cjk-vertical-horizontal-mode/style.json @@ -0,0 +1,68 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ -10, 8 ] + } + }, + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ 10, 8 ] + } + }, + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, -34 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "{name_jp}", + "text-writing-mode": ["vertical", "horizontal"], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-size": 14 + } + } + ] +} diff --git a/test/integration/render-tests/text-writing-mode/point_label/cjk-vertical-mode/expected.png b/test/integration/render-tests/text-writing-mode/point_label/cjk-vertical-mode/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..43b2d37adf80da7163f1e9726c3b86367c696cf5 GIT binary patch literal 2275 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 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/text-writing-mode/point_label/mixed-multiline-vertical-horizontal-mode/style.json b/test/integration/render-tests/text-writing-mode/point_label/mixed-multiline-vertical-horizontal-mode/style.json new file mode 100644 index 00000000000..8431de2c512 --- /dev/null +++ b/test/integration/render-tests/text-writing-mode/point_label/mixed-multiline-vertical-horizontal-mode/style.json @@ -0,0 +1,58 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name_jp": "マップボックス\nabc123\na1ボックス" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, 14 ] + } + }, + { + "type": "Feature", + "properties": { + "name_jp": "aマップボックス\nボックスabc123" + }, + "geometry": { + "type": "Point", + "coordinates": [ 0, -28 ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "point", + "layout": { + "text-field": "{name_jp}", + "text-writing-mode": ["vertical", "horizontal"], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-size": 12 + } + } + ] +} diff --git a/yarn.lock b/yarn.lock index 54886694f1b..0697e690681 100644 --- a/yarn.lock +++ b/yarn.lock @@ -224,7 +224,7 @@ "@babel/traverse" "^7.4.3" "@babel/types" "^7.4.0" -"@babel/highlight@^7.0.0": +"@babel/highlight@~7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==