Get the position where drawn text is truncated by GraphicsPath.DrawString - vb.net

I have a couple of methods that draw outlined text. The details of this are unimportant, but it serves to illustrate the problem:
(source code from Graphics DrawPath produces unexpected results when rendering text)
Private Sub FillTextSolid(g As Graphics, bounds As RectangleF, text As String, font As Font, fillColor As Color, sf As StringFormat)
Using gp As GraphicsPath = New GraphicsPath(),
brush As New SolidBrush(fillColor)
gp.AddString(text, font.FontFamily, font.Style, font.Size, bounds, sf)
g.FillPath(brush, gp)
End Using
End Sub
correctly converts a long string into one with an ellipsis inside the bounds. E.g.
Manic Miner is a platform video game originally written for the ZX
Spectrum by Matthew Smith and released by Bug-Byte in 1983. It is the
first game in the Miner Willy series and among the early titles in the
platform game genre.
becomes:
Manic Miner is a platform video game originally written for the ZX
Spectrum by Matthew Smith and released by Bug-Byte in 1983. It is the
first game in the Miner...
All well and good. What I need is a way in code to see exactly what part of the full text has been displayed. This will then be used to cycle through the text in the same bounds (almost paging if you will) to display all the text.
I looked at MeasureString but this didn't seem to achieve this. Is there any way I can discern this? In pseudo code, something like:
Dim textShown as string = gp.AddString(text, font.FontFamily, font.Style, font.Size, bounds, sf).TextShown
Thanks

Given the FillTextSolid() method shown before in:
Graphics DrawPath produces unexpected results when rendering text
Private Sub FillTextSolid(g As Graphics, bounds As RectangleF, text As String, font As Font, fillColor As Color)
Using gp As GraphicsPath = New GraphicsPath(),
brush As New SolidBrush(fillColor),
format = New StringFormat(StringFormat.GenericTypographic)
format.Trimming = StringTrimming.EllipsisWord
gp.AddString(text, font.FontFamily, font.Style, font.Size, bounds, StringFormat.GenericTypographic)
g.FillPath(brush, gp)
Dim lastCharPosition = GetPathLastCharPosition(g, format, gp, bounds, text, font)
End Using
End Sub
you can use the current GraphicsPath, Rectangle bounds, Font size and style used for drawing the the text in a graphics context, to calculate the position of the last character drawn and, as a consequence, the last word, if necessary.
I've added in FillTextSolid() a call to the GetPathLastCharPosition() method, which is responsible for the calculation. Pass to the method the objects described, as they're currently configured (these settings can of course change at any time: see the animation at the bottom).
Dim [Last Char Position] = GetPathLastCharPosition(
[Graphics],
[StringFormat],
[GraphicsPath],
[RectangleF],
[String],
[Font]
)
To determine the current last word printed using a GraphicsPath object, you cannot split the string in parts separated by, e.g., a white space, since each char is part of the rendering.
Also to note: for the measure to work as intended, you cannot set the drawing Font size in Points, the Font size must be expressed in pixels.
You could also use Point units, but the GraphicsPath class, when Points are specified, generates (correctly) a Font measure in EMs - considering the Font Cell Ascent and Descent - which is not the same as the Font.Height.
You can of course convert the measure from Ems to Pixels, but it just adds complexity for no good reason (in the context of this question, at least).
See a description of these details and how to calculate the GraphicsPath EMs in:
Properly draw text using GraphicsPath
GetPathLastCharPosition() uses Graphics.MeasureCharacterRanges to measure the bounding rectangle of each char in the Text string, in chunks of 32 chars per iteration. This is because StringFormat.SetMeasurableCharacterRanges only takes a maximum of 32 CharacterRange elements.
So, we take the Text in chunks of 32 chars, get the bounding Region of each and verify whether the Region contains the last Point in the GraphicsPath.
The last Point generated by a GraphicsPath is returned by the GraphicsPath.GetLastPoint().
This procedure only considers text drawn from top to bottom and left to right.
It can be adapted to handle right to left languages.
Of course, you could also ignore the last point and just consider whether a Region bounds fall outside the bounding rectangle of the canvas.
Anyway, when the a Region that contains the last point is found, the method stops and returns the position of the last character in the string that is part of the drawing.
Private Function GetPathLastCharPosition(g As Graphics, format As StringFormat, path As GraphicsPath, bounds As RectangleF, text As String, font As Font) As Integer
Dim textLength As Integer = text.Length
Dim p = path.GetLastPoint()
bounds.Height += font.Height
For charPos As Integer = 0 To text.Length - 1 Step 32
Dim count As Integer = Math.Min(textLength - charPos, 32)
Dim charRanges = Enumerable.Range(charPos, count).Select(Function(c) New CharacterRange(c, 1)).ToArray()
format.SetMeasurableCharacterRanges(charRanges)
Dim regions As Region() = g.MeasureCharacterRanges(text, font, bounds, format)
For r As Integer = 0 To regions.Length - 1
If regions(r).IsVisible(p.X, p.Y) Then
Return charRanges(r).First
End If
Next
Next
Return -1
End Function
This is how it works:
C# version of the method:
private int GetPathLastCharPosition(Graphics g, StringFormat format, GraphicsPath path, RectangleF bounds, string text, Font font)
{
int textLength = text.Length;
var p = path.GetLastPoint();
bounds.Height += font.Height;
for (int charPos = 0; charPos < text.Length; charPos += 32) {
int count = Math.Min(textLength - charPos, 32);
var charRanges = Enumerable.Range(charPos, count).Select(c => new CharacterRange(c, 1)).ToArray();
format.SetMeasurableCharacterRanges(charRanges);
Region[] regions = g.MeasureCharacterRanges(text, font, bounds, format);
for (int r = 0; r < regions.Length; r++) {
if (regions[r].IsVisible(p.X, p.Y)) {
return charRanges[r].First;
}
}
}
return -1;
}

