<commit_before>import _extend from 'lodash-es/extend';

import {
    geoIdentity as d3_geoIdentity,
    geoPath as d3_geoPath,
    geoStream as d3_geoStream
} from 'd3-geo';

import {
    geoVecAdd,
    geoVecAngle,
    geoVecLength
} from '../geo';

// Touch targets control which other vertices we can drag a vertex onto.
//
// - the activeID - nope
// - 1 away (adjacent) to the activeID - yes (vertices will be merged)
// - 2 away from the activeID - nope (would create a self intersecting segment)
// - all others on a linear way - yes
// - all others on a closed way - nope (would create a self intersecting polygon)
//
// returns
// 0 = active vertex - no touch/connect
// 1 = passive vertex - yes touch/connect
// 2 = adjacent vertex - yes but pay attention segmenting a line here
//
export function svgPassiveVertex(node, graph, activeID) {
    if (!activeID) return 1;
    if (activeID === node.id) return 0;

    var parents = graph.parentWays(node);

    for (var i = 0; i < parents.length; i++) {
        var nodes = parents[i].nodes;
        var isClosed = parents[i].isClosed();
        for (var j = 0; j < nodes.length; j++) {   // find this vertex, look nearby
            if (nodes[j] === node.id) {
                var ix1 = j - 2;
                var ix2 = j - 1;
                var ix3 = j + 1;
                var ix4 = j + 2;

                if (isClosed) {  // wraparound if needed
                    var max = nodes.length - 1;
                    if (ix1 < 0)   ix1 = max + ix1;
                    if (ix2 < 0)   ix2 = max + ix2;
                    if (ix3 > max) ix3 = ix3 - max;
                    if (ix4 > max) ix4 = ix4 - max;
                }

                if (nodes[ix1] === activeID) return 0;        // no - prevent self intersect
                else if (nodes[ix2] === activeID) return 2;   // ok - adjacent
                else if (nodes[ix3] === activeID) return 2;   // ok - adjacent
                else if (nodes[ix4] === activeID) return 0;   // no - prevent self intersect
                else if (isClosed && nodes.indexOf(activeID) !== -1) return 0;  // no - prevent self intersect
            }
        }
    }

    return 1;   // ok
}

