From 489261fdef87746db58619c99e8aa84eac5977f7 Mon Sep 17 00:00:00 2001 From: Young Hahn Date: Sat, 11 Jun 2016 16:25:43 -0400 Subject: [PATCH 1/4] Add support for icon-text-fit, icon-text-fit-padding --- js/data/bucket/symbol_bucket.js | 2 +- js/symbol/quads.js | 37 ++++++++++++++++++++++++++++----- package.json | 2 +- 3 files changed, 34 insertions(+), 7 deletions(-) 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..df49ab9664f 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,36 @@ 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, + padX = layout['icon-text-fit-padding'][0], + padY = layout['icon-text-fit-padding'][1], + 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 - padX, textTop + offsetY - padY); + tr = new Point(textLeft + offsetX + padX + width, textTop + offsetY - padY); + br = new Point(textLeft + offsetX + padX + width, textTop + offsetY + padY + height); + bl = new Point(textLeft + offsetX - padX, textTop + offsetY + padY + 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 58be1704e8a..4ec1f7f0913 100644 --- a/package.json +++ b/package.json @@ -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#db1c5cdbff05dcc0580534a0f43195f1d40c2205", "nyc": "6.4.0", "remark": "4.2.2", "remark-html": "3.0.0", From fd002ef51017a54c16a51e069243aa82c036aed1 Mon Sep 17 00:00:00 2001 From: Young Hahn Date: Sat, 11 Jun 2016 17:54:20 -0400 Subject: [PATCH 2/4] Add unit tests for icon-text-fit support in getIconQuads() --- test/js/symbol/quads.test.js | 173 +++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/test/js/symbol/quads.test.js b/test/js/symbol/quads.test.js index 32df909791d..a72191df40c 100644 --- a/test/js/symbol/quads.test.js +++ b/test/js/symbol/quads.test.js @@ -54,3 +54,176 @@ 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 ] + }, 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 ] + }, 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': [ 10, 5 ] + }, 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 ] + }, 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 ] + }, 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': [ 10, 5 ] + }, 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 ] + }, 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 ] + }, 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', 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': [ 10, 5 ] + }, 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.end(); +}); + From 8ea91a635078a2e56111437bea1dbfbc3ca071ba Mon Sep 17 00:00:00 2001 From: Young Hahn Date: Mon, 13 Jun 2016 17:19:18 -0400 Subject: [PATCH 3/4] Add support for padding in all 4 directions. --- js/symbol/quads.js | 14 ++++++++------ package.json | 4 ++-- test/js/symbol/quads.test.js | 35 +++++++++++++++++++++++++---------- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/js/symbol/quads.js b/js/symbol/quads.js index df49ab9664f..1b720c79c18 100644 --- a/js/symbol/quads.js +++ b/js/symbol/quads.js @@ -77,16 +77,18 @@ function getIconQuads(anchor, shapedIcon, boxScale, line, layout, alongLine, sha textBottom = shapedText.bottom * size, textWidth = textRight - textLeft, textHeight = textBottom - textTop, - padX = layout['icon-text-fit-padding'][0], - padY = layout['icon-text-fit-padding'][1], + 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 - padX, textTop + offsetY - padY); - tr = new Point(textLeft + offsetX + padX + width, textTop + offsetY - padY); - br = new Point(textLeft + offsetX + padX + width, textTop + offsetY + padY + height); - bl = new Point(textLeft + offsetX - padX, textTop + offsetY + padY + height); + 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); diff --git a/package.json b/package.json index 4ec1f7f0913..a5bbc12fa6b 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "grid-index": "^0.1.0", "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-shaders": "mapbox/mapbox-gl-shaders#09ee512cd59a8fb1a241c78833b7c8022bf4f263", + "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#b19dd370b02dfb58cc02820fba0ad6dac3bf78a0", "minifyify": "^7.0.1", "pbf": "^1.3.2", "pngjs": "^2.2.0", diff --git a/test/js/symbol/quads.test.js b/test/js/symbol/quads.test.js index a72191df40c..c12417a7ef0 100644 --- a/test/js/symbol/quads.test.js +++ b/test/js/symbol/quads.test.js @@ -95,7 +95,7 @@ test('getIconQuads text-fit', function(t) { var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { 'text-size': 24, 'icon-text-fit': 'width', - 'icon-text-fit-padding': [ 0, 0 ] + '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 }); @@ -110,7 +110,7 @@ test('getIconQuads text-fit', function(t) { var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { 'text-size': 12, 'icon-text-fit': 'width', - 'icon-text-fit-padding': [ 0, 0 ] + '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 }); @@ -126,7 +126,7 @@ test('getIconQuads text-fit', function(t) { var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { 'text-size': 12, 'icon-text-fit': 'width', - 'icon-text-fit-padding': [ 10, 5 ] + '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 }); @@ -141,7 +141,7 @@ test('getIconQuads text-fit', function(t) { var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { 'text-size': 24, 'icon-text-fit': 'height', - 'icon-text-fit-padding': [ 0, 0 ] + '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 }); @@ -156,7 +156,7 @@ test('getIconQuads text-fit', function(t) { var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { 'text-size': 12, 'icon-text-fit': 'height', - 'icon-text-fit-padding': [ 0, 0 ] + '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 }); @@ -172,7 +172,7 @@ test('getIconQuads text-fit', function(t) { var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { 'text-size': 12, 'icon-text-fit': 'height', - 'icon-text-fit-padding': [ 10, 5 ] + '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 }); @@ -186,7 +186,7 @@ test('getIconQuads text-fit', function(t) { var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { 'text-size': 24, 'icon-text-fit': 'both', - 'icon-text-fit-padding': [ 0, 0 ] + '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 }); @@ -200,7 +200,7 @@ test('getIconQuads text-fit', function(t) { var quads = getIconQuads(anchor, fakeShapedIcon, 2, [], { 'text-size': 12, 'icon-text-fit': 'both', - 'icon-text-fit-padding': [ 0, 0 ] + '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 }); @@ -209,13 +209,13 @@ test('getIconQuads text-fit', function(t) { t.end(); }); - t.test('icon-text-fit: both, x textSize', function(t) { + 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': [ 10, 5 ] + '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 }); @@ -224,6 +224,21 @@ test('getIconQuads text-fit', function(t) { 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(); }); From de0666d6d81033bf785e2b8d6bf233e655db0d28 Mon Sep 17 00:00:00 2001 From: Young Hahn Date: Wed, 15 Jun 2016 12:41:59 -0400 Subject: [PATCH 4/4] Update all hashes post-merge --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a5bbc12fa6b..fd9ca5168cd 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "grid-index": "^0.1.0", "mapbox-gl-function": "^1.2.1", "mapbox-gl-js-supported": "^1.1.0", - "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#09ee512cd59a8fb1a241c78833b7c8022bf4f263", - "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#b19dd370b02dfb58cc02820fba0ad6dac3bf78a0", + "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#59e998295d548f208ee3ec10cdd21ff2630e2079", + "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#db1c5cdbff05dcc0580534a0f43195f1d40c2205", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#146a348f1768ce13e153fce3b32bbed469aa5fe4", "nyc": "6.4.0", "remark": "4.2.2", "remark-html": "3.0.0",