Graphics.DrawString to multiple rectangles - c#

I am trying to write a function that needs to draw a string to an image. The image has anywhere from 1-5 textboxes, which each have a x,y, width, and height. These details are defined in an XML file which I am parsing, so I have access to these for each box.
My question is whether or not I can use the graphics.DrawString (or a similar) method to do this easily. The sample function below will create a rectangle with specified x,y, width, height, and then draw a string within. If the string doesn't fit, it truncates.
public void DrawStringRectangleFormat(Graphics g)
{
// Create string to draw.
String drawString = "Sample Text is too long to fit into this tiny lil rectangle area right here";
// Create font and brush.
Font drawFont = new Font("Arial", 16);
SolidBrush drawBrush = new SolidBrush(Color.Black);
// Create rectangle for drawing.
float x = 150.0F;
float y = 150.0F;
float width = 200.0F;
float height = 50.0F;
RectangleF drawRect = new RectangleF(x, y, width, height);
// Set format of string.
StringFormat drawFormat = new StringFormat();
drawFormat.Alignment = StringAlignment.Center;
// Draw string to screen.
g.DrawString(drawString, drawFont, drawBrush, drawRect, drawFormat);
}
What I want instead of this, is rather than truncating, it will stop at the last fitting word, and go to the next rectangle(textbox). This way I can use all the available textboxes.
Is there a method already made to do this? Otherwise I will need to implement my own drawString method.

OK, what you would have to do is loop through each char in the string, and concatenate to a final string..
so basically foreach (char c in mystring)...
then using measurestring, you check to see if the string is over the box length, if it is, start on the next rect...
https://msdn.microsoft.com/en-us/library/6xe5hazb(v=vs.110).aspx

This solution uses the StringFormat's settings to ensure that each call to DrawString only draws the words that fit. Then, Graphics.MeasureCharacterRanges calculates the words that don't fit into the rectangle, and the remaining text overflows into the next layout rectangle.
You might need to customize how the input string is split into words. Right now I'm just splitting it apart at whitespace boundaries.
using System.Text.RegularExpressions;
using System.Drawing;
/// <summary>
/// Draw a string using one or more layout rectangles. Words which don't fit will overflow into the next layout rectangle.
/// </summary>
private static void DrawOverflowString(Graphics graphics, string drawString, RectangleF[] layoutRectangles, StringAlignment alignment)
{
var drawFont = new Font("Arial", 16.0f);
var black = new SolidBrush(Color.Black);
var format = new StringFormat()
{
Alignment = alignment,
Trimming = StringTrimming.Word,
FormatFlags = StringFormatFlags.LineLimit
};
var wordRegex = new Regex("[^\\s]+");
string remainingText = drawString;
foreach (var layoutRect in layoutRectangles)
{
// Draw everything that will fit into this text box
graphics.DrawString(remainingText, drawFont, black, layoutRect, format);
// calculate which words did not fit
var wordMatches = wordRegex.Matches(remainingText);
var ranges = wordMatches.OfType<Match>().Select(x => new CharacterRange(x.Index, x.Length)).ToArray();
format.SetMeasurableCharacterRanges(ranges);
var wordRegions = graphics.MeasureCharacterRanges(remainingText, drawFont, layoutRect, format);
var allfit = true;
for (int i = 0; i < wordRegions.Length; i++)
{
if (wordRegions[i].GetBounds(graphics).Width == 0.0f)
{
allfit = false;
remainingText = remainingText.Substring(wordMatches[i].Index);
break;
}
}
if (allfit)
break;
}
drawFont.Dispose();
black.Dispose();
}

