Другое

PDF.js не отображается в Tauri: Полное решение для разработчиков

Исправьте проблемы с отображением PDF.js в приложениях Tauri: настройте протоколы ресурсов, CSP и доступ к файлам, чтобы PDF корректно отображался на настольных.

PDF отображается в браузере, но не в приложении Tauri

Я разрабатываю приложение Tauri + Vite + React с просмотрщиком PDF, использующим PDF.js. В браузере, когда я открываю http://localhost:5173/sample.pdf, PDF отображается без ошибок, но внутри настольного приложения Tauri PDF не рендерится. Элемент <canvas> появляется, но страница PDF не отрисовывается, оставаясь пустой. В пользовательском интерфейсе и в терминале нет видимых ошибок.

Детали проблемы

  • PDF-файл находится в lonefoxpdf/public/sample.pdf и корректно загружается в браузере.
  • Я пробовал использовать абсолютный URL файла: file:///C:/Users/getsu/OneDrive/Desktop/lonefoxpdf/public/sample.pdf.
    Браузер всё равно загружает его, но приложение Tauri продолжает показывать пустой <canvas>.

Реализация кода

Настройка воркера PDF.js (PdfViewer.jsx)

javascript
import * as pdfjsLib from "pdfjs-dist/legacy/build/pdf";

pdfjsLib.GlobalWorkerOptions.workerSrc =
  new URL('pdfjs-dist/build/pdf.worker.entry', import.meta.url).href;

Загрузка PDF в useEffect

javascript
useEffect(() => {
  let cancelled = false;

  const loadPdf = async () => {
    try {
      const pdf = await pdfjsLib.getDocument("/sample.pdf").promise;
      if (cancelled) return;

      const page = await pdf.getPage(1);
      const viewport = page.getViewport({ scale: 1.2 });

      const canvas = canvasRef.current;
      canvas.width = viewport.width;
      canvas.height = viewport.height;
      const ctx = canvas.getContext("2d");

      await page.render({ canvasContext: ctx, viewport }).promise;
      setLoading(false);

    } catch (error) {
      console.error("Error loading PDF:", error);
      setLoading(false);
    }
  };

  loadPdf();
  return () => { cancelled = true };
}, []);

Отрисовка компонента (App.jsx)

javascript
<PdfViewer url="/sample.pdf" />

Конфигурация allowlist в Tauri

json
"allowlist": {
  "fs": {
    "readFile": true,
    "scope": ["$RESOURCE/*", "$APP/*"]
  }
}

Вопрос

Что может вызывать корректную отрисовку PDF в браузере, но отсутствие рендеринга в приложении Tauri, и как я могу решить эту проблему? Есть ли какие‑то специфические нюансы загрузки PDF в Tauri, которые отличаются от поведения обычного веб‑браузера?

Проблема, с которой вы столкнулись, является распространённой при работе с PDF.js в приложениях Tauri. Обычно её корень лежит в ограничениях безопасности Tauri, которые запрещают прямой доступ к файлам через обычные веб‑протоколы, в отличие от обычных браузеров. В изолированной среде WebView Tauri не может обращаться к локальным файлам по путям /sample.pdf или URL‑у file://, потому что WebView работает под другими правилами безопасности.

Содержание


Понимание модели безопасности Tauri

Tauri работает с приоритетом безопасности, который существенно отличается от браузерной среды. Когда вы запускаете приложение в браузере, политика безопасности более гибкая и позволяет доступ к локальным файлам, но WebView в Tauri строго изолирован, чтобы сохранить безопасность.

Согласно документации Tauri по файловой системе, для доступа к файлам необходимо использовать специальные протоколы и настройки. WebView в Tauri разрешает пути относительно tauri://localhost, а не напрямую к файловой системе. Это означает, что путь /sample.pdf работает в браузере, потому что он разрешается относительно веб‑сервера, но в Tauri он не найден.

Ключевое различие: браузеры позволяют использовать протокол file:// при работе с локальными файлами, тогда как модель безопасности Tauri блокирует это по умолчанию, чтобы предотвратить несанкционированный доступ к файловой системе пользователя.


Доступ к файлам и настройка путей

Ваш текущий подход с использованием /sample.pdf или абсолютных путей к файлам не будет работать в Tauri из‑за песочницы безопасности. Необходимо использовать протокол ресурсов Tauri и правильные методы доступа к файлам.

