rendering/layout.js

/**
 * Computes unscaled layout values.
 *
 * @param {Layout} layout Page layout.
 * @returns {Object} Unscaled derived layout values.
 */
export function computeLayoutValues(layout) {
  const pw = Number(layout.pw) || 0;
  const ph = Number(layout.ph) || 0;
  const ratio = Number(layout.ratio) || 0;
  const b = Number(layout.b) || 0;
  const inner = (Number(layout.mInner) || 0) * b;
  const top = (Number(layout.mTop) || 0) * b;
  const bottom = (Number(layout.mBottom) || 0) * b;
  const th = ph - ((Number(layout.mTop) || 0) + (Number(layout.mBottom) || 0)) * b;
  const tw = ratio * th;
  const outer = pw - inner - tw;

  return {
    pw,
    ph,
    ratio,
    b,
    inner,
    top,
    bottom,
    th,
    tw,
    outer,
    ok: outer > 0 && th > 0 && tw > 0,
  };
}

/**
 * Computes scaled margin and page dimensions.
 *
 * @param {Layout} layout Page layout.
 * @param {number} scale Render scale.
 * @returns {Object} Scaled margin values.
 */
export function computeMargins(layout, scale) {
  const values = computeLayoutValues(layout);
  return {
    ...values,
    scale,
    pagePxW: values.pw * scale,
    pagePxH: values.ph * scale,
    innerPx: values.inner * scale,
    outerPx: values.outer * scale,
    topPx: values.top * scale,
    bottomPx: values.bottom * scale,
    twPx: values.tw * scale,
    thPx: values.th * scale,
  };
}

/**
 * Computes page, text block, overlay, and content placement rectangles for
 * one side of a spread.
 *
 * @param {Object} margins Scaled margins from {@link computeMargins}.
 * @param {"left"|"right"} sideName Spread side.
 * @param {ViewerPage|Object|null} page Page metadata or viewer page.
 * @param {number} [pageRectX=0] X coordinate for the page rectangle.
 * @returns {Object} Page geometry for rendering and overlays.
 */
export function getPageGeometry(margins, sideName, page, pageRectX = 0) {
  const isLeft = sideName === "left";
  const fitMode = page?.fitAxis === "width" || page?.fitAxis === "height" || page?.fitAxis === "inside"
    ? page.fitAxis
    : "inside";
  const pageRect = {
    x: pageRectX,
    y: 0,
    w: margins.pagePxW,
    h: margins.pagePxH,
  };
  const textblockRect = {
    x: isLeft ? pageRect.x + margins.outerPx : pageRect.x + margins.innerPx,
    y: margins.topPx,
    w: margins.twPx,
    h: margins.thPx,
  };
  const isCover = !!page?.cover;
  const isSpread = !!page?.spread && !isCover;
  const effectiveAlignX = page?.contentAlignX
    || (isSpread ? (isLeft ? "right" : "left") : "center");
  const effectiveAlignY = page?.contentAlignY || "top";
  const overlayRect = isSpread
    ? {
        x: isLeft ? textblockRect.x : pageRect.x,
        y: textblockRect.y,
        w: isLeft
          ? pageRect.x + pageRect.w - textblockRect.x
          : textblockRect.x + textblockRect.w - pageRect.x,
        h: textblockRect.h,
      }
    : textblockRect;

  return {
    isCover,
    isSpread,
    pageRect,
    textblockRect,
    overlayRect,
    contentRect: isCover ? pageRect : overlayRect,
    contentAlignX: effectiveAlignX === "left" ? "start" : effectiveAlignX === "right" ? "end" : "center",
    contentAlignY: effectiveAlignY === "top" ? "start" : effectiveAlignY === "bottom" ? "end" : "center",
    contentMode: isCover
      ? "fill"
      : fitMode === "width"
        ? "fit-width"
        : fitMode === "height"
          ? "fit-height"
          : "fit",
    clipContent: isCover,
    overlayVisible: !isCover,
  };
}

/**
 * Computes a scale that fits a two-page spread inside a container.
 *
 * @param {Layout} layout Page layout.
 * @param {number} containerW Container width in pixels.
 * @param {number} containerH Container height in pixels.
 * @returns {number} Render scale.
 */
export function computeScale(layout, containerW, containerH) {
  return Math.min((containerW - 64) / (2 * layout.pw), (containerH - 64) / layout.ph);
}

/**
 * Computes a scale that fits one page inside a container.
 *
 * @param {Layout} layout Page layout.
 * @param {number} containerW Container width in pixels.
 * @param {number} containerH Container height in pixels.
 * @returns {number} Render scale.
 */
export function computeContentScale(layout, containerW, containerH) {
  return Math.min((containerW - 64) / layout.pw, (containerH - 64) / layout.ph);
}