@meonode/canvas 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CONTRIBUTING.md +75 -0
  2. package/LICENSE +21 -0
  3. package/Readme.md +382 -0
  4. package/dist/cjs/canvas/canvas.helper.d.ts +57 -0
  5. package/dist/cjs/canvas/canvas.helper.d.ts.map +1 -0
  6. package/dist/cjs/canvas/canvas.helper.js +239 -0
  7. package/dist/cjs/canvas/canvas.helper.js.map +1 -0
  8. package/dist/cjs/canvas/canvas.type.d.ts +657 -0
  9. package/dist/cjs/canvas/canvas.type.d.ts.map +1 -0
  10. package/dist/cjs/canvas/grid.canvas.util.d.ts +39 -0
  11. package/dist/cjs/canvas/grid.canvas.util.d.ts.map +1 -0
  12. package/dist/cjs/canvas/grid.canvas.util.js +263 -0
  13. package/dist/cjs/canvas/grid.canvas.util.js.map +1 -0
  14. package/dist/cjs/canvas/image.canvas.util.d.ts +34 -0
  15. package/dist/cjs/canvas/image.canvas.util.d.ts.map +1 -0
  16. package/dist/cjs/canvas/image.canvas.util.js +310 -0
  17. package/dist/cjs/canvas/image.canvas.util.js.map +1 -0
  18. package/dist/cjs/canvas/layout.canvas.util.d.ts +123 -0
  19. package/dist/cjs/canvas/layout.canvas.util.d.ts.map +1 -0
  20. package/dist/cjs/canvas/layout.canvas.util.js +785 -0
  21. package/dist/cjs/canvas/layout.canvas.util.js.map +1 -0
  22. package/dist/cjs/canvas/root.canvas.util.d.ts +42 -0
  23. package/dist/cjs/canvas/root.canvas.util.d.ts.map +1 -0
  24. package/dist/cjs/canvas/root.canvas.util.js +140 -0
  25. package/dist/cjs/canvas/root.canvas.util.js.map +1 -0
  26. package/dist/cjs/canvas/text.canvas.util.d.ts +148 -0
  27. package/dist/cjs/canvas/text.canvas.util.d.ts.map +1 -0
  28. package/dist/cjs/canvas/text.canvas.util.js +1112 -0
  29. package/dist/cjs/canvas/text.canvas.util.js.map +1 -0
  30. package/dist/cjs/constant/common.const.d.ts +37 -0
  31. package/dist/cjs/constant/common.const.d.ts.map +1 -0
  32. package/dist/cjs/constant/common.const.js +51 -0
  33. package/dist/cjs/constant/common.const.js.map +1 -0
  34. package/dist/cjs/index.d.ts +7 -0
  35. package/dist/cjs/index.d.ts.map +1 -0
  36. package/dist/cjs/index.js +31 -0
  37. package/dist/cjs/index.js.map +1 -0
  38. package/dist/esm/canvas/canvas.helper.d.ts +57 -0
  39. package/dist/esm/canvas/canvas.helper.d.ts.map +1 -0
  40. package/dist/esm/canvas/canvas.helper.js +214 -0
  41. package/dist/esm/canvas/canvas.type.d.ts +657 -0
  42. package/dist/esm/canvas/canvas.type.d.ts.map +1 -0
  43. package/dist/esm/canvas/grid.canvas.util.d.ts +39 -0
  44. package/dist/esm/canvas/grid.canvas.util.d.ts.map +1 -0
  45. package/dist/esm/canvas/grid.canvas.util.js +259 -0
  46. package/dist/esm/canvas/image.canvas.util.d.ts +34 -0
  47. package/dist/esm/canvas/image.canvas.util.d.ts.map +1 -0
  48. package/dist/esm/canvas/image.canvas.util.js +306 -0
  49. package/dist/esm/canvas/layout.canvas.util.d.ts +123 -0
  50. package/dist/esm/canvas/layout.canvas.util.d.ts.map +1 -0
  51. package/dist/esm/canvas/layout.canvas.util.js +777 -0
  52. package/dist/esm/canvas/root.canvas.util.d.ts +42 -0
  53. package/dist/esm/canvas/root.canvas.util.d.ts.map +1 -0
  54. package/dist/esm/canvas/root.canvas.util.js +116 -0
  55. package/dist/esm/canvas/text.canvas.util.d.ts +148 -0
  56. package/dist/esm/canvas/text.canvas.util.d.ts.map +1 -0
  57. package/dist/esm/canvas/text.canvas.util.js +1108 -0
  58. package/dist/esm/constant/common.const.d.ts +37 -0
  59. package/dist/esm/constant/common.const.d.ts.map +1 -0
  60. package/dist/esm/constant/common.const.js +23 -0
  61. package/dist/esm/index.d.ts +7 -0
  62. package/dist/esm/index.d.ts.map +1 -0
  63. package/dist/esm/index.js +7 -0
  64. package/dist/meonode-canvas-1.0.0-beta.1.tgz +0 -0
  65. package/package.json +79 -0
