problem with TextRenderer.MeasureText - winforms

Hi I am using TextRenderer.MeasureText() method to measure the text width for a given font. I use Arial Unicode MS font for measuring the width, which is a Unicode font containing characters for all languages. The method returns different widths on different servers. Both machines have Windows 2003, and .net 3.5 SP1 installed.
Here is the code we used
using (Graphics g = Graphics.FromImage(new Bitmap(1, 1)))
{
width = TextRenderer.MeasureText(g, word, textFont, new Size(5, 5), TextFormatFlags.NoPadding).Width;
}
Any idea why this happens?
I use C# 2.0

//--------------------------------------------------------------------------------------
// MeasureText always adds about 1/2 em width of white space on the right,
// even when NoPadding is specified. It returns zero for an empty string.
// To get the precise string width, measure the width of a string containing a
// single period and subtract that from the width of our original string plus a period.
//--------------------------------------------------------------------------------------
public static Size MeasureText(string Text, Font Font) {
TextFormatFlags flags
= TextFormatFlags.Left
| TextFormatFlags.Top
| TextFormatFlags.NoPadding
| TextFormatFlags.NoPrefix;
Size szProposed = new Size(int.MaxValue, int.MaxValue);
Size sz1 = TextRenderer.MeasureText(".", Font, szProposed, flags);
Size sz2 = TextRenderer.MeasureText(Text + ".", Font, szProposed, flags);
return new Size(sz2.Width - sz1.Width, sz2.Height);
}

MeasureText is not known to be accurate.
Heres a better way :
protected int _MeasureDisplayStringWidth ( Graphics graphics, string text, Font font )
{
if ( text == "" )
return 0;
StringFormat format = new StringFormat ( StringFormat.GenericDefault );
RectangleF rect = new RectangleF ( 0, 0, 1000, 1000 );
CharacterRange[] ranges = { new CharacterRange ( 0, text.Length ) };
Region[] regions = new Region[1];
format.SetMeasurableCharacterRanges ( ranges );
format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
regions = graphics.MeasureCharacterRanges ( text, font, rect, format );
rect = regions[0].GetBounds ( graphics );
return (int)( rect.Right );
}

We had a similar problem several years back. In our case, for some reason we had different versions of the same font installed on two different machines. The OS version was the same, but the font was different.
Since you normally don't deploy a system font with your application setup, measuring and output results may vary from one machine to another, based on the font version.
Since you say ...
And not all the machines return different values only some of them..!
...this is something I'd check for.

Related

What is the quickest way to get the background color of a single character in a RichTextBox?

Using WinForms, I have a RichTextBox which will hold the contents of a text file whose length will be limited only by what a RichTextBox can manage.
A section within it will be marked using a certain background color. I need to quickly identify where the section begins and ends. I don't trust that the section will be completely unbroken, and the marked area might cover the entire text--so I need to examine the background color of every character.
The obvious way to do this is to select each character in turn and get its SelectionBackColor:
private TextRange? CalcMarkedTextRange() {
var rtb = RichTextBox;
var textLength = rtb.TextLength;
// Store the range of whatever the user currently has selected
var currentSelectionRange = new TextRange( rtb );
rtb.Visible = false; // <--- To prevent slow screen updates
// Find where the first and last color-marked characters are
var markBegins = -1;
var markEnds = -1;
for ( int ix = 0; ix < textLength; ++ix ) {
rtb.Select( ix, 1 );
if ( rtb.SelectionBackColor == SelectedTextBackColor ) {
if ( markBegins == -1 ) {
markBegins = ix;
}
markEnds = ix;
}
}
// Put back user's selection
rtb.Select( currentSelectionRange );
rtb.Visible = true;
// See what we found
if ( markBegins > -1 ) {
// Return a single range encompassing all marked characters
return new TextRange( markBegins, markEnds - markBegins + 1 );
}
return null;
}
In the code above, TextRange is a structure that stores the start and length of a selection, just as you might expect.
If I don't hide the control by messing with its Visible property before scanning the colors, this code is unbelievably slow. You can see it scanning through, even with a relatively small amount of text. My test using about 4000 characters exhibited unacceptably poor performance.
I do hide it, it operates much more quickly, but there is still an ugly flash as the control disappears for a few seconds then comes back with its scroll position slightly off.
There must be a way to know the background color of an individual character without having to select it, though I imagine one would have to call a native Win32 method. But I have had no luck finding one.

Check if Unicode character is displayed or tofu

