Skip to content

Commit 5277fe8

Browse files
committed
fix(ui): layout alignment for wide characters
1 parent 251482f commit 5277fe8

File tree

2 files changed

+111
-6
lines changed

2 files changed

+111
-6
lines changed

internal/api/types.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
package api
22

3+
import (
4+
"encoding/json"
5+
"strings"
6+
"unicode"
7+
8+
"golang.org/x/text/unicode/norm"
9+
"golang.org/x/text/width"
10+
)
11+
312
type SubsonicResponse struct {
413
Response struct {
514
Status string `json:"status"`
@@ -95,3 +104,100 @@ type Playlist struct {
95104
ID string `json:"id"`
96105
Name string `json:"name"`
97106
}
107+
108+
// Helper: Unmarshal Song for sanitization
109+
func (s *Song) UnmarshalJSON(data []byte) error {
110+
type Alias Song
111+
aux := &struct {
112+
*Alias
113+
}{
114+
Alias: (*Alias)(s),
115+
}
116+
117+
if err := json.Unmarshal(data, &aux); err != nil {
118+
return err
119+
}
120+
121+
s.Title = SanitizeDisplayString(s.Title)
122+
s.Artist = SanitizeDisplayString(s.Artist)
123+
s.Album = SanitizeDisplayString(s.Album)
124+
125+
return nil
126+
}
127+
128+
// Helper: Unmarshal Album for sanitization
129+
func (a *Album) UnmarshalJSON(data []byte) error {
130+
type Alias Album
131+
aux := &struct {
132+
*Alias
133+
}{
134+
Alias: (*Alias)(a),
135+
}
136+
137+
if err := json.Unmarshal(data, &aux); err != nil {
138+
return err
139+
}
140+
141+
a.Name = SanitizeDisplayString(a.Name)
142+
a.Artist = SanitizeDisplayString(a.Artist)
143+
144+
return nil
145+
}
146+
147+
// Helper: Unmarshal Artists for sanitization
148+
func (a *Artist) UnmarshalJSON(data []byte) error {
149+
type Alias Artist
150+
aux := &struct {
151+
*Alias
152+
}{
153+
Alias: (*Alias)(a),
154+
}
155+
156+
if err := json.Unmarshal(data, &aux); err != nil {
157+
return err
158+
}
159+
160+
a.Name = SanitizeDisplayString(a.Name)
161+
162+
return nil
163+
}
164+
165+
// Helper: Unmarshal Playlists for sanitization
166+
func (p *Playlist) UnmarshalJSON(data []byte) error {
167+
type Alias Playlist
168+
aux := &struct {
169+
*Alias
170+
}{
171+
Alias: (*Alias)(p),
172+
}
173+
174+
if err := json.Unmarshal(data, &aux); err != nil {
175+
return err
176+
}
177+
178+
p.Name = SanitizeDisplayString(p.Name)
179+
180+
return nil
181+
}
182+
183+
// Helper: Sanitize string
184+
func SanitizeDisplayString(s string) string {
185+
s = width.Fold.String(s)
186+
s = norm.NFC.String(s)
187+
188+
var b strings.Builder
189+
b.Grow(len(s))
190+
for _, r := range s {
191+
switch {
192+
case r == '\n' || r == '\r' || r == '\t':
193+
b.WriteRune(r)
194+
case unicode.IsControl(r):
195+
continue
196+
case r == '\u200B' || r == '\u200C' || r == '\u200D' || r == '\uFEFF' || r == '\u00AD':
197+
continue
198+
default:
199+
b.WriteRune(r)
200+
}
201+
}
202+
return b.String()
203+
}

internal/ui/view.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -621,8 +621,8 @@ func footerContent(m model) string {
621621
title = "Loading..."
622622
artistAlbumText = ""
623623
} else {
624-
title = m.playerStatus.Title
625-
artistAlbumText = m.playerStatus.Artist + " - " + m.playerStatus.Album
624+
title = api.SanitizeDisplayString(m.playerStatus.Title)
625+
artistAlbumText = api.SanitizeDisplayString(m.playerStatus.Artist + " - " + m.playerStatus.Album)
626626
}
627627

628628
notifyText := ""
@@ -633,8 +633,7 @@ func footerContent(m model) string {
633633
const borderWidth = 2
634634
const spacing = 3
635635

636-
topRowGap := m.width - borderWidth - 2*spacing - len(notifyText) - len(title)
637-
636+
topRowGap := m.width - borderWidth - 2*spacing - runewidth.StringWidth(notifyText) - runewidth.StringWidth(title)
638637
if topRowGap > 0 {
639638
title += strings.Repeat(" ", topRowGap) + notifyText
640639
}
@@ -681,11 +680,11 @@ func footerContent(m model) string {
681680
}
682681

683682
bottomRowGap := 0
684-
bottomRowSpaceTaken := borderWidth + 2*spacing + len(artistAlbumText) + len(loopText) + len(volumeText)
683+
bottomRowSpaceTaken := borderWidth + 2*spacing + runewidth.StringWidth(artistAlbumText) + runewidth.StringWidth(loopText) + runewidth.StringWidth(volumeText)
685684
if artistAlbumText != "" && m.width != 0 && m.width-bottomRowSpaceTaken > 0 {
686685
bottomRowGap = m.width - bottomRowSpaceTaken
687686
} else if m.width != 0 {
688-
bottomRowGap = m.width - borderWidth - 2*spacing - len(loopText)
687+
bottomRowGap = m.width - borderWidth - 2*spacing - runewidth.StringWidth(loopText)
689688
}
690689

691690
bottomRowText := artistAlbumText + strings.Repeat(" ", bottomRowGap) + loopText + volumeText

0 commit comments

Comments
 (0)