@build-qube/papyra 0.2.0 → 0.3.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.
@@ -0,0 +1,163 @@
1
+ /** True when the rotation exchanges the page's width and height. */
2
+ function turned(rotation) {
3
+ return rotation === 90 || rotation === 270;
4
+ }
5
+ /**
6
+ * A page's dimensions after a rotation, still in points.
7
+ *
8
+ * The layout half of a rotated view, and separate from {@link viewport} because it is
9
+ * needed *before* a scale exists: a viewer sizing its page boxes, or resolving a
10
+ * fit-to-width against the turned page, is working in points and cannot use a
11
+ * viewport's floored pixel dimensions without losing the fraction of a point that
12
+ * decides a box's final rounding.
13
+ *
14
+ * Returns the same object when there is nothing to swap, so it is safe in a
15
+ * dependency array.
16
+ */
17
+ export function rotateSize(size, rotation) {
18
+ return turned(rotation) ? { width: size.height, height: size.width } : size;
19
+ }
20
+ /**
21
+ * Build a {@link Viewport} for a page.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const vp = viewport(doc.pageSize(0), { fitWidth: 1600, rotation: 90 });
26
+ * canvasEl.style.width = `${vp.width}px`;
27
+ * canvasEl.style.height = `${vp.height}px`;
28
+ * ```
29
+ */
30
+ export function viewport(size, options = {}) {
31
+ const rotation = options.rotation ?? 0;
32
+ // `fitWidth` is measured on screen, so at 90° it constrains the page's height.
33
+ const along = turned(rotation) ? size.height : size.width;
34
+ const scale = options.fitWidth !== undefined && along > 0
35
+ ? options.fitWidth / along
36
+ : (options.dpi ?? 72) / 72;
37
+ const shown = rotateSize(size, rotation);
38
+ return {
39
+ width: Math.floor(shown.width * scale),
40
+ height: Math.floor(shown.height * scale),
41
+ scale,
42
+ dpi: scale * 72,
43
+ rotation,
44
+ page: size,
45
+ };
46
+ }
47
+ /**
48
+ * Map a point from 72-DPI page space into a viewport's pixels.
49
+ *
50
+ * Rotation happens in point space and the scale is applied after, so a rect and a quad
51
+ * on the same page cannot drift apart by a rounding step.
52
+ */
53
+ function project(x, y, vp) {
54
+ const { width: w, height: h } = vp.page;
55
+ switch (vp.rotation) {
56
+ case 90:
57
+ return { x: (h - y) * vp.scale, y: x * vp.scale };
58
+ case 180:
59
+ return { x: (w - x) * vp.scale, y: (h - y) * vp.scale };
60
+ case 270:
61
+ return { x: y * vp.scale, y: (w - x) * vp.scale };
62
+ default:
63
+ return { x: x * vp.scale, y: y * vp.scale };
64
+ }
65
+ }
66
+ /**
67
+ * Put a rect — a {@link PageLink.rect}, a {@link quadBounds} result — into a viewport's
68
+ * pixels.
69
+ *
70
+ * Stays axis-aligned, because the rotations are quarter turns: only the corners swap
71
+ * roles. Supersedes {@link scaleRect} for anything drawn over a rotated view;
72
+ * `scaleRect` remains the cheaper call when the rotation is always zero.
73
+ */
74
+ export function viewportRect(rect, vp) {
75
+ const a = project(rect.x, rect.y, vp);
76
+ const b = project(rect.x + rect.width, rect.y + rect.height, vp);
77
+ const x = Math.min(a.x, b.x);
78
+ const y = Math.min(a.y, b.y);
79
+ return {
80
+ x,
81
+ y,
82
+ width: Math.max(a.x, b.x) - x,
83
+ height: Math.max(a.y, b.y) - y,
84
+ };
85
+ }
86
+ /**
87
+ * Put a {@link lineQuad} into a viewport's pixels.
88
+ *
89
+ * The corner order survives: rotating the page rotates the text with it, so the
90
+ * quad's top-left is still the top-left of the text's own orientation. That is what
91
+ * keeps a highlight on a drawing's vertical dimension label correct at every angle
92
+ * rather than only at zero.
93
+ */
94
+ export function viewportQuad(quad, vp) {
95
+ const p0 = project(quad.x0, quad.y0, vp);
96
+ const p1 = project(quad.x1, quad.y1, vp);
97
+ const p2 = project(quad.x2, quad.y2, vp);
98
+ const p3 = project(quad.x3, quad.y3, vp);
99
+ return {
100
+ x0: p0.x,
101
+ y0: p0.y,
102
+ x1: p1.x,
103
+ y1: p1.y,
104
+ x2: p2.x,
105
+ y2: p2.y,
106
+ x3: p3.x,
107
+ y3: p3.y,
108
+ };
109
+ }
110
+ /**
111
+ * Turn a rendered page's pixels, returning a new {@link RenderedPage}.
112
+ *
113
+ * **Prefer {@link paintToCanvas} with a `rotation` in a browser** — a canvas rotates
114
+ * in the draw call and this copies the whole buffer, which is 7 MB and a few tens of
115
+ * milliseconds for a 1600px ARCH-E sheet. This exists for the paths that have no
116
+ * canvas: writing a rotated PNG from Node, handing bytes to an encoder.
117
+ *
118
+ * A rotation of `0` returns the page unchanged rather than copying it.
119
+ */
120
+ export function rotatePage(page, rotation) {
121
+ if (rotation === 0)
122
+ return page;
123
+ const { width: w, height: h, stride } = page;
124
+ const outW = turned(rotation) ? h : w;
125
+ const outH = turned(rotation) ? w : h;
126
+ const out = new Uint8Array(outW * outH * 4);
127
+ // One 32-bit move per pixel instead of four 8-bit ones, when the views allow it. On
128
+ // wasm `page.data` is a window onto shared linear memory at an arbitrary offset, and
129
+ // `Uint32Array` refuses one that is not 4-byte aligned — hence the byte fallback
130
+ // rather than an assumption.
131
+ const aligned = page.data.byteOffset % 4 === 0 && stride % 4 === 0;
132
+ const src32 = aligned
133
+ ? new Uint32Array(page.data.buffer, page.data.byteOffset, (h * stride) / 4)
134
+ : null;
135
+ const dst32 = src32 ? new Uint32Array(out.buffer) : null;
136
+ const srcRow = stride / 4;
137
+ for (let y = 0; y < h; y++) {
138
+ for (let x = 0; x < w; x++) {
139
+ // Where this source pixel lands, per quarter turn clockwise.
140
+ const dx = rotation === 90 ? h - 1 - y : rotation === 180 ? w - 1 - x : y;
141
+ const dy = rotation === 90 ? x : rotation === 180 ? h - 1 - y : w - 1 - x;
142
+ if (src32 && dst32) {
143
+ dst32[dy * outW + dx] = src32[y * srcRow + x];
144
+ }
145
+ else {
146
+ const s = y * stride + x * 4;
147
+ const d = (dy * outW + dx) * 4;
148
+ out[d] = page.data[s];
149
+ out[d + 1] = page.data[s + 1];
150
+ out[d + 2] = page.data[s + 2];
151
+ out[d + 3] = page.data[s + 3];
152
+ }
153
+ }
154
+ }
155
+ return {
156
+ width: outW,
157
+ height: outH,
158
+ stride: outW * 4,
159
+ format: page.format,
160
+ data: out,
161
+ };
162
+ }
163
+ //# sourceMappingURL=viewport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewport.js","sourceRoot":"","sources":["../src/viewport.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@build-qube/papyra",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Fast PDF rendering for Node and the browser, powered by Rust",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,7 +40,7 @@
40
40
  "test:integration": "bun test test/integration"
41
41
  },
42
42
  "dependencies": {
43
- "@build-qube/papyra-native": "0.2.0"
43
+ "@build-qube/papyra-native": "0.3.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "typescript": "^7.0.2"