@pixldocs/canvas-renderer 0.5.81 → 0.5.83

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.cjs CHANGED
@@ -494,29 +494,39 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
494
494
  const mode = group.layoutMode ?? "absolute";
495
495
  if (!isStackLayoutMode(mode)) return /* @__PURE__ */ new Map();
496
496
  const gap = group.stackSpacing ?? 8;
497
+ const padTop = group.paddingTop ?? 0;
498
+ const padLeft = group.paddingLeft ?? 0;
497
499
  const kids = group.children ?? [];
498
500
  const out = /* @__PURE__ */ new Map();
499
501
  if (isVerticalStackLayoutMode(mode)) {
500
- let prevBottom = 0;
502
+ let prevBottom = padTop;
503
+ let firstSeen = false;
501
504
  for (let i = 0; i < kids.length; i++) {
502
505
  const child = kids[i];
503
506
  const storedTop = getNodeTop(child);
504
507
  const storedLeft = getNodeLeft(child);
505
- const effectiveTop = i === 0 ? storedTop : prevBottom + gap + storedTop;
506
- out.set(child.id, { top: effectiveTop, left: storedLeft });
508
+ const mTop = child.marginTop ?? 0;
509
+ const mLeft = child.marginLeft ?? 0;
510
+ const effectiveTop = !firstSeen ? padTop + storedTop + mTop : prevBottom + gap + storedTop + mTop;
511
+ firstSeen = true;
512
+ out.set(child.id, { top: effectiveTop, left: padLeft + storedLeft + mLeft });
507
513
  const h = getNodeBounds(child, pageChildren).height;
508
- prevBottom = effectiveTop + h;
514
+ prevBottom = effectiveTop + h + (child.marginBottom ?? 0);
509
515
  }
510
516
  } else {
511
- let prevRight = 0;
517
+ let prevRight = padLeft;
518
+ let firstSeen = false;
512
519
  for (let i = 0; i < kids.length; i++) {
513
520
  const child = kids[i];
514
521
  const storedLeft = getNodeLeft(child);
515
522
  const storedTop = getNodeTop(child);
516
- const effectiveLeft = i === 0 ? storedLeft : prevRight + gap + storedLeft;
517
- out.set(child.id, { top: storedTop, left: effectiveLeft });
523
+ const mTop = child.marginTop ?? 0;
524
+ const mLeft = child.marginLeft ?? 0;
525
+ const effectiveLeft = !firstSeen ? padLeft + storedLeft + mLeft : prevRight + gap + storedLeft + mLeft;
526
+ firstSeen = true;
527
+ out.set(child.id, { top: padTop + storedTop + mTop, left: effectiveLeft });
518
528
  const w = getNodeBounds(child, pageChildren).width;
519
- prevRight = effectiveLeft + w;
529
+ prevRight = effectiveLeft + w + (child.marginRight ?? 0);
520
530
  }
521
531
  }
522
532
  return out;
@@ -541,6 +551,14 @@ function groupBoundsFromChildren(group, pageChildren, options) {
541
551
  maxX = Math.max(maxX, cl + b.width);
542
552
  maxY = Math.max(maxY, ct + b.height);
543
553
  }
