///<summary>Since Graphics doesn't have a line height property. ///Our rendering algorithm focuses on wrapping the text on the same character on each line for all printing options (screen, printer, pdf), ///as well as making the text exactly fix vertically within the bounds given in order to properly support sheet vertical growth behavior. ///Since all printing options have slightly different implementations within their drivers, the same font when used in each option is slightly ///different. As a result, the printed text width will vary depending on which printing option is used. We return a rectangle representing the ///actual drawn area of the output string for use in special cases such as the TreatmentPlan.Note, which has a border drawn around it always. ///</summary> public static RectangleF DrawString(Graphics g, string str, Font font, Brush brush, Rectangle bounds, HorizontalAlignment align) { if (str.Trim() == "") { return(bounds); //Nothing to draw. } StringFormat sf = StringFormat.GenericTypographic; //The overload for DrawString that takes a StringFormat will cause the tabs '\t' to be ignored. //In order for the tabs to not get ignored, we have to tell StringFormat how many pixels each tab should be. //50.0f is the closest to our Fill Sheet Edit preview. sf.SetTabStops(0.0f, new float[1] { 50.0f }); RichTextBox textbox = CreateTextBoxForSheetDisplay(str, font, bounds.Width, bounds.Height, align); List <RichTextLineInfo> listTextLines = GetTextSheetDisplayLines(textbox); float deviceLineHeight = g.MeasureString(str.Replace("\r", "").Replace("\n", ""), font, int.MaxValue, sf).Height; float scale = deviceLineHeight / textbox.Font.Height;//(size when printed)/(size on screen) font = new Font(font.FontFamily, font.Size * scale, font.Style); float maxLineWidth = 0; for (int i = 0; i < listTextLines.Count; i++) { string line = RichTextLineInfo.GetTextForLine(textbox.Text, listTextLines, i); if (line.Trim().Length > 0) { float textWidth = g.MeasureString(line, font, int.MaxValue, sf).Width; float x; if (align == HorizontalAlignment.Left) { x = bounds.X + listTextLines[i].Left * scale; } else if (align == HorizontalAlignment.Center) { x = bounds.X + ((bounds.Width - (textWidth * scale)) / 2); } else //Right { x = bounds.X + bounds.Width - (textWidth * scale); } float y = bounds.Y + listTextLines[i].Top * scale; g.DrawString(line, font, brush, x, y, sf); maxLineWidth = Math.Max(maxLineWidth, (listTextLines[i].Left * scale) + textWidth); } } textbox.Font.Dispose(); //This font was dynamically created when the textbox was created. textbox.Dispose(); sf.Dispose(); font.Dispose(); return(new RectangleF(bounds.X, bounds.Y, maxLineWidth, bounds.Height)); }
///<summary>The PdfSharp version of Graphics.DrawString(). scaleToPix scales xObjects to pixels. ///Our rendering algorithm focuses on wrapping the text on the same character on each line for all printing options (screen, printer, pdf), ///as well as making the text exactly fix vertically within the bounds given in order to properly support sheet vertical growth behavior. ///Since all printing options have slightly different implementations within their drivers, the same font when used in each option is slightly ///different. As a result, the printed text width will vary depending on which printing option is used. We return a rectangle representing the ///actual drawn area of the output string for use in special cases such as the TreatmentPlan.Note, which has a border drawn around it always. ///</summary> public static RectangleF DrawStringX(XGraphics xg, string str, XFont xfont, XBrush xbrush, RectangleF bounds, HorizontalAlignment align) { if (str.Trim() == "") { return(bounds); //Nothing to draw. } XStringFormat sf = XStringFormats.Default; //There are two coordinate systems here: pixels (used by us) and points (used by PdfSharp). //MeasureString and ALL related measurement functions must use pixels. //DrawString is the ONLY function that uses points. //pixels: FontStyle fontstyle = FontStyle.Regular; if (xfont.Style == XFontStyle.Bold) { fontstyle = FontStyle.Bold; } //pixels: (except Size is em-size) Font font = new Font(xfont.Name, (float)xfont.Size, fontstyle); RichTextBox textbox = CreateTextBoxForSheetDisplay(str, font, (int)Math.Ceiling(bounds.Width), (int)Math.Ceiling(bounds.Height), align); List <RichTextLineInfo> listTextLines = GetTextSheetDisplayLines(textbox); float deviceLineHeight = PointsToPixels((float)xg.MeasureString(str.Replace("\r", "").Replace("\n", ""), xfont, sf).Height); float scale = deviceLineHeight / textbox.Font.Height;//(size when printed)/(size on screen) font.Dispose(); xfont = new XFont(xfont.Name, xfont.Size * scale, xfont.Style); double maxLineWidth = 0; for (int i = 0; i < listTextLines.Count; i++) { string line = RichTextLineInfo.GetTextForLine(textbox.Text, listTextLines, i); if (line.Trim().Length > 0) { float x = PixelsToPoints(bounds.X + listTextLines[i].Left * scale); //The +11 was arrived at via trial an error. Without this, the first line of each page after the first is halfway on the previous page. float y = PixelsToPoints(bounds.Y + listTextLines[i].Bottom * scale); //There is currently a problem with printing the tab character '\t' when using XStringFormat. //C#'s StringFormat has a method called SetTabStops() which can be used to get the tabs to be drawn (see regular printing above). //We're doing nothing for now because the current complaint is only for printing, not PDF creation. //A workaround is to not use tabs and to instead use separate static text fields that are spaced out as desired. xg.DrawString(line, xfont, xbrush, x, y, sf); maxLineWidth = Math.Max(maxLineWidth, PixelsToPoints(listTextLines[i].Left * scale) + xg.MeasureString(line, xfont, sf).Width); } } textbox.Font.Dispose(); //This font was dynamically created when the textbox was created. textbox.Dispose(); //sf.Dispose();//Does not exist for PDF. //xfont.Dispose();//Does not exist for PDF fonts. return(new RectangleF(bounds.X, bounds.Y, PointsToPixels((float)maxLineWidth), bounds.Height)); }
///<summary>If lineIndex is past the last line, then the information of the last line will be returned. </summary> public static RichTextLineInfo GetOneTextLine(RichTextBox textbox, int lineIndex) { if (lineIndex < 0 || GetTextLineCount(textbox) == 0) { return(new RichTextLineInfo()); } RichTextLineInfo lineInfo = new RichTextLineInfo(); //GetFirstCharIndexFromLine() returns -1 if lineIndex is past the last line. lineInfo.FirstCharIndex = textbox.GetFirstCharIndexFromLine(lineIndex); //-1 if lineIndex >= count of lines in rtb. if (lineInfo.FirstCharIndex == -1) //Return the last line's information. { lineIndex = GetTextLineCount(textbox) - 1; lineInfo.FirstCharIndex = textbox.GetFirstCharIndexFromLine(lineIndex); //First character of last line. } Point posThisLine = textbox.GetPositionFromCharIndex(lineInfo.FirstCharIndex); Point posNextLine; if (lineIndex != GetTextLineCount(textbox) - 1) //This is not the last line. { posNextLine = textbox.GetPositionFromCharIndex(textbox.GetFirstCharIndexFromLine(lineIndex + 1)); //Top of next line=bottom of this line. } else //Only do this "phony last line" logic when we are looking at the actual last line. Trying to copy all the content/formatting of an //entire RichTextBox causes all kinds of printing/Pdf/alignment/spacing issues in Sheets and Forms. { using (RichTextBox rtb = new RichTextBox()) { //Copy values to new RichTextBox because we might modify text below. rtb.Rtf = textbox.Rtf; //Setting the rtb.Font property in this RichTextBox causes an Out of Memory Exception when the Treatment Finder report attempted to run for 100+ //pdfs. Attempts were made to employ a "using" to dispose of the font, as well as to ensure the textbox passing it in was disposed of, //however neither method succeeded. Since the only importance of the Font in this context is getting the position of the Next Line, it is also //not necessary here because we obtain that from the Rtf and postNextLine. It's removal should not change the user experience. However, it will //remain here, commented out, for future information so that others do not attempt to employ a font here. //rtb.Font=textbox.Font; rtb.Size = textbox.Size; //According to MSDN, "If no paragraph is selected in the control, setting this property applies the alignment setting to the paragraph in //which the insertion point appears as well as to paragraphs created after the paragraph that has the alignment property setting." //https://msdn.microsoft.com/en-us/library/system.windows.forms.richtextbox.selectionalignment(v=vs.110).aspx //When copying the RTF from the given textbox it also passes the selection to the new RichTextBox which is needed for the SelectionAlignment. rtb.SelectionAlignment = textbox.SelectionAlignment; rtb.AppendText("\r\n\t"); //Add a phony line. posNextLine = rtb.GetPositionFromCharIndex(rtb.GetFirstCharIndexFromLine(lineIndex + 1)); //Top of next line=bottom of this line. } } lineInfo.Left = posThisLine.X; lineInfo.Top = posThisLine.Y; lineInfo.Bottom = posNextLine.Y; return(lineInfo); }
///<summary>Returns a list of strings representing the lines of sheet text which will display on screen when viewing the specified text.</summary> public static List <RichTextLineInfo> GetTextSheetDisplayLines(RichTextBox textbox) { List <RichTextLineInfo> listLines = new List <RichTextLineInfo>(); int lineCount = GetTextLineCount(textbox); for (int i = 0; i < lineCount; i++) { RichTextLineInfo line = new RichTextLineInfo(); line.FirstCharIndex = textbox.GetFirstCharIndexFromLine(i); Point pos = textbox.GetPositionFromCharIndex(line.FirstCharIndex); line.Left = pos.X; line.Top = pos.Y; listLines.Add(line); } return(listLines); }
///<summary>If lineIndex is past the last line, then the information of the last line will be returned. </summary> public static RichTextLineInfo GetOneTextLine(RichTextBox textbox, int lineIndex) { if (lineIndex < 0 || GetTextLineCount(textbox) == 0) { return(new RichTextLineInfo()); } RichTextLineInfo lineInfo = new RichTextLineInfo(); Point posThisLine; Point posNextLine; using (RichTextBox rtb = new RichTextBox()) { //Copy values to new RichTextBox because we might modify text below. rtb.Text = textbox.Text; rtb.Font = textbox.Font; rtb.Size = textbox.Size; //According to MSDN, "If no paragraph is selected in the control, setting this property applies the alignment setting to the paragraph in //which the insertion point appears as well as to paragraphs created after the paragraph that has the alignment property setting." //https://msdn.microsoft.com/en-us/library/system.windows.forms.richtextbox.selectionalignment(v=vs.110).aspx //Since rb is new, there is no seletion and insertion point is 0. Therefore, the entire text will be affected by this alignment. rtb.SelectionAlignment = textbox.SelectionAlignment; //GetFirstCharIndexFromLine() returns -1 if lineIndex is past the last line. lineInfo.FirstCharIndex = rtb.GetFirstCharIndexFromLine(lineIndex); //-1 if lineIndex >= count of lines in rtb. if (lineInfo.FirstCharIndex == -1) //Return the last line's information. { lineIndex = GetTextLineCount(rtb) - 1; lineInfo.FirstCharIndex = rtb.GetFirstCharIndexFromLine(lineIndex); //First character of last line. } if (lineIndex == GetTextLineCount(rtb) - 1) //This is the last line. { rtb.AppendText("\r\n\t"); //Add a phony line. } posThisLine = rtb.GetPositionFromCharIndex(lineInfo.FirstCharIndex); posNextLine = rtb.GetPositionFromCharIndex(rtb.GetFirstCharIndexFromLine(lineIndex + 1)); //Top of next line=bottom of this line. } lineInfo.Left = posThisLine.X; lineInfo.Top = posThisLine.Y; lineInfo.Bottom = posNextLine.Y; return(lineInfo); }