++ fix grafica e compilazione s-13
This commit is contained in:
@@ -249,9 +249,9 @@ class Registro extends Component
|
||||
private function impaginaS13(\Illuminate\Support\Collection $righe): \Illuminate\Support\Collection
|
||||
{
|
||||
$vuota = [
|
||||
'numero' => '',
|
||||
'rientro_precedente' => '',
|
||||
'assegnazioni' => $this->completaCelleS13([]),
|
||||
'numero' => '',
|
||||
'riporto' => ['nome' => '', 'data' => ''],
|
||||
'assegnazioni' => $this->completaCelleS13([]),
|
||||
];
|
||||
|
||||
$pagine = $righe->isEmpty()
|
||||
@@ -292,9 +292,10 @@ class Registro extends Component
|
||||
|
||||
return $assegnazioni
|
||||
->map(function ($gruppo, $territorioId) use ($rientriPrecedenti) {
|
||||
// Se un territorio è girato più di quattro volte nell'anno il
|
||||
// modulo non ha spazio per tutte: si stampano le ultime
|
||||
// quattro, mantenendo l'ordine cronologico da sinistra.
|
||||
$gruppo = $gruppo->values();
|
||||
|
||||
// I quattro gruppi "Assegnato a" ospitano le ultime quattro
|
||||
// assegnazioni, in ordine cronologico da sinistra.
|
||||
$ultime = $gruppo->slice(-self::S13_ASSEGNAZIONI_PER_RIGA)->values();
|
||||
|
||||
$celle = $ultime->map(fn (Assegnazione $a) => [
|
||||
@@ -303,18 +304,55 @@ class Registro extends Component
|
||||
'restituito_il' => $a->returned_at?->format('d/m/Y') ?? '',
|
||||
])->all();
|
||||
|
||||
$rientro = $rientriPrecedenti->get($territorioId);
|
||||
|
||||
return [
|
||||
'numero' => $gruppo->first()->territorio?->numero ?? '',
|
||||
'rientro_precedente' => $rientro ? \Carbon\Carbon::parse($rientro)->format('d/m/Y') : '',
|
||||
'assegnazioni' => $this->completaCelleS13($celle),
|
||||
'numero' => $gruppo->first()->territorio?->numero ?? '',
|
||||
'riporto' => $this->riportoS13($gruppo, $rientriPrecedenti->get($territorioId)),
|
||||
'assegnazioni' => $this->completaCelleS13($celle),
|
||||
];
|
||||
})
|
||||
->sort(fn (array $a, array $b) => strnatcasecmp((string) $a['numero'], (string) $b['numero']))
|
||||
->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contenuto della colonna "Restituito l'ultima volta il", che funziona da
|
||||
* quinta casella della riga.
|
||||
*
|
||||
* Quando le assegnazioni dell'anno superano le quattro che i gruppi
|
||||
* "Assegnato a" possono ospitare, la colonna accoglie quella
|
||||
* immediatamente precedente alle quattro mostrate — nome e data di
|
||||
* rientro — così la riga copre cinque passaggi invece di quattro. In quel
|
||||
* caso il riporto dell'anno precedente non ha più senso: la continuità
|
||||
* con il passato ce l'ha già l'assegnazione in colonna.
|
||||
*
|
||||
* Con quattro o meno assegnazioni nell'anno la colonna torna al suo uso
|
||||
* da modulo: l'ultima data di rientro dell'anno di servizio precedente,
|
||||
* senza nome.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection<int, Assegnazione> $gruppo
|
||||
* @return array{nome: string, data: string}
|
||||
*/
|
||||
private function riportoS13(\Illuminate\Support\Collection $gruppo, ?string $rientroAnnoPrecedente): array
|
||||
{
|
||||
$eccedenti = $gruppo->count() - self::S13_ASSEGNAZIONI_PER_RIGA;
|
||||
|
||||
if ($eccedenti > 0) {
|
||||
$precedente = $gruppo->get($eccedenti - 1);
|
||||
|
||||
return [
|
||||
'nome' => $precedente->proclamatore?->nome_completo ?? '',
|
||||
'data' => $precedente->returned_at?->format('d/m/Y') ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'nome' => '',
|
||||
'data' => $rientroAnnoPrecedente
|
||||
? \Carbon\Carbon::parse($rientroAnnoPrecedente)->format('d/m/Y')
|
||||
: '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ultima data di rientro di ogni territorio *precedente* all'inizio
|
||||
* dell'anno di servizio: è il dato che la colonna "Restituito l'ultima
|
||||
|
||||
@@ -18,39 +18,100 @@
|
||||
--}}
|
||||
@php
|
||||
/**
|
||||
* Il nome del proclamatore deve stare su una riga: le righe hanno altezza
|
||||
* fissa e la cella è nowrap, quindi mandare a capo sfonderebbe la griglia.
|
||||
* Invece di andare a capo si scala il corpo.
|
||||
* 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.
|
||||
*
|
||||
* Larghezza utile della cella ~34.8mm (due colonne meno il padding).
|
||||
* DejaVu Sans occupa in media 0.52em per carattere, cioè ~0.1835mm per
|
||||
* carattere per punto di corpo: da lì il corpo massimo che entra.
|
||||
* 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.
|
||||
*
|
||||
* Sotto i 4.5pt il testo non sarebbe più leggibile, quindi oltre i 42
|
||||
* caratteri 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.
|
||||
*/
|
||||
$LARGHEZZA_CELLA_MM = 34.8;
|
||||
$MM_PER_CARATTERE_PER_PUNTO = 0.1835;
|
||||
$CORPO_MAX_PT = 8.0;
|
||||
$MM_PER_CARATTERE_PER_PUNTO = 0.20;
|
||||
$CORPO_MIN_PT = 4.5;
|
||||
|
||||
$maxCaratteri = (int) floor($LARGHEZZA_CELLA_MM / ($CORPO_MIN_PT * $MM_PER_CARATTERE_PER_PUNTO));
|
||||
$LARGHEZZA_NOME_MM = 34.0; // cella misurata 35.9mm
|
||||
$LARGHEZZA_RIPORTO_MM = 20.5; // colonna misurata 22.2mm
|
||||
$CORPO_RIPORTO_MAX_PT = 6.0;
|
||||
|
||||
$nomeTroncato = static fn (string $nome): string => mb_strlen($nome) > $maxCaratteri
|
||||
? mb_substr($nome, 0, $maxCaratteri - 1) . '…'
|
||||
: $nome;
|
||||
|
||||
$nomeFontSize = static function (string $nome) use ($LARGHEZZA_CELLA_MM, $MM_PER_CARATTERE_PER_PUNTO, $CORPO_MAX_PT, $CORPO_MIN_PT): string {
|
||||
$len = mb_strlen($nome);
|
||||
$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 $CORPO_MAX_PT . 'pt';
|
||||
return $corpoMaxPt . 'pt';
|
||||
}
|
||||
|
||||
$pt = $LARGHEZZA_CELLA_MM / ($len * $MM_PER_CARATTERE_PER_PUNTO);
|
||||
$pt = $larghezzaMm / ($len * $MM_PER_CARATTERE_PER_PUNTO);
|
||||
|
||||
return round(max($CORPO_MIN_PT, min($CORPO_MAX_PT, $pt)), 1) . 'pt';
|
||||
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;
|
||||
@@ -109,6 +170,12 @@
|
||||
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;
|
||||
@@ -127,12 +194,21 @@
|
||||
th.sotto { font-size: 6.4pt; }
|
||||
|
||||
/* Corpo */
|
||||
tr.riga-nome td { height: 6mm; }
|
||||
tr.riga-date td { height: 4mm; }
|
||||
/* 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.rientro-prec { font-size: 7.5pt; }
|
||||
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; }
|
||||
@@ -155,11 +231,15 @@
|
||||
@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">Terr.<br>N.</th>
|
||||
<th class="restituito" rowspan="2">Restituito<br>l’ultima volta il*</th>
|
||||
<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">Assegnato a</th>
|
||||
<th class="assegnato-a" colspan="2" style="width:20.25%">Assegnato a</th>
|
||||
@endfor
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -171,12 +251,20 @@
|
||||
</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="rientro-prec" rowspan="2">{{ $riga['rientro_precedente'] }}</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 = $nomeTroncato($a['nome']))
|
||||
<td class="nome" colspan="2" style="font-size:{{ $nomeFontSize($nome) }}">{{ $nome }}</td>
|
||||
@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">
|
||||
|
||||
Reference in New Issue
Block a user