main
1package lipgloss
2
3import (
4 "math"
5 "strings"
6
7 "github.com/charmbracelet/x/ansi"
8)
9
10// Position represents a position along a horizontal or vertical axis. It's in
11// situations where an axis is involved, like alignment, joining, placement and
12// so on.
13//
14// A value of 0 represents the start (the left or top) and 1 represents the end
15// (the right or bottom). 0.5 represents the center.
16//
17// There are constants Top, Bottom, Center, Left and Right in this package that
18// can be used to aid readability.
19type Position float64
20
21func (p Position) value() float64 {
22 return math.Min(1, math.Max(0, float64(p)))
23}
24
25// Position aliases.
26const (
27 Top Position = 0.0
28 Bottom Position = 1.0
29 Center Position = 0.5
30 Left Position = 0.0
31 Right Position = 1.0
32)
33
34// Place places a string or text block vertically in an unstyled box of a given
35// width or height.
36func Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
37 return renderer.Place(width, height, hPos, vPos, str, opts...)
38}
39
40// Place places a string or text block vertically in an unstyled box of a given
41// width or height.
42func (r *Renderer) Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
43 return r.PlaceVertical(height, vPos, r.PlaceHorizontal(width, hPos, str, opts...), opts...)
44}
45
46// PlaceHorizontal places a string or text block horizontally in an unstyled
47// block of a given width. If the given width is shorter than the max width of
48// the string (measured by its longest line) this will be a noop.
49func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
50 return renderer.PlaceHorizontal(width, pos, str, opts...)
51}
52
53// PlaceHorizontal places a string or text block horizontally in an unstyled
54// block of a given width. If the given width is shorter than the max width of
55// the string (measured by its longest line) this will be a noöp.
56func (r *Renderer) PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
57 lines, contentWidth := getLines(str)
58 gap := width - contentWidth
59
60 if gap <= 0 {
61 return str
62 }
63
64 ws := newWhitespace(r, opts...)
65
66 var b strings.Builder
67 for i, l := range lines {
68 // Is this line shorter than the longest line?
69 short := max(0, contentWidth-ansi.StringWidth(l))
70
71 switch pos { //nolint:exhaustive
72 case Left:
73 b.WriteString(l)
74 b.WriteString(ws.render(gap + short))
75
76 case Right:
77 b.WriteString(ws.render(gap + short))
78 b.WriteString(l)
79
80 default: // somewhere in the middle
81 totalGap := gap + short
82
83 split := int(math.Round(float64(totalGap) * pos.value()))
84 left := totalGap - split
85 right := totalGap - left
86
87 b.WriteString(ws.render(left))
88 b.WriteString(l)
89 b.WriteString(ws.render(right))
90 }
91
92 if i < len(lines)-1 {
93 b.WriteRune('\n')
94 }
95 }
96
97 return b.String()
98}
99
100// PlaceVertical places a string or text block vertically in an unstyled block
101// of a given height. If the given height is shorter than the height of the
102// string (measured by its newlines) then this will be a noop.
103func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
104 return renderer.PlaceVertical(height, pos, str, opts...)
105}
106
107// PlaceVertical places a string or text block vertically in an unstyled block
108// of a given height. If the given height is shorter than the height of the
109// string (measured by its newlines) then this will be a noöp.
110func (r *Renderer) PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
111 contentHeight := strings.Count(str, "\n") + 1
112 gap := height - contentHeight
113
114 if gap <= 0 {
115 return str
116 }
117
118 ws := newWhitespace(r, opts...)
119
120 _, width := getLines(str)
121 emptyLine := ws.render(width)
122 b := strings.Builder{}
123
124 switch pos { //nolint:exhaustive
125 case Top:
126 b.WriteString(str)
127 b.WriteRune('\n')
128 for i := 0; i < gap; i++ {
129 b.WriteString(emptyLine)
130 if i < gap-1 {
131 b.WriteRune('\n')
132 }
133 }
134
135 case Bottom:
136 b.WriteString(strings.Repeat(emptyLine+"\n", gap))
137 b.WriteString(str)
138
139 default: // Somewhere in the middle
140 split := int(math.Round(float64(gap) * pos.value()))
141 top := gap - split
142 bottom := gap - top
143
144 b.WriteString(strings.Repeat(emptyLine+"\n", top))
145 b.WriteString(str)
146
147 for i := 0; i < bottom; i++ {
148 b.WriteRune('\n')
149 b.WriteString(emptyLine)
150 }
151 }
152
153 return b.String()
154}