@uurtech/jdf-cli 0.1.21 → 0.1.22

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.js CHANGED
@@ -240,6 +240,58 @@ function shouldUseJdfx(doc) {
240
240
  }
241
241
 
242
242
  // src/commands/import-md.ts
243
+ function parseInline(text) {
244
+ const runs = [];
245
+ let i = 0;
246
+ let buf = "";
247
+ const flush = (extra) => {
248
+ if (buf) {
249
+ runs.push({ text: buf, ...extra });
250
+ buf = "";
251
+ }
252
+ };
253
+ while (i < text.length) {
254
+ const rest = text.slice(i);
255
+ const link = rest.match(/^\[([^\]]+)\]\(([^)\s]+)[^)]*\)/);
256
+ if (link) {
257
+ flush();
258
+ runs.push({ text: link[1], link: link[2] });
259
+ i += link[0].length;
260
+ continue;
261
+ }
262
+ const code = rest.match(/^`([^`]+)`/);
263
+ if (code) {
264
+ flush();
265
+ runs.push({ text: code[1], fontFamily: "JetBrains Mono", color: "#be185d" });
266
+ i += code[0].length;
267
+ continue;
268
+ }
269
+ const bold = rest.match(/^(\*\*|__)([^]+?)\1/);
270
+ if (bold) {
271
+ flush();
272
+ runs.push(...parseInline(bold[2]).map((r) => ({ ...r, bold: true })));
273
+ i += bold[0].length;
274
+ continue;
275
+ }
276
+ const ital = rest.match(/^(\*|_)([^]+?)\1/);
277
+ if (ital) {
278
+ flush();
279
+ runs.push(...parseInline(ital[2]).map((r) => ({ ...r, italic: true })));
280
+ i += ital[0].length;
281
+ continue;
282
+ }
283
+ buf += text[i];
284
+ i++;
285
+ }
286
+ flush();
287
+ return runs.length ? runs : [{ text }];
288
+ }
289
+ function hasFormatting(runs) {
290
+ return runs.some((r) => r.bold || r.italic || r.link || r.fontFamily || r.color);
291
+ }
292
+ function stripInline(text) {
293
+ return parseInline(text).map((r) => r.text).join("");
294
+ }
243
295
  async function importMarkdown(inputPath, outputPath) {
244
296
  const input = path2.resolve(inputPath);
245
297
  console.log(`Importing: ${input}`);
@@ -330,11 +382,28 @@ function convertMarkdownToJdf(md, title, baseDir = process.cwd()) {
330
382
  i++;
331
383
  continue;
332
384
  }
333
- const imageMatch = line.match(/^\s*!\[([^\]]*)\]\(\s*([^)\s]+)[^)]*\)\s*$/);
334
- if (imageMatch) {
335
- const alt = imageMatch[1];
336
- const src = resolveImageSrc(imageMatch[2], baseDir);
337
- const height2 = 60;
385
+ if (line.match(/^\s*([-*_])(\s*\1){2,}\s*$/)) {
386
+ if (y + 6 > maxY) {
387
+ pages.push({ id: `page-${pageNum}`, elements });
388
+ elements = [];
389
+ y = 5;
390
+ pageNum++;
391
+ }
392
+ elements.push({ type: "shape", shape: "rect", position: { x: 0, y: y + 2 }, width: contentWidth, height: 0.3, fill: "#cbd5e1" });
393
+ y += 6;
394
+ i++;
395
+ continue;
396
+ }
397
+ if (line.includes("|") && i + 1 < lines.length && lines[i + 1].match(/^\s*\|?[\s:|-]+\|?\s*$/) && lines[i + 1].includes("-")) {
398
+ const splitRow = (r) => r.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|").map((c) => stripInline(c.trim()));
399
+ const headers = splitRow(line);
400
+ i += 2;
401
+ const rows = [];
402
+ while (i < lines.length && lines[i].includes("|") && lines[i].trim() !== "") {
403
+ rows.push(splitRow(lines[i]));
404
+ i++;
405
+ }
406
+ const height2 = (rows.length + 1) * 7 + 3;
338
407
  if (y + height2 > maxY) {
339
408
  pages.push({ id: `page-${pageNum}`, elements });
340
409
  elements = [];
@@ -342,25 +411,27 @@ function convertMarkdownToJdf(md, title, baseDir = process.cwd()) {
342
411
  pageNum++;
343
412
  }
344
413
  elements.push({
345
- type: "image",
346
- src,
347
- alt,
414
+ type: "table",
415
+ headers,
416
+ rows,
348
417
  position: { x: 0, y },
349
418
  width: contentWidth,
350
- height: height2,
351
- fit: "contain"
419
+ borders: true,
420
+ headerStyle: { backgroundColor: "#f1f5f9", fontWeight: "bold", fontSize: 10, color: "#0f172a" },
421
+ rowStyle: { color: "#334155", fontSize: 10 },
422
+ style: { fontFamily: "Inter", fontSize: 10 }
352
423
  });
353
- y += height2 + 4;
354
- i++;
424
+ y += height2;
355
425
  continue;
356
426
  }
357
- if (line.match(/^\s*[-*+]\s/)) {
358
- const items = [];
359
- while (i < lines.length && lines[i].match(/^\s*[-*+]\s/)) {
360
- items.push({ content: lines[i].replace(/^\s*[-*+]\s+/, "").trim() });
427
+ if (line.match(/^\s*>\s?/)) {
428
+ const quoteLines = [];
429
+ while (i < lines.length && lines[i].match(/^\s*>\s?/)) {
430
+ quoteLines.push(lines[i].replace(/^\s*>\s?/, ""));
361
431
  i++;
362
432
  }
363
- const height2 = items.length * 5 + 3;
433
+ const quote = quoteLines.join(" ").trim();
434
+ const height2 = Math.ceil(quote.length / 70) * 4.5 + 6;
364
435
  if (y + height2 > maxY) {
365
436
  pages.push({ id: `page-${pageNum}`, elements });
366
437
  elements = [];
@@ -368,23 +439,66 @@ function convertMarkdownToJdf(md, title, baseDir = process.cwd()) {
368
439
  pageNum++;
369
440
  }
370
441
  elements.push({
371
- type: "list",
372
- listType: "unordered",
442
+ type: "text",
443
+ content: quote,
373
444
  position: { x: 0, y },
374
445
  width: contentWidth,
375
- style: { fontFamily: "Inter", fontSize: 10, lineHeight: 1.6, color: "#334155" },
376
- items
446
+ style: { fontFamily: "Inter", fontSize: 10, lineHeight: 1.6, color: "#475569", fontStyle: "italic", backgroundColor: "#f8fafc", padding: 10, borderRadius: 6, marginTop: 4 }
377
447
  });
378
448
  y += height2;
379
449
  continue;
380
450
  }
381
- if (line.match(/^\s*\d+\.\s/)) {
382
- const items = [];
383
- while (i < lines.length && lines[i].match(/^\s*\d+\.\s/)) {
384
- items.push({ content: lines[i].replace(/^\s*\d+\.\s+/, "").trim() });
451
+ const imageMatch = line.match(/^\s*!\[([^\]]*)\]\(\s*([^)\s]+)[^)]*\)\s*$/);
452
+ if (imageMatch) {
453
+ const alt = imageMatch[1];
454
+ const src = resolveImageSrc(imageMatch[2], baseDir);
455
+ const height2 = 60;
456
+ if (y + height2 > maxY) {
457
+ pages.push({ id: `page-${pageNum}`, elements });
458
+ elements = [];
459
+ y = 5;
460
+ pageNum++;
461
+ }
462
+ elements.push({
463
+ type: "image",
464
+ src,
465
+ alt,
466
+ position: { x: 0, y },
467
+ width: contentWidth,
468
+ height: height2,
469
+ fit: "contain"
470
+ });
471
+ y += height2 + 4;
472
+ i++;
473
+ continue;
474
+ }
475
+ const listMatch = line.match(/^(\s*)([-*+]|\d+\.)\s/);
476
+ if (listMatch) {
477
+ const raw = [];
478
+ while (i < lines.length) {
479
+ const m = lines[i].match(/^(\s*)([-*+]|\d+\.)\s+(.*)$/);
480
+ if (!m) break;
481
+ raw.push({ indent: m[1].length, ordered: /\d/.test(m[2]), content: m[3].trim() });
385
482
  i++;
386
483
  }
387
- const height2 = items.length * 5 + 3;
484
+ const rootOrdered = raw[0].ordered;
485
+ const root = [];
486
+ const stack = [{ indent: raw[0].indent, items: root }];
487
+ for (const r of raw) {
488
+ while (stack.length > 1 && r.indent < stack[stack.length - 1].indent) stack.pop();
489
+ if (r.indent > stack[stack.length - 1].indent) {
490
+ const parentItems = stack[stack.length - 1].items;
491
+ const parent = parentItems[parentItems.length - 1];
492
+ if (parent) {
493
+ parent.children = parent.children || [];
494
+ parent.listType = r.ordered ? "ordered" : "unordered";
495
+ stack.push({ indent: r.indent, items: parent.children });
496
+ }
497
+ }
498
+ stack[stack.length - 1].items.push({ content: stripInline(r.content) });
499
+ }
500
+ const count = raw.length;
501
+ const height2 = count * 5 + 3;
388
502
  if (y + height2 > maxY) {
389
503
  pages.push({ id: `page-${pageNum}`, elements });
390
504
  elements = [];
@@ -393,11 +507,11 @@ function convertMarkdownToJdf(md, title, baseDir = process.cwd()) {
393
507
  }
394
508
  elements.push({
395
509
  type: "list",
396
- listType: "ordered",
510
+ listType: rootOrdered ? "ordered" : "unordered",
397
511
  position: { x: 0, y },
398
512
  width: contentWidth,
399
513
  style: { fontFamily: "Inter", fontSize: 10, lineHeight: 1.6, color: "#334155" },
400
- items
514
+ items: root
401
515
  });
402
516
  y += height2;
403
517
  continue;
@@ -442,10 +556,15 @@ function convertMarkdownToJdf(md, title, baseDir = process.cwd()) {
442
556
  continue;
443
557
  }
444
558
  let para = "";
445
- while (i < lines.length && lines[i].trim() !== "" && !lines[i].match(/^#{1,6}\s/) && !lines[i].match(/^\s*[-*+]\s/) && !lines[i].match(/^\s*\d+\.\s/) && !lines[i].startsWith("```")) {
559
+ while (i < lines.length && lines[i].trim() !== "" && !lines[i].match(/^#{1,6}\s/) && !lines[i].match(/^\s*([-*+]|\d+\.)\s/) && !lines[i].startsWith("```") && !lines[i].match(/^\s*>\s?/) && !lines[i].match(/^\s*([-*_])(\s*\1){2,}\s*$/) && !lines[i].includes("|")) {
446
560
  para += (para ? " " : "") + lines[i].trim();
447
561
  i++;
448
562
  }
563
+ if (!para) {
564
+ para = lines[i].trim();
565
+ i++;
566
+ if (!para) continue;
567
+ }
449
568
  const paraLines = Math.ceil(para.length / 80);
450
569
  const height = paraLines * 4.5 + 3;
451
570
  if (y + height > maxY) {
@@ -454,13 +573,24 @@ function convertMarkdownToJdf(md, title, baseDir = process.cwd()) {
454
573
  y = 5;
455
574
  pageNum++;
456
575
  }
457
- elements.push({
458
- type: "text",
459
- content: para,
460
- position: { x: 0, y },
461
- width: contentWidth,
462
- style: { fontFamily: "Inter", fontSize: 10, lineHeight: 1.6, color: "#334155" }
463
- });
576
+ const runs = parseInline(para);
577
+ if (hasFormatting(runs)) {
578
+ elements.push({
579
+ type: "richtext",
580
+ runs,
581
+ position: { x: 0, y },
582
+ width: contentWidth,
583
+ style: { fontFamily: "Inter", fontSize: 10, lineHeight: 1.6, color: "#334155" }
584
+ });
585
+ } else {
586
+ elements.push({
587
+ type: "text",
588
+ content: para,
589
+ position: { x: 0, y },
590
+ width: contentWidth,
591
+ style: { fontFamily: "Inter", fontSize: 10, lineHeight: 1.6, color: "#334155" }
592
+ });
593
+ }
464
594
  y += height;
465
595
  }
466
596
  if (elements.length > 0) {
@@ -582,17 +712,35 @@ async function walkOps(page, OPS, viewport) {
582
712
  const y1Local = (va.y - minY) * PT_TO_MM;
583
713
  const x2Local = (vb.x - minX) * PT_TO_MM;
584
714
  const y2Local = (vb.y - minY) * PT_TO_MM;
585
- shapes.push({
586
- kind: "path",
587
- x: minX * PT_TO_MM,
588
- y: minY * PT_TO_MM,
589
- width: Math.max(0.05, (maxX - minX) * PT_TO_MM),
590
- height: Math.max(0.05, (maxY - minY) * PT_TO_MM),
591
- stroke: isStroke ? gs.stroke : void 0,
592
- strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM : void 0,
593
- opacity: gs.strokeAlpha,
594
- path: `M ${x1Local.toFixed(2)} ${y1Local.toFixed(2)} L ${x2Local.toFixed(2)} ${y2Local.toFixed(2)}`
595
- });
715
+ const wLocal = Math.max(0.05, (maxX - minX) * PT_TO_MM);
716
+ const hLocal = Math.max(0.05, (maxY - minY) * PT_TO_MM);
717
+ const dx = Math.abs(va.x - vb.x);
718
+ const dy = Math.abs(va.y - vb.y);
719
+ const axisAligned = dx < 0.5 || dy < 0.5;
720
+ if (axisAligned) {
721
+ shapes.push({
722
+ kind: "line",
723
+ x: minX * PT_TO_MM,
724
+ y: minY * PT_TO_MM,
725
+ width: wLocal,
726
+ height: hLocal,
727
+ stroke: isStroke ? gs.stroke : void 0,
728
+ strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM : void 0,
729
+ opacity: gs.strokeAlpha
730
+ });
731
+ } else {
732
+ shapes.push({
733
+ kind: "path",
734
+ x: minX * PT_TO_MM,
735
+ y: minY * PT_TO_MM,
736
+ width: wLocal,
737
+ height: hLocal,
738
+ stroke: isStroke ? gs.stroke : void 0,
739
+ strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM : void 0,
740
+ opacity: gs.strokeAlpha,
741
+ path: `M ${x1Local.toFixed(2)} ${y1Local.toFixed(2)} L ${x2Local.toFixed(2)} ${y2Local.toFixed(2)}`
742
+ });
743
+ }
596
744
  } else if (pathSegments.length > 0) {
597
745
  const vpSegments = pathSegments.map((seg) => {
598
746
  if (seg.type === "Z") return seg;
@@ -1455,12 +1603,13 @@ The CLI exists for two workflows:
1455
1603
 
1456
1604
  Usage:
1457
1605
  jdf validate <file.jdf>
1458
- jdf import <file.{pdf,json,md}> [-o output.{jdf,jdfx}] [--json]
1606
+ jdf convert <file.{pdf,json,md}> [-o output.{jdf,jdfx}] [--json]
1459
1607
  jdf --help
1460
1608
 
1461
1609
  Commands:
1462
1610
  validate Validate a .jdf / .jdfx file against the JDF schema
1463
- import Convert a PDF, JSON, or Markdown file into JDF
1611
+ convert Convert a PDF, JSON, or Markdown file into JDF
1612
+ (alias: import)
1464
1613
 
1465
1614
  Flags:
1466
1615
  -o, --output <path> Explicit output path (extension picks .jdf vs .jdfx)
@@ -1471,10 +1620,10 @@ Flags:
1471
1620
 
1472
1621
  Examples:
1473
1622
  jdf validate spec/examples/hello-world.jdf
1474
- jdf import paper.pdf # PDF \u2192 JDF (or .jdfx for images)
1475
- jdf import contract.pdf --json | jq . # PDF \u2192 pure JSON, pipe-friendly
1476
- jdf import response.json -o response.jdf # LLM JSON output \u2192 validated JDF
1477
- jdf import README.md
1623
+ jdf convert paper.pdf # PDF \u2192 JDF (or .jdfx for images)
1624
+ jdf convert contract.pdf --json | jq . # PDF \u2192 pure JSON, pipe-friendly
1625
+ jdf convert response.json -o response.jdf # LLM JSON output \u2192 validated JDF
1626
+ jdf convert README.md
1478
1627
  `;
1479
1628
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["help", "h", "json", "verbose", "skip-validate"]);
1480
1629
  function parseArgs(argv) {
@@ -1540,10 +1689,13 @@ async function main() {
1540
1689
  const ok = await validate(positional[0]);
1541
1690
  process.exit(ok ? 0 : 1);
1542
1691
  }
1692
+ // `convert` is the headline verb; `import` stays as a back-compat alias
1693
+ // so existing scripts and docs keep working.
1694
+ case "convert":
1543
1695
  case "import": {
1544
1696
  const input = positional[0];
1545
1697
  if (!input) {
1546
- console.error("Usage: jdf import <file.{pdf,json,md}> [-o output.jdf] [--json]");
1698
+ console.error("Usage: jdf convert <file.{pdf,json,md}> [-o output.jdf] [--json]");
1547
1699
  process.exit(1);
1548
1700
  }
1549
1701
  const output = typeof flags.output === "string" ? flags.output : void 0;
@@ -42,7 +42,8 @@
42
42
  "pageSize": { "$ref": "#/definitions/PageSize" },
43
43
  "pageOrientation": { "$ref": "#/definitions/PageOrientation" },
44
44
  "margins": { "$ref": "#/definitions/Margins" },
45
- "unit": { "$ref": "#/definitions/Unit" }
45
+ "unit": { "$ref": "#/definitions/Unit" },
46
+ "flow": { "type": "boolean", "description": "Document-level default: lay elements out top-to-bottom and auto-paginate overflow in PDF export. Per-page `flow` overrides." }
46
47
  }
47
48
  },
48
49
  "PageSize": {
@@ -335,7 +336,7 @@
335
336
  "headers": { "type": "array", "items": { "type": "string" } },
336
337
  "rows": {
337
338
  "type": "array",
338
- "items": { "type": "array", "items": { "oneOf": [{ "type": "string" }, { "type": "object", "required": ["content"], "properties": { "content": { "type": "string" }, "style": { "$ref": "#/definitions/StyleRef" }, "colspan": { "type": "integer" }, "rowspan": { "type": "integer" } } }] } }
339
+ "items": { "type": "array", "items": { "oneOf": [{ "type": "string" }, { "type": "object", "required": ["content"], "properties": { "content": { "type": "string" }, "style": { "$ref": "#/definitions/StyleRef" }, "align": { "type": "string", "enum": ["left","center","right","justify"] }, "colspan": { "type": "integer" }, "rowspan": { "type": "integer" } } }] } }
339
340
  },
340
341
  "position": { "$ref": "#/definitions/Position" }, "width": { "type": "number" },
341
342
  "headerStyle": { "$ref": "#/definitions/StyleRef" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uurtech/jdf-cli",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Command-line tool for the JDF (JSON Document Format) — validate and convert documents.",
5
5
  "license": "MIT",
6
6
  "author": "Ugur Kazdal",