fix(ios): font variation settings not applied to labels (#10429)

This commit is contained in:
Dimitris-Rafail Katsampas
2023-11-22 18:38:10 +02:00
committed by GitHub
parent 67440095f4
commit 2cf166da59
14 changed files with 74 additions and 44 deletions

View File

@ -30,7 +30,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: Array<FontVariationSettingsType> | null): Font; public abstract withFontVariationSettings(variationSettings: FontVariationSettingsType[]): 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
@ -43,7 +43,7 @@ export abstract class Font implements FontDefinition {
return false; return false;
} }
return value1.fontFamily === value2.fontFamily && value1.fontSize === value2.fontSize && value1.fontStyle === value2.fontStyle && value1.fontWeight === value2.fontWeight; return value1.fontFamily === value2.fontFamily && value1.fontSize === value2.fontSize && value1.fontStyle === value2.fontStyle && value1.fontWeight === value2.fontWeight && value1.fontScale === value2.fontScale && FontVariationSettings.toString(value1.fontVariationSettings) === FontVariationSettings.toString(value2.fontVariationSettings);
} }
} }
@ -93,7 +93,9 @@ export namespace FontVariationSettings {
// See https://drafts.csswg.org/css-fonts/#font-variation-settings-def. // See https://drafts.csswg.org/css-fonts/#font-variation-settings-def.
// Axis name strings longer or shorter than four characters are invalid. // 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('"')))) { if (!isNaN(axisValue) && axisName.length === 6 && ((axisName.startsWith("'") && axisName.endsWith("'")) || (axisName.startsWith('"') && axisName.endsWith('"')))) {
parsed.push({ axis: axisName, value: axisValue }); // Remove quotes as they might cause problems when using name as an object key
const unquotedAxisName = axisName.substring(1, axisName.length - 1);
parsed.push({ axis: unquotedAxisName, value: axisValue });
} else { } else {
Trace.write('Invalid value (font-variation-settings): ' + variationSettingsValue, Trace.categories.Error, Trace.messageType.error); Trace.write('Invalid value (font-variation-settings): ' + variationSettingsValue, Trace.categories.Error, Trace.messageType.error);
} }
@ -110,7 +112,7 @@ export namespace FontVariationSettings {
export function toString(fontVariationSettings: FontVariationSettingsType[] | null): string | null { export function toString(fontVariationSettings: FontVariationSettingsType[] | null): string | null {
if (fontVariationSettings?.length) { if (fontVariationSettings?.length) {
return fontVariationSettings.map(({ axis, value }) => `${axis} ${value}`).join(', '); return fontVariationSettings.map(({ axis, value }) => `'${axis}' ${value}`).join(', ');
} }
return null; return null;

View File

@ -8,7 +8,7 @@ export interface ParsedFont {
lineHeight?: string; lineHeight?: string;
fontSize?: string; fontSize?: string;
fontFamily?: string; fontFamily?: string;
fontVariationSettings?: Array<FontVariationSettingsType>; fontVariationSettings?: FontVariationSettingsType[];
} }
export type FontVariationSettingsType = { export type FontVariationSettingsType = {

View File

@ -16,32 +16,28 @@ export class Font extends FontBase {
private _typeface: android.graphics.Typeface; private _typeface: android.graphics.Typeface;
constructor(family: string, size: number, style?: FontStyleType, weight?: FontWeightType, fontVariationSettings?: Array<FontVariationSettingsType>) {
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, this.fontVariationSettings); return new Font(family, this.fontSize, this.fontStyle, this.fontWeight, 1, this.fontVariationSettings);
} }
public withFontStyle(style: FontStyleType): Font { public withFontStyle(style: FontStyleType): Font {
return new Font(this.fontFamily, this.fontSize, style, this.fontWeight, this.fontVariationSettings); return new Font(this.fontFamily, this.fontSize, style, this.fontWeight, 1, this.fontVariationSettings);
} }
public withFontWeight(weight: FontWeightType): Font { public withFontWeight(weight: FontWeightType): Font {
return new Font(this.fontFamily, this.fontSize, this.fontStyle, weight, this.fontVariationSettings); return new Font(this.fontFamily, this.fontSize, this.fontStyle, weight, 1, this.fontVariationSettings);
} }
public withFontSize(size: number): Font { public withFontSize(size: number): Font {
return new Font(this.fontFamily, size, this.fontStyle, this.fontWeight, this.fontVariationSettings); return new Font(this.fontFamily, size, this.fontStyle, this.fontWeight, 1, this.fontVariationSettings);
} }
public withFontScale(scale: number): Font { public withFontScale(scale: number): Font {
return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, this.fontVariationSettings); return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, 1, this.fontVariationSettings);
} }
public withFontVariationSettings(variationSettings: Array<FontVariationSettingsType> | null): Font { public withFontVariationSettings(variationSettings: Array<FontVariationSettingsType> | null): Font {
return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, variationSettings); return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, 1, variationSettings);
} }
getAndroidTypeface(): android.graphics.Typeface { getAndroidTypeface(): android.graphics.Typeface {
@ -91,9 +87,7 @@ function loadFontFromFile(fontFamily: string, font: Font): android.graphics.Type
if (SDK_VERSION >= 26) { if (SDK_VERSION >= 26) {
const builder = new android.graphics.Typeface.Builder(fontAssetPath); const builder = new android.graphics.Typeface.Builder(fontAssetPath);
if (builder) { if (builder) {
if (font.fontVariationSettings !== undefined) {
builder.setFontVariationSettings(font.fontVariationSettings?.length ? FontVariationSettings.toString(font.fontVariationSettings) : ''); builder.setFontVariationSettings(font.fontVariationSettings?.length ? FontVariationSettings.toString(font.fontVariationSettings) : '');
}
result = builder.build(); result = builder.build();
} else { } else {
result = android.graphics.Typeface.createFromFile(fontAssetPath); result = android.graphics.Typeface.createFromFile(fontAssetPath);
@ -133,16 +127,13 @@ function createTypeface(font: Font): android.graphics.Typeface {
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;
case genericFontFamilies.sansSerif: case genericFontFamilies.sansSerif:
case genericFontFamilies.system: case genericFontFamilies.system:
result = android.graphics.Typeface.create('sans-serif' + getFontWeightSuffix(font.fontWeight), fontStyle); result = android.graphics.Typeface.create('sans-serif' + getFontWeightSuffix(font.fontWeight), fontStyle);
break; break;
case genericFontFamilies.monospace: case genericFontFamilies.monospace:
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(fontFamily, font); result = loadFontFromFile(fontFamily, font);
if (result && fontStyle) { if (result && fontStyle) {

View File

@ -9,7 +9,7 @@ export declare class Font extends FontBase {
public fontWeight: FontWeightType; public fontWeight: FontWeightType;
public fontSize: number; public fontSize: number;
public fontScale: number; public fontScale: number;
public fontVariationSettings?: FontVariationSettings[]; public fontVariationSettings?: FontVariationSettingsType[];
public isBold: boolean; public isBold: boolean;
public isItalic: boolean; public isItalic: boolean;
@ -24,7 +24,7 @@ export declare class Font extends FontBase {
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 withFontVariationSettings(variationSettings: FontVariationSettings[]): Font;
public static equals(value1: Font, value2: Font): boolean; public static equals(value1: Font, value2: Font): boolean;
} }

View File

@ -13,6 +13,8 @@ interface FontDescriptor {
isItalic: boolean; isItalic: boolean;
} }
type FontVariationAxisType = 'kCGFontVariationAxisDefaultValue' | 'kCGFontVariationAxisMaxValue' | 'kCGFontVariationAxisMinValue' | 'kCGFontVariationAxisName';
const uiFontCache = new Map<string, UIFont>(); const uiFontCache = new Map<string, UIFont>();
function computeFontCacheKey(fontDescriptor: FontDescriptor) { function computeFontCacheKey(fontDescriptor: FontDescriptor) {
@ -33,21 +35,28 @@ function getUIFontCached(fontDescriptor: FontDescriptor) {
let uiFont = NativeScriptUtils.createUIFont(fontDescriptor as any); let uiFont = NativeScriptUtils.createUIFont(fontDescriptor as any);
if (fontDescriptor.fontVariationSettings?.length) { if (fontDescriptor.fontVariationSettings?.length) {
let font = CGFontCreateWithFontName(uiFont.fontName); let font = CGFontCreateWithFontName(uiFont.fontName);
const variationAxes: NSArray<NSDictionary<'kCGFontVariationAxisDefaultValue' | 'kCGFontVariationAxisMaxValue' | 'kCGFontVariationAxisMinValue' | 'kCGFontVariationAxisName', string | number>> = CGFontCopyVariationAxes(font); const variationAxes: NSArray<NSDictionary<FontVariationAxisType, string | number>> = CGFontCopyVariationAxes(font);
// This can be null if font doesn't support axes
if (variationAxes?.count) {
const variationSettings = NSMutableDictionary.new();
const variationAxesCount = variationAxes.count;
const variationAxesNames: string[] = []; const variationAxesNames: string[] = [];
for (const axis of variationAxes) {
variationAxesNames.push(String(axis.objectForKey('kCGFontVariationAxisName'))); for (let i = 0, length = variationAxes.count; i < length; i++) {
variationAxesNames.push(String(variationAxes[i].objectForKey('kCGFontVariationAxisName')));
} }
const variationSettings = {};
for (const variationSetting of fontDescriptor.fontVariationSettings) { for (const variationSetting of fontDescriptor.fontVariationSettings) {
const axisName = fuzzySearch(variationSetting.axis, variationAxesNames); const axisName = fuzzySearch(variationSetting.axis, variationAxesNames);
if (axisName?.length) { if (axisName?.length) {
variationSettings[axisName[0]] = variationSetting.value; variationSettings.setValueForKey(variationSetting.value, axisName[0]);
} }
} }
font = CGFontCreateCopyWithVariations(font, variationSettings as any);
font = CGFontCreateCopyWithVariations(font, variationSettings);
uiFont = CTFontCreateWithGraphicsFont(font, fontDescriptor.fontSize, null, null); uiFont = CTFontCreateWithGraphicsFont(font, fontDescriptor.fontSize, null, null);
} }
}
uiFontCache.set(cacheKey, uiFont); uiFontCache.set(cacheKey, uiFont);
if (Trace.isEnabled()) { if (Trace.isEnabled()) {

View File

@ -3,7 +3,7 @@ import { CoreTypes } from '../../core-types';
import { Color } from '../../color'; import { Color } from '../../color';
import { CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty } from '../core/properties'; import { CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty } from '../core/properties';
import { Style } from './style'; import { Style } from './style';
import { Font, FontStyleType, FontWeightType } from './font'; import { Font, FontStyleType, FontWeightType, FontVariationSettingsType } from './font';
import { Background } from './background'; import { Background } from './background';
export namespace Length { export namespace Length {
@ -101,6 +101,7 @@ export const fontWeightProperty: InheritedCssProperty<Style, FontWeightType>;
export const backgroundInternalProperty: CssProperty<Style, Background>; export const backgroundInternalProperty: CssProperty<Style, Background>;
export const fontInternalProperty: InheritedCssProperty<Style, Font>; export const fontInternalProperty: InheritedCssProperty<Style, Font>;
export const fontScaleInternalProperty: InheritedCssProperty<Style, number>; export const fontScaleInternalProperty: InheritedCssProperty<Style, number>;
export const fontVariationSettingsProperty: InheritedCssProperty<Style, FontVariationSettingsType[]>;
export const androidElevationProperty: CssProperty<Style, number>; export const androidElevationProperty: CssProperty<Style, number>;
export const androidDynamicElevationOffsetProperty: CssProperty<Style, number>; export const androidDynamicElevationOffsetProperty: CssProperty<Style, number>;

View File

@ -1424,7 +1424,7 @@ const fontProperty = new ShorthandProperty<Style, string>({
}); });
fontProperty.register(Style); fontProperty.register(Style);
export const fontVariationSettingsProperty = new InheritedCssProperty<Style, Array<FontVariationSettingsType> | null>({ export const fontVariationSettingsProperty = new InheritedCssProperty<Style, FontVariationSettingsType[]>({
name: 'fontVariationSettings', name: 'fontVariationSettings',
cssName: 'font-variation-settings', cssName: 'font-variation-settings',
affectsLayout: global.isIOS, affectsLayout: global.isIOS,

View File

@ -1,6 +1,6 @@
import { Style as StyleDefinition } from '.'; import { Style as StyleDefinition } from '.';
import { Color } from '../../../color'; import { Color } from '../../../color';
import { Font, FontStyleType, FontWeightType } from '../font'; import { Font, FontStyleType, FontWeightType, FontVariationSettingsType } from '../font';
import { Background } from '../background'; import { Background } from '../background';
import { ViewBase } from '../../core/view-base'; import { ViewBase } from '../../core/view-base';
import { LinearGradient } from '../../styling/linear-gradient'; import { LinearGradient } from '../../styling/linear-gradient';
@ -156,6 +156,7 @@ export class Style extends Observable implements StyleDefinition {
public fontFamily: string; public fontFamily: string;
public fontStyle: FontStyleType; public fontStyle: FontStyleType;
public fontWeight: FontWeightType; public fontWeight: FontWeightType;
public fontVariationSettings: FontVariationSettingsType[];
public font: string; public font: string;
public maxLines: CoreTypes.MaxLinesType; public maxLines: CoreTypes.MaxLinesType;

View File

@ -6,7 +6,7 @@ import { Span } from './span';
import { ObservableArray } from '../../data/observable-array'; import { ObservableArray } from '../../data/observable-array';
import { ViewBase } from '../core/view-base'; import { ViewBase } from '../core/view-base';
import { Color } from '../../color'; import { Color } from '../../color';
import { FontStyleType, FontWeightType } from '../styling/font'; import { FontStyleType, FontVariationSettingsType, FontWeightType } from '../styling/font';
import { CoreTypes } from '../../core-types'; import { CoreTypes } from '../../core-types';
/** /**
@ -43,6 +43,11 @@ export class FormattedString extends ViewBase {
*/ */
public fontWeight: FontWeightType; public fontWeight: FontWeightType;
/**
* Gets or sets the font variation settings which will be used for all spans that doesn't have a specific value.
*/
public fontVariationSettings: FontVariationSettingsType[];
/** /**
* Gets or sets text decorations which will be used for all spans that doesn't have a specific value. * Gets or sets text decorations which will be used for all spans that doesn't have a specific value.
*/ */

View File

@ -5,7 +5,7 @@ import { ObservableArray, ChangedData } from '../../data/observable-array';
import { AddArrayFromBuilder, AddChildFromBuilder } from '../core/view'; import { AddArrayFromBuilder, AddChildFromBuilder } from '../core/view';
import { ViewBase } from '../core/view-base'; import { ViewBase } from '../core/view-base';
import { Color } from '../../color'; import { Color } from '../../color';
import { FontStyleType, FontWeightType } from '../styling/font'; import { FontStyleType, FontVariationSettingsType, FontWeightType } from '../styling/font';
import { CoreTypes } from '../../core-types'; import { CoreTypes } from '../../core-types';
export class FormattedString extends ViewBase implements FormattedStringDefinition, AddArrayFromBuilder, AddChildFromBuilder { export class FormattedString extends ViewBase implements FormattedStringDefinition, AddArrayFromBuilder, AddChildFromBuilder {
@ -45,6 +45,13 @@ export class FormattedString extends ViewBase implements FormattedStringDefiniti
this.style.fontWeight = value; this.style.fontWeight = value;
} }
get fontVariationSettings(): FontVariationSettingsType[] {
return this.style.fontVariationSettings;
}
set fontVariationSettings(value: FontVariationSettingsType[]) {
this.style.fontVariationSettings = value;
}
get textDecoration(): CoreTypes.TextDecorationType { get textDecoration(): CoreTypes.TextDecorationType {
return this.style.textDecoration; return this.style.textDecoration;
} }
@ -153,6 +160,7 @@ export class FormattedString extends ViewBase implements FormattedStringDefiniti
style.on('fontSizeChange', this.onPropertyChange, this); style.on('fontSizeChange', this.onPropertyChange, this);
style.on('fontStyleChange', this.onPropertyChange, this); style.on('fontStyleChange', this.onPropertyChange, this);
style.on('fontWeightChange', this.onPropertyChange, this); style.on('fontWeightChange', this.onPropertyChange, this);
style.on('fontVariationSettingsChange', this.onPropertyChange, this);
style.on('textDecorationChange', this.onPropertyChange, this); style.on('textDecorationChange', this.onPropertyChange, this);
style.on('colorChange', this.onPropertyChange, this); style.on('colorChange', this.onPropertyChange, this);
style.on('backgroundColorChange', this.onPropertyChange, this); style.on('backgroundColorChange', this.onPropertyChange, this);
@ -171,6 +179,7 @@ export class FormattedString extends ViewBase implements FormattedStringDefiniti
style.off('fontSizeChange', this.onPropertyChange, this); style.off('fontSizeChange', this.onPropertyChange, this);
style.off('fontStyleChange', this.onPropertyChange, this); style.off('fontStyleChange', this.onPropertyChange, this);
style.off('fontWeightChange', this.onPropertyChange, this); style.off('fontWeightChange', this.onPropertyChange, this);
style.off('fontVariationSettingsChange', this.onPropertyChange, this);
style.off('textDecorationChange', this.onPropertyChange, this); style.off('textDecorationChange', this.onPropertyChange, this);
style.off('colorChange', this.onPropertyChange, this); style.off('colorChange', this.onPropertyChange, this);
style.off('backgroundColorChange', this.onPropertyChange, this); style.off('backgroundColorChange', this.onPropertyChange, this);

View File

@ -607,7 +607,7 @@ function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span,
const fontFamily = span.fontFamily; const fontFamily = span.fontFamily;
if (fontFamily) { if (fontFamily) {
const font = new Font(fontFamily, 0, italic ? 'italic' : 'normal', bold ? 'bold' : 'normal'); const font = new Font(fontFamily, 0, italic ? 'italic' : 'normal', bold ? 'bold' : 'normal', spanStyle.fontScaleInternal, spanStyle.fontVariationSettings);
const typeface = font.getAndroidTypeface() || android.graphics.Typeface.create(fontFamily, 0); const typeface = font.getAndroidTypeface() || android.graphics.Typeface.create(fontFamily, 0);
const typefaceSpan: android.text.style.TypefaceSpan = new org.nativescript.widgets.CustomTypefaceSpan(fontFamily, typeface); const typefaceSpan: android.text.style.TypefaceSpan = new org.nativescript.widgets.CustomTypefaceSpan(fontFamily, typeface);
ssb.setSpan(typefaceSpan, start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.setSpan(typefaceSpan, start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

View File

@ -358,7 +358,7 @@ export class TextBase extends TextBaseCommon {
createMutableStringDetails(span: Span, text: string, index?: number): any { createMutableStringDetails(span: Span, text: string, index?: number): any {
const fontScale = adjustMinMaxFontScale(span.style.fontScaleInternal, span); const fontScale = adjustMinMaxFontScale(span.style.fontScaleInternal, span);
const font = new Font(span.style.fontFamily, span.style.fontSize, span.style.fontStyle, span.style.fontWeight, fontScale); const font = new Font(span.style.fontFamily, span.style.fontSize, span.style.fontStyle, span.style.fontWeight, fontScale, span.style.fontVariationSettings);
const iosFont = font.getUIFont(this.nativeTextViewProtected.font); const iosFont = font.getUIFont(this.nativeTextViewProtected.font);
const backgroundColor = <Color>(span.style.backgroundColor || (<FormattedString>span.parent).backgroundColor || (<TextBase>span.parent.parent).backgroundColor); const backgroundColor = <Color>(span.style.backgroundColor || (<FormattedString>span.parent).backgroundColor || (<TextBase>span.parent.parent).backgroundColor);

View File

@ -1,6 +1,6 @@
import { Color } from '../../color'; import { Color } from '../../color';
import { ViewBase } from '../core/view-base'; import { ViewBase } from '../core/view-base';
import { FontStyleType, FontWeightType } from '../styling/font'; import { FontStyleType, FontVariationSettingsType, FontWeightType } from '../styling/font';
import { CoreTypes } from '../../core-types'; import { CoreTypes } from '../../core-types';
/** /**
@ -27,6 +27,11 @@ export class Span extends ViewBase {
*/ */
public fontWeight: FontWeightType; public fontWeight: FontWeightType;
/**
* Gets or sets the font variation settings of the span.
*/
public fontVariationSettings: FontVariationSettingsType[];
/** /**
* Gets or sets text decorations for the span. * Gets or sets text decorations for the span.
*/ */

View File

@ -1,7 +1,7 @@
import { Color } from '../../color'; import { Color } from '../../color';
import { Span as SpanDefinition } from './span'; import { Span as SpanDefinition } from './span';
import { ViewBase } from '../core/view-base'; import { ViewBase } from '../core/view-base';
import { FontStyleType, FontWeightType } from '../styling/font'; import { FontStyleType, FontVariationSettingsType, FontWeightType } from '../styling/font';
import { CoreTypes } from '../../core-types'; import { CoreTypes } from '../../core-types';
import { EventData } from '../../data/observable'; import { EventData } from '../../data/observable';
import { isNullOrUndefined, isString } from '../../utils/types'; import { isNullOrUndefined, isString } from '../../utils/types';
@ -41,6 +41,13 @@ export class Span extends ViewBase implements SpanDefinition {
this.style.fontWeight = value; this.style.fontWeight = value;
} }
get fontVariationSettings(): FontVariationSettingsType[] {
return this.style.fontVariationSettings;
}
set fontVariationSettings(value: FontVariationSettingsType[]) {
this.style.fontVariationSettings = value;
}
get textDecoration(): CoreTypes.TextDecorationType { get textDecoration(): CoreTypes.TextDecorationType {
return this.style.textDecoration; return this.style.textDecoration;
} }