typstrx 0.1.1 copy "typstrx: ^0.1.1" to clipboard
typstrx: ^0.1.1 copied to clipboard

Typst document viewer for Flutter, backed by the native Rust Typst compiler, with high-resolution viewport rendering, text selection, and link navigation.

typstrx #

A Typst document viewer for Flutter, in the spirit of pdfrx.

typstrx embeds the native Rust Typst compiler: you hand it a string of Typst source, it compiles the document (incrementally, thanks to Typst's built-in memoization), rasterizes pages in high resolution — only the part currently visible in the viewport — and exposes viewer widgets with text selection and tappable links, backed by text/link geometry extracted from the compiled document.

Status: work in progress. APIs may change before 1.0.

Showcase

Features #

  • Compile Typst source strings to paged documents, with diagnostics (errors/warnings with source locations)
  • High-resolution, viewport-based partial page rendering (only the visible window of each page is rasterized at high zoom)
  • TypstViewer widget with pan/zoom, text selection, and link navigation
  • TypstPageView, a host-controlled embeddable surface for canvases and notebooks that need Typst content inline rather than a paginated document
  • Vector PDF export from any successful compilation snapshot
  • Live recompilation while the source changes (debounced, flicker-free)
  • Embedded default fonts (Libertinus, New Computer Modern Math, DejaVu Sans Mono) plus an API to register custom font bytes
  • @preview Typst package support with on-demand downloads and local caching

Platforms #

Platform Status
Linux Supported
Android Supported
Windows Supported
iOS / macOS Scaffolded, untested
Web Not supported yet

If you think this is relevant for you, create a git issue.

Requirements #

The Rust crate embedded in this package is built via cargokit. On supported platforms,
Cargokit automatically downloads and verifies a signed precompiled binary
when Rust is unavailable. You need a Rust toolchain to build it locally or as
a fallback when a matching precompiled binary is unavailable:

  • A Rust toolchain (rustup)
  • For Android: NDK r26 or newer, plus the Rust targets for your ABIs, e.g. rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android

Using precompiled binaries when you have Rust installed #

Cargokit only reaches for the signed precompiled binaries automatically when it can't find a Rust toolchain on your machine (i.e. no rustup on PATH).
If you have Rust installed, Cargokit
defaults to compiling this crate from source instead, even though a matching
precompiled binary is published for your platform. That's a much slower build
(the crate pulls in the Typst compiler and its dependencies).

To opt into using the precompiled binaries anyway, add a cargokit_options.yaml file next to your pubspec.yaml

use_precompiled_binaries: true

Quick start #

import 'package:typstrx/typstrx.dart';

Future<void> main() async {
  await Typstrx.init();
  final session = await TypstSession.create();
  runApp(MaterialApp(home: TypstViewer(session: session)));
  session.updateSource('= Hello, *world*!');
}

See example/ for a full split-view editor + embedded viewer demo.

Render full documents with TypstViewer #

TypstViewer follows the latest successful compilation in its session. It provides pan/zoom, lazy page rasterization, text selection, and link handling:

final controller = TypstViewerController();

TypstViewer(
  session: session,
  controller: controller,
  pageMargin: 12, // Typst points around and between pages
  params: TypstViewerParams(
    previewDpi: 144,
    maxRenderDpi: 576,
    enableTextSelection: true,
    onLinkTap: (link) => print(link.url ?? link.dest),
  ),
)

// Compile immediately, or use updateSource for a debounced live preview.
session.updateSource('= Hello, *world*!');

Use TypstViewerParams.margin instead of pageMargin when you keep all viewer configuration in the params object. The top-level pageMargin property takes precedence when both are supplied.

Custom fonts and project files #

// Register extra font faces (TTF/OTF bytes) — available on the next compile.
await session.registerFont(await File('MyFont.ttf').readAsBytes());

// Provide in-memory project files that the source can reference.
await session.setFile('/images/logo.png', logoBytes);
session.updateSource('#image("/images/logo.png")');

@preview imports (Typst Universe packages) download on demand into TypstSessionOptions.packageCacheDir (pass an app-specific directory on mobile; cached packages work offline).

Working with the document API directly #

final result = await session.compile('= Hi');
final page = result.document!.pages.first;      // sizes in points
final image = await page.render(fullWidth: page.width * 2); // RGBA pixels
final text = await page.loadStructuredText();   // text + char rects
final links = await page.loadLinks();           // URL / internal dests
final pdf = await result.document!.exportPdf(); // vector PDF bytes, tagged
final untaggedPdf = await result.document!.exportPdf(tagged: false); // for embedding

Embedding content with TypstPageView #

For canvases, notebooks, or any surface where Typst content sits inline among other widgets rather than as a paginated document, compile as an embedded fragment and render it with TypstPageView — no pan/zoom, sized to its own content:

const width = 300.0; // Typst points — must match the scale below
await session.compileFragment(
  '#text(fill: blue)[Hello, fragment!]',
  const TypstFragmentOptions(width: width, transparent: true),
);

TypstPageView(
  session: session,
  scale: 1.0, // logical pixels per Typst point; keep in sync with `width`
  pageMargin: 8.0, // optional transparent viewer margin around the page
  onSizeChanged: (size) => print('content is now $size'),
)

TypstFragmentOptions wraps your source in a generated #set page(...) preamble (fixed width, height following content, transparent by default); diagnostics, completions, and hover all report positions in your source, not the wrapped one. TypstPageView only ever shows pages.first, so it expects a session compiled with compileFragment/updateFragmentSource rather than a multi-page compile()/updateSource() document. TypstPageView.pageMargin controls transparent space outside the rendered page. Use TypstFragmentOptions.margin when the margin should be compiled inside the Typst page. A full TypstViewer accepts the same top-level pageMargin override, or it can be configured through TypstViewerParams.margin.

Reading the current text selection (from either widget) works the same way regardless of embedding:

TypstViewerParams(
  onSelectionChanged: (selection) => print(selection?.text),
)
// or, via a controller:
final text = controller.selection?.text;

Roadmap #

  • Text search widget (the text model already supports allMatches-style search)
  • Scroll thumbs, facing-page layouts, selection magnifier
  • SelectionArea integration
  • True sub-region rendering backend (typst-svg + resvg) for very high zoom
  • Web support

License #

Apache-2.0. The Typst compiler is likewise Apache-2.0 licensed. third_party/typst-render-clip vendors a modified copy of Typst's typst-render crate (also Apache-2.0); see its NOTICE.md for the change list, as required by section 4(b) of the license. typstrx is not affiliated with or endorsed by the Typst project.

0
likes
160
points
111
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Typst document viewer for Flutter, backed by the native Rust Typst compiler, with high-resolution viewport rendering, text selection, and link navigation.

Repository (GitHub)
View/report issues

Topics

#typst #pdf #ffi #typesetting #code

License

Apache-2.0 (license)

Dependencies

flutter, flutter_rust_bridge, freezed_annotation, meta, plugin_platform_interface

More

Packages that depend on typstrx

Packages that implement typstrx