ugcinc 4.5.73 → 4.5.74
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.
|
@@ -15,12 +15,16 @@ export interface TextElementProps {
|
|
|
15
15
|
* - Font family, size, weight, color
|
|
16
16
|
* - Letter spacing, line height
|
|
17
17
|
* - Horizontal and vertical alignment
|
|
18
|
-
* - Text stroke/outline
|
|
18
|
+
* - Text stroke/outline (via -webkit-text-stroke + paint-order)
|
|
19
19
|
* - Background with opacity and border radius
|
|
20
20
|
* - Padding (uniform and individual)
|
|
21
21
|
* - Auto-width with box alignment
|
|
22
22
|
* - Rotation
|
|
23
23
|
*
|
|
24
|
+
* Stroke rendering uses -webkit-text-stroke with paint-order: stroke fill.
|
|
25
|
+
* This naturally excludes emoji bitmaps from the outline (only text glyphs
|
|
26
|
+
* are stroked) and avoids the layout issues of a separate stroke layer.
|
|
27
|
+
*
|
|
24
28
|
* When autoWidth is enabled:
|
|
25
29
|
* - Text wraps at maxWidth (the element's width)
|
|
26
30
|
* - Background shrinks to the widest line
|
|
@@ -45,7 +45,6 @@ const react_1 = __importStar(require("react"));
|
|
|
45
45
|
const defaults_1 = require("../utils/defaults");
|
|
46
46
|
const fonts_1 = require("../utils/fonts");
|
|
47
47
|
const text_1 = require("../utils/text");
|
|
48
|
-
const emoji_1 = require("../utils/emoji");
|
|
49
48
|
/**
|
|
50
49
|
* Calculate the actual width for auto-width text AND the line breaks.
|
|
51
50
|
* Uses DOM-based measurement to ensure the same fonts are used as CSS rendering.
|
|
@@ -162,73 +161,21 @@ function calculateAutoWidthAndLines({ text, maxWidth, paddingLeft, paddingRight,
|
|
|
162
161
|
const calculatedWidth = Math.min(widestLineWidth + paddingLeft + paddingRight, maxWidth);
|
|
163
162
|
return { width: calculatedWidth, lines };
|
|
164
163
|
}
|
|
165
|
-
/** Word Joiner (U+2060) — zero-width character that prevents line breaks */
|
|
166
|
-
const WJ = '\u2060';
|
|
167
|
-
/** Style for emoji spans in the fill layer — no visual change */
|
|
168
|
-
const emojiSpanStyle = { display: 'inline' };
|
|
169
|
-
/** Style for emoji spans in the stroke layer — hidden from SVG filter */
|
|
170
|
-
const emojiSpanStrokeStyle = { display: 'inline', opacity: 0 };
|
|
171
|
-
/**
|
|
172
|
-
* Group consecutive parts from splitTextAndEmojis into merged groups.
|
|
173
|
-
* Consecutive emoji parts are joined into a single group so they share one span,
|
|
174
|
-
* reducing break opportunities.
|
|
175
|
-
*/
|
|
176
|
-
function groupEmojiParts(parts) {
|
|
177
|
-
const groups = [];
|
|
178
|
-
for (const part of parts) {
|
|
179
|
-
const last = groups[groups.length - 1];
|
|
180
|
-
if (last && last.type === part.type) {
|
|
181
|
-
last.content += part.content;
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
groups.push({ type: part.type, content: part.content });
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return groups;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Render text with emojis wrapped in spans + Word Joiners at span boundaries.
|
|
191
|
-
*
|
|
192
|
-
* Both fill and stroke divs use this so they have identical DOM structure
|
|
193
|
-
* (matching vertical metrics and line breaks). The only difference is the
|
|
194
|
-
* emoji span style: fill spans are visible, stroke spans have opacity: 0.
|
|
195
|
-
*
|
|
196
|
-
* Word Joiners (U+2060) are inserted at boundaries between text nodes and
|
|
197
|
-
* emoji spans to prevent the browser from introducing break opportunities
|
|
198
|
-
* that don't exist in the original plain text.
|
|
199
|
-
*/
|
|
200
|
-
function renderTextWithEmojiSpans(text, forStroke) {
|
|
201
|
-
if (!(0, emoji_1.hasEmoji)(text))
|
|
202
|
-
return text;
|
|
203
|
-
const parts = (0, emoji_1.splitTextAndEmojis)(text);
|
|
204
|
-
const groups = groupEmojiParts(parts);
|
|
205
|
-
const style = forStroke ? emojiSpanStrokeStyle : emojiSpanStyle;
|
|
206
|
-
const result = [];
|
|
207
|
-
for (let i = 0; i < groups.length; i++) {
|
|
208
|
-
const group = groups[i];
|
|
209
|
-
// Insert Word Joiner between adjacent groups to prevent unwanted line breaks
|
|
210
|
-
if (i > 0)
|
|
211
|
-
result.push(WJ);
|
|
212
|
-
if (group.type === 'emoji') {
|
|
213
|
-
result.push((0, jsx_runtime_1.jsx)("span", { style: style, children: group.content }, i));
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
result.push(group.content);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return result;
|
|
220
|
-
}
|
|
221
164
|
/**
|
|
222
165
|
* TextElement renders text with full styling support including:
|
|
223
166
|
* - Font family, size, weight, color
|
|
224
167
|
* - Letter spacing, line height
|
|
225
168
|
* - Horizontal and vertical alignment
|
|
226
|
-
* - Text stroke/outline
|
|
169
|
+
* - Text stroke/outline (via -webkit-text-stroke + paint-order)
|
|
227
170
|
* - Background with opacity and border radius
|
|
228
171
|
* - Padding (uniform and individual)
|
|
229
172
|
* - Auto-width with box alignment
|
|
230
173
|
* - Rotation
|
|
231
174
|
*
|
|
175
|
+
* Stroke rendering uses -webkit-text-stroke with paint-order: stroke fill.
|
|
176
|
+
* This naturally excludes emoji bitmaps from the outline (only text glyphs
|
|
177
|
+
* are stroked) and avoids the layout issues of a separate stroke layer.
|
|
178
|
+
*
|
|
232
179
|
* When autoWidth is enabled:
|
|
233
180
|
* - Text wraps at maxWidth (the element's width)
|
|
234
181
|
* - Background shrinks to the widest line
|
|
@@ -376,8 +323,7 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
376
323
|
};
|
|
377
324
|
}, [autoWidth, calculatedWidth, width, backgroundColor, backgroundOpacity, borderRadiusStyle]);
|
|
378
325
|
const hasStroke = strokeWidth > 0 && !!strokeColor;
|
|
379
|
-
|
|
380
|
-
// Text style (fill layer)
|
|
326
|
+
// Text style — single div handles both fill and stroke via paint-order
|
|
381
327
|
const textStyle = (0, react_1.useMemo)(() => ({
|
|
382
328
|
fontFamily,
|
|
383
329
|
fontSize,
|
|
@@ -396,45 +342,27 @@ function TextElement({ segment, scale = 1 }) {
|
|
|
396
342
|
flex: 1,
|
|
397
343
|
minHeight: 0,
|
|
398
344
|
}),
|
|
399
|
-
...(hasStroke && {
|
|
345
|
+
...(hasStroke && {
|
|
346
|
+
WebkitTextStroke: `${strokeWidth}px ${strokeColor}`,
|
|
347
|
+
paintOrder: 'stroke fill',
|
|
348
|
+
}),
|
|
400
349
|
}), [
|
|
401
350
|
fontFamily, fontSize, fontWeight, color, lineHeight, letterSpacing, alignment,
|
|
402
351
|
paddingTop, paddingRight, paddingBottom, paddingLeft,
|
|
403
|
-
autoWidth, verticalAlign, hasStroke
|
|
352
|
+
autoWidth, verticalAlign, hasStroke, strokeWidth, strokeColor
|
|
404
353
|
]);
|
|
405
|
-
// Stroke underlay style — same size as fill div, SVG filter handles overflow.
|
|
406
|
-
const strokeTextStyle = (0, react_1.useMemo)(() => {
|
|
407
|
-
if (!hasStroke)
|
|
408
|
-
return undefined;
|
|
409
|
-
return {
|
|
410
|
-
...textStyle,
|
|
411
|
-
color: strokeColor,
|
|
412
|
-
filter: `url(#${filterId})`,
|
|
413
|
-
position: 'absolute',
|
|
414
|
-
zIndex: 0,
|
|
415
|
-
top: 0,
|
|
416
|
-
left: 0,
|
|
417
|
-
right: 0,
|
|
418
|
-
bottom: 0,
|
|
419
|
-
};
|
|
420
|
-
}, [hasStroke, textStyle, strokeColor, filterId]);
|
|
421
|
-
// SVG filter for smooth text outline (avoids miter spikes from -webkit-text-stroke)
|
|
422
|
-
const strokePadPct = Math.max(50, Math.ceil((strokeWidth / fontSize) * 200));
|
|
423
|
-
const strokeFilterSvg = hasStroke ? ((0, jsx_runtime_1.jsx)("svg", { style: { position: 'absolute', width: 0, height: 0, overflow: 'hidden' }, "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("defs", { children: (0, jsx_runtime_1.jsxs)("filter", { id: filterId, x: `-${strokePadPct}%`, y: `-${strokePadPct}%`, width: `${100 + 2 * strokePadPct}%`, height: `${100 + 2 * strokePadPct}%`, children: [(0, jsx_runtime_1.jsx)("feMorphology", { in: "SourceAlpha", operator: "dilate", radius: strokeWidth, result: "dilated" }), (0, jsx_runtime_1.jsx)("feFlood", { floodColor: strokeColor, result: "color" }), (0, jsx_runtime_1.jsx)("feComposite", { in: "color", in2: "dilated", operator: "in" })] }) }) })) : null;
|
|
424
354
|
if (autoWidth) {
|
|
425
|
-
const textContent = calculatedLines.map((line, index) => ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [
|
|
426
|
-
const strokeContent = calculatedLines.map((line, index) => ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [renderTextWithEmojiSpans(line, true), index < calculatedLines.length - 1 && (0, jsx_runtime_1.jsx)("br", {})] }, index)));
|
|
355
|
+
const textContent = calculatedLines.map((line, index) => ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [line, index < calculatedLines.length - 1 && (0, jsx_runtime_1.jsx)("br", {})] }, index)));
|
|
427
356
|
if (backgroundColor) {
|
|
428
|
-
return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.
|
|
357
|
+
return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.jsx)("div", { style: {
|
|
429
358
|
width: calculatedWidth,
|
|
430
359
|
maxWidth: width,
|
|
431
360
|
backgroundColor: (0, text_1.hexToRgba)(backgroundColor, backgroundOpacity),
|
|
432
361
|
borderRadius: borderRadiusStyle,
|
|
433
|
-
|
|
434
|
-
}, children: [strokeFilterSvg, strokeTextStyle && (0, jsx_runtime_1.jsx)("div", { style: strokeTextStyle, "aria-hidden": "true", children: strokeContent }), (0, jsx_runtime_1.jsx)("div", { style: textStyle, children: textContent })] }) }));
|
|
362
|
+
}, children: (0, jsx_runtime_1.jsx)("div", { style: textStyle, children: textContent }) }) }));
|
|
435
363
|
}
|
|
436
|
-
return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.
|
|
364
|
+
return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.jsx)("div", { style: { width: calculatedWidth, maxWidth: width }, children: (0, jsx_runtime_1.jsx)("div", { style: textStyle, children: textContent }) }) }));
|
|
437
365
|
}
|
|
438
|
-
return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.
|
|
366
|
+
return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.jsx)("div", { style: backgroundBoxStyle, children: (0, jsx_runtime_1.jsx)("div", { style: textStyle, children: segment.text }) }) }));
|
|
439
367
|
}
|
|
440
368
|
exports.default = TextElement;
|