changeset 2727:bf585f375286

Android: Initial print implementation for Android.... Currently crashes shortly after displaying printer selection dialog. Fixes coming but I wanted to get the bulk of it committed.
author bsmith@81767d24-ef19-dc11-ae90-00e081727c95
date Sat, 11 Dec 2021 14:14:26 +0000
parents 7a15401e73f4
children 850da6b24830
files android/DWindows.kt android/dw.cpp
diffstat 2 files changed, 233 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/android/DWindows.kt	Sat Dec 11 00:54:01 2021 +0000
+++ b/android/DWindows.kt	Sat Dec 11 14:14:26 2021 +0000
@@ -67,9 +67,9 @@
 import android.content.ContentUris
 import androidx.appcompat.widget.AppCompatSeekBar
 import android.content.DialogInterface
-
-
-
+import android.graphics.pdf.PdfDocument
+import android.print.*
+import android.print.pdf.PrintedPdfDocument
 
 
 // Color Wheel section
@@ -946,6 +946,106 @@
     external fun eventHandlerHTMLChanged(obj1: View, message: Int, URI: String, status: Int)
 }
 
+class DWPrintDocumentAdapter : PrintDocumentAdapter()
+{
+    var context: Context? = null
+    var pages: Int = 0
+    var pdfDocument: PrintedPdfDocument? = null
+    var print: Long = 0
+
+    override fun onLayout(
+        oldAttributes: PrintAttributes?,
+        newAttributes: PrintAttributes,
+        cancellationSignal: CancellationSignal?,
+        callback: LayoutResultCallback,
+        extras: Bundle?
+    ) {
+        // Create a new PdfDocument with the requested page attributes
+        pdfDocument = context?.let { PrintedPdfDocument(it, newAttributes) }
+
+        // Respond to cancellation request
+        if (cancellationSignal?.isCanceled == true) {
+            callback.onLayoutCancelled()
+            return
+        }
+
+        if (pages > 0) {
+            // Return print information to print framework
+            PrintDocumentInfo.Builder("print_output.pdf")
+                .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+                .setPageCount(pages)
+                .build()
+                .also { info ->
+                    // Content layout reflow is complete
+                    callback.onLayoutFinished(info, true)
+                }
+        } else {
+            // Otherwise report an error to the print framework
+            callback.onLayoutFailed("No pages to print.")
+        }
+    }
+
+    override fun onWrite(
+        pageRanges: Array<out PageRange>,
+        destination: ParcelFileDescriptor,
+        cancellationSignal: CancellationSignal?,
+        callback: WriteResultCallback
+    ) {
+        var writtenPagesArray: Array<PdfDocument.Page> = emptyArray()
+
+        // Iterate over each page of the document,
+        // check if it's in the output range.
+        for (i in 0 until pages) {
+            pdfDocument?.startPage(i)?.also { page ->
+
+                // check for cancellation
+                if (cancellationSignal?.isCanceled == true) {
+                    callback.onWriteCancelled()
+                    pdfDocument?.close()
+                    pdfDocument = null
+                    return
+                }
+
+                // Draw page content for printing
+                var bitmap = Bitmap.createBitmap(page.canvas.width, page.canvas.height, Bitmap.Config.ARGB_8888)
+                // Actual drawing is done in the JNI C code callback to the bitmap
+                eventHandlerPrintDraw(print, bitmap, i, page.canvas.width, page.canvas.height)
+                // Copy from the bitmap canvas our C code drew on to the PDF page canvas
+                val rect = Rect(0, 0, page.canvas.width, page.canvas.height)
+                page.canvas.drawBitmap(bitmap, rect, rect, null)
+
+                // Rendering is complete, so page can be finalized.
+                pdfDocument?.finishPage(page)
+
+                // Add the new page to the array
+                writtenPagesArray += page
+            }
+        }
+
+        // Write PDF document to file
+        try {
+            pdfDocument?.writeTo(FileOutputStream(destination.fileDescriptor))
+        } catch (e: IOException) {
+            callback.onWriteFailed(e.toString())
+            return
+        } finally {
+            pdfDocument?.close()
+            pdfDocument = null
+        }
+        // Signal the print framework the document is complete
+        callback.onWriteFinished(pageRanges)
+    }
+
+    override fun onFinish() {
+        // Notify our C code so it can cleanup
+        eventHandlerPrintFinish(print)
+        super.onFinish()
+    }
+
+    external fun eventHandlerPrintDraw(print: Long, bitmap: Bitmap, page: Int, width: Int, height: Int)
+    external fun eventHandlerPrintFinish(print: Long)
+}
+
 class DWSlider
 @JvmOverloads constructor(context: Context): FrameLayout(context) {
     val slider: SeekBar = SeekBar(context)
@@ -4658,6 +4758,35 @@
         return pixmap
     }
 
