"""Draws the JSON number railroad diagram as an inline SVG into docs/images/. The SVG is not an image: the pages include its text with `--8<-- "images/json_syntax_number.svg"`, so it picks up the colours and fonts of the page (see the `railroad` and `nj-fig` rules in docs/css/brand.css). Run `python3 scripts/figures/railroad_number.py` from docs/mkdocs after changing the drawing, and commit the generated file. In SVG text write the word JSON as `J​SON`, otherwise the abbreviation extension wraps it in an and the text disappears. """ import math from pathlib import Path OUT = Path(__file__).resolve().parents[2] / "docs" / "images" Y0, Y1, Y2, Y3 = 44, 92, 136, 180 R = 10 paths, shapes, texts = [], [], [] def rounded(pts, r=R): d = "M%g %g" % pts[0] for i in range(1, len(pts) - 1): (x0, y0), (x1, y1), (x2, y2) = pts[i-1], pts[i], pts[i+1] l1 = math.hypot(x1-x0, y1-y0); l2 = math.hypot(x2-x1, y2-y1) rr = min(r, l1/2, l2/2) ax, ay = x1 - (x1-x0)/l1*rr, y1 - (y1-y0)/l1*rr bx, by = x1 + (x2-x1)/l2*rr, y1 + (y2-y1)/l2*rr d += " L%g %g Q%g %g %g %g" % (ax, ay, x1, y1, bx, by) d += " L%g %g" % pts[-1] return d def path(*pts): paths.append(rounded(list(pts))) def circle(cx, cy, ch): shapes.append('' % (cx, cy)) shapes.append('' % (cx, cy)) texts.append('%s' % (cx, cy+5, ch)) def box(x0, x1, cy, label): shapes.append('' % (x0, cy-14, x1-x0)) shapes.append('' % (x0, cy-14, x1-x0)) texts.append('%s' % ((x0+x1)/2, cy+5, label)) def loop(xr, xl, cy): # return path below a box row path((xr, cy), (xr, cy+34), (xl, cy+34), (xl, cy)) # start / main path((8, Y0), (850, Y0)) # A: optional minus path((24, Y0), (38, Y0), (38, Y1), (59, Y1)); circle(72, Y1, "−"); path((85, Y1), (106, Y1), (106, Y0), (120, Y0)) # B: 0 | 1-9 digit* circle(257, Y0, "0") path((120, Y0), (134, Y0), (134, Y1), (160, Y1)); box(160, 242, Y1, "digit 1–9") path((242, Y1), (380, Y1), (380, Y0), (396, Y0)) path((242, Y1), (258, Y1), (258, Y2), (278, Y2)); box(278, 332, Y2, "digit") path((332, Y2), (348, Y2), (348, Y1)); loop(340, 270, Y2) # C: fraction path((396, Y0), (410, Y0), (410, Y1), (425, Y1)); circle(438, Y1, ".") path((451, Y1), (462, Y1)); box(462, 516, Y1, "digit"); loop(524, 456, Y1) path((516, Y1), (530, Y1), (530, Y0), (546, Y0)) # D: exponent path((546, Y0), (560, Y0), (560, Y1), (577, Y1)); circle(590, Y1, "e") path((546, Y0), (560, Y0), (560, Y2), (577, Y2)); circle(590, Y2, "E") path((603, Y1), (728, Y1)); path((603, Y2), (620, Y2), (620, Y1)) path((640, Y1), (654, Y1), (654, Y2), (670, Y2)); circle(683, Y2, "−") path((640, Y1), (654, Y1), (654, Y3), (670, Y3)); circle(683, Y3, "+") path((696, Y2), (712, Y2), (712, Y1)); path((696, Y3), (712, Y3), (712, Y1)) path((728, Y1), (746, Y1)); box(746, 800, Y1, "digit"); loop(808, 738, Y1) path((800, Y1), (816, Y1), (816, Y0), (832, Y0)) svg = ['', 'Railroad diagram of the J​SON number syntax: optional minus, then 0 or a digit from 1 to 9 followed by digits, then an optional fraction (a dot and digits) and an optional exponent (e or E, an optional sign, and digits).', ''] for d in paths: svg.append('' % d) svg.append('') svg.append('' % (Y0-6, Y0, Y0+6)) svg.append('' % (Y0-6, Y0, Y0+6)) svg += shapes + texts svg.append('number' % (Y0-12)) svg.append('fraction' % (Y0-12)) svg.append('exponent' % (Y0-12)) svg.append('') open(OUT / "json_syntax_number.svg", "w").write("\n".join(svg) + "\n")