diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..1039c520
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github:
+ - espresso3389
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..02c80476
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# Set update schedule for GitHub Actions
+# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
+
+version: 2
+updates:
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ # Check for updates to GitHub Actions every week
+ interval: "weekly"
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
new file mode 100644
index 00000000..2a2023d6
--- /dev/null
+++ b/.github/workflows/build-test.yml
@@ -0,0 +1,287 @@
+name: Build Test
+
+on:
+ push:
+ branches: [ master, main ]
+ paths-ignore:
+ - '**.md'
+ - 'doc/**'
+
+ pull_request:
+ branches: [ master, main ]
+ paths-ignore:
+ - '**.md'
+ - 'doc/**'
+ workflow_dispatch:
+ inputs:
+ build_android:
+ description: 'Build Android'
+ required: false
+ type: boolean
+ default: true
+ build_ios:
+ description: 'Build iOS'
+ required: false
+ type: boolean
+ default: true
+ build_macos:
+ description: 'Build macOS'
+ required: false
+ type: boolean
+ default: true
+ build_linux:
+ description: 'Build Linux'
+ required: false
+ type: boolean
+ default: true
+ build_linux_arm64:
+ description: 'Build Linux ARM64'
+ required: false
+ type: boolean
+ default: true
+ build_windows:
+ description: 'Build Windows'
+ required: false
+ type: boolean
+ default: true
+ build_windows_arm64:
+ description: 'Build Windows ARM64'
+ required: false
+ type: boolean
+ default: true
+ build_web:
+ description: 'Build Web'
+ required: false
+ type: boolean
+ default: true
+
+jobs:
+ # Android build
+ android:
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_android }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Setup Java
+ uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
+ with:
+ distribution: 'temurin'
+ java-version: '18'
+
+ - name: Setup Flutter
+ run: |
+ git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter
+ echo "$HOME/flutter/bin" >> $GITHUB_PATH
+ ~/flutter/bin/flutter config --no-enable-linux-desktop
+ ~/flutter/bin/flutter channel stable
+ ~/flutter/bin/flutter doctor -v
+ yes | ~/flutter/bin/flutter doctor --android-licenses
+
+ - name: Install dependencies
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter pub get
+
+ - name: Build App Bundle
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter build apk --debug --verbose
+
+ # iOS build
+ ios:
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_ios }}
+ runs-on: macos-latest
+ strategy:
+ matrix:
+ package_manager: [cocoapods, swiftpm]
+ name: ios-${{ matrix.package_manager }}
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Setup Flutter
+ run: |
+ git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter
+ echo "$HOME/flutter/bin" >> $GITHUB_PATH
+ ~/flutter/bin/flutter config --no-enable-android --no-enable-macos-desktop
+ ~/flutter/bin/flutter channel stable
+ ~/flutter/bin/flutter doctor -v
+
+ - name: Configure package manager
+ run: |
+ if [ "${{ matrix.package_manager }}" = "swiftpm" ]; then
+ ~/flutter/bin/flutter config --enable-swift-package-manager
+ else
+ ~/flutter/bin/flutter config --no-enable-swift-package-manager
+ fi
+
+ - name: Install dependencies
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter pub get
+
+ - name: Build iOS (no signing) - ${{ matrix.package_manager }}
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter build ios --debug --no-codesign --verbose
+
+ # macOS build
+ macos:
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_macos }}
+ runs-on: macos-latest
+ strategy:
+ matrix:
+ package_manager: [cocoapods, swiftpm]
+ name: macos-${{ matrix.package_manager }}
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Setup Flutter
+ run: |
+ git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter
+ echo "$HOME/flutter/bin" >> $GITHUB_PATH
+ ~/flutter/bin/flutter config --no-enable-android --no-enable-ios
+ ~/flutter/bin/flutter channel stable
+ ~/flutter/bin/flutter doctor -v
+
+ - name: Configure package manager
+ run: |
+ if [ "${{ matrix.package_manager }}" = "swiftpm" ]; then
+ ~/flutter/bin/flutter config --enable-swift-package-manager
+ else
+ ~/flutter/bin/flutter config --no-enable-swift-package-manager
+ fi
+
+ - name: Install dependencies
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter pub get
+
+ - name: Build macOS - ${{ matrix.package_manager }}
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter build macos --debug --verbose
+
+ # Linux build
+ linux:
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Install Linux dependencies
+ run: |
+ sudo apt-get update -y
+ sudo apt-get install -y ninja-build libgtk-3-dev
+
+ - name: Setup Flutter
+ run: |
+ git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter
+ echo "$HOME/flutter/bin" >> $GITHUB_PATH
+ ~/flutter/bin/flutter config --no-enable-android
+ ~/flutter/bin/flutter channel stable
+ ~/flutter/bin/flutter doctor -v
+
+ - name: Install dependencies
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter pub get
+
+ - name: Build Linux
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter build linux --debug --verbose
+
+ # Linux ARM64 build
+ linux-arm64:
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_linux_arm64 }}
+ runs-on: ubuntu-24.04-arm
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Install Linux dependencies
+ run: |
+ sudo apt-get update -y
+ sudo apt-get install -y ninja-build libgtk-3-dev
+
+ - name: Setup Flutter
+ run: |
+ git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter
+ echo "$HOME/flutter/bin" >> $GITHUB_PATH
+ ~/flutter/bin/flutter config --no-enable-android
+ ~/flutter/bin/flutter channel stable
+ ~/flutter/bin/flutter doctor -v
+
+ - name: Install dependencies
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter pub get
+
+ - name: Build Linux
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter build linux --debug --verbose
+
+ # Windows build
+ windows:
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows }}
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Setup Flutter
+ shell: pwsh
+ run: |
+ git clone https://github.com/flutter/flutter.git --depth 1 --branch stable C:\flutter
+ echo "C:\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+ C:\flutter\bin\flutter.bat config --no-enable-android
+ C:\flutter\bin\flutter.bat channel stable
+ C:\flutter\bin\flutter.bat doctor -v
+
+ - name: Install dependencies
+ working-directory: packages/pdfrx/example/viewer
+ run: C:\flutter\bin\flutter.bat pub get
+
+ - name: Build Windows
+ working-directory: packages/pdfrx/example/viewer
+ run: C:\flutter\bin\flutter.bat build windows --debug --verbose
+
+ # Windows ARM64 build (requires ARM64 runner or cross-compilation)
+ windows-arm64:
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_windows_arm64 }}
+ runs-on: windows-11-arm
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Setup Flutter (master branch for ARM64 support)
+ shell: pwsh
+ run: |
+ git clone https://github.com/flutter/flutter.git --depth 1 C:\flutter
+ echo "C:\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+ C:\flutter\bin\flutter.bat config --no-enable-android
+ C:\flutter\bin\flutter.bat doctor -v
+
+ - name: Install dependencies
+ working-directory: packages/pdfrx/example/viewer
+ run: C:\flutter\bin\flutter.bat pub get
+
+ - name: Build Windows ARM64
+ working-directory: packages/pdfrx/example/viewer
+ run: C:\flutter\bin\flutter.bat build windows --debug --verbose
+
+ # Web build
+ web:
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_web }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Setup Flutter
+ run: |
+ git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter
+ echo "$HOME/flutter/bin" >> $GITHUB_PATH
+ ~/flutter/bin/flutter config --no-enable-android --no-enable-linux-desktop
+ ~/flutter/bin/flutter channel stable
+ ~/flutter/bin/flutter doctor -v
+
+ - name: Install dependencies
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter pub get
+
+ - name: Build Web
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter build web --verbose
+
+ - name: Build Web (WASM)
+ working-directory: packages/pdfrx/example/viewer
+ run: flutter build web --wasm --verbose
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
new file mode 100644
index 00000000..def5c851
--- /dev/null
+++ b/.github/workflows/claude.yml
@@ -0,0 +1,37 @@
+name: Claude Code
+
+on:
+ issue_comment:
+ types: [created]
+ pull_request_review_comment:
+ types: [created]
+ issues:
+ types: [opened, assigned]
+ pull_request_review:
+ types: [submitted]
+
+jobs:
+ claude:
+ if: |
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(vars.CLAUDE_ALLOWED_USERS, github.event.comment.user.login)) ||
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(vars.CLAUDE_ALLOWED_USERS, github.event.comment.user.login)) ||
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(vars.CLAUDE_ALLOWED_USERS, github.event.review.user.login)) ||
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(vars.CLAUDE_ALLOWED_USERS, github.event.issue.user.login))
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ issues: read
+ id-token: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code
+ id: claude
+ uses: anthropics/claude-code-action@beta
+ with:
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
+
diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml
index bd073e4f..1efcd7ff 100644
--- a/.github/workflows/github-pages.yml
+++ b/.github/workflows/github-pages.yml
@@ -4,42 +4,191 @@ on:
push:
branches:
- master
+ paths-ignore:
+ - '**.md'
+ - 'doc/**'
jobs:
- build-and-deploy:
+ build:
runs-on: ubuntu-latest
- # NOTE: This workflow automatically update gh-pages branch and it requires write permissions
- permissions:
- contents: write
+ strategy:
+ matrix:
+ example: [viewer, pdf_combine]
steps:
- name: Checkout repository
- uses: actions/checkout@v4
- - name: Set up Flutter
- uses: subosito/flutter-action@v2
- with:
- channel: stable
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ - name: Setup Flutter
+ run: |
+ git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter
+ echo "$HOME/flutter/bin" >> $GITHUB_PATH
+ ~/flutter/bin/flutter config --no-enable-android --no-enable-linux-desktop
+ ~/flutter/bin/flutter channel stable
- name: Check Flutter SDK version
run: |
flutter --version
FLUTTER_VERSION=$(flutter --version | head -n 1 | awk '{print $2}')
echo "FLUTTER_VERSION=$FLUTTER_VERSION" >> $GITHUB_ENV
+
- name: Check pdfrx version
run: |
PDFRX_VERSION=$(awk -F": " '/^version:/ {print $2}' pubspec.yaml)
echo "PDFRX_VERSION=$PDFRX_VERSION" >> $GITHUB_ENV
- - name: Enable Flutter Web
- run: flutter config --enable-web
- name: Install dependencies
run: flutter pub get
- name: Build Flutter Web App (WASM)
run: |
- cd example/viewer/
- flutter build web --wasm --release --dart-define=GITHUB_COMMIT=$GITHUB_SHA --dart-define=PDFRX_VERSION=$PDFRX_VERSION --dart-define=FLUTTER_VERSION=$FLUTTER_VERSION
- sed -i \
- -e 's|||' \
- -e "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" \
- build/web/index.html
+ cd packages/pdfrx/example/${{ matrix.example }}/
+ flutter build web --wasm --release --base-href "/pdfrx/${{ matrix.example }}/" --dart-define=GITHUB_COMMIT=$GITHUB_SHA --dart-define=PDFRX_VERSION=$PDFRX_VERSION
+ sed -i "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" build/web/index.html
+ - name: Upload build artifact
+ uses: actions/upload-artifact@v6.0.0
+ with:
+ name: ${{ matrix.example }}-web
+ path: packages/pdfrx/example/${{ matrix.example }}/build/web/
+ retention-days: 1
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ # NOTE: This workflow automatically update gh-pages branch and it requires write permissions
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ - name: Download all artifacts
+ uses: actions/download-artifact@v7
+ with:
+ path: artifacts/
+ - name: Prepare deployment directory
+ run: |
+ mkdir -p deploy_temp
+
+ # Copy each example to its subdirectory
+ cp -r artifacts/viewer-web deploy_temp/viewer
+ cp -r artifacts/pdf_combine-web deploy_temp/pdf_combine
+
+ # Extract version info from artifact
+ VERSION_INFO=$(grep 'pdfrx-configs' artifacts/viewer-web/index.html | sed 's/.*content="\([^"]*\)".*/\1/')
+ PDFRX_VERSION=$(echo "$VERSION_INFO" | sed 's/.*pdfrx=\([^,]*\).*/\1/')
+ FLUTTER_VERSION=$(echo "$VERSION_INFO" | sed 's/.*flutter=\([^,]*\).*/\1/')
+ echo "PDFRX_VERSION=$PDFRX_VERSION" >> $GITHUB_ENV
+ echo "FLUTTER_VERSION=$FLUTTER_VERSION" >> $GITHUB_ENV
+
+ # Create root index.html
+ cat > deploy_temp/index.html << 'EOF'
+
+
+
+
+
+
+ pdfrx - Flutter PDF Library
+
+
+
+ pdfrx - Flutter PDF Library
+ A powerful and flexible PDF viewer and manipulation library for Flutter.
+
+
+
+
PDF Viewer
+
A full-featured PDF viewer with zoom, pan, text selection, and more.
+
Launch Viewer Demo
+
+
+
+
+
+
+
+
+ EOF
+
+ # Add version info to index.html
+ COMMIT_SHORT=${GITHUB_SHA:0:7}
+ sed -i "s/__CONFIGS__/pdfrx=${PDFRX_VERSION},commit=${GITHUB_SHA},flutter=${FLUTTER_VERSION}/g" deploy_temp/index.html
+ sed -i "s/__PDFRX_VERSION__/${PDFRX_VERSION}/g" deploy_temp/index.html
+ sed -i "s/__COMMIT__/${GITHUB_SHA}/g" deploy_temp/index.html
+ sed -i "s/__COMMIT_SHORT__/${COMMIT_SHORT}/g" deploy_temp/index.html
+ sed -i "s/__FLUTTER_VERSION__/${FLUTTER_VERSION}/g" deploy_temp/index.html
- name: Configure Git for deployment
run: |
git config user.email "action@github.com"
@@ -47,7 +196,7 @@ jobs:
git remote set-url origin https://x-access-token:${{ secrets.TOKEN_FOR_GHPAGE_DEPLOYMENT }}@github.com/${{ github.repository }}.git
- name: Deploy to GitHub Pages using subtree push
run: |
- git add -f example/viewer/build/web
+ git add -f deploy_temp
git commit -m "$PDFRX_VERSION $GITHUB_SHA"
- git subtree split --prefix example/viewer/build/web -b tmp
+ git subtree split --prefix deploy_temp -b tmp
git push -f origin tmp:gh-pages
diff --git a/.github/workflows/pana-analysis.yml b/.github/workflows/pana-analysis.yml
new file mode 100644
index 00000000..37f40d0b
--- /dev/null
+++ b/.github/workflows/pana-analysis.yml
@@ -0,0 +1,54 @@
+name: Pana Analysis
+
+on:
+ push:
+ branches: [ master, main ]
+ paths-ignore:
+ - '**.md'
+ - 'doc/**'
+ pull_request:
+ branches: [ master, main ]
+ paths-ignore:
+ - '**.md'
+ - 'doc/**'
+ workflow_dispatch:
+
+jobs:
+ pana:
+ name: Run pana analysis
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - package_name: pdfrx_engine
+ package_path: packages/pdfrx_engine
+ - package_name: pdfrx
+ package_path: packages/pdfrx
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Install Linux dependencies
+ run: |
+ sudo apt-get update -y
+ sudo apt-get install -y webp
+
+ - name: Setup Flutter
+ run: |
+ git clone https://github.com/flutter/flutter.git --depth 1 --branch stable ~/flutter
+ echo "$HOME/flutter/bin" >> $GITHUB_PATH
+ ~/flutter/bin/flutter config --no-enable-android --no-enable-linux-desktop
+ ~/flutter/bin/flutter channel stable
+ ~/flutter/bin/flutter doctor -v
+
+ - name: Pub get (workspace)
+ run: flutter pub get
+
+ - name: Activate pana
+ run: |
+ dart pub global activate pana
+ echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
+
+ - name: Run pana (${{ matrix.package_name }})
+ working-directory: ${{ matrix.package_path }}
+ run: ~/.pub-cache/bin/pana --json --no-warning --exit-code-threshold 0 | jq -e '.tags | any(. == "is:wasm-ready")'
diff --git a/.github/workflows/pdfium-apple-release.yml b/.github/workflows/pdfium-apple-release.yml
index c0dac841..c7923d2f 100644
--- a/.github/workflows/pdfium-apple-release.yml
+++ b/.github/workflows/pdfium-apple-release.yml
@@ -6,13 +6,36 @@ on:
jobs:
build:
runs-on: macos-latest
+ strategy:
+ matrix:
+ target: [ios, macos]
steps:
- name: Checkout
- uses: actions/checkout@v2
- - name: Build PDFium
- run: ./darwin/pdfium/build
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ - name: Build PDFium ${{ matrix.target }}
+ run: ./packages/pdfrx/darwin/pdfium/build ${{ matrix.target }}
+ - name: Upload PDFium artifact
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: pdfium-${{ matrix.target }}
+ path: ./packages/pdfrx/darwin/pdfium/pdfium-${{ matrix.target }}.zip
+
+ release:
+ needs: build
+ runs-on: macos-latest
+ steps:
+ - name: Download iOS artifact
+ uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v6.0.0
+ with:
+ name: pdfium-ios
+ path: ./packages/pdfrx/darwin/pdfium/
+ - name: Download macOS artifact
+ uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v6.0.0
+ with:
+ name: pdfium-macos
+ path: ./packages/pdfrx/darwin/pdfium/
- name: Release PDFium
- uses: softprops/action-gh-release@v1
+ uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v0.1.15
with:
token: ${{ secrets.TOKEN_FOR_RELEASE }}
tag_name: ${{ github.ref_name }}
@@ -20,5 +43,5 @@ jobs:
prerelease: false
body: iOS/macOS PDFium prebuilt binary distribution for pdfrx (${{ github.ref_name }}).
files: |
- ./darwin/pdfium/pdfium-ios.tgz
- ./darwin/pdfium/pdfium-macos.tgz
+ ./packages/pdfrx/darwin/pdfium/pdfium-ios.zip
+ ./packages/pdfrx/darwin/pdfium/pdfium-macos.zip
diff --git a/.gitignore b/.gitignore
index f15d08a2..f06a6c18 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
.history
.svn/
migrate_working_dir/
+.tmp/
# IntelliJ related
*.iml
@@ -19,6 +20,10 @@ migrate_working_dir/
# C++
.cxx
+# For pdfium builds on macOS
+.build/
+.swiftpm/
+
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
@@ -27,8 +32,21 @@ migrate_working_dir/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
-**/doc/api/
.dart_tool/
-build/
-/test/.tmp
+.claude/
+
+pubspec_overrides.yaml
+.mcp.json
+
+.serena/
+
+# SHOULD WE NEED THIS??: iOS/macOS build artifacts that change frequently
+**/ios/Podfile.lock
+**/macos/Podfile.lock
+**/ios/**/*.xcodeproj/project.pbxproj
+**/macos/**/*.xcodeproj/project.pbxproj
+
+build/ios/
+
+.flutter-plugins-dependencies
diff --git a/.pubignore b/.pubignore
deleted file mode 100644
index 1d508c00..00000000
--- a/.pubignore
+++ /dev/null
@@ -1,2 +0,0 @@
-wasm/
-example/
diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
index 2670dd6d..7e52833e 100644
--- a/.vscode/c_cpp_properties.json
+++ b/.vscode/c_cpp_properties.json
@@ -1,21 +1,48 @@
{
"env": {
- "ndkVersion": "21.1.6352462"
+ "ndkVersion": "27.0.12077973"
},
"configurations": [
{
"name": "Win32",
"includePath": [
- "${workspaceFolder}/android/.lib/include",
- "${workspaceFolder}/iOS/Classes/*",
- "${env:LOCALAPPDATA}/Android/Sdk/ndk/${ndkVersion}/sysroot/usr/include/*"
+ "${workspaceFolder}/packages/pdfrx/android/.lib/latest/include",
+ "${workspaceFolder}/packages/pdfrx/example/viewer/build/windows/x64/.lib/latest/include",
+ "${workspaceFolder}/packages/pdfrx/src",
+ "${env.LOCALAPPDATA}/Android/Sdk/ndk/${ndkVersion}/sysroot/usr/include/*"
],
"defines": [],
"cStandard": "c17",
"cppStandard": "c++17",
- "intelliSenseMode": "linux-clang-arm64",
- "configurationProvider": "go2sh.cmake-integration"
+ "intelliSenseMode": "msvc-x64"
+ },
+ {
+ "name": "Linux",
+ "includePath": [
+ "${workspaceFolder}/packages/pdfrx/android/.lib/latest/include",
+ "${workspaceFolder}/packages/pdfrx/example/viewer/build/linux/x64/.lib/latest/include",
+ "${workspaceFolder}/packages/pdfrx/src"
+ ],
+ "defines": [],
+ "cStandard": "c17",
+ "cppStandard": "c++17",
+ "intelliSenseMode": "linux-clang-arm64"
+ },
+ {
+ "name": "Mac",
+ "includePath": [
+ "${workspaceFolder}/packages/pdfrx/android/.lib/latest/include",
+ "${workspaceFolder}/packages/pdfrx/darwin/pdfrx/Sources/interop",
+ "${workspaceFolder}/packages/pdfrx/src",
+ "${workspaceFolder}/packages/pdfrx/darwin/pdfium/.lib/**",
+ "${workspaceFolder}/packages/pdfrx/example/viewer/build/**"
+ ],
+ "defines": [],
+ "cStandard": "c17",
+ "cppStandard": "c++17",
+ "intelliSenseMode": "macos-clang-arm64"
}
],
- "version": 4
+ "version": 4,
+ "enableConfigurationSquiggles": true
}
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 00000000..e57075c2
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,8 @@
+{
+ "recommendations": [
+ "dart-code.dart-code",
+ "dart-code.flutter",
+ "vknabel.vscode-swiftformat",
+ "ms-vscode.cpptools-extension-pack"
+ ]
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index dfe3c507..134f4d1d 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,17 +5,37 @@
"version": "0.2.0",
"configurations": [
{
- "name": "viewer example",
+ "name": "Example",
"request": "launch",
"type": "dart",
- "program": "example/viewer/lib/main.dart"
+ "program": "packages/pdfrx/example/viewer/lib/main.dart"
},
{
- "name": "viewer example (WASM)",
+ "name": "WASM",
"request": "launch",
"type": "dart",
- "program": "example/viewer/lib/main.dart",
+ "program": "packages/pdfrx/example/viewer/lib/main.dart",
"args": ["-d", "chrome", "--wasm"]
+ },
+ {
+ "name": "WASM-Server",
+ "request": "launch",
+ "type": "dart",
+ "program": "packages/pdfrx/example/viewer/lib/main.dart",
+ "args": ["-d", "web-server", "--wasm", "--web-port", "8080", "--web-hostname", "127.0.0.1"]
+ },
+ {
+ "name": "PDF Combine",
+ "request": "launch",
+ "type": "dart",
+ "program": "packages/pdfrx/example/pdf_combine/lib/main.dart"
+ },
+ {
+ "name": "pdfrx:remove_wasm_modules",
+ "request": "launch",
+ "type": "dart",
+ "program": "packages/pdfrx/bin/remove_wasm_modules.dart",
+ "args": []
}
]
-}
\ No newline at end of file
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d8f1a982..827beeee 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,13 +1,17 @@
{
"cSpell.words": [
"AACTION",
+ "AARRGGBB",
"ACRO",
"alloc",
+ "allprojects",
"ANNOT",
"Annots",
"Antialiasing",
+ "appbundle",
"APPEARANCEMODE",
"ARGB",
+ "armeabi",
"autofocus",
"bblanchon",
"Bezier",
@@ -15,10 +19,13 @@
"bgra",
"BSTR",
"buflen",
+ "buildscript",
"BYTESTRING",
"CALGRAY",
"calloc",
"CALRGB",
+ "ccall",
+ "CGPDF",
"Charcodes",
"clippath",
"cmap",
@@ -26,10 +33,15 @@
"COLORSPACE",
"COLORTYPE",
"COMBOBOX",
+ "contextmenu",
+ "coregraphics",
"credentialless",
"Cupertino",
+ "cwrap",
"d_reclen",
+ "dartdoc",
"dartify",
+ "deinit",
"Dests",
"DEVICECMYK",
"DEVICEGRAY",
@@ -46,12 +58,14 @@
"emscripten",
"endtemplate",
"errn",
+ "externref",
"fcntl",
"ffigen",
"FILEACCESS",
"FILEATTACHMENT",
"FILEHANDLER",
"FILEIDTYPE",
+ "FILEWRITE",
"fillmode",
"findwhat",
"FITBH",
@@ -73,6 +87,7 @@
"FPDFPAGE",
"fpdfview",
"FREETEXT",
+ "FSDK",
"FXCT",
"getdents",
"glyphpath",
@@ -104,6 +119,7 @@
"LISTBOX",
"listenables",
"localtime",
+ "LOGFONT",
"longjmp",
"LTRB",
"LTWH",
@@ -114,11 +130,13 @@
"maxage",
"microtask",
"MOVETO",
+ "Naskh",
"NATIVETEXT",
"NESW",
"newfstatat",
"NOEXPORT",
"NOROTATE",
+ "noto",
"NOVIEW",
"NOZOOM",
"NULLOBJ",
@@ -141,6 +159,7 @@
"POLYLINE",
"PRINTERMARK",
"PRINTMODE",
+ "Pthread",
"pubspec",
"QUADPOINTSF",
"RADIOBUTTON",
@@ -157,6 +176,7 @@
"SCHHANDLE",
"Schyler",
"scrollable",
+ "Scrollables",
"selectable",
"selectables",
"SIZEF",
@@ -172,6 +192,8 @@
"sublist",
"Subrect",
"supercedes",
+ "swiftpm",
+ "SYSFONTINFO",
"SYSTEMTIME",
"TEXTONLY",
"TEXTPAGE",
@@ -182,6 +204,7 @@
"TRAPNET",
"TRUETYPE",
"tzset",
+ "uleb",
"unawaited",
"uncollapsed",
"unlinkat",
@@ -189,6 +212,7 @@
"VBEAM",
"VIEWERREF",
"viiii",
+ "vrtl",
"vsync",
"WCHAR",
"WIDESTRING",
@@ -198,7 +222,9 @@
"XFAWIDGET",
"xobject",
"YESNO",
- "YESNOCANCEL"
+ "YESNOCANCEL",
+ "YMDHMS",
+ "ZapfDingbats"
],
"editor.codeActionsOnSave": {
"source.fixAll": "always",
@@ -277,5 +303,23 @@
"xloctime": "cpp",
"xstring": "cpp"
},
- "cmake.ignoreCMakeListsMissing": true
-}
\ No newline at end of file
+ "cmake.ignoreCMakeListsMissing": true,
+ "[dart]": {
+ "editor.formatOnSave": true,
+ "editor.formatOnType": true,
+ "editor.rulers": [
+ 120
+ ]
+ },
+ "[swift]": {
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "vknabel.vscode-swiftformat",
+ "editor.rulers": [
+ 120
+ ]
+ },
+ "swiftformat.enable": true,
+ "swiftformat.onlyEnableOnSwiftPMProjects": false,
+ "swiftformat.onlyEnableWithConfig": false,
+ "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable"
+}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..3880e83d
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,31 @@
+# AGENTS.md
+
+This file provides guidance to AI agents and developers when working with code in this repository.
+
+## Quick Start for Agents
+
+- Keep existing user changes intact; if you notice unexpected edits you didn't make, pause and ask the user how to proceed.
+- Prefer fast, non-destructive tools (`rg`, `rg --files`, targeted tests) and run commands with an explicit `workdir`; avoid wandering `cd` commands.
+- Leave release artifacts (`CHANGELOG.md`, version numbers, tags) untouched unless the task is explicitly about publishing.
+- Default to ASCII output and add only brief clarifying comments when the code is non-obvious.
+
+## Documentation Index
+
+Detailed guidance is split into focused files in `doc/agents/`:
+
+- [doc/agents/PROJECT-STRUCTURE.md](doc/agents/PROJECT-STRUCTURE.md) - Package overview, dependencies, and architecture
+- [doc/agents/COMMANDS.md](doc/agents/COMMANDS.md) - Common commands, testing, and platform builds
+- [doc/agents/RELEASING.md](doc/agents/RELEASING.md) - Release process and publishing checklist
+- [doc/agents/CODE-STYLE.md](doc/agents/CODE-STYLE.md) - Code style, documentation, and dependency policies
+
+## Project Overview (Summary)
+
+pdfrx is a monorepo containing five packages:
+
+1. **pdfium_dart** - Low-level Dart FFI bindings for PDFium
+2. **pdfium_flutter** - Flutter FFI plugin bundling PDFium binaries
+3. **pdfrx_engine** - Platform-agnostic PDF rendering API (pure Dart)
+4. **pdfrx** - Cross-platform PDF viewer Flutter plugin
+5. **pdfrx_coregraphics** - CoreGraphics-backed renderer for iOS/macOS (experimental)
+
+See [doc/agents/PROJECT-STRUCTURE.md](doc/agents/PROJECT-STRUCTURE.md) for detailed package descriptions.
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 0c6d8c10..00000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,695 +0,0 @@
-# 1.1.11
-
-- Color.withOpacity -> Color.withValues, Color.value -> Color.toARGB32()
-
-# 1.1.10
-
-- Update project structure to conform to [Package layout conventions](https://dart.dev/tools/pub/package-layout)
-- revert: example code move on 1.1.9
-
-# 1.1.9
-
-- Move back the example viewer to example directory
-
-# 1.1.8
-
-- Internal refactoring to improve the code integrity
-
-# 1.1.7
-
-- Introducing allowDataOwnershipTransfer on PdfDocument.openData to allow transfer data ownership of the passed data; it is false by default to keep consistency with the previous behavior
- - This actually fixes #303 but the drawback is that extra memory may be consumed on Flutter Web...
-
-# 1.1.6
-
-- "Bleeding edge" Pdfium WASM support (disabled by default)
-
-# 1.1.5
-
-- Explicitly specify web support on pubspec.yaml
-
-# 1.1.4
-
-- SDK constraint gets back to `>=3.7.0-323.0.dev`
-
-# 1.1.3
-
-- Further WASM compatibility updates
-- Demo page: CORS override for GitHub Pages using [gzuidhof/coi-serviceworker](https://github.com/gzuidhof/coi-serviceworker)
-
-# 1.1.2
-
-- FIXED: if running with WASM enabled on Flutter Web, certain PDF file could not be loaded correctly
-- Debug log to know WASM/SharedArrayBuffer status on Flutter Web
-
-# 1.1.1
-
-- Supporting Flutter 3.29.0/Dart 3.7.0 (Stable) with workaround for breaking changes on Flutter 3.29.0 (#295)
- - It breaks compatibility with older stable Flutter versions :(
-
-# 1.0.103
-
-- Change the default CDN for pdf.js to `https://cdn.jsdelivr.net/npm/pdfjs-dist@/build/pdf.js` to deal with CORS error on loading CMAP files
-- FIXED: pdfjsGetDocumentFromData, which is used by various PdfDocument open functions, does not propagate cMapUrl/cMapPacked to the pdf.js
-
-# 1.0.102
-
-- dart2wasm compatibility updates
-- Pdf.js 4.10.38
-- PdfTextSearcher correctly releases its listeners on dispose
-- Example viewer code updates
-
-# 1.0.101
-
-- Revert commit d66fb3f that breaks consistency; Color.withValues -> Color.withOpacity
-- Update pdfium ffi bindings
-
-# 1.0.100
-
-- PdfTextSearcher introduces text caches (#293)
-- PdfTextSearcher search reset issue (#291)
-- collection's version spec. reverted to pre-1.0.95
-
-# 1.0.99
-
-- Introduces Pdfrx.fontPaths to set pdfium font loading path (#140)
-
-# 1.0.98
-
-- Introduces PdfViewerController.calcFitZoomMatrices to realize fit-to-width easier
-
-# 1.0.97
-
-- Document updates
-
-# 1.0.96
-
-- FIXED: #260 onTextSelectionChange callback cant be called
-
-# 1.0.95
-
-- FIXED: #273; apart from the ream WASM support, it fixes several compilation issues with --wasm option
-
-# 1.0.94
-
-- Merge PR #272; Fix minScale is not used
-
-# 1.0.93
-
-- Merge PR #264; Check for non-existent zoom element in PdfDest.params in some PDFs
-- FIXED: Widget tests starts to fail when using PdfViewer widget #263
-
-# 1.0.92
-
-- Merge PR #262; Remove redundant check that breaks building on some systems
-
-# 1.0.91
-
-- Fixes selection issues caused by the changes on 1.0.90
-
-# 1.0.90
-
-- Introduces selectableRegionInjector/perPageSelectableRegionInjector (#256)
-
-# 1.0.89
-
-- web 1.1.0 support (#254)
-
-# 1.0.88
-
-- Merge PR #251
-
-# 1.0.87
-
-- BREAKING CHANGE: add more parameters to PdfViewerParams.normalizeMatrix to make it easier to handle more complex situations (#239)
-
-# 1.0.86
-
-- Add PdfViewerParams.normalizeMatrix to customize the transform matrix restriction; customizing existing logic on _PdfViewerState._makeMatrixInSafeRange; for issues like #239
-
-# 1.0.85
-
-- Fixes single-page layout issue on viewer start (#247)
-- Fixes blurry image issues (#245, #232)
-
-# 1.0.84
-
-- Merge PR #230 to add try-catch on UTF-8 decoding of URI path
-
-# 1.0.83
-
-- Web related improvements
- - PDF.js 4.5.136
- - Remove dependency to dart:js_interop_unsafe
- - Remove unnecessary synchronized call
-- Improve text selection stability (#4, #185)
-- Add more mounted checks to improve PdfViewer stability and speed
-
-# 1.0.82
-
-- collection/rxdart dependency workaround (#211)
-
-# 1.0.81
-
-- Introduces PdfViewerController.useDocument to make it easy to use PdfDocument safely
-- Introduces PdfViewerController.pageCount to get page count without explicitly access PdfViewerController.pages
-- PdfViewerController.document/PdfViewerController.pages are now deprecated
-
-# 1.0.80
-
-- BREAKING CHANGE: PdfViewerParams.viewerOverlayBuilder introduces third parameter named handleLinkTap, which is used with GestureDetector to handle link-tap events on user code (#175)
-- Fix typos on README.md
-
-# 1.0.79
-
-- FIXED: RangeError on PdfViewer.uri when missing "Expires" header (#206)
-
-# 1.0.78
-
-- Add packagingOptions pickFirst to workaround multiple libpdfium.so problem on Android build (#8)
-- FIXED: \_relayoutPages may cause null access
-- Update README.md to explain PdfViewerParam.linkHandlerParams for link handling
-
-# 1.0.77
-
-- #175: Woops, just missing synchronized to call loadLinks causes multiple load invocations...
-
-# 1.0.76
-
-- Add several tweaks to reduce PdfLink's memory footprint (Related: #175)
-- Introduces PdfViewerParam.linkHandlerParams and PdfLinkHandlerParams to show/handle PDF links without using Flutter Widgets (#175)
-
-# 1.0.75
-
-- PDF.js 4.4.168
-
-# 1.0.74
-
-- Introduces PdfViewerController.getPdfPageHitTestResult
-- Introduces PdfViewerController.layout to get page layout
-
-# 1.0.73
-
-- Introduces PdfViewerParams.onViewSizeChanged, which is called on view size change
- - The feature can be used to keep the screen center on device screen rotation (#194)
-
-# 1.0.72
-
-- FIXED: Example code is not compilable
-- FIXED: Marker could not be placed correctly on the example code (#189)
-- FIXED: Updated podspec file not to download the same archive again and again (#154)
-- Introduces chromium/6555 for all platforms
- - Darwin uses pdfium-apple-v9 (chromium/6555)
- - ~~Improves memory consumption by pdfium's internal caching feature (#184)~~
-
-# 1.0.71
-
-- Introduces withCredentials for Web to download PDF file using current session credentials (Cookie) (#182)
-- FIXED: Re-download logic error that causes 416 on certain web site (#183)
-
-# 1.0.70
-
-- PdfViewer calls re-layout logic on every zoom ratio changes (#131)
-- Add PdfViewerParams.interactionEndFrictionCoefficient (#176)
-- Minor fix for downloading cache
-- rxdart gets back to 0.27.7 because 0.28.0 causes incompatibility with several other plugins...
-
-# 1.0.69
-
-- FIXED: Small Page Size PDF Not Scaling to Fit Screen (#174)
-
-# 1.0.68
-
-- Introduces PdfViewerController.setCurrentPageNumber (#152)
-- BREAKING CHANGE: Current page number behavior change (#152)
-- BREAKING CHANGE: PdfPageAnchor behavior changes for existing PdfPageAnchor enumeration values.
-- Introduces PdfPageAnchor.top/left/right/bottom
-- Introduces PdfViewerController.calcMatrixToEnsureRectVisible
-
-# 1.0.67
-
-- FIXED: LateInitializationError: Field '\_cacheBlockCount@1436474497' has not been initialized (#167)
-
-# 1.0.66
-
-- FIXED: PdfException: Failed to load PDF document (FPDF_GetLastError=3) (#166)
-- Add explicit HTTP error handling code (to show the error detail)
-- bblanchon/pdfium-binaries 127.0.6517.0 (chromium/6517) (iOS/macOS is still using 6406)
-
-# 1.0.65
-
-- Remove dependency to intl (#151)
-
-# 1.0.64
-
-- Android: minSdkVersion to 21 (related #158)
-
-# 1.0.63
-
-- Workaround for SelectionEventType.selectParagraph that is introduced in master (#156/PR #157)
- - The code uses `default` to handle the case but we should update it with the "right" code when it is introduced to the stable
-
-# 1.0.62
-
-- iOS/macOS also uses bblanchon/pdfium-binaries 125.0.6406.0 (chromium/6406)
-- Additional fix for [#147](https://github.com/espresso3389/pdfrx/issues/147)
-- Additional implementation for [#132](https://github.com/espresso3389/pdfrx/issues/132)
-
-# 1.0.61
-
-- Introduces PdfViewerParams.pageDropShadow
-- Introduces PdfViewerParams.pageBackgroundPaintCallbacks
-
-# 1.0.60
-
-- bblanchon/pdfium-binaries 125.0.6406.0 (chromium/6406)
- - default_min_sdk_version=21 to support lower API level devices ([#145](https://github.com/espresso3389/pdfrx/issues/145))
-
-# 1.0.59
-
-- Fixes concurrency issue on PdfDocument dispose (#143)
-- FIXED: Null check operator used on \_guessCurrentPage ([#147](https://github.com/espresso3389/pdfrx/issues/147))
-
-# 1.0.58
-
-- Any API calls that wraps PDFium are now completely synchronized. They are run in an app-wide single worker isolate
- - This is because PDFium does not support any kind of concurrency and even different PdfDocument instances could not be called concurrently
-
-# 1.0.57
-
-- FIXED: possible double-dispose on race condition (#136)
-- Add mechanism to cancel partial real size rendering (#137)
-- WIP: Custom HTTP header for downloading PDF files (#132)
-- Text search match color customization (#142)
-
-# 1.0.56
-
-- Reduce total number of Isolates used when opening PDF documents
-- Add PdfViewerParams.calculateCurrentPageNumber
-- FIXED: Could not handle certain destination coordinates correctly (#135)
-
-# 1.0.55
-
-- Improve memory consumption by opening/closing page handle every time pdfrx need it (PR #125)
-
-# 1.0.54
-
-- Improves [End] button behavior to reach the actual end of document rather than the top of the last page
- - PdfViewerParams.pageAnchorEnd for specifying anchor for the "virtual" page next to the last page
-- PdfViewerParams.onePassRenderingScaleThreshold to specify maximum scale that is rendered in single rendering call
- - If a page is scaled over the threshold scale, the page is once rendered in the threshold scale and after a some delay, the real scaled image is rendered partially that fits in the view port
-- PdfViewerParams.perPageSelectionAreaInjector is introduced to customize text selection behavior
-
-# 1.0.53
-
-- Fixes flicker on scrolling/zooming that was introduced on 1.0.52
-- Revival of high resolution partial rendering
-
-# 1.0.52
-
-- Fixes memory consumption control issues (Related: #121)
-
-# 1.0.51
-
-- FIXED: memory leak on \_PdfPageViewState (#110)
-- Remove dependency on dart:js_util (#109)
-- FIXED: Crash on \_PdfViewerScrollThumbState (#86)
-
-# 1.0.50
-
-- Introduces PdfViewerParams.useAlternativeFitScaleAsMinScale but it's not recommended to set the value to false because it may degrade the viewer performance
-
-# 1.0.49
-
-- iOS minimum deployment target 12.0
-
-# 1.0.11
-
-- intl 0.18.1 (#87)
-
-# 1.0.10+1
-
-- Add note for Flutter 3.19/Dart 3.3 support on 1.0.0+
-
-# 1.0.10
-
-- FIXED: calcZoomStopTable hangs app if zoom ratio is almost 0 (#79)
-
-# 1.0.9
-
-- PdfRect.toRect: scaledTo -> scaledPageSize
-- FIXED: PdfJsConfiguration.cMapUrl/cMapPacked does not have correct default values
-
-# 1.0.8
-
-- Condition analysis warnings on auto-generated pdfium_bindings.dart
-
-# 1.0.7
-
-- Requires Flutter 3.19/Dart 3.3 again (pub.dev is upgraded to the stable🎉)
-- dart:js_interop based pdf.js interop implementation (remove dependency on package:js)
-
-# 1.0.6
-
-- Due to the pub.dev version issues, the version introduces a "temporary workaround", which downgrades several packages:
- - `sdk: '>=3.3.0-76.0.dev <4.0.0'`
- - `flutter: '>=3.19.0-0.4.pre'`
- - `web: ^0.4.2`
- I'll update them as soon as [pub.dev upgrades their toolchains](https://github.com/dart-lang/pub-dev/issues/7484#issuecomment-1948206197)
-- pdf.js interop refactoring
-
-# 1.0.5
-
-_NOTE: On pub.dev, 1.0.0+ versions gets [[ANALYSIS ISSUE]](https://pub.dev/packages/pdfrx/versions/1.0.5-testing-version-constraints-1/score). It does not affect your code consistency but API reference is not available until [pub.dev upgrades their toolchains](https://github.com/dart-lang/pub-dev/issues/7484#issuecomment-1948206197)._
-
-- Requires Flutter 3.19/Dart 3.3
-
-# 1.0.4
-
-- Rollback version constraints to the older stable versions...
- - I've created an issue for pub.dev:
-
-# 1.0.3
-
-- Again, `flutter: '>=3.19.0-0.4.pre'`
-
-# 1.0.2
-
-- To make the pub.dev analyzer work, we should use `sdk: '>=3.3.0-76.0.dev <4.0.0'` as version constraint...
-
-# 1.0.1
-
-- PdfViewerController.addListener/removeListener independently has listener list on it to make it work regardless of PdfViewer attached or not (#74)
-
-# 1.0.0
-
-- Requires Flutter 3.19/Dart 3.3
-- Update Web code to use package:web (removing dependency to dart:html)
-
-# 0.4.44
-
-- FIXED: PdfViewerParams.boundaryMargin does not work correctly.
-
-# 0.4.43
-
-- Add note for dark/night mode support on README.md; the trick is originally introduced by [pckimlong](https://github.com/pckimlong) on #46.
-- FIXED: wrong PdfPageAnchor behavior with landscape pages
-
-# 0.4.42
-
-- FIXED: PdfDocumentRefData's operator== is broken (#66)
-
-# 0.4.41
-
-- Marker example for PdfViewerParams.onTextSelectionChange (#65)
-- Add more explanation for sourceName (#66)
-
-# 0.4.40
-
-- Introduces PdfViewerParams.onTextSelectionChange (#65) to know the last text selection
-
-# 0.4.39
-
-- Minor updates on text selection (still experimental......)
-
-# 0.4.38
-
-- Minor updates on text selection (still experimental...)
-- Minor fix on PdfPageView
-
-# 0.4.37
-
-- CMake version "3.18.1+" for #48, #62
-
-# 0.4.36
-
-- Introduces PdfJsConfiguration to configure pdf.js download URLs
-
-# 0.4.35
-
-- Download cache mechanism update (#57/#58)
-
-# 0.4.34
-
-- Document update
-
-# 0.4.33
-
-- Document update
-
-# 0.4.32
-
-- Add PdfViewerParams.calculateInitialPageNumber to calculate the initial page number dynamically
-- Add PdfViewerParams.onViewerReady to know when the viewer gets ready
-
-# 0.4.31
-
-- Remove explicit CMake version spec 3.18.1
-
-# 0.4.30
-
-- FIXED: Link URI contains null-terminator
-- Add support text/links on rotated pages
-- Stability updates for PdfTextSearcher
-- README.md/example updates
-- Revival of PdfViewer.data/PdfViewer.custom
-
-# 0.4.29
-
-- Minor fixes to PdfTextSearcher
-
-# 0.4.28
-
-- README.md/example updates
-
-# 0.4.27
-
-- Minor updates and README.md updates
-
-# 0.4.26
-
-- Introduces PdfTextSearcher that helps you to implement search UI feature (#47)
-- Example code is vastly changed to explain more about the widget functions
-
-# 0.4.25
-
-- FIXED: Able to scroll outside document area
-
-# 0.4.24
-
-- Huge refactoring on PdfViewerController; it's no longer TransformationController but just a `ValueListenable`
- - This fixes an "Unhandled Exception: Null check operator used on a null value" on widget state disposal (#46)
-
-# 0.4.23
-
-- Introduces PdfDocumentViewBuilder/PdfPageView widgets
-- Example code is super updated with index and thumbnails.
-
-# 0.4.22
-
-- Web: Now pdf.js is loaded automatically and no modification to index.html is required!
-- Default implementation for PdfViewerParams.errorBannerBuilder to show internally thrown errors
-- PdfPasswordException is introduced to notify password error
-- PdfDocumentRef now has stackTrace for error
-- PdfFileCache now uses dedicated http.Client instance
-
-# 0.4.21
-
-- Now PdfDocumentRef has const constructor and PdfViewer.documentRef is also const
-
-# 0.4.20
-
-- Removes PdfDocumentProvider (Actually PdfDocumentRef does everything)
-- Fixes breakage introduced by 0.4.18
-
-# 0.4.19
-
-- firstAttemptByEmptyPassword should be true by default
-
-# 0.4.18
-
-- PdfDocumentProvider supercedes PdfDocumentStore (PR #42)
-- PDFium 6259 for Windows, Linux, and Android
-- FIXED: Bug: Tests fail due to null operator check on PdfViewerController #44
-
-# 0.4.17
-
-- Additional fixes to text selection mechanism
-
-# 0.4.16
-
-- Remove password parameters; use passwordProvider instead.
-- Fixes several resource leak scenarios on PdfDocument open failures
-- Restrict text selection if PDF permission does not allow copying
-- Remove PdfViewer.documentRef; unnamed constructor is enough for the purpose
-
-# 0.4.15
-
-- Introduces PdfViewer.documentRef (#36)
-- FIXED: PdfViewer.uri is broken on web for non relative paths #37
-- FIXED: Don't Animate to initialPage #39
-
-# 0.4.14
-
-- Introduces PdfViewerParams.onDocumentChanged event
-- Introduces PdfDocument.loadOutline to load outline (a.k.a. bookmark)
-
-# 0.4.13
-
-- Improves document password handling by async PasswordProvider (#20)
-- Introduces PdfViewerParams.errorBannerBuilder
-
-# 0.4.12
-
-- Introduces PdfViewerParams.maxImageBytesCachedOnMemory, which restricts the maximum cache memory consumption
- - Better than logic based on maxThumbCacheCount
-- Remove the following parameters from PdfViewerParams:
- - maxThumbCacheCount
- - maxRealSizeImageCount
- - enableRealSizeRendering
-
-# 0.4.11
-
-- Add support for PDF Destination (Page links)
-
-# 0.4.10
-
-- FIXED: isEncrypted property of document returns always true even the document is not encrypted (#29)
-
-# 0.4.9
-
-- FIXED: SelectionArea makes Web version almost unusable (#31)
-
-# 0.4.8
-
-- FIXED: Unhandled Exception: type 'Null' is not a subtype of type 'PdfPageRenderCancellationTokenPdfium' in type cast (#26)
-
-# 0.4.7
-
-- FIXED: Android build broken? Cannot find libpdfium.so error (#25)
-- PdfViewerParams.loadingBannerBuilder to customize HTTP download progress
-- PdfViewerParams.linkWidgetBuilder to support embedded links
-- WIP: Updated text selection mechanism, which is faster and stable but still certain issues
- - Pan-to-scroll does not work on Desktop/Web
- - Selection does not work as expected on mobile devices
-- Support Linux running on arm64 Raspberry PI (#23/#24)
-
-# 0.4.6
-
-- Introduces PdfPage.render cancellation mechanism
- - PdfPageRenderCancellationToken to cancel the rendering process
- - BREAKING CHANGE: PdfPage.render may return null if the rendering process is canceled
-- PdfPageRender.render limits render resolution up to 300-dpi unless you use getPageRenderingScale
- - Even with the restriction, image size may get large and you'd better implement getPageRenderingScale to restrict such large image rendering
-- PdfViewerParams default changes:
- - scrollByMouseWheel default is 0.2
- - maxRealSizeImageCount default is 3
-- PdfViewerParams.scrollByArrowKey to enable keyboard navigation
-
-# 0.4.5
-
-- PdfViewerParams updates
- - PdfViewerParams.onPageChanged replaces onPageChanged parameter on PdfViewer factories
- - PdfViewerParams.pageAnchor replaces anchor parameter on PdfViewer factories
-- pdfDocumentFromUri/PdfFileCache improves mechanism to cache downloaded PDF file
- - ETag check to invalidate the existing cache
- - Better downloaded region handling
-
-# 0.4.4
-
-- PdfPage.render can render Annotations and FORMS
-- PdfFileCache: More realistic file cache mechanism
-- Introduces PasswordProvider to repeatedly test passwords (only API layer)
-
-# 0.4.3
-
-- FIXED: cache mechanism is apparently broken (#12)
-
-# 0.4.2
-
-- PdfViewerParams.pageOverlayBuilder to customize PDF page (#17)
-- Updating README.md
-
-# 0.4.1
-
-- Add PdfViewerParams.enableRenderAnnotations to enable annotations on rendering (#18,#19)
-
-# 0.4.0
-
-- Many breaking changes but they improve the code integrity:
- - PdfDocument.pages supersedes PdfDocument.getPage
- - PdfDocument.pageCount is removed
- - PdfViewerParams.devicePixelRatioOverride is removed; use getPageRenderingScale instead
-- Add PdfPageAnchor.all
-- PdfViewerParams.viewerOverlayBuilder/PdfViewerScrollThumb to support scroll thumbs
-
-# 0.3.6
-
-- PageLayout -> PdfPageLayout
-
-# 0.3.5
-
-- PageLayout class change to ease page layout customization
- - Add example use case in API document
-
-# 0.3.4
-
-- Rewriting page rendering code
- - Due to the internal structure change, page drawing customization parameters are once removed:
- - pageDecoration
- - pageOverlaysBuilder
-- Example code does not enables enableTextSelection; it's still too experimental...
-
-# 0.3.3
-
-- FIXED: Downloading of small PDF file causes internal loading error
-
-# 0.3.2
-
-- Support mouse-wheel-to-scroll on Desktop platforms
-
-# 0.3.1
-
-- Minor API changes
-- Internal integrity updates that controls the viewer behaviors
-- FIX: example code does not have android.permission.INTERNET on AndroidManifest.xml
-- PdfViewerParams.devicePixelRatioOverride is deprecated and introduces PdfViewerParams.getPageRenderingScale
-
-# 0.3.0
-
-- Many renaming of the APIs that potentially breaks existing apps
-
-# 0.2.4
-
-- Now uses plugin_ffi. (Not containing any Flutter plugin stab)
-
-# 0.2.3
-
-- Fixed: #6 PdfPageWeb.render behavior is different from PdfPagePdfium.render
-
-# 0.2.2
-
-- Explicitly specify Flutter 3.16/Dart 3.2 as NativeCallable.listener does not accept non-static function (#5)
-
-# 0.2.1
-
-- Stabilizing API surface
- - Introducing PdfViewer.asset/file/uri/custom
- - PdfViewer has documentLoader to accept function to load PdfDocument
-- Fixes minor issues on PdfViewer
-
-# 0.2.0
-
-- Introducing PdfDocument.openUri/PdfFileCache\* classes
-- Introducing PdfPermissions
-- PdfPage.loadText/PdfPageText for text extraction
-- Android NDK CMake to 3.18.1
-
-# 0.1.1
-
-- Document updates
-- pdf.js 3.11.174
-
-# 0.1.0
-
-- First release (Documentation is not yet ready)
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..b0c347bb
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,3 @@
+# NOTE for Claude
+
+Read and follow the instructions in AGENTS.md.
diff --git a/README.md b/README.md
index d4a6b56d..72c08900 100644
--- a/README.md
+++ b/README.md
@@ -1,207 +1,136 @@
# pdfrx
-[pdfrx](https://pub.dartlang.org/packages/pdfrx) is a rich and fast PDF viewer implementation built on the top of [PDFium](https://pdfium.googlesource.com/pdfium/).
-The plugin supports Android, iOS, Windows, macOS, Linux, and Web.
+This repository contains multiple Dart/Flutter packages for PDF rendering, viewing, and manipulation:
-## Interactive Demo
+## Packages
-A [demo site](https://espresso3389.github.io/pdfrx/) using Flutter Web
+### [pdfrx_engine](https://pub.dev/packages/pdfrx_engine)
-
+A platform-agnostic PDF rendering and manipulation API built on top of [PDFium](https://pdfium.googlesource.com/pdfium/).
-## Multi-platform support
+- Pure Dart package (no Flutter dependencies)
+- Provides low-level PDF document API for viewing and editing
+- Supports page re-arrangement, PDF combining, image import, and document manipulation
+- Can be used in CLI applications or non-Flutter Dart projects
+- Supports all platforms: Android, iOS, Windows, macOS, Linux
-- Android
-- iOS
-- Windows
-- macOS
-- Linux (even on Raspberry PI)
-- Web (\*using [PDF.js](https://mozilla.github.io/pdf.js/)) or Pdfium WASM (\*experimental)
+[View repo](packages/pdfrx_engine/) | [API reference](https://pub.dev/documentation/pdfrx_engine/latest/)
-## Example Code
+### [pdfrx](https://pub.dev/packages/pdfrx)
-The following fragment illustrates the easiest way to show a PDF file in assets:
+A cross-platform PDF viewer and manipulation plugin for Flutter.
-```dart
-import 'package:pdfrx/pdfrx.dart';
+- Flutter plugin with UI widgets
+- Built on top of [pdfrx_engine](https://pub.dev/packages/pdfrx_engine)
+- Provides high-level viewer widgets and overlays
+- Includes text selection, search, zoom controls, and more
+- Supports PDF editing features like page manipulation, document combining, and image import
-...
+[View repo](packages/pdfrx/) | [API reference](https://pub.dev/documentation/pdfrx/latest/)
-class _MyAppState extends State {
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- home: Scaffold(
- appBar: AppBar(
- title: const Text('Pdfrx example'),
- ),
- body: PdfViewer.asset('assets/hello.pdf'),
- ),
- );
- }
-}
-```
+### [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics)
-Anyway, please follow the instructions below to install on your environment.
+**⚠️ EXPERIMENTAL** - CoreGraphics-backed renderer for [pdfrx](https://pub.dev/packages/pdfrx) on iOS/macOS.
-## Getting Started
+- Uses PDFKit/CoreGraphics instead of [PDFium](https://pdfium.googlesource.com/pdfium/) on Apple platforms
+- Drop-in replacement for teams preferring the system PDF stack
+- Maintains full compatibility with [pdfrx](https://pub.dev/packages/pdfrx) widget API
+- iOS and macOS only
-### Installation
+[View repo](packages/pdfrx_coregraphics/) | [API reference](https://pub.dev/documentation/pdfrx_coregraphics/latest/)
-Add this to your package's `pubspec.yaml` file and execute `flutter pub get`:
+### [pdfium_dart](https://pub.dev/packages/pdfium_dart)
-```yaml
-dependencies:
- pdfrx: ^1.1.11
-```
+Low-level Dart FFI bindings for the [PDFium](https://pdfium.googlesource.com/pdfium/) library.
-### Note for Windows
+- Pure Dart package with auto-generated FFI bindings using [ffigen](https://pub.dev/packages/ffigen)
+- Provides direct access to PDFium's C API from Dart
+- Includes [getPdfium()](https://pub.dev/documentation/pdfium_dart/latest/pdfium_dart/getPdfium.html) function that downloads PDFium binaries on demand
+- Used as a foundation by higher-level packages
-**Ensure your Windows installation enables [Developer Mode](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode).**
+[View repo](packages/pdfium_dart/) | [API reference](https://pub.dev/documentation/pdfium_dart/latest/)
-The build process internally uses *symbolic links* and it requires Developer Mode to be enabled.
-Without this, you may encounter errors [like this](https://github.com/espresso3389/pdfrx/issues/34).
+### [pdfium_flutter](https://pub.dev/packages/pdfium_flutter)
-### "Bleeding Edge" Pdfium WASM support on Web
+Flutter FFI plugin for loading [PDFium](https://pdfium.googlesource.com/pdfium/) native libraries.
-pdfrx now supports "bleeding edge" Pdfium WASM support on Web.
-This is still not production-ready, but you can try it by adding additional [pdfrx_wasm](https://pub.dartlang.org/packages/pdfrx_wasm) to your dependencies:
+- Bundles pre-built PDFium binaries for all Flutter platforms (Android, iOS, Windows, macOS, Linux)
+- Provides utilities for loading PDFium at runtime
+- Re-exports [pdfium_dart](https://pub.dev/packages/pdfium_dart) FFI bindings
+- Simplifies PDFium integration in Flutter applications
+
+[View repo](packages/pdfium_flutter/) | [API reference](https://pub.dev/documentation/pdfium_flutter/latest/)
+
+## When to Use Which Package
+
+- **Use [pdfrx](https://pub.dev/packages/pdfrx)** if you're building a Flutter application and need PDF viewing and manipulation capabilities with UI
+- **Use [pdfrx_engine](https://pub.dev/packages/pdfrx_engine)** if you need PDF rendering and manipulation without Flutter dependencies (e.g., server-side PDF processing, CLI tools, PDF combining utilities)
+- **Use [pdfrx_coregraphics](https://pub.dev/packages/pdfrx_coregraphics)** (experimental) if you want to use CoreGraphics/PDFKit instead of [PDFium](https://pdfium.googlesource.com/pdfium/) on iOS/macOS
+- **Use [pdfium_dart](https://pub.dev/packages/pdfium_dart)** if you need low-level PDFium FFI bindings for Dart projects or want on-demand PDFium binary downloads
+- **Use [pdfium_flutter](https://pub.dev/packages/pdfium_flutter)** if you're building a Flutter plugin that needs PDFium integration with bundled binaries
+
+## Getting Started
+
+### For Flutter Applications
+
+Add [pdfrx](https://pub.dev/packages/pdfrx) to your `pubspec.yaml`:
```yaml
dependencies:
- pdfrx: ^1.1.11
- pdfrx_wasm: ^1.1.6
+ pdfrx: ^2.2.15
```
-And then, enable Pdfium WASM by adding the following line to somewhere that runs before calling any pdfrx functions (typically `main` function):
+### For Pure Dart Applications
-```dart
-Pdfrx.webRuntimeType = PdfrxWebRuntimeType.pdfiumWasm;
-```
+Add [pdfrx_engine](https://pub.dev/packages/pdfrx_engine) to your `pubspec.yaml`:
-The plugin, `pdfrx_wasm`, is a satellite plugin for `pdfrx` that contains files required to run Pdfium WASM. Because the WASM binary/support files are relatively large (about 4MB), it is not included in the main `pdfrx` package and you need to add `pdfrx_wasm` to your dependencies.
+```yaml
+dependencies:
+ pdfrx_engine: ^0.3.0
+```
-## Open PDF File
+## Documentation
-[PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) supports following functions to open PDF file on specific medium:
+Comprehensive documentation is available in the [doc/](doc/) directory, including:
-- [PdfViewer.asset](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.asset.html)
- - Open PDF of Flutter's asset
-- [PdfViewer.file](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.file.html)
- - Open PDF from file
- - macOS: may be blocked by [App Sandbox](https://github.com/espresso3389/pdfrx/wiki/macOS:-Deal-with-App-Sandbox)
-- [PdfViewer.uri](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.uri.html)
- - Open PDF from URI (`https://...` or relative path)
- - Flutter Web: may be blocked by [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
- - macOS: may be blocked by [App Sandbox](https://github.com/espresso3389/pdfrx/wiki/macOS:-Deal-with-App-Sandbox)
+- Getting started guides
+- Feature tutorials
+- Platform-specific configurations
+- Code examples
-### Deal with Password Protected PDF Files
+## Development
-```dart
-PdfViewer.asset(
- 'assets/test.pdf',
- // Most easiest way to return some password
- passwordProvider: () => 'password',
+This is a monorepo managed with pub workspaces. Just do `dart pub get` on some directory inside the repo to obtain all the dependencies.
- ...
-),
-```
+## Example Applications
-For more customization and considerations, see [Deal with Password Protected PDF Files using PasswordProvider](https://github.com/espresso3389/pdfrx/wiki/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider).
+### PDF Viewer
-## Customizations/Features
+The example viewer application is located in [packages/pdfrx/example/viewer/](packages/pdfrx/example/viewer/). It demonstrates the full capabilities of the [pdfrx](https://pub.dev/packages/pdfrx) Flutter plugin.
-You can customize the behaviors and the viewer look and feel by configuring [PdfViewerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams-class.html).
+```bash
+cd packages/pdfrx/example/viewer
+flutter run
+```
-### Text Selection
+### PDF Combine
-The following fragment enables text selection feature:
+The [packages/pdfrx/example/pdf_combine/](packages/pdfrx/example/pdf_combine/) application demonstrates PDF page manipulation and combining features:
-```dart
-PdfViewer.asset(
- 'assets/test.pdf',
- params: PdfViewerParams(
- enableTextSelection: true,
- ...
- ),
- ...
-),
-```
+- Drag-and-drop interface for page re-arrangement
+- Visual thumbnails of PDF pages
+- Support for combining multiple PDF documents
+- Platform file drag-and-drop support
-For more text selection customization, see [Text Selection](https://github.com/espresso3389/pdfrx/wiki/Text-Selection).
-
-### PDF Feature Support
-
-- [PDF Link Handling](https://github.com/espresso3389/pdfrx/wiki/PDF-Link-Handling)
-- [Document Outline (a.k.a Bookmarks)](https://github.com/espresso3389/pdfrx/wiki/Document-Outline-(a.k.a-Bookmarks))
-- [Text Search](https://github.com/espresso3389/pdfrx/wiki/Text-Search)
-
-### Viewer Customization
-
-- [Page Layout (Horizontal Scroll View/Facing Pages)](https://github.com/espresso3389/pdfrx/wiki/Page-Layout-Customization)
-- [Showing Scroll Thumbs](https://github.com/espresso3389/pdfrx/wiki/Showing-Scroll-Thumbs)
-- [Dark/Night Mode Support](https://github.com/espresso3389/pdfrx/wiki/Dark-Night-Mode-Support)
-- [Document Loading Indicator](https://github.com/espresso3389/pdfrx/wiki/Document-Loading-Indicator)
-- [Viewer Customization using Widget Overlay](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html)
-
-### Additional Customizations
-
-- [Double‐tap to Zoom](https://github.com/espresso3389/pdfrx/wiki/Double%E2%80%90tap-to-Zoom)
-- [Adding Page Number on Page Bottom](https://github.com/espresso3389/pdfrx/wiki/Adding-Page-Number-on-Page-Bottom)
-- [Per-page Customization using Widget Overlay](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pageOverlaysBuilder.html)
-- [Per-page Customization using Canvas](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pagePaintCallbacks.html)
-
-## Additional Widgets
-
-### PdfDocumentViewBuilder/PdfPageView
-
-[PdfPageView](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageView-class.html) is just another PDF widget that shows only one page. It accepts [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) and page number to show a page within the document.
-
-[PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html) is used to safely manage [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) inside widget tree and it accepts `builder` parameter that creates child widgets.
-
-The following fragment is a typical use of these widgets:
-
-```dart
-PdfDocumentViewBuilder.asset(
- 'asset/test.pdf',
- builder: (context, document) => ListView.builder(
- itemCount: document?.pages.length ?? 0,
- itemBuilder: (context, index) {
- return Container(
- margin: const EdgeInsets.all(8),
- height: 240,
- child: Column(
- children: [
- SizedBox(
- height: 220,
- child: PdfPageView(
- document: document,
- pageNumber: index + 1,
- alignment: Alignment.center,
- ),
- ),
- Text(
- '${index + 1}',
- ),
- ],
- ),
- );
- },
- ),
-),
+```bash
+cd packages/pdfrx/example/pdf_combine
+flutter run
```
-## PdfDocument Management
+## Contributing
-[PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html) can accept [PdfDocumentRef](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentRef-class.html) from [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) to safely share the same [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html) instance. For more information, see [example/viewer/lib/thumbnails_view.dart](example/viewer/lib/thumbnails_view.dart).
+Contributions are welcome! Please read the individual package READMEs for specific development guidelines.
-## Low Level PDF API
+## License
-- Easy to use Flutter widgets
- - [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html)
- - [PdfDocumentViewBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocumentViewBuilder-class.html)
- - [PdfPageView](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageView-class.html)
-- Easy to use PDF APIs
- - [PdfDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument-class.html)
-- PDFium bindings
- - Not encouraged but you can import [package:pdfrx/src/pdfium/pdfium_bindings.dart](https://github.com/espresso3389/pdfrx/blob/master/lib/src/pdfium/pdfium_bindings.dart)
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
diff --git a/android/settings.gradle b/android/settings.gradle
deleted file mode 100644
index bccb4d82..00000000
--- a/android/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-rootProject.name = 'pdfrx'
diff --git a/darwin/Classes/pdfrx.cpp b/darwin/Classes/pdfrx.cpp
deleted file mode 100644
index 2e0ee1fb..00000000
--- a/darwin/Classes/pdfrx.cpp
+++ /dev/null
@@ -1,3 +0,0 @@
-// Relative import to be able to reuse the C sources.
-// See the comment in ../{projectName}}.podspec for more information.
-#include "../../src/pdfium_interop.cpp"
diff --git a/darwin/pdfium/.gitignore b/darwin/pdfium/.gitignore
deleted file mode 100644
index 58c18afd..00000000
--- a/darwin/pdfium/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-.lib/
-.tmp/
-ios/
-macos/
-*.tgz
diff --git a/darwin/pdfium/build b/darwin/pdfium/build
deleted file mode 100755
index 1aa3940d..00000000
--- a/darwin/pdfium/build
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/zsh -e
-
-SCRIPT_DIR=$(cd $(dirname $0) && pwd)
-cd $SCRIPT_DIR
-
-# for iOS/iPhoneSimulator
-if [[ ! -d ios/pdfium.xcframework ]]; then
- ./build-config.sh ios arm64
- ./build-config.sh iossim arm64
- ./build-config.sh iossim x64
-
- mkdir -p .tmp/out/lib/iossim-release
- lipo -create .tmp/out/lib/iossim-arm64-release/libpdfium.a .tmp/out/lib/iossim-x64-release/libpdfium.a -output .tmp/out/lib/iossim-release/libpdfium.a
-
- mkdir -p ios/
- xcodebuild -create-xcframework -library .tmp/out/lib/ios-arm64-release/libpdfium.a -headers .tmp/out/include -library .tmp/out/lib/iossim-release/libpdfium.a -headers .tmp/out/include -output ios/pdfium.xcframework
-
- tar -czvf pdfium-ios.tgz ios
-fi
-
-# for macOS
-if [[ ! -d macos/pdfium.xcframework ]]; then
- ./build-config.sh macos arm64
- ./build-config.sh macos x64
-
- mkdir -p .tmp/out/lib/macos-release
- lipo -create .tmp/out/lib/macos-arm64-release/libpdfium.a .tmp/out/lib/macos-x64-release/libpdfium.a -output .tmp/out/lib/macos-release/libpdfium.a
-
- mkdir -p macos/
- rm -rf macos/pdfium.xcframework
- xcodebuild -create-xcframework -library .tmp/out/lib/macos-release/libpdfium.a -headers .tmp/out/include -output macos/pdfium.xcframework
-
- tar -czvf pdfium-macos.tgz macos
-fi
diff --git a/darwin/pdfium/build-config.sh b/darwin/pdfium/build-config.sh
deleted file mode 100755
index df1f4016..00000000
--- a/darwin/pdfium/build-config.sh
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/bin/zsh -e
-
-if [ "$2" = "" ]; then
- echo "Usage: $0 linux|android|macos|ios|iossim x86|x64|arm|arm64"
- exit 1
-fi
-
-# https://pdfium.googlesource.com/pdfium/+/refs/heads/chromium/6555
-LAST_KNOWN_GOOD_COMMIT=5a6a8741b0e111a6b5bd9ab4e1036377c98885dc
-
-SCRIPT_DIR=$(cd $(dirname $0) && pwd)
-
-# linux, android, macos, ios
-TARGET_OS_ORIG=$1
-# x64, x86, arm64, ...
-TARGET_ARCH=$2
-# static or dll
-STATIC_OR_DLL=static
-# release or debug
-REL_OR_DBG=release
-
-
-if [[ "$TARGET_OS_ORIG" == "iossim" ]]; then
- TARGET_OS=ios
- TARGET_ENVIRONMENT=simulator
-elif [[ "$TARGET_OS_ORIG" == "macos" ]]; then
- TARGET_OS=mac
-else
- TARGET_OS=$TARGET_OS_ORIG
- # only for ios; simulator or device
- TARGET_ENVIRONMENT=device
-fi
-
-
-WORK_ROOT_DIR=$SCRIPT_DIR/.tmp
-DIST_DIR=$WORK_ROOT_DIR/out
-
-DEPOT_DIR=$WORK_ROOT_DIR/depot_tools
-WORK_DIR=$WORK_ROOT_DIR/work
-
-mkdir -p $WORK_ROOT_DIR $WORK_DIR $DIST_DIR
-
-if [[ ! -d $DEPOT_DIR ]]; then
- pushd $WORK_ROOT_DIR
- git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
- popd
-fi
-
-export PATH=$DEPOT_DIR:$PATH
-
-IS_SHAREDLIB=false
-
-if [[ "$REL_OR_DBG" = "release" ]]; then
- IS_DEBUG=false
- DEBUG_DIR_SUFFIX=
-else
- IS_DEBUG=true
- DEBUG_DIR_SUFFIX=/debug
-fi
-
-if [[ "$TARGET_OS" == "macos" || "$TARGET_OS" == "ios" || "$TARGET_OS" == "android" ]]; then
- IS_CLANG=true
-else
- IS_CLANG=false
-fi
-
-cd $WORK_DIR
-if [[ ! -e pdfium/.git/index ]]; then
- fetch pdfium
-fi
-
-PDFIUM_SRCDIR=$WORK_DIR/pdfium
-BUILDDIR=$PDFIUM_SRCDIR/out/$TARGET_OS_ORIG-$TARGET_ARCH-$REL_OR_DBG
-mkdir -p $BUILDDIR
-
-if [[ "$LAST_KNOWN_GOOD_COMMIT" != "" ]]; then
- pushd $PDFIUM_SRCDIR
- git reset --hard
- git checkout $LAST_KNOWN_GOOD_COMMIT
- cd $PDFIUM_SRCDIR/build
- git reset --hard
- cd $PDFIUM_SRCDIR/third_party/libjpeg_turbo
- git reset --hard
- popd
-fi
-
-INCLUDE_DIR=$DIST_DIR/include
-if [[ ! -d $INCLUDE_DIR ]]; then
- mkdir -p $INCLUDE_DIR
- cp -r $PDFIUM_SRCDIR/public/* $INCLUDE_DIR
-fi
-
-if [[ "$TARGET_OS" == "ios" ]]; then
- # (cd $PDFIUM_SRCDIR && git diff > ../../../patches/ios/pdfium.patch)
- pushd $PDFIUM_SRCDIR
- git apply --reject --whitespace=fix $SCRIPT_DIR/patches/ios/pdfium.patch
- popd
- # (cd $PDFIUM_SRCDIR/third_party/libjpeg_turbo && git diff > ../../../../../patches/ios/libjpeg_turbo.patch)
- pushd $PDFIUM_SRCDIR/third_party/libjpeg_turbo/
- git apply --reject --whitespace=fix $SCRIPT_DIR/patches/ios/libjpeg_turbo.patch
- popd
-fi
-
-if [[ "$TARGET_OS" == "mac" ]]; then
- # (cd $PDFIUM_SRCDIR && git diff > ../../../patches/macos/pdfium.patch)
- pushd $PDFIUM_SRCDIR
- git apply --reject --whitespace=fix $SCRIPT_DIR/patches/macos/pdfium.patch
- popd
- # (cd $PDFIUM_SRCDIR/build && git diff > ../../../../patches/macos/build-config.patch)
- pushd $PDFIUM_SRCDIR/build
- git apply --reject --whitespace=fix $SCRIPT_DIR/patches/macos/build-config.patch
- popd
-fi
-
-cat < $BUILDDIR/args.gn
-is_clang = $IS_CLANG
-target_os = "$TARGET_OS"
-target_cpu = "$TARGET_ARCH"
-pdf_is_complete_lib = true
-pdf_is_standalone = true
-is_component_build = $IS_SHAREDLIB
-is_debug = $IS_DEBUG
-enable_iterator_debugging = $IS_DEBUG
-pdf_enable_xfa = false
-pdf_enable_v8 = false
-EOF
-
-if [[ "$TARGET_OS" == "ios" ]]; then
- # See ios/pdfium/.tmp/work/pdfium/build/config/ios/rules.gni
- cat <> $BUILDDIR/args.gn
-ios_enable_code_signing = false
-ios_deployment_target = "12.0"
-use_custom_libcxx = false
-pdf_use_partition_alloc = false
-target_environment = "$TARGET_ENVIRONMENT"
-EOF
-fi
-
-if [[ "$TARGET_OS" == "mac" ]]; then
- cat <> $BUILDDIR/args.gn
-use_custom_libcxx = false
-pdf_use_partition_alloc = false
-EOF
-fi
-
-pushd $BUILDDIR
-gn gen .
-popd
-
-ninja -C $BUILDDIR pdfium
-
-LIB_DIR=$DIST_DIR/lib/$TARGET_OS_ORIG-$TARGET_ARCH-$REL_OR_DBG
-rm -rf $LIB_DIR
-mkdir -p $LIB_DIR
-cp $BUILDDIR/obj/libpdfium.a $LIB_DIR
-
-cd $SCRIPT_DIR
diff --git a/darwin/pdfium/patches/ios/libjpeg_turbo.patch b/darwin/pdfium/patches/ios/libjpeg_turbo.patch
deleted file mode 100644
index 5f633124..00000000
--- a/darwin/pdfium/patches/ios/libjpeg_turbo.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/BUILD.gn b/BUILD.gn
-index b39d278..596ea7a 100644
---- a/BUILD.gn
-+++ b/BUILD.gn
-@@ -12,7 +12,7 @@ if (current_cpu == "arm" || current_cpu == "arm64") {
- }
-
- assert(
-- use_blink,
-+ true,
- "This is not used if blink is not enabled, don't drag it in unintentionally")
-
- source_set("libjpeg_headers") {
diff --git a/darwin/pdfium/patches/ios/pdfium.patch b/darwin/pdfium/patches/ios/pdfium.patch
deleted file mode 100644
index 0071669a..00000000
--- a/darwin/pdfium/patches/ios/pdfium.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-diff --git a/core/fxge/BUILD.gn b/core/fxge/BUILD.gn
-index 215a63b14..357d96735 100644
---- a/core/fxge/BUILD.gn
-+++ b/core/fxge/BUILD.gn
-@@ -164,7 +164,7 @@ source_set("fxge") {
- sources += [ "linux/fx_linux_impl.cpp" ]
- }
-
-- if (is_mac) {
-+ if (is_mac || is_ios) {
- sources += [
- "apple/fx_apple_impl.cpp",
- "apple/fx_apple_platform.cpp",
-diff --git a/public/fpdfview.h b/public/fpdfview.h
-index b374088b4..b1c896e26 100644
---- a/public/fpdfview.h
-+++ b/public/fpdfview.h
-@@ -214,7 +214,7 @@ typedef int FPDF_OBJECT_TYPE;
- #endif // defined(FPDF_IMPLEMENTATION)
- #else
- #if defined(FPDF_IMPLEMENTATION)
--#define FPDF_EXPORT __attribute__((visibility("default")))
-+#define FPDF_EXPORT __attribute__((visibility("default"))) __attribute__((used))
- #else
- #define FPDF_EXPORT
- #endif // defined(FPDF_IMPLEMENTATION)
-diff --git a/testing/test.gni b/testing/test.gni
-index 6ad2c2d4a..bae5117d8 100644
---- a/testing/test.gni
-+++ b/testing/test.gni
-@@ -207,7 +207,7 @@ template("test") {
- }
-
- _bundle_id_suffix = target_name
-- if (ios_automatically_manage_certs) {
-+ if (true) {
- # Use the same bundle identifier for all unit tests when managing
- # certificates automatically as the number of free certs is limited.
- _bundle_id_suffix = "generic-unit-test"
diff --git a/darwin/pdfium/patches/macos/build-config.patch b/darwin/pdfium/patches/macos/build-config.patch
deleted file mode 100644
index 7cd856db..00000000
--- a/darwin/pdfium/patches/macos/build-config.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/config/mac/BUILD.gn b/config/mac/BUILD.gn
-index 85a668d33..54691a2e0 100644
---- a/config/mac/BUILD.gn
-+++ b/config/mac/BUILD.gn
-@@ -52,6 +52,8 @@ config("compiler") {
- if (export_libcxxabi_from_executables) {
- ldflags += [ "-Wl,-undefined,dynamic_lookup" ]
- }
-+
-+ cflags += [ "-Wno-unknown-warning-option" ]
- }
-
- # This is included by reference in the //build/config/compiler:runtime_library
diff --git a/darwin/pdfium/patches/macos/pdfium.patch b/darwin/pdfium/patches/macos/pdfium.patch
deleted file mode 100644
index ef254320..00000000
--- a/darwin/pdfium/patches/macos/pdfium.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/public/fpdfview.h b/public/fpdfview.h
-index b374088b4..b1c896e26 100644
---- a/public/fpdfview.h
-+++ b/public/fpdfview.h
-@@ -214,7 +214,7 @@ typedef int FPDF_OBJECT_TYPE;
- #endif // defined(FPDF_IMPLEMENTATION)
- #else
- #if defined(FPDF_IMPLEMENTATION)
--#define FPDF_EXPORT __attribute__((visibility("default")))
-+#define FPDF_EXPORT __attribute__((visibility("default"))) __attribute__((used))
- #else
- #define FPDF_EXPORT
- #endif // defined(FPDF_IMPLEMENTATION)
diff --git a/darwin/pdfrx.podspec b/darwin/pdfrx.podspec
deleted file mode 100644
index ba08aa0f..00000000
--- a/darwin/pdfrx.podspec
+++ /dev/null
@@ -1,54 +0,0 @@
-#
-# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
-# Run `pod lib lint pdfrx.podspec` to validate before publishing.
-#
-lib_tag = 'pdfium-apple-v9'
-
-Pod::Spec.new do |s|
- s.name = 'pdfrx'
- s.version = '0.0.3'
- s.summary = 'Yet another PDF renderer for Flutter using PDFium.'
- s.description = <<-DESC
- Yet another PDF renderer for Flutter using PDFium.
- DESC
- s.homepage = 'https://github.com/espresso3389/pdfrx'
- s.license = { :type => 'BSD', :file => '../LICENSE' }
- s.author = { 'Takashi Kawasaki' => 'espresso3389@gmail.com' }
- s.source = { :path => '.' }
- s.source_files = 'Classes/**/*'
-
- s.ios.deployment_target = '12.0'
- s.ios.dependency 'Flutter'
- s.ios.private_header_files = "pdfium/.lib/#{lib_tag}/ios/pdfium.xcframework/ios-arm64/Headers/*.h"
- s.ios.vendored_frameworks = "pdfium/.lib/#{lib_tag}/ios/pdfium.xcframework"
- # Flutter.framework does not contain a i386 slice.
- s.ios.pod_target_xcconfig = {
- 'DEFINES_MODULE' => 'YES',
- 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
- }
-
- s.osx.deployment_target = '10.11'
- s.osx.dependency 'FlutterMacOS'
- s.osx.private_header_files = "pdfium/.lib/#{lib_tag}/macos/pdfium.xcframework/macos-arm64_x86_64/Headers/*.h"
- s.osx.vendored_frameworks = "pdfium/.lib/#{lib_tag}/macos/pdfium.xcframework"
- s.osx.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
-
- s.prepare_command = <<-CMD
- mkdir -p pdfium/.lib/#{lib_tag}
- cd pdfium/.lib/#{lib_tag}
- if [ ! -f ios.tgz ]; then
- curl -Lo ios.tgz https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-ios.tgz
- fi
- if [ ! -d ios ]; then
- tar xzf ios.tgz
- fi
- if [ ! -f macos.tgz ]; then
- curl -Lo macos.tgz https://github.com/espresso3389/pdfrx/releases/download/#{lib_tag}/pdfium-macos.tgz
- fi
- if [ ! -d macos ]; then
- tar xzf macos.tgz
- fi
- CMD
-
- s.swift_version = '5.0'
-end
diff --git a/doc/Adding-Page-Number-on-Page-Bottom.md b/doc/Adding-Page-Number-on-Page-Bottom.md
new file mode 100644
index 00000000..12e1ecb7
--- /dev/null
+++ b/doc/Adding-Page-Number-on-Page-Bottom.md
@@ -0,0 +1,17 @@
+# Adding Page Numbers to Pages
+
+If you want to add page number on each page, you can do that by [PdfViewerParams.pageOverlaysBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pageOverlaysBuilder.html):
+
+```dart
+pageOverlaysBuilder: (context, pageRect, page) {
+ return [
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: Text(
+ page.pageNumber.toString(),
+ style: const TextStyle(color: Colors.red),
+ ),
+ ),
+ ];
+},
+```
diff --git a/doc/Coordinate-Conversion.md b/doc/Coordinate-Conversion.md
new file mode 100644
index 00000000..8877c722
--- /dev/null
+++ b/doc/Coordinate-Conversion.md
@@ -0,0 +1,384 @@
+# Coordinate Conversion
+
+This guide explains how to work with different coordinate systems in pdfrx and convert between them.
+
+## Understanding Coordinate Systems
+
+pdfrx uses four distinct coordinate systems:
+
+1. **Global Coordinates** - Screen/window coordinates (origin at top-left of the screen)
+2. **Local Coordinates** - Widget's local coordinate system (origin at top-left of the widget)
+3. **Document Coordinates** - PDF document layout coordinates (72 DPI, unzoomed, with pages laid out according to the layout mode)
+4. **Page Coordinates** - Individual PDF page coordinates (origin at bottom-left corner, following PDF standard)
+
+### Coordinate System Diagram
+
+```
+Global (Screen) Local (Widget) Document (Layout) Page (PDF)
+┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
+│ (0,0) │ │ (0,0) │ │ (0,0) │ │ │
+│ │ │ │ │ ┌────┐ │ │ │
+│ ┌──────┐ │ │ Content │ │ │Page│ │ │ (0,h) │
+│ │Widget│ │ → │ │ → │ └────┘ │ → │ │
+│ └──────┘ │ │ │ │ ┌────┐ │ └─────────────┘
+│ │ │ │ │ │Page│ │ (0,0) (w,0)
+└─────────────┘ └─────────────┘ └──┴────┴─────┘
+```
+
+## Converting Between Coordinate Systems
+
+### Widget Local ↔ Global Coordinates
+
+Use [PdfViewerController.globalToLocal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToLocal.html) and [PdfViewerController.localToGlobal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/localToGlobal.html):
+
+```dart
+final controller = PdfViewerController();
+...
+
+// Global to local (e.g., from GestureDetector.onTapDown)
+final globalPos = details.globalPosition;
+final localPos = controller.globalToLocal(globalPos);
+
+// Local to global
+final localPos = Offset(100, 200);
+final globalPos = controller.localToGlobal(localPos);
+```
+
+### Global ↔ Document Coordinates
+
+Use [PdfViewerController.globalToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToDocument.html) and [PdfViewerController.documentToGlobal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/documentToGlobal.html):
+
+```dart
+// Global to document (accounts for zoom and pan)
+final globalPos = details.globalPosition;
+final docPos = controller.globalToDocument(globalPos);
+
+// Document to global
+final docPos = Offset(100, 200);
+final globalPos = controller.documentToGlobal(docPos);
+```
+
+### Widget Local ↔ Document Coordinates
+
+Use [PdfViewerController.localToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/localToDocument.html) and [PdfViewerController.documentToLocal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/documentToLocal.html):
+
+```dart
+// Local to document
+final localPos = Offset(100, 200);
+final docPos = controller.localToDocument(localPos);
+
+// Document to local
+final docPos = Offset(100, 200);
+final localPos = controller.documentToLocal(docPos);
+```
+
+### Document ↔ Page Coordinates
+
+Convert between document layout coordinates and individual page coordinates:
+
+```dart
+import 'package:pdfrx/pdfrx.dart';
+
+// Get the page layout rectangles
+final pageLayouts = controller.layout.pageLayouts;
+final pageRect = pageLayouts[pageIndex]; // Document coordinates
+
+// Document position to page position
+final docPos = Offset(150, 300);
+if (pageRect.contains(docPos)) {
+ // Offset within the page (top-left origin)
+ final pageOffset = docPos - pageRect.topLeft;
+
+ // Convert to PDF page coordinates (bottom-left origin)
+ // IMPORTANT: This automatically handles page rotation
+ final pdfPoint = pageOffset.toPdfPoint(
+ page: page,
+ scaledPageSize: pageRect.size,
+ );
+}
+
+// Page coordinates to document position
+final pdfPoint = PdfPoint(100, 200); // PDF coordinates (bottom-left origin)
+// IMPORTANT: This automatically handles page rotation
+final pageOffset = pdfPoint.toOffset(
+ page: page,
+ scaledPageSize: pageRect.size,
+); // Top-left origin
+final docPos = pageOffset.translate(pageRect.left, pageRect.top);
+```
+
+**Important Note about Page Rotation**: PDF pages can have rotation metadata (0°, 90°, 180°, or 270°). When converting between document and page coordinates, the conversion methods automatically account for the page's rotation. The coordinate system is rotated so that (0,0) in page coordinates always represents the bottom-left corner of the page as it appears when rendered (after rotation is applied).
+
+## Common Use Cases
+
+### Example 1: Finding Which Page Was Tapped
+
+```dart
+PdfViewerController controller;
+
+void onTapDown(TapDownDetails details) {
+ // Convert global position to document coordinates
+ final docPos = controller.globalToDocument(details.globalPosition);
+ if (docPos == null) return;
+
+ // Find which page contains this position
+ final pageLayouts = controller.layout.pageLayouts;
+ final pageIndex = pageLayouts.indexWhere((rect) => rect.contains(docPos));
+
+ if (pageIndex >= 0) {
+ final pageNumber = pageIndex + 1;
+ print('Tapped on page $pageNumber');
+
+ // Get position within the page
+ final pageRect = pageLayouts[pageIndex];
+ final offsetInPage = docPos - pageRect.topLeft;
+ print('Position in page: $offsetInPage');
+ }
+}
+```
+
+### Example 2: Highlighting a Specific PDF Rectangle
+
+```dart
+Widget buildPageOverlay(BuildContext context, Rect pageRect, PdfPage page) {
+ // Rectangle in PDF page coordinates (bottom-left origin)
+ final pdfRect = PdfRect(
+ left: 100,
+ top: 200,
+ right: 300,
+ bottom: 100,
+ );
+
+ // Convert to document coordinates (top-left origin, scaled)
+ final rect = pdfRect.toRect(
+ page: page,
+ scaledPageSize: pageRect.size,
+ );
+
+ // Position it within the page overlay
+ return Positioned(
+ left: rect.left,
+ top: rect.top,
+ width: rect.width,
+ height: rect.height,
+ child: Container(
+ decoration: BoxDecoration(
+ border: Border.all(color: Colors.yellow, width: 2),
+ ),
+ ),
+ );
+}
+```
+
+### Example 3: Converting Search Results to Overlay Positions
+
+```dart
+// Search for text in the PDF
+final matches = await controller.document.pages[pageNumber - 1].loadText();
+final searchResults = matches.searchText('keyword');
+
+// Build overlays for search results
+Widget buildSearchHighlights(BuildContext context, Rect pageRect, PdfPage page) {
+ return Stack(
+ children: searchResults.map((result) {
+ // result.bounds is in PDF page coordinates
+ final rect = result.bounds.toRect(
+ page: page,
+ scaledPageSize: pageRect.size,
+ );
+
+ return Positioned(
+ left: rect.left,
+ top: rect.top,
+ width: rect.width,
+ height: rect.height,
+ child: Container(
+ color: Colors.yellow.withOpacity(0.3),
+ ),
+ );
+ }).toList(),
+ );
+}
+```
+
+### Example 4: Getting Visible Document Area
+
+```dart
+// Get the currently visible area in document coordinates
+final visibleRect = controller.visibleRect;
+
+// Check if a specific position is visible
+final docPos = Offset(100, 200);
+final isVisible = visibleRect.contains(docPos);
+
+// Scroll to make a position visible
+final targetPos = Offset(500, 1000);
+if (!visibleRect.contains(targetPos)) {
+ controller.goToPosition(targetPos);
+}
+```
+
+## Understanding Zoom and Scale
+
+When working with coordinates, it's important to understand how zoom affects positioning:
+
+- **Document coordinates** are always at 72 DPI (unzoomed), regardless of the current zoom level
+- **Page overlay coordinates** must be scaled by the current zoom factor when positioning widgets
+
+```dart
+// In pageOverlaysBuilder
+Widget buildOverlay(BuildContext context, Rect pageRect, PdfPage page) {
+ // pageRect is already zoomed (in widget coordinates)
+ // To position something at a specific document coordinate:
+ final docOffset = Offset(100, 200); // In document coordinates
+
+ // Method 1: Use documentToLocal (recommended)
+ final localOffset = controller.documentToLocal(
+ docOffset.translate(pageRect.left, pageRect.top)
+ );
+
+ // Method 2: Manual scaling
+ final zoom = controller.currentZoom;
+ final scaledOffset = docOffset * zoom;
+
+ return Positioned(
+ left: scaledOffset.dx,
+ top: scaledOffset.dy,
+ child: YourWidget(),
+ );
+}
+```
+
+## Important Notes
+
+### Y-Axis Orientation
+
+- **Flutter/Widget coordinates**: Y increases downward (top to bottom)
+- **PDF page coordinates**: Y increases upward (bottom to top)
+
+The conversion extensions ([PdfPoint.toOffset](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPointExt/toOffset.html) and [Offset.toPdfPoint](https://pub.dev/documentation/pdfrx/latest/pdfrx/OffsetPdfPointExt/toPdfPoint.html)) handle this Y-axis flipping automatically.
+
+### Page Rotation
+
+PDF pages can have rotation metadata (0°, 90°, 180°, or 270°) that affects how the page is displayed. This rotation is stored in the PDF file and accessible via [PdfPage.rotation](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPage/rotation.html).
+
+#### How Rotation Affects Coordinates
+
+When a page is rotated:
+
+- The page's width and height are **swapped** for 90° and 270° rotations
+- The coordinate system is **rotated** so that (0,0) in page coordinates represents the bottom-left corner of the page **as it appears after rotation**
+- In-page offsets must account for this rotation when converting to/from PDF page coordinates
+
+#### Automatic Rotation Handling
+
+The conversion methods automatically account for page rotation when you use the extension methods:
+
+```dart
+// Automatic rotation handling (uses page.rotation)
+final pdfPoint = PdfPoint(100, 200);
+final offset = pdfPoint.toOffset(page: page); // Rotation applied automatically
+
+// Converting back also handles rotation
+final pageOffset = Offset(50, 100);
+final pdfPoint = pageOffset.toPdfPoint(page: page); // Rotation reversed automatically
+```
+
+#### Manual Rotation Override
+
+You can override the rotation if needed:
+
+```dart
+// Force a specific rotation (useful for preview or testing)
+final offset = pdfPoint.toOffset(page: page, rotation: 90);
+final pdfPoint = pageOffset.toPdfPoint(page: page, rotation: 0);
+```
+
+#### Example: Handling Rotated Pages
+
+```dart
+void highlightAreaOnRotatedPage(PdfPage page, PdfRect area) {
+ // Get page layout in document
+ final pageRect = controller.layout.pageLayouts[page.pageNumber - 1];
+
+ // Check the page rotation
+ print('Page rotation: ${page.rotation.index * 90}°'); // 0, 90, 180, or 270
+
+ // Convert PDF rect to document coordinates (rotation handled automatically)
+ final rect = area.toRect(
+ page: page,
+ scaledPageSize: pageRect.size,
+ ); // rotation is automatically applied
+
+ // For 90° or 270° rotations, note that width and height are swapped
+ if (page.rotation.index == 1 || page.rotation.index == 3) {
+ print('Page is rotated 90° or 270° - dimensions are swapped');
+ print('Original page size: ${page.width} x ${page.height}');
+ print('Rendered page size: ${pageRect.width} x ${pageRect.height}');
+ }
+}
+```
+
+**Key Points**:
+
+- Always use the conversion extension methods ([PdfPoint.toOffset](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPointExt/toOffset.html), [Offset.toPdfPoint](https://pub.dev/documentation/pdfrx/latest/pdfrx/OffsetPdfPointExt/toPdfPoint.html), [PdfRect.toRect](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfRectExt/toRect.html)) instead of manual calculations
+- These methods handle rotation, Y-axis flipping, and scaling automatically
+- The page layout rectangle (`pageRect`) already reflects the rotated dimensions
+- In-page offsets calculated by subtracting `pageRect.topLeft` are relative to the **rotated** page as displayed
+
+### Null Safety
+
+Some conversion methods return nullable values because they may fail if:
+
+- The widget is not yet rendered (`globalToLocal`, `localToGlobal`)
+- The position is outside the document bounds
+
+Always check for null:
+
+```dart
+final docPos = controller.globalToDocument(globalPos);
+if (docPos == null) {
+ // Widget not ready or position invalid
+ return;
+}
+// Proceed with docPos
+```
+
+## Coordinate Converter Interface
+
+For advanced use cases where you need to perform conversions from within custom builders, use [PdfViewerCoordinateConverter](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerCoordinateConverter-class.html):
+
+```dart
+// Available in viewerOverlayBuilder and pageOverlaysBuilder
+final converter = controller.doc2local;
+
+// Convert with BuildContext
+final localPos = converter.offsetToLocal(context, docPos);
+final localRect = converter.rectToLocal(context, docRect);
+```
+
+## API Reference
+
+### PdfViewerController Methods
+
+- [globalToLocal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToLocal.html) - Global → Local
+- [localToGlobal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/localToGlobal.html) - Local → Global
+- [globalToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToDocument.html) - Global → Document
+- [documentToGlobal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/documentToGlobal.html) - Document → Global
+- [localToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/localToDocument.html) - Local → Document
+- [documentToLocal](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/documentToLocal.html) - Document → Local
+- [currentZoom](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/currentZoom.html) - Get current zoom factor
+- [visibleRect](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/visibleRect.html) - Get visible area in document coordinates
+
+### Extension Methods
+
+- [PdfPoint.toOffset](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPointExt/toOffset.html) - PDF page → Document offset
+- [Offset.toPdfPoint](https://pub.dev/documentation/pdfrx/latest/pdfrx/OffsetPdfPointExt/toPdfPoint.html) - Document offset → PDF page
+- [PdfRect.toRect](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfRectExt/toRect.html) - PDF page rect → Document rect
+- [PdfRect.toRectInDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfRectExt/toRectInDocument.html) - PDF page rect → Full document position
+
+## See Also
+
+- [Deal with viewerOverlayBuilder and pageOverlaysBuilder](Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md) - Practical example using coordinate conversion
+- [Text Selection](Text-Selection.md) - Uses coordinate conversion for selection rectangles
+- [PDF Link Handling](PDF-Link-Handling.md) - Uses coordinate conversion for link areas
diff --git a/doc/Customizing-Key-Handling-on-PdfViewer.md b/doc/Customizing-Key-Handling-on-PdfViewer.md
new file mode 100644
index 00000000..0413c193
--- /dev/null
+++ b/doc/Customizing-Key-Handling-on-PdfViewer.md
@@ -0,0 +1,45 @@
+# Customizing Key Handling on PdfViewer
+
+Because [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) handles certain keys to allow users to scroll/zoom PDF view by keyboards, it may sometimes interfere with other widget's key handling, such as [TextField](https://api.flutter.dev/flutter/material/TextField-class.html)'s text input.
+
+To customize the key handling behavior, you can use [PdfViewerParams.onKey](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/onKey.html) and [PdfViewerParams.keyHandlerParams](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/keyHandlerParams.html).
+
+## Default implementation
+
+By default, [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) handles the following keys:
+
+Key | Description
+------------|----------------------
+[pageUp](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/pageUp-constant.html) | Scroll up
+[pageDown](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/pageDown-constant.html) | Scroll up
+[home](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/home-constant.html) | Go to first page
+[end](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/end-constant.html) | go to last page
+[equal](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/equal-constant.html) | Combination with `⌘`/`Ctrl` to zoom up (**currently not I18N-ed**)
+[minus](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/equal-constant.html) | Combination with `⌘`/`Ctrl` to zoom down
+[arrowUp](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/arrowUp-constant.html) | Scroll upward
+[arrowDown](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/arrowDown-constant.html) | Scroll downward
+[arrowLeft](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/arrowLeft-constant.html) | Scroll to left
+[arrowRight](https://api.flutter.dev/flutter/services/LogicalKeyboardKey/arrowRight-constant.html) | Scroll to right
+
+And, the other keys are **not handled** and handled by other widgets.
+
+## Overriding the default implementation
+
+The following fragment illustrates how to use [PdfViewerParams.onKey](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/onKey.html):
+
+```dart
+onKey: (params, key, isRealKeyPress) {
+ if (key == LogicalKeyboardKey.space) {
+ // handling the key inside the function
+ handleSpace();
+ return true;
+ }
+ if (key == LogicalKeyboardKey.arrowLeft || key == LogicalKeyboardKey.arrowRight) {
+ // returning false to disable the default logic
+ return false;
+ }
+ // returning null to let the default logic to handle the keys
+ return null;
+},
+```
+
diff --git a/doc/Dark-Night-Mode-Support.md b/doc/Dark-Night-Mode-Support.md
new file mode 100644
index 00000000..889561f6
--- /dev/null
+++ b/doc/Dark-Night-Mode-Support.md
@@ -0,0 +1,12 @@
+# Dark/Night Mode Support
+
+[PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) does not have any native dark (or night) mode support but it can be easily implemented using [ColorFiltered](https://api.flutter.dev/flutter/widgets/ColorFiltered-class.html) widget:
+
+```dart
+ColorFiltered(
+ colorFilter: ColorFilter.mode(Colors.white, darkMode ? BlendMode.difference : BlendMode.dst),
+ child: PdfViewer.file(filePath, ...),
+),
+```
+
+The trick is originally introduced by [pckimlong](https://github.com/pckimlong).
\ No newline at end of file
diff --git a/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md b/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md
new file mode 100644
index 00000000..6e855fd4
--- /dev/null
+++ b/doc/Deal-with-Password-Protected-PDF-Files-using-PasswordProvider.md
@@ -0,0 +1,51 @@
+# Handling Password-Protected PDF Files
+
+To support password protected PDF files, use [passwordProvider](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPasswordProvider.html) to supply passwords interactively:
+
+```dart
+PdfViewer.asset(
+ 'assets/test.pdf',
+ // Most easiest way to return some password
+ passwordProvider: _showPasswordDialog,
+
+ ...
+),
+
+...
+
+Future _showPasswordDialog() async {
+ final textController = TextEditingController();
+ return await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (context) {
+ return AlertDialog(
+ title: const Text('Enter password'),
+ content: TextField(
+ controller: textController,
+ autofocus: true,
+ keyboardType: TextInputType.visiblePassword,
+ obscureText: true,
+ onSubmitted: (value) => Navigator.of(context).pop(value),
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(null),
+ child: const Text('Cancel'),
+ ),
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(textController.text),
+ child: const Text('OK'),
+ ),
+ ],
+ );
+ },
+ );
+}
+```
+
+When [PdfViewer](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer-class.html) tries to open a password protected document, it calls the function passed to `passwordProvider` (except the first attempt; see [below](#firstattemptbyemptypassword)) repeatedly to get a new password until the document is successfully opened. And if the function returns null, the viewer will give up the password trials and the function is no longer called.
+
+### `firstAttemptByEmptyPassword`
+
+By default, the first password attempt uses empty password. This is because encrypted PDF files frequently use empty password for viewing purpose. It's _normally_ useful but if you want to use authoring password, it can be disabled by setting `firstAttemptByEmptyPassword` to false.
\ No newline at end of file
diff --git a/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md b/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md
new file mode 100644
index 00000000..d50032c7
--- /dev/null
+++ b/doc/Deal-with-viewerOverlayBuilder-and-pageOverlaysBuilder.md
@@ -0,0 +1,56 @@
+# Working with Viewer Overlays and Page Overlays
+
+The following fragment shows red-dot on user tapped location:
+
+```dart
+int? _pageNumber;
+Offset? _offsetInPage;
+
+...
+
+viewerOverlayBuilder: (context, size, handleLinkTap) => [
+ Positioned.fill(child: GestureDetector(
+ onTapDown: (details) {
+ // global position -> in-document position
+ final posInDoc = controller.globalToDocument(details.globalPosition);
+ if (posInDoc == null) return;
+ // determine which page contains the point (position)
+ final pageIndex = controller.layout.pageLayouts.indexWhere((pageRect) => pageRect.contains(posInDoc));
+ if (pageIndex < 0) return;
+ // in-document position -> in-page offset
+ _offsetInPage = posInDoc - controller.layout.pageLayouts[pageIndex].topLeft;
+ _pageNumber = pageIndex + 1;
+
+ // NOTE: you're hosting PdfViewer inside some StatefulWidget
+ // or inside StatefulBuilder
+ setState(() {});
+ },
+ )),
+],
+pageOverlaysBuilder: (context, pageRect, page) {
+ return [
+ if (_pageNumber == page.pageNumber && _offsetInPage != null)
+ Positioned(
+ left: _offsetInPage!.dx * controller.currentZoom, // position should be zoomed
+ top: _offsetInPage!.dy * controller.currentZoom,
+ child: Container(
+ width: 10,
+ height: 10,
+ color: Colors.red,
+ ),
+ ),
+ ];
+},
+```
+
+On [PdfViewerParams.viewerOverlayBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html);
+
+- Convert the global tap position to in-document position using [PdfViewerController.globalToDocument](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/globalToDocument.html)
+ - The in-document position is position in document structure (i.e., page layout in 72-dpi).
+- Determine which page contains the position using [PdfViewerController.layout.pageLayouts](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfPageLayout/pageLayouts.html)
+- Convert the in-document position to the in-page position (just subtract the page's top-left position)
+
+On [PdfViewerParams.pageOverlaysBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/pageOverlaysBuilder.html),
+
+- `pageRect` is the zoomed page rectangle inside the view
+- To correctly locate the position, it must be zoomed (by [PdfViewerController.currentZoom](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerController/currentZoom.html))
diff --git a/doc/Document-Loading-Indicator.md b/doc/Document-Loading-Indicator.md
new file mode 100644
index 00000000..2065a014
--- /dev/null
+++ b/doc/Document-Loading-Indicator.md
@@ -0,0 +1,15 @@
+# Document Loading Indicator
+
+[PdfViewer.uri](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewer/PdfViewer.uri.html) may take long time to download PDF file and you want to show some loading indicator. You can do that by [PdfViewerParams.loadingBannerBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/loadingBannerBuilder.html):
+
+```dart
+loadingBannerBuilder: (context, bytesDownloaded, totalBytes) {
+ return Center(
+ child: CircularProgressIndicator(
+ // totalBytes may not be available on certain case
+ value: totalBytes != null ? bytesDownloaded / totalBytes : null,
+ backgroundColor: Colors.grey,
+ ),
+ );
+}
+```
\ No newline at end of file
diff --git a/doc/Document-Outline-(a.k.a-Bookmarks).md b/doc/Document-Outline-(a.k.a-Bookmarks).md
new file mode 100644
index 00000000..3b915208
--- /dev/null
+++ b/doc/Document-Outline-(a.k.a-Bookmarks).md
@@ -0,0 +1,13 @@
+# Document Outline (Bookmarks)
+
+PDF defines document outline ([PdfOutlineNode](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfOutlineNode-class.html)), which is sometimes called as bookmarks or index. And you can access it by [PdfDocument.loadOutline](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfDocument/loadOutline.html).
+
+The following fragment obtains it on [PdfViewerParams.onViewerReady](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/onViewerReady.html):
+
+```dart
+onViewerReady: (document, controller) async {
+ outline.value = await document.loadOutline();
+},
+```
+
+[PdfOutlineNode](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfOutlineNode-class.html) is tree structured data and for more information, see the usage on the [example code](https://github.com/espresso3389/pdfrx/blob/master/example/viewer/lib/outline_view.dart).
diff --git a/doc/Double-tap-to-Zoom.md b/doc/Double-tap-to-Zoom.md
new file mode 100644
index 00000000..c22b3322
--- /dev/null
+++ b/doc/Double-tap-to-Zoom.md
@@ -0,0 +1,30 @@
+
+# Double-tap to Zoom
+
+You can implement double-tap-to-zoom feature using [PdfViewerParams.viewerOverlayBuilder](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerParams/viewerOverlayBuilder.html):
+
+```dart
+viewerOverlayBuilder: (context, size, handleLinkTap) => [
+ GestureDetector(
+ behavior: HitTestBehavior.translucent,
+ // Your code here:
+ onDoubleTap: () {
+ controller.zoomUp(loop: true);
+ },
+ // If you use GestureDetector on viewerOverlayBuilder, it breaks link-tap handling
+ // and you should manually handle it using onTapUp callback
+ onTapUp: (details) {
+ handleLinkTap(details.localPosition);
+ },
+ // Make the GestureDetector covers all the viewer widget's area
+ // but also make the event go through to the viewer.
+ child: IgnorePointer(
+ child:
+ SizedBox(width: size.width, height: size.height),
+ ),
+ ),
+ ...
+],
+```
+
+If you want to use [PdfViewerScrollThumb](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerScrollThumb-class.html) with double-tap-to-zoom enabled, place the double-tap-to-zoom code before [PdfViewerScrollThumb](https://pub.dev/documentation/pdfrx/latest/pdfrx/PdfViewerScrollThumb-class.html).
\ No newline at end of file
diff --git a/doc/Importing-Images-to-PDF.md b/doc/Importing-Images-to-PDF.md
new file mode 100644
index 00000000..d987fefd
--- /dev/null
+++ b/doc/Importing-Images-to-PDF.md
@@ -0,0 +1,313 @@
+# Importing Images to PDF
+
+pdfrx provides powerful APIs for importing images into PDF documents. This feature enables you to convert images to PDF format, insert images as new pages into existing PDFs, and create PDFs from scanned documents or photos.
+
+## Overview
+
+The image import feature allows you to:
+
+- Convert images (JPEG, PNG, etc.) to PDF format
+- Create PDF documents from JPEG images
+- Insert images as new pages into existing PDFs
+- Build PDFs from scanned documents or photos
+- Control image dimensions and placement in the PDF
+
+## Creating PDF Documents from Images
+
+### PdfDocument.createFromJpegData
+
+[PdfDocument.createFromJpegData](https://pub.dev/documentation/pdfrx_engine/latest/pdfrx_engine/PdfDocument/createFromJpegData.html) creates a new PDF document containing a single page with the specified JPEG image:
+
+```dart
+import 'dart:ui' as ui;
+import 'package:pdfrx/pdfrx.dart';
+import 'package:image/image.dart' as img;
+
+// Load and decode an image
+final imageBytes = await File('photo.jpg').readAsBytes();
+
+// If the image is already JPEG, you can use it directly
+// Otherwise, convert it to JPEG format
+Uint8List jpegData;
+if (imagePath.toLowerCase().endsWith('.jpg') || imagePath.toLowerCase().endsWith('.jpeg')) {
+ jpegData = imageBytes;
+} else {
+ // Decode the image
+ final image = img.decodeImage(imageBytes);
+ if (image == null) throw Exception('Failed to decode image');
+
+ // Encode as JPEG with quality 90
+ jpegData = Uint8List.fromList(img.encodeJpg(image, quality: 90));
+}
+
+// Create PDF with JPEG data (width and height in PDF units: 1/72 inch)
+final imageDoc = await PdfDocument.createFromJpegData(
+ jpegData,
+ width: 595, // A4 width in points (8.27 inches)
+ height: 842, // A4 height in points (11.69 inches)
+ sourceName: 'photo.pdf',
+);
+
+// Encode to PDF bytes
+final pdfBytes = await imageDoc.encodePdf();
+await File('output.pdf').writeAsBytes(pdfBytes);
+
+// Clean up
+imageDoc.dispose();
+```
+
+**Important Note:** The `width` and `height` parameters only control the visible page size in the PDF document. They do NOT resize or compress the actual image data. The JPEG image is embedded in the PDF as-is. If you need to reduce the PDF file size, you must resize the image before converting it to JPEG.
+
+### Understanding Image Dimensions
+
+PDF uses points as its unit of measurement, where 1 point = 1/72 inch. When creating a PDF from an image, you need to specify the page dimensions in points.
+
+#### Common Page Sizes (in points)
+
+```dart
+// A4 (210mm × 297mm)
+width: 595, height: 842
+
+// Letter (8.5" × 11")
+width: 612, height: 792
+
+// Legal (8.5" × 14")
+width: 612, height: 1008
+
+// A3 (297mm × 420mm)
+width: 842, height: 1191
+```
+
+#### Calculating Dimensions from Image Size
+
+If you want to preserve the image's aspect ratio and size based on DPI, you need to decode the JPEG to get its dimensions:
+
+```dart
+import 'package:image/image.dart' as img;
+
+// Decode JPEG to get dimensions
+final jpegImage = img.decodeJpg(jpegData);
+if (jpegImage == null) throw Exception('Failed to decode JPEG');
+
+// Assume image DPI (common values: 72, 96, 150, 300)
+const double assumedDpi = 300;
+
+// Calculate PDF page size from image dimensions
+final width = jpegImage.width * 72 / assumedDpi;
+final height = jpegImage.height * 72 / assumedDpi;
+
+final imageDoc = await PdfDocument.createFromJpegData(
+ jpegData,
+ width: width,
+ height: height,
+ sourceName: 'image.pdf',
+);
+```
+
+**Note:** Many JPEG files contain DPI information in their metadata, but it's often unreliable (typically 96 DPI or 72 DPI), which can result in very large PDF pages. It's usually better to use an assumed DPI (like 300) for better results.
+
+## Inserting Images into Existing PDFs
+
+You can combine `PdfDocument.createFromJpegData` with [page manipulation](PDF-Page-Manipulation.md) to insert images into existing PDF documents:
+
+```dart
+import 'package:image/image.dart' as img;
+
+// Load existing PDF
+final doc = await PdfDocument.openFile('document.pdf');
+
+// Load and convert image to JPEG
+final imageBytes = await File('photo.png').readAsBytes();
+final image = img.decodeImage(imageBytes);
+if (image == null) throw Exception('Failed to decode image');
+
+// Encode as JPEG
+final jpegData = Uint8List.fromList(img.encodeJpg(image, quality: 90));
+
+final imageDoc = await PdfDocument.createFromJpegData(
+ jpegData,
+ width: 595,
+ height: 842,
+ sourceName: 'temp-image.pdf',
+);
+
+// Insert image page at position 2 (after first page)
+doc.pages = [
+ doc.pages[0], // Page 1 (original)
+ imageDoc.pages[0], // Page 2 (new image)
+ ...doc.pages.sublist(1), // Remaining pages
+];
+
+// Save the result
+final pdfBytes = await doc.encodePdf();
+await File('output.pdf').writeAsBytes(pdfBytes);
+
+// Clean up
+doc.dispose();
+imageDoc.dispose();
+```
+
+## Converting Multiple Images to a Single PDF
+
+You can create a PDF document containing multiple images by combining pages from multiple image-based PDFs:
+
+```dart
+import 'package:image/image.dart' as img;
+
+// Create a new PDF document
+final combinedDoc = await PdfDocument.createNew(sourceName: 'multi-image.pdf');
+
+// List to store image documents
+final List imageDocs = [];
+
+try {
+ // Load and convert multiple images
+ for (final imagePath in ['image1.jpg', 'image2.png', 'image3.jpg']) {
+ final imageBytes = await File(imagePath).readAsBytes();
+
+ // Decode the image
+ final image = img.decodeImage(imageBytes);
+ if (image == null) {
+ print('Failed to decode $imagePath');
+ continue;
+ }
+
+ // Encode as JPEG
+ final jpegData = Uint8List.fromList(img.encodeJpg(image, quality: 90));
+
+ // Calculate dimensions (assuming 300 DPI)
+ final width = image.width * 72 / 300;
+ final height = image.height * 72 / 300;
+
+ final imageDoc = await PdfDocument.createFromJpegData(
+ jpegData,
+ width: width,
+ height: height,
+ sourceName: imagePath,
+ );
+ imageDocs.add(imageDoc);
+ }
+
+ // Combine all image pages
+ combinedDoc.pages = imageDocs.map((doc) => doc.pages[0]).toList();
+
+ // Encode to PDF
+ final pdfBytes = await combinedDoc.encodePdf();
+ await File('output.pdf').writeAsBytes(pdfBytes);
+
+} finally {
+ // Clean up resources
+ for (final doc in imageDocs) {
+ doc.dispose();
+ }
+ combinedDoc.dispose();
+}
+```
+
+## Controlling PDF File Size
+
+### Image Resolution and File Size
+
+The `width` and `height` parameters in `createFromJpegData()` **only control the page dimensions**, not the image resolution. The JPEG image data is embedded in the PDF file as-is.
+
+**Example:**
+
+```dart
+import 'package:image/image.dart' as img;
+
+// This creates a small page, but the PDF file will still contain
+// the full 4000x3000 pixel JPEG data
+final largeImage = img.decodeImage(bytes); // 4000x3000 pixels
+final jpegData = Uint8List.fromList(img.encodeJpg(largeImage!, quality: 90));
+
+final doc = await PdfDocument.createFromJpegData(
+ jpegData,
+ width: 200, // Small page width
+ height: 150, // Small page height
+ sourceName: 'small-page-large-file.pdf',
+);
+// Result: Small visible page, but LARGE file size!
+```
+
+### How to Reduce PDF File Size
+
+To reduce the PDF file size, you must resize the image **before** converting it to JPEG:
+
+```dart
+import 'package:image/image.dart' as img;
+
+// Load and decode original image
+final originalImage = img.decodeImage(imageBytes);
+if (originalImage == null) throw Exception('Failed to decode image');
+
+// Target page size at desired DPI (e.g., A4 at 150 DPI)
+const targetDpi = 150.0;
+const pageWidthInches = 8.27; // A4 width in inches
+const pageHeightInches = 11.69; // A4 height in inches
+const pageWidthPixels = (pageWidthInches * targetDpi).round(); // 1240 pixels
+const pageHeightPixels = (pageHeightInches * targetDpi).round(); // 1754 pixels
+
+// Calculate aspect ratios
+final imageAspect = originalImage.width / originalImage.height;
+final pageAspect = pageWidthPixels / pageHeightPixels;
+
+// Calculate target dimensions that fit within the page while preserving aspect ratio
+int targetWidth, targetHeight;
+if (imageAspect > pageAspect) {
+ // Image is wider than page - fit to width
+ targetWidth = pageWidthPixels;
+ targetHeight = (pageWidthPixels / imageAspect).round();
+} else {
+ // Image is taller than page - fit to height
+ targetHeight = pageHeightPixels;
+ targetWidth = (pageHeightPixels * imageAspect).round();
+}
+
+// Resize the image
+final resizedImage = img.copyResize(
+ originalImage,
+ width: targetWidth,
+ height: targetHeight,
+ interpolation: img.Interpolation.linear,
+);
+
+// Encode as JPEG with quality setting (1-100)
+// Lower quality = smaller file size but lower image quality
+final jpegData = Uint8List.fromList(img.encodeJpg(resizedImage, quality: 85));
+
+// Calculate PDF page dimensions
+const pdfPageWidth = pageWidthInches * 72; // A4 width in points (595)
+const pdfPageHeight = pageHeightInches * 72; // A4 height in points (842)
+
+final doc = await PdfDocument.createFromJpegData(
+ jpegData,
+ width: pdfPageWidth,
+ height: pdfPageHeight,
+ sourceName: 'optimized.pdf',
+);
+
+// Encode to PDF
+final pdfBytes = await doc.encodePdf();
+await File('output.pdf').writeAsBytes(pdfBytes);
+
+// Clean up
+doc.dispose();
+```
+
+### Recommended Image Resolutions
+
+Choose your image resolution based on the intended use:
+
+| Use Case | DPI | A4 Page Size (pixels) | Description |
+|----------|-----|----------------------|-------------|
+| Screen viewing | 72-96 | 595×842 to 794×1123 | Smallest file size |
+| Standard printing | 150 | 1240×1754 | Good balance |
+| High-quality printing | 300 | 2480×3508 | Professional quality |
+| Archival/Photo printing | 600 | 4960×7016 | Maximum quality |
+
+**Rule of thumb:** Match the image pixel dimensions to your target DPI and page size to avoid unnecessarily large files.
+
+## Related Documentation
+
+- [PDF Page Manipulation](PDF-Page-Manipulation.md) - Learn how to combine and rearrange pages
+- [pdf_combine example app](../packages/pdfrx/example/pdf_combine/) - Visual interface for combining PDFs and images
diff --git a/doc/Interoperability-with-other-PDFium-Libraries.md b/doc/Interoperability-with-other-PDFium-Libraries.md
new file mode 100644
index 00000000..b46fb88b
--- /dev/null
+++ b/doc/Interoperability-with-other-PDFium-Libraries.md
@@ -0,0 +1,355 @@
+# Interoperability with other PDFium Libraries
+
+This document explains how pdfrx can coexist with other PDFium-based libraries in the same application, and the mechanisms provided to ensure safe concurrent usage.
+
+## The Challenge
+
+PDFium is not thread-safe. When multiple libraries in the same application use PDFium, they can interfere with each other causing:
+
+- Crashes due to concurrent access
+- Data corruption
+- Unexpected behavior
+
+This is particularly important when your application uses:
+
+- Multiple PDF libraries (e.g., pdfrx alongside another PDFium wrapper)
+- Direct PDFium calls through FFI
+- Native plugins that internally use PDFium
+
+## Solution: Coordinated PDFium Access
+
+pdfrx provides mechanisms to coordinate PDFium access across different libraries through the `PdfrxEntryFunctions` class.
+
+## Low-Level PDFium Bindings
+
+For advanced use cases, the [pdfium_dart](https://pub.dev/packages/pdfium_dart) package exposes direct access to PDFium's C API through FFI bindings. This allows you to:
+
+- Call any PDFium function directly
+- Implement custom PDF processing not covered by the high-level API
+- Integrate with existing PDFium-based code
+
+**Important**: When using low-level bindings, you must:
+
+1. Initialize PDFium first using `pdfrxFlutterInitialize()` or `pdfrxInitialize()` (See [pdfrx Initialization](pdfrx-Initialization.md))
+2. Wrap all PDFium calls with `suspendPdfiumWorkerDuringAction()` to prevent conflicts
+3. Properly manage memory allocation and deallocation
+
+See [Example 2](#example-2-low-level-pdfium-bindings-access) below for detailed usage.
+
+### PDFium Initialization
+
+PDFium requires initialization through its C API functions `FPDF_InitLibrary()` or `FPDF_InitLibraryWithConfig()` before any PDF operations can be performed. Starting from pdfrx v2.1.15, this initialization is handled automatically when you call:
+
+- `pdfrxFlutterInitialize()` - For Flutter applications
+- `pdfrxInitialize()` - For pure Dart applications
+
+These functions internally call the PDFium's `FPDF_InitLibraryWithConfig()` to properly initialize the PDFium library. This ensures:
+
+- PDFium is initialized exactly once for all libraries
+- The initialization happens at the right time
+- The PDFium instance can be shared across multiple libraries
+
+**Important**: pdfrx does relatively important PDFium initialization process with `FPDF_InitLibraryWithConfig()`, so it's recommended to call `pdfrxFlutterInitialize()` or `pdfrxInitialize()` for initialization rather than calling `FPDF_InitLibrary()` or `FPDF_InitLibraryWithConfig()` on your code or by other libraries without any other important reason.
+
+### Suspending PDFium Worker
+
+The most important feature for interoperability is `PdfrxEntryFunctions.suspendPdfiumWorkerDuringAction()`. This function temporarily suspends pdfrx's internal PDFium operations while you call other PDFium-based libraries:
+
+```dart
+import 'package:pdfrx/pdfrx.dart';
+
+// Suspend pdfrx's PDFium operations while calling another library
+final result = await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() async {
+ // Safe to call other PDFium-based libraries here
+ // pdfrx won't interfere with these calls
+ return await otherPdfLibrary.processPdf();
+});
+```
+
+## Implementation Details
+
+### Native Platforms (iOS, Android, Windows, macOS, Linux)
+
+On native platforms, pdfrx uses a background isolate worker to handle PDFium operations. The `suspendPdfiumWorkerDuringAction` method:
+
+1. Pauses the background worker's PDFium operations
+2. Executes your action (which can safely call PDFium)
+3. Resumes the background worker
+
+This ensures that pdfrx and other libraries never call PDFium simultaneously.
+
+### Web Platform
+
+On web, pdfrx uses a Web Worker with PDFium WASM. Since the WASM instance is isolated within the worker, there's no risk of interference with other libraries. The `suspendPdfiumWorkerDuringAction` method simply executes the action without additional synchronization.
+
+## Usage Examples
+
+### Example 1: Using pdfrx with Another PDFium Library
+
+```dart
+import 'package:pdfrx/pdfrx.dart';
+import 'package:another_pdf_lib/another_pdf_lib.dart' as other;
+
+class PdfProcessor {
+ // Initialize both libraries
+ static Future initialize() async {
+ // Initialize pdfrx (which calls FPDF_InitLibraryWithConfig internally)
+ pdfrxFlutterInitialize();
+ }
+
+ // Process PDF with the other library
+ Future extractTextWithOtherLibrary(String path) async {
+ // Suspend pdfrx operations during the other library's work
+ return await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() async {
+ final doc = await other.PdfDocument.open(path);
+ final text = await doc.extractText();
+ await doc.close();
+ return text;
+ });
+ }
+
+ // Continue using pdfrx normally
+ Future renderWithPdfrx(String path) async {
+ final doc = await PdfDocument.openFile(path);
+ // ... render pages ...
+ doc.dispose();
+ }
+}
+```
+
+### Example 2: Low-Level PDFium Bindings Access
+
+The [pdfium_dart](https://pub.dev/packages/pdfium_dart) package provides direct access to PDFium bindings for advanced use cases. You can import the low-level bindings to make direct PDFium API calls:
+
+```dart
+import 'dart:ffi';
+import 'dart:typed_data';
+import 'package:ffi/ffi.dart';
+import 'package:pdfrx/pdfrx.dart';
+// Access low-level PDFium bindings from pdfium_dart
+import 'package:pdfium_dart/pdfium_dart.dart';
+
+class LowLevelPdfiumAccess {
+ Future useDirectBindings() async {
+ // Initialize PDFium through pdfrx
+ await pdfrxFlutterInitialize();
+
+ // Get PDFium bindings
+ final pdfium = await getPdfium();
+
+ // Now you can use all PDFium C API functions through the bindings
+ // Remember to wrap calls with suspendPdfiumWorkerDuringAction
+ await PdfrxEntryFunctions.instance.suspendPdfiumWorkerDuringAction(() {
+ // You can call any PDFium function from the bindings
+ // pdfium.FPDF_LoadDocument(...)
+ // pdfium.FPDF_GetPageCount(...)
+ // etc.
+ });
+ }
+
+ Future