Τεχνικό Άρθρο

Εξαγωγή κειμένου PDF σε σειρά δομής σε Delphi με HotPDF

Κάθε γεωμετρικός εξαγωγέας κειμένου μαντεύει. Διαβάζει τα glyphs που ζωγραφίζει μια σελίδα, τα ταξινομεί κατά baseline και οριζόντια θέση, και ελπίζει η οπτική διάταξη να ταιριάζει με τη σειρά που θα διάβαζε ένας άνθρωπος. Σε μια αναφορά μίας στήλης η μαντεψιά είναι σωστή. Σε ένα άρθρο περιοδικού δύο στηλών, μια φόρμα με πλευρική στήλη, ή έναν πίνακα του οποίου τα κελιά εκπέμφθηκαν στήλη προς στήλη, είναι λάθος με τρόπους δύσκολους να προσέξει κανείς και ακριβούς να ανακαλυφθούν κατάντη. Το HotPDF απαντά σε αυτό με την ExtractLoadedPageStructureText, που αγνοεί τελείως τη γεωμετρία: διατρέχει το δέντρο δομής του εγγράφου σε σειρά συγγραφής όπως ορίζεται στο ISO 32000-1 §14.8.4, και μετά επανασυνθέτει τα glyphs της σελίδας κατά το marked-content identifier τους. Για ένα tagged PDF αυτό δεν είναι ευριστική, είναι η σειρά που δήλωσε η εφαρμογή παραγωγής

Η συνάρτηση επιστρέφει False όταν η σελίδα δεν έχει χρηστικό δέντρο δομής, που είναι το σήμα να πέσετε στη γεωμετρική εξαγωγή αντί να αποτύχετε. Ο σχεδιασμός των δύο διαδρομών έχει μεγαλύτερη σημασία από τον αλγόριθμο: η πραγματική εισροή εγγράφων βλέπει tagged κυβερνητικές φόρμες και έξοδο scanner στον ίδιο φάκελο, και ένας αγωγός που χειρίζεται μόνο το ένα δεν είναι αγωγός

Γιατί η γεωμετρική εξαγωγή παίρνει λάθος τη σειρά ανάγνωσης;

Επειδή ένα content stream PDF δεν κουβαλά καθόλου σειρά ανάγνωσης. Είναι μια αλληλουχία τελεστών ζωγραφικής, και ένας παραγωγός είναι ελεύθερος να τους εκπέμψει σε όποια αλληλουχία βολεύει τη δική του μηχανή διάταξης. Οι επεξεργαστές κειμένου εκπέμπουν συνήθως σε σειρά ροής και η γεωμετρική ταξινόμηση φαίνεται καλή. Τα εργαλεία διάταξης, οι σχεδιαστές φορμών και οι γεννήτριες αναφορών συχνά όχι: ένας υποσέλιδος σελίδας μπορεί να εκπεμφθεί πριν από το κυρίως σώμα, ένας πίνακας μπορεί να γεμίσει κατά στήλες, και μια σελίδα δύο στηλών μπορεί να πλέκει γραμμές και από τις δύο στήλες επειδή ο συνθέτης τις έλυσε μαζί

Σύγκριση σελίδας PDF δύο στηλών που δείχνει γεωμετρική εξαγωγή ταξινομημένη σε baseline να ράβει στήλες έναντι εξαγωγής MCID σειράς δομής στο HotPDF
Η ταξινόμηση glyphs κατά baseline πλέκει δύο στήλες σε ανοησίες, ενώ το δέντρο δομής αναπαράγει τη σειρά που δήλωσε ο παραγωγός

