かなり前に puppeteer で作った ことはあったんだけど、生成に時間がかかりすぎるので、やっぱ node.js で画像生成なら canvas が王道だろうということで作り直した。
Astro の Non-HTML Page として作った。
Non-HTML Page では Astro.glob が現状使えないので、早速面倒なことになった。
Astro のテストコードに vite の機能 import.meta.glob
で全記事取得してくるコードがあったので、それを参考に全記事の slug を無理やり取ってきた。
素直に Astro.glob が使える日を待ちたい。
export function getStaticPaths() {
// Ref: https://github.com/withastro/astro/blob/c3f411a7f2d77739cc32e7b7fbceb3d02018238d/packages/astro/test/fixtures/static-build/src/pages/posts.json.js
const posts = Object.keys(import.meta.glob("../posts/**/*.md"));
return posts.map((filename) => ({
params: { slug: filename.replace(/^.*\/(.*)\.md$/, "$1") },
}));
}
またこの glob は VS Code で TypeScript のエラーを吐くので、 src/import-meta.d.ts
に以下を追加してエラーを解消している。エラーのままでも動きはする。
// Ref:
// - https://github.com/vitejs/vite/blob/b5c370941bb36bdb420433ca16cea9c2402b9810/packages/vite/types/importMeta.d.ts
// - https://ja.vitejs.dev/guide/env-and-mode.html#typescript-%E7%94%A8%E3%81%AE%E8%87%AA%E5%8B%95%E8%A3%9C%E5%AE%8C
interface GlobOptions {
as?: string;
}
interface ImportMeta {
glob<Module = { [key: string]: any }>(
pattern: string,
options?: GlobOptions
): Record<string, () => Promise<Module>>;
env: {
SSR: boolean;
};
}
canvas の drawText では文字の折返しはサポートしていない。
前回 puppeteer 使ったのは、文字の折返しが楽だろうと思ったからだった。
今回は自前でなんとかする必要がある。
以下のように、テキストと最大横幅を入れると、複数行に折返して返してくれる関数を書いた。
function wrapText(
ctx: CanvasRenderingContext2D,
text: string,
maxWidth: number
): string[] {
return segmenter.segment(text).reduce(
(lines, segment) => {
const { width } = ctx.measureText(
lines[lines.length - 1] + segment.trim()
);
if (width > maxWidth) {
lines.push("");
}
lines[lines.length - 1] += segment;
return lines;
},
[""]
);
}
やってることは単純で、
他、細かいところとして、空白は trim して文字列の長さに含めないようにしている。
この関数をフォントサイズを変更しながら叩き、3 行以下に収まるところでやめ、中央にレンダリングしている。
余談だけど、 npm i tiny-segmenter
で入るものは作者とは別の方が publish したものだったので、作者のサイトから DL して src/lib/tiny_segmenter.js
に起き、 export default TinySegmenter;
を追記した。
https://github.com/macoshita/macoshita.me-astro/blob/1d81716bc541625a7f9135d9d19b856719e187b0/src/lib/tiny_segmenter.js
ちょっとイマイチな改行位置の画像があったりするので、 tiny-segmenter はそのうち変えるかも。
Astro の Non-HTML ページは Non-HTML といえど Rollup される。
Rollup されることでフォントとの相対位置が変わってしまい、 ../fonts/font.ttf
とか書いてもそこに存在してくれるとは限らない。
import でファイルパスを取得してうまく加工するとか、import ?raw
つけてどこかに保存するとか考えたが、今回は SSG で動けばいいので、 process.cwd()
で rollup 後だろうが同じパスを使って動くようにした。
そもそも Non-HTML は Rollup しなくていいんじゃないかと思わなくもない。
// only static build
registerFont(path.resolve(process.cwd(), "fonts/KiwiMaru-Regular.ttf"), {
family: "KiwiMaru",
});
黒字によさげなフォントで書くだけで十分それっぽくなることがわかった()
Astro の Non-HTML での生成はまだイマイチなところがある。 Astro.*
は使えるようにならないとちょっと厳しい。