@meframe/core 0.0.41 → 0.0.43

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 (36) hide show
  1. package/dist/Meframe.d.ts +1 -0
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +2 -2
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/config/types.d.ts +4 -0
  6. package/dist/config/types.d.ts.map +1 -1
  7. package/dist/controllers/PlaybackController.d.ts +0 -3
  8. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  9. package/dist/controllers/PlaybackController.js +29 -53
  10. package/dist/controllers/PlaybackController.js.map +1 -1
  11. package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
  12. package/dist/orchestrator/CompositionPlanner.js +4 -1
  13. package/dist/orchestrator/CompositionPlanner.js.map +1 -1
  14. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  15. package/dist/orchestrator/Orchestrator.js +3 -2
  16. package/dist/orchestrator/Orchestrator.js.map +1 -1
  17. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  18. package/dist/orchestrator/VideoClipSession.js +1 -0
  19. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  20. package/dist/stages/compose/FrameRateConverter.d.ts +12 -0
  21. package/dist/stages/compose/FrameRateConverter.d.ts.map +1 -1
  22. package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
  23. package/dist/stages/compose/LayerRenderer.js +37 -12
  24. package/dist/stages/compose/LayerRenderer.js.map +1 -1
  25. package/dist/stages/compose/VideoComposer.d.ts +2 -0
  26. package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
  27. package/dist/stages/compose/VideoComposer.js +15 -6
  28. package/dist/stages/compose/VideoComposer.js.map +1 -1
  29. package/dist/stages/compose/font-system/font-templates.d.ts.map +1 -1
  30. package/dist/stages/compose/font-system/font-templates.js +15 -4
  31. package/dist/stages/compose/font-system/font-templates.js.map +1 -1
  32. package/dist/workers/stages/compose/{video-compose.worker.DM_bsOY8.js → video-compose.worker.D_542rY_.js} +103 -39
  33. package/dist/workers/stages/compose/video-compose.worker.D_542rY_.js.map +1 -0
  34. package/dist/workers/worker-manifest.json +1 -1
  35. package/package.json +1 -1
  36. package/dist/workers/stages/compose/video-compose.worker.DM_bsOY8.js.map +0 -1