export function svgOneWaySegments(projection, graph, dt) {
    return function(entity) {
        var i = 0;
        var offset = dt;
        var segments = [];
        var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream;
        var coordinates = graph.childNodes(entity).map(function(n) { return n.loc; });
        var a, b;

        if (entity.tags.oneway === '-1') {
            coordinates.reverse();
        }

        var isReversible = (entity.tags.oneway === 'reversible' || entity.tags.oneway === 'alternating');

        d3_geoStream({
            type: 'LineString',
            coordinates: coordinates
        }, projection.stream(clip({
            lineStart: function() {},
            lineEnd: function() { a = null; },
            point: function(x, y) {
                b = [x, y];

                if (a) {
                    var span = geoVecLength(a, b) - offset;

                    if (span >= 0) {
                        var heading = geoVecAngle(a, b);
                        var dx = dt * Math.cos(heading);
                        var dy = dt * Math.sin(heading);
                        var p = [
                            a[0] + offset * Math.cos(heading),
                            a[1] + offset * Math.sin(heading)
                        ];

                        // gather coordinates
                        var coord = [a, p];
                        for (span -= dt; span >= 0; span -= dt) {
                            p = geoVecAdd(p, [dx, dy]);
                            coord.push(p);
                        }
                        coord.push(b);

                        // generate svg paths
                        var segment = '';
                        var j;

                        for (j = 0; j < coord.length; j++) {
                            segment += (j === 0 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
                        }
                        segments.push({ id: entity.id, index: i++, d: segment });

                        if (isReversible) {
                            segment = '';
                            for (j = coord.length - 1; j >= 0; j--) {
                                segment += (j === coord.length - 1 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
                            }
                            segments.push({ id: entity.id, index: i++, d: segment });
                        }
                    }

                    offset = -span;
                }

                a = b;
            }
        })));

        return segments;
    };
}

export function svgPath(projection, graph, isArea) {

    // Explanation of magic numbers:
    // "padding" here allows space for strokes to extend beyond the viewport,
    // so that the stroke isn't drawn along the edge of the viewport when
    // the shape is clipped.
    //
    // When drawing lines, pad viewport by 5px.
    // When drawing areas, pad viewport by 65px in each direction to allow
    // for 60px area fill stroke (see ".fill-partial path.fill" css rule)

    var cache = {};
    var padding = isArea ? 65 : 5;
    var viewport = projection.clipExtent();
    var paddedExtent = [
        [viewport[0][0] - padding, viewport[0][1] - padding],
        [viewport[1][0] + padding, viewport[1][1] + padding]
    ];
    var clip = d3_geoIdentity().clipExtent(paddedExtent).stream;
    var project = projection.stream;
    var path = d3_geoPath()
        .projection({stream: function(output) { return project(clip(output)); }});

    var svgpath = function(entity) {
        if (entity.id in cache) {
            return cache[entity.id];
        } else {
            return cache[entity.id] = path(entity.asGeoJSON(graph));
        }
    };

    svgpath.geojson = function(d) {
        if (d.__featurehash__ !== undefined) {
            if (d.__featurehash__ in cache) {
                return cache[d.__featurehash__];
            } else {
                return cache[d.__featurehash__] = path(d);
            }
        } else {
            return path(d);
        }
    };

    return svgpath;
}

export function svgPointTransform(projection) {
    var svgpoint = function(entity) {
        // http://jsperf.com/short-array-join
        var pt = projection(entity.loc);
        return 'translate(' + pt[0] + ',' + pt[1] + ')';
    };

    svgpoint.geojson = function(d) {
        return svgpoint(d.properties.entity);
    };

    return svgpoint;
}

export function svgRelationMemberTags(graph) {
    return function(entity) {
        var tags = entity.tags;
        graph.parentRelations(entity).forEach(function(relation) {
            var type = relation.tags.type;
            if (type === 'multipolygon' || type === 'boundary') {
                tags = _extend({}, relation.tags, tags);
            }
        });
        return tags;
    };
}

export function svgSegmentWay(way, graph, activeID) {
    var isActiveWay = (way.nodes.indexOf(activeID) !== -1);
    var features = { passive: [], active: [] };
    var start = {};
    var end = {};
    var node, type;

    for (var i = 0; i < way.nodes.length; i++) {
        node = graph.entity(way.nodes[i]);
        type = svgPassiveVertex(node, graph, activeID);
        end = { node: node, type: type };

        if (start.type !== undefined) {
            if (start.node.id === activeID || end.node.id === activeID) {
                // push nothing
            } else if (isActiveWay && (start.type === 2 || end.type === 2)) {   // one adjacent vertex
                pushActive(start, end, i);
            } else if (start.type === 0 && end.type === 0) {   // both active vertices
                pushActive(start, end, i);
            } else {
                pushPassive(start, end, i);
            }
        }

        start = end;
    }

    return features;

    function pushActive(start, end, index) {
        features.active.push({
            type: 'Feature',
            id: way.id + '-' + index + '-nope',
            properties: {
                nope: true,
                target: true,
                entity: way,
                nodes: [start.node, end.node],
                index: index
            },
            geometry: {
                type: 'LineString',
                coordinates: [start.node.loc, end.node.loc]
            }
        });
    }

    function pushPassive(start, end, index) {
        features.passive.push({
            type: 'Feature',
            id: way.id + '-' + index,
            properties: {
                target: true,
                entity: way,
                nodes: [start.node, end.node],
                index: index
            },
            geometry: {
                type: 'LineString',
                coordinates: [start.node.loc, end.node.loc]
            }
        });
    }
}
<commit_msg>When there is no activeID, we can memoize svgSegmentWay<commit_after>import _extend from 'lodash-es/extend';

import {
    geoIdentity as d3_geoIdentity,
    geoPath as d3_geoPath,
    geoStream as d3_geoStream
} from 'd3-geo';

import {
    geoVecAdd,
    geoVecAngle,
    geoVecLength
} from '../geo';

// Touch targets control which other vertices we can drag a vertex onto.
//
// - the activeID - nope
// - 1 away (adjacent) to the activeID - yes (vertices will be merged)
// - 2 away from the activeID - nope (would create a self intersecting segment)
// - all others on a linear way - yes
// - all others on a closed way - nope (would create a self intersecting polygon)
//
// returns
// 0 = active vertex - no touch/connect
// 1 = passive vertex - yes touch/connect
// 2 = adjacent vertex - yes but pay attention segmenting a line here
//
export function svgPassiveVertex(node, graph, activeID) {
    if (!activeID) return 1;
    if (activeID === node.id) return 0;

    var parents = graph.parentWays(node);

    for (var i = 0; i < parents.length; i++) {
        var nodes = parents[i].nodes;
        var isClosed = parents[i].isClosed();
        for (var j = 0; j < nodes.length; j++) {   // find this vertex, look nearby
            if (nodes[j] === node.id) {
                var ix1 = j - 2;
                var ix2 = j - 1;
                var ix3 = j + 1;
                var ix4 = j + 2;

                if (isClosed) {  // wraparound if needed
                    var max = nodes.length - 1;
                    if (ix1 < 0)   ix1 = max + ix1;
                    if (ix2 < 0)   ix2 = max + ix2;
                    if (ix3 > max) ix3 = ix3 - max;
                    if (ix4 > max) ix4 = ix4 - max;
                }

                if (nodes[ix1] === activeID) return 0;        // no - prevent self intersect
                else if (nodes[ix2] === activeID) return 2;   // ok - adjacent
                else if (nodes[ix3] === activeID) return 2;   // ok - adjacent
                else if (nodes[ix4] === activeID) return 0;   // no - prevent self intersect
                else if (isClosed && nodes.indexOf(activeID) !== -1) return 0;  // no - prevent self intersect
            }
        }
    }

    return 1;   // ok
}

export function svgOneWaySegments(projection, graph, dt) {
    return function(entity) {
        var i = 0;
        var offset = dt;
        var segments = [];
        var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream;
        var coordinates = graph.childNodes(entity).map(function(n) { return n.loc; });
        var a, b;

        if (entity.tags.oneway === '-1') {
            coordinates.reverse();
        }

        var isReversible = (entity.tags.oneway === 'reversible' || entity.tags.oneway === 'alternating');

        d3_geoStream({
            type: 'LineString',
            coordinates: coordinates
        }, projection.stream(clip({
            lineStart: function() {},
            lineEnd: function() { a = null; },
            point: function(x, y) {
                b = [x, y];

                if (a) {
                    var span = geoVecLength(a, b) - offset;

                    if (span >= 0) {
                        var heading = geoVecAngle(a, b);
                        var dx = dt * Math.cos(heading);
                        var dy = dt * Math.sin(heading);
                        var p = [
                            a[0] + offset * Math.cos(heading),
                            a[1] + offset * Math.sin(heading)
                        ];

                        // gather coordinates
                        var coord = [a, p];
                        for (span -= dt; span >= 0; span -= dt) {
                            p = geoVecAdd(p, [dx, dy]);
                            coord.push(p);
                        }
                        coord.push(b);

                        // generate svg paths
                        var segment = '';
                        var j;

                        for (j = 0; j < coord.length; j++) {
                            segment += (j === 0 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
                        }
                        segments.push({ id: entity.id, index: i++, d: segment });

                        if (isReversible) {
                            segment = '';
                            for (j = coord.length - 1; j >= 0; j--) {
                                segment += (j === coord.length - 1 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
                            }
                            segments.push({ id: entity.id, index: i++, d: segment });
                        }
                    }

                    offset = -span;
                }

                a = b;
            }
        })));

        return segments;
    };
}

export function svgPath(projection, graph, isArea) {

    // Explanation of magic numbers:
    // "padding" here allows space for strokes to extend beyond the viewport,
    // so that the stroke isn't drawn along the edge of the viewport when
    // the shape is clipped.
    //
    // When drawing lines, pad viewport by 5px.
    // When drawing areas, pad viewport by 65px in each direction to allow
    // for 60px area fill stroke (see ".fill-partial path.fill" css rule)

    var cache = {};
    var padding = isArea ? 65 : 5;
    var viewport = projection.clipExtent();
    var paddedExtent = [
        [viewport[0][0] - padding, viewport[0][1] - padding],
        [viewport[1][0] + padding, viewport[1][1] + padding]
    ];
    var clip = d3_geoIdentity().clipExtent(paddedExtent).stream;
    var project = projection.stream;
    var path = d3_geoPath()
        .projection({stream: function(output) { return project(clip(output)); }});

    var svgpath = function(entity) {
        if (entity.id in cache) {
            return cache[entity.id];
        } else {
            return cache[entity.id] = path(entity.asGeoJSON(graph));
        }
    };

    svgpath.geojson = function(d) {
        if (d.__featurehash__ !== undefined) {
            if (d.__featurehash__ in cache) {
                return cache[d.__featurehash__];
            } else {
                return cache[d.__featurehash__] = path(d);
            }
        } else {
            return path(d);
        }
    };

    return svgpath;
}

export function svgPointTransform(projection) {
    var svgpoint = function(entity) {
        // http://jsperf.com/short-array-join
        var pt = projection(entity.loc);
        return 'translate(' + pt[0] + ',' + pt[1] + ')';
    };

    svgpoint.geojson = function(d) {
        return svgpoint(d.properties.entity);
    };

    return svgpoint;
}

export function svgRelationMemberTags(graph) {
    return function(entity) {
        var tags = entity.tags;
        graph.parentRelations(entity).forEach(function(relation) {
            var type = relation.tags.type;
            if (type === 'multipolygon' || type === 'boundary') {
                tags = _extend({}, relation.tags, tags);
            }
        });
        return tags;
    };
}

export function svgSegmentWay(way, graph, activeID) {
    // When there is no activeID, we can memoize this expensive computation
    if (activeID === undefined) {
        return graph.transient(way, 'waySegments', getWaySegments);
    } else {
        return getWaySegments();
    }

    function getWaySegments() {
        var isActiveWay = (way.nodes.indexOf(activeID) !== -1);
        var features = { passive: [], active: [] };
        var start = {};
        var end = {};
        var node, type;

        for (var i = 0; i < way.nodes.length; i++) {
            node = graph.entity(way.nodes[i]);
            type = svgPassiveVertex(node, graph, activeID);
            end = { node: node, type: type };

            if (start.type !== undefined) {
                if (start.node.id === activeID || end.node.id === activeID) {
                    // push nothing
                } else if (isActiveWay && (start.type === 2 || end.type === 2)) {   // one adjacent vertex
                    pushActive(start, end, i);
                } else if (start.type === 0 && end.type === 0) {   // both active vertices
                    pushActive(start, end, i);
                } else {
                    pushPassive(start, end, i);
                }
            }

            start = end;
        }

        return features;

        function pushActive(start, end, index) {
            features.active.push({
                type: 'Feature',
                id: way.id + '-' + index + '-nope',
                properties: {
                    nope: true,
                    target: true,
                    entity: way,
                    nodes: [start.node, end.node],
                    index: index
                },
                geometry: {
                    type: 'LineString',
                    coordinates: [start.node.loc, end.node.loc]
                }
            });
        }

        function pushPassive(start, end, index) {
            features.passive.push({
                type: 'Feature',
                id: way.id + '-' + index,
                properties: {
                    target: true,
                    entity: way,
                    nodes: [start.node, end.node],
                    index: index
                },
                geometry: {
                    type: 'LineString',
                    coordinates: [start.node.loc, end.node.loc]
                }
            });
        }
    }
}
<|endoftext|>


<commit_before>'use strict'

var PDFObject = require('./objects/object')
var Pages     = require('./pages')
var Font      = require('./font')
var utils     = require('./utils')

var Document = module.exports = function Document(font, opts) {
  this.version = 1.3

  // list of all objects in this document
  this.objects   = []

  // list of all fonts in this document
  this.fonts       = []
  this.subsets     = []
  this.defaultFont = this.registerFont(font)

  // call parents constructor
  if (!opts) opts = {}
  if (!opts.padding) opts.padding = { top: 20, right: 40, bottom: 20, left: 40 }
  Document.super_.call(this, this, opts)
  this.height = this.opts.height || 792

  // the catalog and pages tree
  this.catalog = this.createObject('Catalog')
  this.pages   = new Pages(this)
  this.catalog.prop('Pages', this.pages.toReference())

  this.areas = { header: null, footer: null }
}

var Fragment = require('./fragment')
utils.inherits(Document, Fragment)

Document.Font = Font

var areas = ['header', 'footer']
areas.forEach(function(area) {
  Document.prototype[area] = function(opts, definition) {
    if (typeof opts !== 'object') {
      definition = opts
      opts = {}
    }
    if (!opts.padding) opts.padding = { top: 0, right: this.padding.right, bottom: 0, left: this.padding.left }
    this.areas[area] = new Fragment(this, opts)
    if (typeof definition === 'function') {
      definition.call(this.areas[area], this.areas[area])
    } else {
      this.areas[area].text(definition, opts)
    }
    return this
  }
})

Document.prototype.registerFont = function(font) {
  var index
  if ((index = this.fonts.indexOf(font)) > -1) return this.subsets[index]
  var id = this.fonts.push(font)
  this.subsets.push(font.subset(this, id))
  return this.subsets[id - 1]
}

Document.prototype.createObject = function(type) {
  var object = new PDFObject()
  if (type) object.addProperty('Type', type)
  this.objects.push(object)
  return object
}

// Transaction
Document.prototype.startTransaction = function() {
  return new Transaction(this)
}

// Rendering

Document.prototype.pagebreak = function() {
  var page = this.cursor = this.pages.addPage()
  if (this.areas.header) {
    this.areas.header.height = 0
    this.areas.header.render(page, this.idth)
    this.areas.header.height = this.height - page.cursor.y - this.opts.padding.top
  }
  if (this.areas.footer) {
    var footer = this.areas.footer
    var transaction = this.startTransaction()
    var y = page.cursor.y
    footer.height = 0
    footer.render(page, this.width)
    var height = y - page.cursor.y
    transaction.rollback()
    page.cursor.y = this.padding.bottom + height
    footer.render(page, this.width)
    page.cursor.y = y
    footer.height = height
  }
  return page
}

Document.prototype.toDataURL = function() {
  return 'data:application/pdfbase64,' + Base64.encode(this.toString())
}

var PDFDictionary = require('./objects/dictionary')
var PDFArray      = require('./objects/array')
var PDFString     = require('./objects/string')

Document.prototype.toString = function() {
  var self = this
  this.objects = [this.catalog, this.pages.tree]

  this.pagebreak()
  this.render(this.cursor)
  this.subsets.forEach(function(subset) {
    subset.embed(self)
  })

  var buf = '', xref = [], startxref

  // header
  buf += '%PDF-' + this.version.toString() + '\n'

  // The PDF format mandates that we add at least 4 commented binary characters
  // (ASCII value >= 128), so that generic tools have a chance to detect
  // that it's a binary file
  buf += '%\xFF\xFF\xFF\xFF\n'

  buf += '\n'

  this.objects.forEach(function(obj, i) {
    obj.id = i + 1
  })

  // body
  this.objects.forEach(function(object) {
    xref.push(buf.length)
    buf += object.toString() + '\n\n'
  })

  // to support random access to individual objects, a PDF file
  // contains a cross-reference table that can be used to locate
  // and directly access pages and other important objects within the file
  startxref = buf.length
  buf += 'xref\n'
  buf += '0 ' + (this.objects.length + 1) + '\n'
  buf += '0000000000 65535 f \n'
  xref.forEach(function(ref) {
    buf += '0000000000'.substr(ref.toString().length) + ref + ' 00000 n \n'
  })

  // trailer
  var id = (new PDFString(uuid4())).toHexString()
  var trailer = new PDFDictionary({
      Size: (this.objects.length + 1),
      Root: this.catalog.toReference(),
      ID:   new PDFArray([id, id])
  })
  buf += 'trailer\n'
  buf += trailer.toString() + '\n'
  buf += 'startxref\n'
  buf += startxref + '\n'
  buf += '%%EOF'

  return buf
}

// Transaction

var Transaction = function(doc) {
  this.doc = doc
  this.page = doc.pages.count - 1
  this.length = doc.cursor.contents.content.length
  this.y = doc.cursor.cursor.y
}

Transaction.prototype.rollback = function() {
  if (this.page < (this.doc.pages.count - 1)) {
    for (var i = this.doc.pages.count - 1; i > this.page; --i)
      this.doc.pages.removePageAt(i)
    this.doc.cursor = this.doc.pages.pages[this.page]
  }

  if (this.length < this.doc.cursor.contents.content.length) {
    this.doc.cursor.contents.slice(0, this.length)
  }

  this.doc.cursor.cursor.y = this.y
}

Transaction.prototype.commit = function() {
}

/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info/
*
**/
var Base64 = {
  // private property
  _keyStr : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',

  // public method for encoding
  encode : function (input) {
    var output = ''
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4
    var i = 0

    input = Base64._utf8Encode(input)

    while (i < input.length) {
      chr1 = input.charCodeAt(i++)
      chr2 = input.charCodeAt(i++)
      chr3 = input.charCodeAt(i++)

      enc1 = chr1 >> 2
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4)
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6)
      enc4 = chr3 & 63

      if (isNaN(chr2)) {
          enc3 = enc4 = 64
      } else if (isNaN(chr3)) {
          enc4 = 64
      }

      output = output +
      this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
      this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4)
    }

    return output
  },

  // private method for UTF-8 encoding
  _utf8Encode : function (string) {
    string = string.replace(/\r\n/g,'\n')
    var utftext = ''

    for (var n = 0; n < string.length; n++) {
      var c = string.charCodeAt(n)

      // workaround to not encode UTF8 characters
      // TODO: improve ...
      utftext += String.fromCharCode(Math.min(c, 0xff))

      // if (c < 128) {
      //   utftext += String.fromCharCode(c)
      // }
      // else if((c > 127) && (c < 2048)) {
      //   utftext += String.fromCharCode((c >> 6) | 192)
      //   utftext += String.fromCharCode((c & 63) | 128)
      // }
      // else {
      //   utftext += String.fromCharCode((c >> 12) | 224)
      //   utftext += String.fromCharCode(((c >> 6) & 63) | 128)
      //   utftext += String.fromCharCode((c & 63) | 128)
      // }
    }

    return utftext
  }
}

// UUID v4
// source: https://gist.github.com/jed/982883
function uuid4(
  a                  // placeholder
){
  return a           // if the placeholder was passed, return
    ? (              // a random number from 0 to 15
      a ^            // unless b is 8,
      Math.random()  // in which case
      * 16           // a random number from
      >> a / 4       // 8 to 11
      ).toString(16) // in hexadecimal
    : (              // or otherwise a concatenated string:
      [1e7] +        // 10000000 +
      -1e3 +         // -1000 +
      -4e3 +         // -4000 +
      -8e3 +         // -80000000 +
      -1e11          // -100000000000,
      ).replace(     // replacing
        /[018]/g,    // zeroes, ones, and eights with
        uuid4        // random hex digits
      )
}
<commit_msg>add basic metadata<commit_after>'use strict'

var PDFObject = require('./objects/object')
var Pages     = require('./pages')
var Font      = require('./font')
var utils     = require('./utils')

var Document = module.exports = function Document(font, opts) {
  this.version = 1.3

  // list of all objects in this document
  this.objects   = []

  // list of all fonts in this document
  this.fonts       = []
  this.subsets     = []
  this.defaultFont = this.registerFont(font)

  // call parents constructor
  if (!opts) opts = {}
  if (!opts.padding) opts.padding = { top: 20, right: 40, bottom: 20, left: 40 }
  Document.super_.call(this, this, opts)
  this.height = this.opts.height || 792

  // the catalog and pages tree
  this.catalog = this.createObject('Catalog')
  this.pages   = new Pages(this)
  this.catalog.prop('Pages', this.pages.toReference())

  this.areas = { header: null, footer: null }
}

var Fragment = require('./fragment')
utils.inherits(Document, Fragment)

Document.Font = Font

var areas = ['header', 'footer']
areas.forEach(function(area) {
  Document.prototype[area] = function(opts, definition) {
    if (typeof opts !== 'object') {
      definition = opts
      opts = {}
    }
    if (!opts.padding) opts.padding = { top: 0, right: this.padding.right, bottom: 0, left: this.padding.left }
    this.areas[area] = new Fragment(this, opts)
    if (typeof definition === 'function') {
      definition.call(this.areas[area], this.areas[area])
    } else {
      this.areas[area].text(definition, opts)
    }
    return this
  }
})

Document.prototype.registerFont = function(font) {
  var index
  if ((index = this.fonts.indexOf(font)) > -1) return this.subsets[index]
  var id = this.fonts.push(font)
  this.subsets.push(font.subset(this, id))
  return this.subsets[id - 1]
}

Document.prototype.createObject = function(type) {
  var object = new PDFObject()
  if (type) object.addProperty('Type', type)
  this.objects.push(object)
  return object
}

// Transaction
Document.prototype.startTransaction = function() {
  return new Transaction(this)
}

// Rendering

Document.prototype.pagebreak = function() {
  var page = this.cursor = this.pages.addPage()
  if (this.areas.header) {
    this.areas.header.height = 0
    this.areas.header.render(page, this.idth)
    this.areas.header.height = this.height - page.cursor.y - this.opts.padding.top
  }
  if (this.areas.footer) {
    var footer = this.areas.footer
    var transaction = this.startTransaction()
    var y = page.cursor.y
    footer.height = 0
    footer.render(page, this.width)
    var height = y - page.cursor.y
    transaction.rollback()
    page.cursor.y = this.padding.bottom + height
    footer.render(page, this.width)
    page.cursor.y = y
    footer.height = height
  }
  return page
}

Document.prototype.toDataURL = function() {
  return 'data:application/pdfbase64,' + Base64.encode(this.toString())
}

var PDFDictionary = require('./objects/dictionary')
var PDFArray      = require('./objects/array')
var PDFString     = require('./objects/string')

Document.prototype.toString = function() {
  var self = this
  this.objects = [this.catalog, this.pages.tree]

  this.pagebreak()
  this.render(this.cursor)
  this.subsets.forEach(function(subset) {
    subset.embed(self)
  })

  var buf = '', xref = [], startxref

  // header
  buf += '%PDF-' + this.version.toString() + '\n'

  // The PDF format mandates that we add at least 4 commented binary characters
  // (ASCII value >= 128), so that generic tools have a chance to detect
  // that it's a binary file
  buf += '%\xFF\xFF\xFF\xFF\n'

  buf += '\n'

  this.objects.forEach(function(obj, i) {
    obj.id = i + 1
  })

  // body
  this.objects.forEach(function(object) {
    xref.push(buf.length)
    buf += object.toString() + '\n\n'
  })

  // to support random access to individual objects, a PDF file
  // contains a cross-reference table that can be used to locate
  // and directly access pages and other important objects within the file
  startxref = buf.length
  buf += 'xref\n'
  buf += '0 ' + (this.objects.length + 1) + '\n'
  buf += '0000000000 65535 f \n'
  xref.forEach(function(ref) {
    buf += '0000000000'.substr(ref.toString().length) + ref + ' 00000 n \n'
  })

  // trailer
  var id = (new PDFString(uuid4())).toHexString()
  var version = require('../package.json').version
  var trailer = new PDFDictionary({
      Size: (this.objects.length + 1),
      Root: this.catalog.toReference(),
      ID:   new PDFArray([id, id]),
      Info: new PDFDictionary({
        Producer: new PDFString('pdfjs v' + version + ' (github.com/rkusa/pdfjs)'),
        CreationDate: new PDFString(formatDate(new Date))
      })
  })
  buf += 'trailer\n'
  buf += trailer.toString() + '\n'
  buf += 'startxref\n'
  buf += startxref + '\n'
  buf += '%%EOF'

  return buf
}

// Transaction

var Transaction = function(doc) {
  this.doc = doc
  this.page = doc.pages.count - 1
  this.length = doc.cursor.contents.content.length
  this.y = doc.cursor.cursor.y
}

Transaction.prototype.rollback = function() {
  if (this.page < (this.doc.pages.count - 1)) {
    for (var i = this.doc.pages.count - 1; i > this.page; --i)
      this.doc.pages.removePageAt(i)
    this.doc.cursor = this.doc.pages.pages[this.page]
  }

  if (this.length < this.doc.cursor.contents.content.length) {
    this.doc.cursor.contents.slice(0, this.length)
  }

  this.doc.cursor.cursor.y = this.y
}

Transaction.prototype.commit = function() {
}

/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info/
*
**/
var Base64 = {
  // private property
  _keyStr : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',

  // public method for encoding
  encode : function (input) {
    var output = ''
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4
    var i = 0

    input = Base64._utf8Encode(input)

    while (i < input.length) {
      chr1 = input.charCodeAt(i++)
      chr2 = input.charCodeAt(i++)
      chr3 = input.charCodeAt(i++)

      enc1 = chr1 >> 2
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4)
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6)
      enc4 = chr3 & 63

      if (isNaN(chr2)) {
          enc3 = enc4 = 64
      } else if (isNaN(chr3)) {
          enc4 = 64
      }

      output = output +
      this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
      this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4)
    }

    return output
  },

  // private method for UTF-8 encoding
  _utf8Encode : function (string) {
    string = string.replace(/\r\n/g,'\n')
    var utftext = ''

    for (var n = 0; n < string.length; n++) {
      var c = string.charCodeAt(n)

      // workaround to not encode UTF8 characters
      // TODO: improve ...
      utftext += String.fromCharCode(Math.min(c, 0xff))

      // if (c < 128) {
      //   utftext += String.fromCharCode(c)
      // }
      // else if((c > 127) && (c < 2048)) {
      //   utftext += String.fromCharCode((c >> 6) | 192)
      //   utftext += String.fromCharCode((c & 63) | 128)
      // }
      // else {
      //   utftext += String.fromCharCode((c >> 12) | 224)
      //   utftext += String.fromCharCode(((c >> 6) & 63) | 128)
      //   utftext += String.fromCharCode((c & 63) | 128)
      // }
    }

    return utftext
  }
}

