<commit_before>///
/// LiveEditor jQuery plugin
///
/// $Header: http://svn.didit.se/public/Cosmo/Cosmo.latest/trunk/CosmoWeb/js/liveeditor.js 2004 2013-09-05 13:53:04Z mattias $
///
/// Copyright 2013 Trustfall AB
/// All rights reserved
///
;//Protection against other malformed scripts that can interfere
(function ($) {

    ///
    /// LiveEditor Initiator
    /// Use this method to unobtrusively initialize liveeditor for a group of containers.
    /// For example: $('table tbody tr td.editable').liveeditor();
    ///

    $.fn.liveeditor = function (options) {

        // Establish our default settings
        settings = $.extend({}, default_settings, options);

        //TODO: Consider iterating all items in the selector and updating their html if they are checkboxes or comboboxes, to make sure the server generated code isn't diffrent from client side
        //TODO: $('.combo,.checkbox', this).each(function(){ set(this, this.attr(value)); });
        this.addClass('liveedit').mouseenter(container_mouseenter);
        return this;
    }

    ///
    /// Public LiveEditor Commands
    ///

    $.liveeditor = {
        //version: function () {
        //    return "0.0.1";
        //},

        ///
        /// Object that holds the liveeditors default values.
        /// This object is read-only. Making changes to it will not affect the liveeditors functionality.
        ///
        defaults: default_settings,

        ///
        /// Enables editing for an unregistered or previously disabled container.
        ///
        enable: function (container) {
            debug("jQuery.liveeditor.enable(container)");
            if (!container.hasClass('liveedit'))
                container.addClass('liveedit').mouseenter(container_mouseenter);
        },

        ///
        /// Disables editing for a registered container.
        ///
        disable: function (container) {
            debug("jQuery.liveeditor.disable(container)");
            if (container.hasClass('liveedit'))
                container.removeClass('liveedit').unbind('mouseenter', container_mouseenter);
        },

        ///
        /// Forces the open editor to save its changes back to its container and close.
        /// Call this method before saving to make sure all changes have been committed.
        ///
        closeEditor: function (container) {
            debug("jQuery.liveeditor.closeEditor(container)");
            if (container||null === null)
                container = current_editing_container;
            return page_hide_editor(container);
        },

        ///
        /// Changes the value of a container as if the user had edited it with the editor.
        ///
        set: function (container, value, html) {
            debug("jQuery.liveeditor.set(container, value, html)");
            var originalValue = settings.get_container_value(container);
            if (container.attr('liveold') === undefined) {
                container.attr('liveold', originalValue);
            }
            if (!set(container, value, html))
                return false;
            if (value !== originalValue) {
                changed(container);
            }
            return true;
        },

        ///
        /// Resets the container to consider its current value the unchanged state.
        /// Call this method for all containers after saving to setup new change-tracking without reloading the entire page.
        ///
        reset: function (container) {
            debug("jQuery.liveeditor.reset(container)");
            container.removeClass('changed');
            container.removeAttr('liveold');
            changed(container);
        }
    };

    ///
    /// Private Methods
    ///

    var EDITOR_MARGIN = 5;//TODO: WHY do we need to remove this margin to make the input/select fit its parent container?

    var settings;

    var default_settings = {
        debug: false,
        editorCss: '',

        combobox: {
            css: 'combo'
        },

        checkbox: {
            css: 'checkbox',
            checked: {
                value: 'true',
                html: 'X'
            },
            unchecked: {
                value: 'false',
                html: ''
            }
        },

        ///
        /// Method called to create a new editor.
        /// creating() can be overloaded to add support for new types of editors.
        /// Returns a new editor object.
        ///
        creating: function (container, value) {
            var editor;
            if (container.hasClass(settings.combobox.css)) {
                editor = $('<select>').width(container.width() - EDITOR_MARGIN);
                var select_options = settings.get_container_options(container);
                switch ($.type(select_options)) {
                    case "string": editor.append($(select_options)); break;
                    default:
                        debug('appending ' + $.type(select_options) + ' options is not supported.');
                        break;
                }
                $('option[value="' + value + '"]', editor).prop('selected', true);
            } else if (container.hasClass(settings.checkbox.css))
                editor = $('<input type="checkbox"/>').prop('checked', (value == settings.checkbox.checked.value));
            else {
                editor = $('<input type="text"/>').width(container.width() - EDITOR_MARGIN).val(value);
            }
            debug(editor);
            return editor.addClass(settings.editorCss);
        },

        ///
        /// Event called to get valid options for a combobox editor
        /// get_container_options() must be overloaded if the comboboxes shall have any options.
        /// Returns a string with <option /> elements to display.
        ///
        get_container_options: function (container) { return {}; },

        ///
        /// Method called to get the current value of a container before displaying its editor.
        /// get_container_value() can be overloaded if the container does not store its value in LiveEditors default way.
        /// Obs! Remember to overload set_container_value() too if changing this method.
        /// Returns the containers current value as a string.
        ///
        get_container_value: function (container) { return container.hasClass(settings.combobox.css) || container.hasClass(settings.checkbox.css) ? container.attr('value') : container.text(); },

        ///
        /// Method called to set the new value of a container and at the same time closing the editor.
        /// set_container_value() can be overloaded if the container does not store its value in LiveEditors default way.
        /// Obs! Remember to overload get_container_value() too if changing this method.
        /// Returns true if the value was written and the editor was closed.
        /// It is also possible to cancel the closing of the editor by returning false from this method.
        ///
        set_container_value: function (container, value, html) {
            if (container.hasClass(settings.combobox.css)) {
                container.attr('value', value);
            }
            else if (container.hasClass(settings.checkbox.css)) {
                container.attr('value', value);
                html = (value == settings.checkbox.checked.value) ? settings.checkbox.checked.html : settings.checkbox.unchecked.html;
            }
            if (html === null || html === undefined)
                html = value;
            container.html(html);
            return true;
        },

        ///
        /// Method to get the current value from the editor.
        /// Returns the editors value, which MAY differ from its displayed text (for checkboxes/comboboxes for instance).
        ///
        get_editor_value: function (editor) {
            if (editor.is(':checkbox'))
                return (editor.prop('checked')
                    ? settings.checkbox.checked.value
                    : settings.checkbox.unchecked.value);
            else if (editor.is('select'))
                return $('option:selected', editor).val();
            else
                return editor.val();
        },

        ///
        /// Method to get the current display text from the editor
        /// Returns the displayed text in the editor, which MAY differ from its value (for checkboxes/comboboxes for instance).
        ///
        get_editor_text: function (editor) {
            if (editor.is('select'))
                return $(':selected', editor).text();
            else
                return null;
        },

        ///
        /// Event called after the editor is shown.
        ///
        shown: null,

        ///
        /// Event called after the editor receives focus.
        ///
        focused: null,

        ///
        /// Event called after the editor is closed.
        ///
        hidden: null,

        ///
        /// Event called when the container changes value after the editor has been closed.
        ///
        changed: null
    };

    var current_editing_container = null;

    function debug(msg) {
        if (settings.debug && console)
            console.log(msg);
    }

    //Display editor when mouse hovers over an editable container
    function page_display_editor(container) {

        debug('liveeditor.page_display_editor(container)');

        var editor = $('.liveeditor', container);
        if (editor.length) {
            debug("Using the existing editor for the requested container instead of creating a new.");
            return editor; //Already an editor in this container
        }

        var currentValue = settings.get_container_value(container);
        if (container.attr('liveold') === undefined) {
            container.attr('liveold', currentValue);
        }
        var editor = settings
            .creating(container, currentValue)
            .addClass('liveeditor');
        editor.attr('original', currentValue);
        editor.focus(page_editor_focus);
        container
            .html(editor)
            .mouseleave(container_mouseleave);
        debug("Added editor to the container");

        if ($.isFunction(settings.shown))
            settings.shown.call(container, editor);
        return editor;
    }

    function container_mouseenter() {
        debug("liveeditor.container_mouseenter()");
        page_display_editor($(this));
    }

    function container_mouseleave() {
        debug("liveeditor.container_mouseleave()");
        page_hide_editor($(this));
    }

    //Restore "label" in the container with the new value from the editor
    function page_hide_editor(container) {
        debug("liveeditor.page_hide_editor(container)");
        var editor = $('.liveeditor', container);
        if (!editor.length) {
            debug("Found no editor to hide for the requested container. Aborting!");
            return true; //No editor to hide
        }
        var originalValue = editor.attr('original');
        var newValue = settings.get_editor_value(editor);
        var newHtml = settings.get_editor_text(editor);
        if (!set(container, newValue, newHtml))
            return false;
        if (originalValue != newValue) {
            changed(container);
        }
        //Throw the hidden event
        if ($.isFunction(settings.hidden))
            settings.hidden.call(container);
        return true;
    }

    function set(container, newValue, newHtml) {
        debug("liveeditor.set(container, newValue, newHtml)");
        var oldValue = container.attr('liveold');
        if (!settings.set_container_value(container, newValue, newHtml))
            return false;
        container.unbind('mouseleave', container_mouseleave);
        if (newValue != oldValue) {
            container.addClass('changed');
            debug("Flagged the container as changed");
        }
        else {
            container.removeClass('changed');
            container.removeAttr('liveold');
            debug("Flagged the container as unchanged");
        }
        if (container === current_editing_container) {
            debug("Closed the current editing container.");
            current_editing_container = null;
        }
        return true;
    }

    function changed(container) {
        if ($.isFunction(settings.changed))
            settings.changed.call(container);
    }

    //Make the editor "sticky" so it doesn't disappear when the mouse is moved from the container
    function page_editor_focus() {
        debug('liveeditor.page_editor_focus()');

        var editor = $(this)
            .unbind('focus', page_editor_focus)
            .select();
        var container = editor.closest('.liveedit')
            .unbind('mouseleave', container_mouseleave);

        if (container === current_editing_container) {
            debug('It is the current editing container that got focus. Ignore?!');
            return true; //Already an editor in the requested container
        }

        if (current_editing_container) {
            debug('Closing old editing container.');
            if (!page_hide_editor(current_editing_container))
                return false;
        }

        current_editing_container = container;
        editor.keydown(editor_keydown);
        if ($.isFunction(settings.focused))
            settings.focused.call(container, editor);
        return true;
    }

    //Handle editor TAB and ESC keys
    function editor_keydown(e) {
        var keyCode = e.keyCode || e.which;
        if (keyCode == 27) {       // ESC
            e.preventDefault();
            if (!current_editing_container) {
                debug("Got ESC keydown for an editor without having a current container. Aborting!");
                return;
            }
            page_hide_editor(current_editing_container);
        }
        else if (keyCode == 9) {    //TAB
            e.preventDefault();
            if (!current_editing_container) {
                debug("Got TAB keydown for an editor without having a current container. Aborting!");
                return;
            }

            //Close the current editor
            var nextContainer = current_editing_container
            page_hide_editor(current_editing_container);

            do {
                //Find previous/next editable container
                debug("Selecting next container for editing.");
                if (e.shiftKey) {
                    debug("TAB to prev container");
                    nextContainer = nextContainer.prevAll('.liveedit').eq(0);
                    if (nextContainer.length == 0) {
                        debug("Found no prev container. Moving to parents previous last");
                        nextContainer = $('.liveedit:last', nextContainer.parent().prev());
                    }
                } else {
                    debug("TAB to next container");
                    nextContainer = $("~ .liveedit", nextContainer).eq(0);
                    if (nextContainer.length == 0) {
                        debug("Found no next container. Moving to parents next first");
                        nextContainer = $('.liveedit:first', nextContainer.parent().next());
                    }
                }
                //Open the editor
                if (nextContainer.length > 0) {
                    try {
                        page_display_editor(nextContainer).focus();
                        break;
                    } catch (e) {
                        debug("Failed to display editor. Move to the next container.");
                    }
                }
                else {
                    debug("No more containers to test. Not displaying any new editor.");
                    break;
                }
            } while (true);//Iterate through all editing containers until we find one that can be edited now
        }
    }

} (jQuery));
<commit_msg>jquery.liveeditor<commit_after>///
/// LiveEditor jQuery plugin
///
/// $Header: http://svn.didit.se/public/Cosmo/Cosmo.latest/trunk/CosmoWeb/js/liveeditor.js 2004 2013-09-05 13:53:04Z mattias $
///
/// Copyright 2013 Trustfall AB
/// All rights reserved
///
;//Protection against other malformed scripts that can interfere
(function ($) {

    var defaultOptions = {

        debug: false,

        editor: {
            css: ''
        },

        combobox: {
            css: 'combo'
        },

        checkbox: {
            css: 'checkbox',
            checked: {
                value: 'true',
                html: 'X'
            },
            unchecked: {
                value: 'false',
                html: ''
            }
        },

        ///
        /// Container events
        ///

        onGetValue: null,

        onSetValue: null,

        /// Called (after any edit has been closed) when the container has changed its value. Also called by reset().
        onChanged: null,

        /// Called when an editor is being created to allow developers to implement their own editors for certain containers.
        onEditorCreating: null,

        /// Called to get valid options for a combobox Editor.
        /// this event must be overloaded if the comboboxes shall have any options.
        /// Return a string with <option /> elements to display.
        onEditorOptions: function (container) { return '<option value=""></option>'; },

        /// Called after the editor is visible.
        onEditorOpened: null,

        /// Called after the editor receives focus.
        onEditorFocused: null,

        /// Called after the editor has been closed.
        onEditorClosed: null,
    };

    var EDITOR_MARGIN = 5;//TODO: WHY do we need to remove this margin to make the input/select fit its parent container?

    ///
    /// Public LiveEditor Commands
    ///

    $.liveeditor = {
        //version: function () {
        //    return "0.0.1";
        //},

        ///
        /// Enables editing for an unregistered or previously disabled container.
        ///
        enable: function (container) {
            debug("jQuery.liveeditor.enable(container)");
            return enable(container);
        },

        ///
        /// Disables editing for a registered container.
        ///
        disable: function (container) {
            debug("jQuery.liveeditor.disable(container)");
            return disable(container);
        },

        ///
        /// Forces the open editor to save its changes back to its container and close.
        /// Call this method before saving to make sure all changes have been committed.
        ///
        closeEditor: function (container) {
            debug("jQuery.liveeditor.closeEditor(container)");
            //if (container || null === null)
            //    container = $(options.editor._focusedContainer);
            return commitEditor(container);
        },

        ///
        /// Changes the value of a container as if the user had edited it with the editor.
        ///
        set: function (container, value, html) {
            debug("jQuery.liveeditor.set(container, value, html)");
            var originalValue = getContainerValue(container);
            if (container.attr('liveold') === undefined) {
                container.attr('liveold', originalValue);
            }
            if (!updateContainer(container, value, html)) {
                return false;
            }
            if (value !== originalValue) {
                containerChanged(container);
            }
            return true;
        },

        ///
        /// Resets the container to consider its current value the unchanged state.
        /// Call this method for all containers after saving to setup new change-tracking without reloading the entire page.
        ///
        reset: function (container) {
            debug("jQuery.liveeditor.reset(container)");
            container.removeClass('changed');
            container.removeAttr('liveold');
            containerChanged(container);
            return container;
        }
    };

    ///
    /// Private Event Listeners
    ///

    //Display the editor when the user hovers over the container
    function container_mouseenter() {
        debug("liveeditor.container_mouseenter()");
        displayEditor($(this));
    }

    function container_mouseleave() {
        debug("liveeditor.container_mouseleave()");
        commitEditor(this);//TODO: Should call cancelEditor() instead to make sure the value doesn't change
    }

    //Make the editor "sticky" when focused so it doesn't disappear when the mouse is moved from the container
    function editor_focus() {
        debug('liveeditor.editor_focus()');

        var editor = $(this)
            .unbind('focus', editor_focus)
            .select();
        var container = editor.closest('.liveedit')
            .unbind('mouseleave', container_mouseleave);

        var options = container.data('liveeditor-options');
        if (container[0] === options.editor._focusedContainer) {
            debug('It is the current editing container that got focus. Ignore?!');
            return true; //Since the current containers editor is focused there is no other editor to close
        }

        if (options.editor._focusedContainer) {
            debug('Closing old editing container.');
            debug(options.editor._focusedContainer);
            if (!commitEditor(options.editor._focusedContainer))
                return false;
        }

        debug('Tracking the current editing container.');
        options.editor._focusedContainer = container[0];
        editor.keydown(editor_keydown);
        if ($.isFunction(options.onEditorFocused)) {
            debug("Calling onEditorFocused()");
            options.onEditorFocused.call(container, editor);
        }
        return true;
    }

    //Handle editor TAB and ESC keys
    function editor_keydown(e) {
        var keyCode = e.keyCode || e.which;
        if (keyCode == 27) {       // ESC
            e.preventDefault();
            var container = $(this).closest('.liveedit');
            var options = container.data('liveeditor-options');
            if (!options.editor._focusedContainer) {
                debug("Got ESC keydown for an editor without having a current container. Aborting!");
                return;
            }
            commitEditor(container);//options.editor._focusedContainer
        }
        else if (keyCode == 9) {    //TAB
            e.preventDefault();
            var container = $(this).closest('.liveedit');
            var options = container.data('liveeditor-options');
            if (!options.editor._focusedContainer) {
                debug("Got TAB keydown for an editor without having a current container. Aborting!");
                return;
            }

            //Close the current editor
            var nextContainer = container;//options.editor._focusedContainer; //TODO: Rewrite to use the initialized selector for stepping instead
            commitEditor(container);//options.editor._focusedContainer

            do {
                //Find previous/next editable container
                debug("Selecting next container for editing.");
                if (e.shiftKey) {
                    debug("TAB to prev container");
                    nextContainer = nextContainer.prevAll('.liveedit').eq(0);
                    if (nextContainer.length == 0) {
                        debug("Found no prev container. Moving to parents previous last");
                        nextContainer = $('.liveedit:last', nextContainer.parent().prev());
                    }
                } else {
                    debug("TAB to next container");
                    nextContainer = $("~ .liveedit", nextContainer).eq(0);
                    if (nextContainer.length == 0) {
                        debug("Found no next container. Moving to parents next first");
                        nextContainer = $('.liveedit:first', nextContainer.parent().next());
                    }
                }
                //Open the editor
                if (nextContainer.length > 0) {
                    try {
                        displayEditor(nextContainer).focus();
                        break;
                    } catch (e) {
                        debug("Failed to display editor. Move to the next container.");
                    }
                }
                else {
                    debug("No more containers to test. Not displaying any new editor.");
                    break;
                }
            } while (true);//Iterate through all editing containers until we find one that can be edited now
        }
    }

    ///
    /// Private methods
    ///
    function debug(msg) {
        //if (settings.debug && console)
        //    console.log(msg);
    }
    function enable(container) {
        if (!container.hasClass('liveedit')) {
            container
                .addClass('liveedit')
                .mouseenter(container_mouseenter);
        }
        return container;
    }
    function disable(container) {
        if (container.hasClass('liveedit')) {
            container
                .removeClass('liveedit')
                .unbind('mouseenter', container_mouseenter);
        }
        return container;
    }

    ///
    /// Method called to get the current value of a container before displaying its editor.
    /// Implement the events onGetValue() and onSetValue() if the container does not store its value in LiveEditors default way.
    /// Returns the containers current value as a string.
    ///
    function getContainerValue (container) {
        var value;
        var options = container.data('liveeditor-options');
        if ($.isFunction(options.onGetValue)) {
            debug('Calling onGetValue()');
            value = options.onGetValue.call(container);
        }
        if (!value) {
            debug('Looking up value from container');
            value = container.hasClass(options.combobox.css) || container.hasClass(options.checkbox.css)
                ? container.attr('value')
                : container.text();
        }
        debug('getContainerValue() returns: ' + value);
        return value;
    }

    ///
    /// Method called to set the new value of a container. If the container has an open editor it is closed by this commmand.
    /// Implement the events onGetValue() and onSetValue() if the container does not store its value in LiveEditors default way.
    /// Returns true if the value was written successfully and false if the operation was cancelled.
    ///
    function updateContainer(container, newValue, newHtml) {
        debug("liveeditor.updateContainer(container, newValue, newHtml)");
        var success;

        var oldValue = container.attr('liveold');

        //Let the user override the setting of the value to the container if he likes
        var options = container.data('liveeditor-options');
        if ($.isFunction(options.onSetValue)) {
            debug('Calling onSetValue()');
            var success = options.onSetValue.call(container, value, html);
            if (success === false)
                return false;
        }

        if (success === undefined) {
            //Set the new value to the container
            debug('Setting value for container');
            if (container.hasClass(options.combobox.css)) {
                container.attr('value', newValue);
            }
            else if (container.hasClass(options.checkbox.css)) {
                container.attr('value', newValue);
                newHtml = (newValue == options.checkbox.checked.value) ? options.checkbox.checked.html : options.checkbox.unchecked.html;
            }
            if (newHtml === null || newHtml === undefined)
                newHtml = newValue;
            container.html(newHtml);
        }

        container.unbind('mouseleave', container_mouseleave);

        if (container[0] === options.editor._focusedContainer) {
            debug("Closed the current editing container.");
            options.editor._focusedContainer = null;
        }

        //Update the containers change-status
        if (newValue != oldValue) {
            container.addClass('changed');
            debug("Flagged the container as changed");
        }
        else {
            container.removeClass('changed');
            container.removeAttr('liveold');
            debug("Flagged the container as unchanged");
        }
        return true;
    }

    function containerChanged (container) {
        var options = container.data('liveeditor-options');
        if ($.isFunction(options.onChanged)) {
            debug("Calling onChanged()");
            options.onChanged.call(container);
        }
    }

    ///
    /// Private Editor methods
    ///

    //Display editor when mouse hovers over an editable container
    function displayEditor(container) {
        debug('liveeditor.displayEditor(container)');
        var options = container.data('liveeditor-options');

        var editor = $('.liveeditor', container);
        if (editor.length) {
            debug("Using the existing editor for the requested container instead of creating a new.");
            return editor; //Already an editor in this container
        }

        var currentValue = getContainerValue(container);
        if (container.attr('liveold') === undefined) {
            container.attr('liveold', currentValue);
        }

        var editor = createEditor(container, currentValue);
        editor
            .addClass('liveeditor')
            .attr('original', currentValue);
        editor.focus(editor_focus);
        container
            .html(editor)
            .mouseleave(container_mouseleave);
        debug("Added editor to the container");

        if ($.isFunction(options.onEditorOpened)) {
            debug("Calling onEditorOpened");
            options.onEditorOpened.call(container, editor);
        }

        debug("liveeditor.displayEditor() DONE");
        return editor;
    }

    function createEditor(container, value) {
        var editor;
        var options = container.data('liveeditor-options');

        var event = options.onEditorCreating;
        if (event) {
            editor = event(container, value);
        }

        if (!editor) {
            if (container.hasClass(options.combobox.css)) {
                editor = $('<select>').width(container.width() - EDITOR_MARGIN);
                var select_options = options.onEditorOptions.call(container);
                switch ($.type(select_options)) {
                    case "string": editor.append($(select_options)); break;
                    default:
                        debug('appending ' + $.type(select_options) + ' options is not supported.');
                        break;
                }
                $('option[value="' + value + '"]', editor).prop('selected', true);
            } else if (container.hasClass(options.checkbox.css))
                editor = $('<input type="checkbox"/>').prop('checked', (value == options.checkbox.checked.value));
            else {
                editor = $('<input type="text"/>').width(container.width() - EDITOR_MARGIN).val(value);
            }
        }

        if (options.editor.css) {
            editor.addClass(options.editor.css);
        }

        return editor;
    }

    //Restore "label" in the container with the new value from the editor
    function commitEditor(obj) {
        debug("liveeditor.commitEditor(obj)");
        var container = $(obj);
        var editor = $('.liveeditor', container);
        if (!editor.length) {
            debug("Found no editor to hide for the requested container. Aborting!");
            return true; //No editor to hide
        }
        var originalValue = editor.attr('original');
        var newValue = getEditorValue(editor);
        var newHtml = getEditorHtml(editor);
        if (!updateContainer(container, newValue, newHtml))
            return false;

        var options = container.data('liveeditor-options');
        if (originalValue != newValue) {
            containerChanged(container);
        }
        //Throw the hidden event
        if ($.isFunction(options.onEditorClosed)) {
            debug("Calling onEditorClosed()");
            options.onEditorClosed.call(container);
        }
        return true;
    }

    ///
    /// Method to get the current value from the editor.
    /// Returns the editors value, which MAY differ from its displayed text (for checkboxes/comboboxes for instance).
    ///
    function getEditorValue(editor) {
        debug('liveeditor.getEditorValue()');
        var value;
        var container = editor.closest('.liveedit');
        var options = container.data('liveeditor-options');

        if ($.isFunction(options.onEditorGetValue)) {
            debug('Calling onEditorGetValue()');
            value = options.onEditorGetValue.call(editor);
        }

        if (!value) {
            debug('Getting editor value');
            if (editor.is(':checkbox'))
                value = (editor.prop('checked')
                    ? options.checkbox.checked.value
                    : options.checkbox.unchecked.value);
            else if (editor.is('select'))
                value = $('option:selected', editor).val();
            else
                value = editor.val();
        }

        debug('liveeditor.getEditorValue() returns ' + value);
        return value;
    }

    ///
    /// Method to get the current display text from the editor
    /// Returns the displayed text in the editor, which MAY differ from its value (for checkboxes/comboboxes for instance).
    ///
    function getEditorHtml (editor) {
        debug('liveeditor.getEditorHtml()');
        var html;
        var container = editor.closest('.liveedit');
        var options = container.data('liveeditor-options');

        if ($.isFunction(options.onEditorGetValue)) {
            debug('Calling onEditorGetHtml()');
            html = options.onEditorGetHtml.call(editor);
        }

        if (editor.is('select'))
            html = $(':selected', editor).text();
        else
            html = null;

        debug('liveeditor.getEditorHtml() returns ' + html);
        return html;
    }

    //called on the native object directly, wrap with $(obj) if needed.
    function initializeObject(obj, options) {
        //assign the options to the object instance.
        $(obj).data('liveeditor-options', options);

        //do other initialization tasks on the individual item here...
    }

    ///
    /// LiveEditor Initiator
    /// Use this method to unobtrusively initialize liveeditor for a group of containers.
    /// For example: $('table tbody tr td.editable').liveeditor();
    ///

    $.fn.liveeditor = function (options) {
        debug("jQuery.liveeditor initializing for selection");

        //Build options for the new instance
        var mergedOptions = $.extend({}, defaultOptions, options)
        mergedOptions.editor._focusedContainer = null; //This is just an internal state-variable, so DON'T expose it in defaultOptions!
        //Initialize all objects in the selection
        this.each(function () {
            initializeObject(this, mergedOptions);
        });
        //Enable all objects in the selection
        debug("Enabling selection");
        $.liveeditor.enable(this);

        //TODO: Consider iterating all items in the selector and updating their html if they are checkboxes or comboboxes, to make sure the server generated code isn't diffrent from client side
        //TODO: $('.combo,.checkbox', this).each(function(){ updateContainer(this, this.attr(value)); });

        debug("jQuery.liveeditor initializing DONE");
        return this;
    }

} (jQuery));
<|endoftext|>


