286 lines
12 KiB
PHP
286 lines
12 KiB
PHP
{{--
|
|
Modulo S-13-I — Registrazione delle assegnazioni del territorio.
|
|
|
|
Ricostruzione fedele del modulo ufficiale, la cui copia è in
|
|
resources/pdf/S-13_I.pdf e va usata come matrice per qualsiasi modifica
|
|
alla griglia. Il modulo originale è un PDF 1.7 con font CID embeddati:
|
|
sovrapporvi testo richiederebbe il parser a pagamento di FPDI, mentre
|
|
dompdf è già in uso per gli altri export del progetto.
|
|
|
|
Struttura per pagina: 21 blocchi-territorio, ognuno su due righe — quella
|
|
alta ospita i nomi dei proclamatori, quella bassa le date. Le colonne
|
|
"Terr. N." e "Restituito l'ultima volta il" occupano entrambe le righe.
|
|
|
|
Il motore CSS di dompdf non è un browser: le larghezze stanno in <colgroup>
|
|
(con i soli colspan sarebbero ripartite a caso) e l'interruzione di pagina è
|
|
decisa dall'indice del ciclo, perché il supporto a :last-child non è
|
|
garantito e un foglio bianco in coda sarebbe visibile.
|
|
--}}
|
|
@php
|
|
/**
|
|
* I nomi devono entrare nella loro cella senza sfondare la griglia: le
|
|
* righe hanno altezza fissa, quindi una riga di troppo sballerebbe tutto
|
|
* il foglio. Invece di andare a capo si scala il corpo.
|
|
*
|
|
* La larghezza si stima a caratteri: 0.20mm per carattere per punto di
|
|
* corpo. È deliberatamente pessimistica — la media reale di DejaVu Sans è
|
|
* ~0.52em (0.1835mm), ma sui nomi propri, pieni di maiuscole, la media non
|
|
* regge e con zero margine il testo deborda dalla cella. Sotto i 4.5pt il
|
|
* testo non sarebbe più leggibile, quindi oltre quel limite si tronca:
|
|
* l'ellissi rende visibile l'accorciamento, mentre un nome che deborda
|
|
* corromperebbe la griglia in silenzio.
|
|
*
|
|
* Le due celle hanno vincoli diversi.
|
|
*
|
|
* Le larghezze qui sotto sono misurate sul PDF renderizzato, non dedotte
|
|
* dalle percentuali: al netto di padding e bordi le celle risultano
|
|
* 35.9mm e 22.2mm, e queste costanti tengono un margine su quei valori.
|
|
* Se cambiano le percentuali delle colonne o i margini di pagina, vanno
|
|
* rimisurate.
|
|
*
|
|
* "Assegnato a" è nowrap su una riga sola: basta il corpo che ci sta, e
|
|
* oltre il minimo si tronca.
|
|
*
|
|
* Il riporto sta in una colonna stretta alta due righe. Lì il
|
|
* nome NON viene mandato a capo da dompdf: lo spezziamo noi in due righe
|
|
* bilanciate e le teniamo nowrap. Lasciar decidere al wrapper significa
|
|
* non sapere quante righe verranno fuori — e una terza riga non è un
|
|
* problema estetico, sfonda l'altezza del blocco e manda l'ultimo
|
|
* territorio su una pagina nuova, raddoppiando i fogli. Spezzando a mano
|
|
* l'altezza della cella è esatta per costruzione.
|
|
*/
|
|
$MM_PER_CARATTERE_PER_PUNTO = 0.20;
|
|
$CORPO_MIN_PT = 4.5;
|
|
|
|
$LARGHEZZA_NOME_MM = 34.0; // cella misurata 35.9mm
|
|
$LARGHEZZA_RIPORTO_MM = 20.5; // colonna misurata 22.2mm
|
|
$CORPO_RIPORTO_MAX_PT = 6.0;
|
|
|
|
$corpoPer = static function (string $testo, float $larghezzaMm, float $corpoMaxPt) use ($MM_PER_CARATTERE_PER_PUNTO, $CORPO_MIN_PT): string {
|
|
$len = mb_strlen($testo);
|
|
|
|
if ($len === 0) {
|
|
return $corpoMaxPt . 'pt';
|
|
}
|
|
|
|
$pt = $larghezzaMm / ($len * $MM_PER_CARATTERE_PER_PUNTO);
|
|
|
|
return round(max($CORPO_MIN_PT, min($corpoMaxPt, $pt)), 1) . 'pt';
|
|
};
|
|
|
|
$troncaPer = static function (string $testo, float $larghezzaMm) use ($MM_PER_CARATTERE_PER_PUNTO, $CORPO_MIN_PT): string {
|
|
$max = (int) floor($larghezzaMm / ($CORPO_MIN_PT * $MM_PER_CARATTERE_PER_PUNTO));
|
|
|
|
return mb_strlen($testo) > $max ? mb_substr($testo, 0, $max - 1) . '…' : $testo;
|
|
};
|
|
|
|
/**
|
|
* Spezza il nome in due righe scegliendo, fra tutti i punti fra una parola
|
|
* e l'altra, quello che rende più corta la riga più lunga: è la riga più
|
|
* lunga a decidere il corpo, quindi bilanciare significa poter scrivere
|
|
* più grande.
|
|
*
|
|
* @return array{righe: array<int, string>, corpo: string}
|
|
*/
|
|
$adattaRiporto = static function (string $testo) use ($corpoPer, $troncaPer, $LARGHEZZA_RIPORTO_MM, $CORPO_RIPORTO_MAX_PT): array {
|
|
$parole = preg_split('/\s+/', trim($testo), -1, PREG_SPLIT_NO_EMPTY);
|
|
|
|
$righe = [$testo];
|
|
|
|
if (count($parole) > 1) {
|
|
$migliore = null;
|
|
|
|
for ($i = 1; $i < count($parole); $i++) {
|
|
$prima = implode(' ', array_slice($parole, 0, $i));
|
|
$dopo = implode(' ', array_slice($parole, $i));
|
|
$peggio = max(mb_strlen($prima), mb_strlen($dopo));
|
|
|
|
if ($migliore === null || $peggio < $migliore['peggio']) {
|
|
$migliore = ['righe' => [$prima, $dopo], 'peggio' => $peggio];
|
|
}
|
|
}
|
|
|
|
$righe = $migliore['righe'];
|
|
}
|
|
|
|
// Una riga più larga della colonna deborderebbe: si accorcia.
|
|
$righe = array_map(fn (string $r) => $troncaPer($r, $LARGHEZZA_RIPORTO_MM), $righe);
|
|
|
|
$piuLunga = array_reduce($righe, fn (?string $a, string $b) => mb_strlen($b) > mb_strlen((string) $a) ? $b : $a);
|
|
|
|
return [
|
|
'righe' => $righe,
|
|
'corpo' => $corpoPer((string) $piuLunga, $LARGHEZZA_RIPORTO_MM, $CORPO_RIPORTO_MAX_PT),
|
|
];
|
|
};
|
|
|
|
$ultimaPagina = $pagine->count() - 1;
|
|
@endphp
|
|
<!DOCTYPE html>
|
|
<html lang="it">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>S-13 — Registrazione delle assegnazioni del territorio</title>
|
|
<style>
|
|
/* dompdf riporta i margini di @page sull'elemento body, quindi il
|
|
reset universale qui sotto li azzererebbe: vanno ridichiarati su
|
|
body, che per specificità vince su "*". Tenere i due valori
|
|
allineati — è @page a definire l'area utile, body a farla
|
|
sopravvivere al reset.
|
|
I margini laterali sono i più larghi perché sono quelli che si
|
|
vedono; in verticale si sta stretti, il foglio pieno misura ~266mm
|
|
e ne restano 273. Alzando questi valori, o gli spazi sotto il
|
|
titolo e sopra il codice, il piè di pagina scivola su una pagina
|
|
nuova e ogni foglio ne occupa due: rimisurare dopo ogni modifica. */
|
|
@page { margin: 14mm 16mm 10mm 16mm; }
|
|
|
|
* { margin: 0; padding: 0; }
|
|
|
|
body {
|
|
margin: 14mm 16mm 10mm 16mm;
|
|
font-family: "DejaVu Sans", sans-serif;
|
|
color: #000;
|
|
}
|
|
|
|
.interrompi { page-break-after: always; }
|
|
|
|
h1 {
|
|
font-size: 13pt;
|
|
font-weight: bold;
|
|
text-align: center;
|
|
letter-spacing: 0.2pt;
|
|
margin-bottom: 6mm;
|
|
}
|
|
|
|
.anno {
|
|
font-size: 10pt;
|
|
font-weight: bold;
|
|
margin-bottom: 3mm;
|
|
}
|
|
.anno .valore {
|
|
display: inline-block;
|
|
width: 32mm;
|
|
border-bottom: 1px solid #000;
|
|
text-align: center;
|
|
font-weight: normal;
|
|
padding-bottom: 0.4mm;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
border: 1.6pt solid #000;
|
|
/* Obbligatorio, non cosmetico: in layout automatico le larghezze
|
|
del <colgroup> sono solo un suggerimento e una cella con del
|
|
testo lungo (la colonna del riporto, quando ospita un nome) si
|
|
allarga schiacciando le altre. I corpi dei nomi sono calcolati
|
|
su larghezze fisse, quindi a quel punto il testo deborda. */
|
|
table-layout: fixed;
|
|
}
|
|
td, th {
|
|
border: 0.5pt solid #000;
|
|
text-align: center;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
/* Intestazione */
|
|
thead th { font-weight: normal; padding: 0.5mm 0.5mm; }
|
|
th.terr { font-size: 7.5pt; line-height: 1.15; }
|
|
/* 6.2pt e non di più: a 6.6pt "l'ultima volta il*" non entra nei
|
|
~21mm utili della colonna e l'intestazione passa da due righe a tre,
|
|
allontanandosi dal modulo originale. */
|
|
th.restituito { font-size: 6.2pt; line-height: 1.15; white-space: nowrap; }
|
|
th.assegnato-a { font-size: 8pt; }
|
|
th.sotto { font-size: 6.4pt; }
|
|
|
|
/* Corpo */
|
|
/* 9.4mm a blocco. Il vincolo non sono i nomi (8pt = 2.8mm) ma la cella
|
|
del riporto, che a cavallo delle due righe ospita due righe di nome
|
|
a 6pt più la data: ~7.2mm. */
|
|
tr.riga-nome td { height: 5.6mm; }
|
|
tr.riga-date td { height: 3.8mm; }
|
|
|
|
td.numero { font-size: 9.5pt; font-weight: bold; }
|
|
td.data { font-size: 7.5pt; }
|
|
|
|
/* La colonna del riporto è alta due righe: quando ospita
|
|
un'assegnazione ci stanno il nome (che qui può andare a capo, c'è
|
|
spazio in verticale) e sotto la data di rientro. */
|
|
td.riporto { padding: 0 0.5mm; }
|
|
.riporto-nome { line-height: 1.05; white-space: nowrap; }
|
|
.riporto-data { font-size: 7pt; line-height: 1.1; }
|
|
td.nome { line-height: 1.1; padding: 0 0.6mm; white-space: nowrap; }
|
|
|
|
.nota { font-size: 7.4pt; margin-top: 1.6mm; }
|
|
.codice { font-size: 8pt; margin-top: 3mm; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
@foreach($pagine as $indice => $righe)
|
|
<div @if($indice < $ultimaPagina) class="interrompi" @endif>
|
|
<h1>REGISTRAZIONE DELLE ASSEGNAZIONI DEL TERRITORIO</h1>
|
|
|
|
<div class="anno">Anno di servizio: <span class="valore">{{ $annoLabel }}</span></div>
|
|
|
|
<table>
|
|
<colgroup>
|
|
<col style="width:6.5%">
|
|
<col style="width:12.5%">
|
|
@for($c = 0; $c < 8; $c++)
|
|
<col style="width:10.125%">
|
|
@endfor
|
|
</colgroup>
|
|
<thead>
|
|
{{-- Le larghezze stanno sia nel <colgroup> sia qui: in layout
|
|
fisso dompdf le ricava dalle celle della prima riga, e
|
|
senza queste le due colonne rowspan si spartiscono lo
|
|
spazio in parti uguali invece di rispettare il colgroup. --}}
|
|
<tr>
|
|
<th class="terr" rowspan="2" style="width:6.5%">Terr.<br>N.</th>
|
|
<th class="restituito" rowspan="2" style="width:12.5%">Restituito<br>l’ultima volta il*</th>
|
|
@for($g = 0; $g < 4; $g++)
|
|
<th class="assegnato-a" colspan="2" style="width:20.25%">Assegnato a</th>
|
|
@endfor
|
|
</tr>
|
|
<tr>
|
|
@for($g = 0; $g < 4; $g++)
|
|
<th class="sotto">Assegnato il</th>
|
|
<th class="sotto">Restituito il</th>
|
|
@endfor
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($righe as $riga)
|
|
@php($riporto = $adattaRiporto($riga['riporto']['nome']))
|
|
<tr class="riga-nome">
|
|
<td class="numero" rowspan="2">{{ $riga['numero'] }}</td>
|
|
<td class="riporto" rowspan="2">
|
|
@if($riga['riporto']['nome'] !== '')
|
|
<div class="riporto-nome" style="font-size:{{ $riporto['corpo'] }}">
|
|
@foreach($riporto['righe'] as $r)<div>{{ $r }}</div>@endforeach
|
|
</div>
|
|
@endif
|
|
<div class="riporto-data">{{ $riga['riporto']['data'] }}</div>
|
|
</td>
|
|
@foreach($riga['assegnazioni'] as $a)
|
|
@php($nome = $troncaPer($a['nome'], $LARGHEZZA_NOME_MM))
|
|
<td class="nome" colspan="2" style="font-size:{{ $corpoPer($nome, $LARGHEZZA_NOME_MM, 8) }}">{{ $nome }}</td>
|
|
@endforeach
|
|
</tr>
|
|
<tr class="riga-date">
|
|
@foreach($riga['assegnazioni'] as $a)
|
|
<td class="data">{{ $a['assegnato_il'] }}</td>
|
|
<td class="data">{{ $a['restituito_il'] }}</td>
|
|
@endforeach
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="nota">*Quando si inizia un nuovo foglio, registrare in questa colonna l’ultima data in cui è stato restituito ogni territorio.</div>
|
|
<div class="codice">S-13-I 1/22</div>
|
|
</div>
|
|
@endforeach
|
|
</body>
|
|
</html>
|