@@ -0,0 +1,310 @@
1
+ 'use strict';
2
+
3
+ var skiaCanvas = require('skia-canvas');
4
+ var layout_canvas_util = require('./layout.canvas.util.js');
5
+ var canvas_helper = require('./canvas.helper.js');
6
+ var fs = require('fs');
7
+ var common_const = require('../constant/common.const.js');
8
+
9
+ /**
10
+ * Calculates pixel offset for image positioning based on percentage or pixel values.
11
+ * This handles centering, edge alignment, and percentage-based positioning.
12
+ */
13
+ function calculateOffsetFromValue(positionValue, availableSpace) {
14
+ const value = positionValue ?? '50%';
15
+ if (typeof value === 'number') {
16
+ return value;
17
+ }
18
+ if (typeof value === 'string' && value.endsWith('%')) {
19
+ const percentage = parseFloat(value) / 100;
20
+ return availableSpace * percentage;
21
+ }
22
+ console.warn(`[ImageNode] Invalid objectPosition value format: ${value}. Defaulting to 50%.`);
23
+ return availableSpace * 0.5;
24
+ }
25
+ /**
26
+ * Renders images with configurable sizing, positioning, and effects.
27
+ * Supports object-fit modes, positioning, border radius, and saturation filters.
28
+ */
29
+ class ImageNode extends layout_canvas_util.BoxNode {
30
+ loadedImage = null;
31
+ naturalWidth = 0;
32
+ naturalHeight = 0;
33
+ loadingPromise = null;
34
+ constructor(props) {
35
+ super({ name: 'Image', ...props, children: undefined });
36
+ this.props = {
37
+ objectFit: 'fill',
38
+ overflow: common_const.Style.Overflow.Hidden,
39
+ saturate: 1,
40
+ objectPosition: { Left: '50%', Top: '50%' },
41
+ ...props,
42
+ };
43
+ this.loadingPromise = this._loadImage();
44
+ }
45
+ /**
46
+ * Loads and processes an image from various sources (URL, file path, or Buffer).
47
+ * Handles SVG color modifications and sets natural dimensions with an aspect ratio.
48
+ *
49
+ * @returns Promise that resolves when image loading completes
50
+ * @throws Error if image loading fails
51
+ */
52
+ _loadImage() {
53
+ if (this.loadingPromise)
54
+ return this.loadingPromise;
55
+ if (this.loadedImage)
56
+ return Promise.resolve();
57
+ if (!this.props.src) {
58
+ const aspectRatioFromProps = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : undefined;
59
+ this.node.setAspectRatio(aspectRatioFromProps);
60
+ this.naturalWidth = 0;
61
+ this.naturalHeight = 0;
62
+ return Promise.resolve();
63
+ }
64
+ return new Promise(async (resolve) => {
65
+ const { fileTypeFromBuffer, fileTypeFromFile } = await import('file-type');
66
+ let finalSource = this.props.src;
67
+ let isSvg = false;
68
+ let contentBuffer = null;
69
+ let detectedMime;
70
+ try {
71
+ if (typeof this.props.src === 'string') {
72
+ if (this.props.src.startsWith('http')) {
73
+ const response = await fetch(this.props.src);
74
+ if (!response.ok) {
75
+ throw new Error(`HTTP error ${response.status} fetching image: ${this.props.src}`);
76
+ }
77
+ const imageArrayBuffer = await response.arrayBuffer();
78
+ contentBuffer = Buffer.from(imageArrayBuffer);
79
+ finalSource = contentBuffer;
80
+ const fileTypeResult = await fileTypeFromBuffer(contentBuffer);
81
+ detectedMime = fileTypeResult?.mime;
82
+ isSvg = detectedMime === 'image/svg+xml';
83
+ if ((!detectedMime || detectedMime === 'application/xml') &&
84
+ contentBuffer.toString('utf-8').includes('<svg')) {
85
+ isSvg = true;
86
+ }
87
+ }
88
+ else {
89
+ finalSource = this.props.src;
90
+ const filePath = this.props.src;
91
+ try {
92
+ const fileTypeResult = await fileTypeFromFile(filePath);
93
+ detectedMime = fileTypeResult?.mime;
94
+ isSvg = detectedMime === 'image/svg+xml';
95
+ if ((!detectedMime || detectedMime === 'application/xml') && filePath.toLowerCase().endsWith('.svg')) {
96
+ isSvg = true;
97
+ }
98
+ }
99
+ catch {
100
+ isSvg = filePath.toLowerCase().endsWith('.svg');
101
+ }
102
+ if (isSvg && this.props.color) {
103
+ try {
104
+ contentBuffer = await fs.promises.readFile(filePath);
105
+ }
106
+ catch {
107
+ isSvg = false;
108
+ contentBuffer = null;
109
+ }
110
+ }
111
+ }
112
+ }
113
+ else {
114
+ contentBuffer = this.props.src;
115
+ finalSource = contentBuffer;
116
+ const fileTypeResult = await fileTypeFromBuffer(contentBuffer);
117
+ detectedMime = fileTypeResult?.mime;
118
+ isSvg = detectedMime === 'image/svg+xml';
119
+ }
120
+ if (isSvg && this.props.color && contentBuffer) {
121
+ const svgString = contentBuffer.toString('utf-8');
122
+ const modifiedSvgString = svgString.replace(/fill="[^"]*"/g, `fill="${this.props.color}"`);
123
+ if (modifiedSvgString !== svgString) {
124
+ finalSource = Buffer.from(modifiedSvgString);
125
+ }
126
+ else {
127
+ finalSource = contentBuffer;
128
+ }
129
+ }
130
+ const img = await skiaCanvas.loadImage(finalSource);
131
+ this.loadedImage = img;
132
+ this.naturalWidth = img.width;
133
+ this.naturalHeight = img.height;
134
+ const calculatedAspectRatio = this.naturalWidth > 0 && this.naturalHeight > 0 ? this.naturalWidth / this.naturalHeight : undefined;
135
+ const finalAspectRatio = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0
136
+ ? this.props.aspectRatio
137
+ : calculatedAspectRatio;
138
+ this.node.setAspectRatio(finalAspectRatio);
139
+ this.props.onLoad?.();
140
+ resolve();
141
+ }
142
+ catch (error) {
143
+ this.naturalWidth = 0;
144
+ this.naturalHeight = 0;
145
+ const finalAspectRatioOnError = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : undefined;
146
+ this.node.setAspectRatio(finalAspectRatioOnError);
147
+ this.props.onError?.(error);
148
+ resolve();
149
+ }
150
+ });
151
+ }
152
+ getLoadingPromise() {
153
+ return this.loadingPromise ?? Promise.resolve();
154
+ }
155
+ /**
156
+ * Renders the image with correct sizing, clipping, and positioning.
157
+ * Handles object-fit, object-position, and visual effects like saturation.
158
+ */
159
+ _renderContent(ctx, x, y, width, height) {
160
+ super._renderContent(ctx, x, y, width, height);
161
+ if (!this.loadedImage || width <= 0 || height <= 0)
162
+ return;
163
+ const img = this.loadedImage;
164
+ const imgW = this.naturalWidth;
165
+ const imgH = this.naturalHeight;
166
+ if (imgW <= 0 || imgH <= 0)
167
+ return;
168
+ // Calculate content box accounting for padding and borders
169
+ const paddingLeft = this.node.getComputedPadding(common_const.Style.Edge.Left);
170
+ const paddingTop = this.node.getComputedPadding(common_const.Style.Edge.Top);
171
+ const paddingRight = this.node.getComputedPadding(common_const.Style.Edge.Right);
172
+ const paddingBottom = this.node.getComputedPadding(common_const.Style.Edge.Bottom);
173
+ const borderLeft = this.node.getComputedBorder(common_const.Style.Edge.Left);
174
+ const borderTop = this.node.getComputedBorder(common_const.Style.Edge.Top);
175
+ const borderRight = this.node.getComputedBorder(common_const.Style.Edge.Right);
176
+ const borderBottom = this.node.getComputedBorder(common_const.Style.Edge.Bottom);
177
+ const contentX = x + borderLeft + paddingLeft;
178
+ const contentY = y + borderTop + paddingTop;
179
+ const contentWidth = Math.max(0, width - borderLeft - paddingLeft - borderRight - paddingRight);
180
+ const contentHeight = Math.max(0, height - borderTop - paddingTop - borderBottom - paddingBottom);
181
+ if (contentWidth <= 0 || contentHeight <= 0)
182
+ return;
183
+ // Apply clipping for border radius
184
+ ctx.save();
185
+ const outerRadii = canvas_helper.parseBorderRadius(this.props.borderRadius);
186
+ const innerBorderRadii = {
187
+ TopLeft: Math.max(0, outerRadii.TopLeft - borderTop),
188
+ TopRight: Math.max(0, outerRadii.TopRight - borderTop),
189
+ BottomRight: Math.max(0, outerRadii.BottomRight - borderBottom),
190
+ BottomLeft: Math.max(0, outerRadii.BottomLeft - borderBottom),
191
+ };
192
+ const contentRadii = {
193
+ TopLeft: Math.max(0, innerBorderRadii.TopLeft - Math.max(paddingLeft, paddingTop)),
194
+ TopRight: Math.max(0, innerBorderRadii.TopRight - Math.max(paddingRight, paddingTop)),
195
+ BottomRight: Math.max(0, innerBorderRadii.BottomRight - Math.max(paddingRight, paddingBottom)),
196
+ BottomLeft: Math.max(0, innerBorderRadii.BottomLeft - Math.max(paddingLeft, paddingBottom)),
197
+ };
198
+ canvas_helper.drawRoundedRectPath(ctx, contentX, contentY, contentWidth, contentHeight, contentRadii);
199
+ ctx.clip();
200
+ // Calculate image dimensions based on object-fit
201
+ const nodeRatio = contentWidth / contentHeight;
202
+ const imgRatio = imgW / imgH;
203
+ const objectFit = this.props.objectFit;
204
+ let dw = contentWidth;
205
+ let dh = contentHeight;
206
+ if (objectFit === 'contain') {
207
+ if (imgRatio > nodeRatio) {
208
+ dw = contentWidth;
209
+ dh = contentWidth / imgRatio;
210
+ }
211
+ else {
212
+ dh = contentHeight;
213
+ dw = contentHeight * imgRatio;
214
+ }
215
+ }
216
+ else if (objectFit === 'cover') {
217
+ if (imgRatio > nodeRatio) {
218
+ dh = contentHeight;
219
+ dw = contentHeight * imgRatio;
220
+ }
221
+ else {
222
+ dw = contentWidth;
223
+ dh = contentWidth / imgRatio;
224
+ }
225
+ }
226
+ else if (objectFit === 'none') {
227
+ dw = imgW;
228
+ dh = imgH;
229
+ }
230
+ else if (objectFit === 'scale-down') {
231
+ if (imgW <= contentWidth && imgH <= contentHeight) {
232
+ dw = imgW;
233
+ dh = imgH;
234
+ }
235
+ else {
236
+ if (imgRatio > nodeRatio) {
237
+ dw = contentWidth;
238
+ dh = contentWidth / imgRatio;
239
+ }
240
+ else {
241
+ dh = contentHeight;
242
+ dw = contentHeight * imgRatio;
243
+ }
244
+ }
245
+ }
246
+ // Calculate image position based on object-position
247
+ const sx = 0;
248
+ const sy = 0;
249
+ const sw = imgW;
250
+ const sh = imgH;
251
+ const availableWidth = contentWidth - dw;
252
+ const availableHeight = contentHeight - dh;
253
+ const posProps = this.props.objectPosition || {};
254
+ const horizontalValue = posProps.Left !== undefined ? posProps.Left : posProps.Right !== undefined ? posProps.Right : '50%';
255
+ const verticalValue = posProps.Top !== undefined ? posProps.Top : posProps.Bottom !== undefined ? posProps.Bottom : '50%';
256
+ let offsetX = calculateOffsetFromValue(horizontalValue, availableWidth);
257
+ let offsetY = calculateOffsetFromValue(verticalValue, availableHeight);
258
+ if (posProps.Left === undefined && posProps.Right !== undefined) {
259
+ offsetX = availableWidth - offsetX;
260
+ }
261
+ if (posProps.Top === undefined && posProps.Bottom !== undefined) {
262
+ offsetY = availableHeight - offsetY;
263
+ }
264
+ const dx = contentX + offsetX;
265
+ const dy = contentY + offsetY;
266
+ // Draw image with filters
267
+ ctx.save();
268
+ try {
269
+ if (this.props.dropShadow) {
270
+ const shadow = this.props.dropShadow;
271
+ const shadowBlur = Math.max(shadow.offsetX ?? 0, shadow.offsetY ?? 0);
272
+ ctx.shadowOffsetX = shadow.offsetX ?? 0;
273
+ ctx.shadowOffsetY = shadow.offsetY ?? 0;
274
+ ctx.shadowBlur = Math.max(0, shadow.blur ?? shadowBlur);
275
+ ctx.shadowColor = shadow.color ?? 'black';
276
+ }
277
+ const saturateValue = this.props.saturate ?? 1;
278
+ let filterString = '';
279
+ if (saturateValue !== 1) {
280
+ filterString += `saturate(${saturateValue * 100}%) `;
281
+ }
282
+ if (filterString) {
283
+ const currentFilter = ctx.filter && ctx.filter !== 'none' ? ctx.filter + ' ' : '';
284
+ ctx.filter = currentFilter + filterString.trim();
285
+ }
286
+ const finalDX = Math.floor(dx);
287
+ const finalDY = Math.floor(dy);
288
+ const finalDW = Math.ceil(dw + (dx - finalDX));
289
+ const finalDH = Math.ceil(dh + (dy - finalDY));
290
+ if (finalDW > 0 && finalDH > 0) {
291
+ ctx.drawImage(img, sx, sy, sw, sh, finalDX, finalDY, finalDW, finalDH);
292
+ }
293
+ }
294
+ catch (drawError) {
295
+ console.error('[ImageNode] Error drawing image:', drawError);
296
+ }
297
+ finally {
298
+ ctx.restore();
299
+ }
300
+ ctx.restore();
301
+ }
302
+ }
303
+ /**
304
+ * Factory function to create ImageNode instances
305
+ */
306
+ const Image = (props) => new ImageNode(props);
307
+
308
+ exports.Image = Image;
309
+ exports.ImageNode = ImageNode;
310
+ //# sourceMappingURL=image.canvas.util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.canvas.util.js","sources":["../../../../src/canvas/image.canvas.util.ts"],"sourcesContent":["import type { BaseProps, ImageProps } from '@/canvas/canvas.type.js'\nimport { type CanvasRenderingContext2D, Image as CanvasImage, loadImage } from 'skia-canvas'\nimport { BoxNode } from '@/canvas/layout.canvas.util.js'\nimport { drawRoundedRectPath, parseBorderRadius } from '@/canvas/canvas.helper.js'\nimport { promises as fs } from 'fs'\nimport { Style } from '@/constant/common.const.js'\n\n/**\n * Calculates pixel offset for image positioning based on percentage or pixel values.\n * This handles centering, edge alignment, and percentage-based positioning.\n */\nfunction calculateOffsetFromValue(positionValue: number | `${number}%` | undefined, availableSpace: number): number {\n const value = positionValue ?? '50%'\n if (typeof value === 'number') {\n return value\n }\n if (typeof value === 'string' && value.endsWith('%')) {\n const percentage = parseFloat(value) / 100\n return availableSpace * percentage\n }\n console.warn(`[ImageNode] Invalid objectPosition value format: ${value}. Defaulting to 50%.`)\n return availableSpace * 0.5\n}\n\n/**\n * Renders images with configurable sizing, positioning, and effects.\n * Supports object-fit modes, positioning, border radius, and saturation filters.\n */\nexport class ImageNode extends BoxNode {\n declare props: ImageProps & BaseProps\n private loadedImage: CanvasImage | null = null\n private naturalWidth = 0\n private naturalHeight = 0\n private readonly loadingPromise: Promise<void> | null = null\n\n constructor(props: ImageProps) {\n super({ name: 'Image', ...props, children: undefined })\n\n this.props = {\n objectFit: 'fill',\n overflow: Style.Overflow.Hidden,\n saturate: 1,\n objectPosition: { Left: '50%', Top: '50%' },\n ...props,\n }\n\n this.loadingPromise = this._loadImage()\n }\n\n /**\n * Loads and processes an image from various sources (URL, file path, or Buffer).\n * Handles SVG color modifications and sets natural dimensions with an aspect ratio.\n *\n * @returns Promise that resolves when image loading completes\n * @throws Error if image loading fails\n */\n private _loadImage(): Promise<void> {\n if (this.loadingPromise) return this.loadingPromise\n if (this.loadedImage) return Promise.resolve()\n\n if (!this.props.src) {\n const aspectRatioFromProps =\n typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : undefined\n this.node.setAspectRatio(aspectRatioFromProps)\n this.naturalWidth = 0\n this.naturalHeight = 0\n\n return Promise.resolve()\n }\n\n return new Promise(async resolve => {\n const { fileTypeFromBuffer, fileTypeFromFile } = await import('file-type')\n let finalSource: string | Buffer = this.props.src\n let isSvg = false\n let contentBuffer: Buffer | null = null\n let detectedMime: string | undefined\n\n try {\n if (typeof this.props.src === 'string') {\n if (this.props.src.startsWith('http')) {\n const response = await fetch(this.props.src)\n if (!response.ok) {\n throw new Error(`HTTP error ${response.status} fetching image: ${this.props.src}`)\n }\n const imageArrayBuffer = await response.arrayBuffer()\n contentBuffer = Buffer.from(imageArrayBuffer)\n finalSource = contentBuffer\n\n const fileTypeResult = await fileTypeFromBuffer(contentBuffer)\n detectedMime = fileTypeResult?.mime\n isSvg = detectedMime === 'image/svg+xml'\n\n if (\n (!detectedMime || detectedMime === 'application/xml') &&\n contentBuffer.toString('utf-8').includes('<svg')\n ) {\n isSvg = true\n }\n } else {\n finalSource = this.props.src\n const filePath = this.props.src\n\n try {\n const fileTypeResult = await fileTypeFromFile(filePath)\n detectedMime = fileTypeResult?.mime\n isSvg = detectedMime === 'image/svg+xml'\n\n if ((!detectedMime || detectedMime === 'application/xml') && filePath.toLowerCase().endsWith('.svg')) {\n isSvg = true\n }\n } catch {\n isSvg = filePath.toLowerCase().endsWith('.svg')\n }\n\n if (isSvg && this.props.color) {\n try {\n contentBuffer = await fs.readFile(filePath)\n } catch {\n isSvg = false\n contentBuffer = null\n }\n }\n }\n } else {\n contentBuffer = this.props.src\n finalSource = contentBuffer\n\n const fileTypeResult = await fileTypeFromBuffer(contentBuffer)\n detectedMime = fileTypeResult?.mime\n isSvg = detectedMime === 'image/svg+xml'\n }\n\n if (isSvg && this.props.color && contentBuffer) {\n const svgString = contentBuffer.toString('utf-8')\n const modifiedSvgString = svgString.replace(/fill=\"[^\"]*\"/g, `fill=\"${this.props.color}\"`)\n\n if (modifiedSvgString !== svgString) {\n finalSource = Buffer.from(modifiedSvgString)\n } else {\n finalSource = contentBuffer\n }\n }\n\n const img = await loadImage(finalSource as never)\n this.loadedImage = img\n this.naturalWidth = img.width\n this.naturalHeight = img.height\n\n const calculatedAspectRatio =\n this.naturalWidth > 0 && this.naturalHeight > 0 ? this.naturalWidth / this.naturalHeight : undefined\n\n const finalAspectRatio =\n typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0\n ? this.props.aspectRatio\n : calculatedAspectRatio\n\n this.node.setAspectRatio(finalAspectRatio)\n\n this.props.onLoad?.()\n resolve()\n } catch (error: any) {\n this.naturalWidth = 0\n this.naturalHeight = 0\n const finalAspectRatioOnError =\n typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : undefined\n this.node.setAspectRatio(finalAspectRatioOnError)\n this.props.onError?.(error)\n resolve()\n }\n })\n }\n\n public getLoadingPromise(): Promise<void> {\n return this.loadingPromise ?? Promise.resolve()\n }\n\n /**\n * Renders the image with correct sizing, clipping, and positioning.\n * Handles object-fit, object-position, and visual effects like saturation.\n */\n protected override _renderContent(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n height: number,\n ) {\n super._renderContent(ctx, x, y, width, height)\n\n if (!this.loadedImage || width <= 0 || height <= 0) return\n const img = this.loadedImage\n const imgW = this.naturalWidth\n const imgH = this.naturalHeight\n if (imgW <= 0 || imgH <= 0) return\n\n // Calculate content box accounting for padding and borders\n const paddingLeft = this.node.getComputedPadding(Style.Edge.Left)\n const paddingTop = this.node.getComputedPadding(Style.Edge.Top)\n const paddingRight = this.node.getComputedPadding(Style.Edge.Right)\n const paddingBottom = this.node.getComputedPadding(Style.Edge.Bottom)\n const borderLeft = this.node.getComputedBorder(Style.Edge.Left)\n const borderTop = this.node.getComputedBorder(Style.Edge.Top)\n const borderRight = this.node.getComputedBorder(Style.Edge.Right)\n const borderBottom = this.node.getComputedBorder(Style.Edge.Bottom)\n const contentX = x + borderLeft + paddingLeft\n const contentY = y + borderTop + paddingTop\n const contentWidth = Math.max(0, width - borderLeft - paddingLeft - borderRight - paddingRight)\n const contentHeight = Math.max(0, height - borderTop - paddingTop - borderBottom - paddingBottom)\n\n if (contentWidth <= 0 || contentHeight <= 0) return\n\n // Apply clipping for border radius\n ctx.save()\n const outerRadii = parseBorderRadius(this.props.borderRadius)\n const innerBorderRadii = {\n TopLeft: Math.max(0, outerRadii.TopLeft - borderTop),\n TopRight: Math.max(0, outerRadii.TopRight - borderTop),\n BottomRight: Math.max(0, outerRadii.BottomRight - borderBottom),\n BottomLeft: Math.max(0, outerRadii.BottomLeft - borderBottom),\n }\n const contentRadii = {\n TopLeft: Math.max(0, innerBorderRadii.TopLeft - Math.max(paddingLeft, paddingTop)),\n TopRight: Math.max(0, innerBorderRadii.TopRight - Math.max(paddingRight, paddingTop)),\n BottomRight: Math.max(0, innerBorderRadii.BottomRight - Math.max(paddingRight, paddingBottom)),\n BottomLeft: Math.max(0, innerBorderRadii.BottomLeft - Math.max(paddingLeft, paddingBottom)),\n }\n drawRoundedRectPath(ctx, contentX, contentY, contentWidth, contentHeight, contentRadii)\n ctx.clip()\n\n // Calculate image dimensions based on object-fit\n const nodeRatio = contentWidth / contentHeight\n const imgRatio = imgW / imgH\n const objectFit = this.props.objectFit\n let dw = contentWidth\n let dh = contentHeight\n\n if (objectFit === 'contain') {\n if (imgRatio > nodeRatio) {\n dw = contentWidth\n dh = contentWidth / imgRatio\n } else {\n dh = contentHeight\n dw = contentHeight * imgRatio\n }\n } else if (objectFit === 'cover') {\n if (imgRatio > nodeRatio) {\n dh = contentHeight\n dw = contentHeight * imgRatio\n } else {\n dw = contentWidth\n dh = contentWidth / imgRatio\n }\n } else if (objectFit === 'none') {\n dw = imgW\n dh = imgH\n } else if (objectFit === 'scale-down') {\n if (imgW <= contentWidth && imgH <= contentHeight) {\n dw = imgW\n dh = imgH\n } else {\n if (imgRatio > nodeRatio) {\n dw = contentWidth\n dh = contentWidth / imgRatio\n } else {\n dh = contentHeight\n dw = contentHeight * imgRatio\n }\n }\n }\n\n // Calculate image position based on object-position\n const sx = 0\n const sy = 0\n const sw = imgW\n const sh = imgH\n\n const availableWidth = contentWidth - dw\n const availableHeight = contentHeight - dh\n const posProps = this.props.objectPosition || {}\n const horizontalValue =\n posProps.Left !== undefined ? posProps.Left : posProps.Right !== undefined ? posProps.Right : '50%'\n const verticalValue =\n posProps.Top !== undefined ? posProps.Top : posProps.Bottom !== undefined ? posProps.Bottom : '50%'\n\n let offsetX = calculateOffsetFromValue(horizontalValue, availableWidth)\n let offsetY = calculateOffsetFromValue(verticalValue, availableHeight)\n\n if (posProps.Left === undefined && posProps.Right !== undefined) {\n offsetX = availableWidth - offsetX\n }\n if (posProps.Top === undefined && posProps.Bottom !== undefined) {\n offsetY = availableHeight - offsetY\n }\n\n const dx = contentX + offsetX\n const dy = contentY + offsetY\n\n // Draw image with filters\n ctx.save()\n try {\n if (this.props.dropShadow) {\n const shadow = this.props.dropShadow\n const shadowBlur = Math.max(shadow.offsetX ?? 0, shadow.offsetY ?? 0)\n ctx.shadowOffsetX = shadow.offsetX ?? 0\n ctx.shadowOffsetY = shadow.offsetY ?? 0\n ctx.shadowBlur = Math.max(0, shadow.blur ?? shadowBlur)\n ctx.shadowColor = shadow.color ?? 'black'\n }\n\n const saturateValue = this.props.saturate ?? 1\n let filterString = ''\n if (saturateValue !== 1) {\n filterString += `saturate(${saturateValue * 100}%) `\n }\n\n if (filterString) {\n const currentFilter = ctx.filter && ctx.filter !== 'none' ? ctx.filter + ' ' : ''\n ctx.filter = currentFilter + filterString.trim()\n }\n\n const finalDX = Math.floor(dx)\n const finalDY = Math.floor(dy)\n const finalDW = Math.ceil(dw + (dx - finalDX))\n const finalDH = Math.ceil(dh + (dy - finalDY))\n\n if (finalDW > 0 && finalDH > 0) {\n ctx.drawImage(img, sx, sy, sw, sh, finalDX, finalDY, finalDW, finalDH)\n }\n } catch (drawError) {\n console.error('[ImageNode] Error drawing image:', drawError)\n } finally {\n ctx.restore()\n }\n\n ctx.restore()\n }\n}\n\n/**\n * Factory function to create ImageNode instances\n */\nexport const Image = (props: ImageProps) => new ImageNode(props)\n"],"names":["BoxNode","Style","fs","loadImage","parseBorderRadius","drawRoundedRectPath"],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ import { type CanvasRenderingContext2D } from 'skia-canvas';
2
+ import type { BaseProps, BoxProps } from '../canvas/canvas.type.js';
3
+ import { Node } from '../constant/common.const.js';
4
+ /**
5
+ * @class BoxNode
6
+ * @classdesc Base node class for rendering rectangular boxes with layout, styling, and children.
7
+ * It uses the Yoga layout engine for positioning and sizing.
8
+ */
9
+ export declare class BoxNode {
10
+ /**
11
+ * @property {Partial<BoxProps>} initialProps - Original props passed to the constructor before any modifications.
12
+ */
13
+ initialProps: Partial<BoxProps>;
14
+ /**
15
+ * @property {Node} node - The Yoga layout engine node.
16
+ */
17
+ node: Node;
18
+ /**
19
+ * @property {BoxNode[]} children - Child nodes.
20
+ */
21
+ children: BoxNode[];
22
+ /**
23
+ * @property {BoxProps & BaseProps} props - Current props including defaults and inherited values.
24
+ */
25
+ props: BoxProps & BaseProps;
26
+ /**
27
+ * @property {string} name - Node type name.
28
+ */
29
+ readonly name?: string;
30
+ /**
31
+ * @property {string} key - Unique node identifier.
32
+ */
33
+ key?: string;
34
+ /**
35
+ * Creates a new BoxNode instance
36
+ * @param props - Initial box properties and styling
37
+ */
38
+ constructor(props?: BoxProps & BaseProps);
39
+ /**
40
+ * Processes and appends any children passed in the initial props.
41
+ */
42
+ processInitialChildren(): void;
43
+ /**
44
+ * Inherits styles from the parent node.
45
+ * @param {BoxProps & BaseProps} parentProps - Parent node properties to inherit from.
46
+ */
47
+ protected resolveInheritedStyles(parentProps: BoxProps & BaseProps): void;
48
+ /**
49
+ * Applies node type-specific default values after inheritance.
50
+ */
51
+ protected applyDefaults(): void;
52
+ /**
53
+ * Appends a child node at the specified index.
54
+ * @param {BoxNode} child - Child node to append.
55
+ * @param index - Index to insert child at
56
+ */
57
+ protected appendChild(child: BoxNode, index: number): void;
58
+ /**
59
+ * Performs final layout adjustments recursively after the main layout calculation.
60
+ * @returns {boolean} Whether any node was marked as dirty during finalization.
61
+ */
62
+ finalizeLayout(): boolean;
63
+ /**
64
+ * Hook for subclasses to update layout based on computed size.
65
+ */
66
+ protected updateLayoutBasedOnComputedSize(): void;
67
+ /**
68
+ * Applies layout properties to the Yoga node.
69
+ * @param props - Box properties containing layout values
70
+ */
71
+ protected setLayout(props: BoxProps): void;
72
+ /**
73
+ * Renders the node and its children to the canvas.
74
+ * @param {CanvasRenderingContext2D} ctx - Canvas rendering context (from skia-canvas).
75
+ * @param {number} offsetX - X offset for rendering.
76
+ * @param {number} offsetY - Y offset for rendering.
77
+ */
78
+ render(ctx: CanvasRenderingContext2D, offsetX?: number, offsetY?: number): void;
79
+ /**
80
+ * Renders the node's visual content including background fills, shadows, and borders.
81
+ * This is an internal method used by the render() pipeline.
82
+ *
83
+ * @param ctx - The skia-canvas 2D rendering context to draw into
84
+ * @param x - The absolute x-coordinate where drawing should begin
85
+ * @param y - The absolute y-coordinate where drawing should begin
86
+ * @param width - The width of the content area to render
87
+ * @param height - The height of the content area to render
88
+ */
89
+ protected _renderContent(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number): void;
90
+ }
91
+ /**
92
+ * Creates a new BoxNode instance.
93
+ * @param {BoxProps} props - Box properties and configuration.
94
+ * @returns {BoxNode} New BoxNode instance.
95
+ */
96
+ export declare const Box: (props: BoxProps) => BoxNode;
97
+ /**
98
+ * @class ColumnNode
99
+ * Node class for vertical column layout
100
+ */
101
+ export declare class ColumnNode extends BoxNode {
102
+ constructor(props?: BoxProps & BaseProps);
103
+ }
104
+ /**
105
+ * Creates a new ColumnNode instance.
106
+ * @param {BoxProps} props - Column properties and configuration.
107
+ * @returns {ColumnNode} New ColumnNode instance.
108
+ */
109
+ export declare const Column: (props: BoxProps) => ColumnNode;
110
+ /**
111
+ * @class RowNode
112
+ * @classdesc Node class for horizontal row layout.
113
+ */
114
+ export declare class RowNode extends BoxNode {
115
+ constructor(props?: BoxProps & BaseProps);
116
+ }
117
+ /**
118
+ * Creates a new RowNode instance.
119
+ * @param {BoxProps} props - Row properties and configuration.
120
+ * @returns {RowNode} New RowNode instance.
121
+ */
122
+ export declare const Row: (props: BoxProps) => RowNode;
123
+ //# sourceMappingURL=layout.canvas.util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/layout.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAkB,MAAM,yBAAyB,CAAA;AAGlF,OAAa,EAAS,IAAI,EAAE,MAAM,4BAA4B,CAAA;AAE9D;;;;GAIG;AACH,qBAAa,OAAO;IAClB;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC/B;;OAEG;IACH,IAAI,EAAE,IAAI,CAAA;IACV;;OAEG;IACH,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB;;OAEG;IACH,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC3B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;OAGG;gBACS,KAAK,GAAE,QAAQ,GAAG,SAAc;IAqB5C;;OAEG;IACI,sBAAsB;IAW7B;;;OAGG;IACH,SAAS,CAAC,sBAAsB,CAAC,WAAW,EAAE,QAAQ,GAAG,SAAS;IAkClE;;OAEG;IACH,SAAS,CAAC,aAAa,IAAI,IAAI;IAI/B;;;;OAIG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM;IAanD;;;OAGG;IACI,cAAc;IAiBrB;;OAEG;IACH,SAAS,CAAC,+BAA+B;IAIzC;;;OAGG;IACH,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ;IAqInC;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,wBAAwB,EAAE,OAAO,SAAI,EAAE,OAAO,SAAI;IAqK9D;;;;;;;;;OASG;IACH,SAAS,CAAC,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CA6R5G;AAED;;;;GAIG;AACH,eAAO,MAAM,GAAG,GAAI,OAAO,QAAQ,YAAuB,CAAA;AAE1D;;;GAGG;AACH,qBAAa,UAAW,SAAQ,OAAO;gBACzB,KAAK,GAAE,QAAQ,GAAG,SAAc;CAS7C;AAED;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,QAAQ,eAA0B,CAAA;AAEhE;;;GAGG;AACH,qBAAa,OAAQ,SAAQ,OAAO;gBACtB,KAAK,GAAE,QAAQ,GAAG,SAAc;CAS7C;AAED;;;;GAIG;AACH,eAAO,MAAM,GAAG,GAAI,OAAO,QAAQ,YAAuB,CAAA"}