From 83754f16833766420cdca1f8527177dbc4b6ace1 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 24 Apr 2022 13:12:59 +0900 Subject: implement --- main.go | 392 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 main.go (limited to 'main.go') diff --git a/main.go b/main.go new file mode 100644 index 0000000..1a14810 --- /dev/null +++ b/main.go @@ -0,0 +1,392 @@ +package main + +import ( + "embed" + "image/png" + "log" + "os" + + "github.com/gdamore/tcell/v2" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/japanese" +) + +const ( + // Bit width of half-width characters. + bitsHW = 32 + // Glyph width of half-width characters. + glyphWidthHW = 4 + // Glyph height of half-width characters. + glyphHeightHW = 8 + // Bit width of full-width characters. + bitsFW = 64 + // Glyph width of full-width characters. + glyphWidthFW = 8 + // Glyph height of full-width characters. + glyphHeightFW = 8 + + fw1FirstByteStart = 0x81 + fw1FirstByteEnd = 0x9F + fw2FirstByteStart = 0xE0 + fw2FirstByteEnd = 0xEF + fwSecondByteStart = 0x40 + fwSecondByteEnd = 0x9F +) + +//go:embed assets/*.png +var fontFiles embed.FS + +// One glyph for half-width characters. +type GlyphHW uint32 + +// One glyph for full-width characters. +type GlyphFW uint64 + +type Font struct { + // Half-width glyphs. It is keyed by a raw character code. + glyphsHW *[256]GlyphHW + // Full-width glyphs. + glyphsFW1 *[31][189]GlyphFW + // Full-width glyphs. + glyphsFW2 *[16][189]GlyphFW +} + +type CharClass uint8 + +const ( + charClassHW = iota + charClassFW1 + charClassFW2 +) + +// Get character class. +func getCharClass(b byte) CharClass { + if fw1FirstByteStart <= b && b <= fw1FirstByteEnd { + return charClassFW1 + } else if fw2FirstByteStart <= b && b <= fw2FirstByteEnd { + return charClassFW2 + } else { + return charClassHW + } +} + +func glyphHWToglyphFW(gHW GlyphHW) GlyphFW { + gFW := GlyphFW(0) + for i := 0; i < bitsHW; i++ { + if gHW&(1< squareH*8 { + squareW = squareH*8 + } + if squareH > squareW { + squareH = squareW + } + + xOffsets := make([]int, len(banner)) + for i, gridWidth := range gridWidths { + xOffsets[i] = (scrW/squareW - gridWidth) / 2 + } + yOffset := (scrH/squareH - gridHeight) / 2 + + return squareW, squareH, xOffsets, yOffset +} + +func drawOneLine(r *Renderer, s string, xOffset, yOffset int, font *Font) { + for i := 0; i < len(s); i++ { + b := s[i] + x := xOffset + i*glyphWidthHW + y := yOffset + var g GlyphFW + switch getCharClass(b) { + case charClassHW: + g = glyphHWToglyphFW(font.glyphsHW[b]) + case charClassFW1: + b2 := s[i+1] + g = font.glyphsFW1[b-fw1FirstByteStart][b2-fwSecondByteStart] + i++ + case charClassFW2: + b2 := s[i+1] + g = font.glyphsFW1[b-fw2FirstByteStart][b2-fwSecondByteStart] + i++ + } + drawGlyph(r, g, x, y) + } +} + +func drawBanner(r *Renderer, banner Banner, font *Font) { + r.ClearScreen() + + sw, sh, xOffsets, yOffset := calcSquareSizeAndOffset(r, banner) + r.SetSquareSize(sw, sh) + + for i, line := range banner { + drawOneLine(r, line, xOffsets[i], yOffset+i*glyphHeightFW, font) + } +} + +func parseGlyphsHW(filePath string) (*[256]GlyphHW, error) { + fp, err := fontFiles.Open(filePath) + if err != nil { + return nil, err + } + defer fp.Close() + + img, err := png.Decode(fp) + if err != nil { + return nil, err + } + + gs := [256]GlyphHW{} + for dy := 0; dy < 16; dy++ { + for dx := 0; dx < 16; dx++ { + glyph := GlyphHW(0) + for i := 0; i < bitsHW; i++ { + x := dx*glyphWidthHW + i%glyphWidthHW + y := dy*glyphHeightHW + i/glyphWidthHW + r, g, b, _ := img.At(x, y).RGBA() + if r == 0 && b == 0 && g == 0 { + glyph |= 1 << i + } + } + c := dy*16 + dx + gs[c] = glyph + } + } + return &gs, nil +} + +func parseGlyphsFW(filePath string) (*[31][189]GlyphFW, *[16][189]GlyphFW, error) { + fp, err := fontFiles.Open(filePath) + if err != nil { + return nil, nil, err + } + defer fp.Close() + + img, err := png.Decode(fp) + if err != nil { + return nil, nil, err + } + + gs1 := [31][189]GlyphFW{} + for dy := 0; dy < 62; dy++ { + for dx := 0; dx < 94; dx++ { + glyph := GlyphFW(0) + for i := 0; i < bitsFW; i++ { + x := dx*glyphWidthFW + i%glyphWidthFW + y := dy*glyphHeightFW + i/glyphWidthFW + r, g, b, _ := img.At(x, y).RGBA() + if r == 0 && b == 0 && g == 0 { + glyph |= 1 << i + } + } + c1 := dy / 2 + c2 := dx + (fwSecondByteEnd-fwSecondByteStart)*(dy%2) + gs1[c1][c2] = glyph + } + } + + yOffset := 31 * glyphHeightFW + gs2 := [16][189]GlyphFW{} + for dy := 0; dy < 16; dy++ { + for dx := 0; dx < 94; dx++ { + glyph := GlyphFW(0) + for i := 0; i < bitsFW; i++ { + x := dx*glyphWidthFW + i%glyphWidthFW + y := dy*glyphHeightFW + i/glyphWidthFW + yOffset + r, g, b, _ := img.At(x, y).RGBA() + if r == 0 && b == 0 && g == 0 { + glyph |= 1 << i + } + } + c1 := dy / 2 + c2 := dx + (fwSecondByteEnd-fwSecondByteStart)*(dy%2) + gs2[c1][c2] = glyph + } + } + + return &gs1, &gs2, nil +} + +func prepareFont(fileHW, fileFW string) (*Font, error) { + glyphsHW, err := parseGlyphsHW(fileHW) + if err != nil { + return nil, err + } + glyphsFW1, glyphsFW2, err := parseGlyphsFW(fileFW) + if err != nil { + return nil, err + } + return &Font{glyphsHW, glyphsFW1, glyphsFW2}, nil +} + +func main() { + if len(os.Args) <= 1 { + return + } + + font, err := prepareFont( + "assets/misaki_gothic_2nd_4x8.png", + "assets/misaki_mincho.png", + ) + if err != nil { + log.Fatalf("%+v", err) + } + + r, err := NewRenderer( + tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset), + tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorOlive), + ) + if err != nil { + log.Fatalf("%+v", err) + } + defer r.Fini() + + banner, err := NewBanner(os.Args[1:]) + if err != nil { + log.Fatalf("%+v", err) + } + drawBanner(r, banner, font) + + for { + r.Show() + + ev := r.PollEvent() + switch ev := ev.(type) { + case *tcell.EventResize: + drawBanner(r, banner, font) + r.Sync() + case *tcell.EventKey: + if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC || ev.Rune() == 'q' { + return + } + } + } +} -- cgit v1.2.3-70-g09d2