api.ts 4.3 KB

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