// UUID v4
// source: https://gist.github.com/jed/982883
function uuid4(
  a                  // placeholder
){
  return a           // if the placeholder was passed, return
    ? (              // a random number from 0 to 15
      a ^            // unless b is 8,
      Math.random()  // in which case
      * 16           // a random number from
      >> a / 4       // 8 to 11
      ).toString(16) // in hexadecimal
    : (              // or otherwise a concatenated string:
      [1e7] +        // 10000000 +
      -1e3 +         // -1000 +
      -4e3 +         // -4000 +
      -8e3 +         // -80000000 +
      -1e11          // -100000000000,
      ).replace(     // replacing
        /[018]/g,    // zeroes, ones, and eights with
        uuid4        // random hex digits
      )
}

function formatDate(date) {
  var str = 'D:'
          + date.getFullYear()
          + ('00' + (date.getMonth() + 1)).slice(-2)
          + ('00' + date.getDate()).slice(-2)
          + ('00' + date.getHours()).slice(-2)
          + ('00' + date.getMinutes()).slice(-2)
          + ('00' + date.getSeconds()).slice(-2)

  var offset = date.getTimezoneOffset()
  var rel = offset === 0 ? 'Z' : (offset > 0 ? '-' : '+')
  offset = Math.abs(offset)
  var hoursOffset = Math.floor(offset / 60)
  var minutesOffset = offset - hoursOffset * 60

  str += rel
      + ('00' + hoursOffset).slice(-2)   + '\''
      + ('00' + minutesOffset).slice(-2) + '\''

  return str
}
<|endoftext|>


