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, 23 + const PdfFontMetrics({
  24 + @required this.left,
25 @required this.top, 25 @required this.top,
26 @required this.right, 26 @required this.right,
27 @required this.bottom, 27 @required this.bottom,
28 double ascent, 28 double ascent,
29 double descent, 29 double descent,
30 - double advanceWidth})  
31 - : ascent = ascent ?? bottom, 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;
@@ -76,7 +80,9 @@ class PdfFontMetrics { @@ -76,7 +80,9 @@ class PdfFontMetrics {
76 bottom: bottom, 80 bottom: bottom,
77 ascent: ascent, 81 ascent: ascent,
78 descent: descent, 82 descent: descent,
79 - advanceWidth: right - spacing); 83 + advanceWidth: right - spacing,
  84 + leftBearing: firstBearing,
  85 + );
80 } 86 }
81 87
82 /// Zero-sized dimensions 88 /// Zero-sized dimensions
@@ -122,24 +128,26 @@ class PdfFontMetrics { @@ -122,24 +128,26 @@ 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, 141 + PdfFontMetrics copyWith({
  142 + double left,
137 double top, 143 double top,
138 double right, 144 double right,
139 double bottom, 145 double bottom,
140 double ascent, 146 double ascent,
141 double descent, 147 double descent,
142 - double advanceWidth}) { 148 + double advanceWidth,
  149 + double leftBearing,
  150 + }) {
143 return PdfFontMetrics( 151 return PdfFontMetrics(
144 left: left ?? this.left, 152 left: left ?? this.left,
145 top: top ?? this.top, 153 top: top ?? this.top,
@@ -147,7 +155,9 @@ class PdfFontMetrics { @@ -147,7 +155,9 @@ class PdfFontMetrics {
147 bottom: bottom ?? this.bottom, 155 bottom: bottom ?? this.bottom,
148 ascent: ascent ?? this.ascent, 156 ascent: ascent ?? this.ascent,
149 descent: descent ?? this.descent, 157 descent: descent ?? this.descent,
150 - advanceWidth: advanceWidth ?? this.advanceWidth); 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,8 +263,13 @@ class TtfParser { @@ -260,8 +263,13 @@ 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, 274 left: xMin.toDouble() / unitsPerEm,
267 top: yMin.toDouble() / unitsPerEm, 275 top: yMin.toDouble() / unitsPerEm,
@@ -269,7 +277,9 @@ class TtfParser { @@ -269,7 +277,9 @@ class TtfParser {
269 bottom: yMax.toDouble() / unitsPerEm, 277 bottom: yMax.toDouble() / unitsPerEm,
270 ascent: ascent.toDouble() / unitsPerEm, 278 ascent: ascent.toDouble() / unitsPerEm,
271 descent: descent.toDouble() / unitsPerEm, 279 descent: descent.toDouble() / unitsPerEm,
272 - advanceWidth: advanceWidth); 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, 568 + CustomPaint({
  569 + this.painter,
570 this.foregroundPainter, 570 this.foregroundPainter,
571 this.size = PdfPoint.zero, 571 this.size = PdfPoint.zero,
572 - Widget child})  
573 - : super(child: child); 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, 97 font: base,
89 fontNormal: base, 98 fontNormal: base,
90 fontBold: bold, 99 fontBold: bold,
91 fontItalic: italic, 100 fontItalic: italic,
92 - fontBoldItalic: boldItalic); 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