Ambas páginas recortadas con un margen de 60 puntos, eliminando el margen blanco exterior y el marco

Los PDF suelen contener más espacio en blanco del que necesitan: documentos escaneados con bordes gruesos, planos de ingeniería con márgenes generosos o facturas en las que solo importa la tabla central. Recortar la página es la solución natural, pero hacerlo en un flujo de trabajo basado en el navegador no es sencillo. Las herramientas de escritorio rompen la experiencia web, y enviar el archivo a un servidor backend plantea problemas de privacidad y cumplimiento.

Aquí es donde entra en juego Spire.PDF for JavaScript. Se ejecuta sobre WebAssembly y opera por completo dentro del navegador, cargando y guardando archivos PDF a través de un sistema de archivos virtual (VFS) sin necesidad de idas y vueltas al servidor. Puede establecer el área visible de cada página mediante programación y permitir que el usuario descargue el resultado recortado, todo del lado del cliente. Para la instalación y la configuración del proyecto, consulte Integrar Spire.PDF for JavaScript en un proyecto de React. Los ejemplos siguientes suponen que Spire.PDF está instalado y que el módulo WebAssembly está inicializado.


CropBox y MediaBox: dos cuadros de página explicados

Cada página de PDF se define mediante dos rectángulos, y comprender la diferencia entre ellos es esencial antes de empezar a recortar.

MediaBox describe las dimensiones físicas de la página, es decir, la hoja de papel completa. Es el límite más externo y define el espacio de coordenadas en el que se coloca todo el contenido. Una página A4 estándar tiene un MediaBox de aproximadamente 595 × 842 puntos.

CropBox define lo que realmente muestra el visor. Es una subregión del MediaBox, y cualquier contenido que quede fuera del CropBox se oculta a la vista. De forma predeterminada, el CropBox coincide con el MediaBox, por lo que normalmente se ve la página completa. Cuando reduce el CropBox, en efecto le está diciendo al lector de PDF: "Muestre solo esta parte de la página."

La clave para el recorte es que el CropBox se deriva del MediaBox. Se leen las dimensiones completas de la página desde page.MediaBox.Width y page.MediaBox.Height, luego se calcula un rectángulo más pequeño, con un margen interior del valor que se desee en cada lado, y se asigna a page.CropBox. Las coordenadas x e y del CropBox se miden desde la esquina superior izquierda de la página, y width y height determinan cuánto de la página se conserva.


Recortar todas las páginas con un margen uniforme

El escenario más común es aplicar la misma reducción de margen a todas las páginas de un documento. Se itera sobre doc.Pages, se lee el MediaBox de cada página y se establece un CropBox con un margen interior de un número fijo de puntos en los cuatro lados.

El ejemplo siguiente recorta 60 puntos de cada borde de cada página, suficiente para eliminar un borde blanco ancho o un marco no deseado:

function App() {
  const cropPdfPage = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be cropped into the VFS
    const inputFileName = 'ToCrop.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Trim 60 points off every side
    const margin = 60;

    for (let i = 0; i < doc.Pages.Count; i++) {
      const page = doc.Pages.get_Item(i);

      // MediaBox gives the full extent of the page, from which the crop box is derived
      // (x and y are measured from the top-left corner of the page)
      const width = page.MediaBox.Width;
      const height = page.MediaBox.Height;

      page.CropBox = new pdfModule.RectangleF({
        x: margin,
        y: margin,
        width: width - margin * 2,
        height: height - margin * 2,
      });
    }

    const outputFileName = 'CropByMargins.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Crop PDF Pages</h1>
      <button onClick={cropPdfPage}>
        Crop PDF
      </button>
    </div>
  );
}

export default App;

Ambas páginas se recortan con un margen de 60 puntos, por lo que se eliminan el margen blanco y el marco que rodea la página:

Ambas páginas recortadas con un margen de 60 puntos, eliminando el margen blanco exterior y el marco

Ajuste el valor de margin para controlar con qué agresividad se recorta la página. Un valor mayor elimina más espacio en blanco; un valor menor realiza un recorte más sutil. Como el margen se aplica de forma uniforme a los cuatro lados, este enfoque funciona mejor cuando el borde no deseado es aproximadamente igual en todos los lados, que es lo habitual en documentos escaneados y planos exportados.


Recortar una sola página o todo el documento

El bucle de la sección anterior aplica el mismo recorte a todas las páginas. Pero CropBox es una propiedad de nivel de página: no existe una interfaz de recorte para todo el documento. El bucle simplemente garantiza que cada página reciba el mismo margen. Esta distinción es importante cuando sus páginas tienen necesidades diferentes.

Cuándo recortar todo el documento: utilice el enfoque con bucle cuando todas las páginas compartan el mismo problema, por ejemplo, un lote escaneado en el que cada página tiene el mismo borde del escáner, o un conjunto de planos de varias páginas exportado con márgenes idénticos. Un único valor de margin mantiene el código sencillo y el resultado coherente.

Cuándo recortar una sola página: elimine el bucle y asigne el CropBox directamente a la página de destino. Esto resulta útil cuando solo una página necesita recorte, por ejemplo, una portada con un logotipo de gran tamaño o una factura en la que solo debe verse la región de la tabla de la página 2. También puede aplicar valores de recorte distintos a páginas distintas combinando lógica por página dentro del bucle.

A continuación se muestra cómo recortar solo la primera página, conservando un bloque de 400 × 500 puntos que comienza en la posición (80, 80) desde la esquina superior izquierda:

// Crop page 1 only: keep a 400 x 500 point block starting at (80, 80) from the top-left corner
const page = doc.Pages.get_Item(0);
page.CropBox = new pdfModule.RectangleF({ x: 80, y: 80, width: 400, height: 500 });

Los valores x e y especifican dónde comienza la región visible, medidos desde la esquina superior izquierda de la página. Los valores width y height definen el tamaño de esa región. Todo lo que quede fuera de este rectángulo se oculta al visor.


Recorte suave: qué ocurre con el contenido

Hay un detalle importante sobre CropBox que es fácil pasar por alto: realiza un recorte suave, no uno duro.

Cuando establece el CropBox, cambia el límite visible de la página: el rectángulo que muestran los visores de PDF y que las impresoras utilizan como área de página. Pero el contenido que queda fuera de este límite no se elimina del archivo. Sigue estando ahí, solo que oculto. Esto tiene dos consecuencias prácticas:

  1. El tamaño del archivo no disminuye. El texto, las imágenes y los gráficos vectoriales recortados permanecen en el flujo de datos del PDF. Si su objetivo es reducir el tamaño del archivo recortando márgenes, establecer solo el CropBox no lo conseguirá.

  2. El contenido recortado sigue siendo buscable. El texto que está fuera del CropBox visible todavía puede encontrarse con las funciones de búsqueda y copiarse por usuarios que sepan cómo seleccionar más allá del área visible. Por lo general, esto no supone un problema al recortar márgenes, pero significa que el recorte no es una forma de censurar ni de eliminar de manera segura información confidencial.

Si necesita eliminar realmente el contenido —para censurarlo, reducir el tamaño del archivo o asegurarse de que el texto oculto no pueda recuperarse—, debe reconstruir la página. El enfoque consiste en crear un documento nuevo, extraer el contenido visible de la página recortada con page.CreateTemplate(), dibujarlo en una página nueva del nuevo documento y guardar el resultado. Esto produce un recorte duro en el que el contenido fuera de los límites ya no existe en el archivo.

No obstante, para la mayoría de los casos de uso de recorte de márgenes y eliminación de espacio en blanco, el recorte suave con CropBox es exactamente lo que se necesita: es rápido, sencillo y produce un resultado visualmente limpio sin la sobrecarga de reconstruir el documento.


Preguntas frecuentes

¿Puedo recortar una sola página en lugar de todo el documento?

Sí. Como CropBox es una propiedad por página, no hay un método integrado para "recortar todo el documento": el bucle del ejemplo principal es solo una comodidad para aplicar el mismo margen a todas las páginas. Para recortar solo una página, omita el bucle y asigne el CropBox directamente a esa página:

// Crop page 1 only: keep a 400 x 500 point block starting at (80, 80) from the top-left corner
const page = doc.Pages.get_Item(0);
page.CropBox = new pdfModule.RectangleF({ x: 80, y: 80, width: 400, height: 500 });

¿Cómo deshago un recorte?

Establecer CropBox modifica el cuadro de página en el lugar, y el documento no almacena el cuadro anterior. Podría esperar que asignar page.MediaBox de nuevo a page.CropBox restaurara la vista original, pero esto no funciona como se pretende: las coordenadas se aplican en relación con el origen del área visible actual, por lo que solo cambian las dimensiones mientras el origen permanece fijo. Después de un recorte de 60 puntos, reasignar las dimensiones del MediaBox sigue dejando el área visible comenzando en (60, 60).

La solución más sencilla es volver a cargar el archivo original si todavía lo tiene. Si solo dispone del archivo recortado, puede desplazar el origen de nuevo a la esquina superior izquierda usando desplazamientos negativos y restaurar las dimensiones completas de la página:

// Undo when the crop offset was (offsetX, offsetY)
page.CropBox = new pdfModule.RectangleF({
  x: -offsetX,
  y: -offsetY,
  width: page.MediaBox.Width,
  height: page.MediaBox.Height,
});

El archivo no se redujo y el contenido recortado sigue siendo buscable, ¿por qué?

Este es el comportamiento esperado. CropBox realiza un recorte suave: solo cambia el límite visible de la página, mientras que el contenido fuera de ese límite permanece en el archivo y todavía puede encontrarse mediante búsquedas de texto u operaciones de copia. Si necesita eliminar físicamente el contenido del PDF, establecer el CropBox no es suficiente: tendría que reconstruir la página creando un documento nuevo, extrayendo el contenido visible con page.CreateTemplate(), dibujándolo en una página nueva y guardando en un archivo nuevo.


Véase también

Both pages cropped by a 60-point margin, removing the outer white margin and frame

PDFs enthalten oft mehr Leerraum als nötig – gescannte Dokumente mit dicken Rändern, technische Zeichnungen mit großzügigen Rändern oder Rechnungen, bei denen nur die mittlere Tabelle von Bedeutung ist. Das Zuschneiden der Seite ist die naheliegende Lösung, aber in einem browserbasierten Workflow ist das nicht ohne Weiteres umsetzbar. Desktop-Tools beeinträchtigen das Web-Erlebnis, und das Senden der Datei an einen Backend-Server wirft Datenschutz- und Compliance-Bedenken auf.

Hier kommt Spire.PDF for JavaScript ins Spiel. Es läuft auf WebAssembly und arbeitet vollständig im Browser, wobei PDFs über ein virtuelles Dateisystem (VFS) geladen und gespeichert werden – ohne Server-Roundtrip. Sie können den sichtbaren Bereich jeder Seite programmatisch festlegen und den Benutzer das zugeschnittene Ergebnis herunterladen lassen – alles clientseitig. Hinweise zur Installation und Projekteinrichtung finden Sie unter Spire.PDF for JavaScript in ein React-Projekt integrieren. Die folgenden Beispiele setzen voraus, dass Spire.PDF installiert und das WebAssembly-Modul initialisiert ist.


CropBox und MediaBox: Zwei Seitenboxen erklärt

Jede PDF-Seite wird durch zwei Rechtecke definiert, und das Verständnis des Unterschieds zwischen ihnen ist unerlässlich, bevor Sie mit dem Zuschneiden beginnen.

