Techninis straipsnis

Skenuotų vaizdų sujungimas į vieną PDF su PDFium

Žalų administravimo komanda per lapinį skaitytuvą leido trisdešimties metų popierines bylas. Skaitytuvas į aplanką išspjovė po vieną JPEG kiekvienam lapui, pavadintus 0001.jpg, 0002.jpg ir taip toliau. Archyvui iš tikrųjų reikėjo po vieną PDF kiekvienai bylai, su puslapiais tvarkinga eile, kad recenzentas galėtų atverti vieną dokumentą, o ne spaudinėti šimtą vaizdų miniatiūrų. Tas paskutinis žingsnis, sunumeruotos skenuotų failų krūvos pavertimas vienu tvarkingu PDF, čia ir yra darbas

PDFium Component tai atlieka tiesiogiai. Be atvaizdavimo ir teksto ištraukimo, komponentas gali sukurti PDF nuo nulio: sukurti tuščią dokumentą, pridėti tuščią bet kokio dydžio puslapį, uždėti ant to puslapio vaizdą naudotojo erdvės koordinatėse ir išsaugoti. Visa grandinė gyvena TPdf komponente, todėl paketinis konvertavimas yra ciklas per failų vardus plius sauja iškvietimų

Delphi paketinė grandinė naudoja PDFium Component iškvietimus AddPage, PageNumber, AddImage ir SaveAs, kad sunumeruotų skenuotų failų aplanką paverstų vienu tvarkingu PDF
Kiekvienas skenuotas failas tampa vienu puslapiu, kurį sukuria AddPage ir nutaiko PageNumber prieš AddImage jį nupiešiant; SaveAs baigtą dokumentą įrašo vieną kartą

Konvertavimo forma

Kiekvienam skenuotam failui turi įvykti trys dalykai. Jūs nusprendžiate puslapio dydį, patalpinate vaizdą puslapio viduje paliekdami paraštę ir pereinate prie kito puslapio. PDFium Component kiekvienam duoda po metodą: AddPage sukuria tuščią nurodyto dydžio puslapį, AddImage (arba AddPicture, jei jau turite TPicture) nupiešia rastrą į dabartinį puslapį, o PageNumber pasako komponentui, į kurį puslapį taikosi tolesni piešimo iškvietimai

Viena detalė, ant kurios žmonės užkliūva, yra koordinačių sistema. PDF naudotojo erdvė pradžios tašką deda apatiniame kairiajame puslapio kampe, o Y auga aukštyn — priešingai nei ekrano koordinatėse, kurių Delphi kūrėjai griebiasi refleksiškai. X, Y, kuriuos paduodate į AddImage, yra apatinis kairysis vaizdo stačiakampio kampas, o Width, Height yra patalpinimo dydis punktais, o ne šaltinio failo pikselių dydis. Supainiokite tai ir jūsų skenuoti vaizdai nusileis už puslapio ribų arba apversti, palyginti su tuo, ko tikėjotės

Dokumento ir po puslapį kiekvienam skenuotam failui kūrimas

Pradėkite nuo tuščio dokumento. CreateDocument išskiria naują PDF ir palieka komponentą aktyvų, todėl atskiro atvėrimo žingsnio nėra. Nuo čia einate per skenuotų failų sąrašą ir kiekvienam pridedate puslapį, padarote jį dabartiniu ir patalpinate vaizdą. Puslapio matmenys čia yra A4 punktais (595 × 842 stačiai) — standartinis lapo dydis archyvuojamai korespondencijai

procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
  PageW = 595.0;   // A4 plotis punktais
  PageH = 842.0;   // A4 aukštis punktais
  Margin = 36.0;   // pusės colio rėmelis aplink kiekvieną skenuotą vaizdą
var
  I: Integer;
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.CreateDocument;                       // naujas, tuščias, jau aktyvus
    for I := 0 to Files.Count - 1 do
    begin
      Pdf.AddPage(I + 1, PageW, PageH);       // puslapio indeksas nuo 1
      Pdf.PageNumber := I + 1;                // padaryti naują puslapį dabartiniu
      PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
    end;
    Pdf.SaveAs(OutputPath);
  finally
    Pdf.Free;
  end;
end;

Kiekviena iteracija sukuria puslapį ir iškart nustato į jį PageNumber. Ta antroji eilutė svarbi: AddPage puslapį įterpia, tačiau piešimo metodai veikia tą puslapį, kuris yra dabartinis, todėl būtent PageNumber nustatymas nutaiko AddImage į ką tik sukurtą puslapį. Praleiskite jį ir jūsų vaizdai susikraus ant to puslapio, kuris buvo įkeltas anksčiau

Tame cikle slepiasi viena prielaida: Files tvarka. Skaitytuvas puslapius pavadina nuo 0001.jpg iki 0100.jpg, tačiau katalogo perrinkimas ne visada grąžina juos surikiuotus, o vos tik pataikysite ant page9.jpg greta page10.jpg, paprastas eilučių rikiavimas 10 puslapį padės pirma 9. Prieš ciklą sąrašą surikiuokite aiškiai, o skenuodami rinkitės nuliais užpildytus vardus, kad leksinė tvarka sutaptų su puslapių tvarka. Puslapių eiliškumą recenzentas pastebi iš karto, ir tai pigiausiai išvengiama klaida

Skenuoto vaizdo patalpinimas išlaikant jo proporcijas

