4  PDF (.pdf) Documents

Author

Daniel Manrique-Castano

Published

December 18, 2025

4.1 Overview // Aperçu

PDF is considered the standard for fixed-layout documents. However, not all PDFs are created in the same manner.

//

Le format PDF est considéré comme la norme pour les documents à mise en page fixe. Cependant, tous les fichiers PDF ne sont pas créés de la même manière.

NoteCuration Goal // Objectif de la curation

Validate the archival suitability of PDF documents. Our objective is to distinguish between high-quality PDF/A files and “Image-Only” scans, ensuring content is searchable, accessible, and free from encryption that would lock data away. // Vérifier si les documents PDF sont adaptés à l’archivage. Notre objectif est de distinguer les fichiers PDF/A de haute qualité des numérisations « uniquement sous forme d’images », afin de garantir que le contenu soit consultable, accessible et exempt de tout cryptage susceptible d’en bloquer l’accès.

WarningIdentifying Risks // Identification des risques

Not all PDFs are equal. Encrypted files, image-only scans without OCR, and documents with external font dependencies pose significant risks for long-term accessibility and automated text mining. // Tous les fichiers PDF ne se valent pas. Les fichiers cryptés, les numérisations contenant uniquement des images sans OCR et les documents dépendant de polices externes présentent des risques importants pour l’accessibilité à long terme et l’exploration automatisée de textes.

This notebook evaluates PDF files on three dimensions:

  1. Security: Detecting Encryption or password protection.
  2. Accessibility: Categorizing files as Searchable or Image-Only documents.
  3. Metadata Quality: Checking for embedded Title and Author fields.

//

Ce cahier d’exercices évalue les fichiers PDF selon trois critères :

  1. Sécurité : détection d’un cryptage ou d’une protection par mot de passe.
  2. Accessibilité : classification des fichiers en documents consultables ou en documents contenant uniquement des images.
  3. Qualité des métadonnées : vérification de la présence des champs « Titre » et « Auteur » intégrés.

4.2 Setup // Configuration

We use pdftools for PDF analysis and tidyverse for reporting.

//

Nous utilisons pdftools pour l’analyse des fichiers PDF et tidyverse pour la création de rapports.

4.2.1 R Packages // Packages R

The following R packages are required. If you don’t have these packages, uncomment this code and run it once in your R console:

//

Les packages R suivants sont requis. Si vous ne disposez pas de ces packages, décommentez ce code et exécutez-le une fois dans votre console R :

Code
# install.packages(c("tidyverse", "pdftools", "rstudioapi"))

4.2.2 Load libraries // Charger les bibliothèques

Code
library(tidyverse)
library(pdftools)
library(rstudioapi)

4.3 Select a target directory // Sélectionnez un répertoire de destination

This block allows for interactive selection of the image directory. If running in a non-interactive environment, it defaults to the path defined in the YAML header.

//

Ce bloc permet de sélectionner de manière interactive le répertoire contenant les images. En cas d’exécution dans un environnement non interactif, le chemin par défaut est celui défini dans l’en-tête YAML.

Code
# 1. Try to select interactively if in RStudio
if (interactive() && .Platform$OS.type == "windows") { 
  # .Platform check helps avoid errors in some non-interactive rendering contexts
  selected_dir <- rstudioapi::selectDirectory(caption = "Select Image Directory")
} else {
  selected_dir <- NULL
}

# 2. Logic to determine final directory (Interactive vs Parameter)
if (!is.null(selected_dir)) {
  target_dir <- selected_dir
} else {
  target_dir <- params$target_dir
}

print(paste("Analyzing directory:", target_dir))
[1] "Analyzing directory: data/Inspect_pdf/"

4.4 Find PDF Files // Rechercher des fichiers PDF

Code
pdf_files <- list.files(
  path = target_dir,
  pattern = "\\.pdf$", 
  recursive = TRUE, 
  full.names = TRUE, 
  ignore.case = TRUE
)

print(paste("Found", length(pdf_files), "PDF files."))
[1] "Found 6 PDF files."

4.5 File inventory and inspection // Inventaire et analyse des fichiers

