@pixldocs/canvas-renderer 0.5.80 → 0.5.82

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.ts CHANGED
@@ -256,7 +256,7 @@ export declare function normalizeFontFamily(fontStack: string): string;
256
256
  * Package version banner. Bump alongside package.json so we can confirm
257
257
  * (via browser:log) that the deployed bundle matches the expected build.
258
258
  */
259
- export declare const PACKAGE_VERSION = "0.5.80";
259
+ export declare const PACKAGE_VERSION = "0.5.81";
260
260
 
261
261
  export declare interface PageSettings {
262
262
  backgroundColor?: string;
@@ -276,6 +276,14 @@ export declare interface PdfAssemblyOptions {
276
276
  fontBaseUrl?: string;
277
277
  /** Convert SVG text to paths before svg2pdf. Default: false — keep live Fabric SVG text for preview/PDF metric parity. */
278
278
  outlineText?: boolean;
279
+ /**
280
+ * Per-template text rendering mode.
281
+ * - 'auto' : outline only complex scripts (Indic/Arabic/CJK/emoji); Latin stays selectable.
282
+ * - 'selectable' : never outline; all text remains real selectable text in the PDF.
283
+ * - 'pixel-perfect' : outline every <text> for 100% font parity.
284
+ * When omitted, falls back to `outlineText` (legacy boolean) for backwards compatibility.
285
+ */
286
+ textMode?: 'auto' | 'selectable' | 'pixel-perfect';
279
287
  }
280
288
 
281
289
  /** Options for PDF rendering */
@@ -294,6 +302,8 @@ export declare interface PdfFromFormOptions {
294
302
  title?: string;
295
303
  /** Base URL for TTF font files for PDF font embedding */
296
304
  fontBaseUrl?: string;
305
+ /** PDF text rendering mode. Default: selectable real text. */
306
+ textMode?: 'auto' | 'selectable' | 'pixel-perfect';
297
307
  }
298
308
 
299
309
  export declare interface PdfRenderResult {
@@ -403,6 +413,7 @@ export declare class PixldocsRenderer {
403
413
  renderPdf(templateConfig: TemplateConfig, options?: {
404
414
  title?: string;
405
415
  fontBaseUrl?: string;
416
+ textMode?: 'auto' | 'selectable' | 'pixel-perfect';
406
417
  }): Promise<PdfRenderResult>;
407
418
  /**
408
419
  * Resolve from V2 sectionState and render a vector PDF.
@@ -669,6 +680,8 @@ export declare interface TemplateConfig {
669
680
  dynamicFields?: DynamicField[];
670
681
  fieldGroups?: any[];
671
682
  themeConfig?: ThemeConfig;
683
+ /** PDF text rendering mode. Default: selectable real text. */
684
+ pdfTextMode?: 'auto' | 'selectable' | 'pixel-perfect';
672
685
  formBindingMode?: string;
673
686
  boundFormDefId?: string;
674
687
  boundFormDefName?: string;
package/dist/index.js CHANGED
@@ -12590,7 +12590,7 @@ function PixldocsPreview(props) {
12590
12590
  !canvasSettled && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
12591
12591
  ] });
12592
12592
  }
