Skip to content

Conversation

@laeubi
Copy link
Contributor

@laeubi laeubi commented Dec 14, 2025

Currently if one wants to create a PDF file it requires external
libraries and as SWT does not allows an abstraction like Grahics2D in
AWT one can not export real content of SWT components (e.g. Canvas)
except exporting as an raster image or using some hacks.

This now introduce a new PDFDocument to enable direct
PDF generation from SWT widgets via Control.print(GC). This allows
applications to export widget content to PDF files using the standard
GC drawing API as well as even creating completely customized documents.

Platform Support

Platform Implementation
GTK (Linux) Cairo PDF surface
Cocoa (macOS) Core Graphics CGPDFContext
Win32 (Windows) Microsoft Print to PDF

I currently have only tested this on Linux GTK, I currently have no way to test it with Cocoa (macOS) it would be great if someone with a mac can take a look. I'll try to take a look on windows soon but also here some help would be appreciated!

The Snippet388 can be used to produce such PDF after compiling the natives with the "Build-SWT-native-binaries-for-running-platform" run configuration.

GTK (Linux)

On GTK (Linux) the snippet looks like this:
grafik

And here is the resulting PDF document:

Win32 (Windows)

On Win32 (Windows) the snippet looks like this:

grafik

Due to the usage of the pdf printer that do not supports custom page size, we have the limitation to not getting perfect page sizes and we currently try to find the best matching one, the PDF document then looks like tihs:

As an interesting observaation I found that Shell#print is not implemented for windows (simply returns), I added an implementation for this as well, but this part is maybe better submited as an own PR.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 14, 2025

Test Results

  162 files   -  14    162 suites   - 14   28m 9s ⏱️ + 1m 10s
4 579 tests  -  92  4 560 ✅  -  89  17 💤  - 5  2 ❌ +2 
  370 runs   - 112    370 ✅  - 106   0 💤  - 6  0 ❌ ±0 

For more details on these failures, see this check.

Results for commit 433898e. ± Comparison against base commit e21a57a.

This pull request removes 94 and adds 2 tests. Note that renamed tests count towards both.
AllGTKTests Test_GtkConverter ‑ test_HeuristicASCII_dollarSign
AllGTKTests Test_GtkConverter ‑ test_HeuristicASCII_emptyString
AllGTKTests Test_GtkConverter ‑ test_HeuristicASCII_letterA
AllGTKTests Test_GtkConverter ‑ test_HeuristicASCII_letters
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16LE_null
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16_AsciiLetters
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16_Asciiletter
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16_LotsOfLetters
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16_letter
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16_letters
…
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_printing_PDFDocument ‑ test_createPDFDocumentMultiplePages
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_printing_PDFDocument ‑ test_createPDFDocumentWithHelloWorld

♻️ This comment has been updated with latest results.

@laeubi laeubi force-pushed the add-pdf-support branch 3 times, most recently from 060ff6c to d27eb11 Compare December 15, 2025 06:55
@Phillipus
Copy link
Contributor

I currently have no way to test it with Cocoa (macOS) it would be great if someone with a mac can take a look.

It seems I am that guy. :-)

java.lang.UnsatisfiedLinkError: 'long org.eclipse.swt.internal.cocoa.OS.CGPDFContextCreateWithURL(long, org.eclipse.swt.internal.cocoa.CGRect, long)'
	at org.eclipse.swt.internal.cocoa.OS.CGPDFContextCreateWithURL(Native Method)
	at org.eclipse.swt.printing.PDFDocument.<init>(PDFDocument.java:140)
	at org.eclipse.swt.printing.PDFDocument.<init>(PDFDocument.java:86)
	at org.eclipse.swt.snippets.Snippet388.lambda$1(Snippet388.java:214)
	at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:91)
	at org.eclipse.swt.widgets.Display.sendEvent(Display.java:4671)
	at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1657)
	at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1680)
	at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1665)
	at org.eclipse.swt.widgets.Widget.notifyListeners(Widget.java:1394)
	at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:4438)
	at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4014)
	at org.eclipse.swt.snippets.Snippet388.main(Snippet388.java:235)

Sorry I don't know what that means or how to fix it.

@laeubi
Copy link
Contributor Author

laeubi commented Dec 15, 2025

Sorry I don't know what that means or how to fix it.

Your first need to rebuild the natives there is a "Build-SWT-native-binaries-for-running-platform" run configuration in the repository for that purpose https://github.com/eclipse-platform/eclipse.platform.swt/blob/master/bundles/org.eclipse.swt/Build-SWT-native-binaries-for-running-platform.launch

this should then give you the required new bindings in the swt fragment.

@Phillipus
Copy link
Contributor

Phillipus commented Dec 15, 2025

