react-spiral
Version:
A React component that renders text in a spiral shape
188 lines (187 loc) • 8.44 kB
JavaScript
;
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;