pdfmarq 0.1.0__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.
Files changed (40) hide show
  1. pdfmarq-0.1.0/PKG-INFO +109 -0
  2. pdfmarq-0.1.0/pdfmarq/__init__.py +57 -0
  3. pdfmarq-0.1.0/pdfmarq/_warn.py +36 -0
  4. pdfmarq-0.1.0/pdfmarq/constants.py +68 -0
  5. pdfmarq-0.1.0/pdfmarq/core.py +668 -0
  6. pdfmarq-0.1.0/pdfmarq/fonts.py +89 -0
  7. pdfmarq-0.1.0/pdfmarq/graphics.py +162 -0
  8. pdfmarq-0.1.0/pdfmarq/inline.py +361 -0
  9. pdfmarq-0.1.0/pdfmarq/layout.py +113 -0
  10. pdfmarq-0.1.0/pdfmarq/md/__init__.py +51 -0
  11. pdfmarq-0.1.0/pdfmarq/md/highlight.py +238 -0
  12. pdfmarq-0.1.0/pdfmarq/md/markdown.py +284 -0
  13. pdfmarq-0.1.0/pdfmarq/md/markdown_style.py +167 -0
  14. pdfmarq-0.1.0/pdfmarq/md/math.py +239 -0
  15. pdfmarq-0.1.0/pdfmarq/md/md_blockquote.py +117 -0
  16. pdfmarq-0.1.0/pdfmarq/md/md_blocks.py +330 -0
  17. pdfmarq-0.1.0/pdfmarq/md/md_estimate.py +63 -0
  18. pdfmarq-0.1.0/pdfmarq/md/md_fonts.py +99 -0
  19. pdfmarq-0.1.0/pdfmarq/md/md_footnotes.py +117 -0
  20. pdfmarq-0.1.0/pdfmarq/md/md_frontmatter.py +662 -0
  21. pdfmarq-0.1.0/pdfmarq/md/md_inline.py +292 -0
  22. pdfmarq-0.1.0/pdfmarq/md/md_list.py +90 -0
  23. pdfmarq-0.1.0/pdfmarq/md/md_plugins.py +102 -0
  24. pdfmarq-0.1.0/pdfmarq/md/md_preprocess.py +75 -0
  25. pdfmarq-0.1.0/pdfmarq/md/md_table.py +349 -0
  26. pdfmarq-0.1.0/pdfmarq/md/mermaid.py +132 -0
  27. pdfmarq-0.1.0/pdfmarq/md/openmoji.py +169 -0
  28. pdfmarq-0.1.0/pdfmarq/structure.py +118 -0
  29. pdfmarq-0.1.0/pdfmarq/styles.py +84 -0
  30. pdfmarq-0.1.0/pdfmarq/tables.py +148 -0
  31. pdfmarq-0.1.0/pdfmarq/text.py +193 -0
  32. pdfmarq-0.1.0/pdfmarq/utils.py +74 -0
  33. pdfmarq-0.1.0/pdfmarq.egg-info/PKG-INFO +109 -0
  34. pdfmarq-0.1.0/pdfmarq.egg-info/SOURCES.txt +38 -0
  35. pdfmarq-0.1.0/pdfmarq.egg-info/dependency_links.txt +1 -0
  36. pdfmarq-0.1.0/pdfmarq.egg-info/requires.txt +21 -0
  37. pdfmarq-0.1.0/pdfmarq.egg-info/top_level.txt +1 -0
  38. pdfmarq-0.1.0/pyproject.toml +24 -0
  39. pdfmarq-0.1.0/readme.md +79 -0
  40. pdfmarq-0.1.0/setup.cfg +4 -0
