@uurtech/jdf 0.2.4 → 0.2.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uurtech/jdf",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Render JDF (JSON Document Format) documents in the browser. Drop-in replacement for embedding PDFs — point at a .jdf file and it appears on the page, fully styled, searchable, with a clickable TOC.",
5
5
  "license": "MIT",
6
6
  "author": "Ugur Kazdal <ugur@uurtech.com>",
package/src/jdfjs.css CHANGED
@@ -275,6 +275,22 @@ textarea.jdfjs-form-control {
275
275
  line-height: 1.4;
276
276
  }
277
277
 
278
+
279
+ /* Compact fields: PDF-form widgets shorter than 9 mm (see compactField). */
280
+ .jdfjs-form-field.jdfjs-form-compact { gap: 0; justify-content: center; }
281
+ .jdfjs-form-compact .jdfjs-form-label { display: none; }
282
+ .jdfjs-form-compact .jdfjs-form-control {
283
+ height: 100%;
284
+ min-height: 0;
285
+ padding: 0 2px;
286
+ border-radius: 1px;
287
+ border-color: rgba(100, 116, 139, 0.35);
288
+ background: transparent;
289
+ line-height: 1;
290
+ }
291
+ .jdfjs-form-compact textarea.jdfjs-form-control { min-height: 0; resize: none; line-height: 1.1; }
292
+ .jdfjs-form-compact.jdfjs-form-checkbox input[type="checkbox"] { width: 100%; height: 100%; margin: 0; }
293
+
278
294
  .jdfjs-form-checkbox {
279
295
  flex-direction: row;
280
296
  align-items: center;
@@ -455,6 +455,7 @@ function buildItems(parent: HTMLElement, items: ListItem[], def: "ordered" | "un
455
455
  }
456
456
 
457
457
  // ── shape ───────────────────────────────────────────────────────────────────
458
+ let gradientSeq = 0;
458
459
  function renderShape(el: ShapeElement): HTMLElement {
459
460
  const wrap = document.createElement("div");
460
461
  wrap.className = "jdfjs-shape";
@@ -534,7 +535,35 @@ function renderShape(el: ShapeElement): HTMLElement {
534
535
  }
535
536
  }
536
537
  if (shapeNode) {
537
- shapeNode.setAttribute("fill", fill);
538
+ // Gradient fill via SVG defs (same geometry the reader uses).
539
+ let paint = fill;
540
+ const g = el.gradient;
541
+ if (g && Array.isArray(g.stops) && g.stops.length >= 2) {
542
+ const id = `jdfjs-grad-${++gradientSeq}`;
543
+ const defs = document.createElementNS(NS_SVG, "defs");
544
+ let gradNode: SVGElement;
545
+ if (g.type === "radial") {
546
+ gradNode = document.createElementNS(NS_SVG, "radialGradient");
547
+ gradNode.setAttribute("cx", "50%"); gradNode.setAttribute("cy", "50%"); gradNode.setAttribute("r", "50%");
548
+ } else {
549
+ gradNode = document.createElementNS(NS_SVG, "linearGradient");
550
+ const a = ((g.angle ?? 180) * Math.PI) / 180;
551
+ const dx = Math.sin(a) / 2, dy = -Math.cos(a) / 2;
552
+ gradNode.setAttribute("x1", String(0.5 - dx)); gradNode.setAttribute("y1", String(0.5 - dy));
553
+ gradNode.setAttribute("x2", String(0.5 + dx)); gradNode.setAttribute("y2", String(0.5 + dy));
554
+ }
555
+ gradNode.setAttribute("id", id);
556
+ for (const st of g.stops) {
557
+ const stop = document.createElementNS(NS_SVG, "stop");
558
+ stop.setAttribute("offset", `${Math.max(0, Math.min(1, st.offset)) * 100}%`);
559
+ stop.setAttribute("stop-color", st.color);
560
+ gradNode.appendChild(stop);
561
+ }
562
+ defs.appendChild(gradNode);
563
+ svg.appendChild(defs);
564
+ paint = `url(#${id})`;
565
+ }
566
+ shapeNode.setAttribute("fill", paint);
538
567
  shapeNode.setAttribute("stroke", strokeColor);
539
568
  if (strokeWidth) shapeNode.setAttribute("stroke-width", String(strokeWidth));
540
569
  svg.appendChild(shapeNode);
@@ -681,10 +710,23 @@ function commitFormChange(ctx: RenderContext, field: string, value: unknown) {
681
710
  ctx.onFormChange(ctx.path, field, value);
682
711
  }
683
712
 
713
+ /**
714
+ * A field whose box is shorter than 9 mm (an AcroForm widget imported from a
715
+ * PDF form) renders compact: no label, no padding, font sized to the box, so
716
+ * the control sits inside the form's printed cell instead of covering the
717
+ * labels around it. Same rule in the reader's FormElement.
718
+ */
719
+ function compactField(wrap: HTMLElement, el: { height?: number }) {
720
+ if (el.height == null || el.height >= 9) return;
721
+ wrap.classList.add("jdfjs-form-compact");
722
+ wrap.style.fontSize = `${Math.max(7, Math.min(12, Math.round(el.height * 3.7795 * 0.62)))}px`;
723
+ }
724
+
684
725
  function renderFormInput(el: FormInputElement, ctx: RenderContext): HTMLElement {
685
726
  const wrap = document.createElement("div");
686
727
  wrap.className = "jdfjs-form-field jdfjs-form-input";
687
728
  applyStyle(wrap, resolveStyle(el.style, ctx.styles));
729
+ compactField(wrap, el);
688
730
  const lab = makeLabel(el.label);
689
731
  if (lab) wrap.appendChild(lab);
690
732
  const input = document.createElement("input");
@@ -706,6 +748,7 @@ function renderFormTextarea(el: FormTextareaElement, ctx: RenderContext): HTMLEl
706
748
  const wrap = document.createElement("div");
707
749
  wrap.className = "jdfjs-form-field jdfjs-form-textarea";
708
750
  applyStyle(wrap, resolveStyle(el.style, ctx.styles));
751
+ compactField(wrap, el);
709
752
  const lab = makeLabel(el.label);
710
753
  if (lab) wrap.appendChild(lab);
711
754
  const ta = document.createElement("textarea");
@@ -726,6 +769,7 @@ function renderFormCheckbox(el: FormCheckboxElement, ctx: RenderContext): HTMLEl
726
769
  const wrap = document.createElement("label");
727
770
  wrap.className = "jdfjs-form-field jdfjs-form-checkbox";
728
771
  applyStyle(wrap, resolveStyle(el.style, ctx.styles));
772
+ compactField(wrap, el);
729
773
  const cb = document.createElement("input");
730
774
  cb.type = "checkbox";
731
775
  cb.name = el.name;
@@ -747,6 +791,7 @@ function renderFormSelect(el: FormSelectElement, ctx: RenderContext): HTMLElemen
747
791
  const wrap = document.createElement("div");
748
792
  wrap.className = "jdfjs-form-field jdfjs-form-select";
749
793
  applyStyle(wrap, resolveStyle(el.style, ctx.styles));
794
+ compactField(wrap, el);
750
795
  const lab = makeLabel(el.label);
751
796
  if (lab) wrap.appendChild(lab);
752
797
  const sel = document.createElement("select");
package/src/viewer.ts CHANGED
@@ -222,6 +222,7 @@ export class JDFViewer {
222
222
  }
223
223
 
224
224
  private mount() {
225
+ ensureMetricFonts(this.doc);
225
226
  this.container.innerHTML = "";
226
227
  this.container.classList.add("jdfjs");
227
228
  this.applyDarkMode();
@@ -756,3 +757,24 @@ function resolveContainer(c: HTMLElement | string): HTMLElement {
756
757
  function escapeHtml(s: string): string {
757
758
  return s.replace(/[&<>"']/g, (m) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[m]!));
758
759
  }
760
+
761
+ /**
762
+ * The PDF importer maps fonts to metric-compatible open families (Carlito =
763
+ * Calibri, Arimo = Arial, Tinos = Times New Roman, Cousine = Courier New,
764
+ * Gelasio = Georgia, Caladea = Cambria) so converted lines keep their width.
765
+ * Load them from Google Fonts once, only when a document uses them; offline
766
+ * embeds fall through to the stack's next family.
767
+ */
768
+ const METRIC_FAMILIES = ["Carlito", "Arimo", "Tinos", "Cousine", "Gelasio", "Caladea", "IBM Plex Sans", "IBM Plex Serif"];
769
+ function ensureMetricFonts(doc: JdfDocument) {
770
+ if (typeof document === "undefined" || document.getElementById("jdfjs-metric-fonts")) return;
771
+ let json: string;
772
+ try { json = JSON.stringify(doc.styles ?? {}) + JSON.stringify(doc.pages ?? []); } catch { return; }
773
+ const used = METRIC_FAMILIES.filter((f) => json.includes(f));
774
+ if (!used.length) return;
775
+ const link = document.createElement("link");
776
+ link.id = "jdfjs-metric-fonts";
777
+ link.rel = "stylesheet";
778
+ link.href = "https://fonts.googleapis.com/css2?" + used.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:ital,wght@0,400;0,700;1,400;1,700`).join("&") + "&display=swap";
779
+ document.head.appendChild(link);
780
+ }