Flutter Quill to PDF

This package allow us create PDF's using deltas from Quill.

Some options that can be configured:

  • DeltaAttributesOptions (this attributes will be applied to whole delta)
  • We can use custom fonts. Using onRequest functions in PDFConverter we can detect the font family detected, and use a custom implementation to return a Font valid to pdf package Just works automatically with the default library implementation
  • CustomConverter, which helps you create custom PDF widgets using custom regular expressions.
  • Optional front matter and back matter
  • Page format using PDFPageFormat class
  • CustomPDFWidget functions in PDFConverterthat let us customize the detected style, and create a custom pdf widget implementation
  • ThemeData optional theme data that let us changes the theme for to pdf document
  • Set custom rules from html2md to customize your own markdown style detection (It could have conflicts if don't customize the CustomPDFWidget functions to detect your custom markdown style implementation)
  • Set a custom ConverterOption to PDFConverter to customize your own html rendering implementation (It could have conflicts if you don't also make your own CustomPDFWidget functions, to detect your new html style. And you should also have to change the default rules of the package to make correct detect of this custom implemenation)

By default, the delta is processed by a local implementation that uses DeltaAttributesOptions to apply custom attributes (if it is not null), making it easier to add an attribute to the entire delta. If you want to create your own implementation or simply use a default delta, use PDFConverter(...params).createDocument(shouldProcessDeltas: false).

Tap to show/hide screenshots
Delta in editor Delta converted in PDF

Add dependencies

dependencies:
  flutter_quill_to_pdf: ^1.2.2

Import package

import 'package:flutter_quill_to_pdf/flutter_quill_to_pdf.dart':

Personalize the settings of the page (height, width and margins)

We can use two types differents constructors of the same PDFPageFormat class

The common, with all set params:
final PDFPageFormat pageFormat = PDFPageFormat(
   width: ..., //max width of the page
   height: ..., //max height of the page,
   marginTop: ...,
   marginBottom: ...,
   marginLeft: ...,
   marginRight: ...,
);
The factory to marginize all PDFPageFormat
final PDFPageFormat pageFormat = PDFPageFormat.all(
   width: ..., //max width of the page
   height: ..., //max height of the page,
   margin: ..., //will set the property to the others margins
);

Using PDFConverter to create finally our document

PDFConverter pdfConverter = PDFConverter(
    backMatterDelta: null,
    frontMatterDelta: null,
    customConverters: [],
    document: QuillController.basic().document.toDelta(),
    fallbacks: [...your global fonts],
    onRequestBoldFont: (String fontFamily) async {
       ...your local font implementation
    },
    onRequestBoldItalicFont: (String fontFamily) async {
       ...your local font implementation
    },
    onRequestFallbackFont: (String fontFamily) async {
       ...your local font implementation
    },
    onRequestItalicFont: (String fontFamily) async {
       ...your local font implementation
    },
    onRequestFont: (String fontFamily) async {
       ...your local font implementation
    },
    params: pageFormat,
);

To create it, we have two options :

createDocument returns the PDF document associated

final pw.Document? document = await pdfConverter.createDocument();

createDocumentFile makes the same of the before one, write in the selected file path

await pdfConverter.createDocumentFile(path: filepath, ...other optional params);

More information about other features

If you want to get the html from delta, you can use convertDeltaToHtml function

//it looks like
String convertDeltaToHtml(Delta delta,
    [ConverterOptions? options, String Function(DeltaInsertOp customOp, DeltaInsertOp? contextOp)? customRenderCallback]) {
  final QuillDeltaToHtmlConverter converterDeltaToHTML = QuillDeltaToHtmlConverter(
    delta.toJson(),
    options ?? ConverterOptions.forEmail(), //If you want to use local converter options use "HTMLConverterOptions.options()"
  );
  converterDeltaToHTML.renderCustomWith = customRenderCallback; // use this callback if you want or need render a custom attribute or block
  return converterDeltaToHTML.convert();
}

If you want to get the markdown, you need to make some steps

  1. Use convertDeltaToHtml function to get html from delta

  2. Use html string, and pass as param in convertHtmlToMarkdown

  3. Pass your custom rules implementation, or just pass the default ones from library, using MarkdownRules class (Optional)

//it looks like
//If you don't pass any new rule, the converter will use the default ones from html2md
String convertHtmlToMarkdown(String htmlText, List<hm2.Rule>? rules, List<String> ignoreRules,
    {bool removeLeadingWhitespaces = false, bool escape = true}) {
  if (!ignoreRules.contains('underline')) ignoreRules.add('underline');
  return hm2.convert(
    styleOptions: <String, String>{'emDelimiter': '*'},
    htmlText,
    escape: escape,
    rules: rules,
    removeLeadingWhitespaces: removeLeadingWhitespaces,
    ignore: ignoreRules,
  );
}

Supported

  • Font family
  • Size
  • Bold
  • Italic
  • Strikethrough
  • Underline
  • Link
  • Color
  • Background Color
  • Line-height (custom attribute used from this package)
  • Code block
  • Blockquote
  • Align
  • Embed image
  • Header
  • List

No supported

  • Indent (working on it)
  • Superscript/Subscript (Working on it)
  • Embed formula (Not planned)
  • Embed video (Not planned)

Custom rendering (HTML and Markdown)

You could configure the delta to html options to create your own implementation (optional)

This is a fragment from: vsc_quill_delta_to_html description (If you want to know more about these configs, and custom attributes rendering, visit his github)

We can configure a custom ConverterOptions using the param convertOptions from PDFConverter()

QuillDeltaToHtmlConverter accepts a few configuration (ConverterOptions, OpConverterOptions, and OpAttributeSanitizerOptions) options as shown below:

Option Type Default Description
converterOptions.paragraphTag string 'p' Custom tag to wrap inline html elements
converterOptions.encodeHtml boolean true If true, <, >, /, ', ", & characters in content will be encoded.
converterOptions.classPrefix string 'ql' A css class name to prefix class generating styles such as size, font, etc.
converterOptions.inlineStylesFlag boolean false If true, use inline styles instead of classes.
converterOptions.inlineStyles InlineStyles null If non-null, use inline styles instead of classes. See Rendering Inline Styles section below for usage.
multiLineBlockquote boolean true Instead of rendering multiple blockquote elements for quotes that are consecutive and have same styles(align, indent, and direction), it renders them into only one
multiLineHeader boolean true Same deal as multiLineBlockquote for headers
multiLineCodeblock boolean true Same deal as multiLineBlockquote for code-blocks
multiLineParagraph boolean true Set to false to generate a new paragraph tag after each enter press (new line)
multiLineCustomBlock boolean true Same deal as multiLineBlockquote for custom blocks.
bulletListTag string 'ul' Tag for unordered bullet lists.
orderedListTag string 'ol' Tag for ordered/numbered lists.
converterOptions.linkRel string none generated Specifies a value to put on the rel attr on all links. This can be overridden by an individual link op by specifying the rel attribute in the respective op's attributes
converterOptions.linkTarget string '_blank' Specifies target for all links; use '' (empty string) to not generate target attribute. This can be overridden by an individual link op by specifiying the target with a value in the respective op's attributes.
converterOptions.allowBackgroundClasses boolean false If true, css classes will be added for background attr
sanitizerOptions.urlSanitizer String? Function(String url) null A function that is called once per url in the ops (image, video, link) for you to do custom sanitization. If your function returns a string, it is assumed that you sanitized the url and no further sanitization will be done by the library; when anything other than a string is returned (e.g. undefined), it is assumed that no sanitization has been done and the library's own function will be used to clean up the url
sanitizerOptions.allow8DigitHexColors boolean false If true, hex colors in #AARRGGBB format are allowed in the ops
converterOptions.customTag String? Function(String format, DeltaInsertOp op) null Callback allows to provide custom html tag for some format
converterOptions.customTagAttributes Map<String, String>? Function(DeltaInsertOp op) null Allows custom html tag attributes for the given op
converterOptions.customCssClasses List<String>? Function(DeltaInsertOp op) null Allows custom CSS classes for the given op
converterOptions.customCssStyles List<String>? Function(DeltaInsertOp op) null Allows custom CSS styles attributes for the given op

You also could configure custom markdown rules to accept and format correctly your custom HTML implementation (optional)

You can set custom rules to PDFConverter using

//By default is null, and it will throws error if rules are empty
PDFConverter(..., customRules: [...your custom rules]);

This is a fragment from html2md package documentation

Custom Rules

Want to customize element converting? Write your rules!

Rule fields explaination

final String name; // unique rule name
final List<String>? filters; // simple element name filters, e.g. ['aside']
final FilterFn? filterFn; // function for building complex element filter logic
final Replacement? replacement; // function for doing the replacing
final Append? append; // function for appending content

Rule example - Convert the onebox section of discourse post to a link

<aside class="onebox">
  <header class="source">
    <img
      src="https://discoursesite/uploads/default/original/1X/test.png"
      class="site-icon"
      width="32"
      height="32"
    />
    <a
      href="https://events.google.com/io/program/content?4=topic_flutter&amp;lng=zh-CN"
      target="_blank"
      rel="noopener"
      >Google I/O 2021</a
    >
  </header>
</aside>
Rule(
  'discourse-onebox',
  filterFn: (node) {
    // Find aside with onebox class
    if (node.nodeName == 'aside' &&
        node.className.contains('onebox')) {
        return true;
    }
    return false;
  },
  replacement: (content, node) {
    // find the first a element under header
    var header = node.firstChild;
    var link = header!
        .childNodes()
        .firstWhere((element) => element.nodeName == 'a');
    var href = link.getAttribute('href');
    if (href != null && href.isNotEmpty) {
      return '[$href]($href)'; // build the link
    }
    return '';
  },
)

You can contribute reporting issues or requesting to add new features in: https://github.com/CatHood0/flutter_quill_to_pdf

Libraries

converter/configurator/abstract_converter
converter/configurator/converter_option/custom_converter
converter/configurator/converter_option/pdf_page_format
converter/configurator/pdf/attribute_functions
converter/configurator/pdf/document_functions
converter/configurator/pdf/pdf_configurator
converter/delta_processor/delta_attributes_options
converter/delta_processor/delta_processor
converter/delta_processor/search_attr_in_delta
converter/pdf_converter
converter/service/pdf_service
core/constant/constants
core/extensions/color_extension
core/extensions/delta_extension_utils
core/extensions/list_extension
core/extensions/map_extension
core/extensions/md_extension
core/extensions/pdf_extension
core/extensions/string_extension
core/options/html_converter_options
flutter_quill_to_pdf
packages/html2md/lib/html2md
Convert html to markdown in Dart.
packages/html2md/lib/src/converter
packages/html2md/lib/src/node
packages/html2md/lib/src/options
packages/html2md/lib/src/rules
packages/html2md/lib/src/utils
packages/vsc_quill_delta_to_html/src/delta_insert_op
packages/vsc_quill_delta_to_html/src/funcs_html
packages/vsc_quill_delta_to_html/src/grouper/group_types
packages/vsc_quill_delta_to_html/src/grouper/grouper
packages/vsc_quill_delta_to_html/src/grouper/list_nester
packages/vsc_quill_delta_to_html/src/grouper/table_grouper
packages/vsc_quill_delta_to_html/src/helpers/array
packages/vsc_quill_delta_to_html/src/helpers/js
packages/vsc_quill_delta_to_html/src/helpers/string
packages/vsc_quill_delta_to_html/src/helpers/url
packages/vsc_quill_delta_to_html/src/insert_data
packages/vsc_quill_delta_to_html/src/insert_op_denormalizer
packages/vsc_quill_delta_to_html/src/insert_ops_converter
packages/vsc_quill_delta_to_html/src/mentions/mention_sanitizer
packages/vsc_quill_delta_to_html/src/op_attribute_sanitizer
packages/vsc_quill_delta_to_html/src/op_to_html_converter
packages/vsc_quill_delta_to_html/src/quill_delta_to_html_converter
packages/vsc_quill_delta_to_html/src/value_types
packages/vsc_quill_delta_to_html/vsc_quill_delta_to_html
utils/converters_utils
utils/default_html_converter_options
utils/markdown_rules
utils/typedefs
utils/utils