<commit_before>var editor = undefined;
var window = new Object();
var navigator = new Object();
navigator.userAgent = "chrome";
window.navigator = navigator;
window.location = new Object();

window.location.protocol = "";
window.location.host = "";
window.location.href = "";

window.NATIVE_EDITOR_ENJINE = true;
window.NATIVE_EDITOR_ENJINE_SYNC_RECALC = true;

var document = new Object();
window.document = document;

function Image()
{
    this.src = "";
    this.onload = function()
    {
    }
    this.onerror = function()
    {
    }
}

function _image_data()
{
    this.data = null;
    this.length = 0;
}

function native_context2d(parent)
{
    this.canvas = parent;

    this.globalAlpha = 0;
    this.globalCompositeOperation = "";
    this.fillStyle = "";
    this.strokeStyle = "";

    this.lineWidth = 0;
    this.lineCap = 0;
    this.lineJoin = 0;
    this.miterLimit = 0;
    this.shadowOffsetX = 0;
    this.shadowOffsetY = 0;
    this.shadowBlur = 0;
    this.shadowColor = 0;
    this.font = "";
    this.textAlign = 0;
    this.textBaseline = 0;
}
native_context2d.prototype =
{
    save : function() {},
    restore : function() {},

    scale : function(x,y) {},
    rotate : function(angle) {},
    translate : function(x,y) {},
    transform : function(m11,m12,m21,m22,dx,dy) {},
    setTransform : function(m11,m12,m21,m22,dx,dy) {},

    createLinearGradient : function(x0,y0,x1,y1) { return null; },
    createRadialGradient : function(x0,y0,r0,x1,y1,r1) { return null; },
    createPattern : function(image,repetition) { return null; },

    clearRect : function(x,y,w,h) {},
    fillRect : function(x,y,w,h) {},
    strokeRect : function(x,y,w,h) {},

    beginPath : function() {},
    closePath : function() {},
    moveTo : function(x,y) {},
    lineTo : function(x,y) {},
    quadraticCurveTo : function(cpx,cpy,x,y) {},
    bezierCurveTo : function(cp1x,cp1y,cp2x,cp2y,x,y) {},
    arcTo : function(x1,y1,x2,y2,radius) {},
    rect : function(x,y,w,h) {},
    arc : function(x,y,radius,startAngle,endAngle,anticlockwise) {},

    fill : function() {},
    stroke : function() {},
    clip : function() {},
    isPointInPath : function(x,y) {},
    drawFocusRing : function(element,xCaret,yCaret,canDrawCustom) {},

    fillText : function(text,x,y,maxWidth) {},
    strokeText : function(text,x,y,maxWidth) {},
    measureText : function(text) {},

    drawImage : function(img_elem,dx_or_sx,dy_or_sy,dw_or_sw,dh_or_sh,dx,dy,dw,dh) {},

    createImageData : function(imagedata_or_sw,sh)
    {
        var _data = new _image_data();
        _data.length = imagedata_or_sw * sh * 4;
        _data.data = new Uint8Array(imagedata_or_sw * sh * 4);
        return _data;
    },
    getImageData : function(sx,sy,sw,sh) {},
    putImageData : function(image_data,dx,dy,dirtyX,dirtyY,dirtyWidth,dirtyHeight) {}
};

function native_canvas()
{
    this.id = "";
    this.width = 300;
    this.height = 150;

    this.nodeType = 1;
}
native_canvas.prototype =
{
    getContext : function(type)
    {
        if (type == "2d")
            return new native_context2d(this);
        return null;
    },

    toDataUrl : function(type)
    {
        return "";
    },

    addEventListener : function()
    {
    },

    attr : function()
    {
    }
};

window["Asc"] = new Object();

var _null_object = new Object();
_null_object.length = 0;
_null_object.nodeType = 1;
_null_object.offsetWidth = 1;
_null_object.offsetHeight = 1;
_null_object.clientWidth = 1;
_null_object.clientHeight = 1;
_null_object.scrollWidth = 1;
_null_object.scrollHeight = 1;
_null_object.style = new Object();
_null_object.documentElement = _null_object;
_null_object.body = _null_object;
_null_object.ownerDocument = _null_object;
_null_object.defaultView = _null_object;

_null_object.addEventListener = function(){};
_null_object.setAttribute = function(){};
_null_object.getElementsByTagName = function() { return []; };
_null_object.appendChild = function() {};
_null_object.removeChild = function() {};
_null_object.insertBefore = function() {};
_null_object.childNodes = [];
_null_object.parent = _null_object;
_null_object.parentNode = _null_object;
_null_object.find = function() { return this; };
_null_object.appendTo = function() { return this; };
_null_object.css = function() { return this; };
_null_object.width = function() { return 0; };
_null_object.height = function() { return 0; };
_null_object.attr = function() { return this; };
_null_object.prop = function() { return this; };
_null_object.val = function() { return this; };
_null_object.remove = function() {};
_null_object.getComputedStyle = function() { return null; };
_null_object.getContext = function(type) {
    if (type == "2d")
        return new native_context2d(this);
    return null;
};

window._null_object = _null_object;

document.createElement = function(type)
{
    if (type && type.toLowerCase)
    {
        if (type.toLowerCase() == "canvas")
            return new native_canvas();
    }

    return _null_object;
}

function _return_empty_html_element() { return _null_object; };

document.createDocumentFragment = _return_empty_html_element;
document.getElementsByTagName = function(tag) {
    var ret = [];
    if ("head" == tag)
        ret.push(_null_object);
    return ret;
};
document.insertBefore = function() {};
document.appendChild = function() {};
document.removeChild = function() {};
document.getElementById = function() { return _null_object; };
document.createComment = function() { return undefined; };

document.documentElement = _null_object;
document.body = _null_object;

var native = CreateNativeEngine();
window.native = native;
window["native"] = native;

function GetNativeEngine()
{
	return window.native;
}

var native_renderer = null;
var _api = null;
var Asc = null;

function NativeOpenFile()
{
    var doc_bin = window.native.GetFileString(g_file_path);
    if (NATIVE_DOCUMENT_TYPE == "presentation" || NATIVE_DOCUMENT_TYPE == "document")
    {
        _api = new window["asc_docs_api"]("");
        _api.asc_nativeOpenFile(doc_bin);
    }
    else
    {
        Asc = window["Asc"];
        _api = new window["Asc"]["spreadsheet_api"];
        var doc_bin = window.native.GetFileString(g_file_path);
        _api.asc_nativeOpenFile(doc_bin);
    }
}

function NativeOpenFile2()
{
	g_oTextMeasurer = new CTextMeasurerWrapper();
	g_oTextMeasurer.Init();

	window.g_file_path = "native_open_file";
	window.NATIVE_DOCUMENT_TYPE = window.native.GetEditorType();
    var doc_bin = window.native.GetFileString(window.g_file_path);
    if (window.NATIVE_DOCUMENT_TYPE == "presentation" || window.NATIVE_DOCUMENT_TYPE == "document")
    {
        _api = new window["asc_docs_api"]("");
        _api.asc_nativeOpenFile(doc_bin);
    }
    else
    {
        Asc = window["Asc"];
        _api = new window["Asc"]["spreadsheet_api"];
        var doc_bin = window.native.GetFileString(window.g_file_path);
        _api.asc_nativeOpenFile(doc_bin);
    }
}

function NativeCalculateFile()
{
	_api.asc_nativeCalculateFile();
}

function NativeApplyChanges()
{
    if (NATIVE_DOCUMENT_TYPE == "presentation" || NATIVE_DOCUMENT_TYPE == "document")
    {
        var __changes = [];
        var _count_main = window.native.GetCountChanges();
        for (var i = 0; i < _count_main; i++)
        {
            var _changes_file = window.native.GetChangesFile(i);
            var _changes = JSON.parse(window.native.GetFileString(_changes_file));

            for (var j = 0; j < _changes.length; j++)
            {
                __changes.push(_changes[j]);
            }
        }
        _api.asc_nativeApplyChanges(__changes);
    }
    else
    {
        var __changes = [];
        var _count_main = window.native.GetCountChanges();
        for (var i = 0; i < _count_main; i++)
        {
            var _changes_file = window.native.GetChangesFile(i);
            var _changes = JSON.parse(window.native.GetFileString(_changes_file));

            for (var j = 0; j < _changes.length; j++)
            {
                __changes.push(_changes[j]);
            }
        }

        _api.asc_nativeApplyChanges(__changes);
    }
}
function NativeGetFileString()
{
    return _api.asc_nativeGetFile();
}

function GetNativeCountPages()
{
    return _api.asc_nativePrintPagesCount();
}

window.memory1 = null;
window.memory2 = null;

function GetNativePageBase64(pageIndex)
{
    if (null == window.memory1)
        window.memory1 = CreateNativeMemoryStream();
    else
        window.memory1.ClearNoAttack();

    if (null == window.memory2)
        window.memory2 = CreateNativeMemoryStream();
    else
        window.memory2.ClearNoAttack();

    if (native_renderer == null)
    {
        native_renderer = _api.asc_nativeCheckPdfRenderer(window.memory1, window.memory2);
    }
    else
    {
        window.memory1.ClearNoAttack();
        window.memory2.ClearNoAttack();
    }

    _api.asc_nativePrint(native_renderer, pageIndex);
    return window.memory1;
}

function GetNativePageMeta(pageIndex)
{
	_api.WordControl.m_oDrawingDocument.LogicDocument = _api.WordControl.m_oDrawingDocument.m_oLogicDocument;
    _api.WordControl.m_oDrawingDocument.RenderPage(pageIndex);
}

function GetNativeId()
{
    return window.native.GetFileId();
}

function clearTimeout() {};
function setTimeout() {};
function clearInterval() {};
function setInterval() {};

window.clearTimeout = clearTimeout;
window.setTimeout = setTimeout;
window.clearInterval = clearInterval;
window.setInterval = setInterval;

window["OfficeExcel"] = {type:'common'};
var OfficeExcel = window["OfficeExcel"];

var console = {
	log : function(param) { window.native.ConsoleLog(param); }
};

// HTML page interface
window.native.Call_OnUpdateOverlay = function(param)
{
    _api.WordControl.m_oDrawingDocument.OnUpdateOverlay();
}

window.native.Call_OnMouseDown = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnMouseDown(e);
}
window.native.Call_OnMouseUp = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnMouseUp(e);
}
window.native.Call_OnMouseMove = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnMouseMove(e);
}

window.native.Call_OnKeyDown = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnKeyDown(e);
}
window.native.Call_OnKeyPress = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnKeyPress(e);
}
window.native.Call_OnKeyUp = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnKeyPress(e);
}
<commit_msg><commit_after>var editor = undefined;
var window = new Object();
var navigator = new Object();
navigator.userAgent = "chrome";
window.navigator = navigator;
window.location = new Object();

