diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4972r1.so index 562b8abc621..1807907f2f8 100755 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4972r1.so +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4972r1.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aea877ec9eff47e0fff89de92ef3383f62e188590e7a037b981631c92aaea752 -size 39168 +oid sha256:ff2855d483fc997f2692a0f9d6e3775eb90743db49e53a576c42a7d60797eb34 +size 43408 diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4972r1.so index 0c6248ab26c..f1368fe08e2 100755 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4972r1.so +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4972r1.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:616e9e63473d7281ac7b995c423cf44f8665fbcbbba0b1048170a0cf7eb8541b -size 14112 +oid sha256:6fba450120f753344e4a52ea6c731118d4272c325c16363ce7e65a22aa74b68d +size 14408 diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4972r1.so index f9c8fe6b96d..e92f8871b20 100755 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4972r1.so +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4972r1.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f79f0ab0bbecd950624dc6c37e242073aa284cec62ffa310b336912b111991b5 -size 47952 +oid sha256:1b257d1d881ab0eb178c88af34d7a5c4c9ce3988d6506ff4a7440f8b2cbc3622 +size 48096 diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4972r1.so index 78ca320c441..4899c5f1c66 100755 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4972r1.so +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4972r1.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:998ff6f410ef05b30b8e57a05cfea2514730febba0e0114348a284d36965976c -size 14352 +oid sha256:1d7ed632ecc66cccaeaacf3cddd59737c74055aceed22a2213b822614737cfed +size 14496 diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4972r1.so index ebeb0a2263b..1f5578e6b76 100755 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4972r1.so +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4972r1.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da7a80e2c4da658f6e3e1495dbcc39a96d3a1a903ce826b9d187d4b5fc0f7c39 -size 610344 +oid sha256:8e34c00dbf368e9f35bed68e5ad54b9c969690f1c26b2df76fecda4d8a3e501f +size 626888 diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4972r1.so index 64c3c7cdf07..af46a94be46 100755 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4972r1.so +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4972r1.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:560a259b92a7a14f2146722d56fc67681d88680d56100fb0fc8297608637d115 -size 466912 +oid sha256:fec14f3cac401f6aa77087dd74e99db1321b0bbedf7aa1adaaefbacd0bff476b +size 491600 diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4972r1.so index 3ad7b8a7098..4d52c2cc367 100755 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4972r1.so +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4972r1.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9c9892b192a09fb79c68a499149d00522584dd0f12aebdaa787da6ca7a76b49 -size 412632 +oid sha256:56527bb18757065129d84b096bc306b7376838e26d6195f0a020549bb5f527a2 +size 437360 diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4972r1.so index 63d41181fe4..79dd2d57aae 100755 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4972r1.so +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4972r1.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:caa1a8258c09b99cfa3076f0207975223268d872dd5c87f8af492c8f631870ab -size 71640 +oid sha256:d3a998c181c39cc7c25b28127fe3fa06c629e71f0447018265817038f0cde262 +size 71792 diff --git a/build_gtk.sh b/build_gtk.sh new file mode 100755 index 00000000000..ea1dfb7db60 --- /dev/null +++ b/build_gtk.sh @@ -0,0 +1,6 @@ +cd /home/runner/work/eclipse.platform.swt/eclipse.platform.swt/bundles/org.eclipse.swt && java -Dws=gtk -Darch=x86_64 build-scripts/CollectSources.java -nativeSources '/home/runner/build/gtk' +cd /home/runner/build/gtk && SWT_JAVA_HOME=/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.9-10/x64 MODEL=x86_64 OUTPUT_DIR=/home/runner/build/tmp ./build.sh install clean +cd /home/runner/work/eclipse.platform.swt/eclipse.platform.swt +cd /home/runner/work/eclipse.platform.swt/eclipse.platform.swt/bundles/org.eclipse.swt && java -Dws=gtk -Darch=x86_64 build-scripts/CollectSources.java -nativeSources '/home/runner/build/gtk' +cd /home/runner/build/gtk && SWT_JAVA_HOME=/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.9-10/x64 MODEL=x86_64 OUTPUT_DIR=/home/runner/build/tmp ./build.sh install clean +cd /home/runner/work/eclipse.platform.swt/eclipse.platform.swt diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo.c b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo.c index 514e744c809..accf0da79be 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo.c @@ -1471,3 +1471,27 @@ JNIEXPORT void JNICALL Cairo_NATIVE(memmove___3DJJ) } #endif +#ifndef NO_cairo_1pdf_1surface_1create +JNIEXPORT jlong JNICALL Cairo_NATIVE(cairo_1pdf_1surface_1create) + (JNIEnv *env, jclass that, jbyteArray arg0, jdouble arg1, jdouble arg2) +{ + jbyte *lparg0=NULL; + jlong rc = 0; + Cairo_NATIVE_ENTER(env, that, cairo_1pdf_1surface_1create_FUNC); + if (arg0) if ((lparg0 = (*env)->GetByteArrayElements(env, arg0, NULL)) == NULL) goto fail; +/* + rc = (jlong)cairo_pdf_surface_create((const char *)lparg0, arg1, arg2); +*/ + { + Cairo_LOAD_FUNCTION(fp, cairo_pdf_surface_create) + if (fp) { + rc = (jlong)((cairo_surface_t * (CALLING_CONVENTION*)(const char *, jdouble, jdouble))fp)((const char *)lparg0, arg1, arg2); + } + } +fail: + if (arg0 && lparg0) (*env)->ReleaseByteArrayElements(env, arg0, lparg0, 0); + Cairo_NATIVE_EXIT(env, that, cairo_1pdf_1surface_1create_FUNC); + return rc; +} +#endif + diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_custom.h b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_custom.h index 3d314e25886..99408ca3154 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_custom.h +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_custom.h @@ -30,6 +30,7 @@ #define cairo_ps_surface_set_size_LIB LIB_CAIRO #define cairo_surface_set_device_scale_LIB LIB_CAIRO #define cairo_surface_get_device_scale_LIB LIB_CAIRO +#define cairo_pdf_surface_create_LIB LIB_CAIRO #ifdef CAIRO_HAS_XLIB_SURFACE #define cairo_xlib_surface_get_height_LIB LIB_CAIRO diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_stats.h index 23572bbca3b..86f31348171 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_stats.h +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_stats.h @@ -87,6 +87,7 @@ typedef enum { cairo_1pattern_1set_1filter_FUNC, cairo_1pattern_1set_1matrix_FUNC, cairo_1pdf_1surface_1set_1size_FUNC, + cairo_1pdf_1surface_1create_FUNC, cairo_1pop_1group_1to_1source_FUNC, cairo_1ps_1surface_1set_1size_FUNC, cairo_1push_1group_FUNC, diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/org/eclipse/swt/internal/cairo/Cairo.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/org/eclipse/swt/internal/cairo/Cairo.java index 19ee94d0dec..bacc4d50b1d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/org/eclipse/swt/internal/cairo/Cairo.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/org/eclipse/swt/internal/cairo/Cairo.java @@ -418,4 +418,13 @@ public class Cairo extends Platform { */ public static final native void memmove(double[] dest, long src, long size); +/** Surface type constant for SVG */ +public static final int CAIRO_SURFACE_TYPE_SVG = 4; + +/** + * @method flags=dynamic + * @param filename cast=(const char *) + */ +public static final native long cairo_pdf_surface_create(byte[] filename, double width_in_points, double height_in_points); + } diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c index 0dd0bd6dd11..552151d3ab0 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c @@ -1596,6 +1596,52 @@ JNIEXPORT void JNICALL OS_NATIVE(CGImageRelease) } #endif +#ifndef NO_CGPDFContextCreateWithURL +JNIEXPORT jlong JNICALL OS_NATIVE(CGPDFContextCreateWithURL) + (JNIEnv *env, jclass that, jlong arg0, jobject arg1, jlong arg2) +{ + CGRect _arg1, *lparg1=NULL; + jlong rc = 0; + OS_NATIVE_ENTER(env, that, CGPDFContextCreateWithURL_FUNC); + if (arg1) if ((lparg1 = getCGRectFields(env, arg1, &_arg1)) == NULL) goto fail; + rc = (jlong)CGPDFContextCreateWithURL((CFURLRef)arg0, lparg1, (CFDictionaryRef)arg2); +fail: + if (arg1 && lparg1) setCGRectFields(env, arg1, lparg1); + OS_NATIVE_EXIT(env, that, CGPDFContextCreateWithURL_FUNC); + return rc; +} +#endif + +#ifndef NO_CGPDFContextBeginPage +JNIEXPORT void JNICALL OS_NATIVE(CGPDFContextBeginPage) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1) +{ + OS_NATIVE_ENTER(env, that, CGPDFContextBeginPage_FUNC); + CGPDFContextBeginPage((CGContextRef)arg0, (CFDictionaryRef)arg1); + OS_NATIVE_EXIT(env, that, CGPDFContextBeginPage_FUNC); +} +#endif + +#ifndef NO_CGPDFContextEndPage +JNIEXPORT void JNICALL OS_NATIVE(CGPDFContextEndPage) + (JNIEnv *env, jclass that, jlong arg0) +{ + OS_NATIVE_ENTER(env, that, CGPDFContextEndPage_FUNC); + CGPDFContextEndPage((CGContextRef)arg0); + OS_NATIVE_EXIT(env, that, CGPDFContextEndPage_FUNC); +} +#endif + +#ifndef NO_CGPDFContextClose +JNIEXPORT void JNICALL OS_NATIVE(CGPDFContextClose) + (JNIEnv *env, jclass that, jlong arg0) +{ + OS_NATIVE_ENTER(env, that, CGPDFContextClose_FUNC); + CGPDFContextClose((CGContextRef)arg0); + OS_NATIVE_EXIT(env, that, CGPDFContextClose_FUNC); +} +#endif + #ifndef NO_CGPathAddCurveToPoint JNIEXPORT void JNICALL OS_NATIVE(CGPathAddCurveToPoint) (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jdouble arg2, jdouble arg3, jdouble arg4, jdouble arg5, jdouble arg6, jdouble arg7) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h index 3391ecb8cd1..8fec5b090ea 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h @@ -117,6 +117,10 @@ typedef enum { CGImageGetHeight_FUNC, CGImageGetWidth_FUNC, CGImageRelease_FUNC, + CGPDFContextCreateWithURL_FUNC, + CGPDFContextBeginPage_FUNC, + CGPDFContextEndPage_FUNC, + CGPDFContextClose_FUNC, CGPathAddCurveToPoint_FUNC, CGPathAddLineToPoint_FUNC, CGPathApply_FUNC, diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java index e125e46194e..d5c87d68bf7 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java @@ -3198,6 +3198,25 @@ public static Selector getSelector (long value) { * @param image cast=(CGImageRef) */ public static final native void CGImageRelease(long image); +/** + * @param url cast=(CFURLRef) + * @param mediaBox flags=struct + * @param auxiliaryInfo cast=(CFDictionaryRef) + */ +public static final native long CGPDFContextCreateWithURL(long url, CGRect mediaBox, long auxiliaryInfo); +/** + * @param context cast=(CGContextRef) + * @param pageInfo cast=(CFDictionaryRef) + */ +public static final native void CGPDFContextBeginPage(long context, long pageInfo); +/** + * @param context cast=(CGContextRef) + */ +public static final native void CGPDFContextEndPage(long context); +/** + * @param context cast=(CGContextRef) + */ +public static final native void CGPDFContextClose(long context); /** * @param path cast=(CGMutablePathRef) * @param m cast=(CGAffineTransform*) diff --git a/bundles/org.eclipse.swt/Eclipse SWT Printing/cocoa/org/eclipse/swt/printing/PDFDocument.java b/bundles/org.eclipse.swt/Eclipse SWT Printing/cocoa/org/eclipse/swt/printing/PDFDocument.java new file mode 100644 index 00000000000..3a14d1294f1 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Printing/cocoa/org/eclipse/swt/printing/PDFDocument.java @@ -0,0 +1,389 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse Platform Contributors and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eclipse Platform Contributors - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.printing; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.cocoa.*; + +/** + * Instances of this class are used to create PDF documents. + * Applications create a GC on a PDFDocument using new GC(pdfDocument) + * and then draw on the GC using the usual graphics calls. + *

