memory/source/main.py

660 lines
13 KiB
Python
Raw Normal View History

2025-12-17 06:31:57 +01:00
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}}</{{tag}}>\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 = "<data-path>",
)
argument_parser.add_argument(
"-W",
"--page-width",
type = int,
metavar = "<page-width>",
default = 210,
help = "in mm",
)
argument_parser.add_argument(
"-H",
"--page-height",
type = int,
metavar = "<page-height>",
default = 297,
help = "in mm",
)
argument_parser.add_argument(
"-u",
"--tile-width",
type = float,
metavar = "<tile-width>",
default = 52.5,
help = "in mm",
)
argument_parser.add_argument(
"-v",
"--tile-height",
type = float,
metavar = "<tile-height>",
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 = "<output-directory>",
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()