Related

How to control the size of ToolTip which appears when AutoEllipsis Enabled for Label in .Vb.Net

I Have a Label Control, and the text I pass to that Control is sometime larger then the viewable area of the control, I set 'AutoEllipsis" property True.
When I hover my mouse over to that control a long long ToolTip appears in front, which is quite annoying.
I want to control that behavior and want to wrap text to multi lines.
Kindly help anybody.
After suggestion, I have think around and an idea came into my mind.
I broken string where text was going off the sight of Label region , but keeping in mind that very next of word completion so it wont add new line in the mid of word.
here is the code:
Dim str As String = "Chorioactis is a genus of fungus that contains the single species Chorioactis geaster, an extremely rare mushroom found only in select locales in Texas and Japan. In the former, it is commonly known a"
Dim startindex As Integer = str.IndexOf(" ", 100)
Dim secondhalf As String = str.Substring(startindex, (str.Length) - startindex)
Dim firsthalf As String = str.Substring(0, startindex)
str = firsthalf + Environment.NewLine + secondhalf
Label1.Text = str
below are attached Images,
Before
After
Before applying such sub-string operations, make sure to verify the string length is greater then to that number which we set to breakup line

VB.NET Cartesian Coordinate System

I'd like to make a Cartesian Coordinate System in a Windows form and be able to plot (x,y) coordinates in it.
How do i do this? I already did my research but unfortunately i only land on "charts" and not the Cartesian plane.
Any links regarding my problem will help ... thanks ...
In WinForms, you can use a PictureBox control and then draw on it using primitives such as DrawLine, DrawEllipse, etc. The following SO question contains an example:
how to draw drawings in picture box
In WPF, you can use a Canvas control similarly:
WPF canvas drawing with Graphics
If you want automatic axes and labeling, Charts are indeed the way to go. For your use case, a point chart seems like the right solution:
Point Chart (Chart Controls)
You should create a custom UserControl and use the Paint even to draw on the surface of the control. The Paint event provides you with a Graphics object which you can use to draw the graph. The big thing to know, however, is that you will need to swap your Y axis. In windows, the top-left of the screen is 0,0 rather than the bottom-left.
So, for instance, the following code will draw the x and y axis of a graph on a contorl:
Public Class CartesianGraph
Public Property BottomLeftExtent() As Point
Get
Return _bottomLeftExtent
End Get
Set(ByVal value As Point)
_bottomLeftExtent = value
End Set
End Property
Private _bottomLeftExtent As Point = New Point(-100, -100)
Public Property TopRightExtent() As Point
Get
Return _topRightExtent
End Get
Set(ByVal value As Point)
_topRightExtent = value
End Set
End Property
Private _topRightExtent As Point = New Point(100, 100)
Private Sub CartesianGraph_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim extentHeight As Integer = _topRightExtent.Y - _bottomLeftExtent.Y
Dim extentWidth As Integer = _topRightExtent.X - _bottomLeftExtent.X
If (extentHeight <> 0) And (extentWidth <> 0) Then
If (_bottomLeftExtent.Y <= 0) And (_topRightExtent.Y >= 0) Then
Dim xAxis As Integer = e.ClipRectangle.Height - (_bottomLeftExtent.Y * -1 * e.ClipRectangle.Height \ extentHeight)
e.Graphics.DrawLine(New Pen(ForeColor), 0, xAxis, e.ClipRectangle.Width, xAxis)
End If
If (_bottomLeftExtent.X <= 0) And (_topRightExtent.X >= 0) Then
Dim yAxis As Integer = e.ClipRectangle.Width * _bottomLeftExtent.X * -1 \ extentWidth
e.Graphics.DrawLine(New Pen(ForeColor), yAxis, 0, yAxis, e.ClipRectangle.Height)
End If
End If
End Sub
End Class
.NET has a charting library, but there are a few open source projects that do this sort of thing quite well. If you want to plot coordinates Zedgraph makes this relatively easy and is quite flexible.
this ZedGraph Example gives a great introduction
Dynamic Data Display is also worth looking at, but it is WPF, not Windows Forms

