diff --git a/CMakeLists.txt b/CMakeLists.txt index 734aba51..5c3a0707 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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() diff --git a/README.md b/README.md index de5e15d2..97ab7ab4 100644 --- a/README.md +++ b/README.md @@ -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 + ```
More Info @@ -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: @@ -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. | diff --git a/src/plugins/printing.c b/src/plugins/printing.c new file mode 100644 index 00000000..b5e205c6 --- /dev/null +++ b/src/plugins/printing.c @@ -0,0 +1,236 @@ +#include "plugins/printing.h" +#include "flutter-pi.h" +#include "pluginregistry.h" +#include "util/logging.h" +#include + +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) \ No newline at end of file diff --git a/src/plugins/printing.h b/src/plugins/printing.h new file mode 100644 index 00000000..a066f94e --- /dev/null +++ b/src/plugins/printing.h @@ -0,0 +1,9 @@ +#ifndef _PRINTING_PLUGIN_H +#define _PRINTING_PLUGIN_H + +#include +#include + +#define PRINTING_CHANNEL "net.nfet.printing" + +#endif