Categories: Programmation PDF

Débogage des Problèmes d’Ordre des Pages PDF

Débogage des Problèmes d’Ordre des Pages PDF : Étude de Cas Réelle du Composant HotPDF

La manipulation de PDF peut être délicate, surtout lorsqu’il s’agit de l’ordre des pages. Récemment, nous avons rencontré une session de débogage fascinante qui a révélé des informations importantes sur la structure des documents PDF et l’indexation des pages. Cette étude de cas démontre comment une erreur apparemment simple “off-by-one” s’est transformée en une plongée profonde dans les spécifications PDF et a révélé des malentendus fondamentaux sur la structure des documents.

Concept de l’Ordre des Pages PDF – Relation entre l’Ordre Physique des Objets et l’Ordre Logique des Pages

Le Problème

Nous travaillions sur un utilitaire de copie de pages PDF de notre composant HotPDF Delphi appelé CopyPage qui devait extraire des pages spécifiques d’un document PDF. Le programme était censé copier la première page par défaut, mais il copiait systématiquement la deuxième page à la place. À première vue, cela semblait être un simple bug d’indexation – peut-être avions-nous utilisé une indexation basée sur 1 au lieu de 0, ou fait une erreur arithmétique de base.

Cependant, après avoir vérifié la logique d’indexation plusieurs fois et l’avoir trouvée correcte, nous avons réalisé que quelque chose de plus fondamental était erroné. Le problème n’était pas dans la logique de copie elle-même, mais dans la façon dont le programme interprétait quelle page était la “page 1” en premier lieu.

Les Symptômes

Le problème se manifestait de plusieurs façons :

  1. Décalage constant : Chaque demande de page était décalée d’une position
  2. Reproductible sur plusieurs documents : Le problème se produisait avec plusieurs fichiers PDF différents
  3. Aucune erreur d’indexation évidente : La logique du code semblait correcte lors d’une inspection de surface
  4. Ordre étrange des pages : Lors de la copie de toutes les pages, l’ordre d’un PDF était : 2, 3, 1, et un autre était : 2, 3, 4, 5, 6, 7, 8, 9, 10, 1

Ce dernier symptôme était l’indice clé qui a mené à la percée.

Enquête Initiale

Analyse de la Structure PDF

La première étape était d’examiner la structure du document PDF. Nous avons utilisé plusieurs outils pour comprendre ce qui se passait en interne :

  1. Inspection manuelle du PDF en utilisant un éditeur hexadécimal pour voir la structure brute
  2. Outils en ligne de commande comme qpdf –show-object pour extraire les informations d’objet
  3. Scripts de débogage PDF Python pour tracer le processus d’analyse

En utilisant ces outils, j’ai découvert que le document source avait une structure d’arbre de pages spécifique :

16 0 obj
<<
  /Count 3
  /Kids [
    20 0 R
    1 0 R  
    4 0 R
  ]
  /Type /Pages
>>

Cela montrait que le document contenait 3 pages, mais les objets de page n’étaient pas arrangés dans un ordre séquentiel dans le fichier PDF. Le tableau Kids définissait l’ordre logique des pages :

  • Page 1 : Objet 20
  • Page 2 : Objet 1
  • Page 3 : Objet 4

Le Premier Indice

L’insight critique est venu de l’examen des numéros d’objet par rapport à leurs positions logiques. Notez que :

  • L’objet 1 apparaît en deuxième dans le tableau Kids (page logique 2)
  • L’objet 4 apparaît en troisième dans le tableau Kids (page logique 3)
  • L’objet 20 apparaît en premier dans le tableau Kids (page logique 1)

Cela signifiait que si le code d’analyse construisait son tableau de pages interne basé sur les numéros d’objet ou leur apparence physique dans le fichier, plutôt que de suivre l’ordre du tableau Kids, les pages seraient dans la mauvaise séquence.

Test de l’Hypothèse

Pour vérifier cette théorie, j’ai créé un test simple :

  1. Extraire chaque page individuellement et vérifier le contenu
  2. Comparer les tailles de fichier des pages extraites (différentes pages ont souvent des tailles différentes)
  3. Chercher des marqueurs spécifiques aux pages comme les numéros de page ou les pieds de page

