using System; using System.Collections.Generic; using System.Text; using C1.Win.C1FlexGrid; using Itenso.Rtf; using Itenso.Rtf.Support; using Volian.Controls.Library; using iTextSharp.text; using iTextSharp.text.pdf; using VEPROMS.CSLA.Library; using System.Text.RegularExpressions; namespace Volian.Print.Library { #region Settings // RHM20150429 - Table Scrunch public partial class vlnTable { public string DebugInfo { get; set; } = null; public int ItemID { get; set; } = 0; public bool ShowDetails { get; set; } = false; public bool IsTooBig { get; set; } = false; } public partial class vlnCell { #region Log4Net private static readonly log4net.ILog _MyLog = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); #endregion public bool ShowDetails => MyTable.ShowDetails; private static readonly float YAdjust_h = 0; } #endregion public partial class vlnTable { #region Properties // HContents: For each row, contains the largest height for all columns in that row. // Merged cells are accounted for. This is done in vlnCells:SetupCells public float[] HContents { get; set; } public float[] RowTop { get; set; } public float[] ColLeft { get; set; } public VlnFlexGrid MyFlexGrid { get; set; } public VlnBorders MyBorders { get; set; } public VlnGridCellShading MyShading { get; set; } public iTextSharp.text.pdf.PdfContentByte MyContentByte { get; set; } public vlnCells MyCells { get; set; } public float Height => RowTop[RowTop.Length - 1]; public float Width => ColLeft[ColLeft.Length - 1]; private static float _XOffset = -1; // This moves the borders with respect to the text public static float XOffset { get { return vlnTable._XOffset; } set { vlnTable._XOffset = value; } } private static float _YOffset = 0; // This moves the borders with respect to the text public static float YOffset { get { return vlnTable._YOffset; } set { vlnTable._YOffset = value; } } private static float _LineThicknessForThin = .9F; public static float LineThicknessForThin { get { return vlnTable._LineThicknessForThin; } set { vlnTable._LineThicknessForThin = value; } } private static float _LineThicknessForThick = 1.8F; public static float LineThicknessForThick { get { return vlnTable._LineThicknessForThick; } set { vlnTable._LineThicknessForThick = value; } } private static float _LineThicknessForDouble = .6F; public static float LineThicknessForDouble { get { return vlnTable._LineThicknessForDouble; } set { vlnTable._LineThicknessForDouble = value; } } public static float DoubleLineOffset { get { return (LineThicknessForThick - LineThicknessForDouble) / 2; }//LineThicknessForThick - 2 * LineThicknessForDouble; } } private float _TotalContentHeight = 0;// RHM20150525 - Table Scrunc public float TotalContentHeight { get { if (_TotalContentHeight == 0) { foreach (float h in HContents) _TotalContentHeight += h; } return _TotalContentHeight; } } // RHM20150525 - Table Scrunc // SpaceAvailable is the amount of white space there is to work with. It uses the bottom location // of the table and subtracts how much height the actual contents take, TotalContentHeight. TotalContentHeight // includes size/height (font size - assumes 12, 6LPI) of text & leading (space between lines) public float SpaceAvailable => TotalContentHeight == 0 ? 0 : RowTop[RowTop.Length - 1] - TotalContentHeight; // B2018-085 - Table Scrunch Fix: // LeadingSpaceAvailable is the amount of 'blank' space between lines, this is included in TotalContentHeight. // 1.5 is difference between 12 and 13.5, 12 is the standard font size, i.e. 6LPI. 13.5 is line leading. // 1.5/13.5 is amount of space used by leading. public float LeadingSpaceAvailable => TotalContentHeight == 0 ? 0 : TotalContentHeight * 1.5F / 13.5F; // TooBig is the amount that the grid is over the amount of space on the page. To start (value in TooBig), it is set to // (height of grid w/ compressed setting if needed + line) - (how much room on page), i.e. negative number if it can fit private float _TooBig;// RHM20150525 - Table Scrunc public float TooBig { get { return _TooBig; } set { _TooBig = value; //Debugging //_Adjustment = Math.Min(1F, (_TooBig / (RowTop.Length - 1)) / 12); //if (ShowDetails) Console.WriteLine("TooBig\t{0}\t{1}", value, _Adjustment); //if(ShowDetails) Console.WriteLine("Before RowTop={0}", RowTop[RowTop.Length-1] ); // SpaceAvailable - amount of white space that can be removed. if the amount of the table that is too big to fit // is less than the amount of white space that can be removed, then set extra to this amount divided by the // number of cells, otherwise it is 0. float extra = (_TooBig < SpaceAvailable) ? (SpaceAvailable - _TooBig) / HContents.Length : 0; // B2018-085 Table Scrunch - Adjust Line Spacing float leadingAdjust = 1.0F; // if removing white space doesn't make table scrunch enough, see if adjusting leading will help if (extra == 0) { // See if we can adjust Leading float newLeading = Math.Max(12.0F, 13.5F - (_TooBig - SpaceAvailable) / LeadingSpaceAvailable); leadingAdjust = newLeading / MyCells.MyLeading; MyCells.MyLeading = newLeading; foreach (vlnCell myCell in MyCells) { myCell.MyPara.SetLeading(MyCells.MyLeading, 0); } } // The following resets the height of all rows in the table for (int r = 0; r < RowTop.Length - 1; r++) { HContents[r] *= leadingAdjust; RowTop[r + 1] = RowTop[r] + HContents[r] + extra; } //if(ShowDetails) Console.WriteLine("After RowTop={0}", RowTop[RowTop.Length-1] ); } } public float Adjustment { get; set; } = 0; public float YAdjust_RowTop => Adjustment * 12; private void DrawRuler(PdfContentByte cb, float x, float yTop, float height)// RHM20150525 - Table Scrunc { VlnSvgPageHelper myPageHelper = cb.PdfWriter.PageEvent as VlnSvgPageHelper; if (myPageHelper.DebugLayer != null) { cb.BeginLayer(myPageHelper.DebugLayer); cb.SaveState(); cb.SetLineWidth(.1F); cb.SetColorStroke(new Color(System.Drawing.Color.Orange)); float yBottom = yTop - height; cb.MoveTo(x, yTop); cb.LineTo(x, yBottom); int i = 0; for (float y = yTop; y >= yBottom; y -= 10) { float w = 10; if (i % 10 == 0) w = 30; else if (i % 5 == 0) w = 20; cb.SetLineWidth(w / 30); i++; cb.MoveTo(x + w, y); cb.LineTo(x, y); cb.Stroke(); } cb.Stroke(); cb.RestoreState(); cb.EndLayer(); } } private void ShowRowTops(string location)// RHM20150525 - Table Scrunc { if (!ShowDetails) return; StringBuilder sb = new StringBuilder(); for (int i = 0; i < RowTop.Length; i++) sb.Append("\t" + RowTop[i].ToString()); Console.WriteLine(location + "\t" + sb.ToString()); } private void EndSetup()// RHM20150525 - Table Scrunc { if (ShowDetails) ShowRowTops("vlnTable.ctor.2"); if (ShowDetails) Console.WriteLine("^^^^^^vlnTable============{0}===============", ItemID); } private void SetupDebug(VlnFlexGrid myFlexGrid)// RHM20150525 - Table Scrunc { //ShowDetails = myFlexGrid.GetMyItemInfo().InList(38329); //if(ShowDetails) Console.WriteLine("vvvvvvlnTable============{0}===============", ItemID); ItemInfo myItemInfo = myFlexGrid.GetMyItemInfo(); ItemID = myItemInfo.ItemID; Path = myItemInfo.ShortPath; DebugInfo = string.Format("DebugID = {0}, ID={1} Type={2} TypeName='{3}' StepLevel={4} ShortPath={5} Width={6} Left={7} YOffset={8}", 0, myItemInfo.ItemID, myItemInfo.FormatStepType, myItemInfo.FormatStepData == null ? "NoStepData" : myItemInfo.FormatStepData.Type, myItemInfo.StepLevel, myItemInfo.ShortPath, Width, XOffset, YOffset); } #endregion public string Path { get; set; } #region Constructors public vlnTable(VlnFlexGrid myFlexGrid, iTextSharp.text.pdf.PdfContentByte myContentByte) { MyFlexGrid = myFlexGrid; MyContentByte = myContentByte; InitializeSizes(); SetupDebug(myFlexGrid);// RHM20150525 - Table Scrunc MyCells = new vlnCells(this, MyFlexGrid, MyContentByte); EndSetup();// RHM20150525 - Table Scrunc } #endregion #region Private Methods private void InitializeSizes() { RowTop = new float[MyFlexGrid.Rows.Count + 1]; HContents = new float[MyFlexGrid.Rows.Count];// RHM20150525 - Table Scrunc RowTop[0] = 0; for (int r = 0; r < MyFlexGrid.Rows.Count; r++) RowTop[r + 1] = RowTop[r] + 72 * (MyFlexGrid.Rows[r].Height == -1 ? MyFlexGrid.Rows.DefaultSize : MyFlexGrid.Rows[r].Height) / (float)MyFlexGrid.DPI; ColLeft = new float[MyFlexGrid.Cols.Count + 1]; ColLeft[0] = 0; for (int c = 0; c < MyFlexGrid.Cols.Count; c++) ColLeft[c + 1] = 2 + ColLeft[c] + 72 * (MyFlexGrid.Cols[c].Width == -1 // RHM20150429 - Table Scrunch ? MyFlexGrid.Cols.DefaultSize : MyFlexGrid.Cols[c].Width) / (float)MyFlexGrid.DPI; } #endregion #region Public Methods public void AdjustRowTop(int row1, int row2, float hNew) { // shift the row top of next row down by the adjustment, i.e. if the content needs more space float hAdjust = hNew - (RowTop[row2 + 1] - RowTop[row1]); for (int r = row2; r < MyFlexGrid.Rows.Count; r++) RowTop[r + 1] += hAdjust; } public void ToPdf(iTextSharp.text.pdf.ColumnText myColumnText, float left, float top) { //ZoomGrid(myColumnText, left, top); if (ShowDetails) Console.WriteLine("vvvvvvToPdf============{0}===============", ItemID);// RHM20150525 - Table Scrunc ShowRowTops("ToPDF1"); PdfContentByte cb = myColumnText.Canvas; // RHM20150429 - Table Scrunch VlnSvgPageHelper myPageHelper = cb.PdfWriter.PageEvent as VlnSvgPageHelper; string myLPI = myPageHelper.YMultiplier == 1.0F ? ",6LPI" : ",7LPI"; Rtf2Pdf.DrawPdfDebug(myColumnText.Canvas, left-2, top+1, left + Width, top-myPageHelper.YMultiplier *(Height+1), DebugInfo+", Height= " + (myPageHelper.YMultiplier * Height).ToString() + myLPI, 0); PdfLayer textLayer = myPageHelper?.TextLayer; // RHM20150429 - Table Scrunch if (textLayer != null) cb.BeginLayer(textLayer); myPageHelper.AddGap(top, top - Height, left, left + Width); MyCells.ToPdf(myColumnText, left, top); DrawRuler(myColumnText.Canvas, left + Width, top, myPageHelper.YMultiplier * (Height + 1)); // RHM20150429 - Table Scrunch if (textLayer != null) cb.EndLayer(); ShowRowTops("ToPDF2"); if (ShowDetails) Console.WriteLine("^^^^^^ToPdf============{0}===============", ItemID); } //For Debugging private void ZoomGrid(iTextSharp.text.pdf.ColumnText myColumnText, float left, float top) { PdfDestination dest = new PdfDestination(PdfDestination.FITR, left - 4, top - Height - 4, left + Width, top + 4); myColumnText.Canvas.SetAction(PdfAction.GotoLocalPage(myColumnText.Canvas.PdfWriter.CurrentPageNumber, dest, myColumnText.Canvas.PdfWriter), left, top - Height, left + Width, top); } #endregion } public partial class vlnCells : List // RHM20150429 - Table Scrunch { #region Log4Net private static readonly log4net.ILog _MyLog = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); #endregion #region Properties public VlnFlexGrid MyFlexGrid { get; set; } public vlnTable MyTable { get; set; } public bool ShowDetails// RHM20150525 - Table Scrunc { get { return MyTable.ShowDetails; }} public iTextSharp.text.pdf.PdfContentByte MyContentByte { get; set; } #endregion #region Constructors public vlnCells(vlnTable myTable, VlnFlexGrid myFlexGrid, iTextSharp.text.pdf.PdfContentByte myContentByte) { MyTable = myTable; MyFlexGrid = myFlexGrid; MyContentByte = myContentByte; SetupCells(); myTable.MyShading = VlnGridCellShading.Get(myFlexGrid.MyShading.ConvertToString()); // C2021-004 Table Cell Shading myTable.MyBorders = VlnBorders.Get(myFlexGrid.MyBorders.ConvertToString()); } #endregion public float MyLeading { get; set; } = 13.5F; #region Private Methods private void SetupCells() { // Create a ColumnText to determine the cell heights iTextSharp.text.pdf.ColumnText myColumnText1 = new iTextSharp.text.pdf.ColumnText(MyContentByte); // Walk through all the rows for (int r = 0; r < MyFlexGrid.Rows.Count; r++) { float hMax = 12; // RHM20150429 - Table Scrunch for (int c = 0; c < MyFlexGrid.Cols.Count; c++) { CellRange cr = MyFlexGrid.GetMergedRange(r, c); // B2018-033 VCS SAG-6 Steps 3 and 9 and SACRG1 Step 13 // Tables not being scrunched properly // Merged cells should be adjusted based upon the bottom most cell of the range if(MyTable.HContents[r]==0) MyTable.HContents[r] = 12; if (cr.r2 == r && cr.c1 == c)// Use last Row { float w = MyTable.ColLeft[cr.c2 + 1] - MyTable.ColLeft[cr.c1]; float h = MyTable.RowTop[cr.r2 + 1] - MyTable.RowTop[cr.r1]; string str = MyFlexGrid.GetCellRTFString(r, c) ?? string.Empty; if (str.Contains(@"\cf")) { str = System.Text.RegularExpressions.Regex.Replace(str, @"\\cf[0-9]\\", @"\"); str = System.Text.RegularExpressions.Regex.Replace(str, @"\\cf[0-9] ", @""); } // the FreeMono font is used for the editor, but VESymbFix is used for printing // The font spefication is embeded in the table text so we need to change the font reference from // FreeMono to VESymbFix before we print the table if (str.ToUpper().Contains("FREEMONO")) str = str.Replace("FreeMono", "VESymbFix"); DisplayText dt = new DisplayText(MyFlexGrid.GetMyItemInfo(), str, false); str = dt.StartText; str = PreProcessRTF(w, str); // If the font is not proportional, pass the symbol font through to RtfToParagraph. It is needed // in underlying code to set the chunk's font if the first character of a cell is a hardspace. Without // this, pdfs were using the Helvetica font, which is a default itextsharp font. ItemInfo ii = MyFlexGrid.GetMyItemInfo(); iTextSharp.text.Font ff = null; if (!ii.FormatStepData.Font.FontIsProportional()) ff = Volian.Svg.Library.VolianPdf.GetFont("VESymbFix", (int)ii.FormatStepData.Font.Size, (int)ii.FormatStepData.Font.WindowsFont.Style); iTextSharp.text.Paragraph myPara = RtfToParagraph(str, ff, r, c); myColumnText1.SetSimpleColumn(0, 0, w-2, MyContentByte.PdfDocument.PageSize.Top); // Padding = 4 if (str.Contains(@"\'05")) { // if there is a hanging indent, the iTextSharp paragraph properties must be set // to print the indent. Replace the indent 'token' with a non-used symbol, this will // create a chunk with that symbol. Then loop through all of the chunks until we find // this symbol, adding up the widths to that point. This width is the value that // needs to be used to set the indent. // Notes: // A hard return will reset the chkW (indent width) back to zero. // We jump out of the processing loop after the first indent token is found and ignor any other ones float chkW = CalculateHangingIndent(str); myPara.IndentationLeft = chkW; myPara.FirstLineIndent = -chkW; } Match match = Regex.Match(str, @"\\fi([-0-9]*) ?\\li([0-9]*)"); if (match.Success) { float fi = float.Parse(match.Groups[1].Value) / 20; // 72 is dots per inch & 96 is standard DPI (120 is KBR's machine) float li = float.Parse(match.Groups[2].Value) / 20; // if there is a hanging indent, the iTextSharp paragraph properties must be set // to print the indent. myPara.IndentationLeft = li; myPara.FirstLineIndent = fi; } // RHM 20120925 - Line spacing should default to 6 lines per inch. // B2018-003 Change code to use absolute Leading rather than Multiplied based on Font size myPara.SetLeading(MyLeading, 0); // TableScrunch Phase 2: SpacingAfter is the space after the bottom line of text. // Setting to 0 removes blank space between bottom of text & line myPara.SpacingAfter = 8; // RHM 20120925 - Add a line to properly space text from lines. if(Rtf2Pdf.GetTableScrunchingStatus(TableScrunching.Phase2)) // RHM20150429 - Table Scrunch myPara.SpacingAfter = 0; FixHyphens(myPara, MyTable); FixBackslashes(myPara, MyTable); // TableScrunch Phase 3: Remove new lines at end of text if (Rtf2Pdf.GetTableScrunchingStatus(TableScrunching.Phase3)) TrimNewlines(myPara); // RHM20150429 - Table Scrunch myColumnText1.AddElement(myPara); float posBefore = myColumnText1.YLine; // position is y of top of paragraph since paragraph was just added _ = myColumnText1.Go(true); // 'puts out' text for info only float posAfter = myColumnText1.YLine; // posAfter is y of bottom of paragraph, really y top of line below this float hContent = 4 + posBefore - posAfter; // TableScrunch Phase 4: sets content height, uses the size derived from iTextsharp putting out text // y loc before text is put out - y loc after text is put out if (Rtf2Pdf.GetTableScrunchingStatus(TableScrunching.Phase4))// RHM20150525 - Table Scrunc { hContent = 0 + posBefore - posAfter; } //if(ShowDetails) Console.WriteLine("{0}\t{1}\t{2}",r,c, hContent/12); // hMax is maximum height of row taking into account merged cells if (cr.r1 == cr.r2) // not merged: hMax = Math.Max(hMax, hContent); // B2018-033 VCS SAG-6 Steps 3 and 9 and SACRG1 Step 13 // Tables not being scrunched properly // Merged cells should be adjusted based upon the bottom most cell of the range else if(cr.r2 == r) // Adjust for Previous rows within merged cells hMax = Math.Max(hMax, hContent- SumContents(cr.r1,cr.r2)); // h is row height defined in table, if height of content is > height of row, adjust. // if the content's height is larger than the height of cell, shift row below down using AdjustRowTop. if (hContent > h) { //if (ShowDetails) Console.WriteLine("Less {0},{1},{2},{3},{4},{5},{6}", r, c, posBefore, posAfter, h, hContent, hMax); MyTable.AdjustRowTop(cr.r1, cr.r2, hContent); } // TableScrunch Phase 5: stores max height for row content if (Rtf2Pdf.GetTableScrunchingStatus(TableScrunching.Phase5)) { //if (ShowDetails) Console.WriteLine("Greater{0},{1},{2},{3},{4},{5}", r, c, posBefore, posAfter, hContent, hMax); //B2018-033 - Adjuust current row // For each row, hMax is te largest height for all columns. MyTable.HContents[r] = hMax+2; // make contents of the row a little bigger than max (max may be for merged cells) } Add(new vlnCell(cr.r1, cr.c1, cr.r2, cr.c2, MyTable, myPara,hContent)); } } } //For Debugging: // This summarizes the Content heights per row //int i=0; //foreach (float h in MyTable.HContents) // Console.WriteLine("{0}\t{1}", ++i, h); } private float SumContents(int first, int last) { float total = 0; for (int i = first; i < last; i++) total += MyTable.HContents[i]; return total; } private string BuildChunks(string str) { StringBuilder sb = new StringBuilder(); foreach(char c in str) { if (((int)c) < 0x20 || ((int)c) > 0x7F) sb.Append(string.Format("<{0:X4}>",(int)c)); else sb.Append(c); } return $"{{{sb}}}"; } private void TrimNewlines(Paragraph myPara) { while (myPara.Count > 0 && ParaEndsWithNewLine(myPara)) myPara.RemoveAt(myPara.Count - 1); } private bool ParaEndsWithNewLine(Paragraph myPara) => myPara[myPara.Count - 1] is Chunk chk && chk.Content == "\n"; internal static void FixHyphens(Paragraph myPara, vlnTable myTable) { // Find chunk with hardhyphen // If a hard hyphen is found, the remove it and add a dash (keyboard minus sign) to the beginning // of the text that follows it. int hype=-1; while ((hype = GetHyphen(myPara)) > -1) { Font fnt = null; if (hype > 0) { Chunk chk1 = (Chunk)myPara[hype - 1]; if (hype > 1 && chk1.Content.Equals("\n")) chk1 = (Chunk)myPara[hype - 2]; // use the font of the text that precedes the hyphen (dash) fnt = new Font(chk1.Font); //B2020-80 create new instance of the font else previous myPara chunks font size may change } else if (hype < myPara.Count - 1) { Chunk chk2 = (Chunk)myPara[hype + 1]; if (hype != 0 && !chk2.Content.Equals("\n")) // use the font of the text that follows the hyphen (dash) fnt = new Font(chk2.Font); //B2020-80 create new instance of the font else previous myPara chunks font size may change } if (fnt == null) { // if there was no text following the hypen, then use the font defined for tables in the plant's format VEPROMS.CSLA.Library.VE_Font vfnt = myTable.MyFlexGrid.GetMyItemInfo().FormatStepData.Font; //System.Drawing.Font ffont = new System.Drawing.Font(vfnt.Family, (float)vfnt.Size); System.Drawing.Font ffont = VE_Font.GetWinSysFont(vfnt.Family, (float)vfnt.Size); fnt = Volian.Svg.Library.VolianPdf.GetFont(ffont); fnt.SetStyle(myPara.Font.Style); fnt.SetColor(myPara.Font.Color.R,myPara.Font.Color.G,myPara.Font.Color.B); } Chunk chk = (Chunk)myPara[hype]; // THIS CODE IS USED FOR DEBUGGING //if (!fnt.Familyname.StartsWith("L")) //{ // Console.WriteLine("font = {0}", makeitpretty(fnt)); // Console.WriteLine("Hype = {0}\r\n{1}",hype, myPara.Content); // if (hype > 0) // { // Chunk chk3 = (Chunk)myPara[hype - 1]; // Console.WriteLine("before '{0}'", chk3.Content); // } // Chunk chk4 = (Chunk)myPara[hype]; // Console.WriteLine("during '{0}'", chk4.Content); // if (hype < myPara.Count -1) // { // Chunk chk5 = (Chunk)myPara[hype + 1]; // Console.WriteLine("after '{0}'", chk5.Content); // } //} string prefix = ""; if (chk.Content == "\u2011" && hype < (myPara.Count - 1)) { myPara.RemoveAt(hype);// Remove standalone hyphen chk = (Chunk)myPara[hype]; prefix = "-"; } myPara.RemoveAt(hype); // B2020-080 Wolf Creek procedure STS-IC-231 at Step 4.3 – 1st row in the I&C folder under Maintenance Procedures, // equation text that has a "-10" that is superscript, is not printing superscript in a table cell. // When we replace the symbol font dash character with the Ascii dash character, we need to // check for super/subscript attributes of the text following the dash. If that text // is super/subscripted then we need to set those attributes in the new chunk. Chunk chkJ = new Chunk(prefix + chk.Content.Replace("\u2011", "-"), fnt); float? subsup = (float?)chk.Attributes["SUBSUPSCRIPT"]; if (subsup != null) { if (subsup > 0.0) // superscript if (PrintOverride.CompressPropSubSup) { chkJ.SetTextRise(.33F * chkJ.Font.Size); chkJ.Font.Size *= .75f; } else { chkJ.SetTextRise(.25F * chkJ.Font.Size); if (PrintOverride.CompressSuper) chkJ.Font.Size = 9; } else if (subsup < 0.0) // subscript if (PrintOverride.CompressPropSubSup) chkJ.Font.Size *= .75f; else { // if the subscript is not compressed or if it is compress but not underlined, then move it down if (!PrintOverride.CompressSub || !fnt.IsUnderlined()) chkJ.SetTextRise(-.25F * chkJ.Font.Size); if (PrintOverride.CompressSub) chkJ.Font.Size = 9; } } myPara.Insert(hype,chkJ); } } internal static void FixBackslashes(Paragraph myPara, vlnTable myTable) { // Find chunk with backslash B2014-108 backslash in tables // If a backslash symbold is found, the remove it and add a backslash (keyboard char) to the beginning // of the text that follows it. int bckSlsh = -1; while ((bckSlsh = GetBackslash(myPara)) > -1) { Font fnt = null; if (bckSlsh > 0) { Chunk chk1 = (Chunk)myPara[bckSlsh - 1]; if (bckSlsh > 1 && chk1.Content.Equals("\n")) chk1 = (Chunk)myPara[bckSlsh - 2]; fnt = chk1.Font; // use the font of the text that follows the hyphen (dash) } else if (bckSlsh < myPara.Count - 1) { Chunk chk2 = (Chunk)myPara[bckSlsh + 1]; if (bckSlsh != 0 && !chk2.Content.Equals("\n")) fnt = chk2.Font; // use the font of the text that follows the hyphen (dash) } if (fnt == null) { // if there was no text following the hypen, then use the font defined for tables in the plant's format VEPROMS.CSLA.Library.VE_Font vfnt = myTable.MyFlexGrid.GetMyItemInfo().FormatStepData.Font; //System.Drawing.Font ffont = new System.Drawing.Font(vfnt.Family, (float)vfnt.Size); System.Drawing.Font ffont = VE_Font.GetWinSysFont(vfnt.Family, (float)vfnt.Size); fnt = Volian.Svg.Library.VolianPdf.GetFont(ffont); fnt.SetStyle(myPara.Font.Style); fnt.SetColor(myPara.Font.Color.R, myPara.Font.Color.G, myPara.Font.Color.B); } Chunk chk = (Chunk)myPara[bckSlsh]; // THIS CODE IS USED FOR DEBUGGING //if (!fnt.Familyname.StartsWith("L")) //{ // Console.WriteLine("font = {0}", makeitpretty(fnt)); // Console.WriteLine("Hype = {0}\r\n{1}",hype, myPara.Content); // if (hype > 0) // { // Chunk chk3 = (Chunk)myPara[hype - 1]; // Console.WriteLine("before '{0}'", chk3.Content); // } // Chunk chk4 = (Chunk)myPara[hype]; // Console.WriteLine("during '{0}'", chk4.Content); // if (hype < myPara.Count -1) // { // Chunk chk5 = (Chunk)myPara[hype + 1]; // Console.WriteLine("after '{0}'", chk5.Content); // } //} string prefix = ""; if (chk.Content == "\u2572" && bckSlsh < (myPara.Count - 1)) { myPara.RemoveAt(bckSlsh);// Remove standalone hyphen chk = (Chunk)myPara[bckSlsh]; prefix = "\\"; } myPara.RemoveAt(bckSlsh); myPara.Insert(bckSlsh, new Chunk(prefix + chk.Content.Replace("\u2572", "\\"), fnt)); } } // THIS FUNCTION IS USED FOR DEBUGGING private static string makeitpretty(Font fnt) { return string.Format("font {0}, size {1}, style {2}", fnt.Familyname, fnt.Size, fnt.Style); } private static int GetHyphen(Paragraph myPara) { int index=0; foreach (Chunk chk in myPara.Chunks) if (chk.Content.Contains("\u2011")) return index; else index++; return -1; } private static int GetBackslash(Paragraph myPara) { int index=0; foreach (Chunk chk in myPara.Chunks) if (chk.Content.Contains("\u2572")) return index; else index++; return -1; } public static float CalculateHangingIndent(string rtf) { float chkW = 0; // **** This is intened to be a temporary fix for V.C.Summer **** // Two procedures would not print: A-2 SAG in Summer's SAMG set, and SAG-3 in Summer's PWOG DAMG set. // These procedures have tables with hanging indents and use the Letter Gothic Tall font. // It appears that the font information for the hanging indent character is not included in the RTF // for those table cells. // This piece of code checks for the existance of a font definition of \f1 which is need for the \u9999? indent character // If the a font definition of \f1 is not found then the indent character is removed. This will allow the procedure to print // but the text in that table cell will not be indented. - jsj 10/10/2014 // add the symbol font and then surround the indent. //IRtfDocument rtfDoc2 = RtfInterpreterTool.BuildDoc(rtf.Replace(@"\'05", @"\f1 \u9999? \f0 ")); IRtfDocument rtfDoc2 = RtfInterpreterTool.BuildDoc(rtf.Replace(@"\'05", @"\par\u9999?\par")); Rtf2iTextSharp rtf2IText2 = new Rtf2iTextSharp(rtfDoc2); iTextSharp.text.Paragraph para2 = rtf2IText2.Convert(); for (int ic = 0; ic < para2.Chunks.Count; ic++) { Chunk chk = para2.Chunks[ic] as Chunk; if (chk.Content[0] == 9999) break; if (chk.Content.Contains("\u270f")) { int i = chk.Content.IndexOf('\u270F'); int n = chk.Content.Length; chkW += chk.GetWidthPoint() * i / (n - i); break; } if (chk.Content.Contains("\n")) { if (ic < para2.Chunks.Count - 2 && (para2.Chunks[ic + 1] as Chunk).Content.StartsWith("\u270f")) return chkW; chkW = 0; //hard return - reset chkW (indent start) } chkW += chk.GetWidthPoint(); } return chkW; } private static readonly StepRTB _StatRTB = new StepRTB(); private string PreProcessRTF(float w, string str) { _StatRTB.Text = string.Empty; _StatRTB.Width = (int)w; _StatRTB.Font = MyFlexGrid.Font; if (str.StartsWith(@"{\rtf")) { // The 'Replace(@" \ulnone", @"\u160?\ulnone")' was added so that data in VEWCNSUR_MTC // tables would print. There were a number of tables, for example in STN NB-115/8.1.6, // that had a series of spaces underlined and these spaces were not underlined in the // generated pdf. It seems that there is a problem somewhere in the rtb -> itextsharp // level. This fixed the immediate problem. If there is 'b0' (bold off) after the // space before the 'ulnone' the following won't work. Also, this change was not // made in the non-table code. If the problem occurs there, it should be added. _StatRTB.Rtf = Regex.Replace(str.Replace(@"\~", @"\u160?"), @"(? /// Fix RTF so that text uses the Text font rather than the Symbol font. /// /// /// private string FixRTFToPrint(string rtf,int r, int c) { // Only do this if the text contains symbols. // B2019-067 Check for a proportional symbol font such as FreeSerif which is used for proportional fonts. // The Clock, Check Mark, and Heavy X symbols were not printing the correct character (all are replaced with a WingDing font character) if (rtf.Contains("VESymbFix") || rtf.Contains(Volian.Base.Library.vlnFont.ProportionalSymbolFont)) { // bug fix: B2017-117 was getting an out of window handles error when doing a print all for Bryon // add the using statment to free up window handle that is created doing a New RichTextBox() using (System.Windows.Forms.RichTextBox rtb = new System.Windows.Forms.RichTextBox()) { rtb.Rtf = rtf.Replace(@"\u9586?", "").Replace(@"\~",@"\u160?");// rename backslash character and B2018-107 \~ to hard space to avoid RTF confusion string strRTF = rtf; bool changed = false; // C2017-008 - WCN wants the box symbol to look like their checkoff/signoff box // Check for a list of replacement character for symbols in the format file // ReplaceSymbolChars lets you do one or more of the following with a symbol character: // - change the point size // - change the style // - change the font family // - change the symbol character // // below is taken from the BaseAll format file - is currently commentout and there for reference // Wolf Creek (WCN) currently uses this to increase the size of the box symbol // // // // // get the list of symbol replacements FormatData fmtdata = (MyFlexGrid.GetMyItemInfo().ActiveFormat != null) ? MyFlexGrid.GetMyItemInfo().ActiveFormat.PlantFormat.FormatData : FormatInfo.PROMSBaseFormat.FormatData; ReplaceSymbolCharList SymReplaceList = (fmtdata != null && fmtdata.SectData.ReplaceSymbolCharList != null) ? fmtdata.SectData.ReplaceSymbolCharList : null; // Look at one character at a time for (int i = 0; i < rtb.TextLength; i++) { rtb.Select(i, 1); // If the character is a printable character set the font to the text font // B2019-067 Also check for a proportional symbol font for example, FreeSerif // B2023-031 Added check of a "Text" SelectionType. // The SelectionType was "Empty" for the non-viewable link information and thus flagged it as an error if (rtb.SelectionLength == 0 && rtb.SelectionType == System.Windows.Forms.RichTextBoxSelectionTypes.Text && strRTF != null && (rtb.SelectionFont.FontFamily.Name == "VESymbFix" || rtb.SelectionFont.FontFamily.Name == Volian.Base.Library.vlnFont.ProportionalSymbolFont)) { // Add debug information to the error log if the selection length is zero // Changed debug information to make it more useful _MyLog.WarnFormat("\r\n=====>Font Issues in Table ItemID {0} [{1},{2}] I= {3}, TXT = {4}",MyTable.ItemID,r,c, i, rtb.Text.Substring(Math.Max(0, i-10), Math.Min(20,rtb.TextLength -i))); strRTF = null; } // bug fix: B2017-046 - check for selection length of zero to fix index out of bounds error // System.Windows.Forms.RichTextBox is having issues selection text in some cases (ex. Symbol char followed by a RO reference - without a space between them) // B2019-067 also check fora proportional symbol font for example, FreeSerif if (rtb.SelectionLength > 0 && (rtb.SelectionFont.FontFamily.Name == "VESymbFix" || rtb.SelectionFont.FontFamily.Name == Volian.Base.Library.vlnFont.ProportionalSymbolFont) && rtb.SelectedText[0] > ' ' && rtb.SelectedText[0] <= '\xFF') { //use bolding underline italics from local font System.Drawing.Font fnt = rtb.SelectionFont; // follow through in fixing an Out of Window Handles bug, use new function to see if // we can retrieve the font from a dictionary instead a doing a New and using another // window handle B2017-117 rtb.SelectionFont = VE_Font.GetWinSysFont(DefaultFont, fnt.Style); changed = true; } if ((rtb.SelectionFont.FontFamily.Name == "VESymbFix" || rtb.SelectionFont.FontFamily.Name.StartsWith(Volian.Base.Library.vlnFont.ProportionalSymbolFont)) && rtb.SelectedText.Length > 0) // C2017-036 get best available proportional font for symbols { for (int j = 0; j < SymReplaceList.MaxIndex; j++) { ReplaceChar rc = SymReplaceList[j]; if (rc.Unicode != null && (rc.Family != null || rc.Size != null || rc.Style != null)) { char rc_uchar = Convert.ToChar(Convert.ToInt32(rc.Unicode)); if (rc_uchar == rtb.SelectedText[0]) { //use bolding underline italics from local font System.Drawing.Font fnt = rtb.SelectionFont; System.Drawing.Font fntw = VE_Font.GetWinSysFont(rc.Family ?? fnt.FontFamily.Name, (rc.Size != null) ? (float)rc.Size : fnt.Size, (rc.Style != null) ? (System.Drawing.FontStyle)rc.Style : fnt.Style); rtb.SelectionFont = fntw; if (rc.Replace != null) { char rc_RplChar = Convert.ToChar(Convert.ToInt32(rc.Replace)); rtb.SelectedText = rc_RplChar.ToString(); } changed = true; break; // break out of foreach loop since we changed the character } } } } } if (changed) return rtb.Rtf.Replace("", @"\u9586?"); } } return rtf; } #endregion #region Public Methods // C2021-005 Get the largest font size being used. // - only need for the first line of table cell text private float GetLargestFontSize(vlnCell vc) { // B2021-034 WCN Maintenance; Mechanical; MPM; MPM KJ-004: 7.1.4 Table // has single cell table with just a jpeg image // need to specify MyPara.Chunks in stead of foreach Chunk in MyPara float largestFontSize = 12; foreach (Chunk ck in vc.MyPara.Chunks) { if (ck.Content.EndsWith("\n")) break; // end of first line if (ck.ToString().Trim().Length > 0) largestFontSize = Math.Max(largestFontSize, ck.Font.Size); } return largestFontSize; } public void ToPdf(iTextSharp.text.pdf.ColumnText myColumnText, float left, float top) { // B2021-137 first print the table shading, then print the table with the borders and text foreach (vlnCell myCell in this) { // B2020-034 - the topPlacementAdjust is used to position the text vertically in the table cells. // B2020-082 - AEP Production - American Electric Power - D. C. Cook // / Current (Post FLEX) - U1 NOP NOT [EOPs] - ES-3.3.SProcedure Body.S9..S1..S1..T1. // Table did not print properly. Cell text was truncated. float largestFontSize = GetLargestFontSize(myCell); // C2021-005 get the largest font size for the cell text int topPlacementAdjust = (myCell.MyPara.Font.Size == 12 && myCell.MyPara.Font.Familyname != "Courier New" && myCell.MyPara.Font.Familyname != "VESymbFix") ? 1 : 3; // for font size of 12 set to one, else set to 3 if (largestFontSize != 12) // C2021-005 adjust the text positon based on the font size change topPlacementAdjust -= (int)(largestFontSize - 12); // C2021-005 adjust placement based on font size change // B2020-140 for Wolf Creek check boxes in tables were printing too high in the table cells - added format variable to overide topPlacementAdjust if (myCell.MyPara != null && ItemInfo.Get(myCell.MyTable.ItemID).ActiveFormat.MyStepSectionLayoutData.OverrideTableTopIndent != null) { if (largestFontSize > 18) // C2021-005 Only need when cell text has the empty box symbol which WCN format override the size to 20pts topPlacementAdjust = (int)ItemInfo.Get(myCell.MyTable.ItemID).ActiveFormat.MyStepSectionLayoutData.OverrideTableTopIndent; } myCell.ToPdf(myColumnText, left, top, topPlacementAdjust, true); // B2021-137 only do the table shading } // do the borders foreach (vlnCell myCell in this) { // B2020-034 - the topPlacementAdjust is used to position the text vertically in the table cells. // B2020-082 - AEP Production - American Electric Power - D. C. Cook // / Current (Post FLEX) - U1 NOP NOT [EOPs] - ES-3.3.SProcedure Body.S9..S1..S1..T1. // Table did not print properly. Cell text was truncated. float largestFontSize = GetLargestFontSize(myCell); // C2021-005 get the largest font size for the cell text int topPlacementAdjust = (myCell.MyPara.Font.Size == 12 && myCell.MyPara.Font.Familyname != "Courier New" && myCell.MyPara.Font.Familyname != "VESymbFix") ? 1 : 3; // for font size of 12 set to one, else set to 3 if (largestFontSize != 12) // C2021-005 adjust the text positon based on the font size change topPlacementAdjust -= (int)(largestFontSize - 12); // C2021-005 adjust placement based on font size change // B2020-140 for Wolf Creek check boxes in tables were printing too high in the table cells - added format variable to overide topPlacementAdjust if (myCell.MyPara != null && ItemInfo.Get(myCell.MyTable.ItemID).ActiveFormat.MyStepSectionLayoutData.OverrideTableTopIndent != null) { if (largestFontSize > 18) // C2021-005 Only need when cell text has the empty box symbol which WCN format override the size to 20pts topPlacementAdjust = (int)ItemInfo.Get(myCell.MyTable.ItemID).ActiveFormat.MyStepSectionLayoutData.OverrideTableTopIndent; } myCell.ToPdf(myColumnText, left, top, topPlacementAdjust, false); // B2021-137 Now do the borders and text } } #endregion } public partial class vlnCell // RHM20150429 - Table Scrunch { #region Properties public vlnTable MyTable { get; set; } public VlnBorders MyBorders => MyTable.MyBorders; public int r1 { get; set; } public int c1 { get; set; } public int r2 { get; set; } public int c2 { get; set; } public iTextSharp.text.Paragraph MyPara { get; set; } public float HContent { get; set; } public VlnGridCellShading MyShading => MyTable.MyShading; #endregion #region Line Pattern Diagram /* The diagram below identifies the various border names for the cell in the center |-AboveLeftSide |-AboveRightSide | | | | | | LeftOfTopSide | TopSide | RightOfTopSide ------------------|-------------------|------------------ | | | | |-LeftSide |-RightSide | | | | LeftOfBottomSide | BottomSide | RightOfBottomSide --------------------------------------|------------------ | | | | | | | | |-BelowLeftSide |-BelowRightSide */ #endregion #region Horizontal Line Patterns private GridLinePattern _TopSide = GridLinePattern.Unknown; public GridLinePattern TopSide { get { if (_TopSide == GridLinePattern.Unknown) _TopSide = MyBorders.HorizontalLines[r1, c1]; return _TopSide; } } private GridLinePattern _BottomSide = GridLinePattern.Unknown; public GridLinePattern BottomSide { get { if (_BottomSide == GridLinePattern.Unknown) _BottomSide = MyBorders.HorizontalLines[r2 + 1, c2]; return _BottomSide; } } private GridLinePattern _RightOfTopSide = GridLinePattern.Unknown; public GridLinePattern RightOfTopSide { get { if (_RightOfTopSide == GridLinePattern.Unknown) { if (c2 == MyTable.MyFlexGrid.Cols.Count - 1) _RightOfTopSide = GridLinePattern.None; else { CellRange cr = MyTable.MyFlexGrid.GetMergedRange(r1, c2 + 1); if (cr.r1 == r1) _RightOfTopSide = MyBorders.HorizontalLines[r1, c2 + 1]; else _RightOfTopSide = GridLinePattern.None; } } return _RightOfTopSide; } } private GridLinePattern _LeftOfTopSide = GridLinePattern.Unknown; public GridLinePattern LeftOfTopSide { get { if (_LeftOfTopSide == GridLinePattern.Unknown) { if (c1 == 0) _LeftOfTopSide = GridLinePattern.None; else { // This property is only used if c1==0 or r1==0: // A merged range for row = 0 will always return an r1=0. CellRange cr = MyTable.MyFlexGrid.GetMergedRange(r1, c1 - 1); if (cr.r1 == r1) _LeftOfTopSide = MyBorders.HorizontalLines[r1, c1 - 1]; else // Since this section of code is only entered when r1==0, this else condition should never happen _LeftOfTopSide = GridLinePattern.None; } } return _LeftOfTopSide; } } private GridLinePattern _RightOfBottomSide = GridLinePattern.Unknown; public GridLinePattern RightOfBottomSide { get { if (_RightOfBottomSide == GridLinePattern.Unknown) // Need to consider Merged Cells { if (c2 == MyTable.MyFlexGrid.Cols.Count - 1) _RightOfBottomSide = GridLinePattern.None; else { CellRange cr = MyTable.MyFlexGrid.GetMergedRange(r2, c2 + 1); if (cr.r2 == r2) _RightOfBottomSide = MyBorders.HorizontalLines[r2 + 1, c2 + 1]; else _RightOfBottomSide = GridLinePattern.None; } } return _RightOfBottomSide; } } private GridLinePattern _LeftOfBottomSide = GridLinePattern.Unknown; public GridLinePattern LeftOfBottomSide { get { if (_LeftOfBottomSide == GridLinePattern.Unknown) // Need to consider Merged Cells { if (c1 == 0) _LeftOfBottomSide = GridLinePattern.None; else { CellRange cr = MyTable.MyFlexGrid.GetMergedRange(r2, c1 - 1); if (cr.r2 == r2) _LeftOfBottomSide = MyBorders.HorizontalLines[r2 + 1, c1 - 1]; else _LeftOfBottomSide = GridLinePattern.None; } } return _LeftOfBottomSide; } } #endregion #region Vertical Line Patterns private GridLinePattern _LeftSide = GridLinePattern.Unknown; public GridLinePattern LeftSide { get { if (_LeftSide == GridLinePattern.Unknown) _LeftSide = MyBorders.VerticalLines[r1, c1]; return _LeftSide; } } private GridLinePattern _RightSide = GridLinePattern.Unknown; public GridLinePattern RightSide { get { if (_RightSide == GridLinePattern.Unknown) _RightSide = MyBorders.VerticalLines[r2, c2 + 1]; return _RightSide; } } private GridLinePattern _BelowRightSide = GridLinePattern.Unknown; public GridLinePattern BelowRightSide { get { if (_BelowRightSide == GridLinePattern.Unknown) { if (r2 == MyTable.MyFlexGrid.Rows.Count - 1) _BelowRightSide = GridLinePattern.None; else { CellRange cr = MyTable.MyFlexGrid.GetMergedRange(r2 + 1, c2); if (cr.c2 == c2) _BelowRightSide = MyBorders.VerticalLines[r2 + 1, c2 + 1]; else _BelowRightSide = GridLinePattern.None; } } return _BelowRightSide; } } private GridLinePattern _AboveRightSide = GridLinePattern.Unknown; public GridLinePattern AboveRightSide { get { if (_AboveRightSide == GridLinePattern.Unknown) { if (r1 == 0) _AboveRightSide = GridLinePattern.None; else { CellRange cr = MyTable.MyFlexGrid.GetMergedRange(r1 - 1, c2); if (cr.c2 == c2) _AboveRightSide = MyBorders.VerticalLines[r1 - 1, c2 + 1]; else _AboveRightSide = GridLinePattern.None; } } return _AboveRightSide; } } private GridLinePattern _BelowLeftSide = GridLinePattern.Unknown; public GridLinePattern BelowLeftSide { get { if (_BelowLeftSide == GridLinePattern.Unknown) { if (r2 == MyTable.MyFlexGrid.Rows.Count - 1) _BelowLeftSide = GridLinePattern.None; else { CellRange cr = MyTable.MyFlexGrid.GetMergedRange(r2 + 1, c1); if (cr.c1 == c1) _BelowLeftSide = MyBorders.VerticalLines[r2 + 1, c1]; else _BelowLeftSide = GridLinePattern.None; } } return _BelowLeftSide; } } private GridLinePattern _AboveLeftSide = GridLinePattern.Unknown; public GridLinePattern AboveLeftSide { get { if (_AboveLeftSide == GridLinePattern.Unknown) { if (r1 == 0) _AboveLeftSide = GridLinePattern.None; else { // This property is only used if c1==0 or r1==0: // A merged range for col = 0 will always return an c1=0. CellRange cr = MyTable.MyFlexGrid.GetMergedRange(r1 - 1, c1); if (cr.c1 == c1) _AboveLeftSide = MyBorders.VerticalLines[r1 - 1, c1]; else // Since this section of code is only entered when c1==0, this else condition should never happen _AboveLeftSide = GridLinePattern.None; } } return _AboveLeftSide; } } #endregion #region Static Properties private static float XOffset => vlnTable.XOffset; private static float YOffset => vlnTable.YOffset; private static float LineThicknessForThin => vlnTable.LineThicknessForThin; private static float LineThicknessForThick => vlnTable.LineThicknessForThick; private static float LineThicknessForDouble => vlnTable.LineThicknessForDouble; private static float DoubleLineOffset => vlnTable.DoubleLineOffset; // adjustments for line intersections: private static float ThickOverDouble => ((2 * DoubleLineOffset) + LineThicknessForDouble - LineThicknessForThick) / 2; private static float ThickUnderDouble => DoubleLineOffset + (LineThicknessForThick - LineThicknessForDouble) / 2; private static float ThickOverThin => (LineThicknessForThin - LineThicknessForThick) / 2; private static float ThinOverThick => (LineThicknessForThick - LineThicknessForThin) / 2; private static float ThinUnderDouble => DoubleLineOffset + (LineThicknessForThin - LineThicknessForDouble) / 2; private static float ThinOverDouble => (DoubleLineOffset + LineThicknessForThin - LineThicknessForDouble) / 2; private static float DoubleUnderThin => (LineThicknessForThin - LineThicknessForDouble) / 2; private static float DoubleUnderThick => (LineThicknessForThick - LineThicknessForDouble) / 2; #endregion #region Constructors public vlnCell(int r1, int c1, int r2, int c2, vlnTable myTable, iTextSharp.text.Paragraph myPara, float hContent) { this.r1 = r1; this.c1 = c1; this.r2 = r2; this.c2 = c2; MyTable = myTable; MyPara = myPara; HContent = hContent; } #endregion #region Public Methods public override string ToString() => string.Format("{0}:{1}", r1, c1); public void PdfDebug(iTextSharp.text.pdf.ColumnText myColumnText, float x, float bottom, float right, float y, string debugText, float yDescent) { PdfContentByte cb = myColumnText.Canvas; PdfLayer debugLayer = !(cb.PdfWriter.PageEvent is VlnSvgPageHelper _MyPageHelper) ? null : _MyPageHelper.DebugLayer; if (debugLayer == null) return; System.Drawing.Color sysColor = PrintOverride.OverrideDebugColor(System.Drawing.Color.Gray); cb.SaveState(); cb.BeginLayer(debugLayer); cb.SetColorStroke(new Color(sysColor)); cb.SetLineWidth(.1F); cb.MoveTo(x, y - yDescent); cb.LineTo(right, y - yDescent); cb.LineTo(right, bottom - yDescent); cb.LineTo(x, bottom - yDescent); cb.LineTo(x, y - yDescent); for (float yy = y - MyPara.TotalLeading; yy > bottom; yy -= MyPara.TotalLeading) { cb.MoveTo(x, yy - yDescent); cb.LineTo(right, yy - yDescent); } cb.Stroke(); if (debugText != "") { ColumnText ct = new ColumnText(cb); ct.SetSimpleColumn(x, y - yDescent, right, y - yDescent - 50); iTextSharp.text.Font font = FontFactory.GetFont("Arial", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, 2); Chunk chk = new Chunk(debugText + string.Format(", Top = {0}", y), font); Phrase ph = new Phrase(chk); ct.AddElement(ph); cb.SetColorFill(new Color(sysColor)); ct.Go(); } cb.EndLayer(); cb.RestoreState(); } // B2021-137 Added doShading parameter // We call this twice. The first time to print the shading the second time to print the borders and text public void ToPdf(iTextSharp.text.pdf.ColumnText myColumnText, float left, float top, int topPlacementAdjust, bool doShading) { myColumnText.Canvas.SaveState(); VlnSvgPageHelper _MyPageHelper = myColumnText.Canvas.PdfWriter.PageEvent as VlnSvgPageHelper; float mult = _MyPageHelper.YMultiplier; float x = MyTable.ColLeft[c1]; float w = MyTable.ColLeft[c2 + 1] - x; float y = mult * MyTable.RowTop[r1]; // r1 - top row of cell or top of merged cells float h = mult * MyTable.RowTop[r2 + 1] - y; // r2 - top of last merged cell. r2+1 - top row of next cell, if merging // Phase 6 has no affect - YAdjust_h is set to 0. May have been used for testing/trying options. if (Rtf2Pdf.GetTableScrunchingStatus(TableScrunching.Phase6))// RHM20150525 - Table Scrunc { // get height of row, h = YAdjust_h + mult * MyTable.RowTop[r2 + 1] - y; } if (doShading) { // B2021-137 do the shading then jump out ShadingToPdf(myColumnText, left, top, x, w, y, h); // C2021-004 Table Cell Shading return; } BordersToPdf(myColumnText, left, top, x, w, y, h ,false); float hAdjust = VeritcalTextAlignment(h,_MyPageHelper.YMultiplier); iTextSharp.text.pdf.ColumnText myColumnText1 = new iTextSharp.text.pdf.ColumnText(myColumnText.Canvas); float adjustTextLocation = mult * 4; // RHM 20120925 Move text down about 1 half line from the border if (Rtf2Pdf.GetTableScrunchingStatus(TableScrunching.Phase7))// RHM20150525 - Table Scrunc { adjustTextLocation = mult * 0; if (mult != 1F) adjustTextLocation=1; // 1 Point: move down a slight bit if printing 7LPI } // B2019-093 missing table cell text when printing South Texas FSG-20 step 1 // Added two more to the last parameter of SetSimplColun (it's the ury value - upper y value) // this will have the cell text print a little more toward the top of the cell // B2019-109 Adjusted width of cell to match edit cell // B2020-034 - use the passed in topPlacementAdjust value which is set based on the font size. myColumnText1.SetSimpleColumn(.5F + left + x, top - y - h, left + x + w - 1.5F, topPlacementAdjust + top - y - hAdjust - adjustTextLocation); // 2 == Default Padding // B2018-003 - RHM 20180319 Change Debug Output string dbg = string.Format("Row={0}, Col={1}, Leading={2}, SpacingBefore={3}", r1, c1, MyPara.TotalLeading * _MyPageHelper.YMultiplier, MyPara.SpacingBefore); // B2018-033 VCS SAG-6 Steps 3 and 9 and SACRG1 Step 13 // Added Header to debug output if (ShowDetails) { if (FirstTime) Console.WriteLine("\"r1\"\t\"c1\"\t\"hAdjust\"\t\"adjustTextLocation\"\t\"y\"\t\"h\"\t\"top\"\t\"1 + top - y - hAdjust - adjustTextLocation\"\t\"" + "_MyPageHelper.YMultiplier\"\t\"Text\""); Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}\t{8}\t\"{9}\"" , r1, c1, hAdjust, adjustTextLocation, y, h, top, 1 + top - y - hAdjust - adjustTextLocation, _MyPageHelper.YMultiplier, ShowText(MyPara.Content.Substring(0, Math.Min(20, MyPara.Content.Length)))); } // C2018-004 create meta file for baseline compares Volian.Base.Library.BaselineMetaFile.WriteLine(" R {0} C {1} Y {2} H {3} Top {4} hAdj {5} TxtLocAdj {6}", r1, c1, y, h, 2+top, hAdjust,adjustTextLocation); foreach (Chunk chk1 in MyPara.Chunks) { string ctai = Rtf2Pdf.ChunkTextAttributesInfo(chk1); Volian.Base.Library.BaselineMetaFile.WriteLine("\t{0},{1},{2},{3},\"{4}\"", chk1.Font.Familyname, chk1.Font.Size, chk1.Font.Style, ctai, TextForBaseline.FixText(chk1.Content)); } // B2018-003 - Adjust Leading for 7LPI if necessary MyPara.SetLeading(MyPara.TotalLeading * _MyPageHelper.YMultiplier, 0); vlnCells.FixHyphens(MyPara, MyTable); vlnCells.FixBackslashes(MyPara, MyTable); myColumnText1.AddElement(MyPara); foreach (object obj in MyPara) { if(obj is iTextSharp.text.Image) { // B2022-028: Insert image into grid & print. Print was accounting for multiple rows in table where // inserted image was not in first row (mod setabsoluteposition to account for location in table). // Also, borders were no longer visible so draw those back in (added call to BordersToPdf) iTextSharp.text.Image img = obj as iTextSharp.text.Image; img.SetAbsolutePosition(left + x, top - y - h); img.ScaleAbsoluteWidth(w); img.ScaleAbsoluteHeight(h); myColumnText.Canvas.AddImage(img); BordersToPdf(myColumnText, left, top, x, w, y, h, true); } } float posBefore = myColumnText1.YLine; // RHM20150429 - Table Scrunch int cellStatus = myColumnText1.Go(); // B2019-109 Add to Error Log if cell too small if (cellStatus != 1) { _MyLog.WarnFormat("\r\n=-=-=-= Cell Width too narrow, text is wrapping ItemID={0} Location={1}, Row={2}, Col={3}, Status={4}",MyTable.ItemID, MyTable.Path, r1, c1, cellStatus); } float posAfter = myColumnText1.YLine; // RHM20150429 - Table Scrunch // B2018-033 Removed debug printout //if(ShowDetails)Console.WriteLine("ToPDF posBefore,posAfter,difference={0},{1},{2},{3}", // posBefore, posAfter, posBefore - posAfter,w); // B2018-003 Calculate hh float hh = posBefore - posAfter; PdfDebug(myColumnText1, 1 + left + x, top - y - hh - hAdjust, left + x + w - 2, 3 + top - y - hAdjust - adjustTextLocation, dbg, MyPara.TotalLeading / 5); myColumnText.Canvas.RestoreState(); } // B2018-033 VCS SAG-6 Steps 3 and 9 and SACRG1 Step 13 // Added Header to debug output private static bool _FirstTime = true; public static bool FirstTime { get { bool retval = _FirstTime; _FirstTime = false; return retval; } set { _FirstTime = value; } } // B2018-033 VCS SAG-6 Steps 3 and 9 and SACRG1 Step 13 // Added Header to debug output private string ShowText(string str) { StringBuilder sb = new StringBuilder(); foreach (char c in str) { if(c<32 || c>126) sb.Append(string.Format("[{0:X}]", (int) c)); else sb.Append(c); } return sb.ToString(); } public bool IsRangeStyleNull = false; private TextAlignEnum? _TextAlign = null; public TextAlignEnum? TextAlign { get { if (_TextAlign == null) { CellRange cr = MyTable.MyFlexGrid.GetCellRange(r1, c1, r2, c2); if (cr.Style == null) { IsRangeStyleNull = true; return _TextAlign; } else { IsRangeStyleNull = false; _TextAlign = cr.Style.TextAlign; } } return (TextAlignEnum)_TextAlign; } } #endregion #region Private Text Methods private float VeritcalTextAlignment(float h, float mult) { float hAdjust = 0; if (!IsRangeStyleNull) { switch (TextAlign) { case TextAlignEnum.CenterBottom: case TextAlignEnum.GeneralBottom: case TextAlignEnum.LeftBottom: case TextAlignEnum.RightBottom: hAdjust = Math.Max(0,h - (mult * HContent) )-1; //Console.WriteLine("\"Bottom\",{0},{1},{2}", h, HContent, hAdjust); break; case TextAlignEnum.CenterCenter: case TextAlignEnum.GeneralCenter: case TextAlignEnum.LeftCenter: case TextAlignEnum.RightCenter: hAdjust = Math.Max(0,(h - (mult * HContent)) / 2)-1; //Console.WriteLine("\"Middle\",{0},{1},{2}", h, HContent, hAdjust); break; default: break; } } return hAdjust; } #endregion #region Private Border Methods // coordinate system is upside down of screen. 0,0 on screen is top/left. 0,0 on pdf is bottom/left. // i.e. x is same, y is flipped. private void BordersToPdf(iTextSharp.text.pdf.ColumnText myColumnText, float left, float top, float x, float w, float y, float h, bool doTop) { PdfContentByte cb = myColumnText.Canvas; cb.SaveState();// Save state before drawing borders //SetOpacity(cb, .7F); // This line makes the borders transparent for testing float xLeft = left + x + XOffset; float yTop = top - y + YOffset; float xRight = left + x + w + XOffset; float yBottom = top - y - h + YOffset; // ZoomToCell(myColumnText, xLeft, yTop, xRight, yBottom); // Draw Top Side for the first row // B2022-028: Insert image into grid & print. The top/left was no longer visible if an image is in a grid cell, redraw it if (r1 == 0 || doTop) DrawHorizontalLine(cb, "Top", xLeft, xRight, yTop, TopSide, LeftSide, AboveLeftSide, LeftOfTopSide, AboveRightSide, RightSide, RightOfTopSide); // Draw Left Side for the first column if (c1 == 0 || doTop) DrawLineVertical(cb, "Left", xLeft, yBottom, yTop, LeftSide, BottomSide, LeftOfBottomSide, BelowLeftSide, LeftOfTopSide, TopSide, AboveLeftSide); // Draw Bottom Side DrawHorizontalLine(cb, "Bottom", xLeft, xRight, yBottom, BottomSide, BelowLeftSide, LeftSide, LeftOfBottomSide, RightSide, BelowRightSide, RightOfBottomSide); // Draw Right Side DrawLineVertical(cb, "Right", xRight, yBottom, yTop, RightSide, RightOfBottomSide, BottomSide, BelowRightSide, TopSide, RightOfTopSide, AboveRightSide); cb.RestoreState(); } // C2021-004 Set the cell background shading color private void ShadingToPdf(iTextSharp.text.pdf.ColumnText myColumnText, float left, float top, float x, float w, float y, float h) { PdfContentByte cb = myColumnText.Canvas; cb.SaveState();// Save state before shading float xLeft = left + x + XOffset; float yTop = top - y + YOffset; float xRight = left + x + w + XOffset; float yBottom = top - y - h + YOffset; //Console.WriteLine("Color {0}", MyShading.TableShadingInfo[r1, c1]); FillRectangle(cb, xLeft, xRight, yTop, yBottom, new Color(MyShading.GetColor(r1, c1)), new Color(MyShading.GetColor(r1, c1))); cb.RestoreState(); } // PIP TESTING SHADING CELLS private void FillRectangle(PdfContentByte cb, float xLeft, float xRight, float yTop, float yBottom, Color FillColor, Color OutlineColor) { cb.SaveState(); cb.SetColorFill(FillColor); cb.SetLineWidth(.5f); cb.SetColorStroke(OutlineColor); cb.RoundRectangle(xLeft, yBottom, xRight-xLeft, yTop-yBottom, .005F); cb.Fill(); cb.RestoreState(); } private void DrawHorizontalLine(PdfContentByte cb, string side, float xStart, float xEnd, float y, GridLinePattern LinePattern, GridLinePattern startToLeft, GridLinePattern startToRight, GridLinePattern startAhead, GridLinePattern endToLeft, GridLinePattern endToRight, GridLinePattern endAhead) { if (LinePattern != GridLinePattern.None) { float direction = (xStart < xEnd) ? 1 : -1; InitializeLineStyle(cb); SetLineColor(cb, r1, c1, side); if (LinePattern == GridLinePattern.Double) { // Top Line float dStart = -direction * DoubleIntersectionRight(startToLeft, startToRight, startAhead); float dEnd = direction * DoubleIntersectionLeft(endToLeft, endToRight, endAhead); DrawLine(cb, LineThicknessForDouble, xStart + dStart, y + DoubleLineOffset, xEnd + dEnd, y + DoubleLineOffset); // Bottom Line dStart = -direction * DoubleIntersectionLeft(startToLeft, startToRight, startAhead); dEnd = direction * DoubleIntersectionRight(endToLeft, endToRight, endAhead); DrawLine(cb, LineThicknessForDouble, xStart + dStart, y - DoubleLineOffset, xEnd + dEnd, y - DoubleLineOffset); } else if (LinePattern != GridLinePattern.Thick) { SetLinePattern(cb, LinePattern); float dStart = -direction * ThinIntersection(startToLeft, startToRight, startAhead); float dEnd = direction * ThinIntersection(endToLeft, endToRight, endAhead); DrawLine(cb, LineThicknessForThin, xStart + dStart, y, xEnd + dEnd, y); } else { float dStart = -direction * ThickIntersection(startToLeft, startToRight, startAhead); float dEnd = direction * ThickIntersection(endToLeft, endToRight, endAhead); DrawLine(cb, LineThicknessForThick, xStart + dStart, y, xEnd + dEnd, y); } cb.Stroke(); } } private void DrawLineVertical(PdfContentByte cb, string side, float x, float yStart, float yEnd, GridLinePattern LinePattern, GridLinePattern startToLeft, GridLinePattern startToRight, GridLinePattern startAhead, GridLinePattern endToLeft, GridLinePattern endToRight, GridLinePattern endAhead) { if (LinePattern != GridLinePattern.None) { float direction = (yStart < yEnd) ? 1 : -1; InitializeLineStyle(cb); SetLineColor(cb, r1, c1, side); if (LinePattern == GridLinePattern.Double) { //Left Line float dStart = -direction * DoubleIntersectionRight(startToLeft, startToRight, startAhead); float dEnd = direction * DoubleIntersectionLeft(endToLeft, endToRight, endAhead); DrawLine(cb, LineThicknessForDouble, x - DoubleLineOffset, yStart + dStart, x - DoubleLineOffset, yEnd + dEnd); //Right Line dStart = -direction * DoubleIntersectionLeft(startToLeft, startToRight, startAhead); dEnd = direction * DoubleIntersectionRight(endToLeft, endToRight, endAhead); DrawLine(cb, LineThicknessForDouble, x + DoubleLineOffset, yStart + dStart, x + DoubleLineOffset, yEnd + dEnd); } else if (LinePattern != GridLinePattern.Thick) { SetLinePattern(cb, LinePattern); float dStart = -direction * ThinIntersection(startToLeft, startToRight, startAhead); float dEnd = direction * ThinIntersection(endToLeft, endToRight, endAhead); DrawLine(cb, LineThicknessForThin, x, yStart + dStart, x, yEnd + dEnd); } else { float dStart = -direction * ThickIntersection(startToLeft, startToRight, startAhead); float dEnd = direction * ThickIntersection(endToLeft, endToRight, endAhead); DrawLine(cb, LineThicknessForThick, x, yStart + dStart, x, yEnd + dEnd); } cb.Stroke(); } } #endregion #region Intersection Diagram /* driving into an intersection on line being drawn, facing the intersection. Each intersection requires * drawing of each of the four cases: Case 1: Line being drawn right: lineToLeft ^ | Line Being Drawn ----->|---> LineAhead | v lineToRight Case 2: Line being drawn left: lineToRight ^ | LineAhead <-----|<--- Line Being Drawn | v lineToLeft Case 3: Line being drawn up: lineAhead ^ | lineToLeft <-----|----> lineToRight ^ | | Line Being Drawn Case 4: Line being drawn down: Line Begin Drawn | | v lineToRight <-----|----> lineToLeft | | v lineAhead * */ #endregion #region Border Intersection Methods private static float DoubleIntersectionLeft(GridLinePattern lineToLeft, GridLinePattern lineToRight, GridLinePattern lineAhead) { if (lineToLeft == GridLinePattern.Double) return -DoubleLineOffset; if (lineToRight == GridLinePattern.Double || lineAhead == GridLinePattern.Double) return DoubleLineOffset; if (lineToLeft == GridLinePattern.Thick || lineToRight == GridLinePattern.Thick) return DoubleUnderThick; if (VlnBorders.LineWidth(lineToLeft) == 1 || VlnBorders.LineWidth(lineToRight) == 1) return DoubleUnderThin; return 0; } private static float DoubleIntersectionRight(GridLinePattern lineToLeft, GridLinePattern lineToRight, GridLinePattern lineAhead) { if (lineToRight == GridLinePattern.Double) return -DoubleLineOffset; if (lineToLeft == GridLinePattern.Double || lineAhead == GridLinePattern.Double) return DoubleLineOffset; if (lineToLeft == GridLinePattern.Thick || lineToRight == GridLinePattern.Thick) return DoubleUnderThick; if (VlnBorders.LineWidth(lineToLeft) == 1 || VlnBorders.LineWidth(lineToRight) == 1) return DoubleUnderThin; return 0; } private static float ThinIntersection(GridLinePattern lineToLeft, GridLinePattern lineToRight, GridLinePattern lineAhead) { if (lineToLeft == GridLinePattern.Double && lineToRight == GridLinePattern.Double) return -ThinUnderDouble; if (lineToLeft == GridLinePattern.Double || lineToRight == GridLinePattern.Double) { if (lineAhead == GridLinePattern.Double) return -ThinUnderDouble; return ThinOverDouble; } if (lineToLeft == GridLinePattern.Thick || lineToRight == GridLinePattern.Thick) return ThinOverThick; return 0; } private static float ThickIntersection(GridLinePattern lineToLeft, GridLinePattern lineToRight, GridLinePattern lineAhead) { if (lineToLeft == GridLinePattern.Double && lineToRight == GridLinePattern.Double) { if (lineAhead == GridLinePattern.Thick) return ThickOverDouble; return -ThickUnderDouble; } if ( lineToLeft == GridLinePattern.Double || lineToRight == GridLinePattern.Double) { if (lineAhead == GridLinePattern.Double) { if (lineToLeft == GridLinePattern.Thick || lineToRight == GridLinePattern.Thick) return ThickOverDouble; return -ThickUnderDouble; } return ThickOverDouble; } if (VlnBorders.LineWidth(lineToLeft) == 1 && VlnBorders.LineWidth(lineToRight) == 1) return ThickOverThin; return 0; } #endregion #region Basic Border Methods private static void DrawLine(PdfContentByte cb, float lineThickness, float xStart, float yStart, float xEnd, float yEnd) { cb.SetLineWidth(lineThickness); cb.MoveTo(xStart, yStart); cb.LineTo(xEnd, yEnd); } private static void SetLinePattern(PdfContentByte cb, GridLinePattern linePattern) { if (linePattern == GridLinePattern.Dotted) { cb.SetLineDash(0, 2, 0); cb.SetLineCap(PdfContentByte.LINE_CAP_ROUND); } else if (linePattern == GridLinePattern.Dashed) cb.SetLineDash(4, 4, 0); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Keeping row and col for Debugging")] private static void SetLineColor(PdfContentByte cb, int row, int col, string side) { iTextSharp.text.Color lineColor = new iTextSharp.text.Color(PrintOverride.OverrideTextColor(System.Drawing.Color.Black)); cb.SetColorStroke(lineColor); } private static void InitializeLineStyle(PdfContentByte cb) { cb.SetLineCap(PdfContentByte.LINE_CAP_PROJECTING_SQUARE); // Extend the line by half of it's thickness cb.SetLineDash(0);// Solid Line } #endregion #region Debug Methods private static void SetOpacity(PdfContentByte cb, float opacity) { PdfGState gs = new PdfGState { StrokeOpacity = opacity }; cb.SetGState(gs); } private static void ZoomToCell(iTextSharp.text.pdf.ColumnText myColumnText, float xLeft, float yTop, float xRight, float yBottom) { PdfDestination dest = new PdfDestination(PdfDestination.FITR, xLeft - 4, yBottom , xRight, yTop + 4); myColumnText.Canvas.SetAction(PdfAction.GotoLocalPage(myColumnText.Canvas.PdfWriter.CurrentPageNumber, dest, myColumnText.Canvas.PdfWriter), xLeft, yBottom, xRight, yTop); } #endregion } }