554
+ if (isStack) {
555
+ const padRight = group.paddingRight ?? 0;
556
+ const padBottom = group.paddingBottom ?? 0;
557
+ return {
558
+ width: Math.max(1, maxX + padRight),
559
+ height: Math.max(1, maxY + padBottom)
560
+ };
561
+ }
544
562
  return {
545
563
  width: Math.max(1, maxX - minX),
546
564
  height: Math.max(1, maxY - minY)
@@ -603,7 +621,7 @@ function absoluteToStorePosition(absoluteLeft, absoluteTop, nodeId, pageChildren
603
621
  const prev = kids[idx - 1];
604
622
  const prevResolved = resolved.get(prev.id);
605
623
  const prevHeight = getNodeBounds(prev, pageChildren).height;
606
- const prevBottom = prevResolved ? prevResolved.top + prevHeight : 0;
624
+ const prevBottom = prevResolved ? prevResolved.top + prevHeight + (prev.marginBottom ?? 0) : 0;
607
625
  const storeTop = inParentTop - prevBottom - gap;
608
626
  return { left: storeLeft, top: Math.max(0, storeTop) };
609
627
  }
@@ -625,7 +643,7 @@ function reflowStackGroup(group, pageChildren, spacing) {
625
643
  const b = getNodeBounds(child, pageChildren);
626
644
  const currentTop = getNodeTop(child);
627
645
  const effectiveTop = i === 0 ? currentTop : prevBottom + gap;
628
- prevBottom = effectiveTop + b.height;
646
+ prevBottom = effectiveTop + b.height + (child.marginBottom ?? 0);
629
647
  if (i > 0) updates.set(child.id, { top: 0, left: firstLeft });
630
648
  }
631
649
  } else {
@@ -636,7 +654,7 @@ function reflowStackGroup(group, pageChildren, spacing) {
636
654
  const b = getNodeBounds(child, pageChildren);
637
655
  const currentLeft = getNodeLeft(child);
638
656
  const effectiveLeft = i === 0 ? currentLeft : prevRight + gap;
639
- prevRight = effectiveLeft + b.width;
657
+ prevRight = effectiveLeft + b.width + (child.marginRight ?? 0);
640
658
  if (i > 0) updates.set(child.id, { left: 0, top: firstTop });
641
659
  }
642
660
  }
@@ -788,7 +806,8 @@ const cloneCanvas = (canvas) => ({
788
806
  themeConfig: canvas.themeConfig ? {
789
807
  properties: canvas.themeConfig.properties.map((p) => ({ ...p })),
790
808
  variants: canvas.themeConfig.variants.map((v) => ({ ...v, values: { ...v.values } }))
791
- } : void 0
809
+ } : void 0,
810
+ pdfTextMode: canvas.pdfTextMode
792
811
  });
