# HG changeset patch # User bsmith@81767d24-ef19-dc11-ae90-00e081727c95 # Date 1639232066 0 # Node ID bf585f37528647832558eedb96dad8e259efa46c # Parent 7a15401e73f48de6900027a10e2a9d549beec59d 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. diff -r 7a15401e73f4 -r bf585f375286 android/DWindows.kt --- 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, + destination: ParcelFileDescriptor, + cancellationSignal: CancellationSignal?, + callback: WriteResultCallback + ) { + var writtenPagesArray: Array = 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 diff -r 7a15401e73f4 -r bf585f375286 android/dw.cpp --- 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); + } } /*