Commit 5241be8

HPCesia <me@hpcesia.com>
2026-03-12 06:39:00
Add PDF archive render support
1 parent 1d12bb9
Changed files (7)
src/contents/_lib/components/fullwidth.typ
@@ -6,5 +6,7 @@
       class: "fullwidth",
       content,
     )
+  } else {
+    return content
   }
 }
src/contents/_lib/components/marginnote.typ
@@ -6,5 +6,7 @@
       class: "marginnote",
       content,
     )
+  } else {
+    return footnote(numbering: (..i) => "", content)
   }
 }
src/contents/home.typ
@@ -1,8 +1,32 @@
 #import "./_lib/lib.typ": *
 #show: tybios.post-template
 
-#lorem(50)
+Tybios is a Tufted style inspired static blog template
+powered by #link("https://astro.build/")[Astro] and #link("https://typst.app/")[Typst].
 
-#lorem(30)
+= Installation
 
-#lorem(40)
+To get started with Tybios, follow these steps:
+
++ Clone the repository:
+  ```bash
+  git clone https://codeberg.org/HPCesia/Tybios.git
+  ```
++ Navigate to the project directory:
+  ```bash
+  cd Tybios
+  ```
++ Install dependencies:
+  ```bash
+  pnpm install
+  ```
++ Configure your site by editing the `src/config.ts` file
+  to set your site's metadata and preferences.
++ Start the development server:
+  ```bash
+  pnpm dev
+  ```
++ Build for production:
+  ```bash
+  pnpm build
+  ```
src/pages/posts/[slug]/archive.pdf.ts
@@ -0,0 +1,30 @@
+import { getCollection } from "astro:content";
+import type { APIRoute, GetStaticPaths, InferGetStaticPropsType } from "astro";
+import { renderTypstToPdf } from "@/utils/render-typst";
+
+export const getStaticPaths = (async () => {
+  const entries = await getCollection("posts");
+  return entries.map((entry) => ({
+    params: {
+      slug: entry.id,
+    },
+    props: { entry },
+  }));
+}) satisfies GetStaticPaths;
+
+type Props = InferGetStaticPropsType<typeof getStaticPaths>;
+
+export const GET: APIRoute = async ({ props }) => {
+  const entry = (props as Props).entry;
+  const filePath = entry.filePath;
+  if (typeof filePath !== "string") {
+    return new Response("Not found", { status: 404 });
+  }
+  const pdfBuffer = renderTypstToPdf(filePath);
+
+  return new Response(pdfBuffer as Buffer<ArrayBuffer>, {
+    headers: {
+      "Content-Type": "application/pdf",
+    },
+  });
+};
src/pages/posts/[slug].astro → src/pages/posts/[slug]/index.astro
@@ -2,6 +2,7 @@
 import { getCollection, render } from "astro:content";
 import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
 import GlobalLayout from "@/layouts/GlobalLayout.astro";
+import { url } from "@/utils/url";
 
 type Props = InferGetStaticPropsType<typeof getStaticPaths>;
 
@@ -30,4 +31,9 @@ const { Content } = await render(entry);
     <h1>{data.title}</h1>
     <Content />
   </article>
+  <footer class="mt-8 article-anchor">
+    <a href={url(`posts/${entry.id}/archive.pdf`)}>
+      <span>PDF archive of this post</span>
+    </a>
+  </footer>
 </GlobalLayout>
src/pages/feed.atom.ts
@@ -1,11 +1,12 @@
 import { getCollection } from "astro:content";
+import type { APIRoute } from "astro";
 import { Feed } from "feed";
 import { SITE_CONFIG } from "@/config";
 import { rehypeExternalLinks } from "@/plugins/rehype-external-links";
-import { renderTypst } from "@/utils/render-typst";
 import { rehypeExtractImage } from "@/plugins/rehype-extract-image";
+import { renderTypstToHtml } from "@/utils/render-typst";
 
-export async function GET() {
+export const GET: APIRoute = async () => {
   const posts = await getCollection("posts");
   const siteUrl = SITE_CONFIG.website;
 
@@ -35,7 +36,7 @@ export async function GET() {
         id: url,
         link: url,
         description: post.data.desc,
-        content: await renderTypst(post.filePath, [
+        content: await renderTypstToHtml(post.filePath, [
           rehypeExtractImage,
           rehypeExternalLinks,
         ]),
@@ -50,4 +51,4 @@ export async function GET() {
       "Content-Type": "application/xml",
     },
   });
-}
+};
src/utils/render-typst.ts
@@ -3,8 +3,8 @@ import { fileURLToPath } from "node:url";
 import type { RehypePlugins } from "@astrojs/markdown-remark";
 import { NodeCompiler } from "@myriaddreamin/typst-ts-node-compiler";
 import { rehypeIt } from "astro-typst/dist/lib/rehype";
-import type { Root } from "hast";
 import * as cheerio from "cheerio";
+import type { Root } from "hast";
 
 const rootPath = fileURLToPath(root);
 const $typst = NodeCompiler.create({
@@ -12,7 +12,7 @@ const $typst = NodeCompiler.create({
   inputs: { "x-renderer": "tybios" },
 });
 
-export async function renderTypst(
+export async function renderTypstToHtml(
   mainfilePath: string,
   plugins?: RehypePlugins,
 ): Promise<string> {
@@ -33,3 +33,14 @@ export async function renderTypst(
 
   return $("body").html() || "";
 }
+
+export function renderTypstToPdf(mainfilePath: string): Buffer {
+  const docRes = $typst.compile({ mainFilePath: mainfilePath });
+  if (!docRes.result) {
+    docRes.printDiagnostics();
+    throw new Error("Failed to compile Typst document.");
+  }
+  const doc = docRes.result;
+  const pdf = $typst.pdf(doc);
+  return pdf;
+}