Showing
25 changed files
with
2851 additions
and
8 deletions
@@ -12,11 +12,12 @@ | @@ -12,11 +12,12 @@ | ||
12 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
13 | # limitations under the License. | 13 | # limitations under the License. |
14 | 14 | ||
15 | - DART_SRC=$(shell find . -name '*.dart') | ||
16 | - CLNG_SRC=$(shell find printing/ios printing/macos printing/windows printing/android -name '*.cpp' -o -name '*.m' -o -name '*.h' -o -name '*.java') | ||
17 | - SWFT_SRC=$(shell find printing/ios printing/macos -name '*.swift') | ||
18 | - FONTS=pdf/open-sans.ttf pdf/open-sans-bold.ttf pdf/roboto.ttf pdf/noto-sans.ttf pdf/genyomintw.ttf demo/assets/roboto1.ttf demo/assets/roboto2.ttf demo/assets/roboto3.ttf demo/assets/open-sans.ttf demo/assets/open-sans-bold.ttf pdf/hacen-tunisia.ttf pdf/material.ttf | ||
19 | - COV_PORT=9292 | 15 | +DART_SRC=$(shell find . -name '*.dart') |
16 | +CLNG_SRC=$(shell find printing/ios printing/macos printing/windows printing/android -name '*.cpp' -o -name '*.m' -o -name '*.h' -o -name '*.java') | ||
17 | +SWFT_SRC=$(shell find printing/ios printing/macos -name '*.swift') | ||
18 | +FONTS=pdf/open-sans.ttf pdf/open-sans-bold.ttf pdf/roboto.ttf pdf/noto-sans.ttf pdf/genyomintw.ttf demo/assets/roboto1.ttf demo/assets/roboto2.ttf demo/assets/roboto3.ttf demo/assets/open-sans.ttf demo/assets/open-sans-bold.ttf pdf/hacen-tunisia.ttf pdf/material.ttf | ||
19 | +COV_PORT=9292 | ||
20 | +SVG=blend_and_mask blend_mode_devil clip_path clip_path_2 clip_path_2 clip_path_3 clip_path_3 dash_path ellipse empty_defs equation fill-rule-inherit group_composite_opacity group_fill_opacity group_mask group_opacity group_opacity_transform hidden href-fill image image_def implicit_fill_with_opacity linear_gradient linear_gradient_2 linear_gradient_absolute_user_space_translate linear_gradient_percentage_bounding_translate linear_gradient_percentage_user_space_translate linear_gradient_xlink male mask mask_with_gradient mask_with_use mask_with_use2 nested_group opacity_on_path radial_gradient radial_gradient_absolute_user_space_translate radial_gradient_focal radial_gradient_percentage_bounding_translate radial_gradient_percentage_user_space_translate radial_gradient_xlink radial_ref_linear_gradient rect_rrect rect_rrect_no_ry stroke_inherit_circles style_attr text text_2 text_3 use_circles use_circles_def use_emc2 use_fill use_opacity_grid width_height_viewbox flutter_logo emoji_u1f600 text_transform dart new-pause-button new-send-circle new-gif new-camera new-image numeric_25 new-mention new-gif-button new-action-expander new-play-button aa alphachannel Ghostscript_Tiger Firefox_Logo_2017 chess_knight Flag_of_the_United_States | ||
20 | 21 | ||
21 | all: $(FONTS) demo/assets/logo.png demo/assets/profile.jpg format printing/example/.metadata get | 22 | all: $(FONTS) demo/assets/logo.png demo/assets/profile.jpg format printing/example/.metadata get |
22 | 23 | ||
@@ -99,7 +100,7 @@ test/pubspec.lock: test/pubspec.yaml | @@ -99,7 +100,7 @@ test/pubspec.lock: test/pubspec.yaml | ||
99 | 100 | ||
100 | get: $(FONTS) pdf/pubspec.lock printing/pubspec.lock demo/pubspec.lock test/pubspec.lock | 101 | get: $(FONTS) pdf/pubspec.lock printing/pubspec.lock demo/pubspec.lock test/pubspec.lock |
101 | 102 | ||
102 | -test-pdf: $(FONTS) pdf/pubspec.lock .coverage | 103 | +test-pdf: svg $(FONTS) pdf/pubspec.lock .coverage |
103 | cd pdf; pub global run coverage:collect_coverage --port=$(COV_PORT) -o coverage.json --resume-isolates --wait-paused &\ | 104 | cd pdf; pub global run coverage:collect_coverage --port=$(COV_PORT) -o coverage.json --resume-isolates --wait-paused &\ |
104 | dart --enable-asserts --disable-service-auth-codes --enable-vm-service=$(COV_PORT) --pause-isolates-on-exit test/all_tests.dart | 105 | dart --enable-asserts --disable-service-auth-codes --enable-vm-service=$(COV_PORT) --pause-isolates-on-exit test/all_tests.dart |
105 | cd pdf; pub global run coverage:format_coverage --packages=.packages -i coverage.json --report-on lib --lcov --out lcov.info | 106 | cd pdf; pub global run coverage:format_coverage --packages=.packages -i coverage.json --report-on lib --lcov --out lcov.info |
@@ -160,7 +161,93 @@ fix: get .dartfix | @@ -160,7 +161,93 @@ fix: get .dartfix | ||
160 | cd pdf; dart pub global run dartfix --pedantic --overwrite . | 161 | cd pdf; dart pub global run dartfix --pedantic --overwrite . |
161 | cd printing; dart pub global run dartfix --pedantic --overwrite . | 162 | cd printing; dart pub global run dartfix --pedantic --overwrite . |
162 | 163 | ||
163 | -ref: | 164 | +ref/svg/%.svg: |
165 | + mkdir -p ref/svg | ||
166 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/simple/$(notdir $@)" > $@ | ||
167 | + | ||
168 | +ref/svg/flutter_logo.svg: | ||
169 | + mkdir -p ref/svg | ||
170 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/$(notdir $@)" > $@ | ||
171 | + | ||
172 | +ref/svg/dart.svg: | ||
173 | + mkdir -p ref/svg | ||
174 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/$(notdir $@)" > $@ | ||
175 | + | ||
176 | +ref/svg/text_transform.svg: | ||
177 | + mkdir -p ref/svg | ||
178 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/$(notdir $@)" > $@ | ||
179 | + | ||
180 | +ref/svg/emoji_u1f600.svg: | ||
181 | + mkdir -p ref/svg | ||
182 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/noto-emoji/$(notdir $@)" > $@ | ||
183 | + | ||
184 | +ref/svg/new-pause-button.svg: | ||
185 | + mkdir -p ref/svg | ||
186 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@ | ||
187 | + | ||
188 | +ref/svg/new-send-circle.svg: | ||
189 | + mkdir -p ref/svg | ||
190 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@ | ||
191 | + | ||
192 | +ref/svg/new-gif.svg: | ||
193 | + mkdir -p ref/svg | ||
194 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@ | ||
195 | + | ||
196 | +ref/svg/new-camera.svg: | ||
197 | + mkdir -p ref/svg | ||
198 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@ | ||
199 | + | ||
200 | +ref/svg/new-image.svg: | ||
201 | + mkdir -p ref/svg | ||
202 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@ | ||
203 | + | ||
204 | +ref/svg/numeric_25.svg: | ||
205 | + mkdir -p ref/svg | ||
206 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@ | ||
207 | + | ||
208 | +ref/svg/new-mention.svg: | ||
209 | + mkdir -p ref/svg | ||
210 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@ | ||
211 | + | ||
212 | +ref/svg/new-gif-button.svg: | ||
213 | + mkdir -p ref/svg | ||
214 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@ | ||
215 | + | ||
216 | +ref/svg/new-action-expander.svg: | ||
217 | + mkdir -p ref/svg | ||
218 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@ | ||
219 | + | ||
220 | +ref/svg/new-play-button.svg: | ||
221 | + mkdir -p ref/svg | ||
222 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@ | ||
223 | + | ||
224 | +ref/svg/aa.svg: | ||
225 | + mkdir -p ref/svg | ||
226 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/w3samples/$(notdir $@)" > $@ | ||
227 | + | ||
228 | +ref/svg/alphachannel.svg: | ||
229 | + mkdir -p ref/svg | ||
230 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/w3samples/$(notdir $@)" > $@ | ||
231 | + | ||
232 | +ref/svg/Ghostscript_Tiger.svg: | ||
233 | + mkdir -p ref/svg | ||
234 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/wikimedia/$(notdir $@)" > $@ | ||
235 | + | ||
236 | +ref/svg/Firefox_Logo_2017.svg: | ||
237 | + mkdir -p ref/svg | ||
238 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/wikimedia/$(notdir $@)" > $@ | ||
239 | + | ||
240 | +ref/svg/chess_knight.svg: | ||
241 | + mkdir -p ref/svg | ||
242 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/wikimedia/$(notdir $@)" > $@ | ||
243 | + | ||
244 | +ref/svg/Flag_of_the_United_States.svg: | ||
245 | + mkdir -p ref/svg | ||
246 | + curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/wikimedia/$(notdir $@)" > $@ | ||
247 | + | ||
248 | +svg: $(patsubst %,ref/svg/%.svg,$(SVG)) | ||
249 | + | ||
250 | +ref: svg | ||
164 | mkdir -p ref | 251 | mkdir -p ref |
165 | cd $@; curl -OL 'https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf' | 252 | cd $@; curl -OL 'https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf' |
166 | cd $@; curl -OL 'https://www.adobe.com/content/dam/acom/en/devnet/pdf/adobe_supplement_iso32000.pdf' | 253 | cd $@; curl -OL 'https://www.adobe.com/content/dam/acom/en/devnet/pdf/adobe_supplement_iso32000.pdf' |
@@ -20,6 +20,7 @@ | @@ -20,6 +20,7 @@ | ||
20 | - Fix RichText.maxLines with multiple TextSpan | 20 | - Fix RichText.maxLines with multiple TextSpan |
21 | - Fix Exif parsing | 21 | - Fix Exif parsing |
22 | - Add Border and BorderSide objects | 22 | - Add Border and BorderSide objects |
23 | +- Add basic support for SVG images | ||
23 | 24 | ||
24 | ## 1.12.0 | 25 | ## 1.12.0 |
25 | 26 |
pdf/lib/svg.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:meta/meta.dart'; | ||
18 | +import 'package:pdf/pdf.dart'; | ||
19 | +import 'package:pdf/widgets.dart'; | ||
20 | +import 'package:vector_math/vector_math_64.dart'; | ||
21 | +import 'package:xml/xml.dart'; | ||
22 | + | ||
23 | +import 'svg/painter.dart'; | ||
24 | +import 'svg/parser.dart'; | ||
25 | + | ||
26 | +class SvgImage extends Widget { | ||
27 | + factory SvgImage({ | ||
28 | + @required String svg, | ||
29 | + BoxFit fit = BoxFit.contain, | ||
30 | + bool clip = true, | ||
31 | + double width, | ||
32 | + double height, | ||
33 | + }) { | ||
34 | + assert(clip != null); | ||
35 | + | ||
36 | + final xml = XmlDocument.parse(svg); | ||
37 | + final parser = SvgParser(xml: xml); | ||
38 | + | ||
39 | + return SvgImage._fromPainter( | ||
40 | + parser, | ||
41 | + fit, | ||
42 | + clip, | ||
43 | + width, | ||
44 | + height, | ||
45 | + ); | ||
46 | + } | ||
47 | + | ||
48 | + SvgImage._fromPainter( | ||
49 | + this._svgParser, | ||
50 | + this.fit, | ||
51 | + this.clip, | ||
52 | + this.width, | ||
53 | + this.height, | ||
54 | + ) : assert(_svgParser != null), | ||
55 | + assert(fit != null); | ||
56 | + | ||
57 | + final SvgParser _svgParser; | ||
58 | + | ||
59 | + final BoxFit fit; | ||
60 | + | ||
61 | + final bool clip; | ||
62 | + | ||
63 | + final double width; | ||
64 | + | ||
65 | + final double height; | ||
66 | + | ||
67 | + @override | ||
68 | + void layout(Context context, BoxConstraints constraints, | ||
69 | + {bool parentUsesSize = false}) { | ||
70 | + final w = width != null || _svgParser.width != null | ||
71 | + ? constraints.constrainWidth(width ?? _svgParser.width) | ||
72 | + : constraints.hasBoundedWidth | ||
73 | + ? constraints.maxWidth | ||
74 | + : constraints.constrainWidth(_svgParser.viewBox.width); | ||
75 | + final h = height != null || _svgParser.height != null | ||
76 | + ? constraints.constrainHeight(height ?? _svgParser.height) | ||
77 | + : constraints.hasBoundedHeight | ||
78 | + ? constraints.maxHeight | ||
79 | + : constraints.constrainHeight(_svgParser.viewBox.height); | ||
80 | + | ||
81 | + final sizes = applyBoxFit( | ||
82 | + fit, | ||
83 | + PdfPoint(_svgParser.viewBox.width, _svgParser.viewBox.height), | ||
84 | + PdfPoint(w, h)); | ||
85 | + box = PdfRect.fromPoints(PdfPoint.zero, sizes.destination); | ||
86 | + } | ||
87 | + | ||
88 | + @override | ||
89 | + void paint(Context context) { | ||
90 | + super.paint(context); | ||
91 | + | ||
92 | + final mat = Matrix4.identity(); | ||
93 | + mat.translate( | ||
94 | + box.x, | ||
95 | + box.y + box.height, | ||
96 | + ); | ||
97 | + mat.scale( | ||
98 | + box.width / _svgParser.viewBox.width, | ||
99 | + -box.height / _svgParser.viewBox.height, | ||
100 | + ); | ||
101 | + mat.translate( | ||
102 | + -_svgParser.viewBox.x, | ||
103 | + -_svgParser.viewBox.y, | ||
104 | + ); | ||
105 | + context.canvas.saveContext(); | ||
106 | + if (clip) { | ||
107 | + context.canvas | ||
108 | + ..drawBox(box) | ||
109 | + ..clipPath(); | ||
110 | + } | ||
111 | + context.canvas.setTransform(mat); | ||
112 | + | ||
113 | + final painter = SvgPainter( | ||
114 | + _svgParser, | ||
115 | + context.canvas, | ||
116 | + context.document, | ||
117 | + PdfRect( | ||
118 | + 0, | ||
119 | + 0, | ||
120 | + context.page.pageFormat.width, | ||
121 | + context.page.pageFormat.height, | ||
122 | + ), | ||
123 | + ); | ||
124 | + painter.paint(); | ||
125 | + context.canvas.restoreContext(); | ||
126 | + } | ||
127 | +} |
pdf/lib/svg/brush.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:meta/meta.dart'; | ||
18 | +import 'package:pdf/pdf.dart'; | ||
19 | +import 'package:pdf/svg/painter.dart'; | ||
20 | +import 'package:xml/xml.dart'; | ||
21 | + | ||
22 | +import 'color.dart'; | ||
23 | +import 'mask_path.dart'; | ||
24 | +import 'parser.dart'; | ||
25 | + | ||
26 | +enum SvgTextAnchor { start, middle, end } | ||
27 | + | ||
28 | +@immutable | ||
29 | +class SvgBrush { | ||
30 | + const SvgBrush({ | ||
31 | + @required this.opacity, | ||
32 | + @required this.fill, | ||
33 | + @required this.fillEvenOdd, | ||
34 | + @required this.fillOpacity, | ||
35 | + @required this.stroke, | ||
36 | + @required this.strokeOpacity, | ||
37 | + @required this.strokeWidth, | ||
38 | + @required this.strokeDashArray, | ||
39 | + @required this.strokeDashOffset, | ||
40 | + @required this.strokeLineCap, | ||
41 | + @required this.strokeLineJoin, | ||
42 | + @required this.strokeMiterLimit, | ||
43 | + @required this.fontFamily, | ||
44 | + @required this.fontSize, | ||
45 | + @required this.fontStyle, | ||
46 | + @required this.fontWeight, | ||
47 | + @required this.textAnchor, | ||
48 | + @required this.blendMode, | ||
49 | + this.mask, | ||
50 | + }); | ||
51 | + | ||
52 | + factory SvgBrush.fromXml( | ||
53 | + XmlElement element, | ||
54 | + SvgBrush parent, | ||
55 | + SvgPainter painter, | ||
56 | + ) { | ||
57 | + SvgParser.convertStyle(element); | ||
58 | + | ||
59 | + final strokeDashArray = element.getAttribute('stroke-dasharray'); | ||
60 | + final fillRule = element.getAttribute('fill-rule'); | ||
61 | + final strokeLineCap = element.getAttribute('stroke-linecap'); | ||
62 | + final strokeLineJoin = element.getAttribute('stroke-linejoin'); | ||
63 | + final blendMode = element.getAttribute('mix-blend-mode'); | ||
64 | + | ||
65 | + final result = parent.merge(SvgBrush( | ||
66 | + opacity: SvgParser.getDouble(element, 'opacity', defaultValue: null), | ||
67 | + blendMode: blendMode == null ? null : _blendModes[blendMode], | ||
68 | + fillOpacity: | ||
69 | + SvgParser.getDouble(element, 'fill-opacity', defaultValue: null), | ||
70 | + strokeOpacity: | ||
71 | + SvgParser.getDouble(element, 'stroke-opacity', defaultValue: null), | ||
72 | + strokeLineCap: | ||
73 | + strokeLineCap == null ? null : _strokeLineCap[strokeLineCap], | ||
74 | + strokeLineJoin: | ||
75 | + strokeLineJoin == null ? null : _strokeLineJoin[strokeLineJoin], | ||
76 | + strokeMiterLimit: | ||
77 | + SvgParser.getDouble(element, 'stroke-miterlimit', defaultValue: null), | ||
78 | + fill: SvgColor.fromXml(element.getAttribute('fill'), painter), | ||
79 | + fillEvenOdd: fillRule == null ? null : fillRule == 'evenodd', | ||
80 | + stroke: SvgColor.fromXml(element.getAttribute('stroke'), painter), | ||
81 | + strokeWidth: SvgParser.getNumeric(element, 'stroke-width', parent), | ||
82 | + strokeDashArray: strokeDashArray == null | ||
83 | + ? null | ||
84 | + : (strokeDashArray == 'none' | ||
85 | + ? [] | ||
86 | + : SvgParser.splitDoubles(strokeDashArray).toList()), | ||
87 | + strokeDashOffset: | ||
88 | + SvgParser.getNumeric(element, 'stroke-dashoffset', parent)?.sizeValue, | ||
89 | + fontSize: SvgParser.getNumeric(element, 'font-size', parent), | ||
90 | + fontFamily: element.getAttribute('font-family'), | ||
91 | + fontStyle: element.getAttribute('font-style'), | ||
92 | + fontWeight: element.getAttribute('font-weight'), | ||
93 | + textAnchor: _textAnchors[element.getAttribute('text-anchor')], | ||
94 | + )); | ||
95 | + | ||
96 | + final mask = SvgMaskPath.fromXml(element, painter, result); | ||
97 | + if (mask != null) { | ||
98 | + return result.copyWith(mask: mask); | ||
99 | + } | ||
100 | + | ||
101 | + return result; | ||
102 | + } | ||
103 | + | ||
104 | + static const defaultContext = SvgBrush( | ||
105 | + opacity: 1, | ||
106 | + blendMode: null, | ||
107 | + fillOpacity: 1, | ||
108 | + strokeOpacity: 1, | ||
109 | + fill: SvgColor.defaultColor, | ||
110 | + fillEvenOdd: false, | ||
111 | + stroke: SvgColor.none, | ||
112 | + strokeLineCap: PdfLineCap.butt, | ||
113 | + strokeLineJoin: PdfLineJoin.miter, | ||
114 | + strokeMiterLimit: 4, | ||
115 | + strokeWidth: SvgNumeric.value(1, null, SvgUnit.pixels), | ||
116 | + strokeDashArray: [], | ||
117 | + strokeDashOffset: 0, | ||
118 | + fontSize: SvgNumeric.value(16, null), | ||
119 | + fontFamily: 'sans-serif', | ||
120 | + fontWeight: 'normal', | ||
121 | + fontStyle: 'normal', | ||
122 | + textAnchor: SvgTextAnchor.start, | ||
123 | + mask: null, | ||
124 | + ); | ||
125 | + | ||
126 | + static const _blendModes = <String, PdfBlendMode>{ | ||
127 | + 'normal': PdfBlendMode.normal, | ||
128 | + 'multiply': PdfBlendMode.multiply, | ||
129 | + 'screen': PdfBlendMode.screen, | ||
130 | + 'overlay': PdfBlendMode.overlay, | ||
131 | + 'darken': PdfBlendMode.darken, | ||
132 | + 'lighten': PdfBlendMode.lighten, | ||
133 | + 'color-dodge': PdfBlendMode.color, | ||
134 | + 'color-burn': PdfBlendMode.color, | ||
135 | + 'hard-light': PdfBlendMode.hardLight, | ||
136 | + 'soft-light': PdfBlendMode.softLight, | ||
137 | + 'difference': PdfBlendMode.difference, | ||
138 | + 'exclusion': PdfBlendMode.exclusion, | ||
139 | + 'hue': PdfBlendMode.hue, | ||
140 | + 'saturation': PdfBlendMode.saturation, | ||
141 | + 'color': PdfBlendMode.color, | ||
142 | + 'luminosity': PdfBlendMode.luminosity, | ||
143 | + }; | ||
144 | + | ||
145 | + static const _strokeLineCap = <String, PdfLineCap>{ | ||
146 | + 'butt': PdfLineCap.butt, | ||
147 | + 'round': PdfLineCap.round, | ||
148 | + 'square': PdfLineCap.square, | ||
149 | + }; | ||
150 | + | ||
151 | + static const _strokeLineJoin = <String, PdfLineJoin>{ | ||
152 | + 'miter ': PdfLineJoin.miter, | ||
153 | + 'bevel': PdfLineJoin.bevel, | ||
154 | + 'round': PdfLineJoin.round, | ||
155 | + }; | ||
156 | + | ||
157 | + static const _textAnchors = <String, SvgTextAnchor>{ | ||
158 | + 'start': SvgTextAnchor.start, | ||
159 | + 'middle': SvgTextAnchor.middle, | ||
160 | + 'end': SvgTextAnchor.end, | ||
161 | + }; | ||
162 | + | ||
163 | + final double opacity; | ||
164 | + final SvgColor fill; | ||
165 | + final bool fillEvenOdd; | ||
166 | + final double fillOpacity; | ||
167 | + final SvgColor stroke; | ||
168 | + final double strokeOpacity; | ||
169 | + final SvgNumeric strokeWidth; | ||
170 | + final List<double> strokeDashArray; | ||
171 | + final double strokeDashOffset; | ||
172 | + final PdfLineCap strokeLineCap; | ||
173 | + final PdfLineJoin strokeLineJoin; | ||
174 | + final double strokeMiterLimit; | ||
175 | + final SvgNumeric fontSize; | ||
176 | + final String fontFamily; | ||
177 | + final String fontStyle; | ||
178 | + final String fontWeight; | ||
179 | + final SvgTextAnchor textAnchor; | ||
180 | + final PdfBlendMode blendMode; | ||
181 | + final SvgMaskPath mask; | ||
182 | + | ||
183 | + SvgBrush merge(SvgBrush other) { | ||
184 | + if (other == null) { | ||
185 | + return this; | ||
186 | + } | ||
187 | + | ||
188 | + var _fill = other.fill ?? fill; | ||
189 | + | ||
190 | + if (_fill.inherit) { | ||
191 | + _fill = fill.merge(other.fill); | ||
192 | + } | ||
193 | + | ||
194 | + var _stroke = other.stroke ?? stroke; | ||
195 | + | ||
196 | + if (_stroke.inherit) { | ||
197 | + _stroke = stroke.merge(other.stroke); | ||
198 | + } | ||
199 | + | ||
200 | + return SvgBrush( | ||
201 | + opacity: other.opacity ?? 1.0, | ||
202 | + blendMode: other.blendMode, | ||
203 | + fillOpacity: other.fillOpacity ?? fillOpacity, | ||
204 | + strokeOpacity: other.strokeOpacity ?? strokeOpacity, | ||
205 | + fill: _fill, | ||
206 | + fillEvenOdd: other.fillEvenOdd ?? fillEvenOdd, | ||
207 | + stroke: _stroke, | ||
208 | + strokeWidth: other.strokeWidth ?? strokeWidth, | ||
209 | + strokeDashArray: other.strokeDashArray ?? strokeDashArray, | ||
210 | + strokeDashOffset: other.strokeDashOffset ?? strokeDashOffset, | ||
211 | + fontSize: other.fontSize ?? fontSize, | ||
212 | + fontFamily: other.fontFamily ?? fontFamily, | ||
213 | + fontStyle: other.fontStyle ?? fontStyle, | ||
214 | + fontWeight: other.fontWeight ?? fontWeight, | ||
215 | + textAnchor: other.textAnchor ?? textAnchor, | ||
216 | + strokeLineCap: other.strokeLineCap ?? strokeLineCap, | ||
217 | + strokeLineJoin: other.strokeLineJoin ?? strokeLineJoin, | ||
218 | + strokeMiterLimit: other.strokeMiterLimit ?? strokeMiterLimit, | ||
219 | + mask: other.mask, | ||
220 | + ); | ||
221 | + } | ||
222 | + | ||
223 | + SvgBrush copyWith({ | ||
224 | + double opacity, | ||
225 | + SvgColor fill, | ||
226 | + bool fillEvenOdd, | ||
227 | + double fillOpacity, | ||
228 | + SvgColor stroke, | ||
229 | + double strokeOpacity, | ||
230 | + SvgNumeric strokeWidth, | ||
231 | + List<double> strokeDashArray, | ||
232 | + double strokeDashOffset, | ||
233 | + PdfLineCap strokeLineCap, | ||
234 | + PdfLineJoin strokeLineJoin, | ||
235 | + double strokeMiterLimit, | ||
236 | + SvgNumeric fontSize, | ||
237 | + String fontFamily, | ||
238 | + String fontStyle, | ||
239 | + String fontWeight, | ||
240 | + SvgTextAnchor textAnchor, | ||
241 | + PdfBlendMode blendMode, | ||
242 | + SvgMaskPath mask, | ||
243 | + }) { | ||
244 | + return SvgBrush( | ||
245 | + opacity: opacity ?? this.opacity, | ||
246 | + fill: fill ?? this.fill, | ||
247 | + fillEvenOdd: fillEvenOdd ?? this.fillEvenOdd, | ||
248 | + fillOpacity: fillOpacity ?? this.fillOpacity, | ||
249 | + stroke: stroke ?? this.stroke, | ||
250 | + strokeOpacity: strokeOpacity ?? this.strokeOpacity, | ||
251 | + strokeWidth: strokeWidth ?? this.strokeWidth, | ||
252 | + strokeDashArray: strokeDashArray ?? this.strokeDashArray, | ||
253 | + strokeDashOffset: strokeDashOffset ?? this.strokeDashOffset, | ||
254 | + strokeLineCap: strokeLineCap ?? this.strokeLineCap, | ||
255 | + strokeLineJoin: strokeLineJoin ?? this.strokeLineJoin, | ||
256 | + strokeMiterLimit: strokeMiterLimit ?? this.strokeMiterLimit, | ||
257 | + fontSize: fontSize ?? this.fontSize, | ||
258 | + fontFamily: fontFamily ?? this.fontFamily, | ||
259 | + fontStyle: fontStyle ?? this.fontStyle, | ||
260 | + fontWeight: fontWeight ?? this.fontWeight, | ||
261 | + textAnchor: textAnchor ?? this.textAnchor, | ||
262 | + blendMode: blendMode ?? this.blendMode, | ||
263 | + mask: mask ?? this.mask, | ||
264 | + ); | ||
265 | + } | ||
266 | + | ||
267 | + @override | ||
268 | + String toString() => | ||
269 | + '$runtimeType fill: $fill fillEvenOdd: $fillEvenOdd stroke:$stroke strokeWidth:$strokeWidth strokeDashArray:$strokeDashArray fontSize:$fontSize fontFamily:$fontFamily textAnchor:$textAnchor '; | ||
270 | +} |
pdf/lib/svg/clip_path.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:meta/meta.dart'; | ||
18 | +import 'package:pdf/pdf.dart'; | ||
19 | +import 'package:xml/xml.dart'; | ||
20 | + | ||
21 | +import 'brush.dart'; | ||
22 | +import 'operation.dart'; | ||
23 | +import 'painter.dart'; | ||
24 | + | ||
25 | +@immutable | ||
26 | +class SvgClipPath { | ||
27 | + const SvgClipPath(this.children, this.isEmpty, this.painter); | ||
28 | + | ||
29 | + factory SvgClipPath.fromXml( | ||
30 | + XmlElement element, SvgPainter painter, SvgBrush brush) { | ||
31 | + final clipPathAttr = element.getAttribute('clip-path'); | ||
32 | + if (clipPathAttr == null) { | ||
33 | + return const SvgClipPath(null, true, null); | ||
34 | + } | ||
35 | + | ||
36 | + Iterable<SvgOperation> children; | ||
37 | + | ||
38 | + if (clipPathAttr.startsWith('url(#')) { | ||
39 | + final id = clipPathAttr.substring(5, clipPathAttr.lastIndexOf(')')); | ||
40 | + final clipPath = painter.parser.findById(id); | ||
41 | + if (clipPath != null) { | ||
42 | + children = clipPath.children | ||
43 | + .whereType<XmlElement>() | ||
44 | + .map<SvgOperation>((c) => SvgOperation.fromXml(c, painter, brush)); | ||
45 | + return SvgClipPath(children, false, painter); | ||
46 | + } | ||
47 | + } | ||
48 | + | ||
49 | + return const SvgClipPath(null, true, null); | ||
50 | + } | ||
51 | + | ||
52 | + final Iterable<SvgOperation> children; | ||
53 | + | ||
54 | + final bool isEmpty; | ||
55 | + | ||
56 | + final SvgPainter painter; | ||
57 | + | ||
58 | + bool get isNotEmpty => !isEmpty; | ||
59 | + | ||
60 | + void apply(PdfGraphics canvas) { | ||
61 | + if (isEmpty) { | ||
62 | + return; | ||
63 | + } | ||
64 | + | ||
65 | + for (final child in children) { | ||
66 | + child.draw(canvas); | ||
67 | + } | ||
68 | + canvas.clipPath(); | ||
69 | + } | ||
70 | +} |
pdf/lib/svg/color.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:pdf/pdf.dart'; | ||
18 | + | ||
19 | +import 'colors.dart'; | ||
20 | +import 'gradient.dart'; | ||
21 | +import 'operation.dart'; | ||
22 | +import 'painter.dart'; | ||
23 | +import 'parser.dart'; | ||
24 | + | ||
25 | +class SvgColor { | ||
26 | + const SvgColor({ | ||
27 | + this.color, | ||
28 | + this.opacity, | ||
29 | + this.inherit = false, | ||
30 | + }); | ||
31 | + | ||
32 | + factory SvgColor.fromXml(String color, SvgPainter painter) { | ||
33 | + if (color == null) { | ||
34 | + return inherited; | ||
35 | + } | ||
36 | + | ||
37 | + if (color == 'none') { | ||
38 | + return none; | ||
39 | + } | ||
40 | + | ||
41 | + if (svgColors.containsKey(color)) { | ||
42 | + return SvgColor(color: svgColors[color]); | ||
43 | + } | ||
44 | + | ||
45 | + // handle rgba() colors e.g. rgba(255, 255, 255, 1.0) | ||
46 | + if (color.toLowerCase().startsWith('rgba')) { | ||
47 | + final rgba = SvgParser.splitNumeric( | ||
48 | + color.substring(color.indexOf('(') + 1, color.indexOf(')')), | ||
49 | + null, | ||
50 | + ).toList(); | ||
51 | + | ||
52 | + return SvgColor( | ||
53 | + color: PdfColor( | ||
54 | + rgba[0].colorValue, | ||
55 | + rgba[1].colorValue, | ||
56 | + rgba[2].colorValue, | ||
57 | + rgba[3].value, | ||
58 | + ), | ||
59 | + ); | ||
60 | + } | ||
61 | + | ||
62 | + // handle hsl() colors e.g. hsl(255, 255, 255) | ||
63 | + if (color.toLowerCase().startsWith('hsl')) { | ||
64 | + final hsl = SvgParser.splitNumeric( | ||
65 | + color.substring(color.indexOf('(') + 1, color.indexOf(')')), | ||
66 | + null, | ||
67 | + ).toList(); | ||
68 | + | ||
69 | + return SvgColor( | ||
70 | + color: PdfColorHsl( | ||
71 | + hsl[0].colorValue, | ||
72 | + hsl[1].colorValue, | ||
73 | + hsl[2].colorValue, | ||
74 | + ), | ||
75 | + ); | ||
76 | + } | ||
77 | + | ||
78 | + // handle rgb() colors e.g. rgb(255, 255, 255) | ||
79 | + if (color.toLowerCase().startsWith('rgb')) { | ||
80 | + final rgb = SvgParser.splitNumeric( | ||
81 | + color.substring(color.indexOf('(') + 1, color.indexOf(')')), | ||
82 | + null, | ||
83 | + ).toList(); | ||
84 | + | ||
85 | + return SvgColor( | ||
86 | + color: PdfColor( | ||
87 | + rgb[0].colorValue, | ||
88 | + rgb[1].colorValue, | ||
89 | + rgb[2].colorValue, | ||
90 | + ), | ||
91 | + ); | ||
92 | + } | ||
93 | + | ||
94 | + if (color.toLowerCase().startsWith('url(#')) { | ||
95 | + final gradient = | ||
96 | + painter.parser.findById(color.substring(5, color.indexOf(')'))); | ||
97 | + if (gradient.name.local == 'linearGradient') { | ||
98 | + return SvgLinearGradient.fromXml(gradient, painter); | ||
99 | + } | ||
100 | + if (gradient.name.local == 'radialGradient') { | ||
101 | + return SvgRadialGradient.fromXml(gradient, painter); | ||
102 | + } | ||
103 | + return SvgColor.unknown; | ||
104 | + } | ||
105 | + | ||
106 | + try { | ||
107 | + return SvgColor(color: PdfColor.fromHex(color)); | ||
108 | + } catch (e) { | ||
109 | + print('Unknown color: $color'); | ||
110 | + return SvgColor.unknown; | ||
111 | + } | ||
112 | + } | ||
113 | + | ||
114 | + static const unknown = SvgColor(); | ||
115 | + static const defaultColor = SvgColor(color: PdfColors.black); | ||
116 | + static const none = SvgColor(); | ||
117 | + static const inherited = SvgColor(inherit: true); | ||
118 | + | ||
119 | + final PdfColor color; | ||
120 | + | ||
121 | + final double opacity; | ||
122 | + | ||
123 | + final bool inherit; | ||
124 | + | ||
125 | + bool get isEmpty => color == null; | ||
126 | + | ||
127 | + bool get isNotEmpty => !isEmpty; | ||
128 | + | ||
129 | + SvgColor merge(SvgColor other) { | ||
130 | + return SvgColor( | ||
131 | + color: other.color ?? color, | ||
132 | + ); | ||
133 | + } | ||
134 | + | ||
135 | + void setFillColor(SvgOperation op, PdfGraphics canvas) { | ||
136 | + if (isEmpty) { | ||
137 | + return; | ||
138 | + } | ||
139 | + | ||
140 | + canvas.setFillColor(color); | ||
141 | + } | ||
142 | + | ||
143 | + void setStrokeColor(SvgOperation op, PdfGraphics canvas) { | ||
144 | + if (isEmpty) { | ||
145 | + return; | ||
146 | + } | ||
147 | + | ||
148 | + canvas.setStrokeColor(color); | ||
149 | + } | ||
150 | + | ||
151 | + @override | ||
152 | + String toString() => | ||
153 | + '$runtimeType color: $color inherit:$inherit isEmpty: $isEmpty'; | ||
154 | +} |
pdf/lib/svg/colors.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:pdf/pdf.dart'; | ||
18 | + | ||
19 | +const svgColors = <String, PdfColor>{ | ||
20 | + 'indigo': PdfColor.fromInt(0xff4b0082), | ||
21 | + 'gold': PdfColor.fromInt(0xffffd700), | ||
22 | + 'hotpink': PdfColor.fromInt(0xffff69b4), | ||
23 | + 'firebrick': PdfColor.fromInt(0xffb22222), | ||
24 | + 'indianred': PdfColor.fromInt(0xffcd5c5c), | ||
25 | + 'yellow': PdfColor.fromInt(0xffffff00), | ||
26 | + 'mistyrose': PdfColor.fromInt(0xffffe4e1), | ||
27 | + 'darkolivegreen': PdfColor.fromInt(0xff556b2f), | ||
28 | + 'olive': PdfColor.fromInt(0xff808000), | ||
29 | + 'darkseagreen': PdfColor.fromInt(0xff8fbc8f), | ||
30 | + 'pink': PdfColor.fromInt(0xffffc0cb), | ||
31 | + 'tomato': PdfColor.fromInt(0xffff6347), | ||
32 | + 'lightcoral': PdfColor.fromInt(0xfff08080), | ||
33 | + 'orangered': PdfColor.fromInt(0xffff4500), | ||
34 | + 'navajowhite': PdfColor.fromInt(0xffffdead), | ||
35 | + 'lime': PdfColor.fromInt(0xff00ff00), | ||
36 | + 'palegreen': PdfColor.fromInt(0xff98fb98), | ||
37 | + 'darkslategrey': PdfColor.fromInt(0xff2f4f4f), | ||
38 | + 'greenyellow': PdfColor.fromInt(0xffadff2f), | ||
39 | + 'burlywood': PdfColor.fromInt(0xffdeb887), | ||
40 | + 'seashell': PdfColor.fromInt(0xfffff5ee), | ||
41 | + 'mediumspringgreen': PdfColor.fromInt(0xff00fa9a), | ||
42 | + 'fuchsia': PdfColor.fromInt(0xffff00ff), | ||
43 | + 'papayawhip': PdfColor.fromInt(0xffffefd5), | ||
44 | + 'blanchedalmond': PdfColor.fromInt(0xffffebcd), | ||
45 | + 'transparent': PdfColor.fromInt(0xffffff), | ||
46 | + 'chartreuse': PdfColor.fromInt(0xff7fff00), | ||
47 | + 'dimgray': PdfColor.fromInt(0xff696969), | ||
48 | + 'black': PdfColor.fromInt(0xff000000), | ||
49 | + 'peachpuff': PdfColor.fromInt(0xffffdab9), | ||
50 | + 'springgreen': PdfColor.fromInt(0xff00ff7f), | ||
51 | + 'aquamarine': PdfColor.fromInt(0xff7fffd4), | ||
52 | + 'white': PdfColor.fromInt(0xffffffff), | ||
53 | + 'orange': PdfColor.fromInt(0xffffa500), | ||
54 | + 'lightsalmon': PdfColor.fromInt(0xffffa07a), | ||
55 | + 'darkslategray': PdfColor.fromInt(0xff2f4f4f), | ||
56 | + 'brown': PdfColor.fromInt(0xffa52a2a), | ||
57 | + 'ivory': PdfColor.fromInt(0xfffffff0), | ||
58 | + 'dodgerblue': PdfColor.fromInt(0xff1e90ff), | ||
59 | + 'peru': PdfColor.fromInt(0xffcd853f), | ||
60 | + 'lawngreen': PdfColor.fromInt(0xff7cfc00), | ||
61 | + 'chocolate': PdfColor.fromInt(0xffd2691e), | ||
62 | + 'crimson': PdfColor.fromInt(0xffdc143c), | ||
63 | + 'forestgreen': PdfColor.fromInt(0xff228b22), | ||
64 | + 'darkgrey': PdfColor.fromInt(0xffa9a9a9), | ||
65 | + 'lightseagreen': PdfColor.fromInt(0xff20b2aa), | ||
66 | + 'cyan': PdfColor.fromInt(0xff00ffff), | ||
67 | + 'mintcream': PdfColor.fromInt(0xfff5fffa), | ||
68 | + 'silver': PdfColor.fromInt(0xffc0c0c0), | ||
69 | + 'antiquewhite': PdfColor.fromInt(0xfffaebd7), | ||
70 | + 'mediumorchid': PdfColor.fromInt(0xffba55d3), | ||
71 | + 'skyblue': PdfColor.fromInt(0xff87ceeb), | ||
72 | + 'gray': PdfColor.fromInt(0xff808080), | ||
73 | + 'darkturquoise': PdfColor.fromInt(0xff00ced1), | ||
74 | + 'goldenrod': PdfColor.fromInt(0xffdaa520), | ||
75 | + 'darkgreen': PdfColor.fromInt(0xff006400), | ||
76 | + 'floralwhite': PdfColor.fromInt(0xfffffaf0), | ||
77 | + 'darkviolet': PdfColor.fromInt(0xff9400d3), | ||
78 | + 'darkgray': PdfColor.fromInt(0xffa9a9a9), | ||
79 | + 'moccasin': PdfColor.fromInt(0xffffe4b5), | ||
80 | + 'saddlebrown': PdfColor.fromInt(0xff8b4513), | ||
81 | + 'grey': PdfColor.fromInt(0xff808080), | ||
82 | + 'darkslateblue': PdfColor.fromInt(0xff483d8b), | ||
83 | + 'lightskyblue': PdfColor.fromInt(0xff87cefa), | ||
84 | + 'lightpink': PdfColor.fromInt(0xffffb6c1), | ||
85 | + 'mediumvioletred': PdfColor.fromInt(0xffc71585), | ||
86 | + 'slategrey': PdfColor.fromInt(0xff708090), | ||
87 | + 'red': PdfColor.fromInt(0xffff0000), | ||
88 | + 'deeppink': PdfColor.fromInt(0xffff1493), | ||
89 | + 'limegreen': PdfColor.fromInt(0xff32cd32), | ||
90 | + 'darkmagenta': PdfColor.fromInt(0xff8b008b), | ||
91 | + 'palegoldenrod': PdfColor.fromInt(0xffeee8aa), | ||
92 | + 'plum': PdfColor.fromInt(0xffdda0dd), | ||
93 | + 'turquoise': PdfColor.fromInt(0xff40e0d0), | ||
94 | + 'lightgrey': PdfColor.fromInt(0xffd3d3d3), | ||
95 | + 'lightgoldenrodyellow': PdfColor.fromInt(0xfffafad2), | ||
96 | + 'darkgoldenrod': PdfColor.fromInt(0xffb8860b), | ||
97 | + 'lavender': PdfColor.fromInt(0xffe6e6fa), | ||
98 | + 'maroon': PdfColor.fromInt(0xff800000), | ||
99 | + 'yellowgreen': PdfColor.fromInt(0xff9acd32), | ||
100 | + 'sandybrown': PdfColor.fromInt(0xfff4a460), | ||
101 | + 'thistle': PdfColor.fromInt(0xffd8bfd8), | ||
102 | + 'violet': PdfColor.fromInt(0xffee82ee), | ||
103 | + 'navy': PdfColor.fromInt(0xff000080), | ||
104 | + 'magenta': PdfColor.fromInt(0xffff00ff), | ||
105 | + 'dimgrey': PdfColor.fromInt(0xff696969), | ||
106 | + 'tan': PdfColor.fromInt(0xffd2b48c), | ||
107 | + 'rosybrown': PdfColor.fromInt(0xffbc8f8f), | ||
108 | + 'olivedrab': PdfColor.fromInt(0xff6b8e23), | ||
109 | + 'blue': PdfColor.fromInt(0xff0000ff), | ||
110 | + 'lightblue': PdfColor.fromInt(0xffadd8e6), | ||
111 | + 'ghostwhite': PdfColor.fromInt(0xfff8f8ff), | ||
112 | + 'honeydew': PdfColor.fromInt(0xfff0fff0), | ||
113 | + 'cornflowerblue': PdfColor.fromInt(0xff6495ed), | ||
114 | + 'slateblue': PdfColor.fromInt(0xff6a5acd), | ||
115 | + 'linen': PdfColor.fromInt(0xfffaf0e6), | ||
116 | + 'darkblue': PdfColor.fromInt(0xff00008b), | ||
117 | + 'powderblue': PdfColor.fromInt(0xffb0e0e6), | ||
118 | + 'seagreen': PdfColor.fromInt(0xff2e8b57), | ||
119 | + 'darkkhaki': PdfColor.fromInt(0xffbdb76b), | ||
120 | + 'snow': PdfColor.fromInt(0xfffffafa), | ||
121 | + 'sienna': PdfColor.fromInt(0xffa0522d), | ||
122 | + 'mediumblue': PdfColor.fromInt(0xff0000cd), | ||
123 | + 'royalblue': PdfColor.fromInt(0xff4169e1), | ||
124 | + 'lightcyan': PdfColor.fromInt(0xffe0ffff), | ||
125 | + 'green': PdfColor.fromInt(0xff008000), | ||
126 | + 'mediumpurple': PdfColor.fromInt(0xff9370db), | ||
127 | + 'midnightblue': PdfColor.fromInt(0xff191970), | ||
128 | + 'cornsilk': PdfColor.fromInt(0xfffff8dc), | ||
129 | + 'paleturquoise': PdfColor.fromInt(0xffafeeee), | ||
130 | + 'bisque': PdfColor.fromInt(0xffffe4c4), | ||
131 | + 'slategray': PdfColor.fromInt(0xff708090), | ||
132 | + 'darkcyan': PdfColor.fromInt(0xff008b8b), | ||
133 | + 'khaki': PdfColor.fromInt(0xfff0e68c), | ||
134 | + 'wheat': PdfColor.fromInt(0xfff5deb3), | ||
135 | + 'teal': PdfColor.fromInt(0xff008080), | ||
136 | + 'darkorchid': PdfColor.fromInt(0xff9932cc), | ||
137 | + 'deepskyblue': PdfColor.fromInt(0xff00bfff), | ||
138 | + 'salmon': PdfColor.fromInt(0xfffa8072), | ||
139 | + 'darkred': PdfColor.fromInt(0xff8b0000), | ||
140 | + 'steelblue': PdfColor.fromInt(0xff4682b4), | ||
141 | + 'palevioletred': PdfColor.fromInt(0xffdb7093), | ||
142 | + 'lightslategray': PdfColor.fromInt(0xff778899), | ||
143 | + 'aliceblue': PdfColor.fromInt(0xfff0f8ff), | ||
144 | + 'lightslategrey': PdfColor.fromInt(0xff778899), | ||
145 | + 'lightgreen': PdfColor.fromInt(0xff90ee90), | ||
146 | + 'orchid': PdfColor.fromInt(0xffda70d6), | ||
147 | + 'gainsboro': PdfColor.fromInt(0xffdcdcdc), | ||
148 | + 'mediumseagreen': PdfColor.fromInt(0xff3cb371), | ||
149 | + 'lightgray': PdfColor.fromInt(0xffd3d3d3), | ||
150 | + 'mediumturquoise': PdfColor.fromInt(0xff48d1cc), | ||
151 | + 'lemonchiffon': PdfColor.fromInt(0xfffffacd), | ||
152 | + 'cadetblue': PdfColor.fromInt(0xff5f9ea0), | ||
153 | + 'lightyellow': PdfColor.fromInt(0xffffffe0), | ||
154 | + 'lavenderblush': PdfColor.fromInt(0xfffff0f5), | ||
155 | + 'coral': PdfColor.fromInt(0xffff7f50), | ||
156 | + 'purple': PdfColor.fromInt(0xff800080), | ||
157 | + 'aqua': PdfColor.fromInt(0xff00ffff), | ||
158 | + 'whitesmoke': PdfColor.fromInt(0xfff5f5f5), | ||
159 | + 'mediumslateblue': PdfColor.fromInt(0xff7b68ee), | ||
160 | + 'darkorange': PdfColor.fromInt(0xffff8c00), | ||
161 | + 'mediumaquamarine': PdfColor.fromInt(0xff66cdaa), | ||
162 | + 'darksalmon': PdfColor.fromInt(0xffe9967a), | ||
163 | + 'beige': PdfColor.fromInt(0xfff5f5dc), | ||
164 | + 'blueviolet': PdfColor.fromInt(0xff8a2be2), | ||
165 | + 'azure': PdfColor.fromInt(0xfff0ffff), | ||
166 | + 'lightsteelblue': PdfColor.fromInt(0xffb0c4de), | ||
167 | + 'oldlace': PdfColor.fromInt(0xfffdf5e6), | ||
168 | +}; |
pdf/lib/svg/gradient.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:pdf/pdf.dart'; | ||
18 | +import 'package:xml/xml.dart'; | ||
19 | + | ||
20 | +import 'color.dart'; | ||
21 | +import 'operation.dart'; | ||
22 | +import 'painter.dart'; | ||
23 | +import 'parser.dart'; | ||
24 | +import 'transform.dart'; | ||
25 | + | ||
26 | +enum GradientUnits { | ||
27 | + objectBoundingBox, | ||
28 | + userSpaceOnUse, | ||
29 | +} | ||
30 | + | ||
31 | +abstract class SvgGradient extends SvgColor { | ||
32 | + const SvgGradient( | ||
33 | + this.gradientUnits, | ||
34 | + this.transform, | ||
35 | + this.colors, | ||
36 | + this.stops, | ||
37 | + this.opacityList, | ||
38 | + ) : assert(colors.length == stops.length), | ||
39 | + assert(stops.length == opacityList.length), | ||
40 | + super(); | ||
41 | + | ||
42 | + final GradientUnits gradientUnits; | ||
43 | + | ||
44 | + final SvgTransform transform; | ||
45 | + | ||
46 | + final List<PdfColor> colors; | ||
47 | + | ||
48 | + final List<double> stops; | ||
49 | + | ||
50 | + final List<double> opacityList; | ||
51 | + | ||
52 | + @override | ||
53 | + bool get isEmpty => colors.isEmpty; | ||
54 | + | ||
55 | + PdfPattern buildGradient( | ||
56 | + SvgOperation op, PdfGraphics canvas, List<PdfColor> colors); | ||
57 | + | ||
58 | + @override | ||
59 | + void setFillColor(SvgOperation op, PdfGraphics canvas) { | ||
60 | + if (isEmpty) { | ||
61 | + return; | ||
62 | + } | ||
63 | + | ||
64 | + canvas.setFillPattern(buildGradient(op, canvas, colors)); | ||
65 | + | ||
66 | + if (opacityList.any((o) => o < 1)) { | ||
67 | + final mask = PdfSoftMask( | ||
68 | + op.painter.document, | ||
69 | + boundingBox: op.painter.boundingBox, | ||
70 | + ); | ||
71 | + canvas.setGraphicState( | ||
72 | + PdfGraphicState( | ||
73 | + softMask: mask, | ||
74 | + ), | ||
75 | + ); | ||
76 | + final maskCanvas = mask.getGraphics(); | ||
77 | + maskCanvas.drawBox(op.boundingBox()); | ||
78 | + maskCanvas.setFillPattern( | ||
79 | + buildGradient( | ||
80 | + op, | ||
81 | + maskCanvas, | ||
82 | + opacityList.map<PdfColor>((o) => PdfColor(o, o, o)).toList(), | ||
83 | + ), | ||
84 | + ); | ||
85 | + maskCanvas.fillPath(); | ||
86 | + canvas.setFillPattern(buildGradient(op, canvas, colors)); | ||
87 | + } | ||
88 | + } | ||
89 | + | ||
90 | + @override | ||
91 | + void setStrokeColor(SvgOperation op, PdfGraphics canvas) { | ||
92 | + if (isEmpty) { | ||
93 | + return; | ||
94 | + } | ||
95 | + | ||
96 | + canvas.setStrokePattern(buildGradient(op, canvas, colors)); | ||
97 | + } | ||
98 | +} | ||
99 | + | ||
100 | +class SvgLinearGradient extends SvgGradient { | ||
101 | + const SvgLinearGradient( | ||
102 | + GradientUnits gradientUnits, | ||
103 | + this.x1, | ||
104 | + this.y1, | ||
105 | + this.x2, | ||
106 | + this.y2, | ||
107 | + SvgTransform transform, | ||
108 | + List<PdfColor> colors, | ||
109 | + List<double> stops, | ||
110 | + List<double> opacityList) | ||
111 | + : super(gradientUnits, transform, colors, stops, opacityList); | ||
112 | + | ||
113 | + factory SvgLinearGradient.fromXml(XmlElement element, SvgPainter painter) { | ||
114 | + final x1 = SvgParser.getNumeric(element, 'x1', null)?.sizeValue; | ||
115 | + final y1 = SvgParser.getNumeric(element, 'y1', null)?.sizeValue; | ||
116 | + final x2 = SvgParser.getNumeric(element, 'x2', null)?.sizeValue; | ||
117 | + final y2 = SvgParser.getNumeric(element, 'y2', null)?.sizeValue; | ||
118 | + | ||
119 | + final colors = <PdfColor>[]; | ||
120 | + final stops = <double>[]; | ||
121 | + final opacityList = <double>[]; | ||
122 | + | ||
123 | + for (final child in element.children | ||
124 | + .whereType<XmlElement>() | ||
125 | + .where((e) => e.name.local == 'stop')) { | ||
126 | + SvgParser.convertStyle(child); | ||
127 | + final color = SvgColor.fromXml( | ||
128 | + child.getAttribute('stop-color') ?? 'black', painter); | ||
129 | + final opacity = | ||
130 | + SvgParser.getDouble(child, 'stop-opacity', defaultValue: 1); | ||
131 | + final stop = SvgParser.getNumeric(child, 'offset', null, defaultValue: 0) | ||
132 | + .sizeValue; | ||
133 | + colors.add(color.color); | ||
134 | + stops.add(stop); | ||
135 | + opacityList.add(opacity); | ||
136 | + } | ||
137 | + | ||
138 | + GradientUnits gradientUnits; | ||
139 | + switch (element.getAttribute('gradientUnits')) { | ||
140 | + case 'userSpaceOnUse': | ||
141 | + gradientUnits = GradientUnits.userSpaceOnUse; | ||
142 | + break; | ||
143 | + case 'objectBoundingBox': | ||
144 | + gradientUnits = GradientUnits.objectBoundingBox; | ||
145 | + break; | ||
146 | + } | ||
147 | + | ||
148 | + final result = SvgLinearGradient( | ||
149 | + gradientUnits, | ||
150 | + x1, | ||
151 | + y1, | ||
152 | + x2, | ||
153 | + y2, | ||
154 | + SvgTransform.fromString(element.getAttribute('gradientTransform')), | ||
155 | + colors, | ||
156 | + stops, | ||
157 | + opacityList, | ||
158 | + ); | ||
159 | + | ||
160 | + SvgLinearGradient href; | ||
161 | + final hrefAttr = element.getAttribute('href') ?? | ||
162 | + element.getAttribute('href', namespace: 'http://www.w3.org/1999/xlink'); | ||
163 | + | ||
164 | + if (hrefAttr != null) { | ||
165 | + final hrefElement = painter.parser.findById(hrefAttr.substring(1)); | ||
166 | + if (hrefElement != null) { | ||
167 | + href = SvgLinearGradient.fromXml(hrefElement, painter); | ||
168 | + return href.mergeWith(result); | ||
169 | + } | ||
170 | + } | ||
171 | + | ||
172 | + return result; | ||
173 | + } | ||
174 | + | ||
175 | + final double x1; | ||
176 | + final double y1; | ||
177 | + final double x2; | ||
178 | + final double y2; | ||
179 | + | ||
180 | + SvgLinearGradient mergeWith(SvgLinearGradient other) { | ||
181 | + return SvgLinearGradient( | ||
182 | + other.gradientUnits ?? gradientUnits, | ||
183 | + other.x1 ?? x1, | ||
184 | + other.y1 ?? y1, | ||
185 | + other.x2 ?? x2, | ||
186 | + other.y2 ?? y2, | ||
187 | + other.transform.isNotEmpty ? other.transform : transform, | ||
188 | + other.colors.isNotEmpty ? other.colors : colors, | ||
189 | + other.stops.isNotEmpty ? other.stops : stops, | ||
190 | + other.opacityList.isNotEmpty ? other.opacityList : opacityList, | ||
191 | + ); | ||
192 | + } | ||
193 | + | ||
194 | + @override | ||
195 | + PdfPattern buildGradient( | ||
196 | + SvgOperation op, PdfGraphics canvas, List<PdfColor> colors) { | ||
197 | + final mat = canvas.getTransform(); | ||
198 | + | ||
199 | + if (gradientUnits != GradientUnits.userSpaceOnUse) { | ||
200 | + final bb = op.boundingBox(); | ||
201 | + mat | ||
202 | + ..translate(bb.x, bb.y) | ||
203 | + ..scale(bb.width, bb.height); | ||
204 | + } | ||
205 | + | ||
206 | + if (transform.isNotEmpty) { | ||
207 | + mat.multiply(transform.matrix); | ||
208 | + } | ||
209 | + | ||
210 | + return PdfShadingPattern( | ||
211 | + op.painter.document, | ||
212 | + shading: PdfShading( | ||
213 | + op.painter.document, | ||
214 | + shadingType: PdfShadingType.axial, | ||
215 | + function: PdfBaseFunction.colorsAndStops( | ||
216 | + op.painter.document, | ||
217 | + colors, | ||
218 | + stops, | ||
219 | + ), | ||
220 | + start: PdfPoint(x1 ?? 0, y1 ?? 0), | ||
221 | + end: PdfPoint(x2 ?? 1, y2 ?? 0), | ||
222 | + extendStart: true, | ||
223 | + extendEnd: true, | ||
224 | + ), | ||
225 | + matrix: mat, | ||
226 | + ); | ||
227 | + } | ||
228 | + | ||
229 | + @override | ||
230 | + String toString() => | ||
231 | + '$runtimeType userSpace:$gradientUnits x1:$x1 y1:$y1 x2:$x2 y2:$y2 colors:$colors stops:$stops opacityList:$opacityList'; | ||
232 | +} | ||
233 | + | ||
234 | +class SvgRadialGradient extends SvgGradient { | ||
235 | + const SvgRadialGradient( | ||
236 | + GradientUnits gradientUnits, | ||
237 | + this.r, | ||
238 | + this.cx, | ||
239 | + this.cy, | ||
240 | + this.fr, | ||
241 | + this.fx, | ||
242 | + this.fy, | ||
243 | + SvgTransform transform, | ||
244 | + List<PdfColor> colors, | ||
245 | + List<double> stops, | ||
246 | + List<double> opacityList, | ||
247 | + ) : super(gradientUnits, transform, colors, stops, opacityList); | ||
248 | + | ||
249 | + factory SvgRadialGradient.fromXml(XmlElement element, SvgPainter painter) { | ||
250 | + final r = | ||
251 | + SvgParser.getNumeric(element, 'r', null, defaultValue: .5).sizeValue; | ||
252 | + final cx = | ||
253 | + SvgParser.getNumeric(element, 'cx', null, defaultValue: .5).sizeValue; | ||
254 | + final cy = | ||
255 | + SvgParser.getNumeric(element, 'cy', null, defaultValue: .5).sizeValue; | ||
256 | + final fr = | ||
257 | + SvgParser.getNumeric(element, 'fr', null, defaultValue: 0).sizeValue; | ||
258 | + final fx = | ||
259 | + SvgParser.getNumeric(element, 'fx', null, defaultValue: cx).sizeValue; | ||
260 | + final fy = | ||
261 | + SvgParser.getNumeric(element, 'fy', null, defaultValue: cy).sizeValue; | ||
262 | + | ||
263 | + final colors = <PdfColor>[]; | ||
264 | + final stops = <double>[]; | ||
265 | + final opacityList = <double>[]; | ||
266 | + | ||
267 | + for (final child in element.children | ||
268 | + .whereType<XmlElement>() | ||
269 | + .where((e) => e.name.local == 'stop')) { | ||
270 | + SvgParser.convertStyle(child); | ||
271 | + final color = SvgColor.fromXml( | ||
272 | + child.getAttribute('stop-color') ?? 'black', painter); | ||
273 | + final opacity = | ||
274 | + SvgParser.getDouble(child, 'stop-opacity', defaultValue: 1); | ||
275 | + final stop = SvgParser.getNumeric(child, 'offset', null, defaultValue: 0) | ||
276 | + .sizeValue; | ||
277 | + colors.add(color.color); | ||
278 | + stops.add(stop); | ||
279 | + opacityList.add(opacity); | ||
280 | + } | ||
281 | + | ||
282 | + GradientUnits gradientUnits; | ||
283 | + switch (element.getAttribute('gradientUnits')) { | ||
284 | + case 'userSpaceOnUse': | ||
285 | + gradientUnits = GradientUnits.userSpaceOnUse; | ||
286 | + break; | ||
287 | + case 'objectBoundingBox': | ||
288 | + gradientUnits = GradientUnits.objectBoundingBox; | ||
289 | + break; | ||
290 | + } | ||
291 | + | ||
292 | + final result = SvgRadialGradient( | ||
293 | + gradientUnits, | ||
294 | + r, | ||
295 | + cx, | ||
296 | + cy, | ||
297 | + fr, | ||
298 | + fx, | ||
299 | + fy, | ||
300 | + SvgTransform.fromString(element.getAttribute('gradientTransform')), | ||
301 | + colors, | ||
302 | + stops, | ||
303 | + opacityList); | ||
304 | + | ||
305 | + SvgRadialGradient href; | ||
306 | + final hrefAttr = element.getAttribute('href') ?? | ||
307 | + element.getAttribute('href', namespace: 'http://www.w3.org/1999/xlink'); | ||
308 | + | ||
309 | + if (hrefAttr != null) { | ||
310 | + final hrefElement = painter.parser.findById(hrefAttr.substring(1)); | ||
311 | + if (hrefElement != null) { | ||
312 | + href = SvgRadialGradient.fromXml(hrefElement, painter); | ||
313 | + return href.mergeWith(result); | ||
314 | + } | ||
315 | + } | ||
316 | + | ||
317 | + return result; | ||
318 | + } | ||
319 | + | ||
320 | + final double r; | ||
321 | + final double cx; | ||
322 | + final double cy; | ||
323 | + final double fr; | ||
324 | + final double fx; | ||
325 | + final double fy; | ||
326 | + | ||
327 | + SvgRadialGradient mergeWith(SvgRadialGradient other) { | ||
328 | + return SvgRadialGradient( | ||
329 | + other.gradientUnits ?? gradientUnits, | ||
330 | + other.r ?? r, | ||
331 | + other.cx ?? cx, | ||
332 | + other.cy ?? cy, | ||
333 | + other.fr ?? fr, | ||
334 | + other.fx ?? fx, | ||
335 | + other.fy ?? fy, | ||
336 | + other.transform.isNotEmpty ? other.transform : transform, | ||
337 | + other.colors.isNotEmpty ? other.colors : colors, | ||
338 | + other.stops.isNotEmpty ? other.stops : stops, | ||
339 | + other.opacityList.isNotEmpty ? other.opacityList : opacityList, | ||
340 | + ); | ||
341 | + } | ||
342 | + | ||
343 | + @override | ||
344 | + PdfPattern buildGradient( | ||
345 | + SvgOperation op, PdfGraphics canvas, List<PdfColor> colors) { | ||
346 | + final mat = canvas.getTransform(); | ||
347 | + | ||
348 | + if (gradientUnits != GradientUnits.userSpaceOnUse) { | ||
349 | + final bb = op.boundingBox(); | ||
350 | + mat | ||
351 | + ..translate(bb.x, bb.y) | ||
352 | + ..scale(bb.width, bb.height); | ||
353 | + } | ||
354 | + | ||
355 | + if (transform.isNotEmpty) { | ||
356 | + mat.multiply(transform.matrix); | ||
357 | + } | ||
358 | + | ||
359 | + return PdfShadingPattern( | ||
360 | + op.painter.document, | ||
361 | + shading: PdfShading( | ||
362 | + op.painter.document, | ||
363 | + shadingType: PdfShadingType.radial, | ||
364 | + function: PdfBaseFunction.colorsAndStops( | ||
365 | + op.painter.document, | ||
366 | + colors, | ||
367 | + stops, | ||
368 | + ), | ||
369 | + start: PdfPoint(fx ?? cx ?? .5, fy ?? cy ?? .5), | ||
370 | + end: PdfPoint(cx ?? .5, cy ?? .5), | ||
371 | + radius0: fr ?? 0, | ||
372 | + radius1: r ?? .5, | ||
373 | + extendStart: true, | ||
374 | + extendEnd: true, | ||
375 | + ), | ||
376 | + matrix: mat, | ||
377 | + ); | ||
378 | + } | ||
379 | + | ||
380 | + @override | ||
381 | + String toString() => | ||
382 | + '$runtimeType userSpace:$gradientUnits cx:$cx cy:$cy r:$r fx:$fx fy:$fy fr:$fr colors:$colors stops:$stops opacityList:$opacityList'; | ||
383 | +} |
pdf/lib/svg/group.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'dart:math'; | ||
18 | + | ||
19 | +import 'package:pdf/pdf.dart'; | ||
20 | +import 'package:xml/xml.dart'; | ||
21 | + | ||
22 | +import 'brush.dart'; | ||
23 | +import 'clip_path.dart'; | ||
24 | +import 'operation.dart'; | ||
25 | +import 'painter.dart'; | ||
26 | +import 'transform.dart'; | ||
27 | + | ||
28 | +class SvgGroup extends SvgOperation { | ||
29 | + SvgGroup( | ||
30 | + this.children, | ||
31 | + SvgBrush brush, | ||
32 | + SvgClipPath clip, | ||
33 | + SvgTransform transform, | ||
34 | + SvgPainter painter, | ||
35 | + ) : super(brush, clip, transform, painter); | ||
36 | + | ||
37 | + factory SvgGroup.fromXml( | ||
38 | + XmlElement element, SvgPainter painter, SvgBrush brush) { | ||
39 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
40 | + | ||
41 | + final children = element.children | ||
42 | + .whereType<XmlElement>() | ||
43 | + .where((element) => element.name.local != 'symbol') | ||
44 | + .map<SvgOperation>( | ||
45 | + (child) => SvgOperation.fromXml(child, painter, _brush)) | ||
46 | + .where((element) => element != null); | ||
47 | + | ||
48 | + return SvgGroup( | ||
49 | + children, | ||
50 | + _brush, | ||
51 | + SvgClipPath.fromXml(element, painter, _brush), | ||
52 | + SvgTransform.fromXml(element), | ||
53 | + painter, | ||
54 | + ); | ||
55 | + } | ||
56 | + | ||
57 | + final Iterable<SvgOperation> children; | ||
58 | + | ||
59 | + @override | ||
60 | + void paintShape(PdfGraphics canvas) { | ||
61 | + for (final child in children) { | ||
62 | + child.paint(canvas); | ||
63 | + } | ||
64 | + } | ||
65 | + | ||
66 | + @override | ||
67 | + void drawShape(PdfGraphics canvas) { | ||
68 | + for (final child in children) { | ||
69 | + child.draw(canvas); | ||
70 | + } | ||
71 | + } | ||
72 | + | ||
73 | + @override | ||
74 | + PdfRect boundingBox() { | ||
75 | + var x = double.infinity, y = double.infinity, w = 0.0, h = 0.0; | ||
76 | + for (final child in children) { | ||
77 | + final b = child.boundingBox(); | ||
78 | + x = min(b.x, x); | ||
79 | + y = min(b.y, y); | ||
80 | + w = max(b.width, w); | ||
81 | + h = max(b.height, w); | ||
82 | + } | ||
83 | + | ||
84 | + return PdfRect(x, y, w, h); | ||
85 | + } | ||
86 | +} |
pdf/lib/svg/image.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'dart:convert'; | ||
18 | + | ||
19 | +import 'package:image/image.dart' as im; | ||
20 | +import 'package:pdf/pdf.dart'; | ||
21 | +import 'package:vector_math/vector_math_64.dart'; | ||
22 | +import 'package:xml/xml.dart'; | ||
23 | + | ||
24 | +import 'brush.dart'; | ||
25 | +import 'clip_path.dart'; | ||
26 | +import 'operation.dart'; | ||
27 | +import 'painter.dart'; | ||
28 | +import 'parser.dart'; | ||
29 | +import 'transform.dart'; | ||
30 | + | ||
31 | +class SvgImg extends SvgOperation { | ||
32 | + SvgImg( | ||
33 | + this.x, | ||
34 | + this.y, | ||
35 | + this.width, | ||
36 | + this.height, | ||
37 | + this.image, | ||
38 | + SvgBrush brush, | ||
39 | + SvgClipPath clip, | ||
40 | + SvgTransform transform, | ||
41 | + SvgPainter painter, | ||
42 | + ) : super(brush, clip, transform, painter); | ||
43 | + | ||
44 | + factory SvgImg.fromXml( | ||
45 | + XmlElement element, | ||
46 | + SvgPainter painter, | ||
47 | + SvgBrush brush, | ||
48 | + ) { | ||
49 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
50 | + | ||
51 | + final width = | ||
52 | + SvgParser.getNumeric(element, 'width', _brush, defaultValue: 0) | ||
53 | + .sizeValue; | ||
54 | + final height = | ||
55 | + SvgParser.getNumeric(element, 'height', _brush, defaultValue: 0) | ||
56 | + .sizeValue; | ||
57 | + final x = | ||
58 | + SvgParser.getNumeric(element, 'x', _brush, defaultValue: 0).sizeValue; | ||
59 | + final y = | ||
60 | + SvgParser.getNumeric(element, 'y', _brush, defaultValue: 0).sizeValue; | ||
61 | + | ||
62 | + PdfImage image; | ||
63 | + | ||
64 | + final hrefAttr = element.getAttribute('href') ?? | ||
65 | + element.getAttribute('href', namespace: 'http://www.w3.org/1999/xlink'); | ||
66 | + | ||
67 | + if (hrefAttr != null) { | ||
68 | + if (hrefAttr.startsWith('data:')) { | ||
69 | + final px = hrefAttr.substring(hrefAttr.indexOf(';') + 1); | ||
70 | + if (px.startsWith('base64,')) { | ||
71 | + final b = px.substring(7).replaceAll(RegExp(r'\s'), ''); | ||
72 | + final bytes = base64.decode(b); | ||
73 | + | ||
74 | + final img = im.decodeImage(bytes); | ||
75 | + image = PdfImage( | ||
76 | + painter.document, | ||
77 | + image: img.data.buffer.asUint8List(), | ||
78 | + width: img.width, | ||
79 | + height: img.height, | ||
80 | + ); | ||
81 | + } | ||
82 | + } | ||
83 | + } | ||
84 | + | ||
85 | + return SvgImg( | ||
86 | + x, | ||
87 | + y, | ||
88 | + width, | ||
89 | + height, | ||
90 | + image, | ||
91 | + _brush, | ||
92 | + SvgClipPath.fromXml(element, painter, _brush), | ||
93 | + SvgTransform.fromXml(element), | ||
94 | + painter, | ||
95 | + ); | ||
96 | + } | ||
97 | + | ||
98 | + final double x; | ||
99 | + | ||
100 | + final double y; | ||
101 | + | ||
102 | + final double width; | ||
103 | + | ||
104 | + final double height; | ||
105 | + | ||
106 | + final PdfImage image; | ||
107 | + | ||
108 | + @override | ||
109 | + void paintShape(PdfGraphics canvas) { | ||
110 | + if (image == null) { | ||
111 | + return; | ||
112 | + } | ||
113 | + | ||
114 | + final sx = width / image.width; | ||
115 | + final sy = height / image.height; | ||
116 | + | ||
117 | + canvas | ||
118 | + ..setTransform( | ||
119 | + Matrix4.identity() | ||
120 | + ..translate(x, y + height, 0) | ||
121 | + ..scale(sx, -sy), | ||
122 | + ) | ||
123 | + ..drawImage(image, 0, 0); | ||
124 | + } | ||
125 | + | ||
126 | + @override | ||
127 | + void drawShape(PdfGraphics canvas) {} | ||
128 | + | ||
129 | + @override | ||
130 | + PdfRect boundingBox() => PdfRect(x, y, width, height); | ||
131 | +} |
pdf/lib/svg/mask_path.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:meta/meta.dart'; | ||
18 | +import 'package:pdf/pdf.dart'; | ||
19 | +import 'package:xml/xml.dart'; | ||
20 | + | ||
21 | +import 'brush.dart'; | ||
22 | +import 'operation.dart'; | ||
23 | +import 'painter.dart'; | ||
24 | + | ||
25 | +@immutable | ||
26 | +class SvgMaskPath { | ||
27 | + const SvgMaskPath(this.children, this.painter); | ||
28 | + | ||
29 | + static SvgMaskPath fromXml( | ||
30 | + XmlElement element, SvgPainter painter, SvgBrush brush) { | ||
31 | + final maskPathAttr = element.getAttribute('mask'); | ||
32 | + if (maskPathAttr == null) { | ||
33 | + return null; | ||
34 | + } | ||
35 | + | ||
36 | + Iterable<SvgOperation> children; | ||
37 | + | ||
38 | + if (maskPathAttr.startsWith('url(#')) { | ||
39 | + final id = maskPathAttr.substring(5, maskPathAttr.lastIndexOf(')')); | ||
40 | + final maskPath = painter.parser.findById(id); | ||
41 | + if (maskPath != null) { | ||
42 | + final maskBrush = SvgBrush.fromXml(maskPath, brush, painter); | ||
43 | + children = maskPath.children.whereType<XmlElement>().map<SvgOperation>( | ||
44 | + (c) => SvgOperation.fromXml(c, painter, maskBrush)); | ||
45 | + return SvgMaskPath(children, painter); | ||
46 | + } | ||
47 | + } | ||
48 | + | ||
49 | + return null; | ||
50 | + } | ||
51 | + | ||
52 | + final Iterable<SvgOperation> children; | ||
53 | + | ||
54 | + final SvgPainter painter; | ||
55 | + | ||
56 | + void apply(PdfGraphics canvas) { | ||
57 | + final mask = PdfSoftMask( | ||
58 | + painter.document, | ||
59 | + boundingBox: painter.boundingBox, | ||
60 | + ); | ||
61 | + | ||
62 | + final maskCanvas = mask.getGraphics(); | ||
63 | + // maskCanvas.setTransform(canvas.getTransform()); | ||
64 | + | ||
65 | + for (final child in children) { | ||
66 | + child.paint(maskCanvas); | ||
67 | + } | ||
68 | + | ||
69 | + canvas.setGraphicState(PdfGraphicState(softMask: mask)); | ||
70 | + } | ||
71 | +} |
pdf/lib/svg/operation.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:meta/meta.dart'; | ||
18 | +import 'package:pdf/pdf.dart'; | ||
19 | +import 'package:xml/xml.dart'; | ||
20 | + | ||
21 | +import 'brush.dart'; | ||
22 | +import 'clip_path.dart'; | ||
23 | +import 'group.dart'; | ||
24 | +import 'image.dart'; | ||
25 | +import 'painter.dart'; | ||
26 | +import 'path.dart'; | ||
27 | +import 'symbol.dart'; | ||
28 | +import 'text.dart'; | ||
29 | +import 'transform.dart'; | ||
30 | +import 'use.dart'; | ||
31 | + | ||
32 | +abstract class SvgOperation { | ||
33 | + SvgOperation(this.brush, this.clip, this.transform, this.painter); | ||
34 | + | ||
35 | + static SvgOperation fromXml( | ||
36 | + XmlElement element, SvgPainter painter, SvgBrush brush) { | ||
37 | + if (element.getAttribute('visibility') == 'hidden') { | ||
38 | + return null; | ||
39 | + } | ||
40 | + | ||
41 | + if (element.getAttribute('display') == 'none') { | ||
42 | + return null; | ||
43 | + } | ||
44 | + | ||
45 | + switch (element.name.local) { | ||
46 | + case 'circle': | ||
47 | + return SvgPath.fromCircleXml(element, painter, brush); | ||
48 | + case 'ellipse': | ||
49 | + return SvgPath.fromEllipseXml(element, painter, brush); | ||
50 | + case 'g': | ||
51 | + return SvgGroup.fromXml(element, painter, brush); | ||
52 | + case 'image': | ||
53 | + return SvgImg.fromXml(element, painter, brush); | ||
54 | + case 'line': | ||
55 | + return SvgPath.fromLineXml(element, painter, brush); | ||
56 | + case 'path': | ||
57 | + return SvgPath.fromXml(element, painter, brush); | ||
58 | + case 'polygon': | ||
59 | + return SvgPath.fromPolygonXml(element, painter, brush); | ||
60 | + case 'polyline': | ||
61 | + return SvgPath.fromPolylineXml(element, painter, brush); | ||
62 | + case 'rect': | ||
63 | + return SvgPath.fromRectXml(element, painter, brush); | ||
64 | + case 'symbol': | ||
65 | + return SvgSymbol.fromXml(element, painter, brush); | ||
66 | + case 'text': | ||
67 | + return SvgText.fromXml(element, painter, brush); | ||
68 | + case 'use': | ||
69 | + return SvgUse.fromXml(element, painter, brush); | ||
70 | + } | ||
71 | + | ||
72 | + return null; | ||
73 | + } | ||
74 | + | ||
75 | + final SvgBrush brush; | ||
76 | + | ||
77 | + final SvgClipPath clip; | ||
78 | + | ||
79 | + final SvgTransform transform; | ||
80 | + | ||
81 | + final SvgPainter painter; | ||
82 | + | ||
83 | + void paint(PdfGraphics canvas) { | ||
84 | + canvas.saveContext(); | ||
85 | + clip.apply(canvas); | ||
86 | + if (transform.isNotEmpty) { | ||
87 | + canvas.setTransform(transform.matrix); | ||
88 | + } | ||
89 | + if (brush.opacity < 1.0 || brush.blendMode != null) { | ||
90 | + canvas.setGraphicState(PdfGraphicState( | ||
91 | + opacity: brush.opacity == 1 ? null : brush.opacity, | ||
92 | + blendMode: brush.blendMode, | ||
93 | + )); | ||
94 | + } | ||
95 | + if (brush.mask != null) { | ||
96 | + brush.mask.apply(canvas); | ||
97 | + } | ||
98 | + paintShape(canvas); | ||
99 | + canvas.restoreContext(); | ||
100 | + } | ||
101 | + | ||
102 | + @protected | ||
103 | + void paintShape(PdfGraphics canvas); | ||
104 | + | ||
105 | + void draw(PdfGraphics canvas) { | ||
106 | + canvas.saveContext(); | ||
107 | + if (transform.isNotEmpty) { | ||
108 | + canvas.setTransform(transform.matrix); | ||
109 | + } | ||
110 | + drawShape(canvas); | ||
111 | + canvas.restoreContext(); | ||
112 | + } | ||
113 | + | ||
114 | + @protected | ||
115 | + void drawShape(PdfGraphics canvas); | ||
116 | + | ||
117 | + PdfRect boundingBox(); | ||
118 | +} |
pdf/lib/svg/painter.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:pdf/pdf.dart'; | ||
18 | +import 'package:pdf/svg/parser.dart'; | ||
19 | +import 'package:pdf/widgets.dart'; | ||
20 | + | ||
21 | +import 'brush.dart'; | ||
22 | +import 'group.dart'; | ||
23 | + | ||
24 | +class SvgPainter { | ||
25 | + SvgPainter( | ||
26 | + this.parser, | ||
27 | + this._canvas, | ||
28 | + this.document, | ||
29 | + this.boundingBox, | ||
30 | + ); | ||
31 | + | ||
32 | + final SvgParser parser; | ||
33 | + | ||
34 | + final PdfGraphics _canvas; | ||
35 | + | ||
36 | + final PdfDocument document; | ||
37 | + | ||
38 | + final PdfRect boundingBox; | ||
39 | + | ||
40 | + void paint() { | ||
41 | + SvgGroup.fromXml( | ||
42 | + parser.root, | ||
43 | + this, | ||
44 | + SvgBrush.defaultContext, | ||
45 | + ).paint(_canvas); | ||
46 | + } | ||
47 | + | ||
48 | + final _fontCache = <String, Font>{}; | ||
49 | + | ||
50 | + Font getFontCache(String fontFamily, String fontStyle, String fontWeight) { | ||
51 | + final cache = '$fontFamily-$fontStyle-$fontWeight'; | ||
52 | + | ||
53 | + if (!_fontCache.containsKey(cache)) { | ||
54 | + _fontCache[cache] = getFont(fontFamily, fontStyle, fontWeight); | ||
55 | + } | ||
56 | + | ||
57 | + return _fontCache[cache]; | ||
58 | + } | ||
59 | + | ||
60 | + Font getFont(String fontFamily, String fontStyle, String fontWeight) { | ||
61 | + switch (fontFamily) { | ||
62 | + case 'serif': | ||
63 | + switch (fontStyle) { | ||
64 | + case 'normal': | ||
65 | + switch (fontWeight) { | ||
66 | + case 'normal': | ||
67 | + case 'lighter': | ||
68 | + return Font.times(); | ||
69 | + } | ||
70 | + return Font.timesBold(); | ||
71 | + } | ||
72 | + switch (fontWeight) { | ||
73 | + case 'normal': | ||
74 | + case 'lighter': | ||
75 | + return Font.timesItalic(); | ||
76 | + } | ||
77 | + return Font.timesBoldItalic(); | ||
78 | + | ||
79 | + case 'monospace': | ||
80 | + switch (fontStyle) { | ||
81 | + case 'normal': | ||
82 | + switch (fontWeight) { | ||
83 | + case 'normal': | ||
84 | + case 'lighter': | ||
85 | + return Font.courier(); | ||
86 | + } | ||
87 | + return Font.courierBold(); | ||
88 | + } | ||
89 | + switch (fontWeight) { | ||
90 | + case 'normal': | ||
91 | + case 'lighter': | ||
92 | + return Font.courierOblique(); | ||
93 | + } | ||
94 | + return Font.courierBoldOblique(); | ||
95 | + } | ||
96 | + | ||
97 | + switch (fontStyle) { | ||
98 | + case 'normal': | ||
99 | + switch (fontWeight) { | ||
100 | + case 'normal': | ||
101 | + case 'lighter': | ||
102 | + return Font.helvetica(); | ||
103 | + } | ||
104 | + return Font.helveticaBold(); | ||
105 | + } | ||
106 | + switch (fontWeight) { | ||
107 | + case 'normal': | ||
108 | + case 'lighter': | ||
109 | + return Font.helveticaOblique(); | ||
110 | + } | ||
111 | + return Font.helveticaBoldOblique(); | ||
112 | + } | ||
113 | +} |
pdf/lib/svg/parser.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:meta/meta.dart'; | ||
18 | +import 'package:pdf/pdf.dart'; | ||
19 | +import 'package:xml/xml.dart'; | ||
20 | + | ||
21 | +import 'brush.dart'; | ||
22 | + | ||
23 | +class SvgParser { | ||
24 | + /// Create an SVG parser | ||
25 | + | ||
26 | + factory SvgParser({@required XmlDocument xml}) { | ||
27 | + assert(xml != null); | ||
28 | + final root = xml.rootElement; | ||
29 | + | ||
30 | + final vbattr = root.getAttribute('viewBox'); | ||
31 | + | ||
32 | + final width = getNumeric(root, 'width', null)?.sizeValue; | ||
33 | + final height = getNumeric(root, 'height', null)?.sizeValue; | ||
34 | + | ||
35 | + final vb = vbattr == null | ||
36 | + ? <double>[0, 0, width ?? 1000, height ?? 1000] | ||
37 | + : splitDoubles(vbattr); | ||
38 | + | ||
39 | + if (vb.isEmpty || vb.length > 4) { | ||
40 | + throw Exception('viewBox must contain 1..4 parameters'); | ||
41 | + } | ||
42 | + | ||
43 | + final fvb = [ | ||
44 | + ...List<double>.filled(4 - vb.length, 0), | ||
45 | + ...vb, | ||
46 | + ]; | ||
47 | + | ||
48 | + final viewBox = PdfRect(fvb[0], fvb[1], fvb[2], fvb[3]); | ||
49 | + | ||
50 | + return SvgParser._(width, height, viewBox, root); | ||
51 | + } | ||
52 | + | ||
53 | + SvgParser._(this.width, this.height, this.viewBox, this.root); | ||
54 | + | ||
55 | + final PdfRect viewBox; | ||
56 | + | ||
57 | + final double width; | ||
58 | + | ||
59 | + final double height; | ||
60 | + | ||
61 | + final XmlElement root; | ||
62 | + | ||
63 | + static final _transformParameterRegExp = RegExp(r'[\w.-]+'); | ||
64 | + | ||
65 | + XmlElement findById(String id) { | ||
66 | + try { | ||
67 | + return root.descendants.whereType<XmlElement>().firstWhere( | ||
68 | + (e) => e.getAttribute('id') == id, | ||
69 | + ); | ||
70 | + } on StateError { | ||
71 | + return null; | ||
72 | + } | ||
73 | + } | ||
74 | + | ||
75 | + static double getDouble(XmlElement xml, String name, | ||
76 | + {String namespace, double defaultValue = 0}) { | ||
77 | + final attr = xml.getAttribute(name, namespace: namespace); | ||
78 | + | ||
79 | + if (attr == null) { | ||
80 | + return defaultValue; | ||
81 | + } | ||
82 | + | ||
83 | + return double.parse(attr); | ||
84 | + } | ||
85 | + | ||
86 | + static SvgNumeric getNumeric(XmlElement xml, String name, SvgBrush brush, | ||
87 | + {String namespace, double defaultValue}) { | ||
88 | + final attr = xml.getAttribute(name, namespace: namespace); | ||
89 | + | ||
90 | + if (attr == null) { | ||
91 | + return defaultValue == null ? null : SvgNumeric.value(defaultValue, null); | ||
92 | + } | ||
93 | + | ||
94 | + return SvgNumeric(attr, brush); | ||
95 | + } | ||
96 | + | ||
97 | + static Iterable<SvgNumeric> splitNumeric(String parameters, SvgBrush brush) { | ||
98 | + final parameterMatches = _transformParameterRegExp.allMatches(parameters); | ||
99 | + return parameterMatches.map((m) => SvgNumeric(m.group(0), brush)); | ||
100 | + } | ||
101 | + | ||
102 | + static Iterable<double> splitDoubles(String parameters) { | ||
103 | + final parameterMatches = _transformParameterRegExp.allMatches(parameters); | ||
104 | + return parameterMatches.map((m) => double.parse(m.group(0))); | ||
105 | + } | ||
106 | + | ||
107 | + static Iterable<int> splitIntegers(String parameters) { | ||
108 | + final parameterMatches = _transformParameterRegExp.allMatches(parameters); | ||
109 | + | ||
110 | + return parameterMatches.map((m) { | ||
111 | + return int.parse(m.group(0)); | ||
112 | + }); | ||
113 | + } | ||
114 | + | ||
115 | + /// Convert style to attributes | ||
116 | + static void convertStyle(XmlElement element) { | ||
117 | + final style = element.getAttribute('style')?.trim(); | ||
118 | + if (style != null && style.isNotEmpty) { | ||
119 | + for (final style in style.split(';')) { | ||
120 | + if (style.trim().isEmpty) { | ||
121 | + continue; | ||
122 | + } | ||
123 | + final kv = RegExp(r'([\w-]+)\s*:\s*(.*)').allMatches(style).first; | ||
124 | + final key = kv.group(1); | ||
125 | + final value = kv.group(2); | ||
126 | + | ||
127 | + element.setAttribute(key, value); | ||
128 | + } | ||
129 | + } | ||
130 | + } | ||
131 | +} | ||
132 | + | ||
133 | +enum SvgUnit { | ||
134 | + pixels, | ||
135 | + milimeters, | ||
136 | + centimeters, | ||
137 | + inch, | ||
138 | + em, | ||
139 | + percent, | ||
140 | + points, | ||
141 | + direct | ||
142 | +} | ||
143 | + | ||
144 | +class SvgNumeric { | ||
145 | + factory SvgNumeric(String value, SvgBrush brush) { | ||
146 | + final r = RegExp(r'([-+]?[\d\.]+)\s*(px|pt|em|cm|mm|in|%|)') | ||
147 | + .allMatches(value) | ||
148 | + .first; | ||
149 | + | ||
150 | + return SvgNumeric.value( | ||
151 | + double.parse(r.group(1)), brush, _svgUnits[r.group(2)]); | ||
152 | + } | ||
153 | + | ||
154 | + const SvgNumeric.value( | ||
155 | + this.value, | ||
156 | + this.brush, [ | ||
157 | + this.unit = SvgUnit.direct, | ||
158 | + ]) : assert(value != null), | ||
159 | + assert(unit != null); | ||
160 | + | ||
161 | + static const _svgUnits = <String, SvgUnit>{ | ||
162 | + 'px': SvgUnit.pixels, | ||
163 | + 'mm': SvgUnit.milimeters, | ||
164 | + 'cm': SvgUnit.centimeters, | ||
165 | + 'in': SvgUnit.inch, | ||
166 | + 'em': SvgUnit.em, | ||
167 | + '%': SvgUnit.percent, | ||
168 | + 'pt': SvgUnit.points, | ||
169 | + '': SvgUnit.direct, | ||
170 | + }; | ||
171 | + | ||
172 | + final double value; | ||
173 | + | ||
174 | + final SvgUnit unit; | ||
175 | + | ||
176 | + final SvgBrush brush; | ||
177 | + | ||
178 | + double get colorValue { | ||
179 | + switch (unit) { | ||
180 | + case SvgUnit.percent: | ||
181 | + return value / 100.0; | ||
182 | + break; | ||
183 | + case SvgUnit.direct: | ||
184 | + return value / 255.0; | ||
185 | + default: | ||
186 | + throw Exception('Invalid color value $value ($unit)'); | ||
187 | + } | ||
188 | + } | ||
189 | + | ||
190 | + double get sizeValue { | ||
191 | + switch (unit) { | ||
192 | + case SvgUnit.percent: | ||
193 | + return value / 100.0; | ||
194 | + break; | ||
195 | + case SvgUnit.direct: | ||
196 | + case SvgUnit.pixels: | ||
197 | + case SvgUnit.points: | ||
198 | + return value; | ||
199 | + case SvgUnit.milimeters: | ||
200 | + return value * PdfPageFormat.mm; | ||
201 | + case SvgUnit.centimeters: | ||
202 | + return value * PdfPageFormat.cm; | ||
203 | + case SvgUnit.inch: | ||
204 | + return value * PdfPageFormat.inch; | ||
205 | + case SvgUnit.em: | ||
206 | + return value * brush.fontSize.sizeValue; | ||
207 | + } | ||
208 | + throw Exception('Invalid size value $value ($unit)'); | ||
209 | + } | ||
210 | +} |
pdf/lib/svg/path.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:pdf/pdf.dart'; | ||
18 | +import 'package:xml/xml.dart'; | ||
19 | + | ||
20 | +import 'brush.dart'; | ||
21 | +import 'clip_path.dart'; | ||
22 | +import 'operation.dart'; | ||
23 | +import 'painter.dart'; | ||
24 | +import 'parser.dart'; | ||
25 | +import 'transform.dart'; | ||
26 | + | ||
27 | +class SvgPath extends SvgOperation { | ||
28 | + SvgPath( | ||
29 | + this.d, | ||
30 | + SvgBrush brush, | ||
31 | + SvgClipPath clip, | ||
32 | + SvgTransform transform, | ||
33 | + SvgPainter painter, | ||
34 | + ) : super(brush, clip, transform, painter); | ||
35 | + | ||
36 | + factory SvgPath.fromXml( | ||
37 | + XmlElement element, | ||
38 | + SvgPainter painter, | ||
39 | + SvgBrush brush, | ||
40 | + ) { | ||
41 | + final d = element.getAttribute('d'); | ||
42 | + if (d == null) { | ||
43 | + throw Exception('Path element must contain "d" attribute'); | ||
44 | + } | ||
45 | + | ||
46 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
47 | + | ||
48 | + return SvgPath( | ||
49 | + d, | ||
50 | + _brush, | ||
51 | + SvgClipPath.fromXml(element, painter, _brush), | ||
52 | + SvgTransform.fromXml(element), | ||
53 | + painter, | ||
54 | + ); | ||
55 | + } | ||
56 | + | ||
57 | + factory SvgPath.fromRectXml( | ||
58 | + XmlElement element, | ||
59 | + SvgPainter painter, | ||
60 | + SvgBrush brush, | ||
61 | + ) { | ||
62 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
63 | + | ||
64 | + final x = | ||
65 | + SvgParser.getNumeric(element, 'x', _brush, defaultValue: 0).sizeValue; | ||
66 | + final y = | ||
67 | + SvgParser.getNumeric(element, 'y', _brush, defaultValue: 0).sizeValue; | ||
68 | + final width = | ||
69 | + SvgParser.getNumeric(element, 'width', _brush, defaultValue: 0) | ||
70 | + .sizeValue; | ||
71 | + final height = | ||
72 | + SvgParser.getNumeric(element, 'height', _brush, defaultValue: 0) | ||
73 | + .sizeValue; | ||
74 | + var rx = SvgParser.getNumeric(element, 'rx', _brush)?.sizeValue; | ||
75 | + var ry = SvgParser.getNumeric(element, 'ry', _brush)?.sizeValue; | ||
76 | + | ||
77 | + ry ??= rx ?? 0; | ||
78 | + rx ??= ry ?? 0; | ||
79 | + final topRight = rx != 0 || ry != 0 ? 'a $rx $ry 0 0 1 $rx $ry' : ''; | ||
80 | + final bottomRight = rx != 0 || ry != 0 ? 'a $rx $ry 0 0 1 ${-rx} $ry' : ''; | ||
81 | + final bottomLeft = | ||
82 | + rx != 0 || ry != 0 ? 'a $rx $ry 0 0 1 ${-rx} ${-ry}' : ''; | ||
83 | + final topLeft = rx != 0 || ry != 0 ? 'a $rx $ry 0 0 1 $rx ${-ry}' : ''; | ||
84 | + final d = | ||
85 | + 'M${x + rx} ${y}h${width - rx * 2}${topRight}v${height - ry * 2}${bottomRight}h${-(width - rx * 2)}${bottomLeft}v${-(height - ry * 2)}${topLeft}z'; | ||
86 | + | ||
87 | + return SvgPath( | ||
88 | + d, | ||
89 | + _brush, | ||
90 | + SvgClipPath.fromXml(element, painter, _brush), | ||
91 | + SvgTransform.fromXml(element), | ||
92 | + painter, | ||
93 | + ); | ||
94 | + } | ||
95 | + | ||
96 | + factory SvgPath.fromCircleXml( | ||
97 | + XmlElement element, | ||
98 | + SvgPainter painter, | ||
99 | + SvgBrush brush, | ||
100 | + ) { | ||
101 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
102 | + | ||
103 | + final cx = SvgParser.getNumeric(element, 'cx', _brush).sizeValue; | ||
104 | + final cy = SvgParser.getNumeric(element, 'cy', _brush).sizeValue; | ||
105 | + final r = SvgParser.getNumeric(element, 'r', _brush).sizeValue; | ||
106 | + final d = | ||
107 | + 'M${cx - r},${cy}A$r,$r 0,0,0 ${cx + r},${cy}A$r,$r 0,0,0 ${cx - r},${cy}z'; | ||
108 | + | ||
109 | + return SvgPath( | ||
110 | + d, | ||
111 | + _brush, | ||
112 | + SvgClipPath.fromXml(element, painter, _brush), | ||
113 | + SvgTransform.fromXml(element), | ||
114 | + painter, | ||
115 | + ); | ||
116 | + } | ||
117 | + | ||
118 | + factory SvgPath.fromEllipseXml( | ||
119 | + XmlElement element, | ||
120 | + SvgPainter painter, | ||
121 | + SvgBrush brush, | ||
122 | + ) { | ||
123 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
124 | + | ||
125 | + final cx = SvgParser.getNumeric(element, 'cx', _brush).sizeValue; | ||
126 | + final cy = SvgParser.getNumeric(element, 'cy', _brush).sizeValue; | ||
127 | + final rx = SvgParser.getNumeric(element, 'rx', _brush).sizeValue; | ||
128 | + final ry = SvgParser.getNumeric(element, 'ry', _brush).sizeValue; | ||
129 | + final d = | ||
130 | + 'M${cx - rx},${cy}A$rx,$ry 0,0,0 ${cx + rx},${cy}A$rx,$ry 0,0,0 ${cx - rx},${cy}z'; | ||
131 | + | ||
132 | + return SvgPath( | ||
133 | + d, | ||
134 | + _brush, | ||
135 | + SvgClipPath.fromXml(element, painter, _brush), | ||
136 | + SvgTransform.fromXml(element), | ||
137 | + painter, | ||
138 | + ); | ||
139 | + } | ||
140 | + | ||
141 | + factory SvgPath.fromPolylineXml( | ||
142 | + XmlElement element, | ||
143 | + SvgPainter painter, | ||
144 | + SvgBrush brush, | ||
145 | + ) { | ||
146 | + final points = element.getAttribute('points'); | ||
147 | + final d = 'M$points'; | ||
148 | + | ||
149 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
150 | + | ||
151 | + return SvgPath( | ||
152 | + d, | ||
153 | + _brush, | ||
154 | + SvgClipPath.fromXml(element, painter, _brush), | ||
155 | + SvgTransform.fromXml(element), | ||
156 | + painter, | ||
157 | + ); | ||
158 | + } | ||
159 | + | ||
160 | + factory SvgPath.fromPolygonXml( | ||
161 | + XmlElement element, | ||
162 | + SvgPainter painter, | ||
163 | + SvgBrush brush, | ||
164 | + ) { | ||
165 | + final points = element.getAttribute('points'); | ||
166 | + final d = 'M${points}z'; | ||
167 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
168 | + | ||
169 | + return SvgPath( | ||
170 | + d, | ||
171 | + _brush, | ||
172 | + SvgClipPath.fromXml(element, painter, _brush), | ||
173 | + SvgTransform.fromXml(element), | ||
174 | + painter, | ||
175 | + ); | ||
176 | + } | ||
177 | + | ||
178 | + factory SvgPath.fromLineXml( | ||
179 | + XmlElement element, | ||
180 | + SvgPainter painter, | ||
181 | + SvgBrush brush, | ||
182 | + ) { | ||
183 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
184 | + | ||
185 | + final x1 = SvgParser.getNumeric(element, 'x1', _brush).sizeValue; | ||
186 | + final y1 = SvgParser.getNumeric(element, 'y1', _brush).sizeValue; | ||
187 | + final x2 = SvgParser.getNumeric(element, 'x2', _brush).sizeValue; | ||
188 | + final y2 = SvgParser.getNumeric(element, 'y2', _brush).sizeValue; | ||
189 | + final d = 'M$x1 $y1 $x2 $y2'; | ||
190 | + | ||
191 | + return SvgPath( | ||
192 | + d, | ||
193 | + _brush, | ||
194 | + SvgClipPath.fromXml(element, painter, _brush), | ||
195 | + SvgTransform.fromXml(element), | ||
196 | + painter, | ||
197 | + ); | ||
198 | + } | ||
199 | + | ||
200 | + final String d; | ||
201 | + | ||
202 | + @override | ||
203 | + void paintShape(PdfGraphics canvas) { | ||
204 | + if (brush.fill.isNotEmpty) { | ||
205 | + brush.fill.setFillColor(this, canvas); | ||
206 | + if (brush.fillOpacity < 1) { | ||
207 | + canvas | ||
208 | + ..saveContext() | ||
209 | + ..setGraphicState(PdfGraphicState(opacity: brush.fillOpacity)); | ||
210 | + } | ||
211 | + canvas | ||
212 | + ..drawShape(d) | ||
213 | + ..fillPath(evenOdd: brush.fillEvenOdd); | ||
214 | + if (brush.fillOpacity < 1) { | ||
215 | + canvas.restoreContext(); | ||
216 | + } | ||
217 | + } | ||
218 | + | ||
219 | + if (brush.stroke.isNotEmpty) { | ||
220 | + brush.stroke.setStrokeColor(this, canvas); | ||
221 | + if (brush.strokeOpacity < 1) { | ||
222 | + canvas.setGraphicState(PdfGraphicState(opacity: brush.strokeOpacity)); | ||
223 | + } | ||
224 | + canvas | ||
225 | + ..drawShape(d) | ||
226 | + ..setLineCap(brush.strokeLineCap) | ||
227 | + ..setLineJoin(brush.strokeLineJoin) | ||
228 | + ..setMiterLimit(brush.strokeMiterLimit) | ||
229 | + ..setLineDashPattern( | ||
230 | + brush.strokeDashArray, brush.strokeDashOffset.toInt()) | ||
231 | + ..setLineWidth(brush.strokeWidth.sizeValue) | ||
232 | + ..strokePath(); | ||
233 | + } | ||
234 | + } | ||
235 | + | ||
236 | + @override | ||
237 | + void drawShape(PdfGraphics canvas) { | ||
238 | + canvas.drawShape(d); | ||
239 | + } | ||
240 | + | ||
241 | + @override | ||
242 | + PdfRect boundingBox() { | ||
243 | + return PdfGraphics.shapeBoundingBox(d); | ||
244 | + } | ||
245 | +} |
pdf/lib/svg/symbol.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:pdf/pdf.dart'; | ||
18 | +import 'package:xml/xml.dart'; | ||
19 | + | ||
20 | +import 'brush.dart'; | ||
21 | +import 'clip_path.dart'; | ||
22 | +import 'group.dart'; | ||
23 | +import 'operation.dart'; | ||
24 | +import 'painter.dart'; | ||
25 | +import 'transform.dart'; | ||
26 | + | ||
27 | +class SvgSymbol extends SvgGroup { | ||
28 | + SvgSymbol( | ||
29 | + Iterable<SvgOperation> children, | ||
30 | + SvgBrush brush, | ||
31 | + SvgClipPath clip, | ||
32 | + SvgTransform transform, | ||
33 | + SvgPainter painter, | ||
34 | + ) : super(children, brush, clip, transform, painter); | ||
35 | + | ||
36 | + factory SvgSymbol.fromXml( | ||
37 | + XmlElement element, SvgPainter painter, SvgBrush brush) { | ||
38 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
39 | + | ||
40 | + final children = element.children | ||
41 | + .whereType<XmlElement>() | ||
42 | + .map<SvgOperation>( | ||
43 | + (child) => SvgOperation.fromXml(child, painter, _brush)) | ||
44 | + .where((element) => element != null); | ||
45 | + | ||
46 | + return SvgSymbol( | ||
47 | + children, | ||
48 | + _brush, | ||
49 | + SvgClipPath.fromXml(element, painter, _brush), | ||
50 | + SvgTransform.fromXml(element), | ||
51 | + painter, | ||
52 | + ); | ||
53 | + } | ||
54 | + | ||
55 | + @override | ||
56 | + void paintShape(PdfGraphics canvas) { | ||
57 | + for (final child in children) { | ||
58 | + child.paint(canvas); | ||
59 | + } | ||
60 | + } | ||
61 | +} |
pdf/lib/svg/text.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'dart:math'; | ||
18 | + | ||
19 | +import 'package:pdf/pdf.dart'; | ||
20 | +import 'package:pdf/widgets.dart'; | ||
21 | +import 'package:vector_math/vector_math_64.dart'; | ||
22 | +import 'package:xml/xml.dart'; | ||
23 | + | ||
24 | +import 'brush.dart'; | ||
25 | +import 'clip_path.dart'; | ||
26 | +import 'operation.dart'; | ||
27 | +import 'painter.dart'; | ||
28 | +import 'parser.dart'; | ||
29 | +import 'transform.dart'; | ||
30 | + | ||
31 | +class SvgText extends SvgOperation { | ||
32 | + SvgText( | ||
33 | + this.x, | ||
34 | + this.y, | ||
35 | + this.dx, | ||
36 | + this.text, | ||
37 | + this.font, | ||
38 | + this.tspan, | ||
39 | + this.metrics, | ||
40 | + SvgBrush brush, | ||
41 | + SvgClipPath clip, | ||
42 | + SvgTransform transform, | ||
43 | + SvgPainter painter, | ||
44 | + ) : super(brush, clip, transform, painter); | ||
45 | + | ||
46 | + factory SvgText.fromXml( | ||
47 | + XmlElement element, | ||
48 | + SvgPainter painter, | ||
49 | + SvgBrush brush, [ | ||
50 | + PdfPoint offset = PdfPoint.zero, | ||
51 | + ]) { | ||
52 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
53 | + | ||
54 | + final dx = | ||
55 | + SvgParser.getNumeric(element, 'dx', _brush, defaultValue: 0).sizeValue; | ||
56 | + final dy = | ||
57 | + SvgParser.getNumeric(element, 'dy', _brush, defaultValue: 0).sizeValue; | ||
58 | + final x = SvgParser.getNumeric(element, 'x', _brush)?.sizeValue; | ||
59 | + final y = SvgParser.getNumeric(element, 'y', _brush)?.sizeValue; | ||
60 | + | ||
61 | + final text = element.children | ||
62 | + .where((node) => node is XmlText || node is XmlCDATA) | ||
63 | + .map((node) => node.text) | ||
64 | + .join() | ||
65 | + .trim(); | ||
66 | + | ||
67 | + final font = painter.getFontCache( | ||
68 | + _brush.fontFamily, _brush.fontStyle, _brush.fontWeight); | ||
69 | + final pdfFont = font.getFont(Context(document: painter.document)); | ||
70 | + final metrics = pdfFont.stringMetrics(text) * _brush.fontSize.sizeValue; | ||
71 | + offset = PdfPoint((x ?? offset.x) + dx, (y ?? offset.y) + dy); | ||
72 | + | ||
73 | + switch (_brush.textAnchor) { | ||
74 | + case SvgTextAnchor.start: | ||
75 | + break; | ||
76 | + case SvgTextAnchor.middle: | ||
77 | + offset = PdfPoint(offset.x - metrics.width / 2, offset.y); | ||
78 | + break; | ||
79 | + case SvgTextAnchor.end: | ||
80 | + offset = PdfPoint(offset.x - metrics.width, offset.y); | ||
81 | + break; | ||
82 | + } | ||
83 | + | ||
84 | + var childOffset = PdfPoint(offset.x + metrics.advanceWidth, offset.y); | ||
85 | + | ||
86 | + final tspan = element.children.whereType<XmlElement>().map<SvgText>((e) { | ||
87 | + final child = SvgText.fromXml(e, painter, _brush, childOffset); | ||
88 | + childOffset = PdfPoint(child.x + child.dx, child.y); | ||
89 | + return child; | ||
90 | + }); | ||
91 | + | ||
92 | + return SvgText( | ||
93 | + offset.x, | ||
94 | + offset.y, | ||
95 | + metrics.advanceWidth, | ||
96 | + text, | ||
97 | + pdfFont, | ||
98 | + tspan, | ||
99 | + metrics, | ||
100 | + _brush, | ||
101 | + SvgClipPath.fromXml(element, painter, _brush), | ||
102 | + SvgTransform.fromXml(element), | ||
103 | + painter, | ||
104 | + ); | ||
105 | + } | ||
106 | + | ||
107 | + final double x; | ||
108 | + | ||
109 | + final double y; | ||
110 | + | ||
111 | + final double dx; | ||
112 | + | ||
113 | + final String text; | ||
114 | + | ||
115 | + final PdfFont font; | ||
116 | + | ||
117 | + final PdfFontMetrics metrics; | ||
118 | + | ||
119 | + final Iterable<SvgText> tspan; | ||
120 | + | ||
121 | + @override | ||
122 | + void paintShape(PdfGraphics canvas) { | ||
123 | + canvas | ||
124 | + ..saveContext() | ||
125 | + ..setTransform(Matrix4.identity() | ||
126 | + ..scale(1.0, -1.0) | ||
127 | + ..translate(x, -y)); | ||
128 | + | ||
129 | + if (brush.fill.isNotEmpty) { | ||
130 | + brush.fill.setFillColor(this, canvas); | ||
131 | + if (brush.fillOpacity < 1) { | ||
132 | + canvas | ||
133 | + ..saveContext() | ||
134 | + ..setGraphicState(PdfGraphicState(opacity: brush.fillOpacity)); | ||
135 | + } | ||
136 | + canvas.drawString(font, brush.fontSize.sizeValue, text, 0, 0); | ||
137 | + if (brush.fillOpacity < 1) { | ||
138 | + canvas.restoreContext(); | ||
139 | + } | ||
140 | + } | ||
141 | + | ||
142 | + if (brush.stroke.isNotEmpty) { | ||
143 | + if (brush.strokeWidth != null) { | ||
144 | + canvas.setLineWidth(brush.strokeWidth.sizeValue); | ||
145 | + } | ||
146 | + if (brush.strokeDashArray != null) { | ||
147 | + canvas.setLineDashPattern(brush.strokeDashArray); | ||
148 | + } | ||
149 | + if (brush.strokeOpacity < 1) { | ||
150 | + canvas.setGraphicState(PdfGraphicState(opacity: brush.strokeOpacity)); | ||
151 | + } | ||
152 | + brush.stroke.setStrokeColor(this, canvas); | ||
153 | + canvas.drawString(font, brush.fontSize.sizeValue, text, 0, 0, | ||
154 | + mode: PdfTextRenderingMode.stroke); | ||
155 | + } | ||
156 | + | ||
157 | + canvas.restoreContext(); | ||
158 | + | ||
159 | + for (final span in tspan) { | ||
160 | + span.paint(canvas); | ||
161 | + } | ||
162 | + } | ||
163 | + | ||
164 | + @override | ||
165 | + void drawShape(PdfGraphics canvas) { | ||
166 | + canvas | ||
167 | + ..saveContext() | ||
168 | + ..setTransform(Matrix4.identity() | ||
169 | + ..scale(1.0, -1.0) | ||
170 | + ..translate(x, -y)) | ||
171 | + ..drawString(font, brush.fontSize.sizeValue, text, 0, 0, | ||
172 | + mode: PdfTextRenderingMode.clip) | ||
173 | + ..restoreContext(); | ||
174 | + | ||
175 | + for (final span in tspan) { | ||
176 | + span.draw(canvas); | ||
177 | + } | ||
178 | + } | ||
179 | + | ||
180 | + @override | ||
181 | + PdfRect boundingBox() { | ||
182 | + final b = metrics.toPdfRect(); | ||
183 | + var x = b.x, y = b.y, w = b.width, h = b.height; | ||
184 | + for (final child in tspan) { | ||
185 | + final b = child.boundingBox(); | ||
186 | + x = min(b.x, x); | ||
187 | + y = min(b.y, y); | ||
188 | + w = max(b.width, w); | ||
189 | + h = max(b.height, w); | ||
190 | + } | ||
191 | + | ||
192 | + return PdfRect(x, y, w, h); | ||
193 | + } | ||
194 | +} |
pdf/lib/svg/transform.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:pdf/svg/parser.dart'; | ||
18 | +import 'package:vector_math/vector_math_64.dart'; | ||
19 | +import 'package:xml/xml.dart'; | ||
20 | + | ||
21 | +class SvgTransform { | ||
22 | + const SvgTransform(this.matrix); | ||
23 | + | ||
24 | + factory SvgTransform.fromXml(XmlElement element) { | ||
25 | + return SvgTransform.fromString(element.getAttribute('transform')); | ||
26 | + } | ||
27 | + | ||
28 | + factory SvgTransform.fromString(String transform) { | ||
29 | + if (transform == null) { | ||
30 | + return none; | ||
31 | + } | ||
32 | + | ||
33 | + if (transform == null) { | ||
34 | + return none; | ||
35 | + } | ||
36 | + | ||
37 | + final mat = Matrix4.identity(); | ||
38 | + | ||
39 | + for (final m in _transformRegExp.allMatches(transform)) { | ||
40 | + final name = m.group(1); | ||
41 | + final parameterList = SvgParser.splitDoubles(m.group(2)).toList(); | ||
42 | + | ||
43 | + switch (name) { | ||
44 | + case 'matrix': | ||
45 | + final mm = <double>[ | ||
46 | + ...parameterList, | ||
47 | + ...List.filled(6 - parameterList.length, 0.0) | ||
48 | + ]; | ||
49 | + | ||
50 | + mat.multiply(Matrix4(mm[0], mm[1], 0, 0, mm[2], mm[3], 0, 0, 0, 0, 1, | ||
51 | + 0, mm[4], mm[5], 0, 1)); | ||
52 | + break; | ||
53 | + case 'translate': | ||
54 | + final dx = parameterList[0]; | ||
55 | + final dy = [...parameterList, .0][1]; | ||
56 | + | ||
57 | + mat.multiply(Matrix4.identity()..translate(dx, dy)); | ||
58 | + break; | ||
59 | + case 'scale': | ||
60 | + final sw = parameterList[0]; | ||
61 | + final sh = [...parameterList, sw][1]; | ||
62 | + | ||
63 | + mat.multiply(Matrix4.identity()..scale(sw, sh)); | ||
64 | + break; | ||
65 | + case 'rotate': | ||
66 | + final degrees = parameterList[0]; | ||
67 | + | ||
68 | + var ox = 0.0; | ||
69 | + var oy = 0.0; | ||
70 | + if (parameterList.length > 1) { | ||
71 | + // Rotation about the origin (ox, oy) | ||
72 | + ox = parameterList[1]; | ||
73 | + oy = [...parameterList, .0][2]; | ||
74 | + mat.translate(ox, oy); | ||
75 | + } | ||
76 | + | ||
77 | + mat.multiply(Matrix4.rotationZ(radians(degrees))); | ||
78 | + | ||
79 | + if (ox != 0 || oy != 0) { | ||
80 | + mat.translate(-ox, -oy); | ||
81 | + } | ||
82 | + break; | ||
83 | + | ||
84 | + case 'skewX': | ||
85 | + // assert(false, 'skewX'); | ||
86 | + mat.multiply(Matrix4.skewX(radians(parameterList[0]))); | ||
87 | + break; | ||
88 | + case 'skewY': | ||
89 | + // assert(false, 'skewY'); | ||
90 | + mat.multiply(Matrix4.skewY(radians(parameterList[0]))); | ||
91 | + break; | ||
92 | + } | ||
93 | + } | ||
94 | + | ||
95 | + return SvgTransform(mat); | ||
96 | + } | ||
97 | + | ||
98 | + final Matrix4 matrix; | ||
99 | + | ||
100 | + bool get isEmpty => matrix == null; | ||
101 | + | ||
102 | + bool get isNotEmpty => matrix != null; | ||
103 | + | ||
104 | + static const none = SvgTransform(null); | ||
105 | + | ||
106 | + static final _transformRegExp = | ||
107 | + RegExp('(matrix|translate|scale|rotate|skewX|skewY)\s*\(([^)]*)\)\s*'); | ||
108 | +} |
pdf/lib/svg/use.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:pdf/pdf.dart'; | ||
18 | +import 'package:vector_math/vector_math_64.dart'; | ||
19 | +import 'package:xml/xml.dart'; | ||
20 | + | ||
21 | +import 'brush.dart'; | ||
22 | +import 'clip_path.dart'; | ||
23 | +import 'operation.dart'; | ||
24 | +import 'painter.dart'; | ||
25 | +import 'parser.dart'; | ||
26 | +import 'transform.dart'; | ||
27 | + | ||
28 | +class SvgUse extends SvgOperation { | ||
29 | + SvgUse( | ||
30 | + this.x, | ||
31 | + this.y, | ||
32 | + this.width, | ||
33 | + this.height, | ||
34 | + this.href, | ||
35 | + SvgBrush brush, | ||
36 | + SvgClipPath clip, | ||
37 | + SvgTransform transform, | ||
38 | + SvgPainter painter, | ||
39 | + ) : super(brush, clip, transform, painter); | ||
40 | + | ||
41 | + factory SvgUse.fromXml( | ||
42 | + XmlElement element, | ||
43 | + SvgPainter painter, | ||
44 | + SvgBrush brush, | ||
45 | + ) { | ||
46 | + final _brush = SvgBrush.fromXml(element, brush, painter); | ||
47 | + | ||
48 | + final width = | ||
49 | + SvgParser.getNumeric(element, 'width', _brush, defaultValue: 0) | ||
50 | + .sizeValue; | ||
51 | + final height = | ||
52 | + SvgParser.getNumeric(element, 'height', _brush, defaultValue: 0) | ||
53 | + .sizeValue; | ||
54 | + final x = | ||
55 | + SvgParser.getNumeric(element, 'x', _brush, defaultValue: 0).sizeValue; | ||
56 | + final y = | ||
57 | + SvgParser.getNumeric(element, 'y', _brush, defaultValue: 0).sizeValue; | ||
58 | + | ||
59 | + SvgOperation href; | ||
60 | + final hrefAttr = element.getAttribute('href') ?? | ||
61 | + element.getAttribute('href', namespace: 'http://www.w3.org/1999/xlink'); | ||
62 | + | ||
63 | + if (hrefAttr != null) { | ||
64 | + final hrefElement = painter.parser.findById(hrefAttr.substring(1)); | ||
65 | + if (hrefElement != null) { | ||
66 | + href = SvgOperation.fromXml(hrefElement, painter, _brush); | ||
67 | + } | ||
68 | + } | ||
69 | + | ||
70 | + return SvgUse( | ||
71 | + x, | ||
72 | + y, | ||
73 | + width, | ||
74 | + height, | ||
75 | + href, | ||
76 | + _brush, | ||
77 | + SvgClipPath.fromXml(element, painter, _brush), | ||
78 | + SvgTransform.fromXml(element), | ||
79 | + painter, | ||
80 | + ); | ||
81 | + } | ||
82 | + | ||
83 | + final double x; | ||
84 | + | ||
85 | + final double y; | ||
86 | + | ||
87 | + final double width; | ||
88 | + | ||
89 | + final double height; | ||
90 | + | ||
91 | + final SvgOperation href; | ||
92 | + | ||
93 | + @override | ||
94 | + void paintShape(PdfGraphics canvas) { | ||
95 | + if (x != 0 || y != 0) { | ||
96 | + canvas.setTransform(Matrix4.translationValues(x, y, 0)); | ||
97 | + } | ||
98 | + href?.paint(canvas); | ||
99 | + } | ||
100 | + | ||
101 | + @override | ||
102 | + void drawShape(PdfGraphics canvas) { | ||
103 | + if (x != 0 || y != 0) { | ||
104 | + canvas.setTransform(Matrix4.translationValues(x, y, 0)); | ||
105 | + } | ||
106 | + href?.draw(canvas); | ||
107 | + } | ||
108 | + | ||
109 | + @override | ||
110 | + PdfRect boundingBox() => href.boundingBox(); | ||
111 | +} |
@@ -16,6 +16,7 @@ | @@ -16,6 +16,7 @@ | ||
16 | 16 | ||
17 | export 'package:barcode/barcode.dart'; | 17 | export 'package:barcode/barcode.dart'; |
18 | 18 | ||
19 | +export 'svg.dart'; | ||
19 | export 'widgets/annotations.dart'; | 20 | export 'widgets/annotations.dart'; |
20 | export 'widgets/barcode.dart'; | 21 | export 'widgets/barcode.dart'; |
21 | export 'widgets/basic.dart'; | 22 | export 'widgets/basic.dart'; |
@@ -17,6 +17,7 @@ | @@ -17,6 +17,7 @@ | ||
17 | import 'dart:math' as math; | 17 | import 'dart:math' as math; |
18 | 18 | ||
19 | import 'package:pdf/pdf.dart'; | 19 | import 'package:pdf/pdf.dart'; |
20 | +import 'package:pdf/svg.dart'; | ||
20 | 21 | ||
21 | import 'basic.dart'; | 22 | import 'basic.dart'; |
22 | import 'geometry.dart'; | 23 | import 'geometry.dart'; |
@@ -84,7 +85,20 @@ class PdfLogo extends StatelessWidget { | @@ -84,7 +85,20 @@ class PdfLogo extends StatelessWidget { | ||
84 | } | 85 | } |
85 | } | 86 | } |
86 | 87 | ||
87 | -class FlutterLogo extends PdfLogo {} | 88 | +class FlutterLogo extends StatelessWidget { |
89 | + FlutterLogo({this.fit = BoxFit.contain}); | ||
90 | + | ||
91 | + final BoxFit fit; | ||
92 | + | ||
93 | + @override | ||
94 | + Widget build(Context context) { | ||
95 | + return SvgImage( | ||
96 | + svg: | ||
97 | + '<?xml version="1.0" encoding="UTF-8"?><svg version="1.1" viewBox="0 0 256 317" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="a" x1="10%" x2="67%" y1="40%" y2="35%"><stop stop-color="#1a237e" stop-opacity=".4" offset="0"/><stop stop-color="#1a237e" stop-opacity="0" offset="1"/></linearGradient></defs><polygon points="157.67 0 0 157.67 48.801 206.47 255.27 0" fill="#54c5f8"/><polygon points="156.57 145.4 72.149 229.82 121.13 279.53 169.84 230.82 255.27 145.4" fill="#54c5f8"/><polygon points="121.13 279.53 158.21 316.61 255.27 316.61 169.84 230.82" fill="#01579b"/><polygon points="71.6 230.36 120.4 181.56 169.84 230.82 121.13 279.53" fill="#29b6f6"/><polygon points="121.13 279.53 189.44 253.83 167.85 233.75" fill="url(#a)" fill-opacity=".8"/></svg>', | ||
98 | + fit: fit, | ||
99 | + ); | ||
100 | + } | ||
101 | +} | ||
88 | 102 | ||
89 | class LoremText { | 103 | class LoremText { |
90 | LoremText({math.Random random}) : random = random ?? math.Random(978); | 104 | LoremText({math.Random random}) : random = random ?? math.Random(978); |
@@ -43,6 +43,7 @@ import 'widget_multipage_test.dart' as widget_multipage; | @@ -43,6 +43,7 @@ import 'widget_multipage_test.dart' as widget_multipage; | ||
43 | import 'widget_opacity_test.dart' as widget_opacity; | 43 | import 'widget_opacity_test.dart' as widget_opacity; |
44 | import 'widget_outline_test.dart' as widget_outline; | 44 | import 'widget_outline_test.dart' as widget_outline; |
45 | import 'widget_partitions_test.dart' as widget_partitions; | 45 | import 'widget_partitions_test.dart' as widget_partitions; |
46 | +import 'widget_svg_test.dart' as widget_svg; | ||
46 | import 'widget_table_test.dart' as widget_table; | 47 | import 'widget_table_test.dart' as widget_table; |
47 | import 'widget_test.dart' as widget; | 48 | import 'widget_test.dart' as widget; |
48 | import 'widget_text_test.dart' as widget_text; | 49 | import 'widget_text_test.dart' as widget_text; |
@@ -78,6 +79,7 @@ void main() { | @@ -78,6 +79,7 @@ void main() { | ||
78 | widget_opacity.main(); | 79 | widget_opacity.main(); |
79 | widget_outline.main(); | 80 | widget_outline.main(); |
80 | widget_partitions.main(); | 81 | widget_partitions.main(); |
82 | + widget_svg.main(); | ||
81 | widget_table.main(); | 83 | widget_table.main(); |
82 | widget_text.main(); | 84 | widget_text.main(); |
83 | widget_theme.main(); | 85 | widget_theme.main(); |
pdf/test/widget_svg_test.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'dart:io'; | ||
18 | + | ||
19 | +import 'package:pdf/pdf.dart'; | ||
20 | +import 'package:pdf/widgets.dart'; | ||
21 | +import 'package:test/test.dart'; | ||
22 | + | ||
23 | +Document pdf; | ||
24 | + | ||
25 | +void main() { | ||
26 | + setUpAll(() { | ||
27 | + Document.debug = true; | ||
28 | + pdf = Document(); | ||
29 | + }); | ||
30 | + | ||
31 | + test('SVG Widgets Flutter logo', () { | ||
32 | + pdf.addPage( | ||
33 | + Page( | ||
34 | + build: (context) => Center( | ||
35 | + child: FlutterLogo(), | ||
36 | + ), | ||
37 | + ), | ||
38 | + ); | ||
39 | + }); | ||
40 | + | ||
41 | + test('SVG Widgets', () { | ||
42 | + print('=' * 120); | ||
43 | + final dir = Directory('../ref/svg'); | ||
44 | + if (!dir.existsSync()) { | ||
45 | + return; | ||
46 | + } | ||
47 | + final files = dir | ||
48 | + .listSync() | ||
49 | + .where((file) => file.path.endsWith('.svg')) | ||
50 | + .map<String>((file) => file.path) | ||
51 | + .toList() | ||
52 | + ..sort(); | ||
53 | + | ||
54 | + pdf.addPage( | ||
55 | + MultiPage( | ||
56 | + build: (context) => [ | ||
57 | + GridView( | ||
58 | + crossAxisCount: 2, | ||
59 | + childAspectRatio: 1, | ||
60 | + mainAxisSpacing: 10, | ||
61 | + crossAxisSpacing: 10, | ||
62 | + children: files.map<Widget>( | ||
63 | + (file) { | ||
64 | + return Container( | ||
65 | + decoration: BoxDecoration( | ||
66 | + border: Border.all(color: PdfColors.blue), | ||
67 | + ), | ||
68 | + child: Column( | ||
69 | + mainAxisSize: MainAxisSize.max, | ||
70 | + children: <Widget>[ | ||
71 | + Expanded( | ||
72 | + child: Center( | ||
73 | + child: SvgImage( | ||
74 | + svg: File(file).readAsStringSync(), | ||
75 | + ), | ||
76 | + ), | ||
77 | + ), | ||
78 | + ClipRect( | ||
79 | + child: Text(file.substring(file.lastIndexOf('/') + 1)), | ||
80 | + ), | ||
81 | + ], | ||
82 | + ), | ||
83 | + ); | ||
84 | + }, | ||
85 | + ).toList(), | ||
86 | + ) | ||
87 | + ], | ||
88 | + ), | ||
89 | + ); | ||
90 | + }); | ||
91 | + | ||
92 | + test('SVG Widgets Text', () { | ||
93 | + pdf.addPage( | ||
94 | + Page( | ||
95 | + build: (context) => SvgImage( | ||
96 | + svg: | ||
97 | + '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1000 300" xmlns="http://www.w3.org/2000/svg" version="1.1"><text x="367.055" y="168.954" font-size="55" fill="dodgerblue" >Hello, PDF</text><rect x="1" y="1" width="998" height="298" fill="none" stroke="purple" stroke-width="2" /></svg>', | ||
98 | + ), | ||
99 | + ), | ||
100 | + ); | ||
101 | + }); | ||
102 | + | ||
103 | + test('SVG Widgets Barcode', () { | ||
104 | + pdf.addPage( | ||
105 | + Page( | ||
106 | + build: (context) => SvgImage( | ||
107 | + svg: Barcode.isbn().toSvg('135459869354'), | ||
108 | + ), | ||
109 | + ), | ||
110 | + ); | ||
111 | + }); | ||
112 | + | ||
113 | + tearDownAll(() { | ||
114 | + final file = File('widgets-svg.pdf'); | ||
115 | + file.writeAsBytesSync(pdf.save()); | ||
116 | + }); | ||
117 | +} |
test/golden/widgets-svg.pdf
0 → 100644
No preview for this file type
-
Please register or login to post a comment