Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

info_density: Calculate values for vertical alignment and apply to emoji. #30050

Merged
merged 3 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 74 additions & 1 deletion web/src/information_density.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,81 @@ import $ from "jquery";

import {user_settings} from "./user_settings";

// These are all relative-unit values for Source Sans Pro VF,
// as opened and inspected in FontForge.
// Source Sans Prof VF reports an em size of 1000, which is
// necessary to know to calculate proper em units.
const BODY_FONT_EM_SIZE = 1000;
// The Typo Ascent Value is reported as 1024, but both Chrome
// and Firefox act as though it is 1025, so that value is used
// here. It represents the portion of the content box above the
// baseline.
const BODY_FONT_ASCENT = 1025;
// The Typo Descent Value is reported as 400. It is the portion
// of the content box below the baseline.
const BODY_FONT_DESCENT = 400;
// The BODY_FONT_CONTENT_BOX size is calculated by adding the
// Typo Ascent and Typo Descent values. The content box for
// Source Sans Pro VF exceeds the size of its em box, meaning
// that the `font-size` value will render text that is smaller
// than the size of the content area. For example, setting
// `font-size: 100px` on Source Sans Prof VF produces a content
// area of 142.5px.
// Note also that the content box is therefore clipped when the
// line-height (in ems or as a unitless value) is less than the
// MAXIMUM_BLOCK_HEIGHT_IN_EMS as calculated below.
const BODY_FONT_CONTENT_BOX = BODY_FONT_ASCENT + BODY_FONT_DESCENT;
// The maximum block height is derived from the content area
// made by an anonymous text node in Source Sans Pro VF.
// This ensures that even as line heights scale above 1.425,
// text-adjacent elements can be sized in scale to the text's
// content area. This is necessary to know, because an element
// such as a checkbox or emoji looks nice occupying the full
// line-height, but only when the text's content area is less
// than the line-height.
const MAXIMUM_BLOCK_HEIGHT_IN_EMS = BODY_FONT_CONTENT_BOX / BODY_FONT_EM_SIZE;

// Eventually this legacy value and references to it should be removed;
// but in the awkward stage where legacy values are in play for
// certain things (e.g., calculating line-height-based offsets for
// emoji alignment), it's necessary to have access to this value.
const LEGACY_LINE_HEIGHT_UNITLESS = 1.214;

function set_vertical_alignment_values(line_height_unitless: number): void {
// We work in ems to keep this agnostic to the font size.
const line_height_in_ems = line_height_unitless;
const text_content_box_height_in_ems = MAXIMUM_BLOCK_HEIGHT_IN_EMS;
// We calculate the descent area according to the BODY_FONT values. However,
// to make that em value relative to the size of the content box, we need
// to multiply that by the maximum block height, which is the content
// box's em square (versus the em square of the value set on `font-size`).
const descent_area_in_ems =
(BODY_FONT_DESCENT / BODY_FONT_CONTENT_BOX) * MAXIMUM_BLOCK_HEIGHT_IN_EMS;

// The height of line-fitted elements, such as inline emoji, is the
// lesser of either the line height or the height of the adjacent
// text content box.
const line_fitted_height_in_ems = Math.min(line_height_in_ems, text_content_box_height_in_ems);

// We obtain the correct vertical offset by taking the negative value
// of the descent area, and adding it to half any non-zero difference
// between the content box and the fitted line height.
const line_fitted_vertical_align_offset_in_ems =
-descent_area_in_ems + (text_content_box_height_in_ems - line_fitted_height_in_ems) / 2;

$(":root").css("--base-maximum-block-height-em", `${MAXIMUM_BLOCK_HEIGHT_IN_EMS}em`);
$(":root").css(
"--line-fitted-vertical-align-offset-em",
`${line_fitted_vertical_align_offset_in_ems}em`,
);
}

export function set_base_typography_css_variables(): void {
const font_size_px = user_settings.web_font_size_px;
const line_height_percent = user_settings.web_line_height_percent;
const line_height_unitless = line_height_percent / 100;
const line_height_unitless = user_settings.dense_mode
? LEGACY_LINE_HEIGHT_UNITLESS
: line_height_percent / 100;
const line_height_px = line_height_unitless * font_size_px;
/* This percentage is a legacy value, rounding up from .294;
additional logic might be useful to make this adjustable;
Expand All @@ -20,6 +91,8 @@ export function set_base_typography_css_variables(): void {
"--markdown-interelement-doubled-space-px",
`${markdown_interelement_space_px * 2}px`,
);

set_vertical_alignment_values(line_height_unitless);
}

export function initialize(): void {
Expand Down
24 changes: 24 additions & 0 deletions web/styles/app_variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,34 @@
/* The legacy values here are updated via JavaScript */
--base-font-size-px: 14px;
--base-line-height-unitless: 1.214;
--base-maximum-block-height-em: 1.425em;
--line-fitted-vertical-align-offset-em: 0;
--markdown-interelement-space-px: 5px;
--markdown-interelement-doubled-space-px: calc(
var(--markdown-interelement-space-px) * 2
);
/* Certain elements need to be fitted perfectly to
the line height; the length here can be used to set
precise heights, and in the case of square elements,
a matching precise width as well. */
--length-line-fitted-block: calc(var(--base-line-height-unitless) * 1em);
/* Emoji elements are allowed to exceed the perfectly-fit
line-height. Classic Zulip emoji sizing is a 20px square
emoji at a 14px font-size, for 1.4286em at 14px/1em. */
--length-line-oversize-block: 1.4286em;
/* To avoid disturbing the flow of text around emoji or other
oversize inline blocks, we calculate a negative margin
adjustment for offsetting the emoji, top and bottom. */
--length-line-oversize-block-margin-adjust: calc(
(
(
min(
var(--base-maximum-block-height-em),
var(--length-line-fitted-block)
)
) - var(--length-line-oversize-block)
) / 2
);

/* Legacy values */
--legacy-body-line-height-unitless: calc(20 / 14);
Expand Down
23 changes: 13 additions & 10 deletions web/styles/rendered_markdown.css
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,19 @@

/* Emoji; sized to be easily understood while not overwhelming text. */
.emoji {
height: calc(20em / 14);
width: calc(20em / 14);
/* "Eyeballed" styles to compensate for inline-block emoji
positioning in messages.
By coordinating the emoji height and width above with the
`line-height` on messages in the future, these properties
should no longer be necessary: */
position: relative;
margin-top: -7px;
top: 3px;
/* The box for emoji is allowed to be larger than the size of the
line-height. */
height: var(--length-line-oversize-block);
width: var(--length-line-oversize-block);
/* A negative top and bottom margin adjustment allows emoji
to size larger than the size of the line, without disturbing
the surrounding lines of text. */
margin: var(--length-line-oversize-block-margin-adjust) auto;
/* We set the alignment programmatically, as an em value.
Because the negative margins above are equal, top and bottom,
this vertical offset value works without adjustment for
oversize emoji blocks. */
vertical-align: var(--line-fitted-vertical-align-offset-em);
}

/* Mentions and alert words */
Expand Down
Loading