From e713bd6ec8d14e33789b4e7ae6fbacd5f497c06a Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Wed, 4 Feb 2026 00:33:47 +0100 Subject: [PATCH 01/10] Numeric font features for Lens. --- .../public/expression_renderer_styles.tsx | 34 +++++++++ .../react_embeddable/expression_wrapper.tsx | 76 ++++++++++--------- 2 files changed, 74 insertions(+), 36 deletions(-) diff --git a/x-pack/platform/plugins/shared/lens/public/expression_renderer_styles.tsx b/x-pack/platform/plugins/shared/lens/public/expression_renderer_styles.tsx index 93e2e5b478bc3..4d83269672455 100644 --- a/x-pack/platform/plugins/shared/lens/public/expression_renderer_styles.tsx +++ b/x-pack/platform/plugins/shared/lens/public/expression_renderer_styles.tsx @@ -8,6 +8,29 @@ import type { UseEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; +/** + * OpenType font features for improved numeric readability in charts. + * - `tnum` (tabular-nums): Fixed-width numbers for aligned columns + * - `zero` (slashed-zero): Distinguishes zero from letter O + * - `ss01`: Open digits stylistic set + * - `ss07`: Squared punctuation stylistic set + * + * @see https://github.com/elastic/kibana/issues/249382 + */ +const numericFontFeatures = css` + font-feature-settings: 'tnum', 'zero', 'ss01', 'ss07'; + font-variant-numeric: tabular-nums slashed-zero; + + // Override user agent form element font features inheritance + button, + input, + select, + textarea { + font-variant-numeric: inherit; + font-feature-settings: inherit; + } +`; + export const lnsExpressionRendererStyle = (euiThemeContext: UseEuiTheme) => { return css` position: relative; @@ -15,5 +38,16 @@ export const lnsExpressionRendererStyle = (euiThemeContext: UseEuiTheme) => { height: 100%; display: flex; overflow: auto; + ${numericFontFeatures} `; }; + +/** + * Global styles for elastic-charts elements rendered via portals (tooltips, annotations). + * These elements are rendered outside the Lens DOM tree and need global targeting. + */ +export const lnsGlobalChartStyles = css` + [id^='echTooltipPortal'] { + ${numericFontFeatures} + } +`; diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/expression_wrapper.tsx b/x-pack/platform/plugins/shared/lens/public/react_embeddable/expression_wrapper.tsx index c1cb5f67fb1b9..04de6a746022c 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/expression_wrapper.tsx +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/expression_wrapper.tsx @@ -15,9 +15,10 @@ import type { KibanaExecutionContext } from '@kbn/core/public'; import type { ExecutionContextSearch } from '@kbn/es-query'; import type { DefaultInspectorAdapters, RenderMode } from '@kbn/expressions-plugin/common'; import classNames from 'classnames'; +import { Global } from '@emotion/react'; import type { UserMessage, LensInspector } from '@kbn/lens-common'; import { getOriginalRequestErrorMessages } from '../editor_frame_service/error_helper'; -import { lnsExpressionRendererStyle } from '../expression_renderer_styles'; +import { lnsExpressionRendererStyle, lnsGlobalChartStyles } from '../expression_renderer_styles'; export interface ExpressionWrapperProps { ExpressionRenderer: ReactExpressionRendererType; @@ -75,40 +76,43 @@ export function ExpressionWrapper({ }: ExpressionWrapperProps) { if (!expression) return null; return ( -
- { - const messages = getOriginalRequestErrorMessages(error || null); - addUserMessages(messages); - onRuntimeError(error?.original || new Error(errorMessage ? errorMessage : '')); - return <>; // the embeddable will take care of displaying the messages - }} - onEvent={handleEvent} - hasCompatibleActions={hasCompatibleActions} - getCompatibleCellValueActions={getCompatibleCellValueActions} - /> -
+ <> + +
+ { + const messages = getOriginalRequestErrorMessages(error || null); + addUserMessages(messages); + onRuntimeError(error?.original || new Error(errorMessage ? errorMessage : '')); + return <>; // the embeddable will take care of displaying the messages + }} + onEvent={handleEvent} + hasCompatibleActions={hasCompatibleActions} + getCompatibleCellValueActions={getCompatibleCellValueActions} + /> +
+ ); } From 24053e2216fe67e1592db629f3a66ab166aad1f7 Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Sat, 28 Feb 2026 23:17:08 +0100 Subject: [PATCH 02/10] Override elastic-chart's own `font-feature-settings`. --- .../shared/lens/public/expression_renderer_styles.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/x-pack/platform/plugins/shared/lens/public/expression_renderer_styles.tsx b/x-pack/platform/plugins/shared/lens/public/expression_renderer_styles.tsx index 4d83269672455..0554a5760b15f 100644 --- a/x-pack/platform/plugins/shared/lens/public/expression_renderer_styles.tsx +++ b/x-pack/platform/plugins/shared/lens/public/expression_renderer_styles.tsx @@ -17,9 +17,11 @@ import { css } from '@emotion/react'; * * @see https://github.com/elastic/kibana/issues/249382 */ +const fontFeatureSettings = "'tnum', 'zero', 'ss01', 'ss07'"; +const fontVariantNumeric = 'tabular-nums slashed-zero'; const numericFontFeatures = css` - font-feature-settings: 'tnum', 'zero', 'ss01', 'ss07'; - font-variant-numeric: tabular-nums slashed-zero; + font-feature-settings: ${fontFeatureSettings}; + font-variant-numeric: ${fontVariantNumeric}; // Override user agent form element font features inheritance button, @@ -49,5 +51,10 @@ export const lnsExpressionRendererStyle = (euiThemeContext: UseEuiTheme) => { export const lnsGlobalChartStyles = css` [id^='echTooltipPortal'] { ${numericFontFeatures} + + // Override elastic-charts .echTooltip__value which sets font-feature-settings: "tnum" + .echTooltip__value { + font-feature-settings: ${fontFeatureSettings}; + } } `; From 51d7331f0b9fc5e2c2ceaf1b5580cbc66a78c741 Mon Sep 17 00:00:00 2001 From: Abdul Zahid Date: Thu, 26 Mar 2026 11:42:29 +0100 Subject: [PATCH 03/10] Introduction of "Elastic UI Numeric" font and licensing. --- .../ElasticUINumeric-Variable.woff2 | Bin 0 -> 7912 bytes .../fonts/elastic_ui_numeric/LICENSE.txt | 104 ++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 src/core/packages/apps/server-internal/assets/fonts/elastic_ui_numeric/ElasticUINumeric-Variable.woff2 create mode 100644 src/core/packages/apps/server-internal/assets/fonts/elastic_ui_numeric/LICENSE.txt diff --git a/src/core/packages/apps/server-internal/assets/fonts/elastic_ui_numeric/ElasticUINumeric-Variable.woff2 b/src/core/packages/apps/server-internal/assets/fonts/elastic_ui_numeric/ElasticUINumeric-Variable.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..feb9713c743555a7d08ec7b34780576238cf6981 GIT binary patch literal 7912 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 S-TbT(_ Date: Thu, 26 Mar 2026 11:42:50 +0100 Subject: [PATCH 04/10] WIP - Testing "Elastic UI Numeric" fonts in Lens. --- .../server-internal/src/views/fonts.tsx | 43 +++++++++++++++++-- .../charts/public/services/theme/theme.ts | 27 +++++++++++- .../public/expression_renderer_styles.tsx | 36 +++++++--------- 3 files changed, 80 insertions(+), 26 deletions(-) diff --git a/src/core/packages/rendering/server-internal/src/views/fonts.tsx b/src/core/packages/rendering/server-internal/src/views/fonts.tsx index 943d2d3e1ea3f..c71337c2876fb 100644 --- a/src/core/packages/rendering/server-internal/src/views/fonts.tsx +++ b/src/core/packages/rendering/server-internal/src/views/fonts.tsx @@ -20,13 +20,42 @@ interface FontFace { family: string; variants: Array<{ style: 'normal' | 'italic'; - weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; + weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | '100 900'; sources: string[]; unicodeRange?: string; format?: string; + display?: 'swap' | 'auto' | 'block' | 'fallback' | 'optional'; }>; } +/** + * `Elastic UI Numeric` is a derivative of Inter with OpenType numeric features + * (tnum, zero, ss01, ss07) baked into the default glyphs. This allows canvas-based + * rendering to display tabular numbers and slashed zeros without requiring CSS + * font-feature-settings, which is not supported by Firefox's canvas implementation. + * + * The font only contains numeric characters and common punctuation, using unicode-range + * to apply only to those characters. When used alongside Inter, numeric characters + * will render with the enhanced features while letters use standard Inter. + * + * @see https://github.com/elastic/kibana/issues/249382 + */ +const getElasticUINumeric = (url: string): FontFace => { + return { + family: 'Elastic UI Numeric', + variants: [ + { + style: 'normal', + weight: '100 900', + sources: [`${url}/fonts/elastic_ui_numeric/ElasticUINumeric-Variable.woff2`], + format: 'woff2', + display: 'swap', + unicodeRange: 'U+20, U+24-25, U+28-29, U+2B-2F, U+30-3A, U+A0, U+202F, U+2212', + }, + ], + }; +}; + /** * `Inter` is the latest version of `Inter UI` */ @@ -220,6 +249,7 @@ const getRoboto = (url: string): FontFace => { }; export const Fonts: FunctionComponent = ({ url }) => { + const elasticUINumericFont = getElasticUINumeric(url); const sansFont = getInter(url); const codeFont = getRoboto(url); @@ -228,9 +258,9 @@ export const Fonts: FunctionComponent = ({ url }) => {