Full Code of Flipboard/react-canvas for AI

master 0b71180b4061 cached
47 files
137.9 KB
35.6k tokens
83 symbols
1 requests
Download .txt
Repository: Flipboard/react-canvas
Branch: master
Commit: 0b71180b4061
Files: 47
Total size: 137.9 KB

Directory structure:
gitextract_70rohpyf/

├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── examples/
│   ├── common/
│   │   ├── data.js
│   │   ├── examples.css
│   │   └── touch-emulator.js
│   ├── css-layout/
│   │   ├── app.js
│   │   └── index.html
│   ├── gradient/
│   │   ├── app.js
│   │   └── index.html
│   ├── listview/
│   │   ├── app.js
│   │   ├── components/
│   │   │   └── Item.js
│   │   └── index.html
│   └── timeline/
│       ├── app.js
│       ├── components/
│       │   └── Page.js
│       └── index.html
├── gulpfile.js
├── lib/
│   ├── Canvas.js
│   ├── CanvasUtils.js
│   ├── ContainerMixin.js
│   ├── DrawingUtils.js
│   ├── Easing.js
│   ├── EventTypes.js
│   ├── FontFace.js
│   ├── FontUtils.js
│   ├── FrameUtils.js
│   ├── Gradient.js
│   ├── Group.js
│   ├── Image.js
│   ├── ImageCache.js
│   ├── Layer.js
│   ├── LayerMixin.js
│   ├── Layout.js
│   ├── ListView.js
│   ├── ReactCanvas.js
│   ├── RenderLayer.js
│   ├── Surface.js
│   ├── Text.js
│   ├── __tests__/
│   │   └── clamp-test.js
│   ├── clamp.js
│   ├── createComponent.js
│   ├── hitTest.js
│   ├── layoutNode.js
│   └── measureText.js
├── package.json
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": ["react"]
}

================================================
FILE: .gitignore
================================================
build
node_modules
yarn.lock
npm-debug.log

================================================
FILE: LICENSE
================================================
Copyright (c) 2015, Flipboard
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this
  list of conditions and the following disclaimer in the documentation and/or
  other materials provided with the distribution.

* Neither the name of Flipboard nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

================================================
FILE: README.md
================================================
# react-canvas