12593
- const PACKAGE_VERSION = "0.5.80";
12593
+ const PACKAGE_VERSION = "0.5.81";
12594
12594
  const roundParityValue = (value) => {
12595
12595
  if (typeof value !== "number") return value;
12596
12596
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -12865,7 +12865,7 @@ class PixldocsRenderer {
12865
12865
  async renderPdf(templateConfig, options) {
12866
12866
  const svgs = await this.renderAllPageSvgs(templateConfig);
12867
12867
  const { assemblePdfFromSvgs: assemblePdfFromSvgs2 } = await Promise.resolve().then(() => pdfExport);
12868
- return assemblePdfFromSvgs2(svgs, { title: options == null ? void 0 : options.title, fontBaseUrl: options == null ? void 0 : options.fontBaseUrl });
12868
+ 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" });
12869
12869
  }
12870
12870
  /**
12871
12871
  * Resolve from V2 sectionState and render a vector PDF.
@@ -12890,7 +12890,7 @@ class PixldocsRenderer {
12890
12890
  }
12891
12891
  const svgs = await this.renderAllPageSvgs(configToRender);
12892
12892
  const { assemblePdfFromSvgs: assemblePdfFromSvgs2 } = await Promise.resolve().then(() => pdfExport);
12893
- return assemblePdfFromSvgs2(svgs, { title: title ?? resolved.config.name, fontBaseUrl });
12893
+ return assemblePdfFromSvgs2(svgs, { title: title ?? resolved.config.name, fontBaseUrl, textMode: options.textMode ?? configToRender.pdfTextMode ?? "selectable" });
12894
12894
  }
12895
12895
  async renderById(templateId, formData, options) {
12896
12896
  const resolved = await resolveTemplateData({
@@ -14140,10 +14140,22 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
14140
14140
  async function embedFontsForConfig(pdf, config, fontBaseUrl) {
14141
14141
  const fontKeys = /* @__PURE__ */ new Set();
14142
14142
  const SEP = "";
14143
+ const normalizeWeight = (raw) => {
14144
+ if (raw == null) return 400;
14145
+ if (typeof raw === "number" && Number.isFinite(raw)) return resolveFontWeight(raw);
14146
+ const str = String(raw).trim().toLowerCase();
14147
+ const parsed = Number.parseInt(str, 10);
14148
+ if (Number.isFinite(parsed)) return resolveFontWeight(parsed);
14149
+ if (str === "bold" || str === "bolder") return 700;
14150
+ if (str === "semibold" || str === "demibold") return 600;
14151
+ if (str === "medium") return 500;
14152
+ if (str === "light" || str === "lighter" || str === "thin") return 300;
14153
+ return 400;
14154
+ };
14143
14155
  const walkElements = (elements) => {
14144
14156
  for (const el of elements) {
14145
14157
  if (el.fontFamily) {
14146
- const w = resolveFontWeight(el.fontWeight ?? 400);
14158
+ const w = normalizeWeight(el.fontWeight);
14147
14159
  fontKeys.add(`${el.fontFamily}${SEP}${w}`);
14148
14160
  }
14149
14161
  if (el.styles && typeof el.styles === "object") {
@@ -14153,7 +14165,7 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
14153
14165
  for (const charKey of Object.keys(lineStyles)) {
14154
14166
  const s = lineStyles[charKey];
14155
14167
  if (s == null ? void 0 : s.fontFamily) {
14156
- const w = resolveFontWeight(s.fontWeight ?? 400);
14168
+ const w = normalizeWeight(s.fontWeight);
14157
14169
  fontKeys.add(`${s.fontFamily}${SEP}${w}`);
14158
14170
  }
14159
14171
  }
@@ -15435,7 +15447,9 @@ async function convertTextDecorationsToLines(svg) {
15435
15447
  const fontFamily = tspan.getAttribute("data-source-font-family") || textEl.getAttribute("data-source-font-family") || resolveInheritedSvgValue(tspan, "font-family") || "sans-serif";
15436
15448
  const fontWeight = tspan.getAttribute("data-source-font-weight") || textEl.getAttribute("data-source-font-weight") || resolveInheritedSvgValue(tspan, "font-weight") || "normal";
15437
15449
  const fontStyle = tspan.getAttribute("data-source-font-style") || textEl.getAttribute("data-source-font-style") || resolveInheritedSvgValue(tspan, "font-style") || "normal";
15438
- const fill = tspan.getAttribute("fill") || textEl.getAttribute("fill") || "#000000";
15450
+ const resolvedFill = tspan.getAttribute("fill") || getInlineStyleValue(tspan, "fill") || resolveInheritedSvgValue(tspan, "fill") || textEl.getAttribute("fill") || getInlineStyleValue(textEl, "fill") || "#000000";
15451
+ const fill = resolvedFill;
15452
+ const fillOpacity = tspan.getAttribute("fill-opacity") || getInlineStyleValue(tspan, "fill-opacity") || resolveInheritedSvgValue(tspan, "fill-opacity") || textEl.getAttribute("fill-opacity") || getInlineStyleValue(textEl, "fill-opacity") || null;
15439
15453
  let textWidth = content.length * fontSize * 0.6;
15440
15454
  if (ctx) {
15441
15455
  ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily.replace(/'/g, "")}`;
@@ -15486,6 +15500,7 @@ async function convertTextDecorationsToLines(svg) {
15486
15500
  line.setAttribute("stroke-width", String(thickness));
15487
15501
  line.setAttribute("stroke-linecap", "butt");
15488
15502
  line.setAttribute("fill", "none");
15503
+ if (fillOpacity) line.setAttribute("stroke-opacity", fillOpacity);
15489
15504
  if (textEl.parentElement) {
15490
15505
  textEl.parentElement.insertBefore(line, textEl.nextSibling);
15491
15506
  }
@@ -15785,7 +15800,9 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
15785
15800
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
15786
15801
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
15787
15802
  const shouldStripBg = stripPageBackground ?? hasGradient;
15788
- const shouldOutlineText = options.outlineText !== false;
15803
+ const textMode = options.textMode ?? (options.outlineText ? "pixel-perfect" : "selectable");
15804
+ const shouldOutlineText = textMode === "pixel-perfect" || textMode === "auto";
15805
+ const outlineSubMode = textMode === "auto" ? "complex-only" : "all";
15789
15806
  let pageSvg = page.svg;
15790
15807
  try {
15791
15808
  pageSvg = await convertSvgTextDecorationsToLinesString(pageSvg);
@@ -15797,8 +15814,8 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
15797
15814
  }
15798
15815
  if (shouldOutlineText) {
15799
15816
  try {
15800
- const { convertAllTextToPath } = await import("./svgTextToPath-BP0Kppla.js");
15801
- pageSvg = await convertAllTextToPath(pageSvg, fontBaseUrl);
15817
+ const { convertAllTextToPath } = await import("./svgTextToPath-B5sT7sre.js");
15818
+ pageSvg = await convertAllTextToPath(pageSvg, fontBaseUrl, { mode: outlineSubMode });
15802
15819
  try {
15803
15820
  dumpSvgTextDiagnostics(pageSvg, i, PARITY_TAG, "STAGE-1b-after-text-to-path-raw");
15804
15821
  } catch {