MediaBox beschreibt die physischen Abmessungen der Seite – sozusagen das vollständige Blatt Papier. Sie ist die äußerste Begrenzung und definiert den Koordinatenraum, in dem alle Inhalte platziert werden. Eine Standard-A4-Seite hat eine MediaBox von etwa 595 × 842 Punkten.

CropBox definiert, was der Betrachter tatsächlich anzeigt. Sie ist ein Teilbereich der MediaBox, und alle Inhalte, die außerhalb der CropBox liegen, werden vor der Ansicht verborgen. Standardmäßig entspricht die CropBox der MediaBox, weshalb Sie normalerweise die gesamte Seite sehen. Wenn Sie die CropBox verkleinern, teilen Sie dem PDF-Reader im Grunde mit: "Zeigen Sie nur diesen Teil der Seite an."

Die zentrale Erkenntnis beim Zuschneiden ist, dass die CropBox von der MediaBox abgeleitet wird. Sie lesen die vollständigen Seitenabmessungen aus page.MediaBox.Width und page.MediaBox.Height, berechnen dann ein kleineres Rechteck – um den gewünschten Rand auf jeder Seite eingerückt – und weisen es page.CropBox zu. Die x- und y-Koordinaten der CropBox werden von der oberen linken Ecke der Seite aus gemessen, und width und height bestimmen, wie viel von der Seite beibehalten wird.


Alle Seiten mit einem einheitlichen Rand zuschneiden

Das häufigste Szenario ist, auf jede Seite eines Dokuments dieselbe Randreduzierung anzuwenden. Sie iterieren über doc.Pages, lesen die MediaBox jeder Seite und legen eine CropBox fest, die auf allen vier Seiten um eine feste Anzahl von Punkten eingerückt ist.

Das folgende Beispiel schneidet 60 Punkte von jedem Rand jeder Seite ab – genug, um einen breiten weißen Rahmen oder einen unerwünschten Rand zu entfernen:

function App() {
  const cropPdfPage = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be cropped into the VFS
    const inputFileName = 'ToCrop.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Trim 60 points off every side
    const margin = 60;

    for (let i = 0; i < doc.Pages.Count; i++) {
      const page = doc.Pages.get_Item(i);

      // MediaBox gives the full extent of the page, from which the crop box is derived
      // (x and y are measured from the top-left corner of the page)
      const width = page.MediaBox.Width;
      const height = page.MediaBox.Height;

      page.CropBox = new pdfModule.RectangleF({
        x: margin,
        y: margin,
        width: width - margin * 2,
        height: height - margin * 2,
      });
    }

    const outputFileName = 'CropByMargins.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Crop PDF Pages</h1>
      <button onClick={cropPdfPage}>
        Crop PDF
      </button>
    </div>
  );
}

export default App;

Beide Seiten werden um einen Rand von 60 Punkten zugeschnitten, sodass der weiße Rand und der Rahmen um die Seite entfernt werden:

Both pages cropped by a 60-point margin, removing the outer white margin and frame

Passen Sie den Wert margin an, um zu steuern, wie stark die Seite beschnitten wird. Ein größerer Wert entfernt mehr Leerraum; ein kleinerer Wert führt einen subtileren Beschnitt durch. Da der Rand gleichmäßig auf alle vier Seiten angewendet wird, eignet sich dieser Ansatz am besten, wenn der unerwünschte Rand an jeder Kante ungefähr gleich groß ist – was bei gescannten Dokumenten und exportierten Zeichnungen typischerweise der Fall ist.


Zuschneiden einer einzelnen Seite vs. des gesamten Dokuments

Die Schleife im vorherigen Abschnitt wendet denselben Beschnitt auf jede Seite an. Aber CropBox ist eine Eigenschaft auf Seitenebene – es gibt keine dokumentsweite Zuschnitt-Schnittstelle. Die Schleife stellt lediglich sicher, dass jede Seite denselben Rand erhält. Dieser Unterschied ist wichtig, wenn Ihre Seiten unterschiedliche Anforderungen haben.

Wann das gesamte Dokument zugeschnitten werden sollte: Verwenden Sie den Schleifenansatz, wenn alle Seiten dasselbe Problem aufweisen – zum Beispiel ein gescannter Stapel, bei dem jede Seite denselben Scanner-Rand hat, oder ein mehrseitiges Zeichnungsset, das mit identischen Rändern exportiert wurde. Ein einziger margin-Wert hält den Code einfach und das Ergebnis konsistent.

Wann eine einzelne Seite zugeschnitten werden sollte: Lassen Sie die Schleife weg und weisen Sie die CropBox direkt der Zielseite zu. Das ist nützlich, wenn nur eine Seite beschnitten werden muss – zum Beispiel eine Titelseite mit einem überdimensionierten Logo oder eine Rechnung, bei der nur der Tabellenbereich auf Seite 2 sichtbar sein soll. Sie können auch unterschiedliche Zuschnittwerte auf verschiedene Seiten anwenden, indem Sie seitenspezifische Logik innerhalb der Schleife kombinieren.

So schneiden Sie nur die erste Seite zu und behalten einen 400 × 500 Punkte großen Block, der an Position (80, 80) von der oberen linken Ecke aus beginnt:

// Crop page 1 only: keep a 400 x 500 point block starting at (80, 80) from the top-left corner
const page = doc.Pages.get_Item(0);
page.CropBox = new pdfModule.RectangleF({ x: 80, y: 80, width: 400, height: 500 });

Die Werte x und y geben an, wo der sichtbare Bereich beginnt, gemessen von der oberen linken Ecke der Seite. Die Werte width und height definieren die Größe dieses Bereichs. Alles außerhalb dieses Rechtecks wird vor dem Betrachter verborgen.


Weiches Zuschneiden: Was mit dem Inhalt passiert

Es gibt ein wichtiges Detail zu CropBox, das leicht übersehen wird: Sie führt einen weichen Beschnitt durch, keinen harten.

Wenn Sie die CropBox festlegen, ändern Sie die sichtbare Begrenzung der Seite – das Rechteck, das PDF-Betrachter anzeigen und das Drucker als Seitenbereich verwenden. Aber der Inhalt, der außerhalb dieser Begrenzung liegt, wird nicht aus der Datei entfernt. Er ist weiterhin vorhanden, nur verborgen. Das hat zwei praktische Konsequenzen:

  1. Die Dateigröße nimmt nicht ab. Der weggeschnittene Text, die Bilder und Vektorgrafiken verbleiben im PDF-Datenstrom. Wenn Ihr Ziel darin besteht, die Dateigröße durch das Beschneiden von Rändern zu reduzieren, erreichen Sie das durch das alleinige Festlegen der CropBox nicht.

  2. Zugeschnittener Inhalt ist weiterhin durchsuchbar. Text außerhalb der sichtbaren CropBox kann weiterhin von Suchfunktionen gefunden und von Benutzern kopiert werden, die wissen, wie sie über den sichtbaren Bereich hinaus markieren. Das ist beim Beschneiden von Rändern normalerweise in Ordnung, bedeutet aber, dass Zuschneiden keine Möglichkeit ist, sensible Informationen zu schwärzen oder sicher zu entfernen.

Wenn Sie Inhalte wirklich entfernen müssen – zur Schwärzung, zur Reduzierung der Dateigröße oder um sicherzustellen, dass verborgener Text nicht wiederhergestellt werden kann –, müssen Sie die Seite neu aufbauen. Der Ansatz besteht darin, ein neues Dokument zu erstellen, den sichtbaren Inhalt mit page.CreateTemplate() aus der zugeschnittenen Seite zu extrahieren, ihn auf eine neue Seite im neuen Dokument zu zeichnen und das Ergebnis zu speichern. Dies erzeugt einen harten Beschnitt, bei dem der außerhalb liegende Inhalt nicht mehr in der Datei vorhanden ist.

Für die meisten Anwendungsfälle zum Beschneiden von Rändern und zum Entfernen von Leerraum ist jedoch das weiche Zuschneiden mit CropBox genau das Richtige: Es ist schnell, einfach und liefert ein optisch sauberes Ergebnis, ohne den Aufwand eines Neuaufbaus des Dokuments.


FAQ

Kann ich eine einzelne Seite anstelle des gesamten Dokuments zuschneiden?

Ja. Da CropBox eine Eigenschaft pro Seite ist, gibt es keine integrierte Methode zum "Zuschneiden des gesamten Dokuments" – die Schleife im Hauptbeispiel ist nur eine Bequemlichkeit, um denselben Rand auf jede Seite anzuwenden. Um nur eine Seite zuzuschneiden, überspringen Sie die Schleife und weisen Sie die CropBox direkt dieser Seite zu:

// Crop page 1 only: keep a 400 x 500 point block starting at (80, 80) from the top-left corner
const page = doc.Pages.get_Item(0);
page.CropBox = new pdfModule.RectangleF({ x: 80, y: 80, width: 400, height: 500 });

Wie mache ich einen Zuschnitt rückgängig?

Das Festlegen von CropBox ändert die Seitenbox direkt, und das Dokument speichert die vorherige Box nicht. Sie könnten erwarten, dass das Zuweisen von page.MediaBox zurück an page.CropBox die ursprüngliche Ansicht wiederherstellt, aber das funktioniert nicht wie beabsichtigt – die Koordinaten werden relativ zum Ursprung des aktuell sichtbaren Bereichs angewendet, sodass sich nur die Abmessungen ändern, während der Ursprung unverändert bleibt. Nach einem Zuschnitt um 60 Punkte beginnt der sichtbare Bereich auch nach dem erneuten Zuweisen der MediaBox-Abmessungen weiterhin bei (60, 60).

Die einfachste Lösung ist, die Originaldatei neu zu laden, wenn Sie sie noch haben. Wenn nur die zugeschnittene Datei verfügbar ist, können Sie den Ursprung mithilfe negativer Offsets zurück in die obere linke Ecke verschieben und die vollständigen Seitenabmessungen wiederherstellen:

// Undo when the crop offset was (offsetX, offsetY)
page.CropBox = new pdfModule.RectangleF({
  x: -offsetX,
  y: -offsetY,
  width: page.MediaBox.Width,
  height: page.MediaBox.Height,
});

Die Datei wurde nicht kleiner und der zugeschnittene Inhalt ist weiterhin durchsuchbar – warum?

Dies ist das erwartete Verhalten. CropBox führt einen weichen Beschnitt durch: Sie ändert nur die sichtbare Begrenzung der Seite, während Inhalt außerhalb dieser Begrenzung in der Datei verbleibt und weiterhin durch Textsuche oder Kopiervorgänge gefunden werden kann. Wenn Sie den Inhalt physisch aus dem PDF entfernen müssen, reicht das Festlegen der CropBox nicht aus – Sie müssten die Seite neu aufbauen, indem Sie ein neues Dokument erstellen, den sichtbaren Inhalt mit page.CreateTemplate() extrahieren, ihn auf eine neue Seite zeichnen und in einer neuen Datei speichern.


Siehe auch

Обе страницы обрезаны с отступом 60 пунктов; внешнее белое поле и рамка удалены

PDF-файлы часто содержат больше пустого пространства, чем необходимо — отсканированные документы с толстыми границами, инженерные чертежи с большими полями или счета, где важна только таблица по центру. Обрезка страницы — естественное решение, но выполнить её в браузерном рабочем процессе непросто. Настольные программы нарушают работу в веб-среде, а отправка файла на серверную часть вызывает вопросы конфиденциальности и соответствия требованиям.

