Font rendering issues when converting PDFs to PNGs or JPEGs, using .NET on Linux vs on Windows

Hi all,

I’m using the Aspose.PDF library in a .NET application to convert PDF files into images.

When running the application natively on Windows, the output looks great. However, when I run the same application in a Docker container on Linux, the fonts in the generated images appear overly bold, which significantly reduces readability.

You can find attached examples for comparison:

windows.jpg – rendered on Windows

linux.jpg – rendered in Linux Docker container

original_document.pdf – the source PDF

What I’ve Tried:

Font Availability Check
I initially suspected font substitution, but upon inspection, every TextFragment I tested showed TextState.Font.IsAvailable == true.
I even copied the entire Windows fonts directory into the container and registered the fonts via FontRepository.Sources, but the issue persisted.

Rendering Engine Differences
I assumed the issue might be due to differences in GDI rendering.
I installed the latest libgdiplus (compiled from source) in the Docker image, but it did not resolve the problem.

.NET Version Compatibility
I encountered the following issue when trying to upgrade the image to .NET 8 inside the Linux container (Aspose fails to render to image):

System.PlatformNotSupportedException: System.Drawing.Common is not supported on non-Windows platforms.
See Breaking change: System.Drawing.Common only supported on Windows - .NET | Microsoft Learn for more information.

Stack trace when trying to run the solution with .NET 8.0:

[09:53:22 ERR] Exception Type: System.TypeInitializationException
[09:53:22 ERR] Exception Message: The type initializer for 'Gdip' threw an exception.
[09:53:22 ERR] Stack Trace:    at System.Drawing.SafeNativeMethods.Gdip.GdipCreateMatrix2(Single m11, Single m12, Single m21, Single m22, Single dx, 
Single dy, IntPtr& matrix)
   at System.Drawing.Drawing2D.Matrix..ctor(Single m11, Single m12, Single m21, Single m22, Single dx, Single dy)
   at #=zCHl3SkG5wkA90MugFMD$bwVFbCtr7QnPt7qrsNQ=..ctor(Single #=zhK6P$xU=, Single #=zzslREYc=, Single #=zO1R7TGk=, Single #=zEbdxUF4=, Single #=zKjldTpk=, Single #=zhhd5Bhs=)
   at #=zzgO2Q6x4l2QRTHx0UL5iTGaMLVVKXdnNRg==.#=zkUTYTcQ=(Single #=zhK6P$xU=, Single #=zzslREYc=, Single #=zO1R7TGk=, Single #=zEbdxUF4=, Single #=zKjldTpk=, Single #=zhhd5Bhs=, #=zVGOmMuaJbd8WlTa9GRMjUpiiu4PL1S86Hg== #=zM6SDqd0=)
   at #=zA7aN_PIrEpANf7hWsEOpslLbBIKuTwkJED1KGyJr9YNy.#=zH05g24fc2Oay(#=zJWvPQRDSw1wI1aaRBPcqarHvZI9NiJM7Tw== #=zAioM7v4=, #=zxKAo70E9lo_C8BMN56cAO7J9$i7Pu6rXpwyLFB8= #=zsws19IEG2_Fo, #=zDnPkXf2TZcGDHD9faZlJijvmQSrj4L6jlQ== #=zl235MT0=, Single #=zG5UHabh6t2ce, Single #=zo9f3y22gxEwm, Boolean #=z3kHkMJvRxYtieIc9kg==, Int32 #=zVEX0bGkpHnjw5J59e1Xvplg=, Boolean #=zSnrFkx4lGm8O, Double& #=zt1NxiOs=, Double& #=zbahmwa4=, #=zrqJr8NPFguLZQYihr1tIOKv8rdHZ08bG6w==& #=zFLtYEew=)
   at #=zA7aN_PIrEpANf7hWsEOpslLbBIKuTwkJED1KGyJr9YNy..ctor(#=z5kEy0I8nwJ7zA08E53HCmWgD0RpI #=zV9xjgWo=, #=zJWvPQRDSw1wI1aaRBPcqarHvZI9NiJM7Tw== #=zAioM7v4=, #=zxKAo70E9lo_C8BMN56cAO7J9$i7Pu6rXpwyLFB8= #=zsws19IEG2_Fo)
   at #=zb0erBJEZd320jOwmmmR4efYU0ESWO5BFoCfxll4=.#=z0uazCWqDYkBe(#=z5kEy0I8nwJ7zA08E53HCmWgD0RpI #=zV9xjgWo=, #=zJWvPQRDSw1wI1aaRBPcqarHvZI9NiJM7Tw== #=zAioM7v4=, #=zxKAo70E9lo_C8BMN56cAO7J9$i7Pu6rXpwyLFB8= #=zsws19IEG2_Fo)
   at #=zEVopfVcJ8Xy1bxtjwprhx8eO6Y9l3RwyNN4yjYpGRGuO.#=zQw5nv1g=(#=z5kEy0I8nwJ7zA08E53HCmWgD0RpI #=zV9xjgWo=, #=zJWvPQRDSw1wI1aaRBPcqarHvZI9NiJM7Tw== #=zAioM7v4=, #=zxKAo70E9lo_C8BMN56cAO7J9$i7Pu6rXpwyLFB8= #=zm9W6zPs=, #=zA7aN_PIrEpANf7hWsEOpslLbBIKuTwkJED1KGyJr9YNy& #=zhgbSbLo=)
   at #=zvOFLeziqL$npkFFs0vCvX6jDHBlQ.#=zDUKdOdY=(#=zA7aN_PIrEpANf7hWsEOpslLbBIKuTwkJED1KGyJr9YNy& #=zhgbSbLo=)
   at #=zvOFLeziqL$npkFFs0vCvX6jDHBlQ.#=zDUKdOdY=()
   at Aspose.Pdf.Devices.ImageDevice.#=zDUKdOdY=(Page #=zAioM7v4=)
   at Aspose.Pdf.Devices.JpegDevice.Process(Page page, Stream output)
   at AsposeService.PDFImage.GenerateJpegImages(Stream fileData, Int32 pageCount, Int32 width, Int32 height) in /src/AsposeService/PDFImage.cs:line 36
   at AsposeService.Controllers.PdfImageProcessingController.<>c__DisplayClass2_0.<ProcessPdfImages>b__1() in /src/AsposeService/Controllers/PdfImageProcessingController.cs:line 73
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
   at AsposeService.Controllers.PdfImageProcessingController.ProcessPdfImages(HttpContext context) in /src/AsposeService/Controllers/PdfImageProcessingController.cs:line 71
