espresso3389/pdfrx

How to Persist PDF Zoom Level Centered at Top white rotating

Closed this issue · 2 comments

Hello,

I'm currently building an app that includes a feature called "lock zoom," which aims to persist the zoom level of a PDF.

The feature works fine if I use it in the same orientation because I just call setZoom. However, when I rotate the phone, I need to persist the zoom of the PDF. To achieve this, I need to adjust the zoom level based on the screen size. Since I don't know how to calculate the top position of the PDF, I use the center position and then use goToArea later.

While I have managed to implement this feature as a workaround, it does not work smoothly but gives me the desired result. I believe there might be a better implementation that I have missed. Any guidance or support on this matter would be greatly appreciated.

Here is the relevant code snippet:

final firstRun = useRef(true);
useEffect(
  () {
    // Only run the function after the first build, so it only runs when the orientation changes
    if (firstRun.value) {
      firstRun.value = false;
      return;
    }

    final currentZoom = pdfCtr.currentZoom;
    final portraitViewWidth = context.actualScreenWidth;
    final landscapeViewWidth = context.actualScreenHeight;

    final newZoom = context.isLandscape
        ? (landscapeViewWidth / portraitViewWidth) * currentZoom
        : (portraitViewWidth / landscapeViewWidth) * currentZoom;

    final persistedZoomValue = ref.read(_zoomProvider);
    final offset = Offset(
      persistedZoomValue?.dx ?? pdfCtr.centerPosition.dx,
      persistedZoomValue?.dy ?? pdfCtr.centerPosition.dy,
    );

    final rec = pdfCtr.visibleRect;
    final bottom = rec.bottom + context.screenHeight;

    final adjustedRect = Rect.fromLTRB(rec.left, rec.top, rec.right, bottom);

    // Only delay which makes zoom work
    Future.delayed(10.milliseconds).then((_) {
      pdfCtr.setZoom(offset, newZoom).then((_) {
        pdfCtr.goToArea(
          rect: adjustedRect,
        );
      });
    });

    return null;
  },
  [context.isLandscape],
);

return PdfViewer.file(
  path,
  controller: pdfCtr,
  params: PdfViewerParams(
    backgroundColor: backgroundColor,
    panEnabled: true,
    enableTextSelection: false,
    scaleEnabled: true,
    panAxis: lockHorizontalScroll ? PanAxis.vertical : PanAxis.free,
    onInteractionEnd: (details) {
      // Persist for local state use
      EasyDebounce.debounce(
        'persist-local-zoom',
        100.milliseconds,
        () {
          final offsetX = pdfCtr.centerPosition.dx;
          final offsetY = pdfCtr.centerPosition.dy;
          final zoom = pdfCtr.currentZoom;
          ref.read(_zoomProvider.notifier).state = (dx: offsetX, dy: offsetY, zoom: zoom);
        },
      );
    },
  ),
)

Below is a video demonstrating the feature:

2024-06-29.09.08.43.mp4

Thank you

1.0.73 introduces PdfViewerParams.onViewSizeChanged, which is called on view size change.

The following fragment illustrates how to use it to keep the same point shown on the device screen center during device rotation:

https://github.com/espresso3389/pdfrx/blob/master/example/viewer/lib/main.dart#L252C1-L271C23

onViewSizeChanged: (viewSize, oldViewSize, controller) {
    if (oldViewSize != null) {
      //
      // Calculate the matrix to keep the center position during device
      // screen rotation
      //
      // The most important thing here is that the transformation matrix
      // is not changed on the view change.
      final centerPosition =
          controller.value.calcPosition(oldViewSize);
      final newMatrix =
          controller.calcMatrixFor(centerPosition);
      // Don't change the matrix in sync; the callback might be called
      // during widget-tree's build process.
      Future.delayed(
        const Duration(milliseconds: 200),
        () => controller.goTo(newMatrix),
      );
    }
  },

Awesome, Thank you very much for continuously enhancing its customizability.