Skip to content

printing plugin integration #364

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ option(TRY_BUILD_GSTREAMER_VIDEO_PLAYER_PLUGIN "Don't throw an error if the gstr

option(BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN "Include the gstreamer based audio plugins in the finished binary." ON)
option(TRY_BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN "Don't throw an error if the gstreamer libs aren't found, instead just don't build gstreamer audio plugin." ON)

option(BUILD_PRINTING_PLUGIN "Include the printing plugin in the finished binary." OFF)
option(BUILD_SENTRY_PLUGIN "Include the sentry plugin in the finished binary. Allows for crash reporting to sentry.io." OFF)

option(BUILD_CHARSET_CONVERTER_PLUGIN "Include the charset converter plugin in the finished binary." OFF)
option(ENABLE_OPENGL "Build with EGL/OpenGL rendering support." ON)
option(TRY_ENABLE_OPENGL "Don't throw an error if EGL/OpenGL aren't found, instead just build without EGL/OpenGL support in that case." ON)
Expand Down Expand Up @@ -375,6 +374,12 @@ if (BUILD_GSTREAMER_AUDIO_PLAYER_PLUGIN)
endif()
endif()

if (BUILD_PRINTING_PLUGIN)
pkg_check_modules(IMAGEMAGICK IMPORTED_TARGET MagickWand)
target_sources(flutterpi_module PRIVATE src/plugins/printing.c)
target_link_libraries(flutterpi_module PUBLIC PkgConfig::IMAGEMAGICK)
endif()

if (BUILD_CHARSET_CONVERTER_PLUGIN)
target_sources(flutterpi_module PRIVATE src/plugins/charset_converter.c)
endif()
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ If you encounter issues running flutter-pi on any of the supported platforms lis
```shell
sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa
```

If you want to use the [printing](#printing), install these too:
```shell
$ sudo apt install imagemagick
```
<details>
<summary>More Info</summary>

Expand Down Expand Up @@ -416,6 +421,13 @@ To use the gstreamer video player, just rebuild flutter-pi (delete your build fo

And then, just use the stuff in the official [video_player](https://pub.dev/packages/video_player) package. (`VideoPlayer`, `VideoPlayerController`, etc, there's nothing specific you need to do on the dart-side)

### printing
Printing is a plugin that allows Flutter apps to generate and print documents to android or ios compatible printers.

To use the printing plugin, just rebuild flutter-pi (delete your build folder and reconfigure) and make sure the necessary printing packages are installed. (See [dependencies](#dependencies))

And then, just use the stuff in the official [printing](https://pub.dev/packages/printing) package.

### audioplayers
As of current moment flutter-pi implements plugin for `audioplayers: ^5.0.0`.
There are several things you need to keep in mind:
Expand Down Expand Up @@ -447,6 +459,7 @@ This is why I created my own (userspace) touchscreen driver, for improved latenc
| linux_spidev ([package](https://pub.dev/packages/linux_spidev/)) ([repo](https://github.com/ardera/flutter_packages/tree/main/packages/linux_spidev)) | 🖨 peripherals | Hannes Winkler | SPI bus support for dart/flutter, uses kernel interfaces directly for more performance. |
| dart_periphery ([package](https://pub.dev/packages/dart_periphery)) ([repo](https://github.com/pezi/dart_periphery)) | 🖨 peripherals | [Peter Sauer](https://github.com/pezi/) | All-in-one package GPIO, I2C, SPI, Serial, PWM, Led, MMIO support using c-periphery. |
| flutterpi_gstreamer_video_player ([package](https://pub.dev/packages/flutterpi_gstreamer_video_player)) ([repo](https://github.com/ardera/flutter_packages/tree/main/packages/flutterpi_gstreamer_video_player)) | ⏯️ multimedia | Hannes Winkler | Official video player implementation for flutter-pi. See [GStreamer video player](#gstreamer-video-player) section above. |
| printing ([package](https://pub.dev/packages/printing)) ([repo](https://github.com/DavBfr/dart_pdf)) | 🖨 peripherals | David PHAM-VAN | Generate and print documents to android or ios compatible printers. See [printing](#printing) section above. |
| charset_converter ([package](https://pub.dev/packages/charset_converter)) ([repo](https://github.com/pr0gramista/charset_converter)) | 🗚 encoding | Bartosz Wiśniewski | Encode and decode charsets using platform built-in converter. |
| sentry_flutter ([package](https://pub.dev/packages/sentry_flutter)) ([repo](https://github.com/getsentry/sentry-dart))| 📊 Monitoring | sentry.io | See https://github.com/ardera/flutter-pi/wiki/Sentry-Support for instructions. |

Expand Down
236 changes: 236 additions & 0 deletions src/plugins/printing.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
#include "plugins/printing.h"
#include "flutter-pi.h"
#include "pluginregistry.h"
#include "util/logging.h"
#include <MagickWand/MagickWand.h>

static void on_page_raster_end(int64_t job, char* error) {
struct std_value response = STDMAP1(STDSTRING("job"), STDINT32(job));
if (error != NULL) {
response = STDMAP2(STDSTRING("job"), STDINT32(job), STDSTRING("error"), STDSTRING(error));

LOG_ERROR("%s\n", error);
}

platch_call_std(PRINTING_CHANNEL, "onPageRasterEnd", &response, NULL, NULL);
}

static void on_page_rasterized(int64_t job, const uint8_t* data, size_t size, int width, int height) {
struct std_value image = (struct std_value){ .type = kStdUInt8Array, .uint8array = data, .size = size };

struct std_value response = STDMAP4(
STDSTRING("image"),
image,
STDSTRING("width"),
STDINT32(width),
STDSTRING("height"),
STDINT32(height),
STDSTRING("job"),
STDINT32(job)
);

platch_call_std(PRINTING_CHANNEL, "onPageRasterized", &response, NULL, NULL);
}

static void raster_pdf(const uint8_t *data, size_t size, const int32_t *pages, size_t pages_count, double scale, int64_t job) {
MagickWand *wand = NULL;
PixelWand *color = NULL;

int width, height;

MagickWandGenesis();

wand = NewMagickWand();

color = NewPixelWand();
PixelSetColor(color, "white");

MagickBooleanType result = MagickReadImageBlob(wand, data, size);
if(result != MagickTrue) {
on_page_raster_end(job, "Cannot read images from PDF blob.");
return;
}

MagickResetIterator(wand);

bool all_pages = false;
if (pages_count == 0) {
all_pages = true;
pages_count = MagickGetNumberImages(wand);
}

int current_page = 0;
while(MagickNextImage(wand) != MagickFalse) {
if(!all_pages){
bool shouldRasterize = false;

//Check if current page is set to be rasterized
for(size_t pn = 0; pn < pages_count; pn++) {
if(pages[pn] == current_page) {
shouldRasterize = true;
break;
}
}

if(!shouldRasterize) {
current_page++;
continue;
}
}

// Get the image's width and height
width = MagickGetImageWidth(wand);
height = MagickGetImageHeight(wand);

int32_t bWidth = width * scale;
int32_t bHeight = height * scale;

MagickResizeImage(wand, bWidth, bHeight, LanczosFilter);
MagickSetImageFormat(wand, "bmp");

size_t page_size;
uint8_t *page_data = MagickGetImageBlob(wand, &page_size);

on_page_rasterized(job, page_data, page_size, bWidth, bHeight);

MagickRelinquishMemory(page_data);

current_page++;
}

/* Clean up */
if(wand){
wand = DestroyMagickWand(wand);
}

if(color){
color = DestroyPixelWand(color);
}

MagickWandTerminus();

on_page_raster_end(job, NULL);
}

static int on_raster_pdf(struct platch_obj *object, FlutterPlatformMessageResponseHandle *response_handle) {
struct std_value *args, *tmp;
const uint8_t *data;
size_t data_length;
double scale;
int64_t job;

args = &object->std_arg;

if (args == NULL || !STDVALUE_IS_MAP(*args)) {
return platch_respond_illegal_arg_std(response_handle, "Expected `arg` to be a map.");
}

tmp = stdmap_get_str(&object->std_arg, "doc");
if (tmp == NULL || (*tmp).type != kStdUInt8Array ) {
return platch_respond_illegal_arg_std(response_handle, "Expected `arg['doc'] to be a uint8_t list.");
}

data = tmp->uint8array;
data_length = tmp->size;

int32_t* pages;
size_t pages_count;
tmp = stdmap_get_str(&object->std_arg, "pages");
if (tmp != NULL || STDVALUE_IS_LIST(*tmp)) {
pages_count = tmp->size;
pages = (int32_t*)malloc(sizeof(int32_t) * pages_count);
for (size_t n = 0; n < pages_count; n++) {
struct std_value page = tmp->list[n];

if(!STDVALUE_IS_INT(page)){
continue;
}

pages[n] = page.int32_value;
}
}

tmp = stdmap_get_str(&object->std_arg, "scale");
if (tmp == NULL || !STDVALUE_IS_FLOAT(*tmp)) {
return platch_respond_illegal_arg_std(response_handle, "Expected `arg['scale'] to be a double.");
}

scale = STDVALUE_AS_FLOAT(*tmp);

tmp = stdmap_get_str(&object->std_arg, "job");
if (tmp == NULL || !STDVALUE_IS_INT(*tmp)) {
return platch_respond_illegal_arg_std(response_handle, "Expected `arg['job'] to be an int.");
}

job = STDVALUE_AS_INT(*tmp);

//Rasterize
raster_pdf(data, data_length, pages, pages_count, scale, job);

free(pages);

return platch_respond(
response_handle,
&(struct platch_obj){ .codec = kStandardMethodCallResponse, .success = true, .std_result = { .type = kStdTrue } }
);
}

static int on_printing_info(struct platch_obj *object, FlutterPlatformMessageResponseHandle *response_handle) {
(void) object;

return platch_respond(
response_handle,
&PLATCH_OBJ_STD_MSG(STDMAP6(
STDSTRING("canPrint"),
STDBOOL(false),
STDSTRING("canShare"),
STDBOOL(false),
STDSTRING("canRaster"),
STDBOOL(true),
STDSTRING("canListPrinters"),
STDBOOL(false),
STDSTRING("directPrint"),
STDBOOL(false),
STDSTRING("dynamicLayout"),
STDBOOL(false)
))
);
}

static int on_receive(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *response_handle) {
(void) channel;

const char *method;
method = object->method;

if (streq(method, "printingInfo")) {
return on_printing_info(object, response_handle);
} else if (streq(method, "rasterPdf")) {
return on_raster_pdf(object, response_handle);
}

return platch_respond_not_implemented(response_handle);
}

enum plugin_init_result printing_init(struct flutterpi *flutterpi, void **userdata_out) {
(void) flutterpi;

int ok;

ok = plugin_registry_set_receiver_locked(PRINTING_CHANNEL, kStandardMethodCall, on_receive);
if (ok != 0) {
return PLUGIN_INIT_RESULT_ERROR;
}

*userdata_out = NULL;

return PLUGIN_INIT_RESULT_INITIALIZED;
}

void printing_deinit(struct flutterpi *flutterpi, void *userdata) {
(void) userdata;

plugin_registry_remove_receiver_v2_locked(flutterpi_get_plugin_registry(flutterpi), PRINTING_CHANNEL);
}

FLUTTERPI_PLUGIN("printing plugin", printing_plugin, printing_init, printing_deinit)
9 changes: 9 additions & 0 deletions src/plugins/printing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef _PRINTING_PLUGIN_H
#define _PRINTING_PLUGIN_H

#include <stdio.h>
#include <string.h>

#define PRINTING_CHANNEL "net.nfet.printing"

#endif
Loading