> ## Documentation Index
> Fetch the complete documentation index at: https://resources.latex-cloud-studio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Understanding TeX Boxes with LuaTeX - Advanced Guide

> Deep dive into TeX's box model using LuaTeX. Learn how LaTeX creates pages, debug layout issues, and manipulate boxes programmatically.

export const RenderedOutput = ({title = "Rendered output", ctaHref, ctaLabel = "Open LaTeX Cloud Studio", children}) => {
  const [isExpanded, setIsExpanded] = useState(false);
  const trackEditorCta = () => {
    const target = new URL(ctaHref, window.location.href);
    globalThis.posthog?.capture?.("docs_app_cta_clicked", {
      source_page: window.location.pathname,
      source_section: "rendered_output",
      cta_variant: "first_compiled_example",
      target_url: target.toString(),
      target_utm_source: target.searchParams.get("utm_source"),
      target_utm_medium: target.searchParams.get("utm_medium"),
      target_utm_campaign: target.searchParams.get("utm_campaign"),
      target_utm_content: target.searchParams.get("utm_content")
    }, {
      transport: "sendBeacon",
      send_instantly: true
    });
  };
  return <details className="rendered-output" onToggle={event => setIsExpanded(event.currentTarget.open)}>
      <summary className="rendered-output__summary">
        <span className="rendered-output__title">{title}</span>
        <span className="rendered-output__hint" aria-hidden="true">View compiled result</span>
      </summary>
      {isExpanded && <div className="rendered-output__content">
          {children}
          {ctaHref && <aside className="rendered-output__cta" aria-label="Continue in the LaTeX editor">
              <span>
                <strong>Ready to use this syntax?</strong>
                Continue in the browser editor when you want to adapt the example in a real project.
              </span>
              <a href={ctaHref} onClick={trackEditorCta}>{ctaLabel}<span aria-hidden="true"> →</span></a>
            </aside>}
        </div>}
    </details>;
};

export const LatexSource = ({filename, source}) => {
  const [copyStatus, setCopyStatus] = useState("Copy");
  const copySource = async () => {
    try {
      await navigator.clipboard.writeText(source);
      setCopyStatus("Copied");
    } catch {
      setCopyStatus("Select and copy");
    }
  };
  return <figure className="latex-source">
      <figcaption className="latex-source__header">
        <span className="latex-source__filename">{filename}</span>
        <button type="button" className="latex-source__copy" onClick={copySource} aria-live="polite">
          {copyStatus}
        </button>
      </figcaption>
      <pre className="latex-source__pre" aria-label={`LaTeX source: ${filename}`} tabIndex="0">
        <code className="language-latex">{source}</code>
      </pre>
    </figure>;
};

