首頁 > 後端開發 > Golang > 主體

透過 Maroto 在 Golang 中產生 PDF 來進行長期操作

王林
發布: 2024-08-30 22:35:02
原創
784 人瀏覽過

作者:Subha Chanda✏️

Go,也稱為 Golang,是由 Google 設計的靜態類型、編譯型程式語言。它結合了靜態類型語言的效能和安全性優勢以及通常與動態類型語言相關的易用性。

由於其並發性和多線程機制以及出色的標準庫,Go 成為各種應用程式的流行選擇,包括 Web 開發、網頁編程和系統編程。

PDF 產生是各種類型的應用程式所需的重要任務。大規模高效地產生 PDF 可能是一項複雜的任務。然而,以其效能和效率而聞名的 Go 可能是在大批量場景中產生 PDF 的不錯選擇。

但是,編寫自己的 PDF 產生器可能是一項複雜的工作。這就是馬羅托發揮作用的地方。它是一個 PDF 生成庫,靈感來自 Bootstrap 的佈局系統。

具有行和列的網格系統使佈局管理更加容易。它還支援自動分頁以處理內容溢出。您還可以獲得對文字、圖像、條碼、QR 碼、簽名等的內建支援。

我將撰寫有關如何在 Maroto 的幫助下在 Golang 中建立動態 PDF 的文章。

為什麼我們需要PDF生成?

PDF 產生功能對於許多類型的專案和應用程式都非常有價值。

  • 發票系統 - 自動產生 PDF 發票並將其發送給客戶,可確保格式的一致性,並允許輕鬆自訂發票範本
  • 報表工具 — PDF 產生對於建立商業分析、財務報表或專案狀態更新的詳細報表至關重要
  • 票務系統 — 產生 PDF 門票對於旅遊預訂、活動入場或電影放映確認至關重要
  • 標籤建立 - 動態產生用於運輸、產品標籤或組織目的的標籤,確保清晰準確地呈現正確的資訊
  • 證書產生 - 自動建立課程完成證書、獎項或認證,可以輕鬆自訂和個人化每個證書
  • 合約管理 — 根據特定條款或條件產生具有動態內容的法律文件或合同,簡化了創建準確和個性化協議的過程

您可以想像其他用例,並了解為什麼自動化 PDF 可確保一致性並提供專業外觀的格式。

在 Golang 中使用 Maroto 之前需要了解什麼

在繼續之前,請確保您已記下以下內容:

  • 對 Go 的基本了解
  • Go 安裝在您的電腦上,最好是最新版本,在撰寫本文時為 1.22.2
  • 您首選的 IDE 或文字編輯器 — 我推薦 Visual Studio
  • 為您的 IDE 擴充(如果可用)

如果您需要熟悉 Go,您可以按照本指南來獲得對該語言的基本了解。如果一切準備就緒,您就可以出發了。

使用 Maroto 產生 PDF

首先,我們先討論一些在使用 Maroto 產生 PDF 時發揮關鍵作用的核心概念。

由高度定義,行代表文件的水平部分。它可以包含多個列 (Col)。 New 函數初始化具有指定高度的 Row。

上校

列代表行內的垂直部分,可以包含各種元件,例如文字、圖像、二維碼或條碼。欄位在內容和大小方面都很靈活。 Col 的 New 函數可以建立具有特定大小或最大大小(如果未提供大小)的欄位。 Add 方法允許將元件新增至列。

成分

這是各種文檔元素實現的介面。文字、圖像、QR 碼和條碼等元素被視為元件。可以將它們新增至列中以建立文件內容。現在讓我們啟動一個Go專案。

初始化一個新的Go專案並安裝依賴項

首先,打開您最喜歡的程式碼編輯器,然後打開編輯器中的資料夾,您將在其中保存與專案相關的所有程式碼。

現在打開終端機窗口,找到項目位置,然後執行以下命令:

go mod init github.com/USERNAME/PROJECT_NAME
登入後複製

在上面的命令中,將 github.com 替換為您儲存檔案的網域,例如 Bitbucket 或 Gitlab。此外,將 USERNAME 替換為您的用戶名,將 PROJECT_NAME 替換為所需的項目名稱。

