UNPKG

cvs

Version:

A basic canvas framework for 2d drawing.

211 lines (182 loc) 5.04 kB
import Matrix from '../utils/matrix' // import {isPointInPath, getPointAtLength, getTotalLength} from './platform' import {isPointInPath, getPointAtLength, getTotalLength} from './deal' import parse from './parse-svg-path' import abs from './abs-svg-path' import normalize from './normalize-svg-path' const _path = Symbol('path') const _initialPath = Symbol('initialPath') const _bounds = Symbol('bounds') const _savedPaths = Symbol('savedPaths') const _renderProps = Symbol('renderProps') const _beginPath = Symbol('beginPath') // https://github.com/dy/is-svg-path function isSvgPath (str) { if (typeof str !== 'string') return false str = str.trim() // https://www.w3.org/TR/SVG/paths.html#PathDataBNF if (/^[mzlhvcsqta]\s*[-+.0-9][^mlhvzcsqta]+/i.test(str) && /[\dz]$/i.test(str) && str.length > 4) return true return false } export default class SvgPath { constructor (d) { if (!isSvgPath(d)) throw new Error('Not an SVG path!') this[_initialPath] = abs(parse(d)) this[_path] = normalize(this[_initialPath]) this[_bounds] = null this[_savedPaths] = [] this[_renderProps] = {} this[_beginPath] = false } save () { this[_savedPaths].push({path: this[_path], bounds: this[_bounds], renderProps: Object.assign({}, this[_renderProps])}) return this } restore () { if (this[_savedPaths].length) { const {path, bounds, renderProps} = this[_savedPaths].pop() this[_path] = path this[_bounds] = bounds this[_renderProps] = renderProps } return this } get bounds () { if (!this[_bounds]) { const path = this[_path] this[_bounds] = [0, 0, 0, 0] if (path.length) { const bounds = [Infinity, Infinity, -Infinity, -Infinity] for (let i = 0, l = path.length; i < l; i++) { const points = path[i].slice(1) for (let j = 0; j < points.length; j += 2) { if (points[j + 0] < bounds[0]) bounds[0] = points[j + 0] if (points[j + 1] < bounds[1]) bounds[1] = points[j + 1] if (points[j + 0] > bounds[2]) bounds[2] = points[j + 0] if (points[j + 1] > bounds[3]) bounds[3] = points[j + 1] } } this[_bounds] = bounds } } return this[_bounds] } get size () { const bounds = this.bounds return [bounds[2] - bounds[0], bounds[3] - bounds[1]] } get center () { const [x0, y0, x1, y1] = this.bounds return [(x0 + x1) / 2, (y0 + y1) / 2] } get d () { return this[_path].map((p) => { const [c, ...points] = p return c + points.join() }).join('') } get path () { return this[_path] } isPointInPath (x, y) { return isPointInPath(this, x, y) } getPointAtLength (len) { return getPointAtLength(this.d, len) } getTotalLength () { return getTotalLength(this.d) } transform (...args) { this[_bounds] = null const m = new Matrix(args) const commands = this[_path] this[_path] = commands.map((c) => { const [cmd, ...args] = c const transformed = [cmd] for (let i = 0; i < args.length; i += 2) { const x0 = args[i], y0 = args[i + 1] const [x, y] = m.transformPoint(x0, y0) transformed.push(x, y) } return transformed }) return this } translate (x, y) { const m = new Matrix().translate(x, y) return this.transform(...m.m) } rotate (deg) { const m = new Matrix().rotate(deg) return this.transform(...m.m) } scale (sx, sy) { if (sy == null) sy = sx const m = new Matrix().scale(sx, sy) return this.transform(...m.m) } skew (degX, degY) { const m = new Matrix().skew(degX, degY) return this.transform(...m.m) } trim () { const [x, y] = this.bounds this.translate(-x, -y) return this } beginPath () { this[_beginPath] = true return this } to (context) { const commands = this[_path] const renderProps = this[_renderProps] if (commands.length) { if (this[_beginPath]) context.beginPath() commands.forEach((c) => { const [cmd, ...args] = c if (cmd === 'M') { context.moveTo(...args) } else if (cmd === 'C') { context.bezierCurveTo(...args) } }) if (this[_initialPath][this[_initialPath].length - 1][0] === 'Z') context.closePath() } Object.assign(context, renderProps) return { stroke () { context.stroke() return this }, fill () { context.fill() return this }, } } strokeStyle (value) { this[_renderProps].strokeStyle = value return this } fillStyle (value) { this[_renderProps].fillStyle = value return this } lineWidth (value) { this[_renderProps].lineWidth = value return this } lineCap (value) { this[_renderProps].lineCap = value return this } lineJoin (value) { this[_renderProps].lineJoin = value return this } }