Questa pagina può contenere testo tradotto automaticamente.

Guida introduttiva alla generazione di PDF C#

Leggi come generare file PDF come report, fatture e ricevute nei tuoi progetti .NET. Utilizzando C# o VB.NET, crea facilmente documenti PDF componendo elementi strutturali. Gli elementi includono intestazioni, piè di pagina, contenitori, tabelle, paragrafi, immagini e simili. L'API suddivide automaticamente il contenuto in pagine.

Alcuni altri metodi per generare file PDF descritti nell'articolo Creare documenti PDF in C# e VB.NET.

Presentazione della libreria di generazione PDF

Offriamo l'API di layout come componente aggiuntivo gratuito per la libreria Docotic.Pdf. Sia la libreria che il componente aggiuntivo Layout sono disponibili su NuGet e dal nostro sito. Ottieni la libreria, il componente aggiuntivo e una chiave di licenza gratuita a tempo limitato sulla pagina Scarica la libreria PDF C# .NET.

Libreria Docotic.Pdf 9.5.17615-dev Componente aggiuntivo di layout 9.5.17615-dev
Test di regressione Ne sono passati 14,813 Download totali di NuGet 4,924,084

Installazione

Installa il pacchetto BitMiracle.Docotic.Pdf.Layout da NuGet. Questo è il modo più semplice e conveniente per installare il componente aggiuntivo Layout.

In alternativa, puoi scaricare lo ZIP con i binari della libreria sul nostro sito. Per utilizzare l'API Layout, aggiungi riferimenti alle seguenti DLL dal pacchetto ZIP:

  • BitMiracle.Docotic.Pdf.dll
  • Layout add-on/BitMiracle.Docotic.Pdf.Layout.dll

L'approccio

Il punto di ingresso è la classe PdfDocumentBuilder. Per iniziare a generare un PDF, chiami il metodo Generate della classe. Il metodo richiede un delegato di tipo Action<Document> come uno dei suoi parametri. La libreria prevede che tu disponga il contenuto del documento nel delegato.

PdfDocumentBuilder.Create().Generate("output.pdf", (Document doc) =>
{
    // crea il contenuto del documento qui
});

Dato un oggetto Document, il delegato dovrebbe definire il layout. Il layout completo è costituito da blocchi più piccoli. Pagine, intestazioni e piè di pagina, contenitori, blocchi di testo sono esempi di tali blocchi.

Molte chiamate di metodo sono concatenate insieme. L'ordine delle chiamate in una catena è importante. Ecco un esempio che mostra come impostare il contenuto delle pagine in poche chiamate concatenate.

PdfDocumentBuilder.Create()
    .Generate("output.pdf", doc => doc.Pages(pages =>
    {
        pages.Content().Padding(24).AlignCenter().Text("Some text");
    }));

Intellisense ti aiuterà a scoprire l'API. Prova tu stesso l'API. Spero che lo troverai facile da usare.

Ciao mondo!

Sviluppiamo una semplice applicazione che mostri gli elementi costitutivi comuni in azione. L'app utilizzerà l'approccio sopra menzionato.

Ho inserito il codice completo dell'applicazione nel nostro repository di codici di esempio Docotic.Pdf. Il codice per questo passaggio si trova nella classe HelloWorld dell'app di esempio.

public void CreatePdf()
{
    PdfDocumentBuilder.Create().Generate("hello.pdf", doc => doc.Pages(page =>
    {
        page.Content().Text("Hello, world!");
    }));
}

Non molto codice, ma cosa sta succedendo qui?

Spiegazione lunga per il codice breve

Il codice crea un'istanza del generatore di documenti, chiede al costruttore di generare hello.pdf. Il costruttore delega il lavoro di layout del contenuto del documento al mio codice.

Nel codice del delegato, chiedo al documento di costruire alcune pagine. Ora il documento delega il compito di disporre il contenuto delle pagine al mio codice.

Nel delegato per le pagine accedo al contenitore di layout per il contenuto principale delle pagine. Lo faccio chiamando il metodo Content. La chiamata concatenata a Text aggiunge il famoso intervallo di testo al contenuto delle pagine.

Ora è il momento che il costruttore generi il documento con alcune pagine secondo il layout fornito. Il builder crea il numero esatto di pagine necessarie per contenere i dati che ho aggiunto nel contenitore del layout per il contenuto principale. In questo caso è sufficiente una pagina? Va bene.

Preparati per gli aggiornamenti futuri

Aggiungerò alcune altre funzionalità al codice. Per rendere più comodo lo sviluppo ulteriore dell'app, ho modificato il codice in questo modo:

public void CreatePdf()
{
    PdfDocumentBuilder.Create().Generate("hello.pdf", BuildDocument);
}

static void BuildDocument(Document doc)
{
    doc.Pages(BuildPages);
}