Running this command will initialize a Go project and will create a go.mod file in the folder. This file is used to manage the dependencies of a Go project.

After the command creates a go.mod file successfully, you are ready to install the necessary packages. The only package required to follow along with this article is Maroto.

To install Maroto as a dependency, run the following command:

go get github.com/johnfercher/maroto/v2@v2
登入後複製

At the time of writing this article, Maroto is in its v2 stage. And, the v2 provides significant performance improvements compared to v1.

All of the logic for this code will reside in the main package. To keep the article simple, everything will be in a single file. For writing code, create a new file called main.go to the root of your project folder, and paste the following code into it:

package main

import "fmt"

func main() {
 fmt.Println("Hello, World!")
}
登入後複製

Now, run the command go run main.go from the terminal, and check if it prints Hello, World! in the terminal. If it does, it means you are ready to get started.

Building the application

In this article, we’ll be building a sample PDF that replicates a ticket copy. The PDF will contain a dynamic QR code, as well as a barcode, image, and other relevant details.

The core layout of Maroto is based on the 12-unit grid system. Meaning that, the width of each page is divided into an equal amount of 12 units. Based on this, you can add a structure to your PDF.

The image shown below replicates a basic ticket of a movie show, and you’ll be creating this PDF programmatically using Go: Go long by generating PDFs in Golang with Maroto  

The ticket contains three basic sections: header, body, and footer.

The header contains a logo, name and address of an imaginary company called Showbees Ticketing.

The body contains all the necessary tickets like the name of the show, language, date, venue, number of tickets, etc.. The body also contains a QR code and a barcode just to demonstrate how these can be implemented in your PDF.

The footer is very basic and contains a single text.

Let’s start by creating a struct that defines the structure of the PDF. Copy and paste the code in your main.go file:

package main

type Company struct {
    Name         string
    Address      string
    LogoLocation string
}

type Ticket struct {
    ID                 int
    ShowName           string
    ShowTime           string
    Language           string
    ShowVenue          string
    SeatNumber         string
    Cost               float64
    Screen             string
    TicketCount        int
    ShowPosterLocation string
}

func main() {
    c := Company{
        Name:         "ShowBees Ticketing",
        Address:      "1234 Main St, City, State 12345",
        LogoLocation: "./logo.png",
    }

    t := Ticket{
        ID:                 1,
        ShowName:           "Planet of the Gophers: The War Begins",
        ShowTime:           "Sat 01/01/2022 7:00 PM",
        Language:           "English",
        ShowVenue:          "Gophedorium",
        SeatNumber:         "Platinum - A1, A2",
        Cost:               620.00,
        Screen:             "Screen 1",
        TicketCount:        2,
        ShowPosterLocation: "./poster.png",
    }
}
登入後複製

Here, we are creating a Company struct first. This represents the details of the company. The Ticket struct defines the structure for the ticket.

In the main function, we are creating a variable called c which holds the company details and another variable called t which holds the ticket details. The field name, and the variable are pretty self-explanatory.

One thing to mention here is that the logo and the poster are stored in the root of the folder. If you are following along, you can either add any two images in this place, or you can download the images of the article from this repo.

Now, let’s create the header.

Creating a header

To create a header, we’ll be creating a new function. The function will take a parameter of type Company and will return a core.Row. The header is pretty simple. Let’s check out the code first:

func getPageHeader(c Company) core.Row {
    return row.New(16).Add(
        image.NewFromFileCol(4, c.LogoLocation, props.Rect{
            Center:  false,
            Percent: 100,
        }),
        col.New(2),
        col.New(6).Add(
            text.New(c.Name, props.Text{
                Style: fontstyle.Bold,
                Size:  10,
            }),
            text.New(c.Address, props.Text{
                Top:  6,
                Size: 10,
            }),
        ),
    )
}
登入後複製

The header consists of three columns: an image column, an empty column, and a column containing text components. Let’s look at the code step-by-step.

The first line row.New(16) creates a new row component with a height of 16. This row will serve as the container for the header columns.

To render the image, you’ll need to use the image.NewFromFileCol method. This function is available in the image component.

This line image.NewFromFileCol(4, c.LogoLocation, props.Rect{...}) creates an image column with a size of 4. It loads an image from the file specified by c.LogoLocation and sets some properties for the image, such as its alignment and size.

