Fonts icon indicating copy to clipboard operation
Fonts copied to clipboard

Few fonts rendering error

Open Socolin opened this issue 1 month ago • 6 comments

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 DEBUG and RELEASE mode
  • [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: Image

Bengali:

Image

Tibetan: (Offset on Y)

Image

Myanmar (Offset on Y + minor issues):

Image

Steps to Reproduce

  • Clone https://github.com/Socolin/TestImageSharpFont/tree/main
  • Run dotnet run
  • Check index.html

Images

No response

Socolin avatar Nov 29 '25 23:11 Socolin

😔 not looking forward to debugging these.

JimBobSquarePants avatar Nov 30 '25 02:11 JimBobSquarePants

@Socolin The Tibetan issue at least is easy to debug.

There are 2 issues there:

  1. 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)
  2. 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);
}
Image

I'll investigate the shaping issues next.

JimBobSquarePants avatar Dec 02 '25 04:12 JimBobSquarePants

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.

Image

Setting overflow: hidden demonstrates this clearly.

Image

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.

Image

JimBobSquarePants avatar Dec 02 '25 11:12 JimBobSquarePants

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

Socolin avatar Dec 02 '25 16:12 Socolin

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.

Image

JimBobSquarePants avatar Dec 03 '25 05:12 JimBobSquarePants

Looks like I have to implement an explicit Myanmar shaper. There's some initial glyph reordering that our universal shaper does not do.

JimBobSquarePants avatar Dec 10 '25 12:12 JimBobSquarePants

@Socolin Looks like my new shaper works well.

Image

JimBobSquarePants avatar Dec 14 '25 11:12 JimBobSquarePants

Yeah this looks good ! Nice :)

Socolin avatar Dec 14 '25 20:12 Socolin

@Socolin One more to go!

Image

JimBobSquarePants avatar Dec 17 '25 00:12 JimBobSquarePants

@Socolin GOT IT!!!

Image

I still have some work to do to clean up and update tests, but I can now match your entire test output.

JimBobSquarePants avatar Dec 17 '25 14:12 JimBobSquarePants

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 :)

Socolin avatar Dec 17 '25 15:12 Socolin