David PHAM-VAN

Add support for Icon Fonts

@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 DART_SRC=$(shell find . -name '*.dart') 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') 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') 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 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 19 COV_PORT=9292
20 20
21 all: $(FONTS) demo/assets/logo.png demo/assets/profile.jpg format printing/example/.metadata get 21 all: $(FONTS) demo/assets/logo.png demo/assets/profile.jpg format printing/example/.metadata get
@@ -42,6 +42,9 @@ pdf/noto-sans.ttf: @@ -42,6 +42,9 @@ pdf/noto-sans.ttf:
42 pdf/genyomintw.ttf: 42 pdf/genyomintw.ttf:
43 curl -L "https://github.com/ButTaiwan/genyo-font/raw/bc2fa246196fefc1ef9e9843bc8cdba22523a39d/TW/GenYoMinTW-Heavy.ttf" > $@ 43 curl -L "https://github.com/ButTaiwan/genyo-font/raw/bc2fa246196fefc1ef9e9843bc8cdba22523a39d/TW/GenYoMinTW-Heavy.ttf" > $@
44 44
  45 +pdf/material.ttf:
  46 + curl -L "https://github.com/google/material-design-icons/raw/master/font/MaterialIcons-Regular.ttf" > $@
  47 +
45 demo/assets/roboto1.ttf: 48 demo/assets/roboto1.ttf:
46 curl -L "https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmSU5vAw.ttf" > $@ 49 curl -L "https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmSU5vAw.ttf" > $@
47 50
@@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
9 - Fix the line cap and joint enums 9 - Fix the line cap and joint enums
10 - Fix PdfOutlineMode enum 10 - Fix PdfOutlineMode enum
11 - Improve API documentation 11 - Improve API documentation
  12 +- Add support for Icon Fonts (MaterialIcons)
12 13
13 ## 1.12.0 14 ## 1.12.0
14 15
@@ -138,6 +138,9 @@ See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management @@ -138,6 +138,9 @@ See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management
138 /// Default width of a glyph 138 /// Default width of a glyph
139 static const double defaultGlyphWidth = 0.600; 139 static const double defaultGlyphWidth = 0.600;
140 140
  141 + /// Internal units per
  142 + int get unitsPerEm;
  143 +