pdfmarq-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: pdfmarq
3
+ Version: 0.1.0
4
+ Summary: PDF generation library with fluent API and optional markdown support
5
+ Author: Xaeian
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/Xaeian/PDFMarQ
8
+ Keywords: pdf,reportlab,document,generation,markdown
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: reportlab
12
+ Requires-Dist: Pillow
13
+ Requires-Dist: svglib
14
+ Provides-Extra: md
15
+ Requires-Dist: PyYAML; extra == "md"
16
+ Requires-Dist: Pygments; extra == "md"
17
+ Requires-Dist: emoji; extra == "md"
18
+ Requires-Dist: markdown-it-py; extra == "md"
19
+ Requires-Dist: matplotlib; extra == "md"
20
+ Requires-Dist: mdit-py-emoji; extra == "md"
21
+ Requires-Dist: mdit-py-plugins; extra == "md"
22
+ Provides-Extra: all
23
+ Requires-Dist: PyYAML; extra == "all"
24
+ Requires-Dist: Pygments; extra == "all"
25
+ Requires-Dist: emoji; extra == "all"
26
+ Requires-Dist: markdown-it-py; extra == "all"
27
+ Requires-Dist: matplotlib; extra == "all"
28
+ Requires-Dist: mdit-py-emoji; extra == "all"
29
+ Requires-Dist: mdit-py-plugins; extra == "all"
30
+
31
+ # PDFMarQ
32
+
33
+ PDF generation with a fluent API. Core is lean _(reportlab + Pillow + svglib)_. Optional `[md]` extra adds full markdown-to-PDF rendering with frontmatter headers, math, mermaid, syntax highlighting and more.
34
+
35
+ ## Philosophy
36
+
37
+ PDFMarQ wraps reportlab's stateful canvas into a fluent, cursor-based API. You describe document flow, not coordinates. Markdown rendering lives in a separate subpackage so the core stays installable without heavyweight dependencies.
38
+
39
+ - **Fluent over imperative**: `pdf.font("Helvetica", 12).text("Hi").enter().text("World")` vs `canvas.setFont() → canvas.drawString() → manual Y tracking`
40
+ - **Cursor flows naturally**: top-left origin, `y` grows down, `enter()` is a newline
41
+ - **One way per feature**: `pdf.table()`, `pdf.image()`, `pdf.svg()`, `pdf.link()`, no overloaded call signatures
42
+ - **Markdown is optional**: core → 3 deps, `[md]` adds the stack
43
+ - **Frontmatter as contract**: YAML block at top of markdown becomes a styled header with logo, status badge, version, dates, signature slot
44
+ - **Lean output**: no headless Chrome, no web stack, no React SSR. Pure Python + native PDF primitives. Files stay small, rendering stays fast, fonts are embedded properly, and the output opens clean in every PDF reader
45
+
46
+ Trade-offs:
47
+ - Cursor mutation is a state machine. Great for linear documents, awkward for complex grid layouts. For those, drop into raw reportlab via `pdf._canvas`.
48
+ - Markdown rendering estimates heights analytically to decide page breaks. Good enough for 95% of content. Edge cases with math + wide tables may push onto the next page more aggressively than necessary.
49
+ - Installing Python + a stack of deps is a barrier for non-technical users. If you're building a tool end-users will actually touch, put PDFMarQ behind a backend service _(FastAPI endpoint, CLI wrapper, desktop app)_ rather than asking them to `pip install` anything.
50
+
51
+ ## Install
52
+
53
+ ```sh
54
+ pip install pdfmarq # core: reportlab, Pillow, svglib
55
+ pip install pdfmarq[md] # + markdown rendering stack
56
+ ```
57
+
58
+ ## Examples
59
+
60
+ ```py
61
+ from pdfmarq import PDF
62
+ # Fluent core API
63
+ with PDF("report.pdf") as pdf:
64
+ pdf.font("Helvetica", 20, "Bold").text("Quarterly Report")
65
+ pdf.enter().font(size=12, mode="Regular")
66
+ pdf.text("Revenue up 23% year-over-year.")
67
+ pdf.table(
68
+ [["Q1", "120k"], ["Q2", "148k"], ["Q3", "172k"]],
69
+ header=["Quarter", "Revenue"],
70
+ sizes=[1, 2], aligns=["C", "R"],
71
+ )
72
+ pdf.image("chart.png", 180, 80)
73
+ pdf.link("https://xaeian.com", 40, 5)
74
+ ```
75
+
76
+ ```py
77
+ from pdfmarq.md import md_to_pdf, MarkdownStyle
78
+ # Markdown to PDF
79
+ style = MarkdownStyle(
80
+ body_family="IBMPlexSans",
81
+ heading_family="Sora",
82
+ page_number_label="Page", # "Page 1/5" in footer
83
+ )
84
+ md_to_pdf(open("doc.md").read(), "doc.pdf", style=style, font_dir="./fonts")
85
+ ```
86
+
87
+ ## Markdown features
88
+
89
+ - GitHub-flavored markdown _(tables, fenced code, lists, strikethrough)_
90
+ - YAML frontmatter with styled document header _(logo, status badge, version, sign block, landscape flag)_
91
+ - Mini-header on continuation pages with aspect-aware logo _(width + height caps)_
92
+ - Page numbering `Page N/M` via deferred canvas rendering _(configurable)_
93
+ - Configurable frontmatter labels for localization _(`fm_label_author`, `fm_label_created`, …)_
94
+ - Skip-duplicate-title: drops `# X` when it matches frontmatter `title`
95
+ - Auto-slugged headings with clickable `[text](#anchor)` internal links _(unicode-aware, broken targets degrade to plain text)_
96
+ - Local-path links configurable via `link_root` + `link_base` _(or per-doc YAML `base:`)_
97
+ - Syntax highlighting _(Pygments)_
98
+ - Math formulas inline `$x^2$` and block `$$...$$` _(matplotlib)_
99
+ - Mermaid diagrams via `mermaid-cli` _(local)_ or `mermaid.ink` _(network fallback)_, capped at a configurable max height
100
+ - Footnotes, emoji shortcodes `:rocket:`, nested lists, blockquotes, GitHub callouts _(`> [!NOTE]`, `> [!WARNING]`, …)_
101
+ - Zebra-striped tables _(subtle, readability without noise)_
102
+ - Smart page breaks for paragraphs, tables, lists, and blockquotes _(pre-measured, no orphans)_
103
+
104
+ ## Modules
105
+
106
+ | Module | Description | Docs |
107
+ | ------------ | -------------------------------------------------- | -------------------------------------------- |
108
+ | `pdfmarq` | Core PDF API _(fluent cursor-based drawing)_ | [pdfmarq/readme.md](pdfmarq/readme.md) |
109
+ | `pdfmarq.md` | Markdown-to-PDF renderer _(optional `[md]` extra)_ | [pdfmarq/md/readme.md](pdfmarq/md/readme.md) |
@@ -0,0 +1,57 @@
1
+ # pdfmarq/__init__.py
2
+
3
+ """
4
+ PDF generation with fluent API. Built on reportlab.
5
+
6
+ Low-level drawing only - no markdown. For markdown rendering install
7
+ the `[md]` extra and import from `pdfmarq.md`:
8
+
9
+ pip install pdfmarq[md]
10
+ from pdfmarq.md import md_to_pdf, MarkdownStyle
11
+
12
+ Example:
13
+ >>> from pdfmarq import PDF, Align
14
+ >>> with PDF("output.pdf") as pdf:
15
+ ... pdf.font("Helvetica", 12, "Bold")
16
+ ... pdf.text("Hello World", align=Align.CENTER)
17
+ """
18
+
19
+ #----------------------------------------------------------------------- Metadata for auto-toml
20
+
21
+ __version__ = "0.1.0"
22
+ __repo__ = "Xaeian/PDFMarQ"
23
+ __python__ = ">=3.10"
24
+ __description__ = "PDF generation library with fluent API and optional markdown support"
25
+ __author__ = "Xaeian"
26
+ __keywords__ = ["pdf", "reportlab", "document", "generation", "markdown"]
27
+ __dependencies__ = ["reportlab", "Pillow", "svglib"]
28
+
29
+ #----------------------------------------------------------------------------------- Public API
30
+
31
+ from .constants import (
32
+ Unit, PageSize, Align, Colors, Defaults, MM_TO_PT,
33
+ A4, A3, A5, LETTER, LEGAL,
34
+ )
35
+ from .styles import Style, TableStyle, Styles
36
+ from .layout import Cursor, PageGeometry
37
+ from .text import TextMetrics, BoxFitResult
38
+ from .tables import TableBuilder, TableData, Cell
39
+ from .fonts import FontManager
40
+ from .structure import Metadata, Bookmark, TOCEntry, BookmarkManager, LinkManager
41
+ from .utils import to_mm, to_pt, mm_to_pt, parse_color, color_alpha, parse_margin
42
+ from .inline import RichSegment, render_rich
43
+ from .core import PDF
44
+
45
+ __all__ = [
46
+ "PDF",
47
+ "Unit", "PageSize", "Align", "Colors", "Defaults", "MM_TO_PT",
48
+ "A4", "A3", "A5", "LETTER", "LEGAL",
49
+ "Style", "TableStyle", "Styles",
50
+ "Cursor", "PageGeometry",
51
+ "TextMetrics", "BoxFitResult",
52
+ "TableBuilder", "TableData", "Cell",
53
+ "FontManager",
54
+ "Metadata", "Bookmark", "TOCEntry", "BookmarkManager", "LinkManager",
55
+ "to_mm", "to_pt", "mm_to_pt", "parse_color", "color_alpha", "parse_margin",
56
+ "RichSegment", "render_rich",
57
+ ]
@@ -0,0 +1,36 @@
1
+ # pdfmarq/_warn.py
2
+
3
+ """
4
+ Deduplicated warning printer for missing optional dependencies.
5
+
6
+ When a feature degrades because an optional package is absent, call
7
+ `warn_missing(key, package, feature)` once. Repeated calls with the same
8
+ key are suppressed so renders do not spam the console.
9
+
10
+ Example:
11
+ >>> from ._warn import warn_missing
12
+ >>> warn_missing("matplotlib", "matplotlib", "math formulas")
13
+ pdfmarq: math formulas disabled, install with: pip install matplotlib
14
+ """
15
+ import sys
16
+
17
+ _seen: set = set()
18
+
19
+ def warn_missing(key:str, package:str, feature:str) -> None:
20
+ """Print a one-time warning about a missing optional package.
21
+ Args:
22
+ key: Deduplication key (typically the import name).
23
+ package: pip-installable name (may differ from import name).
24
+ feature: Short human description of what was disabled.
25
+ """
26
+ if key in _seen:
27
+ return
28
+ _seen.add(key)
29
+ print(
30
+ f"pdfmarq: {feature} disabled, install with: pip install {package}",
31
+ file=sys.stderr,
32
+ )
33
+
34
+ def reset_warnings() -> None:
35
+ """Clear the seen-warnings registry. Used in tests."""
36
+ _seen.clear()
@@ -0,0 +1,68 @@
1
+ # pdfmarq/constants.py
2
+
3
+ """Constants for PDF library - units, colors, defaults."""
4
+ from dataclasses import dataclass
5
+
6
+ # Exact mm ↔ pt conversion (1 inch = 72pt = 25.4mm)
7
+ MM_TO_PT = 72 / 25.4 # ≈ 2.8346456692913384
8
+
9
+ #---------------------------------------------------------------------------------------- Units
10
+
11
+ class Unit:
12
+ """Unit conversion factors to millimeters."""
13
+ MM = 1.0
14
+ CM = 10.0
15
+ INCH = 25.4
16
+ PT = 25.4 / 72 # ≈ 0.35278 mm - exact
17
+ PX = 25.4 / 96 # ≈ 0.26458 mm - 96 DPI
18
+
19
+ #------------------------------------------------------------------------------------- PageSize
20
+
21
+ @dataclass
22
+ class PageSize:
23
+ """Common page sizes in mm."""
24
+ width: float
25
+ height: float
26
+ def landscape(self) -> "PageSize":
27
+ return PageSize(self.height, self.width)
28
+
29
+ A4 = PageSize(210, 297)
30
+ A3 = PageSize(297, 420)
31
+ A5 = PageSize(148, 210)
32
+ LETTER = PageSize(215.9, 279.4)
33
+ LEGAL = PageSize(215.9, 355.6)
34
+
35
+ #---------------------------------------------------------------------------------------- Align
36
+
37
+ class Align:
38
+ """Text/element alignment constants."""
39
+ LEFT = "L"
40
+ RIGHT = "R"
41
+ CENTER = "C"
42
+ JUSTIFY = "J"
43
+
44
+ #--------------------------------------------------------------------------------------- Colors
45
+
46
+ class Colors:
47
+ """Predefined colors as (r, g, b) tuples (0-1 range)."""
48
+ BLACK = (0, 0, 0)
49
+ WHITE = (1, 1, 1)
50
+ RED = (1, 0, 0)
51
+ GREEN = (0, 1, 0)
52
+ BLUE = (0, 0, 1)
53
+ GREY = (0.5, 0.5, 0.5)
54
+ LIGHT_GREY = (0.8, 0.8, 0.8)
55
+ DARK_GREY = (0.3, 0.3, 0.3)
56
+
57
+ #------------------------------------------------------------------------------------- Defaults
58
+
59
+ class Defaults:
60
+ """Default values for PDF generation."""
61
+ PAGE_WIDTH = 210
62
+ PAGE_HEIGHT = 297
63
+ MARGIN = 15
64
+ FONT_FAMILY = "Helvetica"
65
+ FONT_SIZE = 12
66
+ FONT_MODE = "Regular"
67
+ LINE_HEIGHT = 1.2
68
+ UNIT = "mm"