Measuring of Text with Multiple Fonts in One Single Line
-
I am following a request to write C++ code for a label that contains several text elements in one single line, varying by font, size, color.... Ok, that can be done easily in GDI+ by measuring each element's width and then execute a DrawString for each of the text elements starting at its calculated position. So far, I failed miserably. :confused::mad: The horizontal text positions did not appear to be correct. I reverted now to very simple text measuring tests which confused me even more. Test 1: Use of MeasureString With the same font, the width of the string "MM" does not match the double with of the string "M". This cannot be explained with eventual rounding problems. Test2: Use of MeasureCharacterRanges Used the same font as for the first test. The width of "MM" is now exactly double of the width of "M". But: The width of the "M" ist lightyears away from the measurement result in the first test. Here's the code I have used for the two tests:
void ApplWindow_TextDrawTest(HDC hDC)
{
Gdiplus::Graphics *G = new Gdiplus::Graphics(hDC);
G->SetTextRenderingHint(TextRenderingHint::TextRenderingHintClearTypeGridFit);Gdiplus::StringFormat MyFormat; MyFormat.SetAlignment(Gdiplus::StringAlignment::StringAlignmentNear); MyFormat.SetFormatFlags(Gdiplus::StringFormatFlags::StringFormatFlagsNoWrap); Gdiplus::Font TextFont(L"Calibri", 36, Gdiplus::FontStyle::FontStyleBold, Gdiplus::Unit::UnitPoint); const wchar\_t \*Text1M = L"M"; Gdiplus::PointF TextOrigin1M(0, 0); Gdiplus::RectF TextBounds1M; const wchar\_t \*Text2M = L"MM"; Gdiplus::PointF TextOrigin2M(0, 50); Gdiplus::RectF TextBounds2M; //--- Test #1: using MeasureString ---------- G->MeasureString(Text1M, (INT)wcslen(Text1M), &TextFont, TextOrigin1M, &MyFormat, &TextBounds1M); G->MeasureString(Text2M, (INT)wcslen(Text2M), &TextFont, TextOrigin2M, &MyFormat, &TextBounds2M); //--- Results: Text 1 Width= 59.218 ("M") //--- Text 2 Width= 102.427 ("MM") //--- Test #2: using MeasureCharacterRanges ---------- Gdiplus::Status RCode; Gdiplus::RectF LayoutRect(0, 0, 1000, 100); Gdiplus::Region RegionsList\[3\]; Gdiplus::CharacterRange CRanges\[3\]; CRanges\[0\].First = 0; CRanges\[0\].Length = 1; CRanges\[1\].First = 1; CRanges\[1\].Length = 1; CRanges\[2\].First = 0; CRanges\[2\].Length = 2; MyFormat.SetMeasurableCharacterRanges(3, CRanges); G->MeasureCharacterRanges(Text2M, (INT)wcslen(Text2M), &TextFont, LayoutRect, &MyFormat, 3, RegionsList); RCode = RegionsList\[0\].GetBounds(&Text
-
I am following a request to write C++ code for a label that contains several text elements in one single line, varying by font, size, color.... Ok, that can be done easily in GDI+ by measuring each element's width and then execute a DrawString for each of the text elements starting at its calculated position. So far, I failed miserably. :confused::mad: The horizontal text positions did not appear to be correct. I reverted now to very simple text measuring tests which confused me even more. Test 1: Use of MeasureString With the same font, the width of the string "MM" does not match the double with of the string "M". This cannot be explained with eventual rounding problems. Test2: Use of MeasureCharacterRanges Used the same font as for the first test. The width of "MM" is now exactly double of the width of "M". But: The width of the "M" ist lightyears away from the measurement result in the first test. Here's the code I have used for the two tests:
void ApplWindow_TextDrawTest(HDC hDC)
{
Gdiplus::Graphics *G = new Gdiplus::Graphics(hDC);
G->SetTextRenderingHint(TextRenderingHint::TextRenderingHintClearTypeGridFit);Gdiplus::StringFormat MyFormat; MyFormat.SetAlignment(Gdiplus::StringAlignment::StringAlignmentNear); MyFormat.SetFormatFlags(Gdiplus::StringFormatFlags::StringFormatFlagsNoWrap); Gdiplus::Font TextFont(L"Calibri", 36, Gdiplus::FontStyle::FontStyleBold, Gdiplus::Unit::UnitPoint); const wchar\_t \*Text1M = L"M"; Gdiplus::PointF TextOrigin1M(0, 0); Gdiplus::RectF TextBounds1M; const wchar\_t \*Text2M = L"MM"; Gdiplus::PointF TextOrigin2M(0, 50); Gdiplus::RectF TextBounds2M; //--- Test #1: using MeasureString ---------- G->MeasureString(Text1M, (INT)wcslen(Text1M), &TextFont, TextOrigin1M, &MyFormat, &TextBounds1M); G->MeasureString(Text2M, (INT)wcslen(Text2M), &TextFont, TextOrigin2M, &MyFormat, &TextBounds2M); //--- Results: Text 1 Width= 59.218 ("M") //--- Text 2 Width= 102.427 ("MM") //--- Test #2: using MeasureCharacterRanges ---------- Gdiplus::Status RCode; Gdiplus::RectF LayoutRect(0, 0, 1000, 100); Gdiplus::Region RegionsList\[3\]; Gdiplus::CharacterRange CRanges\[3\]; CRanges\[0\].First = 0; CRanges\[0\].Length = 1; CRanges\[1\].First = 1; CRanges\[1\].Length = 1; CRanges\[2\].First = 0; CRanges\[2\].Length = 2; MyFormat.SetMeasurableCharacterRanges(3, CRanges); G->MeasureCharacterRanges(Text2M, (INT)wcslen(Text2M), &TextFont, LayoutRect, &MyFormat, 3, RegionsList); RCode = RegionsList\[0\].GetBounds(&Text
Update I have added one more test which is using standard GDI, and I have changed the GDI+ font to units of pixels. Unfortunately, this third test shows results which do not match the previous results at all. Does anybody have an idea why I get so different results? Since I had not only added a third test but also changed the code of the first tests I am appending the full example here; the results I found have been added as comments:
void ApplWindow_TextDrawTest(HDC hDC)
{
Gdiplus::Graphics *G = new Gdiplus::Graphics(hDC);
G->SetTextRenderingHint(TextRenderingHint::TextRenderingHintClearTypeGridFit);Gdiplus::StringFormat MyFormat; MyFormat.SetAlignment(Gdiplus::StringAlignment::StringAlignmentNear); MyFormat.SetFormatFlags(Gdiplus::StringFormatFlags::StringFormatFlagsNoWrap); Gdiplus::Font TextFont(L"Calibri", 36, Gdiplus::FontStyle::FontStyleBold, Gdiplus::Unit::UnitPixel); const wchar\_t \*Text1M = L"M"; Gdiplus::PointF TextOrigin1M(0, 0); Gdiplus::RectF TextBounds1M; const wchar\_t \*Text2M = L"MM"; Gdiplus::PointF TextOrigin2M(0, 50); Gdiplus::RectF TextBounds2M; //--- Test #1: using MeasureString ---------- G->MeasureString(Text1M, (INT)wcslen(Text1M), &TextFont, TextOrigin1M, &MyFormat, &TextBounds1M); G->MeasureString(Text2M, (INT)wcslen(Text2M), &TextFont, TextOrigin2M, &MyFormat, &TextBounds2M); //--- Results: Text 1 Width= 44.414 ("M") //--- Text 2 Width= 76.828 ("MM") //--- Test #2: using MeasureCharacterRanges ---------- Gdiplus::Status RCode; Gdiplus::RectF LayoutRect(0, 0, 1000, 100); Gdiplus::Region RegionsList\[3\]; Gdiplus::CharacterRange CRanges\[3\]; CRanges\[0\].First = 0; CRanges\[0\].Length = 1; CRanges\[1\].First = 1; CRanges\[1\].Length = 1; CRanges\[2\].First = 0; CRanges\[2\].Length = 2; MyFormat.SetMeasurableCharacterRanges(3, CRanges); G->MeasureCharacterRanges(Text2M, (INT)wcslen(Text2M), &TextFont, LayoutRect, &MyFormat, 3, RegionsList); RCode = RegionsList\[0\].GetBounds(&TextBounds1M, G); // Result: Text 1 Width = 32.000 ("M") RCode = RegionsList\[1\].GetBounds(&TextBounds1M, G); // Result: Text 1 Width = 32.000 ("M"; the second char) RCode = RegionsList\[2\].GetBounds(&TextBounds2M, G); // Result: Text 2 Width = 64.000 ("MM") //--- Test #3: using the good old GDI ---------- int MapModeResult = SetMapMode(hDC, MM\_TEXT); // MM\_TEXT is equivalent to Unit::UnitPixel? HFONT TextFont3 = CreateFont(36, 0, 0, 0, FW\_BOLD, false, false, false, ANSI\_CHARSET, OUT\_TT\_ONLY\_PRECIS, C
-
Update I have added one more test which is using standard GDI, and I have changed the GDI+ font to units of pixels. Unfortunately, this third test shows results which do not match the previous results at all. Does anybody have an idea why I get so different results? Since I had not only added a third test but also changed the code of the first tests I am appending the full example here; the results I found have been added as comments:
void ApplWindow_TextDrawTest(HDC hDC)
{
Gdiplus::Graphics *G = new Gdiplus::Graphics(hDC);
G->SetTextRenderingHint(TextRenderingHint::TextRenderingHintClearTypeGridFit);Gdiplus::StringFormat MyFormat; MyFormat.SetAlignment(Gdiplus::StringAlignment::StringAlignmentNear); MyFormat.SetFormatFlags(Gdiplus::StringFormatFlags::StringFormatFlagsNoWrap); Gdiplus::Font TextFont(L"Calibri", 36, Gdiplus::FontStyle::FontStyleBold, Gdiplus::Unit::UnitPixel); const wchar\_t \*Text1M = L"M"; Gdiplus::PointF TextOrigin1M(0, 0); Gdiplus::RectF TextBounds1M; const wchar\_t \*Text2M = L"MM"; Gdiplus::PointF TextOrigin2M(0, 50); Gdiplus::RectF TextBounds2M; //--- Test #1: using MeasureString ---------- G->MeasureString(Text1M, (INT)wcslen(Text1M), &TextFont, TextOrigin1M, &MyFormat, &TextBounds1M); G->MeasureString(Text2M, (INT)wcslen(Text2M), &TextFont, TextOrigin2M, &MyFormat, &TextBounds2M); //--- Results: Text 1 Width= 44.414 ("M") //--- Text 2 Width= 76.828 ("MM") //--- Test #2: using MeasureCharacterRanges ---------- Gdiplus::Status RCode; Gdiplus::RectF LayoutRect(0, 0, 1000, 100); Gdiplus::Region RegionsList\[3\]; Gdiplus::CharacterRange CRanges\[3\]; CRanges\[0\].First = 0; CRanges\[0\].Length = 1; CRanges\[1\].First = 1; CRanges\[1\].Length = 1; CRanges\[2\].First = 0; CRanges\[2\].Length = 2; MyFormat.SetMeasurableCharacterRanges(3, CRanges); G->MeasureCharacterRanges(Text2M, (INT)wcslen(Text2M), &TextFont, LayoutRect, &MyFormat, 3, RegionsList); RCode = RegionsList\[0\].GetBounds(&TextBounds1M, G); // Result: Text 1 Width = 32.000 ("M") RCode = RegionsList\[1\].GetBounds(&TextBounds1M, G); // Result: Text 1 Width = 32.000 ("M"; the second char) RCode = RegionsList\[2\].GetBounds(&TextBounds2M, G); // Result: Text 2 Width = 64.000 ("MM") //--- Test #3: using the good old GDI ---------- int MapModeResult = SetMapMode(hDC, MM\_TEXT); // MM\_TEXT is equivalent to Unit::UnitPixel? HFONT TextFont3 = CreateFont(36, 0, 0, 0, FW\_BOLD, false, false, false, ANSI\_CHARSET, OUT\_TT\_ONLY\_PRECIS, C
-
Update I have added one more test which is using standard GDI, and I have changed the GDI+ font to units of pixels. Unfortunately, this third test shows results which do not match the previous results at all. Does anybody have an idea why I get so different results? Since I had not only added a third test but also changed the code of the first tests I am appending the full example here; the results I found have been added as comments:
void ApplWindow_TextDrawTest(HDC hDC)
{
Gdiplus::Graphics *G = new Gdiplus::Graphics(hDC);
G->SetTextRenderingHint(TextRenderingHint::TextRenderingHintClearTypeGridFit);Gdiplus::StringFormat MyFormat; MyFormat.SetAlignment(Gdiplus::StringAlignment::StringAlignmentNear); MyFormat.SetFormatFlags(Gdiplus::StringFormatFlags::StringFormatFlagsNoWrap); Gdiplus::Font TextFont(L"Calibri", 36, Gdiplus::FontStyle::FontStyleBold, Gdiplus::Unit::UnitPixel); const wchar\_t \*Text1M = L"M"; Gdiplus::PointF TextOrigin1M(0, 0); Gdiplus::RectF TextBounds1M; const wchar\_t \*Text2M = L"MM"; Gdiplus::PointF TextOrigin2M(0, 50); Gdiplus::RectF TextBounds2M; //--- Test #1: using MeasureString ---------- G->MeasureString(Text1M, (INT)wcslen(Text1M), &TextFont, TextOrigin1M, &MyFormat, &TextBounds1M); G->MeasureString(Text2M, (INT)wcslen(Text2M), &TextFont, TextOrigin2M, &MyFormat, &TextBounds2M); //--- Results: Text 1 Width= 44.414 ("M") //--- Text 2 Width= 76.828 ("MM") //--- Test #2: using MeasureCharacterRanges ---------- Gdiplus::Status RCode; Gdiplus::RectF LayoutRect(0, 0, 1000, 100); Gdiplus::Region RegionsList\[3\]; Gdiplus::CharacterRange CRanges\[3\]; CRanges\[0\].First = 0; CRanges\[0\].Length = 1; CRanges\[1\].First = 1; CRanges\[1\].Length = 1; CRanges\[2\].First = 0; CRanges\[2\].Length = 2; MyFormat.SetMeasurableCharacterRanges(3, CRanges); G->MeasureCharacterRanges(Text2M, (INT)wcslen(Text2M), &TextFont, LayoutRect, &MyFormat, 3, RegionsList); RCode = RegionsList\[0\].GetBounds(&TextBounds1M, G); // Result: Text 1 Width = 32.000 ("M") RCode = RegionsList\[1\].GetBounds(&TextBounds1M, G); // Result: Text 1 Width = 32.000 ("M"; the second char) RCode = RegionsList\[2\].GetBounds(&TextBounds2M, G); // Result: Text 2 Width = 64.000 ("MM") //--- Test #3: using the good old GDI ---------- int MapModeResult = SetMapMode(hDC, MM\_TEXT); // MM\_TEXT is equivalent to Unit::UnitPixel? HFONT TextFont3 = CreateFont(36, 0, 0, 0, FW\_BOLD, false, false, false, ANSI\_CHARSET, OUT\_TT\_ONLY\_PRECIS, C
I think I found the answer to my question: GDI and GDI+ do both not resolve by fractions of pixels. That is mainly why my measurement result and the rendered output do not match in all situations. I have now received an advice from a friend to take a closer look into the features of DirectWrite. Indeed, this engine appears to be the solution for my demands.