Skip to content

Commit

Permalink
fix: font selection regression (#2747)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-spare committed Jun 24, 2024
1 parent f64f3bd commit 5af35ec
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 8 deletions.
6 changes: 6 additions & 0 deletions .changeset/nine-ways-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@react-pdf/examples": patch
"@react-pdf/layout": patch
---

fix: font selection regression
39 changes: 35 additions & 4 deletions packages/examples/src/fontFamilyFallback/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import React from 'react';
import { Document, Page, Text, StyleSheet, Font } from '@react-pdf/renderer';

import RobotoFont from '../../public/Roboto-Regular.ttf';
import RobotoBoldFont from '../../public/Roboto-Bold.ttf';
import RobotItalicFont from '../../public/Roboto-Italic.ttf';

import NotoSansArabicFont from '../../public/NotoSansArabic-Regular.ttf';

const styles = StyleSheet.create({
Expand All @@ -30,7 +33,18 @@ Font.register({
fonts: [
{
src: RobotoFont,
fontWeight: 400,
fontStyle: 'normal',
fontWeight: 'normal',
},
{
src: RobotItalicFont,
fontStyle: 'italic',
fontWeight: 'normal',
},
{
src: RobotoBoldFont,
fontStyle: 'normal',
fontWeight: 'bold',
},
],
});
Expand All @@ -48,19 +62,36 @@ Font.register({
const MyDoc = () => {
return (
<Page style={styles.body}>
<Text style={{ fontFamily: 'Courier', marginBottom: '10px' }}>
<Text style={{ fontFamily: 'Courier', marginBottom: '20px' }}>
This font is default Courier
</Text>

<Text style={{ fontSize: 10 }}>
The following is partially Roboto and Noto Sans Arabic
</Text>
<Text style={[styles.regular, { marginBottom: '10px' }]}>
<Text style={[styles.regular, { marginBottom: '20px' }]}>
Roboto / امتحان
</Text>

<Text style={{ fontSize: 10 }}>
The following is partially Courier-Bold and Noto Sans Arabic
</Text>
<Text style={styles.default}>Courier-Bold / امتحان</Text>
<Text style={[styles.default, { marginBottom: '20px' }]}>
Courier-Bold / امتحان
</Text>

<Text style={{ fontSize: 10 }}>
The following are multiple font families, weights, and styles all on the
same line
</Text>
<Text style={{ fontFamily: 'Roboto' }}>
Roboto Normal{' / '}
<Text style={{ fontWeight: 'bold' }}>Roboto Bold</Text>
{' / '}
<Text style={{ fontStyle: 'italic' }}>Roboto Italic</Text>
{' / '}
<Text style={{ fontFamily: 'Courier' }}>Courier</Text>
</Text>
</Page>
);
};
Expand Down
5 changes: 1 addition & 4 deletions packages/layout/src/text/fontSubstitution.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ const getOrCreateFont = (name) => {
const getFallbackFont = () => getOrCreateFont('Helvetica');

const pickFontFromFontStack = (codePoint, fontStack, lastFont) => {
const fontStackWithFallback = [...fontStack, getFallbackFont()];
if (lastFont) {
fontStackWithFallback.unshift(lastFont);
}
const fontStackWithFallback = [...fontStack, lastFont, getFallbackFont()];
for (let i = 0; i < fontStackWithFallback.length; i += 1) {
const font = fontStackWithFallback[i];
if (
Expand Down
90 changes: 90 additions & 0 deletions packages/layout/tests/text/fontSubstitution.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, expect, test } from 'vitest';
import fontSubstitution from '../../src/text/fontSubstitution';

const instance = fontSubstitution();

describe('FontSubstitution', () => {
test('should return empty array if no runs passed', () => {
const string = instance({ string: '', runs: [] });

expect(string).toHaveProperty('runs', []);
expect(string).toHaveProperty('string', '');
});

test('should merge consecutive runs with same font', () => {
const run1 = { start: 0, end: 3, attributes: { font: ['Helvetica'] } };
const run2 = { start: 3, end: 5, attributes: { font: ['Helvetica'] } };
const string = instance({ string: 'Lorem', runs: [run1, run2] });

expect(string).toHaveProperty('string', 'Lorem');
expect(string.runs).toHaveLength(1);
expect(string.runs[0]).toHaveProperty('start', 0);
expect(string.runs[0]).toHaveProperty('end', 5);
expect(string.runs[0].attributes.font.name).toBe('Helvetica');
});

test('should substitute many runs', () => {
const run1 = { start: 0, end: 3, attributes: { font: ['Courier'] } };
const run2 = { start: 3, end: 5, attributes: { font: ['Helvetica'] } };
const string = instance({ string: 'Lorem', runs: [run1, run2] });

expect(string).toHaveProperty('string', 'Lorem');
expect(string.runs).toHaveLength(2);
expect(string.runs[0]).toHaveProperty('start', 0);
expect(string.runs[0]).toHaveProperty('end', 3);
expect(string.runs[0].attributes.font.name).toBe('Courier');
expect(string.runs[1]).toHaveProperty('start', 3);
expect(string.runs[1]).toHaveProperty('end', 5);
expect(string.runs[1].attributes.font.name).toBe('Helvetica');
});

describe('Fallback Font', () => {
const SimplifiedChineseFont = {
name: 'SimplifiedChineseFont',
hasGlyphForCodePoint: (codePoint) => codePoint === 20320,
};

test('should utilize a fallback font that supports the provided glyph', () => {
const run = {
start: 0,
end: 1,
attributes: {
font: ['Courier', SimplifiedChineseFont],
},
};

const string = instance({ string: '你', runs: [run] });

expect(string).toHaveProperty('string', '你');
expect(string.runs).toHaveLength(1);
expect(string.runs[0]).toHaveProperty('start', 0);
expect(string.runs[0]).toHaveProperty('end', 1);
expect(string.runs[0].attributes.font.name).toBe(
SimplifiedChineseFont.name,
);
});

test('should split a run when fallback font is used on a portion of the run', () => {
const run = {
start: 0,
end: 2,
attributes: {
font: ['Courier', SimplifiedChineseFont],
},
};

const string = instance({ string: 'A你', runs: [run] });

expect(string).toHaveProperty('string', 'A你');
expect(string.runs).toHaveLength(2);
expect(string.runs[0]).toHaveProperty('start', 0);
expect(string.runs[0]).toHaveProperty('end', 1);
expect(string.runs[0].attributes.font.name).toBe('Courier');
expect(string.runs[1]).toHaveProperty('start', 1);
expect(string.runs[1]).toHaveProperty('end', 2);
expect(string.runs[1].attributes.font.name).toBe(
SimplifiedChineseFont.name,
);
});
});
});

0 comments on commit 5af35ec

Please sign in to comment.