Create Image from Graphics

In VB.NET, I need to create an Image based on a Graphics object I have. However, there is no method such as Image.fromGraphics() etc. What should I do then?
Try something like this MSDN article states. Essentialy create a Graphics Object from a Bitmap. Then use Graphic methods to do what you need to to the Image and then you can use the Image how you need to. As #Damien_The_Unbeliever stated your Graphics Object is created to enable drawing on another object, it does not have an Image to copy, the object it was created on does.
From above article:
Dim flag As New Bitmap(200, 100)
Dim flagGraphics As Graphics = Graphics.FromImage(flag)
Dim red As Integer = 0
Dim white As Integer = 11
While white <= 100
flagGraphics.FillRectangle(Brushes.Red, 0, red, 200, 10)
flagGraphics.FillRectangle(Brushes.White, 0, white, 200, 10)
red += 20
white += 20
End While
pictureBox1.Image = flag
Have a look at the Graphics.DrawImage method and its overloads.
Here's a snippet from one of the examples that draws an image onto the screen, using a Graphics object from Winform's Paint event:
Private Sub DrawImageRect(ByVal e As PaintEventArgs)
' Create image.
Dim newImage As Image = Image.FromFile("SampImag.jpg")
' Create rectangle for displaying image.
Dim destRect As New Rectangle(100, 100, 450, 150)
' Draw image to screen.
e.Graphics.DrawImage(newImage, destRect)
End Sub

Invert or Flip Text in RDLC report

