ugcinc-render 1.5.10 → 1.5.12
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 +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +130 -54
- package/dist/index.mjs +126 -54
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1290,6 +1290,34 @@ declare function generateSegmentId(): string;
|
|
|
1290
1290
|
*/
|
|
1291
1291
|
declare function generateOverlayId(): string;
|
|
1292
1292
|
|
|
1293
|
+
/**
|
|
1294
|
+
* Emoji utilities for rendering Apple-style emojis consistently
|
|
1295
|
+
* across all platforms using CDN-hosted emoji images.
|
|
1296
|
+
*/
|
|
1297
|
+
/**
|
|
1298
|
+
* Convert an emoji character to its unified unicode codepoint format
|
|
1299
|
+
* Example: "😀" -> "1f600", "👨👩👧" -> "1f468-200d-1f469-200d-1f467"
|
|
1300
|
+
*/
|
|
1301
|
+
declare function emojiToUnified(emoji: string): string;
|
|
1302
|
+
/**
|
|
1303
|
+
* Get the Apple emoji CDN URL for a given emoji character
|
|
1304
|
+
*/
|
|
1305
|
+
declare function getAppleEmojiUrl(emoji: string): string;
|
|
1306
|
+
/**
|
|
1307
|
+
* Check if a string contains any emoji characters
|
|
1308
|
+
*/
|
|
1309
|
+
declare function hasEmoji(text: string): boolean;
|
|
1310
|
+
/**
|
|
1311
|
+
* Split text into segments of text and emojis
|
|
1312
|
+
* Returns an array of segments with type 'text' or 'emoji'
|
|
1313
|
+
*/
|
|
1314
|
+
interface TextSegmentPart {
|
|
1315
|
+
type: 'text' | 'emoji';
|
|
1316
|
+
content: string;
|
|
1317
|
+
imageUrl?: string;
|
|
1318
|
+
}
|
|
1319
|
+
declare function splitTextAndEmojis(text: string): TextSegmentPart[];
|
|
1320
|
+
|
|
1293
1321
|
/**
|
|
1294
1322
|
* Hook exports for ugcinc-render
|
|
1295
1323
|
*
|
|
@@ -1349,4 +1377,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
|
|
|
1349
1377
|
|
|
1350
1378
|
declare const RenderRoot: React.FC;
|
|
1351
1379
|
|
|
1352
|
-
export { type AudioSegment, type BaseEditorConfig, type BaseSegment, type BorderRadiusConfig, type Channel, type CropAxisConfig, type CropBoundary, type CropBounds, DIMENSION_PRESETS, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentTimelinePosition, type SegmentType, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextWrap, type TimeMode, type TimeValue, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoSegment, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateEstimatedDuration, calculateFitDimensions, calculateLineWidth, calculateTimelineContentEnd, canSetAsReference, defaultOffset, formatTime, generateOverlayId, generateSegmentId, getBaseSegments, getBorderRadii, getDependentElements, getFontFamily, getOverlays, getReferenceElementX, getReferenceElementY, getSegmentTimelinePosition, hexToRgba, isDynamicCropEnabled, isSegmentVisibleAtTime, parseHexColor, parseTime, preloadFonts, resolveElementPositions, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
|
|
1380
|
+
export { type AudioSegment, type BaseEditorConfig, type BaseSegment, type BorderRadiusConfig, type Channel, type CropAxisConfig, type CropBoundary, type CropBounds, DIMENSION_PRESETS, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentTimelinePosition, type SegmentType, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextSegmentPart, type TextWrap, type TimeMode, type TimeValue, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoSegment, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateEstimatedDuration, calculateFitDimensions, calculateLineWidth, calculateTimelineContentEnd, canSetAsReference, defaultOffset, emojiToUnified, formatTime, generateOverlayId, generateSegmentId, getAppleEmojiUrl, getBaseSegments, getBorderRadii, getDependentElements, getFontFamily, getOverlays, getReferenceElementX, getReferenceElementY, getSegmentTimelinePosition, hasEmoji, hexToRgba, isDynamicCropEnabled, isSegmentVisibleAtTime, parseHexColor, parseTime, preloadFonts, resolveElementPositions, splitTextAndEmojis, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
|
package/dist/index.d.ts
CHANGED
|
@@ -1290,6 +1290,34 @@ declare function generateSegmentId(): string;
|
|
|
1290
1290
|
*/
|
|
1291
1291
|
declare function generateOverlayId(): string;
|
|
1292
1292
|
|
|
1293
|
+
/**
|
|
1294
|
+
* Emoji utilities for rendering Apple-style emojis consistently
|
|
1295
|
+
* across all platforms using CDN-hosted emoji images.
|
|
1296
|
+
*/
|
|
1297
|
+
/**
|
|
1298
|
+
* Convert an emoji character to its unified unicode codepoint format
|
|
1299
|
+
* Example: "😀" -> "1f600", "👨👩👧" -> "1f468-200d-1f469-200d-1f467"
|
|
1300
|
+
*/
|
|
1301
|
+
declare function emojiToUnified(emoji: string): string;
|
|
1302
|
+
/**
|
|
1303
|
+
* Get the Apple emoji CDN URL for a given emoji character
|
|
1304
|
+
*/
|
|
1305
|
+
declare function getAppleEmojiUrl(emoji: string): string;
|
|
1306
|
+
/**
|
|
1307
|
+
* Check if a string contains any emoji characters
|
|
1308
|
+
*/
|
|
1309
|
+
declare function hasEmoji(text: string): boolean;
|
|
1310
|
+
/**
|
|
1311
|
+
* Split text into segments of text and emojis
|
|
1312
|
+
* Returns an array of segments with type 'text' or 'emoji'
|
|
1313
|
+
*/
|
|
1314
|
+
interface TextSegmentPart {
|
|
1315
|
+
type: 'text' | 'emoji';
|
|
1316
|
+
content: string;
|
|
1317
|
+
imageUrl?: string;
|
|
1318
|
+
}
|
|
1319
|
+
declare function splitTextAndEmojis(text: string): TextSegmentPart[];
|
|
1320
|
+
|
|
1293
1321
|
/**
|
|
1294
1322
|
* Hook exports for ugcinc-render
|
|
1295
1323
|
*
|
|
@@ -1349,4 +1377,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
|
|
|
1349
1377
|
|
|
1350
1378
|
declare const RenderRoot: React.FC;
|
|
1351
1379
|
|
|
1352
|
-
export { type AudioSegment, type BaseEditorConfig, type BaseSegment, type BorderRadiusConfig, type Channel, type CropAxisConfig, type CropBoundary, type CropBounds, DIMENSION_PRESETS, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentTimelinePosition, type SegmentType, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextWrap, type TimeMode, type TimeValue, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoSegment, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateEstimatedDuration, calculateFitDimensions, calculateLineWidth, calculateTimelineContentEnd, canSetAsReference, defaultOffset, formatTime, generateOverlayId, generateSegmentId, getBaseSegments, getBorderRadii, getDependentElements, getFontFamily, getOverlays, getReferenceElementX, getReferenceElementY, getSegmentTimelinePosition, hexToRgba, isDynamicCropEnabled, isSegmentVisibleAtTime, parseHexColor, parseTime, preloadFonts, resolveElementPositions, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
|
|
1380
|
+
export { type AudioSegment, type BaseEditorConfig, type BaseSegment, type BorderRadiusConfig, type Channel, type CropAxisConfig, type CropBoundary, type CropBounds, DIMENSION_PRESETS, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentTimelinePosition, type SegmentType, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextSegmentPart, type TextWrap, type TimeMode, type TimeValue, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoSegment, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateEstimatedDuration, calculateFitDimensions, calculateLineWidth, calculateTimelineContentEnd, canSetAsReference, defaultOffset, emojiToUnified, formatTime, generateOverlayId, generateSegmentId, getAppleEmojiUrl, getBaseSegments, getBorderRadii, getDependentElements, getFontFamily, getOverlays, getReferenceElementX, getReferenceElementY, getSegmentTimelinePosition, hasEmoji, hexToRgba, isDynamicCropEnabled, isSegmentVisibleAtTime, parseHexColor, parseTime, preloadFonts, resolveElementPositions, splitTextAndEmojis, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
|
package/dist/index.js
CHANGED
|
@@ -56,9 +56,11 @@ __export(index_exports, {
|
|
|
56
56
|
calculateTimelineContentEnd: () => calculateTimelineContentEnd,
|
|
57
57
|
canSetAsReference: () => canSetAsReference,
|
|
58
58
|
defaultOffset: () => defaultOffset,
|
|
59
|
+
emojiToUnified: () => emojiToUnified,
|
|
59
60
|
formatTime: () => formatTime,
|
|
60
61
|
generateOverlayId: () => generateOverlayId,
|
|
61
62
|
generateSegmentId: () => generateSegmentId,
|
|
63
|
+
getAppleEmojiUrl: () => getAppleEmojiUrl,
|
|
62
64
|
getBaseSegments: () => getBaseSegments,
|
|
63
65
|
getBorderRadii: () => getBorderRadii,
|
|
64
66
|
getDependentElements: () => getDependentElements,
|
|
@@ -67,6 +69,7 @@ __export(index_exports, {
|
|
|
67
69
|
getReferenceElementX: () => getReferenceElementX,
|
|
68
70
|
getReferenceElementY: () => getReferenceElementY,
|
|
69
71
|
getSegmentTimelinePosition: () => getSegmentTimelinePosition,
|
|
72
|
+
hasEmoji: () => hasEmoji,
|
|
70
73
|
hexToRgba: () => hexToRgba,
|
|
71
74
|
isDynamicCropEnabled: () => isDynamicCropEnabled,
|
|
72
75
|
isSegmentVisibleAtTime: () => isSegmentVisibleAtTime,
|
|
@@ -74,6 +77,7 @@ __export(index_exports, {
|
|
|
74
77
|
parseTime: () => parseTime,
|
|
75
78
|
preloadFonts: () => preloadFonts,
|
|
76
79
|
resolveElementPositions: () => resolveElementPositions,
|
|
80
|
+
splitTextAndEmojis: () => splitTextAndEmojis,
|
|
77
81
|
useFontsLoaded: () => useFontsLoaded,
|
|
78
82
|
useImageLoader: () => useImageLoader,
|
|
79
83
|
useImagePreloader: () => useImagePreloader,
|
|
@@ -302,6 +306,54 @@ function hexToRgba(hex, opacity = 100) {
|
|
|
302
306
|
return `rgba(${r}, ${g}, ${b}, ${opacity / 100})`;
|
|
303
307
|
}
|
|
304
308
|
|
|
309
|
+
// src/utils/emoji.ts
|
|
310
|
+
var APPLE_EMOJI_CDN_BASE = "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/";
|
|
311
|
+
function emojiToUnified(emoji) {
|
|
312
|
+
const codePoints = [];
|
|
313
|
+
for (const codePoint of emoji) {
|
|
314
|
+
const hex = codePoint.codePointAt(0)?.toString(16);
|
|
315
|
+
if (hex) {
|
|
316
|
+
codePoints.push(hex);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return codePoints.join("-");
|
|
320
|
+
}
|
|
321
|
+
function getAppleEmojiUrl(emoji) {
|
|
322
|
+
const unified = emojiToUnified(emoji);
|
|
323
|
+
return `${APPLE_EMOJI_CDN_BASE}${unified}.png`;
|
|
324
|
+
}
|
|
325
|
+
var EMOJI_REGEX = /(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\p{Emoji_Modifier}|\uFE0F|\u200D(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\p{Emoji_Modifier})?)*|\p{Regional_Indicator}{2}/gu;
|
|
326
|
+
function hasEmoji(text) {
|
|
327
|
+
return EMOJI_REGEX.test(text);
|
|
328
|
+
}
|
|
329
|
+
function splitTextAndEmojis(text) {
|
|
330
|
+
const segments = [];
|
|
331
|
+
let lastIndex = 0;
|
|
332
|
+
EMOJI_REGEX.lastIndex = 0;
|
|
333
|
+
let match;
|
|
334
|
+
while ((match = EMOJI_REGEX.exec(text)) !== null) {
|
|
335
|
+
if (match.index > lastIndex) {
|
|
336
|
+
segments.push({
|
|
337
|
+
type: "text",
|
|
338
|
+
content: text.slice(lastIndex, match.index)
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
segments.push({
|
|
342
|
+
type: "emoji",
|
|
343
|
+
content: match[0],
|
|
344
|
+
imageUrl: getAppleEmojiUrl(match[0])
|
|
345
|
+
});
|
|
346
|
+
lastIndex = match.index + match[0].length;
|
|
347
|
+
}
|
|
348
|
+
if (lastIndex < text.length) {
|
|
349
|
+
segments.push({
|
|
350
|
+
type: "text",
|
|
351
|
+
content: text.slice(lastIndex)
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return segments;
|
|
355
|
+
}
|
|
356
|
+
|
|
305
357
|
// src/components/TextElement.tsx
|
|
306
358
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
307
359
|
function calculateAutoWidthAndLines({
|
|
@@ -436,14 +488,22 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
436
488
|
const backgroundOpacity = segment.backgroundOpacity ?? TEXT_DEFAULTS.backgroundOpacity;
|
|
437
489
|
const backgroundBorderRadius = segment.backgroundBorderRadius;
|
|
438
490
|
const fontFamily = getFontFamily(fontType);
|
|
439
|
-
const {
|
|
491
|
+
const { calculatedWidth, calculatedLines, paddingTop, paddingRight, paddingBottom, paddingLeft } = (0, import_react.useMemo)(() => {
|
|
492
|
+
let finalPaddingTop = basePaddingTop;
|
|
493
|
+
let finalPaddingRight = basePaddingRight;
|
|
494
|
+
let finalPaddingBottom = basePaddingBottom;
|
|
495
|
+
let finalPaddingLeft = basePaddingLeft;
|
|
440
496
|
if (!autoWidth && !hasExtraPadding) {
|
|
441
497
|
return {
|
|
442
|
-
|
|
443
|
-
|
|
498
|
+
calculatedWidth: width,
|
|
499
|
+
calculatedLines: [segment.text],
|
|
500
|
+
paddingTop: finalPaddingTop,
|
|
501
|
+
paddingRight: finalPaddingRight,
|
|
502
|
+
paddingBottom: finalPaddingBottom,
|
|
503
|
+
paddingLeft: finalPaddingLeft
|
|
444
504
|
};
|
|
445
505
|
}
|
|
446
|
-
const
|
|
506
|
+
const baseResult = calculateAutoWidthAndLines({
|
|
447
507
|
text: segment.text,
|
|
448
508
|
maxWidth: width,
|
|
449
509
|
paddingLeft: basePaddingLeft,
|
|
@@ -454,16 +514,47 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
454
514
|
letterSpacing,
|
|
455
515
|
lineHeight
|
|
456
516
|
});
|
|
517
|
+
const isMultiLine = baseResult.lines.length >= 2;
|
|
518
|
+
if (isMultiLine) {
|
|
519
|
+
finalPaddingTop = basePaddingTop + extraPaddingTop;
|
|
520
|
+
finalPaddingRight = basePaddingRight + extraPaddingRight;
|
|
521
|
+
finalPaddingBottom = basePaddingBottom + extraPaddingBottom;
|
|
522
|
+
finalPaddingLeft = basePaddingLeft + extraPaddingLeft;
|
|
523
|
+
}
|
|
524
|
+
let finalResult = baseResult;
|
|
525
|
+
if (isMultiLine && (extraPaddingLeft > 0 || extraPaddingRight > 0)) {
|
|
526
|
+
finalResult = calculateAutoWidthAndLines({
|
|
527
|
+
text: segment.text,
|
|
528
|
+
maxWidth: width,
|
|
529
|
+
paddingLeft: finalPaddingLeft,
|
|
530
|
+
paddingRight: finalPaddingRight,
|
|
531
|
+
fontSize,
|
|
532
|
+
fontWeight,
|
|
533
|
+
fontFamily,
|
|
534
|
+
letterSpacing,
|
|
535
|
+
lineHeight
|
|
536
|
+
});
|
|
537
|
+
}
|
|
457
538
|
return {
|
|
458
|
-
|
|
459
|
-
|
|
539
|
+
calculatedWidth: autoWidth ? finalResult.width : width,
|
|
540
|
+
calculatedLines: autoWidth ? finalResult.lines : [segment.text],
|
|
541
|
+
paddingTop: finalPaddingTop,
|
|
542
|
+
paddingRight: finalPaddingRight,
|
|
543
|
+
paddingBottom: finalPaddingBottom,
|
|
544
|
+
paddingLeft: finalPaddingLeft
|
|
460
545
|
};
|
|
461
546
|
}, [
|
|
462
547
|
autoWidth,
|
|
463
548
|
segment.text,
|
|
464
549
|
width,
|
|
465
|
-
|
|
550
|
+
basePaddingTop,
|
|
466
551
|
basePaddingRight,
|
|
552
|
+
basePaddingBottom,
|
|
553
|
+
basePaddingLeft,
|
|
554
|
+
extraPaddingTop,
|
|
555
|
+
extraPaddingRight,
|
|
556
|
+
extraPaddingBottom,
|
|
557
|
+
extraPaddingLeft,
|
|
467
558
|
hasExtraPadding,
|
|
468
559
|
fontSize,
|
|
469
560
|
fontWeight,
|
|
@@ -471,51 +562,6 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
471
562
|
letterSpacing,
|
|
472
563
|
lineHeight
|
|
473
564
|
]);
|
|
474
|
-
const calculatedLines = autoWidthResult?.lines ?? [segment.text];
|
|
475
|
-
const paddingTop = basePaddingTop + (isMultiLine ? extraPaddingTop : 0);
|
|
476
|
-
const paddingRight = basePaddingRight + (isMultiLine ? extraPaddingRight : 0);
|
|
477
|
-
const paddingBottom = basePaddingBottom + (isMultiLine ? extraPaddingBottom : 0);
|
|
478
|
-
const paddingLeft = basePaddingLeft + (isMultiLine ? extraPaddingLeft : 0);
|
|
479
|
-
const calculatedWidth = (0, import_react.useMemo)(() => {
|
|
480
|
-
if (!autoWidth) return width;
|
|
481
|
-
if (!isMultiLine || extraPaddingLeft === 0 && extraPaddingRight === 0) {
|
|
482
|
-
return autoWidthResult?.width ?? width;
|
|
483
|
-
}
|
|
484
|
-
if (typeof document === "undefined") return width;
|
|
485
|
-
const measureSpan = document.createElement("span");
|
|
486
|
-
measureSpan.style.cssText = `
|
|
487
|
-
position: absolute;
|
|
488
|
-
visibility: hidden;
|
|
489
|
-
pointer-events: none;
|
|
490
|
-
font-family: ${fontFamily};
|
|
491
|
-
font-size: ${fontSize}px;
|
|
492
|
-
font-weight: ${fontWeight};
|
|
493
|
-
letter-spacing: ${letterSpacing}px;
|
|
494
|
-
white-space: nowrap;
|
|
495
|
-
`;
|
|
496
|
-
document.body.appendChild(measureSpan);
|
|
497
|
-
let widestLineWidth = 0;
|
|
498
|
-
for (const line of calculatedLines) {
|
|
499
|
-
measureSpan.textContent = line;
|
|
500
|
-
widestLineWidth = Math.max(widestLineWidth, measureSpan.getBoundingClientRect().width);
|
|
501
|
-
}
|
|
502
|
-
document.body.removeChild(measureSpan);
|
|
503
|
-
return Math.min(widestLineWidth + paddingLeft + paddingRight, width);
|
|
504
|
-
}, [
|
|
505
|
-
autoWidth,
|
|
506
|
-
autoWidthResult,
|
|
507
|
-
width,
|
|
508
|
-
isMultiLine,
|
|
509
|
-
extraPaddingLeft,
|
|
510
|
-
extraPaddingRight,
|
|
511
|
-
paddingLeft,
|
|
512
|
-
paddingRight,
|
|
513
|
-
calculatedLines,
|
|
514
|
-
fontFamily,
|
|
515
|
-
fontSize,
|
|
516
|
-
fontWeight,
|
|
517
|
-
letterSpacing
|
|
518
|
-
]);
|
|
519
565
|
const borderRadiusStyle = (0, import_react.useMemo)(() => {
|
|
520
566
|
if (!backgroundBorderRadius) return void 0;
|
|
521
567
|
const radii = getBorderRadii(backgroundBorderRadius);
|
|
@@ -610,9 +656,35 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
610
656
|
autoWidth,
|
|
611
657
|
verticalAlign
|
|
612
658
|
]);
|
|
659
|
+
const renderTextWithEmojis = (text) => {
|
|
660
|
+
if (!hasEmoji(text)) {
|
|
661
|
+
return text;
|
|
662
|
+
}
|
|
663
|
+
const segments = splitTextAndEmojis(text);
|
|
664
|
+
return segments.map((segment2, i) => {
|
|
665
|
+
if (segment2.type === "emoji" && segment2.imageUrl) {
|
|
666
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
667
|
+
"img",
|
|
668
|
+
{
|
|
669
|
+
src: segment2.imageUrl,
|
|
670
|
+
alt: segment2.content,
|
|
671
|
+
style: {
|
|
672
|
+
height: "1em",
|
|
673
|
+
width: "1em",
|
|
674
|
+
verticalAlign: "-0.1em",
|
|
675
|
+
display: "inline"
|
|
676
|
+
},
|
|
677
|
+
draggable: false
|
|
678
|
+
},
|
|
679
|
+
i
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.default.Fragment, { children: segment2.content }, i);
|
|
683
|
+
});
|
|
684
|
+
};
|
|
613
685
|
if (autoWidth) {
|
|
614
686
|
const textContent = calculatedLines.map((line, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.default.Fragment, { children: [
|
|
615
|
-
line,
|
|
687
|
+
renderTextWithEmojis(line),
|
|
616
688
|
index < calculatedLines.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {})
|
|
617
689
|
] }, index));
|
|
618
690
|
if (backgroundColor) {
|
|
@@ -631,7 +703,7 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
631
703
|
}
|
|
632
704
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: positioningContainerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: calculatedWidth, maxWidth: width }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: textStyle, children: textContent }) }) });
|
|
633
705
|
}
|
|
634
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: positioningContainerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: backgroundBoxStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: textStyle, children: segment.text }) }) });
|
|
706
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: positioningContainerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: backgroundBoxStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: textStyle, children: renderTextWithEmojis(segment.text) }) }) });
|
|
635
707
|
}
|
|
636
708
|
|
|
637
709
|
// src/components/ImageElement.tsx
|
|
@@ -1951,9 +2023,11 @@ var RenderRoot = () => {
|
|
|
1951
2023
|
calculateTimelineContentEnd,
|
|
1952
2024
|
canSetAsReference,
|
|
1953
2025
|
defaultOffset,
|
|
2026
|
+
emojiToUnified,
|
|
1954
2027
|
formatTime,
|
|
1955
2028
|
generateOverlayId,
|
|
1956
2029
|
generateSegmentId,
|
|
2030
|
+
getAppleEmojiUrl,
|
|
1957
2031
|
getBaseSegments,
|
|
1958
2032
|
getBorderRadii,
|
|
1959
2033
|
getDependentElements,
|
|
@@ -1962,6 +2036,7 @@ var RenderRoot = () => {
|
|
|
1962
2036
|
getReferenceElementX,
|
|
1963
2037
|
getReferenceElementY,
|
|
1964
2038
|
getSegmentTimelinePosition,
|
|
2039
|
+
hasEmoji,
|
|
1965
2040
|
hexToRgba,
|
|
1966
2041
|
isDynamicCropEnabled,
|
|
1967
2042
|
isSegmentVisibleAtTime,
|
|
@@ -1969,6 +2044,7 @@ var RenderRoot = () => {
|
|
|
1969
2044
|
parseTime,
|
|
1970
2045
|
preloadFonts,
|
|
1971
2046
|
resolveElementPositions,
|
|
2047
|
+
splitTextAndEmojis,
|
|
1972
2048
|
useFontsLoaded,
|
|
1973
2049
|
useImageLoader,
|
|
1974
2050
|
useImagePreloader,
|
package/dist/index.mjs
CHANGED
|
@@ -218,6 +218,54 @@ function hexToRgba(hex, opacity = 100) {
|
|
|
218
218
|
return `rgba(${r}, ${g}, ${b}, ${opacity / 100})`;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
// src/utils/emoji.ts
|
|
222
|
+
var APPLE_EMOJI_CDN_BASE = "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/";
|
|
223
|
+
function emojiToUnified(emoji) {
|
|
224
|
+
const codePoints = [];
|
|
225
|
+
for (const codePoint of emoji) {
|
|
226
|
+
const hex = codePoint.codePointAt(0)?.toString(16);
|
|
227
|
+
if (hex) {
|
|
228
|
+
codePoints.push(hex);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return codePoints.join("-");
|
|
232
|
+
}
|
|
233
|
+
function getAppleEmojiUrl(emoji) {
|
|
234
|
+
const unified = emojiToUnified(emoji);
|
|
235
|
+
return `${APPLE_EMOJI_CDN_BASE}${unified}.png`;
|
|
236
|
+
}
|
|
237
|
+
var EMOJI_REGEX = /(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\p{Emoji_Modifier}|\uFE0F|\u200D(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\p{Emoji_Modifier})?)*|\p{Regional_Indicator}{2}/gu;
|
|
238
|
+
function hasEmoji(text) {
|
|
239
|
+
return EMOJI_REGEX.test(text);
|
|
240
|
+
}
|
|
241
|
+
function splitTextAndEmojis(text) {
|
|
242
|
+
const segments = [];
|
|
243
|
+
let lastIndex = 0;
|
|
244
|
+
EMOJI_REGEX.lastIndex = 0;
|
|
245
|
+
let match;
|
|
246
|
+
while ((match = EMOJI_REGEX.exec(text)) !== null) {
|
|
247
|
+
if (match.index > lastIndex) {
|
|
248
|
+
segments.push({
|
|
249
|
+
type: "text",
|
|
250
|
+
content: text.slice(lastIndex, match.index)
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
segments.push({
|
|
254
|
+
type: "emoji",
|
|
255
|
+
content: match[0],
|
|
256
|
+
imageUrl: getAppleEmojiUrl(match[0])
|
|
257
|
+
});
|
|
258
|
+
lastIndex = match.index + match[0].length;
|
|
259
|
+
}
|
|
260
|
+
if (lastIndex < text.length) {
|
|
261
|
+
segments.push({
|
|
262
|
+
type: "text",
|
|
263
|
+
content: text.slice(lastIndex)
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return segments;
|
|
267
|
+
}
|
|
268
|
+
|
|
221
269
|
// src/components/TextElement.tsx
|
|
222
270
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
223
271
|
function calculateAutoWidthAndLines({
|
|
@@ -352,14 +400,22 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
352
400
|
const backgroundOpacity = segment.backgroundOpacity ?? TEXT_DEFAULTS.backgroundOpacity;
|
|
353
401
|
const backgroundBorderRadius = segment.backgroundBorderRadius;
|
|
354
402
|
const fontFamily = getFontFamily(fontType);
|
|
355
|
-
const {
|
|
403
|
+
const { calculatedWidth, calculatedLines, paddingTop, paddingRight, paddingBottom, paddingLeft } = useMemo(() => {
|
|
404
|
+
let finalPaddingTop = basePaddingTop;
|
|
405
|
+
let finalPaddingRight = basePaddingRight;
|
|
406
|
+
let finalPaddingBottom = basePaddingBottom;
|
|
407
|
+
let finalPaddingLeft = basePaddingLeft;
|
|
356
408
|
if (!autoWidth && !hasExtraPadding) {
|
|
357
409
|
return {
|
|
358
|
-
|
|
359
|
-
|
|
410
|
+
calculatedWidth: width,
|
|
411
|
+
calculatedLines: [segment.text],
|
|
412
|
+
paddingTop: finalPaddingTop,
|
|
413
|
+
paddingRight: finalPaddingRight,
|
|
414
|
+
paddingBottom: finalPaddingBottom,
|
|
415
|
+
paddingLeft: finalPaddingLeft
|
|
360
416
|
};
|
|
361
417
|
}
|
|
362
|
-
const
|
|
418
|
+
const baseResult = calculateAutoWidthAndLines({
|
|
363
419
|
text: segment.text,
|
|
364
420
|
maxWidth: width,
|
|
365
421
|
paddingLeft: basePaddingLeft,
|
|
@@ -370,16 +426,47 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
370
426
|
letterSpacing,
|
|
371
427
|
lineHeight
|
|
372
428
|
});
|
|
429
|
+
const isMultiLine = baseResult.lines.length >= 2;
|
|
430
|
+
if (isMultiLine) {
|
|
431
|
+
finalPaddingTop = basePaddingTop + extraPaddingTop;
|
|
432
|
+
finalPaddingRight = basePaddingRight + extraPaddingRight;
|
|
433
|
+
finalPaddingBottom = basePaddingBottom + extraPaddingBottom;
|
|
434
|
+
finalPaddingLeft = basePaddingLeft + extraPaddingLeft;
|
|
435
|
+
}
|
|
436
|
+
let finalResult = baseResult;
|
|
437
|
+
if (isMultiLine && (extraPaddingLeft > 0 || extraPaddingRight > 0)) {
|
|
438
|
+
finalResult = calculateAutoWidthAndLines({
|
|
439
|
+
text: segment.text,
|
|
440
|
+
maxWidth: width,
|
|
441
|
+
paddingLeft: finalPaddingLeft,
|
|
442
|
+
paddingRight: finalPaddingRight,
|
|
443
|
+
fontSize,
|
|
444
|
+
fontWeight,
|
|
445
|
+
fontFamily,
|
|
446
|
+
letterSpacing,
|
|
447
|
+
lineHeight
|
|
448
|
+
});
|
|
449
|
+
}
|
|
373
450
|
return {
|
|
374
|
-
|
|
375
|
-
|
|
451
|
+
calculatedWidth: autoWidth ? finalResult.width : width,
|
|
452
|
+
calculatedLines: autoWidth ? finalResult.lines : [segment.text],
|
|
453
|
+
paddingTop: finalPaddingTop,
|
|
454
|
+
paddingRight: finalPaddingRight,
|
|
455
|
+
paddingBottom: finalPaddingBottom,
|
|
456
|
+
paddingLeft: finalPaddingLeft
|
|
376
457
|
};
|
|
377
458
|
}, [
|
|
378
459
|
autoWidth,
|
|
379
460
|
segment.text,
|
|
380
461
|
width,
|
|
381
|
-
|
|
462
|
+
basePaddingTop,
|
|
382
463
|
basePaddingRight,
|
|
464
|
+
basePaddingBottom,
|
|
465
|
+
basePaddingLeft,
|
|
466
|
+
extraPaddingTop,
|
|
467
|
+
extraPaddingRight,
|
|
468
|
+
extraPaddingBottom,
|
|
469
|
+
extraPaddingLeft,
|
|
383
470
|
hasExtraPadding,
|
|
384
471
|
fontSize,
|
|
385
472
|
fontWeight,
|
|
@@ -387,51 +474,6 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
387
474
|
letterSpacing,
|
|
388
475
|
lineHeight
|
|
389
476
|
]);
|
|
390
|
-
const calculatedLines = autoWidthResult?.lines ?? [segment.text];
|
|
391
|
-
const paddingTop = basePaddingTop + (isMultiLine ? extraPaddingTop : 0);
|
|
392
|
-
const paddingRight = basePaddingRight + (isMultiLine ? extraPaddingRight : 0);
|
|
393
|
-
const paddingBottom = basePaddingBottom + (isMultiLine ? extraPaddingBottom : 0);
|
|
394
|
-
const paddingLeft = basePaddingLeft + (isMultiLine ? extraPaddingLeft : 0);
|
|
395
|
-
const calculatedWidth = useMemo(() => {
|
|
396
|
-
if (!autoWidth) return width;
|
|
397
|
-
if (!isMultiLine || extraPaddingLeft === 0 && extraPaddingRight === 0) {
|
|
398
|
-
return autoWidthResult?.width ?? width;
|
|
399
|
-
}
|
|
400
|
-
if (typeof document === "undefined") return width;
|
|
401
|
-
const measureSpan = document.createElement("span");
|
|
402
|
-
measureSpan.style.cssText = `
|
|
403
|
-
position: absolute;
|
|
404
|
-
visibility: hidden;
|
|
405
|
-
pointer-events: none;
|
|
406
|
-
font-family: ${fontFamily};
|
|
407
|
-
font-size: ${fontSize}px;
|
|
408
|
-
font-weight: ${fontWeight};
|
|
409
|
-
letter-spacing: ${letterSpacing}px;
|
|
410
|
-
white-space: nowrap;
|
|
411
|
-
`;
|
|
412
|
-
document.body.appendChild(measureSpan);
|
|
413
|
-
let widestLineWidth = 0;
|
|
414
|
-
for (const line of calculatedLines) {
|
|
415
|
-
measureSpan.textContent = line;
|
|
416
|
-
widestLineWidth = Math.max(widestLineWidth, measureSpan.getBoundingClientRect().width);
|
|
417
|
-
}
|
|
418
|
-
document.body.removeChild(measureSpan);
|
|
419
|
-
return Math.min(widestLineWidth + paddingLeft + paddingRight, width);
|
|
420
|
-
}, [
|
|
421
|
-
autoWidth,
|
|
422
|
-
autoWidthResult,
|
|
423
|
-
width,
|
|
424
|
-
isMultiLine,
|
|
425
|
-
extraPaddingLeft,
|
|
426
|
-
extraPaddingRight,
|
|
427
|
-
paddingLeft,
|
|
428
|
-
paddingRight,
|
|
429
|
-
calculatedLines,
|
|
430
|
-
fontFamily,
|
|
431
|
-
fontSize,
|
|
432
|
-
fontWeight,
|
|
433
|
-
letterSpacing
|
|
434
|
-
]);
|
|
435
477
|
const borderRadiusStyle = useMemo(() => {
|
|
436
478
|
if (!backgroundBorderRadius) return void 0;
|
|
437
479
|
const radii = getBorderRadii(backgroundBorderRadius);
|
|
@@ -526,9 +568,35 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
526
568
|
autoWidth,
|
|
527
569
|
verticalAlign
|
|
528
570
|
]);
|
|
571
|
+
const renderTextWithEmojis = (text) => {
|
|
572
|
+
if (!hasEmoji(text)) {
|
|
573
|
+
return text;
|
|
574
|
+
}
|
|
575
|
+
const segments = splitTextAndEmojis(text);
|
|
576
|
+
return segments.map((segment2, i) => {
|
|
577
|
+
if (segment2.type === "emoji" && segment2.imageUrl) {
|
|
578
|
+
return /* @__PURE__ */ jsx(
|
|
579
|
+
"img",
|
|
580
|
+
{
|
|
581
|
+
src: segment2.imageUrl,
|
|
582
|
+
alt: segment2.content,
|
|
583
|
+
style: {
|
|
584
|
+
height: "1em",
|
|
585
|
+
width: "1em",
|
|
586
|
+
verticalAlign: "-0.1em",
|
|
587
|
+
display: "inline"
|
|
588
|
+
},
|
|
589
|
+
draggable: false
|
|
590
|
+
},
|
|
591
|
+
i
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: segment2.content }, i);
|
|
595
|
+
});
|
|
596
|
+
};
|
|
529
597
|
if (autoWidth) {
|
|
530
598
|
const textContent = calculatedLines.map((line, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
531
|
-
line,
|
|
599
|
+
renderTextWithEmojis(line),
|
|
532
600
|
index < calculatedLines.length - 1 && /* @__PURE__ */ jsx("br", {})
|
|
533
601
|
] }, index));
|
|
534
602
|
if (backgroundColor) {
|
|
@@ -547,7 +615,7 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
547
615
|
}
|
|
548
616
|
return /* @__PURE__ */ jsx("div", { style: positioningContainerStyle, children: /* @__PURE__ */ jsx("div", { style: { width: calculatedWidth, maxWidth: width }, children: /* @__PURE__ */ jsx("div", { style: textStyle, children: textContent }) }) });
|
|
549
617
|
}
|
|
550
|
-
return /* @__PURE__ */ jsx("div", { style: positioningContainerStyle, children: /* @__PURE__ */ jsx("div", { style: backgroundBoxStyle, children: /* @__PURE__ */ jsx("div", { style: textStyle, children: segment.text }) }) });
|
|
618
|
+
return /* @__PURE__ */ jsx("div", { style: positioningContainerStyle, children: /* @__PURE__ */ jsx("div", { style: backgroundBoxStyle, children: /* @__PURE__ */ jsx("div", { style: textStyle, children: renderTextWithEmojis(segment.text) }) }) });
|
|
551
619
|
}
|
|
552
620
|
|
|
553
621
|
// src/components/ImageElement.tsx
|
|
@@ -1866,9 +1934,11 @@ export {
|
|
|
1866
1934
|
calculateTimelineContentEnd,
|
|
1867
1935
|
canSetAsReference,
|
|
1868
1936
|
defaultOffset,
|
|
1937
|
+
emojiToUnified,
|
|
1869
1938
|
formatTime,
|
|
1870
1939
|
generateOverlayId,
|
|
1871
1940
|
generateSegmentId,
|
|
1941
|
+
getAppleEmojiUrl,
|
|
1872
1942
|
getBaseSegments,
|
|
1873
1943
|
getBorderRadii,
|
|
1874
1944
|
getDependentElements,
|
|
@@ -1877,6 +1947,7 @@ export {
|
|
|
1877
1947
|
getReferenceElementX,
|
|
1878
1948
|
getReferenceElementY,
|
|
1879
1949
|
getSegmentTimelinePosition,
|
|
1950
|
+
hasEmoji,
|
|
1880
1951
|
hexToRgba,
|
|
1881
1952
|
isDynamicCropEnabled,
|
|
1882
1953
|
isSegmentVisibleAtTime,
|
|
@@ -1884,6 +1955,7 @@ export {
|
|
|
1884
1955
|
parseTime,
|
|
1885
1956
|
preloadFonts,
|
|
1886
1957
|
resolveElementPositions,
|
|
1958
|
+
splitTextAndEmojis,
|
|
1887
1959
|
useFontsLoaded,
|
|
1888
1960
|
useImageLoader,
|
|
1889
1961
|
useImagePreloader,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ugcinc-render",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.12",
|
|
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",
|