Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Printing from WPF is leaving random small spaces in the text when using Arial or Calibri fonts #7499

Closed
campeters opened this issue Feb 3, 2023 · 16 comments · Fixed by #7545
Assignees
Labels
🚧 work in progress Investigate Requires further investigation by the WPF team.

Comments

@campeters
Copy link

campeters commented Feb 3, 2023

Description

When printing in WPF, the graphics engine is inserting small spaces within words. For instance, the French word 'fonctions' is printed either as 'fonction s' or 'fo nctions' depending on where the word appears within the other text.

Reproduction Steps

public void PrintItAll()
        {
            var printDialog = new PrintDialog();
            if (printDialog.ShowDialog() == true)
            {
                var fontFamily = new FontFamily("Arial");
                var printme = new TextBlock {
                    FontSize = 11, FontFamily = fontFamily, Width = 800, TextWrapping = TextWrapping.Wrap,
                    Text = "L'autre personne était incapable de prendre soin des enfants en raison d'une déficience des fonctions physiques ou mentales qui l'a obligée, durant une période d'au moins deux semaines, à garder le lit, à se déplacer en fauteuil roulant ou à recevoir des soins dans un hôpital ou un établissement semblable. Joignez un certificat du médecin traitant qui confirme la nature et la durée de la déficience."
                };
                printDialog.PrintVisual(printme, "Test Print");
            }
        }

Reproduction repository: https://github.com/campeters/Wpf-Print-Font-Issue

Expected behavior

The text should print correctly without extra spaces.

Actual behavior

The following file shows a space between 'fonction' and the 's'. It should be a single french word 'fonctions'

Test Print.pdf

Regression?

This code works correctly in dot net framework and it also seems to work correctly in .net 5.

Known Workarounds

None.

Impact

This bug impacts the quality of PDF and print files generated from WPF programs using the 'Arial' or 'Calibri' fonts. Some fonts work correctly. However, the filing of some government forms require that only 'Arial' be used.

Configuration

This bug occurs in .net 6, confirmed in 6.0.11 and 6.0.13 and .net 7. It does not appear to be specific to the configuration. It happens on Windows 10 and Windows 11.

The exact same code works correctly in .net framework 4.8.1

Other information

No response

@miloush
Copy link
Contributor

miloush commented Feb 3, 2023

Interesting, net48 blue, net7 red overlaid:

48vs7

Looks like expected in a FixedDocument in DocumentViewer, but printing it to pdf has the same issue.

@dipeshmsft dipeshmsft self-assigned this Feb 4, 2023
@dipeshmsft dipeshmsft added the Investigate Requires further investigation by the WPF team. label Feb 4, 2023
@dipeshmsft
Copy link
Member

@miloush @campeters, I am not sure yet but this issue looks like the effect of this fix #6295
In the attached bug, you will see screenshots related to the problem.

The main issue here comes from the fact that when we print to XPS ( pdf here ) the 'advance width' values are converted from double to int and therefore after a few characters the difference between what is displayed on the screen and when it is printed became significant. Therefore, to mitigate this, we cumulated the error of conversion and update the next 'advance width' value by +/-1 when the cumulated error exceeded +/-1.

@miloush
Copy link
Contributor

miloush commented Feb 4, 2023

Yes it looks like a rounding error (though I would expect it to repeat on the right side). The solution in #6295 might even lead to catastrophic cancellation. It would be better to keep track of the accumulated advance widths rather than errors.

@campeters
Copy link
Author

@dipeshmsft @miloush - I can confirm that reversing #6295 fixes the rendering problem in our app.

@campeters
Copy link
Author

Is there a repo that contains an example of the rendering issue shown here? #6525 If so, I could try to find a fix for my issue that does not break that one...

@dipeshmsft
Copy link
Member

I can find a sample repo for this issue. I will get back to you with the sample application.

@dipeshmsft
Copy link
Member

Hey @campeters, here is the sample app GlyphRunIssue.zip
to repro the behavior before the fix.

@campeters
Copy link
Author

Here is a repo that shows both of the issues:

https://github.com/campeters/GlyphRunIssue

In .net 5, the text renders correctly. In .net 6, the glyphs render correctly.

@campeters
Copy link
Author

@miloush / @dipeshmsft - I've reviewed this in more detail and the advanceErrorRounding is not the root of the problem. Somewhere upstream of the GlyphSerializer (maybe the DrawingContextFlattener), the GlyphRuns are separated into groups of no more than 100 characters. Within the 100 character glyph run, the spacing is correct. However, when the drawing caret is advanced to start the next 100 character glyph run, the error correction is not taken into account, leaving an extra space (or possibly two characters squished together). I'm going to look further, but if you have some idea where in the code the glyph runs are separated we could correct the root cause of this issue.

@miloush
Copy link
Contributor

miloush commented Feb 15, 2023

