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()