From 8b50c3c6cbc37c89eaa0f7ea4a471368196eb4f2 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 19 Nov 2018 15:34:48 -0700 Subject: [PATCH] Pass along the ability to measure and cache text width --- src/ol/geom/flat/textpath.js | 10 +++++---- src/ol/render/canvas.js | 16 +++++++++++++++ src/ol/render/canvas/Executor.js | 27 +++++++++---------------- test/spec/ol/geom/flat/textpath.test.js | 26 ++++++++++++------------ 4 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/ol/geom/flat/textpath.js b/src/ol/geom/flat/textpath.js index f358d8791e..a4941a8467 100644 --- a/src/ol/geom/flat/textpath.js +++ b/src/ol/geom/flat/textpath.js @@ -10,15 +10,17 @@ import {lerp} from '../../math.js'; * @param {number} end End offset of the `flatCoordinates`. * @param {number} stride Stride. * @param {string} text Text to place on the path. - * @param {function(string):number} measure Measure function returning the - * width of the character passed as 1st argument. * @param {number} startM m along the path where the text starts. * @param {number} maxAngle Max angle between adjacent chars in radians. + * @param {number} scale The product of the text scale and the device pixel ratio. + * @param {function(string, string, Object):number} measureAndCacheTextWidth Measure and cache text width. + * @param {string} font The font. + * @param {Object} cache A cache of measured widths. * @return {Array>} The result array of null if `maxAngle` was * exceeded. Entries of the array are x, y, anchorX, angle, chunk. */ export function drawTextOnPath( - flatCoordinates, offset, end, stride, text, measure, startM, maxAngle) { + flatCoordinates, offset, end, stride, text, startM, maxAngle, scale, measureAndCacheTextWidth, font, cache) { const result = []; // Keep text upright @@ -41,7 +43,7 @@ export function drawTextOnPath( index = reverse ? numChars - i - 1 : i; const char = text.charAt(index); chunk = reverse ? char + chunk : chunk + char; - const charLength = measure(chunk) - chunkLength; + const charLength = scale * measureAndCacheTextWidth(font, chunk, cache) - chunkLength; chunkLength += charLength; const charM = startM + charLength / 2; while (offset < end - stride && segmentM + segmentLength < charM) { diff --git a/src/ol/render/canvas.js b/src/ol/render/canvas.js index 43bdc72fc0..bf6e314614 100644 --- a/src/ol/render/canvas.js +++ b/src/ol/render/canvas.js @@ -324,6 +324,22 @@ export function measureTextWidth(font, text) { } +/** + * Measure text width using a cache. + * @param {string} font The font. + * @param {string} text The text to measure. + * @param {Object} cache A lookup of cached widths by text. + * @returns {number} The text width. + */ +export function measureAndCacheTextWidth(font, text, cache) { + if (text in cache) { + return cache[text]; + } + const width = cache[text] = measureTextWidth(font, text); + return width; +} + + /** * @param {string} font Font to use for measuring. * @param {Array} lines Lines to measure. diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index cb018d1e42..e4b5d43f93 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -19,10 +19,8 @@ import { apply as applyTransform, setFromArray as transformSetFromArray } from '../../transform.js'; - - import {createCanvasContext2D} from '../../dom.js'; -import {labelCache, defaultTextAlign, measureTextHeight, measureTextWidth, measureTextWidths} from '../canvas.js'; +import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js'; /** @@ -736,29 +734,22 @@ class CanvasExecutor { const textState = this.textStates[textKey]; const font = textState.font; - const textScale = textState.scale; + const textScale = textState.scale * measurePixelRatio; - let widths = this.widths_[font]; - if (!widths) { - this.widths_[font] = widths = {}; + let cachedWidths; + if (font in this.widths_) { + cachedWidths = this.widths_[font]; + } else { + cachedWidths = this.widths_[font] = {}; } - //FIXME Do not create this function on every call - const measure = function(text) { - let width = widths[text]; - if (!width) { - width = widths[text] = measureTextWidth(font, text); - } - return width * textScale * measurePixelRatio; - }; - const pathLength = lineStringLength(pixelCoordinates, begin, end, 2); - const textLength = measure(text); + const textLength = textScale * measureAndCacheTextWidth(font, text, cachedWidths); if (overflow || textLength <= pathLength) { const textAlign = this.textStates[textKey].textAlign; const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign]; const parts = drawTextOnPath( - pixelCoordinates, begin, end, 2, text, measure, startM, maxAngle); + pixelCoordinates, begin, end, 2, text, startM, maxAngle, textScale, measureAndCacheTextWidth, font, cachedWidths); if (parts) { let c, cc, chars, label, part; if (strokeKey) { diff --git a/test/spec/ol/geom/flat/textpath.test.js b/test/spec/ol/geom/flat/textpath.test.js index b0e3581ae5..8755bfe680 100644 --- a/test/spec/ol/geom/flat/textpath.test.js +++ b/test/spec/ol/geom/flat/textpath.test.js @@ -10,34 +10,34 @@ describe('ol.geom.flat.drawTextOnPath', function() { const angled = [0, 0, 100, 100, 200, 0]; const reverseangled = [151, 17, 163, 22, 159, 30, 150, 30, 143, 24, 151, 17]; - function measure(text) { + function measureAndCacheTextWidth(font, text, cache) { return 10 * text.length; } it('center-aligns text on a horizontal line', function() { const startM = 50 - 15; const instructions = drawTextOnPath( - horizontal, 0, horizontal.length, 2, 'foo', measure, startM, Infinity); + horizontal, 0, horizontal.length, 2, 'foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {}); expect(instructions).to.eql([[40, 0, 5, 0, 'foo']]); }); it('left-aligns text on a horizontal line', function() { const instructions = drawTextOnPath( - horizontal, 0, horizontal.length, 2, 'foo', measure, 0, Infinity); + horizontal, 0, horizontal.length, 2, 'foo', 0, Infinity, 1, measureAndCacheTextWidth, '', {}); expect(instructions).to.eql([[5, 0, 5, 0, 'foo']]); }); it('right-aligns text on a horizontal line', function() { const startM = 100 - 30; const instructions = drawTextOnPath( - horizontal, 0, horizontal.length, 2, 'foo', measure, startM, Infinity); + horizontal, 0, horizontal.length, 2, 'foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {}); expect(instructions).to.eql([[75, 0, 5, 0, 'foo']]); }); it('draws text on a vertical line', function() { const startM = 50 - 15; const instructions = drawTextOnPath( - vertical, 0, vertical.length, 2, 'foo', measure, startM, Infinity); + vertical, 0, vertical.length, 2, 'foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {}); const a = 90 * Math.PI / 180; expect(instructions).to.eql([[0, 40, 5, a, 'foo']]); }); @@ -45,7 +45,7 @@ describe('ol.geom.flat.drawTextOnPath', function() { it('draws text on a diagonal line', function() { const startM = Math.sqrt(2) * 50 - 15; const instructions = drawTextOnPath( - diagonal, 0, diagonal.length, 2, 'foo', measure, startM, Infinity); + diagonal, 0, diagonal.length, 2, 'foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {}); expect(instructions[0][3]).to.be(45 * Math.PI / 180); expect(instructions.length).to.be(1); }); @@ -53,7 +53,7 @@ describe('ol.geom.flat.drawTextOnPath', function() { it('draws reverse text on a diagonal line', function() { const startM = Math.sqrt(2) * 50 - 15; const instructions = drawTextOnPath( - reverse, 0, reverse.length, 2, 'foo', measure, startM, Infinity); + reverse, 0, reverse.length, 2, 'foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {}); expect(instructions[0][3]).to.be(-45 * Math.PI / 180); expect(instructions.length).to.be(1); }); @@ -61,7 +61,7 @@ describe('ol.geom.flat.drawTextOnPath', function() { it('renders long text with extrapolation', function() { const startM = 50 - 75; const instructions = drawTextOnPath( - horizontal, 0, horizontal.length, 2, 'foo-foo-foo-foo', measure, startM, Infinity); + horizontal, 0, horizontal.length, 2, 'foo-foo-foo-foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {}); expect(instructions[0]).to.eql([-20, 0, 5, 0, 'foo-foo-foo-foo']); expect(instructions.length).to.be(1); }); @@ -70,7 +70,7 @@ describe('ol.geom.flat.drawTextOnPath', function() { const length = lineStringLength(angled, 0, angled.length, 2); const startM = length / 2 - 15; const instructions = drawTextOnPath( - angled, 0, angled.length, 2, 'foo', measure, startM, Infinity); + angled, 0, angled.length, 2, 'foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {}); expect(instructions[0][3]).to.eql(45 * Math.PI / 180); expect(instructions[0][4]).to.be('fo'); expect(instructions[1][3]).to.eql(-45 * Math.PI / 180); @@ -81,7 +81,7 @@ describe('ol.geom.flat.drawTextOnPath', function() { const length = lineStringLength(angled, 0, angled.length, 2); const startM = length / 2 - 15; const instructions = drawTextOnPath( - angled, 0, angled.length, 2, 'foo', measure, startM, Math.PI / 4); + angled, 0, angled.length, 2, 'foo', startM, Math.PI / 4, 1, measureAndCacheTextWidth, '', {}); expect(instructions).to.be(null); }); @@ -89,7 +89,7 @@ describe('ol.geom.flat.drawTextOnPath', function() { const length = lineStringLength(reverseangled, 0, reverseangled.length, 2); const startM = length / 2 - 15; const instructions = drawTextOnPath( - reverseangled, 0, reverseangled.length, 2, 'foo', measure, startM, Math.PI); + reverseangled, 0, reverseangled.length, 2, 'foo', startM, Math.PI, 1, measureAndCacheTextWidth, '', {}); expect(instructions).to.not.be(undefined); }); @@ -97,7 +97,7 @@ describe('ol.geom.flat.drawTextOnPath', function() { const length = lineStringLength(angled, 2, angled.length, 2); const startM = length / 2 - 15; const instructions = drawTextOnPath( - angled, 2, angled.length, 2, 'foo', measure, startM, Infinity); + angled, 2, angled.length, 2, 'foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {}); expect(instructions[0][3]).to.be(-45 * Math.PI / 180); expect(instructions.length).to.be(1); }); @@ -106,7 +106,7 @@ describe('ol.geom.flat.drawTextOnPath', function() { const length = lineStringLength(angled, 0, 4, 2); const startM = length / 2 - 15; const instructions = drawTextOnPath( - angled, 0, 4, 2, 'foo', measure, startM, Infinity); + angled, 0, 4, 2, 'foo', startM, Infinity, 1, measureAndCacheTextWidth, '', {}); expect(instructions[0][3]).to.be(45 * Math.PI / 180); expect(instructions.length).to.be(1); });