export const LatexPreview = ({src, alt, caption, width, height}) => {
  const minZoom = 1;
  const maxZoom = 3;
  const zoomStep = 0.5;
  const measureSvgContent = async (assetSrc, pageWidth, pageHeight) => {
    const cacheKey = "__latexCloudSvgContentBoxCache";
    const contentBoxCache = globalThis[cacheKey] ?? new Map();
    globalThis[cacheKey] = contentBoxCache;
    if (contentBoxCache.has(assetSrc)) return contentBoxCache.get(assetSrc);
    const measurement = (async () => {
      const assetUrl = new URL(assetSrc, window.location.href);
      if (assetUrl.origin !== window.location.origin) {
        throw new Error("Rendered output must use a same-origin SVG asset.");
      }
      const response = await fetch(assetUrl, {
        credentials: "same-origin"
      });
      if (!response.ok) throw new Error(`Rendered output request failed with ${response.status}.`);
      const source = await response.text();
      const documentNode = new DOMParser().parseFromString(source, "image/svg+xml");
      if (documentNode.querySelector("parsererror")) throw new Error("Rendered output is not valid SVG.");
      const sourceSvg = documentNode.documentElement;
      sourceSvg.querySelectorAll("script, foreignObject").forEach(node => node.remove());
      [sourceSvg, ...sourceSvg.querySelectorAll("*")].forEach(node => {
        [...node.attributes].forEach(attribute => {
          if ((/^on/i).test(attribute.name)) node.removeAttribute(attribute.name);
          if ((attribute.name === "href" || attribute.name === "xlink:href") && !attribute.value.startsWith("#")) {
            node.removeAttribute(attribute.name);
          }
        });
      });
      const measurementHost = document.createElement("div");
      measurementHost.className = "latex-preview__measurement-host";
      const measuredSvg = document.importNode(sourceSvg, true);
      measuredSvg.setAttribute("aria-hidden", "true");
      measurementHost.appendChild(measuredSvg);
      document.body.appendChild(measurementHost);
      try {
        const measuredElements = [...measuredSvg.children].filter(node => !["defs", "desc", "metadata", "style", "title"].includes(node.tagName.toLowerCase()));
        const elementBounds = measuredElements.map(node => node.getBBox()).filter(box => [box.x, box.y, box.width, box.height].every(Number.isFinite) && box.width > 0 && box.height > 0);
        if (elementBounds.length === 0) {
          throw new Error("Rendered output has no measurable visible content.");
        }
        const sortedBounds = [...elementBounds].sort((left, right) => left.y - right.y);
        const clusterGap = pageHeight * 0.045;
        const clusters = [];
        sortedBounds.forEach(box => {
          const current = clusters[clusters.length - 1];
          if (!current || box.y - current.bottom > clusterGap) {
            clusters.push({
              boxes: [box],
              bottom: box.y + box.height
            });
            return;
          }
          current.boxes.push(box);
          current.bottom = Math.max(current.bottom, box.y + box.height);
        });
        const contentClusters = clusters.filter(cluster => {
          const clusterBox = cluster.boxes.reduce((combined, box) => {
            const right = Math.max(combined.x + combined.width, box.x + box.width);
            const bottom = Math.max(combined.y + combined.height, box.y + box.height);
            const x = Math.min(combined.x, box.x);
            const y = Math.min(combined.y, box.y);
            return {
              x,
              y,
              width: right - x,
              height: bottom - y
            };
          });
          const centerY = clusterBox.y + clusterBox.height / 2;
          const isMarginFurniture = cluster.boxes.length <= 2 && clusterBox.width < pageWidth * 0.2 && clusterBox.height < pageHeight * 0.04 && (centerY < pageHeight * 0.08 || centerY > pageHeight * 0.8);
          return !isMarginFurniture;
        });
        const visibleBounds = (contentClusters.length > 0 ? contentClusters : clusters).flatMap(cluster => cluster.boxes);
        const bounds = visibleBounds.reduce((combined, box) => {
          const right = Math.max(combined.x + combined.width, box.x + box.width);
          const bottom = Math.max(combined.y + combined.height, box.y + box.height);
          const x = Math.min(combined.x, box.x);
          const y = Math.min(combined.y, box.y);
          return {
            x,
            y,
            width: right - x,
            height: bottom - y
          };
        });
        const clampValue = (value, minimum, maximum) => Math.min(maximum, Math.max(minimum, value));
        const padding = Math.max(8, Math.min(pageWidth, pageHeight) * 0.025);
        const x = clampValue(bounds.x - padding, 0, pageWidth);
        const y = clampValue(bounds.y - padding, 0, pageHeight);
        const right = clampValue(bounds.x + bounds.width + padding, 0, pageWidth);
        const bottom = clampValue(bounds.y + bounds.height + padding, 0, pageHeight);
        return {
          x,
          y,
          width: right - x,
          height: bottom - y
        };
      } finally {
        measurementHost.remove();
      }
    })();
    contentBoxCache.set(assetSrc, measurement);
    measurement.catch(() => contentBoxCache.delete(assetSrc));
    return measurement;
  };
  const renderPreviewAsset = ({contentBox: assetContentBox, loading}) => {
    if (!assetContentBox) {
      return <img className="latex-preview__asset" src={src} alt={alt} width={width} height={height} loading={loading} draggable="false" />;
    }
    return <svg className="latex-preview__asset" viewBox={`${assetContentBox.x} ${assetContentBox.y} ${assetContentBox.width} ${assetContentBox.height}`} preserveAspectRatio="xMidYMid meet" role="img" aria-label={alt}>
        <image href={src} x="0" y="0" width={width} height={height} />
      </svg>;
  };
  const [isOpen, setIsOpen] = useState(false);
  const [frameMode, setFrameMode] = useState("content");
  const [viewMode, setViewMode] = useState("fit");
  const [zoom, setZoom] = useState(minZoom);
  const [contentBox, setContentBox] = useState(null);
  const [measurementStatus, setMeasurementStatus] = useState("loading");
  const dialogRef = useRef(null);
  const closeButtonRef = useRef(null);
  const viewportRef = useRef(null);
  const previousFocusRef = useRef(null);
  const dragRef = useRef(null);
  useEffect(() => {
    let isCurrent = true;
    setMeasurementStatus("loading");
    measureSvgContent(src, width, height).then(box => {
      if (!isCurrent) return;
      setContentBox(box);
      setMeasurementStatus("ready");
    }).catch(() => {
      if (!isCurrent) return;
      setContentBox(null);
      setFrameMode("page");
      setMeasurementStatus("error");
    });
    return () => {
      isCurrent = false;
    };
  }, [height, src, width]);
  const closeViewer = useCallback(() => {
    setIsOpen(false);
  }, []);
  const openViewer = () => {
    previousFocusRef.current = document.activeElement;
    setFrameMode(contentBox ? "content" : "page");
    setViewMode("fit");
    setZoom(minZoom);
    setIsOpen(true);
  };
  const applyZoom = useCallback(nextZoom => {
    const boundedZoom = Math.min(maxZoom, Math.max(minZoom, nextZoom));
    setViewMode("custom");
    setZoom(boundedZoom);
  }, []);
  const zoomIn = useCallback(() => {
    applyZoom(viewMode === "fit" ? minZoom + zoomStep : zoom + zoomStep);
  }, [applyZoom, viewMode, zoom]);
  const zoomOut = useCallback(() => {
    applyZoom(viewMode === "fit" ? minZoom : zoom - zoomStep);
  }, [applyZoom, viewMode, zoom]);
  useEffect(() => {
    if (!isOpen) return undefined;
    const previousOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    closeButtonRef.current?.focus();
    return () => {
      document.body.style.overflow = previousOverflow;
      previousFocusRef.current?.focus?.();
    };
  }, [isOpen]);
  useEffect(() => {
    if (!isOpen) return undefined;
    const handleKeyDown = event => {
      if (event.key === "Escape") {
        event.preventDefault();
        closeViewer();
        return;
      }
      if ((event.key === "+" || event.key === "=") && !event.metaKey && !event.ctrlKey) {
        event.preventDefault();
        zoomIn();
        return;
      }
      if (event.key === "-" && !event.metaKey && !event.ctrlKey) {
        event.preventDefault();
        zoomOut();
        return;
      }
      if (event.key !== "Tab" || !dialogRef.current) return;
      const focusable = [...dialogRef.current.querySelectorAll('button:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])')];
      if (focusable.length === 0) return;
      const first = focusable[0];
      const last = focusable[focusable.length - 1];
      if (event.shiftKey && document.activeElement === first) {
        event.preventDefault();
        last.focus();
      } else if (!event.shiftKey && document.activeElement === last) {
        event.preventDefault();
        first.focus();
      }
    };
    window.addEventListener("keydown", handleKeyDown);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, [closeViewer, isOpen, zoomIn, zoomOut]);
  const startDrag = event => {
    if (event.button !== 0 || !viewportRef.current) return;
    const viewport = viewportRef.current;
    dragRef.current = {
      pointerId: event.pointerId,
      x: event.clientX,
      y: event.clientY,
      scrollLeft: viewport.scrollLeft,
      scrollTop: viewport.scrollTop
    };
    viewport.setPointerCapture(event.pointerId);
    viewport.dataset.dragging = "true";
  };
  const continueDrag = event => {
    const drag = dragRef.current;
    const viewport = viewportRef.current;
    if (!drag || !viewport || drag.pointerId !== event.pointerId) return;
    viewport.scrollLeft = drag.scrollLeft - (event.clientX - drag.x);
    viewport.scrollTop = drag.scrollTop - (event.clientY - drag.y);
  };
  const stopDrag = event => {
    const viewport = viewportRef.current;
    if (viewport?.hasPointerCapture(event.pointerId)) viewport.releasePointerCapture(event.pointerId);
    if (viewport) delete viewport.dataset.dragging;
    dragRef.current = null;
  };
  const activeContentBox = frameMode === "content" ? contentBox : null;
  const activeWidth = activeContentBox?.width ?? width;
  const activeHeight = activeContentBox?.height ?? height;
  const activeRatio = activeWidth / activeHeight;
  const inlineContentBox = measurementStatus === "ready" ? contentBox : null;
  const inlineWidth = inlineContentBox?.width ?? width;
  const inlineHeight = inlineContentBox?.height ?? height;
  const inlineGeometry = {
    aspectRatio: `${inlineWidth} / ${inlineHeight}`,
    maxWidth: `${30 * inlineWidth / inlineHeight}rem`
  };
  const imageStyle = viewMode === "fit" ? {
    aspectRatio: `${activeWidth} / ${activeHeight}`,
    width: "100%",
    maxWidth: `${Math.max(16, activeRatio * 78)}dvh`
  } : {
    aspectRatio: `${activeWidth} / ${activeHeight}`,
    width: `${zoom * 100}%`,
    maxWidth: "none"
  };
  const zoomLabel = viewMode === "fit" ? frameMode === "content" ? "Fit content" : "Full page" : `${Math.round(zoom * 100)}%`;
  return <figure className="latex-preview">
      <button type="button" className="latex-preview__trigger" onClick={openViewer} aria-haspopup="dialog" aria-label={`Open zoomable preview: ${alt}`}>
        <span className="latex-preview__page" style={inlineGeometry}>
          {measurementStatus === "loading" ? <span className="latex-preview__loading" role="status">Preparing compiled output…</span> : renderPreviewAsset({
    src,
    alt,
    width,
    height,
    contentBox: inlineContentBox,
    loading: "lazy"
  })}
        </span>
        <span className="latex-preview__trigger-label" aria-hidden="true">
          <span className="latex-preview__trigger-icon">⌕</span>
          Open viewer
        </span>
      </button>
      <figcaption className="latex-preview__caption">
        <span>
          {caption}
          {measurementStatus === "error" && <span className="latex-preview__status" role="status"> Content fit is unavailable; the complete vector page is shown.</span>}
        </span>
        <a href={src} target="_blank" rel="noreferrer" className="latex-preview__source-link">Open SVG</a>
      </figcaption>

      {isOpen && <div className="latex-preview__backdrop" onMouseDown={event => {
    if (event.target === event.currentTarget) closeViewer();
  }}>
          <section ref={dialogRef} className="latex-preview__dialog" role="dialog" aria-modal="true" aria-label={`Rendered LaTeX viewer: ${alt}`}>
            <header className="latex-preview__toolbar">
              <div className="latex-preview__identity">
                <span className="latex-preview__eyebrow">Compiled LaTeX</span>
                <span className="latex-preview__filename">{alt}</span>
              </div>
              <div className="latex-preview__controls" aria-label="Preview controls">
                <button type="button" className={frameMode === "content" && viewMode === "fit" ? "is-active" : undefined} disabled={!contentBox} onClick={() => {
    setFrameMode("content");
    setViewMode("fit");
    setZoom(minZoom);
  }}>
                  Fit content
                </button>
                <button type="button" className={frameMode === "page" && viewMode === "fit" ? "is-active" : undefined} onClick={() => {
    setFrameMode("page");
    setViewMode("fit");
    setZoom(minZoom);
  }}>
                  Full page
                </button>
                <span className="latex-preview__zoom-group">
                  <button type="button" onClick={zoomOut} disabled={viewMode === "fit" || zoom <= minZoom} aria-label="Zoom out">−</button>
                  <output aria-live="polite" aria-label="Current zoom">{zoomLabel}</output>
                  <button type="button" onClick={zoomIn} disabled={viewMode !== "fit" && zoom >= maxZoom} aria-label="Zoom in">+</button>
                </span>
                <a href={src} target="_blank" rel="noreferrer">Open SVG</a>
                <button ref={closeButtonRef} type="button" className="latex-preview__close" onClick={closeViewer} aria-label="Close rendered LaTeX viewer">
                  Close
                </button>
              </div>
            </header>
            <div ref={viewportRef} className="latex-preview__viewport" data-view-mode={viewMode} onPointerDown={startDrag} onPointerMove={continueDrag} onPointerUp={stopDrag} onPointerCancel={stopDrag}>
              <span className="latex-preview__dialog-page" style={imageStyle}>
                {renderPreviewAsset({
    src,
    alt,
    width,
    height,
    contentBox: activeContentBox
  })}
              </span>
            </div>
            <footer className="latex-preview__viewer-note">
              Compiler-generated vector output · Use +/− to zoom · Drag to pan · Esc to close
            </footer>
          </section>
        </div>}
    </figure>;
};

Explore the **inner workings of TeX's box model** using LuaTeX's powerful Lua integration. This advanced guide reveals how LaTeX constructs documents internally and provides practical techniques for debugging and manipulating the typesetting process.

<Warning>
  **Advanced Topic**: This guide assumes strong LaTeX knowledge and basic programming experience. For LaTeX basics, start with [Creating Your First Document](/learn/latex/basics/creating-first-document).
</Warning>

## What You'll Learn

* ✅ TeX's fundamental box model concepts
* ✅ How LaTeX builds pages from boxes
* ✅ Using LuaTeX to inspect box contents
* ✅ Practical debugging techniques
* ✅ Manipulating boxes programmatically
* ✅ Real-world applications
* ✅ Performance considerations

## Introduction to TeX Boxes

### What Are Boxes?

In TeX, everything on a page is built from **boxes**. Think of boxes as rectangular containers that hold content:

<Card title="Expected output" icon="eye">
  The TeX Box Hierarchy shows the progression from smallest to largest units: Character (single glyph box) flows into hbox (horizontal list of characters/words), which combines into vbox (vertical list of lines/paragraphs), ultimately forming the complete Page output. Each level nests within the next, building the document structure from individual glyphs up to full pages.
</Card>

### Box Types

<CardGroup cols={3}>
  <Card title="hbox (Horizontal Box)" icon="arrows-left-right">
    Contains items arranged horizontally:

    * Characters in a word
    * Words in a line
    * Inline math

    `\hbox{Hello World}`
  </Card>

  <Card title="vbox (Vertical Box)" icon="arrows-up-down">
    Contains items arranged vertically:

    * Lines in a paragraph
    * Paragraphs on a page
    * Display math

    `\vbox{Line 1\\Line 2}`
  </Card>

  <Card title="Glue (Flexible Space)" icon="arrows-maximize">
    Stretchable/shrinkable space:

    * Between words
    * Between paragraphs
    * For justification

    `\hskip 1em plus 2pt minus 1pt`
  </Card>
</CardGroup>

## LuaTeX: Opening Pandora's Box

### What Makes LuaTeX Special?

LuaTeX embeds the Lua programming language directly into TeX, providing:

1. **Direct access** to TeX's internal structures
2. **Ability to manipulate** nodes and boxes
3. **Powerful debugging** capabilities
4. **Performance optimizations** through callbacks

### Basic Box Inspection

<LatexSource filename="inspect-box-basic.tex" source={"\\documentclass{article}\n\\begin{document}\n\n% Create a simple box\n\\setbox0=\\hbox{Hello World}\n\n% Inspect it with Lua\n\\directlua{\n  local box = tex.box[0]\n  print(\"Box width: \" .. box.width / 65536 .. \"pt\")\n  print(\"Box height: \" .. box.height / 65536 .. \"pt\")\n  print(\"Box depth: \" .. box.depth / 65536 .. \"pt\")\n}\n\n% Use the box\n\\box0\n\n\\end{document}"} />

<RenderedOutput title="Rendered output" ctaHref="https://app.latex-cloud-studio.com/?utm_source=resources&utm_medium=rendered_output&utm_campaign=docs_open_app&utm_content=learn_latex_advanced_luatex_boxes">
  <LatexPreview src="/images/rendered/learn-latex-advanced-luatex-boxes-01/page-1.svg" alt="Compiled PDF page 1 from inspect-box-basic.tex" caption="Generated from the shown source with LuaLaTeX in the pinned LaTeXCloud TeX Live 2026 environment." width={595.276} height={841.89} />
</RenderedOutput>

**Console output:**

```
Box width: 50.27774pt
Box height: 6.94444pt  
Box depth: 0.0pt
```

### Understanding Node Lists

Every box contains a **node list** - a linked list of items:

<LatexSource filename="node-list-inspection.tex" source={"\\documentclass{article}\n\\begin{document}\n\n\\setbox0=\\hbox{Hi}\n\n\\directlua{\n  local box = tex.box[0]\n  local head = box.head\n\n  -- Traverse the node list\n  for node in node.traverse(head) do\n    print(\"Node type: \" .. node.id ..\n          \" (\" .. node.type(node.id) .. \")\")\n\n    if node.id == node.id(\"glyph\") then\n      print(\"  Character: \" .. unicode.utf8.char(node.char))\n      print(\"  Font: \" .. node.font)\n    elseif node.id == node.id(\"glue\") then\n      print(\"  Width: \" .. node.width / 65536 .. \"pt\")\n    end\n  end\n}\n\n\\end{document}"} />

<RenderedOutput title="Expected effect">
  <Info>
    This is a partial LuaLaTeX programming excerpt whose result depends on the surrounding Lua and TeX program. It is kept as source-level guidance rather than presented as a standalone rendered page.
  </Info>
</RenderedOutput>

**Console output:**

```
Node type: 0 (glyph)
  Character: H
  Font: 1
Node type: 0 (glyph)
  Character: i
  Font: 1
```

## Practical Box Visualization

### Creating a Box Inspector

<LatexSource filename="box-inspector.tex" source={"\\documentclass{article}\n\\usepackage{xcolor}\n\n\\directlua{\n  function inspect_box(n)\n    local box = tex.box[n]\n    if not box then\n      print(\"Box \" .. n .. \" is empty\")\n      return\n    end\n\n    print(\"\\string\\nBox \" .. n .. \" properties:\")\n    print(\"  Type: \" .. (box.id == 0 and \"hbox\" or \"vbox\"))\n    print(\"  Width: \" .. box.width / 65536 .. \"pt\")\n    print(\"  Height: \" .. box.height / 65536 .. \"pt\")\n    print(\"  Depth: \" .. box.depth / 65536 .. \"pt\")\n\n    -- Count nodes\n    local count = 0\n    for node in node.traverse(box.head) do\n      count = count + 1\n    end\n    print(\"  Nodes: \" .. count)\n  end\n}\n\n\\newcommand{\\inspectbox}[1]{%\n  \\directlua{inspect_box(#1)}%\n}\n\n\\begin{document}\n\n% Create different types of boxes\n\\setbox1=\\hbox{Simple text}\n\\setbox2=\\hbox{$x^2 + y^2 = z^2$}\n\\setbox3=\\vbox{\\hsize=3cm Lorem ipsum dolor sit amet.}\n\n\\inspectbox{1}\n\\inspectbox{2}\n\\inspectbox{3}\n\n\\end{document}"} />

<RenderedOutput title="Expected effect">
  <Info>
    This is a partial LuaLaTeX programming excerpt whose result depends on the surrounding Lua and TeX program. It is kept as source-level guidance rather than presented as a standalone rendered page.
  </Info>
</RenderedOutput>

### Visualizing Box Structure

<LatexSource filename="visualize-boxes.tex" source={"\\documentclass{article}\n\\usepackage{tikz}\n\n\\directlua{\n  function draw_box_structure(n, x, y)\n    local box = tex.box[n]\n    if not box then return end\n\n    -- Draw box outline\n    tex.print(\"\\string\\\\draw[red,thick] (\" .. x .. \",\" .. y .. \") rectangle +(\"\n              .. box.width/65536 .. \"pt,\" .. box.height/65536 .. \"pt);\")\n\n    -- Draw baseline\n    tex.print(\"\\string\\\\draw[blue,dashed] (\" .. x .. \",\" .. y .. \") -- +(\"\n              .. box.width/65536 .. \"pt,0);\")\n\n    -- Add dimensions\n    tex.print(\"\\string\\\\node[above,font=\\string\\\\tiny] at (\"\n              .. x + box.width/131072 .. \",\" .. y + box.height/65536\n              .. \") {\" .. string.format(\"%.1f\", box.width/65536) .. \"pt};\")\n  end\n}\n\n\\begin{document}\n\n\\setbox0=\\hbox{Sample Text}\n\n\\begin{tikzpicture}\n\\directlua{draw_box_structure(0, 0, 0)}\n\\node at (0,0) {\\box0};\n\\end{tikzpicture}\n\n\\end{document}"} />

<RenderedOutput title="Expected effect">
  <Info>
    This is a partial LuaLaTeX programming excerpt whose result depends on the surrounding Lua and TeX program. It is kept as source-level guidance rather than presented as a standalone rendered page.
  </Info>
</RenderedOutput>

**Expected output:**

<Card title="Expected output" icon="eye">
  The visualization displays "Sample Text" enclosed in a red rectangular border representing the hbox boundaries. A blue dashed line runs horizontally through the box indicating the baseline. Above the box, the width measurement "53.1pt" is displayed, showing the precise box dimensions calculated by TeX. This visual debugging technique helps identify box boundaries, baselines, and measurements during document development.
</Card>

## Advanced Box Manipulation

### Modifying Box Contents

<LatexSource filename="modify-boxes.tex" source={"\\documentclass{article}\n\n\\directlua{\n  function add_color_to_glyphs(head)\n    for n in node.traverse(head) do\n      if n.id == node.id(\"glyph\") then\n        -- Insert color node before glyph\n        local color = node.new(node.id(\"whatsit\"),\n                              node.subtype(\"pdf_colorstack\"))\n        color.stack = 0\n        color.cmd = 1  -- push\n        color.data = \"1 0 0 rg\"  -- red color\n\n        head = node.insert_before(head, n, color)\n\n        -- Insert color reset after glyph\n        local reset = node.new(node.id(\"whatsit\"),\n                              node.subtype(\"pdf_colorstack\"))\n        reset.stack = 0\n        reset.cmd = 2  -- pop\n\n        head = node.insert_after(head, n, reset)\n      end\n    end\n    return head\n  end\n\n  -- Register callback\n  luatexbase.add_to_callback(\"pre_linebreak_filter\",\n                            add_color_to_glyphs,\n                            \"color glyphs\")\n}\n\n\\begin{document}\nThis text will have each character colored individually!\n\\end{document}"} />

<RenderedOutput title="Expected effect">
  <Info>
    This is a partial LuaLaTeX programming excerpt whose result depends on the surrounding Lua and TeX program. It is kept as source-level guidance rather than presented as a standalone rendered page.
  </Info>
</RenderedOutput>

### Box Metrics Analysis

<LatexSource filename="box-metrics.tex" source={"\\documentclass{article}\n\n\\directlua{\n  function analyze_paragraph_boxes()\n    local head = tex.lists.page_head\n    if not head then return end\n\n    local line_count = 0\n    local total_badness = 0\n\n    for n in node.traverse(head) do\n      if n.id == node.id(\"hlist\") then\n        line_count = line_count + 1\n\n        -- Check glue settings\n        if n.glue_sign == 1 then  -- stretching\n          print(\"Line \" .. line_count ..\n                \" stretched by factor \" .. n.glue_set)\n        elseif n.glue_sign == 2 then  -- shrinking\n          print(\"Line \" .. line_count ..\n                \" shrunk by factor \" .. n.glue_set)\n        end\n      end\n    end\n  end\n\n  -- Add to shipout callback\n  luatexbase.add_to_callback(\"pre_shipout_filter\",\n    function(head)\n      analyze_paragraph_boxes()\n      return head\n    end, \"analyze paragraphs\")\n}\n\n\\begin{document}\n\\parbox{3cm}{\nThis is a narrow paragraph that will likely have\nsome badly stretched or compressed lines that we\ncan detect and analyze using our Lua code.\n}\n\\end{document}"} />

<RenderedOutput title="Expected effect">
  <Info>
    This is a partial LuaLaTeX programming excerpt whose result depends on the surrounding Lua and TeX program. It is kept as source-level guidance rather than presented as a standalone rendered page.
  </Info>
</RenderedOutput>

## Real-World Applications

### 1. Debugging Overfull/Underfull Boxes

<LatexSource filename="debug-badboxes.tex" source={"\\documentclass{article}\n\\usepackage{xcolor}\n\n\\directlua{\n  -- Highlight overfull hboxes\n  function highlight_overfull_boxes(head, groupcode)\n    for n in node.traverse(head) do\n      if n.id == node.id(\"hlist\") and n.width > tex.hsize then\n        -- Create a rule to highlight\n        local rule = node.new(node.id(\"rule\"))\n        rule.width = n.width\n        rule.height = n.height\n        rule.depth = n.depth\n\n        -- Add color\n        local color = node.new(node.id(\"whatsit\"),\n                              node.subtype(\"pdf_colorstack\"))\n        color.stack = 0\n        color.cmd = 1\n        color.data = \"1 0 0 0.2 k\"  -- light red\n\n        n.head = node.insert_before(n.head, n.head, color)\n        n.head = node.insert_before(n.head, n.head, rule)\n      end\n    end\n    return head\n  end\n\n  luatexbase.add_to_callback(\"post_linebreak_filter\",\n                            highlight_overfull_boxes,\n                            \"highlight overfull\")\n}\n\n\\begin{document}\nThis line contains a verylongwordthatwillcauseanoverfullhbox in our text.\n\\end{document}"} />

<RenderedOutput title="Rendered output">
  <LatexPreview src="/images/rendered/learn-latex-advanced-luatex-boxes-07/page-1.svg" alt="Compiled PDF page 1 from debug-badboxes.tex" caption="Generated from the shown source with LuaLaTeX in the pinned LaTeXCloud TeX Live 2026 environment." width={612} height={792} />
</RenderedOutput>

### 2. Custom Line Breaking

<LatexSource filename="custom-linebreak.tex" source={"\\documentclass{article}\n\n\\directlua{\n  function custom_linebreak_filter(head, is_display)\n    -- Get natural breaks\n    local copy = node.copy_list(head)\n    local params = {\n      hsize = tex.hsize,\n      emergencystretch = tex.emergencystretch,\n      pretolerance = tex.pretolerance,\n      tolerance = tex.tolerance\n    }\n\n    local breaks, info = tex.linebreak(copy, params)\n\n    -- Analyze break quality\n    if info.prevgraf > 5 then\n      -- For long paragraphs, try different parameters\n      params.tolerance = 2000\n      params.emergencystretch = tex.sp(\"3em\")\n\n      local alt_breaks, alt_info = tex.linebreak(head, params)\n\n      if alt_info.demerits < info.demerits then\n        print(\"Using alternative line breaking\")\n        return alt_breaks\n      end\n    end\n\n    return breaks\n  end\n\n  luatexbase.add_to_callback(\"linebreak_filter\",\n                            custom_linebreak_filter,\n                            \"custom linebreak\")\n}\n\n\\begin{document}\n\\noindent This paragraph demonstrates custom line\nbreaking logic that adjusts parameters based on\nparagraph length and quality metrics.\n\\end{document}"} />

<RenderedOutput title="Expected effect">
  <Info>
    This is a partial LuaLaTeX programming excerpt whose result depends on the surrounding Lua and TeX program. It is kept as source-level guidance rather than presented as a standalone rendered page.
  </Info>
</RenderedOutput>

### 3. Box Measurement Tools

<LatexSource filename="measure-content.tex" source={"\\documentclass{article}\n\n\\directlua{\n  function measure_content(text)\n    -- Create temporary box\n    local box = node.hpack(\n      node.copy_list(\n        tex.nest[tex.nest.ptr].head\n      )\n    )\n\n    print(\"Content measurements:\")\n    print(\"  Width: \" .. box.width / 65536 .. \"pt\")\n    print(\"  Height: \" .. box.height / 65536 .. \"pt\")\n    print(\"  Depth: \" .. box.depth / 65536 .. \"pt\")\n\n    -- Calculate ink coverage\n    local glyph_area = 0\n    for n in node.traverse(box.head) do\n      if n.id == node.id(\"glyph\") then\n        glyph_area = glyph_area + n.width * n.height\n      end\n    end\n\n    local total_area = box.width * (box.height + box.depth)\n    local coverage = glyph_area / total_area * 100\n\n    print(\"  Ink coverage: \" .. string.format(\"%.1f%%\", coverage))\n  end\n}\n\n\\newcommand{\\measure}[1]{%\n  \\setbox0=\\hbox{#1}%\n  \\directlua{\n    local b = tex.box[0]\n    measure_content()\n  }%\n  \\box0%\n}\n\n\\begin{document}\n\\measure{Sample text for measurement}\n\\end{document}"} />

<RenderedOutput title="Expected effect">
  <Info>
    This is a partial LuaLaTeX programming excerpt whose result depends on the surrounding Lua and TeX program. It is kept as source-level guidance rather than presented as a standalone rendered page.
  </Info>
</RenderedOutput>

## Debugging Techniques

### Visual Box Debugging

<LatexSource filename="visual-debug.tex" source={"\\documentclass{article}\n\\usepackage{xcolor}\n\n\\directlua{\n  local show_boxes = true\n\n  function visualize_boxes(head, groupcode)\n    if not show_boxes then return head end\n\n    for n in node.traverse(head) do\n      if n.id == node.id(\"hlist\") or n.id == node.id(\"vlist\") then\n        -- Add colored frame\n        local rule = node.new(node.id(\"rule\"))\n        rule.width = tex.sp(\"0.1pt\")\n        rule.height = n.height + tex.sp(\"2pt\")\n        rule.depth = n.depth + tex.sp(\"2pt\")\n\n        -- Different colors for different box types\n        local color = n.id == node.id(\"hlist\") and\n                     \"0 0 1\" or \"1 0 0\"  -- blue/red\n\n        -- Insert visualization\n        -- (simplified for clarity)\n      end\n    end\n    return head\n  end\n}\n\n% Toggle command\n\\newcommand{\\showboxes}{\\directlua{show_boxes = true}}\n\\newcommand{\\hideboxes}{\\directlua{show_boxes = false}}\n\n\\begin{document}\n\\showboxes\nThis text will show box boundaries.\n\n\\hideboxes\nThis text will not.\n\\end{document}"} />

<RenderedOutput title="Expected effect">
  <Info>
    This is a partial LuaLaTeX programming excerpt whose result depends on the surrounding Lua and TeX program. It is kept as source-level guidance rather than presented as a standalone rendered page.
  </Info>
</RenderedOutput>

### Performance Profiling

<LatexSource filename="profile-boxes.tex" source={"\\documentclass{article}\n\n\\directlua{\n  local stats = {\n    total_boxes = 0,\n    total_glyphs = 0,\n    total_glue = 0,\n    processing_time = 0\n  }\n\n  function profile_document(head)\n    local start_time = os.clock()\n\n    for n in node.traverse_id(node.id(\"hlist\"), head) do\n      stats.total_boxes = stats.total_boxes + 1\n\n      for m in node.traverse(n.head) do\n        if m.id == node.id(\"glyph\") then\n          stats.total_glyphs = stats.total_glyphs + 1\n        elseif m.id == node.id(\"glue\") then\n          stats.total_glue = stats.total_glue + 1\n        end\n      end\n    end\n\n    stats.processing_time = os.clock() - start_time\n    return head\n  end\n\n  function print_stats()\n    print(\"\\string\\nDocument Statistics:\")\n    print(\"  Total boxes: \" .. stats.total_boxes)\n    print(\"  Total glyphs: \" .. stats.total_glyphs)\n    print(\"  Total glue nodes: \" .. stats.total_glue)\n    print(\"  Processing time: \" ..\n          string.format(\"%.3f\", stats.processing_time) .. \"s\")\n  end\n\n  luatexbase.add_to_callback(\"post_linebreak_filter\",\n                            profile_document,\n                            \"profile\")\n}\n\n\\AtEndDocument{\\directlua{print_stats()}}\n\n\\begin{document}\nYour document content here...\n\\end{document}"} />

<RenderedOutput title="Expected effect">
  <Info>
    This is a partial LuaLaTeX programming excerpt whose result depends on the surrounding Lua and TeX program. It is kept as source-level guidance rather than presented as a standalone rendered page.
  </Info>
</RenderedOutput>

## Best Practices

### 1. Performance Considerations

<Tip>
  **Performance Tips**

  * **Cache calculations**: Store results of expensive operations
  * **Minimize traversals**: Use specific node types when possible
  * **Batch operations**: Group modifications together
  * **Clean up**: Free unused nodes with `node.free()`
</Tip>

### 2. Safety Guidelines

<Warning>
  **Important Safety Rules:**

  * Always check if nodes exist before accessing
  * Use `node.copy_list()` when modifying shared content
  * Be careful with callbacks - they affect all processing
  * Test thoroughly - box manipulation can break output
</Warning>

### 3. Debugging Workflow

1. **Start simple**: Test with minimal examples
2. **Use print statements**: Track execution flow
3. **Visualize**: Draw boxes to understand structure
4. **Compare**: Check against known good output
5. **Profile**: Measure performance impact

## Quick Reference

<Card title="Expected output" icon="eye">
  LuaTeX Box Commands Quick Reference covers three key areas: Box Access functions include tex.box\[n] for accessing numbered boxes, node.traverse(head) for iterating node lists, node.traverse\_id(id, head) for type-specific traversal, and node.copy\_list(head) for duplicating lists. Node Properties include node.id/next/prev for navigation, box.width/height/depth for dimensions, glyph.char/font for character info, and glue.width/stretch for spacing. Common Callbacks include pre\_linebreak\_filter, post\_linebreak\_filter, pre\_shipout\_filter, and buildpage\_filter for intercepting TeX processing at different stages.
</Card>

## Related Pages

* [Create a LaTeX package](/learn/latex/advanced/create-a-latex-package)
* [Create a document class](/learn/latex/advanced/create-a-document-class)
* [Theorems and proof environments](/learn/latex/advanced/theorems-and-proof-environments)
* [Align and multline environments](/learn/latex/mathematics/align-and-multline-environments)
* [Package management](/learn/latex/package-management)

## Further Resources

<CardGroup cols={2}>
  <Card title="LuaTeX Reference" icon="book" href="https://www.luatex.org/svn/trunk/manual/luatex.pdf">
    Official LuaTeX documentation
  </Card>

  <Card title="TeX by Topic" icon="graduation-cap" href="https://texdoc.org/serve/texbytopic/0">
    Deep dive into TeX internals
  </Card>

  <Card title="Node Library" icon="code" href="https://www.luatex.org/svn/trunk/manual/luatex-nodes.pdf">
    Complete node reference
  </Card>

  <Card title="Article Template" icon="images" href="/templates/article">
    Starter article template with common patterns
  </Card>
</CardGroup>

<Info>
  **LaTeX Cloud Studio** supports LuaTeX! Enable it in your project settings to use these advanced features. Our platform provides enhanced debugging output and visualization tools.
</Info>
