Skip to content

Commit 64f402e

Browse files
committed
fix export
1 parent eba95f5 commit 64f402e

3 files changed

Lines changed: 174 additions & 3 deletions

File tree

src/content/export-size.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
function getQuadPoints(rawQuad) {
2+
if (!rawQuad) return [];
3+
if (Array.isArray(rawQuad)) return rawQuad;
4+
return [rawQuad.p1, rawQuad.p2, rawQuad.p3, rawQuad.p4].filter(Boolean);
5+
}
6+
7+
function getIrNodePoints(node) {
8+
if (node.type === 'polygon' || node.type === 'polyline') {
9+
return node.points ?? [];
10+
}
11+
12+
if (node.type === 'text' || node.type === 'image') {
13+
return node.quad ?? [];
14+
}
15+
16+
return [];
17+
}
18+
19+
function getRootQuads(root) {
20+
if (!('getBoxQuads' in root) || typeof root.getBoxQuads !== 'function') {
21+
return [];
22+
}
23+
24+
try {
25+
return root.getBoxQuads({ box: 'border' });
26+
} catch {
27+
return [];
28+
}
29+
}
30+
31+
export function getViewportSizeFromMetrics({
32+
quads = [],
33+
rect = { width: 0, height: 0 },
34+
scrollWidth = 0,
35+
scrollHeight = 0,
36+
clientWidth = 0,
37+
clientHeight = 0,
38+
}) {
39+
if (quads.length > 0) {
40+
let minX = Infinity;
41+
let minY = Infinity;
42+
let maxX = -Infinity;
43+
let maxY = -Infinity;
44+
45+
for (const quad of quads) {
46+
for (const point of getQuadPoints(quad)) {
47+
if (point.x < minX) minX = point.x;
48+
if (point.y < minY) minY = point.y;
49+
if (point.x > maxX) maxX = point.x;
50+
if (point.y > maxY) maxY = point.y;
51+
}
52+
}
53+
54+
if (Number.isFinite(minX) && Number.isFinite(minY) && Number.isFinite(maxX) && Number.isFinite(maxY)) {
55+
return {
56+
width: Math.ceil(Math.max(maxX - minX, scrollWidth, clientWidth)) || 1,
57+
height: Math.ceil(Math.max(maxY - minY, scrollHeight, clientHeight)) || 1,
58+
};
59+
}
60+
}
61+
62+
return {
63+
width: Math.ceil(Math.max(rect.width ?? 0, scrollWidth, clientWidth)) || 1,
64+
height: Math.ceil(Math.max(rect.height ?? 0, scrollHeight, clientHeight)) || 1,
65+
};
66+
}
67+
68+
export function getIrExtent(irNodes) {
69+
let maxX = 0;
70+
let maxY = 0;
71+
72+
for (const node of irNodes ?? []) {
73+
for (const point of getIrNodePoints(node)) {
74+
if (point.x > maxX) maxX = point.x;
75+
if (point.y > maxY) maxY = point.y;
76+
}
77+
}
78+
79+
return { maxX, maxY };
80+
}
81+
82+
export function applyIrExtentFallback(viewport, irNodes) {
83+
const nextViewport = { ...viewport };
84+
const { maxX, maxY } = getIrExtent(irNodes);
85+
86+
if (nextViewport.width <= 1 && maxX > 0) nextViewport.width = Math.ceil(maxX);
87+
if (nextViewport.height <= 1 && maxY > 0) nextViewport.height = Math.ceil(maxY);
88+
89+
return nextViewport;
90+
}
91+
92+
export function calculateExportSize(root, irNodes) {
93+
return applyIrExtentFallback(
94+
getViewportSizeFromMetrics({
95+
quads: getRootQuads(root),
96+
rect: root.getBoundingClientRect(),
97+
scrollWidth: root.scrollWidth,
98+
scrollHeight: root.scrollHeight,
99+
clientWidth: root.clientWidth,
100+
clientHeight: root.clientHeight,
101+
}),
102+
irNodes,
103+
);
104+
}

src/content/run-export.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* data-URL, and sends it to the background service worker which
77
* triggers chrome.downloads.download({ saveAs: true }).
88
*/
9+
import { calculateExportSize } from './export-size.js';
910
import { extensionApi } from '../shared/extension-api.js';
1011

1112
(async () => {
@@ -19,9 +20,6 @@ import { extensionApi } from '../shared/extension-api.js';
1920
const { extractIR, renderIR, writers } = lib;
2021

2122
const root = document.documentElement;
22-
const width = root.scrollWidth;
23-
const height = root.scrollHeight;
24-
const maxY = height;
2523

2624
const BITMAP_FORMATS = ['png', 'jpeg', 'webp'];
2725
const isBitmap = BITMAP_FORMATS.includes(format);
@@ -38,6 +36,8 @@ import { extensionApi } from '../shared/extension-api.js';
3836
}
3937

4038
let ir = await extract(true);
39+
const { width, height } = calculateExportSize(root, ir);
40+
const maxY = height;
4141

4242
// ── Render to chosen format ───────────────────────────
4343
let data; // string | Uint8Array

tests/export-size.test.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { describe, expect, it } from 'vitest';
2+
import {
3+
applyIrExtentFallback,
4+
getViewportSizeFromMetrics,
5+
} from '../src/content/export-size.js';
6+
7+
describe('getViewportSizeFromMetrics', () => {
8+
it('uses quad bounds when they exceed the root metrics', () => {
9+
const viewport = getViewportSizeFromMetrics({
10+
quads: [{
11+
p1: { x: -20, y: 10 },
12+
p2: { x: 100, y: 10 },
13+
p3: { x: 100, y: 140 },
14+
p4: { x: -20, y: 140 },
15+
}],
16+
rect: { width: 80, height: 90 },
17+
scrollWidth: 60,
18+
scrollHeight: 70,
19+
clientWidth: 50,
20+
clientHeight: 40,
21+
});
22+
23+
expect(viewport).toEqual({ width: 120, height: 130 });
24+
});
25+
26+
it('falls back to rect and scrolling metrics when no quads are available', () => {
27+
const viewport = getViewportSizeFromMetrics({
28+
quads: [],
29+
rect: { width: 220, height: 120 },
30+
scrollWidth: 320,
31+
scrollHeight: 240,
32+
clientWidth: 160,
33+
clientHeight: 180,
34+
});
35+
36+
expect(viewport).toEqual({ width: 320, height: 240 });
37+
});
38+
});
39+
40+
describe('applyIrExtentFallback', () => {
41+
it('uses IR bounds when the root viewport is unexpectedly empty', () => {
42+
const viewport = applyIrExtentFallback(
43+
{ width: 1, height: 1 },
44+
[
45+
{
46+
type: 'polygon',
47+
points: [
48+
{ x: 0, y: 0 },
49+
{ x: 150.2, y: 0 },
50+
{ x: 150.2, y: 90.1 },
51+
],
52+
},
53+
{
54+
type: 'text',
55+
quad: [
56+
{ x: 0, y: 0 },
57+
{ x: 25, y: 0 },
58+
{ x: 200.8, y: 101.3 },
59+
{ x: 5, y: 101.3 },
60+
],
61+
},
62+
],
63+
);
64+
65+
expect(viewport).toEqual({ width: 201, height: 102 });
66+
});
67+
});

0 commit comments

Comments
 (0)