Tehnički članak

Spajanje skeniranih slika u jedan PDF pomoću PDFium Component-a u Delphi-ju

Tim za obradu zahteva imao je trideset godina papirnih dosijea koji su prolazili kroz skener sa uvlačenjem listova. Skener je izbacivao jedan JPEG po stranici u fasciklu, pod nazivom 0001.jpg, 0002.jpg i tako dalje. Ono što je arhivi zapravo bilo potrebno jeste jedan PDF po predmetu, sa stranicama u redosledu, tako da recenzent može otvoriti jedan dokument umesto da klikće kroz stotinu sličica slika. Taj poslednji korak, pretvaranje numerisane gomile skeniranih slika u jedan uređeni PDF, jeste posao koji radimo ovde

PDFium Component to rešava direktno. Pored renderovanja i ekstrakcije teksta, komponenta može da izgradi PDF od nule: kreirajte prazan dokument, dodajte praznu stranicu veličine po želji, postavite sliku na tu stranicu u koordinatama korisničkog prostora (user-space coordinates), a zatim sačuvajte. Ceo cevovod (pipeline) živi na TPdf komponenti, tako da je paketni konvertor zapravo petlja preko imena datoteka plus nekoliko poziva

Format konverzije

Tri stvari moraju da se dese za svako skeniranje. Odlučujete o veličini stranice, postavljate sliku unutar stranice ostavljajući marginu i prelazite na sledeću stranicu. PDFium Component vam daje po jednu metodu za svaku akciju: AddPage kreira praznu stranicu date veličine, AddImage (or AddPicture ako već držite TPicture) iscrtava bitmapu na trenutnu stranicu, a PageNumber govori komponenti na koju stranicu ciljaju kasniji pozivi za iscrtavanje

Jedan detalj koji ljudi često previde jeste koordinatni sistem. PDF korisnički prostor postavlja koordinatni početak u donji levi ugao stranice, pri čemu Y raste nagore, što je suprotno koordinatama ekrana koje Delphi programeri po refleksu koriste. Vrednosti X, Y koje prosleđujete metodi AddImage predstavljaju donji levi ugao pravougaonika slike, a Width, Height su veličina pozicioniranja u tačkama (points), a ne veličina piksela izvorne datoteke. Ako to uradite obrnuto, vaša skeniranja će završiti van stranice ili okrenuta naopako u odnosu na ono što ste očekivali

Kreiranje dokumenta i stranice za svako skeniranje

Počnite sa praznim dokumentom. CreateDocument alocira novi PDF i ostavlja komponentu aktivnom, tako da nema posebnog koraka otvaranja. Odatle prolazite kroz listu skeniranih datoteka i za svaku dodajete stranicu, postavljate je kao trenutnu i pozicionirate sliku. Dimenzije stranice su ovde A4 u tačkama (595 × 842 portret), standardna veličina lista za arhiviranu korespondenciju

procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
  PageW = 595.0;   // A4 width in points
  PageH = 842.0;   // A4 height in points
  Margin = 36.0;   // half-inch border around each scan
var
  I: Integer;
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.CreateDocument;                       // new, empty, already active
    for I := 0 to Files.Count - 1 do
    begin
      Pdf.AddPage(I + 1, PageW, PageH);       // 1-based page index
      Pdf.PageNumber := I + 1;                // make the new page current
      PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
    end;
    Pdf.SaveAs(OutputPath);
  finally
    Pdf.Free;
  end;
end;

Svaka iteracija kreira stranicu i odmah postavlja PageNumber na nju. Ta druga linija je važna: AddPage umeće stranicu, ali metode crtanja deluju na stranicu koja je trenutna, tako da je postavljanje PageNumber ono što usmerava AddImage na stranicu koju ste upravo napravili. Preskočite to i vaše slike će se naslagati na stranicu koja je slučajno bila učitana pre toga

Jedna pretpostavka se krije u toj petlji: redosled datoteka (Files). Skener imenuje stranice od 0001.jpg do 0100.jpg, ali ispis direktorijuma ih ne vraća uvek sortirane, a onog trenutka kada naiđete na page9.jpg pored page10.jpg, obično sortiranje stringova stavlja stranicu 10 ispred stranice 9. Sortirajte listu eksplicitno pre petlje i dajte prednost nazivima sa popunjenim nulama (zero-padded) u vreme skeniranja kako bi se leksički redosled poklapao sa redosledom stranica. Redosled stranica je prva stvar koju recenzent odmah primeti, a to je ujedno i najjeftinija greška koju možete sprečiti

Pozicioniranje skenirane slike i očuvanje njenog odnosa širine i visine