window.location.protocol = "";
window.location.host = "";
window.location.href = "";

window.NATIVE_EDITOR_ENJINE = true;
window.NATIVE_EDITOR_ENJINE_SYNC_RECALC = true;

var document = new Object();
window.document = document;

function Image()
{
    this.src = "";
    this.onload = function()
    {
    }
    this.onerror = function()
    {
    }
}

function _image_data()
{
    this.data = null;
    this.length = 0;
}

function native_context2d(parent)
{
    this.canvas = parent;

    this.globalAlpha = 0;
    this.globalCompositeOperation = "";
    this.fillStyle = "";
    this.strokeStyle = "";

    this.lineWidth = 0;
    this.lineCap = 0;
    this.lineJoin = 0;
    this.miterLimit = 0;
    this.shadowOffsetX = 0;
    this.shadowOffsetY = 0;
    this.shadowBlur = 0;
    this.shadowColor = 0;
    this.font = "";
    this.textAlign = 0;
    this.textBaseline = 0;
}
native_context2d.prototype =
{
    save : function() {},
    restore : function() {},

    scale : function(x,y) {},
    rotate : function(angle) {},
    translate : function(x,y) {},
    transform : function(m11,m12,m21,m22,dx,dy) {},
    setTransform : function(m11,m12,m21,m22,dx,dy) {},

    createLinearGradient : function(x0,y0,x1,y1) { return null; },
    createRadialGradient : function(x0,y0,r0,x1,y1,r1) { return null; },
    createPattern : function(image,repetition) { return null; },

    clearRect : function(x,y,w,h) {},
    fillRect : function(x,y,w,h) {},
    strokeRect : function(x,y,w,h) {},

    beginPath : function() {},
    closePath : function() {},
    moveTo : function(x,y) {},
    lineTo : function(x,y) {},
    quadraticCurveTo : function(cpx,cpy,x,y) {},
    bezierCurveTo : function(cp1x,cp1y,cp2x,cp2y,x,y) {},
    arcTo : function(x1,y1,x2,y2,radius) {},
    rect : function(x,y,w,h) {},
    arc : function(x,y,radius,startAngle,endAngle,anticlockwise) {},

    fill : function() {},
    stroke : function() {},
    clip : function() {},
    isPointInPath : function(x,y) {},
    drawFocusRing : function(element,xCaret,yCaret,canDrawCustom) {},

    fillText : function(text,x,y,maxWidth) {},
    strokeText : function(text,x,y,maxWidth) {},
    measureText : function(text) {},

    drawImage : function(img_elem,dx_or_sx,dy_or_sy,dw_or_sw,dh_or_sh,dx,dy,dw,dh) {},

    createImageData : function(imagedata_or_sw,sh)
    {
        var _data = new _image_data();
        _data.length = imagedata_or_sw * sh * 4;
        _data.data = new Uint8Array(imagedata_or_sw * sh * 4);
        return _data;
    },
    getImageData : function(sx,sy,sw,sh) {},
    putImageData : function(image_data,dx,dy,dirtyX,dirtyY,dirtyWidth,dirtyHeight) {}
};

function native_canvas()
{
    this.id = "";
    this.width = 300;
    this.height = 150;

    this.nodeType = 1;
}
native_canvas.prototype =
{
    getContext : function(type)
    {
        if (type == "2d")
            return new native_context2d(this);
        return null;
    },

    toDataUrl : function(type)
    {
        return "";
    },

    addEventListener : function()
    {
    },

    attr : function()
    {
    }
};

window["Asc"] = new Object();

var _null_object = new Object();
_null_object.length = 0;
_null_object.nodeType = 1;
_null_object.offsetWidth = 1;
_null_object.offsetHeight = 1;
_null_object.clientWidth = 1;
_null_object.clientHeight = 1;
_null_object.scrollWidth = 1;
_null_object.scrollHeight = 1;
_null_object.style = new Object();
_null_object.documentElement = _null_object;
_null_object.body = _null_object;
_null_object.ownerDocument = _null_object;
_null_object.defaultView = _null_object;

_null_object.addEventListener = function(){};
_null_object.setAttribute = function(){};
_null_object.getElementsByTagName = function() { return []; };
_null_object.appendChild = function() {};
_null_object.removeChild = function() {};
_null_object.insertBefore = function() {};
_null_object.childNodes = [];
_null_object.parent = _null_object;
_null_object.parentNode = _null_object;
_null_object.find = function() { return this; };
_null_object.appendTo = function() { return this; };
_null_object.css = function() { return this; };
_null_object.width = function() { return 0; };
_null_object.height = function() { return 0; };
_null_object.attr = function() { return this; };
_null_object.prop = function() { return this; };
_null_object.val = function() { return this; };
_null_object.remove = function() {};
_null_object.getComputedStyle = function() { return null; };
_null_object.getContext = function(type) {
    if (type == "2d")
        return new native_context2d(this);
    return null;
};

window._null_object = _null_object;

document.createElement = function(type)
{
    if (type && type.toLowerCase)
    {
        if (type.toLowerCase() == "canvas")
            return new native_canvas();
    }

    return _null_object;
}

function _return_empty_html_element() { return _null_object; };

document.createDocumentFragment = _return_empty_html_element;
document.getElementsByTagName = function(tag) {
    var ret = [];
    if ("head" == tag)
        ret.push(_null_object);
    return ret;
};
document.insertBefore = function() {};
document.appendChild = function() {};
document.removeChild = function() {};
document.getElementById = function() { return _null_object; };
document.createComment = function() { return undefined; };

document.documentElement = _null_object;
document.body = _null_object;

var native = CreateNativeEngine();
window.native = native;
window["native"] = native;

function GetNativeEngine()
{
	return window.native;
}

var native_renderer = null;
var _api = null;
var Asc = null;

function NativeOpenFile()
{
    var doc_bin = window.native.GetFileString(g_file_path);
    if (NATIVE_DOCUMENT_TYPE == "presentation" || NATIVE_DOCUMENT_TYPE == "document")
    {
        _api = new window["asc_docs_api"]("");
        _api.asc_nativeOpenFile(doc_bin);
    }
    else
    {
        Asc = window["Asc"];
        _api = new window["Asc"]["spreadsheet_api"];
        var doc_bin = window.native.GetFileString(g_file_path);
        _api.asc_nativeOpenFile(doc_bin);
    }
}

function NativeOpenFile2()
{
	g_oTextMeasurer = new CTextMeasurerWrapper();
	g_oTextMeasurer.Init();

	window.g_file_path = "native_open_file";
	window.NATIVE_DOCUMENT_TYPE = window.native.GetEditorType();
    var doc_bin = window.native.GetFileString(window.g_file_path);
    if (window.NATIVE_DOCUMENT_TYPE == "presentation" || window.NATIVE_DOCUMENT_TYPE == "document")
    {
        _api = new window["asc_docs_api"]("");
        _api.asc_nativeOpenFile(doc_bin);
    }
    else
    {
        Asc = window["Asc"];
        _api = new window["Asc"]["spreadsheet_api"];
        var doc_bin = window.native.GetFileString(window.g_file_path);
        _api.asc_nativeOpenFile(doc_bin);
    }
}

function NativeCalculateFile()
{
	_api.asc_nativeCalculateFile();
}

function NativeApplyChanges()
{
    if (NATIVE_DOCUMENT_TYPE == "presentation" || NATIVE_DOCUMENT_TYPE == "document")
    {
        var __changes = [];
        var _count_main = window.native.GetCountChanges();
        for (var i = 0; i < _count_main; i++)
        {
            var _changes_file = window.native.GetChangesFile(i);
            var _changes = JSON.parse(window.native.GetFileString(_changes_file));

            for (var j = 0; j < _changes.length; j++)
            {
                __changes.push(_changes[j]);
            }
        }
        _api.asc_nativeApplyChanges(__changes);
    }
    else
    {
        var __changes = [];
        var _count_main = window.native.GetCountChanges();
        for (var i = 0; i < _count_main; i++)
        {
            var _changes_file = window.native.GetChangesFile(i);
            var _changes = JSON.parse(window.native.GetFileString(_changes_file));

            for (var j = 0; j < _changes.length; j++)
            {
                __changes.push(_changes[j]);
            }
        }

        _api.asc_nativeApplyChanges(__changes);
    }
}
function NativeGetFileString()
{
    return _api.asc_nativeGetFile();
}

function GetNativeCountPages()
{
    return _api.asc_nativePrintPagesCount();
}

window.memory1 = null;
window.memory2 = null;

function GetNativePageBase64(pageIndex)
{
    if (null == window.memory1)
        window.memory1 = CreateNativeMemoryStream();
    else
        window.memory1.ClearNoAttack();

    if (null == window.memory2)
        window.memory2 = CreateNativeMemoryStream();
    else
        window.memory2.ClearNoAttack();

    if (native_renderer == null)
    {
        native_renderer = _api.asc_nativeCheckPdfRenderer(window.memory1, window.memory2);
    }
    else
    {
        window.memory1.ClearNoAttack();
        window.memory2.ClearNoAttack();
    }

    _api.asc_nativePrint(native_renderer, pageIndex);
    return window.memory1;
}

function GetNativePageMeta(pageIndex)
{
	_api.WordControl.m_oDrawingDocument.LogicDocument = _api.WordControl.m_oDrawingDocument.m_oLogicDocument;
    _api.WordControl.m_oDrawingDocument.RenderPage(pageIndex);
}

function GetNativeId()
{
    return window.native.GetFileId();
}

function clearTimeout() {};
function setTimeout() {};
function clearInterval() {};
function setInterval() {};

window.clearTimeout = clearTimeout;
window.setTimeout = setTimeout;
window.clearInterval = clearInterval;
window.setInterval = setInterval;

window["OfficeExcel"] = {type:'common'};
var OfficeExcel = window["OfficeExcel"];

var console = {
	log : function(param) { window.native.ConsoleLog(param); }
};

// HTML page interface
window.native.Call_OnUpdateOverlay = function(param)
{
    _api.WordControl.m_oDrawingDocument.OnUpdateOverlay();
};

window.native.Call_OnMouseDown = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnMouseDown(e);
};
window.native.Call_OnMouseUp = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnMouseUp(e);
};
window.native.Call_OnMouseMove = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnMouseMove(e);
};

window.native.Call_OnKeyDown = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnKeyDown(e);
};
window.native.Call_OnKeyPress = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnKeyPress(e);
};
window.native.Call_OnKeyUp = function(e)
{
    _api.WordControl.m_oDrawingDocument.OnKeyUp(e);
};
<|endoftext|>


<commit_before>const { COMMANDS } = require("./constants");
const Variable = require("./variable");
const MAGIC = "FG";
const ENCODING = "binary";