[Introductory blog post](http://engineering.flipboard.com/2015/02/mobile-web)

React Canvas adds the ability for React components to render to `<canvas>` rather than DOM.

This project is a work-in-progress. Though much of the code is in production on flipboard.com, the React canvas bindings are relatively new and the API is subject to change.

## Motivation

Having a long history of building interfaces geared toward mobile devices, we found that the reason mobile web apps feel slow when compared to native apps is the DOM. CSS animations and transitions are the fastest path to smooth animations on the web, but they have several limitations. React Canvas leverages the fact that most modern mobile browsers now have hardware accelerated canvas.

While there have been other attempts to bind canvas drawing APIs to React, they are more focused on visualizations and games. Where React Canvas differs is in the focus on building application user interfaces. The fact that it renders to canvas is an implementation detail.

React Canvas brings some of the APIs web developers are familiar with and blends them with a high performance drawing engine.

## Installation

React Canvas is available through npm:

```npm install react-canvas```

## React Canvas Components

React Canvas provides a set of standard React components that abstract the underlying rendering implementation.

### &lt;Surface&gt;

**Surface** is the top-level component. Think of it as a drawing canvas in which you can place other components.

### &lt;Layer&gt;

**Layer** is the the base component by which other components build upon. Common styles and properties such as top, width, left, height, backgroundColor and zIndex are expressed at this level.

### &lt;Group&gt;

**Group** is a container component. Because React enforces that all components return a single component in `render()`, Groups can be useful for parenting a set of child components. The Group is also an important component for optimizing scrolling performance, as it allows the rendering engine to cache expensive drawing operations.

### &lt;Text&gt;

**Text** is a flexible component that supports multi-line truncation, something which has historically been difficult and very expensive to do in DOM.

### &lt;Image&gt;

**Image** is exactly what you think it is. However, it adds the ability to hide an image until it is fully loaded and optionally fade it in on load.

### &lt;Gradient&gt;

**Gradient** can be used to set the background of a group or surface. 
```javascript
  render() {
    ...
    return (
      <Group style={this.getStyle()}>
        <Gradient style={this.getGradientStyle()} 
                  colorStops={this.getGradientColors()} />
      </Group>
    );
  }
  getGradientColors(){
    return [
      { color: "transparent", position: 0 },
      { color: "#000", position: 1 }
    ]
  }
``` 

### &lt;ListView&gt;

**ListView** is a touch scrolling container that renders a list of elements in a column. Think of it like UITableView for the web. It leverages many of the same optimizations that make table views on iOS and list views on Android fast.

## Events

React Canvas components support the same event model as normal React components. However, not all event types are currently supported.

For a full list of supported events see [EventTypes](lib/EventTypes.js).

## Building Components

Here is a very simple component that renders text below an image:

```javascript
var React = require('react');
var ReactCanvas = require('react-canvas');

var Surface = ReactCanvas.Surface;
var Image = ReactCanvas.Image;
var Text = ReactCanvas.Text;

var MyComponent = React.createClass({

  render: function () {
    var surfaceWidth = window.innerWidth;
    var surfaceHeight = window.innerHeight;
    var imageStyle = this.getImageStyle();
    var textStyle = this.getTextStyle();

    return (
      <Surface width={surfaceWidth} height={surfaceHeight} left={0} top={0}>
        <Image style={imageStyle} src='...' />
        <Text style={textStyle}>
          Here is some text below an image.
        </Text>
      </Surface>
    );
  },

  getImageHeight: function () {
    return Math.round(window.innerHeight / 2);
  },

  getImageStyle: function () {
    return {
      top: 0,
      left: 0,
      width: window.innerWidth,
      height: this.getImageHeight()
    };
  },

  getTextStyle: function () {
    return {
      top: this.getImageHeight() + 10,
      left: 0,
      width: window.innerWidth,
      height: 20,
      lineHeight: 20,
      fontSize: 12
    };
  }

});
```

## ListView

Many mobile interfaces involve an infinitely long scrolling list of items. React Canvas provides the ListView component to do just that.

Because ListView virtualizes elements outside of the viewport, passing children to it is different than a normal React component where children are declared in render().

The `numberOfItemsGetter`, `itemHeightGetter` and `itemGetter` props are all required.

```javascript
var ListView = ReactCanvas.ListView;

var MyScrollingListView = React.createClass({

  render: function () {
    return (
      <ListView
        numberOfItemsGetter={this.getNumberOfItems}
        itemHeightGetter={this.getItemHeight}
        itemGetter={this.renderItem} />
    );
  },

  getNumberOfItems: function () {
    // Return the total number of items in the list
  },

  getItemHeight: function () {
    // Return the height of a single item
  },

  renderItem: function (index) {
    // Render the item at the given index, usually a <Group>
  },

});
```

See the [timeline example](examples/timeline/app.js) for a more complete example.

Currently, ListView requires that each item is of the same height. Future versions will support variable height items.

## Text sizing

React Canvas provides the `measureText` function for computing text metrics.

The [Page component](examples/timeline/components/Page.js) in the timeline example contains an example of using measureText to achieve precise multi-line ellipsized text.

Custom fonts are not currently supported but will be added in a future version.

## css-layout

There is experimental support for using [css-layout](https://github.com/facebook/css-layout) to style React Canvas components. This is a more expressive way of defining styles for a component using standard CSS styles and flexbox.

Future versions may not support css-layout out of the box. The performance implications need to be investigated before baking this in as a core layout principle.

See the [css-layout example](examples/css-layout).

## Accessibility

This area needs further exploration. Using fallback content (the canvas DOM sub-tree) should allow screen readers such as VoiceOver to interact with the content. We've seen mixed results with the iOS devices we've tested. Additionally there is a standard for [focus management](http://www.w3.org/TR/2010/WD-2dcontext-20100304/#dom-context-2d-drawfocusring) that is not supported by browsers yet.

One approach that was raised by [Bespin](http://vimeo.com/3195079) in 2009 is to keep a [parallel DOM](http://robertnyman.com/2009/04/03/mozilla-labs-online-code-editor-bespin/#comment-560310) in sync with the elements rendered in canvas.

## Running the examples

```
npm install
npm start
```

This will start a live reloading server on port 8080. To override the default server and live reload ports, run `npm start` with PORT and/or RELOAD_PORT environment variables.

**A note on NODE_ENV and React**: running the examples with `NODE_ENV=production` will noticeably improve scrolling performance. This is because React skips propType validation in production mode.


## Using with webpack

The [brfs](https://github.com/substack/brfs) transform is required in order to use the project with webpack.

```bash
npm install -g brfs
npm install --save-dev transform-loader brfs
```

Then add the [brfs](https://github.com/substack/brfs) transform to your webpack config

```javascript
module: {
  postLoaders: [
    { loader: "transform?brfs" }
  ]
}
```

## Contributing

We welcome pull requests for bug fixes, new features, and improvements to React Canvas. Contributors to the main repository must accept Flipboard's Apache-style [Individual Contributor License Agreement (CLA)](https://docs.google.com/forms/d/1gh9y6_i8xFn6pA15PqFeye19VqasuI9-bGp_e0owy74/viewform) before any changes can be merged.


================================================
FILE: examples/common/data.js
================================================
module.exports = [
  {
    title: '10 Unbelievable Secrets That Will Make Your Airline Pilot Nervous',
    excerpt: 'With these words the Witch fell down in a brown, melted, shapeless mass and began to spread over the clean boards of the kitchen floor.  Seeing that she had really melted away to nothing, Dorothy drew another bucket of water and threw it over the mess.  She then swept it all out the door.  After picking out the silver shoe, which was all that was left of the old woman, she cleaned and dried it with a cloth, and put it on her foot again.  Then, being at last free to do as she chose, she ran out to the courtyard to tell the Lion that the Wicked Witch of the West had come to an end, and that they were no longer prisoners in a strange land.',
    imageUrl: 'https://placekitten.com/360/420'
  },
  {
    title: 'Will Batman Save Leaf Blowing?',
    excerpt: 'The splendid fellow sprang to his feet, and grasping me by the shoulder raised his sword on high, exclaiming: "And had the choice been left to me I could not have chosen a more fitting mate for the first princess of Barsoom.  Here is my hand upon your shoulder, John Carter, and my word that Sab Than shall go out at the point of my sword for the sake of my love for Helium, for Dejah Thoris, and for you.  This very night I shall try to reach his quarters in the palace." "How?" I asked.  "You are strongly guarded and a quadruple force patrols the sky." He bent his head in thought a moment, then raised it with an air of confidence.',
    imageUrl: 'https://placekitten.com/361/421'
  },
  {
    title: '8 Scary Things Your Professor Is Using Against You',
    excerpt: 'For a minute he scarcely realised what this meant, and, although the heat was excessive, he clambered down into the pit close to the bulk to see the Thing more clearly.  He fancied even then that the cooling of the body might account for this, but what disturbed that idea was the fact that the ash was falling only from the end of the cylinder. And then he perceived that, very slowly, the circular top of the cylinder was rotating on its body.  It was such a gradual movement that he discovered it only through noticing that a black mark that had been near him five minutes ago was now at the other side of the circumference.',
    imageUrl: 'https://placekitten.com/362/422'
  },
  {
    title: 'Kanye West\'s Top 10 Scandalous Microsoft Excel Secrets',
    excerpt: 'My wife was curiously silent throughout the drive, and seemed oppressed with forebodings of evil.  I talked to her reassuringly, pointing out that the Martians were tied to the Pit by sheer heaviness, and at the utmost could but crawl a little out of it; but she answered only in monosyllables.  Had it not been for my promise to the innkeeper, she would, I think, have urged me to stay in Leatherhead that night.  Would that I had!  Her face, I remember, was very white as we parted. For my own part, I had been feverishly excited all day.',
    imageUrl: 'https://placekitten.com/363/423'
  },
  {
    title: 'The Embarassing Secrets Of Julia Roberts',
    excerpt: 'Passepartout heard the street door shut once; it was his new master going out.  He heard it shut again; it was his predecessor, James Forster, departing in his turn.  Passepartout remained alone in the house in Saville Row. "Faith," muttered Passepartout, somewhat flurried, "I\'ve seen people at Madame Tussaud\'s as lively as my new master!" Madame Tussaud\'s "people," let it be said, are of wax, and are much visited in London; speech is all that is wanting to make them human. During his brief interview with Mr. Fogg, Passepartout had been carefully observing him.',
    imageUrl: 'https://placekitten.com/364/424'
  },
  {
    title: '20 Unbelievable Things Girlfriends Won\'t Tell Their Friends',
    excerpt: 'On March 3, 1866, Powell and I packed his provisions on two of our burros, and bidding me good-bye he mounted his horse, and started down the mountainside toward the valley, across which led the first stage of his journey. The morning of Powell\'s departure was, like nearly all Arizona mornings, clear and beautiful; I could see him and his little pack animals picking their way down the mountainside toward the valley, and all during the morning I would catch occasional glimpses of them as they topped a hog back or came out upon a level plateau.',
    imageUrl: 'https://placekitten.com/365/425'
  },
  {
    title: 'Can Vladimir Putin Save Beard Care?',
    excerpt: 'So powerfully did the whole grim aspect of Ahab affect me, and the livid brand which streaked it, that for the first few moments I hardly noted that not a little of this overbearing grimness was owing to the barbaric white leg upon which he partly stood. It had previously come to me that this ivory leg had at sea been fashioned from the polished bone of the sperm whale\'s jaw. "Aye, he was dismasted off Japan," said the old Gay-Head Indian once; "but like his dismasted craft, he shipped another mast without coming home for it.',
    imageUrl: 'https://placekitten.com/366/426'
  },
  {
    title: '15 Truths That Will Make Your Psychiatrist Feel Ashamed',
    excerpt: 'Again was I suddenly recalled to my immediate surroundings by a repetition of the weird moan from the depths of the cave.  Naked and unarmed as I was, I had no desire to face the unseen thing which menaced me. My revolvers were strapped to my lifeless body which, for some unfathomable reason, I could not bring myself to touch.  My carbine was in its boot, strapped to my saddle, and as my horse had wandered off I was left without means of defense.  My only alternative seemed to lie in flight and my decision was crystallized by a recurrence of the rustling sound.',
    imageUrl: 'https://placekitten.com/367/427'
  },
  {
    title: '6 Terrible Facts That Make Boyfriends Stronger',
    excerpt: 'First they came to a great hall in which were many ladies and gentlemen of the court, all dressed in rich costumes.  These people had nothing to do but talk to each other, but they always came to wait outside the Throne Room every morning, although they were never permitted to see Oz.  As Dorothy entered they looked at her curiously, and one of them whispered: "Are you really going to look upon the face of Oz the Terrible?" "Of course," answered the girl, "if he will see me." "Oh, he will see you," said the soldier who had taken her message to the Wizard.',
    imageUrl: 'https://placekitten.com/368/428'
  },
  {
    title: '5 Surprising Dental Care Tips From Robert De Niro',
    excerpt: 'At once, with a quick mental leap, he linked the Thing with the flash upon Mars. The thought of the confined creature was so dreadful to him that he forgot the heat and went forward to the cylinder to help turn.  But luckily the dull radiation arrested him before he could burn his hands on the still-glowing metal.  At that he stood irresolute for a moment, then turned, scrambled out of the pit, and set off running wildly into Woking.  The time then must have been somewhere about six o\'clock. He met a waggoner and tried to make him understand, but the tale he told and his appearance were so wild--his hat had fallen off in the pit--that the man simply drove on.',
    imageUrl: 'https://placekitten.com/369/429'
  },
];


================================================
FILE: examples/common/examples.css
================================================
html, body {
  margin: 0;
  padding: 0;
  font: 16px Helvetica, sans-serif;
  height: 100%;
  overflow: hidden;
  background: #ddd;
}

#main {
  background: #fff;
  position: relative;
  height: 100%;
  max-width: 420px;
  max-height: 700px;
}


================================================
FILE: examples/common/touch-emulator.js
================================================
// https://github.com/hammerjs/touchemulator

(function(window, document, exportName, undefined) {
    "use strict";

    var isMultiTouch = false;
    var multiTouchStartPos;
    var eventTarget;
    var touchElements = {};

    // polyfills
    if(!document.createTouch) {
        document.createTouch = function(view, target, identifier, pageX, pageY, screenX, screenY, clientX, clientY) {
            // auto set
            if(clientX == undefined || clientY == undefined) {
                clientX = pageX - window.pageXOffset;
                clientY = pageY - window.pageYOffset;
            }

            return new Touch(target, identifier, {
                pageX: pageX,
                pageY: pageY,
                screenX: screenX,
                screenY: screenY,
                clientX: clientX,
                clientY: clientY
            });
        };
    }

    if(!document.createTouchList) {
        document.createTouchList = function() {
            var touchList = new TouchList();
            for (var i = 0; i < arguments.length; i++) {
                touchList[i] = arguments[i];
            }
            touchList.length = arguments.length;
            return touchList;
        };
    }

    /**
     * create an touch point
     * @constructor
     * @param target
     * @param identifier
     * @param pos
     * @param deltaX
     * @param deltaY
     * @returns {Object} touchPoint
     */
    function Touch(target, identifier, pos, deltaX, deltaY) {
        deltaX = deltaX || 0;
        deltaY = deltaY || 0;

        this.identifier = identifier;
        this.target = target;
        this.clientX = pos.clientX + deltaX;
        this.clientY = pos.clientY + deltaY;
        this.screenX = pos.screenX + deltaX;
        this.screenY = pos.screenY + deltaY;
        this.pageX = pos.pageX + deltaX;
        this.pageY = pos.pageY + deltaY;
    }

    /**
     * create empty touchlist with the methods
     * @constructor
     * @returns touchList
     */
    function TouchList() {
        var touchList = [];

        touchList.item = function(index) {
            return this[index] || null;
        };

        // specified by Mozilla
        touchList.identifiedTouch = function(id) {
            return this[id + 1] || null;
        };

        return touchList;
    }


    /**
     * Simple trick to fake touch event support
     * this is enough for most libraries like Modernizr and Hammer
     */
    function fakeTouchSupport() {
        var objs = [window, document.documentElement];
        var props = ['ontouchstart', 'ontouchmove', 'ontouchcancel', 'ontouchend'];

        for(var o=0; o<objs.length; o++) {
            for(var p=0; p<props.length; p++) {
                if(objs[o] && objs[o][props[p]] == undefined) {
                    objs[o][props[p]] = null;
                }
            }
        }
    }

    /**
     * we don't have to emulate on a touch device
     * @returns {boolean}
     */
    function hasTouchSupport() {
        return ("ontouchstart" in window) || // touch events
               (window.Modernizr && window.Modernizr.touch) || // modernizr
               (navigator.msMaxTouchPoints || navigator.maxTouchPoints) > 2; // pointer events
    }

    /**
     * disable mouseevents on the page
     * @param ev
     */
    function preventMouseEvents(ev) {
        ev.preventDefault();
        ev.stopPropagation();
    }

    /**
     * only trigger touches when the left mousebutton has been pressed
     * @param touchType
     * @returns {Function}
     */
    function onMouse(touchType) {
        return function(ev) {
            // prevent mouse events
            preventMouseEvents(ev);

            if (ev.which !== 1) {
                return;
            }

            // The EventTarget on which the touch point started when it was first placed on the surface,
            // even if the touch point has since moved outside the interactive area of that element.
            // also, when the target doesnt exist anymore, we update it
            if (ev.type == 'mousedown' || !eventTarget || (eventTarget && !eventTarget.dispatchEvent)) {
                eventTarget = ev.target;
            }

            // shiftKey has been lost, so trigger a touchend
            if (isMultiTouch && !ev.shiftKey) {
                triggerTouch('touchend', ev);
                isMultiTouch = false;
            }

            triggerTouch(touchType, ev);

            // we're entering the multi-touch mode!
            if (!isMultiTouch && ev.shiftKey) {
                isMultiTouch = true;
                multiTouchStartPos = {
                    pageX: ev.pageX,
                    pageY: ev.pageY,
                    clientX: ev.clientX,
                    clientY: ev.clientY,
                    screenX: ev.screenX,
                    screenY: ev.screenY
                };
                triggerTouch('touchstart', ev);
            }

            // reset
            if (ev.type == 'mouseup') {
                multiTouchStartPos = null;
                isMultiTouch = false;
                eventTarget = null;
            }
        }
    }

    /**
     * trigger a touch event
     * @param eventName
     * @param mouseEv
     */
    function triggerTouch(eventName, mouseEv) {
        var touchEvent = document.createEvent('Event');
        touchEvent.initEvent(eventName, true, true);

        touchEvent.altKey = mouseEv.altKey;
        touchEvent.ctrlKey = mouseEv.ctrlKey;
        touchEvent.metaKey = mouseEv.metaKey;
        touchEvent.shiftKey = mouseEv.shiftKey;

        touchEvent.touches = getActiveTouches(mouseEv, eventName);
        touchEvent.targetTouches = getActiveTouches(mouseEv, eventName);
        touchEvent.changedTouches = getChangedTouches(mouseEv, eventName);

        eventTarget.dispatchEvent(touchEvent);
    }

    /**
     * create a touchList based on the mouse event
     * @param mouseEv
     * @returns {TouchList}
     */
    function createTouchList(mouseEv) {
        var touchList = new TouchList();

        if (isMultiTouch) {
            var f = TouchEmulator.multiTouchOffset;
            var deltaX = multiTouchStartPos.pageX - mouseEv.pageX;
            var deltaY = multiTouchStartPos.pageY - mouseEv.pageY;

            touchList.push(new Touch(eventTarget, 1, multiTouchStartPos, (deltaX*-1) - f, (deltaY*-1) + f));
            touchList.push(new Touch(eventTarget, 2, multiTouchStartPos, deltaX+f, deltaY-f));
        } else {
            touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0));
        }

        return touchList;
    }

    /**
     * receive all active touches
     * @param mouseEv
     * @returns {TouchList}
     */
    function getActiveTouches(mouseEv, eventName) {
        // empty list
        if (mouseEv.type == 'mouseup') {
            return new TouchList();
        }

        var touchList = createTouchList(mouseEv);
        if(isMultiTouch && mouseEv.type != 'mouseup' && eventName == 'touchend') {
            touchList.splice(1, 1);
        }
        return touchList;
    }

    /**
     * receive a filtered set of touches with only the changed pointers
     * @param mouseEv
     * @param eventName
     * @returns {TouchList}
     */
    function getChangedTouches(mouseEv, eventName) {
        var touchList = createTouchList(mouseEv);

        // we only want to return the added/removed item on multitouch
        // which is the second pointer, so remove the first pointer from the touchList
        //
        // but when the mouseEv.type is mouseup, we want to send all touches because then
        // no new input will be possible
        if(isMultiTouch && mouseEv.type != 'mouseup' &&
            (eventName == 'touchstart' || eventName == 'touchend')) {
            touchList.splice(0, 1);
        }

        return touchList;
    }

    /**
     * show the touchpoints on the screen
     */
    function showTouches(ev) {
        var touch, i, el, styles;

        // first all visible touches
        for(i = 0; i < ev.touches.length; i++) {
            touch = ev.touches[i];
            el = touchElements[touch.identifier];
            if(!el) {
                el = touchElements[touch.identifier] = document.createElement("div");
                document.body.appendChild(el);
            }

            styles = TouchEmulator.template(touch);
            for(var prop in styles) {
                el.style[prop] = styles[prop];
            }
        }

        // remove all ended touches
        if(ev.type == 'touchend' || ev.type == 'touchcancel') {
            for(i = 0; i < ev.changedTouches.length; i++) {
                touch = ev.changedTouches[i];
                el = touchElements[touch.identifier];
                if(el) {
                    el.parentNode.removeChild(el);
                    delete touchElements[touch.identifier];
                }
            }
        }
    }

    /**
     * TouchEmulator initializer
     */
    function TouchEmulator() {
        if (hasTouchSupport()) {
            return;
        }

        fakeTouchSupport();

        window.addEventListener("mousedown", onMouse('touchstart'), true);
        window.addEventListener("mousemove", onMouse('touchmove'), true);
        window.addEventListener("mouseup", onMouse('touchend'), true);

        window.addEventListener("mouseenter", preventMouseEvents, true);
        window.addEventListener("mouseleave", preventMouseEvents, true);
        window.addEventListener("mouseout", preventMouseEvents, true);
        window.addEventListener("mouseover", preventMouseEvents, true);

        // it uses itself!
        window.addEventListener("touchstart", showTouches, false);
        window.addEventListener("touchmove", showTouches, false);
        window.addEventListener("touchend", showTouches, false);
        window.addEventListener("touchcancel", showTouches, false);
    }

    // start distance when entering the multitouch mode
    TouchEmulator.multiTouchOffset = 75;

    /**
     * css template for the touch rendering
     * @param touch
     * @returns object
     */
    TouchEmulator.template = function(touch) {
        var size = 30;
        var transform = 'translate('+ (touch.clientX-(size/2)) +'px, '+ (touch.clientY-(size/2)) +'px)';
        return {
            position: 'fixed',
            left: 0,
            top: 0,
            background: '#fff',
            border: 'solid 1px #999',
            opacity: .6,
            borderRadius: '100%',
            height: size + 'px',
            width: size + 'px',
            padding: 0,
            margin: 0,
            display: 'block',
            overflow: 'hidden',
            pointerEvents: 'none',
            webkitUserSelect: 'none',
            mozUserSelect: 'none',
            userSelect: 'none',
            webkitTransform: transform,
            mozTransform: transform,
            transform: transform
        }
    };

    // export
    if (typeof define == "function" && define.amd) {
        define(function() {
            return TouchEmulator;
        });
    } else if (typeof module != "undefined" && module.exports) {
        module.exports = TouchEmulator;
    } else {
        window[exportName] = TouchEmulator;
    }
})(window, document, "TouchEmulator");

================================================
FILE: examples/css-layout/app.js
================================================
var React = require('react');
var ReactDOM = require('react-dom');
var ReactCanvas = require('react-canvas');

var Surface = ReactCanvas.Surface;
var Group = ReactCanvas.Group;
var Image = ReactCanvas.Image;
var Text = ReactCanvas.Text;
var FontFace = ReactCanvas.FontFace;

var App = React.createClass({

  componentDidMount: function () {
    window.addEventListener('resize', this.handleResize, true);
  },

  render: function () {
    var size = this.getSize();
    return (
      <Surface top={0} left={0} width={size.width} height={size.height} enableCSSLayout={true}>
        <Group style={this.getPageStyle()}>
          <Text style={this.getTitleStyle()}>
            Professor PuddinPop
          </Text>
          <Group style={this.getImageGroupStyle()}>
            <Image src='https://placekitten.com/720/840' style={this.getImageStyle()} fadeIn={true} />
          </Group>
          <Text style={this.getExcerptStyle()}>
            With these words the Witch fell down in a brown, melted, shapeless mass and began to spread over the clean boards of the kitchen floor.  Seeing that she had really melted away to nothing, Dorothy drew another bucket of water and threw it over the mess.  She then swept it all out the door.  After picking out the silver shoe, which was all that was left of the old woman, she cleaned and dried it with a cloth, and put it on her foot again.  Then, being at last free to do as she chose, she ran out to the courtyard to tell the Lion that the Wicked Witch of the West had come to an end, and that they were no longer prisoners in a strange land.
          </Text>
        </Group>
      </Surface>
    );
  },

  // Styles
  // ======

  getSize: function () {
    return document.getElementById('main').getBoundingClientRect();
  },

  getPageStyle: function () {
    var size = this.getSize();
    return {
      position: 'relative',
      padding: 14,
      width: size.width,
      height: size.height,
      backgroundColor: '#f7f7f7',
      flexDirection: 'column'
    };
  },

  getImageGroupStyle: function () {
    return {
      position: 'relative',
      flex: 1,
      backgroundColor: '#eee'
    };
  },

  getImageStyle: function () {
    return {
      position: 'absolute',
      left: 0,
      top: 0,
      right: 0,
      bottom: 0
    };
  },

  getTitleStyle: function () {
    return {
      fontFace: FontFace('Georgia'),
      fontSize: 22,
      lineHeight: 28,
      height: 28,
      marginBottom: 10,
      color: '#333',
      textAlign: 'center'
    };
  },

  getExcerptStyle: function () {
    return {
      fontFace: FontFace('Georgia'),
      fontSize: 17,
      lineHeight: 25,
      marginTop: 15,
      flex: 1,
      color: '#333'
    };
  },

  // Events
  // ======

  handleResize: function () {
    this.forceUpdate();
  }

});

ReactDOM.render(<App />, document.getElementById('main'));


================================================
FILE: examples/css-layout/index.html
================================================
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
  <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>ReactCanvas: css-layout</title>
  <link rel="stylesheet" type="text/css" href="/examples/common/examples.css">
  <script src="/examples/common/touch-emulator.js"></script>
  <script type="text/javascript">
    TouchEmulator();
  </script>
</head>
<body>
  <div id="main"></div>
  <script src="/build/css-layout.js"></script>
</body>
</html>


================================================
FILE: examples/gradient/app.js
================================================
'use strict';

var React = require('react');
var ReactDOM = require('react-dom');
var ReactCanvas = require('react-canvas');

var Gradient = ReactCanvas.Gradient;
var Surface = ReactCanvas.Surface;

var App = React.createClass({

  render: function () {
    var size = this.getSize();
    return (
      <Surface top={0} left={0} width={size.width} height={size.height}>
        <Gradient style={this.getGradientStyle()}
                  colorStops={this.getGradientColors()} />
      </Surface>
    );
  },

  getGradientStyle: function(){
    var size = this.getSize();
    return {
      top: 0,
      left: 0,
      width: size.width,
      height: size.height
    };
  },

  getGradientColors: function(){
    return [
      { color: "transparent", position: 0 },
      { color: "#000", position: 1 }
    ];
  },

  getSize: function () {
    return document.getElementById('main').getBoundingClientRect();
  }

});

ReactDOM.render(<App />, document.getElementById('main'));


================================================
FILE: examples/gradient/index.html
================================================
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
  <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>ReactCanvas: ListView</title>
  <link rel="stylesheet" type="text/css" href="/examples/common/examples.css">
  <script src="/examples/common/touch-emulator.js"></script>
  <script type="text/javascript">
    TouchEmulator();
  </script>
</head>
<body>
  <div id="main"></div>
  <script src="/build/gradient.js"></script>
</body>
</html>


================================================
FILE: examples/listview/app.js
================================================
'use strict';

var React = require('react');
var ReactDOM = require('react-dom');
var ReactCanvas = require('react-canvas');
var Item = require('./components/Item');
var articles = require('../common/data');

var Surface = ReactCanvas.Surface;
var ListView = ReactCanvas.ListView;

var App = React.createClass({

  render: function () {
    var size = this.getSize();
    return (
      <Surface top={0} left={0} width={size.width} height={size.height}>
        <ListView
          style={this.getListViewStyle()}
          numberOfItemsGetter={this.getNumberOfItems}
          itemHeightGetter={Item.getItemHeight}
          itemGetter={this.renderItem} />
      </Surface>
    );
  },

  renderItem: function (itemIndex, scrollTop) {
    var article = articles[itemIndex % articles.length];
      return (
      <Item
        width={this.getSize().width}
        height={Item.getItemHeight()}
        imageUrl={article.imageUrl}
        title={article.title}
        itemIndex={itemIndex} />
    );
  },

  getSize: function () {
    return document.getElementById('main').getBoundingClientRect();
  },

  // ListView
  // ========

  getListViewStyle: function () {
    return {
      top: 0,
      left: 0,
      width: window.innerWidth,
      height: window.innerHeight
    };
  },

  getNumberOfItems: function () {
    return 1000;
  },

});

ReactDOM.render(<App />, document.getElementById('main'));


================================================
FILE: examples/listview/components/Item.js
================================================
'use strict';

var React = require('react');
var ReactCanvas = require('react-canvas');

var Group = ReactCanvas.Group;
var Image = ReactCanvas.Image;
var Text = ReactCanvas.Text;

var Item = React.createClass({

  propTypes: {
    width: React.PropTypes.number.isRequired,
    height: React.PropTypes.number.isRequired,
    imageUrl: React.PropTypes.string.isRequired,
    title: React.PropTypes.string.isRequired,
    itemIndex: React.PropTypes.number.isRequired,
  },

  statics: {
    getItemHeight: function () {
      return 80;
    }
  },

  render: function () {
    return (
      <Group style={this.getStyle()}>
        <Image style={this.getImageStyle()} src={this.props.imageUrl} />
        <Text style={this.getTitleStyle()}>{this.props.title}</Text>
      </Group>
    );
  },

  getStyle: function () {
    return {
      width: this.props.width,
      height: Item.getItemHeight(),
      backgroundColor: (this.props.itemIndex % 2) ? '#eee' : '#a5d2ee'
    };
  },

  getImageStyle: function () {
    return {
      top: 10,
      left: 10,
      width: 60,
      height: 60,
      backgroundColor: '#ddd',
      borderColor: '#999',
      borderWidth: 1
    };
  },

  getTitleStyle: function () {
    return {
      top: 32,
      left: 80,
      width: this.props.width - 90,
      height: 18,
      fontSize: 14,
      lineHeight: 18
    };
  }

});

module.exports = Item;


================================================
FILE: examples/listview/index.html
================================================
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
  <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>ReactCanvas: ListView</title>
  <link rel="stylesheet" type="text/css" href="/examples/common/examples.css">
  <script src="/examples/common/touch-emulator.js"></script>
  <script type="text/javascript">
    TouchEmulator();
  </script>
</head>
<body>
  <div id="main"></div>
  <script src="/build/listview.js"></script>
</body>
</html>


================================================
FILE: examples/timeline/app.js
================================================
'use strict';

var React = require('react');
var ReactDOM = require('react-dom');
var ReactCanvas = require('react-canvas');
var Page = require('./components/Page');
var articles = require('../common/data');

var Surface = ReactCanvas.Surface;
var ListView = ReactCanvas.ListView;

var App = React.createClass({

  render: function () {
    var size = this.getSize();
    return (
      <Surface top={0} left={0} width={size.width} height={size.height}>
        <ListView
          style={this.getListViewStyle()}
          snapping={true}
          scrollingDeceleration={0.92}
          scrollingPenetrationAcceleration={0.13}
          numberOfItemsGetter={this.getNumberOfPages}
          itemHeightGetter={this.getPageHeight}
          itemGetter={this.renderPage} />
      </Surface>
    );
  },

  renderPage: function (pageIndex, scrollTop) {
    var size = this.getSize();
    var article = articles[pageIndex % articles.length];
    var pageScrollTop = pageIndex * this.getPageHeight() - scrollTop;
    return (
      <Page
        width={size.width}
        height={size.height}
        article={article}
        pageIndex={pageIndex}
        scrollTop={pageScrollTop} />
    );
  },

  getSize: function () {
    return document.getElementById('main').getBoundingClientRect();
  },

  // ListView
  // ========

  getListViewStyle: function () {
    var size = this.getSize();
    return {
      top: 0,
      left: 0,
      width: size.width,
      height: size.height
    };
  },

  getNumberOfPages: function () {
    return 1000;
  },

  getPageHeight: function () {
    return this.getSize().height;
  }

});

ReactDOM.render(<App />, document.getElementById('main'));


================================================
FILE: examples/timeline/components/Page.js
================================================
'use strict';

var React = require('react');
var ReactCanvas = require('react-canvas');

var Group = ReactCanvas.Group;
var Image = ReactCanvas.Image;
var Text = ReactCanvas.Text;
var FontFace = ReactCanvas.FontFace;
var measureText = ReactCanvas.measureText;

var CONTENT_INSET = 14;
var TEXT_SCROLL_SPEED_MULTIPLIER = 0.6;
var TEXT_ALPHA_SPEED_OUT_MULTIPLIER = 1.25;
var TEXT_ALPHA_SPEED_IN_MULTIPLIER = 2.6;
var IMAGE_LAYER_INDEX = 2;
var TEXT_LAYER_INDEX = 1;

var Page = React.createClass({

  propTypes: {
    width: React.PropTypes.number.isRequired,
    height: React.PropTypes.number.isRequired,
    article: React.PropTypes.object.isRequired,
    scrollTop: React.PropTypes.number.isRequired
  },

  componentWillMount: function () {
    // Pre-compute headline/excerpt text dimensions.
    var article = this.props.article;
    var maxWidth = this.props.width - 2 * CONTENT_INSET;
    var titleStyle = this.getTitleStyle();
    var excerptStyle = this.getExcerptStyle();
    this.titleMetrics = measureText(article.title, maxWidth, titleStyle.fontFace, titleStyle.fontSize, titleStyle.lineHeight);
    this.excerptMetrics = measureText(article.excerpt, maxWidth, excerptStyle.fontFace, excerptStyle.fontSize, excerptStyle.lineHeight);
  },

  render: function () {
    var groupStyle = this.getGroupStyle();
    var imageStyle = this.getImageStyle();
    var titleStyle = this.getTitleStyle();
    var excerptStyle = this.getExcerptStyle();

    // Layout title and excerpt below image.
    titleStyle.height = this.titleMetrics.height;
    excerptStyle.top = titleStyle.top + titleStyle.height + CONTENT_INSET;
    excerptStyle.height = this.props.height - excerptStyle.top - CONTENT_INSET;

    return (
      <Group style={groupStyle}>
        <Image style={imageStyle} src={this.props.article.imageUrl} fadeIn={true} useBackingStore={true} />
        <Group style={this.getTextGroupStyle()} useBackingStore={true}>
          <Text style={titleStyle}>{this.props.article.title}</Text>
          <Text style={excerptStyle}>{this.props.article.excerpt}</Text>
        </Group>
      </Group>
    );
  },

  // Styles
  // ======

  getGroupStyle: function () {
    return {
      top: 0,
      left: 0,
      width: this.props.width,
      height: this.props.height,
    };
  },

  getImageHeight: function () {
    return Math.round(this.props.height * 0.5);
  },

  getImageStyle: function () {
    return {
      top: 0,
      left: 0,
      width: this.props.width,
      height: this.getImageHeight(),
      backgroundColor: '#eee',
      zIndex: IMAGE_LAYER_INDEX
    };
  },

  getTitleStyle: function () {
    return {
      top: this.getImageHeight() + CONTENT_INSET,
      left: CONTENT_INSET,
      width: this.props.width - 2 * CONTENT_INSET,
      fontSize: 22,
      lineHeight: 30,
      fontFace: FontFace('Avenir Next Condensed, Helvetica, sans-serif', null, {weight: 500})
    };
  },

  getExcerptStyle: function () {
    return {
      left: CONTENT_INSET,
      width: this.props.width - 2 * CONTENT_INSET,
      fontFace: FontFace('Georgia, serif'),
      fontSize: 15,
      lineHeight: 23
    };
  },

  getTextGroupStyle: function () {
    var imageHeight = this.getImageHeight();
    var translateY = 0;
    var alphaMultiplier = (this.props.scrollTop <= 0) ? -TEXT_ALPHA_SPEED_OUT_MULTIPLIER : TEXT_ALPHA_SPEED_IN_MULTIPLIER;
    var alpha = 1 - (this.props.scrollTop / this.props.height) * alphaMultiplier;
    alpha = Math.min(Math.max(alpha, 0), 1);
    translateY = -this.props.scrollTop * TEXT_SCROLL_SPEED_MULTIPLIER;

    return {
      width: this.props.width,
      height: this.props.height - imageHeight,
      top: imageHeight,
      left: 0,
      alpha: alpha,
      translateY: translateY,
      zIndex: TEXT_LAYER_INDEX
    };
  }

});

module.exports = Page;


================================================
FILE: examples/timeline/index.html
================================================
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
  <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>ReactCanvas: Timeline</title>
  <link rel="stylesheet" type="text/css" href="/examples/common/examples.css">
  <script src="/examples/common/touch-emulator.js"></script>
  <script type="text/javascript">
    TouchEmulator();
  </script>
</head>
<body>
  <div id="main"></div>
  <script src="/build/timeline.js"></script>
</body>
</html>


================================================
FILE: gulpfile.js
================================================
var gulp = require('gulp');
var del = require('del');
var connect = require('gulp-connect');
var webpack = require('webpack-stream');
var webpackConfig = require('./webpack.config.js');

var port = process.env.PORT || 8080;
var reloadPort = process.env.RELOAD_PORT || 35729;

gulp.task('clean', function () {
  del(['build']);
});

gulp.task('build', function () {
  return gulp.src(webpackConfig.entry.timeline[0])
    .pipe(webpack(webpackConfig))
    .pipe(gulp.dest('build/'));
});

gulp.task('serve', function () {
  connect.server({
    port: port,
    livereload: {
      port: reloadPort
    }
  });
});

gulp.task('reload-js', function () {
  return gulp.src('./build/*.js')
    .pipe(connect.reload());
});

gulp.task('watch', function () {
  gulp.watch(['./build/*.js'], ['reload-js']);
});

gulp.task('default', ['clean', 'build', 'serve', 'watch']);


================================================
FILE: lib/Canvas.js
================================================
'use strict';

// Note that this class intentionally does not use PooledClass.
// DrawingUtils manages <canvas> pooling for more fine-grained control.

function Canvas (width, height, scale) {
  // Re-purposing an existing canvas element.
  if (!this._canvas) {
    this._canvas = document.createElement('canvas');
  }

  this.width = width;
  this.height = height;
  this.scale = scale || window.devicePixelRatio;

  this._canvas.width = this.width * this.scale;
  this._canvas.height = this.height * this.scale;
  this._canvas.getContext('2d').scale(this.scale, this.scale);
}

Object.assign(Canvas.prototype, {

  getRawCanvas: function () {
    return this._canvas;
  },

  getContext: function () {
    return this._canvas.getContext('2d');
  }

});

// PooledClass:

// Be fairly conserative - we are potentially drawing a large number of medium
// to large size images.
Canvas.poolSize = 30;

module.exports = Canvas;


================================================
FILE: lib/CanvasUtils.js
================================================
'use strict';

var FontFace = require('./FontFace');
var clamp = require('./clamp');
var measureText = require('./measureText');

/**
 * Draw an image into a <canvas>. This operation requires that the image
 * already be loaded.
 *
 * @param {CanvasContext} ctx
 * @param {Image} image The source image (from ImageCache.get())
 * @param {Number} x The x-coordinate to begin drawing
 * @param {Number} y The y-coordinate to begin drawing
 * @param {Number} width The desired width
 * @param {Number} height The desired height
 * @param {Object} options Available options are:
 *   {Number} originalWidth
 *   {Number} originalHeight
 *   {Object} focusPoint {x,y}
 *   {String} backgroundColor
 */
function drawImage (ctx, image, x, y, width, height, options) {
  options = options || {};

  if (options.backgroundColor) {
    ctx.save();
    ctx.fillStyle = options.backgroundColor;
    ctx.fillRect(x, y, width, height);
    ctx.restore();
  }

  var dx = 0;
  var dy = 0;
  var dw = 0;
  var dh = 0;
  var sx = 0;
  var sy = 0;
  var sw = 0;
  var sh = 0;
  var scale;
  var scaledSize;
  var actualSize;
  var focusPoint = options.focusPoint;

  actualSize = {
    width: image.getWidth(),
    height: image.getHeight()
  };

  scale = Math.max(
    width / actualSize.width,
    height / actualSize.height
  ) || 1;
  scale = parseFloat(scale.toFixed(4), 10);

  scaledSize = {
    width: actualSize.width * scale,
    height: actualSize.height * scale
  };

  if (focusPoint) {
    // Since image hints are relative to image "original" dimensions (original != actual),
    // use the original size for focal point cropping.
    if (options.originalHeight) {
      focusPoint.x *= (actualSize.height / options.originalHeight);
      focusPoint.y *= (actualSize.height / options.originalHeight);
    }
  } else {
    // Default focal point to [0.5, 0.5]
    focusPoint = {
      x: actualSize.width * 0.5,
      y: actualSize.height * 0.5
    };
  }

  // Clip the image to rectangle (sx, sy, sw, sh).
  sx = Math.round(clamp(width * 0.5 - focusPoint.x * scale, width - scaledSize.width, 0)) * (-1 / scale);
  sy = Math.round(clamp(height * 0.5 - focusPoint.y * scale, height - scaledSize.height, 0)) * (-1 / scale);
  sw = Math.round(actualSize.width - (sx * 2));
  sh = Math.round(actualSize.height - (sy * 2));

  // Scale the image to dimensions (dw, dh).
  dw = Math.round(width);
  dh = Math.round(height);

  // Draw the image on the canvas at coordinates (dx, dy).
  dx = Math.round(x);
  dy = Math.round(y);

  ctx.drawImage(image.getRawImage(), sx, sy, sw, sh, dx, dy, dw, dh);
}

/**
 * @param {CanvasContext} ctx
 * @param {String} text The text string to render
 * @param {Number} x The x-coordinate to begin drawing
 * @param {Number} y The y-coordinate to begin drawing
 * @param {Number} width The maximum allowed width
 * @param {Number} height The maximum allowed height
 * @param {FontFace} fontFace The FontFace to to use
 * @param {Object} options Available options are:
 *   {Number} fontSize
 *   {Number} lineHeight
 *   {String} textAlign
 *   {String} color
 *   {String} backgroundColor
 */
function drawText (ctx, text, x, y, width, height, fontFace, options) {
  var textMetrics;
  var currX = x;
  var currY = y;
  var currText;
  var options = options || {};

  options.fontSize = options.fontSize || 16;
  options.lineHeight = options.lineHeight || 18;
  options.textAlign = options.textAlign || 'left';
  options.backgroundColor = options.backgroundColor || 'transparent';
  options.color = options.color || '#000';

  textMetrics = measureText(
    text,
    width,
    fontFace,
    options.fontSize,
    options.lineHeight
  );

  ctx.save();

  // Draw the background
  if (options.backgroundColor !== 'transparent') {
    ctx.fillStyle = options.backgroundColor;
    ctx.fillRect(0, 0, width, height);
  }

  ctx.fillStyle = options.color;
  ctx.font = fontFace.attributes.style + ' ' + fontFace.attributes.weight + ' ' + options.fontSize + 'px ' + fontFace.family;

  textMetrics.lines.forEach(function (line, index) {
    currText = line.text;
    currY = (index === 0) ? y + options.fontSize :
      (y + options.fontSize + options.lineHeight * index);

    // Account for text-align: left|right|center
    switch (options.textAlign) {
      case 'center':
        currX = x + (width / 2) - (line.width / 2);
        break;
      case 'right':
        currX = x + width - line.width;
        break;
      default:
        currX = x;
    }

    if ((index < textMetrics.lines.length - 1) &&
      ((options.fontSize + options.lineHeight * (index + 1)) > height)) {
      currText = currText.replace(/\,?\s?\w+$/, '…');
    }

    if (currY <= (height + y)) {
      ctx.fillText(currText, currX, currY);
    }
  });

  ctx.restore();
}

/**
 * Draw a linear gradient
 *
 * @param {CanvasContext} ctx
 * @param {Number} x1 gradient start-x coordinate
 * @param {Number} y1 gradient start-y coordinate
 * @param {Number} x2 gradient end-x coordinate
 * @param {Number} y2 gradient end-y coordinate
 * @param {Array} colorStops Array of {(String)color, (Number)position} values
 * @param {Number} x x-coordinate to begin fill
 * @param {Number} y y-coordinate to begin fill
 * @param {Number} width how wide to fill
 * @param {Number} height how tall to fill
 */
function drawGradient(ctx, x1, y1, x2, y2, colorStops, x, y, width, height) {
  var grad;

  ctx.save();
  grad = ctx.createLinearGradient(x1, y1, x2, y2);

  colorStops.forEach(function (colorStop) {
    grad.addColorStop(colorStop.position, colorStop.color);
  });

  ctx.fillStyle = grad;
  ctx.fillRect(x, y, width, height);
  ctx.restore();
}

module.exports = {
  drawImage: drawImage,
  drawText: drawText,
  drawGradient: drawGradient,
};



================================================
FILE: lib/ContainerMixin.js
================================================
'use strict';

// Adapted from ReactART:
// https://github.com/reactjs/react-art

var React = require('react');
var ReactMultiChild = require('react-dom/lib/ReactMultiChild');
var emptyObject = require('fbjs/lib/emptyObject');

var ContainerMixin = Object.assign({}, ReactMultiChild.Mixin, {

  /**
   * Moves a child component to the supplied index.
   *
   * @param {ReactComponent} child Component to move.
   * @param {number} toIndex Destination index of the element.
   * @protected
   */
  moveChild: function(child, afterNode, toIndex, lastIndex) {
    var childNode = child._mountImage;
    var mostRecentlyPlacedChild = this._mostRecentlyPlacedChild;
    if (mostRecentlyPlacedChild == null) {
      // I'm supposed to be first.
      if (childNode.previousSibling) {
        if (this.node.firstChild) {
          childNode.injectBefore(this.node.firstChild);
        } else {
          childNode.inject(this.node);
        }
      }
    } else {
      // I'm supposed to be after the previous one.
      if (mostRecentlyPlacedChild.nextSibling !== childNode) {
        if (mostRecentlyPlacedChild.nextSibling) {
          childNode.injectBefore(mostRecentlyPlacedChild.nextSibling);
        } else {
          childNode.inject(this.node);
        }
      }
    }
    this._mostRecentlyPlacedChild = childNode;
  },

  /**
   * Creates a child component.
   *
   * @param {ReactComponent} child Component to create.
   * @param {object} childNode ART node to insert.
   * @protected
   */
  createChild: function(child, afterNode, childNode) {
    child._mountImage = childNode;
    var mostRecentlyPlacedChild = this._mostRecentlyPlacedChild;
    if (mostRecentlyPlacedChild == null) {
      // I'm supposed to be first.
      if (this.node.firstChild) {
        childNode.injectBefore(this.node.firstChild);
      } else {
        childNode.inject(this.node);
      }
    } else {
      // I'm supposed to be after the previous one.
      if (mostRecentlyPlacedChild.nextSibling) {
        childNode.injectBefore(mostRecentlyPlacedChild.nextSibling);
      } else {
        childNode.inject(this.node);
      }
    }
    this._mostRecentlyPlacedChild = childNode;
  },

  /**
   * Removes a child component.
   *
   * @param {ReactComponent} child Child to remove.
   * @protected
   */
  removeChild: function(child) {
    child._mountImage.remove();
    child._mountImage = null;
    this.node.invalidateLayout();
  },

  updateChildrenAtRoot: function(nextChildren, transaction) {
    this.updateChildren(nextChildren, transaction, emptyObject);
  },

  mountAndInjectChildrenAtRoot: function(children, transaction) {
    this.mountAndInjectChildren(children, transaction, emptyObject);
  },

  /**
   * Override to bypass batch updating because it is not necessary.
   *
   * @param {?object} nextChildren.
   * @param {ReactReconcileTransaction} transaction
   * @internal
   * @override {ReactMultiChild.Mixin.updateChildren}
   */
  updateChildren: function(nextChildren, transaction, context) {
    this._mostRecentlyPlacedChild = null;
    this._updateChildren(nextChildren, transaction, context);
  },

  // Shorthands

  mountAndInjectChildren: function(children, transaction, context) {
    var mountedImages = this.mountChildren(
      children,
      transaction,
      context
    );

    // Each mount image corresponds to one of the flattened children
    var i = 0;
    for (var key in this._renderedChildren) {
      if (this._renderedChildren.hasOwnProperty(key)) {
        var child = this._renderedChildren[key];
        child._mountImage = mountedImages[i];
        mountedImages[i].inject(this.node);
        i++;
      }
    }
  },
  getHostNode: function () { return this.node },
  getNativeNode: function () { return this.node },

});

module.exports = ContainerMixin;


================================================
FILE: lib/DrawingUtils.js
================================================
'use strict';

var ImageCache = require('./ImageCache');
var FontUtils = require('./FontUtils');
var FontFace = require('./FontFace');
var FrameUtils = require('./FrameUtils');
var CanvasUtils = require('./CanvasUtils');
var Canvas = require('./Canvas');

// Global backing store <canvas> cache
var _backingStores = [];

/**
 * Maintain a cache of backing <canvas> for RenderLayer's which are accessible
 * through the RenderLayer's `backingStoreId` property.
 *
 * @param {String} id The unique `backingStoreId` for a RenderLayer
 * @return {HTMLCanvasElement}
 */
function getBackingStore (id) {
  for (var i=0, len=_backingStores.length; i < len; i++) {
    if (_backingStores[i].id === id) {
      return _backingStores[i].canvas;
    }
  }
  return null;
}

/**
 * Purge a layer's backing store from the cache.
 *
 * @param {String} id The layer's backingStoreId
 */
function invalidateBackingStore (id) {
  for (var i=0, len=_backingStores.length; i < len; i++) {
    if (_backingStores[i].id === id) {
      _backingStores.splice(i, 1);
      break;
    }
  }
}

/**
 * Purge the entire backing store cache.
 */
function invalidateAllBackingStores () {
  _backingStores = [];
}

/**
 * Find the nearest backing store ancestor for a given layer.
 *
 * @param {RenderLayer} layer
 */
function getBackingStoreAncestor (layer) {
  while (layer) {
    if (layer.backingStoreId) {
      return layer;
    }
    layer = layer.parentLayer;
  }
  return null;
}

/**
 * Check if a layer is using a given image URL.
 *
 * @param {RenderLayer} layer
 * @param {String} imageUrl
 * @return {Boolean}
 */
function layerContainsImage (layer, imageUrl) {
  // Check the layer itself.
  if (layer.type === 'image' && layer.imageUrl === imageUrl) {
    return layer;
  }

  // Check the layer's children.
  if (layer.children) {
    for (var i=0, len=layer.children.length; i < len; i++) {
      if (layerContainsImage(layer.children[i], imageUrl)) {
        return layer.children[i];
      }
    }
  }

  return false;
}

/**
 * Check if a layer is using a given FontFace.
 *
 * @param {RenderLayer} layer
 * @param {FontFace} fontFace
 * @return {Boolean}
 */
function layerContainsFontFace (layer, fontFace) {
  // Check the layer itself.
  if (layer.type === 'text' && layer.fontFace && layer.fontFace.id === fontFace.id) {
    return layer;
  }

  // Check the layer's children.
  if (layer.children) {
    for (var i=0, len=layer.children.length; i < len; i++) {
      if (layerContainsFontFace(layer.children[i], fontFace)) {
        return layer.children[i];
      }
    }
  }

  return false;
}

/**
 * Invalidates the backing stores for layers which contain an image layer
 * associated with the given imageUrl.
 *
 * @param {String} imageUrl
 */
function handleImageLoad (imageUrl) {
  _backingStores.forEach(function (backingStore) {
    if (layerContainsImage(backingStore.layer, imageUrl)) {
      invalidateBackingStore(backingStore.id);
    }
  });
}

/**
 * Invalidates the backing stores for layers which contain a text layer
 * associated with the given font face.
 *
 * @param {FontFace} fontFace
 */
function handleFontLoad (fontFace) {
  _backingStores.forEach(function (backingStore) {
    if (layerContainsFontFace(backingStore.layer, fontFace)) {
      invalidateBackingStore(backingStore.id);
    }
  });
}

/**
 * Draw a RenderLayer instance to a <canvas> context.
 *
 * @param {CanvasRenderingContext2d} ctx
 * @param {RenderLayer} layer
 */
function drawRenderLayer (ctx, layer) {
  var customDrawFunc;

  // Performance: avoid drawing hidden layers.
  if (typeof layer.alpha === 'number' && layer.alpha <= 0) {
    return;
  }

  switch (layer.type) {
    case 'image':
      customDrawFunc = drawImageRenderLayer;
      break;

    case 'text':
      customDrawFunc = drawTextRenderLayer;
      break;

    case 'gradient':
      customDrawFunc = drawGradientRenderLayer;
      break;
  }

  // Establish drawing context for certain properties:
  // - alpha
  // - translate
  var saveContext = (layer.alpha !== null && layer.alpha < 1) ||
                    (layer.translateX || layer.translateY);

  if (saveContext) {
    ctx.save();

    // Alpha:
    if (layer.alpha !== null && layer.alpha < 1) {
      ctx.globalAlpha = layer.alpha;
    }

    // Translation:
    if (layer.translateX || layer.translateY) {
      ctx.translate(layer.translateX || 0, layer.translateY || 0);
    }
  }

  // If the layer is bitmap-cacheable, draw in a pooled off-screen canvas.
  // We disable backing stores on pad since we flip there.
  if (layer.backingStoreId) {
    drawCacheableRenderLayer(ctx, layer, customDrawFunc);
  } else {
    // Draw default properties, such as background color.
    ctx.save();
    drawBaseRenderLayer(ctx, layer);

    // Draw custom properties if needed.
    customDrawFunc && customDrawFunc(ctx, layer);
    ctx.restore();

    // Draw child layers, sorted by their z-index.
    if (layer.children) {
      layer.children.slice().sort(sortByZIndexAscending).forEach(function (childLayer) {
        drawRenderLayer(ctx, childLayer);
      });
    }
  }

  // Pop the context state if we established a new drawing context.
  if (saveContext) {
    ctx.restore();
  }
}

/**
 * Draw base layer properties into a rendering context.
 * NOTE: The caller is responsible for calling save() and restore() as needed.
 *
 * @param {CanvasRenderingContext2d} ctx
 * @param {RenderLayer} layer
 */
function drawBaseRenderLayer (ctx, layer) {
  var frame = layer.frame;

  // Border radius:
  if (layer.borderRadius) {
    ctx.beginPath();
    ctx.moveTo(frame.x + layer.borderRadius, frame.y);
    ctx.arcTo(frame.x + frame.width, frame.y, frame.x + frame.width, frame.y + frame.height, layer.borderRadius);
    ctx.arcTo(frame.x + frame.width, frame.y + frame.height, frame.x, frame.y + frame.height, layer.borderRadius);
    ctx.arcTo(frame.x, frame.y + frame.height, frame.x, frame.y, layer.borderRadius);
    ctx.arcTo(frame.x, frame.y, frame.x + frame.width, frame.y, layer.borderRadius);
    ctx.closePath();

    // Create a clipping path when drawing an image or using border radius.
    if (layer.type === 'image') {
      ctx.clip();
    }

    // Border with border radius:
    if (layer.borderColor) {
      ctx.lineWidth = layer.borderWidth || 1;
      ctx.strokeStyle = layer.borderColor;
      ctx.stroke();
    }
  }

  // Border color (no border radius):
  if (layer.borderColor && !layer.borderRadius) {
    ctx.lineWidth = layer.borderWidth || 1;
    ctx.strokeStyle = layer.borderColor;
    ctx.strokeRect(frame.x, frame.y, frame.width, frame.height);
  }

  // Shadow:
  ctx.shadowBlur = layer.shadowBlur;
  ctx.shadowColor = layer.shadowColor;
  ctx.shadowOffsetX = layer.shadowOffsetX;
  ctx.shadowOffsetY = layer.shadowOffsetY;

  // Background color:
  if (layer.backgroundColor) {
    ctx.fillStyle = layer.backgroundColor;
    if (layer.borderRadius) {
      // Fill the current path when there is a borderRadius set.
      ctx.fill();
    } else {
      ctx.fillRect(frame.x, frame.y, frame.width, frame.height);
    }
  }
}

/**
 * Draw a bitmap-cacheable layer into a pooled <canvas>. The result will be
 * drawn into the given context. This will populate the layer backing store
 * cache with the result.
 *
 * @param {CanvasRenderingContext2d} ctx
 * @param {RenderLayer} layer
 * @param {Function} customDrawFunc
 * @private
 */
function drawCacheableRenderLayer (ctx, layer, customDrawFunc) {
  // See if there is a pre-drawn canvas in the pool.
  var backingStore = getBackingStore(layer.backingStoreId);
  var backingStoreScale = layer.scale || window.devicePixelRatio;
  var frameOffsetY = layer.frame.y;
  var frameOffsetX = layer.frame.x;
  var backingContext;

  if (!backingStore) {
    if (_backingStores.length >= Canvas.poolSize) {
      // Re-use the oldest backing store once we reach the pooling limit.
      backingStore = _backingStores[0].canvas;
      Canvas.call(backingStore, layer.frame.width, layer.frame.height, backingStoreScale);

      // Move the re-use canvas to the front of the queue.
      _backingStores[0].id = layer.backingStoreId;
      _backingStores[0].canvas = backingStore;
      _backingStores.push(_backingStores.shift());
    } else {
      // Create a new backing store, we haven't yet reached the pooling limit
      backingStore = new Canvas(layer.frame.width, layer.frame.height, backingStoreScale);
      _backingStores.push({
        id: layer.backingStoreId,
        layer: layer,
        canvas: backingStore
      });
    }

    // Draw into the backing <canvas> at (0, 0) - we will later use the
    // <canvas> to draw the layer as an image at the proper coordinates.
    backingContext = backingStore.getContext('2d');
    layer.translate(-frameOffsetX, -frameOffsetY);

    // Draw default properties, such as background color.
    backingContext.save();
    drawBaseRenderLayer(backingContext, layer);

    // Custom drawing operations
    customDrawFunc && customDrawFunc(backingContext, layer);
    backingContext.restore();

    // Draw child layers, sorted by their z-index.
    if (layer.children) {
      layer.children.slice().sort(sortByZIndexAscending).forEach(function (childLayer) {
        drawRenderLayer(backingContext, childLayer);
      });
    }

    // Restore layer's original frame.
    layer.translate(frameOffsetX, frameOffsetY);
  }

  // We have the pre-rendered canvas ready, draw it into the destination canvas.
  if (layer.clipRect) {
    // Fill the clipping rect in the destination canvas.
    var sx = (layer.clipRect.x - layer.frame.x) * backingStoreScale;
    var sy = (layer.clipRect.y - layer.frame.y) * backingStoreScale;
    var sw = layer.clipRect.width * backingStoreScale;
    var sh = layer.clipRect.height * backingStoreScale;
    var dx = layer.clipRect.x;
    var dy = layer.clipRect.y;
    var dw = layer.clipRect.width;
    var dh = layer.clipRect.height;

    // No-op for zero size rects. iOS / Safari will throw an exception.
    if (sw > 0 && sh > 0) {
      ctx.drawImage(backingStore.getRawCanvas(), sx, sy, sw, sh, dx, dy, dw, dh);
    }
  } else {
    // Fill the entire canvas
    ctx.drawImage(backingStore.getRawCanvas(), layer.frame.x, layer.frame.y, layer.frame.width, layer.frame.height);
  }
}

/**
 * @private
 */
function sortByZIndexAscending (layerA, layerB) {
  return (layerA.zIndex || 0) - (layerB.zIndex || 0);
}

/**
 * @private
 */
function drawImageRenderLayer (ctx, layer) {
  if (!layer.imageUrl) {
    return;
  }

  // Don't draw until loaded
  var image = ImageCache.get(layer.imageUrl);
  if (!image.isLoaded()) {
    return;
  }

  CanvasUtils.drawImage(ctx, image, layer.frame.x, layer.frame.y, layer.frame.width, layer.frame.height);
}

/**
 * @private
 */
function drawTextRenderLayer (ctx, layer) {
  // Fallback to standard font.
  var fontFace = layer.fontFace || FontFace.Default();

  // Don't draw text until loaded
  if (!FontUtils.isFontLoaded(fontFace)) {
    return;
  }

  CanvasUtils.drawText(ctx, layer.text, layer.frame.x, layer.frame.y, layer.frame.width, layer.frame.height, fontFace, {
    fontSize: layer.fontSize,
    lineHeight: layer.lineHeight,
    textAlign: layer.textAlign,
    color: layer.color
  });
}

/**
 * @private
 */
function drawGradientRenderLayer (ctx, layer) {
  // Default to linear gradient from top to bottom.
  var x1 = layer.x1 || layer.frame.x;
  var y1 = layer.y1 || layer.frame.y;
  var x2 = layer.x2 || layer.frame.x;
  var y2 = layer.y2 || layer.frame.y + layer.frame.height;
  CanvasUtils.drawGradient(ctx, x1, y1, x2, y2, layer.colorStops, layer.frame.x, layer.frame.y, layer.frame.width, layer.frame.height);
}

module.exports = {
  drawRenderLayer: drawRenderLayer,
  invalidateBackingStore: invalidateBackingStore,
  invalidateAllBackingStores: invalidateAllBackingStores,
  handleImageLoad: handleImageLoad,
  handleFontLoad: handleFontLoad,
  layerContainsImage: layerContainsImage,
  layerContainsFontFace: layerContainsFontFace
};


================================================
FILE: lib/Easing.js
================================================
// Penner easing equations
// https://gist.github.com/gre/1650294

var Easing = {

  linear: function (t) {
    return t;
  },

  easeInQuad: function (t) {
    return Math.pow(t, 2);
  },

  easeOutQuad: function (t) {
    return t * (2-t);
  },

  easeInOutQuad: function (t) {
    return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
  },

  easeInCubic: function (t) {
    return t * t * t;
  },

  easeOutCubic: function (t) {
    return (--t) * t * t + 1;
  },

  easeInOutCubic: function (t) {
    return t < .5 ? 4 * t * t * t : (t-1) * (2*t - 2) * (2*t - 2) + 1;
  }

};

module.exports = Easing;


================================================
FILE: lib/EventTypes.js
================================================
'use strict';

// Supported events that RenderLayer's can subscribe to.

module.exports = {
  onTouchStart: 'touchstart',
  onTouchMove: 'touchmove',
  onTouchEnd: 'touchend',
  onTouchCancel: 'touchcancel',
  onClick: 'click',
  onContextMenu: 'contextmenu',
  onDoubleClick: 'dblclick'
};


================================================
FILE: lib/FontFace.js
================================================
'use strict';

var _fontFaces = {};

/**
 * @param {String} family The CSS font-family value
 * @param {String} url The remote URL for the font file
 * @param {Object} attributes Font attributes supported: style, weight
 * @return {Object}
 */
function FontFace (family, url, attributes) {
  var fontFace;
  var fontId;

  attributes = attributes || {};
  attributes.style = attributes.style || 'normal';
  attributes.weight = attributes.weight || 400;

  fontId = getCacheKey(family, url, attributes);
  fontFace = _fontFaces[fontId];

  if (!fontFace) {
    fontFace = {};
    fontFace.id = fontId;
    fontFace.family = family;
    fontFace.url = url;
    fontFace.attributes = attributes;
    _fontFaces[fontId] = fontFace;
  }

  return fontFace;
}

/**
 * Helper for retrieving the default family by weight.
 *
 * @param {Number} fontWeight
 * @return {FontFace}
 */
FontFace.Default = function (fontWeight) {
  return FontFace('sans-serif', null, {weight: fontWeight});
};

/**
 * @internal
 */
function getCacheKey (family, url, attributes) {
  return family + url + Object.keys(attributes).sort().map(function (key) {
    return attributes[key];
  });
}

module.exports = FontFace;


================================================
FILE: lib/FontUtils.js
================================================
'use strict';

var FontFace = require('./FontFace');

var _useNativeImpl = (typeof window.FontFace !== 'undefined');
var _pendingFonts = {};
var _loadedFonts = {};
var _failedFonts = {};

var kFontLoadTimeout = 3000;

/**
 * Check if a font face has loaded
 * @param {FontFace} fontFace
 * @return {Boolean}
 */
function isFontLoaded (fontFace) {
  // For remote URLs, check the cache. System fonts (sans url) assume loaded.
  return _loadedFonts[fontFace.id] !== undefined || !fontFace.url;
}

/**
 * Load a remote font and execute a callback.
 * @param {FontFace} fontFace The font to Load
 * @param {Function} callback Function executed upon font Load
 */
function loadFont (fontFace, callback) {
  var defaultNode;
  var testNode;
  var checkFont;

  // See if we've previously loaded it.
  if (_loadedFonts[fontFace.id]) {
    return callback(null);
  }

  // See if we've previously failed to load it.
  if (_failedFonts[fontFace.id]) {
    return callback(_failedFonts[fontFace.id]);
  }

  // System font: assume already loaded.
  if (!fontFace.url) {
    return callback(null);
  }

  // Font load is already in progress:
  if (_pendingFonts[fontFace.id]) {
    _pendingFonts[fontFace.id].callbacks.push(callback);
    return;
  }

  // Create the test <span>'s for measuring.
  defaultNode = createTestNode('Helvetica', fontFace.attributes);
  testNode = createTestNode(fontFace.family, fontFace.attributes);
  document.body.appendChild(testNode);
  document.body.appendChild(defaultNode);

  _pendingFonts[fontFace.id] = {
    startTime: Date.now(),
    defaultNode: defaultNode,
    testNode: testNode,
    callbacks: [callback]
  };

  // Font watcher
  checkFont = function () {
    var currWidth = testNode.getBoundingClientRect().width;
    var defaultWidth = defaultNode.getBoundingClientRect().width;
    var loaded = currWidth !== defaultWidth;

    if (loaded) {
      handleFontLoad(fontFace, null);
    } else {
      // Timeout?
      if (Date.now() - _pendingFonts[fontFace.id].startTime >= kFontLoadTimeout) {
        handleFontLoad(fontFace, true);
      } else {
        requestAnimationFrame(checkFont);
      }
    }
  };

  // Start watching
  checkFont();
}

// Internal
// ========

/**
 * Native FontFace loader implementation
 * @internal
 */
function loadFontNative (fontFace, callback) {
  var theFontFace;

  // See if we've previously loaded it.
  if (_loadedFonts[fontFace.id]) {
    return callback(null);
  }

  // See if we've previously failed to load it.
  if (_failedFonts[fontFace.id]) {
    return callback(_failedFonts[fontFace.id]);
  }

  // System font: assume it's installed.
  if (!fontFace.url) {
    return callback(null);
  }

  // Font load is already in progress:
  if (_pendingFonts[fontFace.id]) {
    _pendingFonts[fontFace.id].callbacks.push(callback);
    return;
  }

  _pendingFonts[fontFace.id] = {
    startTime: Date.now(),
    callbacks: [callback]
  };

  // Use font loader API
  theFontFace = new window.FontFace(fontFace.family,
    'url(' + fontFace.url + ')', fontFace.attributes);

  theFontFace.load().then(function () {
    _loadedFonts[fontFace.id] = true;
    callback(null);
  }, function (err) {
    _failedFonts[fontFace.id] = err;
    callback(err);
  });
}

/**
 * Helper method for created a hidden <span> with a given font.
 * Uses TypeKit's default test string, which is said to result
 * in highly varied measured widths when compared to the default font.
 * @internal
 */
function createTestNode (family, attributes) {
  var span = document.createElement('span');
  span.setAttribute('data-fontfamily', family);
  span.style.cssText = 'position:absolute; left:-5000px; top:-5000px; visibility:hidden;' +
    'font-size:100px; font-family:"' + family + '", Helvetica;font-weight: ' + attributes.weight + ';' +
    'font-style:' + attributes.style + ';';
  span.innerHTML = 'BESs';
  return span;
}

/**
 * @internal
 */
function handleFontLoad (fontFace, timeout) {
  var error = timeout ? 'Exceeded load timeout of ' + kFontLoadTimeout + 'ms' : null;

  if (!error) {
    _loadedFonts[fontFace.id] = true;
  } else {
    _failedFonts[fontFace.id] = error;
  }

  // Execute pending callbacks.
  _pendingFonts[fontFace.id].callbacks.forEach(function (callback) {
    callback(error);
  });

  // Clean up DOM
  if (_pendingFonts[fontFace.id].defaultNode) {
    document.body.removeChild(_pendingFonts[fontFace.id].defaultNode);
  }
  if (_pendingFonts[fontFace.id].testNode) {
    document.body.removeChild(_pendingFonts[fontFace.id].testNode);
  }

  // Clean up waiting queue
  delete _pendingFonts[fontFace.id];
}

module.exports = {
  isFontLoaded: isFontLoaded,
  loadFont: _useNativeImpl ? loadFontNative : loadFont
};


================================================
FILE: lib/FrameUtils.js
================================================
'use strict';

function Frame (x, y, width, height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
}

/**
 * Get a frame object
 *
 * @param {Number} x
 * @param {Number} y
 * @param {Number} width
 * @param {Number} height
 * @return {Frame}
 */
function make (x, y, width, height) {
  return new Frame(x, y, width, height);
}

/**
 * Return a zero size anchored at (0, 0).
 *
 * @return {Frame}
 */
function zero () {
  return make(0, 0, 0, 0);
}

/**
 * Return a cloned frame
 *
 * @param {Frame} frame
 * @return {Frame}
 */
function clone (frame) {
  return make(frame.x, frame.y, frame.width, frame.height);
}

/**
 * Creates a new frame by a applying edge insets. This method accepts CSS
 * shorthand notation e.g. inset(myFrame, 10, 0);
 *
 * @param {Frame} frame
 * @param {Number} top
 * @param {Number} right
 * @param {?Number} bottom
 * @param {?Number} left
 * @return {Frame}
 */
function inset (frame, top, right, bottom, left) {
  var frameCopy = clone(frame);

  // inset(myFrame, 10, 0) => inset(myFrame, 10, 0, 10, 0)
  if (typeof bottom === 'undefined') {
    bottom = top;
    left = right;
  }

  // inset(myFrame, 10) => inset(myFrame, 10, 10, 10, 10)
  if (typeof right === 'undefined') {
    right = bottom = left = top;
  }

  frameCopy.x += left;
  frameCopy.y += top;
  frameCopy.height -= (top + bottom);
  frameCopy.width -= (left + right);

  return frameCopy;
}

/**
 * Compute the intersection region between 2 frames.
 *
 * @param {Frame} frame
 * @param {Frame} otherFrame
 * @return {Frame}
 */
function intersection (frame, otherFrame) {
  var x = Math.max(frame.x, otherFrame.x);
  var width = Math.min(frame.x + frame.width, otherFrame.x + otherFrame.width);
  var y = Math.max(frame.y, otherFrame.y);
  var height = Math.min(frame.y + frame.height, otherFrame.y + otherFrame.height);
  if (width >= x && height >= y) {
    return make(x, y, width - x, height - y);
  }
  return null;
}

/**
 * Compute the union of two frames
 *
 * @param {Frame} frame
 * @param {Frame} otherFrame
 * @return {Frame}
 */
function union (frame, otherFrame) {
  var x1 = Math.min(frame.x, otherFrame.x);
  var x2 = Math.max(frame.x + frame.width, otherFrame.x + otherFrame.width);
  var y1 = Math.min(frame.y, otherFrame.y);
  var y2 = Math.max(frame.y + frame.height, otherFrame.y + otherFrame.height);
  return make(x1, y1, x2 - x1, y2 - y1);
}

/**
 * Determine if 2 frames intersect each other
 *
 * @param {Frame} frame
 * @param {Frame} otherFrame
 * @return {Boolean}
 */
function intersects (frame, otherFrame) {
  return !(otherFrame.x > frame.x + frame.width ||
           otherFrame.x + otherFrame.width < frame.x ||
           otherFrame.y > frame.y + frame.height ||
           otherFrame.y + otherFrame.height < frame.y);
}

module.exports = {
  make: make,
  zero: zero,
  clone: clone,
  inset: inset,
  intersection: intersection,
  intersects: intersects,
  union: union
};



================================================
FILE: lib/Gradient.js
================================================
'use strict';

var React = require('react');
var createComponent = require('./createComponent');
var LayerMixin = require('./LayerMixin');

var Gradient = createComponent('Gradient', LayerMixin, {

  applyGradientProps: function (prevProps, props) {
    var layer = this.node;
    layer.type = 'gradient';
    layer.colorStops = props.colorStops || [];
    this.applyLayerProps(prevProps, props);
  },

  mountComponent: function (
    transaction,
    nativeParent,
    nativeContainerInfo,
    context
  ) {
    var props = this._currentElement.props;
    var layer = this.node;
    this.applyGradientProps({}, props);
    return layer;
  },

  receiveComponent: function (nextComponent, transaction, context) {
    var prevProps = this._currentElement.props;
    var props = nextComponent.props;
    this.applyGradientProps({}, props);
    this._currentElement = nextComponent;
    this.node.invalidateLayout();
  },

});


module.exports = Gradient;


================================================
FILE: lib/Group.js
================================================
'use strict';

var createComponent = require('./createComponent');
var ContainerMixin = require('./ContainerMixin');
var LayerMixin = require('./LayerMixin');
var RenderLayer = require('./RenderLayer');

var Group = createComponent('Group', LayerMixin, ContainerMixin, {

  mountComponent: function (
    transaction,
    nativeParent,
    nativeContainerInfo,
    context
  ) {
    var props = this._currentElement.props;
    var layer = this.node;

    this.applyLayerProps({}, props);
    this.mountAndInjectChildren(props.children, transaction, context);

    return layer;
  },

  receiveComponent: function (nextComponent, transaction, context) {
    var props = nextComponent.props;
    var prevProps = this._currentElement.props;
    this.applyLayerProps(prevProps, props);
    this.updateChildren(props.children, transaction, context);
    this._currentElement = nextComponent;
    this.node.invalidateLayout();
  },

  unmountComponent: function () {
    LayerMixin.unmountComponent.call(this);
    this.unmountChildren();
  }

});

module.exports = Group;


================================================
FILE: lib/Image.js
================================================
'use strict';

var React = require('react');
var createComponent = require('./createComponent');
var LayerMixin = require('./LayerMixin');
var Layer = require('./Layer');
var Group = require('./Group');
var ImageCache = require('./ImageCache');
var Easing = require('./Easing');
var clamp = require('./clamp');

var FADE_DURATION = 200;

var RawImage = createComponent('Image', LayerMixin, {

  applyImageProps: function (prevProps, props) {
    var layer = this.node;

    layer.type = 'image';
    layer.imageUrl = props.src;
  },

  mountComponent: function (
    transaction,
    nativeParent,
    nativeContainerInfo,
    context
  ) {
    var props = this._currentElement.props;
    var layer = this.node;
    this.applyLayerProps({}, props);
    this.applyImageProps({}, props);
    return layer;
  },

  receiveComponent: function (nextComponent, transaction, context) {
    var prevProps = this._currentElement.props;
    var props = nextComponent.props;
    this.applyLayerProps(prevProps, props);
    this.applyImageProps(prevProps, props);
    this._currentElement = nextComponent;
    this.node.invalidateLayout();
  },

});

var Image = React.createClass({

  propTypes: {
    src: React.PropTypes.string.isRequired,
    style: React.PropTypes.object,
    useBackingStore: React.PropTypes.bool,
    fadeIn: React.PropTypes.bool,
    fadeInDuration: React.PropTypes.number
  },

  getInitialState: function () {
    var loaded = ImageCache.get(this.props.src).isLoaded();
    return {
      loaded: loaded,
      imageAlpha: loaded ? 1 : 0
    };
  },

  componentDidMount: function () {
    ImageCache.get(this.props.src).on('load', this.handleImageLoad);
  },

  componentWillUpdate: function(nextProps, nextState) {
    if(nextProps.src !== this.props.src) {
      ImageCache.get(this.props.src).removeListener('load', this.handleImageLoad);
      ImageCache.get(nextProps.src).on('load', this.handleImageLoad);
      var loaded = ImageCache.get(nextProps.src).isLoaded();
      this.setState({loaded: loaded});
    }
  },

  componentWillUnmount: function () {
    if (this._pendingAnimationFrame) {
      cancelAnimationFrame(this._pendingAnimationFrame);
    }
    ImageCache.get(this.props.src).removeListener('load', this.handleImageLoad);
  },

  componentDidUpdate: function (prevProps, prevState) {
    if (this.refs.image) {
      this.refs.image.invalidateLayout();
    }
  },

  render: function () {
    var rawImage;
    var imageStyle = Object.assign({}, this.props.style);
    var style = Object.assign({}, this.props.style);
    var backgroundStyle = Object.assign({}, this.props.style);
    var useBackingStore = this.state.loaded ? this.props.useBackingStore : false;

    // Hide the image until loaded.
    imageStyle.alpha = this.state.imageAlpha;

    // Hide opaque background if image loaded so that images with transparent
    // do not render on top of solid color.
    style.backgroundColor = imageStyle.backgroundColor = null;
    backgroundStyle.alpha = clamp(1 - this.state.imageAlpha, 0, 1);

    return (
      React.createElement(Group, {ref: 'main', style: style},
        React.createElement(Layer, {ref: 'background', style: backgroundStyle}),
        React.createElement(RawImage, {ref: 'image', src: this.props.src, style: imageStyle, useBackingStore: useBackingStore})
      )
    );
  },

  handleImageLoad: function () {
    var imageAlpha = 1;
    if (this.props.fadeIn) {
      imageAlpha = 0;
      this._animationStartTime = Date.now();
      this._pendingAnimationFrame = requestAnimationFrame(this.stepThroughAnimation);
    }
    this.setState({ loaded: true, imageAlpha: imageAlpha });
  },

  stepThroughAnimation: function () {
    var fadeInDuration = this.props.fadeInDuration || FADE_DURATION;
    var alpha = Easing.easeInCubic((Date.now() - this._animationStartTime) / fadeInDuration);
    alpha = clamp(alpha, 0, 1);
    this.setState({ imageAlpha: alpha });
    if (alpha < 1) {
      this._pendingAnimationFrame = requestAnimationFrame(this.stepThroughAnimation);
    }
  }

});

module.exports = Image;


================================================
FILE: lib/ImageCache.js
================================================
'use strict';

var EventEmitter = require('events');

var NOOP = function () {};

function Img (src) {
  this._originalSrc = src;
  this._img = new Image();
  this._img.onload = this.emit.bind(this, 'load');
  this._img.onerror = this.emit.bind(this, 'error');
  this._img.crossOrigin = true;
  this._img.src = src;

  // The default impl of events emitter will throw on any 'error' event unless
  // there is at least 1 handler. Logging anything in this case is unnecessary
  // since the browser console will log it too.
  this.on('error', NOOP);

  // Default is just 10.
  this.setMaxListeners(100);
}

Object.assign(Img.prototype, EventEmitter.prototype, {

  /**
   * Pooling owner looks for this
   */
  destructor: function () {
    // Make sure we aren't leaking callbacks.
    this.removeAllListeners();
  },

  /**
   * Retrieve the original image URL before browser normalization
   *
   * @return {String}
   */
  getOriginalSrc: function () {
    return this._originalSrc;
  },

  /**
   * Retrieve a reference to the underyling <img> node.
   *
   * @return {HTMLImageElement}
   */
  getRawImage: function () {
    return this._img;
  },

  /**
   * Retrieve the loaded image width
   *
   * @return {Number}
   */
  getWidth: function () {
    return this._img.naturalWidth;
  },

  /**
   * Retrieve the loaded image height
   *
   * @return {Number}
   */
  getHeight: function () {
    return this._img.naturalHeight;
  },

  /**
   * @return {Bool}
   */
  isLoaded: function () {
    return this._img.naturalHeight > 0;
  }

});

var kInstancePoolLength = 300;

var _instancePool = {
  length: 0,
  // Keep all the nodes in memory.
  elements: {
    
  },
  
  // Push with 0 frequency
  push: function (hash, data) {
    this.length++;
    this.elements[hash] = {
      hash: hash, // Helps identifying 
      freq: 0,
      data: data
    };
  },
  
  get: function (path) {
    var element = this.elements[path];
    
    if( element ){
      element.freq++;
      return element.data;
    }
    
    return null;
  },
  
  // used to explicitely remove the path
  removeElement: function (path) {
    // Now almighty GC can claim this soul
    var element = this.elements[path];
    delete this.elements[path];
    this.length--;
    return element;
  },
  
  _reduceLeastUsed: function (least, currentHash) {
    var current = _instancePool.elements[currentHash];
    
    if( least.freq > current.freq ){
      return current;
    }
    
    return least;
  },
  
  popLeastUsed: function () {
    var reducer = _instancePool._reduceLeastUsed;
    var minUsed = Object.keys(this.elements).reduce(reducer, { freq: Infinity });
    
    if( minUsed.hash ){
      return this.removeElement(minUsed.hash);  
    }
    
    return null;
  }
};

var ImageCache = {

  /**
   * Retrieve an image from the cache
   *
   * @return {Img}
   */
  get: function (src) {
    var image = _instancePool.get(src);
    if (!image) {
      // Awesome LRU
      image = new Img(src);
      if (_instancePool.length >= kInstancePoolLength) {
        _instancePool.popLeastUsed().destructor();
      }
      _instancePool.push(image.getOriginalSrc(), image);
    }
    return image;
  }

};

module.exports = ImageCache;


================================================
FILE: lib/Layer.js
================================================
'use strict';

var createComponent = require('./createComponent');
var LayerMixin = require('./LayerMixin');

var Layer = createComponent('Layer', LayerMixin, {

  mountComponent: function (
    transaction,
    nativeParent,
    nativeContainerInfo,
    context
  ) {
    var props = this._currentElement.props;
    var layer = this.node;
    this.applyLayerProps({}, props);
    return layer;
  },

  receiveComponent: function (nextComponent, transaction, context) {
    var prevProps = this._currentElement.props;
    var props = nextComponent.props;
    this.applyLayerProps(prevProps, props);
    this._currentElement = nextComponent;
    this.node.invalidateLayout();
  }

});

module.exports = Layer;


================================================
FILE: lib/LayerMixin.js
================================================
'use strict';

// Adapted from ReactART:
// https://github.com/reactjs/react-art

var FrameUtils = require('./FrameUtils');
var DrawingUtils = require('./DrawingUtils');
var EventTypes = require('./EventTypes');

var LAYER_GUID = 0;

var LayerMixin = {

  construct: function(element) {
    this._currentElement = element;
    this._layerId = LAYER_GUID++;
  },

  getPublicInstance: function() {
    return this.node;
  },

  putEventListener: function(type, listener) {
    var subscriptions = this.subscriptions || (this.subscriptions = {});
    var listeners = this.listeners || (this.listeners = {});
    listeners[type] = listener;
    if (listener) {
      if (!subscriptions[type]) {
        subscriptions[type] = this.node.subscribe(type, listener, this);
      }
    } else {
      if (subscriptions[type]) {
        subscriptions[type]();
        delete subscriptions[type];
      }
    }
  },

  handleEvent: function(event) {
    // TODO
  },

  destroyEventListeners: function() {
    // TODO
  },

  applyLayerProps: function (prevProps, props) {
    var layer = this.node;
    var style = (props && props.style) ? props.style : {};
    layer._originalStyle = style;

    // Common layer properties
    layer.alpha = style.alpha;
    layer.backgroundColor = style.backgroundColor;
    layer.borderColor = style.borderColor;
    layer.borderWidth = style.borderWidth;
    layer.borderRadius = style.borderRadius;
    layer.clipRect = style.clipRect;
    layer.frame = FrameUtils.make(style.left || 0, style.top || 0, style.width || 0, style.height || 0);
    layer.scale = style.scale;
    layer.translateX = style.translateX;
    layer.translateY = style.translateY;
    layer.zIndex = style.zIndex;

    // Shadow
    layer.shadowColor = style.shadowColor;
    layer.shadowBlur = style.shadowBlur;
    layer.shadowOffsetX = style.shadowOffsetX;
    layer.shadowOffsetY = style.shadowOffsetY;

    // Generate backing store ID as needed.
    if (props.useBackingStore) {
      layer.backingStoreId = this._layerId;
    }

    // Register events
    for (var type in EventTypes) {
      this.putEventListener(EventTypes[type], props[type]);
    }
  },

  mountComponentIntoNode: function(rootID, container) {
    throw new Error(
      'You cannot render a Canvas component standalone. ' +
      'You need to wrap it in a Surface.'
    );
  },

  unmountComponent: function() {
    this.destroyEventListeners();
  },
  getHostNode: function () { return this.node },
  getNativeNode: function () { return this.node },

};

module.exports = LayerMixin;


================================================
FILE: lib/Layout.js
================================================
// https://github.com/facebook/css-layout

/**
 * Copyright (c) 2014, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

var computeLayout = (function() {

  function capitalizeFirst(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }

  function getSpacing(node, type, suffix, location) {
    var key = type + capitalizeFirst(location) + suffix;
    if (key in node.style) {
      return node.style[key];
    }

    key = type + suffix;
    if (key in node.style) {
      return node.style[key];
    }

    return 0;
  }

  function getPositiveSpacing(node, type, suffix, location) {
    var key = type + capitalizeFirst(location) + suffix;
    if (key in node.style && node.style[key] >= 0) {
      return node.style[key];
    }

    key = type + suffix;
    if (key in node.style && node.style[key] >= 0) {
      return node.style[key];
    }

    return 0;
  }

  function isUndefined(value) {
    return value === undefined;
  }

  function getMargin(node, location) {
    return getSpacing(node, 'margin', '', location);
  }

  function getPadding(node, location) {
    return getPositiveSpacing(node, 'padding', '', location);
  }

  function getBorder(node, location) {
    return getPositiveSpacing(node, 'border', 'Width', location);
  }

  function getPaddingAndBorder(node, location) {
    return getPadding(node, location) + getBorder(node, location);
  }

  function getMarginAxis(node, axis) {
    return getMargin(node, leading[axis]) + getMargin(node, trailing[axis]);
  }

  function getPaddingAndBorderAxis(node, axis) {
    return getPaddingAndBorder(node, leading[axis]) + getPaddingAndBorder(node, trailing[axis]);
  }

  function getJustifyContent(node) {
    if ('justifyContent' in node.style) {
      return node.style.justifyContent;
    }
    return 'flex-start';
  }

  function getAlignItem(node, child) {
    if ('alignSelf' in child.style) {
      return child.style.alignSelf;
    }
    if ('alignItems' in node.style) {
      return node.style.alignItems;
    }
    return 'stretch';
  }

  function getFlexDirection(node) {
    if ('flexDirection' in node.style) {
      return node.style.flexDirection;
    }
    return 'column';
  }

  function getPositionType(node) {
    if ('position' in node.style) {
      return node.style.position;
    }
    return 'relative';
  }

  function getFlex(node) {
    return node.style.flex;
  }

  function isFlex(node) {
    return (
      getPositionType(node) === CSS_POSITION_RELATIVE &&
      getFlex(node) > 0
    );
  }

  function isFlexWrap(node) {
    return node.style.flexWrap === 'wrap';
  }

  function getDimWithMargin(node, axis) {
    return node.layout[dim[axis]] + getMarginAxis(node, axis);
  }

  function isDimDefined(node, axis) {
    return !isUndefined(node.style[dim[axis]]) && node.style[dim[axis]] >= 0;
  }

  function isPosDefined(node, pos) {
    return !isUndefined(node.style[pos]);
  }

  function isMeasureDefined(node) {
    return 'measure' in node.style;
  }

  function getPosition(node, pos) {
    if (pos in node.style) {
      return node.style[pos];
    }
    return 0;
  }

  // When the user specifically sets a value for width or height
  function setDimensionFromStyle(node, axis) {
    // The parent already computed us a width or height. We just skip it
    if (!isUndefined(node.layout[dim[axis]])) {
      return;
    }
    // We only run if there's a width or height defined
    if (!isDimDefined(node, axis)) {
      return;
    }

    // The dimensions can never be smaller than the padding and border
    node.layout[dim[axis]] = fmaxf(
      node.style[dim[axis]],
      getPaddingAndBorderAxis(node, axis)
    );
  }

  // If both left and right are defined, then use left. Otherwise return
  // +left or -right depending on which is defined.
  function getRelativePosition(node, axis) {
    if (leading[axis] in node.style) {
      return getPosition(node, leading[axis]);
    }
    return -getPosition(node, trailing[axis]);
  }

  var leading = {
    row: 'left',
    column: 'top'
  };
  var trailing = {
    row: 'right',
    column: 'bottom'
  };
  var pos = {
    row: 'left',
    column: 'top'
  };
  var dim = {
    row: 'width',
    column: 'height'
  };

  function fmaxf(a, b) {
    if (a > b) {
      return a;
    }
    return b;
  }

  var CSS_UNDEFINED = undefined;

  var CSS_FLEX_DIRECTION_ROW = 'row';
  var CSS_FLEX_DIRECTION_COLUMN = 'column';

  var CSS_JUSTIFY_FLEX_START = 'flex-start';
  var CSS_JUSTIFY_CENTER = 'center';
  var CSS_JUSTIFY_FLEX_END = 'flex-end';
  var CSS_JUSTIFY_SPACE_BETWEEN = 'space-between';
  var CSS_JUSTIFY_SPACE_AROUND = 'space-around';

  var CSS_ALIGN_FLEX_START = 'flex-start';
  var CSS_ALIGN_CENTER = 'center';
  var CSS_ALIGN_FLEX_END = 'flex-end';
  var CSS_ALIGN_STRETCH = 'stretch';

  var CSS_POSITION_RELATIVE = 'relative';
  var CSS_POSITION_ABSOLUTE = 'absolute';

  return function layoutNode(node, parentMaxWidth) {
    var/*css_flex_direction_t*/ mainAxis = getFlexDirection(node);
    var/*css_flex_direction_t*/ crossAxis = mainAxis === CSS_FLEX_DIRECTION_ROW ?
      CSS_FLEX_DIRECTION_COLUMN :
      CSS_FLEX_DIRECTION_ROW;

    // Handle width and height style attributes
    setDimensionFromStyle(node, mainAxis);
    setDimensionFromStyle(node, crossAxis);

    // The position is set by the parent, but we need to complete it with a
    // delta composed of the margin and left/top/right/bottom
    node.layout[leading[mainAxis]] += getMargin(node, leading[mainAxis]) +
      getRelativePosition(node, mainAxis);
    node.layout[leading[crossAxis]] += getMargin(node, leading[crossAxis]) +
      getRelativePosition(node, crossAxis);

    if (isMeasureDefined(node)) {
      var/*float*/ width = CSS_UNDEFINED;
      if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {
        width = node.style.width;
      } else if (!isUndefined(node.layout[dim[CSS_FLEX_DIRECTION_ROW]])) {
        width = node.layout[dim[CSS_FLEX_DIRECTION_ROW]];
      } else {
        width = parentMaxWidth -
          getMarginAxis(node, CSS_FLEX_DIRECTION_ROW);
      }
      width -= getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);

      // We only need to give a dimension for the text if we haven't got any
      // for it computed yet. It can either be from the style attribute or because
      // the element is flexible.
      var/*bool*/ isRowUndefined = !isDimDefined(node, CSS_FLEX_DIRECTION_ROW) &&
        isUndefined(node.layout[dim[CSS_FLEX_DIRECTION_ROW]]);
      var/*bool*/ isColumnUndefined = !isDimDefined(node, CSS_FLEX_DIRECTION_COLUMN) &&
        isUndefined(node.layout[dim[CSS_FLEX_DIRECTION_COLUMN]]);

      // Let's not measure the text if we already know both dimensions
      if (isRowUndefined || isColumnUndefined) {
        var/*css_dim_t*/ measure_dim = node.style.measure(
          /*(c)!node->context,*/
          width
        );
        if (isRowUndefined) {
          node.layout.width = measure_dim.width +
            getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
        }
        if (isColumnUndefined) {
          node.layout.height = measure_dim.height +
            getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);
        }
      }
      return;
    }

    // Pre-fill some dimensions straight from the parent
    for (var/*int*/ i = 0; i < node.children.length; ++i) {
      var/*css_node_t**/ child = node.children[i];
      // Pre-fill cross axis dimensions when the child is using stretch before
      // we call the recursive layout pass
      if (getAlignItem(node, child) === CSS_ALIGN_STRETCH &&
          getPositionType(child) === CSS_POSITION_RELATIVE &&
          !isUndefined(node.layout[dim[crossAxis]]) &&
          !isDimDefined(child, crossAxis)) {
        child.layout[dim[crossAxis]] = fmaxf(
          node.layout[dim[crossAxis]] -
            getPaddingAndBorderAxis(node, crossAxis) -
            getMarginAxis(child, crossAxis),
          // You never want to go smaller than padding
          getPaddingAndBorderAxis(child, crossAxis)
        );
      } else if (getPositionType(child) == CSS_POSITION_ABSOLUTE) {
        // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
        // left and right or top and bottom).
        for (var/*int*/ ii = 0; ii < 2; ii++) {
          var/*css_flex_direction_t*/ axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
          if (!isUndefined(node.layout[dim[axis]]) &&
              !isDimDefined(child, axis) &&
              isPosDefined(child, leading[axis]) &&
              isPosDefined(child, trailing[axis])) {
            child.layout[dim[axis]] = fmaxf(
              node.layout[dim[axis]] -
              getPaddingAndBorderAxis(node, axis) -
              getMarginAxis(child, axis) -
              getPosition(child, leading[axis]) -
              getPosition(child, trailing[axis]),
              // You never want to go smaller than padding
              getPaddingAndBorderAxis(child, axis)
            );
          }
        }
      }
    }

    var/*float*/ definedMainDim = CSS_UNDEFINED;
    if (!isUndefined(node.layout[dim[mainAxis]])) {
      definedMainDim = node.layout[dim[mainAxis]] -
          getPaddingAndBorderAxis(node, mainAxis);
    }

    // We want to execute the next two loops one per line with flex-wrap
    var/*int*/ startLine = 0;
    var/*int*/ endLine = 0;
    var/*int*/ nextOffset = 0;
    var/*int*/ alreadyComputedNextLayout = 0;
    // We aggregate the total dimensions of the container in those two variables
    var/*float*/ linesCrossDim = 0;
    var/*float*/ linesMainDim = 0;
    while (endLine < node.children.length) {
      // <Loop A> Layout non flexible children and count children by type

      // mainContentDim is accumulation of the dimensions and margin of all the
      // non flexible children. This will be used in order to either set the
      // dimensions of the node if none already exist, or to compute the
      // remaining space left for the flexible children.
      var/*float*/ mainContentDim = 0;

      // There are three kind of children, non flexible, flexible and absolute.
      // We need to know how many there are in order to distribute the space.
      var/*int*/ flexibleChildrenCount = 0;
      var/*float*/ totalFlexible = 0;
      var/*int*/ nonFlexibleChildrenCount = 0;
      for (var/*int*/ i = startLine; i < node.children.length; ++i) {
        var/*css_node_t**/ child = node.children[i];
        var/*float*/ nextContentDim = 0;

        // It only makes sense to consider a child flexible if we have a computed
        // dimension for the node.
        if (!isUndefined(node.layout[dim[mainAxis]]) && isFlex(child)) {
          flexibleChildrenCount++;
          totalFlexible += getFlex(child);

          // Even if we don't know its exact size yet, we already know the padding,
          // border and margin. We'll use this partial information to compute the
          // remaining space.
          nextContentDim = getPaddingAndBorderAxis(child, mainAxis) +
            getMarginAxis(child, mainAxis);

        } else {
          var/*float*/ maxWidth = CSS_UNDEFINED;
          if (mainAxis === CSS_FLEX_DIRECTION_ROW) {
            // do nothing
          } else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {
            maxWidth = node.layout[dim[CSS_FLEX_DIRECTION_ROW]] -
              getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
          } else {
            maxWidth = parentMaxWidth -
              getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) -
              getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
          }

          // This is the main recursive call. We layout non flexible children.
          if (alreadyComputedNextLayout === 0) {
            layoutNode(child, maxWidth);
          }

          // Absolute positioned elements do not take part of the layout, so we
          // don't use them to compute mainContentDim
          if (getPositionType(child) === CSS_POSITION_RELATIVE) {
            nonFlexibleChildrenCount++;
            // At this point we know the final size and margin of the element.
            nextContentDim = getDimWithMargin(child, mainAxis);
          }
        }

        // The element we are about to add would make us go to the next line
        if (isFlexWrap(node) &&
            !isUndefined(node.layout[dim[mainAxis]]) &&
            mainContentDim + nextContentDim > definedMainDim &&
            // If there's only one element, then it's bigger than the content
            // and needs its own line
            i !== startLine) {
          alreadyComputedNextLayout = 1;
          break;
        }
        alreadyComputedNextLayout = 0;
        mainContentDim += nextContentDim;
        endLine = i + 1;
      }

      // <Loop B> Layout flexible children and allocate empty space

      // In order to position the elements in the main axis, we have two
      // controls. The space between the beginning and the first element
      // and the space between each two elements.
      var/*float*/ leadingMainDim = 0;
      var/*float*/ betweenMainDim = 0;

      // The remaining available space that needs to be allocated
      var/*float*/ remainingMainDim = 0;
      if (!isUndefined(node.layout[dim[mainAxis]])) {
        remainingMainDim = definedMainDim - mainContentDim;
      } else {
        remainingMainDim = fmaxf(mainContentDim, 0) - mainContentDim;
      }

      // If there are flexible children in the mix, they are going to fill the
      // remaining space
      if (flexibleChildrenCount !== 0) {
        var/*float*/ flexibleMainDim = remainingMainDim / totalFlexible;

        // The non flexible children can overflow the container, in this case
        // we should just assume that there is no space available.
        if (flexibleMainDim < 0) {
          flexibleMainDim = 0;
        }
        // We iterate over the full array and only apply the action on flexible
        // children. This is faster than actually allocating a new array that
        // contains only flexible children.
        for (var/*int*/ i = startLine; i < endLine; ++i) {
          var/*css_node_t**/ child = node.children[i];
          if (isFlex(child)) {
            // At this point we know the final size of the element in the main
            // dimension
            child.layout[dim[mainAxis]] = flexibleMainDim * getFlex(child) +
              getPaddingAndBorderAxis(child, mainAxis);

            var/*float*/ maxWidth = CSS_UNDEFINED;
            if (mainAxis === CSS_FLEX_DIRECTION_ROW) {
              // do nothing
            } else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {
              maxWidth = node.layout[dim[CSS_FLEX_DIRECTION_ROW]] -
                getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
            } else {
              maxWidth = parentMaxWidth -
                getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) -
                getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
            }

            // And we recursively call the layout algorithm for this child
            layoutNode(child, maxWidth);
          }
        }

      // We use justifyContent to figure out how to allocate the remaining
      // space available
      } else {
        var/*css_justify_t*/ justifyContent = getJustifyContent(node);
        if (justifyContent === CSS_JUSTIFY_FLEX_START) {
          // Do nothing
        } else if (justifyContent === CSS_JUSTIFY_CENTER) {
          leadingMainDim = remainingMainDim / 2;
        } else if (justifyContent === CSS_JUSTIFY_FLEX_END) {
          leadingMainDim = remainingMainDim;
        } else if (justifyContent === CSS_JUSTIFY_SPACE_BETWEEN) {
          remainingMainDim = fmaxf(remainingMainDim, 0);
          if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 !== 0) {
            betweenMainDim = remainingMainDim /
              (flexibleChildrenCount + nonFlexibleChildrenCount - 1);
          } else {
            betweenMainDim = 0;
          }
        } else if (justifyContent === CSS_JUSTIFY_SPACE_AROUND) {
          // Space on the edges is half of the space between elements
          betweenMainDim = remainingMainDim /
            (flexibleChildrenCount + nonFlexibleChildrenCount);
          leadingMainDim = betweenMainDim / 2;
        }
      }

      // <Loop C> Position elements in the main axis and compute dimensions

      // At this point, all the children have their dimensions set. We need to
      // find their position. In order to do that, we accumulate data in
      // variables that are also useful to compute the total dimensions of the
      // container!
      var/*float*/ crossDim = 0;
      var/*float*/ mainDim = leadingMainDim +
        getPaddingAndBorder(node, leading[mainAxis]);

      for (var/*int*/ i = startLine; i < endLine; ++i) {
        var/*css_node_t**/ child = node.children[i];

        if (getPositionType(child) === CSS_POSITION_ABSOLUTE &&
            isPosDefined(child, leading[mainAxis])) {
          // In case the child is position absolute and has left/top being
          // defined, we override the position to whatever the user said
          // (and margin/border).
          child.layout[pos[mainAxis]] = getPosition(child, leading[mainAxis]) +
            getBorder(node, leading[mainAxis]) +
            getMargin(child, leading[mainAxis]);
        } else {
          // If the child is position absolute (without top/left) or relative,
          // we put it at the current accumulated offset.
          child.layout[pos[mainAxis]] += mainDim;
        }

        // Now that we placed the element, we need to update the variables
        // We only need to do that for relative elements. Absolute elements
        // do not take part in that phase.
        if (getPositionType(child) === CSS_POSITION_RELATIVE) {
          // The main dimension is the sum of all the elements dimension plus
          // the spacing.
          mainDim += betweenMainDim + getDimWithMargin(child, mainAxis);
          // The cross dimension is the max of the elements dimension since there
          // can only be one element in that cross dimension.
          crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis));
        }
      }

      var/*float*/ containerMainAxis = node.layout[dim[mainAxis]];
      // If the user didn't specify a width or height, and it has not been set
      // by the container, then we set it via the children.
      if (isUndefined(node.layout[dim[mainAxis]])) {
        containerMainAxis = fmaxf(
          // We're missing the last padding at this point to get the final
          // dimension
          mainDim + getPaddingAndBorder(node, trailing[mainAxis]),
          // We can never assign a width smaller than the padding and borders
          getPaddingAndBorderAxis(node, mainAxis)
        );
      }

      var/*float*/ containerCrossAxis = node.layout[dim[crossAxis]];
      if (isUndefined(node.layout[dim[crossAxis]])) {
        containerCrossAxis = fmaxf(
          // For the cross dim, we add both sides at the end because the value
          // is aggregate via a max function. Intermediate negative values
          // can mess this computation otherwise
          crossDim + getPaddingAndBorderAxis(node, crossAxis),
          getPaddingAndBorderAxis(node, crossAxis)
        );
      }

      // <Loop D> Position elements in the cross axis

      for (var/*int*/ i = startLine; i < endLine; ++i) {
        var/*css_node_t**/ child = node.children[i];

        if (getPositionType(child) === CSS_POSITION_ABSOLUTE &&
            isPosDefined(child, leading[crossAxis])) {
          // In case the child is absolutely positionned and has a
          // top/left/bottom/right being set, we override all the previously
          // computed positions to set it correctly.
          child.layout[pos[crossAxis]] = getPosition(child, leading[crossAxis]) +
            getBorder(node, leading[crossAxis]) +
            getMargin(child, leading[crossAxis]);

        } else {
          var/*float*/ leadingCrossDim = getPaddingAndBorder(node, leading[crossAxis]);

          // For a relative children, we're either using alignItems (parent) or
          // alignSelf (child) in order to determine the position in the cross axis
          if (getPositionType(child) === CSS_POSITION_RELATIVE) {
            var/*css_align_t*/ alignItem = getAlignItem(node, child);
            if (alignItem === CSS_ALIGN_FLEX_START) {
              // Do nothing
            } else if (alignItem === CSS_ALIGN_STRETCH) {
              // You can only stretch if the dimension has not already been set
              // previously.
              if (!isDimDefined(child, crossAxis)) {
                child.layout[dim[crossAxis]] = fmaxf(
                  containerCrossAxis -
                    getPaddingAndBorderAxis(node, crossAxis) -
                    getMarginAxis(child, crossAxis),
                  // You never want to go smaller than padding
                  getPaddingAndBorderAxis(child, crossAxis)
                );
              }
            } else {
              // The remaining space between the parent dimensions+padding and child
              // dimensions+margin.
              var/*float*/ remainingCrossDim = containerCrossAxis -
                getPaddingAndBorderAxis(node, crossAxis) -
                getDimWithMargin(child, crossAxis);

              if (alignItem === CSS_ALIGN_CENTER) {
                leadingCrossDim += remainingCrossDim / 2;
              } else { // CSS_ALIGN_FLEX_END
                leadingCrossDim += remainingCrossDim;
              }
            }
          }

          // And we apply the position
          child.layout[pos[crossAxis]] += linesCrossDim + leadingCrossDim;
        }
      }

      linesCrossDim += crossDim;
      linesMainDim = fmaxf(linesMainDim, mainDim);
      startLine = endLine;
    }

    // If the user didn't specify a width or height, and it has not been set
    // by the container, then we set it via the children.
    if (isUndefined(node.layout[dim[mainAxis]])) {
      node.layout[dim[mainAxis]] = fmaxf(
        // We're missing the last padding at this point to get the final
        // dimension
        linesMainDim + getPaddingAndBorder(node, trailing[mainAxis]),
        // We can never assign a width smaller than the padding and borders
        getPaddingAndBorderAxis(node, mainAxis)
      );
    }

    if (isUndefined(node.layout[dim[crossAxis]])) {
      node.layout[dim[crossAxis]] = fmaxf(
        // For the cross dim, we add both sides at the end because the value
        // is aggregate via a max function. Intermediate negative values
        // can mess this computation otherwise
        linesCrossDim + getPaddingAndBorderAxis(node, crossAxis),
        getPaddingAndBorderAxis(node, crossAxis)
      );
    }

    // <Loop E> Calculate dimensions for absolutely positioned elements

    for (var/*int*/ i = 0; i < node.children.length; ++i) {
      var/*css_node_t**/ child = node.children[i];
      if (getPositionType(child) == CSS_POSITION_ABSOLUTE) {
        // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
        // left and right or top and bottom).
        for (var/*int*/ ii = 0; ii < 2; ii++) {
          var/*css_flex_direction_t*/ axis = (ii !== 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
          if (!isUndefined(node.layout[dim[axis]]) &&
              !isDimDefined(child, axis) &&
              isPosDefined(child, leading[axis]) &&
              isPosDefined(child, trailing[axis])) {
            child.layout[dim[axis]] = fmaxf(
              node.layout[dim[axis]] -
              getPaddingAndBorderAxis(node, axis) -
              getMarginAxis(child, axis) -
              getPosition(child, leading[axis]) -
              getPosition(child, trailing[axis]),
              // You never want to go smaller than padding
              getPaddingAndBorderAxis(child, axis)
            );
          }
        }
        for (var/*int*/ ii = 0; ii < 2; ii++) {
          var/*css_flex_direction_t*/ axis = (ii !== 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
          if (isPosDefined(child, trailing[axis]) &&
              !isPosDefined(child, leading[axis])) {
            child.layout[leading[axis]] =
              node.layout[dim[axis]] -
              child.layout[dim[axis]] -
              getPosition(child, trailing[axis]);
          }
        }
      }
    }
  };
})();

