// Gets the bounds of each character in a line of text. // The line is processed in blocks of 32 characters (GdiPlus.MaxMeasurableCharacterRanges). IEnumerable <RectangleF> GetCharExtents(string text, int height, int line_start, int line_length, RectangleF layoutRect, IntPtr native_graphics, IntPtr native_font, IntPtr native_string_format) { RectangleF rect = new RectangleF(); int line_end = line_start + line_length; while (line_start < line_end) { //if (text[line_start] == '\n' || text[line_start] == '\r') //{ // line_start++; // continue; //} int num_characters = (line_end - line_start) > GdiPlus.MaxMeasurableCharacterRanges ? GdiPlus.MaxMeasurableCharacterRanges : line_end - line_start; int status = 0; for (int i = 0; i < num_characters; i++) { characterRanges[i] = new CharacterRange(line_start + i, 1); IntPtr region; status = GdiPlus.CreateRegion(out region); regions[i] = region; Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); } status = GdiPlus.SetStringFormatMeasurableCharacterRanges(native_string_format, num_characters, characterRanges); Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); status = GdiPlus.MeasureCharacterRanges(native_graphics, text, text.Length, native_font, ref layoutRect, native_string_format, num_characters, regions); Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); for (int i = 0; i < num_characters; i++) { GdiPlus.GetRegionBounds(regions[i], native_graphics, ref rect); Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); GdiPlus.DeleteRegion(regions[i]); Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); rect.Y += height; yield return(rect); } line_start += num_characters; } }
// Gets the bounds of each character in a line of text. // Each line is processed in blocks of 32 characters (GdiPlus.MaxMeasurableCharacterRanges). IEnumerable <RectangleF> MeasureGlyphExtents( ref TextBlock block, string text, IntPtr native_graphics, IntPtr native_font, IntPtr native_string_format, ref RectangleF layoutRect, out float max_width, out float max_height) { measured_glyphs.Clear(); max_width = layoutRect.Left; max_height = layoutRect.Top; float last_line_width = 0, last_line_height = 0; int current = 0; while (current < text.Length) { int num_characters = (text.Length - current) > GdiPlus.MaxMeasurableCharacterRanges ? GdiPlus.MaxMeasurableCharacterRanges : text.Length - current; int status = 0; // Prepare the character ranges and region structs for the measurement. for (int i = 0; i < num_characters; i++) { if (text[current + i] == '\n' || text[current + i] == '\r') { throw new NotSupportedException(); } characterRanges[i] = new CharacterRange(current + i, 1); IntPtr region; status = GdiPlus.CreateRegion(out region); regions[i] = region; Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); } status = GdiPlus.SetStringFormatMeasurableCharacterRanges(native_string_format, num_characters, characterRanges); Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); status = GdiPlus.MeasureCharacterRanges(native_graphics, text, text.Length, native_font, ref layoutRect, native_string_format, num_characters, regions); Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); // Read back the results of the measurement. for (int i = 0; i < num_characters; i++) { RectangleF rect = new RectangleF(); GdiPlus.GetRegionBounds(regions[i], native_graphics, ref rect); Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); GdiPlus.DeleteRegion(regions[i]); Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); if (rect.Bottom > max_height) { max_height = rect.Bottom; } if (rect.Right > max_width) { max_width = rect.Right; } if (rect.X > last_line_width) { last_line_width = rect.X; } if (rect.Y > last_line_height) { last_line_height = rect.Y; } measured_glyphs.Add(rect); } current += num_characters; } // Make sure the current height is updated, if the the current line has wrapped due to word-wraping. // Otherwise, the next line will overlap with the current one. if (measured_glyphs.Count > 1) { if ((block.Direction & TextDirection.Vertical) == 0) { if (layoutRect.Y < last_line_height) { layoutRect.Y = last_line_height; } } else { if (layoutRect.X < last_line_width) { layoutRect.X = last_line_width; } } } // Mono's GDI+ implementation suffers from an issue where the specified layoutRect is not taken into // account. We will try to improve the situation by moving text to the correct location on this // error condition. This will not help word wrapping, but it is better than nothing. // TODO: Mono 2.8 is supposed to ship with a Pango-based GDI+ text renderer, which should not // suffer from this bug. Verify that this is the case and remove the hack. if (Configuration.RunningOnMono && (layoutRect.X != 0 || layoutRect.Y != 0) && measured_glyphs.Count > 0) { for (int i = 0; i < measured_glyphs.Count; i++) { RectangleF rect = measured_glyphs[i]; rect.X += layoutRect.X; rect.Y += layoutRect.Y; measured_glyphs[i] = rect; } } return(measured_glyphs); }