Skenirana slika je retko istog oblika kao stranica. Ako je rastegnete da popuni list, deformisaćete tekst; ako je postavite u punoj veličini piksela, preliće se van stranice. Rešenje je da je skalirate prema manjem od dva odnosa, uklapanjem po širini ili uklapanjem po visini, i centrirate ono što preostane. Pošto se koordinatni početak nalazi u donjem levom uglu, centriranje znači ravnomernu podelu preostalog prostora i njegovo dodavanje na X i Y

procedure TArchiveForm.PlaceScan(Pdf: TPdf; const FileName: string;
  PageW, PageH, Margin: Double);
var
  Pic: TPicture;
  AvailW, AvailH, Scale, DrawW, DrawH, X, Y: Double;
begin
  Pic := TPicture.Create;
  try
    Pic.LoadFromFile(FileName);              // BMP, JPG, PNG, etc. via the VCL graphics units

    AvailW := PageW - 2 * Margin;
    AvailH := PageH - 2 * Margin;

    // Fit inside the margins without distorting the scan.
    Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
    DrawW := Pic.Width * Scale;
    DrawH := Pic.Height * Scale;

    // Center: leftover space split evenly. Y measured from the page bottom.
    X := (PageW - DrawW) / 2;
    Y := (PageH - DrawH) / 2;

    Pdf.AddImage(FileName, X, Y, DrawW, DrawH);
  finally
    Pic.Free;
  end;
end;

Ovaj kod učitava datoteku jednom da bi pročitao njene dimenzije u pikselima, izračunava jedinstvenu uniformnu razmeru i prosleđuje pravougaonik pozicioniranja metodi AddImage. AddImage prihvata putanju datoteke direktno i usmerava je kroz isti cevovod slika kao i AddPicture, tako da bilo koji format koji grafičke jedinice VCL-a prepoznaju radi bez posebnih podešavanja. Ako već imate sliku dekodiranu u TPicture iz okna za pregled, pozovite AddPicture(Pic, X, Y, DrawW, DrawH) sa istim pravougaonikom i preskočite drugo čitanje datoteke

Preskakanje dekodiranja za JPEG skeniranja

Skeneri skoro uvek emituju JPEG. Učitavanje JPEG-a u TPicture dekodira ga u bitmapu, a zatim ga PDFium ponovo kodira prilikom čuvanja, što su dva nepotrebna kruga kompresije sa gubitkom podataka. AddJpegImage ugrađuje originalne komprimovane bajtove direktno u stranicu iz toka podataka (stream), što je i brže i vizuelno čistije za paketnu obradu velikog obima

var
  Stream: TFileStream;
begin
  // ... after AddPage + PageNumber for the current page ...
  Stream := TFileStream.Create(FileName, fmOpenRead);
  try
    // Embeds the JPEG bytes as-is; no decode/re-encode cycle.
    Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
  finally
    Stream.Free;
  end;
end;

I dalje izračunavate X, Y, DrawW i DrawH na isti način, pošto su vam potrebne dimenzije u pikselima za skaliranje. Pročitajte ih iz datoteke ili brzim parsiranjem zaglavlja, a zatim prosledite sirovi tok metodi AddJpegImage. Za PNG ili TIFF skeniranja, putanja AddImage je ispravna; rezervišite prečicu za JPEG samo za format na koji se stvarno odnosi

Označavanje svake stranice

Arhivirana skeniranja je lakše revidirati kada svaka stranica nosi ime svoje izvorne datoteke. AddText crta string na koordinatama korisničkog prostora, tako da natpis stoji tik ispod slike. Zapamtite invertovanu Y osu: da biste postavili oznaku ispod skenirane slike, oduzimate od donje ivice slike umesto da joj dodajete

// Caption below the scan: Y decreases toward the page bottom.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
  X, Y - 14, clGray);

Poslednja tačka u vezi sa čuvanjem. SaveAs is a funkcija koja vraća Boolean vrednost, pa u produkcionom kodu proverite njen rezultat umesto da pretpostavljate da je pisanje uspelo; pun disk ili zaključana izlazna putanja inače tiho propadaju. Kada se petlja završi i datoteka zapiše, imate upravo ono što je arhivi bilo potrebno: jedan uređeni PDF po dosijeu predmeta, sa stranicama skaliranim da se uklope, spreman za čitanje u bilo kom čitaču

Isti gradivni blokovi pokrivaju srodne poslove. Promenite pravilo skaliranja po stranici i dobićete knjigu fotografija sa jednom slikom po listu; zadržite petlju ali čitajte iz višestranog TIFF izvora i imate konvertor faks arhiva. Ako želite širu sliku o programskom kreiranju PDF-ova, pogledajte kreiranje PDF dokumenata od nule pomoću PDFium Component-a; da biste kasnije renderovali rezultat nazad na ekran, pogledajte pretvaranje PDF stranica u JPEG slike pomoću PDFium Component-a

PDFium Component komponenta sa loslab.com objedinjuje API-je za kreiranje dokumenata, renderovanje i tekst koji se koriste u ovoj seriji