Flutter: Print image with right orientation and size A4 - flutter

I want to print an image in flutter. I use the package "printing 3.5.0". My problem is now I want use the hole space of the page. But when I put the orientation or size, both parameters are ignored.
_printImage(GlobalKey globalKey) async {
final pdf = new PdfDocument();
final page = new PdfPage(pdf, pageFormat: PdfPageFormat.a4);
final doc = pw.Document();
final g = page.getGraphics();
RenderRepaintBoundary boundary =
globalKey.currentContext.findRenderObject();
ui.Image _image = await boundary.toImage();
var bytes = await _image.toByteData(format: ui.ImageByteFormat.rawRgba);
PdfImage image = new PdfImage(pdf,
image: bytes.buffer.asUint8List(),
width: _image.width,
height: _image.height);
doc.addPage(pw.Page(build: (pw.Context context) {
return pw.Center(
child: pw.Image(image),
); // Center
}));
g.drawImage(image, 20.0, 0.0);
Printing.layoutPdf(onLayout: (pageFormat) {
return pdf.save();
});
}
Even if I adjust the width and height of the image, the whole page is not used.
Does not exist a parameter that automatically scales the image on the whole page and observes the orientation?
I'm grateful for any help.

Related

How to load custom font correctly in this flutter test?

I have a method in my cubit that captures a widget as an image and I am testing this method. Long story short this method calls _captureFromWidget (see below, the code is copied from package screenshot), which accepts the Widget and returns a Uint8List. (I am not testing that the package works correctly, I am testing that my method and its parameters work correctly).
The problem is, that in real app the widget is captured correctly, but in the test, the font of all the Text widgets is not rendered correctly, and boxes are shown instead of letters.
I know the reason of this, see here, and here.
I tried loading the font as they suggested:
in pubspec.yaml:
assets:
- assets/fonts/
and I have in the assets/fonts folder my font: AbrilFatface-Regular.ttf
in my test:
final Future<ByteData> abrilFatFaceFontData = rootBundle.load('assets/fonts/AbrilFatface-Regular.ttf');
final FontLoader fontLoader = FontLoader('AbrilFatFace-Regular')..addFont(abrilFatFaceFontData);
await fontLoader.load();
Text text = ...; // text that uses custom font from above
cubit.captureWidget(...); // will invoke _captureFromWidget
but still, boxes are shown instead of letters in the captured image:
this is the result of calling the same methods with the same arguments from the real app:
So how to provide the font to the test correctly?
Here is the code that captures the widget:
/// [context] parameter is used to Inherit App Theme and MediaQuery data.
Future<Uint8List> _captureFromWidget(
widgets.Widget widget, {
required Duration delay,
double? pixelRatio,
widgets.BuildContext? context,
}) async {
// Retry counter
int retryCounter = 3;
bool isDirty = false;
widgets.Widget child = widget;
if (context != null) {
// Inherit Theme and MediaQuery of app
child = widgets.InheritedTheme.captureAll(
context,
widgets.MediaQuery(data: widgets.MediaQuery.of(context), child: child),
);
}
final RenderRepaintBoundary repaintBoundary = RenderRepaintBoundary();
Size logicalSize = ui.window.physicalSize / ui.window.devicePixelRatio;
Size imageSize = ui.window.physicalSize;
assert(logicalSize.aspectRatio.toPrecision(5) == imageSize.aspectRatio.toPrecision(5));
final RenderView renderView = RenderView(
window: ui.window,
child: RenderPositionedBox(alignment: Alignment.center, child: repaintBoundary),
configuration: ViewConfiguration(
size: logicalSize,
devicePixelRatio: pixelRatio ?? 1.0,
),
);
final PipelineOwner pipelineOwner = PipelineOwner();
final widgets.BuildOwner buildOwner = widgets.BuildOwner(
focusManager: widgets.FocusManager(),
onBuildScheduled: () {
///
///current render is dirty, mark it.
///
isDirty = true;
});
pipelineOwner.rootNode = renderView;
renderView.prepareInitialFrame();
final widgets.RenderObjectToWidgetElement<RenderBox> rootElement = widgets.RenderObjectToWidgetAdapter<RenderBox>(
container: repaintBoundary,
child: widgets.Directionality(
textDirection: TextDirection.ltr,
child: child,
)).attachToRenderTree(
buildOwner,
);
// Render Widget
buildOwner.buildScope(
rootElement,
);
buildOwner.finalizeTree();
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
ui.Image? image;
do {
// Reset the dirty flag
isDirty = false;
image = await repaintBoundary.toImage(pixelRatio: pixelRatio ?? (imageSize.width / logicalSize.width));
// This delay should increase with Widget tree Size
await Future.delayed(delay);
// Check does this require rebuild
if (isDirty) {
// Previous capture has been updated, re-render again.
buildOwner.buildScope(
rootElement,
);
buildOwner.finalizeTree();
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
}
retryCounter--;
//retry until capture is successful
} while (isDirty && retryCounter >= 0);
final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
return byteData!.buffer.asUint8List();
}
Turns out I was doing everything correctly except one thing.
The line final FontLoader fontLoader = FontLoader('AbrilFatFace-Regular')..addFont(abrilFatFaceFontData); will determine the fontFamily name that must be used in the TextStyle of the Text element.
and since I was using AbrilFatFace as fontFamily, the test was not working.
So the solution is to use in the fontFamily, the same name passed to the FontLoader, since thats the name the font will be identified with.