Les résultats du test ont confirmé l’hypothèse :

  • La “page 1” du programme avait un contenu qui devrait être sur la page 2
  • La “page 2” du programme avait un contenu qui devrait être sur la page 3
  • La “page 3” du programme avait un contenu qui devrait être sur la page 1

Ce motif de décalage circulaire était la preuve irréfutable que le tableau de pages était construit incorrectement.

La Cause Racine

Comprendre la Logique d’Analyse

Le problème central était que le code d’analyse PDF construisait son tableau de pages interne (PageArr) basé sur l’ordre physique des objets dans le fichier PDF, et non sur l’ordre logique défini par la structure de l’arbre Pages.

Voici ce qui se passait pendant le processus d’analyse :

// Logique d'analyse problématique (simplifiée)
procedure BuildPageArray;
begin
  PageArrPosition := 0;
  SetLength(PageArr, PageCount);
  
  // Itérer à travers tous les objets dans l'ordre physique du fichier
  for i := 0 to IndirectObjects.Count - 1 do
  begin
    CurrentObj := IndirectObjects.Items[i];
    if IsPageObject(CurrentObj) then
    begin
      PageArr[PageArrPosition] := CurrentObj;  // Erreur : ordre physique
      Inc(PageArrPosition);
    end;
  end;
end;

Cela résultait en :

  • PageArr[0] contenait l’Objet 1 (en fait la page logique 2)
  • PageArr[1] contenait l’Objet 4 (en fait la page logique 3)
  • PageArr[2] contenait l’Objet 20 (en fait la page logique 1)

Quand le code essayait de copier la “page 1” en utilisant PageArr[0], il copiait en fait la mauvaise page.

Les Deux Ordres Différents

Le problème provenait de la confusion entre deux façons différentes d’ordonner les pages :

Ordre Physique (comment les objets apparaissent dans le fichier PDF) :


Objet 1 (Objet Page) → Index 0 dans PageArr
Objet 4 (Objet Page) → Index 1 dans PageArr  
Objet 20 (Objet Page) → Index 2 dans PageArr

Ordre Logique (défini par le tableau Kids de l’arbre Pages) :


Kids[0] = 20 0 R → Devrait être Index 0 dans PageArr (Page 1)
Kids[1] = 1 0 R  → Devrait être Index 1 dans PageArr (Page 2)
Kids[2] = 4 0 R  → Devrait être Index 2 dans PageArr (Page 3)

Le code d’analyse utilisait l’ordre physique, mais les utilisateurs s’attendaient à l’ordre logique.

Pourquoi Cela Arrive

Les fichiers PDF ne sont pas nécessairement écrits avec les pages dans un ordre séquentiel. Cela peut arriver pour plusieurs raisons :

  1. Mises à jour incrémentales : Les pages ajoutées plus tard obtiennent des numéros d’objet plus élevés
  2. Générateurs PDF : Différents outils peuvent organiser les objets différemment
  3. Optimisation : Certains outils réorganisent les objets pour la compression ou les performances
  4. Historique d’édition : Les modifications de document peuvent causer une renumérotation des objets

Complexité Additionnelle : Multiples Chemins d’Analyse

Il y a deux chemins d’analyse différents dans notre composant HotPDF VCL :

  1. Analyse traditionnelle : Utilisée pour les anciens formats PDF 1.3/1.4
  2. Analyse moderne : Utilisée pour les PDF avec des flux d’objets et des fonctionnalités plus récentes (PDF 1.5/1.6/1.7)

Le bug devait être corrigé dans les deux chemins, car ils construisaient le tableau de pages différemment mais tous deux ignoraient l’ordre logique défini par le tableau Kids.

La Solution

Conception de la Correction

La correction nécessitait l’implémentation d’une fonction de réorganisation des pages qui restructurerait le tableau de pages interne pour correspondre à l’ordre logique défini dans l’arbre Pages du PDF. Cela devait être fait avec précaution pour éviter de casser les fonctionnalités existantes.

Stratégie d’Implémentation

La solution impliquait plusieurs composants clés :

procedure ReorderPageArrByPagesTree;
begin
  // 1. Trouver l'objet Pages racine
  // 2. Extraire le tableau Kids  
  // 3. Réorganiser PageArr pour correspondre à l'ordre Kids
  // 4. S'assurer que les indices de page correspondent aux numéros de page logiques
