Label.js 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576
  1. import BoundingRectangle from "../Core/BoundingRectangle.js";
  2. import Cartesian2 from "../Core/Cartesian2.js";
  3. import Cartesian3 from "../Core/Cartesian3.js";
  4. import Color from "../Core/Color.js";
  5. import defaultValue from "../Core/defaultValue.js";
  6. import defined from "../Core/defined.js";
  7. import DeveloperError from "../Core/DeveloperError.js";
  8. import DistanceDisplayCondition from "../Core/DistanceDisplayCondition.js";
  9. import NearFarScalar from "../Core/NearFarScalar.js";
  10. import Billboard from "./Billboard.js";
  11. import HeightReference from "./HeightReference.js";
  12. import HorizontalOrigin from "./HorizontalOrigin.js";
  13. import LabelStyle from "./LabelStyle.js";
  14. import SDFSettings from "./SDFSettings.js";
  15. import VerticalOrigin from "./VerticalOrigin.js";
  16. var fontInfoCache = {};
  17. var fontInfoCacheLength = 0;
  18. var fontInfoCacheMaxSize = 256;
  19. var defaultBackgroundColor = new Color(0.165, 0.165, 0.165, 0.8);
  20. var defaultBackgroundPadding = new Cartesian2(7, 5);
  21. var textTypes = Object.freeze({
  22. LTR: 0,
  23. RTL: 1,
  24. WEAK: 2,
  25. BRACKETS: 3,
  26. });
  27. function rebindAllGlyphs(label) {
  28. if (!label._rebindAllGlyphs && !label._repositionAllGlyphs) {
  29. // only push label if it's not already been marked dirty
  30. label._labelCollection._labelsToUpdate.push(label);
  31. }
  32. label._rebindAllGlyphs = true;
  33. }
  34. function repositionAllGlyphs(label) {
  35. if (!label._rebindAllGlyphs && !label._repositionAllGlyphs) {
  36. // only push label if it's not already been marked dirty
  37. label._labelCollection._labelsToUpdate.push(label);
  38. }
  39. label._repositionAllGlyphs = true;
  40. }
  41. function getCSSValue(element, property) {
  42. return document.defaultView
  43. .getComputedStyle(element, null)
  44. .getPropertyValue(property);
  45. }
  46. function parseFont(label) {
  47. var fontInfo = fontInfoCache[label._font];
  48. if (!defined(fontInfo)) {
  49. var div = document.createElement("div");
  50. div.style.position = "absolute";
  51. div.style.opacity = 0;
  52. div.style.font = label._font;
  53. document.body.appendChild(div);
  54. fontInfo = {
  55. family: getCSSValue(div, "font-family"),
  56. size: getCSSValue(div, "font-size").replace("px", ""),
  57. style: getCSSValue(div, "font-style"),
  58. weight: getCSSValue(div, "font-weight"),
  59. };
  60. document.body.removeChild(div);
  61. if (fontInfoCacheLength < fontInfoCacheMaxSize) {
  62. fontInfoCache[label._font] = fontInfo;
  63. fontInfoCacheLength++;
  64. }
  65. }
  66. label._fontFamily = fontInfo.family;
  67. label._fontSize = fontInfo.size;
  68. label._fontStyle = fontInfo.style;
  69. label._fontWeight = fontInfo.weight;
  70. }
  71. /**
  72. * A Label draws viewport-aligned text positioned in the 3D scene. This constructor
  73. * should not be used directly, instead create labels by calling {@link LabelCollection#add}.
  74. *
  75. * @alias Label
  76. * @internalConstructor
  77. * @class
  78. *
  79. * @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near
  80. * @exception {DeveloperError} pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near
  81. * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near
  82. *
  83. * @see LabelCollection
  84. * @see LabelCollection#add
  85. *
  86. * @demo {@link https://sandcastle.cesium.com/index.html?src=Labels.html|Cesium Sandcastle Labels Demo}
  87. */
  88. function Label(options, labelCollection) {
  89. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  90. //>>includeStart('debug', pragmas.debug);
  91. if (
  92. defined(options.disableDepthTestDistance) &&
  93. options.disableDepthTestDistance < 0.0
  94. ) {
  95. throw new DeveloperError(
  96. "disableDepthTestDistance must be greater than 0.0."
  97. );
  98. }
  99. //>>includeEnd('debug');
  100. var translucencyByDistance = options.translucencyByDistance;
  101. var pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance;
  102. var scaleByDistance = options.scaleByDistance;
  103. var distanceDisplayCondition = options.distanceDisplayCondition;
  104. if (defined(translucencyByDistance)) {
  105. //>>includeStart('debug', pragmas.debug);
  106. if (translucencyByDistance.far <= translucencyByDistance.near) {
  107. throw new DeveloperError(
  108. "translucencyByDistance.far must be greater than translucencyByDistance.near."
  109. );
  110. }
  111. //>>includeEnd('debug');
  112. translucencyByDistance = NearFarScalar.clone(translucencyByDistance);
  113. }
  114. if (defined(pixelOffsetScaleByDistance)) {
  115. //>>includeStart('debug', pragmas.debug);
  116. if (pixelOffsetScaleByDistance.far <= pixelOffsetScaleByDistance.near) {
  117. throw new DeveloperError(
  118. "pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near."
  119. );
  120. }
  121. //>>includeEnd('debug');
  122. pixelOffsetScaleByDistance = NearFarScalar.clone(
  123. pixelOffsetScaleByDistance
  124. );
  125. }
  126. if (defined(scaleByDistance)) {
  127. //>>includeStart('debug', pragmas.debug);
  128. if (scaleByDistance.far <= scaleByDistance.near) {
  129. throw new DeveloperError(
  130. "scaleByDistance.far must be greater than scaleByDistance.near."
  131. );
  132. }
  133. //>>includeEnd('debug');
  134. scaleByDistance = NearFarScalar.clone(scaleByDistance);
  135. }
  136. if (defined(distanceDisplayCondition)) {
  137. //>>includeStart('debug', pragmas.debug);
  138. if (distanceDisplayCondition.far <= distanceDisplayCondition.near) {
  139. throw new DeveloperError(
  140. "distanceDisplayCondition.far must be greater than distanceDisplayCondition.near."
  141. );
  142. }
  143. //>>includeEnd('debug');
  144. distanceDisplayCondition = DistanceDisplayCondition.clone(
  145. distanceDisplayCondition
  146. );
  147. }
  148. this._renderedText = undefined;
  149. this._text = undefined;
  150. this._show = defaultValue(options.show, true);
  151. this._font = defaultValue(options.font, "30px sans-serif");
  152. this._fillColor = Color.clone(defaultValue(options.fillColor, Color.WHITE));
  153. this._outlineColor = Color.clone(
  154. defaultValue(options.outlineColor, Color.BLACK)
  155. );
  156. this._outlineWidth = defaultValue(options.outlineWidth, 1.0);
  157. this._showBackground = defaultValue(options.showBackground, false);
  158. this._backgroundColor = Color.clone(
  159. defaultValue(options.backgroundColor, defaultBackgroundColor)
  160. );
  161. this._backgroundPadding = Cartesian2.clone(
  162. defaultValue(options.backgroundPadding, defaultBackgroundPadding)
  163. );
  164. this._style = defaultValue(options.style, LabelStyle.FILL);
  165. this._verticalOrigin = defaultValue(
  166. options.verticalOrigin,
  167. VerticalOrigin.BASELINE
  168. );
  169. this._horizontalOrigin = defaultValue(
  170. options.horizontalOrigin,
  171. HorizontalOrigin.LEFT
  172. );
  173. this._pixelOffset = Cartesian2.clone(
  174. defaultValue(options.pixelOffset, Cartesian2.ZERO)
  175. );
  176. this._eyeOffset = Cartesian3.clone(
  177. defaultValue(options.eyeOffset, Cartesian3.ZERO)
  178. );
  179. this._position = Cartesian3.clone(
  180. defaultValue(options.position, Cartesian3.ZERO)
  181. );
  182. this._scale = defaultValue(options.scale, 1.0);
  183. this._id = options.id;
  184. this._translucencyByDistance = translucencyByDistance;
  185. this._pixelOffsetScaleByDistance = pixelOffsetScaleByDistance;
  186. this._scaleByDistance = scaleByDistance;
  187. this._heightReference = defaultValue(
  188. options.heightReference,
  189. HeightReference.NONE
  190. );
  191. this._distanceDisplayCondition = distanceDisplayCondition;
  192. this._disableDepthTestDistance = options.disableDepthTestDistance;
  193. this._labelCollection = labelCollection;
  194. this._glyphs = [];
  195. this._backgroundBillboard = undefined;
  196. this._batchIndex = undefined; // Used only by Vector3DTilePoints and BillboardCollection
  197. this._rebindAllGlyphs = true;
  198. this._repositionAllGlyphs = true;
  199. this._actualClampedPosition = undefined;
  200. this._removeCallbackFunc = undefined;
  201. this._mode = undefined;
  202. this._clusterShow = true;
  203. this.text = defaultValue(options.text, "");
  204. this._relativeSize = 1.0;
  205. parseFont(this);
  206. this._updateClamping();
  207. }
  208. Object.defineProperties(Label.prototype, {
  209. /**
  210. * Determines if this label will be shown. Use this to hide or show a label, instead
  211. * of removing it and re-adding it to the collection.
  212. * @memberof Label.prototype
  213. * @type {Boolean}
  214. * @default true
  215. */
  216. show: {
  217. get: function () {
  218. return this._show;
  219. },
  220. set: function (value) {
  221. //>>includeStart('debug', pragmas.debug);
  222. if (!defined(value)) {
  223. throw new DeveloperError("value is required.");
  224. }
  225. //>>includeEnd('debug');
  226. if (this._show !== value) {
  227. this._show = value;
  228. var glyphs = this._glyphs;
  229. for (var i = 0, len = glyphs.length; i < len; i++) {
  230. var billboard = glyphs[i].billboard;
  231. if (defined(billboard)) {
  232. billboard.show = value;
  233. }
  234. }
  235. var backgroundBillboard = this._backgroundBillboard;
  236. if (defined(backgroundBillboard)) {
  237. backgroundBillboard.show = value;
  238. }
  239. }
  240. },
  241. },
  242. /**
  243. * Gets or sets the Cartesian position of this label.
  244. * @memberof Label.prototype
  245. * @type {Cartesian3}
  246. */
  247. position: {
  248. get: function () {
  249. return this._position;
  250. },
  251. set: function (value) {
  252. //>>includeStart('debug', pragmas.debug);
  253. if (!defined(value)) {
  254. throw new DeveloperError("value is required.");
  255. }
  256. //>>includeEnd('debug');
  257. var position = this._position;
  258. if (!Cartesian3.equals(position, value)) {
  259. Cartesian3.clone(value, position);
  260. var glyphs = this._glyphs;
  261. for (var i = 0, len = glyphs.length; i < len; i++) {
  262. var billboard = glyphs[i].billboard;
  263. if (defined(billboard)) {
  264. billboard.position = value;
  265. }
  266. }
  267. var backgroundBillboard = this._backgroundBillboard;
  268. if (defined(backgroundBillboard)) {
  269. backgroundBillboard.position = value;
  270. }
  271. this._updateClamping();
  272. }
  273. },
  274. },
  275. /**
  276. * Gets or sets the height reference of this billboard.
  277. * @memberof Label.prototype
  278. * @type {HeightReference}
  279. * @default HeightReference.NONE
  280. */
  281. heightReference: {
  282. get: function () {
  283. return this._heightReference;
  284. },
  285. set: function (value) {
  286. //>>includeStart('debug', pragmas.debug);
  287. if (!defined(value)) {
  288. throw new DeveloperError("value is required.");
  289. }
  290. //>>includeEnd('debug');
  291. if (value !== this._heightReference) {
  292. this._heightReference = value;
  293. var glyphs = this._glyphs;
  294. for (var i = 0, len = glyphs.length; i < len; i++) {
  295. var billboard = glyphs[i].billboard;
  296. if (defined(billboard)) {
  297. billboard.heightReference = value;
  298. }
  299. }
  300. var backgroundBillboard = this._backgroundBillboard;
  301. if (defined(backgroundBillboard)) {
  302. backgroundBillboard.heightReference = value;
  303. }
  304. repositionAllGlyphs(this);
  305. this._updateClamping();
  306. }
  307. },
  308. },
  309. /**
  310. * Gets or sets the text of this label.
  311. * @memberof Label.prototype
  312. * @type {String}
  313. */
  314. text: {
  315. get: function () {
  316. return this._text;
  317. },
  318. set: function (value) {
  319. //>>includeStart('debug', pragmas.debug);
  320. if (!defined(value)) {
  321. throw new DeveloperError("value is required.");
  322. }
  323. //>>includeEnd('debug');
  324. if (this._text !== value) {
  325. this._text = value;
  326. this._renderedText = Label.enableRightToLeftDetection
  327. ? reverseRtl(value)
  328. : value;
  329. rebindAllGlyphs(this);
  330. }
  331. },
  332. },
  333. /**
  334. * Gets or sets the font used to draw this label. Fonts are specified using the same syntax as the CSS 'font' property.
  335. * @memberof Label.prototype
  336. * @type {String}
  337. * @default '30px sans-serif'
  338. * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles|HTML canvas 2D context text styles}
  339. */
  340. font: {
  341. get: function () {
  342. return this._font;
  343. },
  344. set: function (value) {
  345. //>>includeStart('debug', pragmas.debug);
  346. if (!defined(value)) {
  347. throw new DeveloperError("value is required.");
  348. }
  349. //>>includeEnd('debug');
  350. if (this._font !== value) {
  351. this._font = value;
  352. rebindAllGlyphs(this);
  353. parseFont(this);
  354. }
  355. },
  356. },
  357. /**
  358. * Gets or sets the fill color of this label.
  359. * @memberof Label.prototype
  360. * @type {Color}
  361. * @default Color.WHITE
  362. * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles}
  363. */
  364. fillColor: {
  365. get: function () {
  366. return this._fillColor;
  367. },
  368. set: function (value) {
  369. //>>includeStart('debug', pragmas.debug);
  370. if (!defined(value)) {
  371. throw new DeveloperError("value is required.");
  372. }
  373. //>>includeEnd('debug');
  374. var fillColor = this._fillColor;
  375. if (!Color.equals(fillColor, value)) {
  376. Color.clone(value, fillColor);
  377. rebindAllGlyphs(this);
  378. }
  379. },
  380. },
  381. /**
  382. * Gets or sets the outline color of this label.
  383. * @memberof Label.prototype
  384. * @type {Color}
  385. * @default Color.BLACK
  386. * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles}
  387. */
  388. outlineColor: {
  389. get: function () {
  390. return this._outlineColor;
  391. },
  392. set: function (value) {
  393. //>>includeStart('debug', pragmas.debug);
  394. if (!defined(value)) {
  395. throw new DeveloperError("value is required.");
  396. }
  397. //>>includeEnd('debug');
  398. var outlineColor = this._outlineColor;
  399. if (!Color.equals(outlineColor, value)) {
  400. Color.clone(value, outlineColor);
  401. rebindAllGlyphs(this);
  402. }
  403. },
  404. },
  405. /**
  406. * Gets or sets the outline width of this label.
  407. * @memberof Label.prototype
  408. * @type {Number}
  409. * @default 1.0
  410. * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles}
  411. */
  412. outlineWidth: {
  413. get: function () {
  414. return this._outlineWidth;
  415. },
  416. set: function (value) {
  417. //>>includeStart('debug', pragmas.debug);
  418. if (!defined(value)) {
  419. throw new DeveloperError("value is required.");
  420. }
  421. //>>includeEnd('debug');
  422. if (this._outlineWidth !== value) {
  423. this._outlineWidth = value;
  424. rebindAllGlyphs(this);
  425. }
  426. },
  427. },
  428. /**
  429. * Determines if a background behind this label will be shown.
  430. * @memberof Label.prototype
  431. * @default false
  432. * @type {Boolean}
  433. */
  434. showBackground: {
  435. get: function () {
  436. return this._showBackground;
  437. },
  438. set: function (value) {
  439. //>>includeStart('debug', pragmas.debug);
  440. if (!defined(value)) {
  441. throw new DeveloperError("value is required.");
  442. }
  443. //>>includeEnd('debug');
  444. if (this._showBackground !== value) {
  445. this._showBackground = value;
  446. rebindAllGlyphs(this);
  447. }
  448. },
  449. },
  450. /**
  451. * Gets or sets the background color of this label.
  452. * @memberof Label.prototype
  453. * @type {Color}
  454. * @default new Color(0.165, 0.165, 0.165, 0.8)
  455. */
  456. backgroundColor: {
  457. get: function () {
  458. return this._backgroundColor;
  459. },
  460. set: function (value) {
  461. //>>includeStart('debug', pragmas.debug);
  462. if (!defined(value)) {
  463. throw new DeveloperError("value is required.");
  464. }
  465. //>>includeEnd('debug');
  466. var backgroundColor = this._backgroundColor;
  467. if (!Color.equals(backgroundColor, value)) {
  468. Color.clone(value, backgroundColor);
  469. var backgroundBillboard = this._backgroundBillboard;
  470. if (defined(backgroundBillboard)) {
  471. backgroundBillboard.color = backgroundColor;
  472. }
  473. }
  474. },
  475. },
  476. /**
  477. * Gets or sets the background padding, in pixels, of this label. The <code>x</code> value
  478. * controls horizontal padding, and the <code>y</code> value controls vertical padding.
  479. * @memberof Label.prototype
  480. * @type {Cartesian2}
  481. * @default new Cartesian2(7, 5)
  482. */
  483. backgroundPadding: {
  484. get: function () {
  485. return this._backgroundPadding;
  486. },
  487. set: function (value) {
  488. //>>includeStart('debug', pragmas.debug);
  489. if (!defined(value)) {
  490. throw new DeveloperError("value is required.");
  491. }
  492. //>>includeEnd('debug');
  493. var backgroundPadding = this._backgroundPadding;
  494. if (!Cartesian2.equals(backgroundPadding, value)) {
  495. Cartesian2.clone(value, backgroundPadding);
  496. repositionAllGlyphs(this);
  497. }
  498. },
  499. },
  500. /**
  501. * Gets or sets the style of this label.
  502. * @memberof Label.prototype
  503. * @type {LabelStyle}
  504. * @default LabelStyle.FILL
  505. */
  506. style: {
  507. get: function () {
  508. return this._style;
  509. },
  510. set: function (value) {
  511. //>>includeStart('debug', pragmas.debug);
  512. if (!defined(value)) {
  513. throw new DeveloperError("value is required.");
  514. }
  515. //>>includeEnd('debug');
  516. if (this._style !== value) {
  517. this._style = value;
  518. rebindAllGlyphs(this);
  519. }
  520. },
  521. },
  522. /**
  523. * Gets or sets the pixel offset in screen space from the origin of this label. This is commonly used
  524. * to align multiple labels and billboards at the same position, e.g., an image and text. The
  525. * screen space origin is the top, left corner of the canvas; <code>x</code> increases from
  526. * left to right, and <code>y</code> increases from top to bottom.
  527. * <br /><br />
  528. * <div align='center'>
  529. * <table border='0' cellpadding='5'><tr>
  530. * <td align='center'><code>default</code><br/><img src='Images/Label.setPixelOffset.default.png' width='250' height='188' /></td>
  531. * <td align='center'><code>l.pixeloffset = new Cartesian2(25, 75);</code><br/><img src='Images/Label.setPixelOffset.x50y-25.png' width='250' height='188' /></td>
  532. * </tr></table>
  533. * The label's origin is indicated by the yellow point.
  534. * </div>
  535. * @memberof Label.prototype
  536. * @type {Cartesian2}
  537. * @default Cartesian2.ZERO
  538. */
  539. pixelOffset: {
  540. get: function () {
  541. return this._pixelOffset;
  542. },
  543. set: function (value) {
  544. //>>includeStart('debug', pragmas.debug);
  545. if (!defined(value)) {
  546. throw new DeveloperError("value is required.");
  547. }
  548. //>>includeEnd('debug');
  549. var pixelOffset = this._pixelOffset;
  550. if (!Cartesian2.equals(pixelOffset, value)) {
  551. Cartesian2.clone(value, pixelOffset);
  552. var glyphs = this._glyphs;
  553. for (var i = 0, len = glyphs.length; i < len; i++) {
  554. var glyph = glyphs[i];
  555. if (defined(glyph.billboard)) {
  556. glyph.billboard.pixelOffset = value;
  557. }
  558. }
  559. var backgroundBillboard = this._backgroundBillboard;
  560. if (defined(backgroundBillboard)) {
  561. backgroundBillboard.pixelOffset = value;
  562. }
  563. }
  564. },
  565. },
  566. /**
  567. * Gets or sets near and far translucency properties of a Label based on the Label's distance from the camera.
  568. * A label's translucency will interpolate between the {@link NearFarScalar#nearValue} and
  569. * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds
  570. * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
  571. * Outside of these ranges the label's translucency remains clamped to the nearest bound. If undefined,
  572. * translucencyByDistance will be disabled.
  573. * @memberof Label.prototype
  574. * @type {NearFarScalar}
  575. *
  576. * @example
  577. * // Example 1.
  578. * // Set a label's translucencyByDistance to 1.0 when the
  579. * // camera is 1500 meters from the label and disappear as
  580. * // the camera distance approaches 8.0e6 meters.
  581. * text.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0);
  582. *
  583. * @example
  584. * // Example 2.
  585. * // disable translucency by distance
  586. * text.translucencyByDistance = undefined;
  587. */
  588. translucencyByDistance: {
  589. get: function () {
  590. return this._translucencyByDistance;
  591. },
  592. set: function (value) {
  593. //>>includeStart('debug', pragmas.debug);
  594. if (defined(value) && value.far <= value.near) {
  595. throw new DeveloperError(
  596. "far distance must be greater than near distance."
  597. );
  598. }
  599. //>>includeEnd('debug');
  600. var translucencyByDistance = this._translucencyByDistance;
  601. if (!NearFarScalar.equals(translucencyByDistance, value)) {
  602. this._translucencyByDistance = NearFarScalar.clone(
  603. value,
  604. translucencyByDistance
  605. );
  606. var glyphs = this._glyphs;
  607. for (var i = 0, len = glyphs.length; i < len; i++) {
  608. var glyph = glyphs[i];
  609. if (defined(glyph.billboard)) {
  610. glyph.billboard.translucencyByDistance = value;
  611. }
  612. }
  613. var backgroundBillboard = this._backgroundBillboard;
  614. if (defined(backgroundBillboard)) {
  615. backgroundBillboard.translucencyByDistance = value;
  616. }
  617. }
  618. },
  619. },
  620. /**
  621. * Gets or sets near and far pixel offset scaling properties of a Label based on the Label's distance from the camera.
  622. * A label's pixel offset will be scaled between the {@link NearFarScalar#nearValue} and
  623. * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds
  624. * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
  625. * Outside of these ranges the label's pixel offset scaling remains clamped to the nearest bound. If undefined,
  626. * pixelOffsetScaleByDistance will be disabled.
  627. * @memberof Label.prototype
  628. * @type {NearFarScalar}
  629. *
  630. * @example
  631. * // Example 1.
  632. * // Set a label's pixel offset scale to 0.0 when the
  633. * // camera is 1500 meters from the label and scale pixel offset to 10.0 pixels
  634. * // in the y direction the camera distance approaches 8.0e6 meters.
  635. * text.pixelOffset = new Cesium.Cartesian2(0.0, 1.0);
  636. * text.pixelOffsetScaleByDistance = new Cesium.NearFarScalar(1.5e2, 0.0, 8.0e6, 10.0);
  637. *
  638. * @example
  639. * // Example 2.
  640. * // disable pixel offset by distance
  641. * text.pixelOffsetScaleByDistance = undefined;
  642. */
  643. pixelOffsetScaleByDistance: {
  644. get: function () {
  645. return this._pixelOffsetScaleByDistance;
  646. },
  647. set: function (value) {
  648. //>>includeStart('debug', pragmas.debug);
  649. if (defined(value) && value.far <= value.near) {
  650. throw new DeveloperError(
  651. "far distance must be greater than near distance."
  652. );
  653. }
  654. //>>includeEnd('debug');
  655. var pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance;
  656. if (!NearFarScalar.equals(pixelOffsetScaleByDistance, value)) {
  657. this._pixelOffsetScaleByDistance = NearFarScalar.clone(
  658. value,
  659. pixelOffsetScaleByDistance
  660. );
  661. var glyphs = this._glyphs;
  662. for (var i = 0, len = glyphs.length; i < len; i++) {
  663. var glyph = glyphs[i];
  664. if (defined(glyph.billboard)) {
  665. glyph.billboard.pixelOffsetScaleByDistance = value;
  666. }
  667. }
  668. var backgroundBillboard = this._backgroundBillboard;
  669. if (defined(backgroundBillboard)) {
  670. backgroundBillboard.pixelOffsetScaleByDistance = value;
  671. }
  672. }
  673. },
  674. },
  675. /**
  676. * Gets or sets near and far scaling properties of a Label based on the label's distance from the camera.
  677. * A label's scale will interpolate between the {@link NearFarScalar#nearValue} and
  678. * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds
  679. * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
  680. * Outside of these ranges the label's scale remains clamped to the nearest bound. If undefined,
  681. * scaleByDistance will be disabled.
  682. * @memberof Label.prototype
  683. * @type {NearFarScalar}
  684. *
  685. * @example
  686. * // Example 1.
  687. * // Set a label's scaleByDistance to scale by 1.5 when the
  688. * // camera is 1500 meters from the label and disappear as
  689. * // the camera distance approaches 8.0e6 meters.
  690. * label.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 1.5, 8.0e6, 0.0);
  691. *
  692. * @example
  693. * // Example 2.
  694. * // disable scaling by distance
  695. * label.scaleByDistance = undefined;
  696. */
  697. scaleByDistance: {
  698. get: function () {
  699. return this._scaleByDistance;
  700. },
  701. set: function (value) {
  702. //>>includeStart('debug', pragmas.debug);
  703. if (defined(value) && value.far <= value.near) {
  704. throw new DeveloperError(
  705. "far distance must be greater than near distance."
  706. );
  707. }
  708. //>>includeEnd('debug');
  709. var scaleByDistance = this._scaleByDistance;
  710. if (!NearFarScalar.equals(scaleByDistance, value)) {
  711. this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance);
  712. var glyphs = this._glyphs;
  713. for (var i = 0, len = glyphs.length; i < len; i++) {
  714. var glyph = glyphs[i];
  715. if (defined(glyph.billboard)) {
  716. glyph.billboard.scaleByDistance = value;
  717. }
  718. }
  719. var backgroundBillboard = this._backgroundBillboard;
  720. if (defined(backgroundBillboard)) {
  721. backgroundBillboard.scaleByDistance = value;
  722. }
  723. }
  724. },
  725. },
  726. /**
  727. * Gets and sets the 3D Cartesian offset applied to this label in eye coordinates. Eye coordinates is a left-handed
  728. * coordinate system, where <code>x</code> points towards the viewer's right, <code>y</code> points up, and
  729. * <code>z</code> points into the screen. Eye coordinates use the same scale as world and model coordinates,
  730. * which is typically meters.
  731. * <br /><br />
  732. * An eye offset is commonly used to arrange multiple label or objects at the same position, e.g., to
  733. * arrange a label above its corresponding 3D model.
  734. * <br /><br />
  735. * Below, the label is positioned at the center of the Earth but an eye offset makes it always
  736. * appear on top of the Earth regardless of the viewer's or Earth's orientation.
  737. * <br /><br />
  738. * <div align='center'>
  739. * <table border='0' cellpadding='5'><tr>
  740. * <td align='center'><img src='Images/Billboard.setEyeOffset.one.png' width='250' height='188' /></td>
  741. * <td align='center'><img src='Images/Billboard.setEyeOffset.two.png' width='250' height='188' /></td>
  742. * </tr></table>
  743. * <code>l.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);</code><br /><br />
  744. * </div>
  745. * @memberof Label.prototype
  746. * @type {Cartesian3}
  747. * @default Cartesian3.ZERO
  748. */
  749. eyeOffset: {
  750. get: function () {
  751. return this._eyeOffset;
  752. },
  753. set: function (value) {
  754. //>>includeStart('debug', pragmas.debug);
  755. if (!defined(value)) {
  756. throw new DeveloperError("value is required.");
  757. }
  758. //>>includeEnd('debug');
  759. var eyeOffset = this._eyeOffset;
  760. if (!Cartesian3.equals(eyeOffset, value)) {
  761. Cartesian3.clone(value, eyeOffset);
  762. var glyphs = this._glyphs;
  763. for (var i = 0, len = glyphs.length; i < len; i++) {
  764. var glyph = glyphs[i];
  765. if (defined(glyph.billboard)) {
  766. glyph.billboard.eyeOffset = value;
  767. }
  768. }
  769. var backgroundBillboard = this._backgroundBillboard;
  770. if (defined(backgroundBillboard)) {
  771. backgroundBillboard.eyeOffset = value;
  772. }
  773. }
  774. },
  775. },
  776. /**
  777. * Gets or sets the horizontal origin of this label, which determines if the label is drawn
  778. * to the left, center, or right of its anchor position.
  779. * <br /><br />
  780. * <div align='center'>
  781. * <img src='Images/Billboard.setHorizontalOrigin.png' width='648' height='196' /><br />
  782. * </div>
  783. * @memberof Label.prototype
  784. * @type {HorizontalOrigin}
  785. * @default HorizontalOrigin.LEFT
  786. * @example
  787. * // Use a top, right origin
  788. * l.horizontalOrigin = Cesium.HorizontalOrigin.RIGHT;
  789. * l.verticalOrigin = Cesium.VerticalOrigin.TOP;
  790. */
  791. horizontalOrigin: {
  792. get: function () {
  793. return this._horizontalOrigin;
  794. },
  795. set: function (value) {
  796. //>>includeStart('debug', pragmas.debug);
  797. if (!defined(value)) {
  798. throw new DeveloperError("value is required.");
  799. }
  800. //>>includeEnd('debug');
  801. if (this._horizontalOrigin !== value) {
  802. this._horizontalOrigin = value;
  803. repositionAllGlyphs(this);
  804. }
  805. },
  806. },
  807. /**
  808. * Gets or sets the vertical origin of this label, which determines if the label is
  809. * to the above, below, or at the center of its anchor position.
  810. * <br /><br />
  811. * <div align='center'>
  812. * <img src='Images/Billboard.setVerticalOrigin.png' width='695' height='175' /><br />
  813. * </div>
  814. * @memberof Label.prototype
  815. * @type {VerticalOrigin}
  816. * @default VerticalOrigin.BASELINE
  817. * @example
  818. * // Use a top, right origin
  819. * l.horizontalOrigin = Cesium.HorizontalOrigin.RIGHT;
  820. * l.verticalOrigin = Cesium.VerticalOrigin.TOP;
  821. */
  822. verticalOrigin: {
  823. get: function () {
  824. return this._verticalOrigin;
  825. },
  826. set: function (value) {
  827. //>>includeStart('debug', pragmas.debug);
  828. if (!defined(value)) {
  829. throw new DeveloperError("value is required.");
  830. }
  831. //>>includeEnd('debug');
  832. if (this._verticalOrigin !== value) {
  833. this._verticalOrigin = value;
  834. var glyphs = this._glyphs;
  835. for (var i = 0, len = glyphs.length; i < len; i++) {
  836. var glyph = glyphs[i];
  837. if (defined(glyph.billboard)) {
  838. glyph.billboard.verticalOrigin = value;
  839. }
  840. }
  841. var backgroundBillboard = this._backgroundBillboard;
  842. if (defined(backgroundBillboard)) {
  843. backgroundBillboard.verticalOrigin = value;
  844. }
  845. repositionAllGlyphs(this);
  846. }
  847. },
  848. },
  849. /**
  850. * Gets or sets the uniform scale that is multiplied with the label's size in pixels.
  851. * A scale of <code>1.0</code> does not change the size of the label; a scale greater than
  852. * <code>1.0</code> enlarges the label; a positive scale less than <code>1.0</code> shrinks
  853. * the label.
  854. * <br /><br />
  855. * Applying a large scale value may pixelate the label. To make text larger without pixelation,
  856. * use a larger font size when calling {@link Label#font} instead.
  857. * <br /><br />
  858. * <div align='center'>
  859. * <img src='Images/Label.setScale.png' width='400' height='300' /><br/>
  860. * From left to right in the above image, the scales are <code>0.5</code>, <code>1.0</code>,
  861. * and <code>2.0</code>.
  862. * </div>
  863. * @memberof Label.prototype
  864. * @type {Number}
  865. * @default 1.0
  866. */
  867. scale: {
  868. get: function () {
  869. return this._scale;
  870. },
  871. set: function (value) {
  872. //>>includeStart('debug', pragmas.debug);
  873. if (!defined(value)) {
  874. throw new DeveloperError("value is required.");
  875. }
  876. //>>includeEnd('debug');
  877. if (this._scale !== value) {
  878. this._scale = value;
  879. var glyphs = this._glyphs;
  880. for (var i = 0, len = glyphs.length; i < len; i++) {
  881. var glyph = glyphs[i];
  882. if (defined(glyph.billboard)) {
  883. glyph.billboard.scale = value * this._relativeSize;
  884. }
  885. }
  886. var backgroundBillboard = this._backgroundBillboard;
  887. if (defined(backgroundBillboard)) {
  888. backgroundBillboard.scale = value * this._relativeSize;
  889. }
  890. repositionAllGlyphs(this);
  891. }
  892. },
  893. },
  894. /**
  895. * Gets the total scale of the label, which is the label's scale multiplied by the computed relative size
  896. * of the desired font compared to the generated glyph size.
  897. * @memberof Label.prototype
  898. * @type {Number}
  899. * @default 1.0
  900. */
  901. totalScale: {
  902. get: function () {
  903. return this._scale * this._relativeSize;
  904. },
  905. },
  906. /**
  907. * Gets or sets the condition specifying at what distance from the camera that this label will be displayed.
  908. * @memberof Label.prototype
  909. * @type {DistanceDisplayCondition}
  910. * @default undefined
  911. */
  912. distanceDisplayCondition: {
  913. get: function () {
  914. return this._distanceDisplayCondition;
  915. },
  916. set: function (value) {
  917. //>>includeStart('debug', pragmas.debug);
  918. if (defined(value) && value.far <= value.near) {
  919. throw new DeveloperError("far must be greater than near");
  920. }
  921. //>>includeEnd('debug');
  922. if (
  923. !DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)
  924. ) {
  925. this._distanceDisplayCondition = DistanceDisplayCondition.clone(
  926. value,
  927. this._distanceDisplayCondition
  928. );
  929. var glyphs = this._glyphs;
  930. for (var i = 0, len = glyphs.length; i < len; i++) {
  931. var glyph = glyphs[i];
  932. if (defined(glyph.billboard)) {
  933. glyph.billboard.distanceDisplayCondition = value;
  934. }
  935. }
  936. var backgroundBillboard = this._backgroundBillboard;
  937. if (defined(backgroundBillboard)) {
  938. backgroundBillboard.distanceDisplayCondition = value;
  939. }
  940. }
  941. },
  942. },
  943. /**
  944. * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain.
  945. * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied.
  946. * @memberof Label.prototype
  947. * @type {Number}
  948. */
  949. disableDepthTestDistance: {
  950. get: function () {
  951. return this._disableDepthTestDistance;
  952. },
  953. set: function (value) {
  954. if (this._disableDepthTestDistance !== value) {
  955. //>>includeStart('debug', pragmas.debug);
  956. if (defined(value) && value < 0.0) {
  957. throw new DeveloperError(
  958. "disableDepthTestDistance must be greater than 0.0."
  959. );
  960. }
  961. //>>includeEnd('debug');
  962. this._disableDepthTestDistance = value;
  963. var glyphs = this._glyphs;
  964. for (var i = 0, len = glyphs.length; i < len; i++) {
  965. var glyph = glyphs[i];
  966. if (defined(glyph.billboard)) {
  967. glyph.billboard.disableDepthTestDistance = value;
  968. }
  969. }
  970. var backgroundBillboard = this._backgroundBillboard;
  971. if (defined(backgroundBillboard)) {
  972. backgroundBillboard.disableDepthTestDistance = value;
  973. }
  974. }
  975. },
  976. },
  977. /**
  978. * Gets or sets the user-defined value returned when the label is picked.
  979. * @memberof Label.prototype
  980. * @type {*}
  981. */
  982. id: {
  983. get: function () {
  984. return this._id;
  985. },
  986. set: function (value) {
  987. if (this._id !== value) {
  988. this._id = value;
  989. var glyphs = this._glyphs;
  990. for (var i = 0, len = glyphs.length; i < len; i++) {
  991. var glyph = glyphs[i];
  992. if (defined(glyph.billboard)) {
  993. glyph.billboard.id = value;
  994. }
  995. }
  996. var backgroundBillboard = this._backgroundBillboard;
  997. if (defined(backgroundBillboard)) {
  998. backgroundBillboard.id = value;
  999. }
  1000. }
  1001. },
  1002. },
  1003. /**
  1004. * @private
  1005. */
  1006. pickId: {
  1007. get: function () {
  1008. if (this._glyphs.length === 0 || !defined(this._glyphs[0].billboard)) {
  1009. return undefined;
  1010. }
  1011. return this._glyphs[0].billboard.pickId;
  1012. },
  1013. },
  1014. /**
  1015. * Keeps track of the position of the label based on the height reference.
  1016. * @memberof Label.prototype
  1017. * @type {Cartesian3}
  1018. * @private
  1019. */
  1020. _clampedPosition: {
  1021. get: function () {
  1022. return this._actualClampedPosition;
  1023. },
  1024. set: function (value) {
  1025. this._actualClampedPosition = Cartesian3.clone(
  1026. value,
  1027. this._actualClampedPosition
  1028. );
  1029. var glyphs = this._glyphs;
  1030. for (var i = 0, len = glyphs.length; i < len; i++) {
  1031. var glyph = glyphs[i];
  1032. if (defined(glyph.billboard)) {
  1033. // Set all the private values here, because we already clamped to ground
  1034. // so we don't want to do it again for every glyph
  1035. glyph.billboard._clampedPosition = value;
  1036. }
  1037. }
  1038. var backgroundBillboard = this._backgroundBillboard;
  1039. if (defined(backgroundBillboard)) {
  1040. backgroundBillboard._clampedPosition = value;
  1041. }
  1042. },
  1043. },
  1044. /**
  1045. * Determines whether or not this label will be shown or hidden because it was clustered.
  1046. * @memberof Label.prototype
  1047. * @type {Boolean}
  1048. * @default true
  1049. * @private
  1050. */
  1051. clusterShow: {
  1052. get: function () {
  1053. return this._clusterShow;
  1054. },
  1055. set: function (value) {
  1056. if (this._clusterShow !== value) {
  1057. this._clusterShow = value;
  1058. var glyphs = this._glyphs;
  1059. for (var i = 0, len = glyphs.length; i < len; i++) {
  1060. var glyph = glyphs[i];
  1061. if (defined(glyph.billboard)) {
  1062. glyph.billboard.clusterShow = value;
  1063. }
  1064. }
  1065. var backgroundBillboard = this._backgroundBillboard;
  1066. if (defined(backgroundBillboard)) {
  1067. backgroundBillboard.clusterShow = value;
  1068. }
  1069. }
  1070. },
  1071. },
  1072. });
  1073. Label.prototype._updateClamping = function () {
  1074. Billboard._updateClamping(this._labelCollection, this);
  1075. };
  1076. /**
  1077. * Computes the screen-space position of the label's origin, taking into account eye and pixel offsets.
  1078. * The screen space origin is the top, left corner of the canvas; <code>x</code> increases from
  1079. * left to right, and <code>y</code> increases from top to bottom.
  1080. *
  1081. * @param {Scene} scene The scene the label is in.
  1082. * @param {Cartesian2} [result] The object onto which to store the result.
  1083. * @returns {Cartesian2} The screen-space position of the label.
  1084. *
  1085. *
  1086. * @example
  1087. * console.log(l.computeScreenSpacePosition(scene).toString());
  1088. *
  1089. * @see Label#eyeOffset
  1090. * @see Label#pixelOffset
  1091. */
  1092. Label.prototype.computeScreenSpacePosition = function (scene, result) {
  1093. //>>includeStart('debug', pragmas.debug);
  1094. if (!defined(scene)) {
  1095. throw new DeveloperError("scene is required.");
  1096. }
  1097. //>>includeEnd('debug');
  1098. if (!defined(result)) {
  1099. result = new Cartesian2();
  1100. }
  1101. var labelCollection = this._labelCollection;
  1102. var modelMatrix = labelCollection.modelMatrix;
  1103. var actualPosition = defined(this._actualClampedPosition)
  1104. ? this._actualClampedPosition
  1105. : this._position;
  1106. var windowCoordinates = Billboard._computeScreenSpacePosition(
  1107. modelMatrix,
  1108. actualPosition,
  1109. this._eyeOffset,
  1110. this._pixelOffset,
  1111. scene,
  1112. result
  1113. );
  1114. return windowCoordinates;
  1115. };
  1116. /**
  1117. * Gets a label's screen space bounding box centered around screenSpacePosition.
  1118. * @param {Label} label The label to get the screen space bounding box for.
  1119. * @param {Cartesian2} screenSpacePosition The screen space center of the label.
  1120. * @param {BoundingRectangle} [result] The object onto which to store the result.
  1121. * @returns {BoundingRectangle} The screen space bounding box.
  1122. *
  1123. * @private
  1124. */
  1125. Label.getScreenSpaceBoundingBox = function (
  1126. label,
  1127. screenSpacePosition,
  1128. result
  1129. ) {
  1130. var x = 0;
  1131. var y = 0;
  1132. var width = 0;
  1133. var height = 0;
  1134. var scale = label.totalScale;
  1135. var backgroundBillboard = label._backgroundBillboard;
  1136. if (defined(backgroundBillboard)) {
  1137. x = screenSpacePosition.x + backgroundBillboard._translate.x;
  1138. y = screenSpacePosition.y - backgroundBillboard._translate.y;
  1139. width = backgroundBillboard.width * scale;
  1140. height = backgroundBillboard.height * scale;
  1141. if (
  1142. label.verticalOrigin === VerticalOrigin.BOTTOM ||
  1143. label.verticalOrigin === VerticalOrigin.BASELINE
  1144. ) {
  1145. y -= height;
  1146. } else if (label.verticalOrigin === VerticalOrigin.CENTER) {
  1147. y -= height * 0.5;
  1148. }
  1149. } else {
  1150. x = Number.POSITIVE_INFINITY;
  1151. y = Number.POSITIVE_INFINITY;
  1152. var maxX = 0;
  1153. var maxY = 0;
  1154. var glyphs = label._glyphs;
  1155. var length = glyphs.length;
  1156. for (var i = 0; i < length; ++i) {
  1157. var glyph = glyphs[i];
  1158. var billboard = glyph.billboard;
  1159. if (!defined(billboard)) {
  1160. continue;
  1161. }
  1162. var glyphX = screenSpacePosition.x + billboard._translate.x;
  1163. var glyphY = screenSpacePosition.y - billboard._translate.y;
  1164. var glyphWidth = glyph.dimensions.width * scale;
  1165. var glyphHeight = glyph.dimensions.height * scale;
  1166. if (
  1167. label.verticalOrigin === VerticalOrigin.BOTTOM ||
  1168. label.verticalOrigin === VerticalOrigin.BASELINE
  1169. ) {
  1170. glyphY -= glyphHeight;
  1171. } else if (label.verticalOrigin === VerticalOrigin.CENTER) {
  1172. glyphY -= glyphHeight * 0.5;
  1173. }
  1174. if (label._verticalOrigin === VerticalOrigin.TOP) {
  1175. glyphY += SDFSettings.PADDING * scale;
  1176. } else if (
  1177. label._verticalOrigin === VerticalOrigin.BOTTOM ||
  1178. label._verticalOrigin === VerticalOrigin.BASELINE
  1179. ) {
  1180. glyphY -= SDFSettings.PADDING * scale;
  1181. }
  1182. x = Math.min(x, glyphX);
  1183. y = Math.min(y, glyphY);
  1184. maxX = Math.max(maxX, glyphX + glyphWidth);
  1185. maxY = Math.max(maxY, glyphY + glyphHeight);
  1186. }
  1187. width = maxX - x;
  1188. height = maxY - y;
  1189. }
  1190. if (!defined(result)) {
  1191. result = new BoundingRectangle();
  1192. }
  1193. result.x = x;
  1194. result.y = y;
  1195. result.width = width;
  1196. result.height = height;
  1197. return result;
  1198. };
  1199. /**
  1200. * Determines if this label equals another label. Labels are equal if all their properties
  1201. * are equal. Labels in different collections can be equal.
  1202. *
  1203. * @param {Label} other The label to compare for equality.
  1204. * @returns {Boolean} <code>true</code> if the labels are equal; otherwise, <code>false</code>.
  1205. */
  1206. Label.prototype.equals = function (other) {
  1207. return (
  1208. this === other ||
  1209. (defined(other) &&
  1210. this._show === other._show &&
  1211. this._scale === other._scale &&
  1212. this._outlineWidth === other._outlineWidth &&
  1213. this._showBackground === other._showBackground &&
  1214. this._style === other._style &&
  1215. this._verticalOrigin === other._verticalOrigin &&
  1216. this._horizontalOrigin === other._horizontalOrigin &&
  1217. this._heightReference === other._heightReference &&
  1218. this._renderedText === other._renderedText &&
  1219. this._font === other._font &&
  1220. Cartesian3.equals(this._position, other._position) &&
  1221. Color.equals(this._fillColor, other._fillColor) &&
  1222. Color.equals(this._outlineColor, other._outlineColor) &&
  1223. Color.equals(this._backgroundColor, other._backgroundColor) &&
  1224. Cartesian2.equals(this._backgroundPadding, other._backgroundPadding) &&
  1225. Cartesian2.equals(this._pixelOffset, other._pixelOffset) &&
  1226. Cartesian3.equals(this._eyeOffset, other._eyeOffset) &&
  1227. NearFarScalar.equals(
  1228. this._translucencyByDistance,
  1229. other._translucencyByDistance
  1230. ) &&
  1231. NearFarScalar.equals(
  1232. this._pixelOffsetScaleByDistance,
  1233. other._pixelOffsetScaleByDistance
  1234. ) &&
  1235. NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) &&
  1236. DistanceDisplayCondition.equals(
  1237. this._distanceDisplayCondition,
  1238. other._distanceDisplayCondition
  1239. ) &&
  1240. this._disableDepthTestDistance === other._disableDepthTestDistance &&
  1241. this._id === other._id)
  1242. );
  1243. };
  1244. /**
  1245. * Returns true if this object was destroyed; otherwise, false.
  1246. * <br /><br />
  1247. * If this object was destroyed, it should not be used; calling any function other than
  1248. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  1249. *
  1250. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  1251. */
  1252. Label.prototype.isDestroyed = function () {
  1253. return false;
  1254. };
  1255. /**
  1256. * Determines whether or not run the algorithm, that match the text of the label to right-to-left languages
  1257. * @memberof Label
  1258. * @type {Boolean}
  1259. * @default false
  1260. *
  1261. * @example
  1262. * // Example 1.
  1263. * // Set a label's rightToLeft before init
  1264. * Cesium.Label.enableRightToLeftDetection = true;
  1265. * var myLabelEntity = viewer.entities.add({
  1266. * label: {
  1267. * id: 'my label',
  1268. * text: 'זה טקסט בעברית \n ועכשיו יורדים שורה',
  1269. * }
  1270. * });
  1271. *
  1272. * @example
  1273. * // Example 2.
  1274. * var myLabelEntity = viewer.entities.add({
  1275. * label: {
  1276. * id: 'my label',
  1277. * text: 'English text'
  1278. * }
  1279. * });
  1280. * // Set a label's rightToLeft after init
  1281. * Cesium.Label.enableRightToLeftDetection = true;
  1282. * myLabelEntity.text = 'טקסט חדש';
  1283. */
  1284. Label.enableRightToLeftDetection = false;
  1285. function convertTextToTypes(text, rtlChars) {
  1286. var ltrChars = /[a-zA-Z0-9]/;
  1287. var bracketsChars = /[()[\]{}<>]/;
  1288. var parsedText = [];
  1289. var word = "";
  1290. var lastType = textTypes.LTR;
  1291. var currentType = "";
  1292. var textLength = text.length;
  1293. for (var textIndex = 0; textIndex < textLength; ++textIndex) {
  1294. var character = text.charAt(textIndex);
  1295. if (rtlChars.test(character)) {
  1296. currentType = textTypes.RTL;
  1297. } else if (ltrChars.test(character)) {
  1298. currentType = textTypes.LTR;
  1299. } else if (bracketsChars.test(character)) {
  1300. currentType = textTypes.BRACKETS;
  1301. } else {
  1302. currentType = textTypes.WEAK;
  1303. }
  1304. if (textIndex === 0) {
  1305. lastType = currentType;
  1306. }
  1307. if (lastType === currentType && currentType !== textTypes.BRACKETS) {
  1308. word += character;
  1309. } else {
  1310. if (word !== "") {
  1311. parsedText.push({ Type: lastType, Word: word });
  1312. }
  1313. lastType = currentType;
  1314. word = character;
  1315. }
  1316. }
  1317. parsedText.push({ Type: currentType, Word: word });
  1318. return parsedText;
  1319. }
  1320. function reverseWord(word) {
  1321. return word.split("").reverse().join("");
  1322. }
  1323. function spliceWord(result, pointer, word) {
  1324. return result.slice(0, pointer) + word + result.slice(pointer);
  1325. }
  1326. function reverseBrackets(bracket) {
  1327. switch (bracket) {
  1328. case "(":
  1329. return ")";
  1330. case ")":
  1331. return "(";
  1332. case "[":
  1333. return "]";
  1334. case "]":
  1335. return "[";
  1336. case "{":
  1337. return "}";
  1338. case "}":
  1339. return "{";
  1340. case "<":
  1341. return ">";
  1342. case ">":
  1343. return "<";
  1344. }
  1345. }
  1346. //To add another language, simply add its Unicode block range(s) to the below regex.
  1347. var hebrew = "\u05D0-\u05EA";
  1348. var arabic = "\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF";
  1349. var rtlChars = new RegExp("[" + hebrew + arabic + "]");
  1350. /**
  1351. *
  1352. * @param {String} value the text to parse and reorder
  1353. * @returns {String} the text as rightToLeft direction
  1354. * @private
  1355. */
  1356. function reverseRtl(value) {
  1357. var texts = value.split("\n");
  1358. var result = "";
  1359. for (var i = 0; i < texts.length; i++) {
  1360. var text = texts[i];
  1361. // first character of the line is a RTL character, so need to manage different cases
  1362. var rtlDir = rtlChars.test(text.charAt(0));
  1363. var parsedText = convertTextToTypes(text, rtlChars);
  1364. var splicePointer = 0;
  1365. var line = "";
  1366. for (var wordIndex = 0; wordIndex < parsedText.length; ++wordIndex) {
  1367. var subText = parsedText[wordIndex];
  1368. var reverse =
  1369. subText.Type === textTypes.BRACKETS
  1370. ? reverseBrackets(subText.Word)
  1371. : reverseWord(subText.Word);
  1372. if (rtlDir) {
  1373. if (subText.Type === textTypes.RTL) {
  1374. line = reverse + line;
  1375. splicePointer = 0;
  1376. } else if (subText.Type === textTypes.LTR) {
  1377. line = spliceWord(line, splicePointer, subText.Word);
  1378. splicePointer += subText.Word.length;
  1379. } else if (
  1380. subText.Type === textTypes.WEAK ||
  1381. subText.Type === textTypes.BRACKETS
  1382. ) {
  1383. // current word is weak, last one was bracket
  1384. if (
  1385. subText.Type === textTypes.WEAK &&
  1386. parsedText[wordIndex - 1].Type === textTypes.BRACKETS
  1387. ) {
  1388. line = reverse + line;
  1389. }
  1390. // current word is weak or bracket, last one was rtl
  1391. else if (parsedText[wordIndex - 1].Type === textTypes.RTL) {
  1392. line = reverse + line;
  1393. splicePointer = 0;
  1394. }
  1395. // current word is weak or bracket, there is at least one more word
  1396. else if (parsedText.length > wordIndex + 1) {
  1397. // next word is rtl
  1398. if (parsedText[wordIndex + 1].Type === textTypes.RTL) {
  1399. line = reverse + line;
  1400. splicePointer = 0;
  1401. } else {
  1402. line = spliceWord(line, splicePointer, subText.Word);
  1403. splicePointer += subText.Word.length;
  1404. }
  1405. }
  1406. // current word is weak or bracket, and it the last in this line
  1407. else {
  1408. line = spliceWord(line, 0, reverse);
  1409. }
  1410. }
  1411. }
  1412. // ltr line, rtl word
  1413. else if (subText.Type === textTypes.RTL) {
  1414. line = spliceWord(line, splicePointer, reverse);
  1415. }
  1416. // ltr line, ltr word
  1417. else if (subText.Type === textTypes.LTR) {
  1418. line += subText.Word;
  1419. splicePointer = line.length;
  1420. }
  1421. // ltr line, weak or bracket word
  1422. else if (
  1423. subText.Type === textTypes.WEAK ||
  1424. subText.Type === textTypes.BRACKETS
  1425. ) {
  1426. // not first word in line
  1427. if (wordIndex > 0) {
  1428. // last word was rtl
  1429. if (parsedText[wordIndex - 1].Type === textTypes.RTL) {
  1430. // there is at least one more word
  1431. if (parsedText.length > wordIndex + 1) {
  1432. // next word is rtl
  1433. if (parsedText[wordIndex + 1].Type === textTypes.RTL) {
  1434. line = spliceWord(line, splicePointer, reverse);
  1435. } else {
  1436. line += subText.Word;
  1437. splicePointer = line.length;
  1438. }
  1439. } else {
  1440. line += subText.Word;
  1441. }
  1442. } else {
  1443. line += subText.Word;
  1444. splicePointer = line.length;
  1445. }
  1446. } else {
  1447. line += subText.Word;
  1448. splicePointer = line.length;
  1449. }
  1450. }
  1451. }
  1452. result += line;
  1453. if (i < texts.length - 1) {
  1454. result += "\n";
  1455. }
  1456. }
  1457. return result;
  1458. }
  1459. export default Label;