Rhymes with Amharic #3 (a.k.a. Read and write a language w/o even getting out of my [em]bed? Kewl!)

by Michael S. Kaplan, published on 2007/04/14 17:26 -04:00, original URI: http://blogs.msdn.com/b/michkap/archive/2007/04/14/2134278.aspx


(see also the first part and the second part)

We now have that binary chunk that needs to be loaded, so let's go ahead and load it!

The core bit of the code for this should have been:

if (File.Exists(FONTNAME)) {
    // We are reading in the embed file info if the file exists (we may have just created it!)
    TTLOAD ulStatusRead = 0;
    FileStream fsRead = new FileStream(FONTNAME, FileMode.Open);
    READEMBEDPROC rep = new READEMBEDPROC(this.ReadEmbedProc);
    TTLOADINFO ttli = new TTLOADINFO();

    ttli.usStructSize = Convert.ToUInt16(Marshal.SizeOf(ttli));
    ttli.usRefStrSize = 0;
    ttli.pusRefStr = IntPtr.Zero;
    ulPrivStatus = 0;

    rc = TTLoadEmbeddedFont(out this.m_hFontReference, TTLOAD.PRIVATE,
                            out ulPrivStatus,
                            LICENSE.EDITABLE, out ulStatusRead,
                            rep, fsRead,
                            "NyalaSIAO", "NyalaSIAO",
                            ttli);
    fsRead.Flush();
    fsRead.Close();

    this.tb1.Font = new Font("NyalaSIAO", siz);
    if (this.tb1.Font.Name != "NyalaSIAO") {
        // We had everything but embedding failed anyway.
        this.lbl1.Text = "Embedding failed, font is: " + this.tb1.Font.Name;
    }
}

And of course the ReadEmbedProc (note the similarities and more importantly the differences when comparing to the WriteEmbedProc mentioned earlier, a ripe potential source of copy/paste codewriting errors!)

[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl, CharSet=CharSet.Unicode)]
internal delegate uint READEMBEDPROC(FileStream lpvReadStream, IntPtr lpvBuffer, uint cbBuffer);

internal uint ReadEmbedProc(FileStream lpvReadStream, IntPtr lpvBuffer, uint cbBuffer)
{
    byte[] rgbyt = new byte[cbBuffer];
    lpvReadStream.Read(rgbyt, 0, (int)cbBuffer);
    Marshal.Copy(rgbyt, 0, lpvBuffer, (int)cbBuffer);
    return cbBuffer;
}

However, in the end it actually proved to be a lot harder than it should have been due to the way that GDI+/WinForms handles the work of fonts, refusing to recognize any font that was not available at application boot time. So even though there is a font available in the process, GDI+ is unwilling to believe it.

The next thing I tried here was to just create it the old fashioned way and stick it into the device context, but that also failed because after all this is not plain old GDI doing the work here, this is either GDI+ using it's notion of the font to use or the WinForms concept of the font to send to Uniscribe (via TextRenderer).Doesn't anyone respect a device context any more? :-)

The solution was to add this last bit of code after succeeding in the call to TTLoadEmbeddedFont:

    IntPtr hdc = GetDC(this.tb1.Handle);

    this.m_hFontEmbedded = CreateFont(MulDiv(Convert.ToInt16(siz), GetDeviceCaps(hdc, LOGPIXELSY), 72),
                                      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, "NyalaSIAO");
    if (this.m_hFontEmbedded != IntPtr.Zero) {
        uint cb = GetFontData(hdc, 0, 0, IntPtr.Zero, 0);
        if (cb != GDI_ERROR) {
            byte[] rgbyt = new byte[cb];
            GetFontData(hdc, 0, 0, rgbyt, cb);
            this.m_pfc = new PrivateFontCollection();
            IntPtr pbyt = Marshal.AllocCoTaskMem(rgbyt.Length);
            Marshal.Copy(rgbyt, 0, pbyt, rgbyt.Length);
            this.m_pfc.AddMemoryFont(pbyt, rgbyt.Length);
            Marshal.FreeCoTaskMem(pbyt);
            this.tb1.Font = new Font(this.m_pfc.Families[0], siz);
        }
    }
}

What this code does is load the font that TTLoadEmbeddedFont has (if you think about it) reconstituted into an actual font and then put it into a memory font via the PrivateFontCollection class, just like what happened in the code from Private fonts: for members only.

When I tested with subsetted font binaries it worked as well, which means that TTLoadEmbeddedFont really is doing a good job here at making what it puts together look like a font. :-)

Now some of the things this code is ignoring include the license info that TTLoadEmbeddedFont returns, as well as the return value. And more importantly, right now the code is assuming that the call succeeds and then just trying to use the results, a strategy which is fine in the constrained situation here but if it is expanded to other fonts then you might want to consider altering that strategy since the CreateFont call will succeed even if it does not recognize the font name and you may specifically not like the font it gives instead....

Next up, some other interesting issues to consider about embedding, and what happens when you are done....

 

This post brought to you by (U+12ee, a.k.a. ETHIOPIC SYLLABLE YO)


no comments

referenced by

2013/10/23 I finally got to meet ali eteraz in person...

2007/12/12 SiaO as The Red Carpet (aka Characters just want to be seen)

2007/04/15 Rhymes with Amharic #5 (a.k.a. [Sub]setting up this code where it can do the most good?)

2007/04/14 Rhymes with Amharic #4 (a.k.a. we're all [sub]set so turning out the lights and going to [em]bed!)

go to newer or older post, or back to index or month or day