Skip to content

Commit

Permalink
Add SVG stream parser
Browse files Browse the repository at this point in the history
  • Loading branch information
rlidwka committed Jun 24, 2016
1 parent 85b036d commit db8cf02
Show file tree
Hide file tree
Showing 4 changed files with 378 additions and 4 deletions.
186 changes: 186 additions & 0 deletions lib/parse_stream/svg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
'use strict';

/* eslint-disable consistent-return */

var once = require('../common').once;
var Transform = require('readable-stream').Transform;

var STATE_IDENTIFY = 0; // look for '<'
var STATE_PARSE = 1; // extract width and height from svg tag
var STATE_IGNORE = 2; // we got all the data we want, skip the rest

// max size for pre-svg-tag comments plus svg tag itself
var MAX_DATA_LENGTH = 65536;

var SVG_HEADER_RE = /<svg\s[^>]+>/;
var SVG_WIDTH_RE = /\bwidth="([^%]+?)"|\bwidth='([^%]+?)'/;
var SVG_HEIGHT_RE = /\bheight="([^%]+?)"|\bheight='([^%]+?)'/;
var SVG_VIEWBOX_RE = /\bviewbox="(.+?)"|\bviewbox='(.+?)'/;
var SVG_UNITS_RE = /in$|mm$|cm$|pt$|pc$|px$|em$|ex$/;


function isWhiteSpace(chr) {
return chr === 0x20 || chr === 0x09 || chr === 0x0D || chr === 0x0A;
}


function svgAttrs(str) {
var width = str.match(SVG_WIDTH_RE);
var height = str.match(SVG_HEIGHT_RE);
var viewbox = str.match(SVG_VIEWBOX_RE);

return {
width: width && (width[1] || width[2]),
height: height && (height[1] || height[2]),
viewbox: viewbox && (viewbox[1] || viewbox[2])
};
}


function units(str) {
if (!SVG_UNITS_RE.test(str)) return 'px';

return str.match(SVG_UNITS_RE)[0];
}


function parseSvg(str) {
if (!SVG_HEADER_RE.test(str)) return;

var attrs = svgAttrs(str.match(SVG_HEADER_RE)[0]);
var width = parseFloat(attrs.width) || 0; // NaN -> 0
var height = parseFloat(attrs.height) || 0; // NaN -> 0

// Extract from direct values

if (attrs.width && attrs.height) {
if (width <= 0 || height <= 0) return;

return {
width: width,
height: height,
type: 'svg',
mime: 'image/svg+xml',
wUnits: units(attrs.width),
hUnits: units(attrs.height)
};
}

// Extract from viewbox

var parts = (attrs.viewbox || '').split(' ');
var viewbox = {
width: parts[2],
height: parts[3]
};
var vbWidth = parseFloat(viewbox.width) || 0; // NaN -> 0
var vbHeight = parseFloat(viewbox.height) || 0; // NaN -> 0

if (vbWidth <= 0 || vbHeight <= 0) return;
if (units(viewbox.width) !== units(viewbox.height)) return;

var ratio = vbWidth / vbHeight;

if (attrs.width) {
if (width <= 0) return;

return {
width: width,
height: width / ratio,
type: 'svg',
mime: 'image/svg+xml',
wUnits: units(attrs.width),
hUnits: units(attrs.width)
};
}

if (attrs.height) {
if (height <= 0) return;

return {
width: height * ratio,
height: height,
type: 'svg',
mime: 'image/svg+xml',
wUnits: units(attrs.height),
hUnits: units(attrs.height)
};
}

return {
width: vbWidth,
height: vbHeight,
type: 'svg',
mime: 'image/svg+xml',
wUnits: units(viewbox.width),
hUnits: units(viewbox.height)
};
}


module.exports = function (input, _callback) {
var callback = once(_callback);
var state = STATE_IDENTIFY;
var data_len = 0;
var str = '';

var parser = new Transform({
transform: function transform(chunk, encoding, next) {
switch (state) {
case STATE_IDENTIFY:
var i = 0, max = chunk.length;

while (i < max && isWhiteSpace(chunk[i])) i++;

if (i >= max) {
data_len += chunk.length;

if (data_len > MAX_DATA_LENGTH) {
state = STATE_IGNORE;
callback();
}

} else if (chunk[i] === 0x3c /* < */) {
state = STATE_PARSE;
return transform(chunk, encoding, next);

} else {
state = STATE_IGNORE;
callback();
}

break;

case STATE_PARSE:
str += chunk.toString();

var result = parseSvg(str);

if (result) {
callback(null, result);
break;
}

data_len += chunk.length;

if (data_len > MAX_DATA_LENGTH) {
state = STATE_IGNORE;
callback();
}

break;
}

next();
},

flush: function () {
state = STATE_IGNORE;
callback();
}
});

input.pipe(parser);

return parser;
};
9 changes: 7 additions & 2 deletions lib/parse_sync/svg.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@

/* eslint-disable consistent-return */

function isWhiteSpace(chr) {
return chr === 0x20 || chr === 0x09 || chr === 0x0D || chr === 0x0A;
}


function canBeSvg(buf) {
var i = 0, max = buf.length;

while (i < max && buf[i] === 0x20) i++;
while (i < max && isWhiteSpace(buf[i])) i++;

if (i === max) return false;
return buf[i] === 0x3c; /* < */
Expand Down Expand Up @@ -72,7 +77,7 @@ module.exports = function (data) {

// Extract from viewbox

var parts = attrs.viewbox.split(' ');
var parts = (attrs.viewbox || '').split(' ');
var viewbox = {
width: parts[2],
height: parts[3]
Expand Down
1 change: 1 addition & 0 deletions lib/parsers_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
jpeg: require('./parse_stream/jpeg'),
png: require('./parse_stream/png'),
psd: require('./parse_stream/psd'),
svg: require('./parse_stream/svg'),
tiff: require('./parse_stream/tiff'),
webp: require('./parse_stream/webp')
};
Loading

0 comments on commit db8cf02

Please sign in to comment.