Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layers acting as masks #70

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion links/main.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions scripts/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ function Client () {
this.acels.set('Style', 'Linejoin', 'W', () => { this.tool.toggle('linejoin') })
this.acels.set('Style', 'Mirror', 'E', () => { this.tool.toggle('mirror') })
this.acels.set('Style', 'Fill', 'R', () => { this.tool.toggle('fill') })
this.acels.set('Style', 'Mask', 'C', () => { this.tool.toggle('mask') })
this.acels.set('Style', 'Thicker', '}', () => { this.tool.toggle('thickness', 1) })
this.acels.set('Style', 'Thinner', '{', () => { this.tool.toggle('thickness', -1) })
this.acels.set('Style', 'Thicker +5', ']', () => { this.tool.toggle('thickness', 5) })
Expand Down
7 changes: 6 additions & 1 deletion scripts/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ function Interface (client) {
linejoin: { key: 'W', icon: 'M60,60 L120,120 L180,120 M120,180 L180,180 L240,240' },
thickness: { key: '', icon: 'M120,90 L120,90 L90,120 L180,210 L210,180 Z M105,105 L105,105 L60,60 M195,195 L195,195 L240,240' },
mirror: { key: 'E', icon: 'M60,60 L60,60 L120,120 M180,180 L180,180 L240,240 M210,90 L210,90 L180,120 M120,180 L120,180 L90,210' },
fill: { key: 'R', icon: 'M60,60 L60,150 L150,150 L240,150 L240,240 Z' }
fill: { key: 'R', icon: 'M60,60 L60,150 L150,150 L240,150 L240,240 Z' },
mask: { key: 'C', icon: 'M105,180 L105,180 L105,240 L240,240 L240,105 L180,105 L105,105 M105,180 L105,180 L105,105 M60,60 L60,60 L60,150 L150,150 L150,60 Z ' }
},
misc: {
color: { key: 'G', icon: 'M150,60 A90,90 0 0,1 240,150 A-90,90 0 0,1 150,240 A-90,-90 0 0,1 60,150 A90,-90 0 0,1 150,60' }
Expand Down Expand Up @@ -138,6 +139,7 @@ function Interface (client) {
options.toggle.linejoin.el.className.baseVal = client.tool.layer().length < 1 || !multiVertices ? 'icon inactive' : 'icon'
options.toggle.mirror.el.className.baseVal = client.tool.layer().length < 1 ? 'icon inactive' : 'icon'
options.toggle.fill.el.className.baseVal = client.tool.layer().length < 1 ? 'icon inactive' : 'icon'
options.toggle.mask.el.className.baseVal = client.tool.layer().length < 1 ? 'icon inactive' : 'icon'
options.misc.color.el.children[0].style.fill = client.tool.style().color
options.misc.color.el.children[0].style.stroke = client.tool.style().color
options.misc.color.el.className.baseVal = 'icon'
Expand All @@ -149,6 +151,9 @@ function Interface (client) {
// Grid
document.getElementById('grid_path').setAttribute('d', client.renderer.showExtras ? 'M65,155 Q155,245 245,155 M65,155 Q155,65 245,155 M155,125 A30,30 0 0,1 185,155 A30,30 0 0,1 155,185 A30,30 0 0,1 125,155 A30,30 0 0,1 155,125 ' : 'M65,155 Q155,245 245,155 M65,155 ')

// Mask
if (!client.tool.style().mask) { document.getElementById('mask_path').setAttribute('d', 'M105,180 L105,180 L105,240 L240,240 L240,105 L180,105 L105,105 M105,180 L105,180 L105,105 M60,60 L60,60 L60,150 L150,150 L150,60 Z ') } else { document.getElementById('mask_path').setAttribute('d', 'M105,180 L105,180 L105,240 L240,240 L240,105 L180,105 L180,180 M105,180 L105,180 L180,180 M60,60 L60,60 L60,150 L150,150 L150,60 Z ') }

// Mirror
document.getElementById('mirror_path').setAttribute('d', mirrorPaths[client.tool.style().mirror_style])
this.prev_operation = client.cursor.operation
Expand Down
135 changes: 117 additions & 18 deletions scripts/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,135 @@ function Manager (client) {
this.el.setAttribute('version', '1.1')
this.el.style.fill = 'none'

this.masks = []
this.layers = []

this.install = function () {
this.masks[0] = [this.maskBg()]
this.masks[1] = [this.maskBg(), document.createElementNS('http://www.w3.org/2000/svg', 'path')]
this.masks[2] = [
this.maskBg(),
document.createElementNS('http://www.w3.org/2000/svg', 'path'),
document.createElementNS('http://www.w3.org/2000/svg', 'path')
]

const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')
this.el.appendChild(defs)
for (const id of [2, 1, 0]) {
const maskPath = document.createElementNS('http://www.w3.org/2000/svg', 'mask')
maskPath.setAttribute('id', 'mask' + id)
for (const mask of this.masks[id]) {
maskPath.appendChild(mask)
}
defs.appendChild(maskPath)
}

this.el.appendChild(this.layers[2] = document.createElementNS('http://www.w3.org/2000/svg', 'path'))
this.el.appendChild(this.layers[1] = document.createElementNS('http://www.w3.org/2000/svg', 'path'))
this.el.appendChild(this.layers[0] = document.createElementNS('http://www.w3.org/2000/svg', 'path'))
}

this.maskBg = function () {
const bg = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
bg.setAttribute('fill', 'white')
bg.setAttribute('width', '100%')
bg.setAttribute('height', '100%')
return bg
}

this.update = function () {
this.el.setAttribute('width', (client.tool.settings.size.width) + 'px')
this.el.setAttribute('height', (client.tool.settings.size.height) + 'px')
this.el.style.width = (client.tool.settings.size.width)
this.el.style.width = client.tool.settings.size.width
this.el.style.height = client.tool.settings.size.height

const styles = client.tool.styles
const paths = client.tool.paths()
const masked = styles.map((style) => style.mask)

const viewBox = this.minimalViewBox();
if (viewBox !== null) {
for (const id in this.masks) {
const [x, y, width, height] = viewBox
this.masks[id][0].setAttribute('x', x)
this.masks[id][0].setAttribute('y', y)
this.masks[id][0].setAttribute('width', width)
this.masks[id][0].setAttribute('height', height)
}
}

for (const id in this.layers) {
const style = styles[id]
const path = paths[id]
const layer = this.layers[id]

const nextLayer = parseInt(id) + 1
for (var i = nextLayer; i < 3; i++) {
const mask = this.masks[i][nextLayer]
if (style.mask) {
mask.setAttribute('d', path)
mask.style.strokeWidth = style.thickness
mask.style.strokeLinecap = style.strokeLinecap
mask.style.strokeLinejoin = style.strokeLinejoin
mask.style.stroke = style.color
mask.style.fill = style.fill
} else {
mask.setAttribute('d', '')
}
}
layer.style.strokeWidth = style.thickness
layer.style.strokeLinecap = style.strokeLinecap
layer.style.strokeLinejoin = style.strokeLinejoin
layer.style.stroke = style.color
layer.style.fill = style.fill

layer.setAttribute('d', path)
layer.setAttribute('d', style.mask ? '' : path)

const maskedAbove = masked.slice(0, id).some((mask) => mask === true)
const maskPath = !(style.mask) && maskedAbove ? 'url(#mask' + id + ')' : null
if (maskPath !== null) {
layer.setAttribute('mask', maskPath)
} else if (layer.hasAttribute('mask')) {
layer.removeAttribute('mask')
}
}
}

this.minimalViewBox = function () {
const styles = client.tool.styles
const paths = client.tool.paths()

var xMin = null
var yMin = null
var xMax = null
var yMax = null
for (const id in this.layers) {
const style = styles[id]
const canOvershoot = style.strokeLinejoin === 'miter' || style.strokeLinecap === 'square'
const offset = canOvershoot ? style.thickness : style.thickness / 2

const path = paths[id]
const matches = path.match(/\d+,\d+/g)
if (matches === null) { continue }

const coordinates = matches.map(p => p.split(/,/))
const xs = coordinates.map(p => p[0]);
const ys = coordinates.map(p => p[1]);
const xMinOfLayer = Math.min(...xs) - offset
const yMinOfLayer = Math.min(...ys) - offset
const xMaxOfLayer = Math.max(...xs) + offset
const yMaxOfLayer = Math.max(...ys) + offset

if (xMin === null || xMinOfLayer < xMin) { xMin = xMinOfLayer }
if (yMin === null || yMinOfLayer < yMin) { yMin = yMinOfLayer }
if (xMax === null || xMaxOfLayer > xMax) { xMax = xMaxOfLayer }
if (yMax === null || yMaxOfLayer > yMax) { yMax = yMaxOfLayer }
}
if (xMin === null || yMin === null || xMax === null || yMax === null) { return null }

return [xMin, yMin, xMax - xMin, yMax - yMin]
}

this.svg64 = function () {
const xml = new XMLSerializer().serializeToString(this.el)
const svg64 = btoa(xml)
Expand All @@ -69,22 +166,24 @@ function Manager (client) {
img.src = image64
}

this.toSVG = function (callback) {
this.update()

const image64 = this.svg64()
callback(image64, 'export.svg')
}

this.toGRID = function (callback) {
this.update()

const text = client.tool.export()
const file = new Blob([text], { type: 'text/plain' })
callback(URL.createObjectURL(file), 'export.grid')
}

this.toString = () => {
return new XMLSerializer().serializeToString(this.el)
const viewBox = this.minimalViewBox();
if (viewBox !== null) {
this.el.setAttribute('width', viewBox[2] + 'px')
this.el.removeAttribute('height')
this.el.style.width = null
this.el.style.height = null
this.el.setAttribute('viewBox', viewBox.join(' '))
}
const serialized = new XMLSerializer().serializeToString(this.el)
if (viewBox !== null) {
this.el.setAttribute('width', (client.tool.settings.size.width) + 'px')
this.el.setAttribute('height', (client.tool.settings.size.height) + 'px')
this.el.style.width = client.tool.settings.size.width
this.el.style.height = client.tool.settings.size.height
this.el.removeAttribute('viewBox')
}

return serialized
}
}
21 changes: 21 additions & 0 deletions scripts/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function Renderer (client) {
this.drawRulers()
this.drawRender(render) //
this.drawVertices()
this.drawMaskPaths()
this.drawHandles()
this.drawTranslation()
this.drawCursor()
Expand Down Expand Up @@ -102,6 +103,26 @@ function Renderer (client) {
}
}

this.drawMaskPaths = function () {
if (client.tool.style().mask) {
for (const part of client.tool.layer()) {
for (const vertex of part['vertices']) {
this.drawVertex(vertex)
this.drawCursor(vertex, client.tool.style().thickness)
}
}
const path = new Generator(client.tool.layer(), client.tool.style()).toString({ x: 0, y: 0 }, 2)
const style = {
color: client.theme.active.f_med,
thickness: 2,
strokeLinecap: 'round',
strokeLinejoin: 'round',
strokeLineDash: [5, 15]
}
this.drawPath(path, style)
}
}

this.drawGrid = function () {
if (!this.showExtras) { return }

Expand Down
8 changes: 5 additions & 3 deletions scripts/tool.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ function Tool (client) {
this.settings = { size: { width: 600, height: 300 } }
this.layers = [[], [], []]
this.styles = [
{ thickness: 15, strokeLinecap: 'round', strokeLinejoin: 'round', color: '#f00', fill: 'none', mirror_style: 0, transform: 'rotate(45)' },
{ thickness: 15, strokeLinecap: 'round', strokeLinejoin: 'round', color: '#0f0', fill: 'none', mirror_style: 0, transform: 'rotate(45)' },
{ thickness: 15, strokeLinecap: 'round', strokeLinejoin: 'round', color: '#00f', fill: 'none', mirror_style: 0, transform: 'rotate(45)' }
{ thickness: 15, strokeLinecap: 'round', strokeLinejoin: 'round', color: '#f00', fill: 'none', mask: false, mirror_style: 0, transform: 'rotate(45)' },
{ thickness: 15, strokeLinecap: 'round', strokeLinejoin: 'round', color: '#0f0', fill: 'none', mask: false, mirror_style: 0, transform: 'rotate(45)' },
{ thickness: 15, strokeLinecap: 'round', strokeLinejoin: 'round', color: '#00f', fill: 'none', mask: false, mirror_style: 0, transform: 'rotate(45)' }
]
this.vertices = []
this.reqs = { line: 2, arc_c: 2, arc_r: 2, arc_c_full: 2, arc_r_full: 2, bezier: 3, close: 0 }
Expand Down Expand Up @@ -191,6 +191,8 @@ function Tool (client) {
this.style().strokeLinejoin = a[this.i.linejoin % a.length]
} else if (type === 'fill') {
this.style().fill = this.style().fill === 'none' ? this.style().color : 'none'
} else if (type === 'mask') {
this.style().mask = !this.style().mask
} else if (type === 'thickness') {
this.style().thickness = clamp(this.style().thickness + mod, 1, 100)
} else if (type === 'mirror') {
Expand Down