@file-viewer/renderer-iwork 0.0.2 → 2.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.
package/dist/iwork.js ADDED
@@ -0,0 +1,368 @@
1
+ import { registerFileViewerZoomProvider, resolveFileViewerFitScale, unregisterFileViewerZoomProvider, } from '@file-viewer/core';
2
+ import { parseIworkWithWorker } from './workerClient.js';
3
+ const MIN_SCALE = 0.25;
4
+ const MAX_SCALE = 4;
5
+ const ZOOM_STEP = 0.1;
6
+ const styleText = `
7
+ .iwork-viewer{width:100%;height:100%;display:grid;grid-template-columns:minmax(190px,250px) minmax(0,1fr);overflow:hidden;background:var(--file-viewer-render-surface-background,#e8edf2);color:#172033;font-family:Aptos,'Helvetica Neue','PingFang SC',sans-serif}.iwork-nav{min-height:0;overflow:auto;padding:14px 10px;border-right:1px solid rgba(15,23,42,.1);background:rgba(255,255,255,.96)}.iwork-nav button{display:block;width:100%;margin:0 0 5px;padding:9px 10px;border:0;border-radius:8px;background:transparent;color:#475569;text-align:left;cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.iwork-nav button.active,.iwork-nav button:hover{background:#e8f4ff;color:#0369a1}.iwork-stage{min-width:0;min-height:0;overflow:auto;padding:24px;box-sizing:border-box}.iwork-scene-wrap{position:relative;margin:0 auto 24px}.iwork-scene{position:absolute;top:0;left:0;overflow:hidden;background:#fff;box-shadow:0 18px 48px rgba(15,23,42,.15);transform-origin:top left;color:#172033}.iwork-preview{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#fff}.iwork-object{position:absolute;box-sizing:border-box}.iwork-object-shape{border-radius:14px;background:#111827;color:#fff;display:grid;place-items:center}.iwork-object-chart{overflow:visible}.iwork-object-image{object-fit:fill}.iwork-text-layer{position:absolute;inset:0;pointer-events:none}.iwork-text{position:absolute;overflow:hidden;white-space:pre-wrap;word-break:break-word;line-height:1.15;box-sizing:border-box;pointer-events:auto}.iwork-table{position:absolute;border-collapse:collapse;table-layout:fixed;background:#fff;font-size:14px}.iwork-table td{min-width:0;padding:0 6px;border:1px solid #aaa;overflow:hidden;white-space:pre-wrap;vertical-align:middle}.iwork-table .iwork-table-header-row{font-weight:700;border-bottom-color:#333}.iwork-table .iwork-table-header-column{font-weight:700;border-right-color:#333}.iwork-table .iwork-table-number{text-align:right}.iwork-notes{position:absolute;z-index:1000000;left:40px;right:40px;bottom:20px;padding:10px 14px;border-radius:7px;background:rgba(248,250,252,.95);color:#475569;font-size:12px}.iwork-empty{padding:48px;color:#64748b;text-align:center}
8
+ [data-viewer-theme='dark'] .iwork-viewer{background:var(--file-viewer-render-surface-background,#0d1117)}[data-viewer-theme='dark'] .iwork-nav{background:#161b22;border-color:#30363d;color:#e6edf3}.iwork-scene{color-scheme:light}
9
+ @media(max-width:720px){.iwork-viewer{grid-template-columns:1fr}.iwork-nav{display:flex;min-height:auto;max-height:96px;overflow:auto;border-right:0;border-bottom:1px solid #d8dee6}.iwork-nav button{flex:0 0 auto;width:auto}.iwork-stage{padding:10px}}
10
+ @media print{.iwork-nav{display:none}.iwork-viewer{display:block;height:auto}.iwork-stage{overflow:visible;padding:0}.iwork-scene-wrap{break-after:page;margin:0}.iwork-scene{box-shadow:none}}
11
+ `;
12
+ const clamp = (value) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, Math.round(value * 100) / 100));
13
+ const createPreviewUrl = (model) => model.preview
14
+ ? URL.createObjectURL(new Blob([Uint8Array.from(model.preview.bytes)], { type: model.preview.mimeType }))
15
+ : '';
16
+ const bytesToDataUrl = ({ bytes, mimeType }) => {
17
+ let binary = '';
18
+ for (let offset = 0; offset < bytes.length; offset += 0x8000) {
19
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
20
+ }
21
+ return `data:${mimeType};base64,${btoa(binary)}`;
22
+ };
23
+ const SVG_NS = 'http://www.w3.org/2000/svg';
24
+ const appendSvg = (parent, tag, attributes) => {
25
+ const element = document.createElementNS(SVG_NS, tag);
26
+ Object.entries(attributes).forEach(([name, value]) => element.setAttribute(name, String(value)));
27
+ parent.appendChild(element);
28
+ return element;
29
+ };
30
+ const renderChart = (object, kind) => {
31
+ var _a;
32
+ const svg = document.createElementNS(SVG_NS, 'svg');
33
+ svg.setAttribute('viewBox', `0 0 ${object.width} ${object.height}`);
34
+ svg.setAttribute('role', 'img');
35
+ svg.setAttribute('aria-label', object.chart ? 'Static Apple chart' : 'Chart preview unavailable');
36
+ if (!((_a = object.chart) === null || _a === void 0 ? void 0 : _a.series.length) || !object.chart.categories.length)
37
+ return svg;
38
+ const values = object.chart.series.flatMap(series => series.values).filter(Number.isFinite);
39
+ const maximum = Math.max(25, Math.ceil(Math.max(...values, 0) / 25) * 25);
40
+ const plotHeight = object.height;
41
+ const colors = ['#0b9fe8', '#58d52f', '#f5a623', '#9b59b6', '#ef4444', '#14b8a6'];
42
+ for (let tick = 0; tick <= 4; tick += 1) {
43
+ const y = plotHeight - plotHeight * tick / 4;
44
+ appendSvg(svg, 'line', { x1: 0, y1: y, x2: object.width, y2: y, stroke: tick === 0 ? '#111' : '#b7b7b7', 'stroke-width': tick === 0 ? 1.2 : 0.6 });
45
+ const label = appendSvg(svg, 'text', { x: -12, y: y + 4, fill: '#111', 'font-size': 10, 'text-anchor': 'end', 'font-family': 'Helvetica Neue, sans-serif' });
46
+ label.textContent = String(Math.round(maximum * tick / 4));
47
+ }
48
+ const groupWidth = object.width / object.chart.categories.length;
49
+ const usableWidth = groupWidth * 0.82;
50
+ const barWidth = usableWidth / object.chart.series.length;
51
+ object.chart.categories.forEach((category, categoryIndex) => {
52
+ if (object.chart.type === 'bar') {
53
+ object.chart.series.forEach((series, seriesIndex) => {
54
+ const value = Number(series.values[categoryIndex] || 0);
55
+ const height = Math.max(0, Math.min(plotHeight, value / maximum * plotHeight));
56
+ appendSvg(svg, 'rect', {
57
+ x: categoryIndex * groupWidth + (groupWidth - usableWidth) / 2 + seriesIndex * barWidth,
58
+ y: plotHeight - height,
59
+ width: Math.max(1, barWidth - 2),
60
+ height,
61
+ fill: colors[seriesIndex % colors.length],
62
+ });
63
+ });
64
+ }
65
+ if (object.chart.type === 'line') {
66
+ const label = appendSvg(svg, 'text', { x: categoryIndex * groupWidth + groupWidth / 2, y: plotHeight + 22, fill: '#111', 'font-size': 10, 'text-anchor': 'middle', 'font-family': 'Helvetica Neue, sans-serif' });
67
+ label.textContent = category;
68
+ }
69
+ });
70
+ if (object.chart.type === 'line') {
71
+ object.chart.series.forEach((series, seriesIndex) => {
72
+ const points = series.values.map((value, index) => `${index * groupWidth + groupWidth / 2},${plotHeight - Math.max(0, Math.min(plotHeight, Number(value || 0) / maximum * plotHeight))}`).join(' ');
73
+ appendSvg(svg, 'polyline', { points, fill: 'none', stroke: colors[seriesIndex % colors.length], 'stroke-width': 3 });
74
+ });
75
+ }
76
+ if (kind === 'pages') {
77
+ const legendY = -39;
78
+ object.chart.series.forEach((series, index) => {
79
+ const x = object.width / 2 - 100 + index * 130;
80
+ appendSvg(svg, 'line', { x1: x, y1: legendY, x2: x + 12, y2: legendY, stroke: colors[index % colors.length], 'stroke-width': 2 });
81
+ const label = appendSvg(svg, 'text', { x: x + 20, y: legendY + 4, fill: '#111', 'font-size': 10, 'font-family': 'Helvetica Neue, sans-serif' });
82
+ label.textContent = series.name;
83
+ });
84
+ }
85
+ return svg;
86
+ };
87
+ const renderScene = (scene, model, scale, previewUrl, showPreview, objectUrls) => {
88
+ const wrap = document.createElement('section');
89
+ wrap.className = 'iwork-scene-wrap';
90
+ wrap.dataset.viewerAnchorId = scene.id;
91
+ wrap.dataset.sceneId = scene.id;
92
+ Object.assign(wrap.style, { width: `${scene.width * scale}px`, height: `${scene.height * scale}px` });
93
+ const surface = document.createElement('article');
94
+ surface.className = 'iwork-scene';
95
+ surface.dataset.pageIndex = scene.id.replace(/\D+/g, '') || '0';
96
+ Object.assign(surface.style, { width: `${scene.width}px`, height: `${scene.height}px`, transform: `scale(${scale})` });
97
+ if (showPreview && previewUrl) {
98
+ const image = document.createElement('img');
99
+ image.className = 'iwork-preview';
100
+ image.src = previewUrl;
101
+ image.alt = `${scene.name} embedded preview`;
102
+ surface.appendChild(image);
103
+ }
104
+ scene.objects.filter(object => object.kind !== 'table').forEach(object => {
105
+ var _a;
106
+ const element = object.kind === 'image' && object.bytes && object.mimeType
107
+ ? document.createElement('img')
108
+ : object.kind === 'chart'
109
+ ? renderChart(object, model.kind)
110
+ : document.createElement('div');
111
+ element.setAttribute('class', `iwork-object iwork-object-${object.kind}`);
112
+ Object.assign(element.style, { left: `${object.x}px`, top: `${object.y}px`, width: `${object.width}px`, height: `${object.height}px`, transform: object.angle ? `rotate(${object.angle}rad)` : '', zIndex: String((_a = object.zIndex) !== null && _a !== void 0 ? _a : 1) });
113
+ if (element instanceof HTMLImageElement) {
114
+ const objectUrl = URL.createObjectURL(new Blob([Uint8Array.from(object.bytes)], { type: object.mimeType }));
115
+ objectUrls.set(objectUrl, { bytes: object.bytes, mimeType: object.mimeType });
116
+ element.src = objectUrl;
117
+ element.alt = object.text || 'Embedded Keynote image';
118
+ }
119
+ else if (object.text)
120
+ element.textContent = object.text;
121
+ surface.appendChild(element);
122
+ });
123
+ const textLayer = document.createElement('div');
124
+ textLayer.className = 'iwork-text-layer';
125
+ scene.blocks.forEach(block => {
126
+ var _a, _b;
127
+ const element = document.createElement('div');
128
+ element.className = 'iwork-text';
129
+ Object.assign(element.style, {
130
+ left: `${block.x}px`, top: `${block.y}px`, width: `${block.width}px`, height: `${block.height}px`,
131
+ fontSize: `${block.fontSize || 16}px`, fontFamily: block.fontFamily || 'inherit', color: block.color || 'inherit',
132
+ fontWeight: block.bold ? '700' : '400', fontStyle: block.italic ? 'italic' : 'normal', textAlign: block.align || 'left',
133
+ letterSpacing: block.letterSpacing == null ? 'normal' : `${block.letterSpacing}em`,
134
+ lineHeight: block.lineHeight == null ? '1.15' : String(block.lineHeight),
135
+ display: block.verticalAlign ? 'flex' : 'block',
136
+ flexDirection: block.verticalAlign ? 'column' : '',
137
+ justifyContent: block.verticalAlign === 'middle' ? 'center' : block.verticalAlign === 'bottom' ? 'flex-end' : 'flex-start',
138
+ padding: block.padding ? `${block.padding.top}px ${block.padding.right}px ${block.padding.bottom}px ${block.padding.left}px` : '0',
139
+ zIndex: String((_a = block.zIndex) !== null && _a !== void 0 ? _a : 2),
140
+ });
141
+ if ((_b = block.paragraphs) === null || _b === void 0 ? void 0 : _b.length) {
142
+ block.paragraphs.forEach(paragraph => {
143
+ const paragraphElement = document.createElement('div');
144
+ Object.assign(paragraphElement.style, {
145
+ display: paragraph.bullet ? 'grid' : 'block',
146
+ gridTemplateColumns: paragraph.bullet ? '42px minmax(0, 1fr)' : '',
147
+ marginTop: `${paragraph.spaceBefore || 0}px`,
148
+ marginBottom: `${paragraph.spaceAfter || 0}px`,
149
+ color: paragraph.color || 'inherit',
150
+ fontWeight: paragraph.bold ? '700' : 'inherit',
151
+ fontStyle: paragraph.italic ? 'italic' : 'inherit',
152
+ });
153
+ if (paragraph.bullet) {
154
+ const bullet = document.createElement('span');
155
+ bullet.textContent = '•';
156
+ paragraphElement.appendChild(bullet);
157
+ }
158
+ const content = document.createElement('span');
159
+ paragraph.runs.forEach(run => {
160
+ const span = document.createElement('span');
161
+ span.textContent = run.text;
162
+ if (run.color)
163
+ span.style.color = run.color;
164
+ if (run.bold)
165
+ span.style.fontWeight = '700';
166
+ if (run.italic)
167
+ span.style.fontStyle = 'italic';
168
+ content.appendChild(span);
169
+ });
170
+ paragraphElement.appendChild(content);
171
+ element.appendChild(paragraphElement);
172
+ });
173
+ }
174
+ else {
175
+ element.textContent = block.text;
176
+ }
177
+ textLayer.appendChild(element);
178
+ });
179
+ surface.appendChild(textLayer);
180
+ scene.tables.forEach(table => {
181
+ var _a, _b;
182
+ const element = document.createElement('table');
183
+ element.className = 'iwork-table';
184
+ Object.assign(element.style, { left: `${table.x}px`, top: `${table.y}px`, width: table.width ? `${table.width}px` : '', height: table.height ? `${table.height}px` : '', zIndex: String((_a = table.zIndex) !== null && _a !== void 0 ? _a : 3) });
185
+ if (table.fontSize)
186
+ element.style.fontSize = `${table.fontSize}px`;
187
+ if (table.fontFamily)
188
+ element.style.fontFamily = table.fontFamily;
189
+ if ((_b = table.columnWidths) === null || _b === void 0 ? void 0 : _b.length) {
190
+ const columns = document.createElement('colgroup');
191
+ table.columnWidths.forEach(width => {
192
+ const column = document.createElement('col');
193
+ column.style.width = `${width}px`;
194
+ columns.appendChild(column);
195
+ });
196
+ element.appendChild(columns);
197
+ }
198
+ const mergeOrigins = new Map((table.merges || []).map(merge => [`${merge.row}:${merge.col}`, merge]));
199
+ const covered = new Set();
200
+ for (const merge of table.merges || []) {
201
+ for (let row = merge.row; row < merge.row + merge.rowspan; row += 1) {
202
+ for (let col = merge.col; col < merge.col + merge.colspan; col += 1) {
203
+ if (row !== merge.row || col !== merge.col)
204
+ covered.add(`${row}:${col}`);
205
+ }
206
+ }
207
+ }
208
+ table.rows.forEach((row, rowIndex) => {
209
+ var _a;
210
+ const tr = document.createElement('tr');
211
+ if ((_a = table.rowHeights) === null || _a === void 0 ? void 0 : _a[rowIndex])
212
+ tr.style.height = `${table.rowHeights[rowIndex]}px`;
213
+ row.forEach((value, colIndex) => {
214
+ if (covered.has(`${rowIndex}:${colIndex}`))
215
+ return;
216
+ const cell = document.createElement('td');
217
+ cell.textContent = value;
218
+ if (table.borderColor)
219
+ cell.style.borderColor = table.borderColor;
220
+ if (rowIndex < (table.headerRows || 0)) {
221
+ cell.classList.add('iwork-table-header-row');
222
+ if (table.headerRowBackground)
223
+ cell.style.background = table.headerRowBackground;
224
+ }
225
+ if (colIndex < (table.headerColumns || 0) && rowIndex >= (table.headerRows || 0)) {
226
+ cell.classList.add('iwork-table-header-column');
227
+ if (table.headerColumnBackground)
228
+ cell.style.background = table.headerColumnBackground;
229
+ }
230
+ if (value !== '' && Number.isFinite(Number(value)) && rowIndex >= (table.headerRows || 0))
231
+ cell.classList.add('iwork-table-number');
232
+ const merge = mergeOrigins.get(`${rowIndex}:${colIndex}`);
233
+ if (merge) {
234
+ cell.rowSpan = merge.rowspan;
235
+ cell.colSpan = merge.colspan;
236
+ }
237
+ tr.appendChild(cell);
238
+ });
239
+ element.appendChild(tr);
240
+ });
241
+ surface.appendChild(element);
242
+ });
243
+ if (scene.notes.length) {
244
+ const notes = document.createElement('aside');
245
+ notes.className = 'iwork-notes';
246
+ notes.textContent = scene.notes.join('\n');
247
+ surface.appendChild(notes);
248
+ }
249
+ wrap.appendChild(surface);
250
+ return wrap;
251
+ };
252
+ export default async function renderIwork(buffer, target, type, context) {
253
+ var _a, _b, _c, _d, _e, _f;
254
+ const model = await parseIworkWithWorker(buffer.slice(0), type, (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.iwork, context === null || context === void 0 ? void 0 : context.signal);
255
+ let scale = 1;
256
+ let activeScene = ((_b = model.scenes[0]) === null || _b === void 0 ? void 0 : _b.id) || '';
257
+ const previewUrl = createPreviewUrl(model);
258
+ const showEmbeddedPreview = ((_d = (_c = context === null || context === void 0 ? void 0 : context.options) === null || _c === void 0 ? void 0 : _c.iwork) === null || _d === void 0 ? void 0 : _d.embeddedPreview) === 'fallback' && model.limitedPreview;
259
+ const style = document.createElement('style');
260
+ style.textContent = styleText;
261
+ const root = document.createElement('div');
262
+ root.className = 'iwork-viewer';
263
+ const nav = document.createElement('nav');
264
+ nav.className = 'iwork-nav';
265
+ const stage = document.createElement('main');
266
+ stage.className = 'iwork-stage';
267
+ let objectUrls = new Map();
268
+ const render = () => {
269
+ const showAll = model.kind === 'pages';
270
+ const scenes = showAll ? model.scenes : model.scenes.filter(scene => scene.id === activeScene);
271
+ const nextObjectUrls = new Map();
272
+ const nextScenes = scenes.map(scene => renderScene(scene, model, scale, previewUrl, showEmbeddedPreview, nextObjectUrls));
273
+ stage.replaceChildren(...nextScenes);
274
+ objectUrls.forEach((_asset, url) => URL.revokeObjectURL(url));
275
+ objectUrls = nextObjectUrls;
276
+ nav.querySelectorAll('button').forEach(button => button.classList.toggle('active', button.dataset.sceneId === activeScene));
277
+ };
278
+ model.scenes.forEach(scene => {
279
+ const button = document.createElement('button');
280
+ button.type = 'button';
281
+ button.dataset.sceneId = scene.id;
282
+ button.textContent = scene.name;
283
+ button.addEventListener('click', () => {
284
+ var _a;
285
+ activeScene = scene.id;
286
+ if (model.kind === 'pages')
287
+ (_a = stage.querySelector(`[data-scene-id="${CSS.escape(scene.id)}"]`)) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: 'start' });
288
+ else
289
+ render();
290
+ });
291
+ nav.appendChild(button);
292
+ });
293
+ root.append(nav, stage);
294
+ target.replaceChildren(style, root);
295
+ render();
296
+ const getState = () => ({
297
+ scale,
298
+ label: `${Math.round(scale * 100)}%`,
299
+ canZoomIn: scale < MAX_SCALE,
300
+ canZoomOut: scale > MIN_SCALE,
301
+ canReset: scale !== 1,
302
+ minScale: MIN_SCALE,
303
+ maxScale: MAX_SCALE,
304
+ });
305
+ const setScale = (value) => { scale = clamp(value); render(); return getState(); };
306
+ const fit = (request) => {
307
+ const scene = model.scenes.find(item => item.id === activeScene) || model.scenes[0];
308
+ if (!scene)
309
+ return { applied: false, mode: request.mode, resize: request.resize, source: request.source, reason: 'unmeasurable', provider: 'zoom' };
310
+ const next = resolveFileViewerFitScale({
311
+ mode: request.mode === 'auto' ? 'width' : request.mode,
312
+ viewportWidth: request.viewportWidth || stage.clientWidth,
313
+ viewportHeight: request.viewportHeight || stage.clientHeight,
314
+ contentWidth: scene.width,
315
+ contentHeight: scene.height,
316
+ currentScale: scale,
317
+ minScale: MIN_SCALE,
318
+ maxScale: MAX_SCALE,
319
+ });
320
+ if (!next)
321
+ return { applied: false, mode: request.mode, resize: request.resize, source: request.source, reason: 'unmeasurable', provider: 'zoom' };
322
+ setScale(next);
323
+ return { applied: true, mode: request.mode, resize: request.resize, source: request.source, scale, provider: 'zoom' };
324
+ };
325
+ registerFileViewerZoomProvider(target, {
326
+ zoomIn: () => setScale(scale + ZOOM_STEP),
327
+ zoomOut: () => setScale(scale - ZOOM_STEP),
328
+ resetZoom: () => setScale(1),
329
+ setZoom: setScale,
330
+ fit,
331
+ getState,
332
+ });
333
+ (_e = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _e === void 0 ? void 0 : _e.call(context, {
334
+ includeDocumentStyles: false,
335
+ getPrintMaskPages: () => Array.from(stage.querySelectorAll('.iwork-scene')),
336
+ printStyle: styleText,
337
+ toHtml: () => {
338
+ const clone = root.cloneNode(true);
339
+ clone.querySelectorAll('img').forEach(image => {
340
+ const asset = objectUrls.get(image.src)
341
+ || (image.src === previewUrl && model.preview
342
+ ? { bytes: model.preview.bytes, mimeType: model.preview.mimeType }
343
+ : undefined);
344
+ if (asset)
345
+ image.src = bytesToDataUrl(asset);
346
+ });
347
+ return clone.outerHTML;
348
+ },
349
+ });
350
+ (_f = context === null || context === void 0 ? void 0 : context.registerThumbnailAdapter) === null || _f === void 0 ? void 0 : _f.call(context, {
351
+ captureSource: showEmbeddedPreview ? 'embedded' : 'rendered',
352
+ capture: showEmbeddedPreview && model.preview ? () => new Blob([Uint8Array.from(model.preview.bytes)], { type: model.preview.mimeType }) : undefined,
353
+ getTarget: () => stage.querySelector('.iwork-scene') || stage,
354
+ });
355
+ return {
356
+ $el: target,
357
+ unmount() {
358
+ var _a, _b;
359
+ unregisterFileViewerZoomProvider(target);
360
+ (_a = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _a === void 0 ? void 0 : _a.call(context, null);
361
+ (_b = context === null || context === void 0 ? void 0 : context.registerThumbnailAdapter) === null || _b === void 0 ? void 0 : _b.call(context, null);
362
+ if (previewUrl)
363
+ URL.revokeObjectURL(previewUrl);
364
+ objectUrls.forEach((_asset, url) => URL.revokeObjectURL(url));
365
+ target.replaceChildren();
366
+ },
367
+ };
368
+ }
@@ -0,0 +1 @@
1
+ export { inspectIworkContainer, parseIworkDocument, } from './parser.js';