import gm from 'gm'; import axios from 'axios'; import sizeOf from 'image-size'; import path from 'node:path'; import fs from 'node:fs'; import PDFDocument from 'pdfkit'; import log from 'electron-log/renderer'; import { getImagicPath, getTempPath, makeDirSync, getGmFontPath, getConfigData, } from './utils'; import { DrawTrackItem, DrawTrackCircleOption, DrawTrackLineOption, DrawTrackTextOption, } from './types'; // macos install gm imagemagick https://github.com/aheckmann/gm/blob/master/README.md const gmInst = process.platform === 'win32' ? gm.subClass({ imageMagick: '7+', appPath: getImagicPath(), }) : gm.subClass({ imageMagick: '7+' }); function cropImage(imgPath: string): Promise { return new Promise((resolve, reject) => { const outpath = path.join(getTempPath(), '001.png'); gmInst(imgPath) .crop(500, 200, 0, 0) .write(outpath, (err) => { if (!err) { return resolve(outpath); } return reject(err); }); }); } function drawTrack( imgPath: string, drawTrackList: DrawTrackItem[], outpath: string ): Promise { return new Promise((resolve, reject) => { const gmObj = gmInst(imgPath); makeDirSync(path.dirname(outpath)); const defaultColor = '#f53f3f'; const defaultFontSize = 22; gmObj.font(getGmFontPath()); drawTrackList.forEach((track) => { // text if (track.type === 'text') { const { x, y, text, color, fontSize } = track.option as DrawTrackTextOption; const fsize = fontSize || defaultFontSize; const textColor = color || defaultColor; const ny = y + fsize; gmObj .stroke(textColor, 1) .fill(textColor) .fontSize(fsize) .drawText(x, ny, text); return; } // circle if (track.type === 'circle') { const { x0, y0, x1, y1 } = track.option as DrawTrackCircleOption; const rx = (x1 - x0) / 2; const ry = (y1 - y0) / 2; const cx = x0 + rx; const cy = y0 + ry; gmObj .stroke(defaultColor, 2) .fill('none') .drawEllipse(cx, cy, rx, ry, 0, 360); return; } // line if (track.type === 'line') { const { x0, y0, x1, y1 } = track.option as DrawTrackLineOption; gmObj.stroke(defaultColor, 2).fill('none').drawLine(x0, y0, x1, y1); } }); gmObj.write(outpath, (err) => { if (!err) { return resolve(outpath); } return reject(err); }); }); } async function downloadFile(url: string, outputPath: string): Promise { return new Promise((resolve, reject) => { axios({ url, method: 'GET', responseType: 'arraybuffer', }) .then((response) => { fs.writeFileSync(outputPath, Buffer.from(response.data, 'binary')); resolve(outputPath); }) .catch(() => { reject(); }); }); } async function downloadImage(url: string, outputPath: string) { makeDirSync(path.dirname(outputPath)); await downloadFile(url, outputPath); const size = sizeOf(outputPath); return { url: outputPath, width: size.width || 100, height: size.height || 100, }; } function joinPath(paths: string[]) { return path.join(...paths); } interface ImageItem { url: string; width: number; height: number; } async function imagesToPdf( images: ImageItem[], outpath: string ): Promise { return new Promise((resolve, reject) => { const doc = new PDFDocument({ autoFirstPage: false }); makeDirSync(path.dirname(outpath)); const steam = fs.createWriteStream(outpath); doc.pipe(steam); images.forEach((image) => { const { url, width, height } = image; doc.addPage({ size: [width, height] }); doc.image(url, 0, 0, { width, height }); }); doc.end(); steam.on('finish', () => { resolve(outpath); }); steam.on('error', (err) => { reject(err); }); }); } function logger(content: string, type?: 'info' | 'error') { if (type === 'error') { log.error(content); } else { log.info(content); } } const commonApi = { cropImage, drawTrack, joinPath, downloadImage, imagesToPdf, logger, getConfigData, }; export type CommonApi = typeof commonApi; export default commonApi;