From efc86d59b0b2851e99f0ff88f8395a30166e39a8 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Thu, 7 Sep 2017 23:32:31 +0200 Subject: [PATCH] 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. --- package/package.json | 3 +- src/ol/geom/flat/textpath.js | 75 ++++ src/ol/render/canvas/imagereplay.js | 4 +- src/ol/render/canvas/instruction.js | 15 +- src/ol/render/canvas/replay.js | 137 +++++-- src/ol/render/canvas/textreplay.js | 369 ++++++++++++++----- src/ol/render/vectorcontext.js | 6 +- src/ol/render/webgl/immediate.js | 32 +- src/ol/render/webgl/textreplay.js | 34 +- src/ol/renderer/vector.js | 22 +- test/spec/ol/geom/flat/textpath.test.js | 121 ++++++ test/spec/ol/render/webgl/textreplay.test.js | 11 +- 12 files changed, 640 insertions(+), 189 deletions(-) create mode 100644 src/ol/geom/flat/textpath.js create mode 100644 test/spec/ol/geom/flat/textpath.test.js diff --git a/package/package.json b/package/package.json index ed32298758..ec1d47429b 100644 --- a/package/package.json +++ b/package/package.json @@ -8,8 +8,7 @@ "dependencies": { "pbf": "3.0.5", "pixelworks": "1.1.0", - "rbush": "2.0.1", - "@mapbox/vector-tile": "1.3.0" + "rbush": "2.0.1" }, "browserify": { "transform": [ diff --git a/src/ol/geom/flat/textpath.js b/src/ol/geom/flat/textpath.js new file mode 100644 index 0000000000..bd84defe32 --- /dev/null +++ b/src/ol/geom/flat/textpath.js @@ -0,0 +1,75 @@ +goog.provide('ol.geom.flat.textpath'); + +goog.require('ol.math'); + + +/** + * @param {Array.} flatCoordinates Path to put text on. + * @param {number} offset Start offset of the `flatCoordinates`. + * @param {number} end End offset of the `flatCoordinates`. + * @param {number} stride Stride. + * @param {string} text Text to place on the path. + * @param {function(string):number} measure Measure function returning the + * width of the character passed as 1st argument. + * @param {number} startM m along the path where the text starts. + * @param {number} maxAngle Max angle between adjacent chars in radians. + * @param {Array.>=} opt_result Array that will be populated with the + * result. Each entry consists of an array of x, y and z of the char to draw. + * If provided, this array will not be truncated to the number of characters of + * the `text`. + * @return {Array.>} The result array of null if `maxAngle` was + * exceeded. + */ +ol.geom.flat.textpath.lineString = function( + flatCoordinates, offset, end, stride, text, measure, startM, maxAngle, opt_result) { + var result = opt_result ? opt_result : []; + + // Keep text upright + var reverse = flatCoordinates[offset] > flatCoordinates[end - stride]; + + var numChars = text.length; + + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + offset += stride; + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + var segmentM = 0; + var segmentLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); + + var index, previousAngle; + for (var i = 0; i < numChars; ++i) { + index = reverse ? numChars - i - 1 : i; + var char = text[index]; + var charLength = measure(char); + var charM = startM + charLength / 2; + while (offset < end - stride && segmentM + segmentLength < charM) { + x1 = x2; + y1 = y2; + offset += stride; + x2 = flatCoordinates[offset]; + y2 = flatCoordinates[offset + 1]; + segmentM += segmentLength; + segmentLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); + } + var segmentPos = charM - segmentM; + var angle = Math.atan2(y2 - y1, x2 - x1); + if (reverse) { + angle += angle > 0 ? -Math.PI : Math.PI; + } + if (previousAngle !== undefined) { + var delta = angle - previousAngle; + delta += (delta > Math.PI) ? -2 * Math.PI : (delta < -Math.PI) ? 2 * Math.PI : 0; + if (Math.abs(delta) > maxAngle) { + return null; + } + } + previousAngle = angle; + var interpolate = segmentPos / segmentLength; + var x = ol.math.lerp(x1, x2, interpolate); + var y = ol.math.lerp(y1, y2, interpolate); + result[index] = [x, y, angle]; + startM += charLength; + } + return result; +}; diff --git a/src/ol/render/canvas/imagereplay.js b/src/ol/render/canvas/imagereplay.js index 8dc0ca37f0..f31f3ed592 100644 --- a/src/ol/render/canvas/imagereplay.js +++ b/src/ol/render/canvas/imagereplay.js @@ -162,7 +162,7 @@ ol.render.canvas.ImageReplay.prototype.drawMultiPoint = function(multiPointGeome this.instructions.push([ ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_, // Remaining arguments to DRAW_IMAGE are in alphabetical order - this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.anchorX_, this.anchorY_, this.height_, undefined, this.opacity_, this.originX_, this.originY_, this.rotateWithView_, this.rotation_, this.scale_, this.snapToPixel_, this.width_ ]); @@ -170,7 +170,7 @@ ol.render.canvas.ImageReplay.prototype.drawMultiPoint = function(multiPointGeome ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.hitDetectionImage_, // Remaining arguments to DRAW_IMAGE are in alphabetical order - this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.anchorX_, this.anchorY_, this.height_, undefined, this.opacity_, this.originX_, this.originY_, this.rotateWithView_, this.rotation_, this.scale_, this.snapToPixel_, this.width_ ]); diff --git a/src/ol/render/canvas/instruction.js b/src/ol/render/canvas/instruction.js index ee786500ce..205e6381f6 100644 --- a/src/ol/render/canvas/instruction.js +++ b/src/ol/render/canvas/instruction.js @@ -9,11 +9,12 @@ ol.render.canvas.Instruction = { CIRCLE: 2, CLOSE_PATH: 3, CUSTOM: 4, - DRAW_IMAGE: 5, - END_GEOMETRY: 6, - FILL: 7, - MOVE_TO_LINE_TO: 8, - SET_FILL_STYLE: 9, - SET_STROKE_STYLE: 10, - STROKE: 11 + DRAW_CHARS: 5, + DRAW_IMAGE: 6, + END_GEOMETRY: 7, + FILL: 8, + MOVE_TO_LINE_TO: 9, + SET_FILL_STYLE: 10, + SET_STROKE_STYLE: 11, + STROKE: 12 }; diff --git a/src/ol/render/canvas/replay.js b/src/ol/render/canvas/replay.js index 1af6291bfb..1341e9a855 100644 --- a/src/ol/render/canvas/replay.js +++ b/src/ol/render/canvas/replay.js @@ -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.} 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.} */ (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: diff --git a/src/ol/render/canvas/textreplay.js b/src/ol/render/canvas/textreplay.js index 69682a0aa2..8c0ff774dd 100644 --- a/src/ol/render/canvas/textreplay.js +++ b/src/ol/render/canvas/textreplay.js @@ -2,12 +2,15 @@ goog.provide('ol.render.canvas.TextReplay'); goog.require('ol'); goog.require('ol.colorlike'); +goog.require('ol.dom'); +goog.require('ol.geom.GeometryType'); goog.require('ol.has'); goog.require('ol.render.canvas'); goog.require('ol.render.canvas.Instruction'); goog.require('ol.render.canvas.Replay'); goog.require('ol.render.replay'); goog.require('ol.structs.LRUCache'); +goog.require('ol.style.TextPlacement'); /** @@ -24,6 +27,12 @@ ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, pixelRa ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, pixelRatio, overlaps); + /** + * @private + * @type {Array.} + */ + this.labels_ = null; + /** * @private * @type {string} @@ -78,6 +87,24 @@ ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, pixelRa */ this.textState_ = null; + /** + * @private + * @type {string} + */ + this.textKey_ = ''; + + /** + * @private + * @type {string} + */ + this.fillKey_ = ''; + + /** + * @private + * @type {string} + */ + this.strokeKey_ = ''; + while (ol.render.canvas.TextReplay.labelCache_.canExpireCache()) { ol.render.canvas.TextReplay.labelCache_.pop(); } @@ -95,33 +122,65 @@ ol.render.canvas.TextReplay.labelCache_ = new ol.structs.LRUCache(); /** * @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 {ol.Size} Measuremnt. + * @return {ol.Size} Measurement. */ -ol.render.canvas.TextReplay.measureText = (function() { +ol.render.canvas.TextReplay.measureTextHeight = (function() { var textContainer; return function(font, lines, widths) { if (!textContainer) { textContainer = document.createElement('span'); + textContainer.textContent = 'M'; textContainer.style.visibility = 'hidden'; textContainer.style.whiteSpace = 'nowrap'; } textContainer.style.font = font; document.body.appendChild(textContainer); + var height = textContainer.offsetHeight; + document.body.removeChild(textContainer); + return height; + }; +})(); + + +/** + * @this {Object} + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {string} text Text. + * @return {number} Width. + */ +ol.render.canvas.TextReplay.getTextWidth = function(context, pixelRatio, text) { + var width = this[text]; + if (!width) { + this[text] = width = context.measureText(text).width; + } + return width * pixelRatio; +}; + + +/** + * @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. + */ +ol.render.canvas.TextReplay.measureTextWidths = (function() { + var context; + return function(font, lines, widths) { + if (!context) { + context = ol.dom.createCanvasContext2D(1, 1); + } + context.font = font; var numLines = lines.length; var width = 0; var currentWidth, i; for (i = 0; i < numLines; ++i) { - textContainer.textContent = lines[i]; - currentWidth = textContainer.offsetWidth; + currentWidth = context.measureText(lines[i]).width; width = Math.max(width, currentWidth); widths.push(currentWidth); } - var measurement = [width, textContainer.offsetHeight * numLines]; - document.body.removeChild(textContainer); - return measurement; + return width; }; })(); @@ -129,51 +188,113 @@ ol.render.canvas.TextReplay.measureText = (function() { /** * @inheritDoc */ -ol.render.canvas.TextReplay.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) { +ol.render.canvas.TextReplay.prototype.drawText = function(geometry, feature) { var fillState = this.textFillState_; var strokeState = this.textStrokeState_; - var textState = this.textState_; - if (this.text_ === '' || !textState || (!fillState && !strokeState)) { + if (this.text_ === '' || !this.textState_ || (!fillState && !strokeState)) { return; } + this.beginGeometry(geometry, feature); + var begin = this.coordinates.length; - var myBegin = this.coordinates.length; - var myEnd = - this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false); - var fill = !!fillState; - var stroke = !!strokeState; - var pixelRatio = this.pixelRatio; - var textAlign = textState.textAlign; - var align = ol.render.replay.TEXT_ALIGN[textAlign]; - var textBaseline = textState.textBaseline; - var baseline = ol.render.replay.TEXT_ALIGN[textBaseline]; - var strokeWidth = stroke && strokeState.lineWidth ? strokeState.lineWidth : 0; + var geometryType = geometry.getType(); + var flatCoordinates = null; + var end = 2; + var stride = 2; + if (this.textState_.placement === ol.style.TextPlacement.LINE) { + var ends; + flatCoordinates = geometry.getFlatCoordinates(); + stride = geometry.getStride(); + if (geometryType == ol.geom.GeometryType.LINE_STRING) { + ends = [flatCoordinates.length]; + } else if (geometryType == ol.geom.GeometryType.MULTI_LINE_STRING) { + ends = geometry.getEnds(); + } else if (geometryType == ol.geom.GeometryType.POLYGON) { + ends = geometry.getEnds().slice(0, 1); + } else if (geometryType == ol.geom.GeometryType.MULTI_POLYGON) { + var endss = geometry.getEndss(); + ends = []; + for (var i = 0, ii = endss.length; i < ii; ++i) { + ends.push(endss[i][0]); + } + } + var flatOffset = 0; + var flatEnd; + for (var o = 0, oo = ends.length; o < oo; ++o) { + flatEnd = ends[o]; + end = this.appendFlatCoordinates(flatCoordinates, flatOffset, flatEnd, stride, false, false); + flatOffset = flatEnd; + this.drawChars_(begin, end); + begin = end; + } + + } else { + switch (geometryType) { + case ol.geom.GeometryType.POINT: + case ol.geom.GeometryType.MULTI_POINT: + flatCoordinates = geometry.getFlatCoordinates(); + end = flatCoordinates.length; + break; + case ol.geom.GeometryType.LINE_STRING: + flatCoordinates = /** @type {ol.geom.LineString} */ (geometry).getFlatMidpoint(); + break; + case ol.geom.GeometryType.CIRCLE: + flatCoordinates = /** @type {ol.geom.Circle} */ (geometry).getCenter(); + break; + case ol.geom.GeometryType.MULTI_LINE_STRING: + flatCoordinates = /** @type {ol.geom.MultiLineString} */ (geometry).getFlatMidpoints(); + end = flatCoordinates.length; + break; + case ol.geom.GeometryType.POLYGON: + flatCoordinates = /** @type {ol.geom.Polygon} */ (geometry).getFlatInteriorPoint(); + break; + case ol.geom.GeometryType.MULTI_POLYGON: + flatCoordinates = /** @type {ol.geom.MultiPolygon} */ (geometry).getFlatInteriorPoints(); + end = flatCoordinates.length; + break; + default: + } + end = this.appendFlatCoordinates(flatCoordinates, 0, end, stride, false, false); + this.drawTextImage_(begin, end); + } + + this.endGeometry(geometry, feature); +}; + + +/** + * @private + * @param {string} text Text. + * @param {boolean} fill Fill. + * @param {boolean} stroke Stroke. + * @return {HTMLCanvasElement} Image. + */ +ol.render.canvas.TextReplay.prototype.getImage_ = function(text, fill, stroke) { var label; - var text = this.text_; - var key = - (stroke ? - (typeof strokeState.strokeStyle == 'string' ? strokeState.strokeStyle : ol.getUid(strokeState.strokeStyle)) + - strokeState.lineCap + strokeState.lineDashOffset + '|' + strokeWidth + - strokeState.lineJoin + strokeState.miterLimit + - '[' + strokeState.lineDash.join() + ']' : '') + - textState.font + textAlign + text + - (fill ? - (typeof fillState.fillStyle == 'string' ? fillState.fillStyle : ('|' + ol.getUid(fillState.fillStyle))) : ''); + var key = (stroke ? this.strokeKey_ : '') + this.textKey_ + text + (fill ? this.fillKey_ : ''); var lines = text.split('\n'); var numLines = lines.length; - if (!ol.render.canvas.TextReplay.labelCache_.containsKey(key)) { - label = /** @type {HTMLCanvasElement} */ (document.createElement('canvas')); - ol.render.canvas.TextReplay.labelCache_.set(key, label); - var context = label.getContext('2d'); + var strokeState = this.textStrokeState_; + var fillState = this.textFillState_; + var textState = this.textState_; + var pixelRatio = this.pixelRatio; + var align = ol.render.replay.TEXT_ALIGN[textState.textAlign]; + var strokeWidth = stroke && strokeState.lineWidth ? strokeState.lineWidth : 0; + var widths = []; - var metrics = ol.render.canvas.TextReplay.measureText(textState.font, lines, widths); - var lineHeight = metrics[1] / numLines; - label.width = Math.ceil(metrics[0] + 2 * strokeWidth) * pixelRatio; - label.height = Math.ceil(metrics[1] + 2 * strokeWidth) * pixelRatio; + var width = ol.render.canvas.TextReplay.measureTextWidths(textState.font, lines, widths); + var lineHeight = ol.render.canvas.TextReplay.measureTextHeight(textState.font); + var height = lineHeight * numLines; + var renderWidth = (width + 2 * strokeWidth); + var context = ol.dom.createCanvasContext2D( + Math.ceil(renderWidth * pixelRatio), + Math.ceil((height + 2 * strokeWidth) * pixelRatio)); + label = context.canvas; + ol.render.canvas.TextReplay.labelCache_.set(key, label); context.scale(pixelRatio, pixelRatio); context.font = textState.font; if (stroke) { @@ -191,39 +312,101 @@ ol.render.canvas.TextReplay.prototype.drawText = function(flatCoordinates, offse context.fillStyle = fillState.fillStyle; } context.textBaseline = 'top'; - context.textAlign = 'left'; - var x = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth; + context.textAlign = 'center'; + var leftRight = (0.5 - align); + var x = align * label.width / pixelRatio + leftRight * 2 * strokeWidth; var i; if (stroke) { for (i = 0; i < numLines; ++i) { - context.strokeText(lines[i], x - align * widths[i], strokeWidth + i * lineHeight); + context.strokeText(lines[i], x + leftRight * widths[i], strokeWidth + i * lineHeight); } } if (fill) { for (i = 0; i < numLines; ++i) { - context.fillText(lines[i], x - align * widths[i], strokeWidth + i * lineHeight); + context.fillText(lines[i], x + leftRight * widths[i], strokeWidth + i * lineHeight); } } } - label = ol.render.canvas.TextReplay.labelCache_.get(key); + return ol.render.canvas.TextReplay.labelCache_.get(key); +}; + + +/** + * @private + * @param {number} begin Begin. + * @param {number} end End. + */ +ol.render.canvas.TextReplay.prototype.drawTextImage_ = function(begin, end) { + var textState = this.textState_; + var strokeState = this.textStrokeState_; + var pixelRatio = this.pixelRatio; + var align = ol.render.replay.TEXT_ALIGN[textState.textAlign]; + var baseline = ol.render.replay.TEXT_ALIGN[textState.textBaseline]; + var strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0; + + var label = this.getImage_(this.text_, !!this.textFillState_, !!this.textStrokeState_); var anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth; var anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth; - - this.instructions.push([ - ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, label, - (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, + this.instructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end, + label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_, this.textScale_, true, label.width ]); - this.hitDetectionInstructions.push([ - ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, label, - (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, + this.hitDetectionInstructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end, + label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_, this.textScale_ / pixelRatio, true, label.width ]); +}; - this.endGeometry(geometry, feature); + +/** + * @private + * @param {number} begin Begin. + * @param {number} end End. + */ +ol.render.canvas.TextReplay.prototype.drawChars_ = function(begin, end) { + var pixelRatio = this.pixelRatio; + var strokeState = this.textStrokeState_; + var fill = !!this.textFillState_; + var stroke = !!strokeState; + var textState = this.textState_; + var baseline = ol.render.replay.TEXT_ALIGN[textState.textBaseline]; + var strokeWidth = stroke && strokeState.lineWidth ? strokeState.lineWidth * pixelRatio : 0; + + var labels = []; + var text = this.text_; + var numChars = this.text_.length; + var i; + + if (stroke) { + for (i = 0; i < numChars; ++i) { + labels.push(this.getImage_(text.charAt(i), false, stroke)); + } + } + if (fill) { + for (i = 0; i < numChars; ++i) { + labels.push(this.getImage_(text.charAt(i), fill, false)); + } + } + + var context = labels[0].getContext('2d'); + var offsetY = this.textOffsetY_ * pixelRatio; + var align = ol.render.replay.TEXT_ALIGN[textState.textAlign || ol.render.canvas.defaultTextAlign]; + var widths = {}; + this.instructions.push([ol.render.canvas.Instruction.DRAW_CHARS, + begin, end, labels, baseline, + textState.exceedLength, textState.maxAngle, + ol.render.canvas.TextReplay.getTextWidth.bind(widths, context, pixelRatio), + offsetY, strokeWidth, this.text_, align, this.textScale_ + ]); + this.hitDetectionInstructions.push([ol.render.canvas.Instruction.DRAW_CHARS, + begin, end, labels, baseline, + textState.exceedLength, textState.maxAngle, + ol.render.canvas.TextReplay.getTextWidth.bind(widths, context, 1), + offsetY, strokeWidth, this.text_, align, this.textScale_ / pixelRatio + ]); }; @@ -231,28 +414,26 @@ ol.render.canvas.TextReplay.prototype.drawText = function(flatCoordinates, offse * @inheritDoc */ ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) { + var textState, fillState, strokeState; if (!textStyle) { this.text_ = ''; } else { var textFillStyle = textStyle.getFill(); if (!textFillStyle) { - this.textFillState_ = null; + fillState = this.textFillState_ = null; } else { var textFillStyleColor = textFillStyle.getColor(); var fillStyle = ol.colorlike.asColorLike(textFillStyleColor ? textFillStyleColor : ol.render.canvas.defaultFillStyle); - if (!this.textFillState_) { - this.textFillState_ = { - fillStyle: fillStyle - }; - } else { - var textFillState = this.textFillState_; - textFillState.fillStyle = fillStyle; + fillState = this.textFillState_; + if (!fillState) { + fillState = this.textFillState_ = /** @type {ol.CanvasFillState} */ ({}); } + fillState.fillStyle = fillStyle; } var textStrokeStyle = textStyle.getStroke(); if (!textStrokeStyle) { - this.textStrokeState_ = null; + strokeState = this.textStrokeState_ = null; } else { var textStrokeStyleColor = textStrokeStyle.getColor(); var textStrokeStyleLineCap = textStrokeStyle.getLineCap(); @@ -275,26 +456,17 @@ ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) { textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; var strokeStyle = ol.colorlike.asColorLike(textStrokeStyleColor ? textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle); - if (!this.textStrokeState_) { - this.textStrokeState_ = { - lineCap: lineCap, - lineDash: lineDash, - lineDashOffset: lineDashOffset, - lineJoin: lineJoin, - lineWidth: lineWidth, - miterLimit: miterLimit, - strokeStyle: strokeStyle - }; - } else { - var textStrokeState = this.textStrokeState_; - textStrokeState.lineCap = lineCap; - textStrokeState.lineDash = lineDash; - textStrokeState.lineDashOffset = lineDashOffset; - textStrokeState.lineJoin = lineJoin; - textStrokeState.lineWidth = lineWidth; - textStrokeState.miterLimit = miterLimit; - textStrokeState.strokeStyle = strokeStyle; + strokeState = this.textStrokeState_; + if (!strokeState) { + strokeState = this.textStrokeState_ = /** @type {ol.CanvasStrokeState} */ ({}); } + strokeState.lineCap = lineCap; + strokeState.lineDash = lineDash; + strokeState.lineDashOffset = lineDashOffset; + strokeState.lineJoin = lineJoin; + strokeState.lineWidth = lineWidth; + strokeState.miterLimit = miterLimit; + strokeState.strokeStyle = strokeStyle; } var textFont = textStyle.getFont(); var textOffsetX = textStyle.getOffsetX(); @@ -311,23 +483,32 @@ ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) { textTextAlign : ol.render.canvas.defaultTextAlign; var textBaseline = textTextBaseline !== undefined ? textTextBaseline : ol.render.canvas.defaultTextBaseline; - if (!this.textState_) { - this.textState_ = { - font: font, - textAlign: textAlign, - textBaseline: textBaseline - }; - } else { - var textState = this.textState_; - textState.font = font; - textState.textAlign = textAlign; - textState.textBaseline = textBaseline; + textState = this.textState_; + if (!textState) { + textState = this.textState_ = /** @type {ol.CanvasTextState} */ ({}); } + textState.exceedLength = textStyle.getExceedLength(); + textState.font = font; + textState.maxAngle = textStyle.getMaxAngle(); + textState.placement = textStyle.getPlacement(); + textState.textAlign = textAlign; + textState.textBaseline = textBaseline; + this.text_ = textText !== undefined ? textText : ''; this.textOffsetX_ = textOffsetX !== undefined ? textOffsetX : 0; this.textOffsetY_ = textOffsetY !== undefined ? textOffsetY : 0; this.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false; this.textRotation_ = textRotation !== undefined ? textRotation : 0; this.textScale_ = textScale !== undefined ? textScale : 1; + + this.strokeKey_ = strokeState ? + (typeof strokeState.strokeStyle == 'string' ? strokeState.strokeStyle : ol.getUid(strokeState.strokeStyle)) + + strokeState.lineCap + strokeState.lineDashOffset + '|' + strokeState.lineWidth + + strokeState.lineJoin + strokeState.miterLimit + '[' + strokeState.lineDash.join() + ']' : + ''; + this.textKey_ = textState.font + textState.textAlign; + this.fillKey_ = fillState ? + (typeof fillState.fillStyle == 'string' ? fillState.fillStyle : ('|' + ol.getUid(fillState.fillStyle))) : + ''; } }; diff --git a/src/ol/render/vectorcontext.js b/src/ol/render/vectorcontext.js index 4428177138..1ea450cd4c 100644 --- a/src/ol/render/vectorcontext.js +++ b/src/ol/render/vectorcontext.js @@ -108,14 +108,10 @@ ol.render.VectorContext.prototype.drawPolygon = function(polygonGeometry, featur /** - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. * @param {ol.Feature|ol.render.Feature} feature Feature. */ -ol.render.VectorContext.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) {}; +ol.render.VectorContext.prototype.drawText = function(geometry, feature) {}; /** diff --git a/src/ol/render/webgl/immediate.js b/src/ol/render/webgl/immediate.js index 1d512b284b..84908b1340 100644 --- a/src/ol/render/webgl/immediate.js +++ b/src/ol/render/webgl/immediate.js @@ -88,19 +88,15 @@ ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext); /** * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group. - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. * @private */ -ol.render.webgl.Immediate.prototype.drawText_ = function(replayGroup, - flatCoordinates, offset, end, stride) { +ol.render.webgl.Immediate.prototype.drawText_ = function(replayGroup, geometry) { var context = this.context_; var replay = /** @type {ol.render.webgl.TextReplay} */ ( replayGroup.getReplay(0, ol.render.ReplayType.TEXT)); replay.setTextStyle(this.textStyle_); - replay.drawText(flatCoordinates, offset, end, stride, null, null); + replay.drawText(geometry, null); replay.finish(context); // default colors var opacity = 1; @@ -219,9 +215,7 @@ ol.render.webgl.Immediate.prototype.drawPoint = function(geometry, data) { replay.getDeleteResourcesFunction(context)(); if (this.textStyle_) { - var flatCoordinates = geometry.getFlatCoordinates(); - var stride = geometry.getStride(); - this.drawText_(replayGroup, flatCoordinates, 0, flatCoordinates.length, stride); + this.drawText_(replayGroup, geometry); } }; @@ -247,9 +241,7 @@ ol.render.webgl.Immediate.prototype.drawMultiPoint = function(geometry, data) { replay.getDeleteResourcesFunction(context)(); if (this.textStyle_) { - var flatCoordinates = geometry.getFlatCoordinates(); - var stride = geometry.getStride(); - this.drawText_(replayGroup, flatCoordinates, 0, flatCoordinates.length, stride); + this.drawText_(replayGroup, geometry); } }; @@ -275,8 +267,7 @@ ol.render.webgl.Immediate.prototype.drawLineString = function(geometry, data) { replay.getDeleteResourcesFunction(context)(); if (this.textStyle_) { - var flatMidpoint = geometry.getFlatMidpoint(); - this.drawText_(replayGroup, flatMidpoint, 0, 2, 2); + this.drawText_(replayGroup, geometry); } }; @@ -302,8 +293,7 @@ ol.render.webgl.Immediate.prototype.drawMultiLineString = function(geometry, dat replay.getDeleteResourcesFunction(context)(); if (this.textStyle_) { - var flatMidpoints = geometry.getFlatMidpoints(); - this.drawText_(replayGroup, flatMidpoints, 0, flatMidpoints.length, 2); + this.drawText_(replayGroup, geometry); } }; @@ -329,8 +319,7 @@ ol.render.webgl.Immediate.prototype.drawPolygon = function(geometry, data) { replay.getDeleteResourcesFunction(context)(); if (this.textStyle_) { - var flatInteriorPoint = geometry.getFlatInteriorPoint(); - this.drawText_(replayGroup, flatInteriorPoint, 0, 2, 2); + this.drawText_(replayGroup, geometry); } }; @@ -356,8 +345,7 @@ ol.render.webgl.Immediate.prototype.drawMultiPolygon = function(geometry, data) replay.getDeleteResourcesFunction(context)(); if (this.textStyle_) { - var flatInteriorPoints = geometry.getFlatInteriorPoints(); - this.drawText_(replayGroup, flatInteriorPoints, 0, flatInteriorPoints.length, 2); + this.drawText_(replayGroup, geometry); } }; @@ -383,7 +371,7 @@ ol.render.webgl.Immediate.prototype.drawCircle = function(geometry, data) { replay.getDeleteResourcesFunction(context)(); if (this.textStyle_) { - this.drawText_(replayGroup, geometry.getCenter(), 0, 2, 2); + this.drawText_(replayGroup, geometry); } }; diff --git a/src/ol/render/webgl/textreplay.js b/src/ol/render/webgl/textreplay.js index 23fb5bbd2d..98a60bc4b8 100644 --- a/src/ol/render/webgl/textreplay.js +++ b/src/ol/render/webgl/textreplay.js @@ -3,6 +3,7 @@ goog.provide('ol.render.webgl.TextReplay'); goog.require('ol'); goog.require('ol.colorlike'); goog.require('ol.dom'); +goog.require('ol.geom.GeometryType'); goog.require('ol.has'); goog.require('ol.render.replay'); goog.require('ol.render.webgl'); @@ -118,9 +119,38 @@ ol.inherits(ol.render.webgl.TextReplay, ol.render.webgl.TextureReplay); /** * @inheritDoc */ -ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, - end, stride, geometry, feature) { +ol.render.webgl.TextReplay.prototype.drawText = function(geometry, feature) { if (this.text_) { + var flatCoordinates = null; + var offset = 0; + var end = 2; + var stride = 2; + switch (geometry.getType()) { + case ol.geom.GeometryType.POINT: + case ol.geom.GeometryType.MULTI_POINT: + flatCoordinates = geometry.getFlatCoordinates(); + end = flatCoordinates.length; + stride = geometry.getStride(); + break; + case ol.geom.GeometryType.CIRCLE: + flatCoordinates = /** @type {ol.geom.Circle} */ (geometry).getCenter(); + break; + case ol.geom.GeometryType.LINE_STRING: + flatCoordinates = /** @type {ol.geom.LineString} */ (geometry).getFlatMidpoint(); + break; + case ol.geom.GeometryType.MULTI_LINE_STRING: + flatCoordinates = /** @type {ol.geom.MultiLineString} */ (geometry).getFlatMidpoints(); + end = flatCoordinates.length; + break; + case ol.geom.GeometryType.POLYGON: + flatCoordinates = /** @type {ol.geom.Polygon} */ (geometry).getFlatInteriorPoint(); + break; + case ol.geom.GeometryType.MULTI_POLYGON: + flatCoordinates = /** @type {ol.geom.MultiPolygon} */ (geometry).getFlatInteriorPoints(); + end = flatCoordinates.length; + break; + default: + } this.startIndices.push(this.indices.length); this.startIndicesFeature.push(feature); diff --git a/src/ol/renderer/vector.js b/src/ol/renderer/vector.js index b0efa64a47..e1e055fc89 100644 --- a/src/ol/renderer/vector.js +++ b/src/ol/renderer/vector.js @@ -58,7 +58,7 @@ ol.renderer.vector.renderCircleGeometry_ = function(replayGroup, geometry, style var textReplay = replayGroup.getReplay( style.getZIndex(), ol.render.ReplayType.TEXT); textReplay.setTextStyle(textStyle); - textReplay.drawText(geometry.getCenter(), 0, 2, 2, geometry, feature); + textReplay.drawText(geometry, feature); } }; @@ -181,7 +181,7 @@ ol.renderer.vector.renderLineStringGeometry_ = function(replayGroup, geometry, s var textReplay = replayGroup.getReplay( style.getZIndex(), ol.render.ReplayType.TEXT); textReplay.setTextStyle(textStyle); - textReplay.drawText(geometry.getFlatMidpoint(), 0, 2, 2, geometry, feature); + textReplay.drawText(geometry, feature); } }; @@ -206,9 +206,7 @@ ol.renderer.vector.renderMultiLineStringGeometry_ = function(replayGroup, geomet var textReplay = replayGroup.getReplay( style.getZIndex(), ol.render.ReplayType.TEXT); textReplay.setTextStyle(textStyle); - var flatMidpointCoordinates = geometry.getFlatMidpoints(); - textReplay.drawText(flatMidpointCoordinates, 0, - flatMidpointCoordinates.length, 2, geometry, feature); + textReplay.drawText(geometry, feature); } }; @@ -234,9 +232,7 @@ ol.renderer.vector.renderMultiPolygonGeometry_ = function(replayGroup, geometry, var textReplay = replayGroup.getReplay( style.getZIndex(), ol.render.ReplayType.TEXT); textReplay.setTextStyle(textStyle); - var flatInteriorPointCoordinates = geometry.getFlatInteriorPoints(); - textReplay.drawText(flatInteriorPointCoordinates, 0, - flatInteriorPointCoordinates.length, 2, geometry, feature); + textReplay.drawText(geometry, feature); } }; @@ -264,8 +260,7 @@ ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style, var textReplay = replayGroup.getReplay( style.getZIndex(), ol.render.ReplayType.TEXT); textReplay.setTextStyle(textStyle); - textReplay.drawText(geometry.getFlatCoordinates(), 0, 2, 2, geometry, - feature); + textReplay.drawText(geometry, feature); } }; @@ -293,9 +288,7 @@ ol.renderer.vector.renderMultiPointGeometry_ = function(replayGroup, geometry, s var textReplay = replayGroup.getReplay( style.getZIndex(), ol.render.ReplayType.TEXT); textReplay.setTextStyle(textStyle); - var flatCoordinates = geometry.getFlatCoordinates(); - textReplay.drawText(flatCoordinates, 0, flatCoordinates.length, - geometry.getStride(), geometry, feature); + textReplay.drawText(geometry, feature); } }; @@ -321,8 +314,7 @@ ol.renderer.vector.renderPolygonGeometry_ = function(replayGroup, geometry, styl var textReplay = replayGroup.getReplay( style.getZIndex(), ol.render.ReplayType.TEXT); textReplay.setTextStyle(textStyle); - textReplay.drawText( - geometry.getFlatInteriorPoint(), 0, 2, 2, geometry, feature); + textReplay.drawText(geometry, feature); } }; diff --git a/test/spec/ol/geom/flat/textpath.test.js b/test/spec/ol/geom/flat/textpath.test.js new file mode 100644 index 0000000000..d85932607f --- /dev/null +++ b/test/spec/ol/geom/flat/textpath.test.js @@ -0,0 +1,121 @@ +goog.require('ol.geom.flat.textpath'); +goog.require('ol.geom.flat.length'); + +describe('textpath', function() { + + var horizontal = [0, 0, 100, 0]; + var vertical = [0, 0, 0, 100]; + var diagonal = [0, 0, 100, 100]; + var reverse = [100, 0, 0, 100]; + var angled = [0, 0, 100, 100, 200, 0]; + var reverseangled = [151, 17, 163, 22, 159, 30, 150, 30, 143, 24, 151, 17]; + + function measure(text) { + return 10 * text.length; + } + + it('center-aligns text on a horizontal line', function() { + var startM = 50 - 15; + var instructions = ol.geom.flat.textpath.lineString( + horizontal, 0, horizontal.length, 2, 'foo', measure, startM, Infinity); + expect(instructions).to.eql([[40, 0, 0], [50, 0, 0], [60, 0, 0]]); + }); + + it('left-aligns text on a horizontal line', function() { + var instructions = ol.geom.flat.textpath.lineString( + horizontal, 0, horizontal.length, 2, 'foo', measure, 0, Infinity); + expect(instructions).to.eql([[5, 0, 0], [15, 0, 0], [25, 0, 0]]); + }); + + it('right-aligns text on a horizontal line', function() { + var startM = 100 - 30; + var instructions = ol.geom.flat.textpath.lineString( + horizontal, 0, horizontal.length, 2, 'foo', measure, startM, Infinity); + expect(instructions).to.eql([[75, 0, 0], [85, 0, 0], [95, 0, 0]]); + }); + + it('draws text on a vertical line', function() { + var startM = 50 - 15; + var instructions = ol.geom.flat.textpath.lineString( + vertical, 0, vertical.length, 2, 'foo', measure, startM, Infinity); + var a = 90 * Math.PI / 180; + expect(instructions).to.eql([[0, 40, a], [0, 50, a], [0, 60, a]]); + }); + + it('draws text on a diagonal line', function() { + var startM = Math.sqrt(2) * 50 - 15; + var instructions = ol.geom.flat.textpath.lineString( + diagonal, 0, diagonal.length, 2, 'foo', measure, startM, Infinity); + expect(instructions[0][2]).to.be(45 * Math.PI / 180); + expect(instructions[0][0]).to.be.lessThan(instructions[2][0]); + expect(instructions[0][1]).to.be.lessThan(instructions[2][1]); + }); + + it('draws reverse text on a diagonal line', function() { + var startM = Math.sqrt(2) * 50 - 15; + var instructions = ol.geom.flat.textpath.lineString( + reverse, 0, reverse.length, 2, 'foo', measure, startM, Infinity); + expect(instructions[0][2]).to.be(-45 * Math.PI / 180); + expect(instructions[0][0]).to.be.lessThan(instructions[2][0]); + expect(instructions[0][1]).to.be.greaterThan(instructions[2][1]); + }); + + it('renders long text with extrapolation', function() { + var startM = 50 - 75; + var instructions = ol.geom.flat.textpath.lineString( + horizontal, 0, horizontal.length, 2, 'foo-foo-foo-foo', measure, startM, Infinity); + expect(instructions[0]).to.eql([-20, 0, 0]); + expect(instructions[14]).to.eql([120, 0, 0]); + }); + + it('renders angled text', function() { + var length = ol.geom.flat.length.lineString(angled, 0, angled.length, 2); + var startM = length / 2 - 15; + var instructions = ol.geom.flat.textpath.lineString( + angled, 0, angled.length, 2, 'foo', measure, startM, Infinity); + expect(instructions[0][2]).to.be(45 * Math.PI / 180); + expect(instructions[2][2]).to.be(-45 * Math.PI / 180); + }); + + it('respects maxAngle', function() { + var length = ol.geom.flat.length.lineString(angled, 0, angled.length, 2); + var startM = length / 2 - 15; + var instructions = ol.geom.flat.textpath.lineString( + angled, 0, angled.length, 2, 'foo', measure, startM, Math.PI / 4); + expect(instructions).to.be(null); + }); + + it('uses the smallest angle for maxAngleDelta', function() { + var length = ol.geom.flat.length.lineString(reverseangled, 0, reverseangled.length, 2); + var startM = length / 2 - 15; + var instructions = ol.geom.flat.textpath.lineString( + reverseangled, 0, reverseangled.length, 2, 'foo', measure, startM, Math.PI); + expect(instructions).to.not.be(undefined); + }); + + it('respects the begin option', function() { + var length = ol.geom.flat.length.lineString(angled, 2, angled.length, 2); + var startM = length / 2 - 15; + var instructions = ol.geom.flat.textpath.lineString( + angled, 2, angled.length, 2, 'foo', measure, startM, Infinity); + expect(instructions[1][0]).to.be(150); + }); + + it('respects the end option', function() { + var length = ol.geom.flat.length.lineString(angled, 0, 4, 2); + var startM = length / 2 - 15; + var instructions = ol.geom.flat.textpath.lineString( + angled, 0, 4, 2, 'foo', measure, startM, Infinity); + expect(instructions[1][0]).to.be(50); + }); + + it('uses the provided result array', function() { + var result = []; + result[3] = undefined; + var startM = 50 - 15; + ol.geom.flat.textpath.lineString( + horizontal, 0, horizontal.length, 2, 'foo', measure, startM, Infinity, result); + expect(result).to.eql([[40, 0, 0], [50, 0, 0], [60, 0, 0], undefined]); + }); + +}); diff --git a/test/spec/ol/render/webgl/textreplay.test.js b/test/spec/ol/render/webgl/textreplay.test.js index 2cdc7eb38e..6a33b886c5 100644 --- a/test/spec/ol/render/webgl/textreplay.test.js +++ b/test/spec/ol/render/webgl/textreplay.test.js @@ -1,6 +1,5 @@ - - goog.require('ol.dom'); +goog.require('ol.geom.Point'); goog.require('ol.render.webgl.TextReplay'); goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); @@ -127,19 +126,19 @@ describe('ol.render.webgl.TextReplay', function() { var point; point = [1000, 2000]; - replay.drawText(point, 0, 2, 2, null, null); + replay.drawText(new ol.geom.Point(point), null); expect(replay.vertices).to.have.length(256); expect(replay.indices).to.have.length(48); point = [2000, 3000]; - replay.drawText(point, 0, 2, 2, null, null); + replay.drawText(new ol.geom.Point(point), null); expect(replay.vertices).to.have.length(512); expect(replay.indices).to.have.length(96); }); it('sets part of its state during drawing', function() { var point = [1000, 2000]; - replay.drawText(point, 0, 2, 2, null, null); + replay.drawText(new ol.geom.Point(point), null); var height = replay.currAtlas_.height; var widths = replay.currAtlas_.width; @@ -163,7 +162,7 @@ describe('ol.render.webgl.TextReplay', function() { var point; point = [1000, 2000]; - replay.drawText(point, 0, 2, 2, null, null); + replay.drawText(new ol.geom.Point(point), null); expect(replay.vertices).to.have.length(0); expect(replay.indices).to.have.length(0); });