Skip to content
Open
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,22 @@ - (UIFont *)headingFontForLevel:(NSInteger)level
}

NSString *weightString = _headingFontWeights[level];
UIFont *font = weightString.length > 0 ? [UIFont systemFontOfSize:size weight:ENRMFontWeightFromString(weightString)]
: [_baseFont fontWithSize:size];
UIFont *font;
if (weightString.length > 0 && ENRMFontWeightFromString(weightString) >= UIFontWeightBold) {

@eszlamczyk eszlamczyk Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

While this is correct, I've checked that ENRMFontWeightFromString is incomplete if we use it for this comparasion; Because the mapping returns UIFontWeightRegular (0.0) for "800", "900", "heavy", and "black", those weights arrive at line 116 as 0.0, fail the >= 0.4 check, and skip the new bold-derivation branch β€” even though semantically they are bolder than bold.

If we want to use this function in this way we would require to fix the ENRMFontWeightFromString to mirror RN's own RCTFont table:

Weight strings UIFont.Weight constant
100, ultralight UIFontWeightUltraLight
200, thin UIFontWeightThin
300, light UIFontWeightLight
400, normal, regular UIFontWeightRegular
500, medium UIFontWeightMedium
600, semibold UIFontWeightSemibold
700, bold UIFontWeightBold
800, heavy UIFontWeightHeavy
900, black UIFontWeightBlack
---- (default) UIFontWeightRegular

// Derive the bold face of the base (content) font β€” matching Android's
// InputHeadingSpan, which folds heading weight into the current typeface β€”
// so a custom editor font keeps its family in headings. Fall back to the
// system font only when the family has no bold face.
UIFont *sized = [_baseFont fontWithSize:size];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nitpick - Two small things in this hunk:

  1. sized is a throwaway - symbolic traits don't depend on point size, so the descriptor can come straight from _baseFont, and fontWithDescriptor:size: already applies the size:
UIFontDescriptor *descriptor = [_baseFont.fontDescriptor
   fontDescriptorWithSymbolicTraits:_baseFont.fontDescriptor.symbolicTraits | UIFontDescriptorTraitBold];
  1. ENRMFontWeightFromString(weightString) runs twice (here and in the else if),
    and weightString.length > 0 is checked twice. Computing the weight once above
    the if simplifies both branches:
UIFontWeight weight = ENRMFontWeightFromString(weightString);
if (weightString.length > 0 && weight >= UIFontWeightBold) {
  ...
} else if (weightString.length > 0) {
  font = [UIFont systemFontOfSize:size weight:weight];
}

Non-blocking β€” and if we go with the UIFontWeightTrait approach from the other
comment, most of this restructures anyway, so feel free to fold it in there instead.

UIFontDescriptor *descriptor = [sized.fontDescriptor
fontDescriptorWithSymbolicTraits:sized.fontDescriptor.symbolicTraits | UIFontDescriptorTraitBold];
font = descriptor ? [UIFont fontWithDescriptor:descriptor size:size]
: [UIFont systemFontOfSize:size weight:UIFontWeightBold];
} else if (weightString.length > 0) {
font = [UIFont systemFontOfSize:size weight:ENRMFontWeightFromString(weightString)];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The new bold branch correctly derives the heading font from the base font's family, but the else if below it still routes every sub-bold weight ('600', '500', '300', …) through [UIFont systemFontOfSize:weight:], which always produces the system font. So with a custom editor font (say Arial) and h1: { fontWeight: '600' }:

  • iOS editor β†’ system font semibold (family lost)
  • Android editor β†’ Arial (InputHeadingSpan never leaves the base typeface)
  • iOS preview β†’ Arial semibold (RCTFont resolves the weight within the family)

This is the same divergence this PR fixes for bold, just for the remaining weights β€” and the family may well have matching faces (Arial Light/Medium/etc.) we could use.

Suggestion: instead of special-casing bold via the symbolic trait, derive every non-empty weight from the base font's descriptor using the weight trait, e.g.

UIFontDescriptor *descriptor = [_baseFont.fontDescriptor fontDescriptorByAddingAttributes:@{
  UIFontDescriptorTraitsAttribute : @{UIFontWeightTrait : @(weight)}
}];

The system picks the closest face in the family to the requested weight, which would collapse the bold branch and this one into a single path (keeping the system-font fallback for families where descriptor resolution fails).

} else {
font = [_baseFont fontWithSize:size];
}
_headingFontCache[level] = font;
return font;
}
Expand Down
Loading