Implement text rendering along paths

This commit also changes the TextReplay.drawText() signature, and moves
geometry calculation into drawText(). This improves performance where no
text needs to be rendered (TextStyle.getText() == ''), which is used often
in applications.
This commit is contained in:
Andreas Hocevar
2017-09-07 23:32:31 +02:00
parent 37dcd79a86
commit efc86d59b0
12 changed files with 640 additions and 189 deletions

View File

@@ -6,6 +6,8 @@ goog.require('ol.extent');
goog.require('ol.extent.Relationship');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.flat.inflate');
goog.require('ol.geom.flat.length');
goog.require('ol.geom.flat.textpath');
goog.require('ol.geom.flat.transform');
goog.require('ol.has');
goog.require('ol.obj');
@@ -130,10 +132,69 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, pixelRatio,
* @type {!ol.Transform}
*/
this.resetTransform_ = ol.transform.create();
/**
* @private
* @type {Array.<*>}
*/
this.chars_ = [];
};
ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext);
/**
* @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 {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.
*/
ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, anchorX, anchorY,
height, opacity, originX, originY, rotation, scale, snapToPixel, width) {
var localTransform = this.tmpLocalTransform_;
anchorX *= scale;
anchorY *= scale;
x -= anchorX;
y -= anchorY;
if (snapToPixel) {
x = Math.round(x);
y = Math.round(y);
}
if (rotation !== 0) {
var centerX = x + anchorX;
var centerY = y + anchorY;
ol.transform.compose(localTransform,
centerX, centerY, 1, 1, rotation, -centerX, -centerY);
context.setTransform.apply(context, localTransform);
}
var alpha = context.globalAlpha;
if (opacity != 1) {
context.globalAlpha = alpha * opacity;
}
var w = (width + originX > image.width) ? image.width - originX : width;
var h = (height + originY > image.height) ? image.height - originY : height;
context.drawImage(image, originX, originY, w, h, x, y, w * scale, h * scale);
if (opacity != 1) {
context.globalAlpha = alpha;
}
if (rotation !== 0) {
context.setTransform.apply(context, this.resetTransform_);
}
};
/**
* @protected
* @param {Array.<number>} dashArray Dash array.
@@ -340,9 +401,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 localTransform = this.tmpLocalTransform_;
var resetTransform = this.resetTransform_;
var prevX, prevY, roundX, roundY;
var anchorX, anchorY, prevX, prevY, roundX, roundY;
var pendingFill = 0;
var pendingStroke = 0;
var coordinateCache = this.coordinateCache_;
@@ -435,53 +494,63 @@ ol.render.canvas.Replay.prototype.replay_ = function(
dd = /** @type {number} */ (instruction[2]);
var image = /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */
(instruction[3]);
var scale = /** @type {number} */ (instruction[12]);
// Remaining arguments in DRAW_IMAGE are in alphabetical order
var anchorX = /** @type {number} */ (instruction[4]) * scale;
var anchorY = /** @type {number} */ (instruction[5]) * scale;
anchorX = /** @type {number} */ (instruction[4]);
anchorY = /** @type {number} */ (instruction[5]);
var height = /** @type {number} */ (instruction[6]);
var opacity = /** @type {number} */ (instruction[7]);
var originX = /** @type {number} */ (instruction[8]);
var originY = /** @type {number} */ (instruction[9]);
var rotateWithView = /** @type {boolean} */ (instruction[10]);
var rotation = /** @type {number} */ (instruction[11]);
var scale = /** @type {number} */ (instruction[12]);
var snapToPixel = /** @type {boolean} */ (instruction[13]);
var width = /** @type {number} */ (instruction[14]);
if (rotateWithView) {
rotation += viewRotation;
}
for (; d < dd; d += 2) {
x = pixelCoordinates[d] - anchorX;
y = pixelCoordinates[d + 1] - anchorY;
if (snapToPixel) {
x = Math.round(x);
y = Math.round(y);
}
if (rotation !== 0) {
var centerX = x + anchorX;
var centerY = y + anchorY;
ol.transform.compose(localTransform,
centerX, centerY, 1, 1, rotation, -centerX, -centerY);
context.setTransform.apply(context, localTransform);
}
var alpha = context.globalAlpha;
if (opacity != 1) {
context.globalAlpha = alpha * opacity;
}
this.replayImage_(context, pixelCoordinates[d], pixelCoordinates[d + 1],
image, anchorX, anchorY, height, opacity, originX, originY,
rotation, scale, snapToPixel, width);
}
++i;
break;
case ol.render.canvas.Instruction.DRAW_CHARS:
var begin = /** @type {number} */ (instruction[1]);
var end = /** @type {number} */ (instruction[2]);
var images = /** @type {Array.<HTMLCanvasElement>} */ (instruction[3]);
// Remaining arguments in DRAW_CHARS are in alphabetical order
var baseline = /** @type {number} */ (instruction[4]);
var exceedLength = /** @type {number} */ (instruction[5]);
var maxAngle = /** @type {number} */ (instruction[6]);
var measure = /** @type {function(string):number} */ (instruction[7]);
var offsetY = /** @type {number} */ (instruction[8]);
var strokeWidth = /** @type {number} */ (instruction[9]);
var text = /** @type {string} */ (instruction[10]);
var align = /** @type {number} */ (instruction[11]);
var textScale = /** @type {number} */ (instruction[12]);
var w = (width + originX > image.width) ? image.width - originX : width;
var h = (height + originY > image.height) ? image.height - originY : height;
context.drawImage(image, originX, originY, w, h,
x, y, w * scale, h * scale);
if (opacity != 1) {
context.globalAlpha = alpha;
}
if (rotation !== 0) {
context.setTransform.apply(context, resetTransform);
var pathLength = ol.geom.flat.length.lineString(pixelCoordinates, begin, end, 2);
var textLength = measure(text);
if (exceedLength || textLength <= pathLength) {
var startM = (pathLength - textLength) * align;
var chars = ol.geom.flat.textpath.lineString(
pixelCoordinates, begin, end, 2, text, measure, startM, maxAngle, this.chars_);
var numChars = text.length;
if (chars) {
for (var c = 0, cc = images.length; c < cc; ++c) {
var char = chars[c % numChars]; // x, y, rotation
var label = images[c];
anchorX = label.width / 2;
anchorY = baseline * label.height + 2 * (0.5 - baseline) * strokeWidth - offsetY;
this.replayImage_(context, char[0], char[1], label, anchorX, anchorY,
label.height, 1, 0, 0, char[2], textScale, false, label.width);
}
}
}
++i;
break;
case ol.render.canvas.Instruction.END_GEOMETRY: