【问题标题】:Rendering subscript using DrawText or similar function使用 DrawText 或类似函数渲染下标
【发布时间】:2012-02-20 12:44:01
【问题描述】:

问题很简单。如何将以下文本绘制到 TStringGrid 单元格中?

操作系统是 Windows XP(或 Windows Vista 或 Windows 7)。首选的开发环境是 C++ Builder 6,但我也会接受 Delphi 的 C++ Builder XE 的解决方案。首选的 API 函数是DrawText,但如果存在比这更好的函数也没问题。字体名称为Times New Roman,字号为11。目前我正在使用这种方法来渲染单元格内容(简体):

void __fastcall TForm_Main::StringGrid_DrawCell(TObject *Sender,
  int ACol, int ARow, TRect &Rect, TGridDrawState State)
{
   TStringGrid *grid = (TStringGrid*)Sender;
   if (grid == NULL) return;

   // 1. BACKGROUND
   grid->Canvas->Brush->Color = grid->Color;
   grid->Canvas->FillRect(Rect);

   // 2. TEXT
   grid->Canvas->Font->Assign(grid->Font);   // Times New Roman, 11pt

   // horizontal centering
   RECT RText = static_cast<RECT>(Rect);
   AnsiString text = grid->Cells[ACol][ARow];
   if (!text.IsEmpty()) {
      int text_len = strlen(text.c_str());
      SIZE size;
      memset(&size, 0, sizeof(SIZE));
      GetTextExtentPoint32(grid->Canvas->Handle, text.c_str(), text_len, &size);
      int offset_x = (Rect.Width() - size.cx) >> 1;
      RText.left += offset_x; RText.right += offset_x;

      // rendering
      DrawText(grid->Canvas->Handle, text.c_str(), text_len, &RText, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
   }
}

某些字符具有作为特殊 unicode 字符的下标字形(例如,代码 0x2081 分配给下标 1)但不幸的是,大写字母 U 并非如此。此外,并非所有字体都支持这些字符(例如 Times New Roman 字体不支持代码范围 0x2070 - 209F),请参阅 this Wikipedia 文章。我正在寻找更通用的解决方案,例如 Microsoft Word 实现的解决方案。 MS Word 使用 Times New Roman 字体将大写字母 U 渲染为下标没有问题。

【问题讨论】:

  • 为什么不以较小的字体大小和比正常低几个像素来渲染文本?
  • 谁知道什么是最佳比例?最佳位置?我认为下标字符应该向下移动一点(这在我直接取自 MS Word 输出的示例中不是很明显)。也许这有一些规范。我不知道这些细节。我希望输出看起来既专业又漂亮。
  • @truthseeker:如果你想让它看起来很专业,你应该使用 italics 表示(标量)变量! This is how it should look like!
  • 我不明白你的问题...是 Unicode 字符吗?
  • Unicode 与否无关紧要。但我当然更喜欢 Unicode 解决方案。问题的重点是“如何将下标和上标字符渲染到画布中”?

标签: windows delphi fonts rendering c++builder


【解决方案1】:

如果你想让一些字符呈现为上标,你必须在它前面加上 ^。同样,下标字符必须以 _ 为前缀。

void __fastcall TForm_Main::StringGrid_DrawCell(TObject *Sender, int ACol, int ARow, TRect &Rect, TGridDrawState State)
{
   TStringGrid *grid = (TStringGrid*)Sender;
   if (grid == NULL)
   {
      return;
   }
   WideString wtext = L"φ_U = 120";
   if (wtext.IsEmpty()) return;

   // layout
   SIZE size;
   memset(&size, 0, sizeof(SIZE));
   SSGetTextExtentPoint(grid->Canvas, wtext, size);
   int offset_x = (Rect.Width() - size.cx + 1) >> 1;  // horizontal centering
   RECT RText = static_cast<RECT>(Rect);
   RText.left += offset_x;
   RText.right += offset_x;

   // rendering
   SetBkMode(grid->Canvas->Handle, TRANSPARENT);
   SSDrawText(grid->Canvas, wtext, RText, DT_LEFT);
}

int TForm_Main::SSGetTextExtentPoint(TCanvas *canvas, WideString text, SIZE &size)
{
   // Source: http://www.codeproject.com/Articles/12660/Using-Subscripts-and-Superscripts-When-Showing-Tex
   SaveDC(canvas->Handle);

   SIZE sz;
   RECT outRect =
   {0, 0, 0, 0};

   HFONT oldFont;

   LOGFONT lf;
   GetObject(canvas->Font->Handle, sizeof(LOGFONT), &lf);

   POINT sub, sup, subofs, supofs;

   // Calculate subscript/superscript size and offsets
   bool use_pixel_unit = false;
   if (lf.lfHeight < 0)
   {
      lf.lfHeight    = MulDiv(-lf.lfHeight, 72, GetDeviceCaps(canvas->Handle, LOGPIXELSY));
      use_pixel_unit = true;
   }
   sub.x = lf.lfWidth / 2;
   sup.x = lf.lfWidth / 2;
   sub.y = lf.lfHeight / 3 * 2;
   sup.y = lf.lfHeight / 3 * 2;

   subofs.x = lf.lfWidth / 2;
   supofs.x = lf.lfWidth / 2;
   subofs.y = lf.lfHeight / 6;
   supofs.y = lf.lfHeight / 3;

   lf.lfWidth  = sub.x;
   lf.lfHeight = sub.y;
   if (use_pixel_unit)
   {
      lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(canvas->Handle, LOGPIXELSY), 72);
   }
   HFONT SubFont;
   SubFont = CreateFontIndirect(&lf);

   lf.lfWidth  = sup.x;
   lf.lfHeight = sup.y;
   HFONT SupFont;
   if (use_pixel_unit)
   {
      lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(canvas->Handle, LOGPIXELSY), 72);
   }
   SupFont = CreateFontIndirect(&lf);

   WideString temp = text;
   TCHAR c;

   // Calculate the size of the text that needs to be displayed
   do
   {
      int x1       = 1, x2 = 1, x = 1;
      WideString s = "";
      c            = ' ';
      bool bFind   = true;

      // Find the first "^" or "_", indicating the sub- or superscript
      while (bFind)
      {
         x1 = text.Pos(L"^");
         x2 = text.Pos(L"_");
         if ((x1 == 0) && (x2 == 0))
         {
            x = 0;
         }
         else if ((x1 > 0) && (x2 > 0))
         {
            x = min(x1, x2);
         }
         else if (x1 > 0)
         {
            x = x1;
         }
         else
         {
            x = x2;
         }
         if (x == 0)
         {
            bFind = false;
            x     = text.Length() + 1;
         }
         else if (x == text.Length())
         {
            bFind = false;
         }
         else if (text[x] != text[x + 1])
         {
            bFind = false;
            c     = text[x];
         }
         else
         {
         x++;
         }
         s = s + text.SubString(1, x - 1);
         text.Delete(1, min(x, text.Length()));
      }
      sz = canvas->TextExtent(s);
      outRect.right += sz.cx;
      if ((outRect.bottom - outRect.top) < sz.cy)
      {
         outRect.top = outRect.bottom - sz.cy;
      }

      switch (c)
      {
      case '^':
         oldFont = (HFONT)SelectObject(canvas->Handle, SupFont);
         GetTextExtentPoint32W(canvas->Handle, text.c_bstr(), 1, &sz);
         outRect.right += sz.cx + supofs.x;
         text.Delete(1, 1);
         SelectObject(canvas->Handle, oldFont);
         break;
      case '_':
         oldFont = (HFONT)SelectObject(canvas->Handle, SubFont);
         GetTextExtentPoint32W(canvas->Handle, text.c_bstr(), 1, &sz);
         outRect.right += sz.cx + subofs.x;
         text.Delete(1, 1);
         SelectObject(canvas->Handle, oldFont);
         break;
      }
   }
   while (c != ' ');

   // Adjust text position
   outRect.bottom += subofs.y;
   outRect.top -= subofs.x;

   size.cx = outRect.right - outRect.left;
   size.cy = outRect.bottom - outRect.top;

   DeleteObject(SubFont);
   DeleteObject(SupFont);
   // Done, restoring the device context
   RestoreDC(canvas->Handle, -1);
   return 0;
}

