diff --git a/.github/workflows/gh_pages.yml b/.github/workflows/gh_pages.yml index 37979128..4af638f7 100644 --- a/.github/workflows/gh_pages.yml +++ b/.github/workflows/gh_pages.yml @@ -50,7 +50,7 @@ jobs: run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | bash - name: Download & install typst-book run: | - curl -L https://github.com/Myriad-Dreamin/typst-book/releases/download/v0.1.2-nightly1/typst-book-x86_64-unknown-linux-gnu.tar.gz | tar -xvz + curl -L https://github.com/Myriad-Dreamin/typst-book/releases/download/v0.1.2-nightly2/typst-book-x86_64-unknown-linux-gnu.tar.gz | tar -xvz chmod +x typst-book-x86_64-unknown-linux-gnu/bin/typst-book sudo cp typst-book-x86_64-unknown-linux-gnu/bin/typst-book /usr/bin/typst-book - name: Run sccache-cache diff --git a/docs/cookery/book.typ b/docs/cookery/book.typ index 94acecc8..d3968161 100644 --- a/docs/cookery/book.typ +++ b/docs/cookery/book.typ @@ -31,11 +31,12 @@ #prefix-chapter("introduction.typ")[Introduction] = Quickstart - #chapter("get-started.typ", section: "1")[Get started] - - #chapter(none, section: "1.1")[All-in-one JavaScript Library] + - #chapter("guide/all-in-one.typ", section: "1.1")[All-in-one JavaScript Library] - #chapter("guide/compilers.typ", section: "2")[Compilers] - #chapter("guide/compiler/ts-cli.typ", section: "2.1")[Command Line Interface] - #chapter("guide/compiler/service.typ", section: "2.2")[Compiler Service Library] - #chapter("guide/compiler/serverless.typ", section: "2.3")[Serverless (In-browser) Compiler] + - #chapter("guide/compiler/node.typ", section: "2.4")[Compiler for Node.js] - #chapter("guide/e2e-renderers.typ", section: "3")[End-to-end Renderers] - #chapter(none, section: "3.1")[React Library] - #chapter(none, section: "3.2")[Angular Library] diff --git a/docs/cookery/guide/all-in-one.typ b/docs/cookery/guide/all-in-one.typ new file mode 100644 index 00000000..545ac59a --- /dev/null +++ b/docs/cookery/guide/all-in-one.typ @@ -0,0 +1,147 @@ +#import "/docs/cookery/book.typ": book-page + +#show: book-page.with(title: "All-in-one (Simplified) JavaScript Library") + += All-in-one (Simplified) JavaScript Library + +#let snippet-source = "https://github.com/Myriad-Dreamin/typst.ts/blob/main/packages/typst.ts/src/contrib/snippet.mts" +#let snippet-lib = link(snippet-source)[`snippet`] + +The most simple examples always work with #snippet-lib utility library, an all-in-one JavaScript Library with simplified API interfaces: + +```ts +import { $typst } from '@myriaddreamin/typst.ts/dist/esm/contrib/snippet.mjs'; +console.log((await $typst.svg({ + mainContent: 'Hello, typst!' })).length); +// :-> 7317 +``` + +However, it is less flexible and stable than the underlying interfaces, the `TypstCompiler` and `TypstRenderer`. If you've become more familar with typst.ts, we recommend you rewrite your library with underlying interfaces according to example usage shown by the #snippet-lib library. + +Note: If your script targets to *CommonJS*, you should import it in *CommonJS* path instead of In *ES Module* path: + +```ts +const { createTypstCompiler } = require( + '@myriaddreamin/typst.ts/dist/cjs/compiler.cjs'); +``` + +== Examples + +Here are some examples for the #snippet-lib utility library. + +=== Example: Use the _global shared_ compiler instance: + +```typescript +import { $typst } from '@myriaddreamin/typst.ts/dist/esm/contrib/snippet.mjs'; +``` + +Note: if you want to compile multiple documents, you should create a new instance for each compilation work or maintain the shared state on the utility instance `$typst` carefully, +because the compilation process may change the state of that. + +=== Example: Create an instance of the utility class: + +```typescript +// optional renderer instance +const renderer = enableRendering ?? (() => { + return createGlobalRenderer(createTypstRenderer, + undefined /* pdfJsLib */, initOptions); +}); +const $typst = new TypstSnippet(() => { + return createGlobalCompiler(createTypstCompiler, + initOptions); +}, renderer); +``` + +=== Example: get output from input + +get output with *single file*: + +```ts +const mainContent = 'Hello, typst!'; +// into vector format +await $typst.vector({ mainContent }); +// into svg format +await $typst.svg({ mainContent }); +// into pdf format +await $typst.pdf({ mainContent }); +// into canvas operations +await $typst.canvas(div, { mainContent }); +``` + +get output with *multiple files*: + +```ts +// the default value of main path is '/main.typ' +await $typst.addSource('/main.typ', mainContent); + +// set path to main file +const mainFilePath = '/tasks/1/main.typ'; +await $typst.setMainFilePath(mainFilePath) +await $typst.addSource(mainFilePath, mainContent); +``` + +What is quite important is that, when you are running multiple tasks asynchronously or in parallel, the call pattern `await $typst.xxx({ mainContent });` is unsafe (introduces undefined behavior). Insteadly you should call compilation by specifying path to the main file: + +```ts +const mainFilePath = '/tasks/1/main.typ'; +await $typst.addSource(mainFilePath, mainContent); + +// compile source of path +await $typst.svg({ mainFilePath }); +``` + +get output with *binary files*: + +```ts +const encoder = new TextEncoder(); +// add a json file (utf8) +compiler.mapShadow('/assets/data.json', encoder.encode(jsonData)); +// remove a json file +compiler.unmapShadow('/assets/data.json'); + +// add an image file +const pngData = await fetch(...).arrayBuffer(); +compiler.mapShadow('/assets/tiger.png', new Uint8Array(pngData)); +``` + +clean up shadow files for underlying access model: + +```ts +compiler.resetShadow(); +``` + +Note: this function will also clean all files added by `addSource`. + +=== Example: reuse compilation result + +The compilation result could be stored in an artifact in #link("https://github.com/Myriad-Dreamin/typst.ts/blob/main/docs/proposals/8-vector-representation-for-rendering.typ")[_Vector Format_], so that you could decouple compilation from rendering or make high-level cache compilation. + +```ts +const vectorData = await $typst.vector({ mainContent }); +// into svg format +await $typst.svg({ vectorData }); +// into canvas operations +await $typst.canvas(div, { vectorData }); +``` + +Note: the compilation is already cached by typst's `comemo` implicitly. + +== Specify extra init options + +The extra init options must be at the start of the main routine, or accurately before all invocations. + +```ts +// Example: cache default fonts to file system +$typst.setCompilerInitOptions(await cachedFontInitOptoins()); +// specify init options to renderer +$typst.setRendererInitOptions(rendererInitOptions); +// wire other `pdfJsLib` instance for renderer +$typst.setPdfjsModule(pdfJsLib); + +// The compiler instance is initialized in this call. +await $typst.svg({ mainContent }); +``` + +== Specify extra render options + +See #link(snippet-source)[comments on source] for more details. diff --git a/docs/cookery/guide/compiler/node.typ b/docs/cookery/guide/compiler/node.typ new file mode 100644 index 00000000..fb002ad0 --- /dev/null +++ b/docs/cookery/guide/compiler/node.typ @@ -0,0 +1,7 @@ +#import "/docs/cookery/book.typ": book-page + +#show: book-page.with(title: "Compiler for Node.js") + += Compiler for Node.js + +We are actively work on documentation, but you could take reference from #link("https://myriad-dreamin.github.io/typst.ts/cookery/guide/compiler/serverless.html")[Serverless Compiler] diff --git a/docs/cookery/guide/compiler/serverless.typ b/docs/cookery/guide/compiler/serverless.typ index c1ddc475..d069fe1b 100644 --- a/docs/cookery/guide/compiler/serverless.typ +++ b/docs/cookery/guide/compiler/serverless.typ @@ -4,4 +4,73 @@ = Serverless Compiler -Sample page +The most simple examples always work with `snippet` utility library: + +```ts +import { $typst } from '@myriaddreamin/typst.ts/dist/esm/contrib/snippet.mjs'; +console.log((await $typst.svg({ + mainContent: 'Hello, typst!' })).length); +// :-> 7317 +``` + +Please + +Quick example: + +```ts +import { createTypstCompiler } from '@myriaddreamin/typst.ts'; + +const mainFile = '/main.typ'; +const cc /* compiler */ = createTypstCompiler(); +await cc.init(); +cc.addSource(mainFile, 'Hello, typst!'); +await cc.compile({ mainFilePath: mainFile }); +``` + +Note: For #link("https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking")[_tree-shaking_], you should import it with longer path: + +In *ES Module* path: + +```ts +import { createTypstCompiler } from '@myriaddreamin/typst.ts/dist/esm/compiler.mjs'; +``` + +Or in *CommonJS* path: + +```ts +const { createTypstCompiler } = require('@myriaddreamin/typst.ts/dist/cjs/compiler.cjs'); +``` + +== Add or remove source/binary files + +You can also use the `{map,unmap,reset}Shadow` function to manipulate any text or binary file data for typst compiler. They will shadow the file access from provided access model directly in memory. + +The `mapShadow(path: string, content: Uint8Array): void;` resembles `addSource(path: string, source: string): void;`, but retrieves some binary data without guessing the underlying encoding. + +Example usage: + +```ts +const encoder = new TextEncoder(); +// add a json file (utf8) +compiler.mapShadow('/assets/data.json', encoder.encode(jsonData)); +// remove a json file +compiler.unmapShadow('/assets/data.json'); +// clean up all shadow files (Note: this function will also clean all files added by `addSource`) +compiler.resetShadow(); + +// add an image file +const pngData = await fetch(...).arrayBuffer(); +compiler.mapShadow('/assets/tiger.png', new Uint8Array(pngData)); +``` + +== Specify output format + +Export document as #link("https://github.com/Myriad-Dreamin/typst.ts/blob/main/docs/proposals/8-vector-representation-for-rendering.typ")[_Vector Format_] which can then load to the renderer to render the document. + +```ts +const artifactData = await compiler.compile({ + mainFilePath: '/main.typ', + // the default value of format field: + // format: 'vector', +}); +``` diff --git a/docs/cookery/guide/compilers.typ b/docs/cookery/guide/compilers.typ index 8f641240..44fdddbd 100644 --- a/docs/cookery/guide/compilers.typ +++ b/docs/cookery/guide/compilers.typ @@ -4,4 +4,9 @@ = Compilers -Sample page +See: + ++ #link("https://myriad-dreamin.github.io/typst.ts/cookery/guide/compiler/ts-cli.html")[Command Line Interface] ++ #link("https://myriad-dreamin.github.io/typst.ts/cookery/guide/compiler/service.html")[Compiler Service Library] ++ #link("https://myriad-dreamin.github.io/typst.ts/cookery/guide/compiler/serverless.html")[Serverless (In-browser) Compiler] ++ #link("https://myriad-dreamin.github.io/typst.ts/cookery/guide/compiler/node.html")[Compiler for Node.js] diff --git a/docs/cookery/templates/page.typ b/docs/cookery/templates/page.typ index cebba173..982b75d1 100644 --- a/docs/cookery/templates/page.typ +++ b/docs/cookery/templates/page.typ @@ -26,6 +26,12 @@ "Source Han Serif TC", ) +#let main-font-size = if is-web-target { + 16pt +} else { + 12pt +} + #let code-font = ( "BlexMono Nerd Font Mono", // typst-book's embedded font @@ -98,7 +104,7 @@ ) if is-web-target; // set text style - set text(font: main-font, size: 16pt, fill: main-color, lang: "en") + set text(font: main-font, size: main-font-size, fill: main-color, lang: "en") set line(stroke: main-color) diff --git a/packages/compiler/README.md b/packages/compiler/README.md index 0a01965f..155178b0 100644 --- a/packages/compiler/README.md +++ b/packages/compiler/README.md @@ -1,3 +1,6 @@ # typst-ts-web-compiler -See [Typst.ts](https://github.com/Myriad-Dreamin/typst.ts) +The compiler can run in both the browser and node.js. See documentation for details: + +- [Serverless Compiler](https://myriad-dreamin.github.io/typst.ts/cookery/guide/compiler/serverless.html) +- [Compiler for Node.js](https://myriad-dreamin.github.io/typst.ts/cookery/guide/compiler/node.html) diff --git a/packages/templates/node.js-compiler-next/README.md b/packages/templates/node.js-compiler-next/README.md index 6074bb63..af1581b3 100644 --- a/packages/templates/node.js-compiler-next/README.md +++ b/packages/templates/node.js-compiler-next/README.md @@ -2,5 +2,8 @@ ```shell yarn install +# use snippet below to run +npx tsc && node ./dist/esm/main.snippet.js +# full example npx tsc && node ./dist/esm/main.js ``` diff --git a/packages/templates/node.js-compiler-next/src/cached-font-middleware.ts b/packages/templates/node.js-compiler-next/src/cached-font-middleware.ts new file mode 100644 index 00000000..46cbfff1 --- /dev/null +++ b/packages/templates/node.js-compiler-next/src/cached-font-middleware.ts @@ -0,0 +1,52 @@ +import { preloadFontAssets } from '@myriaddreamin/typst.ts/dist/cjs/options.init.cjs'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; +import * as path from 'path'; +import { HttpsProxyAgent } from 'https-proxy-agent'; + +export async function cachedFontInitOptoins() { + const fetcher = (await import('node-fetch')).default; + const dataDir = + process.env.APPDATA || + (process.platform == 'darwin' + ? process.env.HOME + '/Library/Preferences' + : process.env.HOME + '/.local/share'); + + const cacheDir = path.join(dataDir, 'typst/fonts'); + + return { + beforeBuild: [ + preloadFontAssets({ + assets: ['text', 'cjk', 'emoji'], + fetcher: async (url: URL | RequestInfo, init?: RequestInit | undefined) => { + const cachePath = path.join(cacheDir, url.toString().replace(/[^a-zA-Z0-9]/g, '_')); + if (existsSync(cachePath)) { + const font_res = { + arrayBuffer: async () => { + return readFileSync(cachePath).buffer; + }, + }; + + return font_res as any; + } + + console.log('loading remote font:', url); + const proxyOption = process.env.HTTPS_PROXY + ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } + : {}; + + const font_res = await fetcher(url as any, { + ...proxyOption, + ...((init as any) || {}), + }); + const buffer = await font_res.arrayBuffer(); + mkdirSync(path.dirname(cachePath), { recursive: true }); + writeFileSync(cachePath, Buffer.from(buffer)); + font_res.arrayBuffer = async () => { + return buffer; + }; + return font_res as any; + }, + }), + ], + }; +} diff --git a/packages/templates/node.js-compiler-next/src/main.snippet.ts b/packages/templates/node.js-compiler-next/src/main.snippet.ts new file mode 100644 index 00000000..0a67aa21 --- /dev/null +++ b/packages/templates/node.js-compiler-next/src/main.snippet.ts @@ -0,0 +1,15 @@ +import { $typst } from '@myriaddreamin/typst.ts/dist/cjs/contrib/snippet.cjs'; + +import { cachedFontInitOptoins } from './cached-font-middleware'; + +async function main() { + $typst.setCompilerInitOptions(await cachedFontInitOptoins()); + + const svg = await $typst.svg({ + mainContent: 'Hello, typst!', + }); + + console.log('Renderer works exactly! The rendered SVG file:', svg.length); +} + +main(); diff --git a/packages/templates/node.js-compiler-next/src/main.ts b/packages/templates/node.js-compiler-next/src/main.ts index dbf00077..59998fc3 100644 --- a/packages/templates/node.js-compiler-next/src/main.ts +++ b/packages/templates/node.js-compiler-next/src/main.ts @@ -5,57 +5,14 @@ import * as _2 from '@myriaddreamin/typst-ts-web-compiler'; import { createTypstCompiler, createTypstRenderer } from '@myriaddreamin/typst.ts'; import { preloadFontAssets } from '@myriaddreamin/typst.ts/dist/cjs/options.init.cjs'; -import { existsSync, mkdirSync, readFileSync, writeFile, writeFileSync } from 'fs'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import * as path from 'path'; import { HttpsProxyAgent } from 'https-proxy-agent'; +import { cachedFontInitOptoins } from './cached-font-middleware'; async function main() { - const fetcher = (await import('node-fetch')).default; - const dataDir = - process.env.APPDATA || - (process.platform == 'darwin' - ? process.env.HOME + '/Library/Preferences' - : process.env.HOME + '/.local/share'); - - const cacheDir = path.join(dataDir, 'typst/fonts'); - const compiler = createTypstCompiler(); - await compiler.init({ - beforeBuild: [ - preloadFontAssets({ - assets: ['text', 'cjk', 'emoji'], - fetcher: async (url: URL | RequestInfo, init?: RequestInit | undefined) => { - const cachePath = path.join(cacheDir, url.toString().replace(/[^a-zA-Z0-9]/g, '_')); - if (existsSync(cachePath)) { - const font_res = { - arrayBuffer: async () => { - return readFileSync(cachePath).buffer; - }, - }; - - return font_res as any; - } - - console.log('loading remote font:', url); - const proxyOption = process.env.HTTPS_PROXY - ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } - : {}; - - const font_res = await fetcher(url as any, { - ...proxyOption, - ...((init as any) || {}), - }); - const buffer = await font_res.arrayBuffer(); - mkdirSync(path.dirname(cachePath), { recursive: true }); - writeFileSync(cachePath, Buffer.from(buffer)); - font_res.arrayBuffer = async () => { - return buffer; - }; - return font_res as any; - }, - }), - ], - }); + await compiler.init(await cachedFontInitOptoins()); compiler.addSource('/main.typ', 'Hello, typst!'); const artifactData = await compiler.compile({ diff --git a/packages/typst.ts/src/compiler.mts b/packages/typst.ts/src/compiler.mts index f7b22c6f..17246e56 100644 --- a/packages/typst.ts/src/compiler.mts +++ b/packages/typst.ts/src/compiler.mts @@ -14,7 +14,7 @@ export type CompileFormat = 'vector' | 'pdf'; /** * The options for compiling the document. */ -export interface CompileOptions { +export interface CompileOptions { /** * The path of the main file. */ @@ -25,7 +25,7 @@ export interface CompileOptions { * - 'pdf': for finally exporting pdf to the user. * @default 'vector' */ - format?: CompileFormat; + format?: F; } /** @@ -57,6 +57,8 @@ export interface TypstCompiler { * @returns {Promise} - artifact in vector format. * You can then load the artifact to the renderer to render the document. */ + compile(options: CompileOptions<'vector'>): Promise; + compile(options: CompileOptions<'pdf'>): Promise; compile(options: CompileOptions): Promise; /** diff --git a/packages/typst.ts/src/contrib/global-compiler.mts b/packages/typst.ts/src/contrib/global-compiler.mts new file mode 100644 index 00000000..a45ec229 --- /dev/null +++ b/packages/typst.ts/src/contrib/global-compiler.mts @@ -0,0 +1,44 @@ +import type { InitOptions } from '../options.init.mjs'; +import type { TypstCompiler } from '../compiler.mjs'; + +let globalCompiler: TypstCompiler | undefined = undefined; +let globalCompilerInitReady: Promise; +let isReady = false; + +export function getGlobalCompiler(): TypstCompiler | undefined { + return isReady ? globalCompiler : undefined; +} + +export function createGlobalCompiler( + creator: () => TypstCompiler, + initOptions?: Partial, +): Promise { + // todo: determine compiler thread-safety + // todo: check inconsistent initOptions + const compiler = globalCompiler || creator(); + + if (globalCompilerInitReady !== undefined) { + return globalCompilerInitReady; + } + + return (globalCompilerInitReady = (async () => { + isReady = true; + await compiler.init(initOptions); + return (globalCompiler = compiler); + })()); +} + +export function withGlobalCompiler( + creator: () => TypstCompiler, + initOptions: Partial | undefined, + resolve: (compiler: TypstCompiler) => void, + reject?: (err: any) => void, +) { + const compiler = getGlobalCompiler(); + if (compiler) { + resolve(compiler); + return; + } + + createGlobalCompiler(creator, initOptions).then(resolve).catch(reject); +} diff --git a/packages/typst.ts/src/contrib/global-renderer.mts b/packages/typst.ts/src/contrib/global-renderer.mts index b5bb2938..f1b04b0e 100644 --- a/packages/typst.ts/src/contrib/global-renderer.mts +++ b/packages/typst.ts/src/contrib/global-renderer.mts @@ -12,7 +12,7 @@ export function getGlobalRenderer(): TypstRenderer | undefined { export function createGlobalRenderer( creator: (pdf: /* typeof pdfjsModule */ any) => TypstRenderer, pdf: /* typeof pdfjsModule */ any, - initOptions: InitOptions, + initOptions?: Partial, ): Promise { // todo: determine renderer thread-safety // todo: check inconsistent initOptions @@ -32,7 +32,7 @@ export function createGlobalRenderer( export function withGlobalRenderer( creator: (pdf: /* typeof pdfjsModule */ any) => TypstRenderer, pdf: /* typeof pdfjsModule */ any, - initOptions: InitOptions, + initOptions: Partial | undefined, resolve: (renderer: TypstRenderer) => void, reject?: (err: any) => void, ) { diff --git a/packages/typst.ts/src/contrib/snippet.mts b/packages/typst.ts/src/contrib/snippet.mts new file mode 100644 index 00000000..71cf1703 --- /dev/null +++ b/packages/typst.ts/src/contrib/snippet.mts @@ -0,0 +1,319 @@ +import type { CompileOptions, TypstCompiler } from '../compiler.mjs'; +import type { InitOptions } from '../options.init.mjs'; +import type { TypstRenderer } from '../renderer.mjs'; +import type { RenderToCanvasOptions, RenderSvgOptions } from '../options.render.mjs'; + +/** + * Some function that returns a promise of value or just that value. + */ +type PromiseJust = (() => Promise) | T; + +/** + * The sweet options for compiling and rendering the document. + */ +export type SweetCompileOptions = + | { + /** + * The path of the main file. + */ + mainFilePath: string; + } + | { + /** + * The source content of the main file. + */ + mainContent: string; + }; + +/** + * The sweet options for compiling and rendering the document. + */ +export type SweetRenderOptions = + | SweetCompileOptions + | { + /** + * The artifact data in vector format. + */ + vectorData: Uint8Array; + }; + +/** + * Convenient util class for compiling documents, which is a wrapper of the + * {@link TypstCompiler} and {@link TypstRenderer}. + * + * Note: the interface of this class is less stable than {@link TypstCompiler} + * and {@link TypstRenderer}. + * + * @example + * Use the *global shared* compiler instance: + * + * ```typescript + * import { $typst } from '@myriaddreamin/typst.ts/dist/esm/contrib/snippet.mjs'; + * ``` + * + * Note: if you want to compile multiple documents, you should create a new + * instance for each compilation work or maintain the shared state on the + * utility instance `$typst` carefully, because the compilation process will + * change the state of that. + * + * @example + * Create an instance of utility: + * + * ```typescript + * // optional renderer instance + * const renderer = enableRendering ?? (() => { + * return createGlobalRenderer(createTypstRenderer, pdfJsLib, initOptions); + * }); + * const $typst = new TypstSnippet(() => { + * return createGlobalCompiler(createTypstCompiler, initOptions); + * }, renderer); + * ``` + */ +export class TypstSnippet { + /** @internal */ + private mainFilePath: string; + /** @internal */ + private cc?: PromiseJust; + /** @internal */ + private ex?: PromiseJust; + + /** + * Create a new instance of {@link TypstSnippet}. + * @param cc the compiler instance, see {@link PromiseJust} and {@link TypstCompiler}. + * @param ex the compiler instance, see {@link PromiseJust} and {@link TypstRenderer}. + * + * @example + * + * Passes a global shared compiler instance that get initialized lazily: + * ```typescript + * const $typst = new TypstSnippet(() => { + * return createGlobalCompiler(createTypstCompiler, initOptions); + * }); + * + */ + constructor(options?: { + compiler?: PromiseJust; + renderer?: PromiseJust; + }) { + this.cc = options?.compiler; + this.ex = options?.renderer; + this.mainFilePath = '/main.typ'; + } + + /** @internal */ + static ccOptions: Partial | undefined = undefined; + /** + * Set compiler init options for initializing global instance {@link $typst}. + * See {@link InitOptions}. + */ + setCompilerInitOptions(options: Partial) { + if (typeof this.cc !== 'function') { + throw new Error('compiler has been initialized'); + } + if (this !== $typst) { + throw new Error('can not set options for non-global instance'); + } + TypstSnippet.ccOptions = options; + } + + /** @internal */ + static exOptions: Partial | undefined = undefined; + /** + * Set renderer init options for initializing global instance {@link $typst}. + * See {@link InitOptions}. + */ + setRendererInitOptions(options: Partial) { + if (typeof this.ex !== 'function') { + throw new Error('renderer has been initialized'); + } + if (this !== $typst) { + throw new Error('can not set options for non-global instance'); + } + TypstSnippet.exOptions = options; + } + + /** @internal */ + static pdfjsModule: unknown | undefined = undefined; + /** + * Set pdf.js module for initializing global instance {@link $typst}. + */ + setPdfjsModule(module: unknown) { + if (typeof this.ex !== 'function') { + throw new Error('renderer has been initialized'); + } + if (this !== $typst) { + throw new Error('can not set pdfjs module for non-global instance'); + } + TypstSnippet.pdfjsModule = module; + } + + /** + * Set shared main file path. + */ + setMainFilePath(path: string) { + this.mainFilePath = path; + } + + /** + * Get shared main file path. + */ + getMainFilePath() { + return this.mainFilePath; + } + + /** + * See {@link TypstCompiler#addSource}. + */ + async addSource(path: string, content: string) { + (await this.getCompiler()).addSource(path, content); + } + + /** + * See {@link TypstCompiler#resetShadow}. + */ + async resetShadow() { + (await this.getCompiler()).resetShadow(); + } + + /** + * See {@link TypstCompiler#mapShadow}. + */ + async mapShadow(path: string, content: Uint8Array) { + (await this.getCompiler()).mapShadow(path, content); + } + + /** + * See {@link TypstCompiler#unmapShadow}. + */ + async unmapShadow(path: string) { + (await this.getCompiler()).unmapShadow(path); + } + + /** + * Compile the document to vector (IR) format. + * See {@link SweetCompileOptions}. + */ + async vector(o?: SweetCompileOptions) { + const opts = await this.getCompileOptions(o); + return (await this.getCompiler()).compile(opts); + } + + /** + * Compile the document to PDF format. + * See {@link SweetCompileOptions}. + */ + async pdf(o?: SweetCompileOptions) { + const opts = await this.getCompileOptions(o); + opts.format = 'pdf'; + return (await this.getCompiler()).compile(opts); + } + + /** + * Compile the document to SVG format. + * See {@link SweetRenderOptions} and {@link RenderSvgOptions}. + */ + async svg(o?: SweetRenderOptions & RenderSvgOptions) { + const rr = await this.getRenderer(); + if (!rr) { + throw new Error('does not provide renderer instance'); + } + const data = await this.getVector(o); + return await rr.runWithSession(async session => { + rr.manipulateData({ + renderSession: session, + action: 'reset', + data, + }); + return rr.renderSvgDiff({ + ...o, + renderSession: session, + }); + }); + } + + /** + * Compile the document to canvas operations. + * See {@link SweetRenderOptions} and {@link RenderToCanvasOptions}. + */ + async canvas( + container: HTMLElement, + o?: SweetRenderOptions & Omit, + ) { + const rr = await this.getRenderer(); + if (!rr) { + throw new Error('does not provide renderer instance'); + } + const data = await this.getVector(o); + return await rr.runWithSession(async session => { + rr.manipulateData({ + renderSession: session, + action: 'reset', + data, + }); + rr.renderToCanvas({ + container, + ...o, + renderSession: session, + }); + }); + } + + private async getCompiler() { + return (typeof this.cc === 'function' ? (this.cc = await this.cc()) : this.cc)!; + } + + private async getRenderer() { + return typeof this.ex === 'function' ? (this.ex = await this.ex()) : this.ex; + } + + private async getCompileOptions(opts?: SweetCompileOptions): Promise { + if (opts === undefined) { + return { mainFilePath: this.mainFilePath }; + } else if ('mainFilePath' in opts) { + return { ...opts }; + } else { + this.addSource(this.mainFilePath, opts.mainContent); + return { mainFilePath: this.mainFilePath }; + } + } + + private async getVector(opts?: SweetRenderOptions): Promise { + if (opts && 'vectorData' in opts) { + return opts.vectorData; + } + + const options = await this.getCompileOptions(opts); + return (await this.getCompiler()).compile(options); + } +} + +/** + * The lazy initialized global shared instance of {@link TypstSnippet}. See + * {@link TypstSnippet} for more details. + */ +export const $typst = new TypstSnippet({ + compiler: async () => { + // lazy import compile module + const { createGlobalCompiler } = (await import( + '@myriaddreamin/typst.ts/dist/esm/contrib/global-compiler.mjs' + )) as any as typeof import('./global-compiler.mjs'); + const { createTypstCompiler } = (await import( + '@myriaddreamin/typst.ts/dist/esm/compiler.mjs' + )) as any as typeof import('../compiler.mjs'); + + return createGlobalCompiler(createTypstCompiler, TypstSnippet.ccOptions); + }, + renderer: async () => { + // lazy import renderer module + const { createGlobalRenderer } = (await import( + '@myriaddreamin/typst.ts/dist/esm/contrib/global-renderer.mjs' + )) as any as typeof import('./global-renderer.mjs'); + const { createTypstRenderer } = (await import( + '@myriaddreamin/typst.ts/dist/esm/renderer.mjs' + )) as any as typeof import('../renderer.mjs'); + + const pdfjs = + TypstSnippet.pdfjsModule || (typeof window !== 'undefined' && (window as any)?.pdfjsLib); + return createGlobalRenderer(createTypstRenderer, pdfjs, TypstSnippet.exOptions); + }, +}); diff --git a/packages/typst.ts/src/renderer.mts b/packages/typst.ts/src/renderer.mts index f85f51a5..80cc5b88 100644 --- a/packages/typst.ts/src/renderer.mts +++ b/packages/typst.ts/src/renderer.mts @@ -800,11 +800,17 @@ class TypstRendererDriver { renderSvgDiff(options: RenderInSessionOptions): string { if (!options.window) { - return this.renderer.render_svg_diff(options.renderSession[kObject], 0, 0, 1e33, 1e33); + return this.renderer.render_svg_diff( + (options.renderSession as any)[kObject], + 0, + 0, + 1e33, + 1e33, + ); } return this.renderer.render_svg_diff( - options.renderSession[kObject], + (options.renderSession as any)[kObject], options.window.lo.x, options.window.lo.y, options.window.hi.x, @@ -824,7 +830,7 @@ class TypstRendererDriver { manipulateData(opts: RenderInSessionOptions): void { return this.renderer.manipulate_data( - opts.renderSession[kObject] as typst.RenderSession, + (opts.renderSession as any)[kObject] as typst.RenderSession, opts.action ?? 'reset', opts.data, ); @@ -841,7 +847,7 @@ class TypstRendererDriver { } if ('renderSession' in options) { - return fn(options.renderSession); + return fn(options.renderSession as RenderSession); } if (isRenderByContentOption(options)) {