Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import html | |
| import math | |
| from pathlib import Path | |
| from typing import Any | |
| from PIL import Image, ImageDraw, ImageFont | |
| from .verifier import ( | |
| make_container_shape, | |
| make_item_shape, | |
| normalize_solution, | |
| solution_case, | |
| ) | |
| GREEN = (205, 247, 214) | |
| BLACK = (0, 0, 0) | |
| WHITE = (255, 255, 255) | |
| def font(size: int, bold: bool = False, scale: int = 1) -> ImageFont.ImageFont: | |
| names = [ | |
| "/System/Library/Fonts/Supplemental/Arial Bold.ttf" if bold else "/System/Library/Fonts/Supplemental/Arial.ttf", | |
| "/System/Library/Fonts/Supplemental/Helvetica Bold.ttf" if bold else "/System/Library/Fonts/Supplemental/Helvetica.ttf", | |
| "/Library/Fonts/Arial Bold.ttf" if bold else "/Library/Fonts/Arial.ttf", | |
| "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", | |
| ] | |
| for name in names: | |
| if Path(name).exists(): | |
| return ImageFont.truetype(name, size=size * scale) | |
| return ImageFont.load_default() | |
| def all_points(container: dict[str, Any], shapes: list[dict[str, Any]]) -> list[tuple[float, float]]: | |
| points: list[tuple[float, float]] = [] | |
| if container["kind"] == "circle": | |
| cx, cy = container["center"] | |
| r = container["radius"] | |
| points.extend([(cx - r, cy - r), (cx + r, cy + r)]) | |
| else: | |
| points.extend(container["vertices"]) | |
| for shape in shapes: | |
| if shape["kind"] == "circle": | |
| cx, cy = shape["center"] | |
| r = shape["radius"] | |
| points.extend([(cx - r, cy - r), (cx + r, cy + r)]) | |
| else: | |
| points.extend(shape["vertices"]) | |
| return points | |
| def transform_factory( | |
| points: list[tuple[float, float]], | |
| size: int, | |
| label_height: int, | |
| margin: int, | |
| supersample: int, | |
| ): | |
| xs = [x for x, _ in points] | |
| ys = [y for _, y in points] | |
| minx, maxx = min(xs), max(xs) | |
| miny, maxy = min(ys), max(ys) | |
| width = max(maxx - minx, 1.0e-12) | |
| height = max(maxy - miny, 1.0e-12) | |
| left, top = margin, label_height | |
| right, bottom = size - margin, size - margin | |
| scale = min((right - left) / width, (bottom - top) / height) | |
| offx = left + ((right - left) - width * scale) * 0.5 | |
| offy = top + ((bottom - top) - height * scale) * 0.5 | |
| def xy(point: tuple[float, float]) -> tuple[int, int]: | |
| x, y = point | |
| px = offx + (x - minx) * scale | |
| py = offy + (maxy - y) * scale | |
| return round(px * supersample), round(py * supersample) | |
| def length(value: float) -> int: | |
| return round(value * scale * supersample) | |
| return xy, length | |
| def draw_closed(draw: ImageDraw.ImageDraw, points: list[tuple[int, int]], width: int) -> None: | |
| draw.line(points + [points[0]], fill=BLACK, width=width, joint="curve") | |
| def render_png(solution: dict[str, Any], path: Path, size: int = 900, label: bool = True) -> Path: | |
| solution = normalize_solution(solution) | |
| container = make_container_shape(solution["container"]) | |
| shapes = [make_item_shape(solution["item"], placement) for placement in solution["placements"]] | |
| ss = 3 | |
| label_height = 82 if label else 28 | |
| xy, lens = transform_factory(all_points(container, shapes), size, label_height, 30, ss) | |
| img = Image.new("RGB", (size * ss, size * ss), WHITE) | |
| draw = ImageDraw.Draw(img) | |
| if label: | |
| title_font = font(30, bold=True, scale=ss) | |
| side_font = font(28, bold=True, scale=ss) | |
| case = solution_case(solution) | |
| side = make_container_shape(solution["container"])["side"] | |
| side_label = f"s={side:.5f}" | |
| draw.text((24 * ss, 18 * ss), case, fill=BLACK, font=title_font) | |
| bbox = draw.textbbox((0, 0), side_label, font=side_font) | |
| draw.text(((size - 24) * ss - (bbox[2] - bbox[0]), 20 * ss), side_label, fill=BLACK, font=side_font) | |
| for shape in shapes: | |
| if shape["kind"] == "circle": | |
| cx, cy = xy(shape["center"]) | |
| r = lens(shape["radius"]) | |
| draw.ellipse((cx - r, cy - r, cx + r, cy + r), fill=GREEN, outline=BLACK, width=3 * ss) | |
| else: | |
| pts = [xy(point) for point in shape["vertices"]] | |
| draw.polygon(pts, fill=GREEN) | |
| draw_closed(draw, pts, width=3 * ss) | |
| if container["kind"] == "circle": | |
| cx, cy = xy(container["center"]) | |
| r = lens(container["radius"]) | |
| draw.ellipse((cx - r, cy - r, cx + r, cy + r), outline=BLACK, width=6 * ss) | |
| else: | |
| draw_closed(draw, [xy(point) for point in container["vertices"]], width=6 * ss) | |
| img = img.resize((size, size), Image.Resampling.LANCZOS) | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(path, optimize=True) | |
| return path | |
| def svg_markup(solution: dict[str, Any], size: int = 900) -> str: | |
| solution = normalize_solution(solution) | |
| container = make_container_shape(solution["container"]) | |
| shapes = [make_item_shape(solution["item"], placement) for placement in solution["placements"]] | |
| points = all_points(container, shapes) | |
| xs = [x for x, _ in points] | |
| ys = [y for _, y in points] | |
| minx, maxx = min(xs), max(xs) | |
| miny, maxy = min(ys), max(ys) | |
| pad = max(maxx - minx, maxy - miny) * 0.05 + 1.0e-9 | |
| view = f"{minx - pad:.12g} {-maxy - pad:.12g} {maxx - minx + 2 * pad:.12g} {maxy - miny + 2 * pad:.12g}" | |
| stroke = max(maxx - minx, maxy - miny) / 260.0 | |
| def pts(poly: list[tuple[float, float]]) -> str: | |
| return " ".join(f"{x:.12g},{-y:.12g}" for x, y in poly) | |
| body = [ | |
| f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{view}" width="{size}" height="{size}">', | |
| f" <title>{html.escape(solution_case(solution))}</title>", | |
| ' <g stroke="black" stroke-linejoin="round">', | |
| ] | |
| for shape in shapes: | |
| if shape["kind"] == "circle": | |
| cx, cy = shape["center"] | |
| body.append( | |
| f' <circle cx="{cx:.12g}" cy="{-cy:.12g}" r="{shape["radius"]:.12g}" fill="#cdf7d6" stroke-width="{stroke:.12g}"/>' | |
| ) | |
| else: | |
| body.append(f' <polygon points="{pts(shape["vertices"])}" fill="#cdf7d6" stroke-width="{stroke:.12g}"/>') | |
| if container["kind"] == "circle": | |
| cx, cy = container["center"] | |
| body.append( | |
| f' <circle cx="{cx:.12g}" cy="{-cy:.12g}" r="{container["radius"]:.12g}" fill="none" stroke-width="{2.6 * stroke:.12g}"/>' | |
| ) | |
| else: | |
| body.append(f' <polygon points="{pts(container["vertices"])}" fill="none" stroke-width="{2.6 * stroke:.12g}"/>') | |
| body.extend([" </g>", "</svg>"]) | |
| return "\n".join(body) + "\n" | |