<commit_before>(function($) {

    var cache = {};

    function getCSS(source, options) {

        var deferred = $.Deferred(), opts = options || {};

        (opts.imports ? resolveImports(source) : $.Deferred().resolve(source)).done(function(source) {

            if (opts.id && cache[opts.id]) {

                createCSS(cache[opts.id]);

            } else {

                new(less.Parser)().parse(source, function(error, tree) {

                    if (error) {
                        return deferred.reject(error);
                    }

                    if (opts.id) {
                        cache[opts.id] = tree;
                    }

                    createCSS(tree);

                });

            }
        });

        function createCSS(tree) {

            var variables = "";

            if (opts.variables) {
                $.each(opts.variables, function(name, value) {
                    variables += ((name.slice(0,1) === "@") ? "" : "@") + name + ": " + ((value.slice(-1) === ";") ? value : value + ";");
                });
            }

            if (variables) {

                new(less.Parser)().parse(variables, function(error, vars) {

                    if (error) {
                        return deferred.reject(error);
                    }

                    var rules = tree.rules;

                    tree.rules = tree.rules.concat(vars.rules);
                    toCSS(tree);
                    tree.rules = rules;

                });

            } else {

                toCSS(tree);

            }
        }

        function toCSS(tree) {

            try {
                deferred.resolve(tree.toCSS(opts));
            } catch (e) {

                if (opts.id && cache[opts.id]) {
                    delete cache[opts.id];
                }

                deferred.reject(e);
            }

        }

        return deferred.promise();
    }

    function resolveImports(source) {

        var deferred = $.Deferred(), imports = {}, host = extractUrlParts(window.location.href).host, importRegex = /@import\s+url\s*\(['"]?(.+?)['"]?\)\s*;/g, urlRegex = /url\s*\(['"]?(.+?)['"]?\)/g;

        function queuedWhen(queue) {

            var deferreds = [], prev = null;

            $.each(queue, function(i, fn) {
                deferreds.push(prev = prev ? prev.then(fn) : fn.call());
            });

            return $.when.apply($, deferreds);
        }

        function rewrite(source, baseUrl) {

            source = source.replace(/@import\s['"]?(.+?)['"]?\s*;/g, function(match, url) {
                return match.indexOf("url(")!=-1 ? match:'@import url("'+url+'");';
            });

            return source.replace(urlRegex, function(match, url) {
                return match.replace(url, extractUrlParts(url, baseUrl).url);
            });
        }

        (function resolve(source) {

            var queue = [];

            source = source.replace(/@import\s['"]?(.+?)['"]?\s*;/g, function(match, url) {
                return match.indexOf("url(")!=-1 ? match:'@import url("'+url+'");';
            });

            source.replace(importRegex, function(match, url) {

                if (!imports[url] && host == extractUrlParts(url).host) {
                    queue.push(
                        function() {
                            return $.ajax({url: url, cache: false}).done(function(data) {
                                imports[url] = rewrite(data.replace(/\/\*(?:[^*]|\*+[^\/*])*\*+\/|^((?!:).)?\/\/.*/g, ''), url);
                            }).fail(function(xhr, status, error) {
                                imports[url] = "/* Can't resolve import '" + url + "' (" + status + ", " + error + ") */";
                            });
                        }
                    );
                }

                return match;
            });

            queuedWhen(queue).always(function() {

                source = source.replace(importRegex, function(match, url) {
                    return imports[url] ? imports[url] : match;
                });

                if (queue.length) {
                    source = resolve(source);
                } else {
                    deferred.resolve(source);
                }
            });

            return source;

        })(rewrite(source.replace(/\/\*(?:[^*]|\*+[^\/*])*\*+\/|^((?!:).)?\/\/.*/g, '')));

        return deferred.promise();
    }

    function getVars(source) {

        var i, vars = {}, lines = source.split("\n");

        for (i = 0, max = lines.length; i < max; i++) {

            var line = $.trim(lines[i]);

            if (!line.length) continue;
            if (!/@[\w\-]+\s*:.[^;]*;/.test(line)) continue;

            var keyval = $.trim(line.replace(";", "").replace(/\s+/, "")).split(":");

            keyval[1] = $.trim(keyval[1].replace(";","").split('//')[0]);
            vars[keyval[0]] = keyval[1];
        }

        return vars;
    }

    function rewriteUrls(source, baseUrl) {
        return source.replace(/url\s*\(['"]?(.+?)['"]?\)/g, function(match, url) {
            return match.replace(url, pathDiff(extractUrlParts(url, baseUrl).url, baseUrl));
        });
    }

    function pathDiff(url, baseUrl) {

        var urlParts = extractUrlParts(url), urlDirs,
            baseUrlParts = extractUrlParts(baseUrl), baseUrlDirs,
            diff = "", max, i;

        if (urlParts.host !== baseUrlParts.host) {
            return "";
        }

        max = Math.max(baseUrlParts.dirs.length, urlParts.dirs.length);

        for (i = 0; i < max; i++) {
            if (baseUrlParts.dirs[i] !== urlParts.dirs[i]) { break; }
        }

        urlDirs = urlParts.dirs.slice(i);
        baseUrlDirs = baseUrlParts.dirs.slice(i);

        for (i = 0; i < baseUrlDirs.length - 1; i++) {
            diff += "../";
        }

        for (i = 0; i < urlDirs.length - 1; i++) {
            diff += urlDirs[i] + "/";
        }

        return diff + urlParts.file + urlParts.query;
    }

    function extractUrlParts(url, baseUrl) {

        var urlPartsRegex = /^((?:[a-z-]+:)?\/\/(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/,
            urlParts = url.match(urlPartsRegex), baseUrlParts,
            parts = {}, dirs = [], i;

        if (!urlParts) {
            throw new Exception("Could not parse url - '" + url + "'");
        }

        if (!urlParts[1] || urlParts[2]) {

            if (!baseUrl) {
                baseUrl = window.location.href;
            }

            baseUrlParts = baseUrl.match(urlPartsRegex);

            if (!baseUrlParts) {
                throw new Exception("Could not parse url - '" + baseUrl + "'");
            }

            urlParts[1] = baseUrlParts[1];

            if (!urlParts[2]) {
                urlParts[3] = baseUrlParts[3] + urlParts[3];
            }
        }

        if (urlParts[3]) {

            dirs = urlParts[3].replace("\\", "/").split("/");

            for (i = 0; i < dirs.length; i++) {
                if (dirs[i] === ".." && i > 0) {
                    dirs.splice(i-1, 2);
                    i -= 2;
                }
            }
        }

        parts.host = urlParts[1];
        parts.path = urlParts[1] + dirs.join("/");
        parts.file = urlParts[4] || "";
        parts.query = urlParts[5] || "";
        parts.url = parts.path + parts.file + parts.query;
        parts.dirs = dirs;

        return parts;
    }

    $.less = $.less || (function() {
        return {
            'getCSS': getCSS,
            'getVars': getVars,
            'resolveImports': resolveImports,
            'rewriteUrls': rewriteUrls,
            'pathDiff': pathDiff,
            'extractUrlParts': extractUrlParts
        };
    })();

})(jQuery);<commit_msg>fixed rewrite url for data URIs<commit_after>(function($) {

    var cache = {};

    function getCSS(source, options) {

        var deferred = $.Deferred(), opts = options || {};

        (opts.imports ? resolveImports(source) : $.Deferred().resolve(source)).done(function(source) {

            if (opts.id && cache[opts.id]) {

                createCSS(cache[opts.id]);

            } else {

                new(less.Parser)().parse(source, function(error, tree) {

                    if (error) {
                        return deferred.reject(error);
                    }

                    if (opts.id) {
                        cache[opts.id] = tree;
                    }

                    createCSS(tree);

                });

            }
        });

        function createCSS(tree) {

            var variables = "";

            if (opts.variables) {
                $.each(opts.variables, function(name, value) {
                    variables += ((name.slice(0,1) === "@") ? "" : "@") + name + ": " + ((value.slice(-1) === ";") ? value : value + ";");
                });
            }

            if (variables) {

                new(less.Parser)().parse(variables, function(error, vars) {

                    if (error) {
                        return deferred.reject(error);
                    }

                    var rules = tree.rules;

                    tree.rules = tree.rules.concat(vars.rules);
                    toCSS(tree);
                    tree.rules = rules;

                });

            } else {

                toCSS(tree);

            }
        }

        function toCSS(tree) {

            try {
                deferred.resolve(tree.toCSS(opts));
            } catch (e) {

                if (opts.id && cache[opts.id]) {
                    delete cache[opts.id];
                }

                deferred.reject(e);
            }

        }

        return deferred.promise();
    }

    function resolveImports(source) {

        var deferred = $.Deferred(), imports = {}, host = extractUrlParts(window.location.href).host, importRegex = /@import\s+url\s*\(['"]?(.+?)['"]?\)\s*;/g, urlRegex = /url\s*\(['"]?(.+?)['"]?\)/g;

        function queuedWhen(queue) {

            var deferreds = [], prev = null;

            $.each(queue, function(i, fn) {
                deferreds.push(prev = prev ? prev.then(fn) : fn.call());
            });

            return $.when.apply($, deferreds);
        }

        function rewrite(source, baseUrl) {

            source = source.replace(/@import\s['"]?(.+?)['"]?\s*;/g, function(match, url) {
                return match.indexOf("url(")!=-1 ? match:'@import url("'+url+'");';
            });

            return source.replace(urlRegex, function(match, url) {
                return match.match(/data\:image\//) ? match : match.replace(url, extractUrlParts(url, baseUrl).url);
            });
        }

        (function resolve(source) {

            var queue = [];

            source = source.replace(/@import\s['"]?(.+?)['"]?\s*;/g, function(match, url) {
                return match.indexOf("url(")!=-1 ? match:'@import url("'+url+'");';
            });

            source.replace(importRegex, function(match, url) {

                if (!imports[url] && host == extractUrlParts(url).host) {
                    queue.push(
                        function() {
                            return $.ajax({url: url, cache: false}).done(function(data) {
                                imports[url] = rewrite(data.replace(/\/\*(?:[^*]|\*+[^\/*])*\*+\/|^((?!:).)?\/\/.*/g, ''), url);
                            }).fail(function(xhr, status, error) {
                                imports[url] = "/* Can't resolve import '" + url + "' (" + status + ", " + error + ") */";
                            });
                        }
                    );
                }

                return match;
            });

            queuedWhen(queue).always(function() {

                source = source.replace(importRegex, function(match, url) {
                    return imports[url] ? imports[url] : match;
                });

                if (queue.length) {
                    source = resolve(source);
                } else {
                    deferred.resolve(source);
                }
            });

            return source;

        })(rewrite(source.replace(/\/\*(?:[^*]|\*+[^\/*])*\*+\/|^((?!:).)?\/\/.*/g, '')));

        return deferred.promise();
    }

    function getVars(source) {

        var i, vars = {}, lines = source.split("\n");

        for (i = 0, max = lines.length; i < max; i++) {

            var line = $.trim(lines[i]);

            if (!line.length) continue;
            if (!/@[\w\-]+\s*:.[^;]*;/.test(line)) continue;

            var keyval = $.trim(line.replace(";", "").replace(/\s+/, "")).split(":");

            keyval[1] = $.trim(keyval[1].replace(";","").split('//')[0]);
            vars[keyval[0]] = keyval[1];
        }

        return vars;
    }

    function rewriteUrls(source, baseUrl) {
        return source.replace(/url\s*\(['"]?(.+?)['"]?\)/g, function(match, url) {
            return match.match(/data\:image\//) ? match : match.replace(url, pathDiff(extractUrlParts(url, baseUrl).url, baseUrl));
        });
    }

    function pathDiff(url, baseUrl) {

        var urlParts = extractUrlParts(url), urlDirs,
            baseUrlParts = extractUrlParts(baseUrl), baseUrlDirs,
            diff = "", max, i;

        if (urlParts.host !== baseUrlParts.host) {
            return "";
        }

        max = Math.max(baseUrlParts.dirs.length, urlParts.dirs.length);

        for (i = 0; i < max; i++) {
            if (baseUrlParts.dirs[i] !== urlParts.dirs[i]) { break; }
        }

        urlDirs = urlParts.dirs.slice(i);
        baseUrlDirs = baseUrlParts.dirs.slice(i);

        for (i = 0; i < baseUrlDirs.length - 1; i++) {
            diff += "../";
        }

        for (i = 0; i < urlDirs.length - 1; i++) {
            diff += urlDirs[i] + "/";
        }

        return diff + urlParts.file + urlParts.query;
    }

    function extractUrlParts(url, baseUrl) {

        var urlPartsRegex = /^((?:[a-z-]+:)?\/\/(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/,
            urlParts = url.match(urlPartsRegex), baseUrlParts,
            parts = {}, dirs = [], i;

        if (!urlParts) {
            throw new Exception("Could not parse url - '" + url + "'");
        }

        if (!urlParts[1] || urlParts[2]) {

            if (!baseUrl) {
                baseUrl = window.location.href;
            }

            baseUrlParts = baseUrl.match(urlPartsRegex);

            if (!baseUrlParts) {
                throw new Exception("Could not parse url - '" + baseUrl + "'");
            }

            urlParts[1] = baseUrlParts[1];

            if (!urlParts[2]) {
                urlParts[3] = baseUrlParts[3] + urlParts[3];
            }
        }

        if (urlParts[3]) {

            dirs = urlParts[3].replace("\\", "/").split("/");

            for (i = 0; i < dirs.length; i++) {
                if (dirs[i] === ".." && i > 0) {
                    dirs.splice(i-1, 2);
                    i -= 2;
                }
            }
        }

        parts.host = urlParts[1];
        parts.path = urlParts[1] + dirs.join("/");
        parts.file = urlParts[4] || "";
        parts.query = urlParts[5] || "";
        parts.url = parts.path + parts.file + parts.query;
        parts.dirs = dirs;

        return parts;
    }

    $.less = $.less || (function() {
        return {
            'getCSS': getCSS,
            'getVars': getVars,
            'resolveImports': resolveImports,
            'rewriteUrls': rewriteUrls,
            'pathDiff': pathDiff,
            'extractUrlParts': extractUrlParts
        };
    })();

})(jQuery);<|endoftext|>


<commit_before>var THREEx	= THREEx || {};

/**
 * [ description]
 * @param  {[type]} skinUrl [description]
 * @return {[type]}         [description]
 */
THREEx.MinecraftChar	= function(skinUrl){
	// set default arguments values
	skinUrl	= skinUrl || (THREEx.MinecraftChar.baseUrl + "images/jetienne.png")

	//////////////////////////////////////////////////////////////////////////////////
	//		comment								//
	//////////////////////////////////////////////////////////////////////////////////
	var texture	= THREE.ImageUtils.loadTexture( skinUrl );
	texture.magFilter	= THREE.NearestFilter;
	texture.minFilter	= THREE.NearestFilter;
	this.texture	= texture

	var material	= new THREE.MeshBasicMaterial({
		map	: texture
	});
	var materialTran= new THREE.MeshBasicMaterial({
		map		: texture,
		transparent	: true,
		depthWrite	: false,
		side		: THREE.DoubleSide
	})

	//////////////////////////////////////////////////////////////////////////////////
	//		define size constant						//
	//////////////////////////////////////////////////////////////////////////////////
	var sizes	= {};
	sizes.charH	= 1;
	sizes.pixRatio	= 1/32;

	sizes.headH	=  8 * sizes.pixRatio;
	sizes.headW	=  8 * sizes.pixRatio;
	sizes.headD	=  8 * sizes.pixRatio;

	sizes.helmetH	=  9 * sizes.pixRatio;
	sizes.helmetW	=  9 * sizes.pixRatio;
	sizes.helmetD	=  9 * sizes.pixRatio;

	sizes.bodyH	= 12 * sizes.pixRatio;
	sizes.bodyW	=  8 * sizes.pixRatio;
	sizes.bodyD	=  4 * sizes.pixRatio;

	sizes.legH	= 12 * sizes.pixRatio;
	sizes.legW	=  4 * sizes.pixRatio;
	sizes.legD	=  4 * sizes.pixRatio;

	sizes.armH	= 12 * sizes.pixRatio;
	sizes.armW	=  4 * sizes.pixRatio;
	sizes.armD	=  4 * sizes.pixRatio;

	// build model core hierachy
	// - origin between 2 feet
	// - height of full character is 1
	var model	= this;
	model.root	= new THREE.Object3D;

	var group	= new THREE.Object3D()
	group.position.y= sizes.charH - sizes.headH
	model.headGroup	= group
	model.root.add(model.headGroup)

	// build model.head
	var geometry	= new THREE.CubeGeometry(sizes.headW, sizes.headH, sizes.headD)
	mapUv(geometry, 0, 16, 24, 24, 16)	// left
	mapUv(geometry, 1,  0, 24,  8, 16)	// right
	mapUv(geometry, 2,  8, 32, 16, 24)	// top
	mapUv(geometry, 3, 16, 32, 24, 24)	// bottom
	mapUv(geometry, 4,  8, 24, 16, 16)	// front
	mapUv(geometry, 5, 24, 24, 32, 16)	// back
	var mesh	= new THREE.Mesh(geometry, material)
	mesh.position.y	= sizes.headH/2
	model.head	= mesh
	model.headGroup.add(model.head)

	// build model.helmet
	var geometry	= new THREE.CubeGeometry(sizes.helmetH, sizes.helmetH, sizes.helmetH)
	model.helmet	= new THREE.Mesh(geometry, materialTran)
	model.headGroup.add(model.helmet)
	model.helmet.position.y	= sizes.headH/2
	mapUv(geometry, 0, 48, 24, 56, 16)	// left
	mapUv(geometry, 1, 32, 24, 40, 16)	// right
	mapUv(geometry, 2, 40, 32, 48, 24)	// top
	mapUv(geometry, 3, 48, 32, 56, 24)	// bottom
	mapUv(geometry, 4, 40, 24, 48, 16)	// front
	mapUv(geometry, 5, 56, 24, 64, 16)	// back

	// build model.body
	var geometry	= new THREE.CubeGeometry(sizes.bodyW, sizes.bodyH, sizes.bodyD)
	model.body	= new THREE.Mesh(geometry, material)
	model.root.add(model.body)
	model.body.position.y	= sizes.legH + sizes.bodyH/2
	mapUv(geometry, 0, 28, 12, 32,  0)	// left
	mapUv(geometry, 1, 16, 12, 20,  0)	// right
	mapUv(geometry, 2, 20, 16, 28, 12)	// top
	mapUv(geometry, 3, 28, 16, 32, 12)	// bottom
	mapUv(geometry, 4, 20, 12, 28,  0)	// front
	mapUv(geometry, 5, 32, 12, 40,  0)	// back

	// build model.armR
	var geometry	= new THREE.CubeGeometry(sizes.armW, sizes.armH, sizes.armD)
	model.armR	= new THREE.Mesh(geometry, material)
	model.root.add(model.armR)
	geometry.applyMatrix( new THREE.Matrix4().makeTranslation(0, -sizes.armH/2 + sizes.armW/2, 0) );
	model.armR.position.x	= -sizes.bodyW/2 - sizes.armW/2
	model.armR.position.y	=  sizes.legH + sizes.bodyH - sizes.armW/2
	mapUv(geometry, 0, 48, 12, 52,  0)	// right
	mapUv(geometry, 1, 40, 12, 44,  0)	// left
	mapUv(geometry, 2, 44, 16, 48, 12)	// top
	mapUv(geometry, 3, 48, 16, 52, 12)	// bottom
	mapUv(geometry, 4, 44, 12, 48,  0)	// front
	mapUv(geometry, 5, 52, 12, 56,  0)	// back

	// build model.armL
	var geometry	= new THREE.CubeGeometry(sizes.armW, sizes.armH, sizes.armD)
	model.armL	= new THREE.Mesh(geometry, material)
	model.root.add(model.armL)
	geometry.applyMatrix( new THREE.Matrix4().makeTranslation(0, -sizes.armH/2 + sizes.armW/2, 0) );
	model.armL.position.x	= sizes.bodyW/2 + sizes.armW/2
	model.armL.position.y	= sizes.legH + sizes.bodyH - sizes.armW/2
	mapUv(geometry, 0, 44, 12, 40,  0)	// right
	mapUv(geometry, 1, 52, 12, 48,  0)	// left
	mapUv(geometry, 2, 44, 16, 48, 12)	// top
	mapUv(geometry, 3, 48, 16, 52, 12)	// bottom
	mapUv(geometry, 4, 48, 12, 44,  0)	// front
	mapUv(geometry, 5, 56, 12, 52,  0)	// back

	// build model.legR
	var geometry	= new THREE.CubeGeometry(sizes.legW, sizes.legH, sizes.legD)
	model.legR	= new THREE.Mesh(geometry, material)
	model.root.add(model.legR)
	geometry.applyMatrix( new THREE.Matrix4().makeTranslation(0, -sizes.legH/2, 0) );
	model.legR.position.x	= -sizes.legW/2
	model.legR.position.y	=  sizes.legH
	mapUv(geometry, 0,  8, 12, 12,  0)	// right
	mapUv(geometry, 1,  0, 12,  4,  0)	// left
	mapUv(geometry, 2,  4, 16,  8, 12)	// top
	mapUv(geometry, 3,  8, 16, 12, 12)	// bottom
	mapUv(geometry, 4,  4, 12,  8,  0)	// front
	mapUv(geometry, 5, 12, 12, 16,  0)	// back

	// build model.legL
	var geometry	= new THREE.CubeGeometry(sizes.legW, sizes.legH, sizes.legD)
	model.legL	= new THREE.Mesh(geometry, material)
	model.root.add(model.legL)
	geometry.applyMatrix( new THREE.Matrix4().makeTranslation(0, -sizes.legH/2, 0) );
	model.legL.position.x	= sizes.legW/2
	model.legL.position.y	= sizes.legH
	mapUv(geometry, 0,  4, 12,  0,  0)	// left
	mapUv(geometry, 1, 12, 12,  8,  0)	// right
	mapUv(geometry, 2,  8, 16,  4, 12)	// top
	mapUv(geometry, 3, 12, 16,  8, 12)	// bottom
	mapUv(geometry, 4,  8, 12,  4,  0)	// front
	mapUv(geometry, 5, 16, 12, 12,  0)	// back

	return

	function mapUv(geometry, faceIdx, x1, y1, x2, y2){
		var tileUvW	= 1/64;
		var tileUvH	= 1/32;
		var UVs		= geometry.faceVertexUvs[0][faceIdx * 2];
		UVs[0].x = x1 * tileUvW;	UVs[0].y = y1 * tileUvH;
		UVs[1].x = x1 * tileUvW;	UVs[1].y = y2 * tileUvH;
		UVs[2].x = x2 * tileUvW;	UVs[2].y = y1 * tileUvH;

		var UVs		= geometry.faceVertexUvs[0][faceIdx * 2 + 1];
		UVs[0].x = x1 * tileUvW;	UVs[0].y = y2 * tileUvH;
		UVs[1].x = x2 * tileUvW;	UVs[1].y = y2 * tileUvH;
		UVs[2].x = x2 * tileUvW;	UVs[2].y = y1 * tileUvH;
	}
}

THREEx.MinecraftChar.baseUrl	= '../'

/**
 * Load a skin
 *
 * @param {string} url the url of the skin image
*/
THREEx.MinecraftChar.prototype.loadSkin	= function(url, onLoad){
	var image	= new Image();
	image.onload	= function () {
		this.texture.image		= image;
		this.texture.needsUpdate	= true;
		onLoad	&& onLoad(this)
	}.bind(this);
	image.src = url;
	return this;	// for chained API
}

//////////////////////////////////////////////////////////////////////////////////
//		support for skin Well Known Url					//
//////////////////////////////////////////////////////////////////////////////////

THREEx.MinecraftChar.prototype.loadWellKnownSkin	= function(name, onLoad){
	console.assert(THREEx.MinecraftChar.skinWellKnownUrls[name])
	var url	= THREEx.MinecraftChar.baseUrl + THREEx.MinecraftChar.skinWellKnownUrls[name];
	return this.loadSkin(url, onLoad)
}

THREEx.MinecraftChar.skinWellKnownUrls	= {
	'3djesus'		: 'images/3djesus.png',
	'iron-man'		: 'images/Iron-Man-Minecraft-Skin.png',
	'joker'			: 'images/Joker.png',
	'mario'			: 'images/Mario.png',
	'sonicthehedgehog'	: 'images/Sonicthehedgehog.png',
	'spiderman'		: 'images/Spiderman.png',
	'superman'		: 'images/Superman.png',
	'agentsmith'		: 'images/agentsmith.png',
	'batman'		: 'images/batman.png',
	'char'			: 'images/char.png',
	'god'			: 'images/god.png',
	'jetienne'		: 'images/jetienne.png',
	'martialartist'		: 'images/martialartist.png',
	'robocop'		: 'images/robocop.png',
	'theflash'		: 'images/theflash.png',
	'woody'			: 'images/woody.png',
}<commit_msg>added THREE.Face4 support<commit_after>var THREEx	= THREEx || {};

/**
 * [ description]
 * @param  {[type]} skinUrl [description]
 * @return {[type]}         [description]
 */
THREEx.MinecraftChar	= function(skinUrl){
	// set default arguments values
	skinUrl	= skinUrl || (THREEx.MinecraftChar.baseUrl + "images/jetienne.png")

	//////////////////////////////////////////////////////////////////////////////////
	//		comment								//
	//////////////////////////////////////////////////////////////////////////////////
	var texture	= THREE.ImageUtils.loadTexture( skinUrl );
	texture.magFilter	= THREE.NearestFilter;
	texture.minFilter	= THREE.NearestFilter;
	this.texture	= texture

	var material	= new THREE.MeshBasicMaterial({
		map	: texture
	});
	var materialTran= new THREE.MeshBasicMaterial({
		map		: texture,
		transparent	: true,
		depthWrite	: false,
		side		: THREE.DoubleSide
	})

	//////////////////////////////////////////////////////////////////////////////////
	//		define size constant						//
	//////////////////////////////////////////////////////////////////////////////////
	var sizes	= {};
	sizes.charH	= 1;
	sizes.pixRatio	= 1/32;

	sizes.headH	=  8 * sizes.pixRatio;
	sizes.headW	=  8 * sizes.pixRatio;
	sizes.headD	=  8 * sizes.pixRatio;

	sizes.helmetH	=  9 * sizes.pixRatio;
	sizes.helmetW	=  9 * sizes.pixRatio;
	sizes.helmetD	=  9 * sizes.pixRatio;

	sizes.bodyH	= 12 * sizes.pixRatio;
	sizes.bodyW	=  8 * sizes.pixRatio;
	sizes.bodyD	=  4 * sizes.pixRatio;

	sizes.legH	= 12 * sizes.pixRatio;
	sizes.legW	=  4 * sizes.pixRatio;
	sizes.legD	=  4 * sizes.pixRatio;

	sizes.armH	= 12 * sizes.pixRatio;
	sizes.armW	=  4 * sizes.pixRatio;
	sizes.armD	=  4 * sizes.pixRatio;

	// build model core hierachy
	// - origin between 2 feet
	// - height of full character is 1
	var model	= this;
	model.root	= new THREE.Object3D;

	var group	= new THREE.Object3D()
	group.position.y= sizes.charH - sizes.headH
	model.headGroup	= group
	model.root.add(model.headGroup)

	// build model.head
	var geometry	= new THREE.CubeGeometry(sizes.headW, sizes.headH, sizes.headD)
	mapUv(geometry, 0, 16, 24, 24, 16)	// left
	mapUv(geometry, 1,  0, 24,  8, 16)	// right
	mapUv(geometry, 2,  8, 32, 16, 24)	// top
	mapUv(geometry, 3, 16, 32, 24, 24)	// bottom
	mapUv(geometry, 4,  8, 24, 16, 16)	// front
	mapUv(geometry, 5, 24, 24, 32, 16)	// back
	var mesh	= new THREE.Mesh(geometry, material)
	mesh.position.y	= sizes.headH/2
	model.head	= mesh
	model.headGroup.add(model.head)

	// build model.helmet
	var geometry	= new THREE.CubeGeometry(sizes.helmetH, sizes.helmetH, sizes.helmetH)
	model.helmet	= new THREE.Mesh(geometry, materialTran)
	model.headGroup.add(model.helmet)
	model.helmet.position.y	= sizes.headH/2
	mapUv(geometry, 0, 48, 24, 56, 16)	// left
	mapUv(geometry, 1, 32, 24, 40, 16)	// right
	mapUv(geometry, 2, 40, 32, 48, 24)	// top
	mapUv(geometry, 3, 48, 32, 56, 24)	// bottom
	mapUv(geometry, 4, 40, 24, 48, 16)	// front
	mapUv(geometry, 5, 56, 24, 64, 16)	// back

	// build model.body
	var geometry	= new THREE.CubeGeometry(sizes.bodyW, sizes.bodyH, sizes.bodyD)
	model.body	= new THREE.Mesh(geometry, material)
	model.root.add(model.body)
	model.body.position.y	= sizes.legH + sizes.bodyH/2
	mapUv(geometry, 0, 28, 12, 32,  0)	// left
	mapUv(geometry, 1, 16, 12, 20,  0)	// right
	mapUv(geometry, 2, 20, 16, 28, 12)	// top
	mapUv(geometry, 3, 28, 16, 32, 12)	// bottom
	mapUv(geometry, 4, 20, 12, 28,  0)	// front
	mapUv(geometry, 5, 32, 12, 40,  0)	// back

	// build model.armR
	var geometry	= new THREE.CubeGeometry(sizes.armW, sizes.armH, sizes.armD)
	model.armR	= new THREE.Mesh(geometry, material)
	model.root.add(model.armR)
	geometry.applyMatrix( new THREE.Matrix4().makeTranslation(0, -sizes.armH/2 + sizes.armW/2, 0) );
	model.armR.position.x	= -sizes.bodyW/2 - sizes.armW/2
	model.armR.position.y	=  sizes.legH + sizes.bodyH - sizes.armW/2
	mapUv(geometry, 0, 48, 12, 52,  0)	// right
	mapUv(geometry, 1, 40, 12, 44,  0)	// left
	mapUv(geometry, 2, 44, 16, 48, 12)	// top
	mapUv(geometry, 3, 48, 16, 52, 12)	// bottom
	mapUv(geometry, 4, 44, 12, 48,  0)	// front
	mapUv(geometry, 5, 52, 12, 56,  0)	// back

	// build model.armL
	var geometry	= new THREE.CubeGeometry(sizes.armW, sizes.armH, sizes.armD)
	model.armL	= new THREE.Mesh(geometry, material)
	model.root.add(model.armL)
	geometry.applyMatrix( new THREE.Matrix4().makeTranslation(0, -sizes.armH/2 + sizes.armW/2, 0) );
	model.armL.position.x	= sizes.bodyW/2 + sizes.armW/2
	model.armL.position.y	= sizes.legH + sizes.bodyH - sizes.armW/2
	mapUv(geometry, 0, 44, 12, 40,  0)	// right
	mapUv(geometry, 1, 52, 12, 48,  0)	// left
	mapUv(geometry, 2, 44, 16, 48, 12)	// top
	mapUv(geometry, 3, 48, 16, 52, 12)	// bottom
	mapUv(geometry, 4, 48, 12, 44,  0)	// front
	mapUv(geometry, 5, 56, 12, 52,  0)	// back

	// build model.legR
	var geometry	= new THREE.CubeGeometry(sizes.legW, sizes.legH, sizes.legD)
	model.legR	= new THREE.Mesh(geometry, material)
	model.root.add(model.legR)
	geometry.applyMatrix( new THREE.Matrix4().makeTranslation(0, -sizes.legH/2, 0) );
	model.legR.position.x	= -sizes.legW/2
	model.legR.position.y	=  sizes.legH
	mapUv(geometry, 0,  8, 12, 12,  0)	// right
	mapUv(geometry, 1,  0, 12,  4,  0)	// left
	mapUv(geometry, 2,  4, 16,  8, 12)	// top
	mapUv(geometry, 3,  8, 16, 12, 12)	// bottom
	mapUv(geometry, 4,  4, 12,  8,  0)	// front
	mapUv(geometry, 5, 12, 12, 16,  0)	// back

	// build model.legL
	var geometry	= new THREE.CubeGeometry(sizes.legW, sizes.legH, sizes.legD)
	model.legL	= new THREE.Mesh(geometry, material)
	model.root.add(model.legL)
	geometry.applyMatrix( new THREE.Matrix4().makeTranslation(0, -sizes.legH/2, 0) );
	model.legL.position.x	= sizes.legW/2
	model.legL.position.y	= sizes.legH
	mapUv(geometry, 0,  4, 12,  0,  0)	// left
	mapUv(geometry, 1, 12, 12,  8,  0)	// right
	mapUv(geometry, 2,  8, 16,  4, 12)	// top
	mapUv(geometry, 3, 12, 16,  8, 12)	// bottom
	mapUv(geometry, 4,  8, 12,  4,  0)	// front
	mapUv(geometry, 5, 16, 12, 12,  0)	// back

	return

	function mapUv(geometry, faceIdx, x1, y1, x2, y2){
		var tileUvW	= 1/64;
		var tileUvH	= 1/32;
		if( geometry.faces[faceIdx] instanceof THREE.Face3 ){
			var UVs		= geometry.faceVertexUvs[0][faceIdx * 2];
			UVs[0].x = x1 * tileUvW;	UVs[0].y = y1 * tileUvH;
			UVs[1].x = x1 * tileUvW;	UVs[1].y = y2 * tileUvH;
			UVs[2].x = x2 * tileUvW;	UVs[2].y = y1 * tileUvH;

			var UVs		= geometry.faceVertexUvs[0][faceIdx * 2 + 1];
			UVs[0].x = x1 * tileUvW;	UVs[0].y = y2 * tileUvH;
			UVs[1].x = x2 * tileUvW;	UVs[1].y = y2 * tileUvH;
			UVs[2].x = x2 * tileUvW;	UVs[2].y = y1 * tileUvH;
		}else if( geometry.faces[faceIdx] instanceof THREE.Face4 ){
			var UVs                = geometry.faceVertexUvs[0][faceIdx];
			UVs[0].x = x1 * tileUvW;        UVs[0].y = y1 * tileUvH;
			UVs[1].x = x1 * tileUvW;        UVs[1].y = y2 * tileUvH;
			UVs[2].x = x2 * tileUvW;        UVs[2].y = y2 * tileUvH;
			UVs[3].x = x2 * tileUvW;        UVs[3].y = y1 * tileUvH;
		}else	console.assert(false)
	}
}

THREEx.MinecraftChar.baseUrl	= '../'

/**
 * Load a skin
 *
 * @param {string} url the url of the skin image
*/
THREEx.MinecraftChar.prototype.loadSkin	= function(url, onLoad){
	var image	= new Image();
	image.onload	= function () {
		this.texture.image		= image;
		this.texture.needsUpdate	= true;
		onLoad	&& onLoad(this)
	}.bind(this);
	image.src = url;
	return this;	// for chained API
}

//////////////////////////////////////////////////////////////////////////////////
//		support for skin Well Known Url					//
//////////////////////////////////////////////////////////////////////////////////

THREEx.MinecraftChar.prototype.loadWellKnownSkin	= function(name, onLoad){
	console.assert(THREEx.MinecraftChar.skinWellKnownUrls[name])
	var url	= THREEx.MinecraftChar.baseUrl + THREEx.MinecraftChar.skinWellKnownUrls[name];
	return this.loadSkin(url, onLoad)
}

THREEx.MinecraftChar.skinWellKnownUrls	= {
	'3djesus'		: 'images/3djesus.png',
	'iron-man'		: 'images/Iron-Man-Minecraft-Skin.png',
	'joker'			: 'images/Joker.png',
	'mario'			: 'images/Mario.png',
	'sonicthehedgehog'	: 'images/Sonicthehedgehog.png',
	'spiderman'		: 'images/Spiderman.png',
	'superman'		: 'images/Superman.png',
	'agentsmith'		: 'images/agentsmith.png',
	'batman'		: 'images/batman.png',
	'char'			: 'images/char.png',
	'god'			: 'images/god.png',
	'jetienne'		: 'images/jetienne.png',
	'martialartist'		: 'images/martialartist.png',
	'robocop'		: 'images/robocop.png',
	'theflash'		: 'images/theflash.png',
	'woody'			: 'images/woody.png',
}<|endoftext|>


<commit_before>/*!
 * Expose `Poplar`.
 */

module.exports = Poplar;

/*!
 * Module dependencies.
 */

var EventEmitter = require('events').EventEmitter;
var debug = require('debug')('poplar:apis');
var util = require('util');
var Url = require('url');
var inherits = util.inherits;
var assert = require('assert');
var path = require('path');
var _ = require('lodash');
var minimatch = require('minimatch');

var Dynamic = require('./dynamic');
var ApiBuilder = require('./api_builder');

function Poplar(options) {
  EventEmitter.call(this, { wildcard: true });
  // Avoid warning: possible EventEmitter memory leak detected
  this.setMaxListeners(16);
  this.options = options || {};
  this._apiBuilders = {};
  this._methods = {};
  this._listenerTree = {};
};

/*!
 * Simplified APIs
 */

Poplar.create = function(options) {
  return new Poplar(options);
};

/*!
 * Inherit from `EventEmitter`.
 */

inherits(Poplar, EventEmitter);

/**
 * Get all methods
 */

Poplar.prototype.allMethods = function() {
  return _.values(this._methods) || [];
};

/**
 * Use a ApiBuilder
 */

Poplar.prototype.use = function(apiBuilder) {
  if (_.isObject(apiBuilder) && !(apiBuilder instanceof ApiBuilder)) {
    apiBuilder = new ApiBuilder(apiBuilder);
  }

  var name = apiBuilder.name;
  var self = this;

  // look up apiBuilder for the collection.
  if (!this._apiBuilders[name]) {
    if (apiBuilder) {
      // cache it so we only apply apiBuilders once
      this._apiBuilders[name] = apiBuilder;
      mergeListeners(apiBuilder);
      mergeMethods(apiBuilder);
    }
  } else {
    assert(false, util.format('Can\'t use the same ApiBuilder: %s more than once!', name));
  }

  // look up all ApiBuilder listeners then add it to api
  function mergeListeners(emitter) {
    var events = emitter._events;
    _.each(events, function(fns, type) {
      if (Array.isArray(fns)) {
        _.each(fns, function(fn) {
          if (_.isFunction(fn)) {
            self.on(type, fn);
          }
        })
      } else if (_.isFunction(fns)) {
        self.on(type, fns);
      }
    })
  };

  // look up all ApiBuilder methods then add it to api
  function mergeMethods(builder) {
    var methods = builder.methods();
    _.each(methods, function(fn, methodName) {
      // e.g.: users.getUserInfo
      self._methods[util.format('%s.%s', name, methodName)] = fn;
    });
  };
};

/**
 * Create a handler from the given adapter.
 *
 * @param {String} name Adapter name
 * @param {Object} options Adapter options
 * @return {Function}
 */

Poplar.prototype.handler = function(name, options) {
  var Adapter = this.adapter(name);
  var adapter = new Adapter(this, options);
  var handler = adapter.createHandler();

  if (handler) {
    // allow adapter reference from handler
    handler.adapter = adapter;
  }

  return handler;
};

/**
 * Get an adapter by name.
 * @param {String} name The adapter name
 * @return {Adapter}
 */
Poplar.prototype.adapter = function(name) {
  return require(path.join(__dirname, 'adapters', name));
};

/**
 * Define a named type conversion. The conversion is used when a
 * `ApiMethod` argument defines a type with the given `name`.
 *
 * ```js
 * api.defineType('MyType', function(val, ctx) {
 *   // use the val and ctx objects to return the concrete value
 *   return new MyType(val);
 * });
 * ```
 *
 * @param {String} name The type name
 * @param {Function} converter
 */

Poplar.defineType = function(name, fn) {
  Dynamic.define(name, fn);
};

/**
 * Execute the given function before the matched method string.
 *
 * **Examples:**
 *
 * ```js
 * // Do something before our `user.greet` example, earlier.
 * api.before('user.greet', function(ctx, next) {
 *   if ((ctx.req.param('password') || '').toString() !== '1234') {
 *     next(new Error('Bad password!'));
 *   } else {
 *     next();
 *   }
 * });
 *
 * // Do something before any `user` method.
 * api.before('user.*', function(ctx, next) {
 *   console.log('Calling a user method.');
 *   next();
 * });
 *
 * // Do something before a `dog` instance method.
 * api.before('dog.*', function(ctx, next) {
 *   var dog = this;
 *   console.log('Calling a method on "%s".', dog.name);
 *   next();
 * });
 * ```
 *
 * @param {String} methodMatch The glob to match a method string
 * @callback {Function} hook
 * @param {Context} ctx The adapter specific context
 * @param {Function} next Call with an optional error object
 * @param {ApiMethod} method The ApiMethod object
 */

Poplar.prototype.before = function(methodMatch, fn) {
  this.on('before.' + methodMatch, fn);
};

/**
 * Execute the given `hook` function after the matched method string.
 *
 * **Examples:**
 *
 * ```js
 * // Do something after the `speak` instance method.
 * // NOTE: you cannot cancel a method after it has been called.
 * api.after('dog.speak', function(ctx, next) {
 *   console.log('After speak!');
 *   next();
 * });
 *
 * // Do something before all methods.
 * api.before('**', function(ctx, next, method) {
 *   console.log('Calling:', method.name);
 *   next();
 * });
 *
 * // Modify all returned values named `result`.
 * api.after('**', function(ctx, next) {
 *   ctx.result += '!!!';
 *   next();
 * });
 * ```
 *
 * @param {String} methodMatch The glob to match a method string
 * @callback {Function} hook
 * @param {Context} ctx The adapter specific context
 * @param {Function} next Call with an optional error object
 * @param {ApiMethod} method The ApiMethod object
 */

Poplar.prototype.after = function(methodMatch, fn) {
  this.on('after.' + methodMatch, fn);
};

/**
 * Execute the given `hook` function after the method matched by the method
 * string failed.
 *
 * **Examples:**
 *
 * ```js
 * // Do something after the `speak` instance method failed.
 * api.afterError('dog.speak', function(ctx, next) {
 *   console.log('Cannot speak!', ctx.error);
 *   next();
 * });
 *
 * // Do something before all methods.
 * api.afterError('**', function(ctx, next, method) {
 *   console.log('Failed', method.name, ctx.error);
 *   next();
 * });
 *
 * // Modify all returned errors
 * api.after('**', function(ctx, next) {
 *   if (!ctx.error.details) ctx.result.details = {};
 *   ctx.error.details.info = 'intercepted by a hook';
 *   next();
 * });
 *
 * // Report a different error
 * api.after('dog.speak', function(ctx, next) {
 *   console.error(ctx.error);
 *   next(new Error('See server console log for details.'));
 * });
 * ```
 *
 * @param {String} methodMatch The glob to match a method string
 * @callback {Function} hook
 * @param {Context} ctx The adapter specific context
 * @param {Function} next Call with an optional error object
 * @param {ApiMethod} method The ApiMethod object
 */

Poplar.prototype.afterError = function(methodMatch, fn) {
  this.on('afterError.' + methodMatch, fn);
};

/*!
 * Create a middleware style emit that supports wildcards.
 */

Poplar.prototype.execHooks = function(when, method, ctx, next) {
  var methodName = method.fullName();
  var stack = [];

  var listenerNames = this.searchListenerTree(methodName, when);

  if (_.isEmpty(stack)) {
    listenerNames = this.searchListeners(methodName, when);
  }

  var self = this;

  listenerNames.forEach(function(listenerName) {
    addToStack(self.listeners(listenerName));
  });

  function addToStack(fn) {
    stack = stack.concat(fn);
  }

  function execStack(err) {
    if (err) return next(err);

    var cur = stack.shift();

    if (cur) {
      try {
        var result = cur.call(method, ctx, execStack, method);
        if (result && typeof result.then === 'function') {
          result.then(function() { next(); }, next);
        }
      } catch (err) {
        next(err);
      }
    } else {
      next();
    }
  }

  return execStack();
};

/**
 * Invoke the given api method using the supplied context.
 * Execute registered before/after hooks.
 * @param {Object} ctx
 * @param {Object} method
 * @param {function(Error=)} cb
 */
Poplar.prototype.invokeMethodInContext = function(ctx, method, cb) {
  var self = this;

  self.execHooks('before', method, ctx, function(err) {
    if (err) return triggerErrorAndCallBack(err);

    method.invoke(ctx, function(err, result) {
      ctx.result = result;
      if (err) return cb(err);

      self.execHooks('after', method, ctx, function(err) {
        if (err) return triggerErrorAndCallBack(err);
        cb();
      });
    });

  });

  function triggerErrorAndCallBack(err) {
    ctx.error = err;
    self.execHooks('afterError', method, ctx, function(hookErr) {
      cb(hookErr || err);
    });
  }
};

/*!
 * Search listeners before or after a given method is called
 * @param {String} methodName name for method
 * @param {String} type: `before`, `after`, `afterError`
 */

Poplar.prototype.searchListeners = function(methodName, type) {
  var allListenerNames = Object.keys(this._events);
  var listenerNames = [];
  var fullType = util.format('%s.%s', type, methodName);
  _.each(allListenerNames, function(name) {
    if (minimatch(fullType, name)) {
      listenerNames.push(name);
    }
  })
  return listenerNames;
};

/*!
 * Search listenerTree before or after a given method is called
 * @param {String} methodName name for method
 * @param {String} type: `before`, `after`, `afterError`
 */

Poplar.prototype.searchListenerTree = function(methodName, type) {
  var listeners = this._listenerTree[methodName];
  if (listeners) {
    return listeners[type];
  } else {
    return [];
  }
};

/*!
 * Analyze all listeners coorespond with methods
 *
 * {
 *   'users.info': {
 *     before: ['before.users.*', 'before.users.info'],
 *     after: ['after.users.*', 'after.users.info'],
 *     afterError: ['afterError.users.*', 'afterError.users.info']
 *   }
 * }
 */

Poplar.prototype.analyzeListenerTree = function() {
  var methods = this._methods;
  var listenerTree = {};
  var self = this;

  _.each(methods, function(fn, name) {
    listenerTree[name] = listenerTree[name] || {};
    ['before', 'after', 'afterError'].forEach(function(type) {
      listenerTree[name][type] = self.searchListeners(name, type) || [];
    });
  });

  this._listenerTree = listenerTree;
};
<commit_msg>change comments<commit_after>/*!
 * Expose `Poplar`.
 */

module.exports = Poplar;

/*!
 * Module dependencies.
 */

var EventEmitter = require('events').EventEmitter;
var debug = require('debug')('poplar:apis');
var util = require('util');
var Url = require('url');
var inherits = util.inherits;
var assert = require('assert');
var path = require('path');
var _ = require('lodash');
var minimatch = require('minimatch');

var Dynamic = require('./dynamic');
var ApiBuilder = require('./api_builder');

function Poplar(options) {
  EventEmitter.call(this);
  // Avoid warning: possible EventEmitter memory leak detected
  this.setMaxListeners(16);
  this.options = options || {};
  this._apiBuilders = {};
  this._methods = {};
  this._listenerTree = {};
};

/*!
 * Simplified APIs
 */

Poplar.create = function(options) {
  return new Poplar(options);
};

/*!
 * Inherit from `EventEmitter`.
 */

inherits(Poplar, EventEmitter);

/**
 * Get all methods
 */

Poplar.prototype.allMethods = function() {
  return _.values(this._methods) || [];
};

/**
 * Use a ApiBuilder
 */

Poplar.prototype.use = function(apiBuilder) {
  if (_.isObject(apiBuilder) && !(apiBuilder instanceof ApiBuilder)) {
    apiBuilder = new ApiBuilder(apiBuilder);
  }

  var name = apiBuilder.name;
  var self = this;

  // look up apiBuilder for the collection.
  if (!this._apiBuilders[name]) {
    if (apiBuilder) {
      // cache it so we only apply apiBuilders once
      this._apiBuilders[name] = apiBuilder;
      mergeListeners(apiBuilder);
      mergeMethods(apiBuilder);
    }
  } else {
    assert(false, util.format('Can\'t use the same ApiBuilder: %s more than once!', name));
  }

  // look up all ApiBuilder listeners then add it to api
  function mergeListeners(emitter) {
    var events = emitter._events;
    _.each(events, function(fns, type) {
      if (Array.isArray(fns)) {
        _.each(fns, function(fn) {
          if (_.isFunction(fn)) {
            self.on(type, fn);
          }
        })
      } else if (_.isFunction(fns)) {
        self.on(type, fns);
      }
    })
  };

  // look up all ApiBuilder methods then add it to api
  function mergeMethods(builder) {
    var methods = builder.methods();
    _.each(methods, function(fn, methodName) {
      // e.g.: users.getUserInfo
      self._methods[util.format('%s.%s', name, methodName)] = fn;
    });
  };
};

/**
 * Create a handler from the given adapter.
 *
 * @param {String} name Adapter name
 * @param {Object} options Adapter options
 * @return {Function}
 */

Poplar.prototype.handler = function(name, options) {
  var Adapter = this.adapter(name);
  var adapter = new Adapter(this, options);
  var handler = adapter.createHandler();

  if (handler) {
    // allow adapter reference from handler
    handler.adapter = adapter;
  }

  return handler;
};

/**
 * Get an adapter by name.
 * @param {String} name The adapter name
 * @return {Adapter}
 */
Poplar.prototype.adapter = function(name) {
  return require(path.join(__dirname, 'adapters', name));
};

/**
 * Define a named type conversion. The conversion is used when a
 * `ApiMethod` argument defines a type with the given `name`.
 *
 * ```js
 * api.defineType('MyType', function(val, ctx) {
 *   // use the val and ctx objects to return the concrete value
 *   return new MyType(val);
 * });
 * ```
 *
 * @param {String} name The type name
 * @param {Function} converter
 */

Poplar.defineType = function(name, fn) {
  Dynamic.define(name, fn);
};

/**
 * Execute the given function before the matched method string.
 *
 * **Examples:**
 *
 * ```js
 * // Do something before our `user.greet` example, earlier.
 * api.before('user.greet', function(ctx, next) {
 *   if ((ctx.req.param('password') || '').toString() !== '1234') {
 *     next(new Error('Bad password!'));
 *   } else {
 *     next();
 *   }
 * });
 *
 * // Do something before any `user` method.
 * api.before('user.*', function(ctx, next) {
 *   console.log('Calling a user method.');
 *   next();
 * });
 *
 * // Do something before a `dog` instance method.
 * api.before('dog.*', function(ctx, next) {
 *   var dog = this;
 *   console.log('Calling a method on "%s".', dog.name);
 *   next();
 * });
 * ```
 *
 * @param {String} methodMatch The glob to match a method string
 * @callback {Function} hook
 * @param {Context} ctx The adapter specific context
 * @param {Function} next Call with an optional error object
 * @param {ApiMethod} method The ApiMethod object
 */

Poplar.prototype.before = function(methodMatch, fn) {
  this.on('before.' + methodMatch, fn);
};

/**
 * Execute the given `hook` function after the matched method string.
 *
 * **Examples:**
 *
 * ```js
 * // Do something after the `speak` instance method.
 * // NOTE: you cannot cancel a method after it has been called.
 * api.after('dog.speak', function(ctx, next) {
 *   console.log('After speak!');
 *   next();
 * });
 *
 * // Do something before all methods.
 * api.before('**', function(ctx, next, method) {
 *   console.log('Calling:', method.name);
 *   next();
 * });
 *
 * // Modify all returned values named `result`.
 * api.after('**', function(ctx, next) {
 *   ctx.result += '!!!';
 *   next();
 * });
 * ```
 *
 * @param {String} methodMatch The glob to match a method string
 * @callback {Function} hook
 * @param {Context} ctx The adapter specific context
 * @param {Function} next Call with an optional error object
 * @param {ApiMethod} method The ApiMethod object
 */

Poplar.prototype.after = function(methodMatch, fn) {
  this.on('after.' + methodMatch, fn);
};

/**
 * Execute the given `hook` function after the method matched by the method
 * string failed.
 *
 * **Examples:**
 *
 * ```js
 * // Do something after the `speak` instance method failed.
 * api.afterError('dog.speak', function(ctx, next) {
 *   console.log('Cannot speak!', ctx.error);
 *   next();
 * });
 *
 * // Do something before all methods.
 * api.afterError('**', function(ctx, next, method) {
 *   console.log('Failed', method.name, ctx.error);
 *   next();
 * });
 *
 * // Modify all returned errors
 * api.after('**', function(ctx, next) {
 *   if (!ctx.error.details) ctx.result.details = {};
 *   ctx.error.details.info = 'intercepted by a hook';
 *   next();
 * });
 *
 * // Report a different error
 * api.after('dog.speak', function(ctx, next) {
 *   console.error(ctx.error);
 *   next(new Error('See server console log for details.'));
 * });
 * ```
 *
 * @param {String} methodMatch The glob to match a method string
 * @callback {Function} hook
 * @param {Context} ctx The adapter specific context
 * @param {Function} next Call with an optional error object
 * @param {ApiMethod} method The ApiMethod object
 */

Poplar.prototype.afterError = function(methodMatch, fn) {
  this.on('afterError.' + methodMatch, fn);
};

/*!
 * Execute Hooks by type and method.
 */

Poplar.prototype.execHooks = function(when, method, ctx, next) {
  var methodName = method.fullName();
  var stack = [];

  var listenerNames = this.searchListenerTree(methodName, when);

  if (_.isEmpty(stack)) {
    listenerNames = this.searchListeners(methodName, when);
  }

  var self = this;

  listenerNames.forEach(function(listenerName) {
    addToStack(self.listeners(listenerName));
  });

  function addToStack(fn) {
    stack = stack.concat(fn);
  }

  function execStack(err) {
    if (err) return next(err);

    var cur = stack.shift();

    if (cur) {
      try {
        var result = cur.call(method, ctx, execStack, method);
        if (result && typeof result.then === 'function') {
          result.then(function() { next(); }, next);
        }
      } catch (err) {
        next(err);
      }
    } else {
      next();
    }
  }

  return execStack();
};

/**
 * Invoke the given api method using the supplied context.
 * Execute registered before/after hooks.
 * @param {Object} ctx
 * @param {Object} method
 * @param {function(Error=)} cb
 */
Poplar.prototype.invokeMethodInContext = function(ctx, method, cb) {
  var self = this;

  self.execHooks('before', method, ctx, function(err) {
    if (err) return triggerErrorAndCallBack(err);

    method.invoke(ctx, function(err, result) {
      ctx.result = result;
      if (err) return cb(err);

      self.execHooks('after', method, ctx, function(err) {
        if (err) return triggerErrorAndCallBack(err);
        cb();
      });
    });

  });

  function triggerErrorAndCallBack(err) {
    ctx.error = err;
    self.execHooks('afterError', method, ctx, function(hookErr) {
      cb(hookErr || err);
    });
  }
};

/*!
 * Search listeners before or after a given method is called
 * @param {String} methodName name for method
 * @param {String} type: `before`, `after`, `afterError`
 */

Poplar.prototype.searchListeners = function(methodName, type) {
  var allListenerNames = Object.keys(this._events);
  var listenerNames = [];
  var fullType = util.format('%s.%s', type, methodName);
  _.each(allListenerNames, function(name) {
    if (minimatch(fullType, name)) {
      listenerNames.push(name);
    }
  })
  return listenerNames;
};

/*!
 * Search listenerTree before or after a given method is called
 * @param {String} methodName name for method
 * @param {String} type: `before`, `after`, `afterError`
 */

Poplar.prototype.searchListenerTree = function(methodName, type) {
  var listeners = this._listenerTree[methodName];
  if (listeners) {
    return listeners[type];
  } else {
    return [];
  }
};

/*!
 * Analyze all listeners coorespond with methods
 *
 * {
 *   'users.info': {
 *     before: ['before.users.*', 'before.users.info'],
 *     after: ['after.users.*', 'after.users.info'],
 *     afterError: ['afterError.users.*', 'afterError.users.info']
 *   }
 * }
 */

Poplar.prototype.analyzeListenerTree = function() {
  var methods = this._methods;
  var listenerTree = {};
  var self = this;

  _.each(methods, function(fn, name) {
    listenerTree[name] = listenerTree[name] || {};
    ['before', 'after', 'afterError'].forEach(function(type) {
      listenerTree[name][type] = self.searchListeners(name, type) || [];
    });
  });

  this._listenerTree = listenerTree;
};
<|endoftext|>


<commit_before>'use strict';
var _ = require('lodash');
var chalk = require('chalk');
var gutil = require('gulp-util');
var request = require('request-promise');
var stringify = require('json-stable-stringify');
var through = require('through2');

/**
 * Interns.
 */
var locoApi = require('./locoApi');

/**
 * Synchronizr
 * This synchronize the local json token with Loco.
 * @param {object} options Synchronize Loco's options.
 *
 * How the synchronization works?
 * This synchronization is mastered by Loco (http://localise.biz), that is to
 * say the right tokens are always from Loco. You can't erease tokens by simply
 * removes it from the json file, you should do it from Loco.
 *
 * This synchronization processes by multiple tasks:
 * 1. Check if locales given in options are comprised in Loco's locales.
 * If not, add this locale to Loco. All Loco's locales are in 'xx_XX' format
 * like 'en_CA', so your locale in 'xx' like 'en' should be converted to Loco's
 * format.
 *
 * 2. Get all tags available from Loco. If the the tags given in options aren't
 * in Loco yet, create it first. Loco will ignore not existing tags otherwise.
 *
 * 3. Get all remote catalogs associated to the locale. Then for each assets in
 * a catalog, test if the assets is already defined in your json file. You have
 * to convert null asset's values to empty string "" because Loco will not
 * import null value.
 *
 * 4. Import to Loco the processed assets.
 *
 * 5. Tag assets (tag+fuzzy).
 *
 * 6. Finaly, get remote catalog of assets from Loco to write the new json
 * localization file with translations.
 */
function Synchronizr (options) {
    this.api = new locoApi(options.apiKey);
}

/**
 * TODO: use mapping like [en][en_US, en_CA] ...
 */
Synchronizr.prototype.convertLocale = function (locale) {
    return locale + '_CA';
};

Synchronizr.prototype.testLocale = function (locale) {
    var self = this,
        convertedLocale = this.convertLocale(locale),
        locoLocales = [];

    this.api
        .getLocales()
        .then(function (apiLocales) {
            _.each(apiLocales, function (apiLocale) {
                locoLocales.push(apiLocale.code);
            });

            if (locoLocales.indexOf(convertedLocale) === -1) {
                locoLocales.push(convertedLocale);
                self.api
                    .addLocale({code: convertedLocale});
            }
        });

    gutil.log('locales: ', this.locales);
};

Synchronizr.prototype.createTags = function () {
    var self = this,
        tags = ['webapp'];

    this.api
        .getTags()
        .then(function (apiTags) {
            _.each(tags, function (tag) {
                if (apiTags.indexOf(tag) === -1) {
                    self.api
                        .createTag({name: tag});
                }
            });
        });
}

Synchronizr.prototype.sync = function (tags, content) {
    var self = this;

    this.api
        .getAssets(tags)
        .then(function (apiAssets) {
            var fileTokens = _.clone(content);
            _.each(content, function (assetValue, assetToken) {
                _.each(apiAssets, function(apiAsset) {
                    if (apiAsset.name === assetToken) {
                        gutil.log(chalk.yellow(
                            'Skip existing asset ' + assetToken
                        ));
                        delete fileTokens[assetToken];
                    }
                });

                // TODO: make this an option.
                // Will not import null value.
                if (typeof assetValue === null) {
                    fileTokens[assetToken] = '';
                }

                gutil.log(chalk.yellow(
                    'Import asset: ' + assetToken
                ));
            });

            // Import tokens to api.
            self.api
                .importAsync('fr_CA', fileTokens)
                .then(function () {
                    // Tag new tokens and set them to fuzzy status.
                    _.each(fileTokens, function (token, tokenKey) {
                        _.each(tags, function (tag) {
                            self.api
                                .tagAsset(tokenKey, tag);
                        });

                        if (token !== '') {
                            self.api
                                .setStatus(tokenKey, 'fuzzy', 'fr_CA');
                        }
                    });

                });
        });
};

module.exports = Synchronizr;
<commit_msg>[PROCESS] add update synchronizr sync method<commit_after>'use strict';
var _ = require('lodash');
var chalk = require('chalk');
var gutil = require('gulp-util');
var request = require('request-promise');
var stringify = require('json-stable-stringify');
var through = require('through2');

/**
 * Interns.
 */
var locoApi = require('./locoApi');

/**
 * Synchronizr
 * This synchronize the local json token with Loco.
 * @param {object} options Synchronize Loco's options.
 *
 * How the synchronization works?
 * This synchronization is mastered by Loco (http://localise.biz), that is to
 * say the right tokens are always from Loco. You can't erease tokens by simply
 * removes it from the json file, you should do it from Loco.
 *
 * This synchronization processes by multiple tasks:
 * 1. Check if locales given in options are comprised in Loco's locales.
 * If not, add this locale to Loco. All Loco's locales are in 'xx_XX' format
 * like 'en_CA', so your locale in 'xx' like 'en' should be converted to Loco's
 * format.
 *
 * 2. Get all tags available from Loco. If the the tags given in options aren't
 * in Loco yet, create it first. Loco will ignore not existing tags otherwise.
 *
 * 3. Get all remote catalogs associated to the locale. Then for each assets in
 * a catalog, test if the assets is already defined in your json file. You have
 * to convert null asset's values to empty string "" because Loco will not
 * import null value.
 *
 * 4. Import to Loco the processed assets.
 *
 * 5. Tag assets (tag+fuzzy).
 *
 * 6. Finaly, get remote catalog of assets from Loco to write the new json
 * localization file with translations.
 */
function Synchronizr (options) {
    this.api = new locoApi(options.apiKey);
}

/**
 * TODO: use mapping like [en][en_US, en_CA] ...
 */
Synchronizr.prototype.convertLocale = function (locale) {
    return locale + '_CA';
};

Synchronizr.prototype.testLocale = function (locale) {
    var self = this,
        convertedLocale = this.convertLocale(locale),
        locoLocales = [];

    this.api
        .getLocales()
        .then(function (apiLocales) {
            _.each(apiLocales, function (apiLocale) {
                locoLocales.push(apiLocale.code);
            });

            if (locoLocales.indexOf(convertedLocale) === -1) {
                locoLocales.push(convertedLocale);
                self.api
                    .addLocale({code: convertedLocale});
            }
        });
};

Synchronizr.prototype.createTags = function (tags) {
    var self = this;

    this.api
        .getTags()
        .then(function (apiTags) {
            _.each(tags, function (tag) {
                if (apiTags.indexOf(tag) === -1) {
                    self.api
                        .createTag({name: tag});
                }
            });
        });
}

Synchronizr.prototype.sync = function (locale, tags, content) {
    var skipToken = false,
        self = this;

    this.api
        .getAssets(tags)
        .then(function (apiAssets) {
            var fileTokens = _.clone(content);
            _.each(fileTokens, function (assetValue, assetToken) {
                _.each(JSON.parse(apiAssets), function(apiAssetValue, apiAssetKey) {
                    if (apiAssetValue.id === assetToken) {
                        gutil.log(chalk.blue(
                            'Skip existing asset: ' + assetToken + '.'
                        ));
                        delete fileTokens[assetToken];
                        skipToken = true;
                        return;
                    }
                });

                if (skipToken) {
                    skipToken = false;
                    return;
                }

                // TODO: make this an option.
                // Will not import null value.
                if (typeof assetValue === null) {
                    fileTokens[assetToken] = '';
                }

                gutil.log(chalk.yellow(
                    'Import asset: ' + assetToken + '.'
                ));
            });

            if (Object.keys(fileTokens).length < 1) {
                gutil.log(chalk.green(
                    'No token to synchronize.'
                ));
                return;
            }

            gutil.log(chalk.yellow(
                'Will import '+ Object.keys(fileTokens).length +' asset(s).'
            ));

            // Import tokens to api.
            self.api
                .importAsync('fr_CA', fileTokens)
                .then(function () {
                    // Tag new tokens and set them to fuzzy status.
                    _.each(fileTokens, function (token, tokenKey) {
                        _.each(tags, function (tag) {
                            self.api
                                .tagAsset(tokenKey, tag);
                        });

                        if (token !== '') {
                            self.api
                                .setStatus(tokenKey, 'fuzzy', 'fr_CA');
                        }
                    });

                });
        });
};

module.exports = Synchronizr;
<|endoftext|>