if (typeof module === 'object') {
  module.exports = computeLayout;
}


================================================
FILE: lib/ListView.js
================================================
'use strict';

var React = require('react');
var Scroller = require('scroller');
var Group = require('./Group');
var clamp = require('./clamp');

var ListView = React.createClass({

  propTypes: {
    style: React.PropTypes.object,
    numberOfItemsGetter: React.PropTypes.func.isRequired,
    itemHeightGetter: React.PropTypes.func.isRequired,
    itemGetter: React.PropTypes.func.isRequired,
    snapping: React.PropTypes.bool,
    scrollingDeceleration: React.PropTypes.number,
    scrollingPenetrationAcceleration: React.PropTypes.number,
    onScroll: React.PropTypes.func
  },

  getDefaultProps: function () {
    return {
      style: { left: 0, top: 0, width: 0, height: 0 },
      snapping: false,
      scrollingDeceleration: 0.95,
      scrollingPenetrationAcceleration: 0.08
    };
  },

  getInitialState: function () {
    return {
      scrollTop: 0
    };
  },

  componentDidMount: function () {
    this.createScroller();
    this.updateScrollingDimensions();
  },

  render: function () {
    var items = this.getVisibleItemIndexes().map(this.renderItem);
    return (
      React.createElement(Group, {
        style: this.props.style,
        onTouchStart: this.handleTouchStart,
        onTouchMove: this.handleTouchMove,
        onTouchEnd: this.handleTouchEnd,
        onTouchCancel: this.handleTouchEnd},
        items
      )
    );
  },

  renderItem: function (itemIndex) {
    var item = this.props.itemGetter(itemIndex, this.state.scrollTop);
    var itemHeight = this.props.itemHeightGetter();
    var style = {
      top: 0,
      left: 0,
      width: this.props.style.width,
      height: itemHeight,
      translateY: (itemIndex * itemHeight) - this.state.scrollTop,
      zIndex: itemIndex
    };

    return (
      React.createElement(Group, {style: style, key: itemIndex},
        item
      )
    );
  },

  // Events
  // ======

  handleTouchStart: function (e) {
    if (this.scroller) {
      this.scroller.doTouchStart(e.touches, e.timeStamp);
    }
  },

  handleTouchMove: function (e) {
    if (this.scroller) {
      e.preventDefault();
      this.scroller.doTouchMove(e.touches, e.timeStamp, e.scale);
    }
  },

  handleTouchEnd: function (e) {
    if (this.scroller) {
      this.scroller.doTouchEnd(e.timeStamp);
      if (this.props.snapping) {
        this.updateScrollingDeceleration();
      }
    }
  },

  handleScroll: function (left, top) {
    this.setState({ scrollTop: top });
    if (this.props.onScroll) {
      this.props.onScroll(top);
    }
  },

  // Scrolling
  // =========

  createScroller: function () {
    var options = {
      scrollingX: false,
      scrollingY: true,
      decelerationRate: this.props.scrollingDeceleration,
      penetrationAcceleration: this.props.scrollingPenetrationAcceleration,
    };
    this.scroller = new Scroller(this.handleScroll, options);
  },

  updateScrollingDimensions: function () {
    var width = this.props.style.width;
    var height = this.props.style.height;
    var scrollWidth = width;
    var scrollHeight = this.props.numberOfItemsGetter() * this.props.itemHeightGetter();
    this.scroller.setDimensions(width, height, scrollWidth, scrollHeight);
  },

  getVisibleItemIndexes: function () {
    var itemIndexes = [];
    var itemHeight = this.props.itemHeightGetter();
    var itemCount = this.props.numberOfItemsGetter();
    var scrollTop = this.state.scrollTop;
    var itemScrollTop = 0;

    for (var index=0; index < itemCount; index++) {
      itemScrollTop = (index * itemHeight) - scrollTop;

      // Item is completely off-screen bottom
      if (itemScrollTop >= this.props.style.height) {
        continue;
      }

      // Item is completely off-screen top
      if (itemScrollTop <= -this.props.style.height) {
        continue;
      }

      // Part of item is on-screen.
      itemIndexes.push(index);
    }

    return itemIndexes;
  },

  updateScrollingDeceleration: function () {
    var currVelocity = this.scroller.__decelerationVelocityY;
    var currScrollTop = this.state.scrollTop;
    var targetScrollTop = 0;
    var estimatedEndScrollTop = currScrollTop;

    while (Math.abs(currVelocity).toFixed(6) > 0) {
      estimatedEndScrollTop += currVelocity;
      currVelocity *= this.props.scrollingDeceleration;
    }

    // Find the page whose estimated end scrollTop is closest to 0.
    var closestZeroDelta = Infinity;
    var pageHeight = this.props.itemHeightGetter();
    var pageCount = this.props.numberOfItemsGetter();
    var pageScrollTop;

    for (var pageIndex=0, len=pageCount; pageIndex < len; pageIndex++) {
      pageScrollTop = (pageHeight * pageIndex) - estimatedEndScrollTop;
      if (Math.abs(pageScrollTop) < closestZeroDelta) {
        closestZeroDelta = Math.abs(pageScrollTop);
        targetScrollTop = pageHeight * pageIndex;
      }
    }

    this.scroller.__minDecelerationScrollTop = targetScrollTop;
    this.scroller.__maxDecelerationScrollTop = targetScrollTop;
  }

});

