@bucky24/react-canvas
Version:
A library of components that can be used to manipulate a canvas using JSX embedded in react.
1,414 lines (1,398 loc) • 56.3 kB
JavaScript
"use strict";
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Image = Image;
exports.renderToImage = renderToImage;
exports.renderToCanvas = renderToCanvas;
exports.blendImage = blendImage;
exports.CompoundElement = CompoundElement;
exports.useWithContext = useWithContext;
exports.BLEND_TYPE = exports.Clip = exports.Pattern = exports.Images = exports.CanvasContext = exports.Raw = exports.Arc = exports.Circle = exports.Rect = exports.Line = exports.CanvasComponent = exports.Shape = exports.Text = exports.Canvas = exports.ButtonTypes = exports.EventTypes = void 0;
var _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _reactFastCompare = _interopRequireDefault(require("react-fast-compare"));
var _reactDom = _interopRequireDefault(require("react-dom"));
var _client = _interopRequireDefault(require("react-dom/client"));
var _AnimationContext = require("./contexts/AnimationContext");
var _RenderContext = require("./contexts/RenderContext");
var _handlerToProps;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var EventTypes = {
MOVE: 'mousemove',
MOUSE_DOWN: 'mousedown',
MOUSE_UP: 'mouseup',
KEY_DOWN: 'keydown',
KEY_UP: 'keyup',
WHEEL: 'wheel'
};
exports.EventTypes = EventTypes;
var ButtonTypes = {
LEFT: 'left',
MIDDLE: 'middle',
RIGHT: 'right'
};
// 0=left, 1=middle, 2=right
exports.ButtonTypes = ButtonTypes;
var ButtonMap = [ButtonTypes.LEFT, ButtonTypes.MIDDLE, ButtonTypes.RIGHT];
// from https://stackoverflow.com/a/7616484/8346513
function hashString(str) {
var hash = 0,
i,
chr;
for (i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return hash >>> 0;
}
function drawShape(x, y, context, points, color, fill, close) {
context.save();
context.fillStyle = color;
context.strokeStyle = color;
context.beginPath();
context.moveTo(points[0].x + x, points[0].y + y);
for (var i = 1; i < points.length; i++) {
context.lineTo(points[i].x + x, points[i].y + y);
}
if (close) {
context.closePath();
}
if (fill) context.fill();
context.stroke();
context.restore();
}
var okCodes = ['Space', 'Backslash', 'BracketLeft', 'BracketRight', 'Quote', 'Semicolon', 'Period', 'Comma', 'Slash', 'Backquote', 'Minus', 'Equal'];
function getChar(_ref) {
var key = _ref.key,
code = _ref.code;
if (code.indexOf('Key') === 0 || code.indexOf('Digit') === 0 || code.indexOf('Numpad') === 0) {
// some though we don't want
if (code !== 'NumpadEnter') {
return key;
}
}
if (okCodes.includes(code)) {
return key;
}
// if not key and not [in map, then no char for it
}
function getCode(_ref2) {
var code = _ref2.code;
return code;
}
var handlerToProps = (_handlerToProps = {}, _defineProperty(_handlerToProps, EventTypes.MOVE, 'onMouseMove'), _defineProperty(_handlerToProps, EventTypes.MOUSE_DOWN, 'onMouseDown'), _defineProperty(_handlerToProps, EventTypes.MOUSE_UP, 'onMouseUp'), _defineProperty(_handlerToProps, EventTypes.KEY_DOWN, 'onKeyDown'), _defineProperty(_handlerToProps, EventTypes.KEY_UP, 'onKeyUp'), _defineProperty(_handlerToProps, EventTypes.WHEEL, 'onWheel'), _handlerToProps);
var CanvasContext = _react.default.createContext({
context: null,
registerListener: null,
unregisterListener: null,
triggerRender: null,
getImage: null,
loadPattern: null,
forceRenderCount: null
});
exports.CanvasContext = CanvasContext;
var CanvasClipContext = _react.default.createContext({
data: null
});
var loadingMap = {};
var imageMap = {};
function loadImage(src, cb) {
var hash = hashString(src);
if (imageMap[hash]) {
return imageMap[hash];
}
if (loadingMap[hash]) {
// if we've already registered this function, don't register it again
if (loadingMap[hash].includes(cb)) {
return null;
}
loadingMap[hash].push(cb);
return null;
}
// else load it
var body = document.getElementsByTagName("body")[0];
var img = document.createElement("img");
img.src = src;
img.onload = function () {
imageMap[hash] = img;
if (hash in loadingMap) {
var list = loadingMap[hash];
// this has to be done first, because we need to
// finish this function call. Otherwise, if the cb
// causes the image to be loaded again, this loops
// forever.
setTimeout(function () {
list.forEach(function (cb) {
cb(src, img);
});
});
}
delete loadingMap[hash];
};
if (img.loaded) {
imageMap[hash] = img;
delete loadingMap[hash];
return img;
} else {
if (!(hash in loadingMap)) {
loadingMap[hash] = [];
}
loadingMap[hash].push(cb);
}
img.style.display = 'none';
body.append(img);
return null;
}
function loadImagePromise(src) {
return new Promise(function (resolve) {
loadImage(src, function (src, img) {
resolve(img);
});
});
}
var patternMap = {};
var patternLoadingMap = {};
function loadPattern(src, context, cb) {
var hash = hashString(src);
if (patternMap[hash]) {
return patternMap[hash];
}
var image = loadImage(src, function (src, img) {
var pattern = context.createPattern(img, 'repeat');
patternMap[hash] = pattern;
patternLoadingMap[hash].forEach(function (cb) {
cb(src);
});
});
if (image) {
var pattern = context.createPattern(image, 'repeat');
patternMap[hash] = pattern;
return image;
}
if (!patternLoadingMap[hash]) {
patternLoadingMap[hash] = [];
}
if (patternLoadingMap[hash].includes(cb)) {
return null;
}
patternLoadingMap[hash].push(cb);
return null;
}
var canvasProps = {
width: _propTypes.default.number.isRequired,
height: _propTypes.default.number.isRequired,
captureAllKeyEvents: _propTypes.default.bool,
drawWidth: _propTypes.default.number,
drawHeight: _propTypes.default.number,
debug: _propTypes.default.bool
};
var canvasDefaultProps = {
captureAllKeyEvents: true,
drawWidth: undefined,
drawHeight: undefined
};
var Canvas = /*#__PURE__*/function (_React$Component) {
_inherits(Canvas, _React$Component);
function Canvas(props) {
var _this;
_classCallCheck(this, Canvas);
_this = _possibleConstructorReturn(this, _getPrototypeOf(Canvas).call(this, props));
_this.state = {
context: null
};
_this.indexList = [];
_this.reattachListeners = _this.reattachListeners.bind(_assertThisInitialized(_this));
_this.removeListeners = _this.removeListeners.bind(_assertThisInitialized(_this));
_this.handleMouseMove = _this.handleMouseMove.bind(_assertThisInitialized(_this));
_this.handleMouseUp = _this.handleMouseUp.bind(_assertThisInitialized(_this));
_this.handleMouseDown = _this.handleMouseDown.bind(_assertThisInitialized(_this));
_this.handleKeyDown = _this.handleKeyDown.bind(_assertThisInitialized(_this));
_this.handleKeyUp = _this.handleKeyUp.bind(_assertThisInitialized(_this));
_this.handleContextMenu = _this.handleContextMenu.bind(_assertThisInitialized(_this));
_this.handleWheel = _this.handleWheel.bind(_assertThisInitialized(_this));
_this.registerListener = _this.registerListener.bind(_assertThisInitialized(_this));
_this.unregisterListener = _this.unregisterListener.bind(_assertThisInitialized(_this));
_this.forceRerender = _this.forceRerender.bind(_assertThisInitialized(_this));
// map of event to array of function callbacks
_this.listeners = {};
// count of how many times we've force-rendered (generally used to determine if the only reason we're rendering is because an image loaded)
_this.forceRenderCount = 0;
return _this;
}
_createClass(Canvas, [{
key: "log",
value: function log(prefix) {
if (this.props.debug) {
var _console;
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
(_console = console).log.apply(_console, [prefix + ":"].concat(args));
}
}
}, {
key: "registerListener",
value: function registerListener(event, fn) {
this.log("Canvas", "Registering listener for event " + event);
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(fn);
}
}, {
key: "unregisterListener",
value: function unregisterListener(event, fn) {
if (!this.listeners[event]) {
return;
}
this.log("Canvas", "Unregistering listener for event " + event);
var index = this.listeners[event].indexOf(fn);
if (index < 0) return;
this.listeners[event].splice(index, 1);
}
}, {
key: "forceRerender",
value: function forceRerender() {
this.log("Canvas", "forceRerender called");
this.forceRenderCount += 1;
this.forceUpdate();
}
}, {
key: "getMyContext",
value: function getMyContext() {
var _this2 = this;
var width = this.props.drawWidth;
if (!width) {
width = this.canvas ? this.canvas.width : this.props.width;
}
var height = this.props.drawHeight;
if (!height) {
height = this.canvas ? this.canvas.height : this.props.height;
}
return {
context: this.state.context,
registerListener: this.registerListener,
unregisterListener: this.unregisterListener,
forceRerender: this.forceRerender,
triggerRender: this.triggerRender,
getImage: loadImage,
loadPattern: loadPattern,
forceRenderCount: this.forceRenderCount,
width: width,
height: height,
debug: this.props.debug,
log: function log() {
_this2.log.apply(_this2, arguments);
}
};
}
}, {
key: "UNSAFE_componentWillUpdate",
value: function UNSAFE_componentWillUpdate(newProps) {
this.processChanges(newProps);
}
}, {
key: "processChanges",
value: function processChanges(props) {
var useWidth = props.drawWidth || props.width;
var useHeight = props.drawHeight || props.height;
if (useWidth !== this.canvas.width) {
this.canvas.width = useWidth;
}
if (useHeight !== this.canvas.height) {
this.canvas.height = useHeight;
}
if (props.drawWidth !== props.width) {
this.canvas.style.width = "".concat(props.width, "px");
}
if (props.drawHeight !== props.height) {
this.canvas.style.height = "".concat(props.height, "px");
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this.removeListeners();
}
}, {
key: "removeListeners",
value: function removeListeners() {
this.canvas.removeEventListener('touchmove', this.handleMouseMove);
this.canvas.removeEventListener('touchstart', this.handleMouseDown);
this.canvas.removeEventListener('touchend', this.handleMouseUp);
this.canvas.removeEventListener('mousemove', this.handleMouseMove);
this.canvas.removeEventListener('mousedown', this.handleMouseDown);
this.canvas.removeEventListener('mouseup', this.handleMouseUp);
this.canvas.removeEventListener('contextmenu', this.handleContextMenu);
window.removeEventListener('keydown', this.handleKeyDown);
window.removeEventListener('keyup', this.handleKeyUp);
this.canvas.removeEventListener('wheel', this.handleWheel);
}
}, {
key: "reattachListeners",
value: function reattachListeners() {
// remove previous event handlers. this is so we avoid
// double and triple triggering events
this.removeListeners();
this.canvas.addEventListener('touchmove', this.handleMouseMove);
this.canvas.addEventListener('touchstart', this.handleMouseDown);
this.canvas.addEventListener('touchend', this.handleMouseUp);
this.canvas.addEventListener('mousemove', this.handleMouseMove);
this.canvas.addEventListener('mousedown', this.handleMouseDown);
this.canvas.addEventListener('mouseup', this.handleMouseUp);
this.canvas.addEventListener('contextmenu', this.handleContextMenu);
window.addEventListener('keydown', this.handleKeyDown);
window.addEventListener('keyup', this.handleKeyUp);
this.canvas.addEventListener('wheel', this.handleWheel);
}
}, {
key: "getRealCoords",
value: function getRealCoords(event) {
var rect = this.canvas.getBoundingClientRect();
// if this is a touch event, handle it specially
if (event.touches) {
var handledTouches = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = event.touches[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _touch = _step.value;
handledTouches.push({
x: _touch.clientX - rect.left,
y: _touch.clientY - rect.top
});
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
if (handledTouches.length === 0) {
// touchend only has changedTouches
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = event.changedTouches[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var touch = _step2.value;
handledTouches.push({
x: touch.clientX - rect.left,
y: touch.clientY - rect.top
});
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
}
if (handledTouches.length === 0) {
// shouldn't happen but just in case
return {};
}
return {
x: handledTouches[0].x,
y: handledTouches[0].y,
touches: handledTouches
};
}
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
}
}, {
key: "handleMouseMove",
value: function handleMouseMove(event) {
this.triggerEvent(EventTypes.MOVE, this.getRealCoords(event));
event.preventDefault();
}
}, {
key: "handleMouseDown",
value: function handleMouseDown(event) {
this.triggerEvent(EventTypes.MOUSE_DOWN, _objectSpread({}, this.getRealCoords(event), {
button: ButtonMap[event.button]
}));
event.preventDefault();
}
}, {
key: "handleMouseUp",
value: function handleMouseUp(event) {
this.triggerEvent(EventTypes.MOUSE_UP, _objectSpread({}, this.getRealCoords(event), {
button: ButtonMap[event.button]
}));
event.preventDefault();
}
}, {
key: "handleKeyDown",
value: function handleKeyDown(event) {
var bodyEvent = event.target.tagName === 'BODY';
if (!bodyEvent && !this.props.captureAllKeyEvents) {
// if this event did not come from the body, check if
// we want to capture all events. If we do, capture it
// if not, ignore it
return;
}
this.triggerEvent(EventTypes.KEY_DOWN, {
char: getChar(event),
code: getCode(event)
});
event.preventDefault();
}
}, {
key: "handleKeyUp",
value: function handleKeyUp(event) {
var bodyEvent = event.target.tagName === 'BODY';
if (!bodyEvent && !this.props.captureAllKeyEvents) {
// if this event did not come from the body, check if
// we want to capture all events. If we do, capture it
// if not, ignore it
return;
}
this.triggerEvent(EventTypes.KEY_UP, {
char: getChar(event),
code: getCode(event)
});
event.preventDefault();
}
}, {
key: "handleContextMenu",
value: function handleContextMenu(event) {
event.preventDefault();
}
}, {
key: "handleWheel",
value: function handleWheel(event) {
// Firefox uses deltaY, but it's opposite of the normal wheel delta
var delta = event.wheelDelta || -event.deltaY;
this.triggerEvent(EventTypes.WHEEL, _objectSpread({}, this.getRealCoords(event), {
up: delta > 0
}));
}
}, {
key: "triggerEvent",
value: function triggerEvent(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(function (fn) {
fn(data);
});
}
if (handlerToProps[event]) {
var propName = handlerToProps[event];
var propFn = this.props[propName];
if (propFn) {
propFn(data);
}
}
}
}, {
key: "render",
value: function render() {
var _this3 = this;
this.indexList = [];
var newChildren = this.props.children;
if (!Array.isArray(newChildren)) {
newChildren = [newChildren];
}
this.log("Canvas", "Rendering canvas");
var refFunc = function refFunc(c) {
if (c) {
var newContext = c.getContext('2d');
if (_this3.state.context !== newContext) {
_this3.log("Canvas", "Canvas context updated, likely new canvas created");
_this3.canvas = c;
_this3.setState({
context: newContext
}, function () {
_this3.reattachListeners();
});
}
}
};
return _react.default.createElement(CanvasContext.Provider, {
value: this.getMyContext()
}, _react.default.createElement("canvas", {
ref: refFunc
}, newChildren));
}
}]);
return Canvas;
}(_react.default.Component);
exports.Canvas = Canvas;
;
Canvas.propTypes = canvasProps;
Canvas.defaultProps = canvasDefaultProps;
var Text = function Text(_ref3) {
var children = _ref3.children,
x = _ref3.x,
y = _ref3.y,
color = _ref3.color,
font = _ref3.font,
backgroundColor = _ref3.backgroundColor,
padding = _ref3.padding,
background = _ref3.background;
var withContext = useWithContext();
return withContext(function (context) {
if (!color) {
color = "#000";
}
if (!font) {
font = "12px Arial";
}
if (!padding) {
padding = 2;
}
if (!backgroundColor) {
backgroundColor = "rgba(255, 255, 255, 0.5)";
}
if (!background) {
background = false;
}
if (!Array.isArray(children)) {
children = [children];
}
context.save();
context.font = font;
var text = children.join('');
if (background) {
var textWidth = context.measureText(text).width;
var textHeight = parseInt(font, 10); // crude estimate of height
// Draw background box
context.fillStyle = backgroundColor;
context.fillRect(x - padding / 2, y - padding / 2 - textHeight, textWidth + padding, textHeight + padding);
}
// Draw the text
context.fillStyle = color;
context.textBaseline = "bottom";
context.fillText(text, x, y);
context.restore();
});
};
exports.Text = Text;
var Line = function Line(_ref4) {
var x = _ref4.x,
y = _ref4.y,
x2 = _ref4.x2,
y2 = _ref4.y2,
color = _ref4.color;
var withContext = useWithContext();
return withContext(function (context) {
context.save();
context.strokeStyle = color;
context.beginPath();
context.moveTo(x, y);
context.lineTo(x2, y2);
context.closePath();
context.stroke();
context.restore();
});
};
exports.Line = Line;
var Shape = function Shape(_ref5) {
var x = _ref5.x,
y = _ref5.y,
points = _ref5.points,
color = _ref5.color,
fill = _ref5.fill,
close = _ref5.close;
var withContext = useWithContext();
return withContext(function (context) {
if (close === undefined) {
close = true;
}
drawShape(x, y, context, points, color, fill, close);
});
};
exports.Shape = Shape;
var Rect = function Rect(_ref6) {
var x = _ref6.x,
y = _ref6.y,
x2 = _ref6.x2,
y2 = _ref6.y2,
color = _ref6.color,
fill = _ref6.fill;
var width = Math.abs(x2 - x);
var height = Math.abs(y2 - y);
return _react.default.createElement(Shape, {
x: x,
y: y,
points: [{
x: 0,
y: 0
}, {
x: 0,
y: height
}, {
x: width,
y: height
}, {
x: width,
y: 0
}],
color: color,
fill: fill
});
};
exports.Rect = Rect;
var imagePropTypes = {
src: _propTypes.default.oneOfType([_propTypes.default.instanceOf(Element), _propTypes.default.string]).isRequired,
x: _propTypes.default.number.isRequired,
y: _propTypes.default.number.isRequired,
width: _propTypes.default.number.isRequired,
height: _propTypes.default.number.isRequired,
clip: _propTypes.default.shape({
x: _propTypes.default.number.isRequired,
y: _propTypes.default.number.isRequired,
width: _propTypes.default.number.isRequired,
height: _propTypes.default.number.isRequired
}),
flipX: _propTypes.default.bool,
flipY: _propTypes.default.bool,
onLoad: _propTypes.default.func
};
var imageDefaultProps = {
flipX: false,
flipY: false
};
function Image(_ref7) {
var src = _ref7.src,
x = _ref7.x,
y = _ref7.y,
width = _ref7.width,
height = _ref7.height,
clip = _ref7.clip,
rot = _ref7.rot,
onLoad = _ref7.onLoad,
flipY = _ref7.flipY,
flipX = _ref7.flipX;
var _useContext = (0, _react.useContext)(CanvasContext),
forceRerender = _useContext.forceRerender,
getImage = _useContext.getImage;
var withContext = useWithContext();
return withContext(function (context) {
var isElement = src instanceof Element || src instanceof HTMLDocument;
var img;
if (isElement) {
if (src.nodeName !== "CANVAS" && src.nodeName !== "IMG") {
throw new Error("A DOM element was passed as a src to Image, but the element was not a canvas or an img.");
}
img = src;
} else if (src instanceof Object) {
throw new Error("An object was passed as a src to Image, but it was not a DOM element");
} else {
var loadFn = onLoad || forceRerender;
img = getImage(src, loadFn);
}
if (!img) {
return null;
}
context.save();
if (clip) {
var sx = clip.x,
sy = clip.y,
sw = clip.width,
sh = clip.height;
var iw = img.width;
var ih = img.height;
// basically convert the clip coords from draw space to image space
var rw = iw / width;
var rh = ih / height;
var finalX = sx * rw;
var finalY = sy * rh;
context.translate(x + width / 2, y + height / 2);
if (rot) {
var rotRad = rot * Math.PI / 180;
context.rotate(rotRad);
}
if (flipX || flipY) {
context.scale(flipX ? -1 : 1, flipY ? -1 : 1);
}
context.translate(-x - width / 2, -y - height / 2);
context.drawImage(img, finalX, finalY, sw * rw, sh * rh, x, y, width, height);
} else {
context.translate(x + width / 2, y + height / 2);
if (rot) {
var _rotRad = rot * Math.PI / 180;
context.rotate(_rotRad);
}
if (flipX || flipY) {
context.scale(flipX ? -1 : 1, flipY ? -1 : 1);
}
context.translate(-x - width / 2, -y - height / 2);
context.drawImage(img, x, y, width, height);
}
context.restore();
});
}
;
Image.propTypes = imagePropTypes;
Image.defaultProps = imageDefaultProps;
var imagesPropTypes = {
images: _propTypes.default.arrayOf(_propTypes.default.shape({
src: _propTypes.default.string.isRequired,
x: _propTypes.default.number.isRequired,
y: _propTypes.default.number.isRequired,
width: _propTypes.default.number.isRequired,
height: _propTypes.default.number.isRequired,
rot: _propTypes.default.number,
clip: _propTypes.default.shape({
x: _propTypes.default.number.isRequired,
y: _propTypes.default.number.isRequired,
width: _propTypes.default.number.isRequired,
height: _propTypes.default.number.isRequired
})
})),
onLoad: _propTypes.default.func
};
var Images = function Images(_ref8) {
var images = _ref8.images,
onLoad = _ref8.onLoad;
var _useContext2 = (0, _react.useContext)(CanvasContext),
forceRerender = _useContext2.forceRerender,
getImage = _useContext2.getImage;
var withContext = useWithContext();
return withContext(function (context) {
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = images[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var image = _step3.value;
var src = image.src,
x = image.x,
y = image.y,
width = image.width,
height = image.height,
clip = image.clip,
rot = image.rot;
var loadFn = onLoad || forceRerender;
var img = getImage(src, loadFn);
context.save();
if (!img) {
continue;
}
if (clip) {
var sx = clip.x,
sy = clip.y,
sw = clip.width,
sh = clip.height;
var iw = img.width;
var ih = img.height;
// basically convert the clip coords from draw space to image space
var rw = iw / width;
var rh = ih / height;
var finalX = sx * rw;
var finalY = sy * rh;
context.translate(x + width / 2, y + height / 2);
if (rot) {
var rotRad = rot * Math.PI / 180;
context.rotate(rotRad);
}
context.translate(-x - width / 2, -y - height / 2);
context.drawImage(img, finalX, finalY, sw * rw, sh * rh, x, y, width, height);
} else {
context.translate(x + width / 2, y + height / 2);
if (rot) {
var _rotRad2 = rot * Math.PI / 180;
context.rotate(_rotRad2);
}
context.translate(-x - width / 2, -y - height / 2);
context.drawImage(img, x, y, width, height);
}
context.restore();
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
});
};
exports.Images = Images;
Images.propTypes = imagesPropTypes;
var Arc = function Arc(_ref9) {
var x = _ref9.x,
y = _ref9.y,
radius = _ref9.radius,
startAngle = _ref9.startAngle,
endAngle = _ref9.endAngle,
color = _ref9.color,
fill = _ref9.fill,
sector = _ref9.sector,
closed = _ref9.closed;
var withContext = useWithContext();
return withContext(function (context) {
context.save();
context.strokeStyle = color;
context.fillStyle = color;
context.beginPath();
if (sector) {
context.moveTo(x, y);
}
context.arc(x, y, radius, startAngle, endAngle);
if (sector) {
context.moveTo(x, y);
}
if (closed) {
context.closePath();
}
if (!fill) {
context.stroke();
} else {
context.fill();
}
context.restore();
});
};
exports.Arc = Arc;
var Circle = function Circle(_ref10) {
var x = _ref10.x,
y = _ref10.y,
radius = _ref10.radius,
color = _ref10.color,
fill = _ref10.fill;
return _react.default.createElement(Arc, {
x: x,
y: y,
radius: radius,
startAngle: 0,
endAngle: 2 * Math.PI,
color: color,
fill: fill
});
};
exports.Circle = Circle;
var Raw = function Raw(_ref11) {
var drawFn = _ref11.drawFn;
var withContext = useWithContext();
return withContext(function (context) {
context.save();
drawFn(context);
context.restore();
});
};
exports.Raw = Raw;
var Pattern = function Pattern(_ref12) {
var x = _ref12.x,
y = _ref12.y,
width = _ref12.width,
height = _ref12.height,
src = _ref12.src;
var _useContext3 = (0, _react.useContext)(CanvasContext),
forceRerender = _useContext3.forceRerender;
var withContext = useWithContext();
return withContext(function (context) {
var pattern = loadPattern(src, context, forceRerender);
if (!pattern) {
return null;
}
context.save();
context.fillStyle = pattern;
context.fillRect(x, y, width, height);
context.restore();
});
};
exports.Pattern = Pattern;
function useWithContext() {
var _useContext4 = (0, _react.useContext)(CanvasContext),
context = _useContext4.context;
var clipContext = (0, _react.useContext)(CanvasClipContext);
return function (cb) {
if (!context) {
return null;
}
context.save();
if (clipContext?.data) {
var data = clipContext.data;
context.beginPath();
context.rect(data.x, data.y, data.width, data.height);
context.clip();
}
var result = cb(context);
context.restore();
return result;
};
}
var Clip = function Clip(_ref13) {
var x = _ref13.x,
y = _ref13.y,
width = _ref13.width,
height = _ref13.height,
children = _ref13.children;
var _useContext5 = (0, _react.useContext)(CanvasContext),
context = _useContext5.context;
if (!context) {
return null;
}
return _react.default.createElement(CanvasClipContext.Provider, {
value: {
data: {
x: x,
y: y,
width: width,
height: height
}
}
}, children);
};
exports.Clip = Clip;
var CanvasComponent = /*#__PURE__*/function (_React$Component2) {
_inherits(CanvasComponent, _React$Component2);
function CanvasComponent(props) {
var _this4;
_classCallCheck(this, CanvasComponent);
_this4 = _possibleConstructorReturn(this, _getPrototypeOf(CanvasComponent).call(this, props));
_this4.bounds = null;
_this4.handleMove = _this4.handleMove.bind(_assertThisInitialized(_this4));
_this4.handleUp = _this4.handleUp.bind(_assertThisInitialized(_this4));
_this4.handleDown = _this4.handleDown.bind(_assertThisInitialized(_this4));
_this4.onKeyDown = _this4.onKeyDown.bind(_assertThisInitialized(_this4));
_this4.onKeyUp = _this4.onKeyUp.bind(_assertThisInitialized(_this4));
_this4.handleWheel = _this4.handleWheel.bind(_assertThisInitialized(_this4));
return _this4;
}
_createClass(CanvasComponent, [{
key: "componentDidMount",
value: function componentDidMount() {
if (!this.context.registerListener) {
console.error('Unable to get child context for CanvasComponent-likely it is not nested inside a Canvas');
return;
}
this.context.registerListener(EventTypes.MOVE, this.handleMove);
this.context.registerListener(EventTypes.MOUSE_UP, this.handleUp);
this.context.registerListener(EventTypes.MOUSE_DOWN, this.handleDown);
this.context.registerListener(EventTypes.KEY_DOWN, this.onKeyDown);
this.context.registerListener(EventTypes.KEY_DOWN, this.onKeyUp);
this.context.registerListener(EventTypes.WHEEL, this.handleWheel);
}
}, {
key: "insideMe",
value: function insideMe(x, y) {
if (!this.bounds) {
return false;
}
return x > this.bounds.x && x < this.bounds.x + this.bounds.width && y > this.bounds.y && y < this.bounds.y + this.bounds.height;
}
}, {
key: "handleMove",
value: function handleMove(data) {
var insideMe = this.insideMe(data.x, data.y);
this.onMouseMove(data, insideMe);
}
}, {
key: "handleUp",
value: function handleUp(data) {
var insideMe = this.insideMe(data.x, data.y);
this.onMouseUp(data, insideMe);
}
}, {
key: "handleDown",
value: function handleDown(data) {
var insideMe = this.insideMe(data.x, data.y);
this.onMouseDown(data, insideMe);
}
}, {
key: "handleWheel",
value: function handleWheel(data) {
var insideMe = this.insideMe(data.x, data.y);
this.onWheel(data, insideMe);
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
if (!this.context.unregisterListener) {
console.error('Unable to get child context for CanvasComponent-likely it is not nested inside a Canvas');
return;
}
this.context.unregisterListener(EventTypes.MOVE, this.handleMove);
this.context.unregisterListener(EventTypes.MOUSE_UP, this.handleUp);
this.context.unregisterListener(EventTypes.MOUSE_DOWN, this.handleDown);
this.context.unregisterListener(EventTypes.KEY_DOWN, this.onKeyDown);
this.context.unregisterListener(EventTypes.KEY_DOWN, this.onKeyUp);
this.context.unregisterListener(EventTypes.WHEEL, this.handleWheel);
} // stubs
}, {
key: "onMouseMove",
value: function onMouseMove() {}
}, {
key: "onMouseUp",
value: function onMouseUp() {}
}, {
key: "onMouseDown",
value: function onMouseDown() {}
}, {
key: "onKeyDown",
value: function onKeyDown() {}
}, {
key: "onKeyUp",
value: function onKeyUp() {}
}, {
key: "onWheel",
value: function onWheel() {}
}]);
return CanvasComponent;
}(_react.default.Component);
exports.CanvasComponent = CanvasComponent;
_defineProperty(CanvasComponent, "contextType", CanvasContext);
CanvasComponent.contextType = CanvasContext;
function renderToCanvas(elements) {
var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var extraContextData = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var holderDiv = document.createElement("div");
var canvas = document.createElement("canvas");
var canvasContext = canvas.getContext("2d");
var textHolder = document.createElement("div");
// don't stretch past content
textHolder.style.display = "inline-block";
document.body.appendChild(textHolder);
var value = _objectSpread({
getImage: loadImage,
loadPattern: loadPattern,
registerListener: function registerListener() {},
unregisterListener: function unregisterListener() {},
forceRerender: function forceRerender() {},
triggerRender: function triggerRender() {}
}, context, {
context: canvasContext
});
var old = canvasContext;
var useMinX = null;
var useMaxX = null;
var useMinY = null;
var useMaxY = null;
var checkX = function checkX(x) {
if (x === null || isNaN(x)) {
console.error("renderToCanvas Got bad x: ", x);
}
if (useMinX === null) {
useMinX = x;
} else {
useMinX = Math.min(x, useMinX);
}
if (useMaxX === null) {
useMaxX = x;
} else {
useMaxX = Math.max(x, useMaxX);
}
};
var checkY = function checkY(y) {
if (y === null || isNaN(y)) {
console.error("renderToCanvas Got bad y: ", y);
}
if (useMinY === null) {
useMinY = y;
} else {
useMinY = Math.min(y, useMinY);
}
if (useMaxY === null) {
useMaxY = y;
} else {
useMaxY = Math.max(y, useMaxY);
}
};
value.context = {
save: function save() {},
restore: function restore() {},
beginPath: function beginPath() {},
closePath: function closePath() {},
fill: function fill() {},
stroke: function stroke() {},
rotate: function rotate() {},
scale: function scale() {},
translate: function translate() {},
moveTo: function moveTo() {
checkX(arguments.length <= 0 ? undefined : arguments[0]);
checkY(arguments.length <= 1 ? undefined : arguments[1]);
},
lineTo: function lineTo() {
checkX(arguments.length <= 0 ? undefined : arguments[0]);
checkY(arguments.length <= 1 ? undefined : arguments[1]);
},
drawImage: function drawImage() {
if (arguments.length === 5) {
checkX(arguments.length <= 1 ? undefined : arguments[1]);
checkY(arguments.length <= 2 ? undefined : arguments[2]);
// x + width
checkX((arguments.length <= 1 ? undefined : arguments[1]) + (arguments.length <= 3 ? undefined : arguments[3]));
// y + height
checkY((arguments.length <= 2 ? undefined : arguments[2]) + (arguments.length <= 4 ? undefined : arguments[4]));
} else {
throw new Error("renderCanvas drawImage hook got unexpected count of arguments: " + arguments.length);
}
},
arc: function arc() {
// the -1 and +1 is so the line fully shows up
checkX((arguments.length <= 0 ? undefined : arguments[0]) - (arguments.length <= 2 ? undefined : arguments[2]) - 1);
checkY((arguments.length <= 1 ? undefined : arguments[1]) - (arguments.length <= 2 ? undefined : arguments[2]) - 1);
checkX((arguments.length <= 0 ? undefined : arguments[0]) + (arguments.length <= 2 ? undefined : arguments[2]) + 1);
checkY((arguments.length <= 1 ? undefined : arguments[1]) + (arguments.length <= 2 ? undefined : arguments[2]) + 1);
},
fillText: function fillText() {
var font = value.context.font;
var _font$split = font.split(" "),
_font$split2 = _slicedToArray(_font$split, 2),
size = _font$split2[0],
family = _font$split2[1];
textHolder.style['font-size'] = size;
textHolder.style['font-family'] = family;
textHolder.innerText = arguments.length <= 0 ? undefined : arguments[0];
var rect = textHolder.getBoundingClientRect();
var width = rect.width,
height = rect.height;
checkX(arguments.length <= 1 ? undefined : arguments[1]);
checkX((arguments.length <= 1 ? undefined : arguments[1]) + width);
// text renders this way for some reason, from -height to 0
checkY((arguments.length <= 2 ? undefined : arguments[2]) - height);
checkY(arguments.length <= 2 ? undefined : arguments[2]);
}
};
var root = _client.default.createRoot(holderDiv);
_reactDom.default.flushSync(function () {
root.render(_reactDom.default.createPortal(_react.default.createElement(CanvasContext.Provider, {
value: value
}, _react.default.createElement(_RenderContext.RenderProvider, {
data: extraContextData
}, elements)), canvas));
});
// calculate real width and height and re-render
var width = useMaxX - useMinX;
var height = useMaxY - useMinY;
// the +1 prevents the "image of size zero" issue if there aren't any children
canvas.width = width + 1;
canvas.height = height + 1;
// use the old context since we don't need the hooks anymore
value.context = old;
canvasContext.save();
// if negative, shifts camvas over so the negative is 0,0
// if positive, does the same
canvasContext.translate(-useMinX, -useMinY);
holderDiv = document.createElement("div");
root = _client.default.createRoot(holderDiv);
_reactDom.default.flushSync(function () {
root.render(_reactDom.default.createPortal(_react.default.createElement(CanvasContext.Provider, {
value: value
}, _react.default.createElement(_RenderContext.RenderProvider, {
data: extraContextData
}, elements)), canvas));
});
canvasContext.restore();
// cleanup
textHolder.parentElement.removeChild(textHolder);
return {
canvas: canvas,
dims: {
x: useMinX,
y: useMinY,
width: width,
height: height
}
};
}
function renderToImage(elements) {
var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _renderToCanvas = renderToCanvas(elements, context),
canvas = _renderToCanvas.canvas;
var image = canvas.toDataURL("image/png");
return image;
}
function blendImage(_x) {
return _blendImage.apply(this, arguments);
}
function _blendImage() {
_blendImage = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(src) {
var replacements,
img,
canvas,
canvasContext,
imageData,
image,
i,
j,
index,
r,
g,
b,
_i2,
pixel,
red,
green,
blue,
hexCode,
_iteratorNormalCompletion6,
_didIteratorError6,
_iteratorError6,
_iterator6,
_step6,
replacement,
newRed,
newGreen,
newBlue,
newRedNum,
newGreenNum,
newBlueNum,
canvas2,
canvasContext2,
newImageData,
_i3,
_index,
_pixel,
_args = arguments;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
replacements = _args.length > 1 && _args[1] !== undefined ? _args[1] : [];
if (!(replacements.length === 0)) {
_context.next = 3;
break;
}
return _context.abrupt("return", src);
case 3:
_context.next = 5;
return loadImagePromise(src);
case 5:
img = _context.sent;
canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
canvasContext = canvas.getContext("2d");
canvasContext.drawImage(img, 0, 0);
imageData = canvasContext.getImageData(0, 0, canvas.width, canvas.height);
image = [];
for (i = 0; i < imageData.width; i++) {
for (j = 0; j < imageData.height; j++) {
index = i * (imageData.width * 4) + j * 4;
r = imageData.data[index];
g = imageData.data[index + 1];
b = imageData.data[index + 2];
image.push({
red: r,
green: g,
blue: b
});
}
}
// manipulation here
_i2 = 0;
case 15:
if (!(_i2 < image.length)) {
_context.next = 60;
break;
}
pixel = image[_i2];
red = pixel.red.toString(16).padStart(2, '0');
green = pixel.green.toString(16).padStart(2, '0');
blue = pixel.blue.toString(16).padStart(2, '0');
hexCode = "#" + red + green + blue;
_iteratorNormalCompletion6 = true;
_didIteratorError6 = false;
_iteratorError6 = undefined;
_context.prev = 24;
_iterator6 = replacements[Symbol.iterator]();
case 26:
if (_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done) {
_context.next = 43;
break;
}
replacement = _step6.value;
if (!(replacement.type === BLEND_TYPE.COLOR_SWAP)) {
_context.next = 40;
break;
}
if (!(hexCode === replacement.from)) {
_context.next = 40;
break;
}
newRed = replacement.to.substr(1, 2);
newGreen = replacement.to.substr(3, 2);
newBlue = replacement.to.substr(5, 2);
newRedNum = parseInt(newRed, 16);
newGreenNum = parseInt(newGreen, 16);
newBlueNum = parseInt(newBlue, 16);
pixel.red = newRedNum;
pixel.green = newGreenNum;
pixel.blue = newBlueNum;
// only run 1 modification on the pixel
return _context.abrupt("break", 43);
case 40:
_iteratorNormalCompletion6 = true;
_context.next = 26;
break;
case 43:
_context.next = 49;
break;
case 45:
_context.prev = 45;
_context.t0 = _context["catch"](24);
_didIteratorError6 = true;
_iteratorError6 = _context.t0;
case 49:
_context.prev = 49;
_context.prev = 50;
if (!_iteratorNormalCompletion6 && _iterator6.return != null) {
_iterator6.return();
}
case 52:
_context.prev = 52;
if (!_didIteratorError6) {
_context.next = 55;
break;
}
throw _iteratorError6;
case 55:
return _context.finish(52);
case 56:
return _context.finish(49);
case 57:
_i2++;
_context.next = 15;
break;
case 60