ugcinc-render 1.5.15 → 1.5.16

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 CHANGED
@@ -955,9 +955,14 @@ declare function VideoElement({ segment, src, startFrame, durationInFrames, scal
955
955
  * Font utilities for the rendering system
956
956
  */
957
957
 
958
+ /**
959
+ * Apple Color Emoji font name - used as fallback for emoji characters
960
+ */
961
+ declare const APPLE_EMOJI_FONT = "\"Apple Color Emoji\"";
958
962
  /**
959
963
  * Font family CSS strings for each font type
960
964
  * These match the fonts registered in the rendering system
965
+ * Apple Color Emoji is added as a fallback for consistent emoji rendering
961
966
  */
962
967
  declare const FONT_FAMILIES: Record<FontType, string>;
963
968
  /**
@@ -984,6 +989,9 @@ declare const FONT_URLS: {
984
989
  apple: {
985
990
  regular: string;
986
991
  };
992
+ emoji: {
993
+ apple: string;
994
+ };
987
995
  };
988
996
  /**
989
997
  * Preload fonts for rendering
@@ -1290,34 +1298,6 @@ declare function generateSegmentId(): string;
1290
1298
  */
1291
1299
  declare function generateOverlayId(): string;
1292
1300
 
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
-
1321
1301
  /**
1322
1302
  * Hook exports for ugcinc-render
1323
1303
  *
@@ -1377,4 +1357,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
1377
1357
 
1378
1358
  declare const RenderRoot: React.FC;
1379
1359
 
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 };
1360
+ export { APPLE_EMOJI_FONT, 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 };
package/dist/index.d.ts CHANGED
@@ -955,9 +955,14 @@ declare function VideoElement({ segment, src, startFrame, durationInFrames, scal
955
955
  * Font utilities for the rendering system
956
956
  */
957
957
 
958
+ /**
959
+ * Apple Color Emoji font name - used as fallback for emoji characters
960
+ */
961
+ declare const APPLE_EMOJI_FONT = "\"Apple Color Emoji\"";
958
962
  /**
959
963
  * Font family CSS strings for each font type
960
964
  * These match the fonts registered in the rendering system
965
+ * Apple Color Emoji is added as a fallback for consistent emoji rendering
961
966
  */
962
967
  declare const FONT_FAMILIES: Record<FontType, string>;
963
968
  /**
@@ -984,6 +989,9 @@ declare const FONT_URLS: {
984
989
  apple: {
985
990
  regular: string;
986
991
  };
992
+ emoji: {
993
+ apple: string;
994
+ };
987
995
  };
988
996
  /**
989
997
  * Preload fonts for rendering
@@ -1290,34 +1298,6 @@ declare function generateSegmentId(): string;
1290
1298
  */
1291
1299
  declare function generateOverlayId(): string;
1292
1300
 
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
-
1321
1301
  /**
1322
1302
  * Hook exports for ugcinc-render
1323
1303
  *
@@ -1377,4 +1357,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
1377
1357
 
1378
1358
  declare const RenderRoot: React.FC;
1379
1359
 
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 };
1360
+ export { APPLE_EMOJI_FONT, 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 };
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ APPLE_EMOJI_FONT: () => APPLE_EMOJI_FONT,
33
34
  DIMENSION_PRESETS: () => DIMENSION_PRESETS,
34
35
  FONT_FAMILIES: () => FONT_FAMILIES,
35
36
  FONT_URLS: () => FONT_URLS,
@@ -56,11 +57,9 @@ __export(index_exports, {
56
57
  calculateTimelineContentEnd: () => calculateTimelineContentEnd,
57
58
  canSetAsReference: () => canSetAsReference,
58
59
  defaultOffset: () => defaultOffset,
59
- emojiToUnified: () => emojiToUnified,
60
60
  formatTime: () => formatTime,
61
61
  generateOverlayId: () => generateOverlayId,
62
62
  generateSegmentId: () => generateSegmentId,
63
- getAppleEmojiUrl: () => getAppleEmojiUrl,
64
63
  getBaseSegments: () => getBaseSegments,
65
64
  getBorderRadii: () => getBorderRadii,
66
65
  getDependentElements: () => getDependentElements,
@@ -69,7 +68,6 @@ __export(index_exports, {
69
68
  getReferenceElementX: () => getReferenceElementX,
70
69
  getReferenceElementY: () => getReferenceElementY,
71
70
  getSegmentTimelinePosition: () => getSegmentTimelinePosition,
72
- hasEmoji: () => hasEmoji,
73
71
  hexToRgba: () => hexToRgba,
74
72
  isDynamicCropEnabled: () => isDynamicCropEnabled,
75
73
  isSegmentVisibleAtTime: () => isSegmentVisibleAtTime,
@@ -77,7 +75,6 @@ __export(index_exports, {
77
75
  parseTime: () => parseTime,
78
76
  preloadFonts: () => preloadFonts,
79
77
  resolveElementPositions: () => resolveElementPositions,
80
- splitTextAndEmojis: () => splitTextAndEmojis,
81
78
  useFontsLoaded: () => useFontsLoaded,
82
79
  useImageLoader: () => useImageLoader,
83
80
  useImagePreloader: () => useImagePreloader,
@@ -163,10 +160,11 @@ function applyVideoDefaults(segment) {
163
160
  }
164
161
 
165
162
  // src/utils/fonts.ts
163
+ var APPLE_EMOJI_FONT = '"Apple Color Emoji"';
166
164
  var FONT_FAMILIES = {
167
- tiktok: '"TikTok Sans", sans-serif',
168
- apple: '"SF Pro", "SF Pro Display", -apple-system, BlinkMacSystemFont, sans-serif',
169
- arial: "Arial, sans-serif"
165
+ tiktok: `"TikTok Sans", ${APPLE_EMOJI_FONT}, sans-serif`,
166
+ apple: `"SF Pro", "SF Pro Display", -apple-system, BlinkMacSystemFont, ${APPLE_EMOJI_FONT}, sans-serif`,
167
+ arial: `Arial, ${APPLE_EMOJI_FONT}, sans-serif`
170
168
  };
171
169
  function getFontFamily(fontType) {
172
170
  return FONT_FAMILIES[fontType] ?? FONT_FAMILIES.arial;
@@ -186,6 +184,9 @@ var FONT_URLS = {
186
184
  },
187
185
  apple: {
188
186
  regular: "/SF-Pro.ttf"
187
+ },
188
+ emoji: {
189
+ apple: "/AppleColorEmoji.ttf"
189
190
  }
190
191
  };
191
192
  async function preloadFonts() {
@@ -209,6 +210,12 @@ async function preloadFonts() {
209
210
  { weight: "normal" }
210
211
  );
211
212
  fontPromises.push(sfPro.load());
213
+ const appleEmoji = new FontFace(
214
+ "Apple Color Emoji",
215
+ `url(${FONT_URLS.emoji.apple})`,
216
+ { weight: "normal" }
217
+ );
218
+ fontPromises.push(appleEmoji.load());
212
219
  const loadedFonts = await Promise.all(fontPromises);
213
220
  loadedFonts.forEach((font) => {
214
221
  document.fonts.add(font);
@@ -219,7 +226,7 @@ function areFontsLoaded() {
219
226
  if (typeof document === "undefined") {
220
227
  return false;
221
228
  }
222
- return document.fonts.check('normal 16px "TikTok Sans"') && document.fonts.check('bold 16px "TikTok Sans"') && document.fonts.check('normal 16px "SF Pro"');
229
+ return document.fonts.check('normal 16px "TikTok Sans"') && document.fonts.check('bold 16px "TikTok Sans"') && document.fonts.check('normal 16px "SF Pro"') && document.fonts.check('normal 16px "Apple Color Emoji"');
223
230
  }
224
231
 
225
232
  // src/utils/text.ts
@@ -306,66 +313,6 @@ function hexToRgba(hex, opacity = 100) {
306
313
  return `rgba(${r}, ${g}, ${b}, ${opacity / 100})`;
307
314
  }
308
315
 
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
- function countEmojis(text) {
357
- EMOJI_REGEX.lastIndex = 0;
358
- const matches = text.match(EMOJI_REGEX);
359
- return matches ? matches.length : 0;
360
- }
361
- function getEmojiWidth(fontSize) {
362
- return fontSize * 1.2;
363
- }
364
- function stripEmojis(text) {
365
- EMOJI_REGEX.lastIndex = 0;
366
- return text.replace(EMOJI_REGEX, "");
367
- }
368
-
369
316
  // src/components/TextElement.tsx
370
317
  var import_jsx_runtime = require("react/jsx-runtime");
371
318
  function calculateAutoWidthAndLines({
@@ -398,17 +345,9 @@ function calculateAutoWidthAndLines({
398
345
  white-space: nowrap;
399
346
  `;
400
347
  document.body.appendChild(measureSpan);
401
- const emojiWidth = getEmojiWidth(fontSize);
402
- const measureTextWithEmojis = (textToMeasure) => {
403
- const emojiCount = countEmojis(textToMeasure);
404
- if (emojiCount === 0) {
405
- measureSpan.textContent = textToMeasure;
406
- return measureSpan.getBoundingClientRect().width;
407
- }
408
- const textWithoutEmojis = stripEmojis(textToMeasure);
409
- measureSpan.textContent = textWithoutEmojis;
410
- const textWidth = measureSpan.getBoundingClientRect().width;
411
- return textWidth + emojiCount * emojiWidth;
348
+ const measureText = (textToMeasure) => {
349
+ measureSpan.textContent = textToMeasure;
350
+ return measureSpan.getBoundingClientRect().width;
412
351
  };
413
352
  measureSpan.textContent = "M";
414
353
  const charWidth = measureSpan.getBoundingClientRect().width;
@@ -432,12 +371,12 @@ function calculateAutoWidthAndLines({
432
371
  continue;
433
372
  }
434
373
  const testLine = currentLine + word;
435
- const testWidth = measureTextWithEmojis(testLine);
374
+ const testWidth = measureText(testLine);
436
375
  const WRAP_TOLERANCE = 0.5;
437
376
  if (testWidth > availableWidth + WRAP_TOLERANCE && currentLine.trim()) {
438
377
  lines.push(currentLine.trimEnd());
439
378
  currentLine = word;
440
- const wordWidth = measureTextWithEmojis(word);
379
+ const wordWidth = measureText(word);
441
380
  if (wordWidth > availableWidth) {
442
381
  let remainingWord = word;
443
382
  while (remainingWord) {
@@ -471,7 +410,7 @@ function calculateAutoWidthAndLines({
471
410
  }
472
411
  let widestLineWidth = 0;
473
412
  for (const line of lines) {
474
- const lineWidth = measureTextWithEmojis(line);
413
+ const lineWidth = measureText(line);
475
414
  widestLineWidth = Math.max(widestLineWidth, lineWidth);
476
415
  }
477
416
  document.body.removeChild(measureSpan);
@@ -678,35 +617,9 @@ function TextElement({ segment, scale = 1 }) {
678
617
  autoWidth,
679
618
  verticalAlign
680
619
  ]);
681
- const renderTextWithEmojis = (text) => {
682
- if (!hasEmoji(text)) {
683
- return text;
684
- }
685
- const segments = splitTextAndEmojis(text);
686
- return segments.map((segment2, i) => {
687
- if (segment2.type === "emoji" && segment2.imageUrl) {
688
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
689
- "img",
690
- {
691
- src: segment2.imageUrl,
692
- alt: segment2.content,
693
- style: {
694
- height: "1.2em",
695
- width: "1.2em",
696
- verticalAlign: "-0.25em",
697
- display: "inline"
698
- },
699
- draggable: false
700
- },
701
- i
702
- );
703
- }
704
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.default.Fragment, { children: segment2.content }, i);
705
- });
706
- };
707
620
  if (autoWidth) {
708
621
  const textContent = calculatedLines.map((line, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.default.Fragment, { children: [
709
- renderTextWithEmojis(line),
622
+ line,
710
623
  index < calculatedLines.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {})
711
624
  ] }, index));
712
625
  if (backgroundColor) {
@@ -725,7 +638,7 @@ function TextElement({ segment, scale = 1 }) {
725
638
  }
726
639
  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 }) }) });
727
640
  }
728
- 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) }) }) });
641
+ 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 }) }) });
729
642
  }
730
643
 
731
644
  // src/components/ImageElement.tsx
@@ -2019,6 +1932,7 @@ var RenderRoot = () => {
2019
1932
  };
2020
1933
  // Annotate the CommonJS export names for ESM import in node:
2021
1934
  0 && (module.exports = {
1935
+ APPLE_EMOJI_FONT,
2022
1936
  DIMENSION_PRESETS,
2023
1937
  FONT_FAMILIES,
2024
1938
  FONT_URLS,
@@ -2045,11 +1959,9 @@ var RenderRoot = () => {
2045
1959
  calculateTimelineContentEnd,
2046
1960
  canSetAsReference,
2047
1961
  defaultOffset,
2048
- emojiToUnified,
2049
1962
  formatTime,
2050
1963
  generateOverlayId,
2051
1964
  generateSegmentId,
2052
- getAppleEmojiUrl,
2053
1965
  getBaseSegments,
2054
1966
  getBorderRadii,
2055
1967
  getDependentElements,
@@ -2058,7 +1970,6 @@ var RenderRoot = () => {
2058
1970
  getReferenceElementX,
2059
1971
  getReferenceElementY,
2060
1972
  getSegmentTimelinePosition,
2061
- hasEmoji,
2062
1973
  hexToRgba,
2063
1974
  isDynamicCropEnabled,
2064
1975
  isSegmentVisibleAtTime,
@@ -2066,7 +1977,6 @@ var RenderRoot = () => {
2066
1977
  parseTime,
2067
1978
  preloadFonts,
2068
1979
  resolveElementPositions,
2069
- splitTextAndEmojis,
2070
1980
  useFontsLoaded,
2071
1981
  useImageLoader,
2072
1982
  useImagePreloader,
package/dist/index.mjs CHANGED
@@ -75,10 +75,11 @@ function applyVideoDefaults(segment) {
75
75
  }
76
76
 
77
77
  // src/utils/fonts.ts
78
+ var APPLE_EMOJI_FONT = '"Apple Color Emoji"';
78
79
  var FONT_FAMILIES = {
79
- tiktok: '"TikTok Sans", sans-serif',
80
- apple: '"SF Pro", "SF Pro Display", -apple-system, BlinkMacSystemFont, sans-serif',
81
- arial: "Arial, sans-serif"
80
+ tiktok: `"TikTok Sans", ${APPLE_EMOJI_FONT}, sans-serif`,
81
+ apple: `"SF Pro", "SF Pro Display", -apple-system, BlinkMacSystemFont, ${APPLE_EMOJI_FONT}, sans-serif`,
82
+ arial: `Arial, ${APPLE_EMOJI_FONT}, sans-serif`
82
83
  };
83
84
  function getFontFamily(fontType) {
84
85
  return FONT_FAMILIES[fontType] ?? FONT_FAMILIES.arial;
@@ -98,6 +99,9 @@ var FONT_URLS = {
98
99
  },
99
100
  apple: {
100
101
  regular: "/SF-Pro.ttf"
102
+ },
103
+ emoji: {
104
+ apple: "/AppleColorEmoji.ttf"
101
105
  }
102
106
  };
103
107
  async function preloadFonts() {
@@ -121,6 +125,12 @@ async function preloadFonts() {
121
125
  { weight: "normal" }
122
126
  );
123
127
  fontPromises.push(sfPro.load());
128
+ const appleEmoji = new FontFace(
129
+ "Apple Color Emoji",
130
+ `url(${FONT_URLS.emoji.apple})`,
131
+ { weight: "normal" }
132
+ );
133
+ fontPromises.push(appleEmoji.load());
124
134
  const loadedFonts = await Promise.all(fontPromises);
125
135
  loadedFonts.forEach((font) => {
126
136
  document.fonts.add(font);
@@ -131,7 +141,7 @@ function areFontsLoaded() {
131
141
  if (typeof document === "undefined") {
132
142
  return false;
133
143
  }
134
- return document.fonts.check('normal 16px "TikTok Sans"') && document.fonts.check('bold 16px "TikTok Sans"') && document.fonts.check('normal 16px "SF Pro"');
144
+ return document.fonts.check('normal 16px "TikTok Sans"') && document.fonts.check('bold 16px "TikTok Sans"') && document.fonts.check('normal 16px "SF Pro"') && document.fonts.check('normal 16px "Apple Color Emoji"');
135
145
  }
136
146
 
137
147
  // src/utils/text.ts
@@ -218,66 +228,6 @@ function hexToRgba(hex, opacity = 100) {
218
228
  return `rgba(${r}, ${g}, ${b}, ${opacity / 100})`;
219
229
  }
220
230
 
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
- function countEmojis(text) {
269
- EMOJI_REGEX.lastIndex = 0;
270
- const matches = text.match(EMOJI_REGEX);
271
- return matches ? matches.length : 0;
272
- }
273
- function getEmojiWidth(fontSize) {
274
- return fontSize * 1.2;
275
- }
276
- function stripEmojis(text) {
277
- EMOJI_REGEX.lastIndex = 0;
278
- return text.replace(EMOJI_REGEX, "");
279
- }
280
-
281
231
  // src/components/TextElement.tsx
282
232
  import { jsx, jsxs } from "react/jsx-runtime";
283
233
  function calculateAutoWidthAndLines({
@@ -310,17 +260,9 @@ function calculateAutoWidthAndLines({
310
260
  white-space: nowrap;
311
261
  `;
312
262
  document.body.appendChild(measureSpan);
313
- const emojiWidth = getEmojiWidth(fontSize);
314
- const measureTextWithEmojis = (textToMeasure) => {
315
- const emojiCount = countEmojis(textToMeasure);
316
- if (emojiCount === 0) {
317
- measureSpan.textContent = textToMeasure;
318
- return measureSpan.getBoundingClientRect().width;
319
- }
320
- const textWithoutEmojis = stripEmojis(textToMeasure);
321
- measureSpan.textContent = textWithoutEmojis;
322
- const textWidth = measureSpan.getBoundingClientRect().width;
323
- return textWidth + emojiCount * emojiWidth;
263
+ const measureText = (textToMeasure) => {
264
+ measureSpan.textContent = textToMeasure;
265
+ return measureSpan.getBoundingClientRect().width;
324
266
  };
325
267
  measureSpan.textContent = "M";
326
268
  const charWidth = measureSpan.getBoundingClientRect().width;
@@ -344,12 +286,12 @@ function calculateAutoWidthAndLines({
344
286
  continue;
345
287
  }
346
288
  const testLine = currentLine + word;
347
- const testWidth = measureTextWithEmojis(testLine);
289
+ const testWidth = measureText(testLine);
348
290
  const WRAP_TOLERANCE = 0.5;
349
291
  if (testWidth > availableWidth + WRAP_TOLERANCE && currentLine.trim()) {
350
292
  lines.push(currentLine.trimEnd());
351
293
  currentLine = word;
352
- const wordWidth = measureTextWithEmojis(word);
294
+ const wordWidth = measureText(word);
353
295
  if (wordWidth > availableWidth) {
354
296
  let remainingWord = word;
355
297
  while (remainingWord) {
@@ -383,7 +325,7 @@ function calculateAutoWidthAndLines({
383
325
  }
384
326
  let widestLineWidth = 0;
385
327
  for (const line of lines) {
386
- const lineWidth = measureTextWithEmojis(line);
328
+ const lineWidth = measureText(line);
387
329
  widestLineWidth = Math.max(widestLineWidth, lineWidth);
388
330
  }
389
331
  document.body.removeChild(measureSpan);
@@ -590,35 +532,9 @@ function TextElement({ segment, scale = 1 }) {
590
532
  autoWidth,
591
533
  verticalAlign
592
534
  ]);
593
- const renderTextWithEmojis = (text) => {
594
- if (!hasEmoji(text)) {
595
- return text;
596
- }
597
- const segments = splitTextAndEmojis(text);
598
- return segments.map((segment2, i) => {
599
- if (segment2.type === "emoji" && segment2.imageUrl) {
600
- return /* @__PURE__ */ jsx(
601
- "img",
602
- {
603
- src: segment2.imageUrl,
604
- alt: segment2.content,
605
- style: {
606
- height: "1.2em",
607
- width: "1.2em",
608
- verticalAlign: "-0.25em",
609
- display: "inline"
610
- },
611
- draggable: false
612
- },
613
- i
614
- );
615
- }
616
- return /* @__PURE__ */ jsx(React.Fragment, { children: segment2.content }, i);
617
- });
618
- };
619
535
  if (autoWidth) {
620
536
  const textContent = calculatedLines.map((line, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
621
- renderTextWithEmojis(line),
537
+ line,
622
538
  index < calculatedLines.length - 1 && /* @__PURE__ */ jsx("br", {})
623
539
  ] }, index));
