Source code for svgen.display

"""
A module for working with common display assets.
"""

# built-in
from math import isclose
from typing import Iterator, NamedTuple, Union

# internal
from svgen.attribute.viewbox import ViewBox

DEFAULT_DELIM = ":"


[docs] class AspectRatio(NamedTuple): """A class to storge a width and height ratio.""" width: int height: int final: bool = False @property def square(self) -> bool: """Determine if this is a square aspect ratio.""" return self.width == self.height def __str__(self) -> str: """Get this aspect ratio as a string.""" return self.as_str()
[docs] def as_str(self, delim: str = DEFAULT_DELIM) -> str: """Get this aspect ratio as a string.""" return str(self.width) + delim + str(self.height)
def __eq__(self, other) -> bool: """Determine if two aspect ratios are equivalent.""" return isclose(self.over_height, other.over_height) @property def landscape(self) -> bool: """Determine if this ratio is landscape.""" return self.width > self.height @property def portrait(self) -> bool: """Determine if this ratio is portrait.""" return self.height > self.width
[docs] def rotate(self) -> "AspectRatio": """Get this aspect ratio rotated.""" return AspectRatio(self.height, self.width, self.final)
@property def over_height(self) -> float: """Express this ratio as one of width over height.""" return float(self.width) / float(self.height)
[docs] @staticmethod def create(wxh: str, delim: str = DEFAULT_DELIM) -> "AspectRatio": """Create an aspect ration from a string.""" parts = wxh.split(delim) return AspectRatio(int(parts[0].strip()), int(parts[1].strip()))
@property def args(self) -> list[str]: """Get inkscape command-line arguments.""" assert self.final return ["-w", str(self.width), "-h", str(self.height)]
COMMON_RATIOS = [ "16:9", "16:10", "4:3", "5:4", "1:1", "256:135", "32:9", "21:9", "3:2", "4:1", ] ICON_SIZES = [16, 24, 32, 64, 128, 256] COMMON_SIZES = { "4:3": [AspectRatio(1024, 768, True), AspectRatio(1600, 1200, True)], "5:4": [AspectRatio(1280, 1024, True)], "3:2": [AspectRatio(2160, 1440, True), AspectRatio(2560, 1700, True)], "16:10": [ AspectRatio(1280, 800, True), AspectRatio(1920, 1200, True), AspectRatio(2880, 1800, True), ], "8:9": [ AspectRatio(960, 1080, True), ], "16:9": [ AspectRatio(1280, 720, True), AspectRatio(1366, 768, True), AspectRatio(1920, 1080, True), AspectRatio(2048, 1152, True), AspectRatio(2560, 1440, True), AspectRatio(3840, 2160, True), ], "17:9": [AspectRatio(4096, 2160, True)], "21:9": [ AspectRatio(2560, 1080, True), AspectRatio(3440, 1440, True), AspectRatio(5120, 2160, True), ], "32:9": [ AspectRatio(3840, 1080, True), AspectRatio(5120, 1440, True), AspectRatio(7680, 2160, True), ], "1:1": [ AspectRatio(150, 150, True), # YouTube video watermark AspectRatio(400, 400, True), AspectRatio(512, 512, True), AspectRatio(1024, 1024, True), AspectRatio(1920, 1920, True), AspectRatio(3000, 3000, True), # album art ], # GitHub social preview. "2:1": [AspectRatio(1280, 640, True)], "4:1": [ AspectRatio(17280, 4320, True), AspectRatio(1600, 400, True), AspectRatio(1584, 396, True), ], # Twitter banner. "3:1": [AspectRatio(1500, 500, True)], # 14-inch Macbook Pro monitor. "189:124": [AspectRatio(3024, 1984, True)], # Facebook banner. "205:78": [AspectRatio(820, 312, True)], # Buy me a Coffee banner. "2560:423": [AspectRatio(2560, 423, True)], # Twitch banner. "1200:480": [AspectRatio(1200, 480, True)], # Google admin logo. "80:33": [AspectRatio(320, 132, True)], } COMMON_SIZES["1:1"].extend(AspectRatio(x, x, True) for x in ICON_SIZES)
[docs] def common_sizes( ratio: Union[str, AspectRatio, ViewBox], ) -> Iterator[AspectRatio]: """Iterate over common sizes for a given aspect ratio.""" if isinstance(ratio, ViewBox): ratio = AspectRatio(ratio.width, ratio.height) if not isinstance(ratio, AspectRatio): ratio = AspectRatio.create(ratio) # Look for a ratio equivalent to a known one and iterate over sizes. for common, sizes in COMMON_SIZES.items(): candidates = [AspectRatio.create(common)] + sizes # Attempt to match to a common aspect ratio. for candidate in candidates: found = False if candidate == ratio: yield from sizes found = True break # Check if this is a rotated version of this common ratio. if not found and candidate.rotate() == ratio: for size in sizes: yield size.rotate()