using System; using System.Collections.Generic; using System.Text; using iTextSharp.text; using iTextSharp.text.pdf; using iTextSharp.text.factories; using Volian.Svg.Library; using System.Text.RegularExpressions; using System.Xml; using VEPROMS.CSLA.Library; namespace Volian.Print.Library { public class VlnSvgPageHelper:SvgPageHelper { private static readonly log4net.ILog _MyLog = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); // This handles Page Breaks within a Step private List _ParaBreaks = new List(); public List ParaBreaks { get { return _ParaBreaks; } set { _ParaBreaks = value; } } private PageBookmarks _PageBookmarks = new PageBookmarks(); public PageBookmarks PageBookmarks { get { return _PageBookmarks; } set { _PageBookmarks = value; } } private PdfOutline _MyPdfOutline = null; public PdfOutline MyPdfOutline { get { return _MyPdfOutline; } set { _MyPdfOutline = value; } } private vlnText _TopMessage; public vlnText TopMessage { get { return _TopMessage; } set { _TopMessage = value; } } private vlnText _BottomMessage; public vlnText BottomMessage { get { return _BottomMessage; } set { _BottomMessage = value; } } Dictionary _MyParagraphs = new Dictionary(); public Dictionary MyParagraphs { get { return _MyParagraphs; } set { _MyParagraphs = value; } } float _YMultiplier = 1; public float YMultiplier { get { return _YMultiplier; } set { _YMultiplier = value; } } private PdfWriter _MyPdfWriter; public PdfWriter MyPdfWriter { get { return _MyPdfWriter; } set { MyPageCounts = new PageCounts(); MyTOCPageCounts = new PageCounts(); _MyPdfWriter = value; } } private PdfContentByte _MyPdfContentByte; public PdfContentByte MyPdfContentByte { get { return _MyPdfContentByte; } set { _MyPdfContentByte = value; } } private int _FinalMessageSectionID; public int FinalMessageSectionID { get { return _FinalMessageSectionID; } set { _FinalMessageSectionID = value; } } public override void OnCloseDocument(PdfWriter writer, iTextSharp.text.Document document) { AddBookmarks(writer); MyPageCounts.DrawTemplates(); } public override void OnEndPage(PdfWriter writer, iTextSharp.text.Document document) { if (!OnFoldoutPage) { AddBookmarks(writer); MyPageCounts.CanIncrement = true; base.OnEndPage(writer, document); DrawChangeBars(writer.DirectContent); DrawMessages(writer.DirectContent); if (!CreatingFoldoutPage) DrawRuler(writer.DirectContent); if ((MySection.MyDocStyle.StructureStyle.Style & E_DocStructStyle.DontCountInTabOfCont) == 0) CurrentTOCPageNumber++; } else { MyPageCounts.CanIncrement = true; base.OnEndPage(writer, document); if (!CreatingFoldoutPage) DrawRuler(writer.DirectContent); if ((MySection.MyDocStyle.StructureStyle.Style & E_DocStructStyle.DontCountInTabOfCont) == 0) CurrentTOCPageNumber++; } PageListTopCheckOffHeader = null; PageListLastCheckOffHeader = null; YMultiplier = 1; } private void DrawRuler(PdfContentByte cb) { if (DebugLayer == null) return; cb.SaveState(); cb.BeginLayer(DebugLayer); float x = (cb.PdfWriter.PageSize.Left + cb.PdfWriter.PageSize.Right) / 2; cb.SetLineWidth(.1F); cb.SetColorStroke(new Color(System.Drawing.Color.CornflowerBlue)); float yTop = 648; float yBottom = 48; cb.MoveTo(x, yTop); cb.LineTo(x, yBottom); int i = 0; for (float y = yTop; y >= yBottom; y -= 12) { float w = 10; if (i % 10 == 0) w = 30; else if (i % 5 == 0) w = 20; i++; cb.MoveTo(x - w, y); cb.LineTo(x, y); } i = 0; for (float y = yTop; y >= yBottom; y -= 10.1F) { float w = 10; if (i % 10 == 0) w = 30; else if (i % 5 == 0) w = 20; i++; cb.MoveTo(x + w, y-1); cb.LineTo(x, y-1); } Layout layout = MySection.MyDocStyle.Layout; cb.Rectangle((float)layout.LeftMargin, (float)(cb.PdfWriter.PageSize.Height - layout.TopMargin), (float)layout.PageWidth - (float)layout.LeftMargin, (float)-layout.PageLength); float yRuler = 612; cb.MoveTo(0, yRuler); cb.LineTo(612, yRuler); for (float x1 = 0; x1 < 612; x1 += 6) // tic marks every 1/12 inch { if (x1 % 72 == 0) { cb.MoveTo(x1, yRuler-20); cb.LineTo(x1, yRuler+20); } else if (x1 % 36 == 0) { cb.MoveTo(x1, yRuler-10); cb.LineTo(x1, yRuler+10); } else { cb.MoveTo(x1, yRuler-5); cb.LineTo(x1, yRuler); } } for (float x1 = 0; x1 < 612; x1 += 7.2f) // tic marks every 1/10 inch { if (x1 % 72 == 0) { cb.MoveTo(x1, yRuler); cb.LineTo(x1, yRuler + 20); } else if (x1 % 36 == 0) { cb.MoveTo(x1, yRuler); cb.LineTo(x1, yRuler + 10); } else { cb.MoveTo(x1, yRuler); cb.LineTo(x1, yRuler + 5); } } //cb.MoveTo(122.4F, 0); //WCN2 HLS //cb.LineTo(122.4F, cb.PdfWriter.PageSize.Height); //cb.MoveTo(78, 0); // HLP HLS //cb.LineTo(78, cb.PdfWriter.PageSize.Height); //cb.MoveTo(441, 0); // HLP cover page date //cb.LineTo(441, cb.PdfWriter.PageSize.Height); //cb.MoveTo(71.4F, 0); // WCN Purpose page, Revision: {REV} //cb.LineTo(71.4F, cb.PdfWriter.PageSize.Height); cb.Stroke(); cb.EndLayer(); cb.RestoreState(); } private void AddBookmarks(PdfWriter writer) { foreach (PageBookmark pb in PageBookmarks) { if (!pb.MyItemInfo.IsSection && MyPdfOutline != null) new PdfOutline(MyPdfOutline, pb.PdfDestination, pb.Title, false); else { PdfDestination dest = new PdfDestination(PdfDestination.FIT); MyPdfOutline = new PdfOutline(writer.DirectContent.RootOutline, dest, pb.Title, false); } } PageBookmarks.Clear(); } private void DrawMessages(PdfContentByte cb) { if (TopMessage != null) { TopMessage.ToPdf(cb, 0, 0, 0); TopMessage = null; // Only output it once. } if (BottomMessage != null) { BottomMessage.ToPdf(cb, 0, 0, 0); BottomMessage = null; // Only output it once. } } private void DrawChangeBars(PdfContentByte cb) { foreach (vlnChangeBar vcb in MyChangeBars) { // TODO: Pass in zeroes because topdf is inherited. vcb.ToPdf(cb, 0, 0, 0); } _MyChangeBars = new List(); } #region SectionLevelData private ChangeBarDefinition _ChangeBarDefinition; public ChangeBarDefinition ChangeBarDefinition { get { return _ChangeBarDefinition; } set { _ChangeBarDefinition = value; } } #endregion private PdfLayer _TextLayer; public PdfLayer TextLayer { get { return _TextLayer; } set { _TextLayer = value; } } private PdfLayer _DebugLayer; public PdfLayer DebugLayer { get { return _DebugLayer; } set { _DebugLayer = value; } } private VEPROMS.CSLA.Library.SectionInfo _MySection; public VEPROMS.CSLA.Library.SectionInfo MySection { get { return _MySection; } set { _MySection = value; MySectionTitle = ((_MySection.DisplayNumber ?? "")=="" ? "" : _MySection.DisplayNumber + " - ") + _MySection.DisplayText; Volian.Svg.Library.Svg sectSvg = BuildSvg(_MySection); if (sectSvg != null) MySvg = sectSvg; } } private string _MySectionTitle; public string MySectionTitle { get { return _MySectionTitle; } set { _MySectionTitle = value; } } private int _MaxRNO; public int MaxRNO { get { if (_MaxRNO == 0) _MaxRNO = _MySection.ColumnMode; return _MaxRNO; } } private int _CurrentPageOf = 0; public int CurrentPageOf { get { return _CurrentPageOf; } set { _CurrentPageOf = value; } } private string _Rev = string.Empty; public string Rev { get { return _Rev; } set { _Rev = value; } } private string _RevDate = string.Empty; public string RevDate { get { return _RevDate; } set { _RevDate = value; } } private List _MyChangeBars = new List(); public List MyChangeBars { get { return _MyChangeBars; } set { _MyChangeBars = value; } } private bool _OriginalPageBreak; // use 16bit page breaks. public bool OriginalPageBreak { get { return _OriginalPageBreak; } set { _OriginalPageBreak = value; } } public void AddChangeBar(vlnChangeBar vcb, string cbmess) { if (vcb == null) return; vlnChangeBar prevCB = null; foreach (vlnChangeBar cb in MyChangeBars) { // only look at changebars in same column if (cb.XOffset == vcb.XOffset) { // if the two change bars end at same location, set prevCB which will // adjust the length (top position) of the change bar. This condition // also flags that the change bar texts overlap. if (cb.YChangeBarBottom == vcb.YChangeBarBottom) prevCB = cb; // continuous case, if changes bars overlap, adjust size and don't add new one to // the MyChangeBars list. if (MySection.ActiveFormat.PlantFormat.FormatData.ProcData.ChangeBarData.ContinuousChangeBars) { if (OverlapOrSpan(cb, vcb)) { if (prevCB == null) prevCB = cb; else if (prevCB.YChangeBarBottom > cb.YChangeBarBottom) prevCB = cb; } } } } // if this and the prevCB overlap, adjust yoffset/height/ybottom in the prevCB so that both change bars are // included in this Dictionary item. A new vlnChangeBar is not added to the MyChangeBars list, the current // one that overlaps with the new is adjusted. if (prevCB != null) { // Bottom for both change bars is same, adjust top (YOffset) and height. if (prevCB.YChangeBarBottom == vcb.YChangeBarBottom) { prevCB.YOffset = Math.Max(prevCB.YOffset, vcb.YOffset); float yChangeBarBottomExtend = prevCB.YChangeBarBottomExtend; prevCB.Height = Math.Max(prevCB.Height, vcb.Height); prevCB.YExtendLine = prevCB.YChangeBarBottom - Math.Min(yChangeBarBottomExtend, vcb.YChangeBarBottomExtend); //Math.Max(prevCB.YExtendLine, vcb.YExtendLine); // if a change bar message at this ychangebarbottom exists, see which message is used if (cbmess != null && prevCB.Messages.ContainsKey(prevCB.YChangeBarBottom)) UpdateCbMessage(cbmess, prevCB, vcb.XOffset); return; } // Bottom locations are not the same, but another change bar is not added, i.e. the // length of the line is adjusted. prevCB.YOffset = Math.Max(prevCB.YOffset, vcb.YOffset); float yChangeBarBottomExtend1 = prevCB.YChangeBarBottomExtend; prevCB.YChangeBarBottom = Math.Min(prevCB.YChangeBarBottom, vcb.YChangeBarBottom); prevCB.YExtendLine = prevCB.YChangeBarBottom - Math.Min(yChangeBarBottomExtend1, vcb.YChangeBarBottomExtend); //Math.Max(prevCB.YExtendLine, vcb.YExtendLine); // Two messages at this location, determine which one to use if (cbmess != null && prevCB.Messages.ContainsKey(prevCB.YChangeBarBottom)) UpdateCbMessage(cbmess, prevCB, vcb.XOffset); // no message at this location, just add it. else if (cbmess != null) prevCB.Messages.Add(vcb.YChangeBarBottom, new vlnChangeBarMessage(vcb.MyParent.XOffset, cbmess)); return; } // There was no overlap, add a message if it exists, and then add the change bar to the MyChangeBars list. if (cbmess != null) vcb.Messages.Add(vcb.YChangeBarBottom, new vlnChangeBarMessage(vcb.MyParent.XOffset, cbmess)); MyChangeBars.Add(vcb); } private static void UpdateCbMessage(string cbmess, vlnChangeBar prevCB, float xoff) { // If there are two messages at the same location, for example an AER change bar & and RNO // change bar, and the messages have different text, see which change bar message should be // added, i.e. use the one in 'rno', the greatest x value. vlnChangeBarMessage tmp = prevCB.Messages[prevCB.YChangeBarBottom]; if (cbmess != tmp.Message) { if (xoff > tmp.ParentX) { prevCB.Messages.Remove(prevCB.YChangeBarBottom); prevCB.Messages.Add(prevCB.YChangeBarBottom, new vlnChangeBarMessage(xoff, cbmess)); } } } private bool OverlapOrSpan(vlnChangeBar cb, vlnChangeBar vcb) { if (cb.MyParent.MyItemInfo.MyHLS.ItemID != vcb.MyParent.MyItemInfo.MyHLS.ItemID) return false; // cb.Yoffset is within range of vcb: if (cb.YOffset <= vcb.YOffset && cb.YOffset >= (vcb.YChangeBarBottomExtend - vlnPrintObject.SixLinesPerInch)) return true; // cb.YChangeBarBottom is within range of vcb: if ((cb.YChangeBarBottomExtend-vlnPrintObject.SixLinesPerInch) <= vcb.YOffset && cb.YChangeBarBottomExtend >= (vcb.YChangeBarBottomExtend - vlnPrintObject.SixLinesPerInch)) return true; // vcb.Yoffset is within range of cb: if (vcb.YOffset <= cb.YOffset && vcb.YOffset >= (cb.YChangeBarBottomExtend - vlnPrintObject.SixLinesPerInch)) return true; // vcb.YChangeBarBottom is within range of cb: if ((vcb.YChangeBarBottomExtend - vlnPrintObject.SixLinesPerInch) <= cb.YOffset && vcb.YChangeBarBottomExtend >= (cb.YChangeBarBottomExtend - vlnPrintObject.SixLinesPerInch)) return true; return false; } private PromsPrinter _MyPromsPrinter; public PromsPrinter MyPromsPrinter { get { return _MyPromsPrinter; } set { _MyPromsPrinter = value; } } public VlnSvgPageHelper(VEPROMS.CSLA.Library.SectionInfo mySection,PromsPrinter myPromsPrinter) : base() { MySection = mySection; MyPromsPrinter = myPromsPrinter; } private Volian.Svg.Library.Svg BuildSvg(VEPROMS.CSLA.Library.SectionInfo mySection) { VEPROMS.CSLA.Library.FormatInfo activeFormat = mySection.ActiveFormat; VEPROMS.CSLA.Library.DocStyle docStyle = mySection.MyDocStyle; Volian.Svg.Library.Svg mySvg = null; mySvg = SvgSerializer.StringDeserialize(BuildMyText(activeFormat)); mySvg.ViewBox.Height = 1100; mySvg.ViewBox.Width = 850; mySvg.Height = new SvgMeasurement(11, E_MeasurementUnits.IN); mySvg.Width = new SvgMeasurement(8.5F, E_MeasurementUnits.IN); mySvg.LeftMargin = (float)docStyle.Layout.LeftMargin; mySvg.TopMargin = 9.6F; VEPROMS.CSLA.Library.PageStyle pageStyle = docStyle.pagestyle; AddPageListItems(mySvg, pageStyle, mySection); mySvg.ProcessText += new SvgProcessTextEvent(mySvg_ProcessText); // if this section had a previous section and this is continuous, don't generate the svg for // it, we'll use the previous section. The only thing we need to do is to process through the // pagelist items in case there are some section items, for example the checkoff header needs // to be processed for each section whether or not it is continuous. SectionConfig.SectionPagination sPag = SectionConfig.SectionPagination.Separate; if (mySection.IsStepSection && mySection.MyPrevious != null && mySection.MyPrevious.IsStepSection) { SectionConfig sc = mySection.MyConfig as SectionConfig; sPag = sc.Section_Pagination; } if (sPag == SectionConfig.SectionPagination.Continuous) return null; return mySvg; } public PageCounts MyPageCounts = null; public PageCounts MyTOCPageCounts = null; private static Regex regexFindToken = new Regex("{[^{}]*}"); private string mySvg_ProcessText(object sender, SvgProcessTextArgs args) { if (args.MyText == null) return string.Empty; // Needed for empty genmac text (was space in 16bit files) if (!args.MyText.Contains("{")) return args.MyText; /* * Check if tokens for handling page counts ({PAGE} {OF} etc). if so, do templates. */ if (args.MyText.Contains("{DOCCURPAGE}") || args.MyText.Contains("{DOCTOTPAGE}")) { string key = "DocCurPage." + MySection.ItemID; string txt = args.MyText.Replace("{DOCCURPAGE}", "{PAGE}").Replace("{DOCTOTPAGE}", "{OF}"); MyPdfContentByte.AddTemplate(MyPageCounts.AddToTemplateList(key, MyPdfWriter, txt, args.MySvgText.Font, args.MySvgText.Align, args.MySvgText.FillColor), args.MySvgScale.X(args.MySvgText.X), args.MySvgScale.Y(MyPdfContentByte, args.MySvgText.Y)); return string.Empty; } string tocKey = "TOC" + MySection.ItemID.ToString(); if (args.MyText.Contains("{PAGE}") || args.MyText.Contains("{OF}")) { #region numberingseq /* public enum E_NumberingSequence : uint { NoPageNum = 0, WithSteps = 1, Add to original steps section page count. WithinEachDocStyle = 2, WithinEachSection = 3, WithinEachDocStyle1 = 4, GroupedByPagination = 5, GroupedByLevel = 6, WithStepsAndSecondaryPageNumber = 7, WithStepsAndSecondaryPageNumberGroupedByLevel = 8, Like6_ButDoesntNeedSubsection = 9, Like6_ButDoesntNeedSubsection1 = 10 }; */ #endregion // default for the key is to make it NumberingSequence. Then handle // specific cases. string key = ((int)MySection.MyDocStyle.NumberingSequence).ToString(); E_NumberingSequence numseq = MySection.MyDocStyle.NumberingSequence??0; switch (numseq) { case E_NumberingSequence.WithSteps: // Number with 'originalstepsection' ProcedureInfo pc = MySection.MyProcedure; int sectid = 0; foreach (SectionInfo si in pc.Sections) { SectionConfig sc = si.MyConfig as SectionConfig; if (sc.Section_OriginalSteps == "Y") { sectid = si.ItemID; break; } } if (sectid != 0) key = (int)E_NumberingSequence.WithinEachSection + "." + sectid; break; case E_NumberingSequence.WithinEachDocStyle: key = key + "." + MySection.ActiveFormat.FormatID + "." + MySection.MyDocStyle.Index; break; case E_NumberingSequence.WithinEachSection: key = key + "." + MySection.ItemID; break; } PdfTemplate tmp = MyPageCounts.AddToTemplateList(key, MyPdfWriter, args.MyText, args.MySvgText.Font, args.MySvgText.Align, args.MySvgText.FillColor); // Proms page numbering designed requires a "{PAGE}" token to increment the page counter. So the easiest way // to do that was to add a "{PAGE}" token to every page that is flagged as not to be printed. if (!args.MyText.StartsWith("Non-printing ")) MyPdfContentByte.AddTemplate(tmp, args.MySvgScale.X(args.MySvgText.X), args.MySvgScale.Y(MyPdfContentByte, args.MySvgText.Y)); return string.Empty; } if (args.MyText.Contains("{FINALPAGE}")) { if (MySection.ItemID == FinalMessageSectionID) { string key = "FinalPage"; MyPdfContentByte.AddTemplate(MyPageCounts.AddToTemplateList(key, MyPdfWriter, args.MyText, MySection.MyDocStyle.Final.Message, args.MySvgText.Font, args.MySvgText.Align, args.MySvgText.FillColor), args.MySvgScale.X(args.MySvgText.X), args.MySvgScale.Y(MyPdfContentByte, args.MySvgText.Y)); } return string.Empty; } return regexFindToken.Replace(args.MyText, new MatchEvaluator(ReplacePageListToken)); } private string BuildMyText(VEPROMS.CSLA.Library.FormatInfo activeFormat) { string sGenMac = activeFormat.GenMac; if (sGenMac == null || sGenMac == "") { // If subformat and does not have its own genmac, find an inherited genmac. FormatInfo tmpf = FormatInfo.Get(activeFormat.ParentID); while (tmpf.FormatID!=1 && (sGenMac==null||sGenMac=="")) { sGenMac = tmpf.GenMac; tmpf = FormatInfo.Get(tmpf.ParentID); } if (sGenMac == null || sGenMac == "") sGenMac = "";// return ""; } if (!sGenMac.Contains("xmlns")) sGenMac = sGenMac.Replace(".StringSerialize(mySvg); XmlDocument xDocPrintout = new XmlDocument(); xDocPrintout.LoadXml(str); XmlNode xn = xDocPrintout.DocumentElement.ChildNodes[0]; xn.AppendChild(xDocPrintout.ImportNode(xDocGenMac.DocumentElement, true)); return xDocPrintout.OuterXml; } private string FirstAndLast(string token) { // strip the curly braces and return the first and last character // For example Header1 becomes H1 and Box2 becomes B2 return token.Substring(1, 1) + token.Substring(token.Length - 2, 1); } private static Regex regexJustTokens = new Regex(@"^{([^{}]*}{)*[^{}]*}$"); // The following variables are used to keep track of whether a check off header should be printed // if the {CHKOFFHEADING} pagelist item is found. PageListTopCheckOffHeader is the text that // should be printed at the top of the page. PageListLastCheckOffHeader is the last header that was // printed (if a header is on the page, and another is to be printed, it doesn't get printed if it // is the same as the previous) and PageListCheckOffHeader is the page list item converted to a // SvgText - this stores X/Y location, etc. public string PageListTopCheckOffHeader = null; public string PageListLastCheckOffHeader = null; public SvgText PageListCheckOffHeader = null; private void AddPageListItems(Volian.Svg.Library.Svg mySvg, VEPROMS.CSLA.Library.PageStyle pageStyle, VEPROMS.CSLA.Library.SectionInfo section) { PageListCheckOffHeader = null; // If this is a continuous section, then the only pagelist items we want are section type since // we are still on the same page. - CHECK IF ON SAME PAGE!!!! SectionConfig.SectionPagination sPag = SectionConfig.SectionPagination.Separate; if (section.IsStepSection && section.MyPrevious != null && section.MyPrevious.IsStepSection) { SectionConfig sc = section.MyConfig as SectionConfig; sPag = sc.Section_Pagination; } SvgGroup svgGroup = new SvgGroup(); //int defPtPerRow = 72 / 6; //int curLPI = 6; // default //int prevLPI = 6; //float PrevRow = 0; //float rowAdj = 0; // = 18; foreach (VEPROMS.CSLA.Library.PageItem pageItem in pageStyle.PageItems) { VE_Font useFontForCheckOffHeader = null; if (sPag == SectionConfig.SectionPagination.Separate || ((sPag == SectionConfig.SectionPagination.Continuous || sPag ==0 )&& pageItem.Row < 0)) { //if (PrevRow > 0) //{ // //float pgRow = (float)(pageItem.Row + rowAdj); // if (curLPI != prevLPI) // { // int prow = (int)(PrevRow / defPtPerRow); // int nrows = ((int)(pageItem.Row / defPtPerRow)) - prow; // rowAdj += (float)Math.Abs(((nrows * (72 / curLPI)) - ((nrows * defPtPerRow) + rowAdj))); // //rowAdj += (float)(((pgRow - PrevRow) / (72 * prevLPI)) * (72 / curLPI)); // prevLPI = curLPI; // } //} MatchCollection matches = regexFindToken.Matches(pageItem.Token); if (matches.Count > 0) { string plstr = ""; // When a pagelist line (row) has more than one token that is resolved to text, each resolved token text was place on top // of each other. Use a temporary string (plstr) to process the pagelist tokens for each pagelist line (row) before adding // it to the svgGroup. plstr = pageItem.Token; foreach (Match match in matches) { string token = match.Value; //token = Regex.Replace(token, @"[\xB3-\xDF]", " "); switch (match.Value) { case "{!atom}": // Add an Atom Figure to the SVG plstr = plstr.Replace(token, ""); AddImage(svgGroup, 160.5f, 170.5f, 288f, 323f, "atom.bmp"); break; case "{!cpllogo}": plstr = plstr.Replace(token, ""); AddImage(svgGroup, 10f, 10f, 78.7f, 29.8f, "cpllogo.bmp"); break; //case "{!domlogo}": // AddImage(svgGroup, 10f, 70f, 123f, 40.1f, "domlogo.bmp"); // break; //case "{!gpclogo}": // AddImage(svgGroup, 10f, 150f, 35.2f, 35.8f, "gpclogo.bmp"); // break; case "{HEADER1}": case "{HEADER2}": case "{HEADER3}": case "{HEADER4}": case "{HEADER5}": case "{BOX1}": case "{BOX2}": case "{BOX3}": case "{BOX4}": case "{BOX5}": case "{BOX6}": case "{BOX7}": case "{BOX8}": case "{BOX9}": plstr = plstr.Replace(token, ""); svgGroup.Add(PageItemToSvgUse(pageItem, FirstAndLast(token))); break; case "{PMODEBOX}": // need to set either 1 or 2 depending on number of columns string box = "1"; if (_MySection.SectionConfig.Section_ColumnMode == SectionConfig.SectionColumnMode.Four) box = "4"; else if (_MySection.SectionConfig.Section_ColumnMode == SectionConfig.SectionColumnMode.Three) box = "3"; else if (_MySection.SectionConfig.Section_ColumnMode == SectionConfig.SectionColumnMode.Two) box = "2"; box = "{BOX" + box + "}"; plstr = plstr.Replace(token, ""); svgGroup.Add(PageItemToSvgUse(pageItem, FirstAndLast(box))); break; case "{DRAFTPAGE}": if (!AllowedWatermarks.Contains("Draft")) AllowedWatermarks.Add("Draft"); break; case "{REFERENCEPAGE}": if (!AllowedWatermarks.Contains("Reference")) AllowedWatermarks.Add("Reference"); break; case "{MASTERPAGE}": if (!AllowedWatermarks.Contains("Master")) AllowedWatermarks.Add("Master"); break; case "{SAMPLEPAGE}": if (!AllowedWatermarks.Contains("Sample")) AllowedWatermarks.Add("Sample"); break; case "{INFORMATIONPAGE}": if (!AllowedWatermarks.Contains("Information Only")) AllowedWatermarks.Add("Information Only"); break; case "{PROCTITLE}": case "{PROCTITLE1}": case "{PROCTITLE2}": case "{COVERPROCTITLE}": float linelen = (int)section.ActiveFormat.PlantFormat.FormatData.ProcData.TitleLength * (float)pageItem.Font.CPI / 12; plstr = SplitTitle(svgGroup, pageItem, section.MyProcedure.MyContent.Text.ToUpper(), (int)linelen, token, plstr); //,rowAdj); //SplitTitle(svgGroup, pageItem, section.MyProcedure.MyContent.Text, (int)section.ActiveFormat.PlantFormat.FormatData.ProcData.TitleLength, token); break; case "{COVERTITLE1}": case "{COVERTITLE2}": int ctlen = section.ActiveFormat.PlantFormat.FormatData.ProcData.CoverTitleLength ?? 0; float coverlinelen = ((ctlen == 0) ? (int)section.ActiveFormat.PlantFormat.FormatData.ProcData.TitleLength : ctlen) * (float)pageItem.Font.CPI / 12; plstr = SplitCoverTitle(svgGroup, pageItem, section.MyProcedure.MyContent.Text, (int)coverlinelen, token, plstr);//, rowAdj); //SplitTitle(svgGroup, pageItem, section.MyProcedure.MyContent.Text, (int)section.ActiveFormat.PlantFormat.FormatData.ProcData.TitleLength, token); break; case "{EOPNUM}": string eopnum = section.MyProcedure.MyContent.Number; string unitnum = MySection.MyDocVersion.DocVersionConfig.Unit_ProcedureNumber; if (unitnum.Length > 0) eopnum = unitnum.Replace("#", eopnum); plstr = plstr.Replace(token, eopnum); //svgGroup.Add(PageItemToSvgText(pageItem, pageItem.Token.Replace(token, eopnum))); //svgGroup.Add(PageItemToSvgText(pageItem, pageItem.Token.Replace(token, section.MyProcedure.MyContent.Number))); break; case "{SECTIONLEVELTITLE}": plstr = SplitTitle(svgGroup, pageItem, section.DisplayText, section.ActiveFormat.PlantFormat.FormatData.SectData.SectionTitleLength, token, plstr); //svgGroup.Add(PageItemToSvgText(pageItem, section.DisplayText)); break; case "{SECTIONLEVELNUMBER}": plstr = plstr.Replace(token, section.DisplayNumber); //svgGroup.Add(PageItemToSvgText(pageItem, pageItem.Token.Replace(token, section.DisplayNumber))); break; case "{UNITTEXT}": plstr = plstr.Replace(token, MySection.MyDocVersion.DocVersionConfig.Unit_Text); //svgGroup.Add(PageItemToSvgText(pageItem, pageItem.Token.Replace(token, MySection.MyDocVersion.DocVersionConfig.Unit_Text))); break; case "{CHKOFFHEADING}": // unfortunately, the font is not stored on the page list item if there is an active // check off header. It is stored with the checkoff data as defined by user selection // of the selected header (selection stored in section config). int sindx = section.CheckOffHeadingIndex(); VE_Font vf = sindx <= 0 ?pageItem.Font: section.ActiveFormat.PlantFormat.FormatData.ProcData.CheckOffData.CheckOffHeaderList[sindx].Font; useFontForCheckOffHeader = vf; PageListCheckOffHeader = PageItemToSvgText(pageItem, pageItem.Token, vf, section); break; default: // see if it's a PSI token: if (token.Contains(@"PS-")) { ProcedureConfig procConfig = section.MyProcedure.MyConfig as ProcedureConfig; if (procConfig != null) { int indx = token.IndexOf("-"); string val = procConfig.GetValue("PSI", token.Substring(4, token.Length - 5)); plstr = plstr.Replace(token, val); //svgGroup.Add(PageItemToSvgText(pageItem, pageItem.Token.Replace(token, val))); } } else if (token.Contains(@"PS=")) { ProcedureConfig procConfig = section.MyProcedure.MyConfig as ProcedureConfig; if (procConfig != null) { int indx = token.IndexOf("="); int qindx = token.IndexOf("?", indx); string pstok = token.Substring(indx + 1, qindx - indx - 1); string val = procConfig.GetValue("PSI", pstok); int bindx = token.IndexOf("|", indx); if (val == "Y") val = token.Substring(qindx + 1, bindx - qindx - 1); else { int eindx = token.IndexOf("}", bindx); val = token.Substring(bindx + 1, eindx - bindx - 1); } if (val != null && val != "") plstr = plstr.Replace(token, val); //if (val != null && val != "") svgGroup.Add(PageItemToSvgText(pageItem, val)); } } //else if (token.Contains(@"{DRV:Lpi ")) //{ // int indx = token.IndexOf("{DRV:Lpi ") + 9; // int endindx = token.IndexOf("}", indx); // string str = token.Substring(indx, endindx - indx); // curLPI = Convert.ToInt32(str) / 2; // //PrevRow = (float)(pageItem.Row + rowAdj); // PrevRow = (float)pageItem.Row; //} else { if (plstr != "") svgGroup.Add(PageItemToSvgText(pageItem, plstr, MySection)); //svgGroup.Add(PageItemToSvgText(pageItem, pageItem.Token)); } //_MyLog.InfoFormat("Token not processed {0}", token); break; } } // end foreach matches if (plstr != "") { if (useFontForCheckOffHeader != null) svgGroup.Add(PageItemToSvgText(pageItem, plstr, useFontForCheckOffHeader, MySection)); else svgGroup.Add(PageItemToSvgText(pageItem, plstr, MySection)); } } else svgGroup.Add(PageItemToSvgText(pageItem, pageItem.Token, MySection)); } } // Proms page numbering designed requires a "{PAGE}" token to increment the page counter. So the easiest way // to do that was to add a "{PAGE}" token to every page that is flagged as not to be printed. if (sPag == SectionConfig.SectionPagination.Separate) { SvgText st = new SvgText(new System.Drawing.PointF(300, 300), "Non-printing {PAGE}", new System.Drawing.Font("Arial", 10), System.Drawing.Color.Black); svgGroup.Add(st); } if (svgGroup.Count>0) mySvg.Add(svgGroup); } private static void AddImage(SvgGroup svgGroup, float x, float y, float w, float h, string figure) { svgGroup.Add(new SvgImage(new System.Drawing.PointF(x, y), new System.Drawing.SizeF(w, h), System.Windows.Forms.Application.StartupPath + @"\Resources\" + figure)); } private string SplitTitle(SvgGroup svgGroup, VEPROMS.CSLA.Library.PageItem pageItem, string title, int? len, string match, string plstr) //private void SplitTitle(SvgGroup svgGroup, VEPROMS.CSLA.Library.PageItem pageItem, string title, int? len, string match) { if (match == "{PROCTITLE2}") return plstr; if (len == null || len == 0 || ItemInfo.StripRtfFormatting(title).Length < len) { if (match == "{PROCTITLE2}") return plstr; // this would have been done in proctitle1 plstr = plstr.Replace(match, title); //svgGroup.Add(PageItemToSvgText(pageItem, title)); return plstr; } // Otherwise determine how many line to split the text into List titleLines = SplitText(title, (int)len); // if the token was proctitle, dont' adjust. If the token was PROCTITLE1/2 then // move down 6. int adj = pageItem.Token.Contains("1") || pageItem.Token.Contains("2") ? 0 : -6; float yOffset = adj * (titleLines.Count - 1); int cnt = 0; foreach (string line in titleLines) { cnt++; if (cnt == 1 && adj == 0) // adj == 0 means we use PROCTITLE1/PROCTITLE2 pagelist tokens plstr = plstr.Replace(match, line); else svgGroup.Add(PageItemToSvgText(pageItem, line, yOffset)); yOffset += 12; } return plstr; } private string SplitCoverTitle(SvgGroup svgGroup, VEPROMS.CSLA.Library.PageItem pageItem, string title, int? len, string match, string plstr) //private void SplitCoverTitle(SvgGroup svgGroup, VEPROMS.CSLA.Library.PageItem pageItem, string title, int? len, string match) { if (len == null || ItemInfo.StripRtfFormatting(title).Length < len) { if (match == "{COVERTITLE2}") return plstr; // this would have been done in COVERTITLE1 plstr = plstr.Replace(match, title); //svgGroup.Add(PageItemToSvgText(pageItem, title)); return plstr; } // Otherwise determine how many line to split the text into List titleLines = SplitText(title, (int)len); if (match == "{COVERTITLE1}") { plstr = plstr.Replace(match, titleLines[0]); //svgGroup.Add(PageItemToSvgText(pageItem, titleLines[0])); return plstr; } // if the token was proctitle, dont' adjust. If the token was PROCTITLE1/2 then // move down 6. // int adj = pageItem.Token.Contains("1") || pageItem.Token.Contains("2") ? 0 : -6; int adj = (titleLines.Count > 2) ? -6 : 0; float yOffset = adj * (titleLines.Count - 2); int lnCnt = 0; foreach (string line in titleLines) { lnCnt++; if (lnCnt == 1) continue; svgGroup.Add(PageItemToSvgText(pageItem, line, yOffset)); yOffset += 12; } return plstr; } private List SplitText(string text, int len) { List results = new List(); int width = 0; // width of text, non-rtf int start = 0; // start of line (index into string 'text'), includes rtf int lastspace = 0; // location of lastspace (index into string 'text'), includes rtf int startNonRtf = 0; // start of line, non-rtf (used for determining starting position to determine width if there was a break) string rtfprefix = ""; string nextprefix = ""; for (int indx = 0; indx < text.Length; indx++) { if (text[indx] == '\\') //rtf command { // look for three things at beginning of string: hex, unicode, rtfcommand. Match m = Regex.Match(text.Substring(indx), @"^\\'[a-fA-F0-9][a-fA-F0-9]"); //hex if (m.Success) { indx += m.Length - 1; width++; } else { m = Regex.Match(text.Substring(indx), @"^\\[uU][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][?]"); if (m.Success) { indx += m.Length - 1; width++; } else { m = Regex.Match(text.Substring(indx), @"^\\[^ ]*? "); if (m.Success) { indx += m.Length - 1; rtfprefix = AdjustRtfPrefix(rtfprefix, m.Value); } } } } else { if (text[indx] == ' ') { lastspace = indx; startNonRtf = width; } width++; if (width > len) { // what should be done if lastspace == 0 results.Add(nextprefix+text.Substring(start, lastspace-start)); nextprefix = rtfprefix; if (nextprefix != "") nextprefix += " "; start = lastspace + 1; width = (width - startNonRtf - 1) > 0 ? width - startNonRtf - 1 : 0; lastspace = 0; } } } if (width > 0 || start < text.Length) results.Add(nextprefix + text.Substring(start)); return results; } private string AdjustRtfPrefix(string rtfprefix, string rtfcommand) { if (rtfcommand.Contains(@"\ulnone") || rtfcommand.Contains(@"\ul0")) // off rtfprefix = rtfprefix.Replace(@"\ul",""); else if (rtfcommand.Contains(@"\ul")) rtfprefix += @"\ul"; if (rtfcommand.Contains(@"\up0") || rtfcommand.Contains(@"\dn0")) rtfprefix = rtfprefix.Replace(@"\up2", "").Replace(@"\dn2", ""); else if (rtfcommand.Contains(@"\up")) rtfprefix += @"\up2"; else if (rtfcommand.Contains(@"\dn")) rtfprefix += @"\dn2"; if (rtfcommand.Contains(@"\b0")) rtfprefix = rtfprefix.Replace(@"\b", ""); else if (rtfcommand.Contains(@"\b")) rtfprefix += @"\b"; if (rtfcommand.Contains(@"\i0")) rtfprefix = rtfprefix.Replace(@"\i", ""); else if (rtfcommand.Contains(@"\i")) rtfprefix += @"\i"; return rtfprefix; } private int FindWidth(string title, int start, int len) { for (int ii = start + len; ii > start; ii--) { if (title[ii] == ' ') { while (title[ii] == ' ') ii--; if (ii > start) return 2 + ii - start; return len; } } return len; } private SvgPart PageItemToSvgUse(VEPROMS.CSLA.Library.PageItem pageItem, string templateName) { SvgUse svgUse = new SvgUse(); svgUse.UseID = templateName; svgUse.X = new SvgMeasurement((float)(pageItem.Col ?? 0), E_MeasurementUnits.PT); svgUse.Y = new SvgMeasurement((float)(pageItem.Row ?? 0), E_MeasurementUnits.PT); return svgUse; } private static SvgText PageItemToSvgText(VEPROMS.CSLA.Library.PageItem pageItem, string text, VE_Font font, SectionInfo mySection) { SvgText svgText = PageItemToSvgText(pageItem, text, mySection); svgText.Font = font.WindowsFont; return svgText; } private static SvgText PageItemToSvgText(VEPROMS.CSLA.Library.PageItem pageItem, string text, SectionInfo mySection) { SvgText svgText = new SvgText(); svgText.Text = text; VEPROMS.CSLA.Library.E_Justify justify = pageItem.Justify ?? VEPROMS.CSLA.Library.E_Justify.PSLeft; float colAdj16bit = 0; if ((justify & VEPROMS.CSLA.Library.E_Justify.PSLeft) == VEPROMS.CSLA.Library.E_Justify.PSLeft) svgText.Justify = SvgJustify.Left; else if ((justify & VEPROMS.CSLA.Library.E_Justify.PSRight) == VEPROMS.CSLA.Library.E_Justify.PSRight) svgText.Justify = SvgJustify.Right; else { svgText.Justify = SvgJustify.Center; if (((justify & VEPROMS.CSLA.Library.E_Justify.PSTrue) != VEPROMS.CSLA.Library.E_Justify.PSTrue) && !mySection.ActiveFormat.PlantFormat.FormatData.SectData.StepSectionData.StepSectionLayoutData.PicaIgnoreFiveSixths) { // the default CPI for Proms is 12, in 16bit 12 is the default - if a font wasn't defined // or when doing a positioning calculation 12 was used. So we need to make an adjustment // to handle non-12 CPI (12CPI will just cancel out in the following calculation). // Take the difference between the width in Points of a character at 12CPI and a character // at the defined font's CPI. Multiply that times the length of title and divide by two // to find the half-way point. colAdj16bit = (1 + text.Length) * ((72 / (float)pageItem.Font.CPI) - (72 / 12)) / 2; } } svgText.Font = pageItem.Font.WindowsFont; float row = (float)pageItem.Row < 0 ? -(float)pageItem.Row : (float)pageItem.Row; float lcol = pageItem.Col ?? 0; // the column may need adjusted based on the document styles PageWidth. This was done in // the 16bit code and was needed here to get the printed output to match. if (pageItem.Font.FontIsProportional() && svgText.Justify == SvgJustify.Center) { int dotsPerChar = (int)(2400 / (mySection.MyDocStyle.Layout.PageWidth / 6)); lcol = (lcol * 25) / dotsPerChar; } svgText.X = new SvgMeasurement((float)lcol - colAdj16bit, E_MeasurementUnits.PT); svgText.Y = new SvgMeasurement(row, E_MeasurementUnits.PT); if (svgText.Font.Underline && svgText.Text.EndsWith(" ")) svgText.Text = svgText.Text.Substring(0, svgText.Text.Length - 1) + "\xA0";// replace last space with a hardspace return svgText; } private SvgPart PageItemToSvgText(VEPROMS.CSLA.Library.PageItem pageItem, string text, float yOffset) { SvgText svgText = new SvgText(); svgText.Text = text; VEPROMS.CSLA.Library.E_Justify justify = pageItem.Justify ?? VEPROMS.CSLA.Library.E_Justify.PSLeft; float colAdj16bit = 0; if ((justify & VEPROMS.CSLA.Library.E_Justify.PSLeft) == VEPROMS.CSLA.Library.E_Justify.PSLeft) svgText.Justify = SvgJustify.Left; else if ((justify & VEPROMS.CSLA.Library.E_Justify.PSRight) == VEPROMS.CSLA.Library.E_Justify.PSRight) svgText.Justify = SvgJustify.Right; else { svgText.Justify = SvgJustify.Center; if (((justify & VEPROMS.CSLA.Library.E_Justify.PSTrue) != VEPROMS.CSLA.Library.E_Justify.PSTrue) && !MySection.ActiveFormat.PlantFormat.FormatData.SectData.StepSectionData.StepSectionLayoutData.PicaIgnoreFiveSixths) { // the default CPI for Proms is 12, in 16bit 12 is the default - if a font wasn't defined // or when doing a positioning calculation 12 was used. So we need to make an adjustment // to handle non-12 CPI (12CPI will just cancel out in the following calculation). // Take the difference between the width in Points of a character at 12CPI and a character // at the defined font's CPI. Multiply that times the length of title and divide by two // to find the half-way point. colAdj16bit = (1 + text.Length) * ((72 / (float)pageItem.Font.CPI) - (72 / 12)) / 2; } } svgText.Font = pageItem.Font.WindowsFont; svgText.X = new SvgMeasurement((float)pageItem.Col - colAdj16bit, E_MeasurementUnits.PT); // new SvgMeasurement((float)(pageItem.Col ?? 0), E_MeasurementUnits.PT); svgText.Y = new SvgMeasurement((float)(yOffset + pageItem.Row ?? 0), E_MeasurementUnits.PT); if (svgText.Font.Underline && svgText.Text.EndsWith(" ")) svgText.Text = svgText.Text.Substring(0, svgText.Text.Length - 1) + "\xA0";// replace last space with a hardspace return svgText; } private static List _MissingTokens = new List(); protected override string ReplacePageListToken(Match match) { switch (match.Value) { case "{PAGE}": // Current Page Number return CurrentPageNumber.ToString(); case "{OF}": // Total Page Count for this section return CurrentPageOf.ToString(); case "{REVDATE}": // Revision Date if (RevDate == null || RevDate == "") return DateTime.Now.ToShortDateString(); return RevDate; case "{REV}": // Revision Number // 16-bit had very specific code to check for first character // == to a ' ' (space) and if so, start Rev from second character. if (Rev !=null && Rev != "" && Rev[0] == ' ') return Rev.Substring(1, Rev.Length - 1); return Rev; case "{CHKOFFHEADING}": return PageListTopCheckOffHeader; } if (!_MissingTokens.Contains(match.Value)) { _MissingTokens.Add(match.Value); _MyLog.InfoFormat("Unhandled token {0}", match.Value); } return ""; } } public class PageBookmarks : List { public PageBookmarks() : base() { } public void Add(ItemInfo itemInfo, string title, PdfDestination pdfDestination) { Add(new PageBookmark(itemInfo, title, pdfDestination)); } } public class PageBookmark { private ItemInfo _MyItemInfo; public ItemInfo MyItemInfo { get { return _MyItemInfo; } set { _MyItemInfo = value; } } private string _Title; public string Title { get { return _Title; } set { _Title = value; } } private PdfDestination _PdfDestination; public PdfDestination PdfDestination { get { return _PdfDestination; } set { _PdfDestination = value; } } public PageBookmark(ItemInfo itemInfo, string title, PdfDestination pdfDestination) { _MyItemInfo = itemInfo; _Title = title; _PdfDestination = pdfDestination; } } public class ChangeBarDefinition // anything that is section level should go in here. { private PrintChangeBar _MyChangeBarType; public PrintChangeBar MyChangeBarType { get { return _MyChangeBarType; } set { _MyChangeBarType = value; } } private PrintChangeBarLoc _MyChangeBarLoc; public PrintChangeBarLoc MyChangeBarLoc { get { return _MyChangeBarLoc; } set { _MyChangeBarLoc = value; } } private PrintChangeBarText _MyChangeBarText; public PrintChangeBarText MyChangeBarText { get { return _MyChangeBarText; } set { _MyChangeBarText = value; } } private string _MyChangeBarMessage; // user defined change bar message from docconfig public string MyChangeBarMessage { get { return _MyChangeBarMessage; } set { _MyChangeBarMessage = value; } } private int _MyChangeBarColumn; // two dimensional array, if both on same side, values are = public int MyChangeBarColumn { get { return _MyChangeBarColumn; } set { _MyChangeBarColumn = value; } } /* * could have line thickness (set default from 16-bit), line color (set default as black), line style */ public ChangeBarDefinition() { } public ChangeBarDefinition(PrintChangeBar myCBT, PrintChangeBarLoc myCBL, PrintChangeBarText myCBTxt, string myCBMsg) { _MyChangeBarType = myCBT; _MyChangeBarLoc = myCBL; _MyChangeBarText = myCBTxt; } public ChangeBarDefinition(DocVersionConfig docverConfig, FormatInfo formatInfo) { // if there is not overridden data on the docversion, prompt user... ChangeBarData changeBarData = formatInfo.PlantFormat.FormatData.ProcData.ChangeBarData; if (docverConfig == null || docverConfig.Print_ChangeBar == PrintChangeBar.SelectBeforePrinting) { // use these for now, i.e. this is what user would have selected from dialog. _MyChangeBarType = PrintChangeBar.WithDefault; _MyChangeBarLoc = PrintChangeBarLoc.OutsideBox; _MyChangeBarText = PrintChangeBarText.None; } else { _MyChangeBarType = docverConfig.Print_ChangeBar; _MyChangeBarLoc = docverConfig.Print_ChangeBarLoc; _MyChangeBarText = docverConfig.Print_ChangeBarText; } if (_MyChangeBarType == PrintChangeBar.WithDefault) // get data from format { _MyChangeBarText = changeBarData.ChangeBarMessage == "ChgID" ? PrintChangeBarText.ChgID : changeBarData.ChangeBarMessage == "DateAndChgID" ? PrintChangeBarText.DateChgID : changeBarData.ChangeBarMessage == "None" ? PrintChangeBarText.None : changeBarData.ChangeBarMessage == "RevNum" ? PrintChangeBarText.RevNum : PrintChangeBarText.UserDef; } if (_MyChangeBarType != PrintChangeBar.Without) { // if the format has the absolutefixedchangecolumn format flag, then always use the fixedchangecolumn from the // format, otherwise, use the default column based on the selected location, stored in the base format. _MyChangeBarColumn = (changeBarData.AbsoluteFixedChangeColumn) ? (int)changeBarData.FixedChangeColumn : System.Convert.ToInt32(changeBarData.DefaultCBLoc.Split(",".ToCharArray())[(int)(_MyChangeBarLoc)]); if (_MyChangeBarText == PrintChangeBarText.UserDef) _MyChangeBarMessage = docverConfig.Print_UserCBMess1 + @"\n" + docverConfig.Print_UserCBMess2; } } } public class CheckOffHeaderHelper { private SvgText _SvgText; public SvgText SvgText { get { return _SvgText; } set { _SvgText = value; } } private VEPROMS.CSLA.Library.VE_Font _CheckOffHeaderFont; public VEPROMS.CSLA.Library.VE_Font CheckOffHeaderFont { get { return _CheckOffHeaderFont; } set { _CheckOffHeaderFont = value; } } public CheckOffHeaderHelper(SvgText stext, VEPROMS.CSLA.Library.VE_Font vf) { _SvgText = stext; _CheckOffHeaderFont = vf; } } }