1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 | var Transform = require('stream').Transform;
|
6 |
|
7 | var STATE_IDENTIFY = 0;
|
8 | var STATE_PARSE = 1;
|
9 | var STATE_IGNORE = 2;
|
10 |
|
11 |
|
12 | var MAX_DATA_LENGTH = 65536;
|
13 |
|
14 | var SVG_HEADER_RE = /<svg\s[^>]+>/;
|
15 | var SVG_WIDTH_RE = /[^-]\bwidth="([^%]+?)"|[^-]\bwidth='([^%]+?)'/;
|
16 | var SVG_HEIGHT_RE = /\bheight="([^%]+?)"|\bheight='([^%]+?)'/;
|
17 | var SVG_VIEWBOX_RE = /\bview[bB]ox="(.+?)"|\bview[bB]ox='(.+?)'/;
|
18 | var SVG_UNITS_RE = /in$|mm$|cm$|pt$|pc$|px$|em$|ex$/;
|
19 |
|
20 |
|
21 | function isWhiteSpace(chr) {
|
22 | return chr === 0x20 || chr === 0x09 || chr === 0x0D || chr === 0x0A;
|
23 | }
|
24 |
|
25 |
|
26 | function isFinitePositive(val) {
|
27 | return typeof val === 'number' && isFinite(val) && val > 0;
|
28 | }
|
29 |
|
30 | function svgAttrs(str) {
|
31 | var width = str.match(SVG_WIDTH_RE);
|
32 | var height = str.match(SVG_HEIGHT_RE);
|
33 | var viewbox = str.match(SVG_VIEWBOX_RE);
|
34 |
|
35 | return {
|
36 | width: width && (width[1] || width[2]),
|
37 | height: height && (height[1] || height[2]),
|
38 | viewbox: viewbox && (viewbox[1] || viewbox[2])
|
39 | };
|
40 | }
|
41 |
|
42 |
|
43 | function units(str) {
|
44 | if (!SVG_UNITS_RE.test(str)) return 'px';
|
45 |
|
46 | return str.match(SVG_UNITS_RE)[0];
|
47 | }
|
48 |
|
49 |
|
50 | function parseSvg(str) {
|
51 | if (!SVG_HEADER_RE.test(str)) return;
|
52 |
|
53 | var attrs = svgAttrs(str.match(SVG_HEADER_RE)[0]);
|
54 | var width = parseFloat(attrs.width);
|
55 | var height = parseFloat(attrs.height);
|
56 |
|
57 |
|
58 |
|
59 | if (attrs.width && attrs.height) {
|
60 | if (!isFinitePositive(width) || !isFinitePositive(height)) return;
|
61 |
|
62 | return {
|
63 | width: width,
|
64 | height: height,
|
65 | type: 'svg',
|
66 | mime: 'image/svg+xml',
|
67 | wUnits: units(attrs.width),
|
68 | hUnits: units(attrs.height)
|
69 | };
|
70 | }
|
71 |
|
72 |
|
73 |
|
74 | var parts = (attrs.viewbox || '').split(' ');
|
75 | var viewbox = {
|
76 | width: parts[2],
|
77 | height: parts[3]
|
78 | };
|
79 | var vbWidth = parseFloat(viewbox.width);
|
80 | var vbHeight = parseFloat(viewbox.height);
|
81 |
|
82 | if (!isFinitePositive(vbWidth) || !isFinitePositive(vbHeight)) return;
|
83 | if (units(viewbox.width) !== units(viewbox.height)) return;
|
84 |
|
85 | var ratio = vbWidth / vbHeight;
|
86 |
|
87 | if (attrs.width) {
|
88 | if (!isFinitePositive(width)) return;
|
89 |
|
90 | return {
|
91 | width: width,
|
92 | height: width / ratio,
|
93 | type: 'svg',
|
94 | mime: 'image/svg+xml',
|
95 | wUnits: units(attrs.width),
|
96 | hUnits: units(attrs.width)
|
97 | };
|
98 | }
|
99 |
|
100 | if (attrs.height) {
|
101 | if (!isFinitePositive(height)) return;
|
102 |
|
103 | return {
|
104 | width: height * ratio,
|
105 | height: height,
|
106 | type: 'svg',
|
107 | mime: 'image/svg+xml',
|
108 | wUnits: units(attrs.height),
|
109 | hUnits: units(attrs.height)
|
110 | };
|
111 | }
|
112 |
|
113 | return {
|
114 | width: vbWidth,
|
115 | height: vbHeight,
|
116 | type: 'svg',
|
117 | mime: 'image/svg+xml',
|
118 | wUnits: units(viewbox.width),
|
119 | hUnits: units(viewbox.height)
|
120 | };
|
121 | }
|
122 |
|
123 |
|
124 | module.exports = function () {
|
125 | var state = STATE_IDENTIFY;
|
126 | var data_len = 0;
|
127 | var str = '';
|
128 |
|
129 | var parser = new Transform({
|
130 | readableObjectMode: true,
|
131 | transform: function transform(chunk, encoding, next) {
|
132 | switch (state) {
|
133 | case STATE_IDENTIFY:
|
134 | var i = 0, max = chunk.length;
|
135 |
|
136 | while (i < max && isWhiteSpace(chunk[i])) i++;
|
137 |
|
138 | if (i >= max) {
|
139 | data_len += chunk.length;
|
140 |
|
141 | if (data_len > MAX_DATA_LENGTH) {
|
142 | state = STATE_IGNORE;
|
143 | parser.push(null);
|
144 | }
|
145 |
|
146 | } else if (chunk[i] === 0x3c ) {
|
147 | state = STATE_PARSE;
|
148 | return transform(chunk, encoding, next);
|
149 |
|
150 | } else {
|
151 | state = STATE_IGNORE;
|
152 | parser.push(null);
|
153 | }
|
154 |
|
155 | break;
|
156 |
|
157 | case STATE_PARSE:
|
158 | str += chunk.toString();
|
159 |
|
160 | var result = parseSvg(str);
|
161 |
|
162 | if (result) {
|
163 | parser.push(result);
|
164 | parser.push(null);
|
165 | break;
|
166 | }
|
167 |
|
168 | data_len += chunk.length;
|
169 |
|
170 | if (data_len > MAX_DATA_LENGTH) {
|
171 | state = STATE_IGNORE;
|
172 | parser.push(null);
|
173 | }
|
174 |
|
175 | break;
|
176 | }
|
177 |
|
178 | next();
|
179 | },
|
180 |
|
181 | flush: function () {
|
182 | state = STATE_IGNORE;
|
183 | parser.push(null);
|
184 | }
|
185 | });
|
186 |
|
187 | return parser;
|
188 | };
|