FEATURE: Handle special font properties from discourse-fonts (#30891)

In https://github.com/discourse/discourse-fonts/pull/15 we are
introducing special font properties for certain fonts,
specifically the `font-variation-settings` and `font-feature-settings`.
For now this will only apply to Inter, but we may do it for other
fonts in future.

This commit makes it so the color_definitions.css file includes
these special properties for each font, either defined on the
root `html` element for the body font or on the `h1-h6` elements
for the heading font. This is done in this way because defining
them on `@font-face` is ignored by the browser.

This also ensures special CSS classes for the wizard container
e.g. wizard-container-font-FONTID are defined, this is so we can
use these special properties scoped to the font selected in the
wizard, which will affect the way the canvas preview is rendered.

Here is an example of before/after with special properties applied to
Inter,
in this case:

```css
font-variation-settings: 'opsz' 28;
font-feature-settings: 'calt' 0, 'ccmp' 0, 'ss02' 1;
```
This commit is contained in:
Martin Brennan 2025-01-22 10:56:09 +10:00 committed by GitHub
parent 32c6d3be06
commit 83cc97994f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 78 additions and 14 deletions

View File

@ -125,7 +125,7 @@ GEM
diffy (3.4.3)
digest (3.1.1)
digest-xxhash (0.2.9)
discourse-fonts (0.0.14)
discourse-fonts (0.0.15)
discourse-seed-fu (2.3.12)
activerecord (>= 3.1)
activesupport (>= 3.1)

View File

@ -100,6 +100,10 @@ export default class PreviewBase extends Component {
}
loadFontVariants(font) {
if (!font) {
return Promise.resolve();
}
const fontVariantData = this.fontMap[font.id];
// System font for example does not need to load from a remote source.
@ -206,8 +210,8 @@ export default class PreviewBase extends Component {
const options = {
ctx,
colors,
font: font?.label,
headingFont: headingFont?.label,
font,
headingFont,
width: this.width,
height: this.height,
};
@ -337,7 +341,7 @@ export default class PreviewBase extends Component {
const badgeHeight = headerHeight * 2 * 0.25;
const headerMargin = headerHeight * 0.2;
const fontSize = Math.round(badgeHeight * 0.5);
ctx.font = `${fontSize}px '${font}'`;
ctx.font = `${fontSize}px '${font.label}'`;
const allCategoriesText = i18n(
"wizard.homepage_preview.nav_buttons.all_categories"
@ -406,7 +410,7 @@ export default class PreviewBase extends Component {
);
ctx.fill();
ctx.font = `${fontSize}px '${font}'`;
ctx.font = `${fontSize}px '${font.label}'`;
ctx.fillStyle = colors.secondary;
const pillButtonTextY = headerHeight + headerMargin * 1.4 + fontSize;
const firstTopMenuItemX = headerMargin * 3.0 + categoriesBoxWidth;

View File

@ -137,12 +137,12 @@ export default class Index extends PreviewBaseComponent {
// Topic title
ctx.beginPath();
ctx.fillStyle = colors.primary;
ctx.font = `700 ${titleFontSize}em '${headingFont}'`;
ctx.font = `700 ${titleFontSize}em '${headingFont.label}'`;
ctx.fillText(i18n("wizard.previews.topic_title"), margin, height * 0.3);
// Topic OP text
const bodyFontSize = 1;
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.font = `${bodyFontSize}em '${font.label}'`;
let verticalLinePos = 0;
const topicOp = i18n("wizard.homepage_preview.topic_ops.what_books");
@ -160,7 +160,7 @@ export default class Index extends PreviewBaseComponent {
}
);
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.font = `${bodyFontSize}em '${font.label}'`;
// Share button
const shareButtonWidth =
@ -221,13 +221,13 @@ export default class Index extends PreviewBaseComponent {
// Timeline post count
const postCountY = height * 0.3 + margin + 10;
ctx.beginPath();
ctx.font = `700 ${bodyFontSize}em '${font}'`;
ctx.font = `700 ${bodyFontSize}em '${font.label}'`;
ctx.fillStyle = colors.primary;
ctx.fillText("1 / 20", timelineX + margin / 2, postCountY);
// Timeline post date
ctx.beginPath();
ctx.font = `${bodyFontSize * 0.9}em '${font}'`;
ctx.font = `${bodyFontSize * 0.9}em '${font.label}'`;
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 70, 65);
ctx.fillText(
"Nov 22",

View File

@ -6,6 +6,7 @@ import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import { schedule } from "@ember/runloop";
import { htmlSafe } from "@ember/template";
import concatClass from "discourse/helpers/concat-class";
import emoji from "discourse/helpers/emoji";
import { i18n } from "discourse-i18n";
import WizardField from "./wizard-field";
@ -80,6 +81,20 @@ export default class WizardStepComponent extends Component {
return !!this.step.fields.find((f) => f.showInSidebar);
}
get containerFontClasses() {
let fontClasses = "";
if (this.wizard.font) {
fontClasses += ` wizard-container-body-font-${this.wizard.font.id}`;
}
if (this.wizard.headingFont) {
fontClasses += ` wizard-container-heading-font-${this.wizard.headingFont.id}`;
}
return fontClasses;
}
@action
stepChanged() {
this.saving = false;
@ -195,7 +210,7 @@ export default class WizardStepComponent extends Component {
</div>
{{/if}}
<div class="wizard-container">
<div class={{concatClass "wizard-container" this.containerFontClasses}}>
<div class="wizard-container__step-contents">
<div class="wizard-container__step-header">
{{#if @step.emoji}}

View File

@ -42,19 +42,23 @@ module Stylesheet
contents = +""
contents << <<~CSS if body_font.present?
// Body font definition
#{font_css(body_font)}
#{render_font_special_properties(body_font, "body")}
:root {
--font-family: #{body_font[:stack]};
}
CSS
contents << <<~CSS if heading_font.present?
// Heading font definition
#{font_css(heading_font)}
#{render_font_special_properties(heading_font, "heading")}
:root {
--heading-font-family: #{heading_font[:stack]};
}
CSS
contents
@ -90,6 +94,7 @@ module Stylesheet
font-family: #{font[:stack]};
}
CSS
contents << render_wizard_font_special_properties(font)
end
contents
@ -108,7 +113,10 @@ module Stylesheet
if resolved_ids
theme = Theme.find_by_id(theme_id)
contents << theme&.scss_variables.to_s
contents << "\n\n// Theme SCSS variables\n\n"
contents << theme&.scss_variables.to_s.split(";").join(";\n") + ";\n\n"
contents << "\n\n"
Theme
.list_baked_fields(resolved_ids, :common, :color_definitions)
.each do |field|
@ -220,6 +228,7 @@ module Stylesheet
"url(\"#{fonts_dir}/#{variant[:filename]}?v=#{DiscourseFonts::VERSION}\") format(\"#{variant[:format]}\")"
end
)
contents << <<~CSS
@font-face {
font-family: '#{font[:name]}';
@ -232,5 +241,41 @@ module Stylesheet
contents
end
def render_font_special_properties(font, font_type)
contents = +""
root_elements = font_type == "body" ? "html" : "h1, h2, h3, h4, h5, h6"
contents << "#{root_elements} {\n"
contents << font_special_properties(font)
contents << "}\n"
end
def render_wizard_font_special_properties(font)
contents = +""
contents << ".wizard-container-heading-font-#{font[:key]} {\n"
contents << " h1, h2, h3, h4, h5, h6 {\n"
contents << font_special_properties(font, 4)
contents << " }\n"
contents << "}\n"
contents << ".wizard-container-body-font-#{font[:key]} {\n"
contents << font_special_properties(font)
contents << "}\n"
end
def font_special_properties(font, indent = 2)
contents = +""
if font[:font_variation_settings].present?
contents << "#{" " * indent}font-variation-settings: #{font[:font_variation_settings]};\n"
else
contents << "#{" " * indent}font-variation-settings: normal;\n"
end
if font[:font_feature_settings].present?
contents << "#{" " * indent}font-feature-settings: #{font[:font_feature_settings]};\n"
else
contents << "#{" " * indent}font-feature-settings: normal;\n"
end
contents
end
end
end