import {Canvas, LineString} from 'maptalks' // https://github.com/Viglino/Canvas-TextPath /** Render text along a path in a Canvas * Adds extra functionality to the CanvasRenderingContext2D by extending its prototype. * Extent the global object with options: * - textOverflow {undefined|visible|ellipsis|string} the text to use on overflow, default "" (hidden) * - textJustify {undefined|boolean} used to justify text (otherwise use textAlign), default false * - textStrokeMin {undefined|number} the min length (in pixel) for the support path to draw the text upon, default 0 * * @param {string} text the text to render * @param {Array} path an array of coordinates as support for the text (ie. [x1,y1,x2,y2,...] */ (function() { /* Usefull function */ function dist2D(x1,y1,x2,y2) { var dx = x2-x1; var dy = y2-y1; return Math.sqrt(dx*dx+dy*dy); } /* Add new properties on CanvasRenderingContext2D */ CanvasRenderingContext2D.prototype.textOverflow = ""; CanvasRenderingContext2D.prototype.textJustify = false; CanvasRenderingContext2D.prototype.textStrokeMin = 0; var state = []; var save = CanvasRenderingContext2D.prototype.save; CanvasRenderingContext2D.prototype.save = function() { state.push( { textOverflow: this.textOverflow, textJustify: this.textJustify, textStrokeMin: this.textStrokeMin, }); save.call(this); } var restore = CanvasRenderingContext2D.prototype.restore; CanvasRenderingContext2D.prototype.restore = function() { restore.call(this); var s = state.pop(); this.textOverflow = s.textOverflow; this.textJustify = s.textJustify; this.textStrokeMin = s.textStrokeMin; } /* textPath function */ CanvasRenderingContext2D.prototype.textPath = function (text, path) { // Helper to get a point on the path, starting at dl // (return x, y and the angle on the path) var di, dpos=0; var pos=2; function pointAt(dl) { if (!di || dpos+didl) break; pos += 2; if (pos>=path.length) break; dpos += di; } } var x, y, dt = dl-dpos; if (pos>=path.length) { pos = path.length-2; } if (!dt) { x = path[pos-2]; y = path[pos-1]; } else { x = path[pos-2]+ (path[pos]-path[pos-2])*dt/di; y = path[pos-1]+ (path[pos+1]-path[pos-1])*dt/di; } return [x, y, Math.atan2(path[pos+1]-path[pos-1], path[pos]-path[pos-2])]; } var letterPadding = this.measureText(" ").width *0.25; // Calculate length var d = 0; for (var i=2; i0.1) this.strokeText(letter,0,0); this.fillText(letter,0,0); this.restore(); start += wl+letterPadding*(letter==" "?2:1); } }; })(); function distance(pt1, pt2){ const xdis = pt2.x - pt1.x; const ydis = pt2.y - pt1.y; return Math.sqrt(xdis*xdis + ydis*ydis); } function ratio(pt1, pt2){ const vecX = pt2.x - pt1.x; const vecY = pt2.y - pt1.y; let vecLen = distance(pt1, pt2); if(vecLen<0.000001) return {x:0, y:0}; return {x: vecX/vecLen, y: vecY/vecLen}; } function getCubicCurveLength(x0, y0, x1, y1, x2, y2){ // from https://math.stackexchange.com/questions/12186/arc-length-of-b%C3%A9zier-curves let v = {}, w = {}; v.x = 2*(x1 - x0); v.y = 2*(y1 - y0); w.x = x2 - 2*x1 + x0; w.y = y2 - 2*y1 + y0; const uu = 4*(w.x*w.x + w.y*w.y); if(uu < 0.00001) { return Math.sqrt((x2 - x0)*(x2 - x0) + (y2 - y0)*(y2 - y0)); } const vv = 4*(v.x*w.x + v.y*w.y), ww = v.x*v.x + v.y*v.y; const t1 = 2*Math.sqrt(uu*(uu + vv + ww)), t2 = 2*uu+vv, t3 = vv*vv - 4*uu*ww, t4 = 2*Math.sqrt(uu*ww); const error = 0.00001; return (t1*t2 - t3*Math.log(t2+t1+error) -(vv*t4 - t3*Math.log(vv+t4+error))) / (8*Math.pow(uu, 1.5)); } function getCubicControlPoints(x0, y0, x1, y1, x2, y2, x3, y3, smoothValue) { //from http://www.antigrain.com/research/bezier_interpolation/ // Assume we need to calculate the control // points between (x1,y1) and (x2,y2). // Then x0,y0 - the previous vertex, // x3,y3 - the next one. const xc1 = (x0 + x1) / 2.0, yc1 = (y0 + y1) / 2.0; const xc2 = (x1 + x2) / 2.0, yc2 = (y1 + y2) / 2.0; const xc3 = (x2 + x3) / 2.0, yc3 = (y2 + y3) / 2.0; const len1 = Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); const len2 = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); const len3 = Math.sqrt((x3 - x2) * (x3 - x2) + (y3 - y2) * (y3 - y2)); const k1 = len1 / (len1 + len2); const k2 = len2 / (len2 + len3); const xm1 = xc1 + (xc2 - xc1) * k1, ym1 = yc1 + (yc2 - yc1) * k1; const xm2 = xc2 + (xc3 - xc2) * k2, ym2 = yc2 + (yc3 - yc2) * k2; // Resulting control points. Here smoothValue is mentioned // above coefficient K whose value should be in range [0...1]. const ctrl1X = xm1 + (xc2 - xm1) * smoothValue + x1 - xm1, ctrl1Y = ym1 + (yc2 - ym1) * smoothValue + y1 - ym1, ctrl2X = xm2 + (xc2 - xm2) * smoothValue + x2 - xm2, ctrl2Y = ym2 + (yc2 - ym2) * smoothValue + y2 - ym2; return [ctrl1X, ctrl1Y, ctrl2X, ctrl2Y]; } //https://stackoverflow.com/questions/22578254/drawing-shapes-along-a-curved-path-in-canvas /* At the start of the curve T==0.00 At the end of the curve T==1.00 */ function getQuadraticBezierXYatT(startPt,controlPt,endPt,T) { var x = Math.pow(1-T,2) * startPt.x + 2 * (1-T) * T * controlPt.x + Math.pow(T,2) * endPt.x; var y = Math.pow(1-T,2) * startPt.y + 2 * (1-T) * T * controlPt.y + Math.pow(T,2) * endPt.y; return( {x:x,y:y} ); } // Anti-Clock rotate function vecRotate(vec, theta){ const sin = Math.sin(theta*Math.PI/180); const cos = Math.cos(theta*Math.PI/180); return new Point([vec.x*cos - vec.y*sin, vec.x*sin + vec.y*cos]); } function reversePts(pts){ let temp, low = 0, high = pts.length-1; while(low