/* Copyright (c) 2018 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ import {DEVICE_PIXEL_RATIO as ol_has_DEVICE_PIXEL_RATIO} from 'ol/has.js' import ol_style_Stroke from 'ol/style/Stroke.js' import {asString as ol_color_asString} from 'ol/color.js' import ol_style_FillPattern from './FillPattern.js' /** * @classdesc * Stroke style with named pattern * * @constructor * @param {any} options * @param {ol.style.Image|undefined} options.image an image pattern, image must be preloaded to draw on first call * @param {number|undefined} options.opacity opacity with image pattern, default:1 * @param {string} options.pattern pattern name (override by image option) * @param {ol.colorLike} options.color pattern color * @param {ol.style.Fill} options.fill fill color (background) * @param {number|Array} options.offset pattern offset for hash/dot/circle/cross pattern * @param {number} options.size line size for hash/dot/circle/cross pattern * @param {number} options.spacing spacing for hash/dot/circle/cross pattern * @param {number|bool} options.angle angle for hash pattern / true for 45deg dot/circle/cross * @param {number} options.scale pattern scale * @extends {ol.style.Fill} * @implements {ol.structs.IHasChecksum} * @api */ var ol_style_StrokePattern = class olstyleStrokePattern extends ol_style_Stroke { constructor(options) { super(options) options = options || {} var pattern, i; var canvas = this.canvas_ = document.createElement('canvas') var scale = Number(options.scale) > 0 ? Number(options.scale) : 1 var ratio = scale * ol_has_DEVICE_PIXEL_RATIO || ol_has_DEVICE_PIXEL_RATIO var ctx = canvas.getContext('2d') if (options.image) { options.image.load() var img = options.image.getImage() if (img.width) { canvas.width = Math.round(img.width * ratio) canvas.height = Math.round(img.height * ratio) ctx.globalAlpha = typeof (options.opacity) == 'number' ? options.opacity : 1 ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height) pattern = ctx.createPattern(canvas, 'repeat') } else { var self = this pattern = [0, 0, 0, 0] img.onload = function () { canvas.width = Math.round(img.width * ratio) canvas.height = Math.round(img.height * ratio) ctx.globalAlpha = typeof (options.opacity) == 'number' ? options.opacity : 1 ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height) pattern = ctx.createPattern(canvas, 'repeat') self.setColor(pattern) } } } else { var pat = this.getPattern_(options) canvas.width = Math.round(pat.width * ratio) canvas.height = Math.round(pat.height * ratio) ctx.beginPath() if (options.fill) { ctx.fillStyle = ol_color_asString(options.fill.getColor()) ctx.fillRect(0, 0, canvas.width, canvas.height) } ctx.scale(ratio, ratio) ctx.lineCap = "round" ctx.lineWidth = pat.stroke || 1 ctx.fillStyle = ol_color_asString(options.color || "#000") ctx.strokeStyle = ol_color_asString(options.color || "#000") if (pat.circles) for (i = 0; i < pat.circles.length; i++) { var ci = pat.circles[i] ctx.beginPath() ctx.arc(ci[0], ci[1], ci[2], 0, 2 * Math.PI) if (pat.fill) ctx.fill() if (pat.stroke) ctx.stroke() } if (!pat.repeat) pat.repeat = [[0, 0]] if (pat.char) { ctx.font = pat.font || (pat.width) + "px Arial" ctx.textAlign = 'center' ctx.textBaseline = 'middle' if (pat.angle) { ctx.fillText(pat.char, pat.width / 4, pat.height / 4) ctx.fillText(pat.char, 5 * pat.width / 4, 5 * pat.height / 4) ctx.fillText(pat.char, pat.width / 4, 5 * pat.height / 4) ctx.fillText(pat.char, 5 * pat.width / 4, pat.height / 4) ctx.fillText(pat.char, 3 * pat.width / 4, 3 * pat.height / 4) ctx.fillText(pat.char, -pat.width / 4, -pat.height / 4) ctx.fillText(pat.char, 3 * pat.width / 4, -pat.height / 4) ctx.fillText(pat.char, -pat.width / 4, 3 * pat.height / 4) } else ctx.fillText(pat.char, pat.width / 2, pat.height / 2) } if (pat.lines) for (i = 0; i < pat.lines.length; i++) for (var r = 0; r < pat.repeat.length; r++) { var li = pat.lines[i] ctx.beginPath() ctx.moveTo(li[0] + pat.repeat[r][0], li[1] + pat.repeat[r][1]) for (var k = 2; k < li.length; k += 2) { ctx.lineTo(li[k] + pat.repeat[r][0], li[k + 1] + pat.repeat[r][1]) } if (pat.fill) ctx.fill() if (pat.stroke) ctx.stroke() ctx.save() ctx.strokeStyle = 'red' ctx.strokeWidth = 0.1 //ctx.strokeRect(0,0,canvas.width,canvas.height); ctx.restore() } pattern = ctx.createPattern(canvas, 'repeat') if (options.offset) { var offset = options.offset if (typeof (offset) == "number") offset = [offset, offset] if (offset instanceof Array) { var dx = Math.round((offset[0] * ratio)) var dy = Math.round((offset[1] * ratio)) // New pattern ctx.scale(1 / ratio, 1 / ratio) ctx.clearRect(0, 0, canvas.width, canvas.height) ctx.translate(dx, dy) ctx.fillStyle = pattern ctx.fillRect(-dx, -dy, canvas.width, canvas.height) pattern = ctx.createPattern(canvas, 'repeat') } } } this.setColor (pattern); } /** * Clones the style. * @return {ol_style_StrokePattern} */ clone() { var s = super.clone() s.canvas_ = this.canvas_ return s } /** Get canvas used as pattern * @return {canvas} */ getImage() { return this.canvas_ } /** Get pattern * @param {olx.style.FillPatternOption} */ getPattern_(options) { var pat = ol_style_FillPattern.patterns[options.pattern] || ol_style_FillPattern.patterns.dot var d = Math.round(options.spacing) || 10 var size // var d2 = Math.round(d/2)+0.5; switch (options.pattern) { case 'dot': case 'circle': { size = options.size === 0 ? 0 : options.size / 2 || 2 if (!options.angle) { pat.width = pat.height = d pat.circles = [[d / 2, d / 2, size]] if (options.pattern == 'circle') { pat.circles = pat.circles.concat([ [d / 2 + d, d / 2, size], [d / 2 - d, d / 2, size], [d / 2, d / 2 + d, size], [d / 2, d / 2 - d, size], [d / 2 + d, d / 2 + d, size], [d / 2 + d, d / 2 - d, size], [d / 2 - d, d / 2 + d, size], [d / 2 - d, d / 2 - d, size] ]) } } else { d = pat.width = pat.height = Math.round(d * 1.4) pat.circles = [[d / 4, d / 4, size], [3 * d / 4, 3 * d / 4, size]] if (options.pattern == 'circle') { pat.circles = pat.circles.concat([ [d / 4 + d, d / 4, size], [d / 4, d / 4 + d, size], [3 * d / 4 - d, 3 * d / 4, size], [3 * d / 4, 3 * d / 4 - d, size], [d / 4 + d, d / 4 + d, size], [3 * d / 4 - d, 3 * d / 4 - d, size] ]) } } break } case 'tile': case 'square': { size = options.size === 0 ? 0 : options.size / 2 || 2 if (!options.angle) { pat.width = pat.height = d pat.lines = [[d / 2 - size, d / 2 - size, d / 2 + size, d / 2 - size, d / 2 + size, d / 2 + size, d / 2 - size, d / 2 + size, d / 2 - size, d / 2 - size]] } else { pat.width = pat.height = d //size *= Math.sqrt(2); pat.lines = [[d / 2 - size, d / 2, d / 2, d / 2 - size, d / 2 + size, d / 2, d / 2, d / 2 + size, d / 2 - size, d / 2]] } if (options.pattern == 'square') pat.repeat = [[0, 0], [0, d], [d, 0], [0, -d], [-d, 0], [-d, -d], [d, d], [-d, d], [d, -d]] break } case 'cross': { // Limit angle to 0 | 45 if (options.angle) options.angle = 45 } // fallthrough case 'hatch': { var a = Math.round(((options.angle || 0) - 90) % 360) if (a > 180) a -= 360 a *= Math.PI / 180 var cos = Math.cos(a) var sin = Math.sin(a) if (Math.abs(sin) < 0.0001) { pat.width = pat.height = d pat.lines = [[0, 0.5, d, 0.5]] pat.repeat = [[0, 0], [0, d]] } else if (Math.abs(cos) < 0.0001) { pat.width = pat.height = d pat.lines = [[0.5, 0, 0.5, d]] pat.repeat = [[0, 0], [d, 0]] if (options.pattern == 'cross') { pat.lines.push([0, 0.5, d, 0.5]) pat.repeat.push([0, d]) } } else { var w = pat.width = Math.round(Math.abs(d / sin)) || 1 var h = pat.height = Math.round(Math.abs(d / cos)) || 1 if (options.pattern == 'cross') { pat.lines = [[-w, -h, 2 * w, 2 * h], [2 * w, -h, -w, 2 * h]] pat.repeat = [[0, 0]] } else if (cos * sin > 0) { pat.lines = [[-w, -h, 2 * w, 2 * h]] pat.repeat = [[0, 0], [w, 0], [0, h]] } else { pat.lines = [[2 * w, -h, -w, 2 * h]] pat.repeat = [[0, 0], [-w, 0], [0, h]] } } pat.stroke = options.size === 0 ? 0 : options.size || 4 break } default: { break } } return pat } } export default ol_style_StrokePattern