@@ -124,13 +124,26 @@ class LayerRenderer {
124
124
  const { videoFrame, crop } = layer;
125
125
  const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;
126
126
  const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;
127
- const scaleX = this.width / videoWidth;
128
- const scaleY = this.height / videoHeight;
129
- const scale = Math.min(scaleX, scaleY);
130
- const renderWidth = Math.round(videoWidth * scale);
131
- const renderHeight = Math.round(videoHeight * scale);
132
- const renderX = Math.round((this.width - renderWidth) / 2);
133
- const renderY = Math.round((this.height - renderHeight) / 2);
127
+ const naturalScale = this.height / videoHeight;
128
+ const scaledWidth = videoWidth * naturalScale;
129
+ const scaledHeight = videoHeight * naturalScale;
130
+ const FILL_THRESHOLD = 0.9;
131
+ const shouldFill = scaledWidth / this.width > FILL_THRESHOLD && scaledHeight / this.height > FILL_THRESHOLD;
132
+ let renderWidth;
133
+ let renderHeight;
134
+ let renderX;
135
+ let renderY;
136
+ if (shouldFill) {
137
+ renderWidth = this.width;
138
+ renderHeight = this.height;
139
+ renderX = 0;
140
+ renderY = 0;
141
+ } else {
142
+ renderWidth = Math.round(scaledWidth);
143
+ renderHeight = Math.round(scaledHeight);
144
+ renderX = Math.round((this.width - renderWidth) / 2);
145
+ renderY = Math.round((this.height - renderHeight) / 2);
146
+ }
134
147
  if (crop) {
135
148
  this.ctx.drawImage(
136
149
  videoFrame,
@@ -187,9 +200,21 @@ class LayerRenderer {
187
200
  renderHeight = imgHeight;
188
201
  }
189
202
  } else {
190
- renderWidth = this.width;
191
- renderHeight = this.height;
203
+ const naturalScale = this.width / imgWidth;
204
+ const scaledWidth = imgWidth * naturalScale;
205
+ const scaledHeight = imgHeight * naturalScale;
206
+ const FILL_THRESHOLD = 0.9;
207
+ const shouldFill = scaledWidth / this.width > FILL_THRESHOLD && scaledHeight / this.height > FILL_THRESHOLD;
208
+ if (shouldFill) {
209
+ renderWidth = this.width;
210
+ renderHeight = this.height;
211
+ } else {
212
+ renderWidth = Math.round(scaledWidth);
213
+ renderHeight = Math.round(scaledHeight);
214
+ }
192
215
  }
216
+ const renderX = isAttachment ? 0 : Math.round((this.width - renderWidth) / 2);
217
+ const renderY = isAttachment ? 0 : Math.round((this.height - renderHeight) / 2);
193
218
  if (crop) {
194
219
  this.ctx.drawImage(
195
220
  source,
@@ -197,13 +222,13 @@ class LayerRenderer {
197
222
  crop.y,
198
223
  crop.width,
199
224
  crop.height,
200
- 0,
201
- 0,
225
+ renderX,
226
+ renderY,
202
227
  renderWidth,
203
228
  renderHeight
204
229
  );
205
230
  } else {
206
- this.ctx.drawImage(source, 0, 0, renderWidth, renderHeight);
231
+ this.ctx.drawImage(source, renderX, renderY, renderWidth, renderHeight);
207
232
  }
208
233
  }
209
234
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LayerRenderer.js","sources":["../../../src/stages/compose/LayerRenderer.ts"],"sourcesContent":["import type { Layer, VideoLayer, ImageLayer, TextLayer, Transform2D, MaskConfig } from './types';\nimport { renderBasicText, renderTextWithEntrance } from './text-renderers/basic-text-renderer';\nimport { renderWordByWord } from './text-renderers/word-by-word-renderer';\nimport { renderCharacterKTV } from './text-renderers/character-ktv-renderer';\nimport { renderWordByWordFancy } from './text-renderers/word-fancy-renderer';\n\n/**\n * LayerRenderer - Handles rendering of individual layers\n * Single responsibility: Draw a single layer to the canvas context\n */\nexport class LayerRenderer {\n private ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n private width: number;\n private height: number;\n private currentFrame: number = 0;\n private fps: number = 30;\n\n constructor(\n ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D,\n width: number,\n height: number,\n fps: number = 30\n ) {\n this.ctx = ctx;\n this.width = width;\n this.height = height;\n this.fps = fps;\n this.ensureHighQualityRendering();\n }\n\n setCurrentFrame(frame: number): void {\n this.currentFrame = frame;\n }\n\n private ensureHighQualityRendering(): void {\n this.ctx.imageSmoothingEnabled = true;\n this.ctx.imageSmoothingQuality = 'high';\n }\n\n /**\n * Render a single layer with all its properties\n */\n async renderLayer(layer: Layer): Promise<void> {\n if (!layer.visible || layer.opacity <= 0) return;\n\n this.ctx.save();\n\n try {\n this.ensureHighQualityRendering();\n\n // Apply layer properties\n this.ctx.globalAlpha = layer.opacity;\n\n if (layer.blendMode) {\n this.ctx.globalCompositeOperation = layer.blendMode;\n }\n\n if (layer.transform) {\n // Get layer dimensions for transform anchor calculation\n const layerDimensions = this.getLayerDimensions(layer);\n this.applyTransform(layer.transform, layerDimensions);\n }\n // Render based on layer type\n switch (layer.type) {\n case 'video':\n await this.renderVideoLayer(layer as VideoLayer);\n break;\n case 'image':\n await this.renderImageLayer(layer as ImageLayer);\n break;\n case 'text':\n await this.renderTextLayer(layer as TextLayer);\n break;\n }\n\n // Apply mask if present\n if (layer.mask) {\n this.applyMask(layer.mask);\n }\n } finally {\n this.ctx.restore();\n }\n }\n\n private parseDimension(\n value: number | string | undefined,\n canvasSize: number\n ): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'number') return value;\n\n // value is string at this point\n const strValue = value as string;\n\n // Parse percentage string like \"5%\"\n if (strValue.includes('%')) {\n const numValue = parseFloat(strValue);\n return isNaN(numValue) ? undefined : (numValue / 100) * canvasSize;\n }\n\n // Parse as pixel value\n const parsed = parseFloat(strValue);\n return isNaN(parsed) ? undefined : parsed;\n }\n\n private getLayerDimensions(layer: Layer): { width: number; height: number } {\n if (layer.type === 'image') {\n const imageLayer = layer as ImageLayer;\n if (imageLayer.source) {\n const imgWidth = imageLayer.source.width;\n const imgHeight = imageLayer.source.height;\n\n // For attachment layers with target dimensions, calculate rendered size\n const isAttachment = !!imageLayer.attachmentId;\n if (isAttachment) {\n const targetWidthRaw = (imageLayer as any).targetWidth;\n const targetHeightRaw = (imageLayer as any).targetHeight;\n\n // Parse dimensions (supports both pixels and percentages)\n const targetWidth = this.parseDimension(targetWidthRaw, this.width);\n const targetHeight = this.parseDimension(targetHeightRaw, this.height);\n\n if (targetWidth && targetHeight) {\n return { width: targetWidth, height: targetHeight };\n } else if (targetWidth) {\n return {\n width: targetWidth,\n height: (imgHeight / imgWidth) * targetWidth,\n };\n } else if (targetHeight) {\n return {\n width: (imgWidth / imgHeight) * targetHeight,\n height: targetHeight,\n };\n }\n }\n\n // No scaling, return original dimensions\n return { width: imgWidth, height: imgHeight };\n }\n } else if (layer.type === 'video') {\n const videoLayer = layer as VideoLayer;\n const videoFrame = videoLayer.videoFrame;\n return {\n width: videoFrame.displayWidth || videoFrame.codedWidth,\n height: videoFrame.displayHeight || videoFrame.codedHeight,\n };\n }\n // Default to canvas dimensions\n return { width: this.width, height: this.height };\n }\n\n private applyTransform(\n transform: Transform2D,\n layerDimensions: { width: number; height: number }\n ): void {\n // Use layer dimensions (not canvas dimensions) for anchor calculation\n const anchorX = transform.anchorX ?? 0.5;\n const anchorY = transform.anchorY ?? 0.5;\n const centerX = layerDimensions.width * anchorX;\n const centerY = layerDimensions.height * anchorY;\n\n // Move to the layer position + anchor offset\n this.ctx.translate(transform.x + centerX, transform.y + centerY);\n\n if (transform.rotation) {\n this.ctx.rotate(transform.rotation);\n }\n\n this.ctx.scale(transform.scaleX, transform.scaleY);\n\n if (transform.skewX || transform.skewY) {\n this.ctx.transform(1, transform.skewY ?? 0, transform.skewX ?? 0, 1, 0, 0);\n }\n\n // Move back by anchor offset\n this.ctx.translate(-centerX, -centerY);\n }\n\n private async renderVideoLayer(layer: VideoLayer): Promise<void> {\n const { videoFrame, crop } = layer;\n\n // Get video dimensions\n const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;\n const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;\n\n // Calculate scaling to fit (contain mode - preserve aspect ratio)\n const scaleX = this.width / videoWidth;\n const scaleY = this.height / videoHeight;\n\n // Use the smaller scale to ensure entire video fits\n const scale = Math.min(scaleX, scaleY);\n\n // Calculate final render dimensions\n const renderWidth = Math.round(videoWidth * scale);\n const renderHeight = Math.round(videoHeight * scale);\n\n // Center the video\n const renderX = Math.round((this.width - renderWidth) / 2);\n const renderY = Math.round((this.height - renderHeight) / 2);\n\n if (crop) {\n this.ctx.drawImage(\n videoFrame,\n crop.x,\n crop.y,\n crop.width,\n crop.height,\n renderX,\n renderY,\n renderWidth,\n renderHeight\n );\n } else {\n this.ctx.drawImage(videoFrame, renderX, renderY, renderWidth, renderHeight);\n }\n videoFrame.close();\n }\n\n private async renderImageLayer(layer: ImageLayer): Promise<void> {\n const { source, crop } = layer;\n\n // Handle ImageData by putting it on canvas first\n if (source instanceof ImageData) {\n if (crop) {\n // For ImageData with crop, we need to extract the cropped region\n const tempCanvas = new OffscreenCanvas(crop.width, crop.height);\n const tempCtx = tempCanvas.getContext('2d')!;\n tempCtx.putImageData(source, -crop.x, -crop.y);\n this.ctx.drawImage(tempCanvas, 0, 0, this.width, this.height);\n } else {\n // Put ImageData directly\n this.ctx.putImageData(source, 0, 0);\n }\n } else {\n // ImageBitmap can be drawn directly\n if (!source) {\n return;\n }\n\n // Determine if this is an attachment layer (has attachmentId)\n // Attachment images use original size (or targetWidth/targetHeight if specified)\n // Main track images fill canvas\n const isAttachment = !!layer.attachmentId;\n const imgWidth = source.width;\n const imgHeight = source.height;\n\n let renderWidth: number;\n let renderHeight: number;\n\n if (isAttachment) {\n const targetWidthRaw = (layer as any).targetWidth;\n const targetHeightRaw = (layer as any).targetHeight;\n\n // Parse dimensions (supports both pixels and percentages)\n const targetWidth = this.parseDimension(targetWidthRaw, this.width);\n const targetHeight = this.parseDimension(targetHeightRaw, this.height);\n\n if (targetWidth && targetHeight) {\n // Both specified, use as-is\n renderWidth = targetWidth;\n renderHeight = targetHeight;\n } else if (targetWidth) {\n // Only width specified, maintain aspect ratio\n renderWidth = targetWidth;\n renderHeight = (imgHeight / imgWidth) * targetWidth;\n } else if (targetHeight) {\n // Only height specified, maintain aspect ratio\n renderHeight = targetHeight;\n renderWidth = (imgWidth / imgHeight) * targetHeight;\n } else {\n // No target size, use original\n renderWidth = imgWidth;\n renderHeight = imgHeight;\n }\n } else {\n // Main track images fill canvas\n renderWidth = this.width;\n renderHeight = this.height;\n }\n\n if (crop) {\n this.ctx.drawImage(\n source,\n crop.x,\n crop.y,\n crop.width,\n crop.height,\n 0,\n 0,\n renderWidth,\n renderHeight\n );\n } else {\n // Draw at appropriate size based on layer type\n this.ctx.drawImage(source, 0, 0, renderWidth, renderHeight);\n }\n }\n }\n\n private async renderTextLayer(layer: TextLayer): Promise<void> {\n const animationType = layer.animation?.type;\n const hasWordTimings = layer.wordTimings && layer.wordTimings.length > 0;\n\n const needsWordTimings = ['wordByWord', 'characterKTV', 'wordByWordFancy'].includes(\n animationType || ''\n );\n\n if (needsWordTimings && !hasWordTimings) {\n renderBasicText(this.ctx, layer, this.width, this.height, this.currentFrame);\n return;\n }\n\n switch (animationType) {\n case 'wordByWord':\n renderWordByWord(this.ctx, layer, this.width, this.height, this.currentFrame, this.fps);\n break;\n case 'characterKTV':\n renderCharacterKTV(this.ctx, layer, this.width, this.height, this.currentFrame, this.fps);\n break;\n case 'wordByWordFancy':\n renderWordByWordFancy(\n this.ctx,\n layer,\n this.width,\n this.height,\n this.currentFrame,\n this.fps\n );\n break;\n case 'fade':\n renderTextWithEntrance(this.ctx, layer, this.width, this.height, this.currentFrame);\n break;\n default:\n renderBasicText(this.ctx, layer, this.width, this.height, this.currentFrame);\n break;\n }\n }\n\n private applyMask(mask: MaskConfig): void {\n this.ctx.globalCompositeOperation = mask.invert ? 'source-out' : 'destination-in';\n\n if (mask.source) {\n this.ctx.drawImage(mask.source, 0, 0, this.width, this.height);\n } else if (mask.shape === 'circle') {\n this.ctx.beginPath();\n this.ctx.arc(\n this.width / 2,\n this.height / 2,\n Math.min(this.width, this.height) / 2,\n 0,\n Math.PI * 2\n );\n this.ctx.fill();\n }\n }\n\n updateDimensions(width: number, height: number): void {\n this.width = width;\n this.height = height;\n this.ensureHighQualityRendering();\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LayerRenderer.js","sources":["../../../src/stages/compose/LayerRenderer.ts"],"sourcesContent":["import type { Layer, VideoLayer, ImageLayer, TextLayer, Transform2D, MaskConfig } from './types';\nimport { renderBasicText, renderTextWithEntrance } from './text-renderers/basic-text-renderer';\nimport { renderWordByWord } from './text-renderers/word-by-word-renderer';\nimport { renderCharacterKTV } from './text-renderers/character-ktv-renderer';\nimport { renderWordByWordFancy } from './text-renderers/word-fancy-renderer';\n\n/**\n * LayerRenderer - Handles rendering of individual layers\n * Single responsibility: Draw a single layer to the canvas context\n */\nexport class LayerRenderer {\n private ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n private width: number;\n private height: number;\n private currentFrame: number = 0;\n private fps: number = 30;\n\n constructor(\n ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D,\n width: number,\n height: number,\n fps: number = 30\n ) {\n this.ctx = ctx;\n this.width = width;\n this.height = height;\n this.fps = fps;\n this.ensureHighQualityRendering();\n }\n\n setCurrentFrame(frame: number): void {\n this.currentFrame = frame;\n }\n\n private ensureHighQualityRendering(): void {\n this.ctx.imageSmoothingEnabled = true;\n this.ctx.imageSmoothingQuality = 'high';\n }\n\n /**\n * Render a single layer with all its properties\n */\n async renderLayer(layer: Layer): Promise<void> {\n if (!layer.visible || layer.opacity <= 0) return;\n\n this.ctx.save();\n\n try {\n this.ensureHighQualityRendering();\n\n // Apply layer properties\n this.ctx.globalAlpha = layer.opacity;\n\n if (layer.blendMode) {\n this.ctx.globalCompositeOperation = layer.blendMode;\n }\n\n if (layer.transform) {\n // Get layer dimensions for transform anchor calculation\n const layerDimensions = this.getLayerDimensions(layer);\n this.applyTransform(layer.transform, layerDimensions);\n }\n // Render based on layer type\n switch (layer.type) {\n case 'video':\n await this.renderVideoLayer(layer as VideoLayer);\n break;\n case 'image':\n await this.renderImageLayer(layer as ImageLayer);\n break;\n case 'text':\n await this.renderTextLayer(layer as TextLayer);\n break;\n }\n\n // Apply mask if present\n if (layer.mask) {\n this.applyMask(layer.mask);\n }\n } finally {\n this.ctx.restore();\n }\n }\n\n private parseDimension(\n value: number | string | undefined,\n canvasSize: number\n ): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'number') return value;\n\n // value is string at this point\n const strValue = value as string;\n\n // Parse percentage string like \"5%\"\n if (strValue.includes('%')) {\n const numValue = parseFloat(strValue);\n return isNaN(numValue) ? undefined : (numValue / 100) * canvasSize;\n }\n\n // Parse as pixel value\n const parsed = parseFloat(strValue);\n return isNaN(parsed) ? undefined : parsed;\n }\n\n private getLayerDimensions(layer: Layer): { width: number; height: number } {\n if (layer.type === 'image') {\n const imageLayer = layer as ImageLayer;\n if (imageLayer.source) {\n const imgWidth = imageLayer.source.width;\n const imgHeight = imageLayer.source.height;\n\n // For attachment layers with target dimensions, calculate rendered size\n const isAttachment = !!imageLayer.attachmentId;\n if (isAttachment) {\n const targetWidthRaw = (imageLayer as any).targetWidth;\n const targetHeightRaw = (imageLayer as any).targetHeight;\n\n // Parse dimensions (supports both pixels and percentages)\n const targetWidth = this.parseDimension(targetWidthRaw, this.width);\n const targetHeight = this.parseDimension(targetHeightRaw, this.height);\n\n if (targetWidth && targetHeight) {\n return { width: targetWidth, height: targetHeight };\n } else if (targetWidth) {\n return {\n width: targetWidth,\n height: (imgHeight / imgWidth) * targetWidth,\n };\n } else if (targetHeight) {\n return {\n width: (imgWidth / imgHeight) * targetHeight,\n height: targetHeight,\n };\n }\n }\n\n // No scaling, return original dimensions\n return { width: imgWidth, height: imgHeight };\n }\n } else if (layer.type === 'video') {\n const videoLayer = layer as VideoLayer;\n const videoFrame = videoLayer.videoFrame;\n return {\n width: videoFrame.displayWidth || videoFrame.codedWidth,\n height: videoFrame.displayHeight || videoFrame.codedHeight,\n };\n }\n // Default to canvas dimensions\n return { width: this.width, height: this.height };\n }\n\n private applyTransform(\n transform: Transform2D,\n layerDimensions: { width: number; height: number }\n ): void {\n // Use layer dimensions (not canvas dimensions) for anchor calculation\n const anchorX = transform.anchorX ?? 0.5;\n const anchorY = transform.anchorY ?? 0.5;\n const centerX = layerDimensions.width * anchorX;\n const centerY = layerDimensions.height * anchorY;\n\n // Move to the layer position + anchor offset\n this.ctx.translate(transform.x + centerX, transform.y + centerY);\n\n if (transform.rotation) {\n this.ctx.rotate(transform.rotation);\n }\n\n this.ctx.scale(transform.scaleX, transform.scaleY);\n\n if (transform.skewX || transform.skewY) {\n this.ctx.transform(1, transform.skewY ?? 0, transform.skewX ?? 0, 1, 0, 0);\n }\n\n // Move back by anchor offset\n this.ctx.translate(-centerX, -centerY);\n }\n\n private async renderVideoLayer(layer: VideoLayer): Promise<void> {\n const { videoFrame, crop } = layer;\n\n // Get video dimensions\n const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;\n const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;\n\n // Video uses scaleY (height-based scaling) to match legacy behavior\n const naturalScale = this.height / videoHeight;\n\n // Calculate scaled dimensions\n const scaledWidth = videoWidth * naturalScale;\n const scaledHeight = videoHeight * naturalScale;\n\n // Smart fill: when scaled size is close to container (>90%), use cover mode\n const FILL_THRESHOLD = 0.9;\n const shouldFill =\n scaledWidth / this.width > FILL_THRESHOLD && scaledHeight / this.height > FILL_THRESHOLD;\n\n let renderWidth: number;\n let renderHeight: number;\n let renderX: number;\n let renderY: number;\n\n if (shouldFill) {\n // Cover mode: fill entire canvas\n renderWidth = this.width;\n renderHeight = this.height;\n renderX = 0;\n renderY = 0;\n } else {\n // Contain mode: use scaled dimensions and center\n renderWidth = Math.round(scaledWidth);\n renderHeight = Math.round(scaledHeight);\n renderX = Math.round((this.width - renderWidth) / 2);\n renderY = Math.round((this.height - renderHeight) / 2);\n }\n\n if (crop) {\n this.ctx.drawImage(\n videoFrame,\n crop.x,\n crop.y,\n crop.width,\n crop.height,\n renderX,\n renderY,\n renderWidth,\n renderHeight\n );\n } else {\n this.ctx.drawImage(videoFrame, renderX, renderY, renderWidth, renderHeight);\n }\n videoFrame.close();\n }\n\n private async renderImageLayer(layer: ImageLayer): Promise<void> {\n const { source, crop } = layer;\n\n // Handle ImageData by putting it on canvas first\n if (source instanceof ImageData) {\n if (crop) {\n // For ImageData with crop, we need to extract the cropped region\n const tempCanvas = new OffscreenCanvas(crop.width, crop.height);\n const tempCtx = tempCanvas.getContext('2d')!;\n tempCtx.putImageData(source, -crop.x, -crop.y);\n this.ctx.drawImage(tempCanvas, 0, 0, this.width, this.height);\n } else {\n // Put ImageData directly\n this.ctx.putImageData(source, 0, 0);\n }\n } else {\n // ImageBitmap can be drawn directly\n if (!source) {\n return;\n }\n\n // Determine if this is an attachment layer (has attachmentId)\n const isAttachment = !!layer.attachmentId;\n const imgWidth = source.width;\n const imgHeight = source.height;\n\n let renderWidth: number;\n let renderHeight: number;\n\n if (isAttachment) {\n // Attachment images use original size (or targetWidth/targetHeight if specified)\n const targetWidthRaw = (layer as any).targetWidth;\n const targetHeightRaw = (layer as any).targetHeight;\n\n // Parse dimensions (supports both pixels and percentages)\n const targetWidth = this.parseDimension(targetWidthRaw, this.width);\n const targetHeight = this.parseDimension(targetHeightRaw, this.height);\n\n if (targetWidth && targetHeight) {\n // Both specified, use as-is\n renderWidth = targetWidth;\n renderHeight = targetHeight;\n } else if (targetWidth) {\n // Only width specified, maintain aspect ratio\n renderWidth = targetWidth;\n renderHeight = (imgHeight / imgWidth) * targetWidth;\n } else if (targetHeight) {\n // Only height specified, maintain aspect ratio\n renderHeight = targetHeight;\n renderWidth = (imgWidth / imgHeight) * targetHeight;\n } else {\n // No target size, use original\n renderWidth = imgWidth;\n renderHeight = imgHeight;\n }\n } else {\n // Main track images use scaleX (width-based scaling) to match legacy behavior\n const naturalScale = this.width / imgWidth;\n\n // Calculate scaled dimensions\n const scaledWidth = imgWidth * naturalScale;\n const scaledHeight = imgHeight * naturalScale;\n\n // Smart fill: when scaled size is close to container (>90%), use cover mode\n const FILL_THRESHOLD = 0.9;\n const shouldFill =\n scaledWidth / this.width > FILL_THRESHOLD && scaledHeight / this.height > FILL_THRESHOLD;\n\n if (shouldFill) {\n // Cover mode: fill entire canvas\n renderWidth = this.width;\n renderHeight = this.height;\n } else {\n // Contain mode: use scaled dimensions\n renderWidth = Math.round(scaledWidth);\n renderHeight = Math.round(scaledHeight);\n }\n }\n\n // Calculate render position (center for main track images)\n const renderX = isAttachment ? 0 : Math.round((this.width - renderWidth) / 2);\n const renderY = isAttachment ? 0 : Math.round((this.height - renderHeight) / 2);\n\n if (crop) {\n this.ctx.drawImage(\n source,\n crop.x,\n crop.y,\n crop.width,\n crop.height,\n renderX,\n renderY,\n renderWidth,\n renderHeight\n );\n } else {\n this.ctx.drawImage(source, renderX, renderY, renderWidth, renderHeight);\n }\n }\n }\n\n private async renderTextLayer(layer: TextLayer): Promise<void> {\n const animationType = layer.animation?.type;\n const hasWordTimings = layer.wordTimings && layer.wordTimings.length > 0;\n\n const needsWordTimings = ['wordByWord', 'characterKTV', 'wordByWordFancy'].includes(\n animationType || ''\n );\n\n if (needsWordTimings && !hasWordTimings) {\n renderBasicText(this.ctx, layer, this.width, this.height, this.currentFrame);\n return;\n }\n\n switch (animationType) {\n case 'wordByWord':\n renderWordByWord(this.ctx, layer, this.width, this.height, this.currentFrame, this.fps);\n break;\n case 'characterKTV':\n renderCharacterKTV(this.ctx, layer, this.width, this.height, this.currentFrame, this.fps);\n break;\n case 'wordByWordFancy':\n renderWordByWordFancy(\n this.ctx,\n layer,\n this.width,\n this.height,\n this.currentFrame,\n this.fps\n );\n break;\n case 'fade':\n renderTextWithEntrance(this.ctx, layer, this.width, this.height, this.currentFrame);\n break;\n default:\n renderBasicText(this.ctx, layer, this.width, this.height, this.currentFrame);\n break;\n }\n }\n\n private applyMask(mask: MaskConfig): void {\n this.ctx.globalCompositeOperation = mask.invert ? 'source-out' : 'destination-in';\n\n if (mask.source) {\n this.ctx.drawImage(mask.source, 0, 0, this.width, this.height);\n } else if (mask.shape === 'circle') {\n this.ctx.beginPath();\n this.ctx.arc(\n this.width / 2,\n this.height / 2,\n Math.min(this.width, this.height) / 2,\n 0,\n Math.PI * 2\n );\n this.ctx.fill();\n }\n }\n\n updateDimensions(width: number, height: number): void {\n this.width = width;\n this.height = height;\n this.ensureHighQualityRendering();\n }\n}\n"],"names":[],"mappings":"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"}
@@ -14,6 +14,8 @@ export declare class VideoComposer {
14
14
  private transitionProcessor;
15
15
  private filterProcessor;
16
16
  private frameDurationUs;
17
+ private fontsLoadingPromise;
18
+ private loadedFonts;
17
19
  constructor(config: VideoComposeConfig);
18
20
  private applyDefaults;
19
21
  createStreams(_instruction?: ClipInstructionSet): TransformStream<ComposeRequest, VideoFrame>;
@@ -1 +1 @@
1
- {"version":3,"file":"VideoComposer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/VideoComposer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAGjB,MAAM,SAAS,CAAC;AAIjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAapD;;GAEG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,gBAAgB,CAAC,GAAG;QACtE,cAAc,CAAC,EAAE,iBAAiB,GAAG,eAAe,CAAC;KACtD,CAAC;IACF,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAErD,OAAO,CAAC,GAAG,CAA+D;IAC1E,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,eAAe,CAAS;gBAEpB,MAAM,EAAE,kBAAkB;IAgDtC,OAAO,CAAC,aAAa;IA8BrB,aAAa,CAAC,YAAY,CAAC,EAAE,kBAAkB,GAAG,eAAe,CAAC,cAAc,EAAE,UAAU,CAAC;IA6BvF,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA2E7D,iBAAiB,CACrB,WAAW,EAAE,cAAc,EAC3B,SAAS,EAAE,cAAc,EACzB,UAAU,EAAE,gBAAgB,GAC3B,OAAO,CAAC,aAAa,CAAC;IAWzB,OAAO,CAAC,WAAW;YAaL,iBAAiB;YAUjB,SAAS;IAkBvB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI;IAuBvD,OAAO,IAAI,IAAI;CAGhB"}
1
+ {"version":3,"file":"VideoComposer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/VideoComposer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAGjB,MAAM,SAAS,CAAC;AAIjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAapD;;GAEG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,gBAAgB,CAAC,GAAG;QACtE,cAAc,CAAC,EAAE,iBAAiB,GAAG,eAAe,CAAC;KACtD,CAAC;IACF,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAErD,OAAO,CAAC,GAAG,CAA+D;IAC1E,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,mBAAmB,CAA8B;IACzD,OAAO,CAAC,WAAW,CAAqB;gBAE5B,MAAM,EAAE,kBAAkB;IAiDtC,OAAO,CAAC,aAAa;IA8BrB,aAAa,CAAC,YAAY,CAAC,EAAE,kBAAkB,GAAG,eAAe,CAAC,cAAc,EAAE,UAAU,CAAC;IA6BvF,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAiF7D,iBAAiB,CACrB,WAAW,EAAE,cAAc,EAC3B,SAAS,EAAE,cAAc,EACzB,UAAU,EAAE,gBAAgB,GAC3B,OAAO,CAAC,aAAa,CAAC;IAWzB,OAAO,CAAC,WAAW;YAaL,iBAAiB;YAUjB,SAAS;IAyBvB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI;IAuBvD,OAAO,IAAI,IAAI;CAGhB"}
@@ -18,6 +18,8 @@ class VideoComposer {
18
18
  filterProcessor;
19
19
  frameDurationUs;
20
20
  // Cached frame duration
21
+ fontsLoadingPromise = null;
22
+ loadedFonts = /* @__PURE__ */ new Set();
21
23
  constructor(config) {
22
24
  this.config = this.applyDefaults(config);
23
25
  this.frameDurationUs = Math.round(1e6 / this.config.fps);
@@ -46,7 +48,7 @@ class VideoComposer {
46
48
  throw new Error("Failed to create 2D rendering context");
47
49
  }
48
50
  this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;
49
- this.loadFonts();
51
+ this.fontsLoadingPromise = this.loadFonts();
50
52
  this.ctx.imageSmoothingQuality = "high";
51
53
  this.layerRenderer = new LayerRenderer(
52
54
  this.ctx,
@@ -104,6 +106,10 @@ class VideoComposer {
104
106
  return stream;
105
107
  }
106
108
  async composeFrame(request) {
109
+ if (this.fontsLoadingPromise) {
110
+ await this.fontsLoadingPromise;
111
+ this.fontsLoadingPromise = null;
112
+ }
107
113
  if (request.layers.length > this.config.maxLayers) {
108
114
  throw new Error(`Too many layers: ${request.layers.length} > ${this.config.maxLayers}`);
109
115
  }
@@ -194,14 +200,17 @@ class VideoComposer {
194
200
  return;
195
201
  }
196
202
  for (const font of this.config.fonts) {
203
+ if (this.loadedFonts.has(font.family)) {
204
+ continue;
205
+ }
197
206
  try {
198
207
  const fontFace = new FontFace(font.family, `url(${font.url})`);
199
- await fontFace.load();
208
+ const loadedFont = await fontFace.load();
200
209
  if ("fonts" in self) {
201
- self.fonts.add(fontFace);
210
+ self.fonts.add(loadedFont);
202
211
  }
203
- } catch (error) {
204
- console.warn(`[VideoComposer] Failed to load font ${font.family}:`, error);
212
+ this.loadedFonts.add(font.family);
213
+ } catch {
205
214
  }
206
215
  }
207
216
  }
@@ -220,7 +229,7 @@ class VideoComposer {
220
229
  this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;
221
230
  }
222
231
  if (config.fonts) {
223
- this.loadFonts();
232
+ this.fontsLoadingPromise = this.loadFonts();
224
233
  }
225
234
  }
226
235
  dispose() {
@@ -1 +1 @@
1
- {"version":3,"file":"VideoComposer.js","sources":["../../../src/stages/compose/VideoComposer.ts"],"sourcesContent":["import type {\n VideoComposeConfig,\n ComposeRequest,\n ComposeResult,\n TransitionEffect,\n VideoLayer,\n Layer,\n} from './types';\nimport { LayerRenderer } from './LayerRenderer';\nimport { TransitionProcessor } from './TransitionProcessor';\nimport { FilterProcessor } from './FilterProcessor';\nimport { ClipInstructionSet } from './instructions';\n\nconst closeLayerFrame = (layer: Layer) => {\n if ((layer as any).type === 'video') {\n // If layer has videoFrame, close it\n const vf = (layer as VideoLayer).videoFrame;\n if (vf?.close) {\n vf.close();\n }\n // RcFrame should not be closed here as it's managed by CacheManager\n }\n};\n\n/**\n * VideoComposer - Main visual composition orchestrator\n */\nexport class VideoComposer {\n readonly config: Omit<Required<VideoComposeConfig>, 'externalCanvas'> & {\n externalCanvas?: HTMLCanvasElement | OffscreenCanvas;\n };\n readonly canvas: OffscreenCanvas | HTMLCanvasElement;\n\n private ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n private layerRenderer: LayerRenderer;\n private transitionProcessor: TransitionProcessor;\n private filterProcessor: FilterProcessor;\n private frameDurationUs: number; // Cached frame duration\n\n constructor(config: VideoComposeConfig) {\n this.config = this.applyDefaults(config);\n this.frameDurationUs = Math.round(1_000_000 / this.config.fps); // Pre-calculate\n\n if (config.externalCanvas) {\n this.canvas = config.externalCanvas;\n\n // FIX: Ensure external canvas dimensions match the config\n if (this.canvas.width !== this.config.width || this.canvas.height !== this.config.height) {\n this.canvas.width = this.config.width;\n this.canvas.height = this.config.height;\n }\n\n this.ctx = this.canvas.getContext('2d', {\n alpha: true,\n desynchronized: true,\n willReadFrequently: false,\n colorSpace: 'srgb',\n }) as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n } else {\n this.canvas = new OffscreenCanvas(this.config.width, this.config.height);\n this.ctx = this.canvas.getContext('2d', {\n alpha: true,\n desynchronized: true,\n willReadFrequently: false,\n colorSpace: 'srgb',\n }) as OffscreenCanvasRenderingContext2D;\n }\n\n if (!this.ctx) {\n throw new Error('Failed to create 2D rendering context');\n }\n\n this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;\n\n this.loadFonts();\n this.ctx.imageSmoothingQuality = 'high';\n\n this.layerRenderer = new LayerRenderer(\n this.ctx,\n this.config.width,\n this.config.height,\n this.config.fps\n );\n this.transitionProcessor = new TransitionProcessor(this.config.width, this.config.height);\n this.filterProcessor = new FilterProcessor();\n }\n\n private applyDefaults(config: VideoComposeConfig): Omit<\n Required<VideoComposeConfig>,\n 'externalCanvas'\n > & {\n externalCanvas?: HTMLCanvasElement | OffscreenCanvas;\n } {\n return {\n width: config.width || 720,\n height: config.height || 1280,\n fps: config.fps || 30,\n backgroundColor: config.backgroundColor ?? '#000',\n renderer: config.renderer ?? 'canvas2d',\n enableSmoothing: config.enableSmoothing ?? true,\n enableHardwareAcceleration: config.enableHardwareAcceleration ?? true,\n revision: config.revision ?? 0,\n inputHighWaterMark: config.inputHighWaterMark ?? 3,\n outputHighWaterMark: config.outputHighWaterMark ?? 1,\n maxLayers: config.maxLayers ?? 100,\n timeline: config.timeline ?? {\n clipId: 'default',\n trackId: 'main',\n clipStartUs: 0,\n clipDurationUs: Infinity,\n compositionFps: 30,\n },\n fonts: config.fonts ?? [],\n externalCanvas: config.externalCanvas,\n };\n }\n\n createStreams(_instruction?: ClipInstructionSet): TransformStream<ComposeRequest, VideoFrame> {\n // Always create new streams for each clip\n // ReadableStreams can only be consumed once\n const stream = new TransformStream<ComposeRequest, VideoFrame>(\n {\n transform: async (request, controller) => {\n // console.log('[VideoComposer] transform', request, controller.desiredSize);\n const result = await this.composeFrame(request);\n if (result.frame) {\n controller.enqueue(result.frame);\n }\n // setTimeout(() => {\n // result.frame.close();\n // }, 1000);\n },\n\n flush: async () => {\n this.filterProcessor.clearCache();\n },\n },\n {\n highWaterMark: this.config.inputHighWaterMark,\n },\n {\n highWaterMark: this.config.outputHighWaterMark,\n }\n );\n return stream;\n }\n async composeFrame(request: ComposeRequest): Promise<ComposeResult> {\n if (request.layers.length > this.config.maxLayers) {\n throw new Error(`Too many layers: ${request.layers.length} > ${this.config.maxLayers}`);\n }\n\n this.clearCanvas();\n\n // Calculate current frame number for animations (relative to clip start)\n const frameDurationUs = 1_000_000 / this.config.fps;\n const relativeFrame = Math.floor(request.timeUs / frameDurationUs);\n this.layerRenderer.setCurrentFrame(relativeFrame);\n\n if (request.transition) {\n this.ctx.save();\n this.transitionProcessor.applyTransition(this.ctx, request.transition);\n }\n\n for (const layer of request.layers) {\n if (!layer.visible || layer.opacity <= 0) {\n // Close video frame for invisible layers\n closeLayerFrame(layer);\n continue;\n }\n\n try {\n // If layer has RcFrame, use it within the rendering scope\n if ((layer as any).rcFrame) {\n await (layer as any).rcFrame.use(async (frame: VideoFrame) => {\n // Set the cloned frame for this render cycle\n (layer as VideoLayer).videoFrame = frame;\n\n if (layer.filters && layer.filters.length > 0) {\n this.ctx.save();\n this.filterProcessor.applyFilters(this.ctx, layer.filters);\n }\n await this.layerRenderer.renderLayer(layer);\n\n if (layer.filters && layer.filters.length > 0) {\n this.ctx.restore();\n }\n });\n } else {\n // Regular layer without RcFrame\n if (layer.filters && layer.filters.length > 0) {\n this.ctx.save();\n this.filterProcessor.applyFilters(this.ctx, layer.filters);\n }\n await this.layerRenderer.renderLayer(layer);\n\n if (layer.filters && layer.filters.length > 0) {\n this.ctx.restore();\n }\n }\n } catch (error) {\n console.error('[VideoComposer] composeFrame error: ', error);\n // Always close video frame after rendering (or on error)\n closeLayerFrame(layer);\n }\n }\n\n if (request.transition) {\n this.ctx.restore();\n }\n\n let frame: VideoFrame | null = null;\n if (!this.config.externalCanvas) {\n frame = await this.createOutputFrame(request.timeUs);\n }\n\n return {\n frame,\n timeUs: request.timeUs,\n };\n }\n\n async composeTransition(\n fromRequest: ComposeRequest,\n toRequest: ComposeRequest,\n transition: TransitionEffect\n ): Promise<ComposeResult> {\n await this.composeFrame(fromRequest);\n\n const toFrameRequest = {\n ...toRequest,\n transition,\n };\n\n return this.composeFrame(toFrameRequest);\n }\n\n private clearCanvas(): void {\n if (this.config.backgroundColor) {\n this.ctx.fillStyle = this.config.backgroundColor;\n this.ctx.fillRect(0, 0, this.config.width, this.config.height);\n } else {\n this.ctx.clearRect(0, 0, this.config.width, this.config.height);\n }\n }\n\n // private sortLayers(layers: Layer[]): Layer[] {\n // return [...layers].sort((a, b) => a.zIndex - b.zIndex);\n // }\n\n private async createOutputFrame(timeUs: number): Promise<VideoFrame> {\n const frame = new VideoFrame(this.canvas, {\n timestamp: timeUs,\n duration: this.frameDurationUs, // Use cached duration\n alpha: 'discard',\n visibleRect: { x: 0, y: 0, width: this.canvas.width, height: this.canvas.height },\n });\n return frame;\n }\n\n private async loadFonts(): Promise<void> {\n if (!this.config.fonts || this.config.fonts.length === 0) {\n return;\n }\n\n for (const font of this.config.fonts) {\n try {\n const fontFace = new FontFace(font.family, `url(${font.url})`);\n await fontFace.load();\n if ('fonts' in self) {\n (self as any).fonts.add(fontFace);\n }\n } catch (error) {\n console.warn(`[VideoComposer] Failed to load font ${font.family}:`, error);\n }\n }\n }\n\n updateConfig(config: Partial<VideoComposeConfig>): void {\n Object.assign(this.config, this.applyDefaults({ ...this.config, ...config }));\n\n if (config.fps !== undefined) {\n this.frameDurationUs = Math.round(1_000_000 / this.config.fps); // Update cached duration\n }\n\n if (config.width || config.height) {\n this.canvas.width = this.config.width;\n this.canvas.height = this.config.height;\n this.layerRenderer.updateDimensions(this.config.width, this.config.height);\n this.transitionProcessor.updateDimensions(this.config.width, this.config.height);\n }\n\n if (config.enableSmoothing !== undefined) {\n this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;\n }\n\n if (config.fonts) {\n this.loadFonts();\n }\n }\n\n dispose(): void {\n this.filterProcessor.clearCache();\n }\n}\n"],"names":["frame"],"mappings":"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"}
1
+ {"version":3,"file":"VideoComposer.js","sources":["../../../src/stages/compose/VideoComposer.ts"],"sourcesContent":["import type {\n VideoComposeConfig,\n ComposeRequest,\n ComposeResult,\n TransitionEffect,\n VideoLayer,\n Layer,\n} from './types';\nimport { LayerRenderer } from './LayerRenderer';\nimport { TransitionProcessor } from './TransitionProcessor';\nimport { FilterProcessor } from './FilterProcessor';\nimport { ClipInstructionSet } from './instructions';\n\nconst closeLayerFrame = (layer: Layer) => {\n if ((layer as any).type === 'video') {\n // If layer has videoFrame, close it\n const vf = (layer as VideoLayer).videoFrame;\n if (vf?.close) {\n vf.close();\n }\n // RcFrame should not be closed here as it's managed by CacheManager\n }\n};\n\n/**\n * VideoComposer - Main visual composition orchestrator\n */\nexport class VideoComposer {\n readonly config: Omit<Required<VideoComposeConfig>, 'externalCanvas'> & {\n externalCanvas?: HTMLCanvasElement | OffscreenCanvas;\n };\n readonly canvas: OffscreenCanvas | HTMLCanvasElement;\n\n private ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n private layerRenderer: LayerRenderer;\n private transitionProcessor: TransitionProcessor;\n private filterProcessor: FilterProcessor;\n private frameDurationUs: number; // Cached frame duration\n private fontsLoadingPromise: Promise<void> | null = null;\n private loadedFonts = new Set<string>();\n\n constructor(config: VideoComposeConfig) {\n this.config = this.applyDefaults(config);\n this.frameDurationUs = Math.round(1_000_000 / this.config.fps); // Pre-calculate\n\n if (config.externalCanvas) {\n this.canvas = config.externalCanvas;\n\n // FIX: Ensure external canvas dimensions match the config\n if (this.canvas.width !== this.config.width || this.canvas.height !== this.config.height) {\n this.canvas.width = this.config.width;\n this.canvas.height = this.config.height;\n }\n\n this.ctx = this.canvas.getContext('2d', {\n alpha: true,\n desynchronized: true,\n willReadFrequently: false,\n colorSpace: 'srgb',\n }) as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n } else {\n this.canvas = new OffscreenCanvas(this.config.width, this.config.height);\n this.ctx = this.canvas.getContext('2d', {\n alpha: true,\n desynchronized: true,\n willReadFrequently: false,\n colorSpace: 'srgb',\n }) as OffscreenCanvasRenderingContext2D;\n }\n\n if (!this.ctx) {\n throw new Error('Failed to create 2D rendering context');\n }\n\n this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;\n\n // Start loading fonts but don't block constructor\n this.fontsLoadingPromise = this.loadFonts();\n this.ctx.imageSmoothingQuality = 'high';\n\n this.layerRenderer = new LayerRenderer(\n this.ctx,\n this.config.width,\n this.config.height,\n this.config.fps\n );\n this.transitionProcessor = new TransitionProcessor(this.config.width, this.config.height);\n this.filterProcessor = new FilterProcessor();\n }\n\n private applyDefaults(config: VideoComposeConfig): Omit<\n Required<VideoComposeConfig>,\n 'externalCanvas'\n > & {\n externalCanvas?: HTMLCanvasElement | OffscreenCanvas;\n } {\n return {\n width: config.width || 720,\n height: config.height || 1280,\n fps: config.fps || 30,\n backgroundColor: config.backgroundColor ?? '#000',\n renderer: config.renderer ?? 'canvas2d',\n enableSmoothing: config.enableSmoothing ?? true,\n enableHardwareAcceleration: config.enableHardwareAcceleration ?? true,\n revision: config.revision ?? 0,\n inputHighWaterMark: config.inputHighWaterMark ?? 3,\n outputHighWaterMark: config.outputHighWaterMark ?? 1,\n maxLayers: config.maxLayers ?? 100,\n timeline: config.timeline ?? {\n clipId: 'default',\n trackId: 'main',\n clipStartUs: 0,\n clipDurationUs: Infinity,\n compositionFps: 30,\n },\n fonts: config.fonts ?? [],\n externalCanvas: config.externalCanvas,\n };\n }\n\n createStreams(_instruction?: ClipInstructionSet): TransformStream<ComposeRequest, VideoFrame> {\n // Always create new streams for each clip\n // ReadableStreams can only be consumed once\n const stream = new TransformStream<ComposeRequest, VideoFrame>(\n {\n transform: async (request, controller) => {\n // console.log('[VideoComposer] transform', request, controller.desiredSize);\n const result = await this.composeFrame(request);\n if (result.frame) {\n controller.enqueue(result.frame);\n }\n // setTimeout(() => {\n // result.frame.close();\n // }, 1000);\n },\n\n flush: async () => {\n this.filterProcessor.clearCache();\n },\n },\n {\n highWaterMark: this.config.inputHighWaterMark,\n },\n {\n highWaterMark: this.config.outputHighWaterMark,\n }\n );\n return stream;\n }\n async composeFrame(request: ComposeRequest): Promise<ComposeResult> {\n // Ensure fonts are loaded before rendering\n if (this.fontsLoadingPromise) {\n await this.fontsLoadingPromise;\n this.fontsLoadingPromise = null; // Only wait once\n }\n\n if (request.layers.length > this.config.maxLayers) {\n throw new Error(`Too many layers: ${request.layers.length} > ${this.config.maxLayers}`);\n }\n\n this.clearCanvas();\n\n // Calculate current frame number for animations (relative to clip start)\n const frameDurationUs = 1_000_000 / this.config.fps;\n const relativeFrame = Math.floor(request.timeUs / frameDurationUs);\n this.layerRenderer.setCurrentFrame(relativeFrame);\n\n if (request.transition) {\n this.ctx.save();\n this.transitionProcessor.applyTransition(this.ctx, request.transition);\n }\n\n for (const layer of request.layers) {\n if (!layer.visible || layer.opacity <= 0) {\n // Close video frame for invisible layers\n closeLayerFrame(layer);\n continue;\n }\n\n try {\n // If layer has RcFrame, use it within the rendering scope\n if ((layer as any).rcFrame) {\n await (layer as any).rcFrame.use(async (frame: VideoFrame) => {\n // Set the cloned frame for this render cycle\n (layer as VideoLayer).videoFrame = frame;\n\n if (layer.filters && layer.filters.length > 0) {\n this.ctx.save();\n this.filterProcessor.applyFilters(this.ctx, layer.filters);\n }\n await this.layerRenderer.renderLayer(layer);\n\n if (layer.filters && layer.filters.length > 0) {\n this.ctx.restore();\n }\n });\n } else {\n // Regular layer without RcFrame\n if (layer.filters && layer.filters.length > 0) {\n this.ctx.save();\n this.filterProcessor.applyFilters(this.ctx, layer.filters);\n }\n await this.layerRenderer.renderLayer(layer);\n\n if (layer.filters && layer.filters.length > 0) {\n this.ctx.restore();\n }\n }\n } catch (error) {\n console.error('[VideoComposer] composeFrame error: ', error);\n // Always close video frame after rendering (or on error)\n closeLayerFrame(layer);\n }\n }\n\n if (request.transition) {\n this.ctx.restore();\n }\n\n let frame: VideoFrame | null = null;\n if (!this.config.externalCanvas) {\n frame = await this.createOutputFrame(request.timeUs);\n }\n\n return {\n frame,\n timeUs: request.timeUs,\n };\n }\n\n async composeTransition(\n fromRequest: ComposeRequest,\n toRequest: ComposeRequest,\n transition: TransitionEffect\n ): Promise<ComposeResult> {\n await this.composeFrame(fromRequest);\n\n const toFrameRequest = {\n ...toRequest,\n transition,\n };\n\n return this.composeFrame(toFrameRequest);\n }\n\n private clearCanvas(): void {\n if (this.config.backgroundColor) {\n this.ctx.fillStyle = this.config.backgroundColor;\n this.ctx.fillRect(0, 0, this.config.width, this.config.height);\n } else {\n this.ctx.clearRect(0, 0, this.config.width, this.config.height);\n }\n }\n\n // private sortLayers(layers: Layer[]): Layer[] {\n // return [...layers].sort((a, b) => a.zIndex - b.zIndex);\n // }\n\n private async createOutputFrame(timeUs: number): Promise<VideoFrame> {\n const frame = new VideoFrame(this.canvas, {\n timestamp: timeUs,\n duration: this.frameDurationUs, // Use cached duration\n alpha: 'discard',\n visibleRect: { x: 0, y: 0, width: this.canvas.width, height: this.canvas.height },\n });\n return frame;\n }\n\n private async loadFonts(): Promise<void> {\n if (!this.config.fonts || this.config.fonts.length === 0) {\n return;\n }\n\n for (const font of this.config.fonts) {\n if (this.loadedFonts.has(font.family)) {\n continue;\n }\n\n try {\n const fontFace = new FontFace(font.family, `url(${font.url})`);\n const loadedFont = await fontFace.load();\n\n if ('fonts' in self) {\n self.fonts.add(loadedFont);\n }\n\n this.loadedFonts.add(font.family);\n } catch {\n // Font loading failed, will fallback to system fonts\n }\n }\n }\n\n updateConfig(config: Partial<VideoComposeConfig>): void {\n Object.assign(this.config, this.applyDefaults({ ...this.config, ...config }));\n\n if (config.fps !== undefined) {\n this.frameDurationUs = Math.round(1_000_000 / this.config.fps); // Update cached duration\n }\n\n if (config.width || config.height) {\n this.canvas.width = this.config.width;\n this.canvas.height = this.config.height;\n this.layerRenderer.updateDimensions(this.config.width, this.config.height);\n this.transitionProcessor.updateDimensions(this.config.width, this.config.height);\n }\n\n if (config.enableSmoothing !== undefined) {\n this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;\n }\n\n if (config.fonts) {\n this.fontsLoadingPromise = this.loadFonts();\n }\n }\n\n dispose(): void {\n this.filterProcessor.clearCache();\n }\n}\n"],"names":["frame"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"font-templates.d.ts","sourceRoot":"","sources":["../../../../src/stages/compose/font-system/font-templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/B,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACzC,cAAc,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC/C;AAED,wBAAgB,kBAAkB,CAAC,GAAG,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CAgB7E;AAqVD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA6DvD,CAAC;AAEF,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,CA0B9D"}
1
+ {"version":3,"file":"font-templates.d.ts","sourceRoot":"","sources":["../../../../src/stages/compose/font-system/font-templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/B,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACzC,cAAc,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC/C;AAED,wBAAgB,kBAAkB,CAAC,GAAG,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CAgB7E;AAgWD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA+DvD,CAAC;AAEF,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,CA0B9D"}
@@ -238,6 +238,13 @@ const globalTop80 = {
238
238
  top: "80%"
239
239
  }
240
240
  };
241
+ const globalTop92 = {
242
+ name: "global-top-92",
243
+ globalPosition: {
244
+ position: "absolute",
245
+ top: "92%"
246
+ }
247
+ };
241
248
  const globalBottom5 = {
242
249
  name: "global-bottom-5",
243
250
  globalPosition: {
@@ -268,13 +275,15 @@ blackText.name = "blackText";
268
275
  const baseSubtitle = mergeFontTemplates(normal40, fillWhite, strokeBlack8, globalTop70);
269
276
  baseSubtitle.name = "baseSubtitle";
270
277
  const baseSubtitleCenter = mergeFontTemplates(
271
- normal50,
278
+ normal40,
272
279
  fillWhite,
273
- strokeBlack16,
274
- globalCenter,
280
+ strokeBlack8,
281
+ globalTop70,
275
282
  container60
276
283
  );
277
284
  baseSubtitleCenter.name = "baseSubtitleCenter";
285
+ const baseSubtitle16_9 = mergeFontTemplates(normal40, fillWhite, strokeBlack8, globalTop92);
286
+ baseSubtitle16_9.name = "baseSubtitle16_9";
278
287
  const yellowSubtitle = mergeFontTemplates(yellowText, globalTop70);
279
288
  yellowSubtitle.name = "yellowSubtitle";
280
289
  const whiteSubtitle = mergeFontTemplates(whiteText, globalTop70);
@@ -339,6 +348,7 @@ const FONT_TEMPLATES = {
339
348
  "global-top-20": globalTop20,
340
349
  "global-top-70": globalTop70,
341
350
  "global-top-80": globalTop80,
351
+ "global-top-92": globalTop92,
342
352
  "global-bottom-5": globalBottom5,
343
353
  "global-bottom-10": globalBottom10,
344
354
  "global-bottom-15": globalBottom15,
@@ -347,6 +357,7 @@ const FONT_TEMPLATES = {
347
357
  blackText,
348
358
  baseSubtitle,
349
359
  baseSubtitleCenter,
360
+ baseSubtitle16_9,
350
361
  yellowSubtitle,
351
362
  whiteSubtitle,
352
363
  blackSubtitle,
@@ -366,7 +377,7 @@ function getTemplate(templateName) {
366
377
  fontFamily: "Arial, sans-serif",
367
378
  fill: "rgb(255, 255, 255)",
368
379
  stroke: "rgb(0, 0, 0)",
369
- strokeWidth: 4,
380
+ strokeWidth: 8,
370
381
  lineHeight: 1.2
371
382
  }
372
383
  };
@@ -1 +1 @@
1
- {"version":3,"file":"font-templates.js","sources":["../../../../src/stages/compose/font-system/font-templates.ts"],"sourcesContent":["import type { TextStyle, ContainerStyle, GlobalPositionStyle } from './types';\n\nexport interface FontTemplate {\n name: string;\n textStyle?: Partial<TextStyle>;\n containerStyle?: Partial<ContainerStyle>;\n globalPosition?: Partial<GlobalPositionStyle>;\n}\n\nexport function mergeFontTemplates(...templates: FontTemplate[]): FontTemplate {\n const merged: FontTemplate = { name: 'merged' };\n\n for (const template of templates) {\n if (template.textStyle) {\n merged.textStyle = { ...merged.textStyle, ...template.textStyle };\n }\n if (template.containerStyle) {\n merged.containerStyle = { ...merged.containerStyle, ...template.containerStyle };\n }\n if (template.globalPosition) {\n merged.globalPosition = { ...merged.globalPosition, ...template.globalPosition };\n }\n }\n\n return merged;\n}\n\nconst normal30: FontTemplate = {\n name: 'normal-30',\n textStyle: { fontSize: 30 },\n};\n\nconst normal40: FontTemplate = {\n name: 'normal-40',\n textStyle: { fontSize: 40 },\n};\n\nconst normal50: FontTemplate = {\n name: 'normal-50',\n textStyle: { fontSize: 50 },\n};\n\nconst normal60: FontTemplate = {\n name: 'normal-60',\n textStyle: { fontSize: 60 },\n};\n\nconst normal80: FontTemplate = {\n name: 'normal-80',\n textStyle: { fontSize: 80 },\n};\n\nconst bold40: FontTemplate = {\n name: 'bold-40',\n textStyle: { fontWeight: 'bold', fontSize: 40 },\n};\n\nconst bold50: FontTemplate = {\n name: 'bold-50',\n textStyle: { fontWeight: 'bold', fontSize: 50 },\n};\n\nconst bold60: FontTemplate = {\n name: 'bold-60',\n textStyle: { fontWeight: 'bold', letterSpacing: '0.05em', fontSize: 60 },\n};\n\nconst fillBlack: FontTemplate = {\n name: 'fill-black',\n textStyle: { fill: 'rgb(0, 0, 0)' },\n};\n\nconst fillWhite: FontTemplate = {\n name: 'fill-white',\n textStyle: { fill: 'rgb(255, 255, 255)' },\n};\n\nconst fillYellow: FontTemplate = {\n name: 'fill-yellow',\n textStyle: { fill: 'rgb(255, 215, 0)' },\n};\n\nconst fillRed: FontTemplate = {\n name: 'fill-red',\n textStyle: { fill: 'rgb(255, 77, 77)' },\n};\n\nconst fillPink: FontTemplate = {\n name: 'fill-pink',\n textStyle: { fill: 'rgb(255, 192, 203)' },\n};\n\nconst strokeBlack: FontTemplate = {\n name: 'stroke-black',\n textStyle: {\n paintOrder: 'stroke',\n strokeLinejoin: 'round',\n strokeLinecap: 'round',\n stroke: 'rgb(0, 0, 0)',\n },\n};\n\nconst strokeWhite: FontTemplate = {\n name: 'stroke-white',\n textStyle: {\n paintOrder: 'stroke',\n strokeLinejoin: 'round',\n strokeLinecap: 'round',\n stroke: 'rgb(255, 255, 255)',\n },\n};\n\nconst strokeYellow: FontTemplate = {\n name: 'stroke-yellow',\n textStyle: {\n paintOrder: 'stroke',\n strokeLinejoin: 'round',\n strokeLinecap: 'round',\n stroke: 'rgb(255, 215, 0)',\n },\n};\n\nconst stroke0: FontTemplate = {\n name: 'stroke-0',\n textStyle: { strokeWidth: 0 },\n};\n\nconst stroke2: FontTemplate = {\n name: 'stroke-2',\n textStyle: { strokeWidth: 2 },\n};\n\nconst stroke4: FontTemplate = {\n name: 'stroke-4',\n textStyle: { strokeWidth: 4 },\n};\n\nconst stroke6: FontTemplate = {\n name: 'stroke-6',\n textStyle: { strokeWidth: 6 },\n};\n\nconst stroke8: FontTemplate = {\n name: 'stroke-8',\n textStyle: { strokeWidth: 8 },\n};\n\nconst stroke16: FontTemplate = {\n name: 'stroke-16',\n textStyle: { strokeWidth: 16 },\n};\n\nconst strokeBlack4 = mergeFontTemplates(strokeBlack, stroke4);\nstrokeBlack4.name = 'stroke-black-4';\n\nconst strokeBlack6 = mergeFontTemplates(strokeBlack, stroke6);\nstrokeBlack6.name = 'stroke-black-6';\n\nconst strokeBlack8 = mergeFontTemplates(strokeBlack, stroke8);\nstrokeBlack8.name = 'stroke-black-8';\n\nconst strokeBlack16 = mergeFontTemplates(strokeBlack, stroke16);\nstrokeBlack16.name = 'stroke-black-16';\n\nconst strokeWhite4 = mergeFontTemplates(strokeWhite, stroke4);\nstrokeWhite4.name = 'stroke-white-4';\n\nconst strokeWhite8 = mergeFontTemplates(strokeWhite, stroke8);\nstrokeWhite8.name = 'stroke-white-8';\n\nconst strokeYellow4 = mergeFontTemplates(strokeYellow, stroke4);\nstrokeYellow4.name = 'stroke-yellow-4';\n\nconst strokeYellow8 = mergeFontTemplates(strokeYellow, stroke8);\nstrokeYellow8.name = 'stroke-yellow-8';\n\nconst containerFull: FontTemplate = {\n name: 'container-full',\n containerStyle: { width: '100%' },\n};\n\nconst container60: FontTemplate = {\n name: 'container-60',\n containerStyle: { width: '60%' },\n};\n\nconst container80: FontTemplate = {\n name: 'container-80',\n containerStyle: { width: '80%' },\n};\n\nconst container95: FontTemplate = {\n name: 'container-95',\n containerStyle: { width: '95%' },\n};\n\nconst container98: FontTemplate = {\n name: 'container-98',\n containerStyle: { width: '98%' },\n};\n\nconst containerBlack: FontTemplate = {\n name: 'container-black',\n containerStyle: {\n backgroundColor: 'black',\n padding: '10px 10px 4px 10px',\n borderRadius: 10,\n width: '80%',\n },\n};\n\nconst containerWhite: FontTemplate = {\n name: 'container-white',\n containerStyle: {\n backgroundColor: 'white',\n padding: '10px 10px 4px 10px',\n borderRadius: 10,\n width: '80%',\n },\n};\n\nconst containerYellow: FontTemplate = {\n name: 'container-yellow',\n containerStyle: {\n backgroundColor: 'yellow',\n padding: '10px 10px 4px 10px',\n borderRadius: 10,\n width: '80%',\n },\n};\n\nconst containerBlackOpacity40: FontTemplate = {\n name: 'container-black-opacity-40',\n containerStyle: {\n backgroundColor: 'rgba(0, 0, 0, 0.4)',\n padding: '10px 10px 4px 10px',\n borderRadius: 10,\n width: '80%',\n },\n};\n\nconst globalCenter: FontTemplate = {\n name: 'global-center',\n globalPosition: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n },\n};\n\nconst globalHorizontalCenter: FontTemplate = {\n name: 'global-horizontal-center',\n globalPosition: {\n display: 'flex',\n justifyContent: 'center',\n },\n};\n\nconst globalTop5: FontTemplate = {\n name: 'global-top-5',\n globalPosition: {\n position: 'absolute',\n top: '5%',\n },\n};\n\nconst globalTop10: FontTemplate = {\n name: 'global-top-10',\n globalPosition: {\n position: 'absolute',\n top: '10%',\n },\n};\n\nconst globalTop20: FontTemplate = {\n name: 'global-top-20',\n globalPosition: {\n position: 'absolute',\n top: '20%',\n },\n};\n\nconst globalTop70: FontTemplate = {\n name: 'global-top-70',\n globalPosition: {\n position: 'absolute',\n top: '70%',\n },\n};\n\nconst globalTop80: FontTemplate = {\n name: 'global-top-80',\n globalPosition: {\n position: 'absolute',\n top: '80%',\n },\n};\n\nconst globalBottom5: FontTemplate = {\n name: 'global-bottom-5',\n globalPosition: {\n position: 'absolute',\n bottom: '5%',\n },\n};\n\nconst globalBottom10: FontTemplate = {\n name: 'global-bottom-10',\n globalPosition: {\n position: 'absolute',\n bottom: '10%',\n },\n};\n\nconst globalBottom15: FontTemplate = {\n name: 'global-bottom-15',\n globalPosition: {\n position: 'absolute',\n bottom: '15%',\n },\n};\n\nconst yellowText = mergeFontTemplates(normal40, fillYellow, strokeBlack8);\nyellowText.name = 'yellowText';\n\nconst whiteText = mergeFontTemplates(normal40, fillWhite, strokeBlack8);\nwhiteText.name = 'whiteText';\n\nconst blackText = mergeFontTemplates(normal40, fillBlack, strokeWhite8);\nblackText.name = 'blackText';\n\nconst baseSubtitle = mergeFontTemplates(normal40, fillWhite, strokeBlack8, globalTop70);\nbaseSubtitle.name = 'baseSubtitle';\n\nconst baseSubtitleCenter = mergeFontTemplates(\n normal50,\n fillWhite,\n strokeBlack16,\n globalCenter,\n container60\n);\nbaseSubtitleCenter.name = 'baseSubtitleCenter';\n\nconst yellowSubtitle = mergeFontTemplates(yellowText, globalTop70);\nyellowSubtitle.name = 'yellowSubtitle';\n\nconst whiteSubtitle = mergeFontTemplates(whiteText, globalTop70);\nwhiteSubtitle.name = 'whiteSubtitle';\n\nconst blackSubtitle = mergeFontTemplates(blackText, globalTop70);\nblackSubtitle.name = 'blackSubtitle';\n\nconst subtitleBackgroundBlack = mergeFontTemplates(\n normal40,\n fillWhite,\n globalTop70,\n containerBlack\n);\nsubtitleBackgroundBlack.name = 'subtitleBackgroundBlack';\n\nconst title = mergeFontTemplates(yellowText, bold50, globalTop5, container95);\ntitle.name = 'title';\n\nconst subtitle = mergeFontTemplates(normal30, fillWhite, strokeBlack8, globalTop5, container80);\nsubtitle.name = 'subtitle';\n\nexport const FONT_TEMPLATES: Record<string, FontTemplate> = {\n 'normal-30': normal30,\n 'normal-40': normal40,\n 'normal-50': normal50,\n 'normal-60': normal60,\n 'normal-80': normal80,\n 'bold-40': bold40,\n 'bold-50': bold50,\n 'bold-60': bold60,\n 'fill-black': fillBlack,\n 'fill-white': fillWhite,\n 'fill-yellow': fillYellow,\n 'fill-red': fillRed,\n 'fill-pink': fillPink,\n 'stroke-black': strokeBlack,\n 'stroke-white': strokeWhite,\n 'stroke-yellow': strokeYellow,\n 'stroke-0': stroke0,\n 'stroke-2': stroke2,\n 'stroke-4': stroke4,\n 'stroke-6': stroke6,\n 'stroke-8': stroke8,\n 'stroke-16': stroke16,\n 'stroke-black-4': strokeBlack4,\n 'stroke-black-6': strokeBlack6,\n 'stroke-black-8': strokeBlack8,\n 'stroke-black-16': strokeBlack16,\n 'stroke-white-4': strokeWhite4,\n 'stroke-white-8': strokeWhite8,\n 'stroke-yellow-4': strokeYellow4,\n 'stroke-yellow-8': strokeYellow8,\n 'container-full': containerFull,\n 'container-60': container60,\n 'container-80': container80,\n 'container-95': container95,\n 'container-98': container98,\n 'container-black': containerBlack,\n 'container-white': containerWhite,\n 'container-yellow': containerYellow,\n 'container-black-opacity-40': containerBlackOpacity40,\n 'global-center': globalCenter,\n 'global-horizontal-center': globalHorizontalCenter,\n 'global-top-5': globalTop5,\n 'global-top-10': globalTop10,\n 'global-top-20': globalTop20,\n 'global-top-70': globalTop70,\n 'global-top-80': globalTop80,\n 'global-bottom-5': globalBottom5,\n 'global-bottom-10': globalBottom10,\n 'global-bottom-15': globalBottom15,\n yellowText,\n whiteText,\n blackText,\n baseSubtitle,\n baseSubtitleCenter,\n yellowSubtitle,\n whiteSubtitle,\n blackSubtitle,\n subtitleBackgroundBlack,\n title,\n subtitle,\n};\n\nexport function getTemplate(templateName: string): FontTemplate {\n const templateNames = templateName.split(' ').filter(Boolean);\n const templates = templateNames\n .map((name) => FONT_TEMPLATES[name])\n .filter((t): t is FontTemplate => t !== undefined);\n\n if (templates.length === 0) {\n return {\n name: 'default',\n textStyle: {\n fontSize: 40,\n fontWeight: 400,\n fontFamily: 'Arial, sans-serif',\n fill: 'rgb(255, 255, 255)',\n stroke: 'rgb(0, 0, 0)',\n strokeWidth: 4,\n lineHeight: 1.2,\n },\n };\n }\n\n if (templates.length === 1) {\n return templates[0]!;\n }\n\n return mergeFontTemplates(...templates);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"font-templates.js","sources":["../../../../src/stages/compose/font-system/font-templates.ts"],"sourcesContent":["import type { TextStyle, ContainerStyle, GlobalPositionStyle } from './types';\n\nexport interface FontTemplate {\n name: string;\n textStyle?: Partial<TextStyle>;\n containerStyle?: Partial<ContainerStyle>;\n globalPosition?: Partial<GlobalPositionStyle>;\n}\n\nexport function mergeFontTemplates(...templates: FontTemplate[]): FontTemplate {\n const merged: FontTemplate = { name: 'merged' };\n\n for (const template of templates) {\n if (template.textStyle) {\n merged.textStyle = { ...merged.textStyle, ...template.textStyle };\n }\n if (template.containerStyle) {\n merged.containerStyle = { ...merged.containerStyle, ...template.containerStyle };\n }\n if (template.globalPosition) {\n merged.globalPosition = { ...merged.globalPosition, ...template.globalPosition };\n }\n }\n\n return merged;\n}\n\nconst normal30: FontTemplate = {\n name: 'normal-30',\n textStyle: { fontSize: 30 },\n};\n\nconst normal40: FontTemplate = {\n name: 'normal-40',\n textStyle: { fontSize: 40 },\n};\n\nconst normal50: FontTemplate = {\n name: 'normal-50',\n textStyle: { fontSize: 50 },\n};\n\nconst normal60: FontTemplate = {\n name: 'normal-60',\n textStyle: { fontSize: 60 },\n};\n\nconst normal80: FontTemplate = {\n name: 'normal-80',\n textStyle: { fontSize: 80 },\n};\n\nconst bold40: FontTemplate = {\n name: 'bold-40',\n textStyle: { fontWeight: 'bold', fontSize: 40 },\n};\n\nconst bold50: FontTemplate = {\n name: 'bold-50',\n textStyle: { fontWeight: 'bold', fontSize: 50 },\n};\n\nconst bold60: FontTemplate = {\n name: 'bold-60',\n textStyle: { fontWeight: 'bold', letterSpacing: '0.05em', fontSize: 60 },\n};\n\nconst fillBlack: FontTemplate = {\n name: 'fill-black',\n textStyle: { fill: 'rgb(0, 0, 0)' },\n};\n\nconst fillWhite: FontTemplate = {\n name: 'fill-white',\n textStyle: { fill: 'rgb(255, 255, 255)' },\n};\n\nconst fillYellow: FontTemplate = {\n name: 'fill-yellow',\n textStyle: { fill: 'rgb(255, 215, 0)' },\n};\n\nconst fillRed: FontTemplate = {\n name: 'fill-red',\n textStyle: { fill: 'rgb(255, 77, 77)' },\n};\n\nconst fillPink: FontTemplate = {\n name: 'fill-pink',\n textStyle: { fill: 'rgb(255, 192, 203)' },\n};\n\nconst strokeBlack: FontTemplate = {\n name: 'stroke-black',\n textStyle: {\n paintOrder: 'stroke',\n strokeLinejoin: 'round',\n strokeLinecap: 'round',\n stroke: 'rgb(0, 0, 0)',\n },\n};\n\nconst strokeWhite: FontTemplate = {\n name: 'stroke-white',\n textStyle: {\n paintOrder: 'stroke',\n strokeLinejoin: 'round',\n strokeLinecap: 'round',\n stroke: 'rgb(255, 255, 255)',\n },\n};\n\nconst strokeYellow: FontTemplate = {\n name: 'stroke-yellow',\n textStyle: {\n paintOrder: 'stroke',\n strokeLinejoin: 'round',\n strokeLinecap: 'round',\n stroke: 'rgb(255, 215, 0)',\n },\n};\n\nconst stroke0: FontTemplate = {\n name: 'stroke-0',\n textStyle: { strokeWidth: 0 },\n};\n\nconst stroke2: FontTemplate = {\n name: 'stroke-2',\n textStyle: { strokeWidth: 2 },\n};\n\nconst stroke4: FontTemplate = {\n name: 'stroke-4',\n textStyle: { strokeWidth: 4 },\n};\n\nconst stroke6: FontTemplate = {\n name: 'stroke-6',\n textStyle: { strokeWidth: 6 },\n};\n\nconst stroke8: FontTemplate = {\n name: 'stroke-8',\n textStyle: { strokeWidth: 8 },\n};\n\nconst stroke16: FontTemplate = {\n name: 'stroke-16',\n textStyle: { strokeWidth: 16 },\n};\n\nconst strokeBlack4 = mergeFontTemplates(strokeBlack, stroke4);\nstrokeBlack4.name = 'stroke-black-4';\n\nconst strokeBlack6 = mergeFontTemplates(strokeBlack, stroke6);\nstrokeBlack6.name = 'stroke-black-6';\n\nconst strokeBlack8 = mergeFontTemplates(strokeBlack, stroke8);\nstrokeBlack8.name = 'stroke-black-8';\n\nconst strokeBlack16 = mergeFontTemplates(strokeBlack, stroke16);\nstrokeBlack16.name = 'stroke-black-16';\n\nconst strokeWhite4 = mergeFontTemplates(strokeWhite, stroke4);\nstrokeWhite4.name = 'stroke-white-4';\n\nconst strokeWhite8 = mergeFontTemplates(strokeWhite, stroke8);\nstrokeWhite8.name = 'stroke-white-8';\n\nconst strokeYellow4 = mergeFontTemplates(strokeYellow, stroke4);\nstrokeYellow4.name = 'stroke-yellow-4';\n\nconst strokeYellow8 = mergeFontTemplates(strokeYellow, stroke8);\nstrokeYellow8.name = 'stroke-yellow-8';\n\nconst containerFull: FontTemplate = {\n name: 'container-full',\n containerStyle: { width: '100%' },\n};\n\nconst container60: FontTemplate = {\n name: 'container-60',\n containerStyle: { width: '60%' },\n};\n\nconst container80: FontTemplate = {\n name: 'container-80',\n containerStyle: { width: '80%' },\n};\n\nconst container95: FontTemplate = {\n name: 'container-95',\n containerStyle: { width: '95%' },\n};\n\nconst container98: FontTemplate = {\n name: 'container-98',\n containerStyle: { width: '98%' },\n};\n\nconst containerBlack: FontTemplate = {\n name: 'container-black',\n containerStyle: {\n backgroundColor: 'black',\n padding: '10px 10px 4px 10px',\n borderRadius: 10,\n width: '80%',\n },\n};\n\nconst containerWhite: FontTemplate = {\n name: 'container-white',\n containerStyle: {\n backgroundColor: 'white',\n padding: '10px 10px 4px 10px',\n borderRadius: 10,\n width: '80%',\n },\n};\n\nconst containerYellow: FontTemplate = {\n name: 'container-yellow',\n containerStyle: {\n backgroundColor: 'yellow',\n padding: '10px 10px 4px 10px',\n borderRadius: 10,\n width: '80%',\n },\n};\n\nconst containerBlackOpacity40: FontTemplate = {\n name: 'container-black-opacity-40',\n containerStyle: {\n backgroundColor: 'rgba(0, 0, 0, 0.4)',\n padding: '10px 10px 4px 10px',\n borderRadius: 10,\n width: '80%',\n },\n};\n\nconst globalCenter: FontTemplate = {\n name: 'global-center',\n globalPosition: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n },\n};\n\nconst globalHorizontalCenter: FontTemplate = {\n name: 'global-horizontal-center',\n globalPosition: {\n display: 'flex',\n justifyContent: 'center',\n },\n};\n\nconst globalTop5: FontTemplate = {\n name: 'global-top-5',\n globalPosition: {\n position: 'absolute',\n top: '5%',\n },\n};\n\nconst globalTop10: FontTemplate = {\n name: 'global-top-10',\n globalPosition: {\n position: 'absolute',\n top: '10%',\n },\n};\n\nconst globalTop20: FontTemplate = {\n name: 'global-top-20',\n globalPosition: {\n position: 'absolute',\n top: '20%',\n },\n};\n\nconst globalTop70: FontTemplate = {\n name: 'global-top-70',\n globalPosition: {\n position: 'absolute',\n top: '70%',\n },\n};\n\nconst globalTop80: FontTemplate = {\n name: 'global-top-80',\n globalPosition: {\n position: 'absolute',\n top: '80%',\n },\n};\n\nconst globalTop92: FontTemplate = {\n name: 'global-top-92',\n globalPosition: {\n position: 'absolute',\n top: '92%',\n },\n};\n\nconst globalBottom5: FontTemplate = {\n name: 'global-bottom-5',\n globalPosition: {\n position: 'absolute',\n bottom: '5%',\n },\n};\n\nconst globalBottom10: FontTemplate = {\n name: 'global-bottom-10',\n globalPosition: {\n position: 'absolute',\n bottom: '10%',\n },\n};\n\nconst globalBottom15: FontTemplate = {\n name: 'global-bottom-15',\n globalPosition: {\n position: 'absolute',\n bottom: '15%',\n },\n};\n\nconst yellowText = mergeFontTemplates(normal40, fillYellow, strokeBlack8);\nyellowText.name = 'yellowText';\n\nconst whiteText = mergeFontTemplates(normal40, fillWhite, strokeBlack8);\nwhiteText.name = 'whiteText';\n\nconst blackText = mergeFontTemplates(normal40, fillBlack, strokeWhite8);\nblackText.name = 'blackText';\n\nconst baseSubtitle = mergeFontTemplates(normal40, fillWhite, strokeBlack8, globalTop70);\nbaseSubtitle.name = 'baseSubtitle';\n\nconst baseSubtitleCenter = mergeFontTemplates(\n normal40,\n fillWhite,\n strokeBlack8,\n globalTop70,\n container60\n);\nbaseSubtitleCenter.name = 'baseSubtitleCenter';\n\nconst baseSubtitle16_9 = mergeFontTemplates(normal40, fillWhite, strokeBlack8, globalTop92);\nbaseSubtitle16_9.name = 'baseSubtitle16_9';\n\nconst yellowSubtitle = mergeFontTemplates(yellowText, globalTop70);\nyellowSubtitle.name = 'yellowSubtitle';\n\nconst whiteSubtitle = mergeFontTemplates(whiteText, globalTop70);\nwhiteSubtitle.name = 'whiteSubtitle';\n\nconst blackSubtitle = mergeFontTemplates(blackText, globalTop70);\nblackSubtitle.name = 'blackSubtitle';\n\nconst subtitleBackgroundBlack = mergeFontTemplates(\n normal40,\n fillWhite,\n globalTop70,\n containerBlack\n);\nsubtitleBackgroundBlack.name = 'subtitleBackgroundBlack';\n\nconst title = mergeFontTemplates(yellowText, bold50, globalTop5, container95);\ntitle.name = 'title';\n\nconst subtitle = mergeFontTemplates(normal30, fillWhite, strokeBlack8, globalTop5, container80);\nsubtitle.name = 'subtitle';\n\nexport const FONT_TEMPLATES: Record<string, FontTemplate> = {\n 'normal-30': normal30,\n 'normal-40': normal40,\n 'normal-50': normal50,\n 'normal-60': normal60,\n 'normal-80': normal80,\n 'bold-40': bold40,\n 'bold-50': bold50,\n 'bold-60': bold60,\n 'fill-black': fillBlack,\n 'fill-white': fillWhite,\n 'fill-yellow': fillYellow,\n 'fill-red': fillRed,\n 'fill-pink': fillPink,\n 'stroke-black': strokeBlack,\n 'stroke-white': strokeWhite,\n 'stroke-yellow': strokeYellow,\n 'stroke-0': stroke0,\n 'stroke-2': stroke2,\n 'stroke-4': stroke4,\n 'stroke-6': stroke6,\n 'stroke-8': stroke8,\n 'stroke-16': stroke16,\n 'stroke-black-4': strokeBlack4,\n 'stroke-black-6': strokeBlack6,\n 'stroke-black-8': strokeBlack8,\n 'stroke-black-16': strokeBlack16,\n 'stroke-white-4': strokeWhite4,\n 'stroke-white-8': strokeWhite8,\n 'stroke-yellow-4': strokeYellow4,\n 'stroke-yellow-8': strokeYellow8,\n 'container-full': containerFull,\n 'container-60': container60,\n 'container-80': container80,\n 'container-95': container95,\n 'container-98': container98,\n 'container-black': containerBlack,\n 'container-white': containerWhite,\n 'container-yellow': containerYellow,\n 'container-black-opacity-40': containerBlackOpacity40,\n 'global-center': globalCenter,\n 'global-horizontal-center': globalHorizontalCenter,\n 'global-top-5': globalTop5,\n 'global-top-10': globalTop10,\n 'global-top-20': globalTop20,\n 'global-top-70': globalTop70,\n 'global-top-80': globalTop80,\n 'global-top-92': globalTop92,\n 'global-bottom-5': globalBottom5,\n 'global-bottom-10': globalBottom10,\n 'global-bottom-15': globalBottom15,\n yellowText,\n whiteText,\n blackText,\n baseSubtitle,\n baseSubtitleCenter,\n baseSubtitle16_9,\n yellowSubtitle,\n whiteSubtitle,\n blackSubtitle,\n subtitleBackgroundBlack,\n title,\n subtitle,\n};\n\nexport function getTemplate(templateName: string): FontTemplate {\n const templateNames = templateName.split(' ').filter(Boolean);\n const templates = templateNames\n .map((name) => FONT_TEMPLATES[name])\n .filter((t): t is FontTemplate => t !== undefined);\n\n if (templates.length === 0) {\n return {\n name: 'default',\n textStyle: {\n fontSize: 40,\n fontWeight: 400,\n fontFamily: 'Arial, sans-serif',\n fill: 'rgb(255, 255, 255)',\n stroke: 'rgb(0, 0, 0)',\n strokeWidth: 8,\n lineHeight: 1.2,\n },\n };\n }\n\n if (templates.length === 1) {\n return templates[0]!;\n }\n\n return mergeFontTemplates(...templates);\n}\n"],"names":[],"mappings":"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"}