diff --git a/apps/ui-tests-app/css/letter-spacing.xml b/apps/ui-tests-app/css/letter-spacing.xml new file mode 100644 index 000000000..d1572b6b5 --- /dev/null +++ b/apps/ui-tests-app/css/letter-spacing.xml @@ -0,0 +1,59 @@ + + + + + \ No newline at end of file diff --git a/ui/button/button.ios.ts b/ui/button/button.ios.ts index 8f7b1eb6f..788763528 100644 --- a/ui/button/button.ios.ts +++ b/ui/button/button.ios.ts @@ -170,20 +170,20 @@ export class ButtonStyler implements style.Styler { // text-decoration private static setTextDecorationProperty(view: view.View, newValue: any) { - utils.ios.setTextDecorationAndTransform(view, newValue, view.style.textTransform); + utils.ios.setTextDecorationAndTransform(view, newValue, view.style.textTransform, view.style.letterSpacing); } private static resetTextDecorationProperty(view: view.View, nativeValue: any) { - utils.ios.setTextDecorationAndTransform(view, enums.TextDecoration.none, view.style.textTransform); + utils.ios.setTextDecorationAndTransform(view, enums.TextDecoration.none, view.style.textTransform, view.style.letterSpacing); } // text-transform private static setTextTransformProperty(view: view.View, newValue: any) { - utils.ios.setTextDecorationAndTransform(view, view.style.textDecoration, newValue); + utils.ios.setTextDecorationAndTransform(view, view.style.textDecoration, newValue, view.style.letterSpacing); } private static resetTextTransformProperty(view: view.View, nativeValue: any) { - utils.ios.setTextDecorationAndTransform(view, view.style.textDecoration, enums.TextTransform.none); + utils.ios.setTextDecorationAndTransform(view, view.style.textDecoration, enums.TextTransform.none, view.style.letterSpacing); } // white-space diff --git a/ui/styling/converters.ts b/ui/styling/converters.ts index 4dbcd68d5..ff595c7a9 100644 --- a/ui/styling/converters.ts +++ b/ui/styling/converters.ts @@ -12,6 +12,12 @@ export function fontSizeConverter(value: string): number { return result; } +export function letterSpacingConverter(value: string): number { + // TODO: parse different unit types + var result: number = parseFloat(value); + return result; +} + export function textAlignConverter(value: string): string { switch (value) { case enums.TextAlignment.left: diff --git a/ui/styling/style.d.ts b/ui/styling/style.d.ts index 38081a3e6..8402f3d89 100644 --- a/ui/styling/style.d.ts +++ b/ui/styling/style.d.ts @@ -77,6 +77,7 @@ declare module "ui/styling/style" { public visibility: string; public opacity: number; public whiteSpace: string; + public letterSpacing: number; constructor(parentView: View); @@ -123,6 +124,7 @@ declare module "ui/styling/style" { export var textDecorationProperty: styleProperty.Property; export var textTransformProperty: styleProperty.Property; export var whiteSpaceProperty: styleProperty.Property; + export var letterSpacingProperty: styleProperty.Property; // Helper property holding most layout related properties available in CSS. // When layout related properties are set in CSS we chache them and send them to the native view in a single call. diff --git a/ui/styling/style.ts b/ui/styling/style.ts index f3cee7ee1..fcdbdff3f 100644 --- a/ui/styling/style.ts +++ b/ui/styling/style.ts @@ -417,6 +417,11 @@ function isOpacityValid(value: string): boolean { return !isNaN(parsedValue) && 0 <= parsedValue && parsedValue <= 1; } +function isLetterSpacingValid(value: string): boolean { + var parsedValue: number = parseFloat(value); + return !isNaN(parsedValue); +} + function isFontWeightValid(value: string): boolean { return value === enums.FontWeight.normal || value === enums.FontWeight.bold; } @@ -768,6 +773,13 @@ export class Style extends DependencyObservable implements styling.Style { set whiteSpace(value: string) { this._setValue(whiteSpaceProperty, value); } + + get letterSpacing(): number { + return this._getValue(letterSpacingProperty); + } + set letterSpacing(value: number) { + this._setValue(letterSpacingProperty, value); + } public _updateTextDecoration() { if (this._getValue(textDecorationProperty) !== enums.TextDecoration.none) { @@ -1059,6 +1071,9 @@ export var textTransformProperty = new styleProperty.Property("textTransform", " export var whiteSpaceProperty = new styleProperty.Property("whiteSpace", "white-space", new PropertyMetadata(undefined, AffectsLayout, undefined, isWhiteSpaceValid), converters.whiteSpaceConverter); + +export var letterSpacingProperty = new styleProperty.Property("letterSpacing", "letter-spacing", + new PropertyMetadata(Number.NaN, AffectsLayout, undefined, isLetterSpacingValid), converters.letterSpacingConverter); // Helper property holding most layout related properties available in CSS. // When layout related properties are set in CSS we chache them and send them to the native view in a single call. diff --git a/ui/styling/styling.d.ts b/ui/styling/styling.d.ts index c88983b80..e47395a84 100644 --- a/ui/styling/styling.d.ts +++ b/ui/styling/styling.d.ts @@ -201,6 +201,7 @@ textDecoration: string; textTransform: string; whiteSpace: string; + letterSpacing: number; //@private public _beginUpdate(); diff --git a/ui/text-base/text-base-styler.android.ts b/ui/text-base/text-base-styler.android.ts index af6c505e5..339e88e8b 100644 --- a/ui/text-base/text-base-styler.android.ts +++ b/ui/text-base/text-base-styler.android.ts @@ -108,6 +108,23 @@ export class TextBaseStyler implements style.Styler { utils.ad.setWhiteSpace(view._nativeView, enums.WhiteSpace.normal); } + // letter-spacing + private static getLetterSpacingProperty(view: view.View) : any { + return view.android.getLetterSpacing ? view.android.getLetterSpacing() : 0; + } + + private static setLetterSpacingProperty(view: view.View, newValue: any) { + if(view.android.setLetterSpacing) { + view.android.setLetterSpacing(utils.layout.toDeviceIndependentPixels(newValue)); + } + } + + private static resetLetterSpacingProperty(view: view.View, nativeValue: any) { + if(view.android.setLetterSpacing) { + view.android.setLetterSpacing(nativeValue); + } + } + public static registerHandlers() { style.registerHandler(style.colorProperty, new style.StylePropertyChangedHandler( TextBaseStyler.setColorProperty, @@ -136,6 +153,11 @@ export class TextBaseStyler implements style.Styler { TextBaseStyler.setWhiteSpaceProperty, TextBaseStyler.resetWhiteSpaceProperty), "TextBase"); + style.registerHandler(style.letterSpacingProperty, new style.StylePropertyChangedHandler( + TextBaseStyler.setLetterSpacingProperty, + TextBaseStyler.resetLetterSpacingProperty, + TextBaseStyler.getLetterSpacingProperty), "TextBase"); + // Register the same stylers for Button. // It also derives from TextView but is not under TextBase in our View hierarchy. style.registerHandler(style.colorProperty, new style.StylePropertyChangedHandler( @@ -164,5 +186,10 @@ export class TextBaseStyler implements style.Styler { style.registerHandler(style.whiteSpaceProperty, new style.StylePropertyChangedHandler( TextBaseStyler.setWhiteSpaceProperty, TextBaseStyler.resetWhiteSpaceProperty), "Button"); + + style.registerHandler(style.letterSpacingProperty, new style.StylePropertyChangedHandler( + TextBaseStyler.setLetterSpacingProperty, + TextBaseStyler.resetLetterSpacingProperty, + TextBaseStyler.getLetterSpacingProperty), "Button"); } } diff --git a/ui/text-base/text-base-styler.ios.ts b/ui/text-base/text-base-styler.ios.ts index 17fcfe320..0a8650eec 100644 --- a/ui/text-base/text-base-styler.ios.ts +++ b/ui/text-base/text-base-styler.ios.ts @@ -38,20 +38,20 @@ export class TextBaseStyler implements style.Styler { // text-decoration private static setTextDecorationProperty(view: view.View, newValue: any) { - utils.ios.setTextDecorationAndTransform(view, newValue, view.style.textTransform); + utils.ios.setTextDecorationAndTransform(view, newValue, view.style.textTransform, view.style.letterSpacing); } private static resetTextDecorationProperty(view: view.View, nativeValue: any) { - utils.ios.setTextDecorationAndTransform(view, enums.TextDecoration.none, view.style.textTransform); + utils.ios.setTextDecorationAndTransform(view, enums.TextDecoration.none, view.style.textTransform, view.style.letterSpacing); } // text-transform private static setTextTransformProperty(view: view.View, newValue: any) { - utils.ios.setTextDecorationAndTransform(view, view.style.textDecoration, newValue); + utils.ios.setTextDecorationAndTransform(view, view.style.textDecoration, newValue, view.style.letterSpacing); } private static resetTextTransformProperty(view: view.View, nativeValue: any) { - utils.ios.setTextDecorationAndTransform(view, view.style.textDecoration, enums.TextTransform.none); + utils.ios.setTextDecorationAndTransform(view, view.style.textDecoration, enums.TextTransform.none, view.style.letterSpacing); } // white-space @@ -62,6 +62,15 @@ export class TextBaseStyler implements style.Styler { private static resetWhiteSpaceProperty(view: view.View, nativeValue: any) { utils.ios.setWhiteSpace(view._nativeView, enums.WhiteSpace.normal); } + + // letter-spacing + private static setLetterSpacingProperty(view: view.View, newValue: any) { + utils.ios.setTextDecorationAndTransform(view, view.style.textDecoration, enums.TextTransform.none, newValue); + } + + private static resetLetterSpacingProperty(view: view.View, nativeValue: any) { + utils.ios.setTextDecorationAndTransform(view, view.style.textDecoration, enums.TextTransform.none, 0); + } // color private static setColorProperty(view: view.View, newValue: any) { @@ -106,5 +115,9 @@ export class TextBaseStyler implements style.Styler { style.registerHandler(style.whiteSpaceProperty, new style.StylePropertyChangedHandler( TextBaseStyler.setWhiteSpaceProperty, TextBaseStyler.resetWhiteSpaceProperty), "TextBase"); + + style.registerHandler(style.letterSpacingProperty, new style.StylePropertyChangedHandler( + TextBaseStyler.setLetterSpacingProperty, + TextBaseStyler.resetLetterSpacingProperty), "TextBase"); } } diff --git a/utils/utils.d.ts b/utils/utils.d.ts index ffab8394e..b5381759b 100644 --- a/utils/utils.d.ts +++ b/utils/utils.d.ts @@ -145,7 +145,7 @@ * Module with ios specific utilities. */ module ios { - export function setTextDecorationAndTransform(view: any, decoration: string, transform: string); + export function setTextDecorationAndTransform(view: any, decoration: string, transform: string, letterSpacing : number); export function setWhiteSpace(view, value: string, parentView?: any); export function setTextAlignment(view, value: string); diff --git a/utils/utils.ios.ts b/utils/utils.ios.ts index 479ece0b6..652bdb196 100644 --- a/utils/utils.ios.ts +++ b/utils/utils.ios.ts @@ -1,4 +1,5 @@ import dts = require("utils/utils"); +import types = require("utils/types"); import common = require("./utils-common"); import {Color} from "color"; import enums = require("ui/enums"); @@ -49,7 +50,9 @@ export module ios { } } - export function setTextDecorationAndTransform(v: any, decoration: string, transform: string) { + export function setTextDecorationAndTransform(v: any, decoration: string, transform: string, letterSpacing: number) { + let hasLetterSpacing = types.isNumber(letterSpacing) && !isNaN(letterSpacing); + if (v.formattedText) { if (v.style.textDecoration.indexOf(enums.TextDecoration.none) === -1) { @@ -69,6 +72,22 @@ export module ios { let span = v.formattedText.spans.getItem(i); span.text = getTransformedText(v, span.text, transform); } + + if (hasLetterSpacing) { + let attrText; + if(v._nativeView instanceof UIButton){ + attrText = (v._nativeView).attributedTitleForState(UIControlState.UIControlStateNormal); + } else { + attrText = v._nativeView.attributedText; + } + + attrText.addAttributeValueRange(NSKernAttributeName, letterSpacing, { location: 0, length: v._nativeView.attributedText.length }); + + if(v._nativeView instanceof UIButton){ + (v._nativeView).setAttributedTitleForState(attrText, UIControlState.UIControlStateNormal); + } + } + } else { let source = v.text; let attributes = new Array(); @@ -76,7 +95,7 @@ export module ios { var decorationValues = (decoration + "").split(" "); - if (decorationValues.indexOf(enums.TextDecoration.none) === -1) { + if (decorationValues.indexOf(enums.TextDecoration.none) === -1 || hasLetterSpacing) { let dict = new Map(); if (decorationValues.indexOf(enums.TextDecoration.underline) !== -1) { @@ -87,6 +106,10 @@ export module ios { dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.NSUnderlineStyleSingle); } + if (hasLetterSpacing) { + dict.set(NSKernAttributeName, letterSpacing); + } + attributes.push({ attrs: dict, range: NSValue.valueWithRange(range) }); } @@ -238,7 +261,7 @@ export function openUrl(location: string): boolean { class UIDocumentInteractionControllerDelegateImpl extends NSObject implements UIDocumentInteractionControllerDelegate { public static ObjCProtocols = [UIDocumentInteractionControllerDelegate]; - public getViewController() : UIViewController { + public getViewController(): UIViewController { var frame = require("ui/frame"); return frame.topmost().currentPage.ios; }