private void BuildContextMenus()
		{
			// EDIT MENU
			menuCopy = new MenuItem("&Copy", new EventHandler(this.menuCopy_Click));
			menuPaste = new MenuItem("Paste", new EventHandler(this.menuPaste_Click));
			menuDelete = new MenuItem("&Delete", new EventHandler(this.menuDelete_Click));
			menuFSep1 = new MenuItem("-");
			menuSelectAll = new MenuItem("Select &All", new EventHandler(this.menuSelectAll_Click));
			menuFSep2 = new MenuItem("-");

            List<MenuItem> insertItems = new List<MenuItem>();
            insertItems.Add(new MenuItem("&Chart...", new EventHandler(this.menuInsertChart_Click)));
            insertItems.Add(new MenuItem("&Grid", new EventHandler(this.menuInsertGrid_Click)));
            insertItems.Add(new MenuItem("&System.Drawing.Image", new EventHandler(this.menuInsertImage_Click)));
            insertItems.Add(new MenuItem("&Line", new EventHandler(this.menuInsertLine_Click)));
			insertItems.Add(new MenuItem("&List", new EventHandler(this.menuInsertList_Click)));
			insertItems.Add(new MenuItem("&Matrix...", new EventHandler(this.menuInsertMatrix_Click)));
            insertItems.Add(new MenuItem("&System.Drawing.Rectangle", new EventHandler(this.menuInsertRectangle_Click)));
            insertItems.Add(new MenuItem("&Subreport", new EventHandler(this.menuInsertSubreport_Click)));
            insertItems.Add(new MenuItem("Ta&ble...", new EventHandler(this.menuInsertTable_Click)));
            insertItems.Add(new MenuItem("&Textbox", new EventHandler(this.menuInsertTextbox_Click)));
            // Now add any CustomReportItems
            BuildContextMenusCustom(insertItems);

			menuInsert = new MenuItem("&Insert");
			menuInsert.MenuItems.AddRange(insertItems.ToArray());

			menuProperties = new MenuItem("&Properties...", new EventHandler(this.menuProperties_Click));

			//
			// Create chart context menu and add array of sub-menu items
			menuPropertiesLegend = new MenuItem("Legend...", new EventHandler(this.menuPropertiesLegend_Click));
			menuPropertiesCategoryAxis = new MenuItem("Category (X) Axis...", new EventHandler(this.menuPropertiesCategoryAxis_Click));
			menuPropertiesValueAxis = new MenuItem("Value (Y) Axis...", new EventHandler(this.menuPropertiesValueAxis_Click));
			
			menuPropertiesCategoryAxisTitle = new MenuItem("Category (X) Axis Title...", new EventHandler(this.menuPropertiesCategoryAxisTitle_Click));
			menuPropertiesValueAxisTitle = new MenuItem("Value (Y) Axis Title...", new EventHandler(this.menuPropertiesValueAxisTitle_Click));
            menuPropertiesValueAxis2Title = new MenuItem("Value (Y) Axis (Right) Title...", new EventHandler(this.menuPropertiesValueAxis2Title_Click));
			menuPropertiesChartTitle = new MenuItem("Title...", new EventHandler(this.menuPropertiesChartTitle_Click));

		}
        private void HighlightString(Graphics g, PageText dtext, RectangleF r, Font f, StringFormat sf)
        {
            if (_HighlightText == null || _HighlightText.Length == 0)
                return;         // nothing to highlight
            bool bhighlightItem = dtext == _HighlightItem ||
                    (_HighlightItem != null && dtext.HtmlParent == _HighlightItem);
            if (!(_HighlightAll || bhighlightItem))
                return;         // not highlighting all and not on current highlight item

            string hlt = _HighlightCaseSensitive ? _HighlightText : _HighlightText.ToLower();
            string text = _HighlightCaseSensitive ? dtext.Text : dtext.Text.ToLower();

            if (text.IndexOf(hlt) < 0)
                return;         // string not in text

            StringFormat sf2 = null;
            try
            {
                // Create a CharacterRange array with the highlight location and length
                // Handle multiple occurences of text
                List<CharacterRange> rangel = new List<CharacterRange>();
                int loc = text.IndexOf(hlt);
                int hlen = hlt.Length;
                int len = text.Length;
                while (loc >= 0)
                {
                    rangel.Add(new CharacterRange(loc, hlen));
                    if (loc + hlen < len)  // out of range of text
                        loc = text.IndexOf(hlt, loc + hlen);
                    else
                        loc = -1;
                }

                if (rangel.Count <= 0)      // we should have gotten one; but
                    return;

                CharacterRange[] ranges = rangel.ToArray();

                // Construct a new StringFormat object.
                sf2 = sf.Clone() as StringFormat;

                // Set the ranges on the StringFormat object.
                sf2.SetMeasurableCharacterRanges(ranges);

                // Get the Regions to highlight by calling the 
                // MeasureCharacterRanges method.
                if (r.Width <= 0 || r.Height <= 0)
                {
                    SizeF ts = g.MeasureString(dtext.Text, f);
                    r.Height = ts.Height;
                    r.Width = ts.Width;
                }
                Region[] charRegion = g.MeasureCharacterRanges(dtext.Text, f, r, sf2);

                // Fill in the region using a semi-transparent color to highlight
                foreach (Region rg in charRegion)
                {
                    Color hl = bhighlightItem ? _HighlightItemColor : _HighlightAllColor;
                    g.FillRegion(new SolidBrush(Color.FromArgb(50, hl)), rg);
                }
            }
            catch { }   // if highlighting fails we don't care; need to continue
            finally
            {
                if (sf2 != null)
                    sf2.Dispose();
            }
        }
		// FuncIDent: IDENTIFIER ( [Expr] [, Expr]*) | IDENTIFIER
		private bool MatchFuncIDent(out IExpr result)
		{
			IExpr e;
			string fullname;			// will hold the full name
			string method;				// will hold method name or second part of name
			string firstPart;			// will hold the collection name
			string thirdPart;			// will hold third part of name
			bool bOnePart;				// simple name: no ! or . in name

			result = null;

			if (curToken.Type != TokenTypes.IDENTIFIER)
				return false;

			// Disentangle method calls from collection references
			method = fullname = curToken.Value;
			curToken = tokens.Extract();

			// Break the name into parts
			char[] breakChars = new char[] {'!', '.'};

			int posBreak = method.IndexOfAny(breakChars);
			if (posBreak > 0)
			{
				bOnePart = false;
				firstPart = method.Substring(0, posBreak);
				method = method.Substring(posBreak+1);		// rest of expression
			}
			else
			{
				bOnePart = true;
				firstPart = method;
			}

			posBreak = method.IndexOf('.');
			if (posBreak > 0)
			{
				thirdPart = method.Substring(posBreak+1);	// rest of expression
				method = method.Substring(0, posBreak);
			}
			else
				thirdPart = null;

			if (curToken.Type != TokenTypes.LPAREN) switch (firstPart)
			{
				case "Fields":
					Field f = idLookup.LookupField(method);
					if (f == null && !this._InAggregate)
						throw new ParserException("Field '" + method + "'  not found.");
					if (thirdPart == null || thirdPart == "Value")
					{
						if (f == null)
						{
                            FunctionField ff;
                            result = ff = new FunctionField(method);
							this._FieldResolve.Add(ff);
						}
						else
							result = new FunctionField(f);	
					}
					else if (thirdPart == "IsMissing")
					{
						if (f == null)
						{
                            FunctionField ff;
							result = ff = new FunctionFieldIsMissing(method);
							this._FieldResolve.Add(ff);
						}
						else
							result = new FunctionFieldIsMissing(f);
					}
					else
						throw new ParserException("Field '" + method + "'  only supports 'Value' and 'IsMissing' properties.");
					return true;
                case "Parameters":  // see ResolveParametersMethod for resolution of MultiValue parameter function reference
					ReportParameter p = idLookup.LookupParameter(method);
					if (p == null)
						throw new ParserException("Report parameter '" + method + "'  not found.");
                    int ci = thirdPart == null? -1: thirdPart.IndexOf(".Count");
                    if (ci > 0)
                        thirdPart = thirdPart.Substring(0, ci);
                    FunctionReportParameter r;                    
					if (thirdPart == null || thirdPart == "Value")
						r = new FunctionReportParameter(p);
					else if (thirdPart == "Label")
						r = new FunctionReportParameterLabel(p);
					else
						throw new ParserException("Parameter '" + method + "'  only supports 'Value' and 'Label' properties.");
                    if (ci > 0)
                        r.SetParameterMethod("Count", null);
                    
                    result = r;
                    return true;
				case "ReportItems":
					Textbox t = idLookup.LookupReportItem(method);
					if (t == null)
						throw new ParserException("ReportItem '" + method + "'  not found.");
					if (thirdPart != null && thirdPart != "Value")
						throw new ParserException("ReportItem '" + method + "'  only supports 'Value' property.");
					result = new FunctionTextbox(t, idLookup.ExpressionName);	
					return true;
				case "Globals":
					e = idLookup.LookupGlobal(method);
					if (e == null)
						throw new ParserException("Globals '" + method + "'  not found.");
					result = e;
					return true;
				case "User":
					e = idLookup.LookupUser(method);
					if (e == null)
						throw new ParserException("User variable '" + method + "'  not found.");
					result = e;
					return true;
				case "Recursive":	// Only valid for some aggregate functions
					result = new IdentifierKey(IdentifierKeyEnum.Recursive);
					return true;
				case "Simple":		// Only valid for some aggregate functions
					result = new IdentifierKey(IdentifierKeyEnum.Simple);
					return true;
				default:
					if (!bOnePart)
						throw new ParserException(string.Format("'{0}' is an unknown identifer.", fullname));

					switch (method.ToLower())		// lexer should probably mark these
					{
						case "true":
						case "false":
							result = new ConstantBoolean(method.ToLower());
							break;
						default:
							// usually this is enum that will be used in an aggregate 
							result = new Identifier(method);
							break;
					}
					return true;
			}

			// We've got an function reference
			curToken = tokens.Extract();		// get rid of '('

			// Got a function now obtain the arguments
			int argCount=0;
			
			bool isAggregate = IsAggregate(method, bOnePart);
			if (_NoAggregate && isAggregate)
				throw new ParserException("Aggregate function '" + method + "' cannot be used within a Grouping expression.");
			if (_InAggregate && isAggregate)
				throw new ParserException("Aggregate function '" + method + "' cannot be nested in another aggregate function.");
			_InAggregate = isAggregate;
			if (_InAggregate)
				_FieldResolve = new List<FunctionField>();

            List<IExpr> largs = new List<IExpr>();
			while(true)
			{
				if (curToken.Type == TokenTypes.RPAREN)
				{	// We've got our function
					curToken = tokens.Extract();
					break;
				}
				if (argCount == 0)
				{
					// don't need to do anything
				}
				else if (curToken.Type == TokenTypes.COMMA)
				{
					curToken = tokens.Extract();
				}
				else
					throw new ParserException("Invalid function arguments.  Found '" + curToken.Value + "'  At column " + Convert.ToString(curToken.StartCol));
				
				MatchExprAndOr(out e);
				if (e == null)
					throw new ParserException("Expecting ',' or ')'.  Found '" + curToken.Value + "'  At column " + Convert.ToString(curToken.StartCol));

				largs.Add(e);
				argCount++;
			}
			if (_InAggregate)
			{
				ResolveFields(method, this._FieldResolve, largs);
				_FieldResolve = null;
				_InAggregate = false;
			}

            IExpr[] args = largs.ToArray();

			object scope;
			bool bSimple;
			if (!bOnePart)				
            {
                result = (firstPart == "Parameters")?
                    ResolveParametersMethod(method, thirdPart, args):
                    ResolveMethodCall(fullname, args);	// throw exception when fails
            }
			else switch(method.ToLower())
			{
				case "iif":
					if (args.Length != 3)
						throw new ParserException("iff function requires 3 arguments." + "  At column " + Convert.ToString(curToken.StartCol));
//  We allow any type for the first argument; it will get converted to boolean at runtime
//					if (args[0].GetTypeCode() != TypeCode.Boolean)
//						throw new ParserException("First argument to iif function must be boolean." + "  At column " + Convert.ToString(curToken.StartCol));
					result = new FunctionIif(args[0], args[1], args[2]);
					break;
				case "choose":
					if (args.Length <= 2)
						throw new ParserException("Choose function requires at least 2 arguments." + "  At column " + Convert.ToString(curToken.StartCol));
					switch (args[0].GetTypeCode())
					{
						case TypeCode.Double:
						case TypeCode.Single:
						case TypeCode.Int32:
						case TypeCode.Decimal:
						case TypeCode.Int16:
						case TypeCode.Int64:
							break;
						default:
							throw new ParserException("First argument to Choose function must be numeric." + "  At column " + Convert.ToString(curToken.StartCol));
					}
					result = new FunctionChoose(args);
					break;
				case "switch":
					if (args.Length <= 2)
						throw new ParserException("Switch function requires at least 2 arguments." + "  At column " + Convert.ToString(curToken.StartCol));
				    if (args.Length % 2 != 0)
						throw new ParserException("Switch function must have an even number of arguments." + "  At column " + Convert.ToString(curToken.StartCol));
					for (int i=0; i < args.Length; i = i+2)
					{
						if (args[i].GetTypeCode() != TypeCode.Boolean)
							throw new ParserException("Switch function must have a boolean expression every other argument." + "  At column " + Convert.ToString(curToken.StartCol));
					}
					result = new FunctionSwitch(args);
					break;
				case "format":
					if (args.Length > 2 || args.Length < 1)
						throw new ParserException("Format function requires 2 arguments." + "  At column " + Convert.ToString(curToken.StartCol));
					if (args.Length == 1)
					{
						result = new FunctionFormat(args[0], new ConstantString(""));
					}
					else
					{
						if (args[1].GetTypeCode() != TypeCode.String)
							throw new ParserException("Second argument to Format function must be a string." + "  At column " + Convert.ToString(curToken.StartCol));
						result = new FunctionFormat(args[0], args[1]);
					}
					break;

				case "fields":
					if (args.Length != 1)
						throw new ParserException("Fields collection requires exactly 1 argument." + "  At column " + Convert.ToString(curToken.StartCol));
					result = new FunctionFieldCollection(idLookup.Fields, args[0]);
                    if (curToken.Type == TokenTypes.DOT)
                    {	// user placed "."                  TODO: generalize this code
                        curToken = tokens.Extract();                // skip past dot operator
                        if (curToken.Type == TokenTypes.IDENTIFIER && curToken.Value.ToLowerInvariant() == "value")
                            curToken = tokens.Extract();            // only support "value" property for now
                        else
                            throw new ParserException(curToken.Value + " is not a known property for Fields." + "  At column " + Convert.ToString(curToken.StartCol));
                    }
                    break;
				case "parameters":
					if (args.Length != 1)
						throw new ParserException("Parameters collection requires exactly 1 argument." + "  At column " + Convert.ToString(curToken.StartCol));
					result = new FunctionParameterCollection(idLookup.Parameters, args[0]);
                    if (curToken.Type == TokenTypes.DOT)
                    {	// user placed "." 
                        curToken = tokens.Extract();                // skip past dot operator
                        if (curToken.Type == TokenTypes.IDENTIFIER && curToken.Value.ToLowerInvariant() == "value")
                            curToken = tokens.Extract();            // only support "value" property for now
                        else
                            throw new ParserException(curToken.Value + " is not a known property for Fields." + "  At column " + Convert.ToString(curToken.StartCol));
                    }
                    break;
				case "reportitems":
					if (args.Length != 1)
						throw new ParserException("ReportItems collection requires exactly 1 argument." + "  At column " + Convert.ToString(curToken.StartCol));
					result = new FunctionReportItemCollection(idLookup.ReportItems, args[0]);
                    if (curToken.Type == TokenTypes.DOT)
                    {	// user placed "." 
                        curToken = tokens.Extract();                // skip past dot operator
                        if (curToken.Type == TokenTypes.IDENTIFIER && curToken.Value.ToLowerInvariant() == "value")
                            curToken = tokens.Extract();            // only support "value" property for now
                        else
                            throw new ParserException(curToken.Value + " is not a known property for Fields." + "  At column " + Convert.ToString(curToken.StartCol));
                    }
                    break;
				case "globals":
					if (args.Length != 1)
						throw new ParserException("Globals collection requires exactly 1 argument." + "  At column " + Convert.ToString(curToken.StartCol));
					result = new FunctionGlobalCollection(idLookup.Globals, args[0]);
					break;
				case "user":
					if (args.Length != 1)
						throw new ParserException("User collection requires exactly 1 argument." + "  At column " + Convert.ToString(curToken.StartCol));
					result = new FunctionUserCollection(idLookup.User, args[0]);
					break;
				case "sum":
					scope = ResolveAggrScope(args, 2, out bSimple);
					FunctionAggrSum aggrFS = new FunctionAggrSum(_DataCache, args[0], scope);
					aggrFS.LevelCheck = bSimple;
					result = aggrFS;
					break;
				case "avg":
					scope = ResolveAggrScope(args, 2, out bSimple);
					FunctionAggrAvg aggrFA = new FunctionAggrAvg(_DataCache, args[0], scope);
					aggrFA.LevelCheck = bSimple;
					result = aggrFA;
					break;
				case "min":
					scope = ResolveAggrScope(args, 2, out bSimple);
					FunctionAggrMin aggrFMin = new FunctionAggrMin(_DataCache, args[0], scope);
					aggrFMin.LevelCheck = bSimple;
					result = aggrFMin;
					break;
				case "max":
					scope = ResolveAggrScope(args, 2, out bSimple);
					FunctionAggrMax aggrFMax = new FunctionAggrMax(_DataCache, args[0], scope);
					aggrFMax.LevelCheck = bSimple;
					result = aggrFMax;
					break;
				case "first":
					scope = ResolveAggrScope(args, 2, out bSimple);
					result = new FunctionAggrFirst(_DataCache, args[0], scope);
					break;
				case "last":
					scope = ResolveAggrScope(args, 2, out bSimple);
					result = new FunctionAggrLast(_DataCache, args[0], scope);
					break;
				case "next":
					scope = ResolveAggrScope(args, 2, out bSimple);
					result = new FunctionAggrNext(_DataCache, args[0], scope);
					break;
				case "previous":
				    scope = ResolveAggrScope(args, 2, out bSimple);
					result = new FunctionAggrPrevious(_DataCache, args[0], scope);
					break;
				case "level":
					scope = ResolveAggrScope(args, 1, out bSimple);
					result = new FunctionAggrLevel(scope);
					break;
                case "aggregate":
                    scope = ResolveAggrScope(args, 2, out bSimple);
                    FunctionAggrArray aggr = new FunctionAggrArray(_DataCache, args[0], scope);
                    aggr.LevelCheck = bSimple;
                    result = aggr;
                    break;
                case "count":
					scope = ResolveAggrScope(args, 2, out bSimple);
					FunctionAggrCount aggrFC = new FunctionAggrCount(_DataCache, args[0], scope);
					aggrFC.LevelCheck = bSimple;
					result = aggrFC;
					break;
				case "countrows":
					scope = ResolveAggrScope(args, 1, out bSimple);
					FunctionAggrCountRows aggrFCR = new FunctionAggrCountRows(scope);
					aggrFCR.LevelCheck = bSimple;
					result = aggrFCR;
					break;
                case "countdistinct":
					scope = ResolveAggrScope(args, 2, out bSimple);
					FunctionAggrCountDistinct aggrFCD = new FunctionAggrCountDistinct(_DataCache, args[0], scope);
					aggrFCD.LevelCheck = bSimple;
					result = aggrFCD;
					break;
				case "rownumber":
					scope = ResolveAggrScope(args, 1, out bSimple);
					IExpr texpr = new ConstantDouble("0");
					result = new FunctionAggrRvCount(_DataCache, texpr, scope);
					break;
				case "runningvalue":
					if (args.Length < 2 || args.Length > 3)
						throw new ParserException("RunningValue takes 2 or 3 arguments." + "  At column " + Convert.ToString(curToken.StartCol));
					string aggrFunc = args[1].EvaluateString(null, null);
					if (aggrFunc == null)
						throw new ParserException("RunningValue 'Function' argument is invalid." + "  At column " + Convert.ToString(curToken.StartCol));
					scope = ResolveAggrScope(args, 3, out bSimple);
					switch(aggrFunc.ToLower())
					{
						case "sum":
							result = new FunctionAggrRvSum(_DataCache, args[0], scope);
							break;
						case "avg":
							result = new FunctionAggrRvAvg(_DataCache, args[0], scope);
							break;
						case "count":
							result = new FunctionAggrRvCount(_DataCache, args[0], scope);
							break;
						case "max":
							result = new FunctionAggrRvMax(_DataCache, args[0], scope);
							break;
						case "min":
							result = new FunctionAggrRvMin(_DataCache, args[0], scope);
							break;
						case "stdev":
							result = new FunctionAggrRvStdev(_DataCache, args[0], scope);
							break;
						case "stdevp":
							result = new FunctionAggrRvStdevp(_DataCache, args[0], scope);
							break;
						case "var":
							result = new FunctionAggrRvVar(_DataCache, args[0], scope);
							break;
						case "varp":
							result = new FunctionAggrRvVarp(_DataCache, args[0], scope);
							break;
						default:
							throw new ParserException("RunningValue function '" + aggrFunc + "' is not supported.  At column " + Convert.ToString(curToken.StartCol));
					}
					break;
				case "stdev":
					scope = ResolveAggrScope(args, 2, out bSimple);
					FunctionAggrStdev aggrSDev = new FunctionAggrStdev(_DataCache, args[0], scope);
					aggrSDev.LevelCheck = bSimple;
					result = aggrSDev;
					break;
				case "stdevp":
					scope = ResolveAggrScope(args, 2, out bSimple);
					FunctionAggrStdevp aggrSDevP = new FunctionAggrStdevp(_DataCache, args[0], scope);
					aggrSDevP.LevelCheck = bSimple;
					result = aggrSDevP;
					break;
				case "var":
					scope = ResolveAggrScope(args, 2, out bSimple);
					FunctionAggrVar aggrVar = new FunctionAggrVar(_DataCache, args[0], scope);
					aggrVar.LevelCheck = bSimple;
					result = aggrVar;
					break;
				case "varp":
					scope = ResolveAggrScope(args, 2, out bSimple);
					FunctionAggrVarp aggrVarP = new FunctionAggrVarp(_DataCache, args[0], scope);
					aggrVarP.LevelCheck = bSimple;
					result = aggrVarP;
					break;
				default:
					result = ResolveMethodCall(fullname, args);		// through exception when fails
					break;
			}

			return true;
		}
		private string[] MeasureString(PageText pt, Graphics g, out float[] width)
		{
			StyleInfo si = pt.SI;
			string s = pt.Text;

			Font drawFont=null;
			StringFormat drawFormat=null;
			SizeF ms;
			string[] sa=null;
			width=null;
			try
			{
				// STYLE
				System.Drawing.FontStyle fs = 0;
				if (si.FontStyle == FontStyleEnum.Italic)
					fs |= System.Drawing.FontStyle.Italic;

				// WEIGHT
				switch (si.FontWeight)
				{
					case FontWeightEnum.Bold:
					case FontWeightEnum.Bolder:
					case FontWeightEnum.W500:
					case FontWeightEnum.W600:
					case FontWeightEnum.W700:
					case FontWeightEnum.W800:
					case FontWeightEnum.W900:
						fs |= System.Drawing.FontStyle.Bold;
						break;
					default:
						break;
				}

				drawFont = new Font(StyleInfo.GetFontFamily(si.FontFamilyFull), si.FontSize, fs);
				drawFormat = new StringFormat();
				drawFormat.Alignment = StringAlignment.Near;

				// Measure string   
				//  pt.NoClip indicates that this was generated by PageTextHtml Build.  It has already word wrapped.
				if (pt.NoClip || pt.SI.WritingMode == WritingModeEnum.tb_rl)	// TODO: support multiple lines for vertical text
				{
					ms = MeasureString(s, g, drawFont, drawFormat);
					width = new float[1];
					width[0] = RSize.PointsFromPixels(g, ms.Width);	// convert to points from pixels
					sa = new string[1];
					sa[0] = s;
					return sa;
				}

				// handle multiple lines;
				//  1) split the string into the forced line breaks (ie "\n and \r")
				//  2) foreach of the forced line breaks; break these into words and recombine 
				s = s.Replace("\r\n", "\n");	// don't want this to result in double lines
				string[] flines = s.Split(lineBreak);
				List<string> lines = new List<string>();
				List<float> lineWidths = new List<float>();
                // remove the size reserved for left and right padding
                float ptWidth = pt.W - pt.SI.PaddingLeft - pt.SI.PaddingRight;
                if (ptWidth <= 0)
                    ptWidth = 1;
				foreach (string tfl in flines)
				{
					string fl;
					if (tfl.Length > 0 && tfl[tfl.Length-1] == ' ')
						fl = tfl.TrimEnd(' ');
					else
						fl = tfl;

                    // Check if entire string fits into a line
                    ms = MeasureString(fl, g, drawFont, drawFormat);
                    float tw = RSize.PointsFromPixels(g, ms.Width);
                    if (tw <= ptWidth)      
                    {                       // line fits don't need to break it down further
                        lines.Add(fl);
                        lineWidths.Add(tw);
                        continue;
                    }
                    
                    // Line too long; need to break into multiple lines
                    // 1) break line into parts; then build up again keeping track of word positions
					string[] parts = fl.Split(wordBreak);	// this is the maximum split of lines
                    StringBuilder sb = new StringBuilder(fl.Length);
                    CharacterRange[] cra = new CharacterRange[parts.Length];
                    for (int i = 0; i < parts.Length; i++)
					{
                        int sc = sb.Length;     // starting character
                        sb.Append(parts[i]);    // endding character
                        if (i != parts.Length - 1)  // last item doesn't need blank
                            sb.Append(" ");
                        int ec = sb.Length;   
                        CharacterRange cr = new CharacterRange(sc, ec - sc);
                        cra[i] = cr;            // add to character array
					}

                    // 2) Measure the word locations within the line
                    string wfl = sb.ToString();
                    WordStartFinish[] wordLocations = MeasureString(wfl, g, drawFont, drawFormat, cra);
                    if (wordLocations == null)
                        continue;

					// 3) Loop thru creating new lines as needed
                    int startLoc = 0;
                    CharacterRange crs = cra[startLoc];
                    CharacterRange cre = cra[startLoc];
                    float cwidth = wordLocations[0].end;    // length of the first
                    float bwidth = wordLocations[0].start;  // characters need a little extra on start
                    string ts;
                    bool bLine = true;
                    for (int i=1; i < cra.Length; i++)
					{
                        cwidth = wordLocations[i].end - wordLocations[startLoc].start + bwidth;
                        if (cwidth > ptWidth)
						{	// time for a new line
                            cre = cra[i-1];
                            ts = wfl.Substring(crs.First, cre.First + cre.Length - crs.First);
                            lines.Add(ts);
                            lineWidths.Add(wordLocations[i-1].end - wordLocations[startLoc].start + bwidth);
                            
                            // Find the first non-blank character of the next line
                            while (i < cra.Length &&
                                    cra[i].Length == 1 &&
                                    fl[cra[i].First] == ' ')
                            {
                                i++;
                            }
                            if (i < cra.Length)   // any lines left?
                            {  // yes, continue on
                                startLoc = i;
                                crs = cre = cra[startLoc];
                                cwidth = wordLocations[i].end - wordLocations[startLoc].start + bwidth;
                            }
                            else  // no, we can stop
                                bLine = false;
                          //  bwidth = wordLocations[startLoc].start - wordLocations[startLoc - 1].end;
                        }
                        else
                            cre = cra[i];
                    }
                    if (bLine)
                    {
                        ts = fl.Substring(crs.First, cre.First + cre.Length - crs.First);
                        lines.Add(ts);
                        lineWidths.Add(cwidth);
                    }
				}
                // create the final array from the Lists
                string[] la = lines.ToArray();
                width = lineWidths.ToArray(); 
				return la;
			}
			finally
			{
				if (drawFont != null)
					drawFont.Dispose();
				if (drawFormat != null)
					drawFont.Dispose();
			}
		}