Text rendered differently on screen and printer in C# - c#

I have a class for writing texts on screen or printer.
public void draw(float scale, Graphics g)
{
Font font = new Font(fontName, size, FontStyle.Regular, GraphicsUnit.Millimeter);
SolidBrush brush = new SolidBrush(Color.Black);
Pen pen = new Pen(Color.Black, 1);
PointF point = new PointF(0, 0);// new PointF(l + scale * x, t + scale * y);
rect = new Rectangle(0, 0, (int)(g.MeasureString(text, font).Width), (int)(g.MeasureString(text, font).Height));
System.Drawing.Drawing2D.Matrix m = g.Transform;
g.ScaleTransform(scale, scale);
g.TranslateTransform(x - rect.Width / 2, y - rect.Height / 2);
g.RotateTransform(angle);
g.DrawString(text, font, brush, point);
g.Transform = m;
}
The different textblocks are of different font sizes and placed at different x positions at the "page". I place two blocks of diffent size at an x where the text ends in the same position on the screen. When I print the "page" the two words don't end at the same position even if the center seem to be at the same position.
I have searched but not been able to find anything that resolves my problem.

I am not sure if this will help or not, but I learned long ago that printers compress spacing differently than screens do. For example, if you wrote a bunch of letters on a document with tabs and space bar spaces between them, it would look different on the screen than if you printed the document on paper. Maybe it gives you a place to start?

Related

Transforming Rectangle Coordinates to a Large Resolution Image

I'm using the following code to Transform a small rectangle coordinates to a larger one ie: A rectangle position on a small image to the same position on the larger resolution of the same image
Rectangle ConvertToLargeRect(Rectangle smallRect, Size largeImageSize, Size smallImageSize)
{
double xScale = (double)largeImageSize.Width / smallImageSize.Width;
double yScale = (double)largeImageSize.Height / smallImageSize.Height;
int x = (int)(smallRect.X * xScale + 0.5);
int y = (int)(smallRect.Y * yScale + 0.5);
int right = (int)(smallRect.Right * xScale + 0.5);
int bottom = (int)(smallRect.Bottom * yScale + 0.5);
return new Rectangle(x, y, right - x, bottom - y);
}
But there seems to be a problem with some images.The transformed rectangle coordinates seems to be off the image.
UPDATE:
img.Draw(rect, new Bgr(232, 3, 3), 2);
Rectangle transret= ConvertToLargeRect(rect, orgbitmap.Size, bit.Size);
target = new Bitmap(transret.Width, transret.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawImage(orgbitmap, new Rectangle(0, 0, target.Width, target.Height),
transret, GraphicsUnit.Pixel);
}
Rectangle Drawn on small resolution Image
{X=190,Y=2,Width=226,Height=286}
Rectangle Transformed into Orginal Large Resolution Image {X=698,Y=7,Width=830,Height=931}
Original Image
First of all, if you resize the shape it shouldn't move position. That's not what one would expect out of enlarging a shape. This means the X,Y point of the top-left corner shouldn't be transformed.
Second, you shouldn't be adding 0.5 manually to operations, that's not a clean way to proceed. Use the ceiling function as suggested by #RezaAghaei
Third, you should not substract X/Y from the height/width, your calculations should be done as width * scale.
Please correct those mistakes, and if it doesn't work I'll update the answer with extra steps.

drawstring rotate long text 180

I use drawstring method to draw text inside rectangle ,.. i wanted to rotate the text 180 degree so i used this code
Rectangle displayRectangleh =
new Rectangle(new Point((int)posh.X, (int)posh.Y), new Size(rectwdh, recth));
StringFormat format1h = new StringFormat(StringFormatFlags.DirectionVertical);
format1h.LineAlignment = StringAlignment.Center;
format1h.Alignment = StringAlignment.Center;
b = new SolidBrush(Color.Black);
e.ChartGraphics.Graphics.TranslateTransform((float)rectwdh / 2 + posh.X, recth / 2 + posh.Y);
e.ChartGraphics.Graphics.RotateTransform(180);
e.ChartGraphics.Graphics.TranslateTransform(-((float)rectwdh / 2 + posh.X), -(recth / 2 + posh.Y));
Font boldFonth = new Font(FontFamily.GenericSansSerif, 16, FontStyle.Bold, GraphicsUnit.Pixel, 1, true);
e.ChartGraphics.Graphics.DrawRectangle(new Pen(Color.Black, 2), displayRectangleh);
e.ChartGraphics.Graphics.DrawString("This is rotated long text test ", boldFonth, b, (RectangleF)displayRectangleh, format1h);
e.ChartGraphics.Graphics.ResetTransform();
where posh.X and posh.Y (recatangle position) , rectwdh (rectangle width) and recth (rectangle height)
It works perfect for short text but for long text the new line will be above my old line see the next pic :
I even tried to add new line character \n but same result.
my question : how to rotate the text 180 inside rectangle and end up with proper alignment ??
Never having even used the StringFormat APIs before, I pounded at it randomly until I found this:
StringFormat format1h = new StringFormat(StringFormatFlags.DirectionVertical | StringFormatFlags.DirectionRightToLeft);