const PRIMITIVE_TYPES = {
  5: "BOOLEAN",
  2: "INT",
  3: "FLOAT",
  4: "DOUBLE",
  6: "STRING"
};

// Holds a buffer and a pointer. Consumers can consume bytesoff the end of the
// file. When we want to run in the browser, we can refactor this class to use a
// typed array: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
class MakiFile {
  constructor(buffer) {
    this._buffer = buffer;
    this._i = 0;
  }

  readUInt32LE() {
    const int = this._buffer.readUInt32LE(this._i);
    this._i += 4;
    return int;
  }

  readUInt16LE() {
    const int = this._buffer.readUInt16LE(this._i);
    this._i += 2;
    return int;
  }

  readUInt8() {
    const int = this._buffer.readUInt8(this._i);
    this._i++;
    return int;
  }

  readStringOfLength(length) {
    const str = this._buffer.toString(ENCODING, this._i, this._i + length);
    this._i += length;
    return str;
  }

  readString() {
    return this.readStringOfLength(this.readUInt16LE());
  }

  getNextNBytesDEPRECATED(length) {
    const bytes = this._buffer.slice(this._i, this._i + length);
    this._i += length;
    return bytes;
  }
}

function readMagic(makiFile) {
  const magic = makiFile.readStringOfLength(MAGIC.length);
  if (magic !== MAGIC) {
    throw new Error("Magic number does not mach. Is this a maki file?");
  }
  return magic;
}

function readVersion(makiFile) {
  // No idea what we're actually expecting here.
  return makiFile.readUInt16LE();
}

function readClasses(makiFile) {
  let count = makiFile.readUInt32LE();
  const classes = [];
  while (count--) {
    let identifier = "";
    let chunks = 4;
    while (chunks--) {
      identifier += makiFile
        .readUInt32LE()
        .toString(16)
        .padStart(8, "0");
    }
    classes.push(identifier);
  }
  return classes;
}

function readMethods(makiFile) {
  let count = makiFile.readUInt32LE();
  const methods = [];
  while (count--) {
    const classCode = makiFile.readUInt16LE();
    // Offset into our parsed types
    const typeOffset = classCode & 0xff;
    // This is probably the second half of a uint32
    const dummy2 = makiFile.readUInt16LE();
    const name = makiFile.readString();
    methods.push({ dummy2, name, typeOffset });
  }
  return methods;
}

function readVariables({ makiFile, classes }) {
  let count = makiFile.readUInt32LE();
  const variables = [];
  while (count--) {
    const typeOffset = makiFile.readUInt8();
    const object = makiFile.readUInt8();
    const subClass = makiFile.readUInt16LE();
    const uinit1 = makiFile.readUInt16LE();
    const uinit2 = makiFile.readUInt16LE();
    const uinit3 = makiFile.readUInt16LE();
    const uinit4 = makiFile.readUInt16LE();
    const global = makiFile.readUInt8();
    const system = makiFile.readUInt8();
    const props = {
      typeOffset,
      object,
      subClass,
      uinit1,
      uinit2,
      uinit3,
      uinit4,
      global,
      system
    };

    if (object) {
      const klass = classes[typeOffset];
      if (klass == null) {
        throw new Error("Invalid type");
      }
      variables.push(
        new Variable({ ...props, type: klass, typeName: "OBJECT" })
      );
    } else if (subClass) {
      const variable = variables[typeOffset];
      if (variable == null) {
        throw new Error("Invalid type");
      }
      variables.push(
        new Variable({ ...props, type: variable, typeName: "SUBCLASS" })
      );
    } else {
      const typeName = PRIMITIVE_TYPES[typeOffset];
      if (typeName == null) {
        throw new Error("Invalid type");
      }
      let value = null;

      switch (typeName) {
        // BOOLEAN
        case PRIMITIVE_TYPES[5]:
        // INT
        case PRIMITIVE_TYPES[2]:
          value = uinit1;
          break;
        case PRIMITIVE_TYPES[3]:
        case PRIMITIVE_TYPES[4]:
          const exponent = (uinit2 & 0xff80) >> 7;
          const mantisse = ((0x80 | (uinit2 & 0x7f)) << 16) | uinit1;
          value = mantisse * 2.0 ** (exponent - 0x96);
          break;
        case PRIMITIVE_TYPES[6]:
          // This will likely get set by constants later on.
          break;
        default:
          throw new Error("Invalid primitive type");
      }
      const variable = new Variable({ ...props, type: typeName, typeName });
      variable.setValue(value);
      variables.push(variable);
    }
  }
  return variables;
}

function readConstants({ makiFile, variables }) {
  let count = makiFile.readUInt32LE();
  while (count--) {
    const i = makiFile.readUInt32LE();
    const variable = variables[i];
    const value = makiFile.readString();
    // TODO: Don't mutate
    variable.setValue(value);
  }
}

function readBindings(makiFile) {
  let count = makiFile.readUInt32LE();
  const bindings = [];
  while (count--) {
    const variableOffset = makiFile.readUInt32LE();
    const methodOffset = makiFile.readUInt32LE();
    const binaryOffset = makiFile.readUInt32LE();
    bindings.push({ variableOffset, binaryOffset, methodOffset });
  }
  return bindings;
}

function decodeCode({ makiFile, classes, variables, methods, bindings }) {
  const length = makiFile.readUInt32LE();
  const commandsBuffer = makiFile.getNextNBytesDEPRECATED(length);

  let pos = 0;
  const localFunctions = {};
  const results = [];
  while (pos < commandsBuffer.length) {
    const command = parseComand({
      commandsBuffer,
      pos,
      classes,
      variables,
      methods,
      localFunctions
    });
    pos += command._size;
    results.push(command);
  }
  // TODO: Don't mutate
  Object.values(localFunctions).forEach(localFunction => {
    bindings.push(localFunction);
  });

  bindings.sort((a, b) => {
    return a.binaryOffset - b.binaryOffset;
  });

  return results;
}

// TODO: Refactor this to consume bytes directly off the end of MakiFile
function parseComand({ commandsBuffer, pos, localFunctions }) {
  const command = {};
  const opcode = commandsBuffer.readInt8(pos);
  command.offset = pos;
  command.pos = pos;
  command.opcode = opcode;
  command.arguments = [];
  command.command = COMMANDS[opcode];
  command._size = 1;

  if (command.command == null) {
    throw new Error(`Unknown opcode "${opcode}"`);
  }

  if (command.command.arg == null) {
    command._size = 1;
    return command;
  }

  const argType = command.command.arg;
  let arg = null;
  switch (argType) {
    case "var": {
      arg = commandsBuffer.readUInt32LE(pos + 1);
      break;
    }
    case "line": {
      arg = commandsBuffer.readUInt32LE(pos + 1) + 5;
      break;
    }
    case "objFunc": {
      // TODO: ClassesOffset
      arg = commandsBuffer.readUInt32LE(pos + 1);
      break;
    }
    case "func": {
      // Note in the perl code here: "todo, something strange going on here..."
      const variable = commandsBuffer.readUInt32LE(pos + 1) + 5;
      const offset = variable + pos;
      arg = {
        name: `func${offset}`,
        code: [],
        offset
      };
      if (localFunctions[offset] == null) {
        localFunctions[offset] = {
          function: arg,
          offset
        };
      }
      break;
    }
    case "obj": {
      // Classes Offset
      arg = commandsBuffer.readUInt32LE(pos + 1);
      break;
    }
  }

  command.arguments = [arg];
  command._size = 5;

  // From perl: look forward for a stack protection block
  // (why do I have to look FORWARD. stupid nullsoft)
  if (
    commandsBuffer.length > pos + 5 + 4 &&
    commandsBuffer.readUInt32LE(pos + 5) >= 0xffff0000
  ) {
    command._size += 4;
  }

  if (opcode === 112) {
    command._size += 1;
  }
  return command;
}

function parse(buffer) {
  const makiFile = new MakiFile(buffer);

  const magic = readMagic(makiFile);
  readVersion(makiFile);
  makiFile.readUInt32LE(); // Not sure what we are skipping over here. Just some UInt 32.
  const classes = readClasses(makiFile);
  const methods = readMethods(makiFile);
  const variables = readVariables({ makiFile: makiFile, classes });
  readConstants({ makiFile: makiFile, variables });
  const bindings = readBindings(makiFile);
  const commands = decodeCode({
    makiFile: makiFile,
    classes,
    variables,
    methods,
    bindings
  });

  // Map binary offsets to command indexes.
  // Some bindings/functions ask us to jump to a place in the binary data and
  // start executing. However, we want to do all the parsing up front, and just
  // return a list of commands. This map allows anything that mentions a binary
  // offset to find the command they should jump to.
  const offsetToCommand = {};
  commands.forEach((command, i) => {
    if (command.offset != null) {
      offsetToCommand[command.offset] = i;
    }
  });

  const resolvedBindings = bindings.map(binding => {
    const { binaryOffset, ...rest } = binding;
    return {
      commandOffset: offsetToCommand[binaryOffset],
      ...rest
    };
  });
  return {
    magic,
    classes,
    methods,
    variables,
    bindings: resolvedBindings,
    commands
  };
}

module.exports = parse;
<commit_msg>Don't use Buffer outside of MakiFile<commit_after>const { COMMANDS } = require("./constants");
const Variable = require("./variable");
const MAGIC = "FG";
const ENCODING = "binary";

const PRIMITIVE_TYPES = {
  5: "BOOLEAN",
  2: "INT",
  3: "FLOAT",
  4: "DOUBLE",
  6: "STRING"
};

// Holds a buffer and a pointer. Consumers can consume bytesoff the end of the
// file. When we want to run in the browser, we can refactor this class to use a
// typed array: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
class MakiFile {
  constructor(buffer) {
    this._buffer = buffer;
    this._i = 0;
  }

  readUInt32LE() {
    const int = this._buffer.readUInt32LE(this._i);
    this._i += 4;
    return int;
  }

  peakUInt32LE() {
    return this._buffer.readUInt32LE(this._i);
  }

  readUInt16LE() {
    const int = this._buffer.readUInt16LE(this._i);
    this._i += 2;
    return int;
  }

  readUInt8() {
    const int = this._buffer.readUInt8(this._i);
    this._i++;
    return int;
  }

  readStringOfLength(length) {
    const str = this._buffer.toString(ENCODING, this._i, this._i + length);
    this._i += length;
    return str;
  }

  readString() {
    return this.readStringOfLength(this.readUInt16LE());
  }

  getPosition() {
    return this._i;
  }
}

function readMagic(makiFile) {
  const magic = makiFile.readStringOfLength(MAGIC.length);
  if (magic !== MAGIC) {
    throw new Error("Magic number does not mach. Is this a maki file?");
  }
  return magic;
}

function readVersion(makiFile) {
  // No idea what we're actually expecting here.
  return makiFile.readUInt16LE();
}

