Articol tehnic

Combinați scanările într-un singur PDF cu PDFium Component

O echipă de procesare a dosarelor de daune avea treizeci de ani de arhivă pe hârtie care treceau printr-un scaner cu alimentare din coli. Scanerul scuipa câte un JPEG per pagină într-un folder, numite 0001.jpg, 0002.jpg și așa mai departe. Ce îi trebuia de fapt arhivei era câte un PDF per dosar, cu paginile în ordine, ca un evaluator să poată deschide un singur document în loc să dea clic prin o sută de miniaturi. Acest ultim pas, transformarea unui teanc numerotat de scanări într-un singur PDF ordonat, este treaba de aici

PDFium Component se ocupă direct de el. Dincolo de randare și de extragerea textului, componenta poate construi un PDF de la zero: creează un document gol, adaugă o pagină goală de dimensiunea dorită, așază o imagine pe acea pagină în coordonate din spațiul utilizator, apoi salvează. Toată conducta trăiește pe componenta TPdf, așa că un convertor pe loturi înseamnă o buclă peste nume de fișiere plus un pumn de apeluri

O conductă de lot în Delphi folosește apelurile AddPage, PageNumber, AddImage și SaveAs din PDFium Component pentru a transforma un folder de scanări numerotate într-un singur PDF ordonat
Fiecare scanare devine o pagină creată de AddPage și țintită de PageNumber înainte ca AddImage să o deseneze; SaveAs scrie o singură dată documentul finit

Forma conversiei

Trei lucruri trebuie să se întâmple pentru fiecare scanare. Decideți dimensiunea paginii, plasați imaginea în interiorul paginii lăsând o margine și avansați la pagina următoare. PDFium Component vă dă câte o metodă pentru fiecare: AddPage creează o pagină goală de o anumită dimensiune, AddImage (sau AddPicture, dacă aveți deja un TPicture) desenează imaginea bitmap în pagina curentă, iar PageNumber îi spune componentei ce pagină vizează apelurile de desenare ulterioare

Singurul detaliu care încurcă lumea este sistemul de coordonate. Spațiul utilizator PDF pune originea în colțul din stânga-jos al paginii, cu Y crescând în sus, opusul coordonatelor de ecran spre care dezvoltatorii Delphi se întind din reflex. Perechea X, Y pe care o transmiteți lui AddImage este colțul din stânga-jos al dreptunghiului imaginii, iar Width, Height sunt dimensiunea de plasare în puncte, nu dimensiunea în pixeli a fișierului sursă. Dacă le luați invers, scanările ajung în afara paginii sau răsturnate față de unde vă așteptați

Crearea documentului și a câte unei pagini per scanare

Începeți cu un document gol. CreateDocument alocă un PDF proaspăt și lasă componenta activă, așa că nu există un pas separat de deschidere. De acolo parcurgeți lista de fișiere scanate și, pentru fiecare, adăugați o pagină, o faceți curentă și plasați imaginea. Dimensiunile paginii de aici sunt A4 în puncte (595 × 842 portret), formatul standard de coală pentru corespondența arhivată

procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
  PageW = 595.0;   // lățimea A4 în puncte
  PageH = 842.0;   // înălțimea A4 în puncte
  Margin = 36.0;   // chenar de o jumătate de țol în jurul fiecărei scanări
var
  I: Integer;
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.CreateDocument;                       // nou, gol, deja activ
    for I := 0 to Files.Count - 1 do
    begin
      Pdf.AddPage(I + 1, PageW, PageH);       // index de pagină de la 1
      Pdf.PageNumber := I + 1;                // fă curentă pagina nouă
      PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
    end;
    Pdf.SaveAs(OutputPath);
  finally
    Pdf.Free;
  end;
end;

Fiecare iterație creează o pagină și îi setează imediat PageNumber. Al doilea rând contează: AddPage inserează pagina, dar metodele de desenare acționează asupra paginii care este curentă, așa că setarea lui PageNumber este cea care îndreaptă AddImage spre pagina tocmai făcută. Dacă o săriți, imaginile se îngrămădesc pe orice pagină se întâmpla să fie încărcată înainte

În bucla aceea se ascunde o presupunere: ordinea lui Files. Un scaner numește paginile de la 0001.jpg la 0100.jpg, dar o enumerare de director nu le returnează întotdeauna sortate, iar în clipa în care ajungeți la page9.jpg lângă page10.jpg, o sortare simplă de șiruri pune pagina 10 înaintea paginii 9. Sortați lista explicit înainte de buclă și preferați nume completate cu zerouri la momentul scanării, ca ordinea lexicală să corespundă ordinii paginilor. Succesiunea paginilor este singurul lucru pe care un evaluator îl observă imediat și este cea mai ieftină greșeală de prevenit

Plasarea unei scanări și păstrarea raportului de aspect

