Find longest initial substring which renders in a limited width - c#

I have a long string, ex: "Please help me to solve this problem."
This string is so long to fit in a width of 100 pixels.
I need to get a substring of this string and substring will fit in 100 pixels. Ex: substring "Please help me to sol" is fit in 100 pixels.
Please help me how to estimate a substring like this. Thanks.
My application is Win Forms and C#.

As usual, the Win32 API has a function designed exactly for this: GetTextExtentExPoint
P/invoke declaration: http://pinvoke.net/default.aspx/gdi32/GetTextExtentExPoint.html

Looks like you could use TextRenderer::MeasureText().

this should work... not really tested and no optimisations, though
var g = Graphics.FromImage(someimage); // or any from hwnd etc... should use your panel/whatever
var f = new Font("YOURFONT", 12f, FontStyle.Regular, GraphicsUnit.Pixel); // replace with your vals
var yourtext = "yourtextyourtextyourtextyourtextyourtext";
while (g.MeasureString(yourtext, f).Width > 100)
yourtext = yourtext.Remove(yourtext.Length - 2, 1);

If all you are looking for is a good estimate, you might first measure the width of a representative string to determine the average character width of your font once. A good representative might not be just the alphabet; you might look for a character frequency histogram to get a better average width.
string Representative = "abcdefghijklmnopqrstuvwxyz";
float CharacterWidth;
using(Bitmap b = new Bitmap(0, 0))
using(Graphics g = Graphics.FromImage(b))
using(Font f = /* some font definition */)
{
CharacterWidth = g.MeasureString(Representative, f).Width / Representative.Length;
}
Then use that to estimate how many characters would fit within N pixels.
string Text = ...
int DisplayWidth = 100;
int FitLength = Math.Min(Text.Length, (int)(DisplayWidth / CharacterWidth));
string FitText = Text.Substring(0, FitLength);

Related

Get most similar image [duplicate]

This question already has answers here:
How can I measure the similarity between two images? [closed]
(17 answers)
Closed 5 years ago.
I have one Bitmap A and one array of Bitmap, in the array there is a Bitmap that looks the same as Bitmap A. I'm using the code below but it sometimes doesnt work, it iterates the entire array without finding it, it seems there are some minor differences, is there a way to change the function to return true if its 90% similar or pick the most similar image in the array? The array has only 6 images.
for(int i = 0; i < list.Count;i++)
{
if(ImageCompareString(image,list[i])
{
answerIndex = i;
break;
}
}
private static bool ImageCompareString(Bitmap firstImage, Bitmap secondImage)
{
MemoryStream ms = new MemoryStream();
firstImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
String firstBitmap = Convert.ToBase64String(ms.ToArray());
ms.Position = 0;
secondImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
String secondBitmap = Convert.ToBase64String(ms.ToArray());
if (firstBitmap.Equals(secondBitmap))
{
return true;
}
else
{
return false;
}
}
Of course there is such way... But you have to code it yourself.
First you shoud not compare the base64 data... You'll loose direct pixel value access and increase the size of the data to compare by more then 150% (Originaly 200% but corrected thanks to PeterDuniho's comment) in C# due to UTF16.
Second I assume that all pictures have the same fixed size. Before comparing, reduce the image size to something really small, but keep the width/height aspect. This will speed up the comparsion and also eliminates noise.
Third Iterate both pictures and compare their grayscaled pixel values. I Assume that you have resized the picture to 16x16. Since we're comparing their grayscale-values the value of one pixel is between 0 and 255. So the maximum distance between both pictures will be 16 * 16 * 256 = 65536. If both pictures are black, the distance between the pictures will be zero (100% similarity). If one picture is black and the other is white the distance will be 65535 (0% similarity).
To compare the images iterate the picture-pixels and subtract the grayscale-pixel-value-from-picture-a from the grayscale-pixel-value-of-picture-b at the point x,y and add the absolute difference value to the counter. This counter will be the total distance between both pictures.
Lets assume this counter has a value of 1000 after the comparison loop, you get the percentage-similarity by 1000 / 65535 ~ 1.5% difference (or 98.5% similarity) between both pictures.
pseudo-compare-code
long counter = 0;
long total = image.Width * image.Height * (Color.White - Color.Black);
for(int x = 0; x < image.Width; x++)
{
for(int y = 0; y < image.Height; y++)
{
var p1 = image.GetPixel(x, y);
var p2 = otherImage.GetPixel(x, y);
var g1 = ((p1.R + p1.G + p1.B) / 3);
var g2 = ((p2.R + p2.G + p2.B) / 3);
var distance = Math.Abs(g1 - g2);
counter += distance;
}
}
var similarity = 100 - ((counter / total) * 100);
This is an more or less easy approach, but you have to test this with you scenario/images. Instead of comparing grayscale-values you could also compare rgb-values. Look for distance definitions like the euclidean distance... Start and keep reading :)
EDIT
This is just a really basic approach that should explain how you can start comparing images. It does not take into account that there might be different image formats (jpeg, png, gif), color formats (indexed, 16bit, 24bit, 32bit) or images with different resolutions.

