ugcinc-render 1.5.7 → 1.5.9

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
@@ -290,6 +290,10 @@ interface ImageEditorElement {
290
290
  paddingRight?: number;
291
291
  paddingBottom?: number;
292
292
  paddingLeft?: number;
293
+ multiLinePaddingTop?: number;
294
+ multiLinePaddingRight?: number;
295
+ multiLinePaddingBottom?: number;
296
+ multiLinePaddingLeft?: number;
293
297
  /** When true, box shrinks to fit content (width becomes max width) */
294
298
  autoWidth?: boolean;
295
299
  /** Which side the box anchors to when autoWidth is true */
@@ -618,6 +622,14 @@ interface TextSegment extends VisualSegment {
618
622
  paddingBottom?: number;
619
623
  /** Left padding in pixels (overrides uniform padding) */
620
624
  paddingLeft?: number;
625
+ /** Top padding for multi-line text (2+ lines) */
626
+ multiLinePaddingTop?: number;
627
+ /** Right padding for multi-line text (2+ lines) */
628
+ multiLinePaddingRight?: number;
629
+ /** Bottom padding for multi-line text (2+ lines) */
630
+ multiLinePaddingBottom?: number;
631
+ /** Left padding for multi-line text (2+ lines) */
632
+ multiLinePaddingLeft?: number;
621
633
  /** Font family (default: 'arial') */
622
634
  fontType?: FontType;
623
635
  /** Font size in pixels (default: 40) */
package/dist/index.d.ts CHANGED
@@ -290,6 +290,10 @@ interface ImageEditorElement {
290
290
  paddingRight?: number;
291
291
  paddingBottom?: number;
292
292
  paddingLeft?: number;
293
+ multiLinePaddingTop?: number;
294
+ multiLinePaddingRight?: number;
295
+ multiLinePaddingBottom?: number;
296
+ multiLinePaddingLeft?: number;
293
297
  /** When true, box shrinks to fit content (width becomes max width) */
294
298
  autoWidth?: boolean;
295
299
  /** Which side the box anchors to when autoWidth is true */
@@ -618,6 +622,14 @@ interface TextSegment extends VisualSegment {
618
622
  paddingBottom?: number;
619
623
  /** Left padding in pixels (overrides uniform padding) */
620
624
  paddingLeft?: number;
625
+ /** Top padding for multi-line text (2+ lines) */
626
+ multiLinePaddingTop?: number;
627
+ /** Right padding for multi-line text (2+ lines) */
628
+ multiLinePaddingRight?: number;
629
+ /** Bottom padding for multi-line text (2+ lines) */
630
+ multiLinePaddingBottom?: number;
631
+ /** Left padding for multi-line text (2+ lines) */
632
+ multiLinePaddingLeft?: number;
621
633
  /** Font family (default: 'arial') */
622
634
  fontType?: FontType;
623
635
  /** Font size in pixels (default: 40) */
package/dist/index.js CHANGED
@@ -335,7 +335,7 @@ function calculateAutoWidthAndLines({
335
335
  `;
336
336
  document.body.appendChild(measureSpan);
337
337
  measureSpan.textContent = "M";
338
- const charWidth = measureSpan.offsetWidth;
338
+ const charWidth = measureSpan.getBoundingClientRect().width;
339
339
  const words = text.split(/(\s+)/);
340
340
  const lines = [];
341
341
  let currentLine = "";
@@ -357,19 +357,20 @@ function calculateAutoWidthAndLines({
357
357
  }
358
358
  const testLine = currentLine + word;
359
359
  measureSpan.textContent = testLine;
360
- const testWidth = measureSpan.offsetWidth;
361
- if (testWidth > availableWidth && currentLine.trim()) {
360
+ const testWidth = measureSpan.getBoundingClientRect().width;
361
+ const WRAP_TOLERANCE = 0.5;
362
+ if (testWidth > availableWidth + WRAP_TOLERANCE && currentLine.trim()) {
362
363
  lines.push(currentLine.trimEnd());
363
364
  currentLine = word;
364
365
  measureSpan.textContent = word;
365
- const wordWidth = measureSpan.offsetWidth;
366
+ const wordWidth = measureSpan.getBoundingClientRect().width;
366
367
  if (wordWidth > availableWidth) {
367
368
  let remainingWord = word;
368
369
  while (remainingWord) {
369
370
  let breakPoint = 1;
370
371
  for (let j = 1; j <= remainingWord.length; j++) {
371
372
  measureSpan.textContent = remainingWord.substring(0, j);
372
- if (measureSpan.offsetWidth > availableWidth) {
373
+ if (measureSpan.getBoundingClientRect().width > availableWidth) {
373
374
  breakPoint = Math.max(1, j - 1);
374
375
  break;
375
376
  }
@@ -397,7 +398,7 @@ function calculateAutoWidthAndLines({
397
398
  let widestLineWidth = 0;
398
399
  for (const line of lines) {
399
400
  measureSpan.textContent = line;
400
- widestLineWidth = Math.max(widestLineWidth, measureSpan.offsetWidth);
401
+ widestLineWidth = Math.max(widestLineWidth, measureSpan.getBoundingClientRect().width);
401
402
  }
402
403
  document.body.removeChild(measureSpan);
403
404
  const calculatedWidth = Math.min(widestLineWidth + paddingLeft + paddingRight, maxWidth);
@@ -415,10 +416,15 @@ function TextElement({ segment, scale = 1 }) {
415
416
  const strokeColor = segment.strokeColor;
416
417
  const strokeWidth = (segment.strokeWidth ?? TEXT_DEFAULTS.strokeWidth) * scale;
417
418
  const uniformPadding = (segment.padding ?? TEXT_DEFAULTS.padding) * scale;
418
- const paddingTop = segment.paddingTop !== void 0 ? segment.paddingTop * scale : uniformPadding;
419
- const paddingRight = segment.paddingRight !== void 0 ? segment.paddingRight * scale : uniformPadding;
420
- const paddingBottom = segment.paddingBottom !== void 0 ? segment.paddingBottom * scale : uniformPadding;
421
- const paddingLeft = segment.paddingLeft !== void 0 ? segment.paddingLeft * scale : uniformPadding;
419
+ const basePaddingTop = segment.paddingTop !== void 0 ? segment.paddingTop * scale : uniformPadding;
420
+ const basePaddingRight = segment.paddingRight !== void 0 ? segment.paddingRight * scale : uniformPadding;
421
+ const basePaddingBottom = segment.paddingBottom !== void 0 ? segment.paddingBottom * scale : uniformPadding;
422
+ const basePaddingLeft = segment.paddingLeft !== void 0 ? segment.paddingLeft * scale : uniformPadding;
423
+ const multiLinePaddingTop = segment.multiLinePaddingTop !== void 0 ? segment.multiLinePaddingTop * scale : void 0;
424
+ const multiLinePaddingRight = segment.multiLinePaddingRight !== void 0 ? segment.multiLinePaddingRight * scale : void 0;
425
+ const multiLinePaddingBottom = segment.multiLinePaddingBottom !== void 0 ? segment.multiLinePaddingBottom * scale : void 0;
426
+ const multiLinePaddingLeft = segment.multiLinePaddingLeft !== void 0 ? segment.multiLinePaddingLeft * scale : void 0;
427
+ const hasMultiLinePadding = multiLinePaddingTop !== void 0 || multiLinePaddingRight !== void 0 || multiLinePaddingBottom !== void 0 || multiLinePaddingLeft !== void 0;
422
428
  const x = segment.xOffset * scale;
423
429
  const y = segment.yOffset * scale;
424
430
  const width = segment.width * scale;
@@ -430,22 +436,93 @@ function TextElement({ segment, scale = 1 }) {
430
436
  const backgroundOpacity = segment.backgroundOpacity ?? TEXT_DEFAULTS.backgroundOpacity;
431
437
  const backgroundBorderRadius = segment.backgroundBorderRadius;
432
438
  const fontFamily = getFontFamily(fontType);
433
- const autoWidthResult = (0, import_react.useMemo)(() => {
434
- if (!autoWidth) return null;
435
- return calculateAutoWidthAndLines({
439
+ const { autoWidthResult, finalPaddingTop, finalPaddingRight, finalPaddingBottom, finalPaddingLeft, isMultiLine } = (0, import_react.useMemo)(() => {
440
+ let effectivePaddingTop = basePaddingTop;
441
+ let effectivePaddingRight = basePaddingRight;
442
+ let effectivePaddingBottom = basePaddingBottom;
443
+ let effectivePaddingLeft = basePaddingLeft;
444
+ if (!autoWidth && !hasMultiLinePadding) {
445
+ return {
446
+ autoWidthResult: null,
447
+ finalPaddingTop: effectivePaddingTop,
448
+ finalPaddingRight: effectivePaddingRight,
449
+ finalPaddingBottom: effectivePaddingBottom,
450
+ finalPaddingLeft: effectivePaddingLeft,
451
+ isMultiLine: false
452
+ };
453
+ }
454
+ const firstPassResult = calculateAutoWidthAndLines({
436
455
  text: segment.text,
437
456
  maxWidth: width,
438
- paddingLeft,
439
- paddingRight,
457
+ paddingLeft: basePaddingLeft,
458
+ paddingRight: basePaddingRight,
440
459
  fontSize,
441
460
  fontWeight,
442
461
  fontFamily,
443
462
  letterSpacing,
444
463
  lineHeight
445
464
  });
446
- }, [autoWidth, segment.text, width, paddingLeft, paddingRight, fontSize, fontWeight, fontFamily, letterSpacing, lineHeight]);
465
+ const multiLine = firstPassResult.lines.length >= 2;
466
+ if (multiLine && hasMultiLinePadding) {
467
+ effectivePaddingTop = multiLinePaddingTop ?? basePaddingTop;
468
+ effectivePaddingRight = multiLinePaddingRight ?? basePaddingRight;
469
+ effectivePaddingBottom = multiLinePaddingBottom ?? basePaddingBottom;
470
+ effectivePaddingLeft = multiLinePaddingLeft ?? basePaddingLeft;
471
+ if (effectivePaddingLeft !== basePaddingLeft || effectivePaddingRight !== basePaddingRight) {
472
+ const secondPassResult = calculateAutoWidthAndLines({
473
+ text: segment.text,
474
+ maxWidth: width,
475
+ paddingLeft: effectivePaddingLeft,
476
+ paddingRight: effectivePaddingRight,
477
+ fontSize,
478
+ fontWeight,
479
+ fontFamily,
480
+ letterSpacing,
481
+ lineHeight
482
+ });
483
+ return {
484
+ autoWidthResult: autoWidth ? secondPassResult : null,
485
+ finalPaddingTop: effectivePaddingTop,
486
+ finalPaddingRight: effectivePaddingRight,
487
+ finalPaddingBottom: effectivePaddingBottom,
488
+ finalPaddingLeft: effectivePaddingLeft,
489
+ isMultiLine: secondPassResult.lines.length >= 2
490
+ };
491
+ }
492
+ }
493
+ return {
494
+ autoWidthResult: autoWidth ? firstPassResult : null,
495
+ finalPaddingTop: effectivePaddingTop,
496
+ finalPaddingRight: effectivePaddingRight,
497
+ finalPaddingBottom: effectivePaddingBottom,
498
+ finalPaddingLeft: effectivePaddingLeft,
499
+ isMultiLine: multiLine
500
+ };
501
+ }, [
502
+ autoWidth,
503
+ segment.text,
504
+ width,
505
+ basePaddingTop,
506
+ basePaddingRight,
507
+ basePaddingBottom,
508
+ basePaddingLeft,
509
+ multiLinePaddingTop,
510
+ multiLinePaddingRight,
511
+ multiLinePaddingBottom,
512
+ multiLinePaddingLeft,
513
+ hasMultiLinePadding,
514
+ fontSize,
515
+ fontWeight,
516
+ fontFamily,
517
+ letterSpacing,
518
+ lineHeight
519
+ ]);
447
520
  const calculatedWidth = autoWidthResult?.width ?? width;
448
521
  const calculatedLines = autoWidthResult?.lines ?? [segment.text];
522
+ const paddingTop = finalPaddingTop;
523
+ const paddingRight = finalPaddingRight;
524
+ const paddingBottom = finalPaddingBottom;
525
+ const paddingLeft = finalPaddingLeft;
449
526
  const borderRadiusStyle = (0, import_react.useMemo)(() => {
450
527
  if (!backgroundBorderRadius) return void 0;
451
528
  const radii = getBorderRadii(backgroundBorderRadius);
@@ -1112,6 +1189,10 @@ function elementToTextSegment(elem) {
1112
1189
  paddingRight: elem.paddingRight,
1113
1190
  paddingBottom: elem.paddingBottom,
1114
1191
  paddingLeft: elem.paddingLeft,
1192
+ multiLinePaddingTop: elem.multiLinePaddingTop,
1193
+ multiLinePaddingRight: elem.multiLinePaddingRight,
1194
+ multiLinePaddingBottom: elem.multiLinePaddingBottom,
1195
+ multiLinePaddingLeft: elem.multiLinePaddingLeft,
1115
1196
  autoWidth: elem.autoWidth,
1116
1197
  boxAlign: elem.boxAlign
1117
1198
  };
package/dist/index.mjs CHANGED
@@ -251,7 +251,7 @@ function calculateAutoWidthAndLines({
251
251
  `;
252
252
  document.body.appendChild(measureSpan);
253
253
  measureSpan.textContent = "M";
254
- const charWidth = measureSpan.offsetWidth;
254
+ const charWidth = measureSpan.getBoundingClientRect().width;
255
255
  const words = text.split(/(\s+)/);
256
256
  const lines = [];
257
257
  let currentLine = "";
@@ -273,19 +273,20 @@ function calculateAutoWidthAndLines({
273
273
  }
274
274
  const testLine = currentLine + word;
275
275
  measureSpan.textContent = testLine;
276
- const testWidth = measureSpan.offsetWidth;
277
- if (testWidth > availableWidth && currentLine.trim()) {
276
+ const testWidth = measureSpan.getBoundingClientRect().width;
277
+ const WRAP_TOLERANCE = 0.5;
278
+ if (testWidth > availableWidth + WRAP_TOLERANCE && currentLine.trim()) {
278
279
  lines.push(currentLine.trimEnd());
279
280
  currentLine = word;
280
281
  measureSpan.textContent = word;
281
- const wordWidth = measureSpan.offsetWidth;
282
+ const wordWidth = measureSpan.getBoundingClientRect().width;
282
283
  if (wordWidth > availableWidth) {
283
284
  let remainingWord = word;
284
285
  while (remainingWord) {
285
286
  let breakPoint = 1;
286
287
  for (let j = 1; j <= remainingWord.length; j++) {
287
288
  measureSpan.textContent = remainingWord.substring(0, j);
288
- if (measureSpan.offsetWidth > availableWidth) {
289
+ if (measureSpan.getBoundingClientRect().width > availableWidth) {
289
290
  breakPoint = Math.max(1, j - 1);
290
291
  break;
291
292
  }
@@ -313,7 +314,7 @@ function calculateAutoWidthAndLines({
313
314
  let widestLineWidth = 0;
314
315
  for (const line of lines) {
315
316
  measureSpan.textContent = line;
316
- widestLineWidth = Math.max(widestLineWidth, measureSpan.offsetWidth);
317
+ widestLineWidth = Math.max(widestLineWidth, measureSpan.getBoundingClientRect().width);
317
318
  }
318
319
  document.body.removeChild(measureSpan);
319
320
  const calculatedWidth = Math.min(widestLineWidth + paddingLeft + paddingRight, maxWidth);
@@ -331,10 +332,15 @@ function TextElement({ segment, scale = 1 }) {
331
332
  const strokeColor = segment.strokeColor;
332
333
  const strokeWidth = (segment.strokeWidth ?? TEXT_DEFAULTS.strokeWidth) * scale;
333
334
  const uniformPadding = (segment.padding ?? TEXT_DEFAULTS.padding) * scale;
334
- const paddingTop = segment.paddingTop !== void 0 ? segment.paddingTop * scale : uniformPadding;
335
- const paddingRight = segment.paddingRight !== void 0 ? segment.paddingRight * scale : uniformPadding;
336
- const paddingBottom = segment.paddingBottom !== void 0 ? segment.paddingBottom * scale : uniformPadding;
337
- const paddingLeft = segment.paddingLeft !== void 0 ? segment.paddingLeft * scale : uniformPadding;
335
+ const basePaddingTop = segment.paddingTop !== void 0 ? segment.paddingTop * scale : uniformPadding;
336
+ const basePaddingRight = segment.paddingRight !== void 0 ? segment.paddingRight * scale : uniformPadding;
337
+ const basePaddingBottom = segment.paddingBottom !== void 0 ? segment.paddingBottom * scale : uniformPadding;
338
+ const basePaddingLeft = segment.paddingLeft !== void 0 ? segment.paddingLeft * scale : uniformPadding;
339
+ const multiLinePaddingTop = segment.multiLinePaddingTop !== void 0 ? segment.multiLinePaddingTop * scale : void 0;
340
+ const multiLinePaddingRight = segment.multiLinePaddingRight !== void 0 ? segment.multiLinePaddingRight * scale : void 0;
341
+ const multiLinePaddingBottom = segment.multiLinePaddingBottom !== void 0 ? segment.multiLinePaddingBottom * scale : void 0;
342
+ const multiLinePaddingLeft = segment.multiLinePaddingLeft !== void 0 ? segment.multiLinePaddingLeft * scale : void 0;
343
+ const hasMultiLinePadding = multiLinePaddingTop !== void 0 || multiLinePaddingRight !== void 0 || multiLinePaddingBottom !== void 0 || multiLinePaddingLeft !== void 0;
338
344
  const x = segment.xOffset * scale;
339
345
  const y = segment.yOffset * scale;
340
346
  const width = segment.width * scale;
@@ -346,22 +352,93 @@ function TextElement({ segment, scale = 1 }) {
346
352
  const backgroundOpacity = segment.backgroundOpacity ?? TEXT_DEFAULTS.backgroundOpacity;
347
353
  const backgroundBorderRadius = segment.backgroundBorderRadius;
348
354
  const fontFamily = getFontFamily(fontType);
349
- const autoWidthResult = useMemo(() => {
350
- if (!autoWidth) return null;
351
- return calculateAutoWidthAndLines({
355
+ const { autoWidthResult, finalPaddingTop, finalPaddingRight, finalPaddingBottom, finalPaddingLeft, isMultiLine } = useMemo(() => {
356
+ let effectivePaddingTop = basePaddingTop;
357
+ let effectivePaddingRight = basePaddingRight;
358
+ let effectivePaddingBottom = basePaddingBottom;
359
+ let effectivePaddingLeft = basePaddingLeft;
360
+ if (!autoWidth && !hasMultiLinePadding) {
361
+ return {
362
+ autoWidthResult: null,
363
+ finalPaddingTop: effectivePaddingTop,
364
+ finalPaddingRight: effectivePaddingRight,
365
+ finalPaddingBottom: effectivePaddingBottom,
366
+ finalPaddingLeft: effectivePaddingLeft,
367
+ isMultiLine: false
368
+ };
369
+ }
370
+ const firstPassResult = calculateAutoWidthAndLines({
352
371
  text: segment.text,
353
372
  maxWidth: width,
354
- paddingLeft,
355
- paddingRight,
373
+ paddingLeft: basePaddingLeft,
374
+ paddingRight: basePaddingRight,
356
375
  fontSize,
357
376
  fontWeight,
358
377
  fontFamily,
359
378
  letterSpacing,
360
379
  lineHeight
361
380
  });
362
- }, [autoWidth, segment.text, width, paddingLeft, paddingRight, fontSize, fontWeight, fontFamily, letterSpacing, lineHeight]);
381
+ const multiLine = firstPassResult.lines.length >= 2;
382
+ if (multiLine && hasMultiLinePadding) {
383
+ effectivePaddingTop = multiLinePaddingTop ?? basePaddingTop;
384
+ effectivePaddingRight = multiLinePaddingRight ?? basePaddingRight;
385
+ effectivePaddingBottom = multiLinePaddingBottom ?? basePaddingBottom;
386
+ effectivePaddingLeft = multiLinePaddingLeft ?? basePaddingLeft;
387
+ if (effectivePaddingLeft !== basePaddingLeft || effectivePaddingRight !== basePaddingRight) {
388
+ const secondPassResult = calculateAutoWidthAndLines({
389
+ text: segment.text,
390
+ maxWidth: width,
391
+ paddingLeft: effectivePaddingLeft,
392
+ paddingRight: effectivePaddingRight,
393
+ fontSize,
394
+ fontWeight,
395
+ fontFamily,
396
+ letterSpacing,
397
+ lineHeight
398
+ });
399
+ return {
400
+ autoWidthResult: autoWidth ? secondPassResult : null,
401
+ finalPaddingTop: effectivePaddingTop,
402
+ finalPaddingRight: effectivePaddingRight,
403
+ finalPaddingBottom: effectivePaddingBottom,
404
+ finalPaddingLeft: effectivePaddingLeft,
405
+ isMultiLine: secondPassResult.lines.length >= 2
406
+ };
407
+ }
408
+ }
409
+ return {
410
+ autoWidthResult: autoWidth ? firstPassResult : null,
411
+ finalPaddingTop: effectivePaddingTop,
412
+ finalPaddingRight: effectivePaddingRight,
413
+ finalPaddingBottom: effectivePaddingBottom,
414
+ finalPaddingLeft: effectivePaddingLeft,
415
+ isMultiLine: multiLine
416
+ };
417
+ }, [
418
+ autoWidth,
419
+ segment.text,
420
+ width,
421
+ basePaddingTop,
422
+ basePaddingRight,
423
+ basePaddingBottom,
424
+ basePaddingLeft,
425
+ multiLinePaddingTop,
426
+ multiLinePaddingRight,
427
+ multiLinePaddingBottom,
428
+ multiLinePaddingLeft,
429
+ hasMultiLinePadding,
430
+ fontSize,
431
+ fontWeight,
432
+ fontFamily,
433
+ letterSpacing,
434
+ lineHeight
435
+ ]);
363
436
  const calculatedWidth = autoWidthResult?.width ?? width;
364
437
  const calculatedLines = autoWidthResult?.lines ?? [segment.text];
438
+ const paddingTop = finalPaddingTop;
439
+ const paddingRight = finalPaddingRight;
440
+ const paddingBottom = finalPaddingBottom;
441
+ const paddingLeft = finalPaddingLeft;
365
442
  const borderRadiusStyle = useMemo(() => {
366
443
  if (!backgroundBorderRadius) return void 0;
367
444
  const radii = getBorderRadii(backgroundBorderRadius);
@@ -1028,6 +1105,10 @@ function elementToTextSegment(elem) {
1028
1105
  paddingRight: elem.paddingRight,
1029
1106
  paddingBottom: elem.paddingBottom,
1030
1107
  paddingLeft: elem.paddingLeft,
1108
+ multiLinePaddingTop: elem.multiLinePaddingTop,
1109
+ multiLinePaddingRight: elem.multiLinePaddingRight,
1110
+ multiLinePaddingBottom: elem.multiLinePaddingBottom,
1111
+ multiLinePaddingLeft: elem.multiLinePaddingLeft,
1031
1112
  autoWidth: elem.autoWidth,
1032
1113
  boxAlign: elem.boxAlign
1033
1114
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugcinc-render",
3
- "version": "1.5.7",
3
+ "version": "1.5.9",
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",