@ioffice/svg2png
Advanced tools
Comparing version 0.0.1-beta.1811291558 to 0.0.1-beta.1812111332
@@ -19,9 +19,9 @@ import { Options } from 'generic-pool'; | ||
/** | ||
* A configuration object that can be used to initialize the pool of browsers. | ||
* A global configuration object for the Svg2png static class. | ||
*/ | ||
interface ISvg2pngPoolConfig { | ||
interface ISvg2pngConfig { | ||
/** | ||
* Our custom configuration which is used to specify the maximum number of uses for each browser. | ||
*/ | ||
config: IPuppeteerPoolConfig; | ||
puppeteerPoolConfig: IPuppeteerPoolConfig; | ||
/** | ||
@@ -75,2 +75,2 @@ * The options to be used when a new browser gets launched. | ||
} | ||
export { IConfig, IDimensions, IPuppeteerPoolConfig, ISvg2pngPoolConfig, }; | ||
export { IConfig, IDimensions, IPuppeteerPoolConfig, ISvg2pngConfig, }; |
{ | ||
"name": "@ioffice/svg2png", | ||
"description": "Converts SVGs to PNGs, using Chromium", | ||
"version": "0.0.1-beta.1811291558", | ||
"version": "0.0.1-beta.1812111332", | ||
"author": "Orignal Author: Domenic Denicola (https://domenic.me)", | ||
@@ -23,3 +23,3 @@ "contributors": [ | ||
"generic-pool": "^3.4.2", | ||
"puppeteer": "1.7.0", | ||
"puppeteer": "1.11.0", | ||
"sharp": "^0.19.0", | ||
@@ -39,3 +39,3 @@ "tslib": "^1.9.3", | ||
"@types/progress": "2.0.1", | ||
"@types/puppeteer": "1.5.0", | ||
"@types/puppeteer": "1.11.1", | ||
"@types/request-promise": "4.1.41", | ||
@@ -42,0 +42,0 @@ "@types/semver": "5.5.0", |
@@ -52,3 +52,3 @@ #!/usr/bin/env node | ||
inputFilename = argv._[0]; | ||
outputFilename = argv.output || path.basename(inputFilename, '.svg') + ".png"; | ||
outputFilename = argv.output || path.basename(inputFilename, '.svg') + '.png'; | ||
return [4 /*yield*/, svg2png_1.svg2png({ | ||
@@ -55,0 +55,0 @@ width: +argv.width, |
/// <reference types="node" /> | ||
import { Page } from 'puppeteer'; | ||
import { IConfig, IDimensions, ISvg2pngPoolConfig } from './interfaces'; | ||
import { IConfig, IDimensions, ISvg2pngConfig } from './interfaces'; | ||
/** | ||
@@ -21,2 +20,3 @@ * The possible status of a conversion. | ||
declare class Svg2png { | ||
private static inProgress; | ||
private static configuration; | ||
@@ -32,3 +32,6 @@ private static pageCloseErrors; | ||
private cancellingReason; | ||
private lastTic; | ||
constructor(config: IConfig); | ||
tic(): void; | ||
toc(): number; | ||
/** | ||
@@ -39,2 +42,7 @@ * Returns an array with the conversion ids that failed to close the browser page. | ||
/** | ||
* Provides an array of times (in milliseconds) which a conversion has taken up to the time | ||
* that the method was called. | ||
*/ | ||
static getTimeSpentOnConversions(): number[]; | ||
/** | ||
* To be used before any call to `svg2png`. This will override the settings used to create the | ||
@@ -45,3 +53,3 @@ * singleton pool of browsers. | ||
*/ | ||
static setPoolConfig(options: ISvg2pngPoolConfig): void; | ||
static setConfiguration(options: ISvg2pngConfig): void; | ||
private static getPool; | ||
@@ -87,3 +95,2 @@ /** | ||
private convertInBrowser; | ||
private cleanUp; | ||
/** | ||
@@ -94,2 +101,3 @@ * Obtain the page with the svg loaded. | ||
private loadPage; | ||
private navigateToSource; | ||
private setGetDimensions; | ||
@@ -99,15 +107,4 @@ private closePage; | ||
private stitchBlocks; | ||
/** | ||
* Sets the dimensions of the svg. The dimensions passed in must be positive numbers. The same | ||
* goes for the `scale` value. It is not possible to have an infinite picture (scale of 0). | ||
* Returns of promise which resolves to an array of strings stating the operations that took | ||
* place. | ||
*/ | ||
setDimensions(page: Page, dimensions: Partial<IDimensions>): Promise<string[]>; | ||
/** | ||
* Obtain the dimensions of the svg. Note that the scale property has been arbitrarily set 1. | ||
*/ | ||
getDimensions(page: Page): Promise<IDimensions>; | ||
} | ||
declare function svg2png(config: IConfig): Promise<Buffer>; | ||
export { Status, IDimensions, IConfig, Svg2png, svg2png, }; |
190
svg2png.js
@@ -6,2 +6,3 @@ "use strict"; | ||
var sharp = require("sharp"); | ||
var page_utils_1 = require("./page-utils"); | ||
var puppeteer_pool_1 = require("./puppeteer-pool"); | ||
@@ -31,2 +32,3 @@ /** | ||
this.cancellingReason = ''; | ||
this.lastTic = 0; | ||
this.id = ++Svg2png.idCounter; | ||
@@ -40,3 +42,10 @@ var opt = Object.assign({}, config); | ||
this.options = opt; | ||
Svg2png.inProgress[this.id] = Date.now(); | ||
} | ||
Svg2png.prototype.tic = function () { | ||
this.lastTic = Date.now(); | ||
}; | ||
Svg2png.prototype.toc = function () { | ||
return Date.now() - this.lastTic; | ||
}; | ||
/** | ||
@@ -49,2 +58,10 @@ * Returns an array with the conversion ids that failed to close the browser page. | ||
/** | ||
* Provides an array of times (in milliseconds) which a conversion has taken up to the time | ||
* that the method was called. | ||
*/ | ||
Svg2png.getTimeSpentOnConversions = function () { | ||
var ids = Object.keys(Svg2png.inProgress); | ||
return ids.map(function (id) { return Date.now() - Svg2png.inProgress[id]; }); | ||
}; | ||
/** | ||
* To be used before any call to `svg2png`. This will override the settings used to create the | ||
@@ -55,6 +72,6 @@ * singleton pool of browsers. | ||
*/ | ||
Svg2png.setPoolConfig = function (options) { | ||
Svg2png.setConfiguration = function (options) { | ||
if (!Svg2png.pool) { | ||
this.configuration = { | ||
config: tslib_1.__assign({}, options.config, { maxUses: 1, validator: function () { return Promise.resolve(true); } }), | ||
puppeteerPoolConfig: tslib_1.__assign({}, options.puppeteerPoolConfig, { maxUses: 1, validator: function () { return Promise.resolve(true); } }), | ||
puppeteerlaunchOptions: tslib_1.__assign({}, options.puppeteerlaunchOptions, { args: ['--no-sandbox', '--disable-setuid-sandbox'] }), | ||
@@ -70,7 +87,7 @@ genericPoolConfig: tslib_1.__assign({}, options.genericPoolConfig, { min: 1, max: 2, idleTimeoutMillis: 5000, testOnBorrow: true }), | ||
* Returns the singleton pool of browsers. This is made as a method so that we only create | ||
* a pool the moment we need it. (trying to avoid side effects from loading the file). | ||
* a pool the moment we need it. (trying to avoid side effects from loading the svg2png module). | ||
*/ | ||
Svg2png.getPool = function () { | ||
if (!Svg2png.pool) { | ||
Svg2png.pool = puppeteer_pool_1.createPuppeteerPool(Svg2png.configuration.config, Svg2png.configuration.puppeteerlaunchOptions, Svg2png.configuration.genericPoolConfig); | ||
Svg2png.pool = puppeteer_pool_1.createPuppeteerPool(Svg2png.configuration.puppeteerPoolConfig, Svg2png.configuration.puppeteerlaunchOptions, Svg2png.configuration.genericPoolConfig); | ||
} | ||
@@ -85,3 +102,3 @@ return Svg2png.pool; | ||
Svg2png.prototype.cancelConversion = function (reason) { | ||
if (!this.cancellingReason) { | ||
if (!this.cancellingReason && this.status !== Status.CANCELLED) { | ||
this.cancellingReason = reason; | ||
@@ -188,2 +205,3 @@ this.status = Status.CANCELLED; | ||
this.log('SVG2PNG::success'); | ||
delete Svg2png.inProgress[this.id]; | ||
return [2 /*return*/, result]; | ||
@@ -198,2 +216,3 @@ case 2: | ||
}; | ||
delete Svg2png.inProgress[this.id]; | ||
return [2 /*return*/, Promise.reject(err_2)]; | ||
@@ -213,3 +232,3 @@ case 3: return [2 /*return*/]; | ||
case 0: | ||
timeout = this.options.conversionTimeout || 30000; | ||
timeout = this.options.conversionTimeout || 300000; | ||
this.log('setting timeout', { conversionId: this.id, timeout: timeout }); | ||
@@ -239,6 +258,8 @@ timeoutHandle = setTimeout(function () { | ||
err_3 = _a.sent(); | ||
this.cleanUp(timeoutHandle); | ||
this.log('clearing timeout due to caught error'); | ||
clearTimeout(timeoutHandle); | ||
return [2 /*return*/, reject(err_3)]; | ||
case 4: | ||
this.cleanUp(timeoutHandle); | ||
this.log('clearing timeout'); | ||
clearTimeout(timeoutHandle); | ||
resolve(buffer); | ||
@@ -250,11 +271,2 @@ return [2 /*return*/]; | ||
}; | ||
Svg2png.prototype.cleanUp = function (timeoutHandle) { | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
return tslib_1.__generator(this, function (_a) { | ||
this.log('clearing timeout'); | ||
clearTimeout(timeoutHandle); | ||
return [2 /*return*/]; | ||
}); | ||
}); | ||
}; | ||
/** | ||
@@ -266,3 +278,3 @@ * Obtain the page with the svg loaded. | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
var page, resp, err_4; | ||
var page, err_4; | ||
return tslib_1.__generator(this, function (_a) { | ||
@@ -276,19 +288,12 @@ switch (_a.label) { | ||
page = _a.sent(); | ||
this.status = Status.PENDING; | ||
return [4 /*yield*/, this.throwIfCancelled(page)]; | ||
case 2: | ||
_a.sent(); | ||
this.status = Status.PENDING; | ||
this.tic(); | ||
this.log("navigating to page", { url: this.source }); | ||
return [4 /*yield*/, page.goto(this.source, { | ||
waitUntil: ['load', 'domcontentloaded', 'networkidle0'], | ||
timeout: this.options.navigationTimeout, | ||
})]; | ||
return [4 /*yield*/, this.navigateToSource(page)]; | ||
case 3: | ||
resp = _a.sent(); | ||
if (!resp) { | ||
return [2 /*return*/, this.failure(new Error('obtained null response from `page.goto`'))]; | ||
} | ||
if (!resp.ok()) { | ||
return [2 /*return*/, this.failure(new Error("navigation status: " + resp.status()))]; | ||
} | ||
_a.sent(); | ||
this.log(" " + this.toc() + "ms"); | ||
return [4 /*yield*/, this.throwIfCancelled(page)]; | ||
@@ -307,2 +312,24 @@ case 4: | ||
}; | ||
Svg2png.prototype.navigateToSource = function (page) { | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
var resp; | ||
return tslib_1.__generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, page.goto(this.source, { | ||
waitUntil: ['load', 'domcontentloaded', 'networkidle0'], | ||
timeout: this.options.navigationTimeout, | ||
})]; | ||
case 1: | ||
resp = _a.sent(); | ||
if (!resp) { | ||
return [2 /*return*/, this.failure(new Error('obtained null response from `page.goto`'))]; | ||
} | ||
if (!resp.ok()) { | ||
return [2 /*return*/, this.failure(new Error("navigation status: " + resp.status()))]; | ||
} | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
Svg2png.prototype.setGetDimensions = function (page) { | ||
@@ -320,3 +347,3 @@ return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
}); | ||
return [4 /*yield*/, this.setDimensions(page, this.options)]; | ||
return [4 /*yield*/, page_utils_1.setDimensions(page, this.options)]; | ||
case 1: | ||
@@ -333,3 +360,3 @@ actions = _a.sent(); | ||
this.log('getting dimensions'); | ||
return [4 /*yield*/, this.getDimensions(page)]; | ||
return [4 /*yield*/, page_utils_1.getDimensions(page)]; | ||
case 4: | ||
@@ -446,3 +473,3 @@ dimensions = _a.sent(); | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
var totalChunks, chunks, ypos, chunk, clipHeight, screenshot, buffer, err_9, channels, bufferSize, composite, err_10; | ||
var totalChunks, chunks, ypos, chunk, clipHeight, screenshot, buffer, err_9, channels, bufferSize, composite, result, err_10; | ||
return tslib_1.__generator(this, function (_a) { | ||
@@ -457,2 +484,3 @@ switch (_a.label) { | ||
if (!(ypos < height)) return [3 /*break*/, 9]; | ||
this.tic(); | ||
this.log("processing " + chunk + "/" + totalChunks); | ||
@@ -480,2 +508,3 @@ clipHeight = Math.min(height - ypos, maxScreenshotHeight); | ||
buffer = _a.sent(); | ||
this.log(" " + this.toc() + "ms"); | ||
return [4 /*yield*/, this.throwIfCancelled(page)]; | ||
@@ -502,2 +531,5 @@ case 6: | ||
_a.trys.push([10, 12, , 13]); | ||
// We already have everything to create the image. If we proceed can we still have a way | ||
// to cancel the operation? | ||
this.tic(); | ||
return [4 /*yield*/, sharp(composite, { | ||
@@ -509,7 +541,7 @@ raw: { | ||
}, | ||
}).png().toBuffer()]; | ||
case 11: | ||
// We already have everything to create the image. If we proceed can we still have a way | ||
// to cancel the operation? | ||
return [2 /*return*/, _a.sent()]; | ||
}).limitInputPixels(false).png().toBuffer()]; | ||
case 11: | ||
result = _a.sent(); | ||
this.log(" " + this.toc() + "ms"); | ||
return [2 /*return*/, result]; | ||
case 12: | ||
@@ -524,82 +556,6 @@ err_10 = _a.sent(); | ||
}; | ||
/** | ||
* Sets the dimensions of the svg. The dimensions passed in must be positive numbers. The same | ||
* goes for the `scale` value. It is not possible to have an infinite picture (scale of 0). | ||
* Returns of promise which resolves to an array of strings stating the operations that took | ||
* place. | ||
*/ | ||
Svg2png.prototype.setDimensions = function (page, dimensions) { | ||
if (!dimensions.width && !dimensions.height && !dimensions.scale) { | ||
return Promise.resolve(['nothing to set']); | ||
} | ||
return page.evaluate(function (_a) { | ||
var width = _a.width, height = _a.height, scale = _a.scale; | ||
var el = document.querySelector('svg'); | ||
if (!el) { | ||
return Promise.reject(new Error('setDimensions: no svg element found')); | ||
} | ||
var actions = []; | ||
if (width) { | ||
el.setAttribute('width', width + "px"); | ||
actions.push("set width to " + width + "px"); | ||
} | ||
else { | ||
el.removeAttribute('width'); | ||
actions.push("removed width"); | ||
} | ||
if (height) { | ||
el.setAttribute('height', height + "px"); | ||
actions.push("set height to " + height + "px"); | ||
} | ||
else { | ||
el.removeAttribute('height'); | ||
actions.push("removed height"); | ||
} | ||
if (scale) { | ||
var viewBoxWidth = el.viewBox.animVal.width; | ||
var viewBoxHeight = el.viewBox.animVal.height; | ||
var scaledWidth = viewBoxWidth / scale; | ||
var scaledHeight = viewBoxHeight / scale; | ||
el.setAttribute('width', scaledWidth + 'px'); | ||
el.setAttribute('height', scaledHeight + 'px'); | ||
actions.push("set scaled dimensions to [" + scaledWidth + "px, " + scaledHeight + "px]"); | ||
el.removeAttribute('clip-path'); | ||
/* It might eventually be necessary to scale the clip path of the root svg element | ||
const clipPathEl = document.querySelector("svg > clipPath > path"); | ||
clipPathEl.setAttribute("d", `M0 0v${scaledHeight}h${scaledWidth}V0z`) | ||
*/ | ||
} | ||
return Promise.resolve(actions); | ||
}, dimensions); | ||
}; | ||
/** | ||
* Obtain the dimensions of the svg. Note that the scale property has been arbitrarily set 1. | ||
*/ | ||
Svg2png.prototype.getDimensions = function (page) { | ||
return page.evaluate(function () { | ||
var el = document.querySelector('svg'); | ||
if (!el) { | ||
return Promise.reject(new Error('getDimensions: no svg element found')); | ||
} | ||
var widthIsPercent = (el.getAttribute('width') || '').endsWith('%'); | ||
var heightIsPercent = (el.getAttribute('height') || '').endsWith('%'); | ||
var width = !widthIsPercent && parseFloat(el.getAttribute('width') || '0'); | ||
var height = !heightIsPercent && parseFloat(el.getAttribute('height') || '0'); | ||
if (width && height) { | ||
return Promise.resolve({ width: width, height: height, scale: 1 }); | ||
} | ||
var viewBoxWidth = el.viewBox.animVal.width; | ||
var viewBoxHeight = el.viewBox.animVal.height; | ||
if (width && viewBoxHeight) { | ||
return Promise.resolve({ width: width, height: width * viewBoxHeight / viewBoxWidth, scale: 1 }); | ||
} | ||
if (height && viewBoxWidth) { | ||
return Promise.resolve({ width: height * viewBoxWidth / viewBoxHeight, height: height, scale: 1 }); | ||
} | ||
return Promise.reject(new Error('getDimensions: no width/height found')); | ||
}); | ||
}; | ||
// Default configuration. Can be overriden by using `Svg2Png.setPoolConfig`. | ||
Svg2png.inProgress = {}; | ||
// Default configuration. Can be overriden by using `Svg2Png.setConfiguration`. | ||
Svg2png.configuration = { | ||
config: { | ||
puppeteerPoolConfig: { | ||
maxUses: 1, | ||
@@ -606,0 +562,0 @@ validator: function () { return Promise.resolve(true); }, |
55543
18
1158
+ Addeddebug@4.4.0(transitive)
+ Addedpuppeteer@1.11.0(transitive)
+ Addedws@6.2.3(transitive)
- Removedpuppeteer@1.7.0(transitive)
- Removedws@5.2.4(transitive)
Updatedpuppeteer@1.11.0