Completed
Last Updated: 10 Nov 2021 16:55 by ADMIN
Release R3 2021 SP1
Sparky
Created on: 05 Jun 2021 12:07
Type: Bug Report
3
A fundamental problem (and a way to resolve it) with PDF rendering on linux (in fact, on any system)

Ways to reproduce the problem:


1. Create simple one table one column report, with right horizontal text alignment in the column.

2. Set a data source with multiple rows, with different lengths for the string, displayed in the table's column.

3. Render the report under Windows and under Linux - the column text on different rows will not be aligned properly on linux, but will be shifted to the left or right depending on string's contents and length.

I've done a bit reserch on PDF rendering, concerning libgdiplus "MeasureString" method deficiencies, and here's what I discovered so far:

1. The source of the problem is the fact that libgdiplus ends up using cairo to measure string glyphs, and it does it's font metric in whole pixels (integer), using the DPI of the context it is called in. In short, if the context is using 96DPI, cairo will use these 96DPI to calculate (and round) the font metrics, and to write strings eventually.

2. When libgdiplus is compiled to use pango, the problem is much smaller (several millimeter differences become part of the millimeter), because internally pango scales the measurement 1024 times, but it appears that this is not enough to resolve the problem.

3. When using libgdiplus (with or without pango inbetween it and cairo) for both "DrawString" and "MeasureString", everything is perfect, but the fundamental problem in Relerik Reporting to PDF is that it renders in a vector format, but uses text metering for pixel format - it uses System.Graphics.MeasureString which is a DPI vased metering (on Linux at least) but uses proprietary "PdfRenderer.DrawString" of the "PdfRendered" class, not the native Graphics.DrawString. Since cairo rounds every character glyph metrics to an integer value, the longer the string is, the larger the discrepancy will be between string rendering when PDF is viewed (based on floats/doubles) and Graphics.MeasureString (based on integers, at least on Linux).

Ways to resolve the issue:

1. The best way to resolve the issue is not to call Graphics.MeasureString at all, when rendering to vector based format, because it measures string based on the DPI on graphics context it is in. In other words, proprietary "PdfRenderer.MeasureString" must be  implemented, which works with font glyphs directly, entirely using single or double precision math to calculate glyph dimensions and string dimensions.

2. Another, not so perfect, but HUGELY EASIER way is to set DPI of the rendering context of the PDF to a higher value, here's how:

For the moment, PdfContext's constructor looks like this
public PdfContext(string ownerPassword, string userPassword)
{
            this.dataFormatter = string.IsNullOrWhiteSpace(ownerPassword) ?
                                    new DataFormatter() :
                                    new DataFormatterEncypted(ownerPassword, userPassword);

            this.bmp = new Bitmap(1, 1);
            this.graphics = Graphics.FromImage(this.bmp);
            this.hdc = this.graphics.GetHdc();
            this.pdfFontCache = new PdfFontCache();
 }
This creates a Graphics object, based on a Bitmap with default system's DPI, which is usually 96DPI, or somethiong around this. So, all rendering metrics are callulated for 96DPI, so there will ALWAYS be a discrepancy between PDF's real rendering (when viewed with a reader), and Telerik reporting engine's calculations.

So, a simple way to get around this is to use higer DPI Graphics object, like this:

public PdfContext(string ownerPassword, string userPassword)
{
    this.dataFormatter = string.IsNullOrWhiteSpace(ownerPassword) ?
                            new DataFormatter() :
                            new DataFormatterEncypted(ownerPassword, userPassword);

    this.bmp = new Bitmap(1, 1);
    bmp.SetResolution(9600, 9600); //ADDED
    this.graphics = Graphics.FromImage(this.bmp);
    this.hdc = this.graphics.GetHdc();
    this.pdfFontCache = new PdfFontCache();
}
This makes the discrepancy 100 times lower, and in most cases practically invisible.

3. Another, more or less simple way to get around this, is to multiply font size and layout metrics by some number (I personally tried 16, 32, 128 and 256) before calling Graphics.MeasureString(), and dividing the result of the call by the same number after calling it. From my experience, 16 makes things MUCH better, and 128 is maybe enough to resolve 99.99% of the cases.

Attached is the result when rendered without multiplication (Linux-Orig.png) and when font size and layout methrics are multiplied by 128 before calling "MeasureString", and dividing the result by 128 afterwards (Linux-MultipliedBy128.png). On Windows the result looks exactly like the the corrected rendering on the "Linux-MultipliedBy128.png.
2 comments
ADMIN
Todor
Posted on: 11 Jun 2021 13:16

Hi Sparky,

Thank you for digging that deep into the problem. We will definitely investigate all the suggested approaches and update the thread with our findings.

As a token of gratitude for your efforts, we have updated your Telerik points.

Regards,
Todor
Progress Telerik

Brand new Telerik Reporting course in Virtual Classroom - the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products. Check it out at https://learn.telerik.com/.
Sparky
Posted on: 05 Jun 2021 22:49

I've just found a way to rebuild the sources of Telerik Reporting in order to try Solution 2. Unfortunately Solution 2 does not work - libgdiplus ignores the image DPI when calculating font metrics and uses the device's display dpi instead, which is hardcoded to 96dpi on Lunux. So the remaining solutions 1 and 3 are the only options for now.

I will fill a bug in libgdiplus repository about this anyway, but for now it prevents the most simple solution given in my post above. Nasty! At least I can confirm that solution 3 works, as seen from the pictures.