mirror of
https://github.com/aljazceru/njump.git
synced 2025-12-19 07:14:24 +01:00
use NotoSans for all text except when hiragana or katakana is detected, then use NotoSansJP -- and break japanese characters even without whitespace.
This commit is contained in:
BIN
fonts/NotoSans.ttf
Normal file
BIN
fonts/NotoSans.ttf
Normal file
Binary file not shown.
123
render_image.go
123
render_image.go
@@ -2,17 +2,19 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
"image/png"
|
"image/png"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/apatters/go-wordwrap"
|
"github.com/apatters/go-wordwrap"
|
||||||
"github.com/lukevers/freetype-go/freetype"
|
"github.com/lukevers/freetype-go/freetype"
|
||||||
|
"github.com/lukevers/freetype-go/freetype/truetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -24,6 +26,9 @@ const (
|
|||||||
BLOCK = "▒"
|
BLOCK = "▒"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed fonts/*
|
||||||
|
var fonts embed.FS
|
||||||
|
|
||||||
func renderImage(w http.ResponseWriter, r *http.Request) {
|
func renderImage(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Println(r.URL.Path, ":~", r.Header.Get("user-agent"))
|
fmt.Println(r.URL.Path, ":~", r.Header.Get("user-agent"))
|
||||||
|
|
||||||
@@ -39,15 +44,24 @@ func renderImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the font and language specifics based on the characters used
|
||||||
|
font, breakWords, err := getLanguage(event.Content)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "error getting font: "+err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// this turns the raw event.Content into a series of lines ready to drawn
|
||||||
lines := normalizeText(
|
lines := normalizeText(
|
||||||
replaceUserReferencesWithNames(r.Context(),
|
replaceUserReferencesWithNames(r.Context(),
|
||||||
renderQuotesAsBlockPrefixedText(r.Context(),
|
renderQuotesAsBlockPrefixedText(r.Context(),
|
||||||
event.Content,
|
event.Content,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
breakWords,
|
||||||
)
|
)
|
||||||
|
|
||||||
img, err := drawImage(lines, getPreviewStyle(r))
|
img, err := drawImage(lines, font, getPreviewStyle(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error writing image: %s", err)
|
log.Printf("error writing image: %s", err)
|
||||||
http.Error(w, "error writing image!", 500)
|
http.Error(w, "error writing image!", 500)
|
||||||
@@ -64,7 +78,7 @@ func renderImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeText(input []string) []string {
|
func normalizeText(input []string, breakWords bool) []string {
|
||||||
lines := make([]string, 0, MAX_LINES)
|
lines := make([]string, 0, MAX_LINES)
|
||||||
l := 0 // global line counter
|
l := 0 // global line counter
|
||||||
|
|
||||||
@@ -84,12 +98,47 @@ func normalizeText(input []string) []string {
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
line = wordwrap.Wrap(maxChars, strings.TrimSpace(line))
|
// turn a single line into multiple if it is long enough -- carefully splitting on word ends
|
||||||
for _, subline := range strings.Split(line, "\n") {
|
wrappedLines := strings.Split(wordwrap.Wrap(maxChars, strings.TrimSpace(line)), "\n")
|
||||||
|
|
||||||
|
// now we go over all these lines and further split them if necessary
|
||||||
|
// in japanese, for example, we must break the words otherwise nothing works
|
||||||
|
var sublines []string
|
||||||
|
if breakWords {
|
||||||
|
sublines = make([]string, 0, len(wrappedLines))
|
||||||
|
for _, wline := range wrappedLines {
|
||||||
|
// split until we have a bunch of lines all under maxChars
|
||||||
|
for {
|
||||||
|
if len(wline) > maxChars {
|
||||||
|
// we can't split exactly at maxChars because that would break utf-8 runes
|
||||||
|
// so we do this range mess to try to grab where the last rune in the line ends
|
||||||
|
subline := make([]rune, 0, maxChars)
|
||||||
|
var i int
|
||||||
|
var r rune
|
||||||
|
for i, r = range wline {
|
||||||
|
if i > maxChars {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
subline = append(subline, r)
|
||||||
|
}
|
||||||
|
sublines = append(sublines, string(subline))
|
||||||
|
wline = wline[i:]
|
||||||
|
} else {
|
||||||
|
sublines = append(sublines, wline)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sublines = wrappedLines
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, subline := range sublines {
|
||||||
// if a line has a word so big that it would overflow (like a nevent), hide it with an ellipsis
|
// if a line has a word so big that it would overflow (like a nevent), hide it with an ellipsis
|
||||||
if len(subline) > maxChars {
|
if len([]rune(subline)) > maxChars {
|
||||||
subline = subline[0:maxChars-1] + "…"
|
subline = subline[0:maxChars-1] + "…"
|
||||||
}
|
}
|
||||||
|
|
||||||
if quoting {
|
if quoting {
|
||||||
subline = BLOCK + " " + subline
|
subline = BLOCK + " " + subline
|
||||||
}
|
}
|
||||||
@@ -102,7 +151,7 @@ func normalizeText(input []string) []string {
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawImage(lines []string, style string) (image.Image, error) {
|
func drawImage(lines []string, font *truetype.Font, style string) (image.Image, error) {
|
||||||
width := 700
|
width := 700
|
||||||
height := 525
|
height := 525
|
||||||
paddingLeft := 0
|
paddingLeft := 0
|
||||||
@@ -120,11 +169,7 @@ func drawImage(lines []string, style string) (image.Image, error) {
|
|||||||
// draw the empty image
|
// draw the empty image
|
||||||
draw.Draw(img, img.Bounds(), bg, image.Point{}, draw.Src)
|
draw.Draw(img, img.Bounds(), bg, image.Point{}, draw.Src)
|
||||||
|
|
||||||
// create new freetype context to get ready for
|
// create new freetype context to get ready for adding text.
|
||||||
// adding text.
|
|
||||||
fontData, _ := os.ReadFile("fonts/NotoSansJP.ttf")
|
|
||||||
font, _ := freetype.ParseFont(fontData)
|
|
||||||
|
|
||||||
c := freetype.NewContext()
|
c := freetype.NewContext()
|
||||||
c.SetDPI(300)
|
c.SetDPI(300)
|
||||||
c.SetFont(font)
|
c.SetFont(font)
|
||||||
@@ -203,3 +248,57 @@ func renderQuotesAsBlockPrefixedText(ctx context.Context, input string) []string
|
|||||||
|
|
||||||
return blocks
|
return blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getLanguage(text string) (*truetype.Font, bool, error) {
|
||||||
|
fontName := "fonts/NotoSans.ttf"
|
||||||
|
shouldBreakWords := false
|
||||||
|
|
||||||
|
for _, group := range []struct {
|
||||||
|
lang *unicode.RangeTable
|
||||||
|
fontName string
|
||||||
|
breakWords bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
unicode.Katakana,
|
||||||
|
"fonts/NotoSansJP.ttf",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unicode.Hiragana,
|
||||||
|
"fonts/NotoSansJP.ttf",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
for _, rune := range text {
|
||||||
|
rune16 := uint16(rune)
|
||||||
|
for _, r16 := range group.lang.R16 {
|
||||||
|
if rune16 >= r16.Lo && rune16 <= r16.Hi {
|
||||||
|
fontName = group.fontName
|
||||||
|
shouldBreakWords = group.breakWords
|
||||||
|
goto gotLang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rune32 := uint32(rune)
|
||||||
|
for _, r32 := range group.lang.R32 {
|
||||||
|
if rune32 >= r32.Lo && rune32 <= r32.Hi {
|
||||||
|
fontName = group.fontName
|
||||||
|
shouldBreakWords = group.breakWords
|
||||||
|
goto gotLang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gotLang:
|
||||||
|
fontData, err := fonts.ReadFile(fontName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
font, err := freetype.ParseFont(fontData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return font, shouldBreakWords, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user