Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add NativeFontMetrics package",
"packageName": "@fluentui-react-native/experimental-native-font-metrics",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Empty file.
Empty file.
22 changes: 22 additions & 0 deletions packages/experimental/NativeFontMetrics/FRNFontMetrics.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require 'json'

package = JSON.parse(File.read(File.join(__dir__, 'package.json')))

Pod::Spec.new do |s|
s.name = 'FRNFontMetrics'
s.version = package['version']
s.summary = package['description']
s.license = package['license']

s.authors = package['author']
s.homepage = "https://github.com/microsoft/fluentui-react-native"

s.source = { :git => "https://github.com/microsoft/fluentui-react-native.git", :tag => "#{s.version}" }
s.swift_version = "5"

s.dependency 'React'

s.ios.deployment_target = "14.0"
s.ios.source_files = "ios/*.{swift,h,m}"

end
1 change: 1 addition & 0 deletions packages/experimental/NativeFontMetrics/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@fluentui-react-native/scripts/babel.config');
31 changes: 31 additions & 0 deletions packages/experimental/NativeFontMetrics/ios/FRNFontMetrics.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface FRNFontMetrics : RCTEventEmitter <RCTBridgeModule>
@end

#import <React/RCTConvert.h>

typedef NS_ENUM(NSInteger, FRNTextStyle) {
FRNTextStyleUndefined,
FRNTextStyleCaption2,
FRNTextStyleCaption1,
FRNTextStyleFootnote,
FRNTextStyleSubheadline,
FRNTextStyleCallout,
FRNTextStyleBody,
FRNTextStyleHeadline,
FRNTextStyleTitle3,
FRNTextStyleTitle2,
FRNTextStyleTitle1,
FRNTextStyleLargeTitle
};

@interface RCTConvert (FRNFontMetrics)

+ (FRNTextStyle)FRNTextStyle:(nullable id)json;

@end

UIFontMetrics *_Nonnull FRNUIFontMetricsForTextStyle(FRNTextStyle textStyle);
CGFloat FRNBaseSizeForTextStyle(FRNTextStyle textStyle);
140 changes: 140 additions & 0 deletions packages/experimental/NativeFontMetrics/ios/FRNFontMetrics.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#import "FRNFontMetrics.h"

static NSDictionary<NSString *, NSNumber *> *FRNRecognizedTextStyles() {
static NSDictionary<NSString *, NSNumber *> *styles;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
styles = @{
@"caption1": @(FRNTextStyleCaption1),
@"caption2": @(FRNTextStyleCaption2),
@"footnote": @(FRNTextStyleFootnote),
@"subheadline": @(FRNTextStyleSubheadline),
@"callout": @(FRNTextStyleCallout),
@"body": @(FRNTextStyleBody),
@"headline": @(FRNTextStyleHeadline),
@"title3": @(FRNTextStyleTitle3),
@"title2": @(FRNTextStyleTitle2),
@"title1": @(FRNTextStyleTitle1),
@"largeTitle": @(FRNTextStyleLargeTitle),
};
});
return styles;
}

@implementation RCTConvert (FRNTextStyle)

RCT_ENUM_CONVERTER(FRNTextStyle, FRNRecognizedTextStyles(), FRNTextStyleUndefined, integerValue)

@end

NS_ASSUME_NONNULL_BEGIN

UIFontMetrics *FRNUIFontMetricsForTextStyle(FRNTextStyle textStyle) {
static NSDictionary<NSNumber *, UIFontTextStyle> *mapping;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mapping = @{
@(FRNTextStyleCaption2): UIFontTextStyleCaption2,
@(FRNTextStyleCaption1): UIFontTextStyleCaption1,
@(FRNTextStyleFootnote): UIFontTextStyleFootnote,
@(FRNTextStyleSubheadline): UIFontTextStyleSubheadline,
@(FRNTextStyleCallout): UIFontTextStyleCallout,
@(FRNTextStyleBody): UIFontTextStyleBody,
@(FRNTextStyleHeadline): UIFontTextStyleHeadline,
@(FRNTextStyleTitle3): UIFontTextStyleTitle3,
@(FRNTextStyleTitle2): UIFontTextStyleTitle2,
@(FRNTextStyleTitle1): UIFontTextStyleTitle1,
@(FRNTextStyleLargeTitle): UIFontTextStyleLargeTitle,
};
});

