Technisch artikel

PDF-pagina's exporteren naar SVG in Delphi met HotPDF

HotPDF exporteert een pagina van elk geladen PDF-document naar zelfstandige SVG-markup met een enkele aanroep, BuildLoadedPageSVG, die het volledige SVG-document als string retourneert. De geëxporteerde markup bevat de paginageometrie, tekst als echte SVG-text-elementen, ingesloten rasterafbeeldingen en de lijnstatus die PDF-operatoren bij elke tekenbewerking hadden vastgelegd

Dat laatste deel is waar de meeste zelfgebouwde converters stilletjes stuklopen. Het omzetten van een PDF-pagina naar SVG lijkt een coördinatenprobleem, maar blijkt een statusprobleem te zijn. PDF is een stackmachine waarvan de grafische status verandert terwijl de inhoudsstream wordt geïnterpreteerd; SVG is een declaratieve boom waarvan elk element zijn eigen presentatie-attributen draagt. Alles wat de interpreter niet vastlegt op het moment dat een element wordt uitgevoerd, is simpelweg verdwenen uit de uitvoer, en de fout is stil: u krijgt geldige SVG die een subtiel verkeerde pagina weergeeft

Waarom wordt een PDF-pagina niet zomaar omgezet naar SVG?

Drie mismatches maken de conversie niet-triviaal, en alle drie leveren ze uitvoer op die plausibel oogt totdat u deze naast het origineel legt. De eerste is de y-as. PDF-gebruikersruimte groeit omhoog vanaf de linkerbenedenhoek van de pagina; SVG groeit omlaag vanaf linksboven. Eén enkele omkering op paginaniveau corrigeert de tekencoördinaten en breekt vervolgens elk glyph, omdat het spiegelen van het hele canvas ook de lettervormen spiegelt

De tweede mismatch is overerving. In PDF plaatsen q en Q een grafische status op de stack en halen deze eraf, inclusief lijndikte, lijndop, lijnverbinding, mijterlimiet, streeppatroon, streepfase en alfa. In SVG erft een element dat een attribuut niet noemt dit van een voorouderlijke groep, wat een geheel andere scoperegel is. Een exporteur die alleen de huidige transformatiematrix bijhoudt en de lijnstatus vergeet, laat de na een Q herstelde status doorlekken naar de elementen die volgen

De derde is dat PDF verschillende zaken via conventie uitdrukt in plaats van via waarde. Lijndoppen en -verbindingen zijn gehele getallen, een lijndikte van nul betekent een haarlijn in apparaatruimte in plaats van een onzichtbare lijn, en de ster-varianten van de tekenoperatoren veranderen de omloopregel in plaats van de kleur. Elk daarvan vereist een vertaling, geen kopie

Eén aanroep voor het gangbare geval

Voor de gewone taak van het exporteren van pagina's voor een webviewer, een diffing-tool of een design-overdracht, bestaat het API-oppervlak uit één functie. BuildLoadedPageSVG neemt een bij nul beginnende pagina-index voor het momenteel geladen document en retourneert het SVG-document als een AnsiString:

var
  Pdf: THotPDF;
  I: Integer;
  Svg: AnsiString;
  Output: TFileStream;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('statements.pdf', '') <= 0 then
      Exit;                     // LoadFromFile retourneert het aantal pagina's
    for I := 0 to Pdf.LoadedPageCount - 1 do
    begin
      Svg := Pdf.BuildLoadedPageSVG(I);
      if Length(Svg) = 0 then
        Continue;
      Output := TFileStream.Create(Format('page-%d.svg', [I + 1]), fmCreate);
      try
        Output.WriteBuffer(Svg[1], Length(Svg));
      finally
        Output.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Dezelfde export wordt door de HotPDF-commandoregeltool aangeboden als het commando export-svg, wat handig is in buildpijplijnen en regressiescripts waar u een tekstueel diffbare weergave van een pagina wilt zonder Pascal te schrijven. Omdat SVG tekst is, vormt het een natuurlijke aanvulling op het rasterpad dat wordt beschreven in het renderen van een PDF-pagina naar een bitmap: de bitmap laat zien hoe de pagina eruitziet, de SVG laat zien waaruit deze is opgebouwd

Hoe wordt PDF-tekst afgebeeld op SVG-tekstelementen?

HotPDF stelt de tekstmatrixketen samen als prefix maal CTM maal tekstmatrix maal glyph-omkering, waarbij de glyph-omkering een rechtse vermenigvuldiging is met matrix(1,0,0,-1,0,0). Die rechterfactor bestaat uitsluitend om de omkering op paginaniveau voor glyph-vormen ongedaan te maken, aangezien SVG-tekst getekend in een omgekeerd lokaal frame anders ondersteboven zou verschijnen. Door de correctie in de matrix te plaatsen in plaats van in speciaal behandelde code, komen geroteerde, gespiegelde en schuingetrokken tekst allemaal correct uit zonder extra vertakkingen

