{"id":51591638,"url":"https://github.com/atomiechen/astroprint","last_synced_at":"2026-07-11T15:01:59.502Z","repository":{"id":362733108,"uuid":"1240904498","full_name":"atomiechen/astroprint","owner":"atomiechen","description":"Print-ready Markdown documents for Astro, with normal web preview, paged preview, and PDF export.","archived":false,"fork":false,"pushed_at":"2026-06-05T16:32:33.000Z","size":483,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-05T18:35:39.186Z","etag":null,"topics":["astro-integration","citation","markdown","paged-media","pdf","print","withastro"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/astroprint","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/atomiechen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"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":null}},"created_at":"2026-05-16T18:03:17.000Z","updated_at":"2026-06-05T16:32:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/atomiechen/astroprint","commit_stats":null,"previous_names":["atomiechen/astroprint"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/atomiechen/astroprint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomiechen%2Fastroprint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomiechen%2Fastroprint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomiechen%2Fastroprint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomiechen%2Fastroprint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/atomiechen","download_url":"https://codeload.github.com/atomiechen/astroprint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomiechen%2Fastroprint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35365809,"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-11T02:00:05.354Z","response_time":104,"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":["astro-integration","citation","markdown","paged-media","pdf","print","withastro"],"created_at":"2026-07-11T15:01:54.201Z","updated_at":"2026-07-11T15:01:59.492Z","avatar_url":"https://github.com/atomiechen.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# astroprint — print-ready Markdown for Astro\n\n[![NPM](https://img.shields.io/npm/v/astroprint?logo=npm\u0026label=astroprint)](https://www.npmjs.com/package/astroprint)\n[![GitHub](https://img.shields.io/badge/github-gray?logo=github)](https://github.com/atomiechen/astroprint)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/atomiechen/astroprint)\n\n\nPrint-ready Markdown documents for [Astro](https://astro.build/), with normal web preview, Paged.js preview, and PDF export.\n\nUse `astroprint` for CVs, reports, notes, and other Markdown-first documents that should stay editable as Astro pages while still exporting clean PDFs. It uses Astro's content, layout, asset, and dev-server behavior, then adds print-oriented Markdown transforms, optional injected document routes, paged preview, and a PDF CLI.\n\n## Quick Start\n\nAdd the integration to an Astro project:\n\n```bash\nnpm create astro@latest my-docs\ncd my-docs\nnpx astro add astroprint\n```\n\nThis installs `astroprint` and updates `astro.config.mjs` with the default integration setup.\nUse the equivalent `pnpm astro add`, `yarn astro add`, or `bunx astro add` command if your project uses another package manager.\n\n`astroprint` currently targets Node 20+ and Astro 5.\n\nWith no options, the integration only installs Markdown processing plugins for directives, `:logolink`, BibTeX conversion, and HTML comment stripping. `astro add` imports it as `print` by default, so use normal Astro pages and layouts when you want to control routes yourself:\n\n```js title=\"astro.config.mjs\"\nimport { defineConfig } from \"astro/config\";\nimport print from \"astroprint\";\n\nexport default defineConfig({\n  integrations: [print()],\n});\n```\n\nThe integration also excludes astroprint-generated work directories matching `**/.astroprint*/**` from Vite's dev-server watcher.\n\nAdd `injectedRoutes` only when you want `astroprint` to inject normal and optional preview routes for a document source. Each injected route must provide an explicit `route`; astroprint will not guess a public URL for you. Add top-level `pdf` when you want `astroprint pdf` to work without passing `--route`:\n\n```js title=\"astro.config.mjs\"\n// astro.config.mjs\nimport { defineConfig } from \"astro/config\";\nimport print from \"astroprint\";\n\nexport default defineConfig({\n  integrations: [\n    print({\n      injectedRoutes: [\n        {\n          markdown: \"./src/content/cv.md\",\n          route: \"/astroprint\",\n          previewRoute: true,\n          injectDuringBuild: false,\n        },\n      ],\n      pdf: {\n        route: \"/astroprint\",\n        outputDir: \"public\",\n        backend: \"weasyprint\",\n      },\n    }),\n  ],\n});\n```\n\nBy default, configured injected routes are emitted during normal `astro build`, so `/astroprint/` can be part of your production site. Set `injectDuringBuild: false` on an injected route when you only want it during `astro dev` and `astroprint pdf`; the PDF command always enables route injection internally with `ASTROPRINT_RENDER_HTML=true`.\n\nPaged preview routes are opt-in. Omit `previewRoute` or set `previewRoute: false` to inject only the normal route. Set `previewRoute: true` to inject the default preview path (`${route}-preview`, or `/preview` for `/`), or pass a string such as `previewRoute: \"/astroprint-preview\"`.\n\nRoute sources can be:\n\n```js\n[\n  { markdown: \"./src/content/cv.md\", route: \"/cv\" },\n  { collection: \"cv\", entry: \"main\", route: \"/cv\" },\n  { collection: \"cv\", route: \"/cv\", defaultId: \"main\" },\n]\n```\n\nFor collection routes, define an Astro content collection:\n\n```ts\n// src/content.config.ts\nimport { defineCollection, z } from \"astro:content\";\nimport { glob } from \"astro/loaders\";\n\nconst cv = defineCollection({\n  loader: glob({ pattern: \"**/*.md\", base: \"src/content/cv\" }),\n  schema: z.object({\n    title: z.string().optional(),\n    secondaryTitle: z.string().optional(),\n  }),\n});\n\nexport const collections = { cv };\n```\n\nCollection-backed generated routes pass the raw collection `entry` to the configured layout. Single Markdown routes pass `frontmatter`. The built-in academic layout maps `title`/`secondaryTitle`; custom layouts can use any frontmatter shape.\n\nExample Markdown:\n\n```md title=\"src/content/cv/main.md\"\n---\ntitle: Ada Lovelace\nsecondaryTitle: Computing Notes\n---\n\n:::::ul{.two-col}\n\n::::entry\n:::col\n**Example University**\n:::\n\n:::col\n2026\n:::\n::::\n\n:::::\n```\n\nFor multi-document collection routes, the filename becomes the document id. `defaultId: \"main\"` renders `main.md` at `/cv/`; other ids render at paths such as `/cv/example/`.\n\nRun Astro for development:\n\n```bash\nnpx astro dev\n```\n\nThen open:\n\n- `/astroprint/` for the normal document view\n- `/astroprint-preview/` for Paged.js pagination preview, if `previewRoute` is enabled\n\nGenerate the final PDF:\n\n```bash\nnpx astroprint pdf\n```\n\nSupported PDF backends:\n\n| Backend | Install |\n| --- | --- |\n| `weasyprint` (default) | Install the `weasyprint` command for your platform. See the [WeasyPrint installation guide](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation). |\n| `playwright` | Install Playwright and a browser for your environment. See the [Playwright browser installation guide](https://playwright.dev/docs/browsers). |\n\nFor `weasyprint`, set `WEASYPRINT_BIN=/path/to/weasyprint` when the executable is not named `weasyprint` or is not on `PATH`.\n\nFor manually routed pages, pass the route explicitly:\n\n```bash\nnpx astroprint pdf --route /cv-notes/\n```\n\n## Markdown Directives\n\n`astroprint` installs `remark-directive` and maps lightweight directives to semantic HTML classes:\n\n```md\n:::::ul{.two-col}\n\n::::entry\n:::col\n**Tsinghua University**\n\nPh.D. Candidate\n:::\n\n:::col\nBeijing, China\n\n2020-2026\n:::\n::::\n\n:::::\n```\n\nThe built-in academic CV theme styles these classes through `astroprint/styles/academic-cv.css`, imported by the academic layouts. Treat directives and CSS as a pair: directives give Markdown a small semantic vocabulary, and the stylesheet defines how that vocabulary renders.\n\nUse directive attributes for CSS classes, for example `:::::ul{.two-col}`. The bracket form is directive label/content syntax, so `:::ul[two-col]` is not recommended for classes.\n\nYou can add your own vocabulary through the integration `directives` option:\n\n```js title=\"astro.config.mjs\"\nimport { defineConfig } from \"astro/config\";\nimport print from \"astroprint\";\n\nexport default defineConfig({\n  integrations: [\n    print({\n      directives: {\n        callout: {\n          tag: \"aside\",\n          className: \"my-callout\",\n        },\n        timeline: {\n          tag: \"ol\",\n          className: \"my-timeline\",\n        },\n      },\n    }),\n  ],\n});\n```\n\nThen provide matching CSS from your layout or theme stylesheet.\n\nHTML comments in Markdown are stripped by default. Set `stripHtmlComments: false` in the integration options when you need comments to remain in the rendered HTML.\n\nBibTeX code blocks with `style=acm`, `style=apa`, or `style=ieee` are converted to publication HTML at build time:\n\n````md\n```bibtex style=acm\n@inproceedings{example,\n  author = {Ada Lovelace and Grace Hopper},\n  title = {Computing Notes},\n  year = {2026},\n  booktitle = {Proceedings of Example Conference}\n}\n```\n````\n\nSet `bibtex: false` to leave BibTeX code blocks untouched, or pass `bibtex: { style: \"apa\", highlightedAuthors: [\"Ada Lovelace\"] }` to set global defaults. Local code-block meta wins over global options, so `style=ieee highlight=\"Ada Lovelace\"` can configure one BibTeX block. Style names are case-insensitive. `acm` uses a built-in ACM DL-like formatter; `apa` and `ieee` use bundled CSL styles from the Citation Style Language styles repository. Pass `lang` globally or in code-block meta, for example `style=apa lang=en-US`, when a CSL-backed style should use a specific locale.\n\n## Custom Layouts\n\n`astroprint` separates document structure from route shell. Use the smallest component that matches the surface you are building:\n\n```mermaid\nflowchart TD\n  BaseCSS[styles/base.css]\n  AcademicCSS[styles/academic-cv.css]\n\n  Document[Document.astro]\n  AcademicDocument[AcademicDocument.astro]\n  PrintPreview[PrintPreview.astro]\n  PreviewShell[PreviewShell.astro]\n\n  BaseLayout[layouts/BaseLayout.astro]\n  AcademicLayout[layouts/AcademicLayout.astro]\n\n  BaseCSS --\u003e Document\n  Document --\u003e AcademicDocument\n  AcademicCSS --\u003e AcademicDocument\n  PrintPreview --\u003e PreviewShell\n  AcademicDocument --\u003e AcademicLayout\n  PreviewShell --\u003e AcademicLayout\n  BaseLayout --\u003e AcademicLayout\n```\n\n- `Document.astro` provides the default document root and baseline page styles.\n- `AcademicDocument.astro` provides `Document` plus the built-in academic theme and title block.\n- `PrintPreview.astro` provides the document-agnostic Paged.js wrapper.\n- `PreviewShell.astro` provides the normal/preview navigation, print button, scroll restoration, and optional `PrintPreview`.\n- `AcademicLayout.astro` provides the built-in academic document surface and uses `PreviewShell` when generated routes pass `withPreviewShell={true}`.\n\nWhen a custom theme wraps `PreviewShell.astro`, it mainly needs to map entry/frontmatter data into markup and import its own stylesheet:\n\n```astro title=\"src/layouts/MyThemedDocumentLayout.astro\"\n---\nimport type { ComponentProps } from \"astro/types\";\nimport Document from \"astroprint/components/Document.astro\";\nimport PreviewShell from \"astroprint/components/PreviewShell.astro\";\nimport BaseLayout from \"./BaseLayout.astro\";\nimport \"./my-document.css\";\n\ntype Props = ComponentProps\u003ctypeof PreviewShell\u003e \u0026 {\n  frontmatter?: Record\u003cstring, unknown\u003e;\n  entry?: {\n    id?: string;\n    data?: Record\u003cstring, unknown\u003e;\n  };\n};\n\nconst { entry, frontmatter } = Astro.props;\nconst title =\n  (typeof entry?.data?.title === \"string\" ? entry.data.title : undefined) ??\n  (typeof frontmatter?.title === \"string\" ? frontmatter.title : undefined) ??\n  entry?.id;\nconst secondaryTitle =\n  (typeof entry?.data?.secondaryTitle === \"string\" ? entry.data.secondaryTitle : undefined) ??\n  (typeof frontmatter?.secondaryTitle === \"string\" ? frontmatter.secondaryTitle : undefined);\n---\n\n\u003cBaseLayout pageTitle={title}\u003e\n  \u003cPreviewShell {...Astro.props}\u003e\n    \u003cDocument\u003e\n      \u003ch1 class=\"my-title\"\u003e\n        \u003cspan\u003e{title}\u003c/span\u003e\n        {secondaryTitle \u0026\u0026 \u003cspan\u003e{secondaryTitle}\u003c/span\u003e}\n      \u003c/h1\u003e\n      \u003cslot /\u003e\n    \u003c/Document\u003e\n  \u003c/PreviewShell\u003e\n\u003c/BaseLayout\u003e\n```\n\nThen point the generated route at that layout:\n\n```js title=\"astro.config.mjs\"\nimport { defineConfig } from \"astro/config\";\nimport print from \"astroprint\";\n\nexport default defineConfig({\n  integrations: [\n    print({\n      injectedRoutes: [\n        {\n          collection: \"cv\",\n          layout: \"./src/layouts/MyThemedDocumentLayout.astro\",\n          route: \"/astroprint\",\n          previewRoute: true,\n        },\n      ],\n    }),\n  ],\n});\n```\n\nRelative `layout` paths are resolved from your Astro project root. Package specifiers and aliases, such as `astroprint/layouts/AcademicLayout.astro` or `@/layouts/MyDocumentLayout.astro`, are passed through to Astro/Vite. The generated route passes rendered Markdown as the slot, plus route props such as `withPreviewShell`, `normalHref`, optional `previewHref`, `printPreview`, `entry`, and `documentConfig`.\n\nFor a completely custom template, create your own layout and stylesheet, then use the directive classes generated by `astroprint` (`.astroprint-entry`, `.two-col`, and so on), or extend the directive mapping with the integration `directives` option.\n\nFor standalone Markdown pages that should use the built-in academic document surface, set the page frontmatter layout. The academic layout maps `title` and `secondaryTitle` from Markdown frontmatter. It renders the plain document by default; add `withPreviewShell: true` when the page should also show the built-in navigation and print button. Generated routes pass `withPreviewShell={true}` automatically.\n\n```md title=\"src/pages/cv-notes.md\"\n---\nlayout: astroprint/layouts/AcademicLayout.astro\ntitle: CV Notes\nsecondaryTitle: Draft\nwithPreviewShell: true\n---\n\n:::::ul{.two-col}\n\n::::entry\n:::col\n**Example**\n:::\n\n:::col\n2026\n:::\n::::\n\n:::::\n```\n\n## Commands\n\n```bash\nastroprint dev          # thin wrapper around astro dev\nastroprint build        # thin wrapper around astro build\nastroprint pdf          # Generate from the configured pdf.route\nastroprint pdf mydoc    # Generate from pdf.route plus /mydoc/\nastroprint pdf a/b/c    # Generate from pdf.route plus /a/b/c/\nastroprint pdf --route /cv-notes/  # Generate from a regular Astro route\nastroprint pdf --route /cv-notes/ --output-dir public\n```\n\nThe PDF command sets `ASTROPRINT_RENDER_HTML=true` so injected normal routes are generated for export, regardless of each injected route's `injectDuringBuild` setting. `previewRoute` remains opt-in. Without top-level `pdf`, use `--route` to print an existing Astro page; `astroprint` will not guess a default PDF route.\n\n`astroprint pdf` builds temporary HTML into `.astroprint/` before rendering the PDF.\n\n`npx astroprint ...` runs the `astroprint` CLI, but the Playwright backend imports the `playwright` package from the project at runtime. Install Playwright in the project when using `backend: \"playwright\"`; `npx playwright ...` is useful for Playwright's own install/setup commands, but it does not replace the runtime dependency.\n\nEach injected route config must include `route`; astroprint does not inject a default route path. `pdf.route` and `--route` accept either `/cv-notes` or `/cv-notes/`; `astroprint` resolves both against Astro's static output and uses a trailing slash internally for directory routes so relative assets keep the same base URL. `pdf.document` and the optional `astroprint pdf [document]` positional argument append a document path to the selected route, so `pdf.route: \"/cv\"` plus `astroprint pdf mydoc` prints `/cv/mydoc/`. The positional argument overrides `pdf.document`; omit it to print the base route as before.\n\n`pdf.output`, `pdf.outputDir`, `--output`, and `--output-dir` are normal filesystem paths, not Astro routes. `outputDir` is the base directory, and `output` is resolved inside it. Absolute `output` paths are used as-is. Relative paths are resolved from the project root/current working directory. For example, `outputDir: \"public\"` plus `/cv-notes` writes `public/cv-notes.pdf`; `outputDir: \"public\"` plus `output: \"CV.pdf\"` writes `public/CV.pdf`.\n\nCLI options override the matching config fields, so `--backend` overrides `pdf.backend`, `--output-dir` overrides `pdf.outputDir`, and `--output` overrides `pdf.output`. If no output is specified, the filename is derived from the route: `/` becomes `index.pdf`, `/cv-notes` becomes `cv-notes.pdf`, and `/nested/report` becomes `report.pdf`. If `--port` is omitted, `astroprint` asks the OS for an available temporary port; if `--port` is provided, that exact port is used.\n\n## Maintainers\n\nMaintainer notes, including the vendored Paged.js refresh workflow, live in [`AGENTS.md`](https://github.com/atomiechen/astroprint/blob/main/AGENTS.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatomiechen%2Fastroprint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatomiechen%2Fastroprint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatomiechen%2Fastroprint/lists"}