pdfrx 0.2.4 copy "pdfrx: ^0.2.4" to clipboard
pdfrx: ^0.2.4 copied to clipboard

Yet another PDF renderer for Flutter using PDFium.

example/lib/main.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:pdfrx/pdfrx.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final searchTextFocusNode = FocusNode();
  final searchTextController = TextEditingController();
  final controller = PdfViewerController();
  bool showSearchToolbar = false;

  @override
  void dispose() {
    searchTextController.dispose();
    searchTextFocusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Pdfrx example')),
        body: Stack(
          children: [
            PdfViewer.uri(
              //'assets/PDF32000_2008.pdf',
              Uri.parse(kIsWeb
                  ? 'assets/hello.pdf'
                  : 'https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf'),
              controller: controller,

              displayParams: PdfDisplayParams(
                maxScale: 8,
                pageOverlaysBuilder: (context, page, pageRect, controller) {
                  return [
                    // FIXME: Bad sample; don't directly load text in FutureBuilder on production code
                    FutureBuilder(
                      future: page.loadText(),
                      builder: (context, snapshot) {
                        if (snapshot.data == null) {
                          return Container();
                        }
                        return _generateRichText(
                            snapshot.data?.fragments ?? [], page, pageRect);
                      },
                    ),
                  ];
                },
              ),
            ),
            AnimatedPositioned(
              duration: const Duration(milliseconds: 300),
              right: 2,
              top: showSearchToolbar ? 2 : -80,
              child: Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(8),
                  color: Colors.white,
                  boxShadow: const [
                    BoxShadow(
                        color: Colors.black45,
                        blurRadius: 4,
                        offset: Offset(2, 2))
                  ],
                ),
                padding: const EdgeInsets.all(8),
                width: 300,
                height: 60,
                child: Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: searchTextController,
                        focusNode: searchTextFocusNode,
                      ),
                    ),
                    IconButton(
                      onPressed: () {},
                      icon: const Icon(Icons.arrow_downward),
                    ),
                    IconButton(
                      onPressed: () {},
                      icon: const Icon(Icons.arrow_upward),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
        floatingActionButton: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              onPressed: () {
                controller.zoomUp();
              },
              child: const Icon(Icons.add),
            ),
            FloatingActionButton(
              onPressed: () {
                controller.zoomDown();
              },
              child: const Icon(Icons.remove),
            ),
            FloatingActionButton(
              child: const Icon(Icons.first_page),
              onPressed: () => controller.goToPage(pageNumber: 1),
            ),
            FloatingActionButton(
              child: const Icon(Icons.last_page),
              onPressed: () =>
                  controller.goToPage(pageNumber: controller.pageCount!),
            ),
            FloatingActionButton(
                child: const Icon(Icons.search),
                onPressed: () =>
                    setState(() => showSearchToolbar = !showSearchToolbar))
          ],
        ),
      ),
    );
  }
}

// NOTE: The code will be moved into the package in the future; currently just demonstration and testing purpose only
Widget _generateRichText(
    List<PdfPageTextFragment> list, PdfPage page, Rect pageRect) {
  final scale = pageRect.height / page.height;
  final texts = <Widget>[];

  Rect? finalBounds;
  for (int i = 0; i < list.length; i++) {
    final text = list[i];
    if (text.bounds.isEmpty) continue;
    final rect = text.bounds.toRect(height: page.height, scale: scale);
    if (rect.isEmpty) continue;
    if (finalBounds == null) {
      finalBounds = rect;
    } else {
      finalBounds = finalBounds.expandToInclude(rect);
    }
  }
  if (finalBounds == null) return Container();

  for (int i = 0; i < list.length; i++) {
    final text = list[i];
    if (text.bounds.isEmpty) continue;
    final rect = text.bounds.toRect(height: page.height, scale: scale);
    if (rect.isEmpty) continue;
    texts.add(
      Positioned(
        left: rect.left - finalBounds.left,
        top: rect.top - finalBounds.top,
        width: rect.width,
        height: rect.height,
        child: FittedBox(
          fit: BoxFit.fill,
          child: Text(
            text.fragment,
            style: TextStyle(
              fontSize: 5,
              color: Colors.transparent,
              background: Paint()
                ..color = Colors.red.withAlpha(50)
                ..style = PaintingStyle.fill,
            ),
          ),
        ),
      ),
    );
  }
  return Positioned(
    left: pageRect.left + finalBounds.left,
    top: pageRect.top + finalBounds.top,
    width: finalBounds.width,
    height: finalBounds.height,
    child: SelectionArea(
      child: Stack(
        children: texts,
      ),
    ),
  );
}