blockGenerators method

  1. @override
Future<List<Widget>> blockGenerators(
  1. List<String> lines, [
  2. Map<String, dynamic>? extraInfo
])
override

This function generate widgets to create a book with custom views of the content

Implementation

@override
Future<List<pw.Widget>> blockGenerators(List<String> lines,
    [Map<String, dynamic>? extraInfo]) async {
  final bool isDefaulBlockConvertion = customHTMLToMarkdownConverter == null;
  final List<pw.Widget> contentPerPage = <pw.Widget>[];
  for (int i = 0; i < lines.length; i++) {
    late String line;
    //if is custom HTML, then just get the default line.
    //The local implementation use markdown with html
    if (!isDefaulBlockConvertion) {
      line = lines.elementAt(i);
    } else {
      line = lines
          .elementAt(i)
          .replaceAll(r'\"', '"')
          .convertHTMLToMarkdown; //delete the encode that avoid conflicts with delta map
    }
    print(line);
    if (customConverters.isNotEmpty) {
      for (final CustomConverter detector in customConverters) {
        if (detector.predicate.hasMatch(line)) {
          final List<RegExpMatch> matches =
              List<RegExpMatch>.from(detector.predicate.allMatches(line));
          if (matches.isNotEmpty) {
            contentPerPage.add(detector.widgetCallback(
              matches: matches,
              input: line,
              lineWithoutFormatting:
                  line.decodeSymbols.convertUTF8QuotesToValidString,
            ));
            continue;
          }
        }
      }
    }
    //search any span that contains just ![]() images
    if (Constant.IMAGE_PATTERN_IN_SPAN.hasMatch(line.decodeSymbols)) {
      if (onDetectImageBlock != null) {
        contentPerPage.add(await onDetectImageBlock!
            .call(Constant.IMAGE_PATTERN_IN_SPAN, line));
        continue;
      }
      final pw.Widget? image = await getImageBlock.call(Constant
          .IMAGE_PATTERN_IN_SPAN
          .firstMatch(line.decodeSymbols)!
          .group(1)!);
      if (image != null) contentPerPage.add(image);
      continue;
    } else if (Constant.BLOCKQUOTE_PATTERN.hasMatch(line.decodeSymbols)) {
      if (onDetectBlockquote != null) {
        contentPerPage.add(await onDetectBlockquote!
            .call(Constant.BLOCKQUOTE_PATTERN, line.decodeSymbols));
        continue;
      }

      /// founds multiline where starts with <pre> and ends with </pre>
      contentPerPage.addAll(await getBlockQuote.call(line.decodeSymbols));
    } else if (Constant.CODE_PATTERN
        .hasMatch(line.replaceAll('\n', r'\n').decodeSymbols)) {
      if (onDetectCodeBlock != null) {
        contentPerPage.add(await onDetectCodeBlock!
            .call(Constant.CODE_PATTERN, line.decodeSymbols));
        continue;
      }

      /// founds multiline where starts with <pre> and ends with </pre>
      contentPerPage.addAll(await getCodeBlock.call(line.decodeSymbols));
    } else if (Constant.NEWLINE_WITH_SPACING_PATTERN.hasMatch(line)) {
      /// founds lines like <span style="line-spacing: 1.0">\n</span>
      contentPerPage.add(pw.RichText(
          softWrap: true,
          overflow: pw.TextOverflow.span,
          text: pw.TextSpan(children: await getNewLinesWithSpacing(line))));
    } else if (Constant.STARTS_WITH_RICH_TEXT_INLINE_STYLES_PATTERN
        .hasMatch(line)) {
      if (onDetectInlineRichTextStyles != null) {
        contentPerPage.add(await onDetectInlineRichTextStyles!.call(
            Constant.STARTS_WITH_RICH_TEXT_INLINE_STYLES_PATTERN, line));
        continue;
      }

      /// founds lines like <span style="wiki-doc: id">(.*?)<\/span> or <span style="line-height: 2.0")">(.*?)<\/span> or <span\s?style="font-size: 12">(.*?)<\/span>)
      /// and those three ones together are matched
      final List<pw.InlineSpan> spans =
          await getRichTextInlineStyles.call(line, defaultTextStyle);
      final double spacing = (spans.first.style?.lineSpacing ?? 1.0);
      contentPerPage.add(
        pw.Padding(
          padding: pw.EdgeInsets.only(
              bottom: spacing.resolvePaddingByLineHeight()),
          child: pw.RichText(
            softWrap: true,
            overflow: pw.TextOverflow.span,
            text: pw.TextSpan(
              children: spans,
            ),
          ),
        ),
      );
    } else if (Constant.HEADER_PATTERN.hasMatch(line) ||
        Constant.ALIGNED_HEADER_PATTERN.hasMatch(line)) {
      /// founds lines like # header 1 or <h1 style="text-align:center">header 1</h1>
      if (Constant.HEADER_PATTERN.hasMatch(line)) {
        if (onDetectHeaderBlock != null) {
          contentPerPage.add(
              await onDetectHeaderBlock!.call(Constant.HEADER_PATTERN, line));
          continue;
        }

        /// founds lines like # header 1 or ## header 2
        contentPerPage.add(await getHeaderBlock.call(line));
        continue;
      }
      if (onDetectHeaderBlock != null) {
        contentPerPage.add(await onDetectHeaderBlock!
            .call(Constant.ALIGNED_HEADER_PATTERN, line));
        continue;
      }
      contentPerPage.addAll(await getAlignedHeaderBlock.call(line));
    } else if (Constant.IMAGE_PATTERN.hasMatch(line)) {
      /// founds lines like ![max-width: 100%;object-fit: fill](image_bytes)
      /// also ![styles](url|file-path)
      if (onDetectImageBlock != null) {
        contentPerPage.add(
            await onDetectImageBlock!.call(Constant.IMAGE_PATTERN, line));
        continue;
      }
      final pw.Widget? image = await getImageBlock.call(line);
      if (image != null) contentPerPage.add(image);
    } else if (Constant.ALIGNED_P_PATTERN.hasMatch(line)) {
      /// founds lines like <p style="text-align:center">paragraph</p>
      if (onDetectAlignedParagraph != null) {
        contentPerPage.add(await onDetectAlignedParagraph!
            .call(Constant.ALIGNED_P_PATTERN, line));
        continue;
      }
      contentPerPage.addAll(await getAlignedParagraphBlock.call(line));
    } else if (line.isTotallyEmpty ||
        Constant.EMPTY_ALIGNED_H.hasMatch(line) ||
        Constant.EMPTY_ALIGNED_P.hasMatch(line)) {
      /// founds lines like [] or <p style="text-align:center">\n</p> or <h1 style="text-align:center">\n</h1>
      // this could be returning/printing br word in document instead \n
      //TODO: make a function to get the last or the first and get the spacing
      bool isHeaderEmpty = Constant.EMPTY_ALIGNED_H.hasMatch(line);
      final String newLineDecided = line.isNotEmpty
          ? isHeaderEmpty
              ? line
                  .replaceAll(RegExp(r'<h([1-6])(.+?)?>|<\/h(\1)>'), '')
                  .replaceHtmlBrToManyNewLines
              : line
                  .replaceAll(RegExp(r'<p>|<p.*?>|<\/p>'), '')
                  .replaceHtmlBrToManyNewLines
          : '\n';
      contentPerPage.add(
        pw.Paragraph(
          text: newLineDecided,
          style: defaultTextStyle,
          padding: const pw.EdgeInsets.symmetric(vertical: 1.5),
          margin: pw.EdgeInsets.zero,
        ),
      );
    } else if (Constant.LIST_PATTERN.hasMatch(line) ||
        Constant.LIST_CHECK_MD_PATTERN.hasMatch(line)) {
      if (onDetectList != null) {
        contentPerPage.add(await onDetectList!.call(
            Constant.LIST_PATTERN.hasMatch(line)
                ? Constant.LIST_PATTERN
                : Constant.LIST_CHECK_MD_PATTERN,
            line));
        continue;
      }
      //TODO: now add support for indented lists ->
      //TODO: now add support for list with different prefixes
      /// founds lines like:
      /// "[x] checked" or
      /// "[ ] uncheck" or
      /// "1. ordered list" or
      /// "i. ordered list" or
      /// "a. ordered list" or
      /// "* unordered list"
      contentPerPage.add(await getListBlock.call(
          line, Constant.LIST_CHECK_MD_PATTERN.hasMatch(line)));
    } else if (Constant.HTML_LINK_TAGS_PATTERN.hasMatch(line)) {
      if (onDetectLink != null) {
        contentPerPage.add(
            await onDetectLink!.call(Constant.HTML_LINK_TAGS_PATTERN, line));
        continue;
      }

      /// founds lines like (title)[href]
      contentPerPage.add(
        pw.RichText(
          softWrap: true,
          overflow: pw.TextOverflow.span,
          text: pw.TextSpan(
            children: await getLinkStyle.call(line),
          ),
        ),
      );
    } else if (Constant.INLINE_STYLES_PATTERN.hasMatch(line)) {
      if (onDetectInlinesMarkdown != null) {
        contentPerPage.add(await onDetectInlinesMarkdown!
            .call(Constant.INLINE_STYLES_PATTERN, line));
        continue;
      }

      /// founds lines like *italic* _underline_ **bold** or those three ones together
      final List<pw.TextSpan> spans = await getInlineStyles.call(line);
      final double spacing = (spans.firstOrNull?.style?.lineSpacing ?? 1.0);
      contentPerPage.add(
        pw.Padding(
          padding: pw.EdgeInsets.symmetric(
              vertical: spacing.resolvePaddingByLineHeight()),
          child: pw.RichText(
            softWrap: true,
            overflow: pw.TextOverflow.span,
            text: pw.TextSpan(children: spans),
          ),
        ),
      );
    } else {
      if (Constant.RICH_TEXT_INLINE_STYLES_PATTERN.hasMatch(line)) {
        if (onDetectInlineRichTextStyles != null) {
          contentPerPage.add(await onDetectInlineRichTextStyles!
              .call(Constant.RICH_TEXT_INLINE_STYLES_PATTERN, line));
          continue;
        }

        /// founds lines like <span style="wiki-doc: id">(.*?)<\/span>) or <span style="line-height: 2.0")">(.*?)<\/span> or <span\s?style="font-size: 12">(.*?)<\/span>)
        /// and those three ones together are matched
        final List<pw.InlineSpan> spans =
            await getRichTextInlineStyles.call(line, defaultTextStyle);
        final double spacing = (spans.first.style?.lineSpacing ?? 1.0);
        contentPerPage.add(
          pw.Padding(
            padding: pw.EdgeInsets.symmetric(
                vertical: spacing.resolvePaddingByLineHeight()),
            child: pw.RichText(
              softWrap: true,
              overflow: pw.TextOverflow.span,
              text: pw.TextSpan(
                children: spans,
              ),
            ),
          ),
        );
        continue;
      }
      if (onDetectCommonText != null) {
        contentPerPage.add(await onDetectCommonText!.call(null, line));
        continue;
      }
      if (isHTML(line)) {
        final List<pw.TextSpan> spans = await applyInlineStyles.call(line);
        contentPerPage.add(
          pw.Padding(
            padding: pw.EdgeInsets.symmetric(
                vertical:
                    ((spans.firstOrNull?.style?.lineSpacing ?? 0.40) - 0.40)
                        .resolveLineHeight()),
            child: pw.RichText(
              softWrap: true,
              overflow: pw.TextOverflow.span,
              text: pw.TextSpan(children: spans),
            ),
          ),
        );
        continue;
      }
      //Wether found a plain text, then set default styles since we cannot detect any style to plain content
      contentPerPage.add(
        pw.Padding(
          padding: const pw.EdgeInsets.symmetric(vertical: 1.0),
          child: pw.RichText(
            softWrap: true,
            overflow: pw.TextOverflow.span,
            text: pw.TextSpan(text: line, style: defaultTextStyle),
          ),
        ),
      );
    }
  }
  return contentPerPage;
}