660 lines
13 KiB
Python
660 lines
13 KiB
Python
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()
|
|
|