diff --git a/go.mod b/go.mod index f9ba957..e50ab13 100644 --- a/go.mod +++ b/go.mod @@ -77,5 +77,3 @@ require ( google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/nbd-wtf/emoji => /home/fiatjaf/comp/emoji diff --git a/go.sum b/go.sum index 6de56bb..2a770c0 100644 --- a/go.sum +++ b/go.sum @@ -152,8 +152,8 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw= github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8= -github.com/nbd-wtf/emoji v0.0.2 h1:5Bgkh7JOvnW5yAo+QPyFrtXLtPKr4vBgs9RJWlCX2Ho= -github.com/nbd-wtf/emoji v0.0.2/go.mod h1:tS6D9iI34qwBmWc5g8X7tVDkWXulqbTJRsvsM6QsS88= +github.com/nbd-wtf/emoji v0.0.3 h1:YtkT7MVPXvqU1SQjvC/CShlWexnREzqNCxmhUnL00CA= +github.com/nbd-wtf/emoji v0.0.3/go.mod h1:tS6D9iI34qwBmWc5g8X7tVDkWXulqbTJRsvsM6QsS88= github.com/nbd-wtf/go-nostr v0.27.3 h1:u9fdP5h+Ap3LcDFD2j6F2buU/xOM9ddMY0LCDcC6ZyY= github.com/nbd-wtf/go-nostr v0.27.3/go.mod h1:e5WOFsKEpslDOxIgK00NqemH7KuAvKIW6sBXeJYCfUs= github.com/nbd-wtf/nostr-sdk v0.0.4 h1:vMCiYpFElKMHPXpZjFVEq4utoTLdTYbkqXVYH1/4uzs= diff --git a/image_utils.go b/image_utils.go index 2e72e71..52d9922 100644 --- a/image_utils.go +++ b/image_utils.go @@ -3,7 +3,6 @@ package main import ( "bytes" "context" - "fmt" "image" "image/color" "image/draw" @@ -414,61 +413,73 @@ func shapeText(rawText []rune, fontSize int) (shaping.Output, []bool) { } // this will be used to determine whether a given glyph is an emoji or not when rendering - emojiMask := make([]bool, 0, len(mainBuffer.Info)) + emojiMask := make([]bool, len(emojiBuffer.Info)) - // convert the shaped text into an output - glyphs := make([]shaping.Glyph, 0, len(mainBuffer.Info)) - g := -1 - fmt.Printf("[::] %s %X\n", string(rawText), rawText) - for i := 0; i < len(mainBuffer.Info); i++ { - g++ - var buf *harfbuzz.Buffer - var font *harfbuzz.Font - if chars, visChars := emoji.GetNextEmojiCharacters(rawText[i:], len(rawText)-i); chars > 0 { - buf = emojiBuffer - font = emojiFont - emojiMask = append(emojiMask, true) + if len(mainBuffer.Info) > len(emojiBuffer.Info) { + // remove from mainBuffer characters that are not present in emojiBuffer + newMainBufferInfo := make([]harfbuzz.GlyphInfo, len(emojiBuffer.Info)) + newMainBufferPos := make([]harfbuzz.GlyphPosition, len(emojiBuffer.Info)) + for e, m := 0, 0; e < len(emojiBuffer.Info); { + ec := emojiBuffer.Info[e].Codepoint + if ec == mainBuffer.Info[m].Codepoint { + newMainBufferInfo[e] = mainBuffer.Info[m] + newMainBufferPos[e] = mainBuffer.Pos[m] - // remove the invalid glyphs from mainBuffer - fmt.Println(chars, visChars) - if chars > 1 { - cutN := chars - 1 - - for _, g := range mainBuffer.Info[g+1 : len(mainBuffer.Info[g+1+cutN:])] { - fmt.Printf(" excluding %s %X \n", string(g.Codepoint), g.Codepoint) + if emoji.IsEmoji(ec) || emoji.IsTag(ec) || emoji.IsRegionalIndicator(ec) { + emojiMask[e] = true } - copy(mainBuffer.Info[g+1:], mainBuffer.Info[g+1+cutN:]) - mainBuffer.Info = mainBuffer.Info[0 : len(mainBuffer.Info)-cutN] - copy(mainBuffer.Pos[g+1:], mainBuffer.Pos[g+1+cutN:]) - mainBuffer.Pos = mainBuffer.Pos[0 : len(mainBuffer.Pos)-cutN] - i += chars - 1 + e++ + m++ + } else { + m++ + for ; emojiBuffer.Info[e].Codepoint != mainBuffer.Info[m].Codepoint; m++ { + } } + } + mainBuffer.Info = newMainBufferInfo + mainBuffer.Pos = newMainBufferPos + } else { + // just go through the glyphs and decide which ones are emojis + for e := range emojiBuffer.Info { + ec := emojiBuffer.Info[e].Codepoint + if emoji.IsEmoji(ec) || emoji.IsTag(ec) || emoji.IsRegionalIndicator(ec) { + emojiMask[e] = true + } + } + } + + // convert the shaped text into an output + glyphs := make([]shaping.Glyph, len(mainBuffer.Info)) + for i := 0; i < len(glyphs); i++ { + var buf *harfbuzz.Buffer + var font *harfbuzz.Font + if emojiMask[i] { + buf = emojiBuffer + font = emojiFont } else { - emojiMask = append(emojiMask, false) buf = mainBuffer font = mainFont } + glyph := buf.Info[i] - glyph := buf.Info[g] - glyphs = append(glyphs, shaping.Glyph{ + glyphs[i] = shaping.Glyph{ ClusterIndex: glyph.Cluster, GlyphID: glyph.Glyph, Mask: glyph.Mask, - }) + } extents, ok := font.GlyphExtents(glyph.Glyph) if !ok { continue } - idx := len(glyphs) - 1 - glyphs[idx].Width = fixed.I(int(extents.Width)) >> scaleShift - glyphs[idx].Height = fixed.I(int(extents.Height)) >> scaleShift - glyphs[idx].XBearing = fixed.I(int(extents.XBearing)) >> scaleShift - glyphs[idx].YBearing = fixed.I(int(extents.YBearing)) >> scaleShift - glyphs[idx].XAdvance = fixed.I(int(buf.Pos[g].XAdvance)) >> scaleShift - glyphs[idx].YAdvance = fixed.I(int(buf.Pos[g].YAdvance)) >> scaleShift - glyphs[idx].XOffset = fixed.I(int(buf.Pos[g].XOffset)) >> scaleShift - glyphs[idx].YOffset = fixed.I(int(buf.Pos[g].YOffset)) >> scaleShift + glyphs[i].Width = fixed.I(int(extents.Width)) >> scaleShift + glyphs[i].Height = fixed.I(int(extents.Height)) >> scaleShift + glyphs[i].XBearing = fixed.I(int(extents.XBearing)) >> scaleShift + glyphs[i].YBearing = fixed.I(int(extents.YBearing)) >> scaleShift + glyphs[i].XAdvance = fixed.I(int(buf.Pos[i].XAdvance)) >> scaleShift + glyphs[i].YAdvance = fixed.I(int(buf.Pos[i].YAdvance)) >> scaleShift + glyphs[i].XOffset = fixed.I(int(buf.Pos[i].XOffset)) >> scaleShift + glyphs[i].YOffset = fixed.I(int(buf.Pos[i].YOffset)) >> scaleShift } countClusters(glyphs, input.RunEnd, input.Direction.Progression()) @@ -492,6 +503,46 @@ func shapeText(rawText []rune, fontSize int) (shaping.Output, []bool) { return out, emojiMask } +// this function is copied from go-text/typesetting/shaping because shapeText needs it +func countClusters(glyphs []shaping.Glyph, textLen int, dir di.Progression) { + currentCluster := -1 + runesInCluster := 0 + glyphsInCluster := 0 + previousCluster := textLen + for i := range glyphs { + g := glyphs[i].ClusterIndex + if g != currentCluster { + // If we're processing a new cluster, count the runes and glyphs + // that compose it. + runesInCluster = 0 + glyphsInCluster = 1 + currentCluster = g + nextCluster := -1 + glyphCountLoop: + for k := i + 1; k < len(glyphs); k++ { + if glyphs[k].ClusterIndex == g { + glyphsInCluster++ + } else { + nextCluster = glyphs[k].ClusterIndex + break glyphCountLoop + } + } + if nextCluster == -1 { + nextCluster = textLen + } + switch dir { + case di.FromTopLeft: + runesInCluster = nextCluster - currentCluster + case di.TowardTopLeft: + runesInCluster = previousCluster - currentCluster + } + previousCluster = g + } + glyphs[i].GlyphCount = glyphsInCluster + glyphs[i].RuneCount = runesInCluster + } +} + // this function is copied from go-text/render, but adapted to not require a "class" to be instantiated and also, // more importantly, to take an emojiMask parameter, with the same length as out.Glyphs, to determine when a // glyph should be rendered with the emoji font instead of with the default font @@ -562,46 +613,6 @@ func drawOutline(g shaping.Glyph, bitmap api.GlyphOutline, f *rasterx.Filler, sc f.Stop(true) } -// this function is copied from go-text/typesetting/shaping because shapeText needs it -func countClusters(glyphs []shaping.Glyph, textLen int, dir di.Progression) { - currentCluster := -1 - runesInCluster := 0 - glyphsInCluster := 0 - previousCluster := textLen - for i := range glyphs { - g := glyphs[i].ClusterIndex - if g != currentCluster { - // If we're processing a new cluster, count the runes and glyphs - // that compose it. - runesInCluster = 0 - glyphsInCluster = 1 - currentCluster = g - nextCluster := -1 - glyphCountLoop: - for k := i + 1; k < len(glyphs); k++ { - if glyphs[k].ClusterIndex == g { - glyphsInCluster++ - } else { - nextCluster = glyphs[k].ClusterIndex - break glyphCountLoop - } - } - if nextCluster == -1 { - nextCluster = textLen - } - switch dir { - case di.FromTopLeft: - runesInCluster = nextCluster - currentCluster - case di.TowardTopLeft: - runesInCluster = previousCluster - currentCluster - } - previousCluster = g - } - glyphs[i].GlyphCount = glyphsInCluster - glyphs[i].RuneCount = runesInCluster - } -} - func fixed266ToFloat(i fixed.Int26_6) float32 { return float32(float64(i) / 64) }