static void BuildPages(PageLayout pages)
{
    pages.Content().Text("Hello, world!");
}

Potrebbe sembrare inutile, ma dividere il codice in metodi almeno lo rende più leggibile.

Il codice di esempio per questo passaggio si trova nella classe HelloWorld2 dell'app di esempio.

Per ulteriori informazioni su documenti, pagine e contenitori, consultare i seguenti articoli.

Caratteri e colori

Il carattere predefinito è buono, ma mostrerò come usarne un altro. L'idea principale è creare uno stile di testo utilizzando un carattere. Quindi, se necessario, applica alcune proprietà opzionali allo stile e, infine, utilizza lo stile su un intervallo di testo.

È possibile utilizzare un carattere dalla raccolta di caratteri installati nel sistema operativo o caricare un carattere da un file o da un flusso.

Consiglio di sovrascrivere gli stili predefiniti impostando la tipografia per il documento. Ma ciò non è obbligatorio e puoi utilizzare direttamente gli stili di testo.

Il codice di esempio per questo passaggio si trova nella classe HelloWorld3 dell'app di esempio. Di seguito sono riportate le parti che sono cambiate rispetto al passaggio precedente.

static void BuildDocument(Document doc)
{
    doc.Typography(t =>
    {
        t.Document = doc.TextStyleWithFont(SystemFont.Family("Calibri"));
    });
    doc.Pages(BuildPages);
}

static void BuildPages(PageLayout pages)
{
    pages.Size(PdfPaperSize.A6);
    pages.Margin(10);

    BuildTextContent(pages.Content());
}

static void BuildTextContent(LayoutContainer content)
{
    var colorAccented = new PdfRgbColor(56, 194, 10);
    var styleAccented = TextStyle.Parent.FontColor(colorAccented);

    content.Text(t =>
    {
        t.Span("Hello, ").Style(styleAccented);
        t.Span("world!").Style(styleAccented.Underline());
    });
}

Come puoi vedere, ho aggiornato il codice per BuildDocument e BuildPages. Ho anche aggiunto un nuovo metodo denominato BuildTextContent.

In BuildDocument, creo uno stile di testo dal carattere di sistema denominato "Calibri". Quindi imposto questo stile di testo come stile di testo predefinito per il documento.

Il metodo BuildPages ora contiene il codice per impostare la dimensione e il margine per tutte le pagine. Inoltre, il metodo chiama BuildTextContent, passando il contenitore per il contenuto principale delle pagine come parametro.

Il modo in cui costruisco il contenuto principale ora è diverso. Utilizzo due porzioni di testo e applico stili di testo diversi a ciascuna porzione. In effetti, entrambe le estensioni utilizzano il colore principale, ma anche la seconda estensione ha una sottolineatura.

Il codice produce un PDF con il carattere e il colore del testo personalizzati. Se il risultato contiene un messaggio di avviso di prova, ottieni una licenza gratuita limitata nel tempo nella pagina della libreria Docotic.Pdf.

Molti documenti PDF reali come report o fatture contengono intestazione e piè di pagina. Lascia che ti mostri come aggiungere intestazione e piè di pagina a un PDF.

Il codice di esempio per questo passaggio si trova nella classe HelloWorld4 dell'app di esempio. Di seguito sono riportate le parti che sono cambiate rispetto al passaggio precedente.

static void BuildPages(PageLayout pages)
{
    pages.Size(PdfPaperSize.A6);
    pages.Margin(10);

    BuildPagesHeader(pages.Header());
    BuildPagesFooter(pages.Footer());

    BuildTextContent(pages.Content());
}

static void BuildPagesHeader(LayoutContainer header)
{
    header.TextStyle(TextStyle.Parent.FontSize(8))
        .AlignRight()
        .Text(t =>
        {
            t.Line($"Created by: {Environment.UserName}");
            t.Line($"Date: {DateTime.Now}");
        });
}

static void BuildPagesFooter(LayoutContainer footer)
{
    footer.Height(20)
        .Background(new PdfGrayColor(95))
        .AlignCenter()
        .Text(t => t.CurrentPageNumber());
}

Ho cambiato il metodo BuildPages per chiamare BuildPagesHeader con il contenitore dell'intestazione come parametro. Il metodo chiama anche BuildPagesFooter passandogli il contenitore del footer.

Il PDF con intestazione e piè di pagina risultante assomiglia decisamente di più a un documento PDF professionale.

Il metodo BuildPagesHeader imposta una dimensione del carattere più piccola per il testo. Utilizzo due righe per il contenuto dell'intestazione: una con il nome dell'utente corrente e la seconda con la data corrente. Il testo è allineato a destra.

Tieni presente che non specifico alcuna dimensione esplicita per l'intestazione. Occuperà l'intera larghezza della pagina meno i margini sinistro e destro. L'altezza dipenderà dall'altezza delle righe di testo.