Crop a diagonal area from an image in WPF

I want to crop from an image using user-drawn rectangles on a canvas. The rectangles can be moved, re-sized, and rotated.
When the user selects "Get Cropped Image", the area inside the rectangle should be saved in a second image location on the page, which I can do perfectly well, so long as the rectangle is not rotated. (Straight-forward use of CroppedBitmap.) However, when the rectangle is at an angle I do not know how to perform the crop.
This is what I want to do (forgive my poor MS Paint skills):
My questions are:
1) How do I correctly track or calculate the points of the rectangle?
and,
2) Once I have the points, how do I crop the rotated rectangle?
EDIT:
Thanks to user Rotem, I believe that I have the answer to the second question. Using code modified from the following answers: Answer 1, Answer 2, I am seeing good results. Unfortunately, I am still unable to track the correct location points for the rectangle, so I cannot fully test this as of yet.
public static Bitmap CropRotatedRect(Bitmap source, System.Drawing.Rectangle rect, float angle, bool HighQuality)
{
Bitmap result = new Bitmap((int)rect.Width, (int)rect.Height);
using (Graphics g = Graphics.FromImage(result))
{
g.InterpolationMode = HighQuality ? InterpolationMode.HighQualityBicubic : InterpolationMode.Default;
using (Matrix mat = new Matrix())
{
mat.Translate(-rect.Location.X, -rect.Location.Y);
mat.RotateAt(-(angle), rect.Location);
g.Transform = mat;
g.DrawImage(source, new System.Drawing.Point(0, 0));
}
}
return result;
}
EDIT:
The answer to the first point is much easier than I had originally thought. You can always get the top-left corner of the rectangle by calling—
double top = Canvas.GetTop(rect);
double left = Canvas.GetLeft(rect);
You can then calculate the rest of the points using the width and the height—
Point topLeft = new Point(left, top);
Point topRight = new Point(left + rect.Width, top);
Point bottomLeft = new Point(left, top + rect.Height);
Point bottomRight = new Point(left + rect.Width, top + rect.Height);
Point centerPoint = new Point(left + (rect.Width / 2), top + (rect.Height / 2));
If your rectangle is rotated, then you have to translate these points to determine where they truly lie on the canvas—
public Point TranslatePoint(Point center, Point p, double angle)
{
// get the point relative to (0, 0) by subtracting the center of the rotated shape.
Point relToOrig = new Point(p.X - center.X, p.Y - center.Y);
double angleInRadians = angle * Math.PI / 180;
double sinOfA = Math.Sin(angleInRadians);
double cosOfA = Math.Cos(angleInRadians);
Point translatedPoint = new Point(relToOrig.X * cosOfA - relToOrig.Y * sinOfA,
relToOrig.X * sinOfA + relToOrig.Y * cosOfA);
return new Point(translatedPoint.X + center.X, translatedPoint.Y + center.Y);
}
Once you are able to translate the top-left corner, you can use Rotem's cropping method. You can also calculate the position of the rest of the rectangle, so you are able to determine if the rectangle is within the bounds of the image, if it is touching an edge, or any other thing that you might want to do in regards to the position.
I discovered the answer to my own question(s), and made the appropriate edits along the way. Please see above for the answer.

How Can i Distribute String in rectangle