module.exports = ListView;


================================================
FILE: lib/ReactCanvas.js
================================================
'use strict';

var ReactCanvas = {
  Surface: require('./Surface'),

  Layer: require('./Layer'),
  Group: require('./Group'),
  Image: require('./Image'),
  Text: require('./Text'),
  ListView: require('./ListView'),
  Gradient: require('./Gradient'),

  FontFace: require('./FontFace'),
  measureText: require('./measureText')
};

module.exports = ReactCanvas;


================================================
FILE: lib/RenderLayer.js
================================================
'use strict';

var FrameUtils = require('./FrameUtils');
var DrawingUtils = require('./DrawingUtils');
var EventTypes = require('./EventTypes');

function RenderLayer () {
  this.children = [];
  this.frame = FrameUtils.zero();
}

RenderLayer.prototype = {

  /**
   * Retrieve the root injection layer
   *
   * @return {RenderLayer}
   */
  getRootLayer: function () {
    var root = this;
    while (root.parentLayer) {
      root = root.parentLayer;
    }
    return root;
  },

  /**
   * RenderLayers are injected into a root owner layer whenever a Surface is
   * mounted. This is the integration point with React internals.
   *
   * @param {RenderLayer} parentLayer
   */
  inject: function (parentLayer) {
    if (this.parentLayer && this.parentLayer !== parentLayer) {
      this.remove();
    }
    if (!this.parentLayer) {
      parentLayer.addChild(this);
    }
  },

  /**
   * Inject a layer before a reference layer
   *
   * @param {RenderLayer} parentLayer
   * @param {RenderLayer} referenceLayer
   */
  injectBefore: function (parentLayer, referenceLayer) {
    // FIXME
    this.inject(parentLayer);
  },

  /**
   * Add a child to the render layer
   *
   * @param {RenderLayer} child
   */
  addChild: function (child) {
    child.parentLayer = this;
    this.children.push(child);
  },

  /**
   * Remove a layer from it's parent layer
   */
  remove: function () {
    if (this.parentLayer) {
      this.parentLayer.children.splice(this.parentLayer.children.indexOf(this), 1);
    }
  },

  /**
   * Attach an event listener to a layer. Supported events are defined in
   * lib/EventTypes.js
   *
   * @param {String} type
   * @param {Function} callback
   * @param {?Object} callbackScope
   * @return {Function} invoke to unsubscribe the listener
   */
  subscribe: function (type, callback, callbackScope) {
    // This is the integration point with React, called from LayerMixin.putEventListener().
    // Enforce that only a single callbcak can be assigned per event type.
    for (var eventType in EventTypes) {
      if (EventTypes[eventType] === type) {
        this[eventType] = callback;
      }
    }

    // Return a function that can be called to unsubscribe from the event.
    return this.removeEventListener.bind(this, type, callback, callbackScope);
  },

  /**
   * @param {String} type
   * @param {Function} callback
   * @param {?Object} callbackScope
   */
  addEventListener: function (type, callback, callbackScope) {
    for (var eventType in EventTypes) {
      if (EventTypes[eventType] === type) {
        delete this[eventType];
      }
    }
  },

  /**
   * @param {String} type
   * @param {Function} callback
   * @param {?Object} callbackScope
   */
  removeEventListener: function (type, callback, callbackScope) {
    var listeners = this.eventListeners[type];
    var listener;
    if (listeners) {
      for (var index=0, len=listeners.length; index < len; index++) {
        listener = listeners[index];
        if (listener.callback === callback &&
            listener.callbackScope === callbackScope) {
          listeners.splice(index, 1);
          break;
        }
      }
    }
  },

  /**
   * Translate a layer's frame
   *
   * @param {Number} x
   * @param {Number} y
   */
  translate: function (x, y) {
    if (this.frame) {
      this.frame.x += x;
      this.frame.y += y;
    }

    if (this.clipRect) {
      this.clipRect.x += x;
      this.clipRect.y += y;
    }

    if (this.children) {
      this.children.forEach(function (child) {
        child.translate(x, y);
      });
    }
  },

  /**
   * Layers should call this method when they need to be redrawn. Note the
   * difference here between `invalidateBackingStore`: updates that don't
   * trigger layout should prefer `invalidateLayout`. For instance, an image
   * component that is animating alpha level after the image loads would
   * call `invalidateBackingStore` once after the image loads, and at each
   * step in the animation would then call `invalidateRect`.
   *
   * @param {?Frame} frame Optional, if not passed the entire layer's frame
   *   will be invalidated.
   */
  invalidateLayout: function () {
    // Bubble all the way to the root layer.
    this.getRootLayer().draw();
  },

  /**
   * Layers should call this method when their backing <canvas> needs to be
   * redrawn. For instance, an image component would call this once after the
   * image loads.
   */
  invalidateBackingStore: function () {
    if (this.backingStoreId) {
      DrawingUtils.invalidateBackingStore(this.backingStoreId);
    }
    this.invalidateLayout();
  },

  /**
   * Only the root owning layer should implement this function.
   */
  draw: function () {
    // Placeholer
  }

};