String formatting with spaces

I need to do some hand made formatting in c# with only spaces. Here's what I display now:
Conc2_CO ( Y? = 170.2; Y? = 2; delta = -15)
atns_UreaMassFlowDemand ( Y? = 0; Y? = 0; delta = 0)
And this is what I'd like to have:
Conc2_CO ( Y? = 170.2; Y? = 2; delta = -15)
atns_UreaMassFlowDemand ( Y? = 0; Y? = 0; delta = 0)
I tried playing with string length using new string(' ', x) but this is a big pain and seems to work randomly since all the characters don't have the same length (i.e: l is shorter than w)... Is there some better option?
Edit:
The resulting string is construct with the concatenation of the name (left) and the informations I add (right) in a library so I can't use string.format() since I can only play with the right part.
I need to display this information on the ZedGraph Legend using WinForms (but I doubt this will change anything).
Edit 2
Using paddings, this is what I've got:
Which is not what I want.
You can change your font in order to use a monospace font.
This way you will be able to use the String.Format and work with paddings.
I was delete may previous answer.
Try this:
string s = String.Format("{0,-12}({1,8})", "Example", 223);
{0,-12} -0 - value index, -12 align to left 12 chars
{1,8} - 1 - value index, 8 align to right 8 chars
More:http://msdn.microsoft.com/pl-pl/library/system.string.format%28v=vs.110%29.aspx see: The format item

Get text occurrences contained in a specified area with iTextSharp

Is it possible, using iTextSharp, get all text occurrences contained in a specified area of ​​a pdf document?
Thanks.
First you need the actual coordinates of the rectangle you marked in Red. On sight, I'd say the x value 144 (2 inches) is probably about right, but it would surprise me if the y value is 76, so you'll have to double check.
Once you have the exact coordinates of the rectangle, you can use iText's text extraction functionality using a LocationTextExtractionStrategy as is done in the ExtractPageContentArea example.
For the iTextSharp version of this example, see the C# port of the examples of chapter 15.
System.util.RectangleJ rect = new System.util.RectangleJ(70, 80, 420, 500);
RenderFilter[] filter = {new RegionTextRenderFilter(rect)};
ITextExtractionStrategy strategy = new FilteredTextRenderListener(
new LocationTextExtractionStrategy(), filter);
text = PdfTextExtractor.GetTextFromPage(reader, 1, strategy);
#BrunoLowagie gives an excellent answer but something I really struggled with was getting the actual coordinates to use. I started out with using Cursor Coordinates from Adobe Acrobat Pro.
From here I could get the coordinate in inches and calculate the DTP point (PostScript points) by multiplying the value with 72.
However something was still not right. Looking at the Y value this seemed way off. I then noticed that Adobe Acrobat counts coordinates in this view from the top left instead of bottom left. This means that Y needs to be calculated.
I solved this in code like this:
var rect = new RectangleJ(GetPostScriptPoints(4.19f),
GetPostScriptPoints(GetInverseCoordinateInInches(pdfReader, 1, 1.42f)),
GetPostScriptPoints(3.5f), GetPostScriptPoints(0.39f));
RenderFilter[] filter = { new RegionTextRenderFilter(rect) };
ITextExtractionStrategy strategy = new FilteredTextRenderListener(
new LocationTextExtractionStrategy(), filter);
var output = PdfTextExtractor.GetTextFromPage(pdfReader, 1, strategy);
private float GetPostScriptPoints(float inch)
{
return inch * 72;
}
private float GetInverseCoordinateInInches(PdfReader pdfReader, int pageIndex, float coordinateInInches)
{
Rectangle mediabox = pdfReader.GetPageSize(pageIndex);
return mediabox.Height / 72 - coordinateInInches;
}
This worked but I think it looks a little messy. I then used the tool Prepare Form in Adobe Acrobat Pro and here the Y coordinate showed up correctly when looking at Text Field Properties. It could also convert the box into points right away.
This means I could write code like this instead:
var rect = new RectangleJ(301.68f, 738f, 252f, 28.08f);
RenderFilter[] filter = { new RegionTextRenderFilter(rect) };
ITextExtractionStrategy strategy = new FilteredTextRenderListener(
new LocationTextExtractionStrategy(), filter);
var output = PdfTextExtractor.GetTextFromPage(pdfReader, 1, strategy);
This was a lot cleaner and faster so this was the way I choose to do it in the end.
See this answer if you would like to get a value from a specific location for every page in the document:
https://stackoverflow.com/a/20959388/3850405

