UNPKG

@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
"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