index.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. const { platform, homedir } = require('os')
  2. const { join } = require('path')
  3. const {
  4. clearAllCache,
  5. CanvasRenderingContext2D,
  6. CanvasElement,
  7. SVGCanvas,
  8. Path: Path2D,
  9. ImageData,
  10. Image,
  11. FontKey,
  12. GlobalFonts,
  13. PathOp,
  14. FillType,
  15. StrokeJoin,
  16. StrokeCap,
  17. convertSVGTextToPath,
  18. PdfDocument,
  19. GifEncoder,
  20. GifDisposal,
  21. LottieAnimation,
  22. } = require('./js-binding')
  23. const { DOMPoint, DOMMatrix, DOMRect } = require('./geometry')
  24. const loadImage = require('./load-image')
  25. // Add Symbol.dispose support for GifEncoder (ECMAScript 2024 Explicit Resource Management)
  26. if (GifEncoder && typeof Symbol.dispose !== 'undefined') {
  27. GifEncoder.prototype[Symbol.dispose] = function () {
  28. this.dispose()
  29. }
  30. }
  31. const SvgExportFlag = {
  32. ConvertTextToPaths: 0x01,
  33. NoPrettyXML: 0x02,
  34. RelativePathEncoding: 0x04,
  35. }
  36. if (!('families' in GlobalFonts)) {
  37. Object.defineProperty(GlobalFonts, 'families', {
  38. get: function () {
  39. return JSON.parse(GlobalFonts.getFamilies().toString())
  40. },
  41. })
  42. }
  43. if (!('has' in GlobalFonts)) {
  44. Object.defineProperty(GlobalFonts, 'has', {
  45. value: function has(name) {
  46. return !!JSON.parse(GlobalFonts.getFamilies().toString()).find(({ family }) => family === name)
  47. },
  48. configurable: false,
  49. enumerable: false,
  50. writable: false,
  51. })
  52. }
  53. const _toBlob = CanvasElement.prototype.toBlob
  54. const _convertToBlob = CanvasElement.prototype.convertToBlob
  55. if ('Blob' in globalThis) {
  56. CanvasElement.prototype.toBlob = function toBlob(callback, mimeType, quality) {
  57. _toBlob.call(
  58. this,
  59. function (/** @type {Uint8Array} */ imageBuffer) {
  60. const blob = new Blob([imageBuffer.buffer], { type: mimeType })
  61. callback(blob)
  62. },
  63. mimeType,
  64. quality,
  65. )
  66. }
  67. CanvasElement.prototype.convertToBlob = function convertToBlob(options) {
  68. return _convertToBlob.call(this, options).then((/** @type {Uint8Array} */ imageBuffer) => {
  69. const blob = new Blob([imageBuffer.buffer], { type: options?.mime || 'image/png' })
  70. return blob
  71. })
  72. }
  73. } else {
  74. // oxlint-disable-next-line no-unused-vars
  75. CanvasElement.prototype.toBlob = function toBlob(callback, mimeType, quality) {
  76. callback(null)
  77. }
  78. // oxlint-disable-next-line no-unused-vars
  79. CanvasElement.prototype.convertToBlob = function convertToBlob(options) {
  80. return Promise.reject(new Error('Blob is not supported in this environment'))
  81. }
  82. }
  83. const _getTransform = CanvasRenderingContext2D.prototype.getTransform
  84. CanvasRenderingContext2D.prototype.getTransform = function getTransform() {
  85. const transform = _getTransform.apply(this, arguments)
  86. // monkey patched, skip
  87. if (transform instanceof DOMMatrix) {
  88. return transform
  89. }
  90. const { a, b, c, d, e, f } = transform
  91. return new DOMMatrix([a, b, c, d, e, f])
  92. }
  93. // Workaround for webpack bundling issue with drawImage
  94. // Store the original drawImage method
  95. const _drawImage = CanvasRenderingContext2D.prototype.drawImage
  96. // Override drawImage to ensure proper type recognition in bundled environments
  97. CanvasRenderingContext2D.prototype.drawImage = function drawImage(image, ...args) {
  98. // If the image is a Canvas-like object but not recognized due to bundling,
  99. // we need to ensure it's properly identified
  100. if (image && typeof image === 'object') {
  101. // First check if it's a wrapped canvas object
  102. if (image.canvas instanceof CanvasElement || image.canvas instanceof SVGCanvas) {
  103. image = image.canvas
  104. } else if (image._canvas instanceof CanvasElement || image._canvas instanceof SVGCanvas) {
  105. image = image._canvas
  106. }
  107. // Then check if it's a Canvas-like object by checking for getContext method
  108. else if (typeof image.getContext === 'function' && image.width && image.height) {
  109. // If it has canvas properties but isn't recognized as CanvasElement or SVGCanvas,
  110. // try to correct the prototype chain
  111. if (!(image instanceof CanvasElement) && !(image instanceof SVGCanvas)) {
  112. // Try to create a proper CanvasElement from the canvas-like object
  113. // This helps when webpack has transformed the prototype chain
  114. Object.setPrototypeOf(image, CanvasElement.prototype)
  115. }
  116. }
  117. }
  118. // Call the original drawImage with the potentially corrected image
  119. return _drawImage.apply(this, [image, ...args])
  120. }
  121. function createCanvas(width, height, flag) {
  122. const isSvgBackend = typeof flag !== 'undefined'
  123. return isSvgBackend ? new SVGCanvas(width, height, flag) : new CanvasElement(width, height)
  124. }
  125. class Canvas {
  126. constructor(width, height, flag) {
  127. return createCanvas(width, height, flag)
  128. }
  129. static [Symbol.hasInstance](instance) {
  130. return instance instanceof CanvasElement || instance instanceof SVGCanvas
  131. }
  132. }
  133. if (!process.env.DISABLE_SYSTEM_FONTS_LOAD) {
  134. GlobalFonts.loadSystemFonts()
  135. const platformName = platform()
  136. const homedirPath = homedir()
  137. switch (platformName) {
  138. case 'win32':
  139. GlobalFonts.loadFontsFromDir(join(homedirPath, 'AppData', 'Local', 'Microsoft', 'Windows', 'Fonts'))
  140. break
  141. case 'darwin':
  142. GlobalFonts.loadFontsFromDir(join(homedirPath, 'Library', 'Fonts'))
  143. break
  144. case 'linux':
  145. GlobalFonts.loadFontsFromDir(join('usr', 'local', 'share', 'fonts'))
  146. GlobalFonts.loadFontsFromDir(join(homedirPath, '.fonts'))
  147. break
  148. }
  149. }
  150. module.exports = {
  151. clearAllCache,
  152. Canvas,
  153. createCanvas,
  154. Path2D,
  155. ImageData,
  156. Image,
  157. PathOp,
  158. FillType,
  159. StrokeCap,
  160. StrokeJoin,
  161. SvgExportFlag,
  162. GlobalFonts: GlobalFonts,
  163. convertSVGTextToPath,
  164. DOMPoint,
  165. DOMMatrix,
  166. DOMRect,
  167. loadImage,
  168. FontKey,
  169. // Export these for better webpack compatibility
  170. CanvasElement,
  171. SVGCanvas,
  172. PDFDocument: PdfDocument,
  173. // GIF encoding
  174. GifEncoder,
  175. GifDisposal,
  176. // Lottie animation
  177. LottieAnimation,
  178. }