Η λειτουργία αστοχίας είναι ήσυχη. Ένας γεωμετρικός εξαγωγέας δεν αναφέρει ποτέ σφάλμα, απλώς παραδίδει πεζό κείμενο του οποίου οι προτάσεις είναι ραμμένες από δύο στήλες. Οτιδήποτε καταναλώνει αυτό το κείμενο, ένα ευρετήριο αναζήτησης, ένας mapper πεδίων ηλεκτρονικού τιμολογίου, ένας αγωγός ανάκτησης που τροφοδοτεί γλωσσικό μοντέλο, κληρονομεί τη ζημιά χωρίς προειδοποίηση. Το HotPDF διανέμει επίσης τους γεωμετρικούς εξαγωγείς για φορτωμένα έγγραφα, και αυτοί παραμένουν το σωστό εργαλείο για αρχεία χωρίς ετικέτες· το νόημα της διαδρομής σειράς δομής είναι να σταματήσει η μαντεψιά όταν το έγγραφο κουβαλά ήδη την απάντηση

Τι αποθηκεύει πραγματικά το δέντρο δομής

Ένα tagged PDF κρατά μια δεύτερη, παράλληλη περιγραφή της σελίδας. Ο κατάλογος δείχνει σε ένα /StructTreeRoot, του οποίου τα τέκνα /K σχηματίζουν δέντρο στοιχείων δομής: /Document, /Sect, /P, /Table, /TR, /TD, και ούτω καθεξής. Τα φύλλα εκείνου του δέντρου είναι αναφορές marked-content, ακέραιοι που ονομάζουν ένα τμήμα του content stream της σελίδας. Στην πλευρά περιεχομένου, εκείνα τα τμήματα ανοίγουν με τελεστή BDC που κουβαλά /MCID και κλείνουν με EMC. Κάθε στοιχείο δομής κουβαλά επίσης εγγραφή /Pg που ονομάζει τη σελίδα στην οποία ανήκει, που είναι αυτό που κάνει δυνατή τη διάτρεξη ανά σελίδα σε ένα έγγραφο του οποίου το δέντρο δομής απλώνεται σε εκατοντάδες σελίδες

Ανατομία δέντρου δομής PDF που συνδέει στοιχεία StructTreeRoot όπως Sect, Table, TR και TD με τμήματα BDC MCID στο content stream της σελίδας του HotPDF
Τα φύλλα του δέντρου είναι αναφορές marked-content, και κάθε στοιχείο κουβαλά εγγραφή Pg που αφήνει τη διάτρεξη να φιλτράρει στην τρέχουσα σελίδα

Το HotPDF διατρέχει εκείνο το δέντρο με όριο βάθους 128 επιπέδων και φιλτράρει στο /Pg ώστε μόνο η τρέχουσα σελίδα να συνεισφέρει. Η έξοδος της διάτρεξης δεν είναι κείμενο, είναι μια διατεταγμένη λίστα τιμών MCID: η σειρά συγγραφής των τμημάτων marked-content σε αυτή τη σελίδα. Η επανασύνθεση κειμένου είναι τότε θέμα αναπαραγωγής των glyphs σε εκείνη τη σειρά

Το MCID καταγράφεται κατά την εξαγωγή glyphs, δεν αναζητείται μετά

Αυτή είναι η λεπτομέρεια υλοποίησης που κάνει τη λειτουργία φθηνή. Το HotPDF καταγράφει ήδη το ενεργό marked-content identifier σε κάθε glyph που εξάγει, στο πεδίο MCID του THPDFGlyphRecord, επειδή ο διερμηνέας content stream ξέρει ποιο εύρος BDC είναι ανοιχτό τη στιγμή που επεξεργάζεται κάθε τελεστή Tj ή TJ. Η εξαγωγή σειράς δομής χρειάζεται επομένως κανένα δεύτερο πέρασμα πάνω στο content stream. Συλλέγει την αλληλουχία MCID από το δέντρο δομής, και μετά κατηγοριοποιεί τα ήδη εξαχμένα glyphs ανά MCID και τα εκπέμπει σε εκείνη την αλληλουχία