<commit_before>

var util = require('./util');
var argv = require('minimist')(process.argv.slice(2));
var cpy = require("cpy");
var path = require("path");
var fs = require("fs-extra");

if(argv._ && argv._.length > 0) //look release build
{
    var subCommand = argv._[0];
    if(subCommand.toLowerCase() === "release")
    {
        build(true);
    }

}
else //do dev build
{
    build();
}

function createStandAlone(cb){

    var stealTools = require("steal-tools");

    json = JSON.parse(fs.readFileSync(path.resolve("./package.json"), 'utf8'));
    var version = json.version;
    var moduleName = json.name;

    var standAloneFile = "./standalone/" + moduleName;

    var pathToStandAloneFile = standAloneFile + ".js";
    var pathToStandAloneMinifiedFile = standAloneFile + ".min.js";

    var buildOptions = {
        system: {
            config:path.resolve("./")+"/package.json!npm"
        },
        outputs: {
            "+standalone": {
                dest: pathToStandAloneFile
            }
        }
    };

    stealTools.export(buildOptions).then(function(){

        buildOptions.outputs = {
            "+standalone": {
                dest: pathToStandAloneMinifiedFile,
                minify:true
            }
        };

        //buiding minified version
        stealTools.export(buildOptions).then(function(){

            cb()

        },function(err){
            cb(err)
        })

    },function(err){
        cb(err)
    })
}