protected override void OnPaint(PaintEventArgs e)
{
// Call the OnPaint method of the base class.
base.OnPaint(e);
List<Rectanglestring> testrecs = new List<Rectanglestring>();
testrecs.Add(new Rectanglestring { targetrect = new Rectangle(0, 12, 40, 12), whattodraw = "" });
testrecs.Add(new Rectanglestring {targetrect= new Rectangle(0, 25, 35, 12),whattodraw="" });
testrecs.Add(new Rectanglestring { targetrect = new Rectangle(0, 35, 35, 12), whattodraw = "" });
testrecs.Add(new Rectanglestring { targetrect = new Rectangle(0, 45, 35, 12), whattodraw = "" });
testrecs.Add(new Rectanglestring { targetrect = new Rectangle(0, 65, 35, 12), whattodraw = "" });
testrecs.Add(new Rectanglestring { targetrect = new Rectangle(0, 85, 35, 12), whattodraw = "" });
testrecs.Add(new Rectanglestring { targetrect = new Rectangle(0, 95, 55, 12), whattodraw = "" });
string mystringtofit = "This is just an example";
foreach (Rectanglestring rect in testrecs)
{
for (int i = 0; i < mystringtofit.Length; i++)
{
if (mystringtofit[i] == ' ' && rect.whattodraw.Length > 0) break;
if (mystringtofit[i] == ' ') continue;
string teststring = rect.whattodraw + mystringtofit[i];
SizeF stringSize = stringSize = e.Graphics.MeasureString(teststring, new Font("Ariel", 12));
if (stringSize.Width >= rect.targetrect.Width) break;
rect.whattodraw += mystringtofit[i];
}
mystringtofit = mystringtofit.Substring(rect.whattodraw.Length);
if (mystringtofit.StartsWith(" "))
{
mystringtofit = mystringtofit.Substring(1);
}
e.Graphics.DrawString(rect.whattodraw, Font, new SolidBrush(ForeColor), rect.targetrect);
}
// Call methods of the System.Drawing.Graphics object.
}
public class Rectanglestring
{
public Rectangle targetrect = new Rectangle();
public string whattodraw = "";
}

This is what I have. It does what I described. Thanks for the answers.
public void DrawStringInTextboxes(string text, Graphics g)
{
String drawString = text;
PrivateFontCollection fontCollection = new PrivateFontCollection();
fontCollection.AddFontFile(System.Web.Hosting.HostingEnvironment.MapPath("~/Content/Fonts/Squidgingtons.ttf"));
var squidingtonsFontFamily = fontCollection.Families[0];
Font squidingtons = new Font(squidingtonsFontFamily, textParameters[0].MaxFontSize);
Font drawFont = new Font("Arial", 60);
SolidBrush drawBrush = new SolidBrush(Color.Black);
StringFormat drawFormat = new StringFormat();
drawFormat.Alignment = StringAlignment.Center;
char[] delimiterChars = { ' ' };
string[] words = drawString.Split(delimiterChars);
string finalString = "";
int textBoxIndex = 0;
foreach (string word in words)
{
//set the dimensions for the first textbox and create a rectangle with those specifications.
float x = textParameters[textBoxIndex].Left;
float y = textParameters[textBoxIndex].Top;
float width = textParameters[textBoxIndex].Width;
float height = textParameters[textBoxIndex].Height;
RectangleF Rect = new RectangleF(x, y, width, height);
//if the current finalString + the next word fits in the current box, add the word to finalString.
if (g.MeasureString(finalString + word + " ", squidingtons).Width < textParameters[textBoxIndex].Width)
{
finalString = finalString + " " + word;
//if this is the last word, print the finalString and we are done.
if (word == words[words.Length - 1])
{
g.DrawString(finalString, squidingtons, drawBrush, Rect, drawFormat);
break;
}
}
//the current finalString + next word did not fit in the box. Draw what we have to the first box.
else {
g.DrawString(finalString, squidingtons, drawBrush, Rect, drawFormat);
//Hold onto the word that didnt fit. It will be the first word of the next box.
finalString = word;
if (textBoxIndex +1 >= textParameters.Length)
{
//if we are out of textboxes, we are done.
break;
}
else
{
//move on to the next textbox. The loop begins again with new specifications set for the textbox.
textBoxIndex++;
}
}
}
squidingtons.Dispose();
drawBrush.Dispose();
drawFont.Dispose();
}

Related

System.Drawing: Texts are not top-aligned with DrawString

