<NME> WorldModel.cs
<BEF> using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Threading;
using TextAdventures.Quest.Scripts;
using System.Linq;
using System.Net;
using TextAdventures.Quest.Functions;

namespace TextAdventures.Quest
{
    public enum GameState
    {
        NotStarted,
        Loading,
        Running,
        Finished
    }

    public enum ThreadState
    {
        Ready,
        Working,
        Waiting
    }

    public enum UpdateSource
    {
        System,
        User
    }

    public enum WorldModelVersion
    {
        v500,
        v510,
        v520,
        v530,
        v540,
        v550,
        v580
    }

    public class WorldModel : IASL, IASLDebug, IASLTimer
    {
        private Element m_game;
        private Elements m_elements;
        private Dictionary<string, int> m_nextUniqueID = new Dictionary<string, int>();
        private Template m_template;
        private UndoLogger m_undoLogger;
        private string m_filename;
        private string m_originalFilename;
        private readonly string m_data;
        private string m_libFolder = null;
        private List<string> m_errors;
        private Dictionary<string, ObjectType> m_debuggerObjectTypes = new Dictionary<string, ObjectType>();
        private Dictionary<string, ElementType> m_debuggerElementTypes = new Dictionary<string, ElementType>();
        private GameState m_state = GameState.NotStarted;
        private Dictionary<ElementType, IElementFactory> m_elementFactories = new Dictionary<ElementType, IElementFactory>();
        private ObjectFactory m_objectFactory;
        private GameSaver m_saver;
        private bool m_loadedFromSaved = false;
        private string m_saveFilename = string.Empty;
        private bool m_editMode = false;
        private Functions.ExpressionOwner m_expressionOwner;
        private IPlayer m_playerUI = null;
        private ThreadState m_threadState = ThreadState.Ready;
        private object m_threadLock = new object();
        private Walkthroughs m_walkthroughs;
        private List<string> m_attributeNames = new List<string>();
        private bool m_commandOverride = false;
        private string m_commandOverrideInput;
        private object m_commandOverrideLock = new object();
        private TimerRunner m_timerRunner;
        private RegexCache m_regexCache = new RegexCache();
        private IOutputLogger m_outputLogger;
        private LegacyOutputLogger m_legacyOutputLogger;
        private CallbackManager m_callbacks = new CallbackManager();

        private static Dictionary<ObjectType, string> s_defaultTypeNames = new Dictionary<ObjectType, string>();
        private static Dictionary<string, Type> s_typeNamesToTypes = new Dictionary<string, Type>();
        private static Dictionary<Type, string> s_typesToTypeNames = new Dictionary<Type, string>();

        public event EventHandler<ElementFieldUpdatedEventArgs> ElementFieldUpdated;
        public event EventHandler<ElementRefreshEventArgs> ElementRefreshed;
        public event EventHandler<ElementFieldUpdatedEventArgs> ElementMetaFieldUpdated;
        public event EventHandler<LoadStatusEventArgs> LoadStatus;

        public event Action<int> RequestNextTimerTick;

        public class ElementFieldUpdatedEventArgs : EventArgs
        {
            internal ElementFieldUpdatedEventArgs(Element element, string attribute, object newValue, bool isUndo)
            {
                Element = element;
                Attribute = attribute;
                NewValue = newValue;
                IsUndo = isUndo;
            }

            public Element Element { get; private set; }
            public string Attribute { get; private set; }
            public object NewValue { get; private set; }
            public bool IsUndo { get; private set; }
            public bool Refresh { get; private set; }
        }

        public class ElementRefreshEventArgs : EventArgs
        {
            internal ElementRefreshEventArgs(Element element)
            {
                Element = element;
            }

            public Element Element { get; private set; }
        }

        public class LoadStatusEventArgs : EventArgs
        {
            public LoadStatusEventArgs(string status)
            {
                Status = status;
            }

            public string Status { get; private set; }
        }

        static WorldModel()
        {
            s_defaultTypeNames.Add(ObjectType.Object, "defaultobject");
            s_defaultTypeNames.Add(ObjectType.Exit, "defaultexit");
            s_defaultTypeNames.Add(ObjectType.Command, "defaultcommand");
            s_defaultTypeNames.Add(ObjectType.Game, "defaultgame");
            s_defaultTypeNames.Add(ObjectType.TurnScript, "defaultturnscript");

            s_typeNamesToTypes.Add("string", typeof(string));
            s_typeNamesToTypes.Add("script", typeof(IScript));
            s_typeNamesToTypes.Add("boolean", typeof(bool));
            s_typeNamesToTypes.Add("int", typeof(int));
            s_typeNamesToTypes.Add("double", typeof(double));
            s_typeNamesToTypes.Add("object", typeof(Element));
            s_typeNamesToTypes.Add("stringlist", typeof(QuestList<string>));
            s_typeNamesToTypes.Add("objectlist", typeof(QuestList<Element>));
            s_typeNamesToTypes.Add("stringdictionary", typeof(QuestDictionary<string>));
            s_typeNamesToTypes.Add("objectdictionary", typeof(QuestDictionary<Element>));
            s_typeNamesToTypes.Add("scriptdictionary", typeof(QuestDictionary<IScript>));
            s_typeNamesToTypes.Add("dictionary", typeof(QuestDictionary<object>));
            s_typeNamesToTypes.Add("list", typeof (QuestList<object>));

            foreach (KeyValuePair<string, Type> kvp in s_typeNamesToTypes)
            {
                s_typesToTypeNames.Add(kvp.Value, kvp.Key);
            }
        }

        public WorldModel()
            : this(null, null)
        {
        }

        public WorldModel(string data)
            : this(null, null)
        {
            m_data = data;
        }

        public WorldModel(string filename, string originalFilename)
        {
            m_expressionOwner = new Functions.ExpressionOwner(this);
            m_template = new Template(this);
            InitialiseElementFactories();
            m_objectFactory = (ObjectFactory)m_elementFactories[ElementType.Object];

            InitialiseDebuggerObjectTypes();
            m_filename = filename;
            m_originalFilename = originalFilename;
            m_elements = new Elements();
            m_undoLogger = new UndoLogger(this);
            m_game = ObjectFactory.CreateObject("game", ObjectType.Game);
        }

        public WorldModel(string filename, string libFolder, string originalFilename)
            : this(filename, originalFilename)
        {
            m_libFolder = libFolder;
        }

        private void InitialiseElementFactories()
        {
            foreach (Type t in TextAdventures.Utility.Classes.GetImplementations(System.Reflection.Assembly.GetExecutingAssembly(),
                typeof(IElementFactory)))
            {
                AddElementFactory((IElementFactory)Activator.CreateInstance(t));
            }
        }

        private void AddElementFactory(IElementFactory factory)
        {
            m_elementFactories.Add(factory.CreateElementType, factory);
            factory.WorldModel = this;
            factory.ObjectsUpdated += ElementsUpdated;
        }

        internal static Dictionary<ObjectType, string> DefaultTypeNames
        {
            get { return s_defaultTypeNames; }
        }

        void ElementsUpdated(object sender, ObjectsUpdatedEventArgs args)
        {
            if (ObjectsUpdated != null) ObjectsUpdated(this, args);
        }

        private void InitialiseDebuggerObjectTypes()
        {
            m_debuggerObjectTypes.Add("Objects", ObjectType.Object);
            m_debuggerObjectTypes.Add("Exits", ObjectType.Exit);
            m_debuggerObjectTypes.Add("Commands", ObjectType.Command);
            m_debuggerObjectTypes.Add("Game", ObjectType.Game);
            m_debuggerObjectTypes.Add("Turn Scripts", ObjectType.TurnScript);
            m_debuggerElementTypes.Add("Timers", ElementType.Timer);
        }

        public void FinishGame()
        {
            m_state = GameState.Finished;

            // Pulse all locks in case we're in the middle of waiting for player input for GetInput() etc.

            lock (m_commandOverrideLock)
            {
                Monitor.PulseAll(m_commandOverrideLock);
            }

            lock (m_threadLock)
            {
                Monitor.PulseAll(m_threadLock);
            }

            lock (m_waitForResponseLock)
            {
                Monitor.PulseAll(m_waitForResponseLock);
            }

            if (RequestNextTimerTick != null) RequestNextTimerTick(0);

            if (Finished != null) Finished();
        }

        internal string GetUniqueID()
        {
            return GetUniqueID(null);
        }

        internal string GetUniqueID(string prefix)
        {
            if (string.IsNullOrEmpty(prefix)) prefix = "k";
            if (!m_nextUniqueID.ContainsKey(prefix))
            {
                m_nextUniqueID.Add(prefix, 0);
            }

            string newid;
            do
            {
                m_nextUniqueID[prefix]++;
                newid = prefix + m_nextUniqueID[prefix].ToString();
            } while (m_elements.ContainsKey(newid));
            
            return newid;
        }

        public Element Game
        {
            get { return m_game; }
        }

        public Element Object(string name)
        {
            return m_elements.Get(ElementType.Object, name);
        }

        public ObjectFactory ObjectFactory
        {
            get { return m_objectFactory; }
        }

        public IElementFactory GetElementFactory(ElementType t)
        {
            return m_elementFactories[t];
        }

        public void PrintTemplate(string t)
        {
            Print(m_template.GetText(t));
        }

        public void Print(string text, bool linebreak = true)
        {
            if (Version >= WorldModelVersion.v540 && m_elements.ContainsKey(ElementType.Function, "OutputText"))
            {
                try
                {
                    RunProcedure("OutputText", new Parameters(new Dictionary<string, string> {{"text", text}}), false);
                }
                catch (Exception ex)
                {
                    LogException(ex);
                }
            }
            else
            {
                if (PrintText != null)
                {
                    if (linebreak)
                    {
                        PrintText("<output>" + text + "</output>");
                    }
                    else
                    {
                        PrintText("<output nobr=\"true\">" + text + "</output>");
                    }
                }

                if (m_legacyOutputLogger != null)
                {
                    m_legacyOutputLogger.AddText(text, linebreak);
                }
            }
        }

        internal QuestList<Element> GetAllObjects()
        {
            return new QuestList<Element>(m_elements.Objects);
        }

        internal QuestList<Element> GetObjectsInScope(string scopeFunction)
        {
            if (m_elements.ContainsKey(ElementType.Function, scopeFunction))
            {
                return (QuestList<Element>)RunProcedure(scopeFunction, true);
            }
            throw new Exception(string.Format("No function '{0}'", scopeFunction));
        }

        public bool ObjectContains(Element parent, Element searchObj)
        {
            if (searchObj.Parent == null) return false;
            if (searchObj.Parent == parent) return true;
            return ObjectContains(parent, searchObj.Parent);
        }

        private object m_waitForResponseLock = new object();
        private string m_menuResponse = null;
        private IDictionary<string, string> m_menuOptions = null;

        internal string DisplayMenu(string caption, IDictionary<string, string> options, bool allowCancel, bool async)
        {
            Print(caption);

            MenuData menuData = new MenuData(caption, options, allowCancel);
            m_menuOptions = options;

            m_playerUI.ShowMenu(menuData);

            if (async)
            {
                return string.Empty;
            }

            m_callbacks.Pop(CallbackManager.CallbackTypes.Menu);
            m_menuOptions = null;

            ChangeThreadState(ThreadState.Waiting);

            lock (m_waitForResponseLock)
            {
                Monitor.Wait(m_waitForResponseLock);
            }

            ChangeThreadState(ThreadState.Working);

            return m_menuResponse;
        }

        internal string DisplayMenu(string caption, IList<string> options, bool allowCancel, bool async)
        {
            Dictionary<string, string> optionsDictionary = new Dictionary<string, string>();
            foreach (string option in options)
            {
                optionsDictionary.Add(option, option);
            }
            return DisplayMenu(caption, optionsDictionary, allowCancel, async);
        }

        public void SetMenuResponse(string response)
        {
            Callback menuCallback = m_callbacks.Pop(CallbackManager.CallbackTypes.Menu);
            if (menuCallback != null)
            {
                if (response != null) Print(" - " + m_menuOptions[response]);
                menuCallback.Context.Parameters["result"] = response;
                m_menuOptions = null;
                DoInNewThreadAndWait(() =>
                {
                    RunCallbackAndFinishTurn(menuCallback);
                });
            }
            else
            {
                DoInNewThreadAndWait(() =>
                {
                    m_menuResponse = response;

                    lock (m_waitForResponseLock)
                    {
                        Monitor.Pulse(m_waitForResponseLock);
                    }
                });
            }
        }

        internal void DisplayMenuAsync(string caption, IList<string> options, bool allowCancel, IScript callback, Context c)
        {
            m_callbacks.Push(CallbackManager.CallbackTypes.Menu, new Callback(callback, c), "Only one menu can be shown at a time.");
            DisplayMenu(caption, options, allowCancel, true);
        }

        internal void DisplayMenuAsync(string caption, IDictionary<string, string> options, bool allowCancel, IScript callback, Context c)
        {
            m_callbacks.Push(CallbackManager.CallbackTypes.Menu, new Callback(callback, c), "Only one menu can be shown at a time.");
            DisplayMenu(caption, options, allowCancel, true);
        }

        public IEnumerable<Element> Objects
        {
            get
            {
                foreach (Element o in m_elements.Objects)
                    yield return o;
            }
        }

        public bool ObjectExists(string name)
        {
            return m_elements.ContainsKey(ElementType.Object, name);
        }

        /// <summary>
        /// Attempt to resolve an element name from elements which are eligible for expression,
        /// i.e. objects and timers
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool TryResolveExpressionElement(string name, out Element element)
        {
            element = null;
            if (!m_elements.ContainsKey(name)) return false;

            Element result = m_elements.Get(name);
            if (result.ElemType == ElementType.Object || result.ElemType == ElementType.Timer)
            {
                element = result;
                return true;
            }

            return false;
        }

        internal void RemoveElement(ElementType type, string name)
        {
            m_elements.Remove(type, name);
        }

        internal IEnumerable<Element> ElementTypes
        {
            get { return m_elements.GetElements(ElementType.ObjectType); }
        }

        internal void SetGameName(string name)
        {
            if (m_playerUI != null) m_playerUI.UpdateGameName(name);
        }

        public bool Initialise(IPlayer player, bool? isCompiled = null)
        {
            m_editMode = false;
            m_playerUI = player;
            GameLoader loader = new GameLoader(this, GameLoader.LoadMode.Play, isCompiled);
            bool result = InitialiseInternal(loader);
            if (result)
            {
                m_walkthroughs = new Walkthroughs(this);
            }
            return result;
        }

        public bool InitialiseEdit()
        {
            m_editMode = true;
            GameLoader loader = new GameLoader(this, GameLoader.LoadMode.Edit);
            return InitialiseInternal(loader);
        }

        private bool InitialiseInternal(GameLoader loader)
        {
            if (m_state != GameState.NotStarted)
            {
                throw new Exception("Game already initialised");
            }
            loader.FilenameUpdated += loader_FilenameUpdated;
            loader.LoadStatus += loader_LoadStatus;
            m_state = GameState.Loading;
            
            bool success;
            if (m_data != null)
            {
                success = loader.Load(data: m_data);
            }
            else
            {
                success = m_filename == null || loader.Load(m_filename);    
            }
            
            DebugEnabled = !loader.IsCompiledFile;
            m_state = success ? GameState.Running : GameState.Finished;
            m_errors = loader.Errors;
            m_saver = new GameSaver(this);
            if (Version <= WorldModelVersion.v530)
            {
                m_legacyOutputLogger = new LegacyOutputLogger(this);
                m_outputLogger = m_legacyOutputLogger;
            }
            else
            {
                m_outputLogger = new OutputLogger(this);
            }
            return success;
        }

        void loader_LoadStatus(object sender, GameLoader.LoadStatusEventArgs e)
        {
            if (LoadStatus != null)
            {
                LoadStatus(this, new LoadStatusEventArgs(e.Status));
            }
        }

        void loader_FilenameUpdated(string filename)
        {
            // Update base ASLX filename to original filename if we're loading a saved game
            m_saveFilename = m_filename;
            m_filename = filename;
            m_loadedFromSaved = true;
        }

        public void Begin()
        {
            DoInNewThreadAndWait(() =>
            {
                try
                {
                    m_timerRunner = new TimerRunner(this, !m_loadedFromSaved);
                    if (Version <= WorldModelVersion.v540)
                    {
                        PlayerUI.Show("Panes");
                        PlayerUI.Show("Location");
                        PlayerUI.Show("Command");
                    }
                    if (m_elements.ContainsKey(ElementType.Function, "InitInterface")) RunProcedure("InitInterface");
                    if (!m_loadedFromSaved)
                    {
                        if (m_elements.ContainsKey(ElementType.Function, "StartGame")) RunProcedure("StartGame");
                    }
                    TryRunOnFinallyScripts();
                    UpdateLists();
                    if (m_loadedFromSaved)
                    {
                        var output = Elements.GetSingle(ElementType.Output);
                        if (output == null)
                        {
                            Print("Loaded saved game");
                        }
                        else if (Version >= WorldModelVersion.v540)
                        {
                            PlayerUI.RunScript("loadHtml", new object[] { output.Fields.GetString("html") });
                            PlayerUI.RunScript("markScrollPosition", null);
                            ScrollToEnd();
                        }
                        else if (m_legacyOutputLogger != null)
                        {
                            m_legacyOutputLogger.DisplayOutput(output.Fields.GetString("text"));
                        }
                    }
                    SendNextTimerRequest();
                }
                catch (Exception ex)
                {
                    LogException(ex);
                    Finish();
                }

                ChangeThreadState(ThreadState.Ready);
            });

            SendNextTimerRequest();
        }

        public List<string> Errors
        {
            get { return m_errors; }
        }

        public IWalkthroughs Walkthroughs
        {
            get
            {
                return m_walkthroughs;
            }
        }

        public List<string> DebuggerObjectTypes
        {
            get { return new List<string>(m_debuggerObjectTypes.Keys.Union(m_debuggerElementTypes.Keys)); }
        }

        public void SendCommand(string command, int elapsedTime, IDictionary<string, string> metadata)
        {
            if (elapsedTime > 0)
            {
                m_timerRunner.IncrementTime(elapsedTime);
            }

            DoInNewThreadAndWait(() =>
            {
                if (!m_commandOverride)
                {
                    if (Version < WorldModelVersion.v520)
                    {
                        Print("");
                        Print("> " + Utility.SafeXML(command));
                    }

                    try
                    {
                        RunProcedure("HandleCommand", new Parameters(new Dictionary<string, object>{
                            {"command", command},
                            {"metadata", new QuestDictionary<string>(metadata)}
                        }), false);
                        if (Version < WorldModelVersion.v580)
                        {
                             TryFinishTurn();
                        }
                    }
                    catch (Exception ex)
                    {
                        LogException(ex);
                    }

                    if (State != GameState.Finished)
                    {
                        UpdateLists();
                    }

                    ChangeThreadState(ThreadState.Ready);
                }
                else
                {
                    Callback getinputCallback = m_callbacks.Pop(CallbackManager.CallbackTypes.GetInput);
                    if (getinputCallback != null)
                    {
                        m_commandOverride = false;
                        getinputCallback.Context.Parameters["result"] = command;
                        RunCallbackAndFinishTurn(getinputCallback);
                    }
                    else
                    {
                        m_commandOverrideInput = command;

                        lock (m_commandOverrideLock)
                        {
                            Monitor.Pulse(m_commandOverrideLock);
                        }
                    }
                }
            });

            if (elapsedTime > 0)
            {
                // we increase the timer counter above, before the command has been run,
                // so we pass in 0 here
                Tick(0);
            }
            else
            {
                SendNextTimerRequest();
            }
        }

        public void SendCommand(string command, IDictionary<string, string> metadata)
        {
            SendCommand(command, 0, metadata);
        }

        public void SendCommand(string command)
        {
            SendCommand(command, 0, null);
        }

        public void SendEvent(string eventName, string param)
        {
            Element handler;
            m_elements.TryGetValue(ElementType.Function, eventName, out handler);

            if (handler == null)
            {
                Print(string.Format("Error - no handler for event '{0}'", eventName));
                return;
            }

            Parameters parameters = new Parameters();
            parameters.Add((string)handler.Fields[FieldDefinitions.ParamNames][0], param);

            RunProcedure(eventName, parameters, false);
            if (Version >= WorldModelVersion.v540)
            {
                if (Version < WorldModelVersion.v580)
                {
                     TryFinishTurn();
                }
                if (State != GameState.Finished)
                {
                    UpdateLists();
                }
                SendNextTimerRequest();
            }
        }

        public string Filename
        {
            get { return m_filename; }
        }

        public string SaveFilename
        {
            get { return m_saveFilename; }
        }

        public string BasePath
        {
            get { return System.IO.Path.GetDirectoryName(m_filename); }
        }

        public void Finish()
        {
            FinishGame();
        }

        public string SaveExtension { get { return "quest-save"; } }

        public event PrintTextHandler PrintText;
        public event UpdateListHandler UpdateList;
        public event FinishedHandler Finished;
        public event EventHandler<ObjectsUpdatedEventArgs> ObjectsUpdated;
        public event ErrorHandler LogError;

        internal Template Template
        {
            get { return m_template; }
        }

        public UndoLogger UndoLogger
        {
            get { return m_undoLogger; }
        }

        private void UpdateStatusVariables()
        {
            if (m_elements.ContainsKey(ElementType.Function, "UpdateStatusAttributes"))
            {
                try
                {
                    RunProcedure("UpdateStatusAttributes");
                }
                catch (Exception ex)
                {
                    LogException(ex);
                }
            }
        }

        internal void UpdateLists()
        {
            UpdateObjectsList();
            UpdateExitsList();
            UpdateStatusVariables();
        }

        internal void UpdateObjectsList()
        {
            UpdateObjectsList("GetPlacesObjectsList", ListType.ObjectsList);
            UpdateObjectsList("ScopeInventory", ListType.InventoryList);
        }

        internal void UpdateObjectsList(string scope, ListType listType)
        {
            if (UpdateList != null)
            {
                List<ListData> objects = new List<ListData>();
                foreach (Element obj in GetObjectsInScope(scope))
                {
                    if (Version <= WorldModelVersion.v520 || !m_elements.ContainsKey(ElementType.Function, "GetDisplayVerbs"))
                    {
                        if (scope == "ScopeInventory")
                        {
                            objects.Add(new ListData(GetListDisplayAlias(obj), obj.Fields[FieldDefinitions.InventoryVerbs], obj.Name, GetDisplayAlias(obj)));
                        }
                        else
                        {
                            objects.Add(new ListData(GetListDisplayAlias(obj), obj.Fields[FieldDefinitions.DisplayVerbs], obj.Name, GetDisplayAlias(obj)));
                        }
                    }
                    else
                    {
                        objects.Add(new ListData(GetListDisplayAlias(obj), GetDisplayVerbs(obj), obj.Name, GetDisplayAlias(obj)));
                    }
                }
                // The "Places and Objects" list is generated by function, so we also
                // need to add any exits. (The UI is responsible for filtering out the
                // directional exits so they only display in the compass)
                if (scope == "GetPlacesObjectsList") objects.AddRange(GetExitsListData());
                UpdateList(listType, objects);
            }
        }

        internal void UpdateExitsList()
        {
            if (UpdateList != null)
            {
                UpdateList(ListType.ExitsList, GetExitsListData());
            }
        }

        private string GetListDisplayAlias(Element obj)
        {
            if (m_elements.ContainsKey(ElementType.Function, "GetListDisplayAlias"))
            {
                return (string)RunProcedure("GetListDisplayAlias", new Parameters("obj", obj), true);
            }
            return GetDisplayAlias(obj);
        }

        private string GetDisplayAlias(Element obj)
        {
            if (m_elements.ContainsKey(ElementType.Function, "GetDisplayAlias"))
            {
                return (string)RunProcedure("GetDisplayAlias", new Parameters("obj", obj), true);
            }
            return obj.Name;
        }

        private IEnumerable<string> GetDisplayVerbs(Element obj)
        {
            return (QuestList<string>)RunProcedure("GetDisplayVerbs", new Parameters("object", obj), true);
        }

        private List<ListData> GetExitsListData()
        {
            List<ListData> exits = new List<ListData>();
            var scopeFunction = "ScopeExits";
            if (Version >= WorldModelVersion.v530 && Elements.ContainsKey(ElementType.Function, "GetExitsList"))
            {
                scopeFunction = "GetExitsList";
            }
            foreach (Element exit in GetObjectsInScope(scopeFunction))
            {
                IEnumerable<string> verbs;
                if (Version <= WorldModelVersion.v520 || !m_elements.ContainsKey(ElementType.Function, "GetDisplayVerbs"))
                {
                    verbs = exit.Fields[FieldDefinitions.DisplayVerbs];
                }
                else
                {
                    verbs = GetDisplayVerbs(exit);
                }
                exits.Add(new ListData(GetListDisplayAlias(exit), verbs, exit.Name, GetDisplayAlias(exit)));
            }
            return exits;
        }

        public List<string> GetObjects(string type)
        {
            List<string> result = new List<string>();
            IEnumerable<Element> elements;

            if (m_debuggerObjectTypes.ContainsKey(type))
            {
                ObjectType filterType = m_debuggerObjectTypes[type];
                elements = m_elements.ObjectsFiltered(o => o.Type == filterType);
            }
            else
            {
                ElementType filterType = m_debuggerElementTypes[type];
                elements = m_elements.GetElements(filterType);
            }

            foreach (Element obj in elements)
            {
                result.Add(obj.Name);
            }

            return result;
        }

        public DebugData GetDebugData(string el)
        {
            return m_elements.Get(el).GetDebugData();
        }

        public DebugData GetInheritedTypesDebugData(string el)
        {
            return m_elements.Get(el).Fields.GetInheritedTypesDebugData();
        }

        public DebugDataItem GetDebugDataItem(string el, string attribute)
        {
            return m_elements.Get(el).Fields.GetDebugDataItem(attribute);
        }

        public void StartWait()
        {
            m_callbacks.Pop(CallbackManager.CallbackTypes.Wait);
            m_playerUI.DoWait();

            ChangeThreadState(ThreadState.Waiting);

            lock (m_waitForResponseLock)
            {
                Monitor.Wait(m_waitForResponseLock);
            }

            ChangeThreadState(ThreadState.Working);
        }

        public void StartWaitAsync(IScript callback, Context c)
        {
            m_callbacks.Push(CallbackManager.CallbackTypes.Wait, new Callback(callback, c), "Only one wait can be in progress at a time.");
            m_playerUI.DoWait();
        }

        public void FinishWait()
        {
            Callback waitCallback = m_callbacks.Pop(CallbackManager.CallbackTypes.Wait);
            if (waitCallback != null)
            {
                DoInNewThreadAndWait(() =>
                {
                    RunCallbackAndFinishTurn(waitCallback);
                });
            }
            else
            {
                if (m_state == GameState.Finished) return;
                DoInNewThreadAndWait(() =>
                {
                    lock (m_waitForResponseLock)
                    {
                        Monitor.Pulse(m_waitForResponseLock);
                    }
                });
            }
        }

        public void StartPause(int ms)
        {
            m_playerUI.DoPause(ms);

            ChangeThreadState(ThreadState.Waiting);

            lock (m_waitForResponseLock)
            {
                Monitor.Wait(m_waitForResponseLock);
            }

            ChangeThreadState(ThreadState.Working);
        }

        public void FinishPause()
        {
            DoInNewThreadAndWait(() =>
            {
                lock (m_waitForResponseLock)
                {
                    Monitor.Pulse(m_waitForResponseLock);
                }
            });
        }

        public IEnumerable<string> GetExternalScripts()
        {
            var result = new List<string>();
            foreach (Element jsRef in m_elements.GetElements(ElementType.Javascript))
            {
                if (Version == WorldModelVersion.v500)
                {
                    // v500 games used Frame.js functions for static panel feature. This is now implemented natively
                    // in Player and WebPlayer.
                    if (jsRef.Fields[FieldDefinitions.Src].ToLower() == "frame.js") continue;
                }

                result.Add(jsRef.Fields[FieldDefinitions.Src]);
            }
            return result;
        }

        // TO DO: This could actually be removed now, as we can dynamically load stylesheets. Core.aslx InitInterface
        // should simply be able to use the SetWebFontName function to load game.defaultwebfont
        public IEnumerable<string> GetExternalStylesheets()
        {
            if (Version < WorldModelVersion.v530) return null;

            var webFontsInUse = new List<string>();
            var defaultWebFont = m_game.Fields[FieldDefinitions.DefaultWebFont];
            if (!string.IsNullOrEmpty(defaultWebFont))
            {
                webFontsInUse.Add(defaultWebFont);
            }
            
            var result = webFontsInUse.Select(f => "https://fonts.googleapis.com/css?family=" + f.Replace(' ', '+'));
            
            return result;
        }

        public void RunScript(IScript script)
        {
            RunScript(script, (Parameters)null, false);
        }

        /// <summary>
        /// Use this version of RunScript when executing an object action. Set thisElement to the object whose action it is.
        /// </summary>
        /// <param name="script"></param>
        /// <param name="thisElement"></param>
        public void RunScript(IScript script, Element thisElement)
        {
            RunScript(script, null, false, thisElement);
        }

        public void RunScript(IScript script, Parameters parameters)
        {
            RunScript(script, parameters, false);
        }

        public void RunScript(IScript script, Parameters parameters, Element thisElement)
        {
            RunScript(script, parameters, false, thisElement);
        }

        public object RunDelegateScript(IScript script, Parameters parameters, Element thisElement)
        {
            return RunScript(script, parameters, true, thisElement);
        }

        private object RunScript(IScript script, Parameters parameters, bool expectResult)
        {
            return RunScript(script, parameters, expectResult, null);
        }

        private object RunScript(IScript script, Parameters parameters, bool expectResult, Element thisElement)
        {
            Context c = new Context();
            if (parameters == null) parameters = new Parameters();
            if (thisElement != null) parameters.Add("this", thisElement);
            c.Parameters = parameters;

            return RunScript(script, c, expectResult);
        }

        private void RunScript(IScript script, Context c)
        {
            RunScript(script, c, false);
        }

        private object RunScript(IScript script, Context c, bool expectResult)
        {
            try
            {
                script.Execute(c);
                if (expectResult && c.ReturnValue is NoReturnValue) throw new Exception("Function did not return a value");
                return c.ReturnValue;
            }
            catch (Exception ex)
            {
                Print("Error running script: " + Utility.SafeXML(ex.Message));
            }
            return null;
        }

        public Element AddProcedure(string name)
        {
            Element proc = GetElementFactory(ElementType.Function).Create(name);
            return proc;
        }

        public Element AddProcedure(string name, IScript script, string[] parameters)
        {
            Element proc = AddProcedure(name);
            proc.Fields[FieldDefinitions.Script] = script;
            proc.Fields[FieldDefinitions.ParamNames] = new QuestList<string>(parameters);
            return proc;
        }

        public Element AddDelegate(string name)
        {
            Element del = GetElementFactory(ElementType.Delegate).Create(name);
            return del;
        }

        public void RunProcedure(string name)
        {
            RunProcedure(name, false);
        }

        public object RunProcedure(string name, bool expectResult)
        {
            return RunProcedure(name, null, expectResult);
        }

        public object RunProcedure(string name, Parameters parameters, bool expectResult)
        {
            if (m_elements.ContainsKey(ElementType.Function, name))
            {
                Element function = m_elements.Get(ElementType.Function, name);

                // Only check for too few parameters for games for Quest 5.2 or later, as previous Quest versions
                // would ignore this (but would usually still fail when the function was run, as the required
                // variable wouldn't exist). For Quest 5.3, an additional check if parameters is non-null but empty.

                bool parametersInvalid = false;
                if (Version == WorldModelVersion.v520)
                {
                    parametersInvalid = parameters == null && function.Fields[FieldDefinitions.ParamNames].Count > 0;
                }
                else if (Version >= WorldModelVersion.v530)
                {
                    parametersInvalid = (parameters == null || parameters.Count == 0) && function.Fields[FieldDefinitions.ParamNames].Count > 0;
                }

                if (parametersInvalid)
                {
                    throw new Exception(string.Format("No parameters passed to {0} function - expected {1} parameters",
                            name,
                            function.Fields[FieldDefinitions.ParamNames].Count));
                }

                return RunScript(function.Fields[FieldDefinitions.Script], parameters, expectResult);
            }
            else
            {
                Print(string.Format("Error - no such procedure '{0}'", name));
            }
            return null;
        }

        public Element Procedure(string name)
        {
            if (!m_elements.ContainsKey(ElementType.Function, name)) return null;
            return m_elements.Get(ElementType.Function, name);
        }

        internal Element GetObjectType(string name)
        {
            return m_elements.Get(ElementType.ObjectType, name);
        }

        public GameState State
        {
            get { return m_state; }
        }

        public Elements Elements
        {
            get { return m_elements; }
        }

        public void Save(string filename, string html)
        {
            string saveData = Save(SaveMode.SavedGame, html: html);
            File.WriteAllText(filename, saveData);
        }

        public byte[] Save(string html)
        {
            string saveData = Save(SaveMode.SavedGame, html: html);
            return System.Text.Encoding.UTF8.GetBytes(saveData);
        }

        public string Save(SaveMode mode, bool? includeWalkthrough = null, string html = null)
        {
            return m_saver.Save(mode, includeWalkthrough, html);
        }

        public static Type ConvertTypeNameToType(string name)
        {
            Type type;
            if (s_typeNamesToTypes.TryGetValue(name, out type))
            {
                return type;
            }

            if (name == "null") return null;

            // TO DO: type name could also be a DelegateImplementation
            //if (value is DelegateImplementation) return ((DelegateImplementation)value).TypeName;

            throw new ArgumentOutOfRangeException(string.Format("Unrecognised type name '{0}'", name));
        }

        public static string ConvertTypeToTypeName(Type type)
        {
            string name;
            if (s_typesToTypeNames.TryGetValue(type, out name))
            {
                return name;
            }

            foreach (KeyValuePair<Type, string> kvp in s_typesToTypeNames)
            {
                if (kvp.Key.IsAssignableFrom(type))
                {
                    return kvp.Value;
                }
            }

            throw new ArgumentOutOfRangeException(string.Format("Unrecognised type '{0}'", type.ToString()));
        }

        public string GetExternalPath(string file)
        {
            return GetExternalPath(file, true);
        }

        private string TryGetExternalPath(string file)
        {
            return GetExternalPath(file, false);
        }