Il piè di pagina è simile, tranne per il fatto che la sua altezza è specificata esplicitamente. E ad esso è applicato un colore di sfondo. La parte interessante è che per inserire il numero di pagina corrente nel piè di pagina del PDF, posso semplicemente usare il metodo CurrentPageNumber. Tutti i calcoli avvengono all'interno della libreria.

Immagini

Come si suol dire, puoi risparmiare molte parole usando una sola immagine. In un preventivo o in una ricevuta, probabilmente utilizzerai il logo della tua azienda o qualche altra immagine importante. Per questo codice di esempio, ne userò solo uno dall'aspetto gradevole.

Il file è sul nostro sito, così puoi vedere come appare il PDF con la bellissima immagine risultante.

Per utilizzare un'immagine, devi prima aggiungerla al documento. La libreria può leggere l'immagine da un file o da un flusso. Ho cambiato il metodo BuildDocument per mostrare come aggiungere un'immagine al documento.

Il codice di esempio per questo passaggio si trova nella classe HelloWorld5 dell'app di esempio. Di seguito sono riportate le parti che sono cambiate rispetto al passaggio precedente.

static void BuildDocument(Document doc)
{
    doc.Typography(t =>
    {
        t.Document = doc.TextStyleWithFont(SystemFont.Family("Calibri"));
    });

    var imageFile = new FileInfo("red-flowers-at-butterfly-world.jpg");
    var image = doc.Image(imageFile);

    doc.Pages(pages => {
        BuildPages(pages);

        pages.Content().Column(c =>
        {
            BuildTextContent(c.Item());
            BuildImageContent(c.Item(), image);
        });
    });
}

static void BuildPages(PageLayout pages)
{
    pages.Size(PdfPaperSize.A6);
    pages.Margin(10);

    BuildPagesHeader(pages.Header());
    BuildPagesFooter(pages.Footer());
}

static void BuildImageContent(LayoutContainer content, Image image)
{
    content.AlignCenter()
        .PaddingVertical(20)
        .Image(image);
}

Come puoi vedere, ci sono ulteriori modifiche nel metodo BuildDocument. Prima, il codice utilizzava il metodo Text per impostare del testo come contenuto principale delle pagine. Ciò accade ancora nel metodo BuildTextContent. Ma ora voglio anche un'immagine nel contenuto.

Per avere sia il testo che l'immagine nel contenuto principale delle pagine, ho bisogno di un contenitore. Utilizzo il contenitore Column per aggiungere il testo e l'immagine verticalmente uno dopo l'altro. Il metodo Item del contenitore di colonne fornisce un sottocontenitore. Utilizzo una chiamata al metodo Item per ottenere un contenitore per il testo e un'altra chiamata per ottenere un contenitore per l'immagine.

Come puoi vedere, non ho modificato minimamente BuildTextContent. Ma, ovviamente, ho dovuto rimuovere una chiamata a BuildTextContent dal metodo BuildPages.

BuildImageContent svolge un lavoro importante in tre righe. Aggiunge l'immagine con un po' di riempimento in alto e in basso. E centra anche l'immagine sulla pagina.

Ulteriori informazioni sul contenitore Column e sulle immagini sono disponibili nei seguenti articoli.

Liste

Cos'è una lista? Consideriamolo come un gruppo di elementi numerati scritti uno sotto l'altro. Questa idea suggerisce un modo per implementare un elenco.

Il codice di esempio per questo passaggio si trova nella classe HelloWorld6 dell'app di esempio. Di seguito sono riportate le parti che sono cambiate rispetto al passaggio precedente.

static void BuildDocument(Document doc)
{
    ....

    doc.Pages(pages => {
        BuildPages(pages);

        pages.Content().Column(c =>
        {
            BuildTextContent(c.Item());
            BuildImageContent(c.Item(), image);
            BuildListContent(c.Item());
        });
    });
}

static void BuildListContent(LayoutContainer content)
{
    var dayNames = DateTimeFormatInfo.InvariantInfo.DayNames;
    var dayNamesSpain = DateTimeFormatInfo.GetInstance(new CultureInfo("es-ES")).DayNames;

    content.Column(column =>
    {
        for (int i = 0; i < dayNames.Length; i++)
        {
            column.Item().Row(row =>
            {
                row.Spacing(5);
                row.AutoItem().Text($"{i + 1}.");
                row.RelativeItem().Text(t => {
                    t.Line(dayNames[i]);
                    t.Line($"In Spain they call it {dayNamesSpain[i]}");
                });
            });
        }
    });
}

L'unico cambiamento in BuildDocument è la nuova chiamata al metodo. Ho aggiunto la chiamata a BuildListContent dopo la chiamata a BuildImageContent.

