Producing WYSIWYG Text Output

Environment: VC6 SP5, WinNT Family, Win9X

People who have ever tried to implement WYSIWYG text output encountered the problem of how to draw text on different devices and in different resolutions in the same way -- especially if you implemented text formatting.

Here is an explanation how to implement WYSIWYG text output:

  1. First of all, we have to retrieve the refernce font data:
  2. // Allocate memory to store font cached data
    *ppFontInfo=new CTLFontInfo();
    
    // Retrieve OUTLINETEXTMETRIC structure size
    UINT nSize=GetOutlineTextMetrics(hDC,0,NULL);
    
    if(!nSize)
      throw system_exception();
    
    pTextMetrics=(OUTLINETEXTMETRIC*)new BYTE[nSize];
    
    // Retrieve OUTLINETEXTMETRIC structure
    if(!GetOutlineTextMetrics(hDC,nSize,pTextMetrics))
      throw system_exception();
    
    // Get reference dc
    // The best solution is to use device context with 
    // the largest resolution
    if(!m_hReferenceDC)
    {
      if(!CreateReferenceDC())
        leave;
    }
    
    // Retrieve device resolution
    // Note: if you use GetDeviceCaps(LOGPIXELSY) with 
    // display device context it will return 96 or 120 
    // (depends on yours display settings). We have 
    // to calculate display resolution using next formula: 
    // GetDeviceCaps(HORZRES)/GetDeviceCaps(HORZSIZE)*25.4.
    // Where: 25.4 - mm per inch.
    double nLogPixelsY=GetDisplayLogPixelsY( m_hReferenceDC,
                                             m_bDisplay);
    
    // Create reference font with height equal to EMSquare.
    LOGFONT lReferenceFont=lFont;
    lReferenceFont.lfHeight=-MulDiv(pTextMetrics->otmEMSquare,
                                    nLogPixelsY,72);
    HFONT hReferenceFont=CreateFontIndirect(&lReferenceFont);
    
    if(!hReferenceFont)
      throw system_exception();
    
    if((hOldFont=
       (HFONT)SelectObject(m_hReferenceDC,hReferenceFont))==NULL)
      throw system_exception();
    
    // Retrieve reference font's OUTLINETEXTMETRIC structure size
    UINT nSize1=GetOutlineTextMetrics(m_hReferenceDC,0,NULL);
    
    if(nSize!=nSize1)
    {
      delete pTextMetrics;
      pTextMetrics=(OUTLINETEXTMETRIC*)new BYTE[nSize1];
    }
    
    // Retrieve reference font's OUTLINETEXTMETRIC structure
    if(!GetOutlineTextMetrics(m_hReferenceDC,nSize1,pTextMetrics))
      throw system_exception();
    
    // Store text metrics inside cached object
    // NOTE: We have to divide all metrics by nLogPixelsY to 
    // use them later with another device context
    (*ppFontInfo)->FillInTextMetrics(pTextMetrics,nLogPixelsY);
    
    // Retrieve characters widths
    int nCharsCount=pTextMetrics->otmTextMetrics.tmLastChar-
                  pTextMetrics->otmTextMetrics.tmFirstChar+1;
    
    int* pCharWidths=new int[nCharsCount];
    
    if(!GetCharWidth(m_hReferenceDC,
                     pTextMetrics->otmTextMetrics.tmFirstChar,
                     pTextMetrics->otmTextMetrics.tmLastChar,
                     pCharWidths))
      leave;
    
    (*ppFontInfo)->FillInCharactersWidths(pCharWidths,nCharsCount,
                      pTextMetrics->otmTextMetrics.tmFirstChar,
                      nLogPixelsY);
    
    // Retrieve kerning pairs
    DWORD dwSize=GetKerningPairs(m_hReferenceDC,0,NULL);
    
    if(dwSize)
    {
      pKerningPairs=
            (KERNINGPAIR*)new BYTE[dwSize*sizeof(KERNINGPAIR)];
    
      if(!GetKerningPairs(m_hReferenceDC,dwSize,pKerningPairs))
        throw system_exception();
    
      (*ppFontInfo)->FillInKerningPairs( pKerningPairs,
                                            dwSize,
                                            nLogPixelsY);
    
      delete pKerningPairs;
      pKerningPairs=NULL;
    }
    
    delete pTextMetrics;
    pTextMetrics=NULL;
    
  3. We have to calculate distances between letters using our current device context resolution.
  4. TL_PAIR Pair(0,0);
    
    long nCount=strlen(szText);
    
    double fKerningValue=0.0;
    double fCurrentWidth=0.0;
    double fTotalWidth=0.0;
    
    // This coefficient will be used to convert font 
    // height from EMSquare to required height 
    double fReferenceFont2LocalFont=m_fFontHeight/
          (double)pFontInfo->m_TextMetrics.otmEMSquare;
    double fLogPixelsY=GetDisplayLogPixelsY(hDC,bDisplay);
    
    for(long i=0;i < nCount;i++)
    {
      // Do we need to correct distances between characters?
      if(bPerformKerning)
      {
        if(i < nCount-1)
        {
        // Adjust kerning
        Pair.m_nFirst=szText[i];
          Pair.m_nSecond=szText[i+1];
          fKerningValue=pFontInfo->m_KerningPairsArray[Pair];
        }
        else
          fKerningValue=0.0;
      }
    
      // Make sure we don't use incorrect (zero-sized) characters...
      _ASSERT(pFontInfo->m_CharactersWidthsArray[szText[i]]);
    
      // Calculate local character width
      fCurrentWidth=
             pFontInfo->m_CharactersWidthsArray[szText[i]]+
                    fKerningValue;
    
      fCurrentWidth*=
              fLogPixelsY*fReferenceFont2LocalFont*fZoomFactor;
    
      CharactersWidthsArray.push_back(fCurrentWidth);
    
      fTotalWidth+=fCurrentWidth;
    }
    
  5. Now we can display our text on any devices we want using ExtTextOut function.
  6. ExtTextOut(hDC,x,y,ETO_CLIPPED,
               &Rect,
               m_TextLines[i]->GetTextLine(),
               m_TextLines[i]->GetTextLineLength(),
               m_TextLines[i]->GetWidthsArray());
    