id uiFontTextStyle = mapping[@(textStyle)] ?: UIFontTextStyleBody; // Default to body if we don't recognize the specified ramp
return [UIFontMetrics metricsForTextStyle:uiFontTextStyle];
}

CGFloat FRNBaseSizeForTextStyle(FRNTextStyle textStyle) {
static NSDictionary<NSNumber *, NSNumber *> *mapping;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Values taken from https://developer.apple.com/design/human-interface-guidelines/foundations/typography/#specifications
mapping = @{
@(FRNTextStyleCaption2): @11,
@(FRNTextStyleCaption1): @12,
@(FRNTextStyleFootnote): @13,
@(FRNTextStyleSubheadline): @15,
@(FRNTextStyleCallout): @16,
@(FRNTextStyleBody): @17,
@(FRNTextStyleHeadline): @17,
@(FRNTextStyleTitle3): @20,
@(FRNTextStyleTitle2): @22,
@(FRNTextStyleTitle1): @28,
@(FRNTextStyleLargeTitle): @34,
};
});

NSNumber *baseSize = mapping[@(textStyle)] ?: @17; // Default to body size if we don't recognize the specified ramp
return CGFLOAT_IS_DOUBLE ? [baseSize doubleValue] : [baseSize floatValue];
}

@implementation FRNFontMetrics {
BOOL hasListeners;
}

+ (BOOL)requiresMainQueueSetup
{
return YES;
}

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(allScaleFactors)
{
NSMutableDictionary *result = [NSMutableDictionary new];
[FRNRecognizedTextStyles() enumerateKeysAndObjectsUsingBlock:^(NSString * styleString, __unused NSNumber * boxedTextStyle, __unused BOOL * stop) {
result[styleString] = [self scaleFactorForStyle:styleString];
}];
return result;
}

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(scaleFactorForStyle:(NSString *)styleString)
{
FRNTextStyle style = [RCTConvert FRNTextStyle:styleString];
UIFontMetrics *fontMetrics = FRNUIFontMetricsForTextStyle(style);
CGFloat baseSize = FRNBaseSizeForTextStyle(style);
CGFloat scaleFactor = [fontMetrics scaledValueForValue:baseSize] / baseSize;
return @(scaleFactor);
}

#pragma mark - RCTEventEmitter

- (NSArray<NSString *> *)supportedEvents
{
return @[ @"onFontMetricsChanged" ];
}

- (void)startObserving
{
hasListeners = YES;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onFontMetricsChanged:)
name:UIContentSizeCategoryDidChangeNotification
object:nil];
}