141 @override 144 @override
142 void _prepare() { 145 void _prepare() {
143 super._prepare(); 146 super._prepare();
@@ -20,17 +20,19 @@ part of pdf; @@ -20,17 +20,19 @@ part of pdf;
20 @immutable 20 @immutable
21 class PdfFontMetrics { 21 class PdfFontMetrics {
22 /// Create a PdfFontMetrics object 22 /// Create a PdfFontMetrics object
23 - const PdfFontMetrics(  
24 - {@required this.left,  
25 - @required this.top,  
26 - @required this.right,  
27 - @required this.bottom,  
28 - double ascent,  
29 - double descent,  
30 - double advanceWidth})  
31 - : ascent = ascent ?? bottom, 23 + const PdfFontMetrics({
  24 + @required this.left,
  25 + @required this.top,
  26 + @required this.right,
  27 + @required this.bottom,
  28 + double ascent,
  29 + double descent,
  30 + double advanceWidth,
  31 + double leftBearing,
  32 + }) : ascent = ascent ?? bottom,
32 descent = descent ?? top, 33 descent = descent ?? top,
33 advanceWidth = advanceWidth ?? right - left, 34 advanceWidth = advanceWidth ?? right - left,
  35 + leftBearing = leftBearing ?? left,
34 assert(left != null), 36 assert(left != null),
35 assert(top != null), 37 assert(top != null),
36 assert(right != null), 38 assert(right != null),
@@ -55,9 +57,11 @@ class PdfFontMetrics { @@ -55,9 +57,11 @@ class PdfFontMetrics {
55 double ascent; 57 double ascent;
56 double descent; 58 double descent;
57 double lastBearing; 59 double lastBearing;
  60 + double firstBearing;
58 double spacing; 61 double spacing;
59 62
60 for (var metric in metrics) { 63 for (var metric in metrics) {
  64 + firstBearing ??= metric.leftBearing;
61 left ??= metric.left; 65 left ??= metric.left;
62 spacing = metric.advanceWidth > 0 ? letterSpacing : 0.0; 66 spacing = metric.advanceWidth > 0 ? letterSpacing : 0.0;
63 right += metric.advanceWidth + spacing; 67 right += metric.advanceWidth + spacing;
@@ -70,13 +74,15 @@ class PdfFontMetrics { @@ -70,13 +74,15 @@ class PdfFontMetrics {
70 } 74 }
71 75
72 return PdfFontMetrics( 76 return PdfFontMetrics(
73 - left: left,  
74 - top: top,  
75 - right: right - lastBearing - spacing,  
76 - bottom: bottom,  
77 - ascent: ascent,  
78 - descent: descent,  
79 - advanceWidth: right - spacing); 77 + left: left,
  78 + top: top,
  79 + right: right - lastBearing - spacing,
  80 + bottom: bottom,
  81 + ascent: ascent,
  82 + descent: descent,
  83 + advanceWidth: right - spacing,
  84 + leftBearing: firstBearing,
  85 + );
80 } 86 }
81 87
82 /// Zero-sized dimensions 88 /// Zero-sized dimensions
@@ -122,32 +128,36 @@ class PdfFontMetrics { @@ -122,32 +128,36 @@ class PdfFontMetrics {
122 double get effectiveLeft => math.min(leftBearing, 0); 128 double get effectiveLeft => math.min(leftBearing, 0);
123 129
124 /// Starting point 130 /// Starting point
125 - double get leftBearing => left; 131 + final double leftBearing;
126 132
127 /// Ending point 133 /// Ending point
128 double get rightBearing => advanceWidth - right; 134 double get rightBearing => advanceWidth - right;
129 135
130 @override 136 @override
131 String toString() => 137 String toString() =>
132 - 'PdfFontMetrics(left:$left, top:$top, right:$right, bottom:$bottom, ascent:$ascent, descent:$descent, advanceWidth:$advanceWidth)'; 138 + 'PdfFontMetrics(left:$left, top:$top, right:$right, bottom:$bottom, ascent:$ascent, descent:$descent, advanceWidth:$advanceWidth, leftBearing:$leftBearing, rightBearing:$rightBearing)';
133 139
134 /// Make a copy of this object 140 /// Make a copy of this object
135 - PdfFontMetrics copyWith(  
136 - {double left,  
137 - double top,  
138 - double right,  
139 - double bottom,  
140 - double ascent,  
141 - double descent,  
142 - double advanceWidth}) { 141 + PdfFontMetrics copyWith({
  142 + double left,
  143 + double top,
  144 + double right,
  145 + double bottom,
  146 + double ascent,
  147 + double descent,
  148 + double advanceWidth,
  149 + double leftBearing,
  150 + }) {
143 return PdfFontMetrics( 151 return PdfFontMetrics(
144 - left: left ?? this.left,  
145 - top: top ?? this.top,  
146 - right: right ?? this.right,  
147 - bottom: bottom ?? this.bottom,  
148 - ascent: ascent ?? this.ascent,  
149 - descent: descent ?? this.descent,  
150 - advanceWidth: advanceWidth ?? this.advanceWidth); 152 + left: left ?? this.left,
  153 + top: top ?? this.top,
  154 + right: right ?? this.right,
  155 + bottom: bottom ?? this.bottom,
  156 + ascent: ascent ?? this.ascent,
  157 + descent: descent ?? this.descent,
  158 + advanceWidth: advanceWidth ?? this.advanceWidth,
  159 + leftBearing: leftBearing ?? this.leftBearing,
  160 + );
151 } 161 }
152 162
153 /// Multiply this metrics object with a font size 163 /// Multiply this metrics object with a font size
@@ -160,6 +170,7 @@ class PdfFontMetrics { @@ -160,6 +170,7 @@ class PdfFontMetrics {
160 ascent: ascent * factor, 170 ascent: ascent * factor,
161 descent: descent * factor, 171 descent: descent * factor,
162 advanceWidth: advanceWidth * factor, 172 advanceWidth: advanceWidth * factor,
  173 + leftBearing: leftBearing * factor,
163 ); 174 );
164 } 175 }
165 176
@@ -253,6 +253,9 @@ class TtfParser { @@ -253,6 +253,9 @@ class TtfParser {
253 final baseOffset = tableOffsets[glyf_table]; 253 final baseOffset = tableOffsets[glyf_table];
254 final hmtxOffset = tableOffsets[hmtx_table]; 254 final hmtxOffset = tableOffsets[hmtx_table];
255 final unitsPerEm = this.unitsPerEm; 255 final unitsPerEm = this.unitsPerEm;
  256 + final numOfLongHorMetrics = this.numOfLongHorMetrics;
  257 + final defaultadvanceWidth =
  258 + bytes.getUint16(hmtxOffset + (numOfLongHorMetrics - 1) * 4);
256 var glyphIndex = 0; 259 var glyphIndex = 0;
257 for (var offset in glyphOffsets) { 260 for (var offset in glyphOffsets) {
258 final xMin = bytes.getInt16(baseOffset + offset + 2); // 2 261 final xMin = bytes.getInt16(baseOffset + offset + 2); // 2
@@ -260,16 +263,23 @@ class TtfParser { @@ -260,16 +263,23 @@ class TtfParser {
260 final xMax = bytes.getInt16(baseOffset + offset + 6); // 6 263 final xMax = bytes.getInt16(baseOffset + offset + 6); // 6
261 final yMax = bytes.getInt16(baseOffset + offset + 8); // 8 264 final yMax = bytes.getInt16(baseOffset + offset + 8); // 8
262 final advanceWidth = glyphIndex < numOfLongHorMetrics 265 final advanceWidth = glyphIndex < numOfLongHorMetrics
263 - ? bytes.getInt16(hmtxOffset + glyphIndex * 4).toDouble() / unitsPerEm  
264 - : null; 266 + ? bytes.getUint16(hmtxOffset + glyphIndex * 4)
  267 + : defaultadvanceWidth;
  268 + final leftBearing = glyphIndex < numOfLongHorMetrics
  269 + ? bytes.getInt16(hmtxOffset + glyphIndex * 4 + 2)
  270 + : bytes.getInt16(hmtxOffset +
  271 + numOfLongHorMetrics * 4 +
  272 + (glyphIndex - numOfLongHorMetrics) * 2);
265 glyphInfoMap[glyphIndex] = PdfFontMetrics( 273 glyphInfoMap[glyphIndex] = PdfFontMetrics(
266 - left: xMin.toDouble() / unitsPerEm,  
267 - top: yMin.toDouble() / unitsPerEm,  
268 - right: xMax.toDouble() / unitsPerEm,  
269 - bottom: yMax.toDouble() / unitsPerEm,  
270 - ascent: ascent.toDouble() / unitsPerEm,  
271 - descent: descent.toDouble() / unitsPerEm,  
272 - advanceWidth: advanceWidth); 274 + left: xMin.toDouble() / unitsPerEm,
  275 + top: yMin.toDouble() / unitsPerEm,
  276 + right: xMax.toDouble() / unitsPerEm,
  277 + bottom: yMax.toDouble() / unitsPerEm,
  278 + ascent: ascent.toDouble() / unitsPerEm,
  279 + descent: descent.toDouble() / unitsPerEm,
  280 + advanceWidth: advanceWidth.toDouble() / unitsPerEm,
  281 + leftBearing: leftBearing.toDouble() / unitsPerEm,
  282 + );
273 glyphIndex++; 283 glyphIndex++;
274 } 284 }
275 } 285 }
@@ -187,10 +187,21 @@ class TtfWriter { @@ -187,10 +187,21 @@ class TtfWriter {
187 final hmtx = Uint8List(_wordAlign(len, 4)); 187 final hmtx = Uint8List(_wordAlign(len, 4));
188 final hmtxOffset = ttf.tableOffsets[TtfParser.hmtx_table]; 188 final hmtxOffset = ttf.tableOffsets[TtfParser.hmtx_table];
189 final hmtxData = hmtx.buffer.asByteData(); 189 final hmtxData = hmtx.buffer.asByteData();
  190 + final numOfLongHorMetrics = ttf.numOfLongHorMetrics;
  191 + final defaultadvanceWidth =
  192 + ttf.bytes.getUint16(hmtxOffset + (numOfLongHorMetrics - 1) * 4);
190 var index = 0; 193 var index = 0;
191 for (var glyph in glyphsInfo) { 194 for (var glyph in glyphsInfo) {
192 - hmtxData.setUint32(  
193 - index, ttf.bytes.getInt32(hmtxOffset + glyph.index * 4)); 195 + final advanceWidth = glyph.index < numOfLongHorMetrics
  196 + ? ttf.bytes.getUint16(hmtxOffset + glyph.index * 4)
  197 + : defaultadvanceWidth;
  198 + final leftBearing = glyph.index < numOfLongHorMetrics
  199 + ? ttf.bytes.getInt16(hmtxOffset + glyph.index * 4 + 2)
  200 + : ttf.bytes.getInt16(hmtxOffset +
  201 + numOfLongHorMetrics * 4 +
  202 + (glyph.index - numOfLongHorMetrics) * 2);
  203 + hmtxData.setUint16(index, advanceWidth);
  204 + hmtxData.setInt16(index + 2, leftBearing);
194 index += 4; 205 index += 4;
195 } 206 }
196 tables[TtfParser.hmtx_table] = hmtx; 207 tables[TtfParser.hmtx_table] = hmtx;
@@ -50,6 +50,9 @@ class PdfTtfFont extends PdfFont { @@ -50,6 +50,9 @@ class PdfTtfFont extends PdfFont {
50 double get descent => font.descent.toDouble() / font.unitsPerEm; 50 double get descent => font.descent.toDouble() / font.unitsPerEm;
51 51
52 @override 52 @override
  53 + int get unitsPerEm => font.unitsPerEm;
  54 +
  55 + @override
53 PdfFontMetrics glyphMetrics(int charCode) { 56 PdfFontMetrics glyphMetrics(int charCode) {
54 final g = font.charToGlyphIndexMap[charCode]; 57 final g = font.charToGlyphIndexMap[charCode];
55 58
@@ -44,6 +44,9 @@ class PdfType1Font extends PdfFont { @@ -44,6 +44,9 @@ class PdfType1Font extends PdfFont {
44 @override 44 @override
45 final double descent; 45 final double descent;
46 46
  47 + @override
  48 + int get unitsPerEm => 1000;
  49 +
47 /// Width of each glyph 50 /// Width of each glyph
48 final List<double> widths; 51 final List<double> widths;
49 52
@@ -20,9 +20,9 @@ import 'dart:collection'; @@ -20,9 +20,9 @@ import 'dart:collection';
20 import 'dart:convert'; 20 import 'dart:convert';
21 import 'dart:math' as math; 21 import 'dart:math' as math;
22 import 'dart:typed_data'; 22 import 'dart:typed_data';
23 -import 'package:image/image.dart' as im;  
24 23
25 import 'package:barcode/barcode.dart'; 24 import 'package:barcode/barcode.dart';
  25 +import 'package:image/image.dart' as im;
26 import 'package:meta/meta.dart'; 26 import 'package:meta/meta.dart';
27 import 'package:pdf/pdf.dart'; 27 import 'package:pdf/pdf.dart';
28 import 'package:vector_math/vector_math_64.dart'; 28 import 'package:vector_math/vector_math_64.dart';
@@ -48,6 +48,7 @@ part 'widgets/font.dart'; @@ -48,6 +48,7 @@ part 'widgets/font.dart';
48 part 'widgets/forms.dart'; 48 part 'widgets/forms.dart';
49 part 'widgets/geometry.dart'; 49 part 'widgets/geometry.dart';
50 part 'widgets/grid_view.dart'; 50 part 'widgets/grid_view.dart';
  51 +part 'widgets/icon.dart';
51 part 'widgets/image.dart'; 52 part 'widgets/image.dart';
52 part 'widgets/multi_page.dart'; 53 part 'widgets/multi_page.dart';
53 part 'widgets/page.dart'; 54 part 'widgets/page.dart';
@@ -565,12 +565,12 @@ class AspectRatio extends SingleChildWidget { @@ -565,12 +565,12 @@ class AspectRatio extends SingleChildWidget {
565 typedef CustomPainter = Function(PdfGraphics canvas, PdfPoint size); 565 typedef CustomPainter = Function(PdfGraphics canvas, PdfPoint size);
566 566
567 class CustomPaint extends SingleChildWidget { 567 class CustomPaint extends SingleChildWidget {
568 - CustomPaint(  
569 - {this.painter,  
570 - this.foregroundPainter,  
571 - this.size = PdfPoint.zero,  
572 - Widget child})  
573 - : super(child: child); 568 + CustomPaint({
  569 + this.painter,
  570 + this.foregroundPainter,
  571 + this.size = PdfPoint.zero,
  572 + Widget child,
  573 + }) : super(child: child);
574 574
575 final CustomPainter painter; 575 final CustomPainter painter;
576 final CustomPainter foregroundPainter; 576 final CustomPainter foregroundPainter;
  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 +part of widget;
  18 +
  19 +/// A description of an icon fulfilled by a font glyph.
  20 +@immutable
  21 +class IconData {
  22 + /// Creates icon data.
  23 + const IconData(
  24 + this.codePoint, {
  25 + this.matchTextDirection = false,
  26 + });
  27 +
  28 + /// The Unicode code point at which this icon is stored in the icon font.
  29 + final int codePoint;
  30 +
  31 + /// Whether this icon should be automatically mirrored in right-to-left
  32 + /// environments.
  33 + final bool matchTextDirection;
  34 +}
  35 +
  36 +/// Defines the color, opacity, and size of icons.
  37 +@immutable
  38 +class IconThemeData {
  39 + /// Creates an icon theme data.
  40 + const IconThemeData({this.color, this.opacity, this.size, this.font});
  41 +
  42 + /// Creates an icon them with some reasonable default values.
  43 + const IconThemeData.fallback(this.font)
  44 + : color = PdfColors.black,
  45 + opacity = 1.0,
  46 + size = 24.0;
  47 +
  48 + /// Creates a copy of this icon theme but with the given fields replaced with
  49 + /// the new values.
  50 + IconThemeData copyWith(
  51 + {PdfColor color, double opacity, double size, Font font}) {
  52 + return IconThemeData(
  53 + color: color ?? this.color,
  54 + opacity: opacity ?? this.opacity,
  55 + size: size ?? this.size,
  56 + font: font ?? this.font,
  57 + );
  58 + }
  59 +
  60 + /// The default color for icons.
  61 + final PdfColor color;
  62 +
  63 + /// An opacity to apply to both explicit and default icon colors.
  64 + final double opacity;
  65 +
  66 + /// The default size for icons.
  67 + final double size;
  68 +
  69 + /// The font to use
  70 + final Font font;
  71 +}
  72 +
  73 +/// A graphical icon widget drawn with a glyph from a font described in
  74 +/// an [IconData] such as material's predefined [IconData]s in [Icons].
  75 +class Icon extends StatelessWidget {
  76 + /// Creates an icon.
  77 + Icon(
  78 + this.icon, {
  79 + this.size,
  80 + this.color,
  81 + this.textDirection,
  82 + this.font,
  83 + }) : assert(icon != null),
  84 + super();
  85 +
  86 + /// The icon to display. The available icons are described in [Icons].
  87 + final IconData icon;
  88 +
  89 + /// The size of the icon in logical pixels.
  90 + final double size;
  91 +
  92 + /// The color to use when drawing the icon.
  93 + final PdfColor color;
  94 +
  95 + /// The text direction to use for rendering the icon.
  96 + final TextDirection textDirection;
  97 +
  98 + /// Font to use to draw the icon
  99 + final Font font;
  100 +
  101 + @override
  102 + Widget build(Context context) {
  103 + final textDirection = this.textDirection ?? Directionality.of(context);
  104 + final iconTheme = Theme.of(context).iconTheme;
  105 + final iconSize = size ?? iconTheme.size;
  106 + final iconColor = color ?? iconTheme.color;
  107 + final iconOpacity = iconColor.alpha;
  108 + final iconFont = font ?? iconTheme.font;
  109 +
  110 + Widget iconWidget = RichText(
  111 + textDirection: textDirection,
  112 + text: TextSpan(
  113 + text: String.fromCharCode(icon.codePoint),
  114 + style: TextStyle.defaultStyle().copyWith(
  115 + color: iconColor,
  116 + fontSize: iconSize,
  117 + fontNormal: iconFont,
  118 + ),
  119 + ),
  120 + );
  121 +
  122 + if (icon.matchTextDirection) {
  123 + switch (textDirection) {
  124 + case TextDirection.rtl:
  125 + iconWidget = Transform(
  126 + transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0),
  127 + alignment: Alignment.center,
  128 + child: iconWidget,
  129 + );
  130 + break;
  131 + case TextDirection.ltr:
  132 + break;
  133 + }
  134 + }
  135 +
  136 + if (iconOpacity < 1.0) {
  137 + iconWidget = Opacity(
  138 + opacity: iconOpacity,
  139 + child: iconWidget,
  140 + );
  141 + }
  142 + return iconWidget;
  143 + }
  144 +}
@@ -33,6 +33,7 @@ class ThemeData extends Inherited { @@ -33,6 +33,7 @@ class ThemeData extends Inherited {
33 bool softWrap, 33 bool softWrap,
34 TextAlign textAlign, 34 TextAlign textAlign,
35 int maxLines, 35 int maxLines,
  36 + IconThemeData iconTheme,
36 }) { 37 }) {
37 final base = ThemeData.base(); 38 final base = ThemeData.base();
38 return base.copyWith( 39 return base.copyWith(
@@ -50,6 +51,7 @@ class ThemeData extends Inherited { @@ -50,6 +51,7 @@ class ThemeData extends Inherited {
50 softWrap: softWrap, 51 softWrap: softWrap,
51 textAlign: textAlign, 52 textAlign: textAlign,
52 maxLines: maxLines, 53 maxLines: maxLines,
  54 + iconTheme: iconTheme,
53 ); 55 );
54 } 56 }
55 57
@@ -67,6 +69,7 @@ class ThemeData extends Inherited { @@ -67,6 +69,7 @@ class ThemeData extends Inherited {
67 @required this.tableCell, 69 @required this.tableCell,
68 @required this.softWrap, 70 @required this.softWrap,
69 @required this.textAlign, 71 @required this.textAlign,
  72 + @required this.iconTheme,
70 this.maxLines, 73 this.maxLines,
71 }) : assert(defaultTextStyle.inherit == false), 74 }) : assert(defaultTextStyle.inherit == false),
72 assert(paragraphStyle.inherit == false), 75 assert(paragraphStyle.inherit == false),
@@ -80,16 +83,23 @@ class ThemeData extends Inherited { @@ -80,16 +83,23 @@ class ThemeData extends Inherited {
80 assert(tableHeader.inherit == false), 83 assert(tableHeader.inherit == false),
81 assert(tableCell.inherit == false), 84 assert(tableCell.inherit == false),
82 assert(softWrap != null), 85 assert(softWrap != null),
83 - assert(maxLines == null || maxLines > 0); 86 + assert(maxLines == null || maxLines > 0),
  87 + assert(iconTheme != null);
84 88
85 - factory ThemeData.withFont(  
86 - {Font base, Font bold, Font italic, Font boldItalic}) { 89 + factory ThemeData.withFont({
  90 + Font base,
  91 + Font bold,
  92 + Font italic,
  93 + Font boldItalic,
  94 + Font icons,
  95 + }) {
87 final defaultStyle = TextStyle.defaultStyle().copyWith( 96 final defaultStyle = TextStyle.defaultStyle().copyWith(
88 - font: base,  
89 - fontNormal: base,  
90 - fontBold: bold,  
91 - fontItalic: italic,  
92 - fontBoldItalic: boldItalic); 97 + font: base,
  98 + fontNormal: base,
  99 + fontBold: bold,
  100 + fontItalic: italic,
  101 + fontBoldItalic: boldItalic,
  102 + );
93 final fontSize = defaultStyle.fontSize; 103 final fontSize = defaultStyle.fontSize;
94 104
95 return ThemeData._( 105 return ThemeData._(
@@ -107,6 +117,7 @@ class ThemeData extends Inherited { @@ -107,6 +117,7 @@ class ThemeData extends Inherited {
107 tableCell: defaultStyle.copyWith(fontSize: fontSize * 0.8), 117 tableCell: defaultStyle.copyWith(fontSize: fontSize * 0.8),
108 softWrap: true, 118 softWrap: true,
109 textAlign: TextAlign.left, 119 textAlign: TextAlign.left,
  120 + iconTheme: IconThemeData.fallback(icons),
110 ); 121 );
111 } 122 }
112 123
@@ -127,6 +138,7 @@ class ThemeData extends Inherited { @@ -127,6 +138,7 @@ class ThemeData extends Inherited {
127 bool softWrap, 138 bool softWrap,
128 TextAlign textAlign, 139 TextAlign textAlign,
129 int maxLines, 140 int maxLines,
  141 + IconThemeData iconTheme,
130 }) => 142 }) =>
131 ThemeData._( 143 ThemeData._(
132 defaultTextStyle: this.defaultTextStyle.merge(defaultTextStyle), 144 defaultTextStyle: this.defaultTextStyle.merge(defaultTextStyle),
@@ -143,6 +155,7 @@ class ThemeData extends Inherited { @@ -143,6 +155,7 @@ class ThemeData extends Inherited {
143 softWrap: softWrap ?? this.softWrap, 155 softWrap: softWrap ?? this.softWrap,
144 textAlign: textAlign ?? this.textAlign, 156 textAlign: textAlign ?? this.textAlign,
145 maxLines: maxLines ?? this.maxLines, 157 maxLines: maxLines ?? this.maxLines,
  158 + iconTheme: iconTheme ?? this.iconTheme,
146 ); 159 );
147 160
148 final TextStyle defaultTextStyle; 161 final TextStyle defaultTextStyle;
@@ -165,6 +178,8 @@ class ThemeData extends Inherited { @@ -165,6 +178,8 @@ class ThemeData extends Inherited {
165 final TextAlign textAlign; 178 final TextAlign textAlign;
166 final bool softWrap; 179 final bool softWrap;
167 final int maxLines; 180 final int maxLines;
  181 +
  182 + final IconThemeData iconTheme;
168 } 183 }
169 184
170 class Theme extends StatelessWidget { 185 class Theme extends StatelessWidget {
@@ -38,6 +38,7 @@ import 'widget_container_test.dart' as widget_container; @@ -38,6 +38,7 @@ import 'widget_container_test.dart' as widget_container;
38 import 'widget_flex_test.dart' as widget_flex; 38 import 'widget_flex_test.dart' as widget_flex;
39 import 'widget_form_test.dart' as widget_form; 39 import 'widget_form_test.dart' as widget_form;
40 import 'widget_grid_view_test.dart' as widget_grid_view; 40 import 'widget_grid_view_test.dart' as widget_grid_view;
  41 +import 'widget_icon_test.dart' as widget_icon;
41 import 'widget_multipage_test.dart' as widget_multipage; 42 import 'widget_multipage_test.dart' as widget_multipage;
42 import 'widget_opacity_test.dart' as widget_opacity; 43 import 'widget_opacity_test.dart' as widget_opacity;
43 import 'widget_outline_test.dart' as widget_outline; 44 import 'widget_outline_test.dart' as widget_outline;
@@ -72,6 +73,7 @@ void main() { @@ -72,6 +73,7 @@ void main() {
72 widget_flex.main(); 73 widget_flex.main();
73 widget_form.main(); 74 widget_form.main();
74 widget_grid_view.main(); 75 widget_grid_view.main();
  76 + widget_icon.main();
75 widget_multipage.main(); 77 widget_multipage.main();
76 widget_opacity.main(); 78 widget_opacity.main();
77 widget_outline.main(); 79 widget_outline.main();
@@ -22,50 +22,94 @@ import 'package:pdf/widgets.dart'; @@ -22,50 +22,94 @@ import 'package:pdf/widgets.dart';
22 import 'package:test/test.dart'; 22 import 'package:test/test.dart';
23 23
24 void printMetrics( 24 void printMetrics(
25 - PdfGraphics canvas, String text, PdfFont font, PdfPoint size) {  
26 - final metricsUnscales = font.stringMetrics(text);  
27 - final fontSizeW = size.x / metricsUnscales.maxWidth;  
28 - final fontSizeH = size.y / metricsUnscales.maxHeight; 25 + PdfGraphics canvas,
  26 + int codeUnit,
  27 + PdfFont font,
  28 + PdfPoint size,
  29 +) {
  30 + final metricsUnscaled = font.glyphMetrics(codeUnit);
  31 + final fontSizeW = size.x / metricsUnscaled.maxWidth;
  32 + final fontSizeH = size.y / metricsUnscaled.maxHeight;
29 final fontSize = min(fontSizeW, fontSizeH); 33 final fontSize = min(fontSizeW, fontSizeH);
30 - final metrics = metricsUnscales * fontSize; 34 + final metrics = metricsUnscaled * fontSize;
  35 + final m = metricsUnscaled * font.unitsPerEm.toDouble();
31 36
32 const deb = 20; 37 const deb = 20;
  38 + const s = 5.0;
33 39
34 final x = (size.x - metrics.maxWidth) / 2.0; 40 final x = (size.x - metrics.maxWidth) / 2.0;
35 final y = (size.y - metrics.maxHeight) / 2.0 - metrics.descent; 41 final y = (size.y - metrics.maxHeight) / 2.0 - metrics.descent;
36 42
  43 + int index;
  44 + if (font is PdfTtfFont) {
  45 + index = font.font.charToGlyphIndexMap[codeUnit];
  46 + }
  47 +
37 canvas 48 canvas
38 ..setLineWidth(0.5) 49 ..setLineWidth(0.5)
  50 + // Glyph maximum size
39 ..drawRect(x, y + metrics.descent, metrics.advanceWidth, metrics.maxHeight) 51 ..drawRect(x, y + metrics.descent, metrics.advanceWidth, metrics.maxHeight)
40 ..setStrokeColor(PdfColors.green) 52 ..setStrokeColor(PdfColors.green)
41 ..strokePath() 53 ..strokePath()
  54 + // Glyph bounding box
42 ..drawRect(x + metrics.left, y + metrics.top, metrics.width, metrics.height) 55 ..drawRect(x + metrics.left, y + metrics.top, metrics.width, metrics.height)
43 ..setStrokeColor(PdfColors.amber) 56 ..setStrokeColor(PdfColors.amber)
44 ..strokePath() 57 ..strokePath()
  58 + // Glyph baseline
45 ..drawLine(x + metrics.effectiveLeft - deb, y, 59 ..drawLine(x + metrics.effectiveLeft - deb, y,
46 x + metrics.maxWidth + metrics.effectiveLeft + deb, y) 60 x + metrics.maxWidth + metrics.effectiveLeft + deb, y)
47 ..setColor(PdfColors.blue) 61 ..setColor(PdfColors.blue)
48 ..strokePath() 62 ..strokePath()
  63 + // Drawing Start
49 ..drawEllipse(x, y, 5, 5) 64 ..drawEllipse(x, y, 5, 5)
50 ..setFillColor(PdfColors.black) 65 ..setFillColor(PdfColors.black)
51 ..fillPath() 66 ..fillPath()
  67 + // Next glyph
52 ..drawEllipse(x + metrics.advanceWidth, y, 5, 5) 68 ..drawEllipse(x + metrics.advanceWidth, y, 5, 5)
53 ..setFillColor(PdfColors.red) 69 ..setFillColor(PdfColors.red)
54 ..fillPath() 70 ..fillPath()
  71 + // Left Bearing
  72 + ..saveContext()
  73 + ..setGraphicState(const PdfGraphicState(opacity: 0.5))
  74 + ..drawEllipse(x + metrics.leftBearing, y, 5, 5)
  75 + ..setFillColor(PdfColors.purple)
  76 + ..fillPath()
  77 + ..restoreContext()
  78 + // The glyph
55 ..setFillColor(PdfColors.grey) 79 ..setFillColor(PdfColors.grey)
56 - ..drawString(font, fontSize, text, x, y); 80 + ..drawString(font, fontSize, String.fromCharCode(codeUnit), x, y)
  81 + // Metrics information
  82 + ..setFillColor(PdfColors.black)
  83 + ..drawString(canvas.defaultFont, s,
  84 + 'unicode: 0x${codeUnit.toRadixString(16)}', 10, size.y - 20 - s * 0)
  85 + ..drawString(canvas.defaultFont, s, 'index: 0x${index.toRadixString(16)}',
  86 + 10, size.y - 20 - s * 1)
  87 + ..drawString(canvas.defaultFont, s, 'left: ${m.left.toInt()}', 10,
  88 + size.y - 20 - s * 2)
  89 + ..drawString(canvas.defaultFont, s, 'right: ${m.right.toInt()}', 10,
  90 + size.y - 20 - s * 3)
  91 + ..drawString(
  92 + canvas.defaultFont, s, 'top: ${m.top.toInt()}', 10, size.y - 20 - s * 4)
  93 + ..drawString(canvas.defaultFont, s, 'bottom: ${m.bottom.toInt()}', 10,
  94 + size.y - 20 - s * 5)
  95 + ..drawString(canvas.defaultFont, s,
  96 + 'advanceWidth: ${m.advanceWidth.toInt()}', 10, size.y - 20 - s * 6)
  97 + ..drawString(canvas.defaultFont, s, 'leftBearing: ${m.leftBearing.toInt()}',
  98 + 10, size.y - 20 - s * 7);
57 } 99 }
58 100
59 void main() { 101 void main() {
60 test('Pdf Font Metrics', () { 102 test('Pdf Font Metrics', () {
61 final pdf = Document(); 103 final pdf = Document();
62 104
  105 + PdfFont.courier(pdf.document);
63 final ttfFont = File('open-sans.ttf'); 106 final ttfFont = File('open-sans.ttf');
64 final fontData = ttfFont.readAsBytesSync(); 107 final fontData = ttfFont.readAsBytesSync();
65 final font = PdfTtfFont(pdf.document, fontData.buffer.asByteData()); 108 final font = PdfTtfFont(pdf.document, fontData.buffer.asByteData());
66 109
67 - for (var letter  
68 - in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&%!?0123456789' 110 + for (var letter in
  111 + //font.font.charToGlyphIndexMap.keys
  112 + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&%!?0123456789'
69 .codeUnits) { 113 .codeUnits) {
70 pdf.addPage(Page( 114 pdf.addPage(Page(
71 pageFormat: const PdfPageFormat(500, 500, marginAll: 20), 115 pageFormat: const PdfPageFormat(500, 500, marginAll: 20),
@@ -76,8 +120,7 @@ void main() { @@ -76,8 +120,7 @@ void main() {
76 child: CustomPaint( 120 child: CustomPaint(
77 size: const PdfPoint(200, 200), 121 size: const PdfPoint(200, 200),
78 painter: (PdfGraphics canvas, PdfPoint size) { 122 painter: (PdfGraphics canvas, PdfPoint size) {
79 - printMetrics(  
80 - canvas, String.fromCharCode(letter), font, size); 123 + printMetrics(canvas, letter, font, size);
81 }))); 124 })));
82 })); 125 }));
83 } 126 }
@@ -81,3 +81,55 @@ Font loadFont(String filename) { @@ -81,3 +81,55 @@ Font loadFont(String filename) {
81 final data = File(filename).readAsBytesSync(); 81 final data = File(filename).readAsBytesSync();
82 return Font.ttf(data.buffer.asByteData()); 82 return Font.ttf(data.buffer.asByteData());
83 } 83 }
  84 +
  85 +void hexDump(
  86 + ByteData bytes,
  87 + int offset,
  88 + int length, [
  89 + int highlight,
  90 + int highlightLength,
  91 +]) {
  92 + const reset = '\x1B[0m';
  93 + const red = '\x1B[1;31m';
  94 + var s = '';
  95 + var t = '';
  96 + var n = 0;
  97 + var hl = false;
  98 + for (var i = 0; i < length; i++) {
  99 + final b = bytes.getUint8(offset + i);
  100 + if (highlight != null && highlightLength != null) {
  101 + if (offset + i >= highlight && offset + i < highlight + highlightLength) {
  102 + if (!hl) {
  103 + hl = true;
  104 + s += red;
  105 + t += red;
  106 + }
  107 + } else {
  108 + if (hl) {
  109 + hl = false;
  110 + s += reset;
  111 + t += reset;
  112 + }
  113 + }
  114 + }
  115 + s += b.toRadixString(16).padLeft(2, '0') + ' ';
  116 + if (b > 31 && b < 128) {
  117 + t += String.fromCharCode(b);
  118 + } else {
  119 + t += '.';
  120 + }
  121 +
  122 + n++;
  123 + if (n % 16 == 0) {
  124 + if (hl) {
  125 + s += reset;
  126 + t += reset;
  127 + hl = false;
  128 + }
  129 + print('$s $t');
  130 + s = '';
  131 + t = '';
  132 + }
  133 + }
  134 + print('$s $t');
  135 +}
  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 +import 'utils.dart';
  24 +
  25 +Document pdf;
  26 +Font icons;
  27 +
  28 +void main() {
  29 + setUpAll(() {
  30 + Document.debug = true;
  31 + pdf = Document();
  32 + icons = loadFont('material.ttf');
  33 + });
  34 +
  35 + test('Icon Widgets', () {
  36 + pdf.addPage(
  37 + MultiPage(
  38 + theme: ThemeData.withFont(icons: icons),
  39 + build: (Context context) {
  40 + final iconList = List<IconData>();
  41 + final pdfFont = icons.getFont(context);
  42 + if (pdfFont is PdfTtfFont) {
  43 + iconList.addAll(
  44 + pdfFont.font.charToGlyphIndexMap.keys
  45 + .where((e) => e > 0x7f && e < 0xe05d)
  46 + .map((e) => IconData(e)),
  47 + );
  48 + }
  49 +
  50 + return <Widget>[
  51 + Wrap(
  52 + spacing: 10,
  53 + runSpacing: 10,
  54 + children: <Widget>[
  55 + ...iconList.map<Widget>(
  56 + (e) => Column(children: [
  57 + Icon(e, size: 50, color: PdfColors.blueGrey),
  58 + Text('0x${e.codePoint.toRadixString(16)}'),
  59 + ]),
  60 + ),
  61 + ],
  62 + ),
  63 + ];
  64 + },
  65 + ),
  66 + );
  67 + });
  68 +
  69 + tearDownAll(() {
  70 + final file = File('widgets-icons.pdf');
  71 + file.writeAsBytesSync(pdf.save());
  72 + });
  73 +}
No preview for this file type
No preview for this file type