ZetCode

Dart PDF

最后修改日期:2024 年 1 月 28 日

在本文中,我们将展示如何在 Dart 语言中创建 PDF 文档。

便携式文档格式 (PDF) 是 Adobe 创建的一种多功能文件格式,它为人们提供了一种简单可靠的方式来展示和交换文档。

$ dart pub add pdf

要在 Dart 中生成 PDF 文件,我们使用 pdf 包。

Dart PDF 简单示例

以下是一个简单的 Dart 示例,用于生成 PDF 文档。

main.dart
import 'dart:io';

import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;

void main() async {
  final pdf = pw.Document();

  pdf.addPage(pw.Page(
      pageFormat: PdfPageFormat.a4,
      build: (pw.Context context) {
        return pw.Center(
          child: pw.Text("An old falcon"),
        );
      }));

  final file = File('simple.pdf');
  await file.writeAsBytes(await pdf.save());
}

该程序创建一个新的 PDF 文件并将其写入磁盘。它包含居中文本。

import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;

我们导入所需的包。

final pdf = pw.Document();

创建一个新的空文档。

pdf.addPage(pw.Page(
    pageFormat: PdfPageFormat.a4,
    build: (pw.Context context) {
      return pw.Center(
        child: pw.Text("An old falcon"),
      );
    }));

使用 addPage 添加一页。我们提供页面格式和内容。内容由一个 Text 小部件组成,该小部件使用 Center 在页面上居中。

final file = File('simple.pdf');
await file.writeAsBytes(await pdf.save());

文档被写入文件。

Dart PDF 页眉

使用 Header 创建文档页眉。

main.dart
import 'dart:io';

import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;


void main() async {
  final txt = '''
The Battle of Thermopylae was fought between an alliance of Greek city-states, 
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the 
course of three days, during the second Persian invasion of Greece.
''';

  final pdf = pw.Document();

  pdf.addPage(pw.Page(
    pageFormat: PdfPageFormat.a4,
    build: (pw.Context context) {
      return pw.Column(children: [
        pw.Header(level: 0, text: 'The Battle of Thermopylae'),
        pw.Paragraph(text: txt)
      ]);
    },
  ));

  final file = File('header.pdf');
  await file.writeAsBytes(await pdf.save());
}

文档包含页眉和一段文本。

pdf.addPage(pw.Page(
  pageFormat: PdfPageFormat.a4,
  build: (pw.Context context) {
    return pw.Column(children: [
      pw.Header(level: 0, text: 'The Battle of Thermopylae'),
      pw.Paragraph(text: txt)
    ]);
  },
));

页眉和段落被添加到列小部件中。我们为页眉指定级别和文本,为段落指定文本。

Dart PDF 列表项

使用 Bullet 小部件创建列表项。

main.dart
import 'dart:io';

import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;

void main() async {
  final pdf = pw.Document();

  pdf.addPage(pw.Page(
      pageFormat: PdfPageFormat.a4,
      build: (pw.Context context) {
        return pw.Column(children: [
          pw.Header(level: 1, text: 'Programming languages'),
          pw.Bullet(text: 'Dart'),
          pw.Bullet(text: 'F#'),
          pw.Bullet(text: 'Clojure'),
          pw.Bullet(text: 'Go'),
          pw.Bullet(text: 'Groovy'),
          pw.Bullet(text: 'Raku'),
          pw.Bullet(text: 'Python'),
        ]);
      }));

  final file = File('bullets.pdf');
  await file.writeAsBytes(await pdf.save());
}

我们有一个页眉和一系列编程语言。每种语言都显示为一个列表项。

Dart PDF 矩形

使用 Rectangle 创建一个矩形。

main.dart
import 'dart:io';

import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;

void main() async {
  final pdf = pw.Document();

  pdf.addPage(pw.Page(
      pageFormat: PdfPageFormat.a4,
      build: (pw.Context context) {
        return pw.Row(children: [
          pw.SizedBox(
            width: 80,
            height: 80,
            child: pw.Rectangle(fillColor: PdfColors.grey),
          ),
          pw.Spacer(flex: 1),
          pw.SizedBox(
            width: 80,
            height: 80,
            child: pw.Rectangle(fillColor: PdfColors.amber300),
          ),
          pw.Spacer(flex: 1),
          pw.SizedBox(
            width: 80,
            height: 80,
            child: pw.Rectangle(fillColor: PdfColors.green500),
          ),
          pw.Spacer(flex: 1),
          pw.SizedBox(
            width: 80,
            height: 80,
            child: pw.Rectangle(fillColor: PdfColors.blue600),
          ),
        ]);
      })); // Pa

  final file = File('rectangles.pdf');
  await file.writeAsBytes(await pdf.save());
}

我们创建了一系列 SizedBox 小部件。在框中,我们放置了矩形小部件。

pw.SizedBox(
  width: 80,
  height: 80,
  child: pw.Rectangle(fillColor: PdfColors.grey),
),

对于每个矩形,我们都定义了填充颜色。

Dart PDF 页脚

在下一个示例中,我们将页脚添加到我们的页面。

main.dart
import 'dart:io';

import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;

void main() async {
  final pdf = pw.Document(pageMode: PdfPageMode.outlines);

  final title = pw.LoremText();

  pdf.addPage(
    pw.MultiPage(
      footer: _buildFooter,
      build: (context) => [
        pw.Header(level: 1, text: title.sentence(1)),
        pw.SizedBox(height: 20),
        pw.Lorem(length: 50),
        pw.SizedBox(height: 20),
        pw.Lorem(length: 70),
        pw.SizedBox(height: 20),
        pw.Lorem(length: 150),
        pw.SizedBox(height: 20),
        pw.Lorem(length: 250),
        pw.SizedBox(height: 20),
        pw.Lorem(length: 350),
      ],
    ),
  );

  final file = File('footer.pdf');
  await file.writeAsBytes(await pdf.save());
}

pw.Widget _buildFooter(pw.Context context) {
  return pw.Row(
    mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
    crossAxisAlignment: pw.CrossAxisAlignment.end,
    children: [
      pw.Text(
        'Page ${context.pageNumber}/${context.pagesCount}',
        style: const pw.TextStyle(
          fontSize: 12,
          color: PdfColors.blue700,
        ),
      )
    ],
  );
}

页脚由一个包含当前页码和总页码的行组成。

final title = pw.LoremText();

lorem 方法为文档提供了一些填充文本。这是桌面排版在测试时的常见做法。

pdf.addPage(
  pw.MultiPage(
    footer: _buildFooter,
    build: (context) => [
      pw.Header(level: 1, text: title.sentence(1)),
      pw.SizedBox(height: 20),
      pw.Lorem(length: 50),
      pw.SizedBox(height: 20),
      pw.Lorem(length: 70),
      pw.SizedBox(height: 20),
      pw.Lorem(length: 150),
      pw.SizedBox(height: 20),
      pw.Lorem(length: 250),
      pw.SizedBox(height: 20),
      pw.Lorem(length: 350),
    ],
  ),
);

我们有一个多页文档。页脚的创建委托给 _buildFooter 函数。

pw.SizedBox(height: 20),
pw.Lorem(length: 50),

我们使用 SizedBox 添加一些空白。Lorem 方法添加 50 个拉丁文本字符。

pw.Widget _buildFooter(pw.Context context) {
  return pw.Row(
    mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
    crossAxisAlignment: pw.CrossAxisAlignment.end,
    children: [
      pw.Text(
        'Page ${context.pageNumber}/${context.pagesCount}',
        style: const pw.TextStyle(
          fontSize: 12,
          color: PdfColors.blue700,
        ),
      )
    ],
  );
}

这构建了页脚。它是一个包含 Text 小部件的行。它包含当前页码和所有页面的总数。此外,我们还对文本进行了样式设置:我们定义了它的大小和颜色。

