Rhymes with Amharic #2 (a.k.a. Before you embed, you have build something to embed)

Sorting it all Out
Michael Kaplan's random stuff of dubious value
Be sure to read the disclaimer here first!

Rhymes with Amharic #2 (a.k.a. Before you embed, you have build something to embed)

  • Comments 10

(see here for the first part) 

The first part of the code centers around a call to TTEmbedFont. It only runs on Vista and above (since no one else should have the font on their machine!):

IntPtr hDC = CreateDC("DISPLAY", IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (hDC != IntPtr.Zero) {
    IntPtr hFont = CreateFont(MulDiv(Convert.ToInt16(siz), GetDeviceCaps(hDC, LOGPIXELSY), 72),
                              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, "Nyala");
    if (hFont != IntPtr.Zero) {
        IntPtr hFontOld = SelectObject(hDC, hFont);
        if (hFontOld != IntPtr.Zero) {
            // We are writing out the embed file info for the font if the file doesn't exist.
            uint ulStatus = 0;
            FileStream fsWrite = new FileStream(FONTNAME, FileMode.CreateNew);
            WRITEEMBEDPROC wep = new WRITEEMBEDPROC(this.WriteEmbedProc);
            TTEMBEDINFO ttie = new TTEMBEDINFO();

            ttie.usStructSize = Convert.ToUInt16(Marshal.SizeOf(ttie));
            ttie.usRootStrSize = 0;
            ttie.pusRootStr = IntPtr.Zero;
            ulPrivStatus = 0;
            ulStatus = 0;
            rc = TTEmbedFont(hDC,
                             TTEMBED.RAW | TTEMBED.TTCOMPRESSED,
                             CHARSET.UNICODE,
                             out ulPrivStatus,
                             out ulStatus,
                             wep,
                             fsWrite,
                             IntPtr.Zero,
                             0,
                             0,
                             ttie);
            fsWrite.Flush();
            fsWrite.Close();
            if (rc != E.NONE) {
                // Since creation of the file ultimately failed, delete whatever
                // interim bits might have been written.
                File.Delete(FONTNAME);
            }
            SelectObject(hDC, hFontOld);
        }
        DeleteObject(hFont);
    }
    DeleteDC(hDC);
}

You'll notice that I am passing the flags to include the raw font and not a subset of it. My initial reason for that was the suggestion from some people that the subsetting would not pick up any of the different forms of glyphs that might be available. But Sergey actually told me that the code is rather generous at including all of the alternate forms and glyphs that could potentially derived from the ones that are specified, so in the situation where the text is static, subsetting the font may be worthwhile (and will certainly make for a smaller file!).

If one was going to subset, putting all the text in a string and then changing that IntPtr in the pinvoke declare of pusCharCodeSet to a string and then passing it (after all, what else is a string but an array of ushort values?). :-)

The key piece of code that does this part of  work is that WriteEmbedProc. To be honest, I am not entirely happy with it. You may see why if you look it:

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

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

Okay, so because I am using the .NET FileStream class to do the writing, I am forced to do that extra bit of copying into a byte array that I'd rather avoid. You know, just something to write from that lpvBuffer pointer directly to the file. But the actual hit is small (it is a small file, after all!), so I just kind of thought it would be worth earmarking as an area to potentially revisit if performance became a problem. In the meantime, it does get the job done....

I also chose not to get involved with the whole TTEMBEDINFO structure and its link checking, though people looking at the sample might see it as worthwhile to look into (this is why I bothered to define the struct rather than just making it an IntPtr and passing IntPtr.Zero in this case).

Anyway, when everything is done you end up with a nice little binary file that can be used in your application that needs to display text that may not be available....

In the next post I'll talk about the harder bit, which is actually loading that file....

 

This post brought to you by(U+1275, a.k.a. ETHIOPIC SYLLABLE TE)

Comment on the blather
Leave a Comment
  • Please add 4 and 6 and type the answer here:
  • Post
Blog - Comment List
  • (see also the first part and the second part) We now have that binary chunk that needs to be loaded,

  • Ack! You've succumbed to the bizarre dialect of Group Program Manager/Vice President English!

    I'm not usually not so much of a language purist, but one of the things adequately beaten into my little head during a a class with my university's most dedicated professor of literature was this: geometrically speaking, a center is a single point. Therefore, things do not center around anything; they center upon something. It's just like focus: you can only focus upon one point, you can't focus around something.

    Examples of misuse are widespread, but "to center around" seems particularly pervasive in corporate jargon.

    My own writing is still full of language barbarisms and mishaps, but that one mistake I can never make again. Once, I was listening to a  a GPM at Microsoft talking about our product strategy, and I bit my tongue and clenched my teeth as he used the phrase repeatedly.

  • PingBack from http://www.hanselman.com/blog/CustomCulturesWinFormsFontEmbeddingCodeWithEthiopianAmharicForVistaAndXP.aspx

  • (see also parts 1 , 2 , and 3 ) OK, we are getting close to the end of this little mini-series.... First

  • I may never be entirely used to working for Microsoft, as opposed to working with Microsoft products....

  • <<If one was going to subset, putting all the text in a string and then changing that IntPtr in the pinvoke declare of pusCharCodeSet to a string>>

    Trap here!

    Do not try collecting all the characters that are used in a string, use the real text.

    So don't use ": Cadefhiklnost" for "delete all the files on disk C:," because it will not include the ligatures glyphs (ie "fi" in file) and will mess contextual shaping, which might be needed.

  • Over in the Suggestion Box, Doug asks: Hi Michael, Your posts include a "This post brought to you by"

  • Well done.  I have no words to appreciate your efforts.

Page 1 of 1 (10 items)