diff --git a/apps/tests/ui/style/style-properties-tests.ts b/apps/tests/ui/style/style-properties-tests.ts index 65adc0164..7f0e81653 100644 --- a/apps/tests/ui/style/style-properties-tests.ts +++ b/apps/tests/ui/style/style-properties-tests.ts @@ -243,7 +243,7 @@ export function test_setting_margin_shorthand_property_sets_all_margins() { test_margin_shorthand_property("10 20 30 40", 10, 20, 30, 40); } -function test_margin_shorthand_property(short: string, top:number, right:number, bottom:number, left:number) { +function test_margin_shorthand_property(short: string, top: number, right: number, bottom: number, left: number) { var testView = new buttonModule.Button(); testView.style.margin = short; @@ -269,3 +269,23 @@ function test_padding_shorthand_property(short: string, top: number, right: numb TKUnit.assertEqual(testView.style.paddingLeft, left, "left"); } +export function test_setting_font_shorthand_property() { + test_font_shorthand_property("15px Arial", "Arial", 15, "normal", "normal"); + test_font_shorthand_property("bold 15px Arial", "Arial", 15, "normal", "bold"); + test_font_shorthand_property("italic 15px Arial", "Arial", 15, "italic", "normal"); + test_font_shorthand_property("bold italic 15px Arial", "Arial", 15, "italic", "bold"); + test_font_shorthand_property("italic normal bold 15px Arial, serif", "Arial, serif", 15, "italic", "bold"); + test_font_shorthand_property("small-caps normal bold 15px Arial", "Arial", 15, "normal", "bold"); + test_font_shorthand_property("normal normal normal 15px Arial", "Arial", 15, "normal", "normal"); + test_font_shorthand_property("normal normal normal 15px/30px Arial", "Arial", 15, "normal", "normal"); +} + +function test_font_shorthand_property(short: string, family: string, size: number, style: string, weight:string) { + var testView = new buttonModule.Button(); + (testView).style = "font: " + short; + + TKUnit.assertEqual(testView.style.fontFamily, family, "style.fontFamily"); + TKUnit.assertEqual(testView.style.fontStyle, style, "style.fontStyle"); + TKUnit.assertEqual(testView.style.fontWeight, weight, "style.fontWeight"); + TKUnit.assertEqual(testView.style.fontSize, size, "style.fontSize"); +} diff --git a/ui/styling/font-common.ts b/ui/styling/font-common.ts index c9f3c9158..20454269f 100644 --- a/ui/styling/font-common.ts +++ b/ui/styling/font-common.ts @@ -1,12 +1,14 @@ import enums = require("ui/enums"); import definitios = require("ui/styling/font"); +import converters = require("ui/styling/converters"); export class Font implements definitios.Font { - public static default = new Font(undefined, enums.FontStyle.normal, enums.FontWeight.normal); + public static default = undefined; private _fontFamily: string; private _fontStyle: string; private _fontWeight: string; + private _fontSize: number; get fontFamily(): string { return this._fontFamily; @@ -29,6 +31,13 @@ export class Font implements definitios.Font { throw new Error("fontWeight is read-only"); } + get fontSize(): number { + return this._fontSize; + } + set fontSize(value: number) { + throw new Error("fontSize is read-only"); + } + get isBold(): boolean { return this._fontWeight.toLowerCase() === enums.FontWeight.bold;; } @@ -43,20 +52,21 @@ export class Font implements definitios.Font { throw new Error("isItalic is read-only"); } - get ios(): UIFontDescriptor { - return undefined; - } - - get android(): android.graphics.Typeface { - return undefined; - } - - constructor(family: string, style: string, weight: string) { + constructor(family: string, size: number, style: string, weight: string) { this._fontFamily = family; + this._fontSize = size; this._fontStyle = style; this._fontWeight = weight; } + public getAndroidTypeface(): android.graphics.Typeface { + return undefined; + } + + public getUIFont(defaultFont: UIFont): UIFont { + return undefined; + } + public withFontFamily(family: string): Font { throw new Error("This should be called on the derived class"); } @@ -68,6 +78,25 @@ export class Font implements definitios.Font { public withFontWeight(weight: string): Font { throw new Error("This should be called on the derived class"); } + + public withFontSize(size: number): Font { + throw new Error("This should be called on the derived class"); + } + + public static equals(value1: Font, value2: Font): boolean { + return value1.fontFamily === value2.fontFamily && + value1.fontSize === value2.fontSize && + value1.fontStyle === value2.fontStyle && + value1.fontWeight === value2.fontWeight; + } + + public static parse(cssValue: string): Font { + var parsed = parseFont(cssValue); + var size = converters.fontSizeConverter(parsed.fontSize); + size = !!size ? size : undefined; + + return new Font(parsed.fontFamily, size, parsed.fontStyle, parsed.fontWeight); + } } export function parseFontFamily(value: string): Array { @@ -90,4 +119,58 @@ export module genericFontFamilies { export var serif = "serif"; export var sansSerif = "sans-serif"; export var monospace = "monospace"; +} + +var styles = new Set(); +["italic", "oblique"].forEach((val, i, a) => styles.add(val)); +var weights = new Set(); +["bold", "bolder", "lighter", "100", "200", "300", "400", "500", "600", "700", "800", "900"].forEach((val, i, a) => weights.add(val)); + +interface ParsedFont { + fontStyle?: string; + fontVariant?: string; + fontWeight?: string, + lineHeight?: string, + fontSize?: string, + fontFamily?: string +} + +function parseFont(fontValue: string): ParsedFont { + var result: ParsedFont = { + fontStyle: "normal", + fontVariant: "normal", + fontWeight: "normal", + } + + var parts = fontValue.split(/\s+/); + var part: string; + while (part = parts.shift()) { + if (part === "normal") { + // nothing to do here + } + else if (part === "small-caps") { + // The only supported font variant in shorthand font + result.fontVariant = part; + } + else if (styles.has(part)) { + result.fontStyle = part + } + else if (weights.has(part)) { + result.fontWeight = part; + } + else if (!result.fontSize) { + var sizes = part.split("/"); + result.fontSize = sizes[0]; + result.lineHeight = sizes.length > 1 ? sizes[1] : undefined; + } + else { + result.fontFamily = part; + if (parts.length) { + result.fontFamily += " " + parts.join(" "); + } + break; + } + } + + return result; } \ No newline at end of file diff --git a/ui/styling/font.android.ts b/ui/styling/font.android.ts index 016820ccd..e81bb4f76 100644 --- a/ui/styling/font.android.ts +++ b/ui/styling/font.android.ts @@ -9,11 +9,32 @@ var typefaceCache = new Map(); var appAssets: android.content.res.AssetManager; export class Font extends common.Font { - public static default = new Font(undefined, enums.FontStyle.normal, enums.FontWeight.normal); + public static default = new Font(undefined, undefined, enums.FontStyle.normal, enums.FontWeight.normal); - private _android: android.graphics.Typeface; - get android(): android.graphics.Typeface { - if (!this._android) { + private _typeface: android.graphics.Typeface; + + constructor(family: string, size: number, style: string, weight: string) { + super(family, size, style, weight); + } + + public withFontFamily(family: string): Font { + return new Font(family, this.fontSize, this.fontStyle, this.fontWeight); + } + + public withFontStyle(style: string): Font { + return new Font(this.fontFamily, this.fontSize, style, this.fontWeight); + } + + public withFontWeight(weight: string): Font { + return new Font(this.fontFamily, this.fontSize, this.fontStyle, weight); + } + + public withFontSize(size: number): Font { + return new Font(this.fontFamily, size, this.fontStyle, this.fontWeight); + } + + public getAndroidTypeface(): android.graphics.Typeface { + if (!this._typeface) { var style: number = 0; if (this.isBold) { @@ -25,25 +46,10 @@ export class Font extends common.Font { } var typeFace = this.getTypeFace(this.fontFamily); - this._android = android.graphics.Typeface.create(typeFace, style); + this._typeface = android.graphics.Typeface.create(typeFace, style); } - return this._android; - } - constructor(family: string, style: string, weight: string) { - super(family, style, weight); - } - - public withFontFamily(family: string): Font { - return new Font(family, this.fontStyle, this.fontWeight); - } - - public withFontStyle(style: string): Font { - return new Font(this.fontFamily, style, this.fontWeight); - } - - public withFontWeight(weight: string): Font { - return new Font(this.fontFamily, this.fontStyle, weight); + return this._typeface; } private getTypeFace(fontFamily: string): android.graphics.Typeface { @@ -101,8 +107,8 @@ export class Font extends common.Font { else { trace.write("Could not find font file for " + fontFamily, trace.categories.Error, trace.messageType.error); } - - if (fontAssetPath){ + + if (fontAssetPath) { try { result = android.graphics.Typeface.createFromAsset(appAssets, fontAssetPath); } catch (e) { diff --git a/ui/styling/font.d.ts b/ui/styling/font.d.ts index 12544cbfd..bf30098d7 100644 --- a/ui/styling/font.d.ts +++ b/ui/styling/font.d.ts @@ -5,17 +5,22 @@ public fontFamily: string; public fontStyle: string; public fontWeight: string; + public fontSize: number; public isBold: boolean; public isItalic: boolean; - public ios: UIFontDescriptor; - public android: android.graphics.Typeface; + constructor(family: string, size: number, style: string, weight: string); - constructor(family: string, style: string, weight: string); + public getAndroidTypeface(): android.graphics.Typeface; + public getUIFont(defaultFont: UIFont): UIFont; public withFontFamily(family: string): Font; public withFontStyle(style: string): Font; public withFontWeight(weight: string): Font; + public withFontSize(size: number): Font; + + public static equals(value1: Font, value2: Font): boolean; + public static parse(cssValue: string): Font; } } \ No newline at end of file diff --git a/ui/styling/font.ios.ts b/ui/styling/font.ios.ts index 99c1ddbbc..7cf9e5e30 100644 --- a/ui/styling/font.ios.ts +++ b/ui/styling/font.ios.ts @@ -24,11 +24,16 @@ function initSystemFotns() { initSystemFotns(); export class Font extends common.Font { - public static default = new Font(undefined, enums.FontStyle.normal, enums.FontWeight.normal); + public static default = new Font(undefined, undefined, enums.FontStyle.normal, enums.FontWeight.normal); - private _ios: UIFontDescriptor; - get ios(): UIFontDescriptor { - if (!this._ios) { + private _uiFont: UIFont; + + constructor(family: string, size: number, style: string, weight: string) { + super(family, size, style, weight); + } + + public getUIFont(defaultFont: UIFont): UIFont { + if (!this._uiFont) { var symbolicTraits: number = 0; if (this.isBold) { symbolicTraits |= UIFontDescriptorSymbolicTraits.UIFontDescriptorTraitBold; @@ -37,29 +42,32 @@ export class Font extends common.Font { symbolicTraits |= UIFontDescriptorSymbolicTraits.UIFontDescriptorTraitItalic; } - this._ios = resolveFontDescriptor(this.fontFamily, symbolicTraits); - - if (!this._ios) { - this._ios = UIFontDescriptor.new().fontDescriptorWithSymbolicTraits(symbolicTraits); + var descriptor = resolveFontDescriptor(this.fontFamily, symbolicTraits); + if (!descriptor) { + descriptor = UIFontDescriptor.new().fontDescriptorWithSymbolicTraits(symbolicTraits); } - } - return this._ios; - } - constructor(family: string, style: string, weight: string) { - super(family, style, weight); + var size = this.fontSize || defaultFont.pointSize; + + this._uiFont = UIFont.fontWithDescriptorSize(descriptor, size); + } + return this._uiFont; } public withFontFamily(family: string): Font { - return new Font(family, this.fontStyle, this.fontWeight); + return new Font(family, this.fontSize, this.fontStyle, this.fontWeight); } public withFontStyle(style: string): Font { - return new Font(this.fontFamily, style, this.fontWeight); + return new Font(this.fontFamily, this.fontSize, style, this.fontWeight); } public withFontWeight(weight: string): Font { - return new Font(this.fontFamily, this.fontStyle, weight); + return new Font(this.fontFamily, this.fontSize, this.fontStyle, weight); + } + + public withFontSize(size: number): Font { + return new Font(this.fontFamily, size, this.fontStyle, this.fontWeight); } } diff --git a/ui/styling/style.ts b/ui/styling/style.ts index fdabfaf09..ce20a42c8 100644 --- a/ui/styling/style.ts +++ b/ui/styling/style.ts @@ -97,6 +97,13 @@ export class Style extends observable.DependencyObservable implements styling.St this._setValue(fontWeightProperty, value, observable.ValueSource.Local); } + get font(): string { + return this._getValue(fontProperty); + } + set font(value: string) { + this._setValue(fontProperty, value, observable.ValueSource.Local); + } + get textAlignment(): string { return this._getValue(textAlignmentProperty); } @@ -310,8 +317,8 @@ export class Style extends observable.DependencyObservable implements styling.St } else { trace.write("Found handler for property: " + property.name + ", view:" + this._view, trace.categories.Style); - - if (types.isUndefined(newValue)) { + + if (types.isUndefined(newValue) || newValue === property.metadata.defaultValue) { (handler).resetProperty(property, this._view); } else { (handler).applyProperty(property, this._view, newValue); @@ -462,18 +469,23 @@ export var backgroundColorProperty = new styleProperty.Property("backgroundColor new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.None, undefined, undefined, color.Color.equals), converters.colorConverter); +export var fontProperty = new styleProperty.Property("font", "font", + new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.None, onFontChanged)); + export var fontSizeProperty = new styleProperty.Property("fontSize", "font-size", - new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.AffectsLayout | observable.PropertyMetadataSettings.Inheritable), - converters.fontSizeConverter); + new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.Inheritable, onFontSizeChanged),converters.fontSizeConverter); export var fontFamilyProperty = new styleProperty.Property("fontFamily", "font-family", new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.Inheritable, onFontFamilyChanged)); export var fontStyleProperty = new styleProperty.Property("fontStyle", "font-style", - new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.Inheritable, onFontStyleChanged, isFontStyleValid)); + new observable.PropertyMetadata(enums.FontStyle.normal, observable.PropertyMetadataSettings.Inheritable, onFontStyleChanged, isFontStyleValid)); export var fontWeightProperty = new styleProperty.Property("fontWeight", "font-weight", - new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.Inheritable, onFontWeightChanged, isFontWeightValid)); + new observable.PropertyMetadata(enums.FontWeight.normal, observable.PropertyMetadataSettings.Inheritable, onFontWeightChanged, isFontWeightValid)); + +export var fontInternalProperty = new styleProperty.Property("_fontInternal", "_fontInternal", + new observable.PropertyMetadata(font.Font.default, observable.PropertyMetadataSettings.AffectsLayout, null, null, font.Font.equals), font.Font.parse); function isFontWeightValid(value: string): boolean { return value === enums.FontWeight.normal || value === enums.FontWeight.bold; @@ -486,26 +498,49 @@ function isFontStyleValid(value: string): boolean { function onFontFamilyChanged(data: observable.PropertyChangeData) { var style =