decksmith 0.1.12__py3-none-any.whl → 0.1.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- decksmith/card_builder.py +627 -627
- decksmith/deck_builder.py +101 -101
- decksmith/export.py +168 -168
- decksmith/main.py +138 -138
- decksmith/templates/deck.csv +5 -5
- decksmith/templates/deck.json +31 -31
- decksmith/utils.py +69 -69
- decksmith/validate.py +132 -132
- decksmith-0.1.15.dist-info/METADATA +102 -0
- decksmith-0.1.15.dist-info/RECORD +13 -0
- decksmith-0.1.12.dist-info/METADATA +0 -54
- decksmith-0.1.12.dist-info/RECORD +0 -13
- {decksmith-0.1.12.dist-info → decksmith-0.1.15.dist-info}/WHEEL +0 -0
- {decksmith-0.1.12.dist-info → decksmith-0.1.15.dist-info}/entry_points.txt +0 -0
decksmith/main.py
CHANGED
|
@@ -1,138 +1,138 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module provides a command-line tool for building decks of cards.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import shutil
|
|
6
|
-
from importlib import resources
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
import traceback
|
|
9
|
-
|
|
10
|
-
import click
|
|
11
|
-
from decksmith.deck_builder import DeckBuilder
|
|
12
|
-
from decksmith.export import PdfExporter
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@click.group()
|
|
16
|
-
def cli():
|
|
17
|
-
"""A command-line tool for building decks of cards."""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@cli.command()
|
|
21
|
-
def init():
|
|
22
|
-
"""Initializes a new project by creating deck.json and deck.csv."""
|
|
23
|
-
if Path("deck.json").exists() or Path("deck.csv").exists():
|
|
24
|
-
click.echo("(!) Project already initialized.")
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
with resources.path("decksmith.templates", "deck.json") as template_path:
|
|
28
|
-
shutil.copy(template_path, "deck.json")
|
|
29
|
-
with resources.path("decksmith.templates", "deck.csv") as template_path:
|
|
30
|
-
shutil.copy(template_path, "deck.csv")
|
|
31
|
-
|
|
32
|
-
click.echo("(✔) Initialized new project from templates.")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@cli.command(context_settings={"show_default": True})
|
|
36
|
-
@click.option("--output", default="output", help="The output directory for the deck.")
|
|
37
|
-
@click.option(
|
|
38
|
-
"--spec", default="deck.json", help="The path to the deck specification file."
|
|
39
|
-
)
|
|
40
|
-
@click.option("--data", default="deck.csv", help="The path to the data file.")
|
|
41
|
-
@click.pass_context
|
|
42
|
-
def build(ctx, output, spec, data):
|
|
43
|
-
"""Builds the deck of cards."""
|
|
44
|
-
output_path = Path(output)
|
|
45
|
-
output_path.mkdir(exist_ok=True)
|
|
46
|
-
|
|
47
|
-
click.echo(f"(i) Building deck in {output_path}...")
|
|
48
|
-
|
|
49
|
-
try:
|
|
50
|
-
spec_path = Path(spec)
|
|
51
|
-
if not spec_path.exists():
|
|
52
|
-
raise FileNotFoundError(f"Spec file not found: {spec_path}")
|
|
53
|
-
|
|
54
|
-
csv_path = Path(data)
|
|
55
|
-
if not csv_path.exists():
|
|
56
|
-
source = ctx.get_parameter_source("data")
|
|
57
|
-
if source.name == "DEFAULT":
|
|
58
|
-
click.echo(
|
|
59
|
-
f"(i) Building a single card deck because '{csv_path}' was not found"
|
|
60
|
-
)
|
|
61
|
-
csv_path = None
|
|
62
|
-
else:
|
|
63
|
-
raise FileNotFoundError(f"Data file not found: {csv_path}")
|
|
64
|
-
|
|
65
|
-
builder = DeckBuilder(spec_path, csv_path)
|
|
66
|
-
builder.build_deck(output_path)
|
|
67
|
-
except FileNotFoundError as exc:
|
|
68
|
-
click.echo(f"(x) {exc}")
|
|
69
|
-
ctx.exit(1)
|
|
70
|
-
# pylint: disable=W0718
|
|
71
|
-
except Exception as exc:
|
|
72
|
-
with open("log.txt", "a", encoding="utf-8") as log:
|
|
73
|
-
log.write(traceback.format_exc())
|
|
74
|
-
# print(f"{traceback.format_exc()}", end="\n")
|
|
75
|
-
print(f"(x) Error building deck '{data}' from spec '{spec}':")
|
|
76
|
-
print(" " * 4 + f"{exc}")
|
|
77
|
-
ctx.exit(1)
|
|
78
|
-
|
|
79
|
-
click.echo("(✔) Deck built successfully.")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@cli.command(context_settings={"show_default": True})
|
|
83
|
-
@click.argument("image_folder")
|
|
84
|
-
@click.option(
|
|
85
|
-
"--output", default="output.pdf", help="The path for the output PDF file."
|
|
86
|
-
)
|
|
87
|
-
@click.option(
|
|
88
|
-
"--page-size", default="A4", help="The page size for the PDF (e.g., A4, Letter)."
|
|
89
|
-
)
|
|
90
|
-
@click.option(
|
|
91
|
-
"--width", type=float, default=63.5, help="The width for each image in millimeters."
|
|
92
|
-
)
|
|
93
|
-
@click.option(
|
|
94
|
-
"--height",
|
|
95
|
-
type=float,
|
|
96
|
-
default=88.9,
|
|
97
|
-
help="The height for each image in millimeters.",
|
|
98
|
-
)
|
|
99
|
-
@click.option(
|
|
100
|
-
"--gap", type=float, default=0, help="The gap between images in millimeters."
|
|
101
|
-
)
|
|
102
|
-
@click.option(
|
|
103
|
-
"--margins",
|
|
104
|
-
type=float,
|
|
105
|
-
nargs=2,
|
|
106
|
-
default=[2, 2],
|
|
107
|
-
help="The horizontal and vertical page margins in millimeters.",
|
|
108
|
-
)
|
|
109
|
-
def export(image_folder, output, page_size, width, height, gap, margins):
|
|
110
|
-
"""Exports images from a folder to a PDF file."""
|
|
111
|
-
try:
|
|
112
|
-
image_folder_path = Path(image_folder)
|
|
113
|
-
if not image_folder_path.exists():
|
|
114
|
-
raise FileNotFoundError(f"Image folder not found: {image_folder_path}")
|
|
115
|
-
|
|
116
|
-
exporter = PdfExporter(
|
|
117
|
-
image_folder=image_folder_path,
|
|
118
|
-
output_path=Path(output),
|
|
119
|
-
page_size_str=page_size,
|
|
120
|
-
image_width=width,
|
|
121
|
-
image_height=height,
|
|
122
|
-
gap=gap,
|
|
123
|
-
margins=margins,
|
|
124
|
-
)
|
|
125
|
-
exporter.export()
|
|
126
|
-
click.echo(f"(✔) Successfully exported PDF to {output}")
|
|
127
|
-
except FileNotFoundError as exc:
|
|
128
|
-
click.echo(f"(x) {exc}")
|
|
129
|
-
# pylint: disable=W0718
|
|
130
|
-
except Exception as exc:
|
|
131
|
-
with open("log.txt", "a", encoding="utf-8") as log:
|
|
132
|
-
log.write(traceback.format_exc())
|
|
133
|
-
print(f"(x) Error exporting images to '{output}':")
|
|
134
|
-
print(" " * 4 + f"{exc}")
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if __name__ == "__main__":
|
|
138
|
-
cli()
|
|
1
|
+
"""
|
|
2
|
+
This module provides a command-line tool for building decks of cards.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
from importlib import resources
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import traceback
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
from decksmith.deck_builder import DeckBuilder
|
|
12
|
+
from decksmith.export import PdfExporter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
def cli():
|
|
17
|
+
"""A command-line tool for building decks of cards."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@cli.command()
|
|
21
|
+
def init():
|
|
22
|
+
"""Initializes a new project by creating deck.json and deck.csv."""
|
|
23
|
+
if Path("deck.json").exists() or Path("deck.csv").exists():
|
|
24
|
+
click.echo("(!) Project already initialized.")
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
with resources.path("decksmith.templates", "deck.json") as template_path:
|
|
28
|
+
shutil.copy(template_path, "deck.json")
|
|
29
|
+
with resources.path("decksmith.templates", "deck.csv") as template_path:
|
|
30
|
+
shutil.copy(template_path, "deck.csv")
|
|
31
|
+
|
|
32
|
+
click.echo("(✔) Initialized new project from templates.")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@cli.command(context_settings={"show_default": True})
|
|
36
|
+
@click.option("--output", default="output", help="The output directory for the deck.")
|
|
37
|
+
@click.option(
|
|
38
|
+
"--spec", default="deck.json", help="The path to the deck specification file."
|
|
39
|
+
)
|
|
40
|
+
@click.option("--data", default="deck.csv", help="The path to the data file.")
|
|
41
|
+
@click.pass_context
|
|
42
|
+
def build(ctx, output, spec, data):
|
|
43
|
+
"""Builds the deck of cards."""
|
|
44
|
+
output_path = Path(output)
|
|
45
|
+
output_path.mkdir(exist_ok=True)
|
|
46
|
+
|
|
47
|
+
click.echo(f"(i) Building deck in {output_path}...")
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
spec_path = Path(spec)
|
|
51
|
+
if not spec_path.exists():
|
|
52
|
+
raise FileNotFoundError(f"Spec file not found: {spec_path}")
|
|
53
|
+
|
|
54
|
+
csv_path = Path(data)
|
|
55
|
+
if not csv_path.exists():
|
|
56
|
+
source = ctx.get_parameter_source("data")
|
|
57
|
+
if source.name == "DEFAULT":
|
|
58
|
+
click.echo(
|
|
59
|
+
f"(i) Building a single card deck because '{csv_path}' was not found"
|
|
60
|
+
)
|
|
61
|
+
csv_path = None
|
|
62
|
+
else:
|
|
63
|
+
raise FileNotFoundError(f"Data file not found: {csv_path}")
|
|
64
|
+
|
|
65
|
+
builder = DeckBuilder(spec_path, csv_path)
|
|
66
|
+
builder.build_deck(output_path)
|
|
67
|
+
except FileNotFoundError as exc:
|
|
68
|
+
click.echo(f"(x) {exc}")
|
|
69
|
+
ctx.exit(1)
|
|
70
|
+
# pylint: disable=W0718
|
|
71
|
+
except Exception as exc:
|
|
72
|
+
with open("log.txt", "a", encoding="utf-8") as log:
|
|
73
|
+
log.write(traceback.format_exc())
|
|
74
|
+
# print(f"{traceback.format_exc()}", end="\n")
|
|
75
|
+
print(f"(x) Error building deck '{data}' from spec '{spec}':")
|
|
76
|
+
print(" " * 4 + f"{exc}")
|
|
77
|
+
ctx.exit(1)
|
|
78
|
+
|
|
79
|
+
click.echo("(✔) Deck built successfully.")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@cli.command(context_settings={"show_default": True})
|
|
83
|
+
@click.argument("image_folder")
|
|
84
|
+
@click.option(
|
|
85
|
+
"--output", default="output.pdf", help="The path for the output PDF file."
|
|
86
|
+
)
|
|
87
|
+
@click.option(
|
|
88
|
+
"--page-size", default="A4", help="The page size for the PDF (e.g., A4, Letter)."
|
|
89
|
+
)
|
|
90
|
+
@click.option(
|
|
91
|
+
"--width", type=float, default=63.5, help="The width for each image in millimeters."
|
|
92
|
+
)
|
|
93
|
+
@click.option(
|
|
94
|
+
"--height",
|
|
95
|
+
type=float,
|
|
96
|
+
default=88.9,
|
|
97
|
+
help="The height for each image in millimeters.",
|
|
98
|
+
)
|
|
99
|
+
@click.option(
|
|
100
|
+
"--gap", type=float, default=0, help="The gap between images in millimeters."
|
|
101
|
+
)
|
|
102
|
+
@click.option(
|
|
103
|
+
"--margins",
|
|
104
|
+
type=float,
|
|
105
|
+
nargs=2,
|
|
106
|
+
default=[2, 2],
|
|
107
|
+
help="The horizontal and vertical page margins in millimeters.",
|
|
108
|
+
)
|
|
109
|
+
def export(image_folder, output, page_size, width, height, gap, margins):
|
|
110
|
+
"""Exports images from a folder to a PDF file."""
|
|
111
|
+
try:
|
|
112
|
+
image_folder_path = Path(image_folder)
|
|
113
|
+
if not image_folder_path.exists():
|
|
114
|
+
raise FileNotFoundError(f"Image folder not found: {image_folder_path}")
|
|
115
|
+
|
|
116
|
+
exporter = PdfExporter(
|
|
117
|
+
image_folder=image_folder_path,
|
|
118
|
+
output_path=Path(output),
|
|
119
|
+
page_size_str=page_size,
|
|
120
|
+
image_width=width,
|
|
121
|
+
image_height=height,
|
|
122
|
+
gap=gap,
|
|
123
|
+
margins=margins,
|
|
124
|
+
)
|
|
125
|
+
exporter.export()
|
|
126
|
+
click.echo(f"(✔) Successfully exported PDF to {output}")
|
|
127
|
+
except FileNotFoundError as exc:
|
|
128
|
+
click.echo(f"(x) {exc}")
|
|
129
|
+
# pylint: disable=W0718
|
|
130
|
+
except Exception as exc:
|
|
131
|
+
with open("log.txt", "a", encoding="utf-8") as log:
|
|
132
|
+
log.write(traceback.format_exc())
|
|
133
|
+
print(f"(x) Error exporting images to '{output}':")
|
|
134
|
+
print(" " * 4 + f"{exc}")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
cli()
|
decksmith/templates/deck.csv
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
title;description
|
|
2
|
-
Welcome to DeckSmith!;To get started, edit "deck.csv" to add new cards, or "deck.json" to change their structure.
|
|
3
|
-
Building the deck;Run "decksmith build" when you are ready to save the cards as images.
|
|
4
|
-
Exporting to PDF;You can create a printable PDF with "decksmith export --width 63.5 --height 88.9 output" after building.
|
|
5
|
-
Check out the guide;Visit "github.com/julynx/decksmith" to learn all the things you can do using DeckSmith. Enjoy!
|
|
1
|
+
title;description
|
|
2
|
+
Welcome to DeckSmith!;To get started, edit "deck.csv" to add new cards, or "deck.json" to change their structure.
|
|
3
|
+
Building the deck;Run "decksmith build" when you are ready to save the cards as images.
|
|
4
|
+
Exporting to PDF;You can create a printable PDF with "decksmith export --width 63.5 --height 88.9 output" after building.
|
|
5
|
+
Check out the guide;Visit "github.com/julynx/decksmith" to learn all the things you can do using DeckSmith. Enjoy!
|
decksmith/templates/deck.json
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
{
|
|
2
|
-
"width": 250,
|
|
3
|
-
"height": 350,
|
|
4
|
-
"background_color": [200, 200, 200],
|
|
5
|
-
"elements": [
|
|
6
|
-
{
|
|
7
|
-
"id": "title",
|
|
8
|
-
"type": "text",
|
|
9
|
-
"position": [125, 25],
|
|
10
|
-
"anchor": "top-center",
|
|
11
|
-
"text": "%title%",
|
|
12
|
-
"color": [0, 0, 0],
|
|
13
|
-
"font_path": "arial.ttf",
|
|
14
|
-
"font_size": 20,
|
|
15
|
-
"align": "center"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"id": "description",
|
|
19
|
-
"type": "text",
|
|
20
|
-
"position": [0, 25],
|
|
21
|
-
"relative_to": ["title", "bottom-center"],
|
|
22
|
-
"anchor": "top-center",
|
|
23
|
-
"text": "%description%",
|
|
24
|
-
"color": [0, 0, 0],
|
|
25
|
-
"font_path": "arial.ttf",
|
|
26
|
-
"font_size": 16,
|
|
27
|
-
"width": 200,
|
|
28
|
-
"align": "center"
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"width": 250,
|
|
3
|
+
"height": 350,
|
|
4
|
+
"background_color": [200, 200, 200],
|
|
5
|
+
"elements": [
|
|
6
|
+
{
|
|
7
|
+
"id": "title",
|
|
8
|
+
"type": "text",
|
|
9
|
+
"position": [125, 25],
|
|
10
|
+
"anchor": "top-center",
|
|
11
|
+
"text": "%title%",
|
|
12
|
+
"color": [0, 0, 0],
|
|
13
|
+
"font_path": "arial.ttf",
|
|
14
|
+
"font_size": 20,
|
|
15
|
+
"align": "center"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "description",
|
|
19
|
+
"type": "text",
|
|
20
|
+
"position": [0, 25],
|
|
21
|
+
"relative_to": ["title", "bottom-center"],
|
|
22
|
+
"anchor": "top-center",
|
|
23
|
+
"text": "%description%",
|
|
24
|
+
"color": [0, 0, 0],
|
|
25
|
+
"font_path": "arial.ttf",
|
|
26
|
+
"font_size": 16,
|
|
27
|
+
"width": 200,
|
|
28
|
+
"align": "center"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
decksmith/utils.py
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module provides utility functions for text wrapping and positioning.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from typing import Tuple
|
|
6
|
-
|
|
7
|
-
from PIL import ImageFont
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def get_wrapped_text(text: str, font: ImageFont.ImageFont, line_length: int) -> str:
|
|
11
|
-
"""
|
|
12
|
-
Wraps text to fit within a specified line length using the given font,
|
|
13
|
-
preserving existing newlines.
|
|
14
|
-
Args:
|
|
15
|
-
text (str): The text to wrap.
|
|
16
|
-
font (ImageFont.ImageFont): The font to use for measuring text length.
|
|
17
|
-
line_length (int): The maximum length of each line in pixels.
|
|
18
|
-
|
|
19
|
-
Returns:
|
|
20
|
-
str: The wrapped text with newlines inserted where necessary.
|
|
21
|
-
"""
|
|
22
|
-
wrapped_lines = []
|
|
23
|
-
for line in text.split("\n"):
|
|
24
|
-
lines = [""]
|
|
25
|
-
for word in line.split():
|
|
26
|
-
line_to_check = f"{lines[-1]} {word}".strip()
|
|
27
|
-
if font.getlength(line_to_check) <= line_length:
|
|
28
|
-
lines[-1] = line_to_check
|
|
29
|
-
else:
|
|
30
|
-
lines.append(word)
|
|
31
|
-
wrapped_lines.extend(lines)
|
|
32
|
-
return "\n".join(wrapped_lines)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def apply_anchor(size: Tuple[int, ...], anchor: str) -> Tuple[int, int]:
|
|
36
|
-
"""
|
|
37
|
-
Applies an anchor to a size tuple to determine the position of an element.
|
|
38
|
-
Args:
|
|
39
|
-
size (Tuple[int, ...]): A tuple representing the size (width, height)
|
|
40
|
-
or a bounding box (x1, y1, x2, y2).
|
|
41
|
-
anchor (str): The anchor position, e.g., "center", "top-left".
|
|
42
|
-
Returns:
|
|
43
|
-
Tuple[int, int]: A tuple representing the position (x, y) based on the anchor.
|
|
44
|
-
"""
|
|
45
|
-
if len(size) == 2:
|
|
46
|
-
w, h = size
|
|
47
|
-
x, y = 0, 0
|
|
48
|
-
elif len(size) == 4:
|
|
49
|
-
x, y, x2, y2 = size
|
|
50
|
-
w, h = x2 - x, y2 - y
|
|
51
|
-
else:
|
|
52
|
-
raise ValueError("Size must be a tuple of 2 or 4 integers.")
|
|
53
|
-
|
|
54
|
-
anchor_points = {
|
|
55
|
-
"top-left": (x, y),
|
|
56
|
-
"top-center": (x + w // 2, y),
|
|
57
|
-
"top-right": (x + w, y),
|
|
58
|
-
"middle-left": (x, y + h // 2),
|
|
59
|
-
"center": (x + w // 2, y + h // 2),
|
|
60
|
-
"middle-right": (x + w, y + h // 2),
|
|
61
|
-
"bottom-left": (x, y + h),
|
|
62
|
-
"bottom-center": (x + w // 2, y + h),
|
|
63
|
-
"bottom-right": (x + w, y + h),
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if anchor not in anchor_points:
|
|
67
|
-
raise ValueError(f"Unknown anchor: {anchor}")
|
|
68
|
-
|
|
69
|
-
return anchor_points[anchor]
|
|
1
|
+
"""
|
|
2
|
+
This module provides utility functions for text wrapping and positioning.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Tuple
|
|
6
|
+
|
|
7
|
+
from PIL import ImageFont
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_wrapped_text(text: str, font: ImageFont.ImageFont, line_length: int) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Wraps text to fit within a specified line length using the given font,
|
|
13
|
+
preserving existing newlines.
|
|
14
|
+
Args:
|
|
15
|
+
text (str): The text to wrap.
|
|
16
|
+
font (ImageFont.ImageFont): The font to use for measuring text length.
|
|
17
|
+
line_length (int): The maximum length of each line in pixels.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
str: The wrapped text with newlines inserted where necessary.
|
|
21
|
+
"""
|
|
22
|
+
wrapped_lines = []
|
|
23
|
+
for line in text.split("\n"):
|
|
24
|
+
lines = [""]
|
|
25
|
+
for word in line.split():
|
|
26
|
+
line_to_check = f"{lines[-1]} {word}".strip()
|
|
27
|
+
if font.getlength(line_to_check) <= line_length:
|
|
28
|
+
lines[-1] = line_to_check
|
|
29
|
+
else:
|
|
30
|
+
lines.append(word)
|
|
31
|
+
wrapped_lines.extend(lines)
|
|
32
|
+
return "\n".join(wrapped_lines)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def apply_anchor(size: Tuple[int, ...], anchor: str) -> Tuple[int, int]:
|
|
36
|
+
"""
|
|
37
|
+
Applies an anchor to a size tuple to determine the position of an element.
|
|
38
|
+
Args:
|
|
39
|
+
size (Tuple[int, ...]): A tuple representing the size (width, height)
|
|
40
|
+
or a bounding box (x1, y1, x2, y2).
|
|
41
|
+
anchor (str): The anchor position, e.g., "center", "top-left".
|
|
42
|
+
Returns:
|
|
43
|
+
Tuple[int, int]: A tuple representing the position (x, y) based on the anchor.
|
|
44
|
+
"""
|
|
45
|
+
if len(size) == 2:
|
|
46
|
+
w, h = size
|
|
47
|
+
x, y = 0, 0
|
|
48
|
+
elif len(size) == 4:
|
|
49
|
+
x, y, x2, y2 = size
|
|
50
|
+
w, h = x2 - x, y2 - y
|
|
51
|
+
else:
|
|
52
|
+
raise ValueError("Size must be a tuple of 2 or 4 integers.")
|
|
53
|
+
|
|
54
|
+
anchor_points = {
|
|
55
|
+
"top-left": (x, y),
|
|
56
|
+
"top-center": (x + w // 2, y),
|
|
57
|
+
"top-right": (x + w, y),
|
|
58
|
+
"middle-left": (x, y + h // 2),
|
|
59
|
+
"center": (x + w // 2, y + h // 2),
|
|
60
|
+
"middle-right": (x + w, y + h // 2),
|
|
61
|
+
"bottom-left": (x, y + h),
|
|
62
|
+
"bottom-center": (x + w // 2, y + h),
|
|
63
|
+
"bottom-right": (x + w, y + h),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if anchor not in anchor_points:
|
|
67
|
+
raise ValueError(f"Unknown anchor: {anchor}")
|
|
68
|
+
|
|
69
|
+
return anchor_points[anchor]
|