The ±100 character chopping happens in MS.Internal.TextFormatting.TextStore, resp. MS.Internal.TextFormatting.TextRunCacheImp.FetchTextRun, but I don't think it is very relevant. If you turn off the rounding completely, it renders as expected. Fix the rounding and you should be good.

@miloush
Copy link
Contributor

miloush commented Feb 16, 2023

OK while technically true, it is not a simple algorithmical issue, there seems to be an actual bug in what is rounded. I suspect the advance widths from shaping and from font can differ in floating precision but not integer precision. In such case, the error from shaping is taken but the font one should be used. There is also question about what precision does the "font width" use in XPS. I will have a look.

@miloush miloush self-assigned this Feb 16, 2023
@campeters
Copy link
Author

I've messed around with the code in TextStore and I can improve the rendering by changing FetchTextRun so that it prefers breaking on a space. That way the extra fraction of a space is not noticeable.

However, I suspect you are right... there is a a difference in the precision XPS uses for font width and what WPF uses when it does the layout.

@miloush
Copy link
Contributor

miloush commented Feb 16, 2023

Not all scripts use spaces for word separation, and there can be significant differences after and before shaping due to kerning etc., so changing the breaking limit is not really solving the issue.

Btw. Open XPS specification ECMA-388 states in [M5.6]:

So that rounding errors do not accumulate, the advance MUST be calculated as the exact unrounded origin of the subsequent glyph minus the sum of the calculated (that is, rounded) advance widths of the preceding glyphs [M5.6].

Which is what I originally suggested needs to be done in terms of rounding. @dipeshmsft a good practice might be to consult the specification when changing code in areas that have one.

Nowhere it is stated that the advance width coming from the font table is rounded. Therefore, when the value is omitted from XML, it sounds like the advance width is potentially not an integer.

[M5.13] states:

When advance widths are omitted from the markup and the glyphs are algorithmically emboldened, the advance widths obtained from the horizontal metrics font table (if IsSideways is false) or the vertical metrics font table (if IsSideways is true) of the font MUST be increased by 2% of the em size [M5.13].

Glyph advance widths MAY be omitted from markup where the advance width desired is specified in the font tables, once adjusted for algorithmic emboldening. [O5.5]

I don't see the 2% increase in the code for IsSideways (or for algorithmically emboldened glyphs). However, these rules are only for optimizing the XML and I expect the conflicting cases to affect the layout only very rarely. Either way, if that is to be fixed, it should be fixed as a separate issue.

@dipeshmsft
Copy link
Member

The solution in #6295 might even lead to catastrophic cancellation. It would be better to keep track of the accumulated advance widths rather than errors.

Yes, it might even lead to catastrophic cancellation, but I think that was good in this case - it just means that the unrounded position and the rounded position are so close together that 0 is a good enough approximation. Am I missing something?

@miloush
Copy link
Contributor

miloush commented Feb 25, 2023

@dipeshmsft My intent with the catastrophic cancellation note was that if you subtract two close numbers, you can get arbitrarily large relative error, which then keeps accumulating. I thought I could avoid it by keeping the total advance in an integer, but then I noticed the XPS specification is not clear on whether the advance is integral or not. You are correct, if we don't accumulate it it is fine.

@wstaelens
Copy link
Contributor

wstaelens commented Mar 20, 2023

@miloush / @dipeshmsft - I've reviewed this in more detail and the advanceErrorRounding is not the root of the problem. Somewhere upstream of the GlyphSerializer (maybe the DrawingContextFlattener), the GlyphRuns are separated into groups of no more than 100 characters. Within the 100 character glyph run, the spacing is correct. However, when the drawing caret is advanced to start the next 100 character glyph run, the error correction is not taken into account, leaving an extra space (or possibly two characters squished together). I'm going to look further, but if you have some idea where in the code the glyph runs are separated we could correct the root cause of this issue.

Glad I found this topic. We are experiencing the same issue when printing out XPS documents.

When we render on screen, everything looks fine (dpi of screen is much smaller compared to dpi of printer. (printer often 1200, 600 dpi):
xps-bug-01-screen

When we print the XPS we see 10 being smashed together exactly at character position 100/101.
xps-bug-02-print

This is the content:
xps-bug-03

The two strings:
xxx DOOS 200ST 3.3776 10 xxx

xxx DOOS 100ST 8.6726 10 xxx

@jeffhandley @dipeshmsft : How can we get this fix at/for our customers?

I've seen this being merged
#6555
#6295

But there has also seen a question by @campeters if this is going into a dotnet 6 maintenance release. So I assume this fix it not available yet for us?

We are also on .net 6.

(also this issue explains why we see the pipe-chars | at the end not being align under each other. The issue is also possibly bigger when there is also bold text in the line).

@ghost ghost removed the 🚧 work in progress label Apr 3, 2023
@ghost ghost locked as resolved and limited conversation to collaborators May 3, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
🚧 work in progress Investigate Requires further investigation by the WPF team.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants