"""Draws the parser-callback and iterator illustrations as an inline SVG into docs/images/. The SVG is not an image: the pages include its text with `--8<-- "images/callback_events.svg, range-begin-end.svg and range-rbegin-rend.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/iterator_callback_figures.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 html from pathlib import Path OUT = Path(__file__).resolve().parents[2] / "docs" / "images" CW = 9.6 # JetBrains Mono advance at 16px def write(name, W, H, cls, tid, title, body, defs=""): s = ['' % (cls, W, H, tid), '%s' % (tid, html.escape(title).replace('J&#8203;SON', 'J​SON'))] if defs: s.append(defs) s += body s.append('') open(OUT / name, "w").write("\n".join(s) + "\n") # ---------- callback events ---------- def callback(): body, y0, lh = [], 74, 28 body.append('J​SON to parse') body.append('callback events') lines = [ (0, [("{", None), (None, "os")]), (2, [('"name"', None), (None, "k"), (': "Berlin"', None), (None, "v"), (",", None)]), (2, [('"location"', None), (None, "k"), (": [", None), (None, "as")]), (4, [("52.519444", None), (None, "v"), (",", None)]), (4, [("13.406667", None), (None, "v")]), (2, [("]", None), (None, "ae"), (None, "v")]), (0, [("}", None), (None, "oe"), (None, "v")]), ] for i, (indent, parts) in enumerate(lines): y = y0 + i * lh x = 16 + indent * CW for text, ev in parts: if text is not None: body.append('%s' % (x, y, html.escape(text))) x += CW * len(text) else: body.append('' % (ev, x + 10, y - 5)) x += 20 if text is None else 0 legend = [("os", "object_start"), ("k", "key"), ("oe", "object_end"), ("as", "array_start"), ("ae", "array_end"), ("v", "value")] for i, (ev, label) in enumerate(legend): y = 74 + i * 30 body.append('' % (ev, y - 5)) body.append('%s' % (y, label)) write("callback_events.svg", 480, 262, "nj-fig--cb", "fig-cb-title", "Example of the parse events a callback sees for the J​SON text {\"name\": \"Berlin\", \"location\": [52.519444, 13.406667]}: object_start after the opening brace, key after each key, array_start after the opening bracket, value after each value, array_end after the closing bracket, and object_end after the closing brace. Every array and object also produces a value event when it is complete.", body) # ---------- ranges ---------- BW, GAP, X0 = 30, 4, 30 def cell_x(i): return X0 + i * (BW + GAP) def cell(i, y, kind): c = "fg-cell" + (" fg-cell--end" if kind == "end" else "") return '' % (c, cell_x(i), y, BW) def pill(cx, y, label, W, label_w): x = max(4, min(W - 4 - label_w, cx - label_w / 2)) return ['' % (x, y, label_w), '%s' % (x + label_w / 2, y + 16.5, label)] def arrow(x, ya, yb, mid, dashed=False): return '' % (" fg-arrow--dash" if dashed else "", x, ya, x, yb, mid) def ranges1(): W, H, mid = 600, 150, "fig-be-arrow" defs = '' % mid body = [] for i in range(15): body.append(cell(i, 62, "elem")) body.append(cell(15, 62, "end")) body += pill(cell_x(0) + BW / 2, 8, "begin", W, 60); body.append(arrow(cell_x(0) + BW / 2, 32, 60, mid)) body += pill(cell_x(15) + BW / 2, 8, "end", W, 48); body.append(arrow(cell_x(15) + BW / 2, 32, 60, mid)) body.append('Past-the-last element' % (cell_x(15) + BW / 2 - 18)) body.append(arrow(cell_x(15) + BW / 2, 124, 96, mid)) write("range-begin-end.svg", W, H, "nj-fig--range", "fig-be-title", "A row of fifteen elements followed by a dashed past-the-last position. begin() points to the first element and end() points to the past-the-last position.", body, defs) def ranges2(): W, H, mid = 600, 236, "fig-rbe-arrow" defs = '' % mid body = [] ytop, ybot = 62, 144 body.append(cell(0, ytop, "end")) for i in range(1, 16): body.append(cell(i, ytop, "elem")) for i in range(15): body.append(cell(i, ybot, "elem")) body.append(cell(15, ybot, "end")) body += pill(cell_x(0) + BW / 2, 8, "rend", W, 52); body.append(arrow(cell_x(0) + BW / 2, 32, 60, mid)) body += pill(cell_x(15) + BW / 2, 8, "rbegin", W, 68); body.append(arrow(cell_x(15) + BW / 2, 32, 60, mid)) body.append('Reverse past-the-last element' % (cell_x(0) + BW / 2 + 36)) for col in (0, 15): x = cell_x(col) + BW / 2 body.append('' % (x, x, ybot - 2)) body.append('A reverse iterator stores an iterator to the next element') body.append('') body += pill(cell_x(0) + BW / 2, 204, "begin", W, 60); body.append(arrow(cell_x(0) + BW / 2, 202, 178, mid)) body += pill(cell_x(15) + BW / 2, 204, "end", W, 48); body.append(arrow(cell_x(15) + BW / 2, 202, 178, mid)) body.append('Past-the-last element' % (cell_x(15) + BW / 2 - 40)) write("range-rbegin-rend.svg", W, H, "nj-fig--range", "fig-rbe-title", "Two rows of sixteen cells. The upper row shows the reversed sequence: rend() points to a dashed position before the first element and rbegin() to the last element. The lower row shows the forward sequence: begin() points to the first element and end() to a dashed position after the last element. A reverse iterator stores an iterator to the next element, so rend() corresponds to begin() and rbegin() to end().", body, defs) callback(); ranges1(); ranges2()