function readClasses(makiFile) {
  let count = makiFile.readUInt32LE();
  const classes = [];
  while (count--) {
    let identifier = "";
    let chunks = 4;
    while (chunks--) {
      identifier += makiFile
        .readUInt32LE()
        .toString(16)
        .padStart(8, "0");
    }
    classes.push(identifier);
  }
  return classes;
}

function readMethods(makiFile) {
  let count = makiFile.readUInt32LE();
  const methods = [];
  while (count--) {
    const classCode = makiFile.readUInt16LE();
    // Offset into our parsed types
    const typeOffset = classCode & 0xff;
    // This is probably the second half of a uint32
    makiFile.readUInt16LE();
    const name = makiFile.readString();
    methods.push({ name, typeOffset });
  }
  return methods;
}

function readVariables({ makiFile, classes }) {
  let count = makiFile.readUInt32LE();
  const variables = [];
  while (count--) {
    const typeOffset = makiFile.readUInt8();
    const object = makiFile.readUInt8();
    const subClass = makiFile.readUInt16LE();
    const uinit1 = makiFile.readUInt16LE();
    const uinit2 = makiFile.readUInt16LE();
    const uinit3 = makiFile.readUInt16LE();
    const uinit4 = makiFile.readUInt16LE();
    const global = makiFile.readUInt8();
    const system = makiFile.readUInt8();
    const props = {
      typeOffset,
      object,
      subClass,
      uinit1,
      uinit2,
      uinit3,
      uinit4,
      global,
      system
    };

    if (object) {
      const klass = classes[typeOffset];
      if (klass == null) {
        throw new Error("Invalid type");
      }
      variables.push(
        new Variable({ ...props, type: klass, typeName: "OBJECT" })
      );
    } else if (subClass) {
      const variable = variables[typeOffset];
      if (variable == null) {
        throw new Error("Invalid type");
      }
      variables.push(
        new Variable({ ...props, type: variable, typeName: "SUBCLASS" })
      );
    } else {
      const typeName = PRIMITIVE_TYPES[typeOffset];
      if (typeName == null) {
        throw new Error("Invalid type");
      }
      let value = null;

      switch (typeName) {
        // BOOLEAN
        case PRIMITIVE_TYPES[5]:
          value = uinit1;
          break;
        // INT
        case PRIMITIVE_TYPES[2]:
          value = uinit1;
          break;
        case PRIMITIVE_TYPES[3]:
        case PRIMITIVE_TYPES[4]:
          const exponent = (uinit2 & 0xff80) >> 7;
          const mantisse = ((0x80 | (uinit2 & 0x7f)) << 16) | uinit1;
          value = mantisse * 2.0 ** (exponent - 0x96);
          break;
        case PRIMITIVE_TYPES[6]:
          // This will likely get set by constants later on.
          break;
        default:
          throw new Error("Invalid primitive type");
      }
      const variable = new Variable({ ...props, type: typeName, typeName });
      variable.setValue(value);
      variables.push(variable);
    }
  }
  return variables;
}

function readConstants({ makiFile, variables }) {
  let count = makiFile.readUInt32LE();
  while (count--) {
    const i = makiFile.readUInt32LE();
    const variable = variables[i];
    const value = makiFile.readString();
    // TODO: Don't mutate
    variable.setValue(value);
  }
}

function readBindings(makiFile) {
  let count = makiFile.readUInt32LE();
  const bindings = [];
  while (count--) {
    const variableOffset = makiFile.readUInt32LE();
    const methodOffset = makiFile.readUInt32LE();
    const binaryOffset = makiFile.readUInt32LE();
    bindings.push({ variableOffset, binaryOffset, methodOffset });
  }
  return bindings;
}

function decodeCode({ makiFile, classes, variables, methods, bindings }) {
  const length = makiFile.readUInt32LE();
  const start = makiFile.getPosition();

  function getPos() {
    return makiFile.getPosition() - start;
  }
  const localFunctions = {};
  const results = [];
  while (makiFile.getPosition() < start + length) {
    const command = parseComand({
      makiFile,
      length,
      pos: getPos(),
      classes,
      variables,
      methods,
      localFunctions
    });
    results.push(command);
  }
  // TODO: Don't mutate
  Object.values(localFunctions).forEach(localFunction => {
    bindings.push(localFunction);
  });

  bindings.sort((a, b) => {
    return a.binaryOffset - b.binaryOffset;
  });

  return results;
}

// TODO: Refactor this to consume bytes directly off the end of MakiFile
function parseComand({ makiFile, length, pos, localFunctions }) {
  const command = {};
  const opcode = makiFile.readUInt8();
  command.offset = pos;
  command.pos = pos;
  command.opcode = opcode;
  command.arguments = [];
  command.command = COMMANDS[opcode];

  if (command.command == null) {
    throw new Error(`Unknown opcode "${opcode}"`);
  }

  if (command.command.arg == null) {
    return command;
  }

  const argValue = makiFile.readUInt32LE();
  const argType = command.command.arg;
  let arg = null;
  switch (argType) {
    case "var": {
      arg = argValue;
      break;
    }
    case "line": {
      arg = argValue + 5;
      break;
    }
    case "objFunc": {
      // TODO: ClassesOffset
      arg = argValue;
      break;
    }
    case "func": {
      // Note in the perl code here: "todo, something strange going on here..."
      const variable = argValue + 5;
      const offset = variable + pos;
      arg = {
        name: `func${offset}`,
        code: [],
        offset
      };
      if (localFunctions[offset] == null) {
        localFunctions[offset] = {
          function: arg,
          offset
        };
      }
      break;
    }
    case "obj": {
      // Classes Offset
      arg = argValue;
      break;
    }
  }

  command.arguments = [arg];

  // From perl: look forward for a stack protection block
  // (why do I have to look FORWARD. stupid nullsoft)
  if (
    // Is there another UInt32 to read?
    length > pos + 5 + 4 &&
    makiFile.peakUInt32LE() >= 0xffff0000
  ) {
    makiFile.readUInt32LE();
  }

  if (opcode === 112) {
    makiFile.readUInt8();
  }
  return command;
}

function parse(buffer) {
  const makiFile = new MakiFile(buffer);

  const magic = readMagic(makiFile);
  readVersion(makiFile);
  makiFile.readUInt32LE(); // Not sure what we are skipping over here. Just some UInt 32.
  const classes = readClasses(makiFile);
  const methods = readMethods(makiFile);
  const variables = readVariables({ makiFile: makiFile, classes });
  readConstants({ makiFile: makiFile, variables });
  const bindings = readBindings(makiFile);
  const commands = decodeCode({
    makiFile,
    classes,
    variables,
    methods,
    bindings
  });

  // Map binary offsets to command indexes.
  // Some bindings/functions ask us to jump to a place in the binary data and
  // start executing. However, we want to do all the parsing up front, and just
  // return a list of commands. This map allows anything that mentions a binary
  // offset to find the command they should jump to.
  const offsetToCommand = {};
  commands.forEach((command, i) => {
    if (command.offset != null) {
      offsetToCommand[command.offset] = i;
    }
  });

  const resolvedBindings = bindings.map(binding => {
    const { binaryOffset, ...rest } = binding;
    return {
      commandOffset: offsetToCommand[binaryOffset],
      ...rest
    };
  });
  return {
    magic,
    classes,
    methods,
    variables,
    bindings: resolvedBindings,
    commands
  };
}

module.exports = parse;
<|endoftext|>


<commit_before>'use strict';

/* Services */
// Demonstrate how to register services
// In this case it is a simple value service.
var myService = angular.module('monadexApp.services', []);

myService.value('version', '0.0.1');

