api.ts 4.2 KB

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