Article technique

Linéariser des PDF pour Fast Web View avec PDFium en Delphi

PDFium Component produit des PDF Fast Web View (linéarisés) depuis Delphi via TPdf.SaveAsLinearized. La méthode enregistre le document courant avec PDFium, puis réécrit le résultat selon l'Annexe F d'ISO 32000-1 : les objets de première page passent devant, deux sections de table de références croisées sont écrites, et les tables de hints de décalages de pages et d'objets partagés disent à une visionneuse quelles plages d'octets aller chercher pour n'importe quelle page

La raison pour laquelle ça compte, c'est le réseau. Un catalogue de 40 Mo servi depuis un stockage d'objets ou un portail documentaire devrait montrer la page une après les premiers kilooctets, et sauter à la page 380 devrait coûter une couple de requêtes HTTP Range, pas un téléchargement complet. Les visionneuses ne peuvent le faire que si le fichier est linéarisé. La plupart des PDF sortant d'un pipeline d'édition ne le sont pas, parce que la façon naturelle d'écrire un PDF place la table de références croisées à la fin et disperse les objets de pages là où le rédacteur les a émis

Pourquoi PDFium sait lire les fichiers linéarisés mais pas les écrire ?

PDFium a un lecteur complet de linéarisation et aucun rédacteur pour elle. L'API de disponibilité FPDFAvail_* détecte le dictionnaire de linéarisation, décode les tables de hints, et demande à l'hôte exactement les plages d'octets dont il a besoin, ce sur quoi repose le flux de travail de téléchargement progressif et d'annulation dans PDFium Component. L'ABI publique d'enregistrement, en revanche, n'offre qu'une copie simple et un enregistrement avec version PDF cible. Rien là-dedans ne réordonne des objets ni n'émet des flux de hints

PDFium Component referme ce trou sans réimplémenter ce que PDFium fait déjà bien. TPdf.SaveAsLinearized appelle d'abord le SaveAs existant, si bien que PDFium garde la main sur l'enregistrement sémantique : pages éditées, état des champs de formulaire et gestion de version passent tous par le chemin de code exercé depuis des années. Le composant exécute ensuite une passe de restructuration Annexe F sur ce PDF intermédiaire stable et écrit le résultat linéarisé vers votre cible. Découper le travail ainsi veut dire que la linéarisation n'a jamais à comprendre une annotation ou un formulaire, seulement des objets, des références et des décalages d'octets

Appeler TPdf.SaveAsLinearized depuis Delphi

TPdf.SaveAsLinearized a deux surcharges, une pour un TStream et une pour un nom de fichier, et toutes deux prennent les mêmes paramètres optionnels Option: TSaveOption et PdfVersion: TPdfVersion que SaveAs. Toutes deux renvoient True en cas de succès. La surcharge fichier écrit dans un fichier temporaire du répertoire cible et remplace la destination de façon atomique, si bien qu'un enregistrement en échec ne laisse jamais un PDF à moitié écrit là où se trouvait un bon

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'catalog-2026.pdf';
    Pdf.Active := True;
    // ... éditez des pages, remplissez des formulaires, tamponnez, ce que fait le pipeline ...
    if not Pdf.SaveAsLinearized('catalog-2026-web.pdf', saNoIncremental, pv17) then
      raise Exception.Create('Linearized save failed');
  finally
    Pdf.Free;
  end;
end;

Les règles de paramètres sont strictes exprès. saIncremental lève une erreur, parce qu'une mise à jour incrémentale appose une nouvelle révision à la fin du fichier et détruirait aussitôt la disposition chargée devant. Une version cible sous PDF 1.2 est aussi rejetée ; pvUnknown garde la version source. Une entrée chiffrée demande saRemoveSecurity, puisque le rédacteur renumérote les objets et que les clés de chiffrement au niveau objet de PDF dérivent du numéro d'objets. Et comme la linéarisation est une réécriture complète, toute signature numérique existante est invalidée. Si vous devez livrer signé et linéarisé, linéarisez d'abord et signez ensuite