var
  Pdf: THotPDF;
  PageCount, I, Untagged: Integer;
  PageText, AllText: UnicodeString;
  Report: TStrings;   // δοχείο διαγνωστικών υπό ιδιοκτησία καλούντος
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('accessible-form.pdf');
    AllText := '';
    for I := 0 to PageCount - 1 do
    begin
      if Pdf.ExtractLoadedPageStructureText(I, PageText, Untagged) then
      begin
        // Σειρά συγγραφής ευθεία από το δέντρο δομής
        if Untagged > 0 then
          Report.Add(Format('page %d: %d glyphs outside the structure tree',
            [I, Untagged]));
      end
      else
        // Χωρίς χρηστικό δέντρο δομής στη σελίδα: γεωμετρική εναλλακτική
        Pdf.ExtractLoadedPageText(I, PageText);
      AllText := AllText + PageText + #13#10;
    end;
  finally
    Pdf.Free;
  end;
end;

Τα glyphs χωρίς ετικέτα μετριούνται, δεν εγκαταλείπονται ποτέ σιωπηλά

Μια σελίδα μπορεί να είναι μερικώς με ετικέτες. Οι παραγωγοί προσθέτουν μια διακοσμητική γραμμή, έναν αριθμό σελίδας, ή ένα υδατογράφημα τελευταίας στιγμής έξω από κάθε εύρος BDC, και εκείνα τα glyphs δεν ανήκουν σε κανένα MCID. Η εγκατάλειψή τους θα ήταν η τακτοποιημένη υλοποίηση και η λάθος, γιατί το ίδιο κενό εμφανίζεται και όταν ένας παραγωγός βάζει ετικέτα στο σώμα αλλά ξεχνά τον πίνακα, και θα χάνατε τον πίνακα χωρίς να το προσέξετε

Το HotPDF προσαπτά τα ανεκδίκαφα glyphs ως γεωμετρική ουρά μετά το κείμενο σειράς δομής και αναφέρει τον αριθμό τους μέσω της παραμέτρου εξόδου UntaggedGlyphCount. Ο αριθμός εκείνος είναι σήμα ποιότητας στο οποίο μπορείτε να δράσετε. Μια χούφτα glyphs σε σελίδα των δύο χιλιάδων είναι έπιπλο σελίδας και μπορεί να αγνοηθεί. Σαράντα τοις εκατό της σελίδας έξω από το δέντρο δομής σημαίνει ότι η ετικετοποίηση είναι διακοσμητική και ο γεωμετρικός εξαγωγέας είναι η πιο ειλικρινής απάντηση για εκείνο το αρχείο

Ροή απόφασης για εξαγωγή κειμένου δομής HotPDF με γεωμετρική εναλλακτική όταν μια σελίδα δεν έχει χρηστικό δέντρο δομής ή διακοσμητική ετικετοποίηση
True σημαίνει σειρά δομής με την ουρά χωρίς ετικέτες προσαρτημένη, και False δρομολογεί τη σελίδα στον γεωμετρικό εξαγωγέα αντί να αποτύχει
function ExtractPageBestEffort(Pdf: THotPDF; PageIndex: Integer;
  out AText: UnicodeString; out UsedStructure: Boolean): Boolean;
var
  Untagged, TotalGlyphs: Integer;
  Glyphs: THPDFGlyphArray;
begin
  UsedStructure := False;
  if Pdf.ExtractLoadedPageStructureText(PageIndex, AText, Untagged) then
  begin
    TotalGlyphs := 0;
    if Pdf.ExtractLoadedPageGlyphs(PageIndex, Glyphs) then
      TotalGlyphs := Length(Glyphs);
    // Εμπιστευτείτε το δέντρο δομής μόνο όταν διεκδικεί το μεγαλύτερο
    // μέρος της σελίδας
    if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
    begin
      UsedStructure := True;
      Result := True;
      Exit;
    end;
  end;
  Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;

Τι κάνει τη συνάρτηση να επιστρέφει False