In windows application(C#), When draw string using DrawString function of System.Drawing.Graphics class. But values are not properly aligned with other values though X and Y coordinates are the same. It behaves differently for different fonts with different font sizes. I have considered an internal leading and deduct it from Y coordinate but still is not working for all font types.
In my example, I want all text part to be top-aligned with the horizontal line. But for different fonts and font size, It is not aligned properly. It is working for first font(Avenir Black) but not for others.
Below is the code I am using to generate this printed document:
static void PrintDocument(string filePath)
{
// Create new document
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(delegate (Object sender, PrintPageEventArgs e)
{
//e.Graphics.Clear(Color.White);
var Canvas = e.Graphics;
var rectF = new RectangleF(0f, 100, 1000, 78.74016f);
Pen p = new Pen(Color.Black);
p.Width = 1 / 2;
p.DashStyle = DashStyle.Solid;
Canvas.DrawLine(p, rectF.X, rectF.Y, rectF.X + rectF.Width, rectF.Y);
rectF = new RectangleF(0f, 100, 250, 78.74016f);
DrawPrice(Canvas, "Avenir Black", ref rectF);
rectF.X += 20;
rectF.Y = 100;
DrawPrice(Canvas, "Arial", ref rectF);
rectF.X += 20;
rectF.Y = 100;
DrawPrice(Canvas, "Calibri", ref rectF);
rectF.X += 20;
rectF.Y = 100;
DrawPrice(Canvas, "Times New Roman", ref rectF);
});
pd.Print();
Process.Start(filePath);
}
private static void DrawPrice(Graphics Canvas, string fontName, ref RectangleF rectF)
{
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
var superFont = new Font(fontName, 20, new FontStyle());
var font = new Font(fontName, 61, new FontStyle());
Brush brush = new SolidBrush(Color.FromName("black"));
var currencySymbol = "$";
var mainPrice = "29";
var cents = "50";
var superFontMetrics = FontInfo(Canvas, superFont, GraphicsUnit.Point);
var fontMetrics = FontInfo(Canvas, font, GraphicsUnit.Point);
var symbolSize = Canvas.MeasureString(currencySymbol, superFont, rectF.Size, format);
var centsSize = Canvas.MeasureString(cents, superFont, rectF.Size, format);
var dollarSize = Canvas.MeasureString(mainPrice, font, rectF.Size, format);
Canvas.DrawString(currencySymbol, superFont, brush, new RectangleF(rectF.X, rectF.Y - superFontMetrics.InternalLeading, symbolSize.Width, symbolSize.Height), format);
rectF.X += symbolSize.Width;
Canvas.DrawString(mainPrice, font, brush, new RectangleF(rectF.X, rectF.Y, dollarSize.Width, rectF.Height), format);
var mainPriceX = rectF.X + (dollarSize.Width / 2);
rectF.X += dollarSize.Width;
Canvas.DrawString(cents, superFont, brush, new RectangleF(rectF.X, rectF.Y - superFontMetrics.InternalLeading, centsSize.Width, centsSize.Height), format);
rectF.Y += rectF.Height;
rectF.X += centsSize.Width;
var titleFont = new Font(fontName, 5, new FontStyle());
var titleTextSize = Canvas.MeasureString(fontName, titleFont, rectF.Size, format);
Canvas.DrawString(fontName, titleFont, brush, new RectangleF(mainPriceX, rectF.Y, titleTextSize.Width, titleTextSize.Height), format);
}
public static FontMetrics FontInfo(Graphics gr, Font font, GraphicsUnit toUnit)
{
FontMetrics metrics = new FontMetrics();
float em_height = font.FontFamily.GetEmHeight(font.Style);
metrics.EmHeight = ConvertUnits(gr, font.Size, font.Unit, toUnit);
float design_to_points = metrics.EmHeight / em_height;
metrics.Ascent = design_to_points * font.FontFamily.GetCellAscent(font.Style);
metrics.Descent = design_to_points * font.FontFamily.GetCellDescent(font.Style);
metrics.CellHeight = metrics.Ascent + metrics.Descent;
metrics.InternalLeading = metrics.CellHeight - metrics.EmHeight;
metrics.LineSpacing = design_to_points * font.FontFamily.GetLineSpacing(font.Style);
metrics.ExternalLeading = metrics.LineSpacing - metrics.CellHeight;
return metrics;
}
After using GraphicsPath >> AddString method to print string, I got the result I want i.e. printed texts are top aligned for all type of fonts. Here is the output:
Below is the code I used:
private void DrawStringByGraphicsPath(Graphics g, Font font, Brush brush, ref RectangleF rectF, RectangleF elementRectF, StringFormat format, string text)
{
if (!string.IsNullOrEmpty(text))
{
using (GraphicsPath graphicsPath = new GraphicsPath())
{
graphicsPath.AddString(text, font.FontFamily, (int)font.Style, GetEMSize(g, font), elementRectF.Location, format);
// this is the net bounds without any whitespace:
RectangleF br = graphicsPath.GetBounds();
// Transform it for top alignment
g.TranslateTransform(elementRectF.X - br.X, (elementRectF.Y - br.Y));
g.FillPath(brush, graphicsPath);
g.ResetTransform();
}
}
}
private float GetEMSize(Graphics canvas, Font font)
{
return (font.SizeInPoints * (font.FontFamily.GetCellAscent(font.Style) + font.FontFamily.GetCellDescent(font.Style))) / font.FontFamily.GetEmHeight(font.Style);
}

How to rotate a text in rectangle shape C# [duplicate]

This question already has answers here:
Drawing a Rotated Text to an Image in C#
(2 answers)
Closed 7 years ago.
I have made an application which generates me a QR Code in a PNG image, and prints out the text from QR image, but now I need to rotate that text 90 degrees and I can't find a way to do this...I think that the rectangle must be rotated because the text it's inside this rectangle.
Example:
Code:
namespace QR_Code_with_WFA
{
public void CreateQRImage(string inputData)
{
if (inputData.Trim() == String.Empty)
{
System.Windows.Forms.MessageBox.Show("Data must not be empty.");
}
BarcodeWriter qrcoder = new ZXing.BarcodeWriter
{
Format = BarcodeFormat.QR_CODE,
Options = new ZXing.QrCode.QrCodeEncodingOptions
{
ErrorCorrection = ZXing.QrCode.Internal.ErrorCorrectionLevel.H,
Height = 250,
Width = 250
}
};
string tempFileName = System.IO.Path.GetTempPath() + inputData + ".png";
Image image;
String data = inputData;
var result = qrcoder.Write(inputData);
image = new Bitmap(result);
image.Save(tempFileName);
System.Diagnostics.Process.Start(tempFileName);
var result2 = qrcoder.Write(inputData);
int textWidth = 200, textHeight = 20;
// creating new bitmap having imcreased width
var img = new Bitmap(result2.Width + textWidth, result2.Height);
using (var g = Graphics.FromImage(img))
using (var font = new Font(FontFamily.GenericMonospace, 12))
using (var brush = new SolidBrush(Color.Black))
using (var bgBrush = new SolidBrush(Color.White))
using (var format = new StringFormat() { Alignment = StringAlignment.Near })
{
// filling background with white color
g.FillRectangle(bgBrush, 0, 0, img.Width, img.Height);
// drawing your generated image over new one
g.DrawImage(result, new Point(0,0));
// drawing text
g.DrawString(inputData, font, brush, result2.Width, (result2.Height - textHeight) / 2, format);
}
img.Save(tempFileName);
}
}
You need to apply a RotateTransform on the Graphics object before drawing the text:
// Change alignment to center so you don't have to do the math yourself :)
using (var format = new StringFormat() { Alignment = StringAlignment.Center })
{
...
// Translate to the point where you want the text
g.TranslateTransform(result2.Width, result2.Height / 2);
// Rotation happens around that point
g.RotateTransform(-90);
// Note that we draw on [0, 0] because we translated our coordinates already
g.DrawString(inputData, font, brush, 0, 0, format);
// When done, reset the transform
g.ResetTransform();
}
You have to rotate full Graphics:
https://msdn.microsoft.com/en-us/library/a0z3f662(v=vs.110).aspx
Similar topic:
Rotated text align in C#

How to Add the values of List<string> into the List<PictureBox> after encoding it to Barcode in c#

I have two List one is of string and the other is of PictureBox type. I want to take the values of List of type string and the convert it into the Barcode then save it to the List of type PictureBox.
I am doing it like this right now:
List<System.Windows.Forms.PictureBox> PictureBoxList = new List<System.Windows.Forms.PictureBox>();
List<string> SerialNumberList = new List<string>();
int SerialNumberStart = 0;
for(int i = 0; i < 10 ; i++)
{
SerialNumberStart++;
SerialNumberList.Add("S" + SerialNumberStart);
}
private void PrintButton_Click(object sender, EventArgs e)
{
for(int j =0 ; j < SerialNumberList.Count ; j++)
{
BarcodeLib.TYPE barcodetype1 = BarcodeLib.TYPE.CODE39;
BarcodeLib.Barcode bar1 = new BarcodeLib.Barcode();
bar1.IncludeLabel = true;
PictureBoxList[j].Image = bar1.Encode(barcodetype1 ,SerialNumberList[j]); // It gives me exception of Index out of range
PictureBoxList.Add(PictureBoxList[j]);
printDocument1.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(printDocument1_PrintPage);
printDocument1.Print();
}
}
private void PrintDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
Bitmap myBitmap1 = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.DrawToBitmap(myBitmap1, new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Height));
e.Graphics.DrawImage(myBitmap1, 0, 0);
myBitmap1.Dispose();
}
My first question is that How can I convert the string into the PictureBox.
And then converting each item of the PictureBox to the Bitmap and then printing all of the bitmaps now the code only Prints one barcode
you want like this.. right?? see this is the representaion of the this string "S1253551" in 3of9 and plain text and finally as image right??
public Image stringToImage(string inputString)
{
string text = inputString.Trim();
Bitmap bmp = new Bitmap(1, 1);
//Set the font style of output image
Font font = new Font("Free 3 of 9", 25, FontStyle.Regular, GraphicsUnit.Pixel);
Font font2 = new Font("Arial", 15, FontStyle.Regular, GraphicsUnit.Pixel);
Graphics graphics = Graphics.FromImage(bmp);
int width = (int)graphics.MeasureString(text, font).Width;
int height = (int)graphics.MeasureString(text, font).Height;
int height2 = (int)graphics.MeasureString(text, font2).Height;
bmp = new Bitmap(bmp, new Size(width, height+height2));
graphics = Graphics.FromImage(bmp);
//Specify the background color of the image
graphics.Clear(Color.Cyan);
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
//Specify the text, font, Text Color, X position and Y position of the image
graphics.DrawString(text, font, new SolidBrush(Color.Black), 0, 0);
graphics.DrawString(text, font2, new SolidBrush(Color.Black), 0, height);
graphics.Flush();
graphics.Dispose();
//if you want to save the image uncomment the below line.
//bmp.Save(#"d:\myimage.jpg", ImageFormat.Jpeg);
return bmp;
}
Remember you must have installed "free 3 of 9" font.
you pass the string "S1253551" and it generate the barcode and add the plain text at bottom and finally return it as image.
Its working code i have tried at my end. Enjoy. :)
Download the working code from here Download
mean you have string and you have converted it to barcode. finally we have a property in barcode which holds the value of string right?? now you want to display that string as image??
if so then refer the below code -
public Image stringToImage(string inputString)
{
string text = inputString.Trim();
Bitmap bmp = new Bitmap(1, 1);
//Set the font style of output image
Font font = new Font("Arial", 25, FontStyle.Regular, GraphicsUnit.Pixel);
Graphics graphics = Graphics.FromImage(bmp);
int width = (int)graphics.MeasureString(text, font).Width;
int height = (int)graphics.MeasureString(text, font).Height;
bmp = new Bitmap(bmp, new Size(width, height));
graphics = Graphics.FromImage(bmp);
//Specify the background color of the image
graphics.Clear(Color.Cyan);
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
//Specify the text, font, Text Color, X position and Y position of the image
graphics.DrawString(text, font, new SolidBrush(Color.Black), 0, 0);
graphics.Flush();
graphics.Dispose();
//if you want to save the image uncomment the below line.
//bmp.Save(#"d:\myimage.jpg", ImageFormat.Jpeg);
return bmp;
}
I believe, if bar1.Encode actually has return type of Image, this lines in PrintButton_Click method :
PictureBoxList[j].Image = bar1.Encode(barcodetype1 ,SerialNumberList[j]); // It gives me exception of Index out of range
PictureBoxList.Add(PictureBoxList[j]);
should be like this :
var pictureBox = new PictureBox();
pictureBox.Image = bar1.Encode(barcodetype1 ,SerialNumberList[j]);
PictureBoxList.Add(pictureBox);
UPDATE :
To make it clear, my answer above meant PrintButton_Click should be as follow :
private void PrintButton_Click(object sender, EventArgs e)
{
for(int j =0 ; j < SerialNumberList.Count ; j++)
{
BarcodeLib.TYPE barcodetype1 = BarcodeLib.TYPE.CODE39;
BarcodeLib.Barcode bar1 = new BarcodeLib.Barcode();
bar1.IncludeLabel = true;
var pictureBox = new PictureBox();
pictureBox.Image = bar1.Encode(barcodetype1 ,SerialNumberList[j]);
PictureBoxList.Add(pictureBox);
printDocument1.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(printDocument1_PrintPage);
printDocument1.Print();
}
}

