{"id":49284585,"url":"https://github.com/fulgur-rs/fulgur","last_synced_at":"2026-07-04T08:01:01.947Z","repository":{"id":345119701,"uuid":"1184542356","full_name":"fulgur-rs/fulgur","owner":"fulgur-rs","description":"Blazing fast HTML to PDF engine built on blitz and krilla","archived":false,"fork":false,"pushed_at":"2026-07-02T01:28:34.000Z","size":19256,"stargazers_count":50,"open_issues_count":22,"forks_count":8,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-02T02:22:47.647Z","etag":null,"topics":["pdf","rust"],"latest_commit_sha":null,"homepage":"https://fulgur.dev","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fulgur-rs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/security/threat-model.ja.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":"CLA.md"}},"created_at":"2026-03-17T17:30:22.000Z","updated_at":"2026-07-01T17:28:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"a96124dd-07a8-4e76-a3d0-03123edded3f","html_url":"https://github.com/fulgur-rs/fulgur","commit_stats":null,"previous_names":["mitsuru/fulgur","fulgur-rs/fulgur"],"tags_count":40,"template":false,"template_full_name":null,"purl":"pkg:github/fulgur-rs/fulgur","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fulgur-rs%2Ffulgur","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fulgur-rs%2Ffulgur/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fulgur-rs%2Ffulgur/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fulgur-rs%2Ffulgur/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fulgur-rs","download_url":"https://codeload.github.com/fulgur-rs/fulgur/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fulgur-rs%2Ffulgur/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35114174,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["pdf","rust"],"created_at":"2026-04-25T21:00:26.094Z","updated_at":"2026-07-04T08:01:01.937Z","avatar_url":"https://github.com/fulgur-rs.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fulgur\n\n[![codecov](https://codecov.io/gh/fulgur-rs/fulgur/graph/badge.svg)](https://codecov.io/gh/fulgur-rs/fulgur)\n[![Coverage](https://raw.githubusercontent.com/mitsuru/octocovs/main/badges/fulgur-rs/fulgur/coverage.svg)](https://github.com/mitsuru/octocovs)\n[![Code to Test Ratio](https://raw.githubusercontent.com/mitsuru/octocovs/main/badges/fulgur-rs/fulgur/ratio.svg)](https://github.com/mitsuru/octocovs)\n[![Test Execution Time](https://raw.githubusercontent.com/mitsuru/octocovs/main/badges/fulgur-rs/fulgur/time.svg)](https://github.com/mitsuru/octocovs)\n\nA modern, lightweight alternative to wkhtmltopdf. Converts HTML/CSS to PDF without a browser engine.\n\nBuilt in Rust for server-side workloads where memory footprint and startup time matter.\n\n## Why Fulgur?\n\n- **No browser required** — No Chromium, no WebKit, no headless browser. Single binary, instant cold start.\n- **Low memory footprint** — Designed for server-side batch processing without blowing up your container's memory limits.\n- **Deterministic output** — Same input always produces the same PDF, byte for byte. Safe for CI/CD and automated pipelines.\n- **Template + JSON data** — Feed an HTML template and a JSON file to generate PDFs at scale. Built-in [MiniJinja](https://github.com/mitsuhiko/minijinja) engine.\n- **Offline by design** — No network access. All assets (fonts, images, CSS) are explicitly bundled.\n\n## Features\n\n- HTML/CSS to PDF conversion with automatic page splitting\n- CSS pagination control (`break-before`, `break-after`, `break-inside`, orphans/widows)\n- CSS Generated Content for Paged Media (page counters, running headers/footers, margin boxes)\n- Template engine with JSON data binding (MiniJinja)\n- Image embedding (PNG / JPEG / GIF)\n- Custom font bundling with subsetting (TTF / OTF / TTC / WOFF2)\n- External CSS injection\n- Page sizes (A4 / Letter / A3) with landscape support\n- PDF metadata (title, author, keywords, language)\n- PDF bookmarks from heading structure\n- Tagged PDF / PDF/UA-1 for accessibility (structure tree, alt text, heading levels)\n- [CSS property support reference](docs/css-support.md)\n\n## Installation\n\nRun directly with `npx` (no install needed):\n\n```bash\nnpx @fulgur-rs/cli render -o output.pdf input.html\n```\n\nOr install globally via npm:\n\n```bash\nnpm install -g @fulgur-rs/cli\nfulgur render -o output.pdf input.html\n```\n\nOr via Cargo:\n\n```bash\ncargo install fulgur-cli\n```\n\nFrom source:\n\n```bash\ncargo install --path crates/fulgur-cli\n```\n\n## CLI Usage\n\n```bash\n# Basic conversion\nfulgur render -o output.pdf input.html\n\n# Read from stdin\ncat input.html | fulgur render --stdin -o output.pdf\n\n# Page options\nfulgur render -o output.pdf -s Letter -l --margin \"20 30\" input.html\n\n# Custom fonts and CSS\nfulgur render -o output.pdf -f fonts/NotoSansJP.ttf --css style.css input.html\n\n# Images\nfulgur render -o output.pdf -i logo.png=assets/logo.png input.html\n\n# Template + JSON data\nfulgur render -o invoice.pdf -d data.json template.html\n\n# Tagged PDF (accessibility / PDF/UA-1)\nfulgur render --tagged --language en -o report.pdf report.html\nfulgur render --pdf-ua --language en -o report.pdf report.html\n```\n\n### Template Example\n\n`template.html`:\n\n```html\n\u003ch1\u003eInvoice #{{ invoice_number }}\u003c/h1\u003e\n\u003cp\u003e{{ customer_name }}\u003c/p\u003e\n\u003ctable\u003e\n  {% for item in items %}\n  \u003ctr\u003e\u003ctd\u003e{{ item.name }}\u003c/td\u003e\u003ctd\u003e{{ item.price }}\u003c/td\u003e\u003c/tr\u003e\n  {% endfor %}\n\u003c/table\u003e\n```\n\n`data.json`:\n\n```json\n{\n  \"invoice_number\": \"2026-001\",\n  \"customer_name\": \"Acme Corp\",\n  \"items\": [\n    { \"name\": \"Widget\", \"price\": \"$10.00\" },\n    { \"name\": \"Gadget\", \"price\": \"$25.00\" }\n  ]\n}\n```\n\n### Options\n\n| Option | Description | Default |\n|---|---|---|\n| `-o, --output` | Output PDF file path (required, use `-` for stdout) | — |\n| `-s, --size` | Page size (A4, Letter, A3) | A4 |\n| `-l, --landscape` | Landscape orientation | false |\n| `--margin` | Page margins in mm (CSS shorthand: `\"20\"`, `\"20 30\"`, `\"10 20 30\"`, `\"10 20 30 40\"`) | — |\n| `--title` | PDF title metadata | — |\n| `--language` | Document language tag (BCP 47, e.g. `en`, `ja`). Required for PDF/UA-1. | — |\n| `-f, --font` | Font files to bundle (repeatable) | — |\n| `--css` | CSS files to include (repeatable) | — |\n| `-i, --image` | Image files to bundle as name=path (repeatable) | — |\n| `-d, --data` | JSON data file for template mode (use `-` for stdin) | — |\n| `--bookmarks` | Generate PDF bookmarks (outline) from `h1`-`h6` headings | false |\n| `--tagged` | Enable Tagged PDF output (structure tree for accessibility) | false |\n| `--pdf-ua` | Enable PDF/UA-1 conformance (implies `--tagged` and `--bookmarks`) | false |\n| `--stdin` | Read HTML from stdin | false |\n\n## Library Usage\n\n```rust\nuse fulgur::engine::Engine;\nuse fulgur::config::{PageSize, Margin};\n\n// Basic conversion\nlet engine = Engine::builder().build();\nlet pdf = engine.render(\"\u003ch1\u003eHello\u003c/h1\u003e\")?;\n\n// With page options\nlet engine = Engine::builder()\n    .page_size(PageSize::A4)\n    .margin(Margin::uniform_mm(20.0))\n    .title(\"My Document\")\n    .build();\n\nlet pdf = engine.render(html)?;\nengine.render_file(html, \"output.pdf\")?;\n\n// Template + JSON\nlet engine = Engine::builder()\n    .template(\"invoice.html\", template_str)\n    .data(serde_json::json!({\n        \"invoice_number\": \"2026-001\",\n        \"customer_name\": \"Acme Corp\",\n    }))\n    .build();\n\nlet pdf = engine.render_template()?;\n\n// Tagged PDF (accessibility / PDF/UA-1)\nlet engine = Engine::builder()\n    .tagged(true)  // structure tree only\n    .build();\n// or: .pdf_ua(true) for full PDF/UA-1 conformance (implies tagged + bookmarks)\n\nlet pdf = engine.render(html)?;\n```\n\n## Tagged PDF\n\nFulgur can embed a semantic structure tree in the PDF output, required for accessibility tools and PDF/UA-1 conformance.\n\n### CLI\n\n```bash\n# Structure tree only (--tagged)\nfulgur render --tagged --language en -o report.pdf report.html\n\n# Full PDF/UA-1 conformance (implies --tagged and --bookmarks)\nfulgur render --pdf-ua --language en -o report.pdf report.html\n```\n\n### Supported HTML elements\n\n| HTML element | PDF structure tag |\n|---|---|\n| `h1`–`h6` | H1–H6 |\n| `p`, `div` | P, Div |\n| `span` | Span |\n| `ul` | L (unordered) |\n| `ol` | L (ordered) |\n| `li` | LI → Lbl + LBody |\n| `table` | Table |\n| `thead` / `tbody` / `tfoot` | THead / TBody / TFoot |\n| `tr` | TR |\n| `th` | TH (scope: Row/Column/Both) |\n| `td` | TD |\n| `img` | Figure (alt text propagated) |\n| `a` | Link |\n\n### Limitations\n\n- Alt text is propagated from `\u003cimg alt\u003e` attributes only. CSS background images are not tagged.\n- PDF/UA-1 requires a document language. Always pass `--language` (CLI) or `.lang()` (Rust API) when using `--pdf-ua`.\n- Python and Ruby bindings do not yet expose `tagged` / `pdf_ua`. Follow-up work is tracked separately.\n\n## Architecture\n\nFulgur integrates [Blitz](https://github.com/nickelpack/blitz) (HTML parsing, CSS style resolution, layout) with [Krilla](https://github.com/LaurenzV/krilla) (PDF generation) through a pagination-aware layout abstraction.\n\n```text\nHTML/CSS input\n  ↓\nBlitz (HTML parse → DOM → style resolution → Taffy layout)\n  ↓\nDOM → Pageable conversion (BlockPageable / ParagraphPageable / ImagePageable)\n  ↓\nPagination (split Pageable tree at page boundaries)\n  ↓\nKrilla rendering (Pageable.draw() per page → PDF Surface)\n  ↓\nPDF bytes\n```\n\n## Project Structure\n\n```text\ncrates/\n├── fulgur/        # Core library (conversion, layout, rendering)\n└── fulgur-cli/    # CLI tool\n```\n\n## Development\n\n```bash\n# Build\ncargo build\n\n# Test\ncargo test\n\n# Run CLI directly\ncargo run -p fulgur-cli -- render -o output.pdf input.html\n```\n\n## Versioning\n\nFulgur follows the [ZeroVer](https://0ver.org) convention while still in\nthe `0.x` line. The number after the leading zero — the **minor** — is\nwhat we treat as a release boundary; the patch field exists only to ship\nhotfixes between minors.\n\n- **Each normal release bumps the minor** (`0.5.x` → `0.6` → `0.7`). We\n  refer to it as \"Fulgur 0.6\" externally; internal artefacts (Cargo.toml,\n  PyPI, RubyGems, npm, git tag) carry the full semver string `0.6.0`\n  because those registries require it.\n- **Patch numbers are reserved for hotfixes** off a previous minor\n  (e.g. `0.6.1` to fix a regression in `0.6.0`). They are not used for\n  routine releases.\n- **`fulgur`, `fulgur-cli`, `fulgur-wasm`, `fulgur-ruby`, `pyfulgur` share\n  the same version**. Independent binding versioning is tracked as future\n  work.\n- **API stability is not guaranteed in `0.x`**. Each minor may introduce\n  breaking changes. The 1.0 line will be cut once the public surface\n  stabilises (criteria TBD).\n\nIf you depend on Fulgur from another Rust crate or a binding, pin the\nexact minor (`fulgur = \"0.6\"` resolves to `\u003e=0.6.0, \u003c0.7.0` under Cargo's\ndefault caret behaviour) until you are ready to absorb a breaking change.\n\n## Release\n\nReleases are automated with [release-plz](https://release-plz.dev): a push to\n`main` opens a Release PR (minor version bump + a PR-based changelog rendered\nfrom `release-notes:*` labels). Approving and merging it publishes to crates.io,\ncreates the `vX.Y.Z` tag, and cascades to the platform binaries, npm, PyPI and\nRubyGems. See [docs/RELEASE_SETUP.md](docs/RELEASE_SETUP.md) for the full flow\nand PyPI / RubyGems Trusted Publisher setup.\n\n## Determinism and fonts\n\nFulgur aims for byte-identical PDF output from identical input. The core pipeline\n(Blitz → Taffy → Parley → Krilla) is deterministic, but there is **one known\nenvironment dependency** you should be aware of:\n\n- [Blitz `blitz-dom` 0.2.4](https://docs.rs/blitz-dom) uses a process-global\n  `fontdb::Database::load_system_fonts()` call for parsing inline `\u003csvg\u003e`\n  elements. Fulgur cannot currently override it, so the font chosen for\n  `\u003ctext\u003e` elements inside SVG — and the default fallback for HTML text when\n  no bundled fonts are supplied — depends on which `.ttf`/`.otf` files are\n  installed on the host. The same HTML can therefore produce *different*\n  PDFs on two machines if their system font sets differ.\n\nTo get reproducible output today, pin the font environment via `fontconfig`:\n\n```bash\n# Point fontconfig at a controlled set of font files.\nexport FONTCONFIG_FILE=\"$PWD/my-fonts.conf\"\nfulgur render -o output.pdf input.html\n```\n\nThe repository ships a pinned Noto Sans bundle under `examples/.fonts/`\ntogether with a matching `examples/.fontconfig/fonts.conf`, which is what\n`mise run update-examples` and the GitHub Actions regen workflows use to\nkeep `examples/*/index.pdf` byte-identical across environments. See\n`examples/.fonts/README.md` for the exact font list and re-fetch\ninstructions. Making this configurable at the library API level (so\n`fulgur::Engine` callers get determinism without touching fontconfig) is\ntracked as a follow-up — once landed, library callers will be able to\nsupply their own font database directly.\n\n## Security\n\nIf you plan to accept untrusted HTML templates or JSON data (e.g. in a\nmulti-tenant SaaS), see the\n[threat model](docs/security/threat-model.md)\n([日本語](docs/security/threat-model.ja.md)) for the full analysis of\nattack vectors and mitigations.\n\n## Contributing\n\nContributions are welcome! Please read the [Contributing Guide](CONTRIBUTING.md)\nbefore opening a pull request. All contributors must sign the\n[Contributor License Agreement](CLA.md) — a bot will guide you through the\none-time sign-off on your first PR.\n\n## License\n\nFulgur is dual-licensed under either of:\n\n- [Apache License, Version 2.0](LICENSE-APACHE)\n- [MIT License](LICENSE-MIT)\n\nat your option.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffulgur-rs%2Ffulgur","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffulgur-rs%2Ffulgur","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffulgur-rs%2Ffulgur/lists"}