Τρεις περιπτώσεις, και αξίζει να διακριθούν επειδή μόνο μία από αυτές είναι ελάττωμα στο έγγραφο. Η πρώτη είναι ένα συνηθισμένο PDF χωρίς ετικέτες: κανένα /StructTreeRoot, τίποτα προς διάτρεξη, και το False είναι απλώς η αλήθεια. Η δεύτερη είναι μια σαρωμένη σελίδα του οποίου το κείμενο προέρχεται από στρώμα OCR που δεν ετικετοποιήθηκε ποτέ. Η τρίτη είναι η ενδιαφέρουσα: περιεχόμενο που κουβαλά τελεστές BDC με τιμές /MCID αλλά του οποίου η σελίδα δεν έχει εγγραφή /StructParents και του οποίου το δέντρο δομής δεν αναφέρεται ποτέ σε εκείνα τα αναγνωριστικά. Το marked content υπάρχει, η πλευρά δομής δεν υπάρχει, και δεν υπάρχει σειρά προς ανάκτηση. Το HotPDF αναφέρει False αντί να εφεύρει μία

Εκείνη η τελευταία περίπτωση εμφανίζεται σε αρχεία επεξεργασμένα με το χέρι και σε έξοδο από εργαλεία που εκπέμπουν marked content για σκοπούς optional-content ή artifact χωρίς να χτίζουν δέντρο δομής. Αν παράγετε εσείς tagged PDF, η ίδια ασυμμετρία είναι αυτό που ελέγχει η επικύρωση PDF/UA, και το αντίστοιχο πλευράς writer καλύπτεται στο layout DOM που εκπέμπει tagged, σελιδοποιημένη έξοδο

Πού η σειρά δομής αποπληρώνεται

Ο έλεγχος προσβασιμότητας είναι το προφανές: αν πιστοποιείτε ένα έγγραφο απέναντι στο PDF/UA, η σειρά ανάγνωσης που θα ανακοινώσει ένας screen reader είναι ακριβώς η σειρά δομής, οπότε η εξαγωγή της είναι ο τρόπος να την εξετάσετε χωρίς screen reader. Η σύλληψη δεδομένων είναι η μεγαλύτερη εμπορική περίπτωση. Tagged κυβερνητικές φόρμες, ρυθμιζόμενες γνωστοποιήσεις, και συνημμένα ηλεκτρονικών τιμολογίων κουβαλούν ετικέτες πεδίων και τιμές σε δηλωμένη σειρά, και η ανάγνωσή τους σε εκείνη τη σειρά αφαιρεί μια ολόκληρη κατηγορία σφαλμάτων αντιστοίχισης που η γεωμετρική εξαγωγή δημιουργεί σε διατάξεις πολλών στηλών

Ο νεότερος καταναλωτής είναι η ανάκτηση για γλωσσικά μοντέλα. Ο τεμαχισμός ενός εγγράφου για embedding είναι καλός όσο η σειρά κειμένου, και ένα τμήμα που ράβει δύο στήλες παράγει προτάσεις που δεν υπήρξαν ποτέ. Η εξαγωγή σειράς δομής είναι η φθηνότερη διαθέσιμη διόρθωση για αυτό, γιατί για tagged έγγραφα η σωστή σειρά είναι ήδη στο αρχείο και χρειάζεται μόνο να διαβαστεί

Το HotPDF είναι ένα εγγενές στοιχείο VCL για Delphi και C++Builder, οπότε η διάτρεξη δέντρου δομής και η αναπαραγωγή glyphs τρέχουν και οι δύο εντός διεργασίας πάνω σε φορτωμένο έγγραφο χωρίς εμπλοκή εξωτερικού renderer. Οι πλήρεις λεπτομέρειες API της οικογένειας εξαγωγής φορτωμένων εγγράφων βρίσκονται στη σελίδα προϊόντος HotPDF Delphi PDF component