793
812
  const sameIdSet = (a, b) => {
794
813
  if (a.length !== b.length) return false;
@@ -1268,6 +1287,12 @@ const useEditorStore = zustand.create((set, get) => ({
1268
1287
  const committed = commitFromState(state, nextCanvas);
1269
1288
  return { canvas: nextCanvas, ...committed };
1270
1289
  }),
1290
+ setPdfTextMode: (mode) => set((state) => {
1291
+ if (state.canvas.pdfTextMode === mode) return {};
1292
+ const nextCanvas = { ...state.canvas, pdfTextMode: mode };
1293
+ const committed = commitFromState(state, nextCanvas);
1294
+ return { canvas: nextCanvas, ...committed };
1295
+ }),
1271
1296
  setZoom: (zoom) => set((state) => ({
1272
1297
  canvas: { ...state.canvas, zoom: Math.max(0.1, Math.min(10, zoom)) }
1273
1298
  })),
@@ -2455,7 +2480,8 @@ const useEditorStore = zustand.create((set, get) => ({
2455
2480
  formBindingMode: config.formBindingMode,
2456
2481
  boundFormDefId: config.boundFormDefId,
2457
2482
  boundFormDefName: config.boundFormDefName,
2458
- themeConfig: config.themeConfig ?? void 0
2483
+ themeConfig: config.themeConfig ?? void 0,
2484
+ pdfTextMode: config.pdfTextMode ?? "selectable"
2459
2485
  };
2460
2486
  const committed = commitFromState(state, nextCanvas);
2461
2487
  const out = {
@@ -4922,7 +4948,9 @@ function extractTextBgConfig(element) {
4922
4948
  if (!Number.isFinite(n)) return void 0;
4923
4949
  return Math.max(0, Math.min(1, n));
4924
4950
  })(),
4925
- shadowAffectsBg: element.textShadowAffectsBg !== false
4951
+ shadowAffectsBg: element.textShadowAffectsBg !== false,
4952
+ shadowAffectsText: element.textShadowAffectsText !== false,
4953
+ fitToText: element.textBgFitToText === true
4926
4954
  };
4927
4955
  }
4928
4956
  function hasTextBackground(cfg) {
@@ -4978,10 +5006,6 @@ function applyTextBackground(obj, cfg) {
4978
5006
  const pR = Math.max(0, Number(bg.padRight ?? 0));
4979
5007
  const pB = Math.max(0, Number(bg.padBottom ?? 0));
4980
5008
  const pL = Math.max(0, Number(bg.padLeft ?? 0));
4981
- const x = -w / 2 - pL;
4982
- const y = -h / 2 - pT;
4983
- const bgW = w + pL + pR;
4984
- const bgH = h + pT + pB;
4985
5009
  ctx.save();
4986
5010
  const suppressShadowOnBg = bg.shadowAffectsBg === false;
4987
5011
  if (suppressShadowOnBg) {
@@ -4990,24 +5014,38 @@ function applyTextBackground(obj, cfg) {
4990
5014
  ctx.shadowOffsetX = 0;
4991
5015
  ctx.shadowOffsetY = 0;
4992
5016
  }
4993
- buildRoundedRectPath2D(
4994
- ctx,
4995
- x,
4996
- y,
4997
- bgW,
4998
- bgH,
4999
- bg.rxTL ?? 0,
5000
- bg.rxTR ?? 0,
5001
- bg.rxBR ?? 0,
5002
- bg.rxBL ?? 0
5003
- );
5004
5017
  const op = typeof bg.opacity === "number" ? Math.max(0, Math.min(1, bg.opacity)) : 1;
5005
5018
  if (op < 1) ctx.globalAlpha = (ctx.globalAlpha ?? 1) * op;
5006
5019
  ctx.fillStyle = bg.color;
5007
- ctx.fill();
5020
+ const rects = computeBgRects(this, w, h, pT, pR, pB, pL, !!bg.fitToText);
5021
+ for (const r of rects) {
5022
+ buildRoundedRectPath2D(
5023
+ ctx,
5024
+ r.x,
5025
+ r.y,
5026
+ r.w,
5027
+ r.h,
5028
+ bg.rxTL ?? 0,
5029
+ bg.rxTR ?? 0,
5030
+ bg.rxBR ?? 0,
5031
+ bg.rxBL ?? 0
5032
+ );
5033
+ ctx.fill();
5034
+ }
5008
5035
  ctx.restore();
5009
5036
  }
5010
- originalRender(ctx);
5037
+ const suppressShadowOnText = bg && bg.shadowAffectsText === false;
5038
+ if (suppressShadowOnText) {
5039
+ ctx.save();
5040
+ ctx.shadowColor = "transparent";
5041
+ ctx.shadowBlur = 0;
5042
+ ctx.shadowOffsetX = 0;
5043
+ ctx.shadowOffsetY = 0;
5044
+ originalRender(ctx);
5045
+ ctx.restore();
5046
+ } else {
5047
+ originalRender(ctx);
5048
+ }
5011
5049
  };
5012
5050
  const originalToObject = obj.toObject.bind(obj);
5013
5051
  obj.toObject = function(propertiesToInclude) {
@@ -5033,20 +5071,18 @@ function applyTextBackground(obj, cfg) {
5033
5071
  const pR = Math.max(0, Number((bg == null ? void 0 : bg.padRight) ?? 0));
5034
5072
  const pB = Math.max(0, Number((bg == null ? void 0 : bg.padBottom) ?? 0));
5035
5073
  const pL = Math.max(0, Number((bg == null ? void 0 : bg.padLeft) ?? 0));
5036
- const x = -w / 2 - pL;
5037
- const y = -h / 2 - pT;
5038
- const bgW = w + pL + pR;
5039
- const bgH = h + pT + pB;
5040
- const bgD = buildRoundedRectPathD(
5041
- x,
5042
- y,
5043
- bgW,
5044
- bgH,
5074
+ const fit = !!(bg == null ? void 0 : bg.fitToText);
5075
+ const rects = computeBgRects(this, w, h, pT, pR, pB, pL, fit);
5076
+ const bgD = rects.map((r) => buildRoundedRectPathD(
5077
+ r.x,
5078
+ r.y,
5079
+ r.w,
5080
+ r.h,
5045
5081
  (bg == null ? void 0 : bg.rxTL) ?? 0,
5046
5082
  (bg == null ? void 0 : bg.rxTR) ?? 0,
5047
5083
  (bg == null ? void 0 : bg.rxBR) ?? 0,
5048
5084
  (bg == null ? void 0 : bg.rxBL) ?? 0
5049
- );
5085
+ )).join(" ");
5050
5086
  const bgFill = (bg == null ? void 0 : bg.color) || "";
5051
5087
  const bgOpacity = typeof (bg == null ? void 0 : bg.opacity) === "number" ? Math.max(0, Math.min(1, bg.opacity)) : 1;
5052
5088
  const bgOpacityAttr = bgOpacity < 1 ? ` fill-opacity="${bgOpacity}"` : "";
@@ -5071,9 +5107,11 @@ function applyTextBackground(obj, cfg) {
5071
5107
  const shadowBgPath = `<path d="${bgD}" fill="${escapeXmlAttr(shadowColor)}" />`;
5072
5108
  bgShadowMarker = wrapShadow(shadowBgPath);
5073
5109
  }
5074
- const inner = extractGInnerMarkup(svg);
5075
- const recoloredText = recolorSvgFills(inner, shadowColor);
5076
- if (recoloredText) textShadowMarker = wrapShadow(recoloredText);
5110
+ if ((bg == null ? void 0 : bg.shadowAffectsText) !== false) {
5111
+ const inner = extractGInnerMarkup(svg);
5112
+ const recoloredText = recolorSvgFills(inner, shadowColor);
5113
+ if (recoloredText) textShadowMarker = wrapShadow(recoloredText);
5114
+ }
5077
5115
  }
5078
5116
  const openTagMatch = svg.match(/^\s*<g\b[^>]*>/);
5079
5117
  const inserted = bgShadowMarker + bgPath + textShadowMarker;
@@ -5124,6 +5162,74 @@ function buildRoundedRectPathD(x, y, w, h, rTL, rTR, rBR, rBL) {
5124
5162
  parts.push("Z");
5125
5163
  return parts.join(" ");
5126
5164
  }
5165
+ function computeBgRects(obj, w, h, pT, pR, pB, pL, fit) {
5166
+ var _a;
5167
+ if (!fit) {
5168
+ return [{
5169
+ x: -w / 2 - pL,
5170
+ y: -h / 2 - pT,
5171
+ w: w + pL + pR,
5172
+ h: h + pT + pB
5173
+ }];
5174
+ }
5175
+ const lines = (obj == null ? void 0 : obj._textLines) ?? [];
5176
+ if (!lines || lines.length === 0) {
5177
+ return [{
5178
+ x: -w / 2 - pL,
5179
+ y: -h / 2 - pT,
5180
+ w: w + pL + pR,
5181
+ h: h + pT + pB
5182
+ }];
5183
+ }
5184
+ const rects = [];
5185
+ const halfW = w / 2;
5186
+ const halfH = h / 2;
5187
+ const lineHeightRatio = Math.max(0.01, Number((obj == null ? void 0 : obj.lineHeight) ?? 1) || 1);
5188
+ let cursorY = -halfH;
5189
+ for (let i = 0; i < lines.length; i++) {
5190
+ let lineW = 0;
5191
+ let lineLeft = 0;
5192
+ let lineH = 0;
5193
+ try {
5194
+ lineW = obj.getLineWidth(i) || 0;
5195
+ } catch {
5196
+ lineW = 0;
5197
+ }
5198
+ try {
5199
+ lineLeft = ((_a = obj._getLineLeftOffset) == null ? void 0 : _a.call(obj, i)) ?? 0;
5200
+ } catch {
5201
+ lineLeft = 0;
5202
+ }
5203
+ try {
5204
+ lineH = obj.getHeightOfLine(i) || 0;
5205
+ } catch {
5206
+ lineH = 0;
5207
+ }
5208
+ const rawSlotH = i === lines.length - 1 ? lineH / lineHeightRatio : lineH;
5209
+ const usedH = cursorY + halfH;
5210
+ const slotH = Math.max(0, Math.min(rawSlotH, h - usedH));
5211
+ if (lineW <= 0 || slotH <= 0) {
5212
+ cursorY += slotH;
5213
+ continue;
5214
+ }
5215
+ rects.push({
5216
+ x: -halfW + lineLeft - pL,
5217
+ y: cursorY - pT,
5218
+ w: lineW + pL + pR,
5219
+ h: slotH + pT + pB
5220
+ });
5221
+ cursorY += slotH;
5222
+ }
5223
+ if (rects.length === 0) {
5224
+ return [{
5225
+ x: -w / 2 - pL,
5226
+ y: -h / 2 - pT,
5227
+ w: w + pL + pR,
5228
+ h: h + pT + pB
5229
+ }];
5230
+ }
5231
+ return rects;
5232
+ }
5127
5233
  function escapeXmlAttr(s) {
5128
5234
  return String(s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5129
5235
  }
@@ -8143,10 +8249,14 @@ const PageCanvas = react.forwardRef(
8143
8249
  tr: element.textBgRxTR ?? 0,
8144
8250
  br: element.textBgRxBR ?? 0,
8145
8251
  bl: element.textBgRxBL ?? 0,
8252
+ ft: element.textBgFitToText === true,
8253
+ bo: element.textBgOpacity ?? 1,
8146
8254
  sc: element.textShadowColor ?? null,
8147
8255
  sb: element.textShadowBlur ?? 0,
8148
8256
  sx: element.textShadowOffsetX ?? 0,
8149
- sy: element.textShadowOffsetY ?? 0
8257
+ sy: element.textShadowOffsetY ?? 0,
8258
+ st: element.textShadowAffectsText !== false,
8259
+ sa: element.textShadowAffectsBg !== false
8150
8260
  }) !== (existingObj.__lastTextBgShadowJson ?? "") || // CRITICAL: Detect gradient fill/stroke changes — serialise to JSON for deep comparison
8151
8261
  JSON.stringify(element.fillGradient || null) !== (existingObj.__lastFillGradientJson ?? "null") || JSON.stringify(element.strokeGradient || null) !== (existingObj.__lastStrokeGradientJson ?? "null");
8152
8262
  const forceApplyFromPanel = syncTriggeredByPanelRef.current;
@@ -9060,10 +9170,14 @@ const PageCanvas = react.forwardRef(
9060
9170
  tr: element.textBgRxTR ?? 0,
9061
9171
  br: element.textBgRxBR ?? 0,
9062
9172
  bl: element.textBgRxBL ?? 0,
9173
+ ft: element.textBgFitToText === true,
9174
+ bo: element.textBgOpacity ?? 1,
9063
9175
  sc: element.textShadowColor ?? null,
9064
9176
  sb: element.textShadowBlur ?? 0,
9065
9177
  sx: element.textShadowOffsetX ?? 0,
9066
- sy: element.textShadowOffsetY ?? 0
9178
+ sy: element.textShadowOffsetY ?? 0,
9179
+ st: element.textShadowAffectsText !== false,
9180
+ sa: element.textShadowAffectsBg !== false
9067
9181
  });
9068
9182
  obj.dirty = true;
9069
9183
  } catch (err) {
@@ -12884,7 +12998,7 @@ class PixldocsRenderer {
12884
12998
  async renderPdf(templateConfig, options) {
12885
12999
  const svgs = await this.renderAllPageSvgs(templateConfig);
12886
13000
  const { assemblePdfFromSvgs: assemblePdfFromSvgs2 } = await Promise.resolve().then(() => pdfExport);
12887
- return assemblePdfFromSvgs2(svgs, { title: options == null ? void 0 : options.title, fontBaseUrl: options == null ? void 0 : options.fontBaseUrl });
13001
+ return assemblePdfFromSvgs2(svgs, { title: options == null ? void 0 : options.title, fontBaseUrl: options == null ? void 0 : options.fontBaseUrl, textMode: (options == null ? void 0 : options.textMode) ?? templateConfig.pdfTextMode ?? "selectable" });
12888
13002
  }
12889
13003
  /**
12890
13004
  * Resolve from V2 sectionState and render a vector PDF.
@@ -12909,7 +13023,7 @@ class PixldocsRenderer {
12909
13023
  }
12910
13024
  const svgs = await this.renderAllPageSvgs(configToRender);
12911
13025
  const { assemblePdfFromSvgs: assemblePdfFromSvgs2 } = await Promise.resolve().then(() => pdfExport);
12912
- return assemblePdfFromSvgs2(svgs, { title: title ?? resolved.config.name, fontBaseUrl });
13026
+ return assemblePdfFromSvgs2(svgs, { title: title ?? resolved.config.name, fontBaseUrl, textMode: options.textMode ?? configToRender.pdfTextMode ?? "selectable" });
12913
13027
  }
12914
13028
  async renderById(templateId, formData, options) {
12915
13029
  const resolved = await resolveTemplateData({
@@ -14115,6 +14229,7 @@ async function fetchTTFAsBase64(url) {
14115
14229
  if (!res.ok) return null;
14116
14230
  const buf = await res.arrayBuffer();
14117
14231
  const bytes = new Uint8Array(buf);
14232
+ if (!isJsPdfEmbeddableTrueType(bytes)) return null;
14118
14233
  let binary = "";
14119
14234
  for (let i = 0; i < bytes.length; i++) {
14120
14235
  binary += String.fromCharCode(bytes[i]);
@@ -14126,6 +14241,33 @@ async function fetchTTFAsBase64(url) {
14126
14241
  return null;
14127
14242
  }
14128
14243
  }
14244
+ function isJsPdfEmbeddableTrueType(bytes) {
14245
+ if (bytes.length < 12) return false;
14246
+ const signature = String.fromCharCode(bytes[0], bytes[1], bytes[2], bytes[3]);
14247
+ if (signature !== "\0\0\0" && signature !== "true") return false;
14248
+ const u16 = (offset) => bytes[offset] << 8 | bytes[offset + 1];
14249
+ const u32 = (offset) => (bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
14250
+ const tableCount = u16(4);
14251
+ for (let i = 0; i < tableCount; i++) {
14252
+ const recordOffset = 12 + i * 16;
14253
+ if (recordOffset + 16 > bytes.length) return false;
14254
+ const tag = String.fromCharCode(bytes[recordOffset], bytes[recordOffset + 1], bytes[recordOffset + 2], bytes[recordOffset + 3]);
14255
+ if (tag !== "cmap") continue;
14256
+ const cmapOffset = u32(recordOffset + 8);
14257
+ const cmapLength = u32(recordOffset + 12);
14258
+ if (cmapOffset + Math.min(cmapLength, 4) > bytes.length) return false;
14259
+ const subtables = u16(cmapOffset + 2);
14260
+ for (let j = 0; j < subtables; j++) {
14261
+ const encOffset = cmapOffset + 4 + j * 8;
14262
+ if (encOffset + 8 > bytes.length) return false;
14263
+ const platform = u16(encOffset);
14264
+ const encoding = u16(encOffset + 2);
14265
+ if (platform === 0 || platform === 3 && (encoding === 1 || encoding === 10)) return true;
14266
+ }
14267
+ return false;
14268
+ }
14269
+ return false;
14270
+ }
14129
14271
  async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
14130
14272
  const fontFiles = FONT_FILES[fontName];
14131
14273
  if (!fontFiles) return false;
@@ -14159,10 +14301,22 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
14159
14301
  async function embedFontsForConfig(pdf, config, fontBaseUrl) {
14160
14302
  const fontKeys = /* @__PURE__ */ new Set();
14161
14303
  const SEP = "";
14304
+ const normalizeWeight = (raw) => {
14305
+ if (raw == null) return 400;
14306
+ if (typeof raw === "number" && Number.isFinite(raw)) return resolveFontWeight(raw);
14307
+ const str = String(raw).trim().toLowerCase();
14308
+ const parsed = Number.parseInt(str, 10);
14309
+ if (Number.isFinite(parsed)) return resolveFontWeight(parsed);
14310
+ if (str === "bold" || str === "bolder") return 700;
14311
+ if (str === "semibold" || str === "demibold") return 600;
14312
+ if (str === "medium") return 500;
14313
+ if (str === "light" || str === "lighter" || str === "thin") return 300;
14314
+ return 400;
14315
+ };
14162
14316
  const walkElements = (elements) => {
14163
14317
  for (const el of elements) {
14164
14318
  if (el.fontFamily) {
14165
- const w = resolveFontWeight(el.fontWeight ?? 400);
14319
+ const w = normalizeWeight(el.fontWeight);
14166
14320
  fontKeys.add(`${el.fontFamily}${SEP}${w}`);
14167
14321
  }
14168
14322
  if (el.styles && typeof el.styles === "object") {
@@ -14172,7 +14326,7 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
14172
14326
  for (const charKey of Object.keys(lineStyles)) {
14173
14327
  const s = lineStyles[charKey];
14174
14328
  if (s == null ? void 0 : s.fontFamily) {
14175
- const w = resolveFontWeight(s.fontWeight ?? 400);
14329
+ const w = normalizeWeight(s.fontWeight);
14176
14330
  fontKeys.add(`${s.fontFamily}${SEP}${w}`);
14177
14331
  }
14178
14332
  }
@@ -15807,7 +15961,9 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
15807
15961
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
15808
15962
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
15809
15963
  const shouldStripBg = stripPageBackground ?? hasGradient;
15810
- const shouldOutlineText = options.outlineText !== false;
15964
+ const textMode = options.textMode ?? (options.outlineText ? "pixel-perfect" : "selectable");
15965
+ const shouldOutlineText = textMode === "pixel-perfect" || textMode === "auto";
15966
+ const outlineSubMode = textMode === "auto" ? "complex-only" : "all";
15811
15967
  let pageSvg = page.svg;
15812
15968
  try {
15813
15969
  pageSvg = await convertSvgTextDecorationsToLinesString(pageSvg);
@@ -15819,8 +15975,8 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
15819
15975
  }
15820
15976
  if (shouldOutlineText) {
15821
15977
  try {
15822
- const { convertAllTextToPath } = await Promise.resolve().then(() => require("./svgTextToPath-BTHnqJpM.cjs"));
15823
- pageSvg = await convertAllTextToPath(pageSvg, fontBaseUrl);
15978
+ const { convertAllTextToPath } = await Promise.resolve().then(() => require("./svgTextToPath-CBcIgtk1.cjs"));
15979
+ pageSvg = await convertAllTextToPath(pageSvg, fontBaseUrl, { mode: outlineSubMode });
15824
15980
  try {
15825
15981
  dumpSvgTextDiagnostics(pageSvg, i, PARITY_TAG, "STAGE-1b-after-text-to-path-raw");
15826
15982
  } catch {