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:
@@ -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"
|
||||
|
||||
@@ -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; } }
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user