module.exports = RenderLayer;


================================================
FILE: lib/Surface.js
================================================
'use strict';

var React = require('react');
var ReactUpdates = require('react-dom/lib/ReactUpdates');
var invariant = require('fbjs/lib/invariant');
var ContainerMixin = require('./ContainerMixin');
var RenderLayer = require('./RenderLayer');
var FrameUtils = require('./FrameUtils');
var DrawingUtils = require('./DrawingUtils');
var hitTest = require('./hitTest');
var layoutNode = require('./layoutNode');

/**
 * Surface is a standard React component and acts as the main drawing canvas.
 * ReactCanvas components cannot be rendered outside a Surface.
 */

var Surface = React.createClass({

  mixins: [ContainerMixin],

  propTypes: {
    className: React.PropTypes.string,
    id: React.PropTypes.string,
    top: React.PropTypes.number.isRequired,
    left: React.PropTypes.number.isRequired,
    width: React.PropTypes.number.isRequired,
    height: React.PropTypes.number.isRequired,
    scale: React.PropTypes.number.isRequired,
    enableCSSLayout: React.PropTypes.bool
  },

  getDefaultProps: function () {
    return {
      scale: window.devicePixelRatio || 1
    };
  },

  componentDidMount: function () {
    // Prepare the <canvas> for drawing.
    this.scale();

    // ContainerMixin expects `this.node` to be set prior to mounting children.
    // `this.node` is injected into child components and represents the current
    // render tree.
    this.node = new RenderLayer();
    this.node.frame = FrameUtils.make(this.props.left, this.props.top, this.props.width, this.props.height);
    this.node.draw = this.batchedTick;

    // This is the integration point between custom canvas components and React
    var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
    transaction.perform(
      this.mountAndInjectChildrenAtRoot,
      this,
      this.props.children,
      transaction
    );
    ReactUpdates.ReactReconcileTransaction.release(transaction);

    // Execute initial draw on mount.
    this.node.draw();
  },

  componentWillUnmount: function () {
    // Implemented in ReactMultiChild.Mixin
    this.unmountChildren();
  },

  componentDidUpdate: function (prevProps, prevState) {
    // We have to manually apply child reconciliation since child are not
    // declared in render().
    var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
    transaction.perform(
      this.updateChildrenAtRoot,
      this,
      this.props.children,
      transaction
    );
    ReactUpdates.ReactReconcileTransaction.release(transaction);

    // Re-scale the <canvas> when changing size.
    if (prevProps.width !== this.props.width || prevProps.height !== this.props.height) {
      this.scale();
    }

    // Redraw updated render tree to <canvas>.
    if (this.node) {
      this.node.draw();
    }
  },

  render: function () {
    // Scale the drawing area to match DPI.
    var width = this.props.width * this.props.scale;
    var height = this.props.height * this.props.scale;
    var style = {
      width: this.props.width,
      height: this.props.height
    };

    return (
      React.createElement('canvas', {
        ref: 'canvas',
        className: this.props.className,
        id: this.props.id,
        width: width,
        height: height,
        style: style,
        onTouchStart: this.handleTouchStart,
        onTouchMove: this.handleTouchMove,
        onTouchEnd: this.handleTouchEnd,
        onTouchCancel: this.handleTouchEnd,
        onClick: this.handleClick,
        onContextMenu: this.handleContextMenu,
        onDoubleClick: this.handleDoubleClick})
    );
  },

  // Drawing
  // =======

  getContext: function () {
    ('production' !== process.env.NODE_ENV ? invariant(
      this.isMounted(),
      'Tried to access drawing context on an unmounted Surface.'
    ) : invariant(this.isMounted()));
    return this.refs.canvas.getContext('2d');
  },

  scale: function () {
    this.getContext().scale(this.props.scale, this.props.scale);
  },

  batchedTick: function () {
    if (this._frameReady === false) {
      this._pendingTick = true;
      return;
    }
    this.tick();
  },

  tick: function () {
    // Block updates until next animation frame.
    this._frameReady = false;
    this.clear();
    this.draw();
    requestAnimationFrame(this.afterTick);
  },

  afterTick: function () {
    // Execute pending draw that may have been scheduled during previous frame
    this._frameReady = true;
    if (this._pendingTick) {
      this._pendingTick = false;
      this.batchedTick();
    }
  },

  clear: function () {
    this.getContext().clearRect(0, 0, this.props.width, this.props.height);
  },

  draw: function () {
    var layout;
    if (this.node) {
      if (this.props.enableCSSLayout) {
        layout = layoutNode(this.node);
      }
      DrawingUtils.drawRenderLayer(this.getContext(), this.node);
    }
  },

  // Events
  // ======

  hitTest: function (e) {
    var hitTarget = hitTest(e, this.node, this.refs.canvas);
    if (hitTarget) {
      hitTarget[hitTest.getHitHandle(e.type)](e);
    }
  },

  handleTouchStart: function (e) {
    var hitTarget = hitTest(e, this.node, this.refs.canvas);
    var touch;
    if (hitTarget) {
      // On touchstart: capture the current hit target for the given touch.
      this._touches = this._touches || {};
      for (var i=0, len=e.touches.length; i < len; i++) {
        touch = e.touches[i];
        this._touches[touch.identifier] = hitTarget;
      }
      hitTarget[hitTest.getHitHandle(e.type)](e);
    }
  },

  handleTouchMove: function (e) {
    this.hitTest(e);
  },

  handleTouchEnd: function (e) {
    // touchend events do not generate a pageX/pageY so we rely
    // on the currently captured touch targets.
    if (!this._touches) {
      return;
    }

    var hitTarget;
    var hitHandle = hitTest.getHitHandle(e.type);
    for (var i=0, len=e.changedTouches.length; i < len; i++) {
      hitTarget = this._touches[e.changedTouches[i].identifier];
      if (hitTarget && hitTarget[hitHandle]) {
        hitTarget[hitHandle](e);
      }
      delete this._touches[e.changedTouches[i].identifier];
    }
  },

  handleClick: function (e) {
    this.hitTest(e);
  },

  handleContextMenu: function (e) {
    this.hitTest(e);
  },

  handleDoubleClick: function (e) {
    this.hitTest(e);
  },

});

