@typfolio/core 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 +16 -5
- package/dist/capture/dom.d.ts +8 -3
- package/dist/capture/dom.js +5 -9
- package/dist/capture/svg.d.ts +19 -6
- package/dist/capture/svg.js +67 -48
- package/dist/definition.d.ts +156 -0
- package/dist/definition.js +161 -0
- package/dist/engine.js +32 -10
- package/dist/explain.d.ts +2 -0
- package/dist/explain.js +42 -0
- package/dist/index.d.ts +18 -2
- package/dist/index.js +67 -4
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +2 -0
- package/dist/request.d.ts +3 -1
- package/dist/request.js +20 -12
- package/dist/types.d.ts +15 -2
- package/dist/types.js +2 -0
- package/dist/typst-package.d.ts +2 -0
- package/dist/typst-package.generated.d.ts +2 -2
- package/dist/typst-package.generated.js +2 -2
- package/dist/typst-package.js +2 -0
- package/package.json +11 -2
- package/typst-packages/local/typfolio/0.1.0/lib.typ +14 -5
- package/typst-packages/local/typfolio/0.1.0/sample.typ +58 -20
package/dist/engine.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import init, { TypstCompilerBuilder } from '@typfolio/wasm';
|
|
2
|
-
import { TypstPdfError } from './types.js';
|
|
2
|
+
import { errorLink, TypstPdfError } from './types.js';
|
|
3
3
|
import { resolvePackage } from './typst-package.js';
|
|
4
4
|
let initialization;
|
|
5
5
|
let loadedSource;
|
|
@@ -31,6 +31,31 @@ function message(error) {
|
|
|
31
31
|
return String(error);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
const quoted = /"((?:[^"\\]|\\.)*)"/g;
|
|
35
|
+
const unquote = (text) => { try {
|
|
36
|
+
return JSON.parse(`"${text}"`);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return text;
|
|
40
|
+
} };
|
|
41
|
+
// The binding reports a failed PDF export as Rust debug text,
|
|
42
|
+
// `[SourceDiagnostic { severity: Error, span: …, message: "…", trace: [], hints: ["…"] }]`:
|
|
43
|
+
// one diagnostic per message, its hints appended. A character no loaded font can display (refused
|
|
44
|
+
// by a PDF standard) is `missing-glyph`. Anything else is kept as it is.
|
|
45
|
+
function exportDiagnostics(text) {
|
|
46
|
+
const found = text.split('SourceDiagnostic {').slice(1).flatMap((part) => {
|
|
47
|
+
const [, said] = /message: "((?:[^"\\]|\\.)*)"/.exec(part) ?? [];
|
|
48
|
+
if (said === undefined)
|
|
49
|
+
return [];
|
|
50
|
+
const message = unquote(said);
|
|
51
|
+
const [, glyph] = /^PDF\/\S+ error: the text `"(.*)"` could not be displayed with font/.exec(message) ?? [];
|
|
52
|
+
if (glyph !== undefined)
|
|
53
|
+
return [{ severity: 'error', code: 'missing-glyph', text: glyph, message: `${message}. See ${errorLink('missing-glyph')}` }];
|
|
54
|
+
const hints = [...(/hints: \[(.*)\]/s.exec(part)?.[1] ?? '').matchAll(quoted)].map(([, hint]) => `\nhint: ${unquote(hint)}`);
|
|
55
|
+
return [{ severity: 'error', message: message + hints.join('') }];
|
|
56
|
+
});
|
|
57
|
+
return found.length ? found : [{ severity: 'error', message: text }];
|
|
58
|
+
}
|
|
34
59
|
function fontUrl(value) {
|
|
35
60
|
if (value instanceof URL)
|
|
36
61
|
return value;
|
|
@@ -126,12 +151,9 @@ class Engine {
|
|
|
126
151
|
let diagnostics = [];
|
|
127
152
|
try {
|
|
128
153
|
this.compiler.reset_shadow();
|
|
129
|
-
for
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
// For a runtime compile these are the helper package plus the input at its root (absolute paths inside a package resolve there).
|
|
134
|
-
for (const [path, bytes] of extras.mounts) {
|
|
154
|
+
// The request's files, then the extras: for a runtime compile, the helper package plus the input at
|
|
155
|
+
// its root (absolute paths inside a package resolve there).
|
|
156
|
+
for (const [path, bytes] of [...request.files, ...extras.mounts]) {
|
|
135
157
|
if (!this.compiler.map_shadow(path, bytes))
|
|
136
158
|
throw new TypstPdfError('ENGINE', `Could not mount ${path}`);
|
|
137
159
|
}
|
|
@@ -151,9 +173,9 @@ class Engine {
|
|
|
151
173
|
catch (error) {
|
|
152
174
|
if (error instanceof TypstPdfError)
|
|
153
175
|
throw error;
|
|
154
|
-
// Upstream export failures
|
|
155
|
-
const
|
|
156
|
-
throw new TypstPdfError('COMPILATION',
|
|
176
|
+
// Upstream export failures (a PDF standard's checks) lack source locations.
|
|
177
|
+
const failures = exportDiagnostics(message(error));
|
|
178
|
+
throw new TypstPdfError('COMPILATION', failures.map(d => d.message).join('\n'), [...diagnostics, ...failures], { cause: error });
|
|
157
179
|
}
|
|
158
180
|
finally {
|
|
159
181
|
try {
|
package/dist/explain.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { errorLink, TypstPdfError } from './types.js';
|
|
2
|
+
const decoder = new TextDecoder();
|
|
3
|
+
function resolve(from, path) {
|
|
4
|
+
const parts = path.startsWith('/') ? [] : from.split('/').slice(1, -1);
|
|
5
|
+
for (const part of path.split('/')) {
|
|
6
|
+
if (part === '' || part === '.')
|
|
7
|
+
continue;
|
|
8
|
+
if (part === '..')
|
|
9
|
+
parts.pop();
|
|
10
|
+
else
|
|
11
|
+
parts.push(part);
|
|
12
|
+
}
|
|
13
|
+
return `/${parts.join('/')}`;
|
|
14
|
+
}
|
|
15
|
+
// The file a missing-file diagnostic is about. Typst reports only "failed to load file (is a
|
|
16
|
+
// directory)", with the range of the path string in the file that reads it.
|
|
17
|
+
function missingPath(diagnostic, files) {
|
|
18
|
+
if (!/failed to load file/.test(diagnostic.message) || diagnostic.package || !diagnostic.path)
|
|
19
|
+
return undefined;
|
|
20
|
+
const source = files.get(diagnostic.path);
|
|
21
|
+
const range = /^(\d+):(\d+)-(\d+):(\d+)$/.exec(diagnostic.range ?? '');
|
|
22
|
+
if (!source || !range || range[1] !== range[3])
|
|
23
|
+
return undefined;
|
|
24
|
+
const literal = decoder.decode(source).split('\n')[Number(range[1])]?.slice(Number(range[2]), Number(range[4])).trim();
|
|
25
|
+
const quoted = literal && /^"([^"\\]*)"$/.exec(literal);
|
|
26
|
+
return quoted ? resolve(diagnostic.path, quoted[1]) : undefined;
|
|
27
|
+
}
|
|
28
|
+
/** Turns each "failed to load file" diagnostic about a file the request lacks into `file-not-found`. */
|
|
29
|
+
export function explainCompileError(error, files) {
|
|
30
|
+
if (!(error instanceof TypstPdfError) || error.code !== 'COMPILATION')
|
|
31
|
+
return error;
|
|
32
|
+
let found = false;
|
|
33
|
+
const diagnostics = error.diagnostics.map(diagnostic => {
|
|
34
|
+
const file = missingPath(diagnostic, files);
|
|
35
|
+
if (file === undefined || files.has(file))
|
|
36
|
+
return diagnostic;
|
|
37
|
+
found = true;
|
|
38
|
+
const message = `File not found: ${file} (read by ${diagnostic.path}). See ${errorLink('file-not-found')}`;
|
|
39
|
+
return { ...diagnostic, code: 'file-not-found', file, message };
|
|
40
|
+
});
|
|
41
|
+
return found ? new TypstPdfError('COMPILATION', diagnostics.map(d => d.message).join('\n'), diagnostics, { cause: error }) : error;
|
|
42
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { type FontSource, type PdfCompiler, type WasmSource } from './types.js';
|
|
1
|
+
import { type FontSource, type PdfClient, type PdfCompiler, type WasmSource } from './types.js';
|
|
2
2
|
export { capabilities, pdfStandards, TypstPdfError } from './types.js';
|
|
3
|
-
export type { JsonValue, PdfOptions, PdfStandard, PdfRequest, PdfTemplate, PdfResult, PdfClient, PdfCompiler, Diagnostic, ErrorCode } from './types.js';
|
|
3
|
+
export type { JsonValue, PdfOptions, PdfStandard, PdfRequest, PdfTemplate, PdfResult, PdfClient, PdfCompiler, Diagnostic, DiagnosticCode, ErrorCode } from './types.js';
|
|
4
|
+
export { createAttachments, defineInput, definePartial, defineTemplate } from './definition.js';
|
|
5
|
+
export type { AnyPdfRequest, CheckedRequest, DocumentsRequest, InferSchemaInput, PartialDefinition, PartialsData, PartialsMap, StandardSchemaIssue, StandardSchemaResult, StandardSchemaV1, TemplateDefinition, TemplateInput, TemplateInputSlot, TemplateRequest, } from './definition.js';
|
|
4
6
|
export type { FontSource, WasmSource } from './types.js';
|
|
5
7
|
export { templateFromFiles } from './template.js';
|
|
6
8
|
export type { TemplateFromFilesOptions, TemplateFiles } from './template.js';
|
|
@@ -22,5 +24,19 @@ export interface CompilerOptions {
|
|
|
22
24
|
*/
|
|
23
25
|
copyAssets?: boolean;
|
|
24
26
|
}
|
|
27
|
+
/** A `PdfClient` whose compiler is created on its first `compile`. */
|
|
28
|
+
export interface LazyCompiler extends PdfClient {
|
|
29
|
+
/** Stops accepting jobs, waits for the queued ones, and frees the compiler if one was created. */
|
|
30
|
+
dispose(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A compiler for the page, created when it is first needed: nothing is downloaded or initialized until
|
|
34
|
+
* the first `compile`, which loads the WASM and the fonts once for every later call. A load that fails is
|
|
35
|
+
* not kept, so the next `compile` tries again. Calls run one at a time, in order, so callers never see
|
|
36
|
+
* `BUSY`; a call whose signal is aborted before its turn rejects with `ABORTED` without running. A request
|
|
37
|
+
* is read when its turn comes. Synchronous, so it can be created at module level and passed as a
|
|
38
|
+
* `PdfClient` (to `PdfProvider`, or as `client`). Use `@typfolio/workers` to compile off the main thread.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createLazyCompiler(options: CompilerOptions): LazyCompiler;
|
|
25
41
|
/** One active job per compiler. Use a worker client for queues and hard cancellation. */
|
|
26
42
|
export declare function createCompiler(options: CompilerOptions): Promise<PdfCompiler>;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,61 @@
|
|
|
1
1
|
import { copyFonts, createEngine, fetchFonts, initialize } from './engine.js';
|
|
2
2
|
import { prepareRequest } from './request.js';
|
|
3
3
|
import { capabilities, TypstPdfError } from './types.js';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveRequest } from './definition.js';
|
|
5
|
+
import { explainCompileError } from './explain.js';
|
|
6
|
+
import { documentsMarker, packageMounts, runtimeMarker } from './typst-package.js';
|
|
5
7
|
export { capabilities, pdfStandards, TypstPdfError } from './types.js';
|
|
8
|
+
export { createAttachments, defineInput, definePartial, defineTemplate } from './definition.js';
|
|
6
9
|
export { templateFromFiles } from './template.js';
|
|
10
|
+
/**
|
|
11
|
+
* A compiler for the page, created when it is first needed: nothing is downloaded or initialized until
|
|
12
|
+
* the first `compile`, which loads the WASM and the fonts once for every later call. A load that fails is
|
|
13
|
+
* not kept, so the next `compile` tries again. Calls run one at a time, in order, so callers never see
|
|
14
|
+
* `BUSY`; a call whose signal is aborted before its turn rejects with `ABORTED` without running. A request
|
|
15
|
+
* is read when its turn comes. Synchronous, so it can be created at module level and passed as a
|
|
16
|
+
* `PdfClient` (to `PdfProvider`, or as `client`). Use `@typfolio/workers` to compile off the main thread.
|
|
17
|
+
*/
|
|
18
|
+
export function createLazyCompiler(options) {
|
|
19
|
+
// Font sources are checked (and byte fonts copied) now, so a mistake shows where the client is made.
|
|
20
|
+
const fonts = copyFonts(options?.fonts);
|
|
21
|
+
let loading;
|
|
22
|
+
let queue = Promise.resolve();
|
|
23
|
+
let closed = false;
|
|
24
|
+
const load = () => {
|
|
25
|
+
if (loading === undefined) {
|
|
26
|
+
const attempt = createCompiler({ ...options, fonts });
|
|
27
|
+
loading = attempt;
|
|
28
|
+
attempt.catch(() => {
|
|
29
|
+
if (loading === attempt)
|
|
30
|
+
loading = undefined;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return loading;
|
|
34
|
+
};
|
|
35
|
+
const disposed = () => new TypstPdfError('DISPOSED', 'The compiler is disposed');
|
|
36
|
+
return {
|
|
37
|
+
compile(request, compileOptions = {}) {
|
|
38
|
+
if (closed)
|
|
39
|
+
return Promise.reject(disposed());
|
|
40
|
+
const job = queue.then(async () => {
|
|
41
|
+
if (closed)
|
|
42
|
+
throw disposed();
|
|
43
|
+
if (compileOptions.signal?.aborted) {
|
|
44
|
+
throw new TypstPdfError('ABORTED', 'Compilation was cancelled', [], { cause: compileOptions.signal.reason });
|
|
45
|
+
}
|
|
46
|
+
return (await load()).compile(request, compileOptions);
|
|
47
|
+
});
|
|
48
|
+
queue = job.catch(() => undefined);
|
|
49
|
+
return job;
|
|
50
|
+
},
|
|
51
|
+
async dispose() {
|
|
52
|
+
closed = true;
|
|
53
|
+
await queue;
|
|
54
|
+
const compiler = await loading?.catch(() => undefined);
|
|
55
|
+
await compiler?.dispose();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
7
59
|
/** One active job per compiler. Use a worker client for queues and hard cancellation. */
|
|
8
60
|
export async function createCompiler(options) {
|
|
9
61
|
const sources = copyFonts(options?.fonts);
|
|
@@ -27,14 +79,25 @@ export async function createCompiler(options) {
|
|
|
27
79
|
if (busy)
|
|
28
80
|
throw new TypstPdfError('BUSY', 'The compiler already has an active job');
|
|
29
81
|
checkAbort(options.signal);
|
|
30
|
-
|
|
82
|
+
// A defined template's documents are checked against their schemas here; a synchronous schema
|
|
83
|
+
// throws before anything is captured.
|
|
84
|
+
const { request: resolved, validated } = resolveRequest(request);
|
|
85
|
+
const prepared = prepareRequest(resolved, { copyAssets }); // Capture data before yielding to caller code.
|
|
31
86
|
busy = true;
|
|
32
87
|
let release;
|
|
33
88
|
finished = new Promise(resolve => { release = resolve; });
|
|
34
89
|
try {
|
|
35
|
-
|
|
90
|
+
// Yields to the caller even without an asynchronous schema, so a signal can still abort.
|
|
91
|
+
await validated;
|
|
36
92
|
checkAbort(options.signal);
|
|
37
|
-
const
|
|
93
|
+
const inputs = prepared.documents ? [runtimeMarker, documentsMarker] : [runtimeMarker];
|
|
94
|
+
let result;
|
|
95
|
+
try {
|
|
96
|
+
result = engine.compile(prepared, { mounts: packageMounts(prepared.files.get('/input.json')), inputs });
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
throw explainCompileError(error, prepared.files);
|
|
100
|
+
}
|
|
38
101
|
checkAbort(options.signal);
|
|
39
102
|
return result;
|
|
40
103
|
}
|
package/dist/internal.d.ts
CHANGED
|
@@ -5,4 +5,6 @@
|
|
|
5
5
|
export { createEngine, initialize, resolveFonts } from './engine.js';
|
|
6
6
|
export type { CompileExtras } from './engine.js';
|
|
7
7
|
export { prepareRequest } from './request.js';
|
|
8
|
+
export { isTemplateDefinition, resolveRequest } from './definition.js';
|
|
8
9
|
export { bundledPackageDirectory, packageFileMounts } from './typst-package.js';
|
|
10
|
+
export { explainCompileError } from './explain.js';
|
package/dist/internal.js
CHANGED
|
@@ -4,4 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { createEngine, initialize, resolveFonts } from './engine.js';
|
|
6
6
|
export { prepareRequest } from './request.js';
|
|
7
|
+
export { isTemplateDefinition, resolveRequest } from './definition.js';
|
|
7
8
|
export { bundledPackageDirectory, packageFileMounts } from './typst-package.js';
|
|
9
|
+
export { explainCompileError } from './explain.js';
|
package/dist/request.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import type { DocumentsRequest } from './definition.js';
|
|
1
2
|
import { type PdfRequest } from './types.js';
|
|
2
3
|
export declare function virtualPath(path: unknown): string;
|
|
3
4
|
export interface PrepareRequestOptions {
|
|
4
5
|
/** Copy every asset (default). `false` mounts the caller's `Uint8Array` views as they are. */
|
|
5
6
|
readonly copyAssets?: boolean;
|
|
6
7
|
}
|
|
7
|
-
export declare function prepareRequest(request: PdfRequest<unknown
|
|
8
|
+
export declare function prepareRequest(request: PdfRequest<unknown> | DocumentsRequest, options?: PrepareRequestOptions): {
|
|
8
9
|
entrypoint: string;
|
|
9
10
|
files: Map<string, Uint8Array<ArrayBufferLike>>;
|
|
11
|
+
documents: boolean;
|
|
10
12
|
pdf: {
|
|
11
13
|
pdf_standard: any;
|
|
12
14
|
pdf_tags: {};
|
package/dist/request.js
CHANGED
|
@@ -63,7 +63,7 @@ function jsonData(value, ancestors, at) {
|
|
|
63
63
|
export function prepareRequest(request, options = {}) {
|
|
64
64
|
const copyAssets = options.copyAssets ?? true;
|
|
65
65
|
const value = record(request, 'request');
|
|
66
|
-
keys(value, ['template', 'input', 'assets', 'pdf'], 'request');
|
|
66
|
+
keys(value, ['template', 'input', 'documents', 'assets', 'pdf'], 'request');
|
|
67
67
|
// A string template is shorthand for { files: { 'main.typ': template } } with the default entrypoint.
|
|
68
68
|
const template = typeof value.template === 'string'
|
|
69
69
|
? { files: { [defaultEntrypoint]: value.template } }
|
|
@@ -74,7 +74,7 @@ export function prepareRequest(request, options = {}) {
|
|
|
74
74
|
const files = new Map();
|
|
75
75
|
const put = (name, contents) => {
|
|
76
76
|
const path = virtualPath(name);
|
|
77
|
-
if (path === '/input.json')
|
|
77
|
+
if (path === '/input.json' || path.startsWith('/input.json/'))
|
|
78
78
|
invalid('/input.json is reserved for request.input');
|
|
79
79
|
if (isReservedPath(path))
|
|
80
80
|
invalid(`${reservedDirectory}/ is reserved for core's Typst package: ${path}`);
|
|
@@ -103,16 +103,24 @@ export function prepareRequest(request, options = {}) {
|
|
|
103
103
|
put(name, copyAssets ? new Uint8Array(bytes) : bytes);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
// The JSON bytes of request data, or INVALID_REQUEST naming it.
|
|
107
|
+
const json = (data, at) => {
|
|
108
|
+
try {
|
|
109
|
+
return encoder.encode(JSON.stringify(jsonData(data, new Set(), at)));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof TypstPdfError)
|
|
113
|
+
throw error;
|
|
114
|
+
throw new TypstPdfError('INVALID_REQUEST', `Could not serialize ${at} as JSON`, [], { cause: error });
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
// Documents: JSON data mounted at the paths of their schema files, read with `input(json.with(...))`.
|
|
118
|
+
const documents = value.documents !== undefined;
|
|
119
|
+
if (documents) {
|
|
120
|
+
for (const [name, data] of Object.entries(record(value.documents, 'documents')))
|
|
121
|
+
put(name, json(data, `document ${name}`));
|
|
115
122
|
}
|
|
123
|
+
files.set('/input.json', json(value.input, 'input'));
|
|
116
124
|
const pdf = value.pdf === undefined ? {} : record(value.pdf, 'pdf');
|
|
117
125
|
keys(pdf, ['standards', 'tags', 'creationTimestamp'], 'pdf');
|
|
118
126
|
const standards = pdf.standards ?? [];
|
|
@@ -128,7 +136,7 @@ export function prepareRequest(request, options = {}) {
|
|
|
128
136
|
if (pdf.creationTimestamp !== undefined && !Number.isSafeInteger(pdf.creationTimestamp))
|
|
129
137
|
invalid('creationTimestamp must be Unix seconds as a safe integer');
|
|
130
138
|
return {
|
|
131
|
-
entrypoint, files,
|
|
139
|
+
entrypoint, files, documents,
|
|
132
140
|
pdf: { pdf_standard: standards[0], pdf_tags: pdf.tags ?? true, creation_timestamp: pdf.creationTimestamp },
|
|
133
141
|
};
|
|
134
142
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnyPdfRequest, CheckedRequest } from './definition.js';
|
|
1
2
|
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
2
3
|
[key: string]: JsonValue;
|
|
3
4
|
};
|
|
@@ -34,6 +35,8 @@ export interface PdfRequest<Input = JsonValue> {
|
|
|
34
35
|
assets?: Readonly<Record<string, Uint8Array>>;
|
|
35
36
|
pdf?: PdfOptions;
|
|
36
37
|
}
|
|
38
|
+
/** A failure Typfolio recognises; `docs/errors.md` explains each and what to do. */
|
|
39
|
+
export type DiagnosticCode = 'file-not-found' | 'missing-glyph';
|
|
37
40
|
export interface Diagnostic {
|
|
38
41
|
severity: 'error' | 'warning' | string;
|
|
39
42
|
message: string;
|
|
@@ -41,7 +44,15 @@ export interface Diagnostic {
|
|
|
41
44
|
/** Upstream range: zero-based line:column-line:column. */
|
|
42
45
|
range?: string;
|
|
43
46
|
package?: string;
|
|
47
|
+
/** Set on a failure Typfolio recognises; switch on it rather than on `message`. */
|
|
48
|
+
code?: DiagnosticCode;
|
|
49
|
+
/** `file-not-found`: the missing file, as an absolute virtual path. */
|
|
50
|
+
file?: string;
|
|
51
|
+
/** `missing-glyph`: the text none of the loaded fonts can display. */
|
|
52
|
+
text?: string;
|
|
44
53
|
}
|
|
54
|
+
/** The section of docs/errors.md that explains `code`. */
|
|
55
|
+
export declare const errorLink: (code: DiagnosticCode) => string;
|
|
45
56
|
export interface PdfResult {
|
|
46
57
|
bytes: Uint8Array;
|
|
47
58
|
diagnostics: readonly Diagnostic[];
|
|
@@ -50,7 +61,8 @@ export interface PdfResult {
|
|
|
50
61
|
};
|
|
51
62
|
}
|
|
52
63
|
export interface PdfClient {
|
|
53
|
-
|
|
64
|
+
/** A plain request, or one for a template made with `defineTemplate`, type-checked against its `TemplateRequest`. */
|
|
65
|
+
compile<R extends AnyPdfRequest>(request: R & CheckedRequest<R>, options?: {
|
|
54
66
|
signal?: AbortSignal;
|
|
55
67
|
}): Promise<PdfResult>;
|
|
56
68
|
}
|
|
@@ -72,7 +84,8 @@ export declare const capabilities: Readonly<{
|
|
|
72
84
|
readonly typstPackages: readonly string[];
|
|
73
85
|
readonly hardCancellation: false;
|
|
74
86
|
}>;
|
|
75
|
-
|
|
87
|
+
/** `INVALID_INPUT`: a defined template's input, or a partial's data, does not match its schema. */
|
|
88
|
+
export type ErrorCode = 'INITIALIZATION' | 'INVALID_REQUEST' | 'INVALID_INPUT' | 'UNSUPPORTED_OPTION' | 'BUSY' | 'DISPOSED' | 'ABORTED' | 'COMPILATION' | 'ENGINE';
|
|
76
89
|
export declare class TypstPdfError extends Error {
|
|
77
90
|
readonly code: ErrorCode;
|
|
78
91
|
readonly diagnostics: readonly Diagnostic[];
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { bundledPackageSpec } from './typst-package.js';
|
|
2
2
|
export const pdfStandards = ['1.4', '1.5', '1.6', '1.7', '2.0', 'a-1b', 'a-1a', 'a-2b', 'a-2u', 'a-2a', 'a-3b', 'a-3u', 'a-3a', 'a-4', 'a-4f', 'a-4e', 'ua-1'];
|
|
3
|
+
/** The section of docs/errors.md that explains `code`. */
|
|
4
|
+
export const errorLink = (code) => `https://github.com/HelioCampus/typfolio/blob/main/docs/errors.md#${code}`;
|
|
3
5
|
export const capabilities = Object.freeze({
|
|
4
6
|
output: 'pdf',
|
|
5
7
|
standards: Object.freeze([...pdfStandards]),
|
package/dist/typst-package.d.ts
CHANGED
|
@@ -16,3 +16,5 @@ export declare function packageFileMounts(): [string, Uint8Array][];
|
|
|
16
16
|
export declare function packageMounts(input: Uint8Array): [string, Uint8Array][];
|
|
17
17
|
/** `sys.inputs` entry that tells the helper it is running under Typfolio. */
|
|
18
18
|
export declare const runtimeMarker: [string, string];
|
|
19
|
+
/** Set when the request mounts documents: `input(json.with(path))` then reads the data mounted at `path`. */
|
|
20
|
+
export declare const documentsMarker: [string, string];
|
|
@@ -3,7 +3,7 @@ export declare const typfolioPackage: {
|
|
|
3
3
|
readonly name: "typfolio";
|
|
4
4
|
readonly version: "0.1.0";
|
|
5
5
|
readonly files: {
|
|
6
|
-
readonly "typst.toml": "[package]\nname = \"typfolio\"\nversion = \"0.1.0\"\nentrypoint = \"lib.typ\"\
|
|
7
|
-
readonly "lib.typ": "
|
|
6
|
+
readonly "typst.toml": "[package]\nname = \"typfolio\"\nversion = \"0.1.0\"\nentrypoint = \"lib.typ\"\n";
|
|
7
|
+
readonly "lib.typ": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n#let input(schema) = {\n if \"typfolio\" in sys.inputs {\n if sys.inputs.at(\"typfolio-inputs\", default: none) != \"documents\" { return json(\"/input.json\") }\n\n if type(schema) == function { return schema() }\n panic(\"@local/typfolio: in a template made with defineTemplate, input() needs json.with(\\\"<schema>.json\\\"): the data is mounted at that path\")\n }\n import \"sample.typ\": sample-from-schema\n let mode = sys.inputs.at(\"typfolio-sample\", default: \"full\")\n if type(schema) == function { return sample-from-schema(schema(), mode: mode) }\n if type(schema) == dictionary { return sample-from-schema(schema, mode: mode) }\n if schema == none {\n panic(\"@local/typfolio: input() needs a JSON Schema so the editor can build a sample. Pass json.with(\\\"<schema>.json\\\"), for example input(json.with(\\\"./report.schema.json\\\")).\")\n }\n panic(\"@local/typfolio: input() expects json.with(\\\"<schema>.json\\\") or a schema dictionary, got \" + repr(type(schema)))\n}\n";
|
|
8
8
|
};
|
|
9
9
|
};
|
|
@@ -6,7 +6,7 @@ export const typfolioPackage = {
|
|
|
6
6
|
"name": "typfolio",
|
|
7
7
|
"version": "0.1.0",
|
|
8
8
|
"files": {
|
|
9
|
-
"typst.toml": "[package]\nname = \"typfolio\"\nversion = \"0.1.0\"\nentrypoint = \"lib.typ\"\
|
|
10
|
-
"lib.typ": "
|
|
9
|
+
"typst.toml": "[package]\nname = \"typfolio\"\nversion = \"0.1.0\"\nentrypoint = \"lib.typ\"\n",
|
|
10
|
+
"lib.typ": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n#let input(schema) = {\n if \"typfolio\" in sys.inputs {\n if sys.inputs.at(\"typfolio-inputs\", default: none) != \"documents\" { return json(\"/input.json\") }\n\n if type(schema) == function { return schema() }\n panic(\"@local/typfolio: in a template made with defineTemplate, input() needs json.with(\\\"<schema>.json\\\"): the data is mounted at that path\")\n }\n import \"sample.typ\": sample-from-schema\n let mode = sys.inputs.at(\"typfolio-sample\", default: \"full\")\n if type(schema) == function { return sample-from-schema(schema(), mode: mode) }\n if type(schema) == dictionary { return sample-from-schema(schema, mode: mode) }\n if schema == none {\n panic(\"@local/typfolio: input() needs a JSON Schema so the editor can build a sample. Pass json.with(\\\"<schema>.json\\\"), for example input(json.with(\\\"./report.schema.json\\\")).\")\n }\n panic(\"@local/typfolio: input() expects json.with(\\\"<schema>.json\\\") or a schema dictionary, got \" + repr(type(schema)))\n}\n"
|
|
11
11
|
}
|
|
12
12
|
};
|
package/dist/typst-package.js
CHANGED
|
@@ -26,3 +26,5 @@ export function packageMounts(input) {
|
|
|
26
26
|
}
|
|
27
27
|
/** `sys.inputs` entry that tells the helper it is running under Typfolio. */
|
|
28
28
|
export const runtimeMarker = ['typfolio', version];
|
|
29
|
+
/** Set when the request mounts documents: `input(json.with(path))` then reads the data mounted at `path`. */
|
|
30
|
+
export const documentsMarker = ['typfolio-inputs', 'documents'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typfolio/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Framework-independent Typst PDF compiler for the browser and Node, running on WebAssembly",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"homepage": "https://github.com/HelioCampus/typfolio#readme",
|
|
14
14
|
"bugs": { "url": "https://github.com/HelioCampus/typfolio/issues" },
|
|
15
15
|
"files": ["dist", "typst-packages", "README.md"],
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
16
17
|
"exports": {
|
|
17
18
|
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
|
|
18
19
|
"./capture": { "types": "./dist/capture.d.ts", "import": "./dist/capture.js" },
|
|
@@ -20,8 +21,16 @@
|
|
|
20
21
|
"./capture/dom": { "types": "./dist/capture/dom.d.ts", "import": "./dist/capture/dom.js" },
|
|
21
22
|
"./internal": { "types": "./dist/internal.d.ts", "import": "./dist/internal.js" }
|
|
22
23
|
},
|
|
24
|
+
"typesVersions": {
|
|
25
|
+
"*": {
|
|
26
|
+
"capture": ["./dist/capture.d.ts"],
|
|
27
|
+
"capture/svg": ["./dist/capture/svg.d.ts"],
|
|
28
|
+
"capture/dom": ["./dist/capture/dom.d.ts"],
|
|
29
|
+
"internal": ["./dist/internal.d.ts"]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
23
32
|
"dependencies": {
|
|
24
|
-
"@typfolio/wasm": "0.1.
|
|
33
|
+
"@typfolio/wasm": "0.1.1"
|
|
25
34
|
},
|
|
26
35
|
"peerDependencies": {
|
|
27
36
|
"html2canvas": "^1.4.1"
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
// #import "@local/typfolio:0.1.0": input
|
|
4
4
|
// #let data = input(json.with("./report.schema.json"))
|
|
5
5
|
//
|
|
6
|
-
// While Typfolio compiles, core sets `sys.inputs.typfolio` and `input()` returns
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// While Typfolio compiles, core sets `sys.inputs.typfolio` and `input()` returns runtime data. For a
|
|
7
|
+
// template made with `defineTemplate`, every input (the template's own and each partial's) is mounted
|
|
8
|
+
// at the path of its schema file, so `input(json.with("./header.schema.json"))` reads the header's
|
|
9
|
+
// data there: the schema path is the input's identity, and a file can only read its own. For a plain
|
|
10
|
+
// request, the input is the package root's /input.json. In an editor nothing sets the marker, so
|
|
11
|
+
// `input()` builds a sample from the JSON Schema and the editor can complete `#data.` fields. The
|
|
12
|
+
// sample is for authoring only: it is not validated, and keywords that only restrict values are
|
|
13
|
+
// ignored.
|
|
10
14
|
//
|
|
11
15
|
// The sample builder lives in `sample.typ` and is imported inside `input()`, in the editor branch only,
|
|
12
16
|
// so a runtime compile never loads it. `--input typfolio-sample=minimal` (Tinymist's `typstExtraArgs`)
|
|
@@ -20,7 +24,12 @@
|
|
|
20
24
|
/// `schema` is `json.with("<path>")` (a function that loads the schema, with the path resolved
|
|
21
25
|
/// relative to the calling file) or an already-loaded schema dictionary.
|
|
22
26
|
#let input(schema) = {
|
|
23
|
-
if "typfolio" in sys.inputs {
|
|
27
|
+
if "typfolio" in sys.inputs {
|
|
28
|
+
if sys.inputs.at("typfolio-inputs", default: none) != "documents" { return json("/input.json") }
|
|
29
|
+
// The data is mounted where the schema file sits, so the same function now loads the data.
|
|
30
|
+
if type(schema) == function { return schema() }
|
|
31
|
+
panic("@local/typfolio: in a template made with defineTemplate, input() needs json.with(\"<schema>.json\"): the data is mounted at that path")
|
|
32
|
+
}
|
|
24
33
|
import "sample.typ": sample-from-schema
|
|
25
34
|
let mode = sys.inputs.at("typfolio-sample", default: "full")
|
|
26
35
|
if type(schema) == function { return sample-from-schema(schema(), mode: mode) }
|