+ * A PDFDocument object may be constructed by providing + * a filename and the page dimensions. After drawing is complete, + * the document must be disposed to finalize the PDF file. + *

+ * Application code must explicitly invoke the PDFDocument.dispose() + * method to release the operating system resources managed by each instance + * when those instances are no longer required. + *

+ *

+ * The following example demonstrates how to use PDFDocument: + *

+ *
+ *    PDFDocument pdf = new PDFDocument("output.pdf", 612, 792); // Letter size in points
+ *    GC gc = new GC(pdf);
+ *    gc.drawText("Hello, PDF!", 100, 100);
+ *    gc.dispose();
+ *    pdf.dispose();
+ * 
+ * + * @see GC + * @since 3.131 + */ +public class PDFDocument implements Drawable { + Device device; + long pdfContext; + NSGraphicsContext graphicsContext; + boolean isGCCreated = false; + boolean disposed = false; + boolean pageStarted = false; + + /** + * Width of the page in points (1/72 inch) + */ + double widthInPoints; + + /** + * Height of the page in points (1/72 inch) + */ + double heightInPoints; + + /** + * Constructs a new PDFDocument with the specified filename and page dimensions. + *

+ * You must dispose the PDFDocument when it is no longer required. + *

+ * + * @param filename the path to the PDF file to create + * @param widthInPoints the width of each page in points (1/72 inch) + * @param heightInPoints the height of each page in points (1/72 inch) + * + * @exception IllegalArgumentException + * @exception SWTError + * + * @see #dispose() + */ + public PDFDocument(String filename, double widthInPoints, double heightInPoints) { + this(null, filename, widthInPoints, heightInPoints); + } + + /** + * Constructs a new PDFDocument with the specified filename and page dimensions, + * associated with the given device. + *

+ * You must dispose the PDFDocument when it is no longer required. + *

+ * + * @param device the device to associate with this PDFDocument + * @param filename the path to the PDF file to create + * @param widthInPoints the width of each page in points (1/72 inch) + * @param heightInPoints the height of each page in points (1/72 inch) + * + * @exception IllegalArgumentException + * @exception SWTError + * + * @see #dispose() + */ + public PDFDocument(Device device, String filename, double widthInPoints, double heightInPoints) { + if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + this.widthInPoints = widthInPoints; + this.heightInPoints = heightInPoints; + + // Get device from the current display if not provided + if (device == null) { + try { + this.device = org.eclipse.swt.widgets.Display.getDefault(); + } catch (Exception e) { + this.device = null; + } + } else { + this.device = device; + } + + // Create CFURL from the filename + NSString path = NSString.stringWith(filename); + NSURL fileURL = NSURL.fileURLWithPath(path); + + // Create the PDF context with the media box + CGRect mediaBox = createMediaBox(); + + // Use CGPDFContextCreateWithURL + pdfContext = OS.CGPDFContextCreateWithURL(fileURL.id, mediaBox, 0); + if (pdfContext == 0) SWT.error(SWT.ERROR_NO_HANDLES); + + // Create an NSGraphicsContext from the CGContext + graphicsContext = NSGraphicsContext.graphicsContextWithGraphicsPort(pdfContext, true); + if (graphicsContext == null) { + OS.CGContextRelease(pdfContext); + pdfContext = 0; + SWT.error(SWT.ERROR_NO_HANDLES); + } + graphicsContext.retain(); + } finally { + if (pool != null) pool.release(); + } + } + + /** + * Creates a CGRect for the current page dimensions + */ + private CGRect createMediaBox() { + CGRect mediaBox = new CGRect(); + mediaBox.origin.x = 0; + mediaBox.origin.y = 0; + mediaBox.size.width = widthInPoints; + mediaBox.size.height = heightInPoints; + return mediaBox; + } + + /** + * Ensures the first page has been started + */ + private void ensurePageStarted() { + if (!pageStarted) { + OS.CGPDFContextBeginPage(pdfContext, 0); + pageStarted = true; + } + } + + /** + * Starts a new page in the PDF document. + *

+ * This method should be called after completing the content of one page + * and before starting to draw on the next page. The new page will have + * the same dimensions as the initial page. + *

+ * + * @exception SWTException + */ + public void newPage() { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + if (pageStarted) { + OS.CGPDFContextEndPage(pdfContext); + } + OS.CGPDFContextBeginPage(pdfContext, 0); + pageStarted = true; + } finally { + if (pool != null) pool.release(); + } + } + + /** + * Starts a new page in the PDF document with the specified dimensions. + *

+ * This method should be called after completing the content of one page + * and before starting to draw on the next page. + *

+ * + * @param widthInPoints the width of the new page in points (1/72 inch) + * @param heightInPoints the height of the new page in points (1/72 inch) + * + * @exception IllegalArgumentException + * @exception SWTException + */ + public void newPage(double widthInPoints, double heightInPoints) { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + this.widthInPoints = widthInPoints; + this.heightInPoints = heightInPoints; + newPage(); + } + + /** + * Returns the width of the current page in points. + * + * @return the width in points (1/72 inch) + * + * @exception SWTException + */ + public double getWidth() { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + return widthInPoints; + } + + /** + * Returns the height of the current page in points. + * + * @return the height in points (1/72 inch) + * + * @exception SWTException + */ + public double getHeight() { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + return heightInPoints; + } + + /** + * Invokes platform specific functionality to allocate a new GC handle. + *

+ * IMPORTANT: This method is not part of the public + * API for PDFDocument. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param data the platform specific GC data + * @return the platform specific GC handle + * + * @noreference This method is not intended to be referenced by clients. + */ + @Override + public long internal_new_GC(GCData data) { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + if (isGCCreated) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + ensurePageStarted(); + + // Set up current graphics context + NSGraphicsContext.static_saveGraphicsState(); + NSGraphicsContext.setCurrentContext(graphicsContext); + + if (data != null) { + int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + if ((data.style & mask) == 0) { + data.style |= SWT.LEFT_TO_RIGHT; + } + data.device = device; + data.flippedContext = graphicsContext; + data.restoreContext = true; + NSSize size = new NSSize(); + size.width = widthInPoints; + size.height = heightInPoints; + data.size = size; + if (device != null) { + data.background = device.getSystemColor(SWT.COLOR_WHITE).handle; + data.foreground = device.getSystemColor(SWT.COLOR_BLACK).handle; + data.font = device.getSystemFont(); + } + } + isGCCreated = true; + return graphicsContext.id; + } finally { + if (pool != null) pool.release(); + } + } + + /** + * Invokes platform specific functionality to dispose a GC handle. + *

+ * IMPORTANT: This method is not part of the public + * API for PDFDocument. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param hDC the platform specific GC handle + * @param data the platform specific GC data + * + * @noreference This method is not intended to be referenced by clients. + */ + @Override + public void internal_dispose_GC(long hDC, GCData data) { + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + NSGraphicsContext.static_restoreGraphicsState(); + if (data != null) isGCCreated = false; + } finally { + if (pool != null) pool.release(); + } + } + + /** + * @noreference This method is not intended to be referenced by clients. + */ + @Override + public boolean isAutoScalable() { + return false; + } + + /** + * Returns true if the PDFDocument has been disposed, + * and false otherwise. + * + * @return true when the PDFDocument is disposed and false otherwise + */ + public boolean isDisposed() { + return disposed; + } + + /** + * Disposes of the operating system resources associated with + * the PDFDocument. Applications must dispose of all PDFDocuments + * that they allocate. + *

+ * This method finalizes the PDF file and writes it to disk. + *

+ */ + public void dispose() { + if (disposed) return; + disposed = true; + + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + if (pdfContext != 0) { + if (pageStarted) { + OS.CGPDFContextEndPage(pdfContext); + } + OS.CGPDFContextClose(pdfContext); + OS.CGContextRelease(pdfContext); + pdfContext = 0; + } + if (graphicsContext != null) { + graphicsContext.release(); + graphicsContext = null; + } + } finally { + if (pool != null) pool.release(); + } + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Printing/gtk/org/eclipse/swt/printing/PDFDocument.java b/bundles/org.eclipse.swt/Eclipse SWT Printing/gtk/org/eclipse/swt/printing/PDFDocument.java new file mode 100644 index 00000000000..704d29880a7 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Printing/gtk/org/eclipse/swt/printing/PDFDocument.java @@ -0,0 +1,336 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse Platform Contributors and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eclipse Platform Contributors - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.printing; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.*; +import org.eclipse.swt.internal.cairo.*; + +/** + * Instances of this class are used to create PDF documents. + * Applications create a GC on a PDFDocument using new GC(pdfDocument) + * and then draw on the GC using the usual graphics calls. + *

+ * A PDFDocument object may be constructed by providing + * a filename and the page dimensions. After drawing is complete, + * the document must be disposed to finalize the PDF file. + *

+ * Application code must explicitly invoke the PDFDocument.dispose() + * method to release the operating system resources managed by each instance + * when those instances are no longer required. + *

+ *

+ * The following example demonstrates how to use PDFDocument: + *

+ *
+ *    PDFDocument pdf = new PDFDocument("output.pdf", 612, 792); // Letter size in points
+ *    GC gc = new GC(pdf);
+ *    gc.drawText("Hello, PDF!", 100, 100);
+ *    gc.dispose();
+ *    pdf.dispose();
+ * 
+ * + * @see GC + * @since 3.131 + */ +public class PDFDocument implements Drawable { + Device device; + long surface; + long cairo; + boolean isGCCreated = false; + boolean disposed = false; + + /** + * Width of the page in points (1/72 inch) + */ + double widthInPoints; + + /** + * Height of the page in points (1/72 inch) + */ + double heightInPoints; + + /** + * Constructs a new PDFDocument with the specified filename and page dimensions. + *

+ * You must dispose the PDFDocument when it is no longer required. + *

+ * + * @param filename the path to the PDF file to create + * @param widthInPoints the width of each page in points (1/72 inch) + * @param heightInPoints the height of each page in points (1/72 inch) + * + * @exception IllegalArgumentException + * @exception SWTError + * + * @see #dispose() + */ + public PDFDocument(String filename, double widthInPoints, double heightInPoints) { + if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + this.widthInPoints = widthInPoints; + this.heightInPoints = heightInPoints; + + byte[] filenameBytes = Converter.wcsToMbcs(filename, true); + surface = Cairo.cairo_pdf_surface_create(filenameBytes, widthInPoints, heightInPoints); + if (surface == 0) SWT.error(SWT.ERROR_NO_HANDLES); + + cairo = Cairo.cairo_create(surface); + if (cairo == 0) { + Cairo.cairo_surface_destroy(surface); + surface = 0; + SWT.error(SWT.ERROR_NO_HANDLES); + } + + // Get device from the current display or create a temporary one + try { + device = org.eclipse.swt.widgets.Display.getDefault(); + } catch (Exception e) { + device = null; + } + } + + /** + * Constructs a new PDFDocument with the specified filename and page dimensions, + * associated with the given device. + *

+ * You must dispose the PDFDocument when it is no longer required. + *

+ * + * @param device the device to associate with this PDFDocument + * @param filename the path to the PDF file to create + * @param widthInPoints the width of each page in points (1/72 inch) + * @param heightInPoints the height of each page in points (1/72 inch) + * + * @exception IllegalArgumentException + * @exception SWTError + * + * @see #dispose() + */ + public PDFDocument(Device device, String filename, double widthInPoints, double heightInPoints) { + if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + this.device = device; + this.widthInPoints = widthInPoints; + this.heightInPoints = heightInPoints; + + byte[] filenameBytes = Converter.wcsToMbcs(filename, true); + surface = Cairo.cairo_pdf_surface_create(filenameBytes, widthInPoints, heightInPoints); + if (surface == 0) SWT.error(SWT.ERROR_NO_HANDLES); + + cairo = Cairo.cairo_create(surface); + if (cairo == 0) { + Cairo.cairo_surface_destroy(surface); + surface = 0; + SWT.error(SWT.ERROR_NO_HANDLES); + } + } + + /** + * Starts a new page in the PDF document. + *

+ * This method should be called after completing the content of one page + * and before starting to draw on the next page. The new page will have + * the same dimensions as the initial page. + *

+ * + * @exception SWTException + */ + public void newPage() { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + Cairo.cairo_show_page(cairo); + } + + /** + * Starts a new page in the PDF document with the specified dimensions. + *

+ * This method should be called after completing the content of one page + * and before starting to draw on the next page. + *

+ * + * @param widthInPoints the width of the new page in points (1/72 inch) + * @param heightInPoints the height of the new page in points (1/72 inch) + * + * @exception IllegalArgumentException + * @exception SWTException + */ + public void newPage(double widthInPoints, double heightInPoints) { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + Cairo.cairo_show_page(cairo); + Cairo.cairo_pdf_surface_set_size(surface, widthInPoints, heightInPoints); + this.widthInPoints = widthInPoints; + this.heightInPoints = heightInPoints; + } + + /** + * Returns the width of the current page in points. + * + * @return the width in points (1/72 inch) + * + * @exception SWTException + */ + public double getWidth() { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + return widthInPoints; + } + + /** + * Returns the height of the current page in points. + * + * @return the height in points (1/72 inch) + * + * @exception SWTException + */ + public double getHeight() { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + return heightInPoints; + } + + /** + * Invokes platform specific functionality to allocate a new GC handle. + *

+ * IMPORTANT: This method is not part of the public + * API for PDFDocument. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param data the platform specific GC data + * @return the platform specific GC handle + * + * @noreference This method is not intended to be referenced by clients. + */ + @Override + public long internal_new_GC(GCData data) { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + if (isGCCreated) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + if (data != null) { + int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + if ((data.style & mask) == 0) { + data.style |= SWT.LEFT_TO_RIGHT; + } + data.device = device; + data.cairo = cairo; + data.width = (int) widthInPoints; + data.height = (int) heightInPoints; + if (device != null) { + data.foregroundRGBA = device.getSystemColor(SWT.COLOR_BLACK).handle; + data.backgroundRGBA = device.getSystemColor(SWT.COLOR_WHITE).handle; + data.font = device.getSystemFont(); + } else { + // Fallback: create default colors manually using GdkRGBA values + data.foregroundRGBA = new org.eclipse.swt.internal.gtk.GdkRGBA(); + data.foregroundRGBA.red = 0; + data.foregroundRGBA.green = 0; + data.foregroundRGBA.blue = 0; + data.foregroundRGBA.alpha = 1; + data.backgroundRGBA = new org.eclipse.swt.internal.gtk.GdkRGBA(); + data.backgroundRGBA.red = 1; + data.backgroundRGBA.green = 1; + data.backgroundRGBA.blue = 1; + data.backgroundRGBA.alpha = 1; + } + } + isGCCreated = true; + return cairo; + } + + /** + * Invokes platform specific functionality to dispose a GC handle. + *

+ * IMPORTANT: This method is not part of the public + * API for PDFDocument. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param hDC the platform specific GC handle + * @param data the platform specific GC data + * + * @noreference This method is not intended to be referenced by clients. + */ + @Override + public void internal_dispose_GC(long hDC, GCData data) { + if (data != null) isGCCreated = false; + } + + /** + * @noreference This method is not intended to be referenced by clients. + */ + @Override + public boolean isAutoScalable() { + return false; + } + + /** + * Returns true if the PDFDocument has been disposed, + * and false otherwise. + * + * @return true when the PDFDocument is disposed and false otherwise + */ + public boolean isDisposed() { + return disposed; + } + + /** + * Disposes of the operating system resources associated with + * the PDFDocument. Applications must dispose of all PDFDocuments + * that they allocate. + *

+ * This method finalizes the PDF file and writes it to disk. + *

+ */ + public void dispose() { + if (disposed) return; + disposed = true; + + if (cairo != 0) { + Cairo.cairo_destroy(cairo); + cairo = 0; + } + if (surface != 0) { + Cairo.cairo_surface_finish(surface); + Cairo.cairo_surface_destroy(surface); + surface = 0; + } + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Printing/win32/org/eclipse/swt/printing/PDFDocument.java b/bundles/org.eclipse.swt/Eclipse SWT Printing/win32/org/eclipse/swt/printing/PDFDocument.java new file mode 100644 index 00000000000..0f3e2a1a0c9 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Printing/win32/org/eclipse/swt/printing/PDFDocument.java @@ -0,0 +1,390 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse Platform Contributors and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eclipse Platform Contributors - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.printing; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.win32.*; + +/** + * Instances of this class are used to create PDF documents. + * Applications create a GC on a PDFDocument using new GC(pdfDocument) + * and then draw on the GC using the usual graphics calls. + *

+ * A PDFDocument object may be constructed by providing + * a filename and the page dimensions. After drawing is complete, + * the document must be disposed to finalize the PDF file. + *

+ * Application code must explicitly invoke the PDFDocument.dispose() + * method to release the operating system resources managed by each instance + * when those instances are no longer required. + *

+ *

+ * Note: On Windows, this class uses the built-in "Microsoft Print to PDF" + * printer which is available on Windows 10 and later. + *

+ *

+ * The following example demonstrates how to use PDFDocument: + *

+ *
+ *    PDFDocument pdf = new PDFDocument("output.pdf", 612, 792); // Letter size in points
+ *    GC gc = new GC(pdf);
+ *    gc.drawText("Hello, PDF!", 100, 100);
+ *    gc.dispose();
+ *    pdf.dispose();
+ * 
+ * + * @see GC + * @since 3.131 + */ +public class PDFDocument implements Drawable { + Device device; + long handle; + boolean isGCCreated = false; + boolean disposed = false; + boolean jobStarted = false; + boolean pageStarted = false; + String filename; + + /** + * Width of the page in points (1/72 inch) + */ + double widthInPoints; + + /** + * Height of the page in points (1/72 inch) + */ + double heightInPoints; + + /** The name of the Microsoft Print to PDF printer */ + private static final String PDF_PRINTER_NAME = "Microsoft Print to PDF"; + + /** + * Constructs a new PDFDocument with the specified filename and page dimensions. + *

+ * You must dispose the PDFDocument when it is no longer required. + *

+ * + * @param filename the path to the PDF file to create + * @param widthInPoints the width of each page in points (1/72 inch) + * @param heightInPoints the height of each page in points (1/72 inch) + * + * @exception IllegalArgumentException + * @exception SWTError + * + * @see #dispose() + */ + public PDFDocument(String filename, double widthInPoints, double heightInPoints) { + this(null, filename, widthInPoints, heightInPoints); + } + + /** + * Constructs a new PDFDocument with the specified filename and page dimensions, + * associated with the given device. + *

+ * You must dispose the PDFDocument when it is no longer required. + *

+ * + * @param device the device to associate with this PDFDocument + * @param filename the path to the PDF file to create + * @param widthInPoints the width of each page in points (1/72 inch) + * @param heightInPoints the height of each page in points (1/72 inch) + * + * @exception IllegalArgumentException + * @exception SWTError + * + * @see #dispose() + */ + public PDFDocument(Device device, String filename, double widthInPoints, double heightInPoints) { + if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + this.filename = filename; + this.widthInPoints = widthInPoints; + this.heightInPoints = heightInPoints; + + // Get device from the current display if not provided + if (device == null) { + try { + this.device = org.eclipse.swt.widgets.Display.getDefault(); + } catch (SWTException e) { + this.device = null; + } + } else { + this.device = device; + } + + // Create printer DC for "Microsoft Print to PDF" + TCHAR driver = new TCHAR(0, "WINSPOOL", true); + TCHAR deviceName = new TCHAR(0, PDF_PRINTER_NAME, true); + + // Get printer settings + long[] hPrinter = new long[1]; + if (OS.OpenPrinter(deviceName, hPrinter, 0)) { + int dwNeeded = OS.DocumentProperties(0, hPrinter[0], deviceName, 0, 0, 0); + if (dwNeeded >= 0) { + long hHeap = OS.GetProcessHeap(); + long lpInitData = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, dwNeeded); + if (lpInitData != 0) { + int rc = OS.DocumentProperties(0, hPrinter[0], deviceName, lpInitData, 0, OS.DM_OUT_BUFFER); + if (rc == OS.IDOK) { + handle = OS.CreateDC(driver, deviceName, 0, lpInitData); + } + OS.HeapFree(hHeap, 0, lpInitData); + } + } + OS.ClosePrinter(hPrinter[0]); + } + + if (handle == 0) { + SWT.error(SWT.ERROR_NO_HANDLES); + } + } + + /** + * Ensures the print job has been started + */ + private void ensureJobStarted() { + if (!jobStarted) { + DOCINFO di = new DOCINFO(); + di.cbSize = DOCINFO.sizeof; + long hHeap = OS.GetProcessHeap(); + + // Set output filename + TCHAR buffer = new TCHAR(0, filename, true); + int byteCount = buffer.length() * TCHAR.sizeof; + long lpszOutput = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount); + OS.MoveMemory(lpszOutput, buffer, byteCount); + di.lpszOutput = lpszOutput; + + // Set document name + TCHAR docName = new TCHAR(0, "SWT PDF Document", true); + int docByteCount = docName.length() * TCHAR.sizeof; + long lpszDocName = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, docByteCount); + OS.MoveMemory(lpszDocName, docName, docByteCount); + di.lpszDocName = lpszDocName; + + int rc = OS.StartDoc(handle, di); + + OS.HeapFree(hHeap, 0, lpszOutput); + OS.HeapFree(hHeap, 0, lpszDocName); + + if (rc <= 0) { + SWT.error(SWT.ERROR_NO_HANDLES); + } + jobStarted = true; + } + } + + /** + * Ensures the current page has been started + */ + private void ensurePageStarted() { + ensureJobStarted(); + if (!pageStarted) { + OS.StartPage(handle); + pageStarted = true; + } + } + + /** + * Starts a new page in the PDF document. + *

+ * This method should be called after completing the content of one page + * and before starting to draw on the next page. The new page will have + * the same dimensions as the initial page. + *

+ * + * @exception SWTException + */ + public void newPage() { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + if (pageStarted) { + OS.EndPage(handle); + pageStarted = false; + } + ensurePageStarted(); + } + + /** + * Starts a new page in the PDF document with the specified dimensions. + *

+ * This method should be called after completing the content of one page + * and before starting to draw on the next page. + *

+ *

+ * Note: On Windows, changing page dimensions after the document + * has been started may not be fully supported by all printer drivers. + *

+ * + * @param widthInPoints the width of the new page in points (1/72 inch) + * @param heightInPoints the height of the new page in points (1/72 inch) + * + * @exception IllegalArgumentException + * @exception SWTException + */ + public void newPage(double widthInPoints, double heightInPoints) { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + this.widthInPoints = widthInPoints; + this.heightInPoints = heightInPoints; + newPage(); + } + + /** + * Returns the width of the current page in points. + * + * @return the width in points (1/72 inch) + * + * @exception SWTException + */ + public double getWidth() { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + return widthInPoints; + } + + /** + * Returns the height of the current page in points. + * + * @return the height in points (1/72 inch) + * + * @exception SWTException + */ + public double getHeight() { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + return heightInPoints; + } + + /** + * Invokes platform specific functionality to allocate a new GC handle. + *

+ * IMPORTANT: This method is not part of the public + * API for PDFDocument. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param data the platform specific GC data + * @return the platform specific GC handle + * + * @noreference This method is not intended to be referenced by clients. + */ + @Override + public long internal_new_GC(GCData data) { + if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED); + if (isGCCreated) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + ensurePageStarted(); + + if (data != null) { + int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + if ((data.style & mask) != 0) { + data.layout = (data.style & SWT.RIGHT_TO_LEFT) != 0 ? OS.LAYOUT_RTL : 0; + } else { + data.style |= SWT.LEFT_TO_RIGHT; + } + data.device = device; + data.nativeZoom = 100; + if (device != null) { + data.font = device.getSystemFont(); + } + } + isGCCreated = true; + return handle; + } + + /** + * Invokes platform specific functionality to dispose a GC handle. + *

+ * IMPORTANT: This method is not part of the public + * API for PDFDocument. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param hDC the platform specific GC handle + * @param data the platform specific GC data + * + * @noreference This method is not intended to be referenced by clients. + */ + @Override + public void internal_dispose_GC(long hDC, GCData data) { + if (data != null) isGCCreated = false; + } + + /** + * @noreference This method is not intended to be referenced by clients. + */ + @Override + public boolean isAutoScalable() { + return false; + } + + /** + * Returns true if the PDFDocument has been disposed, + * and false otherwise. + * + * @return true when the PDFDocument is disposed and false otherwise + */ + public boolean isDisposed() { + return disposed; + } + + /** + * Disposes of the operating system resources associated with + * the PDFDocument. Applications must dispose of all PDFDocuments + * that they allocate. + *

+ * This method finalizes the PDF file and writes it to disk. + *

+ */ + public void dispose() { + if (disposed) return; + disposed = true; + + if (handle != 0) { + if (pageStarted) { + OS.EndPage(handle); + } + if (jobStarted) { + OS.EndDoc(handle); + } + OS.DeleteDC(handle); + handle = 0; + } + } +} diff --git a/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/AllGTKTests.java b/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/AllGTKTests.java index 7cb58ea5ebf..1b1e3115362 100644 --- a/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/AllGTKTests.java +++ b/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/AllGTKTests.java @@ -19,7 +19,8 @@ @Suite @SelectClasses({ - Test_GtkConverter.class + Test_GtkConverter.class, + Test_PDFDocument.class }) public class AllGTKTests { diff --git a/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/Test_PDFDocument.java b/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/Test_PDFDocument.java new file mode 100644 index 00000000000..9e73073f849 --- /dev/null +++ b/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/Test_PDFDocument.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse Platform Contributors and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eclipse Platform Contributors - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.tests.gtk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; + +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.printing.PDFDocument; +import org.eclipse.swt.widgets.Display; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Automated Test Suite for class org.eclipse.swt.printing.PDFDocument + * + * @see org.eclipse.swt.printing.PDFDocument + */ +public class Test_PDFDocument { + + private static Display display; + + @TempDir + File tempDir; + + private PDFDocument pdfDocument; + + @BeforeAll + public static void setUpBeforeClass() { + display = Display.getDefault(); + } + + @AfterAll + public static void tearDownAfterClass() { + if (display != null && !display.isDisposed()) { + display.dispose(); + } + } + + @AfterEach + public void tearDown() { + if (pdfDocument != null && !pdfDocument.isDisposed()) { + pdfDocument.dispose(); + } + } + + @Test + public void test_Constructor_NullFilename() { + assertThrows(IllegalArgumentException.class, () -> new PDFDocument(null, 612, 792)); + } + + @Test + public void test_Constructor_InvalidDimensions() { + String filename = new File(tempDir, "test.pdf").getAbsolutePath(); + assertThrows(IllegalArgumentException.class, () -> new PDFDocument(filename, 0, 792)); + assertThrows(IllegalArgumentException.class, () -> new PDFDocument(filename, 612, 0)); + assertThrows(IllegalArgumentException.class, () -> new PDFDocument(filename, -1, 792)); + assertThrows(IllegalArgumentException.class, () -> new PDFDocument(filename, 612, -1)); + } + + @Test + public void test_Constructor_ValidParameters() { + String filename = new File(tempDir, "test_valid.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(filename, 612, 792); + assertNotNull(pdfDocument); + assertFalse(pdfDocument.isDisposed()); + } + + @Test + public void test_ConstructorWithDevice() { + String filename = new File(tempDir, "test_device.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + assertNotNull(pdfDocument); + assertFalse(pdfDocument.isDisposed()); + } + + @Test + public void test_getWidth() { + String filename = new File(tempDir, "test_width.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + assertEquals(612.0, pdfDocument.getWidth(), 0.001); + } + + @Test + public void test_getHeight() { + String filename = new File(tempDir, "test_height.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + assertEquals(792.0, pdfDocument.getHeight(), 0.001); + } + + @Test + public void test_isAutoScalable() { + String filename = new File(tempDir, "test_autoscale.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + assertFalse(pdfDocument.isAutoScalable()); + } + + @Test + public void test_dispose() { + String filename = new File(tempDir, "test_dispose.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + assertFalse(pdfDocument.isDisposed()); + pdfDocument.dispose(); + assertTrue(pdfDocument.isDisposed()); + } + + @Test + public void test_createGC() { + String filename = new File(tempDir, "test_gc.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + GC gc = new GC(pdfDocument); + assertNotNull(gc); + gc.dispose(); + } + + @Test + public void test_drawOnGC() { + String filename = new File(tempDir, "test_draw.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + GC gc = new GC(pdfDocument); + + // Test drawing operations - should not throw exceptions + gc.drawRectangle(10, 10, 100, 80); + gc.drawText("Hello, PDF!", 50, 50); + gc.drawLine(0, 0, 100, 100); + + gc.dispose(); + pdfDocument.dispose(); + + // Verify file was created + File file = new File(filename); + assertTrue(file.exists(), "PDF file should have been created"); + assertTrue(file.length() > 0, "PDF file should not be empty"); + } + + @Test + public void test_newPage() { + String filename = new File(tempDir, "test_newpage.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + GC gc = new GC(pdfDocument); + + gc.drawText("Page 1", 50, 50); + pdfDocument.newPage(); + gc.drawText("Page 2", 50, 50); + + gc.dispose(); + pdfDocument.dispose(); + + // Verify file was created + File file = new File(filename); + assertTrue(file.exists(), "PDF file should have been created"); + } + + @Test + public void test_newPageWithDifferentSize() { + String filename = new File(tempDir, "test_newpage_size.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); // Letter size + + assertEquals(612.0, pdfDocument.getWidth(), 0.001); + assertEquals(792.0, pdfDocument.getHeight(), 0.001); + + GC gc = new GC(pdfDocument); + gc.drawText("Page 1 - Letter", 50, 50); + + pdfDocument.newPage(842, 595); // A4 Landscape + + assertEquals(842.0, pdfDocument.getWidth(), 0.001); + assertEquals(595.0, pdfDocument.getHeight(), 0.001); + + gc.drawText("Page 2 - A4 Landscape", 50, 50); + + gc.dispose(); + pdfDocument.dispose(); + } + + @Test + public void test_operationsAfterDispose() { + String filename = new File(tempDir, "test_disposed.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + pdfDocument.dispose(); + + assertThrows(Exception.class, () -> pdfDocument.getWidth()); + assertThrows(Exception.class, () -> pdfDocument.getHeight()); + assertThrows(Exception.class, () -> pdfDocument.newPage()); + } +}