O scanare are rareori aceeași formă ca pagina. Dacă o întindeți ca să umple coala, distorsionați textul; dacă o plasați la dimensiunea completă în pixeli, dă pe dinafară. Remediul este să scalați cu cel mai mic dintre cele două rapoarte, potrivirea pe lățime sau pe înălțime, și să centrați ce rămâne. Pentru că originea stă în stânga-jos, centrarea înseamnă împărțirea egală a spațiului rămas și adăugarea lui atât la X, cât și la Y

O diagramă de pagină A4 arată cum AddImage din PDFium Component plasează o scanare scalată în interiorul marginilor, folosind cod Delphi și o origine în stânga-jos
AddImage primește colțul din stânga-jos al dreptunghiului de plasare, așa că potrivirea și centrarea se calculează în puncte de pagină, pornind de la origine
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. prin unitățile grafice VCL

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

    // Încadrează în margini fără să distorsioneze scanarea.
    Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
    DrawW := Pic.Width * Scale;
    DrawH := Pic.Height * Scale;

    // Centrare: spațiul rămas se împarte egal. Y se măsoară de la baza paginii.
    X := (PageW - DrawW) / 2;
    Y := (PageH - DrawH) / 2;

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

Acest cod încarcă fișierul o dată ca să îi citească dimensiunile în pixeli, calculează o singură scalare uniformă și transmite dreptunghiul de plasare lui AddImage. AddImage acceptă direct o cale de fișier și o dirijează prin aceeași conductă de imagine ca AddPicture, așa că orice format recunoscut de unitățile grafice VCL funcționează fără tratare specială. Dacă aveți deja imaginea decodată într-un TPicture dintr-un panou de previzualizare, apelați AddPicture(Pic, X, Y, DrawW, DrawH) cu același dreptunghi și săriți a doua citire de fișier

Evitarea decodării pentru scanările JPEG

Scanerele emit aproape întotdeauna JPEG. Încărcarea unui JPEG într-un TPicture îl decodează într-o imagine bitmap, iar apoi PDFium îl recodează la salvare, două drumuri dus-întors cu pierderi de care nu aveți nevoie. AddJpegImage încorporează octeții comprimați originali direct în pagină, dintr-un flux, ceea ce este și mai rapid, și mai curat vizual pentru un lot de volum mare

AddJpegImage încorporează octeții JPEG originali într-o pagină PDFium Component, în timp ce AddImage decodează, iar SaveAs recodează pixelii în Delphi
AddJpegImage încorporează octeții comprimați ai scanerului ca atare, evitând trecerile de decodare și recodare pe care le fac AddImage și SaveAs
var
  Stream: TFileStream;
begin
  // ... după AddPage + PageNumber pentru pagina curentă ...
  Stream := TFileStream.Create(FileName, fmOpenRead);
  try
    // Încorporează octeții JPEG ca atare; fără ciclu de decodare/recodare.
    Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
  finally
    Stream.Free;
  end;
end;

Tot calculați X, Y, DrawW și DrawH în același fel, pentru că aveți nevoie de dimensiunile în pixeli ca să scalați. Citiți-le din fișier sau printr-o parsare rapidă a antetului, apoi dați fluxul brut lui AddJpegImage. Pentru scanări PNG sau TIFF, drumul AddImage este cel potrivit; păstrați scurtătura JPEG pentru formatul căruia i se aplică de fapt

Etichetarea fiecărei pagini

Scanările arhivate sunt mai ușor de auditat când fiecare pagină poartă numele fișierului ei sursă. AddText desenează un șir la o coordonată din spațiul utilizator, așa că o legendă stă chiar sub imagine. Rețineți axa Y inversată: pentru a pune o etichetă sub scanare, scădeți din marginea de jos a imaginii, nu adunați la ea

// Legendă sub scanare: Y scade spre baza paginii.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
  X, Y - 14, clGray);

O ultimă observație despre salvare. SaveAs este o funcție care returnează un Boolean, așa că în codul de producție verificați-i rezultatul în loc să presupuneți că scrierea a reușit; altfel un disc plin sau o cale de ieșire blocată eșuează în tăcere. Odată ce bucla se termină și fișierul este scris, aveți exact ce îi trebuia arhivei: câte un PDF ordonat per dosar, cu paginile scalate ca să încapă, gata de citit în orice vizualizator

Aceleași cărămizi acoperă și lucrări înrudite. Schimbați regula de dimensionare per pagină și obțineți un album foto cu o imagine per coală; păstrați bucla, dar citiți dintr-o sursă TIFF cu mai multe pagini și aveți un convertor de arhivă de faxuri. Dacă vreți imaginea mai largă a construirii programatice de PDF-uri, vedeți crearea documentelor PDF de la zero cu PDFium Component; pentru a randa mai târziu rezultatul înapoi pe ecran, vedeți conversia paginilor PDF în imagini JPEG cu PDFium Component

PDFium Component de la loslab.com include API-urile de creare a documentelor, de randare și de text folosite în toată această serie