myService.service("canvasService", ['$rootScope',
    function($rootScope) {
        var canvas;

        // Variables related to the demarcation lines for the
        // Drawing area.
        var DrawAreaWidth   = 200,
            DrawAreaHeight  = 400,
            UpperLeftPoint  = [0, 0],
            UpperRightPoint = [DrawAreaWidth, 0],
            DownLeftPoint   = [0, DrawAreaHeight],
            DownRightPoint  = [DrawAreaWidth, DrawAreaHeight],
            LineWidthOffset = 1,
            lineProps = {
                "stroke":"#000000",
                "strokeWidth":1,
                hasBorders:false,
                hasControls:false,
                hasRotatingPoint:false,
                selectable:false
            },
            lineL, lineR, lineU, lineD;

        // Position for the newly added text
        var newTextTop = 50;

        var offsetXFun = function(Point, OffsetVal) {
            return [Point[0]-OffsetVal, Point[1]];
        };

        var offsetYFun = function(Point, OffsetVal) {
            return [Point[0], Point[1]-OffsetVal];
        };

        var fabricLineFun = function(Point1, Point2) {
            return new fabric.Line(Point1.concat(Point2), lineProps);
        };

        lineL = fabricLineFun(
            offsetXFun(UpperLeftPoint, 1),
            offsetXFun(DownLeftPoint, 1)
        );
        lineR = fabricLineFun(
            offsetXFun(UpperRightPoint, 1),
            offsetXFun(DownRightPoint, 1)
        );
        lineU = fabricLineFun(
            offsetYFun(UpperRightPoint, 1),
            offsetYFun(UpperLeftPoint, 1)
        );
        lineD = fabricLineFun(
            offsetYFun(DownLeftPoint, 1),
            offsetYFun(DownRightPoint, 1)
        );

        // return the canvas instance
        this.getCanvas = function() {
            return canvas;
        };

        this.init = function(canvasid, imageId) {
            console.log("initialize...");

            this.imageId = imageId;
            this.prevCanvas = null;

            fabric.Object.prototype._drawControl = function(control, ctx, methodName, left, top) {
              var degreesToRadians = fabric.util.degreesToRadians,
              isVML = typeof G_vmlCanvasManager !== 'undefined';
              var sizeX = this.cornerSize / this.scaleX,
              sizeY = this.cornerSize / this.scaleY,
              img   = new Image();

              if (this.isControlVisible(control)) {
                isVML || this.transparentCorners || ctx.clearRect(left, top, sizeX, sizeY);
                switch(control) {
                  case 'br':
                    img.src = 'img/tool/scale.png';
                  ctx.drawImage(img, left, top, sizeX, sizeY);
                  break;
                  case 'bl':
                    img.src = 'img/tool/delete.png';
                  ctx.drawImage(img, left, top, sizeX, sizeY);
                  break;
                  case 'tl':
                    img.src = 'img/tool/drag.png';
                  ctx.drawImage(img, left, top, sizeX, sizeY);
                  break;
                  case 'tr':
                    img.src = 'img/tool/rotate.png';
                  ctx.drawImage(img, left, top, sizeX, sizeY);
                  break;
                  default:
                    ctx[methodName](left, top, sizeX, sizeY);
                }
              }
            };

            canvas = new fabric.Canvas(canvasid, {
                hoverCursor: 'pointer',
                selection: true,
                selectionBorderColor:'blue'
            });

            canvas.selection = false;

            canvas.cursorMap = [
                'n-resize',
                '-webkit-grab',
                'e-resize',
                '-webkit-grab',
                's-resize',
                'pointer',
                'w-resize',
                '-webkit-grab'
            ];

            canvas._getActionFromCorner = function(target, corner) {
                var action = 'drag';
                if (corner) {
                    action = (corner === 'ml' || corner === 'mr')
                        ? 'scaleX'
                        : (corner === 'mt' || corner === 'mb')
                            ? 'scaleY'
                            : (corner === 'tr' || corner === 'mtr')
                                ? 'rotate'
                                : corner === 'bl'
                                    ? 'delete'
                                    : corner === 'tl'
                                        ? 'drag'
                                        : 'scale';
                }
                return action;
            };

            canvas._setupCurrentTransform = function(e, target) {
                var degreesToRadians = fabric.util.degreesToRadians;

                if (!target) return;

                var pointer = this.getPointer(e),
                corner = target._findTargetCorner(pointer),
                action = this._getActionFromCorner(target, corner),
                origin = this._getOriginFromCorner(target, corner);

                this._currentTransform = {
                    target: target,
                    action: action,
                    scaleX: target.scaleX,
                    scaleY: target.scaleY,
                    offsetX: pointer.x - target.left,
                    offsetY: pointer.y - target.top,
                    originX: origin.x,
                    originY: origin.y,
                    ex: pointer.x,
                    ey: pointer.y,
                    left: target.left,
                    top: target.top,
                    theta: degreesToRadians(target.angle),
                    width: target.width * target.scaleX,
                    mouseXSign: 1,
                    mouseYSign: 1
                };

                this._currentTransform.original = {
                    left: target.left,
                    top: target.top,
                    scaleX: target.scaleX,
                    scaleY: target.scaleY,
                    originX: origin.x,
                    originY: origin.y
                };

                this._resetCurrentTransform(e);
                if(action === 'delete') {
                    this.remove(target);
                }
            };

            canvas._getRotatedCornerCursor = function(corner, target) {
              var cursorOffset = {
                mt: 0, // n
                tr: 1, // ne
                mr: 2, // e
                br: 3, // se
                mb: 4, // s
                bl: 5, // sw
                ml: 6, // w
                tl: 7 // nw
              };
              var n = cursorOffset[corner];
              return this.cursorMap[n];
            };

            canvas.on({
                'object:moving': function(e) {
                    e.target.opacity = 0.5;
                },
                'object:modified': function(e) {
                    e.target.opacity = 1;
                },
                'object:selected': function(e) {
                    var selectedObject = e.target;
                    selectedObject.hasRotatingPoint = true;
                    if (selectedObject &&
                        selectedObject.type === 'text') {
                        $rootScope.$broadcast(
                            'mdeTextObjectSelected',
                            {
                                text: selectedObject.getText(),
                                fontColor: selectedObject.fill,
                                fontFamily: selectedObject.fontFamily
                            }
                        );
                    } else if (selectedObject &&
                               selectedObject.type === 'image'){
                        $rootScope.$broadcast('mdeImageObjectSelected');
                    };
                },
                'selection:cleared': function(e) {
                    $rootScope.$broadcast('mdeObjectCleared');
                }
            });
        };

        var addText = function(text, fontColor, fontFamily) {
            var textSample = new fabric.Text(
                text,
                {
                    //left: fabric.util.getRandomInt(0, 200),
                    //top: fabric.util.getRandomInt(0, 400),
                    left: 80,
                    top: 50,
                    fontFamily: fontFamily,
                    angle: 0,
                    fill: fontColor,
                    scaleX: 0.5,
                    scaleY: 0.5,
                    textAlign: "center",
                    fontWeight: '',
                    hasRotatingPoint: true
                }
            );

            textSample.setControlVisible('mtr', false);
            textSample.lockUniScaling = true;
            textSample.centeredScaling = true;

            canvas.add(textSample);
            canvas.item(canvas.item.length-1).hasRotatingPoint = true;
        };

        this.addTextWhenNoActiveText =
            function(text, fontColor, fontFamily) {
                var activeObject = canvas.getActiveObject();
                if (!activeObject || activeObject.type != 'text') {
                    addText(text, fontColor, fontFamily);
                }
            };

        this.activeTextP = function() {
            var activeObject = canvas.getActiveObject();
            return activeObject && activeObject.type == 'text';
        };

        this.setTheLastObjActive = function() {
            canvas.setActiveObject(canvas.item(canvas.getObjects().length - 1));
        };

        this.addImage = function(ImgSrc) {
            /*temp code*/
            var offset = 50;
            var left = fabric.util.getRandomInt(0 + offset, 200 - offset);
            var top = fabric.util.getRandomInt(0 + offset, 400 - offset);
            var angle = fabric.util.getRandomInt(-20, 40);
            var width = fabric.util.getRandomInt(30, 50);
            var opacity = (function(min, max){
                return Math.random() * (max - min) + min;
            })(0.5, 1);

            fabric.Image.fromURL(ImgSrc, function(image) {
                image.set(
                    {
                        left: left,
                        top: top,
                        angle: 0,
                        padding: 10,
                        cornersize: 10,
                        hasRotatingPoint:true
                    }
                );

                image.setControlVisible('mtr', false);
                image.centeredScaling = true;
                canvas.add(image);
            });
        };

        this.removeSelected = function() {
            var activeObject = canvas.getActiveObject(),
                activeGroup = canvas.getActiveGroup();
            if (activeObject) {
                canvas.remove(activeObject);
                $("#text-string").val("");
            }
            else if (activeGroup) {
                var objectsInGroup = activeGroup.getObjects();
                canvas.discardActiveGroup();
                objectsInGroup.forEach(function(object) {
                    canvas.remove(object);
                });
            }
        };

        this.flip = function(imageSrc) {
            var currentCanvas;

            $(this.imageId).attr("src", imageSrc);
            currentCanvas = JSON.stringify(canvas);
            canvas.clear();

            if(this.prevCanvas != null) {
                canvas.loadFromJSON(
                    this.prevCanvas,
                    canvas.renderAll.bind(canvas)
                );
            }

            this.prevCanvas = currentCanvas;
        };

        this.changeBackground = function(color) {
            $rootScope.$broadcast('mdeChangeBackground', color);
        };

        this.addCanvasBorder = function() {
           [lineL, lineR, lineU, lineD].map(function(elem) {
                canvas.add(elem);
            });
            canvas.renderAll();
        };

        this.removeCanvasBorder = function() {
           [lineL, lineR, lineU, lineD].map(function(elem) {
                canvas.remove(elem);
            });
            canvas.renderAll();
        };

        var applyToActiveTextFun = function(Fun) {
            var activeObject = canvas.getActiveObject();
            if (activeObject && activeObject.type === 'text') {
                Fun(activeObject);
                canvas.renderAll();
            }
        };

        this.renderActiveTextContent = function(text) {
            var activeObject = canvas.getActiveObject();
            if (activeObject && activeObject.type === 'text') {
                if(text === "") {
                    canvas.remove(activeObject);
                } else {
                    activeObject.text = text;
                    activeObject.left = (DrawAreaWidth - activeObject.width)/2;
                }
                canvas.renderAll();
            }
        };

        this.renderActiveTextFontColor = function(color) {
            applyToActiveTextFun(function(activeObject) {
                activeObject.fill = color;
            });
        };

        this.changeTextFontFamily = function(font) {
            applyToActiveTextFun(function(activeObject) {
                activeObject.fontFamily = font;
            });
        };

        this.toggleActiveTextBold = function() {
            applyToActiveTextFun(function(activeObject) {
                activeObject.fontWeight = (
                    activeObject.fontWeight == 'bold' ? '' : 'bold'
                );
            });
        };

        this.toggleActiveTextItalic = function() {
            applyToActiveTextFun(function(activeObject) {
                activeObject.fontStyle = (
                    activeObject.fontStyle == 'italic' ? '' : 'italic'
                );
            });
        };

        this.toggleActiveTextStrike = function() {
            applyToActiveTextFun(function(activeObject) {
                activeObject.textDecoration = (
                    activeObject.textDecoration ==
                        'line-through' ? '' : 'line-through'
                );
            });
        };

        this.toggleActiveTextUnderline = function() {
            applyToActiveTextFun(function(activeObject) {
                activeObject.textDecoration = (
                    activeObject.textDecoration ==
                        'underline' ? '' : 'underline'
                );
            });
        };

        this.setActiveTextAlignment = function(position) {
            applyToActiveTextFun(function(activeObject) {
                activeObject.textAlign = position;
            });
        };
    }
]);
<commit_msg>Collect fabric.js monkey patch into a function<commit_after>'use strict';

/* Services */
// Demonstrate how to register services
// In this case it is a simple value service.
var myService = angular.module('monadexApp.services', []);

myService.value('version', '0.0.1');

