Skip to content

Commit ddde18b

Browse files
committed
pre tag and horizontal divider support
1 parent 5e6a42d commit ddde18b

File tree

6 files changed

+127
-45
lines changed

6 files changed

+127
-45
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 1.0.9
2+
* added support for horizontal divider
3+
* code block and pre tag support
14
## 1.0.8
25
* fix: Links get formatted but are not clickable in the PDF ([#35](https://github.com/alihassan143/htmltopdfwidgets/issues/35))
36
## 1.0.7

example/pubspec.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ packages:
8787
path: ".."
8888
relative: true
8989
source: path
90-
version: "1.0.6"
90+
version: "1.0.9"
9191
http:
9292
dependency: transitive
9393
description:
@@ -124,10 +124,10 @@ packages:
124124
dependency: transitive
125125
description:
126126
name: markdown
127-
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
127+
sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1"
128128
url: "https://pub.dev"
129129
source: hosted
130-
version: "7.2.2"
130+
version: "7.3.0"
131131
meta:
132132
dependency: transitive
133133
description:

lib/src/html_tags.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ class HTMLTags {
1111
static const paragraph = 'p';
1212
static const image = 'img';
1313
static const anchor = 'a';
14+
static const horizontalDivider = 'hr';
1415
static const italic = 'i';
1516
static const em = 'em';
1617
static const bold = 'b';
18+
static const pre = 'pre';
1719
static const underline = 'u';
1820
static const strikethrough = 's';
1921
static const del = 'del';

lib/src/html_to_widgets.dart

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ class WidgetsHTMLDecoder {
8282
8383
Future<List<Widget>> _parseElement(
8484
Iterable<dom.Node> domNodes,
85-
TextStyle baseTextStyle,
86-
) async {
85+
TextStyle baseTextStyle, {
86+
bool preTag = false,
87+
}) async {
8788
final result = <Widget>[];
8889
final delta = <TextSpan>[];
8990
TextAlign? textAlign;
@@ -98,10 +99,22 @@ class WidgetsHTMLDecoder {
9899
delta.add(const TextSpan(
99100
text: "\n",
100101
));
102+
} else if (localName == HTMLTags.pre) {
103+
final childrens =
104+
await _parseElement(domNode.nodes, baseTextStyle, preTag: true);
105+
delta.add(TextSpan(children: [
106+
WidgetSpan(
107+
child: Container(
108+
width: double.infinity,
109+
decoration: customStyles.codeDecoration ??
110+
BoxDecoration(color: customStyles.codeblockColor),
111+
child: Column(children: childrens)))
112+
]));
101113
} else if (HTMLTags.formattingElements.contains(localName)) {
102114
/// Check if the element is a simple formatting element like <span>, <bold>, or <italic>
103-
final attributes =
104-
await _parserFormattingElementAttributes(domNode, baseTextStyle);
115+
final attributes = await _parserFormattingElementAttributes(
116+
domNode, baseTextStyle,
117+
preTag: preTag);
105118

106119
textAlign = attributes.$1;
107120

@@ -156,6 +169,12 @@ class WidgetsHTMLDecoder {
156169
}
157170

158171
/// Handle special elements (e.g., headings, lists, images)
172+
} else if (localName == HTMLTags.horizontalDivider) {
173+
result.add(Divider(
174+
color: customStyles.dividerColor,
175+
thickness: customStyles.dividerthickness,
176+
height: customStyles.dividerHight,
177+
borderStyle: customStyles.dividerBorderStyle));
159178
}
160179
} else if (domNode is dom.Text) {
161180
if (delta.isNotEmpty && domNode.text.trim().isNotEmpty) {
@@ -270,7 +289,8 @@ class WidgetsHTMLDecoder {
270289

271290
//// Parses the attributes of a formatting element and returns a TextStyle.
272291
Future<(TextAlign?, TextStyle, String?)> _parserFormattingElementAttributes(
273-
dom.Element element, TextStyle baseTextStyle) async {
292+
dom.Element element, TextStyle baseTextStyle,
293+
{bool preTag = false}) async {
274294
final localName = element.localName;
275295
TextAlign? textAlign;
276296
String? link;
@@ -331,17 +351,23 @@ class WidgetsHTMLDecoder {
331351

332352
/// Handle <code> element
333353
case HTMLTags.code:
334-
attributes = attributes
335-
.copyWith(background: const BoxDecoration(color: PdfColors.red))
336-
.merge(customStyles.codeStyle);
354+
if (!preTag) {
355+
attributes = attributes
356+
.copyWith(
357+
background: BoxDecoration(
358+
color: customStyles.codeBlockBackgroundColor))
359+
.merge(customStyles.codeStyle);
360+
}
361+
337362
break;
338363
default:
339364
break;
340365
}
341366

342367
for (final child in element.children) {
343-
final nattributes =
344-
await _parserFormattingElementAttributes(child, baseTextStyle);
368+
final nattributes = await _parserFormattingElementAttributes(
369+
child, baseTextStyle,
370+
preTag: preTag);
345371
attributes = attributes.merge(nattributes.$2);
346372
if (nattributes.$2.decoration != null) {
347373
decoration.add(nattributes.$2.decoration!);
@@ -402,7 +428,7 @@ class WidgetsHTMLDecoder {
402428

403429
///iterate over <tr>children
404430
for (final data in element.children) {
405-
if (data.children.isEmpty) {
431+
if (data.nodes.isEmpty) {
406432
///if single <th> or<td> tag found
407433
final node = paragraphNode(text: data.text);
408434

@@ -423,18 +449,15 @@ class WidgetsHTMLDecoder {
423449

424450
///parse the nodes and handle theem accordingly
425451
Future<Iterable<Widget>> _parseTableSpecialNodes(
426-
dom.Element element, TextStyle baseTextStyle) async {
452+
dom.Element node, TextStyle baseTextStyle) async {
427453
final List<Widget> nodes = [];
428454

429455
///iterate over multiple childrens
430-
if (element.children.isNotEmpty) {
431-
for (final childrens in element.children) {
432-
///parse them according to their widget
433-
nodes.addAll(
434-
await _parseTableDataElementsData(childrens, baseTextStyle));
435-
}
456+
if (node.nodes.isNotEmpty) {
457+
///parse them according to their widget
458+
nodes.addAll(await _parseElement(node.nodes, baseTextStyle));
436459
} else {
437-
nodes.addAll(await _parseTableDataElementsData(element, baseTextStyle));
460+
nodes.addAll(await _parseTableDataElementsData(node, baseTextStyle));
438461
}
439462
return nodes;
440463
}
@@ -452,6 +475,7 @@ class WidgetsHTMLDecoder {
452475

453476
/// Check if the element is a simple formatting element like <span>, <bold>, or <italic>
454477
if (localName == HTMLTags.br) {
478+
result.add(Text('\n'));
455479
} else if (HTMLTags.formattingElements.contains(localName)) {
456480
final attributes =
457481
await _parserFormattingElementAttributes(element, baseTextStyle);
@@ -519,9 +543,9 @@ class WidgetsHTMLDecoder {
519543
text: TextSpan(
520544
children: delta,
521545
style: baseTextStyle
522-
.merge(TextStyle(
546+
.copyWith(
523547
fontSize: level.getHeadingSize,
524-
fontWeight: FontWeight.bold))
548+
fontWeight: FontWeight.bold)
525549
.merge(level.getHeadingStyle(customStyles)))));
526550
}
527551

@@ -669,6 +693,19 @@ class WidgetsHTMLDecoder {
669693
HTMLTags.formattingElements.contains(child.localName) == false) {
670694
childNodes.addAll(await _parseElement(child.nodes, baseTextStyle));
671695
} else {
696+
if (child.localName == HTMLTags.pre) {
697+
final childrens =
698+
await _parseElement(child.nodes, baseTextStyle, preTag: true);
699+
delta.add(TextSpan(children: [
700+
WidgetSpan(
701+
child: Container(
702+
width: double.infinity,
703+
decoration: customStyles.codeDecoration ??
704+
BoxDecoration(color: customStyles.codeblockColor),
705+
child: Column(children: childrens)))
706+
]));
707+
} else
708+
672709
/// Handle special elements (e.g., headings, lists) within a paragraph
673710
if (HTMLTags.specialElements.contains(child.localName)) {
674711
childNodes.addAll(
@@ -678,6 +715,12 @@ class WidgetsHTMLDecoder {
678715
type: BuiltInAttributeKey.bulletedList,
679716
),
680717
);
718+
} else if (child.localName == HTMLTags.horizontalDivider) {
719+
childNodes.add(Divider(
720+
color: customStyles.dividerColor,
721+
thickness: customStyles.dividerthickness,
722+
height: customStyles.dividerHight,
723+
borderStyle: customStyles.dividerBorderStyle));
681724
} else {
682725
if (child.localName == HTMLTags.br) {
683726
delta.add(const TextSpan(

lib/src/htmltagstyles.dart

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,58 @@ class HtmlTagStyle {
3636
//quote bar style that will merge with default style
3737
final PdfColor? quoteBarColor;
3838
//bullet list style style that will merge with default style
39+
/// The color of the bullet list icon in a PDF document.
3940
final PdfColor? bulletListIconColor;
40-
const HtmlTagStyle({
41-
this.boldStyle,
42-
this.italicStyle,
43-
this.h1Style,
44-
this.h2Style,
45-
this.h3Style,
46-
this.imageAlignment = Alignment.center,
47-
this.h4Style,
48-
this.h5Style,
49-
this.h6Style,
50-
this.strikeThrough,
51-
this.paragraphStyle,
52-
this.codeStyle,
53-
this.headingStyle,
54-
this.listIndexStyle,
55-
this.linkStyle,
56-
this.quoteBarColor,
57-
this.bulletListIconColor,
58-
});
41+
42+
/// The color of the divider in a PDF document.
43+
final PdfColor dividerColor;
44+
45+
/// The border style of the divider in a PDF document.
46+
final BorderStyle? dividerBorderStyle;
47+
48+
/// The thickness of the divider in a PDF document.
49+
/// The thickness of the divider line.
50+
///
51+
/// This value determines how thick the divider line will be.
52+
final double dividerthickness;
53+
54+
/// The background color of the code block.
55+
///
56+
/// This color is used as the background for code blocks in the PDF.
57+
final PdfColor codeBlockBackgroundColor;
58+
59+
/// The color of the code block text.
60+
///
61+
/// This color is used for the text within code blocks in the PDF.
62+
final PdfColor codeblockColor;
63+
// The decoration style that will merge with default style
64+
final BoxDecoration? codeDecoration;
65+
66+
/// The height of the divider in a PDF document.
67+
final double dividerHight;
68+
const HtmlTagStyle(
69+
{this.boldStyle,
70+
this.italicStyle,
71+
this.h1Style,
72+
this.h2Style,
73+
this.h3Style,
74+
this.imageAlignment = Alignment.center,
75+
this.h4Style,
76+
this.h5Style,
77+
this.h6Style,
78+
this.strikeThrough,
79+
this.paragraphStyle,
80+
this.codeStyle,
81+
this.headingStyle,
82+
this.listIndexStyle,
83+
this.linkStyle,
84+
this.quoteBarColor,
85+
this.bulletListIconColor,
86+
this.dividerBorderStyle,
87+
this.dividerHight = 0.5,
88+
this.codeBlockBackgroundColor = PdfColors.red,
89+
this.codeblockColor = PdfColors.grey,
90+
this.codeDecoration,
91+
this.dividerthickness = 1.0,
92+
this.dividerColor = PdfColors.grey});
5993
}

pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: htmltopdfwidgets
22
description: A Dart package that converts HTML and Markdown rich text content into high-quality PDF widgets. Simplify the creation of PDF documents by seamlessly transforming your structured text content into printable, professional PDFs. Perfect for generating reports, invoices, and more!
3-
version: 1.0.8
3+
version: 1.0.9
44
homepage: https://github.com/alihassan143/htmltopdfwidgets
55

66
environment:
@@ -10,7 +10,7 @@ dependencies:
1010
pdf: ">=3.11.1 <4.0.0"
1111
html: ">=0.15.5 <1.0.0"
1212
http: ">=1.2.2 <2.0.0"
13-
markdown: ^7.2.2
13+
markdown: ^7.3.0
1414

1515
dev_dependencies:
1616
flutter_lints: ^4.0.0

0 commit comments

Comments
 (0)