end;

Implémentation Détaillée

Voici la fonction de réorganisation complète :

procedure THotPDF.ReorderPageArrByPagesTree;
var
  RootObj: THPDFDictionaryObject;
  PagesObj: THPDFDictionaryObject;
  KidsArray: THPDFArrayObject;
  NewPageArr: array of THPDFDictArrItem;
  I, J, KidsIndex, TypeIndex, PageIndex: Integer;
  KidsItem: THPDFObject;
  RefObj: THPDFLink;
  PageObjNum: Integer;
  TypeObj: THPDFNameObject;
  Found: Boolean;
begin
  WriteLn('[DEBUG] Début de ReorderPageArrByPagesTree');
  
  try
    // Étape 1 : Trouver l'objet Root
    RootObj := nil;
    if (FRootIndex >= 0) and (FRootIndex < IndirectObjects.Count) then
    begin
      RootObj := THPDFDictionaryObject(IndirectObjects.Items[FRootIndex]);
      WriteLn('[DEBUG] Objet Root trouvé à l\'index ', FRootIndex);
    end
    else
    begin
      WriteLn('[DEBUG] Objet Root non trouvé, impossible de réorganiser les pages');
      Exit;
    end;

    // Étape 2 : Trouver l'objet Pages depuis Root
    PagesObj := nil;
    if RootObj <> nil then
    begin
      var PagesIndex := RootObj.FindValue('Pages');
      if PagesIndex >= 0 then
      begin
        var PagesRef := RootObj.GetIndexedItem(PagesIndex);
        if PagesRef is THPDFLink then
        begin
          var PagesObjIndex := THPDFLink(PagesRef).ObjectIndex;
          if (PagesObjIndex >= 0) and (PagesObjIndex < IndirectObjects.Count) then
          begin
            PagesObj := THPDFDictionaryObject(IndirectObjects.Items[PagesObjIndex]);
            WriteLn('[DEBUG] Objet Pages trouvé à l\'index ', PagesObjIndex);
          end;
        end;
      end;
    end;

    if PagesObj = nil then
    begin
      WriteLn('[DEBUG] Objet Pages non trouvé, impossible de réorganiser');
      Exit;
    end;

    // Étape 3 : Extraire le tableau Kids
    KidsIndex := PagesObj.FindValue('Kids');
    if KidsIndex < 0 then begin WriteLn('[DEBUG] Tableau Kids non trouvé dans l\'objet Pages'); Exit; end; KidsArray := THPDFArrayObject(PagesObj.GetIndexedItem(KidsIndex)); if KidsArray = nil then begin WriteLn('[DEBUG] Tableau Kids invalide'); Exit; end; WriteLn('[DEBUG] Tableau Kids trouvé avec ', KidsArray.Count, ' éléments'); // Étape 4 : Créer un nouveau tableau de pages dans l'ordre logique SetLength(NewPageArr, Length(PageArr)); for I := 0 to KidsArray.Count - 1 do begin KidsItem := KidsArray.GetIndexedItem(I); if KidsItem is THPDFLink then begin RefObj := THPDFLink(KidsItem); PageObjNum := RefObj.ObjectIndex; WriteLn('[DEBUG] Traitement de l\'élément Kids[', I, '] -> Objet ', PageObjNum);
        
        // Trouver cet objet dans le PageArr actuel
        Found := False;
        for J := 0 to Length(PageArr) - 1 do
        begin
          if PageArr[J].ObjectIndex = PageObjNum then
          begin
            NewPageArr[I] := PageArr[J];
            Found := True;
            WriteLn('[DEBUG] Page trouvée : Kids[', I, '] mappé vers PageArr[', J, ']');
            Break;
          end;
        end;
        
        if not Found then
        begin
          WriteLn('[DEBUG] AVERTISSEMENT : Objet page ', PageObjNum, ' non trouvé dans PageArr');
        end;
      end;
    end;

    // Étape 5 : Remplacer l'ancien tableau par le nouveau
    for I := 0 to Length(NewPageArr) - 1 do
    begin
      PageArr[I] := NewPageArr[I];
    end;
    
    WriteLn('[DEBUG] Réorganisation des pages terminée avec succès');
    
  except
    on E: Exception do
    begin
      WriteLn('[DEBUG] ERREUR dans ReorderPageArrByPagesTree : ', E.Message);
    end;
  end;
