Add text background rendering and text padding

This commit is contained in:
Andreas Hocevar
2017-11-10 16:47:15 +01:00
parent bbec759c5e
commit 1afc686af9
7 changed files with 312 additions and 48 deletions

View File

@@ -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.<number>|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.<number>|undefined}
* @api
*/
olx.style.TextOptions.prototype.padding;
/**
* @typedef {{geometry: (undefined|string|ol.geom.Geometry|ol.StyleGeometryFunction),
* fill: (ol.style.Fill|undefined),

View File

@@ -77,6 +77,13 @@ ol.render.canvas.defaultTextAlign = 'center';
ol.render.canvas.defaultTextBaseline = 'middle';
/**
* @const
* @type {Array.<number>}
*/
ol.render.canvas.defaultPadding = [0, 0, 0, 0];
/**
* @const
* @type {number}

View File

@@ -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]);
};

View File

@@ -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);
}
};

View File

@@ -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.<number>} 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.<number>} */ (p1)));
ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.<number>} */ (p2)));
ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.<number>} */ (p3)));
ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, /** @type {Array.<number>} */ (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.<number>} */ (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;

View File

@@ -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();

View File

@@ -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.<number>}
*/
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.<number>} 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.<number>} padding Padding.
* @api
*/
ol.style.Text.prototype.setPadding = function(padding) {
this.padding_ = padding;
};