UNPKG

react-spiral

Version:

A React component that renders text in a spiral shape

188 lines (187 loc) 8.44 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; exports.__esModule = true; exports.Spiral = exports.getNumInOutsets = exports.calcInOutset = exports.measureShape = void 0; var react_1 = __importStar(require("react")); // TODO: test this function measureShape(size, sides, centralAngle) { var halfCentralAngle = centralAngle / 2; var cosine = Math.cos(halfCentralAngle); if (sides % 2) { // odd number of sides var circumradius = size / Math.sin(((sides - 1) / (sides * 2)) * Math.PI) / 2; var inradius_1 = circumradius * cosine; var a = Math.pow(circumradius, 2) * 2; return { height: inradius_1 + circumradius, sideLength: Math.sqrt(a - a * Math.cos(centralAngle)) }; } var halfSize = size / 2; var isHalfOdd = (sides / 2) % 2; var ratio = isHalfOdd ? Math.sin : Math.tan; var inradius = isHalfOdd ? halfSize * cosine : halfSize; return { height: inradius * 2, sideLength: halfSize * ratio(halfCentralAngle) * 2 }; } exports.measureShape = measureShape; // TODO: test this function calcInOutset(spacing, centralAngle) { var interiorAngle = Math.PI - centralAngle; // the distance needed to space out parallel lines appropriately // this is the hypoteneuse of a triangle with an opposite side of `spacing` var inset = spacing / Math.sin(interiorAngle); // the amount needed to add or subtract from the outside of the shape var outset = Math.sqrt(Math.pow(inset, 2) - Math.pow(spacing, 2)); if (interiorAngle > centralAngle) { // triangles subtract outsets, polygons with > 4 sides add outsets outset *= -1; } return [inset, outset]; } exports.calcInOutset = calcInOutset; function getNumInOutsets(side, numSides) { // key values needed to calculate the amount of insets and offsets required var _a = Array.from({ length: 3 }, function (_, index) { return Math.floor(Math.max(side - index, 0) / numSides); }), a = _a[0], b = _a[1], c = _a[2]; var numInsets = a + c; var numOutsets = 2 * b; return [numInsets, numOutsets]; } exports.getNumInOutsets = getNumInOutsets; function Spiral(props) { var boxSize = props.boxSize, fontSize = props.fontSize, sides = props.sides, spacing = props.spacing, children = props.children; var centralAngle = react_1.useMemo(function () { return (Math.PI * 2) / sides; }, [sides]); var totalSize = react_1.useMemo(function () { return boxSize - fontSize; }, [boxSize, fontSize]); var _a = react_1.useMemo(function () { return measureShape(totalSize, sides, centralAngle); }, [totalSize, sides, centralAngle]), height = _a.height, sideLength = _a.sideLength; var _b = react_1.useMemo(function () { return calcInOutset(spacing, centralAngle); }, [spacing, centralAngle]), inset = _b[0], outset = _b[1]; var _c = react_1.useMemo(function () { var charWidth = fontSize / 1.5; return [ charWidth, charWidth / 2 // base padding is half a character width ]; }, [fontSize]), charWidth = _c[0], basePadding = _c[1]; var padding = react_1.useMemo(function () { // we give progressively less padding to polygons with more than 4 sides var paddingModifier = 4 / sides; return basePadding * paddingModifier; }, [basePadding, sides]); var segments = react_1.useMemo(function () { var segments = []; var words = children .trim() // trim leading/trailing space .split(/\s+/) // split into array of words .map(function (text) { return ({ text: text, isFullWord: true // mark as full word }); }); while (sideLength > inset) { var side = segments.length + 1; var _a = getNumInOutsets(side, sides), numInsets = _a[0], numOutsets = _a[1]; var outerWidth_1 = sideLength - inset * numInsets - outset * numOutsets; if (outerWidth_1 < inset) { break; } // account for padding on either side of the segment var innerWidth_1 = outerWidth_1 - padding * 2; // calculate the number of characters that can fit in the segment var numChars = Math.floor(innerWidth_1 / charWidth); if (numChars <= 0) { break; } // fill the segment with text until all characters have been accounted for var text = ''; while (numChars > 0) { // grab the first word var word = words.shift(); // return it to the end of the stack if it's a full word if (word.isFullWord) { words.push(word); } // if there isn't enough space for the full word if (word.text.length > numChars) { // grab the part of it that will fit var fragment = word.text.slice(0, numChars); // and put the rest of it back into the front of the stack words.unshift({ text: word.text.slice(numChars), isFullWord: false // not a full word }); text += fragment; numChars -= fragment.length; } else { // otherwise add the word to the segment text += word.text; numChars -= word.text.length; if (numChars > 1) { // add a space if there's room text += ' '; numChars -= 1; } else if (numChars) { // otherwise bail out and complete the segment break; } } } segments.unshift({ side: side, text: text, width: outerWidth_1 }); } return segments; }, [sideLength, charWidth, inset, outset, padding, sides, children]); return (react_1["default"].createElement("div", { style: { overflow: 'hidden', lineHeight: 1, fontSize: fontSize, padding: fontSize / 2 } }, react_1["default"].createElement("div", { style: { width: totalSize, height: totalSize, paddingTop: (totalSize - height) / 2, paddingLeft: (totalSize - sideLength) / 2 } }, segments.reduce(function (child, segment) { var side = segment.side, width = segment.width, text = segment.text; return (react_1["default"].createElement("div", { style: { display: 'flex', transformOrigin: 'left', transform: side > 1 ? "rotate(" + centralAngle + "rad)" : 'translateY(-50%)' } }, react_1["default"].createElement("span", { style: { flexShrink: 0, display: 'flex', whiteSpace: 'pre', justifyContent: 'space-evenly', width: width, padding: "0 " + padding + "px" } }, text.split('').map(function (char, index) { return (react_1["default"].createElement("span", { key: index }, char)); })), child)); }, null)))); } exports.Spiral = Spiral;