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 tr; final List 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 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 toMap() => { 'punti': punti.map((p) => {'dx': p.dx, 'dy': p.dy}).toList(), 'tipo': tipo, }; factory TrattoPenna.fromMap(Map 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 toMap() => { 'pos': {'dx': posizione.dx, 'dy': posizione.dy}, 'tipo': tipo, 'rot': rotazione, 'label': label, }; factory ElementoGrafico.fromMap(Map 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 fondiGraficoDinamica(List trattiRaw, List elementiRaw) async { // Conversione sicura dei tipi (nel caso arrivino come dynamic da GlobalData) List tratti = trattiRaw.cast(); List elementi = elementiRaw.cast(); 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(); } }