api.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import gm from 'gm';
  2. import axios from 'axios';
  3. import sizeOf from 'image-size';
  4. import path from 'node:path';
  5. import fs from 'node:fs';
  6. import PDFDocument from 'pdfkit';
  7. import log from 'electron-log/renderer';
  8. import {
  9. getImagicPath,
  10. getTempPath,
  11. makeDirSync,
  12. getGmFontPath,
  13. getConfigData,
  14. } from './utils';
  15. import {
  16. DrawTrackItem,
  17. DrawTrackCircleOption,
  18. DrawTrackLineOption,
  19. DrawTrackTextOption,
  20. } from './types';
  21. // macos install gm imagemagick https://github.com/aheckmann/gm/blob/master/README.md
  22. const gmInst =
  23. process.platform === 'win32'
  24. ? gm.subClass({
  25. // imageMagick: '7+',
  26. imageMagick: true,
  27. appPath: getImagicPath(),
  28. })
  29. : gm.subClass({ imageMagick: '7+' });
  30. function cropImage(imgPath: string): Promise<string> {
  31. return new Promise((resolve, reject) => {
  32. const outpath = path.join(getTempPath(), '001.png');
  33. gmInst(imgPath)
  34. .crop(500, 200, 0, 0)
  35. .write(outpath, (err) => {
  36. if (!err) {
  37. return resolve(outpath);
  38. }
  39. return reject(err);
  40. });
  41. });
  42. }
  43. function drawTrack(
  44. imgPath: string,
  45. drawTrackList: DrawTrackItem[],
  46. outpath: string
  47. ): Promise<string> {
  48. return new Promise((resolve, reject) => {
  49. const gmObj = gmInst(imgPath);
  50. makeDirSync(path.dirname(outpath));
  51. const defaultColor = '#f53f3f';
  52. const defaultFontSize = 22;
  53. gmObj.font(getGmFontPath());
  54. log.info(`drawTrackList:${JSON.stringify(drawTrackList)}`);
  55. drawTrackList.forEach((track) => {
  56. // text
  57. if (track.type === 'text') {
  58. const { x, y, text, color, fontSize } =
  59. track.option as DrawTrackTextOption;
  60. const fsize = fontSize || defaultFontSize;
  61. const textColor = color || defaultColor;
  62. const ny = y + fsize;
  63. gmObj
  64. .stroke(textColor, 1)
  65. .fill(textColor)
  66. .fontSize(fsize)
  67. .drawText(x, ny, text);
  68. return;
  69. }
  70. // circle
  71. if (track.type === 'circle') {
  72. const { x0, y0, x1, y1 } = track.option as DrawTrackCircleOption;
  73. const rx = (x1 - x0) / 2;
  74. const ry = (y1 - y0) / 2;
  75. const cx = x0 + rx;
  76. const cy = y0 + ry;
  77. gmObj
  78. .stroke(defaultColor, 2)
  79. .fill('none')
  80. .drawEllipse(cx, cy, rx, ry, 0, 360);
  81. return;
  82. }
  83. // line
  84. if (track.type === 'line') {
  85. const { x0, y0, x1, y1 } = track.option as DrawTrackLineOption;
  86. gmObj.stroke(defaultColor, 2).fill('none').drawLine(x0, y0, x1, y1);
  87. }
  88. });
  89. log.info(`gm write file: ${outpath}`);
  90. gmObj.write(outpath, (err) => {
  91. if (!err) {
  92. log.info(`gm write success`);
  93. return resolve(outpath);
  94. }
  95. console.error(err);
  96. log.error(`gm write failed: ${err.toString()}`);
  97. return reject(err);
  98. });
  99. });
  100. }
  101. async function downloadFile(url: string, outputPath: string): Promise<string> {
  102. return new Promise((resolve, reject) => {
  103. axios({
  104. url,
  105. method: 'GET',
  106. responseType: 'arraybuffer',
  107. })
  108. .then((response) => {
  109. fs.writeFileSync(outputPath, Buffer.from(response.data, 'binary'));
  110. resolve(outputPath);
  111. })
  112. .catch(() => {
  113. reject();
  114. });
  115. });
  116. }
  117. async function downloadImage(url: string, outputPath: string) {
  118. makeDirSync(path.dirname(outputPath));
  119. await downloadFile(url, outputPath);
  120. const size = sizeOf(outputPath);
  121. return {
  122. url: outputPath,
  123. width: size.width || 100,
  124. height: size.height || 100,
  125. };
  126. }
  127. function joinPath(paths: string[]) {
  128. return path.join(...paths);
  129. }
  130. interface ImageItem {
  131. url: string;
  132. width: number;
  133. height: number;
  134. }
  135. async function imagesToPdf(
  136. images: ImageItem[],
  137. outpath: string
  138. ): Promise<string> {
  139. return new Promise((resolve, reject) => {
  140. const doc = new PDFDocument({ autoFirstPage: false });
  141. makeDirSync(path.dirname(outpath));
  142. const steam = fs.createWriteStream(outpath);
  143. doc.pipe(steam);
  144. images.forEach((image) => {
  145. const { url, width, height } = image;
  146. doc.addPage({ size: [width, height] });
  147. doc.image(url, 0, 0, { width, height });
  148. });
  149. doc.end();
  150. steam.on('finish', () => {
  151. resolve(outpath);
  152. });
  153. steam.on('error', (err) => {
  154. reject(err);
  155. });
  156. });
  157. }
  158. function logger(content: string, type?: 'info' | 'error') {
  159. if (type === 'error') {
  160. log.error(content);
  161. } else {
  162. log.info(content);
  163. }
  164. }
  165. const commonApi = {
  166. cropImage,
  167. drawTrack,
  168. joinPath,
  169. downloadImage,
  170. imagesToPdf,
  171. logger,
  172. getConfigData,
  173. };
  174. export type CommonApi = typeof commonApi;
  175. export default commonApi;