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 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 15:25:06 +02:00
co-authored by Claude Opus 5.5
parent e3c31b6312
commit bce880f5d7
5 changed files with 31 additions and 8 deletions
+1 -1
View File
@@ -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"
+16 -2
View File
@@ -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:
@@ -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; } }
+8
View File
@@ -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()
+1 -1
View File
@@ -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"