We iterate through the files to extract metadata, check for encryption, and sample the text content to determine its accessibility status.

//

Nous parcourons les fichiers pour en extraire les métadonnées, vérifier s’ils sont cryptés et analyser un échantillon du contenu textuel afin de déterminer leur niveau d’accessibilité.

Code
message("Generating PDF Report...")

results_list <- lapply(pdf_files, function(file_path) {
  fname <- basename(file_path)
  
  tryCatch({
    # 1. Metadata & Security
    info <- pdftools::pdf_info(file_path)
    
    # NEW: Capture Encryption Status explicitly
    is_encrypted <- isTRUE(info$encrypted)
    
    # 2. Text Extraction
    # If encrypted, text extraction might fail, so we wrap it or handle it
    first_page_text <- tryCatch(pdftools::pdf_text(file_path)[1], error = function(e) "")
    
    # Handle NA/NULL text
    if(is.na(first_page_text) || is.null(first_page_text)) first_page_text <- ""
    
    char_count <- nchar(trimws(first_page_text))
    has_text <- char_count > 10 
    
    # 3. Fonts
    fonts <- tryCatch(pdftools::pdf_fonts(file_path), error = function(e) NULL)
    font_names <- if (!is.null(fonts)) paste(head(unique(fonts$name), 5), collapse = ", ") else "Unknown"
    
    # Helper for metadata
    get_meta <- function(x) {
      if (is.null(x) || length(x) == 0 || x == "") return("Unknown")
      return(as.character(x))
    }

    # Return valid tibble with explicit types
    tibble(
      FileName = fname,
      Pages = as.integer(info$pages),
      Encrypted = as.logical(is_encrypted), # <--- NEW COLUMN
      Author = get_meta(info$keys$Author),
      Title = get_meta(info$keys$Title),
      Creator = get_meta(info$keys$Creator),
      Created = as.character(info$created),
      HasText = as.logical(has_text),
      FirstPageChars = as.integer(char_count),
      Fonts = as.character(font_names),
      Status = "Success"
    )
    
  }, error = function(e) {
    tibble(
      FileName = fname,
      Pages = as.integer(NA),
      Encrypted = as.logical(NA),
      Author = as.character(NA),
      Title = as.character(NA),
      Creator = as.character(NA),
      Created = as.character(NA),
      HasText = as.logical(NA),
      FirstPageChars = as.integer(NA),
      Fonts = as.character(NA),
      Status = paste("Failed:", e$message)
    )
  })
})

report <- bind_rows(results_list)

# Display preview
print("--- PDF Report Preview ---")
[1] "--- PDF Report Preview ---"
Code
print(head(report))
# A tibble: 6 × 11
  FileName   Pages Encrypted Author Title Creator Created HasText FirstPageChars
  <chr>      <int> <lgl>     <chr>  <chr> <chr>   <chr>   <lgl>            <int>
1 20230724_…     7 FALSE     Unkno… Unkn… Unknown 2024-0… TRUE                57
2 example_1…     1 FALSE     Unkno… Unkn… Matplo… 2024-0… FALSE                0
3 Oil_capac…     4 FALSE     Unkno… Oil_… TextEd… 2025-0… TRUE              2883
4 Oil_Trans…     1 FALSE     Unkno… Oil_… TextEd… 2025-0… TRUE              1982
5 Past_Spil…     2 FALSE     Unkno… Past… TextEd… 2025-0… TRUE              2819
6 Spill_eve…     1 FALSE     Unkno… Spil… TextEd… 2025-0… TRUE               814
# ℹ 2 more variables: Fonts <chr>, Status <chr>

4.6 Save Results // Enregistrer les résultats

Code
output_dir <- file.path("Results", "Inspect_pdf")
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)

output_file <- file.path(output_dir, paste0("PDF_Report_", Sys.Date(), ".csv"))

write.csv(report, output_file, row.names = FALSE)

print(paste("Report saved to:", output_file))
[1] "Report saved to: Results/Inspect_pdf/PDF_Report_2026-07-03.csv"

4.7 Curation Insights // Aperçu de la curation

