From 1afc686af93ce8112f3bdcfe03140c85c00e5a05 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Fri, 10 Nov 2017 16:47:15 +0100 Subject: [PATCH 1/3] Add text background rendering and text padding --- externs/olx.js | 35 ++++- src/ol/render/canvas.js | 7 + src/ol/render/canvas/linestringreplay.js | 18 ++- src/ol/render/canvas/polygonreplay.js | 12 +- src/ol/render/canvas/replay.js | 189 +++++++++++++++++++---- src/ol/render/canvas/textreplay.js | 18 ++- src/ol/style/text.js | 81 ++++++++++ 7 files changed, 312 insertions(+), 48 deletions(-) diff --git a/externs/olx.js b/externs/olx.js index cf29fa0d01..404a6eba61 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -275,7 +275,7 @@ olx.interaction.InteractionOptions; * Method called by the map to notify the interaction that a browser event was * dispatched to the map. If the function returns a falsy value, * propagation of the event to other interactions in the map's interactions - * chain will be prevented (this includes functions with no explicit return). See + * chain will be prevented (this includes functions with no explicit return). See * {@link https://developer.mozilla.org/en-US/docs/Glossary/Falsy} * @type {function(ol.MapBrowserEvent):boolean} * @api @@ -7861,7 +7861,10 @@ olx.style.StrokeOptions.prototype.width; * textAlign: (string|undefined), * textBaseline: (string|undefined), * fill: (ol.style.Fill|undefined), - * stroke: (ol.style.Stroke|undefined)}} + * stroke: (ol.style.Stroke|undefined), + * backgroundFill: (ol.style.Fill|undefined), + * backgroundStroke: (ol.style.Stroke|undefined), + * padding: (Array.|undefined)}} */ olx.style.TextOptions; @@ -7990,6 +7993,34 @@ olx.style.TextOptions.prototype.fill; olx.style.TextOptions.prototype.stroke; +/** + * Fill style for the text background when `placement` is `'point'`. Default is + * no fill. + * @type {ol.style.Fill|undefined} + * @api + */ +olx.style.TextOptions.prototype.backgroundFill; + + +/** + * Stroke style for the text background when `placement` is `'point'`. Default + * is no stroke. + * @type {ol.style.Stroke|undefined} + * @api + */ +olx.style.TextOptions.prototype.backgroundStroke; + + +/** + * Padding in pixels around the text for decluttering and background. The order + * of values in the array is `[top, right, bottom, left]`. Default is + * `[0, 0, 0, 0]`. + * @type {Array.|undefined} + * @api + */ +olx.style.TextOptions.prototype.padding; + + /** * @typedef {{geometry: (undefined|string|ol.geom.Geometry|ol.StyleGeometryFunction), * fill: (ol.style.Fill|undefined), diff --git a/src/ol/render/canvas.js b/src/ol/render/canvas.js index 2680f08077..710519d128 100644 --- a/src/ol/render/canvas.js +++ b/src/ol/render/canvas.js @@ -77,6 +77,13 @@ ol.render.canvas.defaultTextAlign = 'center'; ol.render.canvas.defaultTextBaseline = 'middle'; +/** + * @const + * @type {Array.} + */ +ol.render.canvas.defaultPadding = [0, 0, 0, 0]; + + /** * @const * @type {number} diff --git a/src/ol/render/canvas/linestringreplay.js b/src/ol/render/canvas/linestringreplay.js index 7741236a44..04035b33d9 100644 --- a/src/ol/render/canvas/linestringreplay.js +++ b/src/ol/render/canvas/linestringreplay.js @@ -54,7 +54,7 @@ ol.render.canvas.LineStringReplay.prototype.drawLineString = function(lineString if (strokeStyle === undefined || lineWidth === undefined) { return; } - this.updateStrokeStyle(state, true); + this.updateStrokeStyle(state, this.applyStroke); this.beginGeometry(lineStringGeometry, feature); this.hitDetectionInstructions.push([ ol.render.canvas.Instruction.SET_STROKE_STYLE, @@ -81,7 +81,7 @@ ol.render.canvas.LineStringReplay.prototype.drawMultiLineString = function(multi if (strokeStyle === undefined || lineWidth === undefined) { return; } - this.updateStrokeStyle(state, true); + this.updateStrokeStyle(state, this.applyStroke); this.beginGeometry(multiLineStringGeometry, feature); this.hitDetectionInstructions.push([ ol.render.canvas.Instruction.SET_STROKE_STYLE, @@ -115,3 +115,17 @@ ol.render.canvas.LineStringReplay.prototype.finish = function() { this.reverseHitDetectionInstructions(); this.state = null; }; + + +/** + * @inheritDoc. + */ +ol.render.canvas.LineStringReplay.prototype.applyStroke = function(state) { + if (state.lastStroke != undefined && state.lastStroke != this.coordinates.length) { + this.instructions.push([ol.render.canvas.Instruction.STROKE]); + state.lastStroke = this.coordinates.length; + } + state.lastStroke = 0; + ol.render.canvas.Replay.prototype.applyStroke.call(this, state); + this.instructions.push([ol.render.canvas.Instruction.BEGIN_PATH]); +}; diff --git a/src/ol/render/canvas/polygonreplay.js b/src/ol/render/canvas/polygonreplay.js index c3ea0a763e..8589850203 100644 --- a/src/ol/render/canvas/polygonreplay.js +++ b/src/ol/render/canvas/polygonreplay.js @@ -214,16 +214,10 @@ ol.render.canvas.PolygonReplay.prototype.finish = function() { ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function(geometry) { var state = this.state; var fillStyle = state.fillStyle; - if (fillStyle !== undefined && (typeof fillStyle !== 'string' || state.currentFillStyle != fillStyle)) { - var fillInstruction = [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]; - if (typeof fillStyle !== 'string') { - var fillExtent = geometry.getExtent(); - fillInstruction.push([fillExtent[0], fillExtent[3]]); - } - this.instructions.push(fillInstruction); - state.currentFillStyle = state.fillStyle; + if (fillStyle !== undefined) { + this.updateFillStyle(state, this.applyFill, geometry); } if (state.strokeStyle !== undefined) { - this.updateStrokeStyle(state, false); + this.updateStrokeStyle(state, this.applyStroke); } }; diff --git a/src/ol/render/canvas/replay.js b/src/ol/render/canvas/replay.js index b7159d814e..77175d4890 100644 --- a/src/ol/render/canvas/replay.js +++ b/src/ol/render/canvas/replay.js @@ -178,9 +178,14 @@ ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext); * @param {number} scale Scale. * @param {boolean} snapToPixel Snap to pixel. * @param {number} width Width. + * @param {Array.} padding Padding. + * @param {boolean} fill Background fill. + * @param {boolean} stroke Backgroud stroke. */ -ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, anchorX, anchorY, - declutterGroup, height, opacity, originX, originY, rotation, scale, snapToPixel, width) { +ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, + anchorX, anchorY, declutterGroup, height, opacity, originX, originY, + rotation, scale, snapToPixel, width, padding, fill, stroke) { + var fillStroke = fill || stroke; var localTransform = this.tmpLocalTransform_; anchorX *= scale; anchorY *= scale; @@ -194,6 +199,18 @@ ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, var w = (width + originX > image.width) ? image.width - originX : width; var h = (height + originY > image.height) ? image.height - originY : height; var box = this.tmpExtent_; + var boxW = padding[3] + w * scale + padding[1]; + var boxH = padding[0] + h * scale + padding[2]; + var boxX = x - padding[3]; + var boxY = y - padding[0]; + + var p1, p2, p3, p4; + if (fillStroke || rotation !== 0) { + p1 = [boxX, boxY]; + p2 = [boxX + boxW, boxY]; + p3 = [boxX + boxW, boxY + boxH]; + p4 = [boxX, boxY + boxH]; + } var transform = null; if (rotation !== 0) { @@ -201,13 +218,14 @@ ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, var centerY = y + anchorY; transform = ol.transform.compose(localTransform, centerX, centerY, 1, 1, rotation, -centerX, -centerY); + ol.extent.createOrUpdateEmpty(box); - ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, [x, y])); - ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, [x + w, y])); - ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, [x + w, y + h])); - ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, [x, y + w])); + ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.} */ (p1))); + ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.} */ (p2))); + ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.} */ (p3))); + ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.} */ (p4))); } else { - ol.extent.createOrUpdate(x, y, x + w * scale, y + h * scale, box); + ol.extent.createOrUpdate(boxX, boxY, boxX + boxW, boxY + boxH, box); } var canvas = context.canvas; var intersects = box[0] <= canvas.width && box[2] >= 0 && box[1] <= canvas.height && box[3] >= 0; @@ -215,11 +233,48 @@ ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, if (!intersects && declutterGroup[4] == 1) { return; } + /** @type {ol.CanvasFillStrokeState} */ + var state; + if (intersects && fillStroke) { + state = /** @type {ol.CanvasFillStrokeState} */ ({}); + if (fill) { + state.fillStyle = context.fillStyle; + } + if (stroke) { + state.strokeStyle = context.strokeStyle; + state.lineWidth = context.lineWidth; + state.lineCap = context.lineCap; + state.lineJoin = context.lineJoin; + state.miterLimit = context.miterLimit; + if (ol.has.CANVAS_LINE_DASH) { + state.lineDashOffset = context.lineDashOffset; + state.lineDash = context.getLineDash(); + } + } + } ol.extent.extend(declutterGroup, box); - declutterGroup.push(intersects ? + var declutterArgs = intersects ? [context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] : - null); + null; + if (declutterArgs && fillStroke) { + declutterArgs.push(state, p1, p2, p3, p4); + } + declutterGroup.push(declutterArgs); } else if (intersects) { + if (fillStroke) { + 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 (fill) { + context.fill(); + } + if (stroke) { + context.stroke(); + } + } ol.render.canvas.drawImage(context, transform, opacity, image, originX, originY, w, h, x, y, scale); } }; @@ -413,8 +468,35 @@ ol.render.canvas.Replay.prototype.renderDeclutter_ = function(declutterGroup) { this.declutterTree.insert(box); var drawImage = ol.render.canvas.drawImage; for (var j = 5, jj = declutterGroup.length; j < jj; ++j) { - if (declutterGroup[j]) { - drawImage.apply(undefined, /** @type {Array} */ (declutterGroup[j])); + var declutterData = /** @type {Array} */ (declutterGroup[j]); + if (declutterData) { + if (declutterData.length > 11) { + var context = /** @type {CanvasRenderingContext2D} */ (declutterData[0]); + context.beginPath(); + context.moveTo.apply(context, declutterData[12]); + context.lineTo.apply(context, declutterData[13]); + context.lineTo.apply(context, declutterData[14]); + context.lineTo.apply(context, declutterData[15]); + context.lineTo.apply(context, declutterData[12]); + var state = /** @type {ol.CanvasFillStrokeState} */ (declutterData[11]); + if (state.fillStyle) { + context.fillStyle = state.fillStyle; + context.fill(); + } + if (state.strokeStyle) { + context.strokeStyle = state.strokeStyle; + context.lineWidth = /** @type {number} */ (state.lineWidth); + context.lineCap = /** @type {string} */ (state.lineCap); + context.lineJoin = /** @type {string} */ (state.lineJoin); + context.miterLimit = /** @type {number} */ (state.miterLimit); + if (ol.has.CANVAS_LINE_DASH) { + context.lineDashOffset = /** @type {number} */ (state.lineDashOffset); + context.setLineDash(state.lineDash); + } + context.stroke(); + } + } + drawImage.apply(undefined, declutterData); } } } @@ -461,7 +543,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( var ii = instructions.length; // end of instructions var d = 0; // data index var dd; // end of per-instruction data - var anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup; + var anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image; var pendingFill = 0; var pendingStroke = 0; var coordinateCache = this.coordinateCache_; @@ -552,7 +634,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( case ol.render.canvas.Instruction.DRAW_IMAGE: d = /** @type {number} */ (instruction[1]); dd = /** @type {number} */ (instruction[2]); - var image = /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */ + image = /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */ (instruction[3]); // Remaining arguments in DRAW_IMAGE are in alphabetical order anchorX = /** @type {number} */ (instruction[4]); @@ -568,13 +650,24 @@ ol.render.canvas.Replay.prototype.replay_ = function( var snapToPixel = /** @type {boolean} */ (instruction[14]); var width = /** @type {number} */ (instruction[15]); + var padding, backgroundFill, backgroundStroke; + if (instruction.length > 16) { + padding = /** @type {Array.} */ (instruction[16]); + backgroundFill = /** @type {boolean} */ (instruction[17]); + backgroundStroke = /** @type {boolean} */ (instruction[18]); + } else { + padding = ol.render.canvas.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); + declutterGroup, height, opacity, originX, originY, rotation, scale, + snapToPixel, width, padding, backgroundFill, backgroundStroke); } this.renderDeclutter_(declutterGroup); ++i; @@ -613,7 +706,8 @@ ol.render.canvas.Replay.prototype.replay_ = function( 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]), textScale, false, label.width, + ol.render.canvas.defaultPadding, false, false); } } if (fill) { @@ -626,7 +720,8 @@ ol.render.canvas.Replay.prototype.replay_ = function( 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]), textScale, false, label.width, + ol.render.canvas.defaultPadding, false, false); } } } @@ -849,9 +944,51 @@ ol.render.canvas.Replay.prototype.setFillStrokeStyle = function(fillStyle, strok /** * @param {ol.CanvasFillStrokeState} state State. - * @param {boolean} managePath Manage stoke() - beginPath() for linestrings. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. */ -ol.render.canvas.Replay.prototype.updateStrokeStyle = function(state, managePath) { +ol.render.canvas.Replay.prototype.applyFill = function(state, geometry) { + var fillStyle = state.fillStyle; + var fillInstruction = [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]; + if (typeof fillStyle !== 'string') { + var fillExtent = geometry.getExtent(); + fillInstruction.push([fillExtent[0], fillExtent[3]]); + } + this.instructions.push(fillInstruction); +}; + + +/** + * @param {ol.CanvasFillStrokeState} state State. + */ +ol.render.canvas.Replay.prototype.applyStroke = function(state) { + this.instructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth * this.pixelRatio, state.lineCap, + state.lineJoin, state.miterLimit, + this.applyPixelRatio(state.lineDash), state.lineDashOffset * this.pixelRatio + ]); +}; + + +/** + * @param {ol.CanvasFillStrokeState} state State. + * @param {function(this:ol.render.canvas.Replay, ol.CanvasFillStrokeState, (ol.geom.Geometry|ol.render.Feature))} applyFill Apply fill. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + */ +ol.render.canvas.Replay.prototype.updateFillStyle = function(state, applyFill, geometry) { + var fillStyle = state.fillStyle; + if (typeof fillStyle !== 'string' || state.currentFillStyle != fillStyle) { + applyFill.call(this, state, geometry); + state.currentFillStyle = fillStyle; + } +}; + + +/** + * @param {ol.CanvasFillStrokeState} state State. + * @param {function(this:ol.render.canvas.Replay, ol.CanvasFillStrokeState)} applyStroke Apply stroke. + */ +ol.render.canvas.Replay.prototype.updateStrokeStyle = function(state, applyStroke) { var strokeStyle = state.strokeStyle; var lineCap = state.lineCap; var lineDash = state.lineDash; @@ -866,21 +1003,7 @@ ol.render.canvas.Replay.prototype.updateStrokeStyle = function(state, managePath state.currentLineJoin != lineJoin || state.currentLineWidth != lineWidth || state.currentMiterLimit != miterLimit) { - if (managePath) { - if (state.lastStroke != undefined && state.lastStroke != this.coordinates.length) { - this.instructions.push([ol.render.canvas.Instruction.STROKE]); - state.lastStroke = this.coordinates.length; - } - state.lastStroke = 0; - } - this.instructions.push([ - ol.render.canvas.Instruction.SET_STROKE_STYLE, - strokeStyle, lineWidth * this.pixelRatio, lineCap, lineJoin, miterLimit, - this.applyPixelRatio(lineDash), lineDashOffset * this.pixelRatio - ]); - if (managePath) { - this.instructions.push([ol.render.canvas.Instruction.BEGIN_PATH]); - } + applyStroke.call(this, state); state.currentStrokeStyle = strokeStyle; state.currentLineCap = lineCap; state.currentLineDash = lineDash; diff --git a/src/ol/render/canvas/textreplay.js b/src/ol/render/canvas/textreplay.js index 2588bc52fb..12a258908e 100644 --- a/src/ol/render/canvas/textreplay.js +++ b/src/ol/render/canvas/textreplay.js @@ -290,6 +290,11 @@ ol.render.canvas.TextReplay.prototype.drawText = function(geometry, feature) { } end = this.appendFlatCoordinates(flatCoordinates, 0, end, stride, false, false); this.beginGeometry(geometry, feature); + if (textState.backgroundFill || textState.backgroundStroke) { + this.setFillStrokeStyle(textState.backgroundFill, textState.backgroundStroke); + this.updateFillStyle(this.state, this.applyFill, geometry); + this.updateStrokeStyle(this.state, this.applyStroke); + } this.drawTextImage_(label, begin, end); this.endGeometry(geometry, feature); } @@ -385,12 +390,18 @@ ol.render.canvas.TextReplay.prototype.drawTextImage_ = function(label, begin, en this.instructions.push([ol.render.canvas.Instruction.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, true, label.width + 1, true, label.width, + textState.padding == ol.render.canvas.defaultPadding ? + ol.render.canvas.defaultPadding : textState.padding.map(function(p) { + return p * pixelRatio; + }), + !!textState.backgroundFill, !!textState.backgroundStroke ]); this.hitDetectionInstructions.push([ol.render.canvas.Instruction.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, true, label.width + 1 / pixelRatio, true, label.width, textState.padding, + !!textState.backgroundFill, !!textState.backgroundStroke ]); }; @@ -500,6 +511,9 @@ ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle, declutt textState.placement = textStyle.getPlacement(); textState.textAlign = textStyle.getTextAlign(); textState.textBaseline = textStyle.getTextBaseline() || ol.render.canvas.defaultTextBaseline; + textState.backgroundFill = textStyle.getBackgroundFill(); + textState.backgroundStroke = textStyle.getBackgroundStroke(); + textState.padding = textStyle.getPadding() || ol.render.canvas.defaultPadding; textState.scale = textScale === undefined ? 1 : textScale; var textOffsetX = textStyle.getOffsetX(); diff --git a/src/ol/style/text.js b/src/ol/style/text.js index ddb484f42b..c17d173a79 100644 --- a/src/ol/style/text.js +++ b/src/ol/style/text.js @@ -101,6 +101,24 @@ ol.style.Text = function(opt_options) { * @type {number} */ this.offsetY_ = options.offsetY !== undefined ? options.offsetY : 0; + + /** + * @private + * @type {ol.style.Fill} + */ + this.backgroundFill_ = options.backgroundFill ? options.backgroundFill : null; + + /** + * @private + * @type {ol.style.Stroke} + */ + this.backgroundStroke_ = options.backgroundStroke ? options.backgroundStroke : null; + + /** + * @private + * @type {Array.} + */ + this.padding_ = options.padding === undefined ? null : options.padding; }; @@ -279,6 +297,36 @@ ol.style.Text.prototype.getTextBaseline = function() { }; +/** + * Get the background fill style for the text. + * @return {ol.style.Fill} Fill style. + * @api + */ +ol.style.Text.prototype.getBackgroundFill = function() { + return this.backgroundFill_; +}; + + +/** + * Get the background stroke style for the text. + * @return {ol.style.Stroke} Stroke style. + * @api + */ +ol.style.Text.prototype.getBackgroundStroke = function() { + return this.backgroundStroke_; +}; + + +/** + * Get the padding for the text. + * @return {Array.} Padding. + * @api + */ +ol.style.Text.prototype.getPadding = function() { + return this.padding_; +}; + + /** * Set the `exceedLength` property. * @@ -420,3 +468,36 @@ ol.style.Text.prototype.setTextAlign = function(textAlign) { ol.style.Text.prototype.setTextBaseline = function(textBaseline) { this.textBaseline_ = textBaseline; }; + + +/** + * Set the background fill. + * + * @param {ol.style.Fill} fill Fill style. + * @api + */ +ol.style.Text.prototype.setBackgroundFill = function(fill) { + this.backgroundFill_ = fill; +}; + + +/** + * Set the background stroke. + * + * @param {ol.style.Stroke} stroke Stroke style. + * @api + */ +ol.style.Text.prototype.setBackgroundStroke = function(stroke) { + this.backgroundStroke_ = stroke; +}; + + +/** + * Set the padding (`[top, right, bottom, left]`). + * + * @param {!Array.} padding Padding. + * @api + */ +ol.style.Text.prototype.setPadding = function(padding) { + this.padding_ = padding; +}; From df90f1b78ce1f8cfa2bde15041a62d2ada47f889 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sun, 12 Nov 2017 21:24:45 +0100 Subject: [PATCH 2/3] Reuse more code --- src/ol/render/canvas/replay.js | 191 +++++++++++++++++---------------- 1 file changed, 99 insertions(+), 92 deletions(-) diff --git a/src/ol/render/canvas/replay.js b/src/ol/render/canvas/replay.js index 77175d4890..25609ddd85 100644 --- a/src/ol/render/canvas/replay.js +++ b/src/ol/render/canvas/replay.js @@ -147,6 +147,12 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, pixelRatio, */ this.state = /** @type {ol.CanvasFillStrokeState} */ ({}); + /** + * @private + * @type {number} + */ + this.viewRotation_ = 0; + /** * @private * @type {!ol.Transform} @@ -162,6 +168,34 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, pixelRatio, ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext); +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {ol.Coordinate} p1 1st point of the background box. + * @param {ol.Coordinate} p2 2nd point of the background box. + * @param {ol.Coordinate} p3 3rd point of the background box. + * @param {ol.Coordinate} p4 4th point of the background box. + * @param {Array.<*>} fillInstruction Fill instruction. + * @param {Array.<*>} strokeInstruction Stroke instruction. + */ +ol.render.canvas.Replay.prototype.replayTextBackground_ = function(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.fillOrigin_ = /** @type {Array.} */ (fillInstruction[2]); + this.fill_(context); + } + if (strokeInstruction) { + this.setStrokeStyle_(context, /** @type {Array.<*>} */ (strokeInstruction)); + context.stroke(); + } +}; + + /** * @param {CanvasRenderingContext2D} context Context. * @param {number} x X. @@ -179,13 +213,13 @@ ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext); * @param {boolean} snapToPixel Snap to pixel. * @param {number} width Width. * @param {Array.} padding Padding. - * @param {boolean} fill Background fill. - * @param {boolean} stroke Backgroud stroke. + * @param {Array.<*>} fillInstruction Fill instruction. + * @param {Array.<*>} strokeInstruction Stroke instruction. */ ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, anchorX, anchorY, declutterGroup, height, opacity, originX, originY, - rotation, scale, snapToPixel, width, padding, fill, stroke) { - var fillStroke = fill || stroke; + rotation, scale, snapToPixel, width, padding, fillInstruction, strokeInstruction) { + var fillStroke = fillInstruction || strokeInstruction; var localTransform = this.tmpLocalTransform_; anchorX *= scale; anchorY *= scale; @@ -204,7 +238,14 @@ ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, var boxX = x - padding[3]; var boxY = y - padding[0]; - var p1, p2, p3, p4; + /** @type {ol.Coordinate} */ + var p1; + /** @type {ol.Coordinate} */ + var p2; + /** @type {ol.Coordinate} */ + var p3; + /** @type {ol.Coordinate} */ + var p4; if (fillStroke || rotation !== 0) { p1 = [boxX, boxY]; p2 = [boxX + boxW, boxY]; @@ -220,10 +261,10 @@ ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, centerX, centerY, 1, 1, rotation, -centerX, -centerY); ol.extent.createOrUpdateEmpty(box); - ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.} */ (p1))); - ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.} */ (p2))); - ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.} */ (p3))); - ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.} */ (p4))); + ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p1)); + ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p2)); + ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p3)); + ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p4)); } else { ol.extent.createOrUpdate(boxX, boxY, boxX + boxW, boxY + boxH, box); } @@ -233,47 +274,19 @@ ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, if (!intersects && declutterGroup[4] == 1) { return; } - /** @type {ol.CanvasFillStrokeState} */ - var state; - if (intersects && fillStroke) { - state = /** @type {ol.CanvasFillStrokeState} */ ({}); - if (fill) { - state.fillStyle = context.fillStyle; - } - if (stroke) { - state.strokeStyle = context.strokeStyle; - state.lineWidth = context.lineWidth; - state.lineCap = context.lineCap; - state.lineJoin = context.lineJoin; - state.miterLimit = context.miterLimit; - if (ol.has.CANVAS_LINE_DASH) { - state.lineDashOffset = context.lineDashOffset; - state.lineDash = context.getLineDash(); - } - } - } ol.extent.extend(declutterGroup, box); var declutterArgs = intersects ? [context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] : null; if (declutterArgs && fillStroke) { - declutterArgs.push(state, p1, p2, p3, p4); + declutterArgs.push(fillInstruction, strokeInstruction, p1, p2, p3, p4); } declutterGroup.push(declutterArgs); } else if (intersects) { if (fillStroke) { - 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 (fill) { - context.fill(); - } - if (stroke) { - context.stroke(); - } + this.replayTextBackground_(context, p1, p2, p3, p4, + /** @type {Array.<*>} */ (fillInstruction), + /** @type {Array.<*>} */ (strokeInstruction)); } ol.render.canvas.drawImage(context, transform, opacity, image, originX, originY, w, h, x, y, scale); } @@ -435,13 +448,12 @@ ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) { /** * @private * @param {CanvasRenderingContext2D} context Context. - * @param {number} rotation Rotation. */ -ol.render.canvas.Replay.prototype.fill_ = function(context, rotation) { +ol.render.canvas.Replay.prototype.fill_ = function(context) { if (this.fillOrigin_) { var origin = ol.transform.apply(this.renderedTransform_, this.fillOrigin_.slice()); context.translate(origin[0], origin[1]); - context.rotate(rotation); + context.rotate(this.viewRotation_); } context.fill(); if (this.fillOrigin_) { @@ -450,6 +462,24 @@ ol.render.canvas.Replay.prototype.fill_ = function(context, rotation) { }; +/** + * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {Array.<*>} instruction Instruction. + */ +ol.render.canvas.Replay.prototype.setStrokeStyle_ = function(context, instruction) { + context.strokeStyle = /** @type {ol.ColorLike} */ (instruction[1]); + context.lineWidth = /** @type {number} */ (instruction[2]); + context.lineCap = /** @type {string} */ (instruction[3]); + context.lineJoin = /** @type {string} */ (instruction[4]); + context.miterLimit = /** @type {number} */ (instruction[5]); + if (ol.has.CANVAS_LINE_DASH) { + context.lineDashOffset = /** @type {number} */ (instruction[7]); + context.setLineDash(/** @type {Array.} */ (instruction[6])); + } +}; + + /** * @param {ol.DeclutterGroup} declutterGroup Declutter group. */ @@ -471,30 +501,9 @@ ol.render.canvas.Replay.prototype.renderDeclutter_ = function(declutterGroup) { var declutterData = /** @type {Array} */ (declutterGroup[j]); if (declutterData) { if (declutterData.length > 11) { - var context = /** @type {CanvasRenderingContext2D} */ (declutterData[0]); - context.beginPath(); - context.moveTo.apply(context, declutterData[12]); - context.lineTo.apply(context, declutterData[13]); - context.lineTo.apply(context, declutterData[14]); - context.lineTo.apply(context, declutterData[15]); - context.lineTo.apply(context, declutterData[12]); - var state = /** @type {ol.CanvasFillStrokeState} */ (declutterData[11]); - if (state.fillStyle) { - context.fillStyle = state.fillStyle; - context.fill(); - } - if (state.strokeStyle) { - context.strokeStyle = state.strokeStyle; - context.lineWidth = /** @type {number} */ (state.lineWidth); - context.lineCap = /** @type {string} */ (state.lineCap); - context.lineJoin = /** @type {string} */ (state.lineJoin); - context.miterLimit = /** @type {number} */ (state.miterLimit); - if (ol.has.CANVAS_LINE_DASH) { - context.lineDashOffset = /** @type {number} */ (state.lineDashOffset); - context.setLineDash(state.lineDash); - } - context.stroke(); - } + this.replayTextBackground_(declutterData[0], + declutterData[13], declutterData[14], declutterData[15], declutterData[16], + declutterData[11], declutterData[12]); } drawImage.apply(undefined, declutterData); } @@ -511,7 +520,6 @@ ol.render.canvas.Replay.prototype.renderDeclutter_ = function(declutterGroup) { * @private * @param {CanvasRenderingContext2D} context Context. * @param {ol.Transform} transform Transform. - * @param {number} viewRotation View rotation. * @param {Object.} skippedFeaturesHash Ids of features * to skip. * @param {Array.<*>} instructions Instructions array. @@ -523,7 +531,7 @@ ol.render.canvas.Replay.prototype.renderDeclutter_ = function(declutterGroup) { * @template T */ ol.render.canvas.Replay.prototype.replay_ = function( - context, transform, viewRotation, skippedFeaturesHash, + context, transform, skippedFeaturesHash, instructions, featureCallback, opt_hitExtent) { /** @type {Array.} */ var pixelCoordinates; @@ -546,7 +554,10 @@ ol.render.canvas.Replay.prototype.replay_ = function( var anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image; var pendingFill = 0; var pendingStroke = 0; + var lastFillInstruction = null; + var lastStrokeInstruction = null; var coordinateCache = this.coordinateCache_; + var viewRotation = this.viewRotation_; var state = /** @type {olx.render.State} */ ({ context: context, @@ -579,7 +590,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( break; case ol.render.canvas.Instruction.BEGIN_PATH: if (pendingFill > batchSize) { - this.fill_(context, viewRotation); + this.fill_(context); pendingFill = 0; } if (pendingStroke > batchSize) { @@ -667,7 +678,9 @@ ol.render.canvas.Replay.prototype.replay_ = function( this.replayImage_(context, pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY, declutterGroup, height, opacity, originX, originY, rotation, scale, - snapToPixel, width, padding, backgroundFill, backgroundStroke); + snapToPixel, width, padding, + backgroundFill ? /** @type {Array.<*>} */ (lastFillInstruction) : null, + backgroundStroke ? /** @type {Array.<*>} */ (lastStrokeInstruction) : null); } this.renderDeclutter_(declutterGroup); ++i; @@ -707,7 +720,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( /** @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, - ol.render.canvas.defaultPadding, false, false); + ol.render.canvas.defaultPadding, null, null); } } if (fill) { @@ -721,7 +734,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( /** @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, - ol.render.canvas.defaultPadding, false, false); + ol.render.canvas.defaultPadding, null, null); } } } @@ -743,7 +756,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( if (batchSize) { pendingFill++; } else { - this.fill_(context, viewRotation); + this.fill_(context); } ++i; break; @@ -773,10 +786,11 @@ ol.render.canvas.Replay.prototype.replay_ = function( ++i; break; case ol.render.canvas.Instruction.SET_FILL_STYLE: + lastFillInstruction = instruction; this.fillOrigin_ = instruction[2]; if (pendingFill) { - this.fill_(context, viewRotation); + this.fill_(context); pendingFill = 0; if (pendingStroke) { context.stroke(); @@ -788,19 +802,12 @@ ol.render.canvas.Replay.prototype.replay_ = function( ++i; break; case ol.render.canvas.Instruction.SET_STROKE_STYLE: + lastStrokeInstruction = instruction; if (pendingStroke) { context.stroke(); pendingStroke = 0; } - context.strokeStyle = /** @type {ol.ColorLike} */ (instruction[1]); - context.lineWidth = /** @type {number} */ (instruction[2]); - context.lineCap = /** @type {string} */ (instruction[3]); - context.lineJoin = /** @type {string} */ (instruction[4]); - context.miterLimit = /** @type {number} */ (instruction[5]); - if (ol.has.CANVAS_LINE_DASH) { - context.lineDashOffset = /** @type {number} */ (instruction[7]); - context.setLineDash(/** @type {Array.} */ (instruction[6])); - } + this.setStrokeStyle_(context, /** @type {Array.<*>} */ (instruction)); ++i; break; case ol.render.canvas.Instruction.STROKE: @@ -817,7 +824,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( } } if (pendingFill) { - this.fill_(context, viewRotation); + this.fill_(context); } if (pendingStroke) { context.stroke(); @@ -835,9 +842,9 @@ ol.render.canvas.Replay.prototype.replay_ = function( */ ol.render.canvas.Replay.prototype.replay = function( context, transform, viewRotation, skippedFeaturesHash) { - var instructions = this.instructions; - this.replay_(context, transform, viewRotation, - skippedFeaturesHash, instructions, undefined, undefined); + this.viewRotation_ = viewRotation; + this.replay_(context, transform, + skippedFeaturesHash, this.instructions, undefined, undefined); }; @@ -857,9 +864,9 @@ ol.render.canvas.Replay.prototype.replay = function( ol.render.canvas.Replay.prototype.replayHitDetection = function( context, transform, viewRotation, skippedFeaturesHash, opt_featureCallback, opt_hitExtent) { - var instructions = this.hitDetectionInstructions; - return this.replay_(context, transform, viewRotation, - skippedFeaturesHash, instructions, opt_featureCallback, opt_hitExtent); + this.viewRotation_ = viewRotation; + return this.replay_(context, transform, skippedFeaturesHash, + this.hitDetectionInstructions, opt_featureCallback, opt_hitExtent); }; From 53b85a91923f22103e1c0177ad4af02e3d6db86a Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Mon, 13 Nov 2017 13:58:11 +0100 Subject: [PATCH 3/3] Add rendering tests --- src/ol/render/canvas/replay.js | 2 +- .../ol/style/expected/text-background.png | Bin 0 -> 3545 bytes test/rendering/ol/style/text.test.js | 26 ++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 test/rendering/ol/style/expected/text-background.png diff --git a/src/ol/render/canvas/replay.js b/src/ol/render/canvas/replay.js index 25609ddd85..0a78d62b73 100644 --- a/src/ol/render/canvas/replay.js +++ b/src/ol/render/canvas/replay.js @@ -1005,7 +1005,7 @@ ol.render.canvas.Replay.prototype.updateStrokeStyle = function(state, applyStrok var miterLimit = state.miterLimit; if (state.currentStrokeStyle != strokeStyle || state.currentLineCap != lineCap || - !ol.array.equals(state.currentLineDash, lineDash) || + (lineDash != state.currentLineDash && !ol.array.equals(state.currentLineDash, lineDash)) || state.currentLineDashOffset != lineDashOffset || state.currentLineJoin != lineJoin || state.currentLineWidth != lineWidth || diff --git a/test/rendering/ol/style/expected/text-background.png b/test/rendering/ol/style/expected/text-background.png new file mode 100644 index 0000000000000000000000000000000000000000..cd78d7c1d6cbd056bdf8fea33fc878e6134c97ed GIT binary patch literal 3545 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