@quillmark/quillify 0.4.0 → 0.5.0

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/assemble.js CHANGED
@@ -1,11 +1,12 @@
1
1
  /** Every quill the library builds is a first version. */
2
2
  export const QUILL_VERSION = '0.1.0';
3
3
  /**
4
- * A string as a YAML double-quoted scalar. JSON writes it, from a well formed
5
- * string, and the characters the engine's reader refuses and JSON leaves raw
6
- * (U+007F to U+009F, U+FFFE and U+FFFF) are written as `\u` escapes.
4
+ * JSON as YAML the engine reads: the characters its reader refuses and JSON
5
+ * leaves raw (U+007F to U+009F, U+FFFE and U+FFFF) are written as `\u` escapes.
7
6
  */
8
- const q = (s) => JSON.stringify(s.toWellFormed()).replace(/[\u007f-\u009f\ufffe\uffff]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
7
+ const escaped = (json) => json.replace(/[\u007f-\u009f\ufffe\uffff]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
8
+ /** A string as a YAML double-quoted scalar, which JSON writes from a well formed string. */
9
+ const q = (s) => escaped(JSON.stringify(s.toWellFormed()));
9
10
  /**
10
11
  * A `default:` says nobody need answer the field. A required field declares
11
12
  * none, a boolean apart: it defaults to false either way.
@@ -14,14 +15,13 @@ function fieldYaml(field) {
14
15
  const lines = [` ${q(field.name)}:`];
15
16
  if (field.title !== undefined)
16
17
  lines.push(` title: ${q(field.title)}`);
17
- const ui = [];
18
18
  switch (field.type) {
19
19
  case 'string':
20
- lines.push(' type: string');
20
+ lines.push(' type: text');
21
21
  if (!field.required)
22
22
  lines.push(' default: ""');
23
23
  if (field.multiline)
24
- ui.push(' multiline: true');
24
+ lines.push(' multiline: true');
25
25
  break;
26
26
  case 'boolean':
27
27
  lines.push(' type: boolean', ' default: false');
@@ -32,7 +32,7 @@ function fieldYaml(field) {
32
32
  // It refuses ";", ">" and "|" in a value too, so a dropdown with one is a text box.
33
33
  const values = (field.options ?? []).filter((option) => option !== '');
34
34
  if (values.length === 0 || values.some((value) => /[;>|]/.test(value)))
35
- lines.push(' type: string');
35
+ lines.push(' type: text');
36
36
  else {
37
37
  lines.push(' type: enum', ' values:');
38
38
  for (const value of values)
@@ -43,14 +43,11 @@ function fieldYaml(field) {
43
43
  break;
44
44
  }
45
45
  case 'array':
46
- lines.push(' type: array', ' items:', ' type: string');
46
+ lines.push(' type: array', ' items:', ' type: text');
47
47
  if (!field.required)
48
48
  lines.push(' default: []');
49
- ui.push(' multiline: true');
50
49
  break;
51
50
  }
52
- if (ui.length > 0)
53
- lines.push(' ui:', ...ui);
54
51
  lines.push(` description: ${q(field.description)}`);
55
52
  return lines;
56
53
  }
@@ -131,7 +128,7 @@ export function assembleQuill(form, inventory, formPdf) {
131
128
  const tree = new Map([
132
129
  ['Quill.yaml', encoder.encode(quillYaml(form, bound))],
133
130
  ['form.pdf', formPdf],
134
- ['form.json', encoder.encode(JSON.stringify(formJson, null, 2) + '\n')]
131
+ ['form.yaml', encoder.encode(escaped(JSON.stringify(formJson, null, 2)) + '\n')]
135
132
  ]);
136
133
  const example = exampleMarkdown(form, bound);
137
134
  if (example)
@@ -8,7 +8,7 @@ export interface XfaWidget {
8
8
  name: string;
9
9
  kind: XfaKind;
10
10
  page: number;
11
- /** Points, top-left origin, page-relative: the rect `form.json` wants. */
11
+ /** Points, top-left origin, page-relative: the rect `form.yaml` wants. */
12
12
  x: number;
13
13
  y: number;
14
14
  w: number;
package/dist/xfa.js CHANGED
@@ -54,9 +54,11 @@ export async function xfaToAcroForm(bytes, signal) {
54
54
  construct: 'image',
55
55
  detail: 'pixels'
56
56
  });
57
- for (const widget of harvestWidgets(laid, number))
57
+ const harvested = harvestWidgets(laid, number);
58
+ for (const widget of harvested)
58
59
  widgets.push(widget);
59
- await paint(out.addPage([list.width, list.height]), number, list, async (name) => {
60
+ const shown = whiten(list, harvested);
61
+ await paint(out.addPage([list.width, list.height]), number, shown, async (name) => {
60
62
  let font = fonts.get(name);
61
63
  if (!font)
62
64
  fonts.set(name, (font = await out.embedFont(name)));
@@ -71,6 +73,28 @@ export async function xfaToAcroForm(bytes, signal) {
71
73
  await opened?.close();
72
74
  }
73
75
  }
76
+ /**
77
+ * The engine fills every field in black, whatever colour the form gives its
78
+ * text. A field whose middle lies on a fill that black reads on at under 4.5:1,
79
+ * WCAG's floor for text, gets a white box drawn just over that fill.
80
+ */
81
+ function whiten(list, widgets) {
82
+ const drawn = [...list.drawn];
83
+ const filled = (item) => 'edges' in item && item.fill !== null;
84
+ for (const { x, y, w, h } of widgets) {
85
+ const [u, v] = [x + w / 2, y + h / 2];
86
+ const at = drawn.findLastIndex((item) => filled(item) && item.x <= u && u <= item.x + item.w && item.y <= v && v <= item.y + item.h);
87
+ const under = drawn[at];
88
+ if (at >= 0 && filled(under) && (luminance(under.fill) + 0.05) / 0.05 < 4.5)
89
+ drawn.splice(at + 1, 0, { x, y, w, h, fill: [1, 1, 1], edges: [null, null, null, null] });
90
+ }
91
+ return { ...list, drawn };
92
+ }
93
+ /** The relative luminance WCAG gives a colour in sRGB. */
94
+ function luminance(colour) {
95
+ const [r, g, b] = colour.map((c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4));
96
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
97
+ }
74
98
  /** PDF's origin is bottom-left; every primitive arrives top-left. */
75
99
  async function paint(page, pageNumber, list, font) {
76
100
  const H = list.height;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/quillify",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Turn a published PDF form (AcroForm or XFA) into a Quillmark quill, in the browser.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@neslinesli93/qpdf-wasm": "0.3.0",
51
- "@quillmark/wasm": "0.125.0",
51
+ "@quillmark/wasm": "0.126.0",
52
52
  "@types/node": "24.19.0",
53
53
  "@vitest/browser-playwright": "5.0.2",
54
54
  "fast-check": "4.10.2",
@@ -63,7 +63,7 @@
63
63
  "pdfjs-dist": "6.4.299"
64
64
  },
65
65
  "peerDependencies": {
66
- "@quillmark/wasm": ">=0.124.0"
66
+ "@quillmark/wasm": ">=0.126.0"
67
67
  },
68
68
  "publishConfig": {
69
69
  "access": "public"