- (void)stopObserving
{
hasListeners = NO;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark - Event processing

- (void)onFontMetricsChanged:(NSNotification *)notification {
if (hasListeners) {
[self sendEventWithName:@"onFontMetricsChanged" body:@{@"newScaleFactors": [self allScaleFactors]}];
}
}

RCT_EXPORT_MODULE();

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// This dummy project file is required for @react-native-community/cli <8.0 to recognize this package as a native module
3 changes: 3 additions & 0 deletions packages/experimental/NativeFontMetrics/just.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { preset } = require('@fluentui-react-native/scripts');

preset();
55 changes: 55 additions & 0 deletions packages/experimental/NativeFontMetrics/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "@fluentui-react-native/experimental-native-font-metrics",
"version": "0.1.0",
"description": "A temporary partial wrapper for UIFontMetrics.",
"license": "MIT",
"author": "Microsoft <[email protected]>",
"homepage": "https://github.com/microsoft/fluentui-react-native",
"main": "src/index.ts",
"module": "src/index.ts",
"typings": "lib/index.d.ts",
"onPublish": {
"main": "lib-commonjs/index.js",
"module": "lib/index.js"
},
"scripts": {
"build": "fluentui-scripts build",
"just": "fluentui-scripts",
"clean": "fluentui-scripts clean",
"lint": "fluentui-scripts eslint",
"depcheck": "fluentui-scripts depcheck",
"test": "fluentui-scripts jest",
"update-snapshots": "fluentui-scripts jest -u",
"prettier": "fluentui-scripts prettier",
"prettier-fix": "fluentui-scripts prettier --fix true"
},
"repository": {
"type": "git",
"url": "https://github.com/microsoft/fluentui-react-native.git",
"directory": "packages/experimental/NativeFontMetrics"
},
"devDependencies": {
"@fluentui-react-native/eslint-config-rules": "^0.1.1",
"@fluentui-react-native/scripts": "^0.1.1",
"@types/react-native": "^0.68.0",
"@types/use-subscription": "1.0.0",
"react": "17.0.2",
"react-native": "^0.68.0",
"use-subscription": "1.8.0"
},
"peerDependencies": {
"react": "17.0.2",
"react-native": "^0.68.0"
},
"rnx-kit": {
"reactNativeVersion": "^0.68",
"reactNativeDevVersion": "^0.68",
"kitType": "library",
"capabilities": [
"core",
"core-android",
"core-ios",
"react"
]
}
}
24 changes: 24 additions & 0 deletions packages/experimental/NativeFontMetrics/src/NativeFontMetrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NativeModules } from 'react-native';

export const NativeFontMetrics = NativeModules.FRNFontMetrics;

export type TextStyle =
| 'caption2'
| 'caption1'
| 'footnote'
| 'subheadline'
| 'callout'
| 'body'
| 'headline'
| 'title3'
| 'title2'
| 'title1'
| 'largeTitle';

export type ScaleFactors = { [K in TextStyle]: number | undefined };

interface NativeFontMetricsInterface {
allScaleFactors(): ScaleFactors;
scaleFactorForStyle(style: TextStyle): number;
}
export default NativeFontMetrics as NativeFontMetricsInterface;
2 changes: 2 additions & 0 deletions packages/experimental/NativeFontMetrics/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './NativeFontMetrics';
export * from './useFontMetrics';
23 changes: 23 additions & 0 deletions packages/experimental/NativeFontMetrics/src/useFontMetrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useMemo } from 'react';
import { NativeEventEmitter } from 'react-native';
import { useSubscription } from 'use-subscription';
import NativeFontMetrics, { ScaleFactors } from './NativeFontMetrics';

const eventEmitter = new NativeEventEmitter(NativeFontMetrics as any);

export function useFontMetrics(): ScaleFactors {
const subscription = useMemo(
() => ({
getCurrentValue: () => NativeFontMetrics.allScaleFactors(),
subscribe: (callback) => {
const appearanceSubscription = eventEmitter.addListener('onFontMetricsChanged', callback);
return () => {
appearanceSubscription.remove();
};
},
}),
[],
);

return useSubscription(subscription);
}
7 changes: 7 additions & 0 deletions packages/experimental/NativeFontMetrics/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@fluentui-react-native/scripts/tsconfig.json",
"compilerOptions": {
"outDir": "lib"
},
"include": ["src"]
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3556,6 +3556,11 @@
resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz#9bd0b47f26b5a3151be21ba4ce9f5fa457c5f190"
integrity sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==

"@types/[email protected]":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/use-subscription/-/use-subscription-1.0.0.tgz#d146f8d834f70f50d48bd8246a481d096f11db19"
integrity sha512-0WWZ5GUDKMXUY/1zy4Ur5/zsC0s/B+JjXfHdkvx6JgDNZzZV5eW+KKhDqsTGyqX56uh99gwGwbsKbVwkcVIKQA==

"@types/[email protected]":
version "8.3.4"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
Expand Down