Lector

repository·main·Indexed 18 days ago

https://github.com/anaralabs/lector

A composable, headless PDF viewer toolkit for React applications powered by PDF.js. Lector provides low-level primitives—including Root, Pages, and specialized layers like CanvasLayer, TextLayer, and HighlightLayer—allowing developers to build fully custom PDF viewing experiences with support for text selection, responsive layouts, and dark mode.

Tokens
22.7K
Snippets
73
Records
92
Agent score
63%

What's inside @anaralabs/lector

  1. Lector Core Features

    main

    Lector is a headless PDF viewer for React that provides the following core capabilities:

    • PDF rendering: Utilizes a canvas layer for high-performance rendering.
    • Text interaction: Supports text selection and copying from the PDF content.
    • UI Adaptability: Includes responsive layout support and dark mode compatibility.
    • UX States: Built-in handling for loading states.
  2. How Lector's component architecture works

    main

    Lector uses a three-layer component-based architecture to build PDF viewers:

    1. Root Container: The top-level component that manages the PDF document state and context.
    2. Pages Container: A component that handles the layout and virtualization of the individual pages.
    3. Layer Components: Specialized components rendered inside each page to handle specific aspects like visual rendering (canvas), text interaction (text layer), or annotations.

    This separation allows you to compose custom viewers by choosing which layers to include and how to arrange the UI components.

    <Root source='/sample.pdf'>
      <Pages>
        <Page>
          <CanvasLayer />
          <TextLayer />
        </Page>
      </Pages>
    </Root>
  3. Understand dark mode limitations and behavior

    main

    Lector's dark mode uses OKLab color space remapping to flip perceived lightness while preserving hue and chroma.

    Key Behaviors:

    • Images: Stay pixel-perfect and are NOT inverted.
    • Scanned PDFs: Since they are treated as single images, they remain in light mode.
    • Text/Lines: Black text becomes the palette foreground; white paper becomes the palette background.

    Limitations:

    • Midtones: Can land a few percent off original opacity in luminosity soft-masks.
    • Mesh-gradients: Keep their original colors.
    • DOM Annotations: Elements rendered via AnnotationLayer (like form widgets or link borders) keep original colors and must be styled via CSS.
    • Blend Modes: Overlays using mix-blend-multiply may need to be switched to mix-blend-screen for dark mode.
    • Custom CanvasFactory: Providing your own CanvasFactory via documentOptions will disable scratch-canvas recoloring.
  4. Implement custom PDF navigation buttons

    main

    To create custom navigation controls (like 'Previous', 'Next', or a page number input) in Lector, you can use the usePdf and usePdfJump hooks.

    1. Use usePdf to access the current state of the PDF, such as state.currentPage and state.pdfDocumentProxy?.numPages.
    2. Use usePdfJump to obtain the jumpToPage function, which allows you to programmatically navigate to a specific page.
    3. Wrap your viewer in the Root component to provide the PDF source and context to these hooks.
    import { usePdf, usePdfJump } from "@anaralabs/lector";
    import { useEffect, useState } from "react";
    
    const PageNavigationButtons = () => {
      const pages = usePdf((state) => state.pdfDocumentProxy?.numPages);
      const currentPage = usePdf((state) => state.currentPage);
      const [pageNumber, setPageNumber] = useState<string | number>(currentPage);
      const { jumpToPage } = usePdfJump();
    
      const handlePreviousPage = () => {
        if (currentPage > 1) {
          jumpToPage(currentPage - 1, { behavior: "auto" });
        }
      };
    
      const handleNextPage = () => {
        if (currentPage < pages) {
          jumpToPage(currentPage + 1, { behavior: "auto" });
        }
      };
    
      // ... implementation of UI components using these handlers
    };
  5. Install @anaralabs/lector and peer dependencies

    main

    Install the main Lector package along with pdfjs-dist using your preferred package manager. Lector requires pdfjs-dist as a peer dependency to handle PDF rendering.

    Prerequisites:

    • Node.js 18.0 or later
    • React 19 or later
    # Using npm
    npm install @anaralabs/lector pdfjs-dist
    
    # Using yarn
    yarn add @anaralabs/lector pdfjs-dist
    
    # Using pnpm
    pnpm add @anaralabs/lector pdfjs-dist
    
    # Using bun
    bun add @anaralabs/lector pdfjs-dist
  6. Best practices for PDF highlights

    main

    When implementing highlights in Lector, follow these best practices:

    • Fallback Content: Always provide fallback content using the loader prop on the <Root /> component.
    • Coordinate Units: Use relative units (pixels) for highlight coordinates to ensure accuracy.
    • Error Handling: Implement error handling when calling functions like jumpToHighlightRects to manage navigation failures.
    • Bounds Checking: Ensure highlight areas are kept within the document bounds.
  7. Implement a basic PDF viewer with Lector

    main

    To create a minimal PDF viewer, use the Root component to wrap your viewer configuration. You must provide a source (the URL to the PDF file) and define the rendering structure using Pages, Page, CanvasLayer, and TextLayer.

    Key features of this implementation pattern:

    • Container Control: Use className on the Root component to define the viewer's dimensions (e.g., w-full h-[500px]).
    • Loading States: Pass a React component to the loader prop to display a placeholder while the PDF is being fetched.
    • Theming: Use the colorScheme prop ('light' or 'dark') to control the native rendering mode of the PDF.
    • Rendering Layers:
      • <CanvasLayer />: Renders the visual content of the PDF page.
      • <TextLayer />: Enables text selection and copying capabilities.
    • Structure: The hierarchy must follow Root -> Pages -> Page -> [CanvasLayer, TextLayer].
    "use client";
    
    import { CanvasLayer, Page, Pages, Root, TextLayer } from "@anaralabs/lector";
    import React from "react";
    
    const fileUrl = "/pdf/pathways.pdf";
    
    const Basic = () => {
      return (
        <Root
          source={fileUrl}
          className='w-full h-[500px] border overflow-hidden rounded-lg'
          loader={<div className='p-4'>Loading...</div>}
          colorScheme="light">
          <Pages>
            <Page>
              <CanvasLayer />
              <TextLayer />
            </Page>
          </Pages>
        </Root>
      );
    };
    
    export default Basic;
  8. Enable clickable links with AnnotationLayer

    main

    To enable clickable links (both internal page navigation and external URLs) in your PDF viewer, you must include the AnnotationLayer component within a Page component. The AnnotationLayer is responsible for detecting and rendering link annotations from the PDF.

    "use client";
    
    import {
      Root,
      Pages,
      Page,
      CanvasLayer,
      TextLayer,
      AnnotationLayer,
    } from "@anaralabs/lector";
    
    export default function MyPdfViewer() {
      return (
        <Root source="/my-document.pdf">
          <Pages>
            <Page>
              <CanvasLayer />
              <TextLayer />
              <AnnotationLayer />
            </Page>
          </Pages>
        </Root>
      );
    }
  9. Implement text selection and highlighting with Select

    main

    To enable text selection and highlighting in a PDF, use the @anaralabs/lector components. You must wrap your viewer in a Root component providing the PDF source, and include a HighlightLayer within your page structure to render the visual highlights.

    Key components required:

    • Root: The main provider that manages the PDF state and source.
    • Pages & Page: Container components for the PDF pages.
    • CanvasLayer: Renders the actual PDF content.
    • TextLayer: Enables text selection capabilities.
    • HighlightLayer: Renders the visual highlight overlays.
    "use client";
    
    import {
      Root,
      Pages,
      Page,
      CanvasLayer,
      TextLayer,
      HighlightLayer,
      usePdf,
      useSelectionDimensions,
    } from "@anaralabs/lector";
    
    const PdfHighlightSelect = () => (
      <Root
        source="/pdf/document.pdf"
        className="flex bg-gray-50 h-[500px]"
        loader={<div className="p-4">Loading...</div>}
      >
        <HighlightLayerContent />
      </Root>
    );
    
    const HighlightLayerContent = () => {
      const selectionDimensions = useSelectionDimensions();
      const setHighlights = usePdf((state) => state.setHighlight);
    
      const handleHighlight = () => {
        const dimension = selectionDimensions.getDimension();
        if (dimension && !dimension.isCollapsed) {
          setHighlights(dimension.highlights);
        }
      };
    
      return (
        <Pages className="p-4 w-full">
          <Page>
            {/* Custom selection logic can be injected here */}
            <CanvasLayer />
            <TextLayer />
            <HighlightLayer className="bg-yellow-200/70" />
          </Page>
        </Pages>
      );
    };
  10. Best practices for Lector thumbnail implementations

    main

    When building PDF viewers with thumbnail navigation in Lector, follow these best practices for optimal user experience:

    • State Management: Use React state to handle UI toggles (e.g., showing/hiding the thumbnail sidebar).
    • Loading States: Always provide a loader prop to the Root component to improve UX during PDF initialization.
    • Smooth Transitions: Use CSS transitions (e.g., transition-all duration-300) when toggling the visibility of the thumbnail panel to prevent jarring layout shifts.
    • Interaction Feedback: Add hover effects to Thumbnail components to provide visual feedback.
    • Rendering Optimization: Ensure thumbnail rendering is optimized to maintain performance in large documents.
  11. Implement a Custom Search UI with Lector

    main

    You can build a custom search interface by using the useSearch hook to access search results and the search function. To implement a complete search experience, follow these steps:

    1. Wrap your application in the <Root> component and provide a PDF source.
    2. Use the <Search> component as a provider for your search UI.
    3. Access search state via useSearch(), which provides searchResults (containing exactMatches and fuzzyMatches) and the search method.
    4. Handle results by mapping over results.exactMatches and using calculateHighlightRects combined with usePdfJump to navigate to the found text.
    5. Implement pagination by checking results.hasMoreResults and calling search with an increased limit.

    Note: It is recommended to use debouncing on your search input to prevent excessive search calls.

    import {
      Root,
      Pages,
      Page,
      CanvasLayer,
      TextLayer,
      HighlightLayer,
      Search,
      calculateHighlightRects,
      usePdf,
      usePdfJump,
      useSearch,
    } from "@anaralabs/lector";
    
    // ... inside your component
    const { searchResults: results, search } = useSearch();
    
    // To search with a limit:
    await search(debouncedSearchText, { limit: 5 });
    
    // To jump to a result:
    const { jumpToHighlightRects } = usePdfJump();
    const pageProxy = getPdfPageProxy(result.pageNumber);
    const rects = await calculateHighlightRects(pageProxy, {
      pageNumber: result.pageNumber,
      text: result.text,
      matchIndex: result.matchIndex,
      searchText: originalSearchText,
    });
    jumpToHighlightRects(rects, "pixels");