I want to distribute string in rectangle.
Except each character set position
Rectangle displayRectangle = new Rectangle (new Point(40, 40), new Size (80, 80));
StringFormat format1 = new StringFormat(StringFormatFlags.NoClip);
format1.LineAlignment = StringAlignment.Center;
e.Graphics.DrawRectangle(Pens.Black, displayRectangle);
e.Graphics.DrawString("Showing Format1", this.Font,
Brushes.Black, (RectangleF)displayRectangle, format1);
But, StringFormat Alignment doesn't have distribute alignment. So I want to know a way how to distribute string in rectangle.
For the moment, I'm going to assume you can/will use the Win32 API (e.g., via. P/Invoke). .NET may have a wrapper for the function I'm going to suggest (but then again, it may not -- I'm really not sure). If it does, it'll be up to you to find and use it. Most of what I'm suggesting is more about the basic approach than the function anyway.
You can use GetTextExtentExPointI, which will compute the size of a rectangle necessary to hold a set characters you specify and (importantly) the horizontal position of each character in that rectangle.
So, what you want to do is use this to compute the size of a rectangle and position of each character in that rectangle, with it assuming normal kerning of the characters. Then, you'll divide the width of that rectangle into the width you actually want. This will give you a factor by which each position must increase to get that character to the position you want. You'll then multiply the position it returned for each character by that factor to get your desired position.
Just for example, let's assume it gave you positions of 0, 17, 35 and 44 for the characters with normal spacing. Let's also assume your target rectangle is 1.8 times as wide as the rectangle it computed for normal spacing. You'll take each of those positions and multiply by 1.8 to get the position you want to use for that character, giving 0, 31, 63, and 79 for the "corrected" positions.
Then you'll (obviously enough) go through your string and draw each character at the computed position.
Here's how to do it if you just want to literally distribute the characters evenly across the middle of the display rectangle:
private void Form1_Paint(object sender, PaintEventArgs e)
{
string text = "this is distribute";
Rectangle displayRectangle = new Rectangle(new Point(40, 40), new Size(400, 80));
e.Graphics.DrawRectangle(Pens.Black, displayRectangle);
int step = displayRectangle.Width / text.Length;
SizeF szF = e.Graphics.MeasureString(text, this.Font); // just to get the HEIGHT
int y = (displayRectangle.Y + displayRectangle.Height / 2) - (int)szF.Height / 2;
for (int i = 0; i < text.Length; i++)
{
e.Graphics.DrawString(text.Substring(i, 1), this.Font, Brushes.Black, displayRectangle.X + (i * step), y);
}
}
Here's my stab at #Jerry Coffin's algorithm using .Net managed methods:
private void Form1_Paint(object sender, PaintEventArgs e)
{
string text = "this is distribute";
Rectangle displayRectangle = new Rectangle(new Point(40, 40), new Size(400, 80));
e.Graphics.DrawRectangle(Pens.Black, displayRectangle);
StringFormat format1 = new StringFormat(StringFormatFlags.NoClip);
format1.LineAlignment = StringAlignment.Center;
format1.Alignment = StringAlignment.Near;
// SetMeasurableCharacterRanges() can only handle 32 regions max at a time!
// The below workaround simply measures each character separately:
RectangleF rcF = (RectangleF)displayRectangle;
List<Region> regions = new List<System.Drawing.Region>();
for (int i = 0; i < text.Length; i++)
{
format1.SetMeasurableCharacterRanges(new CharacterRange[] {new CharacterRange(i, 1)});
regions.AddRange(e.Graphics.MeasureCharacterRanges(text, this.Font, rcF, format1));
}
RectangleF minBounds = regions[0].GetBounds(e.Graphics);
RectangleF maxBounds = regions[regions.Count - 1].GetBounds(e.Graphics);
float ratio = (float)displayRectangle.Width / (float)((maxBounds.X + maxBounds.Width) - minBounds.X);
for(int i = 0; i < regions.Count; i++)
{
Region region = regions[i];
RectangleF boundsF = region.GetBounds(e.Graphics);
PointF ptF = new PointF(displayRectangle.X + (int)((boundsF.Left - minBounds.X) * ratio), (int)boundsF.Top);
e.Graphics.DrawString(text.Substring(i, 1), this.Font, Brushes.Black, ptF);
}
}

Center text output from Graphics.DrawString()

I'm using the .NETCF (Windows Mobile) Graphics class and the DrawString() method to render a single character to the screen.
The problem is that I can't seem to get it centred properly. No matter what I set for the Y coordinate of the location of the string render, it always comes out lower than that and the larger the text size the greater the Y offset.
For example, at text size 12, the offset is about 4, but at 32 the offset is about 10.
I want the character to vertically take up most of the rectangle it's being drawn in and be centred horizontally. Here's my basic code. this is referencing the user control it's being drawn in.
Graphics g = this.CreateGraphics();
float padx = ((float)this.Size.Width) * (0.05F);
float pady = ((float)this.Size.Height) * (0.05F);
float width = ((float)this.Size.Width) - 2 * padx;
float height = ((float)this.Size.Height) - 2 * pady;
float emSize = height;
g.DrawString(letter, new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular),
new SolidBrush(Color.Black), padx, pady);
Yes, I know there is the label control that I could use instead and set the centring with that, but I actually do need to do this manually with the Graphics class.
I'd like to add another vote for the StringFormat object.
You can use this simply to specify "center, center" and the text will be drawn centrally in the rectangle or points provided:
StringFormat format = new StringFormat();
format.LineAlignment = StringAlignment.Center;
format.Alignment = StringAlignment.Center;
However there is one issue with this in CF. If you use Center for both values then it turns TextWrapping off. No idea why this happens, it appears to be a bug with the CF.
To align a text use the following:
StringFormat sf = new StringFormat();
sf.LineAlignment = StringAlignment.Center;
sf.Alignment = StringAlignment.Center;
e.Graphics.DrawString("My String", this.Font, Brushes.Black, ClientRectangle, sf);
Please note that the text here is aligned in the given bounds. In this sample this is the ClientRectangle.
Through a combination of the suggestions I got, I came up with this:
private void DrawLetter()
{
Graphics g = this.CreateGraphics();
float width = ((float)this.ClientRectangle.Width);
float height = ((float)this.ClientRectangle.Width);
float emSize = height;
Font font = new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular);
font = FindBestFitFont(g, letter.ToString(), font, this.ClientRectangle.Size);
SizeF size = g.MeasureString(letter.ToString(), font);
g.DrawString(letter, font, new SolidBrush(Color.Black), (width-size.Width)/2, 0);
}
private Font FindBestFitFont(Graphics g, String text, Font font, Size proposedSize)
{
// Compute actual size, shrink if needed
while (true)
{
SizeF size = g.MeasureString(text, font);
// It fits, back out
if (size.Height <= proposedSize.Height &&
size.Width <= proposedSize.Width) { return font; }
// Try a smaller font (90% of old size)
Font oldFont = font;
font = new Font(font.Name, (float)(font.Size * .9), font.Style);
oldFont.Dispose();
}
}
So far, this works flawlessly.
The only thing I would change is to move the FindBestFitFont() call to the OnResize() event so that I'm not calling it every time I draw a letter. It only needs to be called when the control size changes. I just included it in the function for completeness.
To draw a centered text:
TextRenderer.DrawText(g, "my text", Font, Bounds, ForeColor, BackColor,
TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter |
TextFormatFlags.GlyphOverhangPadding);
Determining optimal font size to fill an area is a bit more difficult. One working soultion I found is trial-and-error: start with a big font, then repeatedly measure the string and shrink the font until it fits.
Font FindBestFitFont(Graphics g, String text, Font font,
Size proposedSize, TextFormatFlags flags)
{
// Compute actual size, shrink if needed
while (true)
{
Size size = TextRenderer.MeasureText(g, text, font, proposedSize, flags);
// It fits, back out
if ( size.Height <= proposedSize.Height &&
size.Width <= proposedSize.Width) { return font; }
// Try a smaller font (90% of old size)
Font oldFont = font;
font = new Font(font.FontFamily, (float)(font.Size * .9));
oldFont.Dispose();
}
}
You'd use this as:
Font bestFitFont = FindBestFitFont(g, text, someBigFont, sizeToFitIn, flags);
// Then do your drawing using the bestFitFont
// Don't forget to dispose the font (if/when needed)
Here's some code. This assumes you are doing this on a form, or a UserControl.
Graphics g = this.CreateGraphics();
SizeF size = g.MeasureString("string to measure");
int nLeft = Convert.ToInt32((this.ClientRectangle.Width / 2) - (size.Width / 2));
int nTop = Convert.ToInt32((this.ClientRectangle.Height / 2) - (size.Height / 2));
From your post, it sounds like the ClientRectangle part (as in, you're not using it) is what's giving you difficulty.
You can use an instance of the StringFormat object passed into the DrawString method to center the text.
See Graphics.DrawString Method and StringFormat Class.

Categories