function build(isRelease){

    var cmd = "tsc";

    if(isRelease)
        cmd = cmd + " --declaration";
    else
        cmd = cmd + " --sourceMap";

    util.series(["npm run clean",cmd], function (err) {

        if(err)
        {
            console.log(err);
            process.exit(1);
        }
        else
        {
            if(isRelease)
            {
                cpy(["**/*.js","**/*.d.ts"],"../dist",{cwd:process.cwd()+"/src",parents: true, nodir: true}).then(function(){

                    createStandAlone(function (err) {

                        if(err)
                        {
                            console.log(err);
                            process.exit(1);
                        }
                        else
                        {
                            process.exit(0);
                        }
                    })

                },function(err){

                    console.log(err);
                    process.exit(1);
                })
            }

        }
    });

}

<commit_msg>"releasing new version"<commit_after>

var util = require('./util');
var argv = require('minimist')(process.argv.slice(2));
var cpy = require("cpy");
var path = require("path");
var fs = require("fs-extra");

if(argv._ && argv._.length > 0) //look release build
{
    var subCommand = argv._[0];
    if(subCommand.toLowerCase() === "release")
    {
        build(true);
    }

}
else //do dev build
{
    build();
}

function createStandAlone(cb){

    var stealTools = require("steal-tools");

    var rootPath = path.resolve("./");
    json = JSON.parse(fs.readFileSync(path.resolve("./package.json"), 'utf8'));
    var version = json.version;
    var moduleName = json.name;

    var mainFilePathArray = json.main.split("/");
    var mainFileName = mainFilePathArray[mainFilePathArray.length-1];

    var standAloneFile = "./standalone/" + moduleName;

    var pathToStandAloneFile = standAloneFile + ".js";
    var pathToStandAloneMinifiedFile = standAloneFile + ".min.js";

    var tempConfigFilePath = path.resolve("./")+"/package_temp.json";
    var tempDirectoryPath = rootPath + "/" + moduleName;
    var tempConfig = json;
    tempConfig.main = moduleName + "/" + mainFileName;

    fs.writeFileSync(tempConfigFilePath,JSON.stringify({main:moduleName + "/" + mainFileName, system:json.system}));

    fs.copySync(rootPath+"/dist",tempDirectoryPath)

    var buildOptions = {
        system: {
            config:tempConfigFilePath+"!npm"
        },
        outputs: {
            "+standalone": {
                dest: pathToStandAloneFile
            }
        }
    };

    stealTools.export(buildOptions).then(function(){

        buildOptions.outputs = {
            "+standalone": {
                dest: pathToStandAloneMinifiedFile,
                minify:true
            }
        };

        //buiding minified version
        stealTools.export(buildOptions).then(function(){
            cleanTempFiles();
            cb()

        },function(err){
            cleanTempFiles();
            cb(err)
        })

    },function(err){
        cleanTempFiles();
        cb(err)
    })

    function cleanTempFiles() {

        fs.removeSync(tempConfigFilePath);
        fs.removeSync(tempDirectoryPath);
    }
}