Текущие проблемы:

  1. Разрешение путей: Путь /sample.pdf работает в браузере, потому что сервер Vite его обслуживает, но в Tauri такого веб‑сервера по умолчанию нет.
  2. Абсолютные пути к файлам: Использование file:///C:/Users/... блокируется политиками безопасности Tauri.
  3. Конфигурация области: Ваш список разрешений ["$RESOURCE/*", "$APP/*"] корректен, но может потребовать дополнительной настройки.

Решение:

Нужно использовать функцию convertFileSrc из Tauri для преобразования путей к файлам в протокол asset:. Согласно обсуждениям Tauri, файлы следует загружать через протокол ресурсов.

javascript
import { convertFileSrc } from '@tauri-apps/api/tauri';

// Вместо "/sample.pdf" преобразуйте путь
const assetPath = convertFileSrc('/path/to/your/sample.pdf');

Проблемы конфигурации воркера PDF.js

PDF.js требует воркер‑файл для обработки PDF‑парсинга и рендеринга. В Tauri загрузка воркера часто завершается неудачей, потому что воркер не может получить доступ к тем же ресурсам, что и основной поток.

Текущая настройка воркера:

javascript
pdfjsLib.GlobalWorkerOptions.workerSrc =
  new URL('pdfjs-dist/build/pdf.worker.entry', import.meta.url).href;

Этот подход работает в браузере, но может не сработать в Tauri, потому что воркер не может корректно разрешить URL в изолированной среде.

Решения для воркера:

  1. Использовать другой способ загрузки воркера:
javascript
import * as pdfjsLib from "pdfjs-dist/legacy/build/pdf";
import { convertFileSrc } from '@tauri-apps/api/tauri';

// Загрузить воркер из публичной директории
pdfjsLib.GlobalWorkerOptions.workerSrc = 
  convertFileSrc('/pdfjs-dist/build/pdf.worker.min.js');
  1. Встроить воркер, если доступ к файлам продолжает не работать.

CSP и требования к протоколам

По умолчанию Content Security Policy (CSP) в Tauri не разрешает протокол asset:, который необходим для доступа к файлам в PDF.js. Нужно настроить CSP в конфигурации Tauri, чтобы включить этот протокол.

Конфигурация CSP:

В файле tauri.conf.json добавьте протокол ресурсов в CSP:

json
"security": {
  "csp": "default-src 'self'; img-src 'self' asset: data:; connect-src 'self' asset:; font-src 'self' asset:; script-src 'self' 'unsafe-inline' 'unsafe-eval' asset:; style-src 'self' 'unsafe-inline' asset:; worker-src 'self' asset: blob:;"
}

Согласно обсуждению Tauri, необходимо явно включить протокол asset: в CSP, чтобы разрешить доступ к файлам.


Пошаговые решения

Решение 1: Использование протокола ресурсов Tauri

  1. Обновите код загрузки PDF:
javascript
import * as pdfjsLib from "pdfjs-dist/legacy/build/pdf";
import { convertFileSrc } from '@tauri-apps/api/tauri';

// Настроить воркер
pdfjsLib.GlobalWorkerOptions.workerSrc = convertFileSrc('/pdfjs-dist/build/pdf.worker.min.js');

useEffect(() => {
  let cancelled = false;

  const loadPdf = async () => {
    try {
      // Преобразовать путь к файлу в протокол asset
      const assetPath = convertFileSrc('/sample.pdf');
      
      const pdf = await pdfjsLib.getDocument(assetPath).promise;
      if (cancelled) return;

      const page = await pdf.getPage(1);
      const viewport = page.getViewport({ scale: 1.2 });

      const canvas = canvasRef.current;
      canvas.width = viewport.width;
      canvas.height = viewport.height;
      const ctx = canvas.getContext("2d");

      await page.render({ canvasContext: ctx, viewport }).promise;
      setLoading(false);

    } catch (error) {
      console.error("Error loading PDF:", error);
      setLoading(false);
    }
  };

  loadPdf();
  return () => { cancelled = true };
}, []);

Решение 2: Использование API файловой системы Tauri

Если протокол ресурсов не работает, можно прочитать файл через API файловой системы Tauri и загрузить его как data URL:

javascript
import { readBinaryFile } from '@tauri-apps/api/fs';
import { convertFileSrc } from '@tauri-apps/api/tauri';

