From bce880f5d7abaeeb8f390dd58c0bf3a62d202169 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 23 Sep 2026 15:25:06 +0200 Subject: [PATCH] feat: narrower margins on landscape topology pages - Landscape pages use 7 mm side margins (PDF) / 0.7 cm (Word) and compact headings, so the topology gets about 28 cm of width. - Portrait pages after a landscape page restore their original margins. - The topology PNG is cropped to its content, removing the blank border. Bump version to 0.6.1. Co-Authored-By: Claude Opus 5.5 --- netbox_customer_export/__init__.py | 2 +- netbox_customer_export/renderers/docx.py | 18 ++++++++++++++++-- .../netbox_customer_export/document.html | 9 +++++---- netbox_customer_export/topology.py | 8 ++++++++ pyproject.toml | 2 +- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/netbox_customer_export/__init__.py b/netbox_customer_export/__init__.py index 8d03ddd..e2c5216 100644 --- a/netbox_customer_export/__init__.py +++ b/netbox_customer_export/__init__.py @@ -5,7 +5,7 @@ class CustomerExportConfig(PluginConfig): name = "netbox_customer_export" verbose_name = "Kunden-Export" description = "Kundendokumentation als PDF oder Word exportieren" - version = "0.6.0" + version = "0.6.1" author = "Claude" base_url = "customer-export" min_version = "4.6.8" diff --git a/netbox_customer_export/renderers/docx.py b/netbox_customer_export/renderers/docx.py index 6243ba2..5487d53 100644 --- a/netbox_customer_export/renderers/docx.py +++ b/netbox_customer_export/renderers/docx.py @@ -123,11 +123,19 @@ def render_docx(doc): portrait_size = (portrait.page_width, portrait.page_height) landscape_open = False + portrait_margins = ( + portrait.left_margin, portrait.right_margin, portrait.top_margin, portrait.bottom_margin, + portrait.header_distance, portrait.footer_distance, + ) + def new_section(landscape): s = d.add_section(WD_SECTION.NEW_PAGE) w, h = portrait_size s.orientation = WD_ORIENT.LANDSCAPE if landscape else WD_ORIENT.PORTRAIT s.page_width, s.page_height = (h, w) if landscape else (w, h) + if not landscape: # Ränder der Hochformat-Seiten wiederherstellen + (s.left_margin, s.right_margin, s.top_margin, s.bottom_margin, + s.header_distance, s.footer_distance) = portrait_margins return s for blk in doc.blocks: @@ -137,15 +145,21 @@ def render_docx(doc): landscape_open = False if blk.kind == "landscape": s = new_section(landscape=True) + # schmale Ränder, damit die Topologie möglichst groß wird + s.left_margin = s.right_margin = Cm(0.7) + s.top_margin, s.bottom_margin = Cm(0.7), Cm(1.1) + s.header_distance, s.footer_distance = Cm(0.3), Cm(0.4) landscape_open = True for h in blk.rows: - d.add_heading(h.text, level=h.level) + heading = d.add_heading(h.text, level=h.level) + heading.paragraph_format.space_before = Pt(0) + heading.paragraph_format.space_after = Pt(2) png = blk.images[0]["png"] from PIL import Image px_w, px_h = Image.open(BytesIO(png)).size avail_w = s.page_width - s.left_margin - s.right_margin - avail_h = s.page_height - s.top_margin - s.bottom_margin - Cm(1.4 * len(blk.rows) + 0.8) + avail_h = s.page_height - s.top_margin - s.bottom_margin - Cm(1.1 * len(blk.rows) + 0.6) para = d.add_paragraph() para.alignment = WD_ALIGN_PARAGRAPH.CENTER if px_w / px_h >= avail_w / avail_h: diff --git a/netbox_customer_export/templates/netbox_customer_export/document.html b/netbox_customer_export/templates/netbox_customer_export/document.html index c93f692..2dabbe5 100644 --- a/netbox_customer_export/templates/netbox_customer_export/document.html +++ b/netbox_customer_export/templates/netbox_customer_export/document.html @@ -8,7 +8,8 @@ @page { size: A4; margin: 20mm 15mm 20mm 15mm; @bottom-left { content: element(footer); } @bottom-right { content: "Seite " counter(page) " / " counter(pages); font-size: 7pt; color: #777; } } - @page landscape { size: A4 landscape; margin: 12mm 15mm 16mm 15mm; } + /* Querformat: schmale Ränder, damit die Topologie möglichst groß wird */ + @page landscape { size: A4 landscape; margin: 7mm 7mm 11mm 7mm; } @page cover { @bottom-left { content: none } @bottom-right { content: none } } body { font-family: "{{ b.font_family }}", "Segoe UI", "DejaVu Sans", Arial, sans-serif; font-size: {{ b.font_size }}pt; color: #222; } .cover { page: cover; padding-top: 60mm; page-break-after: always; } @@ -42,9 +43,9 @@ .footer { position: running(footer); font-size: 7pt; color: #777; } @media screen { .footer { display: none; } } .landscape { page: landscape; page-break-before: always; page-break-after: always; } - .landscape h1 { page-break-before: avoid; margin-top: 0; } - .landscape h2 { margin-top: 4pt; } - .landscape img { display: block; margin: 0 auto; max-width: 100%; max-height: 152mm; } + .landscape h1 { page-break-before: avoid; margin: 0 0 2pt 0; font-size: 15pt; padding-bottom: 1pt; } + .landscape h2 { margin: 2pt 0 3pt 0; font-size: 12pt; } + .landscape img { display: block; margin: 0 auto; max-width: 100%; max-height: 172mm; } @media screen { .landscape img { max-height: none; width: 100%; } } .pagebreak { page-break-after: always; } @media screen { body { max-width: 1100px; margin: auto; padding: 16px; } } diff --git a/netbox_customer_export/topology.py b/netbox_customer_export/topology.py index 215c281..51acee0 100644 --- a/netbox_customer_export/topology.py +++ b/netbox_customer_export/topology.py @@ -438,6 +438,14 @@ def render_png(graph, show_ports=False, scale=2): for text, x, y, anchor in labels: tag(text, x, y, anchor) + # weißen Rand abschneiden: jeder Millimeter kommt der Größe im Dokument zugute + from PIL import ImageChops + + bbox = ImageChops.difference(img, Image.new("RGB", img.size, (255, 255, 255))).getbbox() + if bbox: + pad = 6 * S + img = img.crop((max(bbox[0] - pad, 0), max(bbox[1] - pad, 0), min(bbox[2] + pad, img.width), min(bbox[3] + pad, img.height))) + out = BytesIO() img.save(out, "PNG", optimize=True) return out.getvalue() diff --git a/pyproject.toml b/pyproject.toml index 5cd86b8..7e5e369 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "netbox-customer-export" -version = "0.6.0" +version = "0.6.1" description = "NetBox-Plugin: Kundendokumentation (PDF/Word) für Standorte, Lokationen, Mandanten und Mandantengruppen" readme = "README.md" requires-python = ">=3.10"