Teknisk artikkel

Flattene PDF-siderotasjon uten å ødelegge boksene i Delphi

HotPDF flatter PDF-siderotasjon med THotPDF.FlattenLoadedPageRotation: metoden pakker hver roterte sides innhold inn i en cm-transform som vender med klokken, omskriver hver sideboks siden faktisk har, vrir annotasjonsgeometri, utseendesmatriser, eksplisitte destinasjoner og tagget strukturgeometri med samme vinkel, og setter så /Rotate til 0. Siden ser identisk ut i en visning, men koordinatsystemet dens er nå opprett. Det betyr noe i det øyeblikket et verktøy lenger ned, en print RIP eller din egen stemplingskode ignorerer /Rotate og plasserer ting i rå user space

Den typiske utløseren er en skanner eller en mobil capture-app som skriver liggende sider som stående media med /Rotate 90. Hver viser viser dem riktig, så ingen legger merke til det før noen stampler et sidetall ved «nederst til høyre» og det lander på skrå langs venstre kant, eller et utleggingstrinn som bare leser /MediaBox legger opp en stående plass til en liggende side. Flattering høres ut som en eine-linjes matrisejobb. I praksis berører den fem sidebokser, tre slags annotasjonsgeometri, dokumentets lenkemål og strukturstreet, og hver av dem har sin egen regel i ISO 32000-1

Hvilken vei vrir /Rotate en PDF-side?

/Rotate vrir siden med klokken for visning og utskrift, i multipler av 90 grader (ISO 32000-1 §7.7.3.3, tabell 30). Ved 90 grader blir venstre kant av mediet toppen, og toppkanten blir høyresiden, så i et y-ned enhetsrom er mappingen X = (y - Bottom) * Scale og Y = (x - Left) * Scale. Ved 270 grader blir høyrekanten toppen. /Rotate er også én av bare fire arvelige sideattributter, sammen med /Resources, /MediaBox og /CropBox (§7.7.3.4), så en sideordbok uten egen /Rotate kan fortsatt vris av en /Pages-forgjenger. THotPDF.GetLoadedPageRotation går /Parent-kjeden og normaliserer resultatet til 0–359, som er verdien du vil ha, ikke den rå nøkkelen på siden

Retningen er lett å få feil på en måte som overlever testing, og tidligere HotPDF-bygg gjorde nøyaktig det. Den gamle side-til-enhet-matrisen byttet om y-komponentene for 90 og 270, noe som gir en speiling over diagonalen i stedet for en rotasjon: matrisens orientering flipper relativt til den uroterte tilfellet. Begge vinkler «ser rotert ut», bitmapen har byttet bredde og høyde, og en rundtur fra side til visning og tilbake returnerer startpunktet, så dimensjonssjekker og rundturstester passerer alle. Den eneste pålitelige sjekken er hvor et hjørnemerke ender, sammenlignet piksel for piksel mot en referanserenderer. Fordi visermodellen, SIMD-render-backend-en og highlight-mappingen hadde kopiert samme matrise, ble alle korrigert sammen, og flatte-koden bruker nå samme med-klokken-konvensjon som rendereren

Hvordan HotPDF flatter siderotasjon i Delphi: en stående side lagret med /Rotate 90 vises med klokken som et 792 ganger 612 liggende bilde, enhetsmappingen X = (y - Bottom) * Scale, Y = (x - Left) * Scale flytter hvert hjørne, og å bytte om matrisens y-komponenter gir en speiling bare en hjørnemerkesammenligning fanger
Visere vrir siden med klokken for visning mens bytene forblir stående — GetLoadedPageRotation går /Parent-kjeden først, for /Rotate er én av de fire arvelige sideattributtene

Hvordan FlattenLoadedPageRotation omskriver en side

FlattenLoadedPageRotation(PageRange, Info) behandler hver side i PageRange hvis effektive rotasjon er 90, 180 eller 270, og returnerer antallet sider den flattet. En tom PageRange betyr alle sider; ellers bruker strengen den vanlige enebaserte '1-3,7'-syntaksen, og et sidetall utenfor området reiser et unntak i stedet for å hoppes over. De opprinnelige content streams re-enkodes aldri. Metoden prepender en ny stream som inneholder q 0 -1 1 0 -Bottom Width+Left cm (for 90 grader) på sidens /Contents, appender en stream som inneholder Q og skriver til slutt en eksplisitt /Rotate 0 inn i sideordboken slik at en arvet verdi på en /Pages-node ikke kan vride siden en gang til

