Fonts refactored (#4436)

* Fonts refactored

* Fix: never return null font
This commit is contained in:
Alexander Vakrilov
2017-06-28 14:49:02 +03:00
committed by GitHub
parent f399f6c62b
commit fca8b16ca6
4 changed files with 116 additions and 227 deletions

View File

@@ -6,7 +6,8 @@ import { Label } from "tns-core-modules/ui/label";
import { FontStyle, FontWeight } from "tns-core-modules/ui/enums"; import { FontStyle, FontWeight } from "tns-core-modules/ui/enums";
import * as typeUtils from "tns-core-modules/utils/types"; import * as typeUtils from "tns-core-modules/utils/types";
import { Color } from "tns-core-modules/color"; import { Color } from "tns-core-modules/color";
import * as font from "tns-core-modules/ui/styling/font"; import * as utils from "tns-core-modules/utils/utils";
import { isIOS } from "tns-core-modules/platform";
const genericFontFamilies = [ const genericFontFamilies = [
"system", "system",
@@ -46,18 +47,20 @@ let compareIgnoreCase = function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase()); return a.toLowerCase().localeCompare(b.toLowerCase());
}; };
if (font.ios) { if (isIOS) {
// for (let f = 0; f < embeddedFontNames.length; f++) { const nsFontFamilies = utils.ios.getter(UIFont, UIFont.familyNames);
// font.ios.registerFont(`fonts/${embeddedFontNames[f]}.ttf`); for (let i = 0; i < nsFontFamilies.count; i++) {
// } const family = nsFontFamilies.objectAtIndex(i);
fontFamilies.push(family)
let font_internal = <any>font; const nsFonts = UIFont.fontNamesForFamilyName(family);
font_internal.ensureSystemFontSets(); for (let j = 0; j < nsFonts.count; j++) {
const font = nsFonts.objectAtIndex(j);
fontNames.push(font)
}
}
(<Set<string>>font_internal.systemFontFamilies).forEach(f => fontFamilies.push(f));
fontFamilies = fontFamilies.sort(compareIgnoreCase); fontFamilies = fontFamilies.sort(compareIgnoreCase);
(<Set<string>>font_internal.systemFonts).forEach(f => fontNames.push(f));
fontNames = fontNames.sort(compareIgnoreCase); fontNames = fontNames.sort(compareIgnoreCase);
} }
@@ -82,8 +85,7 @@ function generateLabels(layout: StackLayout) {
} }
} }
if (fontFamilies.length > 0) if (fontFamilies.length > 0) {
{
layout.addChild(prepareTitle("Font Families", 24)); layout.addChild(prepareTitle("Font Families", 24));
} }
for (let f = 0; f < fontFamilies.length; f++) { for (let f = 0; f < fontFamilies.length; f++) {
@@ -134,12 +136,12 @@ function prepareLabel(fontFamily: string, fontStyle: string, fontWeight: string)
let fontStyleCss = fontStyle !== FontStyle.normal ? `font-style: ${fontStyle}; ` : ""; let fontStyleCss = fontStyle !== FontStyle.normal ? `font-style: ${fontStyle}; ` : "";
let fontWeightCss = fontWeight !== FontWeight.normal ? `font-weight: ${fontWeight}; ` : ""; let fontWeightCss = fontWeight !== FontWeight.normal ? `font-weight: ${fontWeight}; ` : "";
let css = `${fontFamilyCss}${fontStyleCss}${fontWeightCss}`; let css = `${fontFamilyCss}${fontStyleCss}${fontWeightCss}`;
label.text = `${typeUtils.getClass(label) } {${css}};`; label.text = `${typeUtils.getClass(label)} {${css}};`;
label.textWrap = true; label.textWrap = true;
label.style.textAlignment = "left"; label.style.textAlignment = "left";
label.borderWidth = 1; label.borderWidth = 1;
label.borderColor = black; label.borderColor = black;
label.style.padding = "2"; label.style.padding = "2";
label.setInlineStyle(css); label.setInlineStyle(css);
label.on("loaded", args => { label.on("loaded", args => {
let sender = <Label>args.object; let sender = <Label>args.object;

View File

@@ -9,8 +9,11 @@ export abstract class FontBase implements FontDefinition {
} }
get isBold(): boolean { get isBold(): boolean {
return this.fontWeight === FontWeight.BOLD return this.fontWeight === FontWeight.SEMI_BOLD ||
|| this.fontWeight === "700"; this.fontWeight === FontWeight.BOLD ||
this.fontWeight === "700" ||
this.fontWeight === FontWeight.EXTRA_BOLD ||
this.fontWeight === FontWeight.BLACK;
} }
protected constructor( protected constructor(

View File

@@ -36,15 +36,7 @@ export class Font extends FontBase {
public getAndroidTypeface(): android.graphics.Typeface { public getAndroidTypeface(): android.graphics.Typeface {
if (!this._typeface) { if (!this._typeface) {
let fontStyle = 0; this._typeface = createTypeface(this);
if (this.isBold) {
fontStyle |= android.graphics.Typeface.BOLD;
}
if (this.isItalic) {
fontStyle |= android.graphics.Typeface.ITALIC;
}
this._typeface = createTypeface(this, fontStyle);
} }
return this._typeface; return this._typeface;
} }
@@ -95,11 +87,19 @@ function loadFontFromFile(fontFamily: string): android.graphics.Typeface {
return result; return result;
} }
function createTypeface(font: Font, fontStyle: number): android.graphics.Typeface { 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;
}
//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 fonts = parseFontFamily(font.fontFamily);
let result = null; let result = null;
for (let i = 0; i < fonts.length && !result; i++) { for (let i = 0; i < fonts.length; i++) {
switch (fonts[i].toLowerCase()) { switch (fonts[i].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);
@@ -116,17 +116,22 @@ function createTypeface(font: Font, fontStyle: number): android.graphics.Typefac
default: default:
result = loadFontFromFile(fonts[i]); result = loadFontFromFile(fonts[i]);
if (fontStyle) { if (result && fontStyle) {
result = android.graphics.Typeface.create(result, fontStyle); result = android.graphics.Typeface.create(result, fontStyle);
} }
break; break;
} }
if (result) {
// Found the font!
break;
}
} }
if (fontStyle && !result) { if (!result) {
result = android.graphics.Typeface.create(result, fontStyle); result = android.graphics.Typeface.create("sans-serif" + getFontWeightSuffix(font.fontWeight), fontStyle);
} }
return result; return result;
} }

View File

@@ -1,10 +1,16 @@
import { FontBase, parseFontFamily, genericFontFamilies, FontStyle, FontWeight } from "./font-common"; import { FontBase, parseFontFamily, genericFontFamilies, FontStyle, FontWeight } from "./font-common";
import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories, messageType as traceMessageType } from "../../trace"; import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories, messageType as traceMessageType } from "../../trace";
import { device } from "../../platform"
import * as fs from "../../file-system"; import * as fs from "../../file-system";
import * as utils from "../../utils/utils"; import * as utils from "../../utils/utils";
export * from "./font-common"; export * from "./font-common";
const EMULATE_OBLIQUE = true;
const OBLIQUE_TRANSFORM = CGAffineTransformMake(1, 0, 0.2, 1, 0, 0);
const DEFAULT_SERIF = "Times New Roman";
const DEFAULT_MONOSPACE = "Courier New";
const SUPPORT_FONT_WEIGHTS = parseFloat(device.osVersion) >= 10.0;
export class Font extends FontBase { export class Font extends FontBase {
public static default = new Font(undefined, undefined, FontStyle.NORMAL, FontWeight.NORMAL); public static default = new Font(undefined, undefined, FontStyle.NORMAL, FontWeight.NORMAL);
@@ -42,32 +48,6 @@ export class Font extends FontBase {
} }
} }
export const systemFontFamilies = new Set<string>();
export const systemFonts = new Set<string>();
let areSystemFontSetsValid: boolean = false;
export function ensureSystemFontSets() {
if (areSystemFontSetsValid) {
return;
}
const nsFontFamilies = utils.ios.getter(UIFont, UIFont.familyNames);
for (let i = 0; i < nsFontFamilies.count; i++) {
const family = nsFontFamilies.objectAtIndex(i);
systemFontFamilies.add(family);
const nsFonts = UIFont.fontNamesForFamilyName(family);
for (let j = 0; j < nsFonts.count; j++) {
const font = nsFonts.objectAtIndex(j);
systemFonts.add(font);
}
}
areSystemFontSetsValid = true;
}
const DEFAULT_SERIF = "Times New Roman";
const DEFAULT_MONOSPACE = "Courier New";
function getFontFamilyRespectingGenericFonts(fontFamily: string): string { function getFontFamilyRespectingGenericFonts(fontFamily: string): string {
if (!fontFamily) { if (!fontFamily) {
return fontFamily; return fontFamily;
@@ -85,119 +65,18 @@ function getFontFamilyRespectingGenericFonts(fontFamily: string): string {
} }
} }
function createUIFont(font: Font, defaultFont: UIFont) { function shouldUseSystemFont(fontFamily: string) {
let size = font.fontSize || defaultFont.pointSize; return !fontFamily ||
let descriptor: UIFontDescriptor; fontFamily === genericFontFamilies.sansSerif ||
fontFamily === genericFontFamilies.system;
let symbolicTraits: number = 0;
if (font.isBold) {
symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitBold;
}
if (font.isItalic) {
symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitItalic;
}
descriptor = tryResolveWithSystemFont(font, size, symbolicTraits);
if (!descriptor) {
descriptor = tryResolveByFamily(font);
}
if (!descriptor) {
descriptor = utils.ios.getter(defaultFont, defaultFont.fontDescriptor).fontDescriptorWithSymbolicTraits(symbolicTraits);
}
return UIFont.fontWithDescriptorSize(descriptor, size);
} }
function tryResolveWithSystemFont(font: Font, size: number, symbolicTraits: number): UIFontDescriptor { function getNativeFontWeight(fontWeight: FontWeight): number {
let systemFont: UIFont;
let result: UIFontDescriptor;
switch (font.fontFamily) {
case genericFontFamilies.sansSerif:
case genericFontFamilies.system:
if ((<any>UIFont).systemFontOfSizeWeight) {// This method is available on iOS 8.2 and later.
systemFont = (<any>UIFont).systemFontOfSizeWeight(size, getiOSFontWeight(font.fontWeight));
}
else {
systemFont = UIFont.systemFontOfSize(size);
}
result = utils.ios.getter(systemFont, systemFont.fontDescriptor).fontDescriptorWithSymbolicTraits(symbolicTraits);
break;
case genericFontFamilies.monospace:
if ((<any>UIFont).monospacedDigitSystemFontOfSizeWeight) {// This method is available on iOS 9.0 and later.
systemFont = (<any>UIFont).monospacedDigitSystemFontOfSizeWeight(size, getiOSFontWeight(font.fontWeight));
result = utils.ios.getter(systemFont, systemFont.fontDescriptor).fontDescriptorWithSymbolicTraits(symbolicTraits);
}
break;
}
if (systemFont) {
result = utils.ios.getter(systemFont, systemFont.fontDescriptor).fontDescriptorWithSymbolicTraits(symbolicTraits);
return result;
}
return null;
}
function tryResolveByFamily(font: Font): UIFontDescriptor {
const fonts = parseFontFamily(font.fontFamily);
let result: UIFontDescriptor = null;
if (fonts.length === 0) {
return null;
}
ensureSystemFontSets();
for (let i = 0; i < fonts.length; i++) {
const fontFamily = getFontFamilyRespectingGenericFonts(fonts[i]);
const fontFace = getiOSFontFace(fontFamily, font.fontWeight, font.isItalic);
if (systemFonts.has(fontFamily) && !fontFace) { // This is an actual font like `HelveticaNeue-UltraLightItalic` - don't apply font face attribute
result = UIFontDescriptor.fontDescriptorWithNameSize(fontFamily, 0);
}
else if (systemFontFamilies.has(fontFamily)) { // This is a font family like `Helvetica Neue` - apply font face attribute
result = createFontDescriptor(fontFamily, fontFace);
}
if (result) {
return result;
}
}
return null;
}
function createFontDescriptor(fontFamily: string, fontFace: string): UIFontDescriptor {
let fontAttributes = NSMutableDictionary.alloc<string, any>().init();
fontAttributes.setObjectForKey(fontFamily, "NSFontFamilyAttribute");
if (fontFace) {
fontAttributes.setObjectForKey(fontFace, "NSFontFaceAttribute");
}
return UIFontDescriptor.fontDescriptorWithFontAttributes(fontAttributes);
}
// Available in iOS 8.2 and later.
declare const UIFontWeightThin: number; //0.8
declare const UIFontWeightUltraLight: number; //0.6
declare const UIFontWeightLight: number; //0.4
declare const UIFontWeightRegular: number; //0
declare const UIFontWeightMedium: number; //0.23
declare const UIFontWeightSemibold: number; //0.3
declare const UIFontWeightBold: number; //0.4
declare const UIFontWeightHeavy: number; //0.56
declare const UIFontWeightBlack: number; //0.62
function getiOSFontWeight(fontWeight: FontWeight): number {
if (!(<any>UIFont).systemFontOfSizeWeight) {
throw new Error("Font weight is available in iOS 8.2 and later.");
}
switch (fontWeight) { switch (fontWeight) {
case FontWeight.THIN: case FontWeight.THIN:
return UIFontWeightThin;
case FontWeight.EXTRA_LIGHT:
return UIFontWeightUltraLight; return UIFontWeightUltraLight;
case FontWeight.EXTRA_LIGHT:
return UIFontWeightThin;
case FontWeight.LIGHT: case FontWeight.LIGHT:
return UIFontWeightLight; return UIFontWeightLight;
case FontWeight.NORMAL: case FontWeight.NORMAL:
@@ -221,74 +100,78 @@ function getiOSFontWeight(fontWeight: FontWeight): number {
} }
} }
function combineWeightStringWithItalic(weight: string, isItalic: boolean) { function getSystemFont(size: number, nativeWeight: number, italic: boolean, symbolicTraits: number): UIFont {
if (!isItalic) { let result = UIFont.systemFontOfSizeWeight(size, nativeWeight);
return weight; if (italic) {
let descriptor = utils.ios.getter(result, result.fontDescriptor).fontDescriptorWithSymbolicTraits(symbolicTraits);
result = UIFont.fontWithDescriptorSize(descriptor, size);
} }
if (!weight) { return result;
return "Italic"; }
function createUIFont(font: Font, defaultFont: UIFont): UIFont {
let result: UIFont;
const size = font.fontSize || defaultFont.pointSize;
const nativeWeight = getNativeFontWeight(font.fontWeight);
const fontFamilies = parseFontFamily(font.fontFamily);
let symbolicTraits: number = 0;
if (font.isBold) {
symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitBold;
}
if (font.isItalic) {
symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitItalic;
} }
return weight + " Italic"; let fontDescriptorTraits = {
} [UIFontSymbolicTrait]: symbolicTraits
};
function canLoadFont(fontFamily: string, fontFace: string) { // IOS versions below 10 will not return the correct font when UIFontWeightTrait is set
const trialDescriptor = createFontDescriptor(fontFamily, fontFace); // In this case - rely on UIFontSymbolicTrait only
const trialFont = UIFont.fontWithDescriptorSize(trialDescriptor, 0); if (SUPPORT_FONT_WEIGHTS) {
return trialFont.familyName === fontFamily; fontDescriptorTraits[UIFontWeightTrait] = nativeWeight;
} }
function findCorrectWeightString(fontFamily: string, weightStringAlternatives: Array<string>, isItalic: boolean) { for (let i = 0; i < fontFamilies.length; i++) {
for (let i = 0, length = weightStringAlternatives.length; i < length; i++) { const fontFamily = getFontFamilyRespectingGenericFonts(fontFamilies[i]);
const possibleFontFace = combineWeightStringWithItalic(weightStringAlternatives[i], isItalic);
if (canLoadFont(fontFamily, possibleFontFace)) { if (shouldUseSystemFont(fontFamily)) {
return weightStringAlternatives[i]; result = getSystemFont(size, nativeWeight, font.isItalic, symbolicTraits);
break;
} else {
const fontAttributes = {
[UIFontDescriptorFamilyAttribute]: fontFamily,
[UIFontDescriptorTraitsAttribute]: fontDescriptorTraits
};
let descriptor = UIFontDescriptor.fontDescriptorWithFontAttributes(<any>fontAttributes);
result = UIFont.fontWithDescriptorSize(descriptor, size);
let actualItalic = utils.ios.getter(result, result.fontDescriptor).symbolicTraits & UIFontDescriptorSymbolicTraits.TraitItalic;
if (font.isItalic && !actualItalic && EMULATE_OBLIQUE) {
// The font we got is not actually italic so emulate that with a matrix
descriptor = descriptor.fontDescriptorWithMatrix(OBLIQUE_TRANSFORM);
result = UIFont.fontWithDescriptorSize(descriptor, size);
}
// Check if the resolved font has the correct font-family
// If not - fallback to the next font-family
if (result.familyName === fontFamily) {
break;
} else {
result = null;
}
} }
} }
return weightStringAlternatives[0];
}
function getiOSFontFace(fontFamily: string, fontWeight: FontWeight, isItalic: boolean): string { // Couldn't resolve font - fallback to the system font
// ... with a lot of fuzzy logic and artificial intelligence thanks to the lack of font naming standards. if (!result) {
let weight: string; result = getSystemFont(size, nativeWeight, font.isItalic, symbolicTraits);
switch (fontWeight) {
case FontWeight.THIN:
weight = "Thin";
break;
case FontWeight.EXTRA_LIGHT:
weight = findCorrectWeightString(fontFamily, ["Ultra Light", "UltraLight", "Extra Light", "ExtraLight", "Ultra light", "Ultralight", "Extra light", "Extralight"], isItalic);
break;
case FontWeight.LIGHT:
weight = "Light";
break;
case FontWeight.NORMAL:
case "400":
case undefined:
case null:
weight = ""; // We dont' need to write Regular
break;
case FontWeight.MEDIUM:
weight = "Medium";
break;
case FontWeight.SEMI_BOLD:
weight = findCorrectWeightString(fontFamily, ["Demi Bold", "DemiBold", "Semi Bold", "SemiBold", "Demi bold", "Demibold", "Semi bold", "Semibold"], isItalic);
break;
case FontWeight.BOLD:
case "700":
weight = "Bold";
break;
case FontWeight.EXTRA_BOLD:
weight = findCorrectWeightString(fontFamily, ["Heavy", "Extra Bold", "ExtraBold", "Extra bold", "Extrabold"], isItalic);
break;
case FontWeight.BLACK:
weight = "Black";
break;
default:
throw new Error(`Invalid font weight: "${fontWeight}"`);
} }
return combineWeightStringWithItalic(weight, isItalic); return result;
} }
export module ios { export module ios {
@@ -314,8 +197,6 @@ export module ios {
traceWrite("Error occur while registering font: " + CFErrorCopyDescription(<NSError>error.value), traceCategories.Error, traceMessageType.error); traceWrite("Error occur while registering font: " + CFErrorCopyDescription(<NSError>error.value), traceCategories.Error, traceMessageType.error);
} }
} }
areSystemFontSetsValid = false;
} }
} }
@@ -326,7 +207,6 @@ function registerFontsInFolder(fontsFolderPath) {
if (fs.Folder.exists(fs.path.join(fontsFolderPath, fileEntity.name))) { if (fs.Folder.exists(fs.path.join(fontsFolderPath, fileEntity.name))) {
return true; return true;
} }
if (fileEntity instanceof fs.File && if (fileEntity instanceof fs.File &&
((<fs.File>fileEntity).extension === ".ttf" || (<fs.File>fileEntity).extension === ".otf")) { ((<fs.File>fileEntity).extension === ".ttf" || (<fs.File>fileEntity).extension === ".otf")) {
ios.registerFont(fileEntity.name); ios.registerFont(fileEntity.name);
@@ -342,5 +222,4 @@ function registerCustomFonts() {
registerFontsInFolder(fontsDir); registerFontsInFolder(fontsDir);
} }
} }
registerCustomFonts(); registerCustomFonts();