diff --git a/js/data/bucket/symbol_bucket.js b/js/data/bucket/symbol_bucket.js index fc62cbfc5a5..04691463eb7 100644 --- a/js/data/bucket/symbol_bucket.js +++ b/js/data/bucket/symbol_bucket.js @@ -579,7 +579,7 @@ SymbolBucket.prototype.addSymbolInstance = function(anchor, line, shapedText, sh var textBoxEndIndex = textCollisionFeature ? textCollisionFeature.boxEndIndex : this.collisionBoxArray.length; if (shapedIcon) { - iconQuads = addToBuffers ? getIconQuads(anchor, shapedIcon, iconBoxScale, line, layout, iconAlongLine) : []; + iconQuads = addToBuffers ? getIconQuads(anchor, shapedIcon, iconBoxScale, line, layout, iconAlongLine, shapedText) : []; iconCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shapedIcon, iconBoxScale, iconPadding, iconAlongLine, true); } diff --git a/js/symbol/quads.js b/js/symbol/quads.js index 1bdc6fd5bdd..1b720c79c18 100644 --- a/js/symbol/quads.js +++ b/js/symbol/quads.js @@ -51,10 +51,11 @@ function SymbolQuad(anchorPoint, tl, tr, bl, br, tex, anchorAngle, glyphAngle, m * @param {Array>} line * @param {LayoutProperties} layout * @param {boolean} alongLine Whether the icon should be placed along the line. + * @param {Shaping} shapedText Shaping for corresponding text * @returns {Array} * @private */ -function getIconQuads(anchor, shapedIcon, boxScale, line, layout, alongLine) { +function getIconQuads(anchor, shapedIcon, boxScale, line, layout, alongLine, shapedText) { var rect = shapedIcon.image.rect; @@ -63,10 +64,38 @@ function getIconQuads(anchor, shapedIcon, boxScale, line, layout, alongLine) { var right = left + rect.w / shapedIcon.image.pixelRatio; var top = shapedIcon.top - border; var bottom = top + rect.h / shapedIcon.image.pixelRatio; - var tl = new Point(left, top); - var tr = new Point(right, top); - var br = new Point(right, bottom); - var bl = new Point(left, bottom); + var tl, tr, br, bl; + + // text-fit mode + if (layout['icon-text-fit'] !== 'none' && shapedText) { + var iconWidth = (right - left), + iconHeight = (bottom - top), + size = layout['text-size'] / 24, + textLeft = shapedText.left * size, + textRight = shapedText.right * size, + textTop = shapedText.top * size, + textBottom = shapedText.bottom * size, + textWidth = textRight - textLeft, + textHeight = textBottom - textTop, + padT = layout['icon-text-fit-padding'][0], + padR = layout['icon-text-fit-padding'][1], + padB = layout['icon-text-fit-padding'][2], + padL = layout['icon-text-fit-padding'][3], + offsetY = layout['icon-text-fit'] === 'width' ? (textHeight - iconHeight) * 0.5 : 0, + offsetX = layout['icon-text-fit'] === 'height' ? (textWidth - iconWidth) * 0.5 : 0, + width = layout['icon-text-fit'] === 'width' || layout['icon-text-fit'] === 'both' ? textWidth : iconWidth, + height = layout['icon-text-fit'] === 'height' || layout['icon-text-fit'] === 'both' ? textHeight : iconHeight; + tl = new Point(textLeft + offsetX - padL, textTop + offsetY - padT); + tr = new Point(textLeft + offsetX + padR + width, textTop + offsetY - padT); + br = new Point(textLeft + offsetX + padR + width, textTop + offsetY + padB + height); + bl = new Point(textLeft + offsetX - padL, textTop + offsetY + padB + height); + // Normal icon size mode + } else { + tl = new Point(left, top); + tr = new Point(right, top); + br = new Point(right, bottom); + bl = new Point(left, bottom); + } var angle = layout['icon-rotate'] * Math.PI / 180; if (alongLine) { diff --git a/package.json b/package.json index 0160eec1d8c..c3a6d796b98 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "mapbox-gl-function": "^1.2.1", "mapbox-gl-js-supported": "^1.1.0", "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#59e998295d548f208ee3ec10cdd21ff2630e2079", - "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#2461efc3d883f2f2e56a6c6b2bfd7d54bbfe9f86", + "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#194fc55b6a7dd54c1e2cf2dd9048fbb5e836716d", "minifyify": "^7.0.1", "pbf": "^1.3.2", "pngjs": "^2.2.0", @@ -57,7 +57,7 @@ "highlight.js": "9.3.0", "istanbul": "^0.4.2", "lodash": "^4.13.1", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#3d06b422b1aa81aec71b4c67128f3d947205d6af", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#146a348f1768ce13e153fce3b32bbed469aa5fe4", "nyc": "6.4.0", "remark": "4.2.2", "remark-html": "3.0.0", diff --git a/test/js/symbol/quads.test.js b/test/js/symbol/quads.test.js index 32df909791d..c12417a7ef0 100644 --- a/test/js/symbol/quads.test.js +++ b/test/js/symbol/quads.test.js @@ -54,3 +54,191 @@ test('getIconQuads', function(t) { }); t.end(); }); + +test('getIconQuads text-fit', function(t) { + var anchor = new Anchor(0, 0, 0, undefined); + var fakeShapedIcon = { + top: -10, + bottom: 10, + left: -10, + right: 10, + image: { + pixelRatio: 1, + rect: { w: 20, h: 20 } + } + }; + var fakeShapedText = { + top: -10, + bottom: 30, + left: -60, + right: 20 + }; + + t.test('icon-text-fit: none', function(t) { + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'icon-text-fit': 'none' + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -11, y: -11 }); + t.deepEqual(quads[0].tr, { x: 9, y: -11 }); + t.deepEqual(quads[0].bl, { x: -11, y: 9 }); + t.deepEqual(quads[0].br, { x: 9, y: 9 }); + t.deepEqual(quads, getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'icon-text-fit': 'none', + 'icon-text-fit-padding': [10, 10] + }, false, fakeShapedText), 'ignores padding'); + t.end(); + }); + + t.test('icon-text-fit: width', function(t) { + // - Uses text width + // - Preserves icon height, centers vertically + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'text-size': 24, + 'icon-text-fit': 'width', + 'icon-text-fit-padding': [ 0, 0, 0, 0 ] + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -60, y: 0 }); + t.deepEqual(quads[0].tr, { x: 20, y: 0 }); + t.deepEqual(quads[0].bl, { x: -60, y: 20 }); + t.deepEqual(quads[0].br, { x: 20, y: 20 }); + t.end(); + }); + + t.test('icon-text-fit: width, x textSize', function(t) { + // - Uses text width (adjusted for textSize) + // - Preserves icon height, centers vertically + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'text-size': 12, + 'icon-text-fit': 'width', + 'icon-text-fit-padding': [ 0, 0, 0, 0 ] + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -30, y: -5 }); + t.deepEqual(quads[0].tr, { x: 10, y: -5 }); + t.deepEqual(quads[0].bl, { x: -30, y: 15 }); + t.deepEqual(quads[0].br, { x: 10, y: 15 }); + t.end(); + }); + + t.test('icon-text-fit: width, x textSize, + padding', function(t) { + // - Uses text width (adjusted for textSize) + // - Preserves icon height, centers vertically + // - Applies padding x, padding y + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'text-size': 12, + 'icon-text-fit': 'width', + 'icon-text-fit-padding': [ 5, 10, 5, 10 ] + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -40, y: -10 }); + t.deepEqual(quads[0].tr, { x: 20, y: -10 }); + t.deepEqual(quads[0].bl, { x: -40, y: 20 }); + t.deepEqual(quads[0].br, { x: 20, y: 20 }); + t.end(); + }); + + t.test('icon-text-fit: height', function(t) { + // - Uses text height + // - Preserves icon width, centers horizontally + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'text-size': 24, + 'icon-text-fit': 'height', + 'icon-text-fit-padding': [ 0, 0, 0, 0 ] + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -30, y: -10 }); + t.deepEqual(quads[0].tr, { x: -10, y: -10 }); + t.deepEqual(quads[0].bl, { x: -30, y: 30 }); + t.deepEqual(quads[0].br, { x: -10, y: 30 }); + t.end(); + }); + + t.test('icon-text-fit: height, x textSize', function(t) { + // - Uses text height (adjusted for textSize) + // - Preserves icon width, centers horizontally + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'text-size': 12, + 'icon-text-fit': 'height', + 'icon-text-fit-padding': [ 0, 0, 0, 0 ] + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -20, y: -5 }); + t.deepEqual(quads[0].tr, { x: 0, y: -5 }); + t.deepEqual(quads[0].bl, { x: -20, y: 15 }); + t.deepEqual(quads[0].br, { x: 0, y: 15 }); + t.end(); + }); + + t.test('icon-text-fit: height, x textSize, + padding', function(t) { + // - Uses text height (adjusted for textSize) + // - Preserves icon width, centers horizontally + // - Applies padding x, padding y + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'text-size': 12, + 'icon-text-fit': 'height', + 'icon-text-fit-padding': [ 5, 10, 5, 10 ] + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -30, y: -10 }); + t.deepEqual(quads[0].tr, { x: 10, y: -10 }); + t.deepEqual(quads[0].bl, { x: -30, y: 20 }); + t.deepEqual(quads[0].br, { x: 10, y: 20 }); + t.end(); + }); + + t.test('icon-text-fit: both', function(t) { + // - Uses text width + height + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'text-size': 24, + 'icon-text-fit': 'both', + 'icon-text-fit-padding': [ 0, 0, 0, 0 ] + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -60, y: -10 }); + t.deepEqual(quads[0].tr, { x: 20, y: -10 }); + t.deepEqual(quads[0].bl, { x: -60, y: 30 }); + t.deepEqual(quads[0].br, { x: 20, y: 30 }); + t.end(); + }); + + t.test('icon-text-fit: both, x textSize', function(t) { + // - Uses text width + height (adjusted for textSize) + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'text-size': 12, + 'icon-text-fit': 'both', + 'icon-text-fit-padding': [ 0, 0, 0, 0 ] + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -30, y: -5 }); + t.deepEqual(quads[0].tr, { x: 10, y: -5 }); + t.deepEqual(quads[0].bl, { x: -30, y: 15 }); + t.deepEqual(quads[0].br, { x: 10, y: 15 }); + t.end(); + }); + + t.test('icon-text-fit: both, x textSize, + padding', function(t) { + // - Uses text width + height (adjusted for textSize) + // - Applies padding x, padding y + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'text-size': 12, + 'icon-text-fit': 'both', + 'icon-text-fit-padding': [ 5, 10, 5, 10 ] + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -40, y: -10 }); + t.deepEqual(quads[0].tr, { x: 20, y: -10 }); + t.deepEqual(quads[0].bl, { x: -40, y: 20 }); + t.deepEqual(quads[0].br, { x: 20, y: 20 }); + t.end(); + }); + + t.test('icon-text-fit: both, padding t/r/b/l', function(t) { + // - Uses text width + height (adjusted for textSize) + // - Applies padding t/r/b/l + var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { + 'text-size': 12, + 'icon-text-fit': 'both', + 'icon-text-fit-padding': [ 0, 5, 10, 15 ] + }, false, fakeShapedText); + t.deepEqual(quads[0].tl, { x: -45, y: -5 }); + t.deepEqual(quads[0].tr, { x: 15, y: -5 }); + t.deepEqual(quads[0].bl, { x: -45, y: 25 }); + t.deepEqual(quads[0].br, { x: 15, y: 25 }); + t.end(); + }); + + t.end(); +}); +