How to measure width of character precisely?

Maybe I've got something wrong, but... I want to simulate character spacing.
I break the word (text) into the list of single characters, measure their widths, and then painting them one after another on the bitmap. I supposed, that overall width of the rendered text will be the same as the width of the whole not splitted string, but there is something wrong. Rendering characters in a loop show wider result. Is there any way to get common (expected) results?
here is a code snippet:
private struct CharWidths
{
public char Char;
public float Width;
}
private List<CharWidths> CharacterWidths = new List<CharWidths>();
...
private void GetCharacterWidths(string Text, Bitmap BMP)
{
int i;
int l = Text.Length;
CharacterWidths.Clear();
Graphics g = Graphics.FromImage(BMP);
CharWidths cw = new CharWidths();
for (i = 0; i < l; i++)
{
Size textSize = TextRenderer.MeasureText(Text[i].ToString(), Font);
cw.Char = Text[i];
cw.Width = textSize.Width;
CharacterWidths.Add(cw);
}
}
...
public void RenderToBitmap(Bitmap BMP)
{
//MessageBox.Show("color");
Graphics g = Graphics.FromImage(BMP);
GetCharacterWidths("Lyborko", BMP);
int i;
float X = 0;
PointF P = new PointF();
for (i = 0; i < CharacterWidths.Count; i++)
{
P.X = X;
P.Y = 0;
g.DrawString(CharacterWidths[i].Char.ToString(), Font, Brushes.White, P);
X = X+CharacterWidths[i].Width;
}
P.X = 0;
P.Y = 30;
g.DrawString("Lyborko", Font, Brushes.White, P);
// see the difference
}
Thanx a lot
First of all should say that don't have a silver bullet solution for this, but have a couple of suggessions on subject:
Considering that you by calling TextRenderer.MeasureText do not pass current device context (the same one you use to draw a string after) and knowing a simple fact that MeasureText simply in case of lack of that parameter creates a new one compatible with desktop and calls DrawTextEx WindowsSDK function, I would say first use an overload of MeasureText where you specify like a first argument device context which you use to render a text after. Could make a difference.
If it fails, I would try to use Control.GetPreferredSize method to guess most presize possible rendering dimension of the control on the screen, so actually the dimension of you future string's bitmap. To do that you can create some temporary control, assign a string, render and after call this function. It's clear to me that this solution may hardly fit in your app architecture, but can possibly produce a better results.
Hope this helps.

LinkLabel needing more space than TextRenderer.MeasureText says

If I give TextRenderer.MeasureText some text to measure and width to use it will return the height needed to display that text.
private static int CalculateHeight(string text, Font font, int width)
{
Size size = TextRenderer.MeasureText(text, font, new Size(width, Int32.MaxValue), TextFormatFlags.NoClipping | TextFormatFlags.WordBreak);
return size.Height;
}
If I give that text, width and height to a LinkLabel it would display the text in the width and height provided with nothing clipped off.
However, if I put a Link into the LinkLabel.Links collection, the LinkLabel will draw the text with what appears to be a little more spacing between the characters and at times this will cause the end of the text to be clipped. Is there anyway to prevent this? I've tried adding padding when there is a link, but there's no reliable way to know exactly how much more space will be needed. Are there any other ways to do this?
You should use Control.GetPreferredSize method to calculate width or height needed for control (LinkLabel in your case). You should not use MeasureText for such purposes, more detailed explanation you can find here (Accuracy of TextRenderer.MeasureText results.)
If a LinkLabel contains more than one link, or there are parts of text which are nor in a link, then the control uses Graphics.DrawString/MeasureString instead of TextRenderer.DrawText/MeasureText. You can easily see it in action, the biggest difference in rendering is with the small L letter:
linkLabel1.Text = new string('l', 100); // 100 x small L
linkLabel1.LinkArea = new LinkArea(0, 50);
linkLabel2.Text = new string('l', 100); // 100 x small L
TextRenderer.MeasureText is a managed wrapper for the DrawTextEx API. The value returned comes from the lprc struct. You might want to look at that API for more details.
I guess you could remove the style that makes it underline. linkLabel.Styles.Add("text-decoration", "none"); but then of course it wouldn't look like a link. :-/
Another solution would be to add the padding yourself I guess.
int heightBefore = linkLabel.Height;
int fontHeight = CalculateHeight(linkLabel.Text, linkLabel.Font, linkLabel.Width);
int paddingHeight = heightBefore - fontHeight;
linkLabel.Font = otherFont;
linkLabel.Height = CalculateHeight(linkLabel.Text, otherFont, linkLabel.Width);
linkLabel.Height += paddingHeight;
Not the prettiest of solutions, but I would guess it works.

Categories

Resources