emoji.go

  1// package emoji is a extension for the goldmark(http://github.com/yuin/goldmark).
  2package emoji
  3
  4import (
  5	"fmt"
  6	"strings"
  7
  8	"github.com/yuin/goldmark"
  9	east "github.com/yuin/goldmark-emoji/ast"
 10	"github.com/yuin/goldmark-emoji/definition"
 11	"github.com/yuin/goldmark/ast"
 12	"github.com/yuin/goldmark/parser"
 13	"github.com/yuin/goldmark/renderer"
 14	"github.com/yuin/goldmark/renderer/html"
 15	"github.com/yuin/goldmark/text"
 16	"github.com/yuin/goldmark/util"
 17)
 18
 19// Option interface sets options for this extension.
 20type Option interface {
 21	emojiOption()
 22}
 23
 24// ParserConfig struct is a data structure that holds configuration of
 25// the Emoji extension.
 26type ParserConfig struct {
 27	Emojis definition.Emojis
 28}
 29
 30const optEmojis parser.OptionName = "EmojiEmojis"
 31
 32// SetOption implements parser.SetOptioner
 33func (c *ParserConfig) SetOption(name parser.OptionName, value interface{}) {
 34	switch name {
 35	case optEmojis:
 36		c.Emojis = value.(definition.Emojis)
 37	}
 38}
 39
 40// A ParserOption interface sets options for the emoji parser.
 41type ParserOption interface {
 42	Option
 43	parser.Option
 44
 45	SetEmojiOption(*ParserConfig)
 46}
 47
 48var _ ParserOption = &withEmojis{}
 49
 50type withEmojis struct {
 51	value definition.Emojis
 52}
 53
 54func (o *withEmojis) emojiOption() {}
 55
 56func (o *withEmojis) SetParserOption(c *parser.Config) {
 57	c.Options[optEmojis] = o.value
 58}
 59
 60func (o *withEmojis) SetEmojiOption(c *ParserConfig) {
 61	c.Emojis = o.value
 62}
 63
 64// WithMaping is a functional option that defines links names to unicode emojis.
 65func WithEmojis(value definition.Emojis) Option {
 66	return &withEmojis{
 67		value: value,
 68	}
 69}
 70
 71// RenderingMethod indicates how emojis are rendered.
 72type RenderingMethod int
 73
 74// RendererFunc will be used for rendering emojis.
 75type RendererFunc func(w util.BufWriter, source []byte, n *east.Emoji, config *RendererConfig)
 76
 77const (
 78	// Entity renders an emoji as an html entity.
 79	Entity RenderingMethod = iota
 80
 81	// Unicode renders an emoji as unicode character.
 82	Unicode
 83
 84	// Twemoji renders an emoji as an img tag with [twemoji](https://github.com/twitter/twemoji).
 85	Twemoji
 86
 87	// Func renders an emoji using RendererFunc.
 88	Func
 89)
 90
 91// RendererConfig struct holds options for the emoji renderer.
 92type RendererConfig struct {
 93	html.Config
 94
 95	// Method indicates how emojis are rendered.
 96	Method RenderingMethod
 97
 98	// TwemojiTemplate is a printf template for twemoji. This value is valid only when Method is set to Twemoji.
 99	// `printf` arguments are:
100	//
101	//     1: name (e.g. "face with tears of joy")
102	//     2: file name without an extension (e.g. 1f646-2642)
103	//     3: '/' if XHTML, otherwise ''
104	//
105	TwemojiTemplate string
106
107	// RendererFunc is a RendererFunc that renders emojis. This value is valid only when Method is set to Func.
108	RendererFunc RendererFunc
109}
110
111// DefaultTwemojiTemplate is a default value for RendererConfig.TwemojiTemplate.
112const DefaultTwemojiTemplate = `<img class="emoji" draggable="false" alt="%[1]s" src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/72x72/%[2]s.png"%[3]s>`
113
114// SetOption implements renderer.SetOptioner.
115func (c *RendererConfig) SetOption(name renderer.OptionName, value interface{}) {
116	switch name {
117	case optRenderingMethod:
118		c.Method = value.(RenderingMethod)
119	case optTwemojiTemplate:
120		c.TwemojiTemplate = value.(string)
121	case optRendererFunc:
122		c.RendererFunc = value.(RendererFunc)
123	default:
124		c.Config.SetOption(name, value)
125	}
126}
127
128// A RendererOption interface sets options for the emoji renderer.
129type RendererOption interface {
130	Option
131	renderer.Option
132
133	SetEmojiOption(*RendererConfig)
134}
135
136var _ RendererOption = &withRenderingMethod{}
137
138type withRenderingMethod struct {
139	value RenderingMethod
140}
141
142func (o *withRenderingMethod) emojiOption() {
143}
144
145// SetConfig implements renderer.Option#SetConfig.
146func (o *withRenderingMethod) SetConfig(c *renderer.Config) {
147	c.Options[optRenderingMethod] = o.value
148}
149
150// SetEmojiOption implements RendererOption#SetEmojiOption
151func (o *withRenderingMethod) SetEmojiOption(c *RendererConfig) {
152	c.Method = o.value
153}
154
155const optRenderingMethod renderer.OptionName = "EmojiRenderingMethod"
156
157// WithRenderingMethod is a functional option that indicates how emojis are rendered.
158func WithRenderingMethod(a RenderingMethod) Option {
159	return &withRenderingMethod{a}
160}
161
162type withTwemojiTemplate struct {
163	value string
164}
165
166func (o *withTwemojiTemplate) emojiOption() {
167}
168
169// SetConfig implements renderer.Option#SetConfig.
170func (o *withTwemojiTemplate) SetConfig(c *renderer.Config) {
171	c.Options[optTwemojiTemplate] = o.value
172}
173
174// SetEmojiOption implements RendererOption#SetEmojiOption
175func (o *withTwemojiTemplate) SetEmojiOption(c *RendererConfig) {
176	c.TwemojiTemplate = o.value
177}
178
179const optTwemojiTemplate renderer.OptionName = "EmojiTwemojiTemplate"
180
181// WithTwemojiTemplate is a functional option that changes a twemoji img tag.
182func WithTwemojiTemplate(s string) Option {
183	return &withTwemojiTemplate{s}
184}
185
186var _ RendererOption = &withRendererFunc{}
187
188type withRendererFunc struct {
189	value RendererFunc
190}
191
192func (o *withRendererFunc) emojiOption() {
193}
194
195// SetConfig implements renderer.Option#SetConfig.
196func (o *withRendererFunc) SetConfig(c *renderer.Config) {
197	c.Options[optRendererFunc] = o.value
198}
199
200// SetEmojiOption implements RendererOption#SetEmojiOption
201func (o *withRendererFunc) SetEmojiOption(c *RendererConfig) {
202	c.RendererFunc = o.value
203}
204
205const optRendererFunc renderer.OptionName = "EmojiRendererFunc"
206
207// WithRendererFunc is a functional option that changes a renderer func.
208func WithRendererFunc(f RendererFunc) Option {
209	return &withRendererFunc{f}
210}
211
212type emojiParser struct {
213	ParserConfig
214}
215
216// NewParser returns a new parser.InlineParser that can parse emoji expressions.
217func NewParser(opts ...ParserOption) parser.InlineParser {
218	p := &emojiParser{
219		ParserConfig: ParserConfig{
220			Emojis: definition.Github(),
221		},
222	}
223	for _, o := range opts {
224		o.SetEmojiOption(&p.ParserConfig)
225	}
226	return p
227}
228
229func (s *emojiParser) Trigger() []byte {
230	return []byte{':'}
231}
232
233func (s *emojiParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
234	line, _ := block.PeekLine()
235	if len(line) < 1 {
236		return nil
237	}
238	i := 1
239	for ; i < len(line); i++ {
240		c := line[i]
241		if !(util.IsAlphaNumeric(c) || c == '_' || c == '-' || c == '+') {
242			break
243		}
244	}
245	if i >= len(line) || line[i] != ':' {
246		return nil
247	}
248	block.Advance(i + 1)
249	shortName := line[1:i]
250	emoji, ok := s.Emojis.Get(util.BytesToReadOnlyString(shortName))
251	if !ok {
252		return nil
253	}
254	return east.NewEmoji(shortName, emoji)
255}
256
257type emojiHTMLRenderer struct {
258	RendererConfig
259}
260
261// NewHTMLRenderer returns a new HTMLRenderer.
262func NewHTMLRenderer(opts ...RendererOption) renderer.NodeRenderer {
263	r := &emojiHTMLRenderer{
264		RendererConfig: RendererConfig{
265			Config:          html.NewConfig(),
266			Method:          Entity,
267			TwemojiTemplate: DefaultTwemojiTemplate,
268			RendererFunc:    nil,
269		},
270	}
271	for _, opt := range opts {
272		opt.SetEmojiOption(&r.RendererConfig)
273	}
274	return r
275}
276
277// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
278func (r *emojiHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
279	reg.Register(east.KindEmoji, r.renderEmoji)
280}
281
282const slash = " /"
283const empty = ""
284
285func (r *emojiHTMLRenderer) renderEmoji(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
286	if !entering {
287		return ast.WalkContinue, nil
288	}
289	node := n.(*east.Emoji)
290	if !node.Value.IsUnicode() && r.Method != Func {
291		fmt.Fprintf(w, `<span title="%s">:%s:</span>`, util.EscapeHTML(util.StringToReadOnlyBytes(node.Value.Name)), node.ShortName)
292		return ast.WalkContinue, nil
293	}
294
295	switch r.Method {
296	case Entity:
297		for _, r := range node.Value.Unicode {
298			if r == 0x200D {
299				_, _ = w.WriteString("&zwj;")
300				continue
301			}
302			fmt.Fprintf(w, "&#x%x;", r)
303		}
304	case Unicode:
305		fmt.Fprintf(w, "%s", string(node.Value.Unicode))
306	case Twemoji:
307		s := slash
308		if !r.XHTML {
309			s = empty
310		}
311		values := []string{}
312		for _, r := range node.Value.Unicode {
313			values = append(values, fmt.Sprintf("%x", r))
314		}
315		fmt.Fprintf(w, r.TwemojiTemplate, util.EscapeHTML(util.StringToReadOnlyBytes(node.Value.Name)), strings.Join(values, "-"), s)
316	case Func:
317		r.RendererFunc(w, source, node, &r.RendererConfig)
318	}
319	return ast.WalkContinue, nil
320}
321
322type emoji struct {
323	options []Option
324}
325
326// Emoji is a goldmark.Extender implementation.
327var Emoji = &emoji{
328	options: []Option{},
329}
330
331// New returns a new extension with given options.
332func New(opts ...Option) goldmark.Extender {
333	return &emoji{
334		options: opts,
335	}
336}
337
338// Extend implements goldmark.Extender.
339func (e *emoji) Extend(m goldmark.Markdown) {
340	pOpts := []ParserOption{}
341	rOpts := []RendererOption{}
342	for _, o := range e.options {
343		if po, ok := o.(ParserOption); ok {
344			pOpts = append(pOpts, po)
345			continue
346		}
347		if ro, ok := o.(RendererOption); ok {
348			rOpts = append(rOpts, ro)
349		}
350	}
351
352	m.Renderer().AddOptions(renderer.WithNodeRenderers(
353		util.Prioritized(NewHTMLRenderer(rOpts...), 200),
354	))
355
356	m.Parser().AddOptions(parser.WithInlineParsers(
357		util.Prioritized(NewParser(pOpts...), 999),
358	))
359
360}