198 lines
6.6 KiB
Dart
198 lines
6.6 KiB
Dart
|
|
import 'dart:typed_data';
|
||
|
|
import 'dart:ui' as ui;
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'dart:math' as math; // Serve per i calcoli della freccia
|
||
|
|
|
||
|
|
// --- PAINTER PER IL DISEGNO (A SCHERMO E SU PDF) ---
|
||
|
|
class PainterV40 extends CustomPainter {
|
||
|
|
final List<TrattoPenna> tr;
|
||
|
|
final List<ElementoGrafico> el;
|
||
|
|
|
||
|
|
PainterV40(this.tr, this.el);
|
||
|
|
|
||
|
|
@override
|
||
|
|
void paint(Canvas canvas, Size size) {
|
||
|
|
// Stile della penna (Strada/Linee)
|
||
|
|
Paint pStrada = Paint()
|
||
|
|
..color = Colors.black
|
||
|
|
..strokeWidth = 3.0
|
||
|
|
..style = PaintingStyle.stroke
|
||
|
|
..strokeCap = StrokeCap.round;
|
||
|
|
|
||
|
|
// 1. DISEGNO TRATTI (PENNA E FRECCE)
|
||
|
|
for (var t in tr) {
|
||
|
|
if (t.punti.length > 1) {
|
||
|
|
Path path = Path()..moveTo(t.punti[0].dx, t.punti[0].dy);
|
||
|
|
for (var pt in t.punti) path.lineTo(pt.dx, pt.dy);
|
||
|
|
|
||
|
|
canvas.drawPath(path, pStrada);
|
||
|
|
|
||
|
|
// Se è una freccia, disegna la punta alla fine
|
||
|
|
if (t.tipo == 'freccia') {
|
||
|
|
// Prendi gli ultimi due punti per calcolare l'angolazione
|
||
|
|
_disegnaPunta(canvas, t.punti[t.punti.length - 2], t.punti.last, pStrada);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. DISEGNO ELEMENTI (AUTO E TESTO)
|
||
|
|
for (var e in el) {
|
||
|
|
canvas.save();
|
||
|
|
// Sposta e ruota il canvas nella posizione dell'elemento
|
||
|
|
canvas.translate(e.posizione.dx, e.posizione.dy);
|
||
|
|
canvas.rotate(e.rotazione);
|
||
|
|
|
||
|
|
if (e.tipo == 'testo') {
|
||
|
|
_disegnaTesto(canvas, e.label ?? "");
|
||
|
|
} else {
|
||
|
|
_disegnaAuto(canvas, e.tipo == 'autoA' ? 'A' : 'B', e.tipo == 'autoA' ? Colors.blue : Colors.orange);
|
||
|
|
}
|
||
|
|
canvas.restore();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Disegna il rettangolo dell'auto con la lettera
|
||
|
|
void _disegnaAuto(Canvas canvas, String lettera, Color colore) {
|
||
|
|
Paint p = Paint()..color = colore;
|
||
|
|
// Auto centrata (70x40 px)
|
||
|
|
Rect r = Rect.fromCenter(center: Offset.zero, width: 70, height: 40);
|
||
|
|
canvas.drawRect(r, p);
|
||
|
|
|
||
|
|
// Bordo nero auto
|
||
|
|
canvas.drawRect(r, Paint()..color = Colors.black..style = PaintingStyle.stroke..strokeWidth = 2);
|
||
|
|
|
||
|
|
// Lettera centrata
|
||
|
|
final tp = TextPainter(
|
||
|
|
text: TextSpan(text: lettera, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)),
|
||
|
|
textDirection: TextDirection.ltr
|
||
|
|
)..layout();
|
||
|
|
|
||
|
|
tp.paint(canvas, Offset(-tp.width / 2, -tp.height / 2));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Disegna etichette di testo
|
||
|
|
void _disegnaTesto(Canvas canvas, String txt) {
|
||
|
|
final tp = TextPainter(
|
||
|
|
text: TextSpan(text: txt, style: const TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold, backgroundColor: Colors.white70)),
|
||
|
|
textDirection: TextDirection.ltr
|
||
|
|
)..layout();
|
||
|
|
tp.paint(canvas, Offset(-tp.width / 2, -tp.height / 2));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calcola e disegna la punta della freccia
|
||
|
|
void _disegnaPunta(Canvas canvas, Offset p1, Offset p2, Paint paint) {
|
||
|
|
double angle = (p2 - p1).direction;
|
||
|
|
// Disegna due linee inclinate rispetto alla direzione finale
|
||
|
|
canvas.drawLine(p2, p2 - Offset.fromDirection(angle - 0.5, 15), paint);
|
||
|
|
canvas.drawLine(p2, p2 - Offset.fromDirection(angle + 0.5, 15), paint);
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- MODELLO DATI: TRATTO PENNA ---
|
||
|
|
class TrattoPenna {
|
||
|
|
List<Offset> punti;
|
||
|
|
String tipo; // 'penna' o 'freccia'
|
||
|
|
|
||
|
|
TrattoPenna(this.punti, {this.tipo = 'penna'});
|
||
|
|
|
||
|
|
// Metodo utile per la cancellazione (hit test)
|
||
|
|
bool contiene(Offset p) {
|
||
|
|
for (var punto in punti) {
|
||
|
|
if ((p - punto).distance < 20.0) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
Map<String, dynamic> toMap() => {
|
||
|
|
'punti': punti.map((p) => {'dx': p.dx, 'dy': p.dy}).toList(),
|
||
|
|
'tipo': tipo,
|
||
|
|
};
|
||
|
|
|
||
|
|
factory TrattoPenna.fromMap(Map<String, dynamic> map) => TrattoPenna(
|
||
|
|
(map['punti'] as List).map((p) => Offset(p['dx'], p['dy'])).toList(),
|
||
|
|
tipo: map['tipo'] ?? 'penna',
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- MODELLO DATI: ELEMENTO GRAFICO (AUTO/TESTO) ---
|
||
|
|
class ElementoGrafico {
|
||
|
|
Offset posizione;
|
||
|
|
String tipo; // 'autoA', 'autoB', 'testo'
|
||
|
|
double rotazione;
|
||
|
|
String? label;
|
||
|
|
|
||
|
|
ElementoGrafico(this.posizione, this.tipo, {this.rotazione = 0, this.label});
|
||
|
|
|
||
|
|
Map<String, dynamic> toMap() => {
|
||
|
|
'pos': {'dx': posizione.dx, 'dy': posizione.dy},
|
||
|
|
'tipo': tipo,
|
||
|
|
'rot': rotazione,
|
||
|
|
'label': label,
|
||
|
|
};
|
||
|
|
|
||
|
|
factory ElementoGrafico.fromMap(Map<String, dynamic> map) => ElementoGrafico(
|
||
|
|
Offset(map['pos']['dx'], map['pos']['dy']),
|
||
|
|
map['tipo'],
|
||
|
|
rotazione: (map['rot'] as num?)?.toDouble() ?? 0,
|
||
|
|
label: map['label'],
|
||
|
|
);
|
||
|
|
|
||
|
|
bool contiene(Offset p) => (p - posizione).distance < 35;
|
||
|
|
|
||
|
|
// --- METODO CRUCIALE PER IL PDF ---
|
||
|
|
// Genera un'immagine PNG ritagliata e ottimizzata del grafico
|
||
|
|
static Future<Uint8List?> fondiGraficoDinamica(List<dynamic> trattiRaw, List<dynamic> elementiRaw) async {
|
||
|
|
// Conversione sicura dei tipi (nel caso arrivino come dynamic da GlobalData)
|
||
|
|
List<TrattoPenna> tratti = trattiRaw.cast<TrattoPenna>();
|
||
|
|
List<ElementoGrafico> elementi = elementiRaw.cast<ElementoGrafico>();
|
||
|
|
|
||
|
|
if (tratti.isEmpty && elementi.isEmpty) return null;
|
||
|
|
|
||
|
|
// 1. Calcolo Bounding Box (i confini del disegno)
|
||
|
|
double minX = double.infinity, minY = double.infinity;
|
||
|
|
double maxX = double.negativeInfinity, maxY = double.negativeInfinity;
|
||
|
|
|
||
|
|
void checkPoint(Offset p) {
|
||
|
|
if (p.dx < minX) minX = p.dx;
|
||
|
|
if (p.dx > maxX) maxX = p.dx;
|
||
|
|
if (p.dy < minY) minY = p.dy;
|
||
|
|
if (p.dy > maxY) maxY = p.dy;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (var t in tratti) { for (var p in t.punti) checkPoint(p); }
|
||
|
|
for (var e in elementi) checkPoint(e.posizione);
|
||
|
|
|
||
|
|
// Se non ci sono dimensioni valide, esci
|
||
|
|
if (minX == double.infinity) return null;
|
||
|
|
|
||
|
|
// Aggiungiamo margine (padding) bianco intorno
|
||
|
|
const double pad = 40.0;
|
||
|
|
double width = (maxX - minX) + (pad * 2);
|
||
|
|
double height = (maxY - minY) + (pad * 2);
|
||
|
|
|
||
|
|
// 2. Disegno su Canvas off-screen
|
||
|
|
final recorder = ui.PictureRecorder();
|
||
|
|
// Crea canvas delle dimensioni esatte
|
||
|
|
final canvas = ui.Canvas(recorder, Rect.fromLTWH(0, 0, width, height));
|
||
|
|
|
||
|
|
// Sfondo Bianco (Copre la griglia del modulo sottostante)
|
||
|
|
canvas.drawRect(Rect.fromLTWH(0, 0, width, height), Paint()..color = Colors.white);
|
||
|
|
|
||
|
|
// Sposta l'origine del canvas per centrare il disegno ed eliminare lo spazio vuoto in alto/sinistra
|
||
|
|
canvas.translate(-minX + pad, -minY + pad);
|
||
|
|
|
||
|
|
// Usa il painter esistente per ridisegnare tutto
|
||
|
|
final painter = PainterV40(tratti, elementi);
|
||
|
|
painter.paint(canvas, Size(width, height));
|
||
|
|
|
||
|
|
// 3. Conversione in PNG
|
||
|
|
final picture = recorder.endRecording();
|
||
|
|
final img = await picture.toImage(width.toInt(), height.toInt());
|
||
|
|
final pngBytes = await img.toByteData(format: ui.ImageByteFormat.png);
|
||
|
|
|
||
|
|
return pngBytes?.buffer.asUint8List();
|
||
|
|
}
|
||
|
|
}
|