diff --git a/src/canvas/textFeature.js b/src/canvas/textFeature.js index e85930182c..620e6a893a 100644 --- a/src/canvas/textFeature.js +++ b/src/canvas/textFeature.js @@ -99,11 +99,13 @@ var canvas_textFeature = function (arg) { this._renderOnCanvas = function (context2d, map) { var data = m_this.data(), posFunc = m_this.style.get('position'), + renderThreshold = m_this.style.get('renderThreshold')(data), textFunc = m_this.style.get('text'), mapRotation = map.rotation(), mapZoom = map.zoom(), - fontFromSubValues, text, pos, visible, color, blur, stroke, width, - rotation, rotateWithMap, scale, offset, + mapSize = map.size(), + fontFromSubValues, text, posArray, pos, visible, color, blur, stroke, + width, rotation, rotateWithMap, scale, offset, transform, lastTransform = util.mat3AsArray(); /* If any of the font styles other than `font` have values, then we need to @@ -117,7 +119,17 @@ var canvas_textFeature = function (arg) { }); /* Clear the canvas property buffer */ m_this._canvasProperty(); + posArray = m_this.featureGcsToDisplay(data.map(posFunc)); data.forEach(function (d, i) { + /* If the position is far enough outside of the map viewport, don't + * render it, even if the offset of size would be sufficient to make it + * appear in the viewport. */ + pos = posArray[i]; + if (renderThreshold > 0 && ( + pos.x < -renderThreshold || pos.x > mapSize.width + renderThreshold || + pos.y < -renderThreshold || pos.y > mapSize.height + renderThreshold)) { + return; + } visible = m_this.style.get('visible')(d, i); if (!visible && visible !== undefined) { return; @@ -130,10 +142,6 @@ var canvas_textFeature = function (arg) { return; } m_this._canvasProperty(context2d, 'fillStyle', util.convertColorToRGBA(color)); - // TODO: get the position position without transform. If it is outside - // of the map to an extent that there is no chance of text showing, - // skip further processing. - pos = m_this.featureGcsToDisplay(posFunc(d, i)); text = textFunc(d, i); m_this._canvasProperty(context2d, 'font', m_this.getFontFromStyles(fontFromSubValues, d, i)); m_this._canvasProperty(context2d, 'textAlign', m_this.style.get('textAlign')(d, i) || 'center'); diff --git a/src/textFeature.js b/src/textFeature.js index 8b2db4a31d..cddd10cda2 100644 --- a/src/textFeature.js +++ b/src/textFeature.js @@ -60,6 +60,12 @@ var feature = require('./feature'); * stroke color. May include opacity. * @property {geo.geoColor|function} [style.textStrokeWidth=0] Text stroke * width in pixels. + * @property {number|function} [style.renderThreshold] If this is a positive + * number, text elements may not be rendered if their base position + * (before offset and font effects are applied) is more than this distance + * in pixels outside of the current viewport. If it is known that such + * text elements cannot affect the current viewport, setting this can + * speed up rendering. This is computed once for the whole feature. */ /** diff --git a/tests/cases/textFeature.js b/tests/cases/textFeature.js index a44c5d8b34..b9cf4afdef 100644 --- a/tests/cases/textFeature.js +++ b/tests/cases/textFeature.js @@ -198,5 +198,36 @@ describe('geo.textFeature', function () { expect(text.getFontFromStyles(true, text.data()[3], 3)).toBe('italic small-caps bold condensed 40px/60px serif'); expect(text.getFontFromStyles(true, text.data()[11], 11)).toBe('bold 16px/60px sans-serif'); }); + it('renderThreshold', function () { + mockAnimationFrame(); + logCanvas2D(); + map = createMap(); + layer = map.createLayer('feature', {renderer: 'canvas'}); + text = layer.createFeature('text').data(testText); + text.draw(); + stepAnimationFrame(); + var count1 = $.extend({}, window._canvasLog.counts).fillText; + text.style({renderThreshold: 0}); + text.draw(); + stepAnimationFrame(); + var count2 = $.extend({}, window._canvasLog.counts).fillText; + expect(count2 - count1).toBe(14); + text.style({renderThreshold: 1}); + text.draw(); + stepAnimationFrame(); + var count3 = $.extend({}, window._canvasLog.counts).fillText; + expect(count3 - count2).toBe(6); + text.style({renderThreshold: 20}); + text.draw(); + stepAnimationFrame(); + var count4 = $.extend({}, window._canvasLog.counts).fillText; + expect(count4 - count3).toBe(8); + text.style({renderThreshold: 0}); + text.draw(); + stepAnimationFrame(); + var count5 = $.extend({}, window._canvasLog.counts).fillText; + expect(count5 - count4).toBe(14); + unmockAnimationFrame(); + }); }); });