624
540
  if (backgroundColor) {
@@ -637,7 +553,7 @@ function TextElement({ segment, scale = 1 }) {
637
553
  }
638
554
  return /* @__PURE__ */ jsx("div", { style: positioningContainerStyle, children: /* @__PURE__ */ jsx("div", { style: { width: calculatedWidth, maxWidth: width }, children: /* @__PURE__ */ jsx("div", { style: textStyle, children: textContent }) }) });
639
555
  }
640
- return /* @__PURE__ */ jsx("div", { style: positioningContainerStyle, children: /* @__PURE__ */ jsx("div", { style: backgroundBoxStyle, children: /* @__PURE__ */ jsx("div", { style: textStyle, children: renderTextWithEmojis(segment.text) }) }) });
556
+ return /* @__PURE__ */ jsx("div", { style: positioningContainerStyle, children: /* @__PURE__ */ jsx("div", { style: backgroundBoxStyle, children: /* @__PURE__ */ jsx("div", { style: textStyle, children: segment.text }) }) });
641
557
  }
642
558
 
643
559
  // src/components/ImageElement.tsx
@@ -1930,6 +1846,7 @@ var RenderRoot = () => {
1930
1846
  ] });
1931
1847
  };
1932
1848
  export {
1849
+ APPLE_EMOJI_FONT,
1933
1850
  DIMENSION_PRESETS,
1934
1851
  FONT_FAMILIES,
1935
1852
  FONT_URLS,
@@ -1956,11 +1873,9 @@ export {
1956
1873
  calculateTimelineContentEnd,
1957
1874
  canSetAsReference,
1958
1875
  defaultOffset,
1959
- emojiToUnified,
1960
1876
  formatTime,
1961
1877
  generateOverlayId,
1962
1878
  generateSegmentId,
1963
- getAppleEmojiUrl,
1964
1879
  getBaseSegments,
1965
1880
  getBorderRadii,
1966
1881
  getDependentElements,
@@ -1969,7 +1884,6 @@ export {
1969
1884
  getReferenceElementX,
1970
1885
  getReferenceElementY,
1971
1886
  getSegmentTimelinePosition,
1972
- hasEmoji,
1973
1887
  hexToRgba,
1974
1888
  isDynamicCropEnabled,
1975
1889
  isSegmentVisibleAtTime,
@@ -1977,7 +1891,6 @@ export {
1977
1891
  parseTime,
1978
1892
  preloadFonts,
1979
1893
  resolveElementPositions,
1980
- splitTextAndEmojis,
1981
1894
  useFontsLoaded,
1982
1895
  useImageLoader,
1983
1896
  useImagePreloader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugcinc-render",
3
- "version": "1.5.15",
3
+ "version": "1.5.16",
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",