Due documenti aperti nello stesso momento, stesso numero di pagina, ciascuno nel proprio pannello scorrevole: questo è il cuore di un viewer di confronto. PDFium Component lo realizza attraverso un modello a oggetti lineare in cui TPdf possiede il file e TPdfView possiede la visualizzazione. Un documento, un TPdf, un TPdfView. Volete tre pannelli, avete tre coppie. Le parti difficili non sono le chiamate API; sono l'aritmetica del layout quando la finestra viene ridimensionata e la logica di sincronizzazione delle pagine quando decidete quale vista debba seguire quale
Layout del form
Il form VCL contiene tre contenitori TScrollBox affiancati, ciascuno con un TPdfView dentro e allineato ad alClient in modo da riempire il box. Due componenti TSplitter stanno fra i box così che l'utente possa regolare le larghezze delle colonne a runtime. Una barra degli strumenti sopra i pannelli porta i pulsanti di apertura, i controlli di zoom e il selettore fra due e tre viste
La modalità a tre viste è un booleano che il form tiene internamente. Quando cambia, ricalcolate le larghezze e mostrate o nascondete la terza colonna. L'approccio più semplice è azzerare tutte le proprietà Align, nascondere gli splitter e poi impostare posizioni assolute:
procedure TFormMain.UpdateLayout;
var
TotalWidth: Integer;
begin
TotalWidth := ClientWidth;
if ThreeViewMode then
begin
ScrollBox3.Visible := True;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 3;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth div 3;
ScrollBox3.Left := ScrollBox2.Left + ScrollBox2.Width;
ScrollBox3.Width := TotalWidth - ScrollBox3.Left;
// Applicate lo stesso (ClientHeight - altezza toolbar) ai tre valori Height
end
else
begin
ScrollBox3.Visible := False;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 2;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth - ScrollBox2.Left;
end;
end;
Impostare Align := alNone su tutti e tre i box prima dell'aritmetica sugli interi evita che il motore dei vincoli della VCL combatta contro le vostre assegnazioni. Ripristinate la visibilità degli splitter dopo il posizionamento se volete il ridimensionamento a trascinamento in modalità a due viste
L'altezza di ogni scroll box è l'area client meno l'altezza del pannello della barra degli strumenti. Poiché la barra è agganciata in alto con alTop, ClientHeight - PanelButtons.Height vi dà lo spazio verticale utilizzabile. Assegnate questo valore a tutti e tre i box dentro la stessa chiamata a UpdateLayout così non esiste mai un fotogramma in cui un box è più alto degli altri e provoca uno sfarfallio del layout
Aprire un documento
Ogni coppia di pannelli ha bisogno della propria procedura di apertura. Lo schema è breve: disattivate il componente, impostate il nome del file, attivate, poi controllate Active; se è rimasto False, chiedete una password e ritentate. Notate che TPdfView.Active è ciò che controlla il rendering, mentre TPdf.Active è ciò che apre davvero il file; sono indipendenti. Impostare PdfView.Active := True quando il TPdf collegato non è ancora attivo è innocuo ma non mostra nulla
procedure TFormMain.OpenPdfFile(PdfComponent: TPdf;
PdfViewComponent: TPdfView);
var
Password: string;
begin
if not OpenDialog.Execute then
Exit;
PdfComponent.Active := False;
PdfComponent.FileName := OpenDialog.FileName;
PdfComponent.Password := '';
PdfComponent.Active := True;
// I fallimenti di caricamento sono silenziosi: Active resta False.
if not PdfComponent.Active then
begin
// Molto probabilmente un file protetto da password; un solo ritentativo.
if InputQuery('Password', 'Enter document password:', Password) then
begin
PdfComponent.Password := Password;
PdfComponent.Active := True;
end;
end;
if not PdfComponent.Active then
begin
ShowMessage('Could not open ' + OpenDialog.FileName +
' (damaged file or wrong password)');
Exit;
end;
PdfViewComponent.PageNumber := 1;
SetActivePdfView(PdfViewComponent);
end;
Controllate sempre PdfComponent.Active dopo l'assegnazione; un file danneggiato o una password sbagliata fanno fallire il caricamento in silenzio, senza sollevare un'eccezione nel percorso predefinito. Impostare esplicitamente PdfViewComponent.PageNumber := 1 dopo un'apertura riuscita evita un numero di pagina residuo dal documento precedente
La finestra di messaggio alla fine è intenzionale: volete che i file corrotti o non supportati emergano subito invece di essere inghiottiti come un pannello vuoto e silenzioso. Un utente che non vede nulla non ha modo di sapere se il file si è caricato ed è semplicemente vuoto, oppure se il componente lo ha rifiutato. Segnalare il fallimento tiene visibile l'errore
Tracciare il pannello attivo
Quando l'utente clicca dentro un pannello, quel pannello diventa attivo. Il form tiene un campo privato FActivePdfView: TPdfView. Il riscontro visivo è un cambio di colore del bordo sul TScrollBox contenitore: impostatelo a clHighlight per quello attivo e a clWindow per gli altri. Collegate tutto questo a ogni TPdfView.OnClick e alla procedura di apertura, così il focus segue il documento appena aperto
Alcune operazioni si applicano a tutti i pannelli visibili anziché al solo pannello attivo. Un booleano FAllViewsMode sul form guida quel ramo. Quando è vero, le variazioni di zoom e la navigazione fra le pagine si diramano verso ogni pannello che ha un documento attivo:
procedure TFormMain.ApplyZoomToAll(NewZoom: Double);
begin
if PdfView1.Active then PdfView1.Zoom := NewZoom;
if PdfView2.Active then PdfView2.Zoom := NewZoom;
if ThreeViewMode and PdfView3.Active then PdfView3.Zoom := NewZoom;
end;
Navigazione di pagina sincronizzata
La navigazione sincronizzata è facoltativa ma utile nei flussi di revisione dei documenti in cui entrambi i file coprono lo stesso intervallo di pagine. La logica appartiene a un gestore di evento che scatta dopo che l'utente ha navigato una vista. Quando una vista sorgente cambia il proprio PageNumber, il gestore propaga quel numero alle altre viste, con una sola guardia: la vista di destinazione deve avere almeno quel numero di pagine, altrimenti si salta
Il PageNumber di TPdfView e quello di TPdf sono indipendenti. TPdf.PageNumber traccia quale pagina il componente documento considera corrente; TPdfView.PageNumber traccia ciò che è mostrato sullo schermo. Per la navigazione vi serve la proprietà della vista, non quella del documento
Una casella di spunta intitolata qualcosa come Sync pages dà il controllo all'utente. Quando non è spuntata, ogni pannello naviga in modo indipendente e il gestore esce subito. Quella indipendenza è importante nei casi in cui i due documenti hanno un numero di pagine diverso, o in cui l'utente vuole trovare il passaggio equivalente in una traduzione che inizia a una pagina diversa. Forzare sempre la sincronizzazione renderebbe lo strumento più scomodo di una semplice disposizione a due finestre sul desktop
Una cosa a cui fare attenzione: impostare PdfView.PageNumber programmaticamente dentro il gestore di sincronizzazione farà scattare a sua volta l'evento di cambio su quella vista. Proteggetevi dalla ricorsione infinita con un flag booleano che impostate prima dell'assegnazione e azzerate subito dopo. Il flag è per form, non per vista, perché tutte e tre le viste condividono lo stesso gestore
Zoom per pannello
Ogni TPdfView porta con sé la propria proprietà Zoom, un Double in percentuale dove Zoom := 100 significa dimensione reale (100%). Impostarla scavalca qualsiasi FitMode attivo. Per un pulsante di adattamento alla larghezza sul pannello attivo, leggete lo zoom di adattamento da PdfView.PageWidthZoom[PdfView.PageNumber] e assegnatelo. Per l'adattamento alla pagina, usate PageZoom[PageNumber]. Entrambe sono proprietà array indicizzate per numero di pagina in base 1, quindi proteggetevi da un numero di pagina pari a zero prima di accedervi
Quando esportate la pagina corrente come immagine, leggete la rotazione dalla vista ma chiamate RenderPage sul componente TPdf, non sulla vista. La forma bitmap di TPdf.RenderPage accetta dimensioni in pixel esplicite più un valore TRotation e un insieme TRenderOptions. La variante a funzione restituisce una TBitmap di proprietà del chiamante che liberate voi stessi dopo il salvataggio:
procedure TFormMain.SaveActiveViewAsImage;
var
Pdf: TPdf;
Bmp: TBitmap;
Jpeg: TJpegImage;
begin
if not Assigned(FActivePdfView) or not FActivePdfView.Active then
Exit;
Pdf := FActivePdfView.Pdf;
Pdf.PageNumber := FActivePdfView.PageNumber;
Bmp := Pdf.RenderPage(
0, 0,
Round(Pdf.PageWidth * 2),
Round(Pdf.PageHeight * 2),
FActivePdfView.Rotation, [], clWhite);
try
if SavePictureDialog.Execute then
begin
Jpeg := TJpegImage.Create;
try
Jpeg.Assign(Bmp);
Jpeg.CompressionQuality := 90;
Jpeg.SaveToFile(SavePictureDialog.FileName);
finally
Jpeg.Free;
end;
end;
finally
Bmp.Free;
end;
end;
Il moltiplicatore 2x su larghezza e altezza dà un risultato più nitido per i documenti con testo minuto. Il try/finally attorno alla liberazione della bitmap non è facoltativo; un annullamento della TSaveDialog passa comunque dal blocco finally, e volete che la bitmap venga rilasciata a prescindere da ciò che l'utente ha fatto
Requisiti della DLL
PDFium Component avvolge la libreria nativa pdfium. Un processo host a 32 bit ha bisogno di pdfium32.dll; uno a 64 bit ha bisogno di pdfium64.dll. Le varianti con il motore JavaScript V8 aggiungono il suffisso v8 e pesano all'incirca 23-27 MB contro i 5-6 MB delle build standard. Per un viewer di confronto che disattiva la compilazione dei moduli (Pdf.FormFill := False), la build standard senza V8 è sufficiente e mantiene la distribuzione più leggera
Collocate la DLL nella stessa directory dell'eseguibile, oppure in una qualsiasi directory presente nel PATH di sistema. Il componente la carica su richiesta quando il primo TPdf viene attivato, quindi una DLL mancante si manifesta in quel momento e non all'avvio dell'applicazione. Se distribuite un installer, l'approccio più affidabile è copiare la DLL nella cartella dell'applicazione durante l'installazione invece di affidarsi a una directory di sistema che un amministratore potrebbe poi ripulire
Le build con V8 sono utili soprattutto quando dovete interagire con le azioni JavaScript dei PDF, per esempio per attivare campi di calcolo o gestori di invio. Un viewer di confronto passivo non ha alcun motivo di eseguire JavaScript; impostare Pdf.FormFill := False prima di Active := True salta del tutto l'ambiente di compilazione dei moduli, il che significa anche che nessun motore JS viene inizializzato nemmeno se si usa la build standard. Quello è il valore predefinito corretto per un viewer in sola lettura a prescindere da quale variante di DLL distribuiate
Per ulteriori dettagli su PDFium Component e sulla sua API completa, visitate la pagina di prodotto di Delphi PDFium Component