All files / src/draw/align vertically.ts

100% Statements 47/47
100% Branches 14/14
100% Functions 3/3
100% Lines 47/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 1181x   1x 1x                                                                                                                               1x 1x 625x 625x 625x 625x   625x 625x   625x 4x 621x 621x 621x 1305x 1305x 1305x 1305x 1305x 1305x 621x 621x 625x   1x 3x 3x 3x 3x       286x 286x 286x 286x 286x 286x 286x 286x 286x 286x 286x 286x 286x 286x 286x 286x 286x  
import {Tuple, identity, pipe, Array, Function, Number} from '#util'
import type {EndoOf} from '#Function'
import {VStruts} from '../struts.js'
import {matchVertical, type VerticalAlignment} from './data.js'
 
/**
 * Vertically and horizontally align an array of arrays of single line
 * strings according to the given alignments.
 *
 * All columns in the returned array will be as tall as the tallest among
 * them. The given vertical struts will be used to fill the available vertical
 * space: the top and the bottom struts filling each direction respectively.
 *
 * The struts will add zero or one strings, from their `prefix`/`body`/`suffix`
 * fields to each line they create for the purpose of alignment. The rest of the
 * line will remain empty, to be filled by the horizontal alignment given
 * in `alignOrthogonal`.
 *
 * For each column we run the given `alignOrthogonal` function, where you
 * can set horizontal alignment and padding to make sure every row, per
 * column is of the same width.
 *
 * If the vertical alignment is `center`, you can configure the handling of the
 * remainder using the optional `useTopRound` argument, by default `false`.
 *
 * Remainders pop up when the height of the available space left for padding
 * around the aligned item is _odd_. By default we add the remainder to the
 * _bottom_ of what we are aligning, so that it will appear _above_ where it
 * should be.
 *
 * For example, when aligning a single row part with a a taller part that
 * renders to an _even_ number of rows, we need to decide if we add the
 * remaining space, which is an _odd_ number, above or below what is being
 * aligned.
 *
 * The default rounding it to the _bottom_, meaning the remainder is added to
 * the bottom pushing the aligned text up so it will appear _above_ the center:
 *
 * ```txt
 * Default useTopRound=false   1 ┌─────────┐
 * remainder ┌───────────────┐-2 │Tall part│
 * is added  │single row part│ 3 │with an  │
 * to shape  └───────────────┘-4 │even     │
 * bottom                      5 │height   │
 *                             6 └─────────┘
 * ```
 *
 * If you set `useTopRound` to true, or call the variant of this function
 * `alignVertically.useTopRound`, the remainder will be added _above_ the shape
 * pushing the shape down so it appears _below_ the center:
 *
 * ```txt
 * When useTopRound=true       1 ┌─────────┐
 * remainder is added          2 │Tall part│
 * to shape  ┌───────────────┐-3 │with an  │
 * top       │single row part│ 4 │even     │
 *           └───────────────┘-5 │height   │
 *                             6 └─────────┘
 * ```
 *
 * @param vStruts Top and bottom vertical struts will be used to fill available space.
 * @param vAlign A vertical alignment will be used when not all shapes are of the same height.
 * @param alignOrthogonal A function that accepts a list of strings at different widths and returns them at the width of the widest, respecting alignments and struts.
 * @param useTopRound Optional flag determining if remainder is added above or below the shape. By default it is `false` and the remainder is added below the shape.
 * @category drawing
 * @function
 */
export const alignVertically =
  (
    vStruts: VStruts,
    vAlign: VerticalAlignment,
    alignOrthogonal: Function.EndoOf<string[]>,
    useTopRound = false,
  ): EndoOf<Array.NonEmptyArray<string[]>> =>
  columns => {
    const available = Array.longestChildLength(columns)
 
    return available === 0
      ? columns
      : pipe(
          columns,
          Array.map(column => {
            const Δ = available - column.length
            return pipe(
              column,
              Δ <= 0 ? identity : expand(vStruts, vAlign, Δ, useTopRound),
              alignOrthogonal,
            )
          }),
        )
  }
 
alignVertically.useTopRound = (
  vStruts: VStruts,
  vAlign: VerticalAlignment,
  alignOrthogonal: Function.EndoOf<string[]>,
) => alignVertically(vStruts, vAlign, alignOrthogonal, true)
 
// Expand column vertically by given delta according to given alignment,
// filling with as possible of the vertical strut.
function expand(
  vStruts: VStruts,
  align: VerticalAlignment,
  Δ: number,
  useTopRound = false,
): Function.EndoOf<string[]> {
  return pipe(
    align,
    matchVertical<[number, number]>(
      () => [Δ, 0],
      () => Number[useTopRound ? 'halfToFirst' : 'halfToSecond'](Δ),
      () => [0, Δ],
    ),
    Tuple.swap,
    VStruts.fill(vStruts),
  )
}