Здесь на помощь приходит Spire.PDF for JavaScript. Он работает на WebAssembly и полностью функционирует внутри браузера, загружая и сохраняя PDF-файлы через виртуальную файловую систему (VFS) без обращения к серверу. Вы можете программно задать видимую область каждой страницы и позволить пользователю скачать обрезанный результат — всё на стороне клиента. Информацию об установке и настройке проекта см. в разделе Интеграция Spire.PDF for JavaScript в проект React. В примерах ниже предполагается, что Spire.PDF установлен и модуль WebAssembly инициализирован.


CropBox и MediaBox: объяснение двух границ страницы

Каждая страница PDF определяется двумя прямоугольниками, и понимание разницы между ними необходимо перед началом обрезки.

MediaBox описывает физические размеры страницы — полный лист бумаги, так сказать. Это внешняя граница, и она определяет систему координат, в которой размещается всё содержимое. Стандартная страница A4 имеет MediaBox примерно 595 × 842 пункта.

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

Ключевой момент для обрезки: CropBox производен от MediaBox. Вы считываете полные размеры страницы из page.MediaBox.Width и page.MediaBox.Height, затем вычисляете меньший прямоугольник — с отступом на нужное значение с каждой стороны — и присваиваете его page.CropBox. Координаты x и y CropBox отсчитываются от верхнего левого угла страницы, а width и height определяют, какая часть страницы сохраняется.


Обрезка всех страниц с одинаковым отступом

Наиболее распространённый сценарий — применение одинакового уменьшения отступа к каждой странице документа. Вы перебираете doc.Pages, считываете MediaBox каждой страницы и задаёте CropBox с отступом на фиксированное число пунктов со всех четырёх сторон.

В приведённом ниже примере с каждого края каждой страницы срезается 60 пунктов — этого достаточно, чтобы удалить широкую белую рамку или ненужную границу:

function App() {
  const cropPdfPage = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be cropped into the VFS
    const inputFileName = 'ToCrop.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Trim 60 points off every side
    const margin = 60;

    for (let i = 0; i < doc.Pages.Count; i++) {
      const page = doc.Pages.get_Item(i);

      // MediaBox gives the full extent of the page, from which the crop box is derived
      // (x and y are measured from the top-left corner of the page)
      const width = page.MediaBox.Width;
      const height = page.MediaBox.Height;

      page.CropBox = new pdfModule.RectangleF({
        x: margin,
        y: margin,
        width: width - margin * 2,
        height: height - margin * 2,
      });
    }

    const outputFileName = 'CropByMargins.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Crop PDF Pages</h1>
      <button onClick={cropPdfPage}>
        Crop PDF
      </button>
    </div>
  );
}

export default App;

Обе страницы обрезаны с отступом 60 пунктов, поэтому белое поле и рамка вокруг страницы удалены:

Обе страницы обрезаны с отступом 60 пунктов; внешнее белое поле и рамка удалены

Настройте значение margin, чтобы управлять степенью обрезки страницы. Большее значение удаляет больше пустого пространства; меньшее — выполняет более тонкую обрезку. Поскольку отступ применяется единообразно ко всем четырём сторонам, этот подход лучше всего работает, когда нежелательная граница примерно одинакова по всем краям — что обычно бывает со сканированными документами и экспортированными чертежами.


Обрезка одной страницы и всего документа

Цикл в предыдущем разделе применяет одинаковую обрезку к каждой странице. Но CropBox — это свойство уровня страницы, не существует интерфейса обрезки для всего документа. Цикл просто гарантирует, что каждая страница получает одинаковый отступ. Это различие важно, когда у ваших страниц разные потребности.

Когда обрезать весь документ: Используйте подход с циклом, когда у всех страниц одна и та же проблема — например, отсканированный пакет, где каждая страница имеет одинаковую границу сканера, или многостраничный набор чертежей, экспортированный с одинаковыми полями. Единое значение margin упрощает код и делает результат согласованным.

Когда обрезать одну страницу: Уберите цикл и назначьте CropBox непосредственно целевой странице. Это полезно, когда нужно обрезать только одну страницу — например, титульную страницу с слишком большим логотипом или счёт, где должна быть видна только область таблицы на странице 2. Вы также можете применять разные значения обрезки к разным страницам, комбинируя логику для каждой страницы внутри цикла.

Вот как обрезать только первую страницу, сохранив блок размером 400 × 500 пунктов, начинающийся в позиции (80, 80) от верхнего левого угла:

// Crop page 1 only: keep a 400 x 500 point block starting at (80, 80) from the top-left corner
const page = doc.Pages.get_Item(0);
page.CropBox = new pdfModule.RectangleF({ x: 80, y: 80, width: 400, height: 500 });

Значения x и y указывают, где начинается видимая область, отсчитываясь от верхнего левого угла страницы. Значения width и height определяют размер этой области. Всё, что находится за пределами этого прямоугольника, скрыто от просмотрщика.


Мягкая обрезка: что происходит с содержимым

Есть важная деталь о CropBox, которую легко упустить: он выполняет мягкую обрезку, а не жёсткую.

Когда вы задаёте CropBox, вы меняете видимую границу страницы — прямоугольник, который отображают программы просмотра PDF и используют принтеры как область страницы. Но содержимое, выходящее за эту границу, не удаляется из файла. Оно по-прежнему там, просто скрыто. Это имеет два практических последствия:

  1. Размер файла не уменьшается. Обрезанные текст, изображения и векторная графика остаются в потоке данных PDF. Если ваша цель — уменьшить размер файла за счёт обрезки полей, одна только установка CropBox этого не даст.

  2. Обрезанное содержимое всё ещё доступно для поиска. Текст за пределами видимого CropBox всё ещё может быть найден функциями поиска и скопирован пользователями, которые знают, как выделять за пределами видимой области. Обычно это нормально для обрезки полей, но это означает, что обрезка не является способом редактирования или безопасного удаления конфиденциальной информации.

Если вам нужно действительно удалить содержимое — для редактирования, уменьшения размера файла или гарантии того, что скрытый текст невозможно восстановить — необходимо пересобрать страницу. Подход таков: создайте новый документ, извлеките видимое содержимое из обрезанной страницы с помощью page.CreateTemplate(), нарисуйте его на новой странице в новом документе и сохраните результат. Это даёт жёсткую обрезку, при которой содержимое за пределами границ больше не существует в файле.

Однако для большинства случаев обрезки полей и удаления пустого пространства мягкая обрезка с помощью CropBox — именно то, что нужно: она быстрая, простая и даёт визуально чистый результат без необходимости пересобирать документ.


Часто задаваемые вопросы

Можно ли обрезать одну страницу вместо всего документа?

Да. Поскольку CropBox — это свойство отдельной страницы, встроенного метода "обрезать весь документ" нет — цикл в основном примере просто удобен для применения одинакового отступа ко всем страницам. Чтобы обрезать только одну страницу, пропустите цикл и назначьте CropBox напрямую этой странице:

// Crop page 1 only: keep a 400 x 500 point block starting at (80, 80) from the top-left corner
const page = doc.Pages.get_Item(0);
page.CropBox = new pdfModule.RectangleF({ x: 80, y: 80, width: 400, height: 500 });

Как отменить обрезку?

Установка CropBox изменяет границу страницы на месте, и документ не сохраняет предыдущую границу. Можно ожидать, что присваивание page.MediaBox обратно page.CropBox восстановит исходный вид, но это не работает как задумано — координаты применяются относительно начала текущей видимой области, поэтому изменяются только размеры, а начало остаётся фиксированным. После обрезки на 60 пунктов переназначение размеров MediaBox по-прежнему оставляет видимую область начинающейся с (60, 60).

Самое простое решение — заново загрузить исходный файл, если он у вас ещё есть. Если доступен только обрезанный файл, можно сдвинуть начало обратно к верхнему левому углу с помощью отрицательных смещений и восстановить полные размеры страницы:

// Undo when the crop offset was (offsetX, offsetY)
page.CropBox = new pdfModule.RectangleF({
  x: -offsetX,
  y: -offsetY,
  width: page.MediaBox.Width,
  height: page.MediaBox.Height,
});

Файл не стал меньше, а обрезанное содержимое всё ещё доступно для поиска — почему?

Это ожидаемое поведение. CropBox выполняет мягкую обрезку: он изменяет только видимую границу страницы, тогда как содержимое за её пределами остаётся в файле и всё ещё может быть найдено текстовым поиском или скопировано. Если вам нужно физически удалить содержимое из PDF, установки CropBox недостаточно — потребуется пересобрать страницу, создав новый документ, извлекая видимое содержимое с помощью page.CreateTemplate(), рисуя его на новой странице и сохраняя в новый файл.


См. также

O resultado é gravado em um arquivo de texto que registra o número total de páginas do documento

Um único número inteiro — o total de páginas de um PDF — está por trás de um número surpreendente de decisões do mundo real: limites de upload, estimativa de papel para impressão, operações de divisão, barras de progresso. A maioria das bibliotecas de renderização de PDF apenas desenha páginas e não expõe uma contagem simples, e enviar o arquivo para um backend só para ler a contagem de páginas adiciona latência e preocupações com privacidade.

O Spire.PDF for JavaScript carrega e analisa documentos PDF diretamente no navegador por meio de WebAssembly, de modo que o arquivo nunca sai do cliente. A contagem de páginas está disponível como uma única propriedade — sem loops, sem idas e voltas ao servidor, sem soluções alternativas de renderização. Este artigo aborda como obter essa contagem e três preocupações práticas: distinguir a contagem física de páginas dos rótulos exibidos, lidar com arquivos protegidos por senha e evitar erros de off-by-one ao iterar pelas páginas.

Para instalação e configuração do projeto, consulte Integrar o Spire.PDF for JavaScript em um Projeto React. Os exemplos abaixo pressupõem que o Spire.PDF está instalado e que o módulo WebAssembly foi inicializado.


Obter a Contagem de Páginas de um Documento PDF

Depois que um objeto PdfDocument carrega um arquivo, sua propriedade Pages expõe a coleção de páginas, e a propriedade Count dessa coleção retorna o número total de páginas. Não é necessário iterar pelas páginas individualmente — a contagem fica disponível imediatamente após o carregamento.

O componente React a seguir demonstra o fluxo de trabalho completo: buscar o PDF para o sistema de arquivos virtual, criar um PdfDocument, carregar o arquivo, ler Pages.Count e gravar o resultado em um arquivo de texto para download.

function App() {
  const getPageCount = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be counted into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Pages is the document's page collection; Count is the total page count
    const pageCount = doc.Pages.Count;

    // Write the result to the VFS
    const outputFileName = 'PageCountResult.txt';
    const report = `Document: ${inputFileName}\r\nTotal pages: ${pageCount}`;
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, report);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get PDF Page Count</h1>
      <button onClick={getPageCount}>
        Count Pages
      </button>
    </div>
  );
}

export default App;

O resultado é gravado em um arquivo de texto que registra o número total de páginas do documento:

O resultado é gravado em um arquivo de texto que registra o número total de páginas do documento

Em uma aplicação de produção, você normalmente usaria o valor de pageCount diretamente em vez de gravá-lo em um arquivo — por exemplo, para validar um upload, definir o limite de um loop ou exibir metadados na interface. A abordagem de gerar um arquivo de saída mostrada aqui é útil para testes e demonstrações.


