Skip to content
Open
Show file tree
Hide file tree
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 @@ -24,7 +24,10 @@ import type {TextInputNativeCommands} from './TextInputNativeCommands';

import * as NativeComponentRegistry from '../../NativeComponent/NativeComponentRegistry';
import codegenNativeCommands from '../../Utilities/codegenNativeCommands';
import {colorAttribute} from '../View/ReactNativeStyleAttributes';
import {
colorAttribute,
fontVariationSettingsAttribute,
} from '../View/ReactNativeStyleAttributes';

export type KeyboardType =
// Cross Platform
Expand Down Expand Up @@ -715,7 +718,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
includeFontPadding: true,
fontWeight: true,
fontFamily: true,
fontVariationSettings: true,
fontVariationSettings: fontVariationSettingsAttribute,
allowFontScaling: true,
onSelectionChange: true,
mostRecentEventCount: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
import type {PartialViewConfig} from '../../Renderer/shims/ReactNativeTypes';

import {ConditionallyIgnoredEventHandlers} from '../../NativeComponent/ViewConfigIgnore';
import {colorAttribute} from '../View/ReactNativeStyleAttributes';
import {
colorAttribute,
fontVariationSettingsAttribute,
} from '../View/ReactNativeStyleAttributes';

type PartialViewConfigWithoutName = Omit<PartialViewConfig, 'uiViewClassName'>;

Expand Down Expand Up @@ -102,6 +105,7 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
},
allowFontScaling: true,
fontStyle: true,
fontVariationSettings: fontVariationSettingsAttribute,
textTransform: true,
textAlign: true,
fontFamily: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

import {__INTERNAL_VIEW_CONFIG as AndroidTextInputViewConfig} from '../AndroidTextInputNativeComponent';
import RCTTextInputViewConfig from '../RCTTextInputViewConfig';
import nullthrows from 'nullthrows';

const {
create,
} = require('../../../ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload');

const androidValidAttributes = nullthrows(
AndroidTextInputViewConfig.validAttributes,
);
const appleValidAttributes = nullthrows(RCTTextInputViewConfig.validAttributes);

describe('Android TextInput view config', () => {
it('serializes object font variation settings', () => {
expect(
create(
{fontVariationSettings: {wght: 552.5, opsz: 17.25}},
androidValidAttributes,
),
).toEqual({fontVariationSettings: "'opsz' 17.25, 'wght' 552.5"});
});

it('serializes an empty object as an explicit clear', () => {
expect(create({fontVariationSettings: {}}, androidValidAttributes)).toEqual(
{fontVariationSettings: ''},
);
});
});

describe('Apple TextInput view config', () => {
it('serializes object font variation settings', () => {
expect(
create(
{fontVariationSettings: {wght: 552.5, opsz: 17.25}},
appleValidAttributes,
),
).toEqual({fontVariationSettings: "'opsz' 17.25, 'wght' 552.5"});
});

it('serializes an empty object as an explicit clear', () => {
expect(create({fontVariationSettings: {}}, appleValidAttributes)).toEqual({
fontVariationSettings: '',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import processBoxShadow from '../../StyleSheet/processBoxShadow';
import processColor from '../../StyleSheet/processColor';
import processFilter from '../../StyleSheet/processFilter';
import processFontVariant from '../../StyleSheet/processFontVariant';
import processFontVariationSettings from '../../StyleSheet/processFontVariationSettings';
import processTransform from '../../StyleSheet/processTransform';
import processTransformOrigin from '../../StyleSheet/processTransformOrigin';
import sizesDiffer from '../../Utilities/differ/sizesDiffer';
Expand Down Expand Up @@ -71,6 +72,10 @@ export const fontVariantAttribute: AnyAttributeType = nativeCSSParsing
? true
: {process: processFontVariant};

export const fontVariationSettingsAttribute: AnyAttributeType = {
process: processFontVariationSettings,
};

export const aspectRatioAttribute: AnyAttributeType = nativeCSSParsing
? true
: {process: processAspectRatio};
Expand Down Expand Up @@ -258,7 +263,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
fontSize: true,
fontStyle: true,
fontVariant: fontVariantAttribute,
fontVariationSettings: true,
fontVariationSettings: fontVariationSettingsAttribute,
fontWeight: true,
includeFontPadding: true,
letterSpacing: true,
Expand Down
12 changes: 8 additions & 4 deletions packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,9 @@ export type ____FontVariant_Internal =
export type ____FontVariantArray_Internal =
ReadonlyArray<____FontVariant_Internal>;

export type ____FontVariationSettings_Internal =
string | Readonly<{[axis: string]: number}>;

type ____TextStyle_InternalBase = Readonly<{
color?: ____ColorValue_Internal,
fontFamily?: string,
Expand All @@ -1012,11 +1015,12 @@ type ____TextStyle_InternalBase = Readonly<{
fontWeight?: ____FontWeight_Internal,
fontVariant?: ____FontVariantArray_Internal | string,
/**
* Specifies OpenType font variation axis values using CSS syntax. An empty
* string resets inherited variation settings. On Android, this requires API
* level 26 or later.
* Specifies OpenType font variation axis values using CSS syntax or an
* object keyed by four-character axis tags. An empty string or object resets
* inherited variation settings. On Android, this requires API level 26 or
* later.
*/
fontVariationSettings?: string,
fontVariationSettings?: ____FontVariationSettings_Internal,
textShadowOffset?: Readonly<{
width: number,
height: number,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';

import processFontVariationSettings from '../processFontVariationSettings';

describe('processFontVariationSettings', () => {
it('passes string settings through unchanged', () => {
expect(processFontVariationSettings("'wght' 550, 'opsz' 18")).toBe(
"'wght' 550, 'opsz' 18",
);
});

it('serializes object settings in deterministic axis order', () => {
expect(processFontVariationSettings({wght: 552.5, opsz: 17.25})).toBe(
"'opsz' 17.25, 'wght' 552.5",
);
});

it('serializes an empty object as an explicit clear', () => {
expect(processFontVariationSettings({})).toBe('');
});

it('supports printable four-character tags containing a single quote', () => {
expect(processFontVariationSettings({["a'b "]: 1})).toBe('"a\'b " 1');
});

it('preserves backslashes in printable four-character tags', () => {
expect(processFontVariationSettings({['a\\bc']: 1})).toBe("'a\\bc' 1");
});

it('rejects tags that cannot be delimited without escaping', () => {
expect(() => processFontVariationSettings({[`a'"b`]: 1})).toThrow(
'Font variation axis tags containing both quote characters must use the string form: "a\'\\"b"',
);
});

it('rejects invalid axis tags', () => {
expect(() => processFontVariationSettings({weight: 550})).toThrow(
'Font variation axis tags must be exactly four printable ASCII characters: "weight"',
);
expect(() => processFontVariationSettings({['a\nbc']: 1})).toThrow(
'Font variation axis tags must be exactly four printable ASCII characters: "a\\nbc"',
);
});

it('rejects non-finite axis values', () => {
expect(() => processFontVariationSettings({wght: NaN})).toThrow(
'Font variation axis values must be finite numbers: NaN',
);
expect(() => processFontVariationSettings({wght: Infinity})).toThrow(
'Font variation axis values must be finite numbers: Infinity',
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

'use strict';

import type {____FontVariationSettings_Internal} from './StyleSheetTypes';

function quoteAxis(axis: string): string {
if (
axis.length !== 4 ||
axis.split('').some(character => {
const code = character.charCodeAt(0);
return code < 0x20 || code > 0x7e;
})
) {
throw new Error(
`Font variation axis tags must be exactly four printable ASCII characters: ${JSON.stringify(axis)}`,
);
}

if (!axis.includes("'")) {
return `'${axis}'`;
}
if (!axis.includes('"')) {
return `"${axis}"`;
}

throw new Error(
`Font variation axis tags containing both quote characters must use the string form: ${JSON.stringify(axis)}`,
);
}

function processFontVariationSettings(
settings: ____FontVariationSettings_Internal,
): string {
if (typeof settings === 'string') {
return settings;
}

return Object.keys(settings)
.sort()
.map(axis => {
const value = settings[axis];
if (!Number.isFinite(value)) {
throw new Error(
`Font variation axis values must be finite numbers: ${String(value)}`,
);
}
return `${quoteAxis(axis)} ${String(value)}`;
})
.join(', ');
}

export default processFontVariationSettings;
38 changes: 38 additions & 0 deletions packages/react-native/Libraries/Text/__tests__/Text-itest.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,44 @@ describe('<Text>', () => {
});
});

describe('fontVariationSettings', () => {
it('serializes object settings', () => {
const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(
<Text style={{fontVariationSettings: {wght: 552.5, opsz: 17.25}}}>
{TEST_TEXT}
</Text>,
);
});

expect(
root.getRenderedOutput({props: ['fontVariationSettings']}).toJSX(),
).toEqual(
<rn-paragraph fontVariationSettings="'opsz' 17.25, 'wght' 552.5">
{TEST_TEXT}
</rn-paragraph>,
);
});

it('serializes an empty object as an explicit clear', () => {
const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(
<Text style={{fontVariationSettings: {}}}>{TEST_TEXT}</Text>,
);
});

expect(
root.getRenderedOutput({props: ['fontVariationSettings']}).toJSX(),
).toEqual(
<rn-paragraph fontVariationSettings="">{TEST_TEXT}</rn-paragraph>,
);
});
});

describe('adjustsFontSizeToFit', () => {
it(`can be set to "true"`, () => {
const root = Fantom.createRoot();
Expand Down
Loading
Loading