function build(isRelease){

    var cmd = "tsc";

    if(isRelease)
        cmd = cmd + " --declaration";
    else
        cmd = cmd + " --sourceMap";

    util.series(["npm run clean",cmd], function (err) {

        if(err)
        {
            console.log(err);
            process.exit(1);
        }
        else
        {
            if(isRelease)
            {
                cpy(["**/*.js","**/*.d.ts"],"../dist",{cwd:process.cwd()+"/src",parents: true, nodir: true}).then(function(){

                    createStandAlone(function (err) {

                        if(err)
                        {
                            console.log(err);
                            process.exit(1);
                        }
                        else
                        {
                            process.exit(0);
                        }
                    })

                },function(err){

                    console.log(err);
                    process.exit(1);
                })
            }

        }
    });

}

<|endoftext|>


<commit_before>/*
 * Game core for Robots Order BreakOut Trepidation!
 *
 * @canvas = reference to an HTML5 canvas element.
 */

'use strict';
import Actors from 'Actors.js';

export default class {
  constructor(canvas) {
    this._canvas = canvas;
    this._ctx = this._canvas.getContext("2d");

    this._actors = new Actors(this._canvas.width, this._canvas.height);
  }

  clearScene() {
    this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
    this._ctx.fillStyle = "#9ea7b8";
    this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
  }

  drawScene() {
    this.clearScene();
    this._actors.moveActors();
    this._actors.getActors().forEach((e)=> this.drawElement(e));
    requestAnimationFrame(()=> this.drawScene());
  }

  drawElement(e) {
    if (e.visible) {
      this._ctx.beginPath();
      this._ctx[e.drawType].apply(this._ctx, e.params);
      this._ctx.fillStyle = e.color;
      this._ctx.fill();
      this._ctx.closePath();

      this.handleWallCollisions(e);
    }
  }

  handleWallCollisions(e) {
    if (e.isMachine) {
      // This is not pretty. Will fix it later.
      let futureX = e.positionVector.x() + e.velocityVector.x();
      let futureY = e.positionVector.y() + e.velocityVector.y();
      if (futureX > this._canvas.width - e.radius || futureX < e.radius) {
        e.flipDirection('v');
      }
      if (futureY > this._canvas.height - e.radius || futureY < e.radius) {
        e.flipDirection('h');
      }
    }
  }

  startGameLoop() {
    requestAnimationFrame(()=> this.drawScene());
  }

  start() {
    this.startGameLoop();
  }
}
<commit_msg>Make ball fall off bottom wall<commit_after>/*
 * Game core for Robots Order BreakOut Trepidation!
 *
 * @canvas = reference to an HTML5 canvas element.
 */

'use strict';
import Actors from 'Actors.js';

export default class {
  constructor(canvas) {
    this._canvas = canvas;
    this._ctx = this._canvas.getContext("2d");

    this._actors = new Actors(this._canvas.width, this._canvas.height);
  }

  clearScene() {
    this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
    this._ctx.fillStyle = "#9ea7b8";
    this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
  }

  drawScene() {
    this.clearScene();
    this._actors.moveActors();
    this._actors.getActors().forEach((e)=> this.drawElement(e));
    requestAnimationFrame(()=> this.drawScene());
  }

  drawElement(e) {
    if (e.visible) {
      this._ctx.beginPath();
      this._ctx[e.drawType].apply(this._ctx, e.params);
      this._ctx.fillStyle = e.color;
      this._ctx.fill();
      this._ctx.closePath();

      this.handleWallCollisions(e);
    }
  }

  handleWallCollisions(e) {
    if (e.isMachine) {
      // This is not pretty. Will fix it later.
      let futureX = e.positionVector.x() + e.velocityVector.x();
      let futureY = e.positionVector.y() + e.velocityVector.y();
      if (futureX > this._canvas.width - e.radius || futureX < e.radius) {
        e.flipDirection('v');
      }
      if (futureY < e.radius) {
        e.flipDirection('h');
      }
    }
  }

  startGameLoop() {
    requestAnimationFrame(()=> this.drawScene());
  }