myService.service("canvasService", ['$rootScope',
    function($rootScope) {
        var canvas;

        // Variables related to the demarcation lines for the
        // Drawing area.
        var DrawAreaWidth   = 200,
            DrawAreaHeight  = 400,
            UpperLeftPoint  = [0, 0],
            UpperRightPoint = [DrawAreaWidth, 0],
            DownLeftPoint   = [0, DrawAreaHeight],
            DownRightPoint  = [DrawAreaWidth, DrawAreaHeight],
            LineWidthOffset = 1,
            lineProps = {
                "stroke":"#000000",
                "strokeWidth":1,
                hasBorders:false,
                hasControls:false,
                hasRotatingPoint:false,
                selectable:false
            },
            lineL, lineR, lineU, lineD;

        // Position for the newly added text
        var newTextTop = 50;

        var offsetXFun = function(Point, OffsetVal) {
            return [Point[0]-OffsetVal, Point[1]];
        };

        var offsetYFun = function(Point, OffsetVal) {
            return [Point[0], Point[1]-OffsetVal];
        };

        var fabricLineFun = function(Point1, Point2) {
            return new fabric.Line(Point1.concat(Point2), lineProps);
        };

        lineL = fabricLineFun(
            offsetXFun(UpperLeftPoint, 1),
            offsetXFun(DownLeftPoint, 1)
        );
        lineR = fabricLineFun(
            offsetXFun(UpperRightPoint, 1),
            offsetXFun(DownRightPoint, 1)
        );
        lineU = fabricLineFun(
            offsetYFun(UpperRightPoint, 1),
            offsetYFun(UpperLeftPoint, 1)
        );
        lineD = fabricLineFun(
            offsetYFun(DownLeftPoint, 1),
            offsetYFun(DownRightPoint, 1)
        );

        // Monkey patch on fabric.js
        (function(){
            fabric.Canvas.prototype.selection = false;

            fabric.Canvas.prototype.cursorMap = [
                'n-resize',
                '-webkit-grab',
                'e-resize',
                '-webkit-grab',
                's-resize',
                'pointer',
                'w-resize',
                '-webkit-grab'
            ];

            fabric.Canvas.prototype._getActionFromCorner = function(target, corner) {
                var action = 'drag';
                if (corner) {
                    action = (corner === 'ml' || corner === 'mr')
                        ? 'scaleX'
                        : (corner === 'mt' || corner === 'mb')
                            ? 'scaleY'
                            : (corner === 'tr' || corner === 'mtr')
                                ? 'rotate'
                                : corner === 'bl'
                                    ? 'delete'
                                    : corner === 'tl'
                                        ? 'drag'
                                        : 'scale';
                }
                return action;
            };

            fabric.Canvas.prototype._setupCurrentTransform = function(e, target) {
                var degreesToRadians = fabric.util.degreesToRadians;

                if (!target) return;

                var pointer = this.getPointer(e),
                corner = target._findTargetCorner(pointer),
                action = this._getActionFromCorner(target, corner),
                origin = this._getOriginFromCorner(target, corner);

                this._currentTransform = {
                    target: target,
                    action: action,
                    scaleX: target.scaleX,
                    scaleY: target.scaleY,
                    offsetX: pointer.x - target.left,
                    offsetY: pointer.y - target.top,
                    originX: origin.x,
                    originY: origin.y,
                    ex: pointer.x,
                    ey: pointer.y,
                    left: target.left,
                    top: target.top,
                    theta: degreesToRadians(target.angle),
                    width: target.width * target.scaleX,
                    mouseXSign: 1,
                    mouseYSign: 1
                };

                this._currentTransform.original = {
                    left: target.left,
                    top: target.top,
                    scaleX: target.scaleX,
                    scaleY: target.scaleY,
                    originX: origin.x,
                    originY: origin.y
                };

                this._resetCurrentTransform(e);
                if(action === 'delete') {
                    this.remove(target);
                }
            };

            fabric.Canvas.prototype._getRotatedCornerCursor = function(corner, target) {
                var cursorOffset = {
                    mt: 0, // n
                    tr: 1, // ne
                    mr: 2, // e
                    br: 3, // se
                    mb: 4, // s
                    bl: 5, // sw
                    ml: 6, // w
                    tl: 7 // nw
                };
                var n = cursorOffset[corner];
                return this.cursorMap[n];
            };

            fabric.Object.prototype._drawControl = function(control, ctx, methodName, left, top) {
                var degreesToRadians = fabric.util.degreesToRadians,
                isVML = typeof G_vmlCanvasManager !== 'undefined';
                var sizeX = this.cornerSize / this.scaleX,
                sizeY = this.cornerSize / this.scaleY,
                img   = new Image();

                if (this.isControlVisible(control)) {
                    isVML || this.transparentCorners || ctx.clearRect(left, top, sizeX, sizeY);
                    switch(control) {
                        case 'br':
                            img.src = 'img/tool/scale.png';
                        ctx.drawImage(img, left, top, sizeX, sizeY);
                        break;
                        case 'bl':
                            img.src = 'img/tool/delete.png';
                        ctx.drawImage(img, left, top, sizeX, sizeY);
                        break;
                        case 'tl':
                            img.src = 'img/tool/drag.png';
                        ctx.drawImage(img, left, top, sizeX, sizeY);
                        break;
                        case 'tr':
                            img.src = 'img/tool/rotate.png';
                        ctx.drawImage(img, left, top, sizeX, sizeY);
                        break;
                        default:
                            ctx[methodName](left, top, sizeX, sizeY);
                    }
                }
            };

            fabric.Object.prototype._controlsVisibility = {
                tl: true,
                tr: true,
                br: true,
                bl: true,
                ml: true,
                mt: true,
                mr: true,
                mb: true,
                mtr: false
            };

            fabric.Text.prototype.lockUniScaling = true;

            fabric.Text.prototype.centeredScaling = true;

            fabric.Image.prototype.centeredScaling = true;
        })();

        // return the canvas instance
        this.getCanvas = function() {
            return canvas;
        };

        this.init = function(canvasid, imageId) {
            console.log("initialize...");

            this.imageId = imageId;
            this.prevCanvas = null;

            canvas = new fabric.Canvas(canvasid, {
                hoverCursor: 'pointer',
                selectionBorderColor:'blue'
            });

            canvas.on({
                'object:moving': function(e) {
                    e.target.opacity = 0.5;
                },
                'object:modified': function(e) {
                    e.target.opacity = 1;
                },
                'object:selected': function(e) {
                    var selectedObject = e.target;
                    selectedObject.hasRotatingPoint = true;
                    if (selectedObject &&
                        selectedObject.type === 'text') {
                        $rootScope.$broadcast(
                            'mdeTextObjectSelected',
                            {
                                text: selectedObject.getText(),
                                fontColor: selectedObject.fill,
                                fontFamily: selectedObject.fontFamily
                            }
                        );
                    } else if (selectedObject &&
                               selectedObject.type === 'image'){
                        $rootScope.$broadcast('mdeImageObjectSelected');
                    };
                },
                'selection:cleared': function(e) {
                    $rootScope.$broadcast('mdeObjectCleared');
                }
            });
        };

        var addText = function(text, fontColor, fontFamily) {
            var textSample = new fabric.Text(
                text,
                {
                    //left: fabric.util.getRandomInt(0, 200),
                    //top: fabric.util.getRandomInt(0, 400),
                    left: 80,
                    top: 50,
                    fontFamily: fontFamily,
                    angle: 0,
                    fill: fontColor,
                    scaleX: 0.5,
                    scaleY: 0.5,
                    textAlign: "center",
                    fontWeight: '',
                    hasRotatingPoint: true
                }
            );

            canvas.add(textSample);
            canvas.item(canvas.item.length-1).hasRotatingPoint = true;
        };

        this.addTextWhenNoActiveText =
            function(text, fontColor, fontFamily) {
                var activeObject = canvas.getActiveObject();
                if (!activeObject || activeObject.type != 'text') {
                    addText(text, fontColor, fontFamily);
                }
            };

        this.activeTextP = function() {
            var activeObject = canvas.getActiveObject();
            return activeObject && activeObject.type == 'text';
        };

        this.setTheLastObjActive = function() {
            canvas.setActiveObject(canvas.item(canvas.getObjects().length - 1));
        };

        this.addImage = function(ImgSrc) {
            /*temp code*/
            var offset = 50;
            var left = fabric.util.getRandomInt(0 + offset, 200 - offset);
            var top = fabric.util.getRandomInt(0 + offset, 400 - offset);
            var angle = fabric.util.getRandomInt(-20, 40);
            var width = fabric.util.getRandomInt(30, 50);
            var opacity = (function(min, max){
                return Math.random() * (max - min) + min;
            })(0.5, 1);

            fabric.Image.fromURL(ImgSrc, function(image) {
                image.set(
                    {
                        left: left,
                        top: top,
                        angle: 0,
                        padding: 10,
                        cornersize: 10,
                        hasRotatingPoint:true
                    }
                );

                canvas.add(image);
            });
        };

        this.removeSelected = function() {
            var activeObject = canvas.getActiveObject(),
                activeGroup = canvas.getActiveGroup();
            if (activeObject) {
                canvas.remove(activeObject);
                $("#text-string").val("");
            }
            else if (activeGroup) {
                var objectsInGroup = activeGroup.getObjects();
                canvas.discardActiveGroup();
                objectsInGroup.forEach(function(object) {
                    canvas.remove(object);
                });
            }
        };

        this.flip = function(imageSrc) {
            var currentCanvas;

            $(this.imageId).attr("src", imageSrc);
            currentCanvas = JSON.stringify(canvas);
            canvas.clear();

            if(this.prevCanvas != null) {
                canvas.loadFromJSON(
                    this.prevCanvas,
                    canvas.renderAll.bind(canvas)
                );
            }

            this.prevCanvas = currentCanvas;
        };

        this.changeBackground = function(color) {
            $rootScope.$broadcast('mdeChangeBackground', color);
        };

        this.addCanvasBorder = function() {
           [lineL, lineR, lineU, lineD].map(function(elem) {
                canvas.add(elem);
            });
            canvas.renderAll();
        };

        this.removeCanvasBorder = function() {
           [lineL, lineR, lineU, lineD].map(function(elem) {
                canvas.remove(elem);
            });
            canvas.renderAll();
        };

        var applyToActiveTextFun = function(Fun) {
            var activeObject = canvas.getActiveObject();
            if (activeObject && activeObject.type === 'text') {
                Fun(activeObject);
                canvas.renderAll();
            }
        };

        this.renderActiveTextContent = function(text) {
            var activeObject = canvas.getActiveObject();
            if (activeObject && activeObject.type === 'text') {
                if(text === "") {
                    canvas.remove(activeObject);
                } else {
                    activeObject.text = text;
                    activeObject.left = (DrawAreaWidth - activeObject.width)/2;
                }
                canvas.renderAll();
            }
        };

        this.renderActiveTextFontColor = function(color) {
            applyToActiveTextFun(function(activeObject) {
                activeObject.fill = color;
            });
        };

        this.changeTextFontFamily = function(font) {
            applyToActiveTextFun(function(activeObject) {
                activeObject.fontFamily = font;
            });
        };

        this.toggleActiveTextBold = function() {
            applyToActiveTextFun(function(activeObject) {
                activeObject.fontWeight = (
                    activeObject.fontWeight == 'bold' ? '' : 'bold'
                );
            });
        };

        this.toggleActiveTextItalic = function() {
            applyToActiveTextFun(function(activeObject) {
                activeObject.fontStyle = (
                    activeObject.fontStyle == 'italic' ? '' : 'italic'
                );
            });
        };

        this.toggleActiveTextStrike = function() {
            applyToActiveTextFun(function(activeObject) {
                activeObject.textDecoration = (
                    activeObject.textDecoration ==
                        'line-through' ? '' : 'line-through'
                );
            });
        };

        this.toggleActiveTextUnderline = function() {
            applyToActiveTextFun(function(activeObject) {
                activeObject.textDecoration = (
                    activeObject.textDecoration ==
                        'underline' ? '' : 'underline'
                );
            });
        };

        this.setActiveTextAlignment = function(position) {
            applyToActiveTextFun(function(activeObject) {
                activeObject.textAlign = position;
            });
        };
    }
]);
<|endoftext|>


<commit_before>import {
  getNested,
  setNested,
  listKeys,
  listValues,
} from './utils/objects.js';

/**
 * Core setting which are visible to and updatable by the GUI
 *
 * @typedef CoreSettings
 * @prop {number} energy
 * @prop {number} height
 * @prop {number} moteSpawnRate
 * @prop {string} seed
 * @prop {number} startingBodies
 * @prop {number} temperature
 * @prop {number} volatility
 * @prop {number} width
 */

