import 'dart:math'; import 'package:flutter/material.dart'; import 'package:pdfrx/pdfrx.dart'; import '../../../core/models/config.dart'; import '../drawing/drawing.dart'; /// Displays PDF pages with optional two-page mode and drawing overlay. class PdfPageDisplay extends StatelessWidget { final PdfDocument document; final int numPages; final int currentPageNumber; final Config config; /// Controller for the left/main page drawing final DrawingController? leftDrawingController; /// Controller for the right page drawing (two-page mode only) final DrawingController? rightDrawingController; /// Whether drawing is enabled final bool drawingEnabled; const PdfPageDisplay({ super.key, required this.document, required this.numPages, required this.currentPageNumber, required this.config, this.leftDrawingController, this.rightDrawingController, this.drawingEnabled = false, }); /// Whether two-page mode is active and we have enough pages. bool get _showTwoPages => config.twoPageMode && numPages >= 2; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final maxSize = Size(constraints.maxWidth, constraints.maxHeight); final (leftSize, rightSize) = calculateScaledPageSizes(maxSize); if (_showTwoPages) { // Two-page mode: pages touch each other and are centered together return Center( child: Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildPage( pageNumber: currentPageNumber, pageSize: leftSize, controller: leftDrawingController, ), // Only show right page if there is one if (currentPageNumber < numPages) _buildPage( pageNumber: currentPageNumber + 1, pageSize: rightSize!, controller: rightDrawingController, ) else // Empty space to keep left page position consistent on last page SizedBox(width: rightSize!.width, height: rightSize.height), ], ), ); } // Single page mode return Center( child: _buildPage( pageNumber: currentPageNumber, pageSize: leftSize, controller: leftDrawingController, ), ); }, ); } Widget _buildPage({ required int pageNumber, required Size pageSize, required DrawingController? controller, }) { final pdfPage = SizedBox( width: pageSize.width, height: pageSize.height, child: Stack( children: [ PdfPageView( key: ValueKey(pageNumber), document: document, pageNumber: pageNumber, maximumDpi: 300, alignment: Alignment.center, ), _buildPageIndicator(pageNumber, pageSize), ], ), ); // If no controller, just show the PDF if (controller == null) { return pdfPage; } // Wrap with DrawingBoard return DrawingBoard( boardSize: pageSize, controller: controller, drawingEnabled: drawingEnabled, minScale: 1.0, maxScale: 3.0, alignment: Alignment.center, child: pdfPage, ); } Widget _buildPageIndicator(int pageNumber, Size pageSize) { return Positioned( bottom: 5, left: 0, right: 0, child: Center( child: Text( '$pageNumber / $numPages', style: const TextStyle( fontSize: 12, color: Colors.black54, ), ), ), ); } // --------------------------------------------------------------------------- // Page Size Calculations // --------------------------------------------------------------------------- /// Calculates scaled page sizes for the current view. /// /// Returns a tuple of (leftPageSize, rightPageSize). /// rightPageSize is null when not in two-page mode. (Size, Size?) calculateScaledPageSizes(Size parentSize) { if (config.twoPageMode) { return _calculateTwoPageSizes(parentSize); } return (_calculateSinglePageSize(parentSize), null); } (Size, Size?) _calculateTwoPageSizes(Size parentSize) { final leftSize = _getUnscaledPageSize(currentPageNumber); final rightSize = numPages > currentPageNumber ? _getUnscaledPageSize(currentPageNumber + 1) : leftSize; // Combine pages for scaling calculation final combinedSize = Size( leftSize.width + rightSize.width, max(leftSize.height, rightSize.height), ); final scaledCombined = _scaleToFit(parentSize, combinedSize); final scaleFactor = scaledCombined.width / combinedSize.width; return (leftSize * scaleFactor, rightSize * scaleFactor); } Size _calculateSinglePageSize(Size parentSize) { return _scaleToFit(parentSize, _getUnscaledPageSize(currentPageNumber)); } Size _getUnscaledPageSize(int pageNumber) { return document.pages.elementAt(pageNumber - 1).size; } /// Scales a page size to fit within parent bounds while maintaining aspect ratio. Size _scaleToFit(Size parentSize, Size pageSize) { // Determine if height or width is the limiting factor if (parentSize.aspectRatio > pageSize.aspectRatio) { // Constrained by height final height = parentSize.height; final width = height * pageSize.aspectRatio; return Size(width, height); } else { // Constrained by width final width = parentSize.width; final height = width / pageSize.aspectRatio; return Size(width, height); } } }