  start() {
    this.startGameLoop();
  }
}
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Visual representation of a ruler.
 * Lots of options, see default options in constructor.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var Text = require( 'SCENERY/nodes/Text' );

  // constants
  var DEFAULT_FONT = new PhetFont( 18 );

  /**
   * @param {number} rulerWidth  distance between left-most and right-most tick, insets will be added to this
   * @param {number} rulerHeight
   * @param {number} majorTickWidth
   * @param {string[]} majorTickLabels
   * @param {string} units
   * @param {Object} [options]
   * @constructor
   */
  function RulerNode( rulerWidth, rulerHeight, majorTickWidth, majorTickLabels, units, options ) {

    // default options
    options = _.extend( {
      // body of the ruler
      backgroundFill: 'rgb(236, 225, 113)',
      backgroundStroke: 'black',
      backgroundLineWidth: 1,
      insetsWidth: 14, // space between the ends of the ruler and the first and last tick marks

      // major tick options
      majorTickFont: DEFAULT_FONT,
      majorTickHeight: ( 0.4 * rulerHeight ) / 2,
      majorTickStroke: 'black',
      majorTickLineWidth: 1,

      // minor tick options
      minorTickFont: DEFAULT_FONT,
      minorTickHeight: ( 0.2 * rulerHeight ) / 2,
      minorTickStroke: 'black',
      minorTickLineWidth: 1,
      minorTicksPerMajorTick: 0,

      // units options
      unitsFont: DEFAULT_FONT,
      unitsMajorTickIndex: 0, // units will be place to the right of this major tick
      unitsSpacing: 3, // horizontal space between the tick label and the units

      // appearance options
      tickMarksOnTop: true,
      tickMarksOnBottom: true
    }, options );

    // things you're likely to mess up, add more as needed
    assert && assert( Math.floor( rulerWidth / majorTickWidth ) + 1 === majorTickLabels.length ); // do we have enough major tick labels?
    assert && assert( options.unitsMajorTickIndex < majorTickLabels.length );
    assert && assert( options.majorTickHeight < rulerHeight / 2 );
    assert && assert( options.minorTickHeight < rulerHeight / 2 );

    Node.call( this );

    // background
    var backgroundNode = new Rectangle( 0, 0, rulerWidth + ( 2 * options.insetsWidth ), rulerHeight, {
      fill: options.backgroundFill,
      stroke: options.backgroundStroke,
      lineWidth: options.backgroundLineWidth
    } );
    this.addChild( backgroundNode );

    // Lay out tick marks from left to right
    var minorTickWidth = majorTickWidth / ( options.minorTicksPerMajorTick + 1 );
    var x = options.insetsWidth;
    var majorTickIndex = 0;

    // Minimize number of nodes by using one Path for each type of tick line
    var majorTickLinesShape = new Shape();
    var minorTickLinesShape = new Shape();

    while ( x <= ( rulerWidth + options.insetsWidth ) ) {

      if ( ( x - options.insetsWidth ) % majorTickWidth === 0 ) {

        // Major tick label
        var majorTickLabel = majorTickLabels[ majorTickIndex ];
        var majorTickLabelNode = new Text( majorTickLabel, { font: options.majorTickFont } );
        //Clamp and make sure the labels stay within the ruler, especially if the insetsWidth has been set low (or to zero)
        majorTickLabelNode.x = x - ( majorTickLabelNode.width / 2 );
        majorTickLabelNode.centerY = backgroundNode.centerY;

        // Only add the major tick label if the insetsWidth is nonzero, or if it is not an end label
        if ( options.insetsWidth !== 0 || ( majorTickIndex !== 0 ) ) {
          this.addChild( majorTickLabelNode );
        }

        // Major tick line
        if ( options.tickMarksOnTop ) {
          majorTickLinesShape.moveTo( x, 0 ).lineTo( x, options.majorTickHeight );
        }
        if ( options.tickMarksOnBottom ) {
          majorTickLinesShape.moveTo( x, rulerHeight - options.majorTickHeight ).lineTo( x, rulerHeight );
        }

        // Units label
        if ( majorTickIndex === options.unitsMajorTickIndex ) {
          var unitsNode = new Text( units, { font: options.unitsFont } );
          this.addChild( unitsNode );
          unitsNode.x = majorTickLabelNode.x + majorTickLabelNode.width + options.unitsSpacing;
          unitsNode.y = majorTickLabelNode.y + majorTickLabelNode.height - unitsNode.height;
        }

        majorTickIndex++;
      }
      else {
        // Minor tick
        if ( options.tickMarksOnTop ) {
          minorTickLinesShape.moveTo( x, 0 ).lineTo( x, options.minorTickHeight );
        }
        if ( options.tickMarksOnBottom ) {
          minorTickLinesShape.moveTo( x, rulerHeight - options.minorTickHeight ).lineTo( x, rulerHeight );
        }
      }
      x += minorTickWidth;
    }

    // Major tick lines
    this.addChild( new Path( majorTickLinesShape, {
      stroke: options.majorTickStroke,
      lineWidth: options.majorTickLineWidth
    } ) );

    // Minor tick lines
    this.addChild( new Path( minorTickLinesShape, {
      stroke: options.minorTickStroke,
      lineWidth: options.minorTickLineWidth
    } ) );

    this.mutate( options );
  }

  return inherit( Node, RulerNode );
} );
<commit_msg>#152 workaround for floating-point precision errors when placing tick marks<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Visual representation of a ruler.
 * Lots of options, see default options in constructor.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var Text = require( 'SCENERY/nodes/Text' );

  // constants
  var DEFAULT_FONT = new PhetFont( 18 );

  /**
   * @param {number} rulerWidth  distance between left-most and right-most tick, insets will be added to this
   * @param {number} rulerHeight
   * @param {number} majorTickWidth
   * @param {string[]} majorTickLabels
   * @param {string} units
   * @param {Object} [options]
   * @constructor
   */
  function RulerNode( rulerWidth, rulerHeight, majorTickWidth, majorTickLabels, units, options ) {

    // default options
    options = _.extend( {
      // body of the ruler
      backgroundFill: 'rgb(236, 225, 113)',
      backgroundStroke: 'black',
      backgroundLineWidth: 1,
      insetsWidth: 14, // space between the ends of the ruler and the first and last tick marks

      // major tick options
      majorTickFont: DEFAULT_FONT,
      majorTickHeight: ( 0.4 * rulerHeight ) / 2,
      majorTickStroke: 'black',
      majorTickLineWidth: 1,

      // minor tick options
      minorTickFont: DEFAULT_FONT,
      minorTickHeight: ( 0.2 * rulerHeight ) / 2,
      minorTickStroke: 'black',
      minorTickLineWidth: 1,
      minorTicksPerMajorTick: 0,

      // units options
      unitsFont: DEFAULT_FONT,
      unitsMajorTickIndex: 0, // units will be place to the right of this major tick
      unitsSpacing: 3, // horizontal space between the tick label and the units

      // appearance options
      tickMarksOnTop: true,
      tickMarksOnBottom: true
    }, options );

    // things you're likely to mess up, add more as needed
    assert && assert( Math.floor( rulerWidth / majorTickWidth ) + 1 === majorTickLabels.length ); // do we have enough major tick labels?
    assert && assert( options.unitsMajorTickIndex < majorTickLabels.length );
    assert && assert( options.majorTickHeight < rulerHeight / 2 );
    assert && assert( options.minorTickHeight < rulerHeight / 2 );

    Node.call( this );

    // background
    var backgroundNode = new Rectangle( 0, 0, rulerWidth + ( 2 * options.insetsWidth ), rulerHeight, {
      fill: options.backgroundFill,
      stroke: options.backgroundStroke,
      lineWidth: options.backgroundLineWidth
    } );
    this.addChild( backgroundNode );

    // Lay out tick marks from left to right
    var minorTickWidth = majorTickWidth / ( options.minorTicksPerMajorTick + 1 );
    var numberOfTicks = ( rulerWidth / minorTickWidth ) + 1;
    var x = options.insetsWidth;
    var majorTickIndex = 0;

    // Minimize number of nodes by using one Path for each type of tick line
    var majorTickLinesShape = new Shape();
    var minorTickLinesShape = new Shape();

    for ( var i = 0; i < numberOfTicks; i++ ) {

      if ( i % ( options.minorTicksPerMajorTick + 1 ) === 0 ) {  // assumes that the first (leftmost) tick is a major tick

        // Major tick label
        var majorTickLabel = majorTickLabels[ majorTickIndex ];
        var majorTickLabelNode = new Text( majorTickLabel, { font: options.majorTickFont } );
        //Clamp and make sure the labels stay within the ruler, especially if the insetsWidth has been set low (or to zero)
        majorTickLabelNode.x = x - ( majorTickLabelNode.width / 2 );
        majorTickLabelNode.centerY = backgroundNode.centerY;

        // Only add the major tick label if the insetsWidth is nonzero, or if it is not an end label
        if ( options.insetsWidth !== 0 || ( majorTickIndex !== 0 ) ) {
          this.addChild( majorTickLabelNode );
        }

        // Major tick line
        if ( options.tickMarksOnTop ) {
          majorTickLinesShape.moveTo( x, 0 ).lineTo( x, options.majorTickHeight );
        }
        if ( options.tickMarksOnBottom ) {
          majorTickLinesShape.moveTo( x, rulerHeight - options.majorTickHeight ).lineTo( x, rulerHeight );
        }

        // Units label
        if ( majorTickIndex === options.unitsMajorTickIndex ) {
          var unitsNode = new Text( units, { font: options.unitsFont } );
          this.addChild( unitsNode );
          unitsNode.x = majorTickLabelNode.x + majorTickLabelNode.width + options.unitsSpacing;
          unitsNode.y = majorTickLabelNode.y + majorTickLabelNode.height - unitsNode.height;
        }

        majorTickIndex++;
      }
      else {
        // Minor tick
        if ( options.tickMarksOnTop ) {
          minorTickLinesShape.moveTo( x, 0 ).lineTo( x, options.minorTickHeight );
        }
        if ( options.tickMarksOnBottom ) {
          minorTickLinesShape.moveTo( x, rulerHeight - options.minorTickHeight ).lineTo( x, rulerHeight );
        }
      }
      x += minorTickWidth;
    }

    // Major tick lines
    this.addChild( new Path( majorTickLinesShape, {
      stroke: options.majorTickStroke,
      lineWidth: options.majorTickLineWidth
    } ) );

    // Minor tick lines
    this.addChild( new Path( minorTickLinesShape, {
      stroke: options.minorTickStroke,
      lineWidth: options.minorTickLineWidth
    } ) );

    this.mutate( options );
  }

  return inherit( Node, RulerNode );
} );
<|endoftext|>


<commit_before><commit_msg>Add render/dispose behavior for toggle.  Rework person field UI to match proposed flow and behavior.  Add/change slimform styles.  Change toggle script to accomodate a toggle container which will then search for next element from.  Change toggle icon size.  Add dirty tracking.<commit_after><|endoftext|>


<commit_before>'use strict';

attachLiveContent('homepage');