Comment le rédacteur groupe les objets pour Fast Web View

La linéarisation n'est pas juste déplacer la page une en haut du fichier. Le rédacteur parse la révision courante, que ses données de références croisées soient une table classique, un fichier hybride ou un flux de références croisées, et étend chaque membre d'un flux d'objets actif en objet autonome. Il construit ensuite un graphe de références et trie les objets en cinq groupes : les objets de niveau document nécessaires pour ouvrir le fichier, les objets de première page, les objets privés d'une page ultérieure, les objets partagés par plusieurs pages ultérieures, et tout le reste

Le piège de cette marche dans le graphe est /Parent. Chaque dictionnaire de page pointe vers le haut de l'arbre de pages, et un parcours naïf suit ce pointeur vers le nœud parent, puis redescend par /Kids dans chaque page sœur et ses flux de contenu. Un pas plus tard, le groupe de première page contient tout le document, et le fichier est techniquement linéarisé mais inutile pour un accès plage d'abord. PDFium Component ignore le lien /Parent de page et les nœuds de l'arbre de pages hors de la page collectée, et c'est ce qui garde petite la section de première page. Sur un échantillon de régression de 51 pages à arbre de pages profond, la section de première page fait 5289 octets, 0.48 pour cent de la sortie de 1105354 octets

Les flux d'objets et flux de références croisées de l'entrée sont aplatis en objets indirects ordinaires de génération 0, chaque référence est réécrite vers les nouveaux numéros, et la sortie utilise des sections de références croisées classiques. Cela coûte un peu de taille de fichier comparé aux flux d'objets compressés, mais cela simplifie la couverture des tables de hints et c'est la disposition que toute visionneuse consciente de la linéarisation gère. Si la compacité compte plus que le Fast Web View pour une sortie donnée, PDFium Component a un rédacteur de flux d'objets séparé pour ce travail

Que contiennent le dictionnaire de linéarisation et les deux sections xref ?

Un fichier linéarisé écrit par PDFium Component porte deux sections de références croisées classiques et un dictionnaire de linéarisation dans le tout premier objet. La première section xref suit le dictionnaire de linéarisation et couvre les objets de niveau document et de première page, si bien qu'une visionneuse peut rendre la page une depuis le seul début du fichier. La section xref principale siège à la fin et couvre tout le reste

Le dictionnaire de linéarisation lui-même porte les nombres dont une visionneuse a besoin avant de lire quoi que ce soit : /L la longueur du fichier, /H le décalage et la longueur du flux de hints, /O le numéro d'objets de la première page, /E la fin de la section de première page, /N le compte de pages, et /T le décalage de la première entrée de la table xref principale. Tout cela dépend de la disposition finale, et le dictionnaire siège avant la disposition. L'écrire avec des nombres de largeur naturelle crée une boucle : une longueur qui grandit de 999999 à 1000000 ajoute un octet, décale chaque décalage suivant, ce qui peut changer la largeur d'un autre nombre, et ainsi de suite

PDFium Component casse la boucle en réservant 320 octets fixes pour le dictionnaire de linéarisation. Chaque objet, le flux de hints, les deux sections xref et les trailers sont écrits d'abord ; une fois tous les décalages finals, le dictionnaire est rempli sur place et paddé à la largeur réservée. Un détail subtil est /T : il doit pointer vers l'entrée objet 0 de la table xref principale, pas vers le mot-clé xref qui la précède. Les validateurs stricts signalent le décalage du mot-clé même si les visionneuses indulgentes ouvrent le fichier quand même

Pourquoi les décalages des tables de hints ignorent-ils le flux de hints ?

Les décalages des tables de hints dans un PDF linéarisé sont interprétés comme si le flux de hints n'existait pas dans le fichier. L'Annexe F le définit ainsi pour que le flux de hints puisse siéger entre la section de première page et le reste sans que sa propre taille re-alimente les nombres qu'il contient. La conséquence pratique pour un rédacteur est facile à rater : tout décalage réel situé après le flux de hints doit être réduit avant d'aller dans la table de hints de décalages de pages ou d'objets partagés

