diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,15 @@
+0.1.0.4: 20141115
+-----------------
+
+* add installation and usage instructions to README
+* merge puzzle-draw-cmdline
+* update for diagrams 1.2 and ghc 7.8
+* add new puzzle types: Bahnhof, Cave
+* rename `countnumbers` to `meanderingnumbers`
+* include and use a nicer font
+* command-line option to list supported puzzle types
+* various tweaks and fixes
+
 0.1.0.3: 20140519
 -----------------
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -3,14 +3,11 @@
 puzzle-draw
 ===========
 
-puzzle-draw is a library for drawing pencil puzzles using 
-[Diagrams][diagrams]. It aims to provide a utility layer on top of 
-Diagrams to help with drawing arbitrary puzzles, as well as supporting 
-several specific puzzle types directly. In addition, it includes 
-functionality for parsing puzzle data from a YAML file format.
-
-See [puzzle-draw-cmdline][cmdline] for a command line tool that
-uses this.
+puzzle-draw is a library and command-line tool for drawing pencil
+puzzles using [Diagrams][diagrams]. It aims to provide a utility layer
+on top of Diagrams to help with drawing arbitrary puzzles, as well as
+supporting several specific puzzle types directly. In addition, it
+includes functionality for parsing puzzle data from a YAML file format.
 
 Examples
 --------
