Dynamic rendering service on Cloudflare Workers that generates PNG images of the Goban (Go/Weiqi) from HEN (Hemme Notation) strings.
- The Worker intercepts URLs in the format
[options]/hen<hen_string>.png - Parses the HEN string and generates a Goban SVG
- Converts the SVG to PNG via
@resvg/resvg-wasm(WebAssembly) - Embeds the HEN string as a
tEXtmetadata chunk in the PNG (readable withexiftool) - Serves the PNG with an immutable cache header (1 year)
- Responds from Cloudflare's edge cache for subsequent requests
- Node.js >= 18
- A Cloudflare account (free)
- Wrangler CLI (installed as a devDependency)
npm installCopy the Wrangler configuration template and fill in your KV namespace ID:
cp wrangler.toml.template wrangler.tomlEdit wrangler.toml and replace YOUR_RATE_LIMIT_KV_ID with your actual Cloudflare KV namespace ID for the RATE_LIMIT binding.
npm run devThe Worker will be available at http://localhost:8787.
Try with a sample URL:
http://localhost:8787/hen.19x19.b_16DbQw.png
http://localhost:8787/c/hen.19x19.b_16DbQw.png
npm run deployOn first deploy, Wrangler will prompt you to authenticate with Cloudflare.
/[options]/hen<hen_string>.png
Options are placed in the path before hen, enclosed between /:
| Option | Effect |
|---|---|
c |
Show coordinates (column letters + row numbers) |
Example: /c/hen.19x19.b_16DbQw.png
The HEN string is a compact notation for describing a Go position. For details on the HEN format, see the HEN specification.
# 19×19 Goban with a black stone at Q16
hen.19x19.b_16Db.png
# With coordinates
/c/hen.19x19.b_16Db.png
# Full position with last move, ko, and mark
hen.19x19_19bwb2w3_10Kb_8Kbw_7JbwMw_6Kbw_1Cw3bNwb2.L7.K7w.b.png
# 9×9 Goban
hen.9x9_9b3w3b_7w5w_5b2w3b2w_3b6w_1b2w3b2w.png
The Worker enforces IP-based rate limiting using a Cloudflare KV namespace (RATE_LIMIT).
| Parameter | Value | Description |
|---|---|---|
RATE_LIMIT |
20 | Max requests per IP per window |
RATE_LIMIT_WINDOW |
60 s | Sliding window duration |
- IPv4 addresses are tracked as-is
- IPv6 addresses are normalized to their
/64prefix (first 4 blocks), so all devices in the same /64 share the limit - When the limit is exceeded, the Worker returns
429 Too Many Requestswith aRetry-Afterheader - Rate limit state is stored in KV with a TTL slightly above the window duration, so entries auto-expire
The RATE_LIMIT KV namespace is configured in wrangler.toml (see wrangler.toml.template for the required structure). The namespace ID must match an existing KV namespace in your Cloudflare account.
Every generated PNG embeds the original HEN string in a tEXt metadata chunk with the keyword HEN. You can retrieve it with any tool that reads PNG text chunks.
exiftool -HEN image.png
# HEN : .19x19.b_16DbQwfrom PIL import Image
img = Image.open("image.png")
print(img.text.get("HEN"))import { readFileSync } from "fs";
import extractChunks from "png-chunks-extract";
import textChunk from "png-chunk-text";
const data = readFileSync("image.png");
const chunks = extractChunks(data).filter(c => c.name === "tEXt");
const text = Object.fromEntries(chunks.map(c => textChunk.decode(c.data)));
console.log(text.HEN);This project is licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
This project uses the following third-party components:
| Component | License | Source |
|---|---|---|
| @resvg/resvg-wasm | MPL-2.0 | SVG-to-PNG rendering via WebAssembly |
| @resvg/resvg-js | MPL-2.0 | SVG-to-PNG rendering (native bindings) |
| Roboto | Apache 2.0 | Font used for text rendering |