La question est : réduit de combien. La réponse tentante est la longueur des données du flux, parce que c'est la partie qui ressemble au contenu de hints. La bonne réponse est la longueur de l'objet indirect de hints entier, N 0 obj compris, le dictionnaire de flux, les mots-clés stream et endstream, et endobj. Ne soustrayez que la longueur des données et chaque décalage ultérieur est faux de quelques dizaines d'octets. La page une se rend encore parce qu'elle vit avant le flux de hints, si bien que le bug survit à une vérification visuelle rapide et n'apparaît que quand une visionneuse saute à une page ultérieure et demande une plage qui démarre au milieu du mauvais objet

Les deux tables de hints sont compactées bit à bit avec le bit de poids fort d'abord, encore une fois selon l'Annexe F. Le composant vérifie le résultat comme le ferait un vrai client : un magasin de plages éparses ne fournit que le premier Kio de l'échantillon de 51 pages, et la gestion native des hints de PDFium demande le reste. La dernière page s'ouvre sans que le fichier complet ait jamais été présent

Vérifier un PDF linéarisé en Delphi

La vérification la moins chère est de demander à PDFium lui-même. TPdf.InspectAvailability fait tourner l'API de disponibilité sur un flux sans charger le document, et son champ Linearization rapporte plnLinearized, plnNotLinearized ou plnUnknown

function IsFastWebView(const FileName: string): Boolean;
var
  Stream: TFileStream;
  Info: TPdfAvailability;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Info := TPdf.InspectAvailability(Stream);
    Result := Info.Linearization = plnLinearized;
  finally
    Stream.Free;
  end;
end;

Cela confirme que PDFium reconnaît la disposition ; cela ne prouve pas que les hints sont corrects. Pour un vrai test de bout en bout, servez le fichier à travers un transport limité en plages et ouvrez une page tardive avec TPdfProgressiveDocument, de la même façon que le guide de streaming de gros PDF à la demande avec PDFium VCL. Si la page se rend après n'avoir cherché qu'une petite fraction du fichier, les tables de hints font leur travail. Ajouter un second vérificateur de linéarisation indépendant au pipeline de publication vaut aussi le coup, parce que les visionneuses diffèrent dans leur indulgence face à un /T mal placé ou un décalage légèrement faux

Deux limites à garder en tête. Le matérialiseur d'objets rejette encore les tableaux de 2 Go ou plus, si bien que les documents extrêmement grands demandent une autre stratégie pour le moment. Et la linéarisation ne rapporte que si le serveur honore les requêtes HTTP Range ; derrière un mandataire qui étrille Accept-Ranges, un fichier linéarisé se télécharge exactement comme tout autre

FAQ

TPdf.SaveAsLinearized garde-t-il les signatures numériques valides ?

Non. TPdf.SaveAsLinearized renumérote et réécrit chaque objet, si bien que les signatures existantes sont invalidées. Linéarisez d'abord, appliquez ensuite la signature

PDFium Component peut-il linéariser un PDF chiffré ?

Seulement si vous passez saRemoveSecurity. Les clés de chiffrement au niveau objet dépendent des numéros d'objets, que le rédacteur change, si bien que la sortie est écrite sans chiffrement

Quelles versions de PDF la sortie linéarisée prend-elle en charge ?

TPdf.SaveAsLinearized accepte pvUnknown pour garder la version source ou n'importe quelle cible depuis PDF 1.2 vers le haut, et rejette saIncremental parce qu'une révision apposée casserait la disposition

La sortie Fast Web View, le chargement progressif et l'API de disponibilité font tous partie du PDFium Component pour Delphi et C++Builder, qui enveloppe PDFium pour les projets Delphi, C++Builder et Lazarus