        private string GetExternalPath(string file, bool throwException)
        {
            string resourcesFolder = ResourcesFolder ?? Path.GetDirectoryName(Filename);
            return GetExternalPath(resourcesFolder, file, throwException);
        }

        private string GetExternalPath(string current, string file, bool throwException)
        {
            string path;

            if (TryPath(current, file, out path, false)) return path;
            if (ResourcesFolder == null)
            {
                // Only try other folders if we're not using a resource folder (i.e. a .quest file)
                // Because if we do have a resource folder, all required external files should be there.

                if (TryPath(Environment.CurrentDirectory, file, out path, false)) return path;
                if (!string.IsNullOrEmpty(m_libFolder) && TryPath(m_libFolder, file, out path, true)) return path;
                if (System.Reflection.Assembly.GetEntryAssembly() != null)
                {
                    if (TryPath(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().CodeBase), file, out path, true)) return path;
                }
            }
            if (throwException)
            {
                throw new Exception(
                    string.Format("Cannot find a file called '{0}' in current path or application/resource path", file));
            }
            return null;
        }

        internal string GetExternalURL(string file)
        {
            return m_playerUI.GetURL(file);
        }

        public IEnumerable<string> GetAvailableLibraries()
        {
            List<string> result = new List<string>();
            AddFilesInPathToList(result, System.IO.Path.GetDirectoryName(Filename), false);
            AddFilesInPathToList(result, Environment.CurrentDirectory, false);
            if (m_libFolder != null) AddFilesInPathToList(result, m_libFolder, false);
            if (System.Reflection.Assembly.GetEntryAssembly() != null)
            {
                AddFilesInPathToList(result, System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().CodeBase), true);
            }
            return result;
        }

        private void AddFilesInPathToList(List<string> list, string path, bool recurse, string searchPattern = "*.aslx")
        {
            path = TextAdventures.Utility.Utility.RemoveFileColonPrefix(path);
            System.IO.SearchOption option = recurse ? System.IO.SearchOption.AllDirectories : System.IO.SearchOption.TopDirectoryOnly;
            foreach (var result in System.IO.Directory.GetFiles(path, searchPattern, option))
            {
                if (result == Filename) continue;
                string filename = System.IO.Path.GetFileName(result);
                if (!list.Contains(filename)) list.Add(filename);
            }
        }

        public IEnumerable<string> GetAvailableExternalFiles(string searchPatterns)
        {
            List<string> result = new List<string>();
            string[] patterns = searchPatterns.Split(';');
            foreach (string searchPattern in patterns)
            {
                AddFilesInPathToList(result, System.IO.Path.GetDirectoryName(Filename), false, searchPattern);
            }
            return result;
        }

        private bool TryPath(string path, string file, out string fullPath, bool recurse)
        {
            path = TextAdventures.Utility.Utility.RemoveFileColonPrefix(path);
            fullPath = System.IO.Path.Combine(path, file);
            if (System.IO.File.Exists(fullPath))
            {
                return true;
            }
            else
            {
                if (recurse && !file.Contains("\\") && !file.Contains("/"))
                {
                    var results = System.IO.Directory.GetFiles(path, file, System.IO.SearchOption.AllDirectories);
                    if (results.Length > 0)
                    {
                        fullPath = results[0];
                        return true;
                    }
                }
                return false;
            }
        }

        internal void NotifyElementFieldUpdate(Element element, string attribute, object newValue, bool isUndo)
        {
            if (!element.Initialised) return;
            if (ElementFieldUpdated != null) ElementFieldUpdated(this, new ElementFieldUpdatedEventArgs(element, attribute, newValue, isUndo));
        }

        internal void NotifyElementMetaFieldUpdate(Element element, string attribute, object newValue, bool isUndo)
        {
            if (!element.Initialised) return;
            if (ElementMetaFieldUpdated != null) ElementMetaFieldUpdated(this, new ElementFieldUpdatedEventArgs(element, attribute, newValue, isUndo));
        }

        internal void NotifyElementRefreshed(Element element)
        {
            if (ElementRefreshed != null) ElementRefreshed(this, new ElementRefreshEventArgs(element));
        }

        public bool EditMode
        {
            get { return m_editMode; }
        }

        internal Functions.ExpressionOwner ExpressionOwner
        {
            get { return m_expressionOwner; }
        }

        private void ScrollToEnd()
        {
            PlayerUI.RunScript("scrollToEnd", null);
        }

        private void ChangeThreadState(ThreadState newState, bool scroll = true)
        {
            if (scroll && Version >= WorldModelVersion.v540 && (newState == ThreadState.Ready || newState == ThreadState.Waiting))
            {
                ScrollToEnd();
            }

            if (newState == ThreadState.Waiting && m_state == GameState.Finished) throw new Exception("Game is finished");
            m_threadState = newState;
            lock (m_threadLock)
            {
                Monitor.PulseAll(m_threadLock);
            }
        }

        private void WaitUntilFinishedWorking()
        {
            lock (m_threadLock)
            {
                while (m_threadState == ThreadState.Working)
                {
                    Monitor.Wait(m_threadLock);
                }
            }
        }

        private void DoInNewThreadAndWait(Action routine)
        {
            Action wrappedRoutine = () =>
            {
                try
                {
                    routine();
                }
                catch { }
            };

            ChangeThreadState(ThreadState.Working);
            Thread newThread = new Thread(new ThreadStart(wrappedRoutine));
            newThread.Start();
            WaitUntilFinishedWorking();
        }

        void LogException(Exception ex)
        {
            if (LogError != null) LogError(ex.Message + Environment.NewLine + ex.StackTrace);
        }

        internal IPlayer PlayerUI
        {
            get { return m_playerUI; }
        }

        public ElementType GetElementTypeForTypeString(string typeString)
        {
            return Element.GetElementTypeForTypeString(typeString);
        }

        public ObjectType GetObjectTypeForTypeString(string typeString)
        {
            return Element.GetObjectTypeForTypeString(typeString);
        }

        public string GetTypeStringForElementType(ElementType type)
        {
            return Element.GetTypeStringForElementType(type);
        }

        public string GetTypeStringForObjectType(ObjectType type)
        {
            return Element.GetTypeStringForObjectType(type);
        }

        public bool IsDefaultTypeName(string typeName)
        {
            return DefaultTypeNames.ContainsValue(typeName);
        }

        public string OriginalFilename
        {
            get { return m_originalFilename; }
        }

        public Element AddNewTemplate(string templateName)
        {
            return m_template.AddTemplate(templateName, string.Empty, false);
        }

        public Element TryGetTemplateElement(string templateName)
        {
            if (!m_template.TemplateExists(templateName)) return null;
            return m_template.GetTemplateElement(templateName);
        }

        private static System.Text.RegularExpressions.Regex s_removeTrailingDigits = new System.Text.RegularExpressions.Regex(@"\d*$");

        public string GetUniqueElementName(string elementName)
        {
            // If element name doesn't exist (element might have been Cut in the editor),
            // then just return the original name
            if (!Elements.ContainsKey(elementName))
            {
                return elementName;
            }

            // Otherwise get a uniquely numbered element
            string root = s_removeTrailingDigits.Replace(elementName, "");
            bool elementAlreadyExists = true;
            int number = 0;
            string result = null;

            while (elementAlreadyExists)
            {
                number++;
                result = root + number.ToString();
                elementAlreadyExists = Elements.ContainsKey(result);
            }

            return result;
        }

        internal void AddAttributeName(string name)
        {
            if (!m_attributeNames.Contains(name)) m_attributeNames.Add(name);
        }

        public IEnumerable<string> GetAllAttributeNames
        {
            get { return m_attributeNames.AsReadOnly(); }
        }

        internal string GetNextCommandInput(bool async)
        {
            m_commandOverride = true;

            if (async)
            {
                return string.Empty;
            }

            m_callbacks.Pop(CallbackManager.CallbackTypes.GetInput);

            ChangeThreadState(ThreadState.Waiting);

            lock (m_commandOverrideLock)
            {
                Monitor.Wait(m_commandOverrideLock);
            }

            ChangeThreadState(ThreadState.Working);

            m_commandOverride = false;
            return m_commandOverrideInput;
        }

        internal void GetNextCommandInputAsync(IScript callback, Context c)
        {
            m_callbacks.Push(CallbackManager.CallbackTypes.GetInput, new Callback(callback, c), "Only one 'get input' can be in progress at a time");
            GetNextCommandInput(true);
        }

        public void Tick(int elapsedTime)
        {
            if (m_state == GameState.Finished) return;
            DoInNewThreadAndWait(() =>
            {
                try
                {
                    var scripts = m_timerRunner.TickAndGetScripts(elapsedTime);

                    foreach (var timerScript in scripts)
                    {
                        RunScript(timerScript.Value, timerScript.Key);
                    }
                }
                catch (Exception ex)
                {
                    LogException(ex);
                }

                UpdateLists();

                ChangeThreadState(ThreadState.Ready, false);
            });

            SendNextTimerRequest();
        }

        private void SendNextTimerRequest()
        {
            if (m_state == GameState.Finished) return;
            int next = m_timerRunner.GetTimeUntilNextTimerRuns();
            if (RequestNextTimerTick != null) RequestNextTimerTick(next);
            System.Diagnostics.Debug.Print("Request next timer in {0}", next);
        }

        private bool m_questionResponse;

        internal bool ShowQuestion(string caption)
        {
            m_callbacks.Pop(CallbackManager.CallbackTypes.Question);
            m_playerUI.ShowQuestion(caption);

            ChangeThreadState(ThreadState.Waiting);

            lock (m_waitForResponseLock)
            {
                Monitor.Wait(m_waitForResponseLock);
            }

            ChangeThreadState(ThreadState.Working);

            return m_questionResponse;
        }

        internal void ShowQuestionAsync(string caption, IScript callback, Context c)
        {
            m_callbacks.Push(CallbackManager.CallbackTypes.Question, new Callback(callback, c), "Only one question can be asked at a time.");
            m_playerUI.ShowQuestion(caption);
        }

        public void SetQuestionResponse(bool response)
        {
            Callback questionCallback = m_callbacks.Pop(CallbackManager.CallbackTypes.Question);
            if (questionCallback != null)
            {
                questionCallback.Context.Parameters["result"] = response;
                DoInNewThreadAndWait(() =>
                {
                    RunCallbackAndFinishTurn(questionCallback);
                });
            }
            else
            {
                DoInNewThreadAndWait(() =>
                {
                    m_questionResponse = response;

                    lock (m_waitForResponseLock)
                    {
                        Monitor.Pulse(m_waitForResponseLock);
                    }
                });
            }
        }

        private void RunCallbackAndFinishTurn(Callback callback)
        {
            RunScript(callback.Script, callback.Context);
            TryFinishTurn();
            if (State != GameState.Finished)
            {
                UpdateLists();
            }
            ChangeThreadState(ThreadState.Ready);
            SendNextTimerRequest();
        }

        private void TryFinishTurn()
        {
            TryRunOnFinallyScripts();
            if (!m_callbacks.AnyOutstanding())
            {
                if (m_elements.ContainsKey(ElementType.Function, "FinishTurn"))
                {
                    try
                    {
                        RunProcedure("FinishTurn");
                    }
                    catch (Exception ex)
                    {
                        LogException(ex);
                    }
                }
            }
        }

        private void TryRunOnFinallyScripts()
        {
            if (m_callbacks.AnyOutstanding()) return;
            IEnumerable<Callback> onReadyScripts = m_callbacks.FlushOnReadyCallbacks();
            foreach (var callback in onReadyScripts)
            {
                RunScript(callback.Script, callback.Context);
            }
        }

        public class PackageIncludeFile
        {
            public string Filename { get; set; }
            public Stream Content { get; set; }
        }

        public bool CreatePackage(string filename, bool includeWalkthrough, out string error, IEnumerable<PackageIncludeFile> includeFiles, Stream outputStream)
        {
            Packager packager = new Packager(this);
            return packager.CreatePackage(filename, includeWalkthrough, out error, includeFiles, outputStream);
        }

        public string ResourcesFolder { get; internal set; }
        public bool DebugEnabled { get; private set; }

        private static List<string> s_functionNames = null;

        public IEnumerable<string> GetBuiltInFunctionNames()
        {
            if (s_functionNames == null)
            {
                System.Reflection.MethodInfo[] methods = typeof(ExpressionOwner).GetMethods();
                System.Reflection.MethodInfo[] stringMethods = typeof(StringFunctions).GetMethods();

                IEnumerable<System.Reflection.MethodInfo> allMethods = methods.Union(stringMethods);

                s_functionNames = new List<string>(allMethods.Select(m => m.Name));
            }

            return s_functionNames.AsReadOnly();
        }

        public void PlaySound(string filename, bool sync, bool looped)
        {
            m_playerUI.PlaySound(filename, sync, looped);
            if (sync)
            {
                ChangeThreadState(ThreadState.Waiting);

                lock (m_waitForResponseLock)
                {
                    Monitor.Wait(m_waitForResponseLock);
                }
            }
        }

        internal void UpdateElementSortOrder(Element movedElement)
        {
            // There's no need to worry about element sort order when playing the game, unless this is an element that can be seen by the
            // player
            if (!EditMode)
            return new FileStream(GetExternalPath(filename), FileMode.Open, FileAccess.Read);
        }

        public string GetResourcePath(string filename)
        {
            return TryGetExternalPath(filename);
            // is at the end of the list of children.

            int maxIndex = -1;

            foreach (Element sibling in m_elements.GetDirectChildren(movedElement.Parent))
            {
                int thisSortIndex = sibling.MetaFields[MetaFieldDefinitions.SortIndex];
                if (thisSortIndex > maxIndex) maxIndex = thisSortIndex;
            }

            movedElement.MetaFields[MetaFieldDefinitions.SortIndex] = maxIndex + 1;
        }

        public bool Assert(string expr)
        {
            Expression<bool> expression = new Expression<bool>(expr, new ScriptContext(this));
            Context c = new Context();
            return expression.Execute(c);
        }

        internal void AddOnReady(IScript callback, Context c)
        {
            if (!m_callbacks.AnyOutstanding())
            {
                RunScript(callback, c);
            }
            else
            {
                m_callbacks.AddOnReadyCallback(new Callback(callback, c));
            }
        }

        public Stream GetResource(string filename)
        {
            string path = TryGetExternalPath(filename);
            if (path == null) return null;
            return new FileStream(GetExternalPath(filename), FileMode.Open, FileAccess.Read);
        }

        public string GetResourceData(string filename)
        {
            if (Config.ReadGameFileFromAzureBlob)
            {
                var url = GetExternalURL(filename);
                using (var client = new WebClient())
                {
                    client.Encoding = System.Text.Encoding.UTF8;
                    return client.DownloadString(url);
                }
            }

            return File.ReadAllText(GetExternalPath(filename));
        }

        public string GetResourceExternalData(string filename)
        {
            if (!IsUrlValid(filename))
            {
                return "";
            }
            using (var client = new WebClient())
            {
                client.Encoding = System.Text.Encoding.UTF8;
                return client.DownloadString(filename);
            }
        }

        public string SetResourceExternalData(string filename, String content)
        {
            if (!IsUrlValid(filename))
            {
                return "";
            }
            using (var client = new WebClient())
            {
                var reqparm = new System.Collections.Specialized.NameValueCollection();
                reqparm.Add("param", content);
                byte[] responsebytes = client.UploadValues(filename, "POST", reqparm);
                return System.Text.Encoding.UTF8.GetString(responsebytes);
            }
        }

        private static bool IsUrlValid(string uriName)
        {
            Uri uriResult;
            return Uri.TryCreate(uriName, UriKind.Absolute, out uriResult) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
        }

        public string GetResourcePath(string filename)
        {
            return TryGetExternalPath(filename);
        }

        internal RegexCache RegexCache { get { return m_regexCache; } }

        ~WorldModel()
        {
            if (ResourcesFolder != null && System.IO.Directory.Exists(ResourcesFolder))
            {
                try
                {
                    System.IO.Directory.Delete(ResourcesFolder, true);
                }
                catch { }
            }
        }

        public WorldModelVersion Version { get; internal set; }

        internal string VersionString { get; set; }

        public string TempFolder { get; set; }

        internal IOutputLogger OutputLogger { get { return m_outputLogger; } }

        public int ASLVersion { get { return int.Parse(VersionString); } }

        public string GameID
        {
            get
            {
                string gameId = m_game.Fields[FieldDefinitions.GameID];
                if (gameId != null) return gameId;
                if (Config.ReadGameFileFromAzureBlob)
                {
                    var parts = m_filename.Split('/');
                    return parts[parts.Length - 2];
                }
                return TextAdventures.Utility.Utility.FileMD5Hash(m_filename);
            }
        }
        public string Category { get { return m_game.Fields[FieldDefinitions.Category]; } }
        public string Description { get { return m_game.Fields[FieldDefinitions.Description]; } }
        public string Cover { get { return m_game.Fields[FieldDefinitions.Cover]; } }
    }
}

<MSG> Implementing support for drawing SVG graphics to custom drawing layer

<DFF> @@ -1714,6 +1714,11 @@ namespace TextAdventures.Quest
             return new FileStream(GetExternalPath(filename), FileMode.Open, FileAccess.Read);
         }
 