This method has one disadvantage: we can use only OpenType/TrueType fonts.

I haven't implemented print preview because I didn't have time to do so. Source code has been written for demo purposes only and I didn't optimize it.

You can find basic information about fonts and text in the book Windows Graphics Programming: Win32 GDI and DirectDraw by Feng Yuan.

Downloads

Download demo project - 23 Kb
Download source - 32 Kb


Comments

  • Resource Leak

    Posted by oneillg on 06/11/2007 07:17am

    Has anyone else found and/or solved the resource leaks in this code? I did discover one but there is another buried somewhere. I agree that the code is helpful. Thanks, -Gabe

    • Owner

      Posted by Dennis Jones on 04/24/2014 08:10pm

      Yes, the calls to DeleteObject(hOldFont); are wrong. They attempt to delete an object which is not owned/created by the code; and by contrast, the fonts that are created by the code are not deleted. To fix the errors, create both of the font variables outside the scope of the try block: HFONT hReferenceFont=NULL; HFONT hOldFont=NULL; And then after the catch(), delete the font that is created by the code: DeleteObject(hReferenceFont); There are three places where this must be done: 1. CreateFontChacheData 2. DrawText 3. FormatText I too found this code helpful, and it even worked in C++Builder (with minor modifications), but it has four significant problems: 1. It lacks the ability to write text without having to create a display rectangle (like TextOut, where you just have to supply x,y coords). 2. It does not work for non-TrueType fonts 3. Doesn't provide a way to handle tabbed text 4. It doesn't automatically zoom or take into account the ViewportExt/WindowExt values, so you have to calculate the zoom level and adjust the start position of the text based on the zoom level yourself Ideally, a library like this would provide TextOut/TabbedTextOut replacement functions that operate based on the MapMode and ViewportExt/WindowExt values as the original WinAPI functions do. Does anyone know of such a library, or how to modify this one to work that way?

      Reply
    Reply
  • Great article and code

    Posted by rossamac on 05/15/2007 06:03am

    Just want to say thanks for putting this up. It saved me a lot of time. I have wrapped it for some basic use on image files. The cropping, the wrapping, and the justification all work perfectly for me. The multi-line formatting, I found particularily invaluable. Thanks again.

    Reply
  • Tabs?

    Posted by oneillg on 05/09/2007 11:57am

    Has anyone used this and expanded tab characters like the CEdit control does? -Gabe

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds