Title: Draw ransom note text in C#
This example draws each of the characters in a string in a different random font to produce a ransom note text effect. The program starts by using the following code to create arrays holding font names and brushes from which the program will later randomly select. This code also creates a Random object.
// Font names we may use.
private string FontNames =
"Times New Roman",
// Colors we may use.
private Brush FontBrushes =
// The random number generator we will use.
private Random Rand = new Random();
When you change the text in the program's TextBox, the control's TextChanged event handler calls the following DrawText method.
// Draw the text in the PictureBox.
private void DrawText()
if (picText.Image != null) picText.Image.Dispose();
Bitmap bm = new Bitmap(picText.Width, picText.Height);
using (Graphics gr = Graphics.FromImage(bm))
gr.TextRenderingHint = TextRenderingHint.AntiAlias;
gr.PageUnit = GraphicsUnit.Pixel;
float x = 0;
float y = 0;
float max_y = 0;
foreach (char ch in txtText.Text)
DrawCharacter(gr, bm.Width - 10,
ref x, ref y, ref max_y, ch);
// Display the result.
picText.Image = bm;
If the picText PictureBox currently has an image, the code disposes of it.
Next, the method creates a Bitmap to fit the PictureBox and an associated Graphics object.
The method then initializes three variables that determine where text should be drawn. The x and y values give the coordinates of the next character. The value max_y records the largest Y value used by a character. That value is used to move to a new line. when no more text will fit on the current line in the PictureBox.
The method then loops through the characters in the text and calls the following DrawCharacter method for each.
// Draw a character in a random font.
private void DrawCharacter(Graphics gr, int right_margin,
ref float x, ref float y, ref float max_y, char ch)
const float min_size = 25;
const float max_size = 35;
// Pick the random font characteristics.
string font_name = FontNames[Rand.Next(0, FontNames.Length)];
float font_size = (float)(min_size + Rand.NextDouble() *
(max_size - min_size));
FontStyle font_style = FontStyle.Regular;
if (Rand.Next(0,2) == 1) font_style |= FontStyle.Bold;
if (Rand.Next(0,2) == 1) font_style |= FontStyle.Italic;
//if (Rand.Next(0,2) == 1) font_style |= FontStyle.Strikeout;
//if (Rand.Next(0,2) == 1) font_style |= FontStyle.Underline;
Brush brush = FontBrushes[Rand.Next(0, FontBrushes.Length)];
// Draw the character.
using (Font font = new Font(font_name, font_size, font_style))
// Measure the character.
string text = "M" + ch + "M";
SizeF size = gr.MeasureString(text, font);
SizeF x_size = gr.MeasureString("MM", font);
size.Width = size.Width - x_size.Width + 5;
// See if we have room.
if (x + size.Width * 0.75f > right_margin)
// Start a new line.
x = 0;
y = max_y;
// Draw the character.
gr.DrawString(ch.ToString(), font, brush, x, y);
// Set the position for the next character.
x += size.Width * 0.75f;
if (max_y < y + size.Height) max_y = y + size.Height;
This code uses the Random object created earlier to select random properties for the character's font. (The code that selects random items from the FontNames and FontBrushes arrays is probably the most generally useful part of the program.) The code then creates a new Font with the chosen characteristics. That gives the ransom note text effect.
Next, the program uses MeasureString to see how big the character is in the new font. Unfortunately, the MeasureString method seems to assume that you are drawing a sentence that will be followed by other sentences. To space the sentences correctly, it adds some extra space. If the character is a space character, MeasureString seems to include it as part of the after-sentence spacing, so it doesn't allow the full amount of room necessary for the character. As a result, if you simply use the size returned by MeasureString, the characters are too far apart and any spaces in the text are basically ignored.
To make the spacing more reasonable, the program measures the character surrounded by Ms. It then measures a string holding only two Ms and subtracts that string's width from the width of the first string. This removes the space used by the Ms and any extra space that is added by MeasureString. It also forces MeasureString to allow room for a space character because it is enclosed by Ms and therefore cannot be swallowed up by the inter-sentence spacing. (I also add 5 extra pixels to make the spacing work out better. You can adjust that if you like.)
Having calculated the space required by the character, the program determines whether the character will fit before the end of the current line in the PictureBox. If the character won't fit, the program moves to the next line by setting x = 0 and y = max_y.
The program then draws the character and updates the X coordinate for the next character. If max_y is smaller than the character's largest Y coordinate, the code also updates max_y.
The values x, y, and max_y are passed by reference, so they are retained between calls to DrawCharacter.
Download the example to experiment with it and to see additional details.