module.exports = Surface;


================================================
FILE: lib/Text.js
================================================
'use strict';

var createComponent = require('./createComponent');
var LayerMixin = require('./LayerMixin');

var Text = createComponent('Text', LayerMixin, {

  applyTextProps: function (prevProps, props) {
    var style = (props && props.style) ? props.style : {};
    var layer = this.node;

    layer.type = 'text';
    layer.text = childrenAsString(props.children);

    layer.color = style.color;
    layer.fontFace = style.fontFace;
    layer.fontSize = style.fontSize;
    layer.lineHeight = style.lineHeight;
    layer.textAlign = style.textAlign;
  },

  mountComponent: function (
    transaction,
    nativeParent,
    nativeContainerInfo,
    context
  ) {
    var props = this._currentElement.props;
    var layer = this.node;
    this.applyLayerProps({}, props);
    this.applyTextProps({}, props);
    return layer;
  },

  receiveComponent: function (nextComponent, transaction, context) {
    var props = nextComponent.props;
    var prevProps = this._currentElement.props;
    this.applyLayerProps(prevProps, props);
    this.applyTextProps(prevProps, props);
    this._currentElement = nextComponent;
    this.node.invalidateLayout();
  }

});

function childrenAsString(children) {
  if (!children) {
    return '';
  }
  if (typeof children === 'string') {
    return children;
  }
  if (children.length) {
    return children.join('\n');
  }
  return '';
}

