| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 |
- """
- Emoji.
- pymdownx.emoji
- Emoji extension for EmojiOne's, GitHub's, or Twemoji's gemoji.
- MIT license.
- Copyright (c) 2016 - 2017 Isaac Muse <isaacmuse@gmail.com>
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
- documentation files (the "Software"), to deal in the Software without restriction, including without limitation
- the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
- and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or substantial portions
- of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
- TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
- CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- DEALINGS IN THE SOFTWARE.
- """
- from markdown import Extension
- from markdown.inlinepatterns import InlineProcessor
- from markdown import util as md_util
- import xml.etree.ElementTree as etree
- import inspect
- import copy
- import warnings
- from . import util
- RE_EMOJI = r'(:[+\-\w]+:)'
- SUPPORTED_INDEXES = ('emojione', 'gemoji', 'twemoji')
- UNICODE_VARIATION_SELECTOR_16 = 'fe0f'
- EMOJIONE_SVG_CDN = 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.7/assets/svg/'
- EMOJIONE_PNG_CDN = 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.7/assets/png/'
- TWEMOJI_SVG_CDN = 'https://twemoji.maxcdn.com/v/latest/svg/'
- TWEMOJI_PNG_CDN = 'https://twemoji.maxcdn.com/v/latest/72x72/'
- GITHUB_UNICODE_CDN = 'https://github.githubassets.com/images/icons/emoji/unicode/'
- GITHUB_CDN = 'https://github.githubassets.com/images/icons/emoji/'
- NO_TITLE = 'none'
- LONG_TITLE = 'long'
- SHORT_TITLE = 'short'
- VALID_TITLE = (LONG_TITLE, SHORT_TITLE, NO_TITLE)
- UNICODE_ENTITY = 'html_entity'
- UNICODE_ALT = ('unicode', UNICODE_ENTITY)
- LEGACY_ARG_COUNT = 8
- MSG_INDEX_WARN = """Using emoji indexes with no arguments is now deprecated.
- Emoji indexes now take 2 arguments: 'options' and 'md'.
- Please update your custom index accordingly.
- """
- def add_attriubtes(options, attributes):
- """Add additional attributes from options."""
- attr = options.get('attributes', {})
- if attr:
- for k, v in attr.items():
- attributes[k] = v
- def emojione(options, md):
- """The EmojiOne index."""
- from . import emoji1_db as emoji_map
- return {
- "name": emoji_map.name,
- "emoji": copy.deepcopy(emoji_map.emoji),
- "aliases": copy.deepcopy(emoji_map.aliases)
- }
- def gemoji(options, md):
- """The Gemoji index."""
- from . import gemoji_db as emoji_map
- return {
- "name": emoji_map.name,
- "emoji": copy.deepcopy(emoji_map.emoji),
- "aliases": copy.deepcopy(emoji_map.aliases)
- }
- def twemoji(options, md):
- """The Twemoji index."""
- from . import twemoji_db as emoji_map
- return {
- "name": emoji_map.name,
- "emoji": copy.deepcopy(emoji_map.emoji),
- "aliases": copy.deepcopy(emoji_map.aliases)
- }
- ###################
- # Converters
- ###################
- def to_png(index, shortname, alias, uc, alt, title, category, options, md):
- """Return PNG element."""
- if index == 'gemoji':
- def_image_path = GITHUB_UNICODE_CDN
- def_non_std_image_path = GITHUB_CDN
- elif index == 'twemoji':
- def_image_path = TWEMOJI_PNG_CDN
- def_image_path = TWEMOJI_PNG_CDN
- else:
- def_image_path = EMOJIONE_PNG_CDN
- def_non_std_image_path = EMOJIONE_PNG_CDN
- is_unicode = uc is not None
- classes = options.get('classes', index)
- # In general we can use the alias, but github specific images don't have one for each alias.
- # We can tell we have a github specific if there is no Unicode value.
- if is_unicode:
- image_path = options.get('image_path', def_image_path)
- else:
- image_path = options.get('non_standard_image_path', def_non_std_image_path)
- src = "%s%s.png" % (
- image_path,
- uc if is_unicode else shortname[1:-1]
- )
- attributes = {
- "class": classes,
- "alt": alt,
- "src": src
- }
- if title:
- attributes['title'] = title
- add_attriubtes(options, attributes)
- return etree.Element("img", attributes)
- def to_svg(index, shortname, alias, uc, alt, title, category, options, md):
- """Return SVG element."""
- if index == 'twemoji':
- svg_path = TWEMOJI_SVG_CDN
- else:
- svg_path = EMOJIONE_SVG_CDN
- attributes = {
- "class": options.get('classes', index),
- "alt": alt,
- "src": "%s%s.svg" % (
- options.get('image_path', svg_path),
- uc
- )
- }
- if title:
- attributes['title'] = title
- add_attriubtes(options, attributes)
- return etree.Element("img", attributes)
- def to_png_sprite(index, shortname, alias, uc, alt, title, category, options, md):
- """Return PNG sprite element."""
- attributes = {
- "class": '%(class)s-%(size)s-%(category)s _%(unicode)s' % {
- "class": options.get('classes', index),
- "size": options.get('size', '64'),
- "category": (category if category else ''),
- "unicode": uc
- }
- }
- if title:
- attributes['title'] = title
- add_attriubtes(options, attributes)
- el = etree.Element("span", attributes)
- el.text = md_util.AtomicString(alt)
- return el
- def to_svg_sprite(index, shortname, alias, uc, alt, title, category, options, md):
- """
- Return SVG sprite element.
- ```
- <svg class="%(classes)s"><description>%(alt)s</description>
- <use xlink:href="%(sprite)s#emoji-%(unicode)s"></use></svg>
- ```
- """
- xlink_href = '%s#emoji-%s' % (
- options.get('image_path', './../assets/sprites/emojione.sprites.svg'), uc
- )
- svg = etree.Element("svg", {"class": options.get('classes', index)})
- desc = etree.SubElement(svg, 'description')
- desc.text = md_util.AtomicString(alt)
- etree.SubElement(svg, 'use', {'xlink:href': xlink_href})
- return svg
- def to_alt(index, shortname, alias, uc, alt, title, category, options, md):
- """Return html entities."""
- return md.htmlStash.store(alt)
- ###################
- # Classes
- ###################
- class EmojiPattern(InlineProcessor):
- """Return element of type `tag` with a text attribute of group(2) of an `InlineProcessor`."""
- def __init__(self, pattern, config, md):
- """Initialize."""
- InlineProcessor.__init__(self, pattern, md)
- title = config['title']
- alt = config['alt']
- self.options = config['options']
- self._set_index(config["emoji_index"])
- self.unicode_alt = alt in UNICODE_ALT
- self.encoded_alt = alt == UNICODE_ENTITY
- self.remove_var_sel = config['remove_variation_selector']
- self.title = title if title in VALID_TITLE else NO_TITLE
- self.generator = config['emoji_generator']
- def _set_index(self, index):
- """Set the index."""
- if len(inspect.getfullargspec(index).args):
- self.emoji_index = index(self.options, self.md)
- else:
- warnings.warn(MSG_INDEX_WARN, util.PymdownxDeprecationWarning)
- self.emoji_index = index()
- def _remove_variation_selector(self, value):
- """Remove variation selectors."""
- return value.replace('-' + UNICODE_VARIATION_SELECTOR_16, '')
- def _get_unicode_char(self, value):
- """Get the Unicode char."""
- return ''.join([util.get_char(int(c, 16)) for c in value.split('-')])
- def _get_unicode(self, emoji):
- """
- Get Unicode and Unicode alt.
- Unicode: This is the stripped down form of the Unicode, no joining chars and no variation chars.
- Unicode code points are not always valid. If this is present and there is no 'unicode_alt',
- Unicode code points can be counted on as valid. For the most part, the returned `uc` should
- be used to reference image files, or create classes, but for inserting actual Unicode, 'uc_alt'
- should be used.
- Unicode Alt: When present, this will always be valid Unicode points. This contains not just the
- needed characters to identify the Unicode emoji, but the formatting as well. Joining characters
- and variation characters will be present. If you don't want variation chars, enable the global
- 'remove_variation_selector' option.
- If using gemoji, it is possible you will get no Unicode and no Unicode alt. This occurs with emoji
- like `:octocat:`. `:octocat:` is not a real emoji and has no Unicode code points, but it is provided by
- gemoji as an emoji anyways.
- """
- uc = emoji.get('unicode')
- uc_alt = emoji.get('unicode_alt', uc)
- if uc_alt and self.remove_var_sel:
- uc_alt = self._remove_variation_selector(uc_alt)
- return uc, uc_alt
- def _get_title(self, shortname, emoji):
- """Get the title."""
- if self.title == LONG_TITLE:
- title = emoji['name']
- elif self.title == SHORT_TITLE:
- title = shortname
- else:
- title = None
- return title
- def _get_alt(self, shortname, uc_alt):
- """Get alt form."""
- if uc_alt is None or not self.unicode_alt:
- alt = shortname
- else:
- alt = self._get_unicode_char(uc_alt)
- if self.encoded_alt:
- alt = ''.join(
- [md_util.AMP_SUBSTITUTE + ('#x%04x;' % util.get_ord(point)) for point in util.get_code_points(alt)]
- )
- return alt
- def _get_category(self, emoji):
- """Get the category."""
- return emoji.get('category')
- def handleMatch(self, m, data):
- """Handle emoji pattern matches."""
- el = m.group(1)
- shortname = self.emoji_index['aliases'].get(el, el)
- alias = None if shortname == el else el
- emoji = self.emoji_index['emoji'].get(shortname, None)
- if emoji:
- uc, uc_alt = self._get_unicode(emoji)
- title = self._get_title(el, emoji)
- alt = self._get_alt(el, uc_alt)
- category = self._get_category(emoji)
- el = self.generator(
- self.emoji_index['name'],
- shortname,
- alias,
- uc,
- alt,
- title,
- category,
- self.options,
- self.md
- )
- return el, m.start(0), m.end(0)
- class EmojiExtension(Extension):
- """Add emoji extension to Markdown class."""
- def __init__(self, *args, **kwargs):
- """Initialize."""
- self.config = {
- 'emoji_index': [
- emojione,
- "Function that returns the desired emoji index. - Default: 'pymdownx.emoji.emojione'"
- ],
- 'emoji_generator': [
- to_png,
- "Emoji generator method. - Default: pymdownx.emoji.to_png"
- ],
- 'title': [
- 'short',
- "What title to use on images. You can use 'long' which shows the long name, "
- "'short' which shows the shortname (:short:), or 'none' which shows no title. "
- "- Default: 'short'"
- ],
- 'alt': [
- 'unicode',
- "Control alt form. 'short' sets alt to the shortname (:short:), 'uniocde' sets "
- "alt to the raw Unicode value, and 'html_entity' sets alt to the HTML entity. "
- "- Default: 'unicode'"
- ],
- 'remove_variation_selector': [
- False,
- "Remove variation selector 16 from unicode. - Default: False"
- ],
- 'options': [
- {},
- "Emoji options see documentation for options for github and emojione."
- ]
- }
- super(EmojiExtension, self).__init__(*args, **kwargs)
- def extendMarkdown(self, md):
- """Add support for emoji."""
- config = self.getConfigs()
- util.escape_chars(md, [':'])
- md.inlinePatterns.register(EmojiPattern(RE_EMOJI, config, md), "emoji", 75)
- ###################
- # Make Available
- ###################
- def makeExtension(*args, **kwargs):
- """Return extension."""
- return EmojiExtension(*args, **kwargs)
|