emoji sprites: Avoid hard coding CSS percentages.

This commit changes the calculation of the
background-size parameter that we use to
render emojis from sprite sheets.

In particular, it now makes the parameter
match the sizes of our latest sprite
sheets from Twitter/Google.

This should fix the geometry aspect of #13959,
but we also need to fix some issues with the
cache being sticky.

There is also some minor cleanup:

    - Remove obsolete -moz/-webkit CSS.
    - Remove needless precision in percentages.
    - Fix the transposed nrows/ncols names.
    - Add extensive commenting.

Finally, we add a minor bump to the provision
number.  This commit should be merged in the
same series as the other fix for this issue,
which will probably have a major bump, and we'll
need to rebase this appropriately.
This commit is contained in:
Steve Howell 2020-02-20 20:35:27 +00:00 committed by Tim Abbott
parent 621716bf30
commit da1ce9a577
2 changed files with 75 additions and 12 deletions

View File

@ -30,9 +30,7 @@ span.emoji
{ {
display: inline-block; display: inline-block;
background-image: url('sheet-%(emojiset)s-64.png'); background-image: url('sheet-%(emojiset)s-64.png');
-webkit-background-size: 5200%%; background-size: %(background_size)s;
-moz-background-size: 5200%%;
background-size: 5200%%;
background-repeat: no-repeat; background-repeat: no-repeat;
/* Hide the text. */ /* Hide the text. */
@ -53,7 +51,7 @@ span.emoji
EMOJI_POS_INFO_TEMPLATE = """\ EMOJI_POS_INFO_TEMPLATE = """\
.emoji-%(codepoint)s { .emoji-%(codepoint)s {
background-position: %(pos_x)s%% %(pos_y)s%%; background-position: %(pos_x)s %(pos_y)s;
} }
""" """
@ -83,6 +81,9 @@ def get_success_stamp() -> str:
sha1_hexdigest = generate_sha1sum_emoji(ZULIP_PATH) sha1_hexdigest = generate_sha1sum_emoji(ZULIP_PATH)
return os.path.join(EMOJI_CACHE_PATH, sha1_hexdigest, 'emoji', '.success-stamp') return os.path.join(EMOJI_CACHE_PATH, sha1_hexdigest, 'emoji', '.success-stamp')
def percent(f: float) -> str:
return '%0.3f%%' % (f * 100,)
def generate_sprite_css_files(cache_path: str, def generate_sprite_css_files(cache_path: str,
emoji_data: List[Dict[str, Any]], emoji_data: List[Dict[str, Any]],
emojiset: str) -> None: emojiset: str) -> None:
@ -95,11 +96,33 @@ def generate_sprite_css_files(cache_path: str,
max_val = max(max_val, img_info[field]) max_val = max(max_val, img_info[field])
return max_val return max_val
# Spritesheet CSS generation code. Spritesheets are squared using """
# padding, so we have to take only the maximum of two dimensions. Spritesheets are usually NxN squares, and we have to
nrows = get_max_val('sheet_x', emoji_data) infer N from the sheet_x/sheet_y values of emojis.
ncols = get_max_val('sheet_y', emoji_data) """
max_dim = max(nrows, ncols) max_x = get_max_val('sheet_x', emoji_data)
max_y = get_max_val('sheet_y', emoji_data)
n = max(max_x, max_y) + 1
"""
Each single emoji is 64x64, with 1px gutters on every border.
We just consider the gutters to be part of the image for
simplicity reasons, so you can think of the spritesheet as
an NxN square of 66x66 pre-padded emojis. The CSS
background-size parameter below says to size the background
element as N times the size of the element that you're drawing.
Note that we use percentages here, instead of absolute
pixel values, because when we render emojis as actual elements,
their size will vary depending on which part of the UI we're
in (message emojis, emoji reactions, emoji popup, emoji
popup showcase, etc.).
(The next step is to offset the image; that will be in the
upcoming loop.)
"""
background_size = percent(n)
emoji_positions = "" emoji_positions = ""
for emoji in emoji_data: for emoji in emoji_data:
if emoji["has_img_google"]: if emoji["has_img_google"]:
@ -108,16 +131,56 @@ def generate_sprite_css_files(cache_path: str,
# Google emoji (not just the universal ones), we need to # Google emoji (not just the universal ones), we need to
# ensure the spritesheet is setup to correctly display # ensure the spritesheet is setup to correctly display
# those google emoji (in case anyone used them). # those google emoji (in case anyone used them).
"""
For background-position we need to use percentages.
Absolute pixel values won't work, because the size
of the background sprite image is proportional to
the size of the element we're rendering, and we render
elements in multiple sizes.
The way that CSS background-position works is linear
interpolation. When you tell CSS background-position
is "42% 37%", then in the `x` dimension it will align
the image such that 42% of the background image is to
the left of the 42% mark in the element itself.
For simplicity assume we render the emoji as 66px
(and everything will scale appropriately for other
size images as long as we use percentages).
The image size will be 66n.
The left offset of the x-th emoji (including its
padding) will be 66x. And the element's width
will be 66.
So, solve this equation for `p`, where p is
the ratio that we'll later express as a
percentage:
<image offset> = <offset of p% mark of element>
(p * 66n) = 66x + p66
p * n = x + p
p * n - p = x
p * (n - 1) = x
p = x / (n - 1)
If you ever want to change the code so that the
gutters don't show up in the element, the algebra
will get more complicated.
"""
emoji_positions += EMOJI_POS_INFO_TEMPLATE % { emoji_positions += EMOJI_POS_INFO_TEMPLATE % {
'codepoint': get_emoji_code(emoji), 'codepoint': get_emoji_code(emoji),
'pos_x': (emoji["sheet_x"] * 100) / max_dim, 'pos_x': percent(emoji["sheet_x"] / (n - 1)),
'pos_y': (emoji["sheet_y"] * 100) / max_dim, 'pos_y': percent(emoji["sheet_y"] / (n - 1)),
} }
SPRITE_CSS_PATH = os.path.join(cache_path, '%s-sprite.css' % (emojiset,)) SPRITE_CSS_PATH = os.path.join(cache_path, '%s-sprite.css' % (emojiset,))
with open(SPRITE_CSS_PATH, 'w') as f: with open(SPRITE_CSS_PATH, 'w') as f:
f.write(SPRITE_CSS_FILE_TEMPLATE % {'emojiset': emojiset, f.write(SPRITE_CSS_FILE_TEMPLATE % {'emojiset': emojiset,
'emoji_positions': emoji_positions, 'emoji_positions': emoji_positions,
'background_size': background_size,
}) })
def setup_emoji_farms(cache_path: str, emoji_data: List[Dict[str, Any]]) -> None: def setup_emoji_farms(cache_path: str, emoji_data: List[Dict[str, Any]]) -> None:

View File

@ -26,4 +26,4 @@ LATEST_RELEASE_ANNOUNCEMENT = "https://blog.zulip.org/2019/12/13/zulip-2-1-relea
# historical commits sharing the same major version, in which case a # historical commits sharing the same major version, in which case a
# minor version bump suffices. # minor version bump suffices.
PROVISION_VERSION = '73.1' PROVISION_VERSION = '73.2'