+    fun printRun(print: Long, flags: Int, jobname: String, pages: Int, runflags: Int): PrintJob?
+    {
+        var retval: PrintJob? = null
+
+        waitOnUiThread {
+            // Get a PrintManager instance
+            val printManager = this.getSystemService(Context.PRINT_SERVICE) as PrintManager
+            // Setup our print adapter
+            val printAdapter = DWPrintDocumentAdapter()
+            printAdapter.context = this
+            printAdapter.pages = pages
+            printAdapter.print = print
+            // Start a print job, passing in a PrintDocumentAdapter implementation
+            // to handle the generation of a print document
+            retval = printManager.print(jobname, printAdapter, null)
+        }
+        return retval
+    }
+
+    fun printCancel(printjob: PrintJob)
+    {
+        waitOnUiThread {
+            // Get a PrintManager instance
+            val printManager = this.getSystemService(Context.PRINT_SERVICE) as PrintManager
+            // Remove the job we earlier added from the queue
+            printManager.printJobs.remove(printjob)
+        }
+    }
+
     fun pixmapGetDimensions(pixmap: Bitmap): Long
     {
         var dimensions: Long = 0
--- a/android/dw.cpp	Sat Dec 11 00:54:01 2021 +0000
+++ b/android/dw.cpp	Sat Dec 11 14:14:26 2021 +0000
@@ -668,6 +668,49 @@
     _dw_event_handler(obj1, params);
 }
 
+typedef struct _dwprint
+{
+    int (*drawfunc)(HPRINT, HPIXMAP, int, void *);
+    void *drawdata;
+    unsigned long flags;
+    char *jobname;
+    jint pages;
+    jobject printjob;
+} DWPrint;
+
+/* Handlers for Print events */
+JNIEXPORT void JNICALL
+Java_org_dbsoft_dwindows_DWPrintDocumentAdapter_eventHandlerPrintDraw(JNIEnv* env, jobject obj, jlong pr,
+                                                                      jobject bitmap, jint page, jint width, jint height)
+{
+    DWPrint *print = (DWPrint *)pr;
+
+    if(print && print->drawfunc)
+    {
+        HPIXMAP pixmap = (HPIXMAP)alloca(sizeof(HPIXMAP));
+
+        pixmap->width = width;
+        pixmap->height = height;
+        pixmap->bitmap = bitmap;
+        pixmap->handle = nullptr;
+
+        print->drawfunc(print, pixmap, (int)page, print->drawdata);
+    }
+}
+
+JNIEXPORT void JNICALL
+Java_org_dbsoft_dwindows_DWPrintDocumentAdapter_eventHandlerPrintFinish(JNIEnv* env, jobject obj, jlong pr)
+{
+    DWPrint *print = (DWPrint *) pr;
+
+    if(print)
+    {
+        if(print->jobname)
+            free(print->jobname);
+        free(print);
+    }
+}
+
 JNIEXPORT void JNICALL
 Java_org_dbsoft_dwindows_DWindows_eventHandlerInt(JNIEnv* env, jobject obj, jobject obj1, jint message,
                                                jint inta, jint intb, jint intc, jint intd) {
@@ -7509,8 +7552,21 @@
  */
 HPRINT API dw_print_new(const char *jobname, unsigned long flags, unsigned int pages, void *drawfunc, void *drawdata)
 {
-    /* TODO: Implement printing */
-    return nullptr;
+    DWPrint *print;
+
+    if(!drawfunc || !(print = (DWPrint *)calloc(1, sizeof(DWPrint))))
+        return nullptr;
+
+    if(!jobname)
+        jobname = "Dynamic Windows Print Job";
+
+    print->drawfunc = (int (*)(HPRINT, HPIXMAP, int, void *))drawfunc;
+    print->drawdata = drawdata;
+    print->flags = flags;
+    print->jobname = strdup(jobname);
+    print->pages = (jint)pages;
+
+    return print;
 }
 
 /*
@@ -7521,10 +7577,27 @@
  * Returns:
  *       DW_ERROR_UNKNOWN on error or DW_ERROR_NONE on success.
  */
-int API dw_print_run(HPRINT print, unsigned long flags)
-{
-    /* TODO: Implement printing */
-    return DW_ERROR_UNKNOWN;
+int API dw_print_run(HPRINT pr, unsigned long flags)
+{
+    JNIEnv *env;
+    DWPrint *print = (DWPrint *)pr;
+    int retval = DW_ERROR_UNKNOWN;
+
+    if(print && print->drawfunc && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
+    {
+        // Construct a String
+        jstring jstr = env->NewStringUTF(print->jobname);
+        // First get the class that contains the method you need to call
+        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
+        // Get the method that you want to call
+        jmethodID printRun = env->GetMethodID(clazz, "printRun",
+                                              "(JILjava/lang/String;II)Landroid/print/PrintJob;");
+        // Call the method on the object
+        print->printjob = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, printRun, (jlong)pr, (jint)print->flags, jstr, (jint)print->pages, (jint)flags), _DW_REFERENCE_WEAK);
+        if(print->printjob)
+            retval = DW_ERROR_NONE;
+    }
+    return retval;
 }
 
 /*
@@ -7532,9 +7605,28 @@
  * Parameters:
  *       print: Handle to the print object returned by dw_print_new().
  */
-void API dw_print_cancel(HPRINT print)
-{
-    /* TODO: Implement printing */
+void API dw_print_cancel(HPRINT pr)
+{
+    JNIEnv *env;
+    DWPrint *print = (DWPrint *)pr;
+
+    if(print)
+    {
+        if(print->printjob && (env = (JNIEnv *) pthread_getspecific(_dw_env_key)))
+        {
+            // First get the class that contains the method you need to call
+            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
+            // Get the method that you want to call
+            jmethodID printCancel = env->GetMethodID(clazz, "printCancel",
+                                                     "(Landroid/print/PrintJob;)V");
+            // Call the method on the object
+            env->CallVoidMethod(_dw_obj, printCancel, print->printjob);
+            _dw_jni_check_exception(env);
+        }
+        if(print->jobname)
+            free(print->jobname);
+        free(print);
+    }
 }
 
 /*