<commit_before>'use strict';

/**
 * Pterodactyl - Daemon
 * Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
const _ = require('lodash');
const Async = require('async');
const Fs = require('fs-extra');
const Properties = require('properties-parser');
const Yaml = require('node-yaml');
const Ini = require('ini');

class FileParser {

    constructor(server) {
        this.server = server;
    }

    file(file, strings, next) {
        if (!_.isObject(strings)) {
            return next(new Error('Variable `strings` must be passed as an object.'));
        }

        Async.waterfall([
            callback => {
                Fs.readFile(this.server.path(file), (err, data) => {
                    if (err) {
                        if (_.startsWith(err.message, 'ENOENT: no such file or directory')) return next();
                        return next(err);
                    }
                    return callback(null, data.toString().split('\n'));
                });
            },
            (lines, callback) => {
                // @TODO: add line if its not already there.
                Async.forEachOf(lines, (line, index, eachCallback) => {
                    Async.forEachOf(strings, (replaceString, findString, eachEachCallback) => {
                        if (line.startsWith(findString)) {
                            lines[index] = replaceString.replace(/{{ (\S+) }}/g, ($0, $1) => { // eslint-disable-line
                                return _.reduce(($1).split('.'), (o, i) => o[i], this.server.json);
                            });
                        }
                        return eachEachCallback();
                    }, () => {
                        eachCallback();
                    });
                }, () => {
                    callback(null, lines.join('\n'));
                });
            },
            (lines, callback) => {
                Fs.writeFile(this.server.path(file), lines, callback);
            },
        ], (err, result) => {
            next(err, result);
        });
    }

    yaml(file, strings, next) {
        Yaml.read(this.server.path(file), (err, data) => {
            if (err) {
                if (_.startsWith(err.message, 'ENOENT: no such file or directory')) return next();
                return next(err);
            }
            Async.forEachOf(strings, (value, key, callback) => {
                let newValue;
                if (_.isString(value)) {
                    newValue = value.replace(/{{ (\S+) }}/g, ($0, $1) => { // eslint-disable-line
                        return _.reduce(($1).split('.'), (o, i) => o[i], this.server.json);
                    });
                } else { newValue = value; }
                if (!_.isBoolean(newValue) && !_.isNaN(_.toNumber(newValue))) {
                    newValue = _.toNumber(newValue);
                }

                _.set(data, key, newValue);
                callback();
            }, () => {
                Yaml.write(this.server.path(file), data, writeErr => {
                    next(writeErr);
                });
            });
        });
    }

    prop(file, strings, next) {
        Properties.createEditor(this.server.path(file), (err, Editor) => {
            if (err) {
                if (_.startsWith(err.message, 'ENOENT: no such file or directory')) return next();
                return next(err);
            }
            Async.forEachOf(strings, (value, key, callback) => {
                let newValue;
                if (_.isString(value)) {
                    newValue = value.replace(/{{ (\S+) }}/g, ($0, $1) => { // eslint-disable-line
                        return _.reduce(($1).split('.'), (o, i) => o[i], this.server.json);
                    });
                } else { newValue = value; }
                if (!_.isBoolean(newValue) && !_.isNaN(_.toNumber(newValue))) {
                    newValue = _.toNumber(newValue);
                }

                Editor.set(key, newValue);
                callback();
            }, () => {
                Editor.save(this.server.path(file), next);
            });
        });
    }

    json(file, strings, next) {
        Fs.readJson(this.server.path(file), (err, data) => {
            if (err) {
                if (_.startsWith(err.message, 'ENOENT: no such file or directory')) return next();
                return next(err);
            }
            Async.forEachOf(strings, (value, key, callback) => {
                let newValue;
                if (_.isString(value)) {
                    newValue = value.replace(/{{ (\S+) }}/g, ($0, $1) => { // eslint-disable-line
                        return _.reduce(($1).split('.'), (o, i) => o[i], this.server.json);
                    });
                } else { newValue = value; }
                if (!_.isBoolean(newValue) && !_.isNaN(_.toNumber(newValue))) {
                    newValue = _.toNumber(newValue);
                }

                _.set(data, key, newValue);
                callback();
            }, () => {
                Fs.writeJson(this.server.path(file), data, next);
            });
        });
    }

    ini(file, strings, next) {
        Async.waterfall([
            callback => {
                Fs.readFile(this.server.path(file), 'utf8', (err, result) => {
                    if (err) {
                        if (_.startsWith(err.message, 'ENOENT: no such file or directory')) return next();
                        return next(err);
                    }
                    callback(null, result);
                });
            },
            (contents, callback) => {
                const data = Ini.parse(contents);
                Async.forEachOf(strings, (value, key, eachCallback) => {
                    let newValue;
                    if (_.isString(value)) {
                        newValue = value.replace(/{{ (\S+) }}/g, ($0, $1) => { // eslint-disable-line
                            return _.reduce(($1).split('.'), (o, i) => o[i], this.server.json);
                        });
                    } else { newValue = value; }
                    if (!_.isBoolean(newValue) && !_.isNaN(_.toNumber(newValue))) {
                        newValue = _.toNumber(newValue);
                    }

                    _.set(data, key, Ini.safe(newValue));
                    eachCallback();
                }, () => {
                    callback(null, data);
                });
            },
            (data, callback) => {
                Fs.writeFile(this.server.path(file), Ini.encode(data), 'utf8', callback);
            },
        ], next);
    }

}

module.exports = FileParser;
<commit_msg>properties parser does not like us telling it what a value is<commit_after>'use strict';

/**
 * Pterodactyl - Daemon
 * Copyright (c) 2015 - 2016 Dane Everitt <dane@daneeveritt.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
const _ = require('lodash');
const Async = require('async');
const Fs = require('fs-extra');
const Properties = require('properties-parser');
const Yaml = require('node-yaml');
const Ini = require('ini');

class FileParser {

    constructor(server) {
        this.server = server;
    }

    file(file, strings, next) {
        if (!_.isObject(strings)) {
            return next(new Error('Variable `strings` must be passed as an object.'));
        }

        Async.waterfall([
            callback => {
                Fs.readFile(this.server.path(file), (err, data) => {
                    if (err) {
                        if (_.startsWith(err.message, 'ENOENT: no such file or directory')) return next();
                        return next(err);
                    }
                    return callback(null, data.toString().split('\n'));
                });
            },
            (lines, callback) => {
                // @TODO: add line if its not already there.
                Async.forEachOf(lines, (line, index, eachCallback) => {
                    Async.forEachOf(strings, (replaceString, findString, eachEachCallback) => {
                        if (line.startsWith(findString)) {
                            lines[index] = replaceString.replace(/{{ (\S+) }}/g, ($0, $1) => { // eslint-disable-line
                                return _.reduce(($1).split('.'), (o, i) => o[i], this.server.json);
                            });
                        }
                        return eachEachCallback();
                    }, () => {
                        eachCallback();
                    });
                }, () => {
                    callback(null, lines.join('\n'));
                });
            },
            (lines, callback) => {
                Fs.writeFile(this.server.path(file), lines, callback);
            },
        ], (err, result) => {
            next(err, result);
        });
    }

    yaml(file, strings, next) {
        Yaml.read(this.server.path(file), (err, data) => {
            if (err) {
                if (_.startsWith(err.message, 'ENOENT: no such file or directory')) return next();
                return next(err);
            }
            Async.forEachOf(strings, (value, key, callback) => {
                let newValue;
                if (_.isString(value)) {
                    newValue = value.replace(/{{ (\S+) }}/g, ($0, $1) => { // eslint-disable-line
                        return _.reduce(($1).split('.'), (o, i) => o[i], this.server.json);
                    });
                } else { newValue = value; }
                if (!_.isBoolean(newValue) && !_.isNaN(_.toNumber(newValue))) {
                    newValue = _.toNumber(newValue);
                }

                _.set(data, key, newValue);
                callback();
            }, () => {
                Yaml.write(this.server.path(file), data, writeErr => {
                    next(writeErr);
                });
            });
        });
    }

    prop(file, strings, next) {
        Properties.createEditor(this.server.path(file), (err, Editor) => {
            if (err) {
                if (_.startsWith(err.message, 'ENOENT: no such file or directory')) return next();
                return next(err);
            }
            Async.forEachOf(strings, (value, key, callback) => {
                let newValue;
                if (_.isString(value)) {
                    newValue = value.replace(/{{ (\S+) }}/g, ($0, $1) => { // eslint-disable-line
                        return _.reduce(($1).split('.'), (o, i) => o[i], this.server.json);
                    });
                } else { newValue = value; }

                Editor.set(key, newValue);
                callback();
            }, () => {
                Editor.save(this.server.path(file), next);
            });
        });
    }

    json(file, strings, next) {
        Fs.readJson(this.server.path(file), (err, data) => {
            if (err) {
                if (_.startsWith(err.message, 'ENOENT: no such file or directory')) return next();
                return next(err);
            }
            Async.forEachOf(strings, (value, key, callback) => {
                let newValue;
                if (_.isString(value)) {
                    newValue = value.replace(/{{ (\S+) }}/g, ($0, $1) => { // eslint-disable-line
                        return _.reduce(($1).split('.'), (o, i) => o[i], this.server.json);
                    });
                } else { newValue = value; }
                if (!_.isBoolean(newValue) && !_.isNaN(_.toNumber(newValue))) {
                    newValue = _.toNumber(newValue);
                }

                _.set(data, key, newValue);
                callback();
            }, () => {
                Fs.writeJson(this.server.path(file), data, next);
            });
        });
    }

    ini(file, strings, next) {
        Async.waterfall([
            callback => {
                Fs.readFile(this.server.path(file), 'utf8', (err, result) => {
                    if (err) {
                        if (_.startsWith(err.message, 'ENOENT: no such file or directory')) return next();
                        return next(err);
                    }
                    callback(null, result);
                });
            },
            (contents, callback) => {
                const data = Ini.parse(contents);
                Async.forEachOf(strings, (value, key, eachCallback) => {
                    let newValue;
                    if (_.isString(value)) {
                        newValue = value.replace(/{{ (\S+) }}/g, ($0, $1) => { // eslint-disable-line
                            return _.reduce(($1).split('.'), (o, i) => o[i], this.server.json);
                        });
                    } else { newValue = value; }
                    if (!_.isBoolean(newValue) && !_.isNaN(_.toNumber(newValue))) {
                        newValue = _.toNumber(newValue);
                    }

                    _.set(data, key, Ini.safe(newValue));
                    eachCallback();
                }, () => {
                    callback(null, data);
                });
            },
            (data, callback) => {
                Fs.writeFile(this.server.path(file), Ini.encode(data), 'utf8', callback);
            },
        ], next);
    }

}

module.exports = FileParser;
<|endoftext|>


<commit_before>// module generate object code from ast
var Interpreter = function() {
  this.name = 'Interpreter';
  this.objects = [];
  this.define = [];
  this.rules = {};
  this.computed = [];
  this.maxdepth = 100;
  this.depth = 0;
  this.maxobjects = 1000;
  this.objectnum = 0;
  this.minsize = .2;
  this.maxsize = 1.0;
  this.seed = 'initial'; // integer or 'initial'
  this.stack = [];
  this.curr = {};
  this.curr.matrix = new Matrix4();
  this.curr.hex = Color('#ff0000');
  this.curr.hsv = _.extend(Color({ hue: 0, saturation: 1, value: 1 }), { computed: false });
  this.curr.blend = { color: null, strength: 0, computed: false };
  this.curr.alpha = 1;
  this.mt = new MersenneTwister();
}

// termination criteria
Interpreter.prototype.terminated = function() {
  if (this.objectnum > this.maxobjects) return true;
  if (this.depth >= this.maxdepth) return true;
  return false;
}

// stack current transformation state
Interpreter.prototype.pushState = function() {
  this.depth++;
  this.stack.push({
    matrix: this.curr.matrix.clone(),
    hex: this.curr.hex.clone(),
    hsv: this.curr.hsv.clone(),
    blend: _.extend({}, this.curr.blend),
    alpha: this.curr.alpha
  });
  return this;
}

// pull the parent transformation state
Interpreter.prototype.popState = function() {
  if (this.stack.length > 0) {
    this.curr = this.stack.pop();
    this.depth--;
  }
  return this;
}

Interpreter.prototype.translate = function(x, y, z) {
  this.curr.matrix.translate({
    x: x,
    y: y,
    z: z
  });
  return this;
}

Interpreter.prototype.rotateX = function(angle) {
  this.curr.matrix.rotateX(angle);
  return this;
}

Interpreter.prototype.rotateY = function(angle) {
  this.curr.matrix.rotateY(angle);
  return this;
}

Interpreter.prototype.rotateZ = function(angle) {
  this.curr.matrix.rotateZ(angle);
  return this;
}

Interpreter.prototype.scale = function(x, y, z) {
  this.curr.matrix.scale({
    x: x,
    y: y,
    z: z
  });
  return this;
}

// make 3x3 rotation matrix to 4x4 matrix
// test: { m 1 0 0 0 .53 -.85 0 .85 .53 } box
Interpreter.prototype.matrix = function(v) {
  this.curr.matrix.set(
    v[0], v[1], v[2], 0,
    v[3], v[4], v[5], 0,
    v[6], v[7], v[8], 0,
       0,    0,    0, 1
  );
  return this;
}

Interpreter.prototype.random16 = function() {
  var rand = this.mt.next() * 0xffffff;
  return Math.floor(rand).toString(16);
}

Interpreter.prototype.randomColor = function() {
  return sprintf('#%s', this.random16());
}

Interpreter.prototype.setColor = function(color) {
  if (color === 'random') color = this.randomColor();
  this.curr.hex = Color(color);
  return this;
}

Interpreter.prototype.setHue = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.hue += v % 360;
  return this;
}

Interpreter.prototype.setSaturation = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.saturation = clamp(this.curr.hsv.saturation * v, 0, 1);
  return this;
}

Interpreter.prototype.setBrightness = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.value = clamp(this.curr.hsv.value * v, 0, 1);
  return this;
}

Interpreter.prototype.setBlend = function(color, strength) {
  this.curr.blend.computed = true;
  this.curr.blend.color = color;
  this.curr.blend.strength = this.curr.blend.strength + clamp(strength, 0, 1);
  return this;
}

// execute eisenscript
Interpreter.prototype.generate = function(ast) {
  // rewriting ast
  var that = this;
  ast.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Rule: that.rewriteRule(statement); break;
    }
  });

  // pull the defines
  ast.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Define: that.define.push(statement); break;
      case Symbol.Set: that.define.push(statement); break;
      case Symbol.Statement: if (statement.computed) that.computed.push(statement); break;
    }
  });

  // creating intermediate code...
  // promise
  this.define.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Set:
        switch (statement.key) {
          case Symbol.Maxdepth: that.maxdepth = statement.value; break;
          case Symbol.Maxobjects: that.maxobjects = statement.value; break;
          case Symbol.Minsize: that.minsize = statement.value; break;
          case Symbol.Maxsize: that.maxsize = statement.value; break;
          case Symbol.Seed: that.seed = statement.value; break;
        }
        break;
      case Symbol.Define:
        // not implemented, but I don't think the definition statement is need.
        break;
    }
  });

  // initial value is randomised chosen integer
  this.mt.setSeed(this.seed === 'initial' ? randInt(0, 65535) : this.seed);

  // pull the statement of system environment
  this.define.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Set:
        if (statement.key === Symbol.Background) that.generateBackground(statement);
        break;
    }
  });

  // execute main
  this.parseStatements(this.computed);

  // return the intermediate code
  return {
    maxdepth: this.maxdepth,
    maxobjects: this.maxobjects,
    minsize: this.minsize,
    maxsize: this.maxsize,
    seed: this.seed,
    objects: this.objects,
    num: this.objects.length
  }
}

// rewrite subtree related to rule statement
Interpreter.prototype.rewriteRule = function(rule) {
  rule.params.forEach(function(param) {
    if (param.type === Symbol.Modifier) {
      switch (param.key) {
        case Symbol.Weight: rule.weight = param.value; break;
        case Symbol.Maxdepth: rule.maxdepth = param.value; rule.alternate = param.alternate; break;
      }
    }
  });
  if (!this.rules[rule.id]) this.rules[rule.id] = [];
  this.rules[rule.id].push(rule);
  return this;
}

// execute statements
Interpreter.prototype.parseStatements = function(statements) {
  var i = 0, len = statements.length;
  while (i < len) {
    if (this.terminated()) break;
    this.parseStatement(statements[i], 0);
    i++;
  }
  return this;
}

// execute a statement
Interpreter.prototype.parseStatement = function(statement, index) {
  // parse transformation expression
  var expr = statement.exprs[index];
  if (expr) {
    this.pushState();
    for (var i = 0; i < expr.left; i++) {
      if (this.terminated()) break;
      this.parseTransformStatement(expr.right);
      // if statement.exprs[index + 1] is undefined, it would break the transformation loops.
      this.parseStatement(statement, index + 1);
    }
    this.popState();
    return this;
  }

  // if not primitive, call rule and parse next transformation loops
  if (_.values(Primitive).indexOf(statement.id) === -1) {
    var rule = this.sampling(statement.id);
    if (rule) this.parseStatements(rule.body);
    return this;
  }

  // achieve the end of nested transformation loops
  this.generatePrimitive(statement);
  return this;
}

// break down transformation set
Interpreter.prototype.parseTransformStatement = function(transform) {
  var i = 0, len = transform.properties.length;
  while (i < len) {
    this.parseTransform(transform.properties[i]);
    i++;
  }
  return this;
}

// parse transformation property
Interpreter.prototype.parseTransform = function(property) {
  var v = property.value;
  switch (property.key) {
    case Symbol.XShift: this.translate(v, 0, 0); break;
    case Symbol.YShift: this.translate(0, v, 0); break;
    case Symbol.ZShift: this.translate(0, 0, v); break;
    case Symbol.RotateX: this.rotateX(degToRad(v)); break;
    case Symbol.RotateY: this.rotateY(degToRad(v)); break;
    case Symbol.RotateZ: this.rotateZ(degToRad(v)); break;
    case Symbol.Size: this.scale(v.x, v.y, v.z); break;
    case Symbol.Matrix: this.matrix(v); break;
    case Symbol.Color: this.setColor(v); break;
    case Symbol.Hue: this.setHue(v); break;
    case Symbol.Saturation: this.setSaturation(v); break;
    case Symbol.Brightness: this.setBrightness(v); break;
    case Symbol.Blend: this.setBlend(property.color, property.strength); break;
    case Symbol.Alpha: this.curr.alpha *= v; break;
  }
  return this;
}

// create primitive object and stack it as intermediate code for renderer
Interpreter.prototype.generatePrimitive = function(statement) {
  // if achieved maxobjects
  this.objectnum++;
  if (this.terminated()) return;

  // blend the current color with the specified color
  if (this.curr.blend.computed) {
    this.curr.hex = this.curr.hex.toHSV();
    var blend = Color(this.curr.blend.color).toHSV();
    this.curr.hex.hue += (blend.hue - this.curr.hex.hue) * this.curr.blend.strength / 6;
    this.curr.hex.hue %= 360;
  }

  // primitive object
  this.objects.push({
    type: Type.Primitive,
    name: statement.id,
    matrix: this.curr.matrix.clone(),
    color: this.curr.hsv.computed ? this.curr.hex.blend(this.curr.hsv, 1).toCSS() : this.curr.hex.toCSS(),
    opacity: this.curr.alpha,
    depth: this.depth
  });
}

// create background object code and stack it as intermediate code for renderer
Interpreter.prototype.generateBackground = function(statement) {
  this.objects.push({
    type: Type.Background,
    color: statement.value
  });
}

// randomly choose one of the rules according to their weights
Interpreter.prototype.sampling = function(name, retry) {
  if (!this.rules[name]) {
    throw new Error(
      sprintf("ReferenceError: '%s' is not defined. As reported by eisenscript interpreter.", name),
      sprintf("%s.js", this.name)
    );
  }

  // sum weights of each rules
  var sum = 0;
  this.rules[name].forEach(function(rule) {
    rule.weight = rule.weight || 1;
    sum += rule.weight;
  });

  // choosing...
  var rand = this.mt.next() * sum;
  var chosen;
  for (var i = 0; i < this.rules[name].length; i++) {
    var rule = this.rules[name][i];
    if (rule.weight - rand < 0) {
      rand -= rule.weight
      continue;
    }
    chosen = rule;
    break;
  }

  // if rule could not be selected, interpreter tries to choose until 3 times
  if (!chosen) {
    retry = retry || 0;
    if (retry < 3) return this.sampling(name, ++retry);
    // if achieve max retry count
    return false;
  }

  // if achieved maxdepth
  chosen.depth = (chosen.depth || 0) + 1;
  if (chosen.maxdepth && chosen.maxdepth < chosen.depth) {
    if (chosen.alternate) return this.sampling(chosen.alternate);
    if (this.depth < chosen.maxdepth) return chosen;
    return false;
  }

  // the rule randomly chosen
  return chosen;
}
<commit_msg>fix problem depth around recursively rule calling<commit_after>// module generate object code from ast
var Interpreter = function() {
  this.name = 'Interpreter';
  this.objects = [];
  this.define = [];
  this.rules = {};
  this.computed = [];
  this.maxdepth = 100;
  this.depth = 0;
  this.maxobjects = 1000;
  this.objectnum = 0;
  this.minsize = .2;
  this.maxsize = 1.0;
  this.seed = 'initial'; // integer or 'initial'
  this.stack = [];
  this.curr = {};
  this.curr.matrix = new Matrix4();
  this.curr.hex = Color('#ff0000');
  this.curr.hsv = _.extend(Color({ hue: 0, saturation: 1, value: 1 }), { computed: false });
  this.curr.blend = { color: null, strength: 0, computed: false };
  this.curr.alpha = 1;
  this.mt = new MersenneTwister();
}

// termination criteria
Interpreter.prototype.terminated = function() {
  if (this.objectnum > this.maxobjects) return true;
  if (this.depth >= this.maxdepth) return true;
  return false;
}

// stack current transformation state
Interpreter.prototype.pushState = function() {
  this.depth++;
  this.stack.push({
    matrix: this.curr.matrix.clone(),
    hex: this.curr.hex.clone(),
    hsv: this.curr.hsv.clone(),
    blend: _.extend({}, this.curr.blend),
    alpha: this.curr.alpha
  });
  return this;
}

// pull the parent transformation state
Interpreter.prototype.popState = function() {
  if (this.stack.length > 0) {
    this.curr = this.stack.pop();
    this.depth--;
  }
  return this;
}

Interpreter.prototype.translate = function(x, y, z) {
  this.curr.matrix.translate({
    x: x,
    y: y,
    z: z
  });
  return this;
}

Interpreter.prototype.rotateX = function(angle) {
  this.curr.matrix.rotateX(angle);
  return this;
}

Interpreter.prototype.rotateY = function(angle) {
  this.curr.matrix.rotateY(angle);
  return this;
}

Interpreter.prototype.rotateZ = function(angle) {
  this.curr.matrix.rotateZ(angle);
  return this;
}

Interpreter.prototype.scale = function(x, y, z) {
  this.curr.matrix.scale({
    x: x,
    y: y,
    z: z
  });
  return this;
}

// make 3x3 rotation matrix to 4x4 matrix
// test: { m 1 0 0 0 .53 -.85 0 .85 .53 } box
Interpreter.prototype.matrix = function(v) {
  this.curr.matrix.set(
    v[0], v[1], v[2], 0,
    v[3], v[4], v[5], 0,
    v[6], v[7], v[8], 0,
       0,    0,    0, 1
  );
  return this;
}

Interpreter.prototype.random16 = function() {
  var rand = this.mt.next() * 0xffffff;
  return Math.floor(rand).toString(16);
}

Interpreter.prototype.randomColor = function() {
  return sprintf('#%s', this.random16());
}

Interpreter.prototype.setColor = function(color) {
  if (color === 'random') color = this.randomColor();
  this.curr.hex = Color(color);
  return this;
}

Interpreter.prototype.setHue = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.hue += v % 360;
  return this;
}

Interpreter.prototype.setSaturation = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.saturation = clamp(this.curr.hsv.saturation * v, 0, 1);
  return this;
}

Interpreter.prototype.setBrightness = function(v) {
  this.curr.hsv.computed = true;
  this.curr.hsv.value = clamp(this.curr.hsv.value * v, 0, 1);
  return this;
}

Interpreter.prototype.setBlend = function(color, strength) {
  this.curr.blend.computed = true;
  this.curr.blend.color = color;
  this.curr.blend.strength = this.curr.blend.strength + clamp(strength, 0, 1);
  return this;
}

// execute eisenscript
Interpreter.prototype.generate = function(ast) {
  // rewriting ast
  var that = this;
  ast.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Rule: that.rewriteRule(statement); break;
    }
  });

  // pull the defines
  ast.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Define: that.define.push(statement); break;
      case Symbol.Set: that.define.push(statement); break;
      case Symbol.Statement: if (statement.computed) that.computed.push(statement); break;
    }
  });

  // creating intermediate code...
  // promise
  this.define.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Set:
        switch (statement.key) {
          case Symbol.Maxdepth: that.maxdepth = statement.value; break;
          case Symbol.Maxobjects: that.maxobjects = statement.value; break;
          case Symbol.Minsize: that.minsize = statement.value; break;
          case Symbol.Maxsize: that.maxsize = statement.value; break;
          case Symbol.Seed: that.seed = statement.value; break;
        }
        break;
      case Symbol.Define:
        // not implemented, but I don't think the definition statement is need.
        break;
    }
  });

  // initial value is randomised chosen integer
  this.mt.setSeed(this.seed === 'initial' ? randInt(0, 65535) : this.seed);

  // pull the statement of system environment
  this.define.forEach(function(statement) {
    switch (statement.type) {
      case Symbol.Set:
        if (statement.key === Symbol.Background) that.generateBackground(statement);
        break;
    }
  });

  // execute main
  this.parseStatements(this.computed);

  // return the intermediate code
  return {
    maxdepth: this.maxdepth,
    maxobjects: this.maxobjects,
    minsize: this.minsize,
    maxsize: this.maxsize,
    seed: this.seed,
    objects: this.objects,
    num: this.objects.length
  }
}

// rewrite subtree related to rule statement
Interpreter.prototype.rewriteRule = function(rule) {
  rule.params.forEach(function(param) {
    if (param.type === Symbol.Modifier) {
      switch (param.key) {
        case Symbol.Weight: rule.weight = param.value; break;
        case Symbol.Maxdepth: rule.maxdepth = param.value; rule.alternate = param.alternate; break;
      }
    }
  });
  if (!this.rules[rule.id]) this.rules[rule.id] = [];
  this.rules[rule.id].push(rule);
  return this;
}

// execute statements
Interpreter.prototype.parseStatements = function(statements) {
  var i = 0, len = statements.length;
  while (i < len) {
    if (this.terminated()) break;
    this.parseStatement(statements[i], 0);
    i++;
  }
  return this;
}

// execute a statement
Interpreter.prototype.parseStatement = function(statement, index) {
  // parse transformation expression
  var expr = statement.exprs[index];
  if (expr) {
    this.pushState();
    for (var i = 0; i < expr.left; i++) {
      if (this.terminated()) break;
      this.parseTransformStatement(expr.right);
      // if statement.exprs[index + 1] is undefined, it would break the transformation loops.
      this.parseStatement(statement, index + 1);
    }
    this.popState();
    return this;
  }

  // if not primitive, call rule and parse next transformation loops
  if (_.values(Primitive).indexOf(statement.id) === -1) {
    var rule = this.sampling(statement.id);
    if (rule) this.parseStatements(rule.body);
    return this;
  }

  // achieve the end of nested transformation loops
  this.generatePrimitive(statement);
  return this;
}

// break down transformation set
Interpreter.prototype.parseTransformStatement = function(transform) {
  var i = 0, len = transform.properties.length;
  while (i < len) {
    this.parseTransform(transform.properties[i]);
    i++;
  }
  return this;
}

// parse transformation property
Interpreter.prototype.parseTransform = function(property) {
  var v = property.value;
  switch (property.key) {
    case Symbol.XShift: this.translate(v, 0, 0); break;
    case Symbol.YShift: this.translate(0, v, 0); break;
    case Symbol.ZShift: this.translate(0, 0, v); break;
    case Symbol.RotateX: this.rotateX(degToRad(v)); break;
    case Symbol.RotateY: this.rotateY(degToRad(v)); break;
    case Symbol.RotateZ: this.rotateZ(degToRad(v)); break;
    case Symbol.Size: this.scale(v.x, v.y, v.z); break;
    case Symbol.Matrix: this.matrix(v); break;
    case Symbol.Color: this.setColor(v); break;
    case Symbol.Hue: this.setHue(v); break;
    case Symbol.Saturation: this.setSaturation(v); break;
    case Symbol.Brightness: this.setBrightness(v); break;
    case Symbol.Blend: this.setBlend(property.color, property.strength); break;
    case Symbol.Alpha: this.curr.alpha *= v; break;
  }
  return this;
}

// create primitive object and stack it as intermediate code for renderer
Interpreter.prototype.generatePrimitive = function(statement) {
  // if achieved maxobjects
  this.objectnum++;
  if (this.terminated()) return;

  // blend the current color with the specified color
  if (this.curr.blend.computed) {
    this.curr.hex = this.curr.hex.toHSV();
    var blend = Color(this.curr.blend.color).toHSV();
    this.curr.hex.hue += (blend.hue - this.curr.hex.hue) * this.curr.blend.strength / 6;
    this.curr.hex.hue %= 360;
  }

  // primitive object
  this.objects.push({
    type: Type.Primitive,
    name: statement.id,
    matrix: this.curr.matrix.clone(),
    color: this.curr.hsv.computed ? this.curr.hex.blend(this.curr.hsv, 1).toCSS() : this.curr.hex.toCSS(),
    opacity: this.curr.alpha,
    depth: this.depth
  });
}

// create background object code and stack it as intermediate code for renderer
Interpreter.prototype.generateBackground = function(statement) {
  this.objects.push({
    type: Type.Background,
    color: statement.value
  });
}

// randomly choose one of the rules according to their weights
Interpreter.prototype.sampling = function(name, retry) {
  if (!this.rules[name]) {
    throw new Error(
      sprintf("ReferenceError: '%s' is not defined. As reported by eisenscript interpreter.", name),
      sprintf("%s.js", this.name)
    );
  }

  // sum weights of each rules
  var sum = 0;
  this.rules[name].forEach(function(rule) {
    rule.weight = rule.weight || 1;
    sum += rule.weight;
  });

  // choosing...
  var rand = this.mt.next() * sum;
  var chosen;
  for (var i = 0; i < this.rules[name].length; i++) {
    var rule = this.rules[name][i];
    if (rule.weight - rand < 0) {
      rand -= rule.weight
      continue;
    }
    chosen = rule;
    break;
  }

  // if rule could not be selected, interpreter tries to choose until 3 times
  if (!chosen) {
    retry = retry || 0;
    if (retry < 3) return this.sampling(name, ++retry);
    // if achieve max retry count
    return false;
  }

  // if achieved maxdepth
  chosen.depth = (chosen.depth || 0) + 1;
  if (chosen.maxdepth && chosen.maxdepth < chosen.depth) {
    if (chosen.alternate) return this.sampling(chosen.alternate);
    if (this.depth < chosen.maxdepth) return chosen;
    chosen.depth = 0;
    return false;
  }

  // the rule randomly chosen
  return chosen;
}
<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name Raster
 *
 * @class The Raster item represents an image in a Paper.js project.
 *
 * @extends PlacedItem
 */