@@ -48,11 +45,59 @@
     ..X...
 ```
 
+There is a demo web application at [demo] that provides some more
+examples and that can be used to generate SVG images from such
+puzzle descriptions.
+
 Or see a [puzzle set][twentyfour] that covers the puzzle types
-that are supported currently.
+that are supported as of 2014-05.
 
-[travis]: http://travis-ci.org/robx/puzzle-draw
-[cmdline]: http://github.com/robx/puzzle-draw-cmdline
-[liarslither]: http://maybepuzzles.wordpress.com/types/liar-slither-link/
-[twentyfour]: http://maybepuzzles.wordpress.com/2014/03/29/puzzle-set-24-hour-marathon/
+Installing
+----------
+
+This is a brief overview of how to install the command line tool.
+You'll need to get a Haskell environment, by installing the
+[Haskell Platform][platform]. Then install `puzzle-draw` with cabal:
+
+```
+$ cabal install puzzle-draw
+```
+
+This will compile puzzle-draw against the SVG backend, and install
+a binary `drawpuzzle` that formats puzzles to SVG. Alternatively,
+you can compile against the Cairo backend by passing `-fcairo`:
+
+```
+$ cabal install -fcairo puzzle-draw
+```
+
+This will enable PDF and PNG output. Be warned that getting Cairo
+installed can be tricky on non-Linux platforms.
+
+Running
+-------
+
+As an example, suppose the Liar Slitherlink puzzle description above
+is copied into a file `slitherliar.pzl`.
+
+```
+$ drawpuzzle slitherliar.pzl
+```
+
+This will generate two files `slitherliar.svg` and `slitherliar-sol.svg`,
+containing the puzzle and solved puzzle, respectively. Run
+
+```
+$ drawpuzzle -h
+```
+
+to see some command line options that allow modifying the program's
+behaviour, e.g., choosing the output format.
+
+[travis]: https://travis-ci.org/robx/puzzle-draw
+[cmdline]: https://github.com/robx/puzzle-draw-cmdline
+[liarslither]: https://maybepuzzles.wordpress.com/types/liar-slither-link/
+[twentyfour]: https://maybepuzzles.wordpress.com/2014/03/29/puzzle-set-24-hour-marathon/
 [diagrams]: http://projects.haskell.org/diagrams/
+[demo]: https://puzzle-draw-demo.herokuapp.com
+[platform]: https://www.haskell.org/platform/
diff --git a/data/fonts/gen-light.svg b/data/fonts/gen-light.svg
new file mode 100644
--- /dev/null
+++ b/data/fonts/gen-light.svg
@@ -0,0 +1,419 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg>
+<metadata>
+Created by FontForge 20120731 at Thu Nov  6 05:54:01 2014
+ By Orthosie Webhosting
+Gen Bold is a font by Alfredo Marco Pradil, designed in 2014.
+</metadata>
+<defs>
+<font id="Gen-Light" horiz-adv-x="500" >
+  <font-face 
+    font-family="Gen Light"
+    font-weight="300"
+    font-stretch="normal"
+    units-per-em="1000"
+    panose-1="0 0 0 0 0 0 0 0 0 0"
+    ascent="800"
+    descent="-200"
+    x-height="502"
+    cap-height="725"
+    bbox="-25 -230 991 957"
+    underline-thickness="50"
+    underline-position="-50"
+    unicode-range="U+000D-2122"
+  />
+<missing-glyph horiz-adv-x="250" 
+ />
+    <glyph glyph-name=".notdef" horiz-adv-x="250" 
+ />
+    <glyph glyph-name="NULL" horiz-adv-x="0" 
+ />
+    <glyph glyph-name="NULL" horiz-adv-x="0" 
+ />
+    <glyph glyph-name="nonmarkingreturn" unicode="&#xd;" horiz-adv-x="250" 
+ />
+    <glyph glyph-name="space" unicode=" " horiz-adv-x="250" 
+ />
+    <glyph glyph-name="numbersign" unicode="#" horiz-adv-x="560" 
+d="M405 224v-204h-43v204h-151v-204h-42v204h-98v45h98v155h-98v44h98v204h42v-204h151v204h43v-204h98v-44h-98v-155h98v-45h-98zM362 424h-151v-155h151v155z" />
+    <glyph glyph-name="dollar" unicode="$" horiz-adv-x="565" 
+d="M296 -68h-40v53q-43 2 -81 13.5t-66.5 33.5t-45 55t-16.5 78v4h60q2 -67 42.5 -98t106.5 -35v287q-43 15 -79 30t-62 37t-40.5 54t-14.5 82q0 36 17 68.5t44.5 57.5t62.5 39.5t72 14.5v49h40v-49q40 0 75 -11.5t62 -33.5t44 -52.5t21 -69.5h-60q-1 30 -13 51.5t-31 36
+t-44.5 22t-53.5 9.5v-287q37 -11 75.5 -25.5t69 -36.5t50 -52.5t19.5 -74.5q0 -96 -59 -146t-155 -50v-54zM256 656q-30 0 -55 -11.5t-43 -30.5t-28 -44t-10 -53q0 -31 11.5 -52.5t30 -37t43 -26t51.5 -18.5v273zM296 36q30 2 57.5 12t49 27t34.5 41t13 56q0 34 -13.5 57
+t-35.5 39.5t-49.5 27.5t-55.5 20v-280z" />
+    <glyph glyph-name="percent" unicode="%" horiz-adv-x="900" 
+d="M401 518q0 24 -5.5 49.5t-18 46.5t-32.5 34.5t-48 13.5q-29 0 -49 -13t-32 -34t-17.5 -46.5t-5.5 -50.5t5.5 -50.5t17.5 -46.5t32 -34t49 -13q28 0 48 13.5t32.5 34.5t18 46.5t5.5 49.5zM673 707h58l-390 -707h-58zM138 518q0 36 10 70t30.5 60.5t50 42.5t68.5 16
+t68.5 -16t50 -42.5t30.5 -60.5t10 -70t-10 -70t-30 -60.5t-49.5 -42.5t-69.5 -16q-39 0 -69 16t-50 42.5t-30 60.5t-10 70zM544 174q0 36 10 70t30.5 60.5t50 42.5t68.5 16t68.5 -16t50 -42.5t30.5 -60.5t10 -70t-10 -70t-30 -60.5t-49.5 -42.5t-69.5 -16q-39 0 -69 16
+t-50 42.5t-30 60.5t-10 70zM807 174q0 24 -5.5 49.5t-18 46.5t-32.5 34.5t-48 13.5q-29 0 -49 -13t-32 -34t-17.5 -46.5t-5.5 -50.5t5.5 -50.5t17.5 -46.5t32 -34t49 -13q28 0 48 13.5t32.5 34.5t18 46.5t5.5 49.5z" />
+    <glyph glyph-name="ampersand" unicode="&#x26;" horiz-adv-x="720" 
+d="M264 44q34 0 61 14t49.5 36t41 48.5t35.5 51.5l-176 187q-26 -17 -53 -41t-48.5 -53.5t-35 -62t-13.5 -65.5q0 -29 12 -50.5t31.5 -35.5t44.5 -21.5t51 -7.5zM209 591q0 -20 7.5 -37.5t19 -34t25.5 -31t26 -27.5q16 15 34 27t33 26t24.5 32t9.5 45q0 18 -8 33.5t-21.5 27
+t-30 18.5t-33.5 7q-35 0 -60.5 -25.5t-25.5 -60.5zM233 422q-16 19 -32.5 38t-30 39t-22.5 43t-9 49q0 31 13.5 58t35.5 46.5t50.5 30.5t58.5 11t59 -10.5t51.5 -30t36.5 -46.5t14 -59q0 -31 -12 -56t-31.5 -45t-43 -37.5t-45.5 -33.5l158 -171l95 160h74l-128 -204
+l193 -204h-93l-134 150q-25 -37 -46.5 -67.5t-47 -52t-59 -33.5t-82.5 -12q-38 0 -73.5 12t-64 34t-45.5 54t-17 73t15.5 79.5t40.5 72.5t57 62.5t64 49.5z" />
+    <glyph glyph-name="quotesingle" unicode="'" horiz-adv-x="226" 
+d="M87 496v226h52v-226h-52z" />
+    <glyph glyph-name="parenleft" unicode="(" horiz-adv-x="266" 
+d="M183 -96q-2 7 -8 23t-16 41t-16 42.5t-10 27.5t-9 26t-12 38t-12 40t-8 32q-5 28 -10.5 66.5t-5.5 72.5q0 64 16 135q8 37 17 67.5t17 53.5t20.5 55.5t28.5 74.5l9 23h50q-88 -225 -88 -409t88 -409h-51z" />
+    <glyph glyph-name="parenright" unicode=")" horiz-adv-x="275" 
+d="M44 -96q88 225 88 409t-88 409h50q35 -91 57 -155t32 -103q19 -79 19 -151q0 -34 -6 -73q-3 -19 -5 -34.5t-5.5 -31.5t-8 -33t-11.5 -39t-12 -38t-9 -26t-10 -27.5t-16 -42.5t-16 -41t-8 -23h-51z" />
+    <glyph glyph-name="asterisk" unicode="*" horiz-adv-x="475" 
+d="M116 567h131l-66 114l31 17l66 -113l65 113l31 -17l-66 -114h131v-35h-131l66 -114l-31 -18l-65 114l-66 -114l-31 18l66 114h-131v35z" />
+    <glyph glyph-name="plus" unicode="+" horiz-adv-x="595" 
+d="M568 332h-220v-221h-36v221h-220v36h220v221h36v-221h220v-36z" />
+    <glyph glyph-name="comma" unicode="," horiz-adv-x="240" 
+d="M102 81h81l-72 -205h-52z" />
+    <glyph glyph-name="hyphen" unicode="-" horiz-adv-x="322" 
+d="M253 333h-228v36h228v-36z" />
+    <glyph glyph-name="period" unicode="." horiz-adv-x="242" 
+d="M179 0h-80v86h80v-86z" />
+    <glyph glyph-name="slash" unicode="/" horiz-adv-x="553" 
+d="M503 707l-405 -707h-48l407 707h46z" />
+    <glyph glyph-name="zero" unicode="0" horiz-adv-x="570" 
+d="M445 346q0 27 -1.5 60t-7 66t-16 65t-28.5 56t-44 39t-63 15t-63 -15t-44 -39t-28.5 -56t-16 -65t-7 -66t-1.5 -60t1.5 -60t7 -66t16 -65t28.5 -56t44 -39t63 -15t63 15t44 39t28.5 56t16 65t7 66t1.5 60zM55 346q0 58 8 121.5t32.5 117t70 88t119.5 34.5t119.5 -34.5
+t70 -88t32.5 -117t8 -121.5t-8 -121.5t-32.5 -117t-70 -88t-119.5 -34.5t-119.5 34.5t-70 88t-32.5 117t-8 121.5z" />
+    <glyph glyph-name="one" unicode="1" horiz-adv-x="570" 
+d="M175 583l159 109h62v-692h-65v624l-128 -84z" />
+    <glyph glyph-name="two" unicode="2" horiz-adv-x="570" 
+d="M503 0h-435v68q23 22 59 55.5t75.5 73.5t79 84.5t71 88t51.5 83.5t20 71q0 30 -11.5 53t-30.5 38.5t-44 23.5t-53 8q-30 0 -56 -10t-44.5 -28t-29 -43.5t-10.5 -56.5h-70q0 46 16 83t44.5 62.5t66.5 39t83 13.5q44 0 82 -12.5t66.5 -37t44.5 -60.5t16 -82q0 -36 -19 -77
+t-50 -84t-69 -86t-77 -81.5t-74 -71t-59 -55.5h357v-60z" />
+    <glyph glyph-name="three" unicode="3" horiz-adv-x="570" 
+d="M206 392q33 0 73.5 3.5t75.5 16.5t58.5 39t23.5 71q0 30 -14 53.5t-36 39.5t-49.5 24.5t-54.5 8.5q-29 0 -55 -8.5t-46 -25t-33 -40t-16 -53.5h-70q0 45 19 80t50 58.5t71 36t81 12.5q42 0 82 -12.5t71.5 -36.5t50.5 -59.5t19 -81.5q0 -32 -15 -58t-38 -45.5t-51.5 -33
+t-56.5 -19.5v-2q31 -7 62 -22t55.5 -37t39.5 -51.5t15 -64.5q0 -49 -21 -86t-55 -62.5t-77 -38.5t-87 -13t-84.5 13.5t-71 39t-49.5 62t-20 83.5h70q0 -33 13.5 -59t36.5 -43.5t52 -26.5t60 -9q33 0 63 10t52 29t35 46.5t13 62.5q0 46 -25 73t-62 41t-79 18t-76 4v63z" />
+    <glyph glyph-name="four" unicode="4" horiz-adv-x="570" 
+d="M360 0v161h-325v63l304 468h86v-477h111v-54h-111v-161h-65zM360 215v412h-2l-268 -412h270z" />
+    <glyph glyph-name="five" unicode="5" horiz-adv-x="570" 
+d="M62 291v401h411v-60h-341v-249q28 33 68.5 53t85.5 20q49 0 90.5 -18t70.5 -49t45.5 -73t16.5 -91q0 -48 -16 -91.5t-45.5 -76.5t-70.5 -52.5t-91 -19.5q-44 0 -84.5 10.5t-71 33t-49 57.5t-19.5 84h70q1 -31 13.5 -54.5t32.5 -39t46 -23.5t55 -8q37 0 67 14.5t51 39
+t32.5 56.5t11.5 68t-11.5 67.5t-32.5 55t-51 37t-67 13.5q-24 0 -48 -7t-44.5 -20.5t-35 -33t-19.5 -44.5h-70z" />
+    <glyph glyph-name="six" unicode="6" horiz-adv-x="570" 
+d="M300 394q-41 0 -72 -14.5t-52 -39t-31.5 -58t-10.5 -71.5q0 -34 13.5 -64t36 -52.5t52.5 -36t64 -13.5q38 0 66 12.5t46 34.5t27 52.5t9 66.5q0 34 -8.5 67t-26.5 59t-46 41.5t-67 15.5zM300 707q41 0 78.5 -11t66.5 -33t46 -55t18 -78h-70q-1 27 -13 49t-31.5 37
+t-44.5 23t-50 8q-63 0 -98.5 -31t-53 -77.5t-22 -101t-4.5 -101.5h2q24 58 69.5 85.5t106.5 27.5q53 0 94 -18.5t68.5 -50.5t41.5 -75.5t14 -93.5t-15 -91.5t-43 -71.5t-68.5 -46.5t-91.5 -16.5q-73 0 -120.5 27.5t-76 73t-40 104.5t-11.5 121q0 43 3 89.5t11.5 90.5
+t25.5 83t44.5 69t67.5 47t96 17z" />
+    <glyph glyph-name="seven" unicode="7" horiz-adv-x="570" 
+d="M506 692v-60l-269 -632h-76l277 632h-373v60h441z" />
+    <glyph glyph-name="eight" unicode="8" horiz-adv-x="570" 
+d="M201 361q-32 4 -58 17.5t-44.5 35t-28.5 49.5t-10 60q0 46 19.5 81t51.5 57.5t72 34t82 11.5q41 0 81.5 -12t72.5 -35t52 -57.5t20 -80.5q0 -31 -11 -57.5t-30 -47.5t-44.5 -35.5t-54.5 -20.5v-1q31 -8 58.5 -23.5t48 -37t32.5 -50t12 -61.5q0 -48 -20.5 -85.5t-54 -64
+t-76 -40t-86.5 -13.5t-86.5 13t-75.5 38.5t-53.5 62.5t-20.5 85q0 34 11.5 63t32 51.5t48.5 38t60 22.5v2zM123 520q0 -33 14 -57.5t37 -40.5t52 -23.5t59 -7.5q29 0 58 7.5t52 23.5t37.5 40.5t14.5 57.5t-14.5 57.5t-37.5 41t-52 25t-58 8.5q-30 0 -59 -8.5t-52 -25
+t-37 -41t-14 -57.5zM285 331q-33 0 -63 -9t-53 -27t-36.5 -45t-13.5 -62q0 -36 13.5 -62.5t36.5 -44.5t53 -27t63 -9q32 0 62 9t53.5 27t37.5 44.5t14 62.5q0 35 -14 61.5t-37.5 44.5t-53.5 27.5t-62 9.5z" />
+    <glyph glyph-name="nine" unicode="9" horiz-adv-x="570" 
+d="M274 647q-39 0 -67.5 -13t-47 -36.5t-28 -54.5t-9.5 -67q0 -34 11 -62.5t30.5 -49t47.5 -32t63 -11.5t65 10.5t51.5 30.5t34 49t12.5 65q0 83 -39 127t-124 44zM124 170q1 -32 13.5 -55t33 -39t47.5 -23.5t56 -7.5q38 0 66 14t47.5 37.5t31.5 54.5t18.5 64.5t9 67
+t2.5 62.5v27h-2q-55 -106 -173 -106q-47 0 -87.5 15t-70.5 42.5t-47 66t-17 86.5q0 49 15.5 91.5t44.5 73.5t70 48.5t92 17.5q78 0 126 -29.5t74.5 -77.5t35.5 -109.5t9 -125.5q0 -41 -4 -85t-14 -86.5t-27.5 -80t-44.5 -66t-65.5 -45.5t-89.5 -17q-44 0 -82.5 11.5t-68 35
+t-47.5 58t-22 80.5h70z" />
+    <glyph glyph-name="colon" unicode=":" horiz-adv-x="230" 
+d="M99 502h80v-86h-80v86zM99 86h80v-86h-80v86z" />
+    <glyph glyph-name="semicolon" unicode=";" horiz-adv-x="254" 
+d="M121 81h81l-72 -205h-52zM107 502h80v-86h-80v86z" />
+    <glyph glyph-name="less" unicode="&#x3c;" horiz-adv-x="613" 
+d="M159 251l421 -197v-64l-500 235v53l500 223v-64z" />
+    <glyph glyph-name="equal" unicode="=" horiz-adv-x="602" 
+d="M580 378v-60h-500v60h500zM580 184v-60h-500v60h500z" />
+    <glyph glyph-name="greater" unicode="&#x3e;" horiz-adv-x="599" 
+d="M501 251l-421 197v64l500 -235v-53l-500 -223v64z" />
+    <glyph glyph-name="question" unicode="?" horiz-adv-x="464" 
+d="M425 556q0 -38 -14 -68t-35.5 -56t-46 -49t-46 -45.5t-35.5 -46.5t-14 -52v-70h-60v70q0 34 14 61t34.5 51t44.5 46t44.5 46t34.5 51t14 62q0 54 -28 87.5t-84 33.5q-51 0 -90 -16t-75 -53l-50 38q44 48 98 69.5t117 21.5q87 0 132 -47.5t45 -133.5zM164 86h80v-86h-80
+v86z" />
+    <glyph glyph-name="at" unicode="@" horiz-adv-x="808" 
+d="M430 525q-37 0 -68.5 -22.5t-55 -57t-36.5 -75t-12 -76.5q2 -45 20 -70.5t56 -27.5q28 -1 57 25t53 64t39 81t14 78q-1 37 -17 58.5t-50 22.5zM554 566h66l-100 -300q-12 -35 -12 -51q0 -24 29 -26q29 -1 57.5 21.5t51 58t36 77.5t12.5 81q-2 118 -83 188q-83 72 -199 72
+q-136 0 -224 -97q-88 -95 -88 -233q0 -135 89 -229q90 -93 223 -93q78 0 149.5 35.5t117.5 99.5h58q-47 -86 -136 -135q-89 -50 -189 -50q-159 0 -266 107q-106 107 -106 265q0 169 108 274q107 106 264 106q143 0 243 -89q99 -89 99 -224q0 -113 -74 -202q-73 -88 -157 -88
+q-29 0 -53 13.5t-32 38.5q-50 -50 -119 -50t-102.5 43t-33.5 110q0 49 17.5 101t50 95.5t77.5 71.5t100 28q36 0 66 -17t45 -48z" />
+    <glyph glyph-name="A" unicode="A" horiz-adv-x="668" 
+d="M173 255h315l-156 407zM76 0h-67l286 722h76l288 -722h-71l-77 195h-359z" />
+    <glyph glyph-name="B" unicode="B" horiz-adv-x="665" 
+d="M165 404h120q36 0 67.5 4.5t55.5 18.5t38 40t14 68q0 61 -40 94t-111 33h-144v-258zM100 0v722h209q41 0 81 -9.5t71 -31t50 -57.5t19 -89q0 -21 -5.5 -45.5t-18 -47t-32.5 -40t-48 -24.5v-2q35 -6 65 -21.5t52 -39t34.5 -54.5t12.5 -68q0 -52 -18.5 -88.5t-50.5 -60
+t-74 -34t-88 -10.5h-259zM165 344v-284h154q34 0 69.5 5t64.5 19t48 40.5t19 68.5q0 54 -21 84t-55 45t-75.5 18.5t-83.5 3.5h-120z" />
+    <glyph glyph-name="C" unicode="C" horiz-adv-x="667" 
+d="M549 560q-8 31 -25.5 53t-41 36.5t-52 21t-59.5 6.5q-71 0 -117 -26.5t-74 -71t-39 -101.5t-11 -117q0 -59 12 -115.5t40.5 -101t74.5 -72t114 -27.5q31 0 60.5 9.5t53.5 27.5t41 44t23 58h70q-8 -45 -31 -81.5t-57 -63t-75.5 -40.5t-84.5 -14q-83 0 -142 31t-96.5 83
+t-55 120t-17.5 142q0 76 17 144t54 119.5t96 82t144 30.5q91 0 157 -44.5t91 -132.5h-70z" />
+    <glyph glyph-name="D" unicode="D" horiz-adv-x="722" 
+d="M155 60h102q60 0 119 11t106 43.5t76 91.5t29 155q0 94 -32.5 152.5t-82 91.5t-107 45t-108.5 12h-102v-602zM90 722h167q41 0 87 -6.5t90.5 -22t85 -41.5t71 -66t48.5 -95.5t18 -129.5q0 -75 -16.5 -131t-45.5 -96t-68 -66t-83.5 -41t-92 -21t-94.5 -6h-167v722z" />
+    <glyph glyph-name="E" unicode="E" horiz-adv-x="609" 
+d="M544 0h-444v722h424v-60h-359v-259h340v-60h-340v-283h379v-60z" />
+    <glyph glyph-name="F" unicode="F" horiz-adv-x="578" 
+d="M160 0h-65v722h428v-60h-363v-255h343v-60h-343v-347z" />
+    <glyph glyph-name="G" unicode="G" horiz-adv-x="725" 
+d="M376 368h259v-278q0 -28 -29.5 -48t-69 -32.5t-79.5 -18.5t-61 -6q-80 0 -141.5 30t-103.5 80.5t-64 117.5t-22 142q0 68 20 136.5t59.5 123t97.5 88.5t134 34q44 0 87.5 -12t79.5 -36.5t59.5 -60.5t27.5 -84h-74q-5 34 -21.5 59t-41.5 41.5t-55.5 24.5t-61.5 8
+q-62 0 -107 -28.5t-74.5 -73.5t-43.5 -100.5t-14 -109.5q0 -59 15.5 -116.5t48 -102.5t81 -73t114.5 -28q14 0 43 4t58 12.5t51 21.5t22 32v193h-194v60z" />
+    <glyph glyph-name="H" unicode="H" horiz-adv-x="724" 
+d="M165 0h-65v722h65v-317h394v317h65v-722h-65v345h-394v-345z" />
+    <glyph glyph-name="I" unicode="I" horiz-adv-x="245" 
+d="M155 0h-65v725h65v-725z" />
+    <glyph glyph-name="J" unicode="J" horiz-adv-x="504" 
+d="M85 178q2 -25 10 -49t22 -42.5t34.5 -30t49.5 -11.5q43 0 72 14t45.5 38t23.5 57t7 71v497h65v-497q0 -51 -13 -95t-39 -76.5t-66 -50.5t-95 -18q-42 0 -75 15.5t-56.5 41.5t-37 61.5t-17.5 74.5h70z" />
+    <glyph glyph-name="K" unicode="K" horiz-adv-x="610" 
+d="M169 0h-64v722h64v-343l303 343h82l-308 -346l359 -376h-89l-347 377v-377z" />
+    <glyph glyph-name="L" unicode="L" horiz-adv-x="528" 
+d="M528 0h-428v722h65v-662h363v-60z" />
+    <glyph glyph-name="M" unicode="M" horiz-adv-x="951" 
+d="M165 0h-65v722h104l271 -629l277 629h99v-722h-65v667h-2l-276 -667h-65l-276 664h-2v-664z" />
+    <glyph glyph-name="N" unicode="N" horiz-adv-x="719" 
+d="M90 0v722h74l398 -635h2v635h65v-722h-82l-390 624h-2v-624h-65z" />
+    <glyph glyph-name="O" unicode="O" horiz-adv-x="774" 
+d="M387 677q-68 0 -116 -27.5t-78 -72.5t-44 -102t-14 -117q0 -59 14.5 -115t45.5 -100t78.5 -71t113.5 -27q68 0 116 28t78 73t44 102.5t14 117.5q0 58 -14.5 114t-45.5 100t-78.5 70.5t-113.5 26.5zM387 737q83 0 144 -30t100.5 -81.5t58.5 -120t19 -144.5t-19 -144.5
+t-58.5 -120t-100.5 -81.5t-144 -30q-84 0 -144.5 30t-100 81.5t-58.5 119.5t-19 145q0 76 19 144.5t58.5 120t100 81.5t144.5 30z" />
+    <glyph glyph-name="P" unicode="P" horiz-adv-x="611" 
+d="M165 662v-288h158q37 0 70.5 6.5t58.5 23t39.5 44.5t14.5 70t-14.5 69.5t-39.5 44t-58.5 23.5t-70.5 7h-158zM165 0h-65v722h181q52 0 105 -8t95 -30.5t68.5 -62t26.5 -103.5q0 -56 -19.5 -93t-52.5 -59.5t-75 -33.5t-88 -15t-91.5 -3.5t-84.5 0.5v-314z" />
+    <glyph glyph-name="Q" unicode="Q" horiz-adv-x="810" 
+d="M735 -80q-20 14 -41 30.5t-42.5 30.5t-45.5 23.5t-50 9.5q-21 0 -41.5 -4.5t-40.5 -10t-40 -10t-41 -4.5q-79 0 -140 31.5t-102 83.5t-62.5 120t-21.5 141t21.5 141t62.5 120t102 83.5t140 31.5t139.5 -30t101.5 -81t62 -118t21 -140q0 -43 -7.5 -87t-24 -85.5
+t-41.5 -77.5t-61 -63l2 -2q26 3 50.5 -4t47 -20t44 -28.5t41.5 -28.5zM647 361q0 57 -15.5 113.5t-47 101.5t-79.5 73t-112 28q-65 0 -112.5 -28t-79 -73.5t-47 -102t-15.5 -113.5t15.5 -113.5t47 -101t79.5 -72.5t112 -28q65 0 112.5 28t79 73t47 101.5t15.5 113.5z" />
+    <glyph glyph-name="R" unicode="R" horiz-adv-x="660" 
+d="M165 404h147q39 0 74.5 4t63 18t44 41t16.5 74q0 37 -15 60.5t-39 37t-54.5 18.5t-60.5 5h-176v-258zM165 344v-344h-65v722h204q51 0 100.5 -6t88.5 -25.5t63 -57t24 -100.5q0 -31 -12 -59t-32.5 -49.5t-47.5 -36t-57 -17.5v-2q51 -15 81 -60t47 -101t25 -112.5
+t16 -95.5h-66q-8 44 -14 86.5t-14 80.5t-20.5 70.5t-35 56t-58 37t-88.5 13.5h-139z" />
+    <glyph glyph-name="S" unicode="S" horiz-adv-x="615" 
+d="M555 179q0 -52 -20.5 -89.5t-54.5 -62t-77.5 -36t-89.5 -11.5q-51 0 -96.5 10.5t-80 34.5t-55 63t-21.5 95h70q1 -41 15.5 -69t38.5 -45t56.5 -24.5t70.5 -7.5q36 0 68 7t55.5 23.5t37 43.5t13.5 68q0 49 -30 77t-75.5 46.5t-98.5 34t-98.5 39t-75.5 61t-30 100.5
+q0 47 20 83.5t52 61t73.5 37.5t84.5 13t83 -9t72 -29.5t53 -53.5t27 -80h-70q-1 33 -16.5 55t-39 35.5t-52.5 19t-57 5.5q-31 0 -60 -7t-51 -22.5t-35.5 -40t-13.5 -59.5q0 -51 30 -81t75.5 -50t98.5 -36t98.5 -39.5t75.5 -61t30 -99.5z" />
+    <glyph glyph-name="T" unicode="T" horiz-adv-x="558" 
+d="M249 0l-1 662h-233v60h528v-60h-229l-1 -662h-64z" />
+    <glyph glyph-name="U" unicode="U" horiz-adv-x="728" 
+d="M100 722h65v-467q0 -45 13.5 -83.5t39 -66.5t62.5 -44t84 -16t84 16t62.5 44t39 66.5t13.5 83.5v467h65v-468q0 -61 -18 -110.5t-52 -85t-83 -54.5t-111 -19t-111 19t-83 54.5t-52 85t-18 110.5v468z" />
+    <glyph glyph-name="V" unicode="V" horiz-adv-x="619" 
+d="M81 722l232 -657h2l229 657h65l-261 -722h-74l-264 722h71z" />
+    <glyph glyph-name="W" unicode="W" horiz-adv-x="1001" 
+d="M497 655l-207 -655h-92l-188 722h69l168 -657h2l211 657h79l220 -657h2l167 657h63l-190 -722h-89l-213 655h-2z" />
+    <glyph glyph-name="X" unicode="X" horiz-adv-x="611" 
+d="M301 323l-219 -323h-74l257 372l-244 350h85l204 -304l212 304h73l-250 -350l258 -372h-83z" />
+    <glyph glyph-name="Y" unicode="Y" horiz-adv-x="615" 
+d="M276 0v312l-266 410h83l221 -349l221 349h70l-264 -408v-314h-65z" />
+    <glyph glyph-name="Z" unicode="Z" horiz-adv-x="535" 
+d="M517 0h-478v60l401 602h-401v60h478v-60l-402 -602h402v-60z" />
+    <glyph glyph-name="bracketleft" unicode="[" horiz-adv-x="260" 
+d="M222 -46h-137v818h137v-50h-80v-718h80v-50z" />
+    <glyph glyph-name="backslash" unicode="\" horiz-adv-x="445" 
+d="M352 0l-302 722h43l302 -722h-43z" />
+    <glyph glyph-name="bracketright" unicode="]" horiz-adv-x="278" 
+d="M56 772h137v-818h-137v50h80v718h-80v50z" />
+    <glyph glyph-name="asciicircum" unicode="^" horiz-adv-x="626" 
+d="M132 239h-58l230 453h52l231 -453h-57l-201 392z" />
+    <glyph glyph-name="underscore" unicode="_" horiz-adv-x="467" 
+d="M500 -124h-500v56h500v-56z" />
+    <glyph glyph-name="grave" unicode="`" horiz-adv-x="229" 
+d="M20 707h80l91 -143h-44z" />
+    <glyph glyph-name="a" unicode="a" horiz-adv-x="494" 
+d="M101 140q0 -49 29.5 -74.5t72.5 -25.5q27 0 55.5 8t52 23t38.5 36.5t15 48.5v81h-44h-57t-73 -8t-62.5 -28.5t-26.5 -60.5zM36 140q0 42 16 69.5t42.5 44.5t60.5 25t70.5 11t72.5 2.5t66 -0.5q0 37 -2.5 68.5t-15 54t-38.5 35t-72 12.5q-57 0 -90.5 -32.5t-33.5 -82.5
+h-64q0 39 15.5 70.5t41.5 53.5t60 34t72 12q96 0 141.5 -44.5t45.5 -121.5v-351h-59v74h-2q-10 -23 -27.5 -39.5t-39 -27.5t-46 -16.5t-47.5 -5.5q-35 0 -65.5 10.5t-53 30.5t-35.5 49t-13 65z" />
+    <glyph glyph-name="b" unicode="b" horiz-adv-x="551" 
+d="M299 462q-44 0 -76.5 -18t-55 -48t-33.5 -68t-11 -77t11 -77t33.5 -68t55 -48t76.5 -18q39 0 66.5 22t45 54.5t25 70.5t7.5 70q0 33 -7.5 69t-24.5 66.5t-44.5 50t-67.5 19.5zM125 78v-78h-60v722h60v-296h2q26 45 73.5 68t98.5 23q52 0 91 -23.5t65 -61t38.5 -84.5
+t12.5 -93t-12.5 -94t-38 -87t-64.5 -64t-92 -25q-55 0 -98 24t-74 69h-2z" />
+    <glyph glyph-name="c" unicode="c" 
+d="M397 348q-4 62 -42 88t-95 26q-43 0 -71.5 -19.5t-46 -50t-24.5 -67.5t-7 -73q0 -33 8 -70.5t26 -69t46 -52t69 -20.5q27 0 51 8t42.5 23t30 36.5t13.5 49.5h65q-24 -172 -196 -172q-57 0 -98.5 22.5t-68.5 59.5t-40 85t-13 99q0 54 12.5 102.5t39.5 84.5t69.5 57.5
+t102.5 21.5q38 0 72 -11.5t60 -33t42 -53t18 -71.5h-65z" />
+    <glyph glyph-name="d" unicode="d" horiz-adv-x="551" 
+d="M252 40q43 0 76 18.5t54.5 48.5t32.5 68t11 77t-11 76.5t-33 67.5t-54.5 48t-75.5 18q-40 0 -67.5 -20.5t-44.5 -51.5t-24.5 -67.5t-7.5 -69.5t7.5 -70t24.5 -69t44.5 -53t67.5 -21zM426 0v78h-2q-31 -45 -74 -69t-98 -24q-53 0 -92 24.5t-64.5 63.5t-38 87t-12.5 94
+q0 47 12 94t37.5 84.5t64.5 61t93 23.5q51 0 98.5 -23t73.5 -68h2v296h60v-722h-60z" />
+    <glyph glyph-name="e" unicode="e" horiz-adv-x="568" 
+d="M115 237q1 -41 9.5 -77t28 -62.5t50.5 -42t77 -15.5q27 0 52 9t45.5 25t35.5 38t21 48h63q-22 -85 -75 -130t-142 -45q-55 0 -98.5 21t-73.5 56.5t-45.5 82.5t-15.5 99q0 49 15 98t44 88t71 63t97 24q64 0 108 -22.5t71 -60.5t38.5 -89t11.5 -108h-388zM438 292
+q-3 36 -14 67.5t-31 54t-49.5 35.5t-68.5 13q-36 0 -64.5 -14t-48 -37.5t-31 -54.5t-14.5 -64h321z" />
+    <glyph glyph-name="f" unicode="f" horiz-adv-x="333" 
+d="M183 0h-60v447h-101v55h101q0 39 -2 80.5t6.5 75t34.5 55t82 21.5q14 0 28.5 -3.5t28.5 -7.5l-12 -49q-30 7 -50.5 6.5t-33 -10.5t-17.5 -30t-5 -51v-87h106v-55h-106v-447z" />
+    <glyph glyph-name="g" unicode="g" horiz-adv-x="551" 
+d="M259 57q42 0 73.5 17t52.5 45t31.5 64t10.5 74q0 37 -10.5 73.5t-31.5 66t-52.5 47.5t-73.5 18q-40 0 -68 -19.5t-46 -50t-26.5 -67t-8.5 -69.5q0 -34 8 -69.5t26 -64t46.5 -47t68.5 -18.5zM426 424v78h60v-455q0 -119 -52 -179t-175 -60q-32 0 -66 6.5t-62 22t-46 41.5
+t-18 65h65q3 -24 15.5 -39.5t30.5 -24.5t39 -12.5t42 -3.5q32 0 62 8.5t53.5 25.5t37.5 43.5t14 61.5v94h-2q-23 -45 -69.5 -69.5t-95.5 -24.5q-52 0 -92.5 21.5t-68 57.5t-42 81.5t-14.5 93.5t14 94.5t41.5 83.5t68 60t93.5 23q52 0 94 -25.5t71 -67.5h2z" />
+    <glyph glyph-name="h" unicode="h" horiz-adv-x="556" 
+d="M140 722v-296h2q52 91 157 91q49 0 83 -16.5t55 -45.5t30 -68t9 -83v-304h-60v304q0 29 -5.5 57.5t-19 51t-36 36t-56.5 13.5q-72 0 -115.5 -42.5t-43.5 -115.5v-304h-60v722h60z" />
+    <glyph glyph-name="i" unicode="i" horiz-adv-x="220" 
+d="M80 502h60v-502h-60v502zM145 722v-75h-70v75h70z" />
+    <glyph glyph-name="j" unicode="j" horiz-adv-x="223" 
+d="M43 -137q14 0 22 4.5t11.5 12t4.5 17.5t1 22l-1 583h61v-568q0 -32 -2 -56t-12 -39.5t-29.5 -23t-55.5 -7.5v55zM146 722v-75h-70v75h70z" />
+    <glyph glyph-name="k" unicode="k" 
+d="M145 722v-446l257 226h77l-259 -230l265 -272h-76l-264 272v-272h-60v722h60z" />
+    <glyph glyph-name="l" unicode="l" horiz-adv-x="220" 
+d="M140 722v-722h-60v722h60z" />
+    <glyph glyph-name="m" unicode="m" horiz-adv-x="887" 
+d="M135 502v-76h2q26 46 68 68.5t94 22.5q55 0 97.5 -28.5t60.5 -81.5q29 46 75.5 78t102.5 32q55 0 89 -18.5t52.5 -49.5t24.5 -73t6 -88v-288h-60v288q0 36 -4 67.5t-16 55.5t-34 37.5t-58 13.5q-57 0 -89.5 -21t-49 -49.5t-21 -58t-4.5 -45.5v-288h-60v288q0 36 -4 68
+t-16 55.5t-34 37t-58 13.5q-57 0 -89.5 -21t-49 -49.5t-21 -58t-4.5 -45.5v-288h-60v502h60z" />
+    <glyph glyph-name="n" unicode="n" horiz-adv-x="551" 
+d="M294 462q-73 0 -116 -46t-43 -127v-289h-60v502h60v-76h2q52 91 157 91q53 0 87.5 -18.5t54.5 -50t27.5 -72.5t7.5 -87v-289h-60v289q0 33 -3.5 64t-15.5 55t-35 39t-63 15z" />
+    <glyph glyph-name="o" unicode="o" horiz-adv-x="560" 
+d="M111 251q0 -38 10.5 -75.5t31.5 -67.5t52.5 -49t74.5 -19q42 0 74 18.5t53 48.5t31.5 67.5t10.5 75.5q0 37 -10.5 75t-31.5 68.5t-53 49.5t-74 19q-43 0 -75 -18.5t-53 -48.5t-31 -68t-10 -76zM45 251q0 52 15.5 100.5t45.5 85t73.5 58.5t99.5 22q57 0 101 -22t74 -58.5
+t45.5 -85t15.5 -101.5t-16 -101t-46 -84.5t-74 -58t-100 -21.5t-99.5 22t-73.5 58.5t-45.5 84.5t-15.5 101z" />
+    <glyph glyph-name="p" unicode="p" horiz-adv-x="550" 
+d="M287 462q-55 0 -92 -31.5t-55.5 -78t-18.5 -101.5t18.5 -101.5t55.5 -78t92 -31.5q38 0 67 18.5t48.5 48.5t29 68t9.5 78q0 39 -9.5 76.5t-29 67t-48.5 47.5t-67 18zM125 78v-270h-60v694h60v-76h2q52 91 160 91q53 0 94 -22t68.5 -59t41.5 -84t14 -97t-14.5 -98.5
+t-42 -87t-68.5 -61.5t-93 -23q-54 0 -91 22.5t-69 70.5h-2z" />
+    <glyph glyph-name="q" unicode="q" horiz-adv-x="551" 
+d="M255 40q42 0 74.5 18t54 48t33 68t11.5 78t-11.5 77.5t-33 67t-54 47.5t-74.5 18q-37 0 -64 -18t-45 -47t-27.5 -67t-9.5 -77q-1 -40 8.5 -78t28.5 -68t46.5 -48.5t62.5 -18.5zM486 502v-693h-60v269h-2q-65 -93 -169 -93q-51 0 -90 23t-65.5 61t-40.5 86.5t-14 99.5
+q0 49 13.5 96t40 84t66 59.5t90.5 22.5q52 0 97.5 -23.5t71.5 -65.5h2v74h60z" />
+    <glyph glyph-name="r" unicode="r" horiz-adv-x="330" 
+d="M75 0v502h60v-74h2q16 42 51.5 65.5t80.5 23.5h41v-55q-44 0 -76.5 -7t-54.5 -25t-33 -49t-11 -79v-302h-60z" />
+    <glyph glyph-name="s" unicode="s" 
+d="M63 377q0 39 16.5 65.5t42.5 43t59 24t66 7.5q39 0 72.5 -9.5t58 -29.5t38.5 -52.5t14 -78.5h-65q0 35 -8 57.5t-24 35t-39 17.5t-53 5q-19 0 -39 -4t-36.5 -13.5t-27 -25t-10.5 -38.5q0 -31 23.5 -47t59 -26.5t76.5 -19t76.5 -24t59 -42.5t23.5 -73q0 -43 -19 -74
+t-49 -51t-67.5 -29.5t-75.5 -9.5q-91 0 -140.5 45.5t-48.5 138.5h65q-1 -73 34.5 -101t104.5 -28q23 0 46.5 5.5t42 17.5t30.5 31t12 46q0 36 -23.5 54.5t-59 29t-76.5 18.5t-76.5 22.5t-59 40t-23.5 72.5z" />
+    <glyph glyph-name="t" unicode="t" horiz-adv-x="336" 
+d="M245 -15q-42 0 -66 10t-36 29t-15 46.5t-3 63.5v313h-100v55h100v133h60v-133h121v-55h-121v-313q0 -17 1.5 -34.5t7.5 -32t18.5 -23.5t33.5 -9q15 0 30 3t30 5v-49q-15 -4 -30 -6.5t-31 -2.5z" />
+    <glyph glyph-name="u" unicode="u" horiz-adv-x="551" 
+d="M257 40q33 0 62 8t50.5 25t34 43t12.5 62v324h60v-502h-60v76h-2q-25 -45 -65.5 -68t-91.5 -23q-44 0 -77 14.5t-55.5 40.5t-33.5 61t-11 77v324h60v-324q0 -27 7 -52t21 -44t36.5 -30.5t52.5 -11.5z" />
+    <glyph glyph-name="v" unicode="v" horiz-adv-x="502" 
+d="M15 502h66l173 -436l167 436h66l-199 -502h-68z" />
+    <glyph glyph-name="w" unicode="w" horiz-adv-x="832" 
+d="M81 502l150 -423h2l152 423h71l148 -419h2l145 419h66l-187 -502h-57l-154 433h-2l-157 -433h-61l-184 502h66z" />
+    <glyph glyph-name="x" unicode="x" horiz-adv-x="502" 
+d="M254 300l151 202h63l-180 -243l196 -259h-79l-161 218l-158 -218h-68l2 2l192 255l-185 245h80z" />
+    <glyph glyph-name="y" unicode="y" 
+d="M10 502h73l169 -420l168 420h65l-277 -694h-66l76 192z" />
+    <glyph glyph-name="z" unicode="z" horiz-adv-x="444" 
+d="M40 0v55l293 392h-293v55h364v-55l-291 -392h291v-55h-364z" />
+    <glyph glyph-name="braceleft" unicode="{" horiz-adv-x="290" 
+d="M112 262q35 -22 48 -58t15 -77t-1 -81.5t0.5 -73t19.5 -52t56 -16.5v-50h-50q-44 0 -62 26.5t-22.5 67t-2 88t-0.5 90t-18 72.5t-55 36v55q29 4 44.5 22t22 45.5t6.5 62t-0.5 69.5t1 68t9.5 59t26 41.5t51 15.5h50v-50q-39 2 -54.5 -15.5t-19 -47.5t-0.5 -69t1 -80
+t-15.5 -79.5t-49.5 -68.5z" />
+    <glyph glyph-name="bar" unicode="|" horiz-adv-x="220" 
+d="M80 -230v1000h60v-1000h-60z" />
+    <glyph glyph-name="braceright" unicode="}" horiz-adv-x="280" 
+d="M168 264q-35 25 -48 62t-15.5 77.5t0.5 80.5t-0.5 71.5t-19.5 50t-55 16.5v50h50q32 0 49.5 -15.5t25.5 -41t8.5 -58.5t0 -67.5t0 -68.5t7.5 -61.5t23 -46t46 -22.5v-55q-30 -4 -45.5 -22.5t-22 -46.5t-6.5 -61.5t0.5 -68.5t-0.5 -68t-9 -58.5t-26 -41t-51 -15.5h-50v50
+q39 -3 55 13t19 44t0 65t-1 78t15.5 82.5t49.5 77.5z" />
+    <glyph glyph-name="asciitilde" unicode="~" horiz-adv-x="660" 
+d="M221 319q28 0 57.5 -11.5t58.5 -25t55.5 -24.5t48.5 -11q18 0 33 8t27.5 21t22 28.5t16.5 29.5l40 -34q-11 -20 -25 -39.5t-31 -35t-38 -25t-46 -9.5q-23 0 -54.5 11.5t-62.5 25t-57.5 25t-38.5 11.5q-21 0 -38 -8.5t-31 -22t-25 -30.5t-20 -34l-33 47q12 18 26.5 36.5
+t31.5 33.5t38 24t45 9z" />
+    <glyph glyph-name="emdash" unicode="&#x2014;" horiz-adv-x="1020" 
+d="M958 230h-916v32h916v-32z" />
+    <glyph glyph-name="trademark" unicode="&#x2122;" horiz-adv-x="940" 
+d="M643 411h2l118 311h99v-421h-55v377h-2l-145 -377h-34l-145 377h-2v-377h-55v421h99zM349 682h-132v-381h-55v381h-133v40h320v-40z" />
+    <glyph glyph-name="copyright" unicode="&#xa9;" horiz-adv-x="825" 
+d="M401 -15q-79 0 -147.5 29.5t-119.5 80.5t-80.5 119.5t-29.5 147.5q0 78 29.5 146.5t80.5 119.5t120 80t147 29t146.5 -29.5t119 -80t80 -119t29.5 -146.5t-29 -146.5t-80 -120t-119.5 -81t-146.5 -29.5zM84 362q0 -69 23.5 -127.5t65.5 -100.5t100 -65.5t128 -23.5
+q69 0 127 23.5t100 66t65 100.5t23 127t-23.5 126.5t-65.5 99.5t-99.5 65.5t-126.5 23.5t-127 -23t-100.5 -65t-66 -100t-23.5 -127zM591 294q-3 -34 -19 -61t-40 -46t-54.5 -29t-62.5 -10q-48 0 -86 16.5t-64.5 45.5t-40.5 68.5t-14 86.5t14 86.5t40 68t64.5 44.5t86.5 16
+q33 0 63 -9.5t53.5 -27.5t39 -44.5t20.5 -60.5h-55q-5 21 -16.5 38t-28 29t-36 18.5t-40.5 6.5q-36 0 -63.5 -13t-45.5 -35.5t-27 -52t-9 -63.5q0 -33 9.5 -63.5t27.5 -53.5t45 -37t63 -14q21 0 41.5 7t37 19.5t28 30t14.5 39.5h55z" />
+    <glyph glyph-name="registered" unicode="&#xae;" horiz-adv-x="825" 
+d="M401 -15q-78 0 -147 29.5t-120 80.5t-80.5 120t-29.5 147t29.5 146.5t81 119t120 80t146.5 29.5t146 -29.5t119 -80.5t80.5 -119t29.5 -146t-29.5 -146.5t-80 -120t-119 -81t-146.5 -29.5zM401 45q69 0 127 24t99.5 66t65 100t23.5 127q0 68 -23.5 126t-65.5 100
+t-100 65.5t-126 23.5q-69 0 -127 -23.5t-100 -65t-66 -99.5t-24 -127t23.5 -127.5t65.5 -100.5t100.5 -65.5t127.5 -23.5zM315 340v-191h-55v430h185q31 0 58.5 -5t48 -18.5t32 -36.5t11.5 -59q0 -31 -10 -53t-28 -36.5t-42.5 -21.5t-52.5 -9l128 -191h-62l-121 191h-92z
+M315 390h114q20 0 39 1.5t34 8.5t24 21.5t9 39.5q0 23 -9 36.5t-23.5 20.5t-33 9t-36.5 2h-118v-139z" />
+    <glyph glyph-name="degree" unicode="&#xb0;" horiz-adv-x="391" 
+d="M200 681q29 0 55 -11t45.5 -30.5t30.5 -45.5t11 -55t-11 -55t-30.5 -45.5t-45.5 -31t-55 -11.5q-30 0 -55.5 11t-45 30.5t-30.5 45.5t-11 56t11 55.5t30.5 45t45 30.5t55.5 11zM100 539q0 -43 28.5 -71.5t71.5 -28.5q44 0 72 28t28 72t-29 71.5t-72 27.5t-71 -28t-28 -71
+z" />
+    <glyph glyph-name="Aacute" unicode="&#xc1;" horiz-adv-x="667" 
+d="M453 927l-127 -143h-44l91 143h80zM9 0l286 722h76l288 -722h-71l-77 195h-359l-76 -195h-67zM488 255l-156 407l-159 -407h315z" />
+    <glyph glyph-name="Acircumflex" unicode="&#xc2;" horiz-adv-x="667" 
+d="M298 927h73l99 -145h-47l-89 106l-90 -106h-46zM9 0l286 722h76l288 -722h-71l-77 195h-359l-76 -195h-67zM488 255l-156 407l-159 -407h315z" />
+    <glyph glyph-name="Adieresis" unicode="&#xc4;" horiz-adv-x="667" 
+d="M210 839v88h74v-88h-74zM384 839v88h74v-88h-74zM9 0l286 722h76l288 -722h-71l-77 195h-359l-76 -195h-67zM488 255l-156 407l-159 -407h315z" />
+    <glyph glyph-name="Aring" unicode="&#xc5;" horiz-adv-x="667" 
+d="M173 255h315l-156 407zM76 0h-67l286 722h76l288 -722h-71l-77 195h-359zM389 869q0 21 -17 38t-38 17q-22 0 -38.5 -16.5t-16.5 -38.5q0 -21 17 -38t38 -17q20 0 37.5 17.5t17.5 37.5zM246 869q0 18 7 34t19 28t28 19t34 7t34 -7t28 -19t19 -28t7 -34t-7 -34t-19 -28
+t-28 -19t-34 -7t-34 7t-28 19t-19 28t-7 34z" />
+    <glyph glyph-name="Ccedilla" unicode="&#xc7;" horiz-adv-x="667" 
+d="M255 -133q25 -11 48.5 -18t40.5 -7q20 0 37 8.5t17 34.5q0 19 -15.5 29t-32.5 7h-21l11 67q-75 6 -128 39.5t-86.5 85t-49 116t-15.5 132.5q0 73 18 141t56 120.5t96.5 83.5t140.5 31q44 0 85.5 -10.5t74.5 -33t54.5 -57.5t25.5 -83h-70q-5 33 -20.5 56.5t-38.5 38.5
+t-52 22t-59 7q-68 0 -114 -27.5t-74 -72.5t-40.5 -101.5t-12.5 -114.5t12.5 -114.5t40.5 -101.5t74 -72.5t114 -27.5q31 0 60.5 8t53.5 24.5t41 41t23 56.5h70q-8 -45 -31.5 -80.5t-57 -59.5t-75 -37t-84.5 -13l-9 1l-5 -39q19 2 36 1t30.5 -8t21.5 -21t8 -36
+q0 -18 -8 -30.5t-21.5 -20.5t-30 -11.5t-33.5 -3.5q-30 1 -57 7t-56 15z" />
+    <glyph glyph-name="Egrave" unicode="&#xc8;" horiz-adv-x="611" 
+d="M267 927l91 -143h-44l-127 143h80zM102 0v722h429v-60h-364v-259h350v-60h-350v-283h379v-60h-444z" />
+    <glyph glyph-name="Eacute" unicode="&#xc9;" horiz-adv-x="611" 
+d="M425 927l-127 -143h-44l91 143h80zM102 0v722h429v-60h-364v-259h350v-60h-350v-283h379v-60h-444z" />
+    <glyph glyph-name="Ecircumflex" unicode="&#xca;" horiz-adv-x="611" 
+d="M270 927h73l99 -145h-47l-89 106l-90 -106h-46zM102 0v722h429v-60h-364v-259h350v-60h-350v-283h379v-60h-444z" />
+    <glyph glyph-name="Edieresis" unicode="&#xcb;" horiz-adv-x="611" 
+d="M182 839v88h74v-88h-74zM356 839v88h74v-88h-74zM102 0v722h429v-60h-364v-259h350v-60h-350v-283h379v-60h-444z" />
+    <glyph glyph-name="Igrave" unicode="&#xcc;" horiz-adv-x="278" 
+d="M100 927l91 -143h-44l-127 143h80zM107 0v725h65v-725h-65z" />
+    <glyph glyph-name="Iacute" unicode="&#xcd;" horiz-adv-x="278" 
+d="M258 927l-127 -143h-44l91 143h80zM107 0v725h65v-725h-65z" />
+    <glyph glyph-name="Icircumflex" unicode="&#xce;" horiz-adv-x="278" 
+d="M103 927h73l99 -145h-47l-89 106l-90 -106h-46zM107 0v725h65v-725h-65z" />
+    <glyph glyph-name="Idieresis" unicode="&#xcf;" horiz-adv-x="278" 
+d="M15 839v88h74v-88h-74zM189 839v88h74v-88h-74zM107 0v725h65v-725h-65z" />
+    <glyph glyph-name="Ntilde" unicode="&#xd1;" horiz-adv-x="719" 
+d="M90 0v722h74l398 -635h2v635h65v-722h-82l-390 624h-2v-624h-65zM497 915q-8 -34 -30.5 -59.5t-60.5 -25.5q-13 0 -25 3.5l-24 7t-24.5 7t-25.5 3.5q-16 0 -29 -6t-18 -23h-38q8 35 30 58t60 23q26 0 49.5 -10.5t47.5 -10.5q36 0 50 33h38z" />
+    <glyph glyph-name="Ograve" unicode="&#xd2;" horiz-adv-x="778" 
+d="M350 927l91 -143h-44l-127 143h80zM711 361q0 -84 -21 -153.5t-62 -119t-101 -76.5t-138 -27t-138 27t-101 76.5t-62 119t-21 153.5t21 153.5t62 119t101 76.5t138 27t138 -27t101 -76.5t62 -119t21 -153.5zM137 361q0 -59 14 -116t44.5 -101.5t78 -71.5t115.5 -27
+q67 0 115 27t78.5 71.5t44.5 101.5t14 116t-14 116t-44.5 101.5t-78.5 71.5t-115 27q-68 0 -115.5 -27t-78 -71.5t-44.5 -101.5t-14 -116z" />
+    <glyph glyph-name="Oacute" unicode="&#xd3;" horiz-adv-x="778" 
+d="M387 677q-68 0 -116 -27.5t-78 -72.5t-44 -102t-14 -117q0 -59 14.5 -115t45.5 -100t78.5 -71t113.5 -27q68 0 116 28t78 73t44 102.5t14 117.5q0 58 -14.5 114t-45.5 100t-78.5 70.5t-113.5 26.5zM387 737q83 0 144 -30t100.5 -81.5t58.5 -120t19 -144.5t-19 -144.5
+t-58.5 -120t-100.5 -81.5t-144 -30q-84 0 -144.5 30t-100 81.5t-58.5 119.5t-19 145q0 76 19 144.5t58.5 120t100 81.5t144.5 30zM508 927l-127 -143h-44l91 143h80z" />
+    <glyph glyph-name="Ocircumflex" unicode="&#xd4;" horiz-adv-x="778" 
+d="M387 677q-68 0 -116 -27.5t-78 -72.5t-44 -102t-14 -117q0 -59 14.5 -115t45.5 -100t78.5 -71t113.5 -27q68 0 116 28t78 73t44 102.5t14 117.5q0 58 -14.5 114t-45.5 100t-78.5 70.5t-113.5 26.5zM387 737q83 0 144 -30t100.5 -81.5t58.5 -120t19 -144.5t-19 -144.5
+t-58.5 -120t-100.5 -81.5t-144 -30q-84 0 -144.5 30t-100 81.5t-58.5 119.5t-19 145q0 76 19 144.5t58.5 120t100 81.5t144.5 30zM353 927h73l99 -145h-47l-89 106l-90 -106h-46z" />
+    <glyph glyph-name="Odieresis" unicode="&#xd6;" horiz-adv-x="778" 
+d="M387 677q-68 0 -116 -27.5t-78 -72.5t-44 -102t-14 -117q0 -59 14.5 -115t45.5 -100t78.5 -71t113.5 -27q68 0 116 28t78 73t44 102.5t14 117.5q0 58 -14.5 114t-45.5 100t-78.5 70.5t-113.5 26.5zM387 737q83 0 144 -30t100.5 -81.5t58.5 -120t19 -144.5t-19 -144.5
+t-58.5 -120t-100.5 -81.5t-144 -30q-84 0 -144.5 30t-100 81.5t-58.5 119.5t-19 145q0 76 19 144.5t58.5 120t100 81.5t144.5 30zM265 839v88h74v-88h-74zM439 839v88h74v-88h-74z" />
+    <glyph glyph-name="multiply" unicode="&#xd7;" horiz-adv-x="631" 
+d="M591 9l-23 -23l-237 236l-239 -236l-23 23l238 235l-238 237l23 23l239 -236l237 236l23 -23l-237 -237z" />
+    <glyph glyph-name="Oslash" unicode="&#xd8;" horiz-adv-x="778" 
+d="M209 127q33 -42 80 -62t100 -20q67 0 115 27t78.5 71.5t44.5 101.5t14 116q0 52 -11 103.5t-37 96.5zM83 -15l-29 27l84 94q-41 54 -56 121t-15 134q0 76 19 144.5t58.5 120t100.5 81.5t144 30q64 0 123.5 -22t104.5 -70l82 92l27 -25l-85 -97q39 -55 54.5 -121.5
+t15.5 -132.5q0 -77 -19 -145t-58.5 -119.5t-100.5 -81.5t-144 -30q-64 0 -122.5 21.5t-103.5 69.5zM571 593q-33 42 -80.5 63t-101.5 21q-67 0 -115 -27t-78.5 -71.5t-44.5 -101.5t-14 -116q0 -52 10.5 -104t38.5 -97z" />
+    <glyph glyph-name="Ugrave" unicode="&#xd9;" horiz-adv-x="722" 
+d="M322 927l91 -143h-44l-127 143h80zM162 722v-467q0 -46 13.5 -84.5t39 -66.5t62.5 -43.5t84 -15.5t84 15.5t62.5 43.5t39 66.5t13.5 84.5v467h65v-468q0 -61 -18 -110.5t-52 -85t-83 -54.5t-111 -19t-111 19t-83 54.5t-52 85t-18 110.5v468h65z" />
+    <glyph glyph-name="Uacute" unicode="&#xda;" horiz-adv-x="722" 
+d="M100 722h65v-467q0 -45 13.5 -83.5t39 -66.5t62.5 -44t84 -16t84 16t62.5 44t39 66.5t13.5 83.5v467h65v-468q0 -61 -18 -110.5t-52 -85t-83 -54.5t-111 -19t-111 19t-83 54.5t-52 85t-18 110.5v468zM480 927l-127 -143h-44l91 143h80z" />
+    <glyph glyph-name="Ucircumflex" unicode="&#xdb;" horiz-adv-x="722" 
+d="M325 927h73l99 -145h-47l-89 106l-90 -106h-46zM162 722v-467q0 -46 13.5 -84.5t39 -66.5t62.5 -43.5t84 -15.5t84 15.5t62.5 43.5t39 66.5t13.5 84.5v467h65v-468q0 -61 -18 -110.5t-52 -85t-83 -54.5t-111 -19t-111 19t-83 54.5t-52 85t-18 110.5v468h65z" />
+    <glyph glyph-name="Udieresis" unicode="&#xdc;" horiz-adv-x="722" 
+d="M100 722h65v-467q0 -45 13.5 -83.5t39 -66.5t62.5 -44t84 -16t84 16t62.5 44t39 66.5t13.5 83.5v467h65v-468q0 -61 -18 -110.5t-52 -85t-83 -54.5t-111 -19t-111 19t-83 54.5t-52 85t-18 110.5v468zM237 839v88h74v-88h-74zM411 839v88h74v-88h-74z" />
+    <glyph glyph-name="agrave" unicode="&#xe0;" 
+d="M101 140q0 -49 29.5 -74.5t72.5 -25.5q27 0 55.5 8t52 23t38.5 36.5t15 48.5v81h-44h-57t-73 -8t-62.5 -28.5t-26.5 -60.5zM36 140q0 42 16 69.5t42.5 44.5t60.5 25t70.5 11t72.5 2.5t66 -0.5q0 37 -2.5 68.5t-15 54t-38.5 35t-72 12.5q-57 0 -90.5 -32.5t-33.5 -82.5
+h-64q0 39 15.5 70.5t41.5 53.5t60 34t72 12q96 0 141.5 -44.5t45.5 -121.5v-351h-59v74h-2q-10 -23 -27.5 -39.5t-39 -27.5t-46 -16.5t-47.5 -5.5q-35 0 -65.5 10.5t-53 30.5t-35.5 49t-13 65zM211 707l91 -143h-44l-127 143h80z" />
+    <glyph glyph-name="aacute" unicode="&#xe1;" 
+d="M101 140q0 -49 29.5 -74.5t72.5 -25.5q27 0 55.5 8t52 23t38.5 36.5t15 48.5v81h-44h-57t-73 -8t-62.5 -28.5t-26.5 -60.5zM36 140q0 42 16 69.5t42.5 44.5t60.5 25t70.5 11t72.5 2.5t66 -0.5q0 37 -2.5 68.5t-15 54t-38.5 35t-72 12.5q-57 0 -90.5 -32.5t-33.5 -82.5
+h-64q0 39 15.5 70.5t41.5 53.5t60 34t72 12q96 0 141.5 -44.5t45.5 -121.5v-351h-59v74h-2q-10 -23 -27.5 -39.5t-39 -27.5t-46 -16.5t-47.5 -5.5q-35 0 -65.5 10.5t-53 30.5t-35.5 49t-13 65zM369 707l-127 -143h-44l91 143h80z" />
+    <glyph glyph-name="acircumflex" unicode="&#xe2;" 
+d="M101 140q0 -49 29.5 -74.5t72.5 -25.5q27 0 55.5 8t52 23t38.5 36.5t15 48.5v81h-44h-57t-73 -8t-62.5 -28.5t-26.5 -60.5zM36 140q0 42 16 69.5t42.5 44.5t60.5 25t70.5 11t72.5 2.5t66 -0.5q0 37 -2.5 68.5t-15 54t-38.5 35t-72 12.5q-57 0 -90.5 -32.5t-33.5 -82.5
+h-64q0 39 15.5 70.5t41.5 53.5t60 34t72 12q96 0 141.5 -44.5t45.5 -121.5v-351h-59v74h-2q-10 -23 -27.5 -39.5t-39 -27.5t-46 -16.5t-47.5 -5.5q-35 0 -65.5 10.5t-53 30.5t-35.5 49t-13 65zM214 707h73l99 -145h-47l-89 106l-90 -106h-46z" />
+    <glyph glyph-name="atilde" unicode="&#xe3;" 
+d="M101 140q0 -49 29.5 -74.5t72.5 -25.5q27 0 55.5 8t52 23t38.5 36.5t15 48.5v81h-44h-57t-73 -8t-62.5 -28.5t-26.5 -60.5zM36 140q0 42 16 69.5t42.5 44.5t60.5 25t70.5 11t72.5 2.5t66 -0.5q0 37 -2.5 68.5t-15 54t-38.5 35t-72 12.5q-57 0 -90.5 -32.5t-33.5 -82.5
+h-64q0 39 15.5 70.5t41.5 53.5t60 34t72 12q96 0 141.5 -44.5t45.5 -121.5v-351h-59v74h-2q-10 -23 -27.5 -39.5t-39 -27.5t-46 -16.5t-47.5 -5.5q-35 0 -65.5 10.5t-53 30.5t-35.5 49t-13 65zM126 619v88h74v-88h-74zM300 619v88h74v-88h-74z" />
+    <glyph glyph-name="adieresis" unicode="&#xe4;" 
+d="M101 140q0 -49 29.5 -74.5t72.5 -25.5q27 0 55.5 8t52 23t38.5 36.5t15 48.5v81h-44h-57t-73 -8t-62.5 -28.5t-26.5 -60.5zM36 140q0 42 16 69.5t42.5 44.5t60.5 25t70.5 11t72.5 2.5t66 -0.5q0 37 -2.5 68.5t-15 54t-38.5 35t-72 12.5q-57 0 -90.5 -32.5t-33.5 -82.5
+h-64q0 39 15.5 70.5t41.5 53.5t60 34t72 12q96 0 141.5 -44.5t45.5 -121.5v-351h-59v74h-2q-10 -23 -27.5 -39.5t-39 -27.5t-46 -16.5t-47.5 -5.5q-35 0 -65.5 10.5t-53 30.5t-35.5 49t-13 65zM388 695q-8 -34 -30.5 -59.5t-60.5 -25.5q-11 0 -22 3t-23 8q-11 4 -24.5 7
+t-29.5 3q-15 0 -29 -7t-18 -22h-38q9 41 36.5 64t71.5 16q7 -1 18 -4t22 -7l22 -6t17 -3q17 0 30.5 8.5t19.5 24.5h38z" />
+    <glyph glyph-name="aring" unicode="&#xe5;" 
+d="M101 140q0 -49 29.5 -74.5t72.5 -25.5q27 0 55.5 8t52 23t38.5 36.5t15 48.5v81h-44h-57t-73 -8t-62.5 -28.5t-26.5 -60.5zM36 140q0 42 16 69.5t42.5 44.5t60.5 25t70.5 11t72.5 2.5t66 -0.5q0 37 -2.5 68.5t-15 54t-38.5 35t-72 12.5q-57 0 -90.5 -32.5t-33.5 -82.5
+h-64q0 39 15.5 70.5t41.5 53.5t60 34t72 12q96 0 141.5 -44.5t45.5 -121.5v-351h-59v74h-2q-10 -23 -27.5 -39.5t-39 -27.5t-46 -16.5t-47.5 -5.5q-35 0 -65.5 10.5t-53 30.5t-35.5 49t-13 65zM305 649q0 21 -17 38t-38 17t-38 -16.5t-17 -38.5q0 -20 17 -37.5t38 -17.5
+q20 0 37.5 17.5t17.5 37.5zM162 649q0 18 7 34t19 28t28 19t34 7t34 -7t28 -19t19 -28t7 -34t-7 -34t-19 -28t-28 -19t-34 -7t-34 7t-28 19t-19 28t-7 34z" />
+    <glyph glyph-name="ccedilla" unicode="&#xe7;" 
+d="M394 349q-1 31 -14 57q-11 21 -35.5 39t-71.5 18t-78.5 -21t-50.5 -52t-27 -66t-8 -63t6 -66t23 -72.5t48 -58.5t81 -24t76.5 18t37.5 41q13 26 14 59q6 -1 16 -1h49q0 -30 -11.5 -60.5t-35 -55t-59 -40.5t-83.5 -16v-36q26 2 47 -3q17 -5 32 -18.5t15 -42.5
+q0 -22 -9 -35.5t-24 -21t-34 -9.5t-38 -1q-47 3 -105 20q2 5 4 9q4 9 5 18q16 -8 40 -13t46.5 -5t38.5 8t16 27t-10 26.5t-22 9.5q-14 2 -32 -3v72q-44 5 -76 22t-54 41t-35 52.5t-20.5 57t-9.5 55t-2 45.5q0 16 4.5 41t14.5 53.5t26.5 57.5t41 52.5t57 38t75.5 14.5
+q35 0 62.5 -7t47.5 -19t34.5 -27t24.5 -31q23 -37 29 -83l-67 -1v0z" />
+    <glyph glyph-name="egrave" unicode="&#xe8;" horiz-adv-x="560" 
+d="M116 237q1 -41 9.5 -77t28 -62.5t51 -42t76.5 -15.5q27 0 52 9t45.5 25t35.5 38t21 48h63q-22 -84 -75.5 -129.5t-141.5 -45.5q-55 0 -98.5 21t-73.5 56.5t-45.5 82.5t-15.5 99q0 49 15 98t44 88t71.5 63t96.5 24q64 0 107.5 -22.5t70.5 -61t39 -89t12 -107.5h-388z
+M439 292q-3 36 -14 67.5t-31 54t-49.5 35.5t-68.5 13q-36 0 -64.5 -14t-48 -37.5t-31 -54.5t-14.5 -64h321zM240 707l91 -143h-44l-127 143h80z" />
+    <glyph glyph-name="eacute" unicode="&#xe9;" horiz-adv-x="560" 
+d="M116 237q1 -41 9.5 -77t28 -62.5t51 -42t76.5 -15.5q27 0 52 9t45.5 25t35.5 38t21 48h63q-22 -84 -75.5 -129.5t-141.5 -45.5q-55 0 -98.5 21t-73.5 56.5t-45.5 82.5t-15.5 99q0 49 15 98t44 88t71.5 63t96.5 24q64 0 107.5 -22.5t70.5 -61t39 -89t12 -107.5h-388z
+M439 292q-3 36 -14 67.5t-31 54t-49.5 35.5t-68.5 13q-36 0 -64.5 -14t-48 -37.5t-31 -54.5t-14.5 -64h321zM398 707l-127 -143h-44l91 143h80z" />
+    <glyph glyph-name="ecircumflex" unicode="&#xea;" horiz-adv-x="560" 
+d="M116 237q1 -41 9.5 -77t28 -62.5t51 -42t76.5 -15.5q27 0 52 9t45.5 25t35.5 38t21 48h63q-22 -84 -75.5 -129.5t-141.5 -45.5q-55 0 -98.5 21t-73.5 56.5t-45.5 82.5t-15.5 99q0 49 15 98t44 88t71.5 63t96.5 24q64 0 107.5 -22.5t70.5 -61t39 -89t12 -107.5h-388z
+M439 292q-3 36 -14 67.5t-31 54t-49.5 35.5t-68.5 13q-36 0 -64.5 -14t-48 -37.5t-31 -54.5t-14.5 -64h321zM243 707h73l99 -145h-47l-89 106l-90 -106h-46z" />
+    <glyph glyph-name="edieresis" unicode="&#xeb;" horiz-adv-x="560" 
+d="M116 237q1 -41 9.5 -77t28 -62.5t51 -42t76.5 -15.5q27 0 52 9t45.5 25t35.5 38t21 48h63q-22 -84 -75.5 -129.5t-141.5 -45.5q-55 0 -98.5 21t-73.5 56.5t-45.5 82.5t-15.5 99q0 49 15 98t44 88t71.5 63t96.5 24q64 0 107.5 -22.5t70.5 -61t39 -89t12 -107.5h-388z
+M439 292q-3 36 -14 67.5t-31 54t-49.5 35.5t-68.5 13q-36 0 -64.5 -14t-48 -37.5t-31 -54.5t-14.5 -64h321zM155 619v88h74v-88h-74zM329 619v88h74v-88h-74z" />
+    <glyph glyph-name="igrave" unicode="&#xec;" horiz-adv-x="222" 
+d="M72 707l91 -143h-44l-127 143h80zM81 0v502h60v-502h-60z" />
+    <glyph glyph-name="iacute" unicode="&#xed;" horiz-adv-x="222" 
+d="M230 707l-127 -143h-44l91 143h80zM81 0v502h60v-502h-60z" />
+    <glyph glyph-name="icircumflex" unicode="&#xee;" horiz-adv-x="222" 
+d="M75 707h73l99 -145h-47l-89 106l-90 -106h-46zM81 0v502h60v-502h-60z" />
+    <glyph glyph-name="idieresis" unicode="&#xef;" horiz-adv-x="222" 
+d="M-13 619v88h74v-88h-74zM161 619v88h74v-88h-74zM81 0v502h60v-502h-60z" />
+    <glyph glyph-name="ntilde" unicode="&#xf1;" horiz-adv-x="551" 
+d="M411 695q-8 -34 -30.5 -59.5t-60.5 -25.5q-13 0 -25 3.5l-24 7t-24.5 7t-25.5 3.5q-16 0 -29 -6t-18 -23h-38q8 35 30 58t60 23q26 0 49.5 -10.5t47.5 -10.5q36 0 50 33h38zM294 462q-36 0 -65.5 -14t-50.5 -37.5t-32 -55t-11 -66.5v-289h-60v502h60v-76h2q52 91 157 91
+q53 0 87.5 -18.5t54.5 -50t27.5 -72.5t7.5 -87v-289h-60v289q0 33 -3.5 64t-15.5 55t-35 39t-63 15z" />
+    <glyph glyph-name="ograve" unicode="&#xf2;" horiz-adv-x="556" 
+d="M239 707l91 -143h-44l-127 143h80zM278 517q60 0 104 -23t73 -60.5t43 -85.5t14 -97q0 -50 -14 -97.5t-43 -85t-73 -60.5t-104 -23t-104 23t-73 60.5t-43 85t-14 97.5q0 49 14 97t43 85.5t73 60.5t104 23zM447 251q0 35 -9.5 72.5t-29.5 68t-52.5 50.5t-77.5 20
+t-77.5 -20t-52.5 -50.5t-29.5 -68t-9.5 -72.5t9.5 -72.5t29.5 -68t52.5 -50.5t77.5 -20t77.5 20t52.5 50.5t29.5 68t9.5 72.5z" />
+    <glyph glyph-name="oacute" unicode="&#xf3;" horiz-adv-x="556" 
+d="M397 707l-127 -143h-44l91 143h80zM278 517q60 0 104 -23t73 -60.5t43 -85.5t14 -97q0 -50 -14 -97.5t-43 -85t-73 -60.5t-104 -23t-104 23t-73 60.5t-43 85t-14 97.5q0 49 14 97t43 85.5t73 60.5t104 23zM447 251q0 35 -9.5 72.5t-29.5 68t-52.5 50.5t-77.5 20
+t-77.5 -20t-52.5 -50.5t-29.5 -68t-9.5 -72.5t9.5 -72.5t29.5 -68t52.5 -50.5t77.5 -20t77.5 20t52.5 50.5t29.5 68t9.5 72.5z" />
+    <glyph glyph-name="ocircumflex" unicode="&#xf4;" horiz-adv-x="556" 
+d="M242 707h73l99 -145h-47l-89 106l-90 -106h-46zM278 517q60 0 104 -23t73 -60.5t43 -85.5t14 -97q0 -50 -14 -97.5t-43 -85t-73 -60.5t-104 -23t-104 23t-73 60.5t-43 85t-14 97.5q0 49 14 97t43 85.5t73 60.5t104 23zM447 251q0 35 -9.5 72.5t-29.5 68t-52.5 50.5
+t-77.5 20t-77.5 -20t-52.5 -50.5t-29.5 -68t-9.5 -72.5t9.5 -72.5t29.5 -68t52.5 -50.5t77.5 -20t77.5 20t52.5 50.5t29.5 68t9.5 72.5z" />
+    <glyph glyph-name="otilde" unicode="&#xf5;" horiz-adv-x="556" 
+d="M416 695q-8 -34 -30.5 -59.5t-60.5 -25.5q-11 0 -22 3t-23 8q-11 4 -24.5 7t-29.5 3q-15 0 -29 -7t-18 -22h-38q9 41 36.5 64t71.5 16q7 -1 18 -4t22 -7l22 -6t17 -3q17 0 30.5 8.5t19.5 24.5h38zM278 517q60 0 104 -23t73 -60.5t43 -85.5t14 -97q0 -50 -14 -97.5
+t-43 -85t-73 -60.5t-104 -23t-104 23t-73 60.5t-43 85t-14 97.5q0 49 14 97t43 85.5t73 60.5t104 23zM447 251q0 35 -9.5 72.5t-29.5 68t-52.5 50.5t-77.5 20t-77.5 -20t-52.5 -50.5t-29.5 -68t-9.5 -72.5t9.5 -72.5t29.5 -68t52.5 -50.5t77.5 -20t77.5 20t52.5 50.5
+t29.5 68t9.5 72.5z" />
+    <glyph glyph-name="odieresis" unicode="&#xf6;" horiz-adv-x="556" 
+d="M154 619v88h74v-88h-74zM328 619v88h74v-88h-74zM278 517q60 0 104 -23t73 -60.5t43 -85.5t14 -97q0 -50 -14 -97.5t-43 -85t-73 -60.5t-104 -23t-104 23t-73 60.5t-43 85t-14 97.5q0 49 14 97t43 85.5t73 60.5t104 23zM447 251q0 35 -9.5 72.5t-29.5 68t-52.5 50.5
+t-77.5 20t-77.5 -20t-52.5 -50.5t-29.5 -68t-9.5 -72.5t9.5 -72.5t29.5 -68t52.5 -50.5t77.5 -20t77.5 20t52.5 50.5t29.5 68t9.5 72.5z" />
+    <glyph glyph-name="ugrave" unicode="&#xf9;" horiz-adv-x="556" 
+d="M257 40q33 0 62 8t50.5 25t34 43t12.5 62v324h60v-502h-60v76h-2q-25 -45 -65.5 -68t-91.5 -23q-44 0 -77 14.5t-55.5 40.5t-33.5 61t-11 77v324h60v-324q0 -27 7 -52t21 -44t36.5 -30.5t52.5 -11.5zM239 707l91 -143h-44l-127 143h80z" />
+    <glyph glyph-name="uacute" unicode="&#xfa;" horiz-adv-x="556" 
+d="M257 40q33 0 62 8t50.5 25t34 43t12.5 62v324h60v-502h-60v76h-2q-25 -45 -65.5 -68t-91.5 -23q-44 0 -77 14.5t-55.5 40.5t-33.5 61t-11 77v324h60v-324q0 -27 7 -52t21 -44t36.5 -30.5t52.5 -11.5zM397 707l-127 -143h-44l91 143h80z" />
+    <glyph glyph-name="ucircumflex" unicode="&#xfb;" horiz-adv-x="556" 
+d="M257 40q33 0 62 8t50.5 25t34 43t12.5 62v324h60v-502h-60v76h-2q-25 -45 -65.5 -68t-91.5 -23q-44 0 -77 14.5t-55.5 40.5t-33.5 61t-11 77v324h60v-324q0 -27 7 -52t21 -44t36.5 -30.5t52.5 -11.5zM242 707h73l99 -145h-47l-89 106l-90 -106h-46z" />
+    <glyph glyph-name="udieresis" unicode="&#xfc;" horiz-adv-x="556" 
+d="M257 40q33 0 62 8t50.5 25t34 43t12.5 62v324h60v-502h-60v76h-2q-25 -45 -65.5 -68t-91.5 -23q-44 0 -77 14.5t-55.5 40.5t-33.5 61t-11 77v324h60v-324q0 -27 7 -52t21 -44t36.5 -30.5t52.5 -11.5zM154 619v88h74v-88h-74zM328 619v88h74v-88h-74z" />
+    <glyph glyph-name="dotlessi" unicode="&#x131;" horiz-adv-x="222" 
+d="M81 0v502h60v-502h-60z" />
+    <glyph glyph-name="fraction" unicode="&#x2044;" horiz-adv-x="553" 
+d="M503 707l-405 -707h-48l407 707h46z" />
+    <glyph glyph-name="quotedbl" unicode="&#x22;" horiz-adv-x="281" 
+d="M58 496v226h52v-226h-52zM168 496v226h52v-226h-52z" />
+    <glyph glyph-name="exclam" unicode="!" horiz-adv-x="303" 
+d="M223 171h-56l-13 551h82zM155 86h80v-86h-80v86z" />
+    <hkern u1="B" u2="W" k="55" />
+    <hkern u1="V" u2="A" k="65" />
+    <hkern u1="a" u2="v" k="35" />
+    <hkern u1="e" u2="s" k="20" />
+    <hkern u1="n" u2="&#x27;" k="70" />
+    <hkern u1="z" u2="e" k="25" />
+  </font>
+</defs></svg>
diff --git a/puzzle-draw.cabal b/puzzle-draw.cabal
--- a/puzzle-draw.cabal
+++ b/puzzle-draw.cabal
@@ -1,24 +1,30 @@
 name:                puzzle-draw
