commit eb8e992adf0a78d204cb15a10704969b526c3946 Author: Fenris Wolf Date: Wed Dec 17 06:31:57 2025 +0100 [ini] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/data/addition.json b/data/addition.json new file mode 100644 index 0000000..e7278c4 --- /dev/null +++ b/data/addition.json @@ -0,0 +1,39 @@ +[ + {"links": [" 2", "+", "2 "], "rechts": ["4"]}, + {"links": [" 2", "+", "3 "], "rechts": ["5"]}, + {"links": [" 2", "+", "4 "], "rechts": ["6"]}, + {"links": [" 2", "+", "5 "], "rechts": ["7"]}, + {"links": [" 2", "+", "6 "], "rechts": ["8"]}, + {"links": [" 2", "+", "7 "], "rechts": ["9"]}, + {"links": [" 2", "+", "8 "], "rechts": ["10"]}, + {"links": [" 2", "+", "9 "], "rechts": ["11"]}, + {"links": [" 3", "+", "3 "], "rechts": ["6"]}, + {"links": [" 3", "+", "4 "], "rechts": ["7"]}, + {"links": [" 3", "+", "5 "], "rechts": ["8"]}, + {"links": [" 3", "+", "6 "], "rechts": ["9"]}, + {"links": [" 3", "+", "7 "], "rechts": ["10"]}, + {"links": [" 3", "+", "8 "], "rechts": ["11"]}, + {"links": [" 3", "+", "9 "], "rechts": ["12"]}, + {"links": [" 4", "+", "4 "], "rechts": ["8"]}, + {"links": [" 4", "+", "5 "], "rechts": ["9"]}, + {"links": [" 4", "+", "6 "], "rechts": ["10"]}, + {"links": [" 4", "+", "7 "], "rechts": ["11"]}, + {"links": [" 4", "+", "8 "], "rechts": ["12"]}, + {"links": [" 4", "+", "9 "], "rechts": ["13"]}, + {"links": [" 5", "+", "5 "], "rechts": ["10"]}, + {"links": [" 5", "+", "6 "], "rechts": ["11"]}, + {"links": [" 5", "+", "7 "], "rechts": ["12"]}, + {"links": [" 5", "+", "8 "], "rechts": ["13"]}, + {"links": [" 5", "+", "9 "], "rechts": ["14"]}, + {"links": [" 6", "+", "6 "], "rechts": ["12"]}, + {"links": [" 6", "+", "7 "], "rechts": ["13"]}, + {"links": [" 6", "+", "8 "], "rechts": ["14"]}, + {"links": [" 6", "+", "9 "], "rechts": ["15"]}, + {"links": [" 7", "+", "7 "], "rechts": ["14"]}, + {"links": [" 7", "+", "8 "], "rechts": ["15"]}, + {"links": [" 7", "+", "9 "], "rechts": ["16"]}, + {"links": [" 8", "+", "8 "], "rechts": ["16"]}, + {"links": [" 8", "+", "9 "], "rechts": ["17"]}, + {"links": [" 9", "+", "9 "], "rechts": ["18"]} +] + diff --git a/data/multipliktion.json b/data/multipliktion.json new file mode 100644 index 0000000..5a3a5c7 --- /dev/null +++ b/data/multipliktion.json @@ -0,0 +1,48 @@ +[ + {"links": [" 2", "·", "2 "], "rechts": ["4"]}, + {"links": [" 2", "·", "3 "], "rechts": ["6"]}, + {"links": [" 2", "·", "4 "], "rechts": ["8"]}, + {"links": [" 2", "·", "5 "], "rechts": ["10"]}, + {"links": [" 2", "·", "6 "], "rechts": ["12"]}, + {"links": [" 2", "·", "7 "], "rechts": ["14"]}, + {"links": [" 2", "·", "8 "], "rechts": ["16"]}, + {"links": [" 2", "·", "9 "], "rechts": ["18"]}, + {"links": [" 2", "·", "10 "], "rechts": ["20"]}, + {"links": [" 3", "·", "3 "], "rechts": ["9"]}, + {"links": [" 3", "·", "4 "], "rechts": ["12"]}, + {"links": [" 3", "·", "5 "], "rechts": ["15"]}, + {"links": [" 3", "·", "6 "], "rechts": ["18"]}, + {"links": [" 3", "·", "7 "], "rechts": ["21"]}, + {"links": [" 3", "·", "8 "], "rechts": ["24"]}, + {"links": [" 3", "·", "9 "], "rechts": ["27"]}, + {"links": [" 3", "·", "10 "], "rechts": ["30"]}, + {"links": [" 4", "·", "4 "], "rechts": ["16"]}, + {"links": [" 4", "·", "5 "], "rechts": ["20"]}, + {"links": [" 4", "·", "6 "], "rechts": ["24"]}, + {"links": [" 4", "·", "7 "], "rechts": ["28"]}, + {"links": [" 4", "·", "8 "], "rechts": ["32"]}, + {"links": [" 4", "·", "9 "], "rechts": ["36"]}, + {"links": [" 4", "·", "10 "], "rechts": ["40"]}, + {"links": [" 5", "·", "5 "], "rechts": ["25"]}, + {"links": [" 5", "·", "6 "], "rechts": ["30"]}, + {"links": [" 5", "·", "7 "], "rechts": ["35"]}, + {"links": [" 5", "·", "8 "], "rechts": ["40"]}, + {"links": [" 5", "·", "9 "], "rechts": ["45"]}, + {"links": [" 5", "·", "10 "], "rechts": ["50"]}, + {"links": [" 6", "·", "6 "], "rechts": ["36"]}, + {"links": [" 6", "·", "7 "], "rechts": ["42"]}, + {"links": [" 6", "·", "8 "], "rechts": ["48"]}, + {"links": [" 6", "·", "9 "], "rechts": ["54"]}, + {"links": [" 6", "·", "10 "], "rechts": ["60"]}, + {"links": [" 7", "·", "7 "], "rechts": ["49"]}, + {"links": [" 7", "·", "8 "], "rechts": ["56"]}, + {"links": [" 7", "·", "9 "], "rechts": ["63"]}, + {"links": [" 7", "·", "10 "], "rechts": ["70"]}, + {"links": [" 8", "·", "8 "], "rechts": ["64"]}, + {"links": [" 8", "·", "9 "], "rechts": ["72"]}, + {"links": [" 8", "·", "10 "], "rechts": ["80"]}, + {"links": [" 9", "·", "9 "], "rechts": ["81"]}, + {"links": [" 9", "·", "10 "], "rechts": ["90"]}, + {"links": [" 10", "·", "10 "], "rechts": ["100"]} +] + diff --git a/misc/uebersicht.ods b/misc/uebersicht.ods new file mode 100644 index 0000000..e8053d1 Binary files /dev/null and b/misc/uebersicht.ods differ diff --git a/source/main.py b/source/main.py new file mode 100644 index 0000000..8e6d8db --- /dev/null +++ b/source/main.py @@ -0,0 +1,659 @@ +import sys as _sys +import os as _os +import argparse as _argparse +import json as _json +import functools as _functools +import math as _math + + +def convey( + x, + fs +): + y = x + for f in fs: + y = f(y) + return y + + +def string_coin( + template, + arguments +): + result = template + for (key, value, ) in arguments.items(): + result = result.replace("{{%s}}" % key, value) + return result + + +def string_count_begin( + string, + character +): + result = 0 + while (string[result] == character): + result += 1 + return result + + +def string_count_end( + string, + character +): + result = 0 + while (string[-1-result] == character): + result += 1 + return result + + +def file_text_read( + path +): + handle = open(path, "r") + content = handle.read() + handle.close() + return content + + +def file_text_write( + path, + content +): + _os.makedirs( + _os.path.dirname(path), + exist_ok = True + ) + handle = open(path, "w") + handle.write(content) + handle.close() + + +def xml_node_regular( + tag, + attributes, + content +): + return string_coin( + ( + "<{{tag}}{{attributes}}/>\n" + if + (content is None) + else + "<{{tag}}{{attributes}}>\n{{content}}\n" + ), + { + "tag": tag, + "attributes": convey( + (attributes or {}).items(), + [ + lambda x: filter( + lambda pair: ( + not (pair[1] is None) + ), + x + ), + lambda x: map( + lambda pair: string_coin( + " {{key}}=\"{{value}}\"", + { + "key": pair[0], + "value": pair[1], + } + ), + x + ), + "".join, + ] + ), + "content": (content or ""), + } + ) + + +def css_color_hsl( + hue, + options = None +): + options = ( + { + "hue": 0.5, + "lightness": 0.5, + } + | + (options or {}) + ) + return string_coin( + "hsl({{hue}}, {{saturation}}%, {{lightness}}%)", + { + "hue": ("%.4f" % (hue * 360)), + "saturation": ("%.4f" % (options["saturation"] * 100)), + "lightness": ("%.4f" % (options["lightness"] * 100)), + } + ) + + +def css_commands( + actions +): + return " ".join( + map( + lambda pair: string_coin( + "{{key}}: {{value}};", + { + "key": pair[0], + "value": pair[1], + } + ), + actions.items() + ) + ) + + +def svg_measurement( + unit, + value +): + return string_coin( + "{{value}}{{unit}}", + { + "value": ("%.4f" % value), + "unit": (unit or ""), + } + ) + + +def svg_group( + children, + options = None +): + options = ( + { + "transform": None, + "style": None, + } + | + (options or {}) + ) + return xml_node_regular( + "g", + { + "transform": options["transform"], + "style": options["style"], + }, + "".join(children) + ) + + +def svg_rect( + from_x, + from_y, + to_x, + to_y, + options = None +): + options = ( + { + "style": None, + "unit": None, + } + | + (options or {}) + ) + return xml_node_regular( + "rect", + { + "x": svg_measurement(options["unit"], from_x), + "y": svg_measurement(options["unit"], from_y), + "width": svg_measurement(options["unit"], to_x - from_x), + "height": svg_measurement(options["unit"], to_y - from_y), + "style": options["style"], + }, + None + ) + + +def svg_text( + children, + options = None +): + options = ( + { + "unit": None, + "style": None, + "transform": None, + } + | + (options or {}) + ) + return xml_node_regular( + "text", + { + "x": svg_measurement(options["unit"], 0), + "y": svg_measurement(options["unit"], 0), + "style": options["style"], + "transform": options["transform"], + }, + "".join(children) + ) + + +def svg_tspan( + content, + options = None +): + options = ( + { + "unit": None, + "x": 0, + "y": 0, + "style": None, + } + | + (options or {}) + ) + return xml_node_regular( + "tspan", + { + "x": svg_measurement(options["unit"], options["x"]), + "y": svg_measurement(options["unit"], options["y"]), + "style": options["style"], + }, + content + ) + + +def svg_root( + content, + options = None +): + options = ( + { + "width": 210, + "height": 297, + "window": { + "from": {"x": 0, "y": 0}, + "to": {"x": 297, "y": 210}, + }, + } + | + (options or {}) + ) + return xml_node_regular( + "svg", + { + "xmlns": "http://www.w3.org/2000/svg", + "xmlns:svg": "http://www.w3.org/2000/svg", + "version": "1.1", + "width": svg_measurement("mm", options["width"]), + "height": svg_measurement("mm", options["height"]), + "viewBox": string_coin( + "{{anchor_x}} {{anchor_y}} {{size_x}} {{size_y}}", + { + "anchor_x": ("%u" % options["window"]["from"]["x"]), + "anchor_y": ("%u" % options["window"]["from"]["y"]), + "size_x": ("%u" % (options["window"]["to"]["x"] - options["window"]["from"]["x"])), + "size_y": ("%u" % (options["window"]["to"]["y"] - options["window"]["from"]["y"])), + } + ), + }, + content + ) + + +def main( +): + ## args + argument_parser = _argparse.ArgumentParser( + ) + argument_parser.add_argument( + "data_path", + type = str, + metavar = "", + ) + argument_parser.add_argument( + "-W", + "--page-width", + type = int, + metavar = "", + default = 210, + help = "in mm", + ) + argument_parser.add_argument( + "-H", + "--page-height", + type = int, + metavar = "", + default = 297, + help = "in mm", + ) + argument_parser.add_argument( + "-u", + "--tile-width", + type = float, + metavar = "", + default = 52.5, + help = "in mm", + ) + argument_parser.add_argument( + "-v", + "--tile-height", + type = float, + metavar = "", + default = 74.25, + help = "in mm", + ) + argument_parser.add_argument( + "-f", + "--fill-tiles", + action = "store_true", + ) + argument_parser.add_argument( + "-o", + "--output-directory", + type = str, + metavar = "", + default = "/tmp/memory", + ) + args = argument_parser.parse_args() + + ## consts + phi = ((_math.sqrt(5) - 1) / 2) + unit = None + conf = { + "colors": { + "background": { + "saturation": 0.0, + "lightness": 1.0, + }, + "foreground": { + "saturation": 0.0, + "lightness": 0.0, + }, + }, + "stroke": 1.0, + } + + ## input + content_in = file_text_read(args.data_path) + data = _json.loads(content_in) + + ## execution + tiles = convey( + data, + [ + len, + range, + lambda indices: list(map( + lambda index: [ + { + "id": index, + "content": list(map( + lambda text: { + "text": text.strip(), + "offset_left": string_count_begin(text, " "), + "offset_right": string_count_end(text, " "), + }, + data[index]["links"] + )), + "invert": False, + }, + { + "id": index, + "content": list(map( + lambda text: { + "text": text.strip(), + "offset_left": string_count_begin(text, " "), + "offset_right": string_count_end(text, " "), + }, + data[index]["rechts"] + )), + "invert": True, + }, + ], + indices + )), + lambda x: _functools.reduce(lambda a, b: (a + b), x, []), + ] + ) + tiles_per_line = int(args.page_width // args.tile_width) + lines_per_page = int(args.page_height // args.tile_height) + tiles_per_page = int(tiles_per_line * lines_per_page) + pages = int(_math.ceil(len(tiles) / tiles_per_page)) + if False: + _sys.stderr.write( + _json.dumps( + { + "page_width": args.page_width, + "page_height": args.page_height, + "tile_width": args.tile_width, + "tile_height": args.tile_height, + "tiles_per_line": tiles_per_line, + "lines_per_page": lines_per_page, + "tiles_per_page": tiles_per_page, + "tiles": len(tiles), + "pages": pages, + }, + indent = "\t" + ) + + + "\n" + ) + + ## output + for page in range(pages): + _sys.stderr.write("-- page %u/%u …\n" % ((page + 1), pages, )) + content_out = svg_root( + svg_group( + convey( + range( + ((page + 0) * tiles_per_page), + min( + ((page + 1) * tiles_per_page), + len(tiles) + ) + ), + [ + lambda indices: map( + lambda index: svg_group( + [ + svg_rect( + 0, + 0, + args.tile_width, + args.tile_height, + { + "unit": unit, + "style": css_commands( + { + "stroke": css_color_hsl( + ((tiles[index]["id"] * phi) % 1), + { + "saturation": ( + conf["colors"]["foreground"]["saturation"] + if (not tiles[index]["invert"]) else + conf["colors"]["background"]["saturation"] + ), + "lightness": ( + conf["colors"]["foreground"]["lightness"] + if (not tiles[index]["invert"]) else + conf["colors"]["background"]["lightness"] + ), + } + ), + "stroke-width": svg_measurement(unit, 0.5), + "fill": ( + "none" + if (not args.fill_tiles) else + css_color_hsl( + ((tiles[index]["id"] * phi) % 1), + { + "saturation": ( + conf["colors"]["background"]["saturation"] + if (not tiles[index]["invert"]) else + conf["colors"]["foreground"]["saturation"] + ), + "lightness": ( + conf["colors"]["background"]["lightness"] + if (not tiles[index]["invert"]) else + conf["colors"]["foreground"]["lightness"] + ), + } + ) + ), + } + ), + } + ), + svg_text( + list( + map( + lambda number: svg_tspan( + ( + tiles[index]["content"][number]["text"] + .replace(" ", "\xA0") + ), + { + "unit": unit, + "x": ( + ( + (-len(tiles[index]["content"][number]["text"])) + + + tiles[index]["content"][number]["offset_left"] + - + tiles[index]["content"][number]["offset_right"] + ) + * + 3.6 + ), + "y": ( + ( + (-((len(tiles[index]["content"]) - 1) / 2)) + + + number + ) + * + 16.0 + ), + "style": css_commands( + { + "stroke": css_color_hsl( + ((tiles[index]["id"] * phi) % 1), + { + "saturation": ( + conf["colors"]["foreground"]["saturation"] + if (not tiles[index]["invert"]) else + conf["colors"]["background"]["saturation"] + ), + "lightness": ( + conf["colors"]["foreground"]["lightness"] + if (not tiles[index]["invert"]) else + conf["colors"]["background"]["lightness"] + ), + } + ), + "stroke-width": svg_measurement(unit, conf["stroke"]), + "fill": css_color_hsl( + ((tiles[index]["id"] * phi) % 1), + { + "saturation": ( + conf["colors"]["foreground"]["saturation"] + if (not tiles[index]["invert"]) else + conf["colors"]["background"]["saturation"] + ), + "lightness": ( + conf["colors"]["foreground"]["lightness"] + if (not tiles[index]["invert"]) else + conf["colors"]["background"]["lightness"] + ), + } + ), + } + ), + } + ), + range(len(tiles[index]["content"])) + ) + ), + { + "unit": unit, + "style": css_commands( + { + "font-family": "Noto Mono", + "font-width": "bolder", + "font-size": "16", + } + ), + "transform": string_coin( + "translate({{x}} {{y}})", + { + "x": svg_measurement( + unit, + (args.tile_width * 0.5) + ), + "y": svg_measurement( + unit, + (args.tile_height * 0.6) + ), + } + ), + } + ), + ], + { + "transform": string_coin( + "translate({{x}} {{y}})", + { + "x": svg_measurement( + unit, + convey( + index, + [ + lambda x: (x % tiles_per_page), + lambda x: (x % tiles_per_line), + lambda x: (x * args.tile_width), + ] + ) + ), + "y": svg_measurement( + unit, + convey( + index, + [ + lambda x: (x % tiles_per_page), + lambda x: (x // tiles_per_line), + lambda x: (x * args.tile_height), + ] + ) + ), + } + ), + } + ), + indices + ), + list, + ] + ) + ), + { + "width": args.page_width, + "height": args.page_height, + "window": { + "from": {"x": 0, "y": 0}, + "to": {"x": args.page_width, "y": args.page_height}, + }, + } + ) + file_text_write( + _os.path.join( + args.output_directory, + "%u.svg" % (page + 1) + ), + content_out + ) + + _sys.stderr.write("-- output written to %s\n" % (args.output_directory, )) + + +main() + diff --git a/tools/build b/tools/build new file mode 100755 index 0000000..09a1544 --- /dev/null +++ b/tools/build @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +## const + +dir_source="source" +dir_build="build" + + +## exec + +mkdir -p ${dir_build} +echo "#!/usr/bin/env python3\n" > ${dir_build}/memory +cat ${dir_source}/main.py >> ${dir_build}/memory +chmod +x ${dir_build}/memory + +