pptx-lint 0.1.0__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.
pptx_lint/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """pptx-lint: Quality checker for python-pptx presentations."""
2
+
3
+ __version__ = "0.1.0"
4
+ __description__ = (
5
+ "Detects overflow, overlap, fake tables, small fonts, "
6
+ "and empty slides in python-pptx generated presentations."
7
+ )
pptx_lint/checker.py ADDED
@@ -0,0 +1,163 @@
1
+ """Core checker: orchestrate all lint rules against one or more PPTX files."""
2
+
3
+ import os
4
+ from pptx import Presentation
5
+
6
+ from .utils import emu_to_inches, emu_to_pt
7
+ from . import rules
8
+
9
+
10
+ def get_shape_bounds(shape):
11
+ """Return (left, top, width, height, right, bottom) in inches."""
12
+ try:
13
+ left = emu_to_inches(shape.left)
14
+ top = emu_to_inches(shape.top)
15
+ w = emu_to_inches(shape.width)
16
+ h = emu_to_inches(shape.height)
17
+ return left, top, w, h, left + w, top + h
18
+ except Exception:
19
+ return None
20
+
21
+
22
+ def get_shape_texts(shape):
23
+ """Extract plain text from a shape (text-frame or table)."""
24
+ texts = []
25
+ if shape.has_text_frame:
26
+ for p in shape.text_frame.paragraphs:
27
+ t = p.text.strip()
28
+ if t:
29
+ texts.append(t)
30
+ if shape.has_table:
31
+ table = shape.table
32
+ for row in table.rows:
33
+ parts = []
34
+ for cell in row.cells:
35
+ t = cell.text.strip()
36
+ if t:
37
+ parts.append(t)
38
+ if parts:
39
+ texts.append(' | '.join(parts))
40
+ return texts
41
+
42
+
43
+ def collect_shapes_info(slide):
44
+ """Return list of shape info dicts for a single slide."""
45
+ infos = []
46
+ for idx, shape in enumerate(slide.shapes):
47
+ bounds = get_shape_bounds(shape)
48
+ if bounds is None:
49
+ continue
50
+
51
+ texts = get_shape_texts(shape)
52
+
53
+ font_info = []
54
+ if shape.has_text_frame:
55
+ for p in shape.text_frame.paragraphs:
56
+ if p.text.strip():
57
+ sz = p.font.size
58
+ font_info.append({
59
+ 'text': p.text.strip(),
60
+ 'size': emu_to_pt(sz) if sz else None,
61
+ })
62
+
63
+ infos.append({
64
+ 'idx': idx,
65
+ 'bounds': bounds,
66
+ 'texts': texts,
67
+ 'font_info': font_info,
68
+ 'type': str(shape.shape_type),
69
+ })
70
+ return infos
71
+
72
+
73
+ def check_slide(slide, slide_num, sw, sh):
74
+ """Run all lint rules against one slide.
75
+
76
+ Returns a dict with keys:
77
+ slide_num, errors, warnings, infos, text_content, is_empty
78
+ """
79
+ result = {
80
+ 'slide_num': slide_num,
81
+ 'total_shapes': len(slide.shapes),
82
+ 'errors': [],
83
+ 'warnings': [],
84
+ 'infos': [],
85
+ 'text_content': [],
86
+ 'is_empty': True,
87
+ }
88
+
89
+ shapes_info = collect_shapes_info(slide)
90
+ for info in shapes_info:
91
+ result['text_content'].extend(info['texts'])
92
+ result['is_empty'] = len(result['text_content']) == 0
93
+
94
+ # ── overflow (error) ──
95
+ for issue in rules.overflow.check(sw, sh, shapes_info):
96
+ result['errors'].append({
97
+ 'type': 'Overflow',
98
+ 'detail': (
99
+ f"Shape#{issue['shape_idx']} ({issue['shape_name']}): "
100
+ f"{' | '.join(issue['issues'])}"
101
+ ),
102
+ 'bounds': (
103
+ f"({issue['bounds'][0]:.2f}, {issue['bounds'][1]:.2f}, "
104
+ f"{issue['bounds'][2]:.2f}, {issue['bounds'][3]:.2f})"
105
+ ),
106
+ })
107
+
108
+ # ── empty slide (error) ──
109
+ if result['total_shapes'] == 0:
110
+ result['errors'].append({
111
+ 'type': 'EmptySlide',
112
+ 'detail': 'Slide has no shapes at all.',
113
+ })
114
+
115
+ # ── overlap (warning) ──
116
+ for issue in rules.overlap.check(shapes_info):
117
+ result['warnings'].append({
118
+ 'type': 'Overlap',
119
+ 'detail': (
120
+ f"Shape#{issue['shapes'][0]} ({issue['names'][0]}) "
121
+ f"overlaps Shape#{issue['shapes'][1]} ({issue['names'][1]}) "
122
+ f"— overlap area: {issue['overlap_area']}"
123
+ ),
124
+ })
125
+
126
+ # ── fake table (warning) ──
127
+ for issue in rules.fake_table.check(shapes_info):
128
+ samples = ', '.join(issue['sample_texts'])
129
+ result['warnings'].append({
130
+ 'type': 'FakeTable',
131
+ 'detail': (
132
+ f"{issue['shape_count']} text boxes form a grid-like layout "
133
+ f"(e.g.: {samples}) — consider using ``add_table()`` instead."
134
+ ),
135
+ })
136
+
137
+ # ── small font (info) ──
138
+ for issue in rules.font_size.check(shapes_info):
139
+ result['infos'].append({
140
+ 'type': 'SmallFont',
141
+ 'detail': (
142
+ f"Shape#{issue['shape_idx']}: '{issue['text']}' "
143
+ f"font size = {issue['font_size']:.0f}pt "
144
+ f"(< {rules.font_size.MIN_FONT_SIZE}pt)"
145
+ ),
146
+ })
147
+
148
+ return result
149
+
150
+
151
+ def check_pptx(filepath):
152
+ """Run all checks on a single .pptx file.
153
+
154
+ Returns list of slide-level result dicts.
155
+ """
156
+ prs = Presentation(filepath)
157
+ sw = emu_to_inches(prs.slide_width)
158
+ sh = emu_to_inches(prs.slide_height)
159
+
160
+ all_results = []
161
+ for si, slide in enumerate(prs.slides):
162
+ all_results.append(check_slide(slide, si + 1, sw, sh))
163
+ return all_results, os.path.basename(filepath)
pptx_lint/cli.py ADDED
@@ -0,0 +1,72 @@
1
+ """Command-line interface for pptx-lint."""
2
+
3
+ import sys
4
+ import os
5
+
6
+ from . import __version__
7
+ from .checker import check_pptx
8
+ from .reporters.console import print_report
9
+
10
+
11
+ def collect_targets(args):
12
+ """Resolve CLI args to a list of .pptx file paths."""
13
+ files = []
14
+ for arg in args:
15
+ if os.path.isdir(arg):
16
+ for f in os.listdir(arg):
17
+ if f.endswith('.pptx'):
18
+ files.append(os.path.join(arg, f))
19
+ elif os.path.isfile(arg) and arg.endswith('.pptx'):
20
+ files.append(arg)
21
+ else:
22
+ print(f"[pptx-lint] Skipping: {arg} (not a .pptx file)")
23
+ return files
24
+
25
+
26
+ def main():
27
+ if len(sys.argv) < 2:
28
+ print(f"pptx-lint v{__version__}")
29
+ print()
30
+ print("Usage: pptx-lint <file.pptx> [file.pptx ...]")
31
+ print(" pptx-lint <directory>")
32
+ print()
33
+ print("Examples:")
34
+ print(" pptx-lint my_presentation.pptx")
35
+ print(" pptx-lint ./output/")
36
+ sys.exit(1)
37
+
38
+ targets = collect_targets(sys.argv[1:])
39
+ if not targets:
40
+ print("[pptx-lint] No .pptx files found.")
41
+ sys.exit(1)
42
+
43
+ print(f"\n{'=' * 70}")
44
+ print(f" pptx-lint v{__version__} — {len(targets)} file(s)")
45
+ print(f"{'=' * 70}")
46
+
47
+ grand_total = {'errors': 0, 'warnings': 0, 'infos': 0}
48
+
49
+ for fpath in targets:
50
+ results, name = check_pptx(fpath)
51
+ total_e = sum(len(r['errors']) for r in results)
52
+ total_w = sum(len(r['warnings']) for r in results)
53
+ total_i = sum(len(r['infos']) for r in results)
54
+ grand_total['errors'] += total_e
55
+ grand_total['warnings'] += total_w
56
+ grand_total['infos'] += total_i
57
+
58
+ print_report(results, name)
59
+
60
+ print(f"{'=' * 70}")
61
+ print(f" Grand total across all files:")
62
+ print(f" 🔴 {grand_total['errors']} errors")
63
+ print(f" 🟡 {grand_total['warnings']} warnings")
64
+ print(f" â„šī¸ {grand_total['infos']} infos")
65
+ print(f"{'=' * 70}\n")
66
+
67
+ if grand_total['errors'] > 0:
68
+ sys.exit(1)
69
+
70
+
71
+ if __name__ == '__main__':
72
+ main()
@@ -0,0 +1 @@
1
+ """Reporters for pptx-lint (pluggable output formats)."""
@@ -0,0 +1,58 @@
1
+ """Console (terminal) reporter for pptx-lint."""
2
+
3
+ from ..utils import red, yellow, cyan, blue, green, bold
4
+
5
+
6
+ def print_report(all_results, filename):
7
+ """Print a colourised per-file report to stdout."""
8
+ total_e = sum(len(r['errors']) for r in all_results)
9
+ total_w = sum(len(r['warnings']) for r in all_results)
10
+ total_i = sum(len(r['infos']) for r in all_results)
11
+ total_slides = len(all_results)
12
+ empty = sum(1 for r in all_results if r['is_empty'])
13
+
14
+ print(f"\n{'=' * 70}")
15
+ print(f" {bold('pptx-lint report')}")
16
+ print(f" File: {blue(filename)}")
17
+ print(f"{'=' * 70}")
18
+ print(f" Slides: {total_slides} | Empty: {empty}")
19
+ print(f" {red(f'Errors: {total_e}')} "
20
+ f"{yellow(f'Warnings: {total_w}')} "
21
+ f"{cyan(f'Infos: {total_i}')}")
22
+ print(f"{'=' * 70}")
23
+
24
+ for result in all_results:
25
+ sn = result['slide_num']
26
+ if not (result['errors'] or result['warnings'] or result['infos']):
27
+ continue
28
+
29
+ label = f'[Slide {sn}]'
30
+ empty_label = ' (EMPTY)' if result['is_empty'] else ''
31
+ print(f"\n{green(label)}{empty_label}")
32
+
33
+ if result['text_content']:
34
+ preview = ' | '.join(result['text_content'][:2])
35
+ if len(preview) > 80:
36
+ preview = preview[:80] + '...'
37
+ print(f" {blue('Content:')} {preview}")
38
+
39
+ for e in result['errors']:
40
+ et = e['type']
41
+ print(f" {red(' ✗ [' + et + ']')} {e['detail']}")
42
+
43
+ for w in result['warnings']:
44
+ wt = w['type']
45
+ print(f" {yellow(' ▲ [' + wt + ']')} {w['detail']}")
46
+
47
+ for i in result['infos']:
48
+ it = i['type']
49
+ print(f" {cyan(' i [' + it + ']')} {i['detail']}")
50
+
51
+ # Summary line
52
+ if total_e == 0 and total_w == 0:
53
+ print(f"\n {green('✓ No issues found!')}")
54
+ elif total_e == 0:
55
+ print(f"\n {yellow('⚠ Warnings only, no errors.')}")
56
+ else:
57
+ print(f"\n {red('✗ Errors detected — review above.')}")
58
+ print()
@@ -0,0 +1,8 @@
1
+ """Lint rules for pptx-lint."""
2
+
3
+ from . import overflow
4
+ from . import overlap
5
+ from . import fake_table
6
+ from . import font_size
7
+
8
+ __all__ = ['overflow', 'overlap', 'fake_table', 'font_size']
@@ -0,0 +1,65 @@
1
+ """Fake-table rule: detect multiple text boxes arranged as a grid
2
+ that should probably be a proper ``Table`` object instead.
3
+ """
4
+
5
+ EPSILON = 0.25 # inches — alignment tolerance
6
+
7
+
8
+ def check(shapes_info):
9
+ """Return list of fake-table issues (warning severity).
10
+
11
+ Detection criteria:
12
+ * at least 4 text-carrying shapes
13
+ * shapes can be grouped into â‰Ĩ2 rows (by Y-centre)
14
+ * at least 2 of those rows have â‰Ĩ2 shapes each
15
+ """
16
+ text_shapes = []
17
+ for info in shapes_info:
18
+ l, t, w, h, r, b = info['bounds']
19
+ if w > 0.3 and h > 0.2 and info['texts'] and l >= 0 and t >= 0:
20
+ text_shapes.append({
21
+ 'idx': info['idx'],
22
+ 'left': l, 'top': t, 'w': w, 'h': h,
23
+ 'cx': l + w / 2, 'cy': t + h / 2,
24
+ 'texts': info['texts'],
25
+ })
26
+
27
+ if len(text_shapes) < 4:
28
+ return []
29
+
30
+ # Group by row (similar Y-centre)
31
+ rows = []
32
+ used = set()
33
+ for ts in text_shapes:
34
+ if ts['idx'] in used:
35
+ continue
36
+ row = [ts]
37
+ used.add(ts['idx'])
38
+ for ts2 in text_shapes:
39
+ if ts2['idx'] in used:
40
+ continue
41
+ if abs(ts2['cy'] - ts['cy']) < EPSILON:
42
+ row.append(ts2)
43
+ used.add(ts2['idx'])
44
+ rows.append(row)
45
+
46
+ if len(rows) < 2:
47
+ return []
48
+
49
+ multi_col = [r for r in rows if len(r) >= 2]
50
+ if len(multi_col) < 2:
51
+ return []
52
+
53
+ total = sum(len(r) for r in multi_col)
54
+ samples = []
55
+ for r in multi_col[:2]:
56
+ for ts in r[:2]:
57
+ if ts['texts']:
58
+ samples.append(ts['texts'][0][:30])
59
+
60
+ return [{
61
+ 'shape_count': total,
62
+ 'rows': len(multi_col),
63
+ 'sample_texts': samples[:3],
64
+ 'severity': 'WARN',
65
+ }]
@@ -0,0 +1,18 @@
1
+ """Font-size rule: detect text smaller than a minimum threshold."""
2
+
3
+ MIN_FONT_SIZE = 8 # points
4
+
5
+
6
+ def check(shapes_info):
7
+ """Return list of font-size issues (info severity)."""
8
+ issues = []
9
+ for info in shapes_info:
10
+ for fi in info.get('font_info', []):
11
+ if fi['size'] is not None and fi['size'] < MIN_FONT_SIZE:
12
+ issues.append({
13
+ 'shape_idx': info['idx'],
14
+ 'text': fi['text'][:40],
15
+ 'font_size': fi['size'],
16
+ 'severity': 'INFO',
17
+ })
18
+ return issues
@@ -0,0 +1,45 @@
1
+ """Overflow rule: detect shapes that exceed slide boundaries."""
2
+
3
+ from ..utils import emu_to_inches
4
+
5
+
6
+ def check(slide_width, slide_height, shapes_info):
7
+ """Return list of overflow issues.
8
+
9
+ Each issue dict:
10
+ shape_idx, shape_name, bounds, issues (list of str), texts
11
+ """
12
+ issues = []
13
+ sw = slide_width # already in inches
14
+ sh = slide_height
15
+
16
+ TOLERANCE = 0.05 # inches, small positive margin
17
+
18
+ for info in shapes_info:
19
+ left, top, width, height, right, bottom = info['bounds']
20
+ name = (info['texts'][0] if info['texts']
21
+ else f"Shape {info['idx']}")
22
+
23
+ overflow_msgs = []
24
+ if left < 0:
25
+ overflow_msgs.append(f"left overflow ({left:.2f}\")")
26
+ if top < 0:
27
+ overflow_msgs.append(f"top overflow ({top:.2f}\")")
28
+ if right > sw + TOLERANCE:
29
+ overflow_msgs.append(
30
+ f"right overflow (r={right:.2f}\" > {sw:.2f}\")")
31
+ if bottom > sh + TOLERANCE:
32
+ overflow_msgs.append(
33
+ f"bottom overflow (b={bottom:.2f}\" > {sh:.2f}\")")
34
+
35
+ if overflow_msgs:
36
+ issues.append({
37
+ 'shape_idx': info['idx'],
38
+ 'shape_name': name,
39
+ 'bounds': (left, top, width, height),
40
+ 'issues': overflow_msgs,
41
+ 'severity': 'ERROR',
42
+ 'texts': info['texts'],
43
+ })
44
+
45
+ return issues
@@ -0,0 +1,48 @@
1
+ """Overlap rule: detect shapes that overlap each other."""
2
+
3
+
4
+ def check(shapes_info):
5
+ """Return list of overlap issues (warning severity)."""
6
+ issues = []
7
+ rects = []
8
+
9
+ for info in shapes_info:
10
+ l, t, w, h, r, b = info['bounds']
11
+ if w == 0 or h == 0:
12
+ continue
13
+ rects.append((info['idx'], l, t, r, b, info['texts'], w * h))
14
+
15
+ if not rects:
16
+ return issues
17
+
18
+ max_area = max(a for (_, _, _, _, _, _, a) in rects)
19
+
20
+ for i in range(len(rects)):
21
+ for j in range(i + 1, len(rects)):
22
+ idx1, l1, t1, r1, b1, t1s, a1 = rects[i]
23
+ idx2, l2, t2, r2, b2, t2s, a2 = rects[j]
24
+
25
+ # Skip full-slide background shapes (no text, huge area)
26
+ if a1 > max_area * 0.8 and not t1s:
27
+ continue
28
+ if a2 > max_area * 0.8 and not t2s:
29
+ continue
30
+
31
+ ox = max(0.0, min(r1, r2) - max(l1, l2))
32
+ oy = max(0.0, min(b1, b2) - max(t1, t2))
33
+
34
+ if ox > 0 and oy > 0:
35
+ oa = ox * oy
36
+ threshold = min(a1, a2) * 0.5
37
+ if oa > threshold:
38
+ name1 = t1s[0] if t1s else f"Shape {idx1}"
39
+ name2 = t2s[0] if t2s else f"Shape {idx2}"
40
+ issues.append({
41
+ 'shapes': (idx1, idx2),
42
+ 'names': (name1, name2),
43
+ 'overlap_area': f"{oa:.2f} sq.in.",
44
+ 'severity': 'WARN',
45
+ 'texts': (t1s, t2s),
46
+ })
47
+
48
+ return issues
pptx_lint/utils.py ADDED
@@ -0,0 +1,44 @@
1
+ """Shared utilities for pptx-lint."""
2
+
3
+ from pptx.util import Emu, Pt
4
+
5
+
6
+ def emu_to_inches(emu):
7
+ """Convert EMU (English Metric Unit) to inches."""
8
+ if emu is None:
9
+ return 0.0
10
+ return emu / 914400
11
+
12
+
13
+ def emu_to_pt(emu):
14
+ """Convert EMU to points (font size)."""
15
+ if emu is None:
16
+ return None
17
+ return emu / 12700
18
+
19
+
20
+ def inches(x):
21
+ """Alias: convert inches to EMU via python-pptx helper.""" # noqa
22
+ # We keep the emu_to_inches path for readability.
23
+ # Actual inches-to-EMU is done by pptx.util.Inches downstream.
24
+ return x
25
+
26
+
27
+ # ── Terminal colours ──────────────────────────────────────────
28
+
29
+ class Colors:
30
+ RED = '\033[91m'
31
+ YELLOW = '\033[93m'
32
+ BLUE = '\033[94m'
33
+ CYAN = '\033[96m'
34
+ GREEN = '\033[92m'
35
+ RESET = '\033[0m'
36
+ BOLD = '\033[1m'
37
+
38
+
39
+ def red(s): return f"{Colors.RED}{s}{Colors.RESET}"
40
+ def yellow(s): return f"{Colors.YELLOW}{s}{Colors.RESET}"
41
+ def blue(s): return f"{Colors.BLUE}{s}{Colors.RESET}"
42
+ def cyan(s): return f"{Colors.CYAN}{s}{Colors.RESET}"
43
+ def green(s): return f"{Colors.GREEN}{s}{Colors.RESET}"
44
+ def bold(s): return f"{Colors.BOLD}{s}{Colors.RESET}"
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: pptx-lint
3
+ Version: 0.1.0
4
+ Summary: Lint / quality checker for python-pptx presentations — detects overflow, overlap, fake tables, small fonts, and empty slides
5
+ License: MIT
6
+ Keywords: pptx,powerpoint,lint,quality,python-pptx,presentation
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: python-pptx>=0.6.21
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == "dev"
13
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
14
+ Dynamic: license-file
15
+
16
+ # pptx-lint
17
+
18
+ **Lint / quality checker for python-pptx presentations.**
19
+
20
+ [![PyPI](https://img.shields.io/pypi/v/pptx-lint)](https://pypi.org/project/pptx-lint/)
21
+ [![Python](https://img.shields.io/pypi/pyversions/pptx-lint)](https://pypi.org/project/pptx-lint/)
22
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
23
+ [![CI](https://github.com/LeppardWang/pptx-lint/actions/workflows/test.yml/badge.svg)](https://github.com/LeppardWang/pptx-lint/actions/workflows/test.yml)
24
+
25
+ ---
26
+
27
+ ## Why?
28
+
29
+ If you generate PowerPoint files with [python-pptx](https://python-pptx.readthedocs.io/),
30
+ you've probably run into these problems:
31
+
32
+ - 🔴 **Overflow** — Text boxes or tables exceeding slide boundaries
33
+ - 🟡 **Overlap** — Shapes covering each other because of hard-coded coordinates
34
+ - 🟡 **Fake tables** — Multiple text boxes trying (and failing) to look like a table
35
+ - â„šī¸ **Small fonts** — Text too small to read
36
+ - 🔴 **Empty slides** — Slides without any content
37
+
38
+ **pptx-lint** automatically detects all of these, giving you a clear report
39
+ so you can fix them *before* presenting.
40
+
41
+ ---
42
+
43
+ ## Quick start
44
+
45
+ ```bash
46
+ pip install pptx-lint
47
+
48
+ # Check a single file
49
+ pptx-lint presentation.pptx
50
+
51
+ # Check all files in a directory
52
+ pptx-lint ./output/
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Example output
58
+
59
+ ```
60
+ ======================================================================
61
+ pptx-lint report
62
+ File: my_presentation.pptx
63
+ ======================================================================
64
+ Slides: 12 | Empty: 0
65
+ Errors: 3 | Warnings: 5 | Infos: 2
66
+ ======================================================================
67
+
68
+ [Slide 4]
69
+ Content: Performance Metrics | Results
70
+ ✗ [Overflow] Shape#3 (Performance table): right overflow (r=12.80" > 10.00")
71
+ ▲ [FakeTable] 6 text boxes form a grid (e.g.: Precision, Recall)
72
+
73
+ [Slide 7]
74
+ Content: System Architecture Overview
75
+ ▲ [Overlap] Shape#2 overlaps Shape#4 — overlap area: 2.30 sq.in.
76
+
77
+ [Slide 9]
78
+ Content: References
79
+ i [SmallFont] Shape#1: 'Acknowledgements' font = 7pt (< 8pt)
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Checks
85
+
86
+ | Rule | Severity | What it detects |
87
+ |------|----------|-----------------|
88
+ | **Overflow** | 🔴 Error | Shapes whose `left`/`top` < 0 or `right`/`bottom` > slide dimensions |
89
+ | **Empty slide** | 🔴 Error | Slides with zero shapes |
90
+ | **Overlap** | 🟡 Warning | Two shapes overlapping > 50% of the smaller shape's area |
91
+ | **Fake table** | 🟡 Warning | â‰Ĩ4 text boxes arranged in a â‰Ĩ2×2 grid (should use `add_table()`) |
92
+ | **Small font** | â„šī¸ Info | Text smaller than 8 pt |
93
+
94
+ ---
95
+
96
+ ## Use as a library
97
+
98
+ ```python
99
+ from pptx_lint.checker import check_pptx
100
+
101
+ results, filename = check_pptx("my_slides.pptx")
102
+ for slide in results:
103
+ for err in slide['errors']:
104
+ print(f"Slide {slide['slide_num']}: {err['detail']}")
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ git clone https://github.com/LeppardWang/pptx-lint.git
113
+ cd pptx-lint
114
+ pip install -e ".[dev]"
115
+ pytest
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Related
121
+
122
+ - [python-pptx](https://python-pptx.readthedocs.io/) — the library that creates `.pptx` files
123
+ - [python-pptx-table](https://pypi.org/project/python-pptx-table/) — helpers for Table objects
124
+
125
+ ---
126
+
127
+ ## License
128
+
129
+ MIT
@@ -0,0 +1,17 @@
1
+ pptx_lint/__init__.py,sha256=TFUJioHHQ9avDvFDkmqEo4mcU7e7q12L-F6VfuNoi8w,239
2
+ pptx_lint/checker.py,sha256=adkWN-smhmMteafX8scix8tDQbq0eqhJivkVhOxZpeY,5149
3
+ pptx_lint/cli.py,sha256=XH7N7MC66GZQjDtqM52RYDuyZgrAtHiB5ABfY1qIRj8,2164
4
+ pptx_lint/utils.py,sha256=hhAkNpvUUKXI8jjkaCDJfMRL71T66Py8GzI4q_SktvQ,1264
5
+ pptx_lint/reporters/__init__.py,sha256=2Y7Y18cvlV_b9_sXwF0f0eF3iozBr2HGSCei7gQWDHo,59
6
+ pptx_lint/reporters/console.py,sha256=3Ckj3huDplsOSWbewXHtR3jPNgTkSr0lyoZdlygtWAY,2099
7
+ pptx_lint/rules/__init__.py,sha256=UKcmbLRdM8BQH4aB3RVgRg7TRdKHXxaRObzSnUpaNEY,197
8
+ pptx_lint/rules/fake_table.py,sha256=wHwhpliF9LNlc3E5uHSCJTgn0DcQoBoVWQZFziuXI5Q,1862
9
+ pptx_lint/rules/font_size.py,sha256=EpVq9oNL-FwQWwWMSyugZz91GLZCiw8hgMxCERP6LuM,605
10
+ pptx_lint/rules/overflow.py,sha256=wZztnDcCa1Aqvap-GjwNmngrG43E_Op1W5CyxpDgf3o,1471
11
+ pptx_lint/rules/overlap.py,sha256=rAZZAErwMJc_LeaMYqIPpnZfBHMFSCZmBq1mJzAuO1w,1615
12
+ pptx_lint-0.1.0.dist-info/licenses/LICENSE,sha256=Y3sHqXDTbDTHSD2zsn_CO0sWTCqhqzxkb1REt2acc8A,1100
13
+ pptx_lint-0.1.0.dist-info/METADATA,sha256=s5E_HAG9iSMlRS6YuTRtbZR9ot-f5VP3I8aRL_l5z3I,3819
14
+ pptx_lint-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
15
+ pptx_lint-0.1.0.dist-info/entry_points.txt,sha256=QBOC8PeGLEw9lUuQQ7hHGqPWi-UH_y6jizp7VveM0jg,49
16
+ pptx_lint-0.1.0.dist-info/top_level.txt,sha256=9zwpgNanRXuzxPlael79P84FmJFViCE-pUjLYEpeaYU,10
17
+ pptx_lint-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pptx-lint = pptx_lint.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pptx-lint contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ pptx_lint