Contagem Física de Páginas vs. Rótulos de Página

Eis uma situação que pega os desenvolvedores de surpresa: você lê Pages.Count e obtém 12, mas o leitor de PDF na tela do usuário mostra a última página como "página 8". Qual número está correto?

Ambos estão — eles medem coisas diferentes. Pages.Count retorna o número de páginas físicas do documento, simplesmente. Já o número exibido por um leitor vem dos rótulos de página (a entrada /PageLabels na especificação PDF). Os rótulos de página são uma camada de apresentação que os editores usam para controlar como os números de página aparecem para o leitor. Um editor de livros pode excluir a capa da numeração, usar algarismos romanos (i, ii, iii) para o material preliminar e reiniciar o corpo do texto em 1. Depois de tudo isso, a quinta página física pode ser exibida como iii ou 1, dependendo de como os rótulos estão configurados.

Essa distinção importa quando sua aplicação precisa mostrar aos usuários um número de página que corresponda ao que eles veem em seu leitor. Se você exibir Pages.Count como a "página atual", ele não corresponderá à numeração do leitor sempre que houver rótulos de página em uso.

Quando você precisar do rótulo exibido em vez do índice físico, leia a propriedade PageLabel no objeto de página individual:

// What label the 5th physical page displays in a reader
const page = doc.Pages.get_Item(4);
console.log(page.PageLabel);

Observe o índice baseado em zero: get_Item(4) recupera a quinta página física. Quando o documento não tem rótulos de página configurados, PageLabel retorna uma string vazia. Nesse caso comum, o número exibido corresponde à ordem física das páginas, então Count é o valor que você quer.

Uma forma prática de lidar com os dois cenários é verificar PageLabel primeiro e recorrer ao índice físico quando ele estiver vazio. Isso garante que sua aplicação mostre um número de página que sempre corresponde ao que o usuário vê, independentemente de o documento usar rótulos personalizados.


Contar Páginas em um PDF Criptografado

Muitos PDFs em ambientes corporativos são protegidos por uma senha de abertura — uma medida de segurança que impede a leitura do documento sem a credencial correta. Se você tentar carregar esse arquivo com uma chamada simples de LoadFromFile, o runtime do WASM lança um erro antes mesmo de Pages.Count ser alcançado:

Can not open an encrypted document. The password is invalid.

Isso acontece no momento do carregamento, não no ponto em que você lê a contagem de páginas. O conteúdo do documento — incluindo sua estrutura de páginas — está criptografado, então a biblioteca não consegue analisá-lo sem a senha. Não há como contar páginas sem primeiro desbloquear o documento.

A solução é direta: passe a senha de abertura como segundo argumento para LoadFromFile. Depois que o documento é desbloqueado, a contagem de páginas fica disponível exatamente como em um arquivo não criptografado:

// The second argument is the open password
doc.LoadFromFile(inputFileName, 'spire123');
const pageCount = doc.Pages.Count;

Em uma aplicação real, você normalmente coletaria a senha do usuário por meio de um campo de formulário e a passaria dinamicamente, em vez de codificá-la diretamente. Se o usuário digitar a senha errada, o mesmo erro será lançado — por isso, envolver a chamada de LoadFromFile em um bloco try/catch e exibir uma mensagem amigável de "senha incorreta" é uma boa prática.

Mais uma coisa que vale destacar: essa senha é a senha de abertura (também chamada de senha de usuário), que controla quem pode visualizar o documento. Um PDF também pode ter uma senha de permissões (senha de proprietário) que restringe edição, impressão ou cópia sem bloquear a visualização. Para fins de contagem de páginas, apenas a senha de abertura é relevante — uma vez que o documento está aberto, Pages.Count funciona independentemente das restrições de permissão.


Usar a Contagem de Páginas como Limite de Loop

Depois de obter a contagem de páginas, um próximo passo natural é percorrer cada página — para extrair texto, renderizar miniaturas, dividir o documento ou aplicar alguma transformação. É aí que surge um bug sutil, mas comum: usar Count como limite superior inclusivo.

A coleção Pages é baseada em zero, o que significa que os índices válidos vão de 0 a Count - 1. Se a condição do loop for escrita com <= em vez de <, a iteração final tentará acessar a página no índice Count, que não existe. O runtime do WASM encapsula a ArgumentOutOfRangeException subjacente do .NET como um Error do JavaScript com uma mensagem como:

ArgumentOutOfRange_IndexMustBeLess Arg_ParamName_Name, index

Como a propriedade name do erro é apenas o genérico Error, você não consegue distingui-lo apenas pelo nome — é preciso comparar a string da mensagem se quiser tratá-lo especificamente.

O loop correto usa <, de modo que o último índice acessado seja Count - 1:

// The upper bound is Count - 1, so use < rather than <=
for (let i = 0; i < doc.Pages.Count; i++) {
  const page = doc.Pages.get_Item(i);
}

Esse padrão de off-by-one é uma das fontes mais frequentes de erros em tempo de execução ao trabalhar com coleções de páginas. É fácil não percebê-lo nos testes se seus documentos de exemplo tiverem apenas uma ou duas páginas — o erro só aparece na iteração final, então um documento de uma única página não o acionará de forma alguma. Teste sempre a lógica do loop com um documento que tenha pelo menos três páginas para garantir que a condição de limite esteja correta.


Veja Também

The result is written to a text file that records the document's total page count

PDF의 총 페이지 수라는 단일 정수는 업로드 제한, 인쇄용 용지 추산, 분할 작업, 진행률 표시줄 등 현실 세계의 놀라울 정도로 많은 결정 뒤에 자리 잡고 있습니다. 대부분의 PDF 렌더링 라이브러리는 페이지만 그릴 뿐 간단한 페이지 수를 노출하지 않으며, 단지 페이지 수를 읽기 위해 파일을 백엔드로 보내는 것은 지연 시간과 개인정보 보호 문제를 야기합니다.

JavaScript용 Spire.PDF는 WebAssembly를 통해 브라우저에서 직접 PDF 문서를 로드하고 구문 분석하므로 파일이 클라이언트를 떠나지 않습니다. 페이지 수는 단일 속성으로 제공됩니다. 루프도, 서버 왕복도, 렌더링 우회도 필요 없습니다. 이 문서에서는 해당 페이지 수를 가져오는 방법과 세 가지 실용적인 고려 사항을 살펴봅니다: 실제 페이지 수와 표시 레이블 구분하기, 암호로 보호된 파일 처리하기, 페이지를 반복할 때 오프바이원 오류 피하기.

설치 및 프로젝트 설정은 React 프로젝트에 JavaScript용 Spire.PDF 통합하기를 참조하세요. 아래 예제는 Spire.PDF가 설치되어 있고 WebAssembly 모듈이 초기화되었다고 가정합니다.


PDF 문서의 페이지 수 가져오기

PdfDocument 개체가 파일을 로드하면 Pages 속성이 페이지 컬렉션을 노출하고, 해당 컬렉션의 Count 속성이 총 페이지 수를 반환합니다. 페이지를 개별적으로 반복할 필요가 없으며, 로드 직후 페이지 수를 사용할 수 있습니다.

다음 React 컴포넌트는 전체 워크플로를 보여줍니다: PDF를 가상 파일 시스템으로 가져오고, PdfDocument를 만들고, 파일을 로드하고, Pages.Count를 읽고, 결과를 다운로드 가능한 텍스트 파일에 작성합니다.

function App() {
  const getPageCount = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be counted into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Pages is the document's page collection; Count is the total page count
    const pageCount = doc.Pages.Count;

    // Write the result to the VFS
    const outputFileName = 'PageCountResult.txt';
    const report = `Document: ${inputFileName}\r\nTotal pages: ${pageCount}`;
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, report);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get PDF Page Count</h1>
      <button onClick={getPageCount}>
        Count Pages
      </button>
    </div>
  );
}

export default App;

결과는 문서의 총 페이지 수를 기록하는 텍스트 파일에 작성됩니다:

The result is written to a text file that records the document's total page count

프로덕션 애플리케이션에서는 일반적으로 pageCount 값을 파일에 작성하기보다 직접 사용합니다. 예를 들어 업로드를 검증하거나, 루프 경계를 설정하거나, UI에 메타데이터를 표시할 때 사용합니다. 여기서 보여 주는 파일 출력 방식은 테스트와 데모에 유용합니다.


실제 페이지 수와 페이지 레이블

개발자를 당황하게 만드는 상황이 있습니다. Pages.Count를 읽었는데 12가 나오지만, 사용자 화면의 PDF 리더는 마지막 페이지를 "8페이지"로 표시합니다. 어느 숫자가 맞을까요?

둘 다 맞습니다. 서로 다른 것을 측정하기 때문입니다. Pages.Count는 문서의 실제 페이지 수를 그대로 반환합니다. 반면 리더에 표시되는 숫자는 페이지 레이블(PDF 사양의 /PageLabels 항목)에서 나옵니다. 페이지 레이블은 게시자가 독자에게 페이지 번호가 표시되는 방식을 제어하기 위해 사용하는 표현 계층입니다. 책 출판사는 표지를 번호 매기기에서 제외하고, 앞부분에 로마 숫자(i, ii, iii)를 사용하고, 본문을 1부터 다시 시작할 수 있습니다. 그렇게 하면 다섯 번째 실제 페이지는 레이블 구성 방식에 따라 iii 또는 1로 표시될 수 있습니다.

이 구분은 애플리케이션이 사용자에게 리더에서 보는 것과 일치하는 페이지 번호를 표시해야 할 때 중요합니다. Pages.Count를 "현재 페이지"로 표시하면 페이지 레이블이 사용되는 경우 리더의 번호 매기기와 일치하지 않습니다.

실제 인덱스가 아니라 표시되는 레이블이 필요할 때는 개별 페이지 개체의 PageLabel 속성을 읽습니다:

// What label the 5th physical page displays in a reader
const page = doc.Pages.get_Item(4);
console.log(page.PageLabel);

0부터 시작하는 인덱스에 유의하세요. get_Item(4)는 다섯 번째 실제 페이지를 가져옵니다. 문서에 구성된 페이지 레이블이 없으면 PageLabel은 빈 문자열을 반환합니다. 이러한 일반적인 경우 표시되는 숫자가 실제 페이지 순서와 일치하므로 Count가 원하는 값입니다.

두 시나리오를 모두 처리하는 실용적인 방법은 먼저 PageLabel을 확인하고 비어 있으면 실제 인덱스로 대체하는 것입니다. 이렇게 하면 문서가 사용자 지정 레이블을 사용하든 아니든 애플리케이션이 항상 사용자가 보는 것과 일치하는 페이지 번호를 제공할 수 있습니다.


암호화된 PDF의 페이지 수 세기

비즈니스 환경의 많은 PDF는 열기 암호로 보호됩니다. 이는 올바른 자격 증명 없이는 문서를 읽을 수 없게 하는 보안 조치입니다. 이러한 파일을 일반 LoadFromFile 호출로 로드하려고 하면 WASM 런타임이 Pages.Count에 도달하기도 전에 오류를 throw합니다:

암호화된 문서를 열 수 없습니다. 암호가 올바르지 않습니다.

이 문제는 페이지 수를 읽는 시점이 아니라 로드 시점에 발생합니다. 문서의 내용(페이지 구조 포함)이 암호화되어 있으므로 라이브러리는 암호 없이 구문 분석할 수 없습니다. 먼저 문서의 잠금을 해제하지 않고는 페이지 수를 셀 방법이 없습니다.

