StringFormat.Trimming changes vertical placement of text - c#

This is for a header/footer on a printed page. The code below is painting the left & center parts of the header/footer (I've omitted the code that paints the right) and simplified logic to illustrate the problem better. The idea is the center part of header takes precedence and the left and right will be trimmed via StringTrimming.EllipsisPath if the center is too big for them to fit. This all works except for whenever trimming occurs, GDI+ is shifting the trimmed text vertically a bit.
EDIT: I've written a dedicated sample to illustrate the problem because my original post was not clear enough for folks. This is the entire code of the sample. It's a .NET Framework Winforms app created with VS19.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
Font font = new Font("Century Gothic", 24F, FontStyle.Regular, GraphicsUnit.Point);
string demo = "[Emergency 123]";
StringFormat fmtTrimmed = new StringFormat(StringFormat.GenericTypographic)
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Near,
Trimming = StringTrimming.EllipsisCharacter,
};
StringFormat fmtNotTrimmed = new StringFormat(StringFormat.GenericTypographic)
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Near,
};
try
{
Rectangle boundsHeader = new Rectangle(10, 10, this.ClientSize.Width - 20, (int)font.GetHeight() + 8);
SizeF textSize = e.Graphics.MeasureString(demo, font, boundsHeader.Size, fmtNotTrimmed);
boundsHeader.Inflate(1, 1);
e.Graphics.DrawRectangle(Pens.Blue, boundsHeader);
float textCenterBounds = (boundsHeader.Width - textSize.Width) / 2;
e.Graphics.DrawRectangle(Pens.Green, boundsHeader.X + textCenterBounds, boundsHeader.Y, textSize.Width, textSize.Height);
e.Graphics.DrawString(demo, font, Brushes.Black, boundsHeader, fmtNotTrimmed);
RectangleF boundsLeft = new RectangleF(boundsHeader.X, boundsHeader.Y, textCenterBounds, boundsHeader.Height);
SizeF sizeLeft = e.Graphics.MeasureString(demo, font, boundsLeft.Size, fmtTrimmed);
e.Graphics.DrawRectangle(Pens.Red, boundsLeft.X, boundsLeft.Y, boundsLeft.Width, textSize.Height);
e.Graphics.DrawString(demo, font, Brushes.Black, boundsLeft, fmtTrimmed);
}
finally
{
font.Dispose();
fmtTrimmed.Dispose();
fmtNotTrimmed.Dispose();
}
}
Here the app shows that if left is of size that trimming does not occur, it is aligned vertically correctly:
If left is has a length where trimming occurs (note elipsis), vertical alignment shifts up slightly (note code uses Form1_Resize to resize rects).
You can see this most clearly by looking at the y, g, and brackets.
I've tried every combination of StringTrimming, LineAlignment, and FormatFlags and cannot resolve this. Note that in the current implementation I've also tried making the Rectangle have a height of 0. Same issue occurs if the bounds.Height is the font height, sizeCenter.Height, or bigger.
I can't believe this is a bug in GDI+, so I've got to be doing something wrong. Many searches and re-reads of the docs have not helped. I'm at my wits end.
EDIT: based on #HansPassant's comments below, I've found different fonts behave differently. In the images above I was using "Microsoft Sans Serif".
"Arial" has same problem (bold or regular)
"Tahoma" does reproduce the problem.
"Cascadia Code" DOES reproduce (it's new and fixed pitch)
"Lucida Console" DOES NOT!?!?! reproduce
"Courier New" DOES.
I've determined that some fonts are worse than others. For example, if I change the code above to use 24F as the font size I can't reproduce it with Microsoft Sans Serif).
I've also determined that StringTrimming.Character and StringTrimming.ElipsisCharacter and StringTrimming.ElipsisPath all behave the same way (so the ellipsis char does not seem to be the cause).
EDIT: Showing another example here using Century Gothic regular:
EDIT: This sample illustrates that e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias does NOT fix the problem when LineAlignment = StringAlignment.Far.

Related

Receipt printing in roll paper

