"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CanvasService = void 0; const tsyringe_1 = require("tsyringe"); const civkit_1 = require("civkit"); const promises_1 = require("fs/promises"); const logger_1 = require("./logger"); const temp_file_1 = require("./temp-file"); const worker_threads_1 = require("worker_threads"); const path_1 = __importDefault(require("path")); const threaded_1 = require("./threaded"); const downloadFile = async (uri) => { const resp = await fetch(uri); if (!(resp.ok && resp.body)) { throw new Error(`Unexpected response ${resp.statusText}`); } const contentLength = parseInt(resp.headers.get('content-length') || '0'); if (contentLength > 1024 * 1024 * 100) { throw new Error('File too large'); } const buff = await resp.arrayBuffer(); return { buff, contentType: resp.headers.get('content-type') }; }; let CanvasService = class CanvasService extends civkit_1.AsyncService { constructor(temp, globalLogger) { super(...arguments); this.temp = temp; this.globalLogger = globalLogger; this.logger = this.globalLogger.child({ service: this.constructor.name }); } async init() { await this.dependencyReady(); if (!worker_threads_1.isMainThread) { const { createSvg2png, initialize } = require('svg2png-wasm'); const wasmBuff = await (0, promises_1.readFile)(path_1.default.resolve(path_1.default.dirname(require.resolve('svg2png-wasm')), '../svg2png_wasm_bg.wasm')); const fontBuff = await (0, promises_1.readFile)(path_1.default.resolve(__dirname, '../../licensed/SourceHanSansSC-Regular.otf')); await initialize(wasmBuff); this.svg2png = createSvg2png({ fonts: [Uint8Array.from(fontBuff)], defaultFontFamily: { serifFamily: 'Source Han Sans SC', sansSerifFamily: 'Source Han Sans SC', cursiveFamily: 'Source Han Sans SC', fantasyFamily: 'Source Han Sans SC', monospaceFamily: 'Source Han Sans SC', } }); } this.canvas = require('@napi-rs/canvas'); this.emit('ready'); } async renderSvgToPng(svgContent) { return this.svg2png(svgContent, { backgroundColor: '#D3D3D3' }); } async _loadImage(input) { let buff; let contentType; do { if (typeof input === 'string') { if (input.startsWith('data:')) { const firstComma = input.indexOf(','); const header = input.slice(0, firstComma); const data = input.slice(firstComma + 1); const encoding = header.split(';')[1]; contentType = header.split(';')[0].split(':')[1]; if (encoding?.startsWith('base64')) { buff = Buffer.from(data, 'base64'); } else { buff = Buffer.from(decodeURIComponent(data), 'utf-8'); } break; } if (input.startsWith('http')) { const r = await downloadFile(input); buff = Buffer.from(r.buff); contentType = r.contentType; break; } } if (Buffer.isBuffer(input)) { buff = input; const mime = await (0, civkit_1.mimeOf)(buff); contentType = `${mime.mediaType}/${mime.subType}`; break; } throw new civkit_1.ParamValidationError('Invalid input'); } while (false); if (!buff) { throw new civkit_1.ParamValidationError('Invalid input'); } if (contentType?.includes('svg')) { buff = await this.renderSvgToPng(buff.toString('utf-8')); } const img = await this.canvas.loadImage(buff); Reflect.set(img, 'contentType', contentType); return img; } async loadImage(uri) { const t0 = Date.now(); try { const theImage = await this._loadImage(uri); const t1 = Date.now(); this.logger.debug(`Image loaded in ${t1 - t0}ms`); return theImage; } catch (err) { if (err?.message?.includes('Unsupported image type') || err?.message?.includes('unsupported')) { this.logger.warn(`Failed to load image ${uri.slice(0, 128)}`, { err }); throw new civkit_1.SubmittedDataMalformedError(`Unknown image format for ${uri.slice(0, 128)}`); } throw err; } } fitImageToSquareBox(image, size = 1024) { // this.logger.debug(`Fitting image(${ image.width }x${ image.height }) to ${ size } box`); // const t0 = Date.now(); if (image.width <= size && image.height <= size) { if (image instanceof this.canvas.Canvas) { return image; } const canvasInstance = this.canvas.createCanvas(image.width, image.height); const ctx = canvasInstance.getContext('2d'); ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvasInstance.width, canvasInstance.height); // this.logger.debug(`No need to resize, copied to canvas in ${ Date.now() - t0 } ms`); return canvasInstance; } const aspectRatio = image.width / image.height; const resizedWidth = Math.round(aspectRatio > 1 ? size : size * aspectRatio); const resizedHeight = Math.round(aspectRatio > 1 ? size / aspectRatio : size); const canvasInstance = this.canvas.createCanvas(resizedWidth, resizedHeight); const ctx = canvasInstance.getContext('2d'); ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, resizedWidth, resizedHeight); // this.logger.debug(`Resized to ${ resizedWidth }x${ resizedHeight } in ${ Date.now() - t0 } ms`); return canvasInstance; } corpImage(image, x, y, w, h) { // this.logger.debug(`Cropping image(${ image.width }x${ image.height }) to ${ w }x${ h } at ${ x },${ y } `); // const t0 = Date.now(); const canvasInstance = this.canvas.createCanvas(w, h); const ctx = canvasInstance.getContext('2d'); ctx.drawImage(image, x, y, w, h, 0, 0, w, h); // this.logger.debug(`Crop complete in ${ Date.now() - t0 } ms`); return canvasInstance; } canvasToDataUrl(canvas, mimeType) { // this.logger.debug(`Exporting canvas(${ canvas.width }x${ canvas.height })`); // const t0 = Date.now(); return canvas.toDataURLAsync((mimeType || 'image/png')); } async canvasToBuffer(canvas, mimeType) { // this.logger.debug(`Exporting canvas(${ canvas.width }x${ canvas.height })`); // const t0 = Date.now(); return canvas.toBuffer((mimeType || 'image/png')); } }; exports.CanvasService = CanvasService; __decorate([ (0, threaded_1.Threaded)(), __metadata("design:type", Function), __metadata("design:paramtypes", [String]), __metadata("design:returntype", Promise) ], CanvasService.prototype, "renderSvgToPng", null); exports.CanvasService = CanvasService = __decorate([ (0, tsyringe_1.singleton)(), __metadata("design:paramtypes", [temp_file_1.TempFileManager, logger_1.GlobalLogger]) ], CanvasService); const instance = tsyringe_1.container.resolve(CanvasService); exports.default = instance; //# sourceMappingURL=canvas.js.map