해결 방법은 간단합니다. 열기 암호를 LoadFromFile의 두 번째 인수로 전달하세요. 문서의 잠금이 해제되면 암호화되지 않은 파일과 마찬가지로 페이지 수를 사용할 수 있습니다:

// The second argument is the open password
doc.LoadFromFile(inputFileName, 'spire123');
const pageCount = doc.Pages.Count;

실제 애플리케이션에서는 일반적으로 양식 필드를 통해 사용자로부터 암호를 수집하고 하드코딩하는 대신 동적으로 전달합니다. 사용자가 잘못된 암호를 입력하면 동일한 오류가 throw되므로 LoadFromFile 호출을 try/catch 블록으로 감싸고 친숙한 "잘못된 암호" 메시지를 표시하는 것이 좋습니다.

한 가지 더 주목할 점은 이 암호가 열기 암호(사용자 암호라고도 함)라는 것입니다. 이는 누가 문서를 볼 수 있는지 제어합니다. PDF에는 보기 차단 없이 편집, 인쇄 또는 복사를 제한하는 권한 암호(소유자 암호)가 있을 수도 있습니다. 페이지 수를 세는 목적에서는 열기 암호만 관련이 있습니다. 문서가 열리면 Pages.Count는 권한 제한과 관계없이 작동합니다.


페이지 수를 루프 경계로 사용하기

페이지 수를 구한 후 자연스러운 다음 단계는 모든 페이지를 반복하는 것입니다. 텍스트 추출, 썸네일 렌더링, 문서 분할 또는 일부 변환 적용 등이 있습니다. 여기에서 미묘하지만 흔한 버그가 나타납니다. Count를 포함 상한으로 사용하는 것입니다.

Pages 컬렉션은 0부터 시작하는 인덱스를 사용하므로 유효한 인덱스는 0부터 Count - 1까지입니다. 루프 조건을 < 대신 <=로 작성하면 마지막 반복에서 존재하지 않는 인덱스 Count의 페이지에 접근하려고 합니다. WASM 런타임은 기본 .NET ArgumentOutOfRangeException을 다음과 같은 메시지의 JavaScript Error로 래핑합니다:

ArgumentOutOfRange_IndexMustBeLess Arg_ParamName_Name, index

오류의 name 속성이 일반 Error일 뿐이므로 이름만으로 구분할 수 없습니다. 특별히 처리하려면 메시지 문자열을 일치시켜야 합니다.

올바른 루프는 <를 사용하므로 마지막으로 접근하는 인덱스는 Count - 1입니다:

// The upper bound is Count - 1, so use < rather than <=
for (let i = 0; i < doc.Pages.Count; i++) {
  const page = doc.Pages.get_Item(i);
}

이 오프바이원 패턴은 페이지 컬렉션으로 작업할 때 가장 흔한 런타임 오류 원인 중 하나입니다. 샘플 문서가 우연히 한두 페이지뿐이라면 테스트에서 놓치기 쉽습니다. 오류는 마지막 반복에서만 나타나므로 한 페이지 문서에서는 전혀 발생하지 않습니다. 경계 조건이 올바른지 확인하려면 항상 최소 세 페이지가 있는 문서로 루프 로직을 테스트하세요.


참고 항목

Il risultato viene scritto in un file di testo che registra il numero totale di pagine del documento

Un singolo numero intero — il numero totale di pagine di un PDF — è alla base di un numero sorprendente di decisioni concrete: limiti di caricamento, stima della carta per la stampa, operazioni di suddivisione, barre di avanzamento. La maggior parte delle librerie di rendering PDF si limita a disegnare le pagine e non espone un semplice conteggio, e inviare il file a un backend solo per leggere il numero di pagine comporta latenza e problemi di privacy.

Spire.PDF for JavaScript carica e analizza i documenti PDF direttamente nel browser tramite WebAssembly, così il file non lascia mai il client. Il numero di pagine è disponibile come singola proprietà — nessun ciclo, nessun round-trip verso il server, nessuna soluzione alternativa basata sul rendering. Questo articolo illustra come recuperare tale conteggio e tre aspetti pratici: distinguere il numero di pagine fisiche dalle etichette visualizzate, gestire i file protetti da password ed evitare errori di off-by-one durante l'iterazione sulle pagine.

Per l'installazione e la configurazione del progetto, consulta Integrare Spire.PDF for JavaScript in un progetto React. Gli esempi seguenti presuppongono che Spire.PDF sia installato e che il modulo WebAssembly sia stato inizializzato.


Ottenere il numero di pagine di un documento PDF

Dopo che un oggetto PdfDocument ha caricato un file, la sua proprietà Pages espone la raccolta delle pagine e la proprietà Count di tale raccolta restituisce il numero totale di pagine. Non è necessario scorrere le pagine una per una — il conteggio è disponibile subito dopo il caricamento.

Il seguente componente React mostra l'intero flusso di lavoro: recupera il PDF nel file system virtuale, crea un PdfDocument, carica il file, legge Pages.Count e scrive il risultato in un file di testo scaricabile.

function App() {
  const getPageCount = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be counted into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Pages is the document's page collection; Count is the total page count
    const pageCount = doc.Pages.Count;

    // Write the result to the VFS
    const outputFileName = 'PageCountResult.txt';
    const report = `Document: ${inputFileName}\r\nTotal pages: ${pageCount}`;
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, report);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get PDF Page Count</h1>
      <button onClick={getPageCount}>
        Count Pages
      </button>
    </div>
  );
}

export default App;

Il risultato viene scritto in un file di testo che registra il numero totale di pagine del documento:

Il risultato viene scritto in un file di testo che registra il numero totale di pagine del documento

In un'applicazione di produzione, in genere useresti direttamente il valore pageCount invece di scriverlo in un file — ad esempio per convalidare un caricamento, impostare il limite di un ciclo o mostrare metadati nell'interfaccia utente. L'approccio con output su file mostrato qui è utile per i test e per scopi dimostrativi.


Numero di pagine fisiche vs etichette di pagina

Ecco una situazione che coglie di sorpresa gli sviluppatori: leggi Pages.Count e ottieni 12, ma il lettore PDF sullo schermo dell'utente mostra l'ultima pagina come "pagina 8". Quale numero è corretto?

Lo sono entrambi — misurano cose diverse. Pages.Count restituisce il numero di pagine fisiche del documento, in modo semplice e diretto. Il numero visualizzato da un lettore, invece, deriva dalle etichette di pagina (la voce /PageLabels nella specifica PDF). Le etichette di pagina sono un livello di presentazione che gli editori usano per controllare come i numeri di pagina appaiono al lettore. Un editore di libri potrebbe escludere la copertina dalla numerazione, usare numeri romani (i, ii, iii) per le pagine introduttive e ricominciare il corpo da 1. Dopo tutto questo, la quinta pagina fisica potrebbe apparire come iii o 1 a seconda di come sono configurate le etichette.

Questa distinzione è importante quando l'applicazione deve mostrare agli utenti un numero di pagina che corrisponda a quello che vedono nel loro lettore. Se visualizzi Pages.Count come "pagina corrente", non coinciderà con la numerazione del lettore ogni volta che sono in gioco le etichette di pagina.

Quando ti serve l'etichetta visualizzata anziché l'indice fisico, leggi la proprietà PageLabel sul singolo oggetto pagina:

// What label the 5th physical page displays in a reader
const page = doc.Pages.get_Item(4);
console.log(page.PageLabel);

Nota l'indice a base zero: get_Item(4) recupera la quinta pagina fisica. Quando il documento non ha etichette di pagina configurate, PageLabel restituisce una stringa vuota. In questo caso comune, il numero visualizzato corrisponde all'ordine fisico delle pagine, quindi Count è il valore che ti serve.

Un modo pratico per gestire entrambi gli scenari è controllare prima PageLabel e ricorrere all'indice fisico quando è vuoto. In questo modo la tua applicazione ottiene un numero di pagina che corrisponde sempre a ciò che vede l'utente, indipendentemente dal fatto che il documento utilizzi etichette personalizzate.


Contare le pagine in un PDF crittografato

Molti PDF negli ambienti aziendali sono protetti da una password di apertura — una misura di sicurezza che impedisce la lettura del documento senza le credenziali corrette. Se provi a caricare un file di questo tipo con una semplice chiamata LoadFromFile, il runtime WASM genera un errore prima ancora di arrivare a Pages.Count:

Impossibile aprire un documento crittografato. La password non è valida.

Ciò accade al momento del caricamento, non nel punto in cui leggi il numero di pagine. Il contenuto del documento — inclusa la sua struttura di pagine — è crittografato, quindi la libreria non può analizzarlo senza la password. Non c'è modo di contare le pagine senza prima sbloccare il documento.

La soluzione è semplice: passa la password di apertura come secondo argomento a LoadFromFile. Una volta sbloccato il documento, il numero di pagine è disponibile esattamente come per un file non crittografato:

// The second argument is the open password
doc.LoadFromFile(inputFileName, 'spire123');
const pageCount = doc.Pages.Count;

In un'applicazione reale, in genere raccoglieresti la password dall'utente tramite un campo di un modulo e la passeresti dinamicamente invece di inserirla nel codice. Se l'utente inserisce la password sbagliata, viene generato lo stesso errore — quindi racchiudere la chiamata LoadFromFile in un blocco try/catch e mostrare un messaggio amichevole di "password errata" è una buona pratica.

C'è un'altra cosa da notare: questa password è la password di apertura (chiamata anche password utente), che controlla chi può visualizzare il documento. Un PDF può avere anche una password di autorizzazione (password proprietario) che limita la modifica, la stampa o la copia senza bloccare la visualizzazione. Ai fini del conteggio delle pagine, è rilevante solo la password di apertura — una volta aperto il documento, Pages.Count funziona indipendentemente dalle restrizioni di autorizzazione.


Usare il numero di pagine come limite di un ciclo

Una volta ottenuto il numero di pagine, un passo successivo naturale è scorrere ogni pagina — per estrarre testo, generare anteprime, dividere il documento o applicare qualche trasformazione. È qui che compare un bug sottile ma comune: usare Count come limite superiore inclusivo.

La raccolta Pages è a base zero, il che significa che gli indici validi vanno da 0 a Count - 1. Se la condizione del ciclo è scritta con <= invece di <, l'iterazione finale tenta di accedere alla pagina con indice Count, che non esiste. Il runtime WASM incapsula l'eccezione .NET sottostante ArgumentOutOfRangeException come Error JavaScript con un messaggio simile a:

ArgumentOutOfRange_IndexMustBeLess Arg_ParamName_Name, index

Poiché la proprietà name dell'errore è solo il generico Error, non puoi distinguerlo dal solo nome — devi confrontare la stringa del messaggio se vuoi gestirlo in modo specifico.

Il ciclo corretto usa < in modo che l'ultimo indice a cui si accede sia Count - 1:

// The upper bound is Count - 1, so use < rather than <=
for (let i = 0; i < doc.Pages.Count; i++) {
  const page = doc.Pages.get_Item(i);
}

Questo schema di off-by-one è una delle fonti più frequenti di errori di runtime quando si lavora con raccolte di pagine. È facile non accorgersene durante i test se i documenti di esempio hanno solo una o due pagine — l'errore emerge solo all'iterazione finale, quindi un documento di una sola pagina non lo attiverà affatto. Testa sempre la logica del ciclo con un documento che abbia almeno tre pagine per assicurarti che la condizione al contorno sia corretta.


