Refactor font family rendering using CSS inlining

We don't reset font family on any level, so there is no need to
bubble the setting using a preprocessor and render the inline styles
explicitly in every block.

In this commit, I change how font-family settings are distributed/rendered
in the email renderer. In the new approach, we rely on class names defining font-family
and a generated CSS sheet with font-family definitions.
We apply the font-family CSS by inlining CSS rules for families in the later phase of
rendering after all individual blocks are processed.
[MAILPOET-5740]
This commit is contained in:
Rostislav Wolny
2024-01-11 17:08:12 +01:00
committed by Jan Lysý
parent a7aaf97070
commit e8bb1b5ac0
10 changed files with 38 additions and 47 deletions

View File

@ -12,7 +12,6 @@ class TypographyPreprocessor implements Preprocessor {
private const TYPOGRAPHY_STYLES = [ private const TYPOGRAPHY_STYLES = [
'color', 'color',
'font-size', 'font-size',
'font-family',
'text-decoration', 'text-decoration',
]; ];
@ -54,9 +53,6 @@ class TypographyPreprocessor implements Preprocessor {
if (isset($block['attrs']['style']['color']['text'])) { if (isset($block['attrs']['style']['color']['text'])) {
$emailAttrs['color'] = $block['attrs']['style']['color']['text']; $emailAttrs['color'] = $block['attrs']['style']['color']['text'];
} }
if (isset($block['attrs']['fontFamily'])) {
$emailAttrs['font-family'] = $this->getFontFamilyBySlug($block['attrs']['fontFamily']);
}
if (isset($block['attrs']['style']['typography']['fontSize'])) { if (isset($block['attrs']['style']['typography']['fontSize'])) {
$emailAttrs['font-size'] = $block['attrs']['style']['typography']['fontSize']; $emailAttrs['font-size'] = $block['attrs']['style']['typography']['fontSize'];
} }
@ -80,20 +76,6 @@ class TypographyPreprocessor implements Preprocessor {
if (!($block['email_attrs']['font-size'] ?? '')) { if (!($block['email_attrs']['font-size'] ?? '')) {
$block['email_attrs']['font-size'] = $contentStyles['typography']['fontSize']; $block['email_attrs']['font-size'] = $contentStyles['typography']['fontSize'];
} }
if (!($block['email_attrs']['font-family'] ?? '')) {
$block['email_attrs']['font-family'] = $contentStyles['typography']['fontFamily'];
}
return $block; return $block;
} }
private function getFontFamilyBySlug(string $slug): ?string {
$themeData = $this->settingsController->getTheme()->get_data();
$fontFamilies = $themeData['settings']['typography']['fontFamilies'] ?? [];
foreach ($fontFamilies as $fontFamily) {
if ($fontFamily['slug'] === $slug) {
return $fontFamily['fontFamily'];
}
}
return null;
}
} }

View File

@ -49,6 +49,7 @@ class Renderer {
$renderedBody = $this->renderBlocks($parsedBlocks); $renderedBody = $this->renderBlocks($parsedBlocks);
$styles = (string)file_get_contents(dirname(__FILE__) . '/' . self::TEMPLATE_STYLES_FILE); $styles = (string)file_get_contents(dirname(__FILE__) . '/' . self::TEMPLATE_STYLES_FILE);
$styles .= $this->settingsController->getStylesheetForRendering();
$styles = apply_filters('mailpoet_email_renderer_styles', $styles, $post); $styles = apply_filters('mailpoet_email_renderer_styles', $styles, $post);
$template = (string)file_get_contents(dirname(__FILE__) . '/' . self::TEMPLATE_FILE); $template = (string)file_get_contents(dirname(__FILE__) . '/' . self::TEMPLATE_FILE);

View File

@ -236,4 +236,14 @@ class SettingsController {
/** @var array $themeJson */ /** @var array $themeJson */
return new \WP_Theme_JSON($themeJson); return new \WP_Theme_JSON($themeJson);
} }
public function getStylesheetForRendering(): string {
$settings = $this->getTheme()->get_settings();
$css = '';
// Font family classes
foreach ($settings['typography']['fontFamilies']['theme'] as $fontFamily) {
$css .= ".has-{$fontFamily['slug']}-font-family { font-family: {$fontFamily['fontFamily']}; } \n";
}
return $css;
}
} }

View File