+        public string GetResourceData(string filename)
+        {
+            return File.ReadAllText(GetExternalPath(filename));
+        }
+
         public string GetResourcePath(string filename)
         {
             return TryGetExternalPath(filename);

{'commit': 'diff --git a/PlayerController/grid.js b/PlayerController/grid.js\nindex 3eeccf3d..b1655146 100644\n--- a/PlayerController/grid.js\n+++ b/PlayerController/grid.js\n@@ -8,6 +8,7 @@ var layers = new Array();\n var maxLayer = 3;\r\n var currentLayer = 0;\r\n var offset = new Point(0, 0);\r\n+var symbols = new Object();\r\n \r\n for (var i = -maxLayer; i <= maxLayer; i++) {\r\n     var layer = new Layer();\r\n@@ -346,4 +347,18 @@ gridApi.drawCustomLayerObject = function (id, points, text, textPoint, border, f\n     customLayerObjects[id] = paths;\r\n };\r\n \r\n+gridApi.drawCustomLayerSvg = function (symbol, x, y, width, height) {\r\n+    if (symbol in symbols) {\r\n+        var placedSymbol = symbols[symbol].place();\r\n+        placedSymbol.position = gridPoint(x, y);\r\n+        // TODO: Resize\r\n+    } else {\r\n+        var svg = paper.project.importSVG(symbol);\r\n+        if (svg) {\r\n+            symbols[symbol] = new Symbol(svg);\r\n+            gridApi.drawCustomLayerSvg(symbol, x, y, width, height);\r\n+        }\r\n+    }\r\n+};\r\n+\r\n gridApi.onLoad();\n\\ No newline at end of file\ndiff --git a/PlayerController/paper.js b/PlayerController/paper.js\nindex 65b6b2a9..7cc44ff1 100644\n--- a/PlayerController/paper.js\n+++ b/PlayerController/paper.js\n@@ -1,5 +1,5 @@\n /*!\n- * Paper.js v0.9.9 - The Swiss Army Knife of Vector Graphics Scripting.\n+ * Paper.js v0.9.10 - The Swiss Army Knife of Vector Graphics Scripting.\n  * http://paperjs.org/\n  *\n  * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey\n@@ -9,7 +9,7 @@\n  *\n  * All rights reserved.\n  *\n- * Date: Sun Jul 21 16:44:30 2013 -0700\n+ * Date: Wed Oct 30 16:19:10 2013 +0100\n  *\n  ***\n  *\n@@ -30,7 +30,7 @@\n  *\n  */\n \n-var paper = new function() {\n+var paper = new function(undefined) {\n \n var Base = new function() {\n \tvar hidden = /^(statics|generics|preserve|enumerable|prototype|toString|valueOf)$/,\n@@ -105,7 +105,8 @@ var Base = new function() {\n \t\t\t\tif (isFunc && beans && val.length === 0\n \t\t\t\t\t\t&& (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))\n \t\t\t\t\tbeans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);\n-\t\t\t\tif (!res || isFunc || !res.get)\n+\t\t\t\tif (!res || isFunc || !res.get || typeof res.get !== \'function\'\n+\t\t\t\t\t\t|| res.get.length !== 0)\n \t\t\t\t\tres = { value: res, writable: true };\n \t\t\t\tif ((describe(dest, name)\n \t\t\t\t\t\t|| { configurable: true }).configurable) {\n@@ -128,15 +129,14 @@ var Base = new function() {\n \t\t\t\t\tfield(name, null, true, generics);\n \t\t\tfield(\'toString\');\n \t\t\tfield(\'valueOf\');\n-\t\t\tfor (var i = 0, l = beans && beans.length; i < l; i++)\n-\t\t\t\ttry {\n-\t\t\t\t\tvar bean = beans[i],\n-\t\t\t\t\t\tpart = bean[1];\n-\t\t\t\t\tfield(bean[0], {\n-\t\t\t\t\t\tget: dest[\'get\' + part] || dest[\'is\' + part],\n-\t\t\t\t\t\tset: dest[\'set\' + part]\n-\t\t\t\t\t}, true);\n-\t\t\t\t} catch (e) {}\n+\t\t\tfor (var i = 0, l = beans.length; i < l; i++) {\n+\t\t\t\tvar bean = beans[i],\n+\t\t\t\t\tpart = bean[1];\n+\t\t\t\tfield(bean[0], {\n+\t\t\t\t\tget: dest[\'get\' + part] || dest[\'is\' + part],\n+\t\t\t\t\tset: dest[\'set\' + part]\n+\t\t\t\t}, true);\n+\t\t\t}\n \t\t}\n \t\treturn dest;\n \t}\n@@ -213,23 +213,16 @@ var Base = new function() {\n \t\tstatics: {\n \t\t\teach: each,\n \t\t\tclone: clone,\n+\t\t\tcreate: create,\n \t\t\tdefine: define,\n \t\t\tdescribe: describe,\n \n-\t\t\tcreate: function(ctor) {\n-\t\t\t\treturn create(ctor.prototype);\n-\t\t\t},\n-\n \t\t\tisPlainObject: function(obj) {\n \t\t\t\tvar ctor = obj != null && obj.constructor;\n \t\t\t\treturn ctor && (ctor === Object || ctor === Base\n \t\t\t\t\t\t|| ctor.name === \'Object\');\n \t\t\t},\n \n-\t\t\tcheck: function(obj) {\n-\t\t\t\treturn !!(obj || obj === 0);\n-\t\t\t},\n-\n \t\t\tpick: function() {\n \t\t\t\tfor (var i = 0, l = arguments.length; i < l; i++)\n \t\t\t\t\tif (arguments[i] !== undefined)\n@@ -277,10 +270,15 @@ Base.inject({\n \n \t_set: function(props, exclude) {\n \t\tif (props && Base.isPlainObject(props)) {\n-\t\t\tfor (var key in props)\n-\t\t\t\tif (props.hasOwnProperty(key) && key in this\n-\t\t\t\t\t\t&& (!exclude || !exclude[key]))\n-\t\t\t\t\tthis[key] = props[key];\n+\t\t\tvar orig = props._filtering || props;\n+\t\t\tfor (var key in orig) {\n+\t\t\t\tif (key in this && orig.hasOwnProperty(key)\n+\t\t\t\t\t\t&& (!exclude || !exclude[key])) {\n+\t\t\t\t\tvar value = props[key];\n+\t\t\t\t\tif (value !== undefined)\n+\t\t\t\t\t\tthis[key] = value;\n+\t\t\t\t}\n+\t\t\t}\n \t\t\treturn true;\n \t\t}\n \t},\n@@ -300,7 +298,7 @@ Base.inject({\n \t\tequals: function(obj1, obj2) {\n \t\t\tfunction checkKeys(o1, o2) {\n \t\t\t\tfor (var i in o1)\n-\t\t\t\t\tif (o1.hasOwnProperty(i) && typeof o2[i] === \'undefined\')\n+\t\t\t\t\tif (o1.hasOwnProperty(i) && !o2.hasOwnProperty(i))\n \t\t\t\t\t\treturn false;\n \t\t\t\treturn true;\n \t\t\t}\n@@ -351,7 +349,7 @@ Base.inject({\n \t\t\t\t\tlist._index = index + 1;\n \t\t\t\treturn obj && options && options.clone ? obj.clone() : obj;\n \t\t\t}\n-\t\t\tobj = Base.create(this);\n+\t\t\tobj = Base.create(this.prototype);\n \t\t\tif (readIndex)\n \t\t\t\tobj.__read = true;\n \t\t\tif (options)\n@@ -384,9 +382,17 @@ Base.inject({\n \t\t},\n \n \t\treadNamed: function(list, name, start, length, options) {\n-\t\t\tvar value = this.getNamed(list, name);\n-\t\t\treturn this.read(value != null ? [value] : list, start, length,\n-\t\t\t\t\toptions);\n+\t\t\tvar value = this.getNamed(list, name),\n+\t\t\t\thasObject = value !== undefined;\n+\t\t\tif (hasObject) {\n+\t\t\t\tvar filtered = list._filtered;\n+\t\t\t\tif (!filtered) {\n+\t\t\t\t\tfiltered = list._filtered = Base.create(list[0]);\n+\t\t\t\t\tfiltered._filtering = list[0];\n+\t\t\t\t}\n+\t\t\t\tfiltered[name] = undefined;\n+\t\t\t}\n+\t\t\treturn this.read(hasObject ? [value] : list, start, length, options);\n \t\t},\n \n \t\tgetNamed: function(list, name) {\n@@ -394,7 +400,7 @@ Base.inject({\n \t\t\tif (list._hasObject === undefined)\n \t\t\t\tlist._hasObject = list.length === 1 && Base.isPlainObject(arg);\n \t\t\tif (list._hasObject)\n-\t\t\t\treturn name ? arg[name] : arg;\n+\t\t\t\treturn name ? arg[name] : list._filtered || arg;\n \t\t},\n \n \t\thasNamed: function(list, name) {\n@@ -478,7 +484,7 @@ Base.inject({\n \t\t\t\t\tdata.dictionary = res[0];\n \t\t\t\t} else if (type) {\n \t\t\t\t\tvar args = res;\n-\t\t\t\t\tres = Base.create(type);\n+\t\t\t\t\tres = Base.create(type.prototype);\n \t\t\t\t\ttype.apply(res, args);\n \t\t\t\t}\n \t\t\t} else if (Base.isPlainObject(obj)) {\n@@ -676,7 +682,7 @@ var PaperScope = Base.extend({\n \t\t}\n \t},\n \n-\tversion: \'0.9.9\',\n+\tversion: \'0.9.10\',\n \n \tgetView: function() {\n \t\treturn this.project && this.project.view;\n@@ -705,7 +711,7 @@ var PaperScope = Base.extend({\n \t\t\t});\n \t\t});\n \t\tfor (var key in this) {\n-\t\t\tif (!/^(version|_id)/.test(key) && !(key in scope))\n+\t\t\tif (!/^(version|_id)/.test(key))\n \t\t\t\tscope[key] = this[key];\n \t\t}\n \t},\n@@ -818,7 +824,7 @@ var Formatter = Base.extend({\n \t}\n });\n \n-Formatter.instance = new Formatter(5);\n+Formatter.instance = new Formatter();\n \n var Numerical = new function() {\n \n@@ -870,7 +876,7 @@ var Numerical = new function() {\n \t\tKAPPA: 4 * (sqrt(2) - 1) / 3,\n \n \t\tisZero: function(val) {\n-\t\t\treturn abs(val) <= this.EPSILON;\n+\t\t\treturn abs(val) <= Numerical.EPSILON;\n \t\t},\n \n \t\tintegrate: function(f, a, b, n) {\n@@ -905,31 +911,48 @@ var Numerical = new function() {\n \t\t\t}\n \t\t},\n \n-\t\tsolveQuadratic: function(a, b, c, roots) {\n+\t\tsolveQuadratic: function(a, b, c, roots, min, max) {\n+\t\t\tvar unbound = min === undefined,\n+\t\t\t\tcount = 0;\n+\n+\t\t\tfunction add(root) {\n+\t\t\t\tif (unbound || root >= min && root <= max)\n+\t\t\t\t\troots[count++] = root;\n+\t\t\t\treturn count;\n+\t\t\t}\n+\n \t\t\tvar epsilon = this.EPSILON;\n \t\t\tif (abs(a) < epsilon) {\n-\t\t\t\tif (abs(b) >= epsilon) {\n-\t\t\t\t\troots[0] = -c / b;\n-\t\t\t\t\treturn 1;\n-\t\t\t\t}\n+\t\t\t\tif (abs(b) >= epsilon)\n+\t\t\t\t\treturn add(-c / b);\n \t\t\t\treturn abs(c) < epsilon ? -1 : 0; \n \t\t\t}\n-\t\t\tvar q = b * b - 4 * a * c;\n-\t\t\tif (q < 0)\n-\t\t\t\treturn 0; \n-\t\t\tq = sqrt(q);\n-\t\t\ta *= 2; \n-\t\t\tvar n = 0;\n-\t\t\troots[n++] = (-b - q) / a;\n-\t\t\tif (q > 0)\n-\t\t\t\troots[n++] = (-b + q) / a;\n-\t\t\treturn n; \n+\t\t\tvar p = b / (2 * a);\n+\t\t\tvar q = c / a;\n+\t\t\tvar p2 = p * p;\n+\t\t\tif (p2 < q - epsilon)\n+\t\t\t\treturn 0;\n+\t\t\tvar s = p2 > q ? sqrt(p2 - q) : 0;\n+\t\t\tadd (s - p);\n+\t\t\tif (s > 0)\n+\t\t\t\tadd(-s - p);\n+\t\t\treturn count;\n \t\t},\n \n-\t\tsolveCubic: function(a, b, c, d, roots) {\n+\t\tsolveCubic: function(a, b, c, d, roots, min, max) {\n \t\t\tvar epsilon = this.EPSILON;\n \t\t\tif (abs(a) < epsilon)\n-\t\t\t\treturn Numerical.solveQuadratic(b, c, d, roots);\n+\t\t\t\treturn Numerical.solveQuadratic(b, c, d, roots, min, max);\n+\n+\t\t\tvar unbound = min === undefined,\n+\t\t\t\tcount = 0;\n+\n+\t\t\tfunction add(root) {\n+\t\t\t\tif (unbound || root >= min && root <= max)\n+\t\t\t\t\troots[count++] = root;\n+\t\t\t\treturn count;\n+\t\t\t}\n+\n \t\t\tb /= a;\n \t\t\tc /= a;\n \t\t\td /= a;\n@@ -940,29 +963,24 @@ var Numerical = new function() {\n \t\t\t\tD = q * q - ppp;\n \t\t\tb /= 3;\n \t\t\tif (abs(D) < epsilon) {\n-\t\t\t\tif (abs(q) < epsilon) { \n-\t\t\t\t\troots[0] = - b;\n-\t\t\t\t\treturn 1;\n-\t\t\t\t} \n+\t\t\t\tif (abs(q) < epsilon) \n+\t\t\t\t\treturn add(-b);\n \t\t\t\tvar sqp = sqrt(p),\n \t\t\t\t\tsnq = q > 0 ? 1 : -1;\n-\t\t\t\troots[0] = -snq * 2 * sqp - b;\n-\t\t\t\troots[1] = snq * sqp - b;\n-\t\t\t\treturn 2;\n+\t\t\t\tadd(-snq * 2 * sqp - b);\n+\t\t\t\treturn add(snq * sqp - b);\n \t\t\t}\n \t\t\tif (D < 0) { \n \t\t\t\tvar sqp = sqrt(p),\n \t\t\t\t\tphi = Math.acos(q / (sqp * sqp * sqp)) / 3,\n \t\t\t\t\tt = -2 * sqp,\n \t\t\t\t\to = 2 * PI / 3;\n-\t\t\t\troots[0] = t * cos(phi) - b;\n-\t\t\t\troots[1] = t * cos(phi + o) - b;\n-\t\t\t\troots[2] = t * cos(phi - o) - b;\n-\t\t\t\treturn 3;\n+\t\t\t\tadd(t * cos(phi) - b);\n+\t\t\t\tadd(t * cos(phi + o) - b);\n+\t\t\t\treturn add(t * cos(phi - o) - b);\n \t\t\t}\n \t\t\tvar A = (q > 0 ? -1 : 1) * pow(abs(q) + sqrt(D), 1 / 3);\n-\t\t\troots[0] = A + p / A - b;\n-\t\t\treturn 1;\n+\t\t\treturn add(A + p / A - b);\n \t\t}\n \t};\n };\n@@ -1031,8 +1049,7 @@ var Point = Base.extend({\n \n \t_serialize: function(options) {\n \t\tvar f = options.formatter;\n-\t\treturn [f.number(this.x),\n-\t\t\t\tf.number(this.y)];\n+\t\treturn [f.number(this.x), f.number(this.y)];\n \t},\n \n \tadd: function(point) {\n@@ -1522,7 +1539,7 @@ var Rectangle = Base.extend({\n \t\t\trect = Rectangle.read(arguments);\n \t\treturn rect === this\n \t\t\t\t|| rect && this.x === rect.x && this.y === rect.y\n-\t\t\t\t\t&& this.width === rect.width && this.height=== rect.height\n+\t\t\t\t\t&& this.width === rect.width && this.height === rect.height\n \t\t\t\t|| false;\n \t},\n \n@@ -1829,9 +1846,9 @@ var Matrix = Base.extend({\n \tinitialize: function Matrix(arg) {\n \t\tvar count = arguments.length,\n \t\t\tok = true;\n-\t\tif (count == 6) {\n+\t\tif (count === 6) {\n \t\t\tthis.set.apply(this, arguments);\n-\t\t} else if (count == 1) {\n+\t\t} else if (count === 1) {\n \t\t\tif (arg instanceof Matrix) {\n \t\t\t\tthis.set(arg._a, arg._c, arg._b, arg._d, arg._tx, arg._ty);\n \t\t\t} else if (Array.isArray(arg)) {\n@@ -1839,7 +1856,7 @@ var Matrix = Base.extend({\n \t\t\t} else {\n \t\t\t\tok = false;\n \t\t\t}\n-\t\t} else if (count == 0) {\n+\t\t} else if (count === 0) {\n \t\t\tthis.reset();\n \t\t} else {\n \t\t\tok = false;\n@@ -1868,9 +1885,9 @@ var Matrix = Base.extend({\n \t},\n \n \tequals: function(mx) {\n-\t\treturn mx === this || mx && this._a == mx._a && this._b == mx._b\n-\t\t\t\t&& this._c == mx._c && this._d == mx._d && this._tx == mx._tx\n-\t\t\t\t&& this._ty == mx._ty\n+\t\treturn mx === this || mx && this._a === mx._a && this._b === mx._b\n+\t\t\t\t&& this._c === mx._c && this._d === mx._d\n+\t\t\t\t&& this._tx === mx._tx && this._ty === mx._ty\n \t\t\t\t|| false;\n \t},\n \n@@ -1949,19 +1966,6 @@ var Matrix = Base.extend({\n \t\treturn this;\n \t},\n \n-\tisIdentity: function() {\n-\t\treturn this._a == 1 && this._c == 0 && this._b == 0 && this._d == 1\n-\t\t\t\t&& this._tx == 0 && this._ty == 0;\n-\t},\n-\n-\tisInvertible: function() {\n-\t\treturn !!this._getDeterminant();\n-\t},\n-\n-\tisSingular: function() {\n-\t\treturn !this._getDeterminant();\n-\t},\n-\n \tconcatenate: function(mx) {\n \t\tvar a = this._a,\n \t\t\tb = this._b,\n@@ -1992,6 +1996,19 @@ var Matrix = Base.extend({\n \t\treturn this;\n \t},\n \n+\tisIdentity: function() {\n+\t\treturn this._a == 1 && this._c == 0 && this._b == 0 && this._d == 1\n+\t\t\t\t&& this._tx == 0 && this._ty == 0;\n+\t},\n+\n+\tisInvertible: function() {\n+\t\treturn !!this._getDeterminant();\n+\t},\n+\n+\tisSingular: function() {\n+\t\treturn !this._getDeterminant();\n+\t},\n+\n \ttransform: function( src, srcOff, dst, dstOff, numPts) {\n \t\treturn arguments.length < 5\n \t\t\t? this._transformPoint(Point.read(arguments))\n@@ -2325,32 +2342,36 @@ var Project = PaperScopeItem.extend({\n \t\tvar items = [];\n \t\tfor (var id in this._selectedItems) {\n \t\t\tvar item = this._selectedItems[id];\n-\t\t\tif (item._drawCount === this._drawCount)\n+\t\t\tif (item.isInserted())\n \t\t\t\titems.push(item);\n \t\t}\n \t\treturn items;\n \t},\n \n \t_updateSelection: function(item) {\n+\t\tvar id = item._id,\n+\t\t\tselectedItems = this._selectedItems;\n \t\tif (item._selected) {\n-\t\t\tthis._selectedItemCount++;\n-\t\t\tthis._selectedItems[item._id] = item;\n-\t\t\tif (item.isInserted())\n-\t\t\t\titem._drawCount = this._drawCount;\n-\t\t} else {\n+\t\t\tif (selectedItems[id] !== item) {\n+\t\t\t\tthis._selectedItemCount++;\n+\t\t\t\tselectedItems[id] = item;\n+\t\t\t}\n+\t\t} else if (selectedItems[id] === item) {\n \t\t\tthis._selectedItemCount--;\n-\t\t\tdelete this._selectedItems[item._id];\n+\t\t\tdelete selectedItems[id];\n \t\t}\n \t},\n \n \tselectAll: function() {\n-\t\tfor (var i = 0, l = this.layers.length; i < l; i++)\n-\t\t\tthis.layers[i].setSelected(true);\n+\t\tvar layers = this.layers;\n+\t\tfor (var i = 0, l = layers.length; i < l; i++)\n+\t\t\tlayers[i].setFullySelected(true);\n \t},\n \n \tdeselectAll: function() {\n-\t\tfor (var i in this._selectedItems)\n-\t\t\tthis._selectedItems[i].setSelected(false);\n+\t\tvar selectedItems = this._selectedItems;\n+\t\tfor (var i in selectedItems)\n+\t\t\tselectedItems[i].setFullySelected(false);\n \t},\n \n \thitTest: function(point, options) {\n@@ -2361,7 +2382,31 @@ var Project = PaperScopeItem.extend({\n \t\t\tif (res) return res;\n \t\t}\n \t\treturn null;\n-\t},\n+\t}\n+}, new function() {\n+\tfunction getItems(project, match, list) {\n+\t\tvar layers = project.layers,\n+\t\t\titems = list && [];\n+\t\tfor (var i = 0, l = layers.length; i < l; i++) {\n+\t\t\tvar res = layers[i][list ? \'getItems\' : \'getItem\'](match);\n+\t\t\tif (list) {\n+\t\t\t\titems.push.apply(items, res);\n+\t\t\t} else if (res)\n+\t\t\t\treturn res;\n+\t\t}\n+\t\treturn list ? items : null;\n+\t}\n+\n+\treturn {\n+\t\tgetItems: function(match) {\n+\t\t\treturn getItems(this, match, true);\n+\t\t},\n+\n+\t\tgetItem: function(match) {\n+\t\t\treturn getItems(this, match, false);\n+\t\t}\n+\t};\n+}, {\n \n \timportJSON: function(json) {\n \t\tthis.activate();\n@@ -2374,7 +2419,8 @@ var Project = PaperScopeItem.extend({\n \t\tmatrix.applyToContext(ctx);\n \t\tvar param = Base.merge({\n \t\t\toffset: new Point(0, 0),\n-\t\t\ttransforms: [matrix]\n+\t\t\ttransforms: [matrix],\n+\t\t\ttrackTransforms: true\n \t\t});\n \t\tfor (var i = 0, l = this.layers.length; i < l; i++)\n \t\t\tthis.layers[i].draw(ctx, param);\n@@ -2494,6 +2540,7 @@ var Item = Base.extend(Callback, {\n \t\tblendMode: \'normal\',\n \t\topacity: 1,\n \t\tguide: false,\n+\t\tselected: false,\n \t\tclipMask: false,\n \t\tdata: {}\n \t},\n@@ -2587,9 +2634,11 @@ var Item = Base.extend(Callback, {\n \t\tfunction serialize(fields) {\n \t\t\tfor (var key in fields) {\n \t\t\t\tvar value = that[key];\n-\t\t\t\tif (!Base.equals(value, fields[key]))\n+\t\t\t\tif (!Base.equals(value, key === \'leading\'\n+\t\t\t\t\t\t? fields.fontSize * 1.2 : fields[key])) {\n \t\t\t\t\tprops[key] = Base.serialize(value, options,\n \t\t\t\t\t\t\tkey !== \'data\', dictionary);\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n@@ -2603,6 +2652,7 @@ var Item = Base.extend(Callback, {\n \t\tvar parent = this._parent,\n \t\t\tproject = this._project,\n \t\t\tsymbol = this._parentSymbol;\n+\t\tthis._drawCount = null;\n \t\tif (flags & 4) {\n \t\t\tdelete this._bounds;\n \t\t\tdelete this._position;\n@@ -2654,7 +2704,10 @@ var Item = Base.extend(Callback, {\n \tsetName: function(name, unique) {\n \n \t\tif (this._name)\n-\t\t\tthis._removeFromNamed();\n+\t\t\tthis._removeNamed();\n+\t\tif (name === (+name) + \'\')\n+\t\t\tthrow new Error(\n+\t\t\t\t\t\'Names consisting only of numbers are not supported.\');\n \t\tif (name && this._parent) {\n \t\t\tvar children = this._parent._children,\n \t\t\t\tnamedChildren = this._parent._namedChildren,\n@@ -2678,12 +2731,15 @@ var Item = Base.extend(Callback, {\n \t},\n \n \thasFill: function() {\n-\t\treturn !!this.getStyle().getFillColor();\n+\t\treturn this.getStyle().hasFill();\n \t},\n \n \thasStroke: function() {\n-\t\tvar style = this.getStyle();\n-\t\treturn !!style.getStrokeColor() && style.getStrokeWidth() > 0;\n+\t\treturn this.getStyle().hasStroke();\n+\t},\n+\n+\thasShadow: function() {\n+\t\treturn this.getStyle().hasShadow();\n \t}\n }, Base.each([\'locked\', \'visible\', \'blendMode\', \'opacity\', \'guide\'],\n \tfunction(name) {\n@@ -2801,8 +2857,23 @@ var Item = Base.extend(Callback, {\n \t\tthis._changed(5);\n \t},\n \n+\tgetGlobalMatrix: function() {\n+\t\treturn this._drawCount === this._project._drawCount\n+\t\t\t\t&& this._globalMatrix || null;\n+\t},\n+\n+\tglobalToLocal: function() {\n+\t\tvar matrix = this.getGlobalMatrix();\n+\t\treturn matrix && matrix._transformPoint(Point.read(arguments));\n+\t},\n+\n+\tlocalToGlobal: function() {\n+\t\tvar matrix = this.getGlobalMatrix();\n+\t\treturn matrix && matrix._inverseTransform(Point.read(arguments));\n+\t},\n+\n \tisEmpty: function() {\n-\t\treturn this._children.length == 0;\n+\t\treturn !this._children || this._children.length == 0;\n \t}\n }, Base.each([\'getBounds\', \'getStrokeBounds\', \'getHandleBounds\', \'getRoughBounds\'],\n \tfunction(name) {\n@@ -2965,6 +3036,24 @@ var Item = Base.extend(Callback, {\n \t\treturn this._parent ? this._parent.isInserted() : false;\n \t},\n \n+\tequals: function(item) {\n+\t\treturn item === this || item && this._class === item._class\n+\t\t\t\t&& this._style.equals(item._style) \n+\t\t\t\t&& this._matrix.equals(item._matrix)\n+\t\t\t\t&& this._locked === item._locked\n+\t\t\t\t&& this._visible === item._visible\n+\t\t\t\t&& this._blendMode === item._blendMode\n+\t\t\t\t&& this._opacity === item._opacity\n+\t\t\t\t&& this._clipMask === item._clipMask\n+\t\t\t\t&& this._guide === item._guide\n+\t\t\t\t&& this._equals(item)\n+\t\t\t\t|| false;\n+\t},\n+\n+\t_equals: function(item) {\n+\t\treturn Base.equals(this._children, item._children);\n+\t},\n+\n \tclone: function(insert) {\n \t\treturn this._clone(new this.constructor({ insert: false }), insert);\n \t},\n@@ -3013,9 +3102,13 @@ var Item = Base.extend(Callback, {\n \t\tctx.save();\n \t\tmatrix.applyToContext(ctx);\n \t\tthis.draw(ctx, Base.merge({ transforms: [matrix] }));\n-\t\tvar raster = new Raster(canvas);\n-\t\traster.setPosition(topLeft.add(size.divide(2)));\n \t\tctx.restore();\n+\t\tvar raster = new Raster({\n+\t\t\tcanvas: canvas,\n+\t\t\tinsert: false\n+\t\t});\n+\t\traster.setPosition(topLeft.add(size.divide(2)));\n+\t\traster.insertAbove(this);\n \t\treturn raster;\n \t},\n \n@@ -3043,7 +3136,7 @@ var Item = Base.extend(Callback, {\n \t\t\treturn null;\n \n \t\tif (!this._children && !this.getRoughBounds()\n-\t\t\t\t.expand(options.tolerance)._containsPoint(point))\n+\t\t\t\t.expand(2 * options.tolerance)._containsPoint(point))\n \t\t\treturn null;\n \t\tpoint = this._matrix._inverseTransform(point);\n \n@@ -3081,15 +3174,86 @@ var Item = Base.extend(Callback, {\n \t},\n \n \t_hitTest: function(point, options) {\n-\t\tif (this._children) {\n-\t\t\tfor (var i = this._children.length - 1, res; i >= 0; i--)\n-\t\t\t\tif (res = this._children[i].hitTest(point, options))\n+\t\tvar children = this._children;\n+\t\tif (children) {\n+\t\t\tfor (var i = children.length - 1, res; i >= 0; i--)\n+\t\t\t\tif (res = children[i].hitTest(point, options))\n \t\t\t\t\treturn res;\n \t\t} else if (options.fill && this.hasFill() && this._contains(point)) {\n \t\t\treturn new HitResult(\'fill\', this);\n \t\t}\n \t},\n \n+\tmatches: function(match) {\n+\t\tfunction matchObject(obj1, obj2) {\n+\t\t\tfor (var i in obj1) {\n+\t\t\t\tif (obj1.hasOwnProperty(i)) {\n+\t\t\t\t\tvar val1 = obj1[i],\n+\t\t\t\t\t\tval2 = obj2[i];\n+\t\t\t\t\tif (Base.isPlainObject(val1) && Base.isPlainObject(val2)) {\n+\t\t\t\t\t\tif (!matchObject(val1, val2))\n+\t\t\t\t\t\t\treturn false;\n+\t\t\t\t\t} else if (!Base.equals(val1, val2)) {\n+\t\t\t\t\t\treturn false;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\treturn true;\n+\t\t}\n+\t\tfor (var key in match) {\n+\t\t\tif (match.hasOwnProperty(key)) {\n+\t\t\t\tvar value = this[key],\n+\t\t\t\t\tcompare = match[key];\n+\t\t\t\tif (compare instanceof RegExp) {\n+\t\t\t\t\tif (!compare.test(value))\n+\t\t\t\t\t\treturn false;\n+\t\t\t\t} else if (typeof compare === \'function\') {\n+\t\t\t\t\tif (!compare(value))\n+\t\t\t\t\t\treturn false;\n+\t\t\t\t} else if (Base.isPlainObject(compare)) {\n+\t\t\t\t\tif (!matchObject(compare, value))\n+\t\t\t\t\t\treturn false;\n+\t\t\t\t} else if (!Base.equals(value, compare)) {\n+\t\t\t\t\treturn false;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\treturn true;\n+\t}\n+}, new function() {\n+\tfunction getItems(item, match, list) {\n+\t\tvar children = item._children,\n+\t\t\titems = list && [];\n+\t\tfor (var i = 0, l = children && children.length; i < l; i++) {\n+\t\t\tvar child = children[i];\n+\t\t\tif (child.matches(match)) {\n+\t\t\t\tif (list) {\n+\t\t\t\t\titems.push(child);\n+\t\t\t\t} else {\n+\t\t\t\t\treturn child;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tvar res = getItems(child, match, list);\n+\t\t\tif (list) {\n+\t\t\t\titems.push.apply(items, res);\n+\t\t\t} else if (res) {\n+\t\t\t\treturn res;\n+\t\t\t}\n+\t\t}\n+\t\treturn list ? items : null;\n+\t}\n+\n+\treturn {\n+\t\tgetItems: function(match) {\n+\t\t\treturn getItems(this, match, true);\n+\t\t},\n+\n+\t\tgetItem: function(match) {\n+\t\t\treturn getItems(this, match, false);\n+\t\t}\n+\t};\n+}, {\n+\n \timportJSON: function(json) {\n \t\treturn this.addChild(Base.importJSON(json));\n \t},\n@@ -3168,7 +3332,17 @@ var Item = Base.extend(Callback, {\n \n \tmoveBelow: \'#insertBelow\',\n \n-\t_removeFromNamed: function() {\n+\treduce: function() {\n+\t\tif (this._children && this._children.length === 1) {\n+\t\t\tvar child = this._children[0];\n+\t\t\tchild.insertAbove(this);\n+\t\t\tthis.remove();\n+\t\t\treturn child;\n+\t\t}\n+\t\treturn this;\n+\t},\n+\n+\t_removeNamed: function() {\n \t\tvar children = this._parent._children,\n \t\t\tnamedChildren = this._parent._namedChildren,\n \t\t\tname = this._name,\n@@ -3189,7 +3363,7 @@ var Item = Base.extend(Callback, {\n \t_remove: function(notify) {\n \t\tif (this._parent) {\n \t\t\tif (this._name)\n-\t\t\t\tthis._removeFromNamed();\n+\t\t\t\tthis._removeNamed();\n \t\t\tif (this._index != null)\n \t\t\t\tBase.splice(this._parent._children, null, this._index, 1);\n \t\t\tif (notify)\n@@ -3391,43 +3565,50 @@ var Item = Base.extend(Callback, {\n \n \t_setStyles: function(ctx) {\n \t\tvar style = this._style,\n-\t\t\twidth = style.getStrokeWidth(),\n-\t\t\tjoin = style.getStrokeJoin(),\n-\t\t\tcap = style.getStrokeCap(),\n-\t\t\tlimit = style.getMiterLimit(),\n+\t\t\tmatrix = this._matrix,\n \t\t\tfillColor = style.getFillColor(),\n \t\t\tstrokeColor = style.getStrokeColor(),\n \t\t\tshadowColor = style.getShadowColor();\n-\t\tif (width != null)\n-\t\t\tctx.lineWidth = width;\n-\t\tif (join)\n-\t\t\tctx.lineJoin = join;\n-\t\tif (cap)\n-\t\t\tctx.lineCap = cap;\n-\t\tif (limit)\n-\t\t\tctx.miterLimit = limit;\n \t\tif (fillColor)\n \t\t\tctx.fillStyle = fillColor.toCanvasStyle(ctx);\n \t\tif (strokeColor) {\n-\t\t\tctx.strokeStyle = strokeColor.toCanvasStyle(ctx);\n-\t\t\tvar dashArray = style.getDashArray(),\n-\t\t\t\tdashOffset = style.getDashOffset();\n-\t\t\tif (paper.support.nativeDash && dashArray && dashArray.length) {\n-\t\t\t\tif (\'setLineDash\' in ctx) {\n-\t\t\t\t\tctx.setLineDash(dashArray);\n-\t\t\t\t\tctx.lineDashOffset = dashOffset;\n-\t\t\t\t} else {\n-\t\t\t\t\tctx.mozDash = dashArray;\n-\t\t\t\t\tctx.mozDashOffset = dashOffset;\n+\t\t\tvar strokeWidth = style.getStrokeWidth();\n+\t\t\tif (strokeWidth > 0) {\n+\t\t\t\tctx.strokeStyle = strokeColor.toCanvasStyle(ctx);\n+\t\t\t\tctx.lineWidth = strokeWidth;\n+\t\t\t\tvar strokeJoin = style.getStrokeJoin(),\n+\t\t\t\t\tstrokeCap = style.getStrokeCap(),\n+\t\t\t\t\tmiterLimit = style.getMiterLimit();\n+\t\t\t\tif (strokeJoin)\n+\t\t\t\t\tctx.lineJoin = strokeJoin;\n+\t\t\t\tif (strokeCap)\n+\t\t\t\t\tctx.lineCap = strokeCap;\n+\t\t\t\tif (miterLimit)\n+\t\t\t\t\tctx.miterLimit = miterLimit;\n+\t\t\t\tif (paper.support.nativeDash) {\n+\t\t\t\t\tvar dashArray = style.getDashArray(),\n+\t\t\t\t\t\tdashOffset = style.getDashOffset();\n+\t\t\t\t\tif (dashArray && dashArray.length) {\n+\t\t\t\t\t\tif (\'setLineDash\' in ctx) {\n+\t\t\t\t\t\t\tctx.setLineDash(dashArray);\n+\t\t\t\t\t\t\tctx.lineDashOffset = dashOffset;\n+\t\t\t\t\t\t} else {\n+\t\t\t\t\t\t\tctx.mozDash = dashArray;\n+\t\t\t\t\t\t\tctx.mozDashOffset = dashOffset;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n \t\t\t\t}\n \t\t\t}\n \t\t}\n \t\tif (shadowColor) {\n-\t\t\tctx.shadowColor = shadowColor.toCanvasStyle(ctx);\n-\t\t\tctx.shadowBlur = style.getShadowBlur();\n-\t\t\tvar offset = this.getShadowOffset();\n-\t\t\tctx.shadowOffsetX = offset.x;\n-\t\t\tctx.shadowOffsetY = offset.y;\n+\t\t\tvar shadowBlur = style.getShadowBlur();\n+\t\t\tif (shadowBlur > 0) {\n+\t\t\t\tctx.shadowColor = shadowColor.toCanvasStyle(ctx);\n+\t\t\t\tctx.shadowBlur = shadowBlur;\n+\t\t\t\tvar offset = this.getShadowOffset();\n+\t\t\t\tctx.shadowOffsetX = offset.x;\n+\t\t\t\tctx.shadowOffsetY = offset.y;\n+\t\t\t}\n \t\t}\n \t},\n \n@@ -3435,10 +3616,12 @@ var Item = Base.extend(Callback, {\n \t\tif (!this._visible || this._opacity === 0)\n \t\t\treturn;\n \t\tthis._drawCount = this._project._drawCount;\n-\t\tvar transforms = param.transforms,\n+\t\tvar trackTransforms = param.trackTransforms,\n+\t\t\ttransforms = param.transforms,\n \t\t\tparentMatrix = transforms[transforms.length - 1],\n \t\t\tglobalMatrix = parentMatrix.clone().concatenate(this._matrix);\n-\t\ttransforms.push(this._globalMatrix = globalMatrix);\n+\t\tif (trackTransforms)\n+\t\t\ttransforms.push(this._globalMatrix = globalMatrix);\n \t\tvar blendMode = this._blendMode,\n \t\t\topacity = this._opacity,\n \t\t\tnormalBlend = blendMode === \'normal\',\n@@ -3470,7 +3653,8 @@ var Item = Base.extend(Callback, {\n \t\t\tparam.clipItem.draw(ctx, param.extend({ clip: true }));\n \t\tthis._draw(ctx, param);\n \t\tctx.restore();\n-\t\ttransforms.pop();\n+\t\tif (trackTransforms)\n+\t\t\ttransforms.pop();\n \t\tif (param.clip)\n \t\t\tctx.clip();\n \t\tif (!direct) {\n@@ -3630,11 +3814,30 @@ var Layer = Group.extend({\n var Shape = Item.extend({\n \t_class: \'Shape\',\n \t_transformContent: false,\n+\t_boundsSelected: true,\n \n-\tinitialize: function Shape(type, point, size, props) {\n-\t\tthis._initialize(props, point);\n-\t\tthis._type = type;\n+\tinitialize: function Shape(shape, center, size, radius, props) {\n+\t\tthis._shape = shape;\n \t\tthis._size = size;\n+\t\tthis._radius = radius;\n+\t\tthis._initialize(props, center);\n+\t},\n+\n+\t_equals: function(item) {\n+\t\treturn this._shape === item._shape\n+\t\t\t&& this._size.equals(item._size)\n+\t\t\t&& Base.equals(this._radius, item._radius);\n+\t},\n+\n+\tclone: function(insert) {\n+\t\treturn this._clone(new Shape(this._shape, this.getPosition(true),\n+\t\t\t\tthis._size.clone(),\n+\t\t\t\tthis._radius.clone ? this._radius.clone() : this._radius,\n+\t\t\t\t{ insert: false }), insert);\n+\t},\n+\n+\tgetShape: function() {\n+\t\treturn this._shape;\n \t},\n \n \tgetSize: function() {\n@@ -3643,58 +3846,127 @@ var Shape = Item.extend({\n \t},\n \n \tsetSize: function() {\n-\t\tvar size = Size.read(arguments);\n+\t\tvar shape = this._shape,\n+\t\t\tsize = Size.read(arguments);\n \t\tif (!this._size.equals(size)) {\n-\t\t\tthis._size.set(size.width, size.height);\n+\t\t\tvar width = size.width,\n+\t\t\t\theight = size.height;\n+\t\t\tif (shape === \'rectangle\') {\n+\t\t\t\tvar radius = Size.min(this._radius, size.divide(2));\n+\t\t\t\tthis._radius.set(radius.width, radius.height);\n+\t\t\t} else if (shape === \'circle\') {\n+\t\t\t\twidth = height = (width + height) / 2;\n+\t\t\t\tthis._radius = width / 2;\n+\t\t\t} else if (shape === \'ellipse\') {\n+\t\t\t\tthis._radius.set(width / 2, height / 2);\n+\t\t\t}\n+\t\t\tthis._size.set(width, height);\n \t\t\tthis._changed(5);\n \t\t}\n \t},\n \n \tgetRadius: function() {\n-\t\tvar size = this._size;\n-\t\treturn (size.width + size.height) / 4;\n+\t\tvar rad = this._radius;\n+\t\treturn this._shape === \'circle\'\n+\t\t\t\t? rad\n+\t\t\t\t: new LinkedSize(rad.width, rad.height, this, \'setRadius\');\n \t},\n \n \tsetRadius: function(radius) {\n-\t\tvar size = radius * 2;\n-\t\tthis.setSize(size, size);\n+\t\tvar shape = this._shape;\n+\t\tif (shape === \'circle\') {\n+\t\t\tif (radius === this._radius)\n+\t\t\t\treturn;\n+\t\t\tvar size = radius * 2;\n+\t\t\tthis._size.set(size, size);\n+\t\t} else {\n+\t\t\tradius = Size.read(arguments);\n+\t\t\tif (this._radius.equals(radius))\n+\t\t\t\treturn;\n+\t\t\tthis._radius.set(radius.width, radius.height);\n+\t\t\tif (shape === \'rectangle\') {\n+\t\t\t\tvar size = Size.max(this._size, radius.multiply(2));\n+\t\t\t\tthis._size.set(size.width, size.height);\n+\t\t\t} else if (shape === \'ellipse\') {\n+\t\t\t\tthis._size.set(radius.width * 2, radius.height * 2);\n+\t\t\t}\n+\t\t}\n+\t\tthis._changed(5);\n+\t},\n+\n+\tisEmpty: function() {\n+\t\treturn false;\n+\t},\n+\n+\ttoPath: function(insert) {\n+\t\tvar path = new Path[Base.capitalize(this._shape)]({\n+\t\t\tcenter: new Point(),\n+\t\t\tsize: this._size,\n+\t\t\tradius: this._radius,\n+\t\t\tinsert: false\n+\t\t});\n+\t\tpath.setStyle(this._style);\n+\t\tpath.transform(this._matrix);\n+\t\tif (insert || insert === undefined)\n+\t\t\tpath.insertAbove(this);\n+\t\treturn path;\n \t},\n \n \t_draw: function(ctx, param) {\n \t\tvar style = this._style,\n-\t\t\tsize = this._size,\n-\t\t\twidth = size.width,\n-\t\t\theight = size.height,\n-\t\t\tfillColor = style.getFillColor(),\n-\t\t\tstrokeColor = style.getStrokeColor();\n-\t\tif (fillColor || strokeColor || param.clip) {\n+\t\t\thasFill = style.hasFill(),\n+\t\t\thasStroke = style.hasStroke(),\n+\t\t\tclip = param.clip;\n+\t\tif (hasFill || hasStroke || clip) {\n+\t\t\tvar radius = this._radius,\n+\t\t\t\tshape = this._shape;\n \t\t\tctx.beginPath();\n-\t\t\tswitch (this._type) {\n-\t\t\tcase \'rect\':\n-\t\t\t\tctx.rect(-width / 2, -height / 2, width, height);\n-\t\t\t\tbreak;\n-\t\t\tcase \'circle\':\n-\t\t\t\tctx.arc(0, 0, (width + height) / 4, 0, Math.PI * 2, true);\n-\t\t\t\tbreak;\n-\t\t\tcase \'ellipse\':\n-\t\t\t\tvar mx = width / 2,\n-\t\t\t\t\tmy = height / 2,\n-\t\t\t\t\tkappa = Numerical.KAPPA,\n-\t\t\t\t\tcx = mx * kappa,\n-\t\t\t\t\tcy = my * kappa;\n-\t\t\t\tctx.moveTo(-mx, 0);\n-\t\t\t\tctx.bezierCurveTo(-mx, -cy, -cx, -my, 0, -my);\n-\t\t\t\tctx.bezierCurveTo(cx, -my, mx, -cy, mx, 0);\n-\t\t\t\tctx.bezierCurveTo(mx, cy, cx, my, 0, my);\n-\t\t\t\tctx.bezierCurveTo(-cx, my, -mx, cy, -mx, 0);\n-\t\t\t\tbreak;\n+\t\t\tif (shape === \'circle\') {\n+\t\t\t\tctx.arc(0, 0, radius, 0, Math.PI * 2, true);\n+\t\t\t} else {\n+\t\t\t\tvar rx = radius.width,\n+\t\t\t\t\try = radius.height,\n+\t\t\t\t\tkappa = Numerical.KAPPA;\n+\t\t\t\tif (shape === \'ellipse\') {\n+\t\t\t\t\tvar\tcx = rx * kappa,\n+\t\t\t\t\t\tcy = ry * kappa;\n+\t\t\t\t\tctx.moveTo(-rx, 0);\n+\t\t\t\t\tctx.bezierCurveTo(-rx, -cy, -cx, -ry, 0, -ry);\n+\t\t\t\t\tctx.bezierCurveTo(cx, -ry, rx, -cy, rx, 0);\n+\t\t\t\t\tctx.bezierCurveTo(rx, cy, cx, ry, 0, ry);\n+\t\t\t\t\tctx.bezierCurveTo(-cx, ry, -rx, cy, -rx, 0);\n+\t\t\t\t} else { \n+\t\t\t\t\tvar size = this._size,\n+\t\t\t\t\t\twidth = size.width,\n+\t\t\t\t\t\theight = size.height;\n+\t\t\t\t\tif (rx === 0 && ry === 0) {\n+\t\t\t\t\t\tctx.rect(-width / 2, -height / 2, width, height);\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tkappa = 1 - kappa;\n+\t\t\t\t\t\tvar x = width / 2,\n+\t\t\t\t\t\t\ty = height / 2,\n+\t\t\t\t\t\t\tcx = rx * kappa,\n+\t\t\t\t\t\t\tcy = ry * kappa;\n+\t\t\t\t\t\tctx.moveTo(-x, -y + ry);\n+\t\t\t\t\t\tctx.bezierCurveTo(-x, -y + cy, -x + cx, -y, -x + rx, -y);\n+\t\t\t\t\t\tctx.lineTo(x - rx, -y);\n+\t\t\t\t\t\tctx.bezierCurveTo(x - cx, -y, x, -y + cy, x, -y + ry);\n+\t\t\t\t\t\tctx.lineTo(x, y - ry);\n+\t\t\t\t\t\tctx.bezierCurveTo(x, y - cy, x - cx, y, x - rx, y);\n+\t\t\t\t\t\tctx.lineTo(-x + rx, y);\n+\t\t\t\t\t\tctx.bezierCurveTo(-x + cx, y, -x, y - cy, -x, y - ry);\n+\t\t\t\t\t}\n+\t\t\t\t}\n \t\t\t}\n+\t\t\tctx.closePath();\n \t\t}\n-\t\tif (!param.clip && (fillColor || strokeColor)) {\n+\t\tif (!clip && (hasFill || hasStroke)) {\n \t\t\tthis._setStyles(ctx);\n-\t\t\tif (fillColor)\n-\t\t\t\tctx.fill();\n-\t\t\tif (strokeColor)\n+\t\t\tif (hasFill) {\n+\t\t\t\tctx.fill(style.getWindingRule());\n+\t\t\t\tctx.shadowColor = \'rgba(0,0,0,0)\';\n+\t\t\t}\n+\t\t\tif (hasStroke)\n \t\t\t\tctx.stroke();\n \t\t}\n \t},\n@@ -3708,79 +3980,123 @@ var Shape = Item.extend({\n \t\tif (getter !== \'getBounds\' && this.hasStroke())\n \t\t\trect = rect.expand(this.getStrokeWidth());\n \t\treturn matrix ? matrix._transformBounds(rect) : rect;\n-\t},\n+\t}\n+},\n+new function() { \n \n-\t_contains: function _contains(point) {\n-\t\tswitch (this._type) {\n-\t\tcase \'rect\':\n-\t\t\treturn _contains.base.call(this, point);\n-\t\tcase \'circle\':\n-\t\tcase \'ellipse\':\n-\t\t\treturn point.divide(this._size).getLength() <= 0.5;\n+\tfunction getCornerCenter(that, point, expand) {\n+\t\tvar radius = that._radius;\n+\t\tif (!radius.isZero()) {\n+\t\t\tvar halfSize = that._size.divide(2);\n+\t\t\tfor (var i = 0; i < 4; i++) {\n+\t\t\t\tvar dir = new Point(i & 1 ? 1 : -1, i > 1 ? 1 : -1),\n+\t\t\t\t\tcorner = dir.multiply(halfSize),\n+\t\t\t\t\tcenter = corner.subtract(dir.multiply(radius)),\n+\t\t\t\t\trect = new Rectangle(corner, center);\n+\t\t\t\tif ((expand ? rect.expand(expand) : rect).contains(point))\n+\t\t\t\t\treturn center;\n+\t\t\t}\n \t\t}\n-\t},\n+\t}\n \n-\t_hitTest: function _hitTest(point) {\n-\t\tif (this.hasStroke()) {\n-\t\t\tvar type = this._type,\n-\t\t\t\tstrokeWidth = this.getStrokeWidth();\n-\t\t\tswitch (type) {\n-\t\t\tcase \'rect\':\n-\t\t\t\tvar rect = new Rectangle(this._size).setCenter(0, 0),\n-\t\t\t\t\touter = rect.expand(strokeWidth),\n-\t\t\t\t\tinner = rect.expand(-strokeWidth);\n-\t\t\t\tif (outer._containsPoint(point) && !inner._containsPoint(point))\n-\t\t\t\t\treturn new HitResult(\'stroke\', this);\n-\t\t\t\tbreak;\n-\t\t\tcase \'circle\':\n-\t\t\tcase \'ellipse\':\n-\t\t\t\tvar size = this._size,\n-\t\t\t\t\twidth = size.width,\n-\t\t\t\t\theight = size.height,\n-\t\t\t\t\tradius;\n-\t\t\t\tif (type === \'ellipse\') {\n-\t\t\t\t\tvar angle = point.getAngleInRadians(),\n-\t\t\t\t\t\tx = width * Math.sin(angle),\n-\t\t\t\t\t\ty = height * Math.cos(angle);\n-\t\t\t\t\tradius = width * height / (2 * Math.sqrt(x * x + y * y));\n+\tfunction getEllipseRadius(point, radius) {\n+\t\tvar angle = point.getAngleInRadians(),\n+\t\t\twidth = radius.width * 2,\n+\t\t\theight = radius.height * 2,\n+\t\t\tx = width * Math.sin(angle),\n+\t\t\ty = height * Math.cos(angle);\n+\t\treturn width * height / (2 * Math.sqrt(x * x + y * y));\n+\t}\n+\n+\treturn {\n+\t\t_contains: function _contains(point) {\n+\t\t\tif (this._shape === \'rectangle\') {\n+\t\t\t\tvar center = getCornerCenter(this, point);\n+\t\t\t\treturn center\n+\t\t\t\t\t\t? point.subtract(center).divide(this._radius)\n+\t\t\t\t\t\t\t.getLength() <= 1\n+\t\t\t\t\t\t: _contains.base.call(this, point);\n+\t\t\t} else {\n+\t\t\t\treturn point.divide(this.size).getLength() <= 0.5;\n+\t\t\t}\n+\t\t},\n+\n+\t\t_hitTest: function _hitTest(point, options) {\n+\t\t\tvar hit = false;\n+\t\t\tif (this.hasStroke()) {\n+\t\t\t\tvar shape = this._shape,\n+\t\t\t\t\tradius = this._radius,\n+\t\t\t\t\tstrokeWidth = this.getStrokeWidth() + 2 * options.tolerance;\n+\t\t\t\tif (shape === \'rectangle\') {\n+\t\t\t\t\tvar center = getCornerCenter(this, point, strokeWidth);\n+\t\t\t\t\tif (center) {\n+\t\t\t\t\t\tvar pt = point.subtract(center);\n+\t\t\t\t\t\thit = 2 * Math.abs(pt.getLength()\n+\t\t\t\t\t\t\t\t- getEllipseRadius(pt, radius)) <= strokeWidth;\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tvar rect = new Rectangle(this._size).setCenter(0, 0),\n+\t\t\t\t\t\t\touter = rect.expand(strokeWidth),\n+\t\t\t\t\t\t\tinner = rect.expand(-strokeWidth);\n+\t\t\t\t\t\thit = outer._containsPoint(point)\n+\t\t\t\t\t\t\t\t&& !inner._containsPoint(point);\n+\t\t\t\t\t}\n \t\t\t\t} else {\n-\t\t\t\t\tradius = (width + height) / 4;\n+\t\t\t\t\tif (shape === \'ellipse\')\n+\t\t\t\t\t\tradius = getEllipseRadius(point, radius);\n+\t\t\t\t\thit = 2 * Math.abs(point.getLength() - radius)\n+\t\t\t\t\t\t\t<= strokeWidth;\n \t\t\t\t}\n-\t\t\t\tif (2 * Math.abs(point.getLength() - radius) <= strokeWidth)\n-\t\t\t\t\treturn new HitResult(\'stroke\', this);\n-\t\t\t\tbreak;\n \t\t\t}\n+\t\t\treturn hit\n+\t\t\t\t\t? new HitResult(\'stroke\', this)\n+\t\t\t\t\t: _hitTest.base.apply(this, arguments);\n \t\t}\n-\t\treturn _hitTest.base.apply(this, arguments);\n-\t},\n+\t};\n+}, {\n \n-\tstatics: new function() {\n-\t\tfunction createShape(type, point, size, args) {\n-\t\t\treturn new Shape(type, point, size, Base.getNamed(args));\n-\t\t}\n+statics: new function() {\n+\tfunction createShape(shape, point, size, radius, args) {\n+\t\treturn new Shape(shape, point, size, radius, Base.getNamed(args));\n+\t}\n \n-\t\treturn {\n-\t\t\tCircle: function() {\n-\t\t\t\tvar center = Point.readNamed(arguments, \'center\'),\n-\t\t\t\t\tradius = Base.readNamed(arguments, \'radius\');\n-\t\t\t\treturn createShape(\'circle\', center, new Size(radius * 2),\n-\t\t\t\t\t\targuments);\n-\t\t\t},\n+\treturn {\n+\t\tCircle: function() {\n+\t\t\tvar center = Point.readNamed(arguments, \'center\'),\n+\t\t\t\tradius = Base.readNamed(arguments, \'radius\');\n+\t\t\treturn createShape(\'circle\', center, new Size(radius * 2), radius,\n+\t\t\t\t\targuments);\n+\t\t},\n \n-\t\t\tRectangle: function() {\n-\t\t\t\tvar rect = Rectangle.readNamed(arguments, \'rectangle\');\n-\t\t\t\treturn createShape(\'rect\', rect.getCenter(true),\n-\t\t\t\t\t\trect.getSize(true), arguments);\n-\t\t\t},\n+\t\tRectangle: function() {\n+\t\t\tvar rect = Rectangle.readNamed(arguments, \'rectangle\'),\n+\t\t\t\tradius = Size.min(Size.readNamed(arguments, \'radius\'),\n+\t\t\t\t\t\trect.getSize(true).divide(2));\n+\t\t\treturn createShape(\'rectangle\', rect.getCenter(true),\n+\t\t\t\t\trect.getSize(true), radius, arguments);\n+\t\t},\n+\n+\t\tEllipse: function() {\n+\t\t\tvar ellipse = Shape._readEllipse(arguments);\n+\t\t\t\tradius = ellipse.radius;\n+\t\t\treturn createShape(\'ellipse\', ellipse.center, radius.multiply(2),\n+\t\t\t\t\tradius, arguments);\n+\t\t},\n \n-\t\t\tEllipse: function() {\n-\t\t\t\tvar rect = Rectangle.readNamed(arguments, \'rectangle\');\n-\t\t\t\treturn createShape(\'ellipse\', rect.getCenter(true),\n-\t\t\t\t\t\trect.getSize(true), arguments);\n+\t\t_readEllipse: function(args) {\n+\t\t\tvar center,\n+\t\t\t\tradius;\n+\t\t\tif (Base.hasNamed(args, \'radius\')) {\n+\t\t\t\tcenter = Point.readNamed(args, \'center\');\n+\t\t\t\tradius = Size.readNamed(args, \'radius\');\n+\t\t\t} else {\n+\t\t\t\tvar rect = Rectangle.readNamed(args, \'rectangle\');\n+\t\t\t\tcenter = rect.getCenter(true);\n+\t\t\t\tradius = rect.getSize(true).divide(2);\n \t\t\t}\n-\t\t};\n-\t}\n-});\n+\t\t\treturn { center: center, radius: radius };\n+\t\t}\n+\t};\n+}});\n \n var Raster = Item.extend({\n \t_class: \'Raster\',\n@@ -3794,9 +4110,7 @@ var Raster = Item.extend({\n \tinitialize: function Raster(object, position) {\n \t\tif (!this._initialize(object,\n \t\t\t\tposition !== undefined && Point.read(arguments, 1))) {\n-\t\t\tif (object.getContext) {\n-\t\t\t\tthis.setCanvas(object);\n-\t\t\t} else if (typeof object === \'string\') {\n+\t\t\tif (typeof object === \'string\') {\n \t\t\t\tthis.setSource(object);\n \t\t\t} else {\n \t\t\t\tthis.setImage(object);\n@@ -3806,6 +4120,10 @@ var Raster = Item.extend({\n \t\t\tthis._size = new Size();\n \t},\n \n+\t_equals: function(item) {\n+\t\treturn this.getSource() === item.getSource();\n+\t},\n+\n \tclone: function(insert) {\n \t\tvar param = { insert: false },\n \t\t\timage = this._image;\n@@ -3857,18 +4175,25 @@ var Raster = Item.extend({\n \t\t);\n \t},\n \n-\tgetContext: function() {\n-\t\tif (!this._context)\n-\t\t\tthis._context = this.getCanvas().getContext(\'2d\');\n-\t\tif (arguments[0]) {\n-\t\t\tthis._image = null;\n-\t\t\tthis._changed(129);\n-\t\t}\n-\t\treturn this._context;\n+\tgetImage: function() {\n+\t\treturn this._image;\n \t},\n \n-\tsetContext: function(context) {\n-\t\tthis._context = context;\n+\tsetImage: function(image) {\n+\t\tif (this._canvas)\n+\t\t\tCanvasProvider.release(this._canvas);\n+\t\tif (image.getContext) {\n+\t\t\tthis._image = null;\n+\t\t\tthis._canvas = image;\n+\t\t} else {\n+\t\t\tthis._image = image;\n+\t\t\tthis._canvas = null;\n+\t\t}\n+\t\tthis._size = new Size(\n+\t\t\t\timage.naturalWidth || image.width,\n+\t\t\t\timage.naturalHeight || image.height);\n+\t\tthis._context = null;\n+\t\tthis._changed(5 | 129);\n \t},\n \n \tgetCanvas: function() {\n@@ -3885,28 +4210,20 @@ var Raster = Item.extend({\n \t\treturn this._canvas;\n \t},\n \n-\tsetCanvas: function(canvas) {\n-\t\tif (this._canvas)\n-\t\t\tCanvasProvider.release(this._canvas);\n-\t\tthis._canvas = canvas;\n-\t\tthis._size = new Size(canvas.width, canvas.height);\n-\t\tthis._image = null;\n-\t\tthis._context = null;\n-\t\tthis._changed(5 | 129);\n-\t},\n+\tsetCanvas: \'#setImage\',\n \n-\tgetImage: function() {\n-\t\treturn this._image;\n+\tgetContext: function() {\n+\t\tif (!this._context)\n+\t\t\tthis._context = this.getCanvas().getContext(\'2d\');\n+\t\tif (arguments[0]) {\n+\t\t\tthis._image = null;\n+\t\t\tthis._changed(129);\n+\t\t}\n+\t\treturn this._context;\n \t},\n \n-\tsetImage: function(image) {\n-\t\tif (this._canvas)\n-\t\t\tCanvasProvider.release(this._canvas);\n-\t\tthis._image = image;\n-\t\tthis._size = new Size(image.width, image.height);\n-\t\tthis._canvas = null;\n-\t\tthis._context = null;\n-\t\tthis._changed(5);\n+\tsetContext: function(context) {\n+\t\tthis._context = context;\n \t},\n \n \tgetSource: function() {\n@@ -3918,21 +4235,25 @@ var Raster = Item.extend({\n \t\t\timage = document.getElementById(src) || new Image();\n \n \t\tfunction loaded() {\n+\t\t\tvar view = that._project.view;\n+\t\t\tif (view)\n+\t\t\t\tpaper = view._scope;\n \t\t\tthat.fire(\'load\');\n-\t\t\tif (that._project.view)\n-\t\t\t\tthat._project.view.draw(true);\n+\t\t\tif (view)\n+\t\t\t\tview.draw(true);\n \t\t}\n \n-\t\tif (image.width && image.height) {\n+\t\tif (image.naturalWidth && image.naturalHeight) {\n \t\t\tsetTimeout(loaded, 0);\n-\t\t} else if (!image.src) {\n+\t\t} else {\n \t\t\tDomEvent.add(image, {\n \t\t\t\tload: function() {\n \t\t\t\t\tthat.setImage(image);\n \t\t\t\t\tloaded();\n \t\t\t\t}\n \t\t\t});\n-\t\t\timage.src = src;\n+\t\t\tif (!image.src)\n+\t\t\t\timage.src = src;\n \t\t}\n \t\tthis.setImage(image);\n \t},\n@@ -3941,7 +4262,7 @@ var Raster = Item.extend({\n \t\treturn this._canvas || this._image;\n \t},\n \n-\tgetSubImage: function(rect) {\n+\tgetSubCanvas: function(rect) {\n \t\trect = Rectangle.read(arguments);\n \t\tvar ctx = CanvasProvider.getContext(rect.getSize());\n \t\tctx.drawImage(this.getCanvas(), rect.x, rect.y,\n@@ -3949,6 +4270,18 @@ var Raster = Item.extend({\n \t\treturn ctx.canvas;\n \t},\n \n+\tgetSubRaster: function(rect) {\n+\t\trect = Rectangle.read(arguments);\n+\t\tvar raster = new Raster({\n+\t\t\tcanvas: this.getSubCanvas(rect),\n+\t\t\tinsert: false\n+\t\t});\n+\t\traster.translate(rect.getCenter().subtract(this.getSize().divide(2)));\n+\t\traster._matrix.preConcatenate(this._matrix);\n+\t\traster.insertAbove(this);\n+\t\treturn raster;\n+\t},\n+\n \ttoDataURL: function() {\n \t\tvar src = this._image && this._image.src;\n \t\tif (/^data:/.test(src))\n@@ -4100,6 +4433,10 @@ var PlacedSymbol = Item.extend({\n \t\t\tthis.setSymbol(arg0 instanceof Symbol ? arg0 : new Symbol(arg0));\n \t},\n \n+\t_equals: function(item) {\n+\t\treturn this._symbol === item._symbol;\n+\t},\n+\n \tgetSymbol: function() {\n \t\treturn this._symbol;\n \t},\n@@ -4186,22 +4523,20 @@ var Segment = Base.extend({\n \t\t\t} else {\n \t\t\t\tpoint = arg0;\n \t\t\t}\n-\t\t} else if (count < 6) {\n-\t\t\tif (count == 2 && arg1.x === undefined) {\n-\t\t\t\tpoint = [ arg0, arg1 ];\n-\t\t\t} else {\n-\t\t\t\tpoint = arg0;\n-\t\t\t\thandleIn = arg1;\n-\t\t\t\thandleOut = arg2;\n-\t\t\t}\n-\t\t} else if (count === 6) {\n-\t\t\tpoint = [ arg0, arg1 ];\n-\t\t\thandleIn = [ arg2, arg3 ];\n-\t\t\thandleOut = [ arg4, arg5 ];\n+\t\t} else if (count === 2 && typeof arg0 === \'number\') {\n+\t\t\tpoint = arguments;\n+\t\t} else if (count <= 3) {\n+\t\t\tpoint = arg0;\n+\t\t\thandleIn = arg1;\n+\t\t\thandleOut = arg2;\n+\t\t} else { \n+\t\t\tpoint = arg0 !== undefined ? [ arg0, arg1 ] : null;\n+\t\t\thandleIn = arg2 !== undefined ? [ arg2, arg3 ] : null;\n+\t\t\thandleOut = arg4 !== undefined ? [ arg4, arg5 ] : null;\n \t\t}\n-\t\tthis._point = new SegmentPoint(point, this);\n-\t\tthis._handleIn = new SegmentPoint(handleIn, this);\n-\t\tthis._handleOut = new SegmentPoint(handleOut, this);\n+\t\tnew SegmentPoint(point, this, \'_point\');\n+\t\tnew SegmentPoint(handleIn, this, \'_handleIn\');\n+\t\tnew SegmentPoint(handleOut, this, \'_handleOut\');\n \t},\n \n \t_serialize: function(options) {\n@@ -4261,6 +4596,42 @@ var Segment = Base.extend({\n \t\tthis._handleOut.set(0, 0);\n \t},\n \n+\tisColinear: function(segment) {\n+\t\tvar next1 = this.getNext(),\n+\t\t\tnext2 = segment.getNext();\n+\t\treturn this._handleOut.isZero() && next1._handleIn.isZero()\n+\t\t\t\t&& segment._handleOut.isZero() && next2._handleIn.isZero()\n+\t\t\t\t&& next1._point.subtract(this._point).isColinear(\n+\t\t\t\t\tnext2._point.subtract(segment._point));\n+\t},\n+\n+\tisOrthogonal: function() {\n+\t\tvar prev = this.getPrevious(),\n+\t\t\tnext = this.getNext();\n+\t\treturn prev._handleOut.isZero() && this._handleIn.isZero()\n+\t\t\t&& this._handleOut.isZero() && next._handleIn.isZero()\n+\t\t\t&& this._point.subtract(prev._point).isOrthogonal(\n+\t\t\t\t\tnext._point.subtract(this._point));\n+\t},\n+\n+\tisArc: function() {\n+\t\tvar next = this.getNext(),\n+\t\t\thandle1 = this._handleOut,\n+\t\t\thandle2 = next._handleIn,\n+\t\t\tkappa = Numerical.KAPPA;\n+\t\tif (handle1.isOrthogonal(handle2)) {\n+\t\t\tvar from = this._point,\n+\t\t\t\tto = next._point,\n+\t\t\t\tcorner = new Line(from, handle1, true).intersect(\n+\t\t\t\t\t\tnew Line(to, handle2, true), true);\n+\t\t\treturn corner && Numerical.isZero(handle1.getLength() /\n+\t\t\t\t\tcorner.subtract(from).getLength() - kappa)\n+\t\t\t\t&& Numerical.isZero(handle2.getLength() /\n+\t\t\t\t\tcorner.subtract(to).getLength() - kappa);\n+\t\t}\n+\t\treturn false;\n+\t},\n+\n \t_isSelected: function(point) {\n \t\tvar state = this._selectionState;\n \t\treturn point == this._point ? !!(state & 4)\n@@ -4278,7 +4649,7 @@ var Segment = Base.extend({\n \t\t\t\t!!(state & 1),\n \t\t\t\t!!(state & 2)\n \t\t\t];\n-\t\tif (point == this._point) {\n+\t\tif (point === this._point) {\n \t\t\tif (selected) {\n \t\t\t\tselection[1] = selection[2] = false;\n \t\t\t} else {\n@@ -4291,7 +4662,7 @@ var Segment = Base.extend({\n \t\t\t}\n \t\t\tselection[0] = selected;\n \t\t} else {\n-\t\t\tvar index = point == this._handleIn ? 1 : 2;\n+\t\t\tvar index = point === this._handleIn ? 1 : 2;\n \t\t\tif (selection[index] != selected) {\n \t\t\t\tif (selected)\n \t\t\t\t\tselection[0] = false;\n@@ -4364,7 +4735,7 @@ var Segment = Base.extend({\n \t},\n \n \tequals: function(segment) {\n-\t\treturn segment === this || segment\n+\t\treturn segment === this || segment && this._class === segment._class\n \t\t\t\t&& this._point.equals(segment._point)\n \t\t\t\t&& this._handleIn.equals(segment._handleIn)\n \t\t\t\t&& this._handleOut.equals(segment._handleOut)\n@@ -4431,7 +4802,7 @@ var Segment = Base.extend({\n });\n \n var SegmentPoint = Point.extend({\n-\tinitialize: function SegmentPoint(point, owner) {\n+\tinitialize: function SegmentPoint(point, owner, key) {\n \t\tvar x, y, selected;\n \t\tif (!point) {\n \t\t\tx = y = 0;\n@@ -4448,6 +4819,7 @@ var SegmentPoint = Point.extend({\n \t\tthis._x = x;\n \t\tthis._y = y;\n \t\tthis._owner = owner;\n+\t\towner[key] = this;\n \t\tif (selected)\n \t\t\tthis.setSelected(true);\n \t},\n@@ -4459,6 +4831,15 @@ var SegmentPoint = Point.extend({\n \t\treturn this;\n \t},\n \n+\t_serialize: function(options) {\n+\t\tvar f = options.formatter,\n+\t\t\tx = f.number(this._x),\n+\t\t\ty = f.number(this._y);\n+\t\treturn this.isSelected()\n+\t\t\t\t? { x: x, y: y, selected: true }\n+\t\t\t\t: [x, y];\n+\t},\n+\n \tgetX: function() {\n \t\treturn this._x;\n \t},\n@@ -4792,7 +5173,7 @@ statics: {\n \t\t];\n \t},\n \n-\tsolveCubic: function (v, coord, val, roots) {\n+\tsolveCubic: function (v, coord, val, roots, min, max) {\n \t\tvar p1 = v[coord],\n \t\t\tc1 = v[coord + 2],\n \t\t\tc2 = v[coord + 4],\n@@ -4800,7 +5181,7 @@ statics: {\n \t\t\tc = 3 * (c1 - p1),\n \t\t\tb = 3 * (c2 - c1) - c,\n \t\t\ta = p2 - p1 - c - b;\n-\t\treturn Numerical.solveCubic(a, b, c, p1 - val, roots);\n+\t\treturn Numerical.solveCubic(a, b, c, p1 - val, roots, min, max);\n \t},\n \n \tgetParameterOf: function(v, x, y) {\n@@ -4841,7 +5222,9 @@ statics: {\n \t},\n \n \tisLinear: function(v) {\n-\t\treturn v[0] === v[2] && v[1] === v[3] && v[4] === v[6] && v[5] === v[7];\n+\t\tvar isZero = Numerical.isZero;\n+\t\treturn isZero(v[0] - v[2]) && isZero(v[1] - v[3])\n+\t\t\t\t&& isZero(v[4] - v[6]) && isZero(v[5] - v[7]);\n \t},\n \n \tisFlatEnough: function(v, tolerance) {\n@@ -4880,44 +5263,6 @@ statics: {\n \t\treturn new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);\n \t},\n \n-\t_getCrossings: function(v, prev, x, y, roots) {\n-\t\tvar count = Curve.solveCubic(v, 1, y, roots),\n-\t\t\tcrossings = 0,\n-\t\t\ttolerance = 0.00001,\n-\t\t\tabs = Math.abs;\n-\n-\t\tfunction changesOrientation(tangent) {\n-\t\t\treturn Curve.evaluate(prev, 1, 1).y\n-\t\t\t\t\t* tangent.y > 0;\n-\t\t}\n-\n-\t\tif (count === -1) {\n-\t\t\troots[0] = Curve.getParameterOf(v, x, y);\n-\t\t\tcount = roots[0] !== null ? 1 : 0;\n-\t\t}\n-\t\tfor (var i = 0; i < count; i++) {\n-\t\t\tvar t = roots[i];\n-\t\t\tif (t > -tolerance && t < 1 - tolerance) {\n-\t\t\t\tvar pt = Curve.evaluate(v, t, 0);\n-\t\t\t\tif (x < pt.x + tolerance) {\n-\t\t\t\t\tvar tan = Curve.evaluate(v, t, 1);\n-\t\t\t\t\tif (abs(pt.x - x) < tolerance) {\n-\t\t\t\t\t\tvar angle = tan.getAngle();\n-\t\t\t\t\t\tif (angle > -180 && angle < 0\n-\t\t\t\t\t\t\t&& (t > tolerance || changesOrientation(tan)))\n-\t\t\t\t\t\t\t\tcontinue;\n-\t\t\t\t\t} else  {\n-\t\t\t\t\t\tif (abs(tan.y) < tolerance\n-\t\t\t\t\t\t\t|| t < tolerance && !changesOrientation(tan))\n-\t\t\t\t\t\t\t\tcontinue;\n-\t\t\t\t\t}\n-\t\t\t\t\tcrossings++;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t}\n-\t\treturn crossings;\n-\t},\n-\n \t_addBounds: function(v0, v1, v2, v3, coord, padding, min, max, roots) {\n \t\tfunction add(value, padding) {\n \t\t\tvar left = value - padding,\n@@ -4944,6 +5289,87 @@ statics: {\n \t\t\t\t\t+ t * t * t * v3,\n \t\t\t\t\tpadding);\n \t\t}\n+\t},\n+\n+\t_getWinding: function(v, x, y, roots1, roots2) {\n+\t\tvar tolerance = 0.00001,\n+\t\t\tabs = Math.abs;\n+\n+\t\tfunction getOrientation(v) {\n+\t\t\tvar y0 = v[1],\n+\t\t\t\ty1 = v[7],\n+\t\t\t\tdir = 1;\n+\t\t\tif (y0 > y1) {\n+\t\t\t\tvar tmp = y0;\n+\t\t\t\ty0 = y1;\n+\t\t\t\ty1 = tmp;\n+\t\t\t    dir = -1;\n+\t\t\t}\n+\t\t\tif (y < y0 || y > y1)\n+\t\t\t    dir = 0;\n+\t\t\treturn dir;\n+\t\t}\n+\n+\t\tif (Curve.isLinear(v)) {\n+\t\t\tvar dir = getOrientation(v);\n+\t\t\tif (!dir)\n+\t\t\t\treturn 0;\n+\t\t\tvar cross = (v[6] - v[0]) * (y - v[1]) - (v[7] - v[1]) * (x - v[0]);\n+\t\t\treturn (cross < -tolerance ? -1 : 1) == dir ? 0 : dir;\n+\t\t}\n+\n+\t\tvar y0 = v[1],\n+\t\t\ty1 = v[3],\n+\t\t\ty2 = v[5],\n+\t\t\ty3 = v[7];\n+\t\tvar a = 3 * (y1 - y2) - y0 + y3,\n+\t\t\tb = 2 * (y0 + y2) - 4 * y1,\n+\t\t\tc = y1 - y0;\n+\t\tvar count = Numerical.solveQuadratic(a, b, c, roots1, tolerance,\n+\t\t\t\t1 - tolerance),\n+\t\t\tpart, \n+\t\t\trest = v, \n+\t\t\tt1 = roots1[0], \n+\t\t\twinding = 0;\n+\t\tfor (var i = 0; i <= count; i++) {\n+\t\t\tif (i === count) {\n+\t\t\t\tpart = rest;\n+\t\t\t} else {\n+\t\t\t\tvar curves = Curve.subdivide(rest, t1);\n+\t\t\t\tpart = curves[0];\n+\t\t\t\trest = curves[1];\n+\t\t\t\tt1 = roots1[i];\n+\t\t\t\tt1 = (roots1[i + 1] - t1) / (1 - t1);\n+\t\t\t}\n+\t\t\tif (i > 0)\n+\t\t\t\tpart[3] = part[1]; \n+\t\t\tif (i < count)\n+\t\t\t\tpart[5] = rest[1]; \n+\t\t\tvar dir = getOrientation(part);\n+\t\t\tif (!dir)\n+\t\t\t    continue;\n+\t\t\tvar t2,\n+\t\t\t\tpx;\n+\t\t\tif (Curve.solveCubic(part, 1, y, roots2, -tolerance, 1 + -tolerance)\n+\t\t\t\t\t=== 1) {\n+\t\t\t\tt2 = roots2[0];\n+\t\t\t\tpx = Curve.evaluate(part, t2, 0).x;\n+\t\t\t} else {\n+\t\t\t\tvar mid = (part[1] + part[7]) / 2;\n+\t\t\t\tt2 = y < mid && dir > 0 ? 0 : 1;\n+\t\t\t\tif (t2 === 1 && y == part[7])\n+\t\t\t\t\tcontinue;\n+\t\t\t\tpx = t2 === 0 ? part[0] : part[6];\n+\t\t\t}\n+\t\t\tvar flat = abs(Curve.evaluate(part, t2, 1).y) < tolerance;\n+\t\t\tif (x >= px + (flat ? -tolerance : tolerance * dir)\n+\t\t\t\t\t&& !(flat && (abs(t2) < tolerance && x != part[0]\n+\t\t\t\t\t\t|| abs(t2 - 1) < tolerance && x != part[6]))) {\n+\t\t\t\twinding += flat && abs(t2 - (dir > 0 ? 1 : 0)) < tolerance\n+\t\t\t\t\t\t? -dir : dir;\n+\t\t\t}\n+\t\t}\n+\t\treturn winding;\n \t}\n }}, Base.each([\'getBounds\', \'getStrokeBounds\', \'getHandleBounds\', \'getRoughBounds\'],\n \tfunction(name) {\n@@ -5068,7 +5494,9 @@ new function() {\n \t\t\t\ta = 0;\n \t\t\tif (b === undefined)\n \t\t\t\tb = 1;\n-\t\t\tif (v[0] == v[2] && v[1] == v[3] && v[6] == v[4] && v[7] == v[5]) {\n+\t\t\tvar isZero = Numerical.isZero;\n+\t\t\tif (isZero(v[0] - v[2]) && isZero(v[1] - v[3])\n+\t\t\t\t\t&& isZero(v[6] - v[4]) && isZero(v[7] - v[5])) {\n \t\t\t\tvar dx = v[6] - v[0], \n \t\t\t\t\tdy = v[7] - v[1]; \n \t\t\t\treturn (b - a) * Math.sqrt(dx * dx + dy * dy);\n@@ -5277,34 +5705,35 @@ new function() {\n \t\tvar flip = Curve.isLinear(v1),\n \t\t\tvc = flip ? v2 : v1,\n \t\t\tvl = flip ? v1 : v2,\n-\t\t\tl1x = vl[0], l1y = vl[1],\n-\t\t\tl2x = vl[6], l2y = vl[7],\n-\t\t\tlvx = l2x - l1x,\n-\t\t\tlvy = l2y - l1y,\n-\t\t\tangle = Math.atan2(-lvy, lvx),\n+\t\t\tlx1 = vl[0], ly1 = vl[1],\n+\t\t\tlx2 = vl[6], ly2 = vl[7],\n+\t\t\tldx = lx2 - lx1,\n+\t\t\tldy = ly2 - ly1,\n+\t\t\tangle = Math.atan2(-ldy, ldx),\n \t\t\tsin = Math.sin(angle),\n \t\t\tcos = Math.cos(angle),\n-\t\t\trl2x = lvx * cos - lvy * sin,\n-\t\t\tvcr = [];\n-\n+\t\t\trlx2 = ldx * cos - ldy * sin,\n+\t\t\trvl = [0, 0, 0, 0, rlx2, 0, rlx2, 0],\n+\t\t\trvc = [];\n \t\tfor(var i = 0; i < 8; i += 2) {\n-\t\t\tvar x = vc[i] - l1x,\n-\t\t\t\ty = vc[i + 1] - l1y;\n-\t\t\tvcr.push(\n+\t\t\tvar x = vc[i] - lx1,\n+\t\t\t\ty = vc[i + 1] - ly1;\n+\t\t\trvc.push(\n \t\t\t\tx * cos - y * sin,\n \t\t\t\ty * cos + x * sin);\n \t\t}\n \t\tvar roots = [],\n-\t\t\tcount = Curve.solveCubic(vcr, 1, 0, roots);\n+\t\t\tcount = Curve.solveCubic(rvc, 1, 0, roots, 0, 1);\n \t\tfor (var i = 0; i < count; i++) {\n-\t\t\tvar t = roots[i];\n-\t\t\tif (t >= 0 && t <= 1) {\n-\t\t\t\tvar point = Curve.evaluate(vcr, t, 0);\n-\t\t\t\tif (point.x  >= 0 && point.x <= rl2x)\n-\t\t\t\t\taddLocation(locations,\n-\t\t\t\t\t\t\tflip ? curve2 : curve1,\n-\t\t\t\t\t\t\tt, Curve.evaluate(vc, t, 0),\n-\t\t\t\t\t\t\tflip ? curve1 : curve2);\n+\t\t\tvar tc = roots[i],\n+\t\t\t\tx = Curve.evaluate(rvc, tc, 0).x;\n+\t\t\tif (x >= 0 && x <= rlx2) {\n+\t\t\t\tvar tl = Curve.getParameterOf(rvl, x, 0),\n+\t\t\t\t\tt1 = flip ? tl : tc,\n+\t\t\t\t\tt2 = flip ? tc : tl;\n+\t\t\t\taddLocation(locations,\n+\t\t\t\t\t\tcurve1, t1, Curve.evaluate(v1, t1, 0),\n+\t\t\t\t\t\tcurve2, t2, Curve.evaluate(v2, t2, 0));\n \t\t\t}\n \t\t}\n \t}\n@@ -5586,6 +6015,11 @@ var PathItem = Item.extend({\n \n \t_canComposite: function() {\n \t\treturn !(this.hasFill() && this.hasStroke());\n+\t},\n+\n+\t_contains: function(point) {\n+\t\tvar winding = this._getWinding(point);\n+\t\treturn !!(this.getWindingRule() === \'evenodd\' ? winding & 1 : winding);\n \t}\n \n });\n@@ -5612,6 +6046,10 @@ var Path = PathItem.extend({\n \t\tthis._initialize(!segments && arg);\n \t},\n \n+\t_equals: function(item) {\n+\t\treturn Base.equals(this._segments, item._segments);\n+\t},\n+\n \tclone: function(insert) {\n \t\tvar copy = this._clone(new Path({\n \t\t\tsegments: this._segments,\n@@ -5643,10 +6081,13 @@ var Path = PathItem.extend({\n \t},\n \n \tsetSegments: function(segments) {\n-\t\tthis._selectedSegmentState = 0;\n+\t\tvar fullySelected = this.isFullySelected();\n \t\tthis._segments.length = 0;\n+\t\tthis._selectedSegmentState = 0;\n \t\tdelete this._curves;\n \t\tthis._add(Segment.readAll(segments));\n+\t\tif (fullySelected)\n+\t\t\tthis.setFullySelected(true);\n \t},\n \n \tgetFirstSegment: function() {\n@@ -5755,16 +6196,13 @@ var Path = PathItem.extend({\n \t\t\tcurves = this._curves,\n \t\t\tamount = segs.length,\n \t\t\tappend = index == null,\n-\t\t\tindex = append ? segments.length : index,\n-\t\t\tfullySelected = this.isFullySelected();\n+\t\t\tindex = append ? segments.length : index;\n \t\tfor (var i = 0; i < amount; i++) {\n \t\t\tvar segment = segs[i];\n \t\t\tif (segment._path)\n \t\t\t\tsegment = segs[i] = segment.clone();\n \t\t\tsegment._path = this;\n \t\t\tsegment._index = index + i;\n-\t\t\tif (fullySelected)\n-\t\t\t\tsegment._selectionState = 4;\n \t\t\tif (segment._selectionState)\n \t\t\t\tthis._updateSelection(segment, 0, segment._selectionState);\n \t\t}\n@@ -5880,8 +6318,9 @@ var Path = PathItem.extend({\n \t},\n \n \tisFullySelected: function() {\n-\t\treturn this._selected && this._selectedSegmentState\n-\t\t\t\t== this._segments.length * 4;\n+\t\tvar length = this._segments.length;\n+\t\treturn this._selected && length > 0 && this._selectedSegmentState\n+\t\t\t\t=== length * 4;\n \t},\n \n \tsetFullySelected: function(selected) {\n@@ -6032,10 +6471,6 @@ var Path = PathItem.extend({\n \t\treturn false;\n \t},\n \n-\treduce: function() {\n-\t\treturn this;\n-\t},\n-\n \tgetLength: function() {\n \t\tif (this._length == null) {\n \t\t\tvar curves = this.getCurves();\n@@ -6139,52 +6574,123 @@ var Path = PathItem.extend({\n \t\t\t\t? parent : this)._style;\n \t},\n \n-\t_contains: function(point) {\n+\ttoShape: function(insert) {\n+\t\tif (!this._closed)\n+\t\t\treturn null;\n+\n+\t\tvar segments = this._segments,\n+\t\t\ttype,\n+\t\t\tsize,\n+\t\t\tradius,\n+\t\t\ttopCenter;\n+\n+\t\tfunction isColinear(i, j) {\n+\t\t\treturn segments[i].isColinear(segments[j]);\n+\t\t}\n+\n+\t\tfunction isOrthogonal(i) {\n+\t\t\treturn segments[i].isOrthogonal();\n+\t\t}\n+\n+\t\tfunction isArc(i) {\n+\t\t\treturn segments[i].isArc();\n+\t\t}\n+\n+\t\tfunction getDistance(i, j) {\n+\t\t\treturn segments[i]._point.getDistance(segments[j]._point);\n+\t\t}\n+\n+\t\tif (this.isPolygon() && segments.length === 4\n+\t\t\t\t&& isColinear(0, 2) && isColinear(1, 3) && isOrthogonal(1)) {\n+\t\t\ttype = Shape.Rectangle;\n+\t\t\tsize = new Size(getDistance(0, 3), getDistance(0, 1));\n+\t\t\ttopCenter = segments[1]._point.add(segments[2]._point).divide(2);\n+\t\t} else if (segments.length === 8 && isArc(0) && isArc(2) && isArc(4)\n+\t\t\t\t&& isArc(6) && isColinear(1, 5) && isColinear(3, 7)) {\n+\t\t\ttype = Shape.Rectangle;\n+\t\t\tsize = new Size(getDistance(1, 6), getDistance(0, 3));\n+\t\t\tradius = size.subtract(new Size(getDistance(0, 7),\n+\t\t\t\t\tgetDistance(1, 2))).divide(2);\n+\t\t\ttopCenter = segments[3]._point.add(segments[4]._point).divide(2);\n+\t\t} else if (segments.length === 4\n+\t\t\t\t&& isArc(0) && isArc(1) && isArc(2) && isArc(3)) {\n+\t\t\tif (Numerical.isZero(getDistance(0, 2) - getDistance(1, 3))) {\n+\t\t\t\ttype = Shape.Circle;\n+\t\t\t\tradius = getDistance(0, 2) / 2;\n+\t\t\t} else {\n+\t\t\t\ttype = Shape.Ellipse;\n+\t\t\t\tradius = new Size(getDistance(2, 0) / 2, getDistance(3, 1) / 2);\n+\t\t\t}\n+\t\t\ttopCenter = segments[1]._point;\n+\t\t}\n+\n+\t\tif (type) {\n+\t\t\tvar center = this.getPosition(true),\n+\t\t\t\tshape = new type({\n+\t\t\t\t\tcenter: center,\n+\t\t\t\t\tsize: size,\n+\t\t\t\t\tradius: radius,\n+\t\t\t\t\tinsert: false\n+\t\t\t\t});\n+\t\t\tshape.rotate(topCenter.subtract(center).getAngle() + 90);\n+\t\t\tshape.setStyle(this._style);\n+\t\t\tif (insert || insert === undefined)\n+\t\t\t\tshape.insertAbove(this);\n+\t\t\treturn shape;\n+\t\t}\n+\t\treturn null;\n+\t},\n+\n+\t_getWinding: function(point) {\n \t\tvar closed = this._closed;\n \t\tif (!closed && !this.hasFill()\n \t\t\t\t|| !this._getBounds(\'getRoughBounds\')._containsPoint(point))\n-\t\t\treturn false;\n+\t\t\treturn 0;\n \t\tvar curves = this.getCurves(),\n \t\t\tsegments = this._segments,\n-\t\t\tcrossings = 0,\n-\t\t\troots = new Array(3),\n+\t\t\twinding = 0,\n+\t\t\troots1 = [],\n+\t\t\troots2 = [],\n \t\t\tlast = (closed\n \t\t\t\t\t? curves[curves.length - 1]\n \t\t\t\t\t: new Curve(segments[segments.length - 1]._point,\n-\t\t\t\t\t\tsegments[0]._point)).getValues(),\n-\t\t\tprevious = last;\n+\t\t\t\t\t\tsegments[0]._point)).getValues();\n \t\tfor (var i = 0, l = curves.length; i < l; i++) {\n \t\t\tvar vals = curves[i].getValues(),\n \t\t\t\tx = vals[0],\n \t\t\t\ty = vals[1];\n \t\t\tif (!(x === vals[2] && y === vals[3] && x === vals[4]\n \t\t\t\t\t&& y === vals[5] && x === vals[6] && y === vals[7])) {\n-\t\t\t\tcrossings += Curve._getCrossings(vals, previous,\n-\t\t\t\t\t\tpoint.x, point.y, roots);\n-\t\t\t\tprevious = vals;\n+\t\t\t\twinding += Curve._getWinding(vals, point.x, point.y,\n+\t\t\t\t\t\troots1, roots2);\n \t\t\t}\n \t\t}\n \t\tif (!closed) {\n-\t\t\tcrossings += Curve._getCrossings(last, previous, point.x, point.y,\n-\t\t\t\t\troots);\n+\t\t\twinding += Curve._getWinding(last, point.x, point.y,\n+\t\t\t\t\troots1, roots2);\n \t\t}\n-\t\treturn (crossings & 1) === 1;\n+\t\treturn winding;\n \t},\n \n \t_hitTest: function(point, options) {\n \t\tvar style = this.getStyle(),\n \t\t\tsegments = this._segments,\n \t\t\tclosed = this._closed,\n-\t\t\ttolerance = options.tolerance || 0,\n+\t\t\ttolerance = options.tolerance,\n \t\t\tradius = 0, join, cap, miterLimit,\n \t\t\tthat = this,\n \t\t\tarea, loc, res;\n \n-\t\tif (options.stroke && style.getStrokeColor()) {\n-\t\t\tjoin = style.getStrokeJoin();\n-\t\t\tcap = style.getStrokeCap();\n-\t\t\tradius = style.getStrokeWidth() / 2 + tolerance;\n-\t\t\tmiterLimit = radius * style.getMiterLimit();\n+\t\tif (options.stroke) {\n+\t\t\tradius = style.getStrokeWidth() / 2;\n+\t\t\tif (radius > 0) {\n+\t\t\t\tjoin = style.getStrokeJoin();\n+\t\t\t\tcap = style.getStrokeCap();\n+\t\t\t\tmiterLimit = radius * style.getMiterLimit();\n+\t\t\t} else {\n+\t\t\t\tjoin = cap = \'round\';\n+\t\t\t}\n+\t\t\tradius += tolerance;\n \t\t}\n \n \t\tfunction checkPoint(seg, pt, name) {\n@@ -6212,16 +6718,13 @@ var Path = PathItem.extend({\n \n \t\tfunction isInArea(point) {\n \t\t\tvar length = area.length,\n-\t\t\t\tprevious = getAreaCurve(length - 1),\n-\t\t\t\troots = new Array(3),\n-\t\t\t\tcrossings = 0;\n-\t\t\tfor (var i = 0; i < length; i++) {\n-\t\t\t\tvar curve = getAreaCurve(i);\n-\t\t\t\tcrossings += Curve._getCrossings(curve, previous,\n-\t\t\t\t\t\tpoint.x, point.y, roots);\n-\t\t\t\tprevious = curve;\n-\t\t\t}\n-\t\t\treturn (crossings & 1) === 1;\n+\t\t\t\troots1 = [],\n+\t\t\t\troots2 = [],\n+\t\t\t\twinding = 0;\n+\t\t\tfor (var i = 0; i < length; i++)\n+\t\t\t\twinding += Curve._getWinding(getAreaCurve(i), point.x, point.y,\n+\t\t\t\t\t\troots1, roots2);\n+\t\t\treturn !!winding;\n \t\t}\n \n \t\tfunction checkSegmentStroke(segment) {\n@@ -6391,32 +6894,45 @@ var Path = PathItem.extend({\n \t\t\t\tctx.beginPath();\n \n \t\t\tvar style = this.getStyle(),\n-\t\t\t\tfillColor = style.getFillColor(),\n-\t\t\t\tstrokeColor = style.getStrokeColor(),\n+\t\t\t\thasFill = style.hasFill(),\n+\t\t\t\thasStroke = style.hasStroke(),\n \t\t\t\tdashArray = style.getDashArray(),\n-\t\t\t\tdrawDash = !paper.support.nativeDash && strokeColor\n+\t\t\t\tdashLength = !paper.support.nativeDash && hasStroke\n \t\t\t\t\t\t&& dashArray && dashArray.length;\n \n-\t\t\tif (fillColor || strokeColor && !drawDash || compound || clip)\n+\t\t\tfunction getOffset(i) {\n+\t\t\t\treturn dashArray[((i % dashLength) + dashLength) % dashLength];\n+\t\t\t}\n+\n+\t\t\tif (hasFill || hasStroke && !dashLength || compound || clip)\n \t\t\t\tdrawSegments(ctx, this);\n \n \t\t\tif (this._closed)\n \t\t\t\tctx.closePath();\n \n-\t\t\tif (!clip && !compound && (fillColor || strokeColor)) {\n+\t\t\tif (!clip && !compound && (hasFill || hasStroke)) {\n \t\t\t\tthis._setStyles(ctx);\n-\t\t\t\tif (fillColor)\n-\t\t\t\t\tctx.fill();\n-\t\t\t\tif (strokeColor) {\n-\t\t\t\t\tif (drawDash) {\n+\t\t\t\tif (hasFill) {\n+\t\t\t\t\tctx.fill(style.getWindingRule());\n+\t\t\t\t\tctx.shadowColor = \'rgba(0,0,0,0)\';\n+\t\t\t\t}\n+\t\t\t\tif (hasStroke) {\n+\t\t\t\t\tif (dashLength) {\n \t\t\t\t\t\tctx.beginPath();\n \t\t\t\t\t\tvar flattener = new PathFlattener(this),\n-\t\t\t\t\t\t\tfrom = style.getDashOffset(), to,\n+\t\t\t\t\t\t\tlength = flattener.length,\n+\t\t\t\t\t\t\tfrom = -style.getDashOffset(), to,\n \t\t\t\t\t\t\ti = 0;\n-\t\t\t\t\t\twhile (from < flattener.length) {\n-\t\t\t\t\t\t\tto = from + dashArray[(i++) % dashArray.length];\n-\t\t\t\t\t\t\tflattener.drawPart(ctx, from, to);\n-\t\t\t\t\t\t\tfrom = to + dashArray[(i++) % dashArray.length];\n+\t\t\t\t\t\tfrom = from % length;\n+\t\t\t\t\t\twhile (from > 0) {\n+\t\t\t\t\t\t\tfrom -= getOffset(i--) + getOffset(i--);\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\twhile (from < length) {\n+\t\t\t\t\t\t\tto = from + getOffset(i++);\n+\t\t\t\t\t\t\tif (from > 0 || to > 0)\n+\t\t\t\t\t\t\t\tflattener.drawPart(ctx,\n+\t\t\t\t\t\t\t\t\t\tMath.max(from, 0), Math.max(to, 0));\n+\t\t\t\t\t\t\tfrom = to + getOffset(i++);\n \t\t\t\t\t\t}\n \t\t\t\t\t}\n \t\t\t\t\tctx.stroke();\n@@ -6556,16 +7072,16 @@ var Path = PathItem.extend({\n \t\tcubicCurveTo: function() {\n \t\t\tvar handle1 = Point.read(arguments),\n \t\t\t\thandle2 = Point.read(arguments),\n-\t\t\t\tto = Point.read(arguments);\n-\t\t\tvar current = getCurrentSegment(this);\n+\t\t\t\tto = Point.read(arguments),\n+\t\t\t\tcurrent = getCurrentSegment(this);\n \t\t\tcurrent.setHandleOut(handle1.subtract(current._point));\n \t\t\tthis._add([ new Segment(to, handle2.subtract(to)) ]);\n \t\t},\n \n \t\tquadraticCurveTo: function() {\n \t\t\tvar handle = Point.read(arguments),\n-\t\t\t\tto = Point.read(arguments);\n-\t\t\tvar current = getCurrentSegment(this)._point;\n+\t\t\t\tto = Point.read(arguments),\n+\t\t\t\tcurrent = getCurrentSegment(this)._point;\n \t\t\tthis.cubicCurveTo(\n \t\t\t\thandle.add(current.subtract(handle).multiply(1 / 3)),\n \t\t\t\thandle.add(to.subtract(handle).multiply(1 / 3)),\n@@ -6613,7 +7129,7 @@ var Path = PathItem.extend({\n \t\t\tif (!center) {\n \t\t\t\tif (!throughSide)\n \t\t\t\t\treturn this.lineTo(to);\n-\t\t\t\tthrow new Error("Cannot put an arc through the given points: "\n+\t\t\t\tthrow new Error(\'Cannot put an arc through the given points: \'\n \t\t\t\t\t+ [from, through, to]);\n \t\t\t}\n \t\t\tvar vector = from.subtract(center),\n@@ -6644,25 +7160,41 @@ var Path = PathItem.extend({\n \t\t\tthis._add(segments);\n \t\t},\n \n-\t\tlineBy: function(vector) {\n-\t\t\tvector = Point.read(arguments);\n-\t\t\tvar current = getCurrentSegment(this);\n-\t\t\tthis.lineTo(current._point.add(vector));\n+\t\tlineBy: function() {\n+\t\t\tvar to = Point.read(arguments),\n+\t\t\t\tcurrent = getCurrentSegment(this)._point;\n+\t\t\tthis.lineTo(current.add(to));\n+\t\t},\n+\n+\t\tcurveBy: function() {\n+\t\t\tvar through = Point.read(arguments),\n+\t\t\t\tto = Point.read(arguments),\n+\t\t\t\tparameter = Base.read(arguments),\n+\t\t\t\tcurrent = getCurrentSegment(this)._point;\n+\t\t\tthis.curveTo(current.add(through), current.add(to), parameter);\n \t\t},\n \n-\t\tcurveBy: function(throughVector, toVector, parameter) {\n-\t\t\tthroughVector = Point.read(throughVector);\n-\t\t\ttoVector = Point.read(toVector);\n-\t\t\tvar current = getCurrentSegment(this)._point;\n-\t\t\tthis.curveTo(current.add(throughVector), current.add(toVector),\n-\t\t\t\t\tparameter);\n+\t\tcubicCurveBy: function() {\n+\t\t\tvar handle1 = Point.read(arguments),\n+\t\t\t\thandle2 = Point.read(arguments),\n+\t\t\t\tto = Point.read(arguments),\n+\t\t\t\tcurrent = getCurrentSegment(this)._point;\n+\t\t\tthis.cubicCurveTo(current.add(handle1), current.add(handle2),\n+\t\t\t\t\tcurrent.add(to));\n \t\t},\n \n-\t\tarcBy: function(throughVector, toVector) {\n-\t\t\tthroughVector = Point.read(throughVector);\n-\t\t\ttoVector = Point.read(toVector);\n-\t\t\tvar current = getCurrentSegment(this)._point;\n-\t\t\tthis.arcTo(current.add(throughVector), current.add(toVector));\n+\t\tquadraticCurveBy: function() {\n+\t\t\tvar handle = Point.read(arguments),\n+\t\t\t\tto = Point.read(arguments),\n+\t\t\t\tcurrent = getCurrentSegment(this)._point;\n+\t\t\tthis.quadraticCurveTo(current.add(handle), current.add(to));\n+\t\t},\n+\n+\t\tarcBy: function() {\n+\t\t\tvar through = Point.read(arguments),\n+\t\t\t\tto = Point.read(arguments),\n+\t\t\t\tcurrent = getCurrentSegment(this)._point;\n+\t\t\tthis.arcTo(current.add(through), current.add(to));\n \t\t},\n \n \t\tclosePath: function() {\n@@ -6684,27 +7216,12 @@ var Path = PathItem.extend({\n \n statics: {\n \tisClockwise: function(segments) {\n-\t\tvar sum = 0,\n-\t\t\txPre, yPre,\n-\t\t\tadd = false;\n-\t\tfunction edge(x, y) {\n-\t\t\tif (add)\n-\t\t\t\tsum += (xPre - x) * (y + yPre);\n-\t\t\txPre = x;\n-\t\t\tyPre = y;\n-\t\t\tadd = true;\n-\t\t}\n+\t\tvar sum = 0;\n \t\tfor (var i = 0, l = segments.length; i < l; i++) {\n-\t\t\tvar seg1 = segments[i],\n-\t\t\t\tseg2 = segments[i + 1 < l ? i + 1 : 0],\n-\t\t\t\tpoint1 = seg1._point,\n-\t\t\t\thandle1 = seg1._handleOut,\n-\t\t\t\thandle2 = seg2._handleIn,\n-\t\t\t\tpoint2 = seg2._point;\n-\t\t\tedge(point1._x, point1._y);\n-\t\t\tedge(point1._x + handle1._x, point1._y + handle1._y);\n-\t\t\tedge(point2._x + handle2._x, point2._y + handle2._y);\n-\t\t\tedge(point2._x, point2._y);\n+\t\t\tvar v = Curve.getValues(\n+\t\t\t\t\tsegments[i], segments[i + 1 < l ? i + 1 : 0]);\n+\t\t\tfor (var j = 2; j < 8; j += 2)\n+\t\t\t\tsum += (v[j - 2] - v[j]) * (v[j + 1] + v[j - 1]);\n \t\t}\n \t\treturn sum > 0;\n \t},\n@@ -6760,7 +7277,7 @@ statics: {\n \t\t\t\t\tMath.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)];\n \t\t}\n \n-\t\tif (!style.getStrokeColor() || !style.getStrokeWidth())\n+\t\tif (!style.hasStroke())\n \t\t\treturn Path.getBounds(segments, closed, style, matrix);\n \t\tvar length = segments.length - (closed ? 0 : 1),\n \t\t\tradius = style.getStrokeWidth() / 2,\n@@ -6887,7 +7404,9 @@ statics: {\n \tgetRoughBounds: function(segments, closed, style, matrix) {\n \t\tvar strokeWidth = style.getStrokeColor() ? style.getStrokeWidth() : 0,\n \t\t\tjoinWidth = strokeWidth;\n-\t\tif (strokeWidth > 0) {\n+\t\tif (strokeWidth === 0) {\n+\t\t\tstrokeWidth = 0.00001;\n+\t\t} else {\n \t\t\tif (style.getStrokeJoin() === \'miter\')\n \t\t\t\tjoinWidth = strokeWidth * style.getMiterLimit();\n \t\t\tif (style.getStrokeCap() === \'square\')\n@@ -6900,73 +7419,28 @@ statics: {\n \n Path.inject({ statics: new function() {\n \n-\tfunction createPath(args) {\n-\t\treturn new Path(Base.getNamed(args));\n-\t}\n-\n-\tfunction createRectangle() {\n-\t\tvar rect = Rectangle.readNamed(arguments, \'rectangle\'),\n-\t\t\tradius = Size.readNamed(arguments, \'radius\', 0, 0,\n-\t\t\t\t\t{ readNull: true }),\n-\t\t\tbl = rect.getBottomLeft(true),\n-\t\t\ttl = rect.getTopLeft(true),\n-\t\t\ttr = rect.getTopRight(true),\n-\t\t\tbr = rect.getBottomRight(true),\n-\t\t\tpath = createPath(arguments);\n-\t\tif (!radius || radius.isZero()) {\n-\t\t\tpath._add([\n-\t\t\t\tnew Segment(bl),\n-\t\t\t\tnew Segment(tl),\n-\t\t\t\tnew Segment(tr),\n-\t\t\t\tnew Segment(br)\n-\t\t\t]);\n-\t\t} else {\n-\t\t\tradius = Size.min(radius, rect.getSize(true).divide(2));\n-\t\t\tvar h = radius.multiply(kappa * 2); \n-\t\t\tpath._add([\n-\t\t\t\tnew Segment(bl.add(radius.width, 0), null, [-h.width, 0]),\n-\t\t\t\tnew Segment(bl.subtract(0, radius.height), [0, h.height], null),\n-\n-\t\t\t\tnew Segment(tl.add(0, radius.height), null, [0, -h.height]),\n-\t\t\t\tnew Segment(tl.add(radius.width, 0), [-h.width, 0], null),\n-\n-\t\t\t\tnew Segment(tr.subtract(radius.width, 0), null, [h.width, 0]),\n-\t\t\t\tnew Segment(tr.add(0, radius.height), [0, -h.height], null),\n-\n-\t\t\t\tnew Segment(br.subtract(0, radius.height), null, [0, h.height]),\n-\t\t\t\tnew Segment(br.subtract(radius.width, 0), [h.width, 0], null)\n-\t\t\t]);\n-\t\t}\n-\t\tpath._closed = true;\n-\t\treturn path;\n-\t}\n-\n-\tvar kappa = Numerical.KAPPA / 2;\n-\n-\tvar ellipseSegments = [\n-\t\tnew Segment([0, 0.5], [0, kappa ], [0, -kappa]),\n-\t\tnew Segment([0.5, 0], [-kappa, 0], [kappa, 0 ]),\n-\t\tnew Segment([1, 0.5], [0, -kappa], [0, kappa ]),\n-\t\tnew Segment([0.5, 1], [kappa, 0 ], [-kappa, 0])\n-\t];\n+\tvar kappa = Numerical.KAPPA,\n+\t\tellipseSegments = [\n+\t\t\tnew Segment([-1, 0], [0, kappa ], [0, -kappa]),\n+\t\t\tnew Segment([0, -1], [-kappa, 0], [kappa, 0 ]),\n+\t\t\tnew Segment([1, 0], [0, -kappa], [0, kappa ]),\n+\t\t\tnew Segment([0, 1], [kappa, 0 ], [-kappa, 0])\n+\t\t];\n \n-\tfunction createEllipse() {\n-\t\tvar rect = Rectangle.readNamed(arguments, \'rectangle\'),\n-\t\t\tpath = createPath(arguments),\n-\t\t\tpoint = rect.getPoint(true),\n-\t\t\tsize = rect.getSize(true),\n+\tfunction createEllipse(center, radius, args) {\n+\t\tvar path = new Path(),\n \t\t\tsegments = new Array(4);\n \t\tfor (var i = 0; i < 4; i++) {\n \t\t\tvar segment = ellipseSegments[i];\n \t\t\tsegments[i] = new Segment(\n-\t\t\t\tsegment._point.multiply(size).add(point),\n-\t\t\t\tsegment._handleIn.multiply(size),\n-\t\t\t\tsegment._handleOut.multiply(size)\n+\t\t\t\tsegment._point.multiply(radius).add(center),\n+\t\t\t\tsegment._handleIn.multiply(radius),\n+\t\t\t\tsegment._handleOut.multiply(radius)\n \t\t\t);\n \t\t}\n \t\tpath._add(segments);\n \t\tpath._closed = true;\n-\t\treturn path;\n+\t\treturn path.set(Base.getNamed(args));\n \t}\n \n \treturn {\n@@ -6980,34 +7454,70 @@ Path.inject({ statics: new function() {\n \t\tCircle: function() {\n \t\t\tvar center = Point.readNamed(arguments, \'center\'),\n \t\t\t\tradius = Base.readNamed(arguments, \'radius\');\n-\t\t\treturn createEllipse(new Rectangle(center.subtract(radius),\n-\t\t\t\t\tnew Size(radius * 2, radius * 2)))\n-\t\t\t\t\t.set(Base.getNamed(arguments));\n+\t\t\treturn createEllipse(center, new Size(radius), arguments);\n+\t\t},\n+\n+\t\tRectangle: function() {\n+\t\t\tvar rect = Rectangle.readNamed(arguments, \'rectangle\'),\n+\t\t\t\tradius = Size.readNamed(arguments, \'radius\', 0, 0,\n+\t\t\t\t\t\t{ readNull: true }),\n+\t\t\t\tbl = rect.getBottomLeft(true),\n+\t\t\t\ttl = rect.getTopLeft(true),\n+\t\t\t\ttr = rect.getTopRight(true),\n+\t\t\t\tbr = rect.getBottomRight(true);\n+\t\t\t\tpath = new Path();\n+\t\t\tif (!radius || radius.isZero()) {\n+\t\t\t\tpath._add([\n+\t\t\t\t\tnew Segment(bl),\n+\t\t\t\t\tnew Segment(tl),\n+\t\t\t\t\tnew Segment(tr),\n+\t\t\t\t\tnew Segment(br)\n+\t\t\t\t]);\n+\t\t\t} else {\n+\t\t\t\tradius = Size.min(radius, rect.getSize(true).divide(2));\n+\t\t\t\tvar rx = radius.width,\n+\t\t\t\t\try = radius.height,\n+\t\t\t\t\thx = rx * kappa,\n+\t\t\t\t\thy = ry * kappa;\n+\t\t\t\tpath._add([\n+\t\t\t\t\tnew Segment(bl.add(rx, 0), null, [-hx, 0]),\n+\t\t\t\t\tnew Segment(bl.subtract(0, ry), [0, hy]),\n+\t\t\t\t\tnew Segment(tl.add(0, ry), null, [0, -hy]),\n+\t\t\t\t\tnew Segment(tl.add(rx, 0), [-hx, 0], null),\n+\t\t\t\t\tnew Segment(tr.subtract(rx, 0), null, [hx, 0]),\n+\t\t\t\t\tnew Segment(tr.add(0, ry), [0, -hy], null),\n+\t\t\t\t\tnew Segment(br.subtract(0, ry), null, [0, hy]),\n+\t\t\t\t\tnew Segment(br.subtract(rx, 0), [hx, 0])\n+\t\t\t\t]);\n+\t\t\t}\n+\t\t\tpath._closed = true;\n+\t\t\treturn path.set(Base.getNamed(arguments));\n \t\t},\n \n-\t\tRectangle: createRectangle,\n+\t\tRoundRectangle: \'#Rectangle\',\n \n-\t\tRoundRectangle: createRectangle,\n-\n-\t\tEllipse: createEllipse,\n+\t\tEllipse: function() {\n+\t\t\tvar ellipse = Shape._readEllipse(arguments);\n+\t\t\treturn createEllipse(ellipse.center, ellipse.radius, arguments);\n+\t\t},\n \n-\t\tOval: createEllipse,\n+\t\tOval: \'#Ellipse\',\n \n \t\tArc: function() {\n \t\t\tvar from = Point.readNamed(arguments, \'from\'),\n \t\t\t\tthrough = Point.readNamed(arguments, \'through\'),\n \t\t\t\tto = Point.readNamed(arguments, \'to\'),\n-\t\t\t\tpath = createPath(arguments);\n+\t\t\t\tpath = new Path();\n \t\t\tpath.moveTo(from);\n \t\t\tpath.arcTo(through, to);\n-\t\t\treturn path;\n+\t\t\treturn path.set(Base.getNamed(arguments));\n \t\t},\n \n \t\tRegularPolygon: function() {\n \t\t\tvar center = Point.readNamed(arguments, \'center\'),\n \t\t\t\tsides = Base.readNamed(arguments, \'sides\'),\n \t\t\t\tradius = Base.readNamed(arguments, \'radius\'),\n-\t\t\t\tpath = createPath(arguments),\n+\t\t\t\tpath = new Path(),\n \t\t\t\tstep = 360 / sides,\n \t\t\t\tthree = !(sides % 3),\n \t\t\t\tvector = new Point(0, three ? -radius : radius),\n@@ -7019,7 +7529,7 @@ Path.inject({ statics: new function() {\n \t\t\t}\n \t\t\tpath._add(segments);\n \t\t\tpath._closed = true;\n-\t\t\treturn path;\n+\t\t\treturn path.set(Base.getNamed(arguments));\n \t\t},\n \n \t\tStar: function() {\n@@ -7027,7 +7537,7 @@ Path.inject({ statics: new function() {\n \t\t\t\tpoints = Base.readNamed(arguments, \'points\') * 2,\n \t\t\t\tradius1 = Base.readNamed(arguments, \'radius1\'),\n \t\t\t\tradius2 = Base.readNamed(arguments, \'radius2\'),\n-\t\t\t\tpath = createPath(arguments),\n+\t\t\t\tpath = new Path(),\n \t\t\t\tstep = 360 / points,\n \t\t\t\tvector = new Point(0, -1),\n \t\t\t\tsegments = new Array(points);\n@@ -7037,7 +7547,7 @@ Path.inject({ statics: new function() {\n \t\t\t}\n \t\t\tpath._add(segments);\n \t\t\tpath._closed = true;\n-\t\t\treturn path;\n+\t\t\treturn path.set(Base.getNamed(arguments));\n \t\t}\n \t};\n }});\n@@ -7065,16 +7575,6 @@ var CompoundPath = PathItem.extend({\n \t\treturn items;\n \t},\n \n-\treduce: function() {\n-\t\tif (this._children.length == 1) {\n-\t\t\tvar child = this._children[0];\n-\t\t\tchild.insertAbove(this);\n-\t\t\tthis.remove();\n-\t\t\treturn child;\n-\t\t}\n-\t\treturn this;\n-\t},\n-\n \treverse: function() {\n \t\tvar children = this._children;\n \t\tfor (var i = 0, l = children.length; i < l; i++)\n@@ -7140,29 +7640,32 @@ var CompoundPath = PathItem.extend({\n \t\treturn paths.join(\' \');\n \t},\n \n-\t_contains: function(point) {\n-\t\tvar children = [];\n-\t\tfor (var i = 0, l = this._children.length; i < l; i++) {\n-\t\t\tvar child = this._children[i];\n-\t\t\tif (child.contains(point))\n-\t\t\t\tchildren.push(child);\n-\t\t}\n-\t\treturn (children.length & 1) == 1 && children;\n+\t_getWinding: function(point) {\n+\t\tvar children =  this._children,\n+\t\t\twinding = 0;\n+\t\tfor (var i = 0, l = children.length; i < l; i++)\n+\t\t\twinding += children[i]._getWinding(point);\n+\t\treturn winding;\n \t},\n \n-\t_hitTest: function _hitTest(point, options) {\n+\t_hitTest : function _hitTest(point, options) {\n \t\tvar res = _hitTest.base.call(this, point,\n \t\t\t\tBase.merge(options, { fill: false }));\n-\t\tif (!res && options.fill && this.hasFill()) {\n-\t\t\tres = this._contains(point);\n-\t\t\tres = res ? new HitResult(\'fill\', res[0]) : null;\n-\t\t}\n+\t\tif (!res) {\n+\t\t\tif (options.compoundChildren) {\n+\t\t\t\tvar children =  this._children;\n+\t\t\t\tfor (var i = children.length - 1; i >= 0 && !res; i--)\n+\t\t\t\t\tres = children[i]._hitTest(point, options);\n+\t\t\t} else if (options.fill && this.hasFill()\n+\t\t\t\t\t&& this._contains(point)) {\n+\t\t\t\tres = new HitResult(\'fill\', this);\n+\t\t\t}\n+\t\t} \n \t\treturn res;\n \t},\n \n \t_draw: function(ctx, param) {\n-\t\tvar children = this._children,\n-\t\t\tstyle = this._style;\n+\t\tvar children = this._children;\n \t\tif (children.length === 0)\n \t\t\treturn;\n \t\tctx.beginPath();\n@@ -7171,9 +7674,12 @@ var CompoundPath = PathItem.extend({\n \t\t\tchildren[i].draw(ctx, param);\n \t\tif (!param.clip) {\n \t\t\tthis._setStyles(ctx);\n-\t\t\tif (style.getFillColor())\n-\t\t\t\tctx.fill();\n-\t\t\tif (style.getStrokeColor())\n+\t\t\tvar style = this._style;\n+\t\t\tif (style.hasFill()) {\n+\t\t\t\tctx.fill(style.getWindingRule());\n+\t\t\t\tctx.shadowColor = \'rgba(0,0,0,0)\';\n+\t\t\t}\n+\t\t\tif (style.hasStroke())\n \t\t\t\tctx.stroke();\n \t\t}\n \t}\n@@ -7201,13 +7707,15 @@ var CompoundPath = PathItem.extend({\n \t\t}\n \t};\n \n-\tBase.each([\'lineTo\', \'cubicCurveTo\', \'quadraticCurveTo\', \'curveTo\',\n-\t\t\t\'arcTo\', \'lineBy\', \'curveBy\', \'arcBy\'], function(key) {\n-\t\tfields[key] = function() {\n-\t\t\tvar path = getCurrentPath(this);\n-\t\t\tpath[key].apply(path, arguments);\n-\t\t};\n-\t});\n+\tBase.each([\'lineTo\', \'cubicCurveTo\', \'quadraticCurveTo\', \'curveTo\', \'arcTo\',\n+\t\t\t\'lineBy\', \'cubicCurveBy\', \'quadraticCurveBy\', \'curveBy\', \'arcBy\'],\n+\t\t\tfunction(key) {\n+\t\t\t\tfields[key] = function() {\n+\t\t\t\t\tvar path = getCurrentPath(this);\n+\t\t\t\t\tpath[key].apply(path, arguments);\n+\t\t\t\t};\n+\t\t\t}\n+\t);\n \n \treturn fields;\n });\n@@ -7515,6 +8023,7 @@ PathItem.inject(new function() {\n \t\t\tif (others)\n \t\t\t\tothers.push(other);\n \t\t\tsegment._intersection = other;\n+\t\t\tloc._segment = segment;\n \t\t}\n \t\treturn others;\n \t}\n@@ -7549,10 +8058,12 @@ PathItem.inject(new function() {\n \t\t\tpath2Clockwise = path2.isClockwise(),\n \t\t\tintersections = path1.getIntersections(path2);\n \t\tsplitPath(splitPath(intersections, true));\n-\t\tif (subtract) {\n+\t\tif (!path1Clockwise)\n+\t\t\tpath1.reverse();\n+\t\tif (!(subtract ^ path2Clockwise))\n \t\t\tpath2.reverse();\n-\t\t\tpath2Clockwise = !path2Clockwise;\n-\t\t}\n+\t\tpath1Clockwise = true;\n+\t\tpath2Clockwise = !subtract;\n \t\tvar paths = []\n \t\t\t\t.concat(path1._children || [path1])\n \t\t\t\t.concat(path2._children || [path2]),\n@@ -7679,6 +8190,10 @@ var TextItem = Item.extend({\n \t\tthis._initialize(hasProps && arg, !hasProps && Point.read(arguments));\n \t},\n \n+\t_equals: function(item) {\n+\t\treturn this._content === item._content;\n+\t},\n+\n \t_clone: function _clone(copy) {\n \t\tcopy.setContent(this._content);\n \t\treturn _clone.base.call(this, copy);\n@@ -7732,14 +8247,19 @@ var PointText = TextItem.extend({\n \t\tthis._setStyles(ctx);\n \t\tvar style = this._style,\n \t\t\tlines = this._lines,\n-\t\t\tleading = style.getLeading();\n+\t\t\tleading = style.getLeading(),\n+\t\t\tshadowColor = ctx.shadowColor;\n+\n \t\tctx.font = style.getFontStyle();\n \t\tctx.textAlign = style.getJustification();\n \t\tfor (var i = 0, l = lines.length; i < l; i++) {\n+\t\t\tctx.shadowColor = shadowColor;\n \t\t\tvar line = lines[i];\n-\t\t\tif (style.getFillColor())\n+\t\t\tif (style.hasFill()) {\n \t\t\t\tctx.fillText(line, 0, 0);\n-\t\t\tif (style.getStrokeColor())\n+\t\t\t\tctx.shadowColor = \'rgba(0,0,0,0)\';\n+\t\t\t}\n+\t\t\tif (style.hasStroke())\n \t\t\t\tctx.strokeText(line, 0, 0);\n \t\t\tctx.translate(0, leading);\n \t\t}\n@@ -8117,7 +8637,7 @@ var Color = Base.extend(new function() {\n \t\t},\n \n \t\tclone: function() {\n-\t\t\treturn new Color(this._type, this._components.slice(), this._alpha);\n+\t\t\treturn new Color(this);\n \t\t},\n \n \t\t_convert: function(type) {\n@@ -8168,7 +8688,8 @@ var Color = Base.extend(new function() {\n \t\tequals: function(color) {\n \t\t\tif (Base.isPlainValue(color))\n \t\t\t\tcolor = Color.read(arguments);\n-\t\t\treturn color === this || color && this._type === color._type\n+\t\t\treturn color === this || color && this._class === color._class\n+\t\t\t\t\t&& this._type === color._type\n \t\t\t\t\t&& this._alpha === color._alpha\n \t\t\t\t\t&& Base.equals(this._components, color._components)\n \t\t\t\t\t|| false;\n@@ -8406,8 +8927,10 @@ var Gradient = Base.extend({\n \t},\n \n \tequals: function(gradient) {\n-\t\tif (gradient && gradient.constructor == this.constructor\n-\t\t\t\t&& this._stops.length == gradient._stops.length) {\n+\t\tif (gradient === this)\n+\t\t\treturn true;\n+\t\tif (gradient &&  this._class === gradient._class\n+\t\t\t\t&& this._stops.length === gradient._stops.length) {\n \t\t\tfor (var i = 0, l = this._stops.length; i < l; i++) {\n \t\t\t\tif (!this._stops[i].equals(gradient._stops[i]))\n \t\t\t\t\treturn false;\n@@ -8476,7 +8999,7 @@ var GradientStop = Base.extend({\n \t},\n \n \tequals: function(stop) {\n-\t\treturn stop === this || stop instanceof GradientStop\n+\t\treturn stop === this || stop && this._class === stop._class\n \t\t\t\t&& this._color.equals(stop._color)\n \t\t\t\t&& this._rampPoint == stop._rampPoint\n \t\t\t\t|| false;\n@@ -8493,6 +9016,7 @@ var Style = Base.extend(new function() {\n \t\tmiterLimit: 10,\n \t\tdashOffset: 0,\n \t\tdashArray: [],\n+\t\twindingRule: \'nonzero\',\n \t\tshadowColor: undefined,\n \t\tshadowBlur: 0,\n \t\tshadowOffset: new Point(),\n@@ -8541,8 +9065,11 @@ var Style = Base.extend(new function() {\n \t\t\t\t\tif (isColor) {\n \t\t\t\t\t\tif (old)\n \t\t\t\t\t\t\tdelete old._owner;\n-\t\t\t\t\t\tif (value && value.constructor === Color)\n+\t\t\t\t\t\tif (value && value.constructor === Color) {\n+\t\t\t\t\t\t\tif (value._owner)\n+\t\t\t\t\t\t\t\tvalue = value.clone();\n \t\t\t\t\t\t\tvalue._owner = this._item;\n+\t\t\t\t\t\t}\n \t\t\t\t\t}\n \t\t\t\t\tthis._values[key] = value;\n \t\t\t\t\tif (this._item)\n@@ -8618,6 +9145,24 @@ var Style = Base.extend(new function() {\n \t\t}\n \t},\n \n+\tequals: function(style) {\n+\t\treturn style === this || style && this._class === style._class\n+\t\t\t\t&& Base.equals(this._values, style._values)\n+\t\t\t\t|| false;\n+\t},\n+\n+\thasFill: function() {\n+\t\treturn !!this.getFillColor();\n+\t},\n+\n+\thasStroke: function() {\n+\t\treturn !!this.getStrokeColor() && this.getStrokeWidth() > 0;\n+\t},\n+\n+\thasShadow: function() {\n+\t\treturn !!this.getShadowColor() && this.getShadowBlur() > 0;\n+\t},\n+\n \tgetLeading: function getLeading() {\n \t\tvar leading = getLeading.base.call(this);\n \t\treturn leading != null ? leading : this.getFontSize() * 1.2;\n@@ -8625,8 +9170,7 @@ var Style = Base.extend(new function() {\n \n \tgetFontStyle: function() {\n \t\tvar size = this.getFontSize();\n-\t\treturn (/[a-z]/i.test(size) ? size + \' \' : size + \'px \')\n-\t\t\t\t+ this.getFont();\n+\t\treturn size + (/[a-z]/i.test(size + \'\') ? \' \' : \'px \') + this.getFont();\n \t}\n \n });\n@@ -8706,7 +9250,8 @@ var DomElement = new function() {\n \t\t},\n \n \t\tgetStyles: function(el) {\n-\t\t\tvar view = el && el.ownerDocument.defaultView;\n+\t\t\tvar doc = el && el.nodeType !== 9 ? el.ownerDocument : el,\n+\t\t\t\tview = doc && doc.defaultView;\n \t\t\treturn view && view.getComputedStyle(el, \'\');\n \t\t},\n \n@@ -8795,6 +9340,15 @@ var DomElement = new function() {\n \t\tisInView: function(el) {\n \t\t\treturn !this.isInvisible(el) && this.getViewportBounds(el).intersects(\n \t\t\t\t\tthis.getBounds(el, true));\n+\t\t},\n+\n+\t\tgetPrefixValue: function(el, name) {\n+\t\t\tvar value = el[name],\n+\t\t\t\tprefixes = [\'webkit\', \'moz\', \'ms\', \'o\'],\n+\t\t\t\tsuffix = name[0].toUpperCase() + name.substring(1);\n+\t\t\tfor (var i = 0; i < 4 && value == null; i++)\n+\t\t\t\tvalue = el[prefixes[i] + suffix];\n+\t\t\treturn value;\n \t\t}\n \t};\n };\n@@ -8948,7 +9502,7 @@ var View = Base.extend(Callback, {\n \t\telement.width = size.width;\n \t\telement.height = size.height;\n \t\tif (PaperScope.hasAttribute(element, \'stats\')\n-\t\t\t\t&& typeof Stats !== \'undefined\') {\n+\t\t\t\t&& typeof Stats === \'object\') {\n \t\t\tthis._stats = new Stats();\n \t\t\tvar stats = this._stats.domElement,\n \t\t\t\tstyle = stats.style,\n@@ -9079,7 +9633,6 @@ var View = Base.extend(Callback, {\n \t_transform: function(matrix) {\n \t\tthis._matrix.concatenate(matrix);\n \t\tthis._bounds = null;\n-\t\tthis._inverse = null;\n \t\tthis._redraw();\n \t},\n \n@@ -9109,7 +9662,7 @@ var View = Base.extend(Callback, {\n \n \tgetBounds: function() {\n \t\tif (!this._bounds)\n-\t\t\tthis._bounds = this._getInverse()._transformBounds(\n+\t\t\tthis._bounds = this._matrix.inverted()._transformBounds(\n \t\t\t\t\tnew Rectangle(new Point(), this._viewSize));\n \t\treturn this._bounds;\n \t},\n@@ -9150,15 +9703,9 @@ var View = Base.extend(Callback, {\n \t},\n \n \tviewToProject: function() {\n-\t\treturn this._getInverse()._transformPoint(Point.read(arguments));\n+\t\treturn this._matrix._inverseTransform(Point.read(arguments));\n \t},\n \n-\t_getInverse: function() {\n-\t\tif (!this._inverse)\n-\t\t\tthis._inverse = this._matrix.inverted();\n-\t\treturn this._inverse;\n-\t}\n-\n }, {\n \tstatics: {\n \t\t_views: [],\n@@ -9284,13 +9831,27 @@ var CanvasView = View.extend({\n \n \tinitialize: function CanvasView(canvas) {\n \t\tif (!(canvas instanceof HTMLCanvasElement)) {\n-\t\t\tvar size = Size.read(arguments, 1);\n+\t\t\tvar size = Size.read(arguments);\n \t\t\tif (size.isZero())\n-\t\t\t\tsize = new Size(1024, 768);\n+\t\t\t\tthrow new Error(\n+\t\t\t\t\t\t\'Cannot create CanvasView with the provided arguments: \'\n+\t\t\t\t\t\t+ arguments);\n \t\t\tcanvas = CanvasProvider.getCanvas(size);\n \t\t}\n-\t\tthis._context = canvas.getContext(\'2d\');\n+\t\tvar ctx = this._context = canvas.getContext(\'2d\');\n \t\tthis._eventCounters = {};\n+\t\tvar ratio = (window.devicePixelRatio || 1) / (DomElement.getPrefixValue(\n+\t\t\t\tctx, \'backingStorePixelRatio\') || 1);\n+\t\tif (ratio > 1) {\n+\t\t\tvar width = canvas.clientWidth,\n+\t\t\t\theight = canvas.clientHeight,\n+\t\t\t\tstyle = canvas.style;\n+\t\t\tcanvas.width = width * ratio;\n+\t\t\tcanvas.height = height * ratio;\n+\t\t\tstyle.width = width + \'px\';\n+\t\t\tstyle.height = height + \'px\';\n+\t\t\tctx.scale(ratio, ratio);\n+\t\t}\n \t\tView.call(this, canvas);\n \t},\n \n@@ -9569,7 +10130,7 @@ var MouseEvent = Event.extend({\n \t\t\t|| DomElement.find(\'body\').appendChild(\n \t\t\t\tDomElement.create(\'div\', { \'class\': \'palettejs-panel\' }));\n \t\tthis._element = parent.appendChild(\n-\t\t\tDomElement.create(\'table\', { \'class\': \'palettejs-pane\' })),\n+\t\t\tDomElement.create(\'table\', { \'class\': \'palettejs-pane\' }));\n \t\tthis._title = title;\n \t\tif (!values)\n \t\t\tvalues = {};\n@@ -9670,23 +10231,24 @@ var Component = Base.extend(Callback, {\n \t\t\t\t\t: typeof obj.value;\n \t\tthis._info = this._types[this._type] || { type: this._type };\n \t\tvar that = this,\n-\t\t\tfireChange = false;\n+\t\t\tdontFire = true;\n \t\tthis._inputItem = DomElement.create(this._info.tag || \'input\', {\n \t\t\ttype: this._info.type,\n \t\t\tevents: {\n \t\t\t\tchange: function() {\n \t\t\t\t\tthat.setValue(\n-\t\t\t\t\t\tDomElement.get(this, that._info.value || \'value\'));\n-\t\t\t\t\tif (fireChange) {\n-\t\t\t\t\t\tthat._palette.fire(\'change\', that, that.name, that._value);\n-\t\t\t\t\t\tthat.fire(\'change\', that._value);\n-\t\t\t\t\t}\n+\t\t\t\t\t\tDomElement.get(this, that._info.value || \'value\'),\n+\t\t\t\t\t\tdontFire);\n \t\t\t\t},\n \t\t\t\tclick: function() {\n \t\t\t\t\tthat.fire(\'click\');\n \t\t\t\t}\n \t\t\t}\n \t\t});\n+\t\tthis.attach(\'change\', function(value) {\n+\t\t\tif (!dontFire)\n+\t\t\t\tthis._palette.fire(\'change\', this, this.name, value);\n+\t\t});\n \t\tthis._element = DomElement.create(\'tr\', [\n \t\t\tthis._labelItem = DomElement.create(\'td\'),\n \t\t\t\'td\', [this._inputItem]\n@@ -9695,7 +10257,7 @@ var Component = Base.extend(Callback, {\n \t\t\tthis[key] = value;\n \t\t}, this);\n \t\tthis._defaultValue = this._value;\n-\t\tfireChange = true;\n+\t\tdontFire = false;\n \t},\n \n \tgetType: function() {\n@@ -9725,11 +10287,17 @@ var Component = Base.extend(Callback, {\n \t\treturn this._value;\n \t},\n \n-\tsetValue: function(value) {\n+\tsetValue: function(value, _dontFire) {\n \t\tvar key = this._info.value || \'value\';\n \t\tDomElement.set(this._inputItem, key, value);\n \t\tvalue = DomElement.get(this._inputItem, key);\n-\t\tthis._value = this._info.number ? parseFloat(value, 10) : value;\n+\t\tif (this._info.number)\n+\t\t\tvalue = parseFloat(value, 10);\n+\t\tif (this._value !== value) {\n+\t\t\tthis._value = value;\n+\t\t\tif (!_dontFire)\n+\t\t\t\tthis.fire(\'change\', value);\n+\t\t}\n \t},\n \n \tgetRange: function() {\n@@ -10366,19 +10934,15 @@ new function() {\n \t\t\tdocument.createElementNS(\'http://www.w3.org/2000/svg\', tag), attrs);\n \t}\n \n-\tfunction getDistance(segments, index1, index2) {\n-\t\treturn segments[index1]._point.getDistance(segments[index2]._point);\n-\t}\n-\n-\tfunction getTransform(item, coordinates) {\n+\tfunction getTransform(item, coordinates, center) {\n \t\tvar matrix = item._matrix,\n \t\t\ttrans = matrix.getTranslation(),\n \t\t\tattrs = {};\n \t\tif (coordinates) {\n \t\t\tmatrix = matrix.shiftless();\n \t\t\tvar point = matrix._inverseTransform(trans);\n-\t\t\tattrs.x = point.x;\n-\t\t\tattrs.y = point.y;\n+\t\t\tattrs[center ? \'cx\' : \'x\'] = point.x;\n+\t\t\tattrs[center ? \'cy\' : \'y\'] = point.y;\n \t\t\ttrans = null;\n \t\t}\n \t\tif (matrix.isIdentity())\n@@ -10401,80 +10965,13 @@ new function() {\n \t\treturn attrs;\n \t}\n \n-\tfunction determineAngle(path, segments, type, center) {\n-\t\tvar topCenter = type === \'rect\'\n-\t\t\t\t? segments[1]._point.add(segments[2]._point).divide(2)\n-\t\t\t\t: type === \'roundrect\'\n-\t\t\t\t? segments[3]._point.add(segments[4]._point).divide(2)\n-\t\t\t\t: type === \'circle\' || type === \'ellipse\'\n-\t\t\t\t? segments[1]._point\n-\t\t\t\t: null;\n-\t\tvar angle = topCenter && topCenter.subtract(center).getAngle() + 90;\n-\t\treturn Numerical.isZero(angle || 0) ? 0 : angle;\n-\t}\n-\n-\tfunction determineType(path, segments) {\n-\t\tfunction isColinear(i, j) {\n-\t\t\tvar seg1 = segments[i],\n-\t\t\t\tseg2 = seg1.getNext(),\n-\t\t\t\tseg3 = segments[j],\n-\t\t\t\tseg4 = seg3.getNext();\n-\t\t\treturn seg1._handleOut.isZero() && seg2._handleIn.isZero()\n-\t\t\t\t\t&& seg3._handleOut.isZero() && seg4._handleIn.isZero()\n-\t\t\t\t\t&& seg2._point.subtract(seg1._point).isColinear(\n-\t\t\t\t\t\tseg4._point.subtract(seg3._point));\n-\t\t}\n-\n-\t\tfunction isArc(i) {\n-\t\t\tvar segment = segments[i],\n-\t\t\t\tnext = segment.getNext(),\n-\t\t\t\thandle1 = segment._handleOut,\n-\t\t\t\thandle2 = next._handleIn,\n-\t\t\t\tkappa = Numerical.KAPPA;\n-\t\t\tif (handle1.isOrthogonal(handle2)) {\n-\t\t\t\tvar from = segment._point,\n-\t\t\t\t\tto = next._point,\n-\t\t\t\t\tcorner = new Line(from, handle1, true).intersect(\n-\t\t\t\t\t\t\tnew Line(to, handle2, true), true);\n-\t\t\t\treturn corner && Numerical.isZero(handle1.getLength() /\n-\t\t\t\t\t\tcorner.subtract(from).getLength() - kappa)\n-\t\t\t\t\t&& Numerical.isZero(handle2.getLength() /\n-\t\t\t\t\t\tcorner.subtract(to).getLength() - kappa);\n-\t\t\t}\n-\t\t}\n-\n-\t\tif (path.isPolygon()) {\n-\t\t\treturn  segments.length === 4 && path._closed\n-\t\t\t\t\t&& isColinear(0, 2) && isColinear(1, 3)\n-\t\t\t\t\t? \'rect\'\n-\t\t\t\t\t: segments.length === 0\n-\t\t\t\t\t\t? \'empty\'\n-\t\t\t\t\t\t: segments.length >= 3\n-\t\t\t\t\t\t\t? path._closed ? \'polygon\' : \'polyline\'\n-\t\t\t\t\t\t\t: \'line\';\n-\t\t} else if (path._closed) {\n-\t\t\tif (segments.length === 8\n-\t\t\t\t\t&& isArc(0) && isArc(2) && isArc(4) && isArc(6)\n-\t\t\t\t\t&& isColinear(1, 5) && isColinear(3, 7)) {\n-\t\t\t\treturn \'roundrect\';\n-\t\t\t} else if (segments.length === 4\n-\t\t\t\t\t&& isArc(0) && isArc(1) && isArc(2) && isArc(3)) {\n-\t\t\t\treturn Numerical.isZero(getDistance(segments, 0, 2)\n-\t\t\t\t\t\t- getDistance(segments, 1, 3))\n-\t\t\t\t\t\t? \'circle\'\n-\t\t\t\t\t\t: \'ellipse\';\n-\t\t\t} \n-\t\t}\n-\t\treturn \'path\';\n-\t}\n-\n-\tfunction exportGroup(item) {\n+\tfunction exportGroup(item, options) {\n \t\tvar attrs = getTransform(item),\n \t\t\tchildren = item._children;\n \t\tvar node = createElement(\'g\', attrs);\n \t\tfor (var i = 0, l = children.length; i < l; i++) {\n \t\t\tvar child = children[i];\n-\t\t\tvar childNode = exportSVG(child);\n+\t\t\tvar childNode = exportSVG(child, options);\n \t\t\tif (childNode) {\n \t\t\t\tif (child.isClipMask()) {\n \t\t\t\t\tvar clip = createElement(\'clipPath\');\n@@ -10502,95 +10999,72 @@ new function() {\n \t\treturn createElement(\'image\', attrs);\n \t}\n \n-\tfunction exportPath(item) {\n+\tfunction exportPath(item, options) {\n+\t\tif (options.matchShapes) {\n+\t\t\tvar shape = item.toShape(false);\n+\t\t\tif (shape)\n+\t\t\t\treturn exportShape(shape, options);\n+\t\t}\n \t\tvar segments = item._segments,\n-\t\t\tcenter = item.getPosition(true),\n-\t\t\ttype = determineType(item, segments),\n-\t\t\tangle = determineAngle(item, segments, type, center),\n+\t\t\ttype,\n \t\t\tattrs;\n-\t\tswitch (type) {\n-\t\tcase \'empty\':\n+\t\tif (segments.length === 0)\n \t\t\treturn null;\n-\t\tcase \'path\':\n+\t\tif (item.isPolygon()) {\n+\t\t\tif (segments.length >= 3) {\n+\t\t\t\ttype = item._closed ? \'polygon\' : \'polyline\';\n+\t\t\t\tvar parts = [];\n+\t\t\t\tfor(i = 0, l = segments.length; i < l; i++)\n+\t\t\t\t\tparts.push(formatter.point(segments[i]._point));\n+\t\t\t\tattrs = {\n+\t\t\t\t\tpoints: parts.join(\' \')\n+\t\t\t\t};\n+\t\t\t} else {\n+\t\t\t\ttype = \'line\';\n+\t\t\t\tvar first = segments[0]._point,\n+\t\t\t\t\tlast = segments[segments.length - 1]._point;\n+\t\t\t\tattrs = {\n+\t\t\t\t\tx1: first.x,\n+\t\t\t\t\ty1: first.y,\n+\t\t\t\t\tx2: last.x,\n+\t\t\t\t\ty2: last.y\n+\t\t\t\t};\n+\t\t\t}\n+\t\t} else {\n+\t\t\ttype = \'path\';\n \t\t\tvar data = item.getPathData();\n \t\t\tattrs = data && { d: data };\n-\t\t\tbreak;\n-\t\tcase \'polyline\':\n-\t\tcase \'polygon\':\n-\t\t\tvar parts = [];\n-\t\t\tfor(i = 0, l = segments.length; i < l; i++)\n-\t\t\t\tparts.push(formatter.point(segments[i]._point));\n-\t\t\tattrs = {\n-\t\t\t\tpoints: parts.join(\' \')\n-\t\t\t};\n-\t\t\tbreak;\n-\t\tcase \'rect\':\n-\t\t\tvar width = getDistance(segments, 0, 3),\n-\t\t\t\theight = getDistance(segments, 0, 1),\n-\t\t\t\tpoint = segments[1]._point.rotate(-angle, center);\n-\t\t\tattrs = {\n-\t\t\t\tx: point.x,\n-\t\t\t\ty: point.y,\n-\t\t\t\twidth: width,\n-\t\t\t\theight: height\n-\t\t\t};\n-\t\t\tbreak;\n-\t\tcase \'roundrect\':\n-\t\t\ttype = \'rect\';\n-\t\t\tvar width = getDistance(segments, 1, 6),\n-\t\t\t\theight = getDistance(segments, 0, 3),\n-\t\t\t\trx = (width - getDistance(segments, 0, 7)) / 2,\n-\t\t\t\try = (height - getDistance(segments, 1, 2)) / 2,\n-\t\t\t\tleft = segments[3]._point, \n-\t\t\t\tright = segments[4]._point, \n-\t\t\t\tpoint = left.subtract(right.subtract(left).normalize(rx))\n-\t\t\t\t\t\t.rotate(-angle, center);\n-\t\t\tattrs = {\n-\t\t\t\tx: point.x,\n-\t\t\t\ty: point.y,\n-\t\t\t\twidth: width,\n-\t\t\t\theight: height,\n-\t\t\t\trx: rx,\n-\t\t\t\try: ry\n-\t\t\t};\n-\t\t\tbreak;\n-\t\tcase\'line\':\n-\t\t\tvar first = segments[0]._point,\n-\t\t\t\tlast = segments[segments.length - 1]._point;\n-\t\t\tattrs = {\n-\t\t\t\tx1: first.x,\n-\t\t\t\ty1: first.y,\n-\t\t\t\tx2: last.x,\n-\t\t\t\ty2: last.y\n-\t\t\t};\n-\t\t\tbreak;\n-\t\tcase \'circle\':\n-\t\t\tvar radius = getDistance(segments, 0, 2) / 2;\n-\t\t\tattrs = {\n-\t\t\t\tcx: center.x,\n-\t\t\t\tcy: center.y,\n-\t\t\t\tr: radius\n-\t\t\t};\n-\t\t\tbreak;\n-\t\tcase \'ellipse\':\n-\t\t\tvar rx = getDistance(segments, 2, 0) / 2,\n-\t\t\t\try = getDistance(segments, 3, 1) / 2;\n-\t\t\tattrs = {\n-\t\t\t\tcx: center.x,\n-\t\t\t\tcy: center.y,\n-\t\t\t\trx: rx,\n-\t\t\t\try: ry\n-\t\t\t};\n-\t\t\tbreak;\n-\t\t}\n-\t\tif (angle) {\n-\t\t\tattrs.transform = \'rotate(\' + formatter.number(angle) + \',\'\n-\t\t\t\t\t+ formatter.point(center) + \')\';\n-\t\t\titem._gradientMatrix = new Matrix().rotate(-angle, center);\n \t\t}\n \t\treturn createElement(type, attrs);\n \t}\n \n+\tfunction exportShape(item) {\n+\t\tvar shape = item._shape,\n+\t\t\tradius = item._radius,\n+\t\t\tattrs = getTransform(item, true, shape !== \'rectangle\');\n+\t\tif (shape === \'rectangle\') {\n+\t\t\tshape = \'rect\'; \n+\t\t\tvar size = item._size,\n+\t\t\t\twidth = size.width,\n+\t\t\t\theight = size.height;\n+\t\t\tattrs.x -= width / 2;\n+\t\t\tattrs.y -= height / 2;\n+\t\t\tattrs.width = width;\n+\t\t\tattrs.height = height;\n+\t\t\tif (radius.isZero())\n+\t\t\t\tradius = null;\n+\t\t}\n+\t\tif (radius) {\n+\t\t\tif (shape === \'circle\') {\n+\t\t\t\tattrs.r = radius;\n+\t\t\t} else {\n+\t\t\t\tattrs.rx = radius.width;\n+\t\t\t\tattrs.ry = radius.height;\n+\t\t\t}\n+\t\t}\n+\t\treturn createElement(shape, attrs);\n+\t}\n+\n \tfunction exportCompoundPath(item) {\n \t\tvar attrs = getTransform(item, true);\n \t\tvar data = item.getPathData();\n@@ -10599,17 +11073,17 @@ new function() {\n \t\treturn createElement(\'path\', attrs);\n \t}\n \n-\tfunction exportPlacedSymbol(item) {\n+\tfunction exportPlacedSymbol(item, options) {\n \t\tvar attrs = getTransform(item, true),\n \t\t\tsymbol = item.getSymbol(),\n-\t\t\tsymbolNode = getDefinition(symbol, \'symbol\');\n+\t\t\tsymbolNode = getDefinition(symbol, \'symbol\'),\n \t\t\tdefinition = symbol.getDefinition(),\n \t\t\tbounds = definition.getBounds();\n \t\tif (!symbolNode) {\n \t\t\tsymbolNode = createElement(\'symbol\', {\n \t\t\t\tviewBox: formatter.rectangle(bounds)\n \t\t\t});\n-\t\t\tsymbolNode.appendChild(exportSVG(definition));\n+\t\t\tsymbolNode.appendChild(exportSVG(definition, options));\n \t\t\tsetDefinition(symbol, symbolNode, \'symbol\');\n \t\t}\n \t\tattrs.href = \'#\' + symbolNode.id;\n@@ -10620,14 +11094,13 @@ new function() {\n \t\treturn createElement(\'use\', attrs);\n \t}\n \n-\tfunction exportGradient(color, item) {\n+\tfunction exportGradient(color) {\n \t\tvar gradientNode = getDefinition(color, \'color\');\n \t\tif (!gradientNode) {\n \t\t\tvar gradient = color.getGradient(),\n \t\t\t\tradial = gradient._radial,\n-\t\t\t\tmatrix = item._gradientMatrix,\n-\t\t\t\torigin = color.getOrigin().transform(matrix),\n-\t\t\t\tdestination = color.getDestination().transform(matrix),\n+\t\t\t\torigin = color.getOrigin().transform(),\n+\t\t\t\tdestination = color.getDestination().transform(),\n \t\t\t\tattrs;\n \t\t\tif (radial) {\n \t\t\t\tattrs = {\n@@ -10637,7 +11110,7 @@ new function() {\n \t\t\t\t};\n \t\t\t\tvar highlight = color.getHighlight();\n \t\t\t\tif (highlight) {\n-\t\t\t\t\thighlight = highlight.transform(matrix);\n+\t\t\t\t\thighlight = highlight.transform();\n \t\t\t\t\tattrs.fx = highlight.x;\n \t\t\t\t\tattrs.fy = highlight.y;\n \t\t\t\t}\n@@ -10681,6 +11154,7 @@ new function() {\n \t\tlayer: exportGroup,\n \t\traster: exportRaster,\n \t\tpath: exportPath,\n+\t\tshape: exportShape,\n \t\t\'compound-path\': exportCompoundPath,\n \t\t\'placed-symbol\': exportPlacedSymbol,\n \t\t\'point-text\': exportText\n@@ -10725,7 +11199,6 @@ new function() {\n \t\tif (item._visibility != null && !item._visibility)\n \t\t\tattrs.visibility = \'hidden\';\n \n-\t\tdelete item._gradientMatrix; \n \t\treturn setAttributes(node, attrs);\n \t}\n \n@@ -10760,35 +11233,36 @@ new function() {\n \t\t\tdefs.appendChild(definitions.svgs[i]);\n \t\t}\n \t\tdefinitions = null;\n-\t\treturn options && options.asString\n+\t\treturn options.asString\n \t\t\t\t? new XMLSerializer().serializeToString(svg)\n \t\t\t\t: svg;\n \t}\n \n-\tfunction exportSVG(item) {\n+\tfunction exportSVG(item, options) {\n \t\tvar exporter = exporters[item._type],\n-\t\t\tnode = exporter && exporter(item, item._type);\n+\t\t\tnode = exporter && exporter(item, options);\n \t\tif (node && item._data)\n \t\t\tnode.setAttribute(\'data-paper-data\', JSON.stringify(item._data));\n \t\treturn node && applyStyle(item, node);\n \t}\n \n \tfunction setOptions(options) {\n-\t\tformatter = options && options.precision\n-\t\t\t\t? new Formatter(options.precision)\n-\t\t\t\t: Formatter.instance;\n+\t\tif (!options)\n+\t\t\toptions = {};\n+\t\tformatter = new Formatter(options.precision);\n+\t\treturn options;\n \t}\n \n \tItem.inject({\n \t\texportSVG: function(options) {\n-\t\t\tsetOptions(options);\n-\t\t\treturn exportDefinitions(exportSVG(this), options);\n+\t\t\toptions = setOptions(options);\n+\t\t\treturn exportDefinitions(exportSVG(this, options), options);\n \t\t}\n \t});\n \n \tProject.inject({\n \t\texportSVG: function(options) {\n-\t\t\tsetOptions(options);\n+\t\t\toptions = setOptions(options);\n \t\t\tvar layers = this.layers,\n \t\t\t\tsize = this.view.getSize(),\n \t\t\t\tnode = createElement(\'svg\', {\n@@ -10801,7 +11275,7 @@ new function() {\n \t\t\t\t\t\'xmlns:xlink\': \'http://www.w3.org/1999/xlink\'\n \t\t\t\t});\n \t\t\tfor (var i = 0, l = layers.length; i < l; i++)\n-\t\t\t\tnode.appendChild(exportSVG(layers[i]));\n+\t\t\t\tnode.appendChild(exportSVG(layers[i], options));\n \t\t\treturn exportDefinitions(node, options);\n \t\t}\n \t});\n@@ -10855,10 +11329,10 @@ new function() {\n \t\t\t\t\t\t\t\t: value;\n \t}\n \n-\tfunction importGroup(node, type) {\n+\tfunction importGroup(node, type, options) {\n \t\tvar nodes = node.childNodes,\n \t\t\tclip = type === \'clippath\',\n-\t\t\titem = clip ? new CompoundPath() : new Group(),\n+\t\t\titem = new Group(),\n \t\t\tproject = item._project,\n \t\t\tcurrentStyle = project._currentStyle,\n \t\t\tchildren = [];\n@@ -10870,14 +11344,10 @@ new function() {\n \t\tfor (var i = 0, l = nodes.length; i < l; i++) {\n \t\t\tvar childNode = nodes[i],\n \t\t\t\tchild;\n-\t\t\tif (childNode.nodeType == 1 && (child = importSVG(childNode))) {\n-\t\t\t\tif (clip && child instanceof CompoundPath) {\n-\t\t\t\t\tchildren.push.apply(children, child.removeChildren());\n-\t\t\t\t\tchild.remove();\n-\t\t\t\t} else if (!(child instanceof Symbol)) {\n-\t\t\t\t\tchildren.push(child);\n-\t\t\t\t}\n-\t\t\t}\n+\t\t\tif (childNode.nodeType === 1\n+\t\t\t\t\t&& (child = importSVG(childNode, options))\n+\t\t\t\t\t&& !(child instanceof Symbol))\n+\t\t\t\tchildren.push(child);\n \t\t}\n \t\titem.addChildren(children);\n \t\tif (clip)\n@@ -10915,7 +11385,7 @@ new function() {\n \t\t\tstops = [];\n \t\tfor (var i = 0, l = nodes.length; i < l; i++) {\n \t\t\tvar child = nodes[i];\n-\t\t\tif (child.nodeType == 1)\n+\t\t\tif (child.nodeType === 1)\n \t\t\t\tstops.push(applyAttributes(new GradientStop(), child));\n \t\t}\n \t\tvar isRadial = type === \'radialgradient\',\n@@ -10935,6 +11405,10 @@ new function() {\n \t}\n \n \tvar importers = {\n+\t\t\'#document\': function(node, type, options) {\n+\t\t\treturn importSVG(node.childNodes[0], options);\n+\t\t},\n+\n \t\tg: importGroup,\n \t\tsvg: importGroup,\n \t\tclippath: importGroup,\n@@ -10972,22 +11446,22 @@ new function() {\n \t\t},\n \n \t\tcircle: function(node) {\n-\t\t\treturn new Path.Circle(getPoint(node, \'cx\', \'cy\'),\n+\t\t\treturn new Shape.Circle(getPoint(node, \'cx\', \'cy\'),\n \t\t\t\t\tgetValue(node, \'r\'));\n \t\t},\n \n \t\tellipse: function(node) {\n-\t\t\tvar center = getPoint(node, \'cx\', \'cy\'),\n-\t\t\t\tradius = getSize(node, \'rx\', \'ry\');\n-\t\t\treturn new Path.Ellipse(new Rectangle(center.subtract(radius),\n-\t\t\t\t\tcenter.add(radius)));\n+\t\t\treturn new Shape.Ellipse({\n+\t\t\t\tcenter: getPoint(node, \'cx\', \'cy\'),\n+\t\t\t\tradius: getSize(node, \'rx\', \'ry\')\n+\t\t\t});\n \t\t},\n \n \t\trect: function(node) {\n \t\t\tvar point = getPoint(node, \'x\', \'y\'),\n \t\t\t\tsize = getSize(node, \'width\', \'height\'),\n \t\t\t\tradius = getSize(node, \'rx\', \'ry\');\n-\t\t\treturn new Path.Rectangle(new Rectangle(point, size), radius);\n+\t\t\treturn new Shape.Rectangle(new Rectangle(point, size), radius);\n \t\t},\n \n \t\tline: function(node) {\n@@ -10996,8 +11470,8 @@ new function() {\n \t\t},\n \n \t\ttext: function(node) {\n-\t\t\tvar text = new PointText(getPoint(node, \'x\', \'y\', false)\n-\t\t\t\t\t.add(getPoint(node, \'dx\', \'dy\', false)));\n+\t\t\tvar text = new PointText(getPoint(node, \'x\', \'y\')\n+\t\t\t\t\t.add(getPoint(node, \'dx\', \'dy\')));\n \t\t\ttext.setContent(node.textContent.trim() || \'\');\n \t\t\treturn text;\n \t\t}\n@@ -11018,7 +11492,7 @@ new function() {\n \t\t\tswitch (command) {\n \t\t\tcase \'matrix\':\n \t\t\t\tmatrix.concatenate(\n-\t\t\t\t\t\tnew Matrix(v[0], v[2], v[1], v[3], v[4], v[5]));\n+\t\t\t\t\t\tnew Matrix(v[0], v[1], v[2], v[3], v[4], v[5]));\n \t\t\t\tbreak;\n \t\t\tcase \'rotate\':\n \t\t\t\tmatrix.rotate(v[0], v[1], v[2]);\n@@ -11051,6 +11525,12 @@ new function() {\n \t\tthis[entry.attribute] = function(item, value) {\n \t\t\titem[entry.set](\n \t\t\t\t\tconvertValue(value, entry.type, entry.fromSVG));\n+\t\t\tif (entry.type === \'color\' && item instanceof Shape) {\n+\t\t\t\tvar color = item[entry.get]();\n+\t\t\t\tif (color)\n+\t\t\t\t\tcolor.transform(new Matrix().translate(\n+\t\t\t\t\t\t\titem.getPosition(true).negate()));\n+\t\t\t}\n \t\t};\n \t}, {}), {\n \t\tid: function(item, value) {\n@@ -11112,7 +11592,7 @@ new function() {\n \t\t\t\tvar clip = getAttribute(node, \'overflow\', styles) != \'visible\',\n \t\t\t\t\tgroup = item._definition;\n \t\t\t\tif (clip && !rect.contains(group.getBounds())) {\n-\t\t\t\t\tclip = new Path.Rectangle(rect).transform(group._matrix);\n+\t\t\t\t\tclip = new Shape.Rectangle(rect).transform(group._matrix);\n \t\t\t\t\tclip.setClipMask(true);\n \t\t\t\t\tgroup.addChild(clip);\n \t\t\t\t}\n@@ -11155,32 +11635,40 @@ new function() {\n \t\treturn match && definitions[match[1]];\n \t}\n \n-\tfunction importSVG(node, clearDefs) {\n+\tfunction importSVG(node, options, clearDefs) {\n+\t\tif (!options)\n+\t\t\toptions = {};\n \t\tif (typeof node === \'string\')\n \t\t\tnode = new DOMParser().parseFromString(node, \'image/svg+xml\');\n \t\tvar type = node.nodeName.toLowerCase(),\n \t\t\timporter = importers[type],\n-\t\t\titem = importer && importer(node, type),\n-\t\t\tdata = node.getAttribute(\'data-paper-data\');\n-\t\tif (item && !(item instanceof Group))\n-\t\t\titem = applyAttributes(item, node);\n-\t\tif (item && data)\n-\t\t\titem._data = JSON.parse(data);\n+\t\t\titem = importer && importer(node, type, options),\n+\t\t\tdata = type !== \'#document\' && node.getAttribute(\'data-paper-data\');\n+\t\tif (item) {\n+\t\t\tif (!(item instanceof Group))\n+\t\t\t\titem = applyAttributes(item, node);\n+\t\t\tif (options.expandShapes && item instanceof Shape) {\n+\t\t\t\titem.remove();\n+\t\t\t\titem = item.toPath();\n+\t\t\t}\n+\t\t\tif (data)\n+\t\t\t\titem._data = JSON.parse(data);\n+\t\t}\n \t\tif (clearDefs)\n \t\t\tdefinitions = {};\n \t\treturn item;\n \t}\n \n \tItem.inject({\n-\t\timportSVG: function(node) {\n-\t\t\treturn this.addChild(importSVG(node, true));\n+\t\timportSVG: function(node, options) {\n+\t\t\treturn this.addChild(importSVG(node, options, true));\n \t\t}\n \t});\n \n \tProject.inject({\n-\t\timportSVG: function(node) {\n+\t\timportSVG: function(node, options) {\n \t\t\tthis.activate();\n-\t\t\treturn importSVG(node, true);\n+\t\t\treturn importSVG(node, options, true);\n \t\t}\n \t});\n };\n@@ -11195,7 +11683,7 @@ paper = new (PaperScope.inject(Base.merge(Base.exports, {\n })))();\n \n if (typeof define === \'function\' && define.amd)\n-\tdefine(paper);\n+\tdefine(\'paper\', paper);\n \n return paper;\n };\n@@ -11293,8 +11781,8 @@ paper.PaperScope.prototype.PaperScript = (function(root) {\n \t\t\tcode = code.substring(0, start) + str + code.substring(end);\n \t\t}\n \n-\t\tfunction walkAst(node) {\n-\t\t\tif (!node || node.type === \'MemberExpression\' && node.computed)\n+\t\tfunction walkAst(node, parent) {\n+\t\t\tif (!node)\n \t\t\t\treturn;\n \t\t\tfor (var key in node) {\n \t\t\t\tif (key === \'range\')\n@@ -11302,9 +11790,9 @@ paper.PaperScope.prototype.PaperScript = (function(root) {\n \t\t\t\tvar value = node[key];\n \t\t\t\tif (Array.isArray(value)) {\n \t\t\t\t\tfor (var i = 0, l = value.length; i < l; i++)\n-\t\t\t\t\t\twalkAst(value[i]);\n+\t\t\t\t\t\twalkAst(value[i], node);\n \t\t\t\t} else if (value && typeof value === \'object\') {\n-\t\t\t\t\twalkAst(value);\n+\t\t\t\t\twalkAst(value, node);\n \t\t\t\t}\n \t\t\t}\n \t\t\tswitch (node && node.type) {\n@@ -11327,7 +11815,11 @@ paper.PaperScope.prototype.PaperScript = (function(root) {\n \t\t\t\t}\n \t\t\t\tbreak;\n \t\t\tcase \'UpdateExpression\':\n-\t\t\t\tif (!node.prefix) {\n+\t\t\t\tif (!node.prefix && !(parent && (\n+\t\t\t\t\t\tparent.type === \'BinaryExpression\'\n+\t\t\t\t\t\t\t&& /^[=!<>]/.test(parent.operator)\n+\t\t\t\t\t\t|| parent.type === \'MemberExpression\'\n+\t\t\t\t\t\t\t&& parent.computed))) {\n \t\t\t\t\tvar arg = getCode(node.argument);\n \t\t\t\t\treplaceCode(node, arg + \' = _$_(\' + arg + \', "\'\n \t\t\t\t\t\t\t+ node.operator[0] + \'", 1)\');\ndiff --git a/PlayerController/playercore.js b/PlayerController/playercore.js\nindex ad28a64d..b25e703f 100644\n--- a/PlayerController/playercore.js\n+++ b/PlayerController/playercore.js\n@@ -1081,6 +1081,11 @@ function Grid_DrawSquare(id, x, y, width, height, text, fill) {\n     gridApi.drawCustomLayerSquare(id, parseInt(x), parseInt(y), parseInt(width), parseInt(height), text, fill);\r\n }\r\n \r\n+function Grid_DrawSvg(symbol, x, y, width, height) {\r\n+    if (!_canvasSupported) return;\r\n+    gridApi.drawCustomLayerSvg(symbol, parseInt(x), parseInt(y), parseInt(width), parseInt(height));\r\n+}\r\n+\r\n // JQUERY.MOUSEWEHEEL.JS ****************************************************************************************************************\r\n // https://github.com/brandonaaron/jquery-mousewheel\r\n \r\ndiff --git a/WorldModel/WorldModel/Core/CoreGrid.aslx b/WorldModel/WorldModel/Core/CoreGrid.aslx\nindex 3d6ae3bb..bf89335b 100644\n--- a/WorldModel/WorldModel/Core/CoreGrid.aslx\n+++ b/WorldModel/WorldModel/Core/CoreGrid.aslx\n@@ -209,6 +209,10 @@\n     JS.Grid_DrawSquare(id, x, y, width, height, text, fill)\r\n   </function>\r\n \r\n+  <function name="Grid_DrawSvg" parameters="symbol, x, y, width, height">\r\n+    JS.Grid_DrawSvg(symbol, x, y, width, height)\r\n+  </function>\r\n+\r\n   <function name="JS_GridSquareClick" parameters="parameterstring">\r\n     parameters = Split(parameterstring, ";")\r\n     x = ToInt(StringListItem(parameters, 0))\r\ndiff --git a/WorldModel/WorldModel/Functions/ExpressionOwner.cs b/WorldModel/WorldModel/Functions/ExpressionOwner.cs\nindex 335c6f01..9f9b549d 100644\n--- a/WorldModel/WorldModel/Functions/ExpressionOwner.cs\n+++ b/WorldModel/WorldModel/Functions/ExpressionOwner.cs\n@@ -484,6 +484,12 @@ namespace TextAdventures.Quest.Functions\n             return m_worldModel.GetExternalURL(filename);\r\n         }\r\n \r\n+        public string GetFileData(string filename)\r\n+        {\r\n+            if (filename.Contains("..")) throw new ArgumentOutOfRangeException("Invalid filename");\r\n+            return m_worldModel.GetResourceData(filename);\r\n+        }\r\n+\r\n         public string GetUniqueElementName(string name)\r\n         {\r\n             return m_worldModel.GetUniqueElementName(name);\r\ndiff --git a/WorldModel/WorldModel/WorldModel.cs b/WorldModel/WorldModel/WorldModel.cs\nindex f69a2541..910ebb2e 100644\n--- a/WorldModel/WorldModel/WorldModel.cs\n+++ b/WorldModel/WorldModel/WorldModel.cs\n@@ -1714,6 +1714,11 @@ namespace TextAdventures.Quest\n             return new FileStream(GetExternalPath(filename), FileMode.Open, FileAccess.Read);\r\n         }\r\n \r\n+        public string GetResourceData(string filename)\r\n+        {\r\n+            return File.ReadAllText(GetExternalPath(filename));\r\n+        }\r\n+\r\n         public string GetResourcePath(string filename)\r\n         {\r\n             return TryGetExternalPath(filename);\r\n', 'commit_subject': 'Implementing support for drawing SVG graphics to custom drawing layer', 'repo_name': 'textadventures/quest', 'committer': {'name': 'Alex Warren', 'email': '60c6d277a8bd81de7fdde19201bf9c58a3df08f4@axeuk.com', 'time_sec': '1383245539', 'tz_offset': '0', 'date': {'seconds': '1383245539'}}, 'commit_tree': 'd887c944124d69e4f3293c04d343d559f1a2a393', 'commit_message': 'Implementing support for drawing SVG graphics to custom drawing layer\n', 'language_name': 'HTML', 'license': 'mit', 'hunks': '@@ -1714,6 +1714,11 @@ namespace TextAdventures.Quest\n             return new FileStream(GetExternalPath(filename), FileMode.Open, FileAccess.Read);\r\n         }\r\n \r\n+        public string GetResourceData(string filename)\r\n+        {\r\n+            return File.ReadAllText(GetExternalPath(filename));\r\n+        }\r\n+\r\n         public string GetResourcePath(string filename)\r\n         {\r\n             return TryGetExternalPath(filename);\r\n', 'addition_count': 5, 'deletion_count': 0, 'src_file': 'a/WorldModel/WorldModel/WorldModel.cs', 'tgt_file': 'b/WorldModel/WorldModel/WorldModel.cs', 'file_extension': '.cs', 'before_file': 'using System;\nusing System.Collections.Generic;\nusing System.Collections.Specialized;\nusing System.IO;\nusing System.Threading;\nusing TextAdventures.Quest.Scripts;\nusing System.Linq;\nusing System.Net;\nusing TextAdventures.Quest.Functions;\n\nnamespace TextAdventures.Quest\n{\n    public enum GameState\n    {\n        NotStarted,\n        Loading,\n        Running,\n        Finished\n    }\n\n    public enum ThreadState\n    {\n        Ready,\n        Working,\n        Waiting\n    }\n\n    public enum UpdateSource\n    {\n        System,\n        User\n    }\n\n    public enum WorldModelVersion\n    {\n        v500,\n        v510,\n        v520,\n        v530,\n        v540,\n        v550,\n        v580\n    }\n\n    public class WorldModel : IASL, IASLDebug, IASLTimer\n    {\n        private Element m_game;\n        private Elements m_elements;\n        private Dictionary<string, int> m_nextUniqueID = new Dictionary<string, int>();\n        private Template m_template;\n        private UndoLogger m_undoLogger;\n        private string m_filename;\n        private string m_originalFilename;\n        private readonly string m_data;\n        private string m_libFolder = null;\n        private List<string> m_errors;\n        private Dictionary<string, ObjectType> m_debuggerObjectTypes = new Dictionary<string, ObjectType>();\n        private Dictionary<string, ElementType> m_debuggerElementTypes = new Dictionary<string, ElementType>();\n        private GameState m_state = GameState.NotStarted;\n        private Dictionary<ElementType, IElementFactory> m_elementFactories = new Dictionary<ElementType, IElementFactory>();\n        private ObjectFactory m_objectFactory;\n        private GameSaver m_saver;\n        private bool m_loadedFromSaved = false;\n        private string m_saveFilename = string.Empty;\n        private bool m_editMode = false;\n        private Functions.ExpressionOwner m_expressionOwner;\n        private IPlayer m_playerUI = null;\n        private ThreadState m_threadState = ThreadState.Ready;\n        private object m_threadLock = new object();\n        private Walkthroughs m_walkthroughs;\n        private List<string> m_attributeNames = new List<string>();\n        private bool m_commandOverride = false;\n        private string m_commandOverrideInput;\n        private object m_commandOverrideLock = new object();\n        private TimerRunner m_timerRunner;\n        private RegexCache m_regexCache = new RegexCache();\n        private IOutputLogger m_outputLogger;\n        private LegacyOutputLogger m_legacyOutputLogger;\n        private CallbackManager m_callbacks = new CallbackManager();\n\n        private static Dictionary<ObjectType, string> s_defaultTypeNames = new Dictionary<ObjectType, string>();\n        private static Dictionary<string, Type> s_typeNamesToTypes = new Dictionary<string, Type>();\n        private static Dictionary<Type, string> s_typesToTypeNames = new Dictionary<Type, string>();\n\n        public event EventHandler<ElementFieldUpdatedEventArgs> ElementFieldUpdated;\n        public event EventHandler<ElementRefreshEventArgs> ElementRefreshed;\n        public event EventHandler<ElementFieldUpdatedEventArgs> ElementMetaFieldUpdated;\n        public event EventHandler<LoadStatusEventArgs> LoadStatus;\n\n        public event Action<int> RequestNextTimerTick;\n\n        public class ElementFieldUpdatedEventArgs : EventArgs\n        {\n            internal ElementFieldUpdatedEventArgs(Element element, string attribute, object newValue, bool isUndo)\n            {\n                Element = element;\n                Attribute = attribute;\n                NewValue = newValue;\n                IsUndo = isUndo;\n            }\n\n            public Element Element { get; private set; }\n            public string Attribute { get; private set; }\n            public object NewValue { get; private set; }\n            public bool IsUndo { get; private set; }\n            public bool Refresh { get; private set; }\n        }\n\n        public class ElementRefreshEventArgs : EventArgs\n        {\n            internal ElementRefreshEventArgs(Element element)\n            {\n                Element = element;\n            }\n\n            public Element Element { get; private set; }\n        }\n\n        public class LoadStatusEventArgs : EventArgs\n        {\n            public LoadStatusEventArgs(string status)\n            {\n                Status = status;\n            }\n\n            public string Status { get; private set; }\n        }\n\n        static WorldModel()\n        {\n            s_defaultTypeNames.Add(ObjectType.Object, "defaultobject");\n            s_defaultTypeNames.Add(ObjectType.Exit, "defaultexit");\n            s_defaultTypeNames.Add(ObjectType.Command, "defaultcommand");\n            s_defaultTypeNames.Add(ObjectType.Game, "defaultgame");\n            s_defaultTypeNames.Add(ObjectType.TurnScript, "defaultturnscript");\n\n            s_typeNamesToTypes.Add("string", typeof(string));\n            s_typeNamesToTypes.Add("script", typeof(IScript));\n            s_typeNamesToTypes.Add("boolean", typeof(bool));\n            s_typeNamesToTypes.Add("int", typeof(int));\n            s_typeNamesToTypes.Add("double", typeof(double));\n            s_typeNamesToTypes.Add("object", typeof(Element));\n            s_typeNamesToTypes.Add("stringlist", typeof(QuestList<string>));\n            s_typeNamesToTypes.Add("objectlist", typeof(QuestList<Element>));\n            s_typeNamesToTypes.Add("stringdictionary", typeof(QuestDictionary<string>));\n            s_typeNamesToTypes.Add("objectdictionary", typeof(QuestDictionary<Element>));\n            s_typeNamesToTypes.Add("scriptdictionary", typeof(QuestDictionary<IScript>));\n            s_typeNamesToTypes.Add("dictionary", typeof(QuestDictionary<object>));\n            s_typeNamesToTypes.Add("list", typeof (QuestList<object>));\n\n            foreach (KeyValuePair<string, Type> kvp in s_typeNamesToTypes)\n            {\n                s_typesToTypeNames.Add(kvp.Value, kvp.Key);\n            }\n        }\n\n        public WorldModel()\n            : this(null, null)\n        {\n        }\n\n        public WorldModel(string data)\n            : this(null, null)\n        {\n            m_data = data;\n        }\n\n        public WorldModel(string filename, string originalFilename)\n        {\n            m_expressionOwner = new Functions.ExpressionOwner(this);\n            m_template = new Template(this);\n            InitialiseElementFactories();\n            m_objectFactory = (ObjectFactory)m_elementFactories[ElementType.Object];\n\n            InitialiseDebuggerObjectTypes();\n            m_filename = filename;\n            m_originalFilename = originalFilename;\n            m_elements = new Elements();\n            m_undoLogger = new UndoLogger(this);\n            m_game = ObjectFactory.CreateObject("game", ObjectType.Game);\n        }\n\n        public WorldModel(string filename, string libFolder, string originalFilename)\n            : this(filename, originalFilename)\n        {\n            m_libFolder = libFolder;\n        }\n\n        private void InitialiseElementFactories()\n        {\n            foreach (Type t in TextAdventures.Utility.Classes.GetImplementations(System.Reflection.Assembly.GetExecutingAssembly(),\n                typeof(IElementFactory)))\n            {\n                AddElementFactory((IElementFactory)Activator.CreateInstance(t));\n            }\n        }\n\n        private void AddElementFactory(IElementFactory factory)\n        {\n            m_elementFactories.Add(factory.CreateElementType, factory);\n            factory.WorldModel = this;\n            factory.ObjectsUpdated += ElementsUpdated;\n        }\n\n        internal static Dictionary<ObjectType, string> DefaultTypeNames\n        {\n            get { return s_defaultTypeNames; }\n        }\n\n        void ElementsUpdated(object sender, ObjectsUpdatedEventArgs args)\n        {\n            if (ObjectsUpdated != null) ObjectsUpdated(this, args);\n        }\n\n        private void InitialiseDebuggerObjectTypes()\n        {\n            m_debuggerObjectTypes.Add("Objects", ObjectType.Object);\n            m_debuggerObjectTypes.Add("Exits", ObjectType.Exit);\n            m_debuggerObjectTypes.Add("Commands", ObjectType.Command);\n            m_debuggerObjectTypes.Add("Game", ObjectType.Game);\n            m_debuggerObjectTypes.Add("Turn Scripts", ObjectType.TurnScript);\n            m_debuggerElementTypes.Add("Timers", ElementType.Timer);\n        }\n\n        public void FinishGame()\n        {\n            m_state = GameState.Finished;\n\n            // Pulse all locks in case we\'re in the middle of waiting for player input for GetInput() etc.\n\n            lock (m_commandOverrideLock)\n            {\n                Monitor.PulseAll(m_commandOverrideLock);\n            }\n\n            lock (m_threadLock)\n            {\n                Monitor.PulseAll(m_threadLock);\n            }\n\n            lock (m_waitForResponseLock)\n            {\n                Monitor.PulseAll(m_waitForResponseLock);\n            }\n\n            if (RequestNextTimerTick != null) RequestNextTimerTick(0);\n\n            if (Finished != null) Finished();\n        }\n\n        internal string GetUniqueID()\n        {\n            return GetUniqueID(null);\n        }\n\n        internal string GetUniqueID(string prefix)\n        {\n            if (string.IsNullOrEmpty(prefix)) prefix = "k";\n            if (!m_nextUniqueID.ContainsKey(prefix))\n            {\n                m_nextUniqueID.Add(prefix, 0);\n            }\n\n            string newid;\n            do\n            {\n                m_nextUniqueID[prefix]++;\n                newid = prefix + m_nextUniqueID[prefix].ToString();\n            } while (m_elements.ContainsKey(newid));\n            \n            return newid;\n        }\n\n        public Element Game\n        {\n            get { return m_game; }\n        }\n\n        public Element Object(string name)\n        {\n            return m_elements.Get(ElementType.Object, name);\n        }\n\n        public ObjectFactory ObjectFactory\n        {\n            get { return m_objectFactory; }\n        }\n\n        public IElementFactory GetElementFactory(ElementType t)\n        {\n            return m_elementFactories[t];\n        }\n\n        public void PrintTemplate(string t)\n        {\n            Print(m_template.GetText(t));\n        }\n\n        public void Print(string text, bool linebreak = true)\n        {\n            if (Version >= WorldModelVersion.v540 && m_elements.ContainsKey(ElementType.Function, "OutputText"))\n            {\n                try\n                {\n                    RunProcedure("OutputText", new Parameters(new Dictionary<string, string> {{"text", text}}), false);\n                }\n                catch (Exception ex)\n                {\n                    LogException(ex);\n                }\n            }\n            else\n            {\n                if (PrintText != null)\n                {\n                    if (linebreak)\n                    {\n                        PrintText("<output>" + text + "</output>");\n                    }\n                    else\n                    {\n                        PrintText("<output nobr=\\"true\\">" + text + "</output>");\n                    }\n                }\n\n                if (m_legacyOutputLogger != null)\n                {\n                    m_legacyOutputLogger.AddText(text, linebreak);\n                }\n            }\n        }\n\n        internal QuestList<Element> GetAllObjects()\n        {\n            return new QuestList<Element>(m_elements.Objects);\n        }\n\n        internal QuestList<Element> GetObjectsInScope(string scopeFunction)\n        {\n            if (m_elements.ContainsKey(ElementType.Function, scopeFunction))\n            {\n                return (QuestList<Element>)RunProcedure(scopeFunction, true);\n            }\n            throw new Exception(string.Format("No function \'{0}\'", scopeFunction));\n        }\n\n        public bool ObjectContains(Element parent, Element searchObj)\n        {\n            if (searchObj.Parent == null) return false;\n            if (searchObj.Parent == parent) return true;\n            return ObjectContains(parent, searchObj.Parent);\n        }\n\n        private object m_waitForResponseLock = new object();\n        private string m_menuResponse = null;\n        private IDictionary<string, string> m_menuOptions = null;\n\n        internal string DisplayMenu(string caption, IDictionary<string, string> options, bool allowCancel, bool async)\n        {\n            Print(caption);\n\n            MenuData menuData = new MenuData(caption, options, allowCancel);\n            m_menuOptions = options;\n\n            m_playerUI.ShowMenu(menuData);\n\n            if (async)\n            {\n                return string.Empty;\n            }\n\n            m_callbacks.Pop(CallbackManager.CallbackTypes.Menu);\n            m_menuOptions = null;\n\n            ChangeThreadState(ThreadState.Waiting);\n\n            lock (m_waitForResponseLock)\n            {\n                Monitor.Wait(m_waitForResponseLock);\n            }\n\n            ChangeThreadState(ThreadState.Working);\n\n            return m_menuResponse;\n        }\n\n        internal string DisplayMenu(string caption, IList<string> options, bool allowCancel, bool async)\n        {\n            Dictionary<string, string> optionsDictionary = new Dictionary<string, string>();\n            foreach (string option in options)\n            {\n                optionsDictionary.Add(option, option);\n            }\n            return DisplayMenu(caption, optionsDictionary, allowCancel, async);\n        }\n\n        public void SetMenuResponse(string response)\n        {\n            Callback menuCallback = m_callbacks.Pop(CallbackManager.CallbackTypes.Menu);\n            if (menuCallback != null)\n            {\n                if (response != null) Print(" - " + m_menuOptions[response]);\n                menuCallback.Context.Parameters["result"] = response;\n                m_menuOptions = null;\n                DoInNewThreadAndWait(() =>\n                {\n                    RunCallbackAndFinishTurn(menuCallback);\n                });\n            }\n            else\n            {\n                DoInNewThreadAndWait(() =>\n                {\n                    m_menuResponse = response;\n\n                    lock (m_waitForResponseLock)\n                    {\n                        Monitor.Pulse(m_waitForResponseLock);\n                    }\n                });\n            }\n        }\n\n        internal void DisplayMenuAsync(string caption, IList<string> options, bool allowCancel, IScript callback, Context c)\n        {\n            m_callbacks.Push(CallbackManager.CallbackTypes.Menu, new Callback(callback, c), "Only one menu can be shown at a time.");\n            DisplayMenu(caption, options, allowCancel, true);\n        }\n\n        internal void DisplayMenuAsync(string caption, IDictionary<string, string> options, bool allowCancel, IScript callback, Context c)\n        {\n            m_callbacks.Push(CallbackManager.CallbackTypes.Menu, new Callback(callback, c), "Only one menu can be shown at a time.");\n            DisplayMenu(caption, options, allowCancel, true);\n        }\n\n        public IEnumerable<Element> Objects\n        {\n            get\n            {\n                foreach (Element o in m_elements.Objects)\n                    yield return o;\n            }\n        }\n\n        public bool ObjectExists(string name)\n        {\n            return m_elements.ContainsKey(ElementType.Object, name);\n        }\n\n        /// <summary>\n        /// Attempt to resolve an element name from elements which are eligible for expression,\n        /// i.e. objects and timers\n        /// </summary>\n        /// <param name="name"></param>\n        /// <returns></returns>\n        public bool TryResolveExpressionElement(string name, out Element element)\n        {\n            element = null;\n            if (!m_elements.ContainsKey(name)) return false;\n\n            Element result = m_elements.Get(name);\n            if (result.ElemType == ElementType.Object || result.ElemType == ElementType.Timer)\n            {\n                element = result;\n                return true;\n            }\n\n            return false;\n        }\n\n        internal void RemoveElement(ElementType type, string name)\n        {\n            m_elements.Remove(type, name);\n        }\n\n        internal IEnumerable<Element> ElementTypes\n        {\n            get { return m_elements.GetElements(ElementType.ObjectType); }\n        }\n\n        internal void SetGameName(string name)\n        {\n            if (m_playerUI != null) m_playerUI.UpdateGameName(name);\n        }\n\n        public bool Initialise(IPlayer player, bool? isCompiled = null)\n        {\n            m_editMode = false;\n            m_playerUI = player;\n            GameLoader loader = new GameLoader(this, GameLoader.LoadMode.Play, isCompiled);\n            bool result = InitialiseInternal(loader);\n            if (result)\n            {\n                m_walkthroughs = new Walkthroughs(this);\n            }\n            return result;\n        }\n\n        public bool InitialiseEdit()\n        {\n            m_editMode = true;\n            GameLoader loader = new GameLoader(this, GameLoader.LoadMode.Edit);\n            return InitialiseInternal(loader);\n        }\n\n        private bool InitialiseInternal(GameLoader loader)\n        {\n            if (m_state != GameState.NotStarted)\n            {\n                throw new Exception("Game already initialised");\n            }\n            loader.FilenameUpdated += loader_FilenameUpdated;\n            loader.LoadStatus += loader_LoadStatus;\n            m_state = GameState.Loading;\n            \n            bool success;\n            if (m_data != null)\n            {\n                success = loader.Load(data: m_data);\n            }\n            else\n            {\n                success = m_filename == null || loader.Load(m_filename);    \n            }\n            \n            DebugEnabled = !loader.IsCompiledFile;\n            m_state = success ? GameState.Running : GameState.Finished;\n            m_errors = loader.Errors;\n            m_saver = new GameSaver(this);\n            if (Version <= WorldModelVersion.v530)\n            {\n                m_legacyOutputLogger = new LegacyOutputLogger(this);\n                m_outputLogger = m_legacyOutputLogger;\n            }\n            else\n            {\n                m_outputLogger = new OutputLogger(this);\n            }\n            return success;\n        }\n\n        void loader_LoadStatus(object sender, GameLoader.LoadStatusEventArgs e)\n        {\n            if (LoadStatus != null)\n            {\n                LoadStatus(this, new LoadStatusEventArgs(e.Status));\n            }\n        }\n\n        void loader_FilenameUpdated(string filename)\n        {\n            // Update base ASLX filename to original filename if we\'re loading a saved game\n            m_saveFilename = m_filename;\n            m_filename = filename;\n            m_loadedFromSaved = true;\n        }\n\n        public void Begin()\n        {\n            DoInNewThreadAndWait(() =>\n            {\n                try\n                {\n                    m_timerRunner = new TimerRunner(this, !m_loadedFromSaved);\n                    if (Version <= WorldModelVersion.v540)\n                    {\n                        PlayerUI.Show("Panes");\n                        PlayerUI.Show("Location");\n                        PlayerUI.Show("Command");\n                    }\n                    if (m_elements.ContainsKey(ElementType.Function, "InitInterface")) RunProcedure("InitInterface");\n                    if (!m_loadedFromSaved)\n                    {\n                        if (m_elements.ContainsKey(ElementType.Function, "StartGame")) RunProcedure("StartGame");\n                    }\n                    TryRunOnFinallyScripts();\n                    UpdateLists();\n                    if (m_loadedFromSaved)\n                    {\n                        var output = Elements.GetSingle(ElementType.Output);\n                        if (output == null)\n                        {\n                            Print("Loaded saved game");\n                        }\n                        else if (Version >= WorldModelVersion.v540)\n                        {\n                            PlayerUI.RunScript("loadHtml", new object[] { output.Fields.GetString("html") });\n                            PlayerUI.RunScript("markScrollPosition", null);\n                            ScrollToEnd();\n                        }\n                        else if (m_legacyOutputLogger != null)\n                        {\n                            m_legacyOutputLogger.DisplayOutput(output.Fields.GetString("text"));\n                        }\n                    }\n                    SendNextTimerRequest();\n                }\n                catch (Exception ex)\n                {\n                    LogException(ex);\n                    Finish();\n                }\n\n                ChangeThreadState(ThreadState.Ready);\n            });\n\n            SendNextTimerRequest();\n        }\n\n        public List<string> Errors\n        {\n            get { return m_errors; }\n        }\n\n        public IWalkthroughs Walkthroughs\n        {\n            get\n            {\n                return m_walkthroughs;\n            }\n        }\n\n        public List<string> DebuggerObjectTypes\n        {\n            get { return new List<string>(m_debuggerObjectTypes.Keys.Union(m_debuggerElementTypes.Keys)); }\n        }\n\n        public void SendCommand(string command, int elapsedTime, IDictionary<string, string> metadata)\n        {\n            if (elapsedTime > 0)\n            {\n                m_timerRunner.IncrementTime(elapsedTime);\n            }\n\n            DoInNewThreadAndWait(() =>\n            {\n                if (!m_commandOverride)\n                {\n                    if (Version < WorldModelVersion.v520)\n                    {\n                        Print("");\n                        Print("> " + Utility.SafeXML(command));\n                    }\n\n                    try\n                    {\n                        RunProcedure("HandleCommand", new Parameters(new Dictionary<string, object>{\n                            {"command", command},\n                            {"metadata", new QuestDictionary<string>(metadata)}\n                        }), false);\n                        if (Version < WorldModelVersion.v580)\n                        {\n                             TryFinishTurn();\n                        }\n                    }\n                    catch (Exception ex)\n                    {\n                        LogException(ex);\n                    }\n\n                    if (State != GameState.Finished)\n                    {\n                        UpdateLists();\n                    }\n\n                    ChangeThreadState(ThreadState.Ready);\n                }\n                else\n                {\n                    Callback getinputCallback = m_callbacks.Pop(CallbackManager.CallbackTypes.GetInput);\n                    if (getinputCallback != null)\n                    {\n                        m_commandOverride = false;\n                        getinputCallback.Context.Parameters["result"] = command;\n                        RunCallbackAndFinishTurn(getinputCallback);\n                    }\n                    else\n                    {\n                        m_commandOverrideInput = command;\n\n                        lock (m_commandOverrideLock)\n                        {\n                            Monitor.Pulse(m_commandOverrideLock);\n                        }\n                    }\n                }\n            });\n\n            if (elapsedTime > 0)\n            {\n                // we increase the timer counter above, before the command has been run,\n                // so we pass in 0 here\n                Tick(0);\n            }\n            else\n            {\n                SendNextTimerRequest();\n            }\n        }\n\n        public void SendCommand(string command, IDictionary<string, string> metadata)\n        {\n            SendCommand(command, 0, metadata);\n        }\n\n        public void SendCommand(string command)\n        {\n            SendCommand(command, 0, null);\n        }\n\n        public void SendEvent(string eventName, string param)\n        {\n            Element handler;\n            m_elements.TryGetValue(ElementType.Function, eventName, out handler);\n\n            if (handler == null)\n            {\n                Print(string.Format("Error - no handler for event \'{0}\'", eventName));\n                return;\n            }\n\n            Parameters parameters = new Parameters();\n            parameters.Add((string)handler.Fields[FieldDefinitions.ParamNames][0], param);\n\n            RunProcedure(eventName, parameters, false);\n            if (Version >= WorldModelVersion.v540)\n            {\n                if (Version < WorldModelVersion.v580)\n                {\n                     TryFinishTurn();\n                }\n                if (State != GameState.Finished)\n                {\n                    UpdateLists();\n                }\n                SendNextTimerRequest();\n            }\n        }\n\n        public string Filename\n        {\n            get { return m_filename; }\n        }\n\n        public string SaveFilename\n        {\n            get { return m_saveFilename; }\n        }\n\n        public string BasePath\n        {\n            get { return System.IO.Path.GetDirectoryName(m_filename); }\n        }\n\n        public void Finish()\n        {\n            FinishGame();\n        }\n\n        public string SaveExtension { get { return "quest-save"; } }\n\n        public event PrintTextHandler PrintText;\n        public event UpdateListHandler UpdateList;\n        public event FinishedHandler Finished;\n        public event EventHandler<ObjectsUpdatedEventArgs> ObjectsUpdated;\n        public event ErrorHandler LogError;\n\n        internal Template Template\n        {\n            get { return m_template; }\n        }\n\n        public UndoLogger UndoLogger\n        {\n            get { return m_undoLogger; }\n        }\n\n        private void UpdateStatusVariables()\n        {\n            if (m_elements.ContainsKey(ElementType.Function, "UpdateStatusAttributes"))\n            {\n                try\n                {\n                    RunProcedure("UpdateStatusAttributes");\n                }\n                catch (Exception ex)\n                {\n                    LogException(ex);\n                }\n            }\n        }\n\n        internal void UpdateLists()\n        {\n            UpdateObjectsList();\n            UpdateExitsList();\n            UpdateStatusVariables();\n        }\n\n        internal void UpdateObjectsList()\n        {\n            UpdateObjectsList("GetPlacesObjectsList", ListType.ObjectsList);\n            UpdateObjectsList("ScopeInventory", ListType.InventoryList);\n        }\n\n        internal void UpdateObjectsList(string scope, ListType listType)\n        {\n            if (UpdateList != null)\n            {\n                List<ListData> objects = new List<ListData>();\n                foreach (Element obj in GetObjectsInScope(scope))\n                {\n                    if (Version <= WorldModelVersion.v520 || !m_elements.ContainsKey(ElementType.Function, "GetDisplayVerbs"))\n                    {\n                        if (scope == "ScopeInventory")\n                        {\n                            objects.Add(new ListData(GetListDisplayAlias(obj), obj.Fields[FieldDefinitions.InventoryVerbs], obj.Name, GetDisplayAlias(obj)));\n                        }\n                        else\n                        {\n                            objects.Add(new ListData(GetListDisplayAlias(obj), obj.Fields[FieldDefinitions.DisplayVerbs], obj.Name, GetDisplayAlias(obj)));\n                        }\n                    }\n                    else\n                    {\n                        objects.Add(new ListData(GetListDisplayAlias(obj), GetDisplayVerbs(obj), obj.Name, GetDisplayAlias(obj)));\n                    }\n                }\n                // The "Places and Objects" list is generated by function, so we also\n                // need to add any exits. (The UI is responsible for filtering out the\n                // directional exits so they only display in the compass)\n                if (scope == "GetPlacesObjectsList") objects.AddRange(GetExitsListData());\n                UpdateList(listType, objects);\n            }\n        }\n\n        internal void UpdateExitsList()\n        {\n            if (UpdateList != null)\n            {\n                UpdateList(ListType.ExitsList, GetExitsListData());\n            }\n        }\n\n        private string GetListDisplayAlias(Element obj)\n        {\n            if (m_elements.ContainsKey(ElementType.Function, "GetListDisplayAlias"))\n            {\n                return (string)RunProcedure("GetListDisplayAlias", new Parameters("obj", obj), true);\n            }\n            return GetDisplayAlias(obj);\n        }\n\n        private string GetDisplayAlias(Element obj)\n        {\n            if (m_elements.ContainsKey(ElementType.Function, "GetDisplayAlias"))\n            {\n                return (string)RunProcedure("GetDisplayAlias", new Parameters("obj", obj), true);\n            }\n            return obj.Name;\n        }\n\n        private IEnumerable<string> GetDisplayVerbs(Element obj)\n        {\n            return (QuestList<string>)RunProcedure("GetDisplayVerbs", new Parameters("object", obj), true);\n        }\n\n        private List<ListData> GetExitsListData()\n        {\n            List<ListData> exits = new List<ListData>();\n            var scopeFunction = "ScopeExits";\n            if (Version >= WorldModelVersion.v530 && Elements.ContainsKey(ElementType.Function, "GetExitsList"))\n            {\n                scopeFunction = "GetExitsList";\n            }\n            foreach (Element exit in GetObjectsInScope(scopeFunction))\n            {\n                IEnumerable<string> verbs;\n                if (Version <= WorldModelVersion.v520 || !m_elements.ContainsKey(ElementType.Function, "GetDisplayVerbs"))\n                {\n                    verbs = exit.Fields[FieldDefinitions.DisplayVerbs];\n                }\n                else\n                {\n                    verbs = GetDisplayVerbs(exit);\n                }\n                exits.Add(new ListData(GetListDisplayAlias(exit), verbs, exit.Name, GetDisplayAlias(exit)));\n            }\n            return exits;\n        }\n\n        public List<string> GetObjects(string type)\n        {\n            List<string> result = new List<string>();\n            IEnumerable<Element> elements;\n\n            if (m_debuggerObjectTypes.ContainsKey(type))\n            {\n                ObjectType filterType = m_debuggerObjectTypes[type];\n                elements = m_elements.ObjectsFiltered(o => o.Type == filterType);\n            }\n            else\n            {\n                ElementType filterType = m_debuggerElementTypes[type];\n                elements = m_elements.GetElements(filterType);\n            }\n\n            foreach (Element obj in elements)\n            {\n                result.Add(obj.Name);\n            }\n\n            return result;\n        }\n\n        public DebugData GetDebugData(string el)\n        {\n            return m_elements.Get(el).GetDebugData();\n        }\n\n        public DebugData GetInheritedTypesDebugData(string el)\n        {\n            return m_elements.Get(el).Fields.GetInheritedTypesDebugData();\n        }\n\n        public DebugDataItem GetDebugDataItem(string el, string attribute)\n        {\n            return m_elements.Get(el).Fields.GetDebugDataItem(attribute);\n        }\n\n        public void StartWait()\n        {\n            m_callbacks.Pop(CallbackManager.CallbackTypes.Wait);\n            m_playerUI.DoWait();\n\n            ChangeThreadState(ThreadState.Waiting);\n\n            lock (m_waitForResponseLock)\n            {\n                Monitor.Wait(m_waitForResponseLock);\n            }\n\n            ChangeThreadState(ThreadState.Working);\n        }\n\n        public void StartWaitAsync(IScript callback, Context c)\n        {\n            m_callbacks.Push(CallbackManager.CallbackTypes.Wait, new Callback(callback, c), "Only one wait can be in progress at a time.");\n            m_playerUI.DoWait();\n        }\n\n        public void FinishWait()\n        {\n            Callback waitCallback = m_callbacks.Pop(CallbackManager.CallbackTypes.Wait);\n            if (waitCallback != null)\n            {\n                DoInNewThreadAndWait(() =>\n                {\n                    RunCallbackAndFinishTurn(waitCallback);\n                });\n            }\n            else\n            {\n                if (m_state == GameState.Finished) return;\n                DoInNewThreadAndWait(() =>\n                {\n                    lock (m_waitForResponseLock)\n                    {\n                        Monitor.Pulse(m_waitForResponseLock);\n                    }\n                });\n            }\n        }\n\n        public void StartPause(int ms)\n        {\n            m_playerUI.DoPause(ms);\n\n            ChangeThreadState(ThreadState.Waiting);\n\n            lock (m_waitForResponseLock)\n            {\n                Monitor.Wait(m_waitForResponseLock);\n            }\n\n            ChangeThreadState(ThreadState.Working);\n        }\n\n        public void FinishPause()\n        {\n            DoInNewThreadAndWait(() =>\n            {\n                lock (m_waitForResponseLock)\n                {\n                    Monitor.Pulse(m_waitForResponseLock);\n                }\n            });\n        }\n\n        public IEnumerable<string> GetExternalScripts()\n        {\n            var result = new List<string>();\n            foreach (Element jsRef in m_elements.GetElements(ElementType.Javascript))\n            {\n                if (Version == WorldModelVersion.v500)\n                {\n                    // v500 games used Frame.js functions for static panel feature. This is now implemented natively\n                    // in Player and WebPlayer.\n                    if (jsRef.Fields[FieldDefinitions.Src].ToLower() == "frame.js") continue;\n                }\n\n                result.Add(jsRef.Fields[FieldDefinitions.Src]);\n            }\n            return result;\n        }\n\n        // TO DO: This could actually be removed now, as we can dynamically load stylesheets. Core.aslx InitInterface\n        // should simply be able to use the SetWebFontName function to load game.defaultwebfont\n        public IEnumerable<string> GetExternalStylesheets()\n        {\n            if (Version < WorldModelVersion.v530) return null;\n\n            var webFontsInUse = new List<string>();\n            var defaultWebFont = m_game.Fields[FieldDefinitions.DefaultWebFont];\n            if (!string.IsNullOrEmpty(defaultWebFont))\n            {\n                webFontsInUse.Add(defaultWebFont);\n            }\n            \n            var result = webFontsInUse.Select(f => "https://fonts.googleapis.com/css?family=" + f.Replace(\' \', \'+\'));\n            \n            return result;\n        }\n\n        public void RunScript(IScript script)\n        {\n            RunScript(script, (Parameters)null, false);\n        }\n\n        /// <summary>\n        /// Use this version of RunScript when executing an object action. Set thisElement to the object whose action it is.\n        /// </summary>\n        /// <param name="script"></param>\n        /// <param name="thisElement"></param>\n        public void RunScript(IScript script, Element thisElement)\n        {\n            RunScript(script, null, false, thisElement);\n        }\n\n        public void RunScript(IScript script, Parameters parameters)\n        {\n            RunScript(script, parameters, false);\n        }\n\n        public void RunScript(IScript script, Parameters parameters, Element thisElement)\n        {\n            RunScript(script, parameters, false, thisElement);\n        }\n\n        public object RunDelegateScript(IScript script, Parameters parameters, Element thisElement)\n        {\n            return RunScript(script, parameters, true, thisElement);\n        }\n\n        private object RunScript(IScript script, Parameters parameters, bool expectResult)\n        {\n            return RunScript(script, parameters, expectResult, null);\n        }\n\n        private object RunScript(IScript script, Parameters parameters, bool expectResult, Element thisElement)\n        {\n            Context c = new Context();\n            if (parameters == null) parameters = new Parameters();\n            if (thisElement != null) parameters.Add("this", thisElement);\n            c.Parameters = parameters;\n\n            return RunScript(script, c, expectResult);\n        }\n\n        private void RunScript(IScript script, Context c)\n        {\n            RunScript(script, c, false);\n        }\n\n        private object RunScript(IScript script, Context c, bool expectResult)\n        {\n            try\n            {\n                script.Execute(c);\n                if (expectResult && c.ReturnValue is NoReturnValue) throw new Exception("Function did not return a value");\n                return c.ReturnValue;\n            }\n            catch (Exception ex)\n            {\n                Print("Error running script: " + Utility.SafeXML(ex.Message));\n            }\n            return null;\n        }\n\n        public Element AddProcedure(string name)\n        {\n            Element proc = GetElementFactory(ElementType.Function).Create(name);\n            return proc;\n        }\n\n        public Element AddProcedure(string name, IScript script, string[] parameters)\n        {\n            Element proc = AddProcedure(name);\n            proc.Fields[FieldDefinitions.Script] = script;\n            proc.Fields[FieldDefinitions.ParamNames] = new QuestList<string>(parameters);\n            return proc;\n        }\n\n        public Element AddDelegate(string name)\n        {\n            Element del = GetElementFactory(ElementType.Delegate).Create(name);\n            return del;\n        }\n\n        public void RunProcedure(string name)\n        {\n            RunProcedure(name, false);\n        }\n\n        public object RunProcedure(string name, bool expectResult)\n        {\n            return RunProcedure(name, null, expectResult);\n        }\n\n        public object RunProcedure(string name, Parameters parameters, bool expectResult)\n        {\n            if (m_elements.ContainsKey(ElementType.Function, name))\n            {\n                Element function = m_elements.Get(ElementType.Function, name);\n\n                // Only check for too few parameters for games for Quest 5.2 or later, as previous Quest versions\n                // would ignore this (but would usually still fail when the function was run, as the required\n                // variable wouldn\'t exist). For Quest 5.3, an additional check if parameters is non-null but empty.\n\n                bool parametersInvalid = false;\n                if (Version == WorldModelVersion.v520)\n                {\n                    parametersInvalid = parameters == null && function.Fields[FieldDefinitions.ParamNames].Count > 0;\n                }\n                else if (Version >= WorldModelVersion.v530)\n                {\n                    parametersInvalid = (parameters == null || parameters.Count == 0) && function.Fields[FieldDefinitions.ParamNames].Count > 0;\n                }\n\n                if (parametersInvalid)\n                {\n                    throw new Exception(string.Format("No parameters passed to {0} function - expected {1} parameters",\n                            name,\n                            function.Fields[FieldDefinitions.ParamNames].Count));\n                }\n\n                return RunScript(function.Fields[FieldDefinitions.Script], parameters, expectResult);\n            }\n            else\n            {\n                Print(string.Format("Error - no such procedure \'{0}\'", name));\n            }\n            return null;\n        }\n\n        public Element Procedure(string name)\n        {\n            if (!m_elements.ContainsKey(ElementType.Function, name)) return null;\n            return m_elements.Get(ElementType.Function, name);\n        }\n\n        internal Element GetObjectType(string name)\n        {\n            return m_elements.Get(ElementType.ObjectType, name);\n        }\n\n        public GameState State\n        {\n            get { return m_state; }\n        }\n\n        public Elements Elements\n        {\n            get { return m_elements; }\n        }\n\n        public void Save(string filename, string html)\n        {\n            string saveData = Save(SaveMode.SavedGame, html: html);\n            File.WriteAllText(filename, saveData);\n        }\n\n        public byte[] Save(string html)\n        {\n            string saveData = Save(SaveMode.SavedGame, html: html);\n            return System.Text.Encoding.UTF8.GetBytes(saveData);\n        }\n\n        public string Save(SaveMode mode, bool? includeWalkthrough = null, string html = null)\n        {\n            return m_saver.Save(mode, includeWalkthrough, html);\n        }\n\n        public static Type ConvertTypeNameToType(string name)\n        {\n            Type type;\n            if (s_typeNamesToTypes.TryGetValue(name, out type))\n            {\n                return type;\n            }\n\n            if (name == "null") return null;\n\n            // TO DO: type name could also be a DelegateImplementation\n            //if (value is DelegateImplementation) return ((DelegateImplementation)value).TypeName;\n\n            throw new ArgumentOutOfRangeException(string.Format("Unrecognised type name \'{0}\'", name));\n        }\n\n        public static string ConvertTypeToTypeName(Type type)\n        {\n            string name;\n            if (s_typesToTypeNames.TryGetValue(type, out name))\n            {\n                return name;\n            }\n\n            foreach (KeyValuePair<Type, string> kvp in s_typesToTypeNames)\n            {\n                if (kvp.Key.IsAssignableFrom(type))\n                {\n                    return kvp.Value;\n                }\n            }\n\n            throw new ArgumentOutOfRangeException(string.Format("Unrecognised type \'{0}\'", type.ToString()));\n        }\n\n        public string GetExternalPath(string file)\n        {\n            return GetExternalPath(file, true);\n        }\n\n        private string TryGetExternalPath(string file)\n        {\n            return GetExternalPath(file, false);\n        }\n\n        private string GetExternalPath(string file, bool throwException)\n        {\n            string resourcesFolder = ResourcesFolder ?? Path.GetDirectoryName(Filename);\n            return GetExternalPath(resourcesFolder, file, throwException);\n        }\n\n        private string GetExternalPath(string current, string file, bool throwException)\n        {\n            string path;\n\n            if (TryPath(current, file, out path, false)) return path;\n            if (ResourcesFolder == null)\n            {\n                // Only try other folders if we\'re not using a resource folder (i.e. a .quest file)\n                // Because if we do have a resource folder, all required external files should be there.\n\n                if (TryPath(Environment.CurrentDirectory, file, out path, false)) return path;\n                if (!string.IsNullOrEmpty(m_libFolder) && TryPath(m_libFolder, file, out path, true)) return path;\n                if (System.Reflection.Assembly.GetEntryAssembly() != null)\n                {\n                    if (TryPath(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().CodeBase), file, out path, true)) return path;\n                }\n            }\n            if (throwException)\n            {\n                throw new Exception(\n                    string.Format("Cannot find a file called \'{0}\' in current path or application/resource path", file));\n            }\n            return null;\n        }\n\n        internal string GetExternalURL(string file)\n        {\n            return m_playerUI.GetURL(file);\n        }\n\n        public IEnumerable<string> GetAvailableLibraries()\n        {\n            List<string> result = new List<string>();\n            AddFilesInPathToList(result, System.IO.Path.GetDirectoryName(Filename), false);\n            AddFilesInPathToList(result, Environment.CurrentDirectory, false);\n            if (m_libFolder != null) AddFilesInPathToList(result, m_libFolder, false);\n            if (System.Reflection.Assembly.GetEntryAssembly() != null)\n            {\n                AddFilesInPathToList(result, System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().CodeBase), true);\n            }\n            return result;\n        }\n\n        private void AddFilesInPathToList(List<string> list, string path, bool recurse, string searchPattern = "*.aslx")\n        {\n            path = TextAdventures.Utility.Utility.RemoveFileColonPrefix(path);\n            System.IO.SearchOption option = recurse ? System.IO.SearchOption.AllDirectories : System.IO.SearchOption.TopDirectoryOnly;\n            foreach (var result in System.IO.Directory.GetFiles(path, searchPattern, option))\n            {\n                if (result == Filename) continue;\n                string filename = System.IO.Path.GetFileName(result);\n                if (!list.Contains(filename)) list.Add(filename);\n            }\n        }\n\n        public IEnumerable<string> GetAvailableExternalFiles(string searchPatterns)\n        {\n            List<string> result = new List<string>();\n            string[] patterns = searchPatterns.Split(\';\');\n            foreach (string searchPattern in patterns)\n            {\n                AddFilesInPathToList(result, System.IO.Path.GetDirectoryName(Filename), false, searchPattern);\n            }\n            return result;\n        }\n\n        private bool TryPath(string path, string file, out string fullPath, bool recurse)\n        {\n            path = TextAdventures.Utility.Utility.RemoveFileColonPrefix(path);\n            fullPath = System.IO.Path.Combine(path, file);\n            if (System.IO.File.Exists(fullPath))\n            {\n                return true;\n            }\n            else\n            {\n                if (recurse && !file.Contains("\\\\") && !file.Contains("/"))\n                {\n                    var results = System.IO.Directory.GetFiles(path, file, System.IO.SearchOption.AllDirectories);\n                    if (results.Length > 0)\n                    {\n                        fullPath = results[0];\n                        return true;\n                    }\n                }\n                return false;\n            }\n        }\n\n        internal void NotifyElementFieldUpdate(Element element, string attribute, object newValue, bool isUndo)\n        {\n            if (!element.Initialised) return;\n            if (ElementFieldUpdated != null) ElementFieldUpdated(this, new ElementFieldUpdatedEventArgs(element, attribute, newValue, isUndo));\n        }\n\n        internal void NotifyElementMetaFieldUpdate(Element element, string attribute, object newValue, bool isUndo)\n        {\n            if (!element.Initialised) return;\n            if (ElementMetaFieldUpdated != null) ElementMetaFieldUpdated(this, new ElementFieldUpdatedEventArgs(element, attribute, newValue, isUndo));\n        }\n\n        internal void NotifyElementRefreshed(Element element)\n        {\n            if (ElementRefreshed != null) ElementRefreshed(this, new ElementRefreshEventArgs(element));\n        }\n\n        public bool EditMode\n        {\n            get { return m_editMode; }\n        }\n\n        internal Functions.ExpressionOwner ExpressionOwner\n        {\n            get { return m_expressionOwner; }\n        }\n\n        private void ScrollToEnd()\n        {\n            PlayerUI.RunScript("scrollToEnd", null);\n        }\n\n        private void ChangeThreadState(ThreadState newState, bool scroll = true)\n        {\n            if (scroll && Version >= WorldModelVersion.v540 && (newState == ThreadState.Ready || newState == ThreadState.Waiting))\n            {\n                ScrollToEnd();\n            }\n\n            if (newState == ThreadState.Waiting && m_state == GameState.Finished) throw new Exception("Game is finished");\n            m_threadState = newState;\n            lock (m_threadLock)\n            {\n                Monitor.PulseAll(m_threadLock);\n            }\n        }\n\n        private void WaitUntilFinishedWorking()\n        {\n            lock (m_threadLock)\n            {\n                while (m_threadState == ThreadState.Working)\n                {\n                    Monitor.Wait(m_threadLock);\n                }\n            }\n        }\n\n        private void DoInNewThreadAndWait(Action routine)\n        {\n            Action wrappedRoutine = () =>\n            {\n                try\n                {\n                    routine();\n                }\n                catch { }\n            };\n\n            ChangeThreadState(ThreadState.Working);\n            Thread newThread = new Thread(new ThreadStart(wrappedRoutine));\n            newThread.Start();\n            WaitUntilFinishedWorking();\n        }\n\n        void LogException(Exception ex)\n        {\n            if (LogError != null) LogError(ex.Message + Environment.NewLine + ex.StackTrace);\n        }\n\n        internal IPlayer PlayerUI\n        {\n            get { return m_playerUI; }\n        }\n\n        public ElementType GetElementTypeForTypeString(string typeString)\n        {\n            return Element.GetElementTypeForTypeString(typeString);\n        }\n\n        public ObjectType GetObjectTypeForTypeString(string typeString)\n        {\n            return Element.GetObjectTypeForTypeString(typeString);\n        }\n\n        public string GetTypeStringForElementType(ElementType type)\n        {\n            return Element.GetTypeStringForElementType(type);\n        }\n\n        public string GetTypeStringForObjectType(ObjectType type)\n        {\n            return Element.GetTypeStringForObjectType(type);\n        }\n\n        public bool IsDefaultTypeName(string typeName)\n        {\n            return DefaultTypeNames.ContainsValue(typeName);\n        }\n\n        public string OriginalFilename\n        {\n            get { return m_originalFilename; }\n        }\n\n        public Element AddNewTemplate(string templateName)\n        {\n            return m_template.AddTemplate(templateName, string.Empty, false);\n        }\n\n        public Element TryGetTemplateElement(string templateName)\n        {\n            if (!m_template.TemplateExists(templateName)) return null;\n            return m_template.GetTemplateElement(templateName);\n        }\n\n        private static System.Text.RegularExpressions.Regex s_removeTrailingDigits = new System.Text.RegularExpressions.Regex(@"\\d*$");\n\n        public string GetUniqueElementName(string elementName)\n        {\n            // If element name doesn\'t exist (element might have been Cut in the editor),\n            // then just return the original name\n            if (!Elements.ContainsKey(elementName))\n            {\n                return elementName;\n            }\n\n            // Otherwise get a uniquely numbered element\n            string root = s_removeTrailingDigits.Replace(elementName, "");\n            bool elementAlreadyExists = true;\n            int number = 0;\n            string result = null;\n\n            while (elementAlreadyExists)\n            {\n                number++;\n                result = root + number.ToString();\n                elementAlreadyExists = Elements.ContainsKey(result);\n            }\n\n            return result;\n        }\n\n        internal void AddAttributeName(string name)\n        {\n            if (!m_attributeNames.Contains(name)) m_attributeNames.Add(name);\n        }\n\n        public IEnumerable<string> GetAllAttributeNames\n        {\n            get { return m_attributeNames.AsReadOnly(); }\n        }\n\n        internal string GetNextCommandInput(bool async)\n        {\n            m_commandOverride = true;\n\n            if (async)\n            {\n                return string.Empty;\n            }\n\n            m_callbacks.Pop(CallbackManager.CallbackTypes.GetInput);\n\n            ChangeThreadState(ThreadState.Waiting);\n\n            lock (m_commandOverrideLock)\n            {\n                Monitor.Wait(m_commandOverrideLock);\n            }\n\n            ChangeThreadState(ThreadState.Working);\n\n            m_commandOverride = false;\n            return m_commandOverrideInput;\n        }\n\n        internal void GetNextCommandInputAsync(IScript callback, Context c)\n        {\n            m_callbacks.Push(CallbackManager.CallbackTypes.GetInput, new Callback(callback, c), "Only one \'get input\' can be in progress at a time");\n            GetNextCommandInput(true);\n        }\n\n        public void Tick(int elapsedTime)\n        {\n            if (m_state == GameState.Finished) return;\n            DoInNewThreadAndWait(() =>\n            {\n                try\n                {\n                    var scripts = m_timerRunner.TickAndGetScripts(elapsedTime);\n\n                    foreach (var timerScript in scripts)\n                    {\n                        RunScript(timerScript.Value, timerScript.Key);\n                    }\n                }\n                catch (Exception ex)\n                {\n                    LogException(ex);\n                }\n\n                UpdateLists();\n\n                ChangeThreadState(ThreadState.Ready, false);\n            });\n\n            SendNextTimerRequest();\n        }\n\n        private void SendNextTimerRequest()\n        {\n            if (m_state == GameState.Finished) return;\n            int next = m_timerRunner.GetTimeUntilNextTimerRuns();\n            if (RequestNextTimerTick != null) RequestNextTimerTick(next);\n            System.Diagnostics.Debug.Print("Request next timer in {0}", next);\n        }\n\n        private bool m_questionResponse;\n\n        internal bool ShowQuestion(string caption)\n        {\n            m_callbacks.Pop(CallbackManager.CallbackTypes.Question);\n            m_playerUI.ShowQuestion(caption);\n\n            ChangeThreadState(ThreadState.Waiting);\n\n            lock (m_waitForResponseLock)\n            {\n                Monitor.Wait(m_waitForResponseLock);\n            }\n\n            ChangeThreadState(ThreadState.Working);\n\n            return m_questionResponse;\n        }\n\n        internal void ShowQuestionAsync(string caption, IScript callback, Context c)\n        {\n            m_callbacks.Push(CallbackManager.CallbackTypes.Question, new Callback(callback, c), "Only one question can be asked at a time.");\n            m_playerUI.ShowQuestion(caption);\n        }\n\n        public void SetQuestionResponse(bool response)\n        {\n            Callback questionCallback = m_callbacks.Pop(CallbackManager.CallbackTypes.Question);\n            if (questionCallback != null)\n            {\n                questionCallback.Context.Parameters["result"] = response;\n                DoInNewThreadAndWait(() =>\n                {\n                    RunCallbackAndFinishTurn(questionCallback);\n                });\n            }\n            else\n            {\n                DoInNewThreadAndWait(() =>\n                {\n                    m_questionResponse = response;\n\n                    lock (m_waitForResponseLock)\n                    {\n                        Monitor.Pulse(m_waitForResponseLock);\n                    }\n                });\n            }\n        }\n\n        private void RunCallbackAndFinishTurn(Callback callback)\n        {\n            RunScript(callback.Script, callback.Context);\n            TryFinishTurn();\n            if (State != GameState.Finished)\n            {\n                UpdateLists();\n            }\n            ChangeThreadState(ThreadState.Ready);\n            SendNextTimerRequest();\n        }\n\n        private void TryFinishTurn()\n        {\n            TryRunOnFinallyScripts();\n            if (!m_callbacks.AnyOutstanding())\n            {\n                if (m_elements.ContainsKey(ElementType.Function, "FinishTurn"))\n                {\n                    try\n                    {\n                        RunProcedure("FinishTurn");\n                    }\n                    catch (Exception ex)\n                    {\n                        LogException(ex);\n                    }\n                }\n            }\n        }\n\n        private void TryRunOnFinallyScripts()\n        {\n            if (m_callbacks.AnyOutstanding()) return;\n            IEnumerable<Callback> onReadyScripts = m_callbacks.FlushOnReadyCallbacks();\n            foreach (var callback in onReadyScripts)\n            {\n                RunScript(callback.Script, callback.Context);\n            }\n        }\n\n        public class PackageIncludeFile\n        {\n            public string Filename { get; set; }\n            public Stream Content { get; set; }\n        }\n\n        public bool CreatePackage(string filename, bool includeWalkthrough, out string error, IEnumerable<PackageIncludeFile> includeFiles, Stream outputStream)\n        {\n            Packager packager = new Packager(this);\n            return packager.CreatePackage(filename, includeWalkthrough, out error, includeFiles, outputStream);\n        }\n\n        public string ResourcesFolder { get; internal set; }\n        public bool DebugEnabled { get; private set; }\n\n        private static List<string> s_functionNames = null;\n\n        public IEnumerable<string> GetBuiltInFunctionNames()\n        {\n            if (s_functionNames == null)\n            {\n                System.Reflection.MethodInfo[] methods = typeof(ExpressionOwner).GetMethods();\n                System.Reflection.MethodInfo[] stringMethods = typeof(StringFunctions).GetMethods();\n\n                IEnumerable<System.Reflection.MethodInfo> allMethods = methods.Union(stringMethods);\n\n                s_functionNames = new List<string>(allMethods.Select(m => m.Name));\n            }\n\n            return s_functionNames.AsReadOnly();\n        }\n\n        public void PlaySound(string filename, bool sync, bool looped)\n        {\n            m_playerUI.PlaySound(filename, sync, looped);\n            if (sync)\n            {\n                ChangeThreadState(ThreadState.Waiting);\n\n                lock (m_waitForResponseLock)\n                {\n                    Monitor.Wait(m_waitForResponseLock);\n                }\n            }\n        }\n\n        internal void UpdateElementSortOrder(Element movedElement)\n        {\n            // There\'s no need to worry about element sort order when playing the game, unless this is an element that can be seen by the\n            // player\n            if (!EditMode)\n            return new FileStream(GetExternalPath(filename), FileMode.Open, FileAccess.Read);\r\n        }\r\n\r\n        public string GetResourcePath(string filename)\r\n        {\r\n            return TryGetExternalPath(filename);\r\n            // is at the end of the list of children.\n\n            int maxIndex = -1;\n\n            foreach (Element sibling in m_elements.GetDirectChildren(movedElement.Parent))\n            {\n                int thisSortIndex = sibling.MetaFields[MetaFieldDefinitions.SortIndex];\n                if (thisSortIndex > maxIndex) maxIndex = thisSortIndex;\n            }\n\n            movedElement.MetaFields[MetaFieldDefinitions.SortIndex] = maxIndex + 1;\n        }\n\n        public bool Assert(string expr)\n        {\n            Expression<bool> expression = new Expression<bool>(expr, new ScriptContext(this));\n            Context c = new Context();\n            return expression.Execute(c);\n        }\n\n        internal void AddOnReady(IScript callback, Context c)\n        {\n            if (!m_callbacks.AnyOutstanding())\n            {\n                RunScript(callback, c);\n            }\n            else\n            {\n                m_callbacks.AddOnReadyCallback(new Callback(callback, c));\n            }\n        }\n\n        public Stream GetResource(string filename)\n        {\n            string path = TryGetExternalPath(filename);\n            if (path == null) return null;\n            return new FileStream(GetExternalPath(filename), FileMode.Open, FileAccess.Read);\n        }\n\n        public string GetResourceData(string filename)\n        {\n            if (Config.ReadGameFileFromAzureBlob)\n            {\n                var url = GetExternalURL(filename);\n                using (var client = new WebClient())\n                {\n                    client.Encoding = System.Text.Encoding.UTF8;\n                    return client.DownloadString(url);\n                }\n            }\n\n            return File.ReadAllText(GetExternalPath(filename));\n        }\n\n        public string GetResourceExternalData(string filename)\n        {\n            if (!IsUrlValid(filename))\n            {\n                return "";\n            }\n            using (var client = new WebClient())\n            {\n                client.Encoding = System.Text.Encoding.UTF8;\n                return client.DownloadString(filename);\n            }\n        }\n\n        public string SetResourceExternalData(string filename, String content)\n        {\n            if (!IsUrlValid(filename))\n            {\n                return "";\n            }\n            using (var client = new WebClient())\n            {\n                var reqparm = new System.Collections.Specialized.NameValueCollection();\n                reqparm.Add("param", content);\n                byte[] responsebytes = client.UploadValues(filename, "POST", reqparm);\n                return System.Text.Encoding.UTF8.GetString(responsebytes);\n            }\n        }\n\n        private static bool IsUrlValid(string uriName)\n        {\n            Uri uriResult;\n            return Uri.TryCreate(uriName, UriKind.Absolute, out uriResult) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);\n        }\n\n        public string GetResourcePath(string filename)\n        {\n            return TryGetExternalPath(filename);\n        }\n\n        internal RegexCache RegexCache { get { return m_regexCache; } }\n\n        ~WorldModel()\n        {\n            if (ResourcesFolder != null && System.IO.Directory.Exists(ResourcesFolder))\n            {\n                try\n                {\n                    System.IO.Directory.Delete(ResourcesFolder, true);\n                }\n                catch { }\n            }\n        }\n\n        public WorldModelVersion Version { get; internal set; }\n\n        internal string VersionString { get; set; }\n\n        public string TempFolder { get; set; }\n\n        internal IOutputLogger OutputLogger { get { return m_outputLogger; } }\n\n        public int ASLVersion { get { return int.Parse(VersionString); } }\n\n        public string GameID\n        {\n            get\n            {\n                string gameId = m_game.Fields[FieldDefinitions.GameID];\n                if (gameId != null) return gameId;\n                if (Config.ReadGameFileFromAzureBlob)\n                {\n                    var parts = m_filename.Split(\'/\');\n                    return parts[parts.Length - 2];\n                }\n                return TextAdventures.Utility.Utility.FileMD5Hash(m_filename);\n            }\n        }\n        public string Category { get { return m_game.Fields[FieldDefinitions.Category]; } }\n        public string Description { get { return m_game.Fields[FieldDefinitions.Description]; } }\n        public string Cover { get { return m_game.Fields[FieldDefinitions.Cover]; } }\n    }\n}\n', 'message': 'Implementing support for drawing SVG graphics to custom drawing layer\n', 'author_name': 'Alex Warren', 'lang': 'cs'}