From 60da6aa1b8febaa15c86bf4cef566b9dccdcd6ee Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:19:47 +0300 Subject: [PATCH 01/47] support rtl characters (currently, only hebrew) at label to show in correct direction --- Source/DataSources/LabelGraphics.js | 15 ++- Source/DataSources/LabelVisualizer.js | 2 + Source/Scene/Label.js | 187 ++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 1 deletion(-) diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js index 026fa778b3b5..d93c0e556d21 100644 --- a/Source/DataSources/LabelGraphics.js +++ b/Source/DataSources/LabelGraphics.js @@ -48,6 +48,7 @@ define([ * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to set scale based on distance from the camera. * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this label will be displayed. + * @param {Property} [options.rtl=false] A Property specifying if to modify text when there is possibly rtl characters. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} */ @@ -94,6 +95,8 @@ define([ this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; + this._rtl = undefined; + this._rtlSubscribtion = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -319,7 +322,15 @@ define([ * @memberof LabelGraphics.prototype * @type {Property} */ - disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') + disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance'), + + /** + * Gets or sets the ability to modify text characters direction. + * @memberof LabelGraphics.prototype + * @type {Property} + * @default false + */ + rtl: createPropertyDescriptor('rtl') }); /** @@ -353,6 +364,7 @@ define([ result.scaleByDistance = this.scaleByDistance; result.distanceDisplayCondition = this.distanceDisplayCondition; result.disableDepthTestDistance = this.disableDepthTestDistance; + result.rtl = this.rtl; return result; }; @@ -390,6 +402,7 @@ define([ this.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance); this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); + this.rtl = defaultValue(this.rtl, source.rtl); }; return LabelGraphics; diff --git a/Source/DataSources/LabelVisualizer.js b/Source/DataSources/LabelVisualizer.js index 0407205174d0..b8b4d60afe58 100644 --- a/Source/DataSources/LabelVisualizer.js +++ b/Source/DataSources/LabelVisualizer.js @@ -50,6 +50,7 @@ define([ var defaultHorizontalOrigin = HorizontalOrigin.CENTER; var defaultVerticalOrigin = VerticalOrigin.CENTER; var defaultDisableDepthTestDistance = 0.0; + var defaultRtl = false; var position = new Cartesian3(); var fillColor = new Color(); @@ -166,6 +167,7 @@ define([ label.scaleByDistance = Property.getValueOrUndefined(labelGraphics._scaleByDistance, time, scaleByDistance); label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition); label.disableDepthTestDistance = Property.getValueOrDefault(labelGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); + label.rtl = Property.getValueOrDefault(labelGraphics._rtl, time, defaultRtl); } return true; }; diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 1d3b4803684a..e5800bd6ef9e 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -9,6 +9,7 @@ define([ '../Core/defineProperties', '../Core/DeveloperError', '../Core/DistanceDisplayCondition', + '../Core/freezeObject', '../Core/NearFarScalar', './Billboard', './HeightReference', @@ -25,6 +26,7 @@ define([ defineProperties, DeveloperError, DistanceDisplayCondition, + freezeObject, NearFarScalar, Billboard, HeightReference, @@ -109,6 +111,7 @@ define([ this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); this._distanceDisplayCondition = options.distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); + this._rtl = defaultValue(options.rtl, false); this._labelCollection = labelCollection; this._glyphs = []; @@ -257,6 +260,10 @@ define([ } //>>includeEnd('debug'); + if (this.rtl) { + value = this.reverseRtl(value); + } + if (this._text !== value) { this._text = value; rebindAllGlyphs(this); @@ -1013,6 +1020,23 @@ define([ } } } + }, + + /** + * Determines whether or not run the reverseRtl algorithm on the text of the label + * @memberof Label.prototype + * @type {Boolean} + * @default false + */ + rtl : { + get : function() { + return this._rtl; + }, + set : function(value) { + if (this._rtl !== value) { + this._rtl = value; + } + } } }); @@ -1179,5 +1203,168 @@ define([ return false; }; + function declareTypes() { + var TextTypes = { + LTR : 0, + RTL : 1, + WEAK : 2, + BRACKETS : 3 + }; + return freezeObject(TextTypes); + } + + function convertTextToTypes(text, rtlDir, rtlChars) { + var ltrChars = /[a-zA-Z0-9]/; + var bracketsChars = /[()[\]{}<>]/; + var parsedText = []; + var word = ''; + var types = declareTypes(); + var lastType = rtlDir ? types.RTL : types.LTR; + var currentType = ''; + var textLength = text.length; + for (var textIndex = 0; textIndex < textLength; ++textIndex) { + var character = text.charAt(textIndex); + if (rtlChars.test(character)) { + currentType = types.RTL; + } + else if (ltrChars.test(character)) { + currentType = types.LTR; + } + else if (bracketsChars.test(character)) { + currentType = types.BRACKETS; + } + else { + currentType = types.WEAK; + } + + if (lastType === currentType && currentType !== types.BRACKETS) { + word += character; + } + else { + parsedText.push({Type : lastType, Word : word}); + lastType = currentType; + word = character; + } + } + parsedText.push({Type : currentType, Word : word}); + return parsedText; + } + + function reverseWord(word) { + return word.split('').reverse().join(''); + } + + function spliceWord(result, pointer, word) { + return result.slice(0, pointer) + word + result.slice(pointer); + } + + function reverseBrackets(bracket) { + switch(bracket) { + case '(': + return ')'; + case ')': + return '('; + case '[': + return ']'; + case ']': + return '['; + case '{': + return '}'; + case '}': + return '{'; + case '<': + return '>'; + case '>': + return '<'; + } + } + + /** + * + * @param {String} text the text to parse and reorder + * @returns {String} the text as rtl direction + */ + Label.prototype.reverseRtl = function(text) { + var rtlChars = /[א-ת]/; + var rtlDir = rtlChars.test(text.charAt(0)); + var parsedText = convertTextToTypes(text, rtlDir, rtlChars); + + var types = declareTypes(); + var result = ''; + var splicePointer = 0; + for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { + var subText = parsedText[wordIndex]; + var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; + if(rtlDir) { + if (subText.Type === types.RTL) { + result = reverseWord(subText.Word) + result; + splicePointer = 0; + } + else if (subText.Type === types.LTR) { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (parsedText[wordIndex -1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + } + else { + result = spliceWord(result,splicePointer, subText.Word); + } + } + } + } + else { + if (subText.Type === types.RTL) { + result = spliceWord(result, splicePointer, reverseWord(subText.Word)); + } + else if (subText.Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (wordIndex > 0) { + if (parsedText[wordIndex -1].Type === types.RTL) { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (parsedText[wordIndex +1].Type === types.RTL) { + result = spliceWord(result, splicePointer, reverse); + } + } + else { + result += subText.Word; + } + } + else { + result += subText.Word; + splicePointer = result.length; + } + } + else { + result += subText.Word; + splicePointer = result.length; + } + } + } + } + + return result; + }; + return Label; }); From fc812bca3f5eaad4916a2eae58a9adcbbd9a5568 Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:22:02 +0300 Subject: [PATCH 02/47] add example of supporting rtl characters to sandcastle --- Apps/Sandcastle/gallery/Labels.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index aaf32e0826d2..d77681485b4e 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -121,6 +121,17 @@ }); } +function setRtl() { + Sandcastle.declare(setRtl); + viewer.entities.add({ + position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), + label : { + text : 'שלום', + rtl : true + } + }); +} + Sandcastle.addToolbarMenu([{ text : 'Add label', onselect : function() { @@ -157,6 +168,12 @@ scaleByDistance(); Sandcastle.highlight(scaleByDistance); } +}, { + text : 'Set rtl', + onselect : function() { + setRtl(); + Sandcastle.highlight(setRtl); + } }]); Sandcastle.reset = function() { From 8762607e32af765d7a802a96cab6dd8b71f5d873 Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:23:21 +0300 Subject: [PATCH 03/47] add unit tests to check rtl (unfortunately, not working yet) --- Specs/DataSources/LabelGraphicsSpec.js | 13 ++++++++- Specs/DataSources/LabelVisualizerSpec.js | 4 +++ Specs/Scene/LabelCollectionSpec.js | 36 ++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/Specs/DataSources/LabelGraphicsSpec.js b/Specs/DataSources/LabelGraphicsSpec.js index 70e9d6c9c3f7..ffc83a946776 100644 --- a/Specs/DataSources/LabelGraphicsSpec.js +++ b/Specs/DataSources/LabelGraphicsSpec.js @@ -41,7 +41,8 @@ defineSuite([ pixelOffsetScaleByDistance : new NearFarScalar(13, 14, 15, 16), scaleByDistance : new NearFarScalar(17, 18, 19, 20), distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0), - disableDepthTestDistance : 10.0 + disableDepthTestDistance : 10.0, + rtl : false }; var label = new LabelGraphics(options); @@ -61,6 +62,7 @@ defineSuite([ expect(label.scaleByDistance).toBeInstanceOf(ConstantProperty); expect(label.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(label.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); + expect(label.rtl).toBeInstanceOf(ConstantProperty); expect(label.text.getValue()).toEqual(options.text); expect(label.font.getValue()).toEqual(options.font); @@ -78,6 +80,7 @@ defineSuite([ expect(label.scaleByDistance.getValue()).toEqual(options.scaleByDistance); expect(label.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); expect(label.disableDepthTestDistance.getValue()).toEqual(options.disableDepthTestDistance); + expect(label.rtl.getValue()).toEqual(options.rtl); }); it('merge assigns unassigned properties', function() { @@ -99,6 +102,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(false); var target = new LabelGraphics(); target.merge(source); @@ -120,6 +124,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(source.scaleByDistance); expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(source.disableDepthTestDistance); + expect(target.rtl).toBe(source.rtl); }); it('merge does not assign assigned properties', function() { @@ -141,6 +146,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(true); var text = new ConstantProperty('my text'); var font = new ConstantProperty('10px serif'); @@ -159,6 +165,7 @@ defineSuite([ var scaleByDistance = new ConstantProperty(new NearFarScalar()); var distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var disableDepthTestDistance = new ConstantProperty(20.0); + var rtl = new ConstantProperty(false); var target = new LabelGraphics(); target.text = text; @@ -178,6 +185,7 @@ defineSuite([ target.scaleByDistance = scaleByDistance; target.distanceDisplayCondition = distanceDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; + target.rtl = rtl; target.merge(source); @@ -198,6 +206,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(scaleByDistance); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); + expect(target.rtl).toBe(rtl); }); it('clone works', function() { @@ -219,6 +228,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(false); var result = source.clone(); expect(result.text).toBe(source.text); @@ -238,6 +248,7 @@ defineSuite([ expect(result.scaleByDistance).toBe(source.scaleByDistance); expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(result.disableDepthTestDistance).toBe(source.disableDepthTestDistance); + expect(result.rtl).toBe(source.rtl); }); it('merge throws if source undefined', function() { diff --git a/Specs/DataSources/LabelVisualizerSpec.js b/Specs/DataSources/LabelVisualizerSpec.js index 591f0fe84094..84837fbff8fb 100644 --- a/Specs/DataSources/LabelVisualizerSpec.js +++ b/Specs/DataSources/LabelVisualizerSpec.js @@ -157,6 +157,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(10.0); + label.rtl = new ConstantProperty(false); visualizer.update(time); @@ -184,6 +185,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); + expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); testObject.position = new ConstantProperty(new Cartesian3(5678, 1234, 1293434)); label.text = new ConstantProperty('b'); @@ -203,6 +205,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(20.0); + label.rtl = new ConstantProperty(true); visualizer.update(time); expect(l.position).toEqual(testObject.position.getValue(time)); @@ -223,6 +226,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); + expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); label.show = new ConstantProperty(false); visualizer.update(time); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 61b348fe3dc5..3459972a538c 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1841,6 +1841,42 @@ defineSuite([ expect(newlinesBbox.height).toBeGreaterThan(originalBbox.height); }); + it('should not modify text when rtl is false', function() { + var text = 'bla bla bla'; + var label = labels.add({ + text : text + }); + scene.renderForSpecs(); + + expect(label.rtl).toEqual(false); + expect(label.text).toEqual(text); + }); + + it('should not modify text when rtl is true and there is no hebrew characters', function() { + var text = 'bla bla bla'; + var label = labels.add({ + text : text, + rtl : true + }); + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).toEqual(text); + }); + + it('should reverse text when there is only hebrew characters and rtl is true', function() { + var text = 'שלום'; + var label = labels.add({ + text : text + }); + label.rtl = true; + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(text.split('').reverse().join('')); + }); + }, 'WebGL'); it('computes bounding sphere in 3D', function() { From 64fd8d2c8ccb782c9909b61f5f0d7b9138fcf455 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 00:13:34 +0300 Subject: [PATCH 04/47] RTL labels now work in constructor Adjusted the text position and used its setter in order to construct the Label Adjusted tests accordinglt --- Source/Scene/Label.js | 6 +++--- Specs/Scene/LabelCollectionSpec.js | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index e5800bd6ef9e..65b537ffbae3 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -87,8 +87,6 @@ define([ throw new DeveloperError('disableDepthTestDistance must be greater than 0.0.'); } //>>includeEnd('debug'); - - this._text = defaultValue(options.text, ''); this._show = defaultValue(options.show, true); this._font = defaultValue(options.font, '30px sans-serif'); this._fillColor = Color.clone(defaultValue(options.fillColor, Color.WHITE)); @@ -111,7 +109,6 @@ define([ this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); this._distanceDisplayCondition = options.distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); - this._rtl = defaultValue(options.rtl, false); this._labelCollection = labelCollection; this._glyphs = []; @@ -126,6 +123,9 @@ define([ this._clusterShow = true; + this._rtl = defaultValue(options.rtl, false); + this.text = defaultValue(options.text, ''); + this._updateClamping(); } diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 3459972a538c..d9ed3a573eaa 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1867,9 +1867,10 @@ defineSuite([ it('should reverse text when there is only hebrew characters and rtl is true', function() { var text = 'שלום'; var label = labels.add({ - text : text + text : text, + rtl: true }); - label.rtl = true; + scene.renderForSpecs(); expect(label.rtl).toEqual(true); From 5826c7e4525a0efd82c3364cfc7592f00a140234 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 00:40:28 +0300 Subject: [PATCH 05/47] Upadated Changes.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 34f80cc4f458..f2abb2eebe87 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Change Log ========== ### 1.37 - 2017-09-01 - +* Added suppport for RTL labels * Fixed `replaceState` bug that was causing the `CesiumViewer` demo application to crash in Safari and iOS * Fixed issue where `Model` and `BillboardCollection` would throw an error if the globe is undefined [#5638](https://github.com/AnalyticalGraphicsInc/cesium/issues/5638) * Fixed issue where the `Model` glTF cache loses reference to the model's buffer data. [#5720](https://github.com/AnalyticalGraphicsInc/cesium/issues/5720) From 775e63789e2deecbef9d89f4dcdcb29f92ebd70b Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 12:07:21 +0300 Subject: [PATCH 06/47] Updated contributors --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 089f762d9b52..f30dc69a9e7f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -86,6 +86,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Jason Crow](https://github.com/jason-crow) * [Flightradar24 AB](https://www.flightradar24.com) * [Aleksei Kalmykov](https://github.com/kalmykov) +* [webiks](https://www.webiks.com) + * [Hod Bauer](https://github.com/hodbauer) ## [Individual CLA](Documentation/Contributors/CLAs/individual-cla-agi-v1.0.txt) * [Victor Berchet](https://github.com/vicb) From f09b8838ab0a39a47a054e77045d06a4efb15c29 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 16:30:39 +0300 Subject: [PATCH 07/47] Support for \n in RTL --- Source/Scene/Label.js | 113 ++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 75c8d26894c7..0eb71c3f310d 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -1303,70 +1303,76 @@ define([ * @param {String} text the text to parse and reorder * @returns {String} the text as rtl direction */ - Label.prototype.reverseRtl = function(text) { + Label.prototype.reverseRtl = function(value) { var rtlChars = /[א-ת]/; - var rtlDir = rtlChars.test(text.charAt(0)); - var parsedText = convertTextToTypes(text, rtlDir, rtlChars); - - var types = declareTypes(); + var texts = value.split('\n'); var result = ''; - var splicePointer = 0; - for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { - var subText = parsedText[wordIndex]; - var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; - if(rtlDir) { - if (subText.Type === types.RTL) { - result = reverseWord(subText.Word) + result; - splicePointer = 0; - } - else if (subText.Type === types.LTR) { - result = spliceWord(result,splicePointer, subText.Word); - splicePointer += subText.Word.length; - } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (parsedText[wordIndex -1].Type === types.RTL) { - result = reverse + result; + for (var i = 0; i < texts.length; i++) { + var text = texts[i]; + var rtlDir = rtlChars.test(text.charAt(0)); + var parsedText = convertTextToTypes(text, rtlDir, rtlChars); + + var types = declareTypes(); + + var splicePointer = 0; + for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { + var subText = parsedText[wordIndex]; + var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; + if(rtlDir) { + if (subText.Type === types.RTL) { + result = reverseWord(subText.Word) + result; splicePointer = 0; } - else { - if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.RTL) { - result = reverse + result; - splicePointer = 0; + else if (subText.Type === types.LTR) { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (parsedText[wordIndex -1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } } else { result = spliceWord(result,splicePointer, subText.Word); - splicePointer += subText.Word.length; } - } - else { - result = spliceWord(result,splicePointer, subText.Word); - } } } - } - else { - if (subText.Type === types.RTL) { - result = spliceWord(result, splicePointer, reverseWord(subText.Word)); - } - else if (subText.Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; - } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (wordIndex > 0) { - if (parsedText[wordIndex -1].Type === types.RTL) { - if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; + else if (subText.Type === types.RTL) { + result = spliceWord(result, splicePointer, reverseWord(subText.Word)); + } + else if (subText.Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (wordIndex > 0) { + if (parsedText[wordIndex -1].Type === types.RTL) { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (parsedText[wordIndex +1].Type === types.RTL) { + result = spliceWord(result, splicePointer, reverse); + } } - else if (parsedText[wordIndex +1].Type === types.RTL) { - result = spliceWord(result, splicePointer, reverse); + else { + result += subText.Word; } } else { result += subText.Word; + splicePointer = result.length; } } else { @@ -1374,14 +1380,11 @@ define([ splicePointer = result.length; } } - else { - result += subText.Word; - splicePointer = result.length; - } - } + } + if (i < texts.length - 1) { + result += '\n'; } } - return result; }; From 70c3ab64b957c70b33b4dc9eb29c13b88d01aa0b Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 16:46:33 +0300 Subject: [PATCH 08/47] Cache RTL original string In order to prevent the function from running on each update if no change was made to the text --- Source/Scene/Label.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 0eb71c3f310d..9deb7cce9b05 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -285,6 +285,10 @@ define([ //>>includeEnd('debug'); if (this.rtl) { + if (this._originalValue === value) { + return; + } + this._originalValue = value; value = this.reverseRtl(value); } From 8d18aa3b30ece2430dbc5af802041bfd494bc5e2 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 16:56:09 +0300 Subject: [PATCH 09/47] Prevent glyphs parser from running for nought --- Source/Scene/Label.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 9deb7cce9b05..bfa13a93f398 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -286,6 +286,7 @@ define([ if (this.rtl) { if (this._originalValue === value) { + value = this._text; return; } this._originalValue = value; From 52dc64f0a019c29973eafd4a3a8a4f28130c1102 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 17:13:15 +0300 Subject: [PATCH 10/47] Documentation examples --- Source/Scene/Label.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index bfa13a93f398..172977aa038e 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -1051,6 +1051,25 @@ define([ * @memberof Label.prototype * @type {Boolean} * @default false + * + * @example + * // Example 1. + * // Set a label's rtl during init + * var myLabelEntity = viewer.entities.add({ + * id: 'my label', + * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', + * rtl: true + * }); + * + * @example + * // Example 2. + * var myLabelEntity = viewer.entities.add({ + * id: 'my label', + * text: 'English text' + * }); + * // Set a label's rtl after init + * myLabelEntity.rtl = true; + * myLabelEntity.text = 'טקסט חדש' */ rtl : { get : function() { From 8350668bf8f6249de1a01ce06f2ad75d291fbc57 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 17:14:14 +0300 Subject: [PATCH 11/47] Updated contributors list --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f30dc69a9e7f..257620f225be 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -88,6 +88,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Aleksei Kalmykov](https://github.com/kalmykov) * [webiks](https://www.webiks.com) * [Hod Bauer](https://github.com/hodbauer) + * [Yonatan Kra](https://github.com/yonatankra) ## [Individual CLA](Documentation/Contributors/CLAs/individual-cla-agi-v1.0.txt) * [Victor Berchet](https://github.com/vicb) @@ -154,4 +155,5 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Rishabh Shah](https://github.com/rms13) * [Rudraksha Shah](https://github.com/Rudraksha20) * [Cody Guldner](https://github.com/burn123) +* [Yonatan Kra](https://github.com/yonatankra) From a0f0c1a711546165f50fe52735d1fb739bf360fc Mon Sep 17 00:00:00 2001 From: hodbauer Date: Sun, 3 Sep 2017 10:18:21 +0300 Subject: [PATCH 12/47] * fix descriptions * make `reverseRtl` private * fix multi-lines support * add complex test * fix some edge cases: * special characters after rtl characters when direction is not rtl * "Weak" characters after brackets at rtl direction --- Apps/Sandcastle/gallery/Labels.html | 4 +- CHANGES.md | 3 +- Source/Scene/Label.js | 109 +++++++++++++++------------- Specs/Scene/LabelCollectionSpec.js | 15 ++++ 4 files changed, 79 insertions(+), 52 deletions(-) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index d77681485b4e..edd8d3669696 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -126,7 +126,7 @@ viewer.entities.add({ position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), label : { - text : 'שלום', + text : 'Master (אדון): Hello\nתלמיד (student): שלום', rtl : true } }); @@ -169,7 +169,7 @@ Sandcastle.highlight(scaleByDistance); } }, { - text : 'Set rtl', + text : 'Set label with right-to-left language', onselect : function() { setRtl(); Sandcastle.highlight(setRtl); diff --git a/CHANGES.md b/CHANGES.md index f2abb2eebe87..3d435f02a949 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,8 @@ Change Log ========== ### 1.37 - 2017-09-01 -* Added suppport for RTL labels + +* Added support for right-to-left languages in labels * Fixed `replaceState` bug that was causing the `CesiumViewer` demo application to crash in Safari and iOS * Fixed issue where `Model` and `BillboardCollection` would throw an error if the globe is undefined [#5638](https://github.com/AnalyticalGraphicsInc/cesium/issues/5638) * Fixed issue where the `Model` glTF cache loses reference to the model's buffer data. [#5720](https://github.com/AnalyticalGraphicsInc/cesium/issues/5720) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 172977aa038e..b5e04ace3d19 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -290,7 +290,7 @@ define([ return; } this._originalValue = value; - value = this.reverseRtl(value); + value = reverseRtl(value); } if (this._text !== value) { @@ -1056,20 +1056,24 @@ define([ * // Example 1. * // Set a label's rtl during init * var myLabelEntity = viewer.entities.add({ - * id: 'my label', - * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', - * rtl: true + * label: { + * id: 'my label', + * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', + * rtl: true + * } * }); * * @example * // Example 2. * var myLabelEntity = viewer.entities.add({ - * id: 'my label', - * text: 'English text' + * label: { + * id: 'my label', + * text: 'English text' + * } * }); * // Set a label's rtl after init * myLabelEntity.rtl = true; - * myLabelEntity.text = 'טקסט חדש' + * myLabelEntity.text = 'טקסט חדש'; */ rtl : { get : function() { @@ -1324,10 +1328,11 @@ define([ /** * - * @param {String} text the text to parse and reorder + * @param {String} value the text to parse and reorder * @returns {String} the text as rtl direction + * @private */ - Label.prototype.reverseRtl = function(value) { + function reverseRtl(value) { var rtlChars = /[א-ת]/; var texts = value.split('\n'); var result = ''; @@ -1339,78 +1344,84 @@ define([ var types = declareTypes(); var splicePointer = 0; - for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { + var line = ''; + for (var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { var subText = parsedText[wordIndex]; var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; - if(rtlDir) { + if (rtlDir) { if (subText.Type === types.RTL) { - result = reverseWord(subText.Word) + result; + line = reverseWord(subText.Word) + line; splicePointer = 0; } else if (subText.Type === types.LTR) { - result = spliceWord(result,splicePointer, subText.Word); + line = spliceWord(line, splicePointer, subText.Word); splicePointer += subText.Word.length; } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (parsedText[wordIndex -1].Type === types.RTL) { - result = reverse + result; + else if (subText.Type === types.WEAK || subText.Type === types.BRACKETS) { + if (subText.Type === types.WEAK && parsedText[wordIndex - 1].Type === types.BRACKETS) { + line = reverseWord(subText.Word) + line; + } + else if (parsedText[wordIndex - 1].Type === types.RTL) { + line = reverse + line; splicePointer = 0; } - else if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.RTL) { - result = reverse + result; - splicePointer = 0; - } - else { - result = spliceWord(result,splicePointer, subText.Word); - splicePointer += subText.Word.length; - } + else if (parsedText.length > wordIndex + 1) { + if (parsedText[wordIndex + 1].Type === types.RTL) { + line = reverse + line; + splicePointer = 0; } else { - result = spliceWord(result,splicePointer, subText.Word); + line = spliceWord(line, splicePointer, subText.Word); + splicePointer += subText.Word.length; } + } + else { + line = spliceWord(line, splicePointer, subText.Word); + } } } else if (subText.Type === types.RTL) { - result = spliceWord(result, splicePointer, reverseWord(subText.Word)); - } - else if (subText.Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; - } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (wordIndex > 0) { - if (parsedText[wordIndex -1].Type === types.RTL) { - if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; - } - else if (parsedText[wordIndex +1].Type === types.RTL) { - result = spliceWord(result, splicePointer, reverse); - } + line = spliceWord(line, splicePointer, reverseWord(subText.Word)); + } + else if (subText.Type === types.LTR) { + line += subText.Word; + splicePointer = line.length; + } + else if (subText.Type === types.WEAK || subText.Type === types.BRACKETS) { + if (wordIndex > 0) { + if (parsedText[wordIndex - 1].Type === types.RTL) { + if (parsedText.length > wordIndex + 1) { + if (parsedText[wordIndex + 1].Type === types.RTL) { + line = spliceWord(line, splicePointer, reverse); } else { - result += subText.Word; + line += subText.Word; + splicePointer = line.length; } } else { - result += subText.Word; - splicePointer = result.length; + line += subText.Word; } } else { - result += subText.Word; - splicePointer = result.length; + line += subText.Word; + splicePointer = line.length; } } + else { + line += subText.Word; + splicePointer = line.length; + } + } } + + result += line; if (i < texts.length - 1) { result += '\n'; } } return result; - }; + } return Label; }); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 0ff08a6035b9..dd36426e6bc1 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1877,6 +1877,21 @@ defineSuite([ expect(label.text).toEqual(text.split('').reverse().join('')); }); + it('should reverse part of text when there is mix of right-to-left and other kind of characters and rtl is true', function() { + var text = 'Master (אדון): "Hello"\nתלמיד (student): "שלום"'; + var expectedText = 'Master (ןודא): "Hello"\n"םולש" :(student) דימלת'; + var label = labels.add({ + text : text, + rtl: true + }); + + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(expectedText); + }); + }, 'WebGL'); it('computes bounding sphere in 3D', function() { From 41cea836fc176c5f0d966b8b7eb5e1e152549e8f Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:19:47 +0300 Subject: [PATCH 13/47] support rtl characters (currently, only hebrew) at label to show in correct direction --- Source/DataSources/LabelGraphics.js | 15 ++- Source/DataSources/LabelVisualizer.js | 2 + Source/Scene/Label.js | 187 ++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 1 deletion(-) diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js index 52cf604de454..0b8b81217810 100644 --- a/Source/DataSources/LabelGraphics.js +++ b/Source/DataSources/LabelGraphics.js @@ -47,6 +47,7 @@ define([ * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to set scale based on distance from the camera. * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this label will be displayed. + * @param {Property} [options.rtl=false] A Property specifying if to modify text when there is possibly rtl characters. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} */ @@ -93,6 +94,8 @@ define([ this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; + this._rtl = undefined; + this._rtlSubscribtion = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -318,7 +321,15 @@ define([ * @memberof LabelGraphics.prototype * @type {Property} */ - disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') + disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance'), + + /** + * Gets or sets the ability to modify text characters direction. + * @memberof LabelGraphics.prototype + * @type {Property} + * @default false + */ + rtl: createPropertyDescriptor('rtl') }); /** @@ -352,6 +363,7 @@ define([ result.scaleByDistance = this.scaleByDistance; result.distanceDisplayCondition = this.distanceDisplayCondition; result.disableDepthTestDistance = this.disableDepthTestDistance; + result.rtl = this.rtl; return result; }; @@ -389,6 +401,7 @@ define([ this.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance); this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); + this.rtl = defaultValue(this.rtl, source.rtl); }; return LabelGraphics; diff --git a/Source/DataSources/LabelVisualizer.js b/Source/DataSources/LabelVisualizer.js index 208fd19c31c0..87df78e5692c 100644 --- a/Source/DataSources/LabelVisualizer.js +++ b/Source/DataSources/LabelVisualizer.js @@ -49,6 +49,7 @@ define([ var defaultHorizontalOrigin = HorizontalOrigin.CENTER; var defaultVerticalOrigin = VerticalOrigin.CENTER; var defaultDisableDepthTestDistance = 0.0; + var defaultRtl = false; var position = new Cartesian3(); var fillColor = new Color(); @@ -165,6 +166,7 @@ define([ label.scaleByDistance = Property.getValueOrUndefined(labelGraphics._scaleByDistance, time, scaleByDistance); label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition); label.disableDepthTestDistance = Property.getValueOrDefault(labelGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); + label.rtl = Property.getValueOrDefault(labelGraphics._rtl, time, defaultRtl); } return true; }; diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 813522182a0e..c02481e6b211 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -8,6 +8,7 @@ define([ '../Core/defineProperties', '../Core/DeveloperError', '../Core/DistanceDisplayCondition', + '../Core/freezeObject', '../Core/NearFarScalar', './Billboard', './HeightReference', @@ -24,6 +25,7 @@ define([ defineProperties, DeveloperError, DistanceDisplayCondition, + freezeObject, NearFarScalar, Billboard, HeightReference, @@ -133,6 +135,7 @@ define([ this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); this._distanceDisplayCondition = distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); + this._rtl = defaultValue(options.rtl, false); this._labelCollection = labelCollection; this._glyphs = []; @@ -279,6 +282,10 @@ define([ } //>>includeEnd('debug'); + if (this.rtl) { + value = this.reverseRtl(value); + } + if (this._text !== value) { this._text = value; rebindAllGlyphs(this); @@ -1030,6 +1037,23 @@ define([ } } } + }, + + /** + * Determines whether or not run the reverseRtl algorithm on the text of the label + * @memberof Label.prototype + * @type {Boolean} + * @default false + */ + rtl : { + get : function() { + return this._rtl; + }, + set : function(value) { + if (this._rtl !== value) { + this._rtl = value; + } + } } }); @@ -1196,5 +1220,168 @@ define([ return false; }; + function declareTypes() { + var TextTypes = { + LTR : 0, + RTL : 1, + WEAK : 2, + BRACKETS : 3 + }; + return freezeObject(TextTypes); + } + + function convertTextToTypes(text, rtlDir, rtlChars) { + var ltrChars = /[a-zA-Z0-9]/; + var bracketsChars = /[()[\]{}<>]/; + var parsedText = []; + var word = ''; + var types = declareTypes(); + var lastType = rtlDir ? types.RTL : types.LTR; + var currentType = ''; + var textLength = text.length; + for (var textIndex = 0; textIndex < textLength; ++textIndex) { + var character = text.charAt(textIndex); + if (rtlChars.test(character)) { + currentType = types.RTL; + } + else if (ltrChars.test(character)) { + currentType = types.LTR; + } + else if (bracketsChars.test(character)) { + currentType = types.BRACKETS; + } + else { + currentType = types.WEAK; + } + + if (lastType === currentType && currentType !== types.BRACKETS) { + word += character; + } + else { + parsedText.push({Type : lastType, Word : word}); + lastType = currentType; + word = character; + } + } + parsedText.push({Type : currentType, Word : word}); + return parsedText; + } + + function reverseWord(word) { + return word.split('').reverse().join(''); + } + + function spliceWord(result, pointer, word) { + return result.slice(0, pointer) + word + result.slice(pointer); + } + + function reverseBrackets(bracket) { + switch(bracket) { + case '(': + return ')'; + case ')': + return '('; + case '[': + return ']'; + case ']': + return '['; + case '{': + return '}'; + case '}': + return '{'; + case '<': + return '>'; + case '>': + return '<'; + } + } + + /** + * + * @param {String} text the text to parse and reorder + * @returns {String} the text as rtl direction + */ + Label.prototype.reverseRtl = function(text) { + var rtlChars = /[א-ת]/; + var rtlDir = rtlChars.test(text.charAt(0)); + var parsedText = convertTextToTypes(text, rtlDir, rtlChars); + + var types = declareTypes(); + var result = ''; + var splicePointer = 0; + for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { + var subText = parsedText[wordIndex]; + var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; + if(rtlDir) { + if (subText.Type === types.RTL) { + result = reverseWord(subText.Word) + result; + splicePointer = 0; + } + else if (subText.Type === types.LTR) { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (parsedText[wordIndex -1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + } + else { + result = spliceWord(result,splicePointer, subText.Word); + } + } + } + } + else { + if (subText.Type === types.RTL) { + result = spliceWord(result, splicePointer, reverseWord(subText.Word)); + } + else if (subText.Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (wordIndex > 0) { + if (parsedText[wordIndex -1].Type === types.RTL) { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (parsedText[wordIndex +1].Type === types.RTL) { + result = spliceWord(result, splicePointer, reverse); + } + } + else { + result += subText.Word; + } + } + else { + result += subText.Word; + splicePointer = result.length; + } + } + else { + result += subText.Word; + splicePointer = result.length; + } + } + } + } + + return result; + }; + return Label; }); From 84354e4caeb04072f85f1d6ee53a706e66ecc2da Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:22:02 +0300 Subject: [PATCH 14/47] add example of supporting rtl characters to sandcastle --- Apps/Sandcastle/gallery/Labels.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index aaf32e0826d2..d77681485b4e 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -121,6 +121,17 @@ }); } +function setRtl() { + Sandcastle.declare(setRtl); + viewer.entities.add({ + position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), + label : { + text : 'שלום', + rtl : true + } + }); +} + Sandcastle.addToolbarMenu([{ text : 'Add label', onselect : function() { @@ -157,6 +168,12 @@ scaleByDistance(); Sandcastle.highlight(scaleByDistance); } +}, { + text : 'Set rtl', + onselect : function() { + setRtl(); + Sandcastle.highlight(setRtl); + } }]); Sandcastle.reset = function() { From c22adcfa8441f30624f5a8ef172b59b31da9d18a Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:23:21 +0300 Subject: [PATCH 15/47] add unit tests to check rtl (unfortunately, not working yet) --- Specs/DataSources/LabelGraphicsSpec.js | 13 ++++++++- Specs/DataSources/LabelVisualizerSpec.js | 4 +++ Specs/Scene/LabelCollectionSpec.js | 36 ++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/Specs/DataSources/LabelGraphicsSpec.js b/Specs/DataSources/LabelGraphicsSpec.js index 282fc3d7a824..1539dbbe7b35 100644 --- a/Specs/DataSources/LabelGraphicsSpec.js +++ b/Specs/DataSources/LabelGraphicsSpec.js @@ -40,7 +40,8 @@ defineSuite([ pixelOffsetScaleByDistance : new NearFarScalar(13, 14, 15, 16), scaleByDistance : new NearFarScalar(17, 18, 19, 20), distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0), - disableDepthTestDistance : 10.0 + disableDepthTestDistance : 10.0, + rtl : false }; var label = new LabelGraphics(options); @@ -60,6 +61,7 @@ defineSuite([ expect(label.scaleByDistance).toBeInstanceOf(ConstantProperty); expect(label.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(label.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); + expect(label.rtl).toBeInstanceOf(ConstantProperty); expect(label.text.getValue()).toEqual(options.text); expect(label.font.getValue()).toEqual(options.font); @@ -77,6 +79,7 @@ defineSuite([ expect(label.scaleByDistance.getValue()).toEqual(options.scaleByDistance); expect(label.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); expect(label.disableDepthTestDistance.getValue()).toEqual(options.disableDepthTestDistance); + expect(label.rtl.getValue()).toEqual(options.rtl); }); it('merge assigns unassigned properties', function() { @@ -98,6 +101,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(false); var target = new LabelGraphics(); target.merge(source); @@ -119,6 +123,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(source.scaleByDistance); expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(source.disableDepthTestDistance); + expect(target.rtl).toBe(source.rtl); }); it('merge does not assign assigned properties', function() { @@ -140,6 +145,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(true); var text = new ConstantProperty('my text'); var font = new ConstantProperty('10px serif'); @@ -158,6 +164,7 @@ defineSuite([ var scaleByDistance = new ConstantProperty(new NearFarScalar()); var distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var disableDepthTestDistance = new ConstantProperty(20.0); + var rtl = new ConstantProperty(false); var target = new LabelGraphics(); target.text = text; @@ -177,6 +184,7 @@ defineSuite([ target.scaleByDistance = scaleByDistance; target.distanceDisplayCondition = distanceDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; + target.rtl = rtl; target.merge(source); @@ -197,6 +205,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(scaleByDistance); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); + expect(target.rtl).toBe(rtl); }); it('clone works', function() { @@ -218,6 +227,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(false); var result = source.clone(); expect(result.text).toBe(source.text); @@ -237,6 +247,7 @@ defineSuite([ expect(result.scaleByDistance).toBe(source.scaleByDistance); expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(result.disableDepthTestDistance).toBe(source.disableDepthTestDistance); + expect(result.rtl).toBe(source.rtl); }); it('merge throws if source undefined', function() { diff --git a/Specs/DataSources/LabelVisualizerSpec.js b/Specs/DataSources/LabelVisualizerSpec.js index c5a2112c6ac6..070355f7d155 100644 --- a/Specs/DataSources/LabelVisualizerSpec.js +++ b/Specs/DataSources/LabelVisualizerSpec.js @@ -161,6 +161,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(10.0); + label.rtl = new ConstantProperty(false); visualizer.update(time); @@ -188,6 +189,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); + expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); testObject.position = new ConstantProperty(new Cartesian3(5678, 1234, 1293434)); label.text = new ConstantProperty('b'); @@ -207,6 +209,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(20.0); + label.rtl = new ConstantProperty(true); visualizer.update(time); expect(l.position).toEqual(testObject.position.getValue(time)); @@ -227,6 +230,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); + expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); label.show = new ConstantProperty(false); visualizer.update(time); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 2f999ca1ea9d..4d5bf2909070 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1840,6 +1840,42 @@ defineSuite([ expect(newlinesBbox.height).toBeGreaterThan(originalBbox.height); }); + it('should not modify text when rtl is false', function() { + var text = 'bla bla bla'; + var label = labels.add({ + text : text + }); + scene.renderForSpecs(); + + expect(label.rtl).toEqual(false); + expect(label.text).toEqual(text); + }); + + it('should not modify text when rtl is true and there is no hebrew characters', function() { + var text = 'bla bla bla'; + var label = labels.add({ + text : text, + rtl : true + }); + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).toEqual(text); + }); + + it('should reverse text when there is only hebrew characters and rtl is true', function() { + var text = 'שלום'; + var label = labels.add({ + text : text + }); + label.rtl = true; + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(text.split('').reverse().join('')); + }); + }, 'WebGL'); it('computes bounding sphere in 3D', function() { From 281d4d367fde3135358d88c944049fd65b6b3f3f Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:19:47 +0300 Subject: [PATCH 16/47] support rtl characters (currently, only hebrew) at label to show in correct direction --- Source/DataSources/LabelGraphics.js | 15 ++- Source/DataSources/LabelVisualizer.js | 2 + Source/Scene/Label.js | 187 ++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 1 deletion(-) diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js index 52cf604de454..0b8b81217810 100644 --- a/Source/DataSources/LabelGraphics.js +++ b/Source/DataSources/LabelGraphics.js @@ -47,6 +47,7 @@ define([ * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to set scale based on distance from the camera. * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this label will be displayed. + * @param {Property} [options.rtl=false] A Property specifying if to modify text when there is possibly rtl characters. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} */ @@ -93,6 +94,8 @@ define([ this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; + this._rtl = undefined; + this._rtlSubscribtion = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -318,7 +321,15 @@ define([ * @memberof LabelGraphics.prototype * @type {Property} */ - disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') + disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance'), + + /** + * Gets or sets the ability to modify text characters direction. + * @memberof LabelGraphics.prototype + * @type {Property} + * @default false + */ + rtl: createPropertyDescriptor('rtl') }); /** @@ -352,6 +363,7 @@ define([ result.scaleByDistance = this.scaleByDistance; result.distanceDisplayCondition = this.distanceDisplayCondition; result.disableDepthTestDistance = this.disableDepthTestDistance; + result.rtl = this.rtl; return result; }; @@ -389,6 +401,7 @@ define([ this.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance); this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); + this.rtl = defaultValue(this.rtl, source.rtl); }; return LabelGraphics; diff --git a/Source/DataSources/LabelVisualizer.js b/Source/DataSources/LabelVisualizer.js index 208fd19c31c0..87df78e5692c 100644 --- a/Source/DataSources/LabelVisualizer.js +++ b/Source/DataSources/LabelVisualizer.js @@ -49,6 +49,7 @@ define([ var defaultHorizontalOrigin = HorizontalOrigin.CENTER; var defaultVerticalOrigin = VerticalOrigin.CENTER; var defaultDisableDepthTestDistance = 0.0; + var defaultRtl = false; var position = new Cartesian3(); var fillColor = new Color(); @@ -165,6 +166,7 @@ define([ label.scaleByDistance = Property.getValueOrUndefined(labelGraphics._scaleByDistance, time, scaleByDistance); label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition); label.disableDepthTestDistance = Property.getValueOrDefault(labelGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); + label.rtl = Property.getValueOrDefault(labelGraphics._rtl, time, defaultRtl); } return true; }; diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 813522182a0e..c02481e6b211 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -8,6 +8,7 @@ define([ '../Core/defineProperties', '../Core/DeveloperError', '../Core/DistanceDisplayCondition', + '../Core/freezeObject', '../Core/NearFarScalar', './Billboard', './HeightReference', @@ -24,6 +25,7 @@ define([ defineProperties, DeveloperError, DistanceDisplayCondition, + freezeObject, NearFarScalar, Billboard, HeightReference, @@ -133,6 +135,7 @@ define([ this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); this._distanceDisplayCondition = distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); + this._rtl = defaultValue(options.rtl, false); this._labelCollection = labelCollection; this._glyphs = []; @@ -279,6 +282,10 @@ define([ } //>>includeEnd('debug'); + if (this.rtl) { + value = this.reverseRtl(value); + } + if (this._text !== value) { this._text = value; rebindAllGlyphs(this); @@ -1030,6 +1037,23 @@ define([ } } } + }, + + /** + * Determines whether or not run the reverseRtl algorithm on the text of the label + * @memberof Label.prototype + * @type {Boolean} + * @default false + */ + rtl : { + get : function() { + return this._rtl; + }, + set : function(value) { + if (this._rtl !== value) { + this._rtl = value; + } + } } }); @@ -1196,5 +1220,168 @@ define([ return false; }; + function declareTypes() { + var TextTypes = { + LTR : 0, + RTL : 1, + WEAK : 2, + BRACKETS : 3 + }; + return freezeObject(TextTypes); + } + + function convertTextToTypes(text, rtlDir, rtlChars) { + var ltrChars = /[a-zA-Z0-9]/; + var bracketsChars = /[()[\]{}<>]/; + var parsedText = []; + var word = ''; + var types = declareTypes(); + var lastType = rtlDir ? types.RTL : types.LTR; + var currentType = ''; + var textLength = text.length; + for (var textIndex = 0; textIndex < textLength; ++textIndex) { + var character = text.charAt(textIndex); + if (rtlChars.test(character)) { + currentType = types.RTL; + } + else if (ltrChars.test(character)) { + currentType = types.LTR; + } + else if (bracketsChars.test(character)) { + currentType = types.BRACKETS; + } + else { + currentType = types.WEAK; + } + + if (lastType === currentType && currentType !== types.BRACKETS) { + word += character; + } + else { + parsedText.push({Type : lastType, Word : word}); + lastType = currentType; + word = character; + } + } + parsedText.push({Type : currentType, Word : word}); + return parsedText; + } + + function reverseWord(word) { + return word.split('').reverse().join(''); + } + + function spliceWord(result, pointer, word) { + return result.slice(0, pointer) + word + result.slice(pointer); + } + + function reverseBrackets(bracket) { + switch(bracket) { + case '(': + return ')'; + case ')': + return '('; + case '[': + return ']'; + case ']': + return '['; + case '{': + return '}'; + case '}': + return '{'; + case '<': + return '>'; + case '>': + return '<'; + } + } + + /** + * + * @param {String} text the text to parse and reorder + * @returns {String} the text as rtl direction + */ + Label.prototype.reverseRtl = function(text) { + var rtlChars = /[א-ת]/; + var rtlDir = rtlChars.test(text.charAt(0)); + var parsedText = convertTextToTypes(text, rtlDir, rtlChars); + + var types = declareTypes(); + var result = ''; + var splicePointer = 0; + for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { + var subText = parsedText[wordIndex]; + var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; + if(rtlDir) { + if (subText.Type === types.RTL) { + result = reverseWord(subText.Word) + result; + splicePointer = 0; + } + else if (subText.Type === types.LTR) { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (parsedText[wordIndex -1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + } + else { + result = spliceWord(result,splicePointer, subText.Word); + } + } + } + } + else { + if (subText.Type === types.RTL) { + result = spliceWord(result, splicePointer, reverseWord(subText.Word)); + } + else if (subText.Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (wordIndex > 0) { + if (parsedText[wordIndex -1].Type === types.RTL) { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (parsedText[wordIndex +1].Type === types.RTL) { + result = spliceWord(result, splicePointer, reverse); + } + } + else { + result += subText.Word; + } + } + else { + result += subText.Word; + splicePointer = result.length; + } + } + else { + result += subText.Word; + splicePointer = result.length; + } + } + } + } + + return result; + }; + return Label; }); From c1adde17a69f3b1b67cea027dd74586a06b04ef0 Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:22:02 +0300 Subject: [PATCH 17/47] add example of supporting rtl characters to sandcastle --- Apps/Sandcastle/gallery/Labels.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index aaf32e0826d2..d77681485b4e 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -121,6 +121,17 @@ }); } +function setRtl() { + Sandcastle.declare(setRtl); + viewer.entities.add({ + position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), + label : { + text : 'שלום', + rtl : true + } + }); +} + Sandcastle.addToolbarMenu([{ text : 'Add label', onselect : function() { @@ -157,6 +168,12 @@ scaleByDistance(); Sandcastle.highlight(scaleByDistance); } +}, { + text : 'Set rtl', + onselect : function() { + setRtl(); + Sandcastle.highlight(setRtl); + } }]); Sandcastle.reset = function() { From 6d6591803b370fa721f45aff043e9169dc10e6e6 Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:23:21 +0300 Subject: [PATCH 18/47] add unit tests to check rtl (unfortunately, not working yet) --- Specs/DataSources/LabelGraphicsSpec.js | 13 ++++++++- Specs/DataSources/LabelVisualizerSpec.js | 4 +++ Specs/Scene/LabelCollectionSpec.js | 36 ++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/Specs/DataSources/LabelGraphicsSpec.js b/Specs/DataSources/LabelGraphicsSpec.js index 282fc3d7a824..1539dbbe7b35 100644 --- a/Specs/DataSources/LabelGraphicsSpec.js +++ b/Specs/DataSources/LabelGraphicsSpec.js @@ -40,7 +40,8 @@ defineSuite([ pixelOffsetScaleByDistance : new NearFarScalar(13, 14, 15, 16), scaleByDistance : new NearFarScalar(17, 18, 19, 20), distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0), - disableDepthTestDistance : 10.0 + disableDepthTestDistance : 10.0, + rtl : false }; var label = new LabelGraphics(options); @@ -60,6 +61,7 @@ defineSuite([ expect(label.scaleByDistance).toBeInstanceOf(ConstantProperty); expect(label.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(label.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); + expect(label.rtl).toBeInstanceOf(ConstantProperty); expect(label.text.getValue()).toEqual(options.text); expect(label.font.getValue()).toEqual(options.font); @@ -77,6 +79,7 @@ defineSuite([ expect(label.scaleByDistance.getValue()).toEqual(options.scaleByDistance); expect(label.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); expect(label.disableDepthTestDistance.getValue()).toEqual(options.disableDepthTestDistance); + expect(label.rtl.getValue()).toEqual(options.rtl); }); it('merge assigns unassigned properties', function() { @@ -98,6 +101,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(false); var target = new LabelGraphics(); target.merge(source); @@ -119,6 +123,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(source.scaleByDistance); expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(source.disableDepthTestDistance); + expect(target.rtl).toBe(source.rtl); }); it('merge does not assign assigned properties', function() { @@ -140,6 +145,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(true); var text = new ConstantProperty('my text'); var font = new ConstantProperty('10px serif'); @@ -158,6 +164,7 @@ defineSuite([ var scaleByDistance = new ConstantProperty(new NearFarScalar()); var distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var disableDepthTestDistance = new ConstantProperty(20.0); + var rtl = new ConstantProperty(false); var target = new LabelGraphics(); target.text = text; @@ -177,6 +184,7 @@ defineSuite([ target.scaleByDistance = scaleByDistance; target.distanceDisplayCondition = distanceDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; + target.rtl = rtl; target.merge(source); @@ -197,6 +205,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(scaleByDistance); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); + expect(target.rtl).toBe(rtl); }); it('clone works', function() { @@ -218,6 +227,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(false); var result = source.clone(); expect(result.text).toBe(source.text); @@ -237,6 +247,7 @@ defineSuite([ expect(result.scaleByDistance).toBe(source.scaleByDistance); expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(result.disableDepthTestDistance).toBe(source.disableDepthTestDistance); + expect(result.rtl).toBe(source.rtl); }); it('merge throws if source undefined', function() { diff --git a/Specs/DataSources/LabelVisualizerSpec.js b/Specs/DataSources/LabelVisualizerSpec.js index c5a2112c6ac6..070355f7d155 100644 --- a/Specs/DataSources/LabelVisualizerSpec.js +++ b/Specs/DataSources/LabelVisualizerSpec.js @@ -161,6 +161,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(10.0); + label.rtl = new ConstantProperty(false); visualizer.update(time); @@ -188,6 +189,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); + expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); testObject.position = new ConstantProperty(new Cartesian3(5678, 1234, 1293434)); label.text = new ConstantProperty('b'); @@ -207,6 +209,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(20.0); + label.rtl = new ConstantProperty(true); visualizer.update(time); expect(l.position).toEqual(testObject.position.getValue(time)); @@ -227,6 +230,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); + expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); label.show = new ConstantProperty(false); visualizer.update(time); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 2f999ca1ea9d..4d5bf2909070 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1840,6 +1840,42 @@ defineSuite([ expect(newlinesBbox.height).toBeGreaterThan(originalBbox.height); }); + it('should not modify text when rtl is false', function() { + var text = 'bla bla bla'; + var label = labels.add({ + text : text + }); + scene.renderForSpecs(); + + expect(label.rtl).toEqual(false); + expect(label.text).toEqual(text); + }); + + it('should not modify text when rtl is true and there is no hebrew characters', function() { + var text = 'bla bla bla'; + var label = labels.add({ + text : text, + rtl : true + }); + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).toEqual(text); + }); + + it('should reverse text when there is only hebrew characters and rtl is true', function() { + var text = 'שלום'; + var label = labels.add({ + text : text + }); + label.rtl = true; + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(text.split('').reverse().join('')); + }); + }, 'WebGL'); it('computes bounding sphere in 3D', function() { From 5f304a82cb9bd65ee5f618cba514d252afe65cb6 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 00:13:34 +0300 Subject: [PATCH 19/47] RTL labels now work in constructor Adjusted the text position and used its setter in order to construct the Label Adjusted tests accordinglt --- Source/Scene/Label.js | 5 +++-- Specs/Scene/LabelCollectionSpec.js | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index c02481e6b211..d5fb35acd303 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -112,7 +112,6 @@ define([ distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition); } - this._text = defaultValue(options.text, ''); this._show = defaultValue(options.show, true); this._font = defaultValue(options.font, '30px sans-serif'); this._fillColor = Color.clone(defaultValue(options.fillColor, Color.WHITE)); @@ -135,7 +134,6 @@ define([ this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); this._distanceDisplayCondition = distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); - this._rtl = defaultValue(options.rtl, false); this._labelCollection = labelCollection; this._glyphs = []; @@ -150,6 +148,9 @@ define([ this._clusterShow = true; + this._rtl = defaultValue(options.rtl, false); + this.text = defaultValue(options.text, ''); + this._updateClamping(); } diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 4d5bf2909070..0ff08a6035b9 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1866,9 +1866,10 @@ defineSuite([ it('should reverse text when there is only hebrew characters and rtl is true', function() { var text = 'שלום'; var label = labels.add({ - text : text + text : text, + rtl: true }); - label.rtl = true; + scene.renderForSpecs(); expect(label.rtl).toEqual(true); From 304e77485e6500cd6882ea02969a4922ee6b9b65 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 12:07:21 +0300 Subject: [PATCH 20/47] Updated contributors --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 66b0f0a0c4fa..1cab0bac474b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -90,6 +90,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [William Wall](https://github.com/wallw-bits) * [virtualcitySYSTEMS GmbH](https://www.virtualcitysystems.de) * [Jannes Bolling](https://github.com/jbo023) +* [webiks](https://www.webiks.com) + * [Hod Bauer](https://github.com/hodbauer) ## [Individual CLA](Documentation/Contributors/CLAs/individual-cla-agi-v1.0.txt) * [Victor Berchet](https://github.com/vicb) From f7dfcaa890d8ab754868b617c88c0ac9415e544d Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 16:30:39 +0300 Subject: [PATCH 21/47] Support for \n in RTL --- Source/Scene/Label.js | 113 ++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index d5fb35acd303..91a0df66738b 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -1302,70 +1302,76 @@ define([ * @param {String} text the text to parse and reorder * @returns {String} the text as rtl direction */ - Label.prototype.reverseRtl = function(text) { + Label.prototype.reverseRtl = function(value) { var rtlChars = /[א-ת]/; - var rtlDir = rtlChars.test(text.charAt(0)); - var parsedText = convertTextToTypes(text, rtlDir, rtlChars); - - var types = declareTypes(); + var texts = value.split('\n'); var result = ''; - var splicePointer = 0; - for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { - var subText = parsedText[wordIndex]; - var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; - if(rtlDir) { - if (subText.Type === types.RTL) { - result = reverseWord(subText.Word) + result; - splicePointer = 0; - } - else if (subText.Type === types.LTR) { - result = spliceWord(result,splicePointer, subText.Word); - splicePointer += subText.Word.length; - } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (parsedText[wordIndex -1].Type === types.RTL) { - result = reverse + result; + for (var i = 0; i < texts.length; i++) { + var text = texts[i]; + var rtlDir = rtlChars.test(text.charAt(0)); + var parsedText = convertTextToTypes(text, rtlDir, rtlChars); + + var types = declareTypes(); + + var splicePointer = 0; + for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { + var subText = parsedText[wordIndex]; + var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; + if(rtlDir) { + if (subText.Type === types.RTL) { + result = reverseWord(subText.Word) + result; splicePointer = 0; } - else { - if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.RTL) { - result = reverse + result; - splicePointer = 0; + else if (subText.Type === types.LTR) { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (parsedText[wordIndex -1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } } else { result = spliceWord(result,splicePointer, subText.Word); - splicePointer += subText.Word.length; } - } - else { - result = spliceWord(result,splicePointer, subText.Word); - } } } - } - else { - if (subText.Type === types.RTL) { - result = spliceWord(result, splicePointer, reverseWord(subText.Word)); - } - else if (subText.Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; - } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (wordIndex > 0) { - if (parsedText[wordIndex -1].Type === types.RTL) { - if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; + else if (subText.Type === types.RTL) { + result = spliceWord(result, splicePointer, reverseWord(subText.Word)); + } + else if (subText.Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (wordIndex > 0) { + if (parsedText[wordIndex -1].Type === types.RTL) { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (parsedText[wordIndex +1].Type === types.RTL) { + result = spliceWord(result, splicePointer, reverse); + } } - else if (parsedText[wordIndex +1].Type === types.RTL) { - result = spliceWord(result, splicePointer, reverse); + else { + result += subText.Word; } } else { result += subText.Word; + splicePointer = result.length; } } else { @@ -1373,14 +1379,11 @@ define([ splicePointer = result.length; } } - else { - result += subText.Word; - splicePointer = result.length; - } - } + } + if (i < texts.length - 1) { + result += '\n'; } } - return result; }; From 10b05d2860af67b156f71de10381370c72f6c179 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 16:46:33 +0300 Subject: [PATCH 22/47] Cache RTL original string In order to prevent the function from running on each update if no change was made to the text --- Source/Scene/Label.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 91a0df66738b..096cd6fb7d52 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -284,6 +284,10 @@ define([ //>>includeEnd('debug'); if (this.rtl) { + if (this._originalValue === value) { + return; + } + this._originalValue = value; value = this.reverseRtl(value); } From 8878a7bb15ff518ba11c4429b6feab89235db9e0 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 16:56:09 +0300 Subject: [PATCH 23/47] Prevent glyphs parser from running for nought --- Source/Scene/Label.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 096cd6fb7d52..20932e5cfcbb 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -285,6 +285,7 @@ define([ if (this.rtl) { if (this._originalValue === value) { + value = this._text; return; } this._originalValue = value; From 12fe81df53c52db95324a84d622b5ecec5d2d22d Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 17:13:15 +0300 Subject: [PATCH 24/47] Documentation examples --- Source/Scene/Label.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 20932e5cfcbb..c2356251a9f1 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -1050,6 +1050,25 @@ define([ * @memberof Label.prototype * @type {Boolean} * @default false + * + * @example + * // Example 1. + * // Set a label's rtl during init + * var myLabelEntity = viewer.entities.add({ + * id: 'my label', + * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', + * rtl: true + * }); + * + * @example + * // Example 2. + * var myLabelEntity = viewer.entities.add({ + * id: 'my label', + * text: 'English text' + * }); + * // Set a label's rtl after init + * myLabelEntity.rtl = true; + * myLabelEntity.text = 'טקסט חדש' */ rtl : { get : function() { From 15a3c9c298b73959964aafc3981347ec077a750d Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 17:14:14 +0300 Subject: [PATCH 25/47] Updated contributors list --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1cab0bac474b..516e15a3fbfe 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -92,6 +92,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Jannes Bolling](https://github.com/jbo023) * [webiks](https://www.webiks.com) * [Hod Bauer](https://github.com/hodbauer) + * [Yonatan Kra](https://github.com/yonatankra) ## [Individual CLA](Documentation/Contributors/CLAs/individual-cla-agi-v1.0.txt) * [Victor Berchet](https://github.com/vicb) @@ -158,4 +159,5 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Rishabh Shah](https://github.com/rms13) * [Rudraksha Shah](https://github.com/Rudraksha20) * [Cody Guldner](https://github.com/burn123) +* [Yonatan Kra](https://github.com/yonatankra) From 9ab17046d02e611b56d513215d85e2d1ed9a2a43 Mon Sep 17 00:00:00 2001 From: hodbauer Date: Sun, 3 Sep 2017 10:18:21 +0300 Subject: [PATCH 26/47] * fix descriptions * make `reverseRtl` private * fix multi-lines support * add complex test * fix some edge cases: * special characters after rtl characters when direction is not rtl * "Weak" characters after brackets at rtl direction --- Apps/Sandcastle/gallery/Labels.html | 4 +- Source/Scene/Label.js | 109 +++++++++++++++------------- Specs/Scene/LabelCollectionSpec.js | 15 ++++ 3 files changed, 77 insertions(+), 51 deletions(-) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index d77681485b4e..edd8d3669696 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -126,7 +126,7 @@ viewer.entities.add({ position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), label : { - text : 'שלום', + text : 'Master (אדון): Hello\nתלמיד (student): שלום', rtl : true } }); @@ -169,7 +169,7 @@ Sandcastle.highlight(scaleByDistance); } }, { - text : 'Set rtl', + text : 'Set label with right-to-left language', onselect : function() { setRtl(); Sandcastle.highlight(setRtl); diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index c2356251a9f1..6636471b8580 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -289,7 +289,7 @@ define([ return; } this._originalValue = value; - value = this.reverseRtl(value); + value = reverseRtl(value); } if (this._text !== value) { @@ -1055,20 +1055,24 @@ define([ * // Example 1. * // Set a label's rtl during init * var myLabelEntity = viewer.entities.add({ - * id: 'my label', - * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', - * rtl: true + * label: { + * id: 'my label', + * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', + * rtl: true + * } * }); * * @example * // Example 2. * var myLabelEntity = viewer.entities.add({ - * id: 'my label', - * text: 'English text' + * label: { + * id: 'my label', + * text: 'English text' + * } * }); * // Set a label's rtl after init * myLabelEntity.rtl = true; - * myLabelEntity.text = 'טקסט חדש' + * myLabelEntity.text = 'טקסט חדש'; */ rtl : { get : function() { @@ -1323,10 +1327,11 @@ define([ /** * - * @param {String} text the text to parse and reorder + * @param {String} value the text to parse and reorder * @returns {String} the text as rtl direction + * @private */ - Label.prototype.reverseRtl = function(value) { + function reverseRtl(value) { var rtlChars = /[א-ת]/; var texts = value.split('\n'); var result = ''; @@ -1338,78 +1343,84 @@ define([ var types = declareTypes(); var splicePointer = 0; - for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { + var line = ''; + for (var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { var subText = parsedText[wordIndex]; var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; - if(rtlDir) { + if (rtlDir) { if (subText.Type === types.RTL) { - result = reverseWord(subText.Word) + result; + line = reverseWord(subText.Word) + line; splicePointer = 0; } else if (subText.Type === types.LTR) { - result = spliceWord(result,splicePointer, subText.Word); + line = spliceWord(line, splicePointer, subText.Word); splicePointer += subText.Word.length; } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (parsedText[wordIndex -1].Type === types.RTL) { - result = reverse + result; + else if (subText.Type === types.WEAK || subText.Type === types.BRACKETS) { + if (subText.Type === types.WEAK && parsedText[wordIndex - 1].Type === types.BRACKETS) { + line = reverseWord(subText.Word) + line; + } + else if (parsedText[wordIndex - 1].Type === types.RTL) { + line = reverse + line; splicePointer = 0; } - else if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.RTL) { - result = reverse + result; - splicePointer = 0; - } - else { - result = spliceWord(result,splicePointer, subText.Word); - splicePointer += subText.Word.length; - } + else if (parsedText.length > wordIndex + 1) { + if (parsedText[wordIndex + 1].Type === types.RTL) { + line = reverse + line; + splicePointer = 0; } else { - result = spliceWord(result,splicePointer, subText.Word); + line = spliceWord(line, splicePointer, subText.Word); + splicePointer += subText.Word.length; } + } + else { + line = spliceWord(line, splicePointer, subText.Word); + } } } else if (subText.Type === types.RTL) { - result = spliceWord(result, splicePointer, reverseWord(subText.Word)); - } - else if (subText.Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; - } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (wordIndex > 0) { - if (parsedText[wordIndex -1].Type === types.RTL) { - if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; - } - else if (parsedText[wordIndex +1].Type === types.RTL) { - result = spliceWord(result, splicePointer, reverse); - } + line = spliceWord(line, splicePointer, reverseWord(subText.Word)); + } + else if (subText.Type === types.LTR) { + line += subText.Word; + splicePointer = line.length; + } + else if (subText.Type === types.WEAK || subText.Type === types.BRACKETS) { + if (wordIndex > 0) { + if (parsedText[wordIndex - 1].Type === types.RTL) { + if (parsedText.length > wordIndex + 1) { + if (parsedText[wordIndex + 1].Type === types.RTL) { + line = spliceWord(line, splicePointer, reverse); } else { - result += subText.Word; + line += subText.Word; + splicePointer = line.length; } } else { - result += subText.Word; - splicePointer = result.length; + line += subText.Word; } } else { - result += subText.Word; - splicePointer = result.length; + line += subText.Word; + splicePointer = line.length; } } + else { + line += subText.Word; + splicePointer = line.length; + } + } } + + result += line; if (i < texts.length - 1) { result += '\n'; } } return result; - }; + } return Label; }); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 0ff08a6035b9..dd36426e6bc1 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1877,6 +1877,21 @@ defineSuite([ expect(label.text).toEqual(text.split('').reverse().join('')); }); + it('should reverse part of text when there is mix of right-to-left and other kind of characters and rtl is true', function() { + var text = 'Master (אדון): "Hello"\nתלמיד (student): "שלום"'; + var expectedText = 'Master (ןודא): "Hello"\n"םולש" :(student) דימלת'; + var label = labels.add({ + text : text, + rtl: true + }); + + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(expectedText); + }); + }, 'WebGL'); it('computes bounding sphere in 3D', function() { From 04538bae6f97100b0bf6fb5e95f35f3d64256b0b Mon Sep 17 00:00:00 2001 From: hodbauer Date: Mon, 11 Sep 2017 23:32:09 +0300 Subject: [PATCH 27/47] rename rtl to rightToLeft --- Apps/Sandcastle/gallery/Labels.html | 10 +++++----- CHANGES.md | 1 + Source/DataSources/LabelGraphics.js | 12 ++++++------ Source/DataSources/LabelVisualizer.js | 4 ++-- Source/Scene/Label.js | 22 +++++++++++----------- Specs/DataSources/LabelGraphicsSpec.js | 22 +++++++++++----------- Specs/DataSources/LabelVisualizerSpec.js | 8 ++++---- Specs/Scene/LabelCollectionSpec.js | 22 +++++++++++----------- 8 files changed, 51 insertions(+), 50 deletions(-) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index edd8d3669696..a6a9d7a45fd5 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -121,13 +121,13 @@ }); } -function setRtl() { - Sandcastle.declare(setRtl); +function setRightToLeft() { + Sandcastle.declare(setRightToLeft); viewer.entities.add({ position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), label : { text : 'Master (אדון): Hello\nתלמיד (student): שלום', - rtl : true + rightToLeft : true } }); } @@ -171,8 +171,8 @@ }, { text : 'Set label with right-to-left language', onselect : function() { - setRtl(); - Sandcastle.highlight(setRtl); + setRightToLeft(); + Sandcastle.highlight(setRightToLeft); } }]); diff --git a/CHANGES.md b/CHANGES.md index 1f200ef4e868..a08842b38baf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ Change Log ========== ### 1.38 - 2017-10-02 +* Added support for right-to-left languages in labels * Added ability to add an animation to `ModelAnimationCollection` by its index. [#5815](https://github.com/AnalyticalGraphicsInc/cesium/pull/5815) * Fixed a bug in `ModelAnimationCollection` that caused adding an animation by its name to throw an error. [#5815](https://github.com/AnalyticalGraphicsInc/cesium/pull/5815) * Zoom about mouse now maintains camera heading, pitch, and roll [#4639](https://github.com/AnalyticalGraphicsInc/cesium/pull/5603) diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js index 0b8b81217810..78f7dcedbfd7 100644 --- a/Source/DataSources/LabelGraphics.js +++ b/Source/DataSources/LabelGraphics.js @@ -47,7 +47,7 @@ define([ * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to set scale based on distance from the camera. * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this label will be displayed. - * @param {Property} [options.rtl=false] A Property specifying if to modify text when there is possibly rtl characters. + * @param {Property} [options.rightToLeft=false] A Property specifying if to modify text when there is possibly rightToLeft characters. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} */ @@ -94,8 +94,8 @@ define([ this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; - this._rtl = undefined; - this._rtlSubscribtion = undefined; + this._rightToLeft = undefined; + this._rightToLeftSubscribtion = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -329,7 +329,7 @@ define([ * @type {Property} * @default false */ - rtl: createPropertyDescriptor('rtl') + rightToLeft: createPropertyDescriptor('rightToLeft') }); /** @@ -363,7 +363,7 @@ define([ result.scaleByDistance = this.scaleByDistance; result.distanceDisplayCondition = this.distanceDisplayCondition; result.disableDepthTestDistance = this.disableDepthTestDistance; - result.rtl = this.rtl; + result.rightToLeft = this.rightToLeft; return result; }; @@ -401,7 +401,7 @@ define([ this.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance); this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); - this.rtl = defaultValue(this.rtl, source.rtl); + this.rightToLeft = defaultValue(this.rightToLeft, source.rightToLeft); }; return LabelGraphics; diff --git a/Source/DataSources/LabelVisualizer.js b/Source/DataSources/LabelVisualizer.js index 87df78e5692c..989587252db6 100644 --- a/Source/DataSources/LabelVisualizer.js +++ b/Source/DataSources/LabelVisualizer.js @@ -49,7 +49,7 @@ define([ var defaultHorizontalOrigin = HorizontalOrigin.CENTER; var defaultVerticalOrigin = VerticalOrigin.CENTER; var defaultDisableDepthTestDistance = 0.0; - var defaultRtl = false; + var defaultRightToLeft = false; var position = new Cartesian3(); var fillColor = new Color(); @@ -166,7 +166,7 @@ define([ label.scaleByDistance = Property.getValueOrUndefined(labelGraphics._scaleByDistance, time, scaleByDistance); label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition); label.disableDepthTestDistance = Property.getValueOrDefault(labelGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); - label.rtl = Property.getValueOrDefault(labelGraphics._rtl, time, defaultRtl); + label.rightToLeft = Property.getValueOrDefault(labelGraphics._rightToLeft, time, defaultRightToLeft); } return true; }; diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 6636471b8580..e0720779f65e 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -148,7 +148,7 @@ define([ this._clusterShow = true; - this._rtl = defaultValue(options.rtl, false); + this._rightToLeft = defaultValue(options.rightToLeft, false); this.text = defaultValue(options.text, ''); this._updateClamping(); @@ -283,7 +283,7 @@ define([ } //>>includeEnd('debug'); - if (this.rtl) { + if (this.rightToLeft) { if (this._originalValue === value) { value = this._text; return; @@ -1053,12 +1053,12 @@ define([ * * @example * // Example 1. - * // Set a label's rtl during init + * // Set a label's rightToLeft during init * var myLabelEntity = viewer.entities.add({ * label: { * id: 'my label', * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', - * rtl: true + * rightToLeft: true * } * }); * @@ -1070,17 +1070,17 @@ define([ * text: 'English text' * } * }); - * // Set a label's rtl after init - * myLabelEntity.rtl = true; + * // Set a label's rightToLeft after init + * myLabelEntity.rightToLeft = true; * myLabelEntity.text = 'טקסט חדש'; */ - rtl : { + rightToLeft : { get : function() { - return this._rtl; + return this._rightToLeft; }, set : function(value) { - if (this._rtl !== value) { - this._rtl = value; + if (this._rightToLeft !== value) { + this._rightToLeft = value; } } } @@ -1328,7 +1328,7 @@ define([ /** * * @param {String} value the text to parse and reorder - * @returns {String} the text as rtl direction + * @returns {String} the text as rightToLeft direction * @private */ function reverseRtl(value) { diff --git a/Specs/DataSources/LabelGraphicsSpec.js b/Specs/DataSources/LabelGraphicsSpec.js index 1539dbbe7b35..6229ede3606a 100644 --- a/Specs/DataSources/LabelGraphicsSpec.js +++ b/Specs/DataSources/LabelGraphicsSpec.js @@ -41,7 +41,7 @@ defineSuite([ scaleByDistance : new NearFarScalar(17, 18, 19, 20), distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance : 10.0, - rtl : false + rightToLeft : false }; var label = new LabelGraphics(options); @@ -61,7 +61,7 @@ defineSuite([ expect(label.scaleByDistance).toBeInstanceOf(ConstantProperty); expect(label.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(label.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); - expect(label.rtl).toBeInstanceOf(ConstantProperty); + expect(label.rightToLeft).toBeInstanceOf(ConstantProperty); expect(label.text.getValue()).toEqual(options.text); expect(label.font.getValue()).toEqual(options.font); @@ -79,7 +79,7 @@ defineSuite([ expect(label.scaleByDistance.getValue()).toEqual(options.scaleByDistance); expect(label.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); expect(label.disableDepthTestDistance.getValue()).toEqual(options.disableDepthTestDistance); - expect(label.rtl.getValue()).toEqual(options.rtl); + expect(label.rightToLeft.getValue()).toEqual(options.rightToLeft); }); it('merge assigns unassigned properties', function() { @@ -101,7 +101,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); - source.rtl = new ConstantProperty(false); + source.rightToLeft = new ConstantProperty(false); var target = new LabelGraphics(); target.merge(source); @@ -123,7 +123,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(source.scaleByDistance); expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(source.disableDepthTestDistance); - expect(target.rtl).toBe(source.rtl); + expect(target.rightToLeft).toBe(source.rightToLeft); }); it('merge does not assign assigned properties', function() { @@ -145,7 +145,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); - source.rtl = new ConstantProperty(true); + source.rightToLeft = new ConstantProperty(true); var text = new ConstantProperty('my text'); var font = new ConstantProperty('10px serif'); @@ -164,7 +164,7 @@ defineSuite([ var scaleByDistance = new ConstantProperty(new NearFarScalar()); var distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var disableDepthTestDistance = new ConstantProperty(20.0); - var rtl = new ConstantProperty(false); + var rightToLeft = new ConstantProperty(false); var target = new LabelGraphics(); target.text = text; @@ -184,7 +184,7 @@ defineSuite([ target.scaleByDistance = scaleByDistance; target.distanceDisplayCondition = distanceDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; - target.rtl = rtl; + target.rightToLeft = rightToLeft; target.merge(source); @@ -205,7 +205,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(scaleByDistance); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); - expect(target.rtl).toBe(rtl); + expect(target.rightToLeft).toBe(rightToLeft); }); it('clone works', function() { @@ -227,7 +227,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); - source.rtl = new ConstantProperty(false); + source.rightToLeft = new ConstantProperty(false); var result = source.clone(); expect(result.text).toBe(source.text); @@ -247,7 +247,7 @@ defineSuite([ expect(result.scaleByDistance).toBe(source.scaleByDistance); expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(result.disableDepthTestDistance).toBe(source.disableDepthTestDistance); - expect(result.rtl).toBe(source.rtl); + expect(result.rightToLeft).toBe(source.rightToLeft); }); it('merge throws if source undefined', function() { diff --git a/Specs/DataSources/LabelVisualizerSpec.js b/Specs/DataSources/LabelVisualizerSpec.js index 070355f7d155..f15c9b78e7d2 100644 --- a/Specs/DataSources/LabelVisualizerSpec.js +++ b/Specs/DataSources/LabelVisualizerSpec.js @@ -161,7 +161,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(10.0); - label.rtl = new ConstantProperty(false); + label.rightToLeft = new ConstantProperty(false); visualizer.update(time); @@ -189,7 +189,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); - expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); + expect(l.rightToLeft).toEqual(testObject.label.rightToLeft.getValue(time)); testObject.position = new ConstantProperty(new Cartesian3(5678, 1234, 1293434)); label.text = new ConstantProperty('b'); @@ -209,7 +209,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(20.0); - label.rtl = new ConstantProperty(true); + label.rightToLeft = new ConstantProperty(true); visualizer.update(time); expect(l.position).toEqual(testObject.position.getValue(time)); @@ -230,7 +230,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); - expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); + expect(l.rightToLeft).toEqual(testObject.label.rightToLeft.getValue(time)); label.show = new ConstantProperty(false); visualizer.update(time); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index dd36426e6bc1..995b4b51149a 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1840,54 +1840,54 @@ defineSuite([ expect(newlinesBbox.height).toBeGreaterThan(originalBbox.height); }); - it('should not modify text when rtl is false', function() { + it('should not modify text when rightToLeft is false', function() { var text = 'bla bla bla'; var label = labels.add({ text : text }); scene.renderForSpecs(); - expect(label.rtl).toEqual(false); + expect(label.rightToLeft).toEqual(false); expect(label.text).toEqual(text); }); - it('should not modify text when rtl is true and there is no hebrew characters', function() { + it('should not modify text when rightToLeft is true and there is no hebrew characters', function() { var text = 'bla bla bla'; var label = labels.add({ text : text, - rtl : true + rightToLeft : true }); scene.renderForSpecs(); - expect(label.rtl).toEqual(true); + expect(label.rightToLeft).toEqual(true); expect(label.text).toEqual(text); }); - it('should reverse text when there is only hebrew characters and rtl is true', function() { + it('should reverse text when there is only hebrew characters and rightToLeft is true', function() { var text = 'שלום'; var label = labels.add({ text : text, - rtl: true + rightToLeft: true }); scene.renderForSpecs(); - expect(label.rtl).toEqual(true); + expect(label.rightToLeft).toEqual(true); expect(label.text).not.toEqual(text); expect(label.text).toEqual(text.split('').reverse().join('')); }); - it('should reverse part of text when there is mix of right-to-left and other kind of characters and rtl is true', function() { + it('should reverse part of text when there is mix of right-to-left and other kind of characters and rightToLeft is true', function() { var text = 'Master (אדון): "Hello"\nתלמיד (student): "שלום"'; var expectedText = 'Master (ןודא): "Hello"\n"םולש" :(student) דימלת'; var label = labels.add({ text : text, - rtl: true + rightToLeft: true }); scene.renderForSpecs(); - expect(label.rtl).toEqual(true); + expect(label.rightToLeft).toEqual(true); expect(label.text).not.toEqual(text); expect(label.text).toEqual(expectedText); }); From 97c5c36bd4113749d60ec0d13f1f209287775c67 Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:19:47 +0300 Subject: [PATCH 28/47] support rtl characters (currently, only hebrew) at label to show in correct direction --- Source/DataSources/LabelGraphics.js | 15 ++- Source/DataSources/LabelVisualizer.js | 2 + Source/Scene/Label.js | 187 ++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 1 deletion(-) diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js index 52cf604de454..0b8b81217810 100644 --- a/Source/DataSources/LabelGraphics.js +++ b/Source/DataSources/LabelGraphics.js @@ -47,6 +47,7 @@ define([ * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to set scale based on distance from the camera. * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this label will be displayed. + * @param {Property} [options.rtl=false] A Property specifying if to modify text when there is possibly rtl characters. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} */ @@ -93,6 +94,8 @@ define([ this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; + this._rtl = undefined; + this._rtlSubscribtion = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -318,7 +321,15 @@ define([ * @memberof LabelGraphics.prototype * @type {Property} */ - disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') + disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance'), + + /** + * Gets or sets the ability to modify text characters direction. + * @memberof LabelGraphics.prototype + * @type {Property} + * @default false + */ + rtl: createPropertyDescriptor('rtl') }); /** @@ -352,6 +363,7 @@ define([ result.scaleByDistance = this.scaleByDistance; result.distanceDisplayCondition = this.distanceDisplayCondition; result.disableDepthTestDistance = this.disableDepthTestDistance; + result.rtl = this.rtl; return result; }; @@ -389,6 +401,7 @@ define([ this.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance); this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); + this.rtl = defaultValue(this.rtl, source.rtl); }; return LabelGraphics; diff --git a/Source/DataSources/LabelVisualizer.js b/Source/DataSources/LabelVisualizer.js index 208fd19c31c0..87df78e5692c 100644 --- a/Source/DataSources/LabelVisualizer.js +++ b/Source/DataSources/LabelVisualizer.js @@ -49,6 +49,7 @@ define([ var defaultHorizontalOrigin = HorizontalOrigin.CENTER; var defaultVerticalOrigin = VerticalOrigin.CENTER; var defaultDisableDepthTestDistance = 0.0; + var defaultRtl = false; var position = new Cartesian3(); var fillColor = new Color(); @@ -165,6 +166,7 @@ define([ label.scaleByDistance = Property.getValueOrUndefined(labelGraphics._scaleByDistance, time, scaleByDistance); label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition); label.disableDepthTestDistance = Property.getValueOrDefault(labelGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); + label.rtl = Property.getValueOrDefault(labelGraphics._rtl, time, defaultRtl); } return true; }; diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 813522182a0e..c02481e6b211 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -8,6 +8,7 @@ define([ '../Core/defineProperties', '../Core/DeveloperError', '../Core/DistanceDisplayCondition', + '../Core/freezeObject', '../Core/NearFarScalar', './Billboard', './HeightReference', @@ -24,6 +25,7 @@ define([ defineProperties, DeveloperError, DistanceDisplayCondition, + freezeObject, NearFarScalar, Billboard, HeightReference, @@ -133,6 +135,7 @@ define([ this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); this._distanceDisplayCondition = distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); + this._rtl = defaultValue(options.rtl, false); this._labelCollection = labelCollection; this._glyphs = []; @@ -279,6 +282,10 @@ define([ } //>>includeEnd('debug'); + if (this.rtl) { + value = this.reverseRtl(value); + } + if (this._text !== value) { this._text = value; rebindAllGlyphs(this); @@ -1030,6 +1037,23 @@ define([ } } } + }, + + /** + * Determines whether or not run the reverseRtl algorithm on the text of the label + * @memberof Label.prototype + * @type {Boolean} + * @default false + */ + rtl : { + get : function() { + return this._rtl; + }, + set : function(value) { + if (this._rtl !== value) { + this._rtl = value; + } + } } }); @@ -1196,5 +1220,168 @@ define([ return false; }; + function declareTypes() { + var TextTypes = { + LTR : 0, + RTL : 1, + WEAK : 2, + BRACKETS : 3 + }; + return freezeObject(TextTypes); + } + + function convertTextToTypes(text, rtlDir, rtlChars) { + var ltrChars = /[a-zA-Z0-9]/; + var bracketsChars = /[()[\]{}<>]/; + var parsedText = []; + var word = ''; + var types = declareTypes(); + var lastType = rtlDir ? types.RTL : types.LTR; + var currentType = ''; + var textLength = text.length; + for (var textIndex = 0; textIndex < textLength; ++textIndex) { + var character = text.charAt(textIndex); + if (rtlChars.test(character)) { + currentType = types.RTL; + } + else if (ltrChars.test(character)) { + currentType = types.LTR; + } + else if (bracketsChars.test(character)) { + currentType = types.BRACKETS; + } + else { + currentType = types.WEAK; + } + + if (lastType === currentType && currentType !== types.BRACKETS) { + word += character; + } + else { + parsedText.push({Type : lastType, Word : word}); + lastType = currentType; + word = character; + } + } + parsedText.push({Type : currentType, Word : word}); + return parsedText; + } + + function reverseWord(word) { + return word.split('').reverse().join(''); + } + + function spliceWord(result, pointer, word) { + return result.slice(0, pointer) + word + result.slice(pointer); + } + + function reverseBrackets(bracket) { + switch(bracket) { + case '(': + return ')'; + case ')': + return '('; + case '[': + return ']'; + case ']': + return '['; + case '{': + return '}'; + case '}': + return '{'; + case '<': + return '>'; + case '>': + return '<'; + } + } + + /** + * + * @param {String} text the text to parse and reorder + * @returns {String} the text as rtl direction + */ + Label.prototype.reverseRtl = function(text) { + var rtlChars = /[א-ת]/; + var rtlDir = rtlChars.test(text.charAt(0)); + var parsedText = convertTextToTypes(text, rtlDir, rtlChars); + + var types = declareTypes(); + var result = ''; + var splicePointer = 0; + for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { + var subText = parsedText[wordIndex]; + var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; + if(rtlDir) { + if (subText.Type === types.RTL) { + result = reverseWord(subText.Word) + result; + splicePointer = 0; + } + else if (subText.Type === types.LTR) { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (parsedText[wordIndex -1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + } + else { + result = spliceWord(result,splicePointer, subText.Word); + } + } + } + } + else { + if (subText.Type === types.RTL) { + result = spliceWord(result, splicePointer, reverseWord(subText.Word)); + } + else if (subText.Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (wordIndex > 0) { + if (parsedText[wordIndex -1].Type === types.RTL) { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (parsedText[wordIndex +1].Type === types.RTL) { + result = spliceWord(result, splicePointer, reverse); + } + } + else { + result += subText.Word; + } + } + else { + result += subText.Word; + splicePointer = result.length; + } + } + else { + result += subText.Word; + splicePointer = result.length; + } + } + } + } + + return result; + }; + return Label; }); From 7245d72f79707c9558ba8315dfbdcbaaf3961752 Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:22:02 +0300 Subject: [PATCH 29/47] add example of supporting rtl characters to sandcastle --- Apps/Sandcastle/gallery/Labels.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index aaf32e0826d2..d77681485b4e 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -121,6 +121,17 @@ }); } +function setRtl() { + Sandcastle.declare(setRtl); + viewer.entities.add({ + position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), + label : { + text : 'שלום', + rtl : true + } + }); +} + Sandcastle.addToolbarMenu([{ text : 'Add label', onselect : function() { @@ -157,6 +168,12 @@ scaleByDistance(); Sandcastle.highlight(scaleByDistance); } +}, { + text : 'Set rtl', + onselect : function() { + setRtl(); + Sandcastle.highlight(setRtl); + } }]); Sandcastle.reset = function() { From 4a69abf9968ef360949f8e2cfa0145fb5c712367 Mon Sep 17 00:00:00 2001 From: hodbauer Date: Thu, 4 May 2017 17:23:21 +0300 Subject: [PATCH 30/47] add unit tests to check rtl (unfortunately, not working yet) --- Specs/DataSources/LabelGraphicsSpec.js | 13 ++++++++- Specs/DataSources/LabelVisualizerSpec.js | 4 +++ Specs/Scene/LabelCollectionSpec.js | 36 ++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/Specs/DataSources/LabelGraphicsSpec.js b/Specs/DataSources/LabelGraphicsSpec.js index 282fc3d7a824..1539dbbe7b35 100644 --- a/Specs/DataSources/LabelGraphicsSpec.js +++ b/Specs/DataSources/LabelGraphicsSpec.js @@ -40,7 +40,8 @@ defineSuite([ pixelOffsetScaleByDistance : new NearFarScalar(13, 14, 15, 16), scaleByDistance : new NearFarScalar(17, 18, 19, 20), distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0), - disableDepthTestDistance : 10.0 + disableDepthTestDistance : 10.0, + rtl : false }; var label = new LabelGraphics(options); @@ -60,6 +61,7 @@ defineSuite([ expect(label.scaleByDistance).toBeInstanceOf(ConstantProperty); expect(label.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(label.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); + expect(label.rtl).toBeInstanceOf(ConstantProperty); expect(label.text.getValue()).toEqual(options.text); expect(label.font.getValue()).toEqual(options.font); @@ -77,6 +79,7 @@ defineSuite([ expect(label.scaleByDistance.getValue()).toEqual(options.scaleByDistance); expect(label.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); expect(label.disableDepthTestDistance.getValue()).toEqual(options.disableDepthTestDistance); + expect(label.rtl.getValue()).toEqual(options.rtl); }); it('merge assigns unassigned properties', function() { @@ -98,6 +101,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(false); var target = new LabelGraphics(); target.merge(source); @@ -119,6 +123,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(source.scaleByDistance); expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(source.disableDepthTestDistance); + expect(target.rtl).toBe(source.rtl); }); it('merge does not assign assigned properties', function() { @@ -140,6 +145,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(true); var text = new ConstantProperty('my text'); var font = new ConstantProperty('10px serif'); @@ -158,6 +164,7 @@ defineSuite([ var scaleByDistance = new ConstantProperty(new NearFarScalar()); var distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var disableDepthTestDistance = new ConstantProperty(20.0); + var rtl = new ConstantProperty(false); var target = new LabelGraphics(); target.text = text; @@ -177,6 +184,7 @@ defineSuite([ target.scaleByDistance = scaleByDistance; target.distanceDisplayCondition = distanceDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; + target.rtl = rtl; target.merge(source); @@ -197,6 +205,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(scaleByDistance); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); + expect(target.rtl).toBe(rtl); }); it('clone works', function() { @@ -218,6 +227,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.rtl = new ConstantProperty(false); var result = source.clone(); expect(result.text).toBe(source.text); @@ -237,6 +247,7 @@ defineSuite([ expect(result.scaleByDistance).toBe(source.scaleByDistance); expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(result.disableDepthTestDistance).toBe(source.disableDepthTestDistance); + expect(result.rtl).toBe(source.rtl); }); it('merge throws if source undefined', function() { diff --git a/Specs/DataSources/LabelVisualizerSpec.js b/Specs/DataSources/LabelVisualizerSpec.js index c5a2112c6ac6..070355f7d155 100644 --- a/Specs/DataSources/LabelVisualizerSpec.js +++ b/Specs/DataSources/LabelVisualizerSpec.js @@ -161,6 +161,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(10.0); + label.rtl = new ConstantProperty(false); visualizer.update(time); @@ -188,6 +189,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); + expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); testObject.position = new ConstantProperty(new Cartesian3(5678, 1234, 1293434)); label.text = new ConstantProperty('b'); @@ -207,6 +209,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(20.0); + label.rtl = new ConstantProperty(true); visualizer.update(time); expect(l.position).toEqual(testObject.position.getValue(time)); @@ -227,6 +230,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); + expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); label.show = new ConstantProperty(false); visualizer.update(time); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 2f999ca1ea9d..4d5bf2909070 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1840,6 +1840,42 @@ defineSuite([ expect(newlinesBbox.height).toBeGreaterThan(originalBbox.height); }); + it('should not modify text when rtl is false', function() { + var text = 'bla bla bla'; + var label = labels.add({ + text : text + }); + scene.renderForSpecs(); + + expect(label.rtl).toEqual(false); + expect(label.text).toEqual(text); + }); + + it('should not modify text when rtl is true and there is no hebrew characters', function() { + var text = 'bla bla bla'; + var label = labels.add({ + text : text, + rtl : true + }); + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).toEqual(text); + }); + + it('should reverse text when there is only hebrew characters and rtl is true', function() { + var text = 'שלום'; + var label = labels.add({ + text : text + }); + label.rtl = true; + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(text.split('').reverse().join('')); + }); + }, 'WebGL'); it('computes bounding sphere in 3D', function() { From cfb64193d713aff101aa2ab09b80adf852d33b08 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 00:13:34 +0300 Subject: [PATCH 31/47] RTL labels now work in constructor Adjusted the text position and used its setter in order to construct the Label Adjusted tests accordinglt --- Source/Scene/Label.js | 4 +++- Specs/Scene/LabelCollectionSpec.js | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index c02481e6b211..75c8d26894c7 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -135,7 +135,6 @@ define([ this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); this._distanceDisplayCondition = distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); - this._rtl = defaultValue(options.rtl, false); this._labelCollection = labelCollection; this._glyphs = []; @@ -150,6 +149,9 @@ define([ this._clusterShow = true; + this._rtl = defaultValue(options.rtl, false); + this.text = defaultValue(options.text, ''); + this._updateClamping(); } diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 4d5bf2909070..0ff08a6035b9 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1866,9 +1866,10 @@ defineSuite([ it('should reverse text when there is only hebrew characters and rtl is true', function() { var text = 'שלום'; var label = labels.add({ - text : text + text : text, + rtl: true }); - label.rtl = true; + scene.renderForSpecs(); expect(label.rtl).toEqual(true); From ea5ceeb11a6de4c9df5fcddffb60211a0fe8f9dc Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 12:07:21 +0300 Subject: [PATCH 32/47] Updated contributors --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 66b0f0a0c4fa..1cab0bac474b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -90,6 +90,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [William Wall](https://github.com/wallw-bits) * [virtualcitySYSTEMS GmbH](https://www.virtualcitysystems.de) * [Jannes Bolling](https://github.com/jbo023) +* [webiks](https://www.webiks.com) + * [Hod Bauer](https://github.com/hodbauer) ## [Individual CLA](Documentation/Contributors/CLAs/individual-cla-agi-v1.0.txt) * [Victor Berchet](https://github.com/vicb) From c0984547650f5c30436f448d4a8878ceab744dff Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 16:30:39 +0300 Subject: [PATCH 33/47] Support for \n in RTL --- Source/Scene/Label.js | 113 ++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 75c8d26894c7..0eb71c3f310d 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -1303,70 +1303,76 @@ define([ * @param {String} text the text to parse and reorder * @returns {String} the text as rtl direction */ - Label.prototype.reverseRtl = function(text) { + Label.prototype.reverseRtl = function(value) { var rtlChars = /[א-ת]/; - var rtlDir = rtlChars.test(text.charAt(0)); - var parsedText = convertTextToTypes(text, rtlDir, rtlChars); - - var types = declareTypes(); + var texts = value.split('\n'); var result = ''; - var splicePointer = 0; - for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { - var subText = parsedText[wordIndex]; - var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; - if(rtlDir) { - if (subText.Type === types.RTL) { - result = reverseWord(subText.Word) + result; - splicePointer = 0; - } - else if (subText.Type === types.LTR) { - result = spliceWord(result,splicePointer, subText.Word); - splicePointer += subText.Word.length; - } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (parsedText[wordIndex -1].Type === types.RTL) { - result = reverse + result; + for (var i = 0; i < texts.length; i++) { + var text = texts[i]; + var rtlDir = rtlChars.test(text.charAt(0)); + var parsedText = convertTextToTypes(text, rtlDir, rtlChars); + + var types = declareTypes(); + + var splicePointer = 0; + for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { + var subText = parsedText[wordIndex]; + var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; + if(rtlDir) { + if (subText.Type === types.RTL) { + result = reverseWord(subText.Word) + result; splicePointer = 0; } - else { - if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.RTL) { - result = reverse + result; - splicePointer = 0; + else if (subText.Type === types.LTR) { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (parsedText[wordIndex -1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.RTL) { + result = reverse + result; + splicePointer = 0; + } + else { + result = spliceWord(result,splicePointer, subText.Word); + splicePointer += subText.Word.length; + } } else { result = spliceWord(result,splicePointer, subText.Word); - splicePointer += subText.Word.length; } - } - else { - result = spliceWord(result,splicePointer, subText.Word); - } } } - } - else { - if (subText.Type === types.RTL) { - result = spliceWord(result, splicePointer, reverseWord(subText.Word)); - } - else if (subText.Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; - } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (wordIndex > 0) { - if (parsedText[wordIndex -1].Type === types.RTL) { - if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; + else if (subText.Type === types.RTL) { + result = spliceWord(result, splicePointer, reverseWord(subText.Word)); + } + else if (subText.Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { + if (wordIndex > 0) { + if (parsedText[wordIndex -1].Type === types.RTL) { + if (parsedText.length > wordIndex +1) { + if (parsedText[wordIndex +1].Type === types.LTR) { + result += subText.Word; + splicePointer = result.length; + } + else if (parsedText[wordIndex +1].Type === types.RTL) { + result = spliceWord(result, splicePointer, reverse); + } } - else if (parsedText[wordIndex +1].Type === types.RTL) { - result = spliceWord(result, splicePointer, reverse); + else { + result += subText.Word; } } else { result += subText.Word; + splicePointer = result.length; } } else { @@ -1374,14 +1380,11 @@ define([ splicePointer = result.length; } } - else { - result += subText.Word; - splicePointer = result.length; - } - } + } + if (i < texts.length - 1) { + result += '\n'; } } - return result; }; From d3df024e0e4af0eac0dae3fbe6703e68224ce5f6 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 16:46:33 +0300 Subject: [PATCH 34/47] Cache RTL original string In order to prevent the function from running on each update if no change was made to the text --- Source/Scene/Label.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 0eb71c3f310d..9deb7cce9b05 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -285,6 +285,10 @@ define([ //>>includeEnd('debug'); if (this.rtl) { + if (this._originalValue === value) { + return; + } + this._originalValue = value; value = this.reverseRtl(value); } From 7068841cac1d1a0b4c1e13234f98c42c44b70d78 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 16:56:09 +0300 Subject: [PATCH 35/47] Prevent glyphs parser from running for nought --- Source/Scene/Label.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 9deb7cce9b05..bfa13a93f398 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -286,6 +286,7 @@ define([ if (this.rtl) { if (this._originalValue === value) { + value = this._text; return; } this._originalValue = value; From bdb582e384c997eec5bfe587ff4deaf3dd12def3 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 17:13:15 +0300 Subject: [PATCH 36/47] Documentation examples --- Source/Scene/Label.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index bfa13a93f398..172977aa038e 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -1051,6 +1051,25 @@ define([ * @memberof Label.prototype * @type {Boolean} * @default false + * + * @example + * // Example 1. + * // Set a label's rtl during init + * var myLabelEntity = viewer.entities.add({ + * id: 'my label', + * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', + * rtl: true + * }); + * + * @example + * // Example 2. + * var myLabelEntity = viewer.entities.add({ + * id: 'my label', + * text: 'English text' + * }); + * // Set a label's rtl after init + * myLabelEntity.rtl = true; + * myLabelEntity.text = 'טקסט חדש' */ rtl : { get : function() { From 99010f26c99189310fab0873ce615c3c4809259c Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 17:14:14 +0300 Subject: [PATCH 37/47] Updated contributors list --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1cab0bac474b..516e15a3fbfe 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -92,6 +92,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Jannes Bolling](https://github.com/jbo023) * [webiks](https://www.webiks.com) * [Hod Bauer](https://github.com/hodbauer) + * [Yonatan Kra](https://github.com/yonatankra) ## [Individual CLA](Documentation/Contributors/CLAs/individual-cla-agi-v1.0.txt) * [Victor Berchet](https://github.com/vicb) @@ -158,4 +159,5 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Rishabh Shah](https://github.com/rms13) * [Rudraksha Shah](https://github.com/Rudraksha20) * [Cody Guldner](https://github.com/burn123) +* [Yonatan Kra](https://github.com/yonatankra) From ab9ac1966069c7a51b6663288dffcb102a473788 Mon Sep 17 00:00:00 2001 From: hodbauer Date: Sun, 3 Sep 2017 10:18:21 +0300 Subject: [PATCH 38/47] * fix descriptions * make `reverseRtl` private * fix multi-lines support * add complex test * fix some edge cases: * special characters after rtl characters when direction is not rtl * "Weak" characters after brackets at rtl direction --- Apps/Sandcastle/gallery/Labels.html | 4 +- CHANGES.md | 1 + Source/Scene/Label.js | 109 +++++++++++++++------------- Specs/Scene/LabelCollectionSpec.js | 15 ++++ 4 files changed, 78 insertions(+), 51 deletions(-) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index d77681485b4e..edd8d3669696 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -126,7 +126,7 @@ viewer.entities.add({ position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), label : { - text : 'שלום', + text : 'Master (אדון): Hello\nתלמיד (student): שלום', rtl : true } }); @@ -169,7 +169,7 @@ Sandcastle.highlight(scaleByDistance); } }, { - text : 'Set rtl', + text : 'Set label with right-to-left language', onselect : function() { setRtl(); Sandcastle.highlight(setRtl); diff --git a/CHANGES.md b/CHANGES.md index 6a04cd71c154..034639a46306 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Change Log * `Scene/OrthographicOffCenterFrustum` has been removed. Use `Core/OrthographicOffCenterFrustum`. * `Scene/PerspectiveFrustum` has been removed. Use `Core/PerspectiveFrustum`. * `Scene/PerspectiveOffCenterFrustum` has been removed. Use `Core/PerspectiveOffCenterFrustum`. +* Added support for right-to-left languages in labels * Added ability to add an animation to `ModelAnimationCollection` by its index. [#5815](https://github.com/AnalyticalGraphicsInc/cesium/pull/5815) * Fixed a bug in `ModelAnimationCollection` that caused adding an animation by its name to throw an error. [#5815](https://github.com/AnalyticalGraphicsInc/cesium/pull/5815) * Zoom about mouse now maintains camera heading, pitch, and roll [#4639](https://github.com/AnalyticalGraphicsInc/cesium/pull/5603) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 172977aa038e..b5e04ace3d19 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -290,7 +290,7 @@ define([ return; } this._originalValue = value; - value = this.reverseRtl(value); + value = reverseRtl(value); } if (this._text !== value) { @@ -1056,20 +1056,24 @@ define([ * // Example 1. * // Set a label's rtl during init * var myLabelEntity = viewer.entities.add({ - * id: 'my label', - * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', - * rtl: true + * label: { + * id: 'my label', + * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', + * rtl: true + * } * }); * * @example * // Example 2. * var myLabelEntity = viewer.entities.add({ - * id: 'my label', - * text: 'English text' + * label: { + * id: 'my label', + * text: 'English text' + * } * }); * // Set a label's rtl after init * myLabelEntity.rtl = true; - * myLabelEntity.text = 'טקסט חדש' + * myLabelEntity.text = 'טקסט חדש'; */ rtl : { get : function() { @@ -1324,10 +1328,11 @@ define([ /** * - * @param {String} text the text to parse and reorder + * @param {String} value the text to parse and reorder * @returns {String} the text as rtl direction + * @private */ - Label.prototype.reverseRtl = function(value) { + function reverseRtl(value) { var rtlChars = /[א-ת]/; var texts = value.split('\n'); var result = ''; @@ -1339,78 +1344,84 @@ define([ var types = declareTypes(); var splicePointer = 0; - for(var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { + var line = ''; + for (var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { var subText = parsedText[wordIndex]; var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; - if(rtlDir) { + if (rtlDir) { if (subText.Type === types.RTL) { - result = reverseWord(subText.Word) + result; + line = reverseWord(subText.Word) + line; splicePointer = 0; } else if (subText.Type === types.LTR) { - result = spliceWord(result,splicePointer, subText.Word); + line = spliceWord(line, splicePointer, subText.Word); splicePointer += subText.Word.length; } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (parsedText[wordIndex -1].Type === types.RTL) { - result = reverse + result; + else if (subText.Type === types.WEAK || subText.Type === types.BRACKETS) { + if (subText.Type === types.WEAK && parsedText[wordIndex - 1].Type === types.BRACKETS) { + line = reverseWord(subText.Word) + line; + } + else if (parsedText[wordIndex - 1].Type === types.RTL) { + line = reverse + line; splicePointer = 0; } - else if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.RTL) { - result = reverse + result; - splicePointer = 0; - } - else { - result = spliceWord(result,splicePointer, subText.Word); - splicePointer += subText.Word.length; - } + else if (parsedText.length > wordIndex + 1) { + if (parsedText[wordIndex + 1].Type === types.RTL) { + line = reverse + line; + splicePointer = 0; } else { - result = spliceWord(result,splicePointer, subText.Word); + line = spliceWord(line, splicePointer, subText.Word); + splicePointer += subText.Word.length; } + } + else { + line = spliceWord(line, splicePointer, subText.Word); + } } } else if (subText.Type === types.RTL) { - result = spliceWord(result, splicePointer, reverseWord(subText.Word)); - } - else if (subText.Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; - } - else if (subText.Type === types.WEAK || subText.Type ===types.BRACKETS) { - if (wordIndex > 0) { - if (parsedText[wordIndex -1].Type === types.RTL) { - if (parsedText.length > wordIndex +1) { - if (parsedText[wordIndex +1].Type === types.LTR) { - result += subText.Word; - splicePointer = result.length; - } - else if (parsedText[wordIndex +1].Type === types.RTL) { - result = spliceWord(result, splicePointer, reverse); - } + line = spliceWord(line, splicePointer, reverseWord(subText.Word)); + } + else if (subText.Type === types.LTR) { + line += subText.Word; + splicePointer = line.length; + } + else if (subText.Type === types.WEAK || subText.Type === types.BRACKETS) { + if (wordIndex > 0) { + if (parsedText[wordIndex - 1].Type === types.RTL) { + if (parsedText.length > wordIndex + 1) { + if (parsedText[wordIndex + 1].Type === types.RTL) { + line = spliceWord(line, splicePointer, reverse); } else { - result += subText.Word; + line += subText.Word; + splicePointer = line.length; } } else { - result += subText.Word; - splicePointer = result.length; + line += subText.Word; } } else { - result += subText.Word; - splicePointer = result.length; + line += subText.Word; + splicePointer = line.length; } } + else { + line += subText.Word; + splicePointer = line.length; + } + } } + + result += line; if (i < texts.length - 1) { result += '\n'; } } return result; - }; + } return Label; }); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 0ff08a6035b9..dd36426e6bc1 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1877,6 +1877,21 @@ defineSuite([ expect(label.text).toEqual(text.split('').reverse().join('')); }); + it('should reverse part of text when there is mix of right-to-left and other kind of characters and rtl is true', function() { + var text = 'Master (אדון): "Hello"\nתלמיד (student): "שלום"'; + var expectedText = 'Master (ןודא): "Hello"\n"םולש" :(student) דימלת'; + var label = labels.add({ + text : text, + rtl: true + }); + + scene.renderForSpecs(); + + expect(label.rtl).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(expectedText); + }); + }, 'WebGL'); it('computes bounding sphere in 3D', function() { From 84e5d95eaf2ac414aff2a6d13749badc4c142300 Mon Sep 17 00:00:00 2001 From: Yonatan Date: Wed, 23 Aug 2017 00:13:34 +0300 Subject: [PATCH 39/47] RTL labels now work in constructor Adjusted the text position and used its setter in order to construct the Label Adjusted tests accordinglt --- Source/Scene/Label.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index b5e04ace3d19..6636471b8580 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -112,7 +112,6 @@ define([ distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition); } - this._text = defaultValue(options.text, ''); this._show = defaultValue(options.show, true); this._font = defaultValue(options.font, '30px sans-serif'); this._fillColor = Color.clone(defaultValue(options.fillColor, Color.WHITE)); From eec1e4d9f186c4cf318fb34d6c3a6d07db33b53b Mon Sep 17 00:00:00 2001 From: hodbauer Date: Mon, 11 Sep 2017 23:32:09 +0300 Subject: [PATCH 40/47] rename rtl to rightToLeft --- Apps/Sandcastle/gallery/Labels.html | 10 +++++----- Source/DataSources/LabelGraphics.js | 12 ++++++------ Source/DataSources/LabelVisualizer.js | 4 ++-- Source/Scene/Label.js | 22 +++++++++++----------- Specs/DataSources/LabelGraphicsSpec.js | 22 +++++++++++----------- Specs/DataSources/LabelVisualizerSpec.js | 8 ++++---- Specs/Scene/LabelCollectionSpec.js | 22 +++++++++++----------- 7 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index edd8d3669696..a6a9d7a45fd5 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -121,13 +121,13 @@ }); } -function setRtl() { - Sandcastle.declare(setRtl); +function setRightToLeft() { + Sandcastle.declare(setRightToLeft); viewer.entities.add({ position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), label : { text : 'Master (אדון): Hello\nתלמיד (student): שלום', - rtl : true + rightToLeft : true } }); } @@ -171,8 +171,8 @@ }, { text : 'Set label with right-to-left language', onselect : function() { - setRtl(); - Sandcastle.highlight(setRtl); + setRightToLeft(); + Sandcastle.highlight(setRightToLeft); } }]); diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js index 0b8b81217810..78f7dcedbfd7 100644 --- a/Source/DataSources/LabelGraphics.js +++ b/Source/DataSources/LabelGraphics.js @@ -47,7 +47,7 @@ define([ * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to set scale based on distance from the camera. * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this label will be displayed. - * @param {Property} [options.rtl=false] A Property specifying if to modify text when there is possibly rtl characters. + * @param {Property} [options.rightToLeft=false] A Property specifying if to modify text when there is possibly rightToLeft characters. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} */ @@ -94,8 +94,8 @@ define([ this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; - this._rtl = undefined; - this._rtlSubscribtion = undefined; + this._rightToLeft = undefined; + this._rightToLeftSubscribtion = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -329,7 +329,7 @@ define([ * @type {Property} * @default false */ - rtl: createPropertyDescriptor('rtl') + rightToLeft: createPropertyDescriptor('rightToLeft') }); /** @@ -363,7 +363,7 @@ define([ result.scaleByDistance = this.scaleByDistance; result.distanceDisplayCondition = this.distanceDisplayCondition; result.disableDepthTestDistance = this.disableDepthTestDistance; - result.rtl = this.rtl; + result.rightToLeft = this.rightToLeft; return result; }; @@ -401,7 +401,7 @@ define([ this.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance); this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); - this.rtl = defaultValue(this.rtl, source.rtl); + this.rightToLeft = defaultValue(this.rightToLeft, source.rightToLeft); }; return LabelGraphics; diff --git a/Source/DataSources/LabelVisualizer.js b/Source/DataSources/LabelVisualizer.js index 87df78e5692c..989587252db6 100644 --- a/Source/DataSources/LabelVisualizer.js +++ b/Source/DataSources/LabelVisualizer.js @@ -49,7 +49,7 @@ define([ var defaultHorizontalOrigin = HorizontalOrigin.CENTER; var defaultVerticalOrigin = VerticalOrigin.CENTER; var defaultDisableDepthTestDistance = 0.0; - var defaultRtl = false; + var defaultRightToLeft = false; var position = new Cartesian3(); var fillColor = new Color(); @@ -166,7 +166,7 @@ define([ label.scaleByDistance = Property.getValueOrUndefined(labelGraphics._scaleByDistance, time, scaleByDistance); label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition); label.disableDepthTestDistance = Property.getValueOrDefault(labelGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); - label.rtl = Property.getValueOrDefault(labelGraphics._rtl, time, defaultRtl); + label.rightToLeft = Property.getValueOrDefault(labelGraphics._rightToLeft, time, defaultRightToLeft); } return true; }; diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 6636471b8580..e0720779f65e 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -148,7 +148,7 @@ define([ this._clusterShow = true; - this._rtl = defaultValue(options.rtl, false); + this._rightToLeft = defaultValue(options.rightToLeft, false); this.text = defaultValue(options.text, ''); this._updateClamping(); @@ -283,7 +283,7 @@ define([ } //>>includeEnd('debug'); - if (this.rtl) { + if (this.rightToLeft) { if (this._originalValue === value) { value = this._text; return; @@ -1053,12 +1053,12 @@ define([ * * @example * // Example 1. - * // Set a label's rtl during init + * // Set a label's rightToLeft during init * var myLabelEntity = viewer.entities.add({ * label: { * id: 'my label', * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', - * rtl: true + * rightToLeft: true * } * }); * @@ -1070,17 +1070,17 @@ define([ * text: 'English text' * } * }); - * // Set a label's rtl after init - * myLabelEntity.rtl = true; + * // Set a label's rightToLeft after init + * myLabelEntity.rightToLeft = true; * myLabelEntity.text = 'טקסט חדש'; */ - rtl : { + rightToLeft : { get : function() { - return this._rtl; + return this._rightToLeft; }, set : function(value) { - if (this._rtl !== value) { - this._rtl = value; + if (this._rightToLeft !== value) { + this._rightToLeft = value; } } } @@ -1328,7 +1328,7 @@ define([ /** * * @param {String} value the text to parse and reorder - * @returns {String} the text as rtl direction + * @returns {String} the text as rightToLeft direction * @private */ function reverseRtl(value) { diff --git a/Specs/DataSources/LabelGraphicsSpec.js b/Specs/DataSources/LabelGraphicsSpec.js index 1539dbbe7b35..6229ede3606a 100644 --- a/Specs/DataSources/LabelGraphicsSpec.js +++ b/Specs/DataSources/LabelGraphicsSpec.js @@ -41,7 +41,7 @@ defineSuite([ scaleByDistance : new NearFarScalar(17, 18, 19, 20), distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance : 10.0, - rtl : false + rightToLeft : false }; var label = new LabelGraphics(options); @@ -61,7 +61,7 @@ defineSuite([ expect(label.scaleByDistance).toBeInstanceOf(ConstantProperty); expect(label.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(label.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); - expect(label.rtl).toBeInstanceOf(ConstantProperty); + expect(label.rightToLeft).toBeInstanceOf(ConstantProperty); expect(label.text.getValue()).toEqual(options.text); expect(label.font.getValue()).toEqual(options.font); @@ -79,7 +79,7 @@ defineSuite([ expect(label.scaleByDistance.getValue()).toEqual(options.scaleByDistance); expect(label.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); expect(label.disableDepthTestDistance.getValue()).toEqual(options.disableDepthTestDistance); - expect(label.rtl.getValue()).toEqual(options.rtl); + expect(label.rightToLeft.getValue()).toEqual(options.rightToLeft); }); it('merge assigns unassigned properties', function() { @@ -101,7 +101,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); - source.rtl = new ConstantProperty(false); + source.rightToLeft = new ConstantProperty(false); var target = new LabelGraphics(); target.merge(source); @@ -123,7 +123,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(source.scaleByDistance); expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(source.disableDepthTestDistance); - expect(target.rtl).toBe(source.rtl); + expect(target.rightToLeft).toBe(source.rightToLeft); }); it('merge does not assign assigned properties', function() { @@ -145,7 +145,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); - source.rtl = new ConstantProperty(true); + source.rightToLeft = new ConstantProperty(true); var text = new ConstantProperty('my text'); var font = new ConstantProperty('10px serif'); @@ -164,7 +164,7 @@ defineSuite([ var scaleByDistance = new ConstantProperty(new NearFarScalar()); var distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var disableDepthTestDistance = new ConstantProperty(20.0); - var rtl = new ConstantProperty(false); + var rightToLeft = new ConstantProperty(false); var target = new LabelGraphics(); target.text = text; @@ -184,7 +184,7 @@ defineSuite([ target.scaleByDistance = scaleByDistance; target.distanceDisplayCondition = distanceDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; - target.rtl = rtl; + target.rightToLeft = rightToLeft; target.merge(source); @@ -205,7 +205,7 @@ defineSuite([ expect(target.scaleByDistance).toBe(scaleByDistance); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); - expect(target.rtl).toBe(rtl); + expect(target.rightToLeft).toBe(rightToLeft); }); it('clone works', function() { @@ -227,7 +227,7 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); - source.rtl = new ConstantProperty(false); + source.rightToLeft = new ConstantProperty(false); var result = source.clone(); expect(result.text).toBe(source.text); @@ -247,7 +247,7 @@ defineSuite([ expect(result.scaleByDistance).toBe(source.scaleByDistance); expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(result.disableDepthTestDistance).toBe(source.disableDepthTestDistance); - expect(result.rtl).toBe(source.rtl); + expect(result.rightToLeft).toBe(source.rightToLeft); }); it('merge throws if source undefined', function() { diff --git a/Specs/DataSources/LabelVisualizerSpec.js b/Specs/DataSources/LabelVisualizerSpec.js index 070355f7d155..f15c9b78e7d2 100644 --- a/Specs/DataSources/LabelVisualizerSpec.js +++ b/Specs/DataSources/LabelVisualizerSpec.js @@ -161,7 +161,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(10.0); - label.rtl = new ConstantProperty(false); + label.rightToLeft = new ConstantProperty(false); visualizer.update(time); @@ -189,7 +189,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); - expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); + expect(l.rightToLeft).toEqual(testObject.label.rightToLeft.getValue(time)); testObject.position = new ConstantProperty(new Cartesian3(5678, 1234, 1293434)); label.text = new ConstantProperty('b'); @@ -209,7 +209,7 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(20.0); - label.rtl = new ConstantProperty(true); + label.rightToLeft = new ConstantProperty(true); visualizer.update(time); expect(l.position).toEqual(testObject.position.getValue(time)); @@ -230,7 +230,7 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); - expect(l.rtl).toEqual(testObject.label.rtl.getValue(time)); + expect(l.rightToLeft).toEqual(testObject.label.rightToLeft.getValue(time)); label.show = new ConstantProperty(false); visualizer.update(time); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index dd36426e6bc1..995b4b51149a 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1840,54 +1840,54 @@ defineSuite([ expect(newlinesBbox.height).toBeGreaterThan(originalBbox.height); }); - it('should not modify text when rtl is false', function() { + it('should not modify text when rightToLeft is false', function() { var text = 'bla bla bla'; var label = labels.add({ text : text }); scene.renderForSpecs(); - expect(label.rtl).toEqual(false); + expect(label.rightToLeft).toEqual(false); expect(label.text).toEqual(text); }); - it('should not modify text when rtl is true and there is no hebrew characters', function() { + it('should not modify text when rightToLeft is true and there is no hebrew characters', function() { var text = 'bla bla bla'; var label = labels.add({ text : text, - rtl : true + rightToLeft : true }); scene.renderForSpecs(); - expect(label.rtl).toEqual(true); + expect(label.rightToLeft).toEqual(true); expect(label.text).toEqual(text); }); - it('should reverse text when there is only hebrew characters and rtl is true', function() { + it('should reverse text when there is only hebrew characters and rightToLeft is true', function() { var text = 'שלום'; var label = labels.add({ text : text, - rtl: true + rightToLeft: true }); scene.renderForSpecs(); - expect(label.rtl).toEqual(true); + expect(label.rightToLeft).toEqual(true); expect(label.text).not.toEqual(text); expect(label.text).toEqual(text.split('').reverse().join('')); }); - it('should reverse part of text when there is mix of right-to-left and other kind of characters and rtl is true', function() { + it('should reverse part of text when there is mix of right-to-left and other kind of characters and rightToLeft is true', function() { var text = 'Master (אדון): "Hello"\nתלמיד (student): "שלום"'; var expectedText = 'Master (ןודא): "Hello"\n"םולש" :(student) דימלת'; var label = labels.add({ text : text, - rtl: true + rightToLeft: true }); scene.renderForSpecs(); - expect(label.rtl).toEqual(true); + expect(label.rightToLeft).toEqual(true); expect(label.text).not.toEqual(text); expect(label.text).toEqual(expectedText); }); From b3971a9dbd5c7137216c0ce2bfc792629b4684fe Mon Sep 17 00:00:00 2001 From: hodbauer Date: Sun, 17 Sep 2017 09:30:52 +0300 Subject: [PATCH 41/47] add link to PR at CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 034639a46306..5c318e42253c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Change Log * `Scene/OrthographicOffCenterFrustum` has been removed. Use `Core/OrthographicOffCenterFrustum`. * `Scene/PerspectiveFrustum` has been removed. Use `Core/PerspectiveFrustum`. * `Scene/PerspectiveOffCenterFrustum` has been removed. Use `Core/PerspectiveOffCenterFrustum`. -* Added support for right-to-left languages in labels +* Added support for right-to-left languages in labels. [#5771](https://github.com/AnalyticalGraphicsInc/cesium/pull/5771) * Added ability to add an animation to `ModelAnimationCollection` by its index. [#5815](https://github.com/AnalyticalGraphicsInc/cesium/pull/5815) * Fixed a bug in `ModelAnimationCollection` that caused adding an animation by its name to throw an error. [#5815](https://github.com/AnalyticalGraphicsInc/cesium/pull/5815) * Zoom about mouse now maintains camera heading, pitch, and roll [#4639](https://github.com/AnalyticalGraphicsInc/cesium/pull/5603) From df1ddeadb4e805423bfdac6a70c2605552d8473b Mon Sep 17 00:00:00 2001 From: hodbauer Date: Mon, 16 Oct 2017 22:55:57 +0300 Subject: [PATCH 42/47] * add more tests for better code coverage * fix some edge cases * change the regex that identify right-to-left languages to use unicode instead of hebrew chars for js engines that can't parse this letters (like jscoverage) --- CHANGES.md | 1 + CONTRIBUTORS.md | 2 - Source/Scene/Label.js | 12 +++-- Specs/Scene/LabelCollectionSpec.js | 76 +++++++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 482555cc0d9b..f65c269dab42 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ Change Log ========== ### 1.39 - 2017-11-01 + * Added support for right-to-left languages in labels. [#5771](https://github.com/AnalyticalGraphicsInc/cesium/pull/5771) * Added support for the layer.json `parentUrl` property in `CesiumTerrainProvider` to allow for compositing of tilesets. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4e44bea94ace..5d9183fcc4d2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -162,5 +162,3 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Rudraksha Shah](https://github.com/Rudraksha20) * [Cody Guldner](https://github.com/burn123) * [Nacho Carnicero](https://github.com/nacho-carnicero) -* [Yonatan Kra](https://github.com/yonatankra) - diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index e0720779f65e..546247b8a782 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -1283,11 +1283,17 @@ define([ currentType = types.WEAK; } + if (textIndex === 0) { + lastType = currentType; + } + if (lastType === currentType && currentType !== types.BRACKETS) { word += character; } else { - parsedText.push({Type : lastType, Word : word}); + if (word !== '') { + parsedText.push({Type : lastType, Word : word}); + } lastType = currentType; word = character; } @@ -1332,7 +1338,7 @@ define([ * @private */ function reverseRtl(value) { - var rtlChars = /[א-ת]/; + var rtlChars = /[\u05D0-\u05EA]/; var texts = value.split('\n'); var result = ''; for (var i = 0; i < texts.length; i++) { @@ -1375,7 +1381,7 @@ define([ } } else { - line = spliceWord(line, splicePointer, subText.Word); + line = spliceWord(line, 0, reverse); } } } diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 995b4b51149a..345021538184 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1867,8 +1867,36 @@ defineSuite([ var text = 'שלום'; var label = labels.add({ text : text, - rightToLeft: true + rightToLeft : true + }); + + scene.renderForSpecs(); + + expect(label.rightToLeft).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(text.split('').reverse().join('')); + }); + + it('should reverse text even if rightToLeft is set to true after creation of label', function() { + var text = 'שלום'; + var label = labels.add({}); + label.rightToLeft = true; + label.text = text; + + scene.renderForSpecs(); + + expect(label.rightToLeft).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(text.split('').reverse().join('')); + }); + + it('should not change text if it updated to the same value, although rightToLeft is true', function() { + var text = 'שלום'; + var label = labels.add({ + text : text, + rightToLeft : true }); + label.text = text; scene.renderForSpecs(); @@ -1882,7 +1910,7 @@ defineSuite([ var expectedText = 'Master (ןודא): "Hello"\n"םולש" :(student) דימלת'; var label = labels.add({ text : text, - rightToLeft: true + rightToLeft : true }); scene.renderForSpecs(); @@ -1892,6 +1920,50 @@ defineSuite([ expect(label.text).toEqual(expectedText); }); + it('should reverse all text and replace brackets when there is right-to-left characters and rightToLeft is true', function() { + var text = 'משפט [מורכב] {עם} תווים <מיוחדים special>'; + var expectedText = ' םיוות {םע} [בכרומ] טפשמ'; + var label = labels.add({ + text : text, + rightToLeft : true + }); + + scene.renderForSpecs(); + + expect(label.rightToLeft).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(expectedText); + }); + + it('should reverse only text that detected as rtl text when it begin with non rtl characters when rightToLeft is true', function() { + var text = '(interesting sentence with hebrew characters) שלום(עליך)חביבי.'; + var expectedText = '(interesting sentence with hebrew characters) יביבח(ךילע)םולש.'; + var label = labels.add({ + text : text, + rightToLeft : true + }); + + scene.renderForSpecs(); + + expect(label.rightToLeft).toEqual(true); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(expectedText); + }); + + it('should not change nothing if it only non alphanumeric characters when rightToLeft is true', function() { + var text = '([{- -}])'; + var expectedText = '([{- -}])'; + var label = labels.add({ + text : text, + rightToLeft : true + }); + + scene.renderForSpecs(); + + expect(label.rightToLeft).toEqual(true); + expect(label.text).toEqual(expectedText); + }); + }, 'WebGL'); it('computes bounding sphere in 3D', function() { From 32c56574fc30368ddebea8f060c5fbd1934d053a Mon Sep 17 00:00:00 2001 From: hodbauer Date: Mon, 23 Oct 2017 23:39:53 +0300 Subject: [PATCH 43/47] declare textTypes of rightToLeft mechanism as object and not a function --- Source/Scene/Label.js | 60 +++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 546247b8a782..d3ce099e89b7 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -34,6 +34,13 @@ define([ VerticalOrigin) { 'use strict'; + var textTypes = freezeObject({ + LTR : 0, + RTL : 1, + WEAK : 2, + BRACKETS : 3 + }); + function rebindAllGlyphs(label) { if (!label._rebindAllGlyphs && !label._repositionAllGlyphs) { // only push label if it's not already been marked dirty @@ -1249,45 +1256,34 @@ define([ return false; }; - function declareTypes() { - var TextTypes = { - LTR : 0, - RTL : 1, - WEAK : 2, - BRACKETS : 3 - }; - return freezeObject(TextTypes); - } - - function convertTextToTypes(text, rtlDir, rtlChars) { + function convertTextToTypes(text, rtlChars) { var ltrChars = /[a-zA-Z0-9]/; var bracketsChars = /[()[\]{}<>]/; var parsedText = []; var word = ''; - var types = declareTypes(); - var lastType = rtlDir ? types.RTL : types.LTR; + var lastType = textTypes.LTR; var currentType = ''; var textLength = text.length; for (var textIndex = 0; textIndex < textLength; ++textIndex) { var character = text.charAt(textIndex); if (rtlChars.test(character)) { - currentType = types.RTL; + currentType = textTypes.RTL; } else if (ltrChars.test(character)) { - currentType = types.LTR; + currentType = textTypes.LTR; } else if (bracketsChars.test(character)) { - currentType = types.BRACKETS; + currentType = textTypes.BRACKETS; } else { - currentType = types.WEAK; + currentType = textTypes.WEAK; } if (textIndex === 0) { lastType = currentType; } - if (lastType === currentType && currentType !== types.BRACKETS) { + if (lastType === currentType && currentType !== textTypes.BRACKETS) { word += character; } else { @@ -1344,34 +1340,32 @@ define([ for (var i = 0; i < texts.length; i++) { var text = texts[i]; var rtlDir = rtlChars.test(text.charAt(0)); - var parsedText = convertTextToTypes(text, rtlDir, rtlChars); - - var types = declareTypes(); + var parsedText = convertTextToTypes(text, rtlChars); var splicePointer = 0; var line = ''; for (var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) { var subText = parsedText[wordIndex]; - var reverse = subText.Type === types.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; + var reverse = subText.Type === textTypes.BRACKETS ? reverseBrackets(subText.Word) : subText.Word; if (rtlDir) { - if (subText.Type === types.RTL) { + if (subText.Type === textTypes.RTL) { line = reverseWord(subText.Word) + line; splicePointer = 0; } - else if (subText.Type === types.LTR) { + else if (subText.Type === textTypes.LTR) { line = spliceWord(line, splicePointer, subText.Word); splicePointer += subText.Word.length; } - else if (subText.Type === types.WEAK || subText.Type === types.BRACKETS) { - if (subText.Type === types.WEAK && parsedText[wordIndex - 1].Type === types.BRACKETS) { + else if (subText.Type === textTypes.WEAK || subText.Type === textTypes.BRACKETS) { + if (subText.Type === textTypes.WEAK && parsedText[wordIndex - 1].Type === textTypes.BRACKETS) { line = reverseWord(subText.Word) + line; } - else if (parsedText[wordIndex - 1].Type === types.RTL) { + else if (parsedText[wordIndex - 1].Type === textTypes.RTL) { line = reverse + line; splicePointer = 0; } else if (parsedText.length > wordIndex + 1) { - if (parsedText[wordIndex + 1].Type === types.RTL) { + if (parsedText[wordIndex + 1].Type === textTypes.RTL) { line = reverse + line; splicePointer = 0; } @@ -1385,18 +1379,18 @@ define([ } } } - else if (subText.Type === types.RTL) { + else if (subText.Type === textTypes.RTL) { line = spliceWord(line, splicePointer, reverseWord(subText.Word)); } - else if (subText.Type === types.LTR) { + else if (subText.Type === textTypes.LTR) { line += subText.Word; splicePointer = line.length; } - else if (subText.Type === types.WEAK || subText.Type === types.BRACKETS) { + else if (subText.Type === textTypes.WEAK || subText.Type === textTypes.BRACKETS) { if (wordIndex > 0) { - if (parsedText[wordIndex - 1].Type === types.RTL) { + if (parsedText[wordIndex - 1].Type === textTypes.RTL) { if (parsedText.length > wordIndex + 1) { - if (parsedText[wordIndex + 1].Type === types.RTL) { + if (parsedText[wordIndex + 1].Type === textTypes.RTL) { line = spliceWord(line, splicePointer, reverse); } else { From 118abe482aedb99343d807e6df84114e52d6dc7e Mon Sep 17 00:00:00 2001 From: hodbauer Date: Tue, 24 Oct 2017 00:52:50 +0300 Subject: [PATCH 44/47] manage rightToLeft as a static property of Label --- Apps/Sandcastle/gallery/Labels.html | 4 +- Source/DataSources/LabelGraphics.js | 15 +-- Source/DataSources/LabelVisualizer.js | 2 - Source/Scene/Label.js | 76 +++++------ Specs/DataSources/LabelGraphicsSpec.js | 13 +- Specs/DataSources/LabelVisualizerSpec.js | 4 - Specs/Scene/LabelCollectionSpec.js | 155 +++++++++++------------ 7 files changed, 110 insertions(+), 159 deletions(-) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index a6a9d7a45fd5..73450ff8e150 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -123,11 +123,11 @@ function setRightToLeft() { Sandcastle.declare(setRightToLeft); + Cesium.Label.enableRightToLeftDetection = true; viewer.entities.add({ position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), label : { - text : 'Master (אדון): Hello\nתלמיד (student): שלום', - rightToLeft : true + text : 'Master (אדון): Hello\nתלמיד (student): שלום' } }); } diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js index 555964a5ca55..b06f92f343ae 100644 --- a/Source/DataSources/LabelGraphics.js +++ b/Source/DataSources/LabelGraphics.js @@ -47,7 +47,6 @@ define([ * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to set scale based on distance from the camera. * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to. * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this label will be displayed. - * @param {Property} [options.rightToLeft=false] A Property specifying if to modify text when there is possibly rightToLeft characters. * @param {Property} [options.disableDepthTestDistance] A Property specifying the distance from the camera at which to disable the depth test to. * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} @@ -95,8 +94,6 @@ define([ this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; - this._rightToLeft = undefined; - this._rightToLeftSubscribtion = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -322,15 +319,7 @@ define([ * @memberof LabelGraphics.prototype * @type {Property} */ - disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance'), - - /** - * Gets or sets the ability to modify text characters direction. - * @memberof LabelGraphics.prototype - * @type {Property} - * @default false - */ - rightToLeft: createPropertyDescriptor('rightToLeft') + disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') }); /** @@ -364,7 +353,6 @@ define([ result.scaleByDistance = this.scaleByDistance; result.distanceDisplayCondition = this.distanceDisplayCondition; result.disableDepthTestDistance = this.disableDepthTestDistance; - result.rightToLeft = this.rightToLeft; return result; }; @@ -402,7 +390,6 @@ define([ this.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance); this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); - this.rightToLeft = defaultValue(this.rightToLeft, source.rightToLeft); }; return LabelGraphics; diff --git a/Source/DataSources/LabelVisualizer.js b/Source/DataSources/LabelVisualizer.js index 989587252db6..208fd19c31c0 100644 --- a/Source/DataSources/LabelVisualizer.js +++ b/Source/DataSources/LabelVisualizer.js @@ -49,7 +49,6 @@ define([ var defaultHorizontalOrigin = HorizontalOrigin.CENTER; var defaultVerticalOrigin = VerticalOrigin.CENTER; var defaultDisableDepthTestDistance = 0.0; - var defaultRightToLeft = false; var position = new Cartesian3(); var fillColor = new Color(); @@ -166,7 +165,6 @@ define([ label.scaleByDistance = Property.getValueOrUndefined(labelGraphics._scaleByDistance, time, scaleByDistance); label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition); label.disableDepthTestDistance = Property.getValueOrDefault(labelGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); - label.rightToLeft = Property.getValueOrDefault(labelGraphics._rightToLeft, time, defaultRightToLeft); } return true; }; diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index d3ce099e89b7..1cf1a1ab1d94 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -119,6 +119,7 @@ define([ distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition); } + this._text = defaultValue(options.text, ''); this._show = defaultValue(options.show, true); this._font = defaultValue(options.font, '30px sans-serif'); this._fillColor = Color.clone(defaultValue(options.fillColor, Color.WHITE)); @@ -155,9 +156,6 @@ define([ this._clusterShow = true; - this._rightToLeft = defaultValue(options.rightToLeft, false); - this.text = defaultValue(options.text, ''); - this._updateClamping(); } @@ -290,7 +288,7 @@ define([ } //>>includeEnd('debug'); - if (this.rightToLeft) { + if (Label.enableRightToLeftDetection) { if (this._originalValue === value) { value = this._text; return; @@ -1050,46 +1048,6 @@ define([ } } } - }, - - /** - * Determines whether or not run the reverseRtl algorithm on the text of the label - * @memberof Label.prototype - * @type {Boolean} - * @default false - * - * @example - * // Example 1. - * // Set a label's rightToLeft during init - * var myLabelEntity = viewer.entities.add({ - * label: { - * id: 'my label', - * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', - * rightToLeft: true - * } - * }); - * - * @example - * // Example 2. - * var myLabelEntity = viewer.entities.add({ - * label: { - * id: 'my label', - * text: 'English text' - * } - * }); - * // Set a label's rightToLeft after init - * myLabelEntity.rightToLeft = true; - * myLabelEntity.text = 'טקסט חדש'; - */ - rightToLeft : { - get : function() { - return this._rightToLeft; - }, - set : function(value) { - if (this._rightToLeft !== value) { - this._rightToLeft = value; - } - } } }); @@ -1256,6 +1214,36 @@ define([ return false; }; + /** + * Determines whether or not run the reverseRtl algorithm on the text of the label + * @memberof Label + * @type {Boolean} + * @default false + * + * @example + * // Set a label's rightToLeft before init + * Cesium.Label.enableRightToLeftDetection = true; + * var myLabelEntity = viewer.entities.add({ + * label: { + * id: 'my label', + * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה', + * } + * }); + * + * @example + * // Example 2. + * var myLabelEntity = viewer.entities.add({ + * label: { + * id: 'my label', + * text: 'English text' + * } + * }); + * // Set a label's rightToLeft after init + * Cesium.Label.enableRightToLeftDetection = true; + * myLabelEntity.text = 'טקסט חדש'; + */ + Label.enableRightToLeftDetection = false; + function convertTextToTypes(text, rtlChars) { var ltrChars = /[a-zA-Z0-9]/; var bracketsChars = /[()[\]{}<>]/; diff --git a/Specs/DataSources/LabelGraphicsSpec.js b/Specs/DataSources/LabelGraphicsSpec.js index 6229ede3606a..282fc3d7a824 100644 --- a/Specs/DataSources/LabelGraphicsSpec.js +++ b/Specs/DataSources/LabelGraphicsSpec.js @@ -40,8 +40,7 @@ defineSuite([ pixelOffsetScaleByDistance : new NearFarScalar(13, 14, 15, 16), scaleByDistance : new NearFarScalar(17, 18, 19, 20), distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0), - disableDepthTestDistance : 10.0, - rightToLeft : false + disableDepthTestDistance : 10.0 }; var label = new LabelGraphics(options); @@ -61,7 +60,6 @@ defineSuite([ expect(label.scaleByDistance).toBeInstanceOf(ConstantProperty); expect(label.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(label.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); - expect(label.rightToLeft).toBeInstanceOf(ConstantProperty); expect(label.text.getValue()).toEqual(options.text); expect(label.font.getValue()).toEqual(options.font); @@ -79,7 +77,6 @@ defineSuite([ expect(label.scaleByDistance.getValue()).toEqual(options.scaleByDistance); expect(label.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); expect(label.disableDepthTestDistance.getValue()).toEqual(options.disableDepthTestDistance); - expect(label.rightToLeft.getValue()).toEqual(options.rightToLeft); }); it('merge assigns unassigned properties', function() { @@ -101,7 +98,6 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); - source.rightToLeft = new ConstantProperty(false); var target = new LabelGraphics(); target.merge(source); @@ -123,7 +119,6 @@ defineSuite([ expect(target.scaleByDistance).toBe(source.scaleByDistance); expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(source.disableDepthTestDistance); - expect(target.rightToLeft).toBe(source.rightToLeft); }); it('merge does not assign assigned properties', function() { @@ -145,7 +140,6 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); - source.rightToLeft = new ConstantProperty(true); var text = new ConstantProperty('my text'); var font = new ConstantProperty('10px serif'); @@ -164,7 +158,6 @@ defineSuite([ var scaleByDistance = new ConstantProperty(new NearFarScalar()); var distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); var disableDepthTestDistance = new ConstantProperty(20.0); - var rightToLeft = new ConstantProperty(false); var target = new LabelGraphics(); target.text = text; @@ -184,7 +177,6 @@ defineSuite([ target.scaleByDistance = scaleByDistance; target.distanceDisplayCondition = distanceDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; - target.rightToLeft = rightToLeft; target.merge(source); @@ -205,7 +197,6 @@ defineSuite([ expect(target.scaleByDistance).toBe(scaleByDistance); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); - expect(target.rightToLeft).toBe(rightToLeft); }); it('clone works', function() { @@ -227,7 +218,6 @@ defineSuite([ source.scaleByDistance = new ConstantProperty(new NearFarScalar(1.0, 0.0, 3.0e9, 0.0)); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); source.disableDepthTestDistance = new ConstantProperty(10.0); - source.rightToLeft = new ConstantProperty(false); var result = source.clone(); expect(result.text).toBe(source.text); @@ -247,7 +237,6 @@ defineSuite([ expect(result.scaleByDistance).toBe(source.scaleByDistance); expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); expect(result.disableDepthTestDistance).toBe(source.disableDepthTestDistance); - expect(result.rightToLeft).toBe(source.rightToLeft); }); it('merge throws if source undefined', function() { diff --git a/Specs/DataSources/LabelVisualizerSpec.js b/Specs/DataSources/LabelVisualizerSpec.js index f15c9b78e7d2..c5a2112c6ac6 100644 --- a/Specs/DataSources/LabelVisualizerSpec.js +++ b/Specs/DataSources/LabelVisualizerSpec.js @@ -161,7 +161,6 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(10.0); - label.rightToLeft = new ConstantProperty(false); visualizer.update(time); @@ -189,7 +188,6 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); - expect(l.rightToLeft).toEqual(testObject.label.rightToLeft.getValue(time)); testObject.position = new ConstantProperty(new Cartesian3(5678, 1234, 1293434)); label.text = new ConstantProperty('b'); @@ -209,7 +207,6 @@ defineSuite([ label.scaleByDistance = new ConstantProperty(new NearFarScalar()); label.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); label.disableDepthTestDistance = new ConstantProperty(20.0); - label.rightToLeft = new ConstantProperty(true); visualizer.update(time); expect(l.position).toEqual(testObject.position.getValue(time)); @@ -230,7 +227,6 @@ defineSuite([ expect(l.scaleByDistance).toEqual(testObject.label.scaleByDistance.getValue(time)); expect(l.distanceDisplayCondition).toEqual(testObject.label.distanceDisplayCondition.getValue(time)); expect(l.disableDepthTestDistance).toEqual(testObject.label.disableDepthTestDistance.getValue(time)); - expect(l.rightToLeft).toEqual(testObject.label.rightToLeft.getValue(time)); label.show = new ConstantProperty(false); visualizer.update(time); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 345021538184..9ba0a0fe0ec7 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1841,127 +1841,120 @@ defineSuite([ }); it('should not modify text when rightToLeft is false', function() { - var text = 'bla bla bla'; - var label = labels.add({ - text : text - }); - scene.renderForSpecs(); + var text = 'bla bla bla'; + var label = labels.add({ + text : text + }); + scene.renderForSpecs(); - expect(label.rightToLeft).toEqual(false); - expect(label.text).toEqual(text); + expect(label.text).toEqual(text); }); it('should not modify text when rightToLeft is true and there is no hebrew characters', function() { - var text = 'bla bla bla'; - var label = labels.add({ - text : text, - rightToLeft : true - }); - scene.renderForSpecs(); + Label.enableRightToLeftDetection = true; + var text = 'bla bla bla'; + var label = labels.add({ + text : text + }); + scene.renderForSpecs(); - expect(label.rightToLeft).toEqual(true); - expect(label.text).toEqual(text); + expect(label.text).toEqual(text); }); it('should reverse text when there is only hebrew characters and rightToLeft is true', function() { - var text = 'שלום'; - var label = labels.add({ - text : text, - rightToLeft : true - }); + Label.enableRightToLeftDetection = true; + var text = 'שלום'; + var label = labels.add({ + text : text + }); - scene.renderForSpecs(); + scene.renderForSpecs(); - expect(label.rightToLeft).toEqual(true); - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(text.split('').reverse().join('')); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(text.split('').reverse().join('')); }); it('should reverse text even if rightToLeft is set to true after creation of label', function() { - var text = 'שלום'; - var label = labels.add({}); - label.rightToLeft = true; - label.text = text; + var text = 'שלום'; + var expectedText = 'םלוש'; + var label = labels.add(); + Label.enableRightToLeftDetection = true; + label.text = text; - scene.renderForSpecs(); + scene.renderForSpecs(); - expect(label.rightToLeft).toEqual(true); - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(text.split('').reverse().join('')); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(expectedText); }); it('should not change text if it updated to the same value, although rightToLeft is true', function() { - var text = 'שלום'; - var label = labels.add({ - text : text, - rightToLeft : true - }); - label.text = text; + Label.enableRightToLeftDetection = true; + var text = 'שלום'; + var expectedText = 'םלוש'; + var label = labels.add({ + text : text + }); + label.text = text; - scene.renderForSpecs(); + scene.renderForSpecs(); - expect(label.rightToLeft).toEqual(true); - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(text.split('').reverse().join('')); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(expectedText); }); it('should reverse part of text when there is mix of right-to-left and other kind of characters and rightToLeft is true', function() { - var text = 'Master (אדון): "Hello"\nתלמיד (student): "שלום"'; - var expectedText = 'Master (ןודא): "Hello"\n"םולש" :(student) דימלת'; - var label = labels.add({ - text : text, - rightToLeft : true - }); + Label.enableRightToLeftDetection = true; + var text = 'Master (אדון): "Hello"\nתלמיד (student): "שלום"'; + var expectedText = 'Master (ןודא): "Hello"\n"םולש" :(student) דימלת'; + var label = labels.add({ + text : text + }); - scene.renderForSpecs(); + scene.renderForSpecs(); - expect(label.rightToLeft).toEqual(true); - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(expectedText); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(expectedText); }); it('should reverse all text and replace brackets when there is right-to-left characters and rightToLeft is true', function() { - var text = 'משפט [מורכב] {עם} תווים <מיוחדים special>'; - var expectedText = ' םיוות {םע} [בכרומ] טפשמ'; - var label = labels.add({ - text : text, - rightToLeft : true - }); + Label.enableRightToLeftDetection = true; + var text = 'משפט [מורכב] {עם} תווים <מיוחדים special>'; + var expectedText = ' םיוות {םע} [בכרומ] טפשמ'; + var label = labels.add({ + text : text + }); - scene.renderForSpecs(); + scene.renderForSpecs(); - expect(label.rightToLeft).toEqual(true); - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(expectedText); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(expectedText); }); it('should reverse only text that detected as rtl text when it begin with non rtl characters when rightToLeft is true', function() { - var text = '(interesting sentence with hebrew characters) שלום(עליך)חביבי.'; - var expectedText = '(interesting sentence with hebrew characters) יביבח(ךילע)םולש.'; - var label = labels.add({ - text : text, - rightToLeft : true - }); + Label.enableRightToLeftDetection = true; + var text = '(interesting sentence with hebrew characters) שלום(עליך)חביבי.'; + var expectedText = '(interesting sentence with hebrew characters) יביבח(ךילע)םולש.'; + var label = labels.add({ + text : text + }); - scene.renderForSpecs(); + scene.renderForSpecs(); - expect(label.rightToLeft).toEqual(true); - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(expectedText); + expect(label.text).not.toEqual(text); + expect(label.text).toEqual(expectedText); }); it('should not change nothing if it only non alphanumeric characters when rightToLeft is true', function() { - var text = '([{- -}])'; - var expectedText = '([{- -}])'; - var label = labels.add({ - text : text, - rightToLeft : true - }); + Label.enableRightToLeftDetection = true; + var text = '([{- -}])'; + var expectedText = '([{- -}])'; + var label = labels.add({ + text : text + }); - scene.renderForSpecs(); + scene.renderForSpecs(); - expect(label.rightToLeft).toEqual(true); - expect(label.text).toEqual(expectedText); + expect(label.text).toEqual(expectedText); }); }, 'WebGL'); From e109e982b5bc6222cab4ebc45ea5b04a7bac3640 Mon Sep 17 00:00:00 2001 From: hodbauer Date: Tue, 24 Oct 2017 23:06:36 +0300 Subject: [PATCH 45/47] fix tests by set text property also in the Label constructor, which call text setter --- Source/Scene/Label.js | 5 ++++- Specs/Scene/LabelCollectionSpec.js | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 1cf1a1ab1d94..a336611ae2b8 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -156,6 +156,8 @@ define([ this._clusterShow = true; + this.text = defaultValue(options.text, ''); + this._updateClamping(); } @@ -1215,12 +1217,13 @@ define([ }; /** - * Determines whether or not run the reverseRtl algorithm on the text of the label + * Determines whether or not run the algorithm, that match the text of the label to right-to-left languages * @memberof Label * @type {Boolean} * @default false * * @example + * // Example 1. * // Set a label's rightToLeft before init * Cesium.Label.enableRightToLeftDetection = true; * var myLabelEntity = viewer.entities.add({ diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 9ba0a0fe0ec7..a6e4ead27af4 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1864,6 +1864,7 @@ defineSuite([ it('should reverse text when there is only hebrew characters and rightToLeft is true', function() { Label.enableRightToLeftDetection = true; var text = 'שלום'; + var expectedText = 'םולש'; var label = labels.add({ text : text }); @@ -1871,12 +1872,12 @@ defineSuite([ scene.renderForSpecs(); expect(label.text).not.toEqual(text); - expect(label.text).toEqual(text.split('').reverse().join('')); + expect(label.text).toEqual(expectedText); }); it('should reverse text even if rightToLeft is set to true after creation of label', function() { var text = 'שלום'; - var expectedText = 'םלוש'; + var expectedText = 'םולש'; var label = labels.add(); Label.enableRightToLeftDetection = true; label.text = text; @@ -1890,7 +1891,7 @@ defineSuite([ it('should not change text if it updated to the same value, although rightToLeft is true', function() { Label.enableRightToLeftDetection = true; var text = 'שלום'; - var expectedText = 'םלוש'; + var expectedText = 'םולש'; var label = labels.add({ text : text }); From c2aa9cce6902b8ae017489f6f91a5a816f7ea63f Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Wed, 25 Oct 2017 09:59:57 -0400 Subject: [PATCH 46/47] Tweaks to right-to-left label handling. --- Source/Scene/Label.js | 15 ++---- Source/Scene/LabelCollection.js | 4 +- Specs/Scene/LabelCollectionSpec.js | 75 ++++++++---------------------- 3 files changed, 26 insertions(+), 68 deletions(-) diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index a336611ae2b8..e6e7e2bd1abb 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -119,7 +119,8 @@ define([ distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition); } - this._text = defaultValue(options.text, ''); + this._renderedText = undefined; + this._text = undefined; this._show = defaultValue(options.show, true); this._font = defaultValue(options.font, '30px sans-serif'); this._fillColor = Color.clone(defaultValue(options.fillColor, Color.WHITE)); @@ -290,17 +291,9 @@ define([ } //>>includeEnd('debug'); - if (Label.enableRightToLeftDetection) { - if (this._originalValue === value) { - value = this._text; - return; - } - this._originalValue = value; - value = reverseRtl(value); - } - if (this._text !== value) { this._text = value; + this._renderedText = Label.enableRightToLeftDetection ? reverseRtl(value) : value; rebindAllGlyphs(this); } } @@ -1187,7 +1180,7 @@ define([ this._verticalOrigin === other._verticalOrigin && this._horizontalOrigin === other._horizontalOrigin && this._heightReference === other._heightReference && - this._text === other._text && + this._renderedText === other._renderedText && this._font === other._font && Cartesian3.equals(this._position, other._position) && Color.equals(this._fillColor, other._fillColor) && diff --git a/Source/Scene/LabelCollection.js b/Source/Scene/LabelCollection.js index efb9f4fd11b0..abc1692180c1 100644 --- a/Source/Scene/LabelCollection.js +++ b/Source/Scene/LabelCollection.js @@ -122,7 +122,7 @@ define([ } function rebindAllGlyphs(labelCollection, label) { - var text = label._text; + var text = label._renderedText; var textLength = text.length; var glyphs = label._glyphs; var glyphsLength = glyphs.length; @@ -290,7 +290,7 @@ define([ function repositionAllGlyphs(label, resolutionScale) { var glyphs = label._glyphs; - var text = label._text; + var text = label._renderedText; var glyph; var dimensions; var lastLineWidth = 0; diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index a6e4ead27af4..9470681dc01e 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -1850,115 +1850,80 @@ defineSuite([ expect(label.text).toEqual(text); }); - it('should not modify text when rightToLeft is true and there is no hebrew characters', function() { + }, 'WebGL'); + + describe('right to left detection', function() { + beforeAll(function() { Label.enableRightToLeftDetection = true; - var text = 'bla bla bla'; - var label = labels.add({ - text : text - }); - scene.renderForSpecs(); + }); - expect(label.text).toEqual(text); + afterAll(function() { + Label.enableRightToLeftDetection = false; }); - it('should reverse text when there is only hebrew characters and rightToLeft is true', function() { - Label.enableRightToLeftDetection = true; - var text = 'שלום'; - var expectedText = 'םולש'; + it('should not modify text when rightToLeft is true and there is no hebrew characters', function() { + var text = 'bla bla bla'; var label = labels.add({ text : text }); - scene.renderForSpecs(); - - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(expectedText); - }); - - it('should reverse text even if rightToLeft is set to true after creation of label', function() { - var text = 'שלום'; - var expectedText = 'םולש'; - var label = labels.add(); - Label.enableRightToLeftDetection = true; - label.text = text; - - scene.renderForSpecs(); - - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(expectedText); + expect(label.text).toEqual(text); }); - it('should not change text if it updated to the same value, although rightToLeft is true', function() { - Label.enableRightToLeftDetection = true; + it('should reverse text when there is only hebrew characters and rightToLeft is true', function() { var text = 'שלום'; var expectedText = 'םולש'; var label = labels.add({ text : text }); - label.text = text; - scene.renderForSpecs(); - - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(expectedText); + expect(label.text).toEqual(text); + expect(label._renderedText).toEqual(expectedText); }); it('should reverse part of text when there is mix of right-to-left and other kind of characters and rightToLeft is true', function() { - Label.enableRightToLeftDetection = true; var text = 'Master (אדון): "Hello"\nתלמיד (student): "שלום"'; var expectedText = 'Master (ןודא): "Hello"\n"םולש" :(student) דימלת'; var label = labels.add({ text : text }); - scene.renderForSpecs(); - - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(expectedText); + expect(label.text).toEqual(text); + expect(label._renderedText).toEqual(expectedText); }); it('should reverse all text and replace brackets when there is right-to-left characters and rightToLeft is true', function() { - Label.enableRightToLeftDetection = true; var text = 'משפט [מורכב] {עם} תווים <מיוחדים special>'; var expectedText = ' םיוות {םע} [בכרומ] טפשמ'; var label = labels.add({ text : text }); - scene.renderForSpecs(); - - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(expectedText); + expect(label.text).toEqual(text); + expect(label._renderedText).toEqual(expectedText); }); it('should reverse only text that detected as rtl text when it begin with non rtl characters when rightToLeft is true', function() { - Label.enableRightToLeftDetection = true; var text = '(interesting sentence with hebrew characters) שלום(עליך)חביבי.'; var expectedText = '(interesting sentence with hebrew characters) יביבח(ךילע)םולש.'; var label = labels.add({ text : text }); - scene.renderForSpecs(); - - expect(label.text).not.toEqual(text); - expect(label.text).toEqual(expectedText); + expect(label.text).toEqual(text); + expect(label._renderedText).toEqual(expectedText); }); it('should not change nothing if it only non alphanumeric characters when rightToLeft is true', function() { - Label.enableRightToLeftDetection = true; var text = '([{- -}])'; var expectedText = '([{- -}])'; var label = labels.add({ text : text }); - scene.renderForSpecs(); - expect(label.text).toEqual(expectedText); }); - - }, 'WebGL'); + }); it('computes bounding sphere in 3D', function() { var one = labels.add({ From 6bca30d46e5e394cb5392a50a62f3f5ce7c3ea93 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Wed, 25 Oct 2017 10:02:57 -0400 Subject: [PATCH 47/47] Tweak Sandcastle --- Apps/Sandcastle/gallery/Labels.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index 73450ff8e150..f8e623ce7e5c 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -123,7 +123,7 @@ function setRightToLeft() { Sandcastle.declare(setRightToLeft); - Cesium.Label.enableRightToLeftDetection = true; + Cesium.Label.enableRightToLeftDetection = true; //Only needs to be set once at the beginning of the application. viewer.entities.add({ position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), label : {