My question is similar to this one, but a little step-forward.
In my Win32 program I have some menu button with Unicode characters above BMP, such as U+1F5A4 🖤 (UTF-16 surrogate pairs 0xD83D 0xDDA4).
In Windows 10 the system font Segoe UI doesn't have this glyph: it is automagically replaced with a glyph from the font Segoe UI Symbol and displayed correctly in the button, thanks to a process called font linking (or font fallback, still not clear to me).
But in Windows 7 the font linking brings to a font that doesn't have this glyph neither, and the surrogate pairs appear as two empty boxes ▯▯. The same in Windows XP with Tahoma font.
I want to avoid these replacement boxes, by parsing the text before or after the assignment to the button, and replacing the missing glyph with some common ASCII character.
I tried GetGlyphOutline, ScriptGetCMap, GetFontUnicodeRanges and GetGlyphIndices but they don't support surrogate pairs.
I also tried GetCharacterPlacement and Uniscribe ScriptItemize+ScriptShape that support surrogate pairs, but all these functions search only into the base font of HDC (Segoe UI), they don't search for eventually fallback font (Segoe UI Symbol), which is the one that provides the 🖤 glyph.
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink it's a place where I looked, but I really think it's not there the system takes the fonts to link to.
The question is: how can I know if the system font-linking produces the correct glyph or tofu boxes instead?
Edit
I found some kind of solution copying trom this code and adding the last GetCharacterPlacement.
#include <usp10.h>
wchar_t *checkGlyphExist( HWND hwnd, wchar_t *sUnicode, wchar_t *sLimited ) {
// Create metafile
HDC hdc = GetDC( hwnd );
HDC metaFileDC = CreateEnhMetaFile( hdc, NULL, NULL, NULL );
// Select menu font
NONCLIENTMETRICSW ncm;
ncm.cbSize = sizeof(ncm);
SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0 );
HFONT hFont = CreateFontIndirectW( &(ncm.lfMenuFont) );
SelectObject( metaFileDC, hFont );
wprintf( L"%s\n", ncm.lfMenuFont.lfFaceName ); // 'Segoe UI' in Win 10 and 7 (ok)
// 'Tahoma' in Win XP (ok)
// Use the meta file to intercept the fallback font chosen by Uniscribe
SCRIPT_STRING_ANALYSIS ssa;
ScriptStringAnalyse( metaFileDC, sUnicode, wcslen(sUnicode), 0, -1,
SSA_METAFILE | SSA_FALLBACK | SSA_GLYPHS | SSA_LINK,
0, NULL, NULL, NULL, NULL, NULL, &ssa );
ScriptStringFree( &ssa );
HENHMETAFILE metaFile = CloseEnhMetaFile(metaFileDC);
LOGFONTW logFont = {0};
EnumEnhMetaFile( 0, metaFile, metaFileEnumProc, &logFont, NULL );
DeleteEnhMetaFile( metaFile );
wprintf( L"%s\n", logFont.lfFaceName );
// 'Segoe UI Symbol' in Win 10 (ok)
// 'Microsoft Sans Serif' in Win 7 (wrong, should be 'Segoe UI Symbol')
// 'Tahoma' in Win XP for characters above 0xFFFF (wrong, should be 'Microsoft Sans Serif', I guess)
// Get glyph indices for the 'sUnicode' string
hFont = CreateFontIndirectW( &logFont );
SelectObject( hdc, hFont );
GCP_RESULTSW infoStr = {0};
infoStr.lStructSize = sizeof(GCP_RESULTSW);
wchar_t tempStr[wcslen(sUnicode)];
wcscpy( tempStr, sUnicode );
infoStr.lpGlyphs = tempStr;
infoStr.nGlyphs = wcslen(tempStr);
GetCharacterPlacementW( hdc, tempStr, wcslen(tempStr), 0, &infoStr, GCP_GLYPHSHAPE );
ReleaseDC( hwnd, hdc );
// Return one string
if( infoStr.lpGlyphs[0] == 3 || // for Windows 7 and 10
infoStr.lpGlyphs[0] == 0 ) // for Windows XP
return sLimited;
else
return sUnicode;
}
// Callback function to intercept font creation
int CALLBACK metaFileEnumProc( HDC hdc, HANDLETABLE *table, const ENHMETARECORD *record,
int tableEntries, LPARAM logFont ) {
if( record->iType == EMR_EXTCREATEFONTINDIRECTW ) {
const EMREXTCREATEFONTINDIRECTW* fontRecord = (const EMREXTCREATEFONTINDIRECTW *)record;
*(LOGFONTW *)logFont = fontRecord->elfw.elfLogFont;
}
return 1;
}
You can call it with checkGlyphExist( hWnd, L"🖤", L"<3" );
I tested on Windows 10 and on two virtual machines: Windows 7 Professional, Windows XP SP2.
It works quite well, but two problems still remain about the fallback font that EnumEnhMetaFile retrieves when a glyph is missing in base font:
in Windows 7 is always Microsoft Sans Serif, but the real fallback font should be Segoe UI Symbol.
in Windows XP is Tahoma instead of Microsoft Sans Serif, but only for surrogate pairs characters (for BMP characters is Microsoft Sans Serif that is correct, I guess).
Can someone help me to solve this?
First you have to make sure you're using same API on both Win7 and Win10. Lower level gdi32 API is not supposed to support surrogate pairs in general I think, while newer DirectWrite does, on every level. Next thing to keep in mind is that font fallback (font linking is a different thing) data differs from release to release and it's not something user has access to, and it's not modifiable.
Second thing to check if Win7 provides fonts for symbol at U+1F5A4 in a first place, it's possible it was introduced in later versions only.
Basically if you're using system rendering functionality, older or newer, you're not supposed to control fallback most of the time, if it doesn't work for you it usually means it won't work. DirectWrite allows custom fallback lists, where you can for example explicitly assign U+1F5A4 to any font you want, that supports it, including custom fonts that you can bundle with your application.
If you want more detailed answer, you'll need to show some sources excerpts that don't work for you.
I believe the high and low 16-bit words are well defined for surrogate pairs. You should be able to identify surrogate pairs by checking the range of values for each of the 16-bit words.
For the high word it should be in the range of 0xd800 to 0xdbff
For the low word it should be in the range of 0xdc00 to 0xdfff
If any two pair of "characters" meets this criteria, they are a surrogate pair.
See the wikipedia article on UTF-16 for more information.

