feat(core): support css font-variation-settings (#9995)

This commit is contained in:
Ruslan Lekhman
2022-11-23 21:57:22 -07:00
committed by GitHub
parent 501d310431
commit a5e3e223dd
12 changed files with 219 additions and 66 deletions

View File

@ -6,7 +6,7 @@
@interface NativeScriptUtils : NSObject @interface NativeScriptUtils : NSObject
+(UIFont*) getSystemFont:(CGFloat)size weight:(UIFontWeight)weight italic:(BOOL)italic symbolicTraits:(CGFloat)symbolicTraits; +(UIFont*) getSystemFont:(CGFloat)size weight:(UIFontWeight)weight italic:(BOOL)italic symbolicTraits:(UIFontDescriptorSymbolicTraits)symbolicTraits;
+(UIFont*) createUIFont:(NSDictionary*)font; +(UIFont*) createUIFont:(NSDictionary*)font;
+(NSMutableAttributedString*)createMutableStringWithDetails:(NSDictionary*)details; +(NSMutableAttributedString*)createMutableStringWithDetails:(NSDictionary*)details;
+(NSMutableAttributedString*)createMutableStringForSpan:(NSString*)text font:(UIFont*)font color:(UIColor*)color backgroundColor:(UIColor*)backgroundColor textDecoration:(NSString*)textDecoration baselineOffset:(CGFloat)baselineOffset; +(NSMutableAttributedString*)createMutableStringForSpan:(NSString*)text font:(UIFont*)font color:(UIColor*)color backgroundColor:(UIColor*)backgroundColor textDecoration:(NSString*)textDecoration baselineOffset:(CGFloat)baselineOffset;

View File

@ -2,7 +2,7 @@
@implementation NativeScriptUtils @implementation NativeScriptUtils
+(UIFont*) getSystemFont:(CGFloat)size weight:(UIFontWeight)weight italic:(BOOL)italic symbolicTraits:(CGFloat)symbolicTraits { +(UIFont*) getSystemFont:(CGFloat)size weight:(UIFontWeight)weight italic:(BOOL)italic symbolicTraits:(UIFontDescriptorSymbolicTraits)symbolicTraits {
UIFont *result = [UIFont systemFontOfSize:size weight:weight]; UIFont *result = [UIFont systemFontOfSize:size weight:weight];
if (italic) { if (italic) {
result = [UIFont fontWithDescriptor:[result.fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits] size:size]; result = [UIFont fontWithDescriptor:[result.fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits] size:size];
@ -35,11 +35,11 @@
} else if ([family.lowercaseString isEqualToString:@"monospace"]) { } else if ([family.lowercaseString isEqualToString:@"monospace"]) {
fontFamily = @"Courier New"; fontFamily = @"Courier New";
} }
if (!fontFamily || [fontFamily isEqualToString:@"sans-serif"] || [fontFamily isEqualToString:@"system"]) { if (!fontFamily || [fontFamily isEqualToString:@"sans-serif"] || [fontFamily isEqualToString:@"system"]) {
result = [NativeScriptUtils getSystemFont:size weight:(UIFontWeight)[[font valueForKey:@"fontWeight"] floatValue] italic:[[font valueForKey:@"isItalic"] boolValue] symbolicTraits:symbolicTraits]; result = [NativeScriptUtils getSystemFont:size weight:(UIFontWeight)[[font valueForKey:@"fontWeight"] floatValue] italic:[[font valueForKey:@"isItalic"] boolValue] symbolicTraits:symbolicTraits];
break; break;
} else { } else {
UIFontDescriptor *descriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{ UIFontDescriptor *descriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{
UIFontDescriptorFamilyAttribute: fontFamily, UIFontDescriptorFamilyAttribute: fontFamily,
UIFontDescriptorTraitsAttribute: fontDescriptorTraits UIFontDescriptorTraitsAttribute: fontDescriptorTraits

View File

@ -1,4 +1,3 @@
declare const enum MDCAnimationTimingFunction { declare const enum MDCAnimationTimingFunction {
Standard = 0, Standard = 0,

View File

@ -1,4 +1,3 @@
declare class NativeScriptUtils extends NSObject { declare class NativeScriptUtils extends NSObject {
static alloc(): NativeScriptUtils; // inherited from NSObject static alloc(): NativeScriptUtils; // inherited from NSObject
@ -11,7 +10,7 @@ declare class NativeScriptUtils extends NSObject {
static getImageDataFormatQuality(image: UIImage, format: string, quality: number): NSData; static getImageDataFormatQuality(image: UIImage, format: string, quality: number): NSData;
static getSystemFontWeightItalicSymbolicTraits(size: number, weight: number, italic: boolean, symbolicTraits: number): UIFont; static getSystemFontWeightItalicSymbolicTraits(size: number, weight: number, italic: boolean, symbolicTraits: UIFontDescriptorSymbolicTraits): UIFont;
static new(): NativeScriptUtils; // inherited from NSObject static new(): NativeScriptUtils; // inherited from NSObject

View File

@ -59,7 +59,7 @@ export { Background } from './styling/background';
export type { CacheMode } from './styling/background'; export type { CacheMode } from './styling/background';
export { parseCSSShadow } from './styling/css-shadow'; export { parseCSSShadow } from './styling/css-shadow';
export { animationTimingFunctionConverter, timeConverter } from './styling/converters'; export { animationTimingFunctionConverter, timeConverter } from './styling/converters';
export { Font, FontStyle, FontWeight } from './styling/font'; export { Font, FontStyle, FontWeight, FontVariationSettings } from './styling/font';
export { Style } from './styling/style'; export { Style } from './styling/style';
export type { CommonLayoutParams } from './styling/style'; export type { CommonLayoutParams } from './styling/style';
export * from './styling/style-properties'; export * from './styling/style-properties';

View File

@ -1,8 +1,9 @@
import { Font as FontDefinition } from './font'; import { Font as FontDefinition, FontVariationSettings as FontVariationSettingsType } from './font';
import { ParsedFont } from './font-interfaces'; import { ParsedFont } from './font-interfaces';
import { makeValidator, makeParser } from '../core/properties'; import { makeValidator, makeParser } from '../core/properties';
export * from './font-interfaces'; export * from './font-interfaces';
export { FontVariationSettingsType };
export abstract class Font implements FontDefinition { export abstract class Font implements FontDefinition {
public static default = undefined; public static default = undefined;
@ -18,7 +19,7 @@ export abstract class Font implements FontDefinition {
return this.fontWeight === FontWeight.SEMI_BOLD || this.fontWeight === FontWeight.BOLD || this.fontWeight === '700' || this.fontWeight === FontWeight.EXTRA_BOLD || this.fontWeight === FontWeight.BLACK; return this.fontWeight === FontWeight.SEMI_BOLD || this.fontWeight === FontWeight.BOLD || this.fontWeight === '700' || this.fontWeight === FontWeight.EXTRA_BOLD || this.fontWeight === FontWeight.BLACK;
} }
protected constructor(public readonly fontFamily: string, public readonly fontSize: number, fontStyle?: FontStyleType, fontWeight?: FontWeightType, fontScale?: number) { protected constructor(public readonly fontFamily: string, public readonly fontSize: number, fontStyle?: FontStyleType, fontWeight?: FontWeightType, fontScale?: number, public readonly fontVariationSettings?: FontVariationSettingsType[]) {
this.fontStyle = fontStyle ?? FontStyle.NORMAL; this.fontStyle = fontStyle ?? FontStyle.NORMAL;
this.fontWeight = fontWeight ?? FontWeight.NORMAL; this.fontWeight = fontWeight ?? FontWeight.NORMAL;
this.fontScale = fontScale ?? 1; this.fontScale = fontScale ?? 1;
@ -31,6 +32,7 @@ export abstract class Font implements FontDefinition {
public abstract withFontWeight(weight: FontWeightType): Font; public abstract withFontWeight(weight: FontWeightType): Font;
public abstract withFontSize(size: number): Font; public abstract withFontSize(size: number): Font;
public abstract withFontScale(scale: number): Font; public abstract withFontScale(scale: number): Font;
public abstract withFontVariationSettings(variationSettings: FontVariationSettingsType[] | null): Font;
public static equals(value1: Font, value2: Font): boolean { public static equals(value1: Font, value2: Font): boolean {
// both values are falsy // both values are falsy
@ -70,6 +72,50 @@ export namespace FontWeight {
export const parse = makeParser<FontWeightType>(isValid); export const parse = makeParser<FontWeightType>(isValid);
} }
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace FontVariationSettings {
export function parse(fontVariationSettings: string): FontVariationSettingsType[] | null {
const allowedValues = ['normal', 'inherit', 'initial', 'revert', 'revert-layer', 'unset'];
const lower = fontVariationSettings?.toLowerCase().trim();
if (allowedValues.indexOf(lower) !== -1) {
return null;
}
const chunks = lower.split(',');
if (chunks.length) {
const parsed: FontVariationSettingsType[] = [];
for (const chunk of chunks) {
const axisChunks = chunk.trim();
if (axisChunks.length === 2) {
const axisName = chunk[0].trim();
const axisValue = parseFloat(chunk[0]);
// See https://drafts.csswg.org/css-fonts/#font-variation-settings-def.
// Axis name strings longer or shorter than four characters are invalid.
if (!isNaN(axisValue) && axisName.length === 6 && ((axisName.startsWith("'") && axisName.endsWith("'")) || (axisName.startsWith('"') && axisName.endsWith('"')))) {
parsed.push({ axis: axisName, value: axisValue });
} else {
throw new Error('Invalid value (font-variation-settings): ' + fontVariationSettings);
}
} else {
throw new Error('Invalid value (font-variation-settings): ' + fontVariationSettings);
}
}
return parsed;
}
throw new Error('Invalid value (font-variation-settings): ' + fontVariationSettings);
}
export function toString(fontVariationSettings: FontVariationSettingsType[] | null): string | null {
if (fontVariationSettings?.length) {
return fontVariationSettings.map(({ axis, value }) => `'${axis}' ${value}`).join(', ');
}
return null;
}
}
export function parseFontFamily(value: string): Array<string> { export function parseFontFamily(value: string): Array<string> {
if (!value) { if (!value) {
return []; return [];
@ -140,3 +186,31 @@ export function parseFont(fontValue: string): ParsedFont {
return result; return result;
} }
/**
* Kind of hack.
* Used to search font variation axis names, since iOS for some reason requires names
* but tags are the standards.
*/
export function fuzzySearch(query: string, dataset: string[]): string[] | null {
const q = query ? query.trim().toLowerCase() : '';
const result: string[] = [];
if (!q.length) {
return null;
}
dataset.forEach((item) => {
const s = item.trim().toLowerCase();
let n = -1;
for (const char of q) {
n = s.indexOf(char, n + 1);
if (!~n) {
return;
}
}
result.push(item);
});
return result.length ? result : null;
}

View File

@ -1,8 +1,10 @@
import { Font as FontBase, parseFontFamily, genericFontFamilies, FontStyleType, FontWeight, FontWeightType } from './font-common'; import { Font as FontBase, parseFontFamily, genericFontFamilies, FontStyleType, FontWeight, FontWeightType, FontVariationSettingsType, FontVariationSettings } from './font-common';
import { Trace } from '../../trace'; import { Trace } from '../../trace';
import { SDK_VERSION } from '../../utils'; import { SDK_VERSION } from '../../utils';
import * as application from '../../application'; import * as application from '../../application';
import * as fs from '../../file-system'; import * as fs from '../../file-system';
import { Trace } from '../../trace';
import { ad } from '../../utils';
export * from './font-common'; export * from './font-common';
@ -15,28 +17,32 @@ export class Font extends FontBase {
private _typeface: android.graphics.Typeface; private _typeface: android.graphics.Typeface;
constructor(family: string, size: number, style?: FontStyleType, weight?: FontWeightType) { constructor(family: string, size: number, style?: FontStyleType, weight?: FontWeightType, fontVariationSettings?: FontVariationSettingsType[]) {
super(family, size, style, weight, 1); super(family, size, style, weight, 1, fontVariationSettings);
} }
public withFontFamily(family: string): Font { public withFontFamily(family: string): Font {
return new Font(family, this.fontSize, this.fontStyle, this.fontWeight); return new Font(family, this.fontSize, this.fontStyle, this.fontWeight, this.fontVariationSettings);
} }
public withFontStyle(style: FontStyleType): Font { public withFontStyle(style: FontStyleType): Font {
return new Font(this.fontFamily, this.fontSize, style, this.fontWeight); return new Font(this.fontFamily, this.fontSize, style, this.fontWeight, this.fontVariationSettings);
} }
public withFontWeight(weight: FontWeightType): Font { public withFontWeight(weight: FontWeightType): Font {
return new Font(this.fontFamily, this.fontSize, this.fontStyle, weight); return new Font(this.fontFamily, this.fontSize, this.fontStyle, weight, this.fontVariationSettings);
} }
public withFontSize(size: number): Font { public withFontSize(size: number): Font {
return new Font(this.fontFamily, size, this.fontStyle, this.fontWeight); return new Font(this.fontFamily, size, this.fontStyle, this.fontWeight, this.fontVariationSettings);
} }
public withFontScale(scale: number): Font { public withFontScale(scale: number): Font {
return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight); return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, this.fontVariationSettings);
}
public withFontVariationSettings(variationSettings: FontVariationSettingsType[] | null): Font {
return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, variationSettings);
} }
public getAndroidTypeface(): android.graphics.Typeface { public getAndroidTypeface(): android.graphics.Typeface {
@ -52,13 +58,21 @@ export class Font extends FontBase {
} }
} }
function loadFontFromFile(fontFamily: string): android.graphics.Typeface { function computeFontCacheKey(fontFamily: string, font: Font) {
appAssets = appAssets || application.android.context.getAssets(); const sep = ':';
return [fontFamily, String(FontVariationSettings.toString(font.fontVariationSettings)).replace(/'/g, '').replace(/[\s,]/g, '_')].join(sep);
}
function loadFontFromFile(fontFamily: string, font: Font): android.graphics.Typeface {
const apiLevel = android.os.Build.VERSION.SDK_INT;
const cacheKey = apiLevel >= 26 ? computeFontCacheKey(fontFamily, font) : fontFamily;
appAssets = appAssets || (ad.getApplicationContext() as android.content.Context).getAssets();
if (!appAssets) { if (!appAssets) {
return null; return null;
} }
let result = typefaceCache.get(fontFamily); let result = typefaceCache.get(cacheKey);
// Check for undefined explicitly as null mean we tried to load the font, but failed. // Check for undefined explicitly as null mean we tried to load the font, but failed.
if (result === undefined) { if (result === undefined) {
result = null; result = null;
@ -69,23 +83,36 @@ function loadFontFromFile(fontFamily: string): android.graphics.Typeface {
fontAssetPath = FONTS_BASE_PATH + fontFamily + '.ttf'; fontAssetPath = FONTS_BASE_PATH + fontFamily + '.ttf';
} else if (fs.File.exists(basePath + '.otf')) { } else if (fs.File.exists(basePath + '.otf')) {
fontAssetPath = FONTS_BASE_PATH + fontFamily + '.otf'; fontAssetPath = FONTS_BASE_PATH + fontFamily + '.otf';
} else { } else if (Trace.isEnabled()) {
if (Trace.isEnabled()) {
Trace.write('Could not find font file for ' + fontFamily, Trace.categories.Error, Trace.messageType.error); Trace.write('Could not find font file for ' + fontFamily, Trace.categories.Error, Trace.messageType.error);
} }
}
if (fontAssetPath) { if (fontAssetPath) {
try { try {
fontAssetPath = fs.path.join(fs.knownFolders.currentApp().path, fontAssetPath); fontAssetPath = fs.path.join(fs.knownFolders.currentApp().path, fontAssetPath);
if (apiLevel >= 26) {
const builder = new android.graphics.Typeface.Builder(fontAssetPath);
if (builder) {
if (font.fontVariationSettings !== undefined) {
builder.setFontVariationSettings(font.fontVariationSettings?.length ? FontVariationSettings.toString(font.fontVariationSettings) : '');
}
result = builder.build();
} else {
result = android.graphics.Typeface.createFromFile(fontAssetPath); result = android.graphics.Typeface.createFromFile(fontAssetPath);
if (Trace.isEnabled()) {
Trace.write('Could not create builder for ' + fontFamily, Trace.categories.Error, Trace.messageType.error);
}
}
} else {
result = android.graphics.Typeface.createFromFile(fontAssetPath);
}
} catch (e) { } catch (e) {
if (Trace.isEnabled()) { if (Trace.isEnabled()) {
Trace.write('Error loading font asset: ' + fontAssetPath, Trace.categories.Error, Trace.messageType.error); Trace.write('Error loading font asset: ' + fontAssetPath, Trace.categories.Error, Trace.messageType.error);
} }
} }
} }
typefaceCache.set(fontFamily, result); typefaceCache.set(cacheKey, result);
} }
return result; return result;
@ -101,10 +128,10 @@ function createTypeface(font: Font): android.graphics.Typeface {
} }
//http://stackoverflow.com/questions/19691530/valid-values-for-androidfontfamily-and-what-they-map-to //http://stackoverflow.com/questions/19691530/valid-values-for-androidfontfamily-and-what-they-map-to
const fonts = parseFontFamily(font.fontFamily); const fontFamilies = parseFontFamily(font.fontFamily);
let result = null; let result: android.graphics.Typeface = null;
for (let i = 0; i < fonts.length; i++) { for (const fontFamily of fontFamilies) {
switch (fonts[i].toLowerCase()) { switch (fontFamily.toLowerCase()) {
case genericFontFamilies.serif: case genericFontFamilies.serif:
result = android.graphics.Typeface.create('serif' + getFontWeightSuffix(font.fontWeight), fontStyle); result = android.graphics.Typeface.create('serif' + getFontWeightSuffix(font.fontWeight), fontStyle);
break; break;
@ -118,13 +145,14 @@ function createTypeface(font: Font): android.graphics.Typeface {
result = android.graphics.Typeface.create('monospace' + getFontWeightSuffix(font.fontWeight), fontStyle); result = android.graphics.Typeface.create('monospace' + getFontWeightSuffix(font.fontWeight), fontStyle);
break; break;
default: default: {
result = loadFontFromFile(fonts[i]); result = loadFontFromFile(fontFamily, font);
if (result && fontStyle) { if (result && fontStyle) {
result = android.graphics.Typeface.create(result, fontStyle); result = android.graphics.Typeface.create(result, fontStyle);
} }
break; break;
} }
}
if (result) { if (result) {
// Found the font! // Found the font!

View File

@ -6,11 +6,12 @@
public fontWeight: FontWeightType; public fontWeight: FontWeightType;
public fontSize: number; public fontSize: number;
public fontScale: number; public fontScale: number;
public fontVariationSettings?: FontVariationSettings[];
public isBold: boolean; public isBold: boolean;
public isItalic: boolean; public isItalic: boolean;
constructor(family: string, size: number, style?: FontStyleType, weight?: FontWeightType, scale?: number); constructor(family: string, size: number, style?: FontStyleType, weight?: FontWeightType, scale?: number, fontVariationSettings?: FontVariationSettings[]);
public getAndroidTypeface(): any /* android.graphics.Typeface */; public getAndroidTypeface(): any /* android.graphics.Typeface */;
public getUIFont(defaultFont: any /* UIFont */): any /* UIFont */; public getUIFont(defaultFont: any /* UIFont */): any /* UIFont */;
@ -20,6 +21,7 @@
public withFontWeight(weight: FontWeightType): Font; public withFontWeight(weight: FontWeightType): Font;
public withFontSize(size: number): Font; public withFontSize(size: number): Font;
public withFontScale(scale: number): Font; public withFontScale(scale: number): Font;
public withFontVariationSettings(variationSettings: FontVariationSettings[] | null): Font;
public static equals(value1: Font, value2: Font): boolean; public static equals(value1: Font, value2: Font): boolean;
} }
@ -47,6 +49,16 @@ export namespace FontWeight {
export function parse(value: string): FontWeightType; export function parse(value: string): FontWeightType;
} }
export interface FontVariationSettings {
axis: string;
value: number;
}
export namespace FontVariationSettings {
export function parse(fontVariationSettings: string): FontVariationSettings[] | null;
export function toString(fontVariationSettings: FontVariationSettings[] | null): string | null;
}
export interface ParsedFont { export interface ParsedFont {
fontStyle?: FontStyleType; fontStyle?: FontStyleType;
fontVariant?: string; fontVariant?: string;
@ -54,6 +66,7 @@ export interface ParsedFont {
lineHeight?: string; lineHeight?: string;
fontSize?: string; fontSize?: string;
fontFamily?: string; fontFamily?: string;
fontVariationSettings?: FontVariationSettings[];
} }
export function parseFont(fontValue: string): ParsedFont; export function parseFont(fontValue: string): ParsedFont;

View File

@ -1,4 +1,4 @@
import { Font as FontBase, parseFontFamily, FontStyle, FontWeight, FontStyleType, FontWeightType } from './font-common'; import { Font as FontBase, parseFontFamily, FontWeight, FontStyleType, FontWeightType, FontVariationSettingsType, FontVariationSettings } from './font-common';
import { Trace } from '../../trace'; import { Trace } from '../../trace';
import * as fs from '../../file-system'; import * as fs from '../../file-system';
export * from './font-common'; export * from './font-common';
@ -7,6 +7,7 @@ interface FontDescriptor {
fontFamily: string[]; fontFamily: string[];
fontSize: number; fontSize: number;
fontWeight: number; fontWeight: number;
fontVariationSettings: FontVariationSettingsType[] | null;
isBold: boolean; isBold: boolean;
isItalic: boolean; isItalic: boolean;
} }
@ -14,9 +15,9 @@ interface FontDescriptor {
const uiFontCache = new Map<string, UIFont>(); const uiFontCache = new Map<string, UIFont>();
function computeFontCacheKey(fontDescriptor: FontDescriptor) { function computeFontCacheKey(fontDescriptor: FontDescriptor) {
const { fontFamily, fontSize, fontWeight, isBold, isItalic } = fontDescriptor; const { fontFamily, fontSize, fontWeight, fontVariationSettings, isBold, isItalic } = fontDescriptor;
const sep = ':'; const sep = ':';
return fontFamily.join(sep) + sep + fontSize + sep + fontWeight + sep + isBold + sep + isItalic; return [fontFamily.join(sep), fontSize, fontWeight, String(FontVariationSettings.toString(fontVariationSettings)).replace(/'/g, '').replace(/[\s,]/g, '_'), isBold, isItalic].join(sep);
} }
function getUIFontCached(fontDescriptor: FontDescriptor) { function getUIFontCached(fontDescriptor: FontDescriptor) {
@ -28,7 +29,25 @@ function getUIFontCached(fontDescriptor: FontDescriptor) {
} }
return uiFontCache.get(cacheKey); return uiFontCache.get(cacheKey);
} }
const uiFont = NativeScriptUtils.createUIFont(fontDescriptor as any); let uiFont = NativeScriptUtils.createUIFont(fontDescriptor as any);
if (fontDescriptor.fontVariationSettings?.length) {
let font = CGFontCreateWithFontName(uiFont.fontName);
const variationAxes: NSArray<NSDictionary<'kCGFontVariationAxisDefaultValue' | 'kCGFontVariationAxisMaxValue' | 'kCGFontVariationAxisMinValue' | 'kCGFontVariationAxisName', string | number>> = CGFontCopyVariationAxes(font);
const variationAxesNames: string[] = [];
for (const axis of variationAxes) {
variationAxesNames.push(String(axis.objectForKey('kCGFontVariationAxisName')));
}
const variationSettings = {};
for (const variationSetting of fontDescriptor.fontVariationSettings) {
const axisName = fuzzySearch(variationSetting.axis, variationAxesNames);
if (axisName?.length) {
variationSettings[axisName[0]] = variationSetting.value;
}
}
font = CGFontCreateCopyWithVariations(font, variationSettings as any);
uiFont = CTFontCreateWithGraphicsFont(font, fontDescriptor.fontSize, null, null);
}
uiFontCache.set(cacheKey, uiFont); uiFontCache.set(cacheKey, uiFont);
if (Trace.isEnabled()) { if (Trace.isEnabled()) {
Trace.write(`UIFont creation: ${JSON.stringify(fontDescriptor)}, cache size: ${uiFontCache.size}`, Trace.categories.Style, Trace.messageType.info); Trace.write(`UIFont creation: ${JSON.stringify(fontDescriptor)}, cache size: ${uiFontCache.size}`, Trace.categories.Style, Trace.messageType.info);
@ -40,28 +59,32 @@ function getUIFontCached(fontDescriptor: FontDescriptor) {
export class Font extends FontBase { export class Font extends FontBase {
public static default = new Font(undefined, undefined); public static default = new Font(undefined, undefined);
constructor(family: string, size: number, style?: FontStyleType, weight?: FontWeightType, scale?: number) { constructor(family: string, size: number, style?: FontStyleType, weight?: FontWeightType, scale?: number, variationSettings?: FontVariationSettingsType[]) {
super(family, size, style, weight, scale); super(family, size, style, weight, scale, variationSettings);
} }
public withFontFamily(family: string): Font { public withFontFamily(family: string): Font {
return new Font(family, this.fontSize, this.fontStyle, this.fontWeight, this.fontScale); return new Font(family, this.fontSize, this.fontStyle, this.fontWeight, this.fontScale, this.fontVariationSettings);
} }
public withFontStyle(style: FontStyleType): Font { public withFontStyle(style: FontStyleType): Font {
return new Font(this.fontFamily, this.fontSize, style, this.fontWeight, this.fontScale); return new Font(this.fontFamily, this.fontSize, style, this.fontWeight, this.fontScale, this.fontVariationSettings);
} }
public withFontWeight(weight: FontWeightType): Font { public withFontWeight(weight: FontWeightType): Font {
return new Font(this.fontFamily, this.fontSize, this.fontStyle, weight, this.fontScale); return new Font(this.fontFamily, this.fontSize, this.fontStyle, weight, this.fontScale, this.fontVariationSettings);
} }
public withFontSize(size: number): Font { public withFontSize(size: number): Font {
return new Font(this.fontFamily, size, this.fontStyle, this.fontWeight, this.fontScale); return new Font(this.fontFamily, size, this.fontStyle, this.fontWeight, this.fontScale, this.fontVariationSettings);
} }
public withFontScale(scale: number): Font { public withFontScale(scale: number): Font {
return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, scale); return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, scale, this.fontVariationSettings);
}
public withFontVariationSettings(variationSettings: FontVariationSettingsType[] | null): Font {
return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, this.fontScale, variationSettings);
} }
public getUIFont(defaultFont: UIFont): UIFont { public getUIFont(defaultFont: UIFont): UIFont {
@ -69,6 +92,7 @@ export class Font extends FontBase {
fontFamily: parseFontFamily(this.fontFamily), fontFamily: parseFontFamily(this.fontFamily),
fontSize: this.fontSize || defaultFont.pointSize, fontSize: this.fontSize || defaultFont.pointSize,
fontWeight: getNativeFontWeight(this.fontWeight), fontWeight: getNativeFontWeight(this.fontWeight),
fontVariationSettings: this.fontVariationSettings,
isBold: this.isBold, isBold: this.isBold,
isItalic: this.isItalic, isItalic: this.isItalic,
}); });

View File

@ -1,10 +1,10 @@
// Types // Types
import { unsetValue, CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty, makeValidator, makeParser } from '../core/properties'; import { unsetValue, CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty } from '../core/properties';
import { Style } from '../styling/style'; import { Style } from '../styling/style';
import { Transformation, TransformationValue, TransformFunctionsInfo } from '../animation'; import { Transformation, TransformationValue, TransformFunctionsInfo } from '../animation';
import { Color } from '../../color'; import { Color } from '../../color';
import { Font, parseFont, FontStyle, FontWeight } from '../../ui/styling/font'; import { Font, parseFont, FontStyle, FontWeight, FontVariationSettings } from '../../ui/styling/font';
import { layout, hasDuplicates } from '../../utils'; import { layout, hasDuplicates } from '../../utils';
import { Background } from '../../ui/styling/background'; import { Background } from '../../ui/styling/background';
@ -1424,6 +1424,23 @@ const fontProperty = new ShorthandProperty<Style, string>({
}); });
fontProperty.register(Style); fontProperty.register(Style);
export const fontVariationSettingsProperty = new InheritedCssProperty<Style, FontVariationSettings[] | null>({
name: 'fontVariationSettings',
cssName: 'font-variation-settings',
affectsLayout: global.isIOS,
valueChanged: (target, oldValue, newValue) => {
const currentFont = target.fontInternal || Font.default;
if (currentFont.fontVariationSettings !== newValue) {
const newFont = currentFont.withFontVariationSettings(newValue);
target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont;
}
},
valueConverter: (value) => {
return FontVariationSettings.parse(value);
},
});
fontVariationSettingsProperty.register(Style);
export const visibilityProperty = new CssProperty<Style, CoreTypes.VisibilityType>({ export const visibilityProperty = new CssProperty<Style, CoreTypes.VisibilityType>({
name: 'visibility', name: 'visibility',
cssName: 'visibility', cssName: 'visibility',

View File

@ -1,4 +1,3 @@
declare class NativeScriptUtils extends NSObject { declare class NativeScriptUtils extends NSObject {
static alloc(): NativeScriptUtils; // inherited from NSObject static alloc(): NativeScriptUtils; // inherited from NSObject
@ -11,7 +10,7 @@ declare class NativeScriptUtils extends NSObject {
static getImageDataFormatQuality(image: UIImage, format: string, quality: number): NSData; static getImageDataFormatQuality(image: UIImage, format: string, quality: number): NSData;
static getSystemFontWeightItalicSymbolicTraits(size: number, weight: number, italic: boolean, symbolicTraits: number): UIFont; static getSystemFontWeightItalicSymbolicTraits(size: number, weight: number, italic: boolean, symbolicTraits: UIFontDescriptorSymbolicTraits): UIFont;
static new(): NativeScriptUtils; // inherited from NSObject static new(): NativeScriptUtils; // inherited from NSObject