var
  Pdf: THotPDF;
  Info: THPDFRotationFlattenInfo;
  Flattened: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('scanned-batch.pdf') > 0 then
    begin
      // '' = alle sider; sider på 0 grader skannes men får stå i fred
      Flattened := Pdf.FlattenLoadedPageRotation('', Info);
      Writeln(Format('Scanned %d, flattened %d pages', [Info.ScannedPageCount, Info.FlattenedPageCount]));
      Writeln(Format('Turned %d annotations, %d destinations, %d tagged geometry entries',
        [Info.TransformedAnnotationCount, Info.TransformedDestinationCount,
         Info.TransformedStructureGeometryCount]));
      if Flattened > 0 then
        Pdf.SaveLoadedDocument('scanned-batch-upright.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

THPDFRotationFlattenInfo-recorden er verdt å logge i stedet for å forkaste. ScannedPageCount er størrelsen på området, FlattenedPageCount er lik returverdien, og de tre Transformed...-tellerne forteller deg om dokumentet hadde lenker, bokmerker eller tagget geometri som pekte på de vridde sidene. En bunke der hver fil rapporterer null destinasjoner er grei; en tagget PDF/UA-fil som rapporterer null strukturgeometri når du ventet figuravgrensingsbokser, er et signal om å inspisere den for hånd

Hvilke sidebokser omskriver flatting, og i hvilken rekkefølge?

Flatting omskriver bare boksene siden allerede har, og den leser hver boks før den skriver noen av dem. Rekkefølgen betyr noe på grunn av standardkjeden: GetLoadedPageBox(PageIndex, pbCropBox, ...) returnerer /MediaBox når siden ikke har /CropBox, og /BleedBox, /TrimBox og /ArtBox faller tilbake til CropBox (§14.11.2). En tidligere versjon leste, transformerte og skrev én boks om gangen. Den omskrev MediaBox først, leste så «CropBox», fikk den allerede vridde MediaBox-en tilbake, vridde den en gang til og skrev en CropBox siden aldri hadde hatt, noe som beskar en liggende side ned til et kvadrat. Arvereglene deler seg på samme måte: MediaBox og CropBox slås opp langs /Parent-kjeden, mens Bleed, Trim og ArtBox bare teller hvis de sitter på sideordboken selv, så en vill /TrimBox på en /Pages-node behandles som fraværende og kopieres aldri over på siden

procedure DumpPageGeometry(Pdf: THotPDF; PageIndex: Integer);
var
  L, B, R, T: Single;
begin
  Writeln('Effective /Rotate: ', Pdf.GetLoadedPageRotation(PageIndex));
  if Pdf.GetLoadedPageBox(PageIndex, pbMediaBox, L, B, R, T) then
    Writeln(Format('MediaBox [%g %g %g %g]', [L, B, R, T]));
  // True selv uten en /TrimBox-nøkkel: verdien faller tilbake til CropBox, så MediaBox
  if Pdf.GetLoadedPageBox(PageIndex, pbTrimBox, L, B, R, T) then
    Writeln(Format('TrimBox  [%g %g %g %g]', [L, B, R, T]));
  // Forhåndsatt Letter; GetLoadedPageVisibleBox lar outputene stå urørt ved feil
  L := 0; B := 0; R := 612; T := 792;
  Pdf.GetLoadedPageVisibleBox(PageIndex, L, B, R, T);
  Writeln(Format('Visible  [%g %g %g %g]', [L, B, R, T]));
end;

Kjør den hjelperen før og etter flatting, så forklarer tallene seg selv. For en 90-graders side med MediaBox [0 0 612 792] blir den flattede MediaBox-en [0 0 792 612]; hver omskrevne boks mappes gjennom samme med-klokken-vending, relativt til den opprinnelige MediaBox-origoen, så den nye MediaBox-en starter alltid i origo og de andre boksene beholder posisjonen sin inni den. GetLoadedPageVisibleBox returnerer det visere viser og skrivere skriver ut, CropBox-en beskåret til MediaBox-en og normalisert slik at Left er mindre enn Right, og HotPDFs renderer, SVG-eksport, viser og utskriftssti bruker alle samme boks. Når du trenger sidestørrelsen et menneske ser, kall GetLoadedPageVisibleBox i stedet for å lese /MediaBox

Hvorfor HotPDF leser hver sideboks før den skriver noen under FlattenLoadedPageRotation: BleedBox, TrimBox og ArtBox faller tilbake til CropBox, som selv faller tilbake til MediaBox, så å vride bokser én om gangen fikk CropBox til å lese den allerede omskrevne MediaBox-en, og en andre vending skrev en boks siden aldri hadde hatt, og beskar en liggende side til et kvadrat
Standardkjeden betyr at én bokses output er en annen bokses input — les alt først, transformer mot den opprinnelige MediaBox-origoen, skriv så

Hvorfor knekker annotasjoner når du bare vrir /Rect?

Annotasjoner knekker fordi en appearance stream ikke tegnes rett inn i /Rect. Under §12.5.5 transformerer viseren først formens /BBox med dens /Matrix, skalerer og oversetter så avgrensingsboksen til det resultatet inn i /Rect. Vrir du bare /Rect, blir et 200 × 40-stempel klemt inn i en 40 × 200-plass, uleselig og på høykant. FlattenLoadedPageRotation høyremultipliserer derfor sidens med-klokken-vending på hver appearance-/Matrix (for 90 grader, [0 -1 1 0 0 0] i radvektor-konvensjonen), på tvers av /N-, /R- og /D-utseendene og hver tilstand inni dem. Én appearance stream kan deles av flere annotasjoner eller tilstander, så hver stream vrir nøyaktig én gang per kall. Tilfellet uten rent svar er en stream delt på tvers av sider med ulike rotasjoner; den følger den første siden som når den

To flere regler holder skjemafelt og lapper på plass. En widgets /MK /R-oppføring (§12.5.6.19) er en mot-klokken-vinkel, så sidens med-klokken-vinkel trekkes fra den, modulo 360; hopp over det, og neste appearance-regenerering tegner feltteksten i feil retning. Annotasjoner med NoRotate-flagget (bitposisjon 5, verdi 16, §12.5.3) forblir opprette på en rotert side og dreier rundt øvre venstre hjørne av sin /Rect, så flatting beholder bredde, høyde og opprett utseende og flytter bare det hjørnet til der vendingen plasserer det. Utover annotasjoner vrir metoden også /QuadPoints, /Vertices, /L og /InkList, omskriver eksplisitte destinasjoner som navngir siden (/XYZ-punkter, /FitR-rektangler, og /FitH / /FitV byttet om ved 90 og 270 grader, §12.3.2.2), og transformerer tagget geometri som attributt-/BBox-oppføringer for struktorelementer hvis /Pg er siden

Hvorfor annotasjoner knekker når en HotPDF-side flatter ved bare å vride /Rect: et 200 ganger 40-stempel skaleres inn i en 40 ganger 200-plass og blir uleselig, så FlattenLoadedPageRotation høyremultipliserer med-klokken-vendingen på hver appearance /Matrix på tvers av /N, /R og /D, justerer den mot-klokken-gående /MK /R og dreier NoRotate-annotasjoner rundt sitt øvre venstre hjørne
Viseren fitter appearance-ens transformerte BBox inn i /Rect, så selve stream-en må vris — ett pass per delt appearance, nøyaktig én gang per kall

Hva dekker ikke flatting?

Flatting er en geometrisk omskriving av ett sides egne objekter, og flere situasjoner faller utenfor den stille i stedet for høylytt

  • Sider hvis effektive rotasjon allerede er 0, eller hvis MediaBox mangler eller har null bredde eller høyde, hoppes over uten en feil; sammenlign returverdien med antallet sider du ventet skulle endres
  • Form XObjects referert fra sideressursene beholder sin egen /BBox i formrommet, for den ytre cm-en vrir dem allerede; strukturtre-skanningen følger bare /K og /A, så den går aldri inn i sidens ressurser eller annotasjoner en gang til
  • Destinasjoner finnes ved å skanne hvert indirekte objekt én gang per flatte side, så et stort dokument med hundrevis av roterte sider betaler for den vandringen på hver av dem
  • HotPDFs siderenderer tegner ikke annotasjoner, så en visuell sjekk av vridde stempler trenger FlattenLoadedAnnotations først
// Bake utseender inn i innholdet slik at rendereren kan vise dem,
// render så side 1 før og etter å ha fjernet dens /Rotate
Pdf.FlattenLoadedAnnotations('1');
Before := Pdf.RenderLoadedPageToBitmap(0, 96);
try
  Pdf.FlattenLoadedPageRotation('1', Info);
  After := Pdf.RenderLoadedPageToBitmap(0, 96);
  try
    Assert((Before.Width = After.Width) and (Before.Height = After.Height));
    // Sammenlign hjørnemerkes-piksler her, ikke bare dimensjonene
  finally
    After.Free;
  end;
finally
  Before.Free;
end;

For dypere bakgrunn fortsetter annotasjonssiden av denne historien i å syntetisere annotasjonsutseender før de flatter, rendereren bak før-og-etter-sammenligningen er dekket i å rendere en lastet PDF-side til en bitmap, og redaksjon og N-up-sying på lastede PDF-er viser samme content stream-append-teknikk som rotasjonsprefikset og -suffikset støtter seg på. HotPDF, inkludert FlattenLoadedPageRotation og sideboksleserne, er tilgjengelig for Delphi og C++Builder på HotPDF Delphi PDF-komponentsiden