gerber-to-svg
Version:
Render individual Gerber / NC drill files as SVGs
250 lines (202 loc) • 6.21 kB
JavaScript
// transform stream to take plotter objects and convert them to an SVG string
'use strict'
var Transform = require('readable-stream').Transform
var inherits = require('inherits')
var isFinite = require('lodash.isfinite')
var reduceShapeArray = require('./_reduce-shape')
var flashPad = require('./_flash-pad')
var createPath = require('./_create-path')
var util = require('./_util')
var render = require('../render')
var shift = util.shift
var maskLayer = util.maskLayer
var createMask = util.createMask
var BLOCK_MODE_OFF = 0
var BLOCK_MODE_DARK = 1
var BLOCK_MODE_CLEAR = 2
var PlotterToSvg = function(id, attributes, createElement, objectMode) {
Transform.call(this, {
writableObjectMode: true,
readableObjectMode: objectMode,
})
this.id = id
this.attributes = attributes
this.defs = []
this.layer = []
this.viewBox = [0, 0, 0, 0]
this.width = 0
this.height = 0
this.units = ''
this._maskId = ''
this._maskBox = []
this._mask = []
this._blockMode = false
this._blockBox = []
this._block = []
this._blockCount = 0
this._blockLayerCount = 0
this._offsets = []
this._clearCount = 0
this._lastLayer = 0
this._blockCount = 0
this._blockCount = 0
this._element = createElement
}
inherits(PlotterToSvg, Transform)
PlotterToSvg.prototype._transform = function(chunk, encoding, done) {
switch (chunk.type) {
case 'shape':
this.defs = this.defs.concat(
reduceShapeArray(this.id, chunk.tool, chunk.shape, this._element)
)
break
case 'pad':
this._draw(flashPad(this.id, chunk.tool, chunk.x, chunk.y, this._element))
break
case 'fill':
this._draw(createPath(chunk.path, null, this._element))
break
case 'stroke':
this._draw(createPath(chunk.path, chunk.width, this._element))
break
case 'polarity':
this._handleNewPolarity(chunk.polarity, chunk.box)
break
case 'repeat':
this._handleNewRepeat(chunk.offsets, chunk.box)
break
case 'size':
this._handleSize(chunk.box, chunk.units)
}
done()
}
PlotterToSvg.prototype._flush = function(done) {
// shut off step repeat finish any in-progress clear layer and/or repeat
this._handleNewRepeat([])
this.push(render(this, this.attributes, this._element))
done()
}
PlotterToSvg.prototype._finishBlockLayer = function() {
// if there's a block, wrap it up, give it an id, and repeat it
if (this._block.length) {
this._blockLayerCount++
var blockLayerId =
this.id + '_block-' + this._blockCount + '-' + this._blockLayerCount
this.defs.push(this._element('g', {id: blockLayerId}, this._block))
this._block = []
}
}
PlotterToSvg.prototype._finishClearLayer = function() {
if (this._maskId) {
this.defs.push(
createMask(this._maskId, this._maskBox, this._mask, this._element)
)
this._maskId = ''
this._maskBox = []
this._mask = []
return true
}
return false
}
PlotterToSvg.prototype._handleNewPolarity = function(polarity, box) {
if (this._blockMode) {
if (this._blockLayerCount === 0 && !this._block.length) {
this._blockMode = polarity === 'dark' ? BLOCK_MODE_DARK : BLOCK_MODE_CLEAR
}
return this._finishBlockLayer()
}
this._clearCount =
polarity === 'clear' ? this._clearCount + 1 : this._clearCount
var maskId = this.id + '_clear-' + this._clearCount
// if clear polarity, wrap the layer and start a mask
if (polarity === 'clear') {
this.layer = [maskLayer(maskId, this.layer, this._element)]
this._maskId = maskId
this._maskBox = box.slice(0)
} else {
// else, finish the mask and add it to the defs
this._finishClearLayer(box)
}
}
PlotterToSvg.prototype._handleNewRepeat = function(offsets, box) {
var endOfBlock = offsets.length === 0
// finish any in progress clear layer and block layer
var wasClear = this._finishClearLayer()
this._finishBlockLayer()
var layer = this.layer
var element = this._element
var blockMode = this._blockMode
var blockLayers = this._blockLayerCount
var blockIdStart = this.id + '_block-' + this._blockCount + '-'
// add dark layers to layer
this._offsets.forEach(function(offset) {
for (var i = blockMode; i <= blockLayers; i += 2) {
layer.push(
element('use', {
'xlink:href': '#' + blockIdStart + i,
x: shift(offset[0]),
y: shift(offset[1]),
})
)
}
})
// if there are clear layers in the block, mask the layer with them
if (blockLayers > 2 - blockMode) {
var maskId = blockIdStart + 'clear'
this.layer = [maskLayer(maskId, layer, this._element)]
this._maskId = maskId
this._maskBox = this._blockBox.slice(0)
this._mask = this._offsets.reduce(function(result, offset) {
var isDark
for (var i = 1; i <= blockLayers; i++) {
isDark = blockMode === BLOCK_MODE_DARK ? i % 2 === 1 : i % 2 === 0
var attr = {
'xlink:href': '#' + blockIdStart + i,
x: shift(offset[0]),
y: shift(offset[1]),
}
if (isDark) {
attr.fill = '#fff'
attr.stroke = '#fff'
}
result.push(element('use', attr))
}
return result
}, [])
wasClear = this._finishClearLayer()
}
// save the offsets
this._offsets = offsets
if (!endOfBlock) {
this._blockMode = !wasClear ? BLOCK_MODE_DARK : BLOCK_MODE_CLEAR
this._blockCount++
this._blockLayerCount = 0
this._blockBox = box.every(isFinite) ? box : [0, 0, 0, 0]
} else {
this._blockMode = BLOCK_MODE_OFF
}
}
PlotterToSvg.prototype._handleSize = function(box, units) {
if (box.every(isFinite)) {
var x = shift(box[0])
var y = shift(box[1])
var width = shift(box[2] - box[0])
var height = shift(box[3] - box[1])
this.viewBox = [x, y, width, height]
this.width = width / 1000
this.height = height / 1000
this.units = units
}
}
PlotterToSvg.prototype._draw = function(object) {
if (!this._blockMode) {
if (!this._maskId) {
this.layer.push(object)
} else {
this._mask.push(object)
}
} else {
this._block.push(object)
}
}
module.exports = PlotterToSvg