Merge pull request #10527 from ahocevar/no-label-cache

Remove label cache, render text directly to target canvas
This commit is contained in:
Andreas Hocevar
2020-01-16 09:23:41 +01:00
committed by GitHub
11 changed files with 175 additions and 187 deletions
+38 -27
View File
@@ -5,7 +5,9 @@ import {getFontParameters} from '../css.js';
import {createCanvasContext2D} from '../dom.js'; import {createCanvasContext2D} from '../dom.js';
import {clear} from '../obj.js'; import {clear} from '../obj.js';
import {create as createTransform} from '../transform.js'; import {create as createTransform} from '../transform.js';
import LabelCache from './canvas/LabelCache.js'; import {executeLabelInstructions} from './canvas/Executor.js';
import BaseObject from '../Object.js';
import EventTarget from '../events/Target.js';
/** /**
@@ -164,21 +166,23 @@ export const defaultPadding = [0, 0, 0, 0];
*/ */
export const defaultLineWidth = 1; export const defaultLineWidth = 1;
/**
* @type {BaseObject}
*/
export const checkedFonts = new BaseObject();
/** /**
* The label cache for text rendering. To change the default cache size of 2048 * The label cache for text rendering. To change the default cache size of 2048
* entries, use {@link module:ol/structs/LRUCache#setSize}. * entries, use {@link module:ol/structs/LRUCache#setSize}.
* @type {LabelCache} * Deprecated - there is no label cache any more.
* @type {?}
* @api * @api
* @deprecated
*/ */
export const labelCache = new LabelCache(); export const labelCache = new EventTarget();
labelCache.setSize = function() {
console.warn('labelCache is deprecated.'); //eslint-disable-line
/** };
* @type {!Object<string, number>}
*/
export const checkedFonts = {};
/** /**
* @type {CanvasRenderingContext2D} * @type {CanvasRenderingContext2D}
@@ -200,9 +204,8 @@ export const textHeights = {};
* Clears the label cache when a font becomes available. * Clears the label cache when a font becomes available.
* @param {string} fontSpec CSS font spec. * @param {string} fontSpec CSS font spec.
*/ */
export const checkFont = (function() { export const registerFont = (function() {
const retries = 100; const retries = 100;
const checked = checkedFonts;
const size = '32px '; const size = '32px ';
const referenceFonts = ['monospace', 'serif']; const referenceFonts = ['monospace', 'serif'];
const len = referenceFonts.length; const len = referenceFonts.length;
@@ -235,19 +238,18 @@ export const checkFont = (function() {
function check() { function check() {
let done = true; let done = true;
for (const font in checked) { const fonts = checkedFonts.getKeys();
if (checked[font] < retries) { for (let i = 0, ii = fonts.length; i < ii; ++i) {
const font = fonts[i];
if (checkedFonts.get(font) < retries) {
if (isAvailable.apply(this, font.split('\n'))) { if (isAvailable.apply(this, font.split('\n'))) {
checked[font] = retries;
clear(textHeights); clear(textHeights);
// Make sure that loaded fonts are picked up by Safari // Make sure that loaded fonts are picked up by Safari
measureContext = null; measureContext = null;
measureFont = undefined; measureFont = undefined;
if (labelCache.getCount()) { checkedFonts.set(font, retries);
labelCache.clear();
}
} else { } else {
++checked[font]; checkedFonts.set(font, checkedFonts.get(font) + 1, true);
done = false; done = false;
} }
} }
@@ -267,10 +269,10 @@ export const checkFont = (function() {
for (let i = 0, ii = families.length; i < ii; ++i) { for (let i = 0, ii = families.length; i < ii; ++i) {
const family = families[i]; const family = families[i];
const key = font.style + '\n' + font.weight + '\n' + family; const key = font.style + '\n' + font.weight + '\n' + family;
if (!(key in checked)) { if (checkedFonts.get(key) === undefined) {
checked[key] = retries; checkedFonts.set(key, retries, true);
if (!isAvailable(font.style, font.weight, family)) { if (!isAvailable(font.style, font.weight, family)) {
checked[key] = 0; checkedFonts.set(key, 0, true);
if (interval === undefined) { if (interval === undefined) {
interval = setInterval(check, 32); interval = setInterval(check, 32);
} }
@@ -388,7 +390,7 @@ export const resetTransform = createTransform();
* @param {CanvasRenderingContext2D} context Context. * @param {CanvasRenderingContext2D} context Context.
* @param {import("../transform.js").Transform|null} transform Transform. * @param {import("../transform.js").Transform|null} transform Transform.
* @param {number} opacity Opacity. * @param {number} opacity Opacity.
* @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image. * @param {import("./canvas/Executor.js").Label|HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} labelOrImage Label.
* @param {number} originX Origin X. * @param {number} originX Origin X.
* @param {number} originY Origin Y. * @param {number} originY Origin Y.
* @param {number} w Width. * @param {number} w Width.
@@ -397,8 +399,8 @@ export const resetTransform = createTransform();
* @param {number} y Y. * @param {number} y Y.
* @param {number} scale Scale. * @param {number} scale Scale.
*/ */
export function drawImage(context, export function drawImageOrLabel(context,
transform, opacity, image, originX, originY, w, h, x, y, scale) { transform, opacity, labelOrImage, originX, originY, w, h, x, y, scale) {
let alpha; let alpha;
if (opacity != 1) { if (opacity != 1) {
alpha = context.globalAlpha; alpha = context.globalAlpha;
@@ -408,12 +410,21 @@ export function drawImage(context,
context.setTransform.apply(context, transform); context.setTransform.apply(context, transform);
} }
context.drawImage(image, originX, originY, w, h, x, y, w * scale, h * scale); const isLabel = !!(/** @type {*} */ (labelOrImage).contextInstructions);
if (isLabel) {
context.translate(x, y);
context.scale(scale, scale);
executeLabelInstructions(/** @type {import("./canvas/Executor.js").Label} */ (labelOrImage), context);
} else {
context.drawImage(/** @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} */ (labelOrImage), originX, originY, w, h, x, y, w * scale, h * scale);
}
if (opacity != 1) { if (opacity != 1) {
context.globalAlpha = alpha; context.globalAlpha = alpha;
} }
if (transform) {
if (transform || isLabel) {
context.setTransform.apply(context, resetTransform); context.setTransform.apply(context, resetTransform);
} }
} }
+98 -80
View File
@@ -7,7 +7,7 @@ import {createEmpty, createOrUpdate,
import {lineStringLength} from '../../geom/flat/length.js'; import {lineStringLength} from '../../geom/flat/length.js';
import {drawTextOnPath} from '../../geom/flat/textpath.js'; import {drawTextOnPath} from '../../geom/flat/textpath.js';
import {transform2D} from '../../geom/flat/transform.js'; import {transform2D} from '../../geom/flat/transform.js';
import {drawImage, defaultPadding, defaultTextBaseline} from '../canvas.js'; import {drawImageOrLabel, defaultPadding, defaultTextBaseline} from '../canvas.js';
import CanvasInstruction from './Instruction.js'; import CanvasInstruction from './Instruction.js';
import {TEXT_ALIGN} from './TextBuilder.js'; import {TEXT_ALIGN} from './TextBuilder.js';
import { import {
@@ -16,8 +16,7 @@ import {
apply as applyTransform, apply as applyTransform,
setFromArray as transformSetFromArray setFromArray as transformSetFromArray
} from '../../transform.js'; } from '../../transform.js';
import {createCanvasContext2D} from '../../dom.js'; import {defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
import RBush from 'rbush/rbush.js'; import RBush from 'rbush/rbush.js';
@@ -31,6 +30,28 @@ import RBush from 'rbush/rbush.js';
* @property {!Object<string, import("../canvas.js").StrokeState>} strokeStates The stroke states (decluttering). * @property {!Object<string, import("../canvas.js").StrokeState>} strokeStates The stroke states (decluttering).
*/ */
/**
* @typedef Label
* @property {number} width
* @property {number} height
* @property {Array<string|number>} contextInstructions
*/
/**
* @param {Label} label Label.
* @param {CanvasRenderingContext2D} context Context.
*/
export function executeLabelInstructions(label, context) {
const contextInstructions = label.contextInstructions;
for (let i = 0, ii = contextInstructions.length; i < ii; i += 2) {
if (Array.isArray(contextInstructions[i + 1])) {
CanvasRenderingContext2D.prototype[contextInstructions[i]].apply(context, contextInstructions[i + 1]);
} else {
context[contextInstructions[i]] = contextInstructions[i + 1];
}
}
}
/** /**
* @type {import("../../extent.js").Extent} * @type {import("../../extent.js").Extent}
*/ */
@@ -159,69 +180,66 @@ class Executor {
* @param {string} textKey Text style key. * @param {string} textKey Text style key.
* @param {string} fillKey Fill style key. * @param {string} fillKey Fill style key.
* @param {string} strokeKey Stroke style key. * @param {string} strokeKey Stroke style key.
* @return {HTMLCanvasElement} Image. * @return {Label} Label.
*/ */
getTextImage(text, textKey, fillKey, strokeKey) { createLabel(text, textKey, fillKey, strokeKey) {
let label; const strokeState = strokeKey ? this.strokeStates[strokeKey] : null;
const key = strokeKey + textKey + text + fillKey + this.pixelRatio; const fillState = fillKey ? this.fillStates[fillKey] : null;
const textState = this.textStates[textKey];
const pixelRatio = this.pixelRatio;
const scale = textState.scale * pixelRatio;
const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign];
const strokeWidth = strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0;
if (!labelCache.containsKey(key)) { const lines = text.split('\n');
const strokeState = strokeKey ? this.strokeStates[strokeKey] : null; const numLines = lines.length;
const fillState = fillKey ? this.fillStates[fillKey] : null; const widths = [];
const textState = this.textStates[textKey]; const width = measureTextWidths(textState.font, lines, widths);
const pixelRatio = this.pixelRatio; const lineHeight = measureTextHeight(textState.font);
const scale = textState.scale * pixelRatio; const height = lineHeight * numLines;
const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign]; const renderWidth = width + strokeWidth;
const strokeWidth = strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0; const contextInstructions = [];
/** @type {Label} */
const lines = text.split('\n'); const label = {
const numLines = lines.length; // make canvas 2 pixels wider to account for italic text width measurement errors
const widths = []; width: Math.ceil((renderWidth + 2) * scale),
const width = measureTextWidths(textState.font, lines, widths); height: Math.ceil((height + strokeWidth) * scale),
const lineHeight = measureTextHeight(textState.font); contextInstructions: contextInstructions
const height = lineHeight * numLines; };
const renderWidth = width + strokeWidth; if (scale != 1) {
const context = createCanvasContext2D( contextInstructions.push('scale', [scale, scale]);
// make canvas 2 pixels wider to account for italic text width measurement errors }
Math.ceil((renderWidth + 2) * scale), contextInstructions.push('font', textState.font);
Math.ceil((height + strokeWidth) * scale)); if (strokeKey) {
label = context.canvas; contextInstructions.push('strokeStyle', strokeState.strokeStyle);
labelCache.set(key, label); contextInstructions.push('lineWidth', strokeWidth);
if (scale != 1) { contextInstructions.push('lineCap', strokeState.lineCap);
context.scale(scale, scale); contextInstructions.push('lineJoin', strokeState.lineJoin);
} contextInstructions.push('miterLimit', strokeState.miterLimit);
context.font = textState.font; if (CanvasRenderingContext2D.prototype.setLineDash && strokeState.lineDash.length) {
if (strokeKey) { contextInstructions.push('setLineDash', [strokeState.lineDash]);
context.strokeStyle = strokeState.strokeStyle; contextInstructions.push('lineDashOffset', strokeState.lineDashOffset);
context.lineWidth = strokeWidth;
context.lineCap = strokeState.lineCap;
context.lineJoin = strokeState.lineJoin;
context.miterLimit = strokeState.miterLimit;
if (context.setLineDash && strokeState.lineDash.length) {
context.setLineDash(strokeState.lineDash);
context.lineDashOffset = strokeState.lineDashOffset;
}
}
if (fillKey) {
context.fillStyle = fillState.fillStyle;
}
context.textBaseline = 'middle';
context.textAlign = 'center';
const leftRight = (0.5 - align);
const x = align * renderWidth + leftRight * strokeWidth;
let i;
if (strokeKey) {
for (i = 0; i < numLines; ++i) {
context.strokeText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
}
}
if (fillKey) {
for (i = 0; i < numLines; ++i) {
context.fillText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
}
} }
} }
return labelCache.get(key, this); if (fillKey) {
contextInstructions.push('fillStyle', fillState.fillStyle);
}
contextInstructions.push('textBaseline', 'middle');
contextInstructions.push('textAlign', 'center');
const leftRight = (0.5 - align);
const x = align * renderWidth + leftRight * strokeWidth;
let i;
if (strokeKey) {
for (i = 0; i < numLines; ++i) {
contextInstructions.push('strokeText', [lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight]);
}
}
if (fillKey) {
for (i = 0; i < numLines; ++i) {
contextInstructions.push('fillText', [lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight]);
}
}
return label;
} }
/** /**
@@ -254,7 +272,7 @@ class Executor {
* @param {CanvasRenderingContext2D} context Context. * @param {CanvasRenderingContext2D} context Context.
* @param {number} x X. * @param {number} x X.
* @param {number} y Y. * @param {number} y Y.
* @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image. * @param {Label|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} imageOrLabel Image.
* @param {number} anchorX Anchor X. * @param {number} anchorX Anchor X.
* @param {number} anchorY Anchor Y. * @param {number} anchorY Anchor Y.
* @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group.
@@ -270,11 +288,11 @@ class Executor {
* @param {Array<*>} fillInstruction Fill instruction. * @param {Array<*>} fillInstruction Fill instruction.
* @param {Array<*>} strokeInstruction Stroke instruction. * @param {Array<*>} strokeInstruction Stroke instruction.
*/ */
replayImage_( replayImageOrLabel_(
context, context,
x, x,
y, y,
image, imageOrLabel,
anchorX, anchorX,
anchorY, anchorY,
declutterGroup, declutterGroup,
@@ -296,8 +314,8 @@ class Executor {
x -= anchorX; x -= anchorX;
y -= anchorY; y -= anchorY;
const w = (width + originX > image.width) ? image.width - originX : width; const w = (width + originX > imageOrLabel.width) ? imageOrLabel.width - originX : width;
const h = (height + originY > image.height) ? image.height - originY : height; const h = (height + originY > imageOrLabel.height) ? imageOrLabel.height - originY : height;
const boxW = padding[3] + w * scale + padding[1]; const boxW = padding[3] + w * scale + padding[1];
const boxH = padding[0] + h * scale + padding[2]; const boxH = padding[0] + h * scale + padding[2];
const boxX = x - padding[3]; const boxX = x - padding[3];
@@ -351,7 +369,7 @@ class Executor {
} }
extend(declutterGroup, tmpExtent); extend(declutterGroup, tmpExtent);
const declutterArgs = intersects ? const declutterArgs = intersects ?
[context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] : [context, transform ? transform.slice(0) : null, opacity, imageOrLabel, originX, originY, w, h, x, y, scale] :
null; null;
if (declutterArgs) { if (declutterArgs) {
if (fillStroke) { if (fillStroke) {
@@ -365,7 +383,7 @@ class Executor {
/** @type {Array<*>} */ (fillInstruction), /** @type {Array<*>} */ (fillInstruction),
/** @type {Array<*>} */ (strokeInstruction)); /** @type {Array<*>} */ (strokeInstruction));
} }
drawImage(context, transform, opacity, image, originX, originY, w, h, x, y, scale); drawImageOrLabel(context, transform, opacity, imageOrLabel, originX, originY, w, h, x, y, scale);
} }
} }
@@ -440,7 +458,7 @@ class Executor {
declutterData[13], declutterData[14], declutterData[15], declutterData[16], declutterData[13], declutterData[14], declutterData[15], declutterData[16],
declutterData[11], declutterData[12]); declutterData[11], declutterData[12]);
} }
drawImage.apply(undefined, declutterData); drawImageOrLabel.apply(undefined, declutterData);
if (currentAlpha !== opacity) { if (currentAlpha !== opacity) {
context.globalAlpha = currentAlpha; context.globalAlpha = currentAlpha;
} }
@@ -459,12 +477,12 @@ class Executor {
* @param {string} textKey The key of the text state. * @param {string} textKey The key of the text state.
* @param {string} strokeKey The key for the stroke state. * @param {string} strokeKey The key for the stroke state.
* @param {string} fillKey The key for the fill state. * @param {string} fillKey The key for the fill state.
* @return {{label: HTMLCanvasElement, anchorX: number, anchorY: number}} The text image and its anchor. * @return {{label: Label, anchorX: number, anchorY: number}} The text image and its anchor.
*/ */
drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey) { drawLabelWithPointPlacement_(text, textKey, strokeKey, fillKey) {
const textState = this.textStates[textKey]; const textState = this.textStates[textKey];
const label = this.getTextImage(text, textKey, fillKey, strokeKey); const label = this.createLabel(text, textKey, fillKey, strokeKey);
const strokeState = this.strokeStates[strokeKey]; const strokeState = this.strokeStates[strokeKey];
const pixelRatio = this.pixelRatio; const pixelRatio = this.pixelRatio;
@@ -472,7 +490,7 @@ class Executor {
const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline]; const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline];
const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0; const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;
// Remove the 2 pixels we added in getTextImage() for the anchor // Remove the 2 pixels we added in createLabel() for the anchor
const width = label.width / pixelRatio - 2 * textState.scale; const width = label.width / pixelRatio - 2 * textState.scale;
const anchorX = align * width + 2 * (0.5 - align) * strokeWidth; const anchorX = align * width + 2 * (0.5 - align) * strokeWidth;
const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth; const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth;
@@ -637,7 +655,7 @@ class Executor {
textKey = /** @type {string} */ (instruction[19]); textKey = /** @type {string} */ (instruction[19]);
strokeKey = /** @type {string} */ (instruction[20]); strokeKey = /** @type {string} */ (instruction[20]);
fillKey = /** @type {string} */ (instruction[21]); fillKey = /** @type {string} */ (instruction[21]);
const labelWithAnchor = this.drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey); const labelWithAnchor = this.drawLabelWithPointPlacement_(text, textKey, strokeKey, fillKey);
image = labelWithAnchor.label; image = labelWithAnchor.label;
instruction[3] = image; instruction[3] = image;
const textOffsetX = /** @type {number} */ (instruction[22]); const textOffsetX = /** @type {number} */ (instruction[22]);
@@ -690,7 +708,7 @@ class Executor {
} }
declutterGroup = declutterGroups[index]; declutterGroup = declutterGroups[index];
} }
this.replayImage_(context, this.replayImageOrLabel_(context,
pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY, pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY,
declutterGroup, height, opacity, originX, originY, rotation, scale, declutterGroup, height, opacity, originX, originY, rotation, scale,
snapToPixel, width, padding, snapToPixel, width, padding,
@@ -747,10 +765,10 @@ class Executor {
for (c = 0, cc = parts.length; c < cc; ++c) { for (c = 0, cc = parts.length; c < cc; ++c) {
part = parts[c]; // x, y, anchorX, rotation, chunk part = parts[c]; // x, y, anchorX, rotation, chunk
chars = /** @type {string} */ (part[4]); chars = /** @type {string} */ (part[4]);
label = this.getTextImage(chars, textKey, '', strokeKey); label = this.createLabel(chars, textKey, '', strokeKey);
anchorX = /** @type {number} */ (part[2]) + strokeWidth; anchorX = /** @type {number} */ (part[2]) + strokeWidth;
anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY; anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY;
this.replayImage_(context, this.replayImageOrLabel_(context,
/** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label, /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
/** @type {number} */ (part[3]), pixelRatioScale, false, label.width, /** @type {number} */ (part[3]), pixelRatioScale, false, label.width,
@@ -761,10 +779,10 @@ class Executor {
for (c = 0, cc = parts.length; c < cc; ++c) { for (c = 0, cc = parts.length; c < cc; ++c) {
part = parts[c]; // x, y, anchorX, rotation, chunk part = parts[c]; // x, y, anchorX, rotation, chunk
chars = /** @type {string} */ (part[4]); chars = /** @type {string} */ (part[4]);
label = this.getTextImage(chars, textKey, fillKey, ''); label = this.createLabel(chars, textKey, fillKey, '');
anchorX = /** @type {number} */ (part[2]); anchorX = /** @type {number} */ (part[2]);
anchorY = baseline * label.height - offsetY; anchorY = baseline * label.height - offsetY;
this.replayImage_(context, this.replayImageOrLabel_(context,
/** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label, /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
/** @type {number} */ (part[3]), pixelRatioScale, false, label.width, /** @type {number} */ (part[3]), pixelRatioScale, false, label.width,
-20
View File
@@ -1,20 +0,0 @@
import LRUCache from '../../structs/LRUCache.js';
/**
* @module ol/render/canvas/LabelCache
*/
/**
* @classdesc
* Cache of pre-rendered labels.
*/
class LabelCache extends LRUCache {
expireCache() {
while (this.canExpireCache()) {
this.pop();
}
}
}
export default LabelCache;
+2 -3
View File
@@ -6,7 +6,7 @@ import {asColorLike} from '../../colorlike.js';
import {intersects} from '../../extent.js'; import {intersects} from '../../extent.js';
import {matchingChunk} from '../../geom/flat/straightchunk.js'; import {matchingChunk} from '../../geom/flat/straightchunk.js';
import GeometryType from '../../geom/GeometryType.js'; import GeometryType from '../../geom/GeometryType.js';
import {labelCache, defaultTextAlign, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; import {defaultTextAlign, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, registerFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js';
import CanvasInstruction from './Instruction.js'; import CanvasInstruction from './Instruction.js';
import CanvasBuilder from './Builder.js'; import CanvasBuilder from './Builder.js';
import TextPlacement from '../../style/TextPlacement.js'; import TextPlacement from '../../style/TextPlacement.js';
@@ -138,7 +138,6 @@ class CanvasTextBuilder extends CanvasBuilder {
*/ */
finish() { finish() {
const instructions = super.finish(); const instructions = super.finish();
labelCache.expireCache();
instructions.textStates = this.textStates; instructions.textStates = this.textStates;
instructions.fillStates = this.fillStates; instructions.fillStates = this.fillStates;
instructions.strokeStates = this.strokeStates; instructions.strokeStates = this.strokeStates;
@@ -432,7 +431,7 @@ class CanvasTextBuilder extends CanvasBuilder {
textState = this.textState_; textState = this.textState_;
const font = textStyle.getFont() || defaultFont; const font = textStyle.getFont() || defaultFont;
checkFont(font); registerFont(font);
const textScale = textStyle.getScale(); const textScale = textStyle.getScale();
textState.overflow = textStyle.getOverflow(); textState.overflow = textStyle.getOverflow();
textState.font = font; textState.font = font;
+4 -4
View File
@@ -8,9 +8,9 @@ import RenderEventType from '../render/EventType.js';
import MapRenderer from './Map.js'; import MapRenderer from './Map.js';
import SourceState from '../source/State.js'; import SourceState from '../source/State.js';
import {replaceChildren} from '../dom.js'; import {replaceChildren} from '../dom.js';
import {labelCache} from '../render/canvas.js';
import EventType from '../events/EventType.js';
import {listen, unlistenByKey} from '../events.js'; import {listen, unlistenByKey} from '../events.js';
import {checkedFonts} from '../render/canvas.js';
import ObjectEventType from '../ObjectEventType.js';
/** /**
@@ -29,7 +29,7 @@ class CompositeMapRenderer extends MapRenderer {
/** /**
* @type {import("../events.js").EventsKey} * @type {import("../events.js").EventsKey}
*/ */
this.labelCacheKey_ = listen(labelCache, EventType.CLEAR, map.redrawText.bind(map)); this.fontChangeListenerKey_ = listen(checkedFonts, ObjectEventType.PROPERTYCHANGE, map.redrawText.bind(map));
/** /**
* @private * @private
@@ -73,7 +73,7 @@ class CompositeMapRenderer extends MapRenderer {
} }
disposeInternal() { disposeInternal() {
unlistenByKey(this.labelCacheKey_); unlistenByKey(this.fontChangeListenerKey_);
this.element_.parentNode.removeChild(this.element_); this.element_.parentNode.removeChild(this.element_);
super.disposeInternal(); super.disposeInternal();
} }
+1 -6
View File
@@ -3,8 +3,6 @@
*/ */
import {assert} from '../asserts.js'; import {assert} from '../asserts.js';
import EventTarget from '../events/Target.js';
import EventType from '../events/EventType.js';
/** /**
@@ -25,15 +23,13 @@ import EventType from '../events/EventType.js';
* @fires import("../events/Event.js").default * @fires import("../events/Event.js").default
* @template T * @template T
*/ */
class LRUCache extends EventTarget { class LRUCache {
/** /**
* @param {number=} opt_highWaterMark High water mark. * @param {number=} opt_highWaterMark High water mark.
*/ */
constructor(opt_highWaterMark) { constructor(opt_highWaterMark) {
super();
/** /**
* @type {number} * @type {number}
*/ */
@@ -82,7 +78,6 @@ class LRUCache extends EventTarget {
this.entries_ = {}; this.entries_ = {};
this.oldest_ = null; this.oldest_ = null;
this.newest_ = null; this.newest_ = null;
this.dispatchEvent(EventType.CLEAR);
} }
+22 -22
View File
@@ -1,4 +1,3 @@
import {clear} from '../../../../../src/ol/obj.js';
import * as render from '../../../../../src/ol/render/canvas.js'; import * as render from '../../../../../src/ol/render/canvas.js';
@@ -9,69 +8,70 @@ describe('ol.render.canvas', function() {
font.rel = 'stylesheet'; font.rel = 'stylesheet';
const head = document.getElementsByTagName('head')[0]; const head = document.getElementsByTagName('head')[0];
describe('ol.render.canvas.checkFont()', function() { describe('ol.render.canvas.registerFont()', function() {
beforeEach(function() { beforeEach(function() {
clear(render.checkedFonts); render.checkedFonts.values_ = {};
render.measureTextHeight('12px sans-serif'); render.measureTextHeight('12px sans-serif');
}); });
const retries = 100; const retries = 100;
it('does not clear label cache and measurements for unavailable fonts', function(done) { it('does not trigger redraw and clear measurements for unavailable fonts', function(done) {
this.timeout(4000); this.timeout(4000);
const spy = sinon.spy(); const spy = sinon.spy();
render.labelCache.addEventListener('clear', spy); render.checkedFonts.addEventListener('propertychange', spy);
const interval = setInterval(function() { const interval = setInterval(function() {
if (render.checkedFonts['normal\nnormal\nfoo'] == retries && render.checkedFonts['normal\nnormal\nsans-serif'] == retries) { if (render.checkedFonts.get('normal\nnormal\nfoo') == retries && render.checkedFonts.get('normal\nnormal\nsans-serif') == retries) {
clearInterval(interval); clearInterval(interval);
render.labelCache.removeEventListener('clear', spy); render.checkedFonts.removeEventListener('propertychange', spy);
expect(spy.callCount).to.be(0); expect(spy.callCount).to.be(0);
expect(render.textHeights).to.not.eql({}); expect(render.textHeights).to.not.eql({});
done(); done();
} }
}, 32); }, 32);
render.checkFont('12px foo,sans-serif'); render.registerFont('12px foo,sans-serif');
}); });
it('does not clear label cache and measurements for available fonts', function(done) { it('does not trigger redraw and clear measurements for available fonts', function(done) {
const spy = sinon.spy(); const spy = sinon.spy();
render.labelCache.addEventListener('clear', spy); render.checkedFonts.addEventListener('propertychange', spy);
const interval = setInterval(function() { const interval = setInterval(function() {
if (render.checkedFonts['normal\nnormal\nsans-serif'] == retries) { if (render.checkedFonts.get('normal\nnormal\nsans-serif') == retries) {
clearInterval(interval); clearInterval(interval);
render.labelCache.removeEventListener('clear', spy); render.checkedFonts.removeEventListener('propertychange', spy);
expect(spy.callCount).to.be(0); expect(spy.callCount).to.be(0);
expect(render.textHeights).to.not.eql({}); expect(render.textHeights).to.not.eql({});
done(); done();
} }
}, 32); }, 32);
render.checkFont('12px sans-serif'); render.registerFont('12px sans-serif');
}); });
it('does not clear label cache and measurements for the \'monospace\' font', function(done) { it('does not trigger redraw and clear measurements for the \'monospace\' font', function(done) {
const spy = sinon.spy(); const spy = sinon.spy();
render.labelCache.addEventListener('clear', spy); render.checkedFonts.addEventListener('propertychange', spy);
const interval = setInterval(function() { const interval = setInterval(function() {
if (render.checkedFonts['normal\nnormal\nmonospace'] == retries) { if (render.checkedFonts.get('normal\nnormal\nmonospace') == retries) {
clearInterval(interval); clearInterval(interval);
render.labelCache.removeEventListener('clear', spy); render.checkedFonts.removeEventListener('propertychange', spy);
expect(spy.callCount).to.be(0); expect(spy.callCount).to.be(0);
expect(render.textHeights).to.not.eql({}); expect(render.textHeights).to.not.eql({});
done(); done();
} }
}, 32); }, 32);
render.checkFont('12px monospace'); render.registerFont('12px monospace');
}); });
it('clears label cache and measurements for fonts that become available', function(done) { it('triggers redraw and clear measurements for fonts that become available', function(done) {
head.appendChild(font); head.appendChild(font);
render.labelCache.set('dummy', {}); render.checkedFonts.addEventListener('propertychange', function onPropertyChange(e) {
render.labelCache.addEventListener('clear', function() { render.checkedFonts.removeEventListener('propertychange', onPropertyChange);
expect(e.key).to.be('normal\nnormal\nAbel');
expect(render.textHeights).to.eql({}); expect(render.textHeights).to.eql({});
done(); done();
}); });
render.checkFont('12px Abel'); render.registerFont('12px Abel');
}); });
}); });
@@ -1,13 +0,0 @@
import LabelCache from '../../../../../src/ol/render/canvas/LabelCache.js';
describe('ol.render.canvas.LabelCache', function() {
it('#expireCache()', function() {
const labelCache = new LabelCache(1);
labelCache.set('key1', document.createElement('canvas'));
labelCache.set('key2', document.createElement('canvas'));
labelCache.expireCache();
expect(labelCache.getCount()).to.be(1);
});
});
@@ -29,11 +29,11 @@ function executeInstructions(builder, expectedDrawTextImageCalls, expectedBuilde
const transform = createTransform(); const transform = createTransform();
const context = createContext(); const context = createContext();
const executor = new Executor(0.02, 1, false, builder.finish()); const executor = new Executor(0.02, 1, false, builder.finish());
sinon.spy(executor, 'drawTextImageWithPointPlacement_'); sinon.spy(executor, 'drawLabelWithPointPlacement_');
const replayImageStub = sinon.stub(executor, 'replayImage_'); const replayImageOrLabelStub = sinon.stub(executor, 'replayImageOrLabel_');
executor.execute(context, transform); executor.execute(context, transform);
expect(executor.drawTextImageWithPointPlacement_.callCount).to.be(expectedDrawTextImageCalls); expect(executor.drawLabelWithPointPlacement_.callCount).to.be(expectedDrawTextImageCalls);
expect(replayImageStub.callCount).to.be(expectedBuilderImageCalls); expect(replayImageOrLabelStub.callCount).to.be(expectedBuilderImageCalls);
} }
describe('ol.render.canvas.TextBuilder', function() { describe('ol.render.canvas.TextBuilder', function() {
@@ -6,7 +6,6 @@ import Circle from '../../../../../src/ol/geom/Circle.js';
import Point from '../../../../../src/ol/geom/Point.js'; import Point from '../../../../../src/ol/geom/Point.js';
import {fromExtent} from '../../../../../src/ol/geom/Polygon.js'; import {fromExtent} from '../../../../../src/ol/geom/Polygon.js';
import VectorLayer from '../../../../../src/ol/layer/Vector.js'; import VectorLayer from '../../../../../src/ol/layer/Vector.js';
import {clear} from '../../../../../src/ol/obj.js';
import {get as getProjection} from '../../../../../src/ol/proj.js'; import {get as getProjection} from '../../../../../src/ol/proj.js';
import {checkedFonts} from '../../../../../src/ol/render/canvas.js'; import {checkedFonts} from '../../../../../src/ol/render/canvas.js';
import CanvasVectorLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorLayer.js'; import CanvasVectorLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorLayer.js';
@@ -88,7 +87,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
}); });
it('does not re-render for unavailable fonts', function(done) { it('does not re-render for unavailable fonts', function(done) {
clear(checkedFonts); checkedFonts.values_ = {};
const map = new Map({ const map = new Map({
view: new View({ view: new View({
center: [0, 0], center: [0, 0],
@@ -119,7 +118,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
}); });
it('does not re-render for available fonts', function(done) { it('does not re-render for available fonts', function(done) {
clear(checkedFonts); checkedFonts.values_ = {};
const map = new Map({ const map = new Map({
view: new View({ view: new View({
center: [0, 0], center: [0, 0],
@@ -150,7 +149,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
}); });
it('re-renders for fonts that become available', function(done) { it('re-renders for fonts that become available', function(done) {
clear(checkedFonts); checkedFonts.values_ = {};
head.appendChild(font); head.appendChild(font);
const map = new Map({ const map = new Map({
view: new View({ view: new View({
@@ -1,4 +1,3 @@
import {clear} from '../../../../../src/ol/obj.js';
import Feature from '../../../../../src/ol/Feature.js'; import Feature from '../../../../../src/ol/Feature.js';
import Map from '../../../../../src/ol/Map.js'; import Map from '../../../../../src/ol/Map.js';
import TileState from '../../../../../src/ol/TileState.js'; import TileState from '../../../../../src/ol/TileState.js';
@@ -171,7 +170,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
it('does not re-render for unavailable fonts', function(done) { it('does not re-render for unavailable fonts', function(done) {
map.renderSync(); map.renderSync();
clear(checkedFonts); checkedFonts.values_ = {};
layerStyle[0].getText().setFont('12px "Unavailable font",sans-serif'); layerStyle[0].getText().setFont('12px "Unavailable font",sans-serif');
layer.changed(); layer.changed();
const revision = layer.getRevision(); const revision = layer.getRevision();
@@ -183,7 +182,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
it('does not re-render for available fonts', function(done) { it('does not re-render for available fonts', function(done) {
map.renderSync(); map.renderSync();
clear(checkedFonts); checkedFonts.values_ = {};
layerStyle[0].getText().setFont('12px sans-serif'); layerStyle[0].getText().setFont('12px sans-serif');
layer.changed(); layer.changed();
const revision = layer.getRevision(); const revision = layer.getRevision();
@@ -195,7 +194,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
it('re-renders for fonts that become available', function(done) { it('re-renders for fonts that become available', function(done) {
map.renderSync(); map.renderSync();
clear(checkedFonts); checkedFonts.values_ = {};
head.appendChild(font); head.appendChild(font);
layerStyle[0].getText().setFont('12px "Dancing Script",sans-serif'); layerStyle[0].getText().setFont('12px "Dancing Script",sans-serif');
layer.changed(); layer.changed();