@ -25,7 +25,11 @@ class Button implements BlockRenderer {
return ''; return '';
} }
$buttonOriginalWrapper = $buttonDom->getElementsByTagName('div')->item(0);
$buttonClasses = $buttonOriginalWrapper instanceof \DOMElement ? $buttonOriginalWrapper->getAttribute('class') : '';
$markup = $this->getMarkup(); $markup = $this->getMarkup();
$markup = str_replace('{classes}', $buttonClasses, $markup);
// Add Link Text // Add Link Text
$markup = str_replace('{linkText}', $buttonLink->textContent ?: '', $markup); $markup = str_replace('{linkText}', $buttonLink->textContent ?: '', $markup);
@ -91,8 +95,6 @@ class Button implements BlockRenderer {
// Escaping // Escaping
$wrapperStyles = array_map('esc_attr', $wrapperStyles); $wrapperStyles = array_map('esc_attr', $wrapperStyles);
$linkStyles = array_map('esc_attr', $linkStyles); $linkStyles = array_map('esc_attr', $linkStyles);
// Font family may contain single quotes
$linkStyles['font-family'] = str_replace(''', "'", esc_attr("{$parsedBlock['email_attrs']['font-family']}"));
$markup = str_replace('{linkStyles}', $settingsController->convertStylesToString($linkStyles), $markup); $markup = str_replace('{linkStyles}', $settingsController->convertStylesToString($linkStyles), $markup);
$markup = str_replace('{wrapperStyles}', $settingsController->convertStylesToString($wrapperStyles), $markup); $markup = str_replace('{wrapperStyles}', $settingsController->convertStylesToString($wrapperStyles), $markup);
@ -103,7 +105,7 @@ class Button implements BlockRenderer {
private function getMarkup(): string { private function getMarkup(): string {
return '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;border-collapse:separate;line-height:100%;width:{width};"> return '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;border-collapse:separate;line-height:100%;width:{width};">
<tr> <tr>
<td align="center" bgcolor="{backgroundColor}" role="presentation" style="{wrapperStyles}" valign="middle"> <td align="center" class="{classes}" bgcolor="{backgroundColor}" role="presentation" style="{wrapperStyles}" valign="middle">
<a href="{linkUrl}" style="{linkStyles}" target="_blank">{linkText}</a> <a href="{linkUrl}" style="{linkStyles}" target="_blank">{linkText}</a>
</td> </td>
</tr> </tr>

View File

@ -42,9 +42,6 @@ class Heading implements BlockRenderer {
if (!isset($styles['font-size'])) { if (!isset($styles['font-size'])) {
$styles['font-size'] = $contentStyles['typography']['fontSize']; $styles['font-size'] = $contentStyles['typography']['fontSize'];
} }
if (!isset($styles['font-family'])) {
$styles['font-family'] = $contentStyles['typography']['fontFamily'];
}
$styles = array_merge($styles, $this->fetchStylesFromBlockAttrs($availableStylesheets, $parsedBlock['attrs'])); $styles = array_merge($styles, $this->fetchStylesFromBlockAttrs($availableStylesheets, $parsedBlock['attrs']));

View File

@ -23,9 +23,6 @@ class ListBlock implements BlockRenderer {
if (!isset($styles['font-size'])) { if (!isset($styles['font-size'])) {
$styles['font-size'] = $contentStyles['typography']['fontSize']; $styles['font-size'] = $contentStyles['typography']['fontSize'];
} }
if (!isset($styles['font-family'])) {
$styles['font-family'] = $contentStyles['typography']['fontFamily'];
}
$html->set_attribute('style', $settingsController->convertStylesToString($styles)); $html->set_attribute('style', $settingsController->convertStylesToString($styles));
$blockContent = $html->get_updated_html(); $blockContent = $html->get_updated_html();

View File

@ -41,9 +41,6 @@ class Paragraph implements BlockRenderer {
if (!isset($styles['font-size'])) { if (!isset($styles['font-size'])) {
$styles['font-size'] = $contentStyles['typography']['fontSize']; $styles['font-size'] = $contentStyles['typography']['fontSize'];
} }
if (!isset($styles['font-family'])) {
$styles['font-family'] = $contentStyles['typography']['fontFamily'];
}
$styles = array_merge($styles, $this->fetchStylesFromBlockAttrs($availableStylesheets, $parsedBlock['attrs'] ?? [])); $styles = array_merge($styles, $this->fetchStylesFromBlockAttrs($availableStylesheets, $parsedBlock['attrs'] ?? []));

View File

@ -0,0 +1,19 @@
<?php declare(strict_types = 1);
namespace MailPoet\EmailEditor\Engine;
class SettingsControllerTest extends \MailPoetTest {
/** @var SettingsController */
private $settingsController;
public function _before() {
parent::_before();
$this->settingsController = $this->diContainer->get(SettingsController::class);
}
public function testItGeneratesCssStylesForThemeWithFontFamilies() {
$css = $this->settingsController->getStylesheetForRendering();
verify($css)->stringContainsString('.has-system-sans-serif-font-family');
verify($css)->stringContainsString('.has-system-Serif-font-family');
}
}

View File

@ -150,15 +150,4 @@ class ButtonTest extends \MailPoetTest {
verify($output)->stringContainsString('border-bottom-left-radius:3px;'); verify($output)->stringContainsString('border-bottom-left-radius:3px;');
verify($output)->stringContainsString('border-bottom-right-radius:4px;'); verify($output)->stringContainsString('border-bottom-right-radius:4px;');
} }
public function testItAllowsSingleQuotesInFontFamilyDefinition(): void {
$settingsControllerMock = $this->createPartialMock(SettingsController::class, ['getEmailContentStyles']);
$settingsControllerMock->method('getEmailContentStyles')->willReturn([
'typography' => [
'fontFamily' => '"Font\'", serif',
],
]);
$output = $this->buttonRenderer->render($this->parsedButton['innerHTML'], $this->parsedButton, $settingsControllerMock);
verify($output)->stringContainsString('&quot;Font\'&quot;, serif');
}
} }

View File

@ -81,7 +81,6 @@ class TypographyPreprocessorTest extends \MailPoetUnitTest {
]]; ]];
$expectedEmailAttrs = [ $expectedEmailAttrs = [
'color' => '#aa00dd', 'color' => '#aa00dd',
'font-family' => 'Arial',
'font-size' => '12px', 'font-size' => '12px',
'text-decoration' => 'underline', 'text-decoration' => 'underline',
]; ];
@ -121,8 +120,8 @@ class TypographyPreprocessorTest extends \MailPoetUnitTest {
$result = $this->preprocessor->preprocess($blocks, []); $result = $this->preprocessor->preprocess($blocks, []);
$result = $result[0]; $result = $result[0];
verify($result['innerBlocks'])->arrayCount(2); verify($result['innerBlocks'])->arrayCount(2);
verify($result['email_attrs'])->equals(['width' => '640px', 'color' => '#000000', 'font-size' => '13px', 'font-family' => 'Arial']); verify($result['email_attrs'])->equals(['width' => '640px', 'color' => '#000000', 'font-size' => '13px']);
$defaultFontStyles = ['color' => '#000000', 'font-size' => '13px', 'font-family' => 'Arial']; $defaultFontStyles = ['color' => '#000000', 'font-size' => '13px'];
verify($result['innerBlocks'][0]['email_attrs'])->equals($defaultFontStyles); verify($result['innerBlocks'][0]['email_attrs'])->equals($defaultFontStyles);
verify($result['innerBlocks'][1]['email_attrs'])->equals($defaultFontStyles); verify($result['innerBlocks'][1]['email_attrs'])->equals($defaultFontStyles);
verify($result['innerBlocks'][1]['innerBlocks'][0]['email_attrs'])->equals($defaultFontStyles); verify($result['innerBlocks'][1]['innerBlocks'][0]['email_attrs'])->equals($defaultFontStyles);
@ -207,12 +206,10 @@ class TypographyPreprocessorTest extends \MailPoetUnitTest {
]; ];
$expectedEmailAttrs1 = [ $expectedEmailAttrs1 = [
'color' => '#aa00dd', 'color' => '#aa00dd',
'font-family' => 'Arial',
'font-size' => '12px', 'font-size' => '12px',
]; ];
$expectedEmailAttrs2 = [ $expectedEmailAttrs2 = [
'color' => '#cc22aa', 'color' => '#cc22aa',
'font-family' => 'Georgia',
'font-size' => '18px', 'font-size' => '18px',
]; ];
$result = $this->preprocessor->preprocess($blocks, []); $result = $this->preprocessor->preprocess($blocks, []);
@ -225,7 +222,7 @@ class TypographyPreprocessorTest extends \MailPoetUnitTest {
verify($child1['innerBlocks'][1]['email_attrs'])->equals($expectedEmailAttrs1); verify($child1['innerBlocks'][1]['email_attrs'])->equals($expectedEmailAttrs1);
verify($child1['innerBlocks'][1]['innerBlocks'][0]['email_attrs'])->equals($expectedEmailAttrs1); verify($child1['innerBlocks'][1]['innerBlocks'][0]['email_attrs'])->equals($expectedEmailAttrs1);
verify($child2['innerBlocks'])->arrayCount(1); verify($child2['innerBlocks'])->arrayCount(1);
verify($child2['email_attrs'])->equals(['color' => '#000000', 'font-size' => '13px', 'font-family' => 'Arial']); verify($child2['email_attrs'])->equals(['color' => '#000000', 'font-size' => '13px']);
verify($child2['innerBlocks'][0]['email_attrs'])->equals($expectedEmailAttrs2); verify($child2['innerBlocks'][0]['email_attrs'])->equals($expectedEmailAttrs2);
verify($child2['innerBlocks'][0]['innerBlocks'][0]['email_attrs'])->equals($expectedEmailAttrs2); verify($child2['innerBlocks'][0]['innerBlocks'][0]['email_attrs'])->equals($expectedEmailAttrs2);
} }