function openNav() {
    document.getElementById("myNav").style.height = "100%";
}

function closeNav() {
    document.getElementById("myNav").style.height = "0%";
}
<commit_msg>Removed navbar script<commit_after>'use strict';

attachLiveContent('homepage');
<|endoftext|>


<commit_before>/**
 * Traverser module

 * Currently only check if the circuit on the board is closed or not.
 * NOTE: This currently assumes that there is only one circuit on the board.
 * @module Traverser
 */

'use strict';

/**
 * Traverser
 *
 * @instance
 * @returns {Traverser}
 */
let Traverser = function Traverser() {

    /**
     * Deactivates all open-ended branches
     *
     * @private
     * @method deactivateOpenBranches
     * @param {Board} board
     */
    function deactivateOpenBranches(board) {
        board.occupiedSlots.forEach((slot) => {
            deactivateOpenEnds(board, slot);
        });
    }

    /**
     * Given a slot, we recursively see if the components connected to it needs deactivating or not.
     *
     * @private
     * @method deactivateOpenEnds
     * @param {Board} board
     * @param {Slot} slot
     */
    function deactivateOpenEnds(board, slot) {
        if (slot.activeCount === 1) {
            let pinAndComponent = slot.activeConnections.values()    // returns @@iterator that contains values
                                    .next().value,                   // getting the only value in that Map
                component = pinAndComponent.component,
                pin = pinAndComponent.pin;

            let otherPins = component.getOtherPins([slot.x, slot.y]);

            /*  If the component is NOT open ended AND active, deactivate
                If the component is open ended AND the pin index is 0 AND active, deactivate
                The second condition is to deactivate stray/isolated open ended components
            */
            if ((!component.openEnded || (component.openEnded && pin === 0)) && component.active) {
                component.active = false;
                otherPins.forEach((pos) => {
                    deactivateOpenEnds(board, board.board[pos[0]][pos[1]]);
                });
            }
        }
    }

    /**
     * Errors if no source component found
     *
     * @private
     * @method checkSourceExists
     * @param {Board} board
     */
    function checkSourceExists(board) {
        if(!board.hasType(ComponentType.Source)){
            logger('No source component found!');
        }
    }

    /**
     * Errors if no ground component found
     *
     * @private
     * @method checkGroundExists
     * @param {Board} board
     */
    function checkGroundExists(board) {
        if(!board.hasType(ComponentType.Ground)){
            logger('No ground component found!');
        }
    }

    /**
     * Traverses through each active source component to check if the circuit is closed or not
     *
     * @private
     * @method checkClosedCircuit
     * @param {Board} board
     */
    function checkClosedCircuit(board) {
        checkSourceExists(board);
        //checkGroundExists(board);
        deactivateOpenBranches(board);

        // We have to find a list of active Sources, so we can start traversing!
        let activeSources = board.activeSources;

        // The source component(s) might be deactivated due to being on an open branch
        // so that counts as an open circuit too
        if(activeSources.length === 0){
            logger('No closed circuit found');
        }

        // Start traversal from each source components
        activeSources.forEach((source)=>{
            traverseSource(board, source);
        });
    }

    /**
     * Traverse through the circuit starting from the source component.

     * Sets the Board.closed field to 'true' if circuit is closed.

     *
     * @private
     * @method traverseSource
     * @param {Board} board
     * @param {Source} source
     */
    function traverseSource(board, source) {
        // Note: This is code is assuming that the source has two pins

        // Exit if this source has been traveled already
        if(source.traveled){
            return;
        }
        source.traveled = true;

        // Store the negative pin positions of the source so we can tell if we are back at source later
        let currentPos = source.pins[0],
            negativePinPos = source.pins[1];

        // This acts like a 'stack', storing the most recent traveled node
        let nodeTrace = [];

        console.log('START OF TRAVERSAL');

        // Start traversing from the source
        checkBackAtSource(currentPos);

        /**
         * Check to see if we're back at the source component
         *
         * @private
         * @memberOf traverseSource
         * @method checkBackAtSource
         * @param {Position} pos
         */
        function checkBackAtSource(pos) {
            if(pos.toString() === negativePinPos.toString()){
                board.closed = true;
                findUnfinishedNode();
            }
            else {
                checkTrueNode(pos);
            }
        };

        /**
         * Find node(s) that has untraveled connection(s).

         * Returns true if the traversal is done.
         *
         * @private
         * @memberOf traverseSource
         * @method  findUnfinishedNode
         * @returns {boolean}
         */
        function findUnfinishedNode() {
            if(board.hasUnfinishedNode){
                backToLastNode();
            }
            else {
                console.log('END OF TRAVERSAL', nodeTrace);
                return true;
            }
        }

        /**
         * Jump back to the most recent traveled node and start traversing from there again
         *
         * @private
         * @memberOf traverseSource
         * @method backToLastNode
         */
        function backToLastNode() {
            let lastNodePos = nodeTrace[nodeTrace.length-1];
            findUntraveledConnections(lastNodePos);
        }

        /**
         * Find untraveled Connection(s) in a position/node
         *
         * @private
         * @memberOf traverseSource
         * @method findUntraveledConnections
         * @param {Position} pos
         */
        function findUntraveledConnections(pos) {
            let connection = board.getSlot(pos).untraveledConnections[0];

            if(!!connection){ // Found
                let nextPos = connection.component.pins[connection.pin];
                connection.component.traveled = true;
                goNextSlot(nextPos);
            }
            else { // Not found
                nodeTrace.pop();
                findUnfinishedNode();
            }
        }

        /**
         * Check if this position is a true node or not
         *
         * @private
         * @memberOf traverseSource
         * @method checkTrueNode
         * @param {Position} pos
         */
        function checkTrueNode(pos) {
            if(board.getSlot(pos).isTrueNode){
                existsInTrace(pos);
            }
            else {
                let connection = board.getSlot(pos).untraveledConnections[0],
                    nextPos = connection.component.getOtherPins(pos)[0];

                connection.component.traveled = true;
                goNextSlot(nextPos);
            }
        }

        /**
         * Check if this position/node exists in the trace already or not
         *
         * @private
         * @memberOf traverseSource
         * @method existsInTrace
         * @param {Position} pos
         */
        function existsInTrace(pos) {
            if(nodeTrace.find((node) => node.toString() === pos.toString())){
                backToLastNode();
            }
            else {
                nodeTrace.push(pos);

                let connection = board.getSlot(pos).untraveledConnections[0],
                    component = connection.component,
                    nextPos = component.getOtherPins(component.pins[connection.pin])[0];

                component.traveled = true;
                goNextSlot(nextPos);
            }
        }

        /**
         * Move onto the next position for traversal
         *
         * @private
         * @memberOf traverseSource
         * @method goNextSlot
         * @param {Position} nextPos
         */
        function goNextSlot(nextPos) {
            checkBackAtSource(nextPos);
        }
    }

    /**
     * Start the traversal
     *
     * @param {Board} board
     */
    function start(board) {
        checkClosedCircuit(board);
    }

    return {
        start: start,
        _checkSourceExists: checkSourceExists,
        _checkGroundExists: checkGroundExists,
        _deactivateOpenBranches: deactivateOpenBranches,
        _checkClosedCircuit: checkClosedCircuit
    };
}
<commit_msg>Corrected the comment on 'start' method. Added simple examples for Traverser.<commit_after>/**
 * Traverser module

 * Currently only check if the circuit on the board is closed or not.

 * NOTE: This currently assumes that there is only one circuit on the board.
 * @module Traverser
 */

'use strict';

/**
 * Traverser
 *
 * @instance
 * @returns {Traverser}
 *
 * @example
 * let traverser = Traverser();
 */
