diff --git a/Apps/Sandcastle/gallery/Picking.html b/Apps/Sandcastle/gallery/Picking.html
index 22c7c73ff95a..6ae7162463fe 100644
--- a/Apps/Sandcastle/gallery/Picking.html
+++ b/Apps/Sandcastle/gallery/Picking.html
@@ -38,7 +38,12 @@
Sandcastle.addDefaultToolbarButton('Show Cartographic Position on Mouse Over', function() {
var entity = viewer.entities.add({
label : {
- show : false
+ show : false,
+ showBackground : true,
+ font : '14px monospace',
+ horizontalOrigin : Cesium.HorizontalOrigin.LEFT,
+ verticalOrigin : Cesium.VerticalOrigin.TOP,
+ pixelOffset : new Cesium.Cartesian2(15, 0)
}
});
@@ -53,7 +58,9 @@
entity.position = cartesian;
entity.label.show = true;
- entity.label.text = '(' + longitudeString + ', ' + latitudeString + ')';
+ entity.label.text =
+ 'Lon: ' + (' ' + longitudeString).slice(-7) + '\u00B0' +
+ '\nLat: ' + (' ' + latitudeString).slice(-7) + '\u00B0';
} else {
entity.label.show = false;
}
@@ -159,7 +166,11 @@
var labelEntity = viewer.entities.add({
label : {
show : false,
- horizontalOrigin : Cesium.HorizontalOrigin.LEFT
+ showBackground : true,
+ font : '14px monospace',
+ horizontalOrigin : Cesium.HorizontalOrigin.LEFT,
+ verticalOrigin : Cesium.VerticalOrigin.TOP,
+ pixelOffset : new Cesium.Cartesian2(15, 0)
}
});
@@ -181,8 +192,11 @@
labelEntity.position = cartesian;
labelEntity.label.show = true;
- labelEntity.label.text = '(' + longitudeString + ', ' + latitudeString + ', ' + heightString + ')';
-
+ labelEntity.label.text =
+ 'Lon: ' + (' ' + longitudeString).slice(-7) + '\u00B0' +
+ '\nLat: ' + (' ' + latitudeString).slice(-7) + '\u00B0' +
+ '\nAlt: ' + (' ' + heightString).slice(-7) + 'm';
+
var camera = scene.camera;
labelEntity.label.eyeOffset = new Cesium.Cartesian3(0.0, 0.0, camera.frustum.near * 1.5 - Cesium.Cartesian3.distance(cartesian, camera.position));
@@ -201,7 +215,7 @@
handler = handler && handler.destroy();
};
//Sandcastle_End
-Sandcastle.finishedLoading();
+ Sandcastle.finishedLoading();
}
if (typeof Cesium !== "undefined") {
startup(Cesium);
diff --git a/CHANGES.md b/CHANGES.md
index 1d754f687786..7dfff1c2f52d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -6,6 +6,7 @@ Change Log
* Added the ability to blend a `Model` with a color/translucency. Added `color`, `colorBlendMode`, and `colorBlendAmount` properties to `Model`, `ModelGraphics`, and CZML. Added `ColorBlendMode` enum. [#4547](https://github.com/AnalyticalGraphicsInc/cesium/pull/4547)
* Added new `Label` properties `showBackground`, `backgroundColor`, and `backgroundPadding` to the primitive, Entity, and CZML layers.
* Added new enum `VerticalOrigin.BASELINE`. Previously, `VerticalOrigin.BOTTOM` would sometimes align to the baseline depending on the contents of a label.
+* Added support for newlines (`\n`) in Cesium `Label`s and CZML. [#2402](https://github.com/AnalyticalGraphicsInc/cesium/issues/2402)
* Fixed tooltips for gallery thumbnails in Sandcastle [#4702](https://github.com/AnalyticalGraphicsInc/cesium/pull/4702)
* Fixed `Rectangle.union` to correctly account for rectangles that cross the IDL [#4732](https://github.com/AnalyticalGraphicsInc/cesium/pull/4732).
* Fixed texture rotation for `RectangleGeometry` [#2737](https://github.com/AnalyticalGraphicsInc/cesium/issues/2737)
diff --git a/Documentation/Images/Billboard.setHorizontalOrigin.png b/Documentation/Images/Billboard.setHorizontalOrigin.png
index 01ac63db3ccc..77ce870e0a60 100644
Binary files a/Documentation/Images/Billboard.setHorizontalOrigin.png and b/Documentation/Images/Billboard.setHorizontalOrigin.png differ
diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js
index e3aacdaca9eb..3f5396ccd463 100644
--- a/Source/DataSources/LabelGraphics.js
+++ b/Source/DataSources/LabelGraphics.js
@@ -28,7 +28,7 @@ define([
* @constructor
*
* @param {Object} [options] Object with the following properties:
- * @param {Property} [options.text] A Property specifying the text.
+ * @param {Property} [options.text] A Property specifying the text. Explicit newlines '\n' are supported.
* @param {Property} [options.font='10px sans-serif'] A Property specifying the CSS font.
* @param {Property} [options.style=LabelStyle.FILL] A Property specifying the {@link LabelStyle}.
* @param {Property} [options.fillColor=Color.WHITE] A Property specifying the fill {@link Color}.
@@ -110,6 +110,7 @@ define([
/**
* Gets or sets the string Property specifying the text of the label.
+ * Explicit newlines '\n' are supported.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
diff --git a/Source/Scene/Billboard.js b/Source/Scene/Billboard.js
index f50cd4eca1ca..b51d5d8dc59c 100644
--- a/Source/Scene/Billboard.js
+++ b/Source/Scene/Billboard.js
@@ -472,7 +472,7 @@ define([
* to the left, center, or right of its anchor position.
*
*
- *

+ *

*
* @memberof Billboard.prototype
* @type {HorizontalOrigin}
diff --git a/Source/Scene/HorizontalOrigin.js b/Source/Scene/HorizontalOrigin.js
index db1737fcf688..87f5d981370e 100644
--- a/Source/Scene/HorizontalOrigin.js
+++ b/Source/Scene/HorizontalOrigin.js
@@ -12,7 +12,7 @@ define([
* of the anchor position.
*
*
- *

+ *

*
*
* @exports HorizontalOrigin
diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js
index ca1b68d2e8ef..67502ac5d74c 100644
--- a/Source/Scene/Label.js
+++ b/Source/Scene/Label.js
@@ -671,7 +671,7 @@ define([
* to the left, center, or right of its anchor position.
*
*
- *

+ *

*
* @memberof Label.prototype
* @type {HorizontalOrigin}
diff --git a/Source/Scene/LabelCollection.js b/Source/Scene/LabelCollection.js
index 2ff096b9706f..f7416ffc40d7 100644
--- a/Source/Scene/LabelCollection.js
+++ b/Source/Scene/LabelCollection.js
@@ -55,6 +55,9 @@ define([
this.dimensions = dimensions;
}
+ // Traditionally, leading is %20 of the font size.
+ var defaultLineSpacingPercent = 1.2;
+
var whitePixelCanvasId = 'ID_WHITE_PIXEL';
var whitePixelSize = new Cartesian2(4, 4);
var whitePixelBoundingRegion = new BoundingRectangle(1, 1, 1, 1);
@@ -135,7 +138,7 @@ define([
// presize glyphs to match the new text length
glyphs.length = textLength;
- var showBackground = label._showBackground && (glyphs.length > 0);
+ var showBackground = label._showBackground && (text.split('\n').join('').length > 0);
var backgroundBillboard = label._backgroundBillboard;
var backgroundBillboardCollection = labelCollection._backgroundBillboardCollection;
if (!showBackground) {
@@ -267,17 +270,32 @@ define([
label._repositionAllGlyphs = true;
}
+ function calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding) {
+ if (horizontalOrigin === HorizontalOrigin.CENTER) {
+ return -lineWidth / 2;
+ } else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
+ return -(lineWidth + backgroundPadding.x);
+ }
+ return backgroundPadding.x;
+ }
+
// reusable Cartesian2 instances
var glyphPixelOffset = new Cartesian2();
var scratchBackgroundPadding = new Cartesian2();
function repositionAllGlyphs(label, resolutionScale) {
var glyphs = label._glyphs;
+ var text = label._text;
var glyph;
var dimensions;
- var totalWidth = 0;
- var maxDescent = Number.NEGATIVE_INFINITY;
- var maxY = 0;
+ var lastLineWidth = 0;
+ var maxLineWidth = 0;
+ var lineWidths = [];
+ var maxGlyphDescent = Number.NEGATIVE_INFINITY;
+ var maxGlyphY = 0;
+ var numberOfLines = 1;
+ var glyphIndex = 0;
+ var glyphLength = glyphs.length;
var backgroundBillboard = label._backgroundBillboard;
var backgroundPadding = scratchBackgroundPadding;
@@ -285,89 +303,109 @@ define([
(defined(backgroundBillboard) ? label._backgroundPadding : Cartesian2.ZERO),
backgroundPadding);
- var glyphIndex = 0;
- var glyphLength = glyphs.length;
for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
- glyph = glyphs[glyphIndex];
- dimensions = glyph.dimensions;
- maxY = Math.max(maxY, dimensions.height - dimensions.descent);
- maxDescent = Math.max(maxDescent, dimensions.descent);
-
- //Computing the total width must also account for the kering that occurs between letters.
- totalWidth += dimensions.width - dimensions.bounds.minx;
- if (glyphIndex < glyphLength - 1) {
- totalWidth += glyphs[glyphIndex + 1].dimensions.bounds.minx;
+ if (text.charAt(glyphIndex) === '\n') {
+ lineWidths.push(lastLineWidth);
+ ++numberOfLines;
+ lastLineWidth = 0;
+ } else {
+ glyph = glyphs[glyphIndex];
+ dimensions = glyph.dimensions;
+ maxGlyphY = Math.max(maxGlyphY, dimensions.height - dimensions.descent);
+ maxGlyphDescent = Math.max(maxGlyphDescent, dimensions.descent);
+
+ //Computing the line width must also account for the kerning that occurs between letters.
+ lastLineWidth += dimensions.width - dimensions.bounds.minx;
+ if (glyphIndex < glyphLength - 1) {
+ lastLineWidth += glyphs[glyphIndex + 1].dimensions.bounds.minx;
+ }
+ maxLineWidth = Math.max(maxLineWidth, lastLineWidth);
}
}
- var maxHeight = maxY + maxDescent;
+ lineWidths.push(lastLineWidth);
+ var maxLineHeight = maxGlyphY + maxGlyphDescent;
var scale = label._scale;
var horizontalOrigin = label._horizontalOrigin;
- var widthOffset = 0;
- if (horizontalOrigin === HorizontalOrigin.CENTER) {
- widthOffset -= totalWidth / 2 * scale;
- } else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
- widthOffset -= (totalWidth + backgroundPadding.x) * scale;
- } else {
- widthOffset += backgroundPadding.x * scale;
- }
+ var verticalOrigin = label._verticalOrigin;
+ var lineIndex = 0;
+ var lineWidth = lineWidths[lineIndex];
+ var widthOffset = calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding);
+ var lineSpacing = defaultLineSpacingPercent * maxLineHeight;
+ var otherLinesHeight = lineSpacing * (numberOfLines - 1);
- glyphPixelOffset.x = widthOffset * resolutionScale;
+ glyphPixelOffset.x = widthOffset * scale * resolutionScale;
glyphPixelOffset.y = 0;
- var verticalOrigin = label._verticalOrigin;
+ var lineOffsetY = 0;
for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
- glyph = glyphs[glyphIndex];
- dimensions = glyph.dimensions;
-
- if (verticalOrigin === VerticalOrigin.BASELINE) {
- glyphPixelOffset.y = -dimensions.descent * scale;
- } else if (verticalOrigin === VerticalOrigin.TOP) {
- glyphPixelOffset.y = -(maxY - dimensions.height + dimensions.descent + backgroundPadding.y) * scale;
- } else if (verticalOrigin === VerticalOrigin.CENTER) {
- glyphPixelOffset.y = -(maxY - dimensions.height) / 2 * scale - dimensions.descent * scale;
+ if (text.charAt(glyphIndex) === '\n') {
+ ++lineIndex;
+ lineOffsetY += lineSpacing;
+ lineWidth = lineWidths[lineIndex];
+ widthOffset = calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding);
+ glyphPixelOffset.x = widthOffset * scale * resolutionScale;
} else {
- // VerticalOrigin.BOTTOM
- glyphPixelOffset.y = (maxDescent - dimensions.descent + backgroundPadding.y) * scale;
- }
+ glyph = glyphs[glyphIndex];
+ dimensions = glyph.dimensions;
+
+ if (verticalOrigin === VerticalOrigin.TOP) {
+ glyphPixelOffset.y = dimensions.height - maxGlyphY - backgroundPadding.y;
+ } else if (verticalOrigin === VerticalOrigin.CENTER) {
+ glyphPixelOffset.y = (otherLinesHeight + dimensions.height - maxGlyphY) / 2;
+ } else if (verticalOrigin === VerticalOrigin.BASELINE) {
+ glyphPixelOffset.y = otherLinesHeight;
+ } else {
+ // VerticalOrigin.BOTTOM
+ glyphPixelOffset.y = otherLinesHeight + maxGlyphDescent + backgroundPadding.y;
+ }
+ glyphPixelOffset.y = (glyphPixelOffset.y - dimensions.descent - lineOffsetY) * scale * resolutionScale;
- glyphPixelOffset.y *= resolutionScale;
+ if (defined(glyph.billboard)) {
+ glyph.billboard._setTranslate(glyphPixelOffset);
+ }
- if (defined(glyph.billboard)) {
- glyph.billboard._setTranslate(glyphPixelOffset);
+ //Compute the next x offset taking into acocunt the kerning performed
+ //on both the current letter as well as the next letter to be drawn
+ //as well as any applied scale.
+ if (glyphIndex < glyphLength - 1) {
+ var nextGlyph = glyphs[glyphIndex + 1];
+ glyphPixelOffset.x += ((dimensions.width - dimensions.bounds.minx) + nextGlyph.dimensions.bounds.minx) * scale * resolutionScale;
+ }
}
+ }
- //Compute the next x offset taking into acocunt the kerning performed
- //on both the current letter as well as the next letter to be drawn
- //as well as any applied scale.
- if (glyphIndex < glyphLength - 1) {
- var nextGlyph = glyphs[glyphIndex + 1];
- glyphPixelOffset.x += ((dimensions.width - dimensions.bounds.minx) + nextGlyph.dimensions.bounds.minx) * scale * resolutionScale;
+ if (defined(backgroundBillboard) && (text.split('\n').join('').length > 0)) {
+ if (horizontalOrigin === HorizontalOrigin.CENTER) {
+ widthOffset = -maxLineWidth / 2 - backgroundPadding.x;
+ } else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
+ widthOffset = -(maxLineWidth + backgroundPadding.x * 2);
+ } else {
+ widthOffset = 0;
}
- }
+ glyphPixelOffset.x = widthOffset * scale * resolutionScale;
- if (defined(backgroundBillboard) && (glyphLength > 0)) {
- glyphPixelOffset.x = (widthOffset - backgroundPadding.x * scale) * resolutionScale;
- if (verticalOrigin === VerticalOrigin.BASELINE) {
- glyphPixelOffset.y = -backgroundPadding.y * scale - maxDescent * scale;
- } else if (verticalOrigin === VerticalOrigin.TOP) {
- glyphPixelOffset.y = -(maxY - maxHeight) * scale - maxDescent * scale;
+ if (verticalOrigin === VerticalOrigin.TOP) {
+ glyphPixelOffset.y = maxLineHeight - maxGlyphY - maxGlyphDescent;
} else if (verticalOrigin === VerticalOrigin.CENTER) {
- glyphPixelOffset.y = -(maxY - maxHeight) / 2 * scale - maxDescent * scale;
+ glyphPixelOffset.y = (maxLineHeight - maxGlyphY) / 2 - maxGlyphDescent;
+ } else if (verticalOrigin === VerticalOrigin.BASELINE) {
+ glyphPixelOffset.y = -backgroundPadding.y - maxGlyphDescent;
} else {
// VerticalOrigin.BOTTOM
glyphPixelOffset.y = 0;
}
- glyphPixelOffset.y *= resolutionScale;
- backgroundBillboard.width = totalWidth + (backgroundPadding.x * 2);
- backgroundBillboard.height = maxHeight + (backgroundPadding.y * 2);
+ glyphPixelOffset.y = glyphPixelOffset.y * scale * resolutionScale;
+
+ backgroundBillboard.width = maxLineWidth + (backgroundPadding.x * 2);
+ backgroundBillboard.height = maxLineHeight + otherLinesHeight + (backgroundPadding.y * 2);
backgroundBillboard._setTranslate(glyphPixelOffset);
}
}
function destroyLabel(labelCollection, label) {
var glyphs = label._glyphs;
- for ( var i = 0, len = glyphs.length; i < len; ++i) {
+ for (var i = 0, len = glyphs.length; i < len; ++i) {
unbindGlyph(labelCollection, glyphs[i]);
}
if (defined(label._backgroundBillboard)) {
@@ -628,7 +666,7 @@ define([
LabelCollection.prototype.removeAll = function() {
var labels = this._labels;
- for ( var i = 0, len = labels.length; i < len; ++i) {
+ for (var i = 0, len = labels.length; i < len; ++i) {
destroyLabel(this, labels[i]);
}
diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js
index 8a87183fa3ab..1477b8a87b0f 100644
--- a/Specs/Scene/LabelCollectionSpec.js
+++ b/Specs/Scene/LabelCollectionSpec.js
@@ -1133,6 +1133,10 @@ defineSuite([
return Cartesian2.clone(label._glyphs[index].billboard._translate, new Cartesian2());
}
+ function getBackgroundBillboardVertexTranslate(label) {
+ return Cartesian2.clone(label._backgroundBillboard._translate, new Cartesian2());
+ }
+
it('sets billboard properties properly when they change on the label', function() {
var position1 = new Cartesian3(1.0, 2.0, 3.0);
var position2 = new Cartesian3(4.0, 5.0, 6.0);
@@ -1396,7 +1400,8 @@ defineSuite([
var label = labels.add({
text : 'apl',
font : '90px "Open Sans"',
- horizontalOrigin : HorizontalOrigin.CENTER
+ horizontalOrigin : HorizontalOrigin.CENTER,
+ showBackground : true
});
scene.renderForSpecs();
@@ -1404,6 +1409,7 @@ defineSuite([
var offset0 = getGlyphBillboardVertexTranslate(label, 0);
var offset1 = getGlyphBillboardVertexTranslate(label, 1);
var offset2 = getGlyphBillboardVertexTranslate(label, 2);
+ var offsetBack = getBackgroundBillboardVertexTranslate(label);
label.horizontalOrigin = HorizontalOrigin.LEFT;
scene.renderForSpecs();
@@ -1412,11 +1418,13 @@ defineSuite([
expect(getGlyphBillboardVertexTranslate(label, 0).x).toBeGreaterThan(offset0.x);
expect(getGlyphBillboardVertexTranslate(label, 1).x).toBeGreaterThan(offset1.x);
expect(getGlyphBillboardVertexTranslate(label, 2).x).toBeGreaterThan(offset2.x);
+ expect(getBackgroundBillboardVertexTranslate(label).x).toBeGreaterThan(offsetBack.x);
// Y offset should be unchanged
expect(getGlyphBillboardVertexTranslate(label, 0).y).toEqual(offset0.y);
expect(getGlyphBillboardVertexTranslate(label, 1).y).toEqual(offset1.y);
expect(getGlyphBillboardVertexTranslate(label, 2).y).toEqual(offset2.y);
+ expect(getBackgroundBillboardVertexTranslate(label).y).toEqual(offsetBack.y);
label.horizontalOrigin = HorizontalOrigin.RIGHT;
scene.renderForSpecs();
@@ -1425,11 +1433,13 @@ defineSuite([
expect(getGlyphBillboardVertexTranslate(label, 0).x).toBeLessThan(offset0.x);
expect(getGlyphBillboardVertexTranslate(label, 1).x).toBeLessThan(offset1.x);
expect(getGlyphBillboardVertexTranslate(label, 2).x).toBeLessThan(offset2.x);
+ expect(getBackgroundBillboardVertexTranslate(label).x).toBeLessThan(offsetBack.x);
// Y offset should be unchanged
expect(getGlyphBillboardVertexTranslate(label, 0).y).toEqual(offset0.y);
expect(getGlyphBillboardVertexTranslate(label, 1).y).toEqual(offset1.y);
expect(getGlyphBillboardVertexTranslate(label, 2).y).toEqual(offset2.y);
+ expect(getBackgroundBillboardVertexTranslate(label).y).toEqual(offsetBack.y);
});
it('should set vertexTranslate of billboards correctly when scale is changed', function() {
@@ -1666,6 +1676,23 @@ defineSuite([
expect(dimensions.height).toBeLessThan(originalDimensions.height);
expect(dimensions.descent).toBeLessThanOrEqualTo(originalDimensions.descent);
});
+
+ it('should increase label height and decrease width when adding newlines', function() {
+ var label = labels.add({
+ text : 'apl apl apl',
+ });
+ scene.renderForSpecs();
+
+ var originalBbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO);
+
+ label.text = 'apl\napl\napl';
+ scene.renderForSpecs();
+ var newlinesBbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO);
+
+ expect(newlinesBbox.width).toBeLessThan(originalBbox.width);
+ expect(newlinesBbox.height).toBeGreaterThan(originalBbox.height);
+ });
+
}, 'WebGL');
it('computes bounding sphere in 3D', function() {