// ---------------------------------------------------------------------------
int TForm_Main::SSDrawText(TCanvas *canvas, WideString text, RECT &drawRect, int justification)
{
   // Source: http://www.codeproject.com/Articles/12660/Using-Subscripts-and-Superscripts-When-Showing-Tex
   SaveDC(canvas->Handle);

   SIZE sz;
   RECT outRect =
   {0, 0, 0, 0};

   HFONT oldFont;
   LOGFONT lf;
   GetObject(canvas->Font->Handle, sizeof(LOGFONT), &lf);

   POINT sub, sup, subofs, supofs;
   bool contains_subscript   = false;
   bool contains_superscript = false;

   // Calculate subscript/superscript size and offsets
   bool use_pixel_unit = false;
   if (lf.lfHeight < 0)
   {
      lf.lfHeight    = MulDiv(-lf.lfHeight, 72, GetDeviceCaps(canvas->Handle, LOGPIXELSY));
      use_pixel_unit = true;
   }

   sub.x = (lf.lfWidth + 1) >> 1;
   sup.x = (lf.lfWidth + 1) >> 1;
   sub.y = (lf.lfHeight << 1) / 3;
   sup.y = (lf.lfHeight << 1) / 3;
   if (lf.lfHeight == 10)
   {
      sub.y++; // make subscript a little larger
   }

   subofs.x = (lf.lfWidth + 1) >> 1;
   supofs.x = (lf.lfWidth + 1) >> 1;
   subofs.y = (lf.lfHeight + 3) / 6;
   supofs.y = (lf.lfHeight) / 3;

   long sub_shift_down = lf.lfHeight - sub.y;

   lf.lfWidth  = sub.x;
   lf.lfHeight = sub.y;
   if (use_pixel_unit)
   {
      lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(canvas->Handle, LOGPIXELSY), 72);
   }
   HFONT SubFont;
   SubFont = CreateFontIndirect(&lf);

   lf.lfWidth  = sup.x;
   lf.lfHeight = sup.y;
   if (use_pixel_unit)
   {
      lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(canvas->Handle, LOGPIXELSY), 72);
   }
   HFONT SupFont;
   SupFont = CreateFontIndirect(&lf);

   WideString temp = text;
   TCHAR c;

   // Calculate the size of the text that needs to be displayed
   do
   {
      int x1       = 1, x2 = 1, x = 1;
      WideString s = "";
      c            = ' ';
      bool bFind   = true;

      // Find the first "^" or "_", indicating the sub- or superscript
      while (bFind)
      {
         x1 = text.Pos(L"^");
         x2 = text.Pos(L"_");
         if ((x1 == 0) && (x2 == 0))
         {
            x = 0;
         }
         else if ((x1 > 0) && (x2 > 0))
         {
            x = min(x1, x2);
         }
         else if (x1 > 0)
         {
            x = x1;
         }
         else
       {
            x = x2;
         }
         if (x == 0)
         {
            bFind = false;
            x     = text.Length() + 1;
         }
         else if (x == text.Length())
         {
            bFind = false;
         }
         else if (text[x] != text[x + 1])
         {
            bFind = false;
            c     = text[x];
         }
         else
         {
            x++;
         }
         s = s + text.SubString(1, x - 1);
         text.Delete(1, min(x, text.Length()));
      }
      sz = canvas->TextExtent(s);
      outRect.right += sz.cx;
      if ((outRect.bottom - outRect.top) < sz.cy)
      {
         outRect.top = outRect.bottom - sz.cy;
      }

      switch (c)
      {
      case '^':
         oldFont = (HFONT)SelectObject(canvas->Handle, SupFont);
         GetTextExtentPoint32W(canvas->Handle, text.c_bstr(), 1, &sz);
         outRect.right += sz.cx + supofs.x;
         text.Delete(1, 1);
         SelectObject(canvas->Handle, oldFont);
         contains_superscript = true;
         break;
      case '_':
         oldFont = (HFONT)SelectObject(canvas->Handle, SubFont);
         GetTextExtentPoint32W(canvas->Handle, text.c_bstr(), 1, &sz);
         outRect.right += sz.cx + subofs.x;
       text.Delete(1, 1);
         SelectObject(canvas->Handle, oldFont);
         contains_subscript = true;
         break;
      }
   }
   while (c != ' ');

   // Adjust text position
   if (contains_subscript)
   {
      outRect.bottom += subofs.y;
   }
   if (contains_superscript)
   {
      outRect.top -= supofs.y;
   }
   POINT Origin;
   Origin.y = drawRect.top + (((drawRect.bottom - drawRect.top) - (outRect.bottom - outRect.top) + 1) >> 1);

   switch (justification)
   {
   case DT_CENTER:
     Origin.x = (drawRect.right - drawRect.left) / 2 - (outRect.right - outRect.left) / 2 + drawRect.left;
     break;
   case DT_LEFT:
     Origin.x = drawRect.left;
     break;
   case DT_RIGHT:
     Origin.x = drawRect.right - (outRect.right - outRect.left);
   }

   POINT pnt = Origin;

   text = temp;

   // Draw text
   do
   {
      int x1       = 1, x2 = 1, x = 1;
      WideString s = "";
      c            = ' ';
      bool bFind   = true;

      // Find the first "^" or "_", indicating the sub- or superscript
     while (bFind)
      {
         x  = text.Pos(L"^_");
         x1 = text.Pos(L"^");
         x2 = text.Pos(L"_");
         if ((x1 == 0) && (x2 == 0))
         {
            x = 0;
         }
         else if ((x1 > 0) && (x2 > 0))
         {
            x = min(x1, x2);
         }
         else if (x1 > 0)
         {
            x = x1;
         }
         else
         {
            x = x2;
         }
         if (x == 0)
         {
            bFind = false;
            x     = text.Length() + 1;
         }
         else if (x == text.Length())
         {
            bFind = false;
         }
         else if (text[x] != text[x + 1])
         {
            bFind = false;
            c     = text[x];
         }
         else
         {
            x++;
         }
         s = s + text.SubString(1, x - 1);
         text.Delete(1, min(x, text.Length()));
      }
      // Draw main text
      ExtTextOutW(canvas->Handle, pnt.x, pnt.y, 0, &drawRect, s.c_bstr(), s.Length(), NULL);
      GetTextExtentPoint32W(canvas->Handle, s.c_bstr(), s.Length(), &sz);
     pnt.x += sz.cx;

      // Draw subscript or superscript
      switch (c)
      {
      case '^':
         oldFont = (HFONT)SelectObject(canvas->Handle, SupFont);
         ExtTextOutW(canvas->Handle, pnt.x + supofs.x, pnt.y - supofs.y, 0, &drawRect, text.c_bstr(), 1, NULL);
         GetTextExtentPoint32W(canvas->Handle, text.c_bstr(), 1, &sz);
         pnt.x += sz.cx + supofs.x;
         text.Delete(1, 1);
         SelectObject(canvas->Handle, oldFont);
         break;
      case '_':
         oldFont = (HFONT)SelectObject(canvas->Handle, SubFont);
         ExtTextOutW(canvas->Handle, pnt.x + subofs.x, pnt.y + subofs.y + sub_shift_down, 0, &drawRect, text.c_bstr(), 1, NULL);
         GetTextExtentPoint32W(canvas->Handle, text.c_bstr(), 1, &sz);
         pnt.x += sz.cx + subofs.x;
         text.Delete(1, 1);
         SelectObject(canvas->Handle, oldFont);
         break;
      }
   }
   while (c != ' ');

   DeleteObject(SubFont);
   DeleteObject(SupFont);
   // Done, restoring the device context
   RestoreDC(canvas->Handle, -1);
   return 0;
}

