Flutter web - 显示在应用程序中生成的 pdf 文件。 (Uint8List 格式)

Flutter web - display pdf file generated in application. (Uint8List format)

我正在开发 iOS/Android/Web 应用程序,我在此处使用 pdf 插件根据用户输入生成 PDF:https://pub.dev/packages/pdf。在 iOS 和 Android 上,我可以访问文件系统,所以我可以先保存生成的 pdf,然后使用任何 pdf 显示插件打开它。

但是,对于Flutter web,据我所知,是无法访问任何类型的文件系统的。我见过的所有 pdf 渲染插件都假定 pdf 文件要么存储在文件中,要么存储在网络上。 我看了:

如何在 flutter web 上显示这个应用程序生成的 pdf?有没有办法将 Uint8List 数据欺骗为文件,或者可能是另一种方法来解决这个问题? (我唯一能想到的解决方法是在生成文件后将文件上传到网络以显示它,但这似乎有点过分了。)

我们不需要额外的插件就可以做到,因为网络浏览器本身可以显示(或下载)pdf文档。

更新为 pdf 2.0.0 和更新版本

自方法 save 现在 returns 未来,按钮处理程序变为异步(当然你可以使用 FurureBuilder 或其他东西)。

import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:universal_html/html.dart' as html;

class PdfLabPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final pdf = pw.Document();
    pdf.addPage(pw.Page(
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return pw.Center(
            child: pw.Text('Hello World'),
          );
        }));

    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () async {
                final bytes = await pdf.save();
                final blob = html.Blob([bytes], 'application/pdf');
                final url = html.Url.createObjectUrlFromBlob(blob);
                html.window.open(url, '_blank');
                html.Url.revokeObjectUrl(url);
              },
              child: Text('Open'),
            ),
            ElevatedButton(
              onPressed: () async {
                final bytes = await pdf.save();
                final blob = html.Blob([bytes], 'application/pdf');
                final url = html.Url.createObjectUrlFromBlob(blob);
                final anchor = html.AnchorElement()
                  ..href = url
                  ..style.display = 'none'
                  ..download = 'some_name.pdf';
                html.document.body?.children.add(anchor);
                anchor.click();
                html.document.body?.children.remove(anchor);
                html.Url.revokeObjectUrl(url);
              },
              child: Text('Download'),
            ),
          ],
        ),
      ),
    );
  }
}

原回答

import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:universal_html/html.dart' as html;

class PdfDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final pdf = pw.Document();
    pdf.addPage(pw.Page(
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return pw.Center(
            child: pw.Text("Hello World"),
          );
        }));
    final bytes = pdf.save();
    final blob = html.Blob([bytes], 'application/pdf');

    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("Open"),
              onPressed: () {
                final url = html.Url.createObjectUrlFromBlob(blob);
                html.window.open(url, "_blank");
                html.Url.revokeObjectUrl(url);
              },
            ),
            RaisedButton(
              child: Text("Download"),
              onPressed: () {
                final url = html.Url.createObjectUrlFromBlob(blob);
                final anchor =
                    html.document.createElement('a') as html.AnchorElement
                      ..href = url
                      ..style.display = 'none'
                      ..download = 'some_name.pdf';
                html.document.body.children.add(anchor);
                anchor.click();
                html.document.body.children.remove(anchor);
                html.Url.revokeObjectUrl(url);
              },
            ),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
      ),
    );
  }
}

2021 年 2 月 12 日更新:

经过多轮谷歌搜索和深入研究文档后,我已经开始使用它了。以下是要点:

1- 无需像我之前指出的那样将任何 javascript 代码插入 <app_directory>/web/index.html 文件。对不起!我正在学习flutter/dart...

2- 我必须创建一个 Future<html.Blob> 方法(请查看 myGetBlobPdfContent() async {...} )来处理 html/pdf 内容,保存 (await pdf.save()) 和到 return 一个 Blob 值。我不是说这与“不完整的未来”相关的人是在 pdf 内容上抛出一个空点...

3- 此外,那些 RaisedButton 的 onPressed 方法已更改为异步,以允许将 pdf 内容作为 Blob 值获取 (createObjectUrlFromBlob( await myGetBlobPdfContent());)。

4- 最后,我收到警告消息:Helvetica has no Unicode support see https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management,所以我安装了自定义字体,如 this cookbook instructions,在这个例子中我设置了 Arial ttf (data = await rootBundle.load("fonts/arial.ttf"); ) 并且那些警告消息消失了。

5- 这是我的 pubspec.yaml 的片段:

...
dependencies:
  flutter:
    sdk: flutter
  pdf: ^2.1.0
  universal_html: ^1.2.4

...

fonts:
  - family: Arial
    fonts:
      - asset: fonts/arial.ttf
      - asset: fonts/arialbd.ttf
        weight: 700
      - asset: fonts/ariali.ttf
        style: italic
  ...