I searched for a lot in google and did not find what i actually wanted. I got following code, which will print the variable name. I got a Epson Dot Matrix Printer and Roll Paper (Endless continuous paper).
My problem is that, after printing name paper feeds up to size of A4. I don`t want the paper feed. This application is intended to do print receipts which will have unlimited data which need to be printed flawless ( with out page break).
Can you, the smart folk out there to point me in the correct direction with these codes?
edited this code and changed scenario .. please move down further
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
Font Heading2 = new Font("Times New Roman", 13);
StringFormat sf = new StringFormat();
sf.LineAlignment = StringAlignment.Near;
sf.Alignment = StringAlignment.Center;
//e.HasMorePages = false;
PaperSize pkCustomSize1 = new PaperSize("First custom size", 100, 200);
pd.DefaultPageSettings.PaperSize = pkCustomSize1;
e.Graphics.DrawString(name.ToString(), Heading1, Brushes.Black, e.MarginBounds.Left + (e.MarginBounds.Width / 2), e.MarginBounds.Top, sf);
}
Edit 1:- #Adriano Repetti suggested this is a duplicate with Form feed in c# printing. What i learned from the above question is that he want to add the form feed. But i want to remove the form feed.
Edit 2:- I got an another hint by googling that setting the page Height equal to line height will make stop feeding which sounds promising. I am trying to figure that out too.
Edit 3:- #Adriano Repetti suggested me with Raw Printing (Directly printing binary data) with KB link. I googled around about it, and found out its c# better equivalent paste bin or pastie.org (provided because it is a handy one) . At first it sounded good and it worked nicely stopping form feeding. But eventually i struck some ice berg.
On my code i had to align some printing quotes to center or align to left`. for which i got only option of using space and tabs. But there will be no guaranty that it will be well formatted as we can not certain about built in fonts with printer.( Refer:SO Question by #syncis )
And secondly, i will have to move my application to unicode(local language support) capable one, at least with in a month or so. In that scenario, raw printing wont help and i will have to go through theses faces again. SO, For avoiding that its better for me to stay with Graphics DrawString. And for this i changed my code as.
//---------
// start button click
//---------
PrintDocument pdoc = new PrintDocument();
pdoc.DefaultPageSettings.PaperSize.Height = 300;
pdoc.Print();
//---------
// end button click
//---------
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
Font Heading2 = new Font("Times New Roman", 13);
// changed following statement to met with new **unicode** criteria
//e.Graphics.DrawString(name.ToString(), Heading1, Brushes.Black, e.MarginBounds.Left + (e.MarginBounds.Width / 2), e.MarginBounds.Top, sf);
TextRenderer.DrawText(e.Graphics, "My name in local language is വിനീത്", Heading2, new Point(0, 0), Color.Black);
}
With current problems, i am redefining the Question extending tag to unicode as.
How can print with TextRenderer.DrawText with unicode support without form feeding ? I think setting paper height to line height will solve my problem. If so how or suggest me a better way to stop paper feeding. It really eats a lot of my valuable time...
EDIT 4: Today I found out a very interesting thing about my printer. I cant even set custom paper size manually (Not by coding.. I mean control panel->printers and faxes ->Epson LX-300+ ->properties -> printing preference-> paper/quality -> advanced -> paper size -> BOOOOOM not showing my custom paper size). I am using Epson LX-300+ printer. Do guys think it wont support custom paper sizes? is that causing me problems?
I found the solution by my self ( sorry for my english ) As Hans Passant says ( PrintDocument is page based. end of story ) You must use ( e.HasMorePages = true; )
float cordenadaX;
float cordenadaY;
int totalPages;
int paginaAtual;
int indiceItem;
List<string> items;
public void ImprimeDanfeNFCe()
{
totalPages = 1;
paginaAtual = 1;
indiceItem = 0;
cordenadaX = 0;
cordenadaY = 0;
items = new List<string>();
items.Add("Item1");
items.Add("Item2");
items.Add("Item3");
(............)
PrintDocument recordDoc = new PrintDocument();
recordDoc.DocumentName = "xMarket danfe";
recordDoc.PrintPage += new PrintPageEventHandler(imprimeDanfeReceipt);
PrinterSettings ps = new PrinterSettings();
ps.PrinterName = "My printer";
recordDoc.PrinterSettings = ps;
recordDoc.Print();
recordDoc.Dispose();
}
void imprimeDanfeReceipt(PrintPageEventArgs e)
{
float pageHeight = e.MarginBounds.Height;
string text = "";
if (paginaAtual == 1)
{
text = "Cupom header";
e.Graphics.DrawString(text, drawFontDanfeTitulo, drawBrush, new
RectangleF(cordenadaX, cordenadaY, width, height),
drawFormatCenter);
cordenadaY += e.Graphics.MeasureString(text, drawFontDanfeTitulo).Height;
}
for (int i = indiceItem; i < items.Count; i++)
{
int indice = i + 1;
//items[i] Is very important to not print same items again while print next page
e.Graphics.DrawString(items[i], drawFontDanfeItems, drawBrush,
new RectangleF(cordenadaX, cordenadaY, width, height), drawFormatLeft);
cordenadaY += e.Graphics.MeasureString(text, drawFontDanfeTitulo).Height;
indiceItem++;
//cordenadaY+100 is for the size of the footer
if (cordenadaY+100 >= pageHeight)
{
paginaAtual++;
e.HasMorePages = true;
return;
}
}
e.Graphics.DrawString("page footer", drawFontDanfeItems, drawBrush,
new RectangleF(cordenadaX, cordenadaY, width, height), drawFormatLeft);
cordenadaY += e.Graphics.MeasureString(text, drawFontDanfeTitulo).Height;
}

