Label.js 49 KB

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