end;

Points d’Intégration

La fonction de réorganisation doit être appelée aux bons moments dans le processus d’analyse :

// Dans la méthode d'analyse principale
procedure THotPDF.ParsePDFDocument;
begin
  // ... logique d'analyse existante ...
  
  // Construire le tableau de pages initial (ordre physique)
  BuildInitialPageArray;
  
  // NOUVEAU : Réorganiser selon l'ordre logique
  ReorderPageArrByPagesTree;
  
  // ... continuer avec le reste de l'analyse ...
end;

Gestion des Erreurs

Plusieurs cas limites doivent être gérés :

  1. PDF corrompus : Objets manquants ou références invalides
  2. Structures non standard : Arbres de pages imbriqués ou complexes
  3. Incohérences de données : Nombre de pages ne correspondant pas au tableau Kids
  4. Contraintes de mémoire : Documents très volumineux

Cas Limites

La solution doit également gérer :

  • Arbres de pages imbriqués : Quand les pages sont organisées en sous-arbres
  • Pages héritées : Propriétés héritées des nœuds parents
  • Références circulaires : Prévention des boucles infinies
  • Objets compressés : Pages dans des flux d’objets

Techniques de Débogage

Isolation par Étapes

Pour déboguer ce type de problème, nous avons utilisé une approche d’isolation par étapes :

  1. Analyse PDF : Vérifier que la structure PDF est correctement analysée
  2. Construction du tableau de pages : Valider que tous les objets de page sont trouvés
  3. Copie de pages : Tester la logique de copie avec des indices connus
  4. Validation de sortie : Vérifier que les pages copiées ont le bon contenu

Analyse de Différences Binaires

Comparer les fichiers PDF au niveau binaire a révélé des motifs :

// Comparer les structures d'objets
qpdf --show-object=1 input.pdf > obj1.txt
qpdf --show-object=4 input.pdf > obj4.txt
qpdf --show-object=20 input.pdf > obj20.txt

// Analyser les références croisées
qpdf --show-xref input.pdf

Comparaison d’Implémentations de Référence

Tester contre des bibliothèques PDF connues :

# Script Python pour validation
import PyPDF2

def validate_page_order(pdf_path):
    with open(pdf_path, 'rb') as file:
        reader = PyPDF2.PdfReader(file)
        for i, page in enumerate(reader.pages):
            print(f"Page {i+1}: {page.extract_text()[:50]}...")

validate_page_order('test.pdf')

Débogage Mémoire

Utiliser des outils de profilage mémoire pour détecter :

  • Fuites mémoire : Objets non libérés après l’analyse
  • Corruption de données : Écrasement de tableaux ou pointeurs invalides
  • Problèmes d’alignement : Accès mémoire non alignés

Archéologie de Contrôle de Version

Examiner l’historique Git pour comprendre quand le problème a été introduit :

// Trouver quand le comportement a changé
git bisect start
git bisect bad HEAD
git bisect good v2.1.0

// Tester chaque commit
git bisect run ./test_page_order.sh

Leçons Apprises

Ordre Logique vs Physique PDF

La leçon la plus importante est la distinction entre :

  • Ordre physique : Comment les objets sont stockés dans le fichier
  • Ordre logique : Comment les pages doivent être présentées à l’utilisateur

Toujours suivre l’ordre logique défini par la structure de l’arbre Pages.

Timing de Correction

La réorganisation doit se produire :

  • Après la construction du tableau de pages initial
  • Avant toute opération de page (copie, suppression, etc.)
  • Une seule fois par session d’analyse de document

Multiples Chemins d’Analyse

Les bibliothèques PDF modernes ont souvent plusieurs chemins d’analyse :

  • Analyse héritée : Pour les anciens formats PDF
  • Analyse moderne : Pour les PDF avec flux d’objets
  • Analyse de récupération : Pour les PDF corrompus

Chaque chemin doit être testé et corrigé indépendamment.

Tests Approfondis

Les tests doivent inclure :

  • PDF de différents générateurs : Adobe, LibreOffice, LaTeX, etc.
  • Différentes versions PDF : 1.3, 1.4, 1.5, 1.6, 1.7
  • Différentes tailles : Documents d’une page à des milliers de pages
  • Différentes complexités : Simples, avec annotations, chiffrés