Vedi anche

The result is written to a text file that records the document's total page count

Un simple entier — le nombre total de pages d'un PDF — se cache derrière un nombre surprenant de décisions concrètes : limites de téléversement, estimation du papier pour l'impression, opérations de fractionnement, barres de progression. La plupart des bibliothèques de rendu PDF se contentent de dessiner les pages et n'exposent pas de simple compteur, et envoyer le fichier à un backend juste pour lire un nombre de pages ajoute de la latence et soulève des problèmes de confidentialité.

Spire.PDF for JavaScript charge et analyse les documents PDF directement dans le navigateur via WebAssembly, ainsi le fichier ne quitte jamais le client. Le nombre de pages est disponible sous la forme d'une seule propriété — sans boucles, sans allers-retours vers le serveur, sans contournements de rendu. Cet article explique comment récupérer ce nombre et aborde trois préoccupations pratiques : distinguer les nombres de pages physiques des étiquettes affichées, gérer les fichiers protégés par mot de passe, et éviter les erreurs de décalage d'un lorsque l'on parcourt les pages.

Pour l'installation et la configuration du projet, consultez Intégrer Spire.PDF for JavaScript dans un projet React. Les exemples ci-dessous supposent que Spire.PDF est installé et que le module WebAssembly a été initialisé.


Obtenir le nombre de pages d'un document PDF

Une fois qu'un objet PdfDocument a chargé un fichier, sa propriété Pages expose la collection de pages, et la propriété Count de cette collection renvoie le nombre total de pages. Il n'est pas nécessaire de parcourir les pages une à une — le nombre est disponible immédiatement après le chargement.

Le composant React suivant illustre le flux de travail complet : récupérer le PDF dans le système de fichiers virtuel, créer un PdfDocument, charger le fichier, lire Pages.Count, et écrire le résultat dans un fichier texte téléchargeable.

function App() {
  const getPageCount = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be counted into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Pages is the document's page collection; Count is the total page count
    const pageCount = doc.Pages.Count;

    // Write the result to the VFS
    const outputFileName = 'PageCountResult.txt';
    const report = `Document: ${inputFileName}\r\nTotal pages: ${pageCount}`;
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, report);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get PDF Page Count</h1>
      <button onClick={getPageCount}>
        Count Pages
      </button>
    </div>
  );
}

export default App;

Le résultat est écrit dans un fichier texte qui enregistre le nombre total de pages du document :

The result is written to a text file that records the document's total page count

Dans une application de production, vous utiliseriez généralement la valeur pageCount directement plutôt que de l'écrire dans un fichier — par exemple, pour valider un téléversement, définir une limite de boucle, ou afficher des métadonnées dans l'interface. L'approche de sortie vers un fichier présentée ici est utile pour les tests et la démonstration.


Nombre de pages physiques vs étiquettes de page

Voici une situation qui prend les développeurs au dépourvu : vous lisez Pages.Count et obtenez 12, mais le lecteur PDF à l'écran de l'utilisateur affiche la dernière page comme « page 8 ». Quel nombre est correct ?

Les deux le sont — ils mesurent des choses différentes. Pages.Count renvoie le nombre de pages physiques du document, tout simplement. Le nombre affiché par un lecteur, en revanche, provient des étiquettes de page (l'entrée /PageLabels dans la spécification PDF). Les étiquettes de page constituent une couche de présentation que les éditeurs utilisent pour contrôler la façon dont les numéros de page apparaissent au lecteur. Un éditeur de livre peut exclure la couverture de la numérotation, utiliser des chiffres romains (i, ii, iii) pour les pages liminaires, et redémarrer le corps à 1. Après tout cela, la cinquième page physique pourrait s'afficher comme iii ou 1 selon la configuration des étiquettes.

Cette distinction importe lorsque votre application doit afficher à l'utilisateur un numéro de page correspondant à ce qu'il voit dans son lecteur. Si vous affichez Pages.Count comme « page actuelle », cela ne correspondra pas à la numérotation du lecteur chaque fois que des étiquettes de page sont en jeu.

Lorsque vous avez besoin de l'étiquette affichée plutôt que de l'index physique, lisez la propriété PageLabel sur l'objet de page individuel :

// What label the 5th physical page displays in a reader
const page = doc.Pages.get_Item(4);
console.log(page.PageLabel);

Notez que l'index est basé sur zéro : get_Item(4) récupère la cinquième page physique. Lorsque le document n'a pas d'étiquettes de page configurées, PageLabel renvoie une chaîne vide. Dans ce cas courant, le numéro affiché correspond à l'ordre des pages physiques, donc Count est la valeur que vous voulez.

Une manière pratique de gérer les deux scénarios consiste à vérifier d'abord PageLabel et à revenir à l'index physique lorsqu'il est vide. Cela donne à votre application un numéro de page qui correspond toujours à ce que l'utilisateur voit, que le document utilise ou non des étiquettes personnalisées.


Compter les pages d'un PDF chiffré

De nombreux PDF dans les environnements professionnels sont protégés par un mot de passe d'ouverture — une mesure de sécurité qui empêche la lecture du document sans les identifiants corrects. Si vous essayez de charger un tel fichier avec un simple appel LoadFromFile, le moteur d'exécution WASM lève une erreur avant même que Pages.Count ne soit atteint :

Impossible d'ouvrir un document chiffré. Le mot de passe est invalide.

Cela se produit au moment du chargement, et non au moment où vous lisez le nombre de pages. Le contenu du document — y compris sa structure de pages — est chiffré, donc la bibliothèque ne peut pas l'analyser sans le mot de passe. Il n'est pas possible de compter les pages sans d'abord déverrouiller le document.

La solution est simple : transmettez le mot de passe d'ouverture comme deuxième argument à LoadFromFile. Une fois le document déverrouillé, le nombre de pages est disponible exactement comme pour un fichier non chiffré :

// The second argument is the open password
doc.LoadFromFile(inputFileName, 'spire123');
const pageCount = doc.Pages.Count;

Dans une application réelle, vous collecteriez généralement le mot de passe auprès de l'utilisateur via un champ de formulaire et le transmetteriez dynamiquement plutôt que de le coder en dur. Si l'utilisateur saisit un mot de passe erroné, la même erreur est levée — il est donc recommandé d'envelopper l'appel LoadFromFile dans un bloc try/catch et d'afficher un message convivial « mot de passe incorrect ».

Une autre précision à noter : ce mot de passe est le mot de passe d'ouverture (aussi appelé mot de passe utilisateur), qui contrôle qui peut consulter le document. Un PDF peut également avoir un mot de passe de permissions (mot de passe propriétaire) qui restreint la modification, l'impression ou la copie sans bloquer la consultation. Pour ce qui est de compter les pages, seul le mot de passe d'ouverture est pertinent — une fois le document ouvert, Pages.Count fonctionne indépendamment des restrictions de permissions.


Utiliser le nombre de pages comme limite de boucle

Une fois que vous avez le nombre de pages, une étape naturelle consiste à parcourir chaque page — pour extraire du texte, générer des miniatures, fractionner le document, ou appliquer une transformation. C'est là qu'apparaît un bug subtil mais courant : utiliser Count comme borne supérieure inclusive.

La collection Pages est basée sur zéro, ce qui signifie que les indices valides vont de 0 à Count - 1. Si la condition de boucle est écrite avec <= au lieu de <, l'itération finale tente d'accéder à la page à l'index Count, qui n'existe pas. Le moteur d'exécution WASM encapsule l'ArgumentOutOfRangeException .NET sous-jacente dans une Error JavaScript avec un message du type :

ArgumentOutOfRange_IndexMustBeLess Arg_ParamName_Name, index

Comme la propriété name de l'erreur est simplement l'Error générique, vous ne pouvez pas la distinguer par son nom seul — vous devez faire correspondre la chaîne du message si vous voulez la gérer spécifiquement.

La boucle correcte utilise <, de sorte que le dernier indice accédé soit Count - 1 :

// The upper bound is Count - 1, so use < rather than <=
for (let i = 0; i < doc.Pages.Count; i++) {
  const page = doc.Pages.get_Item(i);
}

Ce schéma de décalage d'un est l'une des sources les plus fréquentes d'erreurs d'exécution lorsqu'on travaille avec des collections de pages. Il est facile à manquer lors des tests si vos documents d'exemple ne comportent qu'une ou deux pages — l'erreur ne se manifeste qu'à l'itération finale, donc un document d'une seule page ne la déclenchera pas du tout. Testez toujours la logique de boucle avec un document comportant au moins trois pages pour vous assurer que la condition de limite est correcte.


Voir aussi

The result is written to a text file that records the document's total page count

Un solo número entero —el número total de páginas de un PDF— está detrás de una sorprendente cantidad de decisiones del mundo real: límites de carga, estimación de papel para impresión, operaciones de división, barras de progreso. La mayoría de las bibliotecas de renderización de PDF solo dibujan páginas y no exponen un recuento sencillo, y enviar el archivo a un backend solo para leer un recuento de páginas añade latencia y problemas de privacidad.

Spire.PDF for JavaScript carga y analiza documentos PDF directamente en el navegador mediante WebAssembly, por lo que el archivo nunca sale del cliente. El recuento de páginas está disponible como una única propiedad: sin bucles, sin idas y vueltas al servidor, sin soluciones alternativas de renderización. Este artículo explica cómo obtener ese recuento y tres aspectos prácticos: distinguir el número de páginas físico de las etiquetas de visualización, manejar archivos protegidos con contraseña y evitar errores de desfase (off-by-one) al iterar sobre las páginas.

Para obtener información sobre la instalación y la configuración del proyecto, consulte Integrar Spire.PDF for JavaScript en un proyecto de React. Los ejemplos a continuación asumen que Spire.PDF está instalado y que el módulo WebAssembly se ha inicializado.


Obtener el número de páginas de un documento PDF

Una vez que un objeto PdfDocument ha cargado un archivo, su propiedad Pages expone la colección de páginas, y la propiedad Count de esa colección devuelve el número total de páginas. No es necesario iterar por las páginas individualmente: el recuento está disponible inmediatamente después de la carga.

El siguiente componente de React demuestra el flujo de trabajo completo: obtener el PDF en el sistema de archivos virtual, crear un PdfDocument, cargar el archivo, leer Pages.Count y escribir el resultado en un archivo de texto descargable.

function App() {
  const getPageCount = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be counted into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Pages is the document's page collection; Count is the total page count
    const pageCount = doc.Pages.Count;

    // Write the result to the VFS
    const outputFileName = 'PageCountResult.txt';
    const report = `Document: ${inputFileName}\r\nTotal pages: ${pageCount}`;
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, report);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get PDF Page Count</h1>
      <button onClick={getPageCount}>
        Count Pages
      </button>
    </div>
  );
}

export default App;

El resultado se escribe en un archivo de texto que registra el número total de páginas del documento:

The result is written to a text file that records the document's total page count

En una aplicación de producción, normalmente usaría el valor de pageCount directamente en lugar de escribirlo en un archivo; por ejemplo, para validar una carga, establecer un límite de bucle o mostrar metadatos en la interfaz de usuario. El enfoque de salida a archivo que se muestra aquí es útil para pruebas y demostraciones.


Número de páginas físico vs. etiquetas de página

Esta es una situación que pilla a los desarrolladores desprevenidos: usted lee Pages.Count y obtiene 12, pero el lector de PDF en la pantalla del usuario muestra la última página como "página 8". ¿Qué número es correcto?

