UNPKG

svg-inplace-rasterize

Version:

Rasterize parts of an SVG to keep it animatable while reducing size and improving render time.

144 lines (143 loc) 6.92 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const sharp_1 = __importDefault(require("sharp")); // Load this before librsvg to avoid zlib loading order error const librsvg_prebuilt_1 = require("librsvg-prebuilt"); const stream_1 = require("stream"); const cheerio_1 = __importDefault(require("cheerio")); const image_data_uri_1 = __importDefault(require("image-data-uri")); const cheerio_get_css_selector_1 = __importDefault(require("cheerio-get-css-selector")); class InplaceRasterizer { rasterize(readable, options) { return __awaiter(this, void 0, void 0, function* () { console.log('Starting rasterize'); const originalSvg = yield new Promise((res, rej) => { const chunks = []; readable.on("data", function (chunk) { chunks.push(chunk); }); readable.on("end", function () { res(Buffer.concat(chunks).toString()); }); }); console.log('Cheerio loading original svg'); const $ = cheerio_1.default.load(originalSvg, { xmlMode: true, xml: true, withDomLvl1: false, decodeEntities: false }); cheerio_get_css_selector_1.default.init($); const viewbox = $('svg').attr('viewBox').split(' '); function isCheerio(x) { return !!x.toArray; } console.log('Filtering elements'); let filtered = options.filter($); let elements = isCheerio(filtered) ? filtered.toArray() : filtered; console.log(`Getting roots and selectors for ${elements.length} elements`); const rootsAndSelectors = elements.map((element, i) => { if (i % 25 == 0) console.log(`Creating unique selector for element ${i}`); const selector = $(element).getUniqueSelector(); return { selector, element }; }); console.log('Creating exclusive svgs'); function asyncForEach(array, callback) { return __awaiter(this, void 0, void 0, function* () { for (let index = 0; index < array.length; index++) { yield callback(array[index], index, array); } }); } const stuff = yield asyncForEach(rootsAndSelectors, ({ selector, element }, i) => __awaiter(this, void 0, void 0, function* () { if (i % 25 == 0) console.log(`Creating exclusive svgs for element ${i}`); const els = []; console.log(selector); let el = $(selector); els.push(el.clone()); while ((el = el.parent()) && el.length) { const clone = el.clone(); clone.children(':not(use):not(defs):not(clipPath)').remove(); els.push(clone); } const $sub = cheerio_1.default.load('', { xmlMode: true, xml: true, withDomLvl1: false, decodeEntities: false }); let p = $sub.root(); let lastEl; els.forEach((el, i) => { if (i === 0) { lastEl = el; } else { lastEl.appendTo(el); lastEl = el; } }); lastEl.appendTo(p); let data; const exclusiveSvg = $sub.html(); try { console.log(`Converted to html: ${i} size ${exclusiveSvg.length}`); data = yield this.rsvgConvert(exclusiveSvg, { multiplier: options.multiplier }); console.log(`SVG ${i} converted`); } catch (e) { console.error(`Error in partial ${i} with size ${exclusiveSvg.length}`); throw e; } let uri; if (!options.format || options.format.toLowerCase() === 'png') { uri = image_data_uri_1.default.encode(data, 'PNG'); } else if (options.format.toLowerCase() === 'webp') { uri = image_data_uri_1.default.encode(yield sharp_1.default(data).webp().toBuffer(), 'WEBP'); console.log(`PNG ${i} converted to WEBP (uri length ${uri.length})`); } const totalElement = $(element); const group = $('<g></g>'); const image = $('<image></image>'); const attrs = totalElement.attr(); for (const key in attrs) { group.attr(key, attrs[key]); } image .attr('href', uri) // .attr('href', '' + i) .attr('x', viewbox[0]) .attr('y', viewbox[1]) .attr('width', viewbox[2]) .attr('height', viewbox[3]); $(group).append(image); totalElement.before(group); totalElement.remove(); })); return $.html(); }); } rsvgConvert(orig, options) { return new Promise((res, rej) => { // const svg = new Rsvg(, {keepImageData: true, unlimited: true}, {keepImageData: true, unlimited: true}); const svg = new librsvg_prebuilt_1.Rsvg(); // const svg = new Rsvg({keepImageData: true, unlimited: true}); const readable = new stream_1.Readable(); svg.on('finish', () => { res(svg.render({ format: 'png', width: svg.width * (options.multiplier || 1), height: svg.height * (options.multiplier || 1) }).data); }); readable._read = () => { }; readable.pipe(svg); readable.push(orig); readable.push(null); }); } } exports.InplaceRasterizer = InplaceRasterizer;