Use the generated CSV report to guide your actions:

  • Encryption (Encrypted = TRUE): Encrypted files cannot be migrated, indexed, or preserved. Contact the depositor to discuss removing the password. If the password is lost, the file cannot be reused.

  • Accessibility (TextStatus = “Image-Only”): These are scanned images. They are readable by humans but invisible to search engines and screen readers (accessibility violation). You can run these files through an OCR tool like Tesseract or Adobe Acrobat to generate a text layer.

  • Font Embedding (Fonts): If Fonts is empty or lists generic names like “Helvetica” without embedding, the layout may break on future systems. the curator can convert the file to PDF/A-1b or PDF/A-2b, which forces font embedding.

//

Utilisez le rapport CSV généré pour orienter vos actions :

  • Chiffrement (Encrypted = TRUE) : Les fichiers chiffrés ne peuvent être ni migrés, ni indexés, ni conservés. Contactez le déposant pour discuter de la suppression du mot de passe. Si le mot de passe est perdu, le fichier ne pourra pas être réutilisé.

  • Accessibilité (TextStatus = « Image-Only ») : Il s’agit d’images numérisées. Elles sont lisibles par les humains mais invisibles pour les moteurs de recherche et les lecteurs d’écran (violation de l’accessibilité). Vous pouvez traiter ces fichiers à l’aide d’un outil OCR tel que Tesseract ou Adobe Acrobat afin de générer une couche de texte.

  • Intégration des polices (Fonts) : Si le champ « Fonts » est vide ou contient des noms génériques tels que « Helvetica » sans intégration, la mise en page risque d’être altérée sur les systèmes futurs. Le conservateur peut convertir le fichier au format PDF/A-1b ou PDF/A-2b, ce qui force l’intégration des polices.

4.8 Additional Tools // Outils supplémentaires

  • VeraPDF: This is the standard open-source validator for PDF/A compliance. It checks if fonts are embedded and if multimedia is excluded (see https://verapdf.org).

  • Tesseract OCR: Is an open-source engine for optical character recognition . It can take an “Image-Only” PDF and output a searchable text file or a new PDF with a text layer.

  • Google AI: Extractd text and data from images and documents using AI capabilities. See a use case in another chapter of this book.

  • Ghostscript: This is a command-line interpreter often used to repair corrupted PDFs or convert standard PDFs into PDF/A format.

//

  • VeraPDF : Il s’agit du validateur open source de référence pour la conformité au format PDF/A. Il vérifie si les polices sont intégrées et si les éléments multimédias sont exclus (voir https://verapdf.org).

  • Tesseract OCR : Il s’agit d’un moteur open source dédié à la reconnaissance optique de caractères. Il peut prendre un PDF « Image-Only » et générer un fichier texte consultable ou un nouveau PDF avec un calque de texte.

  • Google AI : Extrait du texte et des données à partir d’images et de documents à l’aide de fonctionnalités d’IA. Découvrez un cas d’utilisation dans un autre chapitre de ce livre.

  • Ghostscript : Il s’agit d’un interpréteur en ligne de commande souvent utilisé pour réparer des PDF corrompus ou convertir des PDF standard au format PDF/A.

4.9 Using the Non-Interactive R Script // Utilisation du script R non interactif

For users who want to run this analysis on a server, in a batch job, or from the command line, here is a pure R script that performs the same process.

Download the R Script: Inspect_PDF_Script.R

//

Pour les utilisateurs qui souhaitent exécuter cette analyse sur un serveur, dans le cadre d’un traitement par lots ou depuis la ligne de commande, voici un script R pur qui effectue le même processus.

Télécharger le script R : Inspect_PDF_Script.R

4.9.1 Example HPC Submission Script // Exemple de script de soumission HPC

#!/bin/bash
#SBATCH --job-name=pdf_inspect
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --time=00:30:00
#SBATCH --mem=8G
#SBATCH --output=logs/pdf_inspect_%j.log

module load R

# Define target directory
TARGET_DIR="/scratch/user/project_data/documents"

# Prepare output folders
mkdir -p Results/Inspect_pdf
mkdir -p logs

# Run
echo "Starting PDF Inspection on $TARGET_DIR"
Rscript Inspect_PDF_Script.R "$TARGET_DIR"