Horizontale positionering gebruikt de multi-waarde x-syntax van het SVG-text-element, één coördinaat per teken, opgebouwd uit elk glyph-voorschot plus de op dat moment geldende tekenspatiëring Tc en woordspatiëring Tw. Horizontale schaling Tz wordt verwerkt in de a- en c-kolommen van de tekstmatrix in plaats van apart te worden uitgevoerd, zodat een viewer die exotische tekstattributen negeert elk glyph nog steeds plaatst waar de PDF het heeft neergezet. Tekst die is geproduceerd via complexe shaping, behandeld in complexe scripttekst-shaping, volgt hetzelfde pad, omdat de shaper clusters al heeft omgezet in gepositioneerde glyphs tegen de tijd dat de inhoudsstream wordt geïnterpreteerd

Rotatie en afbeeldingen: twee omkeringen die makkelijk verkeerd om gaan

Een pagina met een niet-nul /Rotate-vermelding heeft een voortransformatie nodig die is samengesteld uit een omkering ten opzichte van de geroteerde canvashoogte en een rotatie uitgedrukt in y-omhoog weergaveruimte. De drie rotatiematrices zijn (0,-1,1,0,0,W) voor 90 graden, (-1,0,0,-1,W,H) voor 180 en (0,1,-1,0,H,0) voor 270, waarbij W en H de paginadimensies vóór rotatie zijn. Deze met de hand afleiden nodigt uit tot tekenfouten op precies drie plaatsen, dus stelt de exporteur ze samen via dezelfde matrixvermenigvuldigingsroutine die elke andere transformatie afhandelt

Ingesloten afbeeldingen hebben hun eigen omkering nodig, omdat PDF-afbeeldingsruimte de eerste sample-rij aan de bovenrand van het eenheidsvierkant plaatst, terwijl het SVG-image-element een y-omlaag lokaal frame heeft. De uitgevoerde transformatie is daarom de CTM rechts vermenigvuldigd met matrix(1,0,0,-1,0,1). Dit verkeerd doen levert verticaal gespiegelde foto's op een verder perfecte pagina op, het soort defect dat een reviewer meteen opmerkt en een geautomatiseerde test vaak niet

Wat behoudt het grafische-statusapparaat eigenlijk?

HotPDF verzendt de lijnstatus-operatoren w, J, j, M en d via een aparte optionele apparaat-interface, zodat lijngetrouwheid werd toegevoegd zonder de vtable van het bestaande inhoudsapparaat te wijzigen en zonder de binaire compatibiliteit te breken voor code die tegen eerdere versies is gebouwd. Concreet ontvangt de geëxporteerde SVG vertaalde sleutelwoorden in plaats van ruwe PDF-gehele getallen:

// PDF-integer-enumeraties worden SVG-sleutelwoordattributen
//   lijndop  0, 1, 2  ->  butt, round, square
//   lijnverbinding 0, 1, 2  ->  miter, round, bevel
//
// Een lijndikte van nul betekent een haarlijn in apparaatruimte in PDF,
// dus de exporteur genereert vector-effect="non-scaling-stroke" om de
// lijn zichtbaar en ongeveer één apparaatpixel breed te houden na de CTM
//
// f* B* b* selecteren de even-oneven-regel en genereren fill-rule="evenodd",
// terwijl f B b de SVG-standaard van nonzero winding behouden

Het herstellen van status bij een Q omvat samen opaciteit, lijndikte, dop, verbinding, mijterlimiet, streeppatroon en streepfase. Geneste Form XObjects leggen dezelfde volledige set vast en herstellen deze op hun grenzen, zodat een streeprand die binnen een stempel is gedefinieerd zijn patroon niet kan laten doorlekken naar de paginainhoud die volgt. Als u om andere redenen al uitknip- en CTM-gedrag bijhoudt, is dit hetzelfde statusmodel dat voorkomt in EMF- en WMF-vectorimport, maar dan in de tegenovergestelde richting

Grenzen die het waard zijn te kennen voordat u het uitlevert

De exporteur is eerlijk over zijn bereik, en de grenzen vooraf kennen is goedkoper dan ze in productie ontdekken. Kleur bereikt het SVG-apparaat via de operatoren rg, RG, g en G. Vullingen die zijn ingesteld via een kleurruimte plus scn, de manier waarop Separation-, DeviceN- en ICCBased-kleuren worden geschilderd, komen niet als opgeloste RGB-triple bij het apparaat aan, dus pagina's die steunkleuren op deze manier gebruiken exporteren wel hun geometrie maar niet die kleuren. Rasteriseer voor printgerichte bronnen in plaats daarvan, of plat de steunkleuren eerst uit; het schildermodel zelf wordt behandeld in het renderen van Separation- en DeviceN-steunkleuren

Twee kleinere opmerkingen besparen debugtijd. Hexadecimale kleurliteralen worden in hoofdletters uitgevoerd, dus een test die #ff0000 verwacht faalt tegen een volkomen correcte #FF0000. En het SVG-apparaat wordt via zijn interface met referentietelling beheerd, wat betekent dat het vrijgeven ervan neerkomt op het uit scope laten lopen van de interface in plaats van Free op het object aan te roepen, een onderscheid dat ertoe doet als u het apparaat uitbreidt om uw eigen markup samen met de paginainhoud uit te voeren

SVG-export vormt een natuurlijk koppel met structurele vergelijking wanneer u moet weten of een gegenereerd document daadwerkelijk is veranderd tussen twee builds. De bredere toolkit rond geladen documenten, van renderen tot bewerken tot exporteren, is gedocumenteerd op de HotPDF Delphi PDF-componentpagina