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.
- pdfmarq-0.1.0/PKG-INFO +109 -0
- pdfmarq-0.1.0/pdfmarq/__init__.py +57 -0
- pdfmarq-0.1.0/pdfmarq/_warn.py +36 -0
- pdfmarq-0.1.0/pdfmarq/constants.py +68 -0
- pdfmarq-0.1.0/pdfmarq/core.py +668 -0
- pdfmarq-0.1.0/pdfmarq/fonts.py +89 -0
- pdfmarq-0.1.0/pdfmarq/graphics.py +162 -0
- pdfmarq-0.1.0/pdfmarq/inline.py +361 -0
- pdfmarq-0.1.0/pdfmarq/layout.py +113 -0
- pdfmarq-0.1.0/pdfmarq/md/__init__.py +51 -0
- pdfmarq-0.1.0/pdfmarq/md/highlight.py +238 -0
- pdfmarq-0.1.0/pdfmarq/md/markdown.py +284 -0
- pdfmarq-0.1.0/pdfmarq/md/markdown_style.py +167 -0
- pdfmarq-0.1.0/pdfmarq/md/math.py +239 -0
- pdfmarq-0.1.0/pdfmarq/md/md_blockquote.py +117 -0
- pdfmarq-0.1.0/pdfmarq/md/md_blocks.py +330 -0
- pdfmarq-0.1.0/pdfmarq/md/md_estimate.py +63 -0
- pdfmarq-0.1.0/pdfmarq/md/md_fonts.py +99 -0
- pdfmarq-0.1.0/pdfmarq/md/md_footnotes.py +117 -0
- pdfmarq-0.1.0/pdfmarq/md/md_frontmatter.py +662 -0
- pdfmarq-0.1.0/pdfmarq/md/md_inline.py +292 -0
- pdfmarq-0.1.0/pdfmarq/md/md_list.py +90 -0
- pdfmarq-0.1.0/pdfmarq/md/md_plugins.py +102 -0
- pdfmarq-0.1.0/pdfmarq/md/md_preprocess.py +75 -0
- pdfmarq-0.1.0/pdfmarq/md/md_table.py +349 -0
- pdfmarq-0.1.0/pdfmarq/md/mermaid.py +132 -0
- pdfmarq-0.1.0/pdfmarq/md/openmoji.py +169 -0
- pdfmarq-0.1.0/pdfmarq/structure.py +118 -0
- pdfmarq-0.1.0/pdfmarq/styles.py +84 -0
- pdfmarq-0.1.0/pdfmarq/tables.py +148 -0
- pdfmarq-0.1.0/pdfmarq/text.py +193 -0
- pdfmarq-0.1.0/pdfmarq/utils.py +74 -0
- pdfmarq-0.1.0/pdfmarq.egg-info/PKG-INFO +109 -0
- pdfmarq-0.1.0/pdfmarq.egg-info/SOURCES.txt +38 -0
- pdfmarq-0.1.0/pdfmarq.egg-info/dependency_links.txt +1 -0
- pdfmarq-0.1.0/pdfmarq.egg-info/requires.txt +21 -0
- pdfmarq-0.1.0/pdfmarq.egg-info/top_level.txt +1 -0
- pdfmarq-0.1.0/pyproject.toml +24 -0
- pdfmarq-0.1.0/readme.md +79 -0
- 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"
|