From fd935bae53151658f3c718263b64d17a47968906 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Mon, 13 Jan 2020 21:52:55 +0100 Subject: [PATCH] Remove label cache, render text directly to target canvas --- src/ol/render/canvas.js | 65 ++++--- src/ol/render/canvas/Executor.js | 178 ++++++++++-------- src/ol/render/canvas/LabelCache.js | 20 -- src/ol/render/canvas/TextBuilder.js | 5 +- src/ol/renderer/Composite.js | 8 +- src/ol/structs/LRUCache.js | 7 +- test/spec/ol/render/canvas/index.test.js | 44 ++--- test/spec/ol/render/canvas/labelcache.test.js | 13 -- .../spec/ol/render/canvas/textbuilder.test.js | 8 +- .../ol/renderer/canvas/vectorlayer.test.js | 7 +- .../renderer/canvas/vectortilelayer.test.js | 7 +- 11 files changed, 175 insertions(+), 187 deletions(-) delete mode 100644 src/ol/render/canvas/LabelCache.js delete mode 100644 test/spec/ol/render/canvas/labelcache.test.js diff --git a/src/ol/render/canvas.js b/src/ol/render/canvas.js index d345568a64..d4c7498451 100644 --- a/src/ol/render/canvas.js +++ b/src/ol/render/canvas.js @@ -5,7 +5,9 @@ import {getFontParameters} from '../css.js'; import {createCanvasContext2D} from '../dom.js'; import {clear} from '../obj.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; +/** + * @type {BaseObject} + */ +export const checkedFonts = new BaseObject(); /** * The label cache for text rendering. To change the default cache size of 2048 * entries, use {@link module:ol/structs/LRUCache#setSize}. - * @type {LabelCache} + * Deprecated - there is no label cache any more. + * @type {?} * @api + * @deprecated */ -export const labelCache = new LabelCache(); - - -/** - * @type {!Object} - */ -export const checkedFonts = {}; - +export const labelCache = new EventTarget(); +labelCache.setSize = function() { + console.warn('labelCache is deprecated.'); //eslint-disable-line +}; /** * @type {CanvasRenderingContext2D} @@ -200,9 +204,8 @@ export const textHeights = {}; * Clears the label cache when a font becomes available. * @param {string} fontSpec CSS font spec. */ -export const checkFont = (function() { +export const registerFont = (function() { const retries = 100; - const checked = checkedFonts; const size = '32px '; const referenceFonts = ['monospace', 'serif']; const len = referenceFonts.length; @@ -235,19 +238,18 @@ export const checkFont = (function() { function check() { let done = true; - for (const font in checked) { - if (checked[font] < retries) { + const fonts = checkedFonts.getKeys(); + 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'))) { - checked[font] = retries; clear(textHeights); // Make sure that loaded fonts are picked up by Safari measureContext = null; measureFont = undefined; - if (labelCache.getCount()) { - labelCache.clear(); - } + checkedFonts.set(font, retries); } else { - ++checked[font]; + checkedFonts.set(font, checkedFonts.get(font) + 1, true); done = false; } } @@ -267,10 +269,10 @@ export const checkFont = (function() { for (let i = 0, ii = families.length; i < ii; ++i) { const family = families[i]; const key = font.style + '\n' + font.weight + '\n' + family; - if (!(key in checked)) { - checked[key] = retries; + if (checkedFonts.get(key) === undefined) { + checkedFonts.set(key, retries, true); if (!isAvailable(font.style, font.weight, family)) { - checked[key] = 0; + checkedFonts.set(key, 0, true); if (interval === undefined) { interval = setInterval(check, 32); } @@ -388,7 +390,7 @@ export const resetTransform = createTransform(); * @param {CanvasRenderingContext2D} context Context. * @param {import("../transform.js").Transform|null} transform Transform. * @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} originY Origin Y. * @param {number} w Width. @@ -397,8 +399,8 @@ export const resetTransform = createTransform(); * @param {number} y Y. * @param {number} scale Scale. */ -export function drawImage(context, - transform, opacity, image, originX, originY, w, h, x, y, scale) { +export function drawImageOrLabel(context, + transform, opacity, labelOrImage, originX, originY, w, h, x, y, scale) { let alpha; if (opacity != 1) { alpha = context.globalAlpha; @@ -408,12 +410,21 @@ export function drawImage(context, 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) { context.globalAlpha = alpha; } - if (transform) { + + if (transform || isLabel) { context.setTransform.apply(context, resetTransform); } } diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index 5ec354c5b0..e94f666554 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -7,7 +7,7 @@ import {createEmpty, createOrUpdate, import {lineStringLength} from '../../geom/flat/length.js'; import {drawTextOnPath} from '../../geom/flat/textpath.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 {TEXT_ALIGN} from './TextBuilder.js'; import { @@ -16,8 +16,7 @@ import { apply as applyTransform, setFromArray as transformSetFromArray } from '../../transform.js'; -import {createCanvasContext2D} from '../../dom.js'; -import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js'; +import {defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js'; import RBush from 'rbush/rbush.js'; @@ -31,6 +30,28 @@ import RBush from 'rbush/rbush.js'; * @property {!Object} strokeStates The stroke states (decluttering). */ +/** + * @typedef Label + * @property {number} width + * @property {number} height + * @property {Array} 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} */ @@ -159,69 +180,66 @@ class Executor { * @param {string} textKey Text style key. * @param {string} fillKey Fill style key. * @param {string} strokeKey Stroke style key. - * @return {HTMLCanvasElement} Image. + * @return {Label} Label. */ - getTextImage(text, textKey, fillKey, strokeKey) { - let label; - const key = strokeKey + textKey + text + fillKey + this.pixelRatio; + createLabel(text, textKey, fillKey, strokeKey) { + const strokeState = strokeKey ? this.strokeStates[strokeKey] : null; + 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 strokeState = strokeKey ? this.strokeStates[strokeKey] : null; - 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; - - const lines = text.split('\n'); - const numLines = lines.length; - const widths = []; - const width = measureTextWidths(textState.font, lines, widths); - const lineHeight = measureTextHeight(textState.font); - const height = lineHeight * numLines; - const renderWidth = width + strokeWidth; - const context = createCanvasContext2D( - // make canvas 2 pixels wider to account for italic text width measurement errors - Math.ceil((renderWidth + 2) * scale), - Math.ceil((height + strokeWidth) * scale)); - label = context.canvas; - labelCache.set(key, label); - if (scale != 1) { - context.scale(scale, scale); - } - context.font = textState.font; - if (strokeKey) { - context.strokeStyle = strokeState.strokeStyle; - 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); - } + const lines = text.split('\n'); + const numLines = lines.length; + const widths = []; + const width = measureTextWidths(textState.font, lines, widths); + const lineHeight = measureTextHeight(textState.font); + const height = lineHeight * numLines; + const renderWidth = width + strokeWidth; + const contextInstructions = []; + /** @type {Label} */ + const label = { + // make canvas 2 pixels wider to account for italic text width measurement errors + width: Math.ceil((renderWidth + 2) * scale), + height: Math.ceil((height + strokeWidth) * scale), + contextInstructions: contextInstructions + }; + if (scale != 1) { + contextInstructions.push('scale', [scale, scale]); + } + contextInstructions.push('font', textState.font); + if (strokeKey) { + contextInstructions.push('strokeStyle', strokeState.strokeStyle); + contextInstructions.push('lineWidth', strokeWidth); + contextInstructions.push('lineCap', strokeState.lineCap); + contextInstructions.push('lineJoin', strokeState.lineJoin); + contextInstructions.push('miterLimit', strokeState.miterLimit); + if (CanvasRenderingContext2D.prototype.setLineDash && strokeState.lineDash.length) { + contextInstructions.push('setLineDash', [strokeState.lineDash]); + contextInstructions.push('lineDashOffset', strokeState.lineDashOffset); } } - 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 {number} x X. * @param {number} y Y. - * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image. + * @param {Label|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} imageOrLabel Image. * @param {number} anchorX Anchor X. * @param {number} anchorY Anchor Y. * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. @@ -270,11 +288,11 @@ class Executor { * @param {Array<*>} fillInstruction Fill instruction. * @param {Array<*>} strokeInstruction Stroke instruction. */ - replayImage_( + replayImageOrLabel_( context, x, y, - image, + imageOrLabel, anchorX, anchorY, declutterGroup, @@ -296,8 +314,8 @@ class Executor { x -= anchorX; y -= anchorY; - const w = (width + originX > image.width) ? image.width - originX : width; - const h = (height + originY > image.height) ? image.height - originY : height; + const w = (width + originX > imageOrLabel.width) ? imageOrLabel.width - originX : width; + const h = (height + originY > imageOrLabel.height) ? imageOrLabel.height - originY : height; const boxW = padding[3] + w * scale + padding[1]; const boxH = padding[0] + h * scale + padding[2]; const boxX = x - padding[3]; @@ -351,7 +369,7 @@ class Executor { } extend(declutterGroup, tmpExtent); 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; if (declutterArgs) { if (fillStroke) { @@ -365,7 +383,7 @@ class Executor { /** @type {Array<*>} */ (fillInstruction), /** @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[11], declutterData[12]); } - drawImage.apply(undefined, declutterData); + drawImageOrLabel.apply(undefined, declutterData); if (currentAlpha !== opacity) { context.globalAlpha = currentAlpha; } @@ -459,12 +477,12 @@ class Executor { * @param {string} textKey The key of the text state. * @param {string} strokeKey The key for the stroke 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 label = this.getTextImage(text, textKey, fillKey, strokeKey); + const label = this.createLabel(text, textKey, fillKey, strokeKey); const strokeState = this.strokeStates[strokeKey]; const pixelRatio = this.pixelRatio; @@ -472,7 +490,7 @@ class Executor { const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline]; 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 anchorX = align * width + 2 * (0.5 - align) * strokeWidth; const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth; @@ -637,7 +655,7 @@ class Executor { textKey = /** @type {string} */ (instruction[19]); strokeKey = /** @type {string} */ (instruction[20]); fillKey = /** @type {string} */ (instruction[21]); - const labelWithAnchor = this.drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey); + const labelWithAnchor = this.drawLabelWithPointPlacement_(text, textKey, strokeKey, fillKey); image = labelWithAnchor.label; instruction[3] = image; const textOffsetX = /** @type {number} */ (instruction[22]); @@ -690,7 +708,7 @@ class Executor { } declutterGroup = declutterGroups[index]; } - this.replayImage_(context, + this.replayImageOrLabel_(context, pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY, declutterGroup, height, opacity, originX, originY, rotation, scale, snapToPixel, width, padding, @@ -747,10 +765,10 @@ class Executor { for (c = 0, cc = parts.length; c < cc; ++c) { part = parts[c]; // x, y, anchorX, rotation, chunk chars = /** @type {string} */ (part[4]); - label = this.getTextImage(chars, textKey, '', strokeKey); + label = this.createLabel(chars, textKey, '', strokeKey); anchorX = /** @type {number} */ (part[2]) + strokeWidth; 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, anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, /** @type {number} */ (part[3]), pixelRatioScale, false, label.width, @@ -761,10 +779,10 @@ class Executor { for (c = 0, cc = parts.length; c < cc; ++c) { part = parts[c]; // x, y, anchorX, rotation, chunk chars = /** @type {string} */ (part[4]); - label = this.getTextImage(chars, textKey, fillKey, ''); + label = this.createLabel(chars, textKey, fillKey, ''); anchorX = /** @type {number} */ (part[2]); anchorY = baseline * label.height - offsetY; - this.replayImage_(context, + this.replayImageOrLabel_(context, /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label, anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, /** @type {number} */ (part[3]), pixelRatioScale, false, label.width, diff --git a/src/ol/render/canvas/LabelCache.js b/src/ol/render/canvas/LabelCache.js deleted file mode 100644 index 37de689738..0000000000 --- a/src/ol/render/canvas/LabelCache.js +++ /dev/null @@ -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; diff --git a/src/ol/render/canvas/TextBuilder.js b/src/ol/render/canvas/TextBuilder.js index c14b3ce367..aec72113ad 100644 --- a/src/ol/render/canvas/TextBuilder.js +++ b/src/ol/render/canvas/TextBuilder.js @@ -6,7 +6,7 @@ import {asColorLike} from '../../colorlike.js'; import {intersects} from '../../extent.js'; import {matchingChunk} from '../../geom/flat/straightchunk.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 CanvasBuilder from './Builder.js'; import TextPlacement from '../../style/TextPlacement.js'; @@ -138,7 +138,6 @@ class CanvasTextBuilder extends CanvasBuilder { */ finish() { const instructions = super.finish(); - labelCache.expireCache(); instructions.textStates = this.textStates; instructions.fillStates = this.fillStates; instructions.strokeStates = this.strokeStates; @@ -432,7 +431,7 @@ class CanvasTextBuilder extends CanvasBuilder { textState = this.textState_; const font = textStyle.getFont() || defaultFont; - checkFont(font); + registerFont(font); const textScale = textStyle.getScale(); textState.overflow = textStyle.getOverflow(); textState.font = font; diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index 12cce9ba7c..24ee0ef801 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -8,9 +8,9 @@ import RenderEventType from '../render/EventType.js'; import MapRenderer from './Map.js'; import SourceState from '../source/State.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 {checkedFonts} from '../render/canvas.js'; +import ObjectEventType from '../ObjectEventType.js'; /** @@ -29,7 +29,7 @@ class CompositeMapRenderer extends MapRenderer { /** * @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 @@ -73,7 +73,7 @@ class CompositeMapRenderer extends MapRenderer { } disposeInternal() { - unlistenByKey(this.labelCacheKey_); + unlistenByKey(this.fontChangeListenerKey_); this.element_.parentNode.removeChild(this.element_); super.disposeInternal(); } diff --git a/src/ol/structs/LRUCache.js b/src/ol/structs/LRUCache.js index 0e57889820..be36c25717 100644 --- a/src/ol/structs/LRUCache.js +++ b/src/ol/structs/LRUCache.js @@ -3,8 +3,6 @@ */ 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 * @template T */ -class LRUCache extends EventTarget { +class LRUCache { /** * @param {number=} opt_highWaterMark High water mark. */ constructor(opt_highWaterMark) { - super(); - /** * @type {number} */ @@ -82,7 +78,6 @@ class LRUCache extends EventTarget { this.entries_ = {}; this.oldest_ = null; this.newest_ = null; - this.dispatchEvent(EventType.CLEAR); } diff --git a/test/spec/ol/render/canvas/index.test.js b/test/spec/ol/render/canvas/index.test.js index f2d4d23ca1..58a4a5551d 100644 --- a/test/spec/ol/render/canvas/index.test.js +++ b/test/spec/ol/render/canvas/index.test.js @@ -1,4 +1,3 @@ -import {clear} from '../../../../../src/ol/obj.js'; import * as render from '../../../../../src/ol/render/canvas.js'; @@ -9,69 +8,70 @@ describe('ol.render.canvas', function() { font.rel = 'stylesheet'; const head = document.getElementsByTagName('head')[0]; - describe('ol.render.canvas.checkFont()', function() { + describe('ol.render.canvas.registerFont()', function() { beforeEach(function() { - clear(render.checkedFonts); + render.checkedFonts.values_ = {}; render.measureTextHeight('12px sans-serif'); }); 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); const spy = sinon.spy(); - render.labelCache.addEventListener('clear', spy); + render.checkedFonts.addEventListener('propertychange', spy); 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); - render.labelCache.removeEventListener('clear', spy); + render.checkedFonts.removeEventListener('propertychange', spy); expect(spy.callCount).to.be(0); expect(render.textHeights).to.not.eql({}); done(); } }, 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(); - render.labelCache.addEventListener('clear', spy); + render.checkedFonts.addEventListener('propertychange', spy); const interval = setInterval(function() { - if (render.checkedFonts['normal\nnormal\nsans-serif'] == retries) { + if (render.checkedFonts.get('normal\nnormal\nsans-serif') == retries) { clearInterval(interval); - render.labelCache.removeEventListener('clear', spy); + render.checkedFonts.removeEventListener('propertychange', spy); expect(spy.callCount).to.be(0); expect(render.textHeights).to.not.eql({}); done(); } }, 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(); - render.labelCache.addEventListener('clear', spy); + render.checkedFonts.addEventListener('propertychange', spy); const interval = setInterval(function() { - if (render.checkedFonts['normal\nnormal\nmonospace'] == retries) { + if (render.checkedFonts.get('normal\nnormal\nmonospace') == retries) { clearInterval(interval); - render.labelCache.removeEventListener('clear', spy); + render.checkedFonts.removeEventListener('propertychange', spy); expect(spy.callCount).to.be(0); expect(render.textHeights).to.not.eql({}); done(); } }, 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); - render.labelCache.set('dummy', {}); - render.labelCache.addEventListener('clear', function() { + render.checkedFonts.addEventListener('propertychange', function onPropertyChange(e) { + render.checkedFonts.removeEventListener('propertychange', onPropertyChange); + expect(e.key).to.be('normal\nnormal\nAbel'); expect(render.textHeights).to.eql({}); done(); }); - render.checkFont('12px Abel'); + render.registerFont('12px Abel'); }); }); diff --git a/test/spec/ol/render/canvas/labelcache.test.js b/test/spec/ol/render/canvas/labelcache.test.js deleted file mode 100644 index d6c5fb8531..0000000000 --- a/test/spec/ol/render/canvas/labelcache.test.js +++ /dev/null @@ -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); - }); - -}); diff --git a/test/spec/ol/render/canvas/textbuilder.test.js b/test/spec/ol/render/canvas/textbuilder.test.js index 850134b675..c4f90fe651 100644 --- a/test/spec/ol/render/canvas/textbuilder.test.js +++ b/test/spec/ol/render/canvas/textbuilder.test.js @@ -29,11 +29,11 @@ function executeInstructions(builder, expectedDrawTextImageCalls, expectedBuilde const transform = createTransform(); const context = createContext(); const executor = new Executor(0.02, 1, false, builder.finish()); - sinon.spy(executor, 'drawTextImageWithPointPlacement_'); - const replayImageStub = sinon.stub(executor, 'replayImage_'); + sinon.spy(executor, 'drawLabelWithPointPlacement_'); + const replayImageOrLabelStub = sinon.stub(executor, 'replayImageOrLabel_'); executor.execute(context, transform); - expect(executor.drawTextImageWithPointPlacement_.callCount).to.be(expectedDrawTextImageCalls); - expect(replayImageStub.callCount).to.be(expectedBuilderImageCalls); + expect(executor.drawLabelWithPointPlacement_.callCount).to.be(expectedDrawTextImageCalls); + expect(replayImageOrLabelStub.callCount).to.be(expectedBuilderImageCalls); } describe('ol.render.canvas.TextBuilder', function() { diff --git a/test/spec/ol/renderer/canvas/vectorlayer.test.js b/test/spec/ol/renderer/canvas/vectorlayer.test.js index 280fc4d8c6..a766fdb767 100644 --- a/test/spec/ol/renderer/canvas/vectorlayer.test.js +++ b/test/spec/ol/renderer/canvas/vectorlayer.test.js @@ -6,7 +6,6 @@ import Circle from '../../../../../src/ol/geom/Circle.js'; import Point from '../../../../../src/ol/geom/Point.js'; import {fromExtent} from '../../../../../src/ol/geom/Polygon.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 {checkedFonts} from '../../../../../src/ol/render/canvas.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) { - clear(checkedFonts); + checkedFonts.values_ = {}; const map = new Map({ view: new View({ center: [0, 0], @@ -119,7 +118,7 @@ describe('ol.renderer.canvas.VectorLayer', function() { }); it('does not re-render for available fonts', function(done) { - clear(checkedFonts); + checkedFonts.values_ = {}; const map = new Map({ view: new View({ center: [0, 0], @@ -150,7 +149,7 @@ describe('ol.renderer.canvas.VectorLayer', function() { }); it('re-renders for fonts that become available', function(done) { - clear(checkedFonts); + checkedFonts.values_ = {}; head.appendChild(font); const map = new Map({ view: new View({ diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js index 27abdf7871..718187bac3 100644 --- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js +++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js @@ -1,4 +1,3 @@ -import {clear} from '../../../../../src/ol/obj.js'; import Feature from '../../../../../src/ol/Feature.js'; import Map from '../../../../../src/ol/Map.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) { map.renderSync(); - clear(checkedFonts); + checkedFonts.values_ = {}; layerStyle[0].getText().setFont('12px "Unavailable font",sans-serif'); layer.changed(); const revision = layer.getRevision(); @@ -183,7 +182,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { it('does not re-render for available fonts', function(done) { map.renderSync(); - clear(checkedFonts); + checkedFonts.values_ = {}; layerStyle[0].getText().setFont('12px sans-serif'); layer.changed(); const revision = layer.getRevision(); @@ -195,7 +194,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { it('re-renders for fonts that become available', function(done) { map.renderSync(); - clear(checkedFonts); + checkedFonts.values_ = {}; head.appendChild(font); layerStyle[0].getText().setFont('12px "Dancing Script",sans-serif'); layer.changed();