BuildListContent crea l'elenco come una colonna di righe. In ogni riga sono presenti due elementi. Il primo (a sinistra) è per il numero dell'articolo. Un altro (a destra) è per il testo dell'elemento. Il codice imposta esplicitamente la spaziatura tra gli elementi nella riga.

Per organizzare gli articoli, il contenitore Row deve conoscere in anticipo o calcolare la dimensione di ciascun articolo. In questo esempio utilizzo i metodi AutoItem e RelativeItem. Di conseguenza, il contenitore della riga calcolerà la larghezza necessaria per contenere il primo elemento. Quindi il contenitore utilizzerà la larghezza disponibile rimanente per il secondo articolo.

Dopo aver aggiunto l'elenco, il contenuto del PDF risultante non occupa più una pagina. L'API Layout aggiunge automaticamente la seconda pagina e vi inserisce l'elenco. Puoi vedere che l'API ripete l'intestazione e il piè di pagina nella nuova pagina nel documento PDF con più pagine.

Abbiamo maggiori informazioni sugli elenchi nell'articolo Contenitori composti.

Tabelle

Molti documenti PDF contengono tabelle. Non c'è alcuna sorpresa qui perché le tabelle migliorano la chiarezza e l'organizzazione dei dati. Lascia che ti mostri come creare una tabella in PDF utilizzando l'API Layout.

Il codice di esempio per questo passaggio si trova nella classe HelloWorld7 dell'app di esempio. Di seguito sono riportate le parti che sono cambiate rispetto al passaggio precedente.

static void BuildDocument(Document doc)
{
    ....

    doc.Pages(pages => {
        BuildPages(pages);

        pages.Content().Column(c =>
        {
            BuildTextContent(c.Item());
            BuildImageContent(c.Item(), image);
            BuildListContent(c.Item());
            BuildTableContent(c.Item());
        });
    });
}

static void BuildTableContent(LayoutContainer content)
{
    var color = new PdfGrayColor(75);

    content.PaddingTop(20).Table(t =>
    {
        t.Columns(c =>
        {
            c.RelativeColumn(4);
            c.RelativeColumn(1);
            c.RelativeColumn(4);
        });

        t.Header(h =>
        {
            h.Cell().Background(color).Text("Month in 2024");
            h.Cell().Background(color).Text("Days");
            h.Cell().Background(color).Text("First Day");
        });

        for (int i = 0; i < 12; i++)
        {
            var stats = GetMonthStats(2024, i);

            t.Cell().Text(stats.Item1);
            t.Cell().Text(stats.Item2);
            t.Cell().Text(stats.Item3);
        }
    });
}

static (string, string, string) GetMonthStats(int year, int monthIndex)
{
    return (
        DateTimeFormatInfo.InvariantInfo.MonthNames[monthIndex],
        DateTime.DaysInMonth(year, monthIndex + 1).ToString(),
        new DateTime(year, monthIndex + 1, 1).DayOfWeek.ToString()
    );
}

La chiamata a BuildTableContent è l'unica modifica in BuildDocument.

In BuildTableContent, creo una tabella semplice. La tabella mostra informazioni banali sui mesi dell'anno 2024. Per cominciare, definisco tre colonne con relative larghezze. Le colonne più a sinistra e a destra saranno 4 volte più larghe della colonna al centro.

Il codice definisce anche l'intestazione della tabella. Lo fa aggiungendo celle di intestazione e specificando il testo e il colore di sfondo per ciascuna cella. Quando la tabella non rientra in una pagina, l'intestazione viene ripetuta su ogni pagina occupata dalla tabella. Puoi vederlo nel documento PDF con tabella risultante. Nel documento la tabella inizia nella seconda pagina e continua nella terza.

Sto usando il ciclo semplice per creare righe. Il codice nel ciclo inizia recuperando le informazioni su ogni mese. Quindi aggiunge tre celle che compongono una riga di informazioni.

L'articolo Contenitore Table spiega tutte le funzionalità del contenitore Table in maggior dettaglio.

Codice d'esempio

Sopra ho presentato alcune delle funzionalità più popolari della generazione di PDF in C#. Ti suggerisco di continuare a scoprire l'API leggendo il seguente articolo. Prova anche il codice di esempio dal repository Docotic.Pdf.Samples su GitHub. Il codice di esempio per l'API Layout si trova nella cartella Layout del repository.

È possibile utilizzare il codice di esempio come parco giochi per il codice. In questo modo puoi provare alcune idee senza ricominciare da capo ogni volta.

Lo stesso codice di esempio si trova nella cartella Samples del pacchetto ZIP. Sono presenti due file di soluzione. SamplesCSharp è per progetti di esempio che utilizzano il linguaggio C#. E SamplesVB.NET è per le versioni VB.NET.