context: /*
   * Copyright (c) Contributors, http://opensimulator.org/
   * See CONTRIBUTORS.TXT for a full list of copyright holders.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *     * Redistributions of source code must retain the above copyright
   *       notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above copyright
   *       notice, this list of conditions and the following disclaimer in the
   *       documentation and/or other materials provided with the distribution.
   *     * Neither the name of the OpenSimulator Project nor the
   *       names of its contributors may be used to endorse or promote products
   *       derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  using System;
  using System.Drawing;
  using System.Drawing.Imaging;
  using System.Globalization;
  using System.IO;
  using System.Linq;
  using System.Net;
  using Nini.Config;
  using OpenMetaverse;
  using OpenMetaverse.Imaging;
  using OpenSim.Region.CoreModules.Scripting.DynamicTexture;
  using OpenSim.Region.Framework.Interfaces;
  using OpenSim.Region.Framework.Scenes;
  using log4net;
  using System.Reflection;
  using Mono.Addins;
  
  //using Cairo;
  
  namespace OpenSim.Region.CoreModules.Scripting.VectorRender
  {
      [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "VectorRenderModule")]
      public class VectorRenderModule : ISharedRegionModule, IDynamicTextureRender
      {
          // These fields exist for testing purposes, please do not remove.
  //        private static bool s_flipper;
  //        private static byte[] s_asset1Data;
  //        private static byte[] s_asset2Data;
  
          private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  
          private Scene m_scene;
          private IDynamicTextureManager m_textureManager;
  
          private Graphics m_graph;
          private string m_fontName = "Arial";
  
          public VectorRenderModule()
          {
          }
  
          #region IDynamicTextureRender Members
  
          public string GetContentType()
          {
              return "vector";
          }
  
          public string GetName()
          {
              return Name;
          }
  
          public bool SupportsAsynchronous()
          {
              return true;
          }
  
  //        public bool AlwaysIdenticalConversion(string bodyData, string extraParams)
  //        {
  //            string[] lines = GetLines(bodyData);
  //            return lines.Any((str, r) => str.StartsWith("Image"));
  //        }
  
          public IDynamicTexture ConvertUrl(string url, string extraParams)
          {
              return null;
          }
  
          public IDynamicTexture ConvertData(string bodyData, string extraParams)
          {
              return Draw(bodyData, extraParams);
          }
  
          public bool AsyncConvertUrl(UUID id, string url, string extraParams)
          {
              return false;
          }
  
          public bool AsyncConvertData(UUID id, string bodyData, string extraParams)
          {
              if (m_textureManager == null)
              {
                  m_log.Warn("[VECTORRENDERMODULE]: No texture manager. Can't function");
                  return false;
              }
              // XXX: This isn't actually being done asynchronously!
              m_textureManager.ReturnData(id, ConvertData(bodyData, extraParams));
  
              return true;
          }
  
          public void GetDrawStringSize(string text, string fontName, int fontSize, 
                                        out double xSize, out double ySize)
          {
              lock (this)
              {
                  using (Font myFont = new Font(fontName, fontSize))
                  {
                      SizeF stringSize = new SizeF();
  
                      // XXX: This lock may be unnecessary.
                      lock (m_graph)
                      {
                          stringSize = m_graph.MeasureString(text, myFont);
                          xSize = stringSize.Width;
                          ySize = stringSize.Height;
                      }
                  }
              }
          }
  
          #endregion
  
          #region ISharedRegionModule Members
  
          public void Initialise(IConfigSource config)
          {
              IConfig cfg = config.Configs["VectorRender"];
              if (null != cfg)
              {
                  m_fontName = cfg.GetString("font_name", m_fontName);
              }
              m_log.DebugFormat("[VECTORRENDERMODULE]: using font \"{0}\" for text rendering.", m_fontName);
  
              // We won't dispose of these explicitly since this module is only removed when the entire simulator
              // is shut down.
              Bitmap bitmap = new Bitmap(1024, 1024, PixelFormat.Format32bppArgb);
              m_graph = Graphics.FromImage(bitmap);
          }
  
          public void PostInitialise()
          {
          }
  
          public void AddRegion(Scene scene)
          {
              if (m_scene == null)
              {
                  m_scene = scene;
              }
          }
  
          public void RegionLoaded(Scene scene)
          {
              if (m_textureManager == null && m_scene == scene)
              {
                  m_textureManager = m_scene.RequestModuleInterface<IDynamicTextureManager>();
                  if (m_textureManager != null)
                  {
                      m_textureManager.RegisterRender(GetContentType(), this);
                  }
              }
          }
  
          public void RemoveRegion(Scene scene)
          {
          }
  
          public void Close()
          {
          }
  
          public string Name
          {
              get { return "VectorRenderModule"; }
          }
  
          public Type ReplaceableInterface
          {
              get { return null; }
          }
  
          #endregion
  
          private IDynamicTexture Draw(string data, string extraParams)
          {
              // We need to cater for old scripts that didnt use extraParams neatly, they use either an integer size which represents both width and height, or setalpha
              // we will now support multiple comma seperated params in the form  width:256,height:512,alpha:255
              int width = 256;
              int height = 256;
              int alpha = 255; // 0 is transparent
              Color bgColor = Color.White;  // Default background color
              char altDataDelim = ';';
              
              char[] paramDelimiter = { ',' };
              char[] nvpDelimiter = { ':' };
             
              extraParams = extraParams.Trim();
              extraParams = extraParams.ToLower();
              
              string[] nvps = extraParams.Split(paramDelimiter);
              
              int temp = -1;
              foreach (string pair in nvps)
              {
                  string[] nvp = pair.Split(nvpDelimiter);
                  string name = "";
                  string value = "";
                  
                  if (nvp[0] != null)
                  {
                      name = nvp[0].Trim();
                  }
                  
                  if (nvp.Length == 2)
                  {
                      value = nvp[1].Trim();
                  }
                  
                  switch (name)
                  {
                      case "width":
                          temp = parseIntParam(value);
                          if (temp != -1)
                          {
                              if (temp < 1)
                              {
                                  width = 1;
                              }
                              else if (temp > 2048)
                              {
                                  width = 2048;
                              }
                              else
                              {
                                  width = temp;
                              }
                          }
                          break;
                      case "height":
                          temp = parseIntParam(value);
                          if (temp != -1)
                          {
                              if (temp < 1)
                              {
                                  height = 1;
                              }
                              else if (temp > 2048)
                              {
                                  height = 2048;
                              }
                              else
                              {
                                  height = temp;
                              }
                          }
                          break;
                       case "alpha":
                            temp = parseIntParam(value);
                            if (temp != -1)
                            {
                                if (temp < 0)
                                {
                                    alpha = 0;
                                }
                                else if (temp > 255)
                                {
                                    alpha = 255;
                                }
                                else
                                {
                                    alpha = temp;
                                }
                            }
                            // Allow a bitmap w/o the alpha component to be created
                            else if (value.ToLower() == "false") {
                                 alpha = 256;
                            }
                            break;
                       case "bgcolor":
                       case "bgcolour":
                            int hex = 0;
                           if (Int32.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hex))
                           {
                               bgColor = Color.FromArgb(hex);
                           } 
                           else
                           {
                               bgColor = Color.FromName(value);
                           }
                           break;
                       case "altdatadelim":
                           altDataDelim = value.ToCharArray()[0];
                           break;
                       case "":
                           // blank string has been passed do nothing just use defaults
                       break;
                       default: // this is all for backwards compat, all a bit ugly hopfully can be removed in future
                           // could be either set alpha or just an int
                           if (name == "setalpha")
                           {
                               alpha = 0; // set the texture to have transparent background (maintains backwards compat)
                           }
                           else
                           {
                               // this function used to accept an int on its own that represented both 
                               // width and height, this is to maintain backwards compat, could be removed
                               // but would break existing scripts
                               temp = parseIntParam(name);
                               if (temp != -1)
                               {
                                   if (temp > 1024)
                                      temp = 1024;
                                      
                                   if (temp < 128)
                                       temp = 128;
                                    
                                   width = temp;
                                   height = temp;
                               }
                           }
                       break;
                  }
              }
  
              Bitmap bitmap = null;
              Graphics graph = null;
              bool reuseable = false;
  
              try
              {
                  // XXX: In testing, it appears that if multiple threads dispose of separate GDI+ objects simultaneously,
                  // the native malloc heap can become corrupted, possibly due to a double free().  This may be due to
                  // bugs in the underlying libcairo used by mono's libgdiplus.dll on Linux/OSX.  These problems were
                  // seen with both libcario 1.10.2-6.1ubuntu3 and 1.8.10-2ubuntu1.  They go away if disposal is perfomed
                  // under lock.
                  lock (this)
                  {
                      if (alpha == 256)
                          bitmap = new Bitmap(width, height, PixelFormat.Format32bppRgb);
                      else
                          bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
      
                      graph = Graphics.FromImage(bitmap);
          
                      // this is really just to save people filling the 
                      // background color in their scripts, only do when fully opaque
                      if (alpha >= 255)
                      {
                          using (SolidBrush bgFillBrush = new SolidBrush(bgColor))
                          {
                              graph.FillRectangle(bgFillBrush, 0, 0, width, height);
                          }
                      }
          
                      for (int w = 0; w < bitmap.Width; w++)
                      {
                          if (alpha <= 255) 
                          {
                              for (int h = 0; h < bitmap.Height; h++)
                              {
                                  bitmap.SetPixel(w, h, Color.FromArgb(alpha, bitmap.GetPixel(w, h)));
                              }
                          }
                      }
          
                      GDIDraw(data, graph, altDataDelim, out reuseable);
                  }
      
                  byte[] imageJ2000 = new byte[0];
  
                  // This code exists for testing purposes, please do not remove.
  //                if (s_flipper)
  //                    imageJ2000 = s_asset1Data;
  //                else
  //                    imageJ2000 = s_asset2Data;
  //
  //                s_flipper = !s_flipper;
      
                  try
                  {
                      imageJ2000 = OpenJPEG.EncodeFromImage(bitmap, true);
                  }
                  catch (Exception e)
                  {
                      m_log.ErrorFormat(
                          "[VECTORRENDERMODULE]: OpenJpeg Encode Failed.  Exception {0}{1}",
                          e.Message, e.StackTrace);
                  }
  
                  return new OpenSim.Region.CoreModules.Scripting.DynamicTexture.DynamicTexture(
                      data, extraParams, imageJ2000, new Size(width, height), reuseable);
              }
              finally
              {
                  // XXX: In testing, it appears that if multiple threads dispose of separate GDI+ objects simultaneously,
                  // the native malloc heap can become corrupted, possibly due to a double free().  This may be due to
                  // bugs in the underlying libcairo used by mono's libgdiplus.dll on Linux/OSX.  These problems were
                  // seen with both libcario 1.10.2-6.1ubuntu3 and 1.8.10-2ubuntu1.  They go away if disposal is perfomed
                  // under lock.
                  lock (this)
                  {
                      if (graph != null)
                          graph.Dispose();
      
                      if (bitmap != null)
                          bitmap.Dispose();
                  }
              }
          }
          
          private int parseIntParam(string strInt)
          {
              int parsed;
              try
              {
                  parsed = Convert.ToInt32(strInt);
              }
              catch (Exception)
              {
                  //Ckrinke: Add a WriteLine to remove the warning about 'e' defined but not used
                  // m_log.Debug("Problem with Draw. Please verify parameters." + e.ToString());
                  parsed = -1;
              }
              
              return parsed;
          }
  
  /*
          private void CairoDraw(string data, System.Drawing.Graphics graph)
          {
              using (Win32Surface draw = new Win32Surface(graph.GetHdc()))
              {
                  Context contex = new Context(draw);
  
                  contex.Antialias = Antialias.None;    //fastest method but low quality
                  contex.LineWidth = 7;
                  char[] lineDelimiter = { ';' };
                  char[] partsDelimiter = { ',' };
                  string[] lines = data.Split(lineDelimiter);
  
                  foreach (string line in lines)
                  {
                      string nextLine = line.Trim();
  
                      if (nextLine.StartsWith("MoveTO"))
                      {
                          float x = 0;
                          float y = 0;
                          GetParams(partsDelimiter, ref nextLine, ref x, ref y);
                          contex.MoveTo(x, y);
                      }
                      else if (nextLine.StartsWith("LineTo"))
                      {
                          float x = 0;
                          float y = 0;
                          GetParams(partsDelimiter, ref nextLine, ref x, ref y);
                          contex.LineTo(x, y);
                          contex.Stroke();
                      }
                  }
              }
              graph.ReleaseHdc();
          }
  */
  
          /// <summary>
          /// Split input data into discrete command lines.
          /// </summary>
          /// <returns></returns>
          /// <param name='data'></param>
          /// <param name='dataDelim'></param>
          private string[] GetLines(string data, char dataDelim)
          {
              char[] lineDelimiter = { dataDelim };
              return data.Split(lineDelimiter);
          }
  
          private void GDIDraw(string data, Graphics graph, char dataDelim, out bool reuseable)
          {
              reuseable = true;
              Point startPoint = new Point(0, 0);
              Point endPoint = new Point(0, 0);
              Pen drawPen = null;
              Font myFont = null;
              SolidBrush myBrush = null;
  
              try
              {
                  drawPen = new Pen(Color.Black, 7);
                  string fontName = m_fontName;
                  float fontSize = 14;
                  myFont = new Font(fontName, fontSize);
                  myBrush = new SolidBrush(Color.Black);
  
                  char[] partsDelimiter = {','};
  
                  foreach (string line in GetLines(data, dataDelim))
                  {
                      string nextLine = line.Trim();
                      //replace with switch, or even better, do some proper parsing
                      if (nextLine.StartsWith("MoveTo"))
                      {
                          float x = 0;
                          float y = 0;
                          GetParams(partsDelimiter, ref nextLine, 6, ref x, ref y);
                          startPoint.X = (int) x;
                          startPoint.Y = (int) y;
                      }
                      else if (nextLine.StartsWith("LineTo"))
                      {
                          float x = 0;
                          float y = 0;
                          GetParams(partsDelimiter, ref nextLine, 6, ref x, ref y);
                          endPoint.X = (int) x;
                          endPoint.Y = (int) y;
                          graph.DrawLine(drawPen, startPoint, endPoint);
                          startPoint.X = endPoint.X;
                          startPoint.Y = endPoint.Y;
                      }
                      else if (nextLine.StartsWith("Text"))
                      {
                          nextLine = nextLine.Remove(0, 4);
                          nextLine = nextLine.Trim();
                          graph.DrawString(nextLine, myFont, myBrush, startPoint);
                      }
                      else if (nextLine.StartsWith("Image"))
                      {
                          // We cannot reuse any generated texture involving fetching an image via HTTP since that image
                          // can change.
                          reuseable = false;
  
                          float x = 0;
                          float y = 0;
                          GetParams(partsDelimiter, ref nextLine, 5, ref x, ref y);
                          endPoint.X = (int) x;
                          endPoint.Y = (int) y;
  
                          using (Image image = ImageHttpRequest(nextLine))
                          {
                              if (image != null)
                              {
                                  graph.DrawImage(image, (float)startPoint.X, (float)startPoint.Y, x, y);
                              }
                              else
                              {
                                  using (Font errorFont = new Font(m_fontName,6))
                                  {
                                      graph.DrawString("URL couldn't be resolved or is", errorFont,
                                                       myBrush, startPoint);
                                      graph.DrawString("not an image. Please check URL.", errorFont,
                                                       myBrush, new Point(startPoint.X, 12 + startPoint.Y));
                                  }
      
                                  graph.DrawRectangle(drawPen, startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
                              }
                          }
  
                          startPoint.X += endPoint.X;
                          startPoint.Y += endPoint.Y;
                      }
                      else if (nextLine.StartsWith("Rectangle"))
                      {
                          float x = 0;
                          float y = 0;
                          GetParams(partsDelimiter, ref nextLine, 9, ref x, ref y);
                          endPoint.X = (int) x;
                          endPoint.Y = (int) y;
                          graph.DrawRectangle(drawPen, startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
                          startPoint.X += endPoint.X;
                          startPoint.Y += endPoint.Y;
                      }
                      else if (nextLine.StartsWith("FillRectangle"))
                      {
                          float x = 0;
                          float y = 0;
                          GetParams(partsDelimiter, ref nextLine, 13, ref x, ref y);
                          endPoint.X = (int) x;
                          endPoint.Y = (int) y;
                          graph.FillRectangle(myBrush, startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
                          startPoint.X += endPoint.X;
                          startPoint.Y += endPoint.Y;
                      }
                      else if (nextLine.StartsWith("FillPolygon"))
                      {
                          PointF[] points = null;
                          GetParams(partsDelimiter, ref nextLine, 11, ref points);
                          graph.FillPolygon(myBrush, points);
                      }
                      else if (nextLine.StartsWith("Polygon"))
                      {
                          PointF[] points = null;
                          GetParams(partsDelimiter, ref nextLine, 7, ref points);
                          graph.DrawPolygon(drawPen, points);
                      }
                      else if (nextLine.StartsWith("Ellipse"))
                      {
                          float x = 0;
                          float y = 0;
                          GetParams(partsDelimiter, ref nextLine, 7, ref x, ref y);
                          endPoint.X = (int)x;
                          endPoint.Y = (int)y;
                          graph.DrawEllipse(drawPen, startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
                          startPoint.X += endPoint.X;
                          startPoint.Y += endPoint.Y;
                      }
                      else if (nextLine.StartsWith("FontSize"))
                      {
                          nextLine = nextLine.Remove(0, 8);
                          nextLine = nextLine.Trim();
                          fontSize = Convert.ToSingle(nextLine, CultureInfo.InvariantCulture);
  
                          myFont.Dispose();
                          myFont = new Font(fontName, fontSize);
                      }
                      else if (nextLine.StartsWith("FontProp"))
                      {
                          nextLine = nextLine.Remove(0, 8);
                          nextLine = nextLine.Trim();
      
                          string[] fprops = nextLine.Split(partsDelimiter);
                          foreach (string prop in fprops)
                          {
      
                              switch (prop)
                              {
                                  case "B":
                                      if (!(myFont.Bold))
                                      {
                                          Font newFont = new Font(myFont, myFont.Style | FontStyle.Bold);
                                          myFont.Dispose();
                                          myFont = newFont;
                                      }
                                      break;
                                  case "I":
                                      if (!(myFont.Italic))
                                      {
                                          Font newFont = new Font(myFont, myFont.Style | FontStyle.Italic);
                                          myFont.Dispose();
                                          myFont = newFont;
                                      }
                                      break;
                                  case "U":
                                      if (!(myFont.Underline))
                                      {
                                          Font newFont = new Font(myFont, myFont.Style | FontStyle.Underline);
                                          myFont.Dispose();
                                          myFont = newFont;
                                      }
                                      break;
                                  case "S":
                                      if (!(myFont.Strikeout))
                                      {
                                          Font newFont = new Font(myFont, myFont.Style | FontStyle.Strikeout);
                                          myFont.Dispose();
                                          myFont = newFont;
                                      }
                                      break;
                                  case "R":
                                      // We need to place this newFont inside its own context so that the .NET compiler
                                      // doesn't complain about a redefinition of an existing newFont, even though there is none
                                      // The mono compiler doesn't produce this error.
                                      {
                                          Font newFont = new Font(myFont, FontStyle.Regular);
                                          myFont.Dispose();
                                          myFont = newFont;
                                      }
                                      break;
                              }
                          }
                      }
                      else if (nextLine.StartsWith("FontName"))
                      {
                          nextLine = nextLine.Remove(0, 8);
                          fontName = nextLine.Trim();
                          myFont.Dispose();
                          myFont = new Font(fontName, fontSize);
                      }
                      else if (nextLine.StartsWith("PenSize"))
                      {
                          nextLine = nextLine.Remove(0, 7);
                          nextLine = nextLine.Trim();
                          float size = Convert.ToSingle(nextLine, CultureInfo.InvariantCulture);
                          drawPen.Width = size;
                      }
                      else if (nextLine.StartsWith("PenCap"))
                      {
                          bool start = true, end = true;
                          nextLine = nextLine.Remove(0, 6);
                          nextLine = nextLine.Trim();
                          string[] cap = nextLine.Split(partsDelimiter);
                          if (cap[0].ToLower() == "start")
                              end = false;
                          else if (cap[0].ToLower() == "end")
                              start = false;
                          else if (cap[0].ToLower() != "both")
                              return;
                          string type = cap[1].ToLower();
                          
                          if (end)
                          {
                              switch (type)
                              {
                                  case "arrow":
                                      drawPen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
                                      break;
                                  case "round":
                                      drawPen.EndCap = System.Drawing.Drawing2D.LineCap.RoundAnchor;
                                      break;
                                  case "diamond":
                                      drawPen.EndCap = System.Drawing.Drawing2D.LineCap.DiamondAnchor;
                                      break;
                                  case "flat":
                                      drawPen.EndCap = System.Drawing.Drawing2D.LineCap.Flat;
                                      break;
                              }
                          }
                          if (start)
                          {
                              switch (type)
                              {
                                  case "arrow":
                                      drawPen.StartCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
                                      break;
                                  case "round":
                                      drawPen.StartCap = System.Drawing.Drawing2D.LineCap.RoundAnchor;
                                      break;
                                  case "diamond":
                                      drawPen.StartCap = System.Drawing.Drawing2D.LineCap.DiamondAnchor;
                                      break;
                                  case "flat":
                                      drawPen.StartCap = System.Drawing.Drawing2D.LineCap.Flat;
                                      break;
                              }
                          }
                      }
                      else if (nextLine.StartsWith("PenColour") || nextLine.StartsWith("PenColor"))
                      {
                          nextLine = nextLine.Remove(0, 9);
                          nextLine = nextLine.Trim();
                          int hex = 0;
      
                          Color newColor;
                          if (Int32.TryParse(nextLine, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hex))
                          {
                              newColor = Color.FromArgb(hex);
                          }
                          else
                          {
                              // this doesn't fail, it just returns black if nothing is found
                              newColor = Color.FromName(nextLine);
                          }
  
                          myBrush.Color = newColor;
                          drawPen.Color = newColor;
                      }
                  }
              }
              finally
              {
                  if (drawPen != null)
                      drawPen.Dispose();
  
                  if (myFont != null)
                      myFont.Dispose();
  
                  if (myBrush != null)
                      myBrush.Dispose();
              }
          }
  
          private static void GetParams(char[] partsDelimiter, ref string line, int startLength, ref float x, ref float y)
          {
              line = line.Remove(0, startLength);
              string[] parts = line.Split(partsDelimiter);
              if (parts.Length == 2)
              {
                  string xVal = parts[0].Trim();
                  string yVal = parts[1].Trim();
                  x = Convert.ToSingle(xVal, CultureInfo.InvariantCulture);
                  y = Convert.ToSingle(yVal, CultureInfo.InvariantCulture);
              }
              else if (parts.Length > 2)
              {
                  string xVal = parts[0].Trim();
                  string yVal = parts[1].Trim();
                  x = Convert.ToSingle(xVal, CultureInfo.InvariantCulture);
                  y = Convert.ToSingle(yVal, CultureInfo.InvariantCulture);
  
                  line = "";
                  for (int i = 2; i < parts.Length; i++)
                  {
                      line = line + parts[i].Trim();
                      line = line + " ";
                  }
              }
          }
  
          private static void GetParams(char[] partsDelimiter, ref string line, int startLength, ref PointF[] points)
          {
              line = line.Remove(0, startLength);
              string[] parts = line.Split(partsDelimiter);
              if (parts.Length > 1 && parts.Length % 2 == 0)
              {
  
gt:                 points = new PointF[parts.Length / 2];