diff --git a/rendering/cases/rtl-text-align/expected.png b/rendering/cases/rtl-text-align/expected.png new file mode 100644 index 0000000000..ca2dd0b456 Binary files /dev/null and b/rendering/cases/rtl-text-align/expected.png differ diff --git a/rendering/cases/rtl-text-align/main.js b/rendering/cases/rtl-text-align/main.js new file mode 100644 index 0000000000..0fa3452173 --- /dev/null +++ b/rendering/cases/rtl-text-align/main.js @@ -0,0 +1,192 @@ +import CircleStyle from '../../../src/ol/style/Circle.js'; +import Feature from '../../../src/ol/Feature.js'; +import Fill from '../../../src/ol/style/Fill.js'; +import Map from '../../../src/ol/Map.js'; +import Point from '../../../src/ol/geom/Point.js'; +import Stroke from '../../../src/ol/style/Stroke.js'; +import Style from '../../../src/ol/style/Style.js'; +import Text from '../../../src/ol/style/Text.js'; +import VectorLayer from '../../../src/ol/layer/Vector.js'; +import VectorSource from '../../../src/ol/source/Vector.js'; +import View from '../../../src/ol/View.js'; + +const vectorSource = new VectorSource(); +let feature; + +// Latin - end (right) +feature = new Feature({ + geometry: new Point([-10, 50]), +}); +feature.setStyle( + new Style({ + text: new Text({ + text: 'Latin', + font: '24px Ubuntu', + textAlign: 'end', + fill: new Fill({ + color: 'black', + }), + stroke: new Stroke({ + color: 'white', + }), + }), + image: new CircleStyle({ + radius: 10, + fill: new Fill({ + color: 'cyan', + }), + }), + }) +); +vectorSource.addFeature(feature); + +// Hebrew - start (right) +feature = new Feature({ + geometry: new Point([-10, 0]), +}); +feature.setStyle( + new Style({ + text: new Text({ + text: 'עִברִית', + font: '24px Ubuntu', + textAlign: 'start', + fill: new Fill({ + color: 'black', + }), + stroke: new Stroke({ + color: 'white', + }), + }), + image: new CircleStyle({ + radius: 10, + fill: new Fill({ + color: 'cyan', + }), + }), + }) +); +vectorSource.addFeature(feature); + +// Arabic - start (right) +feature = new Feature({ + geometry: new Point([-10, -50]), +}); +feature.setStyle( + new Style({ + text: new Text({ + text: 'عربى', + font: '24px Ubuntu', + textAlign: 'start', + fill: new Fill({ + color: 'black', + }), + stroke: new Stroke({ + color: 'white', + }), + }), + image: new CircleStyle({ + radius: 10, + fill: new Fill({ + color: 'cyan', + }), + }), + }) +); +vectorSource.addFeature(feature); + +// Latin - start (left) +feature = new Feature({ + geometry: new Point([10, 50]), +}); +feature.setStyle( + new Style({ + text: new Text({ + text: 'Latin', + font: '24px Ubuntu', + textAlign: 'start', + fill: new Fill({ + color: 'black', + }), + stroke: new Stroke({ + color: 'white', + }), + }), + image: new CircleStyle({ + radius: 10, + fill: new Fill({ + color: 'cyan', + }), + }), + }) +); +vectorSource.addFeature(feature); + +// Hebrew - end (left) +feature = new Feature({ + geometry: new Point([10, 0]), +}); +feature.setStyle( + new Style({ + text: new Text({ + text: 'עִברִית', + font: '24px Ubuntu', + textAlign: 'end', + fill: new Fill({ + color: 'black', + }), + stroke: new Stroke({ + color: 'white', + }), + }), + image: new CircleStyle({ + radius: 10, + fill: new Fill({ + color: 'cyan', + }), + }), + }) +); +vectorSource.addFeature(feature); + +// Arabic - end (left) +feature = new Feature({ + geometry: new Point([10, -50]), +}); +feature.setStyle( + new Style({ + text: new Text({ + text: 'عربى', + font: '24px Ubuntu', + textAlign: 'end', + fill: new Fill({ + color: 'black', + }), + stroke: new Stroke({ + color: 'white', + }), + }), + image: new CircleStyle({ + radius: 10, + fill: new Fill({ + color: 'cyan', + }), + }), + }) +); +vectorSource.addFeature(feature); + +new Map({ + pixelRatio: 1, + layers: [ + new VectorLayer({ + source: vectorSource, + }), + ], + target: 'map', + view: new View({ + center: [0, 0], + resolution: 1, + }), +}); + +render({tolerance: 0.01}); diff --git a/rendering/cases/text-style-linestring-nice/main.js b/rendering/cases/text-style-linestring-nice/main.js index 4dbdec77fe..9cfb1c0703 100644 --- a/rendering/cases/text-style-linestring-nice/main.js +++ b/rendering/cases/text-style-linestring-nice/main.js @@ -114,7 +114,7 @@ feature4.setStyle( text: 'negative offsetX', font: 'normal 400 10px/1 Ubuntu', offsetX: -10, - textAlign: 'start', + textAlign: 'end', textBaseline: 'top', placement: 'line', }), @@ -133,7 +133,7 @@ feature5.setStyle( font: '10px Ubuntu', offsetY: 5, scale: 0.7, - textAlign: 'end', + textAlign: 'start', placement: 'line', }), }) diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index d7a049c00b..1ec4c94eb0 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -75,6 +75,20 @@ function getDeclutterBox(replayImageOrLabelArgs) { return replayImageOrLabelArgs[3].declutterBox; } +const rtlRegEx = /[\u0591-\u07FF]/; + +/** + * @param {string} text Text. + * @param {string} align Alignment. + * @return {number} Text alignment. + */ +function horizontalTextAlign(text, align) { + if ((align === 'start' || align === 'end') && !rtlRegEx.test(text)) { + align = align === 'start' ? 'left' : 'right'; + } + return TEXT_ALIGN[align]; +} + class Executor { /** * @param {number} resolution Resolution. @@ -205,7 +219,10 @@ class Executor { textState.scale[0] * pixelRatio, textState.scale[1] * pixelRatio, ]; - const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign]; + const align = horizontalTextAlign( + text, + textState.textAlign || defaultTextAlign + ); const strokeWidth = strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0; @@ -541,7 +558,10 @@ class Executor { const strokeState = this.strokeStates[strokeKey]; const pixelRatio = this.pixelRatio; - const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign]; + const align = horizontalTextAlign( + text, + textState.textAlign || defaultTextAlign + ); const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline]; const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;