ref: 43f4fde2f2ef694f355edc2777525cf611dc0182
parent: 2be0e4a242421840b2a36d18f6a4193e4fc67432
author: Simon Tatham <anakin@pobox.com>
date: Thu Jun 15 14:37:39 EDT 2023
hat-test: support SVG output. I want to generate an SVG diagram for an upcoming article.
--- a/auxiliary/hat-test.c
+++ b/auxiliary/hat-test.c
@@ -374,7 +374,7 @@
bbox->started = true;
}
-typedef enum OutFmt { OF_POSTSCRIPT, OF_PYTHON } OutFmt;
+typedef enum OutFmt { OF_POSTSCRIPT, OF_SVG, OF_PYTHON } OutFmt;
typedef enum ColourMode { CM_SEMANTIC, CM_FOURCOLOUR } ColourMode;
typedef struct drawctx {
@@ -384,6 +384,8 @@
KiteEnum *kiteenum;
FourColourMap fourcolourmap[KE_NKEEP];
bool natural_scale, clip;
+
+ float xoff, xscale, yoff, yscale; /* used for SVG only */
} drawctx;
static void bbox_add_hat(void *vctx, Kite kite0, HatCoords *hc, int *coords)
@@ -401,38 +403,38 @@
static void header(drawctx *ctx)
{
- switch (ctx->outfmt) {
- case OF_POSTSCRIPT: {
- float scale, ox, oy;
+ float scale, ox, oy;
- /* Optionally clip to an inner rectangle that guarantees
- * the whole visible area is covered in hats. */
- if (ctx->clip) {
- ctx->bbox->bl.x += 9;
- ctx->bbox->tr.x -= 9;
- ctx->bbox->bl.y += 12 * sqrt(0.75);
- ctx->bbox->tr.y -= 12 * sqrt(0.75);
- }
+ /* Optionally clip to an inner rectangle that guarantees
+ * the whole visible area is covered in hats. */
+ if (ctx->clip) {
+ ctx->bbox->bl.x += 9;
+ ctx->bbox->tr.x -= 9;
+ ctx->bbox->bl.y += 12 * sqrt(0.75);
+ ctx->bbox->tr.y -= 12 * sqrt(0.75);
+ }
- if (!ctx->natural_scale) {
- /* Scale the output to fit on an A4 page, for test prints. */
- float w = 595, h = 842, margin = 12;
- float xext = ctx->bbox->tr.x - ctx->bbox->bl.x;
- float yext = ctx->bbox->tr.y - ctx->bbox->bl.y;
- float xscale = (w - 2*margin) / xext;
- float yscale = (h - 2*margin) / yext;
- scale = xscale < yscale ? xscale : yscale;
- ox = (w - scale * (ctx->bbox->bl.x + ctx->bbox->tr.x)) / 2;
- oy = (h - scale * (ctx->bbox->bl.y + ctx->bbox->tr.y)) / 2;
- } else {
- /* Leave the patch at its natural scale. */
- scale = 1.0;
+ if (!ctx->natural_scale) {
+ /* Scale the output to fit on an A4 page, for test prints. */
+ float w = 595, h = 842, margin = 12;
+ float xext = ctx->bbox->tr.x - ctx->bbox->bl.x;
+ float yext = ctx->bbox->tr.y - ctx->bbox->bl.y;
+ float xscale = (w - 2*margin) / xext;
+ float yscale = (h - 2*margin) / yext;
+ scale = xscale < yscale ? xscale : yscale;
+ ox = (w - scale * (ctx->bbox->bl.x + ctx->bbox->tr.x)) / 2;
+ oy = (h - scale * (ctx->bbox->bl.y + ctx->bbox->tr.y)) / 2;
+ } else {
+ /* Leave the patch at its natural scale. */
+ scale = 1.0;
- /* And translate the lower left corner of the bounding box to 0. */
- ox = -ctx->bbox->bl.x;
- oy = -ctx->bbox->bl.y;
- }
+ /* And translate the lower left corner of the bounding box to 0. */
+ ox = -ctx->bbox->bl.x;
+ oy = -ctx->bbox->bl.y;
+ }
+ switch (ctx->outfmt) {
+ case OF_POSTSCRIPT: {
printf("%%!PS-Adobe-2.0\n%%%%Creator: hat-test from Simon Tatham's "
"Portable Puzzle Collection\n%%%%Pages: 1\n"
"%%%%BoundingBox: %f %f %f %f\n"
@@ -459,6 +461,40 @@
printf("0 setgray 1 setlinejoin 1 setlinecap\n");
break;
}
+ case OF_SVG: {
+ printf("<?xml version=\"1.0\" encoding=\"UTF-8\" "
+ "standalone=\"no\"?>\n");
+ printf("<svg xmlns=\"http://www.w3.org/2000/svg\" "
+ "version=\"1.1\" width=\"%f\" height=\"%f\">\n",
+ scale * (ctx->bbox->tr.x - ctx->bbox->bl.x),
+ scale * (ctx->bbox->tr.y - ctx->bbox->bl.y));
+ printf("<style type=\"text/css\">\n");
+ printf("path { fill: none; stroke: black; stroke-width: %f; "
+ "stroke-linejoin: round; stroke-linecap: round; }\n",
+ 0.06 * scale);
+ switch (ctx->colourmode) {
+ case CM_SEMANTIC:
+ printf(".H { fill: rgb(153, 204, 255); }\n");
+ printf(".H3 { fill: rgb( 0, 128, 204); }\n");
+ printf(".T, .P { fill: rgb(255, 255, 255); }\n");
+ printf(".F { fill: rgb(178, 178, 178); }\n");
+ break;
+
+ default /* case CM_FOURCOLOUR */:
+ printf(".c0 { fill: rgb(255, 178, 178); }\n");
+ printf(".c1 { fill: rgb(255, 255, 178); }\n");
+ printf(".c2 { fill: rgb(178, 255, 178); }\n");
+ printf(".c3 { fill: rgb(153, 153, 255); }\n");
+ break;
+ }
+ printf("</style>\n");
+
+ ctx->xoff = -ctx->bbox->bl.x * scale;
+ ctx->xscale = scale;
+ ctx->yoff = ctx->bbox->tr.y * scale;
+ ctx->yscale = -scale;
+ break;
+ }
default:
break;
}
@@ -535,6 +571,41 @@
printf(" stroke\n");
break;
}
+ case OF_SVG: {
+ const char *class;
+
+ switch (ctx->colourmode) {
+ case CM_SEMANTIC: {
+ static const char *const classes[] = {"H", "T", "P", "F"};
+
+ if (hc->c[2].type == TT_H && hc->c[1].index == 3)
+ class = "H3";
+ else
+ class = classes[hc->c[2].type];
+ break;
+ }
+
+ default /* case CM_FOURCOLOUR */: {
+ static const char *const classes[] = {"c0", "c1", "c2", "c3"};
+ FourColourMap f = ctx->fourcolourmap[ctx->kiteenum->curr_index];
+ const int *m = fourcolours[hc->c[3].type];
+ class = classes[f.map[m[hc->c[2].index * 4 + hc->c[1].index]]];
+ break;
+ }
+ }
+
+ printf("<path class=\"%s\" d=\"", class);
+
+ for (i = 0; i < 14; i++) {
+ p.x = coords[2*i] * 1.5;
+ p.y = coords[2*i+1] * sqrt(0.75);
+ printf("%s %f %f", i == 0 ? "M" : " L",
+ ctx->xoff + ctx->xscale * p.x,
+ ctx->yoff + ctx->yscale * p.y);
+ }
+ printf(" z\"/>\n");
+ break;
+ }
case OF_PYTHON: {
printf("hat('%c', %d, %d, [", "HTPF"[hc->c[2].type], hc->c[1].index,
orientation);
@@ -555,6 +626,10 @@
printf("%%%%EOF\n");
break;
}
+ case OF_SVG: {
+ printf("</svg>\n");
+ break;
+ }
default:
break;
}
@@ -589,6 +664,8 @@
return 0;
} else if (!strcmp(arg, "--test")) {
return unit_tests() ? 0 : 1;
+ } else if (!strcmp(arg, "--svg")) {
+ dctx->outfmt = OF_SVG;
} else if (!strcmp(arg, "--python")) {
dctx->outfmt = OF_PYTHON;
} else if (!strcmp(arg, "--fourcolour")) {