"""Metodo B — rendering SVG server-side con rettangoli di redazione."""
from __future__ import annotations
from xml.sax.saxutils import escape as xml_escape
from pdf.rendering.base import (
MethodInfo,
RenderKind,
RenderMethod,
RenderOutput,
RenderParams,
boxes_on_page,
)
from pdf.rendering.styles import severity_rgb255
class SvgRenderMethod(RenderMethod):
info = MethodInfo(
key="svg",
label="B · SVG",
kind=RenderKind.VIEW,
media_type="image/svg+xml",
styles=("fill", "outline", "label"),
default_style="fill",
description=(
"SVG vettoriale generato dal server con i riquadri incorporati. "
"Geometria precisa, nessuna dipendenza client, testo selezionabile."
),
)
def render(self, params: RenderParams) -> RenderOutput:
import fitz
doc = fitz.open(stream=params.pdf_bytes, filetype="pdf")
page = doc.load_page(params.page)
mat = fitz.Matrix(params.scale, params.scale)
svg_text = page.get_svg_image(matrix=mat)
page_w = page.rect.width * params.scale
page_h = page.rect.height * params.scale
doc.close()
elements = self._build_elements(params)
svg = svg_text.replace("", "\n".join(elements) + "\n")
return RenderOutput(
data=svg.encode("utf-8"),
media_type=self.info.media_type,
headers={
"X-Page-Width": f"{page_w:.1f}",
"X-Page-Height": f"{page_h:.1f}",
"Cache-Control": "private, max-age=300",
},
)
def _build_elements(self, params: RenderParams) -> list[str]:
out: list[str] = []
s = params.scale
for b in boxes_on_page(params):
r, g, bl = severity_rgb255(b.get("severity", "media"))
color = f"rgb({r},{g},{bl})"
x0, y0 = b["x0"] * s, b["y0"] * s
w, h = (b["x1"] - b["x0"]) * s, (b["y1"] - b["y0"]) * s
label = xml_escape(str(b.get("label", "")))
rect = f'x="{x0:.1f}" y="{y0:.1f}" width="{w:.1f}" height="{h:.1f}"'
if params.style == "outline":
out.append(
f'{label}'
)
elif params.style == "label":
fs = max(7.0, min(h * 0.7, 14.0))
out.append(
f''
f'{label}'
f'{label}'
)
else: # fill
out.append(
f'{label}'
)
return out