David PHAM-VAN

Implement ImageProvider

... ... @@ -91,7 +91,7 @@ class Invoice {
double get _grandTotal => _total * (1 + tax);
PdfImage _logo;
pw.MemoryImage _logo;
Future<Uint8List> buildPdf(PdfPageFormat pageFormat) async {
// Create a PDF document.
... ... @@ -101,9 +101,8 @@ class Invoice {
final font2 = await rootBundle.load('assets/roboto2.ttf');
final font3 = await rootBundle.load('assets/roboto3.ttf');
_logo = PdfImage.file(
doc.document,
bytes: (await rootBundle.load('assets/logo.png')).buffer.asUint8List(),
_logo = pw.MemoryImage(
(await rootBundle.load('assets/logo.png')).buffer.asUint8List(),
);
// Add page to the PDF
... ... @@ -191,7 +190,8 @@ class Invoice {
alignment: pw.Alignment.topRight,
padding: const pw.EdgeInsets.only(bottom: 8, left: 30),
height: 72,
child: _logo != null ? pw.Image(_logo) : pw.PdfLogo(),
child:
_logo != null ? pw.Image.provider(_logo) : pw.PdfLogo(),
),
// pw.Container(
// color: baseColor,
... ...
... ... @@ -29,9 +29,8 @@ const sep = 120.0;
Future<Uint8List> generateResume(PdfPageFormat format) async {
final doc = pw.Document(title: 'My Résumé', author: 'David PHAM-VAN');
final profileImage = PdfImage.file(
doc.document,
bytes: (await rootBundle.load('assets/profile.jpg')).buffer.asUint8List(),
final profileImage = pw.MemoryImage(
(await rootBundle.load('assets/profile.jpg')).buffer.asUint8List(),
);
final pageTheme = await _myPageTheme(format);
... ... @@ -121,7 +120,7 @@ Future<Uint8List> generateResume(PdfPageFormat format) async {
width: 100,
height: 100,
color: lightGreen,
child: pw.Image(profileImage),
child: pw.Image.provider(profileImage),
),
),
pw.Column(children: <pw.Widget>[
... ...
... ... @@ -79,6 +79,7 @@
- Add Chart Widget [Marco Papula]
- Add Divider and VerticalDivider Widget
- Replace Theme with ThemeData
- Implement ImageProvider
## 1.6.2
... ...
... ... @@ -56,17 +56,15 @@ pdf.addPage(pw.Page(
To load an image from a file:
```dart
final image = PdfImage.file(
pdf.document,
bytes: File('test.webp').readAsBytesSync(),
final image = pw.MemoryImage(
File('test.webp').readAsBytesSync(),
);
pdf.addPage(pw.Page(
build: (pw.Context context) {
return pw.Center(
child: pw.Image(image),
); // Center
})); // Page
pdf.addPage(pw.Page(build: (pw.Context context) {
return pw.Center(
child: pw.Image.provider(image),
); // Center
})); // Page
```
To use a TrueType font:
... ...
... ... @@ -50,6 +50,7 @@ part 'widgets/geometry.dart';
part 'widgets/grid_view.dart';
part 'widgets/icon.dart';
part 'widgets/image.dart';
part 'widgets/image_provider.dart';
part 'widgets/multi_page.dart';
part 'widgets/page.dart';
part 'widgets/page_theme.dart';
... ...
... ... @@ -144,20 +144,36 @@ class BoxBorder {
@immutable
class DecorationImage {
const DecorationImage(
{@required this.image,
this.fit = BoxFit.cover,
this.alignment = Alignment.center})
: assert(image != null),
@Deprecated('Use DecorationImage.provider()')
DecorationImage({
@required PdfImage image,
this.fit = BoxFit.cover,
this.alignment = Alignment.center,
}) : assert(image != null),
assert(fit != null),
assert(alignment != null),
image = ImageProxy(image),
dpi = null;
const DecorationImage.provider({
@required this.image,
this.fit = BoxFit.cover,
this.alignment = Alignment.center,
this.dpi,
}) : assert(image != null),
assert(fit != null),
assert(alignment != null);
final PdfImage image;
final ImageProvider image;
final BoxFit fit;
final Alignment alignment;
final double dpi;
void paint(Context context, PdfRect box) {
final imageSize = PdfPoint(image.width.toDouble(), image.height.toDouble());
final _image = image.resolve(context, box.size, dpi: dpi);
final imageSize =
PdfPoint(_image.width.toDouble(), _image.height.toDouble());
final sizes = applyBoxFit(fit, imageSize, box.size);
final scaleX = sizes.destination.x / sizes.source.x;
final scaleY = sizes.destination.y / sizes.source.y;
... ... @@ -174,7 +190,7 @@ class DecorationImage {
..drawRect(box.x, box.y, box.width, box.height)
..clipPath()
..setTransform(mat)
..drawImage(image, 0, 0, imageSize.x, imageSize.y)
..drawImage(_image, 0, 0, imageSize.x, imageSize.y)
..restoreContext();
}
}
... ...
... ... @@ -75,18 +75,27 @@ void _drawImageRect(PdfGraphics canvas, PdfImage image, PdfRect sourceRect,
}
class Image extends Widget {
@Deprecated('Use Image.provider instead')
Image(
this.image, {
PdfImage image, {
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.width,
this.height,
}) : assert(image != null),
aspectRatio = image.height.toDouble() / image.width.toDouble();
image = ImageProxy(image),
dpi = null;
final PdfImage image;
Image.provider(
this.image, {
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.width,
this.height,
this.dpi,
}) : assert(image != null);
final double aspectRatio;
final ImageProvider image;
final BoxFit fit;
... ... @@ -96,6 +105,8 @@ class Image extends Widget {
final double height;
final double dpi;
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
... ... @@ -119,9 +130,11 @@ class Image extends Widget {
void paint(Context context) {
super.paint(context);
final rect = context.localToGlobal(box);
_paintImage(
canvas: context.canvas,
image: image,
image: image.resolve(context, rect.size, dpi: dpi),
rect: box,
alignment: alignment,
fit: fit,
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
part of widget;
/// Identifies an image without committing to the precise final asset
abstract class ImageProvider {
ImageProvider(
this._width,
this._height,
this.orientation,
this.dpi,
);
final double dpi;
final int _width;
/// Image width
int get width => orientation.index >= 4 ? _height : _width;
final int _height;
/// Image height
int get height => orientation.index < 4 ? _height : _width;
/// The internal orientation of the image
final PdfImageOrientation orientation;
final _cache = <int, PdfImage>{};
PdfImage buildImage(Context context, {int width, int height});
/// Resolves this image provider using the given context, returning a PdfImage
/// The image is automatically added to the document
PdfImage resolve(Context context, PdfPoint size, {double dpi}) {
assert(size != null);
final effectiveDpi = dpi ?? this.dpi;
if (effectiveDpi == null || _cache[0] != null) {
_cache[0] ??= buildImage(context);
assert(_cache[0].pdfDocument == context.document,
'Do not reuse an ImageProvider object across multiple documents');
return _cache[0];
}
final width = (size.x / PdfPageFormat.inch * effectiveDpi).toInt();
final height = (size.y / PdfPageFormat.inch * effectiveDpi).toInt();
if (!_cache.containsKey(width)) {
_cache[width] ??= buildImage(context, width: width, height: height);
}
assert(_cache[width].pdfDocument == context.document,
'Do not reuse an ImageProvider object across multiple documents');
return _cache[width];
}
}
class ImageProxy extends ImageProvider {
ImageProxy(
this._image, {
double dpi,
}) : super(_image.width, _image.height, _image.orientation, dpi);
/// The proxy image
final PdfImage _image;
@override
PdfImage buildImage(Context context, {int width, int height}) => _image;
}
class MemoryImage extends ImageProvider {
factory MemoryImage(
Uint8List bytes, {
PdfImageOrientation orientation,
double dpi,
}) {
final decoder = im.findDecoderForData(bytes);
if (decoder is im.JpegDecoder) {
final info = PdfJpegInfo(bytes);
return MemoryImage._(
bytes,
info.width,
info.height,
orientation ?? info.orientation,
dpi,
);
}
final info = decoder.startDecode(bytes);
return MemoryImage._(
bytes,
info.width,
info.height,
orientation ?? PdfImageOrientation.topLeft,
dpi,
);
}
MemoryImage._(
this.bytes,
int width,
int height,
PdfImageOrientation orientation,
double dpi,
) : super(width, height, orientation, dpi);
/// The image data
final Uint8List bytes;
@override
PdfImage buildImage(Context context, {int width, int height}) {
if (width == null) {
return PdfImage.file(context.document, bytes: bytes);
}
final image = im.decodeImage(bytes);
print(width);
final resized = im.copyResize(image, width: width);
return PdfImage.fromImage(context.document, image: resized);
}
}
class ImageImage extends ImageProvider {
ImageImage(
this._image, {
double dpi,
PdfImageOrientation orientation,
}) : super(_image.width, _image.height,
orientation ?? PdfImageOrientation.topLeft, dpi);
/// The image data
final im.Image _image;
@override
PdfImage buildImage(Context context, {int width, int height}) {
if (width == null) {
return PdfImage.fromImage(context.document, image: _image);
}
final resized = im.copyResize(_image, width: width);
return PdfImage.fromImage(context.document, image: resized);
}
}
class RawImage extends ImageImage {
RawImage({
@required Uint8List bytes,
@required int width,
@required int height,
PdfImageOrientation orientation,
double dpi,
}) : super(im.Image.fromBytes(width, height, bytes),
orientation: orientation, dpi: dpi);
}
... ...
... ... @@ -19,7 +19,6 @@ import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart';
import 'package:test/test.dart';
... ... @@ -35,12 +34,12 @@ class Message {
void compute(Message message) {
final pdf = Document();
final image = PdfImage.jpeg(
pdf.document,
image: message.image,
final image = MemoryImage(
message.image,
);
pdf.addPage(Page(build: (Context context) => Center(child: Image(image))));
pdf.addPage(
Page(build: (Context context) => Center(child: Image.provider(image))));
message.sendPort.send(pdf.save());
}
... ...
... ... @@ -17,29 +17,27 @@
import 'dart:convert';
import 'dart:io';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart';
import 'package:test/test.dart';
import 'utils.dart';
Document pdf;
PdfImage image;
MemoryImage image;
void main() {
setUpAll(() async {
Document.debug = true;
pdf = Document();
image = PdfImage.jpeg(
pdf.document,
image: await download('https://www.nfet.net/nfet.jpg'),
image = MemoryImage(
await download('https://www.nfet.net/nfet.jpg'),
);
});
test('Pdf Jpeg Download', () async {
pdf.addPage(Page(
build: (Context context) => Center(child: Image(image)),
build: (Context context) => Center(child: Image.provider(image)),
));
});
... ... @@ -51,10 +49,9 @@ void main() {
crossAxisSpacing: 10,
children: List<Widget>.generate(
images.length,
(int index) => Image(
PdfImage.jpeg(
pdf.document,
image: base64.decode(images[index]),
(int index) => Image.provider(
MemoryImage(
base64.decode(images[index]),
),
),
),
... ... @@ -72,7 +69,7 @@ void main() {
return SizedBox(
width: 200,
height: 100,
child: Image(
child: Image.provider(
image,
fit: fit,
),
... ... @@ -85,10 +82,9 @@ void main() {
test('Pdf Image decode', () {
final imageWidgets = imageFiles.map<Widget>(
(String image) => SizedBox(
child: Image(
PdfImage.file(
pdf.document,
bytes: gzip.decode(base64.decode(image)),
child: Image.provider(
MemoryImage(
gzip.decode(base64.decode(image)),
),
),
width: 200,
... ...
... ... @@ -19,7 +19,6 @@ import 'dart:io';
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart';
Future<Uint8List> download(
... ... @@ -56,7 +55,7 @@ Future<Uint8List> download(
return Uint8List.fromList(data);
}
PdfImage generateBitmap(PdfDocument pdf, int w, int h) {
ImageProvider generateBitmap(int w, int h) {
final bm = Uint32List(w * h);
final dw = w.toDouble();
final dh = h.toDouble();
... ... @@ -69,9 +68,8 @@ PdfImage generateBitmap(PdfDocument pdf, int w, int h) {
}
}
return PdfImage(
pdf,
image: bm.buffer.asUint8List(),
return RawImage(
bytes: bm.buffer.asUint8List(),
width: w,
height: h,
);
... ...
... ... @@ -55,7 +55,7 @@ void main() {
});
test('Container Widgets Image', () {
final image = generateBitmap(pdf.document, 100, 200);
final image = generateBitmap(100, 200);
final widgets = <Widget>[];
for (var shape in BoxShape.values) {
... ... @@ -66,7 +66,7 @@ void main() {
decoration: BoxDecoration(
shape: shape,
borderRadiusEx: const BorderRadius.all(Radius.circular(10)),
image: DecorationImage(image: image, fit: fit),
image: DecorationImage.provider(image: image, fit: fit),
),
width: 100,
height: 100,
... ...
... ... @@ -37,7 +37,7 @@ void main() {
MultiPage(
theme: ThemeData.withFont(icons: icons),
build: (Context context) {
final iconList = List<IconData>();
final iconList = <IconData>[];
final pdfFont = icons.getFont(context);
if (pdfFont is PdfTtfFont) {
iconList.addAll(
... ...
... ... @@ -24,7 +24,7 @@ import 'package:test/test.dart';
void main() {
Document pdf;
TextStyle symbol;
PdfImage im;
ImageProvider im;
setUpAll(() {
Document.debug = true;
... ... @@ -43,7 +43,7 @@ void main() {
final imData = zlib.decode(base64.decode(
'eJz7//8/w388uOTCT6a4Ez96Q47++I+OI479mEVALyNU7z9seuNP/mAm196Ekz8YR+0dWHtBmJC9S+7/Zog89iMIKLYaHQPVJGLTD7MXpDfq+I9goNhPdPPDjv3YlnH6Jye6+2H21l/6yeB/4HsSDr1bQXrRwq8HqHcGyF6QXp9933N0tn/7Y7vn+/9gLPaih0PDlV9MIAzVm6ez7dsfzW3f/oMwzAx0e7FhoJutdbcj9MKw9frnL2J2POfBpxeEg478YLba/X0Wsl6lBXf+s0bP/s8ePXeWePJCvPEJNYMRZIYWSO/cq/9Z/Nv+M4bO+M8YDjFDJGkhzvSE7A6jRTdnsQR2wfXCMLHuMC5byyidvGgWE5JeZDOIcYdR+TpmkBno+mFmAAC+DGhl'));
im = PdfImage(pdf.document, image: imData, width: 16, height: 20);
im = RawImage(bytes: imData, width: 16, height: 20);
});
test('Pdf Widgets page 1', () {
... ... @@ -85,7 +85,7 @@ void main() {
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image(im),
Image.provider(im),
PdfLogo(),
Column(
children: <Widget>[
... ...
... ... @@ -55,12 +55,12 @@ To load an image from an ImageProvider:
```dart
const imageProvider = const AssetImage('assets/image.png');
final PdfImage image = await pdfImageFromImageProvider(pdf: doc.document, image: imageProvider);
final image = await flutterImageProvider(imageProvider);
doc.addPage(pw.Page(
build: (pw.Context context) {
return pw.Center(
child: pw.Image(image),
child: pw.Image.provider(image),
); // Center
})); // Page
```
... ...
... ... @@ -17,7 +17,7 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:flutter/rendering.dart' as rdr;
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';
... ... @@ -25,6 +25,7 @@ import 'package:pdf/widgets.dart';
/// Loads an image from a Flutter [ui.Image]
/// into a [PdfImage] instance
@Deprecated('Use flutterImageProvider')
Future<PdfImage> pdfImageFromImage(
{@required PdfDocument pdf, @required ui.Image image}) async {
final bytes = await image.toByteData(format: ui.ImageByteFormat.rawRgba);
... ... @@ -37,16 +38,17 @@ Future<PdfImage> pdfImageFromImage(
/// Loads an image from a Flutter [ImageProvider]
/// into a [PdfImage] instance
@Deprecated('Use flutterImageProvider')
Future<PdfImage> pdfImageFromImageProvider(
{@required PdfDocument pdf,
@required ImageProvider image,
ImageConfiguration configuration,
ImageErrorListener onError}) async {
@required rdr.ImageProvider image,
rdr.ImageConfiguration configuration,
rdr.ImageErrorListener onError}) async {
final completer = Completer<PdfImage>();
final stream = image.resolve(configuration ?? ImageConfiguration.empty);
final stream = image.resolve(configuration ?? rdr.ImageConfiguration.empty);
ImageStreamListener listener;
listener = ImageStreamListener((ImageInfo image, bool sync) async {
rdr.ImageStreamListener listener;
listener = rdr.ImageStreamListener((rdr.ImageInfo image, bool sync) async {
final result = await pdfImageFromImage(pdf: pdf, image: image.image);
if (!completer.isCompleted) {
completer.complete(result);
... ... @@ -68,6 +70,46 @@ Future<PdfImage> pdfImageFromImageProvider(
return completer.future;
}
/// Loads an image from a Flutter [ImageProvider]
/// into an [ImageProvider] instance
Future<ImageProvider> flutterImageProvider(
rdr.ImageProvider image, {
rdr.ImageConfiguration configuration,
rdr.ImageErrorListener onError,
}) async {
final completer = Completer<ImageProvider>();
final stream = image.resolve(configuration ?? rdr.ImageConfiguration.empty);
rdr.ImageStreamListener listener;
listener = rdr.ImageStreamListener((rdr.ImageInfo image, bool sync) async {
final bytes =
await image.image.toByteData(format: ui.ImageByteFormat.rawRgba);
final result = RawImage(
bytes: bytes.buffer.asUint8List(),
width: image.image.width,
height: image.image.height);
if (!completer.isCompleted) {
completer.complete(result);
}
stream.removeListener(listener);
}, onError: (dynamic exception, StackTrace stackTrace) {
if (!completer.isCompleted) {
completer.complete(null);
}
if (onError != null) {
onError(exception, stackTrace);
} else {
// https://groups.google.com/forum/#!topic/flutter-announce/hp1RNIgej38
assert(false, 'image failed to load');
}
});
stream.addListener(listener);
return completer.future;
}
/// Loads a font from an asset bundle key. If used multiple times with the same font name,
/// it will be included multiple times in the pdf file
Future<TtfFont> fontFromAssetBundle(String key, AssetBundle bundle) async {
... ...
... ... @@ -18,7 +18,7 @@ dependencies:
image: ^2.1.4
js: ^0.6.1
meta: ^1.1.5
pdf: ^1.11.1
pdf: ^1.13.0
plugin_platform_interface: ^1.0.2
dev_dependencies:
... ...
... ... @@ -45,15 +45,15 @@ void main() {
// expect(await Printing.platformVersion, '42');
});
test('pdfImageFromImageProvider(FileImage)', () async {
final image = await pdfImageFromImageProvider(
pdf: doc.document, image: FileImage(File('$path/example.png')));
test('flutterImageProvider(FileImage)', () async {
final image =
await flutterImageProvider(FileImage(File('$path/example.png')));
doc.addPage(
pw.Page(
build: (pw.Context context) => pw.Center(
child: pw.Container(
child: pw.Image(image),
child: pw.Image.provider(image),
),
),
),
... ...