The next line col.New(2) creates an empty column with a size of 2. This column is used to create space between the image column and the text column.

The following line col.New(6).Add(...) creates a column with a size of 6.

Inside this column, there are two text components created using the text.New function. The first text component displays the company name (c.Name) with some text properties like bold style, left alignment, and font size of 10.

The second text component displays the company address (c.Address) with different text properties, including a top margin of 6 pixels, left alignment, and font size of 10.

Finally, the image column and the text column are then added to the row using the Add method. So, a row contains all the columns, and columns can be added in a row.

Let’s now create a function called getMaroto which will be responsible for registering necessary components and returning a core.Maroto interface which wraps the basic methods of Maroto.

Using the methods from these interfaces, we’ll be generating the PDF from the main function. Let’s take a look at the function first:

func getMaroto(c Company, t Ticket) core.Maroto {
    cfg := config.NewBuilder().WithDimensions(120, 200).Build()

    mrt := maroto.New(cfg)

    err := mrt.RegisterHeader(getPageHeader(c))

    if err != nil {
        log.Println("Error registering header")
    }

    return mrt
}
登入後複製

At this point, we only have a header component. As we add more components to the PDF, this function will grow larger. The function takes two parameters: a company object and a ticket object.

The function begins by creating a new PDF configuration using config.NewBuilder(), which is customized with width and height for the PDF document through the WithDimensions method. This configuration is then used to instantiate a new Maroto PDF document (mrt) with the specified dimensions.

After setting the desired configuration options, Build() is called to finalize and return the configuration object.

If you do not prefer to set up dimensions like we have done in this article and want to use a standard paper size, Maroto provides a WithPageSize function as well where you can add a standard page size like A4, Letter, etc..

Here’s a sample:

cfg := config.NewBuilder().WithPageSize(pagesize.Letter).Build()
登入後複製

After the cfg variable is initialized, it’s time to register the header for the PDF document. This is done by calling mrt.RegisterHeader(getPageHeader(c)), where getPageHeader(c) generates a header row based on the provided Company object.

The RegisterHeader method adds the header row to the PDF and calculates its height to ensure it fits within the page's useful area. If the header's height exceeds the available space, an error is logged, and the mrt variable is returned from the function.

Update the main function as shown below:

func main() {
  // ...
    // ...

    m := getMaroto(c, t)

    document, err := m.Generate()

    filename := fmt.Sprintf("ticket-%d.pdf", t.ID)

    if err != nil {
        log.Println("Error generating PDF")
    }

    // Check if temp folder exists, if not create it
    if _, err := os.Stat("temp"); os.IsNotExist(err) {
        err = os.Mkdir("temp", 0755)
        if err != nil {
            log.Println("Error creating directory:", err)
        }
    }

    err = document.Save("temp/" + filename)
    if err != nil {
        log.Println("Unable to save file:", err)
    }
}
登入後複製

The code here is pretty straightforward. The company and ticket details are passed into the getMaroto function. The Generate method of the Maroto type is then called to actually generate the PDF document.

After generating the PDF, a filename for the PDF is created using the fmt.Sprintf function, incorporating the ticket's ID into the filename. It then checks if a "temp" directory exists using os.Stat, and if it doesn't, the directory is created with os.Mkdir with appropriate permissions. If any error occurs in this stage, the error is logged in.

Finally, the generated PDF document is saved to the temp directory using the Save method, which internally uses os.WriteFile to write the PDF bytes to a file. The permissions for the file are set to allow any operation by the user.

If you run the code using go run main.go, you’ll be able to see that a new folder called temp is created, and the folder contains a file called ticket-1.pdf. Your PDF should look like this image now: Go long by generating PDFs in Golang with Maroto

Let’s move on and create the body of the PDF. This is the longest of the functions, but now that you understand the basics of how Maroto works, it should be a breeze.

Creating a body

Create a new function called getShowDetails which accepts a ticket struct and returns an array of core.Row interface. First, copy and paste the below code into your main.go function:

