Showing
19 changed files
with
445 additions
and
70 deletions
@@ -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; |
pdf/lib/widgets/icon.dart
0 → 100644
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 | +} |
pdf/test/widget_icon_test.dart
0 → 100644
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
test/golden/widgets-icons.pdf
0 → 100644
No preview for this file type
-
Please register or login to post a comment