@elastic/isomer-image-takumi 0.1.0 → 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 +17 -6
- package/dist/backend.d.ts +30 -17
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +18 -8
- package/dist/backend.js.map +1 -1
- package/dist/cjs/backend.js +19 -9
- package/dist/cjs/backend.js.map +1 -1
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/render_pdf.js +3 -3
- package/dist/cjs/render_pdf.js.map +1 -1
- package/dist/cjs/render_png.js +3 -3
- package/dist/cjs/render_png.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/render_pdf.d.ts +6 -6
- package/dist/render_pdf.d.ts.map +1 -1
- package/dist/render_pdf.js +3 -3
- package/dist/render_pdf.js.map +1 -1
- package/dist/render_png.d.ts +12 -11
- package/dist/render_png.d.ts.map +1 -1
- package/dist/render_png.js +3 -3
- package/dist/render_png.js.map +1 -1
- package/package.json +2 -6
package/README.md
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- markdownlint-disable MD033 -->
|
|
2
|
+
<p align="center">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/elastic/isomer/main/docs/logo.svg" alt="Isomer" width="96" height="96">
|
|
4
|
+
</p>
|
|
5
|
+
<h1 align="center">@elastic/isomer-image-takumi</h1>
|
|
6
|
+
<p align="center"><strong>isomer</strong> <i>n.</i> — one formula, many forms; the same composition rendered to every surface.</p>
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@elastic/isomer-image-takumi"><img src="https://img.shields.io/npm/v/@elastic/isomer-image-takumi.svg" alt="npm version"></a>
|
|
9
|
+
<a href="https://github.com/elastic/isomer/actions/workflows/ci.yml"><img src="https://github.com/elastic/isomer/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
10
|
+
<a href="https://github.com/elastic/isomer/blob/main/LICENSE.txt"><img src="https://img.shields.io/badge/License-Elastic%202.0-blue.svg" alt="License: Elastic License 2.0"></a>
|
|
11
|
+
</p>
|
|
12
|
+
<!-- markdownlint-enable MD033 -->
|
|
2
13
|
|
|
3
|
-
Rasterizes the `
|
|
14
|
+
Rasterizes the `snapshot` surface's output with [takumi](https://takumi.kane.tw): PNG, SVG, or PDF out, and a measured layout for the SDK's `checkLayout`.
|
|
4
15
|
|
|
5
16
|
```sh
|
|
6
|
-
npm install @elastic/isomer-image-takumi
|
|
17
|
+
npm install @elastic/isomer-image-takumi
|
|
7
18
|
```
|
|
8
19
|
|
|
9
20
|
```ts
|
|
10
21
|
import { createTakumiImageBackend } from '@elastic/isomer-image-takumi';
|
|
11
22
|
|
|
12
23
|
const takumi = createTakumiImageBackend({ fonts });
|
|
13
|
-
const png = await takumi.png(runtime.surfaces.
|
|
14
|
-
const pdf = await takumi.pdf(runtime.surfaces.
|
|
24
|
+
const png = await takumi.png(runtime.surfaces.snapshot.render(composition));
|
|
25
|
+
const pdf = await takumi.pdf(runtime.surfaces.snapshot.renderPages(deck));
|
|
15
26
|
```
|
|
16
27
|
|
|
17
|
-
`renderPng` and `renderPdf` wrap validation, the `
|
|
28
|
+
`renderPng` and `renderPdf` wrap validation, the `snapshot` surface, and the backend in one call and return the findings beside the bytes. A host-side rasterizer: it reads the snapshot's markup, so it depends on no Isomer package and no React, and it is the one published package that pulls a native dependency. Pass `takumi.formats` to `createIsomerRuntime({ formats })` so `getCapabilities()` reports PNG, SVG, and PDF. Docs: [Takumi image backend](https://elastic.github.io/isomer/image-takumi/).
|
|
18
29
|
|
|
19
30
|
## License
|
|
20
31
|
|
package/dist/backend.d.ts
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
1
|
import { type FontLoader } from '@takumi-rs/core';
|
|
3
2
|
import type { ImagesInput } from 'takumi-pdf';
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
6
|
-
* out against,
|
|
4
|
+
* The part of a `snapshot` surface result this backend reads: the markup, the
|
|
5
|
+
* stylesheet it is laid out against, and the viewport it was measured for.
|
|
7
6
|
*
|
|
8
7
|
* Declared structurally rather than imported from `@elastic/isomer-runtime`,
|
|
9
|
-
* so this backend depends on no isomer package. `
|
|
8
|
+
* so this backend depends on no isomer package. `SnapshotRenderResult` satisfies it.
|
|
10
9
|
*/
|
|
11
10
|
export interface ImageInput {
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
/** The stylesheet the
|
|
11
|
+
/** Static markup with a single root, as image layout requires. */
|
|
12
|
+
html: string;
|
|
13
|
+
/** The stylesheet the markup is laid out against, `light-dark(…)` already resolved. */
|
|
15
14
|
css: string;
|
|
16
15
|
/** The viewport, in CSS pixels; the output is this size. */
|
|
17
16
|
width: number;
|
|
18
17
|
height: number;
|
|
19
18
|
}
|
|
20
19
|
/**
|
|
21
|
-
*
|
|
22
|
-
* out against one stylesheet at one size.
|
|
23
|
-
* `
|
|
20
|
+
* The part of a `snapshot` surface's `renderPages` result this backend reads:
|
|
21
|
+
* one page's markup each, all laid out against one stylesheet at one size.
|
|
22
|
+
* Structural like {@link ImageInput}; `SnapshotPagesResult` satisfies it.
|
|
24
23
|
*/
|
|
25
24
|
export interface PdfInput {
|
|
26
|
-
/** One
|
|
27
|
-
pages: readonly
|
|
25
|
+
/** One page per entry, in order, each static markup with a single root. */
|
|
26
|
+
pages: readonly {
|
|
27
|
+
html: string;
|
|
28
|
+
}[];
|
|
28
29
|
/** One stylesheet collected across every page. */
|
|
29
30
|
css: string;
|
|
30
31
|
/** Every page's size, in CSS pixels. */
|
|
@@ -50,9 +51,17 @@ export interface TakumiImageBackendOptions {
|
|
|
50
51
|
/** Per-render overrides. Geometry comes from the input, not from here. */
|
|
51
52
|
export interface TakumiRenderOptions {
|
|
52
53
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* Raster pixels per CSS pixel: the PNG is `input.width * scale` by
|
|
55
|
+
* `input.height * scale`, rounded. A layout fixed in CSS pixels is
|
|
56
|
+
* unchanged; rounding moves the viewport by under half a raster pixel. `2`
|
|
57
|
+
* gives a 2x raster. Defaults to `1`.
|
|
58
|
+
*/
|
|
59
|
+
scale?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Takumi's zoom at a fixed canvas: the tree is laid out at
|
|
62
|
+
* `input.width / devicePixelRatio` CSS pixels and magnified to fill the
|
|
63
|
+
* canvas, so above `1` the PNG keeps its size and shows less of the frame.
|
|
64
|
+
* For a larger raster, use {@link TakumiRenderOptions.scale}.
|
|
56
65
|
*/
|
|
57
66
|
devicePixelRatio?: number;
|
|
58
67
|
}
|
|
@@ -117,7 +126,7 @@ export interface LayoutBox {
|
|
|
117
126
|
children: LayoutBox[];
|
|
118
127
|
}
|
|
119
128
|
/**
|
|
120
|
-
* Rasterizes the `
|
|
129
|
+
* Rasterizes the `snapshot` surface's output.
|
|
121
130
|
*
|
|
122
131
|
* Both methods are one call over takumi: the tree is serialized, `fromHtml`
|
|
123
132
|
* turns it into a takumi node tree and lifts the stylesheet out, and takumi
|
|
@@ -129,7 +138,7 @@ export interface TakumiImageBackend {
|
|
|
129
138
|
/** Renders to an SVG document. Vector output, so raster options do not apply. */
|
|
130
139
|
svg(input: ImageInput): Promise<string>;
|
|
131
140
|
}
|
|
132
|
-
/** Writes the `
|
|
141
|
+
/** Writes the `snapshot` surface's pages as a PDF, one page each. */
|
|
133
142
|
export interface TakumiPdfBackend {
|
|
134
143
|
/**
|
|
135
144
|
* Renders one page per entry in `pages`, each the input's size with no
|
|
@@ -143,8 +152,12 @@ export interface TakumiMeasuringBackend extends TakumiImageBackend {
|
|
|
143
152
|
/** Lays the input out as {@link TakumiImageBackend.png} would, and returns every box, e.g. to find content past its area. */
|
|
144
153
|
measure(input: ImageInput): Promise<LayoutBox>;
|
|
145
154
|
}
|
|
155
|
+
/** The formats a {@link TakumiBackend} writes, for `createIsomerRuntime({ formats })`. */
|
|
156
|
+
export declare const TAKUMI_FORMATS: readonly ["png", "svg", "pdf"];
|
|
146
157
|
/** Everything {@link createTakumiImageBackend} returns: raster, measure, and PDF. */
|
|
147
158
|
export interface TakumiBackend extends TakumiMeasuringBackend, TakumiPdfBackend {
|
|
159
|
+
/** {@link TAKUMI_FORMATS}: one entry per method that writes a format. */
|
|
160
|
+
readonly formats: typeof TAKUMI_FORMATS;
|
|
148
161
|
}
|
|
149
162
|
/**
|
|
150
163
|
* Builds a {@link TakumiBackend} over one takumi renderer. `fonts` are
|
package/dist/backend.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAOA,OAAO,
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
|
package/dist/backend.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
5
5
|
* 2.0.
|
|
6
6
|
*/
|
|
7
|
-
import { renderToStaticMarkup } from 'react-dom/server';
|
|
8
7
|
import { Renderer, } from '@takumi-rs/core';
|
|
9
8
|
import { fromHtml } from '@takumi-rs/helpers/html';
|
|
9
|
+
/** The formats a {@link TakumiBackend} writes, for `createIsomerRuntime({ formats })`. */
|
|
10
|
+
export const TAKUMI_FORMATS = ['png', 'svg', 'pdf'];
|
|
10
11
|
/** The canvas rectangle `transform` maps a local `width` × `height` box at `x`, `y` to. */
|
|
11
12
|
const mapRect = ([a, b, c, d, e, f], x, y, width, height) => {
|
|
12
13
|
const corners = [
|
|
@@ -57,7 +58,12 @@ const escapeStyleEndTags = (css) => css.replace(/<\/style/gi, (tag) => tag.repla
|
|
|
57
58
|
* a different path through takumi and changes the committed PNG bytes.
|
|
58
59
|
*/
|
|
59
60
|
const withStylesheet = (css, markup) => `<style>${escapeStyleEndTags(css)}</style>${markup}`;
|
|
60
|
-
const toTakumiSource = ({
|
|
61
|
+
const toTakumiSource = ({ html, css }) => fromHtml(withStylesheet(css, html));
|
|
62
|
+
/**
|
|
63
|
+
* One side of a scaled canvas, in whole pixels. Takumi takes one ratio for
|
|
64
|
+
* both axes, so the ratio stays `scale` and the canvas absorbs the rounding.
|
|
65
|
+
*/
|
|
66
|
+
const toRasterSide = (side, scale) => scale === 1 ? side : Math.max(1, Math.round(side * scale));
|
|
61
67
|
/** Inline, so no pack class name can collide with it. */
|
|
62
68
|
const PAGE_STYLE = 'overflow:hidden;break-after:page;break-inside:avoid';
|
|
63
69
|
/** Each page in a fixed-size block that ends the page, so a frame never spills onto the next. */
|
|
@@ -66,7 +72,7 @@ const toPagedSource = ({ pages, css, width, height }) => {
|
|
|
66
72
|
throw new Error('pdf: no pages; a document needs at least one');
|
|
67
73
|
}
|
|
68
74
|
const markup = pages
|
|
69
|
-
.map((
|
|
75
|
+
.map(({ html }) => `<div style="width:${width}px;height:${height}px;${PAGE_STYLE}">${html}</div>`)
|
|
70
76
|
.join('');
|
|
71
77
|
return {
|
|
72
78
|
...fromHtml(withStylesheet(css, `<div>${markup}</div>`)),
|
|
@@ -110,17 +116,21 @@ export const createTakumiImageBackend = ({ fonts = [], cacheMaxBytes, } = {}) =>
|
|
|
110
116
|
return registerFonts(new PdfRenderer());
|
|
111
117
|
});
|
|
112
118
|
return {
|
|
113
|
-
|
|
119
|
+
formats: TAKUMI_FORMATS,
|
|
120
|
+
png: async (input, { scale = 1, devicePixelRatio } = {}) => {
|
|
121
|
+
if (!Number.isFinite(scale) || scale <= 0) {
|
|
122
|
+
throw new RangeError(`png: scale must be a positive finite number, got ${scale}`);
|
|
123
|
+
}
|
|
114
124
|
await ready();
|
|
115
125
|
const { node, css } = toTakumiSource(input);
|
|
116
126
|
const renderOptions = {
|
|
117
|
-
width: input.width,
|
|
118
|
-
height: input.height,
|
|
127
|
+
width: toRasterSide(input.width, scale),
|
|
128
|
+
height: toRasterSide(input.height, scale),
|
|
119
129
|
format: 'png',
|
|
120
130
|
css,
|
|
121
131
|
};
|
|
122
|
-
if (
|
|
123
|
-
renderOptions.devicePixelRatio =
|
|
132
|
+
if (devicePixelRatio !== undefined || scale !== 1) {
|
|
133
|
+
renderOptions.devicePixelRatio = (devicePixelRatio ?? 1) * scale;
|
|
124
134
|
}
|
|
125
135
|
return renderer.render(node, renderOptions);
|
|
126
136
|
},
|
package/dist/backend.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nimport type { ReactNode } from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport {\n type FontLoader,\n type MeasuredNode,\n Renderer,\n type RenderOptions,\n} from '@takumi-rs/core';\nimport { fromHtml } from '@takumi-rs/helpers/html';\nimport type {\n ImagesInput,\n RenderOptions as PdfRenderOptions,\n} from 'takumi-pdf';\n\n/**\n * What the `svg` surface returns: a React tree and the stylesheet it is laid\n * out against, plus the viewport it was measured for.\n *\n * Declared structurally rather than imported from `@elastic/isomer-runtime`,\n * so this backend depends on no isomer package. `SvgRenderResult` satisfies it.\n */\nexport interface ImageInput {\n /** A single root, as image layout requires: one element, string, or number, never an array or fragment. */\n element: ReactNode;\n /** The stylesheet the tree is laid out against, `light-dark(…)` already resolved. */\n css: string;\n /** The viewport, in CSS pixels; the output is this size. */\n width: number;\n height: number;\n}\n\n/**\n * What the `svg` surface's `renderPages` returns: one root per page, all laid\n * out against one stylesheet at one size. Structural like {@link ImageInput};\n * `SvgPagesResult` satisfies it.\n */\nexport interface PdfInput {\n /** One root per page, in order. */\n pages: readonly ReactNode[];\n /** One stylesheet collected across every page. */\n css: string;\n /** Every page's size, in CSS pixels. */\n width: number;\n height: number;\n}\n\n/** Options for {@link createTakumiImageBackend}. */\nexport interface TakumiImageBackendOptions {\n /**\n * Fonts to register, in fallback order. A pack names families in its theme\n * and this backend holds no opinion about which; nothing is registered by\n * default, so an unregistered family falls back to takumi's built-in face.\n *\n * WOFF and WOFF2 are accepted as well as raw sfnt — `@takumi-rs/core` builds\n * with takumi's `woff`/`woff2` features, so a `@fontsource/*` file can be\n * passed as-is, as raw bytes or a lazy `data()`. A `FontLoader` string is a\n * URL fetched on demand, not a filesystem path.\n */\n fonts?: readonly FontLoader[];\n /** Byte budget for takumi's resource cache. `0` disables it. */\n cacheMaxBytes?: number;\n}\n\n/** Per-render overrides. Geometry comes from the input, not from here. */\nexport interface TakumiRenderOptions {\n /**\n * Raises rendering fidelity (sharper text and gradients) at a fixed output\n * size — the PNG stays sized to `input.width` / `input.height` regardless\n * of this value; it does not produce a larger raster.\n */\n devicePixelRatio?: number;\n}\n\n/** Document properties written into a PDF. */\nexport interface TakumiPdfMetadata {\n title?: string;\n description?: string;\n authors?: string[];\n keywords?: string[];\n creator?: string;\n /** UTC, `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`. Fixing it makes the bytes stable across renders. */\n creationDate?: string;\n}\n\n/** Per-document options for {@link TakumiPdfBackend.pdf}. Geometry comes from the input. */\nexport interface TakumiPdfOptions {\n metadata?: TakumiPdfMetadata;\n /**\n * What a glyph no registered font covers becomes. The default, `'error'`,\n * rejects the render naming the code point, where `png` draws takumi's\n * built-in face instead; `'placeholder'` draws the font's glyph 0 and\n * `'blank'` draws nothing.\n */\n uncoveredText?: 'error' | 'placeholder' | 'blank';\n /**\n * Bytes for the `img` sources in the tree. The PDF engine fetches nothing,\n * so a remote `src` with no entry here draws blank; a `data:` URI needs none.\n */\n images?: ImagesInput;\n /** Writes a document outline from the headings. */\n outline?: boolean;\n /** BCP 47 tag for the document's language. */\n lang?: string;\n /** Paper color painted under every page; unset leaves the page empty. */\n backgroundColor?: string;\n}\n\n/** A laid-out element on the canvas, in pixels, with the elements nested in it. */\nexport interface LayoutBox {\n x: number;\n y: number;\n width: number;\n height: number;\n /** How much the box's x axis is scaled on the canvas, e.g. by `transform: scale()`. */\n scaleX: number;\n /** How much the box's y axis is scaled on the canvas. */\n scaleY: number;\n /** Whichever of `scaleX` and `scaleY` is further from 1, so any value but 1 means the box is scaled. */\n scale: number;\n /** Text laid out in this box, each run positioned on the canvas. */\n runs: { text: string; x: number; y: number; width: number; height: number }[];\n /**\n * The element's attributes other than `class`, `id`, and `style`, such as\n * `data-*`. Absent when it has none, or when its parent's laid-out children\n * do not line up with its elements, as when inline content folds into runs.\n */\n attributes?: Record<string, string>;\n children: LayoutBox[];\n}\n\n/**\n * Rasterizes the `svg` surface's output.\n *\n * Both methods are one call over takumi: the tree is serialized, `fromHtml`\n * turns it into a takumi node tree and lifts the stylesheet out, and takumi\n * lays the result out at the input's viewport.\n */\nexport interface TakumiImageBackend {\n /** Renders to an encoded PNG. */\n png(input: ImageInput, options?: TakumiRenderOptions): Promise<Buffer>;\n /** Renders to an SVG document. Vector output, so raster options do not apply. */\n svg(input: ImageInput): Promise<string>;\n}\n\n/** Writes the `svg` surface's pages as a PDF, one page each. */\nexport interface TakumiPdfBackend {\n /**\n * Renders one page per entry in `pages`, each the input's size with no\n * margin, so a frame fills its page. Vector output with selectable text and\n * the registered fonts subset and embedded. Throws on an empty `pages` list.\n */\n pdf(input: PdfInput, options?: TakumiPdfOptions): Promise<Buffer>;\n}\n\n/** A {@link TakumiImageBackend} that also measures layout. */\nexport interface TakumiMeasuringBackend extends TakumiImageBackend {\n /** Lays the input out as {@link TakumiImageBackend.png} would, and returns every box, e.g. to find content past its area. */\n measure(input: ImageInput): Promise<LayoutBox>;\n}\n\n/** Everything {@link createTakumiImageBackend} returns: raster, measure, and PDF. */\nexport interface TakumiBackend\n extends TakumiMeasuringBackend, TakumiPdfBackend {}\n\ntype Matrix = MeasuredNode['transform'];\n\ntype SourceNode = ReturnType<typeof fromHtml>['node'];\n\n/** The canvas rectangle `transform` maps a local `width` × `height` box at `x`, `y` to. */\nconst mapRect = (\n [a, b, c, d, e, f]: Matrix,\n x: number,\n y: number,\n width: number,\n height: number\n): { x: number; y: number; width: number; height: number } => {\n const corners = [\n [x, y],\n [x + width, y],\n [x, y + height],\n [x + width, y + height],\n ].map(([px = 0, py = 0]) => [a * px + c * py + e, b * px + d * py + f]);\n const xs = corners.map(([cx = 0]) => cx);\n const ys = corners.map(([, cy = 0]) => cy);\n const left = Math.min(...xs);\n const top = Math.min(...ys);\n return {\n x: left,\n y: top,\n width: Math.max(...xs) - left,\n height: Math.max(...ys) - top,\n };\n};\n\n/**\n * Takumi reports each node's size in its own units with its full canvas\n * transform, and carries no attributes, so those come from the `fromHtml`\n * node at the same position.\n */\nconst toLayoutBox = (\n { width, height, transform, runs, children }: MeasuredNode,\n source: SourceNode | undefined\n): LayoutBox => {\n const [a, b, c, d] = transform;\n const scaleX = Math.hypot(a, b);\n const scaleY = Math.hypot(c, d);\n const sources = source?.type === 'container' ? (source.children ?? []) : [];\n const paired = sources.length === children.length;\n return {\n ...mapRect(transform, 0, 0, width, height),\n scaleX,\n scaleY,\n scale: Math.abs(scaleY - 1) > Math.abs(scaleX - 1) ? scaleY : scaleX,\n runs: runs.map((run) => ({\n text: run.text,\n ...mapRect(transform, run.x, run.y, run.width, run.height),\n })),\n ...(source?.attributes && { attributes: { ...source.attributes } }),\n children: children.map((child, index) =>\n toLayoutBox(child, paired ? sources[index] : undefined)\n ),\n };\n};\n\nconst escapeStyleEndTags = (css: string) =>\n css.replace(/<\\/style/gi, (tag) => tag.replace('/', '\\\\/'));\n\n/**\n * Inlines the stylesheet ahead of the markup, the form `fromHtml` lifts into\n * its own `css` list. Passing the stylesheet as a separate `CssInput` entry is\n * a different path through takumi and changes the committed PNG bytes.\n */\nconst withStylesheet = (css: string, markup: string) =>\n `<style>${escapeStyleEndTags(css)}</style>${markup}`;\n\nconst toTakumiSource = ({ element, css }: ImageInput) =>\n fromHtml(withStylesheet(css, renderToStaticMarkup(element)));\n\n/** Inline, so no pack class name can collide with it. */\nconst PAGE_STYLE = 'overflow:hidden;break-after:page;break-inside:avoid';\n\n/** Each page in a fixed-size block that ends the page, so a frame never spills onto the next. */\nconst toPagedSource = ({ pages, css, width, height }: PdfInput) => {\n if (pages.length === 0) {\n throw new Error('pdf: no pages; a document needs at least one');\n }\n const markup = pages\n .map(\n (page) =>\n `<div style=\"width:${width}px;height:${height}px;${PAGE_STYLE}\">${renderToStaticMarkup(page)}</div>`\n )\n .join('');\n return {\n ...fromHtml(withStylesheet(css, `<div>${markup}</div>`)),\n width,\n height,\n };\n};\n\n/** Runs `create` once for every caller. A rejection is forgotten, so the next call retries instead of inheriting it. */\nconst once = <T>(create: () => Promise<T>): (() => Promise<T>) => {\n let pending: Promise<T> | undefined;\n return () => {\n pending ??= create().catch((error: unknown) => {\n pending = undefined;\n throw error;\n });\n return pending;\n };\n};\n\n/**\n * Builds a {@link TakumiBackend} over one takumi renderer. `fonts` are\n * registered once, in order, before the first render; the PDF engine is\n * imported and created on the first `pdf`, and registers them again.\n */\nexport const createTakumiImageBackend = ({\n fonts = [],\n cacheMaxBytes,\n}: TakumiImageBackendOptions = {}): TakumiBackend => {\n const renderer = new Renderer(\n cacheMaxBytes === undefined ? undefined : { cacheMaxBytes }\n );\n\n /**\n * Registration is async and every render needs it, so each engine registers\n * once and is awaited. Sequential because the registration order is the\n * default fallback order.\n */\n const registerFonts = async <\n TEngine extends { registerFont(font: FontLoader): Promise<unknown> },\n >(\n engine: TEngine\n ): Promise<TEngine> => {\n for (const font of fonts) {\n await engine.registerFont(font);\n }\n return engine;\n };\n const ready = once(() => registerFonts(renderer));\n /** A second engine, imported and created on the first PDF, so a raster-only host never loads its wasm. */\n const pdfReady = once(async () => {\n const { PdfRenderer } = await import('takumi-pdf');\n return registerFonts(new PdfRenderer());\n });\n\n return {\n png: async (input, options = {}) => {\n await ready();\n const { node, css } = toTakumiSource(input);\n const renderOptions: RenderOptions = {\n width: input.width,\n height: input.height,\n format: 'png',\n css,\n };\n if (options.devicePixelRatio !== undefined) {\n renderOptions.devicePixelRatio = options.devicePixelRatio;\n }\n return renderer.render(node, renderOptions);\n },\n svg: async (input) => {\n await ready();\n const { node, css } = toTakumiSource(input);\n return renderer.renderSvg(node, {\n width: input.width,\n height: input.height,\n css,\n });\n },\n measure: async (input) => {\n await ready();\n const { node, css } = toTakumiSource(input);\n const measured = await renderer.measure(node, {\n width: input.width,\n height: input.height,\n css,\n });\n return toLayoutBox(measured, node);\n },\n pdf: async (input, options = {}) => {\n const engine = await pdfReady();\n const { node, css, width, height } = toPagedSource(input);\n const renderOptions: PdfRenderOptions = {\n ...options,\n size: { width, height },\n margin: 0,\n css,\n };\n // Copied out of wasm memory, which a later render can move.\n return Buffer.from(await engine.render(node, renderOptions));\n },\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAGL,QAAQ,GAET,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAgKnD,0FAA0F;AAC1F,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAa7D,2FAA2F;AAC3F,MAAM,OAAO,GAAG,CACd,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAS,EAC1B,CAAS,EACT,CAAS,EACT,KAAa,EACb,MAAc,EAC2C,EAAE;IAC3D,MAAM,OAAO,GAAG;QACd,CAAC,CAAC,EAAE,CAAC,CAAC;QACN,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC;QACd,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC;QACf,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC;KACxB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;IACzC,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC5B,OAAO;QACL,CAAC,EAAE,IAAI;QACP,CAAC,EAAE,GAAG;QACN,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI;QAC7B,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG;KAC9B,CAAC;AACJ,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAClB,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAgB,EAC1D,MAA8B,EACnB,EAAE;IACb,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,MAAM,EAAE,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,CAAC;IAClD,OAAO;QACL,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC;QAC1C,MAAM;QACN,MAAM;QACN,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QACpE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACvB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,GAAG,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC;SAC3D,CAAC,CAAC;QACH,GAAG,CAAC,MAAM,EAAE,UAAU,IAAI,EAAE,UAAU,EAAE,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC;QACnE,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CACtC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CACxD;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,GAAW,EAAE,EAAE,CACzC,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAE9D;;;;GAIG;AACH,MAAM,cAAc,GAAG,CAAC,GAAW,EAAE,MAAc,EAAE,EAAE,CACrD,UAAU,kBAAkB,CAAC,GAAG,CAAC,WAAW,MAAM,EAAE,CAAC;AAEvD,MAAM,cAAc,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,EAAc,EAAE,EAAE,CACnD,QAAQ,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AAEtC;;;GAGG;AACH,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,EAAE,CACnD,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC;AAE7D,yDAAyD;AACzD,MAAM,UAAU,GAAG,qDAAqD,CAAC;AAEzE,iGAAiG;AACjG,MAAM,aAAa,GAAG,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAY,EAAE,EAAE;IAChE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,MAAM,GAAG,KAAK;SACjB,GAAG,CACF,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACX,qBAAqB,KAAK,aAAa,MAAM,MAAM,UAAU,KAAK,IAAI,QAAQ,CACjF;SACA,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,OAAO;QACL,GAAG,QAAQ,CAAC,cAAc,CAAC,GAAG,EAAE,QAAQ,MAAM,QAAQ,CAAC,CAAC;QACxD,KAAK;QACL,MAAM;KACP,CAAC;AACJ,CAAC,CAAC;AAEF,wHAAwH;AACxH,MAAM,IAAI,GAAG,CAAI,MAAwB,EAAsB,EAAE;IAC/D,IAAI,OAA+B,CAAC;IACpC,OAAO,GAAG,EAAE;QACV,OAAO,KAAK,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC5C,OAAO,GAAG,SAAS,CAAC;YACpB,MAAM,KAAK,CAAC;QACd,CAAC,CAAC,CAAC;QACH,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,EACvC,KAAK,GAAG,EAAE,EACV,aAAa,MACgB,EAAE,EAAiB,EAAE;IAClD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAC3B,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAC5D,CAAC;IAEF;;;;OAIG;IACH,MAAM,aAAa,GAAG,KAAK,EAGzB,MAAe,EACG,EAAE;QACpB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClD,0GAA0G;IAC1G,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE;QAC/B,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACnD,OAAO,aAAa,CAAC,IAAI,WAAW,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,OAAO,EAAE,cAAc;QACvB,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,GAAG,CAAC,EAAE,gBAAgB,EAAE,GAAG,EAAE,EAAE,EAAE;YACzD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBAC1C,MAAM,IAAI,UAAU,CAClB,oDAAoD,KAAK,EAAE,CAC5D,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,EAAE,CAAC;YACd,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;YAC5C,MAAM,aAAa,GAAkB;gBACnC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC;gBACvC,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC;gBACzC,MAAM,EAAE,KAAK;gBACb,GAAG;aACJ,CAAC;YACF,IAAI,gBAAgB,KAAK,SAAS,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAClD,aAAa,CAAC,gBAAgB,GAAG,CAAC,gBAAgB,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC;YACnE,CAAC;YACD,OAAO,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QAC9C,CAAC;QACD,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACnB,MAAM,KAAK,EAAE,CAAC;YACd,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;YAC5C,OAAO,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE;gBAC9B,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,GAAG;aACJ,CAAC,CAAC;QACL,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,KAAK,EAAE,CAAC;YACd,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;YAC5C,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE;gBAC5C,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,GAAG;aACJ,CAAC,CAAC;YACH,OAAO,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC;QACD,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG,MAAM,QAAQ,EAAE,CAAC;YAChC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;YAC1D,MAAM,aAAa,GAAqB;gBACtC,GAAG,OAAO;gBACV,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;gBACvB,MAAM,EAAE,CAAC;gBACT,GAAG;aACJ,CAAC;YACF,4DAA4D;YAC5D,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC;QAC/D,CAAC;KACF,CAAC;AACJ,CAAC,CAAC","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nimport {\n type FontLoader,\n type MeasuredNode,\n Renderer,\n type RenderOptions,\n} from '@takumi-rs/core';\nimport { fromHtml } from '@takumi-rs/helpers/html';\nimport type {\n ImagesInput,\n RenderOptions as PdfRenderOptions,\n} from 'takumi-pdf';\n\n/**\n * The part of a `snapshot` surface result this backend reads: the markup, the\n * stylesheet it is laid out against, and the viewport it was measured for.\n *\n * Declared structurally rather than imported from `@elastic/isomer-runtime`,\n * so this backend depends on no isomer package. `SnapshotRenderResult` satisfies it.\n */\nexport interface ImageInput {\n /** Static markup with a single root, as image layout requires. */\n html: string;\n /** The stylesheet the markup is laid out against, `light-dark(…)` already resolved. */\n css: string;\n /** The viewport, in CSS pixels; the output is this size. */\n width: number;\n height: number;\n}\n\n/**\n * The part of a `snapshot` surface's `renderPages` result this backend reads:\n * one page's markup each, all laid out against one stylesheet at one size.\n * Structural like {@link ImageInput}; `SnapshotPagesResult` satisfies it.\n */\nexport interface PdfInput {\n /** One page per entry, in order, each static markup with a single root. */\n pages: readonly { html: string }[];\n /** One stylesheet collected across every page. */\n css: string;\n /** Every page's size, in CSS pixels. */\n width: number;\n height: number;\n}\n\n/** Options for {@link createTakumiImageBackend}. */\nexport interface TakumiImageBackendOptions {\n /**\n * Fonts to register, in fallback order. A pack names families in its theme\n * and this backend holds no opinion about which; nothing is registered by\n * default, so an unregistered family falls back to takumi's built-in face.\n *\n * WOFF and WOFF2 are accepted as well as raw sfnt — `@takumi-rs/core` builds\n * with takumi's `woff`/`woff2` features, so a `@fontsource/*` file can be\n * passed as-is, as raw bytes or a lazy `data()`. A `FontLoader` string is a\n * URL fetched on demand, not a filesystem path.\n */\n fonts?: readonly FontLoader[];\n /** Byte budget for takumi's resource cache. `0` disables it. */\n cacheMaxBytes?: number;\n}\n\n/** Per-render overrides. Geometry comes from the input, not from here. */\nexport interface TakumiRenderOptions {\n /**\n * Raster pixels per CSS pixel: the PNG is `input.width * scale` by\n * `input.height * scale`, rounded. A layout fixed in CSS pixels is\n * unchanged; rounding moves the viewport by under half a raster pixel. `2`\n * gives a 2x raster. Defaults to `1`.\n */\n scale?: number;\n /**\n * Takumi's zoom at a fixed canvas: the tree is laid out at\n * `input.width / devicePixelRatio` CSS pixels and magnified to fill the\n * canvas, so above `1` the PNG keeps its size and shows less of the frame.\n * For a larger raster, use {@link TakumiRenderOptions.scale}.\n */\n devicePixelRatio?: number;\n}\n\n/** Document properties written into a PDF. */\nexport interface TakumiPdfMetadata {\n title?: string;\n description?: string;\n authors?: string[];\n keywords?: string[];\n creator?: string;\n /** UTC, `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`. Fixing it makes the bytes stable across renders. */\n creationDate?: string;\n}\n\n/** Per-document options for {@link TakumiPdfBackend.pdf}. Geometry comes from the input. */\nexport interface TakumiPdfOptions {\n metadata?: TakumiPdfMetadata;\n /**\n * What a glyph no registered font covers becomes. The default, `'error'`,\n * rejects the render naming the code point, where `png` draws takumi's\n * built-in face instead; `'placeholder'` draws the font's glyph 0 and\n * `'blank'` draws nothing.\n */\n uncoveredText?: 'error' | 'placeholder' | 'blank';\n /**\n * Bytes for the `img` sources in the tree. The PDF engine fetches nothing,\n * so a remote `src` with no entry here draws blank; a `data:` URI needs none.\n */\n images?: ImagesInput;\n /** Writes a document outline from the headings. */\n outline?: boolean;\n /** BCP 47 tag for the document's language. */\n lang?: string;\n /** Paper color painted under every page; unset leaves the page empty. */\n backgroundColor?: string;\n}\n\n/** A laid-out element on the canvas, in pixels, with the elements nested in it. */\nexport interface LayoutBox {\n x: number;\n y: number;\n width: number;\n height: number;\n /** How much the box's x axis is scaled on the canvas, e.g. by `transform: scale()`. */\n scaleX: number;\n /** How much the box's y axis is scaled on the canvas. */\n scaleY: number;\n /** Whichever of `scaleX` and `scaleY` is further from 1, so any value but 1 means the box is scaled. */\n scale: number;\n /** Text laid out in this box, each run positioned on the canvas. */\n runs: { text: string; x: number; y: number; width: number; height: number }[];\n /**\n * The element's attributes other than `class`, `id`, and `style`, such as\n * `data-*`. Absent when it has none, or when its parent's laid-out children\n * do not line up with its elements, as when inline content folds into runs.\n */\n attributes?: Record<string, string>;\n children: LayoutBox[];\n}\n\n/**\n * Rasterizes the `snapshot` surface's output.\n *\n * Both methods are one call over takumi: the tree is serialized, `fromHtml`\n * turns it into a takumi node tree and lifts the stylesheet out, and takumi\n * lays the result out at the input's viewport.\n */\nexport interface TakumiImageBackend {\n /** Renders to an encoded PNG. */\n png(input: ImageInput, options?: TakumiRenderOptions): Promise<Buffer>;\n /** Renders to an SVG document. Vector output, so raster options do not apply. */\n svg(input: ImageInput): Promise<string>;\n}\n\n/** Writes the `snapshot` surface's pages as a PDF, one page each. */\nexport interface TakumiPdfBackend {\n /**\n * Renders one page per entry in `pages`, each the input's size with no\n * margin, so a frame fills its page. Vector output with selectable text and\n * the registered fonts subset and embedded. Throws on an empty `pages` list.\n */\n pdf(input: PdfInput, options?: TakumiPdfOptions): Promise<Buffer>;\n}\n\n/** A {@link TakumiImageBackend} that also measures layout. */\nexport interface TakumiMeasuringBackend extends TakumiImageBackend {\n /** Lays the input out as {@link TakumiImageBackend.png} would, and returns every box, e.g. to find content past its area. */\n measure(input: ImageInput): Promise<LayoutBox>;\n}\n\n/** The formats a {@link TakumiBackend} writes, for `createIsomerRuntime({ formats })`. */\nexport const TAKUMI_FORMATS = ['png', 'svg', 'pdf'] as const;\n\n/** Everything {@link createTakumiImageBackend} returns: raster, measure, and PDF. */\nexport interface TakumiBackend\n extends TakumiMeasuringBackend, TakumiPdfBackend {\n /** {@link TAKUMI_FORMATS}: one entry per method that writes a format. */\n readonly formats: typeof TAKUMI_FORMATS;\n}\n\ntype Matrix = MeasuredNode['transform'];\n\ntype SourceNode = ReturnType<typeof fromHtml>['node'];\n\n/** The canvas rectangle `transform` maps a local `width` × `height` box at `x`, `y` to. */\nconst mapRect = (\n [a, b, c, d, e, f]: Matrix,\n x: number,\n y: number,\n width: number,\n height: number\n): { x: number; y: number; width: number; height: number } => {\n const corners = [\n [x, y],\n [x + width, y],\n [x, y + height],\n [x + width, y + height],\n ].map(([px = 0, py = 0]) => [a * px + c * py + e, b * px + d * py + f]);\n const xs = corners.map(([cx = 0]) => cx);\n const ys = corners.map(([, cy = 0]) => cy);\n const left = Math.min(...xs);\n const top = Math.min(...ys);\n return {\n x: left,\n y: top,\n width: Math.max(...xs) - left,\n height: Math.max(...ys) - top,\n };\n};\n\n/**\n * Takumi reports each node's size in its own units with its full canvas\n * transform, and carries no attributes, so those come from the `fromHtml`\n * node at the same position.\n */\nconst toLayoutBox = (\n { width, height, transform, runs, children }: MeasuredNode,\n source: SourceNode | undefined\n): LayoutBox => {\n const [a, b, c, d] = transform;\n const scaleX = Math.hypot(a, b);\n const scaleY = Math.hypot(c, d);\n const sources = source?.type === 'container' ? (source.children ?? []) : [];\n const paired = sources.length === children.length;\n return {\n ...mapRect(transform, 0, 0, width, height),\n scaleX,\n scaleY,\n scale: Math.abs(scaleY - 1) > Math.abs(scaleX - 1) ? scaleY : scaleX,\n runs: runs.map((run) => ({\n text: run.text,\n ...mapRect(transform, run.x, run.y, run.width, run.height),\n })),\n ...(source?.attributes && { attributes: { ...source.attributes } }),\n children: children.map((child, index) =>\n toLayoutBox(child, paired ? sources[index] : undefined)\n ),\n };\n};\n\nconst escapeStyleEndTags = (css: string) =>\n css.replace(/<\\/style/gi, (tag) => tag.replace('/', '\\\\/'));\n\n/**\n * Inlines the stylesheet ahead of the markup, the form `fromHtml` lifts into\n * its own `css` list. Passing the stylesheet as a separate `CssInput` entry is\n * a different path through takumi and changes the committed PNG bytes.\n */\nconst withStylesheet = (css: string, markup: string) =>\n `<style>${escapeStyleEndTags(css)}</style>${markup}`;\n\nconst toTakumiSource = ({ html, css }: ImageInput) =>\n fromHtml(withStylesheet(css, html));\n\n/**\n * One side of a scaled canvas, in whole pixels. Takumi takes one ratio for\n * both axes, so the ratio stays `scale` and the canvas absorbs the rounding.\n */\nconst toRasterSide = (side: number, scale: number) =>\n scale === 1 ? side : Math.max(1, Math.round(side * scale));\n\n/** Inline, so no pack class name can collide with it. */\nconst PAGE_STYLE = 'overflow:hidden;break-after:page;break-inside:avoid';\n\n/** Each page in a fixed-size block that ends the page, so a frame never spills onto the next. */\nconst toPagedSource = ({ pages, css, width, height }: PdfInput) => {\n if (pages.length === 0) {\n throw new Error('pdf: no pages; a document needs at least one');\n }\n const markup = pages\n .map(\n ({ html }) =>\n `<div style=\"width:${width}px;height:${height}px;${PAGE_STYLE}\">${html}</div>`\n )\n .join('');\n return {\n ...fromHtml(withStylesheet(css, `<div>${markup}</div>`)),\n width,\n height,\n };\n};\n\n/** Runs `create` once for every caller. A rejection is forgotten, so the next call retries instead of inheriting it. */\nconst once = <T>(create: () => Promise<T>): (() => Promise<T>) => {\n let pending: Promise<T> | undefined;\n return () => {\n pending ??= create().catch((error: unknown) => {\n pending = undefined;\n throw error;\n });\n return pending;\n };\n};\n\n/**\n * Builds a {@link TakumiBackend} over one takumi renderer. `fonts` are\n * registered once, in order, before the first render; the PDF engine is\n * imported and created on the first `pdf`, and registers them again.\n */\nexport const createTakumiImageBackend = ({\n fonts = [],\n cacheMaxBytes,\n}: TakumiImageBackendOptions = {}): TakumiBackend => {\n const renderer = new Renderer(\n cacheMaxBytes === undefined ? undefined : { cacheMaxBytes }\n );\n\n /**\n * Registration is async and every render needs it, so each engine registers\n * once and is awaited. Sequential because the registration order is the\n * default fallback order.\n */\n const registerFonts = async <\n TEngine extends { registerFont(font: FontLoader): Promise<unknown> },\n >(\n engine: TEngine\n ): Promise<TEngine> => {\n for (const font of fonts) {\n await engine.registerFont(font);\n }\n return engine;\n };\n const ready = once(() => registerFonts(renderer));\n /** A second engine, imported and created on the first PDF, so a raster-only host never loads its wasm. */\n const pdfReady = once(async () => {\n const { PdfRenderer } = await import('takumi-pdf');\n return registerFonts(new PdfRenderer());\n });\n\n return {\n formats: TAKUMI_FORMATS,\n png: async (input, { scale = 1, devicePixelRatio } = {}) => {\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new RangeError(\n `png: scale must be a positive finite number, got ${scale}`\n );\n }\n await ready();\n const { node, css } = toTakumiSource(input);\n const renderOptions: RenderOptions = {\n width: toRasterSide(input.width, scale),\n height: toRasterSide(input.height, scale),\n format: 'png',\n css,\n };\n if (devicePixelRatio !== undefined || scale !== 1) {\n renderOptions.devicePixelRatio = (devicePixelRatio ?? 1) * scale;\n }\n return renderer.render(node, renderOptions);\n },\n svg: async (input) => {\n await ready();\n const { node, css } = toTakumiSource(input);\n return renderer.renderSvg(node, {\n width: input.width,\n height: input.height,\n css,\n });\n },\n measure: async (input) => {\n await ready();\n const { node, css } = toTakumiSource(input);\n const measured = await renderer.measure(node, {\n width: input.width,\n height: input.height,\n css,\n });\n return toLayoutBox(measured, node);\n },\n pdf: async (input, options = {}) => {\n const engine = await pdfReady();\n const { node, css, width, height } = toPagedSource(input);\n const renderOptions: PdfRenderOptions = {\n ...options,\n size: { width, height },\n margin: 0,\n css,\n };\n // Copied out of wasm memory, which a later render can move.\n return Buffer.from(await engine.render(node, renderOptions));\n },\n };\n};\n"]}
|
package/dist/cjs/backend.js
CHANGED
|
@@ -39,10 +39,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.createTakumiImageBackend = void 0;
|
|
43
|
-
const server_1 = require("react-dom/server");
|
|
42
|
+
exports.createTakumiImageBackend = exports.TAKUMI_FORMATS = void 0;
|
|
44
43
|
const core_1 = require("@takumi-rs/core");
|
|
45
44
|
const html_1 = require("@takumi-rs/helpers/html");
|
|
45
|
+
/** The formats a {@link TakumiBackend} writes, for `createIsomerRuntime({ formats })`. */
|
|
46
|
+
exports.TAKUMI_FORMATS = ['png', 'svg', 'pdf'];
|
|
46
47
|
/** The canvas rectangle `transform` maps a local `width` × `height` box at `x`, `y` to. */
|
|
47
48
|
const mapRect = ([a, b, c, d, e, f], x, y, width, height) => {
|
|
48
49
|
const corners = [
|
|
@@ -93,7 +94,12 @@ const escapeStyleEndTags = (css) => css.replace(/<\/style/gi, (tag) => tag.repla
|
|
|
93
94
|
* a different path through takumi and changes the committed PNG bytes.
|
|
94
95
|
*/
|
|
95
96
|
const withStylesheet = (css, markup) => `<style>${escapeStyleEndTags(css)}</style>${markup}`;
|
|
96
|
-
const toTakumiSource = ({
|
|
97
|
+
const toTakumiSource = ({ html, css }) => (0, html_1.fromHtml)(withStylesheet(css, html));
|
|
98
|
+
/**
|
|
99
|
+
* One side of a scaled canvas, in whole pixels. Takumi takes one ratio for
|
|
100
|
+
* both axes, so the ratio stays `scale` and the canvas absorbs the rounding.
|
|
101
|
+
*/
|
|
102
|
+
const toRasterSide = (side, scale) => scale === 1 ? side : Math.max(1, Math.round(side * scale));
|
|
97
103
|
/** Inline, so no pack class name can collide with it. */
|
|
98
104
|
const PAGE_STYLE = 'overflow:hidden;break-after:page;break-inside:avoid';
|
|
99
105
|
/** Each page in a fixed-size block that ends the page, so a frame never spills onto the next. */
|
|
@@ -102,7 +108,7 @@ const toPagedSource = ({ pages, css, width, height }) => {
|
|
|
102
108
|
throw new Error('pdf: no pages; a document needs at least one');
|
|
103
109
|
}
|
|
104
110
|
const markup = pages
|
|
105
|
-
.map((
|
|
111
|
+
.map(({ html }) => `<div style="width:${width}px;height:${height}px;${PAGE_STYLE}">${html}</div>`)
|
|
106
112
|
.join('');
|
|
107
113
|
return {
|
|
108
114
|
...(0, html_1.fromHtml)(withStylesheet(css, `<div>${markup}</div>`)),
|
|
@@ -146,17 +152,21 @@ const createTakumiImageBackend = ({ fonts = [], cacheMaxBytes, } = {}) => {
|
|
|
146
152
|
return registerFonts(new PdfRenderer());
|
|
147
153
|
});
|
|
148
154
|
return {
|
|
149
|
-
|
|
155
|
+
formats: exports.TAKUMI_FORMATS,
|
|
156
|
+
png: async (input, { scale = 1, devicePixelRatio } = {}) => {
|
|
157
|
+
if (!Number.isFinite(scale) || scale <= 0) {
|
|
158
|
+
throw new RangeError(`png: scale must be a positive finite number, got ${scale}`);
|
|
159
|
+
}
|
|
150
160
|
await ready();
|
|
151
161
|
const { node, css } = toTakumiSource(input);
|
|
152
162
|
const renderOptions = {
|
|
153
|
-
width: input.width,
|
|
154
|
-
height: input.height,
|
|
163
|
+
width: toRasterSide(input.width, scale),
|
|
164
|
+
height: toRasterSide(input.height, scale),
|
|
155
165
|
format: 'png',
|
|
156
166
|
css,
|
|
157
167
|
};
|
|
158
|
-
if (
|
|
159
|
-
renderOptions.devicePixelRatio =
|
|
168
|
+
if (devicePixelRatio !== undefined || scale !== 1) {
|
|
169
|
+
renderOptions.devicePixelRatio = (devicePixelRatio ?? 1) * scale;
|
|
160
170
|
}
|
|
161
171
|
return renderer.render(node, renderOptions);
|
|
162
172
|
},
|
package/dist/cjs/backend.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/backend.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nimport type { ReactNode } from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport {\n type FontLoader,\n type MeasuredNode,\n Renderer,\n type RenderOptions,\n} from '@takumi-rs/core';\nimport { fromHtml } from '@takumi-rs/helpers/html';\nimport type {\n ImagesInput,\n RenderOptions as PdfRenderOptions,\n} from 'takumi-pdf';\n\n/**\n * What the `svg` surface returns: a React tree and the stylesheet it is laid\n * out against, plus the viewport it was measured for.\n *\n * Declared structurally rather than imported from `@elastic/isomer-runtime`,\n * so this backend depends on no isomer package. `SvgRenderResult` satisfies it.\n */\nexport interface ImageInput {\n /** A single root, as image layout requires: one element, string, or number, never an array or fragment. */\n element: ReactNode;\n /** The stylesheet the tree is laid out against, `light-dark(…)` already resolved. */\n css: string;\n /** The viewport, in CSS pixels; the output is this size. */\n width: number;\n height: number;\n}\n\n/**\n * What the `svg` surface's `renderPages` returns: one root per page, all laid\n * out against one stylesheet at one size. Structural like {@link ImageInput};\n * `SvgPagesResult` satisfies it.\n */\nexport interface PdfInput {\n /** One root per page, in order. */\n pages: readonly ReactNode[];\n /** One stylesheet collected across every page. */\n css: string;\n /** Every page's size, in CSS pixels. */\n width: number;\n height: number;\n}\n\n/** Options for {@link createTakumiImageBackend}. */\nexport interface TakumiImageBackendOptions {\n /**\n * Fonts to register, in fallback order. A pack names families in its theme\n * and this backend holds no opinion about which; nothing is registered by\n * default, so an unregistered family falls back to takumi's built-in face.\n *\n * WOFF and WOFF2 are accepted as well as raw sfnt — `@takumi-rs/core` builds\n * with takumi's `woff`/`woff2` features, so a `@fontsource/*` file can be\n * passed as-is, as raw bytes or a lazy `data()`. A `FontLoader` string is a\n * URL fetched on demand, not a filesystem path.\n */\n fonts?: readonly FontLoader[];\n /** Byte budget for takumi's resource cache. `0` disables it. */\n cacheMaxBytes?: number;\n}\n\n/** Per-render overrides. Geometry comes from the input, not from here. */\nexport interface TakumiRenderOptions {\n /**\n * Raises rendering fidelity (sharper text and gradients) at a fixed output\n * size — the PNG stays sized to `input.width` / `input.height` regardless\n * of this value; it does not produce a larger raster.\n */\n devicePixelRatio?: number;\n}\n\n/** Document properties written into a PDF. */\nexport interface TakumiPdfMetadata {\n title?: string;\n description?: string;\n authors?: string[];\n keywords?: string[];\n creator?: string;\n /** UTC, `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`. Fixing it makes the bytes stable across renders. */\n creationDate?: string;\n}\n\n/** Per-document options for {@link TakumiPdfBackend.pdf}. Geometry comes from the input. */\nexport interface TakumiPdfOptions {\n metadata?: TakumiPdfMetadata;\n /**\n * What a glyph no registered font covers becomes. The default, `'error'`,\n * rejects the render naming the code point, where `png` draws takumi's\n * built-in face instead; `'placeholder'` draws the font's glyph 0 and\n * `'blank'` draws nothing.\n */\n uncoveredText?: 'error' | 'placeholder' | 'blank';\n /**\n * Bytes for the `img` sources in the tree. The PDF engine fetches nothing,\n * so a remote `src` with no entry here draws blank; a `data:` URI needs none.\n */\n images?: ImagesInput;\n /** Writes a document outline from the headings. */\n outline?: boolean;\n /** BCP 47 tag for the document's language. */\n lang?: string;\n /** Paper color painted under every page; unset leaves the page empty. */\n backgroundColor?: string;\n}\n\n/** A laid-out element on the canvas, in pixels, with the elements nested in it. */\nexport interface LayoutBox {\n x: number;\n y: number;\n width: number;\n height: number;\n /** How much the box's x axis is scaled on the canvas, e.g. by `transform: scale()`. */\n scaleX: number;\n /** How much the box's y axis is scaled on the canvas. */\n scaleY: number;\n /** Whichever of `scaleX` and `scaleY` is further from 1, so any value but 1 means the box is scaled. */\n scale: number;\n /** Text laid out in this box, each run positioned on the canvas. */\n runs: { text: string; x: number; y: number; width: number; height: number }[];\n /**\n * The element's attributes other than `class`, `id`, and `style`, such as\n * `data-*`. Absent when it has none, or when its parent's laid-out children\n * do not line up with its elements, as when inline content folds into runs.\n */\n attributes?: Record<string, string>;\n children: LayoutBox[];\n}\n\n/**\n * Rasterizes the `svg` surface's output.\n *\n * Both methods are one call over takumi: the tree is serialized, `fromHtml`\n * turns it into a takumi node tree and lifts the stylesheet out, and takumi\n * lays the result out at the input's viewport.\n */\nexport interface TakumiImageBackend {\n /** Renders to an encoded PNG. */\n png(input: ImageInput, options?: TakumiRenderOptions): Promise<Buffer>;\n /** Renders to an SVG document. Vector output, so raster options do not apply. */\n svg(input: ImageInput): Promise<string>;\n}\n\n/** Writes the `svg` surface's pages as a PDF, one page each. */\nexport interface TakumiPdfBackend {\n /**\n * Renders one page per entry in `pages`, each the input's size with no\n * margin, so a frame fills its page. Vector output with selectable text and\n * the registered fonts subset and embedded. Throws on an empty `pages` list.\n */\n pdf(input: PdfInput, options?: TakumiPdfOptions): Promise<Buffer>;\n}\n\n/** A {@link TakumiImageBackend} that also measures layout. */\nexport interface TakumiMeasuringBackend extends TakumiImageBackend {\n /** Lays the input out as {@link TakumiImageBackend.png} would, and returns every box, e.g. to find content past its area. */\n measure(input: ImageInput): Promise<LayoutBox>;\n}\n\n/** Everything {@link createTakumiImageBackend} returns: raster, measure, and PDF. */\nexport interface TakumiBackend\n extends TakumiMeasuringBackend, TakumiPdfBackend {}\n\ntype Matrix = MeasuredNode['transform'];\n\ntype SourceNode = ReturnType<typeof fromHtml>['node'];\n\n/** The canvas rectangle `transform` maps a local `width` × `height` box at `x`, `y` to. */\nconst mapRect = (\n [a, b, c, d, e, f]: Matrix,\n x: number,\n y: number,\n width: number,\n height: number\n): { x: number; y: number; width: number; height: number } => {\n const corners = [\n [x, y],\n [x + width, y],\n [x, y + height],\n [x + width, y + height],\n ].map(([px = 0, py = 0]) => [a * px + c * py + e, b * px + d * py + f]);\n const xs = corners.map(([cx = 0]) => cx);\n const ys = corners.map(([, cy = 0]) => cy);\n const left = Math.min(...xs);\n const top = Math.min(...ys);\n return {\n x: left,\n y: top,\n width: Math.max(...xs) - left,\n height: Math.max(...ys) - top,\n };\n};\n\n/**\n * Takumi reports each node's size in its own units with its full canvas\n * transform, and carries no attributes, so those come from the `fromHtml`\n * node at the same position.\n */\nconst toLayoutBox = (\n { width, height, transform, runs, children }: MeasuredNode,\n source: SourceNode | undefined\n): LayoutBox => {\n const [a, b, c, d] = transform;\n const scaleX = Math.hypot(a, b);\n const scaleY = Math.hypot(c, d);\n const sources = source?.type === 'container' ? (source.children ?? []) : [];\n const paired = sources.length === children.length;\n return {\n ...mapRect(transform, 0, 0, width, height),\n scaleX,\n scaleY,\n scale: Math.abs(scaleY - 1) > Math.abs(scaleX - 1) ? scaleY : scaleX,\n runs: runs.map((run) => ({\n text: run.text,\n ...mapRect(transform, run.x, run.y, run.width, run.height),\n })),\n ...(source?.attributes && { attributes: { ...source.attributes } }),\n children: children.map((child, index) =>\n toLayoutBox(child, paired ? sources[index] : undefined)\n ),\n };\n};\n\nconst escapeStyleEndTags = (css: string) =>\n css.replace(/<\\/style/gi, (tag) => tag.replace('/', '\\\\/'));\n\n/**\n * Inlines the stylesheet ahead of the markup, the form `fromHtml` lifts into\n * its own `css` list. Passing the stylesheet as a separate `CssInput` entry is\n * a different path through takumi and changes the committed PNG bytes.\n */\nconst withStylesheet = (css: string, markup: string) =>\n `<style>${escapeStyleEndTags(css)}</style>${markup}`;\n\nconst toTakumiSource = ({ element, css }: ImageInput) =>\n fromHtml(withStylesheet(css, renderToStaticMarkup(element)));\n\n/** Inline, so no pack class name can collide with it. */\nconst PAGE_STYLE = 'overflow:hidden;break-after:page;break-inside:avoid';\n\n/** Each page in a fixed-size block that ends the page, so a frame never spills onto the next. */\nconst toPagedSource = ({ pages, css, width, height }: PdfInput) => {\n if (pages.length === 0) {\n throw new Error('pdf: no pages; a document needs at least one');\n }\n const markup = pages\n .map(\n (page) =>\n `<div style=\"width:${width}px;height:${height}px;${PAGE_STYLE}\">${renderToStaticMarkup(page)}</div>`\n )\n .join('');\n return {\n ...fromHtml(withStylesheet(css, `<div>${markup}</div>`)),\n width,\n height,\n };\n};\n\n/** Runs `create` once for every caller. A rejection is forgotten, so the next call retries instead of inheriting it. */\nconst once = <T>(create: () => Promise<T>): (() => Promise<T>) => {\n let pending: Promise<T> | undefined;\n return () => {\n pending ??= create().catch((error: unknown) => {\n pending = undefined;\n throw error;\n });\n return pending;\n };\n};\n\n/**\n * Builds a {@link TakumiBackend} over one takumi renderer. `fonts` are\n * registered once, in order, before the first render; the PDF engine is\n * imported and created on the first `pdf`, and registers them again.\n */\nexport const createTakumiImageBackend = ({\n fonts = [],\n cacheMaxBytes,\n}: TakumiImageBackendOptions = {}): TakumiBackend => {\n const renderer = new Renderer(\n cacheMaxBytes === undefined ? undefined : { cacheMaxBytes }\n );\n\n /**\n * Registration is async and every render needs it, so each engine registers\n * once and is awaited. Sequential because the registration order is the\n * default fallback order.\n */\n const registerFonts = async <\n TEngine extends { registerFont(font: FontLoader): Promise<unknown> },\n >(\n engine: TEngine\n ): Promise<TEngine> => {\n for (const font of fonts) {\n await engine.registerFont(font);\n }\n return engine;\n };\n const ready = once(() => registerFonts(renderer));\n /** A second engine, imported and created on the first PDF, so a raster-only host never loads its wasm. */\n const pdfReady = once(async () => {\n const { PdfRenderer } = await import('takumi-pdf');\n return registerFonts(new PdfRenderer());\n });\n\n return {\n png: async (input, options = {}) => {\n await ready();\n const { node, css } = toTakumiSource(input);\n const renderOptions: RenderOptions = {\n width: input.width,\n height: input.height,\n format: 'png',\n css,\n };\n if (options.devicePixelRatio !== undefined) {\n renderOptions.devicePixelRatio = options.devicePixelRatio;\n }\n return renderer.render(node, renderOptions);\n },\n svg: async (input) => {\n await ready();\n const { node, css } = toTakumiSource(input);\n return renderer.renderSvg(node, {\n width: input.width,\n height: input.height,\n css,\n });\n },\n measure: async (input) => {\n await ready();\n const { node, css } = toTakumiSource(input);\n const measured = await renderer.measure(node, {\n width: input.width,\n height: input.height,\n css,\n });\n return toLayoutBox(measured, node);\n },\n pdf: async (input, options = {}) => {\n const engine = await pdfReady();\n const { node, css, width, height } = toPagedSource(input);\n const renderOptions: PdfRenderOptions = {\n ...options,\n size: { width, height },\n margin: 0,\n css,\n };\n // Copied out of wasm memory, which a later render can move.\n return Buffer.from(await engine.render(node, renderOptions));\n },\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/backend.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nimport {\n type FontLoader,\n type MeasuredNode,\n Renderer,\n type RenderOptions,\n} from '@takumi-rs/core';\nimport { fromHtml } from '@takumi-rs/helpers/html';\nimport type {\n ImagesInput,\n RenderOptions as PdfRenderOptions,\n} from 'takumi-pdf';\n\n/**\n * The part of a `snapshot` surface result this backend reads: the markup, the\n * stylesheet it is laid out against, and the viewport it was measured for.\n *\n * Declared structurally rather than imported from `@elastic/isomer-runtime`,\n * so this backend depends on no isomer package. `SnapshotRenderResult` satisfies it.\n */\nexport interface ImageInput {\n /** Static markup with a single root, as image layout requires. */\n html: string;\n /** The stylesheet the markup is laid out against, `light-dark(…)` already resolved. */\n css: string;\n /** The viewport, in CSS pixels; the output is this size. */\n width: number;\n height: number;\n}\n\n/**\n * The part of a `snapshot` surface's `renderPages` result this backend reads:\n * one page's markup each, all laid out against one stylesheet at one size.\n * Structural like {@link ImageInput}; `SnapshotPagesResult` satisfies it.\n */\nexport interface PdfInput {\n /** One page per entry, in order, each static markup with a single root. */\n pages: readonly { html: string }[];\n /** One stylesheet collected across every page. */\n css: string;\n /** Every page's size, in CSS pixels. */\n width: number;\n height: number;\n}\n\n/** Options for {@link createTakumiImageBackend}. */\nexport interface TakumiImageBackendOptions {\n /**\n * Fonts to register, in fallback order. A pack names families in its theme\n * and this backend holds no opinion about which; nothing is registered by\n * default, so an unregistered family falls back to takumi's built-in face.\n *\n * WOFF and WOFF2 are accepted as well as raw sfnt — `@takumi-rs/core` builds\n * with takumi's `woff`/`woff2` features, so a `@fontsource/*` file can be\n * passed as-is, as raw bytes or a lazy `data()`. A `FontLoader` string is a\n * URL fetched on demand, not a filesystem path.\n */\n fonts?: readonly FontLoader[];\n /** Byte budget for takumi's resource cache. `0` disables it. */\n cacheMaxBytes?: number;\n}\n\n/** Per-render overrides. Geometry comes from the input, not from here. */\nexport interface TakumiRenderOptions {\n /**\n * Raster pixels per CSS pixel: the PNG is `input.width * scale` by\n * `input.height * scale`, rounded. A layout fixed in CSS pixels is\n * unchanged; rounding moves the viewport by under half a raster pixel. `2`\n * gives a 2x raster. Defaults to `1`.\n */\n scale?: number;\n /**\n * Takumi's zoom at a fixed canvas: the tree is laid out at\n * `input.width / devicePixelRatio` CSS pixels and magnified to fill the\n * canvas, so above `1` the PNG keeps its size and shows less of the frame.\n * For a larger raster, use {@link TakumiRenderOptions.scale}.\n */\n devicePixelRatio?: number;\n}\n\n/** Document properties written into a PDF. */\nexport interface TakumiPdfMetadata {\n title?: string;\n description?: string;\n authors?: string[];\n keywords?: string[];\n creator?: string;\n /** UTC, `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`. Fixing it makes the bytes stable across renders. */\n creationDate?: string;\n}\n\n/** Per-document options for {@link TakumiPdfBackend.pdf}. Geometry comes from the input. */\nexport interface TakumiPdfOptions {\n metadata?: TakumiPdfMetadata;\n /**\n * What a glyph no registered font covers becomes. The default, `'error'`,\n * rejects the render naming the code point, where `png` draws takumi's\n * built-in face instead; `'placeholder'` draws the font's glyph 0 and\n * `'blank'` draws nothing.\n */\n uncoveredText?: 'error' | 'placeholder' | 'blank';\n /**\n * Bytes for the `img` sources in the tree. The PDF engine fetches nothing,\n * so a remote `src` with no entry here draws blank; a `data:` URI needs none.\n */\n images?: ImagesInput;\n /** Writes a document outline from the headings. */\n outline?: boolean;\n /** BCP 47 tag for the document's language. */\n lang?: string;\n /** Paper color painted under every page; unset leaves the page empty. */\n backgroundColor?: string;\n}\n\n/** A laid-out element on the canvas, in pixels, with the elements nested in it. */\nexport interface LayoutBox {\n x: number;\n y: number;\n width: number;\n height: number;\n /** How much the box's x axis is scaled on the canvas, e.g. by `transform: scale()`. */\n scaleX: number;\n /** How much the box's y axis is scaled on the canvas. */\n scaleY: number;\n /** Whichever of `scaleX` and `scaleY` is further from 1, so any value but 1 means the box is scaled. */\n scale: number;\n /** Text laid out in this box, each run positioned on the canvas. */\n runs: { text: string; x: number; y: number; width: number; height: number }[];\n /**\n * The element's attributes other than `class`, `id`, and `style`, such as\n * `data-*`. Absent when it has none, or when its parent's laid-out children\n * do not line up with its elements, as when inline content folds into runs.\n */\n attributes?: Record<string, string>;\n children: LayoutBox[];\n}\n\n/**\n * Rasterizes the `snapshot` surface's output.\n *\n * Both methods are one call over takumi: the tree is serialized, `fromHtml`\n * turns it into a takumi node tree and lifts the stylesheet out, and takumi\n * lays the result out at the input's viewport.\n */\nexport interface TakumiImageBackend {\n /** Renders to an encoded PNG. */\n png(input: ImageInput, options?: TakumiRenderOptions): Promise<Buffer>;\n /** Renders to an SVG document. Vector output, so raster options do not apply. */\n svg(input: ImageInput): Promise<string>;\n}\n\n/** Writes the `snapshot` surface's pages as a PDF, one page each. */\nexport interface TakumiPdfBackend {\n /**\n * Renders one page per entry in `pages`, each the input's size with no\n * margin, so a frame fills its page. Vector output with selectable text and\n * the registered fonts subset and embedded. Throws on an empty `pages` list.\n */\n pdf(input: PdfInput, options?: TakumiPdfOptions): Promise<Buffer>;\n}\n\n/** A {@link TakumiImageBackend} that also measures layout. */\nexport interface TakumiMeasuringBackend extends TakumiImageBackend {\n /** Lays the input out as {@link TakumiImageBackend.png} would, and returns every box, e.g. to find content past its area. */\n measure(input: ImageInput): Promise<LayoutBox>;\n}\n\n/** The formats a {@link TakumiBackend} writes, for `createIsomerRuntime({ formats })`. */\nexport const TAKUMI_FORMATS = ['png', 'svg', 'pdf'] as const;\n\n/** Everything {@link createTakumiImageBackend} returns: raster, measure, and PDF. */\nexport interface TakumiBackend\n extends TakumiMeasuringBackend, TakumiPdfBackend {\n /** {@link TAKUMI_FORMATS}: one entry per method that writes a format. */\n readonly formats: typeof TAKUMI_FORMATS;\n}\n\ntype Matrix = MeasuredNode['transform'];\n\ntype SourceNode = ReturnType<typeof fromHtml>['node'];\n\n/** The canvas rectangle `transform` maps a local `width` × `height` box at `x`, `y` to. */\nconst mapRect = (\n [a, b, c, d, e, f]: Matrix,\n x: number,\n y: number,\n width: number,\n height: number\n): { x: number; y: number; width: number; height: number } => {\n const corners = [\n [x, y],\n [x + width, y],\n [x, y + height],\n [x + width, y + height],\n ].map(([px = 0, py = 0]) => [a * px + c * py + e, b * px + d * py + f]);\n const xs = corners.map(([cx = 0]) => cx);\n const ys = corners.map(([, cy = 0]) => cy);\n const left = Math.min(...xs);\n const top = Math.min(...ys);\n return {\n x: left,\n y: top,\n width: Math.max(...xs) - left,\n height: Math.max(...ys) - top,\n };\n};\n\n/**\n * Takumi reports each node's size in its own units with its full canvas\n * transform, and carries no attributes, so those come from the `fromHtml`\n * node at the same position.\n */\nconst toLayoutBox = (\n { width, height, transform, runs, children }: MeasuredNode,\n source: SourceNode | undefined\n): LayoutBox => {\n const [a, b, c, d] = transform;\n const scaleX = Math.hypot(a, b);\n const scaleY = Math.hypot(c, d);\n const sources = source?.type === 'container' ? (source.children ?? []) : [];\n const paired = sources.length === children.length;\n return {\n ...mapRect(transform, 0, 0, width, height),\n scaleX,\n scaleY,\n scale: Math.abs(scaleY - 1) > Math.abs(scaleX - 1) ? scaleY : scaleX,\n runs: runs.map((run) => ({\n text: run.text,\n ...mapRect(transform, run.x, run.y, run.width, run.height),\n })),\n ...(source?.attributes && { attributes: { ...source.attributes } }),\n children: children.map((child, index) =>\n toLayoutBox(child, paired ? sources[index] : undefined)\n ),\n };\n};\n\nconst escapeStyleEndTags = (css: string) =>\n css.replace(/<\\/style/gi, (tag) => tag.replace('/', '\\\\/'));\n\n/**\n * Inlines the stylesheet ahead of the markup, the form `fromHtml` lifts into\n * its own `css` list. Passing the stylesheet as a separate `CssInput` entry is\n * a different path through takumi and changes the committed PNG bytes.\n */\nconst withStylesheet = (css: string, markup: string) =>\n `<style>${escapeStyleEndTags(css)}</style>${markup}`;\n\nconst toTakumiSource = ({ html, css }: ImageInput) =>\n fromHtml(withStylesheet(css, html));\n\n/**\n * One side of a scaled canvas, in whole pixels. Takumi takes one ratio for\n * both axes, so the ratio stays `scale` and the canvas absorbs the rounding.\n */\nconst toRasterSide = (side: number, scale: number) =>\n scale === 1 ? side : Math.max(1, Math.round(side * scale));\n\n/** Inline, so no pack class name can collide with it. */\nconst PAGE_STYLE = 'overflow:hidden;break-after:page;break-inside:avoid';\n\n/** Each page in a fixed-size block that ends the page, so a frame never spills onto the next. */\nconst toPagedSource = ({ pages, css, width, height }: PdfInput) => {\n if (pages.length === 0) {\n throw new Error('pdf: no pages; a document needs at least one');\n }\n const markup = pages\n .map(\n ({ html }) =>\n `<div style=\"width:${width}px;height:${height}px;${PAGE_STYLE}\">${html}</div>`\n )\n .join('');\n return {\n ...fromHtml(withStylesheet(css, `<div>${markup}</div>`)),\n width,\n height,\n };\n};\n\n/** Runs `create` once for every caller. A rejection is forgotten, so the next call retries instead of inheriting it. */\nconst once = <T>(create: () => Promise<T>): (() => Promise<T>) => {\n let pending: Promise<T> | undefined;\n return () => {\n pending ??= create().catch((error: unknown) => {\n pending = undefined;\n throw error;\n });\n return pending;\n };\n};\n\n/**\n * Builds a {@link TakumiBackend} over one takumi renderer. `fonts` are\n * registered once, in order, before the first render; the PDF engine is\n * imported and created on the first `pdf`, and registers them again.\n */\nexport const createTakumiImageBackend = ({\n fonts = [],\n cacheMaxBytes,\n}: TakumiImageBackendOptions = {}): TakumiBackend => {\n const renderer = new Renderer(\n cacheMaxBytes === undefined ? undefined : { cacheMaxBytes }\n );\n\n /**\n * Registration is async and every render needs it, so each engine registers\n * once and is awaited. Sequential because the registration order is the\n * default fallback order.\n */\n const registerFonts = async <\n TEngine extends { registerFont(font: FontLoader): Promise<unknown> },\n >(\n engine: TEngine\n ): Promise<TEngine> => {\n for (const font of fonts) {\n await engine.registerFont(font);\n }\n return engine;\n };\n const ready = once(() => registerFonts(renderer));\n /** A second engine, imported and created on the first PDF, so a raster-only host never loads its wasm. */\n const pdfReady = once(async () => {\n const { PdfRenderer } = await import('takumi-pdf');\n return registerFonts(new PdfRenderer());\n });\n\n return {\n formats: TAKUMI_FORMATS,\n png: async (input, { scale = 1, devicePixelRatio } = {}) => {\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new RangeError(\n `png: scale must be a positive finite number, got ${scale}`\n );\n }\n await ready();\n const { node, css } = toTakumiSource(input);\n const renderOptions: RenderOptions = {\n width: toRasterSide(input.width, scale),\n height: toRasterSide(input.height, scale),\n format: 'png',\n css,\n };\n if (devicePixelRatio !== undefined || scale !== 1) {\n renderOptions.devicePixelRatio = (devicePixelRatio ?? 1) * scale;\n }\n return renderer.render(node, renderOptions);\n },\n svg: async (input) => {\n await ready();\n const { node, css } = toTakumiSource(input);\n return renderer.renderSvg(node, {\n width: input.width,\n height: input.height,\n css,\n });\n },\n measure: async (input) => {\n await ready();\n const { node, css } = toTakumiSource(input);\n const measured = await renderer.measure(node, {\n width: input.width,\n height: input.height,\n css,\n });\n return toLayoutBox(measured, node);\n },\n pdf: async (input, options = {}) => {\n const engine = await pdfReady();\n const { node, css, width, height } = toPagedSource(input);\n const renderOptions: PdfRenderOptions = {\n ...options,\n size: { width, height },\n margin: 0,\n css,\n };\n // Copied out of wasm memory, which a later render can move.\n return Buffer.from(await engine.render(node, renderOptions));\n },\n };\n};\n"]}
|
package/dist/cjs/index.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* 2.0.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.renderPng = exports.renderPdf = exports.createTakumiImageBackend = void 0;
|
|
9
|
+
exports.renderPng = exports.renderPdf = exports.createTakumiImageBackend = exports.TAKUMI_FORMATS = void 0;
|
|
10
10
|
var backend_1 = require("./backend");
|
|
11
|
+
Object.defineProperty(exports, "TAKUMI_FORMATS", { enumerable: true, get: function () { return backend_1.TAKUMI_FORMATS; } });
|
|
11
12
|
Object.defineProperty(exports, "createTakumiImageBackend", { enumerable: true, get: function () { return backend_1.createTakumiImageBackend; } });
|
|
12
13
|
var render_pdf_1 = require("./render_pdf");
|
|
13
14
|
Object.defineProperty(exports, "renderPdf", { enumerable: true, get: function () { return render_pdf_1.renderPdf; } });
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEH,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEH,qCAcmB;AAFjB,yGAAA,cAAc,OAAA;AACd,mHAAA,wBAAwB,OAAA;AAE1B,2CAKsB;AADpB,uGAAA,SAAS,OAAA;AAEX,2CAQsB;AADpB,uGAAA,SAAS,OAAA","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nexport {\n type ImageInput,\n type LayoutBox,\n type PdfInput,\n type TakumiBackend,\n type TakumiImageBackend,\n type TakumiImageBackendOptions,\n type TakumiMeasuringBackend,\n type TakumiPdfBackend,\n type TakumiPdfMetadata,\n type TakumiPdfOptions,\n type TakumiRenderOptions,\n TAKUMI_FORMATS,\n createTakumiImageBackend,\n} from './backend';\nexport {\n type PdfRuntime,\n type RenderPdfOptions,\n type RenderPdfResult,\n renderPdf,\n} from './render_pdf';\nexport {\n type PngCheckedValidationResult,\n type PngRuntime,\n type PngValidationResult,\n type RenderPngOptions,\n type RenderPngResult,\n type SnapshotOptions,\n renderPng,\n} from './render_png';\nexport type { Font, FontDetails, FontLoader } from '@takumi-rs/core';\nexport type { ImagesInput } from 'takumi-pdf';\n"]}
|
package/dist/cjs/render_pdf.js
CHANGED
|
@@ -18,11 +18,11 @@ const render_png_1 = require("./render_png");
|
|
|
18
18
|
* `renderPages`, which discards its own result. An empty deck throws the
|
|
19
19
|
* runtime's `EMPTY_PAGES`.
|
|
20
20
|
*/
|
|
21
|
-
const renderPdf = async (runtime, deck, backend, {
|
|
21
|
+
const renderPdf = async (runtime, deck, backend, { snapshot, ...options } = {}) => {
|
|
22
22
|
const pages = deck.map((composition) => (0, render_png_1.checkedForDrawing)('renderPdf', runtime.validate(composition)));
|
|
23
23
|
const validations = pages.map(({ validation }) => validation);
|
|
24
|
-
const rendered = runtime.surfaces.
|
|
25
|
-
...
|
|
24
|
+
const rendered = runtime.surfaces.snapshot.renderPages(pages.map(({ checked }) => checked), {
|
|
25
|
+
...snapshot,
|
|
26
26
|
onValidationError: 'collect',
|
|
27
27
|
});
|
|
28
28
|
const pdf = await backend.pdf(rendered, options);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render_pdf.js","sourceRoot":"","sources":["../../src/render_pdf.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAGH,6CAKsB;AAkCtB;;;;;;;;;GASG;AACI,MAAM,SAAS,GAAG,KAAK,EAC5B,OAAmB,EACnB,IAAwB,EACxB,OAAyB,EACzB,EAAE,
|
|
1
|
+
{"version":3,"file":"render_pdf.js","sourceRoot":"","sources":["../../src/render_pdf.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAGH,6CAKsB;AAkCtB;;;;;;;;;GASG;AACI,MAAM,SAAS,GAAG,KAAK,EAC5B,OAAmB,EACnB,IAAwB,EACxB,OAAyB,EACzB,EAAE,QAAQ,EAAE,GAAG,OAAO,KAAuB,EAAE,EACrB,EAAE;IAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CACrC,IAAA,8BAAiB,EAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAC9D,CAAC;IACF,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CACpD,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,EACnC;QACE,GAAG,QAAQ;QACX,iBAAiB,EAAE,SAAS;KAC7B,CACF,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjD,OAAO;QACL,GAAG;QACH,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;QAChC,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,WAAW;KACZ,CAAC;AACJ,CAAC,CAAC;AAzBW,QAAA,SAAS,aAyBpB","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nimport type { PdfInput, TakumiPdfBackend, TakumiPdfOptions } from './backend';\nimport {\n checkedForDrawing,\n type PngCheckedValidationResult,\n type PngValidationResult,\n type SnapshotOptions,\n} from './render_png';\n\n/**\n * The slice of `IsomerRuntime` this helper needs, declared structurally like\n * {@link PdfInput}. A runtime built with `frames` satisfies it.\n */\nexport interface PdfRuntime {\n validate(composition: unknown): PngCheckedValidationResult;\n surfaces: {\n snapshot: {\n renderPages(\n compositions: readonly unknown[],\n options?: SnapshotOptions & { onValidationError?: 'collect' | 'throw' }\n ): PdfInput;\n };\n };\n}\n\n/** Options for {@link renderPdf}. */\nexport interface RenderPdfOptions extends TakumiPdfOptions {\n /** Forwarded to `runtime.surfaces.snapshot.renderPages`, so every page shares one frame. */\n snapshot?: SnapshotOptions;\n}\n\n/** What {@link renderPdf} resolves to: the bytes, the page count and size, and one finding set per composition. */\nexport interface RenderPdfResult {\n pdf: Buffer;\n pageCount: number;\n width: number;\n height: number;\n /** One per composition, in order. */\n validations: PngValidationResult[];\n}\n\n/**\n * Validates, renders, and writes a deck as one PDF in one call, a page per\n * composition.\n *\n * Every composition is rendered, even an invalid one: `validations` is how a\n * caller finds out, rather than a thrown error. What is drawn is the copy\n * validation checked. Validation runs twice, once here and once inside\n * `renderPages`, which discards its own result. An empty deck throws the\n * runtime's `EMPTY_PAGES`.\n */\nexport const renderPdf = async (\n runtime: PdfRuntime,\n deck: readonly unknown[],\n backend: TakumiPdfBackend,\n { snapshot, ...options }: RenderPdfOptions = {}\n): Promise<RenderPdfResult> => {\n const pages = deck.map((composition) =>\n checkedForDrawing('renderPdf', runtime.validate(composition))\n );\n const validations = pages.map(({ validation }) => validation);\n const rendered = runtime.surfaces.snapshot.renderPages(\n pages.map(({ checked }) => checked),\n {\n ...snapshot,\n onValidationError: 'collect',\n }\n );\n const pdf = await backend.pdf(rendered, options);\n return {\n pdf,\n pageCount: rendered.pages.length,\n width: rendered.width,\n height: rendered.height,\n validations,\n };\n};\n"]}
|
package/dist/cjs/render_png.js
CHANGED
|
@@ -37,10 +37,10 @@ exports.checkedForDrawing = checkedForDrawing;
|
|
|
37
37
|
* `CompositionValidationError`. Validation runs twice, once here and once inside `render`, which
|
|
38
38
|
* discards its own result.
|
|
39
39
|
*/
|
|
40
|
-
const renderPng = async (runtime, composition, backend, {
|
|
40
|
+
const renderPng = async (runtime, composition, backend, { snapshot, ...options } = {}) => {
|
|
41
41
|
const { checked, validation } = (0, exports.checkedForDrawing)('renderPng', runtime.validate(composition));
|
|
42
|
-
const rendered = runtime.surfaces.
|
|
43
|
-
...
|
|
42
|
+
const rendered = runtime.surfaces.snapshot.render(checked, {
|
|
43
|
+
...snapshot,
|
|
44
44
|
onValidationError: 'collect',
|
|
45
45
|
});
|
|
46
46
|
const png = await backend.png(rendered, options);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render_png.js","sourceRoot":"","sources":["../../src/render_png.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAmBH;;;;;;GAMG;AACI,MAAM,iBAAiB,GAAG,CAC/B,MAAc,EACd,EAAE,WAAW,EAAE,GAAG,UAAU,EAA8B,EACH,EAAE;IACzD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,GAAG,MAAM,4DAA4D,CACtE,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CAAC,GAAG,MAAM,sDAAsD,CAAC,EAC1E;QACE,IAAI,EAAE,4BAA4B;QAClC,IAAI,EAAE,qBAAqB;QAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;KAC1B,CACF,CAAC;AACJ,CAAC,CAAC;AApBW,QAAA,iBAAiB,qBAoB5B;
|
|
1
|
+
{"version":3,"file":"render_png.js","sourceRoot":"","sources":["../../src/render_png.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAmBH;;;;;;GAMG;AACI,MAAM,iBAAiB,GAAG,CAC/B,MAAc,EACd,EAAE,WAAW,EAAE,GAAG,UAAU,EAA8B,EACH,EAAE;IACzD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,GAAG,MAAM,4DAA4D,CACtE,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CAAC,GAAG,MAAM,sDAAsD,CAAC,EAC1E;QACE,IAAI,EAAE,4BAA4B;QAClC,IAAI,EAAE,qBAAqB;QAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;KAC1B,CACF,CAAC;AACJ,CAAC,CAAC;AApBW,QAAA,iBAAiB,qBAoB5B;AAkDF;;;;;;;;GAQG;AACI,MAAM,SAAS,GAAG,KAAK,EAC5B,OAAmB,EACnB,WAAoB,EACpB,OAA2B,EAC3B,EAAE,QAAQ,EAAE,GAAG,OAAO,KAAuB,EAAE,EACrB,EAAE;IAC5B,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,IAAA,yBAAiB,EAC/C,WAAW,EACX,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAC9B,CAAC;IACF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE;QACzD,GAAG,QAAQ;QACX,iBAAiB,EAAE,SAAS;KAC7B,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;AAC7E,CAAC,CAAC;AAhBW,QAAA,SAAS,aAgBpB","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nimport type {\n ImageInput,\n TakumiImageBackend,\n TakumiRenderOptions,\n} from './backend';\n\n/** A validation result shaped like the SDK's `ValidationResult`, declared structurally. */\nexport interface PngValidationResult {\n valid: boolean;\n errors: readonly unknown[];\n}\n\n/** What `runtime.validate` returns: findings, and the copy they describe, `undefined` only for refused input. */\nexport type PngCheckedValidationResult = PngValidationResult & {\n composition: unknown;\n};\n\n/**\n * Splits a {@link PngCheckedValidationResult} into the copy to draw and the findings to report.\n *\n * Throws rather than draw the caller's value: shaped as the SDK's\n * `CompositionValidationError` when the runtime refused the input, and a plain\n * `Error` when the runtime returned no copy at all.\n */\nexport const checkedForDrawing = (\n caller: string,\n { composition, ...validation }: PngCheckedValidationResult\n): { checked: unknown; validation: PngValidationResult } => {\n if (composition !== undefined) {\n return { checked: composition, validation };\n }\n if (validation.valid) {\n throw new Error(\n `${caller}: runtime.validate returned no checked composition to draw`\n );\n }\n throw Object.assign(\n new Error(`${caller}: the runtime refused the composition before parsing`),\n {\n name: 'CompositionValidationError',\n code: 'COMPOSITION_INVALID',\n errors: validation.errors,\n }\n );\n};\n\n/**\n * The `snapshot` surface's per-render options, mirroring the runtime's\n * `SnapshotRenderOptions` so a caller gets completion without importing it.\n */\nexport interface SnapshotOptions {\n /** Which of the runtime's frames this render uses; defaults to its `defaultFrame`. */\n frame?: string;\n /** Overrides the frame's `defaultWidth`. */\n width?: number;\n /** Overrides the frame's estimated height. */\n height?: number;\n /** Theme mode; falls back to the composition's own `theme`. `auto` resolves light. */\n theme?: 'light' | 'dark' | 'auto';\n}\n\n/**\n * The slice of `IsomerRuntime` this helper needs, declared structurally like\n * {@link ImageInput} so this package depends on no isomer package. `surfaces.snapshot`\n * is required where the runtime's generic form has `SnapshotSurface | undefined`; a\n * runtime built with `frames` carries `SnapshotSurface` there and satisfies this.\n */\nexport interface PngRuntime {\n validate(composition: unknown): PngCheckedValidationResult;\n surfaces: {\n snapshot: {\n render(\n composition: unknown,\n options?: SnapshotOptions & { onValidationError?: 'collect' | 'throw' }\n ): ImageInput;\n };\n };\n}\n\n/** Options for {@link renderPng}. */\nexport interface RenderPngOptions extends TakumiRenderOptions {\n /** Forwarded to `runtime.surfaces.snapshot.render`. */\n snapshot?: SnapshotOptions;\n}\n\n/** What {@link renderPng} resolves to: the bytes, their size, and the findings on the composition drawn. */\nexport interface RenderPngResult {\n png: Buffer;\n /** The viewport, in CSS pixels; the raster is this times `scale`, rounded. */\n width: number;\n height: number;\n validation: PngValidationResult;\n}\n\n/**\n * Validates, renders, and rasterizes a composition in one call.\n *\n * The composition is rendered even when invalid: `validation` is how a caller\n * finds out, rather than a thrown error. What is drawn is the copy validation\n * checked, and input refused before parsing, which has none, throws\n * `CompositionValidationError`. Validation runs twice, once here and once inside `render`, which\n * discards its own result.\n */\nexport const renderPng = async (\n runtime: PngRuntime,\n composition: unknown,\n backend: TakumiImageBackend,\n { snapshot, ...options }: RenderPngOptions = {}\n): Promise<RenderPngResult> => {\n const { checked, validation } = checkedForDrawing(\n 'renderPng',\n runtime.validate(composition)\n );\n const rendered = runtime.surfaces.snapshot.render(checked, {\n ...snapshot,\n onValidationError: 'collect',\n });\n const png = await backend.png(rendered, options);\n return { png, width: rendered.width, height: rendered.height, validation };\n};\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { type ImageInput, type LayoutBox, type PdfInput, type TakumiBackend, type TakumiImageBackend, type TakumiImageBackendOptions, type TakumiMeasuringBackend, type TakumiPdfBackend, type TakumiPdfMetadata, type TakumiPdfOptions, type TakumiRenderOptions, createTakumiImageBackend, } from './backend.js';
|
|
1
|
+
export { type ImageInput, type LayoutBox, type PdfInput, type TakumiBackend, type TakumiImageBackend, type TakumiImageBackendOptions, type TakumiMeasuringBackend, type TakumiPdfBackend, type TakumiPdfMetadata, type TakumiPdfOptions, type TakumiRenderOptions, TAKUMI_FORMATS, createTakumiImageBackend, } from './backend.js';
|
|
2
2
|
export { type PdfRuntime, type RenderPdfOptions, type RenderPdfResult, renderPdf, } from './render_pdf.js';
|
|
3
|
-
export { type PngCheckedValidationResult, type PngRuntime, type
|
|
3
|
+
export { type PngCheckedValidationResult, type PngRuntime, type PngValidationResult, type RenderPngOptions, type RenderPngResult, type SnapshotOptions, renderPng, } from './render_png.js';
|
|
4
4
|
export type { Font, FontDetails, FontLoader } from '@takumi-rs/core';
|
|
5
5
|
export type { ImagesInput } from 'takumi-pdf';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,wBAAwB,GACzB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,0BAA0B,EAC/B,KAAK,UAAU,EACf,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,cAAc,EACd,wBAAwB,GACzB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,0BAA0B,EAC/B,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACrE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
5
5
|
* 2.0.
|
|
6
6
|
*/
|
|
7
|
-
export { createTakumiImageBackend, } from './backend.js';
|
|
7
|
+
export { TAKUMI_FORMATS, createTakumiImageBackend, } from './backend.js';
|
|
8
8
|
export { renderPdf, } from './render_pdf.js';
|
|
9
9
|
export { renderPng, } from './render_png.js';
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAYL,wBAAwB,GACzB,MAAM,WAAW,CAAC;AACnB,OAAO,EAIL,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAOL,SAAS,GACV,MAAM,cAAc,CAAC","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nexport {\n type ImageInput,\n type LayoutBox,\n type PdfInput,\n type TakumiBackend,\n type TakumiImageBackend,\n type TakumiImageBackendOptions,\n type TakumiMeasuringBackend,\n type TakumiPdfBackend,\n type TakumiPdfMetadata,\n type TakumiPdfOptions,\n type TakumiRenderOptions,\n createTakumiImageBackend,\n} from './backend';\nexport {\n type PdfRuntime,\n type RenderPdfOptions,\n type RenderPdfResult,\n renderPdf,\n} from './render_pdf';\nexport {\n type PngCheckedValidationResult,\n type PngRuntime,\n type
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAYL,cAAc,EACd,wBAAwB,GACzB,MAAM,WAAW,CAAC;AACnB,OAAO,EAIL,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAOL,SAAS,GACV,MAAM,cAAc,CAAC","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nexport {\n type ImageInput,\n type LayoutBox,\n type PdfInput,\n type TakumiBackend,\n type TakumiImageBackend,\n type TakumiImageBackendOptions,\n type TakumiMeasuringBackend,\n type TakumiPdfBackend,\n type TakumiPdfMetadata,\n type TakumiPdfOptions,\n type TakumiRenderOptions,\n TAKUMI_FORMATS,\n createTakumiImageBackend,\n} from './backend';\nexport {\n type PdfRuntime,\n type RenderPdfOptions,\n type RenderPdfResult,\n renderPdf,\n} from './render_pdf';\nexport {\n type PngCheckedValidationResult,\n type PngRuntime,\n type PngValidationResult,\n type RenderPngOptions,\n type RenderPngResult,\n type SnapshotOptions,\n renderPng,\n} from './render_png';\nexport type { Font, FontDetails, FontLoader } from '@takumi-rs/core';\nexport type { ImagesInput } from 'takumi-pdf';\n"]}
|
package/dist/render_pdf.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PdfInput, TakumiPdfBackend, TakumiPdfOptions } from './backend.js';
|
|
2
|
-
import { type PngCheckedValidationResult, type
|
|
2
|
+
import { type PngCheckedValidationResult, type PngValidationResult, type SnapshotOptions } from './render_png.js';
|
|
3
3
|
/**
|
|
4
4
|
* The slice of `IsomerRuntime` this helper needs, declared structurally like
|
|
5
5
|
* {@link PdfInput}. A runtime built with `frames` satisfies it.
|
|
@@ -7,8 +7,8 @@ import { type PngCheckedValidationResult, type PngSvgOptions, type PngValidation
|
|
|
7
7
|
export interface PdfRuntime {
|
|
8
8
|
validate(composition: unknown): PngCheckedValidationResult;
|
|
9
9
|
surfaces: {
|
|
10
|
-
|
|
11
|
-
renderPages(compositions: readonly unknown[], options?:
|
|
10
|
+
snapshot: {
|
|
11
|
+
renderPages(compositions: readonly unknown[], options?: SnapshotOptions & {
|
|
12
12
|
onValidationError?: 'collect' | 'throw';
|
|
13
13
|
}): PdfInput;
|
|
14
14
|
};
|
|
@@ -16,8 +16,8 @@ export interface PdfRuntime {
|
|
|
16
16
|
}
|
|
17
17
|
/** Options for {@link renderPdf}. */
|
|
18
18
|
export interface RenderPdfOptions extends TakumiPdfOptions {
|
|
19
|
-
/** Forwarded to `runtime.surfaces.
|
|
20
|
-
|
|
19
|
+
/** Forwarded to `runtime.surfaces.snapshot.renderPages`, so every page shares one frame. */
|
|
20
|
+
snapshot?: SnapshotOptions;
|
|
21
21
|
}
|
|
22
22
|
/** What {@link renderPdf} resolves to: the bytes, the page count and size, and one finding set per composition. */
|
|
23
23
|
export interface RenderPdfResult {
|
|
@@ -38,5 +38,5 @@ export interface RenderPdfResult {
|
|
|
38
38
|
* `renderPages`, which discards its own result. An empty deck throws the
|
|
39
39
|
* runtime's `EMPTY_PAGES`.
|
|
40
40
|
*/
|
|
41
|
-
export declare const renderPdf: (runtime: PdfRuntime, deck: readonly unknown[], backend: TakumiPdfBackend, {
|
|
41
|
+
export declare const renderPdf: (runtime: PdfRuntime, deck: readonly unknown[], backend: TakumiPdfBackend, { snapshot, ...options }?: RenderPdfOptions) => Promise<RenderPdfResult>;
|
|
42
42
|
//# sourceMappingURL=render_pdf.d.ts.map
|
package/dist/render_pdf.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render_pdf.d.ts","sourceRoot":"","sources":["../src/render_pdf.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC9E,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,
|
|
1
|
+
{"version":3,"file":"render_pdf.d.ts","sourceRoot":"","sources":["../src/render_pdf.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC9E,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AAEtB;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,GAAG,0BAA0B,CAAC;IAC3D,QAAQ,EAAE;QACR,QAAQ,EAAE;YACR,WAAW,CACT,YAAY,EAAE,SAAS,OAAO,EAAE,EAChC,OAAO,CAAC,EAAE,eAAe,GAAG;gBAAE,iBAAiB,CAAC,EAAE,SAAS,GAAG,OAAO,CAAA;aAAE,GACtE,QAAQ,CAAC;SACb,CAAC;KACH,CAAC;CACH;AAED,qCAAqC;AACrC,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACxD,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,mHAAmH;AACnH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,GACpB,SAAS,UAAU,EACnB,MAAM,SAAS,OAAO,EAAE,EACxB,SAAS,gBAAgB,EACzB,2BAA0B,gBAAqB,KAC9C,OAAO,CAAC,eAAe,CAoBzB,CAAC"}
|
package/dist/render_pdf.js
CHANGED
|
@@ -15,11 +15,11 @@ import { checkedForDrawing, } from './render_png.js';
|
|
|
15
15
|
* `renderPages`, which discards its own result. An empty deck throws the
|
|
16
16
|
* runtime's `EMPTY_PAGES`.
|
|
17
17
|
*/
|
|
18
|
-
export const renderPdf = async (runtime, deck, backend, {
|
|
18
|
+
export const renderPdf = async (runtime, deck, backend, { snapshot, ...options } = {}) => {
|
|
19
19
|
const pages = deck.map((composition) => checkedForDrawing('renderPdf', runtime.validate(composition)));
|
|
20
20
|
const validations = pages.map(({ validation }) => validation);
|
|
21
|
-
const rendered = runtime.surfaces.
|
|
22
|
-
...
|
|
21
|
+
const rendered = runtime.surfaces.snapshot.renderPages(pages.map(({ checked }) => checked), {
|
|
22
|
+
...snapshot,
|
|
23
23
|
onValidationError: 'collect',
|
|
24
24
|
});
|
|
25
25
|
const pdf = await backend.pdf(rendered, options);
|
package/dist/render_pdf.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render_pdf.js","sourceRoot":"","sources":["../src/render_pdf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,iBAAiB,GAIlB,MAAM,cAAc,CAAC;AAkCtB;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,OAAmB,EACnB,IAAwB,EACxB,OAAyB,EACzB,EAAE,
|
|
1
|
+
{"version":3,"file":"render_pdf.js","sourceRoot":"","sources":["../src/render_pdf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,iBAAiB,GAIlB,MAAM,cAAc,CAAC;AAkCtB;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,OAAmB,EACnB,IAAwB,EACxB,OAAyB,EACzB,EAAE,QAAQ,EAAE,GAAG,OAAO,KAAuB,EAAE,EACrB,EAAE;IAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CACrC,iBAAiB,CAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAC9D,CAAC;IACF,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CACpD,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,EACnC;QACE,GAAG,QAAQ;QACX,iBAAiB,EAAE,SAAS;KAC7B,CACF,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjD,OAAO;QACL,GAAG;QACH,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;QAChC,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,WAAW;KACZ,CAAC;AACJ,CAAC,CAAC","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nimport type { PdfInput, TakumiPdfBackend, TakumiPdfOptions } from './backend';\nimport {\n checkedForDrawing,\n type PngCheckedValidationResult,\n type PngValidationResult,\n type SnapshotOptions,\n} from './render_png';\n\n/**\n * The slice of `IsomerRuntime` this helper needs, declared structurally like\n * {@link PdfInput}. A runtime built with `frames` satisfies it.\n */\nexport interface PdfRuntime {\n validate(composition: unknown): PngCheckedValidationResult;\n surfaces: {\n snapshot: {\n renderPages(\n compositions: readonly unknown[],\n options?: SnapshotOptions & { onValidationError?: 'collect' | 'throw' }\n ): PdfInput;\n };\n };\n}\n\n/** Options for {@link renderPdf}. */\nexport interface RenderPdfOptions extends TakumiPdfOptions {\n /** Forwarded to `runtime.surfaces.snapshot.renderPages`, so every page shares one frame. */\n snapshot?: SnapshotOptions;\n}\n\n/** What {@link renderPdf} resolves to: the bytes, the page count and size, and one finding set per composition. */\nexport interface RenderPdfResult {\n pdf: Buffer;\n pageCount: number;\n width: number;\n height: number;\n /** One per composition, in order. */\n validations: PngValidationResult[];\n}\n\n/**\n * Validates, renders, and writes a deck as one PDF in one call, a page per\n * composition.\n *\n * Every composition is rendered, even an invalid one: `validations` is how a\n * caller finds out, rather than a thrown error. What is drawn is the copy\n * validation checked. Validation runs twice, once here and once inside\n * `renderPages`, which discards its own result. An empty deck throws the\n * runtime's `EMPTY_PAGES`.\n */\nexport const renderPdf = async (\n runtime: PdfRuntime,\n deck: readonly unknown[],\n backend: TakumiPdfBackend,\n { snapshot, ...options }: RenderPdfOptions = {}\n): Promise<RenderPdfResult> => {\n const pages = deck.map((composition) =>\n checkedForDrawing('renderPdf', runtime.validate(composition))\n );\n const validations = pages.map(({ validation }) => validation);\n const rendered = runtime.surfaces.snapshot.renderPages(\n pages.map(({ checked }) => checked),\n {\n ...snapshot,\n onValidationError: 'collect',\n }\n );\n const pdf = await backend.pdf(rendered, options);\n return {\n pdf,\n pageCount: rendered.pages.length,\n width: rendered.width,\n height: rendered.height,\n validations,\n };\n};\n"]}
|
package/dist/render_png.d.ts
CHANGED
|
@@ -20,10 +20,10 @@ export declare const checkedForDrawing: (caller: string, { composition, ...valid
|
|
|
20
20
|
validation: PngValidationResult;
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
|
-
* The `
|
|
24
|
-
* `
|
|
23
|
+
* The `snapshot` surface's per-render options, mirroring the runtime's
|
|
24
|
+
* `SnapshotRenderOptions` so a caller gets completion without importing it.
|
|
25
25
|
*/
|
|
26
|
-
export interface
|
|
26
|
+
export interface SnapshotOptions {
|
|
27
27
|
/** Which of the runtime's frames this render uses; defaults to its `defaultFrame`. */
|
|
28
28
|
frame?: string;
|
|
29
29
|
/** Overrides the frame's `defaultWidth`. */
|
|
@@ -35,15 +35,15 @@ export interface PngSvgOptions {
|
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
37
|
* The slice of `IsomerRuntime` this helper needs, declared structurally like
|
|
38
|
-
* {@link ImageInput} so this package depends on no isomer package. `surfaces.
|
|
39
|
-
* is required where the runtime's generic form has `
|
|
40
|
-
* runtime built with `frames` carries `
|
|
38
|
+
* {@link ImageInput} so this package depends on no isomer package. `surfaces.snapshot`
|
|
39
|
+
* is required where the runtime's generic form has `SnapshotSurface | undefined`; a
|
|
40
|
+
* runtime built with `frames` carries `SnapshotSurface` there and satisfies this.
|
|
41
41
|
*/
|
|
42
42
|
export interface PngRuntime {
|
|
43
43
|
validate(composition: unknown): PngCheckedValidationResult;
|
|
44
44
|
surfaces: {
|
|
45
|
-
|
|
46
|
-
render(composition: unknown, options?:
|
|
45
|
+
snapshot: {
|
|
46
|
+
render(composition: unknown, options?: SnapshotOptions & {
|
|
47
47
|
onValidationError?: 'collect' | 'throw';
|
|
48
48
|
}): ImageInput;
|
|
49
49
|
};
|
|
@@ -51,12 +51,13 @@ export interface PngRuntime {
|
|
|
51
51
|
}
|
|
52
52
|
/** Options for {@link renderPng}. */
|
|
53
53
|
export interface RenderPngOptions extends TakumiRenderOptions {
|
|
54
|
-
/** Forwarded to `runtime.surfaces.
|
|
55
|
-
|
|
54
|
+
/** Forwarded to `runtime.surfaces.snapshot.render`. */
|
|
55
|
+
snapshot?: SnapshotOptions;
|
|
56
56
|
}
|
|
57
57
|
/** What {@link renderPng} resolves to: the bytes, their size, and the findings on the composition drawn. */
|
|
58
58
|
export interface RenderPngResult {
|
|
59
59
|
png: Buffer;
|
|
60
|
+
/** The viewport, in CSS pixels; the raster is this times `scale`, rounded. */
|
|
60
61
|
width: number;
|
|
61
62
|
height: number;
|
|
62
63
|
validation: PngValidationResult;
|
|
@@ -70,5 +71,5 @@ export interface RenderPngResult {
|
|
|
70
71
|
* `CompositionValidationError`. Validation runs twice, once here and once inside `render`, which
|
|
71
72
|
* discards its own result.
|
|
72
73
|
*/
|
|
73
|
-
export declare const renderPng: (runtime: PngRuntime, composition: unknown, backend: TakumiImageBackend, {
|
|
74
|
+
export declare const renderPng: (runtime: PngRuntime, composition: unknown, backend: TakumiImageBackend, { snapshot, ...options }?: RenderPngOptions) => Promise<RenderPngResult>;
|
|
74
75
|
//# sourceMappingURL=render_png.d.ts.map
|
package/dist/render_png.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render_png.d.ts","sourceRoot":"","sources":["../src/render_png.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,UAAU,EACV,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,WAAW,CAAC;AAEnB,2FAA2F;AAC3F,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5B;AAED,iHAAiH;AACjH,MAAM,MAAM,0BAA0B,GAAG,mBAAmB,GAAG;IAC7D,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,MAAM,EACd,gCAAgC,0BAA0B,KACzD;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,mBAAmB,CAAA;CAiBrD,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"render_png.d.ts","sourceRoot":"","sources":["../src/render_png.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,UAAU,EACV,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,WAAW,CAAC;AAEnB,2FAA2F;AAC3F,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5B;AAED,iHAAiH;AACjH,MAAM,MAAM,0BAA0B,GAAG,mBAAmB,GAAG;IAC7D,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,MAAM,EACd,gCAAgC,0BAA0B,KACzD;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,mBAAmB,CAAA;CAiBrD,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;CACnC;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,GAAG,0BAA0B,CAAC;IAC3D,QAAQ,EAAE;QACR,QAAQ,EAAE;YACR,MAAM,CACJ,WAAW,EAAE,OAAO,EACpB,OAAO,CAAC,EAAE,eAAe,GAAG;gBAAE,iBAAiB,CAAC,EAAE,SAAS,GAAG,OAAO,CAAA;aAAE,GACtE,UAAU,CAAC;SACf,CAAC;KACH,CAAC;CACH;AAED,qCAAqC;AACrC,MAAM,WAAW,gBAAiB,SAAQ,mBAAmB;IAC3D,uDAAuD;IACvD,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,4GAA4G;AAC5G,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,mBAAmB,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GACpB,SAAS,UAAU,EACnB,aAAa,OAAO,EACpB,SAAS,kBAAkB,EAC3B,2BAA0B,gBAAqB,KAC9C,OAAO,CAAC,eAAe,CAWzB,CAAC"}
|
package/dist/render_png.js
CHANGED
|
@@ -33,10 +33,10 @@ export const checkedForDrawing = (caller, { composition, ...validation }) => {
|
|
|
33
33
|
* `CompositionValidationError`. Validation runs twice, once here and once inside `render`, which
|
|
34
34
|
* discards its own result.
|
|
35
35
|
*/
|
|
36
|
-
export const renderPng = async (runtime, composition, backend, {
|
|
36
|
+
export const renderPng = async (runtime, composition, backend, { snapshot, ...options } = {}) => {
|
|
37
37
|
const { checked, validation } = checkedForDrawing('renderPng', runtime.validate(composition));
|
|
38
|
-
const rendered = runtime.surfaces.
|
|
39
|
-
...
|
|
38
|
+
const rendered = runtime.surfaces.snapshot.render(checked, {
|
|
39
|
+
...snapshot,
|
|
40
40
|
onValidationError: 'collect',
|
|
41
41
|
});
|
|
42
42
|
const png = await backend.png(rendered, options);
|
package/dist/render_png.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render_png.js","sourceRoot":"","sources":["../src/render_png.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmBH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,MAAc,EACd,EAAE,WAAW,EAAE,GAAG,UAAU,EAA8B,EACH,EAAE;IACzD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,GAAG,MAAM,4DAA4D,CACtE,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CAAC,GAAG,MAAM,sDAAsD,CAAC,EAC1E;QACE,IAAI,EAAE,4BAA4B;QAClC,IAAI,EAAE,qBAAqB;QAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;KAC1B,CACF,CAAC;AACJ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"render_png.js","sourceRoot":"","sources":["../src/render_png.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmBH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,MAAc,EACd,EAAE,WAAW,EAAE,GAAG,UAAU,EAA8B,EACH,EAAE;IACzD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,GAAG,MAAM,4DAA4D,CACtE,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CAAC,GAAG,MAAM,sDAAsD,CAAC,EAC1E;QACE,IAAI,EAAE,4BAA4B;QAClC,IAAI,EAAE,qBAAqB;QAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;KAC1B,CACF,CAAC;AACJ,CAAC,CAAC;AAkDF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,OAAmB,EACnB,WAAoB,EACpB,OAA2B,EAC3B,EAAE,QAAQ,EAAE,GAAG,OAAO,KAAuB,EAAE,EACrB,EAAE;IAC5B,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,iBAAiB,CAC/C,WAAW,EACX,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAC9B,CAAC;IACF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE;QACzD,GAAG,QAAQ;QACX,iBAAiB,EAAE,SAAS;KAC7B,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;AAC7E,CAAC,CAAC","sourcesContent":["/*\n * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one\n * or more contributor license agreements. Licensed under the Elastic License\n * 2.0; you may not use this file except in compliance with the Elastic License\n * 2.0.\n */\n\nimport type {\n ImageInput,\n TakumiImageBackend,\n TakumiRenderOptions,\n} from './backend';\n\n/** A validation result shaped like the SDK's `ValidationResult`, declared structurally. */\nexport interface PngValidationResult {\n valid: boolean;\n errors: readonly unknown[];\n}\n\n/** What `runtime.validate` returns: findings, and the copy they describe, `undefined` only for refused input. */\nexport type PngCheckedValidationResult = PngValidationResult & {\n composition: unknown;\n};\n\n/**\n * Splits a {@link PngCheckedValidationResult} into the copy to draw and the findings to report.\n *\n * Throws rather than draw the caller's value: shaped as the SDK's\n * `CompositionValidationError` when the runtime refused the input, and a plain\n * `Error` when the runtime returned no copy at all.\n */\nexport const checkedForDrawing = (\n caller: string,\n { composition, ...validation }: PngCheckedValidationResult\n): { checked: unknown; validation: PngValidationResult } => {\n if (composition !== undefined) {\n return { checked: composition, validation };\n }\n if (validation.valid) {\n throw new Error(\n `${caller}: runtime.validate returned no checked composition to draw`\n );\n }\n throw Object.assign(\n new Error(`${caller}: the runtime refused the composition before parsing`),\n {\n name: 'CompositionValidationError',\n code: 'COMPOSITION_INVALID',\n errors: validation.errors,\n }\n );\n};\n\n/**\n * The `snapshot` surface's per-render options, mirroring the runtime's\n * `SnapshotRenderOptions` so a caller gets completion without importing it.\n */\nexport interface SnapshotOptions {\n /** Which of the runtime's frames this render uses; defaults to its `defaultFrame`. */\n frame?: string;\n /** Overrides the frame's `defaultWidth`. */\n width?: number;\n /** Overrides the frame's estimated height. */\n height?: number;\n /** Theme mode; falls back to the composition's own `theme`. `auto` resolves light. */\n theme?: 'light' | 'dark' | 'auto';\n}\n\n/**\n * The slice of `IsomerRuntime` this helper needs, declared structurally like\n * {@link ImageInput} so this package depends on no isomer package. `surfaces.snapshot`\n * is required where the runtime's generic form has `SnapshotSurface | undefined`; a\n * runtime built with `frames` carries `SnapshotSurface` there and satisfies this.\n */\nexport interface PngRuntime {\n validate(composition: unknown): PngCheckedValidationResult;\n surfaces: {\n snapshot: {\n render(\n composition: unknown,\n options?: SnapshotOptions & { onValidationError?: 'collect' | 'throw' }\n ): ImageInput;\n };\n };\n}\n\n/** Options for {@link renderPng}. */\nexport interface RenderPngOptions extends TakumiRenderOptions {\n /** Forwarded to `runtime.surfaces.snapshot.render`. */\n snapshot?: SnapshotOptions;\n}\n\n/** What {@link renderPng} resolves to: the bytes, their size, and the findings on the composition drawn. */\nexport interface RenderPngResult {\n png: Buffer;\n /** The viewport, in CSS pixels; the raster is this times `scale`, rounded. */\n width: number;\n height: number;\n validation: PngValidationResult;\n}\n\n/**\n * Validates, renders, and rasterizes a composition in one call.\n *\n * The composition is rendered even when invalid: `validation` is how a caller\n * finds out, rather than a thrown error. What is drawn is the copy validation\n * checked, and input refused before parsing, which has none, throws\n * `CompositionValidationError`. Validation runs twice, once here and once inside `render`, which\n * discards its own result.\n */\nexport const renderPng = async (\n runtime: PngRuntime,\n composition: unknown,\n backend: TakumiImageBackend,\n { snapshot, ...options }: RenderPngOptions = {}\n): Promise<RenderPngResult> => {\n const { checked, validation } = checkedForDrawing(\n 'renderPng',\n runtime.validate(composition)\n );\n const rendered = runtime.surfaces.snapshot.render(checked, {\n ...snapshot,\n onValidationError: 'collect',\n });\n const png = await backend.png(rendered, options);\n return { png, width: rendered.width, height: rendered.height, validation };\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elastic/isomer-image-takumi",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Takumi image backend for Isomer: rasterizes the
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Takumi image backend for Isomer: rasterizes the snapshot surface's output to PNG, SVG, or PDF.",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"author": "Elastic",
|
|
7
7
|
"repository": {
|
|
@@ -32,10 +32,6 @@
|
|
|
32
32
|
"@takumi-rs/helpers": "2.14.0",
|
|
33
33
|
"takumi-pdf": "0.15.0"
|
|
34
34
|
},
|
|
35
|
-
"peerDependencies": {
|
|
36
|
-
"react": ">=18 <20",
|
|
37
|
-
"react-dom": ">=18 <20"
|
|
38
|
-
},
|
|
39
35
|
"devDependencies": {
|
|
40
36
|
"@fontsource/inter": "^5.3.0",
|
|
41
37
|
"@types/react": "^18.2.14",
|