Repository: pterm/pterm (go)

Issue:
Add `CenterPrinter`
`CenterPrinter` should print centered text. Renderable printer. Custom screen size.

Pull request description:
feat: add `CenterPrinter`
### Description
Describe your work here.

### Scope
> What is affected by this pull request?

- [ ] Bug Fix
- [x] New Feature
- [ ] Documentation
- [ ] Other

### Related Issue
Fixes #76 


### To-Do Checklist
- [x] I tested my changes
- [x] I have commented every method that I created/changed
- [x] I updated the examples to fit with my changes
- [x] I have added tests for my newly created methods


Patch:
diff --git a/README.md b/README.md
index 1755c19a..1ce0ea46 100644
--- a/README.md
+++ b/README.md
@@ -76,6 +76,7 @@
 - [Section](#section)
 - [Spinner](#spinner)
 - [Header](#header)
+- [Header](#center)
 - [Tree](#tree)
 - [Paragraph](#paragraph)
 - [Print With Color](#print-with-color)
diff --git a/_examples/center/main.go b/_examples/center/main.go
new file mode 100644
index 00000000..9c879169
--- /dev/null
+++ b/_examples/center/main.go
@@ -0,0 +1,13 @@
+package main
+
+import "github.com/pterm/pterm"
+
+func main() {
+	pterm.DefaultCenter.Println("This text is centered!\nIt centeres the whole block by default.\nIn that way you can do stuff like this:")
+
+	// Generate BigLetters
+	s, _ := pterm.DefaultBigText.WithLetters(pterm.NewLettersFromString("PTerm")).Srender()
+	pterm.DefaultCenter.Println(s) // Print BigLetters with the default CenterPrinter
+
+	pterm.DefaultCenter.WithCenterEachLineSeparately().Println("This text is centered!\nBut each line is\ncentered\nseparately")
+}
diff --git a/center_printer.go b/center_printer.go
new file mode 100644
index 00000000..a5596ff3
--- /dev/null
+++ b/center_printer.go
@@ -0,0 +1,93 @@
+package pterm
+
+import (
+	"github.com/mattn/go-runewidth"
+	"github.com/pterm/pterm/internal"
+	"strings"
+)
+
+// DefaultCenter is the default CenterPrinter.
+var DefaultCenter = CenterPrinter{
+	CenterEachLineSeparately: false,
+}
+
+// CenterPrinter prints centered text.
+type CenterPrinter struct {
+	CenterEachLineSeparately bool
+}
+
+// WithCenterEachLineSeparately centers each line separately.
+func (p CenterPrinter) WithCenterEachLineSeparately(b ...bool) CenterPrinter {
+	bt := internal.WithBoolean(b)
+	p.CenterEachLineSeparately = bt
+	return p
+}
+
+// Sprint formats using the default formats for its operands and returns the resulting string.
+// Spaces are added between operands when neither is a string.
+func (p CenterPrinter) Sprint(a ...interface{}) string {
+	lines := strings.Split(Sprint(a...), "\n")
+
+	var ret string
+
+	if p.CenterEachLineSeparately {
+		for _, line := range lines {
+			ret += strings.Repeat(" ", (GetTerminalWidth()-runewidth.StringWidth(RemoveColorFromString(line)))/2) + line + "\n"
+		}
+		return ret
+	}
+
+	var maxLineWidth int
+
+	for _, line := range lines {
+		lineLength := runewidth.StringWidth(RemoveColorFromString(line))
+		if maxLineWidth < lineLength {
+			maxLineWidth = lineLength
+		}
+	}
+
+	indent := GetTerminalWidth() - maxLineWidth
+
+	for _, line := range lines {
+		ret += strings.Repeat(" ", indent/2) + line + "\n"
+	}
+
+	return ret
+}
+
+// Sprintln formats using the default formats for its operands and returns the resulting string.
+// Spaces are always added between operands and a newline is appended.
+func (p CenterPrinter) Sprintln(a ...interface{}) string {
+	return Sprintln(p.Sprint(a...))
+}
+
+// Sprintf formats according to a format specifier and returns the resulting string.
+func (p CenterPrinter) Sprintf(format string, a ...interface{}) string {
+	return p.Sprint(Sprintf(format, a...))
+}
+
+// Print formats using the default formats for its operands and writes to standard output.
+// Spaces are added between operands when neither is a string.
+// It returns the number of bytes written and any write error encountered.
+func (p CenterPrinter) Print(a ...interface{}) *TextPrinter {
+	Print(p.Sprint(a...))
+	tp := TextPrinter(p)
+	return &tp
+}
+
+// Println formats using the default formats for its operands and writes to standard output.
+// Spaces are always added between operands and a newline is appended.
+// It returns the number of bytes written and any write error encountered.
+func (p CenterPrinter) Println(a ...interface{}) *TextPrinter {
+	Println(p.Sprint(a...))
+	tp := TextPrinter(p)
+	return &tp
+}
+
+// Printf formats according to a format specifier and writes to standard output.
+// It returns the number of bytes written and any write error encountered.
+func (p CenterPrinter) Printf(format string, a ...interface{}) *TextPrinter {
+	Print(p.Sprintf(format, a...))
+	tp := TextPrinter(p)
+	return &tp
+}


Test patch:
diff --git a/center_printer_test.go b/center_printer_test.go
new file mode 100644
index 00000000..ad8bd12a
--- /dev/null
+++ b/center_printer_test.go
@@ -0,0 +1,96 @@
+package pterm
+
+import (
+	"github.com/pterm/pterm/internal"
+	"github.com/stretchr/testify/assert"
+	"io"
+	"testing"
+)
+
+func TestCenterPrinter_WithCenterEachLineSeparately(t *testing.T) {
+	p := CenterPrinter{}
+	p2 := p.WithCenterEachLineSeparately()
+
+	assert.True(t, p2.CenterEachLineSeparately)
+	assert.False(t, p.CenterEachLineSeparately)
+}
+
+func TestCenterPrinterPrintMethods(t *testing.T) {
+	p := DefaultCenter
+
+	t.Run("Print", func(t *testing.T) {
+		internal.TestPrintContains(t, func(w io.Writer, a interface{}) {
+			p.Print(a)
+		})
+	})
+
+	t.Run("Printf", func(t *testing.T) {
+		internal.TestPrintfContains(t, func(w io.Writer, format string, a interface{}) {
+			p.Printf(format, a)
+		})
+	})
+
+	t.Run("Println", func(t *testing.T) {
+		internal.TestPrintlnContains(t, func(w io.Writer, a interface{}) {
+			p.Println(a)
+		})
+	})
+
+	t.Run("Sprint", func(t *testing.T) {
+		internal.TestSprintContains(t, func(a interface{}) string {
+			return p.Sprint(a)
+		})
+	})
+
+	t.Run("Sprintf", func(t *testing.T) {
+		internal.TestSprintfContains(t, func(format string, a interface{}) string {
+			return p.Sprintf(format, a)
+		})
+	})
+
+	t.Run("Sprintln", func(t *testing.T) {
+		internal.TestSprintlnContains(t, func(a interface{}) string {
+			return p.Sprintln(a)
+		})
+	})
+}
+
+func TestCenterPrinterPrintMethodsCenterSeparately(t *testing.T) {
+	p := DefaultCenter.WithCenterEachLineSeparately()
+
+	t.Run("Print", func(t *testing.T) {
+		internal.TestPrintContains(t, func(w io.Writer, a interface{}) {
+			p.Print(a)
+		})
+	})
+
+	t.Run("Printf", func(t *testing.T) {
+		internal.TestPrintfContains(t, func(w io.Writer, format string, a interface{}) {
+			p.Printf(format, a)
+		})
+	})
+
+	t.Run("Println", func(t *testing.T) {
+		internal.TestPrintlnContains(t, func(w io.Writer, a interface{}) {
+			p.Println(a)
+		})
+	})
+
+	t.Run("Sprint", func(t *testing.T) {
+		internal.TestSprintContains(t, func(a interface{}) string {
+			return p.Sprint(a)
+		})
+	})
+
+	t.Run("Sprintf", func(t *testing.T) {
+		internal.TestSprintfContains(t, func(format string, a interface{}) string {
+			return p.Sprintf(format, a)
+		})
+	})
+
+	t.Run("Sprintln", func(t *testing.T) {
+		internal.TestSprintlnContains(t, func(a interface{}) string {
+			return p.Sprintln(a)
+		})
+	})
+}