Font layouting & rendering with cairo and freetype

I have a system that has only the freetype2 and cairo libraries available. What I want to achieve is:
getting the glyphs for a UTF-8 text
layouting the text, storing position information (by myself)
getting cairo paths for each glyph for rendering
Unfortunately the documentation doesn't really explain how it should be done, as they expect one to use a higher level library like Pango.
What I think could be right is: Create a scaled font with cairo_scaled_font_create and then retrieve the glyphs for the text using cairo_scaled_font_text_to_glyphs. cairo_glyph_extents then gives the extents for each glyph. But how can I then get things like kerning and the advance? Also, how can I then get paths for each font?
Are there some more resources on this topic? Are these functions the expected way to go?
Okay, so I found what's needed.
You first need to create a cairo_scaled_font_t which represents a font in a specific size. To do so, one can simply use cairo_get_scaled_font after setting a font, it creates a scaled font for the current settings in the context.
Next, you convert the input text using cairo_scaled_font_text_to_glyphs, this gives an array of glyphs and also clusters as output. The cluster mappings represent which part of the UTF-8 string belong to the corresponding glyphs in the glyph array.
To get the extents of glyphs, cairo_scaled_font_glyph_extents is used. It gives dimensions, advances and bearings of each glyph/set of glyphs.
Finally, the paths for glyphs can be put in the context using cairo_glyph_path. These paths can then be drawn as wished.
The following example converts an input string to glyphs, retrieves their extents and renders them:
const char* text = "Hello world";
int fontSize = 14;
cairo_font_face_t* fontFace = ...;
// get the scaled font object
cairo_set_font_face(cr, fontFace);
cairo_set_font_size(cr, fontSize);
auto scaled_face = cairo_get_scaled_font(cr);
// get glyphs for the text
cairo_glyph_t* glyphs = NULL;
int glyph_count;
cairo_text_cluster_t* clusters = NULL;
int cluster_count;
cairo_text_cluster_flags_t clusterflags;
auto stat = cairo_scaled_font_text_to_glyphs(scaled_face, 0, 0, text, strlen(text), &glyphs, &glyph_count, &clusters, &cluster_count,
&clusterflags);
// check if conversion was successful
if (stat == CAIRO_STATUS_SUCCESS) {
// text paints on bottom line
cairo_translate(cr, 0, fontSize);
// draw each cluster
int glyph_index = 0;
int byte_index = 0;
for (int i = 0; i < cluster_count; i++) {
cairo_text_cluster_t* cluster = &clusters[i];
cairo_glyph_t* clusterglyphs = &glyphs[glyph_index];
// get extents for the glyphs in the cluster
cairo_text_extents_t extents;
cairo_scaled_font_glyph_extents(scaled_face, clusterglyphs, cluster->num_glyphs, &extents);
// ... for later use
// put paths for current cluster to context
cairo_glyph_path(cr, clusterglyphs, cluster->num_glyphs);
// draw black text with green stroke
cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0);
cairo_fill_preserve(cr);
cairo_set_source_rgba(cr, 0, 1, 0, 1.0);
cairo_set_line_width(cr, 0.5);
cairo_stroke(cr);
// glyph/byte position
glyph_index += cluster->num_glyphs;
byte_index += cluster->num_bytes;
}
}
Those functions seem to be the best way, considering Cairo's text system. It just shows even more that Cairo isn't really meant for text. It won't be able to do kerning or paths really. Pango, I believe, would have its own complex code for doing those things.
For best advancement of Ghost, I would recommend porting Pango, since you (or someone else) will probably eventually want it anyway.

