Robmaister / SharpFont

Cross-platform FreeType bindings for .NET - Need maintainer
Other
294 stars 107 forks source link

Can not use text include space? #142

Closed mhjy closed 1 month ago

mhjy commented 1 month ago

I want to draw a text include space. But the program throw the exception: if (b.Width == 0 || b.Rows == 0) throw new InvalidOperationException("Invalid image size - one or both dimensions are 0.");

HinTak commented 1 month ago

FWIW, that's quite common - you need to learn more about graphics in general. Space has zero width and zero row, which describes the size of the drawn pixels. There is no pixels drawn. Spaces have a non-zero "advance width" or "advance". That's the shift in positioning for the next non-space character.

The positioning and width/row are separate - it is possible for a character to partly overlap its previous or the next character, or have gaps. So the (pixel) width can be large than the advance (width) in these cases.

HinTak commented 1 month ago

You can read two examples in freetype-py - python binding of freetype, which is similar since SharpFont is really just c# binding of freetype:

https://github.com/rougier/freetype-py/blob/e9968d040cfba46610970e4090b2ad383a9638e7/examples/hello-world-cairo.py#L53

https://github.com/rougier/freetype-py/blob/e9968d040cfba46610970e4090b2ad383a9638e7/examples/hello-world.py#L47

They do the same thing. Cairo does not let you draw zero width pixel chunks, but numpy does, hence there is an extra "if ..." in one. But other than that, both do x += advance ... below to reposition for the next character.

HinTak commented 1 month ago

Disclaimer: I wrote the cairo version, and is co-maintainer of freetype-py.

mhjy commented 1 month ago

the code "Bitmap cBmp = face.Glyph.Bitmap.ToGdipBitmap(Color.Firebrick);" has mistake?

namespace SharpFont.HarfBuzz.Example { class Program { static void Main(string[] args) { Console.WriteLine("Version string: " + HB.VersionString); Version v = HB.Version; Console.WriteLine("Version: " + v.Major + "." + v.Minor + "." + v.Build); Console.WriteLine("VersionCheck: " + HB.VersionAtLeast(v));

        var lib = new Library();
        var face = new SharpFont.Face(lib, @"C:\Windows\Fonts\汉仪雪峰体简.ttf");
        //var face = new SharpFont.Face(lib, @"C:\Windows\Fonts\tahoma.ttf");
        //var face = new SharpFont.Face(lib, @"C:\Windows\Fonts\GLTHawangTig.ttf");
        face.SetCharSize(0, 50, 72, 72);

        var font = HarfBuzz.Font.FromFTFace(face);
        var buf = new HarfBuzz.Buffer();
        //buf.Direction = Direction.TopToBottom;  
        //buf.Direction= Direction.RightToLeft;
        buf.Direction = Direction.LeftToRight;
        //buf.Script = Script.Arabic;
        buf.Script = Script.Mongolian;
        buf.AddText("ᠥᠩᠬᠡᠵᠠᠶᠠᠭ ᠎ᠠ");//ᠥᠩᠬᠡᠵᠠᠶᠠᠭ ᠎ᠠ1234 计算机");
        font.Shape(buf);

        var glyphInfos = buf.GlyphInfo();
        var glyphPositions = buf.GlyphPositions();

        int height = (face.MaxAdvanceHeight - face.Descender) >>6;
        int width = 0;
        for (int i = 0; i < glyphInfos.Length; ++i)
        {
            width += glyphPositions[i].xAdvance>> 6;
        }

        Bitmap bmp = new Bitmap(width, height);
        Graphics g = Graphics.FromImage(bmp);
        g.Clear(Color.Gray);

        int penX = 0, penY = face.MaxAdvanceHeight >> 6;
        //draw the string
        for (int i = 0; i < glyphInfos.Length; ++i)
        {
            face.LoadGlyph(glyphInfos[i].codepoint, LoadFlags.Default, LoadTarget.Normal);
            face.Glyph.RenderGlyph(RenderMode.Normal);

            Bitmap cBmp = face.Glyph.Bitmap.ToGdipBitmap(Color.Firebrick);
            g.DrawImageUnscaled(cBmp,
                penX + (glyphPositions[i].xOffset >> 6) + face.Glyph.BitmapLeft,
                penY - (glyphPositions[i].yOffset >> 6) - face.Glyph.BitmapTop);

            penX += glyphPositions[i].xAdvance >> 6;
            penY -= glyphPositions[i].yAdvance >> 6;
        }

        g.Dispose();

        bmp.Save("output.bmp");
    }
}

}

HinTak commented 1 month ago

Not looking at your code in detail - it is your own job to figure out what's wrong with it. I already gave you a clue telling you cairo's drawing routine has a similar issue. I.e. most likely you need to insert a if (pixel_width > 0) before ...Bitmap cBmp.... Already told you that some drawing libraries (numpy/pillow) let you draw zero pixels, some (cairo) don't let you draw zero pixels.

HinTak commented 1 month ago

In the the latter case, you simply skip the draw as there is nothing to draw, but keep doing the re-positioning of the pen. Ie. You mostly likely need to modify your code to looks a bit like this (not debugged , and please do not complain about cut-and-paste into yours don't work):

...
                face.LoadGlyph(glyphInfos[i].codepoint, LoadFlags.Default, LoadTarget.Normal);
            face.Glyph.RenderGlyph(RenderMode.Normal);

If (pixel_width > 0) {
            Bitmap cBmp = face.Glyph.Bitmap.ToGdipBitmap(Color.Firebrick);
            g.DrawImageUnscaled(cBmp,
                penX + (glyphPositions[i].xOffset >> 6) + face.Glyph.BitmapLeft,
                penY - (glyphPositions[i].yOffset >> 6) - face.Glyph.BitmapTop);
}

            penX += glyphPositions[i].xAdvance >> 6;
            penY -= glyphPositions[i].yAdvance >> 6;
        }
...
mhjy commented 1 month ago

Thank you very much! ..................... if (face.Glyph.Bitmap.Width>0) { Bitmap cBmp = face.Glyph.Bitmap.ToGdipBitmap(Color.Firebrick); g.DrawImageUnscaled(cBmp, penX + (glyphPositions[i].xOffset >> 6) + face.Glyph.BitmapLeft, penY - (glyphPositions[i].yOffset >> 6) - face.Glyph.BitmapTop); } penX += glyphPositions[i].xAdvance >> 6; penY -= glyphPositions[i].yAdvance >> 6; ................................