Skip to content

Commit 9933716

Browse files
authored
Merge pull request #5771 from hodbauer/rtl-labels-support
Rtl labels support
2 parents 3f018d7 + 6bca30d commit 9933716

File tree

6 files changed

+318
-4
lines changed

6 files changed

+318
-4
lines changed

Apps/Sandcastle/gallery/Labels.html

+17
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,17 @@
121121
});
122122
}
123123

124+
function setRightToLeft() {
125+
Sandcastle.declare(setRightToLeft);
126+
Cesium.Label.enableRightToLeftDetection = true; //Only needs to be set once at the beginning of the application.
127+
viewer.entities.add({
128+
position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222),
129+
label : {
130+
text : 'Master (אדון): Hello\nתלמיד (student): שלום'
131+
}
132+
});
133+
}
134+
124135
Sandcastle.addToolbarMenu([{
125136
text : 'Add label',
126137
onselect : function() {
@@ -157,6 +168,12 @@
157168
scaleByDistance();
158169
Sandcastle.highlight(scaleByDistance);
159170
}
171+
}, {
172+
text : 'Set label with right-to-left language',
173+
onselect : function() {
174+
setRightToLeft();
175+
Sandcastle.highlight(setRightToLeft);
176+
}
160177
}]);
161178

162179
Sandcastle.reset = function() {

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Change Log
33

44
### 1.39 - 2017-11-01
55

6+
* Added support for right-to-left languages in labels. [#5771](https://github.com/AnalyticalGraphicsInc/cesium/pull/5771)
67
* Added the ability to load Cesium's assets from the local file system if security permissions allow it. [#5830](https://github.com/AnalyticalGraphicsInc/cesium/issues/5830)
78
* Added function that inserts missing namespace declarations into KML files. [#5860](https://github.com/AnalyticalGraphicsInc/cesium/pull/5860)
89
* Added support for the layer.json `parentUrl` property in `CesiumTerrainProvider` to allow for compositing of tilesets.

CONTRIBUTORS.md

+3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu
9292
* [Jannes Bolling](https://github.com/jbo023)
9393
* [Logilab](https://www.logilab.fr/)
9494
* [Florent Cayré](https://github.com/fcayre/)
95+
* [webiks](https://www.webiks.com)
96+
* [Hod Bauer](https://github.com/hodbauer)
97+
* [Yonatan Kra](https://github.com/yonatankra)
9598
* [Novetta](http://www.novetta.com/)
9699
* [Joshua Bernstein](https://github.com/jbernstein/)
97100
* [Natanael Rivera](https://github.com/nrivera-Novetta/)

Source/Scene/Label.js

+212-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ define([
88
'../Core/defineProperties',
99
'../Core/DeveloperError',
1010
'../Core/DistanceDisplayCondition',
11+
'../Core/freezeObject',
1112
'../Core/NearFarScalar',
1213
'./Billboard',
1314
'./HeightReference',
@@ -24,6 +25,7 @@ define([
2425
defineProperties,
2526
DeveloperError,
2627
DistanceDisplayCondition,
28+
freezeObject,
2729
NearFarScalar,
2830
Billboard,
2931
HeightReference,
@@ -32,6 +34,13 @@ define([
3234
VerticalOrigin) {
3335
'use strict';
3436

37+
var textTypes = freezeObject({
38+
LTR : 0,
39+
RTL : 1,
40+
WEAK : 2,
41+
BRACKETS : 3
42+
});
43+
3544
function rebindAllGlyphs(label) {
3645
if (!label._rebindAllGlyphs && !label._repositionAllGlyphs) {
3746
// only push label if it's not already been marked dirty
@@ -110,7 +119,8 @@ define([
110119
distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition);
111120
}
112121

113-
this._text = defaultValue(options.text, '');
122+
this._renderedText = undefined;
123+
this._text = undefined;
114124
this._show = defaultValue(options.show, true);
115125
this._font = defaultValue(options.font, '30px sans-serif');
116126
this._fillColor = Color.clone(defaultValue(options.fillColor, Color.WHITE));
@@ -147,6 +157,8 @@ define([
147157

148158
this._clusterShow = true;
149159

160+
this.text = defaultValue(options.text, '');
161+
150162
this._updateClamping();
151163
}
152164

@@ -281,6 +293,7 @@ define([
281293

282294
if (this._text !== value) {
283295
this._text = value;
296+
this._renderedText = Label.enableRightToLeftDetection ? reverseRtl(value) : value;
284297
rebindAllGlyphs(this);
285298
}
286299
}
@@ -1167,7 +1180,7 @@ define([
11671180
this._verticalOrigin === other._verticalOrigin &&
11681181
this._horizontalOrigin === other._horizontalOrigin &&
11691182
this._heightReference === other._heightReference &&
1170-
this._text === other._text &&
1183+
this._renderedText === other._renderedText &&
11711184
this._font === other._font &&
11721185
Cartesian3.equals(this._position, other._position) &&
11731186
Color.equals(this._fillColor, other._fillColor) &&
@@ -1196,5 +1209,202 @@ define([
11961209
return false;
11971210
};
11981211

1212+
/**
1213+
* Determines whether or not run the algorithm, that match the text of the label to right-to-left languages
1214+
* @memberof Label
1215+
* @type {Boolean}
1216+
* @default false
1217+
*
1218+
* @example
1219+
* // Example 1.
1220+
* // Set a label's rightToLeft before init
1221+
* Cesium.Label.enableRightToLeftDetection = true;
1222+
* var myLabelEntity = viewer.entities.add({
1223+
* label: {
1224+
* id: 'my label',
1225+
* text: 'זה טקסט בעברית \n ועכשיו יורדים שורה',
1226+
* }
1227+
* });
1228+
*
1229+
* @example
1230+
* // Example 2.
1231+
* var myLabelEntity = viewer.entities.add({
1232+
* label: {
1233+
* id: 'my label',
1234+
* text: 'English text'
1235+
* }
1236+
* });
1237+
* // Set a label's rightToLeft after init
1238+
* Cesium.Label.enableRightToLeftDetection = true;
1239+
* myLabelEntity.text = 'טקסט חדש';
1240+
*/
1241+
Label.enableRightToLeftDetection = false;
1242+
1243+
function convertTextToTypes(text, rtlChars) {
1244+
var ltrChars = /[a-zA-Z0-9]/;
1245+
var bracketsChars = /[()[\]{}<>]/;
1246+
var parsedText = [];
1247+
var word = '';
1248+
var lastType = textTypes.LTR;
1249+
var currentType = '';
1250+
var textLength = text.length;
1251+
for (var textIndex = 0; textIndex < textLength; ++textIndex) {
1252+
var character = text.charAt(textIndex);
1253+
if (rtlChars.test(character)) {
1254+
currentType = textTypes.RTL;
1255+
}
1256+
else if (ltrChars.test(character)) {
1257+
currentType = textTypes.LTR;
1258+
}
1259+
else if (bracketsChars.test(character)) {
1260+
currentType = textTypes.BRACKETS;
1261+
}
1262+
else {
1263+
currentType = textTypes.WEAK;
1264+
}
1265+
1266+
if (textIndex === 0) {
1267+
lastType = currentType;
1268+
}
1269+
1270+
if (lastType === currentType && currentType !== textTypes.BRACKETS) {
1271+
word += character;
1272+
}
1273+
else {
1274+
if (word !== '') {
1275+
parsedText.push({Type : lastType, Word : word});
1276+
}
1277+
lastType = currentType;
1278+
word = character;
1279+
}
1280+
}
1281+
parsedText.push({Type : currentType, Word : word});
1282+
return parsedText;
1283+
}
1284+
1285+
function reverseWord(word) {
1286+
return word.split('').reverse().join('');
1287+
}
1288+
1289+
function spliceWord(result, pointer, word) {
1290+
return result.slice(0, pointer) + word + result.slice(pointer);
1291+
}
1292+
1293+
function reverseBrackets(bracket) {
1294+
switch(bracket) {
1295+
case '(':
1296+
return ')';
1297+
case ')':
1298+
return '(';
1299+
case '[':
1300+
return ']';
1301+
case ']':
1302+
return '[';
1303+
case '{':
1304+
return '}';
1305+
case '}':
1306+
return '{';
1307+
case '<':
1308+
return '>';
1309+
case '>':
1310+
return '<';
1311+
}
1312+
}
1313+
1314+
/**
1315+
*
1316+
* @param {String} value the text to parse and reorder
1317+
* @returns {String} the text as rightToLeft direction
1318+
* @private
1319+
*/
1320+
function reverseRtl(value) {
1321+
var rtlChars = /[\u05D0-\u05EA]/;
1322+
var texts = value.split('\n');
1323+
var result = '';
1324+
for (var i = 0; i < texts.length; i++) {
1325+
var text = texts[i];
1326+
var rtlDir = rtlChars.test(text.charAt(0));
1327+
var parsedText = convertTextToTypes(text, rtlChars);
1328+
1329+
var splicePointer = 0;
1330+
var line = '';
1331+
for (var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) {
1332+
var subText = parsedText[wordIndex];
1333+
var reverse = subText.Type === textTypes.BRACKETS ? reverseBrackets(subText.Word) : subText.Word;
1334+
if (rtlDir) {
1335+
if (subText.Type === textTypes.RTL) {
1336+
line = reverseWord(subText.Word) + line;
1337+
splicePointer = 0;
1338+
}
1339+
else if (subText.Type === textTypes.LTR) {
1340+
line = spliceWord(line, splicePointer, subText.Word);
1341+
splicePointer += subText.Word.length;
1342+
}
1343+
else if (subText.Type === textTypes.WEAK || subText.Type === textTypes.BRACKETS) {
1344+
if (subText.Type === textTypes.WEAK && parsedText[wordIndex - 1].Type === textTypes.BRACKETS) {
1345+
line = reverseWord(subText.Word) + line;
1346+
}
1347+
else if (parsedText[wordIndex - 1].Type === textTypes.RTL) {
1348+
line = reverse + line;
1349+
splicePointer = 0;
1350+
}
1351+
else if (parsedText.length > wordIndex + 1) {
1352+
if (parsedText[wordIndex + 1].Type === textTypes.RTL) {
1353+
line = reverse + line;
1354+
splicePointer = 0;
1355+
}
1356+
else {
1357+
line = spliceWord(line, splicePointer, subText.Word);
1358+
splicePointer += subText.Word.length;
1359+
}
1360+
}
1361+
else {
1362+
line = spliceWord(line, 0, reverse);
1363+
}
1364+
}
1365+
}
1366+
else if (subText.Type === textTypes.RTL) {
1367+
line = spliceWord(line, splicePointer, reverseWord(subText.Word));
1368+
}
1369+
else if (subText.Type === textTypes.LTR) {
1370+
line += subText.Word;
1371+
splicePointer = line.length;
1372+
}
1373+
else if (subText.Type === textTypes.WEAK || subText.Type === textTypes.BRACKETS) {
1374+
if (wordIndex > 0) {
1375+
if (parsedText[wordIndex - 1].Type === textTypes.RTL) {
1376+
if (parsedText.length > wordIndex + 1) {
1377+
if (parsedText[wordIndex + 1].Type === textTypes.RTL) {
1378+
line = spliceWord(line, splicePointer, reverse);
1379+
}
1380+
else {
1381+
line += subText.Word;
1382+
splicePointer = line.length;
1383+
}
1384+
}
1385+
else {
1386+
line += subText.Word;
1387+
}
1388+
}
1389+
else {
1390+
line += subText.Word;
1391+
splicePointer = line.length;
1392+
}
1393+
}
1394+
else {
1395+
line += subText.Word;
1396+
splicePointer = line.length;
1397+
}
1398+
}
1399+
}
1400+
1401+
result += line;
1402+
if (i < texts.length - 1) {
1403+
result += '\n';
1404+
}
1405+
}
1406+
return result;
1407+
}
1408+
11991409
return Label;
12001410
});

Source/Scene/LabelCollection.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ define([
122122
}
123123

124124
function rebindAllGlyphs(labelCollection, label) {
125-
var text = label._text;
125+
var text = label._renderedText;
126126
var textLength = text.length;
127127
var glyphs = label._glyphs;
128128
var glyphsLength = glyphs.length;
@@ -290,7 +290,7 @@ define([
290290

291291
function repositionAllGlyphs(label, resolutionScale) {
292292
var glyphs = label._glyphs;
293-
var text = label._text;
293+
var text = label._renderedText;
294294
var glyph;
295295
var dimensions;
296296
var lastLineWidth = 0;

0 commit comments

Comments
 (0)