From ecf79a9ec271a87221989bb8336eba84d336a274 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Tue, 13 Nov 2018 15:41:50 +0100 Subject: [PATCH 01/30] First step in uncoupling replay creation and rendering Signed-off-by: Guillaume Beraudo --- src/ol/render/canvas/ImageReplay.js | 1 + src/ol/render/canvas/LineStringReplay.js | 1 + src/ol/render/canvas/PolygonReplay.js | 1 + src/ol/render/canvas/Replay.js | 30 +++++++++++++++++++++-- src/ol/render/canvas/ReplayGroup.js | 24 ++++++++++++++++-- src/ol/render/canvas/TextReplay.js | 20 +++++++++++++++ src/ol/renderer/canvas/VectorLayer.js | 9 +++++-- src/ol/renderer/canvas/VectorTileLayer.js | 7 ++++-- 8 files changed, 85 insertions(+), 8 deletions(-) diff --git a/src/ol/render/canvas/ImageReplay.js b/src/ol/render/canvas/ImageReplay.js index 807ea149c4..e7f921889e 100644 --- a/src/ol/render/canvas/ImageReplay.js +++ b/src/ol/render/canvas/ImageReplay.js @@ -185,6 +185,7 @@ class CanvasImageReplay extends CanvasReplay { this.rotateWithView_ = undefined; this.rotation_ = undefined; this.width_ = undefined; + return super.finish(); } /** diff --git a/src/ol/render/canvas/LineStringReplay.js b/src/ol/render/canvas/LineStringReplay.js index 57822418ae..8aed895832 100644 --- a/src/ol/render/canvas/LineStringReplay.js +++ b/src/ol/render/canvas/LineStringReplay.js @@ -97,6 +97,7 @@ class CanvasLineStringReplay extends CanvasReplay { } this.reverseHitDetectionInstructions(); this.state = null; + return super.finish(); } /** diff --git a/src/ol/render/canvas/PolygonReplay.js b/src/ol/render/canvas/PolygonReplay.js index 1175a58fde..6d9c87b007 100644 --- a/src/ol/render/canvas/PolygonReplay.js +++ b/src/ol/render/canvas/PolygonReplay.js @@ -192,6 +192,7 @@ class CanvasPolygonReplay extends CanvasReplay { coordinates[i] = snap(coordinates[i], tolerance); } } + return super.finish(); } /** diff --git a/src/ol/render/canvas/Replay.js b/src/ol/render/canvas/Replay.js index 079f60575a..6b2b871057 100644 --- a/src/ol/render/canvas/Replay.js +++ b/src/ol/render/canvas/Replay.js @@ -28,6 +28,16 @@ import { } from '../../transform.js'; +/** + * @typedef {Object} SerializableInstructions + * @property {Array<*>} instructions The rendering instructions. + * @property {Array<*>} hitDetectionInstructions The rendering hit detection instructions. + * @property {Array} coordinates The array of all coordinates. + * @property {!Object} textStates The text states (decluttering). + * @property {!Object} fillStates The fill states (decluttering). + * @property {!Object} strokeStates The stoke states (decluttering). + */ + /** * @type {import("../../extent.js").Extent} */ @@ -169,6 +179,16 @@ class CanvasReplay extends VectorContext { } + /** + * Recreate replays and populate them using the provided instructions. + * @param {SerializableInstructions} instructions The serializable instructions + */ + replaceInstructions(instructions) { + this.instructions = instructions.instructions; + this.hitDetectionInstructions = instructions.hitDetectionInstructions; + this.coordinates = instructions.coordinates; + } + /** * @param {CanvasRenderingContext2D} context Context. * @param {import("../../coordinate.js").Coordinate} p1 1st point of the background box. @@ -456,9 +476,15 @@ class CanvasReplay extends VectorContext { } /** - * FIXME empty description for jsdoc + * @return {Object} the serializable instructions. */ - finish() {} + finish() { + return { + instructions: this.instructions, + hitDetectionInstructions: this.hitDetectionInstructions, + coordinates: this.coordinates + }; + } /** * @private diff --git a/src/ol/render/canvas/ReplayGroup.js b/src/ol/render/canvas/ReplayGroup.js index fa7aaaf89d..6e52ee59a8 100644 --- a/src/ol/render/canvas/ReplayGroup.js +++ b/src/ol/render/canvas/ReplayGroup.js @@ -150,6 +150,22 @@ class CanvasReplayGroup extends ReplayGroup { context.clip(); } + /** + * Recreate replays and populate them using the provided instructions. + * @param {!Object>} allInstructions The serializable instructions + */ + replaceInstructions(allInstructions) { + this.replaysByZIndex_ = {}; + for (const zIndex in allInstructions) { + const instructionByZindex = allInstructions[zIndex]; + for (const replayType in instructionByZindex) { + const instructions = instructionByZindex[replayType]; + const replay = this.getReplay(zIndex, replayType); + replay.replaceInstructions(instructions); + } + } + } + /** * @param {Array} replays Replays. * @return {boolean} Has replays of the provided types. @@ -167,15 +183,19 @@ class CanvasReplayGroup extends ReplayGroup { } /** - * FIXME empty description for jsdoc + * @return {!Object>} The serializable instructions */ finish() { + const replaysInstructions = {}; for (const zKey in this.replaysByZIndex_) { + replaysInstructions[zKey] = replaysInstructions[zKey] || {}; const replays = this.replaysByZIndex_[zKey]; for (const replayKey in replays) { - replays[replayKey].finish(); + const replayInstructions = replays[replayKey].finish(); + replaysInstructions[zKey][replayKey] = replayInstructions; } } + return replaysInstructions; } /** diff --git a/src/ol/render/canvas/TextReplay.js b/src/ol/render/canvas/TextReplay.js index e740f4a122..eb7203f86f 100644 --- a/src/ol/render/canvas/TextReplay.js +++ b/src/ol/render/canvas/TextReplay.js @@ -126,7 +126,27 @@ class CanvasTextReplay extends CanvasReplay { this.widths_ = {}; labelCache.prune(); + } + /** + * @inheritdoc + */ + finish() { + const instructions = super.finish(); + instructions.textStates = this.textStates; + instructions.fillStates = this.fillStates; + instructions.strokeStates = this.strokeStates; + return instructions; + } + + /** + * @inheritdoc + */ + replaceInstructions(instructions) { + super.replaceInstructions(instructions); + this.textStates = instructions.textStates; + this.fillStates = instructions.fillStates; + this.strokeStates = instructions.strokeStates; } /** diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 6864bda9c9..ea7666c134 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -480,13 +480,18 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { } else { vectorSource.forEachFeatureInExtent(extent, render); } - replayGroup.finish(); + + const replayGroupInstructions = replayGroup.finish(); + const renderingReplayGroup = new CanvasReplayGroup( + getRenderTolerance(resolution, pixelRatio), extent, resolution, + pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer()); + renderingReplayGroup.replaceInstructions(replayGroupInstructions); this.renderedResolution_ = resolution; this.renderedRevision_ = vectorLayerRevision; this.renderedRenderOrder_ = vectorLayerRenderOrder; this.renderedExtent_ = extent; - this.replayGroup_ = replayGroup; + this.replayGroup_ = renderingReplayGroup; this.replayGroupChanged = true; return true; diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 2e608ea9d2..f20fd72eab 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -226,8 +226,11 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { render.call(this, feature); } } - replayGroup.finish(); - sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), replayGroup); + const replayGroupInstructions = replayGroup.finish(); + const renderingReplayGroup = new CanvasReplayGroup(0, sharedExtent, resolution, + pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer()); + renderingReplayGroup.replaceInstructions(replayGroupInstructions); + sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), renderingReplayGroup); } replayState.renderedRevision = revision; replayState.renderedRenderOrder = renderOrder; From fcf470fc8d54baeee7dcd33af144d5637c213856 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Tue, 13 Nov 2018 16:26:29 +0100 Subject: [PATCH 02/30] Introduce Instruction executors Executors are use to render instructions. They do not contain cod for building instructions. Signed-off-by: Guillaume Beraudo --- src/ol/render/canvas/InstructionsExecutor.js | 1368 +++++++++++++++++ .../canvas/InstructionsGroupExecutor.js | 491 ++++++ src/ol/render/canvas/Replay.js | 12 +- src/ol/render/canvas/TextReplay.js | 10 - src/ol/renderer/canvas/VectorLayer.js | 5 +- src/ol/renderer/canvas/VectorTileLayer.js | 3 +- 6 files changed, 1865 insertions(+), 24 deletions(-) create mode 100644 src/ol/render/canvas/InstructionsExecutor.js create mode 100644 src/ol/render/canvas/InstructionsGroupExecutor.js diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js new file mode 100644 index 0000000000..dbf169f44c --- /dev/null +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -0,0 +1,1368 @@ +/** + * @module ol/render/canvas/Replay + */ +import {getUid} from '../../util.js'; +import {equals, reverseSubArray} from '../../array.js'; +import {buffer, clone, createEmpty, createOrUpdate, + createOrUpdateEmpty, extend, extendCoordinate, intersects} from '../../extent.js'; +import {lineStringLength} from '../../geom/flat/length.js'; +import {drawTextOnPath} from '../../geom/flat/textpath.js'; +import {transform2D} from '../../geom/flat/transform.js'; +import {CANVAS_LINE_DASH} from '../../has.js'; +import {isEmpty} from '../../obj.js'; +import {drawImage, resetTransform, defaultPadding} from '../canvas.js'; +import CanvasInstruction from './Instruction.js'; +import {TEXT_ALIGN} from '../replay.js'; +import { + create as createTransform, + compose as composeTransform, + apply as applyTransform, + setFromArray as transformSetFromArray +} from '../../transform.js'; + + +/// Imports copied from TextReplay +import {asColorLike} from '../../colorlike.js'; +import {createCanvasContext2D} from '../../dom.js'; +import {matchingChunk} from '../../geom/flat/straightchunk.js'; +import GeometryType from '../../geom/GeometryType.js'; +import {labelCache, measureTextWidth, defaultTextAlign, measureTextHeight, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; +import TextPlacement from '../../style/TextPlacement.js'; + + +/** + * @typedef {Object} SerializableInstructions + * @property {Array<*>} instructions The rendering instructions. + * @property {Array<*>} hitDetectionInstructions The rendering hit detection instructions. + * @property {Array} coordinates The array of all coordinates. + * @property {!Object} textStates The text states (decluttering). + * @property {!Object} fillStates The fill states (decluttering). + * @property {!Object} strokeStates The stroke states (decluttering). + */ + +/** + * @type {import("../../extent.js").Extent} + */ +const tmpExtent = createEmpty(); + + +/** + * @type {!import("../../transform.js").Transform} + */ +const tmpTransform = createTransform(); + + +class InstructionsExecutor { + /** + * @param {number} tolerance Tolerance. + * @param {import("../../extent.js").Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @param {?} declutterTree Declutter tree. + */ + constructor(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) { + + /** + * @type {?} + */ + this.declutterTree = declutterTree; + + /** + * @protected + * @type {number} + */ + this.tolerance = tolerance; + + /** + * @protected + * @const + * @type {import("../../extent.js").Extent} + */ + this.maxExtent = maxExtent; + + /** + * @protected + * @type {boolean} + */ + this.overlaps = overlaps; + + /** + * @protected + * @type {number} + */ + this.pixelRatio = pixelRatio; + + /** + * @protected + * @type {number} + */ + this.maxLineWidth = 0; + + /** + * @protected + * @const + * @type {number} + */ + this.resolution = resolution; + + /** + * @private + * @type {boolean} + */ + this.alignFill_; + + /** + * @private + * @type {Array<*>} + */ + this.beginGeometryInstruction1_ = null; + + /** + * @private + * @type {Array<*>} + */ + this.beginGeometryInstruction2_ = null; + + /** + * @private + * @type {import("../../extent.js").Extent} + */ + this.bufferedMaxExtent_ = null; + + /** + * @protected + * @type {Array<*>} + */ + this.instructions = []; + + /** + * @protected + * @type {Array} + */ + this.coordinates = []; + + /** + * @private + * @type {!Object|Array>>} + */ + this.coordinateCache_ = {}; + + /** + * @private + * @type {!import("../../transform.js").Transform} + */ + this.renderedTransform_ = createTransform(); + + /** + * @protected + * @type {Array<*>} + */ + this.hitDetectionInstructions = []; + + /** + * @private + * @type {Array} + */ + this.pixelCoordinates_ = null; + + /** + * @protected + * @type {import("../canvas.js").FillStrokeState} + */ + this.state = /** @type {import("../canvas.js").FillStrokeState} */ ({}); + + /** + * @private + * @type {number} + */ + this.viewRotation_ = 0; + + ////////////// Below is code copied from TextReplay //////////// + + + /** + * @private + * @type {Array} + */ + this.labels_ = null; + + /** + * @private + * @type {string} + */ + this.text_ = ''; + + /** + * @private + * @type {number} + */ + this.textOffsetX_ = 0; + + /** + * @private + * @type {number} + */ + this.textOffsetY_ = 0; + + /** + * @private + * @type {boolean|undefined} + */ + this.textRotateWithView_ = undefined; + + /** + * @private + * @type {number} + */ + this.textRotation_ = 0; + + /** + * @private + * @type {?import("../canvas.js").FillState} + */ + this.textFillState_ = null; + + /** + * @type {!Object} + */ + this.fillStates = {}; + + /** + * @private + * @type {?import("../canvas.js").StrokeState} + */ + this.textStrokeState_ = null; + + /** + * @type {!Object} + */ + this.strokeStates = {}; + + /** + * @private + * @type {import("../canvas.js").TextState} + */ + this.textState_ = /** @type {import("../canvas.js").TextState} */ ({}); + + /** + * @type {!Object} + */ + this.textStates = {}; + + /** + * @private + * @type {string} + */ + this.textKey_ = ''; + + /** + * @private + * @type {string} + */ + this.fillKey_ = ''; + + /** + * @private + * @type {string} + */ + this.strokeKey_ = ''; + + /** + * @private + * @type {Object>} + */ + this.widths_ = {}; + + labelCache.prune(); + } + + + /** + * @inheritDoc + */ + drawText(geometry, feature) { + const fillState = this.textFillState_; + const strokeState = this.textStrokeState_; + const textState = this.textState_; + if (this.text_ === '' || !textState || (!fillState && !strokeState)) { + return; + } + + let begin = this.coordinates.length; + + const geometryType = geometry.getType(); + let flatCoordinates = null; + let end = 2; + let stride = 2; + let i, ii; + + if (textState.placement === TextPlacement.LINE) { + if (!intersects(this.getBufferedMaxExtent(), geometry.getExtent())) { + return; + } + let ends; + flatCoordinates = geometry.getFlatCoordinates(); + stride = geometry.getStride(); + if (geometryType == GeometryType.LINE_STRING) { + ends = [flatCoordinates.length]; + } else if (geometryType == GeometryType.MULTI_LINE_STRING) { + ends = geometry.getEnds(); + } else if (geometryType == GeometryType.POLYGON) { + ends = geometry.getEnds().slice(0, 1); + } else if (geometryType == GeometryType.MULTI_POLYGON) { + const endss = geometry.getEndss(); + ends = []; + for (i = 0, ii = endss.length; i < ii; ++i) { + ends.push(endss[i][0]); + } + } + this.beginGeometry(geometry, feature); + const textAlign = textState.textAlign; + let flatOffset = 0; + let flatEnd; + for (let o = 0, oo = ends.length; o < oo; ++o) { + if (textAlign == undefined) { + const range = matchingChunk(textState.maxAngle, flatCoordinates, flatOffset, ends[o], stride); + flatOffset = range[0]; + flatEnd = range[1]; + } else { + flatEnd = ends[o]; + } + for (i = flatOffset; i < flatEnd; i += stride) { + this.coordinates.push(flatCoordinates[i], flatCoordinates[i + 1]); + } + end = this.coordinates.length; + flatOffset = ends[o]; + this.drawChars_(begin, end, this.declutterGroup_); + begin = end; + } + this.endGeometry(geometry, feature); + + } else { + const label = this.getImage(this.text_, this.textKey_, this.fillKey_, this.strokeKey_); + const width = label.width / this.pixelRatio; + switch (geometryType) { + case GeometryType.POINT: + case GeometryType.MULTI_POINT: + flatCoordinates = geometry.getFlatCoordinates(); + end = flatCoordinates.length; + break; + case GeometryType.LINE_STRING: + flatCoordinates = /** @type {import("../../geom/LineString.js").default} */ (geometry).getFlatMidpoint(); + break; + case GeometryType.CIRCLE: + flatCoordinates = /** @type {import("../../geom/Circle.js").default} */ (geometry).getCenter(); + break; + case GeometryType.MULTI_LINE_STRING: + flatCoordinates = /** @type {import("../../geom/MultiLineString.js").default} */ (geometry).getFlatMidpoints(); + end = flatCoordinates.length; + break; + case GeometryType.POLYGON: + flatCoordinates = /** @type {import("../../geom/Polygon.js").default} */ (geometry).getFlatInteriorPoint(); + if (!textState.overflow && flatCoordinates[2] / this.resolution < width) { + return; + } + stride = 3; + break; + case GeometryType.MULTI_POLYGON: + const interiorPoints = /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry).getFlatInteriorPoints(); + flatCoordinates = []; + for (i = 0, ii = interiorPoints.length; i < ii; i += 3) { + if (textState.overflow || interiorPoints[i + 2] / this.resolution >= width) { + flatCoordinates.push(interiorPoints[i], interiorPoints[i + 1]); + } + } + end = flatCoordinates.length; + if (end == 0) { + return; + } + break; + default: + } + end = this.appendFlatCoordinates(flatCoordinates, 0, end, stride, false, false); + if (textState.backgroundFill || textState.backgroundStroke) { + this.setFillStrokeStyle(textState.backgroundFill, textState.backgroundStroke); + if (textState.backgroundFill) { + this.updateFillStyle(this.state, this.createFill, geometry); + this.hitDetectionInstructions.push(this.createFill(this.state, geometry)); + } + if (textState.backgroundStroke) { + this.updateStrokeStyle(this.state, this.applyStroke); + this.hitDetectionInstructions.push(this.createStroke(this.state)); + } + } + this.beginGeometry(geometry, feature); + this.drawTextImage_(label, begin, end); + this.endGeometry(geometry, feature); + } + } + + /** + * @param {string} text Text. + * @param {string} textKey Text style key. + * @param {string} fillKey Fill style key. + * @param {string} strokeKey Stroke style key. + * @return {HTMLCanvasElement} Image. + */ + getImage(text, textKey, fillKey, strokeKey) { + let label; + const key = strokeKey + textKey + text + fillKey + this.pixelRatio; + + if (!labelCache.containsKey(key)) { + const strokeState = strokeKey ? this.strokeStates[strokeKey] || this.textStrokeState_ : null; + const fillState = fillKey ? this.fillStates[fillKey] || this.textFillState_ : null; + const textState = this.textStates[textKey] || this.textState_; + 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( + Math.ceil(renderWidth * 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 = /** @type {CanvasLineCap} */ (strokeState.lineCap); + context.lineJoin = /** @type {CanvasLineJoin} */ (strokeState.lineJoin); + context.miterLimit = strokeState.miterLimit; + if (CANVAS_LINE_DASH && 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 * label.width / scale + 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); + } + + /** + * @private + * @param {HTMLCanvasElement} label Label. + * @param {number} begin Begin. + * @param {number} end End. + */ + drawTextImage_(label, begin, end) { + const textState = this.textState_; + const strokeState = this.textStrokeState_; + const pixelRatio = this.pixelRatio; + const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign]; + const baseline = TEXT_ALIGN[textState.textBaseline]; + const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0; + + const anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth; + const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth; + this.instructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, + label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, + this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_, + 1, label.width, + textState.padding == defaultPadding ? + defaultPadding : textState.padding.map(function(p) { + return p * pixelRatio; + }), + !!textState.backgroundFill, !!textState.backgroundStroke + ]); + this.hitDetectionInstructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, + label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, + this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_, + 1 / pixelRatio, label.width, textState.padding, + !!textState.backgroundFill, !!textState.backgroundStroke + ]); + } + + /** + * @private + * @param {number} begin Begin. + * @param {number} end End. + * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. + */ + drawChars_(begin, end, declutterGroup) { + const strokeState = this.textStrokeState_; + const textState = this.textState_; + const fillState = this.textFillState_; + + const strokeKey = this.strokeKey_; + if (strokeState) { + if (!(strokeKey in this.strokeStates)) { + this.strokeStates[strokeKey] = /** @type {import("../canvas.js").StrokeState} */ ({ + strokeStyle: strokeState.strokeStyle, + lineCap: strokeState.lineCap, + lineDashOffset: strokeState.lineDashOffset, + lineWidth: strokeState.lineWidth, + lineJoin: strokeState.lineJoin, + miterLimit: strokeState.miterLimit, + lineDash: strokeState.lineDash + }); + } + } + const textKey = this.textKey_; + if (!(this.textKey_ in this.textStates)) { + this.textStates[this.textKey_] = /** @type {import("../canvas.js").TextState} */ ({ + font: textState.font, + textAlign: textState.textAlign || defaultTextAlign, + scale: textState.scale + }); + } + const fillKey = this.fillKey_; + if (fillState) { + if (!(fillKey in this.fillStates)) { + this.fillStates[fillKey] = /** @type {import("../canvas.js").FillState} */ ({ + fillStyle: fillState.fillStyle + }); + } + } + + const pixelRatio = this.pixelRatio; + const baseline = TEXT_ALIGN[textState.textBaseline]; + + const offsetY = this.textOffsetY_ * pixelRatio; + const text = this.text_; + const font = textState.font; + const textScale = textState.scale; + const strokeWidth = strokeState ? strokeState.lineWidth * textScale / 2 : 0; + let widths = this.widths_[font]; + if (!widths) { + this.widths_[font] = widths = {}; + } + this.instructions.push([CanvasInstruction.DRAW_CHARS, + begin, end, baseline, declutterGroup, + textState.overflow, fillKey, textState.maxAngle, + function(text) { + let width = widths[text]; + if (!width) { + width = widths[text] = measureTextWidth(font, text); + } + return width * textScale * pixelRatio; + }, + offsetY, strokeKey, strokeWidth * pixelRatio, text, textKey, 1 + ]); + this.hitDetectionInstructions.push([CanvasInstruction.DRAW_CHARS, + begin, end, baseline, declutterGroup, + textState.overflow, fillKey, textState.maxAngle, + function(text) { + let width = widths[text]; + if (!width) { + width = widths[text] = measureTextWidth(font, text); + } + return width * textScale; + }, + offsetY, strokeKey, strokeWidth, text, textKey, 1 / pixelRatio + ]); + } + + /** + * @inheritDoc + */ + setTextStyle(textStyle, declutterGroup) { + let textState, fillState, strokeState; + if (!textStyle) { + this.text_ = ''; + } else { + this.declutterGroup_ = /** @type {import("../canvas.js").DeclutterGroup} */ (declutterGroup); + + const textFillStyle = textStyle.getFill(); + if (!textFillStyle) { + fillState = this.textFillState_ = null; + } else { + fillState = this.textFillState_; + if (!fillState) { + fillState = this.textFillState_ = /** @type {import("../canvas.js").FillState} */ ({}); + } + fillState.fillStyle = asColorLike( + textFillStyle.getColor() || defaultFillStyle); + } + + const textStrokeStyle = textStyle.getStroke(); + if (!textStrokeStyle) { + strokeState = this.textStrokeState_ = null; + } else { + strokeState = this.textStrokeState_; + if (!strokeState) { + strokeState = this.textStrokeState_ = /** @type {import("../canvas.js").StrokeState} */ ({}); + } + const lineDash = textStrokeStyle.getLineDash(); + const lineDashOffset = textStrokeStyle.getLineDashOffset(); + const lineWidth = textStrokeStyle.getWidth(); + const miterLimit = textStrokeStyle.getMiterLimit(); + strokeState.lineCap = textStrokeStyle.getLineCap() || defaultLineCap; + strokeState.lineDash = lineDash ? lineDash.slice() : defaultLineDash; + strokeState.lineDashOffset = + lineDashOffset === undefined ? defaultLineDashOffset : lineDashOffset; + strokeState.lineJoin = textStrokeStyle.getLineJoin() || defaultLineJoin; + strokeState.lineWidth = + lineWidth === undefined ? defaultLineWidth : lineWidth; + strokeState.miterLimit = + miterLimit === undefined ? defaultMiterLimit : miterLimit; + strokeState.strokeStyle = asColorLike( + textStrokeStyle.getColor() || defaultStrokeStyle); + } + + textState = this.textState_; + const font = textStyle.getFont() || defaultFont; + checkFont(font); + const textScale = textStyle.getScale(); + textState.overflow = textStyle.getOverflow(); + textState.font = font; + textState.maxAngle = textStyle.getMaxAngle(); + textState.placement = textStyle.getPlacement(); + textState.textAlign = textStyle.getTextAlign(); + textState.textBaseline = textStyle.getTextBaseline() || defaultTextBaseline; + textState.backgroundFill = textStyle.getBackgroundFill(); + textState.backgroundStroke = textStyle.getBackgroundStroke(); + textState.padding = textStyle.getPadding() || defaultPadding; + textState.scale = textScale === undefined ? 1 : textScale; + + const textOffsetX = textStyle.getOffsetX(); + const textOffsetY = textStyle.getOffsetY(); + const textRotateWithView = textStyle.getRotateWithView(); + const textRotation = textStyle.getRotation(); + this.text_ = textStyle.getText() || ''; + this.textOffsetX_ = textOffsetX === undefined ? 0 : textOffsetX; + this.textOffsetY_ = textOffsetY === undefined ? 0 : textOffsetY; + this.textRotateWithView_ = textRotateWithView === undefined ? false : textRotateWithView; + this.textRotation_ = textRotation === undefined ? 0 : textRotation; + + this.strokeKey_ = strokeState ? + (typeof strokeState.strokeStyle == 'string' ? strokeState.strokeStyle : getUid(strokeState.strokeStyle)) + + strokeState.lineCap + strokeState.lineDashOffset + '|' + strokeState.lineWidth + + strokeState.lineJoin + strokeState.miterLimit + '[' + strokeState.lineDash.join() + ']' : + ''; + this.textKey_ = textState.font + textState.scale + (textState.textAlign || '?'); + this.fillKey_ = fillState ? + (typeof fillState.fillStyle == 'string' ? fillState.fillStyle : ('|' + getUid(fillState.fillStyle))) : + ''; + } + } + + ////////////// Above is code from TextReplay ////////////////// + + /** + * Recreate replays and populate them using the provided instructions. + * @param {SerializableInstructions} instructions The serializable instructions + */ + replaceInstructions(instructions) { + this.instructions = instructions.instructions; + this.hitDetectionInstructions = instructions.hitDetectionInstructions; + this.coordinates = instructions.coordinates; + // Workaround for decluttered text creation / rendering being coupled + this.textStates = instructions.textStates; + this.fillStates = instructions.fillStates; + this.strokeStates = instructions.strokeStates; + } + + /** + * @param {CanvasRenderingContext2D} context Context. + * @param {import("../../coordinate.js").Coordinate} p1 1st point of the background box. + * @param {import("../../coordinate.js").Coordinate} p2 2nd point of the background box. + * @param {import("../../coordinate.js").Coordinate} p3 3rd point of the background box. + * @param {import("../../coordinate.js").Coordinate} p4 4th point of the background box. + * @param {Array<*>} fillInstruction Fill instruction. + * @param {Array<*>} strokeInstruction Stroke instruction. + */ + replayTextBackground_(context, p1, p2, p3, p4, fillInstruction, strokeInstruction) { + context.beginPath(); + context.moveTo.apply(context, p1); + context.lineTo.apply(context, p2); + context.lineTo.apply(context, p3); + context.lineTo.apply(context, p4); + context.lineTo.apply(context, p1); + if (fillInstruction) { + this.alignFill_ = /** @type {boolean} */ (fillInstruction[2]); + this.fill_(context); + } + if (strokeInstruction) { + this.setStrokeStyle_(context, /** @type {Array<*>} */ (strokeInstruction)); + context.stroke(); + } + } + + /** + * @param {CanvasRenderingContext2D} context Context. + * @param {number} x X. + * @param {number} y Y. + * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image. + * @param {number} anchorX Anchor X. + * @param {number} anchorY Anchor Y. + * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. + * @param {number} height Height. + * @param {number} opacity Opacity. + * @param {number} originX Origin X. + * @param {number} originY Origin Y. + * @param {number} rotation Rotation. + * @param {number} scale Scale. + * @param {boolean} snapToPixel Snap to pixel. + * @param {number} width Width. + * @param {Array} padding Padding. + * @param {Array<*>} fillInstruction Fill instruction. + * @param {Array<*>} strokeInstruction Stroke instruction. + */ + replayImage_( + context, + x, + y, + image, + anchorX, + anchorY, + declutterGroup, + height, + opacity, + originX, + originY, + rotation, + scale, + snapToPixel, + width, + padding, + fillInstruction, + strokeInstruction + ) { + const fillStroke = fillInstruction || strokeInstruction; + anchorX *= scale; + anchorY *= scale; + 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 boxW = padding[3] + w * scale + padding[1]; + const boxH = padding[0] + h * scale + padding[2]; + const boxX = x - padding[3]; + const boxY = y - padding[0]; + + /** @type {import("../../coordinate.js").Coordinate} */ + let p1; + /** @type {import("../../coordinate.js").Coordinate} */ + let p2; + /** @type {import("../../coordinate.js").Coordinate} */ + let p3; + /** @type {import("../../coordinate.js").Coordinate} */ + let p4; + if (fillStroke || rotation !== 0) { + p1 = [boxX, boxY]; + p2 = [boxX + boxW, boxY]; + p3 = [boxX + boxW, boxY + boxH]; + p4 = [boxX, boxY + boxH]; + } + + let transform = null; + if (rotation !== 0) { + const centerX = x + anchorX; + const centerY = y + anchorY; + transform = composeTransform(tmpTransform, centerX, centerY, 1, 1, rotation, -centerX, -centerY); + + createOrUpdateEmpty(tmpExtent); + extendCoordinate(tmpExtent, applyTransform(tmpTransform, p1)); + extendCoordinate(tmpExtent, applyTransform(tmpTransform, p2)); + extendCoordinate(tmpExtent, applyTransform(tmpTransform, p3)); + extendCoordinate(tmpExtent, applyTransform(tmpTransform, p4)); + } else { + createOrUpdate(boxX, boxY, boxX + boxW, boxY + boxH, tmpExtent); + } + const canvas = context.canvas; + const strokePadding = strokeInstruction ? (strokeInstruction[2] * scale / 2) : 0; + const intersects = + tmpExtent[0] - strokePadding <= canvas.width && tmpExtent[2] + strokePadding >= 0 && + tmpExtent[1] - strokePadding <= canvas.height && tmpExtent[3] + strokePadding >= 0; + + if (snapToPixel) { + x = Math.round(x); + y = Math.round(y); + } + + if (declutterGroup) { + if (!intersects && declutterGroup[4] == 1) { + return; + } + extend(declutterGroup, tmpExtent); + const declutterArgs = intersects ? + [context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] : + null; + if (declutterArgs && fillStroke) { + declutterArgs.push(fillInstruction, strokeInstruction, p1, p2, p3, p4); + } + declutterGroup.push(declutterArgs); + } else if (intersects) { + if (fillStroke) { + this.replayTextBackground_(context, p1, p2, p3, p4, + /** @type {Array<*>} */ (fillInstruction), + /** @type {Array<*>} */ (strokeInstruction)); + } + drawImage(context, transform, opacity, image, originX, originY, w, h, x, y, scale); + } + } + + /** + * @protected + * @param {Array} dashArray Dash array. + * @return {Array} Dash array with pixel ratio applied + */ + applyPixelRatio(dashArray) { + const pixelRatio = this.pixelRatio; + return pixelRatio == 1 ? dashArray : dashArray.map(function(dash) { + return dash * pixelRatio; + }); + } + + /** + * @private + * @param {CanvasRenderingContext2D} context Context. + */ + fill_(context) { + if (this.alignFill_) { + const origin = applyTransform(this.renderedTransform_, [0, 0]); + const repeatSize = 512 * this.pixelRatio; + context.translate(origin[0] % repeatSize, origin[1] % repeatSize); + context.rotate(this.viewRotation_); + } + context.fill(); + if (this.alignFill_) { + context.setTransform.apply(context, resetTransform); + } + } + + /** + * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {Array<*>} instruction Instruction. + */ + setStrokeStyle_(context, instruction) { + context.strokeStyle = /** @type {import("../../colorlike.js").ColorLike} */ (instruction[1]); + context.lineWidth = /** @type {number} */ (instruction[2]); + context.lineCap = /** @type {CanvasLineCap} */ (instruction[3]); + context.lineJoin = /** @type {CanvasLineJoin} */ (instruction[4]); + context.miterLimit = /** @type {number} */ (instruction[5]); + if (CANVAS_LINE_DASH) { + context.lineDashOffset = /** @type {number} */ (instruction[7]); + context.setLineDash(/** @type {Array} */ (instruction[6])); + } + } + + /** + * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. + * @param {import("../../Feature.js").default|import("../Feature.js").default} feature Feature. + */ + renderDeclutter_(declutterGroup, feature) { + if (declutterGroup && declutterGroup.length > 5) { + const groupCount = declutterGroup[4]; + if (groupCount == 1 || groupCount == declutterGroup.length - 5) { + /** @type {import("../../structs/RBush.js").Entry} */ + const box = { + minX: /** @type {number} */ (declutterGroup[0]), + minY: /** @type {number} */ (declutterGroup[1]), + maxX: /** @type {number} */ (declutterGroup[2]), + maxY: /** @type {number} */ (declutterGroup[3]), + value: feature + }; + if (!this.declutterTree.collides(box)) { + this.declutterTree.insert(box); + for (let j = 5, jj = declutterGroup.length; j < jj; ++j) { + const declutterData = /** @type {Array} */ (declutterGroup[j]); + if (declutterData) { + if (declutterData.length > 11) { + this.replayTextBackground_(declutterData[0], + declutterData[13], declutterData[14], declutterData[15], declutterData[16], + declutterData[11], declutterData[12]); + } + drawImage.apply(undefined, declutterData); + } + } + } + declutterGroup.length = 5; + createOrUpdateEmpty(declutterGroup); + } + } + } + + /** + * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {import("../../transform.js").Transform} transform Transform. + * @param {Object} skippedFeaturesHash Ids of features + * to skip. + * @param {Array<*>} instructions Instructions array. + * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. + * @param {function((import("../../Feature.js").default|import("../Feature.js").default)): T|undefined} featureCallback Feature callback. + * @param {import("../../extent.js").Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T + */ + replay_( + context, + transform, + skippedFeaturesHash, + instructions, + snapToPixel, + featureCallback, + opt_hitExtent + ) { + /** @type {Array} */ + let pixelCoordinates; + if (this.pixelCoordinates_ && equals(transform, this.renderedTransform_)) { + pixelCoordinates = this.pixelCoordinates_; + } else { + if (!this.pixelCoordinates_) { + this.pixelCoordinates_ = []; + } + pixelCoordinates = transform2D( + this.coordinates, 0, this.coordinates.length, 2, + transform, this.pixelCoordinates_); + transformSetFromArray(this.renderedTransform_, transform); + } + const skipFeatures = !isEmpty(skippedFeaturesHash); + let i = 0; // instruction index + const ii = instructions.length; // end of instructions + let d = 0; // data index + let dd; // end of per-instruction data + let anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image; + let pendingFill = 0; + let pendingStroke = 0; + let lastFillInstruction = null; + let lastStrokeInstruction = null; + const coordinateCache = this.coordinateCache_; + const viewRotation = this.viewRotation_; + + const state = /** @type {import("../../render.js").State} */ ({ + context: context, + pixelRatio: this.pixelRatio, + resolution: this.resolution, + rotation: viewRotation + }); + + // When the batch size gets too big, performance decreases. 200 is a good + // balance between batch size and number of fill/stroke instructions. + const batchSize = this.instructions != instructions || this.overlaps ? 0 : 200; + let /** @type {import("../../Feature.js").default|import("../Feature.js").default} */ feature; + let x, y; + while (i < ii) { + const instruction = instructions[i]; + const type = /** @type {CanvasInstruction} */ (instruction[0]); + switch (type) { + case CanvasInstruction.BEGIN_GEOMETRY: + feature = /** @type {import("../../Feature.js").default|import("../Feature.js").default} */ (instruction[1]); + if ((skipFeatures && skippedFeaturesHash[getUid(feature)]) || !feature.getGeometry()) { + i = /** @type {number} */ (instruction[2]); + } else if (opt_hitExtent !== undefined && !intersects( + opt_hitExtent, feature.getGeometry().getExtent())) { + i = /** @type {number} */ (instruction[2]) + 1; + } else { + ++i; + } + break; + case CanvasInstruction.BEGIN_PATH: + if (pendingFill > batchSize) { + this.fill_(context); + pendingFill = 0; + } + if (pendingStroke > batchSize) { + context.stroke(); + pendingStroke = 0; + } + if (!pendingFill && !pendingStroke) { + context.beginPath(); + prevX = prevY = NaN; + } + ++i; + break; + case CanvasInstruction.CIRCLE: + d = /** @type {number} */ (instruction[1]); + const x1 = pixelCoordinates[d]; + const y1 = pixelCoordinates[d + 1]; + const x2 = pixelCoordinates[d + 2]; + const y2 = pixelCoordinates[d + 3]; + const dx = x2 - x1; + const dy = y2 - y1; + const r = Math.sqrt(dx * dx + dy * dy); + context.moveTo(x1 + r, y1); + context.arc(x1, y1, r, 0, 2 * Math.PI, true); + ++i; + break; + case CanvasInstruction.CLOSE_PATH: + context.closePath(); + ++i; + break; + case CanvasInstruction.CUSTOM: + d = /** @type {number} */ (instruction[1]); + dd = instruction[2]; + const geometry = /** @type {import("../../geom/SimpleGeometry.js").default} */ (instruction[3]); + const renderer = instruction[4]; + const fn = instruction.length == 6 ? instruction[5] : undefined; + state.geometry = geometry; + state.feature = feature; + if (!(i in coordinateCache)) { + coordinateCache[i] = []; + } + const coords = coordinateCache[i]; + if (fn) { + fn(pixelCoordinates, d, dd, 2, coords); + } else { + coords[0] = pixelCoordinates[d]; + coords[1] = pixelCoordinates[d + 1]; + coords.length = 2; + } + renderer(coords, state); + ++i; + break; + case CanvasInstruction.DRAW_IMAGE: + d = /** @type {number} */ (instruction[1]); + dd = /** @type {number} */ (instruction[2]); + image = /** @type {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} */ + (instruction[3]); + // Remaining arguments in DRAW_IMAGE are in alphabetical order + anchorX = /** @type {number} */ (instruction[4]); + anchorY = /** @type {number} */ (instruction[5]); + declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[6]); + const height = /** @type {number} */ (instruction[7]); + const opacity = /** @type {number} */ (instruction[8]); + const originX = /** @type {number} */ (instruction[9]); + const originY = /** @type {number} */ (instruction[10]); + const rotateWithView = /** @type {boolean} */ (instruction[11]); + let rotation = /** @type {number} */ (instruction[12]); + const scale = /** @type {number} */ (instruction[13]); + const width = /** @type {number} */ (instruction[14]); + + let padding, backgroundFill, backgroundStroke; + if (instruction.length > 16) { + padding = /** @type {Array} */ (instruction[15]); + backgroundFill = /** @type {boolean} */ (instruction[16]); + backgroundStroke = /** @type {boolean} */ (instruction[17]); + } else { + padding = defaultPadding; + backgroundFill = backgroundStroke = false; + } + + if (rotateWithView) { + rotation += viewRotation; + } + for (; d < dd; d += 2) { + this.replayImage_(context, + pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY, + declutterGroup, height, opacity, originX, originY, rotation, scale, + snapToPixel, width, padding, + backgroundFill ? /** @type {Array<*>} */ (lastFillInstruction) : null, + backgroundStroke ? /** @type {Array<*>} */ (lastStrokeInstruction) : null); + } + this.renderDeclutter_(declutterGroup, feature); + ++i; + break; + case CanvasInstruction.DRAW_CHARS: + const begin = /** @type {number} */ (instruction[1]); + const end = /** @type {number} */ (instruction[2]); + const baseline = /** @type {number} */ (instruction[3]); + declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[4]); + const overflow = /** @type {number} */ (instruction[5]); + const fillKey = /** @type {string} */ (instruction[6]); + const maxAngle = /** @type {number} */ (instruction[7]); + const measure = /** @type {function(string):number} */ (instruction[8]); + const offsetY = /** @type {number} */ (instruction[9]); + const strokeKey = /** @type {string} */ (instruction[10]); + const strokeWidth = /** @type {number} */ (instruction[11]); + const text = /** @type {string} */ (instruction[12]); + const textKey = /** @type {string} */ (instruction[13]); + const textScale = /** @type {number} */ (instruction[14]); + + const pathLength = lineStringLength(pixelCoordinates, begin, end, 2); + const textLength = measure(text); + if (overflow || textLength <= pathLength) { + /** @type {import("./TextReplay.js").default} */ + const textReplay = /** @type {?} */ (this); + const textAlign = textReplay.textStates[textKey].textAlign; + const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign]; + const parts = drawTextOnPath( + pixelCoordinates, begin, end, 2, text, measure, startM, maxAngle); + if (parts) { + let c, cc, chars, label, part; + if (strokeKey) { + for (c = 0, cc = parts.length; c < cc; ++c) { + part = parts[c]; // x, y, anchorX, rotation, chunk + chars = /** @type {string} */ (part[4]); + label = textReplay.getImage(chars, textKey, '', strokeKey); + anchorX = /** @type {number} */ (part[2]) + strokeWidth; + anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY; + this.replayImage_(context, + /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label, + anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, + /** @type {number} */ (part[3]), textScale, false, label.width, + defaultPadding, null, null); + } + } + if (fillKey) { + for (c = 0, cc = parts.length; c < cc; ++c) { + part = parts[c]; // x, y, anchorX, rotation, chunk + chars = /** @type {string} */ (part[4]); + label = textReplay.getImage(chars, textKey, fillKey, ''); + anchorX = /** @type {number} */ (part[2]); + anchorY = baseline * label.height - offsetY; + this.replayImage_(context, + /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label, + anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, + /** @type {number} */ (part[3]), textScale, false, label.width, + defaultPadding, null, null); + } + } + } + } + this.renderDeclutter_(declutterGroup, feature); + ++i; + break; + case CanvasInstruction.END_GEOMETRY: + if (featureCallback !== undefined) { + feature = /** @type {import("../../Feature.js").default|import("../Feature.js").default} */ (instruction[1]); + const result = featureCallback(feature); + if (result) { + return result; + } + } + ++i; + break; + case CanvasInstruction.FILL: + if (batchSize) { + pendingFill++; + } else { + this.fill_(context); + } + ++i; + break; + case CanvasInstruction.MOVE_TO_LINE_TO: + d = /** @type {number} */ (instruction[1]); + dd = /** @type {number} */ (instruction[2]); + x = pixelCoordinates[d]; + y = pixelCoordinates[d + 1]; + roundX = (x + 0.5) | 0; + roundY = (y + 0.5) | 0; + if (roundX !== prevX || roundY !== prevY) { + context.moveTo(x, y); + prevX = roundX; + prevY = roundY; + } + for (d += 2; d < dd; d += 2) { + x = pixelCoordinates[d]; + y = pixelCoordinates[d + 1]; + roundX = (x + 0.5) | 0; + roundY = (y + 0.5) | 0; + if (d == dd - 2 || roundX !== prevX || roundY !== prevY) { + context.lineTo(x, y); + prevX = roundX; + prevY = roundY; + } + } + ++i; + break; + case CanvasInstruction.SET_FILL_STYLE: + lastFillInstruction = instruction; + this.alignFill_ = instruction[2]; + + if (pendingFill) { + this.fill_(context); + pendingFill = 0; + if (pendingStroke) { + context.stroke(); + pendingStroke = 0; + } + } + + context.fillStyle = /** @type {import("../../colorlike.js").ColorLike} */ (instruction[1]); + ++i; + break; + case CanvasInstruction.SET_STROKE_STYLE: + lastStrokeInstruction = instruction; + if (pendingStroke) { + context.stroke(); + pendingStroke = 0; + } + this.setStrokeStyle_(context, /** @type {Array<*>} */ (instruction)); + ++i; + break; + case CanvasInstruction.STROKE: + if (batchSize) { + pendingStroke++; + } else { + context.stroke(); + } + ++i; + break; + default: + ++i; // consume the instruction anyway, to avoid an infinite loop + break; + } + } + if (pendingFill) { + this.fill_(context); + } + if (pendingStroke) { + context.stroke(); + } + return undefined; + } + + /** + * @param {CanvasRenderingContext2D} context Context. + * @param {import("../../transform.js").Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object} skippedFeaturesHash Ids of features + * to skip. + * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. + */ + replay(context, transform, viewRotation, skippedFeaturesHash, snapToPixel) { + this.viewRotation_ = viewRotation; + this.replay_(context, transform, + skippedFeaturesHash, this.instructions, snapToPixel, undefined, undefined); + } + + /** + * @param {CanvasRenderingContext2D} context Context. + * @param {import("../../transform.js").Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object} skippedFeaturesHash Ids of features + * to skip. + * @param {function((import("../../Feature.js").default|import("../Feature.js").default)): T=} opt_featureCallback + * Feature callback. + * @param {import("../../extent.js").Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T + */ + replayHitDetection( + context, + transform, + viewRotation, + skippedFeaturesHash, + opt_featureCallback, + opt_hitExtent + ) { + this.viewRotation_ = viewRotation; + return this.replay_(context, transform, skippedFeaturesHash, + this.hitDetectionInstructions, true, opt_featureCallback, opt_hitExtent); + } + + /** + * Reverse the hit detection instructions. + */ + reverseHitDetectionInstructions() { + const hitDetectionInstructions = this.hitDetectionInstructions; + // step 1 - reverse array + hitDetectionInstructions.reverse(); + // step 2 - reverse instructions within geometry blocks + let i; + const n = hitDetectionInstructions.length; + let instruction; + let type; + let begin = -1; + for (i = 0; i < n; ++i) { + instruction = hitDetectionInstructions[i]; + type = /** @type {CanvasInstruction} */ (instruction[0]); + if (type == CanvasInstruction.END_GEOMETRY) { + begin = i; + } else if (type == CanvasInstruction.BEGIN_GEOMETRY) { + instruction[2] = i; + reverseSubArray(this.hitDetectionInstructions, begin, i); + begin = -1; + } + } + } + + + /** + * @param {import("../canvas.js").FillStrokeState} state State. + * @param {import("../../geom/Geometry.js").default|import("../Feature.js").default} geometry Geometry. + * @return {Array<*>} Fill instruction. + */ + createFill(state, geometry) { + const fillStyle = state.fillStyle; + /** @type {Array<*>} */ + const fillInstruction = [CanvasInstruction.SET_FILL_STYLE, fillStyle]; + if (typeof fillStyle !== 'string') { + // Fill is a pattern or gradient - align it! + fillInstruction.push(true); + } + return fillInstruction; + } + + + /** + * @param {import("../canvas.js").FillStrokeState} state State. + * @return {Array<*>} Stroke instruction. + */ + createStroke(state) { + return [ + CanvasInstruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth * this.pixelRatio, state.lineCap, + state.lineJoin, state.miterLimit, + this.applyPixelRatio(state.lineDash), state.lineDashOffset * this.pixelRatio + ]; + } + + + /** + * Get the buffered rendering extent. Rendering will be clipped to the extent + * provided to the constructor. To account for symbolizers that may intersect + * this extent, we calculate a buffered extent (e.g. based on stroke width). + * @return {import("../../extent.js").Extent} The buffered rendering extent. + * @protected + */ + getBufferedMaxExtent() { + if (!this.bufferedMaxExtent_) { + this.bufferedMaxExtent_ = clone(this.maxExtent); + if (this.maxLineWidth > 0) { + const width = this.resolution * (this.maxLineWidth + 1) / 2; + buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_); + } + } + return this.bufferedMaxExtent_; + } +} + + +export default InstructionsExecutor; + + +/////////////// Below is code copied from TextReplay ///////////////// + +/** + * @param {string} font Font to use for measuring. + * @param {Array} lines Lines to measure. + * @param {Array} widths Array will be populated with the widths of + * each line. + * @return {number} Width of the whole text. + */ +export function measureTextWidths(font, lines, widths) { + const numLines = lines.length; + let width = 0; + for (let i = 0; i < numLines; ++i) { + const currentWidth = measureTextWidth(font, lines[i]); + width = Math.max(width, currentWidth); + widths.push(currentWidth); + } + return width; +} diff --git a/src/ol/render/canvas/InstructionsGroupExecutor.js b/src/ol/render/canvas/InstructionsGroupExecutor.js new file mode 100644 index 0000000000..1bff6b0834 --- /dev/null +++ b/src/ol/render/canvas/InstructionsGroupExecutor.js @@ -0,0 +1,491 @@ +/** + * @module ol/render/canvas/ReplayGroup + */ + +import {numberSafeCompareFunction} from '../../array.js'; +import {createCanvasContext2D} from '../../dom.js'; +import {buffer, createEmpty, extendCoordinate} from '../../extent.js'; +import {transform2D} from '../../geom/flat/transform.js'; +import {isEmpty} from '../../obj.js'; +import ReplayGroup from '../ReplayGroup.js'; +import ReplayType from '../ReplayType.js'; +import {ORDER} from '../replay.js'; +import {create as createTransform, compose as composeTransform} from '../../transform.js'; +import InstructionsExecutor from './InstructionsExecutor.js'; + + +class InstructionsGroupExectuor extends ReplayGroup { + /** + * @param {number} tolerance Tolerance. + * @param {import("../../extent.js").Extent} maxExtent Max extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {boolean} overlaps The replay group can have overlapping geometries. + * @param {?} declutterTree Declutter tree for declutter processing in postrender. + * @param {number=} opt_renderBuffer Optional rendering buffer. + */ + constructor( + tolerance, + maxExtent, + resolution, + pixelRatio, + overlaps, + declutterTree, + opt_renderBuffer + ) { + super(); + + /** + * Declutter tree. + * @private + */ + this.declutterTree_ = declutterTree; + + /** + * @type {import("../canvas.js").DeclutterGroup} + * @private + */ + this.declutterGroup_ = null; + + /** + * @private + * @type {number} + */ + this.tolerance_ = tolerance; + + /** + * @private + * @type {import("../../extent.js").Extent} + */ + this.maxExtent_ = maxExtent; + + /** + * @private + * @type {boolean} + */ + this.overlaps_ = overlaps; + + /** + * @private + * @type {number} + */ + this.pixelRatio_ = pixelRatio; + + /** + * @private + * @type {number} + */ + this.resolution_ = resolution; + + /** + * @private + * @type {number|undefined} + */ + this.renderBuffer_ = opt_renderBuffer; + + /** + * @private + * @type {!Object>} + */ + this.replaysByZIndex_ = {}; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitDetectionContext_ = createCanvasContext2D(1, 1); + + /** + * @private + * @type {import("../../transform.js").Transform} + */ + this.hitDetectionTransform_ = createTransform(); + } + + /** + * @inheritDoc + */ + addDeclutter(group) { + let declutter = null; + if (this.declutterTree_) { + if (group) { + declutter = this.declutterGroup_; + /** @type {number} */ (declutter[4])++; + } else { + declutter = this.declutterGroup_ = createEmpty(); + declutter.push(1); + } + } + return declutter; + } + + /** + * @param {CanvasRenderingContext2D} context Context. + * @param {import("../../transform.js").Transform} transform Transform. + */ + clip(context, transform) { + const flatClipCoords = this.getClipCoords(transform); + context.beginPath(); + context.moveTo(flatClipCoords[0], flatClipCoords[1]); + context.lineTo(flatClipCoords[2], flatClipCoords[3]); + context.lineTo(flatClipCoords[4], flatClipCoords[5]); + context.lineTo(flatClipCoords[6], flatClipCoords[7]); + context.clip(); + } + + /** + * Recreate replays and populate them using the provided instructions. + * @param {!Object>} allInstructions The serializable instructions + */ + replaceInstructions(allInstructions) { + this.replaysByZIndex_ = {}; + for (const zIndex in allInstructions) { + const instructionByZindex = allInstructions[zIndex]; + for (const replayType in instructionByZindex) { + const instructions = instructionByZindex[replayType]; + const replay = this.getReplay(zIndex, replayType); + replay.replaceInstructions(instructions); + } + } + } + + /** + * @param {Array} replays Replays. + * @return {boolean} Has replays of the provided types. + */ + hasReplays(replays) { + for (const zIndex in this.replaysByZIndex_) { + const candidates = this.replaysByZIndex_[zIndex]; + for (let i = 0, ii = replays.length; i < ii; ++i) { + if (replays[i] in candidates) { + return true; + } + } + } + return false; + } + + + /** + * @param {import("../../coordinate.js").Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function((import("../../Feature.js").default|import("../Feature.js").default)): T} callback Feature callback. + * @param {Object} declutterReplays Declutter replays. + * @return {T|undefined} Callback result. + * @template T + */ + forEachFeatureAtCoordinate( + coordinate, + resolution, + rotation, + hitTolerance, + skippedFeaturesHash, + callback, + declutterReplays + ) { + + hitTolerance = Math.round(hitTolerance); + const contextSize = hitTolerance * 2 + 1; + const transform = composeTransform(this.hitDetectionTransform_, + hitTolerance + 0.5, hitTolerance + 0.5, + 1 / resolution, -1 / resolution, + -rotation, + -coordinate[0], -coordinate[1]); + const context = this.hitDetectionContext_; + + if (context.canvas.width !== contextSize || context.canvas.height !== contextSize) { + context.canvas.width = contextSize; + context.canvas.height = contextSize; + } else { + context.clearRect(0, 0, contextSize, contextSize); + } + + /** + * @type {import("../../extent.js").Extent} + */ + let hitExtent; + if (this.renderBuffer_ !== undefined) { + hitExtent = createEmpty(); + extendCoordinate(hitExtent, coordinate); + buffer(hitExtent, resolution * (this.renderBuffer_ + hitTolerance), hitExtent); + } + + const mask = getCircleArray(hitTolerance); + let declutteredFeatures; + if (this.declutterTree_) { + declutteredFeatures = this.declutterTree_.all().map(function(entry) { + return entry.value; + }); + } + + let replayType; + + /** + * @param {import("../../Feature.js").default|import("../Feature.js").default} feature Feature. + * @return {?} Callback result. + */ + function featureCallback(feature) { + const imageData = context.getImageData(0, 0, contextSize, contextSize).data; + for (let i = 0; i < contextSize; i++) { + for (let j = 0; j < contextSize; j++) { + if (mask[i][j]) { + if (imageData[(j * contextSize + i) * 4 + 3] > 0) { + let result; + if (!(declutteredFeatures && (replayType == ReplayType.IMAGE || replayType == ReplayType.TEXT)) || + declutteredFeatures.indexOf(feature) !== -1) { + result = callback(feature); + } + if (result) { + return result; + } else { + context.clearRect(0, 0, contextSize, contextSize); + return undefined; + } + } + } + } + } + } + + /** @type {Array} */ + const zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(numberSafeCompareFunction); + + let i, j, replays, replay, result; + for (i = zs.length - 1; i >= 0; --i) { + const zIndexKey = zs[i].toString(); + replays = this.replaysByZIndex_[zIndexKey]; + for (j = ORDER.length - 1; j >= 0; --j) { + replayType = ORDER[j]; + replay = replays[replayType]; + if (replay !== undefined) { + if (declutterReplays && + (replayType == ReplayType.IMAGE || replayType == ReplayType.TEXT)) { + const declutter = declutterReplays[zIndexKey]; + if (!declutter) { + declutterReplays[zIndexKey] = [replay, transform.slice(0)]; + } else { + declutter.push(replay, transform.slice(0)); + } + } else { + result = replay.replayHitDetection(context, transform, rotation, + skippedFeaturesHash, featureCallback, hitExtent); + if (result) { + return result; + } + } + } + } + } + return undefined; + } + + /** + * @param {import("../../transform.js").Transform} transform Transform. + * @return {Array} Clip coordinates. + */ + getClipCoords(transform) { + const maxExtent = this.maxExtent_; + const minX = maxExtent[0]; + const minY = maxExtent[1]; + const maxX = maxExtent[2]; + const maxY = maxExtent[3]; + const flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY]; + transform2D( + flatClipCoords, 0, 8, 2, transform, flatClipCoords); + return flatClipCoords; + } + + /** + * @inheritDoc + */ + getReplay(zIndex, replayType) { + const zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; + let replays = this.replaysByZIndex_[zIndexKey]; + if (replays === undefined) { + replays = {}; + this.replaysByZIndex_[zIndexKey] = replays; + } + let replay = replays[replayType]; + if (replay === undefined) { + replay = new InstructionsExecutor(this.tolerance_, this.maxExtent_, + this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_); + replays[replayType] = replay; + } + return replay; + } + + /** + * @return {Object>} Replays. + */ + getReplays() { + return this.replaysByZIndex_; + } + + /** + * @inheritDoc + */ + isEmpty() { + return isEmpty(this.replaysByZIndex_); + } + + /** + * @param {CanvasRenderingContext2D} context Context. + * @param {import("../../transform.js").Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {boolean} snapToPixel Snap point symbols and test to integer pixel. + * @param {Array=} opt_replayTypes Ordered replay types to replay. + * Default is {@link module:ol/render/replay~ORDER} + * @param {Object=} opt_declutterReplays Declutter replays. + */ + replay( + context, + transform, + viewRotation, + skippedFeaturesHash, + snapToPixel, + opt_replayTypes, + opt_declutterReplays + ) { + + /** @type {Array} */ + const zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(numberSafeCompareFunction); + + // setup clipping so that the parts of over-simplified geometries are not + // visible outside the current extent when panning + context.save(); + this.clip(context, transform); + + const replayTypes = opt_replayTypes ? opt_replayTypes : ORDER; + let i, ii, j, jj, replays, replay; + for (i = 0, ii = zs.length; i < ii; ++i) { + const zIndexKey = zs[i].toString(); + replays = this.replaysByZIndex_[zIndexKey]; + for (j = 0, jj = replayTypes.length; j < jj; ++j) { + const replayType = replayTypes[j]; + replay = replays[replayType]; + if (replay !== undefined) { + if (opt_declutterReplays && + (replayType == ReplayType.IMAGE || replayType == ReplayType.TEXT)) { + const declutter = opt_declutterReplays[zIndexKey]; + if (!declutter) { + opt_declutterReplays[zIndexKey] = [replay, transform.slice(0)]; + } else { + declutter.push(replay, transform.slice(0)); + } + } else { + replay.replay(context, transform, viewRotation, skippedFeaturesHash, snapToPixel); + } + } + } + } + + context.restore(); + } +} + + +/** + * This cache is used for storing calculated pixel circles for increasing performance. + * It is a static property to allow each Replaygroup to access it. + * @type {Object>>} + */ +const circleArrayCache = { + 0: [[true]] +}; + + +/** + * This method fills a row in the array from the given coordinate to the + * middle with `true`. + * @param {Array>} array The array that will be altered. + * @param {number} x X coordinate. + * @param {number} y Y coordinate. + */ +function fillCircleArrayRowToMiddle(array, x, y) { + let i; + const radius = Math.floor(array.length / 2); + if (x >= radius) { + for (i = radius; i < x; i++) { + array[i][y] = true; + } + } else if (x < radius) { + for (i = x + 1; i < radius; i++) { + array[i][y] = true; + } + } +} + + +/** + * This methods creates a circle inside a fitting array. Points inside the + * circle are marked by true, points on the outside are undefined. + * It uses the midpoint circle algorithm. + * A cache is used to increase performance. + * @param {number} radius Radius. + * @returns {Array>} An array with marked circle points. + */ +export function getCircleArray(radius) { + if (circleArrayCache[radius] !== undefined) { + return circleArrayCache[radius]; + } + + const arraySize = radius * 2 + 1; + const arr = new Array(arraySize); + for (let i = 0; i < arraySize; i++) { + arr[i] = new Array(arraySize); + } + + let x = radius; + let y = 0; + let error = 0; + + while (x >= y) { + fillCircleArrayRowToMiddle(arr, radius + x, radius + y); + fillCircleArrayRowToMiddle(arr, radius + y, radius + x); + fillCircleArrayRowToMiddle(arr, radius - y, radius + x); + fillCircleArrayRowToMiddle(arr, radius - x, radius + y); + fillCircleArrayRowToMiddle(arr, radius - x, radius - y); + fillCircleArrayRowToMiddle(arr, radius - y, radius - x); + fillCircleArrayRowToMiddle(arr, radius + y, radius - x); + fillCircleArrayRowToMiddle(arr, radius + x, radius - y); + + y++; + error += 1 + 2 * y; + if (2 * (error - x) + 1 > 0) { + x -= 1; + error += 1 - 2 * x; + } + } + + circleArrayCache[radius] = arr; + return arr; +} + + +/** + * @param {!Object>} declutterReplays Declutter replays. + * @param {CanvasRenderingContext2D} context Context. + * @param {number} rotation Rotation. + * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. + */ +export function replayDeclutter(declutterReplays, context, rotation, snapToPixel) { + const zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction); + const skippedFeatureUids = {}; + for (let z = 0, zz = zs.length; z < zz; ++z) { + const replayData = declutterReplays[zs[z].toString()]; + for (let i = 0, ii = replayData.length; i < ii;) { + const replay = replayData[i++]; + const transform = replayData[i++]; + replay.replay(context, transform, rotation, skippedFeatureUids, snapToPixel); + } + } +} + + +export default InstructionsGroupExectuor; diff --git a/src/ol/render/canvas/Replay.js b/src/ol/render/canvas/Replay.js index 6b2b871057..143997d5f2 100644 --- a/src/ol/render/canvas/Replay.js +++ b/src/ol/render/canvas/Replay.js @@ -35,7 +35,7 @@ import { * @property {Array} coordinates The array of all coordinates. * @property {!Object} textStates The text states (decluttering). * @property {!Object} fillStates The fill states (decluttering). - * @property {!Object} strokeStates The stoke states (decluttering). + * @property {!Object} strokeStates The stroke states (decluttering). */ /** @@ -179,16 +179,6 @@ class CanvasReplay extends VectorContext { } - /** - * Recreate replays and populate them using the provided instructions. - * @param {SerializableInstructions} instructions The serializable instructions - */ - replaceInstructions(instructions) { - this.instructions = instructions.instructions; - this.hitDetectionInstructions = instructions.hitDetectionInstructions; - this.coordinates = instructions.coordinates; - } - /** * @param {CanvasRenderingContext2D} context Context. * @param {import("../../coordinate.js").Coordinate} p1 1st point of the background box. diff --git a/src/ol/render/canvas/TextReplay.js b/src/ol/render/canvas/TextReplay.js index eb7203f86f..16e138c913 100644 --- a/src/ol/render/canvas/TextReplay.js +++ b/src/ol/render/canvas/TextReplay.js @@ -139,16 +139,6 @@ class CanvasTextReplay extends CanvasReplay { return instructions; } - /** - * @inheritdoc - */ - replaceInstructions(instructions) { - super.replaceInstructions(instructions); - this.textStates = instructions.textStates; - this.fillStates = instructions.fillStates; - this.strokeStates = instructions.strokeStates; - } - /** * @inheritDoc */ diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index ea7666c134..f8d02528bd 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -11,6 +11,7 @@ import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; import RenderEventType from '../../render/EventType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; import CanvasReplayGroup from '../../render/canvas/ReplayGroup.js'; +import InstructionsGroupExecutor from '../../render/canvas/InstructionsGroupExecutor.js'; import CanvasLayerRenderer from './Layer.js'; import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -66,7 +67,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { /** * @private - * @type {import("../../render/canvas/ReplayGroup.js").default} + * @type {import("../../render/canvas/InstructionsGroupExecutor").default} */ this.replayGroup_ = null; @@ -482,7 +483,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { } const replayGroupInstructions = replayGroup.finish(); - const renderingReplayGroup = new CanvasReplayGroup( + const renderingReplayGroup = new InstructionsGroupExecutor( getRenderTolerance(resolution, pixelRatio), extent, resolution, pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer()); renderingReplayGroup.replaceInstructions(replayGroupInstructions); diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index f20fd72eab..35350f23a3 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -24,6 +24,7 @@ import { scale as scaleTransform, translate as translateTransform } from '../../transform.js'; +import InstructionsGroupExectuor from '../../render/canvas/InstructionsGroupExecutor.js'; /** @@ -227,7 +228,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { } } const replayGroupInstructions = replayGroup.finish(); - const renderingReplayGroup = new CanvasReplayGroup(0, sharedExtent, resolution, + const renderingReplayGroup = new InstructionsGroupExectuor(0, sharedExtent, resolution, pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer()); renderingReplayGroup.replaceInstructions(replayGroupInstructions); sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), renderingReplayGroup); From 8097be84192f2ec693f3fcc657453ab96b74f1fe Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Tue, 13 Nov 2018 16:47:10 +0100 Subject: [PATCH 03/30] Remove replay code from Instruction Builder class --- src/ol/render/canvas/Replay.js | 530 +-------------------------------- 1 file changed, 4 insertions(+), 526 deletions(-) diff --git a/src/ol/render/canvas/Replay.js b/src/ol/render/canvas/Replay.js index 143997d5f2..f7938df948 100644 --- a/src/ol/render/canvas/Replay.js +++ b/src/ol/render/canvas/Replay.js @@ -1,30 +1,22 @@ /** * @module ol/render/canvas/Replay */ -import {getUid} from '../../util.js'; import {equals, reverseSubArray} from '../../array.js'; import {asColorLike} from '../../colorlike.js'; -import {buffer, clone, coordinateRelationship, createEmpty, createOrUpdate, - createOrUpdateEmpty, extend, extendCoordinate, intersects} from '../../extent.js'; +import {buffer, clone, coordinateRelationship, + createOrUpdateEmpty} from '../../extent.js'; import Relationship from '../../extent/Relationship.js'; import GeometryType from '../../geom/GeometryType.js'; import {inflateCoordinates, inflateCoordinatesArray, inflateMultiCoordinatesArray} from '../../geom/flat/inflate.js'; -import {lineStringLength} from '../../geom/flat/length.js'; -import {drawTextOnPath} from '../../geom/flat/textpath.js'; -import {transform2D} from '../../geom/flat/transform.js'; import {CANVAS_LINE_DASH} from '../../has.js'; -import {isEmpty} from '../../obj.js'; import VectorContext from '../VectorContext.js'; -import {drawImage, resetTransform, defaultPadding, defaultFillStyle, defaultStrokeStyle, +import {drawImage, resetTransform, defaultFillStyle, defaultStrokeStyle, defaultMiterLimit, defaultLineWidth, defaultLineJoin, defaultLineDashOffset, defaultLineDash, defaultLineCap} from '../canvas.js'; import CanvasInstruction from './Instruction.js'; -import {TEXT_ALIGN} from '../replay.js'; import { create as createTransform, - compose as composeTransform, - apply as applyTransform, - setFromArray as transformSetFromArray + apply as applyTransform } from '../../transform.js'; @@ -38,17 +30,6 @@ import { * @property {!Object} strokeStates The stroke states (decluttering). */ -/** - * @type {import("../../extent.js").Extent} - */ -const tmpExtent = createEmpty(); - - -/** - * @type {!import("../../transform.js").Transform} - */ -const tmpTransform = createTransform(); - class CanvasReplay extends VectorContext { /** @@ -179,147 +160,6 @@ class CanvasReplay extends VectorContext { } - /** - * @param {CanvasRenderingContext2D} context Context. - * @param {import("../../coordinate.js").Coordinate} p1 1st point of the background box. - * @param {import("../../coordinate.js").Coordinate} p2 2nd point of the background box. - * @param {import("../../coordinate.js").Coordinate} p3 3rd point of the background box. - * @param {import("../../coordinate.js").Coordinate} p4 4th point of the background box. - * @param {Array<*>} fillInstruction Fill instruction. - * @param {Array<*>} strokeInstruction Stroke instruction. - */ - replayTextBackground_(context, p1, p2, p3, p4, fillInstruction, strokeInstruction) { - context.beginPath(); - context.moveTo.apply(context, p1); - context.lineTo.apply(context, p2); - context.lineTo.apply(context, p3); - context.lineTo.apply(context, p4); - context.lineTo.apply(context, p1); - if (fillInstruction) { - this.alignFill_ = /** @type {boolean} */ (fillInstruction[2]); - this.fill_(context); - } - if (strokeInstruction) { - this.setStrokeStyle_(context, /** @type {Array<*>} */ (strokeInstruction)); - context.stroke(); - } - } - - /** - * @param {CanvasRenderingContext2D} context Context. - * @param {number} x X. - * @param {number} y Y. - * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image. - * @param {number} anchorX Anchor X. - * @param {number} anchorY Anchor Y. - * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. - * @param {number} height Height. - * @param {number} opacity Opacity. - * @param {number} originX Origin X. - * @param {number} originY Origin Y. - * @param {number} rotation Rotation. - * @param {number} scale Scale. - * @param {boolean} snapToPixel Snap to pixel. - * @param {number} width Width. - * @param {Array} padding Padding. - * @param {Array<*>} fillInstruction Fill instruction. - * @param {Array<*>} strokeInstruction Stroke instruction. - */ - replayImage_( - context, - x, - y, - image, - anchorX, - anchorY, - declutterGroup, - height, - opacity, - originX, - originY, - rotation, - scale, - snapToPixel, - width, - padding, - fillInstruction, - strokeInstruction - ) { - const fillStroke = fillInstruction || strokeInstruction; - anchorX *= scale; - anchorY *= scale; - 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 boxW = padding[3] + w * scale + padding[1]; - const boxH = padding[0] + h * scale + padding[2]; - const boxX = x - padding[3]; - const boxY = y - padding[0]; - - /** @type {import("../../coordinate.js").Coordinate} */ - let p1; - /** @type {import("../../coordinate.js").Coordinate} */ - let p2; - /** @type {import("../../coordinate.js").Coordinate} */ - let p3; - /** @type {import("../../coordinate.js").Coordinate} */ - let p4; - if (fillStroke || rotation !== 0) { - p1 = [boxX, boxY]; - p2 = [boxX + boxW, boxY]; - p3 = [boxX + boxW, boxY + boxH]; - p4 = [boxX, boxY + boxH]; - } - - let transform = null; - if (rotation !== 0) { - const centerX = x + anchorX; - const centerY = y + anchorY; - transform = composeTransform(tmpTransform, centerX, centerY, 1, 1, rotation, -centerX, -centerY); - - createOrUpdateEmpty(tmpExtent); - extendCoordinate(tmpExtent, applyTransform(tmpTransform, p1)); - extendCoordinate(tmpExtent, applyTransform(tmpTransform, p2)); - extendCoordinate(tmpExtent, applyTransform(tmpTransform, p3)); - extendCoordinate(tmpExtent, applyTransform(tmpTransform, p4)); - } else { - createOrUpdate(boxX, boxY, boxX + boxW, boxY + boxH, tmpExtent); - } - const canvas = context.canvas; - const strokePadding = strokeInstruction ? (strokeInstruction[2] * scale / 2) : 0; - const intersects = - tmpExtent[0] - strokePadding <= canvas.width && tmpExtent[2] + strokePadding >= 0 && - tmpExtent[1] - strokePadding <= canvas.height && tmpExtent[3] + strokePadding >= 0; - - if (snapToPixel) { - x = Math.round(x); - y = Math.round(y); - } - - if (declutterGroup) { - if (!intersects && declutterGroup[4] == 1) { - return; - } - extend(declutterGroup, tmpExtent); - const declutterArgs = intersects ? - [context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] : - null; - if (declutterArgs && fillStroke) { - declutterArgs.push(fillInstruction, strokeInstruction, p1, p2, p3, p4); - } - declutterGroup.push(declutterArgs); - } else if (intersects) { - if (fillStroke) { - this.replayTextBackground_(context, p1, p2, p3, p4, - /** @type {Array<*>} */ (fillInstruction), - /** @type {Array<*>} */ (strokeInstruction)); - } - drawImage(context, transform, opacity, image, originX, originY, w, h, x, y, scale); - } - } - /** * @protected * @param {Array} dashArray Dash array. @@ -546,368 +386,6 @@ class CanvasReplay extends VectorContext { } } - /** - * @private - * @param {CanvasRenderingContext2D} context Context. - * @param {import("../../transform.js").Transform} transform Transform. - * @param {Object} skippedFeaturesHash Ids of features - * to skip. - * @param {Array<*>} instructions Instructions array. - * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. - * @param {function((import("../../Feature.js").default|import("../Feature.js").default)): T|undefined} featureCallback Feature callback. - * @param {import("../../extent.js").Extent=} opt_hitExtent Only check features that intersect this - * extent. - * @return {T|undefined} Callback result. - * @template T - */ - replay_( - context, - transform, - skippedFeaturesHash, - instructions, - snapToPixel, - featureCallback, - opt_hitExtent - ) { - /** @type {Array} */ - let pixelCoordinates; - if (this.pixelCoordinates_ && equals(transform, this.renderedTransform_)) { - pixelCoordinates = this.pixelCoordinates_; - } else { - if (!this.pixelCoordinates_) { - this.pixelCoordinates_ = []; - } - pixelCoordinates = transform2D( - this.coordinates, 0, this.coordinates.length, 2, - transform, this.pixelCoordinates_); - transformSetFromArray(this.renderedTransform_, transform); - } - const skipFeatures = !isEmpty(skippedFeaturesHash); - let i = 0; // instruction index - const ii = instructions.length; // end of instructions - let d = 0; // data index - let dd; // end of per-instruction data - let anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image; - let pendingFill = 0; - let pendingStroke = 0; - let lastFillInstruction = null; - let lastStrokeInstruction = null; - const coordinateCache = this.coordinateCache_; - const viewRotation = this.viewRotation_; - - const state = /** @type {import("../../render.js").State} */ ({ - context: context, - pixelRatio: this.pixelRatio, - resolution: this.resolution, - rotation: viewRotation - }); - - // When the batch size gets too big, performance decreases. 200 is a good - // balance between batch size and number of fill/stroke instructions. - const batchSize = this.instructions != instructions || this.overlaps ? 0 : 200; - let /** @type {import("../../Feature.js").default|import("../Feature.js").default} */ feature; - let x, y; - while (i < ii) { - const instruction = instructions[i]; - const type = /** @type {CanvasInstruction} */ (instruction[0]); - switch (type) { - case CanvasInstruction.BEGIN_GEOMETRY: - feature = /** @type {import("../../Feature.js").default|import("../Feature.js").default} */ (instruction[1]); - if ((skipFeatures && skippedFeaturesHash[getUid(feature)]) || !feature.getGeometry()) { - i = /** @type {number} */ (instruction[2]); - } else if (opt_hitExtent !== undefined && !intersects( - opt_hitExtent, feature.getGeometry().getExtent())) { - i = /** @type {number} */ (instruction[2]) + 1; - } else { - ++i; - } - break; - case CanvasInstruction.BEGIN_PATH: - if (pendingFill > batchSize) { - this.fill_(context); - pendingFill = 0; - } - if (pendingStroke > batchSize) { - context.stroke(); - pendingStroke = 0; - } - if (!pendingFill && !pendingStroke) { - context.beginPath(); - prevX = prevY = NaN; - } - ++i; - break; - case CanvasInstruction.CIRCLE: - d = /** @type {number} */ (instruction[1]); - const x1 = pixelCoordinates[d]; - const y1 = pixelCoordinates[d + 1]; - const x2 = pixelCoordinates[d + 2]; - const y2 = pixelCoordinates[d + 3]; - const dx = x2 - x1; - const dy = y2 - y1; - const r = Math.sqrt(dx * dx + dy * dy); - context.moveTo(x1 + r, y1); - context.arc(x1, y1, r, 0, 2 * Math.PI, true); - ++i; - break; - case CanvasInstruction.CLOSE_PATH: - context.closePath(); - ++i; - break; - case CanvasInstruction.CUSTOM: - d = /** @type {number} */ (instruction[1]); - dd = instruction[2]; - const geometry = /** @type {import("../../geom/SimpleGeometry.js").default} */ (instruction[3]); - const renderer = instruction[4]; - const fn = instruction.length == 6 ? instruction[5] : undefined; - state.geometry = geometry; - state.feature = feature; - if (!(i in coordinateCache)) { - coordinateCache[i] = []; - } - const coords = coordinateCache[i]; - if (fn) { - fn(pixelCoordinates, d, dd, 2, coords); - } else { - coords[0] = pixelCoordinates[d]; - coords[1] = pixelCoordinates[d + 1]; - coords.length = 2; - } - renderer(coords, state); - ++i; - break; - case CanvasInstruction.DRAW_IMAGE: - d = /** @type {number} */ (instruction[1]); - dd = /** @type {number} */ (instruction[2]); - image = /** @type {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} */ - (instruction[3]); - // Remaining arguments in DRAW_IMAGE are in alphabetical order - anchorX = /** @type {number} */ (instruction[4]); - anchorY = /** @type {number} */ (instruction[5]); - declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[6]); - const height = /** @type {number} */ (instruction[7]); - const opacity = /** @type {number} */ (instruction[8]); - const originX = /** @type {number} */ (instruction[9]); - const originY = /** @type {number} */ (instruction[10]); - const rotateWithView = /** @type {boolean} */ (instruction[11]); - let rotation = /** @type {number} */ (instruction[12]); - const scale = /** @type {number} */ (instruction[13]); - const width = /** @type {number} */ (instruction[14]); - - let padding, backgroundFill, backgroundStroke; - if (instruction.length > 16) { - padding = /** @type {Array} */ (instruction[15]); - backgroundFill = /** @type {boolean} */ (instruction[16]); - backgroundStroke = /** @type {boolean} */ (instruction[17]); - } else { - padding = defaultPadding; - backgroundFill = backgroundStroke = false; - } - - if (rotateWithView) { - rotation += viewRotation; - } - for (; d < dd; d += 2) { - this.replayImage_(context, - pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY, - declutterGroup, height, opacity, originX, originY, rotation, scale, - snapToPixel, width, padding, - backgroundFill ? /** @type {Array<*>} */ (lastFillInstruction) : null, - backgroundStroke ? /** @type {Array<*>} */ (lastStrokeInstruction) : null); - } - this.renderDeclutter_(declutterGroup, feature); - ++i; - break; - case CanvasInstruction.DRAW_CHARS: - const begin = /** @type {number} */ (instruction[1]); - const end = /** @type {number} */ (instruction[2]); - const baseline = /** @type {number} */ (instruction[3]); - declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[4]); - const overflow = /** @type {number} */ (instruction[5]); - const fillKey = /** @type {string} */ (instruction[6]); - const maxAngle = /** @type {number} */ (instruction[7]); - const measure = /** @type {function(string):number} */ (instruction[8]); - const offsetY = /** @type {number} */ (instruction[9]); - const strokeKey = /** @type {string} */ (instruction[10]); - const strokeWidth = /** @type {number} */ (instruction[11]); - const text = /** @type {string} */ (instruction[12]); - const textKey = /** @type {string} */ (instruction[13]); - const textScale = /** @type {number} */ (instruction[14]); - - const pathLength = lineStringLength(pixelCoordinates, begin, end, 2); - const textLength = measure(text); - if (overflow || textLength <= pathLength) { - /** @type {import("./TextReplay.js").default} */ - const textReplay = /** @type {?} */ (this); - const textAlign = textReplay.textStates[textKey].textAlign; - const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign]; - const parts = drawTextOnPath( - pixelCoordinates, begin, end, 2, text, measure, startM, maxAngle); - if (parts) { - let c, cc, chars, label, part; - if (strokeKey) { - for (c = 0, cc = parts.length; c < cc; ++c) { - part = parts[c]; // x, y, anchorX, rotation, chunk - chars = /** @type {string} */ (part[4]); - label = textReplay.getImage(chars, textKey, '', strokeKey); - anchorX = /** @type {number} */ (part[2]) + strokeWidth; - anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY; - this.replayImage_(context, - /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label, - anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, - /** @type {number} */ (part[3]), textScale, false, label.width, - defaultPadding, null, null); - } - } - if (fillKey) { - for (c = 0, cc = parts.length; c < cc; ++c) { - part = parts[c]; // x, y, anchorX, rotation, chunk - chars = /** @type {string} */ (part[4]); - label = textReplay.getImage(chars, textKey, fillKey, ''); - anchorX = /** @type {number} */ (part[2]); - anchorY = baseline * label.height - offsetY; - this.replayImage_(context, - /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label, - anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, - /** @type {number} */ (part[3]), textScale, false, label.width, - defaultPadding, null, null); - } - } - } - } - this.renderDeclutter_(declutterGroup, feature); - ++i; - break; - case CanvasInstruction.END_GEOMETRY: - if (featureCallback !== undefined) { - feature = /** @type {import("../../Feature.js").default|import("../Feature.js").default} */ (instruction[1]); - const result = featureCallback(feature); - if (result) { - return result; - } - } - ++i; - break; - case CanvasInstruction.FILL: - if (batchSize) { - pendingFill++; - } else { - this.fill_(context); - } - ++i; - break; - case CanvasInstruction.MOVE_TO_LINE_TO: - d = /** @type {number} */ (instruction[1]); - dd = /** @type {number} */ (instruction[2]); - x = pixelCoordinates[d]; - y = pixelCoordinates[d + 1]; - roundX = (x + 0.5) | 0; - roundY = (y + 0.5) | 0; - if (roundX !== prevX || roundY !== prevY) { - context.moveTo(x, y); - prevX = roundX; - prevY = roundY; - } - for (d += 2; d < dd; d += 2) { - x = pixelCoordinates[d]; - y = pixelCoordinates[d + 1]; - roundX = (x + 0.5) | 0; - roundY = (y + 0.5) | 0; - if (d == dd - 2 || roundX !== prevX || roundY !== prevY) { - context.lineTo(x, y); - prevX = roundX; - prevY = roundY; - } - } - ++i; - break; - case CanvasInstruction.SET_FILL_STYLE: - lastFillInstruction = instruction; - this.alignFill_ = instruction[2]; - - if (pendingFill) { - this.fill_(context); - pendingFill = 0; - if (pendingStroke) { - context.stroke(); - pendingStroke = 0; - } - } - - context.fillStyle = /** @type {import("../../colorlike.js").ColorLike} */ (instruction[1]); - ++i; - break; - case CanvasInstruction.SET_STROKE_STYLE: - lastStrokeInstruction = instruction; - if (pendingStroke) { - context.stroke(); - pendingStroke = 0; - } - this.setStrokeStyle_(context, /** @type {Array<*>} */ (instruction)); - ++i; - break; - case CanvasInstruction.STROKE: - if (batchSize) { - pendingStroke++; - } else { - context.stroke(); - } - ++i; - break; - default: - ++i; // consume the instruction anyway, to avoid an infinite loop - break; - } - } - if (pendingFill) { - this.fill_(context); - } - if (pendingStroke) { - context.stroke(); - } - return undefined; - } - - /** - * @param {CanvasRenderingContext2D} context Context. - * @param {import("../../transform.js").Transform} transform Transform. - * @param {number} viewRotation View rotation. - * @param {Object} skippedFeaturesHash Ids of features - * to skip. - * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. - */ - replay(context, transform, viewRotation, skippedFeaturesHash, snapToPixel) { - this.viewRotation_ = viewRotation; - this.replay_(context, transform, - skippedFeaturesHash, this.instructions, snapToPixel, undefined, undefined); - } - - /** - * @param {CanvasRenderingContext2D} context Context. - * @param {import("../../transform.js").Transform} transform Transform. - * @param {number} viewRotation View rotation. - * @param {Object} skippedFeaturesHash Ids of features - * to skip. - * @param {function((import("../../Feature.js").default|import("../Feature.js").default)): T=} opt_featureCallback - * Feature callback. - * @param {import("../../extent.js").Extent=} opt_hitExtent Only check features that intersect this - * extent. - * @return {T|undefined} Callback result. - * @template T - */ - replayHitDetection( - context, - transform, - viewRotation, - skippedFeaturesHash, - opt_featureCallback, - opt_hitExtent - ) { - this.viewRotation_ = viewRotation; - return this.replay_(context, transform, skippedFeaturesHash, - this.hitDetectionInstructions, true, opt_featureCallback, opt_hitExtent); - } - /** * Reverse the hit detection instructions. */ From f3bd08321a4cfa9d4538dd79e90eb44813ec480f Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Tue, 13 Nov 2018 16:50:08 +0100 Subject: [PATCH 04/30] Rename Replay to InstructionsBuilder --- src/ol/render/canvas/ImageReplay.js | 4 ++-- .../canvas/{Replay.js => InstructionsBuilder.js} | 8 ++++---- src/ol/render/canvas/InstructionsGroupExecutor.js | 2 +- src/ol/render/canvas/LineStringReplay.js | 4 ++-- src/ol/render/canvas/PolygonReplay.js | 4 ++-- src/ol/render/canvas/ReplayGroup.js | 14 +++++++------- src/ol/render/canvas/TextReplay.js | 4 ++-- 7 files changed, 20 insertions(+), 20 deletions(-) rename src/ol/render/canvas/{Replay.js => InstructionsBuilder.js} (97%) diff --git a/src/ol/render/canvas/ImageReplay.js b/src/ol/render/canvas/ImageReplay.js index e7f921889e..e9d71797ad 100644 --- a/src/ol/render/canvas/ImageReplay.js +++ b/src/ol/render/canvas/ImageReplay.js @@ -2,9 +2,9 @@ * @module ol/render/canvas/ImageReplay */ import CanvasInstruction from './Instruction.js'; -import CanvasReplay from './Replay.js'; +import CanvasInstructionsBuilder from './InstructionsBuilder.js'; -class CanvasImageReplay extends CanvasReplay { +class CanvasImageReplay extends CanvasInstructionsBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. diff --git a/src/ol/render/canvas/Replay.js b/src/ol/render/canvas/InstructionsBuilder.js similarity index 97% rename from src/ol/render/canvas/Replay.js rename to src/ol/render/canvas/InstructionsBuilder.js index f7938df948..162162077b 100644 --- a/src/ol/render/canvas/Replay.js +++ b/src/ol/render/canvas/InstructionsBuilder.js @@ -31,7 +31,7 @@ import { */ -class CanvasReplay extends VectorContext { +class CanvasInstructionsBuilder extends VectorContext { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. @@ -501,7 +501,7 @@ class CanvasReplay extends VectorContext { /** * @param {import("../canvas.js").FillStrokeState} state State. - * @param {function(this:CanvasReplay, import("../canvas.js").FillStrokeState, (import("../../geom/Geometry.js").default|import("../Feature.js").default)):Array<*>} createFill Create fill. + * @param {function(this:CanvasInstructionsBuilder, import("../canvas.js").FillStrokeState, (import("../../geom/Geometry.js").default|import("../Feature.js").default)):Array<*>} createFill Create fill. * @param {import("../../geom/Geometry.js").default|import("../Feature.js").default} geometry Geometry. */ updateFillStyle(state, createFill, geometry) { @@ -516,7 +516,7 @@ class CanvasReplay extends VectorContext { /** * @param {import("../canvas.js").FillStrokeState} state State. - * @param {function(this:CanvasReplay, import("../canvas.js").FillStrokeState)} applyStroke Apply stroke. + * @param {function(this:CanvasInstructionsBuilder, import("../canvas.js").FillStrokeState)} applyStroke Apply stroke. */ updateStrokeStyle(state, applyStroke) { const strokeStyle = state.strokeStyle; @@ -580,4 +580,4 @@ class CanvasReplay extends VectorContext { } -export default CanvasReplay; +export default CanvasInstructionsBuilder; diff --git a/src/ol/render/canvas/InstructionsGroupExecutor.js b/src/ol/render/canvas/InstructionsGroupExecutor.js index 1bff6b0834..dd3145718d 100644 --- a/src/ol/render/canvas/InstructionsGroupExecutor.js +++ b/src/ol/render/canvas/InstructionsGroupExecutor.js @@ -135,7 +135,7 @@ class InstructionsGroupExectuor extends ReplayGroup { /** * Recreate replays and populate them using the provided instructions. - * @param {!Object>} allInstructions The serializable instructions + * @param {!Object>} allInstructions The serializable instructions */ replaceInstructions(allInstructions) { this.replaysByZIndex_ = {}; diff --git a/src/ol/render/canvas/LineStringReplay.js b/src/ol/render/canvas/LineStringReplay.js index 8aed895832..4a7e90b140 100644 --- a/src/ol/render/canvas/LineStringReplay.js +++ b/src/ol/render/canvas/LineStringReplay.js @@ -2,9 +2,9 @@ * @module ol/render/canvas/LineStringReplay */ import CanvasInstruction, {strokeInstruction, beginPathInstruction} from './Instruction.js'; -import CanvasReplay from './Replay.js'; +import CanvasInstructionsBuilder from './InstructionsBuilder.js'; -class CanvasLineStringReplay extends CanvasReplay { +class CanvasLineStringReplay extends CanvasInstructionsBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. diff --git a/src/ol/render/canvas/PolygonReplay.js b/src/ol/render/canvas/PolygonReplay.js index 6d9c87b007..77bb2d71ff 100644 --- a/src/ol/render/canvas/PolygonReplay.js +++ b/src/ol/render/canvas/PolygonReplay.js @@ -7,10 +7,10 @@ import {defaultFillStyle} from '../canvas.js'; import CanvasInstruction, { fillInstruction, strokeInstruction, beginPathInstruction, closePathInstruction } from './Instruction.js'; -import CanvasReplay from './Replay.js'; +import CanvasInstructionsBuilder from './InstructionsBuilder.js'; -class CanvasPolygonReplay extends CanvasReplay { +class CanvasPolygonReplay extends CanvasInstructionsBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. diff --git a/src/ol/render/canvas/ReplayGroup.js b/src/ol/render/canvas/ReplayGroup.js index 6e52ee59a8..873af34410 100644 --- a/src/ol/render/canvas/ReplayGroup.js +++ b/src/ol/render/canvas/ReplayGroup.js @@ -9,7 +9,7 @@ import {transform2D} from '../../geom/flat/transform.js'; import {isEmpty} from '../../obj.js'; import ReplayGroup from '../ReplayGroup.js'; import ReplayType from '../ReplayType.js'; -import CanvasReplay from './Replay.js'; +import CanvasInstructionsBuilder from './InstructionsBuilder.js'; import CanvasImageReplay from './ImageReplay.js'; import CanvasLineStringReplay from './LineStringReplay.js'; import CanvasPolygonReplay from './PolygonReplay.js'; @@ -19,11 +19,11 @@ import {create as createTransform, compose as composeTransform} from '../../tran /** - * @type {Object} + * @type {Object} */ const BATCH_CONSTRUCTORS = { 'Circle': CanvasPolygonReplay, - 'Default': CanvasReplay, + 'Default': CanvasInstructionsBuilder, 'Image': CanvasImageReplay, 'LineString': CanvasLineStringReplay, 'Polygon': CanvasPolygonReplay, @@ -102,7 +102,7 @@ class CanvasReplayGroup extends ReplayGroup { /** * @private - * @type {!Object>} + * @type {!Object>} */ this.replaysByZIndex_ = {}; @@ -152,7 +152,7 @@ class CanvasReplayGroup extends ReplayGroup { /** * Recreate replays and populate them using the provided instructions. - * @param {!Object>} allInstructions The serializable instructions + * @param {!Object>} allInstructions The serializable instructions */ replaceInstructions(allInstructions) { this.replaysByZIndex_ = {}; @@ -183,7 +183,7 @@ class CanvasReplayGroup extends ReplayGroup { } /** - * @return {!Object>} The serializable instructions + * @return {!Object>} The serializable instructions */ finish() { const replaysInstructions = {}; @@ -359,7 +359,7 @@ class CanvasReplayGroup extends ReplayGroup { } /** - * @return {Object>} Replays. + * @return {Object>} Replays. */ getReplays() { return this.replaysByZIndex_; diff --git a/src/ol/render/canvas/TextReplay.js b/src/ol/render/canvas/TextReplay.js index 16e138c913..694fac4511 100644 --- a/src/ol/render/canvas/TextReplay.js +++ b/src/ol/render/canvas/TextReplay.js @@ -10,11 +10,11 @@ import GeometryType from '../../geom/GeometryType.js'; import {CANVAS_LINE_DASH} from '../../has.js'; import {labelCache, measureTextWidth, defaultTextAlign, measureTextHeight, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; import CanvasInstruction from './Instruction.js'; -import CanvasReplay from './Replay.js'; +import CanvasInstructionsBuilder from './InstructionsBuilder.js'; import {TEXT_ALIGN} from '../replay.js'; import TextPlacement from '../../style/TextPlacement.js'; -class CanvasTextReplay extends CanvasReplay { +class CanvasTextReplay extends CanvasInstructionsBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. From e2998631171db127e20f5b1a20d12e87ee9ae1c1 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Tue, 13 Nov 2018 16:57:03 +0100 Subject: [PATCH 05/30] More renaming --- src/ol/render/canvas/{ImageReplay.js => ImageBuilder.js} | 0 src/ol/render/canvas/InstructionsExecutor.js | 2 +- .../{ReplayGroup.js => InstructionsGroupBuilder.js} | 8 ++++---- .../canvas/{LineStringReplay.js => LineStringBuilder.js} | 0 .../render/canvas/{PolygonReplay.js => PolygonBuilder.js} | 0 src/ol/render/canvas/{TextReplay.js => TextBuilder.js} | 0 src/ol/renderer/canvas/VectorLayer.js | 4 ++-- src/ol/renderer/canvas/VectorTileLayer.js | 4 ++-- test/spec/ol/render/canvas/replaygroup.test.js | 2 +- test/spec/ol/render/canvas/textreplay.test.js | 2 +- test/spec/ol/renderer/canvas/replay.test.js | 8 ++++---- test/spec/ol/renderer/vector.test.js | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) rename src/ol/render/canvas/{ImageReplay.js => ImageBuilder.js} (100%) rename src/ol/render/canvas/{ReplayGroup.js => InstructionsGroupBuilder.js} (98%) rename src/ol/render/canvas/{LineStringReplay.js => LineStringBuilder.js} (100%) rename src/ol/render/canvas/{PolygonReplay.js => PolygonBuilder.js} (100%) rename src/ol/render/canvas/{TextReplay.js => TextBuilder.js} (100%) diff --git a/src/ol/render/canvas/ImageReplay.js b/src/ol/render/canvas/ImageBuilder.js similarity index 100% rename from src/ol/render/canvas/ImageReplay.js rename to src/ol/render/canvas/ImageBuilder.js diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index dbf169f44c..36168bfe12 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -1095,7 +1095,7 @@ class InstructionsExecutor { const pathLength = lineStringLength(pixelCoordinates, begin, end, 2); const textLength = measure(text); if (overflow || textLength <= pathLength) { - /** @type {import("./TextReplay.js").default} */ + /** @type {import("./TextBuilder.js").default} */ const textReplay = /** @type {?} */ (this); const textAlign = textReplay.textStates[textKey].textAlign; const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign]; diff --git a/src/ol/render/canvas/ReplayGroup.js b/src/ol/render/canvas/InstructionsGroupBuilder.js similarity index 98% rename from src/ol/render/canvas/ReplayGroup.js rename to src/ol/render/canvas/InstructionsGroupBuilder.js index 873af34410..276db14ecd 100644 --- a/src/ol/render/canvas/ReplayGroup.js +++ b/src/ol/render/canvas/InstructionsGroupBuilder.js @@ -10,10 +10,10 @@ import {isEmpty} from '../../obj.js'; import ReplayGroup from '../ReplayGroup.js'; import ReplayType from '../ReplayType.js'; import CanvasInstructionsBuilder from './InstructionsBuilder.js'; -import CanvasImageReplay from './ImageReplay.js'; -import CanvasLineStringReplay from './LineStringReplay.js'; -import CanvasPolygonReplay from './PolygonReplay.js'; -import CanvasTextReplay from './TextReplay.js'; +import CanvasImageReplay from './ImageBuilder.js'; +import CanvasLineStringReplay from './LineStringBuilder.js'; +import CanvasPolygonReplay from './PolygonBuilder.js'; +import CanvasTextReplay from './TextBuilder.js'; import {ORDER} from '../replay.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; diff --git a/src/ol/render/canvas/LineStringReplay.js b/src/ol/render/canvas/LineStringBuilder.js similarity index 100% rename from src/ol/render/canvas/LineStringReplay.js rename to src/ol/render/canvas/LineStringBuilder.js diff --git a/src/ol/render/canvas/PolygonReplay.js b/src/ol/render/canvas/PolygonBuilder.js similarity index 100% rename from src/ol/render/canvas/PolygonReplay.js rename to src/ol/render/canvas/PolygonBuilder.js diff --git a/src/ol/render/canvas/TextReplay.js b/src/ol/render/canvas/TextBuilder.js similarity index 100% rename from src/ol/render/canvas/TextReplay.js rename to src/ol/render/canvas/TextBuilder.js diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index f8d02528bd..4f2ebe9f1f 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -10,7 +10,7 @@ import rbush from 'rbush'; import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; import RenderEventType from '../../render/EventType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; -import CanvasReplayGroup from '../../render/canvas/ReplayGroup.js'; +import CanvasReplayGroup from '../../render/canvas/InstructionsGroupBuilder.js'; import InstructionsGroupExecutor from '../../render/canvas/InstructionsGroupExecutor.js'; import CanvasLayerRenderer from './Layer.js'; import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -503,7 +503,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { * @param {number} resolution Resolution. * @param {number} pixelRatio Pixel ratio. * @param {import("../../style/Style.js").default|Array} styles The style or array of styles. - * @param {import("../../render/canvas/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../../render/canvas/InstructionsGroupBuilder.js").default} replayGroup Replay group. * @return {boolean} `true` if an image is loading. */ renderFeature(feature, resolution, pixelRatio, styles, replayGroup) { diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 35350f23a3..87ebc363f2 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -13,7 +13,7 @@ import {equivalent as equivalentProjection} from '../../proj.js'; import Units from '../../proj/Units.js'; import ReplayType from '../../render/ReplayType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; -import CanvasReplayGroup, {replayDeclutter} from '../../render/canvas/ReplayGroup.js'; +import CanvasReplayGroup, {replayDeclutter} from '../../render/canvas/InstructionsGroupBuilder.js'; import {ORDER} from '../../render/replay.js'; import CanvasTileLayerRenderer from './TileLayer.js'; import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -424,7 +424,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @param {import("../../Feature.js").FeatureLike} feature Feature. * @param {number} squaredTolerance Squared tolerance. * @param {import("../../style/Style.js").default|Array} styles The style or array of styles. - * @param {import("../../render/canvas/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../../render/canvas/InstructionsGroupBuilder.js").default} replayGroup Replay group. * @return {boolean} `true` if an image is loading. */ renderFeature(feature, squaredTolerance, styles, replayGroup) { diff --git a/test/spec/ol/render/canvas/replaygroup.test.js b/test/spec/ol/render/canvas/replaygroup.test.js index ac00a62a22..7313a596d3 100644 --- a/test/spec/ol/render/canvas/replaygroup.test.js +++ b/test/spec/ol/render/canvas/replaygroup.test.js @@ -1,4 +1,4 @@ -import {getCircleArray} from '../../../../../src/ol/render/canvas/ReplayGroup.js'; +import {getCircleArray} from '../../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; describe('ol.render.canvas.ReplayGroup', function() { diff --git a/test/spec/ol/render/canvas/textreplay.test.js b/test/spec/ol/render/canvas/textreplay.test.js index e9cce55171..e5e3804d2b 100644 --- a/test/spec/ol/render/canvas/textreplay.test.js +++ b/test/spec/ol/render/canvas/textreplay.test.js @@ -1,7 +1,7 @@ import Feature from '../../../../../src/ol/Feature.js'; import MultiPolygon from '../../../../../src/ol/geom/MultiPolygon.js'; import Polygon from '../../../../../src/ol/geom/Polygon.js'; -import CanvasTextReplay from '../../../../../src/ol/render/canvas/TextReplay.js'; +import CanvasTextReplay from '../../../../../src/ol/render/canvas/TextBuilder.js'; import Text from '../../../../../src/ol/style/Text.js'; describe('ol.render.canvas.TextReplay', function() { diff --git a/test/spec/ol/renderer/canvas/replay.test.js b/test/spec/ol/renderer/canvas/replay.test.js index f449190ec3..9090ab5d56 100644 --- a/test/spec/ol/renderer/canvas/replay.test.js +++ b/test/spec/ol/renderer/canvas/replay.test.js @@ -7,10 +7,10 @@ import MultiPoint from '../../../../../src/ol/geom/MultiPoint.js'; import MultiPolygon from '../../../../../src/ol/geom/MultiPolygon.js'; import Point from '../../../../../src/ol/geom/Point.js'; import Polygon from '../../../../../src/ol/geom/Polygon.js'; -import CanvasLineStringReplay from '../../../../../src/ol/render/canvas/LineStringReplay.js'; -import CanvasPolygonReplay from '../../../../../src/ol/render/canvas/PolygonReplay.js'; -import CanvasReplay from '../../../../../src/ol/render/canvas/Replay.js'; -import CanvasReplayGroup from '../../../../../src/ol/render/canvas/ReplayGroup.js'; +import CanvasLineStringReplay from '../../../../../src/ol/render/canvas/LineStringBuilder.js'; +import CanvasPolygonReplay from '../../../../../src/ol/render/canvas/PolygonBuilder.js'; +import CanvasReplay from '../../../../../src/ol/render/canvas/InstructionsBuilder.js'; +import CanvasReplayGroup from '../../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; import {renderFeature} from '../../../../../src/ol/renderer/vector.js'; import Fill from '../../../../../src/ol/style/Fill.js'; import Stroke from '../../../../../src/ol/style/Stroke.js'; diff --git a/test/spec/ol/renderer/vector.test.js b/test/spec/ol/renderer/vector.test.js index 7d354b34e9..62e7050779 100644 --- a/test/spec/ol/renderer/vector.test.js +++ b/test/spec/ol/renderer/vector.test.js @@ -6,7 +6,7 @@ import Polygon from '../../../../src/ol/geom/Polygon.js'; import MultiLineString from '../../../../src/ol/geom/MultiLineString.js'; import MultiPoint from '../../../../src/ol/geom/MultiPoint.js'; import MultiPolygon from '../../../../src/ol/geom/MultiPolygon.js'; -import CanvasReplayGroup from '../../../../src/ol/render/canvas/ReplayGroup.js'; +import CanvasReplayGroup from '../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; import {renderFeature} from '../../../../src/ol/renderer/vector.js'; import Fill from '../../../../src/ol/style/Fill.js'; import Icon from '../../../../src/ol/style/Icon.js'; From 951d4d5adef7058002c79f780e0aeeb878fa111c Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Tue, 13 Nov 2018 17:29:24 +0100 Subject: [PATCH 06/30] Fix tests --- test/spec/ol/renderer/canvas/replay.test.js | 127 +++++++++++--------- 1 file changed, 72 insertions(+), 55 deletions(-) diff --git a/test/spec/ol/renderer/canvas/replay.test.js b/test/spec/ol/renderer/canvas/replay.test.js index 9090ab5d56..590e8e9e5d 100644 --- a/test/spec/ol/renderer/canvas/replay.test.js +++ b/test/spec/ol/renderer/canvas/replay.test.js @@ -7,10 +7,11 @@ import MultiPoint from '../../../../../src/ol/geom/MultiPoint.js'; import MultiPolygon from '../../../../../src/ol/geom/MultiPolygon.js'; import Point from '../../../../../src/ol/geom/Point.js'; import Polygon from '../../../../../src/ol/geom/Polygon.js'; -import CanvasLineStringReplay from '../../../../../src/ol/render/canvas/LineStringBuilder.js'; -import CanvasPolygonReplay from '../../../../../src/ol/render/canvas/PolygonBuilder.js'; +import CanvasLineStringBuilder from '../../../../../src/ol/render/canvas/LineStringBuilder.js'; +import CanvasPolygonBuilder from '../../../../../src/ol/render/canvas/PolygonBuilder.js'; import CanvasReplay from '../../../../../src/ol/render/canvas/InstructionsBuilder.js'; -import CanvasReplayGroup from '../../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; +import CanvasInstructionsGroupBuilder from '../../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; +import CanvasInstructionsGroupExecutor from '../../../../../src/ol/render/canvas/InstructionsGroupExecutor.js'; import {renderFeature} from '../../../../../src/ol/renderer/vector.js'; import Fill from '../../../../../src/ol/style/Fill.js'; import Stroke from '../../../../../src/ol/style/Stroke.js'; @@ -21,14 +22,15 @@ describe('ol.render.canvas.ReplayGroup', function() { describe('#replay', function() { - let context, replay, fillCount, transform; + let context, builder, executor, fillCount, transform; let strokeCount, beginPathCount, moveToCount, lineToCount; let feature0, feature1, feature2, feature3; let fill0, fill1, style1, style2; beforeEach(function() { transform = createTransform(); - replay = new CanvasReplayGroup(1, [-180, -90, 180, 90], 1, 1, false); + builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 1, false); + executor = new CanvasInstructionsGroupExecutor(1, [-180, -90, 180, 90], 1, 1, false); feature0 = new Feature(new Polygon( [[[-90, 0], [-45, 45], [0, 0], [1, 1], [0, -45], [-90, 0]]])); feature1 = new Feature(new Polygon( @@ -88,95 +90,106 @@ describe('ol.render.canvas.ReplayGroup', function() { }); it('omits lineTo for repeated coordinates', function() { - renderFeature(replay, feature0, fill0, 1); - replay.replay(context, transform, 0, {}); + renderFeature(builder, feature0, fill0, 1); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, {}); expect(lineToCount).to.be(4); lineToCount = 0; scaleTransform(transform, 0.25, 0.25); - replay.replay(context, transform, 0, {}); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, {}); expect(lineToCount).to.be(3); }); it('does not omit moveTo for repeated coordinates', function() { - renderFeature(replay, feature0, fill0, 1); - renderFeature(replay, feature1, fill1, 1); - replay.replay(context, transform, 0, {}); + renderFeature(builder, feature0, fill0, 1); + renderFeature(builder, feature1, fill1, 1); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, {}); expect(moveToCount).to.be(2); }); it('batches fill and stroke instructions for same style', function() { - renderFeature(replay, feature1, style1, 1); - renderFeature(replay, feature2, style1, 1); - renderFeature(replay, feature3, style1, 1); - replay.replay(context, transform, 0, {}); + renderFeature(builder, feature1, style1, 1); + renderFeature(builder, feature2, style1, 1); + renderFeature(builder, feature3, style1, 1); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, {}); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); }); it('batches fill and stroke instructions for different styles', function() { - renderFeature(replay, feature1, style1, 1); - renderFeature(replay, feature2, style1, 1); - renderFeature(replay, feature3, style2, 1); - replay.replay(context, transform, 0, {}); + renderFeature(builder, feature1, style1, 1); + renderFeature(builder, feature2, style1, 1); + renderFeature(builder, feature3, style2, 1); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, {}); expect(fillCount).to.be(2); expect(strokeCount).to.be(2); expect(beginPathCount).to.be(2); }); it('batches fill and stroke instructions for changing styles', function() { - renderFeature(replay, feature1, style1, 1); - renderFeature(replay, feature2, style2, 1); - renderFeature(replay, feature3, style1, 1); - replay.replay(context, transform, 0, {}); + renderFeature(builder, feature1, style1, 1); + renderFeature(builder, feature2, style2, 1); + renderFeature(builder, feature3, style1, 1); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, {}); expect(fillCount).to.be(3); expect(strokeCount).to.be(3); expect(beginPathCount).to.be(3); }); it('batches fill and stroke instructions for skipped feature at the beginning', function() { - renderFeature(replay, feature1, style1, 1); - renderFeature(replay, feature2, style2, 1); - renderFeature(replay, feature3, style2, 1); + renderFeature(builder, feature1, style1, 1); + renderFeature(builder, feature2, style2, 1); + renderFeature(builder, feature3, style2, 1); const skippedUids = {}; skippedUids[getUid(feature1)] = true; - replay.replay(context, transform, 0, skippedUids); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, skippedUids); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); }); it('batches fill and stroke instructions for skipped feature at the end', function() { - renderFeature(replay, feature1, style1, 1); - renderFeature(replay, feature2, style1, 1); - renderFeature(replay, feature3, style2, 1); + renderFeature(builder, feature1, style1, 1); + renderFeature(builder, feature2, style1, 1); + renderFeature(builder, feature3, style2, 1); const skippedUids = {}; skippedUids[getUid(feature3)] = true; - replay.replay(context, transform, 0, skippedUids); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, skippedUids); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); }); it('batches fill and stroke instructions for skipped features', function() { - renderFeature(replay, feature1, style1, 1); - renderFeature(replay, feature2, style1, 1); - renderFeature(replay, feature3, style2, 1); + renderFeature(builder, feature1, style1, 1); + renderFeature(builder, feature2, style1, 1); + renderFeature(builder, feature3, style2, 1); const skippedUids = {}; skippedUids[getUid(feature1)] = true; skippedUids[getUid(feature2)] = true; - replay.replay(context, transform, 0, skippedUids); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, skippedUids); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); }); it('does not batch when overlaps is set to true', function() { - replay = new CanvasReplayGroup(1, [-180, -90, 180, 90], 1, 1, true); - renderFeature(replay, feature1, style1, 1); - renderFeature(replay, feature2, style1, 1); - renderFeature(replay, feature3, style1, 1); - replay.replay(context, transform, 0, {}); + builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 1, true); + executor = new CanvasInstructionsGroupExecutor(1, [-180, -90, 180, 90], 1, 1, true); + renderFeature(builder, feature1, style1, 1); + renderFeature(builder, feature2, style1, 1); + renderFeature(builder, feature3, style1, 1); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, {}); expect(fillCount).to.be(3); expect(strokeCount).to.be(3); expect(beginPathCount).to.be(3); @@ -184,7 +197,8 @@ describe('ol.render.canvas.ReplayGroup', function() { it('applies the pixelRatio to the linedash array and offset', function() { // replay with a pixelRatio of 2 - replay = new CanvasReplayGroup(1, [-180, -90, 180, 90], 1, 2, true); + builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 2, true); + executor = new CanvasInstructionsGroupExecutor(1, [-180, -90, 180, 90], 1, 2, true); let lineDash, lineDashCount = 0, lineDashOffset, lineDashOffsetCount = 0; @@ -201,9 +215,10 @@ describe('ol.render.canvas.ReplayGroup', function() { } }); - renderFeature(replay, feature1, style2, 1); - renderFeature(replay, feature2, style2, 1); - replay.replay(context, transform, 0, {}); + renderFeature(builder, feature1, style2, 1); + renderFeature(builder, feature2, style2, 1); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, {}); expect(lineDashCount).to.be(1); expect(style2.getStroke().getLineDash()).to.eql([3, 6]); @@ -241,16 +256,18 @@ describe('ol.render.canvas.ReplayGroup', function() { [polygon.getGeometry().getCoordinates(), polygon.getGeometry().getCoordinates()])); const geometrycollection = new Feature(new GeometryCollection( [point.getGeometry(), linestring.getGeometry(), polygon.getGeometry()])); - replay = new CanvasReplayGroup(1, [-180, -90, 180, 90], 1, 1, true); - renderFeature(replay, point, style, 1); - renderFeature(replay, multipoint, style, 1); - renderFeature(replay, linestring, style, 1); - renderFeature(replay, multilinestring, style, 1); - renderFeature(replay, polygon, style, 1); - renderFeature(replay, multipolygon, style, 1); - renderFeature(replay, geometrycollection, style, 1); + builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 1, true); + executor = new CanvasInstructionsGroupExecutor(1, [-180, -90, 180, 90], 1, 1, true); + renderFeature(builder, point, style, 1); + renderFeature(builder, multipoint, style, 1); + renderFeature(builder, linestring, style, 1); + renderFeature(builder, multilinestring, style, 1); + renderFeature(builder, polygon, style, 1); + renderFeature(builder, multipolygon, style, 1); + renderFeature(builder, geometrycollection, style, 1); scaleTransform(transform, 0.1, 0.1); - replay.replay(context, transform, 0, {}); + executor.replaceInstructions(builder.finish()); + executor.replay(context, transform, 0, {}); expect(calls.length).to.be(9); expect(calls[0].geometry).to.be(point.getGeometry()); expect(calls[0].feature).to.be(point); @@ -446,7 +463,7 @@ describe('ol.render.canvas.LineStringReplay', function() { const tolerance = 1; const extent = [-180, -90, 180, 90]; const resolution = 10; - const replay = new CanvasLineStringReplay(tolerance, extent, + const replay = new CanvasLineStringBuilder(tolerance, extent, resolution); const stroke = new Stroke({ width: 2 @@ -468,7 +485,7 @@ describe('ol.render.canvas.PolygonReplay', function() { const tolerance = 1; const extent = [-180, -90, 180, 90]; const resolution = 10; - replay = new CanvasPolygonReplay(tolerance, extent, + replay = new CanvasPolygonBuilder(tolerance, extent, resolution); }); From ba6a6fff7d94f6b099a62ecf729e5d14c3c1e609 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Tue, 13 Nov 2018 17:29:37 +0100 Subject: [PATCH 07/30] Module renaming --- src/ol/render/canvas/ImageBuilder.js | 6 ++--- src/ol/render/canvas/InstructionsBuilder.js | 2 +- src/ol/render/canvas/InstructionsExecutor.js | 6 ++--- .../render/canvas/InstructionsGroupBuilder.js | 24 +++++++++---------- .../canvas/InstructionsGroupExecutor.js | 6 ++--- src/ol/render/canvas/LineStringBuilder.js | 6 ++--- src/ol/render/canvas/PolygonBuilder.js | 6 ++--- src/ol/render/canvas/TextBuilder.js | 6 ++--- src/ol/renderer/canvas/VectorLayer.js | 4 ++-- src/ol/renderer/canvas/VectorTileLayer.js | 10 ++++---- 10 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/ol/render/canvas/ImageBuilder.js b/src/ol/render/canvas/ImageBuilder.js index e9d71797ad..72c1add279 100644 --- a/src/ol/render/canvas/ImageBuilder.js +++ b/src/ol/render/canvas/ImageBuilder.js @@ -1,10 +1,10 @@ /** - * @module ol/render/canvas/ImageReplay + * @module ol/render/canvas/ImageBuilder */ import CanvasInstruction from './Instruction.js'; import CanvasInstructionsBuilder from './InstructionsBuilder.js'; -class CanvasImageReplay extends CanvasInstructionsBuilder { +class CanvasImageBuilder extends CanvasInstructionsBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. @@ -214,4 +214,4 @@ class CanvasImageReplay extends CanvasInstructionsBuilder { } -export default CanvasImageReplay; +export default CanvasImageBuilder; diff --git a/src/ol/render/canvas/InstructionsBuilder.js b/src/ol/render/canvas/InstructionsBuilder.js index 162162077b..b24c647a42 100644 --- a/src/ol/render/canvas/InstructionsBuilder.js +++ b/src/ol/render/canvas/InstructionsBuilder.js @@ -1,5 +1,5 @@ /** - * @module ol/render/canvas/Replay + * @module ol/render/canvas/InstructionsBuilder */ import {equals, reverseSubArray} from '../../array.js'; import {asColorLike} from '../../colorlike.js'; diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index 36168bfe12..658576a0aa 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -1,5 +1,5 @@ /** - * @module ol/render/canvas/Replay + * @module ol/render/canvas/InstructionsExecutor */ import {getUid} from '../../util.js'; import {equals, reverseSubArray} from '../../array.js'; @@ -52,7 +52,7 @@ const tmpExtent = createEmpty(); const tmpTransform = createTransform(); -class InstructionsExecutor { +class CanvasInstructionsExecutor { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. @@ -1344,7 +1344,7 @@ class InstructionsExecutor { } -export default InstructionsExecutor; +export default CanvasInstructionsExecutor; /////////////// Below is code copied from TextReplay ///////////////// diff --git a/src/ol/render/canvas/InstructionsGroupBuilder.js b/src/ol/render/canvas/InstructionsGroupBuilder.js index 276db14ecd..4fee44defc 100644 --- a/src/ol/render/canvas/InstructionsGroupBuilder.js +++ b/src/ol/render/canvas/InstructionsGroupBuilder.js @@ -1,5 +1,5 @@ /** - * @module ol/render/canvas/ReplayGroup + * @module ol/render/canvas/InstructionsGroupBuilder */ import {numberSafeCompareFunction} from '../../array.js'; @@ -10,10 +10,10 @@ import {isEmpty} from '../../obj.js'; import ReplayGroup from '../ReplayGroup.js'; import ReplayType from '../ReplayType.js'; import CanvasInstructionsBuilder from './InstructionsBuilder.js'; -import CanvasImageReplay from './ImageBuilder.js'; -import CanvasLineStringReplay from './LineStringBuilder.js'; -import CanvasPolygonReplay from './PolygonBuilder.js'; -import CanvasTextReplay from './TextBuilder.js'; +import CanvasImageBuilder from './ImageBuilder.js'; +import CanvasLineStringBuilder from './LineStringBuilder.js'; +import CanvasPolygonBuilder from './PolygonBuilder.js'; +import CanvasTextBuilder from './TextBuilder.js'; import {ORDER} from '../replay.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; @@ -22,16 +22,16 @@ import {create as createTransform, compose as composeTransform} from '../../tran * @type {Object} */ const BATCH_CONSTRUCTORS = { - 'Circle': CanvasPolygonReplay, + 'Circle': CanvasPolygonBuilder, 'Default': CanvasInstructionsBuilder, - 'Image': CanvasImageReplay, - 'LineString': CanvasLineStringReplay, - 'Polygon': CanvasPolygonReplay, - 'Text': CanvasTextReplay + 'Image': CanvasImageBuilder, + 'LineString': CanvasLineStringBuilder, + 'Polygon': CanvasPolygonBuilder, + 'Text': CanvasTextBuilder }; -class CanvasReplayGroup extends ReplayGroup { +class CanvasInstructionsGroupBuilder extends ReplayGroup { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Max extent. @@ -528,4 +528,4 @@ export function replayDeclutter(declutterReplays, context, rotation, snapToPixel } -export default CanvasReplayGroup; +export default CanvasInstructionsGroupBuilder; diff --git a/src/ol/render/canvas/InstructionsGroupExecutor.js b/src/ol/render/canvas/InstructionsGroupExecutor.js index dd3145718d..1f9ba3ccf7 100644 --- a/src/ol/render/canvas/InstructionsGroupExecutor.js +++ b/src/ol/render/canvas/InstructionsGroupExecutor.js @@ -1,5 +1,5 @@ /** - * @module ol/render/canvas/ReplayGroup + * @module ol/render/canvas/InstructionsGroupExecutor */ import {numberSafeCompareFunction} from '../../array.js'; @@ -11,7 +11,7 @@ import ReplayGroup from '../ReplayGroup.js'; import ReplayType from '../ReplayType.js'; import {ORDER} from '../replay.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; -import InstructionsExecutor from './InstructionsExecutor.js'; +import CanvasInstructionsExecutor from './InstructionsExecutor.js'; class InstructionsGroupExectuor extends ReplayGroup { @@ -311,7 +311,7 @@ class InstructionsGroupExectuor extends ReplayGroup { } let replay = replays[replayType]; if (replay === undefined) { - replay = new InstructionsExecutor(this.tolerance_, this.maxExtent_, + replay = new CanvasInstructionsExecutor(this.tolerance_, this.maxExtent_, this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_); replays[replayType] = replay; } diff --git a/src/ol/render/canvas/LineStringBuilder.js b/src/ol/render/canvas/LineStringBuilder.js index 4a7e90b140..f8990d6959 100644 --- a/src/ol/render/canvas/LineStringBuilder.js +++ b/src/ol/render/canvas/LineStringBuilder.js @@ -1,10 +1,10 @@ /** - * @module ol/render/canvas/LineStringReplay + * @module ol/render/canvas/LineStringBuilder */ import CanvasInstruction, {strokeInstruction, beginPathInstruction} from './Instruction.js'; import CanvasInstructionsBuilder from './InstructionsBuilder.js'; -class CanvasLineStringReplay extends CanvasInstructionsBuilder { +class CanvasLineStringBuilder extends CanvasInstructionsBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. @@ -115,4 +115,4 @@ class CanvasLineStringReplay extends CanvasInstructionsBuilder { } -export default CanvasLineStringReplay; +export default CanvasLineStringBuilder; diff --git a/src/ol/render/canvas/PolygonBuilder.js b/src/ol/render/canvas/PolygonBuilder.js index 77bb2d71ff..f0d0107b67 100644 --- a/src/ol/render/canvas/PolygonBuilder.js +++ b/src/ol/render/canvas/PolygonBuilder.js @@ -1,5 +1,5 @@ /** - * @module ol/render/canvas/PolygonReplay + * @module ol/render/canvas/PolygonBuilder */ import {asString} from '../../color.js'; import {snap} from '../../geom/flat/simplify.js'; @@ -10,7 +10,7 @@ import CanvasInstruction, { import CanvasInstructionsBuilder from './InstructionsBuilder.js'; -class CanvasPolygonReplay extends CanvasInstructionsBuilder { +class CanvasPolygonBuilder extends CanvasInstructionsBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. @@ -212,4 +212,4 @@ class CanvasPolygonReplay extends CanvasInstructionsBuilder { } -export default CanvasPolygonReplay; +export default CanvasPolygonBuilder; diff --git a/src/ol/render/canvas/TextBuilder.js b/src/ol/render/canvas/TextBuilder.js index 694fac4511..43713f6f1f 100644 --- a/src/ol/render/canvas/TextBuilder.js +++ b/src/ol/render/canvas/TextBuilder.js @@ -1,5 +1,5 @@ /** - * @module ol/render/canvas/TextReplay + * @module ol/render/canvas/TextBuilder */ import {getUid} from '../../util.js'; import {asColorLike} from '../../colorlike.js'; @@ -14,7 +14,7 @@ import CanvasInstructionsBuilder from './InstructionsBuilder.js'; import {TEXT_ALIGN} from '../replay.js'; import TextPlacement from '../../style/TextPlacement.js'; -class CanvasTextReplay extends CanvasInstructionsBuilder { +class CanvasTextBuilder extends CanvasInstructionsBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. @@ -547,4 +547,4 @@ export function measureTextWidths(font, lines, widths) { } -export default CanvasTextReplay; +export default CanvasTextBuilder; diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 4f2ebe9f1f..002f07e800 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -10,7 +10,7 @@ import rbush from 'rbush'; import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; import RenderEventType from '../../render/EventType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; -import CanvasReplayGroup from '../../render/canvas/InstructionsGroupBuilder.js'; +import CanvasInstructionsGroupBuilder from '../../render/canvas/InstructionsGroupBuilder.js'; import InstructionsGroupExecutor from '../../render/canvas/InstructionsGroupExecutor.js'; import CanvasLayerRenderer from './Layer.js'; import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -444,7 +444,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { this.dirty_ = false; - const replayGroup = new CanvasReplayGroup( + const replayGroup = new CanvasInstructionsGroupBuilder( getRenderTolerance(resolution, pixelRatio), extent, resolution, pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer()); vectorSource.loadFeatures(extent, resolution, projection); diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 87ebc363f2..d14274912e 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -13,7 +13,7 @@ import {equivalent as equivalentProjection} from '../../proj.js'; import Units from '../../proj/Units.js'; import ReplayType from '../../render/ReplayType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; -import CanvasReplayGroup, {replayDeclutter} from '../../render/canvas/InstructionsGroupBuilder.js'; +import CanvasInstructionsGroupBuilder, {replayDeclutter} from '../../render/canvas/InstructionsGroupBuilder.js'; import {ORDER} from '../../render/replay.js'; import CanvasTileLayerRenderer from './TileLayer.js'; import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -187,7 +187,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { sourceTile.setProjection(projection); } replayState.dirty = false; - const replayGroup = new CanvasReplayGroup(0, sharedExtent, resolution, + const replayGroup = new CanvasInstructionsGroupBuilder(0, sharedExtent, resolution, pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer()); const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio); @@ -263,7 +263,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (sourceTile.getState() != TileState.LOADED) { continue; } - const replayGroup = /** @type {CanvasReplayGroup} */ (sourceTile.getReplayGroup(layer, + const replayGroup = /** @type {CanvasInstructionsGroupBuilder} */ (sourceTile.getReplayGroup(layer, tile.tileCoord.toString())); found = found || replayGroup.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, {}, /** @@ -371,7 +371,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (sourceTile.getState() != TileState.LOADED) { continue; } - const replayGroup = /** @type {CanvasReplayGroup} */ (sourceTile.getReplayGroup(layer, tileCoord.toString())); + const replayGroup = /** @type {CanvasInstructionsGroupBuilder} */ (sourceTile.getReplayGroup(layer, tileCoord.toString())); if (!replayGroup || !replayGroup.hasReplays(replayTypes)) { // sourceTile was not yet loaded when this.createReplayGroup_() was // called, or it has no replays of the types we want to render @@ -478,7 +478,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const transform = resetTransform(this.tmpTransform_); scaleTransform(transform, pixelScale, -pixelScale); translateTransform(transform, -tileExtent[0], -tileExtent[3]); - const replayGroup = /** @type {CanvasReplayGroup} */ (sourceTile.getReplayGroup(layer, + const replayGroup = /** @type {CanvasInstructionsGroupBuilder} */ (sourceTile.getReplayGroup(layer, tile.tileCoord.toString())); replayGroup.replay(context, transform, 0, {}, true, replays); } From f5a8ad63f9a50cb40444deee80d7fbed617c8b88 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Tue, 13 Nov 2018 17:32:06 +0100 Subject: [PATCH 08/30] Improve typing --- src/ol/render/canvas/InstructionsBuilder.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ol/render/canvas/InstructionsBuilder.js b/src/ol/render/canvas/InstructionsBuilder.js index b24c647a42..1eaebb4c69 100644 --- a/src/ol/render/canvas/InstructionsBuilder.js +++ b/src/ol/render/canvas/InstructionsBuilder.js @@ -25,9 +25,9 @@ import { * @property {Array<*>} instructions The rendering instructions. * @property {Array<*>} hitDetectionInstructions The rendering hit detection instructions. * @property {Array} coordinates The array of all coordinates. - * @property {!Object} textStates The text states (decluttering). - * @property {!Object} fillStates The fill states (decluttering). - * @property {!Object} strokeStates The stroke states (decluttering). + * @property {!Object} [textStates] The text states (decluttering). + * @property {!Object} [fillStates] The fill states (decluttering). + * @property {!Object} [strokeStates] The stroke states (decluttering). */ @@ -306,7 +306,7 @@ class CanvasInstructionsBuilder extends VectorContext { } /** - * @return {Object} the serializable instructions. + * @return {SerializableInstructions} the serializable instructions. */ finish() { return { From 3170355b0797799829f1ff0897694fd325ce286e Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Tue, 13 Nov 2018 17:33:53 +0100 Subject: [PATCH 09/30] Remove unused function from executor --- src/ol/render/canvas/InstructionsExecutor.js | 123 ------------------- 1 file changed, 123 deletions(-) diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index 658576a0aa..7f6f12b34a 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -24,10 +24,7 @@ import { /// Imports copied from TextReplay import {asColorLike} from '../../colorlike.js'; import {createCanvasContext2D} from '../../dom.js'; -import {matchingChunk} from '../../geom/flat/straightchunk.js'; -import GeometryType from '../../geom/GeometryType.js'; import {labelCache, measureTextWidth, defaultTextAlign, measureTextHeight, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; -import TextPlacement from '../../style/TextPlacement.js'; /** @@ -278,126 +275,6 @@ class CanvasInstructionsExecutor { } - /** - * @inheritDoc - */ - drawText(geometry, feature) { - const fillState = this.textFillState_; - const strokeState = this.textStrokeState_; - const textState = this.textState_; - if (this.text_ === '' || !textState || (!fillState && !strokeState)) { - return; - } - - let begin = this.coordinates.length; - - const geometryType = geometry.getType(); - let flatCoordinates = null; - let end = 2; - let stride = 2; - let i, ii; - - if (textState.placement === TextPlacement.LINE) { - if (!intersects(this.getBufferedMaxExtent(), geometry.getExtent())) { - return; - } - let ends; - flatCoordinates = geometry.getFlatCoordinates(); - stride = geometry.getStride(); - if (geometryType == GeometryType.LINE_STRING) { - ends = [flatCoordinates.length]; - } else if (geometryType == GeometryType.MULTI_LINE_STRING) { - ends = geometry.getEnds(); - } else if (geometryType == GeometryType.POLYGON) { - ends = geometry.getEnds().slice(0, 1); - } else if (geometryType == GeometryType.MULTI_POLYGON) { - const endss = geometry.getEndss(); - ends = []; - for (i = 0, ii = endss.length; i < ii; ++i) { - ends.push(endss[i][0]); - } - } - this.beginGeometry(geometry, feature); - const textAlign = textState.textAlign; - let flatOffset = 0; - let flatEnd; - for (let o = 0, oo = ends.length; o < oo; ++o) { - if (textAlign == undefined) { - const range = matchingChunk(textState.maxAngle, flatCoordinates, flatOffset, ends[o], stride); - flatOffset = range[0]; - flatEnd = range[1]; - } else { - flatEnd = ends[o]; - } - for (i = flatOffset; i < flatEnd; i += stride) { - this.coordinates.push(flatCoordinates[i], flatCoordinates[i + 1]); - } - end = this.coordinates.length; - flatOffset = ends[o]; - this.drawChars_(begin, end, this.declutterGroup_); - begin = end; - } - this.endGeometry(geometry, feature); - - } else { - const label = this.getImage(this.text_, this.textKey_, this.fillKey_, this.strokeKey_); - const width = label.width / this.pixelRatio; - switch (geometryType) { - case GeometryType.POINT: - case GeometryType.MULTI_POINT: - flatCoordinates = geometry.getFlatCoordinates(); - end = flatCoordinates.length; - break; - case GeometryType.LINE_STRING: - flatCoordinates = /** @type {import("../../geom/LineString.js").default} */ (geometry).getFlatMidpoint(); - break; - case GeometryType.CIRCLE: - flatCoordinates = /** @type {import("../../geom/Circle.js").default} */ (geometry).getCenter(); - break; - case GeometryType.MULTI_LINE_STRING: - flatCoordinates = /** @type {import("../../geom/MultiLineString.js").default} */ (geometry).getFlatMidpoints(); - end = flatCoordinates.length; - break; - case GeometryType.POLYGON: - flatCoordinates = /** @type {import("../../geom/Polygon.js").default} */ (geometry).getFlatInteriorPoint(); - if (!textState.overflow && flatCoordinates[2] / this.resolution < width) { - return; - } - stride = 3; - break; - case GeometryType.MULTI_POLYGON: - const interiorPoints = /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry).getFlatInteriorPoints(); - flatCoordinates = []; - for (i = 0, ii = interiorPoints.length; i < ii; i += 3) { - if (textState.overflow || interiorPoints[i + 2] / this.resolution >= width) { - flatCoordinates.push(interiorPoints[i], interiorPoints[i + 1]); - } - } - end = flatCoordinates.length; - if (end == 0) { - return; - } - break; - default: - } - end = this.appendFlatCoordinates(flatCoordinates, 0, end, stride, false, false); - if (textState.backgroundFill || textState.backgroundStroke) { - this.setFillStrokeStyle(textState.backgroundFill, textState.backgroundStroke); - if (textState.backgroundFill) { - this.updateFillStyle(this.state, this.createFill, geometry); - this.hitDetectionInstructions.push(this.createFill(this.state, geometry)); - } - if (textState.backgroundStroke) { - this.updateStrokeStyle(this.state, this.applyStroke); - this.hitDetectionInstructions.push(this.createStroke(this.state)); - } - } - this.beginGeometry(geometry, feature); - this.drawTextImage_(label, begin, end); - this.endGeometry(geometry, feature); - } - } - /** * @param {string} text Text. * @param {string} textKey Text style key. From 3d203f990edb1df8656104eb82d9c53549588e23 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 13 Nov 2018 17:32:27 +0100 Subject: [PATCH 10/30] Move measureTextWidths to render/canvas --- src/ol/render/canvas.js | 19 ++++++++++++++++ src/ol/render/canvas/InstructionsExecutor.js | 23 +------------------- src/ol/render/canvas/TextBuilder.js | 21 +----------------- 3 files changed, 21 insertions(+), 42 deletions(-) diff --git a/src/ol/render/canvas.js b/src/ol/render/canvas.js index f338e47d7f..43bdc72fc0 100644 --- a/src/ol/render/canvas.js +++ b/src/ol/render/canvas.js @@ -324,6 +324,25 @@ export function measureTextWidth(font, text) { } +/** + * @param {string} font Font to use for measuring. + * @param {Array} lines Lines to measure. + * @param {Array} widths Array will be populated with the widths of + * each line. + * @return {number} Width of the whole text. + */ +export function measureTextWidths(font, lines, widths) { + const numLines = lines.length; + let width = 0; + for (let i = 0; i < numLines; ++i) { + const currentWidth = measureTextWidth(font, lines[i]); + width = Math.max(width, currentWidth); + widths.push(currentWidth); + } + return width; +} + + /** * @param {CanvasRenderingContext2D} context Context. * @param {number} rotation Rotation. diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index 7f6f12b34a..de128a06c2 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -24,7 +24,7 @@ import { /// Imports copied from TextReplay import {asColorLike} from '../../colorlike.js'; import {createCanvasContext2D} from '../../dom.js'; -import {labelCache, measureTextWidth, defaultTextAlign, measureTextHeight, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; +import {labelCache, measureTextWidth, measureTextWidths, defaultTextAlign, measureTextHeight, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; /** @@ -1222,24 +1222,3 @@ class CanvasInstructionsExecutor { export default CanvasInstructionsExecutor; - - -/////////////// Below is code copied from TextReplay ///////////////// - -/** - * @param {string} font Font to use for measuring. - * @param {Array} lines Lines to measure. - * @param {Array} widths Array will be populated with the widths of - * each line. - * @return {number} Width of the whole text. - */ -export function measureTextWidths(font, lines, widths) { - const numLines = lines.length; - let width = 0; - for (let i = 0; i < numLines; ++i) { - const currentWidth = measureTextWidth(font, lines[i]); - width = Math.max(width, currentWidth); - widths.push(currentWidth); - } - return width; -} diff --git a/src/ol/render/canvas/TextBuilder.js b/src/ol/render/canvas/TextBuilder.js index 43713f6f1f..3586556c88 100644 --- a/src/ol/render/canvas/TextBuilder.js +++ b/src/ol/render/canvas/TextBuilder.js @@ -8,7 +8,7 @@ import {intersects} from '../../extent.js'; import {matchingChunk} from '../../geom/flat/straightchunk.js'; import GeometryType from '../../geom/GeometryType.js'; import {CANVAS_LINE_DASH} from '../../has.js'; -import {labelCache, measureTextWidth, defaultTextAlign, measureTextHeight, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; +import {labelCache, measureTextWidth, measureTextWidths, defaultTextAlign, measureTextHeight, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; import CanvasInstruction from './Instruction.js'; import CanvasInstructionsBuilder from './InstructionsBuilder.js'; import {TEXT_ALIGN} from '../replay.js'; @@ -528,23 +528,4 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { } -/** - * @param {string} font Font to use for measuring. - * @param {Array} lines Lines to measure. - * @param {Array} widths Array will be populated with the widths of - * each line. - * @return {number} Width of the whole text. - */ -export function measureTextWidths(font, lines, widths) { - const numLines = lines.length; - let width = 0; - for (let i = 0; i < numLines; ++i) { - const currentWidth = measureTextWidth(font, lines[i]); - width = Math.max(width, currentWidth); - widths.push(currentWidth); - } - return width; -} - - export default CanvasTextBuilder; From 4ce19530cea2bf350a7e49f1a5ad95b2ccd028d6 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 13 Nov 2018 18:01:42 +0100 Subject: [PATCH 11/30] Remove unused methods --- src/ol/render/canvas/InstructionsExecutor.js | 198 ------------------- 1 file changed, 198 deletions(-) diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index de128a06c2..2836a8d874 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -344,204 +344,6 @@ class CanvasInstructionsExecutor { return labelCache.get(key); } - /** - * @private - * @param {HTMLCanvasElement} label Label. - * @param {number} begin Begin. - * @param {number} end End. - */ - drawTextImage_(label, begin, end) { - const textState = this.textState_; - const strokeState = this.textStrokeState_; - const pixelRatio = this.pixelRatio; - const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign]; - const baseline = TEXT_ALIGN[textState.textBaseline]; - const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0; - - const anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth; - const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth; - this.instructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, - label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, - this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_, - 1, label.width, - textState.padding == defaultPadding ? - defaultPadding : textState.padding.map(function(p) { - return p * pixelRatio; - }), - !!textState.backgroundFill, !!textState.backgroundStroke - ]); - this.hitDetectionInstructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, - label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, - this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_, - 1 / pixelRatio, label.width, textState.padding, - !!textState.backgroundFill, !!textState.backgroundStroke - ]); - } - - /** - * @private - * @param {number} begin Begin. - * @param {number} end End. - * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. - */ - drawChars_(begin, end, declutterGroup) { - const strokeState = this.textStrokeState_; - const textState = this.textState_; - const fillState = this.textFillState_; - - const strokeKey = this.strokeKey_; - if (strokeState) { - if (!(strokeKey in this.strokeStates)) { - this.strokeStates[strokeKey] = /** @type {import("../canvas.js").StrokeState} */ ({ - strokeStyle: strokeState.strokeStyle, - lineCap: strokeState.lineCap, - lineDashOffset: strokeState.lineDashOffset, - lineWidth: strokeState.lineWidth, - lineJoin: strokeState.lineJoin, - miterLimit: strokeState.miterLimit, - lineDash: strokeState.lineDash - }); - } - } - const textKey = this.textKey_; - if (!(this.textKey_ in this.textStates)) { - this.textStates[this.textKey_] = /** @type {import("../canvas.js").TextState} */ ({ - font: textState.font, - textAlign: textState.textAlign || defaultTextAlign, - scale: textState.scale - }); - } - const fillKey = this.fillKey_; - if (fillState) { - if (!(fillKey in this.fillStates)) { - this.fillStates[fillKey] = /** @type {import("../canvas.js").FillState} */ ({ - fillStyle: fillState.fillStyle - }); - } - } - - const pixelRatio = this.pixelRatio; - const baseline = TEXT_ALIGN[textState.textBaseline]; - - const offsetY = this.textOffsetY_ * pixelRatio; - const text = this.text_; - const font = textState.font; - const textScale = textState.scale; - const strokeWidth = strokeState ? strokeState.lineWidth * textScale / 2 : 0; - let widths = this.widths_[font]; - if (!widths) { - this.widths_[font] = widths = {}; - } - this.instructions.push([CanvasInstruction.DRAW_CHARS, - begin, end, baseline, declutterGroup, - textState.overflow, fillKey, textState.maxAngle, - function(text) { - let width = widths[text]; - if (!width) { - width = widths[text] = measureTextWidth(font, text); - } - return width * textScale * pixelRatio; - }, - offsetY, strokeKey, strokeWidth * pixelRatio, text, textKey, 1 - ]); - this.hitDetectionInstructions.push([CanvasInstruction.DRAW_CHARS, - begin, end, baseline, declutterGroup, - textState.overflow, fillKey, textState.maxAngle, - function(text) { - let width = widths[text]; - if (!width) { - width = widths[text] = measureTextWidth(font, text); - } - return width * textScale; - }, - offsetY, strokeKey, strokeWidth, text, textKey, 1 / pixelRatio - ]); - } - - /** - * @inheritDoc - */ - setTextStyle(textStyle, declutterGroup) { - let textState, fillState, strokeState; - if (!textStyle) { - this.text_ = ''; - } else { - this.declutterGroup_ = /** @type {import("../canvas.js").DeclutterGroup} */ (declutterGroup); - - const textFillStyle = textStyle.getFill(); - if (!textFillStyle) { - fillState = this.textFillState_ = null; - } else { - fillState = this.textFillState_; - if (!fillState) { - fillState = this.textFillState_ = /** @type {import("../canvas.js").FillState} */ ({}); - } - fillState.fillStyle = asColorLike( - textFillStyle.getColor() || defaultFillStyle); - } - - const textStrokeStyle = textStyle.getStroke(); - if (!textStrokeStyle) { - strokeState = this.textStrokeState_ = null; - } else { - strokeState = this.textStrokeState_; - if (!strokeState) { - strokeState = this.textStrokeState_ = /** @type {import("../canvas.js").StrokeState} */ ({}); - } - const lineDash = textStrokeStyle.getLineDash(); - const lineDashOffset = textStrokeStyle.getLineDashOffset(); - const lineWidth = textStrokeStyle.getWidth(); - const miterLimit = textStrokeStyle.getMiterLimit(); - strokeState.lineCap = textStrokeStyle.getLineCap() || defaultLineCap; - strokeState.lineDash = lineDash ? lineDash.slice() : defaultLineDash; - strokeState.lineDashOffset = - lineDashOffset === undefined ? defaultLineDashOffset : lineDashOffset; - strokeState.lineJoin = textStrokeStyle.getLineJoin() || defaultLineJoin; - strokeState.lineWidth = - lineWidth === undefined ? defaultLineWidth : lineWidth; - strokeState.miterLimit = - miterLimit === undefined ? defaultMiterLimit : miterLimit; - strokeState.strokeStyle = asColorLike( - textStrokeStyle.getColor() || defaultStrokeStyle); - } - - textState = this.textState_; - const font = textStyle.getFont() || defaultFont; - checkFont(font); - const textScale = textStyle.getScale(); - textState.overflow = textStyle.getOverflow(); - textState.font = font; - textState.maxAngle = textStyle.getMaxAngle(); - textState.placement = textStyle.getPlacement(); - textState.textAlign = textStyle.getTextAlign(); - textState.textBaseline = textStyle.getTextBaseline() || defaultTextBaseline; - textState.backgroundFill = textStyle.getBackgroundFill(); - textState.backgroundStroke = textStyle.getBackgroundStroke(); - textState.padding = textStyle.getPadding() || defaultPadding; - textState.scale = textScale === undefined ? 1 : textScale; - - const textOffsetX = textStyle.getOffsetX(); - const textOffsetY = textStyle.getOffsetY(); - const textRotateWithView = textStyle.getRotateWithView(); - const textRotation = textStyle.getRotation(); - this.text_ = textStyle.getText() || ''; - this.textOffsetX_ = textOffsetX === undefined ? 0 : textOffsetX; - this.textOffsetY_ = textOffsetY === undefined ? 0 : textOffsetY; - this.textRotateWithView_ = textRotateWithView === undefined ? false : textRotateWithView; - this.textRotation_ = textRotation === undefined ? 0 : textRotation; - - this.strokeKey_ = strokeState ? - (typeof strokeState.strokeStyle == 'string' ? strokeState.strokeStyle : getUid(strokeState.strokeStyle)) + - strokeState.lineCap + strokeState.lineDashOffset + '|' + strokeState.lineWidth + - strokeState.lineJoin + strokeState.miterLimit + '[' + strokeState.lineDash.join() + ']' : - ''; - this.textKey_ = textState.font + textState.scale + (textState.textAlign || '?'); - this.fillKey_ = fillState ? - (typeof fillState.fillStyle == 'string' ? fillState.fillStyle : ('|' + getUid(fillState.fillStyle))) : - ''; - } - } - ////////////// Above is code from TextReplay ////////////////// /** From 0ece0fb002a47e03754620dc0d7b63a2af7c509a Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Tue, 13 Nov 2018 19:20:26 +0100 Subject: [PATCH 12/30] Cleanup unused text replay code --- src/ol/render/canvas/InstructionsExecutor.js | 68 +------------------- 1 file changed, 1 insertion(+), 67 deletions(-) diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index 2836a8d874..471e4a254a 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -21,10 +21,8 @@ import { } from '../../transform.js'; -/// Imports copied from TextReplay -import {asColorLike} from '../../colorlike.js'; import {createCanvasContext2D} from '../../dom.js'; -import {labelCache, measureTextWidth, measureTextWidths, defaultTextAlign, measureTextHeight, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; +import {labelCache, defaultTextAlign, measureTextHeight, measureTextWidths} from '../canvas.js'; /** @@ -176,44 +174,6 @@ class CanvasInstructionsExecutor { this.viewRotation_ = 0; ////////////// Below is code copied from TextReplay //////////// - - - /** - * @private - * @type {Array} - */ - this.labels_ = null; - - /** - * @private - * @type {string} - */ - this.text_ = ''; - - /** - * @private - * @type {number} - */ - this.textOffsetX_ = 0; - - /** - * @private - * @type {number} - */ - this.textOffsetY_ = 0; - - /** - * @private - * @type {boolean|undefined} - */ - this.textRotateWithView_ = undefined; - - /** - * @private - * @type {number} - */ - this.textRotation_ = 0; - /** * @private * @type {?import("../canvas.js").FillState} @@ -246,32 +206,6 @@ class CanvasInstructionsExecutor { * @type {!Object} */ this.textStates = {}; - - /** - * @private - * @type {string} - */ - this.textKey_ = ''; - - /** - * @private - * @type {string} - */ - this.fillKey_ = ''; - - /** - * @private - * @type {string} - */ - this.strokeKey_ = ''; - - /** - * @private - * @type {Object>} - */ - this.widths_ = {}; - - labelCache.prune(); } From c0df61468ffd4027381094ba1d72e16479f6c775 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Wed, 14 Nov 2018 18:20:38 +0100 Subject: [PATCH 13/30] Move drawText along line to executor --- src/ol/render/canvas/InstructionsExecutor.js | 38 ++++++++++++++++---- src/ol/render/canvas/TextBuilder.js | 30 +++------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index 471e4a254a..2ce3008acc 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -22,7 +22,7 @@ import { import {createCanvasContext2D} from '../../dom.js'; -import {labelCache, defaultTextAlign, measureTextHeight, measureTextWidths} from '../canvas.js'; +import {labelCache, defaultTextAlign, measureTextHeight, measureTextWidth, measureTextWidths} from '../canvas.js'; /** @@ -206,6 +206,14 @@ class CanvasInstructionsExecutor { * @type {!Object} */ this.textStates = {}; + + // Adaptations + + /** + * @private + * @type {Object>} + */ + this.widths_ = {}; } @@ -697,19 +705,35 @@ class CanvasInstructionsExecutor { const overflow = /** @type {number} */ (instruction[5]); const fillKey = /** @type {string} */ (instruction[6]); const maxAngle = /** @type {number} */ (instruction[7]); - const measure = /** @type {function(string):number} */ (instruction[8]); + const measurePixelRatio = /** @type {number} */ (instruction[8]); const offsetY = /** @type {number} */ (instruction[9]); const strokeKey = /** @type {string} */ (instruction[10]); const strokeWidth = /** @type {number} */ (instruction[11]); const text = /** @type {string} */ (instruction[12]); const textKey = /** @type {string} */ (instruction[13]); - const textScale = /** @type {number} */ (instruction[14]); + const pixelRatioScale = /** @type {number} */ (instruction[14]); + + const textState = this.textStates[textKey]; + const font = textState.font; + const textScale = textState.scale; + + let widths = this.widths_[font]; + if (!widths) { + this.widths_[font] = widths = {}; + } + + 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); if (overflow || textLength <= pathLength) { - /** @type {import("./TextBuilder.js").default} */ - const textReplay = /** @type {?} */ (this); + const textReplay = this; const textAlign = textReplay.textStates[textKey].textAlign; const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign]; const parts = drawTextOnPath( @@ -726,7 +750,7 @@ class CanvasInstructionsExecutor { this.replayImage_(context, /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label, anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, - /** @type {number} */ (part[3]), textScale, false, label.width, + /** @type {number} */ (part[3]), pixelRatioScale, false, label.width, defaultPadding, null, null); } } @@ -740,7 +764,7 @@ class CanvasInstructionsExecutor { this.replayImage_(context, /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label, anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, - /** @type {number} */ (part[3]), textScale, false, label.width, + /** @type {number} */ (part[3]), pixelRatioScale, false, label.width, defaultPadding, null, null); } } diff --git a/src/ol/render/canvas/TextBuilder.js b/src/ol/render/canvas/TextBuilder.js index 3586556c88..c516a99a86 100644 --- a/src/ol/render/canvas/TextBuilder.js +++ b/src/ol/render/canvas/TextBuilder.js @@ -8,7 +8,7 @@ import {intersects} from '../../extent.js'; import {matchingChunk} from '../../geom/flat/straightchunk.js'; import GeometryType from '../../geom/GeometryType.js'; import {CANVAS_LINE_DASH} from '../../has.js'; -import {labelCache, measureTextWidth, measureTextWidths, defaultTextAlign, measureTextHeight, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; +import {labelCache, measureTextWidths, defaultTextAlign, measureTextHeight, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; import CanvasInstruction from './Instruction.js'; import CanvasInstructionsBuilder from './InstructionsBuilder.js'; import {TEXT_ALIGN} from '../replay.js'; @@ -119,12 +119,6 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { */ this.strokeKey_ = ''; - /** - * @private - * @type {Object>} - */ - this.widths_ = {}; - labelCache.prune(); } @@ -409,35 +403,19 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { const offsetY = this.textOffsetY_ * pixelRatio; const text = this.text_; - const font = textState.font; const textScale = textState.scale; const strokeWidth = strokeState ? strokeState.lineWidth * textScale / 2 : 0; - let widths = this.widths_[font]; - if (!widths) { - this.widths_[font] = widths = {}; - } + this.instructions.push([CanvasInstruction.DRAW_CHARS, begin, end, baseline, declutterGroup, textState.overflow, fillKey, textState.maxAngle, - function(text) { - let width = widths[text]; - if (!width) { - width = widths[text] = measureTextWidth(font, text); - } - return width * textScale * pixelRatio; - }, + pixelRatio, offsetY, strokeKey, strokeWidth * pixelRatio, text, textKey, 1 ]); this.hitDetectionInstructions.push([CanvasInstruction.DRAW_CHARS, begin, end, baseline, declutterGroup, textState.overflow, fillKey, textState.maxAngle, - function(text) { - let width = widths[text]; - if (!width) { - width = widths[text] = measureTextWidth(font, text); - } - return width * textScale; - }, + 1, offsetY, strokeKey, strokeWidth, text, textKey, 1 / pixelRatio ]); } From afc946b215666b0a84cf5f257d296bed955cbfd4 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 09:54:12 +0100 Subject: [PATCH 14/30] Cleanup duplicated and unused code --- src/ol/render/canvas/InstructionsBuilder.js | 41 +-------------------- 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/src/ol/render/canvas/InstructionsBuilder.js b/src/ol/render/canvas/InstructionsBuilder.js index 1eaebb4c69..85c04e4b43 100644 --- a/src/ol/render/canvas/InstructionsBuilder.js +++ b/src/ol/render/canvas/InstructionsBuilder.js @@ -3,14 +3,13 @@ */ import {equals, reverseSubArray} from '../../array.js'; import {asColorLike} from '../../colorlike.js'; -import {buffer, clone, coordinateRelationship, - createOrUpdateEmpty} from '../../extent.js'; +import {buffer, clone, coordinateRelationship} from '../../extent.js'; import Relationship from '../../extent/Relationship.js'; import GeometryType from '../../geom/GeometryType.js'; import {inflateCoordinates, inflateCoordinatesArray, inflateMultiCoordinatesArray} from '../../geom/flat/inflate.js'; import {CANVAS_LINE_DASH} from '../../has.js'; import VectorContext from '../VectorContext.js'; -import {drawImage, resetTransform, defaultFillStyle, defaultStrokeStyle, +import {resetTransform, defaultFillStyle, defaultStrokeStyle, defaultMiterLimit, defaultLineWidth, defaultLineJoin, defaultLineDashOffset, defaultLineDash, defaultLineCap} from '../canvas.js'; import CanvasInstruction from './Instruction.js'; @@ -350,42 +349,6 @@ class CanvasInstructionsBuilder extends VectorContext { } } - /** - * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. - * @param {import("../../Feature.js").default|import("../Feature.js").default} feature Feature. - */ - renderDeclutter_(declutterGroup, feature) { - if (declutterGroup && declutterGroup.length > 5) { - const groupCount = declutterGroup[4]; - if (groupCount == 1 || groupCount == declutterGroup.length - 5) { - /** @type {import("../../structs/RBush.js").Entry} */ - const box = { - minX: /** @type {number} */ (declutterGroup[0]), - minY: /** @type {number} */ (declutterGroup[1]), - maxX: /** @type {number} */ (declutterGroup[2]), - maxY: /** @type {number} */ (declutterGroup[3]), - value: feature - }; - if (!this.declutterTree.collides(box)) { - this.declutterTree.insert(box); - for (let j = 5, jj = declutterGroup.length; j < jj; ++j) { - const declutterData = /** @type {Array} */ (declutterGroup[j]); - if (declutterData) { - if (declutterData.length > 11) { - this.replayTextBackground_(declutterData[0], - declutterData[13], declutterData[14], declutterData[15], declutterData[16], - declutterData[11], declutterData[12]); - } - drawImage.apply(undefined, declutterData); - } - } - } - declutterGroup.length = 5; - createOrUpdateEmpty(declutterGroup); - } - } - } - /** * Reverse the hit detection instructions. */ From 4ba84d79269a4c2f6f59c780320d2d4c966f7d06 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 11:15:02 +0100 Subject: [PATCH 15/30] Cleanly separate text building and execution --- src/ol/render/canvas/InstructionsExecutor.js | 80 +++++++-- src/ol/render/canvas/TextBuilder.js | 178 ++++++------------- 2 files changed, 129 insertions(+), 129 deletions(-) diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index 2ce3008acc..c2685de71a 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -10,7 +10,7 @@ import {drawTextOnPath} from '../../geom/flat/textpath.js'; import {transform2D} from '../../geom/flat/transform.js'; import {CANVAS_LINE_DASH} from '../../has.js'; import {isEmpty} from '../../obj.js'; -import {drawImage, resetTransform, defaultPadding} from '../canvas.js'; +import {drawImage, resetTransform, defaultPadding, defaultTextBaseline} from '../canvas.js'; import CanvasInstruction from './Instruction.js'; import {TEXT_ALIGN} from '../replay.js'; import { @@ -525,6 +525,35 @@ class CanvasInstructionsExecutor { } } + /** + * @private + * @param {string} text The text to draw. + * @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. + */ + drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey) { + const textState = this.textStates[textKey]; + + const label = this.getImage(text, textKey, fillKey, strokeKey); + + const strokeState = this.strokeStates[strokeKey]; // FIXME: check if it is correct, was this.textStrokeState_; + const pixelRatio = this.pixelRatio; + const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign]; + const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline]; // FIXME: why I need a default now? + const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0; + + const anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth; + const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth; + + return { + label: label, + anchorX: anchorX, + anchorY: anchorY + }; + } + /** * @private * @param {CanvasRenderingContext2D} context Context. @@ -566,7 +595,8 @@ class CanvasInstructionsExecutor { const ii = instructions.length; // end of instructions let d = 0; // data index let dd; // end of per-instruction data - let anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image; + let anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image, text, textKey; + let strokeKey, fillKey; let pendingFill = 0; let pendingStroke = 0; let lastFillInstruction = null; @@ -658,20 +688,44 @@ class CanvasInstructionsExecutor { case CanvasInstruction.DRAW_IMAGE: d = /** @type {number} */ (instruction[1]); dd = /** @type {number} */ (instruction[2]); - image = /** @type {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} */ - (instruction[3]); + image = /** @type {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} */ (instruction[3]); + // Remaining arguments in DRAW_IMAGE are in alphabetical order anchorX = /** @type {number} */ (instruction[4]); anchorY = /** @type {number} */ (instruction[5]); declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[6]); - const height = /** @type {number} */ (instruction[7]); + let height = /** @type {number} */ (instruction[7]); const opacity = /** @type {number} */ (instruction[8]); const originX = /** @type {number} */ (instruction[9]); const originY = /** @type {number} */ (instruction[10]); const rotateWithView = /** @type {boolean} */ (instruction[11]); let rotation = /** @type {number} */ (instruction[12]); const scale = /** @type {number} */ (instruction[13]); - const width = /** @type {number} */ (instruction[14]); + let width = /** @type {number} */ (instruction[14]); + + + if (!image) { + if (instruction.length < 19 || !instruction[18]) { + continue; + } + text = /** @type {string} */ (instruction[18]); + textKey = /** @type {string} */ (instruction[19]); + strokeKey = /** @type {string} */ (instruction[20]); + fillKey = /** @type {string} */ (instruction[21]); + const labelWithAnchor = this.drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey); + const textOffsetX = /** @type {number} */ (instruction[22]); + const textOffsetY = /** @type {number} */ (instruction[23]); + image = instruction[3] = labelWithAnchor.label; + anchorX = instruction[4] = (labelWithAnchor.anchorX - textOffsetX) * this.pixelRatio; + anchorY = instruction[5] = (labelWithAnchor.anchorY - textOffsetY) * this.pixelRatio; + height = instruction[8] = image.height; + width = instruction[14] = image.width; + } + + let geometryWidths; + if (instruction.length > 24) { + geometryWidths = /** @type {number} */ (instruction[24]); + } let padding, backgroundFill, backgroundStroke; if (instruction.length > 16) { @@ -686,7 +740,13 @@ class CanvasInstructionsExecutor { if (rotateWithView) { rotation += viewRotation; } + let widthIndex = 0; for (; d < dd; d += 2) { + if (geometryWidths) { + if (geometryWidths[widthIndex++] < width) { + continue; + } + } this.replayImage_(context, pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY, declutterGroup, height, opacity, originX, originY, rotation, scale, @@ -703,14 +763,14 @@ class CanvasInstructionsExecutor { const baseline = /** @type {number} */ (instruction[3]); declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[4]); const overflow = /** @type {number} */ (instruction[5]); - const fillKey = /** @type {string} */ (instruction[6]); + fillKey = /** @type {string} */ (instruction[6]); const maxAngle = /** @type {number} */ (instruction[7]); const measurePixelRatio = /** @type {number} */ (instruction[8]); const offsetY = /** @type {number} */ (instruction[9]); - const strokeKey = /** @type {string} */ (instruction[10]); + strokeKey = /** @type {string} */ (instruction[10]); const strokeWidth = /** @type {number} */ (instruction[11]); - const text = /** @type {string} */ (instruction[12]); - const textKey = /** @type {string} */ (instruction[13]); + text = /** @type {string} */ (instruction[12]); + textKey = /** @type {string} */ (instruction[13]); const pixelRatioScale = /** @type {number} */ (instruction[14]); const textState = this.textStates[textKey]; diff --git a/src/ol/render/canvas/TextBuilder.js b/src/ol/render/canvas/TextBuilder.js index c516a99a86..68db8f9f95 100644 --- a/src/ol/render/canvas/TextBuilder.js +++ b/src/ol/render/canvas/TextBuilder.js @@ -3,12 +3,10 @@ */ import {getUid} from '../../util.js'; import {asColorLike} from '../../colorlike.js'; -import {createCanvasContext2D} from '../../dom.js'; import {intersects} from '../../extent.js'; import {matchingChunk} from '../../geom/flat/straightchunk.js'; import GeometryType from '../../geom/GeometryType.js'; -import {CANVAS_LINE_DASH} from '../../has.js'; -import {labelCache, measureTextWidths, defaultTextAlign, measureTextHeight, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; +import {labelCache, defaultTextAlign, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js'; import CanvasInstruction from './Instruction.js'; import CanvasInstructionsBuilder from './InstructionsBuilder.js'; import {TEXT_ALIGN} from '../replay.js'; @@ -195,8 +193,8 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { this.endGeometry(geometry, feature); } else { - const label = this.getImage(this.text_, this.textKey_, this.fillKey_, this.strokeKey_); - const width = label.width / this.pixelRatio; + + const geometryWidths = []; switch (geometryType) { case GeometryType.POINT: case GeometryType.MULTI_POINT: @@ -215,8 +213,8 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { break; case GeometryType.POLYGON: flatCoordinates = /** @type {import("../../geom/Polygon.js").default} */ (geometry).getFlatInteriorPoint(); - if (!textState.overflow && flatCoordinates[2] / this.resolution < width) { - return; + if (!textState.overflow) { + geometryWidths.push(flatCoordinates[2] / this.resolution); } stride = 3; break; @@ -224,9 +222,10 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { const interiorPoints = /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry).getFlatInteriorPoints(); flatCoordinates = []; for (i = 0, ii = interiorPoints.length; i < ii; i += 3) { - if (textState.overflow || interiorPoints[i + 2] / this.resolution >= width) { - flatCoordinates.push(interiorPoints[i], interiorPoints[i + 1]); + if (!textState.overflow) { + geometryWidths.push(interiorPoints[i + 2] / this.resolution); } + flatCoordinates.push(interiorPoints[i], interiorPoints[i + 1]); } end = flatCoordinates.length; if (end == 0) { @@ -236,6 +235,9 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { default: } end = this.appendFlatCoordinates(flatCoordinates, 0, end, stride, false, false); + + this.saveTextStates_(); + if (textState.backgroundFill || textState.backgroundStroke) { this.setFillStrokeStyle(textState.backgroundFill, textState.backgroundStroke); if (textState.backgroundFill) { @@ -247,122 +249,42 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { this.hitDetectionInstructions.push(this.createStroke(this.state)); } } + this.beginGeometry(geometry, feature); - this.drawTextImage_(label, begin, end); + + // The image is unknown at this stage so we pass null; it will be computed at render time. + // For clarity, we pass Infinity for numerical values that will be computed at render time. + const pixelRatio = this.pixelRatio; + this.instructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, + null, Infinity, Infinity, this.declutterGroup_, Infinity, 1, 0, 0, + this.textRotateWithView_, this.textRotation_, 1, Infinity, + textState.padding == defaultPadding ? + defaultPadding : textState.padding.map(function(p) { + return p * pixelRatio; + }), + !!textState.backgroundFill, !!textState.backgroundStroke, + this.text_, this.textKey_, this.strokeKey_, this.fillKey_, + this.textOffsetX_, this.textOffsetY_, + geometryWidths.length > 0 ? geometryWidths : null + ]); + this.hitDetectionInstructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, + null, Infinity, Infinity, this.declutterGroup_, Infinity, 1, 0, 0, + this.textRotateWithView_, this.textRotation_, 1 / this.pixelRatio, Infinity, + textState.padding, + !!textState.backgroundFill, !!textState.backgroundStroke, + this.text_, this.textKey_, this.strokeKey_, this.fillKey_, + this.textOffsetX_, this.textOffsetY_, + geometryWidths.length > 0 ? geometryWidths : null + ]); + this.endGeometry(geometry, feature); } } - /** - * @param {string} text Text. - * @param {string} textKey Text style key. - * @param {string} fillKey Fill style key. - * @param {string} strokeKey Stroke style key. - * @return {HTMLCanvasElement} Image. - */ - getImage(text, textKey, fillKey, strokeKey) { - let label; - const key = strokeKey + textKey + text + fillKey + this.pixelRatio; - - if (!labelCache.containsKey(key)) { - const strokeState = strokeKey ? this.strokeStates[strokeKey] || this.textStrokeState_ : null; - const fillState = fillKey ? this.fillStates[fillKey] || this.textFillState_ : null; - const textState = this.textStates[textKey] || this.textState_; - 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( - Math.ceil(renderWidth * 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 = /** @type {CanvasLineCap} */ (strokeState.lineCap); - context.lineJoin = /** @type {CanvasLineJoin} */ (strokeState.lineJoin); - context.miterLimit = strokeState.miterLimit; - if (CANVAS_LINE_DASH && 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 * label.width / scale + 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); - } - /** * @private - * @param {HTMLCanvasElement} label Label. - * @param {number} begin Begin. - * @param {number} end End. */ - drawTextImage_(label, begin, end) { - const textState = this.textState_; - const strokeState = this.textStrokeState_; - const pixelRatio = this.pixelRatio; - const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign]; - const baseline = TEXT_ALIGN[textState.textBaseline]; - const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0; - - const anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth; - const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth; - this.instructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, - label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, - this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_, - 1, label.width, - textState.padding == defaultPadding ? - defaultPadding : textState.padding.map(function(p) { - return p * pixelRatio; - }), - !!textState.backgroundFill, !!textState.backgroundStroke - ]); - this.hitDetectionInstructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, - label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, - this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_, - 1 / pixelRatio, label.width, textState.padding, - !!textState.backgroundFill, !!textState.backgroundStroke - ]); - } - - /** - * @private - * @param {number} begin Begin. - * @param {number} end End. - * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. - */ - drawChars_(begin, end, declutterGroup) { + saveTextStates_() { const strokeState = this.textStrokeState_; const textState = this.textState_; const fillState = this.textFillState_; @@ -382,10 +304,11 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { } } const textKey = this.textKey_; - if (!(this.textKey_ in this.textStates)) { - this.textStates[this.textKey_] = /** @type {import("../canvas.js").TextState} */ ({ + if (!(textKey in this.textStates)) { + this.textStates[textKey] = /** @type {import("../canvas.js").TextState} */ ({ font: textState.font, textAlign: textState.textAlign || defaultTextAlign, + textBaseline: textState.textBaseline || defaultTextBaseline, scale: textState.scale }); } @@ -397,6 +320,23 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { }); } } + } + + /** + * @private + * @param {number} begin Begin. + * @param {number} end End. + * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. + */ + drawChars_(begin, end, declutterGroup) { + const strokeState = this.textStrokeState_; + const textState = this.textState_; + + const strokeKey = this.strokeKey_; + const textKey = this.textKey_; + const fillKey = this.fillKey_; + this.saveTextStates_(); + const pixelRatio = this.pixelRatio; const baseline = TEXT_ALIGN[textState.textBaseline]; From 87a1b926c970c5ee2d67eb6054a2e9e768a79591 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 11:18:22 +0100 Subject: [PATCH 16/30] Cleanup executor of unused properties --- src/ol/render/canvas/InstructionsExecutor.js | 38 +++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index c2685de71a..4854d32db4 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -173,35 +173,16 @@ class CanvasInstructionsExecutor { */ this.viewRotation_ = 0; - ////////////// Below is code copied from TextReplay //////////// - /** - * @private - * @type {?import("../canvas.js").FillState} - */ - this.textFillState_ = null; - /** * @type {!Object} */ this.fillStates = {}; - /** - * @private - * @type {?import("../canvas.js").StrokeState} - */ - this.textStrokeState_ = null; - /** * @type {!Object} */ this.strokeStates = {}; - /** - * @private - * @type {import("../canvas.js").TextState} - */ - this.textState_ = /** @type {import("../canvas.js").TextState} */ ({}); - /** * @type {!Object} */ @@ -224,14 +205,14 @@ class CanvasInstructionsExecutor { * @param {string} strokeKey Stroke style key. * @return {HTMLCanvasElement} Image. */ - getImage(text, textKey, fillKey, strokeKey) { + getTextImage(text, textKey, fillKey, strokeKey) { let label; const key = strokeKey + textKey + text + fillKey + this.pixelRatio; if (!labelCache.containsKey(key)) { - const strokeState = strokeKey ? this.strokeStates[strokeKey] || this.textStrokeState_ : null; - const fillState = fillKey ? this.fillStates[fillKey] || this.textFillState_ : null; - const textState = this.textStates[textKey] || this.textState_; + 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]; @@ -286,8 +267,6 @@ class CanvasInstructionsExecutor { return labelCache.get(key); } - ////////////// Above is code from TextReplay ////////////////// - /** * Recreate replays and populate them using the provided instructions. * @param {SerializableInstructions} instructions The serializable instructions @@ -536,7 +515,7 @@ class CanvasInstructionsExecutor { drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey) { const textState = this.textStates[textKey]; - const label = this.getImage(text, textKey, fillKey, strokeKey); + const label = this.getTextImage(text, textKey, fillKey, strokeKey); const strokeState = this.strokeStates[strokeKey]; // FIXME: check if it is correct, was this.textStrokeState_; const pixelRatio = this.pixelRatio; @@ -793,8 +772,7 @@ class CanvasInstructionsExecutor { const pathLength = lineStringLength(pixelCoordinates, begin, end, 2); const textLength = measure(text); if (overflow || textLength <= pathLength) { - const textReplay = this; - const textAlign = textReplay.textStates[textKey].textAlign; + const textAlign = this.textStates[textKey].textAlign; const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign]; const parts = drawTextOnPath( pixelCoordinates, begin, end, 2, text, measure, startM, maxAngle); @@ -804,7 +782,7 @@ class CanvasInstructionsExecutor { for (c = 0, cc = parts.length; c < cc; ++c) { part = parts[c]; // x, y, anchorX, rotation, chunk chars = /** @type {string} */ (part[4]); - label = textReplay.getImage(chars, textKey, '', strokeKey); + label = this.getTextImage(chars, textKey, '', strokeKey); anchorX = /** @type {number} */ (part[2]) + strokeWidth; anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY; this.replayImage_(context, @@ -818,7 +796,7 @@ class CanvasInstructionsExecutor { for (c = 0, cc = parts.length; c < cc; ++c) { part = parts[c]; // x, y, anchorX, rotation, chunk chars = /** @type {string} */ (part[4]); - label = textReplay.getImage(chars, textKey, fillKey, ''); + label = this.getTextImage(chars, textKey, fillKey, ''); anchorX = /** @type {number} */ (part[2]); anchorY = baseline * label.height - offsetY; this.replayImage_(context, From 5ecd832c922200c27d853470f264d141fc657ade Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 12:03:34 +0100 Subject: [PATCH 17/30] Adapt test to new text builder behaviour --- test/spec/ol/render/canvas/textreplay.test.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/spec/ol/render/canvas/textreplay.test.js b/test/spec/ol/render/canvas/textreplay.test.js index e5e3804d2b..ecad3c8ffd 100644 --- a/test/spec/ol/render/canvas/textreplay.test.js +++ b/test/spec/ol/render/canvas/textreplay.test.js @@ -6,7 +6,7 @@ import Text from '../../../../../src/ol/style/Text.js'; describe('ol.render.canvas.TextReplay', function() { - it('renders polygon labels only when they fit', function() { + it('always build rendering instructions for polygon labels', function() { const replay = new CanvasTextReplay(1, [-180, -90, 180, 90], 0.02, 1, true); const geometry = new Polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]); const feature = new Feature(geometry); @@ -15,8 +15,9 @@ describe('ol.render.canvas.TextReplay', function() { text: 'This is a long text' })); replay.drawText(geometry, feature); - expect(replay.instructions.length).to.be(0); + expect(replay.instructions.length).to.be(3); + replay.instructions.length = 0; replay.setTextStyle(new Text({ text: 'short' })); @@ -24,7 +25,7 @@ describe('ol.render.canvas.TextReplay', function() { expect(replay.instructions.length).to.be(3); }); - it('renders multipolygon labels only when they fit', function() { + it('always build rendering instructinos for multipolygon labels', function() { const replay = new CanvasTextReplay(1, [-180, -90, 180, 90], 0.02, 1, true); const geometry = new MultiPolygon([ [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], @@ -36,8 +37,9 @@ describe('ol.render.canvas.TextReplay', function() { text: 'This is a long text' })); replay.drawText(geometry, feature); - expect(replay.instructions.length).to.be(0); + expect(replay.instructions.length).to.be(3); + replay.instructions.length = 0; replay.setTextStyle(new Text({ text: 'short' })); From 0f5ced84833ae0b2f10da9cd6b5a6e9fc483707f Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 13:38:21 +0100 Subject: [PATCH 18/30] Reduce usage of "replay" term --- src/ol/VectorTile.js | 8 +- src/ol/render/BuilderGroup.js | 38 +++++ .../{ReplayGroup.js => ExecutorGroup.js} | 8 +- src/ol/render/canvas/ImageBuilder.js | 2 +- src/ol/render/canvas/InstructionsBuilder.js | 34 ++--- src/ol/render/canvas/InstructionsExecutor.js | 10 +- .../render/canvas/InstructionsGroupBuilder.js | 139 +++++------------- .../canvas/InstructionsGroupExecutor.js | 80 +++++----- src/ol/render/webgl/Immediate.js | 16 +- src/ol/render/webgl/ReplayGroup.js | 4 +- src/ol/renderer/canvas/VectorLayer.js | 4 +- src/ol/renderer/canvas/VectorTileLayer.js | 20 +-- src/ol/renderer/vector.js | 88 +++++------ test/spec/ol/renderer/vector.test.js | 34 ++--- 14 files changed, 226 insertions(+), 259 deletions(-) create mode 100644 src/ol/render/BuilderGroup.js rename src/ol/render/{ReplayGroup.js => ExecutorGroup.js} (83%) diff --git a/src/ol/VectorTile.js b/src/ol/VectorTile.js index 3a59e2fce4..c4ba60e768 100644 --- a/src/ol/VectorTile.js +++ b/src/ol/VectorTile.js @@ -65,7 +65,7 @@ class VectorTile extends Tile { /** * @private - * @type {Object} + * @type {Object} */ this.replayGroups_ = {}; @@ -142,7 +142,7 @@ class VectorTile extends Tile { /** * @param {import("./layer/Layer.js").default} layer Layer. * @param {string} key Key. - * @return {import("./render/ReplayGroup.js").default} Replay group. + * @return {import("./render/BuilderGroup.js").default} Replay group. */ getReplayGroup(layer, key) { return this.replayGroups_[getUid(layer) + ',' + key]; @@ -153,7 +153,7 @@ class VectorTile extends Tile { * @param {import("./layer/Layer").default} layer Layer. * @param {number} zoom Zoom. * @param {import("./extent").Extent} extent Extent. - * @return {import("./render/ReplayGroup.js").default} Replay groups. + * @return {import("./render/BuilderGroup.js").default} Replay groups. */ getLowResReplayGroup(layer, zoom, extent) { const layerId = getUid(layer); @@ -244,7 +244,7 @@ class VectorTile extends Tile { /** * @param {import("./layer/Layer.js").default} layer Layer. * @param {string} key Key. - * @param {import("./render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("./render/BuilderGroup.js").default} replayGroup Replay group. */ setReplayGroup(layer, key, replayGroup) { this.replayGroups_[getUid(layer) + ',' + key] = replayGroup; diff --git a/src/ol/render/BuilderGroup.js b/src/ol/render/BuilderGroup.js new file mode 100644 index 0000000000..6809455f30 --- /dev/null +++ b/src/ol/render/BuilderGroup.js @@ -0,0 +1,38 @@ +/** + * @module ol/render/BuilderGroup + */ +import {abstract} from '../util.js'; + +/** + * Base class for builder groups. + */ +class BuilderGroup { + /** + * @abstract + * @param {number|undefined} zIndex Z index. + * @param {import("./ReplayType.js").default} replayType Replay type. + * @return {import("./VectorContext.js").default} Replay. + */ + getBuilder(zIndex, replayType) { + return abstract(); + } + + /** + * @abstract + * @return {boolean} Is empty. + */ + isEmpty() { + return abstract(); + } + + /** + * @abstract + * @param {boolean} group Group with previous builder + * @return {Array<*>} The resulting instruction group + */ + addDeclutter(group) { + return abstract(); + } +} + +export default BuilderGroup; diff --git a/src/ol/render/ReplayGroup.js b/src/ol/render/ExecutorGroup.js similarity index 83% rename from src/ol/render/ReplayGroup.js rename to src/ol/render/ExecutorGroup.js index 0cf176a3f5..af533b65a1 100644 --- a/src/ol/render/ReplayGroup.js +++ b/src/ol/render/ExecutorGroup.js @@ -1,19 +1,19 @@ /** - * @module ol/render/ReplayGroup + * @module ol/render/ExecutorGroup */ import {abstract} from '../util.js'; /** * Base class for replay groups. */ -class ReplayGroup { +class ExecutorGroup { /** * @abstract * @param {number|undefined} zIndex Z index. * @param {import("./ReplayType.js").default} replayType Replay type. * @return {import("./VectorContext.js").default} Replay. */ - getReplay(zIndex, replayType) { + getExecutor(zIndex, replayType) { return abstract(); } @@ -35,4 +35,4 @@ class ReplayGroup { } } -export default ReplayGroup; +export default ExecutorGroup; diff --git a/src/ol/render/canvas/ImageBuilder.js b/src/ol/render/canvas/ImageBuilder.js index 72c1add279..12725d8338 100644 --- a/src/ol/render/canvas/ImageBuilder.js +++ b/src/ol/render/canvas/ImageBuilder.js @@ -10,7 +10,7 @@ class CanvasImageBuilder extends CanvasInstructionsBuilder { * @param {import("../../extent.js").Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. * @param {number} pixelRatio Pixel ratio. - * @param {boolean} overlaps The replay can have overlapping geometries. + * @param {boolean} overlaps The builder can have overlapping geometries. * @param {?} declutterTree Declutter tree. */ constructor(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) { diff --git a/src/ol/render/canvas/InstructionsBuilder.js b/src/ol/render/canvas/InstructionsBuilder.js index 85c04e4b43..303392b5e2 100644 --- a/src/ol/render/canvas/InstructionsBuilder.js +++ b/src/ol/render/canvas/InstructionsBuilder.js @@ -36,7 +36,7 @@ class CanvasInstructionsBuilder extends VectorContext { * @param {import("../../extent.js").Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. * @param {number} pixelRatio Pixel ratio. - * @param {boolean} overlaps The replay can have overlapping geometries. + * @param {boolean} overlaps The builder can have overlapping geometries. * @param {?} declutterTree Declutter tree. */ constructor(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) { @@ -230,14 +230,14 @@ class CanvasInstructionsBuilder extends VectorContext { * @param {number} offset Offset. * @param {Array} ends Ends. * @param {number} stride Stride. - * @param {Array} replayEnds Replay ends. + * @param {Array} builderEnds Builder ends. * @return {number} Offset. */ - drawCustomCoordinates_(flatCoordinates, offset, ends, stride, replayEnds) { + drawCustomCoordinates_(flatCoordinates, offset, ends, stride, builderEnds) { for (let i = 0, ii = ends.length; i < ii; ++i) { const end = ends[i]; - const replayEnd = this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false); - replayEnds.push(replayEnd); + const builderEnd = this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false); + builderEnds.push(builderEnd); offset = end; } return offset; @@ -250,44 +250,44 @@ class CanvasInstructionsBuilder extends VectorContext { this.beginGeometry(geometry, feature); const type = geometry.getType(); const stride = geometry.getStride(); - const replayBegin = this.coordinates.length; - let flatCoordinates, replayEnd, replayEnds, replayEndss; + const builderBegin = this.coordinates.length; + let flatCoordinates, builderEnd, builderEnds, builderEndss; let offset; if (type == GeometryType.MULTI_POLYGON) { geometry = /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry); flatCoordinates = geometry.getOrientedFlatCoordinates(); - replayEndss = []; + builderEndss = []; const endss = geometry.getEndss(); offset = 0; for (let i = 0, ii = endss.length; i < ii; ++i) { const myEnds = []; offset = this.drawCustomCoordinates_(flatCoordinates, offset, endss[i], stride, myEnds); - replayEndss.push(myEnds); + builderEndss.push(myEnds); } this.instructions.push([CanvasInstruction.CUSTOM, - replayBegin, replayEndss, geometry, renderer, inflateMultiCoordinatesArray]); + builderBegin, builderEndss, geometry, renderer, inflateMultiCoordinatesArray]); } else if (type == GeometryType.POLYGON || type == GeometryType.MULTI_LINE_STRING) { - replayEnds = []; + builderEnds = []; flatCoordinates = (type == GeometryType.POLYGON) ? /** @type {import("../../geom/Polygon.js").default} */ (geometry).getOrientedFlatCoordinates() : geometry.getFlatCoordinates(); offset = this.drawCustomCoordinates_(flatCoordinates, 0, /** @type {import("../../geom/Polygon.js").default|import("../../geom/MultiLineString.js").default} */ (geometry).getEnds(), - stride, replayEnds); + stride, builderEnds); this.instructions.push([CanvasInstruction.CUSTOM, - replayBegin, replayEnds, geometry, renderer, inflateCoordinatesArray]); + builderBegin, builderEnds, geometry, renderer, inflateCoordinatesArray]); } else if (type == GeometryType.LINE_STRING || type == GeometryType.MULTI_POINT) { flatCoordinates = geometry.getFlatCoordinates(); - replayEnd = this.appendFlatCoordinates( + builderEnd = this.appendFlatCoordinates( flatCoordinates, 0, flatCoordinates.length, stride, false, false); this.instructions.push([CanvasInstruction.CUSTOM, - replayBegin, replayEnd, geometry, renderer, inflateCoordinates]); + builderBegin, builderEnd, geometry, renderer, inflateCoordinates]); } else if (type == GeometryType.POINT) { flatCoordinates = geometry.getFlatCoordinates(); this.coordinates.push(flatCoordinates[0], flatCoordinates[1]); - replayEnd = this.coordinates.length; + builderEnd = this.coordinates.length; this.instructions.push([CanvasInstruction.CUSTOM, - replayBegin, replayEnd, geometry, renderer]); + builderBegin, builderEnd, geometry, renderer]); } this.endGeometry(geometry, feature); } diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index 4854d32db4..ede2f436db 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -547,7 +547,7 @@ class CanvasInstructionsExecutor { * @return {T|undefined} Callback result. * @template T */ - replay_( + execute_( context, transform, skippedFeaturesHash, @@ -909,9 +909,9 @@ class CanvasInstructionsExecutor { * to skip. * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. */ - replay(context, transform, viewRotation, skippedFeaturesHash, snapToPixel) { + execute(context, transform, viewRotation, skippedFeaturesHash, snapToPixel) { this.viewRotation_ = viewRotation; - this.replay_(context, transform, + this.execute_(context, transform, skippedFeaturesHash, this.instructions, snapToPixel, undefined, undefined); } @@ -928,7 +928,7 @@ class CanvasInstructionsExecutor { * @return {T|undefined} Callback result. * @template T */ - replayHitDetection( + executeHitDetection( context, transform, viewRotation, @@ -937,7 +937,7 @@ class CanvasInstructionsExecutor { opt_hitExtent ) { this.viewRotation_ = viewRotation; - return this.replay_(context, transform, skippedFeaturesHash, + return this.execute_(context, transform, skippedFeaturesHash, this.hitDetectionInstructions, true, opt_featureCallback, opt_hitExtent); } diff --git a/src/ol/render/canvas/InstructionsGroupBuilder.js b/src/ol/render/canvas/InstructionsGroupBuilder.js index 4fee44defc..91d0649428 100644 --- a/src/ol/render/canvas/InstructionsGroupBuilder.js +++ b/src/ol/render/canvas/InstructionsGroupBuilder.js @@ -7,7 +7,7 @@ import {createCanvasContext2D} from '../../dom.js'; import {buffer, createEmpty, extendCoordinate} from '../../extent.js'; import {transform2D} from '../../geom/flat/transform.js'; import {isEmpty} from '../../obj.js'; -import ReplayGroup from '../ReplayGroup.js'; +import BuilderGroup from '../BuilderGroup.js'; import ReplayType from '../ReplayType.js'; import CanvasInstructionsBuilder from './InstructionsBuilder.js'; import CanvasImageBuilder from './ImageBuilder.js'; @@ -31,13 +31,13 @@ const BATCH_CONSTRUCTORS = { }; -class CanvasInstructionsGroupBuilder extends ReplayGroup { +class CanvasBuilderGroup extends BuilderGroup { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Max extent. * @param {number} resolution Resolution. * @param {number} pixelRatio Pixel ratio. - * @param {boolean} overlaps The replay group can have overlapping geometries. + * @param {boolean} overlaps The builder group can have overlapping geometries. * @param {?} declutterTree Declutter tree for declutter processing in postrender. * @param {number=} opt_renderBuffer Optional rendering buffer. */ @@ -104,7 +104,7 @@ class CanvasInstructionsGroupBuilder extends ReplayGroup { * @private * @type {!Object>} */ - this.replaysByZIndex_ = {}; + this.buildersByZIndex_ = {}; /** * @private @@ -150,31 +150,16 @@ class CanvasInstructionsGroupBuilder extends ReplayGroup { context.clip(); } - /** - * Recreate replays and populate them using the provided instructions. - * @param {!Object>} allInstructions The serializable instructions - */ - replaceInstructions(allInstructions) { - this.replaysByZIndex_ = {}; - for (const zIndex in allInstructions) { - const instructionByZindex = allInstructions[zIndex]; - for (const replayType in instructionByZindex) { - const instructions = instructionByZindex[replayType]; - const replay = this.getReplay(zIndex, replayType); - replay.replaceInstructions(instructions); - } - } - } /** - * @param {Array} replays Replays. - * @return {boolean} Has replays of the provided types. + * @param {Array} builders Builders. + * @return {boolean} Has builders of the provided types. */ - hasReplays(replays) { - for (const zIndex in this.replaysByZIndex_) { - const candidates = this.replaysByZIndex_[zIndex]; - for (let i = 0, ii = replays.length; i < ii; ++i) { - if (replays[i] in candidates) { + hasBuilders(builders) { + for (const zIndex in this.buildersByZIndex_) { + const candidates = this.buildersByZIndex_[zIndex]; + for (let i = 0, ii = builders.length; i < ii; ++i) { + if (builders[i] in candidates) { return true; } } @@ -186,16 +171,16 @@ class CanvasInstructionsGroupBuilder extends ReplayGroup { * @return {!Object>} The serializable instructions */ finish() { - const replaysInstructions = {}; - for (const zKey in this.replaysByZIndex_) { - replaysInstructions[zKey] = replaysInstructions[zKey] || {}; - const replays = this.replaysByZIndex_[zKey]; - for (const replayKey in replays) { - const replayInstructions = replays[replayKey].finish(); - replaysInstructions[zKey][replayKey] = replayInstructions; + const builderInstructions = {}; + for (const zKey in this.buildersByZIndex_) { + builderInstructions[zKey] = builderInstructions[zKey] || {}; + const builders = this.buildersByZIndex_[zKey]; + for (const builderKey in builders) { + const builderInstruction = builders[builderKey].finish(); + builderInstructions[zKey][builderKey] = builderInstruction; } } - return replaysInstructions; + return builderInstructions; } /** @@ -283,27 +268,27 @@ class CanvasInstructionsGroupBuilder extends ReplayGroup { } /** @type {Array} */ - const zs = Object.keys(this.replaysByZIndex_).map(Number); + const zs = Object.keys(this.buildersByZIndex_).map(Number); zs.sort(numberSafeCompareFunction); - let i, j, replays, replay, result; + let i, j, builders, builder, result; for (i = zs.length - 1; i >= 0; --i) { const zIndexKey = zs[i].toString(); - replays = this.replaysByZIndex_[zIndexKey]; + builders = this.buildersByZIndex_[zIndexKey]; for (j = ORDER.length - 1; j >= 0; --j) { replayType = ORDER[j]; - replay = replays[replayType]; - if (replay !== undefined) { + builder = builders[replayType]; + if (builder !== undefined) { if (declutterReplays && (replayType == ReplayType.IMAGE || replayType == ReplayType.TEXT)) { const declutter = declutterReplays[zIndexKey]; if (!declutter) { - declutterReplays[zIndexKey] = [replay, transform.slice(0)]; + declutterReplays[zIndexKey] = [builder, transform.slice(0)]; } else { - declutter.push(replay, transform.slice(0)); + declutter.push(builder, transform.slice(0)); } } else { - result = replay.replayHitDetection(context, transform, rotation, + result = builder.executeHitDetection(context, transform, rotation, skippedFeaturesHash, featureCallback, hitExtent); if (result) { return result; @@ -341,12 +326,12 @@ class CanvasInstructionsGroupBuilder extends ReplayGroup { /** * @inheritDoc */ - getReplay(zIndex, replayType) { + getBuilder(zIndex, replayType) { const zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; - let replays = this.replaysByZIndex_[zIndexKey]; + let replays = this.buildersByZIndex_[zIndexKey]; if (replays === undefined) { replays = {}; - this.replaysByZIndex_[zIndexKey] = replays; + this.buildersByZIndex_[zIndexKey] = replays; } let replay = replays[replayType]; if (replay === undefined) { @@ -362,70 +347,14 @@ class CanvasInstructionsGroupBuilder extends ReplayGroup { * @return {Object>} Replays. */ getReplays() { - return this.replaysByZIndex_; + return this.buildersByZIndex_; } /** * @inheritDoc */ isEmpty() { - return isEmpty(this.replaysByZIndex_); - } - - /** - * @param {CanvasRenderingContext2D} context Context. - * @param {import("../../transform.js").Transform} transform Transform. - * @param {number} viewRotation View rotation. - * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {boolean} snapToPixel Snap point symbols and test to integer pixel. - * @param {Array=} opt_replayTypes Ordered replay types to replay. - * Default is {@link module:ol/render/replay~ORDER} - * @param {Object=} opt_declutterReplays Declutter replays. - */ - replay( - context, - transform, - viewRotation, - skippedFeaturesHash, - snapToPixel, - opt_replayTypes, - opt_declutterReplays - ) { - - /** @type {Array} */ - const zs = Object.keys(this.replaysByZIndex_).map(Number); - zs.sort(numberSafeCompareFunction); - - // setup clipping so that the parts of over-simplified geometries are not - // visible outside the current extent when panning - context.save(); - this.clip(context, transform); - - const replayTypes = opt_replayTypes ? opt_replayTypes : ORDER; - let i, ii, j, jj, replays, replay; - for (i = 0, ii = zs.length; i < ii; ++i) { - const zIndexKey = zs[i].toString(); - replays = this.replaysByZIndex_[zIndexKey]; - for (j = 0, jj = replayTypes.length; j < jj; ++j) { - const replayType = replayTypes[j]; - replay = replays[replayType]; - if (replay !== undefined) { - if (opt_declutterReplays && - (replayType == ReplayType.IMAGE || replayType == ReplayType.TEXT)) { - const declutter = opt_declutterReplays[zIndexKey]; - if (!declutter) { - opt_declutterReplays[zIndexKey] = [replay, transform.slice(0)]; - } else { - declutter.push(replay, transform.slice(0)); - } - } else { - replay.replay(context, transform, viewRotation, skippedFeaturesHash, snapToPixel); - } - } - } - } - - context.restore(); + return isEmpty(this.buildersByZIndex_); } } @@ -522,10 +451,10 @@ export function replayDeclutter(declutterReplays, context, rotation, snapToPixel for (let i = 0, ii = replayData.length; i < ii;) { const replay = replayData[i++]; const transform = replayData[i++]; - replay.replay(context, transform, rotation, skippedFeatureUids, snapToPixel); + replay.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); } } } -export default CanvasInstructionsGroupBuilder; +export default CanvasBuilderGroup; diff --git a/src/ol/render/canvas/InstructionsGroupExecutor.js b/src/ol/render/canvas/InstructionsGroupExecutor.js index 1f9ba3ccf7..b09e3e0150 100644 --- a/src/ol/render/canvas/InstructionsGroupExecutor.js +++ b/src/ol/render/canvas/InstructionsGroupExecutor.js @@ -7,20 +7,20 @@ import {createCanvasContext2D} from '../../dom.js'; import {buffer, createEmpty, extendCoordinate} from '../../extent.js'; import {transform2D} from '../../geom/flat/transform.js'; import {isEmpty} from '../../obj.js'; -import ReplayGroup from '../ReplayGroup.js'; +import ExecutorGroup from '../ExecutorGroup.js'; import ReplayType from '../ReplayType.js'; import {ORDER} from '../replay.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; import CanvasInstructionsExecutor from './InstructionsExecutor.js'; -class InstructionsGroupExectuor extends ReplayGroup { +class InstructionsGroupExectuor extends ExecutorGroup { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Max extent. * @param {number} resolution Resolution. * @param {number} pixelRatio Pixel ratio. - * @param {boolean} overlaps The replay group can have overlapping geometries. + * @param {boolean} overlaps The executor group can have overlapping geometries. * @param {?} declutterTree Declutter tree for declutter processing in postrender. * @param {number=} opt_renderBuffer Optional rendering buffer. */ @@ -87,7 +87,7 @@ class InstructionsGroupExectuor extends ReplayGroup { * @private * @type {!Object>} */ - this.replaysByZIndex_ = {}; + this.executorsByZIndex_ = {}; /** * @private @@ -134,30 +134,30 @@ class InstructionsGroupExectuor extends ReplayGroup { } /** - * Recreate replays and populate them using the provided instructions. + * Create executors and populate them using the provided instructions. * @param {!Object>} allInstructions The serializable instructions */ replaceInstructions(allInstructions) { - this.replaysByZIndex_ = {}; + this.executorsByZIndex_ = {}; for (const zIndex in allInstructions) { const instructionByZindex = allInstructions[zIndex]; for (const replayType in instructionByZindex) { const instructions = instructionByZindex[replayType]; - const replay = this.getReplay(zIndex, replayType); - replay.replaceInstructions(instructions); + const executor = this.getExecutor(zIndex, replayType); + executor.replaceInstructions(instructions); } } } /** - * @param {Array} replays Replays. - * @return {boolean} Has replays of the provided types. + * @param {Array} executors Executors. + * @return {boolean} Has executors of the provided types. */ - hasReplays(replays) { - for (const zIndex in this.replaysByZIndex_) { - const candidates = this.replaysByZIndex_[zIndex]; - for (let i = 0, ii = replays.length; i < ii; ++i) { - if (replays[i] in candidates) { + hasExecutors(executors) { + for (const zIndex in this.executorsByZIndex_) { + const candidates = this.executorsByZIndex_[zIndex]; + for (let i = 0, ii = executors.length; i < ii; ++i) { + if (executors[i] in candidates) { return true; } } @@ -251,27 +251,27 @@ class InstructionsGroupExectuor extends ReplayGroup { } /** @type {Array} */ - const zs = Object.keys(this.replaysByZIndex_).map(Number); + const zs = Object.keys(this.executorsByZIndex_).map(Number); zs.sort(numberSafeCompareFunction); - let i, j, replays, replay, result; + let i, j, executors, executor, result; for (i = zs.length - 1; i >= 0; --i) { const zIndexKey = zs[i].toString(); - replays = this.replaysByZIndex_[zIndexKey]; + executors = this.executorsByZIndex_[zIndexKey]; for (j = ORDER.length - 1; j >= 0; --j) { replayType = ORDER[j]; - replay = replays[replayType]; - if (replay !== undefined) { + executor = executors[replayType]; + if (executor !== undefined) { if (declutterReplays && (replayType == ReplayType.IMAGE || replayType == ReplayType.TEXT)) { const declutter = declutterReplays[zIndexKey]; if (!declutter) { - declutterReplays[zIndexKey] = [replay, transform.slice(0)]; + declutterReplays[zIndexKey] = [executor, transform.slice(0)]; } else { - declutter.push(replay, transform.slice(0)); + declutter.push(executor, transform.slice(0)); } } else { - result = replay.replayHitDetection(context, transform, rotation, + result = executor.executeHitDetection(context, transform, rotation, skippedFeaturesHash, featureCallback, hitExtent); if (result) { return result; @@ -302,34 +302,34 @@ class InstructionsGroupExectuor extends ReplayGroup { /** * @inheritDoc */ - getReplay(zIndex, replayType) { + getExecutor(zIndex, replayType) { const zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; - let replays = this.replaysByZIndex_[zIndexKey]; - if (replays === undefined) { - replays = {}; - this.replaysByZIndex_[zIndexKey] = replays; + let executors = this.executorsByZIndex_[zIndexKey]; + if (executors === undefined) { + executors = {}; + this.executorsByZIndex_[zIndexKey] = executors; } - let replay = replays[replayType]; - if (replay === undefined) { - replay = new CanvasInstructionsExecutor(this.tolerance_, this.maxExtent_, + let executor = executors[replayType]; + if (executor === undefined) { + executor = new CanvasInstructionsExecutor(this.tolerance_, this.maxExtent_, this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_); - replays[replayType] = replay; + executors[replayType] = executor; } - return replay; + return executor; } /** * @return {Object>} Replays. */ - getReplays() { - return this.replaysByZIndex_; + getExecutors() { + return this.executorsByZIndex_; } /** * @inheritDoc */ isEmpty() { - return isEmpty(this.replaysByZIndex_); + return isEmpty(this.executorsByZIndex_); } /** @@ -353,7 +353,7 @@ class InstructionsGroupExectuor extends ReplayGroup { ) { /** @type {Array} */ - const zs = Object.keys(this.replaysByZIndex_).map(Number); + const zs = Object.keys(this.executorsByZIndex_).map(Number); zs.sort(numberSafeCompareFunction); // setup clipping so that the parts of over-simplified geometries are not @@ -365,7 +365,7 @@ class InstructionsGroupExectuor extends ReplayGroup { let i, ii, j, jj, replays, replay; for (i = 0, ii = zs.length; i < ii; ++i) { const zIndexKey = zs[i].toString(); - replays = this.replaysByZIndex_[zIndexKey]; + replays = this.executorsByZIndex_[zIndexKey]; for (j = 0, jj = replayTypes.length; j < jj; ++j) { const replayType = replayTypes[j]; replay = replays[replayType]; @@ -379,7 +379,7 @@ class InstructionsGroupExectuor extends ReplayGroup { declutter.push(replay, transform.slice(0)); } } else { - replay.replay(context, transform, viewRotation, skippedFeaturesHash, snapToPixel); + replay.execute(context, transform, viewRotation, skippedFeaturesHash, snapToPixel); } } } @@ -482,7 +482,7 @@ export function replayDeclutter(declutterReplays, context, rotation, snapToPixel for (let i = 0, ii = replayData.length; i < ii;) { const replay = replayData[i++]; const transform = replayData[i++]; - replay.replay(context, transform, rotation, skippedFeatureUids, snapToPixel); + replay.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); } } } diff --git a/src/ol/render/webgl/Immediate.js b/src/ol/render/webgl/Immediate.js index 407cb843a7..ad0e77a78c 100644 --- a/src/ol/render/webgl/Immediate.js +++ b/src/ol/render/webgl/Immediate.js @@ -90,7 +90,7 @@ class WebGLImmediateRenderer extends VectorContext { drawText_(replayGroup, geometry) { const context = this.context_; const replay = /** @type {import("./TextReplay.js").default} */ ( - replayGroup.getReplay(0, ReplayType.TEXT)); + replayGroup.getBuilder(0, ReplayType.TEXT)); replay.setTextStyle(this.textStyle_); replay.drawText(geometry, null); replay.finish(context); @@ -191,7 +191,7 @@ class WebGLImmediateRenderer extends VectorContext { const context = this.context_; const replayGroup = new WebGLReplayGroup(1, this.extent_); const replay = /** @type {import("./ImageReplay.js").default} */ ( - replayGroup.getReplay(0, ReplayType.IMAGE)); + replayGroup.getBuilder(0, ReplayType.IMAGE)); replay.setImageStyle(this.imageStyle_); replay.drawPoint(geometry, data); replay.finish(context); @@ -218,7 +218,7 @@ class WebGLImmediateRenderer extends VectorContext { const context = this.context_; const replayGroup = new WebGLReplayGroup(1, this.extent_); const replay = /** @type {import("./ImageReplay.js").default} */ ( - replayGroup.getReplay(0, ReplayType.IMAGE)); + replayGroup.getBuilder(0, ReplayType.IMAGE)); replay.setImageStyle(this.imageStyle_); replay.drawMultiPoint(geometry, data); replay.finish(context); @@ -244,7 +244,7 @@ class WebGLImmediateRenderer extends VectorContext { const context = this.context_; const replayGroup = new WebGLReplayGroup(1, this.extent_); const replay = /** @type {import("./LineStringReplay.js").default} */ ( - replayGroup.getReplay(0, ReplayType.LINE_STRING)); + replayGroup.getBuilder(0, ReplayType.LINE_STRING)); replay.setFillStrokeStyle(null, this.strokeStyle_); replay.drawLineString(geometry, data); replay.finish(context); @@ -270,7 +270,7 @@ class WebGLImmediateRenderer extends VectorContext { const context = this.context_; const replayGroup = new WebGLReplayGroup(1, this.extent_); const replay = /** @type {import("./LineStringReplay.js").default} */ ( - replayGroup.getReplay(0, ReplayType.LINE_STRING)); + replayGroup.getBuilder(0, ReplayType.LINE_STRING)); replay.setFillStrokeStyle(null, this.strokeStyle_); replay.drawMultiLineString(geometry, data); replay.finish(context); @@ -296,7 +296,7 @@ class WebGLImmediateRenderer extends VectorContext { const context = this.context_; const replayGroup = new WebGLReplayGroup(1, this.extent_); const replay = /** @type {import("./PolygonReplay.js").default} */ ( - replayGroup.getReplay(0, ReplayType.POLYGON)); + replayGroup.getBuilder(0, ReplayType.POLYGON)); replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); replay.drawPolygon(geometry, data); replay.finish(context); @@ -322,7 +322,7 @@ class WebGLImmediateRenderer extends VectorContext { const context = this.context_; const replayGroup = new WebGLReplayGroup(1, this.extent_); const replay = /** @type {import("./PolygonReplay.js").default} */ ( - replayGroup.getReplay(0, ReplayType.POLYGON)); + replayGroup.getBuilder(0, ReplayType.POLYGON)); replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); replay.drawMultiPolygon(geometry, data); replay.finish(context); @@ -348,7 +348,7 @@ class WebGLImmediateRenderer extends VectorContext { const context = this.context_; const replayGroup = new WebGLReplayGroup(1, this.extent_); const replay = /** @type {import("./CircleReplay.js").default} */ ( - replayGroup.getReplay(0, ReplayType.CIRCLE)); + replayGroup.getBuilder(0, ReplayType.CIRCLE)); replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); replay.drawCircle(geometry, data); replay.finish(context); diff --git a/src/ol/render/webgl/ReplayGroup.js b/src/ol/render/webgl/ReplayGroup.js index 16fa92713e..207d69164e 100644 --- a/src/ol/render/webgl/ReplayGroup.js +++ b/src/ol/render/webgl/ReplayGroup.js @@ -6,7 +6,7 @@ import {numberSafeCompareFunction} from '../../array.js'; import {buffer, createOrUpdateFromCoordinate} from '../../extent.js'; import {isEmpty} from '../../obj.js'; import {ORDER} from '../replay.js'; -import ReplayGroup from '../ReplayGroup.js'; +import ReplayGroup from '../BuilderGroup.js'; import WebGLCircleReplay from './CircleReplay.js'; import WebGLImageReplay from './ImageReplay.js'; import WebGLLineStringReplay from './LineStringReplay.js'; @@ -113,7 +113,7 @@ class WebGLReplayGroup extends ReplayGroup { /** * @inheritDoc */ - getReplay(zIndex, replayType) { + getBuilder(zIndex, replayType) { const zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; let replays = this.replaysByZIndex_[zIndexKey]; if (replays === undefined) { diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 002f07e800..6429c4b327 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -10,7 +10,7 @@ import rbush from 'rbush'; import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; import RenderEventType from '../../render/EventType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; -import CanvasInstructionsGroupBuilder from '../../render/canvas/InstructionsGroupBuilder.js'; +import CanvasBuilderGroup from '../../render/canvas/InstructionsGroupBuilder.js'; import InstructionsGroupExecutor from '../../render/canvas/InstructionsGroupExecutor.js'; import CanvasLayerRenderer from './Layer.js'; import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -444,7 +444,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { this.dirty_ = false; - const replayGroup = new CanvasInstructionsGroupBuilder( + const replayGroup = new CanvasBuilderGroup( getRenderTolerance(resolution, pixelRatio), extent, resolution, pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer()); vectorSource.loadFeatures(extent, resolution, projection); diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index d14274912e..9d923061c3 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -13,7 +13,7 @@ import {equivalent as equivalentProjection} from '../../proj.js'; import Units from '../../proj/Units.js'; import ReplayType from '../../render/ReplayType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; -import CanvasInstructionsGroupBuilder, {replayDeclutter} from '../../render/canvas/InstructionsGroupBuilder.js'; +import CanvasBuilderGroup, {replayDeclutter} from '../../render/canvas/InstructionsGroupBuilder.js'; import {ORDER} from '../../render/replay.js'; import CanvasTileLayerRenderer from './TileLayer.js'; import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -24,7 +24,7 @@ import { scale as scaleTransform, translate as translateTransform } from '../../transform.js'; -import InstructionsGroupExectuor from '../../render/canvas/InstructionsGroupExecutor.js'; +import CanvasGroupExecutor from '../../render/canvas/InstructionsGroupExecutor.js'; /** @@ -187,7 +187,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { sourceTile.setProjection(projection); } replayState.dirty = false; - const replayGroup = new CanvasInstructionsGroupBuilder(0, sharedExtent, resolution, + const replayGroup = new CanvasBuilderGroup(0, sharedExtent, resolution, pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer()); const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio); @@ -228,7 +228,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { } } const replayGroupInstructions = replayGroup.finish(); - const renderingReplayGroup = new InstructionsGroupExectuor(0, sharedExtent, resolution, + const renderingReplayGroup = new CanvasGroupExecutor(0, sharedExtent, resolution, pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer()); renderingReplayGroup.replaceInstructions(replayGroupInstructions); sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), renderingReplayGroup); @@ -263,7 +263,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (sourceTile.getState() != TileState.LOADED) { continue; } - const replayGroup = /** @type {CanvasInstructionsGroupBuilder} */ (sourceTile.getReplayGroup(layer, + const replayGroup = /** @type {CanvasBuilderGroup} */ (sourceTile.getReplayGroup(layer, tile.tileCoord.toString())); found = found || replayGroup.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, {}, /** @@ -371,8 +371,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (sourceTile.getState() != TileState.LOADED) { continue; } - const replayGroup = /** @type {CanvasInstructionsGroupBuilder} */ (sourceTile.getReplayGroup(layer, tileCoord.toString())); - if (!replayGroup || !replayGroup.hasReplays(replayTypes)) { + const executorGroup = /** @type {CanvasGroupExecutor} */ (sourceTile.getReplayGroup(layer, tileCoord.toString())); + if (!executorGroup || !executorGroup.hasExecutors(replayTypes)) { // sourceTile was not yet loaded when this.createReplayGroup_() was // called, or it has no replays of the types we want to render continue; @@ -381,7 +381,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { transform = this.getTransform(frameState, worldOffset); } const currentZ = sourceTile.tileCoord[0]; - const currentClip = replayGroup.getClipCoords(transform); + const currentClip = executorGroup.getClipCoords(transform); context.save(); context.globalAlpha = layerState.opacity; // Create a clip mask for regions in this low resolution tile that are @@ -403,7 +403,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { context.clip(); } } - replayGroup.replay(context, transform, rotation, {}, snapToPixel, replayTypes, declutterReplays); + executorGroup.replay(context, transform, rotation, {}, snapToPixel, replayTypes, declutterReplays); context.restore(); clips.push(currentClip); zs.push(currentZ); @@ -478,7 +478,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const transform = resetTransform(this.tmpTransform_); scaleTransform(transform, pixelScale, -pixelScale); translateTransform(transform, -tileExtent[0], -tileExtent[3]); - const replayGroup = /** @type {CanvasInstructionsGroupBuilder} */ (sourceTile.getReplayGroup(layer, + const replayGroup = /** @type {CanvasBuilderGroup} */ (sourceTile.getReplayGroup(layer, tile.tileCoord.toString())); replayGroup.replay(context, transform, 0, {}, true, replays); } diff --git a/src/ol/renderer/vector.js b/src/ol/renderer/vector.js index 6ebb0f32ed..356b7da3bf 100644 --- a/src/ol/renderer/vector.js +++ b/src/ol/renderer/vector.js @@ -4,7 +4,7 @@ import {getUid} from '../util.js'; import ImageState from '../ImageState.js'; import GeometryType from '../geom/GeometryType.js'; -import ReplayType from '../render/ReplayType.js'; +import BuilderType from '../render/ReplayType.js'; /** @@ -17,7 +17,7 @@ const SIMPLIFY_TOLERANCE = 0.5; /** * @const * @type {Object} */ const GEOMETRY_RENDERERS = { @@ -64,30 +64,30 @@ export function getTolerance(resolution, pixelRatio) { /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} builderGroup Builder group. * @param {import("../geom/Circle.js").default} geometry Geometry. * @param {import("../style/Style.js").default} style Style. * @param {import("../Feature.js").default} feature Feature. */ -function renderCircleGeometry(replayGroup, geometry, style, feature) { +function renderCircleGeometry(builderGroup, geometry, style, feature) { const fillStyle = style.getFill(); const strokeStyle = style.getStroke(); if (fillStyle || strokeStyle) { - const circleReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.CIRCLE); + const circleReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.CIRCLE); circleReplay.setFillStrokeStyle(fillStyle, strokeStyle); circleReplay.drawCircle(geometry, feature); } const textStyle = style.getText(); if (textStyle) { - const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT); - textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false)); + const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT); + textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(false)); textReplay.drawText(geometry, feature); } } /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} replayGroup Replay group. * @param {import("../Feature.js").FeatureLike} feature Feature. * @param {import("../style/Style.js").default} style Style. * @param {number} squaredTolerance Squared tolerance. @@ -119,7 +119,7 @@ export function renderFeature(replayGroup, feature, style, squaredTolerance, lis /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} replayGroup Replay group. * @param {import("../Feature.js").FeatureLike} feature Feature. * @param {import("../style/Style.js").default} style Style. * @param {number} squaredTolerance Squared tolerance. @@ -141,7 +141,7 @@ function renderFeatureInternal(replayGroup, feature, style, squaredTolerance) { /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} replayGroup Replay group. * @param {import("../geom/Geometry.js").default|import("../render/Feature.js").default} geometry Geometry. * @param {import("../style/Style.js").default} style Style. * @param {import("../Feature.js").FeatureLike} feature Feature. @@ -154,13 +154,13 @@ function renderGeometry(replayGroup, geometry, style, feature) { } return; } - const replay = replayGroup.getReplay(style.getZIndex(), ReplayType.DEFAULT); + const replay = replayGroup.getBuilder(style.getZIndex(), BuilderType.DEFAULT); replay.drawCustom(/** @type {import("../geom/SimpleGeometry.js").default} */ (geometry), feature, style.getRenderer()); } /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} replayGroup Replay group. * @param {import("../geom/GeometryCollection.js").default} geometry Geometry. * @param {import("../style/Style.js").default} style Style. * @param {import("../Feature.js").default} feature Feature. @@ -177,140 +177,140 @@ function renderGeometryCollectionGeometry(replayGroup, geometry, style, feature) /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} builderGroup Replay group. * @param {import("../geom/LineString.js").default|import("../render/Feature.js").default} geometry Geometry. * @param {import("../style/Style.js").default} style Style. * @param {import("../Feature.js").FeatureLike} feature Feature. */ -function renderLineStringGeometry(replayGroup, geometry, style, feature) { +function renderLineStringGeometry(builderGroup, geometry, style, feature) { const strokeStyle = style.getStroke(); if (strokeStyle) { - const lineStringReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.LINE_STRING); + const lineStringReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.LINE_STRING); lineStringReplay.setFillStrokeStyle(null, strokeStyle); lineStringReplay.drawLineString(geometry, feature); } const textStyle = style.getText(); if (textStyle) { - const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT); - textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false)); + const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT); + textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(false)); textReplay.drawText(geometry, feature); } } /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} builderGroup Replay group. * @param {import("../geom/MultiLineString.js").default|import("../render/Feature.js").default} geometry Geometry. * @param {import("../style/Style.js").default} style Style. * @param {import("../Feature.js").FeatureLike} feature Feature. */ -function renderMultiLineStringGeometry(replayGroup, geometry, style, feature) { +function renderMultiLineStringGeometry(builderGroup, geometry, style, feature) { const strokeStyle = style.getStroke(); if (strokeStyle) { - const lineStringReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.LINE_STRING); + const lineStringReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.LINE_STRING); lineStringReplay.setFillStrokeStyle(null, strokeStyle); lineStringReplay.drawMultiLineString(geometry, feature); } const textStyle = style.getText(); if (textStyle) { - const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT); - textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false)); + const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT); + textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(false)); textReplay.drawText(geometry, feature); } } /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} builderGroup Replay group. * @param {import("../geom/MultiPolygon.js").default} geometry Geometry. * @param {import("../style/Style.js").default} style Style. * @param {import("../Feature.js").default} feature Feature. */ -function renderMultiPolygonGeometry(replayGroup, geometry, style, feature) { +function renderMultiPolygonGeometry(builderGroup, geometry, style, feature) { const fillStyle = style.getFill(); const strokeStyle = style.getStroke(); if (strokeStyle || fillStyle) { - const polygonReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.POLYGON); + const polygonReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.POLYGON); polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); polygonReplay.drawMultiPolygon(geometry, feature); } const textStyle = style.getText(); if (textStyle) { - const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT); - textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false)); + const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT); + textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(false)); textReplay.drawText(geometry, feature); } } /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} builderGroup Replay group. * @param {import("../geom/Point.js").default|import("../render/Feature.js").default} geometry Geometry. * @param {import("../style/Style.js").default} style Style. * @param {import("../Feature.js").FeatureLike} feature Feature. */ -function renderPointGeometry(replayGroup, geometry, style, feature) { +function renderPointGeometry(builderGroup, geometry, style, feature) { const imageStyle = style.getImage(); if (imageStyle) { if (imageStyle.getImageState() != ImageState.LOADED) { return; } - const imageReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.IMAGE); - imageReplay.setImageStyle(imageStyle, replayGroup.addDeclutter(false)); + const imageReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.IMAGE); + imageReplay.setImageStyle(imageStyle, builderGroup.addDeclutter(false)); imageReplay.drawPoint(geometry, feature); } const textStyle = style.getText(); if (textStyle) { - const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT); - textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(!!imageStyle)); + const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT); + textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(!!imageStyle)); textReplay.drawText(geometry, feature); } } /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} builderGroup Replay group. * @param {import("../geom/MultiPoint.js").default|import("../render/Feature.js").default} geometry Geometry. * @param {import("../style/Style.js").default} style Style. * @param {import("../Feature.js").FeatureLike} feature Feature. */ -function renderMultiPointGeometry(replayGroup, geometry, style, feature) { +function renderMultiPointGeometry(builderGroup, geometry, style, feature) { const imageStyle = style.getImage(); if (imageStyle) { if (imageStyle.getImageState() != ImageState.LOADED) { return; } - const imageReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.IMAGE); - imageReplay.setImageStyle(imageStyle, replayGroup.addDeclutter(false)); + const imageReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.IMAGE); + imageReplay.setImageStyle(imageStyle, builderGroup.addDeclutter(false)); imageReplay.drawMultiPoint(geometry, feature); } const textStyle = style.getText(); if (textStyle) { - const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT); - textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(!!imageStyle)); + const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT); + textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(!!imageStyle)); textReplay.drawText(geometry, feature); } } /** - * @param {import("../render/ReplayGroup.js").default} replayGroup Replay group. + * @param {import("../render/BuilderGroup.js").default} builderGroup Replay group. * @param {import("../geom/Polygon.js").default|import("../render/Feature.js").default} geometry Geometry. * @param {import("../style/Style.js").default} style Style. * @param {import("../Feature.js").FeatureLike} feature Feature. */ -function renderPolygonGeometry(replayGroup, geometry, style, feature) { +function renderPolygonGeometry(builderGroup, geometry, style, feature) { const fillStyle = style.getFill(); const strokeStyle = style.getStroke(); if (fillStyle || strokeStyle) { - const polygonReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.POLYGON); + const polygonReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.POLYGON); polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); polygonReplay.drawPolygon(geometry, feature); } const textStyle = style.getText(); if (textStyle) { - const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT); - textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false)); + const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT); + textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(false)); textReplay.drawText(geometry, feature); } } diff --git a/test/spec/ol/renderer/vector.test.js b/test/spec/ol/renderer/vector.test.js index 62e7050779..32581697fb 100644 --- a/test/spec/ol/renderer/vector.test.js +++ b/test/spec/ol/renderer/vector.test.js @@ -6,7 +6,7 @@ import Polygon from '../../../../src/ol/geom/Polygon.js'; import MultiLineString from '../../../../src/ol/geom/MultiLineString.js'; import MultiPoint from '../../../../src/ol/geom/MultiPoint.js'; import MultiPolygon from '../../../../src/ol/geom/MultiPolygon.js'; -import CanvasReplayGroup from '../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; +import CanvasBuilderGroup from '../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; import {renderFeature} from '../../../../src/ol/renderer/vector.js'; import Fill from '../../../../src/ol/style/Fill.js'; import Icon from '../../../../src/ol/style/Icon.js'; @@ -17,12 +17,12 @@ import Feature from '../../../../src/ol/Feature.js'; describe('ol.renderer.vector', function() { describe('#renderFeature', function() { - let replayGroup; + let builderGroup; let feature, iconStyle, style, squaredTolerance, listener, listenerThis; let iconStyleLoadSpy; beforeEach(function() { - replayGroup = new CanvasReplayGroup(1); + builderGroup = new CanvasBuilderGroup(1); feature = new Feature(); iconStyle = new Icon({ src: 'http://example.com/icon.png' @@ -50,7 +50,7 @@ describe('ol.renderer.vector', function() { let listeners; // call #1 - renderFeature(replayGroup, feature, + renderFeature(builderGroup, feature, style, squaredTolerance, listener, listenerThis); expect(iconStyleLoadSpy.calledOnce).to.be.ok(); @@ -59,7 +59,7 @@ describe('ol.renderer.vector', function() { expect(listeners.length).to.eql(1); // call #2 - renderFeature(replayGroup, feature, + renderFeature(builderGroup, feature, style, squaredTolerance, listener, listenerThis); expect(iconStyleLoadSpy.calledOnce).to.be.ok(); @@ -74,11 +74,11 @@ describe('ol.renderer.vector', function() { it('does not render the point', function() { feature.setGeometry(new Point([0, 0])); - const imageReplay = replayGroup.getReplay( + const imageReplay = builderGroup.getBuilder( style.getZIndex(), 'Image'); const setImageStyleSpy = sinon.spy(imageReplay, 'setImageStyle'); const drawPointSpy = sinon.stub(imageReplay, 'drawPoint').callsFake(VOID); - renderFeature(replayGroup, feature, + renderFeature(builderGroup, feature, style, squaredTolerance, listener, listenerThis); expect(setImageStyleSpy.called).to.be(false); setImageStyleSpy.restore(); @@ -87,11 +87,11 @@ describe('ol.renderer.vector', function() { it('does not render the multipoint', function() { feature.setGeometry(new MultiPoint([[0, 0], [1, 1]])); - const imageReplay = replayGroup.getReplay( + const imageReplay = builderGroup.getBuilder( style.getZIndex(), 'Image'); const setImageStyleSpy = sinon.spy(imageReplay, 'setImageStyle'); const drawMultiPointSpy = sinon.stub(imageReplay, 'drawMultiPoint').callsFake(VOID); - renderFeature(replayGroup, feature, + renderFeature(builderGroup, feature, style, squaredTolerance, listener, listenerThis); expect(setImageStyleSpy.called).to.be(false); setImageStyleSpy.restore(); @@ -100,12 +100,12 @@ describe('ol.renderer.vector', function() { it('does render the linestring', function() { feature.setGeometry(new LineString([[0, 0], [1, 1]])); - const lineStringReplay = replayGroup.getReplay( + const lineStringReplay = builderGroup.getBuilder( style.getZIndex(), 'LineString'); const setFillStrokeStyleSpy = sinon.spy(lineStringReplay, 'setFillStrokeStyle'); const drawLineStringSpy = sinon.stub(lineStringReplay, 'drawLineString').callsFake(VOID); - renderFeature(replayGroup, feature, + renderFeature(builderGroup, feature, style, squaredTolerance, listener, listenerThis); expect(setFillStrokeStyleSpy.called).to.be(true); expect(drawLineStringSpy.called).to.be(true); @@ -115,12 +115,12 @@ describe('ol.renderer.vector', function() { it('does render the multilinestring', function() { feature.setGeometry(new MultiLineString([[[0, 0], [1, 1]]])); - const lineStringReplay = replayGroup.getReplay( + const lineStringReplay = builderGroup.getBuilder( style.getZIndex(), 'LineString'); const setFillStrokeStyleSpy = sinon.spy(lineStringReplay, 'setFillStrokeStyle'); const drawMultiLineStringSpy = sinon.stub(lineStringReplay, 'drawMultiLineString').callsFake(VOID); - renderFeature(replayGroup, feature, + renderFeature(builderGroup, feature, style, squaredTolerance, listener, listenerThis); expect(setFillStrokeStyleSpy.called).to.be(true); expect(drawMultiLineStringSpy.called).to.be(true); @@ -131,12 +131,12 @@ describe('ol.renderer.vector', function() { it('does render the polygon', function() { feature.setGeometry(new Polygon( [[[0, 0], [1, 1], [1, 0], [0, 0]]])); - const polygonReplay = replayGroup.getReplay( + const polygonReplay = builderGroup.getBuilder( style.getZIndex(), 'Polygon'); const setFillStrokeStyleSpy = sinon.spy(polygonReplay, 'setFillStrokeStyle'); const drawPolygonSpy = sinon.stub(polygonReplay, 'drawPolygon').callsFake(VOID); - renderFeature(replayGroup, feature, + renderFeature(builderGroup, feature, style, squaredTolerance, listener, listenerThis); expect(setFillStrokeStyleSpy.called).to.be(true); expect(drawPolygonSpy.called).to.be(true); @@ -147,12 +147,12 @@ describe('ol.renderer.vector', function() { it('does render the multipolygon', function() { feature.setGeometry(new MultiPolygon( [[[[0, 0], [1, 1], [1, 0], [0, 0]]]])); - const polygonReplay = replayGroup.getReplay( + const polygonReplay = builderGroup.getBuilder( style.getZIndex(), 'Polygon'); const setFillStrokeStyleSpy = sinon.spy(polygonReplay, 'setFillStrokeStyle'); const drawMultiPolygonSpy = sinon.stub(polygonReplay, 'drawMultiPolygon').callsFake(VOID); - renderFeature(replayGroup, feature, + renderFeature(builderGroup, feature, style, squaredTolerance, listener, listenerThis); expect(setFillStrokeStyleSpy.called).to.be(true); expect(drawMultiPolygonSpy.called).to.be(true); From f9dcadb982cd2dc860fe374c96e27755553a840c Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 15:12:51 +0100 Subject: [PATCH 19/30] Remove unused methods --- .../render/canvas/InstructionsGroupBuilder.js | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/ol/render/canvas/InstructionsGroupBuilder.js b/src/ol/render/canvas/InstructionsGroupBuilder.js index 91d0649428..f243475859 100644 --- a/src/ol/render/canvas/InstructionsGroupBuilder.js +++ b/src/ol/render/canvas/InstructionsGroupBuilder.js @@ -150,23 +150,6 @@ class CanvasBuilderGroup extends BuilderGroup { context.clip(); } - - /** - * @param {Array} builders Builders. - * @return {boolean} Has builders of the provided types. - */ - hasBuilders(builders) { - for (const zIndex in this.buildersByZIndex_) { - const candidates = this.buildersByZIndex_[zIndex]; - for (let i = 0, ii = builders.length; i < ii; ++i) { - if (builders[i] in candidates) { - return true; - } - } - } - return false; - } - /** * @return {!Object>} The serializable instructions */ @@ -343,12 +326,6 @@ class CanvasBuilderGroup extends BuilderGroup { return replay; } - /** - * @return {Object>} Replays. - */ - getReplays() { - return this.buildersByZIndex_; - } /** * @inheritDoc From 94bf02176eb43c9ab617fd689cb7d3c692ca59bc Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 15 Nov 2018 17:09:47 +0100 Subject: [PATCH 20/30] Rename ExecutorGroup and move getMaxExtent to Executor --- ...tionsGroupExecutor.js => ExecutorGroup.js} | 34 +++++++------------ .../render/canvas/InstructionsGroupBuilder.js | 7 ---- src/ol/renderer/canvas/VectorLayer.js | 4 +-- src/ol/renderer/canvas/VectorTileLayer.js | 2 +- test/spec/ol/renderer/canvas/replay.test.js | 2 +- 5 files changed, 16 insertions(+), 33 deletions(-) rename src/ol/render/canvas/{InstructionsGroupExecutor.js => ExecutorGroup.js} (95%) diff --git a/src/ol/render/canvas/InstructionsGroupExecutor.js b/src/ol/render/canvas/ExecutorGroup.js similarity index 95% rename from src/ol/render/canvas/InstructionsGroupExecutor.js rename to src/ol/render/canvas/ExecutorGroup.js index b09e3e0150..6db07327ce 100644 --- a/src/ol/render/canvas/InstructionsGroupExecutor.js +++ b/src/ol/render/canvas/ExecutorGroup.js @@ -1,5 +1,5 @@ /** - * @module ol/render/canvas/InstructionsGroupExecutor + * @module ol/render/canvas/ExecutorGroup */ import {numberSafeCompareFunction} from '../../array.js'; @@ -7,14 +7,14 @@ import {createCanvasContext2D} from '../../dom.js'; import {buffer, createEmpty, extendCoordinate} from '../../extent.js'; import {transform2D} from '../../geom/flat/transform.js'; import {isEmpty} from '../../obj.js'; -import ExecutorGroup from '../ExecutorGroup.js'; +import BaseExecutorGroup from '../ExecutorGroup.js'; import ReplayType from '../ReplayType.js'; import {ORDER} from '../replay.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; import CanvasInstructionsExecutor from './InstructionsExecutor.js'; -class InstructionsGroupExectuor extends ExecutorGroup { +class ExecutorGroup extends BaseExecutorGroup { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Max extent. @@ -85,7 +85,7 @@ class InstructionsGroupExectuor extends ExecutorGroup { /** * @private - * @type {!Object>} + * @type {!Object>} */ this.executorsByZIndex_ = {}; @@ -102,23 +102,6 @@ class InstructionsGroupExectuor extends ExecutorGroup { this.hitDetectionTransform_ = createTransform(); } - /** - * @inheritDoc - */ - addDeclutter(group) { - let declutter = null; - if (this.declutterTree_) { - if (group) { - declutter = this.declutterGroup_; - /** @type {number} */ (declutter[4])++; - } else { - declutter = this.declutterGroup_ = createEmpty(); - declutter.push(1); - } - } - return declutter; - } - /** * @param {CanvasRenderingContext2D} context Context. * @param {import("../../transform.js").Transform} transform Transform. @@ -299,6 +282,13 @@ class InstructionsGroupExectuor extends ExecutorGroup { return flatClipCoords; } + /** + * @return {import("../../extent.js").Extent} The extent of the replay group. + */ + getMaxExtent() { + return this.maxExtent_; + } + /** * @inheritDoc */ @@ -488,4 +478,4 @@ export function replayDeclutter(declutterReplays, context, rotation, snapToPixel } -export default InstructionsGroupExectuor; +export default ExecutorGroup; diff --git a/src/ol/render/canvas/InstructionsGroupBuilder.js b/src/ol/render/canvas/InstructionsGroupBuilder.js index f243475859..5bd25618d1 100644 --- a/src/ol/render/canvas/InstructionsGroupBuilder.js +++ b/src/ol/render/canvas/InstructionsGroupBuilder.js @@ -299,13 +299,6 @@ class CanvasBuilderGroup extends BuilderGroup { return flatClipCoords; } - /** - * @return {import("../../extent.js").Extent} The extent of the replay group. - */ - getMaxExtent() { - return this.maxExtent_; - } - /** * @inheritDoc */ diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 6429c4b327..b8c349978c 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -11,7 +11,7 @@ import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; import RenderEventType from '../../render/EventType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; import CanvasBuilderGroup from '../../render/canvas/InstructionsGroupBuilder.js'; -import InstructionsGroupExecutor from '../../render/canvas/InstructionsGroupExecutor.js'; +import InstructionsGroupExecutor from '../../render/canvas/ExecutorGroup.js'; import CanvasLayerRenderer from './Layer.js'; import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -67,7 +67,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { /** * @private - * @type {import("../../render/canvas/InstructionsGroupExecutor").default} + * @type {import("../../render/canvas/ExecutorGroup").default} */ this.replayGroup_ = null; diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 9d923061c3..960fd4ca7d 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -24,7 +24,7 @@ import { scale as scaleTransform, translate as translateTransform } from '../../transform.js'; -import CanvasGroupExecutor from '../../render/canvas/InstructionsGroupExecutor.js'; +import CanvasGroupExecutor from '../../render/canvas/ExecutorGroup.js'; /** diff --git a/test/spec/ol/renderer/canvas/replay.test.js b/test/spec/ol/renderer/canvas/replay.test.js index 590e8e9e5d..de5299c4c7 100644 --- a/test/spec/ol/renderer/canvas/replay.test.js +++ b/test/spec/ol/renderer/canvas/replay.test.js @@ -11,7 +11,7 @@ import CanvasLineStringBuilder from '../../../../../src/ol/render/canvas/LineStr import CanvasPolygonBuilder from '../../../../../src/ol/render/canvas/PolygonBuilder.js'; import CanvasReplay from '../../../../../src/ol/render/canvas/InstructionsBuilder.js'; import CanvasInstructionsGroupBuilder from '../../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; -import CanvasInstructionsGroupExecutor from '../../../../../src/ol/render/canvas/InstructionsGroupExecutor.js'; +import CanvasInstructionsGroupExecutor from '../../../../../src/ol/render/canvas/ExecutorGroup.js'; import {renderFeature} from '../../../../../src/ol/renderer/vector.js'; import Fill from '../../../../../src/ol/style/Fill.js'; import Stroke from '../../../../../src/ol/style/Stroke.js'; From b1a5f4855fcf47d0dfc4e38819b5d5eb199f8688 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 18:23:47 +0100 Subject: [PATCH 21/30] Create geometryWidths array only when needed --- src/ol/render/canvas/TextBuilder.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ol/render/canvas/TextBuilder.js b/src/ol/render/canvas/TextBuilder.js index 68db8f9f95..2989c1d508 100644 --- a/src/ol/render/canvas/TextBuilder.js +++ b/src/ol/render/canvas/TextBuilder.js @@ -194,7 +194,10 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { } else { - const geometryWidths = []; + let geometryWidths = null; + if (!textState.overflow) { + geometryWidths = []; + } switch (geometryType) { case GeometryType.POINT: case GeometryType.MULTI_POINT: @@ -264,8 +267,7 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { }), !!textState.backgroundFill, !!textState.backgroundStroke, this.text_, this.textKey_, this.strokeKey_, this.fillKey_, - this.textOffsetX_, this.textOffsetY_, - geometryWidths.length > 0 ? geometryWidths : null + this.textOffsetX_, this.textOffsetY_, geometryWidths ]); this.hitDetectionInstructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, null, Infinity, Infinity, this.declutterGroup_, Infinity, 1, 0, 0, @@ -273,8 +275,7 @@ class CanvasTextBuilder extends CanvasInstructionsBuilder { textState.padding, !!textState.backgroundFill, !!textState.backgroundStroke, this.text_, this.textKey_, this.strokeKey_, this.fillKey_, - this.textOffsetX_, this.textOffsetY_, - geometryWidths.length > 0 ? geometryWidths : null + this.textOffsetX_, this.textOffsetY_, geometryWidths ]); this.endGeometry(geometry, feature); From eaed37da965f8b98e982f9de740c443a9620c25f Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 18:24:20 +0100 Subject: [PATCH 22/30] Fix width comparison --- src/ol/render/canvas/InstructionsExecutor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/InstructionsExecutor.js index ede2f436db..94d4a0f6e4 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/InstructionsExecutor.js @@ -722,7 +722,7 @@ class CanvasInstructionsExecutor { let widthIndex = 0; for (; d < dd; d += 2) { if (geometryWidths) { - if (geometryWidths[widthIndex++] < width) { + if (geometryWidths[widthIndex++] < width / this.pixelRatio) { continue; } } From da92b2ab3f145ade8ccdbdc2719d10e0653d1e4a Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 20:02:34 +0100 Subject: [PATCH 23/30] Improve text replay test --- test/spec/ol/render/canvas/textreplay.test.js | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/test/spec/ol/render/canvas/textreplay.test.js b/test/spec/ol/render/canvas/textreplay.test.js index ecad3c8ffd..3bb46ba690 100644 --- a/test/spec/ol/render/canvas/textreplay.test.js +++ b/test/spec/ol/render/canvas/textreplay.test.js @@ -2,49 +2,87 @@ import Feature from '../../../../../src/ol/Feature.js'; import MultiPolygon from '../../../../../src/ol/geom/MultiPolygon.js'; import Polygon from '../../../../../src/ol/geom/Polygon.js'; import CanvasTextReplay from '../../../../../src/ol/render/canvas/TextBuilder.js'; +import InstructionExecutor from '../../../../../src/ol/render/canvas/InstructionsExecutor.js'; import Text from '../../../../../src/ol/style/Text.js'; +import {create as createTransform} from '../../../../../src/ol/transform.js'; + +function createBuilder() { + return new CanvasTextReplay(1, [-180, -90, 180, 90], 0.02, 1, true); +} + +function createContext() { + return { + fill: function() {}, + stroke: function() {}, + beginPath: function() {}, + clip: function() {}, + moveTo: function() {}, + lineTo: function() {}, + closePath: function() {}, + setLineDash: function() {}, + save: function() {}, + restore: function() {} + }; +} + +function executeInstructions(builder, expectedDrawTextImageCalls, expectedReplayImageCalls) { + const transform = createTransform(); + const context = createContext(); + const executor = new InstructionExecutor(1, [-180, -90, 180, 90], 0.02, 1, null); + sinon.spy(executor, 'drawTextImageWithPointPlacement_'); + const replayImageStub = sinon.stub(executor, 'replayImage_'); + executor.replaceInstructions(builder.finish()); + executor.execute(context, transform); + expect(executor.drawTextImageWithPointPlacement_.callCount).to.be(expectedDrawTextImageCalls); + expect(replayImageStub.callCount).to.be(expectedReplayImageCalls); +} describe('ol.render.canvas.TextReplay', function() { - it('always build rendering instructions for polygon labels', function() { - const replay = new CanvasTextReplay(1, [-180, -90, 180, 90], 0.02, 1, true); + it('renders polygon labels only when they fit', function() { + let builder = createBuilder(); const geometry = new Polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]); const feature = new Feature(geometry); - replay.setTextStyle(new Text({ + builder.setTextStyle(new Text({ text: 'This is a long text' })); - replay.drawText(geometry, feature); - expect(replay.instructions.length).to.be(3); + builder.drawText(geometry, feature); + expect(builder.instructions.length).to.be(3); + executeInstructions(builder, 1, 0); - replay.instructions.length = 0; - replay.setTextStyle(new Text({ + + builder = createBuilder(); + builder.setTextStyle(new Text({ text: 'short' })); - replay.drawText(geometry, feature); - expect(replay.instructions.length).to.be(3); + builder.drawText(geometry, feature); + expect(builder.instructions.length).to.be(3); + executeInstructions(builder, 1, 1); }); - it('always build rendering instructinos for multipolygon labels', function() { - const replay = new CanvasTextReplay(1, [-180, -90, 180, 90], 0.02, 1, true); + it('renders multipolygon labels only when they fit', function() { const geometry = new MultiPolygon([ [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], [[[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]] ]); const feature = new Feature(geometry); - replay.setTextStyle(new Text({ + let builder = createBuilder(); + builder.setTextStyle(new Text({ text: 'This is a long text' })); - replay.drawText(geometry, feature); - expect(replay.instructions.length).to.be(3); + builder.drawText(geometry, feature); + expect(builder.instructions.length).to.be(3); + executeInstructions(builder, 1, 0); - replay.instructions.length = 0; - replay.setTextStyle(new Text({ + builder = createBuilder(); + builder.setTextStyle(new Text({ text: 'short' })); - replay.drawText(geometry, feature); - expect(replay.instructions.length).to.be(3); + builder.drawText(geometry, feature); + expect(builder.instructions.length).to.be(3); + executeInstructions(builder, 1, 2); }); }); From 89fed65f07e9f9991c4886d5c889e3512685bb6f Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 23:27:43 +0100 Subject: [PATCH 24/30] Fix incorrect Executor/Builder types --- src/ol/VectorTile.js | 16 +++--- src/ol/render/canvas/ExecutorGroup.js | 12 ++--- .../render/canvas/InstructionsGroupBuilder.js | 21 -------- src/ol/renderer/canvas/VectorLayer.js | 12 ++--- src/ol/renderer/canvas/VectorTileLayer.js | 52 +++++++++---------- test/spec/ol/renderer/canvas/replay.test.js | 24 ++++----- .../renderer/canvas/vectortilelayer.test.js | 16 +++--- 7 files changed, 65 insertions(+), 88 deletions(-) diff --git a/src/ol/VectorTile.js b/src/ol/VectorTile.js index c4ba60e768..11956eaeb8 100644 --- a/src/ol/VectorTile.js +++ b/src/ol/VectorTile.js @@ -65,7 +65,7 @@ class VectorTile extends Tile { /** * @private - * @type {Object} + * @type {Object} */ this.replayGroups_ = {}; @@ -142,9 +142,9 @@ class VectorTile extends Tile { /** * @param {import("./layer/Layer.js").default} layer Layer. * @param {string} key Key. - * @return {import("./render/BuilderGroup.js").default} Replay group. + * @return {import("./render/ExecutorGroup.js").default} Replay group. */ - getReplayGroup(layer, key) { + getExecutorGroup(layer, key) { return this.replayGroups_[getUid(layer) + ',' + key]; } @@ -153,9 +153,9 @@ class VectorTile extends Tile { * @param {import("./layer/Layer").default} layer Layer. * @param {number} zoom Zoom. * @param {import("./extent").Extent} extent Extent. - * @return {import("./render/BuilderGroup.js").default} Replay groups. + * @return {import("./render/ExecutorGroup.js").default} Replay groups. */ - getLowResReplayGroup(layer, zoom, extent) { + getLowResExecutorGroup(layer, zoom, extent) { const layerId = getUid(layer); let bestZoom = 0; let replayGroup = null; @@ -244,10 +244,10 @@ class VectorTile extends Tile { /** * @param {import("./layer/Layer.js").default} layer Layer. * @param {string} key Key. - * @param {import("./render/BuilderGroup.js").default} replayGroup Replay group. + * @param {import("./render/ExecutorGroup.js").default} executorGroup Executor group. */ - setReplayGroup(layer, key, replayGroup) { - this.replayGroups_[getUid(layer) + ',' + key] = replayGroup; + setExecutorGroup(layer, key, executorGroup) { + this.replayGroups_[getUid(layer) + ',' + key] = executorGroup; } /** diff --git a/src/ol/render/canvas/ExecutorGroup.js b/src/ol/render/canvas/ExecutorGroup.js index 6db07327ce..eb2b14a36a 100644 --- a/src/ol/render/canvas/ExecutorGroup.js +++ b/src/ol/render/canvas/ExecutorGroup.js @@ -332,7 +332,7 @@ class ExecutorGroup extends BaseExecutorGroup { * Default is {@link module:ol/render/replay~ORDER} * @param {Object=} opt_declutterReplays Declutter replays. */ - replay( + execute( context, transform, viewRotation, @@ -468,11 +468,11 @@ export function replayDeclutter(declutterReplays, context, rotation, snapToPixel const zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction); const skippedFeatureUids = {}; for (let z = 0, zz = zs.length; z < zz; ++z) { - const replayData = declutterReplays[zs[z].toString()]; - for (let i = 0, ii = replayData.length; i < ii;) { - const replay = replayData[i++]; - const transform = replayData[i++]; - replay.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); + const executorData = declutterReplays[zs[z].toString()]; + for (let i = 0, ii = executorData.length; i < ii;) { + const executor = executorData[i++]; + const transform = executorData[i++]; + executor.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); } } } diff --git a/src/ol/render/canvas/InstructionsGroupBuilder.js b/src/ol/render/canvas/InstructionsGroupBuilder.js index 5bd25618d1..883e7d4723 100644 --- a/src/ol/render/canvas/InstructionsGroupBuilder.js +++ b/src/ol/render/canvas/InstructionsGroupBuilder.js @@ -406,25 +406,4 @@ export function getCircleArray(radius) { return arr; } - -/** - * @param {!Object>} declutterReplays Declutter replays. - * @param {CanvasRenderingContext2D} context Context. - * @param {number} rotation Rotation. - * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. - */ -export function replayDeclutter(declutterReplays, context, rotation, snapToPixel) { - const zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction); - const skippedFeatureUids = {}; - for (let z = 0, zz = zs.length; z < zz; ++z) { - const replayData = declutterReplays[zs[z].toString()]; - for (let i = 0, ii = replayData.length; i < ii;) { - const replay = replayData[i++]; - const transform = replayData[i++]; - replay.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); - } - } -} - - export default CanvasBuilderGroup; diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index b8c349978c..92dadf926d 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -166,7 +166,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { const halfWidth = (frameState.size[0] * pixelRatio) / 2; const halfHeight = (frameState.size[1] * pixelRatio) / 2; rotateAtOffset(replayContext, -rotation, halfWidth, halfHeight); - replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); + replayGroup.execute(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); if (vectorSource.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) { let startX = extent[0]; @@ -177,7 +177,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { --world; offsetX = worldWidth * world; transform = this.getTransform(frameState, offsetX); - replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); + replayGroup.execute(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); startX += worldWidth; } world = 0; @@ -186,7 +186,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { ++world; offsetX = worldWidth * world; transform = this.getTransform(frameState, offsetX); - replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); + replayGroup.execute(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); startX -= worldWidth; } } @@ -280,7 +280,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { let transform = this.getRenderTransform(frameState, width, height, 0); const skippedFeatureUids = layerState.managed ? frameState.skippedFeatureUids : {}; - replayGroup.replay(context, transform, rotation, skippedFeatureUids, snapToPixel); + replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); if (vectorSource.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) { let startX = extent[0]; @@ -291,7 +291,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { --world; offsetX = worldWidth * world; transform = this.getRenderTransform(frameState, width, height, offsetX); - replayGroup.replay(context, transform, rotation, skippedFeatureUids, snapToPixel); + replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); startX += worldWidth; } world = 0; @@ -300,7 +300,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { ++world; offsetX = worldWidth * world; transform = this.getRenderTransform(frameState, width, height, offsetX); - replayGroup.replay(context, transform, rotation, skippedFeatureUids, snapToPixel); + replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); startX -= worldWidth; } } diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 960fd4ca7d..9bef8b877f 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -13,7 +13,7 @@ import {equivalent as equivalentProjection} from '../../proj.js'; import Units from '../../proj/Units.js'; import ReplayType from '../../render/ReplayType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; -import CanvasBuilderGroup, {replayDeclutter} from '../../render/canvas/InstructionsGroupBuilder.js'; +import CanvasBuilderGroup from '../../render/canvas/InstructionsGroupBuilder.js'; import {ORDER} from '../../render/replay.js'; import CanvasTileLayerRenderer from './TileLayer.js'; import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -24,7 +24,7 @@ import { scale as scaleTransform, translate as translateTransform } from '../../transform.js'; -import CanvasGroupExecutor from '../../render/canvas/ExecutorGroup.js'; +import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js'; /** @@ -108,7 +108,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { getTile(z, x, y, pixelRatio, projection) { const tile = super.getTile(z, x, y, pixelRatio, projection); if (tile.getState() === TileState.LOADED) { - this.createReplayGroup_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); + this.createExecutorGroup_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); if (this.context) { this.renderTileImage_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); } @@ -143,14 +143,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @param {import("../../proj/Projection.js").default} projection Projection. * @private */ - createReplayGroup_(tile, pixelRatio, projection) { + createExecutorGroup_(tile, pixelRatio, projection) { const layer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()); const revision = layer.getRevision(); const renderOrder = /** @type {import("../../render.js").OrderFunction} */ (layer.getRenderOrder()) || null; - const replayState = tile.getReplayState(layer); - if (!replayState.dirty && replayState.renderedRevision == revision && - replayState.renderedRenderOrder == renderOrder) { + const builderState = tile.getReplayState(layer); + if (!builderState.dirty && builderState.renderedRevision == revision && + builderState.renderedRenderOrder == renderOrder) { return; } @@ -167,10 +167,10 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { continue; } if (tile.useLoadedOnly) { - const lowResReplayGroup = sourceTile.getLowResReplayGroup(layer, zoom, tileExtent); - if (lowResReplayGroup) { + const lowResExecutorGroup = sourceTile.getLowResExecutorGroup(layer, zoom, tileExtent); + if (lowResExecutorGroup) { // reuse existing replay if we're rendering an interim tile - sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), lowResReplayGroup); + sourceTile.setExecutorGroup(layer, tile.tileCoord.toString(), lowResExecutorGroup); continue; } } @@ -186,8 +186,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { reproject = true; sourceTile.setProjection(projection); } - replayState.dirty = false; - const replayGroup = new CanvasBuilderGroup(0, sharedExtent, resolution, + builderState.dirty = false; + const builderGroup = new CanvasBuilderGroup(0, sharedExtent, resolution, pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer()); const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio); @@ -202,14 +202,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { styles = styleFunction(feature, resolution); } if (styles) { - const dirty = this.renderFeature(feature, squaredTolerance, styles, replayGroup); + const dirty = this.renderFeature(feature, squaredTolerance, styles, builderGroup); this.dirty_ = this.dirty_ || dirty; - replayState.dirty = replayState.dirty || dirty; + builderState.dirty = builderState.dirty || dirty; } }; const features = sourceTile.getFeatures(); - if (renderOrder && renderOrder !== replayState.renderedRenderOrder) { + if (renderOrder && renderOrder !== builderState.renderedRenderOrder) { features.sort(renderOrder); } for (let i = 0, ii = features.length; i < ii; ++i) { @@ -227,14 +227,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { render.call(this, feature); } } - const replayGroupInstructions = replayGroup.finish(); - const renderingReplayGroup = new CanvasGroupExecutor(0, sharedExtent, resolution, + const replayGroupInstructions = builderGroup.finish(); + const renderingReplayGroup = new CanvasExecutorGroup(0, sharedExtent, resolution, pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer()); renderingReplayGroup.replaceInstructions(replayGroupInstructions); - sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), renderingReplayGroup); + sourceTile.setExecutorGroup(layer, tile.tileCoord.toString(), renderingReplayGroup); } - replayState.renderedRevision = revision; - replayState.renderedRenderOrder = renderOrder; + builderState.renderedRevision = revision; + builderState.renderedRenderOrder = renderOrder; } /** @@ -263,9 +263,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (sourceTile.getState() != TileState.LOADED) { continue; } - const replayGroup = /** @type {CanvasBuilderGroup} */ (sourceTile.getReplayGroup(layer, + const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer, tile.tileCoord.toString())); - found = found || replayGroup.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, {}, + found = found || executorGroup.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, {}, /** * @param {import("../../Feature.js").FeatureLike} feature Feature. * @return {?} Callback result. @@ -371,7 +371,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (sourceTile.getState() != TileState.LOADED) { continue; } - const executorGroup = /** @type {CanvasGroupExecutor} */ (sourceTile.getReplayGroup(layer, tileCoord.toString())); + const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer, tileCoord.toString())); if (!executorGroup || !executorGroup.hasExecutors(replayTypes)) { // sourceTile was not yet loaded when this.createReplayGroup_() was // called, or it has no replays of the types we want to render @@ -403,7 +403,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { context.clip(); } } - executorGroup.replay(context, transform, rotation, {}, snapToPixel, replayTypes, declutterReplays); + executorGroup.execute(context, transform, rotation, {}, snapToPixel, replayTypes, declutterReplays); context.restore(); clips.push(currentClip); zs.push(currentZ); @@ -478,9 +478,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const transform = resetTransform(this.tmpTransform_); scaleTransform(transform, pixelScale, -pixelScale); translateTransform(transform, -tileExtent[0], -tileExtent[3]); - const replayGroup = /** @type {CanvasBuilderGroup} */ (sourceTile.getReplayGroup(layer, + const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer, tile.tileCoord.toString())); - replayGroup.replay(context, transform, 0, {}, true, replays); + executorGroup.execute(context, transform, 0, {}, true, replays); } } } diff --git a/test/spec/ol/renderer/canvas/replay.test.js b/test/spec/ol/renderer/canvas/replay.test.js index de5299c4c7..a8afb2ffbc 100644 --- a/test/spec/ol/renderer/canvas/replay.test.js +++ b/test/spec/ol/renderer/canvas/replay.test.js @@ -92,12 +92,12 @@ describe('ol.render.canvas.ReplayGroup', function() { it('omits lineTo for repeated coordinates', function() { renderFeature(builder, feature0, fill0, 1); executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, {}); + executor.execute(context, transform, 0, {}); expect(lineToCount).to.be(4); lineToCount = 0; scaleTransform(transform, 0.25, 0.25); executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, {}); + executor.execute(context, transform, 0, {}); expect(lineToCount).to.be(3); }); @@ -105,7 +105,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature0, fill0, 1); renderFeature(builder, feature1, fill1, 1); executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, {}); + executor.execute(context, transform, 0, {}); expect(moveToCount).to.be(2); }); @@ -114,7 +114,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature2, style1, 1); renderFeature(builder, feature3, style1, 1); executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, {}); + executor.execute(context, transform, 0, {}); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); @@ -125,7 +125,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature2, style1, 1); renderFeature(builder, feature3, style2, 1); executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, {}); + executor.execute(context, transform, 0, {}); expect(fillCount).to.be(2); expect(strokeCount).to.be(2); expect(beginPathCount).to.be(2); @@ -136,7 +136,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature2, style2, 1); renderFeature(builder, feature3, style1, 1); executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, {}); + executor.execute(context, transform, 0, {}); expect(fillCount).to.be(3); expect(strokeCount).to.be(3); expect(beginPathCount).to.be(3); @@ -149,7 +149,7 @@ describe('ol.render.canvas.ReplayGroup', function() { const skippedUids = {}; skippedUids[getUid(feature1)] = true; executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, skippedUids); + executor.execute(context, transform, 0, skippedUids); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); @@ -162,7 +162,7 @@ describe('ol.render.canvas.ReplayGroup', function() { const skippedUids = {}; skippedUids[getUid(feature3)] = true; executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, skippedUids); + executor.execute(context, transform, 0, skippedUids); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); @@ -176,7 +176,7 @@ describe('ol.render.canvas.ReplayGroup', function() { skippedUids[getUid(feature1)] = true; skippedUids[getUid(feature2)] = true; executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, skippedUids); + executor.execute(context, transform, 0, skippedUids); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); @@ -189,7 +189,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature2, style1, 1); renderFeature(builder, feature3, style1, 1); executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, {}); + executor.execute(context, transform, 0, {}); expect(fillCount).to.be(3); expect(strokeCount).to.be(3); expect(beginPathCount).to.be(3); @@ -218,7 +218,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature1, style2, 1); renderFeature(builder, feature2, style2, 1); executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, {}); + executor.execute(context, transform, 0, {}); expect(lineDashCount).to.be(1); expect(style2.getStroke().getLineDash()).to.eql([3, 6]); @@ -267,7 +267,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, geometrycollection, style, 1); scaleTransform(transform, 0.1, 0.1); executor.replaceInstructions(builder.finish()); - executor.replay(context, transform, 0, {}); + executor.execute(context, transform, 0, {}); expect(calls.length).to.be(9); expect(calls[0].geometry).to.be(point.getGeometry()); expect(calls[0].feature).to.be(point); diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js index 7902ffbde8..4b3f6d7e1e 100644 --- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js +++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js @@ -241,10 +241,8 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { }); map.addLayer(layer2); - const spy1 = sinon.spy(VectorTile.prototype, - 'getReplayGroup'); - const spy2 = sinon.spy(VectorTile.prototype, - 'setReplayGroup'); + const spy1 = sinon.spy(VectorTile.prototype, 'getExecutorGroup'); + const spy2 = sinon.spy(VectorTile.prototype, 'setExecutorGroup'); map.renderSync(); expect(spy1.callCount).to.be(4); expect(spy2.callCount).to.be(2); @@ -308,7 +306,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { }); describe('#forEachFeatureAtCoordinate', function() { - let layer, renderer, replayGroup; + let layer, renderer, executorGroup; class TileClass extends VectorImageTile { constructor() { super(...arguments); @@ -317,8 +315,8 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { const sourceTile = new VectorTile([0, 0, 0]); sourceTile.setState(TileState.LOADED); sourceTile.setProjection(getProjection('EPSG:3857')); - sourceTile.getReplayGroup = function() { - return replayGroup; + sourceTile.getExecutorGroup = function() { + return executorGroup; }; const key = sourceTile.tileCoord.toString(); this.tileKeys = [key]; @@ -329,7 +327,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { } beforeEach(function() { - replayGroup = {}; + executorGroup = {}; layer = new VectorTileLayer({ source: new VectorTileSource({ tileClass: TileClass, @@ -337,7 +335,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { }) }); renderer = new CanvasVectorTileLayerRenderer(layer); - replayGroup.forEachFeatureAtCoordinate = function(coordinate, + executorGroup.forEachFeatureAtCoordinate = function(coordinate, resolution, rotation, hitTolerance, skippedFeaturesUids, callback) { const feature = new Feature(); callback(feature); From ce44a9a3e4a10ee20f43669b7891f027254b9276 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Thu, 15 Nov 2018 23:46:20 +0100 Subject: [PATCH 25/30] Final renamings --- src/ol/VectorTile.js | 16 ++++++++-------- src/ol/render/ExecutorGroup.js | 11 +++++++++-- .../{InstructionsBuilder.js => Builder.js} | 10 +++++----- ...structionsGroupBuilder.js => BuilderGroup.js} | 12 ++++++------ .../{InstructionsExecutor.js => Executor.js} | 6 +++--- src/ol/render/canvas/ExecutorGroup.js | 8 ++++---- src/ol/render/canvas/ImageBuilder.js | 4 ++-- src/ol/render/canvas/LineStringBuilder.js | 4 ++-- src/ol/render/canvas/PolygonBuilder.js | 4 ++-- src/ol/render/canvas/TextBuilder.js | 4 ++-- src/ol/renderer/canvas/VectorLayer.js | 16 ++++++++-------- src/ol/renderer/canvas/VectorTileLayer.js | 4 ++-- test/spec/ol/render/canvas/replaygroup.test.js | 2 +- test/spec/ol/render/canvas/textreplay.test.js | 2 +- test/spec/ol/renderer/canvas/replay.test.js | 4 ++-- test/spec/ol/renderer/vector.test.js | 2 +- 16 files changed, 58 insertions(+), 51 deletions(-) rename src/ol/render/canvas/{InstructionsBuilder.js => Builder.js} (97%) rename src/ol/render/canvas/{InstructionsGroupBuilder.js => BuilderGroup.js} (96%) rename src/ol/render/canvas/{InstructionsExecutor.js => Executor.js} (99%) diff --git a/src/ol/VectorTile.js b/src/ol/VectorTile.js index 11956eaeb8..d4e0f4ab7c 100644 --- a/src/ol/VectorTile.js +++ b/src/ol/VectorTile.js @@ -67,7 +67,7 @@ class VectorTile extends Tile { * @private * @type {Object} */ - this.replayGroups_ = {}; + this.executorGroups_ = {}; /** * @private @@ -88,7 +88,7 @@ class VectorTile extends Tile { */ disposeInternal() { this.features_ = null; - this.replayGroups_ = {}; + this.executorGroups_ = {}; this.state = TileState.ABORT; this.changed(); super.disposeInternal(); @@ -142,10 +142,10 @@ class VectorTile extends Tile { /** * @param {import("./layer/Layer.js").default} layer Layer. * @param {string} key Key. - * @return {import("./render/ExecutorGroup.js").default} Replay group. + * @return {import("./render/ExecutorGroup.js").default} Executor group. */ getExecutorGroup(layer, key) { - return this.replayGroups_[getUid(layer) + ',' + key]; + return this.executorGroups_[getUid(layer) + ',' + key]; } /** @@ -153,17 +153,17 @@ class VectorTile extends Tile { * @param {import("./layer/Layer").default} layer Layer. * @param {number} zoom Zoom. * @param {import("./extent").Extent} extent Extent. - * @return {import("./render/ExecutorGroup.js").default} Replay groups. + * @return {import("./render/ExecutorGroup.js").default} Executor groups. */ getLowResExecutorGroup(layer, zoom, extent) { const layerId = getUid(layer); let bestZoom = 0; let replayGroup = null; - for (const key in this.replayGroups_) { + for (const key in this.executorGroups_) { const keyData = key.split(','); const candidateZoom = Number(keyData[1]); if (keyData[0] === layerId && candidateZoom <= zoom) { - const candidate = this.replayGroups_[key]; + const candidate = this.executorGroups_[key]; if (containsExtent(candidate.getMaxExtent(), extent) && candidateZoom > bestZoom) { replayGroup = candidate; bestZoom = candidateZoom; @@ -247,7 +247,7 @@ class VectorTile extends Tile { * @param {import("./render/ExecutorGroup.js").default} executorGroup Executor group. */ setExecutorGroup(layer, key, executorGroup) { - this.replayGroups_[getUid(layer) + ',' + key] = executorGroup; + this.executorGroups_[getUid(layer) + ',' + key] = executorGroup; } /** diff --git a/src/ol/render/ExecutorGroup.js b/src/ol/render/ExecutorGroup.js index af533b65a1..fb53545268 100644 --- a/src/ol/render/ExecutorGroup.js +++ b/src/ol/render/ExecutorGroup.js @@ -11,7 +11,7 @@ class ExecutorGroup { * @abstract * @param {number|undefined} zIndex Z index. * @param {import("./ReplayType.js").default} replayType Replay type. - * @return {import("./VectorContext.js").default} Replay. + * @return {import("./VectorContext.js").default} Executor. */ getExecutor(zIndex, replayType) { return abstract(); @@ -25,9 +25,16 @@ class ExecutorGroup { return abstract(); } + /** + * @return {import("../extent.js").Extent} The extent of the group. + */ + getMaxExtent() { + return abstract(); + } + /** * @abstract - * @param {boolean} group Group with previous replay + * @param {boolean} group Group with previous executor * @return {Array<*>} The resulting instruction group */ addDeclutter(group) { diff --git a/src/ol/render/canvas/InstructionsBuilder.js b/src/ol/render/canvas/Builder.js similarity index 97% rename from src/ol/render/canvas/InstructionsBuilder.js rename to src/ol/render/canvas/Builder.js index 303392b5e2..1a0a915e8e 100644 --- a/src/ol/render/canvas/InstructionsBuilder.js +++ b/src/ol/render/canvas/Builder.js @@ -1,5 +1,5 @@ /** - * @module ol/render/canvas/InstructionsBuilder + * @module ol/render/canvas/Builder */ import {equals, reverseSubArray} from '../../array.js'; import {asColorLike} from '../../colorlike.js'; @@ -30,7 +30,7 @@ import { */ -class CanvasInstructionsBuilder extends VectorContext { +class CanvasBuilder extends VectorContext { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. @@ -464,7 +464,7 @@ class CanvasInstructionsBuilder extends VectorContext { /** * @param {import("../canvas.js").FillStrokeState} state State. - * @param {function(this:CanvasInstructionsBuilder, import("../canvas.js").FillStrokeState, (import("../../geom/Geometry.js").default|import("../Feature.js").default)):Array<*>} createFill Create fill. + * @param {function(this:CanvasBuilder, import("../canvas.js").FillStrokeState, (import("../../geom/Geometry.js").default|import("../Feature.js").default)):Array<*>} createFill Create fill. * @param {import("../../geom/Geometry.js").default|import("../Feature.js").default} geometry Geometry. */ updateFillStyle(state, createFill, geometry) { @@ -479,7 +479,7 @@ class CanvasInstructionsBuilder extends VectorContext { /** * @param {import("../canvas.js").FillStrokeState} state State. - * @param {function(this:CanvasInstructionsBuilder, import("../canvas.js").FillStrokeState)} applyStroke Apply stroke. + * @param {function(this:CanvasBuilder, import("../canvas.js").FillStrokeState)} applyStroke Apply stroke. */ updateStrokeStyle(state, applyStroke) { const strokeStyle = state.strokeStyle; @@ -543,4 +543,4 @@ class CanvasInstructionsBuilder extends VectorContext { } -export default CanvasInstructionsBuilder; +export default CanvasBuilder; diff --git a/src/ol/render/canvas/InstructionsGroupBuilder.js b/src/ol/render/canvas/BuilderGroup.js similarity index 96% rename from src/ol/render/canvas/InstructionsGroupBuilder.js rename to src/ol/render/canvas/BuilderGroup.js index 883e7d4723..c36b447f4c 100644 --- a/src/ol/render/canvas/InstructionsGroupBuilder.js +++ b/src/ol/render/canvas/BuilderGroup.js @@ -1,5 +1,5 @@ /** - * @module ol/render/canvas/InstructionsGroupBuilder + * @module ol/render/canvas/BuilderGroup */ import {numberSafeCompareFunction} from '../../array.js'; @@ -9,7 +9,7 @@ import {transform2D} from '../../geom/flat/transform.js'; import {isEmpty} from '../../obj.js'; import BuilderGroup from '../BuilderGroup.js'; import ReplayType from '../ReplayType.js'; -import CanvasInstructionsBuilder from './InstructionsBuilder.js'; +import CanvasBuilder from './Builder.js'; import CanvasImageBuilder from './ImageBuilder.js'; import CanvasLineStringBuilder from './LineStringBuilder.js'; import CanvasPolygonBuilder from './PolygonBuilder.js'; @@ -19,11 +19,11 @@ import {create as createTransform, compose as composeTransform} from '../../tran /** - * @type {Object} + * @type {Object} */ const BATCH_CONSTRUCTORS = { 'Circle': CanvasPolygonBuilder, - 'Default': CanvasInstructionsBuilder, + 'Default': CanvasBuilder, 'Image': CanvasImageBuilder, 'LineString': CanvasLineStringBuilder, 'Polygon': CanvasPolygonBuilder, @@ -102,7 +102,7 @@ class CanvasBuilderGroup extends BuilderGroup { /** * @private - * @type {!Object>} + * @type {!Object>} */ this.buildersByZIndex_ = {}; @@ -151,7 +151,7 @@ class CanvasBuilderGroup extends BuilderGroup { } /** - * @return {!Object>} The serializable instructions + * @return {!Object>} The serializable instructions */ finish() { const builderInstructions = {}; diff --git a/src/ol/render/canvas/InstructionsExecutor.js b/src/ol/render/canvas/Executor.js similarity index 99% rename from src/ol/render/canvas/InstructionsExecutor.js rename to src/ol/render/canvas/Executor.js index 94d4a0f6e4..970486dc2e 100644 --- a/src/ol/render/canvas/InstructionsExecutor.js +++ b/src/ol/render/canvas/Executor.js @@ -1,5 +1,5 @@ /** - * @module ol/render/canvas/InstructionsExecutor + * @module ol/render/canvas/Executor */ import {getUid} from '../../util.js'; import {equals, reverseSubArray} from '../../array.js'; @@ -47,7 +47,7 @@ const tmpExtent = createEmpty(); const tmpTransform = createTransform(); -class CanvasInstructionsExecutor { +class CanvasExecutor { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. @@ -1019,4 +1019,4 @@ class CanvasInstructionsExecutor { } -export default CanvasInstructionsExecutor; +export default CanvasExecutor; diff --git a/src/ol/render/canvas/ExecutorGroup.js b/src/ol/render/canvas/ExecutorGroup.js index eb2b14a36a..962a3e3081 100644 --- a/src/ol/render/canvas/ExecutorGroup.js +++ b/src/ol/render/canvas/ExecutorGroup.js @@ -11,7 +11,7 @@ import BaseExecutorGroup from '../ExecutorGroup.js'; import ReplayType from '../ReplayType.js'; import {ORDER} from '../replay.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; -import CanvasInstructionsExecutor from './InstructionsExecutor.js'; +import CanvasExecutor from './Executor.js'; class ExecutorGroup extends BaseExecutorGroup { @@ -85,7 +85,7 @@ class ExecutorGroup extends BaseExecutorGroup { /** * @private - * @type {!Object>} + * @type {!Object>} */ this.executorsByZIndex_ = {}; @@ -118,7 +118,7 @@ class ExecutorGroup extends BaseExecutorGroup { /** * Create executors and populate them using the provided instructions. - * @param {!Object>} allInstructions The serializable instructions + * @param {!Object>} allInstructions The serializable instructions */ replaceInstructions(allInstructions) { this.executorsByZIndex_ = {}; @@ -301,7 +301,7 @@ class ExecutorGroup extends BaseExecutorGroup { } let executor = executors[replayType]; if (executor === undefined) { - executor = new CanvasInstructionsExecutor(this.tolerance_, this.maxExtent_, + executor = new CanvasExecutor(this.tolerance_, this.maxExtent_, this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_); executors[replayType] = executor; } diff --git a/src/ol/render/canvas/ImageBuilder.js b/src/ol/render/canvas/ImageBuilder.js index 12725d8338..7ba5ef1acd 100644 --- a/src/ol/render/canvas/ImageBuilder.js +++ b/src/ol/render/canvas/ImageBuilder.js @@ -2,9 +2,9 @@ * @module ol/render/canvas/ImageBuilder */ import CanvasInstruction from './Instruction.js'; -import CanvasInstructionsBuilder from './InstructionsBuilder.js'; +import CanvasBuilder from './Builder.js'; -class CanvasImageBuilder extends CanvasInstructionsBuilder { +class CanvasImageBuilder extends CanvasBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. diff --git a/src/ol/render/canvas/LineStringBuilder.js b/src/ol/render/canvas/LineStringBuilder.js index f8990d6959..2139a07add 100644 --- a/src/ol/render/canvas/LineStringBuilder.js +++ b/src/ol/render/canvas/LineStringBuilder.js @@ -2,9 +2,9 @@ * @module ol/render/canvas/LineStringBuilder */ import CanvasInstruction, {strokeInstruction, beginPathInstruction} from './Instruction.js'; -import CanvasInstructionsBuilder from './InstructionsBuilder.js'; +import CanvasBuilder from './Builder.js'; -class CanvasLineStringBuilder extends CanvasInstructionsBuilder { +class CanvasLineStringBuilder extends CanvasBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. diff --git a/src/ol/render/canvas/PolygonBuilder.js b/src/ol/render/canvas/PolygonBuilder.js index f0d0107b67..c3969e1751 100644 --- a/src/ol/render/canvas/PolygonBuilder.js +++ b/src/ol/render/canvas/PolygonBuilder.js @@ -7,10 +7,10 @@ import {defaultFillStyle} from '../canvas.js'; import CanvasInstruction, { fillInstruction, strokeInstruction, beginPathInstruction, closePathInstruction } from './Instruction.js'; -import CanvasInstructionsBuilder from './InstructionsBuilder.js'; +import CanvasBuilder from './Builder.js'; -class CanvasPolygonBuilder extends CanvasInstructionsBuilder { +class CanvasPolygonBuilder extends CanvasBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. diff --git a/src/ol/render/canvas/TextBuilder.js b/src/ol/render/canvas/TextBuilder.js index 2989c1d508..d83e19b012 100644 --- a/src/ol/render/canvas/TextBuilder.js +++ b/src/ol/render/canvas/TextBuilder.js @@ -8,11 +8,11 @@ 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 CanvasInstruction from './Instruction.js'; -import CanvasInstructionsBuilder from './InstructionsBuilder.js'; +import CanvasBuilder from './Builder.js'; import {TEXT_ALIGN} from '../replay.js'; import TextPlacement from '../../style/TextPlacement.js'; -class CanvasTextBuilder extends CanvasInstructionsBuilder { +class CanvasTextBuilder extends CanvasBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 92dadf926d..76a2f205ec 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -10,7 +10,7 @@ import rbush from 'rbush'; import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; import RenderEventType from '../../render/EventType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; -import CanvasBuilderGroup from '../../render/canvas/InstructionsGroupBuilder.js'; +import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import InstructionsGroupExecutor from '../../render/canvas/ExecutorGroup.js'; import CanvasLayerRenderer from './Layer.js'; import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -483,16 +483,16 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { } const replayGroupInstructions = replayGroup.finish(); - const renderingReplayGroup = new InstructionsGroupExecutor( + const renderingExecutorGroup = new InstructionsGroupExecutor( getRenderTolerance(resolution, pixelRatio), extent, resolution, pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer()); - renderingReplayGroup.replaceInstructions(replayGroupInstructions); + renderingExecutorGroup.replaceInstructions(replayGroupInstructions); this.renderedResolution_ = resolution; this.renderedRevision_ = vectorLayerRevision; this.renderedRenderOrder_ = vectorLayerRenderOrder; this.renderedExtent_ = extent; - this.replayGroup_ = renderingReplayGroup; + this.replayGroup_ = renderingExecutorGroup; this.replayGroupChanged = true; return true; @@ -503,10 +503,10 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { * @param {number} resolution Resolution. * @param {number} pixelRatio Pixel ratio. * @param {import("../../style/Style.js").default|Array} styles The style or array of styles. - * @param {import("../../render/canvas/InstructionsGroupBuilder.js").default} replayGroup Replay group. + * @param {import("../../render/canvas/BuilderGroup.js").default} builderGroup Builder group. * @return {boolean} `true` if an image is loading. */ - renderFeature(feature, resolution, pixelRatio, styles, replayGroup) { + renderFeature(feature, resolution, pixelRatio, styles, builderGroup) { if (!styles) { return false; } @@ -514,13 +514,13 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { if (Array.isArray(styles)) { for (let i = 0, ii = styles.length; i < ii; ++i) { loading = renderFeature( - replayGroup, feature, styles[i], + builderGroup, feature, styles[i], getSquaredRenderTolerance(resolution, pixelRatio), this.handleStyleImageChange_, this) || loading; } } else { loading = renderFeature( - replayGroup, feature, styles, + builderGroup, feature, styles, getSquaredRenderTolerance(resolution, pixelRatio), this.handleStyleImageChange_, this); } diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 9bef8b877f..4bd30cc533 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -13,7 +13,7 @@ import {equivalent as equivalentProjection} from '../../proj.js'; import Units from '../../proj/Units.js'; import ReplayType from '../../render/ReplayType.js'; import {labelCache, rotateAtOffset} from '../../render/canvas.js'; -import CanvasBuilderGroup from '../../render/canvas/InstructionsGroupBuilder.js'; +import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import {ORDER} from '../../render/replay.js'; import CanvasTileLayerRenderer from './TileLayer.js'; import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; @@ -424,7 +424,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @param {import("../../Feature.js").FeatureLike} feature Feature. * @param {number} squaredTolerance Squared tolerance. * @param {import("../../style/Style.js").default|Array} styles The style or array of styles. - * @param {import("../../render/canvas/InstructionsGroupBuilder.js").default} replayGroup Replay group. + * @param {import("../../render/canvas/BuilderGroup.js").default} replayGroup Replay group. * @return {boolean} `true` if an image is loading. */ renderFeature(feature, squaredTolerance, styles, replayGroup) { diff --git a/test/spec/ol/render/canvas/replaygroup.test.js b/test/spec/ol/render/canvas/replaygroup.test.js index 7313a596d3..7d9a3855a5 100644 --- a/test/spec/ol/render/canvas/replaygroup.test.js +++ b/test/spec/ol/render/canvas/replaygroup.test.js @@ -1,4 +1,4 @@ -import {getCircleArray} from '../../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; +import {getCircleArray} from '../../../../../src/ol/render/canvas/BuilderGroup.js'; describe('ol.render.canvas.ReplayGroup', function() { diff --git a/test/spec/ol/render/canvas/textreplay.test.js b/test/spec/ol/render/canvas/textreplay.test.js index 3bb46ba690..1b879be727 100644 --- a/test/spec/ol/render/canvas/textreplay.test.js +++ b/test/spec/ol/render/canvas/textreplay.test.js @@ -2,7 +2,7 @@ import Feature from '../../../../../src/ol/Feature.js'; import MultiPolygon from '../../../../../src/ol/geom/MultiPolygon.js'; import Polygon from '../../../../../src/ol/geom/Polygon.js'; import CanvasTextReplay from '../../../../../src/ol/render/canvas/TextBuilder.js'; -import InstructionExecutor from '../../../../../src/ol/render/canvas/InstructionsExecutor.js'; +import InstructionExecutor from '../../../../../src/ol/render/canvas/Executor.js'; import Text from '../../../../../src/ol/style/Text.js'; import {create as createTransform} from '../../../../../src/ol/transform.js'; diff --git a/test/spec/ol/renderer/canvas/replay.test.js b/test/spec/ol/renderer/canvas/replay.test.js index a8afb2ffbc..44a11ec865 100644 --- a/test/spec/ol/renderer/canvas/replay.test.js +++ b/test/spec/ol/renderer/canvas/replay.test.js @@ -9,8 +9,8 @@ import Point from '../../../../../src/ol/geom/Point.js'; import Polygon from '../../../../../src/ol/geom/Polygon.js'; import CanvasLineStringBuilder from '../../../../../src/ol/render/canvas/LineStringBuilder.js'; import CanvasPolygonBuilder from '../../../../../src/ol/render/canvas/PolygonBuilder.js'; -import CanvasReplay from '../../../../../src/ol/render/canvas/InstructionsBuilder.js'; -import CanvasInstructionsGroupBuilder from '../../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; +import CanvasReplay from '../../../../../src/ol/render/canvas/Builder.js'; +import CanvasInstructionsGroupBuilder from '../../../../../src/ol/render/canvas/BuilderGroup.js'; import CanvasInstructionsGroupExecutor from '../../../../../src/ol/render/canvas/ExecutorGroup.js'; import {renderFeature} from '../../../../../src/ol/renderer/vector.js'; import Fill from '../../../../../src/ol/style/Fill.js'; diff --git a/test/spec/ol/renderer/vector.test.js b/test/spec/ol/renderer/vector.test.js index 32581697fb..554079ec95 100644 --- a/test/spec/ol/renderer/vector.test.js +++ b/test/spec/ol/renderer/vector.test.js @@ -6,7 +6,7 @@ import Polygon from '../../../../src/ol/geom/Polygon.js'; import MultiLineString from '../../../../src/ol/geom/MultiLineString.js'; import MultiPoint from '../../../../src/ol/geom/MultiPoint.js'; import MultiPolygon from '../../../../src/ol/geom/MultiPolygon.js'; -import CanvasBuilderGroup from '../../../../src/ol/render/canvas/InstructionsGroupBuilder.js'; +import CanvasBuilderGroup from '../../../../src/ol/render/canvas/BuilderGroup.js'; import {renderFeature} from '../../../../src/ol/renderer/vector.js'; import Fill from '../../../../src/ol/style/Fill.js'; import Icon from '../../../../src/ol/style/Icon.js'; From 81d0bc21d513e0f8393c66bb3c0c269ecba0225e Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Fri, 16 Nov 2018 12:36:50 +0100 Subject: [PATCH 26/30] Pass instructions to executor constructor --- src/ol/render/canvas/Executor.js | 30 +++-------- src/ol/render/canvas/ExecutorGroup.js | 25 +++++++-- src/ol/renderer/canvas/VectorLayer.js | 4 +- src/ol/renderer/canvas/VectorTileLayer.js | 3 +- test/spec/ol/render/canvas/textreplay.test.js | 3 +- test/spec/ol/renderer/canvas/replay.test.js | 54 +++++++++---------- 6 files changed, 57 insertions(+), 62 deletions(-) diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index 970486dc2e..a2d5e470f6 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -55,9 +55,9 @@ class CanvasExecutor { * @param {number} pixelRatio Pixel ratio. * @param {boolean} overlaps The replay can have overlapping geometries. * @param {?} declutterTree Declutter tree. + * @param {SerializableInstructions} instructions The serializable instructions */ - constructor(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) { - + constructor(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree, instructions) { /** * @type {?} */ @@ -129,13 +129,13 @@ class CanvasExecutor { * @protected * @type {Array<*>} */ - this.instructions = []; + this.instructions = instructions.instructions; /** * @protected * @type {Array} */ - this.coordinates = []; + this.coordinates = instructions.coordinates; /** * @private @@ -153,7 +153,7 @@ class CanvasExecutor { * @protected * @type {Array<*>} */ - this.hitDetectionInstructions = []; + this.hitDetectionInstructions = instructions.hitDetectionInstructions; /** * @private @@ -176,17 +176,17 @@ class CanvasExecutor { /** * @type {!Object} */ - this.fillStates = {}; + this.fillStates = instructions.fillStates || {}; /** * @type {!Object} */ - this.strokeStates = {}; + this.strokeStates = instructions.strokeStates || {}; /** * @type {!Object} */ - this.textStates = {}; + this.textStates = instructions.textStates || {}; // Adaptations @@ -267,20 +267,6 @@ class CanvasExecutor { return labelCache.get(key); } - /** - * Recreate replays and populate them using the provided instructions. - * @param {SerializableInstructions} instructions The serializable instructions - */ - replaceInstructions(instructions) { - this.instructions = instructions.instructions; - this.hitDetectionInstructions = instructions.hitDetectionInstructions; - this.coordinates = instructions.coordinates; - // Workaround for decluttered text creation / rendering being coupled - this.textStates = instructions.textStates; - this.fillStates = instructions.fillStates; - this.strokeStates = instructions.strokeStates; - } - /** * @param {CanvasRenderingContext2D} context Context. * @param {import("../../coordinate.js").Coordinate} p1 1st point of the background box. diff --git a/src/ol/render/canvas/ExecutorGroup.js b/src/ol/render/canvas/ExecutorGroup.js index 962a3e3081..c9a0331032 100644 --- a/src/ol/render/canvas/ExecutorGroup.js +++ b/src/ol/render/canvas/ExecutorGroup.js @@ -22,6 +22,8 @@ class ExecutorGroup extends BaseExecutorGroup { * @param {number} pixelRatio Pixel ratio. * @param {boolean} overlaps The executor group can have overlapping geometries. * @param {?} declutterTree Declutter tree for declutter processing in postrender. + * @param {!Object>} allInstructions + * The serializable instructions. * @param {number=} opt_renderBuffer Optional rendering buffer. */ constructor( @@ -31,6 +33,7 @@ class ExecutorGroup extends BaseExecutorGroup { pixelRatio, overlaps, declutterTree, + allInstructions, opt_renderBuffer ) { super(); @@ -100,6 +103,8 @@ class ExecutorGroup extends BaseExecutorGroup { * @type {import("../../transform.js").Transform} */ this.hitDetectionTransform_ = createTransform(); + + this.createExectutors_(allInstructions); } /** @@ -118,16 +123,20 @@ class ExecutorGroup extends BaseExecutorGroup { /** * Create executors and populate them using the provided instructions. + * @private * @param {!Object>} allInstructions The serializable instructions */ - replaceInstructions(allInstructions) { - this.executorsByZIndex_ = {}; + createExectutors_(allInstructions) { for (const zIndex in allInstructions) { + let executors = this.executorsByZIndex_[zIndex]; + if (executors === undefined) { + this.executorsByZIndex_[zIndex] = executors = {}; + } const instructionByZindex = allInstructions[zIndex]; for (const replayType in instructionByZindex) { const instructions = instructionByZindex[replayType]; - const executor = this.getExecutor(zIndex, replayType); - executor.replaceInstructions(instructions); + executors[replayType] = new CanvasExecutor(this.tolerance_, this.maxExtent_, + this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_, instructions); } } } @@ -301,8 +310,14 @@ class ExecutorGroup extends BaseExecutorGroup { } let executor = executors[replayType]; if (executor === undefined) { + // FIXME: it should not be possible to ask for an executor that does not exist executor = new CanvasExecutor(this.tolerance_, this.maxExtent_, - this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_); + this.resolution_, this.pixelRatio_, this.overlaps_, { + instructions: [], + hitDetectionInstructions: [], + coordinates: [] + }, + this.declutterTree_); executors[replayType] = executor; } return executor; diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 76a2f205ec..295dcd31c5 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -485,8 +485,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { const replayGroupInstructions = replayGroup.finish(); const renderingExecutorGroup = new InstructionsGroupExecutor( getRenderTolerance(resolution, pixelRatio), extent, resolution, - pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer()); - renderingExecutorGroup.replaceInstructions(replayGroupInstructions); + pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, + replayGroupInstructions, vectorLayer.getRenderBuffer()); this.renderedResolution_ = resolution; this.renderedRevision_ = vectorLayerRevision; diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 4bd30cc533..62850f0974 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -229,8 +229,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { } const replayGroupInstructions = builderGroup.finish(); const renderingReplayGroup = new CanvasExecutorGroup(0, sharedExtent, resolution, - pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer()); - renderingReplayGroup.replaceInstructions(replayGroupInstructions); + pixelRatio, source.getOverlaps(), this.declutterTree_, replayGroupInstructions, layer.getRenderBuffer()); sourceTile.setExecutorGroup(layer, tile.tileCoord.toString(), renderingReplayGroup); } builderState.renderedRevision = revision; diff --git a/test/spec/ol/render/canvas/textreplay.test.js b/test/spec/ol/render/canvas/textreplay.test.js index 1b879be727..590ab22cbe 100644 --- a/test/spec/ol/render/canvas/textreplay.test.js +++ b/test/spec/ol/render/canvas/textreplay.test.js @@ -28,10 +28,9 @@ function createContext() { function executeInstructions(builder, expectedDrawTextImageCalls, expectedReplayImageCalls) { const transform = createTransform(); const context = createContext(); - const executor = new InstructionExecutor(1, [-180, -90, 180, 90], 0.02, 1, null); + const executor = new InstructionExecutor(1, [-180, -90, 180, 90], 0.02, 1, false, null, builder.finish()); sinon.spy(executor, 'drawTextImageWithPointPlacement_'); const replayImageStub = sinon.stub(executor, 'replayImage_'); - executor.replaceInstructions(builder.finish()); executor.execute(context, transform); expect(executor.drawTextImageWithPointPlacement_.callCount).to.be(expectedDrawTextImageCalls); expect(replayImageStub.callCount).to.be(expectedReplayImageCalls); diff --git a/test/spec/ol/renderer/canvas/replay.test.js b/test/spec/ol/renderer/canvas/replay.test.js index 44a11ec865..3bfb3881fe 100644 --- a/test/spec/ol/renderer/canvas/replay.test.js +++ b/test/spec/ol/renderer/canvas/replay.test.js @@ -22,15 +22,26 @@ describe('ol.render.canvas.ReplayGroup', function() { describe('#replay', function() { - let context, builder, executor, fillCount, transform; + let context, builder, fillCount, transform; let strokeCount, beginPathCount, moveToCount, lineToCount; let feature0, feature1, feature2, feature3; let fill0, fill1, style1, style2; + /** + * @param {CanvasInstructionsGroupBuilder} builder The builder to get instructions from. + * @param {Object=} skippedUids The ids to skip. + * @param {number=} pixelRatio The pixel ratio. + * @param {boolean=} overlaps Whether there is overlaps. + */ + function execute(builder, skippedUids, pixelRatio, overlaps) { + const executor = new CanvasInstructionsGroupExecutor(1, [-180, -90, 180, 90], 1, + pixelRatio || 1, !!overlaps, null, builder.finish()); + executor.execute(context, transform, 0, skippedUids || {}); + } + beforeEach(function() { transform = createTransform(); builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 1, false); - executor = new CanvasInstructionsGroupExecutor(1, [-180, -90, 180, 90], 1, 1, false); feature0 = new Feature(new Polygon( [[[-90, 0], [-45, 45], [0, 0], [1, 1], [0, -45], [-90, 0]]])); feature1 = new Feature(new Polygon( @@ -91,21 +102,18 @@ describe('ol.render.canvas.ReplayGroup', function() { it('omits lineTo for repeated coordinates', function() { renderFeature(builder, feature0, fill0, 1); - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, {}); + execute(builder); expect(lineToCount).to.be(4); lineToCount = 0; scaleTransform(transform, 0.25, 0.25); - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, {}); + execute(builder); expect(lineToCount).to.be(3); }); it('does not omit moveTo for repeated coordinates', function() { renderFeature(builder, feature0, fill0, 1); renderFeature(builder, feature1, fill1, 1); - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, {}); + execute(builder); expect(moveToCount).to.be(2); }); @@ -113,8 +121,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature1, style1, 1); renderFeature(builder, feature2, style1, 1); renderFeature(builder, feature3, style1, 1); - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, {}); + execute(builder); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); @@ -124,8 +131,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature1, style1, 1); renderFeature(builder, feature2, style1, 1); renderFeature(builder, feature3, style2, 1); - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, {}); + execute(builder); expect(fillCount).to.be(2); expect(strokeCount).to.be(2); expect(beginPathCount).to.be(2); @@ -135,8 +141,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature1, style1, 1); renderFeature(builder, feature2, style2, 1); renderFeature(builder, feature3, style1, 1); - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, {}); + execute(builder); expect(fillCount).to.be(3); expect(strokeCount).to.be(3); expect(beginPathCount).to.be(3); @@ -148,8 +153,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature3, style2, 1); const skippedUids = {}; skippedUids[getUid(feature1)] = true; - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, skippedUids); + execute(builder, skippedUids); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); @@ -161,8 +165,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature3, style2, 1); const skippedUids = {}; skippedUids[getUid(feature3)] = true; - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, skippedUids); + execute(builder, skippedUids); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); @@ -175,8 +178,7 @@ describe('ol.render.canvas.ReplayGroup', function() { const skippedUids = {}; skippedUids[getUid(feature1)] = true; skippedUids[getUid(feature2)] = true; - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, skippedUids); + execute(builder, skippedUids); expect(fillCount).to.be(1); expect(strokeCount).to.be(1); expect(beginPathCount).to.be(1); @@ -184,12 +186,10 @@ describe('ol.render.canvas.ReplayGroup', function() { it('does not batch when overlaps is set to true', function() { builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 1, true); - executor = new CanvasInstructionsGroupExecutor(1, [-180, -90, 180, 90], 1, 1, true); renderFeature(builder, feature1, style1, 1); renderFeature(builder, feature2, style1, 1); renderFeature(builder, feature3, style1, 1); - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, {}); + execute(builder, {}, 1, true); expect(fillCount).to.be(3); expect(strokeCount).to.be(3); expect(beginPathCount).to.be(3); @@ -198,7 +198,6 @@ describe('ol.render.canvas.ReplayGroup', function() { it('applies the pixelRatio to the linedash array and offset', function() { // replay with a pixelRatio of 2 builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 2, true); - executor = new CanvasInstructionsGroupExecutor(1, [-180, -90, 180, 90], 1, 2, true); let lineDash, lineDashCount = 0, lineDashOffset, lineDashOffsetCount = 0; @@ -217,8 +216,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, feature1, style2, 1); renderFeature(builder, feature2, style2, 1); - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, {}); + execute(builder, {}, 2, true); expect(lineDashCount).to.be(1); expect(style2.getStroke().getLineDash()).to.eql([3, 6]); @@ -257,7 +255,6 @@ describe('ol.render.canvas.ReplayGroup', function() { const geometrycollection = new Feature(new GeometryCollection( [point.getGeometry(), linestring.getGeometry(), polygon.getGeometry()])); builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 1, true); - executor = new CanvasInstructionsGroupExecutor(1, [-180, -90, 180, 90], 1, 1, true); renderFeature(builder, point, style, 1); renderFeature(builder, multipoint, style, 1); renderFeature(builder, linestring, style, 1); @@ -266,8 +263,7 @@ describe('ol.render.canvas.ReplayGroup', function() { renderFeature(builder, multipolygon, style, 1); renderFeature(builder, geometrycollection, style, 1); scaleTransform(transform, 0.1, 0.1); - executor.replaceInstructions(builder.finish()); - executor.execute(context, transform, 0, {}); + execute(builder, {}, 1, true); expect(calls.length).to.be(9); expect(calls[0].geometry).to.be(point.getGeometry()); expect(calls[0].feature).to.be(point); From d3355f613ca29399f57f8a0a5db8ef0d04a7a597 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 16 Nov 2018 13:04:03 +0100 Subject: [PATCH 27/30] Combine if blocks --- src/ol/render/canvas/Executor.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index a2d5e470f6..3f85ef3729 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -707,10 +707,8 @@ class CanvasExecutor { } let widthIndex = 0; for (; d < dd; d += 2) { - if (geometryWidths) { - if (geometryWidths[widthIndex++] < width / this.pixelRatio) { - continue; - } + if (geometryWidths && geometryWidths[widthIndex++] < width / this.pixelRatio) { + continue; } this.replayImage_(context, pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY, From 6cbde797be062f42ada7bb9afddf1bb4fca0966c Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 16 Nov 2018 13:35:15 +0100 Subject: [PATCH 28/30] Use NaN for unavailable values and handle text creation separately --- src/ol/render/canvas/Executor.js | 12 +++++------- src/ol/render/canvas/TextBuilder.js | 11 ++++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index 3f85ef3729..b1179827bb 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -669,21 +669,19 @@ class CanvasExecutor { let width = /** @type {number} */ (instruction[14]); - if (!image) { - if (instruction.length < 19 || !instruction[18]) { - continue; - } + if (!image && instruction.length >= 19) { + // create label images text = /** @type {string} */ (instruction[18]); textKey = /** @type {string} */ (instruction[19]); strokeKey = /** @type {string} */ (instruction[20]); fillKey = /** @type {string} */ (instruction[21]); const labelWithAnchor = this.drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey); - const textOffsetX = /** @type {number} */ (instruction[22]); - const textOffsetY = /** @type {number} */ (instruction[23]); image = instruction[3] = labelWithAnchor.label; + const textOffsetX = /** @type {number} */ (instruction[22]); anchorX = instruction[4] = (labelWithAnchor.anchorX - textOffsetX) * this.pixelRatio; + const textOffsetY = /** @type {number} */ (instruction[23]); anchorY = instruction[5] = (labelWithAnchor.anchorY - textOffsetY) * this.pixelRatio; - height = instruction[8] = image.height; + height = instruction[7] = image.height; width = instruction[14] = image.width; } diff --git a/src/ol/render/canvas/TextBuilder.js b/src/ol/render/canvas/TextBuilder.js index d83e19b012..9c554c9f49 100644 --- a/src/ol/render/canvas/TextBuilder.js +++ b/src/ol/render/canvas/TextBuilder.js @@ -256,11 +256,12 @@ class CanvasTextBuilder extends CanvasBuilder { this.beginGeometry(geometry, feature); // The image is unknown at this stage so we pass null; it will be computed at render time. - // For clarity, we pass Infinity for numerical values that will be computed at render time. + // For clarity, we pass NaN for offsetX, offsetY, width and height, which will be computed at + // render time. const pixelRatio = this.pixelRatio; this.instructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, - null, Infinity, Infinity, this.declutterGroup_, Infinity, 1, 0, 0, - this.textRotateWithView_, this.textRotation_, 1, Infinity, + null, NaN, NaN, this.declutterGroup_, NaN, 1, 0, 0, + this.textRotateWithView_, this.textRotation_, 1, NaN, textState.padding == defaultPadding ? defaultPadding : textState.padding.map(function(p) { return p * pixelRatio; @@ -270,8 +271,8 @@ class CanvasTextBuilder extends CanvasBuilder { this.textOffsetX_, this.textOffsetY_, geometryWidths ]); this.hitDetectionInstructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, - null, Infinity, Infinity, this.declutterGroup_, Infinity, 1, 0, 0, - this.textRotateWithView_, this.textRotation_, 1 / this.pixelRatio, Infinity, + null, NaN, NaN, this.declutterGroup_, NaN, 1, 0, 0, + this.textRotateWithView_, this.textRotation_, 1 / this.pixelRatio, NaN, textState.padding, !!textState.backgroundFill, !!textState.backgroundStroke, this.text_, this.textKey_, this.strokeKey_, this.fillKey_, From 981b3980420afadebd90bd54e456c266602f49d4 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 16 Nov 2018 13:49:50 +0100 Subject: [PATCH 29/30] Add a FIXME for the measure function to go away --- src/ol/render/canvas/Executor.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index b1179827bb..00a72bbc8e 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -743,6 +743,7 @@ class CanvasExecutor { this.widths_[font] = widths = {}; } + //FIXME Do not create this function on every call const measure = function(text) { let width = widths[text]; if (!width) { From 8d51e0d4879ef17e71c36637cfc12d18bb238d6f Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 16 Nov 2018 13:50:42 +0100 Subject: [PATCH 30/30] Remove resolved FIXMEs --- src/ol/render/canvas/Executor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index 00a72bbc8e..cb018d1e42 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -503,10 +503,10 @@ class CanvasExecutor { const label = this.getTextImage(text, textKey, fillKey, strokeKey); - const strokeState = this.strokeStates[strokeKey]; // FIXME: check if it is correct, was this.textStrokeState_; + const strokeState = this.strokeStates[strokeKey]; const pixelRatio = this.pixelRatio; const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign]; - const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline]; // FIXME: why I need a default now? + const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline]; const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0; const anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth;