UNPKG

warp-grid

Version:

Create a complex grid, warped in 2D space and access data about its lines and cells

1,126 lines (1,125 loc) 36.6 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const coonsPatch = require("coons-patch"); const BezierEasing = require("bezier-easing"); const memoize = require("fast-memoize"); const matrix = require("matrix-js"); const bezierJs = require("bezier-js"); var InterpolationStrategy = /* @__PURE__ */ ((InterpolationStrategy2) => { InterpolationStrategy2["LINEAR"] = `linear`; InterpolationStrategy2["EVEN"] = `even`; return InterpolationStrategy2; })(InterpolationStrategy || {}); var LineStrategy = /* @__PURE__ */ ((LineStrategy2) => { LineStrategy2["STRAIGHT_LINES"] = `straightLines`; LineStrategy2["CURVES"] = `curves`; return LineStrategy2; })(LineStrategy || {}); var CellBoundsOrder = /* @__PURE__ */ ((CellBoundsOrder2) => { CellBoundsOrder2["TTB_LTR"] = `topToBottom-leftToRight`; CellBoundsOrder2["TTB_RTL"] = `topToBottom-rightToLeft`; CellBoundsOrder2["BTT_LTR"] = `bottomToTop-leftToRight`; CellBoundsOrder2["BTT_RTL"] = `bottomToTop-rightToLeft`; CellBoundsOrder2["LTR_TTB"] = `leftToRight-topToBottom`; CellBoundsOrder2["LTR_BTT"] = `leftToRight-bottomToTop`; CellBoundsOrder2["RTL_TTB"] = `rightToLeft-topToBottom`; CellBoundsOrder2["RTL_BTT"] = `rightToLeft-bottomToTop`; return CellBoundsOrder2; })(CellBoundsOrder || {}); const wrapInterpolatePointOnCurveWithEasing = (interpolatePointOnCurve) => (config) => { const interpolatePointOnCurveWrappedWithEasing = (t, curve) => { const ease = BezierEasing(...config.bezierEasing); const tEased = ease(t); return interpolatePointOnCurve(config)(tEased, curve); }; return interpolatePointOnCurveWrappedWithEasing; }; const interpolatePointOnCurveEvenlySpacedEasedFactory = wrapInterpolatePointOnCurveWithEasing( coonsPatch.interpolatePointOnCurveEvenlySpacedFactory ); const interpolatePointOnCurveLinearEasedFactory = wrapInterpolatePointOnCurveWithEasing(coonsPatch.interpolatePointOnCurveLinearFactory); class ValidationError extends Error { constructor(message = `A validation error occurred`) { super(message); this.message = message; } } const times = (f, n) => { const result = []; for (let i = 0; i < n; i++) { result.push(f(i)); } return result; }; const mapObj = (f, o) => { return Object.keys(o).reduce((acc, key, idx) => { const value = o[key]; return { ...acc, [key]: f(value, key, idx) }; }, {}); }; const VALID_GUTTER_REGEXP = /^\d*\.?\d+px$/; const isType = (type, value) => typeof value === type; const isBoolean = (value) => isType(`boolean`, value); const isArray = (value) => Array.isArray(value); const isInt = (value) => Number.isInteger(value); const isNumber = (value) => !isNaN(value) && isType(`number`, value); const isUndefined = (value) => isType(`undefined`, value); const isNull = (value) => value === null; const isNil = (value) => isUndefined(value) || isNull(value); const isString = (value) => isType(`string`, value) || value instanceof String; const isPlainObj = (value) => !isNull(value) && isType(`object`, value) && value.constructor === Object; const isFunction = (value) => isType(`function`, value); const isPixelNumberString = (value) => { return isString(value) && VALID_GUTTER_REGEXP.test(value); }; const getProcessedStepValue = (step) => { if (step === `0px`) { return 0; } return step; }; const ensureArray = (unprocessedSteps) => isInt(unprocessedSteps) ? times(() => 1, unprocessedSteps) : unprocessedSteps; const ensureObjectsAndProcess = (steps) => steps.map((step) => { if (isPlainObj(step)) { return { ...step, value: getProcessedStepValue(step.value) }; } else { return { value: getProcessedStepValue(step) }; } }); const insertGutters = (steps, gutter) => { const lastStepIndex = steps.length - 1; const hasGutter = isGutterNonZero(gutter); return steps.reduce((acc, step, idx) => { const isLastStep = idx === lastStepIndex; const nextStep = steps[idx + 1]; const hasNextStep = !isNil(nextStep); const nextStepIsGutter = hasNextStep && nextStep.isGutter; const shouldAddGutter = hasGutter && !isLastStep && !step.isGutter && !nextStepIsGutter; return shouldAddGutter ? [...acc, step, { value: gutter, isGutter: true }] : [...acc, step]; }, []); }; const addStepRatioValues = (steps) => steps.reduce((total, step) => { const { value } = step; return isNumber(value) ? total + value : total; }, 0); const addStepAbsoluteValues = (steps) => steps.reduce((acc, step) => { const { value } = step; return isPixelNumberString(value) ? acc + getPixelStringNumericComponent(value) : acc; }, 0); const getCounts = (columns, rows) => ({ columnsTotalCount: columns.length, rowsTotalCount: rows.length }); const getTotalRatioValues = (columns, rows) => ({ columnsTotalRatioValue: addStepRatioValues(columns), rowsTotalRatioValue: addStepRatioValues(rows) }); const getAbsoluteRatio = (value, totalLength) => { return getPixelStringNumericComponent(value) / totalLength; }; const getNonAbsoluteSpaceRatios = (columns, rows, curveLengths) => { const totalAbsoluteSizeU = addStepAbsoluteValues(columns); const totalAbsoluteSizeV = addStepAbsoluteValues(rows); const totalAbsoluteRatioTop = totalAbsoluteSizeU / curveLengths.top; const totalAbsoluteRatioBottom = totalAbsoluteSizeU / curveLengths.bottom; const totalAbsoluteRatioLeft = totalAbsoluteSizeV / curveLengths.left; const totalAbsoluteRatioRight = totalAbsoluteSizeV / curveLengths.right; const nonAbsoluteSpaceTopRatio = Math.max(1 - totalAbsoluteRatioTop, 0); const nonAbsoluteSpaceBottomRatio = Math.max(1 - totalAbsoluteRatioBottom, 0); const nonAbsoluteSpaceLeftRatio = Math.max(1 - totalAbsoluteRatioLeft, 0); const nonAbsoluteSpaceRightRatio = Math.max(1 - totalAbsoluteRatioRight, 0); return { nonAbsoluteSpaceTopRatio, nonAbsoluteSpaceBottomRatio, nonAbsoluteSpaceLeftRatio, nonAbsoluteSpaceRightRatio }; }; const getSize = (step, totalNonAbsoluteValue, sideData) => { if (isPixelNumberString(step.value)) { return getAbsoluteRatio(step.value, sideData.curveLength); } const stepRatio = step.value / totalNonAbsoluteValue; return stepRatio * sideData.nonAbsoluteRatio; }; function getPixelStringNumericComponent(value) { return Number(value.split(`px`)[0]); } const isGutterNonZero = (gutter) => isNumber(gutter) && gutter > 0 || isPixelNumberString(gutter); const getEndValues = (step, totalRatioValue, sideData, sideDataOpposite) => { const size = getSize(step, totalRatioValue, sideData); const uOppositeSize = getSize(step, totalRatioValue, sideDataOpposite); const end = sideData.start + size; const oppositeEnd = sideDataOpposite.start + uOppositeSize; return [end, oppositeEnd]; }; const processSteps = ({ steps, gutter }) => { const stepsArray = ensureArray(steps); const stepsArrayOfObjs = ensureObjectsAndProcess(stepsArray); return insertGutters(stepsArrayOfObjs, gutter); }; const getStepData = (columns, rows, curveLengths) => { const processedColumns = columns; const processedRows = rows; return { ...getCounts(columns, rows), ...getTotalRatioValues(columns, rows), ...getNonAbsoluteSpaceRatios(columns, rows, curveLengths), processedColumns, processedRows }; }; const getNonGutterSteps = (steps) => steps.filter((step) => !step.isGutter); const BINOMIAL_COEFFICIENTS = [[1], [1, 1]]; const roundToN = (n, value) => Number(value.toFixed(n)); const roundTo5 = (value) => roundToN(5, value); const binomial = (n, k) => { if (n === 0) { return 1; } const lut = BINOMIAL_COEFFICIENTS; while (n >= lut.length) { const lutLength = lut.length; const nextRow = [1]; for (let i = 1, prev = lutLength - 1; i < lutLength; i++) { nextRow[i] = lut[prev][i - 1] + lut[prev][i]; } nextRow[lutLength] = 1; lut.push(nextRow); } return lut[n][k]; }; const CURVE_NAMES = [`top`, `right`, `bottom`, `left`]; const getPointsAreSame = (point1, point2) => { const roundedPoint1 = mapObj(roundTo5, point1); const roundedPoint2 = mapObj(roundTo5, point2); return roundedPoint1.x === roundedPoint2.x && roundedPoint1.y === roundedPoint2.y; }; const isValidPoint = (point) => isPlainObj(point) && isNumber(point.x) && isNumber(point.y); const validateCurve = (curve, name) => { if (!isPlainObj(curve)) { throw new Error(`Curve '${name}' must be an object`); } }; const validateFunction = (func, name) => { if (!isFunction(func)) { throw new ValidationError(`${name} must be a function`); } }; const validateStepNumber = (value) => { if (value < 0) { throw new ValidationError( `If step is a number, it must be a positive integer, but was '${value}'` ); } }; const validatePixelString = (value) => { const numericPortion = getPixelStringNumericComponent(value); validateStepNumber(numericPortion); }; const validateStep = (value) => { if (isNumber(value)) { validateStepNumber(value); } else if (isPixelNumberString(value)) { validatePixelString(value); } else if (isPlainObj(value)) { validateStepObj(value); } else { throw new ValidationError( `A step value must be a non-negative number or a pixel string, but it was '${value}'` ); } }; const validateStepObj = (step) => { validateStep(step.value); }; const validateColumnsAndRows = (columns, rows) => { if (isInt(columns)) { validateStepNumber(columns); } else if (isArray(columns)) { columns.map((column) => { validateStep(column); }, columns); } else { throw new ValidationError( `columns must be an integer or an array, but it was '${columns}'` ); } if (isInt(rows)) { validateStepNumber(rows); } else if (isArray(rows)) { rows.map((row) => { validateStep(row); }, rows); } else { throw new ValidationError( `rows must be an integer or an array, but it was '${columns}'` ); } }; const validateStartAndEndPoints = ({ startPoint, endPoint }, name) => { if (!isValidPoint(startPoint)) { throw new Error(`Bounding curve '${name}' startPoint must be a valid point`); } if (!isValidPoint(endPoint)) { throw new Error(`Bounding curve '${name}' endPoint must be a valid point`); } }; const validateCurves = (boundingCurves) => { CURVE_NAMES.map((name) => { const curve = boundingCurves[name]; validateCurve(curve, name); validateStartAndEndPoints(curve, name); }); }; const validateCornerPoints = (boundingCurves) => { if (!getPointsAreSame( boundingCurves.top.startPoint, boundingCurves.left.startPoint )) { throw new ValidationError( `top curve startPoint and left curve startPoint must have same coordinates` ); } if (!getPointsAreSame( boundingCurves.bottom.startPoint, boundingCurves.left.endPoint )) { throw new ValidationError( `bottom curve startPoint and left curve endPoint must have the same coordinates` ); } if (!getPointsAreSame( boundingCurves.top.endPoint, boundingCurves.right.startPoint )) { throw new ValidationError( `top curve endPoint and right curve startPoint must have the same coordinates` ); } if (!getPointsAreSame( boundingCurves.bottom.endPoint, boundingCurves.right.endPoint )) { throw new ValidationError( `bottom curve endPoint and right curve endPoint must have the same coordinates` ); } }; const validateBoundingCurves = (boundingCurves) => { if (isNil(boundingCurves)) { throw new ValidationError(`You must supply boundingCurves(Object)`); } if (!isPlainObj(boundingCurves)) { throw new ValidationError(`boundingCurves must be an object`); } validateCurves(boundingCurves); validateCornerPoints(boundingCurves); }; const validateGutterNumber = (gutter) => { if (gutter < 0) { throw new ValidationError( `Gutter must be a positive number, but was '${gutter}'` ); } }; const validateGutter = (gutter) => { if (isNumber(gutter)) { validateGutterNumber(gutter); } else if (isPixelNumberString(gutter)) { const numericPortion = getPixelStringNumericComponent(gutter); validateGutterNumber(numericPortion); } else { throw new ValidationError( `Gutter must be a number, or a pixel string, but was '${gutter}'` ); } }; const validateGridDefinition = (gridDefinition) => { const { rows, columns, gutter, interpolationStrategy, lineStrategy, precision } = gridDefinition; if (isNil(columns)) { throw new ValidationError(`You must supply grid.columns(Array or Int)`); } if (!isArray(columns) && !isInt(columns)) { throw new ValidationError( `grid.columns must be an Int, an Array of Ints and/or pixel strings, or an Array of objects` ); } if (isNil(rows)) { throw new ValidationError(`You must supply grid.rows(Array or Int)`); } if (!isArray(rows) && !isInt(rows)) { throw new ValidationError( `grid.rows must be an Int, an Array of Ints and/or pixel strings, or an Array of objects` ); } validateColumnsAndRows(columns, rows); if (!isNil(gutter)) { if (isArray(gutter)) { if (gutter.length > 2) { throw new ValidationError( `if grid.gutters is an Array it must have a length of 1 or 2` ); } gutter.map(validateGutter); } else { if (!isNumber(gutter) && !isPixelNumberString(gutter)) { throw new ValidationError( `grid.gutters must be an Int, a pixel-string, or an Array of Ints and/or pixel-strings` ); } validateGutter(gutter); } } if (!isUndefined(interpolationStrategy)) { if (isFunction(interpolationStrategy)) { validateFunction(interpolationStrategy, `interpolationStrategy`); } else if (isArray(interpolationStrategy)) { validateFunction(interpolationStrategy[0], `interpolationStrategyU`); validateFunction(interpolationStrategy[1], `interpolationStrategyV`); } else { const possibleValues = Object.values(InterpolationStrategy); if (!possibleValues.includes(interpolationStrategy)) { throw new ValidationError( `Interpolation strategy '${interpolationStrategy}' is not recognised. Must be one of '${possibleValues}'` ); } } } if (!isUndefined(lineStrategy)) { if (isArray(lineStrategy)) { validateFunction(lineStrategy[0], `lineStrategyU`); validateFunction(lineStrategy[1], `lineStrategyV`); } else { const possibleValues = Object.values(LineStrategy); if (!possibleValues.includes(lineStrategy)) { throw new ValidationError( `Line strategy '${lineStrategy}' is not recognised. Must be one of '${possibleValues}'` ); } } } if (!isUndefined(precision)) { if (!isInt(precision) || precision < 1) { throw new ValidationError( `Precision must be a positive integer greater than 0, but was '${precision}'` ); } } }; const validateGetPointArguments = (params) => { mapObj((value, name) => { if (value < 0 || value > 1) { throw new ValidationError( `${name} must be between 0 and 1, but was '${value}'` ); } }, params); }; const validateGetGridSquareArguments = (x, y, columns, rows) => { const columnCount = columns.length; const rowCount = rows.length; if (x < 0 || y < 0) { throw new ValidationError( `Coordinates must not be negative. You supplied x:'${x}' x y:'${y}'` ); } if (x >= columnCount) { throw new ValidationError( `X is zero-based and cannot be greater than number of columns - 1. Grid is '${columnCount}' columns wide and you passed x:'${x}'` ); } if (y >= rowCount) { throw new ValidationError( `Y is zero-based and cannot be greater than number of rows - 1. Grid is '${rowCount}' rows wide and you passed y:'${y}'` ); } }; const validateGetAllCellBoundsArguments = (makeBoundsCurvesSequential, cellBoundsOrder) => { if (!isBoolean(makeBoundsCurvesSequential)) { throw new ValidationError(`makeBoundsCurvesSequential must be a boolean`); } if (!Object.values(CellBoundsOrder).includes(cellBoundsOrder)) { throw new ValidationError( `cellBoundsOrder must be one of ${Object.values(CellBoundsOrder)}` ); } }; const getRatioMatrix = (ratios) => { const ratioLength = ratios.length; const data = times((i) => ratios.map((v) => v ** i), ratioLength); const tMatrix = matrix(data); const tMatrixTransposed = matrix(tMatrix.trans()); return { tMatrix, tMatrixTransposed }; }; const getBasisMatrix = (numberOfPoints) => { let basisMatrix = matrix(matrix.gen(0).size(numberOfPoints, numberOfPoints)); for (let i = 0; i < numberOfPoints; i++) { const value = binomial(numberOfPoints - 1, i); const ret = basisMatrix.set(i, i).to(value); basisMatrix = matrix(ret); } for (let c = 0, r; c < numberOfPoints; c++) { for (r = c + 1; r < numberOfPoints; r++) { const sign = (r + c) % 2 === 0 ? 1 : -1; const value = binomial(r, c) * basisMatrix(r, r); basisMatrix = matrix(basisMatrix.set(r, c).to(sign * value)); } } return basisMatrix; }; const getBezierFromCurve = ({ startPoint, endPoint, controlPoint1, controlPoint2 }) => new bezierJs.Bezier( startPoint.x, startPoint.y, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y ); const pointsToCurve = (points) => ({ startPoint: points[0], controlPoint1: points[1], controlPoint2: points[2], endPoint: points[3] }); const fitCubicBezierToPoints = (points, ratios) => { const numberOfPoints = points.length; const { tMatrix, tMatrixTransposed } = getRatioMatrix(ratios); const basisMatrix = getBasisMatrix(numberOfPoints); const basisMatrixInverted = matrix(basisMatrix.inv()); const ratioMultipliedMatrix = matrix(tMatrix.prod(tMatrixTransposed)); const invertedRatioMultipliedMatrix = matrix(ratioMultipliedMatrix.inv()); const step1 = matrix(invertedRatioMultipliedMatrix.prod(tMatrix)); const step2 = matrix(basisMatrixInverted.prod(step1)); const X = matrix(points.map((v) => [v.x])); const x = step2.prod(X); const Y = matrix(points.map((v) => [v.y])); const y = step2.prod(Y); const result = x.map((r, i) => ({ x: r[0], y: y[i][0] })); return pointsToCurve(result); }; function getBezierCurveLength(curve) { return getBezierFromCurve(curve).length(); } const getCurveReversed = (curve) => { return { startPoint: curve.endPoint, endPoint: curve.startPoint, controlPoint1: curve.controlPoint2, controlPoint2: curve.controlPoint1 }; }; const getAreStepsVerticalFirst = (cellBoundsOrder) => [ CellBoundsOrder.TTB_LTR, CellBoundsOrder.TTB_RTL, CellBoundsOrder.BTT_LTR, CellBoundsOrder.BTT_RTL ].includes(cellBoundsOrder); const getIsOuterReversed = (cellBoundsOrder, isVerticalFirst) => { const reversedEntries = isVerticalFirst ? [CellBoundsOrder.BTT_LTR, CellBoundsOrder.BTT_RTL] : [CellBoundsOrder.RTL_TTB, CellBoundsOrder.RTL_BTT]; return reversedEntries.includes(cellBoundsOrder); }; const getIsInnerReversed = (cellBoundsOrder, isVerticalFirst) => { const reversedEntries = isVerticalFirst ? [CellBoundsOrder.TTB_RTL, CellBoundsOrder.BTT_RTL] : [CellBoundsOrder.RTL_BTT, CellBoundsOrder.LTR_BTT]; return reversedEntries.includes(cellBoundsOrder); }; const clampT = (t) => Math.min(Math.max(t, 0), 1); const mapClampT = (o) => mapObj(clampT, o); const previousWasAlsoGutter = (steps, stepIdx) => !!(stepIdx > 0 && steps[stepIdx - 1].isGutter && steps[stepIdx] && steps[stepIdx].isGutter); const getStepIdxIncludingGutters = (stepIdx, steps) => steps.reduce( (acc, step, idx) => { if (acc.isComplete) { return acc; } if (step.isGutter) { return acc; } else { if (acc.value === stepIdx) { return { value: idx, isComplete: true }; } return { value: acc.value + 1 }; } }, { value: 0 } ).value; const getAllCellCornerPoints = (curves) => curves.reduce((acc, items) => { const subItems = items.reduce( (acc2, curve) => [ ...acc2, curve.startPoint, curve.endPoint ], [] ); return [...acc, ...subItems]; }, []); const iterateOverSteps = (rows, columns, cellBoundsOrder, getBoundingCurves) => { const nonGutterRows = getNonGutterSteps(rows); const nonGutterColumns = getNonGutterSteps(columns); const isVerticalFirst = getAreStepsVerticalFirst(cellBoundsOrder); const outerSteps = isVerticalFirst ? nonGutterRows : nonGutterColumns; const innerSteps = isVerticalFirst ? nonGutterColumns : nonGutterRows; const outerStepsLength = outerSteps.length; const innerStepsLength = innerSteps.length; const isOuterReversed = getIsOuterReversed(cellBoundsOrder, isVerticalFirst); const isInnerReversed = getIsInnerReversed(cellBoundsOrder, isVerticalFirst); const items = []; for (let i = 0; i < outerStepsLength; i++) { const outerIdxResolved = isOuterReversed ? outerStepsLength - i - 1 : i; for (let j = 0; j < innerStepsLength; j++) { const innerIdxResolved = isInnerReversed ? innerStepsLength - j - 1 : j; const rowIdx = isVerticalFirst ? innerIdxResolved : outerIdxResolved; const columnIdx = isVerticalFirst ? outerIdxResolved : innerIdxResolved; items.push(getBoundingCurves(rowIdx, columnIdx)); } } return items; }; const getApi = (boundingCurves, columns, rows, { interpolatePointOnCurveU, interpolatePointOnCurveV, interpolateLineU, interpolateLineV }) => { const curveLengths = { top: getBezierCurveLength(boundingCurves.top), left: getBezierCurveLength(boundingCurves.left), bottom: getBezierCurveLength(boundingCurves.bottom), right: getBezierCurveLength(boundingCurves.right) }; const { processedColumns, processedRows, columnsTotalCount, rowsTotalCount, columnsTotalRatioValue, rowsTotalRatioValue, nonAbsoluteSpaceLeftRatio, nonAbsoluteSpaceRightRatio, nonAbsoluteSpaceTopRatio, nonAbsoluteSpaceBottomRatio } = getStepData(columns, rows, curveLengths); const getPoint = memoize((params) => { validateGetPointArguments(params); return coonsPatch.coonsPatch(boundingCurves, params, { interpolatePointOnCurveU, interpolatePointOnCurveV }); }); const getLinesXAxis = memoize(() => { const curves = []; let vStart = 0; let vOppositeStart = 0; if (columnsTotalCount === 1 && rowsTotalCount === 1) { return [[boundingCurves.top], [boundingCurves.bottom]]; } for (let rowIdx = 0; rowIdx <= rowsTotalCount; rowIdx++) { const lineSections = []; let uStart = 0; let uOppositeStart = 0; const row = processedRows[rowIdx]; processedColumns.map((column) => { const [uEnd, uOppositeEnd] = getEndValues( column, columnsTotalRatioValue, { start: uStart, curveLength: curveLengths.top, nonAbsoluteRatio: nonAbsoluteSpaceTopRatio }, { start: uOppositeStart, curveLength: curveLengths.bottom, nonAbsoluteRatio: nonAbsoluteSpaceBottomRatio } ); const isFirstRowAndRowIsGutter = rowIdx === 0 && row.isGutter; if (!column.isGutter && !isFirstRowAndRowIsGutter && !previousWasAlsoGutter(rows, rowIdx)) { const paramsClamped = mapClampT({ uStart, uEnd, vStart, uOppositeStart, uOppositeEnd, vOppositeStart }); const curve = interpolateLineU( boundingCurves, paramsClamped, interpolatePointOnCurveU, interpolatePointOnCurveV ); lineSections.push(curve); } uStart = uEnd; uOppositeStart = uOppositeEnd; }); curves.push(lineSections); if (rowIdx < rowsTotalCount) { const row2 = processedRows[rowIdx]; const [vEnd, vOppositeEnd] = getEndValues( row2, rowsTotalRatioValue, { start: vStart, curveLength: curveLengths.left, nonAbsoluteRatio: nonAbsoluteSpaceLeftRatio }, { start: vOppositeStart, curveLength: curveLengths.right, nonAbsoluteRatio: nonAbsoluteSpaceRightRatio } ); vStart = vEnd; vOppositeStart = vOppositeEnd; } } return curves; }); const getLinesYAxis = memoize(() => { const curves = []; let uStart = 0; let uOppositeStart = 0; if (columnsTotalCount === 1 && rowsTotalCount === 1) { return [[boundingCurves.left], [boundingCurves.right]]; } for (let columnIdx = 0; columnIdx <= columnsTotalCount; columnIdx++) { const lineSections = []; let vStart = 0; let vOppositeStart = 0; const column = processedColumns[columnIdx]; processedRows.map((row) => { const [vEnd, vOppositeEnd] = getEndValues( row, rowsTotalRatioValue, { start: vStart, curveLength: curveLengths.left, nonAbsoluteRatio: nonAbsoluteSpaceLeftRatio }, { start: vOppositeStart, curveLength: curveLengths.right, nonAbsoluteRatio: nonAbsoluteSpaceRightRatio } ); const isFirstColumnAndColumnIsGutter = columnIdx === 0 && column.isGutter; if (!row.isGutter && !isFirstColumnAndColumnIsGutter && !previousWasAlsoGutter(columns, columnIdx)) { const paramsClamped = mapClampT({ vStart, vEnd, uStart, vOppositeStart, vOppositeEnd, uOppositeStart }); const curve = interpolateLineV( boundingCurves, paramsClamped, interpolatePointOnCurveU, interpolatePointOnCurveV ); lineSections.push(curve); } vStart = vEnd; vOppositeStart = vOppositeEnd; }); curves.push(lineSections); if (columnIdx < columnsTotalCount) { const column2 = processedColumns[columnIdx]; const [uEnd, uOppositeEnd] = getEndValues( column2, columnsTotalRatioValue, { start: uStart, curveLength: curveLengths.top, nonAbsoluteRatio: nonAbsoluteSpaceTopRatio }, { start: uOppositeStart, curveLength: curveLengths.bottom, nonAbsoluteRatio: nonAbsoluteSpaceBottomRatio } ); uStart = uEnd; uOppositeStart = uOppositeEnd; } } return curves; }); const getLines = memoize(() => { return { xAxis: getLinesXAxis(), yAxis: getLinesYAxis() }; }); const getIntersections = memoize(() => { const { xAxis } = getLines(); const intersections = getAllCellCornerPoints(xAxis); return intersections; }); const getCellBounds = memoize( (columnIdx, rowIdx, { makeBoundsCurvesSequential = false } = {}) => { const nonGutterColumns = getNonGutterSteps(columns); const nonGutterRows = getNonGutterSteps(rows); validateGetGridSquareArguments( columnIdx, rowIdx, nonGutterColumns, nonGutterRows ); const rowIdxIncludingGutters = getStepIdxIncludingGutters( rowIdx, processedRows ); const columnIdxIncludingGutters = getStepIdxIncludingGutters( columnIdx, processedColumns ); const { xAxis, yAxis } = getLines(); const top = xAxis[rowIdxIncludingGutters][columnIdx]; const bottom = xAxis[rowIdxIncludingGutters + 1][columnIdx]; const left = yAxis[columnIdxIncludingGutters][rowIdx]; const right = yAxis[columnIdxIncludingGutters + 1][rowIdx]; return { meta: { row: rowIdx, column: columnIdx }, top, bottom: makeBoundsCurvesSequential ? getCurveReversed(bottom) : bottom, left: makeBoundsCurvesSequential ? getCurveReversed(left) : left, right }; } ); const getAllCellBounds = memoize( ({ makeBoundsCurvesSequential = false, cellBoundsOrder = CellBoundsOrder.TTB_LTR } = {}) => { validateGetAllCellBoundsArguments( makeBoundsCurvesSequential, cellBoundsOrder ); return iterateOverSteps( rows, columns, cellBoundsOrder, (rowIdx, columnIdx) => getCellBounds(rowIdx, columnIdx, { makeBoundsCurvesSequential }) ); } ); return { getPoint, getLinesXAxis, getLinesYAxis, getLines, getIntersections, getCellBounds, getAllCellBounds }; }; const interpolateStraightLineU = (boundingCurves, { uStart, uEnd, uOppositeStart, uOppositeEnd, vStart, vOppositeStart }, interpolatePointOnCurveU, interpolatePointOnCurveV) => { const startPoint = coonsPatch.coonsPatch( boundingCurves, { u: uStart, v: vStart, uOpposite: uOppositeStart, vOpposite: vOppositeStart }, { interpolatePointOnCurveU, interpolatePointOnCurveV } ); const endPoint = coonsPatch.coonsPatch( boundingCurves, { u: uEnd, v: vStart, uOpposite: uOppositeEnd, vOpposite: vOppositeStart }, { interpolatePointOnCurveU, interpolatePointOnCurveV } ); return { startPoint, endPoint, controlPoint1: startPoint, controlPoint2: endPoint }; }; const interpolateStraightLineV = (boundingCurves, { vStart, vEnd, vOppositeStart, vOppositeEnd, uStart, uOppositeStart }, interpolatePointOnCurveU, interpolatePointOnCurveV) => { const startPoint = coonsPatch.coonsPatch( boundingCurves, { u: uStart, v: vStart, uOpposite: uOppositeStart, vOpposite: vOppositeStart }, { interpolatePointOnCurveU, interpolatePointOnCurveV } ); const endPoint = coonsPatch.coonsPatch( boundingCurves, { u: uStart, v: vEnd, uOpposite: uOppositeStart, vOpposite: vOppositeEnd }, { interpolatePointOnCurveU, interpolatePointOnCurveV } ); return { startPoint, endPoint, controlPoint1: startPoint, controlPoint2: endPoint }; }; const T_MIDPOINT_1 = 0.25; const T_MIDPOINT_2 = 0.75; const interpolateCurveU = (boundingCurves, { uStart, uEnd, vStart, vOppositeStart, uOppositeEnd, uOppositeStart }, interpolatePointOnCurveU, interpolatePointOnCurveV) => { const uSize = uEnd - uStart; const uOppositeSize = uOppositeEnd - uOppositeStart; const { startPoint, endPoint } = interpolateStraightLineU( boundingCurves, { uStart, uEnd, vStart, vOppositeStart, uOppositeEnd, uOppositeStart }, interpolatePointOnCurveU, interpolatePointOnCurveV ); const midPoint1 = coonsPatch.coonsPatch( boundingCurves, { u: uStart + uSize * T_MIDPOINT_1, v: vStart, uOpposite: uOppositeStart + uOppositeSize * T_MIDPOINT_1, vOpposite: vOppositeStart }, { interpolatePointOnCurveU, interpolatePointOnCurveV } ); const midPoint2 = coonsPatch.coonsPatch( boundingCurves, { u: uStart + uSize * T_MIDPOINT_2, v: vStart, uOpposite: uOppositeStart + uOppositeSize * T_MIDPOINT_2, vOpposite: vOppositeStart }, { interpolatePointOnCurveU, interpolatePointOnCurveV } ); return fitCubicBezierToPoints( [startPoint, midPoint1, midPoint2, endPoint], [0, T_MIDPOINT_1, T_MIDPOINT_2, 1] ); }; const interpolateCurveV = (boundingCurves, { vStart, vEnd, uStart, uOppositeStart, vOppositeEnd, vOppositeStart }, interpolatePointOnCurveU, interpolatePointOnCurveV) => { const vSize = vEnd - vStart; const vOppositeSize = vOppositeEnd - vOppositeStart; const { startPoint, endPoint } = interpolateStraightLineV( boundingCurves, { vStart, vEnd, uStart, uOppositeStart, vOppositeEnd, vOppositeStart }, interpolatePointOnCurveU, interpolatePointOnCurveV ); const midPoint1 = coonsPatch.coonsPatch( boundingCurves, { u: uStart, v: vStart + vSize * T_MIDPOINT_1, uOpposite: uOppositeStart, vOpposite: vOppositeStart + vOppositeSize * T_MIDPOINT_1 }, { interpolatePointOnCurveU, interpolatePointOnCurveV } ); const midPoint2 = coonsPatch.coonsPatch( boundingCurves, { u: uStart, v: vStart + vSize * T_MIDPOINT_2, uOpposite: uOppositeStart, vOpposite: vOppositeStart + vOppositeSize * T_MIDPOINT_2 }, { interpolatePointOnCurveU, interpolatePointOnCurveV } ); return fitCubicBezierToPoints( [startPoint, midPoint1, midPoint2, endPoint], [0, T_MIDPOINT_1, T_MIDPOINT_2, 1] ); }; const interpolatePointOnCurveFactory = ([interpolatePointOnCurveUFactory, interpolatePointOnCurveVFactory], bezierEasing, precision) => { return [ interpolatePointOnCurveUFactory({ precision, bezierEasing: bezierEasing.xAxis }), interpolatePointOnCurveVFactory({ precision, bezierEasing: bezierEasing.yAxis }) ]; }; const getInterpolationStrategy = ({ interpolationStrategy = InterpolationStrategy.EVEN, precision, bezierEasing }) => { if (isFunction(interpolationStrategy)) { return interpolatePointOnCurveFactory( [interpolationStrategy, interpolationStrategy], bezierEasing, precision ); } if (isArray(interpolationStrategy)) { return interpolatePointOnCurveFactory( interpolationStrategy, bezierEasing, precision ); } if (interpolationStrategy === InterpolationStrategy.EVEN) { return interpolatePointOnCurveFactory( [ interpolatePointOnCurveEvenlySpacedEasedFactory, interpolatePointOnCurveEvenlySpacedEasedFactory ], bezierEasing, precision ); } if (interpolationStrategy === InterpolationStrategy.LINEAR) { return interpolatePointOnCurveFactory( [ interpolatePointOnCurveLinearEasedFactory, interpolatePointOnCurveLinearEasedFactory ], bezierEasing, precision ); } throw new ValidationError( `Unknown interpolation strategy: '${interpolationStrategy}'` ); }; const getLineStrategy = ({ lineStrategy }) => { const interpolateLineU = lineStrategy === LineStrategy.CURVES ? interpolateCurveU : interpolateStraightLineU; const interpolateLineV = lineStrategy === LineStrategy.CURVES ? interpolateCurveV : interpolateStraightLineV; return [interpolateLineU, interpolateLineV]; }; const mergeWithDefaults = (definition) => { return { ...{ gutter: 0, interpolationStrategy: InterpolationStrategy.EVEN, precision: 20, lineStrategy: LineStrategy.STRAIGHT_LINES, bezierEasing: { xAxis: [0, 0, 1, 1], yAxis: [0, 0, 1, 1] } }, ...definition }; }; const warpGrid = (boundingCurves, gridDefinition) => { const gridDefinitionWithDefaults = mergeWithDefaults(gridDefinition); validateBoundingCurves(boundingCurves); validateGridDefinition(gridDefinitionWithDefaults); const { gutter } = gridDefinitionWithDefaults; const gutterArray = isArray(gutter) ? gutter : [gutter, gutter]; const [columns, rows] = [ { steps: gridDefinitionWithDefaults.columns, gutter: gutterArray[0] }, { steps: gridDefinitionWithDefaults.rows, gutter: gutterArray[1] } ].map(processSteps); const [interpolatePointOnCurveU, interpolatePointOnCurveV] = getInterpolationStrategy(gridDefinitionWithDefaults); const [interpolateLineU, interpolateLineV] = getLineStrategy( gridDefinitionWithDefaults ); const api = getApi(boundingCurves, columns, rows, { interpolatePointOnCurveU, interpolatePointOnCurveV, interpolateLineU, interpolateLineV }); return { model: { boundingCurves, columns, rows, columnsNonGutter: getNonGutterSteps(columns), rowsNonGutter: getNonGutterSteps(rows) }, ...api }; }; exports.CellBoundsOrder = CellBoundsOrder; exports.InterpolationStrategy = InterpolationStrategy; exports.LineStrategy = LineStrategy; exports.interpolatePointOnCurveEvenlySpacedFactory = interpolatePointOnCurveEvenlySpacedEasedFactory; exports.interpolatePointOnCurveLinearFactory = interpolatePointOnCurveLinearEasedFactory; exports.warpGrid = warpGrid; //# sourceMappingURL=index.cjs.map