let Traverser = function Traverser() {

    /**
     * Deactivates all open-ended branches
     *
     * @private
     * @method deactivateOpenBranches
     * @param {Board} board
     */
    function deactivateOpenBranches(board) {
        board.occupiedSlots.forEach((slot) => {
            deactivateOpenEnds(board, slot);
        });
    }

    /**
     * Given a slot, we recursively see if the components connected to it needs deactivating or not.
     *
     * @private
     * @method deactivateOpenEnds
     * @param {Board} board
     * @param {Slot} slot
     */
    function deactivateOpenEnds(board, slot) {
        if (slot.activeCount === 1) {
            let pinAndComponent = slot.activeConnections.values()    // returns @@iterator that contains values
                                    .next().value,                   // getting the only value in that Map
                component = pinAndComponent.component,
                pin = pinAndComponent.pin;

            let otherPins = component.getOtherPins([slot.x, slot.y]);

            /*  If the component is NOT open ended AND active, deactivate
                If the component is open ended AND the pin index is 0 AND active, deactivate
                The second condition is to deactivate stray/isolated open ended components
            */
            if ((!component.openEnded || (component.openEnded && pin === 0)) && component.active) {
                component.active = false;
                otherPins.forEach((pos) => {
                    deactivateOpenEnds(board, board.board[pos[0]][pos[1]]);
                });
            }
        }
    }

    /**
     * Errors if no source component found
     *
     * @private
     * @method checkSourceExists
     * @param {Board} board
     */
    function checkSourceExists(board) {
        if(!board.hasType(ComponentType.Source)){
            logger('No source component found!');
        }
    }

    /**
     * Errors if no ground component found
     *
     * @private
     * @method checkGroundExists
     * @param {Board} board
     */
    function checkGroundExists(board) {
        if(!board.hasType(ComponentType.Ground)){
            logger('No ground component found!');
        }
    }

    /**
     * Traverses through each active source component to check if the circuit is closed or not
     *
     * @private
     * @method checkClosedCircuit
     * @param {Board} board
     */
    function checkClosedCircuit(board) {
        checkSourceExists(board);
        //checkGroundExists(board);
        deactivateOpenBranches(board);

        // We have to find a list of active Sources, so we can start traversing!
        let activeSources = board.activeSources;

        // The source component(s) might be deactivated due to being on an open branch
        // so that counts as an open circuit too
        if(activeSources.length === 0){
            logger('No closed circuit found');
        }

        // Start traversal from each source components
        activeSources.forEach((source)=>{
            traverseSource(board, source);
        });
    }

    /**
     * Traverse through the circuit starting from the source component.

     * Sets the Board.closed field to 'true' if circuit is closed.

     *
     * @private
     * @method traverseSource
     * @param {Board} board
     * @param {Source} source
     */
    function traverseSource(board, source) {
        // Note: This is code is assuming that the source has two pins

        // Exit if this source has been traveled already
        if(source.traveled){
            return;
        }
        source.traveled = true;

        // Store the negative pin positions of the source so we can tell if we are back at source later
        let currentPos = source.pins[0],
            negativePinPos = source.pins[1];

        // This acts like a 'stack', storing the most recent traveled node
        let nodeTrace = [];

        console.log('START OF TRAVERSAL');

        // Start traversing from the source
        checkBackAtSource(currentPos);

        /**
         * Check to see if we're back at the source component
         *
         * @private
         * @memberOf traverseSource
         * @method checkBackAtSource
         * @param {Position} pos
         */
        function checkBackAtSource(pos) {
            if(pos.toString() === negativePinPos.toString()){
                board.closed = true;
                findUnfinishedNode();
            }
            else {
                checkTrueNode(pos);
            }
        };

        /**
         * Find node(s) that has untraveled connection(s).

         * Returns true if the traversal is done.
         *
         * @private
         * @memberOf traverseSource
         * @method  findUnfinishedNode
         * @returns {boolean}
         */
        function findUnfinishedNode() {
            if(board.hasUnfinishedNode){
                backToLastNode();
            }
            else {
                console.log('END OF TRAVERSAL', nodeTrace);
                return true;
            }
        }

        /**
         * Jump back to the most recent traveled node and start traversing from there again
         *
         * @private
         * @memberOf traverseSource
         * @method backToLastNode
         */
        function backToLastNode() {
            let lastNodePos = nodeTrace[nodeTrace.length-1];
            findUntraveledConnections(lastNodePos);
        }

        /**
         * Find untraveled Connection(s) in a position/node
         *
         * @private
         * @memberOf traverseSource
         * @method findUntraveledConnections
         * @param {Position} pos
         */
        function findUntraveledConnections(pos) {
            let connection = board.getSlot(pos).untraveledConnections[0];

            if(!!connection){ // Found
                let nextPos = connection.component.pins[connection.pin];
                connection.component.traveled = true;
                goNextSlot(nextPos);
            }
            else { // Not found
                nodeTrace.pop();
                findUnfinishedNode();
            }
        }

        /**
         * Check if this position is a true node or not
         *
         * @private
         * @memberOf traverseSource
         * @method checkTrueNode
         * @param {Position} pos
         */
        function checkTrueNode(pos) {
            if(board.getSlot(pos).isTrueNode){
                existsInTrace(pos);
            }
            else {
                let connection = board.getSlot(pos).untraveledConnections[0],
                    nextPos = connection.component.getOtherPins(pos)[0];

                connection.component.traveled = true;
                goNextSlot(nextPos);
            }
        }

        /**
         * Check if this position/node exists in the trace already or not
         *
         * @private
         * @memberOf traverseSource
         * @method existsInTrace
         * @param {Position} pos
         */
        function existsInTrace(pos) {
            if(nodeTrace.find((node) => node.toString() === pos.toString())){
                backToLastNode();
            }
            else {
                nodeTrace.push(pos);

                let connection = board.getSlot(pos).untraveledConnections[0],
                    component = connection.component,
                    nextPos = component.getOtherPins(component.pins[connection.pin])[0];

                component.traveled = true;
                goNextSlot(nextPos);
            }
        }

        /**
         * Move onto the next position for traversal
         *
         * @private
         * @memberOf traverseSource
         * @method goNextSlot
         * @param {Position} nextPos
         */
        function goNextSlot(nextPos) {
            checkBackAtSource(nextPos);
        }
    }

    /**
     * Start the traversal
     *
     * @public
     * @instance
     * @method start
     * @param {Board} board
     *
     * @example
     * let traverser = Traverser();
     * traverser.start(board);
     */
    function start(board) {
        checkClosedCircuit(board);
    }

    return {
        start: start,
        _checkSourceExists: checkSourceExists,
        _checkGroundExists: checkGroundExists,
        _deactivateOpenBranches: deactivateOpenBranches,
        _checkClosedCircuit: checkClosedCircuit
    };
}
<|endoftext|>


<commit_before>/**
 * @file ttf写入器
 * @author mengke01(kekee000@gmail.com)
 * @author Belleve(belleve@typeof.net)
 */

        var Writer = require('./writer');
        var Directory = require('./table/directory');
        var supportTables = require('./table/support');
        var checkSum = require('./util/checkSum');
        var error = require('./error');

        // 支持写的表, 注意表顺序
        var SUPPORT_TABLES = [
            'OS/2',
            'cmap',
            'glyf',
            'head',
            'hhea',
            'hmtx',
            'loca',
            'maxp',
            'name',
            'post'
        ];

        /**
         * 对ttf的表进行评估，标记需要处理的表
         *
         * @param  {Object} ttf ttf对象
         */
        function prepareDump(ttf) {

            if (!ttf.glyf || ttf.glyf.length === 0) {
                error.raise(10201);
            }

            if (!ttf['OS/2'] || !ttf.head || !ttf.name) {
                error.raise(10204);
            }

            var tables = SUPPORT_TABLES.slice(0);
            ttf.writeOptions = {};
            // hinting tables direct copy
            if (this.options.hinting) {
                ['cvt', 'fpgm', 'prep', 'gasp'].forEach(function (table) {
                    if (ttf[table]) {
                        tables.push(table);
                    }
                });
            }

            if (this.options.writeUnknownTables) {
                for(var tableName in ttf) if(ttf[tableName] && ttf[tableName].name === tableName) {
                    tables.push(tableName)
                }
            }

            ttf.writeOptions.hinting = !!this.options.hinting;
            ttf.writeOptions.preserveOS2Version = !!this.options.preserveOS2Version
            ttf.writeOptions.tables = tables.sort();
        }

        /**
         * 处理ttf结构，以便于写
         *
         * @param {ttfObject} ttf ttf数据结构
         */
        function resolve(ttf) {

            // 头部信息
            ttf.version = ttf.version || 0x1;
            ttf.numTables = ttf.writeOptions.tables.length;
            ttf.entrySelector = Math.floor(Math.log(ttf.numTables) / Math.LN2);
            ttf.searchRange = Math.pow(2, ttf.entrySelector) * 16;
            ttf.rangeShift = ttf.numTables * 16 - ttf.searchRange;

            // 重置校验码
            ttf.head.checkSumAdjustment = 0;
            ttf.head.magickNumber = 0x5F0F3CF5;

            if (typeof ttf.head.created === 'string') {
                ttf.head.created = /^\d+$/.test(ttf.head.created) ? +ttf.head.created : Date.parse(ttf.head.created);
            }

            ttf.head.modified = Date.now();

            var checkUnicodeRepeat = {}; // 检查是否有重复代码点

            // 将glyf的代码点按小到大排序
            ttf.glyf.forEach(function (glyf, index) {
                if (glyf.unicode) {
                    glyf.unicode = glyf.unicode.sort();

                    glyf.unicode.forEach(function (u) {
                        if (checkUnicodeRepeat[u]) {
                            error.raise(10200, index);
                        }
                        else {
                            checkUnicodeRepeat[u] = true;
                        }
                    });

                }
            });

        }

        var UnknownTableFormat = function(tableName){
            return function(){
                this.size = function(ttf){
                    return ttf[tableName] ? ttf[tableName].content.length : 0
                }
                this.write = function(writer, ttf){
                    if(ttf[tableName]) {
                        writer.writeBytes(ttf[tableName].content, ttf[tableName].content.length)
                    }
                }
            }
        }

        /**
         * 写ttf文件
         *
         * @param {ttfObject} ttf ttf数据结构
         * @return {ArrayBuffer} 字节流
         */
        function write(ttf) {

            // 用来做写入缓存的对象，用完后删掉
            ttf.support = {};

            // head + directory
            var ttfSize = 12 + ttf.numTables * 16;
            var ttfHeadOffset = 0; // 记录head的偏移

            // 构造tables
            ttf.support.tables = [];
            ttf.writeOptions.tables.forEach(function (tableName) {
                var offset = ttfSize;
                var Writer = supportTables[tableName] || UnknownTableFormat(tableName);
                var tableSize = new Writer().size(ttf); // 原始的表大小
                var size = tableSize; // 对齐后的表大小

                if (tableName === 'head') {
                    ttfHeadOffset = offset;
                }

                // 4字节对齐
                if (size % 4) {
                    size += 4 - size % 4;
                }

                ttf.support.tables.push({
                    name: tableName,
                    checkSum: 0,
                    offset: offset,
                    length: tableSize,
                    size: size,
                    Writer: Writer
                });

                ttfSize += size;
            });

            var writer = new Writer(new ArrayBuffer(ttfSize));

            // 写头部
            writer.writeFixed(ttf.version);
            writer.writeUint16(ttf.numTables);
            writer.writeUint16(ttf.searchRange);
            writer.writeUint16(ttf.entrySelector);
            writer.writeUint16(ttf.rangeShift);

            // 写表偏移
            !new Directory().write(writer, ttf);

            // 写支持的表数据
            ttf.support.tables.forEach(function (table) {

                var tableStart = writer.offset;
                !new table.Writer().write(writer, ttf);

                if (table.length % 4) {
                    // 对齐字节
                    writer.writeEmpty(4 - table.length % 4);
                }

                // 计算校验和
                table.checkSum = checkSum(writer.getBuffer(), tableStart, table.size);

            });

            // 重新写入每个表校验和
            ttf.support.tables.forEach(function (table, index) {
                var offset = 12 + index * 16 + 4;
                writer.writeUint32(table.checkSum, offset);
            });

            // 写入总校验和
            var ttfCheckSum = (0xB1B0AFBA - checkSum(writer.getBuffer()) + 0x100000000) % 0x100000000;
            writer.writeUint32(ttfCheckSum, ttfHeadOffset + 8);

            delete ttf.writeOptions;
            delete ttf.support;

            var buffer = writer.getBuffer();
            writer.dispose();

            return buffer;
        }

        /**
         * ttf写入器的构造函数
         *
         * @param {Object} options 写入参数
         * @param {boolean} options.hinting 保留hinting信息
         * @constructor
         */
        function TTFWriter(options) {
            options = options || {};
            this.options = {
                hinting: options.hinting || false, // 不保留hints信息
                writeUnknownTables: options.writeUnknownTables || false, // Drop unknown tables
                preserveOS2Version: options.preserveOS2Version || false  // Preserve OS2 version before writing
            };
        }

        /**
         * 写一个ttf字体结构
         * @param {Object} ttf ttf数据结构
         * @return {ArrayBuffer} 缓冲数组
         */
        TTFWriter.prototype.write = function (ttf) {
            prepareDump.call(this, ttf);
            resolve.call(this, ttf);
            var buffer = write.call(this, ttf);
            return buffer;
        };

        /**
         * 注销
         */
        TTFWriter.prototype.dispose = function () {
            delete this.options;
        };

        module.exports = TTFWriter;

<commit_msg>Re-order tables being written.<commit_after>/**
 * @file ttf写入器
 * @author mengke01(kekee000@gmail.com)
 * @author Belleve(belleve@typeof.net)
 */

        var Writer = require('./writer');
        var Directory = require('./table/directory');
        var supportTables = require('./table/support');
        var checkSum = require('./util/checkSum');
        var error = require('./error');

        // 支持写的表, 注意表顺序
        var SUPPORT_TABLES = [
            'head',
            'hhea',
            'OS/2',
            'maxp',
            'name',
            'loca',
            'cmap',
            'glyf',
            'hmtx',
            'post'
        ];

        /**
         * 对ttf的表进行评估，标记需要处理的表
         *
         * @param  {Object} ttf ttf对象
         */
        function prepareDump(ttf) {

            if (!ttf.glyf || ttf.glyf.length === 0) {
                error.raise(10201);
            }

            if (!ttf['OS/2'] || !ttf.head || !ttf.name) {
                error.raise(10204);
            }

            var tables = SUPPORT_TABLES.slice(0);
            ttf.writeOptions = {};
            // hinting tables direct copy
            if (this.options.hinting) {
                ['cvt', 'fpgm', 'prep', 'gasp'].forEach(function (table) {
                    if (ttf[table]) {
                        tables.push(table);
                    }
                });
            }

            if (this.options.writeUnknownTables) {
                for(var tableName in ttf) if(ttf[tableName] && ttf[tableName].name === tableName) {
                    tables.push(tableName)
                }
            }

            ttf.writeOptions.hinting = !!this.options.hinting;
            ttf.writeOptions.preserveOS2Version = !!this.options.preserveOS2Version
            ttf.writeOptions.tables = tables;
        }

        /**
         * 处理ttf结构，以便于写
         *
         * @param {ttfObject} ttf ttf数据结构
         */
        function resolve(ttf) {

            // 头部信息
            ttf.version = ttf.version || 0x1;
            ttf.numTables = ttf.writeOptions.tables.length;
            ttf.entrySelector = Math.floor(Math.log(ttf.numTables) / Math.LN2);
            ttf.searchRange = Math.pow(2, ttf.entrySelector) * 16;
            ttf.rangeShift = ttf.numTables * 16 - ttf.searchRange;

            // 重置校验码
            ttf.head.checkSumAdjustment = 0;
            ttf.head.magickNumber = 0x5F0F3CF5;

            if (typeof ttf.head.created === 'string') {
                ttf.head.created = /^\d+$/.test(ttf.head.created) ? +ttf.head.created : Date.parse(ttf.head.created);
            }

            ttf.head.modified = Date.now();

            var checkUnicodeRepeat = {}; // 检查是否有重复代码点

            // 将glyf的代码点按小到大排序
            ttf.glyf.forEach(function (glyf, index) {
                if (glyf.unicode) {
                    glyf.unicode = glyf.unicode.sort();

                    glyf.unicode.forEach(function (u) {
                        if (checkUnicodeRepeat[u]) {
                            error.raise(10200, index);
                        }
                        else {
                            checkUnicodeRepeat[u] = true;
                        }
                    });

                }
            });

        }

        var UnknownTableFormat = function(tableName){
            return function(){
                this.size = function(ttf){
                    return ttf[tableName] ? ttf[tableName].content.length : 0
                }
                this.write = function(writer, ttf){
                    if(ttf[tableName]) {
                        writer.writeBytes(ttf[tableName].content, ttf[tableName].content.length)
                    }
                }
            }
        }

        /**
         * 写ttf文件
         *
         * @param {ttfObject} ttf ttf数据结构
         * @return {ArrayBuffer} 字节流
         */
        function write(ttf) {

            // 用来做写入缓存的对象，用完后删掉
            ttf.support = {};

            // head + directory
            var ttfSize = 12 + ttf.numTables * 16;
            var ttfHeadOffset = 0; // 记录head的偏移

            // 构造tables
            ttf.support.tables = [];
            ttf.writeOptions.tables.forEach(function (tableName) {
                var offset = ttfSize;
                var Writer = supportTables[tableName] || UnknownTableFormat(tableName);
                var tableSize = new Writer().size(ttf); // 原始的表大小
                var size = tableSize; // 对齐后的表大小

                if (tableName === 'head') {
                    ttfHeadOffset = offset;
                }

                // 4字节对齐
                if (size % 4) {
                    size += 4 - size % 4;
                }

                ttf.support.tables.push({
                    name: tableName,
                    checkSum: 0,
                    offset: offset,
                    length: tableSize,
                    size: size,
                    Writer: Writer
                });

                ttfSize += size;
            });

            var writer = new Writer(new ArrayBuffer(ttfSize));

            // 写头部
            writer.writeFixed(ttf.version);
            writer.writeUint16(ttf.numTables);
            writer.writeUint16(ttf.searchRange);
            writer.writeUint16(ttf.entrySelector);
            writer.writeUint16(ttf.rangeShift);

            // 写表偏移
            !new Directory().write(writer, ttf);

            // 写支持的表数据
            ttf.support.tables.forEach(function (table) {

                var tableStart = writer.offset;
                !new table.Writer().write(writer, ttf);

                if (table.length % 4) {
                    // 对齐字节
                    writer.writeEmpty(4 - table.length % 4);
                }

                // 计算校验和
                table.checkSum = checkSum(writer.getBuffer(), tableStart, table.size);

            });

            // 重新写入每个表校验和
            ttf.support.tables.forEach(function (table, index) {
                var offset = 12 + index * 16 + 4;
                writer.writeUint32(table.checkSum, offset);
            });

            // 写入总校验和
            var ttfCheckSum = (0xB1B0AFBA - checkSum(writer.getBuffer()) + 0x100000000) % 0x100000000;
            writer.writeUint32(ttfCheckSum, ttfHeadOffset + 8);

            delete ttf.writeOptions;
            delete ttf.support;

            var buffer = writer.getBuffer();
            writer.dispose();

            return buffer;
        }

        /**
         * ttf写入器的构造函数
         *
         * @param {Object} options 写入参数
         * @param {boolean} options.hinting 保留hinting信息
         * @constructor
         */
        function TTFWriter(options) {
            options = options || {};
            this.options = {
                hinting: options.hinting || false, // 不保留hints信息
                writeUnknownTables: options.writeUnknownTables || false, // Drop unknown tables
                preserveOS2Version: options.preserveOS2Version || false  // Preserve OS2 version before writing
            };
        }

        /**
         * 写一个ttf字体结构
         * @param {Object} ttf ttf数据结构
         * @return {ArrayBuffer} 缓冲数组
         */
        TTFWriter.prototype.write = function (ttf) {
            prepareDump.call(this, ttf);
            resolve.call(this, ttf);
            var buffer = write.call(this, ttf);
            return buffer;
        };

        /**
         * 注销
         */
        TTFWriter.prototype.dispose = function () {
            delete this.options;
        };

        module.exports = TTFWriter;

<|endoftext|>