mixdog 1.0.4 → 1.0.6
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/README.md +117 -434
- package/package.json +18 -17
- package/scripts/gate-local.mjs +93 -0
- package/scripts/lib/gate-scheduler.mjs +24 -0
- package/scripts/lib/run-node-tests.mjs +5 -1
- package/scripts/release-paths.mjs +22 -1
- package/scripts/test-direct.mjs +2 -1
- package/scripts/test.mjs +3 -1
- package/src/defaults/agents.json +0 -12
- package/src/defaults/mixdog-config.template.json +1 -2
- package/src/defaults/skills/docx/SKILL.md +7 -4
- package/src/defaults/skills/docx/references/html.md +46 -0
- package/src/defaults/skills/docx/references/native-authoring.md +18 -4
- package/src/defaults/skills/pdf/SKILL.md +10 -2
- package/src/defaults/skills/pdf/references/html.md +64 -0
- package/src/defaults/skills/pptx/references/html.md +1 -1
- package/src/defaults/skills/xlsx/SKILL.md +7 -3
- package/src/defaults/skills/xlsx/references/html.md +40 -0
- package/src/defaults/skills/xlsx/references/report-design.md +21 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
- package/src/runtime/agent/orchestrator/internal-agents.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +6 -5
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +50 -9
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +28 -1
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +34 -8
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/transcript-restore/agent-envelope.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +3 -1
- package/src/runtime/attachments/pdf-extract.mjs +7 -3
- package/src/runtime/computer-bridge/client.mjs +1 -1
- package/src/runtime/memory/index.mjs +4 -15
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +0 -2
- package/src/runtime/memory/lib/cycle-scheduler/backlog-probe.mjs +3 -11
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +1 -3
- package/src/runtime/memory/lib/cycle-scheduler/scheduled-enqueue.mjs +1 -11
- package/src/runtime/memory/lib/cycle-scheduler/tick-loop.mjs +0 -2
- package/src/runtime/memory/lib/cycle-scheduler.mjs +4 -13
- package/src/runtime/memory/lib/cycle-signatures.mjs +0 -6
- package/src/runtime/memory/lib/cycle1/cycle1-rows.mjs +1 -1
- package/src/runtime/memory/lib/cycle1/cycle1-window.mjs +2 -2
- package/src/runtime/memory/lib/embedding-reindex.mjs +1 -1
- package/src/runtime/memory/lib/http-router/lifecycle-routes.mjs +0 -1
- package/src/runtime/memory/lib/memory-action-handlers/cycle-actions.mjs +7 -52
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +6 -22
- package/src/runtime/memory/lib/memory-action-handlers/cycle-rebuild-action.mjs +10 -17
- package/src/runtime/memory/lib/memory-action-handlers/maintenance-actions.mjs +1 -3
- package/src/runtime/memory/lib/memory-action-handlers/manage-actions.mjs +2 -4
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -4
- package/src/runtime/memory/lib/memory-cycle-requests.mjs +1 -1
- package/src/runtime/memory/lib/{memory-cycle2-shared.mjs → memory-cycle-shared.mjs} +14 -16
- package/src/runtime/memory/lib/memory-cycle.mjs +3 -2
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-embed.mjs +14 -1
- package/src/runtime/memory/lib/memory-ops-policy.mjs +8 -11
- package/src/runtime/memory/lib/memory-schema/entries.mjs +0 -1
- package/src/runtime/memory/lib/memory.mjs +2 -6
- package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +1 -1
- package/src/runtime/memory/lib/query-hybrid-search.mjs +1 -1
- package/src/runtime/memory/lib/query-maintenance-handlers.mjs +0 -6
- package/src/runtime/memory/lib/recall-format.mjs +1 -1
- package/src/runtime/office/authoring/docx-html-build.mjs +375 -0
- package/src/runtime/office/authoring/docx-html-measure.mjs +561 -0
- package/src/runtime/office/authoring/docx-html-runner.mjs +102 -0
- package/src/runtime/office/authoring/html-browser.mjs +61 -0
- package/src/runtime/office/authoring/html-document-author.mjs +181 -0
- package/src/runtime/office/authoring/html-source-drift.mjs +36 -0
- package/src/runtime/office/authoring/pdf-author-action.mjs +266 -0
- package/src/runtime/office/authoring/pdf-html-charts.mjs +379 -0
- package/src/runtime/office/authoring/pdf-html-runner.mjs +317 -0
- package/src/runtime/office/authoring/pptx-author-action.mjs +44 -22
- package/src/runtime/office/authoring/pptx-author-session.mjs +12 -6
- package/src/runtime/office/authoring/pptx-html-build.mjs +23 -4
- package/src/runtime/office/authoring/pptx-html-measure.mjs +38 -44
- package/src/runtime/office/authoring/pptx-html-runner.mjs +18 -3
- package/src/runtime/office/authoring/pptx-receipt.mjs +39 -31
- package/src/runtime/office/authoring/pptx-review-artifacts.mjs +9 -1
- package/src/runtime/office/authoring/pptx-script-runner.mjs +6 -2
- package/src/runtime/office/authoring/xlsx-html-build.mjs +365 -0
- package/src/runtime/office/authoring/xlsx-html-measure.mjs +257 -0
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +139 -0
- package/src/runtime/office/capabilities-catalog.mjs +8 -0
- package/src/runtime/office/capabilities-signatures.mjs +18 -6
- package/src/runtime/office/capabilities.mjs +26 -1
- package/src/runtime/office/com/office-com-host.ps1 +133 -17
- package/src/runtime/office/core/office-actions-batch.mjs +16 -1
- package/src/runtime/office/core/office-actions-inspect.mjs +3 -2
- package/src/runtime/office/core/office-actions-lifecycle.mjs +6 -2
- package/src/runtime/office/core/office-actions-render.mjs +4 -1
- package/src/runtime/office/core/office-finalize/review-stage.mjs +15 -2
- package/src/runtime/office/core/office-qa/design-review-stage.mjs +9 -0
- package/src/runtime/office/core/office-session-dispatch.mjs +3 -0
- package/src/runtime/office/core/office-sessionless-actions.mjs +7 -2
- package/src/runtime/office/core/office-sessions.mjs +19 -2
- package/src/runtime/office/pdf/pdf-writer.mjs +1 -1
- package/src/runtime/office/portable/docx-formatting.mjs +21 -0
- package/src/runtime/office/portable/ooxml-validator.mjs +22 -1
- package/src/runtime/office/portable/portable-chart.mjs +10 -2
- package/src/runtime/office/portable/portable-docx-edits.mjs +12 -6
- package/src/runtime/office/portable/portable-docx-operations.mjs +75 -1
- package/src/runtime/office/portable/portable-docx-xml.mjs +49 -6
- package/src/runtime/office/portable/portable-docx.mjs +2 -0
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +2 -0
- package/src/runtime/office/portable/portable-validation.mjs +6 -3
- package/src/runtime/office/portable/portable-xlsx-cell-values.mjs +87 -0
- package/src/runtime/office/portable/portable-xlsx-charts.mjs +55 -13
- package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +1 -1
- package/src/runtime/office/portable/xlsx-contract.mjs +82 -3
- package/src/runtime/office/quality/assurance-checklist.mjs +2 -2
- package/src/runtime/office/quality/assurance-rendered.mjs +27 -1
- package/src/runtime/office/quality/assurance-structure-docx.mjs +44 -0
- package/src/runtime/office/quality/assurance-structure-pptx.mjs +24 -6
- package/src/runtime/office/quality/assurance-structure-xlsx.mjs +50 -1
- package/src/runtime/office/quality/design-review-critique.mjs +3 -2
- package/src/runtime/office/quality/document-brief.mjs +44 -0
- package/src/runtime/office/quality/inline-audit.mjs +7 -2
- package/src/runtime/office/quality/quality-pipeline.mjs +9 -1
- package/src/runtime/office/tool-defs.mjs +2 -2
- package/src/runtime/shared/llm/quota-value-estimate.mjs +317 -0
- package/src/runtime/shared/llm/usage-ledger-quota.mjs +266 -63
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +35 -0
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +10 -1
- package/src/runtime/shared/provider-accounts.mjs +11 -1
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -4
- package/src/runtime/shared/tool-execution-contract.mjs +1 -1
- package/src/runtime/shared/wait-until.test-support.mjs +33 -0
- package/src/session-runtime/boot/apis.mjs +1 -1
- package/src/session-runtime/services/channel-admin.mjs +4 -3
- package/src/session-runtime/services/usage-stats-model.mjs +22 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +6 -1
- package/src/session-runtime/turn/session-ops.mjs +15 -9
- package/src/session-runtime/usage-stats-api.mjs +34 -2
- package/src/standalone/agent-dispatch-broker.mjs +1 -1
- package/src/standalone/local-session-runtime.mjs +488 -0
- package/src/standalone/session-protocol.mjs +1 -0
- package/src/standalone/session-runtime-inline-host.mjs +1 -1
- package/src/tui/hooks/useSession.mjs +1 -1
- package/src/tui/session/session-api/commands.mjs +1 -1
- package/src/tui/session/session-api/integrations.mjs +3 -0
- package/src/tui/session/session-flow/auto-clear.mjs +6 -7
- package/src/tui/session/tool-card-results.mjs +1 -1
- package/src/tui/session/turn.mjs +5 -0
- package/src/tui/session-local.mjs +3 -488
- package/src/tui/session.mjs +1 -1
- package/src/ui/statusline-agents.mjs +0 -1
- package/src/rules/agent/41-cycle2-agent.md +0 -24
- package/src/runtime/memory/lib/cycle-scheduler/cycle2-runs.mjs +0 -120
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +0 -110
- package/src/runtime/memory/lib/memory-cycle2-quarantine.mjs +0 -99
- package/src/runtime/memory/lib/memory-cycle2-review.mjs +0 -240
- package/src/runtime/memory/lib/memory-cycle2.mjs +0 -191
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// The `author` action's PDF route: a local Chrome/Edge lays an HTML document
|
|
2
|
+
// out under its own @page rules and prints it. Before printing, the document
|
|
3
|
+
// is laid out at the printable width and read for what print would silently
|
|
4
|
+
// cut or move — a block wider than the page, a picture that never loaded —
|
|
5
|
+
// so the file lands only when it holds. Designed pages (section.slide on the
|
|
6
|
+
// sheet) are measured and gated as a deck first (pdf-author-action.mjs) and
|
|
7
|
+
// printed here one page per sheet.
|
|
8
|
+
import { readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
import { PDFDict, PDFDocument, PDFHexString, PDFName } from 'pdf-lib';
|
|
11
|
+
|
|
12
|
+
import { htmlFailure, launchHtmlBrowser, runInBrowser } from './html-browser.mjs';
|
|
13
|
+
import { applyDocumentProperties } from '../pdf/pdf-writer.mjs';
|
|
14
|
+
import { iconTable, injectIcons } from './pptx-html-measure.mjs';
|
|
15
|
+
import { drawDataCharts } from './pdf-html-charts.mjs';
|
|
16
|
+
|
|
17
|
+
// Runs inside the page: every h1-h3 as the browser names it in the outline (its text with the line
|
|
18
|
+
// breaks dropped) and as a reader sees it (a <br> read as a space).
|
|
19
|
+
function headingTitles() {
|
|
20
|
+
return [...document.querySelectorAll('h1, h2, h3')].map((heading) => ({
|
|
21
|
+
raw: heading.textContent || '',
|
|
22
|
+
shown: (heading.innerText || '').replace(/\s+/g, ' ').trim(),
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// The browser names a bookmark after its heading's text with the line breaks dropped, so a title set
|
|
27
|
+
// on two lines ("…틀로<br>설계하고") reads "…틀로설계하고". Each such outline entry takes the heading
|
|
28
|
+
// as shown. Returns how many titles changed.
|
|
29
|
+
function repairOutlineTitles(document, headings) {
|
|
30
|
+
const key = (text) => String(text || '').replace(/\s+/g, '');
|
|
31
|
+
const shown = new Map();
|
|
32
|
+
for (const heading of headings) if (heading.shown) shown.set(key(heading.raw), heading.shown);
|
|
33
|
+
const outlines = document.catalog.lookupMaybe(PDFName.of('Outlines'), PDFDict);
|
|
34
|
+
let changed = 0;
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
const walk = (node) => {
|
|
37
|
+
while (node && !seen.has(node)) {
|
|
38
|
+
seen.add(node);
|
|
39
|
+
const title = node.get(PDFName.of('Title'));
|
|
40
|
+
const text = typeof title?.decodeText === 'function' ? title.decodeText() : '';
|
|
41
|
+
const fixed = shown.get(key(text));
|
|
42
|
+
if (fixed && fixed !== text) {
|
|
43
|
+
node.set(PDFName.of('Title'), PDFHexString.fromText(fixed));
|
|
44
|
+
changed += 1;
|
|
45
|
+
}
|
|
46
|
+
walk(node.lookupMaybe(PDFName.of('First'), PDFDict));
|
|
47
|
+
node = node.lookupMaybe(PDFName.of('Next'), PDFDict);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
walk(outlines?.lookupMaybe(PDFName.of('First'), PDFDict));
|
|
51
|
+
return changed;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The HTML as laid out, kept beside the PDF so relative <img> paths resolve and a re-author starts from it. */
|
|
55
|
+
export function pdfHtmlSourcePath(target) {
|
|
56
|
+
return `${target}.mixdog-source.html`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// CSS px per unit (96 px to the inch) and the named sheets in millimetres, portrait.
|
|
60
|
+
const PX_PER_UNIT = { px: 1, in: 96, cm: 96 / 2.54, mm: 96 / 25.4, q: 96 / 101.6, pt: 96 / 72, pc: 16 };
|
|
61
|
+
const NAMED_SHEETS_MM = {
|
|
62
|
+
a3: [297, 420],
|
|
63
|
+
a4: [210, 297],
|
|
64
|
+
a5: [148, 210],
|
|
65
|
+
b4: [250, 353],
|
|
66
|
+
b5: [176, 250],
|
|
67
|
+
letter: [215.9, 279.4],
|
|
68
|
+
legal: [215.9, 355.6],
|
|
69
|
+
ledger: [279.4, 431.8],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function lengthPx(token, what) {
|
|
73
|
+
const match = /^(-?\d*\.?\d+)([a-z]+)?$/i.exec(String(token).trim());
|
|
74
|
+
const unit = (match?.[2] || (Number(match?.[1]) === 0 ? 'px' : '')).toLowerCase();
|
|
75
|
+
if (!match || !PX_PER_UNIT[unit]) {
|
|
76
|
+
throw new Error(`@page ${what} "${token}" is not a length; write it in mm, cm, in, pt, or px.`);
|
|
77
|
+
}
|
|
78
|
+
return Number(match[1]) * PX_PER_UNIT[unit];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The printable area in CSS px from the @page size and margins the document declared. */
|
|
82
|
+
export function printableArea(rule) {
|
|
83
|
+
const tokens = String(rule?.size || '')
|
|
84
|
+
.trim()
|
|
85
|
+
.toLowerCase()
|
|
86
|
+
.split(/\s+/)
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
if (!tokens.length || tokens[0] === 'auto') {
|
|
89
|
+
throw new Error(
|
|
90
|
+
'The document declares no @page size; open the stylesheet with `@page { size: A4; margin: 18mm }` (or letter, A5 landscape, 148mm 210mm).'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
let landscape = false;
|
|
94
|
+
const lengths = [];
|
|
95
|
+
let named = null;
|
|
96
|
+
for (const token of tokens) {
|
|
97
|
+
if (token === 'landscape') landscape = true;
|
|
98
|
+
else if (token === 'portrait') landscape = false;
|
|
99
|
+
else if (NAMED_SHEETS_MM[token]) named = NAMED_SHEETS_MM[token].map((mm) => mm * PX_PER_UNIT.mm);
|
|
100
|
+
else lengths.push(lengthPx(token, 'size'));
|
|
101
|
+
}
|
|
102
|
+
let [width, height] = named || [lengths[0], lengths[1] ?? lengths[0]];
|
|
103
|
+
if (!(width > 0 && height > 0)) throw new Error(`@page size "${rule.size}" names no sheet.`);
|
|
104
|
+
if (named && landscape) [width, height] = [height, width];
|
|
105
|
+
const margin = Object.fromEntries(
|
|
106
|
+
['top', 'right', 'bottom', 'left'].map((side) => [
|
|
107
|
+
side,
|
|
108
|
+
rule.margin?.[side] ? lengthPx(rule.margin[side], `margin-${side}`) : 0,
|
|
109
|
+
])
|
|
110
|
+
);
|
|
111
|
+
return {
|
|
112
|
+
sheet: { width, height },
|
|
113
|
+
margin,
|
|
114
|
+
width: width - margin.left - margin.right,
|
|
115
|
+
height: height - margin.top - margin.bottom,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Runs inside the page: the unnamed @page rules that apply to print, later declarations winning.
|
|
120
|
+
export function readPageRule() {
|
|
121
|
+
const out = { size: '', margin: {} };
|
|
122
|
+
const visit = (list) => {
|
|
123
|
+
for (const rule of list) {
|
|
124
|
+
if (rule instanceof CSSPageRule && !rule.selectorText) {
|
|
125
|
+
const size = rule.style.getPropertyValue('size');
|
|
126
|
+
if (size) out.size = size;
|
|
127
|
+
for (const side of ['top', 'right', 'bottom', 'left']) {
|
|
128
|
+
const value = rule.style.getPropertyValue(`margin-${side}`);
|
|
129
|
+
if (value) out.margin[side] = value;
|
|
130
|
+
}
|
|
131
|
+
} else if (rule instanceof CSSMediaRule && window.matchMedia(rule.conditionText).matches) {
|
|
132
|
+
visit(rule.cssRules);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
for (const sheet of document.styleSheets) {
|
|
137
|
+
try {
|
|
138
|
+
visit(sheet.cssRules);
|
|
139
|
+
} catch {}
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The canvas of a PDF's designed pages (the deck contract, pptx-html-measure.mjs): the sheet the document
|
|
146
|
+
* declares, printed full-bleed, in CSS px at 96 to the inch. Runs on the page the measure has loaded.
|
|
147
|
+
*/
|
|
148
|
+
export async function frameCanvas(page) {
|
|
149
|
+
await page.emulateMediaType('print');
|
|
150
|
+
const area = printableArea(await page.evaluate(readPageRule));
|
|
151
|
+
if (Object.values(area.margin).some((margin) => margin > 0.5)) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
'Designed pages print full-bleed: declare `@page { size: A4; margin: 0 }` and give each section.slide the whole sheet (width: 210mm; height: 297mm), with the margins inside it.'
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
// The viewport takes whole px; the working deck is sized from those same px so 1 px stays exactly
|
|
157
|
+
// 0.75 pt (A4's 793.7 px rounded to 794 is 0.2 pt wider, and a 22 pt title stays 22 pt).
|
|
158
|
+
const width = Math.round(area.sheet.width);
|
|
159
|
+
const height = Math.round(area.sheet.height);
|
|
160
|
+
return { width, height, inchWidth: width / 96, inchHeight: height / 96 };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Designed pages print one section.slide per sheet whatever the screen CSS does; speaker notes stay off the page.
|
|
164
|
+
const FRAME_PRINT_CSS = `section.slide { break-after: page; break-inside: avoid; }
|
|
165
|
+
section.slide:last-of-type { break-after: auto; }
|
|
166
|
+
aside.notes { display: none !important; }`;
|
|
167
|
+
|
|
168
|
+
// Runs inside the page, laid out at the printable width under print media.
|
|
169
|
+
// Errors refuse the document; warnings ride on the result. Designed pages
|
|
170
|
+
// are measured on their canvas before printing, so only their pictures are read here.
|
|
171
|
+
function measurePrintLayout(area, frame) {
|
|
172
|
+
const describe = (element) => {
|
|
173
|
+
const id = element.id ? `#${element.id}` : '';
|
|
174
|
+
const className = typeof element.className === 'string' ? element.className.trim() : '';
|
|
175
|
+
const classes = className ? `.${className.split(/\s+/).join('.')}` : '';
|
|
176
|
+
const text = (element.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 40);
|
|
177
|
+
return `<${element.localName}${id}${classes}>${text ? ` "${text}"` : ''}`;
|
|
178
|
+
};
|
|
179
|
+
const px = (value) => Math.round(value);
|
|
180
|
+
const findings = [];
|
|
181
|
+
for (const image of document.images) {
|
|
182
|
+
const src = image.getAttribute('src') || '';
|
|
183
|
+
if (/^(?:https?:)?\/\//i.test(src)) {
|
|
184
|
+
findings.push({
|
|
185
|
+
code: 'remote_image',
|
|
186
|
+
severity: 'error',
|
|
187
|
+
element: describe(image),
|
|
188
|
+
message: `${src} is a web address; save the picture beside the document or inline it as a data: URI.`,
|
|
189
|
+
});
|
|
190
|
+
} else if (!image.complete || image.naturalWidth === 0) {
|
|
191
|
+
findings.push({
|
|
192
|
+
code: 'image_missing',
|
|
193
|
+
severity: 'error',
|
|
194
|
+
element: describe(image),
|
|
195
|
+
message: `${src || '(no src)'} did not load; the path resolves from the PDF's folder.`,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (frame) return { findings };
|
|
200
|
+
const clipped = (element) => {
|
|
201
|
+
for (let parent = element.parentElement; parent && parent !== document.body; parent = parent.parentElement) {
|
|
202
|
+
if (getComputedStyle(parent).overflowX !== 'visible') return true;
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
};
|
|
206
|
+
const outside = (box) => box.right > area.width + 1 || box.left < -1;
|
|
207
|
+
const tall = new Set();
|
|
208
|
+
for (const element of document.body.querySelectorAll('*')) {
|
|
209
|
+
const style = getComputedStyle(element);
|
|
210
|
+
if (style.display === 'none' || style.position === 'fixed') continue;
|
|
211
|
+
const box = element.getBoundingClientRect();
|
|
212
|
+
if (!box.width && !box.height) continue;
|
|
213
|
+
const parent = element.parentElement;
|
|
214
|
+
if (
|
|
215
|
+
outside(box) &&
|
|
216
|
+
!clipped(element) &&
|
|
217
|
+
!(parent && parent !== document.body && outside(parent.getBoundingClientRect()))
|
|
218
|
+
) {
|
|
219
|
+
findings.push({
|
|
220
|
+
code: 'overflow_x',
|
|
221
|
+
severity: 'error',
|
|
222
|
+
element: describe(element),
|
|
223
|
+
message: `spans ${px(box.left)}–${px(box.right)} px of a ${px(area.width)} px printable width; print cuts what lies past the margin.`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const unbreakable =
|
|
227
|
+
['img', 'svg', 'canvas', 'video'].includes(element.localName) ||
|
|
228
|
+
['avoid', 'avoid-page'].includes(style.breakInside);
|
|
229
|
+
if (unbreakable && box.height > area.height + 1) {
|
|
230
|
+
let ancestor = element.parentElement;
|
|
231
|
+
while (ancestor && !tall.has(ancestor)) ancestor = ancestor.parentElement;
|
|
232
|
+
tall.add(element);
|
|
233
|
+
if (!ancestor) {
|
|
234
|
+
findings.push({
|
|
235
|
+
code: 'unbreakable_too_tall',
|
|
236
|
+
severity: 'warning',
|
|
237
|
+
element: describe(element),
|
|
238
|
+
message: `is ${px(box.height)} px tall and may not break, but a page holds ${px(area.height)} px; print splits it anyway. Shorten it or let it break.`,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return { findings };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Lays out and prints an HTML document to `output`.
|
|
248
|
+
* @param {string} html the document
|
|
249
|
+
* @param {string} output where the PDF is written (a staging file beside the target)
|
|
250
|
+
* @param {{ sourcePath: string, properties?: object, frame?: boolean, timeoutMs?: number, signal?: AbortSignal }} options
|
|
251
|
+
* frame: designed pages (section.slide on the sheet, already measured): one page per sheet, icons drawn
|
|
252
|
+
* @returns {Promise<object>} `{ ok:true, output, bytes, pageCount, area, warnings }`, or
|
|
253
|
+
* `{ ok:false, error }` / `{ ok:false, layout }` when the browser failed or the layout check refused it
|
|
254
|
+
*/
|
|
255
|
+
export async function printHtmlPdf(
|
|
256
|
+
html,
|
|
257
|
+
output,
|
|
258
|
+
{ sourcePath, properties = {}, frame = false, timeoutMs = 90_000, signal = null }
|
|
259
|
+
) {
|
|
260
|
+
const startedAt = performance.now();
|
|
261
|
+
await writeFile(sourcePath, html, 'utf8');
|
|
262
|
+
await rm(output, { force: true });
|
|
263
|
+
let browser;
|
|
264
|
+
try {
|
|
265
|
+
browser = await launchHtmlBrowser('pdf-html-browser');
|
|
266
|
+
} catch (error) {
|
|
267
|
+
return htmlFailure(error, startedAt);
|
|
268
|
+
}
|
|
269
|
+
const work = async () => {
|
|
270
|
+
const page = await browser.newPage();
|
|
271
|
+
await page.emulateMediaType('print');
|
|
272
|
+
await page.goto(pathToFileURL(sourcePath).href, { waitUntil: 'load' });
|
|
273
|
+
const area = printableArea(await page.evaluate(readPageRule));
|
|
274
|
+
if (frame) {
|
|
275
|
+
await page.evaluate(injectIcons, iconTable().table, '0 0 24 24');
|
|
276
|
+
await page.addStyleTag({ content: FRAME_PRINT_CSS });
|
|
277
|
+
}
|
|
278
|
+
await page.setViewport({ width: Math.round(area.width), height: Math.round(area.height), deviceScaleFactor: 1 });
|
|
279
|
+
await page.evaluate(() => document.fonts.ready);
|
|
280
|
+
// Charts are drawn at the size print lays them out, before the layout is read for what print would cut.
|
|
281
|
+
const charts = await page.evaluate(drawDataCharts);
|
|
282
|
+
const measured = await page.evaluate(measurePrintLayout, { width: area.width, height: area.height }, frame);
|
|
283
|
+
const errors = measured.findings.filter((finding) => finding.severity === 'error');
|
|
284
|
+
if (errors.length) return { refused: errors, area };
|
|
285
|
+
const headings = await page.evaluate(headingTitles);
|
|
286
|
+
await page.pdf({ path: output, preferCSSPageSize: true, printBackground: true, tagged: true, outline: true });
|
|
287
|
+
return {
|
|
288
|
+
area,
|
|
289
|
+
headings,
|
|
290
|
+
charts: charts.drawn,
|
|
291
|
+
warnings: [...charts.warnings, ...measured.findings.filter((finding) => finding.severity !== 'error')],
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
let printed;
|
|
295
|
+
try {
|
|
296
|
+
printed = await runInBrowser(browser, { signal, timeoutMs }, work);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (signal?.aborted) throw error;
|
|
299
|
+
return htmlFailure(error, startedAt);
|
|
300
|
+
}
|
|
301
|
+
if (printed.refused)
|
|
302
|
+
return htmlFailure(new Error('layout check refused the document'), startedAt, { layout: printed.refused });
|
|
303
|
+
const document = await PDFDocument.load(await readFile(output));
|
|
304
|
+
const described = ['title', 'author', 'subject', 'keywords'].some((key) => properties?.[key] != null);
|
|
305
|
+
if (described) applyDocumentProperties(document, properties);
|
|
306
|
+
if (repairOutlineTitles(document, printed.headings) || described) await writeFile(output, await document.save());
|
|
307
|
+
return {
|
|
308
|
+
ok: true,
|
|
309
|
+
output,
|
|
310
|
+
bytes: (await stat(output)).size,
|
|
311
|
+
pageCount: document.getPageCount(),
|
|
312
|
+
area: { width: Math.round(printed.area.width), height: Math.round(printed.area.height) },
|
|
313
|
+
...(printed.charts ? { charts: printed.charts } : {}),
|
|
314
|
+
warnings: printed.warnings,
|
|
315
|
+
elapsedMs: Math.round(performance.now() - startedAt),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
2
3
|
import { render } from '../core/office-actions.mjs';
|
|
3
4
|
import { documentFormat, officeSessionForDocument } from '../core/office-core.mjs';
|
|
4
5
|
import { createAuthoredSession, fullPath, validatePptxAuthorMode } from '../core/office-sessions.mjs';
|
|
@@ -7,14 +8,22 @@ import {
|
|
|
7
8
|
exists,
|
|
8
9
|
landStagedDeck,
|
|
9
10
|
releaseExistingSession,
|
|
11
|
+
releaseStagingTarget,
|
|
10
12
|
reusableAuthoredSession,
|
|
11
13
|
stagingTarget,
|
|
12
14
|
swapAuthoredDocument,
|
|
13
15
|
throwIfAuthoringCancelled,
|
|
14
16
|
} from './pptx-author-session.mjs';
|
|
15
17
|
import { runPptxAuthoringScript } from './pptx-script-runner.mjs';
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
htmlArtifacts,
|
|
20
|
+
htmlBriefScript,
|
|
21
|
+
measureHtmlDrift,
|
|
22
|
+
runPptxHtmlAuthoring,
|
|
23
|
+
writeHtmlComparisons,
|
|
24
|
+
} from './pptx-html-runner.mjs';
|
|
17
25
|
import { factsGate, parseAuthoringBrief, planGate } from './pptx-brief.mjs';
|
|
26
|
+
import { editsReplacedByAuthor, markAuthoredFromHtml } from './html-source-drift.mjs';
|
|
18
27
|
import { readCompositionReceipt } from './pptx-review-artifacts.mjs';
|
|
19
28
|
import { receiptForDelivery } from './pptx-receipt.mjs';
|
|
20
29
|
import { snapshotPortableOoxml } from '../portable/portable-ooxml.mjs';
|
|
@@ -22,10 +31,10 @@ import { snapshotPortableOoxml } from '../portable/portable-ooxml.mjs';
|
|
|
22
31
|
/** The design guide lives in the built-in `pptx` skill; the tool never
|
|
23
32
|
* serves it so one copy stays authoritative and user-overridable. */
|
|
24
33
|
const PPTX_AUTHOR_NEEDS_SOURCE =
|
|
25
|
-
'author requires script: HTML slides or a pptxgenjs script. Load the `pptx` Skill first (Skill name:"pptx"): it carries the authoring workflow, the HTML contract, the composition grammar, and the device kit, then call author again with path and script.';
|
|
34
|
+
'author requires script: HTML slides or a pptxgenjs script (without one it re-authors from <deck>.pptx.mixdog-source.html, and none is there). Load the `pptx` Skill first (Skill name:"pptx"): it carries the authoring workflow, the HTML contract, the composition grammar, and the device kit, then call author again with path and script.';
|
|
26
35
|
|
|
27
36
|
// HTML opens with markup (a doctype, a comment, an element); a pptxgenjs script never does.
|
|
28
|
-
function isHtmlSource(source) {
|
|
37
|
+
export function isHtmlSource(source) {
|
|
29
38
|
return /^\s*</.test(String(source || ''));
|
|
30
39
|
}
|
|
31
40
|
|
|
@@ -33,7 +42,7 @@ function isHtmlSource(source) {
|
|
|
33
42
|
// will hold it; a package the reader cannot open is left to qa, never turned
|
|
34
43
|
// into a refusal. One snapshot answers both: the figures against the fact
|
|
35
44
|
// sheet, then the pages against the plan that was written before them.
|
|
36
|
-
async function gateStagedDeck(path, brief) {
|
|
45
|
+
export async function gateStagedDeck(path, brief) {
|
|
37
46
|
if (!brief.present) return { blocked: false };
|
|
38
47
|
let document;
|
|
39
48
|
try {
|
|
@@ -44,7 +53,8 @@ async function gateStagedDeck(path, brief) {
|
|
|
44
53
|
const facts = factsGate(document, brief);
|
|
45
54
|
if (facts.blocked) return { ...facts, gate: 'facts' };
|
|
46
55
|
const plan = planGate(document, brief);
|
|
47
|
-
|
|
56
|
+
// The snapshot rides on a pass so a caller can read the same deck further (a PDF's design read).
|
|
57
|
+
return plan.blocked ? { ...plan, gate: 'plan' } : { blocked: false, document };
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
function describePlanSlide(entry) {
|
|
@@ -53,7 +63,7 @@ function describePlanSlide(entry) {
|
|
|
53
63
|
return `slide ${entry.slide}`;
|
|
54
64
|
}
|
|
55
65
|
|
|
56
|
-
function planGateResult(target, gate, run) {
|
|
66
|
+
export function planGateResult(target, gate, run) {
|
|
57
67
|
const listed = gate.slides.map(describePlanSlide).join('; ');
|
|
58
68
|
const nextAction = {
|
|
59
69
|
plan_missing:
|
|
@@ -73,7 +83,7 @@ function planGateResult(target, gate, run) {
|
|
|
73
83
|
};
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
function factsGateResult(target, brief, gate, run) {
|
|
86
|
+
export function factsGateResult(target, brief, gate, run) {
|
|
77
87
|
const listed = gate.slides.map((entry) => `slide ${entry.slide}: ${entry.figures.join(', ')}`).join('; ');
|
|
78
88
|
return {
|
|
79
89
|
ok: false,
|
|
@@ -91,7 +101,7 @@ function factsGateResult(target, brief, gate, run) {
|
|
|
91
101
|
|
|
92
102
|
// The HTML's own geometry, read in the browser before anything lands: a declared relation that does not
|
|
93
103
|
// hold, or a near miss no declaration covers (pptx-html-geometry.mjs).
|
|
94
|
-
function geometryGateResult(target, run) {
|
|
104
|
+
export function geometryGateResult(target, run) {
|
|
95
105
|
const listed = run.geometry
|
|
96
106
|
.map((page) => `slide ${page.slide}: ${page.findings.map((finding) => finding.message).join('; ')}`)
|
|
97
107
|
.join(' | ');
|
|
@@ -107,11 +117,12 @@ function geometryGateResult(target, run) {
|
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
function resolveAuthorTarget(args, cwd) {
|
|
110
|
-
if (!String(args.script || '').trim()) throw new Error(PPTX_AUTHOR_NEEDS_SOURCE);
|
|
111
120
|
const requestedPath = String(args.path || args.output || '').trim();
|
|
112
|
-
|
|
121
|
+
// A bare author (no path, no script) is a call made before reading the skill: it is pointed there.
|
|
122
|
+
if (!requestedPath)
|
|
123
|
+
throw new Error(String(args.script || '').trim() ? 'author requires path' : PPTX_AUTHOR_NEEDS_SOURCE);
|
|
113
124
|
const target = fullPath(requestedPath, cwd);
|
|
114
|
-
if (documentFormat(target) !== 'pptx') throw new Error('author writes .pptx targets only');
|
|
125
|
+
if (documentFormat(target) !== 'pptx') throw new Error('author writes .pptx and .pdf targets only');
|
|
115
126
|
return { target, mode: validatePptxAuthorMode(args.mode) };
|
|
116
127
|
}
|
|
117
128
|
|
|
@@ -182,10 +193,10 @@ async function finishAuthoredDeck(session, { args, cwd, target, run, signal, rep
|
|
|
182
193
|
// swaps its deck; an existing session hands its audit rounds on. A re-author
|
|
183
194
|
// replaces the session, but the audit fix rounds belong to the deck: the loop
|
|
184
195
|
// keeps counting across passes on the same path.
|
|
185
|
-
async function authoringSessionState(target, mode, args) {
|
|
196
|
+
async function authoringSessionState(target, mode, args, fromSource) {
|
|
186
197
|
const reusable = reusableAuthoredSession(target, mode);
|
|
187
198
|
const existing = officeSessionForDocument(target);
|
|
188
|
-
if (!reusable && !existing && (await exists(target)) && args.overwrite !== true) {
|
|
199
|
+
if (!reusable && !existing && !fromSource && (await exists(target)) && args.overwrite !== true) {
|
|
189
200
|
throw new Error(`author target already exists: ${target}; pass overwrite:true to replace it`);
|
|
190
201
|
}
|
|
191
202
|
return { reusable, priorAudit: reusable ? null : existing?.inlineAudit || null };
|
|
@@ -193,14 +204,21 @@ async function authoringSessionState(target, mode, args) {
|
|
|
193
204
|
|
|
194
205
|
export async function authorPptx(args, { cwd, dataDir, signal = null }) {
|
|
195
206
|
const { target, mode } = resolveAuthorTarget(args, cwd);
|
|
207
|
+
// Without a script the deck is authored again from the HTML kept beside it, as a PDF or Word document is: a fix
|
|
208
|
+
// is an edit to that file, not the whole deck sent again.
|
|
209
|
+
const sourcePath = htmlArtifacts(target).source;
|
|
210
|
+
const fromSource = !String(args.script || '').trim();
|
|
211
|
+
if (fromSource && !(await exists(sourcePath))) throw new Error(PPTX_AUTHOR_NEEDS_SOURCE);
|
|
212
|
+
const script = fromSource ? await readFile(sourcePath, 'utf8') : String(args.script);
|
|
196
213
|
throwIfAuthoringCancelled(signal);
|
|
197
|
-
const
|
|
214
|
+
const replacedEdits = editsReplacedByAuthor(officeSessionForDocument(target));
|
|
215
|
+
const { reusable, priorAudit } = await authoringSessionState(target, mode, args, fromSource);
|
|
198
216
|
// The script always writes beside the target: a failed script or a refused
|
|
199
217
|
// deck leaves the file on disk and the session holding the previous deck
|
|
200
218
|
// untouched.
|
|
201
|
-
const staging = stagingTarget(target);
|
|
202
|
-
const html = isHtmlSource(
|
|
203
|
-
const brief = parseAuthoringBrief(html ? htmlBriefScript(
|
|
219
|
+
const staging = await stagingTarget(target);
|
|
220
|
+
const html = isHtmlSource(script);
|
|
221
|
+
const brief = parseAuthoringBrief(html ? htmlBriefScript(script) : script);
|
|
204
222
|
let run;
|
|
205
223
|
let session = null;
|
|
206
224
|
let reusedSession = false;
|
|
@@ -209,8 +227,8 @@ export async function authorPptx(args, { cwd, dataDir, signal = null }) {
|
|
|
209
227
|
try {
|
|
210
228
|
throwIfAuthoringCancelled(signal);
|
|
211
229
|
run = html
|
|
212
|
-
? await runPptxHtmlAuthoring(
|
|
213
|
-
: await runPptxAuthoringScript(
|
|
230
|
+
? await runPptxHtmlAuthoring(script, staging, { target, signal })
|
|
231
|
+
: await runPptxAuthoringScript(script, staging, { cwd: dirname(target) });
|
|
214
232
|
throwIfAuthoringCancelled(signal);
|
|
215
233
|
if (!run.ok) return scriptFailedResult(target, run, html);
|
|
216
234
|
if (run.geometry?.length) return geometryGateResult(target, run);
|
|
@@ -236,11 +254,15 @@ export async function authorPptx(args, { cwd, dataDir, signal = null }) {
|
|
|
236
254
|
if (signal?.aborted || error?.name === 'AbortError') discardStaging = true;
|
|
237
255
|
throw error;
|
|
238
256
|
} finally {
|
|
239
|
-
if (discardStaging) await
|
|
257
|
+
if (discardStaging) await releaseStagingTarget(staging).catch(() => {});
|
|
240
258
|
}
|
|
241
259
|
session.authoredBrief = brief;
|
|
260
|
+
// A deck from HTML counts the batch edits that land past its kept source; a pptxgenjs deck keeps no source.
|
|
261
|
+
markAuthoredFromHtml(session, run.htmlSource || null);
|
|
242
262
|
if (priorAudit && !session.inlineAudit) session.inlineAudit = { ...priorAudit };
|
|
243
|
-
|
|
263
|
+
const result = await finishAuthoredDeck(session, { args, cwd, target, run, signal, replacedSession, reusedSession });
|
|
264
|
+
if (replacedEdits) result.replacedEdits = replacedEdits;
|
|
265
|
+
return result;
|
|
244
266
|
}
|
|
245
267
|
|
|
246
268
|
const UNRENDERED_NEXT_ACTION =
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { access, copyFile, rm } from 'node:fs/promises';
|
|
1
|
+
import { access, copyFile, mkdir, mkdtemp, rm } from 'node:fs/promises';
|
|
2
2
|
import { constants as fsConstants } from 'node:fs';
|
|
3
|
-
import { basename, dirname,
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
4
|
import { callMicrosoftOffice } from '../com/com-adapter.mjs';
|
|
5
5
|
import { closeSession } from '../core/office-actions.mjs';
|
|
6
6
|
import { emptyOfficeDesignState, officeSessionForDocument, releaseOfficeSession } from '../core/office-core.mjs';
|
|
@@ -54,10 +54,16 @@ export function reusableAuthoredSession(target, mode = 'auto') {
|
|
|
54
54
|
return reusable ? existing : null;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
export function stagingTarget(target) {
|
|
60
|
-
|
|
57
|
+
// Each call owns a directory on the target's filesystem. A failed author must
|
|
58
|
+
// never consume an existing neighbor or another call's staged document.
|
|
59
|
+
export async function stagingTarget(target) {
|
|
60
|
+
await mkdir(dirname(target), { recursive: true });
|
|
61
|
+
const directory = await mkdtemp(join(dirname(target), '.mixdog-authoring-'));
|
|
62
|
+
return join(directory, basename(target));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function releaseStagingTarget(staging) {
|
|
66
|
+
await rm(dirname(staging), { recursive: true, force: true });
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export async function swapAuthoredDocument(session, source, signal, { callOffice = callMicrosoftOffice } = {}) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The HTML authoring path, second half: the measurement pptx-html-measure.mjs
|
|
2
2
|
// read from the browser becomes a deck of native PowerPoint objects — text
|
|
3
3
|
// boxes that keep the browser's line breaks, shapes, lines, tables, charts,
|
|
4
|
-
// and pictures — on the 13.333 × 7.5 in wide layout.
|
|
4
|
+
// and pictures — on the 13.333 × 7.5 in wide layout, or the sheet the measure names.
|
|
5
5
|
import { readFile } from 'node:fs/promises';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { createRequire } from 'node:module';
|
|
@@ -24,9 +24,23 @@ const MIDDLE_LIFT = 0.09; // em a middle-anchored single line sits above the bro
|
|
|
24
24
|
const middleBaseline = (spacing) => 1.02 * spacing - 0.09; // the same, for a middle-anchored block
|
|
25
25
|
const EDGE = 24; // px: text slack never carries a box past this margin
|
|
26
26
|
|
|
27
|
+
// The deck canvas lands on the 13.333 in wide layout; a measured sheet (a PDF's designed pages) carries its own inches.
|
|
28
|
+
const WIDE_INCHES = 13.333;
|
|
29
|
+
const pageInches = (measure) => ({
|
|
30
|
+
width: measure.inchWidth || WIDE_INCHES,
|
|
31
|
+
height: measure.inchHeight || (WIDE_INCHES * (measure.height || 1080)) / measure.width,
|
|
32
|
+
});
|
|
33
|
+
|
|
27
34
|
function units(measure) {
|
|
28
|
-
const
|
|
29
|
-
|
|
35
|
+
const inches = pageInches(measure).width;
|
|
36
|
+
const K = inches / measure.width; // inches per CSS px
|
|
37
|
+
return {
|
|
38
|
+
K,
|
|
39
|
+
PT: 0.5 * (1920 / measure.width) * (inches / WIDE_INCHES),
|
|
40
|
+
inch: (v) => v * K,
|
|
41
|
+
W: measure.width,
|
|
42
|
+
H: measure.height || 1080,
|
|
43
|
+
};
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
// The page clips what runs past it (section.slide is overflow: hidden); PowerPoint would keep the part past
|
|
@@ -586,7 +600,12 @@ export async function buildPptxFromMeasure(measure, output) {
|
|
|
586
600
|
const PptxGenJS = require('pptxgenjs');
|
|
587
601
|
const sharp = require('sharp');
|
|
588
602
|
const pres = new PptxGenJS();
|
|
589
|
-
|
|
603
|
+
const page = pageInches(measure);
|
|
604
|
+
if (page.width === WIDE_INCHES && Math.abs(page.height - 7.5) < 0.01) pres.layout = 'LAYOUT_WIDE';
|
|
605
|
+
else {
|
|
606
|
+
pres.defineLayout({ name: 'MIXDOG_SHEET', width: page.width, height: page.height });
|
|
607
|
+
pres.layout = 'MIXDOG_SHEET';
|
|
608
|
+
}
|
|
590
609
|
const u = units(measure);
|
|
591
610
|
for (const measured of measure.slides) {
|
|
592
611
|
const slide = pres.addSlide();
|