Skip to content

Conversation

@MendyLanda
Copy link
Owner

@MendyLanda MendyLanda commented Jun 15, 2025

Implements diegomura#2155. Adds the fontFeatureSettings style property for CSS equivalent support. Takes a list of feature tags which appends to the default set, or an object to turn on/off individual features, as supported by fontkit.

Allows users to apply font features like tabular numbers, fractions, alternate glyphs, control over ligatures, etc.

Thanks to @stefanwittwer #2740

Summary by CodeRabbit

  • New Features

    • Added support for advanced font feature settings (such as ligatures, tabular numbers, and small capitals) in PDF rendering.
    • Introduced a new example demonstrating the use of font feature settings with custom fonts.
  • Bug Fixes

    • Ensured font feature settings are properly inherited and applied to text and SVG elements.
  • Tests

    • Added tests to verify correct inheritance of font feature settings.
  • Style

    • Improved formatting of CSS utility classes in the example viewer interface.

stefanwittwer and others added 2 commits February 22, 2025 16:11
Added font feature settings to TextStyle

Adapted example to new structure
Implements diegomura#2155. Adds the fontFeatureSettings style property for CSS equivalent support. Takes a list of feature tags which appends to the default set, or an object to turn on/off individual features, as supported by fontkit.

Allows users to apply font features like tabular numbers, fractions, alternate glyphs, control over ligatures, etc.
@coderabbitai
Copy link

coderabbitai bot commented Jun 15, 2025

Walkthrough

This update introduces support for the fontFeatureSettings CSS property across several packages in the React PDF ecosystem. It adds type definitions, inheritance logic, SVG and text attribute propagation, glyph layout handling, and a new example demonstrating advanced font features like ligatures and tabular numbers in rendered PDFs.

Changes

File(s) Change Summary
.changeset/six-paws-tie.md Documents the new support for fontFeatureSettings in relevant packages.
packages/stylesheet/src/types.ts Adds FontFeatureSetting, FontFeatureSettings, and extends text style types with fontFeatureSettings.
packages/layout/src/steps/resolveInheritance.ts,
.../svg/inheritProps.ts
Adds fontFeatureSettings to inheritable and SVG-inherited style properties.
packages/layout/src/text/getAttributedString.ts Propagates fontFeatureSettings into text fragment attributes as features.
packages/textkit/src/types.ts Updates the features property type in Attributes to FontFeatureSettings.
packages/textkit/src/layout/generateGlyphs.ts Passes font features to glyph layout during text rendering.
packages/layout/tests/steps/resolveInhritance.test.ts Adds a test to verify inheritance of fontFeatureSettings.
packages/examples/vite/src/examples/font-feature-settings/index.jsx Adds a new example demonstrating font feature settings in rendered PDFs.
packages/examples/vite/src/examples/index.ts Registers the new font feature settings example in the examples array.
packages/examples/vite/src/index.tsx Reorders import statements and CSS utility classes for consistency (no logic changes).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ExampleComponent
    participant Renderer
    participant Layout
    participant TextKit
    participant FontEngine

    User->>ExampleComponent: Selects Font Feature Settings example
    ExampleComponent->>Renderer: Renders Document with fontFeatureSettings
    Renderer->>Layout: Processes styles, inherits fontFeatureSettings
    Layout->>TextKit: Passes attributed text with fontFeatureSettings as features
    TextKit->>FontEngine: Layouts glyphs using features
    FontEngine-->>TextKit: Returns glyphs with applied font features
    TextKit-->>Renderer: Renders text with advanced font features
Loading

Poem

In fields of fonts where features play,
New settings hop in—hip, hooray!
Ligatures dance and numbers align,
Small caps parade in tidy line.
With every glyph, a rabbit grins—
For PDF magic now begins!
🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/stylesheet/src/types.ts

Oops! Something went wrong! :(

ESLint: 9.20.0

Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.
at /node_modules/eslint/lib/config/config-loader.js:173:19
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:172:32)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:500:32)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:663:29)
at async /node_modules/eslint/lib/eslint/eslint.js:760:33
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:756:25)
at async Object.execute (/node_modules/eslint/lib/cli.js:505:23)
at async main (/node_modules/eslint/bin/eslint.js:158:22)

packages/textkit/src/types.ts

Oops! Something went wrong! :(

ESLint: 9.20.0

Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.
at /node_modules/eslint/lib/config/config-loader.js:173:19
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:172:32)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:500:32)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:663:29)
at async /node_modules/eslint/lib/eslint/eslint.js:760:33
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:756:25)
at async Object.execute (/node_modules/eslint/lib/cli.js:505:23)
at async main (/node_modules/eslint/bin/eslint.js:158:22)


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15b637a and 0bfb69c.

📒 Files selected for processing (3)
  • .changeset/six-paws-tie.md (1 hunks)
  • packages/stylesheet/src/types.ts (2 hunks)
  • packages/textkit/src/types.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • .changeset/six-paws-tie.md
  • packages/textkit/src/types.ts
  • packages/stylesheet/src/types.ts
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (2)
packages/layout/src/svg/inheritProps.ts (1)

