forked from walle89/svg-stylus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsvg.js
executable file
·243 lines (202 loc) · 5.95 KB
/
svg.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
'use strict';
var _ = require('underscore');
var fs = require('fs');
var path = require('path');
var rework = require('rework');
var xmldoc = require('xmldoc');
var _config = {};
var SVG_PATTERN = new RegExp(
'(\\s?)' +
// url(...)
'(?:svgurl\\((.*?)\\))' + '\\s+' +
// svg(...)
'(?:svg\\(' + '\\s*' +
// { ... } (optional)
'(?:(.*?))?' + '\\s*' +
'\\))'
);
var SELECTOR_PATTERN = new RegExp(
'^' +
// tag name
'([\\w:-]+)' +
// ID (optional)
'(?:#([\\w:-]+))?' +
// attribute (optional)
'(?:\\[' +
// attribute name
'([\\w:-]+)' +
// attribute value (optional)
'(?:=([^\\]]+))?' +
'\\])?' +
'$'
);
/**
* Parse config options
* @param {Object|String} options Options map; falls back to support original `base_path` param
* @returns {undefined} undefined
*/
function parseConfig(options) {
if (typeof options === 'object') {
_config = _.extend(_config, options);
} else if (typeof options === 'string') {
_config.base_path = options;
}
}
/**
* Get SVG as ecoded data
* @param {String} xml_source XML source of the SVG to operate on
* @returns {String} encoded image data
*/
function getStyledSvgAsDataURL(xml_source, style) {
var doc = new xmldoc.XmlDocument(xml_source);
// Populate all SVG styles
style.rules.forEach(function (rule) {
if (rule.declarations) {
rule.selectors.forEach(function (selector) {
var elements = querySelectorAll(doc, selector);
elements.forEach(function (element) {
rule.declarations.forEach(function (declaration) {
element.attr[declaration.property] = declaration.value;
});
});
});
}
});
var svg_buffer = new Buffer(doc.toString(true, true));
return 'data:image/svg+xml;base64,' + svg_buffer.toString('base64');
}
/**
* Get document elements
* @param {Object} root Root element
* @param {String} selector Element selector
* @returns {Array} matched elements
*/
function querySelectorAll(root, selector) {
var selector_path = selector.split(/\s+/);
var elements = [ root ];
var level;
var children = [];
while (level = selector_path.shift()) {
var level_parts = level.match(SELECTOR_PATTERN);
// children by tag name
var matches = _.union.apply(_, elements.map(function (el) {
return el.childrenNamedRecursive(level_parts[1]);
}));
var attr_matches;
// children by ID
if (level_parts[2]) {
attr_matches = _.union.apply(_, elements.map(function (el) {
var value = (level_parts[2] || '').replace(/^["']|["']$/g, '');
return el.childrenWithAttributeRecursive('id', value);
}));
// merge results
matches = _.intersection(matches, attr_matches);
}
// children by attribute
if (level_parts[3]) {
attr_matches = _.union.apply(_, elements.map(function (el) {
var value = (level_parts[4] || '').replace(/^["']|["']$/g, '');
return el.childrenWithAttributeRecursive(level_parts[4], value);
}));
// merge results
matches = _.intersection(matches, attr_matches);
}
children = children.concat(matches);
elements = children;
children = [];
}
return elements;
}
/**
* Get SVG property as JSON
* @param {String} style Style property
* @returns {String} json string
*/
function getSvgPropAsJSON(style) {
var json = '';
if (!style) {
return json;
}
var rules = style.split(',');
var i, len;
for (i = 0, len = rules.length; i < len; i++) {
var parts = rules[i].trim().split(/\s+/);
if (parts.length < 3) {
continue;
}
json += parts[0].replace(/\$/g, '#') + '{' + parts[1] + ':' + parts[2] + '} ';
}
return json;
}
/**
* Replace style declaration value
* String.replace() method
* @param match {String} match Matched substring
* @param match {String} pre_whitespace Original whitespace
* @param match {String} url_match url(...)
* @param match {String} svg_style_json SVG style information
* @returns {String} new declaration value
*/
function replaceDeclarationValue(match, pre_whitespace, url_match, svg_style_json) {
// rewrite param syntax to rework-svg JSON before rework
var svg_style = rework(getSvgPropAsJSON(svg_style_json)).obj.stylesheet;
var xml_source;
if (url_match.indexOf('url(') === 0) { // Assume base64 encoded SVG
// Extract base64 string
var base64string = url_match.replace(/url\(["']?data:image\/svg\+xml;base64,(.*?)["']?\)/, '$1');
// Convert to ASCII
xml_source = new Buffer(base64string, 'base64').toString('ascii');
}
else { // Assume filepath
// Remove quotes
var url = url_match.replace(/^["']|["']$/g, '');
var filename = _config.base_path ? path.join(_config.base_path, url) : url;
xml_source = fs.readFileSync(filename, 'utf8');
}
var svg_data_uri = getStyledSvgAsDataURL(xml_source, svg_style);
return pre_whitespace + 'url(\'' + svg_data_uri + '\')';
}
/**
* Parse style declaration
* @param {Object} declaration Style declaration
* @returns {undefined} undefined
*/
function parseDeclaration(declaration) {
// TODO : allow `background` properties
if (!declaration || declaration.property !== 'background-image') {
return;
}
// allows multiple background images
while (SVG_PATTERN.test(declaration.value)) {
declaration.value = declaration.value.replace(SVG_PATTERN, replaceDeclarationValue);
}
}
/**
* Parse style rule
* @param {Object} rule Style rule
* @returns {undefined} undefined
*/
function parseRule(rule) {
if (rule.declarations) {
rule.declarations.forEach(parseDeclaration);
} else if (rule.rules) {
// nested rules, e.g. media query block
rule.rules.forEach(parseRule);
}
}
/**
* Process styles
* @param {Object} style Style object
* @returns {undefined} undefined
*/
function svgStylus(style) {
if (!style || !style.rules) {
return;
}
style.rules.forEach(parseRule);
}
// Configure and export
module.exports = function (options) {
parseConfig(options);
return svgStylus;
};