Auto Resize Font to fit rectangle

How can I create in .NET 4.5 / C# a font with a adaptive size to fit a specified rectangle ?
I have a resize based on the string length, the longer the string, the smaller the fontsize, but it does not work very well, if the string is too long the text gets very small. The problem with this method is that if I change the rectangle size all the font sizes are not good again.
It's been a while but I came across this issue.
MSDN offers a sample with a GetAdjustedFont method to help with this process:
(shamelessly stolen from https://msdn.microsoft.com/en-us/library/bb986765.aspx)
public Font GetAdjustedFont(Graphics g, string graphicString, Font originalFont, int containerWidth, int maxFontSize, int minFontSize, bool smallestOnFail)
{
Font testFont = null;
// We utilize MeasureString which we get via a control instance
for (int adjustedSize = maxFontSize; adjustedSize >= minFontSize; adjustedSize--)
{
testFont = new Font(originalFont.Name, adjustedSize, originalFont.Style);
// Test the string with the new size
SizeF adjustedSizeNew = g.MeasureString(graphicString, testFont);
if (containerWidth > Convert.ToInt32(adjustedSizeNew.Width))
{
// Good font, return it
return testFont;
}
}
// If you get here there was no fontsize that worked
// return minimumSize or original?
if (smallestOnFail)
{
return testFont;
}
else
{
return originalFont;
}
}
With Graphics.MeasureString you can measure the size of a string, so you can calculate what you need.
Sample from MSDN
private void MeasureStringMin(PaintEventArgs e)
{
// Set up string.
string measureString = "Measure String";
Font stringFont = new Font("Arial", 16);
// Measure string.
SizeF stringSize = 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));
}
You can measure the string width with whatever font size (e.g. 16), and then you can calculate your desired font size like this:
fontSize = (previouslyMeasuredFontSize * targetWidthOfString) / previouslyMeasuredStringWidth;
I also needed something similar. I created some code to answer the need of drawing text inside a defined rectangle with several auto font size options (see DrawMethod enum). It also supports auto warp with and without auto font size. My solution was inspired by the answers above.
Be sure to add WindowsBase and PresentationCore assemblies.
DrawTextToBitmap.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ReportPrepare
{
class TextDrawing
{
public enum DrawMethod
{
AutosizeAccordingToText, // create the smallest bitmap needed to draw the text without word warp
AutoFitInConstantRectangleWithoutWarp, // draw text with the biggest font possible while not exceeding rectangle dimensions, without word warp
AutoWarpInConstantRectangle, // draw text in rectangle while performing word warp. font size is a constant input. drawing may exceed bitmap rectangle.
AutoFitInConstantRectangleWithWarp // draw text with the biggest font possible while not exceeding rectangle dimensions, with word warp
}
private static void SetGraphicsHighQualityForTextRendering(Graphics g)
{
// The smoothing mode specifies whether lines, curves, and the edges of filled areas use smoothing (also called antialiasing). One exception is that path gradient brushes do not obey the smoothing mode. Areas filled using a PathGradientBrush are rendered the same way (aliased) regardless of the SmoothingMode property.
g.SmoothingMode = SmoothingMode.AntiAlias;
// The interpolation mode determines how intermediate values between two endpoints are calculated.
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Use this property to specify either higher quality, slower rendering, or lower quality, faster rendering of the contents of this Graphics object.
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
// This one is important
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
}
public static Size MeasureDrawTextBitmapSize(string text, Font font)
{
Bitmap bmp = new Bitmap(1, 1);
using (Graphics g = Graphics.FromImage(bmp))
{
SizeF size = g.MeasureString(text, font);
return new Size((int)(Math.Ceiling(size.Width)), (int)(Math.Ceiling(size.Height)));
}
}
public static int GetMaximumFontSizeFitInRectangle(string text, Font font, RectangleF rectanglef, bool isWarp, int MinumumFontSize=6, int MaximumFontSize=1000)
{
Font newFont;
Rectangle rect = Rectangle.Ceiling(rectanglef);
for (int newFontSize = MinumumFontSize; ; newFontSize++)
{
newFont = new Font(font.FontFamily, newFontSize, font.Style);
List<string> ls = WarpText(text, newFont, rect.Width);
StringBuilder sb = new StringBuilder();
if (isWarp)
{
for (int i = 0; i < ls.Count; ++i)
{
sb.Append(ls[i] + Environment.NewLine);
}
}
else
{
sb.Append(text);
}
Size size = MeasureDrawTextBitmapSize(sb.ToString(), newFont);
if (size.Width > rectanglef.Width || size.Height > rectanglef.Height)
{
return (newFontSize - 1);
}
if (newFontSize >= MaximumFontSize)
{
return (newFontSize - 1);
}
}
}
public static List<string> WarpText(string text, Font font, int lineWidthInPixels)
{
string[] originalLines = text.Split(new string[] { " " }, StringSplitOptions.None);
List<string> wrappedLines = new List<string>();
StringBuilder actualLine = new StringBuilder();
double actualWidthInPixels = 0;
foreach (string str in originalLines)
{
Size size = MeasureDrawTextBitmapSize(str, font);
actualLine.Append(str + " ");
actualWidthInPixels += size.Width;
if (actualWidthInPixels > lineWidthInPixels)
{
actualLine = actualLine.Remove(actualLine.ToString().Length - str.Length - 1, str.Length);
wrappedLines.Add(actualLine.ToString());
actualLine.Clear();
actualLine.Append(str + " ");
actualWidthInPixels = size.Width;
}
}
if (actualLine.Length > 0)
{
wrappedLines.Add(actualLine.ToString());
}
return wrappedLines;
}
public static Bitmap DrawTextToBitmap(string text, Font font, Color color, DrawMethod mode, RectangleF rectanglef)
{
StringFormat drawFormat = new StringFormat();
Bitmap bmp;
switch (mode)
{
case DrawMethod.AutosizeAccordingToText:
{
Size size = MeasureDrawTextBitmapSize(text, font);
if (size.Width == 0 || size.Height == 0)
{
bmp = new Bitmap(1, 1);
}
else
{
bmp = new Bitmap(size.Width, size.Height);
}
using (Graphics g = Graphics.FromImage(bmp))
{
SetGraphicsHighQualityForTextRendering(g);
g.DrawString(text, font, new SolidBrush(color), 0, 0);
return bmp;
}
}
case DrawMethod.AutoWarpInConstantRectangle:
{
Rectangle rect = Rectangle.Ceiling(rectanglef);
bmp = new Bitmap(rect.Width,rect.Height);
if (rect.Width == 0 || rect.Height == 0)
{
bmp = new Bitmap(1, 1);
}
else
{
bmp = new Bitmap(rect.Width, rect.Height);
}
using (Graphics g = Graphics.FromImage(bmp))
{
SetGraphicsHighQualityForTextRendering(g);
g.DrawString(text, font, new SolidBrush(color), rectanglef, drawFormat);
return bmp;
}
}
case DrawMethod.AutoFitInConstantRectangleWithoutWarp:
{
Rectangle rect = Rectangle.Ceiling(rectanglef);
bmp = new Bitmap(rect.Width, rect.Height);
if (rect.Width == 0 || rect.Height == 0)
{
bmp = new Bitmap(1, 1);
}
else
{
bmp = new Bitmap(rect.Width, rect.Height);
}
using (Graphics g = Graphics.FromImage(bmp))
{
int fontSize = GetMaximumFontSizeFitInRectangle(text, font, rectanglef, false);
SetGraphicsHighQualityForTextRendering(g);
g.DrawString(text, new Font(font.FontFamily, fontSize,font.Style, GraphicsUnit.Point), new SolidBrush(color), rectanglef, drawFormat);
return bmp;
}
}
case DrawMethod.AutoFitInConstantRectangleWithWarp:
{
Rectangle rect = Rectangle.Ceiling(rectanglef);
if (rect.Width == 0 || rect.Height == 0)
{
bmp = new Bitmap(1, 1);
}
else
{
bmp = new Bitmap(rect.Width, rect.Height);
}
using (Graphics g = Graphics.FromImage(bmp))
{
int fontSize = GetMaximumFontSizeFitInRectangle(text, font, rectanglef, true);
SetGraphicsHighQualityForTextRendering(g);
g.DrawString(text, new Font(font.FontFamily, fontSize, font.Style, GraphicsUnit.Point), new SolidBrush(color), rectanglef, drawFormat);
return bmp;
}
}
}
return null;
}
}
}
Usage Example:
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
// add WindowsBase and PresentationCore assemblies
namespace ReportPrepare
{
public partial class Form1 : Form
{
PictureBox picbox = new PictureBox();
int i = 0;
Timer t = new Timer();
public Form1()
{
InitializeComponent();
this.Controls.Add(picbox);
picbox.Dock = DockStyle.Fill;
t.Interval = 5000;
t.Tick += t_Tick;
t.Enabled = true;
this.Shown += Form1_Shown;
this.SizeChanged += Form1_SizeChanged;
this.Size = new Size(812, 400);
this.StartPosition = FormStartPosition.CenterScreen;
}
void Form1_Shown(object sender, EventArgs e)
{
DrawText();
}
void t_Tick(object sender, EventArgs e)
{
i++;
if (i > 3)
{
i = 0;
}
DrawText();
}
private void DrawText()
{
// text and font
string text = "one two three four five six seven eight nine ten eleven twelve";
Font font = new System.Drawing.Font("Arial", 30, FontStyle.Regular, GraphicsUnit.Point);
switch (i)
{
case 0:
picbox.Image = TextDrawing.DrawTextToBitmap(text, font, Color.Red, TextDrawing.DrawMethod.AutosizeAccordingToText, new RectangleF(0, 0, picbox.Width, picbox.Height));
break;
case 1:
picbox.Image = TextDrawing.DrawTextToBitmap(text, font, Color.Red, TextDrawing.DrawMethod.AutoFitInConstantRectangleWithoutWarp, new RectangleF(0, 0, picbox.Width, picbox.Height));
break;
case 2:
picbox.Image = TextDrawing.DrawTextToBitmap(text, font, Color.Red, TextDrawing.DrawMethod.AutoWarpInConstantRectangle, new RectangleF(0, 0, picbox.Width, picbox.Height));
break;
case 3:
picbox.Image = TextDrawing.DrawTextToBitmap(text, font, Color.Red, TextDrawing.DrawMethod.AutoFitInConstantRectangleWithWarp, new RectangleF(0, 0, picbox.Width, picbox.Height));
break;
}
this.Text = ((TextDrawing.DrawMethod)(i)).ToString() + " Please resize window size by mouse to see drawing methods differences";
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
t.Enabled = false;
t.Enabled = true;
DrawText();
}
}
}
The example toggles between drawing modes automatically once every 5 seconds. The picturebox is docked inside main form. Resizing of the form shows the user the difference between the drawing modes.

