aboutsummaryrefslogtreecommitdiff
path: root/internal/render/ascii.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/render/ascii.go')
-rw-r--r--internal/render/ascii.go67
1 files changed, 67 insertions, 0 deletions
diff --git a/internal/render/ascii.go b/internal/render/ascii.go
new file mode 100644
index 0000000..4203bc9
--- /dev/null
+++ b/internal/render/ascii.go
@@ -0,0 +1,67 @@
+package render
+
+import "strings"
+
+// asciiMap is deliberately one rune to one ASCII character.
+//
+// Substitution happens after the table has been laid out, so anything that
+// changed the number of characters would shear every column to its right. That
+// constraint is why the wind arrows become single letters rather than the
+// two-letter compass points that would read better.
+var asciiMap = map[rune]string{
+ // Polish diacritics, so a Polish forecast survives GSM-7.
+ 'ą': "a", 'ć': "c", 'ę': "e", 'ł': "l", 'ń': "n",
+ 'ó': "o", 'ś': "s", 'ź': "z", 'ż': "z",
+ 'Ą': "A", 'Ć': "C", 'Ę': "E", 'Ł': "L", 'Ń': "N",
+ 'Ó': "O", 'Ś': "S", 'Ź': "Z", 'Ż': "Z",
+
+ // Wind arrows. The arrow points the way the wind blows, so v is a northerly.
+ '↑': "^", '↓': "v", '←': "<", '→': ">",
+ '↖': "\\", '↗': "/", '↙': "/", '↘': "\\",
+
+ // Chart: blocks and the axis rule.
+ '█': "#", '▇': "#", '▆': "=", '▅': "=",
+ '▄': "_", '▃': ":", '▂': ".", '▁': ".",
+ '│': "|",
+}
+
+// ASCII rewrites output to pure ASCII, one character for one character.
+//
+// The point is SMS: a single non-ASCII character forces the whole message from
+// GSM-7 into UCS-2, which cuts a segment from 160 characters to 70. A degree
+// sign alone therefore more than doubles the cost of sending a forecast.
+//
+// The degree sign becomes the unit letter, which is both ASCII and clearer to
+// someone reading it cold: "29C" rather than "29".
+func ASCII(s, units string) string {
+ degree := "C"
+ if units == "imperial" {
+ degree = "F"
+ }
+ runes := []rune(s)
+ var b strings.Builder
+ b.Grow(len(s))
+ for i, r := range runes {
+ switch {
+ case r == '°':
+ // Prose from IMGW already writes "30°C"; appending the unit again
+ // would give "30CC". Only a bare degree sign gains the letter.
+ if i+1 < len(runes) && (runes[i+1] == 'C' || runes[i+1] == 'F') {
+ continue
+ }
+ b.WriteString(degree)
+ case r < 128:
+ b.WriteRune(r)
+ default:
+ if sub, ok := asciiMap[r]; ok {
+ b.WriteString(sub)
+ } else {
+ // Anything unmapped -- a place name in another script, a weather
+ // code description we have not transliterated -- becomes '?'
+ // rather than silently vanishing and misaligning the row.
+ b.WriteString("?")
+ }
+ }
+ }
+ return b.String()
+}