@pollium/vyva-convert 0.0.0-stage → 0.2.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/README.md +100 -2
- package/dist/attachment.d.ts +3 -0
- package/dist/attachment.js +8 -0
- package/dist/clean-content.d.ts +6 -0
- package/dist/clean-content.js +63 -0
- package/dist/clean-html.d.ts +3 -0
- package/dist/clean-html.js +161 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +11 -0
- package/dist/export/bundle.d.ts +34 -0
- package/dist/export/bundle.js +41 -0
- package/dist/export/docx.d.ts +5 -0
- package/dist/export/docx.js +382 -0
- package/dist/export/fonts.d.ts +16 -0
- package/dist/export/fonts.js +37 -0
- package/dist/export/html-blocks.d.ts +58 -0
- package/dist/export/html-blocks.js +170 -0
- package/dist/export/html.d.ts +8 -0
- package/dist/export/html.js +31 -0
- package/dist/export/image-size.d.ts +8 -0
- package/dist/export/image-size.js +83 -0
- package/dist/export/pdf.d.ts +28 -0
- package/dist/export/pdf.js +84 -0
- package/dist/export/pptx.d.ts +5 -0
- package/dist/export/pptx.js +334 -0
- package/dist/export.d.ts +19 -0
- package/dist/export.js +39 -0
- package/dist/font-widths.d.ts +8 -0
- package/dist/font-widths.js +3430 -0
- package/dist/gotenberg.d.ts +8 -0
- package/dist/gotenberg.js +39 -0
- package/dist/import/documents.d.ts +12 -0
- package/dist/import/documents.js +44 -0
- package/dist/import/html-to-blocks.d.ts +6 -0
- package/dist/import/html-to-blocks.js +58 -0
- package/dist/import/images.d.ts +10 -0
- package/dist/import/images.js +23 -0
- package/dist/import/pdf-lines.d.ts +4 -0
- package/dist/import/pdf-lines.js +206 -0
- package/dist/import/pdf-slides.d.ts +7 -0
- package/dist/import/pdf-slides.js +21 -0
- package/dist/import/pptx-geometry.d.ts +11 -0
- package/dist/import/pptx-geometry.js +74 -0
- package/dist/import/pptx-package.d.ts +22 -0
- package/dist/import/pptx-package.js +61 -0
- package/dist/import/pptx-placeholders.d.ts +11 -0
- package/dist/import/pptx-placeholders.js +44 -0
- package/dist/import/pptx-slide.d.ts +19 -0
- package/dist/import/pptx-slide.js +348 -0
- package/dist/import/pptx-theme.d.ts +19 -0
- package/dist/import/pptx-theme.js +160 -0
- package/dist/import/pptx-xml.d.ts +23 -0
- package/dist/import/pptx-xml.js +27 -0
- package/dist/import/pptx.d.ts +9 -0
- package/dist/import/pptx.js +126 -0
- package/dist/import/titles.d.ts +3 -0
- package/dist/import/titles.js +6 -0
- package/dist/import.d.ts +16 -0
- package/dist/import.js +68 -0
- package/dist/libreoffice.d.ts +8 -0
- package/dist/libreoffice.js +39 -0
- package/dist/text-measure.d.ts +5 -0
- package/dist/text-measure.js +102 -0
- package/package.json +50 -3
package/README.md
CHANGED
|
@@ -1,3 +1,101 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @pollium/vyva-convert
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Server-side tools for Vyva content. It cleans what clients send, imports Word, PowerPoint, PDF,
|
|
4
|
+
Markdown and text files, and exports to PDF, Word, PowerPoint and HTML. It runs in Node.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install @pollium/vyva-convert
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Cleaning what clients send
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { cleanContent } from '@pollium/vyva-convert/clean-content';
|
|
14
|
+
|
|
15
|
+
const content = cleanContent(body.content);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The cleaner keeps the editor's formatting. It removes scripts, event handlers, unsafe links and any
|
|
19
|
+
image that does not point at a file id. Ids that repeat or are not valid get new ones. Each text box is
|
|
20
|
+
saved as tall as its text. For a single HTML fragment, use `cleanHtml` from
|
|
21
|
+
`@pollium/vyva-convert/clean-html`.
|
|
22
|
+
|
|
23
|
+
## Exporting
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { exportContent, isExportFormat } from '@pollium/vyva-convert/export';
|
|
27
|
+
import { gotenberg } from '@pollium/vyva-convert/gotenberg';
|
|
28
|
+
import { contentDisposition } from '@pollium/vyva-convert/attachment';
|
|
29
|
+
|
|
30
|
+
if(!isExportFormat(request.params.format)) return reply.code(400).send();
|
|
31
|
+
|
|
32
|
+
const file = await exportContent(content, request.params.format, {
|
|
33
|
+
title: 'Subnetting guide',
|
|
34
|
+
language: 'en',
|
|
35
|
+
readImage: async (fileId) => {
|
|
36
|
+
const stored = await files.find(fileId);
|
|
37
|
+
return stored === null ? null : { data: stored.bytes, contentType: stored.contentType };
|
|
38
|
+
},
|
|
39
|
+
pdfEngine: gotenberg({ url: 'http://gotenberg:3000' })
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
reply.header('content-disposition', contentDisposition('Subnetting guide', file.extension)).type(file.contentType).send(file.data);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| Content | Formats |
|
|
46
|
+
|---|---|
|
|
47
|
+
| Document | `pdf`, `docx`, `html` |
|
|
48
|
+
| Deck | `pptx`, `pdf` |
|
|
49
|
+
|
|
50
|
+
- `readImage` is called once for every image the content uses. Return `null` and the export goes
|
|
51
|
+
on without that image.
|
|
52
|
+
- The PDF fonts and images travel with the page, so the PDF engine needs no network access.
|
|
53
|
+
- `pdfEngine` is only needed for `pdf`. It is a function that takes a `PdfJob` (the HTML, the files
|
|
54
|
+
it refers to by name, a footer with Chromium's `pageNumber` and `totalPages` classes, and the
|
|
55
|
+
page size in inches) and returns the PDF. Use `gotenberg` or write your own with Puppeteer or
|
|
56
|
+
Playwright.
|
|
57
|
+
- The `html` export is one self-contained file, with its images and fonts inline.
|
|
58
|
+
|
|
59
|
+
## Importing
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { importFile } from '@pollium/vyva-convert/import';
|
|
63
|
+
|
|
64
|
+
const { title, content } = await importFile(upload.bytes, upload.filename, {
|
|
65
|
+
saveImage: async ({ name, data, contentType }) => (await files.store(name, data, contentType)).id
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- The file is recognized by its content, not its name. Word and PowerPoint files are told apart
|
|
70
|
+
by what their zip holds, and PDF by its signature. Only Markdown (`.md`) and text (`.txt`) are
|
|
71
|
+
recognized by their extension, and only when they are valid UTF-8.
|
|
72
|
+
- Word, PDF, Markdown and text files become documents, and PowerPoint files become decks. A deck
|
|
73
|
+
keeps its theme colors and the font most of its text uses. What its slide master draws comes in
|
|
74
|
+
locked, behind the slide's own elements, and freeform shapes come in as polygons.
|
|
75
|
+
- With `pdfSlides`, a PDF whose pages are landscape becomes a deck instead: each page a slide, with
|
|
76
|
+
its pictures and shapes where they were, and the loose lines a PDF keeps joined back into
|
|
77
|
+
paragraphs and lists. A portrait PDF stays a document. `pdfSlides` takes the PDF and returns it as
|
|
78
|
+
a PowerPoint file; `libreOffice()` from `@pollium/vyva-convert/libreoffice` does that with the
|
|
79
|
+
`soffice` on the machine (`command` changes the path), one conversion per call:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { libreOffice } from '@pollium/vyva-convert/libreoffice';
|
|
83
|
+
|
|
84
|
+
await importFile(upload.bytes, upload.filename, { saveImage, pdfSlides: libreOffice() });
|
|
85
|
+
```
|
|
86
|
+
- `saveImage` stores each image and returns its file id, which must be a UUID. Return `null` to
|
|
87
|
+
leave the image out. If it throws, the import fails with your error.
|
|
88
|
+
- Files over 25 MB (`maxBytes` changes the limit) and files it cannot read are refused.
|
|
89
|
+
|
|
90
|
+
## Errors
|
|
91
|
+
|
|
92
|
+
Expected failures are a `ConvertError` from `@pollium/vyva-convert/errors`, with a `code`. Anything else is
|
|
93
|
+
a bug or a broken file, so log it.
|
|
94
|
+
|
|
95
|
+
| Code | Meaning |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `unsupported` | The file is not one Vyva imports, or the content cannot be exported in that format |
|
|
98
|
+
| `too-large` | The file, or what it unzips to, is over the limit |
|
|
99
|
+
| `empty` | The file has nothing to import |
|
|
100
|
+
| `pdf-failed` | The PDF engine could not be reached or answered with an error |
|
|
101
|
+
| `slides-failed` | `pdfSlides` could not turn a PDF into slides |
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/attachment.ts
|
|
2
|
+
const ASCII_FALLBACK = "download";
|
|
3
|
+
const NAME_LIMIT = 80;
|
|
4
|
+
const asciiSlug = (title) => title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, NAME_LIMIT) || ASCII_FALLBACK;
|
|
5
|
+
const readable = (title) => title.replace(/[\\/:*?"<>|\r\n]+/g, " ").replace(/\s+/g, " ").trim().slice(0, NAME_LIMIT) || ASCII_FALLBACK;
|
|
6
|
+
const contentDisposition = (title, extension) => `attachment; filename="${asciiSlug(title)}.${extension}"; filename*=UTF-8''${encodeURIComponent(`${readable(title)}.${extension}`)}`;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { contentDisposition };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Content, DeckContent, DocumentContent } from "@pollium/vyva-core/content";
|
|
2
|
+
//#region src/clean-content.d.ts
|
|
3
|
+
export declare const cleanDocument: (content: DocumentContent) => DocumentContent;
|
|
4
|
+
export declare const cleanDeck: (content: DeckContent) => DeckContent;
|
|
5
|
+
export declare const cleanContent: (content: Content) => Content;
|
|
6
|
+
//#endregion
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { cleanHtml } from "./clean-html.js";
|
|
2
|
+
import { textHeight } from "./text-measure.js";
|
|
3
|
+
import { TEXT_SHAPES } from "@pollium/vyva-core/content";
|
|
4
|
+
import { newId } from "@pollium/vyva-core/ids";
|
|
5
|
+
import { withTextHeight } from "@pollium/vyva-core/render";
|
|
6
|
+
//#region src/clean-content.ts
|
|
7
|
+
const ID = /^[A-Za-z0-9_-]{1,40}$/;
|
|
8
|
+
const uniqueIds = (items) => {
|
|
9
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10
|
+
return items.map((item) => {
|
|
11
|
+
const id = ID.test(item.id) && !seen.has(item.id) ? item.id : newId();
|
|
12
|
+
seen.add(id);
|
|
13
|
+
return id === item.id ? item : {
|
|
14
|
+
...item,
|
|
15
|
+
id
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
const cleanElement = (element, font) => {
|
|
20
|
+
if (element.type === "text") {
|
|
21
|
+
const html = cleanHtml(element.html);
|
|
22
|
+
const height = textHeight(html, element.style, font, element.w);
|
|
23
|
+
return Number.isFinite(height) && height > 0 ? withTextHeight({
|
|
24
|
+
...element,
|
|
25
|
+
html
|
|
26
|
+
}, height) : {
|
|
27
|
+
...element,
|
|
28
|
+
html
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (element.type !== "shape") return element;
|
|
32
|
+
if (element.points !== void 0 && element.shape !== "polygon") {
|
|
33
|
+
const { points: _points, ...plain } = element;
|
|
34
|
+
return cleanElement(plain, font);
|
|
35
|
+
}
|
|
36
|
+
if (element.text === void 0) return element;
|
|
37
|
+
const { text, ...bare } = element;
|
|
38
|
+
const html = TEXT_SHAPES.includes(element.shape) ? cleanHtml(text.html) : "";
|
|
39
|
+
return html === "" ? bare : {
|
|
40
|
+
...bare,
|
|
41
|
+
text: {
|
|
42
|
+
...text,
|
|
43
|
+
html
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
const cleanDocument = (content) => ({
|
|
48
|
+
...content,
|
|
49
|
+
blocks: uniqueIds(content.blocks).map((block) => ({
|
|
50
|
+
id: block.id,
|
|
51
|
+
html: cleanHtml(block.html)
|
|
52
|
+
})).filter((block) => block.html !== "")
|
|
53
|
+
});
|
|
54
|
+
const cleanDeck = (content) => ({
|
|
55
|
+
...content,
|
|
56
|
+
slides: uniqueIds(content.slides).map((slide) => ({
|
|
57
|
+
...slide,
|
|
58
|
+
elements: uniqueIds(slide.elements).map((element) => cleanElement(element, content.theme.font))
|
|
59
|
+
}))
|
|
60
|
+
});
|
|
61
|
+
const cleanContent = (content) => content.kind === "deck" ? cleanDeck(content) : cleanDocument(content);
|
|
62
|
+
//#endregion
|
|
63
|
+
export { cleanContent, cleanDeck, cleanDocument };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import sanitize from "sanitize-html";
|
|
2
|
+
//#region src/clean-html.ts
|
|
3
|
+
const COLOR = /^(#[0-9a-fA-F]{3,8}|rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(,\s*(0|1|0?\.\d+)\s*)?\))$/;
|
|
4
|
+
const FONT_SIZE = /^(\d{1,3}(\.\d{1,2})?)(px|pt|em|rem|%)$/;
|
|
5
|
+
const ALIGN = /^(left|right|center|justify)$/;
|
|
6
|
+
const LINE_HEIGHT = /^(\d(\.\d{1,2})?|\d{2,3}%)$/;
|
|
7
|
+
const WIDTH = /^(\d{1,4}(\.\d{1,2})?)(px|%)$/;
|
|
8
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
9
|
+
const TEXT_STYLES = {
|
|
10
|
+
color: [COLOR],
|
|
11
|
+
"background-color": [COLOR],
|
|
12
|
+
"font-size": [FONT_SIZE],
|
|
13
|
+
"text-align": [ALIGN],
|
|
14
|
+
"line-height": [LINE_HEIGHT]
|
|
15
|
+
};
|
|
16
|
+
const OPTIONS = {
|
|
17
|
+
allowedTags: [
|
|
18
|
+
"p",
|
|
19
|
+
"h1",
|
|
20
|
+
"h2",
|
|
21
|
+
"h3",
|
|
22
|
+
"h4",
|
|
23
|
+
"strong",
|
|
24
|
+
"b",
|
|
25
|
+
"em",
|
|
26
|
+
"i",
|
|
27
|
+
"u",
|
|
28
|
+
"s",
|
|
29
|
+
"mark",
|
|
30
|
+
"span",
|
|
31
|
+
"a",
|
|
32
|
+
"br",
|
|
33
|
+
"hr",
|
|
34
|
+
"ul",
|
|
35
|
+
"ol",
|
|
36
|
+
"li",
|
|
37
|
+
"blockquote",
|
|
38
|
+
"pre",
|
|
39
|
+
"code",
|
|
40
|
+
"sup",
|
|
41
|
+
"sub",
|
|
42
|
+
"table",
|
|
43
|
+
"colgroup",
|
|
44
|
+
"col",
|
|
45
|
+
"thead",
|
|
46
|
+
"tbody",
|
|
47
|
+
"tr",
|
|
48
|
+
"th",
|
|
49
|
+
"td",
|
|
50
|
+
"img",
|
|
51
|
+
"div",
|
|
52
|
+
"label",
|
|
53
|
+
"input"
|
|
54
|
+
],
|
|
55
|
+
allowedAttributes: {
|
|
56
|
+
a: [
|
|
57
|
+
"href",
|
|
58
|
+
"target",
|
|
59
|
+
"rel"
|
|
60
|
+
],
|
|
61
|
+
img: [
|
|
62
|
+
"data-file-id",
|
|
63
|
+
"alt",
|
|
64
|
+
"width",
|
|
65
|
+
"style",
|
|
66
|
+
{
|
|
67
|
+
name: "data-align",
|
|
68
|
+
multiple: false,
|
|
69
|
+
values: [
|
|
70
|
+
"left",
|
|
71
|
+
"center",
|
|
72
|
+
"right"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
ol: ["start"],
|
|
77
|
+
ul: ["data-type"],
|
|
78
|
+
li: ["data-type", "data-checked"],
|
|
79
|
+
code: ["class"],
|
|
80
|
+
pre: ["class"],
|
|
81
|
+
th: [
|
|
82
|
+
"colspan",
|
|
83
|
+
"rowspan",
|
|
84
|
+
"colwidth",
|
|
85
|
+
"style"
|
|
86
|
+
],
|
|
87
|
+
td: [
|
|
88
|
+
"colspan",
|
|
89
|
+
"rowspan",
|
|
90
|
+
"colwidth",
|
|
91
|
+
"style"
|
|
92
|
+
],
|
|
93
|
+
col: ["style"],
|
|
94
|
+
div: ["data-type", "data-tone"],
|
|
95
|
+
input: [
|
|
96
|
+
"type",
|
|
97
|
+
"checked",
|
|
98
|
+
"disabled"
|
|
99
|
+
],
|
|
100
|
+
"*": ["style"]
|
|
101
|
+
},
|
|
102
|
+
allowedClasses: {
|
|
103
|
+
code: [/^language-[a-z0-9+#-]{1,30}$/],
|
|
104
|
+
pre: [/^language-[a-z0-9+#-]{1,30}$/]
|
|
105
|
+
},
|
|
106
|
+
allowedStyles: {
|
|
107
|
+
"*": TEXT_STYLES,
|
|
108
|
+
img: { width: [WIDTH] },
|
|
109
|
+
col: { width: [WIDTH] },
|
|
110
|
+
td: {
|
|
111
|
+
...TEXT_STYLES,
|
|
112
|
+
width: [WIDTH]
|
|
113
|
+
},
|
|
114
|
+
th: {
|
|
115
|
+
...TEXT_STYLES,
|
|
116
|
+
width: [WIDTH]
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
allowedSchemes: [
|
|
120
|
+
"http",
|
|
121
|
+
"https",
|
|
122
|
+
"mailto"
|
|
123
|
+
],
|
|
124
|
+
allowedSchemesAppliedToAttributes: ["href"],
|
|
125
|
+
allowProtocolRelative: false,
|
|
126
|
+
disallowedTagsMode: "discard",
|
|
127
|
+
nonTextTags: [
|
|
128
|
+
"script",
|
|
129
|
+
"style",
|
|
130
|
+
"textarea",
|
|
131
|
+
"option",
|
|
132
|
+
"noscript",
|
|
133
|
+
"iframe",
|
|
134
|
+
"object",
|
|
135
|
+
"embed",
|
|
136
|
+
"svg",
|
|
137
|
+
"math"
|
|
138
|
+
],
|
|
139
|
+
exclusiveFilter: (frame) => frame.tag === "img" && !UUID.test(frame.attribs["data-file-id"] ?? "") || frame.tag === "input" && frame.attribs.type !== "checkbox" || frame.tag === "div" && frame.attribs["data-type"] !== void 0 && !["page-break", "callout"].includes(frame.attribs["data-type"]),
|
|
140
|
+
transformTags: {
|
|
141
|
+
a: (tagName, attribs) => ({
|
|
142
|
+
tagName,
|
|
143
|
+
attribs: {
|
|
144
|
+
...attribs,
|
|
145
|
+
target: "_blank",
|
|
146
|
+
rel: "noopener noreferrer"
|
|
147
|
+
}
|
|
148
|
+
}),
|
|
149
|
+
input: (tagName, attribs) => ({
|
|
150
|
+
tagName,
|
|
151
|
+
attribs: {
|
|
152
|
+
type: attribs.type ?? "",
|
|
153
|
+
...attribs.checked === void 0 ? {} : { checked: "checked" },
|
|
154
|
+
disabled: "disabled"
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const cleanHtml = (html) => sanitize(html, OPTIONS).trim();
|
|
160
|
+
//#endregion
|
|
161
|
+
export { cleanHtml };
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/errors.d.ts
|
|
2
|
+
export type ConvertErrorCode = 'too-large' | 'unsupported' | 'empty' | 'pdf-failed' | 'slides-failed';
|
|
3
|
+
export declare class ConvertError extends Error {
|
|
4
|
+
readonly code: ConvertErrorCode;
|
|
5
|
+
constructor(code: ConvertErrorCode, message: string, options?: ErrorOptions);
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ImageSize } from "./image-size.js";
|
|
2
|
+
import { Content, FontFamily } from "@pollium/vyva-core/content";
|
|
3
|
+
//#region src/export/bundle.d.ts
|
|
4
|
+
export interface StoredImage {
|
|
5
|
+
data: Uint8Array;
|
|
6
|
+
contentType: string;
|
|
7
|
+
}
|
|
8
|
+
export type ReadImage = (fileId: string) => Promise<StoredImage | null>;
|
|
9
|
+
export interface BundleImage {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
data: Buffer;
|
|
13
|
+
contentType: string;
|
|
14
|
+
extension: string;
|
|
15
|
+
size: ImageSize;
|
|
16
|
+
}
|
|
17
|
+
export interface ExportBundle {
|
|
18
|
+
title: string;
|
|
19
|
+
language: string;
|
|
20
|
+
images: Map<string, BundleImage>;
|
|
21
|
+
fonts: FontFamily[];
|
|
22
|
+
}
|
|
23
|
+
export interface ExportedFile {
|
|
24
|
+
data: Uint8Array;
|
|
25
|
+
contentType: string;
|
|
26
|
+
extension: string;
|
|
27
|
+
}
|
|
28
|
+
export interface BundleOptions {
|
|
29
|
+
title: string;
|
|
30
|
+
language: string;
|
|
31
|
+
readImage: ReadImage;
|
|
32
|
+
}
|
|
33
|
+
export declare const bundleOf: (content: Content, { title, language, readImage }: BundleOptions) => Promise<ExportBundle>;
|
|
34
|
+
//#endregion
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { imageSize } from "./image-size.js";
|
|
2
|
+
import { CODE_FONT } from "@pollium/vyva-core/code";
|
|
3
|
+
import { fileIdsOf } from "@pollium/vyva-core/render";
|
|
4
|
+
//#region src/export/bundle.ts
|
|
5
|
+
const EXTENSIONS = {
|
|
6
|
+
"image/png": "png",
|
|
7
|
+
"image/jpeg": "jpg",
|
|
8
|
+
"image/gif": "gif",
|
|
9
|
+
"image/webp": "webp"
|
|
10
|
+
};
|
|
11
|
+
const fontsOf = (content) => {
|
|
12
|
+
if (content.kind === "document") return [content.design.font, CODE_FONT];
|
|
13
|
+
return content.slides.some((slide) => slide.elements.some((element) => element.type === "code")) ? [content.theme.font, CODE_FONT] : [content.theme.font];
|
|
14
|
+
};
|
|
15
|
+
const bundleImage = (id, stored) => {
|
|
16
|
+
const data = Buffer.from(stored.data);
|
|
17
|
+
const extension = EXTENSIONS[stored.contentType] ?? "bin";
|
|
18
|
+
return {
|
|
19
|
+
id,
|
|
20
|
+
name: `${id}.${extension}`,
|
|
21
|
+
data,
|
|
22
|
+
contentType: stored.contentType,
|
|
23
|
+
extension,
|
|
24
|
+
size: imageSize(data, stored.contentType)
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const bundleOf = async (content, { title, language, readImage }) => {
|
|
28
|
+
const ids = fileIdsOf(content);
|
|
29
|
+
const stored = await Promise.all(ids.map((id) => readImage(id)));
|
|
30
|
+
return {
|
|
31
|
+
title,
|
|
32
|
+
language,
|
|
33
|
+
images: new Map(ids.flatMap((id, index) => {
|
|
34
|
+
const image = stored[index];
|
|
35
|
+
return image === null ? [] : [[id, bundleImage(id, image)]];
|
|
36
|
+
})),
|
|
37
|
+
fonts: fontsOf(content)
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { bundleOf };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ExportBundle, ExportedFile } from "./bundle.js";
|
|
2
|
+
import { DocumentContent } from "@pollium/vyva-core/content";
|
|
3
|
+
//#region src/export/docx.d.ts
|
|
4
|
+
export declare const buildDocx: (content: DocumentContent, bundle: ExportBundle) => Promise<ExportedFile>;
|
|
5
|
+
//#endregion
|