Graphics.MeasureCharacterRanges giving wrong size calculations
up vote
6
down vote
favorite
I'm trying to render some text into a specific part of an image in a Web Forms app. The text will be user entered, so I want to vary the font size to make sure it fits within the bounding box.
I have code that was doing this fine on my proof-of-concept implementation, but I'm now trying it against the assets from the designer, which are larger, and I'm getting some odd results.
I'm running the size calculation as follows:
StringFormat fmt = new StringFormat();
fmt.Alignment = StringAlignment.Center;
fmt.LineAlignment = StringAlignment.Near;
fmt.FormatFlags = StringFormatFlags.NoClip;
fmt.Trimming = StringTrimming.None;
int size = __startingSize;
Font font = __fonts.GetFontBySize(size);
while (GetStringBounds(text, font, fmt).IsLargerThan(__textBoundingBox))
{
context.Trace.Write("MyHandler.ProcessRequest",
"Decrementing font size to " + size + ", as size is "
+ GetStringBounds(text, font, fmt).Size()
+ " and limit is " + __textBoundingBox.Size());
size--;
if (size < __minimumSize)
{
break;
}
font = __fonts.GetFontBySize(size);
}
context.Trace.Write("MyHandler.ProcessRequest", "Writing " + text + " in "
+ font.FontFamily.Name + " at " + font.SizeInPoints + "pt, size is "
+ GetStringBounds(text, font, fmt).Size()
+ " and limit is " + __textBoundingBox.Size());
I then use the following line to render the text onto an image I'm pulling from the filesystem:
g.DrawString(text, font, __brush, __textBoundingBox, fmt);
where:
__fonts
is aPrivateFontCollection
,
PrivateFontCollection.GetFontBySize
is an extension method that returns aFontFamily
RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64);
int __minimumSize = 8;
int __startingSize = 48;
Brush __brush = Brushes.White;
int size
starts out at 48 and decrements within that loop
Graphics g
hasSmoothingMode.AntiAlias
andTextRenderingHint.AntiAlias
set
context
is aSystem.Web.HttpContext
(this is an excerpt from theProcessRequest
method of anIHttpHandler
)
The other methods are:
private static RectangleF GetStringBounds(string text, Font font,
StringFormat fmt)
{
CharacterRange range = { new CharacterRange(0, text.Length) };
StringFormat myFormat = fmt.Clone() as StringFormat;
myFormat.SetMeasurableCharacterRanges(range);
using (Graphics g = Graphics.FromImage(new Bitmap(
(int) __textBoundingBox.Width - 1,
(int) __textBoundingBox.Height - 1)))
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
Region regions = g.MeasureCharacterRanges(text, font,
__textBoundingBox, myFormat);
return regions[0].GetBounds(g);
}
}
public static string Size(this RectangleF rect)
{
return rect.Width + "×" + rect.Height;
}
public static bool IsLargerThan(this RectangleF a, RectangleF b)
{
return (a.Width > b.Width) || (a.Height > b.Height);
}
Now I have two problems.
Firstly, the text sometimes insists on wrapping by inserting a line-break within a word, when it should just fail to fit and cause the while loop to decrement again. I can't see why it is that Graphics.MeasureCharacterRanges
thinks that this fits within the box when it shouldn't be word-wrapping within a word. This behaviour is exhibited irrespective of the character set used (I get it in Latin alphabet words, as well as other parts of the Unicode range, like Cyrillic, Greek, Georgian and Armenian). Is there some setting I should be using to force Graphics.MeasureCharacterRanges
only to be word-wrapping at whitespace characters (or hyphens)? This first problem is the same as post 2499067.
Secondly, in scaling up to the new image and font size, Graphics.MeasureCharacterRanges
is giving me heights that are wildly off. The RectangleF
I am drawing within corresponds to a visually apparent area of the image, so I can easily see when the text is being decremented more than is necessary. Yet when I pass it some text, the GetBounds
call is giving me a height that is almost double what it's actually taking.
Using trial and error to set the __minimumSize
to force an exit from the while loop, I can see that 24pt text fits within the bounding box, yet Graphics.MeasureCharacterRanges
is reporting that the height of that text, once rendered to the image, is 122px (when the bounding box is 64px tall and it fits within that box). Indeed, without forcing the matter, the while loop iterates to 18pt, at which point Graphics.MeasureCharacterRanges
returns a value that fits.
The trace log excerpt is as follows:
Decrementing font size to 24, as size is 193×122 and limit is 212×64
Decrementing font size to 23, as size is 191×117 and limit is 212×64
Decrementing font size to 22, as size is 200×75 and limit is 212×64
Decrementing font size to 21, as size is 192×71 and limit is 212×64
Decrementing font size to 20, as size is 198×68 and limit is 212×64
Decrementing font size to 19, as size is 185×65 and limit is 212×64
Writing VENNEGOOR of HESSELINK in DIN-Black at 18pt, size is 178×61 and limit is 212×64
So why is Graphics.MeasureCharacterRanges
giving me a wrong result? I could understand it being, say, the line height of the font if the loop stopped around 21pt (which would visually fit, if I screenshot the results and measure it in Paint.Net), but it's going far further than it should be doing because, frankly, it's returning the wrong damn results.
gdi+ system.drawing drawstring
add a comment |
up vote
6
down vote
favorite
I'm trying to render some text into a specific part of an image in a Web Forms app. The text will be user entered, so I want to vary the font size to make sure it fits within the bounding box.
I have code that was doing this fine on my proof-of-concept implementation, but I'm now trying it against the assets from the designer, which are larger, and I'm getting some odd results.
I'm running the size calculation as follows:
StringFormat fmt = new StringFormat();
fmt.Alignment = StringAlignment.Center;
fmt.LineAlignment = StringAlignment.Near;
fmt.FormatFlags = StringFormatFlags.NoClip;
fmt.Trimming = StringTrimming.None;
int size = __startingSize;
Font font = __fonts.GetFontBySize(size);
while (GetStringBounds(text, font, fmt).IsLargerThan(__textBoundingBox))
{
context.Trace.Write("MyHandler.ProcessRequest",
"Decrementing font size to " + size + ", as size is "
+ GetStringBounds(text, font, fmt).Size()
+ " and limit is " + __textBoundingBox.Size());
size--;
if (size < __minimumSize)
{
break;
}
font = __fonts.GetFontBySize(size);
}
context.Trace.Write("MyHandler.ProcessRequest", "Writing " + text + " in "
+ font.FontFamily.Name + " at " + font.SizeInPoints + "pt, size is "
+ GetStringBounds(text, font, fmt).Size()
+ " and limit is " + __textBoundingBox.Size());
I then use the following line to render the text onto an image I'm pulling from the filesystem:
g.DrawString(text, font, __brush, __textBoundingBox, fmt);
where:
__fonts
is aPrivateFontCollection
,
PrivateFontCollection.GetFontBySize
is an extension method that returns aFontFamily
RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64);
int __minimumSize = 8;
int __startingSize = 48;
Brush __brush = Brushes.White;
int size
starts out at 48 and decrements within that loop
Graphics g
hasSmoothingMode.AntiAlias
andTextRenderingHint.AntiAlias
set
context
is aSystem.Web.HttpContext
(this is an excerpt from theProcessRequest
method of anIHttpHandler
)
The other methods are:
private static RectangleF GetStringBounds(string text, Font font,
StringFormat fmt)
{
CharacterRange range = { new CharacterRange(0, text.Length) };
StringFormat myFormat = fmt.Clone() as StringFormat;
myFormat.SetMeasurableCharacterRanges(range);
using (Graphics g = Graphics.FromImage(new Bitmap(
(int) __textBoundingBox.Width - 1,
(int) __textBoundingBox.Height - 1)))
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
Region regions = g.MeasureCharacterRanges(text, font,
__textBoundingBox, myFormat);
return regions[0].GetBounds(g);
}
}
public static string Size(this RectangleF rect)
{
return rect.Width + "×" + rect.Height;
}
public static bool IsLargerThan(this RectangleF a, RectangleF b)
{
return (a.Width > b.Width) || (a.Height > b.Height);
}
Now I have two problems.
Firstly, the text sometimes insists on wrapping by inserting a line-break within a word, when it should just fail to fit and cause the while loop to decrement again. I can't see why it is that Graphics.MeasureCharacterRanges
thinks that this fits within the box when it shouldn't be word-wrapping within a word. This behaviour is exhibited irrespective of the character set used (I get it in Latin alphabet words, as well as other parts of the Unicode range, like Cyrillic, Greek, Georgian and Armenian). Is there some setting I should be using to force Graphics.MeasureCharacterRanges
only to be word-wrapping at whitespace characters (or hyphens)? This first problem is the same as post 2499067.
Secondly, in scaling up to the new image and font size, Graphics.MeasureCharacterRanges
is giving me heights that are wildly off. The RectangleF
I am drawing within corresponds to a visually apparent area of the image, so I can easily see when the text is being decremented more than is necessary. Yet when I pass it some text, the GetBounds
call is giving me a height that is almost double what it's actually taking.
Using trial and error to set the __minimumSize
to force an exit from the while loop, I can see that 24pt text fits within the bounding box, yet Graphics.MeasureCharacterRanges
is reporting that the height of that text, once rendered to the image, is 122px (when the bounding box is 64px tall and it fits within that box). Indeed, without forcing the matter, the while loop iterates to 18pt, at which point Graphics.MeasureCharacterRanges
returns a value that fits.
The trace log excerpt is as follows:
Decrementing font size to 24, as size is 193×122 and limit is 212×64
Decrementing font size to 23, as size is 191×117 and limit is 212×64
Decrementing font size to 22, as size is 200×75 and limit is 212×64
Decrementing font size to 21, as size is 192×71 and limit is 212×64
Decrementing font size to 20, as size is 198×68 and limit is 212×64
Decrementing font size to 19, as size is 185×65 and limit is 212×64
Writing VENNEGOOR of HESSELINK in DIN-Black at 18pt, size is 178×61 and limit is 212×64
So why is Graphics.MeasureCharacterRanges
giving me a wrong result? I could understand it being, say, the line height of the font if the loop stopped around 21pt (which would visually fit, if I screenshot the results and measure it in Paint.Net), but it's going far further than it should be doing because, frankly, it's returning the wrong damn results.
gdi+ system.drawing drawstring
+1 for one of the most documented questions I've seen in a while!
– SouthShoreAK
Apr 18 '12 at 16:56
add a comment |
up vote
6
down vote
favorite
up vote
6
down vote
favorite
I'm trying to render some text into a specific part of an image in a Web Forms app. The text will be user entered, so I want to vary the font size to make sure it fits within the bounding box.
I have code that was doing this fine on my proof-of-concept implementation, but I'm now trying it against the assets from the designer, which are larger, and I'm getting some odd results.
I'm running the size calculation as follows:
StringFormat fmt = new StringFormat();
fmt.Alignment = StringAlignment.Center;
fmt.LineAlignment = StringAlignment.Near;
fmt.FormatFlags = StringFormatFlags.NoClip;
fmt.Trimming = StringTrimming.None;
int size = __startingSize;
Font font = __fonts.GetFontBySize(size);
while (GetStringBounds(text, font, fmt).IsLargerThan(__textBoundingBox))
{
context.Trace.Write("MyHandler.ProcessRequest",
"Decrementing font size to " + size + ", as size is "
+ GetStringBounds(text, font, fmt).Size()
+ " and limit is " + __textBoundingBox.Size());
size--;
if (size < __minimumSize)
{
break;
}
font = __fonts.GetFontBySize(size);
}
context.Trace.Write("MyHandler.ProcessRequest", "Writing " + text + " in "
+ font.FontFamily.Name + " at " + font.SizeInPoints + "pt, size is "
+ GetStringBounds(text, font, fmt).Size()
+ " and limit is " + __textBoundingBox.Size());
I then use the following line to render the text onto an image I'm pulling from the filesystem:
g.DrawString(text, font, __brush, __textBoundingBox, fmt);
where:
__fonts
is aPrivateFontCollection
,
PrivateFontCollection.GetFontBySize
is an extension method that returns aFontFamily
RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64);
int __minimumSize = 8;
int __startingSize = 48;
Brush __brush = Brushes.White;
int size
starts out at 48 and decrements within that loop
Graphics g
hasSmoothingMode.AntiAlias
andTextRenderingHint.AntiAlias
set
context
is aSystem.Web.HttpContext
(this is an excerpt from theProcessRequest
method of anIHttpHandler
)
The other methods are:
private static RectangleF GetStringBounds(string text, Font font,
StringFormat fmt)
{
CharacterRange range = { new CharacterRange(0, text.Length) };
StringFormat myFormat = fmt.Clone() as StringFormat;
myFormat.SetMeasurableCharacterRanges(range);
using (Graphics g = Graphics.FromImage(new Bitmap(
(int) __textBoundingBox.Width - 1,
(int) __textBoundingBox.Height - 1)))
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
Region regions = g.MeasureCharacterRanges(text, font,
__textBoundingBox, myFormat);
return regions[0].GetBounds(g);
}
}
public static string Size(this RectangleF rect)
{
return rect.Width + "×" + rect.Height;
}
public static bool IsLargerThan(this RectangleF a, RectangleF b)
{
return (a.Width > b.Width) || (a.Height > b.Height);
}
Now I have two problems.
Firstly, the text sometimes insists on wrapping by inserting a line-break within a word, when it should just fail to fit and cause the while loop to decrement again. I can't see why it is that Graphics.MeasureCharacterRanges
thinks that this fits within the box when it shouldn't be word-wrapping within a word. This behaviour is exhibited irrespective of the character set used (I get it in Latin alphabet words, as well as other parts of the Unicode range, like Cyrillic, Greek, Georgian and Armenian). Is there some setting I should be using to force Graphics.MeasureCharacterRanges
only to be word-wrapping at whitespace characters (or hyphens)? This first problem is the same as post 2499067.
Secondly, in scaling up to the new image and font size, Graphics.MeasureCharacterRanges
is giving me heights that are wildly off. The RectangleF
I am drawing within corresponds to a visually apparent area of the image, so I can easily see when the text is being decremented more than is necessary. Yet when I pass it some text, the GetBounds
call is giving me a height that is almost double what it's actually taking.
Using trial and error to set the __minimumSize
to force an exit from the while loop, I can see that 24pt text fits within the bounding box, yet Graphics.MeasureCharacterRanges
is reporting that the height of that text, once rendered to the image, is 122px (when the bounding box is 64px tall and it fits within that box). Indeed, without forcing the matter, the while loop iterates to 18pt, at which point Graphics.MeasureCharacterRanges
returns a value that fits.
The trace log excerpt is as follows:
Decrementing font size to 24, as size is 193×122 and limit is 212×64
Decrementing font size to 23, as size is 191×117 and limit is 212×64
Decrementing font size to 22, as size is 200×75 and limit is 212×64
Decrementing font size to 21, as size is 192×71 and limit is 212×64
Decrementing font size to 20, as size is 198×68 and limit is 212×64
Decrementing font size to 19, as size is 185×65 and limit is 212×64
Writing VENNEGOOR of HESSELINK in DIN-Black at 18pt, size is 178×61 and limit is 212×64
So why is Graphics.MeasureCharacterRanges
giving me a wrong result? I could understand it being, say, the line height of the font if the loop stopped around 21pt (which would visually fit, if I screenshot the results and measure it in Paint.Net), but it's going far further than it should be doing because, frankly, it's returning the wrong damn results.
gdi+ system.drawing drawstring
I'm trying to render some text into a specific part of an image in a Web Forms app. The text will be user entered, so I want to vary the font size to make sure it fits within the bounding box.
I have code that was doing this fine on my proof-of-concept implementation, but I'm now trying it against the assets from the designer, which are larger, and I'm getting some odd results.
I'm running the size calculation as follows:
StringFormat fmt = new StringFormat();
fmt.Alignment = StringAlignment.Center;
fmt.LineAlignment = StringAlignment.Near;
fmt.FormatFlags = StringFormatFlags.NoClip;
fmt.Trimming = StringTrimming.None;
int size = __startingSize;
Font font = __fonts.GetFontBySize(size);
while (GetStringBounds(text, font, fmt).IsLargerThan(__textBoundingBox))
{
context.Trace.Write("MyHandler.ProcessRequest",
"Decrementing font size to " + size + ", as size is "
+ GetStringBounds(text, font, fmt).Size()
+ " and limit is " + __textBoundingBox.Size());
size--;
if (size < __minimumSize)
{
break;
}
font = __fonts.GetFontBySize(size);
}
context.Trace.Write("MyHandler.ProcessRequest", "Writing " + text + " in "
+ font.FontFamily.Name + " at " + font.SizeInPoints + "pt, size is "
+ GetStringBounds(text, font, fmt).Size()
+ " and limit is " + __textBoundingBox.Size());
I then use the following line to render the text onto an image I'm pulling from the filesystem:
g.DrawString(text, font, __brush, __textBoundingBox, fmt);
where:
__fonts
is aPrivateFontCollection
,
PrivateFontCollection.GetFontBySize
is an extension method that returns aFontFamily
RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64);
int __minimumSize = 8;
int __startingSize = 48;
Brush __brush = Brushes.White;
int size
starts out at 48 and decrements within that loop
Graphics g
hasSmoothingMode.AntiAlias
andTextRenderingHint.AntiAlias
set
context
is aSystem.Web.HttpContext
(this is an excerpt from theProcessRequest
method of anIHttpHandler
)
The other methods are:
private static RectangleF GetStringBounds(string text, Font font,
StringFormat fmt)
{
CharacterRange range = { new CharacterRange(0, text.Length) };
StringFormat myFormat = fmt.Clone() as StringFormat;
myFormat.SetMeasurableCharacterRanges(range);
using (Graphics g = Graphics.FromImage(new Bitmap(
(int) __textBoundingBox.Width - 1,
(int) __textBoundingBox.Height - 1)))
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
Region regions = g.MeasureCharacterRanges(text, font,
__textBoundingBox, myFormat);
return regions[0].GetBounds(g);
}
}
public static string Size(this RectangleF rect)
{
return rect.Width + "×" + rect.Height;
}
public static bool IsLargerThan(this RectangleF a, RectangleF b)
{
return (a.Width > b.Width) || (a.Height > b.Height);
}
Now I have two problems.
Firstly, the text sometimes insists on wrapping by inserting a line-break within a word, when it should just fail to fit and cause the while loop to decrement again. I can't see why it is that Graphics.MeasureCharacterRanges
thinks that this fits within the box when it shouldn't be word-wrapping within a word. This behaviour is exhibited irrespective of the character set used (I get it in Latin alphabet words, as well as other parts of the Unicode range, like Cyrillic, Greek, Georgian and Armenian). Is there some setting I should be using to force Graphics.MeasureCharacterRanges
only to be word-wrapping at whitespace characters (or hyphens)? This first problem is the same as post 2499067.
Secondly, in scaling up to the new image and font size, Graphics.MeasureCharacterRanges
is giving me heights that are wildly off. The RectangleF
I am drawing within corresponds to a visually apparent area of the image, so I can easily see when the text is being decremented more than is necessary. Yet when I pass it some text, the GetBounds
call is giving me a height that is almost double what it's actually taking.
Using trial and error to set the __minimumSize
to force an exit from the while loop, I can see that 24pt text fits within the bounding box, yet Graphics.MeasureCharacterRanges
is reporting that the height of that text, once rendered to the image, is 122px (when the bounding box is 64px tall and it fits within that box). Indeed, without forcing the matter, the while loop iterates to 18pt, at which point Graphics.MeasureCharacterRanges
returns a value that fits.
The trace log excerpt is as follows:
Decrementing font size to 24, as size is 193×122 and limit is 212×64
Decrementing font size to 23, as size is 191×117 and limit is 212×64
Decrementing font size to 22, as size is 200×75 and limit is 212×64
Decrementing font size to 21, as size is 192×71 and limit is 212×64
Decrementing font size to 20, as size is 198×68 and limit is 212×64
Decrementing font size to 19, as size is 185×65 and limit is 212×64
Writing VENNEGOOR of HESSELINK in DIN-Black at 18pt, size is 178×61 and limit is 212×64
So why is Graphics.MeasureCharacterRanges
giving me a wrong result? I could understand it being, say, the line height of the font if the loop stopped around 21pt (which would visually fit, if I screenshot the results and measure it in Paint.Net), but it's going far further than it should be doing because, frankly, it's returning the wrong damn results.
gdi+ system.drawing drawstring
gdi+ system.drawing drawstring
edited Jul 12 at 22:10
Drew Noakes
182k112515600
182k112515600
asked Apr 21 '10 at 16:53
Owen Blacker
3,08812566
3,08812566
+1 for one of the most documented questions I've seen in a while!
– SouthShoreAK
Apr 18 '12 at 16:56
add a comment |
+1 for one of the most documented questions I've seen in a while!
– SouthShoreAK
Apr 18 '12 at 16:56
+1 for one of the most documented questions I've seen in a while!
– SouthShoreAK
Apr 18 '12 at 16:56
+1 for one of the most documented questions I've seen in a while!
– SouthShoreAK
Apr 18 '12 at 16:56
add a comment |
5 Answers
5
active
oldest
votes
up vote
1
down vote
I have a similar problem. I want to know how big the text I'm drawing is going to be, and where it's going to appear, EXACTLY. I haven't had the line-break problem, so I don't think I can help you there. I had the same problems you had with all the various measuring techniques available, including ending up with MeasureCharacterRanges, which worked okay for the left and right, but not at all for the height and top. (Playing with the baseline can work well for some rare applications though.)
I've ended up with a very inelegant, inefficient, but working solution, at least for my use case. I draw the text on a bitmap, check the bits to see where they ended up, and that's my range. Since I'm mostly drawing small fonts and short strings, it's been fast enough for me (especially with the memoization I added). Maybe this won't be exactly what you need, but maybe it can lead you down the right track anyway.
Note it requires compiling the project to allow unsafe code at the moment, as I'm trying to squeeze out every bit of efficiency from it, but that constraint could be removed if you wanted to. Also, it's not as thread safe as it could be right now, you could easily add that if you needed it.
Dictionary<Tuple<string, Font, Brush>, Rectangle> cachedTextBounds = new Dictionary<Tuple<string, Font, Brush>, Rectangle>();
/// <summary>
/// Determines bounds of some text by actually drawing the text to a bitmap and
/// reading the bits to see where it ended up. Bounds assume you draw at 0, 0. If
/// drawing elsewhere, you can easily offset the resulting rectangle appropriately.
/// </summary>
/// <param name="text">The text to be drawn</param>
/// <param name="font">The font to use when drawing the text</param>
/// <param name="brush">The brush to be used when drawing the text</param>
/// <returns>The bounding rectangle of the rendered text</returns>
private unsafe Rectangle RenderedTextBounds(string text, Font font, Brush brush) {
// First check memoization
Tuple<string, Font, Brush> t = new Tuple<string, Font, Brush>(text, font, brush);
try {
return cachedTextBounds[t];
}
catch(KeyNotFoundException) {
// not cached
}
// Draw the string on a bitmap
Rectangle bounds = new Rectangle();
Size approxSize = TextRenderer.MeasureText(text, font);
using(Bitmap bitmap = new Bitmap((int)(approxSize.Width*1.5), (int)(approxSize.Height*1.5))) {
using(Graphics g = Graphics.FromImage(bitmap))
g.DrawString(text, font, brush, 0, 0);
// Unsafe LockBits code takes a bit over 10% of time compared to safe GetPixel code
BitmapData bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* row = (byte*)bd.Scan0;
// Find left, looking for first bit that has a non-zero alpha channel, so it's not clear
for(int x = 0; x < bitmap.Width; x++)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.X = x;
goto foundX;
}
foundX:
// Right
for(int x = bitmap.Width - 1; x >= 0; x--)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Width = x - bounds.X + 1;
goto foundWidth;
}
foundWidth:
// Top
for(int y = 0; y < bitmap.Height; y++)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Y = y;
goto foundY;
}
foundY:
// Bottom
for(int y = bitmap.Height - 1; y >= 0; y--)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Height = y - bounds.Y + 1;
goto foundHeight;
}
foundHeight:
bitmap.UnlockBits(bd);
}
cachedTextBounds[t] = bounds;
return bounds;
}
Nice work! I'll have to give that a try and see if that solves my problem. Though I'm somewhat loathe to have to rely onunsafe
code. How quick do you find that code? The circumstance I'm using it in checks whether the text is too big for a fixed-size bounding box and decreases the font size within awhile
loop (checkingwhile too big or size gt hard-floor-limit
), so I don't want to perform an expensive operation in thatwhile
loop…
– Owen Blacker
Aug 16 '11 at 12:03
Speed depends heavily on font size. It allocates bitmap, renders it, then searches for boundaries. Smaller fonts are a lot faster, and you lose with larger fonts something like O(size^2) I think. May want to guess small size and work up to a size that's too big instead of the other way around. You could use MeasureCharacterRanges as first guess for lower bound, since it always seems to end up too small a guess. There's a lot of room for cleverness searching for the right size, binary search with clever guessing etc. I don't need any of that for my purpose, so I haven't played with it.
– user12861
Aug 16 '11 at 13:14
As to unsafe, you can do it without that but speed takes a hit. As commented this is almost 90% faster than using GetPixel, but you can still do an in between method using LockBits without unsafe code. I didn't try that as I was ok with unsafe code and wanted to squeeze out a bit of easily obtained performance. There are lots of ways to try to squeeze out performance depending on how exactly you're using this stuff, but they're all complicated. This whole thing is kind of nasty I fully realize. I would think there would be a better way, but my search online was as unproductive as yours.
– user12861
Aug 16 '11 at 13:17
That's really helpful, user12861; thank you. Hopefully, I'll get a chance to give this a try sometime soon.
– Owen Blacker
Aug 25 '11 at 10:48
add a comment |
up vote
0
down vote
Could you try removing the following line?
fmt.FormatFlags = StringFormatFlags.NoClip;
Overhanging parts of glyphs, and
unwrapped text reaching outside the
formatting rectangle are allowed to
show. By default all text and glyph
parts reaching outside the formatting
rectangle are clipped.
That's the best I can come up with for this :(
Thanks for posting an answer. I did look at this, having thought it might be the problem, but the reported height is almost double the actual height, so I don'think it can be that. It seems that the difference StringFormatFlags.NoClip makes is that if the bowl of a letter P (for example) just pokes outside the bounding box then it's allowed to render, rather than being clipped. That doesn't seem to be the problem I'm experiencing. But thanks :o)
– Owen Blacker
Apr 29 '10 at 8:51
add a comment |
up vote
0
down vote
I also had some problems with the MeasureCharacterRanges
method. It was giving me inconsistent sizes for the same string and even the same Graphics
object. Then I discovered that it depends on the value of the layoutRect
parametr - I can't see why, in my opinion it's a bug in the .NET code.
For example if layoutRect
was completely empty (all values set to zero), I got correct values for the string "a" - the size was {Width=8.898438, Height=18.10938}
using 12pt Ms Sans Serif font.
However, when I set the value of the 'X' property of the rectangle to a non-integer number (like 1.2), it gave me {Width=9, Height=19}
.
So I really think there is a bug when you use a layout rectangle with non-integer X coordinate.
Interesting — looks like it's always rounding your size up. Unfortunately, my layoutRect is always integral — it's defined as private static readonly RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64); and its value never changes (obviously, as it's marked readonly). It's definitely a bug in the .Net code, but it doesn't look like your bug and my bug are the same one.
– Owen Blacker
Aug 9 '10 at 11:31
add a comment |
up vote
0
down vote
Ok so 4 years late but this question EXACTLY matched my symptoms and I've actually worked out the cause.
There is most certainly a bug in MeasureString AND MeasureCharacterRanges.
The simple answer is:
Make sure you divide your width restriction (int width in MeasureString or the Size.Width property of the boundingRect in MeasureCharacterRanges) by 0.72. When you get your results back multiply each dimension by 0.72 to get the REAL result
int measureWidth = Convert.ToInt32((float)width/0.72);
SizeF measureSize = gfx.MeasureString(text, font, measureWidth, format);
float actualHeight = measureSize.Height * (float)0.72;
or
float measureWidth = width/0.72;
Region regions = gfx.MeasureCharacterRanges(text, font, new RectangleF(0,0,measureWidth, format);
float actualHeight = 0;
if(regions.Length>0)
{
actualHeight = regions[0].GetBounds(gfx).Size.Height * (float)0.72;
}
The explanation (that I can figure out) is that something to do with the context is triggering a conversion in the Measure methods (that doesn't trigger in the DrawString method) for inch->point (*72/100). When you pass in the ACTUAL width limitation it is adjusting this value so the MEASURED width limitation is, in effect, shorter than it should be. Your text then wraps earlier than it is supposed to and so you get a longer height result than expected. Unfortunately the conversion applies to the actual height result as well so it's a good idea to 'unconvert' that value too.
On a REALLY annoying side note, I have absolutely NO idea why MeasureString takes an int for width. If you set your graphics context unit of measurement to inches then (due to the int) you can only set your width limitation to 1 inch, or 2 inches, etc. Completely ridiculous!
– Red Nightingale
Nov 16 '14 at 1:07
add a comment |
up vote
0
down vote
To convert from points to dpi as in screen resolution you need to divide by 72 and multiply by DPI, for example:
graphics.DpiY * text.Width / 72
Red Nightengale was really close, because graphics.DpiY is usually 96 for screen resolutions.
That's not an answer to the question as a whole, though. Could you expand this into an answer — including any of Red Nightingale's answer that you need to duplicate? Alternatively, post this as a comment on Red Nightingale's answer? Thanks!
– Owen Blacker
Nov 12 at 7:25
add a comment |
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
I have a similar problem. I want to know how big the text I'm drawing is going to be, and where it's going to appear, EXACTLY. I haven't had the line-break problem, so I don't think I can help you there. I had the same problems you had with all the various measuring techniques available, including ending up with MeasureCharacterRanges, which worked okay for the left and right, but not at all for the height and top. (Playing with the baseline can work well for some rare applications though.)
I've ended up with a very inelegant, inefficient, but working solution, at least for my use case. I draw the text on a bitmap, check the bits to see where they ended up, and that's my range. Since I'm mostly drawing small fonts and short strings, it's been fast enough for me (especially with the memoization I added). Maybe this won't be exactly what you need, but maybe it can lead you down the right track anyway.
Note it requires compiling the project to allow unsafe code at the moment, as I'm trying to squeeze out every bit of efficiency from it, but that constraint could be removed if you wanted to. Also, it's not as thread safe as it could be right now, you could easily add that if you needed it.
Dictionary<Tuple<string, Font, Brush>, Rectangle> cachedTextBounds = new Dictionary<Tuple<string, Font, Brush>, Rectangle>();
/// <summary>
/// Determines bounds of some text by actually drawing the text to a bitmap and
/// reading the bits to see where it ended up. Bounds assume you draw at 0, 0. If
/// drawing elsewhere, you can easily offset the resulting rectangle appropriately.
/// </summary>
/// <param name="text">The text to be drawn</param>
/// <param name="font">The font to use when drawing the text</param>
/// <param name="brush">The brush to be used when drawing the text</param>
/// <returns>The bounding rectangle of the rendered text</returns>
private unsafe Rectangle RenderedTextBounds(string text, Font font, Brush brush) {
// First check memoization
Tuple<string, Font, Brush> t = new Tuple<string, Font, Brush>(text, font, brush);
try {
return cachedTextBounds[t];
}
catch(KeyNotFoundException) {
// not cached
}
// Draw the string on a bitmap
Rectangle bounds = new Rectangle();
Size approxSize = TextRenderer.MeasureText(text, font);
using(Bitmap bitmap = new Bitmap((int)(approxSize.Width*1.5), (int)(approxSize.Height*1.5))) {
using(Graphics g = Graphics.FromImage(bitmap))
g.DrawString(text, font, brush, 0, 0);
// Unsafe LockBits code takes a bit over 10% of time compared to safe GetPixel code
BitmapData bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* row = (byte*)bd.Scan0;
// Find left, looking for first bit that has a non-zero alpha channel, so it's not clear
for(int x = 0; x < bitmap.Width; x++)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.X = x;
goto foundX;
}
foundX:
// Right
for(int x = bitmap.Width - 1; x >= 0; x--)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Width = x - bounds.X + 1;
goto foundWidth;
}
foundWidth:
// Top
for(int y = 0; y < bitmap.Height; y++)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Y = y;
goto foundY;
}
foundY:
// Bottom
for(int y = bitmap.Height - 1; y >= 0; y--)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Height = y - bounds.Y + 1;
goto foundHeight;
}
foundHeight:
bitmap.UnlockBits(bd);
}
cachedTextBounds[t] = bounds;
return bounds;
}
Nice work! I'll have to give that a try and see if that solves my problem. Though I'm somewhat loathe to have to rely onunsafe
code. How quick do you find that code? The circumstance I'm using it in checks whether the text is too big for a fixed-size bounding box and decreases the font size within awhile
loop (checkingwhile too big or size gt hard-floor-limit
), so I don't want to perform an expensive operation in thatwhile
loop…
– Owen Blacker
Aug 16 '11 at 12:03
Speed depends heavily on font size. It allocates bitmap, renders it, then searches for boundaries. Smaller fonts are a lot faster, and you lose with larger fonts something like O(size^2) I think. May want to guess small size and work up to a size that's too big instead of the other way around. You could use MeasureCharacterRanges as first guess for lower bound, since it always seems to end up too small a guess. There's a lot of room for cleverness searching for the right size, binary search with clever guessing etc. I don't need any of that for my purpose, so I haven't played with it.
– user12861
Aug 16 '11 at 13:14
As to unsafe, you can do it without that but speed takes a hit. As commented this is almost 90% faster than using GetPixel, but you can still do an in between method using LockBits without unsafe code. I didn't try that as I was ok with unsafe code and wanted to squeeze out a bit of easily obtained performance. There are lots of ways to try to squeeze out performance depending on how exactly you're using this stuff, but they're all complicated. This whole thing is kind of nasty I fully realize. I would think there would be a better way, but my search online was as unproductive as yours.
– user12861
Aug 16 '11 at 13:17
That's really helpful, user12861; thank you. Hopefully, I'll get a chance to give this a try sometime soon.
– Owen Blacker
Aug 25 '11 at 10:48
add a comment |
up vote
1
down vote
I have a similar problem. I want to know how big the text I'm drawing is going to be, and where it's going to appear, EXACTLY. I haven't had the line-break problem, so I don't think I can help you there. I had the same problems you had with all the various measuring techniques available, including ending up with MeasureCharacterRanges, which worked okay for the left and right, but not at all for the height and top. (Playing with the baseline can work well for some rare applications though.)
I've ended up with a very inelegant, inefficient, but working solution, at least for my use case. I draw the text on a bitmap, check the bits to see where they ended up, and that's my range. Since I'm mostly drawing small fonts and short strings, it's been fast enough for me (especially with the memoization I added). Maybe this won't be exactly what you need, but maybe it can lead you down the right track anyway.
Note it requires compiling the project to allow unsafe code at the moment, as I'm trying to squeeze out every bit of efficiency from it, but that constraint could be removed if you wanted to. Also, it's not as thread safe as it could be right now, you could easily add that if you needed it.
Dictionary<Tuple<string, Font, Brush>, Rectangle> cachedTextBounds = new Dictionary<Tuple<string, Font, Brush>, Rectangle>();
/// <summary>
/// Determines bounds of some text by actually drawing the text to a bitmap and
/// reading the bits to see where it ended up. Bounds assume you draw at 0, 0. If
/// drawing elsewhere, you can easily offset the resulting rectangle appropriately.
/// </summary>
/// <param name="text">The text to be drawn</param>
/// <param name="font">The font to use when drawing the text</param>
/// <param name="brush">The brush to be used when drawing the text</param>
/// <returns>The bounding rectangle of the rendered text</returns>
private unsafe Rectangle RenderedTextBounds(string text, Font font, Brush brush) {
// First check memoization
Tuple<string, Font, Brush> t = new Tuple<string, Font, Brush>(text, font, brush);
try {
return cachedTextBounds[t];
}
catch(KeyNotFoundException) {
// not cached
}
// Draw the string on a bitmap
Rectangle bounds = new Rectangle();
Size approxSize = TextRenderer.MeasureText(text, font);
using(Bitmap bitmap = new Bitmap((int)(approxSize.Width*1.5), (int)(approxSize.Height*1.5))) {
using(Graphics g = Graphics.FromImage(bitmap))
g.DrawString(text, font, brush, 0, 0);
// Unsafe LockBits code takes a bit over 10% of time compared to safe GetPixel code
BitmapData bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* row = (byte*)bd.Scan0;
// Find left, looking for first bit that has a non-zero alpha channel, so it's not clear
for(int x = 0; x < bitmap.Width; x++)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.X = x;
goto foundX;
}
foundX:
// Right
for(int x = bitmap.Width - 1; x >= 0; x--)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Width = x - bounds.X + 1;
goto foundWidth;
}
foundWidth:
// Top
for(int y = 0; y < bitmap.Height; y++)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Y = y;
goto foundY;
}
foundY:
// Bottom
for(int y = bitmap.Height - 1; y >= 0; y--)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Height = y - bounds.Y + 1;
goto foundHeight;
}
foundHeight:
bitmap.UnlockBits(bd);
}
cachedTextBounds[t] = bounds;
return bounds;
}
Nice work! I'll have to give that a try and see if that solves my problem. Though I'm somewhat loathe to have to rely onunsafe
code. How quick do you find that code? The circumstance I'm using it in checks whether the text is too big for a fixed-size bounding box and decreases the font size within awhile
loop (checkingwhile too big or size gt hard-floor-limit
), so I don't want to perform an expensive operation in thatwhile
loop…
– Owen Blacker
Aug 16 '11 at 12:03
Speed depends heavily on font size. It allocates bitmap, renders it, then searches for boundaries. Smaller fonts are a lot faster, and you lose with larger fonts something like O(size^2) I think. May want to guess small size and work up to a size that's too big instead of the other way around. You could use MeasureCharacterRanges as first guess for lower bound, since it always seems to end up too small a guess. There's a lot of room for cleverness searching for the right size, binary search with clever guessing etc. I don't need any of that for my purpose, so I haven't played with it.
– user12861
Aug 16 '11 at 13:14
As to unsafe, you can do it without that but speed takes a hit. As commented this is almost 90% faster than using GetPixel, but you can still do an in between method using LockBits without unsafe code. I didn't try that as I was ok with unsafe code and wanted to squeeze out a bit of easily obtained performance. There are lots of ways to try to squeeze out performance depending on how exactly you're using this stuff, but they're all complicated. This whole thing is kind of nasty I fully realize. I would think there would be a better way, but my search online was as unproductive as yours.
– user12861
Aug 16 '11 at 13:17
That's really helpful, user12861; thank you. Hopefully, I'll get a chance to give this a try sometime soon.
– Owen Blacker
Aug 25 '11 at 10:48
add a comment |
up vote
1
down vote
up vote
1
down vote
I have a similar problem. I want to know how big the text I'm drawing is going to be, and where it's going to appear, EXACTLY. I haven't had the line-break problem, so I don't think I can help you there. I had the same problems you had with all the various measuring techniques available, including ending up with MeasureCharacterRanges, which worked okay for the left and right, but not at all for the height and top. (Playing with the baseline can work well for some rare applications though.)
I've ended up with a very inelegant, inefficient, but working solution, at least for my use case. I draw the text on a bitmap, check the bits to see where they ended up, and that's my range. Since I'm mostly drawing small fonts and short strings, it's been fast enough for me (especially with the memoization I added). Maybe this won't be exactly what you need, but maybe it can lead you down the right track anyway.
Note it requires compiling the project to allow unsafe code at the moment, as I'm trying to squeeze out every bit of efficiency from it, but that constraint could be removed if you wanted to. Also, it's not as thread safe as it could be right now, you could easily add that if you needed it.
Dictionary<Tuple<string, Font, Brush>, Rectangle> cachedTextBounds = new Dictionary<Tuple<string, Font, Brush>, Rectangle>();
/// <summary>
/// Determines bounds of some text by actually drawing the text to a bitmap and
/// reading the bits to see where it ended up. Bounds assume you draw at 0, 0. If
/// drawing elsewhere, you can easily offset the resulting rectangle appropriately.
/// </summary>
/// <param name="text">The text to be drawn</param>
/// <param name="font">The font to use when drawing the text</param>
/// <param name="brush">The brush to be used when drawing the text</param>
/// <returns>The bounding rectangle of the rendered text</returns>
private unsafe Rectangle RenderedTextBounds(string text, Font font, Brush brush) {
// First check memoization
Tuple<string, Font, Brush> t = new Tuple<string, Font, Brush>(text, font, brush);
try {
return cachedTextBounds[t];
}
catch(KeyNotFoundException) {
// not cached
}
// Draw the string on a bitmap
Rectangle bounds = new Rectangle();
Size approxSize = TextRenderer.MeasureText(text, font);
using(Bitmap bitmap = new Bitmap((int)(approxSize.Width*1.5), (int)(approxSize.Height*1.5))) {
using(Graphics g = Graphics.FromImage(bitmap))
g.DrawString(text, font, brush, 0, 0);
// Unsafe LockBits code takes a bit over 10% of time compared to safe GetPixel code
BitmapData bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* row = (byte*)bd.Scan0;
// Find left, looking for first bit that has a non-zero alpha channel, so it's not clear
for(int x = 0; x < bitmap.Width; x++)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.X = x;
goto foundX;
}
foundX:
// Right
for(int x = bitmap.Width - 1; x >= 0; x--)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Width = x - bounds.X + 1;
goto foundWidth;
}
foundWidth:
// Top
for(int y = 0; y < bitmap.Height; y++)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Y = y;
goto foundY;
}
foundY:
// Bottom
for(int y = bitmap.Height - 1; y >= 0; y--)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Height = y - bounds.Y + 1;
goto foundHeight;
}
foundHeight:
bitmap.UnlockBits(bd);
}
cachedTextBounds[t] = bounds;
return bounds;
}
I have a similar problem. I want to know how big the text I'm drawing is going to be, and where it's going to appear, EXACTLY. I haven't had the line-break problem, so I don't think I can help you there. I had the same problems you had with all the various measuring techniques available, including ending up with MeasureCharacterRanges, which worked okay for the left and right, but not at all for the height and top. (Playing with the baseline can work well for some rare applications though.)
I've ended up with a very inelegant, inefficient, but working solution, at least for my use case. I draw the text on a bitmap, check the bits to see where they ended up, and that's my range. Since I'm mostly drawing small fonts and short strings, it's been fast enough for me (especially with the memoization I added). Maybe this won't be exactly what you need, but maybe it can lead you down the right track anyway.
Note it requires compiling the project to allow unsafe code at the moment, as I'm trying to squeeze out every bit of efficiency from it, but that constraint could be removed if you wanted to. Also, it's not as thread safe as it could be right now, you could easily add that if you needed it.
Dictionary<Tuple<string, Font, Brush>, Rectangle> cachedTextBounds = new Dictionary<Tuple<string, Font, Brush>, Rectangle>();
/// <summary>
/// Determines bounds of some text by actually drawing the text to a bitmap and
/// reading the bits to see where it ended up. Bounds assume you draw at 0, 0. If
/// drawing elsewhere, you can easily offset the resulting rectangle appropriately.
/// </summary>
/// <param name="text">The text to be drawn</param>
/// <param name="font">The font to use when drawing the text</param>
/// <param name="brush">The brush to be used when drawing the text</param>
/// <returns>The bounding rectangle of the rendered text</returns>
private unsafe Rectangle RenderedTextBounds(string text, Font font, Brush brush) {
// First check memoization
Tuple<string, Font, Brush> t = new Tuple<string, Font, Brush>(text, font, brush);
try {
return cachedTextBounds[t];
}
catch(KeyNotFoundException) {
// not cached
}
// Draw the string on a bitmap
Rectangle bounds = new Rectangle();
Size approxSize = TextRenderer.MeasureText(text, font);
using(Bitmap bitmap = new Bitmap((int)(approxSize.Width*1.5), (int)(approxSize.Height*1.5))) {
using(Graphics g = Graphics.FromImage(bitmap))
g.DrawString(text, font, brush, 0, 0);
// Unsafe LockBits code takes a bit over 10% of time compared to safe GetPixel code
BitmapData bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* row = (byte*)bd.Scan0;
// Find left, looking for first bit that has a non-zero alpha channel, so it's not clear
for(int x = 0; x < bitmap.Width; x++)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.X = x;
goto foundX;
}
foundX:
// Right
for(int x = bitmap.Width - 1; x >= 0; x--)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Width = x - bounds.X + 1;
goto foundWidth;
}
foundWidth:
// Top
for(int y = 0; y < bitmap.Height; y++)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Y = y;
goto foundY;
}
foundY:
// Bottom
for(int y = bitmap.Height - 1; y >= 0; y--)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Height = y - bounds.Y + 1;
goto foundHeight;
}
foundHeight:
bitmap.UnlockBits(bd);
}
cachedTextBounds[t] = bounds;
return bounds;
}
answered Aug 15 '11 at 15:45
user12861
1,36931131
1,36931131
Nice work! I'll have to give that a try and see if that solves my problem. Though I'm somewhat loathe to have to rely onunsafe
code. How quick do you find that code? The circumstance I'm using it in checks whether the text is too big for a fixed-size bounding box and decreases the font size within awhile
loop (checkingwhile too big or size gt hard-floor-limit
), so I don't want to perform an expensive operation in thatwhile
loop…
– Owen Blacker
Aug 16 '11 at 12:03
Speed depends heavily on font size. It allocates bitmap, renders it, then searches for boundaries. Smaller fonts are a lot faster, and you lose with larger fonts something like O(size^2) I think. May want to guess small size and work up to a size that's too big instead of the other way around. You could use MeasureCharacterRanges as first guess for lower bound, since it always seems to end up too small a guess. There's a lot of room for cleverness searching for the right size, binary search with clever guessing etc. I don't need any of that for my purpose, so I haven't played with it.
– user12861
Aug 16 '11 at 13:14
As to unsafe, you can do it without that but speed takes a hit. As commented this is almost 90% faster than using GetPixel, but you can still do an in between method using LockBits without unsafe code. I didn't try that as I was ok with unsafe code and wanted to squeeze out a bit of easily obtained performance. There are lots of ways to try to squeeze out performance depending on how exactly you're using this stuff, but they're all complicated. This whole thing is kind of nasty I fully realize. I would think there would be a better way, but my search online was as unproductive as yours.
– user12861
Aug 16 '11 at 13:17
That's really helpful, user12861; thank you. Hopefully, I'll get a chance to give this a try sometime soon.
– Owen Blacker
Aug 25 '11 at 10:48
add a comment |
Nice work! I'll have to give that a try and see if that solves my problem. Though I'm somewhat loathe to have to rely onunsafe
code. How quick do you find that code? The circumstance I'm using it in checks whether the text is too big for a fixed-size bounding box and decreases the font size within awhile
loop (checkingwhile too big or size gt hard-floor-limit
), so I don't want to perform an expensive operation in thatwhile
loop…
– Owen Blacker
Aug 16 '11 at 12:03
Speed depends heavily on font size. It allocates bitmap, renders it, then searches for boundaries. Smaller fonts are a lot faster, and you lose with larger fonts something like O(size^2) I think. May want to guess small size and work up to a size that's too big instead of the other way around. You could use MeasureCharacterRanges as first guess for lower bound, since it always seems to end up too small a guess. There's a lot of room for cleverness searching for the right size, binary search with clever guessing etc. I don't need any of that for my purpose, so I haven't played with it.
– user12861
Aug 16 '11 at 13:14
As to unsafe, you can do it without that but speed takes a hit. As commented this is almost 90% faster than using GetPixel, but you can still do an in between method using LockBits without unsafe code. I didn't try that as I was ok with unsafe code and wanted to squeeze out a bit of easily obtained performance. There are lots of ways to try to squeeze out performance depending on how exactly you're using this stuff, but they're all complicated. This whole thing is kind of nasty I fully realize. I would think there would be a better way, but my search online was as unproductive as yours.
– user12861
Aug 16 '11 at 13:17
That's really helpful, user12861; thank you. Hopefully, I'll get a chance to give this a try sometime soon.
– Owen Blacker
Aug 25 '11 at 10:48
Nice work! I'll have to give that a try and see if that solves my problem. Though I'm somewhat loathe to have to rely on
unsafe
code. How quick do you find that code? The circumstance I'm using it in checks whether the text is too big for a fixed-size bounding box and decreases the font size within a while
loop (checking while too big or size gt hard-floor-limit
), so I don't want to perform an expensive operation in that while
loop…– Owen Blacker
Aug 16 '11 at 12:03
Nice work! I'll have to give that a try and see if that solves my problem. Though I'm somewhat loathe to have to rely on
unsafe
code. How quick do you find that code? The circumstance I'm using it in checks whether the text is too big for a fixed-size bounding box and decreases the font size within a while
loop (checking while too big or size gt hard-floor-limit
), so I don't want to perform an expensive operation in that while
loop…– Owen Blacker
Aug 16 '11 at 12:03
Speed depends heavily on font size. It allocates bitmap, renders it, then searches for boundaries. Smaller fonts are a lot faster, and you lose with larger fonts something like O(size^2) I think. May want to guess small size and work up to a size that's too big instead of the other way around. You could use MeasureCharacterRanges as first guess for lower bound, since it always seems to end up too small a guess. There's a lot of room for cleverness searching for the right size, binary search with clever guessing etc. I don't need any of that for my purpose, so I haven't played with it.
– user12861
Aug 16 '11 at 13:14
Speed depends heavily on font size. It allocates bitmap, renders it, then searches for boundaries. Smaller fonts are a lot faster, and you lose with larger fonts something like O(size^2) I think. May want to guess small size and work up to a size that's too big instead of the other way around. You could use MeasureCharacterRanges as first guess for lower bound, since it always seems to end up too small a guess. There's a lot of room for cleverness searching for the right size, binary search with clever guessing etc. I don't need any of that for my purpose, so I haven't played with it.
– user12861
Aug 16 '11 at 13:14
As to unsafe, you can do it without that but speed takes a hit. As commented this is almost 90% faster than using GetPixel, but you can still do an in between method using LockBits without unsafe code. I didn't try that as I was ok with unsafe code and wanted to squeeze out a bit of easily obtained performance. There are lots of ways to try to squeeze out performance depending on how exactly you're using this stuff, but they're all complicated. This whole thing is kind of nasty I fully realize. I would think there would be a better way, but my search online was as unproductive as yours.
– user12861
Aug 16 '11 at 13:17
As to unsafe, you can do it without that but speed takes a hit. As commented this is almost 90% faster than using GetPixel, but you can still do an in between method using LockBits without unsafe code. I didn't try that as I was ok with unsafe code and wanted to squeeze out a bit of easily obtained performance. There are lots of ways to try to squeeze out performance depending on how exactly you're using this stuff, but they're all complicated. This whole thing is kind of nasty I fully realize. I would think there would be a better way, but my search online was as unproductive as yours.
– user12861
Aug 16 '11 at 13:17
That's really helpful, user12861; thank you. Hopefully, I'll get a chance to give this a try sometime soon.
– Owen Blacker
Aug 25 '11 at 10:48
That's really helpful, user12861; thank you. Hopefully, I'll get a chance to give this a try sometime soon.
– Owen Blacker
Aug 25 '11 at 10:48
add a comment |
up vote
0
down vote
Could you try removing the following line?
fmt.FormatFlags = StringFormatFlags.NoClip;
Overhanging parts of glyphs, and
unwrapped text reaching outside the
formatting rectangle are allowed to
show. By default all text and glyph
parts reaching outside the formatting
rectangle are clipped.
That's the best I can come up with for this :(
Thanks for posting an answer. I did look at this, having thought it might be the problem, but the reported height is almost double the actual height, so I don'think it can be that. It seems that the difference StringFormatFlags.NoClip makes is that if the bowl of a letter P (for example) just pokes outside the bounding box then it's allowed to render, rather than being clipped. That doesn't seem to be the problem I'm experiencing. But thanks :o)
– Owen Blacker
Apr 29 '10 at 8:51
add a comment |
up vote
0
down vote
Could you try removing the following line?
fmt.FormatFlags = StringFormatFlags.NoClip;
Overhanging parts of glyphs, and
unwrapped text reaching outside the
formatting rectangle are allowed to
show. By default all text and glyph
parts reaching outside the formatting
rectangle are clipped.
That's the best I can come up with for this :(
Thanks for posting an answer. I did look at this, having thought it might be the problem, but the reported height is almost double the actual height, so I don'think it can be that. It seems that the difference StringFormatFlags.NoClip makes is that if the bowl of a letter P (for example) just pokes outside the bounding box then it's allowed to render, rather than being clipped. That doesn't seem to be the problem I'm experiencing. But thanks :o)
– Owen Blacker
Apr 29 '10 at 8:51
add a comment |
up vote
0
down vote
up vote
0
down vote
Could you try removing the following line?
fmt.FormatFlags = StringFormatFlags.NoClip;
Overhanging parts of glyphs, and
unwrapped text reaching outside the
formatting rectangle are allowed to
show. By default all text and glyph
parts reaching outside the formatting
rectangle are clipped.
That's the best I can come up with for this :(
Could you try removing the following line?
fmt.FormatFlags = StringFormatFlags.NoClip;
Overhanging parts of glyphs, and
unwrapped text reaching outside the
formatting rectangle are allowed to
show. By default all text and glyph
parts reaching outside the formatting
rectangle are clipped.
That's the best I can come up with for this :(
answered Apr 29 '10 at 8:22
Codesleuth
8,34864363
8,34864363
Thanks for posting an answer. I did look at this, having thought it might be the problem, but the reported height is almost double the actual height, so I don'think it can be that. It seems that the difference StringFormatFlags.NoClip makes is that if the bowl of a letter P (for example) just pokes outside the bounding box then it's allowed to render, rather than being clipped. That doesn't seem to be the problem I'm experiencing. But thanks :o)
– Owen Blacker
Apr 29 '10 at 8:51
add a comment |
Thanks for posting an answer. I did look at this, having thought it might be the problem, but the reported height is almost double the actual height, so I don'think it can be that. It seems that the difference StringFormatFlags.NoClip makes is that if the bowl of a letter P (for example) just pokes outside the bounding box then it's allowed to render, rather than being clipped. That doesn't seem to be the problem I'm experiencing. But thanks :o)
– Owen Blacker
Apr 29 '10 at 8:51
Thanks for posting an answer. I did look at this, having thought it might be the problem, but the reported height is almost double the actual height, so I don'think it can be that. It seems that the difference StringFormatFlags.NoClip makes is that if the bowl of a letter P (for example) just pokes outside the bounding box then it's allowed to render, rather than being clipped. That doesn't seem to be the problem I'm experiencing. But thanks :o)
– Owen Blacker
Apr 29 '10 at 8:51
Thanks for posting an answer. I did look at this, having thought it might be the problem, but the reported height is almost double the actual height, so I don'think it can be that. It seems that the difference StringFormatFlags.NoClip makes is that if the bowl of a letter P (for example) just pokes outside the bounding box then it's allowed to render, rather than being clipped. That doesn't seem to be the problem I'm experiencing. But thanks :o)
– Owen Blacker
Apr 29 '10 at 8:51
add a comment |
up vote
0
down vote
I also had some problems with the MeasureCharacterRanges
method. It was giving me inconsistent sizes for the same string and even the same Graphics
object. Then I discovered that it depends on the value of the layoutRect
parametr - I can't see why, in my opinion it's a bug in the .NET code.
For example if layoutRect
was completely empty (all values set to zero), I got correct values for the string "a" - the size was {Width=8.898438, Height=18.10938}
using 12pt Ms Sans Serif font.
However, when I set the value of the 'X' property of the rectangle to a non-integer number (like 1.2), it gave me {Width=9, Height=19}
.
So I really think there is a bug when you use a layout rectangle with non-integer X coordinate.
Interesting — looks like it's always rounding your size up. Unfortunately, my layoutRect is always integral — it's defined as private static readonly RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64); and its value never changes (obviously, as it's marked readonly). It's definitely a bug in the .Net code, but it doesn't look like your bug and my bug are the same one.
– Owen Blacker
Aug 9 '10 at 11:31
add a comment |
up vote
0
down vote
I also had some problems with the MeasureCharacterRanges
method. It was giving me inconsistent sizes for the same string and even the same Graphics
object. Then I discovered that it depends on the value of the layoutRect
parametr - I can't see why, in my opinion it's a bug in the .NET code.
For example if layoutRect
was completely empty (all values set to zero), I got correct values for the string "a" - the size was {Width=8.898438, Height=18.10938}
using 12pt Ms Sans Serif font.
However, when I set the value of the 'X' property of the rectangle to a non-integer number (like 1.2), it gave me {Width=9, Height=19}
.
So I really think there is a bug when you use a layout rectangle with non-integer X coordinate.
Interesting — looks like it's always rounding your size up. Unfortunately, my layoutRect is always integral — it's defined as private static readonly RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64); and its value never changes (obviously, as it's marked readonly). It's definitely a bug in the .Net code, but it doesn't look like your bug and my bug are the same one.
– Owen Blacker
Aug 9 '10 at 11:31
add a comment |
up vote
0
down vote
up vote
0
down vote
I also had some problems with the MeasureCharacterRanges
method. It was giving me inconsistent sizes for the same string and even the same Graphics
object. Then I discovered that it depends on the value of the layoutRect
parametr - I can't see why, in my opinion it's a bug in the .NET code.
For example if layoutRect
was completely empty (all values set to zero), I got correct values for the string "a" - the size was {Width=8.898438, Height=18.10938}
using 12pt Ms Sans Serif font.
However, when I set the value of the 'X' property of the rectangle to a non-integer number (like 1.2), it gave me {Width=9, Height=19}
.
So I really think there is a bug when you use a layout rectangle with non-integer X coordinate.
I also had some problems with the MeasureCharacterRanges
method. It was giving me inconsistent sizes for the same string and even the same Graphics
object. Then I discovered that it depends on the value of the layoutRect
parametr - I can't see why, in my opinion it's a bug in the .NET code.
For example if layoutRect
was completely empty (all values set to zero), I got correct values for the string "a" - the size was {Width=8.898438, Height=18.10938}
using 12pt Ms Sans Serif font.
However, when I set the value of the 'X' property of the rectangle to a non-integer number (like 1.2), it gave me {Width=9, Height=19}
.
So I really think there is a bug when you use a layout rectangle with non-integer X coordinate.
answered Aug 9 '10 at 9:19
Pavel
1
1
Interesting — looks like it's always rounding your size up. Unfortunately, my layoutRect is always integral — it's defined as private static readonly RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64); and its value never changes (obviously, as it's marked readonly). It's definitely a bug in the .Net code, but it doesn't look like your bug and my bug are the same one.
– Owen Blacker
Aug 9 '10 at 11:31
add a comment |
Interesting — looks like it's always rounding your size up. Unfortunately, my layoutRect is always integral — it's defined as private static readonly RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64); and its value never changes (obviously, as it's marked readonly). It's definitely a bug in the .Net code, but it doesn't look like your bug and my bug are the same one.
– Owen Blacker
Aug 9 '10 at 11:31
Interesting — looks like it's always rounding your size up. Unfortunately, my layoutRect is always integral — it's defined as private static readonly RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64); and its value never changes (obviously, as it's marked readonly). It's definitely a bug in the .Net code, but it doesn't look like your bug and my bug are the same one.
– Owen Blacker
Aug 9 '10 at 11:31
Interesting — looks like it's always rounding your size up. Unfortunately, my layoutRect is always integral — it's defined as private static readonly RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64); and its value never changes (obviously, as it's marked readonly). It's definitely a bug in the .Net code, but it doesn't look like your bug and my bug are the same one.
– Owen Blacker
Aug 9 '10 at 11:31
add a comment |
up vote
0
down vote
Ok so 4 years late but this question EXACTLY matched my symptoms and I've actually worked out the cause.
There is most certainly a bug in MeasureString AND MeasureCharacterRanges.
The simple answer is:
Make sure you divide your width restriction (int width in MeasureString or the Size.Width property of the boundingRect in MeasureCharacterRanges) by 0.72. When you get your results back multiply each dimension by 0.72 to get the REAL result
int measureWidth = Convert.ToInt32((float)width/0.72);
SizeF measureSize = gfx.MeasureString(text, font, measureWidth, format);
float actualHeight = measureSize.Height * (float)0.72;
or
float measureWidth = width/0.72;
Region regions = gfx.MeasureCharacterRanges(text, font, new RectangleF(0,0,measureWidth, format);
float actualHeight = 0;
if(regions.Length>0)
{
actualHeight = regions[0].GetBounds(gfx).Size.Height * (float)0.72;
}
The explanation (that I can figure out) is that something to do with the context is triggering a conversion in the Measure methods (that doesn't trigger in the DrawString method) for inch->point (*72/100). When you pass in the ACTUAL width limitation it is adjusting this value so the MEASURED width limitation is, in effect, shorter than it should be. Your text then wraps earlier than it is supposed to and so you get a longer height result than expected. Unfortunately the conversion applies to the actual height result as well so it's a good idea to 'unconvert' that value too.
On a REALLY annoying side note, I have absolutely NO idea why MeasureString takes an int for width. If you set your graphics context unit of measurement to inches then (due to the int) you can only set your width limitation to 1 inch, or 2 inches, etc. Completely ridiculous!
– Red Nightingale
Nov 16 '14 at 1:07
add a comment |
up vote
0
down vote
Ok so 4 years late but this question EXACTLY matched my symptoms and I've actually worked out the cause.
There is most certainly a bug in MeasureString AND MeasureCharacterRanges.
The simple answer is:
Make sure you divide your width restriction (int width in MeasureString or the Size.Width property of the boundingRect in MeasureCharacterRanges) by 0.72. When you get your results back multiply each dimension by 0.72 to get the REAL result
int measureWidth = Convert.ToInt32((float)width/0.72);
SizeF measureSize = gfx.MeasureString(text, font, measureWidth, format);
float actualHeight = measureSize.Height * (float)0.72;
or
float measureWidth = width/0.72;
Region regions = gfx.MeasureCharacterRanges(text, font, new RectangleF(0,0,measureWidth, format);
float actualHeight = 0;
if(regions.Length>0)
{
actualHeight = regions[0].GetBounds(gfx).Size.Height * (float)0.72;
}
The explanation (that I can figure out) is that something to do with the context is triggering a conversion in the Measure methods (that doesn't trigger in the DrawString method) for inch->point (*72/100). When you pass in the ACTUAL width limitation it is adjusting this value so the MEASURED width limitation is, in effect, shorter than it should be. Your text then wraps earlier than it is supposed to and so you get a longer height result than expected. Unfortunately the conversion applies to the actual height result as well so it's a good idea to 'unconvert' that value too.
On a REALLY annoying side note, I have absolutely NO idea why MeasureString takes an int for width. If you set your graphics context unit of measurement to inches then (due to the int) you can only set your width limitation to 1 inch, or 2 inches, etc. Completely ridiculous!
– Red Nightingale
Nov 16 '14 at 1:07
add a comment |
up vote
0
down vote
up vote
0
down vote
Ok so 4 years late but this question EXACTLY matched my symptoms and I've actually worked out the cause.
There is most certainly a bug in MeasureString AND MeasureCharacterRanges.
The simple answer is:
Make sure you divide your width restriction (int width in MeasureString or the Size.Width property of the boundingRect in MeasureCharacterRanges) by 0.72. When you get your results back multiply each dimension by 0.72 to get the REAL result
int measureWidth = Convert.ToInt32((float)width/0.72);
SizeF measureSize = gfx.MeasureString(text, font, measureWidth, format);
float actualHeight = measureSize.Height * (float)0.72;
or
float measureWidth = width/0.72;
Region regions = gfx.MeasureCharacterRanges(text, font, new RectangleF(0,0,measureWidth, format);
float actualHeight = 0;
if(regions.Length>0)
{
actualHeight = regions[0].GetBounds(gfx).Size.Height * (float)0.72;
}
The explanation (that I can figure out) is that something to do with the context is triggering a conversion in the Measure methods (that doesn't trigger in the DrawString method) for inch->point (*72/100). When you pass in the ACTUAL width limitation it is adjusting this value so the MEASURED width limitation is, in effect, shorter than it should be. Your text then wraps earlier than it is supposed to and so you get a longer height result than expected. Unfortunately the conversion applies to the actual height result as well so it's a good idea to 'unconvert' that value too.
Ok so 4 years late but this question EXACTLY matched my symptoms and I've actually worked out the cause.
There is most certainly a bug in MeasureString AND MeasureCharacterRanges.
The simple answer is:
Make sure you divide your width restriction (int width in MeasureString or the Size.Width property of the boundingRect in MeasureCharacterRanges) by 0.72. When you get your results back multiply each dimension by 0.72 to get the REAL result
int measureWidth = Convert.ToInt32((float)width/0.72);
SizeF measureSize = gfx.MeasureString(text, font, measureWidth, format);
float actualHeight = measureSize.Height * (float)0.72;
or
float measureWidth = width/0.72;
Region regions = gfx.MeasureCharacterRanges(text, font, new RectangleF(0,0,measureWidth, format);
float actualHeight = 0;
if(regions.Length>0)
{
actualHeight = regions[0].GetBounds(gfx).Size.Height * (float)0.72;
}
The explanation (that I can figure out) is that something to do with the context is triggering a conversion in the Measure methods (that doesn't trigger in the DrawString method) for inch->point (*72/100). When you pass in the ACTUAL width limitation it is adjusting this value so the MEASURED width limitation is, in effect, shorter than it should be. Your text then wraps earlier than it is supposed to and so you get a longer height result than expected. Unfortunately the conversion applies to the actual height result as well so it's a good idea to 'unconvert' that value too.
answered Nov 16 '14 at 1:06
Red Nightingale
471217
471217
On a REALLY annoying side note, I have absolutely NO idea why MeasureString takes an int for width. If you set your graphics context unit of measurement to inches then (due to the int) you can only set your width limitation to 1 inch, or 2 inches, etc. Completely ridiculous!
– Red Nightingale
Nov 16 '14 at 1:07
add a comment |
On a REALLY annoying side note, I have absolutely NO idea why MeasureString takes an int for width. If you set your graphics context unit of measurement to inches then (due to the int) you can only set your width limitation to 1 inch, or 2 inches, etc. Completely ridiculous!
– Red Nightingale
Nov 16 '14 at 1:07
On a REALLY annoying side note, I have absolutely NO idea why MeasureString takes an int for width. If you set your graphics context unit of measurement to inches then (due to the int) you can only set your width limitation to 1 inch, or 2 inches, etc. Completely ridiculous!
– Red Nightingale
Nov 16 '14 at 1:07
On a REALLY annoying side note, I have absolutely NO idea why MeasureString takes an int for width. If you set your graphics context unit of measurement to inches then (due to the int) you can only set your width limitation to 1 inch, or 2 inches, etc. Completely ridiculous!
– Red Nightingale
Nov 16 '14 at 1:07
add a comment |
up vote
0
down vote
To convert from points to dpi as in screen resolution you need to divide by 72 and multiply by DPI, for example:
graphics.DpiY * text.Width / 72
Red Nightengale was really close, because graphics.DpiY is usually 96 for screen resolutions.
That's not an answer to the question as a whole, though. Could you expand this into an answer — including any of Red Nightingale's answer that you need to duplicate? Alternatively, post this as a comment on Red Nightingale's answer? Thanks!
– Owen Blacker
Nov 12 at 7:25
add a comment |
up vote
0
down vote
To convert from points to dpi as in screen resolution you need to divide by 72 and multiply by DPI, for example:
graphics.DpiY * text.Width / 72
Red Nightengale was really close, because graphics.DpiY is usually 96 for screen resolutions.
That's not an answer to the question as a whole, though. Could you expand this into an answer — including any of Red Nightingale's answer that you need to duplicate? Alternatively, post this as a comment on Red Nightingale's answer? Thanks!
– Owen Blacker
Nov 12 at 7:25
add a comment |
up vote
0
down vote
up vote
0
down vote
To convert from points to dpi as in screen resolution you need to divide by 72 and multiply by DPI, for example:
graphics.DpiY * text.Width / 72
Red Nightengale was really close, because graphics.DpiY is usually 96 for screen resolutions.
To convert from points to dpi as in screen resolution you need to divide by 72 and multiply by DPI, for example:
graphics.DpiY * text.Width / 72
Red Nightengale was really close, because graphics.DpiY is usually 96 for screen resolutions.
answered Nov 10 at 22:38
JBStCyr
13
13
That's not an answer to the question as a whole, though. Could you expand this into an answer — including any of Red Nightingale's answer that you need to duplicate? Alternatively, post this as a comment on Red Nightingale's answer? Thanks!
– Owen Blacker
Nov 12 at 7:25
add a comment |
That's not an answer to the question as a whole, though. Could you expand this into an answer — including any of Red Nightingale's answer that you need to duplicate? Alternatively, post this as a comment on Red Nightingale's answer? Thanks!
– Owen Blacker
Nov 12 at 7:25
That's not an answer to the question as a whole, though. Could you expand this into an answer — including any of Red Nightingale's answer that you need to duplicate? Alternatively, post this as a comment on Red Nightingale's answer? Thanks!
– Owen Blacker
Nov 12 at 7:25
That's not an answer to the question as a whole, though. Could you expand this into an answer — including any of Red Nightingale's answer that you need to duplicate? Alternatively, post this as a comment on Red Nightingale's answer? Thanks!
– Owen Blacker
Nov 12 at 7:25
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f2684894%2fgraphics-measurecharacterranges-giving-wrong-size-calculations%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
+1 for one of the most documented questions I've seen in a while!
– SouthShoreAK
Apr 18 '12 at 16:56