-version:             0.1.0.3
+version:             0.1.0.4
 synopsis:            Creating graphics for pencil puzzles.
-description:         puzzle-draw is a library for drawing pencil puzzles
-                     using Diagrams. It aims to provide a utility layer
-                     on top of Diagrams to help with drawing arbitrary
-                     puzzles, as well as supporting several specific
-                     puzzle types directly. In addition, it includes
-                     functionality for parsing puzzle data from a
-                     YAML file format.
+description:         puzzle-draw is a library and tool for drawing pencil
+                     puzzles using Diagrams. It aims to provide a utility
+                     layer on top of Diagrams to help with drawing arbitrary
+                     puzzles, and to support several specific puzzles types
+                     In addition, it includes functionality for parsing
+                     puzzle data from a YAML file format.
 license:             MIT
 license-file:        LICENSE
 author:              Robert Vollmert
 maintainer:          rfvollmert@gmail.com
 category:            Graphics
 build-type:          Simple
-Extra-source-files:  README.md, CHANGES.md
+extra-source-files:  README.md, CHANGES.md
+data-files:          data/fonts/*.svg
 cabal-version:       >=1.8
-Source-repository head
+
+source-repository head
   type:     git
   location: http://github.com/robx/puzzle-draw.git
+
+flag cairo
+  description: Build against Cairo backend
+  default:     False
+
 library
   exposed-modules:     Data.Puzzles.Grid
                        Data.Puzzles.GridShape
@@ -38,33 +44,62 @@
                        Diagrams.Puzzles.PuzzleGrids
                        Diagrams.Puzzles.PuzzleTypes
                        Diagrams.Puzzles.Draw
-  build-depends:       base >= 4.2 && < 4.8,
-                       diagrams-lib >= 1.1 && < 1.2,
-                       parsec >= 3.1 && < 3.2,
-                       yaml >= 0.8.4 && < 0.9,
-                       aeson >= 0.7 && < 0.8,
-                       unordered-containers >= 0.2 && < 0.3,
-                       containers >= 0.5 && < 0.6,
-                       hashable >= 1.2 && < 1.3,
-                       text >= 1.1 && < 1.2,
-                       SVGFonts >= 1.4 && < 1.5,
-                       vector-space >= 0.8
+                       Diagrams.Puzzles.CmdLine
+  -- For access to the font files in the data directory.
+  other-modules:       Paths_puzzle_draw
+  build-depends:       base >= 4.2 && < 5,
+                       diagrams-lib >= 1.2,
+                       parsec >= 3.1,
+                       yaml >= 0.8.4,
+                       aeson >= 0.7,
+                       unordered-containers >= 0.2,
+                       containers >= 0.5,
+                       hashable >= 1.2,
+                       text >= 1.1,
+                       SVGFonts >= 1.4,
+                       vector-space >= 0.8,
+                       mtl >= 2.1,
+                       optparse-applicative >= 0.7,
+                       filepath >= 1.3
+  if flag(cairo)
+    cpp-options: "-DCAIRO"
+    build-depends:     diagrams-cairo >= 1.1
+  else
+    build-depends:     diagrams-svg >= 1.1
   hs-source-dirs:      src
   ghc-options:         -Wall
 
+executable drawpuzzle
+  main-is:          drawpuzzle.hs
+  hs-source-dirs:   src/tools
+  build-depends:       base >= 4.2,
+                       puzzle-draw,
+                       diagrams-lib >= 1.2,
+                       yaml >= 0.8.4,
+                       optparse-applicative >= 0.7,
+                       aeson >= 0.7,
+                       filepath >= 1.3
+  if flag(cairo)
+    cpp-options: "-DCAIRO"
+    build-depends:     diagrams-cairo >= 1.1
+  else
+    build-depends:     diagrams-svg >= 1.1
+  ghc-options:         -Wall
+
 test-suite test
   type:                exitcode-stdio-1.0
   hs-source-dirs:      tests
   main-is:             tests.hs
-  build-depends:       base >= 4.2 && < 4.8,
-                       tasty >= 0.8 && < 0.9,
-                       tasty-hunit >= 0.8 && < 0.9,
-                       yaml >= 0.8.4 && < 0.9,
-                       text >= 1.1 && < 1.2,
-                       deepseq >= 1.3 && < 1.4,
-                       blaze-svg >= 0.3 && < 0.4,
-                       diagrams-lib >= 1.1 && < 1.2,
-                       diagrams-svg >= 1.0 && < 1.1,
-                       bytestring >= 0.10 && < 0.11,
+  build-depends:       base >= 4.2,
+                       tasty >= 0.8,
+                       tasty-hunit >= 0.8,
+                       yaml >= 0.8.4,
+                       text >= 1.1,
+                       deepseq >= 1.3,
+                       containers >= 0.5,
+                       blaze-svg >= 0.3,
+                       diagrams-lib >= 1.2,
+                       diagrams-svg >= 1.1,
+                       bytestring >= 0.10,
                        puzzle-draw
   ghc-options:         -Wall
diff --git a/src/Data/Puzzles/Compose.hs b/src/Data/Puzzles/Compose.hs
--- a/src/Data/Puzzles/Compose.hs
+++ b/src/Data/Puzzles/Compose.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, RankNTypes #-}
+{-# LANGUAGE ConstraintKinds #-}
 
 -- |
 -- Helpers to string together parser and renderer by puzzle type.
@@ -22,6 +23,7 @@
 
 import Text.Puzzles.Puzzle
 import Diagrams.Puzzles.Draw
+import Diagrams.Puzzles.Lib
 import Data.Puzzles.PuzzleTypes
 
 import qualified Text.Puzzles.PuzzleTypes as R
@@ -35,7 +37,7 @@
 
 -- | @handle h t@ composes the parser and renderer for the puzzle
 --   type @t@ with the handler @h@.
-handle :: (Backend b R2, Renderable (Path R2) b) =>
+handle :: Backend' b =>
         PuzzleHandler b a -> PuzzleType -> a
 handle f LITS                 = f R.lits                D.lits
 handle f LITSPlus             = f R.litsplus            D.litsplus
@@ -59,13 +61,15 @@
 handle f Compass              = f R.compass             D.compass
 handle f BoxOf2Or3            = f R.boxof2or3           D.boxof2or3
 handle f AfternoonSkyscrapers = f R.afternoonskyscrapers D.afternoonskyscrapers
-handle f CountNumbers         = f R.countnumbers        D.countnumbers
+handle f MeanderingNumbers    = f R.meanderingnumbers   D.meanderingnumbers
 handle f Tapa                 = f R.tapa                D.tapa
 handle f JapaneseSums         = f R.japanesesums        D.japanesesums
 handle f Coral                = f R.coral               D.coral
 handle f MaximalLengths       = f R.maximallengths      D.maximallengths
 handle f PrimePlace           = f R.primeplace          D.primeplace
 handle f Labyrinth            = f R.labyrinth           D.labyrinth
+handle f Bahnhof              = f R.bahnhof             D.bahnhof
+handle f Cave                 = f R.cave                D.cave
 
 -- | Handler that parses a puzzle from a YAML value, and renders.
 drawPuzzle :: PuzzleHandler b (Value -> Parser (Diagram b R2))
@@ -89,7 +93,7 @@
                       -> Parser (Diagram b R2, Maybe (Diagram b R2)))
 drawPuzzleMaybeSol (pp, ps) (dp, ds) (p, s) = do
     p' <- pp p
-    s' <- traverse ps $ s
+    s' <- traverse ps s
     let mps = case s' of Nothing  -> Nothing
                          Just s'' -> Just (p', s'')
     return (dp p', ds <$> mps)
@@ -111,7 +115,7 @@
     return $ ds (p', s')
 
 -- | Like 'drawSolution'', but renders puzzle and solution in example layout.
-drawExample' :: (Backend b R2, Renderable (Path R2) b) =>
+drawExample' :: Backend' b =>
                 PuzzleHandler b ((Value, Maybe Value) -> Parser (Diagram b R2))
 drawExample' (pp, ps) (dp, ds) (p, ms) = do
     p' <- pp p
diff --git a/src/Data/Puzzles/Elements.hs b/src/Data/Puzzles/Elements.hs
--- a/src/Data/Puzzles/Elements.hs
+++ b/src/Data/Puzzles/Elements.hs
@@ -6,6 +6,8 @@
 type Clue a = Maybe a
 
 data MasyuPearl = MWhite | MBlack
+    deriving (Eq, Show)
+
 type MasyuClue = Clue MasyuPearl
 
 type IntClue = Clue Int
@@ -50,14 +52,17 @@
 data Shade = Shade Bool Bool
     deriving Show
 
-data KropkiDot = None | Black | White
+data KropkiDot = KNone | KBlack | KWhite
     deriving Show
 
 newtype TapaClue = TapaClue [Int]
     deriving Show
 
-data JapVal = JapBlack | JapInt Int
-    deriving Show
-
 -- | Diagonal marking for Prime Place: forward diag?, backward diag?
 newtype PrimeDiag = PrimeDiag (Bool, Bool)
+
+data Black = Black
+
+data Crossing = Crossing
+
+type BahnhofClue = Either Int Crossing
diff --git a/src/Data/Puzzles/Grid.hs b/src/Data/Puzzles/Grid.hs
--- a/src/Data/Puzzles/Grid.hs
+++ b/src/Data/Puzzles/Grid.hs
@@ -6,7 +6,7 @@
 
 import Data.Maybe
 import qualified Data.Map as Map
-import Data.Foldable (Foldable, fold)
+import Data.Foldable (Foldable, foldMap)
 import Data.Traversable (Traversable, traverse)
 import Control.Applicative ((<$>))
 import Data.VectorSpace
@@ -21,6 +21,7 @@
             , contents :: Map.Map (Cell s) a} -> Grid s a
 
 deriving instance (Show a, Show s, GridShape s) => Show (Grid s a)
+deriving instance (Eq s, Eq (Cell s), Eq a) => Eq (Grid s a)
 
 -- | Standard square grid.
 type SGrid = Grid Square
@@ -39,12 +40,12 @@
     fmap f (Grid s m) = Grid s (fmap f m)
 
 instance Foldable (Grid s) where
-    fold (Grid _ m) = fold m
+    foldMap f (Grid _ m) = foldMap f m
 
 instance Traversable (Grid s) where
-    traverse f (Grid s m) = Grid s <$> (traverse f m)
+    traverse f (Grid s m) = Grid s <$> traverse f m
 
-filterG :: (a -> Bool) -> (Grid s a) -> (Grid s a)
+filterG :: (a -> Bool) -> Grid s a -> Grid s a
 filterG p (Grid s m) = Grid s (Map.filter p m)
 
 -- | Initialize a square grid from a list of lists. The grid
@@ -63,7 +64,7 @@
 size = GS.size . shape
 
 cells :: GridShape s => Grid s a -> [Cell s]
-cells = GS.cells . shape
+cells = Map.keys . contents
 
 inBounds :: (GridShape s, Eq (Cell s)) => Grid s a -> Cell s -> Bool
 inBounds g c = c `elem` cells g
@@ -77,17 +78,46 @@
 values :: GridShape s => Grid s a -> [(Cell s, a)]
 values (Grid _ m) = Map.toList m
 
--- | The inner edges of a grid that separate unequal cells.
-borders :: Eq a => Grid Square a -> [Edge]
-borders g = [ E p V | p <- vborders ] ++ [ E p H | p <- hborders ]
+edgesGen :: (a -> a -> Bool) -> (a -> Bool) -> Grid Square a -> [Edge]
+edgesGen p n g = [ E pt V | pt <- vedges ] ++ [ E pt H | pt <- hedges ]
   where
-    borders' f (sx, sy) = [ (x + 1, y) | x <- [0 .. sx - 2]
-                                       , y <- [0 .. sy - 1]
-                                       , f (x, y) /= f (x + 1, y) ]
-    vborders = borders' (g !) (size g)
-    hborders = map swap $ borders' ((g !) . swap) (swap . size $ g)
+    edges' f (sx, sy) = [ (x + 1, y) | x <- [-1 .. sx - 1]
+                                     , y <- [-1 .. sy]
+                                     , p' (f (x, y)) (f (x + 1, y)) ]
+
+    vedges = edges' id (size g)
+    hedges = map swap $ edges' swap (swap . size $ g)
     swap (x, y) = (y, x)
+    p' c d = p'' (Map.lookup c (contents g))
+                 (Map.lookup d (contents g))
+    p'' (Just e) (Just f) = p e f
+    p'' (Just e) Nothing  = n e
+    p'' Nothing (Just e)  = n e
+    p'' _        _        = False
 
+edgesP :: (a -> a -> Bool) -> Grid Square a -> [Edge]
+edgesP p g = edgesGen p (const False) g
+
+dualEdgesP :: (a -> a -> Bool) -> Grid Square a -> [Edge]
+dualEdgesP p g = [ E pt H | pt <- hedges ] ++
+                 [ E pt V | pt <- vedges ]
+  where
+    edges' f (sx, sy) = [ (x, y) | x <- [0 .. sx - 2]
+                                 , y <- [0 .. sy - 1]
+                                 , p' (f (x, y)) (f (x + 1, y)) ]
+
+    hedges = edges' id (size g)
+    vedges = map swap $ edges' swap (swap . size $ g)
+    swap (x, y) = (y, x)
+    p' c d = p'' (Map.lookup c (contents g))
+                 (Map.lookup d (contents g))
+    p'' (Just e) (Just f) = p e f
+    p'' _        _        = False
+
+-- | The inner edges of a grid that separate unequal cells.
+borders :: Eq a => Grid Square a -> [Edge]
+borders = edgesP (/=)
+
 -- | Clues along the outside of a square grid.
 data OutsideClues a = OC { left :: [a], right :: [a], bottom :: [a], top :: [a] }
     deriving (Show, Eq)
@@ -96,18 +126,35 @@
     fmap f (OC l r b t) = OC (fmap f l) (fmap f r) (fmap f b) (fmap f t)
 
 outsideSize :: OutsideClues a -> (Int, Int)
-outsideSize (OC l _ _ t) = (length t, length l)
+outsideSize (OC l r b t) = ( max (length t) (length b)
+                           , max (length l) (length r)
+                           )
 
+data OutsideClue a = OClue
+    { ocBase  :: (Int, Int)
+    , ocDir   :: (Int, Int)
+    , ocValue :: a
+    }
+
+instance Functor OutsideClue where
+    fmap f (OClue b d x) = OClue b d (f x)
+
+outsideClueList :: OutsideClues a -> [OutsideClue a]
+outsideClueList o@(OC l r b t) =
+    concat
+       [ zipWith (\ y c -> OClue (0,y)   (-1, 0) c) [0..h-1] l
+       , zipWith (\ y c -> OClue (w-1,y) ( 1, 0) c) [0..h-1] r
+       , zipWith (\ x c -> OClue (x,0)   ( 0,-1) c) [0..w-1] b
+       , zipWith (\ x c -> OClue (x,h-1) ( 0, 1) c) [0..w-1] t
+       ]
+  where
+    (w, h) = outsideSize o
+
 -- | Convert outside clues to association list mapping coordinate to value.
 outsideClues :: OutsideClues (Maybe a) -> [((Int, Int), a)]
-outsideClues o@(OC l r b t) = mapMaybe liftMaybe . concat $
-                             [ zipWith (\ y c -> ((-1, y), c)) [0..h-1] l
-                             , zipWith (\ y c -> (( w, y), c)) [0..h-1] r
-                             , zipWith (\ x c -> (( x,-1), c)) [0..w-1] b
-                             , zipWith (\ x c -> (( x, h), c)) [0..w-1] t
-                             ]
+outsideClues = mapMaybe (liftMaybe . toCell) . outsideClueList
   where
-    (w, h) = outsideSize o
+    toCell (OClue (bx, by) (dx, dy) v) = ((bx + dx, by + dy), v)
     liftMaybe (p, Just x)  = Just (p, x)
     liftMaybe (_, Nothing) = Nothing
 
diff --git a/src/Data/Puzzles/GridShape.hs b/src/Data/Puzzles/GridShape.hs
--- a/src/Data/Puzzles/GridShape.hs
+++ b/src/Data/Puzzles/GridShape.hs
@@ -25,9 +25,9 @@
 --   The bottom-left corner is vertex (0, 0), the bottom-left
 --   cell is cell (0, 0).
 data Square = Square !Int !Int
-    deriving Show
+    deriving (Show, Eq)
 
-squareNeighbours :: [(Int, Int)] -> Square -> (Cell Square) -> [Cell Square]
+squareNeighbours :: [(Int, Int)] -> Square -> Cell Square -> [Cell Square]
 squareNeighbours deltas (Square w h) c = filter inBounds . map (add c) $ deltas
   where
     inBounds (x, y) = x >= 0 && x < w && y >= 0 && y < h
@@ -74,7 +74,7 @@
 
 -- | The edge between two neighbouring cells, with the first cell
 --   on the left.
-orientedEdge :: (Cell Square) -> (Cell Square) -> (Edge' (Vertex Square))
+orientedEdge :: Cell Square -> Cell Square -> Edge' (Vertex Square)
 orientedEdge (x,y) (x',y')
     | x' == x && y' == y+1  = E' (x,y+1) R
     | x' == x && y' == y-1  = E' (x+1,y) L
@@ -97,4 +97,4 @@
         nbrs = [ c ^+^ d | d <- [(-1,0), (0,1), (1,0), (0,-1)] ]
         (ni, no) = partition isc nbrs
         newout = map (orientedEdge c) no
-        newin = map (orientedEdge c) . filter ((>=) c) $ ni
+        newin = map (orientedEdge c) . filter (c >=) $ ni
diff --git a/src/Data/Puzzles/PuzzleTypes.hs b/src/Data/Puzzles/PuzzleTypes.hs
--- a/src/Data/Puzzles/PuzzleTypes.hs
+++ b/src/Data/Puzzles/PuzzleTypes.hs
@@ -4,8 +4,9 @@
 -- List of specific puzzle types.
 
 module Data.Puzzles.PuzzleTypes (
-    PuzzleType(..),
-    lookupType,
+    PuzzleType(..)
+  , lookupType
+  , typeNames
   ) where
 
 import Data.Tuple (swap)
@@ -33,13 +34,15 @@
                 | Compass
                 | BoxOf2Or3
                 | AfternoonSkyscrapers
-                | CountNumbers
+                | MeanderingNumbers
                 | Tapa
                 | JapaneseSums
                 | Coral
                 | MaximalLengths
                 | PrimePlace
                 | Labyrinth
+                | Bahnhof
+                | Cave
     deriving (Show, Eq)
 
 typeNames :: [(PuzzleType, String)]
@@ -65,13 +68,15 @@
             , (Compass, "compass")
             , (BoxOf2Or3, "boxof2or3")
             , (AfternoonSkyscrapers, "afternoonskyscrapers")
-            , (CountNumbers, "countnumbers")
+            , (MeanderingNumbers, "meanderingnumbers")
             , (Tapa, "tapa")
             , (JapaneseSums, "japanesesums")
             , (Coral, "coral")
             , (MaximalLengths, "maximallengths")
             , (PrimePlace, "primeplace")
             , (Labyrinth, "magiclabyrinth")
+            , (Bahnhof, "bahnhof")
+            , (Cave, "cave")
             ]
 
 -- | Look up a puzzle type by name.
diff --git a/src/Data/Puzzles/Pyramid.hs b/src/Data/Puzzles/Pyramid.hs
--- a/src/Data/Puzzles/Pyramid.hs
+++ b/src/Data/Puzzles/Pyramid.hs
@@ -107,23 +107,23 @@
                   let (ks, cs) = unzip kcs in return (ks, c:cs)
 
 pkropki :: GenParser Char st KropkiDot
-pkropki = (char '*' >> return Black)
-          <|> (char 'o' >> return White)
-          <|> (char ' ' >> return None)
+pkropki = (char '*' >> return KBlack)
+          <|> (char 'o' >> return KWhite)
+          <|> (char ' ' >> return KNone)
 
 toParser :: GenParser a () b -> [a] -> Yaml.Parser b
 toParser p v = case parse p "(unknown)" v of Left e  -> fail (show e)
                                              Right x -> pure x
 
 instance FromJSON Pyramid where
-    parseJSON (String t) = Pyr <$> (mapM (toParser prow . T.unpack) $ T.lines t)
+    parseJSON (String t) = Pyr <$> mapM (toParser prow . T.unpack) (T.lines t)
     parseJSON _          = empty
 
 instance FromJSON RowKropkiPyramid where
-    parseJSON (String t) = KP <$> (mapM (toParser pkropkirow . T.unpack) $ T.lines t)
+    parseJSON (String t) = KP <$> mapM (toParser pkropkirow . T.unpack) (T.lines t)
     parseJSON _          = empty
 
 instance FromJSON PyramidSol where
     parseJSON (String t) = PyramidSol <$>
-                           (mapM (toParser pplainrow . T.unpack) $ T.lines t)
+                             mapM (toParser pplainrow . T.unpack) (T.lines t)
     parseJSON _          = empty
diff --git a/src/Diagrams/Puzzles/CmdLine.hs b/src/Diagrams/Puzzles/CmdLine.hs
new file mode 100644
--- /dev/null
+++ b/src/Diagrams/Puzzles/CmdLine.hs
@@ -0,0 +1,67 @@
+{-# LANGUAGE FlexibleContexts, CPP #-}
+
+module Diagrams.Puzzles.CmdLine 
+    ( B
+    , renderToFile
+    , RenderOpts(..)
+    , formats
+    , checkFormat
+    , checkType
+    , exitErr
+    , readPuzzle
+    )
+  where
+
+import Diagrams.Prelude hiding (value, option, (<>), Result)
+
+#ifdef CAIRO
+import Diagrams.Backend.Cairo (B, renderCairo)
+#else
+import Diagrams.Backend.SVG (B, renderSVG)
+#endif
+
+import Text.Puzzles.Puzzle
+import Data.Puzzles.PuzzleTypes
+
+import System.Exit
+
+import Control.Monad (unless)
+import qualified Data.Yaml as Y
+
+data RenderOpts = RenderOpts { _file :: FilePath, _w :: Double }
+
+renderB :: FilePath -> SizeSpec2D -> Diagram B R2 -> IO ()
+renderB =
+#ifdef CAIRO
+    renderCairo
+#else
+    renderSVG
+#endif
+
+renderToFile :: RenderOpts -> Diagram B R2 -> IO ()
+renderToFile ropts = renderB (_file ropts) (Width $ _w ropts)
+
+formats :: [String]
+#ifdef CAIRO
+formats = ["png", "svg", "ps", "pdf"]
+#else
+formats = ["svg"]
+#endif
+
+checkFormat :: String -> IO ()
+checkFormat f = unless (f `elem` formats) $
+                    exitErr $ "unknown format: " ++ f
+
+checkType :: Maybe String -> IO PuzzleType
+checkType mt = do
+    t <- maybe errno return mt
+    maybe (errunk t) return (lookupType t)
+  where
+    errno    = exitErr "no puzzle type given"
+    errunk t = exitErr $ "unknown puzzle type: " ++ t
+
+readPuzzle :: FilePath -> IO (Either Y.ParseException TypedPuzzle)
+readPuzzle = Y.decodeFileEither
+
+exitErr :: String -> IO a
+exitErr e = putStrLn e >> exitFailure
diff --git a/src/Diagrams/Puzzles/Draw.hs b/src/Diagrams/Puzzles/Draw.hs
--- a/src/Diagrams/Puzzles/Draw.hs
+++ b/src/Diagrams/Puzzles/Draw.hs
@@ -1,4 +1,6 @@
-{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ConstraintKinds #-}
 
 module Diagrams.Puzzles.Draw (
     PuzzleSol,
@@ -25,7 +27,7 @@
 
 -- | Optionally render the puzzle, its solution, or a side-by-side
 --   example with puzzle and solution.
-draw :: (Backend b R2, Renderable (Path R2) b) =>
+draw :: Backend' b =>
         PuzzleSol b -> OutputChoice -> Maybe (Diagram b R2)
 draw (p, ms) = fmap (bg white) . d
   where
@@ -49,10 +51,10 @@
     wpix = round (gridresd * w) :: Int  -- grid square size 40px
     wpt = cmtopoint (0.8 * w)     -- grid square size 0.8cm
 
-alignPixel :: (Backend b R2, Renderable (Path R2) b) => Diagram b R2 -> Diagram b R2
+alignPixel :: Backend' b => Diagram b R2 -> Diagram b R2
 alignPixel = scale (1/gridresd) . align' . scale gridresd
   where
-    align' d = maybe id grow (getCorners $ boundingBox d) $ d
+    align' d = maybe id grow (getCorners $ boundingBox d) d
     grow (bl, tr) = mappend $ phantoml (nudge bl False) (nudge tr True)
     nudge p dir = let (px, py) = unp2 p in p2 (nudge' px dir, nudge' py dir)
     nudge' x True  = fromIntegral (ceiling (x - 0.5) :: Int) + 0.5
@@ -60,6 +62,6 @@
     phantoml p q = phantom' $ p ~~ q
 
 -- | Add a phantom border of the given width around a diagram.
-border :: (Backend b R2, Renderable (Path R2) b) => Double -> Diagram b R2 -> Diagram b R2
+border :: Backend' b => Double -> Diagram b R2 -> Diagram b R2
 border w = extrudeEnvelope (w *^ unitX) . extrudeEnvelope (-w *^ unitX)
          . extrudeEnvelope (w *^ unitY) . extrudeEnvelope (-w *^ unitY)
diff --git a/src/Diagrams/Puzzles/Elements.hs b/src/Diagrams/Puzzles/Elements.hs
--- a/src/Diagrams/Puzzles/Elements.hs
+++ b/src/Diagrams/Puzzles/Elements.hs
@@ -1,4 +1,7 @@
-{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE ConstraintKinds #-}
 
 -- | Module: Diagrams.TwoD.Puzzles.Elements
 --
@@ -17,14 +20,14 @@
 import Diagrams.Puzzles.Widths
 import Diagrams.Puzzles.Grid
 
-pearl :: (Renderable (Path R2) b, Backend b R2) =>
+pearl :: Backend' b =>
          MasyuPearl -> Diagram b R2
-pearl m = circle 0.35 # lw 0.05 # fc (c m)
+pearl m = circle 0.35 # lwG 0.05 # fc (c m)
   where
     c MWhite = white
     c MBlack = black
 
-smallPearl :: (Renderable (Path R2) b, Backend b R2) =>
+smallPearl :: Backend' b =>
               MasyuPearl -> Diagram b R2
 smallPearl = scale 0.4 . pearl
 
@@ -41,13 +44,13 @@
 cross = ur <> dr
 
 -- | Draw a cross.
-drawCross :: Renderable (Path R2) b => Diagram b R2
-drawCross = stroke cross # scale 0.8 # lw edgewidth
+drawCross :: Backend' b => Diagram b R2
+drawCross = stroke cross # scale 0.8 # lwG edgewidth
 
 -- | Draw a Compass clue.
-drawCompassClue :: (Renderable (Path R2) b, Backend b R2) =>
+drawCompassClue :: Backend' b =>
                    CompassC -> Diagram b R2
-drawCompassClue (CC n e s w) = texts <> stroke cross # lw onepix
+drawCompassClue (CC n e s w) = texts <> stroke cross # lwG onepix
     where tx Nothing _ = mempty
           tx (Just x) v = text' (show x) # scale 0.5 # translate (r2 v)
           texts = mconcat . zipWith tx [n, e, s, w] $
@@ -56,72 +59,72 @@
 
 -- | Draw a thermometer, given by a list of bottom-left corners
 -- of square cells.
-thermo :: Renderable (Path R2) b => [P2] -> QDiagram b R2 Any
+thermo :: Backend' b => [P2] -> QDiagram b R2 Any
 thermo vs@(v:_) = (bulb `atop` line) # col # translate (r2 (0.5, 0.5))
     where bulb = circle 0.4 # moveTo v
           line = strokeLocLine (fromVertices vs)
-                 # lw 0.55 # lineCap LineCapSquare
+                 # lwG 0.55 # lineCap LineCapSquare
           col = lc gr . fc gr
           gr = blend 0.6 white black
 thermo [] = error "invalid empty thermometer"
 
 -- | Draw a list of thermometers, given as lists of @(Int, Int)@ cell
 -- coordinates.
-drawThermos :: Renderable (Path R2) b => [Thermometer] -> QDiagram b R2 Any
+drawThermos :: Backend' b => [Thermometer] -> QDiagram b R2 Any
 drawThermos = mconcat . map (thermo . map p2i)
 
 -- | @drawTight d t@ draws the tight-fit value @t@, using @d@ to
 -- draw the components.
-drawTight :: (Renderable (Path R2) b, Backend b R2) =>
+drawTight :: Backend' b =>
              (a -> Diagram b R2) -> Tightfit a -> Diagram b R2
 drawTight d (Single x) = d x
-drawTight d (UR x y) = stroke ur # lw onepix
+drawTight d (UR x y) = stroke ur # lwG onepix
                        <> d x # scale s # translate (r2 (-t,t))
                        <> d y # scale s # translate (r2 (t,-t))
     where t = 1/5
           s = 2/3
-drawTight d (DR x y) = stroke dr # lw onepix
+drawTight d (DR x y) = stroke dr # lwG onepix
                        <> d x # scale s # translate (r2 (-t,-t))
                        <> d y # scale s # translate (r2 (t,t))
     where t = 1/5
           s = 2/3
 
 -- | Stack the given words, left-justified.
-stackWords :: (Backend b R2, Renderable (Path R2) b) => [String] -> QDiagram b R2 Any
+stackWords :: Backend' b => [String] -> QDiagram b R2 Any
 stackWords = vcat' with {_sep = 0.1} . scale 0.8 . map (alignL . text')
 
 -- | Mark a word in a grid of letters.
-drawMarkedWord :: (Renderable (Path R2) b) => MarkedWord -> QDiagram b R2 Any
-drawMarkedWord (MW s e) = lw onepix . stroke $ expandTrail' with {_expandCap = LineCapRound} 0.4 t
+drawMarkedWord :: Backend' b => MarkedWord -> QDiagram b R2 Any
+drawMarkedWord (MW s e) = lwG onepix . stroke $ expandTrail' with {_expandCap = LineCapRound} 0.4 t
     where t = fromVertices [p2i s, p2i e] # translate (r2 (1/2,1/2))
 
 -- | Apply 'drawMarkedWord' to a list of words.
-drawMarkedWords :: (Renderable (Path R2) b) => [MarkedWord] -> QDiagram b R2 Any
+drawMarkedWords :: Backend' b => [MarkedWord] -> QDiagram b R2 Any
 drawMarkedWords = mconcat . map drawMarkedWord
 
 -- | Draw a slalom clue.
-drawSlalomClue :: (Show a, Renderable (Path R2) b, Backend b R2) =>
+drawSlalomClue :: (Show a, Backend' b) =>
                   a -> Diagram b R2
 drawSlalomClue x = text' (show x) # scale 0.75
-                   <> circle 0.4 # fc white # lw onepix
+                   <> circle 0.4 # fc white # lwG onepix
 
 -- | Draw text. Shouldn't be more than two characters or so to fit a cell.
-drawText :: (Backend b R2, Renderable (Path R2) b) => String -> QDiagram b R2 Any
+drawText :: Backend' b => String -> QDiagram b R2 Any
 drawText = text'
 
 -- | Draw an @Int@.
-drawInt :: (Renderable (Path R2) b, Backend b R2) =>
+drawInt :: Backend' b =>
            Int -> Diagram b R2
 drawInt s = drawText (show s)
 
 -- | Draw a character.
-drawChar :: (Renderable (Path R2) b, Backend b R2) =>
+drawChar :: Backend' b =>
             Char -> Diagram b R2
 drawChar c = drawText [c]
 
 -- | Stack a list of words into a unit square. Scaled such that at least
 -- three words will fit.
-drawWords :: (Renderable (Path R2) b, Backend b R2) =>
+drawWords :: Backend' b =>
              [String] -> Diagram b R2
 drawWords ws = spread (-1.0 *^ unitY)
                       (map (centerXY . scale 0.4 . drawText) ws)
@@ -129,22 +132,22 @@
 
 -- | Fit a line drawing into a unit square.
 --   For example, a Curve Data clue.
-drawCurve :: Renderable (Path R2) b => [Edge] -> Diagram b R2
-drawCurve = lw onepix . fit 0.6 . centerXY . mconcat . map (stroke . edge)
+drawCurve :: Backend' b => [Edge] -> Diagram b R2
+drawCurve = lwG onepix . fit 0.6 . centerXY . mconcat . map (stroke . edge)
 
 -- | Draw a shadow in the style of Afternoon Skyscrapers.
-drawShade :: Renderable (Path R2) b => Shade -> Diagram b R2
+drawShade :: Backend' b => Shade -> Diagram b R2
 drawShade (Shade s w) = (if s then south else mempty) <>
                         (if w then west else mempty)
   where
     shape = translate (r2 (-1/2, -1/2)) . fromVertices . map p2 $
         [ (0, 0), (1/4, 1/4), (1, 1/4), (1, 0), (0, 0) ]
-    south = strokeLocLoop shape # lw 0 # fc gray
+    south = strokeLocLoop shape # lwG 0 # fc gray
     west = reflectAbout (p2 (0, 0)) (r2 (1, 1)) south
 
 -- | Draws the digits of a tapa clue, ordered
 --   left to right, top to bottom.
-drawTapaClue :: (Backend b R2, Renderable (Path R2) b) =>
+drawTapaClue :: Backend' b =>
                 TapaClue -> Diagram b R2
 drawTapaClue (TapaClue [x]) = drawInt x
 drawTapaClue (TapaClue xs)  = fit 0.8
@@ -159,11 +162,17 @@
     p' 1 = error "singleton clues handled separately"
     p' _ = error "invalid tapa clue"
 
-drawPrimeDiag :: (Backend b R2, Renderable (Path R2) b) =>
+drawPrimeDiag :: Backend' b =>
                  PrimeDiag -> Diagram b R2
-drawPrimeDiag (PrimeDiag d) = stroke p # lw (3 * onepix) # lc (blend 0.5 gray white)
+drawPrimeDiag (PrimeDiag d) = stroke p # lwG (3 * onepix) # lc (blend 0.5 gray white)
   where
     p = case d of (False, False) -> mempty
                   (True,  False) -> ur
                   (False, True)  -> dr
                   (True,  True)  -> ur <> dr
+
+drawCrossing :: Backend' b => Crossing -> Diagram b R2
+drawCrossing = const $ drawChar '+'
+
+drawBahnhofClue :: Backend' b => BahnhofClue -> Diagram b R2
+drawBahnhofClue = either drawInt drawCrossing
diff --git a/src/Diagrams/Puzzles/Grid.hs b/src/Diagrams/Puzzles/Grid.hs
--- a/src/Diagrams/Puzzles/Grid.hs
+++ b/src/Diagrams/Puzzles/Grid.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, TypeFamilies #-}
+{-# LANGUAGE ConstraintKinds #-}
 
 module Diagrams.Puzzles.Grid where
 
@@ -14,11 +15,11 @@
 import Diagrams.Puzzles.Widths
 
 -- | Draw a small black dot with no envelope.
-dot :: (Renderable (Path R2) b, Backend b R2) => Diagram b R2
+dot :: Backend' b => Diagram b R2
 dot = circle 0.05 # fc black # smash
 
 -- | Draw a Slither Link style grid of dots of the specified size.
-slithergrid :: (Backend b R2, Renderable (Path R2) b) =>
+slithergrid :: Backend' b =>
                Size -> Diagram b R2
 slithergrid (x, y) =
     hcatsep . replicate (x + 1) . vcatsep . replicate (y + 1) $ dot
@@ -41,36 +42,39 @@
     fence' n l = fence (map fromIntegral [0..n]) (fromIntegral l)
 
 -- | Draw a frame around the outside of a rectangle.
-outframe :: Renderable (Path R2) b => Size -> Diagram b R2
-outframe (w, h) = strokePointLoop r # lw fw
+outframe' :: Backend' b => Double -> Size -> Diagram b R2
+outframe' f (w, h) = strokePointLoop r # lwG fw
     where wd = fromIntegral w
           hd = fromIntegral h
           strokePointLoop = strokeLocTrail . mapLoc (wrapLoop . closeLine)
                             . fromVertices . map p2
-          fw = framewidthfactor * gridwidth
+          fw = f * gridwidth
           e = fw / 2 - gridwidth / 2
           r = [(-e, -e), (wd + e, -e), (wd + e, hd + e), (-e, hd + e)]
 
+outframe :: Backend' b => Size -> Diagram b R2
+outframe = outframe' framewidthfactor
+
 -- | Draw a square grid, applying the given style to the grid lines.
-grid' :: (Backend b R2, Renderable (Path R2) b) =>
+grid' :: Backend' b =>
          (Diagram b R2 -> Diagram b R2) -> Size -> Diagram b R2
 grid' gridstyle s =
     outframe s
-    <> stroke (gridlines s) # lw gridwidth # gridstyle
+    <> stroke (gridlines s) # lwG gridwidth # gridstyle
 
 -- | Draw a square grid with default grid line style.
-grid :: (Backend b R2, Renderable (Path R2) b) =>
+grid :: Backend' b =>
         Size -> Diagram b R2
 grid = grid' id
 
 -- | Draw a square grid with thin frame.
-plaingrid :: (Backend b R2, Renderable (Path R2) b) =>
+plaingrid :: Backend' b =>
              Size -> Diagram b R2
-plaingrid s = stroke (fullgridlines s) # lw gridwidth
+plaingrid s = stroke (fullgridlines s) # lwG gridwidth
 
-bgdashing :: (Semigroup a, HasStyle a) =>
+bgdashingG :: (Semigroup a, HasStyle a, V a ~ R2) =>
              [Double] -> Double -> Colour Double -> a -> a
-bgdashing ds offs c x = x # dashing ds offs <> x # lc c
+bgdashingG ds offs c x = x # dashingG ds offs <> x # lc c
 
 dashes :: [Double]
 dashes = [5 / 40, 3 / 40]
@@ -78,14 +82,17 @@
 dashoffset :: Double
 dashoffset = 2.5 / 40
 
+gridDashing :: (Semigroup a, HasStyle a, V a ~ R2) => a -> a
+gridDashing = bgdashingG dashes dashoffset white'
+  where
+    white' = blend 0.95 white black
+
 -- | Draw a square grid with dashed grid lines. The gaps
 --   between dashes are off-white to aid in using filling
 --   tools.
-dashedgrid :: (Backend b R2, Renderable (Path R2) b) =>
+dashedgrid :: Backend' b =>
               Size -> Diagram b R2
-dashedgrid = grid' $ bgdashing dashes dashoffset white'
-  where
-    white' = blend 0.95 white black
+dashedgrid = grid' gridDashing
 
 edgePath :: Edge' (Vertex Square) -> Path R2
 edgePath (E' v R) = p2i v ~~ p2i (v ^+^ (1,0))
@@ -96,13 +103,13 @@
 irregularGridPaths :: SGrid a -> (Path R2, Path R2)
 irregularGridPaths (Grid _ m) = (toPath outer, toPath inner)
   where
-    (outer, inner) = edges (M.keysSet m) (flip M.member m)
+    (outer, inner) = edges (M.keysSet m) (`M.member` m)
     toPath = mconcat . map edgePath
 
-irregularGrid :: (Backend b R2, Renderable (Path R2) b) =>
+irregularGrid :: Backend' b =>
                  SGrid a -> Diagram b R2
-irregularGrid g = stroke outer # lw (3 * gridwidth) # lineCap LineCapSquare <>
-                  stroke inner # lw gridwidth
+irregularGrid g = stroke outer # lwG (3 * gridwidth) # lineCap LineCapSquare <>
+                  stroke inner # lwG gridwidth
   where
     (outer, inner) = irregularGridPaths g
 
@@ -133,42 +140,83 @@
 dualEdge :: Edge -> Path R2
 dualEdge = translate (r2 (1/2, 1/2)) . edge
 
-edgeStyle :: HasStyle a => a -> a
-edgeStyle = lineCap LineCapSquare . lw edgewidth
+edgeStyle :: (HasStyle a, V a ~ R2) => a -> a
+edgeStyle = lineCap LineCapSquare . lwG edgewidth
 
-thinEdgeStyle :: HasStyle a => a -> a
-thinEdgeStyle = lineCap LineCapSquare . lw onepix
+thinEdgeStyle :: (HasStyle a, V a ~ R2) => a -> a
+thinEdgeStyle = lineCap LineCapSquare . lwG onepix
 
-drawEdges :: Renderable (Path R2) b => [Edge] -> Diagram b R2
+drawEdges :: Backend' b => [Edge] -> Diagram b R2
 drawEdges = edgeStyle . stroke . mconcat . map edge
 
-drawDualEdges :: Renderable (Path R2) b => [Edge] -> Diagram b R2
+drawDualEdges :: Backend' b => [Edge] -> Diagram b R2
 drawDualEdges = edgeStyle . stroke . mconcat . map dualEdge
 
-drawThinDualEdges :: Renderable (Path R2) b => [Edge] -> Diagram b R2
+drawThinDualEdges :: Backend' b => [Edge] -> Diagram b R2
 drawThinDualEdges = thinEdgeStyle . stroke . mconcat . map dualEdge
 
-drawAreaGrid :: (Backend b R2, Renderable (Path R2) b, Eq a) =>
+drawAreaGrid :: (Backend' b, Eq a) =>
                   SGrid a -> Diagram b R2
 drawAreaGrid = drawEdges . borders <> grid . size
 
-fillBG :: (Backend b R2, Renderable (Path R2) b) => Colour Double -> Diagram b R2
-fillBG c = square 1 # fc c
+fillBG :: Backend' b => Colour Double -> Diagram b R2
+fillBG c = square 1 # lwG onepix # fc c # lc c
 
-shadeGrid :: (Backend b R2, Renderable (Path R2) b) =>
+shadeGrid :: Backend' b =>
               SGrid (Maybe (Colour Double)) -> Diagram b R2
 shadeGrid = mconcat . atCentres' . fmap (maybe mempty fillBG)
 
-drawShadedGrid :: (Backend b R2, Renderable (Path R2) b) =>
+drawShadedGrid :: Backend' b =>
                   SGrid Bool -> Diagram b R2
 drawShadedGrid = shadeGrid . fmap f
   where
     f True  = Just gray
     f False = Nothing
 
-drawAreaGridGray :: (Backend b R2, Renderable (Path R2) b) =>
+drawAreaGridGray :: Backend' b =>
                     SGrid Char -> Diagram b R2
 drawAreaGridGray = drawAreaGrid <> shadeGrid . fmap cols
   where
     cols c | isUpper c  = Just (blend 0.1 black white)
            | otherwise  = Nothing
+
+irregAreaGridX :: Backend' b =>
+                      SGrid Char -> Diagram b R2
+irregAreaGridX = irregularGrid <> drawEdges . borders <> shadeGrid . fmap cols
+  where
+    cols 'X' = Just gray
+    cols _   = Nothing
+
+-- Place a list of diagrams along a ray, with steps of size
+-- @f@.
+distrib :: (Transformable c, Monoid c, V c ~ R2) =>
+           R2 -> (Int, Int) -> Double -> [c] -> c
+distrib base dir f xs =
+    translate (0.75 *^ dir' ^+^ base) . mconcat $
+        zipWith (\i d -> translate (fromIntegral i *^ dir') d) [(0 :: Int)..] xs
+  where
+    dir' = f *^ r2i dir
+
+outsideGen :: (Transformable c, Monoid c, V c ~ R2) =>
+              (OutsideClue [c] -> R2) -> Double -> [OutsideClue [c]] -> c
+outsideGen tobase f ocs = mconcat . map placeOC $ ocs
+  where
+    placeOC o = distrib (tobase o) (ocDir o) f (ocValue o)
+
+outsideCells :: (Transformable c, Monoid c, V c ~ R2) =>
+                Double -> [OutsideClue [c]] -> c
+outsideCells = outsideGen base
+  where
+    base (OClue (bx, by) (dx, dy) _)
+        | dx /= 0   = r2 (fromIntegral bx - 1, fromIntegral by - 1/2)
+        | dy /= 0   = r2 (fromIntegral bx - 1/2, fromIntegral by)
+        | otherwise = error "invalid outside clue"
+
+outsideVertices :: (Transformable c, Monoid c, V c ~ R2) =>
+                   Double -> [OutsideClue [c]] -> c
+outsideVertices = outsideGen base
+  where
+    base (OClue (bx, by) (dx, dy) _)
+        | dx /= 0   = r2 (fromIntegral bx, fromIntegral by)
+        | dy /= 0   = r2 (fromIntegral bx, fromIntegral by)
+        | otherwise = error "invalid outside clue"
diff --git a/src/Diagrams/Puzzles/Lib.hs b/src/Diagrams/Puzzles/Lib.hs
--- a/src/Diagrams/Puzzles/Lib.hs
+++ b/src/Diagrams/Puzzles/Lib.hs
@@ -1,15 +1,20 @@
 {-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, TypeFamilies #-}
+{-# LANGUAGE ConstraintKinds #-}
 
 module Diagrams.Puzzles.Lib where
 
 import Diagrams.Prelude
 
 import Graphics.SVGFonts.ReadFont
-
 import Control.Arrow ((***))
 
+import Paths_puzzle_draw (getDataFileName)
+import System.IO.Unsafe (unsafePerformIO)
+
+type Backend' b = (Backend b R2, Renderable (Path R2) b)
+
 -- | Vertical/horizontal stroked line of given length.
-vline, hline :: Renderable (Path R2) b => Double -> Diagram b R2
+vline, hline :: Backend' b => Double -> Diagram b R2
 vline n = strokeLine . fromVertices . map p2 $ [(0, 0), (0, n)]
 hline n = strokeLine . fromVertices . map p2 $ [(0, 0), (n, 0)]
 
@@ -23,8 +28,8 @@
 vcatsep = cat' (r2 (0,1)) with {_sep = 1}
 
 -- | Collapse the envelope to a point.
-smash :: Backend b R2 => QDiagram b R2 Any -> QDiagram b R2 Any
-smash = withEnvelope (vrule 0 :: D R2)
+smash :: Backend' b => QDiagram b R2 Any -> QDiagram b R2 Any
+smash = withEnvelope (pointDiagram origin :: D R2)
 
 -- | Helper to translate by a point given as @(Int, Int)@.
 translatep :: (Transformable t, V t ~ R2) => (Int, Int) -> t -> t
@@ -47,7 +52,7 @@
 interleave (x:xs) ys = x : interleave ys xs
 
 -- | Spread diagrams evenly along the given vector.
-spread :: (Backend b R2) => R2 -> [Diagram b R2] -> Diagram b R2
+spread :: Backend' b => R2 -> [Diagram b R2] -> Diagram b R2
 spread v things = cat v . interleave (repeat (strut vgap)) $ things
     where ds = map (diameter v) things
           gap' = (magnitude v - sum ds) / fromIntegral (length things + 1)
@@ -61,13 +66,13 @@
 
 -- | Place the second diagram to the right of the first, aligning both
 -- vertically. The origin is the origin of the left diagram.
-besidesL :: (Semigroup m, Backend b R2, Monoid m, Renderable (Path R2) b) =>
+besidesL :: (Backend' b, Semigroup m, Monoid m) =>
             QDiagram b R2 m -> QDiagram b R2 m -> QDiagram b R2 m
 besidesL a b = a ||| strutX 0.5 ||| b'
     where b' = b # centerY # translate (dmid a *^ unitY)
 
 -- | Variant of 'besidesL' where the origin is that of the right diagram.
-besidesR :: (Semigroup m, Backend b R2, Monoid m, Renderable (Path R2) b) =>
+besidesR :: (Backend' b, Semigroup m, Monoid m) =>
            QDiagram b R2 m -> QDiagram b R2 m -> QDiagram b R2 m
 besidesR b a =  b' ||| strutX 0.5 ||| a
     where b' = b # centerY # translate (dmid a *^ unitY)
@@ -82,17 +87,20 @@
 -- | Write text that is centered both vertically and horizontally and that
 -- has an envelope. Sized such that single capital characters fit nicely
 -- into a square of size @1@.
-text' :: (Renderable (Path R2) b, Backend b R2) => String -> Diagram b R2
-text' t = stroke (textSVG' $ TextOpts t bit INSIDE_H KERN False 1 1)
-          # lw 0 # fc black # scale 0.8
+text' :: Backend' b => String -> Diagram b R2
+text' t = stroke (textSVG' $ TextOpts t fnt INSIDE_H KERN False 1 1)
+          # lwG 0 # fc black # scale 0.8
+  where
+    fnt = outlMap . unsafePerformIO . getDataFileName
+        $ "data/fonts/gen-light.svg"
 
 -- text' t = text t # fontSize 0.8 # font "Helvetica" # translate (r2 (0.04, -0.07))
 --          <> phantom' (textrect t)
---textrect :: (Renderable (Path R2) b, Backend b R2) => String -> Diagram b R2
+--textrect :: Backend' b => String -> Diagram b R2
 --textrect t = rect (fromIntegral (length t) * 0.4) 0.7 # lc red
---text'' :: (Renderable (Path R2) b, Backend b R2) => String -> Diagram b R2
+--text'' :: Backend' b => String -> Diagram b R2
 --text'' t = text' t `atop` textrect t
 
 -- | Variant of 'phantom' that forces the argument backend type.
-phantom' :: (Backend b R2) => D R2 -> Diagram b R2
+phantom' :: Backend' b => Diagram b R2 -> Diagram b R2
 phantom' = phantom
diff --git a/src/Diagrams/Puzzles/PuzzleGrids.hs b/src/Diagrams/Puzzles/PuzzleGrids.hs
--- a/src/Diagrams/Puzzles/PuzzleGrids.hs
+++ b/src/Diagrams/Puzzles/PuzzleGrids.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE MultiParamTypeClasses     #-}
 {-# LANGUAGE FlexibleContexts          #-}
 {-# LANGUAGE TypeFamilies              #-}
+{-# LANGUAGE ConstraintKinds           #-}
 
 module Diagrams.Puzzles.PuzzleGrids where
 
@@ -15,82 +16,78 @@
 import Diagrams.Puzzles.Widths
 import Diagrams.Puzzles.Elements
 
-drawFillo :: (Backend b R2, Renderable (Path R2) b) =>
-             SGrid (Clue Int) -> Diagram b R2
-drawFillo = drawIntClues <> dashedgrid . size
-
-drawClueGrid :: (Backend b R2, Renderable (Path R2) b) =>
+drawClueGrid :: Backend' b =>
                 SGrid (Clue Char) -> Diagram b R2
 drawClueGrid = atCentres drawChar . clues <> grid . size
 
-drawIntClues :: (Backend b R2, Renderable (Path R2) b) =>
+drawIntClues :: Backend' b =>
                 SGrid (Clue Int) -> Diagram b R2
 drawIntClues = atCentres drawInt . clues
 
-drawInts :: (Backend b R2, Renderable (Path R2) b) =>
+drawInts :: Backend' b =>
             SGrid Int -> Diagram b R2
 drawInts = atCentres drawInt . values
 
-drawIntGrid :: (Backend b R2, Renderable (Path R2) b) =>
+drawIntGrid :: Backend' b =>
                SGrid (Clue Int) -> Diagram b R2
 drawIntGrid = drawIntClues <> grid . size
 
-drawSlitherGrid :: (Backend b R2, Renderable (Path R2) b) =>
+drawSlitherGrid :: Backend' b =>
                    SGrid (Clue Int) -> Diagram b R2
 drawSlitherGrid = atCentres drawInt . clues <> slithergrid . size
 
-drawMasyuGrid :: (Backend b R2, Renderable (Path R2) b) =>
+drawMasyuGrid :: Backend' b =>
                  SGrid MasyuClue -> Diagram b R2
 drawMasyuGrid = atCentres pearl . clues <> grid . size
 
-drawCompassClues :: (Backend b R2, Renderable (Path R2) b) =>
+drawCompassClues :: Backend' b =>
                     SGrid CompassClue -> Diagram b R2
 drawCompassClues = atCentres drawCompassClue . clues
 
-drawCompassGrid :: (Backend b R2, Renderable (Path R2) b) =>
+drawCompassGrid :: Backend' b =>
                    SGrid CompassClue -> Diagram b R2
 drawCompassGrid = drawCompassClues <> grid . size
 
-sudokugrid :: (Backend b R2, Renderable (Path R2) b) =>
+sudokugrid :: Backend' b =>
               SGrid a -> Diagram b R2
 sudokugrid = drawEdges . sudokubordersg  <> grid . size
 
-drawWordsClues :: (Backend b R2, Renderable (Path R2) b) =>
+drawWordsClues :: Backend' b =>
                   SGrid (Clue [String]) -> Diagram b R2
 drawWordsClues = atCentres drawWords . clues
 
-drawTightGrid :: (Backend b R2, Renderable (Path R2) b) =>
+drawTightGrid :: Backend' b =>
                  (t -> Diagram b R2) -> SGrid (Tightfit t) -> Diagram b R2
 drawTightGrid d g = atCentres (drawTight d) (values g)
                     <> grid (size g)
                     <> phantom' (stroke $ p2i (-1,-1) ~~ p2i (sx + 1, sy + 1))
     where (sx, sy) = size g
 
-drawSlalomGrid :: (Backend b R2, Renderable (Path R2) b) =>
+drawSlalomGrid :: Backend' b =>
                   SGrid (Clue Int) -> Diagram b R2
 drawSlalomGrid g = atVertices drawSlalomClue (clues g)
                    <> grid (w-1, h-1)
     where (w, h) = size g
 
-drawSlalomDiags :: (Backend b R2, Renderable (Path R2) b) =>
+drawSlalomDiags :: Backend' b =>
                    SGrid SlalomDiag -> Diagram b R2
 drawSlalomDiags = atCentres diag . clues . fmap Just
-    where diag SlalomForward  = stroke ur # lw edgewidth
-          diag SlalomBackward = stroke dr # lw edgewidth
+    where diag SlalomForward  = stroke ur # lwG edgewidth
+          diag SlalomBackward = stroke dr # lwG edgewidth
 
-drawCrosses ::  (Backend b R2, Renderable (Path R2) b) =>
+drawCrosses ::  Backend' b =>
                  SGrid Bool -> Diagram b R2
 drawCrosses = atCentres (if' drawCross mempty) . values
   where
     if' a b x = if x then a else b
 
 
-outsideGrid :: (Backend b R2, Renderable (Path R2) b) =>
+outsideGrid :: Backend' b =>
                OutsideClues [String] -> Diagram b R2
 outsideGrid = atCentres (scale 0.8 . drawText) . multiOutsideClues
               <> grid . outsideSize
 
-outsideIntGrid :: (Backend b R2, Renderable (Path R2) b) =>
+outsideIntGrid :: Backend' b =>
                   OutsideClues [Int] -> Diagram b R2
 outsideIntGrid = atCentres (scale 0.8 . drawInt) . multiOutsideClues
                  <> grid . outsideSize
diff --git a/src/Diagrams/Puzzles/PuzzleTypes.hs b/src/Diagrams/Puzzles/PuzzleTypes.hs
--- a/src/Diagrams/Puzzles/PuzzleTypes.hs
+++ b/src/Diagrams/Puzzles/PuzzleTypes.hs
@@ -1,13 +1,16 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE ConstraintKinds #-}
 
 module Diagrams.Puzzles.PuzzleTypes (
     lits, litsplus, geradeweg, fillomino, masyu, nurikabe, latintapa,
     sudoku, thermosudoku, pyramid, kpyramid, slither,
     liarslither, tightfitskyscrapers, wordloop, wordsearch,
     curvedata, doubleback, slalom, compass, boxof2or3,
-    afternoonskyscrapers, countnumbers, tapa, japanesesums,
-    coral, maximallengths, primeplace, labyrinth
+    afternoonskyscrapers, meanderingnumbers, tapa, japanesesums,
+    coral, maximallengths, primeplace, labyrinth, bahnhof,
+    cave
   ) where
 
 import Diagrams.Prelude hiding (Loop, coral)
@@ -18,48 +21,49 @@
 import qualified Diagrams.Puzzles.Pyramid as DPyr
 import Diagrams.Puzzles.Elements
 import Diagrams.Puzzles.Lib
+import Diagrams.Puzzles.Widths
 
 import Data.Puzzles.Grid
 import Data.Puzzles.GridShape (Edge)
 import Data.Puzzles.Elements
 import qualified Data.Puzzles.Pyramid as Pyr
 
-lits :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b AreaGrid ShadedGrid
+lits :: Backend' b => RenderPuzzle b AreaGrid ShadedGrid
 lits = (,)
     drawAreaGridGray
     (drawAreaGrid . fst <> drawShadedGrid . snd)
 
-litsplus :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b AreaGrid ShadedGrid
+litsplus :: Backend' b => RenderPuzzle b AreaGrid ShadedGrid
 litsplus = lits
 
-solstyle :: HasStyle a => a -> a
-solstyle = lc (blend 0.8 black white)
+solstyle :: (HasStyle a, V a ~ R2) => a -> a
+solstyle = lc (blend 0.8 black white) . lwG (3 * onepix)
 
-geradeweg :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b IntGrid Loop
+geradeweg :: Backend' b => RenderPuzzle b IntGrid Loop
 geradeweg = (,)
     drawIntGrid
     (drawIntClues . fst
      <> solstyle . drawDualEdges . snd
      <> grid . size . fst)
 
-fillomino :: (Backend b R2, Renderable (Path R2) b) => RenderPuzzle b IntGrid IntGrid
+fillomino :: Backend' b => RenderPuzzle b IntGrid (SGrid Int)
 fillomino = (,)
-    drawFillo
-    (drawFillo . snd)
+    (atCentres drawInt . clues <> dashedgrid . size)
+    ((atCentres drawInt . values <> drawEdges . borders <> dashedgrid . size) . snd)
 
-masyu :: (Backend b R2, Renderable (Path R2) b) =>
+masyu :: Backend' b =>
          RenderPuzzle b (SGrid (Clue MasyuPearl)) Loop
 masyu = (,)
     drawMasyuGrid
     (solstyle . drawDualEdges . snd <> drawMasyuGrid . fst)
 
-nurikabe :: (Backend b R2, Renderable (Path R2) b) =>
+nurikabe :: Backend' b =>
             RenderPuzzle b IntGrid ShadedGrid
 nurikabe = (,)
     drawIntGrid
     (drawIntGrid . fst <> drawShadedGrid . snd)
 
-latintapa :: (Backend b R2, Renderable (Path R2) b) =>
+latintapa :: Backend' b =>
              RenderPuzzle b (SGrid (Clue [String])) CharClueGrid
 latintapa = (,)
     l
@@ -67,19 +71,19 @@
   where
     l = grid . size <> drawWordsClues
 
-sudoku :: (Backend b R2, Renderable (Path R2) b) =>
+sudoku :: Backend' b =>
           RenderPuzzle b IntGrid IntGrid
 sudoku = (,)
     (drawIntClues <> sudokugrid)
     ((drawIntClues <> sudokugrid) . snd)
 
-thermosudoku :: (Backend b R2, Renderable (Path R2) b) =>
+thermosudoku :: Backend' b =>
                 RenderPuzzle b (SGrid Int, [Thermometer]) IntGrid
 thermosudoku = (,)
     (drawInts . fst <> sudokugrid . fst <> drawThermos . snd)
     (drawIntClues . snd <> sudokugrid . snd <> drawThermos . snd . fst)
 
-pyramid :: (Backend b R2, Renderable (Path R2) b) =>
+pyramid :: Backend' b =>
     RenderPuzzle b Pyr.Pyramid Pyr.PyramidSol
 pyramid = (,)
     DPyr.pyramid
@@ -87,7 +91,7 @@
   where
     merge (p, q) = Pyr.mergepyramidsol p q
 
-kpyramid :: (Backend b R2, Renderable (Path R2) b) =>
+kpyramid :: Backend' b =>
     RenderPuzzle b Pyr.RowKropkiPyramid Pyr.PyramidSol
 kpyramid = (,)
     DPyr.kpyramid
@@ -95,13 +99,13 @@
   where
     merge (p, q) = Pyr.mergekpyramidsol p q
 
-slither :: (Backend b R2, Renderable (Path R2) b) =>
+slither :: Backend' b =>
            RenderPuzzle b IntGrid Loop
 slither = (,)
     drawSlitherGrid
     (drawSlitherGrid . fst <> solstyle . drawEdges . snd)
 
-liarslither :: (Backend b R2, Renderable (Path R2) b) =>
+liarslither :: Backend' b =>
                RenderPuzzle b IntGrid (Loop, SGrid Bool)
 liarslither = (,)
     drawSlitherGrid
@@ -109,7 +113,7 @@
      <> drawSlitherGrid . fst
      <> solstyle . drawEdges . fst . snd)
 
-tightfitskyscrapers :: (Backend b R2, Renderable (Path R2) b) =>
+tightfitskyscrapers :: Backend' b =>
                        RenderPuzzle b (OutsideClues (Maybe Int), SGrid (Tightfit ()))
                                       (SGrid (Tightfit Int))
 tightfitskyscrapers = (,)
@@ -118,49 +122,50 @@
     (atCentres drawInt . outsideClues . fst . fst
      <> drawTightGrid drawInt . snd)
 
-wordgrid :: (Backend b R2, Renderable (Path R2) b) =>
+wordgrid :: Backend' b =>
             SGrid (Maybe Char) -> [String] -> Diagram b R2
 wordgrid g ws = stackWords ws `besidesR` drawClueGrid g
 
-wordloop :: (Backend b R2, Renderable (Path R2) b) =>
+wordloop :: Backend' b =>
             RenderPuzzle b (CharClueGrid, [String]) CharClueGrid
 wordloop = (,)
     (uncurry wordgrid)
     (drawClueGrid . snd)
 
-wordsearch :: (Backend b R2, Renderable (Path R2) b) =>
+wordsearch :: Backend' b =>
               RenderPuzzle b (CharClueGrid, [String]) (CharClueGrid, [MarkedWord])
 wordsearch = (,)
     (uncurry wordgrid) 
     (solstyle . drawMarkedWords . snd . snd
      <> drawClueGrid . fst . snd)
 
-curvedata :: (Backend b R2, Renderable (Path R2) b) =>
+curvedata :: Backend' b =>
              RenderPuzzle b (SGrid (Clue [Edge])) [Edge]
 curvedata = (,)
     cd
     ((solstyle . drawDualEdges . snd) <> cd . fst)
-    where cd = (atCentres drawCurve . clues <> grid . size)
+  where
+    cd = atCentres drawCurve . clues <> grid . size
 
-doubleback :: (Backend b R2, Renderable (Path R2) b) =>
+doubleback :: Backend' b =>
               RenderPuzzle b AreaGrid Loop
 doubleback = (,)
     drawAreaGridGray
     (solstyle . drawDualEdges . snd <> drawAreaGridGray . fst)
 
-slalom :: (Backend b R2, Renderable (Path R2) b) =>
+slalom :: Backend' b =>
           RenderPuzzle b IntGrid (SGrid SlalomDiag)
 slalom = (,)
     drawSlalomGrid
     (drawSlalomGrid . fst <> solstyle . drawSlalomDiags . snd)
 
-compass :: (Backend b R2, Renderable (Path R2) b) =>
+compass :: Backend' b =>
            RenderPuzzle b (SGrid (Clue CompassC)) AreaGrid
 compass = (,)
     drawCompassGrid
     (drawCompassClues . fst <> drawAreaGridGray . snd)
 
-boxof2or3 :: (Backend b R2, Renderable (Path R2) b) =>
+boxof2or3 :: Backend' b =>
              RenderPuzzle b (SGrid MasyuPearl, [Edge]) ()
 boxof2or3 = (,)
     (atCentres smallPearl . values . fst
@@ -168,19 +173,19 @@
      <> drawThinDualEdges . snd)
     (error "boxof2or3 solution not implemented")
 
-afternoonskyscrapers :: (Backend b R2, Renderable (Path R2) b) =>
+afternoonskyscrapers :: Backend' b =>
                         RenderPuzzle b (SGrid Shade) IntGrid
 afternoonskyscrapers = (,)
     (grid . size <> atCentres drawShade . values)
     (drawIntGrid . snd <> atCentres drawShade . values . fst)
 
-countnumbers :: (Backend b R2, Renderable (Path R2) b) =>
+meanderingnumbers :: Backend' b =>
                         RenderPuzzle b AreaGrid IntGrid
-countnumbers = (,)
+meanderingnumbers = (,)
     drawAreaGrid
     (drawIntGrid . snd <> drawAreaGrid . fst)
 
-tapa :: (Backend b R2, Renderable (Path R2) b) =>
+tapa :: Backend' b =>
         RenderPuzzle b (SGrid TapaClue) ShadedGrid
 tapa = (,)
     tapaGrid
@@ -188,23 +193,23 @@
   where
     tapaGrid = atCentres drawTapaClue . values <> grid . size
 
-japanesesums :: (Backend b R2, Renderable (Path R2) b) =>
-                RenderPuzzle b (OutsideClues [Int]) (SGrid JapVal)
+japanesesums :: Backend' b =>
+                RenderPuzzle b (OutsideClues [Int]) (SGrid (Either Black Int))
 japanesesums = (,)
     outsideIntGrid
-    (japcells . snd <> outsideIntGrid . fst)
+    (outsideIntGrid . fst <> japcells . snd)
   where
     japcells = atCentres japcell . values
-    japcell JapBlack = fillBG gray
-    japcell (JapInt x) = drawInt x
+    japcell (Left Black) = fillBG gray
+    japcell (Right x) = drawInt x
 
-coral :: (Backend b R2, Renderable (Path R2) b) =>
+coral :: Backend' b =>
           RenderPuzzle b (OutsideClues [String]) ShadedGrid
 coral = (,)
     outsideGrid
-    (drawShadedGrid . snd <> outsideGrid . fst)
+    (outsideGrid . fst <> drawShadedGrid . snd)
 
-maximallengths :: (Backend b R2, Renderable (Path R2) b) =>
+maximallengths :: Backend' b =>
                   RenderPuzzle b (OutsideClues (Maybe Int)) Loop
 maximallengths = (,)
     g
@@ -213,7 +218,7 @@
     g = atCentres drawInt . outsideClues
         <> grid . outsideSize
 
-primeplace :: (Backend b R2, Renderable (Path R2) b) =>
+primeplace :: Backend' b =>
               RenderPuzzle b (SGrid PrimeDiag) (SGrid Int)
 primeplace = (,)
     g
@@ -221,10 +226,32 @@
   where
     g = irregularGrid <> atCentres drawPrimeDiag . values
 
-labyrinth :: (Backend b R2, Renderable (Path R2) b) =>
+labyrinth :: Backend' b =>
              RenderPuzzle b (SGrid (Clue Int), [Edge]) (SGrid (Clue Int))
 labyrinth = (,)
     (atCentres drawInt . clues . fst <> g)
     (atCentres drawInt . clues . snd <> g . fst)
   where
     g = drawEdges . snd <> plaingrid . size . fst
+
+bahnhof :: Backend' b =>
+            RenderPuzzle b (SGrid (Maybe BahnhofClue)) [Edge]
+bahnhof = (,)
+    (atCentres drawBahnhofClue . clues <> grid . size)
+    (atCentres drawBahnhofStation . clues . fst
+     <> solstyle . drawDualEdges . snd
+     <> grid . size . fst)
+  where
+    drawBahnhofStation = either drawInt (const mempty)
+
+cave ::
+    (Backend b R2, Renderable (Path R2) b) =>
+    RenderPuzzle b (SGrid (Clue Int)) ShadedGrid
+cave = (,)
+    g
+    (drawEdges . edgesGen (/=) not . snd
+     <> drawShadedGrid . snd <> fr . fst
+     <> g . fst)
+  where
+    g = gridDashing . plaingrid . size <> atCentres drawInt . clues
+    fr gr = outframe' 8 (size gr) # lc gray
diff --git a/src/Diagrams/Puzzles/Pyramid.hs b/src/Diagrams/Puzzles/Pyramid.hs
--- a/src/Diagrams/Puzzles/Pyramid.hs
+++ b/src/Diagrams/Puzzles/Pyramid.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ConstraintKinds #-}
 
 module Diagrams.Puzzles.Pyramid where
 
@@ -13,35 +14,34 @@
 pgray :: Colour Double
 pgray = blend 0.6 white black
 
-cell :: (Backend b R2, Renderable (Path R2) b) => Bool -> Diagram b R2
-cell s = square 1 # lw onepix # if s then fc pgray else id
+cell :: Backend' b => Bool -> Diagram b R2
+cell s = square 1 # lwG onepix # if s then fc pgray else id
 
-clue :: (Backend b R2, Renderable (Path R2) b) => Maybe Int -> Diagram b R2
+clue :: Backend' b => Maybe Int -> Diagram b R2
 clue Nothing = mempty
 clue (Just c) = text' (show c)
 
-cellc :: (Backend b R2, Renderable (Path R2) b) => Bool -> Maybe Int -> Diagram b R2
+cellc :: Backend' b => Bool -> Maybe Int -> Diagram b R2
 cellc s c = clue c `atop` cell s
 
-row :: (Backend b R2, Renderable (Path R2) b) => Row -> Diagram b R2
+row :: Backend' b => Row -> Diagram b R2
 row (R cs s) = centerX . hcat . map (cellc s) $ cs
 
-pyramid :: (Backend b R2, Renderable (Path R2) b) => Pyramid -> Diagram b R2
+pyramid :: Backend' b => Pyramid -> Diagram b R2
 pyramid = alignBL . vcat . map row . unPyr
 
-kropki :: (Backend b R2, Renderable (Path R2) b) => KropkiDot -> Diagram b R2
-kropki None = mempty
-kropki c = circle 0.1 # lw 0.03 # fc (col c) # smash
-    where col White = white
-          col Black = blend 0.98 black white
-          col None  = error "can't reach"
+kropki :: Backend' b => KropkiDot -> Diagram b R2
+kropki KNone = mempty
+kropki c = circle 0.1 # lwG 0.03 # fc (col c) # smash
+    where col KWhite = white
+          col KBlack = blend 0.98 black white
+          col KNone  = error "can't reach"
 
-krow :: (Backend b R2, Renderable (Path R2) b) => KropkiRow -> Diagram b R2
+krow :: Backend' b => KropkiRow -> Diagram b R2
 krow (KR cs s ks) = ccat dots <> ccat clues
     where ccat = centerX . hcat
           clues = map (cellc s) cs
-          clues' = map (cellc s) cs :: [D R2]
-          dots = interleave (map phantom' clues') (map kropki ks)
+          dots = interleave (map phantom' clues) (map kropki ks)
 
-kpyramid :: (Backend b R2, Renderable (Path R2) b) => RowKropkiPyramid -> Diagram b R2
+kpyramid :: Backend' b => RowKropkiPyramid -> Diagram b R2
 kpyramid = alignBL . vcat . map krow . unKP
diff --git a/src/Text/Puzzles/PuzzleTypes.hs b/src/Text/Puzzles/PuzzleTypes.hs
--- a/src/Text/Puzzles/PuzzleTypes.hs
+++ b/src/Text/Puzzles/PuzzleTypes.hs
@@ -5,14 +5,12 @@
     sudoku, thermosudoku, pyramid, kpyramid, slither,
     liarslither, tightfitskyscrapers, wordloop, wordsearch,
     curvedata, doubleback, slalom, compass, boxof2or3,
-    afternoonskyscrapers, countnumbers, tapa, japanesesums, coral,
-    maximallengths, primeplace, labyrinth
+    afternoonskyscrapers, meanderingnumbers, tapa, japanesesums, coral,
+    maximallengths, primeplace, labyrinth, bahnhof, cave
   ) where
 
-import Prelude hiding (sequence)
-
 import Control.Applicative
-import Control.Monad hiding (sequence)
+import Control.Monad
 
 import Data.Yaml
 
@@ -32,8 +30,8 @@
 geradeweg :: ParsePuzzle (SGrid (Clue Int)) Loop
 geradeweg = (parseClueGrid, parseEdges)
 
-fillomino :: ParsePuzzle IntGrid IntGrid
-fillomino = (parseClueGrid, parseClueGrid)
+fillomino :: ParsePuzzle IntGrid (SGrid Int)
+fillomino = (parseClueGrid, parseExtGrid)
 
 masyu :: ParsePuzzle (SGrid (Clue MasyuPearl)) Loop
 masyu = (parseClueGrid, parseEdges)
@@ -42,7 +40,8 @@
 nurikabe = (parseSpacedClueGrid, parseShadedGrid)
 
 latintapa :: ParsePuzzle (SGrid (Clue [String])) (SGrid (Maybe Char))
-latintapa = ((unRG <$>) . parseJSON, parseClueGrid)
+latintapa = ((unRG <$>) . parseJSON,
+             fmap (fmap (fmap unAlpha)) . parseClueGrid')
 
 sudoku :: ParsePuzzle IntGrid IntGrid
 sudoku = (parseClueGrid, parseClueGrid)
@@ -120,18 +119,20 @@
 afternoonskyscrapers = (parseAfternoonGrid, parseGrid)
 
 -- this should be changed to support clue numbers
-countnumbers :: ParsePuzzle AreaGrid IntGrid
-countnumbers = (parseGrid, parseGrid)
+meanderingnumbers :: ParsePuzzle AreaGrid IntGrid
+meanderingnumbers = (parseGrid, parseGrid)
 
 tapa :: ParsePuzzle (SGrid TapaClue) ShadedGrid
 tapa = (\v -> fmap unParseTapaClue . unRG <$> parseJSON v,
         parseShadedGrid)
 
-japanesesums :: ParsePuzzle (OutsideClues [Int]) (SGrid JapVal)
+japanesesums :: ParsePuzzle (OutsideClues [Int]) (SGrid (Either Black Int))
 japanesesums = (parseMultiOutsideClues, parseGrid)
 
 coral :: ParsePuzzle (OutsideClues [String]) ShadedGrid
-coral = (parseMultiOutsideClues, parseShadedGrid)
+coral = (,)
+    (fmap (fmap (map unIntString)) . parseMultiOutsideClues)
+    parseShadedGrid
 
 maximallengths :: ParsePuzzle (OutsideClues (Maybe Int)) Loop
 maximallengths = (\v -> fmap blankToMaybe <$> parseCharOutside v,
@@ -142,3 +143,9 @@
 
 labyrinth :: ParsePuzzle (SGrid (Clue Int), [Edge]) (SGrid (Clue Int))
 labyrinth = (parseCellEdges, parseClueGrid')
+
+bahnhof :: ParsePuzzle (SGrid (Maybe BahnhofClue)) [Edge]
+bahnhof = (parseClueGrid, parseEdges)
+
+cave :: ParsePuzzle (SGrid (Clue Int)) ShadedGrid
+cave = (parseClueGrid, parseShadedGrid)
diff --git a/src/Text/Puzzles/Util.hs b/src/Text/Puzzles/Util.hs
--- a/src/Text/Puzzles/Util.hs
+++ b/src/Text/Puzzles/Util.hs
@@ -2,18 +2,18 @@
 
 module Text.Puzzles.Util where
 
-import Prelude hiding (sequence)
+import Prelude hiding (mapM)
 
 import Control.Applicative
 import Control.Arrow
-import Control.Monad hiding (sequence)
+import Control.Monad hiding (mapM)
 
 import Data.Hashable
 import Data.Maybe (catMaybes, fromMaybe)
 import qualified Data.Map as Map
 import qualified Data.HashMap.Strict as HMap
-import Data.Traversable (traverse, sequence, sequenceA, Traversable)
-import Data.Foldable (Foldable, fold)
+import Data.Traversable (traverse, sequenceA, mapM, Traversable)
+import Data.Foldable (Foldable, foldMap)
 import Data.Monoid ((<>))
 import Data.List (intersect)
 
@@ -27,9 +27,24 @@
 import Data.Puzzles.GridShape hiding (size)
 import Data.Puzzles.Elements
 
+type Path = [String]
+
+field :: Path -> Value -> Parser Value
+field = field' . map T.pack
+  where
+    field' [] v                = pure v
+    field' (f:fs) (Object v) = v .: f >>= field' fs
+    field' _  _                = empty
+
+parseFrom :: Path -> (Value -> Parser b) -> (Value -> Parser b)
+parseFrom fs p v = field fs v >>= p
+
 class FromChar a where
     parseChar :: Char -> Parser a
 
+failChar :: Char -> String -> Parser a
+failChar c expect = fail $ "got '" ++ [c] ++ "', expected " ++ expect
+
 instance FromChar Char where
     parseChar = pure
 
@@ -52,6 +67,14 @@
         | isAlpha c  = Alpha <$> parseChar c
         | otherwise  = empty
 
+-- | Helper to parse strings from number-formatted YAML fields.
+--   Somewhat dodgy.
+newtype IntString = IntString { unIntString :: String }
+
+instance FromJSON IntString where
+    parseJSON v@(Number _) = IntString . (show :: Int -> String) <$> parseJSON v
+    parseJSON v            = IntString <$> parseJSON v
+
 -- | A rectangle. Each row has length `w`.
 data Rect a = Rect !Int !Int [[a]]
     deriving Show
@@ -66,15 +89,16 @@
         w = maximum . map T.length $ ls
         h = length ls
         filledc = map (T.unpack . T.justifyLeft w ' ') ls
-        filled = sequence . map (mapM parseChar) $ filledc
-    parseJSON _          = empty
+        filled = mapM (mapM parseChar) filledc
+    parseJSON _          = fail "expected string"
 
 data Border a = Border [a] [a] [a] [a]
     deriving Show
 
 -- | This instance might be a lie.
 instance Foldable Border where
-    fold (Border l r b t) = fold l <> fold r <> fold b <> fold t
+    foldMap f (Border l r b t) = foldMap f l <> foldMap f r
+                              <> foldMap f b <> foldMap f t
 
 instance Traversable Border where
     sequenceA (Border l r b t) = Border <$> sequenceA l
@@ -98,8 +122,8 @@
             ls' = map (middle w) . middle h $ ls
         mapM_ ((parseChar :: Char -> Parser Space) . flip ($) ls)
               [head . head, head . last, last . head, last . last]
-        lsparsed <- sequence . map (mapM parseChar) $ ls'
-        bparsed  <- sequenceA . fmap parseChar $ b
+        lsparsed <- mapM (mapM parseChar) ls'
+        bparsed  <- mapM parseChar b
         return $ BorderedRect (w-2) (h-2) lsparsed bparsed
       where
         middle len = take (len - 2) . drop 1
@@ -114,7 +138,7 @@
         w = maximum . map length $ ls
         wmin = minimum . map length $ ls
         h = length ls
-        p = sequence . map (mapM (parseString . T.unpack)) $ ls
+        p = mapM (mapM (parseString . T.unpack)) ls
     parseJSON _          = empty
 
 data Space = Space
@@ -131,11 +155,24 @@
     parseChar '.' = pure Blank
     parseChar _   = empty
 
+parseCharJSON :: FromChar a => Value -> Parser a
+parseCharJSON v = do
+    [c] <- parseJSON v
+    parseChar c
+
+instance FromJSON Blank where
+   parseJSON = parseCharJSON
+
 instance FromChar Blank' where
     parseChar '.' = pure Blank'
     parseChar '-' = pure Blank'
     parseChar _   = empty
 
+instance FromJSON Blank' where
+    parseJSON (String ".") = pure Blank'
+    parseJSON (String "-") = pure Blank'
+    parseJSON _            = empty
+
 instance FromChar Empty where
     parseChar ' ' = pure Empty
     parseChar _   = empty
@@ -153,19 +190,33 @@
 instance FromChar MasyuPearl where
     parseChar '*' = pure MBlack
     parseChar 'o' = pure MWhite
-    parseChar _   = empty
+    parseChar c   = failChar c "'*' or 'o'"
 
 instance FromChar SlalomDiag where
     parseChar '/'  = pure SlalomForward
     parseChar '\\' = pure SlalomBackward
     parseChar _    = empty
 
+instance FromChar Black where
+    parseChar 'X' = pure Black
+    parseChar 'x' = pure Black
+    parseChar _   = empty
+
 instance (FromChar a, FromChar b) => FromChar (Either a b) where
     parseChar c = Left <$> parseChar c <|> Right <$> parseChar c
 
 instance (FromString a, FromString b) => FromString (Either a b) where
     parseString c = Left <$> parseString c <|> Right <$> parseString c
 
+newtype Either' a b = Either' { unEither' :: Either a b }
+
+instance (FromChar a, FromChar b) => FromChar (Either' a b) where
+    parseChar c = Either' <$> parseChar c
+
+instance (FromJSON a, FromJSON b) => FromJSON (Either' a b) where
+    parseJSON v = Either' <$>
+                      (Left <$> parseJSON v <|> Right <$> parseJSON v)
+
 instance FromChar a => FromChar (Maybe a) where
     parseChar = optional . parseChar
 
@@ -211,6 +262,26 @@
 parseGrid :: FromChar a => Value -> Parser (SGrid a)
 parseGrid v = rectToSGrid <$> parseJSON v
 
+parseGridWith :: (Char -> Parser a) -> Value -> Parser (SGrid a)
+parseGridWith pChar v = traverse pChar =<< parseGrid v
+
+parseWithReplacement :: FromChar a =>
+    (Char -> Maybe a) -> Char -> Parser a
+parseWithReplacement replace c = maybe (parseChar c) pure (replace c)
+
+parseCharMap :: FromJSON a => Value -> Parser (Map.Map Char a)
+parseCharMap v = do
+    m <- parseJSON v
+    guard . all (\k -> length k == 1) . Map.keys $ m
+    return $ Map.mapKeys head m
+
+parseExtGrid :: (FromChar a, FromJSON a) => Value -> Parser (SGrid a)
+parseExtGrid v@(String _) = parseGrid v
+parseExtGrid v = do
+    repl <- parseFrom ["replace"] parseCharMap v
+    parseFrom ["grid"] (parseGridWith
+                        (parseWithReplacement (`Map.lookup` repl))) v
+
 parseClueGrid :: FromChar a => Value -> Parser (SGrid (Clue a))
 parseClueGrid v = rectToClueGrid <$> parseJSON v
 
@@ -271,17 +342,17 @@
         return (gn', gc')
     both f (x, y) = (f x, f y)
     halveGrid (Grid (Square w h) m)
-        | odd w && odd h = pure $ (Grid snode mnode, Grid scell mcell)
-        | otherwise      = empty
+        | odd w && odd h = pure (Grid snode (divkeys mnode),
+                                 Grid scell (divkeys mcell))
+        | otherwise      = fail "non-odd grid size"
       where
         w' = (w + 1) `div` 2
         h' = (h + 1) `div` 2
         snode = Square w' h'
         scell = Square (w' - 1) (h' - 1)
-        (mnode, mcell) =
-            both (Map.mapKeys (both (`div` 2))) .
-            Map.partitionWithKey (const . uncurry (&&) . both even) $
-            m
+        mnode = Map.filterWithKey (const . uncurry (&&) . both even) m
+        mcell = Map.filterWithKey (const . uncurry (&&) . both odd)  m
+        divkeys = Map.mapKeys (both (`div` 2))
 
 -- | Parse a grid of edges with values at the nodes.
 --
@@ -306,15 +377,11 @@
 data HalfDirs = HalfDirs {unHalfDirs :: [Dir]}
 
 instance FromChar HalfDirs where
-    parseChar c | c == '└'        = pure . HalfDirs $ [V, H]
-                | c `elem` "│┘"   = pure . HalfDirs $ [V]
-                | c `elem` "─└┌"  = pure . HalfDirs $ [H]
+    parseChar c | c `elem` "└┴├┼" = pure . HalfDirs $ [V, H]
+                | c `elem` "│┘┤"  = pure . HalfDirs $ [V]
+                | c `elem` "─┌┬"  = pure . HalfDirs $ [H]
                 | otherwise       = pure . HalfDirs $ []
 
-instance FromChar JapVal where
-    parseChar c | c `elem` ['x', 'X']  = pure JapBlack
-    parseChar c = JapInt <$> parseChar c
-
 -- parses a string like
 --  ┌┐┌─┐
 --  ││└┐│
@@ -363,8 +430,8 @@
         succs' q = maybe [] (const $ succs q) (Map.lookup q m')
 
 parseThermoGrid :: ThermoRect -> Parser (SGrid Int, [Thermometer])
-parseThermoGrid (Rect w h ls) = (,) (Grid s ints) <$>
-                                (parseThermos $ Grid s alphas)
+parseThermoGrid (Rect w h ls) = (,) (Grid s ints)
+                              <$> parseThermos (Grid s alphas)
   where
     s = Square w h
     (ints, alphas) = partitionEithers . snd . partitionEithers $
@@ -389,8 +456,8 @@
 
 instance FromChar a => FromString (Tightfit a) where
     parseString [c]           = Single <$> parseChar c
-    parseString (c: '/':d:[]) = UR <$> parseChar c <*> parseChar d
-    parseString (c:'\\':d:[]) = DR <$> parseChar c <*> parseChar d
+    parseString [c, '/',d]    = UR <$> parseChar c <*> parseChar d
+    parseString [c,'\\',d]    = DR <$> parseChar c <*> parseChar d
     parseString _             = empty
 
 parseTightIntGrid :: Value -> Parser (SGrid (Tightfit Int))
@@ -436,7 +503,7 @@
 hashmaptomap = Map.fromList . HMap.toList
 
 compose :: (Ord a, Ord b) => Map.Map a b -> Map.Map b c -> Maybe (Map.Map a c)
-compose m1 m2 = sequence . Map.map (flip Map.lookup m2) $ m1
+compose m1 m2 = mapM (`Map.lookup` m2) m1
 
 instance FromJSON a => FromJSON (RefGrid a) where
     parseJSON (Object v) = RefGrid <$> do
@@ -486,6 +553,15 @@
     pfield f = parseLine . fromMaybe [] =<< v .:? f
 parseCharOutside _          = empty
 
+parseOutside :: FromJSON a => Value -> Parser (OutsideClues a)
+parseOutside (Object v) = reorientOutside <$>
+                              (OC <$>
+                               pfield "left" <*> pfield "right" <*>
+                               pfield "bottom" <*> pfield "top" )
+  where
+    pfield f = pure . fromMaybe [] =<< v .:? f
+parseOutside _          = empty
+
 parseMultiOutsideClues :: FromJSON a => Value -> Parser (OutsideClues [a])
 parseMultiOutsideClues (Object v) = rev <$> raw
   where
@@ -501,3 +577,7 @@
     parseChar '\\' = pure $ PrimeDiag (False, True)
     parseChar 'X'  = pure $ PrimeDiag (True,  True)
     parseChar _    = empty
+
+instance FromChar Crossing where
+    parseChar '+' = pure Crossing
+    parseChar _   = fail "expected '+'"
diff --git a/src/tools/drawpuzzle.hs b/src/tools/drawpuzzle.hs
new file mode 100644
--- /dev/null
+++ b/src/tools/drawpuzzle.hs
@@ -0,0 +1,145 @@
+{-# LANGUAGE FlexibleContexts #-}
+
+module Main where
+
+import Diagrams.Prelude hiding (value, option, (<>), Result)
+
+import Diagrams.Puzzles.CmdLine
+
+import Text.Puzzles.Puzzle
+import Data.Puzzles.Compose
+import Data.Puzzles.PuzzleTypes (typeNames)
+import Diagrams.Puzzles.Draw
+
+import Options.Applicative
+import Control.Monad
+import Data.Maybe
+import Data.List (intercalate, sort)
+
+import System.FilePath
+import System.Environment (getProgName)
+
+import qualified Data.Yaml as Y
+
+optListTypes :: Parser (a -> a)
+optListTypes =
+    infoOption
+        (unlines' . sort . map snd $ typeNames)
+        (long "list-types"
+         <> help "List supported puzzle types")
+  where
+    unlines' = intercalate "\n"
+
+data PuzzleOpts = PuzzleOpts
+    { _format   :: String
+    , _type     :: Maybe String
+    , _puzzle   :: Bool
+    , _solution :: Bool
+    , _example  :: Bool
+    , _input    :: FilePath
+    }
+
+puzzleOpts :: Parser PuzzleOpts
+puzzleOpts = PuzzleOpts
+    <$> strOption
+            (long "format" <> short 'f'
+             <> value (head formats)
+             <> metavar "FMT"
+             <> help ("Desired output format by file extension " ++ fmts))
+    <*> (optional . strOption $
+            (long "type" <> short 't'
+             <> metavar "TYPE"
+             <> help "Puzzle type, overriding type in input file"))
+    <*> switch
+            (long "puzzle" <> short 'p'
+             <> help "Render puzzle (to base.ext)")
+    <*> switch
+            (long "solution" <> short 's'
+             <> help "Render solution (to base-sol.ext)")
+    <*> switch
+            (long "example" <> short 'e'
+             <> help "Render example (to base.ext)")
+    <*> argument str
+            (metavar "INPUT"
+             <> help "Puzzle file in .pzl format")
+  where
+    fmts = "(" ++ intercalate ", " formats ++ ")"
+
+cmtopoint :: Double -> Double
+cmtopoint = (* 28.3464567)
+
+outputSuffix :: OutputChoice -> String
+outputSuffix DrawPuzzle = ""
+outputSuffix DrawSolution = "-sol"
+outputSuffix DrawExample = ""
+
+toRenderOpts :: OutputChoice -> Double -> PuzzleOpts -> RenderOpts
+toRenderOpts oc w opts = RenderOpts out w'
+  where
+    f = _format opts
+    u = case f of "png" -> Pixels
+                  _     -> Points
+    w' = toOutputWidth u w
+    base = takeBaseName (_input opts)
+    out = addExtension (base ++ outputSuffix oc) f
+
+renderPuzzle :: PuzzleOpts -> (OutputChoice -> Maybe (Diagram B R2)) ->
+                (OutputChoice, Bool) -> IO ()
+renderPuzzle opts r (oc, req) = do
+    let x = r oc
+    when (req && isNothing x) $
+        exitErr ("failed to render (no solution?): " ++ show oc)
+    when (isJust x) $ do
+        let Just x' = x
+            ropts = toRenderOpts oc (diagramWidth x') opts
+        renderToFile ropts x'
+
+defaultOpts :: Parser a -> IO a
+defaultOpts optsParser = do
+    prog <- getProgName
+    let p = info (helper <*> optListTypes <*> optsParser)
+                (fullDesc
+                 <> progDesc "Command-line diagram generation."
+                 <> header prog)
+    execParser p
+
+checkOutput :: PuzzleOpts -> IO [(OutputChoice, Bool)]
+checkOutput opts
+    | (p || s) && e  = exitErr "example output conflicts with puzzle/solution"
+    | e              = return . map req $ [DrawExample]
+    | p && s         = return . map req $ [DrawPuzzle, DrawSolution]
+    | p              = return . map req $ [DrawPuzzle]
+    | s              = return . map req $ [DrawSolution]
+    | otherwise      = return [req DrawPuzzle, opt DrawSolution]
+  where
+    p = _puzzle opts
+    s = _solution opts
+    e = _example opts
+    req x = (x, True)
+    opt x = (x, False)
+
+maybeSkipSolution :: [(OutputChoice, Bool)] -> Maybe Y.Value -> Maybe Y.Value
+maybeSkipSolution _ Nothing    = Nothing
+maybeSkipSolution ocs (Just v) =
+    if any hasSol . map fst $ ocs
+        then Just v
+        else Nothing
+  where
+    hasSol DrawSolution = True
+    hasSol DrawExample  = True
+    hasSol DrawPuzzle   = False
+
+main :: IO ()
+main = do
+    opts <- defaultOpts puzzleOpts
+    ocs <- checkOutput opts
+    checkFormat (_format opts)
+    mp <- readPuzzle (_input opts)
+    TP mt pv msv <- case mp of Left  e -> exitErr $
+                                          "parse failure: " ++ show e
+                               Right p -> return p
+    let msv' = maybeSkipSolution ocs msv
+    t <- checkType $ _type opts `mplus` mt
+    let ps = Y.parseEither (handle drawPuzzleMaybeSol t) (pv, msv')
+    case ps of Right ps' -> mapM_ (renderPuzzle opts (draw ps')) ocs
+               Left    e -> exitErr $ "parse failure: " ++ e
diff --git a/tests/tests.hs b/tests/tests.hs
--- a/tests/tests.hs
+++ b/tests/tests.hs
@@ -3,16 +3,18 @@
 
 import Data.Yaml
 import Data.List (sort)
-
+import qualified Data.Map as Map
 import Control.DeepSeq
 
 import Text.Puzzles.Puzzle
-import Data.Puzzles.Elements (Thermometer)
-import Text.Puzzles.Util (parseChar, parseMultiOutsideClues)
+import Data.Puzzles.Elements (Thermometer, MasyuPearl(..))
+import Text.Puzzles.Util
+    (parseChar, parseMultiOutsideClues, parseEdgeGrid)
 import Text.Puzzles.PuzzleTypes
 import qualified Data.Puzzles.Grid as Grid
 import Data.Puzzles.Pyramid (PyramidSol(..))
 import Data.Puzzles.Grid (multiOutsideClues, OutsideClues(..))
+import Data.Puzzles.GridShape (Edge(..), Square(..), Dir(..))
 
 import Diagrams.Puzzles.PuzzleGrids
 import Diagrams.Prelude
@@ -78,6 +80,7 @@
     , testParsePzl "thermosudoku" thermosudoku thermo_2
     , testNonparsePzl "thermosudoku" thermosudoku thermo_broken_1
     , testNonparsePzl "thermosudoku" thermosudoku thermo_broken_2
+    , testParsePzl "boxof2or3" boxof2or3 boxof2or3_1
     ]
 
 test_thermo_1 :: [Thermometer]
@@ -117,6 +120,18 @@
     oc = OC [[3], [1, 2]] [[1, 0], []] [[0, 0, 1]] [[1, -1]]
          :: OutsideClues [Int]
 
+test_edge_grid :: Assertion
+test_edge_grid = Right (gn, gc, es) @=? res
+  where
+    res = parseEither parseEdgeGrid edgeGrid_1
+    gn = Grid.Grid (Square 4 2) $ Map.fromList
+        [((0,0),Just MBlack),((0,1),Just MWhite),((1,0),Just MWhite),((1,1),Just MBlack),
+         ((2,0),Nothing),((2,1),Just MBlack),((3,0),Nothing),((3,1),Just MWhite)]
+    gc :: Grid.SGrid Int
+    gc = Grid.Grid (Square 3 1) $ Map.fromList
+        [((0,0),1),((1,0),2),((2,0),3)]
+    es = [E (0,0) H, E (0,1) H, E (1,1) H, E (2,1) H, E (0,0) V, E (1,0) V]
+
 parseDataTests :: TestTree
 parseDataTests = testGroup "Parsing tests (full puzzles, result checks)"
     [ testCase "parse tightfit, correct size" $ test_tightfit_1 @? "error in puzzle"
@@ -129,6 +144,7 @@
                                             , [(0, 2), (1, 3), (2, 4)]
                                             , [(4, 0), (4, 1), (3, 2)] ]
     , testCase "parse multioutsideclues" $ test_multioutside
+    , testCase "parse edge grid" $ test_edge_grid
     ]
 
 -- this used to cause a rendering crash, though it's
