diff --git a/src/ol/render/webgl.js b/src/ol/render/webgl.js index cac93aa290..26c10c7401 100644 --- a/src/ol/render/webgl.js +++ b/src/ol/render/webgl.js @@ -62,16 +62,16 @@ if (ol.ENABLE_WEBGL) { /** * @const - * @type {string} + * @type {number} */ - ol.render.webgl.defaultTextAlign = 'center'; + ol.render.webgl.defaultTextAlign = 0.5; /** * @const - * @type {string} + * @type {number} */ - ol.render.webgl.defaultTextBaseline = 'middle'; + ol.render.webgl.defaultTextBaseline = 0.5; /** diff --git a/src/ol/render/webgl/textreplay.js b/src/ol/render/webgl/textreplay.js index 101a249459..43f65080e8 100644 --- a/src/ol/render/webgl/textreplay.js +++ b/src/ol/render/webgl/textreplay.js @@ -3,45 +3,23 @@ goog.provide('ol.render.webgl.TextReplay'); goog.require('ol'); goog.require('ol.colorlike'); goog.require('ol.dom'); -goog.require('ol.extent'); goog.require('ol.has'); goog.require('ol.render.webgl'); -goog.require('ol.render.webgl.textreplay.defaultshader'); -goog.require('ol.render.webgl.Replay'); -goog.require('ol.webgl'); +goog.require('ol.render.webgl.TextureReplay'); goog.require('ol.webgl.Buffer'); -goog.require('ol.webgl.Context'); if (ol.ENABLE_WEBGL) { /** * @constructor - * @extends {ol.render.webgl.Replay} + * @extends {ol.render.webgl.TextureReplay} * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. * @struct */ ol.render.webgl.TextReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); - - /** - * @private - * @type {ol.render.webgl.textreplay.defaultshader.Locations} - */ - this.defaultLocations_ = null; - - /** - * @private - * @type {Array.>} - */ - this.styles_ = []; - - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; + ol.render.webgl.TextureReplay.call(this, tolerance, maxExtent); /** * @private @@ -71,17 +49,13 @@ if (ol.ENABLE_WEBGL) { * lineWidth: number, * miterLimit: (number|undefined), * fillColor: (ol.ColorLike|null), - * text: string, + * text: (string|undefined), * font: (string|undefined), - * textAlign: (string|undefined), - * textBaseline: (string|undefined), + * textAlign: (number|undefined), + * textBaseline: (number|undefined), * offsetX: (number|undefined), * offsetY: (number|undefined), - * scale: (number|undefined), - * rotation: (number|undefined), - * rotateWithView: (boolean|undefined), - * height: (number|undefined), - * width: (number|undefined)}} + * scale: (number|undefined)}} */ this.state_ = { strokeColor: null, @@ -98,15 +72,19 @@ if (ol.ENABLE_WEBGL) { textBaseline: undefined, offsetX: undefined, offsetY: undefined, - scale: undefined, - rotation: undefined, - rotateWithView: undefined, - height: undefined, - width: undefined + scale: undefined }; + this.originX = 0; + + this.originY = 0; + + this.scale = 1; + + this.opacity = 1; + }; - ol.inherits(ol.render.webgl.TextReplay, ol.render.webgl.Replay); + ol.inherits(ol.render.webgl.TextReplay, ol.render.webgl.TextureReplay); /** @@ -117,12 +95,11 @@ if (ol.ENABLE_WEBGL) { //For now we create one texture per feature. That is, only multiparts are grouped. //TODO: speed up rendering with SDF, or at least glyph atlases var state = this.state_; - if (state.text && (state.fillColor || state.strokeColor)) { + if (state.text) { this.startIndices.push(this.indices.length); this.startIndicesFeature.push(feature); - this.images_.push(this.createTextImage_()); - this.drawCoordinates_( + this.drawCoordinates( flatCoordinates, offset, end, stride); } }; @@ -142,7 +119,9 @@ if (ol.ENABLE_WEBGL) { var lines = state.text.split('\n'); //FIXME: use pixelRatio var textHeight = Math.ceil(lineHeight * lines.length * state.scale); - state.height = textHeight; + this.height = textHeight; + this.imageHeight = textHeight; + this.anchorY = Math.round(textHeight * state.textBaseline + state.offsetY); var longestLine = lines.map(function(str) { return mCtx.measureText(str).width; }).reduce(function(max, curr) { @@ -150,30 +129,33 @@ if (ol.ENABLE_WEBGL) { }); //FIXME: use pixelRatio var textWidth = Math.ceil((longestLine + state.lineWidth * 2) * state.scale); - state.width = textWidth; + this.width = textWidth; + this.imageWidth = textWidth; + this.anchorX = Math.round(textWidth * state.textAlign + state.offsetX); //Create a canvas var ctx = ol.dom.createCanvasContext2D(textWidth, textHeight); var canvas = ctx.canvas; //Parameterize the canvas - ctx.font = state.font; + ctx.font = /** @type {string} */ (state.font); ctx.fillStyle = state.fillColor; ctx.strokeStyle = state.strokeColor; ctx.lineWidth = state.lineWidth; - ctx.lineCap = state.lineCap; - ctx.lineJoin = state.lineJoin; - ctx.miterLimit = state.miterLimit; + ctx.lineCap = /*** @type {string} */ (state.lineCap); + ctx.lineJoin = /** @type {string} */ (state.lineJoin); + ctx.miterLimit = /** @type {number} */ (state.miterLimit); ctx.textAlign = 'left'; ctx.textBaseline = 'top'; if (ol.has.CANVAS_LINE_DASH && state.lineDash) { //FIXME: use pixelRatio ctx.setLineDash(state.lineDash); - ctx.lineDashOffset = state.lineDashOffset; + ctx.lineDashOffset = /** @type {number} */ (state.lineDashOffset); } if (state.scale !== 1) { //FIXME: use pixelRatio - ctx.setTransform(state.scale, 0, 0, state.scale, 0, 0); + ctx.setTransform(/** @type {number} */ (state.scale), 0, 0, + /** @type {number} */ (state.scale), 0, 0); } //Draw the text on the canvas @@ -192,134 +174,14 @@ if (ol.ENABLE_WEBGL) { }; - /** - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {number} My end. - * @private - */ - ol.render.webgl.TextReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { - var state = this.state_; - var anchorX, anchorY; - var height = /** @type {number} */ (state.height); - var width = /** @type {number} */ (state.width); - switch (state.textAlign) { - default: - anchorX = width / 2; - break; - case 'left': case 'end': - anchorX = 0; - break; - case 'right': case 'start': - anchorX = width; - break; - } - switch (state.textBaseline) { - default: - anchorY = height / 2; - break; - case 'top': - anchorY = 0; - break; - case 'bottom': - anchorY = height; - break; - case 'hanging': - anchorY = height * 0.2; - break; - case 'alphabetic': case 'ideographic': - anchorY = height * 0.8; - break; - } - var rotateWithView = state.rotateWithView ? 1.0 : 0.0; - // this.rotation_ is anti-clockwise, but rotation is clockwise - var rotation = /** @type {number} */ (-state.rotation); - var cos = Math.cos(rotation); - var sin = Math.sin(rotation); - var numIndices = this.indices.length; - var numVertices = this.vertices.length; - var i, n, offsetX, offsetY, x, y; - for (i = offset; i < end; i += stride) { - x = flatCoordinates[i] - this.origin[0]; - y = flatCoordinates[i + 1] - this.origin[1]; - - // There are 4 vertices per [x, y] point, one for each corner of the - // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if - // WebGL supported Geometry Shaders (which can emit new vertices), but that - // is not currently the case. - // - // And each vertex includes 7 values: the x and y coordinates, the x and - // y offsets used to calculate the position of the corner, the u and - // v texture coordinates for the corner, and whether the - // the image should be rotated with the view (rotateWithView). - - n = numVertices / 7; - - // bottom-left corner - offsetX = -anchorX; - offsetY = -(height - anchorY); - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = 0; - this.vertices[numVertices++] = 1; - this.vertices[numVertices++] = rotateWithView; - - // bottom-right corner - offsetX = width - anchorX; - offsetY = -(height - anchorY); - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = 1; - this.vertices[numVertices++] = 1; - this.vertices[numVertices++] = rotateWithView; - - // top-right corner - offsetX = width - anchorX; - offsetY = anchorY; - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = 1; - this.vertices[numVertices++] = 0; - this.vertices[numVertices++] = rotateWithView; - - // top-left corner - offsetX = -anchorX; - offsetY = anchorY; - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = 0; - this.vertices[numVertices++] = 0; - this.vertices[numVertices++] = rotateWithView; - - this.indices[numIndices++] = n; - this.indices[numIndices++] = n + 1; - this.indices[numIndices++] = n + 2; - this.indices[numIndices++] = n; - this.indices[numIndices++] = n + 2; - this.indices[numIndices++] = n + 3; - } - - return numVertices; - }; - - /** * @inheritDoc */ ol.render.webgl.TextReplay.prototype.finish = function(context) { var gl = context.getGL(); - this.startIndices.push(this.indices.length); + this.groupIndices.push(this.indices.length); + this.hitDetectionGroupIndices = this.groupIndices; // create, bind, and populate the vertices buffer this.verticesBuffer = new ol.webgl.Buffer(this.vertices); @@ -328,9 +190,11 @@ if (ol.ENABLE_WEBGL) { this.indicesBuffer = new ol.webgl.Buffer(this.indices); // create textures - this.createTextures_(gl); + /** @type {Object.} */ + var texturePerImage = {}; + + this.createTextures(this.textures_, this.images_, texturePerImage, gl); - this.images_ = []; this.state_ = { strokeColor: null, lineCap: undefined, @@ -346,155 +210,10 @@ if (ol.ENABLE_WEBGL) { textBaseline: undefined, offsetX: undefined, offsetY: undefined, - scale: undefined, - rotation: undefined, - rotateWithView: undefined, - height: undefined, - width: undefined + scale: undefined }; - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { - // get the program - var fragmentShader = ol.render.webgl.textreplay.defaultshader.fragment; - var vertexShader = ol.render.webgl.textreplay.defaultshader.vertex; - var program = context.getProgram(fragmentShader, vertexShader); - - // get the locations - var locations; - if (!this.defaultLocations_) { - // eslint-disable-next-line openlayers-internal/no-missing-requires - locations = new ol.render.webgl.textreplay.defaultshader.Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - - // use the program (FIXME: use the return value) - context.useProgram(program); - - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 28, 0); - - gl.enableVertexAttribArray(locations.a_offsets); - gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT, - false, 28, 8); - - gl.enableVertexAttribArray(locations.a_texCoord); - gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT, - false, 28, 16); - - gl.enableVertexAttribArray(locations.a_rotateWithView); - gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT, - false, 28, 24); - - return locations; - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.shutDownProgram = function(gl, locations) { - gl.disableVertexAttribArray(locations.a_position); - gl.disableVertexAttribArray(locations.a_offsets); - gl.disableVertexAttribArray(locations.a_texCoord); - gl.disableVertexAttribArray(locations.a_rotateWithView); - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { - var textures = this.textures_; - var startIndices = this.startIndices; - - var i, ii, start, end, feature, featureUid; - for (i = 0, ii = textures.length, start = 0; i < ii; ++i) { - feature = this.startIndicesFeature[i]; - featureUid = ol.getUid(feature).toString(); - - end = startIndices[i]; - if (skippedFeaturesHash[featureUid] === undefined) { - gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); - this.drawElements(gl, context, start, end); - } - start = end; - } - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - var textures = this.textures_; - var startIndices = this.startIndices; - - var i, start, end, feature, featureUid; - for (i = textures.length - 1; i >= 0; --i) { - feature = this.startIndicesFeature[i]; - featureUid = ol.getUid(feature).toString(); - - start = (i > 0) ? startIndices[i - 1] : 0; - end = startIndices[i]; - if (skippedFeaturesHash[featureUid] === undefined && - feature.getGeometry() && - (opt_hitExtent === undefined || ol.extent.intersects( - /** @type {Array} */ (opt_hitExtent), - feature.getGeometry().getExtent()))) { - gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawElements(gl, context, start, end); - - var result = featureCallback(feature); - if (result) { - return result; - } - } - } - return undefined; - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.getDeleteResourcesFunction = function(context) { - var verticesBuffer = this.verticesBuffer; - var indicesBuffer = this.indicesBuffer; - var textures = this.textures_; - var gl = context.getGL(); - return function() { - if (!gl.isContextLost()) { - var i, ii; - for (i = 0, ii = textures.length; i < ii; ++i) { - gl.deleteTexture(textures[i]); - } - } - context.deleteBuffer(verticesBuffer); - context.deleteBuffer(indicesBuffer); - }; - }; - - /** - * @private - * @param {WebGLRenderingContext} gl Gl. - */ - ol.render.webgl.TextReplay.prototype.createTextures_ = function(gl) { - for (var i = 0, ii = this.images_.length; i < ii; ++i) { - var image = this.images_[i]; - this.textures_.push(ol.webgl.Context.createTexture( - gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE)); - } + this.images_ = null; + ol.render.webgl.TextureReplay.prototype.finish.call(this, context); }; @@ -503,10 +222,11 @@ if (ol.ENABLE_WEBGL) { */ ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) { var state = this.state_; - if (!textStyle) { + var textFillStyle = textStyle.getFill(); + var textStrokeStyle = textStyle.getStroke(); + if (!textStyle || !textStyle.getText() || (!textFillStyle && !textStrokeStyle)) { state.text = ''; } else { - var textFillStyle = textStyle.getFill(); if (!textFillStyle) { state.fillColor = null; } else { @@ -514,7 +234,6 @@ if (ol.ENABLE_WEBGL) { state.fillColor = ol.colorlike.asColorLike(textFillStyleColor ? textFillStyleColor : ol.render.webgl.defaultFillStyle); } - var textStrokeStyle = textStyle.getStroke(); if (!textStrokeStyle) { state.strokeColor = null; state.lineWidth = 0; @@ -531,15 +250,61 @@ if (ol.ENABLE_WEBGL) { state.lineDash = lineDash ? lineDash.slice() : ol.render.webgl.defaultLineDash; } state.font = textStyle.getFont() || ol.render.webgl.defaultFont; + state.scale = textStyle.getScale() || 1; + state.text = textStyle.getText(); + var textAlign = ol.render.webgl.TextReplay.Align_[textStyle.getTextAlign()]; + var textBaseline = ol.render.webgl.TextReplay.Align_[textStyle.getTextBaseline()]; + state.textAlign = textAlign === undefined ? + ol.render.webgl.defaultTextAlign : textAlign; + state.textBaseline = textBaseline === undefined ? + ol.render.webgl.defaultTextBaseline : textBaseline; state.offsetX = textStyle.getOffsetX() || 0; state.offsetY = textStyle.getOffsetY() || 0; - state.rotateWithView = !!textStyle.getRotateWithView(); - state.rotation = textStyle.getRotation() || 0; - state.scale = textStyle.getScale() || 1; - state.text = textStyle.getText() || ''; - state.textAlign = textStyle.getTextAlign() || ol.render.webgl.defaultTextAlign; - state.textBaseline = textStyle.getTextBaseline() || ol.render.webgl.defaultTextBaseline; + this.rotateWithView = !!textStyle.getRotateWithView(); + this.rotation = textStyle.getRotation() || 0; + + if (this.images_.length === 0) { + this.images_.push(this.createTextImage_()); + } else { + this.groupIndices.push(this.indices.length); + this.images_.push(this.createTextImage_()); + } } }; + + /** + * @inheritDoc + */ + ol.render.webgl.TextReplay.prototype.getTextures = function(opt_all) { + return this.textures_; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextReplay.prototype.getHitDetectionTextures = function() { + return this.textures_; + }; + + + /** + * @enum {number} + * @private + */ + ol.render.webgl.TextReplay.Align_ = { + left: 0, + end: 0, + center: 0.5, + right: 1, + start: 1, + top: 0, + middle: 0.5, + hanging: 0.2, + alphabetic: 0.8, + ideographic: 0.8, + bottom: 1 + }; + }