func getShowDetails(t Ticket) []core.Row {
    rows := []core.Row{
        row.New(30).Add(
            image.NewFromFileCol(4, t.ShowPosterLocation, props.Rect{
                Center:  true,
                Percent: 100,
            }),
            col.New(8).Add(
                text.New(t.ShowName, props.Text{
                    Style: fontstyle.Bold,
                    Size:  10,
                }),
                text.New(t.Language, props.Text{
                    Top:   6,
                    Style: fontstyle.Normal,
                    Size:  8,
                    Color: &props.Color{Red: 95, Green: 95, Blue: 95},
                }),
                text.New(t.ShowTime, props.Text{
                    Top:   12,
                    Style: fontstyle.Bold,
                    Size:  10,
                }),
                text.New(t.ShowVenue, props.Text{
                    Top:   18,
                    Style: fontstyle.Normal,
                    Size:  8,
                    Color: &props.Color{Red: 95, Green: 95, Blue: 95},
                }),
            ),
        ),
        row.New(6),
        row.New(1).Add(
            line.NewCol(12, props.Line{
                Thickness:   0.2,
                Color:       &props.Color{Red: 200, Green: 200, Blue: 200},
                SizePercent: 100,
                Style:       linestyle.Dashed,
            }),
        ),
        row.New(3),
        row.New(16).Add(
            col.New(2).Add(
                text.New(strconv.Itoa(t.TicketCount), props.Text{
                    Style: fontstyle.Bold,
                    Size:  24,
                    Align: align.Center,
                }),
                text.New("Tickets", props.Text{
                    Top:   12,
                    Style: fontstyle.Normal,
                    Size:  8,
                    Color: &props.Color{Red: 95, Green: 95, Blue: 95},
                    Align: align.Center,
                }),
            ),
            col.New(2),
            col.New(8).Add(
                text.New(t.Screen, props.Text{
                    Size:  8,
                    Color: &props.Color{Red: 95, Green: 95, Blue: 95},
                }),
                text.New(t.SeatNumber, props.Text{
                    Top:   6,
                    Style: fontstyle.Bold,
                    Size:  14,
                }),
            ),
        ),
        row.New(3),
        row.New(1).Add(
            line.NewCol(12, props.Line{
                Thickness:   0.2,
                Color:       &props.Color{Red: 200, Green: 200, Blue: 200},
                SizePercent: 100,
                Style:       linestyle.Dashed,
            }),
        ),
        row.New(6),
        row.New(20).Add(
            code.NewQrCol(12,
                fmt.Sprintf("%v\n%v\n%v\n%v", t.ID, t.ShowName, t.ShowTime, t.ShowVenue),
                props.Rect{
                    Center:  true,
                    Percent: 100,
                },
            ),
        ),
        row.New(10).Add(
            col.New(12).Add(text.New(fmt.Sprintf("Booking ID: %v", t.ID), props.Text{
                Style: fontstyle.Normal,
                Size:  8,
                Align: align.Center,
                Top:   2,
            })),
        ),
        row.New(1).Add(
            line.NewCol(12, props.Line{
                Thickness:   0.2,
                Color:       &props.Color{Red: 200, Green: 200, Blue: 200},
                SizePercent: 100,
                Style:       linestyle.Solid,
            }),
        ),
        row.New(3),
        row.New(10).Add(
            code.NewBarCol(12, strconv.Itoa(t.ID),
                props.Barcode{
                    Center:  true,
                    Percent: 100,
                },
            ),
        ),
    }

    return rows
}
登入後複製

The code starts by initializing a new variable called rows, which consists of all the rows that will be returned from the function.

The rows slice starts by initializing a first row of height 30. In this row, an image is first added to the column which takes a third of the available area. The t.ShowPosterLocation is rendered here, with a few properties like centre alignment and size in percentage.

In the remaining 2/3rd space, a new column is added, which renders the show name, show language and the show time. Each of the text has a different size, top position, and color. For defining the colors, the props.Color struct is used, where the value of red, green, and blue is defined.

The next row is added to add a blank space in the PDF. After the blank space, a new line is added to the rows slice. A new line can be added using the line module available in the Maroto package.

The line.NewCol function takes two arguments, one being the area to be taken, and another one for defining the properties of the line. In this case, the line takes the complete width, and different properties like the thickness, color, and size are defined. For the style, a dashed line is added.

