spectre-help.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. /*
  2. * Common code between spectre-test and spectre-gen, since both of
  3. * them want to output SVG graphics.
  4. */
  5. #include <assert.h>
  6. #include <errno.h>
  7. #include <math.h>
  8. #include <stdio.h>
  9. #include <string.h>
  10. #include "puzzles.h"
  11. #include "tree234.h"
  12. #include "spectre-internal.h"
  13. #include "spectre-tables-extra.h"
  14. #include "spectre-help.h"
  15. struct HexData {
  16. const int *edges;
  17. };
  18. static const struct HexData hexdata[] = {
  19. #define HEXDATA_ENTRY(x) { edges_##x },
  20. HEX_LETTERS(HEXDATA_ENTRY)
  21. #undef HEXDATA_ENTRY
  22. };
  23. const char *hex_names[10] = {
  24. "G", "D", "J", "L", "X", "P", "S", "F", "Y",
  25. "" /* NO_HEX */
  26. };
  27. Graphics *gr_new(const char *filename, double xmin, double xmax,
  28. double ymin, double ymax, double scale)
  29. {
  30. Graphics *gr = snew(Graphics);
  31. if (!strcmp(filename, "-")) {
  32. gr->fp = stdout;
  33. gr->close_file = false;
  34. } else {
  35. gr->fp = fopen(filename, "w");
  36. if (!gr->fp) {
  37. fprintf(stderr, "%s: open: %s\n", filename, strerror(errno));
  38. exit(1);
  39. }
  40. gr->close_file = true;
  41. }
  42. fprintf(gr->fp, "<?xml version=\"1.0\" encoding=\"UTF-8\" "
  43. "standalone=\"no\"?>\n");
  44. fprintf(gr->fp, "<svg xmlns=\"http://www.w3.org/2000/svg\" "
  45. "version=\"1.1\" width=\"%f\" height=\"%f\">\n",
  46. (xmax - xmin) * scale, (ymax - ymin) * scale);
  47. gr->absscale = fabs(scale);
  48. gr->xoff = -xmin * scale;
  49. gr->xscale = scale;
  50. /* invert y axis for SVG top-down coordinate system */
  51. gr->yoff = ymax * scale;
  52. gr->yscale = -scale;
  53. /* Defaults, which can be overridden by the caller immediately
  54. * after this constructor returns */
  55. gr->jigsaw_mode = false;
  56. gr->vertex_blobs = true;
  57. gr->number_cells = true;
  58. gr->four_colour = false;
  59. gr->arcs = false;
  60. gr->linewidth = 1.5;
  61. gr->started = false;
  62. return gr;
  63. }
  64. void gr_free(Graphics *gr)
  65. {
  66. if (!gr)
  67. return;
  68. fprintf(gr->fp, "</svg>\n");
  69. if (gr->close_file)
  70. fclose(gr->fp);
  71. sfree(gr);
  72. }
  73. static void gr_ensure_started(Graphics *gr)
  74. {
  75. if (gr->started)
  76. return;
  77. fprintf(gr->fp, "<style type=\"text/css\">\n");
  78. fprintf(gr->fp, "path { fill: none; stroke: black; stroke-width: %f; "
  79. "stroke-linejoin: round; stroke-linecap: round; }\n",
  80. gr->linewidth);
  81. fprintf(gr->fp, "text { fill: black; font-family: Sans; "
  82. "text-anchor: middle; text-align: center; }\n");
  83. if (gr->four_colour) {
  84. fprintf(gr->fp, ".c0 { fill: rgb(255, 178, 178); }\n");
  85. fprintf(gr->fp, ".c1 { fill: rgb(255, 255, 178); }\n");
  86. fprintf(gr->fp, ".c2 { fill: rgb(178, 255, 178); }\n");
  87. fprintf(gr->fp, ".c3 { fill: rgb(153, 153, 255); }\n");
  88. } else {
  89. fprintf(gr->fp, ".G { fill: rgb(255, 128, 128); }\n");
  90. fprintf(gr->fp, ".G1 { fill: rgb(255, 64, 64); }\n");
  91. fprintf(gr->fp, ".F { fill: rgb(255, 192, 128); }\n");
  92. fprintf(gr->fp, ".Y { fill: rgb(255, 255, 128); }\n");
  93. fprintf(gr->fp, ".S { fill: rgb(128, 255, 128); }\n");
  94. fprintf(gr->fp, ".D { fill: rgb(128, 255, 255); }\n");
  95. fprintf(gr->fp, ".P { fill: rgb(128, 128, 255); }\n");
  96. fprintf(gr->fp, ".X { fill: rgb(192, 128, 255); }\n");
  97. fprintf(gr->fp, ".J { fill: rgb(255, 128, 255); }\n");
  98. fprintf(gr->fp, ".L { fill: rgb(128, 128, 128); }\n");
  99. fprintf(gr->fp, ".optional { stroke-dasharray: 5; }\n");
  100. fprintf(gr->fp, ".arrow { fill: rgba(0, 0, 0, 0.2); "
  101. "stroke: none; }\n");
  102. }
  103. fprintf(gr->fp, "</style>\n");
  104. gr->started = true;
  105. }
  106. /* Logical coordinates in our mathematical space */
  107. GrCoords gr_logcoords(Point p)
  108. {
  109. double rt3o2 = sqrt(3) / 2;
  110. GrCoords r = {
  111. p.coeffs[0] + rt3o2 * p.coeffs[1] + 0.5 * p.coeffs[2],
  112. p.coeffs[3] + rt3o2 * p.coeffs[2] + 0.5 * p.coeffs[1],
  113. };
  114. return r;
  115. }
  116. /* Physical coordinates in the output image */
  117. GrCoords gr_log2phys(Graphics *gr, GrCoords c)
  118. {
  119. c.x = gr->xoff + gr->xscale * c.x;
  120. c.y = gr->yoff + gr->yscale * c.y;
  121. return c;
  122. }
  123. GrCoords gr_physcoords(Graphics *gr, Point p)
  124. {
  125. return gr_log2phys(gr, gr_logcoords(p));
  126. }
  127. void gr_draw_text(Graphics *gr, GrCoords logpos, double logheight,
  128. const char *text)
  129. {
  130. GrCoords pos;
  131. double height;
  132. if (!gr)
  133. return;
  134. gr_ensure_started(gr);
  135. pos = gr_log2phys(gr, logpos);
  136. height = gr->absscale * logheight;
  137. fprintf(gr->fp, "<text style=\"font-size: %fpx\" x=\"%f\" y=\"%f\">"
  138. "%s</text>\n", height, pos.x, pos.y + 0.35 * height, text);
  139. }
  140. void gr_draw_path(Graphics *gr, const char *classes, const GrCoords *phys,
  141. size_t n, bool closed)
  142. {
  143. size_t i;
  144. if (!gr)
  145. return;
  146. gr_ensure_started(gr);
  147. fprintf(gr->fp, "<path class=\"%s\" d=\"", classes);
  148. for (i = 0; i < n; i++) {
  149. GrCoords c = phys[i];
  150. if (i == 0)
  151. fprintf(gr->fp, "M %f %f", c.x, c.y);
  152. else if (gr->arcs)
  153. fprintf(gr->fp, "A %f %f 10 0 %zu %f %f",
  154. gr->absscale, gr->absscale, i&1, c.x, c.y);
  155. else
  156. fprintf(gr->fp, "L %f %f", c.x, c.y);
  157. }
  158. if (gr->arcs) {
  159. /* Explicitly return to the starting point so as to curve the
  160. * final edge */
  161. fprintf(gr->fp, "A %f %f 10 0 0 %f %f",
  162. gr->absscale, gr->absscale, phys[0].x, phys[0].y);
  163. }
  164. if (closed)
  165. fprintf(gr->fp, " z");
  166. fprintf(gr->fp, "\"/>\n");
  167. }
  168. void gr_draw_blob(Graphics *gr, const char *classes, GrCoords log,
  169. double logradius)
  170. {
  171. GrCoords centre;
  172. if (!gr)
  173. return;
  174. gr_ensure_started(gr);
  175. centre = gr_log2phys(gr, log);
  176. fprintf(gr->fp, "<circle class=\"%s\" cx=\"%f\" cy=\"%f\" r=\"%f\"/>\n",
  177. classes, centre.x, centre.y, gr->absscale * logradius);
  178. }
  179. void gr_draw_hex(Graphics *gr, unsigned index, Hex htype,
  180. const Point *vertices)
  181. {
  182. size_t i;
  183. Point centre;
  184. if (!gr)
  185. return;
  186. gr_ensure_started(gr);
  187. /* Draw the actual hexagon, in its own colour */
  188. if (!gr->jigsaw_mode) {
  189. GrCoords phys[6];
  190. for (i = 0; i < 6; i++)
  191. phys[i] = gr_physcoords(gr, vertices[i]);
  192. gr_draw_path(gr, (index == 7 && htype == NO_HEX ?
  193. "optional" : hex_names[htype]), phys, 6, true);
  194. } else {
  195. GrCoords phys[66];
  196. size_t pos = 0;
  197. const struct HexData *hd = &hexdata[htype];
  198. for (i = 0; i < 6; i++) {
  199. int edge_type = hd->edges[i];
  200. int sign = edge_type < 0 ? -1 : +1;
  201. int edge_abs = abs(edge_type);
  202. int left_sign = (edge_abs & 4) ? sign : edge_type == 0 ? +1 : 0;
  203. int mid_sign = (edge_abs & 2) ? sign : 0;
  204. int right_sign = (edge_abs & 1) ? sign : edge_type == 0 ? -1 : 0;
  205. GrCoords start = gr_physcoords(gr, vertices[i]);
  206. GrCoords end = gr_physcoords(gr, vertices[(i+1) % 6]);
  207. GrCoords x = { (end.x - start.x) / 7, (end.y - start.y) / 7 };
  208. GrCoords y = { -x.y, +x.x };
  209. #define addpoint(X, Y) do { \
  210. GrCoords p = { \
  211. start.x + (X) * x.x + (Y) * y.x, \
  212. start.y + (X) * x.y + (Y) * y.y, \
  213. }; \
  214. phys[pos++] = p; \
  215. } while (0)
  216. if (sign < 0) {
  217. int tmp = right_sign;
  218. right_sign = left_sign;
  219. left_sign = tmp;
  220. }
  221. addpoint(0, 0);
  222. if (left_sign) {
  223. addpoint(1, 0);
  224. addpoint(2, left_sign);
  225. addpoint(2, 0);
  226. }
  227. if (mid_sign) {
  228. addpoint(3, 0);
  229. addpoint(3, mid_sign);
  230. addpoint(4, mid_sign);
  231. addpoint(4, 0);
  232. }
  233. if (right_sign) {
  234. addpoint(5, 0);
  235. addpoint(5, right_sign);
  236. addpoint(6, 0);
  237. }
  238. #undef addpoint
  239. }
  240. gr_draw_path(gr, hex_names[htype], phys, pos, true);
  241. }
  242. /* Find the centre of the hex */
  243. for (i = 0; i < 4; i++)
  244. centre.coeffs[i] = 0;
  245. for (i = 0; i < 6; i++)
  246. centre = point_add(centre, vertices[i]);
  247. for (i = 0; i < 4; i++)
  248. centre.coeffs[i] /= 6;
  249. /* Draw an arrow towards vertex 0 of the hex */
  250. if (gr->hex_arrows) {
  251. double ext = 0.6;
  252. double headlen = 0.3, thick = 0.08, headwid = 0.25;
  253. GrCoords top = gr_physcoords(gr, vertices[0]);
  254. GrCoords bot = gr_physcoords(gr, vertices[3]);
  255. GrCoords mid = gr_physcoords(gr, centre);
  256. GrCoords base = { mid.x + ext * (bot.x - mid.x),
  257. mid.y + ext * (bot.y - mid.y) };
  258. GrCoords tip = { mid.x + ext * (top.x - mid.x),
  259. mid.y + ext * (top.y - mid.y) };
  260. GrCoords len = { tip.x - base.x, tip.y - base.y };
  261. GrCoords perp = { -len.y, +len.x };
  262. GrCoords basep = { base.x+perp.x*thick, base.y+perp.y*thick };
  263. GrCoords basen = { base.x-perp.x*thick, base.y-perp.y*thick };
  264. GrCoords hbase = { tip.x-len.x*headlen, tip.y-len.y*headlen };
  265. GrCoords headp = { hbase.x+perp.x*thick, hbase.y+perp.y*thick };
  266. GrCoords headn = { hbase.x-perp.x*thick, hbase.y-perp.y*thick };
  267. GrCoords headP = { hbase.x+perp.x*headwid, hbase.y+perp.y*headwid };
  268. GrCoords headN = { hbase.x-perp.x*headwid, hbase.y-perp.y*headwid };
  269. GrCoords phys[] = {
  270. basep, headp, headP, tip, headN, headn, basen
  271. };
  272. gr_draw_path(gr, "arrow", phys, lenof(phys), true);
  273. }
  274. /*
  275. * Label the hex with its index and type.
  276. */
  277. if (gr->number_cells) {
  278. char buf[64];
  279. if (index == (unsigned)-1) {
  280. if (htype == NO_HEX)
  281. buf[0] = '\0';
  282. else
  283. strcpy(buf, hex_names[htype]);
  284. } else {
  285. if (htype == NO_HEX)
  286. sprintf(buf, "%u", index);
  287. else
  288. sprintf(buf, "%u (%s)", index, hex_names[htype]);
  289. }
  290. if (buf[0])
  291. gr_draw_text(gr, gr_logcoords(centre), 1.2, buf);
  292. }
  293. }
  294. void gr_draw_spectre(Graphics *gr, Hex container, unsigned index,
  295. const Point *vertices)
  296. {
  297. size_t i;
  298. GrCoords log[14];
  299. GrCoords centre;
  300. if (!gr)
  301. return;
  302. gr_ensure_started(gr);
  303. for (i = 0; i < 14; i++)
  304. log[i] = gr_logcoords(vertices[i]);
  305. /* Draw the actual Spectre */
  306. {
  307. GrCoords phys[14];
  308. char class[16];
  309. for (i = 0; i < 14; i++)
  310. phys[i] = gr_log2phys(gr, log[i]);
  311. if (gr->four_colour) {
  312. sprintf(class, "c%u", index);
  313. } else if (index == 1 && container == NO_HEX) {
  314. sprintf(class, "optional");
  315. } else {
  316. sprintf(class, "%s%.0u", hex_names[container], index);
  317. }
  318. gr_draw_path(gr, class, phys, 14, true);
  319. }
  320. /* Pick a point to use as the centre of the Spectre for labelling */
  321. centre.x = (log[5].x + log[6].x + log[11].x + log[12].x) / 4;
  322. centre.y = (log[5].y + log[6].y + log[11].y + log[12].y) / 4;
  323. /*
  324. * Label the hex with its index and type.
  325. */
  326. if (gr->number_cells && index != (unsigned)-1) {
  327. char buf[64];
  328. sprintf(buf, "%u", index);
  329. gr_draw_text(gr, centre, 1.2, buf);
  330. }
  331. }
  332. void gr_draw_spectre_from_coords(Graphics *gr, SpectreCoords *sc,
  333. const Point *vertices)
  334. {
  335. Hex h;
  336. unsigned index;
  337. if (!gr)
  338. return;
  339. gr_ensure_started(gr);
  340. if (gr->four_colour) {
  341. h = NO_HEX;
  342. if (sc->index == 1)
  343. index = 3; /* special colour for odd G1 Spectres */
  344. else
  345. index = sc->hex_colour;
  346. } else if (sc) {
  347. h = sc->c[0].type;
  348. index = sc->index;
  349. } else {
  350. h = NO_HEX;
  351. index = -1;
  352. }
  353. gr_draw_spectre(gr, h, index, vertices);
  354. }
  355. void gr_draw_extra_edge(Graphics *gr, Point a, Point b)
  356. {
  357. GrCoords phys[2];
  358. if (!gr)
  359. return;
  360. gr_ensure_started(gr);
  361. phys[0] = gr_physcoords(gr, a);
  362. phys[1] = gr_physcoords(gr, b);
  363. gr_draw_path(gr, "extraedge", phys, 2, false);
  364. }