Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/six-paws-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@react-pdf/textkit": minor
"@react-pdf/layout": minor
"@react-pdf/types": minor
---

Add support for fontFeatureSettings to customise ligatures, tabular number display, and other font features
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* eslint react/prop-types: 0 */
/* eslint react/jsx-sort-props: 0 */

import { Document, Font, Page, StyleSheet, Text } from '@react-pdf/renderer';
import React from 'react';

import RobotoFont from '../../../public/Roboto-Regular.ttf';
import RubikFont from '../../../public/Rubik-Regular.ttf';

const styles = StyleSheet.create({
body: {
paddingTop: 35,
paddingBottom: 45,
paddingHorizontal: 35,
position: 'relative',
fontSize: 14,
},
headline: {
fontFamily: 'Roboto',
fontSize: '24',
paddingVertical: 12,
},
rubik: {
Comment thread
MendyLanda marked this conversation as resolved.
fontFamily: 'Rubik',
},
roboto: {
fontFamily: 'Roboto',
},
tabular: {
fontFeatureSettings: ['tnum'],
},
smallCapitals: {
fontFeatureSettings: ['smcp'],
},
disableCommonLigatures: {
fontFeatureSettings: { liga: 0 },
},
});

Font.register({
family: 'Rubik',
fonts: [{ src: RubikFont, fontWeight: 400 }],
});
Font.register({
family: 'Roboto',
fonts: [{ src: RobotoFont, fontWeight: 400 }],
});

const MyDoc = () => {
const longNumberExample = "012'345'678'901";
const commonLigaturesExample = 'A firefighter from Sheffield';
return (
<Page style={styles.body}>
<Text style={styles.headline}>Rubik</Text>
<Text style={styles.rubik}>{longNumberExample} – Default features</Text>
<Text style={[styles.rubik, styles.tabular]}>
{longNumberExample} – Tabular numbers
</Text>
<Text style={styles.headline}>Roboto</Text>
<Text style={styles.roboto}>
{commonLigaturesExample} – Default features
</Text>
<Text style={[styles.roboto, styles.disableCommonLigatures]}>
{commonLigaturesExample} – Common ligatures off
</Text>
<Text style={[styles.roboto, styles.smallCapitals]}>
{commonLigaturesExample} – Small capitals
</Text>
</Page>
);
};

const FontFeatureSettings = () => {
return (
<Document>
<MyDoc />
</Document>
);
};

export default {
id: 'font-feature-settings',
name: 'Font Feature Settings',
description: '',
Document: FontFeatureSettings,
};
4 changes: 3 additions & 1 deletion packages/examples/vite/src/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import duplicatedImages from './duplicated-images';
import ellipsis from './ellipsis';
import emoji from './emoji';
import fontFamilyFallback from './font-family-fallback';
import fontFeatureSettings from './font-feature-settings';
import fontWeight from './font-weight';
import forms from './forms';
import fractals from './fractals';
import goTo from './go-to';
import imageStressTest from './image-stress-test';
Expand All @@ -18,14 +20,14 @@ import resume from './resume';
import svg from './svg';
import svgTransform from './svg-transform';
import transformOrigin from './transform-origin';
import forms from './forms';

