Merge pull request #8988 from tschaub/measure-and-cache-text-width
Pass along the ability to measure and cache text width
This commit is contained in:
@@ -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<string, number>):number} measureAndCacheTextWidth Measure and cache text width.
|
||||
* @param {string} font The font.
|
||||
* @param {Object<string, number>} cache A cache of measured widths.
|
||||
* @return {Array<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) {
|
||||
|
||||
@@ -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<string, number>} 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<string>} lines Lines to measure.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user