fix(android): Span should accept all font weight types

This commit is contained in:
Dimitris - Rafail Katsampas
2024-11-30 00:34:37 +02:00
committed by Nathan Walker
parent e545f5869c
commit a21d4f94ac
6 changed files with 94 additions and 45 deletions

View File

@ -3,6 +3,8 @@ import { ParsedFont, FontStyleType, FontWeightType, FontVariationSettingsType }
import { makeValidator, makeParser } from '../core/properties';
import { Trace } from '../../trace';
export const FONTS_BASE_PATH = '/fonts/';
export abstract class Font implements FontDefinition {
public static default = undefined;
public readonly fontStyle: FontStyleType;
@ -14,7 +16,7 @@ export abstract class Font implements FontDefinition {
}
get isBold(): boolean {
return this.fontWeight === FontWeight.SEMI_BOLD || this.fontWeight === FontWeight.BOLD || this.fontWeight === '700' || this.fontWeight === FontWeight.EXTRA_BOLD || this.fontWeight === FontWeight.BLACK;
return isFontWeightBold(this.fontWeight);
}
protected constructor(
@ -137,6 +139,10 @@ export function parseFontFamily(value: string): Array<string> {
.filter((v) => !!v);
}
export function isFontWeightBold(fontWeight: FontWeightType): boolean {
return fontWeight === FontWeight.SEMI_BOLD || fontWeight === FontWeight.BOLD || fontWeight === '700' || fontWeight === FontWeight.EXTRA_BOLD || fontWeight === FontWeight.BLACK;
}
export namespace genericFontFamilies {
export const serif = 'serif';
export const sansSerif = 'sans-serif';
@ -144,8 +150,7 @@ export namespace genericFontFamilies {
export const system = 'system';
}
const styles = new Set();
[FontStyle.NORMAL, FontStyle.ITALIC].forEach((val, i, a) => styles.add(val));
const styles = new Set<string>([FontStyle.NORMAL, FontStyle.ITALIC]);
// http://www.w3schools.com/cssref/pr_font_weight.asp
//- normal(same as 400)
@ -159,8 +164,7 @@ const styles = new Set();
//- 700(Bold) (API16 -bold)
//- 800(Extra Bold / Ultra Bold) (API16 -bold)
//- 900(Black / Heavy) (API21 -black)
const weights = new Set();
[FontWeight.THIN, FontWeight.EXTRA_LIGHT, FontWeight.LIGHT, FontWeight.NORMAL, '400', FontWeight.MEDIUM, FontWeight.SEMI_BOLD, FontWeight.BOLD, '700', FontWeight.EXTRA_BOLD, FontWeight.BLACK].forEach((val, i, a) => weights.add(val));
const weights = new Set<string>([FontWeight.THIN, FontWeight.EXTRA_LIGHT, FontWeight.LIGHT, FontWeight.NORMAL, '400', FontWeight.MEDIUM, FontWeight.SEMI_BOLD, FontWeight.BOLD, '700', FontWeight.EXTRA_BOLD, FontWeight.BLACK]);
export function parseFont(fontValue: string): ParsedFont {
const result: ParsedFont = {

View File

@ -1,4 +1,4 @@
import { Font as FontBase, parseFontFamily, genericFontFamilies, FontWeight, FontVariationSettings } from './font-common';
import { Font as FontBase, parseFontFamily, genericFontFamilies, FontWeight, FontVariationSettings, FONTS_BASE_PATH } from './font-common';
import { FontStyleType, FontWeightType, FontVariationSettingsType } from './font-interfaces';
import { Trace } from '../../trace';
import { SDK_VERSION } from '../../utils/constants';
@ -7,7 +7,6 @@ import { ad } from '../../utils';
export * from './font-common';
const FONTS_BASE_PATH = '/fonts/';
const typefaceCache = new Map<string, android.graphics.Typeface>();
let appAssets: android.content.res.AssetManager;
@ -66,24 +65,28 @@ function loadFontFromFile(fontFamily: string, font: Font): android.graphics.Type
return null;
}
let result = typefaceCache.get(cacheKey);
// Check for undefined explicitly as null mean we tried to load the font, but failed.
if (result === undefined) {
result = null;
let result: android.graphics.Typeface;
if (typefaceCache.has(cacheKey)) {
result = typefaceCache.get(cacheKey);
} else {
const basePath = fs.path.join(fs.knownFolders.currentApp().path, FONTS_BASE_PATH, fontFamily);
let fontAssetPath: string;
const basePath = fs.path.join(fs.knownFolders.currentApp().path, 'fonts', fontFamily);
if (fs.File.exists(basePath + '.ttf')) {
fontAssetPath = FONTS_BASE_PATH + fontFamily + '.ttf';
fontAssetPath = basePath + '.ttf';
} else if (fs.File.exists(basePath + '.otf')) {
fontAssetPath = FONTS_BASE_PATH + fontFamily + '.otf';
fontAssetPath = basePath + '.otf';
} else if (Trace.isEnabled()) {
fontAssetPath = null;
Trace.write('Could not find font file for ' + fontFamily, Trace.categories.Error, Trace.messageType.error);
}
result = null; // Default
if (fontAssetPath) {
try {
fontAssetPath = fs.path.join(fs.knownFolders.currentApp().path, fontAssetPath);
if (SDK_VERSION >= 26) {
const builder = new android.graphics.Typeface.Builder(fontAssetPath);
if (builder) {
@ -104,6 +107,8 @@ function loadFontFromFile(fontFamily: string, font: Font): android.graphics.Type
}
}
}
// The value might be null if there has already been an attempt to load the font but failed
typefaceCache.set(cacheKey, result);
}
@ -111,28 +116,39 @@ function loadFontFromFile(fontFamily: string, font: Font): android.graphics.Type
}
function createTypeface(font: Font): android.graphics.Typeface {
let fontStyle = 0;
if (font.isBold) {
fontStyle |= android.graphics.Typeface.BOLD;
}
if (font.isItalic) {
fontStyle |= android.graphics.Typeface.ITALIC;
const fontFamilies = parseFontFamily(font.fontFamily);
const fontWeight = font.fontWeight;
const supportsFontWeight = SDK_VERSION > 27;
let result: android.graphics.Typeface;
let fontStyle: number = 0;
// https://stackoverflow.com/questions/19691530/valid-values-for-androidfontfamily-and-what-they-map-to
let fontSuffix: string;
if (supportsFontWeight) {
fontSuffix = '';
} else {
if (font.isBold) {
fontStyle |= android.graphics.Typeface.BOLD;
}
if (font.isItalic) {
fontStyle |= android.graphics.Typeface.ITALIC;
}
fontSuffix = getFontWeightSuffix(fontWeight);
}
//http://stackoverflow.com/questions/19691530/valid-values-for-androidfontfamily-and-what-they-map-to
const fontFamilies = parseFontFamily(font.fontFamily);
let result: android.graphics.Typeface = null;
for (const fontFamily of fontFamilies) {
switch (fontFamily.toLowerCase()) {
case genericFontFamilies.serif:
result = android.graphics.Typeface.create('serif' + getFontWeightSuffix(font.fontWeight), fontStyle);
result = android.graphics.Typeface.create('serif' + fontSuffix, fontStyle);
break;
case genericFontFamilies.sansSerif:
case genericFontFamilies.system:
result = android.graphics.Typeface.create('sans-serif' + getFontWeightSuffix(font.fontWeight), fontStyle);
result = android.graphics.Typeface.create('sans-serif' + fontSuffix, fontStyle);
break;
case genericFontFamilies.monospace:
result = android.graphics.Typeface.create('monospace' + getFontWeightSuffix(font.fontWeight), fontStyle);
result = android.graphics.Typeface.create('monospace' + fontSuffix, fontStyle);
break;
default: {
result = loadFontFromFile(fontFamily, font);
@ -143,14 +159,19 @@ function createTypeface(font: Font): android.graphics.Typeface {
}
}
// Found the font!
if (result) {
// Found the font!
break;
}
}
if (!result) {
result = android.graphics.Typeface.create('sans-serif' + getFontWeightSuffix(font.fontWeight), fontStyle);
result = android.graphics.Typeface.create('sans-serif' + fontSuffix, fontStyle);
}
// Newer versions can accept a specific font weight number
if (supportsFontWeight) {
result = android.graphics.Typeface.create(result, getFontWeightNumber(fontWeight), font.isItalic);
}
return result;
@ -184,3 +205,27 @@ function getFontWeightSuffix(fontWeight: FontWeightType): string {
throw new Error(`Invalid font weight: "${fontWeight}"`);
}
}
function getFontWeightNumber(fontWeight: FontWeightType): number {
let value: number;
if (typeof fontWeight === 'number') {
value = fontWeight;
} else {
switch (fontWeight) {
case FontWeight.NORMAL:
case undefined:
case null:
value = 400;
break;
case FontWeight.BOLD:
value = 700;
break;
default:
value = parseInt(fontWeight);
break;
}
}
return value;
}

View File

@ -1,6 +1,8 @@
import { Font as FontBase } from './font-common';
export type { FontStyleType, FontWeightType, ParsedFont, FontVariationSettingsType } from './font-interfaces';
export const FONTS_BASE_PATH = '/fonts/';
export declare class Font extends FontBase {
public static default: Font;
@ -56,6 +58,7 @@ export namespace FontVariationSettings {
}
export function parseFont(fontValue: string): ParsedFont;
export function isFontWeightBold(fontWeight: FontWeightType): boolean;
export namespace ios {
export function registerFont(fontFile: string);

View File

@ -1,4 +1,4 @@
import { Font as FontBase, parseFontFamily, FontWeight, FontVariationSettings, fuzzySearch } from './font-common';
import { Font as FontBase, parseFontFamily, FontWeight, FontVariationSettings, fuzzySearch, FONTS_BASE_PATH } from './font-common';
import { FontStyleType, FontWeightType, FontVariationSettingsType } from './font-interfaces';
import { Trace } from '../../trace';
import * as fs from '../../file-system';
@ -115,10 +115,9 @@ export class Font extends FontBase {
}
function getNativeFontWeight(fontWeight: FontWeightType): number {
if (typeof fontWeight === 'number') {
fontWeight = (fontWeight + '') as any;
}
switch (fontWeight) {
const value = typeof fontWeight === 'number' ? fontWeight + '' : fontWeight;
switch (value) {
case FontWeight.THIN:
return UIFontWeightUltraLight;
case FontWeight.EXTRA_LIGHT:
@ -148,7 +147,7 @@ function getNativeFontWeight(fontWeight: FontWeightType): number {
export namespace ios {
export function registerFont(fontFile: string) {
let filePath = fs.path.join(fs.knownFolders.currentApp().path, 'fonts', fontFile);
let filePath = fs.path.join(fs.knownFolders.currentApp().path, FONTS_BASE_PATH, fontFile);
if (!fs.File.exists(filePath)) {
filePath = fs.path.join(fs.knownFolders.currentApp().path, fontFile);
}
@ -179,7 +178,8 @@ function registerFontsInFolder(fontsFolderPath) {
if (fs.Folder.exists(fs.path.join(fontsFolderPath, fileEntity.name))) {
return true;
}
if (fileEntity instanceof fs.File && ((<fs.File>fileEntity).extension === '.ttf' || (<fs.File>fileEntity).extension === '.otf')) {
if (fileEntity instanceof fs.File && (fileEntity.extension === '.ttf' || fileEntity.extension === '.otf')) {
ios.registerFont(fileEntity.name);
}
@ -189,7 +189,7 @@ function registerFontsInFolder(fontsFolderPath) {
function registerCustomFonts() {
const appDir = fs.knownFolders.currentApp().path;
const fontsDir = fs.path.join(appDir, 'fonts');
const fontsDir = fs.path.join(appDir, FONTS_BASE_PATH);
if (fs.Folder.exists(fontsDir)) {
registerFontsInFolder(fontsDir);
}

View File

@ -3,9 +3,9 @@ import { getClosestPropertyValue, maxLinesProperty, textOverflowProperty } from
import { ShadowCSSValues } from '../styling/css-shadow';
// Requires
import { Font } from '../styling/font';
import { Font, isFontWeightBold } from '../styling/font';
import { backgroundColorProperty } from '../styling/style-properties';
import { TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textProperty, textTransformProperty, textShadowProperty, textStrokeProperty, letterSpacingProperty, whiteSpaceProperty, lineHeightProperty, isBold, resetSymbol } from './text-base-common';
import { TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textProperty, textTransformProperty, textShadowProperty, textStrokeProperty, letterSpacingProperty, whiteSpaceProperty, lineHeightProperty, resetSymbol } from './text-base-common';
import { Color } from '../../color';
import { colorProperty, fontSizeProperty, fontInternalProperty, paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length } from '../styling/style-properties';
import { StrokeCSSValues } from '../styling/css-stroke';
@ -593,10 +593,11 @@ function createSpannableStringBuilder(formattedString: FormattedString, defaultF
function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span, start: number, end: number, defaultFontSize: number): void {
const spanStyle = span.style;
const bold = isBold(spanStyle.fontWeight);
const bold = isFontWeightBold(spanStyle.fontWeight);
const italic = spanStyle.fontStyle === 'italic';
const align = spanStyle.verticalAlignment;
// We set font style using StyleSpan in case the font doesn't support font styles
if (bold && italic) {
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD_ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (bold) {
@ -607,7 +608,7 @@ function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span,
const fontFamily = span.fontFamily;
if (fontFamily) {
const font = new Font(fontFamily, 0, italic ? 'italic' : 'normal', bold ? 'bold' : 'normal', spanStyle.fontScaleInternal, spanStyle.fontVariationSettings);
const font = new Font(fontFamily, 0, spanStyle.fontStyle, spanStyle.fontWeight, spanStyle.fontScaleInternal, spanStyle.fontVariationSettings);
const typeface = font.getAndroidTypeface() || android.graphics.Typeface.create(fontFamily, 0);
const typefaceSpan: android.text.style.TypefaceSpan = new org.nativescript.widgets.CustomTypefaceSpan(fontFamily, typeface);
ssb.setSpan(typefaceSpan, start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

View File

@ -224,10 +224,6 @@ export abstract class TextBaseCommon extends View implements TextBaseDefinition
TextBaseCommon.prototype._isSingleLine = false;
export function isBold(fontWeight: FontWeightType): boolean {
return fontWeight === 'bold' || fontWeight === '700' || fontWeight === '800' || fontWeight === '900';
}
export const textProperty = new Property<TextBaseCommon, string>({
name: 'text',
defaultValue: '',