github.com/blend/go-sdk@v1.20220411.3/ansi/slant/print.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package slant 9 10 import ( 11 "bytes" 12 "errors" 13 "io" 14 "unicode" 15 ) 16 17 // PrintString prints a phrase to a given output with the default font. 18 func PrintString(phrase string) (string, error) { 19 buf := new(bytes.Buffer) 20 if err := Print(buf, phrase); err != nil { 21 return "", err 22 } 23 return buf.String(), nil 24 } 25 26 // Print prints a phrase to a given output with the default font. 27 func Print(output io.Writer, phrase string) error { 28 font := Slant 29 30 phraseRunes := []rune(phrase) 31 32 var row, charRow []rune 33 var trimCount, left int 34 var err error 35 for r := 0; r < font.Height; r++ { 36 row = nil 37 for index, char := range phraseRunes { 38 if char < FirstASCII || char > LastASCII { 39 return errors.New("figlet; invalid input") 40 } 41 charRow = []rune(rowsForLetter(char, font.Letters)[r]) 42 43 if index > 0 { 44 trimCount = trimAmount(phraseRunes[index-1], phraseRunes[index], font.Height, font.Letters) 45 row, left = trimRightSpaceMax(row, trimCount) 46 if left > 0 { 47 charRow, _ = trimLeftSpaceMax(charRow, left) 48 } 49 } 50 charRow = replaceRunes(charRow, font.Hardblank, ' ') 51 row = append(row, charRow...) 52 } 53 _, err = io.WriteString(output, string(row)+"\n") 54 if err != nil { 55 return err 56 } 57 } 58 return nil 59 } 60 61 // trimAmount returns the number of characters to trim. 62 // this is typically the minimum sum of trailing whitespace in a, and leading whitespace in b. 63 func trimAmount(a, b rune, height int, letters [][]string) int { 64 rowsA := rowsForLetter(a, letters) 65 rowsB := rowsForLetter(b, letters) 66 67 var trimCount int 68 if len(rowsA) > len(rowsB) { 69 trimCount = len(rowsA) 70 } else { 71 trimCount = len(rowsB) 72 } 73 74 for r := 0; r < height; r++ { 75 rowA := []rune(rowsA[r]) 76 rowB := []rune(rowsB[r]) 77 78 spaceA := countTrailingSpace(rowA) 79 spaceB := countLeadingSpace(rowB) 80 81 if trimCount > (spaceA + spaceB) { 82 trimCount = spaceA + spaceB 83 } 84 } 85 return trimCount 86 } 87 88 func rowsForLetter(letter rune, letters [][]string) []string { 89 return letters[int(letter)-ASCIIOffset] 90 } 91 92 func countLeadingSpace(row []rune) int { 93 for index := 0; index < len(row); index++ { 94 if !unicode.IsSpace(row[index]) { 95 return index 96 } 97 } 98 return len(row) 99 } 100 101 func countTrailingSpace(row []rune) int { 102 for index := 0; index < len(row); index++ { 103 if !unicode.IsSpace(row[len(row)-(index+1)]) { 104 return index 105 } 106 } 107 return 0 108 } 109 110 func trimRightSpaceMax(row []rune, max int) ([]rune, int) { 111 var count int 112 for index := 0; index < len(row) && count < max; index++ { 113 if !unicode.IsSpace(row[len(row)-(index+1)]) { 114 break 115 } 116 count++ 117 } 118 return row[:len(row)-count], max - count 119 } 120 121 func trimLeftSpaceMax(row []rune, max int) ([]rune, int) { 122 for index := 0; index < len(row); index++ { 123 if !unicode.IsSpace(row[index]) || index == max { 124 return row[index:], max - index 125 } 126 } 127 return row, max 128 } 129 130 func replaceRunes(row []rune, old, new rune) []rune { 131 for index := range row { 132 if row[index] == old { 133 row[index] = new 134 } 135 } 136 return row 137 }