Okay, I've learned a bit more and have rephrased my question. I've got a need to flip or invert text 180 degrees (so it appears upside-down) on a RDLC report. I have some custom VB code that takes the text, converts it to a bitmap, then flips the rotates the canvas 180 degrees. The effect of this makes the text look a bit.. dithered... or fuzzy. It's not a sharp font anymore. The problem I'm experiencing is I'm using a special TTF Barcode font that creates something a scanner can read. When I flip the barcode font, the fuzziness isn't good since the barcode lines are so close together and the scanner cannot read it. Here's the code:
Function LoadImage(ByVal sImageText as String, iRotationAngle as Integer, ByVal sFontName as String, iFontSize as Integer)
Dim bmpImage As New Drawing.Bitmap(1, 1)
Dim iWidth As Integer = 0
Dim iHeight As Integer = 0
'// Create the Font object for the image text drawing.
Dim MyFont As New Drawing.Font(sFontName, iFontSize) ', System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point)
'// Create a graphics object to measure the text's width and height.
Dim MyGraphics As Drawing.Graphics = Drawing.Graphics.FromImage(bmpImage)
'// This is where the bitmap size is determined.
iWidth = MyGraphics.MeasureString(sImageText, MyFont).Width
iHeight = MyGraphics.MeasureString(sImageText, MyFont).Height
'// Create the bmpImage again with the correct size for the text and font.
bmpImage = New Drawing.Bitmap(bmpImage, New Drawing.Size(iWidth, iHeight))
'// Add the colors to the new bitmap.
MyGraphics = Drawing.Graphics.FromImage(bmpImage)
MyGraphics.Clear(Drawing.Color.White)
MyGraphics.TextRenderingHint = Drawing.Text.TextRenderingHint.ClearTypeGridFit
MyGraphics.TranslateTransform(iWidth,iHeight)
MyGraphics.RotateTransform(iRotationAngle)
MyGraphics.DrawString(sImageText, MyFont, New Drawing.SolidBrush(Drawing.Color.Black), 0, 0)
MyGraphics.Flush()
Dim stream As IO.MemoryStream = New IO.MemoryStream
Dim bitmapBytes As Byte()
'Create bitmap
bmpImage.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp)
bitmapBytes = stream.ToArray
stream.Close()
bmpImage.Dispose()
Return bitmapBytes
End Function
I really don't know why there's not a built-in way to just flip text. It'll let me reverse it left-to-right. Ridiculous.
Thanks

Get Font Width within VB.net Without Measuring a Control?

I need to display a table in rich text within a form window. It is only a two column table, so tabbing works fine to line everything up (since RichTextBox does not support RTF tables). However, sometimes the tab stops are incorrect because of non-fixed width fonts.
So, I need a way to measure the pixel width of a particular string with a particular font (Arial 10) and space or tab pad to make sure that everything aligns.
I know about the Graphics.MeaureString method, but since this is in a rich text box, I don't have an initilzed PaintEventArgs variable, and that would seem like overkill to create JUST to measure one string.
From MSDN:
Private Sub MeasureStringMin(ByVal e As PaintEventArgs)
' Set up string.
Dim measureString As String = "Measure String"
Dim stringFont As New Font("Arial", 16)
' Measure string.
Dim stringSize As New SizeF
stringSize = e.Graphics.MeasureString(measureString, stringFont)
' Draw rectangle representing size of string.
e.Graphics.DrawRectangle(New Pen(Color.Red, 1), 0.0F, 0.0F, _
stringSize.Width, stringSize.Height)
' Draw string to screen.
e.Graphics.DrawString(measureString, stringFont, Brushes.Black, _
New PointF(0, 0))
End Sub
So is the best bet just to create a dummy PaintEventArgs instance? If so, what is the best way to do that (since I'll have to call this string measuring method several hundred times)?
Also, I don't really want to have to use a fixed width font - they just don't look as good.
Use this
Dim g as Graphics = richbox.CreateGraphics()
Dim sz as SizeF = g.MeasureString(...)