C# Help drawing letter (Word Game)

I'm creating a word game(lingo).This is how it works: A random word is selected from an array and the user has to guess the correct word. Then the word is printed out.
Red: wrong letter
Yellow: correct letter but wrong position
Green :position and letter correct
Right now the problem is that it only draws a few letter of the guessed word. I also need to change the y value so that everything is not on the same line.
This is what i got so far.
public string CorrectPlace; // Correct letter and postion
public string WrongPlace; // Correct letter but incorrect postion
public string NoneExist; // Wrong
public string inputwordstring; // user input
public string CorrectWord; // Correct word
public char[] CorrectWordchar;
public char[] InputWord;
int x;// position
int y; //position
public int points = 0;
char replacemt = '#';
public string[] Wordarray = null; // Declare string array
public string getRandomWord() // generate random word
{
Random ran = new Random();
return Wordarray[(ran.Next(0, Wordarray.Length - 1))];
}
public void Gamers() //
{
Wordarray = new string[] { "radar", "andar", "axars", "rapar", "raser", "matar", "rikas", "ratas", "bakar", "bruka" }; // Dictionary
CorrectWord = getRandomWord(); // store the random word in a string
}
public void CheckWords(string name, Graphics g)
{
if (CorrectWord == inputwordstring)
{
points += 100;
MessageBox.Show("AMAZING");
}
for (int i = 0; i < CorrectWord.Length; i++)
{
for (int b = 0; b < CorrectWord.Length; b++)
{
CorrectWordchar = CorrectWord.ToCharArray();
InputWord = inputwordstring.ToCharArray();
if (InputWord[i] == CorrectWordchar[b]) // check if the have the same index
{
if (i == b)
{
CorrectPlace = CorrectWord;
SolidBrush s = new SolidBrush(Color.Green);
FontFamily ff = new FontFamily("Arial");
System.Drawing.Font font = new System.Drawing.Font(ff, 20);
g.DrawString(InputWord[i].ToString(), font, s, new PointF(x, y));
x += 20;
// draw out green letters
break;
}
else
{
if (InputWord[b] != CorrectWordchar[b])
{
SolidBrush s = new SolidBrush(Color.DarkOrange);
FontFamily ff = new FontFamily("Arial");
System.Drawing.Font font = new System.Drawing.Font(ff, 20);
g.DrawString(InputWord[b].ToString(), font, s, new PointF(x, y));
x += 20;
// Yellow letters
CorrectWordchar[b] = replacemt; // ersätter rätt bokstav med #
break;
}
else
{
b = 4;
SolidBrush s = new SolidBrush(Color.Red);
FontFamily ff = new FontFamily("Arial");
System.Drawing.Font font = new System.Drawing.Font(ff, 20);
g.DrawString(InputWord[b].ToString(), font, s, new PointF(x, y));
x += 20;
// Red letters
break;
}
}
}
}
}
}
}
}
Some comments and sample code.
You have lots of duplicated code just because you are drawing red and green. This can be summarized.
Most of the classes used in WinForms drawing have to be disposed. Otherwise you run into memory leaks and GDI+ leaks. Brushes, Fonts and others should be disposed.
Use Graphics.MeasureString to get the size of each character. The resulting size can be used to forward in X and Y.
The chars of a string can be accessed by an index directly. You don't need to convert them into char arrays.
void YourDrawMethod(Graphics g)
{
var wrongBrush = new SolidBrush(Color.Red);
var correctBrush = new SolidBrush(Color.Green);
var ff = new FontFamily("Arial");
using(var font = new System.Drawing.Font(ff, 20))
{
int x = 0;
int y = 0;
foreach(car letter in InputWord)
{
SolidBrush brush = InputWord[i] == CorrectWord[b] ? correctBrush : wrongBrush;
g.DrawString(letter.ToString(), font, brush, new PointF(x, y));
Size sizeOfLetter = g.MeasureString(letter.ToString(), font);
x += sizeOfLetter.Width;
}
}
wrongBrush.Dispose();
correctBrush.Dispose();
}

Categories

Resources