module.exports = Text;

================================================
FILE: lib/__tests__/clamp-test.js
================================================
jest.dontMock('../clamp.js');

var clamp = require('../clamp');

describe('clamp', function() {
  it('returns the min if n is less than min', function() {
    expect(clamp(-1, 0, 1)).toBe(0);
  });

  it('returns the max if n is greater than max', function() {
    expect(clamp(2, 0, 1)).toBe(1);
  });

  it('returns n if n is between min and max', function() {
    expect(clamp(0.5, 0, 1)).toBe(0.5);
  });
});


================================================
FILE: lib/clamp.js
================================================
'use strict';

/**
 * Clamp a number between a minimum and maximum value.
 * @param {Number} number
 * @param {Number} min
 * @param {Number} max
 * @return {Number}
*/
module.exports = function (number, min, max) {
  return Math.min(Math.max(number, min), max);
};


================================================
FILE: lib/createComponent.js
================================================
'use strict';

// Adapted from ReactART:
// https://github.com/reactjs/react-art

var RenderLayer = require('./RenderLayer');

function createComponent (name) {
  var ReactCanvasComponent = function (element) {
    this.node = null;
    this.subscriptions = null;
    this.listeners = null;
    this.node = new RenderLayer();
    this._mountImage = null;
    this._currentElement = element;
    this._renderedChildren = null;
    this._mostRecentlyPlacedChild = null;
  };
  ReactCanvasComponent.displayName = name;
  for (var i = 1, l = arguments.length; i < l; i++) {
    Object.assign(ReactCanvasComponent.prototype, arguments[i]);
  }

  return ReactCanvasComponent;
}

module.exports = createComponent;


================================================
FILE: lib/hitTest.js
================================================
'use strict';

var FrameUtils = require('./FrameUtils');
var EventTypes = require('./EventTypes');

/**
 * RenderLayer hit testing
 *
 * @param {Event} e
 * @param {RenderLayer} rootLayer
 * @param {?HTMLElement} rootNode
 * @return {RenderLayer}
 */
function hitTest (e, rootLayer, rootNode) {
  var touch = e.touches ? e.touches[0] : e;
  var touchX = touch.pageX;
  var touchY = touch.pageY;
  var rootNodeBox;
  if (rootNode) {
    rootNodeBox = rootNode.getBoundingClientRect();
    touchX -= rootNodeBox.left;
    touchY -= rootNodeBox.top;
  }

  touchY = touchY - window.pageYOffset;
  touchX = touchX - window.pageXOffset;
  return getLayerAtPoint(
    rootLayer,
    e.type,
    FrameUtils.make(touchX, touchY, 1, 1),
    rootLayer.translateX || 0,
    rootLayer.translateY || 0
  );
}

/**
 * @private
 */
function sortByZIndexDescending (layer, otherLayer) {
  return (otherLayer.zIndex || 0) - (layer.zIndex || 0);
}

/**
 * @private
 */
function getHitHandle (type) {
  var hitHandle;
  for (var tryHandle in EventTypes) {
    if (EventTypes[tryHandle] === type) {
      hitHandle = tryHandle;
      break;
    }
  }
  return hitHandle;
}

/**
 * @private
 */
function getLayerAtPoint (root, type, point, tx, ty) {
  var layer = null;
  var hitHandle = getHitHandle(type);
  var sortedChildren;
  var hitFrame = FrameUtils.clone(root.frame);

  // Early bail for non-visible layers
  if (typeof root.alpha === 'number' && root.alpha < 0.01) {
    return null;
  }

  // Child-first search
  if (root.children) {
    sortedChildren = root.children.slice().reverse().sort(sortByZIndexDescending);
    for (var i=0, len=sortedChildren.length; i < len; i++) {
      layer = getLayerAtPoint(
        sortedChildren[i],
        type,
        point,
        tx + (root.translateX || 0),
        ty + (root.translateY || 0)
      );
      if (layer) {
        break;
      }
    }
  }

  // Check for hit outsets
  if (root.hitOutsets) {
    hitFrame = FrameUtils.inset(FrameUtils.clone(hitFrame),
      -root.hitOutsets[0], -root.hitOutsets[1],
      -root.hitOutsets[2], -root.hitOutsets[3]
    );
  }

  // Check for x/y translation
  if (tx) {
    hitFrame.x += tx;
  }

  if (ty) {
    hitFrame.y += ty;
  }

  // No child layer at the given point. Try the parent layer.
  if (!layer && root[hitHandle] && FrameUtils.intersects(hitFrame, point)) {
    layer = root;
  }

  return layer;
}

module.exports = hitTest;
module.exports.getHitHandle = getHitHandle;



================================================
FILE: lib/layoutNode.js
================================================
'use strict';

var computeLayout = require('./Layout');

/**
 * This computes the CSS layout for a RenderLayer tree and mutates the frame
 * objects at each node.
 *
 * @param {Renderlayer} root
 * @return {Object}
 */
function layoutNode (root) {
  var rootNode = createNode(root);
  computeLayout(rootNode);
  walkNode(rootNode);
  return rootNode;
}

function createNode (layer) {
  return {
    layer: layer,
    layout: {
      width: undefined, // computeLayout will mutate
      height: undefined, // computeLayout will mutate
      top: 0,
      left: 0,
    },
    style: layer._originalStyle || {},
    children: (layer.children || []).map(createNode)
  };
}

function walkNode (node, parentLeft, parentTop) {
  node.layer.frame.x = node.layout.left + (parentLeft || 0);
  node.layer.frame.y = node.layout.top + (parentTop || 0);
  node.layer.frame.width = node.layout.width;
  node.layer.frame.height = node.layout.height;
  if (node.children && node.children.length > 0) {
    node.children.forEach(function (child) {
      walkNode(child, node.layer.frame.x, node.layer.frame.y);
    });
  }
}

module.exports = layoutNode;


================================================
FILE: lib/measureText.js
================================================
'use strict';

var FontFace = require('./FontFace');
var FontUtils = require('./FontUtils');
var LineBreaker = require('linebreak');

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');

var _cache = {};
var _zeroMetrics = {
  width: 0,
  height: 0,
  lines: []
};

function getCacheKey (text, width, fontFace, fontSize, lineHeight) {
  return text + width + fontFace.id + fontSize + lineHeight;
}

/**
 * Given a string of text, available width, and font return the measured width
 * and height.
 * @param {String} text The input string
 * @param {Number} width The available width
 * @param {FontFace} fontFace The FontFace to use
 * @param {Number} fontSize The font size in CSS pixels
 * @param {Number} lineHeight The line height in CSS pixels
 * @return {Object} Measured text size with `width` and `height` members.
 */
module.exports = function measureText (text, width, fontFace, fontSize, lineHeight) {
  var cacheKey = getCacheKey(text, width, fontFace, fontSize, lineHeight);
  var cached = _cache[cacheKey];
  if (cached) {
    return cached;
  }

  // Bail and return zero unless we're sure the font is ready.
  if (!FontUtils.isFontLoaded(fontFace)) {
    return _zeroMetrics;
  }

  var measuredSize = {};
  var textMetrics;
  var lastMeasuredWidth;
  var words;
  var tryLine;
  var currentLine;
  var breaker;
  var bk;
  var lastBreak;

  ctx.font = fontFace.attributes.style + ' ' + fontFace.attributes.weight + ' ' + fontSize + 'px ' + fontFace.family;
  textMetrics = ctx.measureText(text);

  measuredSize.width = textMetrics.width;
  measuredSize.height = lineHeight;
  measuredSize.lines = [];

  if (measuredSize.width <= width) {
    // The entire text string fits.
    measuredSize.lines.push({width: measuredSize.width, text: text});
  } else {
    // Break into multiple lines.
    measuredSize.width = width;
    currentLine = '';
    breaker = new LineBreaker(text);
    
    while (bk = breaker.nextBreak()) {
      var word = text.slice(lastBreak ? lastBreak.position : 0, bk.position);
      
      tryLine = currentLine + word;
      textMetrics = ctx.measureText(tryLine);
      if (textMetrics.width > width || (lastBreak && lastBreak.required)) {
        measuredSize.height += lineHeight;
        measuredSize.lines.push({width: lastMeasuredWidth, text: currentLine.trim()});
        currentLine = word;
        lastMeasuredWidth = ctx.measureText(currentLine.trim()).width;
      } else {
        currentLine = tryLine;
        lastMeasuredWidth = textMetrics.width;
      }
      
      lastBreak = bk;
    }
    
    currentLine = currentLine.trim();
    if (currentLine.length > 0) {
      textMetrics = ctx.measureText(currentLine);
      measuredSize.lines.push({width: textMetrics, text: currentLine});
    }
  }

  _cache[cacheKey] = measuredSize;

  return measuredSize;
};


================================================
FILE: package.json
================================================
{
  "name": "react-canvas",
  "version": "1.3.0",
  "description": "High performance <canvas> rendering for React components",
  "main": "lib/ReactCanvas.js",
  "repository": {
    "type": "git",
    "url": "https://github.com/Flipboard/react-canvas.git"
  },
  "scripts": {
    "start": "./node_modules/.bin/gulp",
    "test": "./node_modules/.bin/jest"
  },
  "keywords": [
    "react",
    "canvas"
  ],
  "author": "Michael Johnston <mj@flipboard.com>",
  "license": "BSD-3-Clause",
  "homepage": "https://github.com/Flipboard/react-canvas",
  "bugs": {
    "url": "https://github.com/Flipboard/react-canvas/issues"
  },
  "devDependencies": {
    "babel-core": "^6.22.1",
    "babel-loader": "^6.2.10",
    "babel-preset-react": "^6.22.0",
    "brfs": "^1.4.3",
    "del": "^2.2.2",
    "envify": "^4.0.0",
    "gulp": "^3.9.1",
    "gulp-connect": "^5.0.0",
    "jest": "^18.1.0",
    "react": "^15.0.0",
    "react-dom": "^15.0.0",
    "transform-loader": "^0.2.3",
    "webpack": "^1.14.0",
    "webpack-stream": "^3.2.0"
  },
  "peerDependencies": {
    "react": "^15.0.0"
  },
  "dependencies": {
    "fbjs": "^0.8.8",
    "linebreak": "^0.3.0",
    "scroller": "git://github.com/mjohnston/scroller"
  }
}