FreeType: sizing fonts based on cap height?

I am rendering text, and I need the font to have the cap height to a certain number of pixels. For example, in the sign below, I need to have the words SPEED and LIMIT to be the same height, in this case 45 px, so I set the font size to 45 (which I now understand wasn't such a good idea) and get bounding boxes (in red) different from what I should have had.
So I create the font object,
Text *t = (Text *)e;
cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
cairo_t *cr = cairo_create(s);
cairo_font_face_t *font = cairo_ft_font_face_create_for_ft_face(fonts[t->series], 0);
cairo_set_font_face(cr, font);
Set the font size to 45,
cairo_set_font_size(cr, 1.5 * t->size);
Then calculate the extents of the bounding box, which doesn't line up with the correct size of the text.
cairo_text_extents_t ext;
cairo_text_extents(cr, t->data, &ext);
t->geom->height = ext.height; // t->size;
t->geom->width = ext.width;
t->lsb = ext.x_bearing;
cairo_font_face_destroy(font);
cairo_destroy(cr);
cairo_surface_destroy(s);
I'm guessing I have to use FT_Set_Pixel_Sizes or something, but I don't exactly know how to use that.
EDIT: Is there a way to set the cap height using Cairo? If so, I'd rather use that.
Using #Jongware's comment, I added a scale field which calculates the scaling factor for this particular piece of text (it should be consistent for a certain cap height).
cairo_text_extents_t ext, xext;
cairo_text_extents(cr, t->data, &ext);
cairo_text_extents(cr, "X", &xext);
t->scale = t->size / xext.height;
t->geom->height = t->size;
t->geom->width = ext.width * t->scale;
t->lsb = ext.x_bearing * t->scale;
It works perfectly now.

Measuring text in WPF

Using WPF, what is the most efficient way to measure a large number of short strings? Specifically, I'd like to determine the display height of each string, given uniform formatting (same font, size, weight, etc.) and the maximum width the string may occupy?
The most low-level technique (and therefore giving the most scope for creative optimisations) is to use GlyphRuns.
It's not very well documented but I wrote up a little example here:
http://smellegantcode.wordpress.com/2008/07/03/glyphrun-and-so-forth/
The example works out the length of the string as a necessary step before rendering it.
In WPF:
Remember to call Measure() on the TextBlock before reading the DesiredSize property.
If the TextBlock was created on-the-fly, and not yet shown, you have to call Measure() first, like so:
MyTextBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
return new Size(MyTextBlock.DesiredSize.Width, MyTextBlock.DesiredSize.Height);
In Silverlight:
No need to measure.
return new Size(TextBlock.ActualWidth, TextBlock.ActualHeight);
The complete code looks like this:
public Size MeasureString(string s) {
if (string.IsNullOrEmpty(s)) {
return new Size(0, 0);
}
var TextBlock = new TextBlock() {
Text = s
};
#if SILVERLIGHT
return new Size(TextBlock.ActualWidth, TextBlock.ActualHeight);
#else
TextBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
return new Size(TextBlock.DesiredSize.Width, TextBlock.DesiredSize.Height);
#endif
}
It is very simple and done by FormattedText class!
Try it.
You can use the DesiredSize property on a rendered TextBox to get the height and width
using System.Windows.Threading;
...
Double TextWidth = 0;
Double TextHeight = 0;
...
MyTextBox.Text = "Words to measure size of";
this.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new DispatcherOperationCallback(delegate(Object state) {
var size = MyTextBox.DesiredSize;
this.TextWidth = size.Width;
this.TextHeight = size.Height;
return null;
}
) , null);
If you have a large number of strings it may be quicker to first pre-calualte the height and width of every indiviudal letter and symbol in a given font, and then do a calculation based on the string chars. This may not be 100% acurate due to kerning etc

Resources