/**
 * @typedef BodiesModuleSettings
 * @prop {number} minRadius
 * @prop {string} moteColor
 * @prop {number} moteRadius
 * @prop {number} moteCalorieAdjustment
 * @prop {number} moteSpeedAdjustment
 * @prop {number} spawningEnergyAdjustment
 */

/**
 * @typedef GenomeModuleSettings
 * @prop {number} averageGeneCount
 * @prop {number} averageGeneSize
 * @prop {number} baseChanceMutateBit
 * @prop {number} baseChanceDropByte
 * @prop {number} baseChanceRepeatByte
 * @prop {number} baseChanceTransposeByte
 * @prop {number} baseChanceDropGene
 * @prop {number} baseChanceRepeatGene
 * @prop {number} baseChanceTransposeGene
 * @prop {number} bitsPerMass
 * @prop {number} bitsPerSpikeLength
 * @prop {number} percentSizeGenes
 * @prop {number} percentSpikeGenes
 */

/**
 * @typedef SimulationModuleSettings
 * @prop {number} maxBodies
 * @prop {number} spikeHighlightTime
 */

/**
 * @typedef SpikesModuleSettings
 * @prop {number} baseSpikeDrain
 * @prop {number} drainLengthExponent
 * @prop {number} spikeWidth
 */

/**
 * @typedef VitalsModuleSettings
 * @prop {number} massCalorieExponent
 * @prop {number} percentDiesAt
 * @prop {number} percentSpawnsAt
 * @prop {number} upkeepPerSpike
 * @prop {number} upkeepPerLength
 * @prop {number} baseUpkeepAdjustment
 * @prop {number} spikeUpkeepAdjustment
 */

/**
 * The settings object used by all modules
 *
 * @typedef Settings
 * @prop {CoreSettings} core
 * @prop {BodiesModuleSettings} bodies
 * @prop {GenomeModuleSettings} genome
 * @prop {SimulationModuleSettings} simulation
 * @prop {SpikesModuleSettings} spikes
 * @prop {VitalsModuleSettings} vitals
 */

const STORAGE_KEY = 'meeba-farm.settings';
const EXTRA_BUFFER = 16;
export const TANK_PADDING = 5;
export const UI_WIDTH = 250;

/**
 * @returns {string}
 */
const getRandomSeed = () => Math.random().toString(36).slice(2);

/** @type Settings */
export const settings = {
  core: {
    height: window.innerHeight - 2 * TANK_PADDING - EXTRA_BUFFER,
    energy: 3500000,
    moteSpawnRate: 4,
    seed: getRandomSeed(),
    startingBodies: 75,
    temperature: 30,
    volatility: 100,
    width: window.innerWidth - UI_WIDTH - 2 * TANK_PADDING - EXTRA_BUFFER,
  },
  bodies: {
    minRadius: 10,
    moteColor: '#792',
    moteRadius: 8,
    moteCalorieAdjustment: 2,
    moteSpeedAdjustment: 0.03,
    spawningEnergyAdjustment: 0.75,
  },
  genome: {
    averageGeneCount: 48,
    averageGeneSize: 6,
    baseChanceMutateBit: 0.0005,
    baseChanceDropByte: 0.008,
    baseChanceRepeatByte: 0.008,
    baseChanceTransposeByte: 0.016,
    baseChanceDropGene: 0.03,
    baseChanceRepeatGene: 0.03,
    baseChanceTransposeGene: 0.06,
    bitsPerMass: 1,
    bitsPerSpikeLength: 2,
    percentSizeGenes: 0.95,
    percentSpikeGenes: 0.05,
  },
  simulation: {
    maxBodies: 800,
    spikeHighlightTime: 167,
  },
  spikes: {
    baseSpikeDrain: 2500,
    drainLengthExponent: 1.2,
    spikeWidth: 6,
  },
  vitals: {
    massCalorieExponent: 0.66, // Idealized Kleiber's law
    percentDiesAt: 0.5,
    percentSpawnsAt: 2,
    upkeepPerSpike: 8,
    upkeepPerLength: 1,
    baseUpkeepAdjustment: 0.075, // Adjust so "average" use is ~15 cal/sec
    spikeUpkeepAdjustment: 200, // Adjust so cost of four is about equal to mass cost
  },
};

const SETTINGS_KEYS = listKeys(settings);
const DEFAULT_VALUES = listValues(settings);

const storeSettings = () => {
  const settingsString = window.btoa(JSON.stringify(listValues(settings)));
  window.localStorage.setItem(STORAGE_KEY, settingsString);
};

/** @type Array<function(): void> */
const updateListeners = [];
const triggerListeners = () => {
  for (const onUpdate of updateListeners) {
    onUpdate();
  }
};

/**
 * @param {string} pathString - dot-separated setting path
 * @returns {string[]} - path array
 */
const parsePathString = (pathString) => {
  const pathArray = pathString.split('.');
  return pathArray.length > 1 ? pathArray : ['core', ...pathArray];
};

/**
 * @param {string} pathString - dot-separated setting path
 * @param {string|number|boolean} value
 */
const setSetting = (pathString, value) => {
  const path = parsePathString(pathString);
  setNested(settings, path, value);
};

/**
 * Updates a particular setting with a primitive value, updates a core value if
 * passed a single key rather than a full path
 *
 * @param {string} pathString - dot-separated setting path
 * @param {string|number|boolean} value - the new setting value
 */
export const updateSetting = (pathString, value) => {
  setSetting(pathString, value);
  storeSettings();
  triggerListeners();
};

/**
 * Takes a setting function which will be called immediately, and then again
 * every time the settings object is updated
 *
 * @param {function(): void} onUpdate - a function to run on updates
 */
export const addUpdateListener = (onUpdate) => {
  onUpdate();
  updateListeners.push(onUpdate);
};

/**
 * Gets a setting value by its dot-separated path string
 *
 * @param {string} pathString - dot-separated setting path
 * @returns {string|number|boolean}
 */
export const getSetting = (pathString) => {
  const path = parsePathString(pathString);
  return getNested(settings, path);
};

/**
 * Takes string of settings values and updates all settings to match
 *
 * @param {string} settingsString - base64 encoded JSON array
 */
export const loadSettings = (settingsString) => {
  let values = /** @type {(string|number|boolean)[]} */([]);
  try {
    values = JSON.parse(window.atob(settingsString));
  } catch (err) {
    // eslint-disable-next-line no-console
    console.warn(`Unable to load settings: ${err}`);
  }

  if (values.length === SETTINGS_KEYS.length) {
    SETTINGS_KEYS.forEach((key, i) => setSetting(key, values[i]));
    triggerListeners();
  }
};

/**
 * Returns the currently saved settings string
 *
 * @returns {string|null} - base64 encoded JSON array of settings values
 */
export const getSavedSettings = () => window.localStorage.getItem(STORAGE_KEY);

/**
 * Clears out all saved settings and restores the defaults
 */
export const restoreDefaultSettings = () => {
  window.localStorage.removeItem(STORAGE_KEY);
  SETTINGS_KEYS.forEach((key, i) => setSetting(key, DEFAULT_VALUES[i]));
  setSetting('seed', getRandomSeed());
  triggerListeners();
};

// Load settings from local storage if any, otherwise save defaults
const previousSettings = getSavedSettings();
if (previousSettings) {
  loadSettings(previousSettings);
} else {
  storeSettings();
}
<commit_msg>Tweak default settings to be more dynamic<commit_after>import {
  getNested,
  setNested,
  listKeys,
  listValues,
} from './utils/objects.js';

/**
 * Core setting which are visible to and updatable by the GUI
 *
 * @typedef CoreSettings
 * @prop {number} energy
 * @prop {number} height
 * @prop {number} moteSpawnRate
 * @prop {string} seed
 * @prop {number} startingBodies
 * @prop {number} temperature
 * @prop {number} volatility
 * @prop {number} width
 */

/**
 * @typedef BodiesModuleSettings
 * @prop {number} minRadius
 * @prop {string} moteColor
 * @prop {number} moteRadius
 * @prop {number} moteCalorieAdjustment
 * @prop {number} moteSpeedAdjustment
 * @prop {number} spawningEnergyAdjustment
 */

/**
 * @typedef GenomeModuleSettings
 * @prop {number} averageGeneCount
 * @prop {number} averageGeneSize
 * @prop {number} baseChanceMutateBit
 * @prop {number} baseChanceDropByte
 * @prop {number} baseChanceRepeatByte
 * @prop {number} baseChanceTransposeByte
 * @prop {number} baseChanceDropGene
 * @prop {number} baseChanceRepeatGene
 * @prop {number} baseChanceTransposeGene
 * @prop {number} bitsPerMass
 * @prop {number} bitsPerSpikeLength
 * @prop {number} percentSizeGenes
 * @prop {number} percentSpikeGenes
 */

/**
 * @typedef SimulationModuleSettings
 * @prop {number} maxBodies
 * @prop {number} spikeHighlightTime
 */

/**
 * @typedef SpikesModuleSettings
 * @prop {number} baseSpikeDrain
 * @prop {number} drainLengthExponent
 * @prop {number} spikeWidth
 */

/**
 * @typedef VitalsModuleSettings
 * @prop {number} massCalorieExponent
 * @prop {number} percentDiesAt
 * @prop {number} percentSpawnsAt
 * @prop {number} upkeepPerSpike
 * @prop {number} upkeepPerLength
 * @prop {number} baseUpkeepAdjustment
 * @prop {number} spikeUpkeepAdjustment
 */

/**
 * The settings object used by all modules
 *
 * @typedef Settings
 * @prop {CoreSettings} core
 * @prop {BodiesModuleSettings} bodies
 * @prop {GenomeModuleSettings} genome
 * @prop {SimulationModuleSettings} simulation
 * @prop {SpikesModuleSettings} spikes
 * @prop {VitalsModuleSettings} vitals
 */

const STORAGE_KEY = 'meeba-farm.settings';
const EXTRA_BUFFER = 16;
export const TANK_PADDING = 5;
export const UI_WIDTH = 250;

/**
 * @returns {string}
 */
const getRandomSeed = () => Math.random().toString(36).slice(2);

