figquilt 0.1.6__tar.gz → 0.1.7__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: figquilt
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: figquilt is a small, language-agnostic CLI tool that composes multiple figures (PDF/SVG/PNG) into a single publication-ready figure, based on a simple layout file (YAML/JSON). The key function is creating a PDF by composing multiple PDFs and adding subfigure labels and minimal annotations.
5
5
  Author: YY Ahn
6
6
  Author-email: YY Ahn <yongyeol@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "figquilt"
3
- version = "0.1.6"
3
+ version = "0.1.7"
4
4
  description = "figquilt is a small, language-agnostic CLI tool that composes multiple figures (PDF/SVG/PNG) into a single publication-ready figure, based on a simple layout file (YAML/JSON). The key function is creating a PDF by composing multiple PDFs and adding subfigure labels and minimal annotations."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -100,11 +100,11 @@ class PDFComposer:
100
100
  # No height specified: use source aspect ratio
101
101
  h = w * src_aspect
102
102
 
103
- # Calculate content rect using fit mode
103
+ # Calculate content rect using fit mode and alignment
104
104
  from .units import calculate_fit
105
105
 
106
106
  content_w, content_h, offset_x, offset_y = calculate_fit(
107
- src_aspect, w, h, panel.fit
107
+ src_aspect, w, h, panel.fit, panel.align
108
108
  )
109
109
  rect = fitz.Rect(
110
110
  x + offset_x,
@@ -71,11 +71,11 @@ class SVGComposer:
71
71
  else:
72
72
  h = w * src_aspect
73
73
 
74
- # Calculate content dimensions using fit mode
74
+ # Calculate content dimensions using fit mode and alignment
75
75
  from .units import calculate_fit
76
76
 
77
77
  content_w, content_h, offset_x, offset_y = calculate_fit(
78
- src_aspect, w, h, panel.fit
78
+ src_aspect, w, h, panel.fit, panel.align
79
79
  )
80
80
 
81
81
  # Group for the panel
@@ -58,6 +58,7 @@ def _resolve_node(
58
58
  width=width,
59
59
  height=height,
60
60
  fit=node.fit,
61
+ align=node.align,
61
62
  label=node.label,
62
63
  label_style=node.label_style,
63
64
  )
@@ -4,6 +4,17 @@ from pathlib import Path
4
4
  from pydantic import BaseModel, Field, field_validator, model_validator
5
5
 
6
6
  FitMode = Literal["contain", "cover"]
7
+ Alignment = Literal[
8
+ "center",
9
+ "top",
10
+ "bottom",
11
+ "left",
12
+ "right",
13
+ "top-left",
14
+ "top-right",
15
+ "bottom-left",
16
+ "bottom-right",
17
+ ]
7
18
 
8
19
 
9
20
  class LabelStyle(BaseModel):
@@ -38,6 +49,10 @@ class Panel(BaseModel):
38
49
  "contain",
39
50
  description="How to fit the figure: contain (preserve aspect) or cover (fill and clip)",
40
51
  )
52
+ align: Alignment = Field(
53
+ "center",
54
+ description="How to align content within cell when using contain fit mode",
55
+ )
41
56
  label: Optional[str] = Field(
42
57
  None, description="Override the auto-generated label text"
43
58
  )
@@ -76,6 +91,9 @@ class LayoutNode(BaseModel):
76
91
  None, description="Path to source file (PDF, SVG, or PNG)"
77
92
  )
78
93
  fit: FitMode = Field("contain", description="How to fit the figure in its cell")
94
+ align: Alignment = Field(
95
+ "center", description="How to align content within cell when using contain fit"
96
+ )
79
97
  label: Optional[str] = Field(
80
98
  None, description="Override the auto-generated label text"
81
99
  )
@@ -21,16 +21,22 @@ def to_pt(value: float, units: str) -> float:
21
21
 
22
22
 
23
23
  def calculate_fit(
24
- src_aspect: float, cell_w: float, cell_h: float, fit_mode: str
24
+ src_aspect: float,
25
+ cell_w: float,
26
+ cell_h: float,
27
+ fit_mode: str,
28
+ align: str = "center",
25
29
  ) -> tuple[float, float, float, float]:
26
30
  """
27
- Calculate content dimensions and offset based on fit mode.
31
+ Calculate content dimensions and offset based on fit mode and alignment.
28
32
 
29
33
  Args:
30
34
  src_aspect: Source aspect ratio (height / width)
31
35
  cell_w: Cell width in points
32
36
  cell_h: Cell height in points
33
37
  fit_mode: "contain" or "cover"
38
+ align: Alignment within cell (center, top, bottom, left, right,
39
+ top-left, top-right, bottom-left, bottom-right)
34
40
 
35
41
  Returns:
36
42
  Tuple of (content_w, content_h, offset_x, offset_y)
@@ -58,8 +64,41 @@ def calculate_fit(
58
64
  content_w = cell_w
59
65
  content_h = cell_w * src_aspect
60
66
 
61
- # Center in cell
62
- offset_x = (cell_w - content_w) / 2
63
- offset_y = (cell_h - content_h) / 2
67
+ # Calculate offsets based on alignment
68
+ space_x = cell_w - content_w
69
+ space_y = cell_h - content_h
70
+
71
+ # Parse alignment
72
+ if align == "center":
73
+ offset_x = space_x / 2
74
+ offset_y = space_y / 2
75
+ elif align == "top":
76
+ offset_x = space_x / 2
77
+ offset_y = 0
78
+ elif align == "bottom":
79
+ offset_x = space_x / 2
80
+ offset_y = space_y
81
+ elif align == "left":
82
+ offset_x = 0
83
+ offset_y = space_y / 2
84
+ elif align == "right":
85
+ offset_x = space_x
86
+ offset_y = space_y / 2
87
+ elif align == "top-left":
88
+ offset_x = 0
89
+ offset_y = 0
90
+ elif align == "top-right":
91
+ offset_x = space_x
92
+ offset_y = 0
93
+ elif align == "bottom-left":
94
+ offset_x = 0
95
+ offset_y = space_y
96
+ elif align == "bottom-right":
97
+ offset_x = space_x
98
+ offset_y = space_y
99
+ else:
100
+ # Unknown alignment, default to center
101
+ offset_x = space_x / 2
102
+ offset_y = space_y / 2
64
103
 
65
104
  return content_w, content_h, offset_x, offset_y
File without changes
File without changes