David PHAM-VAN

Add SVG widget

@@ -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
  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 +}
  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 +}
  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 +}
  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 +}
  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 +};
  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 +}
  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 +}
  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 +}
  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 +}
  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 +}
  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 +}
  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 +}
  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 +}
  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 +}
  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 +}
  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 +}
  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);
@@ -17,6 +17,7 @@ dependencies: @@ -17,6 +17,7 @@ dependencies:
17 meta: ^1.1.5 17 meta: ^1.1.5
18 path_parsing: ^0.1.4 18 path_parsing: ^0.1.4
19 vector_math: ^2.0.0 19 vector_math: ^2.0.0
  20 + xml: ^4.0.0
20 21
21 dev_dependencies: 22 dev_dependencies:
22 pedantic: 1.9.2 23 pedantic: 1.9.2
@@ -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();
  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 +}
No preview for this file type