Ambos lo son: miden cosas diferentes. Pages.Count devuelve el número de páginas físicas del documento, sin más. Sin embargo, el número que muestra un lector proviene de las etiquetas de página (la entrada /PageLabels en la especificación PDF). Las etiquetas de página son una capa de presentación que los editores utilizan para controlar cómo aparecen los números de página ante el lector. Un editor de libros podría excluir la portada de la numeración, usar números romanos (i, ii, iii) para los preliminares y reiniciar el cuerpo en 1. Después de todo eso, la quinta página física podría mostrarse como iii o 1, según cómo estén configuradas las etiquetas.

Esta distinción importa cuando su aplicación necesita mostrar a los usuarios un número de página que coincida con lo que ven en su lector. Si muestra Pages.Count como la "página actual", no coincidirá con la numeración del lector siempre que haya etiquetas de página en juego.

Cuando necesite la etiqueta mostrada en lugar del índice físico, lea la propiedad PageLabel del objeto de página individual:

// What label the 5th physical page displays in a reader
const page = doc.Pages.get_Item(4);
console.log(page.PageLabel);

Tenga en cuenta que el índice empieza en cero: get_Item(4) recupera la quinta página física. Cuando el documento no tiene etiquetas de página configuradas, PageLabel devuelve una cadena vacía. En ese caso habitual, el número mostrado coincide con el orden físico de las páginas, por lo que Count es el valor que desea.

Una forma práctica de manejar ambos escenarios es comprobar primero PageLabel y recurrir al índice físico cuando esté vacío. Esto proporciona a su aplicación un número de página que siempre coincide con lo que ve el usuario, independientemente de si el documento utiliza etiquetas personalizadas.


Contar páginas en un PDF cifrado

Muchos PDF en entornos empresariales están protegidos por una contraseña de apertura, una medida de seguridad que impide leer el documento sin la credencial correcta. Si intenta cargar un archivo así con una llamada simple a LoadFromFile, el entorno de ejecución WASM lanza un error antes de que se llegue a Pages.Count:

No se puede abrir un documento cifrado. La contraseña no es válida.

Esto ocurre en el momento de la carga, no en el punto en el que se lee el recuento de páginas. El contenido del documento, incluida su estructura de páginas, está cifrado, por lo que la biblioteca no puede analizarlo sin la contraseña. No hay forma de contar páginas sin desbloquear primero el documento.

La solución es sencilla: pase la contraseña de apertura como segundo argumento a LoadFromFile. Una vez desbloqueado el documento, el recuento de páginas está disponible igual que con un archivo sin cifrar:

// The second argument is the open password
doc.LoadFromFile(inputFileName, 'spire123');
const pageCount = doc.Pages.Count;

En una aplicación real, normalmente recopilaría la contraseña del usuario a través de un campo de formulario y la pasaría dinámicamente en lugar de codificarla de forma fija. Si el usuario introduce una contraseña incorrecta, se lanza el mismo error, por lo que envolver la llamada a LoadFromFile en un bloque try/catch y mostrar un mensaje amigable de "contraseña incorrecta" es una buena práctica.

Hay algo más que vale la pena señalar: esta contraseña es la contraseña de apertura (también llamada contraseña de usuario), que controla quién puede ver el documento. Un PDF también puede tener una contraseña de permisos (contraseña de propietario) que restringe la edición, la impresión o la copia sin bloquear la visualización. A efectos de contar páginas, solo es relevante la contraseña de apertura: una vez abierto el documento, Pages.Count funciona independientemente de las restricciones de permisos.


Usar el número de páginas como límite de bucle

Una vez que tiene el número de páginas, un siguiente paso natural es recorrer cada página en un bucle: para extraer texto, generar miniaturas, dividir el documento o aplicar alguna transformación. Aquí es donde aparece un error sutil pero común: usar Count como límite superior inclusivo.

La colección Pages está indexada desde cero, lo que significa que los índices válidos van de 0 a Count - 1. Si la condición del bucle se escribe con <= en lugar de <, la iteración final intenta acceder a la página en el índice Count, que no existe. El entorno de ejecución WASM envuelve la excepción subyacente ArgumentOutOfRangeException de .NET como un Error de JavaScript con un mensaje como:

ArgumentOutOfRange_IndexMustBeLess Arg_ParamName_Name, index

Como la propiedad name del error es solo el Error genérico, no puede distinguirlo solo por el nombre: tiene que coincidir con la cadena del mensaje si desea manejarlo específicamente.

El bucle correcto usa <, de modo que el último índice al que se accede es Count - 1:

// The upper bound is Count - 1, so use < rather than <=
for (let i = 0; i < doc.Pages.Count; i++) {
  const page = doc.Pages.get_Item(i);
}

Este patrón de error por desfase (off-by-one) es una de las fuentes más frecuentes de errores en tiempo de ejecución al trabajar con colecciones de páginas. Es fácil pasarlo por alto en las pruebas si sus documentos de ejemplo tienen solo una o dos páginas: el error solo aparece en la iteración final, por lo que un documento de una sola página no lo activará en absoluto. Pruebe siempre la lógica de los bucles con un documento que tenga al menos tres páginas para asegurarse de que la condición de límite sea correcta.


Vea también

The result is written to a text file that records the document's total page count

Eine einzige Ganzzahl – die Gesamtzahl der Seiten in einer PDF – steckt hinter einer überraschend großen Zahl realer Entscheidungen: Upload-Limits, Papierschätzung für den Druck, Split-Vorgänge, Fortschrittsbalken. Die meisten PDF-Rendering-Bibliotheken zeichnen nur Seiten und stellen keine einfache Zählung bereit, und das Senden der Datei an ein Backend, nur um eine Seitenzahl auszulesen, verursacht Latenz und Datenschutzbedenken.

Spire.PDF für JavaScript lädt und parst PDF-Dokumente direkt im Browser über WebAssembly, sodass die Datei den Client nie verlässt. Die Seitenzahl ist als einzelne Eigenschaft verfügbar – keine Schleifen, keine Server-Roundtrips, keine Rendering-Workarounds. Dieser Artikel beschreibt, wie Sie diese Anzahl abrufen, und drei praktische Aspekte: physische Seitenzahlen von Anzeige-Beschriftungen zu unterscheiden, passwortgeschützte Dateien zu behandeln und Off-by-one-Fehler beim Iterieren über Seiten zu vermeiden.

Informationen zur Installation und Projekteinrichtung finden Sie unter Spire.PDF für JavaScript in ein React-Projekt integrieren. Die folgenden Beispiele gehen davon aus, dass Spire.PDF installiert und das WebAssembly-Modul initialisiert wurde.


Die Seitenzahl eines PDF-Dokuments abrufen

Sobald ein PdfDocument-Objekt eine Datei geladen hat, macht seine Eigenschaft Pages die Seitensammlung verfügbar, und die Eigenschaft Count dieser Sammlung gibt die Gesamtzahl der Seiten zurück. Es ist nicht nötig, die Seiten einzeln zu durchlaufen – die Anzahl ist unmittelbar nach dem Laden verfügbar.

Die folgende React-Komponente demonstriert den vollständigen Ablauf: die PDF in das virtuelle Dateisystem holen, ein PdfDocument erstellen, die Datei laden, Pages.Count auslesen und das Ergebnis in eine herunterladbare Textdatei schreiben.

function App() {
  const getPageCount = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be counted into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Pages is the document's page collection; Count is the total page count
    const pageCount = doc.Pages.Count;

    // Write the result to the VFS
    const outputFileName = 'PageCountResult.txt';
    const report = `Document: ${inputFileName}\r\nTotal pages: ${pageCount}`;
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, report);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get PDF Page Count</h1>
      <button onClick={getPageCount}>
        Count Pages
      </button>
    </div>
  );
}

export default App;

Das Ergebnis wird in eine Textdatei geschrieben, die die Gesamtzahl der Seiten des Dokuments festhält:

The result is written to a text file that records the document's total page count

In einer Produktionsanwendung würden Sie den Wert pageCount in der Regel direkt verwenden, statt ihn in eine Datei zu schreiben – zum Beispiel, um einen Upload zu validieren, eine Schleifengrenze festzulegen oder Metadaten in der Benutzeroberfläche anzuzeigen. Der hier gezeigte Ansatz mit Dateiausgabe ist für Tests und Demonstrationen nützlich.


Physische Seitenzahl vs. Seitenbeschriftungen

Hier ist eine Situation, die Entwickler oft überrascht: Sie lesen Pages.Count aus und erhalten 12, doch der PDF-Reader auf dem Bildschirm des Nutzers zeigt die letzte Seite als „Seite 8“ an. Welche Zahl ist richtig?

Beide sind es – sie messen unterschiedliche Dinge. Pages.Count gibt schlicht und einfach die Anzahl der physischen Seiten im Dokument zurück. Die von einem Reader angezeigte Zahl stammt dagegen aus den Seitenbeschriftungen (dem Eintrag /PageLabels in der PDF-Spezifikation). Seitenbeschriftungen sind eine Darstellungsebene, mit der Verlage steuern, wie Seitenzahlen dem Leser erscheinen. Ein Buchverlag könnte das Cover von der Nummerierung ausnehmen, römische Ziffern (i, ii, iii) für den Vorspann verwenden und den Hauptteil wieder bei 1 beginnen lassen. Nach alledem könnte die fünfte physische Seite je nach Konfiguration der Beschriftungen als iii oder 1 angezeigt werden.

Dieser Unterschied ist wichtig, wenn Ihre Anwendung Nutzern eine Seitenzahl anzeigen soll, die mit dem übereinstimmt, was sie in ihrem Reader sehen. Wenn Sie Pages.Count als „aktuelle Seite“ anzeigen, stimmt das nicht mit der Nummerierung des Readers überein, sobald Seitenbeschriftungen im Spiel sind.

Wenn Sie die angezeigte Beschriftung statt des physischen Index benötigen, lesen Sie die Eigenschaft PageLabel des einzelnen Seitenobjekts aus:

// What label the 5th physical page displays in a reader
const page = doc.Pages.get_Item(4);
console.log(page.PageLabel);

Beachten Sie den nullbasierten Index: get_Item(4) ruft die fünfte physische Seite ab. Wenn im Dokument keine Seitenbeschriftungen konfiguriert sind, gibt PageLabel eine leere Zeichenkette zurück. In diesem häufigen Fall entspricht die angezeigte Zahl der physischen Seitenreihenfolge, sodass Count der gewünschte Wert ist.

Ein praktischer Weg, beide Szenarien zu behandeln, besteht darin, zuerst PageLabel zu prüfen und auf den physischen Index zurückzugreifen, wenn er leer ist. So erhält Ihre Anwendung immer eine Seitenzahl, die dem entspricht, was der Nutzer sieht – unabhängig davon, ob das Dokument benutzerdefinierte Beschriftungen verwendet.


Seiten in einem verschlüsselten PDF zählen

Viele PDFs in Unternehmensumgebungen sind durch ein Öffnungspasswort geschützt – eine Sicherheitsmaßnahme, die verhindert, dass das Dokument ohne die korrekten Zugangsdaten gelesen wird. Wenn Sie versuchen, eine solche Datei mit einem einfachen LoadFromFile-Aufruf zu laden, wirft die WASM-Laufzeit einen Fehler, bevor Pages.Count überhaupt erreicht wird:

Can not open an encrypted document. The password is invalid.

