Alessandro Tomassini
deploy(hf): overlay README/Dockerfile da huggingface/, senza docs/binari/model
c42a5a1
Raw
History Blame Contribute Delete
3.58 kB
"""Astrazioni del sottosistema di rendering PDF (SOLID).
Ogni metodo di visualizzazione/redazione e' una *strategia* isolata che
implementa `RenderMethod`. Sono registrate in un `RenderRegistry` e abilitate
o escluse da configurazione: aggiungere/togliere un metodo non tocca il codice
esistente (Open/Closed) ne' il livello web (Dependency Inversion).
- Single Responsibility: ogni metodo vive in un proprio file e sa solo
produrre il proprio output.
- Liskov: tutti i metodi rispettano la stessa firma `render(params) -> output`.
- Interface Segregation: l'interfaccia espone solo `info` e `render`.
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from core.contracts import PageGeometry
class RenderKind(str, Enum):
"""Natura dell'output di un metodo."""
VIEW = "view" # immagine renderizzata inline (SVG/PNG)
CLIENT = "client" # rendering lato client (solo dati: raw + overlay)
DOWNLOAD = "download" # file scaricabile (PDF)
@dataclass(frozen=True, slots=True)
class MethodInfo:
"""Metadati di un metodo, usati anche dalla UI per costruire i controlli."""
key: str
label: str
kind: RenderKind
media_type: str
styles: tuple[str, ...]
default_style: str
description: str
destructive: bool = False # True solo per la hard redaction
@dataclass(frozen=True, slots=True)
class RenderParams:
"""Parametri di una richiesta di rendering. Ogni metodo usa cio' che serve."""
pdf_bytes: bytes
boxes: list[dict] = field(default_factory=list)
pages: tuple[PageGeometry, ...] = field(default_factory=tuple)
page: int = 0
style: str = "fill"
scale: float = 1.5
dpi: int = 144
opacity: float = 0.85
@dataclass(frozen=True, slots=True)
class RenderOutput:
"""Risultato neutro di un rendering."""
data: bytes
media_type: str
headers: dict[str, str] = field(default_factory=dict)
filename: str | None = None
def boxes_on_page(params: RenderParams) -> list[dict]:
"""Box della richiesta che appartengono alla pagina corrente (`params.page`).
Filtro condiviso dai metodi che renderizzano una singola pagina (SVG/PNG),
dove prima era ripetuto inline (`if b.get("page") != params.page`).
"""
return [b for b in params.boxes if b.get("page") == params.page]
class RenderMethod(ABC):
"""Interfaccia comune a tutti i metodi di rendering PDF."""
#: Metadati statici del metodo (override nelle sottoclassi o via __init__).
info: MethodInfo
def supports_style(self, style: str) -> bool:
return style in self.info.styles
@abstractmethod
def render(self, params: RenderParams) -> RenderOutput:
"""Produce l'output del metodo a partire dai parametri."""
class RenderRegistry:
"""Collezione ordinata di metodi abilitati (Open/Closed)."""
def __init__(self) -> None:
self._methods: dict[str, RenderMethod] = {}
def register(self, method: RenderMethod) -> RenderRegistry:
if method.info.key in self._methods:
raise ValueError(f"Metodo gia' registrato: {method.info.key}")
self._methods[method.info.key] = method
return self
def has(self, key: str) -> bool:
return key in self._methods
def get(self, key: str) -> RenderMethod | None:
return self._methods.get(key)
def infos(self) -> list[MethodInfo]:
return [m.info for m in self._methods.values()]
def __len__(self) -> int:
return len(self._methods)