5-34: 🛠️ Refactor suggestion

Duplicate key & merge semantics need a follow-up

  1. opacity appears twice (lines 10 and 28).
    Removing the duplicate reduces noise during diff reviews and avoids accidental future edits that touch only one occurrence.

  2. Newly-added fontFeatureSettings is simply overridden downstream; inheriting an array or object will replace, not merge, parent values. For example a parent enabling tnum and a child enabling liga results in only liga. Consider extending merge logic (similar to textDecoration) so both arrays & object values are merged.

-  'opacity',
+  'opacity',
   'textAnchor',
…
-  'opacity',
packages/layout/src/steps/resolveInheritance.ts (1)

38-51: 🛠️ Refactor suggestion

fontFeatureSettings should merge, not clobber

Just like textDecoration, multiple ancestors might want to contribute different feature tags. A minimal merge strategy could:

• For array values – Array.from(new Set([...inherited, ...value]))
• For object values – { ...inherited, ...value }

case 'fontFeatureSettings': {
  if (Array.isArray(value) && Array.isArray(inheritedValue)) {
    return Array.from(new Set([...inheritedValue, ...value]));
  }
  if (
    typeof value === 'object' &&
    typeof inheritedValue === 'object' &&
    value &&
    inheritedValue
  ) {
    return { ...inheritedValue, ...value };
  }
  return value;
}

This avoids silent loss of ancestor settings and mirrors CSS cascade behaviour.

🧹 Nitpick comments (7)
packages/stylesheet/src/types.ts (1)

324-332: Consider surfacing a stronger type for feature tags

fontFeatureSettings?: string[] | Record<string, boolean> is flexible but very loose – nothing prevents typos such as ['smcp ', 'tble']. A branded‐string/enum union for well-known tags (e.g. 'smcp' | 'c2sc' | 'tnum' …) plus a fallback string would give consumers intellisense without sacrificing openness.

-  fontFeatureSettings?: string[] | Record<string, boolean>;
+  /** Four-letter OpenType tag(s) – cf. https://learn.microsoft.com/typography/opentype/spec/featuretags */
+  fontFeatureSettings?: (KnownFontFeature | string)[] | Record<KnownFontFeature | string, boolean>;
+
+export type KnownFontFeature =
+  | 'liga' | 'dlig' | 'smcp' | 'c2sc' | 'tnum' | 'pnum' | 'frac' /* … add as needed */;
.changeset/six-paws-tie.md (1)

7-8: Tiny wording nit – “customise” → “customize” (American English)

The repo docs mostly use American spelling. Aligning the changeset keeps release notes consistent.

-Add support for fontFeatureSettings to customise ligatures, tabular number display, and other font features
+Add support for fontFeatureSettings to customize ligatures, tabular number display, and other font features
packages/examples/vite/src/examples/index.ts (1)

5-7: Import & array order drift – keep examples alphabetically sorted to avoid merge noise

The new entries break the previous (rough) alphabetical order of both the import section and the EXAMPLES array. Keeping them ordered avoids future merge conflicts and makes manual scanning easier.

-import fontFeatureSettings from './font-feature-settings';
-import forms from './forms';
+import fontFeatureSettings from './font-feature-settings';
+import forms from './forms';           // ← move back above `fontFeatureSettings` to restore order

Likewise in the EXAMPLES array, place fontFeatureSettings next to fontFamilyFallback.

Also applies to: 30-32, 45-47

packages/layout/src/text/getAttributedString.ts (1)

50-51: Default fontFeatureSettings to undefined to avoid writing empty arrays/objects into every fragment

-const { ..., fontFeatureSettings, ... } = instance.style;
+const { ..., fontFeatureSettings, ... } = instance.style;

 ...

-  features: fontFeatureSettings,
+  features: fontFeatureSettings ?? undefined,

This keeps the fragment payload minimal and avoids the “disable all features” edge-case mentioned earlier.

Also applies to: 104-105

packages/examples/vite/src/index.tsx (1)

3-4: Nit: follow import grouping guidelines

Project files are usually imported after external libs.
Swap the two imports to keep the existing pattern:

-import { PDFViewer } from '@rpdf/renderer';
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState } from 'react';
+import { PDFViewer } from '@rpdf/renderer';
packages/examples/vite/src/examples/font-feature-settings/index.jsx (2)

24-37: Combine style helpers to avoid duplicate objects

You can DRY the Rubik/Roboto mixins:

-  rubik: { fontFamily: 'Rubik' },
-  roboto: { fontFamily: 'Roboto' },
+  fontRubik: { fontFamily: 'Rubik' },
+  fontRoboto: { fontFamily: 'Roboto' },

Not mandatory, but reduces bundle size when many examples accumulate.


40-48: Register fonts once per bundle

Placing Font.register in module scope means every example import re-registers the font.
Move the registration to a shared “fonts.ts” loaded once, or guard with Font.getRegisteredFonts().has('Rubik').

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 997c60c and 9c1a85f.