Dies geschieht beim Laden, nicht an der Stelle, an der Sie die Seitenzahl auslesen. Der Inhalt des Dokuments – einschließlich seiner Seitenstruktur – ist verschlüsselt, sodass die Bibliothek ihn ohne das Passwort nicht parsen kann. Es gibt keine Möglichkeit, Seiten zu zählen, ohne das Dokument zuvor zu entsperren.

Die Lösung ist unkompliziert: Übergeben Sie das Öffnungspasswort als zweites Argument an LoadFromFile. Sobald das Dokument entsperrt ist, ist die Seitenzahl genauso verfügbar wie bei einer unverschlüsselten Datei:

// The second argument is the open password
doc.LoadFromFile(inputFileName, 'spire123');
const pageCount = doc.Pages.Count;

In einer echten Anwendung würden Sie das Passwort in der Regel über ein Formularfeld vom Nutzer abfragen und dynamisch übergeben, statt es fest im Code zu hinterlegen. Wenn der Nutzer das falsche Passwort eingibt, wird derselbe Fehler geworfen – daher ist es gute Praxis, den LoadFromFile-Aufruf in einen try/catch-Block zu packen und eine freundliche Meldung wie „falsches Passwort“ anzuzeigen.

Noch ein erwähnenswerter Punkt: Dieses Passwort ist das Öffnungspasswort (auch Benutzerpasswort genannt), das steuert, wer das Dokument ansehen darf. Ein PDF kann außerdem ein Berechtigungspasswort (Besitzerpasswort) haben, das Bearbeiten, Drucken oder Kopieren einschränkt, ohne das Ansehen zu blockieren. Für das Zählen von Seiten ist nur das Öffnungspasswort relevant – sobald das Dokument geöffnet ist, funktioniert Pages.Count unabhängig von Berechtigungseinschränkungen.


Die Seitenzahl als Schleifengrenze verwenden

Sobald Sie die Seitenzahl haben, ist der nächste naheliegende Schritt, über jede Seite zu iterieren – um Text zu extrahieren, Miniaturansichten zu rendern, das Dokument zu teilen oder eine Transformation anzuwenden. Genau hier taucht ein subtiler, aber häufiger Fehler auf: die Verwendung von Count als inklusive Obergrenze.

Die Sammlung Pages ist nullbasiert indiziert, das heißt, gültige Indizes reichen von 0 bis Count - 1. Wenn die Schleifenbedingung mit <= statt mit < geschrieben wird, versucht die letzte Iteration, auf die Seite am Index Count zuzugreifen, die nicht existiert. Die WASM-Laufzeit verpackt die zugrunde liegende .NET-ArgumentOutOfRangeException als JavaScript-Error mit einer Meldung wie:

ArgumentOutOfRange_IndexMustBeLess Arg_ParamName_Name, index

Da die Eigenschaft name des Fehlers nur das generische Error ist, können Sie ihn nicht allein anhand des Namens unterscheiden – Sie müssen auf die Meldungszeichenkette prüfen, wenn Sie ihn gezielt behandeln möchten.

Die korrekte Schleife verwendet <, sodass der zuletzt zugegriffene Index Count - 1 ist:

// The upper bound is Count - 1, so use < rather than <=
for (let i = 0; i < doc.Pages.Count; i++) {
  const page = doc.Pages.get_Item(i);
}

Dieses Off-by-one-Muster ist eine der häufigsten Ursachen für Laufzeitfehler bei der Arbeit mit Seitensammlungen. In Tests übersieht man es leicht, wenn Ihre Beispieldokumente zufällig nur ein oder zwei Seiten haben – der Fehler tritt nur in der letzten Iteration auf, sodass ein einseitiges Dokument ihn überhaupt nicht auslöst. Testen Sie die Schleifenlogik immer mit einem Dokument, das mindestens drei Seiten hat, um sicherzustellen, dass die Grenzbedingung korrekt ist.


Siehe auch

The result is written to a text file that records the document's total page count

Одно целое число — общее количество страниц в PDF — стоит за удивительно большим числом реальных решений: ограничения на загрузку, оценка бумаги для печати, операции разделения, индикаторы выполнения. Большинство библиотек отрисовки PDF только отрисовывают страницы и не предоставляют простого счётчика, а отправка файла на серверную часть только для чтения количества страниц добавляет задержку и вызывает вопросы конфиденциальности.

Spire.PDF для JavaScript загружает и анализирует PDF-документы непосредственно в браузере через WebAssembly, поэтому файл никогда не покидает клиент. Количество страниц доступно как одно свойство — никаких циклов, никаких обращений к серверу, никаких обходных путей отрисовки. В этой статье рассматривается получение этого количества и три практических вопроса: отличие физического количества страниц от отображаемых меток, обработка файлов, защищённых паролем, и избежание ошибок на единицу при переборе страниц.

Для установки и настройки проекта см. Интеграция Spire.PDF для JavaScript в проект React. Примеры ниже предполагают, что Spire.PDF установлен и модуль WebAssembly инициализирован.


Получение количества страниц PDF-документа

После того как объект PdfDocument загрузил файл, его свойство Pages предоставляет коллекцию страниц, а свойство Count этой коллекции возвращает общее количество страниц. Нет необходимости перебирать страницы по отдельности — количество доступно сразу после загрузки.

Следующий компонент React демонстрирует полный рабочий процесс: получение PDF в виртуальную файловую систему, создание PdfDocument, загрузка файла, чтение Pages.Count и запись результата в текстовый файл для скачивания.

function App() {
  const getPageCount = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be counted into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Pages is the document's page collection; Count is the total page count
    const pageCount = doc.Pages.Count;

    // Write the result to the VFS
    const outputFileName = 'PageCountResult.txt';
    const report = `Document: ${inputFileName}\r\nTotal pages: ${pageCount}`;
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, report);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get PDF Page Count</h1>
      <button onClick={getPageCount}>
        Count Pages
      </button>
    </div>
  );
}

export default App;

Результат записывается в текстовый файл, в котором фиксируется общее количество страниц документа:

The result is written to a text file that records the document's total page count

В производственном приложении вы обычно использовали бы значение pageCount напрямую, а не записывали его в файл — например, для проверки загрузки, установки границы цикла или отображения метаданных в интерфейсе. Показанный здесь подход с выводом в файл полезен для тестирования и демонстрации.


Физическое количество страниц и метки страниц

Вот ситуация, которая застаёт разработчиков врасплох: вы читаете Pages.Count и получаете 12, но PDF-ридер на экране пользователя показывает последнюю страницу как «страница 8». Какое число правильное?

Оба — они измеряют разные вещи. Pages.Count возвращает количество физических страниц в документе, и ничего больше. Однако число, отображаемое ридером, происходит от меток страниц (запись /PageLabels в спецификации PDF). Метки страниц — это слой представления, который издатели используют для управления тем, как номера страниц отображаются читателю. Издатель книги может исключить обложку из нумерации, использовать римские цифры (i, ii, iii) для вводной части и начать основную часть заново с 1. В результате пятая физическая страница может отображаться как iii или 1 в зависимости от того, как настроены метки.

Это различие имеет значение, когда ваше приложение должно показывать пользователям номер страницы, совпадающий с тем, что они видят в своём ридере. Если вы отображаете Pages.Count как «текущую страницу», оно не будет совпадать с нумерацией ридера всякий раз, когда используются метки страниц.

Когда вам нужна отображаемая метка, а не физический индекс, прочитайте свойство PageLabel у отдельного объекта страницы:

// What label the 5th physical page displays in a reader
const page = doc.Pages.get_Item(4);
console.log(page.PageLabel);

Обратите внимание на индекс с отсчётом от нуля: get_Item(4) извлекает пятую физическую страницу. Когда в документе не настроены метки страниц, PageLabel возвращает пустую строку. В этом распространённом случае отображаемое число совпадает с порядком физических страниц, поэтому Count — это нужное вам значение.

Практичный способ обработки обоих сценариев — сначала проверить PageLabel и вернуться к физическому индексу, когда оно пустое. Это даёт вашему приложению номер страницы, который всегда совпадает с тем, что видит пользователь, независимо от того, использует ли документ пользовательские метки.


Подсчёт страниц в зашифрованном PDF

Многие PDF-файлы в бизнес-среде защищены паролем на открытие — мерой безопасности, которая не позволяет прочитать документ без правильных учётных данных. Если вы попытаетесь загрузить такой файл обычным вызовом LoadFromFile, среда выполнения WASM выдаст ошибку ещё до того, как будет достигнуто Pages.Count:

Не удаётся открыть зашифрованный документ. Пароль неверен.

Это происходит во время загрузки, а не в тот момент, когда вы читаете количество страниц. Содержимое документа — включая его структуру страниц — зашифровано, поэтому библиотека не может его разобрать без пароля. Невозможно подсчитать страницы, не разблокировав документ.

Решение простое: передайте пароль на открытие вторым аргументом в LoadFromFile. Как только документ разблокирован, количество страниц доступно так же, как и для незашифрованного файла:

// The second argument is the open password
doc.LoadFromFile(inputFileName, 'spire123');
const pageCount = doc.Pages.Count;

В реальном приложении вы обычно собирали бы пароль у пользователя через поле формы и передавали бы его динамически, а не жёстко зашивали. Если пользователь введёт неправильный пароль, будет выброшена та же ошибка — поэтому обернуть вызов LoadFromFile в блок try/catch и показать дружественное сообщение «неверный пароль» — хорошая практика.

Ещё одна вещь, которую стоит отметить: этот пароль — пароль на открытие (также называемый паролем пользователя), который определяет, кто может просматривать документ. PDF также может иметь пароль разрешений (пароль владельца), который ограничивает редактирование, печать или копирование, не блокируя просмотр. Для подсчёта страниц важен только пароль на открытие — как только документ открыт, Pages.Count работает независимо от ограничений разрешений.


Использование количества страниц в качестве границы цикла

Получив количество страниц, естественный следующий шаг — перебрать каждую страницу: извлечь текст, отрисовать миниатюры, разделить документ или применить какое-либо преобразование. Именно здесь появляется тонкая, но распространённая ошибка: использование Count в качестве включающей верхней границы.

Коллекция Pages имеет индексацию с нуля, то есть допустимые индексы идут от 0 до Count - 1. Если условие цикла записано с <= вместо <, последняя итерация попытается обратиться к странице с индексом Count, которой не существует. Среда выполнения WASM оборачивает базовое исключение .NET ArgumentOutOfRangeException в JavaScript Error с сообщением вроде:

ArgumentOutOfRange_IndexMustBeLess Arg_ParamName_Name, index

Поскольку свойство name этой ошибки — просто общее Error, вы не можете отличить её только по имени — вам придётся сопоставлять строку сообщения, если вы хотите обработать её особым образом.

Правильный цикл использует <, поэтому последний используемый индекс — Count - 1:

// The upper bound is Count - 1, so use < rather than <=
for (let i = 0; i < doc.Pages.Count; i++) {
  const page = doc.Pages.get_Item(i);
}

Эта ошибка на единицу — один из наиболее частых источников ошибок времени выполнения при работе с коллекциями страниц. Её легко пропустить при тестировании, если ваши образцы документов содержат только одну или две страницы — ошибка проявляется только на последней итерации, поэтому документ с одной страницей её вообще не вызовет. Всегда тестируйте логику цикла на документе, содержащем как минимум три страницы, чтобы убедиться, что граничное условие верно.


См. также