Add watermark below image but within print area in C#

I am trying to insert a text watermark underneath a TIFF image in my windows form and would definitely appreciate anyone's help. I have a print button that retrieves the image, scales it down, then based on my margins, places the image accordingly to print. I'd like to add an additional piece where just before the image prints, I add in a text watermark (in this case a date stamp) that is just below the image.
I've tried adjusting the margin but that just increases (or decreases depending on the number setting) the image scale but does not add the additional room I want to add the watermark. Below is code of what I have so far:
protected void PrintPage(object sender, PrintPageEventArgs e)
{
if (this.Image == null)
{
e.Cancel = true;
return;
}
//ADD TIME STAMP WATERMARK
string watermark = "DATE ISSUED: " + String.Format("{0:MM/dd/yyyy}", System.DateTime.Now.Date);
System.Drawing.Graphics gpr = Graphics.FromImage(Image);
System.Drawing.Brush brush = new SolidBrush(System.Drawing.Color.Black);
Font font = new System.Drawing.Font("Arial", 55, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
SizeF size = e.Graphics.MeasureString(watermark, font);
float x = 0;
float y = Image.Height-size.Height;
RectangleF printArea = new RectangleF(x, y, size.Width, size.Height);
gpr.DrawString(watermark, font, brush, printArea);
e.Graphics.DrawImage(this.Image, e.MarginBounds);
}
The value of e.MarginBounds I have set in my App.config and include the following values: Left=70, Right=90, Top=190; Bottom=475. All the printouts are going to be printed portrait style on Letter 8 1/2 by 11 size paper.
I am able to display the watermark anywhere on top of the image, but I am hoping to place it underneath. When I adjust the y coordinate, and it so happens to be below the image, when I print, I assume that it is outside the print area and therefore, the watermark does not get printed on the page (it only shows the image).
I appreciate anyone's help in this as I have been racking my brain on this and have had no luck.
Aren't you printing your text beneath the image. I think you want to start printing at y=Image.Height + e.MarginBounds.Top, and x=e.MarginBounds.Left
That will print a your label left justified below the image in the margin.
Update: This works:
y=-size.Height + e.MarginBounds.Bottom;
x = e.MarginBounds.Left;
e.Graphics.DrawImage(Image, e.MarginBounds);
// note the change. Using e.graphics instead of gpr below
e.Graphics.DrawString(watermark, font, brush, printArea);

How can i change in Graphics DrawString the text font size?

I'm using this method to draw the text on form1:
private bool DrawText(bool draw, string texttodraw)
{
Graphics g = this.CreateGraphics();
SizeF size = g.MeasureString(texttodraw, SystemFonts.DefaultFont,14);
g.DrawString(texttodraw, Font, Brushes.Red, pictureBox1.Location.X + (pictureBox1.Width / 2) - (size.Width / 2),
pictureBox1.Location.Y - 30);
return draw;
}
I tried to set Width to 14 on the SizeF size lline but it didn't change the dize and the only thing it did is moving a bit the text from it's location .
How can i change the font size of the text, and also to keep the perspective(if this is the right word to use) of the text location ?
This is how it look like when not using the Width 14 at all the text is in the center above the pictureBox1. I want that when i change the text size it will be kept to be in the center like it is now.
The text is in Red and it's in hebrew in this case.
Try using a bigger font:
using (Font bigFont = new Font(SystemFonts.DefaultFont.FontFamily, 14, FontStyle.Regular)) {
SizeF size = g.MeasureString(texttodraw, bigFont, 14);
g.DrawString(texttodraw, bigFont, Brushes.Red, pictureBox1.Location.X + (pictureBox1.Width / 2) - (size.Width / 2),
pictureBox1.Location.Y - 30);
}
Do avoid using CreateGraphics, it's only a temporary drawing that will get erased by overlapping windows or minimizing the form. It will also cause flicker. Use the graphics object from the paint event and invalidate the control to update the painting.
Also, do favor using TextRenderer.DrawText and TextRenderer.MeasureText for your text renderings. DrawString should primarily be used for printing to paper.
I think the best way is to use StringFormat object to center-align the text either horizontally or vertically or both using the 5th overload of Graphics.DrawString() funciton:
You need to provide a Rectangle objet and alignment is done with respect to this object.
StringFormat sf=new StringFormat();
sf.LineAlignment = StringAlignment.Center;//center-align vertically
sf.Alignment = StringAlignment.Center; //center-align horizontally
private void printDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
Font drawFont = new Font("Arial Black", 9);
e.Graphics.DrawString(nic.Text, drawFont, Brushes.Maroon, 174, 12);

DrawString and DrawRectangle using same X&Y start at different places

I have the following code:
public void DrawLetter(int Cell, string Letter, int X, int Y)
{
System.Drawing.Font fBody = new System.Drawing.Font("Courier New", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
Pencil.DrawString(Letter, fBody, System.Drawing.Brushes.Black, X, Y);
Pencil.DrawRectangle(new Pen(System.Drawing.Brushes.Red), X, Y, 10, 10);
}
When I go through this twice and use 140,249 and 296,249 as co-ordinates the rectangle appears as I would expect in the correct position but the string appears a few pixels out and I can't work out why.
I put in the DrawRectangle to check that it was starting in the correct position and it is. Am I doing something wrong in the DrawString? Also if I draw a D and a Z the width of the D is 10px and Z is 8px, I thought using Courier it would give me fixed width?
UPDATE: I looked at the sample on MSDN and here is a screenshot. Even though the DrawString is positioned at 0,0 you can see that the letter does not appear at 0,0. The rectangle does though. There must be padding or something:
UPDATE 2: Using the following code seems to improve things although its not perfect:
StringFormat strFormat = new StringFormat(StringFormat.GenericTypographic);
Pencil.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
Pencil.DrawString(measureString, stringFont, Brushes.Black, new PointF(0, 0), strFormat);
It wouldn't be the default padding / line-height etc being taken into account for the "Courier New" font would it?

TextRenderer.DrawText in Bitmap vs OnPaintBackground

If I use TextRenderer.DrawText() using the Graphics object provided in the OnPaintBackground my text looks perfect. If I create my own Bitmap and use the Graphics object obtained from my Bitmap my text looks terrible. It looks like it is anti-aliasing the text using black, not the bitmap's background color. I can avoid this problem if I use Graphics.DrawString(), but this method has horrible kerning problems. What should I do? How can I get TextRenderer.DrawText() to anti-alias properly using the Bitmap's contents?
Looks terrible:
Bitmap bmp = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Red);
TextFormatFlags tf = TextFormatFlags.Left;
TextRenderer.DrawText(g, #"C:\Development\Testing\blag", font, clip, Color.White,
Color.Transparent, tf);
}
Looks good, but I want to render this onto a bitmap, NOT onto the control's surface:
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Red);
TextFormatFlags tf = TextFormatFlags.Left;
TextRenderer.DrawText(e.Graphics, #"C:\Development\Testing\blag", font, clip,
Color.White, Color.Transparent, tf);
}
What is the difference?
The answer is not to use TextRenderer. TextRenderer is a wrapper for the GDI (not GDI+) implementation of text rendering, which has lots of features, but doesn't interoperate well with in-memory DCs as you have discovered.
Use Graphics.DrawString & Graphics.MeasureString, but remember to pass it StringFormat.GenericTypographic to get accurate size and positioning.
The reason TextRenderer was introduced initially was that GDI+ didn't support all the complex scripts that GDI's Uniscribe engine did. Over time however GDI+ support for complex scripts has been expanded, and these days there aren't any good reasons left to use TextRenderer (it's not even the faster of the two anymore, in fact quite the opposite it appears).
Really, though, unless you are running into serious, measurable performance issues just use Graphics.DrawString.
I believe the problem is that the clear type text rendering doesn't work if the background is transparent. A few possible solutions.
Option 1. Fill the background of your bitmap with a color.
If you do this (as Tim Robinson did above in his code example by using g.Clear(Color.Red)) clear type will do the right thing. But your bitmap won't be completely transparent which might not be acceptable. If you use Graphics.MeasureText, you can fill just the rectangle around your text, if you like.
Option 2. Set TextRenderingHint = TextRenderingHintAntiAliasGridFit
This appears to turn off clear type. The text will be rendered at a lower quality than clear type on a background, but much better than the mess clear type on no background creates.
Option 3. Fill the text rectangle with white, draw the text and then find all the non-text pixels and put them back to transparent.
using (Bitmap bmp = new Bitmap(someWidth, someHeight))
{
using (Graphics g = Graphics.FromImage(bmp))
{
// figure out where our text will go
Point textPoint = new Point(someX, someY);
Size textSize = g.MeasureString(someText, someFont).ToSize();
Rectangle textRect = new Rectangle(textPoint, textSize);
// fill that rect with white
g.FillRectangle(Brushes.White, textRect);
// draw the text
g.DrawString(someText, someFont, Brushes.Black, textPoint);
// set any pure white pixels back to transparent
for (int x = textRect.Left; x <= textRect.Left + textRect.Width; x++)
{
for (int y = textRect.Top; y <= textRect.Top + textRect.Height; y++)
{
Color c = bmp.GetPixel(x, y);
if (c.A == 255 && c.R == 255 && c.G == 255 && c.B == 255)
{
bmp.SetPixel(x, y, Color.Transparent);
}
}
}
}
}
I know, it's a horrible hack, but it appears to work.
The answer is to use a BuffersGraphicsContext. This is the same system that .NET uses internally when you set the ControlStyles.OptimizedDoubleBuffer style on a control.
See http://msdn.microsoft.com/en-us/library/b367a457.aspx for more information about double buffering in .NET.
Another possible solution: Draw the whole thing to the screen, bitmap with text on top, and then write some code to 'screen capture' that portion of the screen. Not practical in all cases but you're right, DrawString creates weird text and DrawText onto a bitmap looks horrible.
If your bitmap is not the same size as your display area, it might just be a resizing issue, where .NET scales the bitmap to the display size and you get funny looking text.
Can you test with a bitmap created at the same size as your display area?
Can you post the smallest program that suffers from this problem? I can't reproduce it like this -- the antialiasing looks fine:
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
public class Program
{
public static void Main()
{
Bitmap bmp = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
using (Font font = new Font("Arial", 10, GraphicsUnit.Point))
using (Graphics g = Graphics.FromImage(bmp))
{
Rectangle clip = Rectangle.FromLTRB(0, 0, 100, 100);
g.Clear(Color.Red);
TextFormatFlags tf = TextFormatFlags.Left;
TextRenderer.DrawText(g, #"C:\Development\Testing\blag", font, clip, Color.White, Color.Transparent, tf);
}
Form form = new Form();
form.BackgroundImage = bmp;
Application.Run(form);
}
}

Categories

Resources