[09:53:22 ERR] Inner Exception 1 Type: System.PlatformNotSupportedException
[09:53:22 ERR] Inner Exception 1 Message: System.Drawing.Common is not supported on non-Windows platforms. See https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only for more information.
[09:53:22 ERR] Inner Exception 1 Stack Trace:    at System.Drawing.SafeNativeMethods.Gdip.<>c.<.cctor>b__2_0(String _, Assembly _, Nullable`1 _)     
   at System.Runtime.InteropServices.NativeLibrary.LoadLibraryCallbackStub(String libraryName, Assembly assembly, Boolean hasDllImportSearchPathFlags, UInt32 dllImportSearchPathFlags)
   at System.Drawing.SafeNativeMethods.Gdip.<GdiplusStartup>g____PInvoke|32_0(IntPtr* __token_native, StartupInputEx* __input_native, StartupOutput* 
__output_native)
   at System.Drawing.SafeNativeMethods.Gdip.GdiplusStartup(IntPtr& token, StartupInputEx& input, StartupOutput& output)
   at System.Drawing.SafeNativeMethods.Gdip..cctor()
[09:53:22 ERR] Environment Information:
[09:53:22 ERR] OS: Unix 5.10.102.1
[09:53:22 ERR] Runtime: 8.0.17

My base Dockerfile for .NET 6.0:

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y libgdiplus
RUN sed -i'.bak' 's/$/ contrib/' /etc/apt/sources.list
RUN apt-get update && apt-get install -y ttf-mscorefonts-installer fontconfig

Request:

I would appreciate any light shedding on:

  1. Why the font rendering appears much bolder on Linux?

  2. Is there any recommended setup (e.g., font config or rendering backend) that ensures visual parity between platforms?

  3. Is it somehow possible to still run AsposePDF JPEG conversion over Linux on .NET > 6.0 ?

Thanks in advance! Let me know if you need more technical info or test files.

@DanLei

When converting PDFs to images using the Aspose.PDF library in a .NET application, you may encounter discrepancies in font rendering between Windows and Linux environments. Here are some insights and recommendations to address your concerns:

1. Why the font rendering appears much bolder on Linux?

The difference in font rendering between Windows and Linux can often be attributed to variations in the underlying graphics rendering systems. Windows uses GDI (Graphics Device Interface) for rendering fonts, while Linux typically uses libraries like Cairo or FreeType. These libraries may handle font smoothing and anti-aliasing differently, leading to fonts appearing bolder or less crisp on Linux.

2. Recommended setup for visual parity between platforms

To improve font rendering consistency across platforms, consider the following steps:

  • Font Configuration: Ensure that the font configuration is properly set up in your Linux Docker container. The fontconfig package can help manage font availability and rendering settings. Make sure you have the same fonts installed and configured as on Windows.

  • Rendering Backend: You mentioned installing libgdiplus, which is necessary for using System.Drawing on Linux. However, it might not fully replicate Windows’ rendering behavior. You can experiment with different image rendering options provided by Aspose to see if they yield better results.

  • Aspose Settings: Check if there are specific rendering options in the Aspose library that can be adjusted to enhance font rendering quality. For instance, using different ImageSaveOptions may help.

3. Running Aspose.PDF JPEG conversion on .NET > 6.0 in Linux

Currently, System.Drawing.Common is not supported on non-Windows platforms in .NET 8, which is causing the exceptions you’re encountering. To work around this limitation, you can:

  • Stick with .NET 6: Continue using .NET 6, as it supports System.Drawing.Common on Linux, allowing you to perform JPEG conversion without encountering this issue.

  • Consider Alternatives: If you must use .NET 8, explore other libraries or rendering techniques that are compatible with Linux for image generation.

By implementing these changes, you should be able to achieve better consistency in font rendering across different platforms. If you continue to experience issues, it may be helpful to review Aspose documentation or community forums for specific tips related to font rendering in your use case.

If you have further questions or need assistance with specific configurations, feel free to ask!

Sources:
[1]: Aspose.PDF for .NET API Reference