figquilt 0.1.2__tar.gz → 0.1.3__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.2
3
+ Version: 0.1.3
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>
@@ -24,9 +24,16 @@ Description-Content-Type: text/markdown
24
24
 
25
25
  # figquilt
26
26
 
27
- **Figure quilter**: A CLI tool to compositing multiple figures (PDF, SVG, PNG) into a publication-ready figure layout.
27
+ **Figure quilter**: A declarative CLI tool for compositing multiple figures (PDF, SVG, PNG) into publication-ready layouts.
28
28
 
29
- `figquilt` takes a simple layout file (YAML) describing panels and their positions, composed of various inputs (plots from R/Python, diagrams, photos), and stitches them into a single output file (PDF, SVG) with precise dimension control and automatic labeling.
29
+ `figquilt` takes a simple layout file (YAML) describing panels and their structure, composed of various inputs (plots from R/Python, diagrams, photos), and stitches them into a single output file (PDF, SVG) with automatic labeling and precise dimension control.
30
+
31
+ ## Philosophy
32
+
33
+ - **Declarative over imperative**: Describe *what* your figure should look like, not *how* to construct it. Layouts are data, not scripts.
34
+ - **Structural composition first**: Prefer high-level layout (rows, columns, ratios) over manual coordinate placement. Let the tool handle positioning.
35
+ - **Fine control when needed**: Override with explicit coordinates and dimensions when precision matters.
36
+ - **Automation-friendly**: Designed to fit into reproducible workflows (Snakemake, Make, CI pipelines). No GUI, no manual steps.
30
37
 
31
38
  ## Features
32
39
 
@@ -1,8 +1,15 @@
1
1
  # figquilt
2
2
 
3
- **Figure quilter**: A CLI tool to compositing multiple figures (PDF, SVG, PNG) into a publication-ready figure layout.
3
+ **Figure quilter**: A declarative CLI tool for compositing multiple figures (PDF, SVG, PNG) into publication-ready layouts.
4
4
 
5
- `figquilt` takes a simple layout file (YAML) describing panels and their positions, composed of various inputs (plots from R/Python, diagrams, photos), and stitches them into a single output file (PDF, SVG) with precise dimension control and automatic labeling.
5
+ `figquilt` takes a simple layout file (YAML) describing panels and their structure, composed of various inputs (plots from R/Python, diagrams, photos), and stitches them into a single output file (PDF, SVG) with automatic labeling and precise dimension control.
6
+
7
+ ## Philosophy
8
+
9
+ - **Declarative over imperative**: Describe *what* your figure should look like, not *how* to construct it. Layouts are data, not scripts.
10
+ - **Structural composition first**: Prefer high-level layout (rows, columns, ratios) over manual coordinate placement. Let the tool handle positioning.
11
+ - **Fine control when needed**: Override with explicit coordinates and dimensions when precision matters.
12
+ - **Automation-friendly**: Designed to fit into reproducible workflows (Snakemake, Make, CI pipelines). No GUI, no manual steps.
6
13
 
7
14
  ## Features
8
15
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "figquilt"
3
- version = "0.1.2"
3
+ version = "0.1.3"
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 = [
@@ -1,15 +1,16 @@
1
1
  import fitz
2
2
  from pathlib import Path
3
3
  from .layout import Layout, Panel
4
- from .units import mm_to_pt
4
+ from .units import mm_to_pt, to_pt
5
5
  from .errors import FigQuiltError
6
6
 
7
7
 
8
8
  class PDFComposer:
9
9
  def __init__(self, layout: Layout):
10
10
  self.layout = layout
11
- self.width_pt = mm_to_pt(layout.page.width)
12
- self.height_pt = mm_to_pt(layout.page.height)
11
+ self.units = layout.page.units
12
+ self.width_pt = to_pt(layout.page.width, self.units)
13
+ self.height_pt = to_pt(layout.page.height, self.units)
13
14
 
14
15
  def compose(self, output_path: Path):
15
16
  doc = self.build()
@@ -63,9 +64,9 @@ class PDFComposer:
63
64
  self, doc: fitz.Document, page: fitz.Page, panel: Panel, index: int
64
65
  ):
65
66
  # Calculate position and size first
66
- x = mm_to_pt(panel.x)
67
- y = mm_to_pt(panel.y)
68
- w = mm_to_pt(panel.width)
67
+ x = to_pt(panel.x, self.units)
68
+ y = to_pt(panel.y, self.units)
69
+ w = to_pt(panel.width, self.units)
69
70
 
70
71
  # Determine height from aspect ratio if needed
71
72
  # We need to open the source to get aspect ratio
@@ -89,7 +90,7 @@ class PDFComposer:
89
90
  aspect = src_rect.height / src_rect.width
90
91
 
91
92
  if panel.height is not None:
92
- h = mm_to_pt(panel.height)
93
+ h = to_pt(panel.height, self.units)
93
94
  else:
94
95
  h = w * aspect
95
96
 
@@ -2,7 +2,7 @@ from pathlib import Path
2
2
  import base64
3
3
  from lxml import etree
4
4
  from .layout import Layout, Panel
5
- from .units import mm_to_pt
5
+ from .units import mm_to_pt, to_pt
6
6
  from .errors import FigQuiltError
7
7
  import fitz
8
8
 
@@ -10,8 +10,9 @@ import fitz
10
10
  class SVGComposer:
11
11
  def __init__(self, layout: Layout):
12
12
  self.layout = layout
13
- self.width_pt = mm_to_pt(layout.page.width)
14
- self.height_pt = mm_to_pt(layout.page.height)
13
+ self.units = layout.page.units
14
+ self.width_pt = to_pt(layout.page.width, self.units)
15
+ self.height_pt = to_pt(layout.page.height, self.units)
15
16
 
16
17
  def compose(self, output_path: Path):
17
18
  # Create root SVG element
@@ -20,8 +21,10 @@ class SVGComposer:
20
21
  "xlink": "http://www.w3.org/1999/xlink",
21
22
  }
22
23
  root = etree.Element("svg", nsmap=nsmap)
23
- root.set("width", f"{self.layout.page.width}mm")
24
- root.set("height", f"{self.layout.page.height}mm")
24
+ # SVG uses "in" for inches, "pt" for points, "mm" for millimeters
25
+ svg_unit = "in" if self.units == "inches" else self.units
26
+ root.set("width", f"{self.layout.page.width}{svg_unit}")
27
+ root.set("height", f"{self.layout.page.height}{svg_unit}")
25
28
  root.set("viewBox", f"0 0 {self.width_pt} {self.height_pt}")
26
29
  root.set("version", "1.1")
27
30
 
@@ -42,9 +45,9 @@ class SVGComposer:
42
45
  tree.write(f, pretty_print=True, xml_declaration=True, encoding="utf-8")
43
46
 
44
47
  def _place_panel(self, root: etree.Element, panel: Panel, index: int):
45
- x = mm_to_pt(panel.x)
46
- y = mm_to_pt(panel.y)
47
- w = mm_to_pt(panel.width)
48
+ x = to_pt(panel.x, self.units)
49
+ y = to_pt(panel.y, self.units)
50
+ w = to_pt(panel.width, self.units)
48
51
 
49
52
  # Determine content sizing
50
53
  # For simplicity in V0, relying on fitz for aspect ratio of all inputs (robust)
@@ -59,7 +62,7 @@ class SVGComposer:
59
62
  aspect = src_rect.height / src_rect.width
60
63
 
61
64
  if panel.height is not None:
62
- h = mm_to_pt(panel.height)
65
+ h = to_pt(panel.height, self.units)
63
66
  else:
64
67
  h = w * aspect
65
68
 
@@ -0,0 +1,25 @@
1
+ def mm_to_pt(mm: float) -> float:
2
+ """Converts millimeters to points (1 inch = 25.4 mm = 72 pts)."""
3
+ return mm * 72 / 25.4
4
+
5
+
6
+ def pt_to_mm(pt: float) -> float:
7
+ """Converts points to millimeters."""
8
+ return pt * 25.4 / 72
9
+
10
+
11
+ def inches_to_pt(inches: float) -> float:
12
+ """Converts inches to points (1 inch = 72 pts)."""
13
+ return inches * 72
14
+
15
+
16
+ def to_pt(value: float, units: str) -> float:
17
+ """Convert a value from the given units to points."""
18
+ if units == "mm":
19
+ return mm_to_pt(value)
20
+ elif units == "inches":
21
+ return inches_to_pt(value)
22
+ elif units == "pt":
23
+ return value
24
+ else:
25
+ raise ValueError(f"Unknown unit: {units}")
@@ -1,7 +0,0 @@
1
- def mm_to_pt(mm: float) -> float:
2
- """Converts millimeters to points (1 inch = 25.4 mm = 72 pts)."""
3
- return mm * 72 / 25.4
4
-
5
- def pt_to_mm(pt: float) -> float:
6
- """Converts points to millimeters."""
7
- return pt * 25.4 / 72
File without changes