Tyler Denniston
Committed by David PHAM-VAN

Add support for custom fonts in SVGs.

This adds the interface `SvgCustomFonts` and a corresponding parameter
to the SvgImage widget. This provides a mechanism to support SVGs that
have known fonts referenced in them.

Closes #1731.
1 # Changelog 1 # Changelog
2 2
  3 +## 3.11.2
  4 +
  5 +- Add support for custom fonts in SVGs. [Tyler Denniston]
  6 +
3 ## 3.11.1 7 ## 3.11.1
4 8
5 - Fixed display problems with textfields [ilaurillard] 9 - Fixed display problems with textfields [ilaurillard]
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 16
17 import '../../pdf.dart'; 17 import '../../pdf.dart';
18 import '../widgets/font.dart'; 18 import '../widgets/font.dart';
  19 +import '../widgets/svg.dart';
19 import 'brush.dart'; 20 import 'brush.dart';
20 import 'color.dart'; 21 import 'color.dart';
21 import 'group.dart'; 22 import 'group.dart';
@@ -26,8 +27,9 @@ class SvgPainter { @@ -26,8 +27,9 @@ class SvgPainter {
26 this.parser, 27 this.parser,
27 this._canvas, 28 this._canvas,
28 this.document, 29 this.document,
29 - this.boundingBox,  
30 - ); 30 + this.boundingBox, {
  31 + this.customFontLookup,
  32 + });
31 33
32 final SvgParser parser; 34 final SvgParser parser;
33 35
@@ -37,6 +39,8 @@ class SvgPainter { @@ -37,6 +39,8 @@ class SvgPainter {
37 39
38 final PdfRect boundingBox; 40 final PdfRect boundingBox;
39 41
  42 + final SvgCustomFontLookup? customFontLookup;
  43 +
40 void paint() { 44 void paint() {
41 final brush = parser.colorFilter == null 45 final brush = parser.colorFilter == null
42 ? SvgBrush.defaultContext 46 ? SvgBrush.defaultContext
@@ -59,6 +63,12 @@ class SvgPainter { @@ -59,6 +63,12 @@ class SvgPainter {
59 } 63 }
60 64
61 Font getFont(String fontFamily, String fontStyle, String fontWeight) { 65 Font getFont(String fontFamily, String fontStyle, String fontWeight) {
  66 + final customFont =
  67 + customFontLookup?.call(fontFamily, fontStyle, fontWeight);
  68 + if (customFont != null) {
  69 + return customFont;
  70 + }
  71 +
62 switch (fontFamily) { 72 switch (fontFamily) {
63 case 'serif': 73 case 'serif':
64 switch (fontStyle) { 74 switch (fontStyle) {
@@ -32,6 +32,7 @@ class SvgImage extends Widget { @@ -32,6 +32,7 @@ class SvgImage extends Widget {
32 double? width, 32 double? width,
33 double? height, 33 double? height,
34 PdfColor? colorFilter, 34 PdfColor? colorFilter,
  35 + SvgCustomFontLookup? customFontLookup,
35 }) { 36 }) {
36 final xml = XmlDocument.parse(svg); 37 final xml = XmlDocument.parse(svg);
37 final parser = SvgParser( 38 final parser = SvgParser(
@@ -46,6 +47,7 @@ class SvgImage extends Widget { @@ -46,6 +47,7 @@ class SvgImage extends Widget {
46 clip, 47 clip,
47 width, 48 width,
48 height, 49 height,
  50 + customFontLookup,
49 ); 51 );
50 } 52 }
51 53
@@ -56,6 +58,7 @@ class SvgImage extends Widget { @@ -56,6 +58,7 @@ class SvgImage extends Widget {
56 this.clip, 58 this.clip,
57 this.width, 59 this.width,
58 this.height, 60 this.height,
  61 + this.customFontLookup,
59 ); 62 );
60 63
61 final SvgParser _svgParser; 64 final SvgParser _svgParser;
@@ -70,6 +73,8 @@ class SvgImage extends Widget { @@ -70,6 +73,8 @@ class SvgImage extends Widget {
70 73
71 final double? height; 74 final double? height;
72 75
  76 + final SvgCustomFontLookup? customFontLookup;
  77 +
73 late FittedSizes sizes; 78 late FittedSizes sizes;
74 79
75 @override 80 @override
@@ -126,6 +131,7 @@ class SvgImage extends Widget { @@ -126,6 +131,7 @@ class SvgImage extends Widget {
126 context.page.pageFormat.width, 131 context.page.pageFormat.width,
127 context.page.pageFormat.height, 132 context.page.pageFormat.height,
128 ), 133 ),
  134 + customFontLookup: customFontLookup,
129 ); 135 );
130 painter.paint(); 136 painter.paint();
131 context.canvas.restoreContext(); 137 context.canvas.restoreContext();
@@ -154,3 +160,6 @@ class DecorationSvgImage extends DecorationGraphic { @@ -154,3 +160,6 @@ class DecorationSvgImage extends DecorationGraphic {
154 ); 160 );
155 } 161 }
156 } 162 }
  163 +
  164 +typedef SvgCustomFontLookup = Font? Function(
  165 + String fontFamily, String fontStyle, String fontWeight);
@@ -12,7 +12,7 @@ topics: @@ -12,7 +12,7 @@ topics:
12 - print 12 - print
13 - printing 13 - printing
14 - report 14 - report
15 -version: 3.11.1 15 +version: 3.11.2
16 16
17 environment: 17 environment:
18 sdk: ">=2.19.0 <4.0.0" 18 sdk: ">=2.19.0 <4.0.0"
@@ -20,6 +20,8 @@ import 'package:pdf/pdf.dart'; @@ -20,6 +20,8 @@ import 'package:pdf/pdf.dart';
20 import 'package:pdf/widgets.dart'; 20 import 'package:pdf/widgets.dart';
21 import 'package:test/test.dart'; 21 import 'package:test/test.dart';
22 22
  23 +import 'utils.dart';
  24 +
23 late Document pdf; 25 late Document pdf;
24 26
25 void main() { 27 void main() {
@@ -195,6 +197,31 @@ void main() { @@ -195,6 +197,31 @@ void main() {
195 ); 197 );
196 }); 198 });
197 199
  200 + test('SVG Widgets Text custom fonts', () {
  201 + const customFamilyName1 = 'Test-Font-Family1';
  202 + const customFamilyName2 = 'Test-Font-Family2';
  203 + final customFont1 = loadFont('open-sans-bold.ttf');
  204 + final customFont2 = loadFont('genyomintw.ttf');
  205 + pdf.addPage(
  206 + Page(
  207 + build: (context) => SvgImage(
  208 + svg:
  209 + '<?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="darkgreen" font-family="$customFamilyName1" >Custom <tspan font-family="$customFamilyName2">fonts</tspan></text><rect x="1" y="1" width="998" height="298" fill="none" stroke="purple" stroke-width="2" /></svg>',
  210 + customFontLookup:
  211 + (String fontFamily, String fontStyle, String fontWeight) {
  212 + switch (fontFamily) {
  213 + case customFamilyName1:
  214 + return customFont1;
  215 + case customFamilyName2:
  216 + return customFont2;
  217 + default:
  218 + return null;
  219 + }
  220 + }),
  221 + ),
  222 + );
  223 + });
  224 +
198 tearDownAll(() async { 225 tearDownAll(() async {
199 final file = File('widgets-svg.pdf'); 226 final file = File('widgets-svg.pdf');
200 await file.writeAsBytes(await pdf.save()); 227 await file.writeAsBytes(await pdf.save());