Skip to content

Commit 29dbc49

Browse files
committed
feat(textkit): expect font array
1 parent b06d3a0 commit 29dbc49

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+647
-444
lines changed

.changeset/late-cats-double.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@react-pdf/textkit": major
3+
"@react-pdf/layout": patch
4+
"@react-pdf/render": patch
5+
---
6+
7+
feat(textkit): expect font array

packages/layout/src/svg/layoutText.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import layoutEngine, {
1010
textDecoration,
1111
fromFragments,
1212
Fragment,
13-
Font,
1413
} from '@react-pdf/textkit';
1514

1615
import transformText from '../text/transformText';
@@ -64,14 +63,11 @@ const getFragments = (fontStore: FontStore, instance) => {
6463
// Fallback font
6564
fontFamilies.push('Helvetica');
6665

67-
// TODO: Fix multiple fonts passed
6866
const font = fontFamilies.map((fontFamilyName) => {
69-
if (typeof fontFamilyName !== 'string') return fontFamilyName;
70-
7167
const opts = { fontFamily: fontFamilyName, fontWeight, fontStyle };
7268
const obj = fontStore.getFont(opts);
73-
return obj ? obj.data : fontFamilyName;
74-
}) as Font[];
69+
return obj?.data;
70+
});
7571

7672
const attributes = {
7773
font,
@@ -98,7 +94,6 @@ const getFragments = (fontStore: FontStore, instance) => {
9894
if (isTextInstance(child)) {
9995
fragments.push({
10096
string: transformText(child.value, textTransform),
101-
// @ts-expect-error custom font substitution engine deals with multiple fonts. unify with textkit
10297
attributes,
10398
});
10499
} else if (child) {

packages/layout/src/text/getAttributedString.ts

+3-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as P from '@react-pdf/primitives';
2-
import { Font, Fragment, fromFragments } from '@react-pdf/textkit';
2+
import { Fragment, fromFragments } from '@react-pdf/textkit';
33
import FontStore from '@react-pdf/font';
44

55
import { embedEmojis } from './emoji';
@@ -65,14 +65,11 @@ const getFragments = (
6565
// Fallback font
6666
fontFamilies.push('Helvetica');
6767

68-
// TODO: Fix multiple fonts passed
6968
const font = fontFamilies.map((fontFamilyName) => {
70-
if (typeof fontFamilyName !== 'string') return fontFamilyName;
71-
7269
const opts = { fontFamily: fontFamilyName, fontWeight, fontStyle };
7370
const obj = fontStore.getFont(opts);
74-
return obj ? obj.data : fontFamilyName;
75-
}) as Font[];
71+
return obj?.data;
72+
});
7673

7774
// Don't pass main background color to textkit. Will be rendered by the render package instead
7875
const backgroundColor = level === 0 ? null : instance.style.backgroundColor;
@@ -111,7 +108,6 @@ const getFragments = (
111108
if (isImage(child)) {
112109
fragments.push({
113110
string: String.fromCharCode(0xfffc),
114-
// @ts-expect-error custom font substitution engine deals with multiple fonts. unify with textkit
115111
attributes: {
116112
...attributes,
117113
attachment: {
@@ -124,7 +120,6 @@ const getFragments = (
124120
} else if (isTextInstance(child)) {
125121
fragments.push({
126122
string: transformText(child.value, textTransform),
127-
// @ts-expect-error custom font substitution engine deals with multiple fonts. unify with textkit
128123
attributes,
129124
});
130125
} else if (child) {

packages/layout/src/text/ignoreChars.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const buildSubsetForFont = (font: Font) =>
1919

2020
const ignoreChars = (fragments: Fragment[]): Fragment[] =>
2121
fragments.map((fragment) => {
22-
const charSubset = buildSubsetForFont(fragment.attributes.font);
22+
const charSubset = buildSubsetForFont(fragment.attributes.font[0]);
2323
const subsetRegex = new RegExp(charSubset.join('|'));
2424

2525
return {

packages/render/src/primitives/renderSvgText.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ const renderRun = (ctx: Context, run: Run) => {
1414
if (!run.positions) return;
1515

1616
const runAdvanceWidth = run.xAdvance;
17-
const { font, fontSize, color, opacity } = run.attributes;
17+
const font = run.attributes.font?.[0];
18+
const { fontSize, color, opacity } = run.attributes;
1819

1920
if (color) ctx.fillColor(color);
2021
ctx.fillOpacity(opacity!);
@@ -42,7 +43,7 @@ const renderSpan = (
4243

4344
const x = line.box?.x || 0;
4445
const y = line.box?.y || 0;
45-
const font = line.runs[0]?.attributes.font;
46+
const font = line.runs[0]?.attributes.font?.[0];
4647
const scale = line.runs[0]?.attributes?.scale || 1;
4748
const width = line.xAdvance!;
4849

packages/render/src/primitives/renderText.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const renderAttachments = (ctx: Context, run: Run) => {
3333
if (!run.glyphs) return;
3434
if (!run.positions) return;
3535

36-
const { font } = run.attributes;
36+
const font = run.attributes.font?.[0];
3737
if (!font) return;
3838

3939
ctx.save();
@@ -63,10 +63,10 @@ const renderRun = (ctx: Context, run: Run) => {
6363
if (!run.glyphs) return;
6464
if (!run.positions) return;
6565

66-
const { font, fontSize, link } = run.attributes;
67-
66+
const font = run.attributes.font?.[0];
6867
if (!font) return;
6968

69+
const { fontSize, link } = run.attributes;
7070
const color = parseColor(run.attributes.color);
7171
const opacity = isNil(run.attributes.opacity)
7272
? color.opacity

packages/textkit/src/engines/fontSubstitution/index.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { last } from '@react-pdf/fns';
2-
import { AttributedString, Run } from '../../types';
2+
import { AttributedString, Font, Run } from '../../types';
33

44
const IGNORED_CODE_POINTS = [173];
55

66
const getFontSize = (run: Run) => run.attributes.fontSize || 12;
77

8-
const pickFontFromFontStack = (codePoint, fontStack, lastFont) => {
8+
const pickFontFromFontStack = (
9+
codePoint: number,
10+
fontStack: Font[],
11+
lastFont?: Font,
12+
) => {
913
const fontStackWithFallback = [...fontStack, lastFont];
1014
for (let i = 0; i < fontStackWithFallback.length; i += 1) {
1115
const font = fontStackWithFallback[i];
@@ -24,8 +28,8 @@ const pickFontFromFontStack = (codePoint, fontStack, lastFont) => {
2428
const fontSubstitution =
2529
() =>
2630
({ string, runs }: AttributedString) => {
27-
let lastFont = null;
28-
let lastFontSize = null;
31+
let lastFont: Font | null = null;
32+
let lastFontSize: number | null = null;
2933
let lastIndex = 0;
3034
let index = 0;
3135

@@ -68,7 +72,7 @@ const fontSubstitution =
6872
start: lastIndex,
6973
end: index,
7074
attributes: {
71-
font: lastFont,
75+
font: [lastFont],
7276
scale: lastFontSize / lastFont.unitsPerEm,
7377
},
7478
});
@@ -90,7 +94,7 @@ const fontSubstitution =
9094
start: lastIndex,
9195
end: string.length,
9296
attributes: {
93-
font: lastFont,
97+
font: [lastFont],
9498
scale: fontSize / lastFont.unitsPerEm,
9599
},
96100
});

packages/textkit/src/layout/applyDefaultStyles.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const applyAttributes = (a: Attributes): Attributes => {
1717
direction: a.direction || 'ltr',
1818
features: a.features || [],
1919
fill: a.fill !== false,
20-
font: a.font || null,
20+
font: a.font || [],
2121
fontSize: a.fontSize || 12,
2222
hangingPunctuation: a.hangingPunctuation || false,
2323
hyphenationFactor: a.hyphenationFactor || 0,

packages/textkit/src/layout/generateGlyphs.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const layoutRun = (string: string) => {
5151
if (typeof font === 'string') throw new Error('Invalid font');
5252

5353
// passing LTR To force fontkit to not reverse the string
54-
const glyphRun = font.layout(
54+
const glyphRun = font[0].layout(
5555
runString,
5656
undefined,
5757
undefined,

packages/textkit/src/layout/preprocessRuns.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@ const omitFont = (attributedString: AttributedString): AttributedString => {
1616
return Object.assign({}, attributedString, { runs });
1717
};
1818

19+
type ProcessRunsEngines = Pick<
20+
Engines,
21+
'bidi' | 'scriptItemizer' | 'fontSubstitution'
22+
>;
23+
1924
/**
2025
* Performs font substitution and script itemization on attributed string
2126
*
2227
* @param engines - engines
2328
*/
24-
const preprocessRuns = (engines: Engines) => {
29+
const preprocessRuns = (engines: ProcessRunsEngines) => {
2530
/**
2631
* @param attributedString - Attributed string
2732
* @returns Processed attributed string

packages/textkit/src/layout/resolveYOffset.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { AttributedString, Run } from '../types';
99
const resolveRunYOffset = (run: Run) => {
1010
if (!run.positions) return run;
1111

12-
const unitsPerEm = run.attributes?.font?.unitsPerEm || 0;
12+
const unitsPerEm = run.attributes?.font?.[0]?.unitsPerEm || 0;
1313
const yOffset = (run.attributes?.yOffset || 0) * unitsPerEm;
1414
const positions = run.positions.map((p) => Object.assign({}, p, { yOffset }));
1515

packages/textkit/src/paragraph/truncate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const getEllipsisCodePoint = (font: Font) => {
2929
*/
3030
const truncate = (paragraph: Paragraph) => {
3131
const runs = last(paragraph)?.runs || [];
32-
const font = last(runs)?.attributes?.font;
32+
const font = last(runs)?.attributes?.font[0];
3333

3434
if (font) {
3535
const index = paragraph.length - 1;

packages/textkit/src/run/ascent.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import scale from './scale';
1010
const ascent = (run: Run) => {
1111
const { font, attachment } = run.attributes;
1212
const attachmentHeight = attachment?.height || 0;
13-
const fontAscent = typeof font === 'string' ? 0 : font?.ascent || 0;
13+
const fontAscent = typeof font === 'string' ? 0 : font?.[0]?.ascent || 0;
1414

1515
return Math.max(attachmentHeight, fontAscent * scale(run));
1616
};

packages/textkit/src/run/descent.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import scale from './scale';
99
*/
1010
const descent = (run: Run) => {
1111
const font = run.attributes?.font;
12-
const fontDescent = typeof font === 'string' ? 0 : font?.descent || 0;
12+
const fontDescent = typeof font === 'string' ? 0 : font?.[0]?.descent || 0;
1313

1414
return scale(run) * fontDescent;
1515
};

packages/textkit/src/run/getFont.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Run } from '../types';
77
* @returns Font
88
*/
99
const getFont = (run: Run) => {
10-
return run.attributes?.font || null;
10+
return run.attributes?.font?.[0] || null;
1111
};
1212

1313
export default getFont;

packages/textkit/src/run/lineGap.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import scale from './scale';
99
*/
1010
const lineGap = (run: Run) => {
1111
const font = run.attributes?.font;
12-
const lineGap = typeof font === 'string' ? 0 : font?.lineGap || 0;
12+
const lineGap = typeof font === 'string' ? 0 : font?.[0]?.lineGap || 0;
1313
return lineGap * scale(run);
1414
};
1515

packages/textkit/src/run/scale.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const calculateScale = (run: Run) => {
1010
const attributes = run.attributes || {};
1111
const fontSize = attributes.fontSize || 12;
1212
const font = attributes.font;
13-
const unitsPerEm = typeof font === 'string' ? null : font?.unitsPerEm;
13+
const unitsPerEm = typeof font === 'string' ? null : font?.[0]?.unitsPerEm;
1414

1515
return unitsPerEm ? fontSize / unitsPerEm : 0;
1616
};

packages/textkit/src/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export type Attributes = {
5353
direction?: 'rtl' | 'ltr';
5454
features?: unknown[];
5555
fill?: boolean;
56-
font?: Font;
56+
font?: Font[];
5757
fontSize?: number;
5858
hangingPunctuation?: boolean;
5959
hyphenationFactor?: number;

0 commit comments

Comments
 (0)