【讨论】:

    【解决方案2】:

    ` function SSTextLeft(ACanvas: TCanvas; ARect: TRect; const S: string): integer;

    var
      i,
      h,
      h3: integer;
      sup,
      sub: boolean;
    begin
      with ACanvas, ARect do begin
        h:= textHeight('H');
        h3:= h div 3;
        brush.Style:= bsClear;
        moveTo(Left +1, Top +1);
        i:= 1;
        while i < Length(s) +1 do begin
          if s[i] = '^' then
            begin
              Inc(i);
              font.Height:= MulDiv(font.Height, 8, 10);
              textOut(penPos.X, penPos.Y -h3, s[i]);
              moveTo(penPos.X +1, penPos.Y +h3);
              font.Height:= h;
              Inc(i);
            end
          else if s[i] = '_' then
            begin
              Inc(i);
              font.Height:= MulDiv(font.Height, 8, 10);
              textOut(penPos.X, penPos.Y +h3, s[i]);
              moveTo(penPos.X +1, penPos.Y -h3);
              font.Height:= h;
              Inc(i);
            end
          else
            begin
              textOut(penPos.X, penPos.Y, s[i]);
              Inc(i);
              moveTo(penPos.X +1, penPos.Y);
            end;
        end;//  while
      end;
      sup:= Pos('^', S) > 0;
      sub:= Pos('_', S) > 0;
      if sup and sub then
        result:= MulDiv(h, 5, 3)
      else if sup or sub then
        result:= h +h3
      else
        result:= h;
    end;//  SSTextLeft
    

    `

    【讨论】:

    • 虽然这段代码 sn-p 可以解决问题,including an explanation 确实有助于提高您的帖子质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。
    猜你喜欢
    • 2014-12-17
    • 2021-09-06
    • 1970-01-01
    • 2020-10-20
    • 2012-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多