using a custom RunIterator that takes emojis into account and shapes them with a different font.

it doesn't actually work since the different outputs returned are treated by LineWrapper as necessarily belonging to different lines, so we'll have to do something different.
This commit is contained in:
fiatjaf
2023-12-26 11:02:43 -03:00
parent b8600ba205
commit 86f6a09ea3
4 changed files with 107 additions and 25 deletions

View File

@@ -12,11 +12,14 @@ import (
"strings"
"time"
"github.com/dmolesUC3/emoji"
"github.com/fogleman/gg"
"github.com/go-text/typesetting/di"
"github.com/go-text/typesetting/font"
"github.com/go-text/typesetting/language"
"github.com/go-text/typesetting/shaping"
"github.com/pemistahl/lingua-go"
"golang.org/x/image/math/fixed"
)
const nSupportedScripts = 13
@@ -325,3 +328,81 @@ func shortenURLs(text string) string {
return strings.Replace(urlStr, "/////", strings.Join(pathParts, "/"), 1)
})
}
type shapedOutputIterator struct {
rawText []rune
idx int
savedIdx int
shaper *shaping.HarfbuzzShaper
fontSize int
face font.Face
language language.Language
script language.Script
direction di.Direction
}
var _ shaping.RunIterator = (*shapedOutputIterator)(nil)
func (it *shapedOutputIterator) Next() (int, shaping.Output, bool) {
idx, nextIdx, run, ok := it.readNext()
if ok {
it.idx = nextIdx
}
return idx, run, ok
}
func (it *shapedOutputIterator) Peek() (int, shaping.Output, bool) {
idx, _, out, more := it.readNext()
return idx, out, more
}
func (it *shapedOutputIterator) readNext() (int, int, shaping.Output, bool) {
if it.idx >= len(it.rawText) {
return it.idx, -1, shaping.Output{}, false
}
// if the next character is an emoji then return a block of emojis
if emoji.IsEmoji(it.rawText[it.idx]) {
shapedEmoji := it.shaper.Shape(shaping.Input{
Text: it.rawText,
RunStart: it.idx,
RunEnd: it.idx + 1,
Face: emojiFont,
Size: fixed.I(int(it.fontSize)),
Script: it.script,
Language: it.language,
Direction: it.direction,
})
return it.idx, it.idx + 1, shapedEmoji, true
}
// otherwise we consume runes until we find an emoji and return everything
var runesConsumed int = 0
for r, rn := range it.rawText[it.idx:] {
if emoji.IsEmoji(rn) {
// reached an emoji, stop now
break
}
runesConsumed = r
}
shapedRunes := it.shaper.Shape(shaping.Input{
Text: it.rawText,
RunStart: it.idx,
RunEnd: it.idx + runesConsumed + 1,
Face: it.face,
Size: fixed.I(int(it.fontSize)),
Script: it.script,
Language: it.language,
Direction: it.direction,
})
return it.idx, it.idx + runesConsumed + 1, shapedRunes, true
}
func (it *shapedOutputIterator) Save() {
it.savedIdx = it.idx
}
func (it *shapedOutputIterator) Restore() {
it.idx = it.savedIdx
}