ugcinc-render 1.4.0 → 1.5.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.
- package/dist/index.d.mts +55 -18
- package/dist/index.d.ts +55 -18
- package/dist/index.js +69 -6
- package/dist/index.mjs +69 -6
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -337,27 +337,47 @@ interface ImageEditorNodeConfig {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
/**
|
|
340
|
-
* Video editor types for
|
|
340
|
+
* Video editor types for the WEBAPP UI
|
|
341
|
+
*
|
|
342
|
+
* These types include UI-only fields (marked with comments).
|
|
343
|
+
* The renderer uses types from editor.ts and segment.ts instead.
|
|
344
|
+
*
|
|
345
|
+
* UI-only fields are stripped before sending to renderer:
|
|
346
|
+
* - timeMode: UI helper for displaying timing mode
|
|
347
|
+
* - inputRef/textInputRef: Resolved to source/text before rendering
|
|
348
|
+
* - dimensionPreset: Renderer uses width/height directly
|
|
349
|
+
* - previewUrls/previewTextValues: Only for UI preview
|
|
341
350
|
*/
|
|
342
351
|
|
|
343
352
|
/**
|
|
344
|
-
* UI helper for timing mode
|
|
353
|
+
* UI helper for timing mode - not sent to renderer
|
|
345
354
|
*/
|
|
346
355
|
type TimeMode = 'fixed' | 'flexible';
|
|
347
356
|
/**
|
|
348
|
-
* Base properties for all video editor segments
|
|
357
|
+
* Base properties for all video editor segments (UI)
|
|
349
358
|
*/
|
|
350
359
|
interface VideoEditorBaseSegment {
|
|
360
|
+
/** Unique segment identifier */
|
|
351
361
|
id: string;
|
|
362
|
+
/** Order in the channel (0-based) */
|
|
352
363
|
order: number;
|
|
364
|
+
/** Time offset from previous segment or absolute position */
|
|
353
365
|
offset: TimeValue;
|
|
366
|
+
/** Segment duration - required for sequential playback */
|
|
354
367
|
duration?: TimeValue;
|
|
368
|
+
/** Trim from start in milliseconds */
|
|
355
369
|
startTrim?: number;
|
|
370
|
+
/** Trim from end in milliseconds */
|
|
356
371
|
endTrim?: number;
|
|
372
|
+
/** UI-only: helper for timing mode display - stripped before rendering */
|
|
357
373
|
timeMode?: TimeMode;
|
|
374
|
+
/** Parent segment ID for overlays */
|
|
358
375
|
parentId?: string;
|
|
376
|
+
/** Relative start (0-1) within parent for overlays */
|
|
359
377
|
relativeStart?: number;
|
|
378
|
+
/** Relative end (0-1) within parent for overlays */
|
|
360
379
|
relativeEnd?: number;
|
|
380
|
+
/** Fade-in duration in milliseconds */
|
|
361
381
|
fadeIn?: number;
|
|
362
382
|
}
|
|
363
383
|
/**
|
|
@@ -374,11 +394,13 @@ interface VideoEditorVisualSegment extends VideoEditorBaseSegment {
|
|
|
374
394
|
opacity?: number;
|
|
375
395
|
}
|
|
376
396
|
/**
|
|
377
|
-
* Video segment
|
|
397
|
+
* Video segment (UI)
|
|
378
398
|
*/
|
|
379
399
|
interface VideoEditorVideoSegment extends VideoEditorVisualSegment {
|
|
380
400
|
type: 'video';
|
|
401
|
+
/** UI-only: reference to input node - resolved to source before rendering */
|
|
381
402
|
inputRef?: string;
|
|
403
|
+
/** Actual source URL (set after inputRef is resolved) */
|
|
382
404
|
source?: string;
|
|
383
405
|
fit?: FitMode;
|
|
384
406
|
speed?: number;
|
|
@@ -386,31 +408,37 @@ interface VideoEditorVideoSegment extends VideoEditorVisualSegment {
|
|
|
386
408
|
borderRadius?: number | BorderRadiusConfig;
|
|
387
409
|
}
|
|
388
410
|
/**
|
|
389
|
-
* Audio segment
|
|
411
|
+
* Audio segment (UI)
|
|
390
412
|
*/
|
|
391
413
|
interface VideoEditorAudioSegment extends VideoEditorBaseSegment {
|
|
392
414
|
type: 'audio';
|
|
415
|
+
/** UI-only: reference to input node - resolved to source before rendering */
|
|
393
416
|
inputRef?: string;
|
|
417
|
+
/** Actual source URL (set after inputRef is resolved) */
|
|
394
418
|
source?: string;
|
|
395
419
|
volume?: number;
|
|
396
420
|
}
|
|
397
421
|
/**
|
|
398
|
-
* Image segment
|
|
422
|
+
* Image segment (UI)
|
|
399
423
|
*/
|
|
400
424
|
interface VideoEditorImageSegment extends VideoEditorVisualSegment {
|
|
401
425
|
type: 'image';
|
|
426
|
+
/** UI-only: reference to input node - resolved to source before rendering */
|
|
402
427
|
inputRef?: string;
|
|
428
|
+
/** Actual source URL (set after inputRef is resolved) */
|
|
403
429
|
source?: string;
|
|
404
430
|
fit?: FitMode;
|
|
405
431
|
loop?: boolean;
|
|
406
432
|
borderRadius?: number | BorderRadiusConfig;
|
|
407
433
|
}
|
|
408
434
|
/**
|
|
409
|
-
* Text segment
|
|
435
|
+
* Text segment (UI)
|
|
410
436
|
*/
|
|
411
437
|
interface VideoEditorTextSegment extends VideoEditorVisualSegment {
|
|
412
438
|
type: 'text';
|
|
439
|
+
/** UI-only: reference to text input - resolved to text before rendering */
|
|
413
440
|
textInputRef?: string;
|
|
441
|
+
/** Actual text content (set after textInputRef is resolved) */
|
|
414
442
|
text?: string;
|
|
415
443
|
alignment?: 'left' | 'center' | 'right' | 'justify';
|
|
416
444
|
verticalAlign?: 'top' | 'middle' | 'bottom';
|
|
@@ -445,16 +473,25 @@ interface VideoEditorChannel {
|
|
|
445
473
|
segments: VideoEditorSegment[];
|
|
446
474
|
}
|
|
447
475
|
/**
|
|
448
|
-
* Video editor node configuration
|
|
476
|
+
* Video editor node configuration for WEBAPP UI
|
|
477
|
+
*
|
|
478
|
+
* Duration is NOT stored - it's calculated from segment durations.
|
|
479
|
+
* dimensionPreset, previewUrls, previewTextValues are UI-only.
|
|
449
480
|
*/
|
|
450
481
|
interface VideoEditorNodeConfig {
|
|
482
|
+
/** Canvas width in pixels */
|
|
451
483
|
width: number;
|
|
484
|
+
/** Canvas height in pixels */
|
|
452
485
|
height: number;
|
|
486
|
+
/** Frames per second */
|
|
453
487
|
fps: number;
|
|
454
|
-
|
|
488
|
+
/** UI-only: dimension preset selector */
|
|
455
489
|
dimensionPreset: DimensionPresetKey;
|
|
490
|
+
/** Channels containing segments */
|
|
456
491
|
channels: VideoEditorChannel[];
|
|
492
|
+
/** UI-only: preview URLs for displaying in editor */
|
|
457
493
|
previewUrls?: Record<string, string>;
|
|
494
|
+
/** UI-only: preview text values for displaying in editor */
|
|
458
495
|
previewTextValues?: Record<string, string>;
|
|
459
496
|
}
|
|
460
497
|
/**
|
|
@@ -644,15 +681,16 @@ type VisualSegmentUnion = VideoSegment | ImageSegment | TextSegment;
|
|
|
644
681
|
type StaticSegment = ImageSegment | TextSegment;
|
|
645
682
|
|
|
646
683
|
/**
|
|
647
|
-
* Editor configuration types
|
|
684
|
+
* Editor configuration types for RENDERING
|
|
685
|
+
*
|
|
686
|
+
* These types define what the renderer receives.
|
|
687
|
+
* Duration is always calculated from segments - there is no duration field.
|
|
648
688
|
*
|
|
649
|
-
*
|
|
650
|
-
* timing, and channels containing segments.
|
|
689
|
+
* Note: UI-specific types (with previewUrls, dimensionPreset, etc.) are in video.ts
|
|
651
690
|
*/
|
|
652
691
|
|
|
653
692
|
/**
|
|
654
|
-
* A channel (track) containing segments
|
|
655
|
-
* Channels allow for parallel playback of segments
|
|
693
|
+
* A channel (track) containing segments for rendering
|
|
656
694
|
*/
|
|
657
695
|
interface Channel<T extends Segment = Segment> {
|
|
658
696
|
/** Unique channel identifier */
|
|
@@ -661,17 +699,16 @@ interface Channel<T extends Segment = Segment> {
|
|
|
661
699
|
segments: T[];
|
|
662
700
|
}
|
|
663
701
|
/**
|
|
664
|
-
* Base editor configuration
|
|
702
|
+
* Base editor configuration for rendering
|
|
703
|
+
* Duration is calculated from segments, not stored.
|
|
665
704
|
*/
|
|
666
705
|
interface BaseEditorConfig {
|
|
667
706
|
/** Canvas width in pixels */
|
|
668
707
|
width: number;
|
|
669
708
|
/** Canvas height in pixels */
|
|
670
709
|
height: number;
|
|
671
|
-
/** Frames per second
|
|
710
|
+
/** Frames per second */
|
|
672
711
|
fps: number;
|
|
673
|
-
/** Total duration in milliseconds (auto-calculated if 0 or undefined) */
|
|
674
|
-
duration?: number;
|
|
675
712
|
}
|
|
676
713
|
/**
|
|
677
714
|
* Full editor configuration with all segment types
|
package/dist/index.d.ts
CHANGED
|
@@ -337,27 +337,47 @@ interface ImageEditorNodeConfig {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
/**
|
|
340
|
-
* Video editor types for
|
|
340
|
+
* Video editor types for the WEBAPP UI
|
|
341
|
+
*
|
|
342
|
+
* These types include UI-only fields (marked with comments).
|
|
343
|
+
* The renderer uses types from editor.ts and segment.ts instead.
|
|
344
|
+
*
|
|
345
|
+
* UI-only fields are stripped before sending to renderer:
|
|
346
|
+
* - timeMode: UI helper for displaying timing mode
|
|
347
|
+
* - inputRef/textInputRef: Resolved to source/text before rendering
|
|
348
|
+
* - dimensionPreset: Renderer uses width/height directly
|
|
349
|
+
* - previewUrls/previewTextValues: Only for UI preview
|
|
341
350
|
*/
|
|
342
351
|
|
|
343
352
|
/**
|
|
344
|
-
* UI helper for timing mode
|
|
353
|
+
* UI helper for timing mode - not sent to renderer
|
|
345
354
|
*/
|
|
346
355
|
type TimeMode = 'fixed' | 'flexible';
|
|
347
356
|
/**
|
|
348
|
-
* Base properties for all video editor segments
|
|
357
|
+
* Base properties for all video editor segments (UI)
|
|
349
358
|
*/
|
|
350
359
|
interface VideoEditorBaseSegment {
|
|
360
|
+
/** Unique segment identifier */
|
|
351
361
|
id: string;
|
|
362
|
+
/** Order in the channel (0-based) */
|
|
352
363
|
order: number;
|
|
364
|
+
/** Time offset from previous segment or absolute position */
|
|
353
365
|
offset: TimeValue;
|
|
366
|
+
/** Segment duration - required for sequential playback */
|
|
354
367
|
duration?: TimeValue;
|
|
368
|
+
/** Trim from start in milliseconds */
|
|
355
369
|
startTrim?: number;
|
|
370
|
+
/** Trim from end in milliseconds */
|
|
356
371
|
endTrim?: number;
|
|
372
|
+
/** UI-only: helper for timing mode display - stripped before rendering */
|
|
357
373
|
timeMode?: TimeMode;
|
|
374
|
+
/** Parent segment ID for overlays */
|
|
358
375
|
parentId?: string;
|
|
376
|
+
/** Relative start (0-1) within parent for overlays */
|
|
359
377
|
relativeStart?: number;
|
|
378
|
+
/** Relative end (0-1) within parent for overlays */
|
|
360
379
|
relativeEnd?: number;
|
|
380
|
+
/** Fade-in duration in milliseconds */
|
|
361
381
|
fadeIn?: number;
|
|
362
382
|
}
|
|
363
383
|
/**
|
|
@@ -374,11 +394,13 @@ interface VideoEditorVisualSegment extends VideoEditorBaseSegment {
|
|
|
374
394
|
opacity?: number;
|
|
375
395
|
}
|
|
376
396
|
/**
|
|
377
|
-
* Video segment
|
|
397
|
+
* Video segment (UI)
|
|
378
398
|
*/
|
|
379
399
|
interface VideoEditorVideoSegment extends VideoEditorVisualSegment {
|
|
380
400
|
type: 'video';
|
|
401
|
+
/** UI-only: reference to input node - resolved to source before rendering */
|
|
381
402
|
inputRef?: string;
|
|
403
|
+
/** Actual source URL (set after inputRef is resolved) */
|
|
382
404
|
source?: string;
|
|
383
405
|
fit?: FitMode;
|
|
384
406
|
speed?: number;
|
|
@@ -386,31 +408,37 @@ interface VideoEditorVideoSegment extends VideoEditorVisualSegment {
|
|
|
386
408
|
borderRadius?: number | BorderRadiusConfig;
|
|
387
409
|
}
|
|
388
410
|
/**
|
|
389
|
-
* Audio segment
|
|
411
|
+
* Audio segment (UI)
|
|
390
412
|
*/
|
|
391
413
|
interface VideoEditorAudioSegment extends VideoEditorBaseSegment {
|
|
392
414
|
type: 'audio';
|
|
415
|
+
/** UI-only: reference to input node - resolved to source before rendering */
|
|
393
416
|
inputRef?: string;
|
|
417
|
+
/** Actual source URL (set after inputRef is resolved) */
|
|
394
418
|
source?: string;
|
|
395
419
|
volume?: number;
|
|
396
420
|
}
|
|
397
421
|
/**
|
|
398
|
-
* Image segment
|
|
422
|
+
* Image segment (UI)
|
|
399
423
|
*/
|
|
400
424
|
interface VideoEditorImageSegment extends VideoEditorVisualSegment {
|
|
401
425
|
type: 'image';
|
|
426
|
+
/** UI-only: reference to input node - resolved to source before rendering */
|
|
402
427
|
inputRef?: string;
|
|
428
|
+
/** Actual source URL (set after inputRef is resolved) */
|
|
403
429
|
source?: string;
|
|
404
430
|
fit?: FitMode;
|
|
405
431
|
loop?: boolean;
|
|
406
432
|
borderRadius?: number | BorderRadiusConfig;
|
|
407
433
|
}
|
|
408
434
|
/**
|
|
409
|
-
* Text segment
|
|
435
|
+
* Text segment (UI)
|
|
410
436
|
*/
|
|
411
437
|
interface VideoEditorTextSegment extends VideoEditorVisualSegment {
|
|
412
438
|
type: 'text';
|
|
439
|
+
/** UI-only: reference to text input - resolved to text before rendering */
|
|
413
440
|
textInputRef?: string;
|
|
441
|
+
/** Actual text content (set after textInputRef is resolved) */
|
|
414
442
|
text?: string;
|
|
415
443
|
alignment?: 'left' | 'center' | 'right' | 'justify';
|
|
416
444
|
verticalAlign?: 'top' | 'middle' | 'bottom';
|
|
@@ -445,16 +473,25 @@ interface VideoEditorChannel {
|
|
|
445
473
|
segments: VideoEditorSegment[];
|
|
446
474
|
}
|
|
447
475
|
/**
|
|
448
|
-
* Video editor node configuration
|
|
476
|
+
* Video editor node configuration for WEBAPP UI
|
|
477
|
+
*
|
|
478
|
+
* Duration is NOT stored - it's calculated from segment durations.
|
|
479
|
+
* dimensionPreset, previewUrls, previewTextValues are UI-only.
|
|
449
480
|
*/
|
|
450
481
|
interface VideoEditorNodeConfig {
|
|
482
|
+
/** Canvas width in pixels */
|
|
451
483
|
width: number;
|
|
484
|
+
/** Canvas height in pixels */
|
|
452
485
|
height: number;
|
|
486
|
+
/** Frames per second */
|
|
453
487
|
fps: number;
|
|
454
|
-
|
|
488
|
+
/** UI-only: dimension preset selector */
|
|
455
489
|
dimensionPreset: DimensionPresetKey;
|
|
490
|
+
/** Channels containing segments */
|
|
456
491
|
channels: VideoEditorChannel[];
|
|
492
|
+
/** UI-only: preview URLs for displaying in editor */
|
|
457
493
|
previewUrls?: Record<string, string>;
|
|
494
|
+
/** UI-only: preview text values for displaying in editor */
|
|
458
495
|
previewTextValues?: Record<string, string>;
|
|
459
496
|
}
|
|
460
497
|
/**
|
|
@@ -644,15 +681,16 @@ type VisualSegmentUnion = VideoSegment | ImageSegment | TextSegment;
|
|
|
644
681
|
type StaticSegment = ImageSegment | TextSegment;
|
|
645
682
|
|
|
646
683
|
/**
|
|
647
|
-
* Editor configuration types
|
|
684
|
+
* Editor configuration types for RENDERING
|
|
685
|
+
*
|
|
686
|
+
* These types define what the renderer receives.
|
|
687
|
+
* Duration is always calculated from segments - there is no duration field.
|
|
648
688
|
*
|
|
649
|
-
*
|
|
650
|
-
* timing, and channels containing segments.
|
|
689
|
+
* Note: UI-specific types (with previewUrls, dimensionPreset, etc.) are in video.ts
|
|
651
690
|
*/
|
|
652
691
|
|
|
653
692
|
/**
|
|
654
|
-
* A channel (track) containing segments
|
|
655
|
-
* Channels allow for parallel playback of segments
|
|
693
|
+
* A channel (track) containing segments for rendering
|
|
656
694
|
*/
|
|
657
695
|
interface Channel<T extends Segment = Segment> {
|
|
658
696
|
/** Unique channel identifier */
|
|
@@ -661,17 +699,16 @@ interface Channel<T extends Segment = Segment> {
|
|
|
661
699
|
segments: T[];
|
|
662
700
|
}
|
|
663
701
|
/**
|
|
664
|
-
* Base editor configuration
|
|
702
|
+
* Base editor configuration for rendering
|
|
703
|
+
* Duration is calculated from segments, not stored.
|
|
665
704
|
*/
|
|
666
705
|
interface BaseEditorConfig {
|
|
667
706
|
/** Canvas width in pixels */
|
|
668
707
|
width: number;
|
|
669
708
|
/** Canvas height in pixels */
|
|
670
709
|
height: number;
|
|
671
|
-
/** Frames per second
|
|
710
|
+
/** Frames per second */
|
|
672
711
|
fps: number;
|
|
673
|
-
/** Total duration in milliseconds (auto-calculated if 0 or undefined) */
|
|
674
|
-
duration?: number;
|
|
675
712
|
}
|
|
676
713
|
/**
|
|
677
714
|
* Full editor configuration with all segment types
|
package/dist/index.js
CHANGED
|
@@ -294,6 +294,55 @@ function hexToRgba(hex, opacity = 100) {
|
|
|
294
294
|
|
|
295
295
|
// src/components/TextElement.tsx
|
|
296
296
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
297
|
+
function calculateAutoWidth({
|
|
298
|
+
text,
|
|
299
|
+
maxWidth,
|
|
300
|
+
paddingLeft,
|
|
301
|
+
paddingRight,
|
|
302
|
+
fontSize,
|
|
303
|
+
fontWeight,
|
|
304
|
+
fontFamily,
|
|
305
|
+
letterSpacing
|
|
306
|
+
}) {
|
|
307
|
+
if (typeof document === "undefined") {
|
|
308
|
+
throw new Error("calculateAutoWidth requires a browser environment with document available");
|
|
309
|
+
}
|
|
310
|
+
const canvas = document.createElement("canvas");
|
|
311
|
+
const ctx = canvas.getContext("2d");
|
|
312
|
+
if (!ctx) {
|
|
313
|
+
throw new Error("Failed to get 2D canvas context for text measurement");
|
|
314
|
+
}
|
|
315
|
+
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
316
|
+
const availableWidth = maxWidth - paddingLeft - paddingRight;
|
|
317
|
+
const words = text.split(" ");
|
|
318
|
+
const lines = [];
|
|
319
|
+
let currentLine = "";
|
|
320
|
+
for (const word of words) {
|
|
321
|
+
if (!word) continue;
|
|
322
|
+
const testLine = currentLine + (currentLine ? " " : "") + word;
|
|
323
|
+
const charCount = [...testLine].length;
|
|
324
|
+
const extraSpacing = charCount > 1 ? (charCount - 1) * letterSpacing : 0;
|
|
325
|
+
const totalWidth = ctx.measureText(testLine).width + extraSpacing;
|
|
326
|
+
if (totalWidth > availableWidth && currentLine) {
|
|
327
|
+
lines.push(currentLine);
|
|
328
|
+
currentLine = word;
|
|
329
|
+
} else {
|
|
330
|
+
currentLine = testLine;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (currentLine) lines.push(currentLine);
|
|
334
|
+
let widestLineWidth = 0;
|
|
335
|
+
for (const line of lines) {
|
|
336
|
+
const chars = [...line];
|
|
337
|
+
let lineWidth = 0;
|
|
338
|
+
for (const char of chars) {
|
|
339
|
+
lineWidth += ctx.measureText(char).width + letterSpacing;
|
|
340
|
+
}
|
|
341
|
+
if (chars.length > 0) lineWidth -= letterSpacing;
|
|
342
|
+
widestLineWidth = Math.max(widestLineWidth, lineWidth);
|
|
343
|
+
}
|
|
344
|
+
return Math.min(widestLineWidth + paddingLeft + paddingRight, maxWidth);
|
|
345
|
+
}
|
|
297
346
|
function TextElement({ segment, scale = 1 }) {
|
|
298
347
|
const fontType = segment.fontType ?? TEXT_DEFAULTS.fontType;
|
|
299
348
|
const fontSize = (segment.fontSize ?? TEXT_DEFAULTS.fontSize) * scale;
|
|
@@ -321,6 +370,19 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
321
370
|
const backgroundOpacity = segment.backgroundOpacity ?? TEXT_DEFAULTS.backgroundOpacity;
|
|
322
371
|
const backgroundBorderRadius = segment.backgroundBorderRadius;
|
|
323
372
|
const fontFamily = getFontFamily(fontType);
|
|
373
|
+
const calculatedWidth = (0, import_react.useMemo)(() => {
|
|
374
|
+
if (!autoWidth) return width;
|
|
375
|
+
return calculateAutoWidth({
|
|
376
|
+
text: segment.text,
|
|
377
|
+
maxWidth: width,
|
|
378
|
+
paddingLeft,
|
|
379
|
+
paddingRight,
|
|
380
|
+
fontSize,
|
|
381
|
+
fontWeight,
|
|
382
|
+
fontFamily,
|
|
383
|
+
letterSpacing
|
|
384
|
+
});
|
|
385
|
+
}, [autoWidth, segment.text, width, paddingLeft, paddingRight, fontSize, fontWeight, fontFamily, letterSpacing]);
|
|
324
386
|
const borderRadiusStyle = (0, import_react.useMemo)(() => {
|
|
325
387
|
if (!backgroundBorderRadius) return void 0;
|
|
326
388
|
const radii = getBorderRadii(backgroundBorderRadius);
|
|
@@ -344,8 +406,10 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
344
406
|
}), [x, y, width, height, rotation, verticalAlign, autoWidth, boxAlign]);
|
|
345
407
|
const backgroundBoxStyle = (0, import_react.useMemo)(() => {
|
|
346
408
|
const baseStyle = autoWidth ? {
|
|
347
|
-
width
|
|
348
|
-
|
|
409
|
+
// Use explicit calculated width instead of fit-content
|
|
410
|
+
// This fixes the issue where multi-line text doesn't shrink to widest line
|
|
411
|
+
width: calculatedWidth,
|
|
412
|
+
maxWidth: width
|
|
349
413
|
} : {
|
|
350
414
|
// When not autoWidth, let the text div handle everything
|
|
351
415
|
display: "flex",
|
|
@@ -359,7 +423,7 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
359
423
|
backgroundColor: hexToRgba(backgroundColor, backgroundOpacity),
|
|
360
424
|
borderRadius: borderRadiusStyle
|
|
361
425
|
};
|
|
362
|
-
}, [autoWidth, backgroundColor, backgroundOpacity, borderRadiusStyle]);
|
|
426
|
+
}, [autoWidth, calculatedWidth, width, backgroundColor, backgroundOpacity, borderRadiusStyle]);
|
|
363
427
|
const textStyle = (0, import_react.useMemo)(() => ({
|
|
364
428
|
fontFamily,
|
|
365
429
|
fontSize,
|
|
@@ -1245,9 +1309,9 @@ function calculateSegmentTimings(config, fps) {
|
|
|
1245
1309
|
const startFrame = currentFrame + offsetFrames;
|
|
1246
1310
|
let durationMs;
|
|
1247
1311
|
if (segment.duration) {
|
|
1248
|
-
durationMs = segment.duration.type === "absolute" ? segment.duration.value :
|
|
1312
|
+
durationMs = segment.duration.type === "absolute" ? segment.duration.value : 5e3;
|
|
1249
1313
|
} else {
|
|
1250
|
-
durationMs =
|
|
1314
|
+
durationMs = 5e3;
|
|
1251
1315
|
}
|
|
1252
1316
|
const durationInFrames = Math.max(1, Math.round(durationMs / 1e3 * fps));
|
|
1253
1317
|
const endFrame = startFrame + durationInFrames;
|
|
@@ -1666,7 +1730,6 @@ var defaultVideoProps = {
|
|
|
1666
1730
|
width: 1080,
|
|
1667
1731
|
height: 1920,
|
|
1668
1732
|
fps: 30,
|
|
1669
|
-
duration: 5e3,
|
|
1670
1733
|
channels: []
|
|
1671
1734
|
},
|
|
1672
1735
|
sources: {},
|
package/dist/index.mjs
CHANGED
|
@@ -220,6 +220,55 @@ function hexToRgba(hex, opacity = 100) {
|
|
|
220
220
|
|
|
221
221
|
// src/components/TextElement.tsx
|
|
222
222
|
import { jsx } from "react/jsx-runtime";
|
|
223
|
+
function calculateAutoWidth({
|
|
224
|
+
text,
|
|
225
|
+
maxWidth,
|
|
226
|
+
paddingLeft,
|
|
227
|
+
paddingRight,
|
|
228
|
+
fontSize,
|
|
229
|
+
fontWeight,
|
|
230
|
+
fontFamily,
|
|
231
|
+
letterSpacing
|
|
232
|
+
}) {
|
|
233
|
+
if (typeof document === "undefined") {
|
|
234
|
+
throw new Error("calculateAutoWidth requires a browser environment with document available");
|
|
235
|
+
}
|
|
236
|
+
const canvas = document.createElement("canvas");
|
|
237
|
+
const ctx = canvas.getContext("2d");
|
|
238
|
+
if (!ctx) {
|
|
239
|
+
throw new Error("Failed to get 2D canvas context for text measurement");
|
|
240
|
+
}
|
|
241
|
+
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
242
|
+
const availableWidth = maxWidth - paddingLeft - paddingRight;
|
|
243
|
+
const words = text.split(" ");
|
|
244
|
+
const lines = [];
|
|
245
|
+
let currentLine = "";
|
|
246
|
+
for (const word of words) {
|
|
247
|
+
if (!word) continue;
|
|
248
|
+
const testLine = currentLine + (currentLine ? " " : "") + word;
|
|
249
|
+
const charCount = [...testLine].length;
|
|
250
|
+
const extraSpacing = charCount > 1 ? (charCount - 1) * letterSpacing : 0;
|
|
251
|
+
const totalWidth = ctx.measureText(testLine).width + extraSpacing;
|
|
252
|
+
if (totalWidth > availableWidth && currentLine) {
|
|
253
|
+
lines.push(currentLine);
|
|
254
|
+
currentLine = word;
|
|
255
|
+
} else {
|
|
256
|
+
currentLine = testLine;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (currentLine) lines.push(currentLine);
|
|
260
|
+
let widestLineWidth = 0;
|
|
261
|
+
for (const line of lines) {
|
|
262
|
+
const chars = [...line];
|
|
263
|
+
let lineWidth = 0;
|
|
264
|
+
for (const char of chars) {
|
|
265
|
+
lineWidth += ctx.measureText(char).width + letterSpacing;
|
|
266
|
+
}
|
|
267
|
+
if (chars.length > 0) lineWidth -= letterSpacing;
|
|
268
|
+
widestLineWidth = Math.max(widestLineWidth, lineWidth);
|
|
269
|
+
}
|
|
270
|
+
return Math.min(widestLineWidth + paddingLeft + paddingRight, maxWidth);
|
|
271
|
+
}
|
|
223
272
|
function TextElement({ segment, scale = 1 }) {
|
|
224
273
|
const fontType = segment.fontType ?? TEXT_DEFAULTS.fontType;
|
|
225
274
|
const fontSize = (segment.fontSize ?? TEXT_DEFAULTS.fontSize) * scale;
|
|
@@ -247,6 +296,19 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
247
296
|
const backgroundOpacity = segment.backgroundOpacity ?? TEXT_DEFAULTS.backgroundOpacity;
|
|
248
297
|
const backgroundBorderRadius = segment.backgroundBorderRadius;
|
|
249
298
|
const fontFamily = getFontFamily(fontType);
|
|
299
|
+
const calculatedWidth = useMemo(() => {
|
|
300
|
+
if (!autoWidth) return width;
|
|
301
|
+
return calculateAutoWidth({
|
|
302
|
+
text: segment.text,
|
|
303
|
+
maxWidth: width,
|
|
304
|
+
paddingLeft,
|
|
305
|
+
paddingRight,
|
|
306
|
+
fontSize,
|
|
307
|
+
fontWeight,
|
|
308
|
+
fontFamily,
|
|
309
|
+
letterSpacing
|
|
310
|
+
});
|
|
311
|
+
}, [autoWidth, segment.text, width, paddingLeft, paddingRight, fontSize, fontWeight, fontFamily, letterSpacing]);
|
|
250
312
|
const borderRadiusStyle = useMemo(() => {
|
|
251
313
|
if (!backgroundBorderRadius) return void 0;
|
|
252
314
|
const radii = getBorderRadii(backgroundBorderRadius);
|
|
@@ -270,8 +332,10 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
270
332
|
}), [x, y, width, height, rotation, verticalAlign, autoWidth, boxAlign]);
|
|
271
333
|
const backgroundBoxStyle = useMemo(() => {
|
|
272
334
|
const baseStyle = autoWidth ? {
|
|
273
|
-
width
|
|
274
|
-
|
|
335
|
+
// Use explicit calculated width instead of fit-content
|
|
336
|
+
// This fixes the issue where multi-line text doesn't shrink to widest line
|
|
337
|
+
width: calculatedWidth,
|
|
338
|
+
maxWidth: width
|
|
275
339
|
} : {
|
|
276
340
|
// When not autoWidth, let the text div handle everything
|
|
277
341
|
display: "flex",
|
|
@@ -285,7 +349,7 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
285
349
|
backgroundColor: hexToRgba(backgroundColor, backgroundOpacity),
|
|
286
350
|
borderRadius: borderRadiusStyle
|
|
287
351
|
};
|
|
288
|
-
}, [autoWidth, backgroundColor, backgroundOpacity, borderRadiusStyle]);
|
|
352
|
+
}, [autoWidth, calculatedWidth, width, backgroundColor, backgroundOpacity, borderRadiusStyle]);
|
|
289
353
|
const textStyle = useMemo(() => ({
|
|
290
354
|
fontFamily,
|
|
291
355
|
fontSize,
|
|
@@ -1171,9 +1235,9 @@ function calculateSegmentTimings(config, fps) {
|
|
|
1171
1235
|
const startFrame = currentFrame + offsetFrames;
|
|
1172
1236
|
let durationMs;
|
|
1173
1237
|
if (segment.duration) {
|
|
1174
|
-
durationMs = segment.duration.type === "absolute" ? segment.duration.value :
|
|
1238
|
+
durationMs = segment.duration.type === "absolute" ? segment.duration.value : 5e3;
|
|
1175
1239
|
} else {
|
|
1176
|
-
durationMs =
|
|
1240
|
+
durationMs = 5e3;
|
|
1177
1241
|
}
|
|
1178
1242
|
const durationInFrames = Math.max(1, Math.round(durationMs / 1e3 * fps));
|
|
1179
1243
|
const endFrame = startFrame + durationInFrames;
|
|
@@ -1592,7 +1656,6 @@ var defaultVideoProps = {
|
|
|
1592
1656
|
width: 1080,
|
|
1593
1657
|
height: 1920,
|
|
1594
1658
|
fps: 30,
|
|
1595
|
-
duration: 5e3,
|
|
1596
1659
|
channels: []
|
|
1597
1660
|
},
|
|
1598
1661
|
sources: {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ugcinc-render",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Unified rendering package for UGC Inc - shared types, components, and compositions for pixel-perfect client/server rendering",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|