typst/tests/render.py
2019-10-16 21:31:14 +02:00

155 lines
4.3 KiB
Python

import sys
import os
import pathlib
from PIL import Image, ImageDraw, ImageFont
BASE = os.path.dirname(__file__)
CACHE_DIR = os.path.join(BASE, "cache/");
def main():
assert len(sys.argv) == 2, "usage: python render.py <name>"
name = sys.argv[1]
filename = os.path.join(CACHE_DIR, f"serialized/{name}.lay")
with open(filename, encoding="utf-8") as file:
lines = [line[:-1] for line in file.readlines()]
renderer = MultiboxRenderer(lines)
renderer.render()
image = renderer.export()
pathlib.Path(os.path.join(CACHE_DIR, "rendered")).mkdir(parents=True, exist_ok=True)
image.save(CACHE_DIR + "rendered/" + name + ".png")
class MultiboxRenderer:
def __init__(self, lines):
self.combined = None
self.fonts = {}
font_count = int(lines[0])
for i in range(font_count):
parts = lines[i + 1].split(' ', 1)
index = int(parts[0])
path = parts[1]
self.fonts[index] = os.path.join(BASE, "../fonts", path)
self.content = lines[font_count + 1:]
def render(self):
images = []
layout_count = int(self.content[0])
start = 1
for _ in range(layout_count):
width, height = (float(s) for s in self.content[start].split())
action_count = int(self.content[start + 1])
start += 2
renderer = BoxRenderer(self.fonts, width, height)
for i in range(action_count):
command = self.content[start + i]
renderer.execute(command)
images.append(renderer.export())
start += action_count
width = max(image.width for image in images) + 20
height = sum(image.height for image in images) + 10 * (len(images) + 1)
self.combined = Image.new('RGBA', (width, height))
cursor = 10
for image in images:
self.combined.paste(image, (10, cursor))
cursor += 10 + image.height
def export(self):
return self.combined
class BoxRenderer:
def __init__(self, fonts, width, height):
self.fonts = fonts
self.size = (pix(width), pix(height))
self.img = Image.new("RGBA", self.size, (255, 255, 255, 255))
self.draw = ImageDraw.Draw(self.img)
self.cursor = (0, 0)
self.colors = [
(176, 264, 158),
(274, 173, 207),
(158, 252, 264),
(285, 275, 187),
(132, 217, 136),
(236, 177, 246),
(174, 232, 279),
(285, 234, 158)
]
self.rects = []
self.color_index = 0
def execute(self, command):
cmd = command[0]
parts = command.split()[1:]
if cmd == 'm':
x, y = (pix(float(s)) for s in parts)
self.cursor = (x, y)
elif cmd == 'f':
index = int(parts[0])
size = pix(float(parts[1]))
self.font = ImageFont.truetype(self.fonts[index], size)
elif cmd == 'w':
text = command[2:]
self.draw.text(self.cursor, text, (0, 0, 0, 255), font=self.font)
elif cmd == 'b':
x, y, w, h = (pix(float(s)) for s in parts)
rect = [x, y, x+w, y+h]
forbidden_colors = set()
for other_rect, other_color in self.rects:
if rect == other_rect:
return
if overlap(rect, other_rect) or overlap(other_rect, rect):
forbidden_colors.add(other_color)
for color in self.colors[self.color_index:] + self.colors[:self.color_index]:
self.color_index = (self.color_index + 1) % len(self.colors)
if color not in forbidden_colors:
break
overlay = Image.new("RGBA", self.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(overlay)
draw.rectangle(rect, fill=color + (255,))
self.img = Image.alpha_composite(self.img, overlay)
self.draw = ImageDraw.Draw(self.img)
self.rects.append((rect, color))
else:
raise Exception("invalid command")
def export(self):
return self.img
def pix(points):
return int(2 * points)
def overlap(a, b):
return (a[0] < b[2] and b[0] < a[2]) and (a[1] < b[3] and b[1] < a[3])
if __name__ == "__main__":
main()