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  }