Dart PDF 表格

使用 Table 小部件创建表格。这是一个具有许多选项的复杂小部件。

main.dart
import 'dart:io';

import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;

class User {
  late String name;
  late String occupation;

  User(String name, String occupation) {
    this.name = name;
    this.occupation = occupation;
  }

  String getIndex(int idx) {
    switch (idx) {
      case 0:
        return name;
      case 1:
        return occupation;
    }
    return '';
  }
}

final users = <User>[
  User("John Doe", "gardener"),
  User("Roder Roe", "driver"),
  User("Joe Smith", "teacher"),
];

void main() async {
  final pdf = pw.Document();

  pdf.addPage(pw.Page(
      pageFormat: PdfPageFormat.a4,
      build: (pw.Context ctx) {
        return genTable(ctx);
      }));

  final file = File('table.pdf');
  await file.writeAsBytes(await pdf.save());
}

pw.Widget genTable(pw.Context ctx) {
  const headers = [
    'Name',
    'Occupation',
  ];

  return pw.Table.fromTextArray(
    border: null,
    cellAlignment: pw.Alignment.centerLeft,
    headerDecoration: pw.BoxDecoration(
      borderRadius: const pw.BorderRadius.all(pw.Radius.circular(1)),
      color: PdfColors.blue500,
    ),
    headerHeight: 25,
    cellHeight: 40,
    cellAlignments: {
      0: pw.Alignment.centerLeft,
      1: pw.Alignment.centerLeft,
    },
    headerStyle: pw.TextStyle(
      color: PdfColors.white,
      fontSize: 10,
      fontWeight: pw.FontWeight.bold,
    ),
    cellStyle: const pw.TextStyle(
      color: PdfColors.blueGrey800,
      fontSize: 10,
    ),
    rowDecoration: pw.BoxDecoration(
      border: pw.Border(
        bottom: pw.BorderSide(
          color: PdfColors.blueGrey900,
          width: .5,
        ),
      ),
    ),
    headers: List<String>.generate(
      headers.length,
      (col) => headers[col],
    ),
    data: List<List<String>>.generate(
      users.length,
      (row) => List<String>.generate(
        headers.length,
        (col) => users[row].getIndex(col),
      ),
    ),
  );
}

我们在表格中显示用户。

class User {
  late String name;
  late String occupation;

  User(String name, String occupation) {
    this.name = name;
    this.occupation = occupation;
  }

  String getIndex(int idx) {
    switch (idx) {
      case 0:
        return name;
      case 1:
        return occupation;
    }
    return '';
  }
}

表格显示用户。getIndex 方法由表格的数据函数调用。它返回相应的字段。

final users = <User>[
  User("John Doe", "gardener"),
  User("Roder Roe", "driver"),
  User("Joe Smith", "teacher"),
];

表格的数据源是用户列表。

headerHeight: 25,
cellHeight: 40,
cellAlignments: {
  0: pw.Alignment.centerLeft,
  1: pw.Alignment.centerLeft,
},

我们有定义表格外观的选项。在这里,我们定义了页眉和单元格的高度以及单元格对齐方式。

rowDecoration: pw.BoxDecoration(
  border: pw.Border(
    bottom: pw.BorderSide(
      color: PdfColors.blueGrey900,
      width: .5,
    ),
  ),
),

我们定义了表格底边框。我们设置了边框颜色和大小。

headers: List<String>.generate(
  headers.length,
  (col) => headers[col],
),

我们为表格设置了页眉。

data: List<List<String>>.generate(
  users.length,
  (row) => List<String>.generate(
    headers.length,
    (col) => users[row].getIndex(col),
  ),
),

表格已填充数据。

来源

Dart pdf 库 - 文档

在本文中,我们展示了如何在 Dart 中生成 PDF 文档。

作者

我叫 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在教授编程方面拥有十多年的经验。

列出 所有 Dart 教程