@pollium/vyva-convert 0.0.0-stage → 0.1.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.
Files changed (56) hide show
  1. package/README.md +88 -2
  2. package/dist/attachment.d.ts +3 -0
  3. package/dist/attachment.js +8 -0
  4. package/dist/clean-content.d.ts +6 -0
  5. package/dist/clean-content.js +58 -0
  6. package/dist/clean-html.d.ts +3 -0
  7. package/dist/clean-html.js +161 -0
  8. package/dist/errors.d.ts +7 -0
  9. package/dist/errors.js +11 -0
  10. package/dist/export/bundle.d.ts +34 -0
  11. package/dist/export/bundle.js +41 -0
  12. package/dist/export/docx.d.ts +5 -0
  13. package/dist/export/docx.js +382 -0
  14. package/dist/export/fonts.d.ts +16 -0
  15. package/dist/export/fonts.js +37 -0
  16. package/dist/export/html-blocks.d.ts +58 -0
  17. package/dist/export/html-blocks.js +170 -0
  18. package/dist/export/html.d.ts +8 -0
  19. package/dist/export/html.js +31 -0
  20. package/dist/export/image-size.d.ts +8 -0
  21. package/dist/export/image-size.js +83 -0
  22. package/dist/export/pdf.d.ts +28 -0
  23. package/dist/export/pdf.js +84 -0
  24. package/dist/export/pptx.d.ts +5 -0
  25. package/dist/export/pptx.js +310 -0
  26. package/dist/export.d.ts +19 -0
  27. package/dist/export.js +39 -0
  28. package/dist/font-widths.d.ts +8 -0
  29. package/dist/font-widths.js +3430 -0
  30. package/dist/gotenberg.d.ts +8 -0
  31. package/dist/gotenberg.js +39 -0
  32. package/dist/import/documents.d.ts +12 -0
  33. package/dist/import/documents.js +44 -0
  34. package/dist/import/html-to-blocks.d.ts +6 -0
  35. package/dist/import/html-to-blocks.js +58 -0
  36. package/dist/import/images.d.ts +10 -0
  37. package/dist/import/images.js +23 -0
  38. package/dist/import/pptx-package.d.ts +22 -0
  39. package/dist/import/pptx-package.js +61 -0
  40. package/dist/import/pptx-placeholders.d.ts +11 -0
  41. package/dist/import/pptx-placeholders.js +44 -0
  42. package/dist/import/pptx-slide.d.ts +19 -0
  43. package/dist/import/pptx-slide.js +324 -0
  44. package/dist/import/pptx-theme.d.ts +18 -0
  45. package/dist/import/pptx-theme.js +161 -0
  46. package/dist/import/pptx-xml.d.ts +23 -0
  47. package/dist/import/pptx-xml.js +27 -0
  48. package/dist/import/pptx.d.ts +9 -0
  49. package/dist/import/pptx.js +112 -0
  50. package/dist/import/titles.d.ts +3 -0
  51. package/dist/import/titles.js +6 -0
  52. package/dist/import.d.ts +14 -0
  53. package/dist/import.js +66 -0
  54. package/dist/text-measure.d.ts +4 -0
  55. package/dist/text-measure.js +98 -0
  56. package/package.json +49 -3
