Automattic / Automattic/node-canvas

Support Path2D API

Open
#1,116 8 comments 30 reactions 0 assignees View on GitHub
Feature
Dominant language
JavaScript
Stars
10.7k
Forks
1.2k
Avg merge
4d 8h
Merged PRs (30d)
1

Description

https://developer.mozilla.org/en-US/docs/Web/API/Path2D

The Path2D interface of the Canvas 2D API is used to declare paths that are then later used on CanvasRenderingContext2D objects. The path methods of the CanvasRenderingContext2D interface are present on this interface as well and are allowing you to create paths that you can retain and replay as required on a canvas.

---

This is my polyfill:

```js
const {CanvasRenderingContext2D, createCanvas, Image} = require('canvas')
const {parseSVG, makeAbsolute} = require('svg-path-parser')

const MAX_SIZE = 2048

function createPathSVG(d, lineWidth, lineCap, lineJoin, strokeColor, fillColor) {
const tpl = `

`

const img = new Image()
img.src = Buffer.from(tpl, 'utf8')

return img
}

class Path2D {
constructor(d) {
this.footprint = []
this.commands = []
if(d instanceof Path2D) {
this.addPath(d)
} else if(typeof d === 'string') {
// svg path
const commands = makeAbsolute(parseSVG(d))
if(commands[0] && commands[0].code === 'M') {
this.footprint.push([commands[0].x, commands[0].y])
}
this.commands.push(['path', d])
}
}
addPath(path) {
this.footprint.push(...path.footprint)
this.commands.push(...path.commands)
}
closePath() {
const point = this.footprint[0]
if(point) {
this.moveTo(...point)
}
}
moveTo(x, y) {
this.footprint.push([x, y])
this.commands.push(['moveTo', x, y])
}
lineTo(x, y) {
this.footprint.push([x, y])
this.commands.push(['lineTo', x, y])
}
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
this.footprint.push([x, y])
this.commands.push(['bezierCurveTo', cp1x, cp1y, cp2x, cp2y, x, y])
}
quadraticCurveTo(cpx, cpy, x, y) {
this.footprint.push([x, y])
this.commands.push('quadraticCurveTo', cpx, cpy, x, y)
}
arc(x, y, ...rest) {
this.footprint.push([x, y])
this.commands.push('arc', x, y, ...rest)
}
arcTo(x1, y1, x2, y2, radius) {
this.commands.push('artTo', x1, y1, x2, y2, radius)
}
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
this.footprint.push([x, y])
this.commands.push('ellipse', x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
}
rect(x, y, width, height) {
this.footprint.push([x, y])
this.commands.push('rect', x, y, width, height)
}
draw(context, type = 'stroke') {
const {lineWidth, lineCap, lineJoin, strokeStyle, fillStyle} = context
context.save()
context.beginPath()
this.commands.forEach((command) => {
const [cmd, ...args] = command
if(cmd === 'path') {
const svg = createPathSVG(...args, lineWidth, lineCap, lineJoin, strokeStyle, type === 'stroke' ? null : fillStyle)
context.drawImage(svg, 0, 0)
} else {
context[cmd](...args)
context[type]()
}
})
context.restore()
}
}

const _stroke = CanvasRenderingContext2D.prototype.stroke
Object.defineProperty(CanvasRenderingContext2D.prototype, 'stroke', {
value(p) {
if(p instanceof Path2D) {
return p.draw(this, 'stroke')
}
return _stroke.call(this)
},
})

const _fill = CanvasRenderingContext2D.prototype.fill
Object.defineProperty(CanvasRenderingContext2D.prototype, 'fill', {
value(p) {
if(p instanceof Path2D) {
return p.draw(this, 'fill')
}
return _fill.call(this)
},
})

const p = new Path2D('M10,80 q100,120 120,20 q140,-50 160,0')
const canvas = createCanvas(800, 600)
const context = canvas.getContext('2d')
context.strokeStyle = 'red'
context.stroke(p)

require('fs').writeFileSync('out.png', canvas.toBuffer())
```

![](https://p0.ssl.qhimg.com/t014e0631f5b564336f.png)

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.