useEffect(() => {
  let cancelled = false;

  const loadPdf = async () => {
    try {
      // Прочитать файл через API Tauri
      const fileData = await readBinaryFile('/sample.pdf');
      
      // Преобразовать в Uint8Array
      const pdfData = new Uint8Array(fileData);
      
      // Загрузить PDF из данных
      const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise;
      if (cancelled) return;

      const page = await pdf.getPage(1);
      const viewport = page.getViewport({ scale: 1.2 });

      const canvas = canvasRef.current;
      canvas.width = viewport.width;
      canvas.height = viewport.height;
      const ctx = canvas.getContext("2d");

      await page.render({ canvasContext: ctx, viewport }).promise;
      setLoading(false);

    } catch (error) {
      console.error("Error loading PDF:", error);
      setLoading(false);
    }
  };

  loadPdf();
  return () => { cancelled = true };
}, []);

Решение 3: Конфигурация области файлов

Убедитесь, что в tauri.conf.json правильно настроена область доступа к файлам:

json
"allowlist": {
  "fs": {
    "readFile": true,
    "scope": ["$RESOURCE/*", "$APP/*", "$APPCONFIG/*"]
  },
  "shell": {
    "open": true
  }
}

Альтернативные подходы

1. Использовать пакет react-pdf-viewer

Если PDF.js продолжает вызывать проблемы, рассмотрите использование специализированного React‑пакета для просмотра PDF, например react-pdf-viewer. Эти пакеты разработаны с учётом специфики Tauri.

bash
npm install react-pdf-viewer

2. Использовать пакет react-pdf

Другой вариант – react-pdf, который имеет лучшую совместимость с Tauri:

bash
npm install react-pdf

3. Служить PDF через локальный сервер

Если необходимо сохранить использование PDF.js, можно создать локальный сервер внутри приложения Tauri, который будет обслуживать PDF‑файлы, тем самым имитируя браузерную среду.


Отладка и проверка

Шаг 1: Включить подробный лог

Добавьте подробный лог, чтобы определить, где возникает проблема:

javascript
const loadPdf = async () => {
  try {
    console.log("Attempting to load PDF...");
    const assetPath = convertFileSrc('/sample.pdf');
    console.log("Asset path:", assetPath);
    
    const loadingTask = pdfjsLib.getDocument(assetPath);
    console.log("Loading task created");
    
    pdf = await loadingTask.promise;
    console.log("PDF loaded successfully");
    
    // Продолжить с рендерингом...
  } catch (error) {
    console.error("Detailed error:", error);
    console.error("Error stack:", error.stack);
  }
};

Шаг 2: Проверить доступ к файлу

Убедитесь, что PDF‑файл находится в правильном месте и доступен:

javascript
import { exists } from '@tauri-apps/api/fs';

const checkFileExists = async () => {
  const exists = await exists('/sample.pdf');
  console.log("File exists:", exists);
};

Шаг 3: Тестировать с разными PDF

Попробуйте загрузить разные PDF‑файлы, чтобы изолировать, является ли проблема специфической для конкретного файла или общей.


Источники

  1. Документация Tauri по файловой системе
  2. Обсуждение Tauri – проблемы доступа к файлам
  3. Конфигурация CSP в Tauri
  4. Stack Overflow – Рендеринг PDF в Tauri
  5. Документация react-pdf-viewer
  6. Справочник API файловой системы Tauri

Заключение

Проблема рендеринга PDF в вашем приложении Tauri связана с ограничениями безопасности платформы, которые существенно отличаются от поведения браузера. Ключевые выводы:

  1. Используйте протокол ресурсов Tauri с convertFileSrc вместо прямых путей к файлам.
  2. Настройте CSP, чтобы включить протокол asset:.
  3. Рассмотрите альтернативные PDF‑просмотрщики, такие как react-pdf-viewer, если PDF.js продолжает вызывать проблемы.
  4. Включайте подробный лог, чтобы точно определить, где происходит сбой.
  5. Проверяйте доступ к файлам через API файловой системы Tauri перед попыткой рендеринга.

Самый надёжный способ – использовать API файловой системы Tauri для чтения PDF как бинарных данных и загружать их напрямую в PDF.js, обходя ограничения доступа к файлам. Это гарантирует, что ваш просмотрщик PDF будет работать одинаково в браузере и в среде Tauri.

Авторы
Проверено модерацией
Модерация