forked from 2gis/node-rsvg
-
Notifications
You must be signed in to change notification settings - Fork 6
/
index.js
393 lines (353 loc) · 11.2 KB
/
index.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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
'use strict';
var binding = require('bindings')('rsvg');
var Writable = require('stream').Writable;
var util = require('util');
/**
* Represents one SVG file to be rendered. You can optionally pass the SVG file
* directly as an argument (Buffer or string) to the constructor. Otherwise the
* object is a writable stream and you can pipe a file into it. Optionally you
* can initialize the writable stream with specific options if you pass an
* object.
*
* @see [LibRSVG Default Constructor]{@link
* https://developer.gnome.org/rsvg/2.40/RsvgHandle.html#rsvg-handle-new}
* @see [LibRSVG Constructor From Data]{@link
* https://developer.gnome.org/rsvg/2.40/RsvgHandle.html#rsvg-handle-new-from-data}
* @see [Writable Stream Constructor]{@link
* http://nodejs.org/api/stream.html#stream_new_stream_writable_options}
*
* @constructor
* @param {(Buffer|string|Object)} [buffer] - SVG file.
*/
function Rsvg(buffer) {
var self = this;
// Create proper options for the writable stream super constructor.
var options;
if (Buffer.isBuffer(buffer)) {
options = {};
} else if (typeof(buffer) === 'string') {
buffer = new Buffer(buffer);
options = {};
} else if (buffer === undefined || buffer === null) {
options = {};
} else if (typeof(buffer) === 'object') {
options = buffer;
buffer = null;
} else {
throw new TypeError('Invalid argument: buffer');
}
// Inheritance pattern: Invoke super constructor.
Writable.call(self, options);
// Create new instance of binding.
try {
self.handle = new binding.Rsvg(buffer);
} catch (error) {
throw new Error('Rsvg load failure: ' + error.message);
}
if (buffer) {
process.nextTick(function() {
self.emit('load');
});
}
// When finished piping into this object, we need to tell the binding that by
// invoking the `close()` method.
self.on('finish', function() {
try {
self.handle.close();
} catch (error) {
if (error.message !== self.lastErrorMessage) {
self.lastErrorMessage = error.message;
self.emit('error', new Error('Rsvg close failure: ' + error.message));
}
return;
}
self.emit('load');
});
}
// Inherit from writable stream.
util.inherits(Rsvg, Writable);
/**
* Base URI.
* @member {string}
*/
Rsvg.prototype.baseURI = null;
// Define getter/setter for `baseURI` property.
Object.defineProperty(Rsvg.prototype, 'baseURI', {
configurable: true,
enumerable: true,
get: function() {
return this.handle.getBaseURI();
},
set: function(uri) {
this.handle.setBaseURI(uri);
}
});
/**
* Horizontal resolution. Allowed values: >= 0.
* @deprecated since version 2.0
* @member {number}
*/
Rsvg.prototype.dpiX = 90;
// Define getter/setter for deprecated `dpiX` property.
Object.defineProperty(Rsvg.prototype, 'dpiX', {
configurable: true,
enumerable: true,
get: util.deprecate(function() {
return this.handle.getDPIX();
}, 'Rsvg#dpiX: DPI does not affect rendering.'),
set: util.deprecate(function(dpi) {
this.handle.setDPIX(dpi);
}, 'Rsvg#dpiX: DPI does not affect rendering.')
});
/**
* Vertical resolution. Allowed values: >= 0.
* @deprecated since version 2.0
* @member {number}
*/
Rsvg.prototype.dpiY = 90;
// Define getter/setter for deprecated `dpiY` property.
Object.defineProperty(Rsvg.prototype, 'dpiY', {
configurable: true,
enumerable: true,
get: util.deprecate(function() {
return this.handle.getDPIY();
}, 'Rsvg#dpiY: DPI does not affect rendering.'),
set: util.deprecate(function(dpi) {
this.handle.setDPIY(dpi);
}, 'Rsvg#dpiY: DPI does not affect rendering.')
});
/**
* Image width. Always integer.
* @readonly
* @member {number}
*/
Rsvg.prototype.width = 0;
// Define getter for `width` property.
Object.defineProperty(Rsvg.prototype, 'width', {
configurable: true,
enumerable: true,
get: function() {
return this.handle.getWidth();
}
});
/**
* Image height. Always integer.
* @readonly
* @member {number}
*/
Rsvg.prototype.height = 0;
// Define getter for `height` property.
Object.defineProperty(Rsvg.prototype, 'height', {
configurable: true,
enumerable: true,
get: function() {
return this.handle.getHeight();
}
});
/**
* @see [Node.JS API]{@link
* http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1}
* @private
*/
Rsvg.prototype._write = function(data, encoding, callback) {
try {
this.handle.write(data);
} catch (error) {
this.lastErrorMessage = error.message;
callback(new Error('Rsvg write failure: ' + error.message));
return;
}
callback();
};
/**
* Get the DPI for the outgoing pixbuf.
*
* @deprecated since version 2.0
* @returns {{x: number, y: number}}
*/
Rsvg.prototype.getDPI = util.deprecate(function() {
return this.handle.getDPI();
}, 'Rsvg#getDPI(): DPI does not affect rendering.');
/**
* Set the DPI for the outgoing pixbuf. Common values are 75, 90, and 300 DPI.
* Passing null to x or y will reset the DPI to whatever the default value
* happens to be (usually 90). You can set both x and y by specifying only the
* first argument.
*
* @deprecated since version 2.0
* @param {number} x - Horizontal resolution.
* @param {number} [y] - Vertical resolution. Set to the same as X if left out.
*/
Rsvg.prototype.setDPI = util.deprecate(function(x, y) {
this.handle.setDPI(x, y);
}, 'Rsvg#setDPI(): DPI does not affect rendering.');
/**
* Get the SVG's size or the size/position of a subelement if id is given. The
* id must begin with "#".
*
* @param {string} [id] - Subelement to determine the size and position of.
* @returns {{width: number, height: number, x: number, y: number}}
*/
Rsvg.prototype.dimensions = function(id) {
return this.handle.dimensions(id);
};
/**
* Checks whether the subelement with given id exists in the SVG document.
*
* @param {string} id - Subelement to check existence of.
* @returns {boolean}
*/
Rsvg.prototype.hasElement = function(id) {
return this.handle.hasElement(id);
};
/**
* Find the drawing area, ie. the smallest area that has image content in the
* SVG document.
*
* @returns {{width: number, height: number, x: number, y: number}}
*/
Rsvg.prototype.autocrop = function() {
var area = this.handle.autocrop();
area.x = area.x.toFixed(3) * 1;
area.y = area.y.toFixed(3) * 1;
area.width = area.width.toFixed(3) * 1;
area.height = area.height.toFixed(3) * 1;
return area;
};
/**
* Base render method. Valid high-level formats are: png, pdf, svg, raw. You
* can also specify the pixel structure of raw images: argb32 (default), rgb24,
* a8, a1, rgb16_565, and rgb30 (only enabled for Cairo >= 1.12). You can read
* more about the low-level pixel formats in the [Cairo Documentation]{@link
* http://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-t}.
*
* If the element property is given, only that subelement is rendered.
*
* The PNG format is the slowest of them all, since it takes time to encode the
* image as a PNG buffer.
*
* @param {Object} [options] - Rendering options.
* @param {string} [options.format] - One of the formats listed above.
* @param {number} [options.width] - Output image width, should be an integer.
* @param {number} [options.height] - Output image height, should be an integer.
* @param {string} [options.id] - Subelement to render.
* @returns {{data: Buffer, format: string, width: number, height: number}}
*/
Rsvg.prototype.render = function(options) {
if (arguments.length > 1 || typeof(options) !== 'object') {
return this._renderArgs.apply(this, arguments);
}
options = options || {};
var img = this.handle.render(
options.width,
options.height,
options.format,
options.id
);
if (this.width + this.height > 0 && img.data.length == 0) {
// sometimes render fails and returns zero-sized buffer, see zerobuffer test
// just rerender image
return this.render(options);
}
return img;
};
/**
* @deprecated since version 2.0
* @private
*/
Rsvg.prototype._renderArgs = util.deprecate(function(width, height, format, id) {
return this.handle.render(width, height, format ? format.toLowerCase() : null, id);
}, 'Rsvg#render(): Call render({ format, width, height, ... }) instead.');
/**
* Render the SVG as a raw memory buffer image. This can be used to create an
* image that is imported into other image libraries. This render method is
* usually very fast.
*
* The pixel format is ARGB and each pixel is 4 bytes, ie. the buffer size is
* width*height*4. There are no memory "spaces" between rows in the image, like
* there can be when calling the base render method with pixel formats like A8.
*
* @deprecated since version 2.0
* @param {number} width - Output image width, should be an integer.
* @param {number} height - Output image height, should be an integer.
* @param {string} [id] - Subelement to render.
* @returns {{data: Buffer, format: string, pixelFormat: string, width: number, height: number}}
*/
Rsvg.prototype.renderRaw = util.deprecate(function(width, height, id) {
return this.render({
format: 'raw',
width: width,
height: height,
element: id
});
}, 'Rsvg#renderRaw(): Call render({ format: "raw" }) instead.');
/**
* Render the SVG as a PNG image.
*
* @deprecated since version 2.0
* @param {number} width - Output image width, should be an integer.
* @param {number} height - Output image height, should be an integer.
* @param {string} [id] - Subelement to render.
* @returns {{data: Buffer, format: string, width: number, height: number}}
*/
Rsvg.prototype.renderPNG = util.deprecate(function(width, height, id) {
return this.render({
format: 'png',
width: width,
height: height,
element: id
});
}, 'Rsvg#renderPNG(): Call render({ format: "png" }) instead.');
/**
* Render the SVG as a PDF document.
*
* @deprecated since version 2.0
* @param {number} width - Output document width, should be an integer.
* @param {number} height - Output document height, should be an integer.
* @param {string} [id] - Subelement to render.
* @returns {{data: Buffer, format: string, width: number, height: number}}
*/
Rsvg.prototype.renderPDF = util.deprecate(function(width, height, id) {
return this.render({
format: 'pdf',
width: width,
height: height,
element: id
});
}, 'Rsvg#renderPDF(): Call render({ format: "pdf" }) instead.');
/**
* Render the SVG as an SVG. This seems superfluous, but it can be used to
* normalize the input SVG. However you can not be sure that the resulting SVG
* file is smaller than the input. It's not a SVG compression engine. You can
* be sure that the output SVG follows a more stringent structure.
*
* @deprecated since version 2.0
* @param {number} width - Output document width, should be an integer.
* @param {number} height - Output document height, should be an integer.
* @param {string} [id] - Subelement to render.
* @returns {{data: string, format: string, width: number, height: number}}
*/
Rsvg.prototype.renderSVG = util.deprecate(function(width, height, id) {
return this.render({
format: 'svg',
width: width,
height: height,
element: id
});
}, 'Rsvg#renderSVG(): Call render({ format: "svg" }) instead.');
/**
* String representation of this SVG render object.
* @returns {string}
*/
Rsvg.prototype.toString = function() {
var obj = {};
var baseURI = this.baseURI;
if (baseURI) {
obj.baseURI = baseURI;
}
obj.width = this.width;
obj.height = this.height;
return '{ [' + this.constructor.name + ']' + util.inspect(obj).slice(1);
};
// Export the Rsvg object.
exports.Rsvg = Rsvg;