📒 Files selected for processing (10)
  • .changeset/six-paws-tie.md (1 hunks)
  • packages/examples/vite/src/examples/font-feature-settings/index.jsx (1 hunks)
  • packages/examples/vite/src/examples/index.ts (2 hunks)
  • packages/examples/vite/src/index.tsx (3 hunks)
  • packages/layout/src/steps/resolveInheritance.ts (1 hunks)
  • packages/layout/src/svg/inheritProps.ts (1 hunks)
  • packages/layout/src/text/getAttributedString.ts (2 hunks)
  • packages/layout/tests/steps/resolveInhritance.test.ts (1 hunks)
  • packages/stylesheet/src/types.ts (1 hunks)
  • packages/textkit/src/layout/generateGlyphs.ts (2 hunks)
🔇 Additional comments (2)
packages/layout/tests/steps/resolveInhritance.test.ts (1)

178-181: 👍 Nice test – covers the new inheritance path
Nothing to add; this ensures the property behaves like the other inheritable text styles.

packages/textkit/src/layout/generateGlyphs.ts (1)

54-59: Safe-guard when features is falsy

Fontkit allows undefined, but passing an empty array/object enables no features, overriding the font defaults.
Consider normalising:

-const glyphRun = font[0].layout(runString, features, undefined, undefined, 'ltr');
+const normalisedFeatures =
+  Array.isArray(features) && features.length === 0
+    ? undefined
+    : (features as any) ?? undefined;
+
+const glyphRun = font[0].layout(runString, normalisedFeatures, undefined, undefined, 'ltr');

Comment on lines +45 to 46
const { font, features } = attributes;

Copy link

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.

MendyLanda and others added 2 commits June 15, 2025 15:52
…x.jsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
.github/workflows/main.yml (1)

114-114: Consistency in job name formatting
The E2E job name currently uses a comma to separate parameters, while other jobs (e.g., unit tests) use a semicolon. For clarity and consistency, consider updating it to:

-name: 'E2E tests (Node.js ${{ matrix.node_version }}, ${{ matrix.cjs_or_esm }})'
+name: 'E2E tests (Node.js ${{ matrix.node_version }}; module system: ${{ matrix.cjs_or_esm }})'
packages/textkit/src/types.ts (2)

3-3: Check cross-package dependency layering

import type { FontFeatureSettings } from '@rpdf/stylesheet'; introduces a direct dependency from textkitstylesheet.
If textkit is intended to remain a low-level, rendering-agnostic package, verify that depending on stylesheet types does not break your internal layering rules or create cyclic deps in the published packages graph. If that coupling is acceptable, the import type qualifier is perfect to keep the dependency compile-time only.


55-55: Type refinement looks good – add a short docstring

Replacing unknown[] with the strongly-typed FontFeatureSettings greatly improves safety and DX – nice!
Consider adding a brief comment (e.g. “OpenType feature settings, mirrors CSS font-feature-settings”) above this line so future contributors immediately understand the expected shape without jumping to stylesheet types.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3872d32 and 15b637a.

📒 Files selected for processing (4)
  • .changeset/six-paws-tie.md (1 hunks)
  • .github/workflows/main.yml (1 hunks)
  • packages/stylesheet/src/types.ts (2 hunks)
  • packages/textkit/src/types.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • .changeset/six-paws-tie.md
  • packages/stylesheet/src/types.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/textkit/src/types.ts (1)
packages/stylesheet/src/types.ts (1)
  • FontFeatureSettings (351-353)
⏰ Context from checks skipped due to timeout of 90000ms (18)
  • GitHub Check: Run unit tests (Node.js 18; React 18)
  • GitHub Check: Lint
  • GitHub Check: Run unit tests (Node.js 18; React 17)
  • GitHub Check: Check size
  • GitHub Check: Run unit tests (Node.js 21; React 18)
  • GitHub Check: Run unit tests (Node.js 20; React 18)
  • GitHub Check: Run unit tests (Node.js 20; React 17)
  • GitHub Check: Run unit tests (Node.js 18; React 16)
  • GitHub Check: Run unit tests (Node.js 21; React 16)
  • GitHub Check: Run unit tests (Node.js 21; React 17)
  • GitHub Check: Run unit tests (Node.js 18; React 17)
  • GitHub Check: Run unit tests (Node.js 20; React 16)
  • GitHub Check: Run unit tests (Node.js 18; React 18)
  • GitHub Check: E2E tests (Node.js 21, cjs)
  • GitHub Check: E2E tests (Node.js 20, cjs)
  • GitHub Check: E2E tests (Node.js 18, esm)
  • GitHub Check: Lint
  • GitHub Check: Check size

@MendyLanda MendyLanda force-pushed the font-feature-settings branch from 15b637a to 0bfb69c Compare June 15, 2025 13:49
@MendyLanda MendyLanda merged commit 444740b into master Jun 15, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants