Teknisk artikel

PDF till Markdown och DOCX i Delphi med PDFlibPas

PDFlibPas konverterar PDF-innehåll till två redigerbara format utan Office-automatisering. ExportPageMarkdown och ExportDocumentMarkdown returnerar semantisk Markdown med härledda rubriker, numrerade och onumrerade listor och pipe-tabeller, medan SaveDOCXToFile och SaveDOCXToStream skriver ett WordprocessingML-paket som innehåller stycken, rubriker, inbyggd listnumrering, upptäckta tabeller, teckensnittsformatering, sidbrytningar och positionerade PNG-bilder

Båda körs helt i Pascal, på en server, utan Word installerat och utan COM. Den begränsningen är anledningen till att funktionen finns i ett PDF-bibliotek snarare än i ett skrivbordsverktyg

Varför är ”PDF till Word” genuint svårt?

Därför att en PDF-sida inte innehåller stycken. Den innehåller textvisande operatorer som placerar glyfsekvenser vid koordinater, i vilken ordning producenten än sände ut dem, utan någon skyldighet att ange att två sekvenser hör till samma mening, långt mindre samma listpunkt. Formatet konstruerades för att beskriva en tryckt sida exakt, och det lyckas med det genom att kasta bort strukturen som skapade sidan

Så varje konverterare måste återskapa det generatorn kastade bort. Radgruppering kommer från vertikala mellanrum och baslinjejustering. Styckesgränser kommer från förändringar i mellanrum och indrag. En rubrik är en rad vars teckensnitt är större eller tyngre än brödtexten och som står avskild från det som följer. En lista är en följd av stycken som börjar med ett punkttecken eller ett nummermönster. En tabell är ett rutnät av textblock vars kanter riktar in sig över rader och kolumner. Vart och ett av dessa är en slutledning, och slutledning innebär ett bra resultat på dokument som följer vanliga typografiska konventioner och ett medelmåttigt på dokument som inte gör det

Taggade PDF:er är undantaget, och ett stort sådant. När dokumentet bär ett strukturträd registreras rollerna för stycke, rubrik, lista och tabell i stället för att gissas, vilket är varför tillgänglighetsarbetet som beskrivs i strukturträd för taggad PDF-tillgänglighet också lönar sig för konverteringskvaliteten. Om du styr producenten är taggning av din utdata det enskilt mest lönsamma du kan göra för alla som senare måste konvertera den

Markdown-export, en sida i taget

Markdown-vägen är den att ta till när målet är en textpipeline: en dokumentationswebbplats, ett sökindex, ett hämtningskorpus för en assistent. Alternativen är en bitmask: PDF_MARKDOWN_INCLUDE_PAGE_MARKERS, PDF_MARKDOWN_DETECT_HEADINGS, PDF_MARKDOWN_PRESERVE_STYLES, där PDF_MARKDOWN_DEFAULT kombinerar alla tre

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

    // En sida, som en sträng
    Md := Pdf.ExportPageMarkdown(1, PDF_MARKDOWN_DEFAULT);

    // Ett sidintervall, strömmat till disk som UTF-8 utan BOM
    Pdf.SaveMarkdownToFile('1-40',
      PDF_MARKDOWN_DETECT_HEADINGS or PDF_MARKDOWN_PRESERVE_STYLES,
      'handbook.md');
  finally
    Pdf.Free;
  end;
end;

Sidmarkörer gör sig förtjänta i hämtningsarbete. En textbit som bär med sig sidan den kom från kan citeras precist, och en läsare som följer citatet hamnar där påståendet faktiskt finns. Stäng av dem när Markdown-utdatan är avsedd för mänsklig läsning, där sidgränser från källayouten bara är brus

De strömmande ingångspunkterna spelar roll för stora dokument. SaveMarkdownToStream och SaveMarkdownToFile skriver UTF-8 en sida i taget och buffrar inte hela utdatan, så en 900-sidig manual blir inte först en 900-sidig sträng i minnet. Frånvaron av en byteordningsmarkör är också medveten: en BOM i en Markdown-fil förvirrar ett förvånansvärt stort antal statiska webbplatsgeneratorer och diff-verktyg

DOCX utan Office på maskinen

DOCX-skrivaren producerar själva paketet: ZIP-poster skrivna som rå Deflate med CRC-kontroller, WordprocessingML-delarna, och relationerna som binder samman dem. Ingenting anropar in i Word, vilket innebär att konverteringen kan köras på en huvudlös server, inuti ett tjänstekonto, i en container, på alla ställen där Office-automatisering antingen är olicensierad, instabil eller förbjuden

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;

Bilddata skrivs allteftersom varje sida bearbetas i stället för att samlas ihop och läggas till i slutet, så toppminnesanvändningen följer en sida i stället för hela dokumentet. Den explicita sidordningen bevaras, och den valda PDF-sidan återställs efteråt, vilket spelar roll när exporten är ett steg inuti ett längre jobb som hade en sida vald av andra skäl

Vad ger deterministisk paketering dig?

Byte-för-byte-reproducerbarhet. Två konverteringar av samma indata med samma alternativ ger samma paket, vilket innebär att du kan hasha utdatan för att upptäcka förändring, diffa två byggen av ett genererat dokument, och cacha aggressivt utan att oroa dig för att identisk indata gav en annan artefakt

Office-automatisering kan inte lova det. Den bäddar in tidsstämplar, revisionsidentifierare och maskinberoende metadata, så samma dokument konverterat två gånger skiljer sig på sätt som omintetgör hashning. Samma resonemang ligger bakom de deterministiska filidentifierarna som diskuteras i deterministiska PDF-ID:n för reproducerbara byggen: när utdatan är reproducerbar blir verifiering en jämförelse i stället för en inspektion

Var utdatan är bra, och var den inte är det

Var ärlig mot dina användare om detta, eftersom konverteringskvaliteten varierar mer med indatan än med konverteraren. Taggade PDF:er och rent genererade affärsdokument, fakturor, rapporter, avtal, konverterar bra: rubriker landar som rubriker, tabeller överlever, listor omnumreras korrekt i Word. Tvåspaltiga akademiska layouter konverterar acceptabelt om spaltgeometrin är regelbunden. Tabeller som sträcker sig över sidbrytningar återmonteras genom slutledning och delas ibland. Kraftigt designat marknadsföringsmaterial, där text placeras för visuell effekt snarare än i läsordning, konverterar dåligt, och ingen mängd slutledning fixar det

Skannade dokument är ett helt separat fall. En sida som är en enda stor bild innehåller inga textobjekt, så det finns inget att exportera förrän ett textlager finns; OCR-vägen som skapar ett sådant är en förutsättning, inte ett alternativ. Innan du kör en stor batch, ta ett stickprov på ett dussin representativa filer och granska utdatan, och överväg att räkna upp sidelement först, enligt beskrivningen i textsökning och uppräkning av sidelement, för att se vad sidorna faktiskt innehåller

För assistent- och hämtningspipelines är Markdown-vägen oftast det bättre målet: rubriker blir chunk-gränser, tabeller förblir läsbara som pipe-tabeller, och sidmarkörer ger varje chunk en citerbar plats. För mänsklig redigering är DOCX svaret, eftersom det användaren vill ha inte är texten utan möjligheten att ändra den

PDFlibPas är ett PDF-bibliotek för Delphi, C++Builder och Lazarus med matchande DLL- och ActiveX-gränssnitt, så samma exportanrop är tillgängliga från C#, C++ eller skriptvärdar. Fullständig dokumentation och en testversion finns på sidan för PDFlibPas Delphi PDF-bibliotek