How to load image from assets folder inside a pdf in Flutter web?

We want to show image on a pdf from assets folder in Flutter web application:
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:flutter/material.dart';
.............
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Container(
height: 400,
width: 900,
child: PdfPreview(
build: (format) => _generatePdf(format, "SOME TITLE"),
)),
),
]));
}
Future<Uint8List> _generatePdf(PdfPageFormat format) async {
final pdf = pw.Document();
pdf.addPage(
pw.Page(
pageFormat: format,
build: (context) {
return pw.Image(AssetImage('assets/imglogo.png')); //This line gives the error
}));
return pdf.save();
}
This code gives error:
The argument type 'AssetImage' can't be assigned to the parameter type 'ImageProvider'
The documentation addresses only two cases To load an image from a file:(dart.io is not supported on the web), and To load an image from the network using the printing package:, which is not the case, so we tried the solutions provided here: 1,2, but each one gives a different exception.
Is there another approach to achieve this?
You can convert your ByteData directly to Uint8List as shown in the example code below. This can then be passed to the MemoryImage constructor:
Future<void> addPage(pw.Document pdf, String filename) async {
final imageByteData = await rootBundle.load('assets/$filename');
// Convert ByteData to Uint8List
final imageUint8List = imageByteData.buffer
.asUint8List(imageByteData.offsetInBytes, imageByteData.lengthInBytes);
final image = pw.MemoryImage(imageUint8List);
pdf.addPage(
pw.Page(
build: (pw.Context context) {
return pw.Center(
child: pw.Image(image),
); // Center
},
),
);
}
To achieve this you can get the asset image as a file, and then use this file in the PDF. If I take your code, we can add a function to get a File representation of your asset image :
Future<File> getImageFileFromAssets(String path) async {
final byteData = await rootBundle.load('assets/$path');
final file = File('${(await getTemporaryDirectory()).path}/$path');
await file.writeAsBytes(byteData.buffer
.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
return file;
}
To make it works, you need to add path_provider to your dependencies.
Then you can use this function in your pdf generation function :
final pdf = pw.Document();
final file = await getImageFileFromAssets(yourpath);
final image = pw.MemoryImage(
file.readAsBytesSync(),
);
pdf.addPage(pw.Page(
pageFormat: format,
build: (context) {
return pw.Image(image);
}));
return pdf.save();

How to prevent "Duplicate GlobalKey detected in widget tree" when building Widget offscreen

I am trying to export a Widget as Image, that is wider than the actual Viewport.
I have found this method:
Future<Uint8List?> createImageFromWidget(Widget widget,
{Size? logicalSize, Size? imageSize}) async {
final repaintBoundary = RenderRepaintBoundary();
logicalSize ??= ui.window.physicalSize / ui.window.devicePixelRatio;
imageSize ??= ui.window.physicalSize;
assert(logicalSize.aspectRatio == imageSize.aspectRatio);
final renderView = RenderView(
window: ui.window,
child: RenderPositionedBox(child: repaintBoundary),
configuration: ViewConfiguration(
size: logicalSize,
),
);
final pipelineOwner = PipelineOwner();
final buildOwner = BuildOwner(focusManager: FocusManager());
pipelineOwner.rootNode = renderView;
renderView.prepareInitialFrame();
final rootElement = RenderObjectToWidgetAdapter<RenderBox>(
container: repaintBoundary,
child: widget,
).attachToRenderTree(buildOwner);
buildOwner.buildScope(rootElement);
buildOwner.finalizeTree();
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
final image = await repaintBoundary.toImage(
pixelRatio: imageSize.width / logicalSize.width);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) return null;
return byteData.buffer.asUint8List();
}
From what I understand, this method builds the referenced Widget in a new Offscreen-Widgettree and sets the viewport size to the size of the Widget, that needs to get exported.
This method in and of itself works, but heres my problem:
I am referencing the Widget that needs to get exported by GlobalKey.
So it looks something like this:
Repaintboundary(
key: myGlobalKey,
child : WidgetIWantToExport()
);
void exportWidget(){
final currentWidgetSize = myGlobalKey.currentContext!.size!;
final keyWidget = myGlobalKey.currentWidget!;
final pngBytes = await createImageFromWidget(
keyWidget,
logicalSize: Size(currentWidgetSize.width, currentWidgetSize.height),
imageSize: Size(currentWidgetSize.width, currentWidgetSize.height),
);
//exporting bytes
}
So when I run this method, the .attachToRenderTree() throws an Error: '_elements.contains(element)': is not true. ... Duplicate GlobalKey detected in widget tree.
So I am assuming the problem is, that it tries to build a widget with the same GobalKey in the new RenderTree, which throws this error.
How can I fix this?
I solved this by accessing the child of the Repaintboundary, that I am referencing by Key.
The code looks like this:
/// Creates an image from the given widget by first spinning up a element and render tree,
/// and then creating an image via a [RepaintBoundary].
///
/// The final image will be of size [exportViewportSize].
/// If no [exportViewportSize] is supplied, the image will be of size [logicalSize], which has a fallback value.
Future<Uint8List?> createImageFromWidget(Widget widget,
{Size? logicalSize, Size? imageSize, Size? exportViewportSize}) async {
final renderRepaintBoundary = RenderRepaintBoundary();
final repaintBoundary = widget as RepaintBoundary; //casting the widget as RepaintBoundary
final renderWidget = repaintBoundary.child!; //Accessing its child widget
logicalSize ??= ui.window.physicalSize / ui.window.devicePixelRatio;
imageSize ??= ui.window.physicalSize;
assert(logicalSize.aspectRatio == imageSize.aspectRatio);
final renderView = RenderView(
window: ui.window,
child: RenderPositionedBox(child: renderRepaintBoundary),
configuration: ViewConfiguration(
size: exportViewportSize ?? logicalSize,
),
);
final pipelineOwner = PipelineOwner();
final buildOwner = BuildOwner(focusManager: FocusManager());
pipelineOwner.rootNode = renderView;
renderView.prepareInitialFrame();
final rootElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderRepaintBoundary,
child: MaterialApp(
home: renderWidget,
),
).attachToRenderTree(buildOwner);
buildOwner.buildScope(rootElement);
buildOwner.finalizeTree();
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
final image = await renderRepaintBoundary.toImage(
pixelRatio: imageSize.width / logicalSize.width);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) return null;
return byteData.buffer.asUint8List();
}

