Few fonts rendering error
Prerequisites
- [x] I have written a descriptive issue title
- [x] I have verified that I am running the latest version of Fonts
- [x] I have verified if the problem exist in both
DEBUGandRELEASEmode - [x] I have searched open and closed issues to ensure it has not already been reported
Fonts version
3.0.0-alpha.0.11
Other Six Labors packages and versions
4.0.0-alpha.0.55 / Drawing: 3.0.0-alpha.0.6
Environment (Operating system, version and so on)
Ubuntu 24.04
.NET Framework version
.net 9.0
Description
Hello,
I wrote a small program to compare rendering between ImageSharp and Chrome. And I just want to share this since I found minor difference.
The result is overall very good !
I don't know if you prefer 1 issue for each one I found (I can split it), but here it is :)
Preview: https://html-preview.github.io/?url=https://github.com/Socolin/TestImageSharpFont/blob/main/index.html
The code: https://github.com/Socolin/TestImageSharpFont/tree/main
And I was surprise to see in my small tests no major issue with Thai and Hebrew (there are open issue for those #214 #215 , maybe my tests did not cover case related to those)
The one I saw are
Sinhala:
Bengali:
Tibetan: (Offset on Y)
Myanmar (Offset on Y + minor issues):
Steps to Reproduce
- Clone https://github.com/Socolin/TestImageSharpFont/tree/main
- Run
dotnet run - Check
index.html
Images
No response
😔 not looking forward to debugging these.
@Socolin The Tibetan issue at least is easy to debug.
There are 2 issues there:
- Fonts, doesn't currently allow vertical alignment by baseline (I keep thinking of adding it, and am tempted to make it the default to match browsers)
- Chrome uses a line height that appears to be roughly 1.4F by default.
So locally, if I use an updated build and tweak your setup code slightly... I get a decent result.
async Task AddSimpleTestCaseAsync(string name, string fontPath, string text)
{
var fontCollection = new FontCollection();
var fontFamily = fontCollection.Add(fontPath);
var font = fontFamily.CreateFont(fontSize, FontStyle.Regular);
var richTextOptions = new RichTextOptions(font)
{
LineSpacing = 1.4f, // Match browser default
VerticalAlignment = VerticalAlignment.Baseline // New!!
};
Directory.CreateDirectory("output");
// Use the measured advance to size the image.
FontRectangle advance = TextMeasurer.MeasureAdvance(text, richTextOptions);
int width = Math.Max((int)Math.Ceiling(advance.Width), 1600);
int height = (int)Math.Ceiling(advance.Height);
var image = new Image<Rgba32>(width, height, Color.White.ToPixel<Rgba32>());
image.Mutate(x => x.DrawText(richTextOptions, text, Brushes.Solid(Color.Black)));
await image.SaveAsync($"output/{name}.png");
htmlTestOutput.AddTest(name, $"output/{name}.png", text, fontPath);
}
I'll investigate the shaping issues next.
Actually @Socolin I had that entirely wrong. That change broke all the other layouts.
Fonts is actually measuring everything perfectly. If I set an explicit line height of 1.4 for both my code and Chrome, you can see that the actual containing div covers the exact area that my output renders. Browsers simply render the text outside the textbox by default.
Setting overflow: hidden demonstrates this clearly.
Given my canvas is the image, its bounds are a hard clip region, so anything that extends above y = 0 or below y = height is simply discarded. Chrome is using the same line-box metrics (ascender, descender, line-height) that I am, but the default overflow: visible allows glyph ink to extend outside the box. As soon as you switch the div to overflow: hidden, Chrome clips the text in exactly the same way my image does.
The practical workaround on my side is to detect how far the shaped text extends above the baseline on the first line (ScaledMinY), and if the ink exceeds the typographic ascender, I shift the baseline down by only that extra amount before drawing. That effectively adds just enough top padding so the full glyph cluster fits inside the rendered image, without affecting normal Latin text layout.
The image below shows the result after applying that workaround. Any small offset difference from Chrome is simply due to compensating for the negative ink position during layout.
Ha yeah, that make sense.
Is the problem is only when drawing the text on the top of the image ? if we try to draw this in the middle, the current behavior is doing the same as chrome ?
So maybe the workaround for this should be done outside DrawText and to handle this case we should always add some padding top on top to handle this case, and draw the image with an offset later.
I'm curious to see what the render is by mixing latin and Tibetan characters on the same line
The glyph information is fully captured so any offset that allows the out of bounds areas to be present in the glyph would be rendered. Mixing Latin and Tibetan characters wouldn't change the output unless you were using a text run to render the Latin glyphs at a much larger font size.
My changes within the text run will add the appropriate offset and advance to the leading text line so that the issue disappears. Measuring the advance will give you the correct required area.
Here's multiline output to demonstrate.
Looks like I have to implement an explicit Myanmar shaper. There's some initial glyph reordering that our universal shaper does not do.
@Socolin Looks like my new shaper works well.
Yeah this looks good ! Nice :)
@Socolin One more to go!
@Socolin GOT IT!!!
I still have some work to do to clean up and update tests, but I can now match your entire test output.
Awesome :) I'm curious so see what you had to do to get this to works (What are the shapers doing is stil fuzzy on my mind), I'll read the code once you merge this :)