/** @type Settings */
export const settings = {
  core: {
    height: window.innerHeight - 2 * TANK_PADDING - EXTRA_BUFFER,
    energy: 3000000,
    moteSpawnRate: 16,
    seed: getRandomSeed(),
    startingBodies: 50,
    temperature: 30,
    volatility: 100,
    width: window.innerWidth - UI_WIDTH - 2 * TANK_PADDING - EXTRA_BUFFER,
  },
  bodies: {
    minRadius: 10,
    moteColor: '#792',
    moteRadius: 5,
    moteCalorieAdjustment: 2,
    moteSpeedAdjustment: 0.03,
    spawningEnergyAdjustment: 0.75,
  },
  genome: {
    averageGeneCount: 48,
    averageGeneSize: 6,
    baseChanceMutateBit: 0.0005,
    baseChanceDropByte: 0.008,
    baseChanceRepeatByte: 0.008,
    baseChanceTransposeByte: 0.016,
    baseChanceDropGene: 0.03,
    baseChanceRepeatGene: 0.03,
    baseChanceTransposeGene: 0.06,
    bitsPerMass: 1,
    bitsPerSpikeLength: 2,
    percentSizeGenes: 0.95,
    percentSpikeGenes: 0.05,
  },
  simulation: {
    maxBodies: 500,
    spikeHighlightTime: 167,
  },
  spikes: {
    baseSpikeDrain: 10000,
    drainLengthExponent: 1.2,
    spikeWidth: 6,
  },
  vitals: {
    massCalorieExponent: 0.66, // Idealized Kleiber's law
    percentDiesAt: 0.5,
    percentSpawnsAt: 2,
    upkeepPerSpike: 16,
    upkeepPerLength: 1,
    baseUpkeepAdjustment: 0.075, // Adjust so "average" use is ~15 cal/sec
    spikeUpkeepAdjustment: 250, // Adjust so cost of four is about equal to mass cost
  },
};

const SETTINGS_KEYS = listKeys(settings);
const DEFAULT_VALUES = listValues(settings);

const storeSettings = () => {
  const settingsString = window.btoa(JSON.stringify(listValues(settings)));
  window.localStorage.setItem(STORAGE_KEY, settingsString);
};

/** @type Array<function(): void> */
const updateListeners = [];
const triggerListeners = () => {
  for (const onUpdate of updateListeners) {
    onUpdate();
  }
};

/**
 * @param {string} pathString - dot-separated setting path
 * @returns {string[]} - path array
 */
const parsePathString = (pathString) => {
  const pathArray = pathString.split('.');
  return pathArray.length > 1 ? pathArray : ['core', ...pathArray];
};

/**
 * @param {string} pathString - dot-separated setting path
 * @param {string|number|boolean} value
 */
const setSetting = (pathString, value) => {
  const path = parsePathString(pathString);
  setNested(settings, path, value);
};

/**
 * Updates a particular setting with a primitive value, updates a core value if
 * passed a single key rather than a full path
 *
 * @param {string} pathString - dot-separated setting path
 * @param {string|number|boolean} value - the new setting value
 */
export const updateSetting = (pathString, value) => {
  setSetting(pathString, value);
  storeSettings();
  triggerListeners();
};

/**
 * Takes a setting function which will be called immediately, and then again
 * every time the settings object is updated
 *
 * @param {function(): void} onUpdate - a function to run on updates
 */
export const addUpdateListener = (onUpdate) => {
  onUpdate();
  updateListeners.push(onUpdate);
};

/**
 * Gets a setting value by its dot-separated path string
 *
 * @param {string} pathString - dot-separated setting path
 * @returns {string|number|boolean}
 */
export const getSetting = (pathString) => {
  const path = parsePathString(pathString);
  return getNested(settings, path);
};

/**
 * Takes string of settings values and updates all settings to match
 *
 * @param {string} settingsString - base64 encoded JSON array
 */
export const loadSettings = (settingsString) => {
  let values = /** @type {(string|number|boolean)[]} */([]);
  try {
    values = JSON.parse(window.atob(settingsString));
  } catch (err) {
    // eslint-disable-next-line no-console
    console.warn(`Unable to load settings: ${err}`);
  }

  if (values.length === SETTINGS_KEYS.length) {
    SETTINGS_KEYS.forEach((key, i) => setSetting(key, values[i]));
    triggerListeners();
  }
};

/**
 * Returns the currently saved settings string
 *
 * @returns {string|null} - base64 encoded JSON array of settings values
 */
export const getSavedSettings = () => window.localStorage.getItem(STORAGE_KEY);

/**
 * Clears out all saved settings and restores the defaults
 */
export const restoreDefaultSettings = () => {
  window.localStorage.removeItem(STORAGE_KEY);
  SETTINGS_KEYS.forEach((key, i) => setSetting(key, DEFAULT_VALUES[i]));
  setSetting('seed', getRandomSeed());
  triggerListeners();
};

// Load settings from local storage if any, otherwise save defaults
const previousSettings = getSavedSettings();
if (previousSettings) {
  loadSettings(previousSettings);
} else {
  storeSettings();
}
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var osenv = require('osenv');

/**
 * Configuration
 * @constructor
 */
var Config = function() {
    var self = this;

    /** vars to use */
    this.vars = {
        home: osenv.home()
    };

    /**
     * load config
     * @param file
     */
    this.load = function(file) {
        var cfg = JSON.parse(fs.readFileSync(file));
        this.parse(cfg);
        return cfg;
    };

    /**
     * parse config
     * @param cfg
     */
    this.parse = function(cfg) {
        cfg.vars = this.parsevars(cfg.vars);
        cfg = this.injectvars(cfg);
        cfg = this.resolvePaths(cfg);
        return cfg;
    };

    /**
     * resolve/normalize paths in config
     * @param cfg
     */
    this.resolvePaths = function(cfg) {
        for ( var c in cfg ) {
            if (typeof cfg[c] === 'string' && (cfg[c].indexOf('/') !== -1 || cfg[c].indexOf('\\') !== -1)) {
                cfg[c] = path.resolve(cfg[c]);
            } else if (typeof cfg[c] === 'object') {
                cfg[c] = self.resolvePaths(cfg[c]);
            }
        }
        return cfg;
    };

    /**
     * inject vars into config
     * @param cfg
     */
    this.injectvars = function(cfg) {
        for ( var c in cfg ) {
            if (typeof cfg[c] === 'string') {
                var matches = cfg[c].match(/{{([^}]*)}}/g);
                for (var d in matches) {
                    var item = matches[d].substr(2, matches[d].length-4);
                    if (self.vars[item]) {
                        var re = new RegExp('{{' + item + '}}','g');
                        cfg[c] = cfg[c].replace(re, self.vars[item]);
                    }
                }
            } else if (typeof cfg[c] === 'object') {
               cfg[c] = self.injectvars(cfg[c]);
            }
        }
        return cfg;
    };

    /**
     * make sure directories exist that are in the config
     */
    this.ensureDirectoryStructure = function() {
        File.prototype.ensureDirectoriesExist([
            cfg.applicationStorage,
            cfg.mediaDirectory
        ]);
    };

    /**
     * parse vars
     * @param cfg
     * @param counter
     */
    this.parsevars = function(cfgvars, counter) {
        if (!counter) { counter = 0; }
        counter ++;
        for ( var c in cfgvars ) {
            if (typeof cfgvars[c] === 'string') {
                var matches = cfgvars[c].match(/{{([^}]*)}}/g);
                for (var d in matches) {
                    var item = matches[d].substr(2, matches[d].length-4);
                    if (self.vars[item]) {
                        var re = new RegExp('{{' + item + '}}','g');
                        cfgvars[c] = cfgvars[c].replace(re, self.vars[item]);
                    }
                }
            }
            self.vars[c] = cfgvars[c];
        }

        if (counter > 100) {
            console.log('Malformed Configuration File, please fix');
            return cfgvars;
        }

        if (JSON.stringify(cfgvars).indexOf('{{') !== -1) {
            cfgvars = self.parsevars(cfgvars, counter)
        }

        this.ensureDirectoryStructure();
        return cfgvars;
    };
};

module.exports = Config;<commit_msg>Update Config.js<commit_after>var fs = require('fs');
var path = require('path');
var osenv = require('osenv');
var File = require('./File')

/**
 * Configuration
 * @constructor
 */
var Config = function() {
    var self = this;

    /** vars to use */
    this.vars = {
        home: osenv.home()
    };

    /**
     * load config
     * @param file
     */
    this.load = function(file) {
        var cfg = JSON.parse(fs.readFileSync(file));
        this.parse(cfg);
        return cfg;
    };

    /**
     * parse config
     * @param cfg
     */
    this.parse = function(cfg) {
        cfg.vars = this.parsevars(cfg.vars);
        cfg = this.injectvars(cfg);
        cfg = this.resolvePaths(cfg);
        return cfg;
    };

    /**
     * resolve/normalize paths in config
     * @param cfg
     */
    this.resolvePaths = function(cfg) {
        for ( var c in cfg ) {
            if (typeof cfg[c] === 'string' && (cfg[c].indexOf('/') !== -1 || cfg[c].indexOf('\\') !== -1)) {
                cfg[c] = path.resolve(cfg[c]);
            } else if (typeof cfg[c] === 'object') {
                cfg[c] = self.resolvePaths(cfg[c]);
            }
        }
        return cfg;
    };

    /**
     * inject vars into config
     * @param cfg
     */
    this.injectvars = function(cfg) {
        for ( var c in cfg ) {
            if (typeof cfg[c] === 'string') {
                var matches = cfg[c].match(/{{([^}]*)}}/g);
                for (var d in matches) {
                    var item = matches[d].substr(2, matches[d].length-4);
                    if (self.vars[item]) {
                        var re = new RegExp('{{' + item + '}}','g');
                        cfg[c] = cfg[c].replace(re, self.vars[item]);
                    }
                }
            } else if (typeof cfg[c] === 'object') {
               cfg[c] = self.injectvars(cfg[c]);
            }
        }
        return cfg;
    };

    /**
     * make sure directories exist that are in the config
     */
    this.ensureDirectoryStructure = function() {
        File.prototype.ensureDirectoriesExist([
            cfg.applicationStorage,
            cfg.mediaDirectory
        ]);
    };

    /**
     * parse vars
     * @param cfg
     * @param counter
     */
    this.parsevars = function(cfgvars, counter) {
        if (!counter) { counter = 0; }
        counter ++;
        for ( var c in cfgvars ) {
            if (typeof cfgvars[c] === 'string') {
                var matches = cfgvars[c].match(/{{([^}]*)}}/g);
                for (var d in matches) {
                    var item = matches[d].substr(2, matches[d].length-4);
                    if (self.vars[item]) {
                        var re = new RegExp('{{' + item + '}}','g');
                        cfgvars[c] = cfgvars[c].replace(re, self.vars[item]);
                    }
                }
            }
            self.vars[c] = cfgvars[c];
        }

        if (counter > 100) {
            console.log('Malformed Configuration File, please fix');
            return cfgvars;
        }

        if (JSON.stringify(cfgvars).indexOf('{{') !== -1) {
            cfgvars = self.parsevars(cfgvars, counter)
        }

        this.ensureDirectoryStructure();
        return cfgvars;
    };
};

module.exports = Config;
<|endoftext|>