I built the binaries but there is a native crash when running the Snippet. Here's the interesting part:

rocess:               java [31953]
Path:                  /Users/USER/*/java
Identifier:            java
Version:               21.0.9 (10)
Code Type:             ARM-64 (Native)
Parent Process:        eclipse [31247]
Responsible:           eclipse [31247]
User ID:               501

Date/Time:             2025-12-15 14:39:15.8479 +0000
OS Version:            macOS 15.7.3 (24G419)
Report Version:        12
Anonymous UUID:        2CF05B38-54EC-D8D2-CC9C-0F2843600541

Sleep/Wake UUID:       36704827-1DFE-4971-8B2E-EF3C2D753D2E

Time Awake Since Boot: 100000 seconds
Time Since Wake:       1233 seconds

System Integrity Protection: enabled

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BREAKPOINT (SIGTRAP)
Exception Codes:       0x0000000000000001, 0x0000000187dbd670

Termination Reason:    Namespace SIGNAL, Code 5 Trace/BPT trap: 5
Terminating Process:   exc handler [31953]

Application Specific Information:
Attempt to pop an empty graphics context stack.


Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   AppKit                        	       0x187dbd670 +[NSGraphicsContext _popGraphicsContext] + 228
1   AppKit                        	       0x187dbd574 +[NSGraphicsContext restoreGraphicsState] + 16
2   libswt-pi-cocoa-4972r1.jnilib 	       0x102fdcba8 Java_org_eclipse_swt_internal_cocoa_OS_objc_1msgSend__JJ + 44
3   ???                           	       0x11485a568 ???
4   ???                           	       0x114279690 ???
5   ???                           	       0x1142798a0 ???
6   ???                           	       0x11427a200 ???
7   ???                           	       0x1142798a0 ???
8   ???                           	       0x1142798a0 ???
9   ???                           	       0x1142798a0 ???
10  ???                           	       0x11427a200 ???
11  ???                           	       0x1142798a0 ???
12  ???                           	       0x1142798a0 ???
13  ???                           	       0x1142798a0 ???
14  ???                           	       0x1142798a0 ???
15  ???                           	       0x1142798a0 ???
16  ???                           	       0x1142798a0 ???
17  ???                           	       0x1142795e0 ???
18  ???                           	       0x1142795e0 ???
19  ???                           	       0x114274154 ???
20  libjvm.dylib                  	       0x1039fb35c JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*) + 984
21  libjvm.dylib                  	       0x103a704a4 jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, JavaThread*) + 368
22  libjvm.dylib                  	       0x103a73d14 jni_CallStaticVoidMethod + 264
23  libjli.dylib                  	       0x1021c6a70 JavaMain + 2320
24  libjli.dylib                  	       0x1021c9834 __JVMInit_block_invoke + 72
25  Foundation                    	       0x18542d0ac __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 24
26  Foundation                    	       0x18542cf74 -[NSBlockOperation main] + 96
27  Foundation                    	       0x18542cf0c __NSOPERATION_IS_INVOKING_MAIN__ + 16
28  Foundation                    	       0x18542c27c -[NSOperation start] + 640
29  Foundation                    	       0x185466f78 __NSThreadPerformPerform + 264
30  CoreFoundation                	       0x183e7aa64 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
31  CoreFoundation                	       0x183e7a9f8 __CFRunLoopDoSource0 + 172
32  CoreFoundation                	       0x183e7a764 __CFRunLoopDoSources0 + 232
33  CoreFoundation                	       0x183e793b8 __CFRunLoopRun + 840
34  CoreFoundation                	       0x183e789e8 CFRunLoopRunSpecific + 572
35  libjli.dylib                  	       0x1021c8ed4 CreateExecutionEnvironment + 404
36  libjli.dylib                  	       0x1021c4be0 JLI_Launch + 1152
37  java                          	       0x10217fba4 main + 404
38  dyld                          	       0x1839eeb98 start + 6076

@Phillipus
Copy link
Contributor

Phillipus commented Dec 15, 2025

The crash happens when calling gc.dispose() in the Snippet. If I comment out that line I get a blank PDF with the words "Exported to PDF..." upside down at the bottom. (The same also if I comment out shell.print(gc);)

The line in GC that triggers the crash is line 1012:

if (drawable != null) drawable.internal_dispose_GC(handle.id, data);

And if I comment out that line I still get the same PDF. So perhaps two things here - a crash and not drawing to the GC?

swt_graphics_demo.pdf

@laeubi
Copy link
Contributor Author

laeubi commented Dec 15, 2025

@Phillipus thanks for the testing, I'll see what I can do here! I suspect we have a similar problem as on widnows that shell.print is simply not implemented...

@Phillipus
Copy link
Contributor

Three things then on Mac:

  1. Crash when disposing the GC
  2. shell.print(gc); does nothing
  3. Inversion of image (see [macOS Sonoma] Control.setBackgroundImage - image is upside down and flipped #772 for something similar)

I copied and pasted all the GC drawing code into the main GC gc = new GC(pdf); and the result is attached:

swt_graphics_demo.pdf

@laeubi
Copy link
Contributor Author

laeubi commented Dec 15, 2025

@Phillipus many thanks at least it looks like it generally works 👍

@Phillipus
Copy link
Contributor

I just tested the latest PR. The crash on dispose() is fixed. :-)

@Phillipus
Copy link
Contributor

2. shell.print(gc); does nothing

I should have checked :-)

public boolean print (GC gc) {
	checkWidget ();
	if (gc == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (gc.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
	return false;
}

@Phillipus
Copy link
Contributor

Latest PR seems to be working. Still flipped image, though.

swt_graphics_demo.pdf

@laeubi
Copy link
Contributor Author

laeubi commented Dec 16, 2025

Latest PR seems to be working. Still flipped image, though.

swt_graphics_demo.pdf

Great thanks for the fast confirmation. What do you think about the flipping, is it a bug of macOs (and weh should maybe wait for a fix of apple) bug of SWT... or just "normal behavior" in wich case I would just try to apply a transformation here.

I read through the referenced bug but I'm not sure what would be the correct fix in this case... Maybe I need to handle isFlipped somewhere in the PDFocument?

@Phillipus
Copy link
Contributor

Phillipus commented Dec 16, 2025

What do you think about the flipping, is it a bug of macOs (and weh should maybe wait for a fix of apple) bug of SWT... or just "normal behavior" in wich case I would just try to apply a transformation here.

I read through the referenced bug but I'm not sure what would be the correct fix in this case... Maybe I need to handle isFlipped somewhere in the PDFocument?

Hard to say. In the case of that bug it was eventually resolved by Apple but there's no guarantee when/if/how long Apple resolves these things.

There may be something in the cocoa API that needs to be set to unflip the image. This works:

graphicsContext = NSGraphicsContext.graphicsContextWithGraphicsPort(pdfContext, false); // Set to false

swt_graphics_demo.pdf

@laeubi
Copy link
Contributor Author

laeubi commented Dec 16, 2025

This works

But now exchanges Top/Bottom controls and text is painted to the bottom :-D

I wonder if/how it looks like if one export a composite/shell to a PNG image ...

@Phillipus
Copy link
Contributor

Phillipus commented Dec 16, 2025

NSGraphicsContext.setCurrentContext(NSGraphicsContext.graphicsContextWithGraphicsPort has false set in Image class

NSGraphicsContext.setCurrentContext(NSGraphicsContext.graphicsContextWithGraphicsPort(ctx, false));

NSGraphicsContext.setCurrentContext(NSGraphicsContext.graphicsContextWithGraphicsPort(alphaBitmapCtx, false));

But true in Control:

NSGraphicsContext flippedContext = NSGraphicsContext.graphicsContextWithGraphicsPort(graphicsContext.graphicsPort(), true);

@laeubi
Copy link
Contributor Author

laeubi commented Dec 16, 2025

I think this is now basically in a state where it could be merged (if no one further objects) and we can improve on any further issues with additional PRs.

@iloveeclipse
Copy link
Member

I think this is now basically in a state where it could be merged (if no one further objects) and we can improve on any further issues with additional PRs.

Can you please add smoke test that creates a PDF document printing "hello world" to it, saving to file & checking that PDF is not empty (or something like this)? Also dispose() should be tested.

Copilot AI added 6 commits December 17, 2025 18:26
Currently if one wants to create a PDF file it requires external
libraries and as SWT does not allows an abstraction like Grahics2D in
AWT one can not export real content of SWT components (e.g. Canvas)
except exporting as an raster image or using some hacks.

This now introduce a new PDFDocument to enable direct
PDF generation from SWT widgets via Control.print(GC). This allows
applications to export widget content to PDF files using the standard
GC drawing API as well as even creating completely customized documents.
Currently if one wants to create a PDF file it requires external
libraries and as SWT does not allows an abstraction like Grahics2D in
AWT one can not export real content of SWT components (e.g. Canvas)
except exporting as an raster image or using some hacks.

This now introduce a new PDFDocument to enable direct
PDF generation from SWT widgets via Control.print(GC). This allows
applications to export widget content to PDF files using the standard
GC drawing API as well as even creating completely customized documents.
The macOS implementation was just returning false without printing children,
similar to a previously fixed Windows issue. Now it properly iterates through
child controls and prints them with correct coordinate transformations using
NSAffineTransform and NSGraphicsContext state management.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants