16

I am working on a iOS/Android/Web app where I am generating a PDF based on user input using the pdf plugin here: https://pub.dev/packages/pdf. On iOS and Android, I have access to the file system, so I am able to save the generated pdf first, and then open it with any pdf display plugin.

However, for Flutter web, as far as I know, there is no access to any type of file system. All the pdf render plugins that I've seen assume that the pdf file is either stored in a file, or on the web. I looked at:

  • flutter_full_pdf_viewer: ^1.0.6
  • flutter_pdf_viewer: ^0.6.1
  • pdf_render: ^0.57.2

How can I display this app-generated pdf on flutter web? Is there a way to spoof the Uint8List data as a file, or perhaps another way to approach this? (The only thing I can think of to solve this is by uploading the file to the web after generating it in order to display it, but it seems like overkill.)

Kirill Matrosov
  • 5,564
  • 4
  • 28
  • 39
William Terrill
  • 3,484
  • 4
  • 31
  • 41

3 Answers3

24

We can do it without additional plug-ins, because the web browser itself can display (or download) pdf document.

updated for pdf 2.0.0 and newer

Since method save now returns Future, button handlers become asynchronous (of course you can use FurureBuilder or something else).

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

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'),
            ),
          ],
        ),
      ),
    );
  }
}

original answer

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,
        ),
      ),
    );
  }
}
Spatz
  • 18,640
  • 7
  • 62
  • 66
  • 1
    Thank you so much for writing this code. I've been trying to overcome this for a while, and it never occurred to me to use html. Bravo! – William Terrill Apr 24 '20 at 05:37
  • 1
    I'm not familiar with HTML really, is there a way I can set the name of the PDF file when opening the PDF in browser instead of downloading? Right now it is getting set to some random string like "006f36b8-08b5-4530-9e7d-30a7a67e6d1e". Also, thank you very much for this solution! It works great. – Matt Lampe Jun 08 '20 at 16:23
  • For those of you receiving "Failed to load PDF document" make sure you are passing the correct blob. e.g. final url = html.Url.createObjectUrlFromBlob(html.Blob([pdfData.buffer])); – grantespo Apr 19 '21 at 01:57
  • 1
    final bytes = await pdf.save(); you need to add await pdf save to avoid corrupted pdfs and not loadings etc – iluvatar_GR May 13 '21 at 10:28
  • Thanks @iluvatar_GR, I've updated answer. – Spatz May 13 '21 at 11:58
  • Any alternative to `universal_html`, getting hundreds of version solving failed messages, can't install any other package without deleting it first. – gegobyte Jun 23 '21 at 18:18
  • On the example for Download is there a way to give the PDF a name? So that if they choose to share the file name doesn't show as Unknown.pdf ? – Chamanhm Mar 15 '23 at 06:34
  • 1
    @Chamanhm look at [download](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#download) attribute. It should work on most recent browsers (with some implementation notes). – Spatz Mar 15 '23 at 08:41
  • Amazing Man, Bravo, Download functionality works properly, but in case of pdf open in a new tab like ( Ctrl + p ) this is not working, and download a file. if i add a .pdf at the end of the downloaded file it is now a pdf. Any way to open this blob or bytes format PDF in flutter-web like ( Ctrl + p ). – sahil prajapati Jun 07 '23 at 05:17
6

Updated on Feb 12th, 2021:

After more rounds of googling and digging into documentation, I have got it to work. Here are the main points:

1- There is no need to insert any javascript code into <app_directory>/web/index.html file as I had pointed before. Sorry! I am learning flutter/dart...

2- I had to create a Future<html.Blob> method (please have a look at myGetBlobPdfContent() async {...} ) to deal with html/pdf content, to save (await pdf.save()) and to return a Blob value. I'm not the right person to say it was related with a "non complete future" was throwing an null point on pdf stuff...

3- Also, Those RaisedButton's onPressed methods were changed to async, to allow to get the pdf content as a Blob value (createObjectUrlFromBlob( await myGetBlobPdfContent());).

4- Finally, I was having warning messages: Helvetica has no Unicode support see https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management, so that I have installed custom font like this cookbook instructions, in this example I have set up Arial ttf (data = await rootBundle.load("fonts/arial.ttf");) and those warning messages went away.

5- Here is my pubspec.yaml's snippet:

...
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- And an minimal working example (full main.dart file):

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- At the end, the PDF generation (and download) is working as expected:

enter image description here

Hope this helps.

Initial post (Only to keep the information log)

@Spatz's answer might solve any pdf issue for web platform. I don't know why it is not working as expected, at least for my setup (see below).

I mean the webpage tries to open the pdf and throws an error: Failed to load PDF document.

enter image description here

Also, the example downloads a pdf file (some_name.pdf) successfully, but it fails when I try to open the pdf file (error: file corrupted).

I have seen that this post talks about insert javascript code into <app_directory>/web/index.html file. I have insert those lines and no success as well.

The source code I am using is a one main.dart file:

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,
        ),
      ),
    );
  }
}

My setup is:

  • Flutter (Channel beta, 1.26.0-17.3.pre, on Microsoft Windows [Version 10.0.19042.746], locale en-150)
  • Android tool chain develop for Android devices (Android SDK version 30.0.3)
  • Chrome - develop for the web
  • Android Studio (version 4.1.0)

Thank you :)

Alex Correia
  • 658
  • 6
  • 14
1

In order to resolve "Failed to open PDF file"... which is due to invalid format of the file... add the following two lines to the existing code:

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

The complete code will be as below:

  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);
Haider Ali
  • 21
  • 4