================================================
FILE: webpack.config.js
================================================
module.exports = {
  cache: true,

  watch: true,

  entry: {
    'listview': ['./examples/listview/app.js'],
    'timeline': ['./examples/timeline/app.js'],
    'gradient': ['./examples/gradient/app.js'],
    'css-layout': ['./examples/css-layout/app.js']
  },

  output: {
    filename: '[name].js'
  },

  module: {
    loaders: [
      { test: /\.js$/, loader: 'babel-loader!transform/cacheable?envify' },
    ],
    postLoaders: [
      { loader: "transform?brfs" }
    ]
  },
  devtool: ['source-map'],
  resolve: {
    root: __dirname,
    alias: {
      'react-canvas': 'lib/ReactCanvas.js'
    }
  }
};
Download .txt
gitextract_70rohpyf/

├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── examples/
│   ├── common/
│   │   ├── data.js
│   │   ├── examples.css
│   │   └── touch-emulator.js
│   ├── css-layout/
│   │   ├── app.js
│   │   └── index.html
│   ├── gradient/
│   │   ├── app.js
│   │   └── index.html
│   ├── listview/
│   │   ├── app.js
│   │   ├── components/
│   │   │   └── Item.js
│   │   └── index.html
│   └── timeline/
│       ├── app.js
│       ├── components/
│       │   └── Page.js
│       └── index.html
├── gulpfile.js
├── lib/
│   ├── Canvas.js
│   ├── CanvasUtils.js
│   ├── ContainerMixin.js
│   ├── DrawingUtils.js
│   ├── Easing.js
│   ├── EventTypes.js
│   ├── FontFace.js
│   ├── FontUtils.js
│   ├── FrameUtils.js
│   ├── Gradient.js
│   ├── Group.js
│   ├── Image.js
│   ├── ImageCache.js
│   ├── Layer.js
│   ├── LayerMixin.js
│   ├── Layout.js
│   ├── ListView.js
│   ├── ReactCanvas.js
│   ├── RenderLayer.js
│   ├── Surface.js
│   ├── Text.js
│   ├── __tests__/
│   │   └── clamp-test.js
│   ├── clamp.js
│   ├── createComponent.js
│   ├── hitTest.js
│   ├── layoutNode.js
│   └── measureText.js
├── package.json
└── webpack.config.js
Download .txt
SYMBOL INDEX (83 symbols across 15 files)

FILE: examples/common/touch-emulator.js
  function Touch (line 52) | function Touch(target, identifier, pos, deltaX, deltaY) {
  function TouchList (line 71) | function TouchList() {
  function fakeTouchSupport (line 91) | function fakeTouchSupport() {
  function hasTouchSupport (line 108) | function hasTouchSupport() {
  function preventMouseEvents (line 118) | function preventMouseEvents(ev) {
  function onMouse (line 128) | function onMouse(touchType) {
  function triggerTouch (line 180) | function triggerTouch(eventName, mouseEv) {
  function createTouchList (line 201) | function createTouchList(mouseEv) {
  function getActiveTouches (line 223) | function getActiveTouches(mouseEv, eventName) {
  function getChangedTouches (line 242) | function getChangedTouches(mouseEv, eventName) {
  function showTouches (line 261) | function showTouches(ev) {
  function TouchEmulator (line 295) | function TouchEmulator() {

FILE: lib/Canvas.js
  function Canvas (line 6) | function Canvas (width, height, scale) {

FILE: lib/CanvasUtils.js
  function drawImage (line 23) | function drawImage (ctx, image, x, y, width, height, options) {
  function drawText (line 109) | function drawText (ctx, text, x, y, width, height, fontFace, options) {
  function drawGradient (line 185) | function drawGradient(ctx, x1, y1, x2, y2, colorStops, x, y, width, heig...

FILE: lib/DrawingUtils.js
  function getBackingStore (line 20) | function getBackingStore (id) {
  function invalidateBackingStore (line 34) | function invalidateBackingStore (id) {
  function invalidateAllBackingStores (line 46) | function invalidateAllBackingStores () {
  function getBackingStoreAncestor (line 55) | function getBackingStoreAncestor (layer) {
  function layerContainsImage (line 72) | function layerContainsImage (layer, imageUrl) {
  function layerContainsFontFace (line 97) | function layerContainsFontFace (layer, fontFace) {
  function handleImageLoad (line 121) | function handleImageLoad (imageUrl) {
  function handleFontLoad (line 135) | function handleFontLoad (fontFace) {
  function drawRenderLayer (line 149) | function drawRenderLayer (ctx, layer) {
  function drawBaseRenderLayer (line 225) | function drawBaseRenderLayer (ctx, layer) {
  function drawCacheableRenderLayer (line 286) | function drawCacheableRenderLayer (ctx, layer, customDrawFunc) {
  function sortByZIndexAscending (line 363) | function sortByZIndexAscending (layerA, layerB) {
  function drawImageRenderLayer (line 370) | function drawImageRenderLayer (ctx, layer) {
  function drawTextRenderLayer (line 387) | function drawTextRenderLayer (ctx, layer) {
  function drawGradientRenderLayer (line 407) | function drawGradientRenderLayer (ctx, layer) {

FILE: lib/FontFace.js
  function FontFace (line 11) | function FontFace (family, url, attributes) {
  function getCacheKey (line 47) | function getCacheKey (family, url, attributes) {

FILE: lib/FontUtils.js
  function isFontLoaded (line 17) | function isFontLoaded (fontFace) {
  function loadFont (line 27) | function loadFont (fontFace, callback) {
  function loadFontNative (line 95) | function loadFontNative (fontFace, callback) {
  function createTestNode (line 143) | function createTestNode (family, attributes) {
  function handleFontLoad (line 156) | function handleFontLoad (fontFace, timeout) {

FILE: lib/FrameUtils.js
  function Frame (line 3) | function Frame (x, y, width, height) {
  function make (line 19) | function make (x, y, width, height) {
  function zero (line 28) | function zero () {
  function clone (line 38) | function clone (frame) {
  function inset (line 53) | function inset (frame, top, right, bottom, left) {
  function intersection (line 82) | function intersection (frame, otherFrame) {
  function union (line 100) | function union (frame, otherFrame) {
  function intersects (line 115) | function intersects (frame, otherFrame) {

FILE: lib/ImageCache.js
  function Img (line 7) | function Img (src) {

FILE: lib/Layout.js
  function capitalizeFirst (line 14) | function capitalizeFirst(str) {
  function getSpacing (line 18) | function getSpacing(node, type, suffix, location) {
  function getPositiveSpacing (line 32) | function getPositiveSpacing(node, type, suffix, location) {
  function isUndefined (line 46) | function isUndefined(value) {
  function getMargin (line 50) | function getMargin(node, location) {
  function getPadding (line 54) | function getPadding(node, location) {
  function getBorder (line 58) | function getBorder(node, location) {
  function getPaddingAndBorder (line 62) | function getPaddingAndBorder(node, location) {
  function getMarginAxis (line 66) | function getMarginAxis(node, axis) {
  function getPaddingAndBorderAxis (line 70) | function getPaddingAndBorderAxis(node, axis) {
  function getJustifyContent (line 74) | function getJustifyContent(node) {
  function getAlignItem (line 81) | function getAlignItem(node, child) {
  function getFlexDirection (line 91) | function getFlexDirection(node) {
  function getPositionType (line 98) | function getPositionType(node) {
  function getFlex (line 105) | function getFlex(node) {
  function isFlex (line 109) | function isFlex(node) {
  function isFlexWrap (line 116) | function isFlexWrap(node) {
  function getDimWithMargin (line 120) | function getDimWithMargin(node, axis) {
  function isDimDefined (line 124) | function isDimDefined(node, axis) {
  function isPosDefined (line 128) | function isPosDefined(node, pos) {
  function isMeasureDefined (line 132) | function isMeasureDefined(node) {
  function getPosition (line 136) | function getPosition(node, pos) {
  function setDimensionFromStyle (line 144) | function setDimensionFromStyle(node, axis) {
  function getRelativePosition (line 163) | function getRelativePosition(node, axis) {
  function fmaxf (line 187) | function fmaxf(a, b) {

FILE: lib/RenderLayer.js
  function RenderLayer (line 7) | function RenderLayer () {

FILE: lib/Text.js
  function childrenAsString (line 46) | function childrenAsString(children) {

FILE: lib/createComponent.js
  function createComponent (line 8) | function createComponent (name) {

FILE: lib/hitTest.js
  function hitTest (line 14) | function hitTest (e, rootLayer, rootNode) {
  function sortByZIndexDescending (line 39) | function sortByZIndexDescending (layer, otherLayer) {
  function getHitHandle (line 46) | function getHitHandle (type) {
  function getLayerAtPoint (line 60) | function getLayerAtPoint (root, type, point, tx, ty) {

FILE: lib/layoutNode.js
  function layoutNode (line 12) | function layoutNode (root) {
  function createNode (line 19) | function createNode (layer) {
  function walkNode (line 33) | function walkNode (node, parentLeft, parentTop) {

FILE: lib/measureText.js
  function getCacheKey (line 17) | function getCacheKey (text, width, fontFace, fontSize, lineHeight) {
Condensed preview — 47 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (148K chars).
[
  {
    "path": ".babelrc",
    "chars": 26,
    "preview": "{\n  \"presets\": [\"react\"]\n}"
  },
  {
    "path": ".gitignore",
    "chars": 42,
    "preview": "build\nnode_modules\nyarn.lock\nnpm-debug.log"
  },
  {
    "path": "LICENSE",
    "chars": 1475,
    "preview": "Copyright (c) 2015, Flipboard\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without m"
  },
  {
    "path": "README.md",
    "chars": 8412,
    "preview": "# react-canvas\n\n[Introductory blog post](http://engineering.flipboard.com/2015/02/mobile-web)\n\nReact Canvas adds the abi"
  },
  {
    "path": "examples/common/data.js",
    "chars": 7290,
    "preview": "module.exports = [\n  {\n    title: '10 Unbelievable Secrets That Will Make Your Airline Pilot Nervous',\n    excerpt: 'Wit"
  },
  {
    "path": "examples/common/examples.css",
    "chars": 244,
    "preview": "html, body {\n  margin: 0;\n  padding: 0;\n  font: 16px Helvetica, sans-serif;\n  height: 100%;\n  overflow: hidden;\n  backgr"
  },
  {
    "path": "examples/common/touch-emulator.js",
    "chars": 11296,
    "preview": "// https://github.com/hammerjs/touchemulator\n\n(function(window, document, exportName, undefined) {\n    \"use strict\";\n\n  "
  },
  {
    "path": "examples/css-layout/app.js",
    "chars": 2881,
    "preview": "var React = require('react');\nvar ReactDOM = require('react-dom');\nvar ReactCanvas = require('react-canvas');\n\nvar Surfa"
  },
  {
    "path": "examples/css-layout/index.html",
    "chars": 540,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <meta name=\"viewpo"
  },
  {
    "path": "examples/gradient/app.js",
    "chars": 982,
    "preview": "'use strict';\n\nvar React = require('react');\nvar ReactDOM = require('react-dom');\nvar ReactCanvas = require('react-canva"
  },
  {
    "path": "examples/gradient/index.html",
    "chars": 536,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <meta name=\"viewpo"
  },
  {
    "path": "examples/listview/app.js",
    "chars": 1410,
    "preview": "'use strict';\n\nvar React = require('react');\nvar ReactDOM = require('react-dom');\nvar ReactCanvas = require('react-canva"
  },
  {
    "path": "examples/listview/components/Item.js",
    "chars": 1394,
    "preview": "'use strict';\n\nvar React = require('react');\nvar ReactCanvas = require('react-canvas');\n\nvar Group = ReactCanvas.Group;\n"
  },
  {
    "path": "examples/listview/index.html",
    "chars": 536,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <meta name=\"viewpo"
  },
  {
    "path": "examples/timeline/app.js",
    "chars": 1686,
    "preview": "'use strict';\n\nvar React = require('react');\nvar ReactDOM = require('react-dom');\nvar ReactCanvas = require('react-canva"
  },
  {
    "path": "examples/timeline/components/Page.js",
    "chars": 3815,
    "preview": "'use strict';\n\nvar React = require('react');\nvar ReactCanvas = require('react-canvas');\n\nvar Group = ReactCanvas.Group;\n"
  },
  {
    "path": "examples/timeline/index.html",
    "chars": 536,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <meta name=\"viewpo"
  },
  {
    "path": "gulpfile.js",
    "chars": 863,
    "preview": "var gulp = require('gulp');\nvar del = require('del');\nvar connect = require('gulp-connect');\nvar webpack = require('webp"
  },
  {
    "path": "lib/Canvas.js",
    "chars": 925,
    "preview": "'use strict';\n\n// Note that this class intentionally does not use PooledClass.\n// DrawingUtils manages <canvas> pooling "
  },
  {
    "path": "lib/CanvasUtils.js",
    "chars": 5750,
    "preview": "'use strict';\n\nvar FontFace = require('./FontFace');\nvar clamp = require('./clamp');\nvar measureText = require('./measur"
  },
  {
    "path": "lib/ContainerMixin.js",
    "chars": 3808,
    "preview": "'use strict';\n\n// Adapted from ReactART:\n// https://github.com/reactjs/react-art\n\nvar React = require('react');\nvar Reac"
  },
  {
    "path": "lib/DrawingUtils.js",
    "chars": 11992,
    "preview": "'use strict';\n\nvar ImageCache = require('./ImageCache');\nvar FontUtils = require('./FontUtils');\nvar FontFace = require("
  },
  {
    "path": "lib/Easing.js",
    "chars": 603,
    "preview": "// Penner easing equations\n// https://gist.github.com/gre/1650294\n\nvar Easing = {\n\n  linear: function (t) {\n    return t"
  },
  {
    "path": "lib/EventTypes.js",
    "chars": 291,
    "preview": "'use strict';\n\n// Supported events that RenderLayer's can subscribe to.\n\nmodule.exports = {\n  onTouchStart: 'touchstart'"
  },
  {
    "path": "lib/FontFace.js",
    "chars": 1191,
    "preview": "'use strict';\n\nvar _fontFaces = {};\n\n/**\n * @param {String} family The CSS font-family value\n * @param {String} url The "
  },
  {
    "path": "lib/FontUtils.js",
    "chars": 4714,
    "preview": "'use strict';\n\nvar FontFace = require('./FontFace');\n\nvar _useNativeImpl = (typeof window.FontFace !== 'undefined');\nvar"
  },
  {
    "path": "lib/FrameUtils.js",
    "chars": 2945,
    "preview": "'use strict';\n\nfunction Frame (x, y, width, height) {\n  this.x = x;\n  this.y = y;\n  this.width = width;\n  this.height = "
  },
  {
    "path": "lib/Gradient.js",
    "chars": 954,
    "preview": "'use strict';\n\nvar React = require('react');\nvar createComponent = require('./createComponent');\nvar LayerMixin = requir"
  },
  {
    "path": "lib/Group.js",
    "chars": 1067,
    "preview": "'use strict';\n\nvar createComponent = require('./createComponent');\nvar ContainerMixin = require('./ContainerMixin');\nvar"
  },
  {
    "path": "lib/Image.js",
    "chars": 4079,
    "preview": "'use strict';\n\nvar React = require('react');\nvar createComponent = require('./createComponent');\nvar LayerMixin = requir"
  },
  {
    "path": "lib/ImageCache.js",
    "chars": 3228,
    "preview": "'use strict';\n\nvar EventEmitter = require('events');\n\nvar NOOP = function () {};\n\nfunction Img (src) {\n  this._originalS"
  },
  {
    "path": "lib/Layer.js",
    "chars": 709,
    "preview": "'use strict';\n\nvar createComponent = require('./createComponent');\nvar LayerMixin = require('./LayerMixin');\n\nvar Layer "
  },
  {
    "path": "lib/LayerMixin.js",
    "chars": 2565,
    "preview": "'use strict';\n\n// Adapted from ReactART:\n// https://github.com/reactjs/react-art\n\nvar FrameUtils = require('./FrameUtils"
  },
  {
    "path": "lib/Layout.js",
    "chars": 24896,
    "preview": "// https://github.com/facebook/css-layout\n\n/**\n * Copyright (c) 2014, Facebook, Inc.\n * All rights reserved.\n *\n * This "
  },
  {
    "path": "lib/ListView.js",
    "chars": 4997,
    "preview": "'use strict';\n\nvar React = require('react');\nvar Scroller = require('scroller');\nvar Group = require('./Group');\nvar cla"
  },
  {
    "path": "lib/ReactCanvas.js",
    "chars": 363,
    "preview": "'use strict';\n\nvar ReactCanvas = {\n  Surface: require('./Surface'),\n\n  Layer: require('./Layer'),\n  Group: require('./Gr"
  },
  {
    "path": "lib/RenderLayer.js",
    "chars": 4763,
    "preview": "'use strict';\n\nvar FrameUtils = require('./FrameUtils');\nvar DrawingUtils = require('./DrawingUtils');\nvar EventTypes = "
  },
  {
    "path": "lib/Surface.js",
    "chars": 6319,
    "preview": "'use strict';\n\nvar React = require('react');\nvar ReactUpdates = require('react-dom/lib/ReactUpdates');\nvar invariant = r"
  },
  {
    "path": "lib/Text.js",
    "chars": 1403,
    "preview": "'use strict';\n\nvar createComponent = require('./createComponent');\nvar LayerMixin = require('./LayerMixin');\n\nvar Text ="
  },
  {
    "path": "lib/__tests__/clamp-test.js",
    "chars": 413,
    "preview": "jest.dontMock('../clamp.js');\n\nvar clamp = require('../clamp');\n\ndescribe('clamp', function() {\n  it('returns the min if"
  },
  {
    "path": "lib/clamp.js",
    "chars": 266,
    "preview": "'use strict';\n\n/**\n * Clamp a number between a minimum and maximum value.\n * @param {Number} number\n * @param {Number} m"
  },
  {
    "path": "lib/createComponent.js",
    "chars": 708,
    "preview": "'use strict';\n\n// Adapted from ReactART:\n// https://github.com/reactjs/react-art\n\nvar RenderLayer = require('./RenderLay"
  },
  {
    "path": "lib/hitTest.js",
    "chars": 2472,
    "preview": "'use strict';\n\nvar FrameUtils = require('./FrameUtils');\nvar EventTypes = require('./EventTypes');\n\n/**\n * RenderLayer h"
  },
  {
    "path": "lib/layoutNode.js",
    "chars": 1137,
    "preview": "'use strict';\n\nvar computeLayout = require('./Layout');\n\n/**\n * This computes the CSS layout for a RenderLayer tree and "
  },
  {
    "path": "lib/measureText.js",
    "chars": 2853,
    "preview": "'use strict';\n\nvar FontFace = require('./FontFace');\nvar FontUtils = require('./FontUtils');\nvar LineBreaker = require('"
  },
  {
    "path": "package.json",
    "chars": 1216,
    "preview": "{\n  \"name\": \"react-canvas\",\n  \"version\": \"1.3.0\",\n  \"description\": \"High performance <canvas> rendering for React compon"
  },
  {
    "path": "webpack.config.js",
    "chars": 612,
    "preview": "module.exports = {\n  cache: true,\n\n  watch: true,\n\n  entry: {\n    'listview': ['./examples/listview/app.js'],\n    'timel"
  }
]

About this extraction

This page contains the full source code of the Flipboard/react-canvas GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 47 files (137.9 KB), approximately 35.6k tokens, and a symbol index with 83 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!