David PHAM-VAN

Add SVG widget

... ... @@ -12,11 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
DART_SRC=$(shell find . -name '*.dart')
CLNG_SRC=$(shell find printing/ios printing/macos printing/windows printing/android -name '*.cpp' -o -name '*.m' -o -name '*.h' -o -name '*.java')
SWFT_SRC=$(shell find printing/ios printing/macos -name '*.swift')
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
COV_PORT=9292
DART_SRC=$(shell find . -name '*.dart')
CLNG_SRC=$(shell find printing/ios printing/macos printing/windows printing/android -name '*.cpp' -o -name '*.m' -o -name '*.h' -o -name '*.java')
SWFT_SRC=$(shell find printing/ios printing/macos -name '*.swift')
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
COV_PORT=9292
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
all: $(FONTS) demo/assets/logo.png demo/assets/profile.jpg format printing/example/.metadata get
... ... @@ -99,7 +100,7 @@ test/pubspec.lock: test/pubspec.yaml
get: $(FONTS) pdf/pubspec.lock printing/pubspec.lock demo/pubspec.lock test/pubspec.lock
test-pdf: $(FONTS) pdf/pubspec.lock .coverage
test-pdf: svg $(FONTS) pdf/pubspec.lock .coverage
cd pdf; pub global run coverage:collect_coverage --port=$(COV_PORT) -o coverage.json --resume-isolates --wait-paused &\
dart --enable-asserts --disable-service-auth-codes --enable-vm-service=$(COV_PORT) --pause-isolates-on-exit test/all_tests.dart
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
cd pdf; dart pub global run dartfix --pedantic --overwrite .
cd printing; dart pub global run dartfix --pedantic --overwrite .
ref:
ref/svg/%.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/simple/$(notdir $@)" > $@
ref/svg/flutter_logo.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/$(notdir $@)" > $@
ref/svg/dart.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/$(notdir $@)" > $@
ref/svg/text_transform.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/$(notdir $@)" > $@
ref/svg/emoji_u1f600.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/noto-emoji/$(notdir $@)" > $@
ref/svg/new-pause-button.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@
ref/svg/new-send-circle.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@
ref/svg/new-gif.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@
ref/svg/new-camera.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@
ref/svg/new-image.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@
ref/svg/numeric_25.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@
ref/svg/new-mention.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@
ref/svg/new-gif-button.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@
ref/svg/new-action-expander.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@
ref/svg/new-play-button.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/$(notdir $@)" > $@
ref/svg/aa.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/w3samples/$(notdir $@)" > $@
ref/svg/alphachannel.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/w3samples/$(notdir $@)" > $@
ref/svg/Ghostscript_Tiger.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/wikimedia/$(notdir $@)" > $@
ref/svg/Firefox_Logo_2017.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/wikimedia/$(notdir $@)" > $@
ref/svg/chess_knight.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/wikimedia/$(notdir $@)" > $@
ref/svg/Flag_of_the_United_States.svg:
mkdir -p ref/svg
curl -L "https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/wikimedia/$(notdir $@)" > $@
svg: $(patsubst %,ref/svg/%.svg,$(SVG))
ref: svg
mkdir -p ref
cd $@; curl -OL 'https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf'
cd $@; curl -OL 'https://www.adobe.com/content/dam/acom/en/devnet/pdf/adobe_supplement_iso32000.pdf'
... ...
... ... @@ -20,6 +20,7 @@
- Fix RichText.maxLines with multiple TextSpan
- Fix Exif parsing
- Add Border and BorderSide objects
- Add basic support for SVG images
## 1.12.0
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart';
import 'package:vector_math/vector_math_64.dart';
import 'package:xml/xml.dart';
import 'svg/painter.dart';
import 'svg/parser.dart';
class SvgImage extends Widget {
factory SvgImage({
@required String svg,
BoxFit fit = BoxFit.contain,
bool clip = true,
double width,
double height,
}) {
assert(clip != null);
final xml = XmlDocument.parse(svg);
final parser = SvgParser(xml: xml);
return SvgImage._fromPainter(
parser,
fit,
clip,
width,
height,
);
}
SvgImage._fromPainter(
this._svgParser,
this.fit,
this.clip,
this.width,
this.height,
) : assert(_svgParser != null),
assert(fit != null);
final SvgParser _svgParser;
final BoxFit fit;
final bool clip;
final double width;
final double height;
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
final w = width != null || _svgParser.width != null
? constraints.constrainWidth(width ?? _svgParser.width)
: constraints.hasBoundedWidth
? constraints.maxWidth
: constraints.constrainWidth(_svgParser.viewBox.width);
final h = height != null || _svgParser.height != null
? constraints.constrainHeight(height ?? _svgParser.height)
: constraints.hasBoundedHeight
? constraints.maxHeight
: constraints.constrainHeight(_svgParser.viewBox.height);
final sizes = applyBoxFit(
fit,
PdfPoint(_svgParser.viewBox.width, _svgParser.viewBox.height),
PdfPoint(w, h));
box = PdfRect.fromPoints(PdfPoint.zero, sizes.destination);
}
@override
void paint(Context context) {
super.paint(context);
final mat = Matrix4.identity();
mat.translate(
box.x,
box.y + box.height,
);
mat.scale(
box.width / _svgParser.viewBox.width,
-box.height / _svgParser.viewBox.height,
);
mat.translate(
-_svgParser.viewBox.x,
-_svgParser.viewBox.y,
);
context.canvas.saveContext();
if (clip) {
context.canvas
..drawBox(box)
..clipPath();
}
context.canvas.setTransform(mat);
final painter = SvgPainter(
_svgParser,
context.canvas,
context.document,
PdfRect(
0,
0,
context.page.pageFormat.width,
context.page.pageFormat.height,
),
);
painter.paint();
context.canvas.restoreContext();
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/svg/painter.dart';
import 'package:xml/xml.dart';
import 'color.dart';
import 'mask_path.dart';
import 'parser.dart';
enum SvgTextAnchor { start, middle, end }
@immutable
class SvgBrush {
const SvgBrush({
@required this.opacity,
@required this.fill,
@required this.fillEvenOdd,
@required this.fillOpacity,
@required this.stroke,
@required this.strokeOpacity,
@required this.strokeWidth,
@required this.strokeDashArray,
@required this.strokeDashOffset,
@required this.strokeLineCap,
@required this.strokeLineJoin,
@required this.strokeMiterLimit,
@required this.fontFamily,
@required this.fontSize,
@required this.fontStyle,
@required this.fontWeight,
@required this.textAnchor,
@required this.blendMode,
this.mask,
});
factory SvgBrush.fromXml(
XmlElement element,
SvgBrush parent,
SvgPainter painter,
) {
SvgParser.convertStyle(element);
final strokeDashArray = element.getAttribute('stroke-dasharray');
final fillRule = element.getAttribute('fill-rule');
final strokeLineCap = element.getAttribute('stroke-linecap');
final strokeLineJoin = element.getAttribute('stroke-linejoin');
final blendMode = element.getAttribute('mix-blend-mode');
final result = parent.merge(SvgBrush(
opacity: SvgParser.getDouble(element, 'opacity', defaultValue: null),
blendMode: blendMode == null ? null : _blendModes[blendMode],
fillOpacity:
SvgParser.getDouble(element, 'fill-opacity', defaultValue: null),
strokeOpacity:
SvgParser.getDouble(element, 'stroke-opacity', defaultValue: null),
strokeLineCap:
strokeLineCap == null ? null : _strokeLineCap[strokeLineCap],
strokeLineJoin:
strokeLineJoin == null ? null : _strokeLineJoin[strokeLineJoin],
strokeMiterLimit:
SvgParser.getDouble(element, 'stroke-miterlimit', defaultValue: null),
fill: SvgColor.fromXml(element.getAttribute('fill'), painter),
fillEvenOdd: fillRule == null ? null : fillRule == 'evenodd',
stroke: SvgColor.fromXml(element.getAttribute('stroke'), painter),
strokeWidth: SvgParser.getNumeric(element, 'stroke-width', parent),
strokeDashArray: strokeDashArray == null
? null
: (strokeDashArray == 'none'
? []
: SvgParser.splitDoubles(strokeDashArray).toList()),
strokeDashOffset:
SvgParser.getNumeric(element, 'stroke-dashoffset', parent)?.sizeValue,
fontSize: SvgParser.getNumeric(element, 'font-size', parent),
fontFamily: element.getAttribute('font-family'),
fontStyle: element.getAttribute('font-style'),
fontWeight: element.getAttribute('font-weight'),
textAnchor: _textAnchors[element.getAttribute('text-anchor')],
));
final mask = SvgMaskPath.fromXml(element, painter, result);
if (mask != null) {
return result.copyWith(mask: mask);
}
return result;
}
static const defaultContext = SvgBrush(
opacity: 1,
blendMode: null,
fillOpacity: 1,
strokeOpacity: 1,
fill: SvgColor.defaultColor,
fillEvenOdd: false,
stroke: SvgColor.none,
strokeLineCap: PdfLineCap.butt,
strokeLineJoin: PdfLineJoin.miter,
strokeMiterLimit: 4,
strokeWidth: SvgNumeric.value(1, null, SvgUnit.pixels),
strokeDashArray: [],
strokeDashOffset: 0,
fontSize: SvgNumeric.value(16, null),
fontFamily: 'sans-serif',
fontWeight: 'normal',
fontStyle: 'normal',
textAnchor: SvgTextAnchor.start,
mask: null,
);
static const _blendModes = <String, PdfBlendMode>{
'normal': PdfBlendMode.normal,
'multiply': PdfBlendMode.multiply,
'screen': PdfBlendMode.screen,
'overlay': PdfBlendMode.overlay,
'darken': PdfBlendMode.darken,
'lighten': PdfBlendMode.lighten,
'color-dodge': PdfBlendMode.color,
'color-burn': PdfBlendMode.color,
'hard-light': PdfBlendMode.hardLight,
'soft-light': PdfBlendMode.softLight,
'difference': PdfBlendMode.difference,
'exclusion': PdfBlendMode.exclusion,
'hue': PdfBlendMode.hue,
'saturation': PdfBlendMode.saturation,
'color': PdfBlendMode.color,
'luminosity': PdfBlendMode.luminosity,
};
static const _strokeLineCap = <String, PdfLineCap>{
'butt': PdfLineCap.butt,
'round': PdfLineCap.round,
'square': PdfLineCap.square,
};
static const _strokeLineJoin = <String, PdfLineJoin>{
'miter ': PdfLineJoin.miter,
'bevel': PdfLineJoin.bevel,
'round': PdfLineJoin.round,
};
static const _textAnchors = <String, SvgTextAnchor>{
'start': SvgTextAnchor.start,
'middle': SvgTextAnchor.middle,
'end': SvgTextAnchor.end,
};
final double opacity;
final SvgColor fill;
final bool fillEvenOdd;
final double fillOpacity;
final SvgColor stroke;
final double strokeOpacity;
final SvgNumeric strokeWidth;
final List<double> strokeDashArray;
final double strokeDashOffset;
final PdfLineCap strokeLineCap;
final PdfLineJoin strokeLineJoin;
final double strokeMiterLimit;
final SvgNumeric fontSize;
final String fontFamily;
final String fontStyle;
final String fontWeight;
final SvgTextAnchor textAnchor;
final PdfBlendMode blendMode;
final SvgMaskPath mask;
SvgBrush merge(SvgBrush other) {
if (other == null) {
return this;
}
var _fill = other.fill ?? fill;
if (_fill.inherit) {
_fill = fill.merge(other.fill);
}
var _stroke = other.stroke ?? stroke;
if (_stroke.inherit) {
_stroke = stroke.merge(other.stroke);
}
return SvgBrush(
opacity: other.opacity ?? 1.0,
blendMode: other.blendMode,
fillOpacity: other.fillOpacity ?? fillOpacity,
strokeOpacity: other.strokeOpacity ?? strokeOpacity,
fill: _fill,
fillEvenOdd: other.fillEvenOdd ?? fillEvenOdd,
stroke: _stroke,
strokeWidth: other.strokeWidth ?? strokeWidth,
strokeDashArray: other.strokeDashArray ?? strokeDashArray,
strokeDashOffset: other.strokeDashOffset ?? strokeDashOffset,
fontSize: other.fontSize ?? fontSize,
fontFamily: other.fontFamily ?? fontFamily,
fontStyle: other.fontStyle ?? fontStyle,
fontWeight: other.fontWeight ?? fontWeight,
textAnchor: other.textAnchor ?? textAnchor,
strokeLineCap: other.strokeLineCap ?? strokeLineCap,
strokeLineJoin: other.strokeLineJoin ?? strokeLineJoin,
strokeMiterLimit: other.strokeMiterLimit ?? strokeMiterLimit,
mask: other.mask,
);
}
SvgBrush copyWith({
double opacity,
SvgColor fill,
bool fillEvenOdd,
double fillOpacity,
SvgColor stroke,
double strokeOpacity,
SvgNumeric strokeWidth,
List<double> strokeDashArray,
double strokeDashOffset,
PdfLineCap strokeLineCap,
PdfLineJoin strokeLineJoin,
double strokeMiterLimit,
SvgNumeric fontSize,
String fontFamily,
String fontStyle,
String fontWeight,
SvgTextAnchor textAnchor,
PdfBlendMode blendMode,
SvgMaskPath mask,
}) {
return SvgBrush(
opacity: opacity ?? this.opacity,
fill: fill ?? this.fill,
fillEvenOdd: fillEvenOdd ?? this.fillEvenOdd,
fillOpacity: fillOpacity ?? this.fillOpacity,
stroke: stroke ?? this.stroke,
strokeOpacity: strokeOpacity ?? this.strokeOpacity,
strokeWidth: strokeWidth ?? this.strokeWidth,
strokeDashArray: strokeDashArray ?? this.strokeDashArray,
strokeDashOffset: strokeDashOffset ?? this.strokeDashOffset,
strokeLineCap: strokeLineCap ?? this.strokeLineCap,
strokeLineJoin: strokeLineJoin ?? this.strokeLineJoin,
strokeMiterLimit: strokeMiterLimit ?? this.strokeMiterLimit,
fontSize: fontSize ?? this.fontSize,
fontFamily: fontFamily ?? this.fontFamily,
fontStyle: fontStyle ?? this.fontStyle,
fontWeight: fontWeight ?? this.fontWeight,
textAnchor: textAnchor ?? this.textAnchor,
blendMode: blendMode ?? this.blendMode,
mask: mask ?? this.mask,
);
}
@override
String toString() =>
'$runtimeType fill: $fill fillEvenOdd: $fillEvenOdd stroke:$stroke strokeWidth:$strokeWidth strokeDashArray:$strokeDashArray fontSize:$fontSize fontFamily:$fontFamily textAnchor:$textAnchor ';
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';
import 'package:xml/xml.dart';
import 'brush.dart';
import 'operation.dart';
import 'painter.dart';
@immutable
class SvgClipPath {
const SvgClipPath(this.children, this.isEmpty, this.painter);
factory SvgClipPath.fromXml(
XmlElement element, SvgPainter painter, SvgBrush brush) {
final clipPathAttr = element.getAttribute('clip-path');
if (clipPathAttr == null) {
return const SvgClipPath(null, true, null);
}
Iterable<SvgOperation> children;
if (clipPathAttr.startsWith('url(#')) {
final id = clipPathAttr.substring(5, clipPathAttr.lastIndexOf(')'));
final clipPath = painter.parser.findById(id);
if (clipPath != null) {
children = clipPath.children
.whereType<XmlElement>()
.map<SvgOperation>((c) => SvgOperation.fromXml(c, painter, brush));
return SvgClipPath(children, false, painter);
}
}
return const SvgClipPath(null, true, null);
}
final Iterable<SvgOperation> children;
final bool isEmpty;
final SvgPainter painter;
bool get isNotEmpty => !isEmpty;
void apply(PdfGraphics canvas) {
if (isEmpty) {
return;
}
for (final child in children) {
child.draw(canvas);
}
canvas.clipPath();
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:pdf/pdf.dart';
import 'colors.dart';
import 'gradient.dart';
import 'operation.dart';
import 'painter.dart';
import 'parser.dart';
class SvgColor {
const SvgColor({
this.color,
this.opacity,
this.inherit = false,
});
factory SvgColor.fromXml(String color, SvgPainter painter) {
if (color == null) {
return inherited;
}
if (color == 'none') {
return none;
}
if (svgColors.containsKey(color)) {
return SvgColor(color: svgColors[color]);
}
// handle rgba() colors e.g. rgba(255, 255, 255, 1.0)
if (color.toLowerCase().startsWith('rgba')) {
final rgba = SvgParser.splitNumeric(
color.substring(color.indexOf('(') + 1, color.indexOf(')')),
null,
).toList();
return SvgColor(
color: PdfColor(
rgba[0].colorValue,
rgba[1].colorValue,
rgba[2].colorValue,
rgba[3].value,
),
);
}
// handle hsl() colors e.g. hsl(255, 255, 255)
if (color.toLowerCase().startsWith('hsl')) {
final hsl = SvgParser.splitNumeric(
color.substring(color.indexOf('(') + 1, color.indexOf(')')),
null,
).toList();
return SvgColor(
color: PdfColorHsl(
hsl[0].colorValue,
hsl[1].colorValue,
hsl[2].colorValue,
),
);
}
// handle rgb() colors e.g. rgb(255, 255, 255)
if (color.toLowerCase().startsWith('rgb')) {
final rgb = SvgParser.splitNumeric(
color.substring(color.indexOf('(') + 1, color.indexOf(')')),
null,
).toList();
return SvgColor(
color: PdfColor(
rgb[0].colorValue,
rgb[1].colorValue,
rgb[2].colorValue,
),
);
}
if (color.toLowerCase().startsWith('url(#')) {
final gradient =
painter.parser.findById(color.substring(5, color.indexOf(')')));
if (gradient.name.local == 'linearGradient') {
return SvgLinearGradient.fromXml(gradient, painter);
}
if (gradient.name.local == 'radialGradient') {
return SvgRadialGradient.fromXml(gradient, painter);
}
return SvgColor.unknown;
}
try {
return SvgColor(color: PdfColor.fromHex(color));
} catch (e) {
print('Unknown color: $color');
return SvgColor.unknown;
}
}
static const unknown = SvgColor();
static const defaultColor = SvgColor(color: PdfColors.black);
static const none = SvgColor();
static const inherited = SvgColor(inherit: true);
final PdfColor color;
final double opacity;
final bool inherit;
bool get isEmpty => color == null;
bool get isNotEmpty => !isEmpty;
SvgColor merge(SvgColor other) {
return SvgColor(
color: other.color ?? color,
);
}
void setFillColor(SvgOperation op, PdfGraphics canvas) {
if (isEmpty) {
return;
}
canvas.setFillColor(color);
}
void setStrokeColor(SvgOperation op, PdfGraphics canvas) {
if (isEmpty) {
return;
}
canvas.setStrokeColor(color);
}
@override
String toString() =>
'$runtimeType color: $color inherit:$inherit isEmpty: $isEmpty';
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:pdf/pdf.dart';
const svgColors = <String, PdfColor>{
'indigo': PdfColor.fromInt(0xff4b0082),
'gold': PdfColor.fromInt(0xffffd700),
'hotpink': PdfColor.fromInt(0xffff69b4),
'firebrick': PdfColor.fromInt(0xffb22222),
'indianred': PdfColor.fromInt(0xffcd5c5c),
'yellow': PdfColor.fromInt(0xffffff00),
'mistyrose': PdfColor.fromInt(0xffffe4e1),
'darkolivegreen': PdfColor.fromInt(0xff556b2f),
'olive': PdfColor.fromInt(0xff808000),
'darkseagreen': PdfColor.fromInt(0xff8fbc8f),
'pink': PdfColor.fromInt(0xffffc0cb),
'tomato': PdfColor.fromInt(0xffff6347),
'lightcoral': PdfColor.fromInt(0xfff08080),
'orangered': PdfColor.fromInt(0xffff4500),
'navajowhite': PdfColor.fromInt(0xffffdead),
'lime': PdfColor.fromInt(0xff00ff00),
'palegreen': PdfColor.fromInt(0xff98fb98),
'darkslategrey': PdfColor.fromInt(0xff2f4f4f),
'greenyellow': PdfColor.fromInt(0xffadff2f),
'burlywood': PdfColor.fromInt(0xffdeb887),
'seashell': PdfColor.fromInt(0xfffff5ee),
'mediumspringgreen': PdfColor.fromInt(0xff00fa9a),
'fuchsia': PdfColor.fromInt(0xffff00ff),
'papayawhip': PdfColor.fromInt(0xffffefd5),
'blanchedalmond': PdfColor.fromInt(0xffffebcd),
'transparent': PdfColor.fromInt(0xffffff),
'chartreuse': PdfColor.fromInt(0xff7fff00),
'dimgray': PdfColor.fromInt(0xff696969),
'black': PdfColor.fromInt(0xff000000),
'peachpuff': PdfColor.fromInt(0xffffdab9),
'springgreen': PdfColor.fromInt(0xff00ff7f),
'aquamarine': PdfColor.fromInt(0xff7fffd4),
'white': PdfColor.fromInt(0xffffffff),
'orange': PdfColor.fromInt(0xffffa500),
'lightsalmon': PdfColor.fromInt(0xffffa07a),
'darkslategray': PdfColor.fromInt(0xff2f4f4f),
'brown': PdfColor.fromInt(0xffa52a2a),
'ivory': PdfColor.fromInt(0xfffffff0),
'dodgerblue': PdfColor.fromInt(0xff1e90ff),
'peru': PdfColor.fromInt(0xffcd853f),
'lawngreen': PdfColor.fromInt(0xff7cfc00),
'chocolate': PdfColor.fromInt(0xffd2691e),
'crimson': PdfColor.fromInt(0xffdc143c),
'forestgreen': PdfColor.fromInt(0xff228b22),
'darkgrey': PdfColor.fromInt(0xffa9a9a9),
'lightseagreen': PdfColor.fromInt(0xff20b2aa),
'cyan': PdfColor.fromInt(0xff00ffff),
'mintcream': PdfColor.fromInt(0xfff5fffa),
'silver': PdfColor.fromInt(0xffc0c0c0),
'antiquewhite': PdfColor.fromInt(0xfffaebd7),
'mediumorchid': PdfColor.fromInt(0xffba55d3),
'skyblue': PdfColor.fromInt(0xff87ceeb),
'gray': PdfColor.fromInt(0xff808080),
'darkturquoise': PdfColor.fromInt(0xff00ced1),
'goldenrod': PdfColor.fromInt(0xffdaa520),
'darkgreen': PdfColor.fromInt(0xff006400),
'floralwhite': PdfColor.fromInt(0xfffffaf0),
'darkviolet': PdfColor.fromInt(0xff9400d3),
'darkgray': PdfColor.fromInt(0xffa9a9a9),
'moccasin': PdfColor.fromInt(0xffffe4b5),
'saddlebrown': PdfColor.fromInt(0xff8b4513),
'grey': PdfColor.fromInt(0xff808080),
'darkslateblue': PdfColor.fromInt(0xff483d8b),
'lightskyblue': PdfColor.fromInt(0xff87cefa),
'lightpink': PdfColor.fromInt(0xffffb6c1),
'mediumvioletred': PdfColor.fromInt(0xffc71585),
'slategrey': PdfColor.fromInt(0xff708090),
'red': PdfColor.fromInt(0xffff0000),
'deeppink': PdfColor.fromInt(0xffff1493),
'limegreen': PdfColor.fromInt(0xff32cd32),
'darkmagenta': PdfColor.fromInt(0xff8b008b),
'palegoldenrod': PdfColor.fromInt(0xffeee8aa),
'plum': PdfColor.fromInt(0xffdda0dd),
'turquoise': PdfColor.fromInt(0xff40e0d0),
'lightgrey': PdfColor.fromInt(0xffd3d3d3),
'lightgoldenrodyellow': PdfColor.fromInt(0xfffafad2),
'darkgoldenrod': PdfColor.fromInt(0xffb8860b),
'lavender': PdfColor.fromInt(0xffe6e6fa),
'maroon': PdfColor.fromInt(0xff800000),
'yellowgreen': PdfColor.fromInt(0xff9acd32),
'sandybrown': PdfColor.fromInt(0xfff4a460),
'thistle': PdfColor.fromInt(0xffd8bfd8),
'violet': PdfColor.fromInt(0xffee82ee),
'navy': PdfColor.fromInt(0xff000080),
'magenta': PdfColor.fromInt(0xffff00ff),
'dimgrey': PdfColor.fromInt(0xff696969),
'tan': PdfColor.fromInt(0xffd2b48c),
'rosybrown': PdfColor.fromInt(0xffbc8f8f),
'olivedrab': PdfColor.fromInt(0xff6b8e23),
'blue': PdfColor.fromInt(0xff0000ff),
'lightblue': PdfColor.fromInt(0xffadd8e6),
'ghostwhite': PdfColor.fromInt(0xfff8f8ff),
'honeydew': PdfColor.fromInt(0xfff0fff0),
'cornflowerblue': PdfColor.fromInt(0xff6495ed),
'slateblue': PdfColor.fromInt(0xff6a5acd),
'linen': PdfColor.fromInt(0xfffaf0e6),
'darkblue': PdfColor.fromInt(0xff00008b),
'powderblue': PdfColor.fromInt(0xffb0e0e6),
'seagreen': PdfColor.fromInt(0xff2e8b57),
'darkkhaki': PdfColor.fromInt(0xffbdb76b),
'snow': PdfColor.fromInt(0xfffffafa),
'sienna': PdfColor.fromInt(0xffa0522d),
'mediumblue': PdfColor.fromInt(0xff0000cd),
'royalblue': PdfColor.fromInt(0xff4169e1),
'lightcyan': PdfColor.fromInt(0xffe0ffff),
'green': PdfColor.fromInt(0xff008000),
'mediumpurple': PdfColor.fromInt(0xff9370db),
'midnightblue': PdfColor.fromInt(0xff191970),
'cornsilk': PdfColor.fromInt(0xfffff8dc),
'paleturquoise': PdfColor.fromInt(0xffafeeee),
'bisque': PdfColor.fromInt(0xffffe4c4),
'slategray': PdfColor.fromInt(0xff708090),
'darkcyan': PdfColor.fromInt(0xff008b8b),
'khaki': PdfColor.fromInt(0xfff0e68c),
'wheat': PdfColor.fromInt(0xfff5deb3),
'teal': PdfColor.fromInt(0xff008080),
'darkorchid': PdfColor.fromInt(0xff9932cc),
'deepskyblue': PdfColor.fromInt(0xff00bfff),
'salmon': PdfColor.fromInt(0xfffa8072),
'darkred': PdfColor.fromInt(0xff8b0000),
'steelblue': PdfColor.fromInt(0xff4682b4),
'palevioletred': PdfColor.fromInt(0xffdb7093),
'lightslategray': PdfColor.fromInt(0xff778899),
'aliceblue': PdfColor.fromInt(0xfff0f8ff),
'lightslategrey': PdfColor.fromInt(0xff778899),
'lightgreen': PdfColor.fromInt(0xff90ee90),
'orchid': PdfColor.fromInt(0xffda70d6),
'gainsboro': PdfColor.fromInt(0xffdcdcdc),
'mediumseagreen': PdfColor.fromInt(0xff3cb371),
'lightgray': PdfColor.fromInt(0xffd3d3d3),
'mediumturquoise': PdfColor.fromInt(0xff48d1cc),
'lemonchiffon': PdfColor.fromInt(0xfffffacd),
'cadetblue': PdfColor.fromInt(0xff5f9ea0),
'lightyellow': PdfColor.fromInt(0xffffffe0),
'lavenderblush': PdfColor.fromInt(0xfffff0f5),
'coral': PdfColor.fromInt(0xffff7f50),
'purple': PdfColor.fromInt(0xff800080),
'aqua': PdfColor.fromInt(0xff00ffff),
'whitesmoke': PdfColor.fromInt(0xfff5f5f5),
'mediumslateblue': PdfColor.fromInt(0xff7b68ee),
'darkorange': PdfColor.fromInt(0xffff8c00),
'mediumaquamarine': PdfColor.fromInt(0xff66cdaa),
'darksalmon': PdfColor.fromInt(0xffe9967a),
'beige': PdfColor.fromInt(0xfff5f5dc),
'blueviolet': PdfColor.fromInt(0xff8a2be2),
'azure': PdfColor.fromInt(0xfff0ffff),
'lightsteelblue': PdfColor.fromInt(0xffb0c4de),
'oldlace': PdfColor.fromInt(0xfffdf5e6),
};
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:pdf/pdf.dart';
import 'package:xml/xml.dart';
import 'color.dart';
import 'operation.dart';
import 'painter.dart';
import 'parser.dart';
import 'transform.dart';
enum GradientUnits {
objectBoundingBox,
userSpaceOnUse,
}
abstract class SvgGradient extends SvgColor {
const SvgGradient(
this.gradientUnits,
this.transform,
this.colors,
this.stops,
this.opacityList,
) : assert(colors.length == stops.length),
assert(stops.length == opacityList.length),
super();
final GradientUnits gradientUnits;
final SvgTransform transform;
final List<PdfColor> colors;
final List<double> stops;
final List<double> opacityList;
@override
bool get isEmpty => colors.isEmpty;
PdfPattern buildGradient(
SvgOperation op, PdfGraphics canvas, List<PdfColor> colors);
@override
void setFillColor(SvgOperation op, PdfGraphics canvas) {
if (isEmpty) {
return;
}
canvas.setFillPattern(buildGradient(op, canvas, colors));
if (opacityList.any((o) => o < 1)) {
final mask = PdfSoftMask(
op.painter.document,
boundingBox: op.painter.boundingBox,
);
canvas.setGraphicState(
PdfGraphicState(
softMask: mask,
),
);
final maskCanvas = mask.getGraphics();
maskCanvas.drawBox(op.boundingBox());
maskCanvas.setFillPattern(
buildGradient(
op,
maskCanvas,
opacityList.map<PdfColor>((o) => PdfColor(o, o, o)).toList(),
),
);
maskCanvas.fillPath();
canvas.setFillPattern(buildGradient(op, canvas, colors));
}
}
@override
void setStrokeColor(SvgOperation op, PdfGraphics canvas) {
if (isEmpty) {
return;
}
canvas.setStrokePattern(buildGradient(op, canvas, colors));
}
}
class SvgLinearGradient extends SvgGradient {
const SvgLinearGradient(
GradientUnits gradientUnits,
this.x1,
this.y1,
this.x2,
this.y2,
SvgTransform transform,
List<PdfColor> colors,
List<double> stops,
List<double> opacityList)
: super(gradientUnits, transform, colors, stops, opacityList);
factory SvgLinearGradient.fromXml(XmlElement element, SvgPainter painter) {
final x1 = SvgParser.getNumeric(element, 'x1', null)?.sizeValue;
final y1 = SvgParser.getNumeric(element, 'y1', null)?.sizeValue;
final x2 = SvgParser.getNumeric(element, 'x2', null)?.sizeValue;
final y2 = SvgParser.getNumeric(element, 'y2', null)?.sizeValue;
final colors = <PdfColor>[];
final stops = <double>[];
final opacityList = <double>[];
for (final child in element.children
.whereType<XmlElement>()
.where((e) => e.name.local == 'stop')) {
SvgParser.convertStyle(child);
final color = SvgColor.fromXml(
child.getAttribute('stop-color') ?? 'black', painter);
final opacity =
SvgParser.getDouble(child, 'stop-opacity', defaultValue: 1);
final stop = SvgParser.getNumeric(child, 'offset', null, defaultValue: 0)
.sizeValue;
colors.add(color.color);
stops.add(stop);
opacityList.add(opacity);
}
GradientUnits gradientUnits;
switch (element.getAttribute('gradientUnits')) {
case 'userSpaceOnUse':
gradientUnits = GradientUnits.userSpaceOnUse;
break;
case 'objectBoundingBox':
gradientUnits = GradientUnits.objectBoundingBox;
break;
}
final result = SvgLinearGradient(
gradientUnits,
x1,
y1,
x2,
y2,
SvgTransform.fromString(element.getAttribute('gradientTransform')),
colors,
stops,
opacityList,
);
SvgLinearGradient href;
final hrefAttr = element.getAttribute('href') ??
element.getAttribute('href', namespace: 'http://www.w3.org/1999/xlink');
if (hrefAttr != null) {
final hrefElement = painter.parser.findById(hrefAttr.substring(1));
if (hrefElement != null) {
href = SvgLinearGradient.fromXml(hrefElement, painter);
return href.mergeWith(result);
}
}
return result;
}
final double x1;
final double y1;
final double x2;
final double y2;
SvgLinearGradient mergeWith(SvgLinearGradient other) {
return SvgLinearGradient(
other.gradientUnits ?? gradientUnits,
other.x1 ?? x1,
other.y1 ?? y1,
other.x2 ?? x2,
other.y2 ?? y2,
other.transform.isNotEmpty ? other.transform : transform,
other.colors.isNotEmpty ? other.colors : colors,
other.stops.isNotEmpty ? other.stops : stops,
other.opacityList.isNotEmpty ? other.opacityList : opacityList,
);
}
@override
PdfPattern buildGradient(
SvgOperation op, PdfGraphics canvas, List<PdfColor> colors) {
final mat = canvas.getTransform();
if (gradientUnits != GradientUnits.userSpaceOnUse) {
final bb = op.boundingBox();
mat
..translate(bb.x, bb.y)
..scale(bb.width, bb.height);
}
if (transform.isNotEmpty) {
mat.multiply(transform.matrix);
}
return PdfShadingPattern(
op.painter.document,
shading: PdfShading(
op.painter.document,
shadingType: PdfShadingType.axial,
function: PdfBaseFunction.colorsAndStops(
op.painter.document,
colors,
stops,
),
start: PdfPoint(x1 ?? 0, y1 ?? 0),
end: PdfPoint(x2 ?? 1, y2 ?? 0),
extendStart: true,
extendEnd: true,
),
matrix: mat,
);
}
@override
String toString() =>
'$runtimeType userSpace:$gradientUnits x1:$x1 y1:$y1 x2:$x2 y2:$y2 colors:$colors stops:$stops opacityList:$opacityList';
}
class SvgRadialGradient extends SvgGradient {
const SvgRadialGradient(
GradientUnits gradientUnits,
this.r,
this.cx,
this.cy,
this.fr,
this.fx,
this.fy,
SvgTransform transform,
List<PdfColor> colors,
List<double> stops,
List<double> opacityList,
) : super(gradientUnits, transform, colors, stops, opacityList);
factory SvgRadialGradient.fromXml(XmlElement element, SvgPainter painter) {
final r =
SvgParser.getNumeric(element, 'r', null, defaultValue: .5).sizeValue;
final cx =
SvgParser.getNumeric(element, 'cx', null, defaultValue: .5).sizeValue;
final cy =
SvgParser.getNumeric(element, 'cy', null, defaultValue: .5).sizeValue;
final fr =
SvgParser.getNumeric(element, 'fr', null, defaultValue: 0).sizeValue;
final fx =
SvgParser.getNumeric(element, 'fx', null, defaultValue: cx).sizeValue;
final fy =
SvgParser.getNumeric(element, 'fy', null, defaultValue: cy).sizeValue;
final colors = <PdfColor>[];
final stops = <double>[];
final opacityList = <double>[];
for (final child in element.children
.whereType<XmlElement>()
.where((e) => e.name.local == 'stop')) {
SvgParser.convertStyle(child);
final color = SvgColor.fromXml(
child.getAttribute('stop-color') ?? 'black', painter);
final opacity =
SvgParser.getDouble(child, 'stop-opacity', defaultValue: 1);
final stop = SvgParser.getNumeric(child, 'offset', null, defaultValue: 0)
.sizeValue;
colors.add(color.color);
stops.add(stop);
opacityList.add(opacity);
}
GradientUnits gradientUnits;
switch (element.getAttribute('gradientUnits')) {
case 'userSpaceOnUse':
gradientUnits = GradientUnits.userSpaceOnUse;
break;
case 'objectBoundingBox':
gradientUnits = GradientUnits.objectBoundingBox;
break;
}
final result = SvgRadialGradient(
gradientUnits,
r,
cx,
cy,
fr,
fx,
fy,
SvgTransform.fromString(element.getAttribute('gradientTransform')),
colors,
stops,
opacityList);
SvgRadialGradient href;
final hrefAttr = element.getAttribute('href') ??
element.getAttribute('href', namespace: 'http://www.w3.org/1999/xlink');
if (hrefAttr != null) {
final hrefElement = painter.parser.findById(hrefAttr.substring(1));
if (hrefElement != null) {
href = SvgRadialGradient.fromXml(hrefElement, painter);
return href.mergeWith(result);
}
}
return result;
}
final double r;
final double cx;
final double cy;
final double fr;
final double fx;
final double fy;
SvgRadialGradient mergeWith(SvgRadialGradient other) {
return SvgRadialGradient(
other.gradientUnits ?? gradientUnits,
other.r ?? r,
other.cx ?? cx,
other.cy ?? cy,
other.fr ?? fr,
other.fx ?? fx,
other.fy ?? fy,
other.transform.isNotEmpty ? other.transform : transform,
other.colors.isNotEmpty ? other.colors : colors,
other.stops.isNotEmpty ? other.stops : stops,
other.opacityList.isNotEmpty ? other.opacityList : opacityList,
);
}
@override
PdfPattern buildGradient(
SvgOperation op, PdfGraphics canvas, List<PdfColor> colors) {
final mat = canvas.getTransform();
if (gradientUnits != GradientUnits.userSpaceOnUse) {
final bb = op.boundingBox();
mat
..translate(bb.x, bb.y)
..scale(bb.width, bb.height);
}
if (transform.isNotEmpty) {
mat.multiply(transform.matrix);
}
return PdfShadingPattern(
op.painter.document,
shading: PdfShading(
op.painter.document,
shadingType: PdfShadingType.radial,
function: PdfBaseFunction.colorsAndStops(
op.painter.document,
colors,
stops,
),
start: PdfPoint(fx ?? cx ?? .5, fy ?? cy ?? .5),
end: PdfPoint(cx ?? .5, cy ?? .5),
radius0: fr ?? 0,
radius1: r ?? .5,
extendStart: true,
extendEnd: true,
),
matrix: mat,
);
}
@override
String toString() =>
'$runtimeType userSpace:$gradientUnits cx:$cx cy:$cy r:$r fx:$fx fy:$fy fr:$fr colors:$colors stops:$stops opacityList:$opacityList';
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'dart:math';
import 'package:pdf/pdf.dart';
import 'package:xml/xml.dart';
import 'brush.dart';
import 'clip_path.dart';
import 'operation.dart';
import 'painter.dart';
import 'transform.dart';
class SvgGroup extends SvgOperation {
SvgGroup(
this.children,
SvgBrush brush,
SvgClipPath clip,
SvgTransform transform,
SvgPainter painter,
) : super(brush, clip, transform, painter);
factory SvgGroup.fromXml(
XmlElement element, SvgPainter painter, SvgBrush brush) {
final _brush = SvgBrush.fromXml(element, brush, painter);
final children = element.children
.whereType<XmlElement>()
.where((element) => element.name.local != 'symbol')
.map<SvgOperation>(
(child) => SvgOperation.fromXml(child, painter, _brush))
.where((element) => element != null);
return SvgGroup(
children,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
final Iterable<SvgOperation> children;
@override
void paintShape(PdfGraphics canvas) {
for (final child in children) {
child.paint(canvas);
}
}
@override
void drawShape(PdfGraphics canvas) {
for (final child in children) {
child.draw(canvas);
}
}
@override
PdfRect boundingBox() {
var x = double.infinity, y = double.infinity, w = 0.0, h = 0.0;
for (final child in children) {
final b = child.boundingBox();
x = min(b.x, x);
y = min(b.y, y);
w = max(b.width, w);
h = max(b.height, w);
}
return PdfRect(x, y, w, h);
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'dart:convert';
import 'package:image/image.dart' as im;
import 'package:pdf/pdf.dart';
import 'package:vector_math/vector_math_64.dart';
import 'package:xml/xml.dart';
import 'brush.dart';
import 'clip_path.dart';
import 'operation.dart';
import 'painter.dart';
import 'parser.dart';
import 'transform.dart';
class SvgImg extends SvgOperation {
SvgImg(
this.x,
this.y,
this.width,
this.height,
this.image,
SvgBrush brush,
SvgClipPath clip,
SvgTransform transform,
SvgPainter painter,
) : super(brush, clip, transform, painter);
factory SvgImg.fromXml(
XmlElement element,
SvgPainter painter,
SvgBrush brush,
) {
final _brush = SvgBrush.fromXml(element, brush, painter);
final width =
SvgParser.getNumeric(element, 'width', _brush, defaultValue: 0)
.sizeValue;
final height =
SvgParser.getNumeric(element, 'height', _brush, defaultValue: 0)
.sizeValue;
final x =
SvgParser.getNumeric(element, 'x', _brush, defaultValue: 0).sizeValue;
final y =
SvgParser.getNumeric(element, 'y', _brush, defaultValue: 0).sizeValue;
PdfImage image;
final hrefAttr = element.getAttribute('href') ??
element.getAttribute('href', namespace: 'http://www.w3.org/1999/xlink');
if (hrefAttr != null) {
if (hrefAttr.startsWith('data:')) {
final px = hrefAttr.substring(hrefAttr.indexOf(';') + 1);
if (px.startsWith('base64,')) {
final b = px.substring(7).replaceAll(RegExp(r'\s'), '');
final bytes = base64.decode(b);
final img = im.decodeImage(bytes);
image = PdfImage(
painter.document,
image: img.data.buffer.asUint8List(),
width: img.width,
height: img.height,
);
}
}
}
return SvgImg(
x,
y,
width,
height,
image,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
final double x;
final double y;
final double width;
final double height;
final PdfImage image;
@override
void paintShape(PdfGraphics canvas) {
if (image == null) {
return;
}
final sx = width / image.width;
final sy = height / image.height;
canvas
..setTransform(
Matrix4.identity()
..translate(x, y + height, 0)
..scale(sx, -sy),
)
..drawImage(image, 0, 0);
}
@override
void drawShape(PdfGraphics canvas) {}
@override
PdfRect boundingBox() => PdfRect(x, y, width, height);
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';
import 'package:xml/xml.dart';
import 'brush.dart';
import 'operation.dart';
import 'painter.dart';
@immutable
class SvgMaskPath {
const SvgMaskPath(this.children, this.painter);
static SvgMaskPath fromXml(
XmlElement element, SvgPainter painter, SvgBrush brush) {
final maskPathAttr = element.getAttribute('mask');
if (maskPathAttr == null) {
return null;
}
Iterable<SvgOperation> children;
if (maskPathAttr.startsWith('url(#')) {
final id = maskPathAttr.substring(5, maskPathAttr.lastIndexOf(')'));
final maskPath = painter.parser.findById(id);
if (maskPath != null) {
final maskBrush = SvgBrush.fromXml(maskPath, brush, painter);
children = maskPath.children.whereType<XmlElement>().map<SvgOperation>(
(c) => SvgOperation.fromXml(c, painter, maskBrush));
return SvgMaskPath(children, painter);
}
}
return null;
}
final Iterable<SvgOperation> children;
final SvgPainter painter;
void apply(PdfGraphics canvas) {
final mask = PdfSoftMask(
painter.document,
boundingBox: painter.boundingBox,
);
final maskCanvas = mask.getGraphics();
// maskCanvas.setTransform(canvas.getTransform());
for (final child in children) {
child.paint(maskCanvas);
}
canvas.setGraphicState(PdfGraphicState(softMask: mask));
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';
import 'package:xml/xml.dart';
import 'brush.dart';
import 'clip_path.dart';
import 'group.dart';
import 'image.dart';
import 'painter.dart';
import 'path.dart';
import 'symbol.dart';
import 'text.dart';
import 'transform.dart';
import 'use.dart';
abstract class SvgOperation {
SvgOperation(this.brush, this.clip, this.transform, this.painter);
static SvgOperation fromXml(
XmlElement element, SvgPainter painter, SvgBrush brush) {
if (element.getAttribute('visibility') == 'hidden') {
return null;
}
if (element.getAttribute('display') == 'none') {
return null;
}
switch (element.name.local) {
case 'circle':
return SvgPath.fromCircleXml(element, painter, brush);
case 'ellipse':
return SvgPath.fromEllipseXml(element, painter, brush);
case 'g':
return SvgGroup.fromXml(element, painter, brush);
case 'image':
return SvgImg.fromXml(element, painter, brush);
case 'line':
return SvgPath.fromLineXml(element, painter, brush);
case 'path':
return SvgPath.fromXml(element, painter, brush);
case 'polygon':
return SvgPath.fromPolygonXml(element, painter, brush);
case 'polyline':
return SvgPath.fromPolylineXml(element, painter, brush);
case 'rect':
return SvgPath.fromRectXml(element, painter, brush);
case 'symbol':
return SvgSymbol.fromXml(element, painter, brush);
case 'text':
return SvgText.fromXml(element, painter, brush);
case 'use':
return SvgUse.fromXml(element, painter, brush);
}
return null;
}
final SvgBrush brush;
final SvgClipPath clip;
final SvgTransform transform;
final SvgPainter painter;
void paint(PdfGraphics canvas) {
canvas.saveContext();
clip.apply(canvas);
if (transform.isNotEmpty) {
canvas.setTransform(transform.matrix);
}
if (brush.opacity < 1.0 || brush.blendMode != null) {
canvas.setGraphicState(PdfGraphicState(
opacity: brush.opacity == 1 ? null : brush.opacity,
blendMode: brush.blendMode,
));
}
if (brush.mask != null) {
brush.mask.apply(canvas);
}
paintShape(canvas);
canvas.restoreContext();
}
@protected
void paintShape(PdfGraphics canvas);
void draw(PdfGraphics canvas) {
canvas.saveContext();
if (transform.isNotEmpty) {
canvas.setTransform(transform.matrix);
}
drawShape(canvas);
canvas.restoreContext();
}
@protected
void drawShape(PdfGraphics canvas);
PdfRect boundingBox();
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:pdf/pdf.dart';
import 'package:pdf/svg/parser.dart';
import 'package:pdf/widgets.dart';
import 'brush.dart';
import 'group.dart';
class SvgPainter {
SvgPainter(
this.parser,
this._canvas,
this.document,
this.boundingBox,
);
final SvgParser parser;
final PdfGraphics _canvas;
final PdfDocument document;
final PdfRect boundingBox;
void paint() {
SvgGroup.fromXml(
parser.root,
this,
SvgBrush.defaultContext,
).paint(_canvas);
}
final _fontCache = <String, Font>{};
Font getFontCache(String fontFamily, String fontStyle, String fontWeight) {
final cache = '$fontFamily-$fontStyle-$fontWeight';
if (!_fontCache.containsKey(cache)) {
_fontCache[cache] = getFont(fontFamily, fontStyle, fontWeight);
}
return _fontCache[cache];
}
Font getFont(String fontFamily, String fontStyle, String fontWeight) {
switch (fontFamily) {
case 'serif':
switch (fontStyle) {
case 'normal':
switch (fontWeight) {
case 'normal':
case 'lighter':
return Font.times();
}
return Font.timesBold();
}
switch (fontWeight) {
case 'normal':
case 'lighter':
return Font.timesItalic();
}
return Font.timesBoldItalic();
case 'monospace':
switch (fontStyle) {
case 'normal':
switch (fontWeight) {
case 'normal':
case 'lighter':
return Font.courier();
}
return Font.courierBold();
}
switch (fontWeight) {
case 'normal':
case 'lighter':
return Font.courierOblique();
}
return Font.courierBoldOblique();
}
switch (fontStyle) {
case 'normal':
switch (fontWeight) {
case 'normal':
case 'lighter':
return Font.helvetica();
}
return Font.helveticaBold();
}
switch (fontWeight) {
case 'normal':
case 'lighter':
return Font.helveticaOblique();
}
return Font.helveticaBoldOblique();
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';
import 'package:xml/xml.dart';
import 'brush.dart';
class SvgParser {
/// Create an SVG parser
factory SvgParser({@required XmlDocument xml}) {
assert(xml != null);
final root = xml.rootElement;
final vbattr = root.getAttribute('viewBox');
final width = getNumeric(root, 'width', null)?.sizeValue;
final height = getNumeric(root, 'height', null)?.sizeValue;
final vb = vbattr == null
? <double>[0, 0, width ?? 1000, height ?? 1000]
: splitDoubles(vbattr);
if (vb.isEmpty || vb.length > 4) {
throw Exception('viewBox must contain 1..4 parameters');
}
final fvb = [
...List<double>.filled(4 - vb.length, 0),
...vb,
];
final viewBox = PdfRect(fvb[0], fvb[1], fvb[2], fvb[3]);
return SvgParser._(width, height, viewBox, root);
}
SvgParser._(this.width, this.height, this.viewBox, this.root);
final PdfRect viewBox;
final double width;
final double height;
final XmlElement root;
static final _transformParameterRegExp = RegExp(r'[\w.-]+');
XmlElement findById(String id) {
try {
return root.descendants.whereType<XmlElement>().firstWhere(
(e) => e.getAttribute('id') == id,
);
} on StateError {
return null;
}
}
static double getDouble(XmlElement xml, String name,
{String namespace, double defaultValue = 0}) {
final attr = xml.getAttribute(name, namespace: namespace);
if (attr == null) {
return defaultValue;
}
return double.parse(attr);
}
static SvgNumeric getNumeric(XmlElement xml, String name, SvgBrush brush,
{String namespace, double defaultValue}) {
final attr = xml.getAttribute(name, namespace: namespace);
if (attr == null) {
return defaultValue == null ? null : SvgNumeric.value(defaultValue, null);
}
return SvgNumeric(attr, brush);
}
static Iterable<SvgNumeric> splitNumeric(String parameters, SvgBrush brush) {
final parameterMatches = _transformParameterRegExp.allMatches(parameters);
return parameterMatches.map((m) => SvgNumeric(m.group(0), brush));
}
static Iterable<double> splitDoubles(String parameters) {
final parameterMatches = _transformParameterRegExp.allMatches(parameters);
return parameterMatches.map((m) => double.parse(m.group(0)));
}
static Iterable<int> splitIntegers(String parameters) {
final parameterMatches = _transformParameterRegExp.allMatches(parameters);
return parameterMatches.map((m) {
return int.parse(m.group(0));
});
}
/// Convert style to attributes
static void convertStyle(XmlElement element) {
final style = element.getAttribute('style')?.trim();
if (style != null && style.isNotEmpty) {
for (final style in style.split(';')) {
if (style.trim().isEmpty) {
continue;
}
final kv = RegExp(r'([\w-]+)\s*:\s*(.*)').allMatches(style).first;
final key = kv.group(1);
final value = kv.group(2);
element.setAttribute(key, value);
}
}
}
}
enum SvgUnit {
pixels,
milimeters,
centimeters,
inch,
em,
percent,
points,
direct
}
class SvgNumeric {
factory SvgNumeric(String value, SvgBrush brush) {
final r = RegExp(r'([-+]?[\d\.]+)\s*(px|pt|em|cm|mm|in|%|)')
.allMatches(value)
.first;
return SvgNumeric.value(
double.parse(r.group(1)), brush, _svgUnits[r.group(2)]);
}
const SvgNumeric.value(
this.value,
this.brush, [
this.unit = SvgUnit.direct,
]) : assert(value != null),
assert(unit != null);
static const _svgUnits = <String, SvgUnit>{
'px': SvgUnit.pixels,
'mm': SvgUnit.milimeters,
'cm': SvgUnit.centimeters,
'in': SvgUnit.inch,
'em': SvgUnit.em,
'%': SvgUnit.percent,
'pt': SvgUnit.points,
'': SvgUnit.direct,
};
final double value;
final SvgUnit unit;
final SvgBrush brush;
double get colorValue {
switch (unit) {
case SvgUnit.percent:
return value / 100.0;
break;
case SvgUnit.direct:
return value / 255.0;
default:
throw Exception('Invalid color value $value ($unit)');
}
}
double get sizeValue {
switch (unit) {
case SvgUnit.percent:
return value / 100.0;
break;
case SvgUnit.direct:
case SvgUnit.pixels:
case SvgUnit.points:
return value;
case SvgUnit.milimeters:
return value * PdfPageFormat.mm;
case SvgUnit.centimeters:
return value * PdfPageFormat.cm;
case SvgUnit.inch:
return value * PdfPageFormat.inch;
case SvgUnit.em:
return value * brush.fontSize.sizeValue;
}
throw Exception('Invalid size value $value ($unit)');
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:pdf/pdf.dart';
import 'package:xml/xml.dart';
import 'brush.dart';
import 'clip_path.dart';
import 'operation.dart';
import 'painter.dart';
import 'parser.dart';
import 'transform.dart';
class SvgPath extends SvgOperation {
SvgPath(
this.d,
SvgBrush brush,
SvgClipPath clip,
SvgTransform transform,
SvgPainter painter,
) : super(brush, clip, transform, painter);
factory SvgPath.fromXml(
XmlElement element,
SvgPainter painter,
SvgBrush brush,
) {
final d = element.getAttribute('d');
if (d == null) {
throw Exception('Path element must contain "d" attribute');
}
final _brush = SvgBrush.fromXml(element, brush, painter);
return SvgPath(
d,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
factory SvgPath.fromRectXml(
XmlElement element,
SvgPainter painter,
SvgBrush brush,
) {
final _brush = SvgBrush.fromXml(element, brush, painter);
final x =
SvgParser.getNumeric(element, 'x', _brush, defaultValue: 0).sizeValue;
final y =
SvgParser.getNumeric(element, 'y', _brush, defaultValue: 0).sizeValue;
final width =
SvgParser.getNumeric(element, 'width', _brush, defaultValue: 0)
.sizeValue;
final height =
SvgParser.getNumeric(element, 'height', _brush, defaultValue: 0)
.sizeValue;
var rx = SvgParser.getNumeric(element, 'rx', _brush)?.sizeValue;
var ry = SvgParser.getNumeric(element, 'ry', _brush)?.sizeValue;
ry ??= rx ?? 0;
rx ??= ry ?? 0;
final topRight = rx != 0 || ry != 0 ? 'a $rx $ry 0 0 1 $rx $ry' : '';
final bottomRight = rx != 0 || ry != 0 ? 'a $rx $ry 0 0 1 ${-rx} $ry' : '';
final bottomLeft =
rx != 0 || ry != 0 ? 'a $rx $ry 0 0 1 ${-rx} ${-ry}' : '';
final topLeft = rx != 0 || ry != 0 ? 'a $rx $ry 0 0 1 $rx ${-ry}' : '';
final d =
'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';
return SvgPath(
d,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
factory SvgPath.fromCircleXml(
XmlElement element,
SvgPainter painter,
SvgBrush brush,
) {
final _brush = SvgBrush.fromXml(element, brush, painter);
final cx = SvgParser.getNumeric(element, 'cx', _brush).sizeValue;
final cy = SvgParser.getNumeric(element, 'cy', _brush).sizeValue;
final r = SvgParser.getNumeric(element, 'r', _brush).sizeValue;
final d =
'M${cx - r},${cy}A$r,$r 0,0,0 ${cx + r},${cy}A$r,$r 0,0,0 ${cx - r},${cy}z';
return SvgPath(
d,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
factory SvgPath.fromEllipseXml(
XmlElement element,
SvgPainter painter,
SvgBrush brush,
) {
final _brush = SvgBrush.fromXml(element, brush, painter);
final cx = SvgParser.getNumeric(element, 'cx', _brush).sizeValue;
final cy = SvgParser.getNumeric(element, 'cy', _brush).sizeValue;
final rx = SvgParser.getNumeric(element, 'rx', _brush).sizeValue;
final ry = SvgParser.getNumeric(element, 'ry', _brush).sizeValue;
final d =
'M${cx - rx},${cy}A$rx,$ry 0,0,0 ${cx + rx},${cy}A$rx,$ry 0,0,0 ${cx - rx},${cy}z';
return SvgPath(
d,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
factory SvgPath.fromPolylineXml(
XmlElement element,
SvgPainter painter,
SvgBrush brush,
) {
final points = element.getAttribute('points');
final d = 'M$points';
final _brush = SvgBrush.fromXml(element, brush, painter);
return SvgPath(
d,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
factory SvgPath.fromPolygonXml(
XmlElement element,
SvgPainter painter,
SvgBrush brush,
) {
final points = element.getAttribute('points');
final d = 'M${points}z';
final _brush = SvgBrush.fromXml(element, brush, painter);
return SvgPath(
d,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
factory SvgPath.fromLineXml(
XmlElement element,
SvgPainter painter,
SvgBrush brush,
) {
final _brush = SvgBrush.fromXml(element, brush, painter);
final x1 = SvgParser.getNumeric(element, 'x1', _brush).sizeValue;
final y1 = SvgParser.getNumeric(element, 'y1', _brush).sizeValue;
final x2 = SvgParser.getNumeric(element, 'x2', _brush).sizeValue;
final y2 = SvgParser.getNumeric(element, 'y2', _brush).sizeValue;
final d = 'M$x1 $y1 $x2 $y2';
return SvgPath(
d,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
final String d;
@override
void paintShape(PdfGraphics canvas) {
if (brush.fill.isNotEmpty) {
brush.fill.setFillColor(this, canvas);
if (brush.fillOpacity < 1) {
canvas
..saveContext()
..setGraphicState(PdfGraphicState(opacity: brush.fillOpacity));
}
canvas
..drawShape(d)
..fillPath(evenOdd: brush.fillEvenOdd);
if (brush.fillOpacity < 1) {
canvas.restoreContext();
}
}
if (brush.stroke.isNotEmpty) {
brush.stroke.setStrokeColor(this, canvas);
if (brush.strokeOpacity < 1) {
canvas.setGraphicState(PdfGraphicState(opacity: brush.strokeOpacity));
}
canvas
..drawShape(d)
..setLineCap(brush.strokeLineCap)
..setLineJoin(brush.strokeLineJoin)
..setMiterLimit(brush.strokeMiterLimit)
..setLineDashPattern(
brush.strokeDashArray, brush.strokeDashOffset.toInt())
..setLineWidth(brush.strokeWidth.sizeValue)
..strokePath();
}
}
@override
void drawShape(PdfGraphics canvas) {
canvas.drawShape(d);
}
@override
PdfRect boundingBox() {
return PdfGraphics.shapeBoundingBox(d);
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:pdf/pdf.dart';
import 'package:xml/xml.dart';
import 'brush.dart';
import 'clip_path.dart';
import 'group.dart';
import 'operation.dart';
import 'painter.dart';
import 'transform.dart';
class SvgSymbol extends SvgGroup {
SvgSymbol(
Iterable<SvgOperation> children,
SvgBrush brush,
SvgClipPath clip,
SvgTransform transform,
SvgPainter painter,
) : super(children, brush, clip, transform, painter);
factory SvgSymbol.fromXml(
XmlElement element, SvgPainter painter, SvgBrush brush) {
final _brush = SvgBrush.fromXml(element, brush, painter);
final children = element.children
.whereType<XmlElement>()
.map<SvgOperation>(
(child) => SvgOperation.fromXml(child, painter, _brush))
.where((element) => element != null);
return SvgSymbol(
children,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
@override
void paintShape(PdfGraphics canvas) {
for (final child in children) {
child.paint(canvas);
}
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'dart:math';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart';
import 'package:vector_math/vector_math_64.dart';
import 'package:xml/xml.dart';
import 'brush.dart';
import 'clip_path.dart';
import 'operation.dart';
import 'painter.dart';
import 'parser.dart';
import 'transform.dart';
class SvgText extends SvgOperation {
SvgText(
this.x,
this.y,
this.dx,
this.text,
this.font,
this.tspan,
this.metrics,
SvgBrush brush,
SvgClipPath clip,
SvgTransform transform,
SvgPainter painter,
) : super(brush, clip, transform, painter);
factory SvgText.fromXml(
XmlElement element,
SvgPainter painter,
SvgBrush brush, [
PdfPoint offset = PdfPoint.zero,
]) {
final _brush = SvgBrush.fromXml(element, brush, painter);
final dx =
SvgParser.getNumeric(element, 'dx', _brush, defaultValue: 0).sizeValue;
final dy =
SvgParser.getNumeric(element, 'dy', _brush, defaultValue: 0).sizeValue;
final x = SvgParser.getNumeric(element, 'x', _brush)?.sizeValue;
final y = SvgParser.getNumeric(element, 'y', _brush)?.sizeValue;
final text = element.children
.where((node) => node is XmlText || node is XmlCDATA)
.map((node) => node.text)
.join()
.trim();
final font = painter.getFontCache(
_brush.fontFamily, _brush.fontStyle, _brush.fontWeight);
final pdfFont = font.getFont(Context(document: painter.document));
final metrics = pdfFont.stringMetrics(text) * _brush.fontSize.sizeValue;
offset = PdfPoint((x ?? offset.x) + dx, (y ?? offset.y) + dy);
switch (_brush.textAnchor) {
case SvgTextAnchor.start:
break;
case SvgTextAnchor.middle:
offset = PdfPoint(offset.x - metrics.width / 2, offset.y);
break;
case SvgTextAnchor.end:
offset = PdfPoint(offset.x - metrics.width, offset.y);
break;
}
var childOffset = PdfPoint(offset.x + metrics.advanceWidth, offset.y);
final tspan = element.children.whereType<XmlElement>().map<SvgText>((e) {
final child = SvgText.fromXml(e, painter, _brush, childOffset);
childOffset = PdfPoint(child.x + child.dx, child.y);
return child;
});
return SvgText(
offset.x,
offset.y,
metrics.advanceWidth,
text,
pdfFont,
tspan,
metrics,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
final double x;
final double y;
final double dx;
final String text;
final PdfFont font;
final PdfFontMetrics metrics;
final Iterable<SvgText> tspan;
@override
void paintShape(PdfGraphics canvas) {
canvas
..saveContext()
..setTransform(Matrix4.identity()
..scale(1.0, -1.0)
..translate(x, -y));
if (brush.fill.isNotEmpty) {
brush.fill.setFillColor(this, canvas);
if (brush.fillOpacity < 1) {
canvas
..saveContext()
..setGraphicState(PdfGraphicState(opacity: brush.fillOpacity));
}
canvas.drawString(font, brush.fontSize.sizeValue, text, 0, 0);
if (brush.fillOpacity < 1) {
canvas.restoreContext();
}
}
if (brush.stroke.isNotEmpty) {
if (brush.strokeWidth != null) {
canvas.setLineWidth(brush.strokeWidth.sizeValue);
}
if (brush.strokeDashArray != null) {
canvas.setLineDashPattern(brush.strokeDashArray);
}
if (brush.strokeOpacity < 1) {
canvas.setGraphicState(PdfGraphicState(opacity: brush.strokeOpacity));
}
brush.stroke.setStrokeColor(this, canvas);
canvas.drawString(font, brush.fontSize.sizeValue, text, 0, 0,
mode: PdfTextRenderingMode.stroke);
}
canvas.restoreContext();
for (final span in tspan) {
span.paint(canvas);
}
}
@override
void drawShape(PdfGraphics canvas) {
canvas
..saveContext()
..setTransform(Matrix4.identity()
..scale(1.0, -1.0)
..translate(x, -y))
..drawString(font, brush.fontSize.sizeValue, text, 0, 0,
mode: PdfTextRenderingMode.clip)
..restoreContext();
for (final span in tspan) {
span.draw(canvas);
}
}
@override
PdfRect boundingBox() {
final b = metrics.toPdfRect();
var x = b.x, y = b.y, w = b.width, h = b.height;
for (final child in tspan) {
final b = child.boundingBox();
x = min(b.x, x);
y = min(b.y, y);
w = max(b.width, w);
h = max(b.height, w);
}
return PdfRect(x, y, w, h);
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:pdf/svg/parser.dart';
import 'package:vector_math/vector_math_64.dart';
import 'package:xml/xml.dart';
class SvgTransform {
const SvgTransform(this.matrix);
factory SvgTransform.fromXml(XmlElement element) {
return SvgTransform.fromString(element.getAttribute('transform'));
}
factory SvgTransform.fromString(String transform) {
if (transform == null) {
return none;
}
if (transform == null) {
return none;
}
final mat = Matrix4.identity();
for (final m in _transformRegExp.allMatches(transform)) {
final name = m.group(1);
final parameterList = SvgParser.splitDoubles(m.group(2)).toList();
switch (name) {
case 'matrix':
final mm = <double>[
...parameterList,
...List.filled(6 - parameterList.length, 0.0)
];
mat.multiply(Matrix4(mm[0], mm[1], 0, 0, mm[2], mm[3], 0, 0, 0, 0, 1,
0, mm[4], mm[5], 0, 1));
break;
case 'translate':
final dx = parameterList[0];
final dy = [...parameterList, .0][1];
mat.multiply(Matrix4.identity()..translate(dx, dy));
break;
case 'scale':
final sw = parameterList[0];
final sh = [...parameterList, sw][1];
mat.multiply(Matrix4.identity()..scale(sw, sh));
break;
case 'rotate':
final degrees = parameterList[0];
var ox = 0.0;
var oy = 0.0;
if (parameterList.length > 1) {
// Rotation about the origin (ox, oy)
ox = parameterList[1];
oy = [...parameterList, .0][2];
mat.translate(ox, oy);
}
mat.multiply(Matrix4.rotationZ(radians(degrees)));
if (ox != 0 || oy != 0) {
mat.translate(-ox, -oy);
}
break;
case 'skewX':
// assert(false, 'skewX');
mat.multiply(Matrix4.skewX(radians(parameterList[0])));
break;
case 'skewY':
// assert(false, 'skewY');
mat.multiply(Matrix4.skewY(radians(parameterList[0])));
break;
}
}
return SvgTransform(mat);
}
final Matrix4 matrix;
bool get isEmpty => matrix == null;
bool get isNotEmpty => matrix != null;
static const none = SvgTransform(null);
static final _transformRegExp =
RegExp('(matrix|translate|scale|rotate|skewX|skewY)\s*\(([^)]*)\)\s*');
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:pdf/pdf.dart';
import 'package:vector_math/vector_math_64.dart';
import 'package:xml/xml.dart';
import 'brush.dart';
import 'clip_path.dart';
import 'operation.dart';
import 'painter.dart';
import 'parser.dart';
import 'transform.dart';
class SvgUse extends SvgOperation {
SvgUse(
this.x,
this.y,
this.width,
this.height,
this.href,
SvgBrush brush,
SvgClipPath clip,
SvgTransform transform,
SvgPainter painter,
) : super(brush, clip, transform, painter);
factory SvgUse.fromXml(
XmlElement element,
SvgPainter painter,
SvgBrush brush,
) {
final _brush = SvgBrush.fromXml(element, brush, painter);
final width =
SvgParser.getNumeric(element, 'width', _brush, defaultValue: 0)
.sizeValue;
final height =
SvgParser.getNumeric(element, 'height', _brush, defaultValue: 0)
.sizeValue;
final x =
SvgParser.getNumeric(element, 'x', _brush, defaultValue: 0).sizeValue;
final y =
SvgParser.getNumeric(element, 'y', _brush, defaultValue: 0).sizeValue;
SvgOperation href;
final hrefAttr = element.getAttribute('href') ??
element.getAttribute('href', namespace: 'http://www.w3.org/1999/xlink');
if (hrefAttr != null) {
final hrefElement = painter.parser.findById(hrefAttr.substring(1));
if (hrefElement != null) {
href = SvgOperation.fromXml(hrefElement, painter, _brush);
}
}
return SvgUse(
x,
y,
width,
height,
href,
_brush,
SvgClipPath.fromXml(element, painter, _brush),
SvgTransform.fromXml(element),
painter,
);
}
final double x;
final double y;
final double width;
final double height;
final SvgOperation href;
@override
void paintShape(PdfGraphics canvas) {
if (x != 0 || y != 0) {
canvas.setTransform(Matrix4.translationValues(x, y, 0));
}
href?.paint(canvas);
}
@override
void drawShape(PdfGraphics canvas) {
if (x != 0 || y != 0) {
canvas.setTransform(Matrix4.translationValues(x, y, 0));
}
href?.draw(canvas);
}
@override
PdfRect boundingBox() => href.boundingBox();
}
... ...
... ... @@ -16,6 +16,7 @@
export 'package:barcode/barcode.dart';
export 'svg.dart';
export 'widgets/annotations.dart';
export 'widgets/barcode.dart';
export 'widgets/basic.dart';
... ...
... ... @@ -17,6 +17,7 @@
import 'dart:math' as math;
import 'package:pdf/pdf.dart';
import 'package:pdf/svg.dart';
import 'basic.dart';
import 'geometry.dart';
... ... @@ -84,7 +85,20 @@ class PdfLogo extends StatelessWidget {
}
}
class FlutterLogo extends PdfLogo {}
class FlutterLogo extends StatelessWidget {
FlutterLogo({this.fit = BoxFit.contain});
final BoxFit fit;
@override
Widget build(Context context) {
return SvgImage(
svg:
'<?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>',
fit: fit,
);
}
}
class LoremText {
LoremText({math.Random random}) : random = random ?? math.Random(978);
... ...
... ... @@ -17,6 +17,7 @@ dependencies:
meta: ^1.1.5
path_parsing: ^0.1.4
vector_math: ^2.0.0
xml: ^4.0.0
dev_dependencies:
pedantic: 1.9.2
... ...
... ... @@ -43,6 +43,7 @@ import 'widget_multipage_test.dart' as widget_multipage;
import 'widget_opacity_test.dart' as widget_opacity;
import 'widget_outline_test.dart' as widget_outline;
import 'widget_partitions_test.dart' as widget_partitions;
import 'widget_svg_test.dart' as widget_svg;
import 'widget_table_test.dart' as widget_table;
import 'widget_test.dart' as widget;
import 'widget_text_test.dart' as widget_text;
... ... @@ -78,6 +79,7 @@ void main() {
widget_opacity.main();
widget_outline.main();
widget_partitions.main();
widget_svg.main();
widget_table.main();
widget_text.main();
widget_theme.main();
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'dart:io';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart';
import 'package:test/test.dart';
Document pdf;
void main() {
setUpAll(() {
Document.debug = true;
pdf = Document();
});
test('SVG Widgets Flutter logo', () {
pdf.addPage(
Page(
build: (context) => Center(
child: FlutterLogo(),
),
),
);
});
test('SVG Widgets', () {
print('=' * 120);
final dir = Directory('../ref/svg');
if (!dir.existsSync()) {
return;
}
final files = dir
.listSync()
.where((file) => file.path.endsWith('.svg'))
.map<String>((file) => file.path)
.toList()
..sort();
pdf.addPage(
MultiPage(
build: (context) => [
GridView(
crossAxisCount: 2,
childAspectRatio: 1,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: files.map<Widget>(
(file) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: PdfColors.blue),
),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Center(
child: SvgImage(
svg: File(file).readAsStringSync(),
),
),
),
ClipRect(
child: Text(file.substring(file.lastIndexOf('/') + 1)),
),
],
),
);
},
).toList(),
)
],
),
);
});
test('SVG Widgets Text', () {
pdf.addPage(
Page(
build: (context) => SvgImage(
svg:
'<?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>',
),
),
);
});
test('SVG Widgets Barcode', () {
pdf.addPage(
Page(
build: (context) => SvgImage(
svg: Barcode.isbn().toSvg('135459869354'),
),
),
);
});
tearDownAll(() {
final file = File('widgets-svg.pdf');
file.writeAsBytesSync(pdf.save());
});
}
... ...
No preview for this file type