6- 以及一个最小的工作示例(完整 main.dart 文件):

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:universal_html/html.dart' as html;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: PdfDemo(),
    );
  }
}

class PdfDemo extends StatelessWidget {
  Future<html.Blob> myGetBlobPdfContent() async {
    var data = await rootBundle.load("fonts/arial.ttf");
    var myFont = pw.Font.ttf(data);
    var myStyle = pw.TextStyle(font: myFont, fontSize: 36.0);

    final pdf = pw.Document();
    pdf.addPage(pw.Page(
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return pw.Center(
            child: pw.Text(
              "Hello World",
              style: myStyle,
            ),
            // child: pw.Text("Hello World", style: myStyle),
          );
        }));
    final bytes = await pdf.save();
    html.Blob blob = html.Blob([bytes], 'application/pdf');
    return blob;
  }

  @override
  Widget build(BuildContext context) {
    myGetBlobPdfContent();
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("Open"),
              onPressed: () async {
                final url = html.Url.createObjectUrlFromBlob(
                    await myGetBlobPdfContent());
                html.window.open(url, "_blank");
                html.Url.revokeObjectUrl(url);
              },
            ),
            RaisedButton(
              child: Text("Download"),
              onPressed: () async {
                final url = html.Url.createObjectUrlFromBlob(
                    await myGetBlobPdfContent());
                final anchor =
                    html.document.createElement('a') as html.AnchorElement
                      ..href = url
                      ..style.display = 'none'
                      ..download = 'some_name.pdf';
                html.document.body.children.add(anchor);
                anchor.click();
                html.document.body.children.remove(anchor);
                html.Url.revokeObjectUrl(url);
              },
            ),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
      ),
    );
  }
}

7- 最后,PDF 生成(和下载)按预期工作:

希望这对您有所帮助。

初始post(只保留信息日志)

@Spatz 的回答可能会解决网络平台的任何 pdf 问题。我不知道为什么它没有按预期工作,至少对于我的设置(见下文)。

我的意思是网页试图打开 pdf 并抛出错误:Failed to load PDF document.

此外,该示例成功下载了一个 pdf 文件 (some_name.pdf),但是当我尝试打开 pdf 文件时却失败了 (error: file corrupted)。

我看到 谈到将 javascript 代码插入 /web/index.html 文件。我已经插入了这些行,但也没有成功。

我使用的源代码是一个main.dart文件:

import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:universal_html/html.dart' as html;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: PdfDemo(),
    );
  }
}

class PdfDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final pdf = pw.Document();
    pdf.addPage(pw.Page(
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return pw.Center(
            child: pw.Text("Hello World"),
          );
        }));
    final bytes = pdf.save();
    final blob = html.Blob([bytes], 'application/pdf');

    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("Open"),
              onPressed: () {
                final url = html.Url.createObjectUrlFromBlob(blob);
                html.window.open(url, "_blank");
                html.Url.revokeObjectUrl(url);
              },
            ),
            RaisedButton(
              child: Text("Download"),
              onPressed: () {
                final url = html.Url.createObjectUrlFromBlob(blob);
                final anchor =
                    html.document.createElement('a') as html.AnchorElement
                      ..href = url
                      ..style.display = 'none'
                      ..download = 'some_name.pdf';
                html.document.body.children.add(anchor);
                anchor.click();
                html.document.body.children.remove(anchor);
                html.Url.revokeObjectUrl(url);
              },
            ),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
      ),
    );
  }
}

我的设置是:

  • Flutter(频道测试版,1.26.0-17。3.pre,在 Microsoft Windows [版本 10.0.19042.746], 语言环境 en-150)
  • Android 为 Android 设备开发的工具链(Android SDK 版本 30.0.3)
  • Chrome - 为网络开发
  • Android Studio(版本 4.1.0)

谢谢:)

为了解决“无法打开 PDF 文件”...这是由于文件格式无效...将以下两行添加到现有代码中:

 var  data = await pdf.save();
 Uint8List bytes = Uint8List.fromList(data);

完整代码如下:

  var  data = await pdf.save();
    Uint8List bytes = Uint8List.fromList(data);
    final blob = html.Blob([bytes], 'application/pdf');
    final url = html.Url.createObjectUrlFromBlob(blob);
    final anchor =
    html.document.createElement('a') as html.AnchorElement
      ..href = url
      ..style.display = 'none'
      ..download = 'some_name.pdf';

    html.document.body.children.add(anchor);
    anchor.click();
    html.document.body.children.remove(anchor);
    html.Url.revokeObjectUrl(url);

我在上面的代码中遇到了问题:

            final url = html.Url.createObjectUrlFromBlob(
                await myGetBlobPdfContent());
            html.window.open(url, "_blank");
            html.Url.revokeObjectUrl(url);

只能在本地主机上工作,但不能在网络上工作HOSTING.PLEASE帮助。