var Raster = this.Raster = PlacedItem.extend(/** @lends Raster# */{
	// Raster doesn't make the distinction between the different bounds,
	// so use the same name for all of them
	_boundsType: 'bounds',

	// TODO: Implement url / type, width, height.
	// TODO: Have PlacedSymbol & Raster inherit from a shared class?
	// DOCS: Document Raster constructor.
	/**
	 * Creates a new raster item and places it in the active layer.
	 *
	 * @param {HTMLImageElement|Canvas|string} [object]
	 */
	initialize: function(object) {
		this.base();
		if (object.getContext) {
			this.setCanvas(object);
		} else {
/*#*/ if (options.browser) {
			// If it's a string, get the element with this id first.
			if (typeof object === 'string')
				object = document.getElementById(object);
/*#*/ } else if (options.server) {
			// If we're running on the server and it's a string,
			// load it from disk:
			if (typeof object === 'string') {
				// TODO: load images async
				var data = fs.readFileSync(object);
				object = new Image();
				object.src = data;
			}
/*#*/ } // options.server
			this.setImage(object);
		}
	},

	clone: function() {
		var image = this._image;
		if (!image) {
			// If the Raster contains a Canvas object, we need to create
			// a new one and draw this raster's canvas on it.
			image = CanvasProvider.getCanvas(this._size);
			image.getContext('2d').drawImage(this._canvas, 0, 0);
		}
		var copy = new Raster(image);
		return this._clone(copy);
	},

	/**
	 * The size of the raster in pixels.
	 *
	 * @type Size
	 * @bean
	 */
	getSize: function() {
		return this._size;
	},

	setSize: function() {
		var size = Size.read(arguments),
			// Get reference to image before changing canvas
			image = this.getImage();
		// Setting canvas internally sets _size
		this.setCanvas(CanvasProvider.getCanvas(size));
		// Draw image back onto new canvas
		this.getContext(true).drawImage(image, 0, 0, size.width, size.height);
	},

	/**
	 * The width of the raster in pixels.
	 *
	 * @type Number
	 * @bean
	 */
	getWidth: function() {
		return this._size.width;
	},

	/**
	 * The height of the raster in pixels.
	 *
	 * @type Number
	 * @bean
	 */
	getHeight: function() {
		return this._size.height;
	},

	/**
	 * Pixels per inch of the raster at its current size.
	 *
	 * @type Size
	 * @bean
	 */
	getPpi: function() {
		var matrix = this._matrix,
			orig = new Point(0, 0).transform(matrix),
			u = new Point(1, 0).transform(matrix).subtract(orig),
			v = new Point(0, 1).transform(matrix).subtract(orig);
		return Size.create(
			72 / u.getLength(),
			72 / v.getLength()
		);
	},

	/**
	 * The Canvas 2d drawing context of the raster.
	 *
	 * @type Context
	 * @bean
	 */
	getContext: function() {
		if (!this._context)
			this._context = this.getCanvas().getContext('2d');
		// Support a hidden parameter that indicates if the context will be used
		// to modify the Raster object. We can notify such changes ahead since
		// they are only used afterwards for redrawing.
		if (arguments[0])
			this._changed(Change.PIXELS);
		return this._context;
	},

	setContext: function(context) {
		this._context = context;
	},

	getCanvas: function() {
		if (!this._canvas) {
			this._canvas = CanvasProvider.getCanvas(this._size);
			if (this._image)
				this.getContext(true).drawImage(this._image, 0, 0);
		}
		return this._canvas;
	},

	setCanvas: function(canvas) {
		if (this._canvas)
			CanvasProvider.returnCanvas(this._canvas);
		this._canvas = canvas;
		this._size = Size.create(canvas.width, canvas.height);
		this._image = null;
		this._context = null;
		this._changed(Change.GEOMETRY);
	},

	/**
	 * The HTMLImageElement or Canvas of the raster.
	 *
	 * @type HTMLImageElement|Canvas
	 * @bean
	 */
	getImage: function() {
		return this._image || this.getCanvas();
	},

	// TODO: Support string id of image element.
	setImage: function(image) {
		if (this._canvas)
			CanvasProvider.returnCanvas(this._canvas);
		this._image = image;
/*#*/ if (options.browser) {
		this._size = Size.create(image.naturalWidth, image.naturalHeight);
/*#*/ } else if (options.server) {
		this._size = Size.create(image.width, image.height);
/*#*/ } // options.server
		this._canvas = null;
		this._context = null;
		this._changed(Change.GEOMETRY);
	},

	// DOCS: document Raster#getSubImage
	/**
	 * @param {Rectangle} rect the boundaries of the sub image in pixel
	 * coordinates
	 *
	 * @return {Canvas}
	 */
	getSubImage: function(rect) {
		rect = Rectangle.read(arguments);
		var canvas = CanvasProvider.getCanvas(rect.getSize());
		canvas.getContext('2d').drawImage(this.getCanvas(), rect.x, rect.y,
				canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
		return canvas;
	},

	/**
	 * Draws an image on the raster.
	 *
	 * @param {HTMLImageELement|Canvas} image
	 * @param {Point} point the offset of the image as a point in pixel
	 * coordinates
	 */
	drawImage: function(image, point) {
		point = Point.read(arguments, 1);
		this.getContext(true).drawImage(image, point.x, point.y);
	},

	/**
	 * Calculates the average color of the image within the given path,
	 * rectangle or point. This can be used for creating raster image
	 * effects.
	 *
	 * @param {Path|Rectangle|Point} object
	 * @return {RgbColor} the average color contained in the area covered by the
	 * specified path, rectangle or point.
	 */
	getAverageColor: function(object) {
		if (!object)
			object = this.getBounds();
		var bounds, path;
		if (object instanceof PathItem) {
			// TODO: What if the path is smaller than 1 px?
			// TODO: How about rounding of bounds.size?
			path = object;
			bounds = object.getBounds();
		} else if (object.width) {
			bounds = new Rectangle(object);
		} else if (object.x) {
			// Create a rectangle of 1px size around the specified coordinates
			bounds = Rectangle.create(object.x - 0.5, object.y - 0.5, 1, 1);
		}
		// Use a sample size of max 32 x 32 pixels, into which the path is
		// scaled as a clipping path, and then the actual image is drawn in and
		// sampled.
		var sampleSize = 32,
			width = Math.min(bounds.width, sampleSize),
			height = Math.min(bounds.height, sampleSize);
		// Reuse the same sample context for speed. Memory consumption is low
		// since it's only 32 x 32 pixels.
		var ctx = Raster._sampleContext;
		if (!ctx) {
			ctx = Raster._sampleContext = CanvasProvider.getCanvas(
					sampleSize.clone()).getContext('2d');
		} else {
			// Clear the sample canvas:
			ctx.clearRect(0, 0, sampleSize, sampleSize);
		}
		ctx.save();
		// Scale the context so that the bounds ends up at the given sample size
		ctx.scale(width / bounds.width, height / bounds.height);
		ctx.translate(-bounds.x, -bounds.y);
		// If a path was passed, draw it as a clipping mask:
		if (path)
			path.draw(ctx, { clip: true });
		// Now draw the image clipped into it.
		this._matrix.applyToContext(ctx);
		ctx.drawImage(this._canvas || this._image,
				-this._size.width / 2, -this._size.height / 2);
		ctx.restore();
		// Get pixel data from the context and calculate the average color value
		// from it, taking alpha into account.
		var pixels = ctx.getImageData(0.5, 0.5, Math.ceil(width),
				Math.ceil(height)).data,
			channels = [0, 0, 0],
			total = 0;
		for (var i = 0, l = pixels.length; i < l; i += 4) {
			var alpha = pixels[i + 3];
			total += alpha;
			alpha /= 255;
			channels[0] += pixels[i] * alpha;
			channels[1] += pixels[i + 1] * alpha;
			channels[2] += pixels[i + 2] * alpha;
		}
		for (var i = 0; i < 3; i++)
			channels[i] /= total;
		return total ? Color.read(channels) : null;
	},

	/**
	 * {@grouptitle Pixels}
	 * Gets the color of a pixel in the raster.
	 *
	 * @name Raster#getPixel
	 * @function
	 * @param x the x offset of the pixel in pixel coordinates
	 * @param y the y offset of the pixel in pixel coordinates
	 * @return {RgbColor} the color of the pixel
	 */
	/**
	 * Gets the color of a pixel in the raster.
	 *
	 * @name Raster#getPixel
	 * @function
	 * @param point the offset of the pixel as a point in pixel coordinates
	 * @return {RgbColor} the color of the pixel
	 */
	getPixel: function(point) {
		point = Point.read(arguments);
		var pixels = this.getContext().getImageData(point.x, point.y, 1, 1).data,
			channels = new Array(4);
		for (var i = 0; i < 4; i++)
			channels[i] = pixels[i] / 255;
		return RgbColor.read(channels);
	},

	/**
	 * Sets the color of the specified pixel to the specified color.
	 *
	 * @name Raster#setPixel
	 * @function
	 * @param x the x offset of the pixel in pixel coordinates
	 * @param y the y offset of the pixel in pixel coordinates
	 * @param color the color that the pixel will be set to
	 */
	/**
	 * Sets the color of the specified pixel to the specified color.
	 *
	 * @name Raster#setPixel
	 * @function
	 * @param point the offset of the pixel as a point in pixel coordinates
	 * @param color the color that the pixel will be set to
	 */
	setPixel: function(point, color) {
		var hasPoint = arguments.length == 2;
		point = Point.read(arguments, 0, hasPoint ? 1 : 2);
		color = Color.read(arguments, hasPoint ? 1 : 2);
		var ctx = this.getContext(true),
			imageData = ctx.createImageData(1, 1),
			alpha = color.getAlpha();
		imageData.data[0] = color.getRed() * 255;
		imageData.data[1] = color.getGreen() * 255;
		imageData.data[2] = color.getBlue() * 255;
		imageData.data[3] = alpha != null ? alpha * 255 : 255;
		ctx.putImageData(imageData, point.x, point.y);
	},

	// DOCS: document Raster#createData
	/**
	 * {@grouptitle Image Data}
	 * @param {Size} size
	 * @return {ImageData}
	 */
	createData: function(size) {
		size = Size.read(arguments);
		return this.getContext().createImageData(size.width, size.height);
	},

	// TODO: Rename to #get/setImageData, as it will conflict with Item#getData
	// DOCS: document Raster#getData
	/**
	 * @param {Rectangle} rect
	 * @return {ImageData}
	 */
	getData: function(rect) {
		rect = Rectangle.read(arguments);
		if (rect.isEmpty())
			rect = new Rectangle(this.getSize());
		return this.getContext().getImageData(rect.x, rect.y,
				rect.width, rect.height);
	},

	// DOCS: document Raster#setData
	/**
	 * @param {ImageData} data
	 * @param {Point} point
	 * @return {ImageData}
	 */
	setData: function(data, point) {
		point = Point.read(arguments, 1);
		this.getContext(true).putImageData(data, point.x, point.y);
	},

	_getBounds: function(type, matrix) {
		var rect = new Rectangle(this._size).setCenter(0, 0);
		return matrix ? matrix._transformBounds(rect) : rect;
	},

	_hitTest: function(point, options) {
		point = this._matrix._inverseTransform(point);
		if (point.isInside(new Rectangle(this._size).setCenter(0, 0))) {
			var that = this;
			return new HitResult('pixel', that, {
				offset: point.add(that._size.divide(2)).round(),
				// Becomes HitResult#color
				getColor: function() {
					return that.getPixel(this.offset);
				}
			});
		}
	},

	draw: function(ctx, param) {
		if (param.selection) {
			var bounds = new Rectangle(this._size).setCenter(0, 0);
			Item.drawSelectedBounds(bounds, ctx, this._matrix);
		} else {
			ctx.drawImage(this._canvas || this._image,
					-this._size.width / 2, -this._size.height / 2);
		}
	}
});
<commit_msg>Use correct flags in _changed() call.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name Raster
 *
 * @class The Raster item represents an image in a Paper.js project.
 *
 * @extends PlacedItem
 */
var Raster = this.Raster = PlacedItem.extend(/** @lends Raster# */{
	// Raster doesn't make the distinction between the different bounds,
	// so use the same name for all of them
	_boundsType: 'bounds',

	// TODO: Implement url / type, width, height.
	// TODO: Have PlacedSymbol & Raster inherit from a shared class?
	// DOCS: Document Raster constructor.
	/**
	 * Creates a new raster item and places it in the active layer.
	 *
	 * @param {HTMLImageElement|Canvas|string} [object]
	 */
	initialize: function(object) {
		this.base();
		if (object.getContext) {
			this.setCanvas(object);
		} else {
/*#*/ if (options.browser) {
			// If it's a string, get the element with this id first.
			if (typeof object === 'string')
				object = document.getElementById(object);
/*#*/ } else if (options.server) {
			// If we're running on the server and it's a string,
			// load it from disk:
			if (typeof object === 'string') {
				// TODO: load images async
				var data = fs.readFileSync(object);
				object = new Image();
				object.src = data;
			}
/*#*/ } // options.server
			this.setImage(object);
		}
	},

	clone: function() {
		var image = this._image;
		if (!image) {
			// If the Raster contains a Canvas object, we need to create
			// a new one and draw this raster's canvas on it.
			image = CanvasProvider.getCanvas(this._size);
			image.getContext('2d').drawImage(this._canvas, 0, 0);
		}
		var copy = new Raster(image);
		return this._clone(copy);
	},

	/**
	 * The size of the raster in pixels.
	 *
	 * @type Size
	 * @bean
	 */
	getSize: function() {
		return this._size;
	},

	setSize: function() {
		var size = Size.read(arguments),
			// Get reference to image before changing canvas
			image = this.getImage();
		// Setting canvas internally sets _size
		this.setCanvas(CanvasProvider.getCanvas(size));
		// Draw image back onto new canvas
		this.getContext(true).drawImage(image, 0, 0, size.width, size.height);
	},

	/**
	 * The width of the raster in pixels.
	 *
	 * @type Number
	 * @bean
	 */
	getWidth: function() {
		return this._size.width;
	},

	/**
	 * The height of the raster in pixels.
	 *
	 * @type Number
	 * @bean
	 */
	getHeight: function() {
		return this._size.height;
	},

	/**
	 * Pixels per inch of the raster at its current size.
	 *
	 * @type Size
	 * @bean
	 */
	getPpi: function() {
		var matrix = this._matrix,
			orig = new Point(0, 0).transform(matrix),
			u = new Point(1, 0).transform(matrix).subtract(orig),
			v = new Point(0, 1).transform(matrix).subtract(orig);
		return Size.create(
			72 / u.getLength(),
			72 / v.getLength()
		);
	},

	/**
	 * The Canvas 2d drawing context of the raster.
	 *
	 * @type Context
	 * @bean
	 */
	getContext: function(/* notifyChange */) {
		if (!this._context)
			this._context = this.getCanvas().getContext('2d');
		// Support a hidden parameter that indicates if the context will be used
		// to modify the Raster object. We can notify such changes ahead since
		// they are only used afterwards for redrawing.
		if (arguments[0])
			this._changed(Change.PIXELS);
		return this._context;
	},

	setContext: function(context) {
		this._context = context;
	},

	getCanvas: function() {
		if (!this._canvas) {
			this._canvas = CanvasProvider.getCanvas(this._size);
			if (this._image)
				this.getContext(true).drawImage(this._image, 0, 0);
		}
		return this._canvas;
	},

	setCanvas: function(canvas) {
		if (this._canvas)
			CanvasProvider.returnCanvas(this._canvas);
		this._canvas = canvas;
		this._size = Size.create(canvas.width, canvas.height);
		this._image = null;
		this._context = null;
		this._changed(Change.GEOMETRY | Change.PIXELS);
	},

	/**
	 * The HTMLImageElement or Canvas of the raster.
	 *
	 * @type HTMLImageElement|Canvas
	 * @bean
	 */
	getImage: function() {
		return this._image || this.getCanvas();
	},

	// TODO: Support string id of image element.
	setImage: function(image) {
		if (this._canvas)
			CanvasProvider.returnCanvas(this._canvas);
		this._image = image;
/*#*/ if (options.browser) {
		this._size = Size.create(image.naturalWidth, image.naturalHeight);
/*#*/ } else if (options.server) {
		this._size = Size.create(image.width, image.height);
/*#*/ } // options.server
		this._canvas = null;
		this._context = null;
		this._changed(Change.GEOMETRY);
	},

	// DOCS: document Raster#getSubImage
	/**
	 * @param {Rectangle} rect the boundaries of the sub image in pixel
	 * coordinates
	 *
	 * @return {Canvas}
	 */
	getSubImage: function(rect) {
		rect = Rectangle.read(arguments);
		var canvas = CanvasProvider.getCanvas(rect.getSize());
		canvas.getContext('2d').drawImage(this.getCanvas(), rect.x, rect.y,
				canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
		return canvas;
	},

	/**
	 * Draws an image on the raster.
	 *
	 * @param {HTMLImageELement|Canvas} image
	 * @param {Point} point the offset of the image as a point in pixel
	 * coordinates
	 */
	drawImage: function(image, point) {
		point = Point.read(arguments, 1);
		this.getContext(true).drawImage(image, point.x, point.y);
	},

	/**
	 * Calculates the average color of the image within the given path,
	 * rectangle or point. This can be used for creating raster image
	 * effects.
	 *
	 * @param {Path|Rectangle|Point} object
	 * @return {RgbColor} the average color contained in the area covered by the
	 * specified path, rectangle or point.
	 */
	getAverageColor: function(object) {
		if (!object)
			object = this.getBounds();
		var bounds, path;
		if (object instanceof PathItem) {
			// TODO: What if the path is smaller than 1 px?
			// TODO: How about rounding of bounds.size?
			path = object;
			bounds = object.getBounds();
		} else if (object.width) {
			bounds = new Rectangle(object);
		} else if (object.x) {
			// Create a rectangle of 1px size around the specified coordinates
			bounds = Rectangle.create(object.x - 0.5, object.y - 0.5, 1, 1);
		}
		// Use a sample size of max 32 x 32 pixels, into which the path is
		// scaled as a clipping path, and then the actual image is drawn in and
		// sampled.
		var sampleSize = 32,
			width = Math.min(bounds.width, sampleSize),
			height = Math.min(bounds.height, sampleSize);
		// Reuse the same sample context for speed. Memory consumption is low
		// since it's only 32 x 32 pixels.
		var ctx = Raster._sampleContext;
		if (!ctx) {
			ctx = Raster._sampleContext = CanvasProvider.getCanvas(
					sampleSize.clone()).getContext('2d');
		} else {
			// Clear the sample canvas:
			ctx.clearRect(0, 0, sampleSize, sampleSize);
		}
		ctx.save();
		// Scale the context so that the bounds ends up at the given sample size
		ctx.scale(width / bounds.width, height / bounds.height);
		ctx.translate(-bounds.x, -bounds.y);
		// If a path was passed, draw it as a clipping mask:
		if (path)
			path.draw(ctx, { clip: true });
		// Now draw the image clipped into it.
		this._matrix.applyToContext(ctx);
		ctx.drawImage(this._canvas || this._image,
				-this._size.width / 2, -this._size.height / 2);
		ctx.restore();
		// Get pixel data from the context and calculate the average color value
		// from it, taking alpha into account.
		var pixels = ctx.getImageData(0.5, 0.5, Math.ceil(width),
				Math.ceil(height)).data,
			channels = [0, 0, 0],
			total = 0;
		for (var i = 0, l = pixels.length; i < l; i += 4) {
			var alpha = pixels[i + 3];
			total += alpha;
			alpha /= 255;
			channels[0] += pixels[i] * alpha;
			channels[1] += pixels[i + 1] * alpha;
			channels[2] += pixels[i + 2] * alpha;
		}
		for (var i = 0; i < 3; i++)
			channels[i] /= total;
		return total ? Color.read(channels) : null;
	},

	/**
	 * {@grouptitle Pixels}
	 * Gets the color of a pixel in the raster.
	 *
	 * @name Raster#getPixel
	 * @function
	 * @param x the x offset of the pixel in pixel coordinates
	 * @param y the y offset of the pixel in pixel coordinates
	 * @return {RgbColor} the color of the pixel
	 */
	/**
	 * Gets the color of a pixel in the raster.
	 *
	 * @name Raster#getPixel
	 * @function
	 * @param point the offset of the pixel as a point in pixel coordinates
	 * @return {RgbColor} the color of the pixel
	 */
	getPixel: function(point) {
		point = Point.read(arguments);
		var pixels = this.getContext().getImageData(point.x, point.y, 1, 1).data,
			channels = new Array(4);
		for (var i = 0; i < 4; i++)
			channels[i] = pixels[i] / 255;
		return RgbColor.read(channels);
	},

	/**
	 * Sets the color of the specified pixel to the specified color.
	 *
	 * @name Raster#setPixel
	 * @function
	 * @param x the x offset of the pixel in pixel coordinates
	 * @param y the y offset of the pixel in pixel coordinates
	 * @param color the color that the pixel will be set to
	 */
	/**
	 * Sets the color of the specified pixel to the specified color.
	 *
	 * @name Raster#setPixel
	 * @function
	 * @param point the offset of the pixel as a point in pixel coordinates
	 * @param color the color that the pixel will be set to
	 */
	setPixel: function(point, color) {
		var hasPoint = arguments.length == 2;
		point = Point.read(arguments, 0, hasPoint ? 1 : 2);
		color = Color.read(arguments, hasPoint ? 1 : 2);
		var ctx = this.getContext(true),
			imageData = ctx.createImageData(1, 1),
			alpha = color.getAlpha();
		imageData.data[0] = color.getRed() * 255;
		imageData.data[1] = color.getGreen() * 255;
		imageData.data[2] = color.getBlue() * 255;
		imageData.data[3] = alpha != null ? alpha * 255 : 255;
		ctx.putImageData(imageData, point.x, point.y);
	},

	// DOCS: document Raster#createData
	/**
	 * {@grouptitle Image Data}
	 * @param {Size} size
	 * @return {ImageData}
	 */
	createData: function(size) {
		size = Size.read(arguments);
		return this.getContext().createImageData(size.width, size.height);
	},

	// TODO: Rename to #get/setImageData, as it will conflict with Item#getData
	// DOCS: document Raster#getData
	/**
	 * @param {Rectangle} rect
	 * @return {ImageData}
	 */
	getData: function(rect) {
		rect = Rectangle.read(arguments);
		if (rect.isEmpty())
			rect = new Rectangle(this.getSize());
		return this.getContext().getImageData(rect.x, rect.y,
				rect.width, rect.height);
	},

	// DOCS: document Raster#setData
	/**
	 * @param {ImageData} data
	 * @param {Point} point
	 * @return {ImageData}
	 */
	setData: function(data, point) {
		point = Point.read(arguments, 1);
		this.getContext(true).putImageData(data, point.x, point.y);
	},

	_getBounds: function(type, matrix) {
		var rect = new Rectangle(this._size).setCenter(0, 0);
		return matrix ? matrix._transformBounds(rect) : rect;
	},

	_hitTest: function(point, options) {
		point = this._matrix._inverseTransform(point);
		if (point.isInside(new Rectangle(this._size).setCenter(0, 0))) {
			var that = this;
			return new HitResult('pixel', that, {
				offset: point.add(that._size.divide(2)).round(),
				// Becomes HitResult#color
				getColor: function() {
					return that.getPixel(this.offset);
				}
			});
		}
	},

	draw: function(ctx, param) {
		if (param.selection) {
			var bounds = new Rectangle(this._size).setCenter(0, 0);
			Item.drawSelectedBounds(bounds, ctx, this._matrix);
		} else {
			ctx.drawImage(this._canvas || this._image,
					-this._size.width / 2, -this._size.height / 2);
		}
	}
});
<|endoftext|>


<commit_before>/*
 * jQuery i18n plugin
 * @requires jQuery v1.1 or later
 *
 * See http://recursive-design.com/projects/jquery-i18n/
 *
 * Licensed under the MIT license:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Version: @VERSION (@DATE)
 */
 (function($) {
  /**
   * i18n provides a mechanism for translating strings using a jscript dictionary.
   *
   */

  /*
   * i18n property list
   */
  $.i18n = {

  	dict: null,

    /**
     * setDictionary()
     *
     * Initialises the dictionary.
     *
     * @param  property_list i18n_dict : The dictionary to use for translation.
     */
  	setDictionary: function(i18n_dict) {
  		this.dict = i18n_dict;
  	},

    /**
     * _()
     *
     * Looks the given string up in the dictionary and returns the translation if
     * one exists. If a translation is not found, returns the original word.
     *
     * @param  string str           : The string to translate.
     * @param  property_list params : params for using printf() on the string.
     *
     * @return string               : Translated word.
     */
  	_: function (str, params) {
  		var transl = str;
  		if (this.dict && this.dict[str]) {
  			transl = this.dict[str];
  		}
  		return this.printf(transl, params);
  	},

    /**
     * toEntity()
     *
     * Changes non-ASCII characters to entity representation.
     *
     * @param  string str    : The string to transform.
     *
     * @return string result : Original string with non-ASCII content converted to entities.
     */
  	toEntity: function (str) {
  		var result = '';
  		for (var i = 0; i < str.length; i++) {
  			if (str.charCodeAt(i) > 128) {
  				result += '&#' + str.charCodeAt(i) + ';';
  			} else {
  				result += str.charAt(i);
  			}
  		}

  		return result;
  	},

    /*
     * printf()
     *
     * Substitutes %s with parameters given in list. %%s is used to escape %s.
     *
     * @param  string S      : String to perform printf on.
     * @param  string L      : Array of arguments for printf.
     *
     * @return string result : Substituted string
     */
  	printf: function(S, L) {
  		if (!L) return S;

  		var nS     = '';
  		var search = /%(\d+)\$s/g;

  		// Replace %n1$ where n is a number.
  		var matches = search.exec(S);
  		while (matches) {
  			var index = parseInt(matches[1], 10) - 1;
  			S         = S.replace('%' + matches[1] + '\$s', (L[index]));
  		  matches   = search.exec(S);
  		}
  		var tS = S.split('%s');

  		if (tS.length > 1) {
  			for(var i = 0; i < L.length; i++) {
  				if (tS[i].length > 0 && tS[i].lastIndexOf('%') == tS[i].length-1 && i != L.length-1) {
  					tS[i] += 's' + tS.splice(i+1,1)[0];
  				}
  				nS += tS[i] + L[i];
  			}
  		}

  		return nS + tS[tS.length-1];
  	}

  };

  /*
   * _t()
   *
   * Allows you to translate a jQuery selector.
   *
   * eg $('h1')._t('some text')
   *
   * @param  string str           : The string to translate .
   * @param  property_list params : Params for using printf() on the string.
   *
   * @return element              : Chained and translated element(s).
  */
  $.fn._t = function(str, params) {
    return $(this).text($.i18n._(str, params));
  };

})(jQuery);
<commit_msg>Rename badly-named variables.<commit_after>/*
 * jQuery i18n plugin
 * @requires jQuery v1.1 or later
 *
 * See http://recursive-design.com/projects/jquery-i18n/
 *
 * Licensed under the MIT license:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Version: @VERSION (@DATE)
 */
 (function($) {
  /**
   * i18n provides a mechanism for translating strings using a jscript dictionary.
   *
   */

  /*
   * i18n property list
   */
  $.i18n = {

  	dict: null,

    /**
     * setDictionary()
     *
     * Initialises the dictionary.
     *
     * @param  property_list i18n_dict : The dictionary to use for translation.
     */
  	setDictionary: function(i18n_dict) {
  		this.dict = i18n_dict;
  	},

    /**
     * _()
     *
     * Looks the given string up in the dictionary and returns the translation if
     * one exists. If a translation is not found, returns the original word.
     *
     * @param  string str           : The string to translate.
     * @param  property_list params : params for using printf() on the string.
     *
     * @return string               : Translated word.
     */
  	_: function (str, params) {
  		var result = str;
  		if (this.dict && this.dict[str]) {
  			result = this.dict[str];
  		}

  		// Substitute any params.
  		return this.printf(result, params);
  	},

    /**
     * toEntity()
     *
     * Changes non-ASCII characters to entity representation.
     *
     * @param  string str    : The string to transform.
     *
     * @return string result : Original string with non-ASCII content converted to entities.
     */
  	toEntity: function (str) {
  		var result = '';
  		for (var i = 0; i < str.length; i++) {
  			if (str.charCodeAt(i) > 128) {
  				result += '&#' + str.charCodeAt(i) + ';';
  			} else {
  				result += str.charAt(i);
  			}
  		}

  		return result;
  	},

    /*
     * printf()
     *
     * Substitutes %s with parameters given in list. %%s is used to escape %s.
     *
     * @param  string str    : String to perform printf on.
     * @param  string args   : Array of arguments for printf.
     *
     * @return string result : Substituted string
     */
  	printf: function(str, args) {
  		if (!args) return str;

  		var result = '';
  		var search = /%(\d+)\$s/g;

  		// Replace %n1$ where n is a number.
  		var matches = search.exec(str);
  		while (matches) {
  			var index = parseInt(matches[1], 10) - 1;
  			str       = str.replace('%' + matches[1] + '\$s', (args[index]));
  		  matches   = search.exec(str);
  		}
  		var parts = str.split('%s');

  		if (parts.length > 1) {
  			for(var i = 0; i < args.length; i++) {
  				if (parts[i].length > 0 && parts[i].lastIndexOf('%') == (parts[i].length - 1) && i != (args.length - 1)) {
  					parts[i] += 's' + parts.splice(i + 1, 1)[0];
  				}
  				result += parts[i] + args[i];
  			}
  		}

  		return result + parts[parts.length-1];
  	}

  };

  /*
   * _t()
   *
   * Allows you to translate a jQuery selector.
   *
   * eg $('h1')._t('some text')
   *
   * @param  string str           : The string to translate .
   * @param  property_list params : Params for using printf() on the string.
   *
   * @return element              : Chained and translated element(s).
  */
  $.fn._t = function(str, params) {
    return $(this).text($.i18n._(str, params));
  };

})(jQuery);
<|endoftext|>


<commit_before>/*
 * Sidr
 * https://github.com/artberri/sidr
 *
 * Copyright (c) 2013 Alberto Varela
 * Licensed under the MIT license.
 */

;(function( $ ){

  var sidrMoving = false,
      sidrOpened = false;

  // Private methods
  var privateMethods = {
    // Check for valids urls
    // From : http://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-an-url
    isUrl: function (str) {
      var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
        '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
        '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
        '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
        '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
        '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
      if(!pattern.test(str)) {
        return false;
      } else {
        return true;
      }
    },
    // Loads the content into the menu bar
    loadContent: function($menu, content) {
      $menu.html(content);
    },
    // Add sidr prefixes
    addPrefix: function($element) {
      var elementId = $element.attr('id'),
          elementClass = $element.attr('class');

      if(typeof elementId === 'string' && '' !== elementId) {
        $element.attr('id', elementId.replace(/([A-Za-z0-9_.\-]+)/g, 'sidr-id-$1'));
      }
      if(typeof elementClass === 'string' && '' !== elementClass && 'sidr-inner' !== elementClass) {
        $element.attr('class', elementClass.replace(/([A-Za-z0-9_.\-]+)/g, 'sidr-class-$1'));
      }
      $element.removeAttr('style');
    },
    execute: function(action, name, callback) {
      // Check arguments
      if(typeof name === 'function') {
        callback = name;
        name = 'sidr';
      }
      else if(!name) {
        name = 'sidr';
      }

      // Declaring
      var $menu = $('#' + name),
          $body = $($menu.data('body')),
          $html = $('html'),
          menuWidth = $menu.outerWidth(true),
          speed = $menu.data('speed'),
          side = $menu.data('side'),
          bodyAnimation,
          menuAnimation,
          scrollTop;

      // Open Sidr
      if('open' === action || ('toogle' === action && !$menu.is(':visible'))) {
        // Check if we can open it
        if( $menu.is(':visible') || sidrMoving ) {
          return;
        }

        // If another menu opened close first
        if(sidrOpened !== false) {
          methods.close(sidrOpened, function() {
            methods.open(name);
          });

          return;
        }

        // Lock sidr
        sidrMoving = true;

        // Left or right?
        if(side === 'left') {
          bodyAnimation = {left: menuWidth + 'px'};
          menuAnimation = {left: '0px'};
        }
        else {
          bodyAnimation = {right: menuWidth + 'px'};
          menuAnimation = {right: '0px'};
        }

        // Prepare page
        scrollTop = $html.scrollTop();
        $html.css('overflow-x', 'hidden').scrollTop(scrollTop);

        // Open menu
        $body.css({
          width: $body.width(),
          position: 'absolute'
        }).animate(bodyAnimation, speed);
        $menu.css('display', 'block').animate(menuAnimation, speed, function() {
          sidrMoving = false;
          sidrOpened = name;
          // Callback
          if(typeof callback === 'function') {
            callback(name);
          }
        });
      }
      // Close Sidr
      else {
        // Check if we can close it
        if( !$menu.is(':visible') || sidrMoving ) {
          return;
        }

        // Lock sidr
        sidrMoving = true;

        // Right or left menu?
        if(side === 'left') {
          bodyAnimation = {left: 0};
          menuAnimation = {left: '-' + menuWidth + 'px'};
        }
        else {
          bodyAnimation = {right: 0};
          menuAnimation = {right: '-' + menuWidth + 'px'};
        }

        // Close menu
        scrollTop = $html.scrollTop();
        $html.removeAttr('style').scrollTop(scrollTop);
        $body.animate(bodyAnimation, speed);
        $menu.animate(menuAnimation, speed, function() {
          $menu.removeAttr('style');
          $body.removeAttr('style');
          $('html').removeAttr('style');
          sidrMoving = false;
          sidrOpened = false;
          // Callback
          if(typeof callback === 'function') {
            callback(name);
          }
        });
      }
    }
  };

  // Sidr public methods
  var methods = {
    open: function(name, callback) {
      privateMethods.execute('open', name, callback);
    },
    close: function(name, callback) {
      privateMethods.execute('close', name, callback);
    },
    toogle: function(name, callback) {
      privateMethods.execute('toogle', name, callback);
    }
  };

  $.sidr = function( method ) {

    if ( methods[method] ) {
      return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'function' ||  typeof method === 'string'  || ! method ) {
      return methods.toogle.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.sidr' );
    }

  };

  $.fn.sidr = function( options ) {

    var settings = $.extend( {
      name          : 'sidr', // Name for the 'sidr'
      speed         : 200,    // Accepts standard jQuery effects speeds (i.e. fast, normal or milliseconds)
      side          : 'left', // Accepts 'left' or 'right'
      source        : null,   // Override the source of the content.
      renaming      : true,   // The ids and classes will be prepended with a prefix when loading existent content
      body          : 'body'  // Page container selector,
    }, options);

    var name = settings.name,
        $sideMenu = $('#' + name);

    // If the side menu do not exist create it
    if( $sideMenu.length === 0 ) {
      $sideMenu = $('
')
        .attr('id', name)
        .appendTo($('body'));
    }

    // Adding styles and options
    $sideMenu
      .addClass('sidr')
      .addClass(settings.side)
      .data({
        speed          : settings.speed,
        side           : settings.side,
        body           : settings.body
      });

    // The menu content
    if(typeof settings.source === 'function') {
      var newContent = settings.source(name);
      privateMethods.loadContent($sideMenu, newContent);
    }
    else if(typeof settings.source === 'string' && privateMethods.isUrl(settings.source)) {
      $.get(settings.source, function(data) {
        privateMethods.loadContent($sideMenu, data);
      });
    }
    else if(typeof settings.source === 'string') {
      var htmlContent = '',
          selectors   = settings.source.split(',');

      $.each(selectors, function(index, element) {
        htmlContent += '
' + $(element).html() + '
';
      });

      // Renaming ids and classes
      if(settings.renaming) {
        var $htmlContent = $('
').html(htmlContent);
        $htmlContent.find('*').each(function(index, element) {
          var $element = $(element);
          privateMethods.addPrefix($element);
        });
        htmlContent = $htmlContent.html();
      }
      privateMethods.loadContent($sideMenu, htmlContent);
    }
    else if(settings.source !== null) {
      $.error('Invalid Sidr Source');
    }

    return this.each(function(){

      var $this = $(this),
          data = $this.data('sidr');

      // If the plugin hasn't been initialized yet
      if ( ! data ) {
        $this.data('sidr', name);
        $this.click(function(e) {
          e.preventDefault();
          methods.toogle(name);
        });
      }
    });
  };

})( jQuery );
<commit_msg>Added callbacks for onOpen and onClose<commit_after>/*
 * Sidr
 * https://github.com/artberri/sidr
 *
 * Copyright (c) 2013 Alberto Varela
 * Licensed under the MIT license.
 */

;(function( $ ){

  var sidrMoving = false,
      sidrOpened = false;

  // Private methods
  var privateMethods = {
    // Check for valids urls
    // From : http://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-an-url
    isUrl: function (str) {
      var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
        '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
        '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
        '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
        '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
        '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
      if(!pattern.test(str)) {
        return false;
      } else {
        return true;
      }
    },
    // Loads the content into the menu bar
    loadContent: function($menu, content) {
      $menu.html(content);
    },
    // Add sidr prefixes
    addPrefix: function($element) {
      var elementId = $element.attr('id'),
          elementClass = $element.attr('class');

      if(typeof elementId === 'string' && '' !== elementId) {
        $element.attr('id', elementId.replace(/([A-Za-z0-9_.\-]+)/g, 'sidr-id-$1'));
      }
      if(typeof elementClass === 'string' && '' !== elementClass && 'sidr-inner' !== elementClass) {
        $element.attr('class', elementClass.replace(/([A-Za-z0-9_.\-]+)/g, 'sidr-class-$1'));
      }
      $element.removeAttr('style');
    },
    execute: function(action, name, callback) {
      // Check arguments
      if(typeof name === 'function') {
        callback = name;
        name = 'sidr';
      }
      else if(!name) {
        name = 'sidr';
      }

      // Declaring
      var $menu = $('#' + name),
          $body = $($menu.data('body')),
          $html = $('html'),
          menuWidth = $menu.outerWidth(true),
          speed = $menu.data('speed'),
          side = $menu.data('side'),
          onOpen = $menu.data('onOpen'),
          onClose = $menu.data('onClose'),
          bodyAnimation,
          menuAnimation,
          scrollTop;

      // Open Sidr
      if('open' === action || ('toogle' === action && !$menu.is(':visible'))) {
        // Check if we can open it
        if( $menu.is(':visible') || sidrMoving ) {
          return;
        }

        // If another menu opened close first
        if(sidrOpened !== false) {
          methods.close(sidrOpened, function() {
            methods.open(name);
          });

          return;
        }

        // Lock sidr
        sidrMoving = true;

        // Left or right?
        if(side === 'left') {
          bodyAnimation = {left: menuWidth + 'px'};
          menuAnimation = {left: '0px'};
        }
        else {
          bodyAnimation = {right: menuWidth + 'px'};
          menuAnimation = {right: '0px'};
        }

        // Prepare page
        scrollTop = $html.scrollTop();
        $html.css('overflow-x', 'hidden').scrollTop(scrollTop);

        // Open menu
        $body.css({
          width: $body.width(),
          position: 'absolute'
        }).animate(bodyAnimation, speed);
        $menu.css('display', 'block').animate(menuAnimation, speed, function() {
          sidrMoving = false;
          sidrOpened = name;
          // Callback
          if(typeof callback === 'function') {
            callback(name);
          }
        });

        // onOpen callback
        onOpen();
      }
      // Close Sidr
      else {
        // Check if we can close it
        if( !$menu.is(':visible') || sidrMoving ) {
          return;
        }

        // Lock sidr
        sidrMoving = true;

        // Right or left menu?
        if(side === 'left') {
          bodyAnimation = {left: 0};
          menuAnimation = {left: '-' + menuWidth + 'px'};
        }
        else {
          bodyAnimation = {right: 0};
          menuAnimation = {right: '-' + menuWidth + 'px'};
        }

        // Close menu
        scrollTop = $html.scrollTop();
        $html.removeAttr('style').scrollTop(scrollTop);
        $body.animate(bodyAnimation, speed);
        $menu.animate(menuAnimation, speed, function() {
          $menu.removeAttr('style');
          $body.removeAttr('style');
          $('html').removeAttr('style');
          sidrMoving = false;
          sidrOpened = false;
          // Callback
          if(typeof callback === 'function') {
            callback(name);
          }
        });

        // onClose callback
        onClose();
      }
    }
  };

  // Sidr public methods
  var methods = {
    open: function(name, callback) {
      privateMethods.execute('open', name, callback);
    },
    close: function(name, callback) {
      privateMethods.execute('close', name, callback);
    },
    toogle: function(name, callback) {
      privateMethods.execute('toogle', name, callback);
    }
  };

  $.sidr = function( method ) {

    if ( methods[method] ) {
      return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'function' ||  typeof method === 'string'  || ! method ) {
      return methods.toogle.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.sidr' );
    }

  };

  $.fn.sidr = function( options ) {

    var settings = $.extend( {
      name          : 'sidr',         // Name for the 'sidr'
      speed         : 200,            // Accepts standard jQuery effects speeds (i.e. fast, normal or milliseconds)
      side          : 'left',         // Accepts 'left' or 'right'
      source        : null,           // Override the source of the content.
      renaming      : true,           // The ids and classes will be prepended with a prefix when loading existent content
      body          : 'body',         // Page container selector,
      onOpen        : function() {},  // Callback when sidr opened
      onClose       : function() {}   // Callback when sidr closed
    }, options);

    var name = settings.name,
        $sideMenu = $('#' + name);

    // If the side menu do not exist create it
    if( $sideMenu.length === 0 ) {
      $sideMenu = $('
')
        .attr('id', name)
        .appendTo($('body'));
    }

    // Adding styles and options
    $sideMenu
      .addClass('sidr')
      .addClass(settings.side)
      .data({
        speed          : settings.speed,
        side           : settings.side,
        body           : settings.body,
        onOpen         : settings.onOpen,
        onClose        : settings.onClose
      });

    // The menu content
    if(typeof settings.source === 'function') {
      var newContent = settings.source(name);
      privateMethods.loadContent($sideMenu, newContent);
    }
    else if(typeof settings.source === 'string' && privateMethods.isUrl(settings.source)) {
      $.get(settings.source, function(data) {
        privateMethods.loadContent($sideMenu, data);
      });
    }
    else if(typeof settings.source === 'string') {
      var htmlContent = '',
          selectors   = settings.source.split(',');

      $.each(selectors, function(index, element) {
        htmlContent += '
' + $(element).html() + '
';
      });

      // Renaming ids and classes
      if(settings.renaming) {
        var $htmlContent = $('
').html(htmlContent);
        $htmlContent.find('*').each(function(index, element) {
          var $element = $(element);
          privateMethods.addPrefix($element);
        });
        htmlContent = $htmlContent.html();
      }
      privateMethods.loadContent($sideMenu, htmlContent);
    }
    else if(settings.source !== null) {
      $.error('Invalid Sidr Source');
    }

    return this.each(function(){

      var $this = $(this),
          data = $this.data('sidr');

      // If the plugin hasn't been initialized yet
      if ( ! data ) {
        $this.data('sidr', name);
        $this.click(function(e) {
          e.preventDefault();
          methods.toogle(name);
        });
      }
    });
  };

})( jQuery );
<|endoftext|>