Stratégies de Prévention

Validation Proactive de Structure PDF

Implémenter des vérifications de validation pendant l’analyse :

procedure ValidatePDFStructure;
begin
  // Vérifier la cohérence de l'arbre Pages
  ValidatePagesTree;
  
  // Vérifier que le nombre de pages correspond
  ValidatePageCount;
  
  // Vérifier l'intégrité des références
  ValidateObjectReferences;
  
  // Vérifier l'ordre logique vs physique
  ValidatePageOrdering;
end;

Cadre de Journalisation Complet

Implémenter une journalisation détaillée pour le débogage :

procedure LogPageStructure;
var
  I: Integer;
begin
  WriteLn('[PDF_STRUCTURE] === Analyse de la Structure des Pages ===');
  WriteLn('[PDF_STRUCTURE] Nombre total de pages : ', Length(PageArr));
  
  for I := 0 to Length(PageArr) - 1 do
  begin
    WriteLn(Format('[PDF_STRUCTURE] Page[%d] -> Objet %d (Physique: %d)', 
      [I, PageArr[I].ObjectIndex, PageArr[I].PhysicalPosition]));
  end;
  
  WriteLn('[PDF_STRUCTURE] === Fin de l\'Analyse ===');
 end;

Stratégies de Test Diversifiées

Créer une suite de tests complète :

procedure RunPageOrderTests;
begin
  // Tests de base
  TestSinglePagePDF;
  TestMultiPageSequentialPDF;
  TestMultiPageNonSequentialPDF;
  
  // Tests de cas limites
  TestEmptyPDF;
  TestCorruptedPDF;
  TestEncryptedPDF;
  
  // Tests de performance
  TestLargeDocuments;
  TestMemoryUsage;
  
  // Tests de régression
  TestKnownProblematicPDFs;
end;

Compréhension Approfondie des Spécifications PDF

Étudier les spécifications PDF officielles :

  • ISO 32000-1:2008 : Spécification PDF 1.7
  • ISO 32000-2:2017 : Spécification PDF 2.0
  • Adobe PDF Reference : Documentation historique
  • Guides d’implémentation : Meilleures pratiques de la communauté

Tests de Régression Automatisés

Mettre en place une infrastructure de test automatisée :

# Script de test automatisé
#!/bin/bash

echo "Exécution des tests d'ordre des pages PDF..."