Flutter: redraw Custompaint lines after changing scale

I am creating a note-taking app using Flutter and I use a CustomPainter for the drawing part. For better performance, the CustomPaint gets converted into an image after some Lines. Now the problem with images is when zooming in, the lines (which are Images now) are very pixelated. Now my first Idea to solve this was redrawing the lines after zooming.
But how do I redraw them correctly without them still having a bad resolution? Also, is it possible to only redraw the lines that are visible on the screen currently?
everything is wrapped with a layoutbuilder which provides constraints + pixelratio (scale):
LayoutBuilder(builder: (context, constraints) {
size = constraints.biggest;
scale = MediaQuery.of(context).devicePixelRatio;
How I did the zooming:
GestureDetector(
onScaleStart: (details) {
_initialFocalPoint = details.focalPoint;
_initialScale = _scale;
},
onScaleUpdate: (details) {
setState(() {
_sessionOffset =
details.focalPoint - _initialFocalPoint;
_scale = _initialScale * details.scale;
});
},
onScaleEnd: (details) {
setState(() {
_offset += _sessionOffset;
_sessionOffset = Offset.zero;
});
},
child: Transform.translate(
offset: _offset + _sessionOffset,
child: Transform.scale(
scale: _scale,
child: ....
my CustomPaint which comes somewhere after the Transform widgets:
CustomPaint(
size: size!,
willChange: true,
painter: DrawingViewPainter(
pointsList: drawingNotifier.points,
image: image
),
);
snippet with the custpmpaint to image converter (drawn lines get converted to images to save performance):
ui.PictureRecorder recorder = ui.PictureRecorder();
Canvas canvas = ui.Canvas(recorder);
canvas.scale(scale!);
DrawingViewPainter(image: image, pointsList: points).paint(canvas, size!);
ui.Picture picture = recorder.endRecording();
ui.Image newImage = await picture.toImage(
(size!.width * scale!).ceil(),
(size!.height * scale!).ceil(),
);
......
image = newImage
the Custompaint part with the image drawer:
canvas.drawImageRect(
image!,
Offset.zero & Size(image!.width.toDouble(), image!.height.toDouble()),
Offset.zero & size,
Paint());
}
(sorry for the huge amount of code)
If you know of a better way please let me know as well.
Here a video of the Problem

How to take screenshot in Flutter?

Is there a way or package that can help either taking fullscreen screenshot, or screenshot of widget that is wrapped or at least sharing the picture of screen via native share option?
There are some packages and I have tried, did not found any useful one.
RepaintBoundary is the Widget you're looking for, this one can be converted into an image.
Example:
Future<CaptureResult> captureImage() async {
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
final boundary = _boundaryKey.currentContext.findRenderObject() as RenderRepaintBoundary;
final image = await boundary.toImage(pixelRatio: pixelRatio);
final data = await image.toByteData(format: ui.ImageByteFormat.png);
return CaptureResult(data.buffer.asUint8List(), image.width, image.height);
}
final _boundaryKey = GlobalKey();
RepaintBoundary(
key: _boundaryKey,
child: Container(),// Your Widgets to be captured.
)
Link: capture_widget.dart