/ ink / squash-text-nodes.ts
squash-text-nodes.ts
 1  import type { DOMElement } from './dom.js'
 2  import type { TextStyles } from './styles.js'
 3  
 4  /**
 5   * A segment of text with its associated styles.
 6   * Used for structured rendering without ANSI string transforms.
 7   */
 8  export type StyledSegment = {
 9    text: string
10    styles: TextStyles
11    hyperlink?: string
12  }
13  
14  /**
15   * Squash text nodes into styled segments, propagating styles down through the tree.
16   * This allows structured styling without relying on ANSI string transforms.
17   */
18  export function squashTextNodesToSegments(
19    node: DOMElement,
20    inheritedStyles: TextStyles = {},
21    inheritedHyperlink?: string,
22    out: StyledSegment[] = [],
23  ): StyledSegment[] {
24    const mergedStyles = node.textStyles
25      ? { ...inheritedStyles, ...node.textStyles }
26      : inheritedStyles
27  
28    for (const childNode of node.childNodes) {
29      if (childNode === undefined) {
30        continue
31      }
32  
33      if (childNode.nodeName === '#text') {
34        if (childNode.nodeValue.length > 0) {
35          out.push({
36            text: childNode.nodeValue,
37            styles: mergedStyles,
38            hyperlink: inheritedHyperlink,
39          })
40        }
41      } else if (
42        childNode.nodeName === 'ink-text' ||
43        childNode.nodeName === 'ink-virtual-text'
44      ) {
45        squashTextNodesToSegments(
46          childNode,
47          mergedStyles,
48          inheritedHyperlink,
49          out,
50        )
51      } else if (childNode.nodeName === 'ink-link') {
52        const href = childNode.attributes['href'] as string | undefined
53        squashTextNodesToSegments(
54          childNode,
55          mergedStyles,
56          href || inheritedHyperlink,
57          out,
58        )
59      }
60    }
61  
62    return out
63  }
64  
65  /**
66   * Squash text nodes into a plain string (without styles).
67   * Used for text measurement in layout calculations.
68   */
69  function squashTextNodes(node: DOMElement): string {
70    let text = ''
71  
72    for (const childNode of node.childNodes) {
73      if (childNode === undefined) {
74        continue
75      }
76  
77      if (childNode.nodeName === '#text') {
78        text += childNode.nodeValue
79      } else if (
80        childNode.nodeName === 'ink-text' ||
81        childNode.nodeName === 'ink-virtual-text'
82      ) {
83        text += squashTextNodes(childNode)
84      } else if (childNode.nodeName === 'ink-link') {
85        text += squashTextNodes(childNode)
86      }
87    }
88  
89    return text
90  }
91  
92  export default squashTextNodes