# Tester avec différents PDF
for pdf in test_files/*.pdf; do
    echo "Test de $pdf"
    ./pdf_page_order_test "$pdf"
    if [ $? -ne 0 ]; then
        echo "ÉCHEC : $pdf"
        exit 1
    fi
done

echo "Tous les tests réussis !"

Techniques de Débogage Avancées

Profilage de Performance

Analyser les goulots d’étranglement de performance :

procedure ProfilePageReordering;
var
  StartTime, EndTime: TDateTime;
  ElapsedMs: Integer;
begin
  StartTime := Now;
  
  ReorderPageArrByPagesTree;
  
  EndTime := Now;
  ElapsedMs := MilliSecondsBetween(EndTime, StartTime);
  
  WriteLn(Format('[PERFORMANCE] Réorganisation des pages terminée en %d ms', [ElapsedMs]));
  
  if ElapsedMs > 1000 then
    WriteLn('[PERFORMANCE] AVERTISSEMENT : Réorganisation lente détectée');
end;

Analyse d’Utilisation Mémoire

Surveiller l’utilisation mémoire pendant l’analyse :

procedure MonitorMemoryUsage;
var
  MemBefore, MemAfter: Cardinal;
begin
  MemBefore := GetHeapStatus.TotalAllocated;
  
  ReorderPageArrByPagesTree;
  
  MemAfter := GetHeapStatus.TotalAllocated;
  
  WriteLn(Format('[MEMORY] Utilisation mémoire : %d bytes -> %d bytes (delta: %d)', 
    [MemBefore, MemAfter, MemAfter - MemBefore]));
end;

Validation Multi-plateforme

Tester sur différentes plateformes et architectures :

  • Windows : 32-bit et 64-bit

Cette étude de cas démontre l’importance de comprendre les subtilités des spécifications PDF lors du développement de bibliothèques de manipulation PDF. Ce qui semblait être un simple bug “off-by-one” s’est révélé être un malentendu fondamental sur la façon dont les pages PDF sont organisées et référencées.

Points Clés Techniques

  1. Ordre Logique vs Physique : Les pages PDF ont un ordre logique (défini par l’arbre Pages) qui peut différer de leur ordre physique dans le fichier
  2. Multiples Chemins d’Analyse : Les bibliothèques PDF modernes doivent gérer différents formats et versions, chacun nécessitant une attention particulière
  3. Conformité aux Spécifications : Suivre strictement les spécifications PDF est crucial pour la compatibilité
  4. Timing des Opérations : La réorganisation des pages doit se produire au bon moment dans le pipeline d’analyse

Recommandations de Gestion de Projet

  1. Tests Complets : Investir dans une suite de tests robuste avec des PDF du monde réel
  2. Journalisation Détaillée : Implémenter une journalisation complète pour faciliter le débogage
  3. Validation Continue : Vérifications automatisées de l’intégrité des données
  4. Documentation : Documenter les cas limites et les décisions d’implémentation

Recommandations Techniques

  1. Validation Proactive : Vérifier la structure PDF pendant l’analyse
  2. Gestion d’Erreurs Robuste : Gérer gracieusement les PDF corrompus ou non standard
  3. Optimisation des Performances : Surveiller et optimiser l’utilisation mémoire et CPU
  4. Compatibilité Étendue : Tester avec des PDF de différents générateurs et versions

Impact sur les Utilisateurs

Cette correction améliore significativement l’expérience utilisateur :

  • Fiabilité : Les opérations de page fonctionnent comme attendu
  • Prévisibilité : Le comportement est cohérent entre différents PDF
  • Compatibilité : Fonctionne avec une gamme plus large de documents PDF
  • Confiance : Les développeurs peuvent faire confiance à la bibliothèque pour gérer correctement les pages

Travaux Futurs

Cette expérience suggère plusieurs domaines d’amélioration :

  1. Validation PDF Étendue : Implémenter des vérifications plus complètes de la structure PDF
  2. Outils de Diagnostic : Développer des utilitaires pour analyser et diagnostiquer les problèmes PDF
  3. Tests Automatisés : Étendre la couverture de test avec plus de PDF du monde réel
  4. Documentation : Créer des guides pour les développeurs sur les pièges courants du PDF

Cette étude de cas fait partie de notre engagement continu à améliorer la qualité et la fiabilité du composant HotPDF Delphi. En partageant nos expériences de débogage, nous espérons aider d’autres développeurs à éviter des pièges similaires et à construire des applications PDF plus robustes.

À propos de HotPDF : HotPDF est un composant PDF natif Delphi qui permet aux développeurs de créer, modifier et manipuler des documents PDF directement depuis leurs applications Delphi. Il offre un contrôle complet sur la génération PDF sans dépendances externes, ce qui en fait un choix idéal pour les applications d’entreprise nécessitant des capacités PDF robustes.

losLab

Devoted to developing PDF and Spreadsheet developer library, including PDF creation, PDF manipulation, PDF rendering library, and Excel Spreadsheet creation & manipulation library.

Recent Posts

HotPDF Delphi组件:在PDF文档中创建垂直文本布局

HotPDF Delphi组件:在PDF文档中创建垂直文本布局 本综合指南演示了HotPDF组件如何让开发者轻松在PDF文档中生成Unicode垂直文本。 理解垂直排版(縦書き/세로쓰기/竖排) 垂直排版,也称为垂直书写,中文称为縱書,日文称为tategaki(縦書き),是一种起源于2000多年前古代中国的传统文本布局方法。这种书写系统从上到下、从右到左流动,创造出具有深厚文化意义的独特视觉外观。 历史和文化背景 垂直书写系统在东亚文学和文献中发挥了重要作用: 中国:传统中文文本、古典诗歌和书法主要使用垂直布局。现代简体中文主要使用横向书写,但垂直文本在艺术和仪式场合仍然常见。 日本:日语保持垂直(縦書き/tategaki)和水平(横書き/yokogaki)两种书写系统。垂直文本仍广泛用于小说、漫画、报纸和传统文档。 韩国:历史上使用垂直书写(세로쓰기),但现代韩语(한글)主要使用水平布局。垂直文本出现在传统场合和艺术应用中。 越南:传统越南文本在使用汉字(Chữ Hán)书写时使用垂直布局,但随着拉丁字母的采用,这种做法已基本消失。 垂直文本的现代应用 尽管全球趋向于水平书写,垂直文本布局在几个方面仍然相关: 出版:台湾、日本和香港的传统小说、诗集和文学作品…

2 days ago

HotPDF Delphi 컴포넌트: PDF 문서에서 세로쓰기

HotPDF Delphi 컴포넌트: PDF 문서에서 세로쓰기 텍스트 레이아웃 생성 이 포괄적인 가이드는 HotPDF 컴포넌트를 사용하여…

2 days ago

HotPDF Delphiコンポーネント-PDFドキュメントでの縦書き

HotPDF Delphiコンポーネント:PDFドキュメントでの縦書きテキストレイアウトの作成 この包括的なガイドでは、HotPDFコンポーネントを使用して、開発者がPDFドキュメントでUnicode縦書きテキストを簡単に生成する方法を実演します。 縦書き組版の理解(縦書き/세로쓰기/竖排) 縦書き組版は、日本語では縦書きまたはたてがきとも呼ばれ、2000年以上前の古代中国で生まれた伝統的なテキストレイアウト方法です。この書字体系は上から下、右から左に流れ、深い文化的意義を持つ独特の視覚的外観を作り出します。 歴史的・文化的背景 縦書きシステムは東アジアの文学と文書において重要な役割を果たしてきました: 中国:伝統的な中国語テキスト、古典詩、書道では主に縦書きレイアウトが使用されていました。現代の簡体字中国語は主に横書きを使用していますが、縦書きテキストは芸術的・儀式的な文脈で一般的です。 日本:日本語は縦書き(縦書き/たてがき)と横書き(横書き/よこがき)の両方の書字体系を維持しています。縦書きテキストは小説、漫画、新聞、伝統的な文書で広く使用されています。 韓国:歴史的には縦書き(세로쓰기)を使用していましたが、現代韓国語(한글)は主に横書きレイアウトを使用しています。縦書きテキストは伝統的な文脈や芸術的応用で見られます。 ベトナム:伝統的なベトナム語テキストは漢字(Chữ Hán)で書かれた際に縦書きレイアウトを使用していましたが、この慣行はラテン文字の採用とともにほぼ消失しました。 縦書きテキストの現代的応用 横書きへの世界的な傾向にもかかわらず、縦書きテキストレイアウトはいくつかの文脈で関連性を保っています: 出版:台湾、日本、香港の伝統的な小説、詩集、文学作品…

2 days ago

Отладка проблем порядка страниц PDF: Реальный кейс-стади

Отладка проблем порядка страниц PDF: Реальный кейс-стади компонента HotPDF Опубликовано losLab | Разработка PDF |…

4 days ago

PDF 페이지 순서 문제 디버깅: HotPDF 컴포넌트 실제 사례 연구

PDF 페이지 순서 문제 디버깅: HotPDF 컴포넌트 실제 사례 연구 발행자: losLab | PDF 개발…

4 days ago

PDFページ順序問題のデバッグ:HotPDFコンポーネント実例研究

PDFページ順序問題のデバッグ:HotPDFコンポーネント実例研究 発行者:losLab | PDF開発 | Delphi PDFコンポーネント PDF操作は特にページ順序を扱う際に複雑になることがあります。最近、私たちはPDF文書構造とページインデックスに関する重要な洞察を明らかにした魅力的なデバッグセッションに遭遇しました。このケーススタディは、一見単純な「オフバイワン」エラーがPDF仕様の深い調査に発展し、文書構造に関する根本的な誤解を明らかにした過程を示しています。 PDFページ順序の概念 - 物理的オブジェクト順序と論理的ページ順序の関係 問題 私たちはHotPDF DelphiコンポーネントのCopyPageと呼ばれるPDFページコピーユーティリティに取り組んでいました。このプログラムはデフォルトで最初のページをコピーするはずでしたが、代わりに常に2番目のページをコピーしていました。一見すると、これは単純なインデックスバグのように見えました -…

4 days ago