After adding the line, another empty row is added, and after the empty row, the ticket count, screen name, and seat number are added.

Similar to the other columns, different props for the text are added for enhancing the look of the document. To display the ticket count, you must have noticed that we are using the strconv package to convert the integer type to string because the text.New function takes the value as a string.

A blank space area and a line is added to the document after the details.

In the next step, a new row of size 20 is created. Inside this row, a column is added. This column contains a QR code generated from a formatted string that includes the ticket ID, show name, show time, and show venue.

The QR code is centered and occupies 100 percent of the column's width. The NewQrCol function is used to create this column by generating a QR code with the specified properties and adding it to a new column of a specified size.

Next, a new row with a height of 10 units is created, and a column is added to it. This column contains a text component displaying the booking ID with centered positioning. Following that, a row with a height of 1 unit is added, containing a single column with a line.

The line's color, thickness, and style are specified, and it spans 100 percent of the column's width. This is used as a separator between sections of the document. An empty row with a height of 3 units is then added to create some vertical spacing in the document.

Finally, a row with a height of 10 units is created, containing a column. The column contains a barcode generated from the ticket ID. The barcode is generated using the code.NewBarCol function. It is centered and occupies 100 percent of the column's width.

With the above complete, let’s move on to the footer.

Creating a footer

The footer contains just a single text. The aim is to demonstrate how a footer can be registered in your PDF:

func getPageFooter() core.Row {
    return row.New(2).Add(
        col.New(12).Add(
            text.New("Powered by ShowBees Ticketing System", props.Text{
                Style: fontstyle.Italic,
                Size:  8,
                Align: align.Center,
                Color: &props.Color{Red: 255, Green: 120, Blue: 218},
            }),
        ),
    )
}
登入後複製

The footer displays a single text in a column. The text has a size of 8 pixels, center aligned with an RGB color value.

Now, to register this footer and the getShowDetails function into the getMaroto function, update this accordingly:

func getMaroto(c Company, t Ticket) core.Maroto {
    cfg := config.NewBuilder().WithDimensions(120, 200).Build()

    // ...
    // ...

    mrt.AddRow(6)

    mrt.AddRow(4, line.NewCol(12, props.Line{
        Thickness:   0.2,
        Color:       &props.Color{Red: 200, Green: 200, Blue: 200},
        SizePercent: 100,
    }))

    mrt.AddRow(6)

    mrt.AddRows(getShowDetails(t)...)

    mrt.AddRow(8)

    err = mrt.RegisterFooter(getPageFooter())

    if err != nil {
        log.Println("Error registering footer")
    }

    return mrt
}
登入後複製

If you generate the PDF by running go run main.go, you should get a PDF that resembles the image shown earlier in the article. Here is a video demonstration of the PDF generation.

The complete code for the article is available in this GitHub repo.

What makes Maroto stand out?

Maroto uses a grid system similar to Bootstrap, making it intuitive for developers familiar with web development. This system allows for easy layout management using rows and columns, which can contain various components like text, images, QR codes, and barcodes.

Maroto makes it easy for developers to create complex PDF layouts without extensive boilerplate code, a significant advantage over more complex libraries like UniDoc and pdfcpu.

Maroto is built on top of gofpdf but offers significant performance improvements, especially in v2. This makes it suitable for high-volume PDF generation tasks.

Maroto also handles content overflow by automatically adding page breaks without manual intervention.

Finally, Maroto has a growing community and comprehensive documentation. The library is open-source and actively maintained, which ensures that it stays up-to-date with the latest features and improvements.

Conclusion

Generating PDF documents using the Maroto library in Go is a straightforward and efficient process. This powerful library provides an intuitive API that allows developers to create complex layout structures with ease.

The use of rows and columns to manage content positioning, alongside the built-in support for various elements such as text, images, QR codes, and barcodes, makes Maroto a highly versatile tool.

To explore more about the package, you can check out their documentation.


Get set up with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.

NPM:

$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
登入後複製

Script Tag:

Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
登入後複製

3.(Optional) Install plugins for deeper integrations with your stack:

  • Redux middleware
  • ngrx middleware
  • Vuex plugin

Get started now

以上是透過 Maroto 在 Golang 中產生 PDF 來進行長期操作的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!