198 lines
5.8 KiB
Dart
198 lines
5.8 KiB
Dart
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);
|
|
}
|
|
}
|
|
}
|