const EXAMPLES = [
duplicatedImages,
ellipsis,
emoji,
fontFamilyFallback,
fontWeight,
fontFeatureSettings,
fractals,
goTo,
JpgOrientation,
Expand Down
8 changes: 4 additions & 4 deletions packages/examples/vite/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import './index.css';

import { PDFViewer } from '@rpdf/renderer';
import React, { useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { PDFViewer } from '@rpdf/renderer';

import EXAMPLES from './examples';

Expand All @@ -25,13 +25,13 @@ const ExamplesPage = () => {
const { Document } = EXAMPLES[index];

return (
<main className="w-screen h-screen flex">
<main className="flex w-screen h-screen">
<nav className="bg-slate-100 w-60">
<ul>
{EXAMPLES.map((example) => (
<li
key={example.id}
className="hover:bg-slate-200 w-full px-4 py-1 cursor-pointer transition-all border-b border-slate-300 flex"
className="flex w-full px-4 py-1 transition-all border-b cursor-pointer hover:bg-slate-200 border-slate-300"
>
<a href={`#${example.id}`} className="flex-1">
{example.name}
Expand All @@ -41,7 +41,7 @@ const ExamplesPage = () => {
</ul>
</nav>

<div key={hash} className="h-full flex-1">
<div key={hash} className="flex-1 h-full">
<PDFViewer showToolbar={false} className="size-full">
<Document />
</PDFViewer>
Expand Down
1 change: 1 addition & 0 deletions packages/layout/src/steps/resolveInheritance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const BASE_INHERITABLE_PROPERTIES = [
'fontSize',
'fontStyle',
'fontWeight',
'fontFeatureSettings',
'letterSpacing',
'opacity',
'textDecoration',
Expand Down
1 change: 1 addition & 0 deletions packages/layout/src/svg/inheritProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const BASE_SVG_INHERITED_PROPS = [
'fontSize',
'fontStyle',
'fontWeight',
'fontFeatureSettings',
'letterSpacing',
'opacity',
'textDecoration',
Expand Down
2 changes: 2 additions & 0 deletions packages/layout/src/text/getAttributedString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const getFragments = (
fontWeight,
fontStyle,
fontSize = 18,
fontFeatureSettings,
textAlign,
lineHeight,
textDecoration,
Expand Down Expand Up @@ -100,6 +101,7 @@ const getFragments = (
// @ts-expect-error allow this props access
link: parentLink || instance.props?.src || instance.props?.href,
align: textAlign || (direction === 'rtl' ? 'right' : 'left'),
features: fontFeatureSettings,
};

for (let i = 0; i < instance.children.length; i += 1) {
Expand Down
4 changes: 4 additions & 0 deletions packages/layout/tests/steps/resolveInhritance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,8 @@ describe('layout resolveInheritance', () => {
test('Should inherit textAlign value', shouldInherit('textAlign'));
test('Should inherit visibility value', shouldInherit('visibility'));
test('Should inherit wordSpacing value', shouldInherit('wordSpacing'));
test(
'Should inherit fontFeatureSettings value',
shouldInherit('fontFeatureSettings'),
);
});
1 change: 1 addition & 0 deletions packages/stylesheet/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ export type TextStyle = {
fontFamily?: string | string[];
fontStyle?: FontStyle;
fontWeight?: FontWeight;
fontFeatureSettings?: string[] | Record<string, boolean>;
letterSpacing?: number | string;
lineHeight?: number | string;
maxLines?: number;
Expand Down
4 changes: 2 additions & 2 deletions packages/textkit/src/layout/generateGlyphs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const layoutRun = (string: string) => {
*/
return (run: Run) => {
const { start, end, attributes = {} } = run;
const { font } = attributes;
const { font, features } = attributes;

Comment on lines +45 to 46

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

features lacks a type on Run.attributes

features is now destructured but Run (and AttributedStringRun upstream) has no field for it, so attributes.features is typed as any.
Add the field in the declaration (string[] | Record<string, number> | undefined) to keep strict TS projects compiling.

🤖 Prompt for AI Agents
In packages/textkit/src/layout/generateGlyphs.ts around lines 45 to 46, the
destructured `features` from `attributes` is typed as `any` because the `Run`
and `AttributedStringRun` types do not declare a `features` field. To fix this,
add a `features` field to the `Run` and `AttributedStringRun` type declarations
with the type `string[] | Record<string, number> | undefined` to ensure proper
typing and compatibility with strict TypeScript settings.

if (!font) return { ...run, glyphs: [], glyphIndices: [], positions: [] };

Expand All @@ -53,7 +53,7 @@ const layoutRun = (string: string) => {
// passing LTR To force fontkit to not reverse the string
const glyphRun = font[0].layout(
runString,
undefined,
features,
undefined,
undefined,
'ltr',
Expand Down