Okay, so I have been working on something for a little while and I have gotten to the point where I am planning the Text rendering part.
I can already draw strings of text in two ways; DrawString and TextRenderer.DrawText. I prefer DrawText since measuring text is more accurate when using TextRenderer.Measure text.
I have a class:
public class Character
{
public string character {get; set; }
public Font font {get; set; }
public Point position {get; set; }
}
And a list of all characters pressed:
public List<Character> chars = new List<Character>();
Now my problem is that I need to be able to set a different font and color and boldness or italicization to any given selected characters or words at runtime. So I can't just draw a whole string because then there'd be no way for me to set individual font settings for each character the user has selected to change.
So I need to be able to store different font style info for each character and then add them all to a list so I can kinda go through each one and draw each one as it should be drawn (I. E. each char having its own style etc).
This solution works fine for me. And since I've not been able to find any info about this anywhere for months, I'm totally stuck.
My main problem is that because I am drawing char by char, I have no idea how far apart each character should be from the previously drawn character (kerning).
For input (text box) controls, how can we custom draw text and allow the user to make a part of a word blue, and the other half of the word a different size and color and style, for example, while still adhering to proper kerning settings?
How do we know where to draw each character?
People have said just keep restarting the whole string at once. But that doesn't solve my initial problem. I need to be able to draw each char one by one so I can save font info about it.
Kerning and Character Spacing are different and if you want to have complete control over what your code prints you may need to implement both.
Let's look at an example output first :
Image one shows direct output with an extra character spacing of 1 pixel, no kerning.
Image two has some kerning applied, but only for three kerning pairs.
I have tried to make things clearer by also drawing the result of the characterwise text measurements. Also there is a tiled 1 pixel raster as the panel BackgroundImage. (To see it better you may want to download the png files!)
private void panel2_Paint(object sender, PaintEventArgs e)
{
string fullText = "Text;1/2' LTA";
StringFormat strgfmt = StringFormat.GenericTypographic;
Font font = new Font("Times", 60f, FontStyle.Regular);
float x = 0f;
using (SolidBrush brush = new SolidBrush(Color.FromArgb(127, 0, 127, 127)))
{
for (int i = 0; i < fullText.Length; i++)
{
string text = fullText.Substring(i, 1);
SizeF sf = e.Graphics.MeasureString(text, font, 9999, strgfmt );
e.Graphics.FillRectangle(brush, new RectangleF(new PointF(x, 0f), sf));
e.Graphics.DrawString(text, font, Brushes.Black, x, 0, strgfmt );
x += sf.Width + 1; // character spacing = +1
//if (i < fullText.Length - 1) doKerning(fullText.Substring(i, 2), ref x);
}
}
}
void doKerning(string c12, ref float x)
{
if (smallKerningTable.ContainsKey(c12)) x -= smallKerningTable[c12];
}
Dictionary<string, float> smallKerningTable = new Dictionary<string, float>();
void initKerningTable()
{
smallKerningTable.Add("Te", 7f);
smallKerningTable.Add("LT", 8f);
smallKerningTable.Add("TA", 11f);
//..
}
This is how the background is created:
public Form1()
{
InitializeComponent();
Bitmap bmpCheck2 = new Bitmap(2, 2);
bmpCheck2.SetPixel(0, 0, Color.FromArgb(127, 127, 127, 0));
panel2.BackgroundImage = bmpCheck2;
panel2.BackgroundImageLayout = ImageLayout.Tile;
//..
}
If you want to use kerning you will need to build a much longer kerning table.
In real life typographers and font designers do that manually, looking hard at the glyphs, tweaking the kerning until it looks real good.
That is rather expensive and still doesn't cover font mixes.
So you may want to either
not use kerning after all. Make sure to use the StringFormat.GenericTypographic option both for measuring and for drawing the strings!
create a small kerning table for some of the especially problematic characters, like 'L', 'T', 'W', "V' and 'A'..
write code to create a full kerning table for all pairs you need or..
for all pairs
To write code to create a kerning table you would:
Create a Bitmap for each charcter
Iterate over all pairs and
move the second bitmap to the left until some non-transparent/ black pixels collide.
the moving should not got further than, say, half of the width, otherwise the distance should be reset to 0, because some character pairs will not collide at all and should not have any kerning, e.g.: '^_' or '.-'
If you want to mix fonts and /or FontStyles the key to the kerning table would have to be expanded to include some ID of the two respective fonts&styles the characters have..
Related
So in c# I have an image in a bitmap with different colors in it. Now i'm trying to only keep one single range of colors in the image and all the others can be removed (turn them into white pixels). Now the color I want to extract is yellow but just comparing the pixels their color with Color.Yellow won't suffice since the pixels can have different shades of yellow so I'm guessing I sort of need to filter out all the other colors but I can't seem to figure out on how to do it.
I've read something about convolution but I don't see a way to implement this directly into the program.
Is there a way that makes this possible that I only keep the color yellow and it's diffent shades in the image?
Thanks in advance.
Here is a fast and simple solution.
It uses a function, which will plug-in with a post you can find here.
This is the function:
public Color ToWhiteExceptYellow(Color c, int range)
{
float hueC = c.GetHue();
float e = 1.5f * range; // you can adapt this nuumber
float hueY = Color.Yellow.GetHue();
float delta = hueC - hueY;
bool ok = (Math.Abs(delta) < e);
//if (!ok) { ok = (Math.Abs(360 + delta) < e); } // include these lines ..
//if (!ok) { ok = (Math.Abs(360 - delta) < e); } // for reddish colors!
return ok ? c : Color.White;
}
It works well with yellow but as color hues is a wraparound number it will need more code to work with the wrap point color (red). I have included two lines to help out.
To make it work change these lines in the linked post:
// pick one of our filter methods
ModifyHue hueChanger = new ModifyHue(ToWhiteExceptYellow);
..and..
// we pull the bitmap from the image
Bitmap bmp = new Bitmap( (Bitmap)pictureBox1.Image); // create a copy
..and..
c = hueChanger(c, trackBar1.Value); // insert a number you like, mine go from 1-10
..and..:
// we need to re-assign the changed bitmap
pictureBox2.Image = (Bitmap)bmp; // show in a 2nd picturebox
Don't forget to include the delegate:
public delegate Color ModifyHue(Color c, int ch);
and the using clause:
using System.Drawing.Imaging;
Note that one ought to dispose of the old content to avoid leaking the images, maybe like so:
Bitmap dummy = (Bitmap )pictureBox2.Image;
pictureBox2.Image = null;
if (dummy != null) dummy.Dispose;
// now assign the new image!
Let's see it at work:
Feel free to expand on this. You could change the function's signature to include a target color and add ranges for brightness and/or saturation..
very vague definition,
if i understood what you want to do, i'd do it like this:
Iterate over every pixel of the bitmap and compare it to the yellow range (if outside - assign white value)
Convert every pixels RGB value to CMYK (search online for a conversion formula) [Y = (1-Blue-Black) / (1-Black)]
Assign white value if YellowMin
Convolution wont help you, it acts in spacial domain not in color
I have some text in a pdf that has been OCR'ed.
The OCR returns the bounding boxes of the words to me.
I'm able to draw the bounding boxes (wordRect) on the pdf and everything seems correct.
But when i tell my fontsize to be the height of these bounding boxes,
it all goes wrong. The text appears way smaller than it should be and doesn't match the height.
There's some conversion i am missing. How can i make sure the text is as high as the bounding boxes?
pdftron.PDF.Font font = pdftron.PDF.Font.Create(convertedPdf.GetSDFDoc(), pdftron.PDF.Font.StandardType1Font.e_helvetica);
for (int j = 0; j < ocrStream.pr_WoordList.Count; j++)
{
wordRect = (Rectangle) ocrStream.pr_Rectangles[j];
Element textBegin = elementBuilder.CreateTextBegin();
gStateTextRun = textBegin.GetGState();
gStateTextRun.SetTextRenderMode(GState.TextRenderingMode.e_stroke_text);
elementWriter.WriteElement(textBegin);
fontSize = wordRect.Height;
double descent;
if (hasColorImg)
{
descent = (-1 * font.GetDescent() / 1000d) * fontSize;
textRun = elementBuilder.CreateTextRun((string)ocrStream.pr_WoordList[j], font, fontSize);
//translate the word to its correct position on the pdf
//the bottom line of the wordrectangle is the baseline for the font, that's why we need the descender
textRun.SetTextMatrix(1, 0, 0, 1, wordRect.Left, wordRect.Bottom + descent );
How can i make sure the text is as high as the bounding boxes?
The font_size is just a scaling factor, which in most cases does map to 1/72 inch (pt), but not always.
The transformations are:
GlyphSpace -> TextSpace -> UserSpace (where UserSpace is essentially the page space, and is 1/72 inch)
The glyphs in the font are defined in GlyphSpace, and there is a font matrix that maps to TextSpace. Typically, 1000 units maps to 1 unit in test space, but not always.
Then the text matrix (element.SetTextMatrix), the font size (variable in question here) and some additional parameters, transform TextSpace coordinates to UserSpace.
In the end though, the exact height, depends on the glyph also.
This forum post shows how to go from the glyph data, to UserSpace. See ProcessElements
https://groups.google.com/d/msg/pdfnet-sdk/eOATUHGFyqU/6tsUF0BHukkJ
transform pdf points to pixels, worked correctly:
point-to-pixel = 1/72*300(DPI)
getting each text chunk positions (X,Y) in PDF the Y is calculated from
bottom-to-top, not as in standard html or java Script.
to get the Y value from top-to-down , cause not accurate Y position as in
html style , or win Form style.
how to get the correct Y top-to-down using any Page height, or rect mediaBox
or cropBox or rect textMarging finder ?
the code I used is your example of :
public class LocationTextExtractionStrategyClass : LocationTextExtractionStrategy
{
//Hold each coordinate
public List<RectAndText> myPoints = new List<RectAndText>();
/*
//The string that we're searching for
public String TextToSearchFor { get; set; }
//How to compare strings
public System.Globalization.CompareOptions CompareOptions { get; set; }
public MyLocationTextExtractionStrategy(String textToSearchFor, System.Globalization.CompareOptions compareOptions = System.Globalization.CompareOptions.None)
{
this.TextToSearchFor = textToSearchFor;
this.CompareOptions = compareOptions;
}
*/
//Automatically called for each chunk of text in the PDF
public override void RenderText(TextRenderInfo renderInfo)
{
base.RenderText(renderInfo);
//See if the current chunk contains the text
var startPosition = 0;// System.Globalization.CultureInfo.CurrentCulture.CompareInfo.IndexOf(renderInfo.GetText(), this.TextToSearchFor, this.CompareOptions);
//If not found bail
if (startPosition < 0)
{
return;
}
//Grab the individual characters
var chars = renderInfo.GetCharacterRenderInfos().ToList();//.Skip(startPosition).Take(this.TextToSearchFor.Length)
var charsText = renderInfo.GetText();
//Grab the first and last character
var firstChar = chars.First();
var lastChar = chars.Last();
//Get the bounding box for the chunk of text
var bottomLeft = firstChar.GetDescentLine().GetStartPoint();
var topRight = lastChar.GetAscentLine().GetEndPoint();
//Create a rectangle from it
var rect = new iTextSharp.text.Rectangle(
bottomLeft[Vector.I1],
bottomLeft[Vector.I2],
topRight[Vector.I1],
topRight[Vector.I2]
);
BaseColor curColor = new BaseColor(0f, 0f, 0f);
if (renderInfo.GetFillColor() != null)
curColor = renderInfo.GetFillColor();
//Add this to our main collection
myPoints.Add(new RectAndText(rect, charsText, curColor));//this.TextToSearchFor));
}
}//end-of-txtLocation-class//
You are asking many different questions in one post.
First let's start with the coordinate system in the PDF standard. Observe that I am talking about a standard, more specifically about ISO 32000. The coordinate system on a PDF page is explained in my answer to the Stack Overflow question How should I interpret the coordinates of a rectangle in PDF?
As you can see, a rectangle drawn in a PDF using a coordinate (llx, lly) for the lower-left corner and a coordinate (urx, ury) for the upper-right corner, assumes that the X-axis points to the right, and the Y-axis points upwards.
As for the width and the height of a page, that's explained in my answer to the Stack Overflow question How to Get PDF page width and Height?
For instance: you could have a /MediaBox that is defined as [0 0 595 842], and therefore measures 595 x 842 points (an A4 page), but that has a /CropBox that is defined as [5 5 590 837], which means that the visible area is only 585 x 832 points.
You also shouldn't assume that the lower-left corner of a page coincides with the (0, 0) coordinate. See Where is the Origin (x,y) of a PDF page?
When you create a document from scratch, a default margin of half an inch is used if you don't define a margin yourself. If you want to change the default, see Fit content on pdf size with iTextSharp?
Now for the height of a Chunk or, if you're using iText 7 (which you should, but —for some reason unknown to me— don't) the height of a Text object, this depends on the font size. The font size is an average size of the different glyphs in a font. If you look at the letter g, and you compare it with the letter h, you see that g takes more space under the baseline of the text than h, whereas h takes more space above the baseline than g.
If you want to calculate the exact space that is taken, read my answer to the question How to calculate the height of an element?
If the text snippet is used in the context of lines in a paragraph, you also have to take the leading into account: Changing text line spacing (Maybe that's not relevant in the context of your question, but it's good to know.)
If you have Chunk objects in iText 5, and you want to do specific things with these Chunks, you might benefit from using page events. See How to draw a line every 25 words?
If you want to add a colored background to a Chunk, it's even easier: How to set the paragraph of itext pdf file as rectangle with background color in Java
Update 1: All of the above may be irrelevant if you are looking to convert HTML to PDF. In that case, it's easy: use iText 7 + pdfHTML as described in Converting HTML to PDF using iText and all the Math is done by the pdfHTML add-on.
Update 2: There seems to be some confusion regarding the measurement units. The differences between user units, points and pixels is explained in the FAQ page How do the measurement systems in HTML relate to the measurement system in PDF?
Summarized:
1 in. = 25.4 mm = 72 user units by default (but it can be changed).
1 in. = 25.4 mm = 72 pt.
1 in. = 25.4 mm = 96 px.
Trying to get string width in C# to simulate wordwrap and position of text (now written in richTextBox).
Size of richTextBox is 555x454 px and I use monospaced font Courier New 12pt.
I tried TextRenderer.MeasureText() and also Graphics.MeasureString() methods.
TextRenderer was returning bigger values than Graphics so text which normally fits into one line, my code determined should be wrapped to other line.
But with using Graphics, on the other hand, my code determined that particular string is shorter than it is printed in original richTextBox so it was wrapped to next line in wrong place.
During debugging I found out that computed widths differs, which is strange because I use monospaced font so widths should be same for all characters. But I get something like this from Graphics.MeasureString()(example.: ' ' - 5.33333254, 'S' - 15.2239571, '\r' - 5.328125).
How can I ACCURATELY compute string width with C# and so simulate word wrap and determine particular text positions in pixels?
Why is width different in different characters when using monospaced font?
Note: I am working on personal Eye tracking project and I want to determine, where particular pieces of text was placed during experiment so I can tell on which words was user looking. For ex. at time t user was looking on point [256,350]px and I know that at this place there is call of method WriteLine.
My target visual stimulus is source code, with indents, tabs, line endings, placed in some editable text area (In the future maybe some simple online source code editor).
Here is my code:
//before method call
var font = new Font("Courier New", 12, GraphicsUnit.Point);
var graphics = this.CreateGraphics();
var wrapped = sourceCode.WordWrap(font, 555, graphics);
public static List<string> WordWrap(this string sourceCode, Font font, int width, Graphics g)
{
var wrappedText = new List<string>(); // output
var actualLine = new StringBuilder();
var actualWidth = 0.0f; // temp var for computing actual string length
var lines = Regex.Split(sourceCode, #"(?<=\r\n)"); // split input to lines and maintain line ending \r\n where they are
string[] wordsOfLine;
foreach (var line in lines)
{
wordsOfLine = Regex.Split(line, #"( |\t)").Where(s => !s.Equals("")).ToArray(); // split line by tabs and spaces and maintain delimiters separately
foreach (string word in wordsOfLine)
{
var wordWidth = g.MeasureString(word, font).Width; // compute width of word
if (actualWidth + wordWidth > width) // if actual line width is grather than width of text area
{
wrappedText.Add(actualLine.ToString()); // add line to list
actualLine.Clear(); // clear StringBuilder
actualWidth = 0; // zero actual line width
}
actualLine.Append(word); // add word to actual line
actualWidth += wordWidth; // add word width to actual line width
}
if (actualLine.Length > 0) // if there is something in actual line add it to list
{
wrappedText.Add(actualLine.ToString());
}
actualLine.Clear(); // clear vars
actualWidth = 0;
}
return wrappedText;
}
I believe that it is much easier to accomplish your task by obtaining a character under a given location on a screen. For example, if you're using the RichTextBox control, refer to the RichTextBox.GetCharIndexFromPosition method to get the index of the character nearest to the specified location. Here is some sample code that demonstrates the idea:
private void richTextBox1_MouseMove(object sender, MouseEventArgs e)
{
var textIndex = richTextBox1.GetCharIndexFromPosition(e.Location);
if (richTextBox1.Text.Length > 0)
label1.Text = richTextBox1.Text[textIndex].ToString();
}
So I finally added some things in my code. I will shorten it.
public static List<string> WordWrap(this string sourceCode, Font font, int width, Graphics g)
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
var format = StringFormat.GenericTypographic;
format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
var width = g.MeasureString(word, font, 0, format).Width;
}
With this corrections I get correct width of common characters (with use of monospaced font i get equal widths).
But there is still problem with other whitespaces like \t and \n where I get 0.0078125 and 9.6015625 when measuring width with Courier New, 12pt font. The second value is a width of any character typed with this font so it is not a big problem but it would be better to be 0 or am I wrong? If anybody have a suggestion to solve this problem leave a comment please.
I'm working on a project that has me approximating text rendered as an image and a DHTML editor for the text. The images are rendered using the .NET 4 DrawingContext object's DrawText method.
The DrawText method will take text along with font information as well as dimensions and calculate the wrapping necessary to get the text to fit as much as possible, placing an ellipsis at the end if the text is too long. So, if I have the following code to draw text in a Rectangle it will abbrevaiate it:
string longText = #"A choice of five engines, although the 2-liter turbo diesel, supposedly good for 48 m.p.g. highway, is not coming to America, at least for now. A 300-horsepower supercharged gasoline engine will likely be the first offered in the United States. All models will use start-stop technology, and fuel consumption will decrease by an average of 19 percent across the A6 lineup. A 245-horsepower A6 hybrid was also unveiled, but no decision has yet been made as to its North America sales prospects. Figure later in 2012, if sufficient demand is detected.";
var drawing = new DrawingGroup();
using (var context = drawing.Open())
{
var text = new FormattedText(longText,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Calibri"),
30,
Brushes.Green);
text.MaxTextHeight = myRect.Height;
text.MaxTextWidth = myRect.Width;
context.DrawText(text, new Point(0, 0));
}
var db = new DrawingBrush(drawing);
db.Stretch = Stretch.None;
myRect.Fill = db;
Is there a way to calculate how the text will be wrapped? In this example, the outputted text is wrapped at "2-liter" and "48 m.p.g" etc as seen in the image below:
You can use the Graphics.MeasureString(String, Font, Int32) function. You pass it the string, font, and maximum width. It returns a SizeF with the rectangle it would form. You can use this to get the overall height, and thus the number of lines:
Graphics g = ...;
Font f = new Font("Calibri", 30.0);
SizeF sz = g.MeasureString(longText, f, myRect.Width);
float height = sz.Height;
int lines = (int)Math.round(height / f.Height); // overall height divided by the line height = number of lines
There are many ways to get a Graphics object, and any will do since you are only using it to measure and not to draw (you may have to correct its DpiX, DpiY, and PageUnit fields since those effect measurements.
Ways to get a Graphics object:
Graphics g = e.Graphics; // in OnPaint, with PaintEventArgs e
Graphics g = x.CreateGrahics(); // where x is any Form or Control
Graphics g = Graphics.CreateFrom(img); // where img is an Image.
Not sure if you still need a solution or if this particular solution is appropriate for your application, but if you insert the below snippet just after your using block it will show you the text in each line (and therefore where the text was broken for wrapping).
I arrived at this solution using the very ghetto/guerrilla approach of just browsing properties while debugging, looking for the wrapped text segments - I found 'em and they were in accessible properties...so there you go. There very well may be a more proper/direct way.
// Object heirarchy:
// DrawingGroup (whole thing)
// - DrawingGroup (lines)
// - GlyphRunDrawing.GlyphRun.Characters (parts of lines)
// Note, if text is clipped, the ellipsis will be placed in its own
// separate "line" below. Give it a try and you'll see what I mean.
List<DrawingGroup> lines = drawing.Children.OfType<DrawingGroup>().ToList();
foreach (DrawingGroup line in lines)
{
List<char> lineparts = line.Children
.OfType<GlyphRunDrawing>()
.SelectMany(grd => grd.GlyphRun.Characters)
.ToList();
string lineText = new string(lineparts.ToArray());
Debug.WriteLine(lineText);
}
Btw, Hi David. :-)