Skenuotas vaizdas retai būna tokios pat formos kaip puslapis. Ištempsite jį per visą lapą — iškraipysite tekstą; padėsite pilnu pikselių dydžiu — jis išsilies per kraštus. Sprendimas yra mastelį imti pagal mažesnį iš dviejų santykių, pločio arba aukščio, ir tai, kas lieka, sucentruoti. Kadangi pradžios taškas yra apatiniame kairiajame kampe, centravimas reiškia likusią vietą padalyti po lygiai ir pridėti prie abiejų X ir Y

A4 puslapio diagrama rodo, kaip PDFium Component AddImage patalpina mastelio pakeistą skenuotą vaizdą paraščių viduje naudojant Delphi kodą ir apatinį kairįjį pradžios tašką
AddImage priima apatinį kairįjį patalpinimo stačiakampio kampą, todėl talpinimas ir centravimas skaičiuojami puslapio punktais nuo pradžios taško
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 ir kt. per VCL grafikos modulius

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

    // Įtalpinti paraščių viduje neiškraipant skenuoto vaizdo.
    Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
    DrawW := Pic.Width * Scale;
    DrawH := Pic.Height * Scale;

    // Centravimas: likusi vieta padalijama po lygiai. Y matuojamas nuo puslapio apačios.
    X := (PageW - DrawW) / 2;
    Y := (PageH - DrawH) / 2;

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

Šis kodas failą įkelia vieną kartą, kad nuskaitytų jo pikselių matmenis, apskaičiuoja vieną vienodą mastelį ir paduoda patalpinimo stačiakampį į AddImage. AddImage priima failo kelią tiesiogiai ir nukreipia jį per tą pačią vaizdų grandinę kaip AddPicture, todėl bet kuris formatas, kurį atpažįsta VCL grafikos moduliai, veikia be atskirų atvejų. Jei vaizdą jau turite dekoduotą TPicture objekte iš peržiūros srities, iškvieskite AddPicture(Pic, X, Y, DrawW, DrawH) su tuo pačiu stačiakampiu ir praleiskite antrą failo skaitymą

Dekodavimo praleidimas JPEG skenuotiems vaizdams

Skaitytuvai beveik visada išveda JPEG. JPEG įkėlimas į TPicture jį dekoduoja į rastrą, o tada PDFium išsaugodamas jį užkoduoja iš naujo — du nuostolingi ciklai, kurių jums nereikia. AddJpegImage originalius suspaustus baitus iš srauto įterpia tiesiai į puslapį, o tai didelės apimties paketui yra ir greičiau, ir vizualiai švariau

AddJpegImage įterpia originalius JPEG baitus į PDFium Component puslapį, o AddImage dekoduoja ir SaveAs Delphi aplinkoje pikselius užkoduoja iš naujo
AddJpegImage įterpia skaitytuvo suspaustus baitus tokius, kokie yra, taip išvengiant dekodavimo ir pakartotinio kodavimo praėjimų, kuriuos atlieka AddImage ir SaveAs
var
  Stream: TFileStream;
begin
  // ... po AddPage + PageNumber dabartiniam puslapiui ...
  Stream := TFileStream.Create(FileName, fmOpenRead);
  try
    // Įterpia JPEG baitus tokius, kokie yra; jokio dekodavimo ir pakartotinio kodavimo ciklo.
    Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
  finally
    Stream.Free;
  end;
end;

X, Y, DrawW ir DrawH vis tiek skaičiuojate taip pat, nes masteliui reikia pikselių matmenų. Nuskaitykite juos iš failo arba greitai išanalizuodami antraštę, o tada paduokite neapdorotą srautą į AddJpegImage. PNG ar TIFF skenuotiems vaizdams teisingas kelias yra AddImage; JPEG trumpinį palikite tam formatui, kuriam jis iš tikrųjų taikomas

Puslapių žymėjimas

Archyvuotus skenuotus vaizdus lengviau audituoti, kai kiekvienas puslapis neša savo šaltinio failo vardą. AddText nupiešia eilutę naudotojo erdvės koordinatėje, todėl antraštė atsisėda tiesiai po vaizdu. Prisiminkite apverstą Y ašį: kad etiketė atsidurtų po skenuotu vaizdu, iš apatinio vaizdo krašto reikia atimti, o ne prie jo pridėti

// Antraštė po skenuotu vaizdu: Y mažėja puslapio apačios link.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
  X, Y - 14, clGray);

Paskutinė pastaba apie išsaugojimą. SaveAs yra funkcija, grąžinanti loginę reikšmę, todėl gamybiniame kode tikrinkite jos rezultatą, o ne laikykite, kad įrašymas pavyko; kitaip pilnas diskas ar užrakintas išvesties kelias suklups tyliai. Kai ciklas baigiasi ir failas įrašytas, turite būtent tai, ko archyvui reikėjo: po vieną tvarkingą PDF kiekvienai bylai, puslapius su tinkamu masteliu, paruoštus skaityti bet kurioje peržiūros programoje

Tie patys statybiniai blokai apima ir giminingus darbus. Pakeiskite puslapio dydžio taisyklę ir gausite fotoknygą su vienu vaizdu lape; palikite ciklą, bet skaitykite iš daugiapuslapio TIFF šaltinio, ir turėsite faksų archyvo konverterį. Jei norite platesnio vaizdo apie programinį PDF kūrimą, žiūrėkite PDF dokumentų kūrimą nuo nulio su PDFium Component; o kaip rezultatą vėliau atvaizduoti ekrane, žiūrėkite PDF puslapių vertimą į JPEG vaizdus su PDFium Component

PDFium Component iš loslab.com sujungia dokumentų kūrimo, atvaizdavimo ir teksto API, naudojamus visoje šioje serijoje