@@ -0,0 +1,8 @@
1
+ import { PdfEngine } from "./export/pdf.js";
2
+ //#region src/gotenberg.d.ts
3
+ export interface GotenbergOptions {
4
+ url: string;
5
+ timeoutMs?: number;
6
+ }
7
+ export declare const gotenberg: ({ url, timeoutMs }: GotenbergOptions) => PdfEngine;
8
+ //#endregion
@@ -0,0 +1,39 @@
1
+ import { ConvertError } from "./errors.js";
2
+ //#region src/gotenberg.ts
3
+ const DEFAULT_TIMEOUT_MS = 6e4;
4
+ const CONVERT_PATH = "/forms/chromium/convert/html";
5
+ const inches = (value) => String(Number(value.toFixed(3)));
6
+ const formOf = ({ html, files, footerHtml, page }) => {
7
+ const form = new FormData();
8
+ form.append("files", new Blob([html], { type: "text/html" }), "index.html");
9
+ for (const file of files) form.append("files", new Blob([new Uint8Array(file.data)], { type: file.contentType }), file.name);
10
+ if (footerHtml !== null) form.append("files", new Blob([footerHtml], { type: "text/html" }), "footer.html");
11
+ const { margins } = page;
12
+ const sizes = {
13
+ paperWidth: page.width,
14
+ paperHeight: page.height,
15
+ marginTop: margins.top,
16
+ marginBottom: margins.bottom,
17
+ marginLeft: margins.left,
18
+ marginRight: margins.right
19
+ };
20
+ for (const [field, value] of Object.entries(sizes)) form.append(field, inches(value));
21
+ form.append("printBackground", "true");
22
+ form.append("preferCssPageSize", "false");
23
+ form.append("waitDelay", "0.5s");
24
+ return form;
25
+ };
26
+ const gotenberg = ({ url, timeoutMs = DEFAULT_TIMEOUT_MS }) => async (job) => {
27
+ const endpoint = `${url.replace(/\/+$/, "")}${CONVERT_PATH}`;
28
+ const response = await fetch(endpoint, {
29
+ method: "POST",
30
+ body: formOf(job),
31
+ signal: AbortSignal.timeout(timeoutMs)
32
+ }).catch((error) => {
33
+ throw new ConvertError("pdf-failed", `Gotenberg at ${url} could not be reached`, { cause: error });
34
+ });
35
+ if (!response.ok) throw new ConvertError("pdf-failed", `Gotenberg answered ${response.status}: ${await response.text().catch(() => "")}`);
36
+ return new Uint8Array(await response.arrayBuffer());
37
+ };
38
+ //#endregion
39
+ export { gotenberg };
@@ -0,0 +1,12 @@
1
+ import { SaveImage } from "./images.js";
2
+ import { DocumentContent } from "@pollium/vyva-core/content";
3
+ //#region src/import/documents.d.ts
4
+ export interface ImportedDocument {
5
+ title: string;
6
+ content: DocumentContent;
7
+ }
8
+ export declare const importDocx: (buffer: Buffer, filename: string, saveImage: SaveImage) => Promise<ImportedDocument>;
9
+ export declare const importMarkdown: (text: string, filename: string) => ImportedDocument;
10
+ export declare const importPlainText: (text: string, filename: string) => ImportedDocument;
11
+ export declare const importPdf: (buffer: Buffer, filename: string) => Promise<ImportedDocument>;
12
+ //#endregion
@@ -0,0 +1,44 @@
1
+ import { htmlToBlocks, paragraphsHtml, titleFrom } from "./html-to-blocks.js";
2
+ import { imageSaver } from "./images.js";
3
+ import { markdownToBlocks } from "@pollium/vyva-core/markdown-blocks";
4
+ import { DEFAULT_DOCUMENT_DESIGN } from "@pollium/vyva-core/themes";
5
+ import mammoth from "mammoth";
6
+ import { extractText, getDocumentProxy } from "unpdf";
7
+ //#region src/import/documents.ts
8
+ const MAX_IMAGES = 60;
9
+ const PAGE_BREAK = "<div data-type=\"page-break\"></div>";
10
+ const documentOf = (blocks, filename) => ({
11
+ title: titleFrom(blocks, filename),
12
+ content: {
13
+ kind: "document",
14
+ design: DEFAULT_DOCUMENT_DESIGN,
15
+ blocks
16
+ }
17
+ });
18
+ const importDocx = async (buffer, filename, saveImage) => {
19
+ const save = imageSaver(saveImage, filename, MAX_IMAGES);
20
+ const failures = [];
21
+ const result = await mammoth.convertToHtml({ buffer }, { convertImage: mammoth.images.imgElement(async (image) => {
22
+ const data = await image.readAsBuffer();
23
+ const fileId = await save(data, image.contentType).catch((error) => {
24
+ failures.push(error);
25
+ return null;
26
+ });
27
+ return fileId === null ? { src: "" } : {
28
+ src: "",
29
+ "data-file-id": fileId,
30
+ alt: ""
31
+ };
32
+ }) });
33
+ if (failures.length > 0) throw failures[0];
34
+ return documentOf(htmlToBlocks(result.value), filename);
35
+ };
36
+ const importMarkdown = (text, filename) => documentOf(markdownToBlocks(text), filename);
37
+ const importPlainText = (text, filename) => documentOf(htmlToBlocks(paragraphsHtml(text)), filename);
38
+ const importPdf = async (buffer, filename) => {
39
+ const { text } = await extractText(await getDocumentProxy(new Uint8Array(buffer)), { mergePages: false });
40
+ const html = text.map((page) => paragraphsHtml(page)).filter((page) => page !== "").join(PAGE_BREAK);
41
+ return documentOf(htmlToBlocks(html), filename);
42
+ };
43
+ //#endregion
44
+ export { importDocx, importMarkdown, importPdf, importPlainText };
@@ -0,0 +1,6 @@
1
+ import { DocumentBlock } from "@pollium/vyva-core/content";
2
+ //#region src/import/html-to-blocks.d.ts
3
+ export declare const htmlToBlocks: (html: string) => DocumentBlock[];
4
+ export declare const titleFrom: (blocks: DocumentBlock[], filename: string) => string;
5
+ export declare const paragraphsHtml: (text: string) => string;
6
+ //#endregion
@@ -0,0 +1,58 @@
1
+ import { cleanHtml } from "../clean-html.js";
2
+ import { titleOf } from "./titles.js";
3
+ import { newId } from "@pollium/vyva-core/ids";
4
+ import { DomUtils, parseDocument } from "htmlparser2";
5
+ //#region src/import/html-to-blocks.ts
6
+ const BLOCK_TAGS = /* @__PURE__ */ new Set([
7
+ "p",
8
+ "h1",
9
+ "h2",
10
+ "h3",
11
+ "h4",
12
+ "ul",
13
+ "ol",
14
+ "table",
15
+ "pre",
16
+ "blockquote",
17
+ "hr",
18
+ "div",
19
+ "img"
20
+ ]);
21
+ const HEADINGS = /* @__PURE__ */ new Set(["h5", "h6"]);
22
+ const ENTITIES = {
23
+ "&amp;": "&",
24
+ "&lt;": "<",
25
+ "&gt;": ">",
26
+ "&quot;": "\"",
27
+ "&#39;": "'"
28
+ };
29
+ const htmlToBlocks = (html) => {
30
+ const blocks = [];
31
+ let loose = [];
32
+ const push = (outer) => {
33
+ const cleaned = cleanHtml(outer);
34
+ if (cleaned !== "" && cleaned !== "<p></p>") blocks.push({
35
+ id: newId(),
36
+ html: cleaned
37
+ });
38
+ };
39
+ const flush = () => {
40
+ const inner = loose.map((node) => DomUtils.getOuterHTML(node)).join("").trim();
41
+ loose = [];
42
+ if (inner !== "") push(`<p>${inner}</p>`);
43
+ };
44
+ for (const node of parseDocument(html).children) if (DomUtils.isTag(node) && (BLOCK_TAGS.has(node.name) || HEADINGS.has(node.name))) {
45
+ flush();
46
+ push(HEADINGS.has(node.name) ? `<h4>${DomUtils.getInnerHTML(node)}</h4>` : DomUtils.getOuterHTML(node));
47
+ } else loose.push(node);
48
+ flush();
49
+ return blocks;
50
+ };
51
+ const titleFrom = (blocks, filename) => {
52
+ const heading = blocks.find((block) => /^<h[1-3]/.test(block.html));
53
+ const text = heading === void 0 ? "" : heading.html.replace(/<[^>]+>/g, "").replace(/&(?:amp|lt|gt|quot|#39);/g, (entity) => ENTITIES[entity] ?? entity);
54
+ return titleOf(text, filename);
55
+ };
56
+ const paragraphsHtml = (text) => text.replace(/\r\n?/g, "\n").split(/\n{2,}/).map((part) => part.trim()).filter((part) => part !== "").map((part) => `<p>${part.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>")}</p>`).join("");
57
+ //#endregion
58
+ export { htmlToBlocks, paragraphsHtml, titleFrom };
@@ -0,0 +1,10 @@
1
+ //#region src/import/images.d.ts
2
+ export interface NewImage {
3
+ name: string;
4
+ data: Uint8Array;
5
+ contentType: string;
6
+ }
7
+ export type SaveImage = (image: NewImage) => Promise<string | null>;
8
+ export type ImageSaver = (data: Uint8Array, contentType: string) => Promise<string | null>;
9
+ export declare const imageSaver: (saveImage: SaveImage, filename: string, limit: number) => ImageSaver;
10
+ //#endregion
@@ -0,0 +1,23 @@
1
+ //#region src/import/images.ts
2
+ const EXTENSIONS = {
3
+ "image/png": "png",
4
+ "image/jpeg": "jpg",
5
+ "image/gif": "gif",
6
+ "image/webp": "webp"
7
+ };
8
+ const imageSaver = (saveImage, filename, limit) => {
9
+ const base = filename.replace(/\.[a-z0-9]+$/i, "");
10
+ let taken = 0;
11
+ return async (data, contentType) => {
12
+ const extension = EXTENSIONS[contentType];
13
+ if (extension === void 0 || taken >= limit) return null;
14
+ taken += 1;
15
+ return saveImage({
16
+ name: `${base} (image ${taken}).${extension}`,
17
+ data,
18
+ contentType
19
+ });
20
+ };
21
+ };
22
+ //#endregion
23
+ export { imageSaver };
@@ -0,0 +1,22 @@
1
+ import { PackageFiles, Relations, XmlDocument, XmlElement } from "./pptx-xml.js";
2
+ import Palette from "./pptx-theme.js";
3
+ //#region src/import/pptx-package.d.ts
4
+ export interface SlidePart {
5
+ root: XmlElement;
6
+ tree: XmlElement | undefined;
7
+ relations: Relations;
8
+ palette: Palette;
9
+ }
10
+ export interface SlideParts {
11
+ slide: SlidePart;
12
+ layout: SlidePart | null;
13
+ master: SlidePart | null;
14
+ }
15
+ export default class PptxPackage {
16
+ #private;
17
+ constructor(files: PackageFiles);
18
+ document(path: string): XmlDocument | undefined;
19
+ relationsOf(path: string): Relations;
20
+ parts(slidePath: string): SlideParts | null;
21
+ }
22
+ //#endregion
@@ -0,0 +1,61 @@
1
+ import { first, relatedPart, relationsOf, xml } from "./pptx-xml.js";
2
+ import Palette, { colorMapOf, themeOf } from "./pptx-theme.js";
3
+ import { DomUtils } from "htmlparser2";
4
+ import { strFromU8 } from "fflate";
5
+ //#region src/import/pptx-package.ts
6
+ var PptxPackage = class {
7
+ #files;
8
+ #documents = /* @__PURE__ */ new Map();
9
+ #palettes = /* @__PURE__ */ new Map();
10
+ constructor(files) {
11
+ this.#files = files;
12
+ }
13
+ document(path) {
14
+ const known = this.#documents.get(path);
15
+ if (known !== void 0) return known;
16
+ const data = this.#files[path];
17
+ if (data === void 0) return void 0;
18
+ const document = xml(strFromU8(data));
19
+ this.#documents.set(path, document);
20
+ return document;
21
+ }
22
+ relationsOf(path) {
23
+ return relationsOf(this.#files, path);
24
+ }
25
+ parts(slidePath) {
26
+ const slideRelations = relationsOf(this.#files, slidePath);
27
+ const layoutPath = relatedPart(slideRelations, "slideLayout");
28
+ const layoutRelations = layoutPath === null ? {} : relationsOf(this.#files, layoutPath);
29
+ const masterPath = layoutPath === null ? null : relatedPart(layoutRelations, "slideMaster");
30
+ const palette = this.#palette(masterPath);
31
+ const slide = this.#part(slidePath, slideRelations, palette);
32
+ if (slide === null) return null;
33
+ return {
34
+ slide,
35
+ layout: layoutPath === null ? null : this.#part(layoutPath, layoutRelations, palette),
36
+ master: masterPath === null ? null : this.#part(masterPath, relationsOf(this.#files, masterPath), palette)
37
+ };
38
+ }
39
+ #part(path, relations, palette) {
40
+ const root = this.document(path)?.children.find((node) => DomUtils.isTag(node));
41
+ if (root === void 0) return null;
42
+ return {
43
+ root,
44
+ tree: first(root, "p:spTree"),
45
+ relations,
46
+ palette
47
+ };
48
+ }
49
+ #palette(masterPath) {
50
+ const key = masterPath ?? "";
51
+ const known = this.#palettes.get(key);
52
+ if (known !== void 0) return known;
53
+ const masterRoot = (masterPath === null ? void 0 : this.document(masterPath))?.children.find((node) => DomUtils.isTag(node));
54
+ const themePath = masterPath === null ? null : relatedPart(relationsOf(this.#files, masterPath), "theme");
55
+ const palette = new Palette(themeOf(themePath === null ? void 0 : this.document(themePath)), colorMapOf(masterRoot));
56
+ this.#palettes.set(key, palette);
57
+ return palette;
58
+ }
59
+ };
60
+ //#endregion
61
+ export { PptxPackage as default };
@@ -0,0 +1,11 @@
1
+ import { XmlElement } from "./pptx-xml.js";
2
+ import { SlidePart, SlideParts } from "./pptx-package.js";
3
+ //#region src/import/pptx-placeholders.d.ts
4
+ export declare const TITLE_TYPES: string[];
5
+ export declare const isDefined: <T>(value: T | undefined) => value is T;
6
+ export declare const placeholderChain: (shape: XmlElement, placeholder: XmlElement, parts: SlideParts) => XmlElement[];
7
+ export declare const levelOf: (shape: XmlElement) => XmlElement | undefined;
8
+ export declare const masterLevel: (parts: SlideParts, type: string | null) => XmlElement | undefined;
9
+ export declare const bulletsByDefault: (levels: XmlElement[]) => boolean;
10
+ export declare const decorOf: (parts: SlideParts) => SlidePart[];
11
+ //#endregion
@@ -0,0 +1,44 @@
1
+ import { all, attr, first, kid, kids } from "./pptx-xml.js";
2
+ //#region src/import/pptx-placeholders.ts
3
+ const TITLE_TYPES = ["title", "ctrTitle"];
4
+ const MASTER_TYPES = {
5
+ ctrTitle: "title",
6
+ subTitle: "body",
7
+ obj: "body"
8
+ };
9
+ const isDefined = (value) => value !== void 0;
10
+ const placeholderType = (shape) => attr(first(shape, "p:ph"), "type") ?? "body";
11
+ const masterType = (type) => MASTER_TYPES[type] ?? type;
12
+ const placeholdersOf = (part) => part?.tree === void 0 ? [] : all(part.tree, "p:sp").filter((shape) => first(shape, "p:ph") !== void 0);
13
+ const placeholderChain = (shape, placeholder, parts) => {
14
+ const type = attr(placeholder, "type") ?? "body";
15
+ const idx = attr(placeholder, "idx");
16
+ const layoutShapes = placeholdersOf(parts.layout);
17
+ const inLayout = (idx === void 0 ? void 0 : layoutShapes.find((candidate) => attr(first(candidate, "p:ph"), "idx") === idx)) ?? layoutShapes.find((candidate) => placeholderType(candidate) === type);
18
+ const wanted = masterType(inLayout === void 0 ? type : placeholderType(inLayout));
19
+ return [
20
+ shape,
21
+ inLayout,
22
+ placeholdersOf(parts.master).find((candidate) => masterType(placeholderType(candidate)) === wanted)
23
+ ].filter(isDefined);
24
+ };
25
+ const levelOf = (shape) => kid(kid(kid(shape, "p:txBody"), "a:lstStyle"), "a:lvl1pPr");
26
+ const masterLevel = (parts, type) => {
27
+ if (type === null || parts.master === null) return void 0;
28
+ const style = TITLE_TYPES.includes(type) ? "p:titleStyle" : masterType(type) === "body" ? "p:bodyStyle" : "p:otherStyle";
29
+ return kid(first(parts.master.root, style), "a:lvl1pPr");
30
+ };
31
+ const bulletsByDefault = (levels) => {
32
+ for (const level of levels) {
33
+ if (kids(level, "a:buNone").length > 0) return false;
34
+ if (kids(level, "a:buChar").length > 0 || kids(level, "a:buAutoNum").length > 0) return true;
35
+ }
36
+ return false;
37
+ };
38
+ const showsMasterShapes = (part) => attr(part.root, "showMasterSp") !== "0";
39
+ const decorOf = (parts) => {
40
+ if (!showsMasterShapes(parts.slide) || parts.layout === null) return [];
41
+ return parts.master !== null && showsMasterShapes(parts.layout) ? [parts.master, parts.layout] : [parts.layout];
42
+ };
43
+ //#endregion
44
+ export { TITLE_TYPES, bulletsByDefault, decorOf, isDefined, levelOf, masterLevel, placeholderChain };
@@ -0,0 +1,19 @@
1
+ import { SlideParts } from "./pptx-package.js";
2
+ import { DeckTheme, Slide } from "@pollium/vyva-core/content";
3
+ //#region src/import/pptx-slide.d.ts
4
+ export interface SlideReaderOptions {
5
+ scale: number;
6
+ fontScale: number;
7
+ theme: DeckTheme;
8
+ image: (path: string) => Promise<string | null>;
9
+ }
10
+ export interface ReadSlide {
11
+ slide: Slide;
12
+ title: string;
13
+ }
14
+ export default class SlideReader {
15
+ #private;
16
+ constructor(options: SlideReaderOptions);
17
+ read(parts: SlideParts): Promise<ReadSlide>;
18
+ }
19
+ //#endregion