Articol tehnic

PDF în Markdown și DOCX în Delphi cu PDFlibPas

PDFlibPas convertește conținut PDF în două formate editabile fără automatizare Office. ExportPageMarkdown și ExportDocumentMarkdown returnează Markdown semantic cu titluri deduse, liste ordonate și neordonate și tabele pipe, în timp ce SaveDOCXToFile și SaveDOCXToStream scriu un pachet WordprocessingML conținând paragrafe, titluri, numerotare nativă de liste, tabele detectate, stilizare de font, salturi de pagină și imagini PNG poziționate

Ambele rulează complet în Pascal, pe un server, fără Word instalat și fără COM. Această constrângere este motivul pentru care funcționalitatea există într-o bibliotecă PDF, nu într-un instrument desktop

De ce este „PDF în Word” cu adevărat dificil?

Pentru că o pagină PDF nu conține paragrafe. Conține operatori de afișare de text care plasează run-uri de glife la coordonate, în orice ordine le-a emis producătorul, fără nicio obligație de a indica dacă două run-uri aparțin aceleiași propoziții, cu atât mai puțin aceluiași element de listă. Formatul a fost proiectat să descrie exact o pagină tipărită, și reușește asta aruncând structura care a produs pagina

Așa că fiecare convertor trebuie să reconstruiască ce a aruncat generatorul. Gruparea liniilor vine din spațierea verticală și alinierea baseline. Limitele de paragraf vin din schimbări de spațiere și indentare. Un titlu este o linie al cărei font este mai mare sau mai gros decât corpul de text și care se distinge de ce urmează. O listă este o serie de paragrafe care încep cu un caracter bulă sau un tipar numeric. Un tabel este o grilă de blocuri de text ale căror margini se aliniază pe rânduri și coloane. Fiecare dintre acestea este o inferență, iar inferența înseamnă un rezultat bun pe documente care urmează convenții tipografice obișnuite și unul mediocru pe documente care nu o fac

PDF-urile tagged sunt excepția, și una mare. Când documentul poartă un arbore de structură, rolurile de paragraf, titlu, listă și tabel sunt înregistrate, nu ghicite, motiv pentru care munca de accesibilitate descrisă în structura de accesibilitate a PDF-ului tagged se răsplătește și în calitatea conversiei. Dacă tu controlezi producătorul, taggarea output-ului tău este cel mai eficient lucru pe care îl poți face pentru oricine trebuie mai târziu să îl convertească

Exportul Markdown, o pagină pe rând

Traseul Markdown este cel de folosit atunci când destinația este un flux de text: un site de documentație, un index de căutare, un corpus de retrieval pentru un asistent. Opțiunile sunt o mască de biți: PDF_MARKDOWN_INCLUDE_PAGE_MARKERS, PDF_MARKDOWN_DETECT_HEADINGS, PDF_MARKDOWN_PRESERVE_STYLES, cu PDF_MARKDOWN_DEFAULT combinând toate cele trei

var
  Pdf: TPDFlib;
  Md: WideString;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.LoadFromFile('handbook.pdf', '');

    // O singură pagină, ca string
    Md := Pdf.ExportPageMarkdown(1, PDF_MARKDOWN_DEFAULT);

    // Un interval de pagini, transmis în flux pe disc ca UTF-8 fără BOM
    Pdf.SaveMarkdownToFile('1-40',
      PDF_MARKDOWN_DETECT_HEADINGS or PDF_MARKDOWN_PRESERVE_STYLES,
      'handbook.md');
  finally
    Pdf.Free;
  end;
end;

Marcajele de pagină își câștigă locul în munca de retrieval. O bucată de text care poartă pagina din care provine poate fi citată precis, iar un cititor care urmează citarea ajunge exact unde se află afirmația. Dezactivează-le când Markdown-ul este destinat lecturii umane, unde limitele de pagină din layout-ul sursă sunt zgomot

Punctele de intrare pentru streaming contează pentru documente mari. SaveMarkdownToStream și SaveMarkdownToFile scriu UTF-8 o pagină pe rând și nu tamponează output-ul complet, așa că un manual de 900 de pagini nu devine mai întâi un string de 900 de pagini în memorie. Absența unui byte-order mark este de asemenea deliberată: un BOM pe un fișier Markdown derutează un număr surprinzător de mare de generatoare de site-uri statice și instrumente de diff

DOCX fără Office pe mașină

Scriitorul DOCX produce chiar el pachetul: intrări ZIP scrise ca Deflate brut cu verificări CRC, părțile WordprocessingML și relațiile care le leagă. Nimic nu apelează Word, ceea ce înseamnă că conversia rulează pe un server fără interfață, într-un cont de serviciu, într-un container, în toate locurile în care automatizarea Office este fie nelicențiată, fie instabilă, fie interzisă

var
  Pdf: TPDFlib;
  Target: TFileStream;
begin
  Pdf := TPDFlib.Create;
  Target := TFileStream.Create('handbook.docx', fmCreate);
  try
    Pdf.LoadFromFile('handbook.pdf', '');
    Pdf.SaveDOCXToStream('1-40',
      PDF_DOCX_INCLUDE_IMAGES or PDF_DOCX_DETECT_HEADINGS or
      PDF_DOCX_PRESERVE_STYLES or PDF_DOCX_PRESERVE_PAGE_BREAKS,
      Target);
  finally
    Target.Free;
    Pdf.Free;
  end;
end;

Datele de imagine sunt scrise pe măsură ce fiecare pagină este procesată, nu colectate și adăugate la final, așa că memoria de vârf urmărește o pagină, nu întregul document. Ordinea explicită a paginilor este păstrată, iar pagina PDF selectată este restaurată ulterior, ceea ce contează atunci când exportul este un pas dintr-o sarcină mai lungă care avea o pagină selectată din alte motive

Ce îți oferă împachetarea deterministă?

Reproductibilitate byte cu byte. Două conversii ale aceleiași intrări cu aceleași opțiuni produc același pachet, ceea ce înseamnă că poți face hash la output pentru a detecta o schimbare, poți diff-ui două build-uri ale unui document generat și poți face cache agresiv fără să te temi că o intrare identică a produs un artefact diferit

Automatizarea Office nu poate promite asta. Include timestamp-uri, identificatori de revizie și metadate dependente de mașină, astfel încât același document convertit de două ori diferă în moduri care distrug hash-ul. Același raționament conduce identificatorii de fișier PDF deterministe discutați în ID-uri PDF deterministe pentru build-uri reproductibile: când output-ul este reproductibil, verificarea devine o comparație, nu o inspecție

Unde este bun output-ul, și unde nu este

Fii onest cu utilizatorii tăi despre asta, pentru că această calitate a conversiei variază mai mult în funcție de intrare decât de convertor. PDF-urile tagged și documentele de afaceri generate curat, facturi, rapoarte, contracte, se convertesc bine: titlurile ajung titluri, tabelele supraviețuiesc, listele se renumerotează corect în Word. Layout-urile academice pe două coloane se convertesc acceptabil dacă geometria coloanelor este regulată. Tabelele care se întind peste salturi de pagină sunt reasamblate prin inferență și uneori despărțite. Materialele de marketing intens design-uite, unde textul este plasat pentru efect vizual, nu în ordine de citire, se convertesc slab, iar nicio cantitate de inferență nu repară asta

Documentele scanate sunt un caz separat cu totul. O pagină care este o singură imagine mare nu conține obiecte de text, așa că nu este nimic de exportat până când nu există un strat de text; traseul OCR care produce unul este o precondiție, nu o opțiune. Înainte să rulezi un lot mare, eșantionează o duzină de fișiere reprezentative și analizează output-ul, și ia în calcul enumerarea elementelor de pagină mai întâi, așa cum este descris în căutarea de text și enumerarea elementelor de pagină, pentru a vedea ce conțin efectiv paginile

Pentru fluxuri de asistenți și retrieval, traseul Markdown este de obicei ținta mai bună: titlurile devin limite de chunk, tabelele rămân lizibile ca tabele pipe, iar marcajele de pagină dau fiecărui chunk o locație citabilă. Pentru editare umană, DOCX este răspunsul, pentru că ce vrea utilizatorul nu este textul, ci abilitatea de a-l schimba

PDFlibPas este o bibliotecă PDF pentru Delphi, C++Builder și Lazarus cu interfețe DLL și ActiveX corespunzătoare, astfel încât aceleași apeluri de export sunt disponibile din C#, C++ sau gazde de scripting. Documentația completă și o versiune de test sunt pe pagina bibliotecii PDFlibPas Delphi