blockGenerators method
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
//TODO: implement a param to add custom conversion from html inlines to markdown for devs
}
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 
/// also 
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;
}