HTML/CSS を SVG に変換する Vercel 製のパッケージ「satori」を試してみる
Next.js では Vercel's Edge Functions を使用して動的に OGP 画像を生成できる @vercel/og
という Vercel 製のパッケージが公開されています。
この @vercel/og
では内部的に satori
という HTML/CSS を SVG に変換するパッケージが使用されています。
面白そうだったので簡単に試してみたメモです。
こういう SVG も簡単に作れます。
バッジっぽいやつ
検証環境
- Node.js v18.12.1
- satori v0.0.44
インストール
$ npm i satori
# or
$ yarn add satori
使い方
基本
satori()
に SVG に変換したい要素とオプションを渡すだけです。
SVG に変換したい要素は JSX 記法を使うことができるので、 React アプリケーションであれば導入しやすいのではないかと思います。
import fs from "fs";
import satori from "satori";
(async () => {
// フォントを読み込む
// ファイルから読み込む例 ( Node.js のみ )
const fontData = fs.readFileSync("/path/to/Roboto-Regular.ttf");
// URL から読み込みたいときは `fetch` 等を使う
const fontData = await fetch("https://example.com/Roboto-Regular.ttf").then((resp) => resp.arrayBuffer());
// `satori()` で SVG を生成する
const svg = await satori(
// 第一引数に SVG に変換したい要素を渡す
<div style={{ color: "red" }}>hello, world</div>,
// 第二引数に幅、高さ、フォントなどのオプションを指定する
{
width: 600, // 幅
height: 400, // 高さ
fonts: [ // フォント
{
name: "Roboto",
data: fontData,
weight: 400,
style: "normal",
},
],
}
);
console.log(svg);
})();
<svg width="600" height="400" viewBox="0 0 600 400" xmlns="http://www.w3.org/2000/svg"><path fill="red" d="M2.5 3.5L2.5 8.1Q3.5 6.9 5.0 6.9L5.0 6.9Q7.7 6.9 7.7 9.9L7.7 9.9L7.7 15.5L6.3 15.5L6.3 9.9Q6.3 9.0 5.9 8.5Q5.5 8.1 4.6 8.1L4.6 8.1Q3.9 8.1 3.4 8.5Q2.8 8.9 2.5 9.5L2.5 9.5L2.5 15.5L1.1 15.5L1.1 3.5L2.5 3.5ZM13.4 15.6L13.4 15.6Q11.7 15.6 10.6 14.5Q9.5 13.4 9.5 11.5L9.5 11.5L9.5 11.2Q9.5 10.0 10.0 9.0Q10.5 8.0 11.4 7.4Q12.2 6.9 13.2 6.9L13.2 6.9Q14.9 6.9 15.8 8.0Q16.7 9.0 16.7 11.1L16.7 11.1L16.7 11.7L11.0 11.7Q11.0 12.9 11.7 13.7Q12.4 14.5 13.5 14.5L13.5 14.5Q14.3 14.5 14.8 14.1Q15.3 13.8 15.7 13.3L15.7 13.3L16.6 14.0Q15.5 15.6 13.4 15.6ZM13.2 8.1L13.2 8.1Q12.4 8.1 11.8 8.7Q11.2 9.3 11.0 10.5L11.0 10.5L15.3 10.5L15.3 10.4Q15.2 9.3 14.7 8.7Q14.1 8.1 13.2 8.1ZM20.0 3.5L20.0 15.5L18.5 15.5L18.5 3.5L20.0 3.5ZM23.8 3.5L23.8 15.5L22.4 15.5L22.4 3.5L23.8 3.5ZM25.8 11.3L25.8 11.2Q25.8 9.9 26.3 8.9Q26.7 8.0 27.6 7.4Q28.5 6.9 29.6 6.9L29.6 6.9Q31.3 6.9 32.4 8.1Q33.5 9.3 33.5 11.2L33.5 11.2L33.5 11.4Q33.5 12.6 33.0 13.6Q32.5 14.5 31.6 15.1Q30.8 15.6 29.6 15.6L29.6 15.6Q27.9 15.6 26.8 14.4Q25.8 13.2 25.8 11.3L25.8 11.3ZM27.2 11.4L27.2 11.4Q27.2 12.8 27.9 13.6Q28.5 14.5 29.6 14.5L29.6 14.5Q30.7 14.5 31.4 13.6Q32.0 12.7 32.0 11.2L32.0 11.2Q32.0 9.8 31.4 8.9Q30.7 8.1 29.6 8.1L29.6 8.1Q28.5 8.1 27.9 8.9Q27.2 9.8 27.2 11.4ZM35.2 17.7L35.2 17.7L34.4 17.2Q35.1 16.2 35.2 15.1L35.2 15.1L35.2 13.8L36.6 13.8L36.6 14.9Q36.6 15.7 36.2 16.5Q35.8 17.3 35.2 17.7Z M47.9 7.0L49.9 13.5L51.5 7.0L52.9 7.0L50.5 15.5L49.3 15.5L47.2 9.1L45.2 15.5L44.1 15.5L41.6 7.0L43.1 7.0L44.7 13.4L46.7 7.0L47.9 7.0ZM54.0 11.3L54.0 11.2Q54.0 9.9 54.5 8.9Q55.0 8.0 55.9 7.4Q56.7 6.9 57.9 6.9L57.9 6.9Q59.6 6.9 60.6 8.1Q61.7 9.3 61.7 11.2L61.7 11.2L61.7 11.4Q61.7 12.6 61.2 13.6Q60.8 14.5 59.9 15.1Q59.0 15.6 57.9 15.6L57.9 15.6Q56.1 15.6 55.1 14.4Q54.0 13.2 54.0 11.3L54.0 11.3ZM55.5 11.4L55.5 11.4Q55.5 12.8 56.1 13.6Q56.8 14.5 57.9 14.5L57.9 14.5Q59.0 14.5 59.6 13.6Q60.3 12.7 60.3 11.2L60.3 11.2Q60.3 9.8 59.6 8.9Q58.9 8.1 57.9 8.1L57.9 8.1Q56.8 8.1 56.1 8.9Q55.5 9.8 55.5 11.4ZM67.6 7.0L67.6 8.3Q67.3 8.3 66.9 8.3L66.9 8.3Q65.5 8.3 65.0 9.5L65.0 9.5L65.0 15.5L63.5 15.5L63.5 7.0L64.9 7.0L65.0 8.0Q65.7 6.9 67.0 6.9L67.0 6.9Q67.4 6.9 67.6 7.0L67.6 7.0ZM70.5 3.5L70.5 15.5L69.1 15.5L69.1 3.5L70.5 3.5ZM72.5 11.3L72.5 11.2Q72.5 9.2 73.4 8.1Q74.3 6.9 75.8 6.9L75.8 6.9Q77.3 6.9 78.2 7.9L78.2 7.9L78.2 3.5L79.6 3.5L79.6 15.5L78.3 15.5L78.2 14.6Q77.3 15.6 75.8 15.6L75.8 15.6Q74.3 15.6 73.4 14.4Q72.5 13.2 72.5 11.3L72.5 11.3ZM73.9 11.4L73.9 11.4Q73.9 12.8 74.5 13.6Q75.1 14.4 76.1 14.4L76.1 14.4Q77.5 14.4 78.2 13.2L78.2 13.2L78.2 9.3Q77.5 8.1 76.2 8.1L76.2 8.1Q75.1 8.1 74.5 8.9Q73.9 9.7 73.9 11.4Z "/></svg>
出力された SVG 画像
JSX 記法を使えない場合は代わりに type
と props
を持つオブジェクトを渡すこともできます。
import fs from "fs";
import satori from "satori";
(async () => {
// フォントを読み込む
// ファイルから読み込む例 ( Node.js のみ )
const fontData = fs.readFileSync("/path/to/Roboto-Regular.ttf");
// URL から読み込みたいときは `fetch` 等を使う
const fontData = await fetch("https://example.com/Roboto-Regular.ttf").then((resp) => resp.arrayBuffer());
// `satori()` で SVG を生成する
const svg = await satori(
// 第一引数に SVG に変換したい要素を渡す
- <div style={{ color: "red" }}>hello, world</div>,
+ {
+ type: "div",
+ props: {
+ children: "hello, world",
+ style: { color: "red" },
+ },
+ },
// 第二引数に幅、高さ、フォントなどのオプションを指定する
{
width: 600, // 幅
height: 400, // 高さ
fonts: [ // フォント
{
name: "Roboto",
data: fontData,
weight: 400,
style: "normal",
},
],
}
);
console.log(svg);
})();
複数のフォントを使用する
オプションの fonts
にはフォントを複数渡すことができます。
フォントを複数渡すことで文字の太さを使い分けたり複数のフォントを使用することができます。
フォントのフォーマットは現時点で TTF
, OTF
, WOFF
の 3 種類に対応しており、 WOFF2
には対応していません。
import fs from "fs";
import satori from "satori";
(async () => {
// フォントを複数読み込む
const fontRegularData = fs.readFileSync("/path/to/Roboto-Regular.ttf");
const fontBoldData = fs.readFileSync("/path/to/Roboto-Bold.ttf");
const svg = await satori(
<div style={{ color: "red", display: "flex", flexDirection: "column" }}>
<div>Regular</div>
<div style={{ fontWeight: "bold" }}>Bold</div>
</div>,
{
width: 600,
height: 400,
// フォントを複数指定する
fonts: [
{
name: "Roboto",
data: fontRegularData,
weight: 400, // font-weight が 400 (normal) のときに使用される
style: "normal",
},
{
name: "Roboto",
data: fontBoldData,
weight: 700, // font-weight が 700 (bold) のときに使用される
style: "normal",
},
],
}
);
console.log(svg);
})();
<svg width="600" height="400" viewBox="0 0 600 400" xmlns="http://www.w3.org/2000/svg"><path fill="red" d="M8.0 15.5L5.5 10.9L2.8 10.9L2.8 15.5L1.3 15.5L1.3 4.1L5.1 4.1Q7 4.1 8.0 5.0Q9.1 5.9 9.1 7.5L9.1 7.5Q9.1 8.6 8.5 9.4Q7.9 10.2 6.9 10.6L6.9 10.6L9.6 15.4L9.6 15.5L8.0 15.5ZM2.8 5.3L2.8 9.7L5.1 9.7Q6.2 9.7 6.9 9.1Q7.6 8.5 7.6 7.5L7.6 7.5Q7.6 6.5 6.9 5.9Q6.3 5.4 5.1 5.3L5.1 5.3L2.8 5.3ZM14.5 15.6L14.5 15.6Q12.7 15.6 11.7 14.5Q10.6 13.4 10.6 11.5L10.6 11.5L10.6 11.2Q10.6 10.0 11.1 9.0Q11.5 8.0 12.4 7.4Q13.3 6.9 14.3 6.9L14.3 6.9Q15.9 6.9 16.8 8.0Q17.8 9.0 17.8 11.1L17.8 11.1L17.8 11.7L12.0 11.7Q12.1 12.9 12.8 13.7Q13.5 14.5 14.5 14.5L14.5 14.5Q15.3 14.5 15.8 14.1Q16.4 13.8 16.8 13.3L16.8 13.3L17.6 14.0Q16.6 15.6 14.5 15.6ZM14.3 8.1L14.3 8.1Q13.4 8.1 12.8 8.7Q12.2 9.3 12.1 10.5L12.1 10.5L16.3 10.5L16.3 10.4Q16.2 9.3 15.7 8.7Q15.2 8.1 14.3 8.1ZM19.1 11.2L19.1 11.2Q19.1 9.2 20.0 8.0Q20.9 6.9 22.4 6.9L22.4 6.9Q24.0 6.9 24.8 8.0L24.8 8.0L24.9 7.0L26.2 7.0L26.2 15.3Q26.2 16.9 25.2 17.9Q24.3 18.8 22.6 18.8L22.6 18.8Q21.7 18.8 20.8 18.4Q20.0 18.0 19.5 17.4L19.5 17.4L20.3 16.5Q21.2 17.6 22.5 17.6L22.5 17.6Q23.6 17.6 24.2 17.0Q24.8 16.4 24.8 15.4L24.8 15.4L24.8 14.6Q23.9 15.6 22.4 15.6L22.4 15.6Q20.9 15.6 20.0 14.4Q19.1 13.2 19.1 11.2ZM20.5 11.4L20.5 11.4Q20.5 12.8 21.1 13.6Q21.7 14.4 22.8 14.4L22.8 14.4Q24.1 14.4 24.8 13.2L24.8 13.2L24.8 9.3Q24.1 8.1 22.8 8.1L22.8 8.1Q21.7 8.1 21.1 8.9Q20.5 9.7 20.5 11.4ZM33.6 15.5L33.6 14.6Q32.8 15.6 31.1 15.6L31.1 15.6Q29.8 15.6 29.1 14.9Q28.4 14.1 28.4 12.5L28.4 12.5L28.4 7.0L29.8 7.0L29.8 12.5Q29.8 14.4 31.4 14.4L31.4 14.4Q33.0 14.4 33.6 13.2L33.6 13.2L33.6 7.0L35.0 7.0L35.0 15.5L33.6 15.5ZM38.8 3.5L38.8 15.5L37.3 15.5L37.3 3.5L38.8 3.5ZM47.8 15.5L46.3 15.5Q46.2 15.2 46.1 14.6L46.1 14.6Q45.1 15.6 43.7 15.6L43.7 15.6Q42.5 15.6 41.7 14.9Q40.9 14.2 40.9 13.1L40.9 13.1Q40.9 11.8 41.9 11.1Q42.9 10.4 44.7 10.4L44.7 10.4L46.1 10.4L46.1 9.7Q46.1 8.9 45.6 8.5Q45.2 8.0 44.3 8.0L44.3 8.0Q43.5 8.0 43.0 8.4Q42.5 8.8 42.5 9.4L42.5 9.4L41.0 9.4Q41.0 8.7 41.5 8.2Q41.9 7.6 42.7 7.2Q43.5 6.9 44.4 6.9L44.4 6.9Q45.9 6.9 46.7 7.6Q47.5 8.3 47.5 9.6L47.5 9.6L47.5 13.5Q47.5 14.7 47.8 15.4L47.8 15.4L47.8 15.5ZM43.9 14.4L43.9 14.4Q44.6 14.4 45.2 14.0Q45.8 13.7 46.1 13.1L46.1 13.1L46.1 11.4L45.0 11.4Q42.3 11.4 42.3 12.9L42.3 12.9Q42.3 13.6 42.8 14.0Q43.2 14.4 43.9 14.4ZM53.9 7.0L53.9 8.3Q53.6 8.3 53.2 8.3L53.2 8.3Q51.8 8.3 51.3 9.5L51.3 9.5L51.3 15.5L49.8 15.5L49.8 7.0L51.2 7.0L51.2 8.0Q51.9 6.9 53.3 6.9L53.3 6.9Q53.7 6.9 53.9 7.0L53.9 7.0Z "/><path fill="red" d="M5.4 34.5L1.0 34.5L1.0 23.1L5 23.1Q7.1 23.1 8.1 23.9Q9.2 24.7 9.2 26.2L9.2 26.2Q9.2 27.1 8.8 27.7Q8.4 28.3 7.6 28.6L7.6 28.6Q8.5 28.9 9.0 29.5Q9.5 30.2 9.5 31.1L9.5 31.1Q9.5 32.8 8.4 33.6Q7.4 34.5 5.4 34.5L5.4 34.5ZM5.5 29.5L3.4 29.5L3.4 32.6L5.4 32.6Q6.2 32.6 6.7 32.2Q7.1 31.8 7.1 31.1L7.1 31.1Q7.1 29.6 5.5 29.5L5.5 29.5ZM3.4 25.0L3.4 27.9L5.1 27.9Q6.9 27.8 6.9 26.5L6.9 26.5Q6.9 25.7 6.4 25.3Q6.0 25.0 5 25.0L5 25.0L3.4 25.0ZM10.7 30.3L10.7 30.2Q10.7 28.9 11.2 27.9Q11.7 27.0 12.6 26.4Q13.5 25.9 14.7 25.9L14.7 25.9Q16.4 25.9 17.5 26.9Q18.6 28.0 18.7 29.8L18.7 29.8L18.7 30.3Q18.7 32.3 17.6 33.5Q16.6 34.6 14.7 34.6Q12.9 34.6 11.8 33.5Q10.7 32.3 10.7 30.3L10.7 30.3ZM13.0 30.3L13.0 30.3Q13.0 31.5 13.4 32.2Q13.9 32.8 14.7 32.8L14.7 32.8Q15.6 32.8 16.0 32.2Q16.5 31.6 16.5 30.2L16.5 30.2Q16.5 29.0 16.0 28.4Q15.6 27.7 14.7 27.7L14.7 27.7Q13.9 27.7 13.4 28.3Q13.0 29.0 13.0 30.3ZM22.5 22.5L22.5 34.5L20.2 34.5L20.2 22.5L22.5 22.5ZM24.0 30.2L24.0 30.2Q24.0 28.2 24.9 27.0Q25.8 25.9 27.3 25.9L27.3 25.9Q28.6 25.9 29.4 26.8L29.4 26.8L29.4 22.5L31.6 22.5L31.6 34.5L29.6 34.5L29.5 33.6Q28.6 34.6 27.3 34.6L27.3 34.6Q25.8 34.6 24.9 33.5Q24.0 32.3 24.0 30.2ZM26.3 30.4L26.3 30.4Q26.3 31.5 26.7 32.2Q27.1 32.8 27.9 32.8L27.9 32.8Q28.9 32.8 29.4 31.9L29.4 31.9L29.4 28.6Q28.9 27.7 27.9 27.7L27.9 27.7Q26.3 27.7 26.3 30.4Z "/></svg>
出力された SVG 画像
画像の埋め込み
<img />
要素を使って画像を埋め込むこともできます。
src
が URL の場合、画像は SVG 生成時に取得されて DataURL に変換されます。
そのため、もしも使用する画像が決まっているのであれば src
に直接 DataURL を設定した方が余計な I/O が発生しません。
また、 width
と height
は必須ではありませんが設定することが推奨されています。
const svg = await satori(
<img src="https://picsum.photos/200/300" width={200} height={300} />,
// ...省略
);
対応している HTML/CSS
当たり前ですが、全ての HTML/CSS に完全に対応しているわけではありません ( とはいえかなり広い範囲をカバーしていますが ) 。
対応している HTML/CSS につきましては下記をご参照ください。
バッジっぽいの作ってみた
よくあるやつ雑に作ってみました。
import fs from "fs";
import satori from "satori";
(async () => {
const fontData = fs.readFileSync("/path/to/Roboto-Regular.ttf");
const svg = await satori(
<div
style={{
borderRadius: "4px",
color: "#fff",
display: "flex",
overflow: "hidden",
}}
>
<div style={{ backgroundColor: "#000", padding: "4px 8px" }}>label</div>
<div style={{ backgroundColor: "#6fc93f", padding: "4px 8px" }}>message</div>
</div>,
{
height: 27,
fonts: [
{
name: "Roboto",
data: fontData,
weight: 400,
style: "normal",
},
],
}
);
console.log(svg);
})();
<svg width="131" height="27" viewBox="0 0 131 27" xmlns="http://www.w3.org/2000/svg"><clipPath id="satori_cp-id"><path x="0" y="0" width="131" height="27" d="M4,0 h123 a4,4 0 0 1 4,4 v19 a4,4 0 0 1 -4,4 h-123 a4,4 0 0 1 -4,-4 v-19 a4,4 0 0 1 4,-4"/></clipPath><mask id="satori_om-id"><rect x="0" y="0" width="131" height="27" fill="#fff"/></mask><rect x="0" y="0" width="50" height="27" fill="#000" clip-path="url(#satori_cp-id)" mask="url(#satori_om-id)"/><path fill="#fff" d="M10.7 7.5L10.7 19.5L9.2 19.5L9.2 7.5L10.7 7.5ZM19.7 19.5L18.2 19.5Q18.1 19.2 18.0 18.6L18.0 18.6Q17.0 19.6 15.6 19.6L15.6 19.6Q14.3 19.6 13.5 18.9Q12.7 18.2 12.7 17.1L12.7 17.1Q12.7 15.8 13.7 15.1Q14.7 14.4 16.6 14.4L16.6 14.4L18.0 14.4L18.0 13.7Q18.0 12.9 17.5 12.5Q17.1 12.0 16.2 12.0L16.2 12.0Q15.4 12.0 14.9 12.4Q14.4 12.8 14.4 13.4L14.4 13.4L12.9 13.4Q12.9 12.7 13.4 12.2Q13.8 11.6 14.6 11.2Q15.3 10.9 16.3 10.9L16.3 10.9Q17.7 10.9 18.6 11.6Q19.4 12.3 19.4 13.6L19.4 13.6L19.4 17.5Q19.4 18.7 19.7 19.4L19.7 19.4L19.7 19.5ZM15.8 18.4L15.8 18.4Q16.5 18.4 17.1 18.0Q17.7 17.7 18.0 17.1L18.0 17.1L18.0 15.4L16.8 15.4Q14.2 15.4 14.2 16.9L14.2 16.9Q14.2 17.6 14.6 18.0Q15.1 18.4 15.8 18.4ZM28.8 15.2L28.8 15.4Q28.8 17.3 27.9 18.5Q27.1 19.6 25.6 19.6L25.6 19.6Q24.0 19.6 23.1 18.5L23.1 18.5L23.0 19.5L21.7 19.5L21.7 7.5L23.1 7.5L23.1 12.0Q24 10.9 25.5 10.9Q27.1 10.9 28.0 12.0Q28.8 13.2 28.8 15.2L28.8 15.2ZM27.4 15.2L27.4 15.2Q27.4 13.7 26.8 12.9Q26.3 12.1 25.2 12.1L25.2 12.1Q23.8 12.1 23.1 13.4L23.1 13.4L23.1 17.1Q23.8 18.4 25.2 18.4L25.2 18.4Q26.2 18.4 26.8 17.6Q27.4 16.8 27.4 15.2ZM34.2 19.6L34.2 19.6Q32.4 19.6 31.4 18.5Q30.3 17.4 30.3 15.5L30.3 15.5L30.3 15.2Q30.3 14.0 30.8 13.0Q31.3 12.0 32.1 11.4Q33.0 10.9 34.0 10.9L34.0 10.9Q35.6 10.9 36.5 12.0Q37.5 13.0 37.5 15.1L37.5 15.1L37.5 15.7L31.7 15.7Q31.8 16.9 32.5 17.7Q33.2 18.5 34.2 18.5L34.2 18.5Q35.0 18.5 35.5 18.1Q36.1 17.8 36.5 17.3L36.5 17.3L37.4 18.0Q36.3 19.6 34.2 19.6ZM34.0 12.1L34.0 12.1Q33.1 12.1 32.5 12.7Q31.9 13.3 31.8 14.5L31.8 14.5L36.0 14.5L36.0 14.4Q36.0 13.3 35.4 12.7Q34.9 12.1 34.0 12.1ZM40.7 7.5L40.7 19.5L39.3 19.5L39.3 7.5L40.7 7.5Z " clip-path="url(#satori_cp-id)" mask="url(#satori_om-id)"/><rect x="50" y="0" width="81" height="27" fill="#6fc93f" clip-path="url(#satori_cp-id)" mask="url(#satori_om-id)"/><path fill="#fff" d="M59.1 11.0L60.5 11.0L60.5 12.0Q61.4 10.9 63 10.9L63 10.9Q64.8 10.9 65.4 12.2L65.4 12.2Q65.8 11.6 66.5 11.2Q67.2 10.9 68.1 10.9L68.1 10.9Q70.9 10.9 70.9 13.8L70.9 13.8L70.9 19.5L69.5 19.5L69.5 13.9Q69.5 13.0 69.1 12.5Q68.7 12.1 67.7 12.1L67.7 12.1Q66.9 12.1 66.4 12.6Q65.8 13.1 65.7 13.9L65.7 13.9L65.7 19.5L64.3 19.5L64.3 13.9Q64.3 12.1 62.5 12.1L62.5 12.1Q61.1 12.1 60.5 13.3L60.5 13.3L60.5 19.5L59.1 19.5L59.1 11.0ZM76.6 19.6L76.6 19.6Q74.9 19.6 73.8 18.5Q72.8 17.4 72.8 15.5L72.8 15.5L72.8 15.2Q72.8 14.0 73.2 13.0Q73.7 12.0 74.6 11.4Q75.4 10.9 76.4 10.9L76.4 10.9Q78.1 10.9 79.0 12.0Q79.9 13.0 79.9 15.1L79.9 15.1L79.9 15.7L74.2 15.7Q74.2 16.9 74.9 17.7Q75.6 18.5 76.7 18.5L76.7 18.5Q77.5 18.5 78 18.1Q78.5 17.8 78.9 17.3L78.9 17.3L79.8 18.0Q78.8 19.6 76.6 19.6ZM76.4 12.1L76.4 12.1Q75.6 12.1 75.0 12.7Q74.4 13.3 74.2 14.5L74.2 14.5L78.5 14.5L78.5 14.4Q78.4 13.3 77.9 12.7Q77.4 12.1 76.4 12.1ZM86.5 17.2L86.5 17.2Q86.5 16.7 86.1 16.3Q85.6 16.0 84.5 15.8Q83.4 15.5 82.8 15.2Q82.1 14.9 81.8 14.4Q81.5 14.0 81.5 13.4L81.5 13.4Q81.5 12.3 82.4 11.6Q83.3 10.9 84.7 10.9L84.7 10.9Q86.1 10.9 87.0 11.6Q87.9 12.4 87.9 13.5L87.9 13.5L86.4 13.5Q86.4 12.9 85.9 12.5Q85.4 12.1 84.7 12.1L84.7 12.1Q83.9 12.1 83.4 12.4Q83.0 12.7 83.0 13.3L83.0 13.3Q83.0 13.8 83.4 14.1Q83.8 14.4 84.9 14.6Q86.0 14.8 86.6 15.2Q87.3 15.5 87.6 16.0Q88.0 16.5 88.0 17.1L88.0 17.1Q88.0 18.3 87.1 19.0Q86.1 19.6 84.7 19.6L84.7 19.6Q83.7 19.6 82.9 19.3Q82.1 18.9 81.7 18.3Q81.2 17.6 81.2 16.9L81.2 16.9L82.7 16.9Q82.7 17.6 83.3 18.0Q83.8 18.5 84.7 18.5L84.7 18.5Q85.5 18.5 86.0 18.1Q86.5 17.8 86.5 17.2ZM94.8 17.2L94.8 17.2Q94.8 16.7 94.3 16.3Q93.9 16.0 92.8 15.8Q91.7 15.5 91.0 15.2Q90.4 14.9 90.1 14.4Q89.8 14.0 89.8 13.4L89.8 13.4Q89.8 12.3 90.7 11.6Q91.5 10.9 92.9 10.9L92.9 10.9Q94.3 10.9 95.2 11.6Q96.1 12.4 96.1 13.5L96.1 13.5L94.7 13.5Q94.7 12.9 94.2 12.5Q93.7 12.1 92.9 12.1L92.9 12.1Q92.1 12.1 91.7 12.4Q91.2 12.7 91.2 13.3L91.2 13.3Q91.2 13.8 91.6 14.1Q92.1 14.4 93.1 14.6Q94.2 14.8 94.9 15.2Q95.6 15.5 95.9 16.0Q96.2 16.5 96.2 17.1L96.2 17.1Q96.2 18.3 95.3 19.0Q94.4 19.6 93.0 19.6L93.0 19.6Q91.9 19.6 91.2 19.3Q90.4 18.9 89.9 18.3Q89.5 17.6 89.5 16.9L89.5 16.9L90.9 16.9Q91.0 17.6 91.5 18.0Q92.1 18.5 93.0 18.5L93.0 18.5Q93.8 18.5 94.3 18.1Q94.8 17.8 94.8 17.2ZM104.8 19.5L103.3 19.5Q103.2 19.2 103.1 18.6L103.1 18.6Q102.1 19.6 100.7 19.6L100.7 19.6Q99.5 19.6 98.7 18.9Q97.9 18.2 97.9 17.1L97.9 17.1Q97.9 15.8 98.9 15.1Q99.9 14.4 101.7 14.4L101.7 14.4L103.1 14.4L103.1 13.7Q103.1 12.9 102.6 12.5Q102.2 12.0 101.3 12.0L101.3 12.0Q100.5 12.0 100 12.4Q99.5 12.8 99.5 13.4L99.5 13.4L98.0 13.4Q98.0 12.7 98.5 12.2Q98.9 11.6 99.7 11.2Q100.5 10.9 101.4 10.9L101.4 10.9Q102.8 10.9 103.7 11.6Q104.5 12.3 104.5 13.6L104.5 13.6L104.5 17.5Q104.5 18.7 104.8 19.4L104.8 19.4L104.8 19.5ZM100.9 18.4L100.9 18.4Q101.6 18.4 102.2 18.0Q102.8 17.7 103.1 17.1L103.1 17.1L103.1 15.4L102.0 15.4Q99.3 15.4 99.3 16.9L99.3 16.9Q99.3 17.6 99.8 18.0Q100.2 18.4 100.9 18.4ZM106.5 15.2L106.5 15.2Q106.5 13.2 107.4 12.0Q108.3 10.9 109.8 10.9L109.8 10.9Q111.3 10.9 112.2 12.0L112.2 12.0L112.3 11.0L113.6 11.0L113.6 19.3Q113.6 20.9 112.6 21.9Q111.6 22.8 110.0 22.8L110.0 22.8Q109.1 22.8 108.2 22.4Q107.3 22.0 106.9 21.4L106.9 21.4L107.6 20.5Q108.6 21.6 109.9 21.6L109.9 21.6Q111.0 21.6 111.6 21.0Q112.1 20.4 112.1 19.4L112.1 19.4L112.1 18.6Q111.3 19.6 109.8 19.6L109.8 19.6Q108.3 19.6 107.4 18.4Q106.5 17.2 106.5 15.2ZM107.9 15.4L107.9 15.4Q107.9 16.8 108.5 17.6Q109.1 18.4 110.1 18.4L110.1 18.4Q111.5 18.4 112.1 17.2L112.1 17.2L112.1 13.3Q111.5 12.1 110.1 12.1L110.1 12.1Q109.1 12.1 108.5 12.9Q107.9 13.7 107.9 15.4ZM119.3 19.6L119.3 19.6Q117.6 19.6 116.5 18.5Q115.4 17.4 115.4 15.5L115.4 15.5L115.4 15.2Q115.4 14.0 115.9 13.0Q116.4 12.0 117.2 11.4Q118.1 10.9 119.1 10.9L119.1 10.9Q120.8 10.9 121.7 12.0Q122.6 13.0 122.6 15.1L122.6 15.1L122.6 15.7L116.9 15.7Q116.9 16.9 117.6 17.7Q118.3 18.5 119.4 18.5L119.4 18.5Q120.1 18.5 120.7 18.1Q121.2 17.8 121.6 17.3L121.6 17.3L122.5 18.0Q121.4 19.6 119.3 19.6ZM119.1 12.1L119.1 12.1Q118.2 12.1 117.6 12.7Q117.0 13.3 116.9 14.5L116.9 14.5L121.1 14.5L121.1 14.4Q121.1 13.3 120.5 12.7Q120.0 12.1 119.1 12.1Z " clip-path="url(#satori_cp-id)" mask="url(#satori_om-id)"/></svg>
出力された SVG 画像
その他
satori
は他にも様々なことができます。
詳しくは公式の README をご参照ください。
まとめ
Shields.io や GitHub Readme Stats のような動的に SVG 生成する機能を簡単に実装できそうですね。
Discussion