mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
perf(ios): uifont and formatted string optimizations plus uiimage scaling (#9761)
This commit is contained in:
@@ -62,14 +62,6 @@ export class ImageAsset extends ImageAssetBase {
|
||||
}
|
||||
|
||||
private scaleImage(image: UIImage, requestedSize: { width: number; height: number }): UIImage {
|
||||
// scaleFactor = 0 takes the scale factor of the devices's main screen.
|
||||
const scaleFactor = this.options && this.options.autoScaleFactor === false ? 1.0 : 0.0;
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(requestedSize, false, scaleFactor);
|
||||
image.drawInRect(CGRectMake(0, 0, requestedSize.width, requestedSize.height));
|
||||
const resultImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return resultImage;
|
||||
return NativeScriptUtils.scaleImageWidthHeightScaleFactor(image, requestedSize.width, requestedSize.height, this.options?.autoScaleFactor === false ? 1.0 : 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,18 +440,7 @@ function getFileName(path: string): string {
|
||||
}
|
||||
|
||||
function getImageData(instance: UIImage, format: 'png' | 'jpeg' | 'jpg', quality = 0.9): NSData {
|
||||
let data = null;
|
||||
switch (format) {
|
||||
case 'png':
|
||||
data = UIImagePNGRepresentation(instance);
|
||||
break;
|
||||
case 'jpeg':
|
||||
case 'jpg':
|
||||
data = UIImageJPEGRepresentation(instance, quality);
|
||||
break;
|
||||
}
|
||||
|
||||
return data;
|
||||
return NativeScriptUtils.getImageDataFormatQuality(instance, format, quality);
|
||||
}
|
||||
|
||||
export function fromAsset(asset: ImageAsset): Promise<ImageSource> {
|
||||
|
||||
16
packages/core/platforms/ios/src/NativeScriptUtils.h
Normal file
16
packages/core/platforms/ios/src/NativeScriptUtils.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// NativeScriptUtils.h
|
||||
//
|
||||
// Created by Nathan Walker on 2/02/2022.
|
||||
#include <UIKit/UIKit.h>
|
||||
|
||||
@interface NativeScriptUtils : NSObject
|
||||
|
||||
+(UIFont*) getSystemFont:(CGFloat)size weight:(UIFontWeight)weight italic:(BOOL)italic symbolicTraits:(CGFloat)symbolicTraits;
|
||||
+(UIFont*) createUIFont:(NSDictionary*)font;
|
||||
+(NSMutableAttributedString*)createMutableStringWithDetails:(NSDictionary*)details;
|
||||
+(NSMutableAttributedString*)createMutableStringForSpan:(NSString*)text font:(UIFont*)font color:(UIColor*)color backgroundColor:(UIColor*)backgroundColor textDecoration:(NSString*)textDecoration baselineOffset:(CGFloat)baselineOffset;
|
||||
+(UIImage*)scaleImage:(UIImage*)image width:(CGFloat)width height:(CGFloat)height scaleFactor:(CGFloat)scaleFactor;
|
||||
+(NSData*)getImageData:(UIImage*)image format:(NSString*)format quality:(CGFloat)quality;
|
||||
|
||||
@end
|
||||
134
packages/core/platforms/ios/src/NativeScriptUtils.m
Normal file
134
packages/core/platforms/ios/src/NativeScriptUtils.m
Normal file
@@ -0,0 +1,134 @@
|
||||
#import "NativeScriptUtils.h"
|
||||
|
||||
@implementation NativeScriptUtils
|
||||
|
||||
+(UIFont*) getSystemFont:(CGFloat)size weight:(UIFontWeight)weight italic:(BOOL)italic symbolicTraits:(CGFloat)symbolicTraits {
|
||||
UIFont *result = [UIFont systemFontOfSize:size weight:weight];
|
||||
if (italic) {
|
||||
result = [UIFont fontWithDescriptor:[result.fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits] size:size];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
+(UIFont*) createUIFont:(NSDictionary*)font {
|
||||
UIFont *result;
|
||||
|
||||
CGFloat size = [[font valueForKey:@"fontSize"] floatValue];
|
||||
|
||||
UIFontDescriptorSymbolicTraits symbolicTraits = 0;
|
||||
if ([[font valueForKey:@"isBold"] boolValue]) {
|
||||
symbolicTraits = symbolicTraits | UIFontDescriptorTraitBold;
|
||||
}
|
||||
if ([[font valueForKey:@"isItalic"] boolValue]) {
|
||||
symbolicTraits = symbolicTraits | UIFontDescriptorTraitItalic;
|
||||
}
|
||||
|
||||
NSDictionary *fontDescriptorTraits = @{
|
||||
UIFontSymbolicTrait : @(symbolicTraits),
|
||||
UIFontWeightTrait : [font valueForKey:@"fontWeight"]
|
||||
};
|
||||
|
||||
for (NSString *family in [NSArray arrayWithArray:[font valueForKey:@"fontFamily"]]) {
|
||||
NSString *fontFamily = family;
|
||||
if ([family.lowercaseString isEqualToString:@"serif"]) {
|
||||
fontFamily = @"Times New Roman";
|
||||
} else if ([family.lowercaseString isEqualToString:@"monospace"]) {
|
||||
fontFamily = @"Courier New";
|
||||
}
|
||||
if (!fontFamily || [fontFamily isEqualToString:@"sans-serif"] || [fontFamily isEqualToString:@"system"]) {
|
||||
result = [NativeScriptUtils getSystemFont:size weight:(UIFontWeight)[[font valueForKey:@"fontWeight"] floatValue] italic:[[font valueForKey:@"isItalic"] boolValue] symbolicTraits:symbolicTraits];
|
||||
break;
|
||||
} else {
|
||||
|
||||
UIFontDescriptor *descriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{
|
||||
UIFontDescriptorFamilyAttribute: fontFamily,
|
||||
UIFontDescriptorTraitsAttribute: fontDescriptorTraits
|
||||
}];
|
||||
|
||||
result = [UIFont fontWithDescriptor:descriptor size:size];
|
||||
|
||||
BOOL actualItalic = result.fontDescriptor.symbolicTraits & UIFontDescriptorTraitItalic;
|
||||
if ([[font valueForKey:@"isItalic"] boolValue] && !actualItalic) {
|
||||
// The font we got is not actually italic so emulate that with a matrix
|
||||
result = [UIFont fontWithDescriptor:[descriptor fontDescriptorWithMatrix:CGAffineTransformMake(1, 0, 0.2, 1, 0, 0)] size:size];
|
||||
}
|
||||
|
||||
// Check if the resolved font has the correct font-family
|
||||
// If not - fallback to the next font-family
|
||||
if ([result.familyName isEqualToString:fontFamily]) {
|
||||
break;
|
||||
} else {
|
||||
result = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't resolve font - fallback to the system font
|
||||
if (result == nil) {
|
||||
result = [NativeScriptUtils getSystemFont:size weight:(UIFontWeight)[[font valueForKey:@"fontWeight"] floatValue] italic:[[font valueForKey:@"isItalic"] boolValue] symbolicTraits:symbolicTraits];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+(NSMutableAttributedString*)createMutableStringWithDetails:(NSDictionary*)details {
|
||||
NSMutableAttributedString *mas = [[NSMutableAttributedString alloc] init];
|
||||
for (NSDictionary *detail in [NSArray arrayWithArray:[details valueForKey:@"spans"]]) {
|
||||
NSMutableAttributedString *attrString = [NativeScriptUtils createMutableStringForSpan:[detail objectForKey:@"text"] font:[detail objectForKey:@"iosFont"] color:[detail objectForKey:@"color"] backgroundColor:[detail objectForKey:@"backgroundColor"] textDecoration:[detail objectForKey:@"textDecoration"] baselineOffset:[[detail valueForKey:@"baselineOffset"] floatValue]];
|
||||
[mas insertAttributedString:attrString atIndex:[[detail valueForKey:@"index"] intValue]];
|
||||
}
|
||||
return mas;
|
||||
}
|
||||
|
||||
+(NSMutableAttributedString*)createMutableStringForSpan:(NSString*)text font:(UIFont*)font color:(UIColor*)color backgroundColor:(UIColor*)backgroundColor textDecoration:(NSString*)textDecoration baselineOffset:(CGFloat)baselineOffset {
|
||||
NSMutableDictionary *attrDict = [[NSMutableDictionary alloc] init];
|
||||
attrDict[NSFontAttributeName] = font;
|
||||
|
||||
if (color != nil) {
|
||||
attrDict[NSForegroundColorAttributeName] = color;
|
||||
}
|
||||
|
||||
if (backgroundColor != nil) {
|
||||
attrDict[NSBackgroundColorAttributeName] = backgroundColor;
|
||||
}
|
||||
|
||||
if (textDecoration != nil) {
|
||||
if ([textDecoration rangeOfString:@"underline"].location != NSNotFound) {
|
||||
attrDict[NSUnderlineStyleAttributeName] = [NSNumber numberWithInt:NSUnderlineStyleSingle];
|
||||
}
|
||||
|
||||
if ([textDecoration rangeOfString:@"line-through"].location != NSNotFound) {
|
||||
attrDict[NSStrikethroughStyleAttributeName] = [NSNumber numberWithInt:NSUnderlineStyleSingle];
|
||||
}
|
||||
}
|
||||
|
||||
attrDict[NSBaselineOffsetAttributeName] = [NSNumber numberWithInt:baselineOffset];
|
||||
|
||||
return [[NSMutableAttributedString alloc] initWithString:text attributes:attrDict];
|
||||
|
||||
}
|
||||
|
||||
+(UIImage*)scaleImage:(UIImage*)image width:(CGFloat)width height:(CGFloat)height scaleFactor:(CGFloat)scaleFactor {
|
||||
UIImage *resultImage;
|
||||
@autoreleasepool {
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, scaleFactor);
|
||||
[image drawInRect:CGRectMake(0, 0, width, height)];
|
||||
resultImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
}
|
||||
return resultImage;
|
||||
}
|
||||
|
||||
+(NSData*)getImageData:(UIImage*)image format:(NSString*)format quality:(CGFloat)quality {
|
||||
NSData *data;
|
||||
@autoreleasepool {
|
||||
if ([format.lowercaseString isEqualToString:@"png"]) {
|
||||
data = UIImagePNGRepresentation(image);
|
||||
} else {
|
||||
data = UIImageJPEGRepresentation(image, quality);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@end
|
||||
13
packages/core/platforms/ios/src/UIView+NativeScript.h
Normal file
13
packages/core/platforms/ios/src/UIView+NativeScript.h
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// UIView+NativeScript.h
|
||||
//
|
||||
// Created by Nathan Walker on 2/02/2022.
|
||||
#include <UIKit/UIKit.h>
|
||||
|
||||
@interface UIView (NativeScript)
|
||||
|
||||
- (void)nativeScriptSetTextDecorationAndTransform:(NSString*)text textDecoration:(NSString*)textDecoration letterSpacing:(CGFloat)letterSpacing lineHeight:(CGFloat)lineHeight;
|
||||
|
||||
-(void)nativeScriptSetFormattedTextDecorationAndTransform:(NSDictionary*)details;
|
||||
|
||||
@end
|
||||
127
packages/core/platforms/ios/src/UIView+NativeScript.m
Normal file
127
packages/core/platforms/ios/src/UIView+NativeScript.m
Normal file
@@ -0,0 +1,127 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "UIView+NativeScript.h"
|
||||
#import "NativeScriptUtils.h"
|
||||
|
||||
@implementation UIView (NativeScript)
|
||||
- (void)nativeScriptSetTextDecorationAndTransform:(NSString*)text textDecoration:(NSString*)textDecoration letterSpacing:(CGFloat)letterSpacing lineHeight:(CGFloat)lineHeight {
|
||||
NSMutableDictionary *attrDict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
if ([textDecoration rangeOfString:@"underline"].location != NSNotFound) {
|
||||
attrDict[NSUnderlineStyleAttributeName] = [NSNumber numberWithInt:NSUnderlineStyleSingle];
|
||||
}
|
||||
|
||||
if ([textDecoration rangeOfString:@"line-through"].location != NSNotFound) {
|
||||
attrDict[NSStrikethroughStyleAttributeName] = [NSNumber numberWithInt:NSUnderlineStyleSingle];
|
||||
}
|
||||
BOOL isTextType = [self isKindOfClass:[UITextField class]] || [self isKindOfClass:[UITextView class]] | [self isKindOfClass:[UILabel class]] | [self isKindOfClass:[UIButton class]];
|
||||
|
||||
if (letterSpacing != 0 && isTextType && ((UITextView*)self).font != nil) {
|
||||
NSNumber *kern = [NSNumber numberWithDouble:letterSpacing * ((UITextView*)self).font.pointSize];
|
||||
attrDict[NSKernAttributeName] = kern;
|
||||
if ([self isKindOfClass:[UITextField class]]) {
|
||||
[((UITextField*)self).defaultTextAttributes setValue:kern forKey:NSKernAttributeName];
|
||||
}
|
||||
}
|
||||
|
||||
BOOL isTextView = [self isKindOfClass:[UITextView class]];
|
||||
if (lineHeight > 0) {
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
|
||||
paragraphStyle.lineSpacing = lineHeight;
|
||||
// make sure a possible previously set text alignment setting is not lost when line height is specified
|
||||
if ([self isKindOfClass:[UIButton class]]) {
|
||||
paragraphStyle.alignment = ((UIButton*)self).titleLabel.textAlignment;
|
||||
} else {
|
||||
paragraphStyle.alignment = ((UILabel*)self).textAlignment;
|
||||
}
|
||||
|
||||
if ([self isKindOfClass:[UILabel class]]) {
|
||||
// make sure a possible previously set line break mode is not lost when line height is specified
|
||||
paragraphStyle.lineBreakMode = ((UILabel*)self).lineBreakMode;
|
||||
}
|
||||
attrDict[NSParagraphStyleAttributeName] = paragraphStyle;
|
||||
} else if (isTextView) {
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
|
||||
paragraphStyle.alignment = ((UITextView*)self).textAlignment;
|
||||
attrDict[NSParagraphStyleAttributeName] = paragraphStyle;
|
||||
}
|
||||
|
||||
if (attrDict.count > 0 || isTextView) {
|
||||
if (isTextView && ((UITextView*)self).font) {
|
||||
// UITextView's font seems to change inside.
|
||||
attrDict[NSFontAttributeName] = ((UITextView*)self).font;
|
||||
}
|
||||
|
||||
NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithString:text];
|
||||
[result setAttributes:attrDict range:(NSRange){
|
||||
0,
|
||||
text.length
|
||||
}];
|
||||
|
||||
if ([self isKindOfClass:[UIButton class]]) {
|
||||
[(UIButton*)self setAttributedTitle:result forState:UIControlStateNormal];
|
||||
} else {
|
||||
((UITextView*)self).attributedText = result;
|
||||
}
|
||||
} else {
|
||||
if ([self isKindOfClass:[UIButton class]]) {
|
||||
// Clear attributedText or title won't be affected.
|
||||
[(UIButton*)self setAttributedTitle:nil forState:UIControlStateNormal];
|
||||
[(UIButton*)self setTitle:text forState:UIControlStateNormal];
|
||||
} else {
|
||||
// Clear attributedText or text won't be affected.
|
||||
((UILabel*)self).attributedText = nil;
|
||||
((UILabel*)self).text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void)nativeScriptSetFormattedTextDecorationAndTransform:(NSDictionary*)details {
|
||||
CGFloat letterSpacing = [[details valueForKey:@"letterSpacing"] doubleValue];
|
||||
CGFloat lineHeight = [[details valueForKey:@"lineHeight"] doubleValue];
|
||||
NSMutableAttributedString *attrText = [NativeScriptUtils createMutableStringWithDetails:details];
|
||||
if (letterSpacing != 0) {
|
||||
NSNumber *kern = [NSNumber numberWithDouble:letterSpacing * ((UITextView*)self).font.pointSize];
|
||||
[attrText addAttribute:NSKernAttributeName value:kern range:(NSRange){
|
||||
0,
|
||||
attrText.length
|
||||
} ];
|
||||
}
|
||||
|
||||
if (lineHeight > 0) {
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
|
||||
paragraphStyle.minimumLineHeight = lineHeight;
|
||||
// make sure a possible previously set text alignment setting is not lost when line height is specified
|
||||
if ([self isKindOfClass:[UIButton class]]) {
|
||||
paragraphStyle.alignment = ((UIButton*)self).titleLabel.textAlignment;
|
||||
} else {
|
||||
paragraphStyle.alignment = ((UILabel*)self).textAlignment;
|
||||
}
|
||||
|
||||
if ([self isKindOfClass:[UILabel class]]) {
|
||||
// make sure a possible previously set line break mode is not lost when line height is specified
|
||||
paragraphStyle.lineBreakMode = ((UILabel*)self).lineBreakMode;
|
||||
}
|
||||
[attrText addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:(NSRange){
|
||||
0,
|
||||
attrText.length
|
||||
}];
|
||||
} else if ([self isKindOfClass:[UITextView class]]) {
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
|
||||
paragraphStyle.alignment = ((UITextView*)self).textAlignment;
|
||||
[attrText addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:(NSRange){
|
||||
0,
|
||||
attrText.length
|
||||
}];
|
||||
}
|
||||
|
||||
if ([self isKindOfClass:[UIButton class]]) {
|
||||
[(UIButton*)self setAttributedTitle:attrText forState:UIControlStateNormal];
|
||||
} else {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
((UILabel*)self).textColor = [UIColor labelColor];
|
||||
}
|
||||
|
||||
((UILabel*)self).attributedText = attrText;
|
||||
}
|
||||
}
|
||||
@end
|
||||
@@ -2,3 +2,13 @@ module NativeScriptEmbedder {
|
||||
header "NativeScriptEmbedder.h"
|
||||
export *
|
||||
}
|
||||
|
||||
module NativeScriptUtils {
|
||||
header "NativeScriptUtils.h"
|
||||
export *
|
||||
}
|
||||
|
||||
module UIViewNativeScript {
|
||||
header "UIView+NativeScript.h"
|
||||
export *
|
||||
}
|
||||
19
packages/core/platforms/ios/typings/objc!NativeScriptUtils.d.ts
vendored
Normal file
19
packages/core/platforms/ios/typings/objc!NativeScriptUtils.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
declare class NativeScriptUtils extends NSObject {
|
||||
|
||||
static alloc(): NativeScriptUtils; // inherited from NSObject
|
||||
|
||||
static createMutableStringForSpanFontColorBackgroundColorTextDecorationBaselineOffset(text: string, font: UIFont, color: UIColor, backgroundColor: UIColor, textDecoration: string, baselineOffset: number): NSMutableAttributedString;
|
||||
|
||||
static createMutableStringWithDetails(details: NSDictionary<any, any>): NSMutableAttributedString;
|
||||
|
||||
static createUIFont(font: NSDictionary<any, any>): UIFont;
|
||||
|
||||
static getImageDataFormatQuality(image: UIImage, format: string, quality: number): NSData;
|
||||
|
||||
static getSystemFontWeightItalicSymbolicTraits(size: number, weight: number, italic: boolean, symbolicTraits: number): UIFont;
|
||||
|
||||
static new(): NativeScriptUtils; // inherited from NSObject
|
||||
|
||||
static scaleImageWidthHeightScaleFactor(image: UIImage, width: number, height: number, scaleFactor: number): UIImage;
|
||||
}
|
||||
1
packages/core/references.d.ts
vendored
1
packages/core/references.d.ts
vendored
@@ -1,4 +1,5 @@
|
||||
/// <reference path="../types-ios/src/lib/ios.d.ts" />
|
||||
/// <reference path="../types-android/src/lib/android-29.d.ts" />
|
||||
/// <reference path="./platforms/ios/typings/objc!MaterialComponents.d.ts" />
|
||||
/// <reference path="./platforms/ios/typings/objc!NativeScriptUtils.d.ts" />
|
||||
/// <reference path="./global-types.d.ts" />
|
||||
|
||||
@@ -17,8 +17,8 @@ export abstract class Font implements FontDefinition {
|
||||
|
||||
protected constructor(public readonly fontFamily: string, public readonly fontSize: number, public readonly fontStyle: FontStyleType, public readonly fontWeight: FontWeightType, public readonly fontScale: number) {}
|
||||
|
||||
public abstract getAndroidTypeface(): any /* android.graphics.Typeface */;
|
||||
public abstract getUIFont(defaultFont: any /* UIFont */): any /* UIFont */;
|
||||
public abstract getAndroidTypeface(): any; /* android.graphics.Typeface */
|
||||
public abstract getUIFont(defaultFont: any /* UIFont */): any; /* UIFont */
|
||||
public abstract withFontFamily(family: string): Font;
|
||||
public abstract withFontStyle(style: string): Font;
|
||||
public abstract withFontWeight(weight: string): Font;
|
||||
@@ -64,20 +64,14 @@ export namespace FontWeight {
|
||||
}
|
||||
|
||||
export function parseFontFamily(value: string): Array<string> {
|
||||
const result = new Array<string>();
|
||||
if (!value) {
|
||||
return result;
|
||||
return [];
|
||||
}
|
||||
|
||||
const split = value.split(',');
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
const str = split[i].trim().replace(/['"]+/g, '');
|
||||
if (str) {
|
||||
result.push(str);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return value
|
||||
.split(',')
|
||||
.map((v) => (v || '').trim().replace(/['"]+/g, ''))
|
||||
.filter((v) => !!v);
|
||||
}
|
||||
|
||||
export namespace genericFontFamilies {
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { Font as FontBase, parseFontFamily, genericFontFamilies, FontStyle, FontWeight, FontStyleType, FontWeightType } from './font-common';
|
||||
import { Font as FontBase, parseFontFamily, FontStyle, FontWeight, FontStyleType, FontWeightType } from './font-common';
|
||||
import { Trace } from '../../trace';
|
||||
import { Device } from '../../platform';
|
||||
import * as fs from '../../file-system';
|
||||
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 {
|
||||
public static default = new Font(undefined, undefined, FontStyle.NORMAL, FontWeight.NORMAL, 1);
|
||||
|
||||
@@ -40,11 +33,15 @@ export class Font extends FontBase {
|
||||
}
|
||||
|
||||
public getUIFont(defaultFont: UIFont): UIFont {
|
||||
if (!this._uiFont) {
|
||||
this._uiFont = createUIFont(this, defaultFont);
|
||||
}
|
||||
|
||||
return this._uiFont;
|
||||
return new WeakRef(
|
||||
NativeScriptUtils.createUIFont(<any>{
|
||||
fontFamily: parseFontFamily(this.fontFamily),
|
||||
fontSize: this.fontSize || defaultFont.pointSize,
|
||||
fontWeight: getNativeFontWeight(this.fontWeight),
|
||||
isBold: this.isBold,
|
||||
isItalic: this.isItalic,
|
||||
})
|
||||
).get();
|
||||
}
|
||||
|
||||
public getAndroidTypeface(): android.graphics.Typeface {
|
||||
@@ -52,27 +49,6 @@ export class Font extends FontBase {
|
||||
}
|
||||
}
|
||||
|
||||
function getFontFamilyRespectingGenericFonts(fontFamily: string): string {
|
||||
if (!fontFamily) {
|
||||
return fontFamily;
|
||||
}
|
||||
|
||||
switch (fontFamily.toLowerCase()) {
|
||||
case genericFontFamilies.serif:
|
||||
return DEFAULT_SERIF;
|
||||
|
||||
case genericFontFamilies.monospace:
|
||||
return DEFAULT_MONOSPACE;
|
||||
|
||||
default:
|
||||
return fontFamily;
|
||||
}
|
||||
}
|
||||
|
||||
function shouldUseSystemFont(fontFamily: string) {
|
||||
return !fontFamily || fontFamily === genericFontFamilies.sansSerif || fontFamily === genericFontFamilies.system;
|
||||
}
|
||||
|
||||
function getNativeFontWeight(fontWeight: FontWeightType): number {
|
||||
switch (fontWeight) {
|
||||
case FontWeight.THIN:
|
||||
@@ -98,84 +74,10 @@ function getNativeFontWeight(fontWeight: FontWeightType): number {
|
||||
case FontWeight.BLACK:
|
||||
return UIFontWeightBlack;
|
||||
default:
|
||||
throw new Error(`Invalid font weight: "${fontWeight}"`);
|
||||
console.log(`Invalid font weight: "${fontWeight}"`);
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemFont(size: number, nativeWeight: number, italic: boolean, symbolicTraits: number): UIFont {
|
||||
let result = UIFont.systemFontOfSizeWeight(size, nativeWeight);
|
||||
if (italic) {
|
||||
const descriptor = result.fontDescriptor.fontDescriptorWithSymbolicTraits(symbolicTraits);
|
||||
result = UIFont.fontWithDescriptorSize(descriptor, size);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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 = 0;
|
||||
if (font.isBold) {
|
||||
symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitBold;
|
||||
}
|
||||
if (font.isItalic) {
|
||||
symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitItalic;
|
||||
}
|
||||
|
||||
const fontDescriptorTraits = {
|
||||
[UIFontSymbolicTrait]: symbolicTraits,
|
||||
};
|
||||
|
||||
// IOS versions below 10 will not return the correct font when UIFontWeightTrait is set
|
||||
// In this case - rely on UIFontSymbolicTrait only
|
||||
if (SUPPORT_FONT_WEIGHTS) {
|
||||
fontDescriptorTraits[UIFontWeightTrait] = nativeWeight;
|
||||
}
|
||||
|
||||
for (let i = 0; i < fontFamilies.length; i++) {
|
||||
const fontFamily = getFontFamilyRespectingGenericFonts(fontFamilies[i]);
|
||||
|
||||
if (shouldUseSystemFont(fontFamily)) {
|
||||
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);
|
||||
|
||||
const actualItalic = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't resolve font - fallback to the system font
|
||||
if (!result) {
|
||||
result = getSystemFont(size, nativeWeight, font.isItalic, symbolicTraits);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export namespace ios {
|
||||
export function registerFont(fontFile: string) {
|
||||
let filePath = fs.path.join(fs.knownFolders.currentApp().path, 'fonts', fontFile);
|
||||
|
||||
@@ -173,7 +173,7 @@ export class TextBase extends TextBaseCommon {
|
||||
break;
|
||||
case 'justify':
|
||||
nativeView.textAlignment = NSTextAlignment.Justified;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,15 @@ export class TextBase extends TextBaseCommon {
|
||||
this._setShadow(value);
|
||||
}
|
||||
|
||||
_setColor(color: UIColor): void {
|
||||
if (this.nativeTextViewProtected instanceof UIButton) {
|
||||
this.nativeTextViewProtected.setTitleColorForState(color, UIControlState.Normal);
|
||||
this.nativeTextViewProtected.titleLabel.textColor = color;
|
||||
} else {
|
||||
this.nativeTextViewProtected.textColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
_setNativeText(reset = false): void {
|
||||
if (reset) {
|
||||
const nativeView = this.nativeTextViewProtected;
|
||||
@@ -214,144 +223,102 @@ export class TextBase extends TextBaseCommon {
|
||||
}
|
||||
|
||||
if (this.formattedText) {
|
||||
this.setFormattedTextDecorationAndTransform();
|
||||
(<any>this.nativeTextViewProtected).nativeScriptSetFormattedTextDecorationAndTransform(this.getFormattedStringDetails(this.formattedText));
|
||||
} else {
|
||||
this.setTextDecorationAndTransform();
|
||||
}
|
||||
}
|
||||
// console.log('setTextDecorationAndTransform...')
|
||||
const text = getTransformedText(isNullOrUndefined(this.text) ? '' : `${this.text}`, this.textTransform);
|
||||
(<any>this.nativeTextViewProtected).nativeScriptSetTextDecorationAndTransformTextDecorationLetterSpacingLineHeight(text, this.style.textDecoration || '', this.style.letterSpacing !== 0 ? this.style.letterSpacing : 0, this.style.lineHeight ? this.style.lineHeight : 0);
|
||||
|
||||
_setColor(color: UIColor): void {
|
||||
if (this.nativeTextViewProtected instanceof UIButton) {
|
||||
this.nativeTextViewProtected.setTitleColorForState(color, UIControlState.Normal);
|
||||
this.nativeTextViewProtected.titleLabel.textColor = color;
|
||||
} else {
|
||||
this.nativeTextViewProtected.textColor = color;
|
||||
if (!this.style?.color && majorVersion >= 13 && UIColor.labelColor) {
|
||||
this._setColor(UIColor.labelColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createFormattedTextNative(value: FormattedString) {
|
||||
return this.createNSMutableAttributedString(value);
|
||||
}
|
||||
setFormattedTextDecorationAndTransform() {
|
||||
const attrText = this.createFormattedTextNative(this.formattedText);
|
||||
// TODO: letterSpacing should be applied per Span.
|
||||
if (this.letterSpacing !== 0) {
|
||||
attrText.addAttributeValueRange(NSKernAttributeName, this.letterSpacing * this.nativeTextViewProtected.font.pointSize, { location: 0, length: attrText.length });
|
||||
}
|
||||
|
||||
if (this.style.lineHeight) {
|
||||
const paragraphStyle = NSMutableParagraphStyle.alloc().init();
|
||||
paragraphStyle.minimumLineHeight = this.lineHeight;
|
||||
// make sure a possible previously set text alignment setting is not lost when line height is specified
|
||||
if (this.nativeTextViewProtected instanceof UIButton) {
|
||||
paragraphStyle.alignment = (<UIButton>this.nativeTextViewProtected).titleLabel.textAlignment;
|
||||
} else {
|
||||
paragraphStyle.alignment = (<UITextField | UITextView | UILabel>this.nativeTextViewProtected).textAlignment;
|
||||
}
|
||||
|
||||
if (this.nativeTextViewProtected instanceof UILabel) {
|
||||
// make sure a possible previously set line break mode is not lost when line height is specified
|
||||
paragraphStyle.lineBreakMode = this.nativeTextViewProtected.lineBreakMode;
|
||||
}
|
||||
attrText.addAttributeValueRange(NSParagraphStyleAttributeName, paragraphStyle, { location: 0, length: attrText.length });
|
||||
} else if (this.nativeTextViewProtected instanceof UITextView) {
|
||||
const paragraphStyle = NSMutableParagraphStyle.alloc().init();
|
||||
paragraphStyle.alignment = (<UITextView>this.nativeTextViewProtected).textAlignment;
|
||||
attrText.addAttributeValueRange(NSParagraphStyleAttributeName, paragraphStyle, { location: 0, length: attrText.length });
|
||||
}
|
||||
|
||||
if (this.nativeTextViewProtected instanceof UIButton) {
|
||||
this.nativeTextViewProtected.setAttributedTitleForState(attrText, UIControlState.Normal);
|
||||
} else {
|
||||
if (majorVersion >= 13 && UIColor.labelColor) {
|
||||
this.nativeTextViewProtected.textColor = UIColor.labelColor;
|
||||
}
|
||||
|
||||
this.nativeTextViewProtected.attributedText = attrText;
|
||||
}
|
||||
return NativeScriptUtils.createMutableStringWithDetails(<any>this.getFormattedStringDetails(value));
|
||||
}
|
||||
|
||||
setTextDecorationAndTransform() {
|
||||
const style = this.style;
|
||||
const dict = new Map<string, any>();
|
||||
switch (style.textDecoration) {
|
||||
case 'none':
|
||||
break;
|
||||
case 'underline':
|
||||
dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.Single);
|
||||
break;
|
||||
case 'line-through':
|
||||
dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.Single);
|
||||
break;
|
||||
case 'underline line-through':
|
||||
dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.Single);
|
||||
dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.Single);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid text decoration value: ${style.textDecoration}. Valid values are: 'none', 'underline', 'line-through', 'underline line-through'.`);
|
||||
}
|
||||
getFormattedStringDetails(formattedString: FormattedString) {
|
||||
const details = {
|
||||
spans: [],
|
||||
};
|
||||
this._spanRanges = [];
|
||||
if (formattedString && formattedString.parent) {
|
||||
for (let i = 0, spanStart = 0, length = formattedString.spans.length; i < length; i++) {
|
||||
const span = formattedString.spans.getItem(i);
|
||||
const text = span.text;
|
||||
const textTransform = (<TextBase>formattedString.parent).textTransform;
|
||||
let spanText = isNullOrUndefined(text) ? '' : `${text}`;
|
||||
if (textTransform !== 'none' && textTransform !== 'initial') {
|
||||
spanText = getTransformedText(spanText, textTransform);
|
||||
}
|
||||
|
||||
if (style.letterSpacing !== 0 && this.nativeTextViewProtected.font) {
|
||||
const kern = style.letterSpacing * this.nativeTextViewProtected.font.pointSize;
|
||||
dict.set(NSKernAttributeName, kern);
|
||||
if (this.nativeTextViewProtected instanceof UITextField) {
|
||||
this.nativeTextViewProtected.defaultTextAttributes.setValueForKey(kern, NSKernAttributeName);
|
||||
details.spans.push(this.createMutableStringDetails(span, spanText, spanStart));
|
||||
this._spanRanges.push({
|
||||
location: spanStart,
|
||||
length: spanText.length,
|
||||
});
|
||||
spanStart += spanText.length;
|
||||
}
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
const isTextView = this.nativeTextViewProtected instanceof UITextView;
|
||||
if (style.lineHeight) {
|
||||
const paragraphStyle = NSMutableParagraphStyle.alloc().init();
|
||||
paragraphStyle.lineSpacing = style.lineHeight;
|
||||
// make sure a possible previously set text alignment setting is not lost when line height is specified
|
||||
if (this.nativeTextViewProtected instanceof UIButton) {
|
||||
paragraphStyle.alignment = (<UIButton>this.nativeTextViewProtected).titleLabel.textAlignment;
|
||||
} else {
|
||||
paragraphStyle.alignment = (<UITextField | UITextView | UILabel>this.nativeTextViewProtected).textAlignment;
|
||||
}
|
||||
createMutableStringDetails(span: Span, text: string, index?: number): any {
|
||||
const font = new Font(span.style.fontFamily, span.style.fontSize, span.style.fontStyle, span.style.fontWeight);
|
||||
const iosFont = font.getUIFont(this.nativeTextViewProtected.font);
|
||||
|
||||
if (this.nativeTextViewProtected instanceof UILabel) {
|
||||
// make sure a possible previously set line break mode is not lost when line height is specified
|
||||
paragraphStyle.lineBreakMode = this.nativeTextViewProtected.lineBreakMode;
|
||||
}
|
||||
dict.set(NSParagraphStyleAttributeName, paragraphStyle);
|
||||
} else if (isTextView) {
|
||||
const paragraphStyle = NSMutableParagraphStyle.alloc().init();
|
||||
paragraphStyle.alignment = (<UITextView>this.nativeTextViewProtected).textAlignment;
|
||||
dict.set(NSParagraphStyleAttributeName, paragraphStyle);
|
||||
const backgroundColor = <Color>(span.style.backgroundColor || (<FormattedString>span.parent).backgroundColor || (<TextBase>span.parent.parent).backgroundColor);
|
||||
return {
|
||||
text,
|
||||
iosFont,
|
||||
color: span.color ? span.color.ios : null,
|
||||
backgroundColor: backgroundColor ? backgroundColor.ios : null,
|
||||
textDecoration: getClosestPropertyValue(textDecorationProperty, span),
|
||||
letterSpacing: this.letterSpacing || 0,
|
||||
lineHeight: this.lineHeight || 0, //this.style?.lineHeight ? this.style.lineHeight : 0,
|
||||
baselineOffset: this.getBaselineOffset(iosFont, span.style.verticalAlignment),
|
||||
index,
|
||||
};
|
||||
}
|
||||
|
||||
createMutableStringForSpan(span: Span, text: string): NSMutableAttributedString {
|
||||
const details = this.createMutableStringDetails(span, text);
|
||||
return NativeScriptUtils.createMutableStringForSpanFontColorBackgroundColorTextDecorationBaselineOffset(details.text, details.iosFont, details.color, details.backgroundColor, details.textDecoration, details.baselineOffset);
|
||||
}
|
||||
|
||||
getBaselineOffset(font: UIFont, align?: CoreTypes.VerticalAlignmentTextType): number {
|
||||
if (!align || ['stretch', 'baseline'].includes(align)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const source = getTransformedText(isNullOrUndefined(this.text) ? '' : `${this.text}`, this.textTransform);
|
||||
if (dict.size > 0 || isTextView) {
|
||||
if (isTextView && this.nativeTextViewProtected.font) {
|
||||
// UITextView's font seems to change inside.
|
||||
dict.set(NSFontAttributeName, this.nativeTextViewProtected.font);
|
||||
}
|
||||
|
||||
const result = NSMutableAttributedString.alloc().initWithString(source);
|
||||
result.setAttributesRange(<any>dict, {
|
||||
location: 0,
|
||||
length: source.length,
|
||||
});
|
||||
if (this.nativeTextViewProtected instanceof UIButton) {
|
||||
this.nativeTextViewProtected.setAttributedTitleForState(result, UIControlState.Normal);
|
||||
} else {
|
||||
this.nativeTextViewProtected.attributedText = result;
|
||||
}
|
||||
} else {
|
||||
if (this.nativeTextViewProtected instanceof UIButton) {
|
||||
// Clear attributedText or title won't be affected.
|
||||
this.nativeTextViewProtected.setAttributedTitleForState(null, UIControlState.Normal);
|
||||
this.nativeTextViewProtected.setTitleForState(source, UIControlState.Normal);
|
||||
} else {
|
||||
// Clear attributedText or text won't be affected.
|
||||
this.nativeTextViewProtected.attributedText = undefined;
|
||||
this.nativeTextViewProtected.text = source;
|
||||
}
|
||||
if (align === 'top') {
|
||||
return -this.fontSize - font.descender - font.ascender - font.leading / 2;
|
||||
}
|
||||
|
||||
if (!style.color && majorVersion >= 13 && UIColor.labelColor) {
|
||||
this._setColor(UIColor.labelColor);
|
||||
if (align === 'bottom') {
|
||||
return font.descender + font.leading / 2;
|
||||
}
|
||||
|
||||
if (align === 'text-top') {
|
||||
return -this.fontSize - font.descender - font.ascender;
|
||||
}
|
||||
|
||||
if (align === 'text-bottom') {
|
||||
return font.descender;
|
||||
}
|
||||
|
||||
if (align === 'middle') {
|
||||
return (font.descender - font.ascender) / 2 - font.descender;
|
||||
}
|
||||
|
||||
if (align === 'sup') {
|
||||
return -this.fontSize * 0.4;
|
||||
}
|
||||
|
||||
if (align === 'sub') {
|
||||
return (font.descender - font.ascender) * 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,108 +357,6 @@ export class TextBase extends TextBaseCommon {
|
||||
// layer.shouldRasterize = true;
|
||||
// }
|
||||
}
|
||||
|
||||
createNSMutableAttributedString(formattedString: FormattedString): NSMutableAttributedString {
|
||||
const mas = NSMutableAttributedString.alloc().init();
|
||||
this._spanRanges = [];
|
||||
if (formattedString && formattedString.parent) {
|
||||
for (let i = 0, spanStart = 0, length = formattedString.spans.length; i < length; i++) {
|
||||
const span = formattedString.spans.getItem(i);
|
||||
const text = span.text;
|
||||
const textTransform = (<TextBase>formattedString.parent).textTransform;
|
||||
let spanText = isNullOrUndefined(text) ? '' : `${text}`;
|
||||
if (textTransform !== 'none' && textTransform !== 'initial') {
|
||||
spanText = getTransformedText(spanText, textTransform);
|
||||
}
|
||||
|
||||
const nsAttributedString = this.createMutableStringForSpan(span, spanText);
|
||||
mas.insertAttributedStringAtIndex(nsAttributedString, spanStart);
|
||||
this._spanRanges.push({
|
||||
location: spanStart,
|
||||
length: spanText.length,
|
||||
});
|
||||
spanStart += spanText.length;
|
||||
}
|
||||
}
|
||||
|
||||
return mas;
|
||||
}
|
||||
|
||||
getBaselineOffset(font: UIFont, align?: CoreTypes.VerticalAlignmentTextType): number {
|
||||
if (!align || ['stretch', 'baseline'].includes(align)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (align === 'top') {
|
||||
return -this.fontSize - font.descender - font.ascender - font.leading / 2;
|
||||
}
|
||||
|
||||
if (align === 'bottom') {
|
||||
return font.descender + font.leading / 2;
|
||||
}
|
||||
|
||||
if (align === 'text-top') {
|
||||
return -this.fontSize - font.descender - font.ascender;
|
||||
}
|
||||
|
||||
if (align === 'text-bottom') {
|
||||
return font.descender;
|
||||
}
|
||||
|
||||
if (align === 'middle') {
|
||||
return (font.descender - font.ascender) / 2 - font.descender;
|
||||
}
|
||||
|
||||
if (align === 'sup') {
|
||||
return -this.fontSize * 0.4;
|
||||
}
|
||||
|
||||
if (align === 'sub') {
|
||||
return (font.descender - font.ascender) * 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
createMutableStringForSpan(span: Span, text: string): NSMutableAttributedString {
|
||||
const viewFont = this.nativeTextViewProtected.font;
|
||||
const attrDict = <{ key: string; value: any }>{};
|
||||
const style = span.style;
|
||||
const align = style.verticalAlignment;
|
||||
|
||||
const font = new Font(style.fontFamily, style.fontSize, style.fontStyle, style.fontWeight);
|
||||
const iosFont = font.getUIFont(viewFont);
|
||||
|
||||
attrDict[NSFontAttributeName] = iosFont;
|
||||
|
||||
if (span.color) {
|
||||
attrDict[NSForegroundColorAttributeName] = span.color.ios;
|
||||
}
|
||||
|
||||
// We don't use isSet function here because defaultValue for backgroundColor is null.
|
||||
const backgroundColor = <Color>(style.backgroundColor || (<FormattedString>span.parent).backgroundColor || (<TextBase>span.parent.parent).backgroundColor);
|
||||
if (backgroundColor) {
|
||||
attrDict[NSBackgroundColorAttributeName] = backgroundColor.ios;
|
||||
}
|
||||
|
||||
const textDecoration: CoreTypes.TextDecorationType = getClosestPropertyValue(textDecorationProperty, span);
|
||||
|
||||
if (textDecoration) {
|
||||
const underline = textDecoration.indexOf('underline') !== -1;
|
||||
if (underline) {
|
||||
attrDict[NSUnderlineStyleAttributeName] = underline;
|
||||
}
|
||||
|
||||
const strikethrough = textDecoration.indexOf('line-through') !== -1;
|
||||
if (strikethrough) {
|
||||
attrDict[NSStrikethroughStyleAttributeName] = strikethrough;
|
||||
}
|
||||
}
|
||||
|
||||
if (align) {
|
||||
attrDict[NSBaselineOffsetAttributeName] = this.getBaselineOffset(iosFont, align);
|
||||
}
|
||||
|
||||
return NSMutableAttributedString.alloc().initWithStringAttributes(text, <any>attrDict);
|
||||
}
|
||||
}
|
||||
|
||||
export function getTransformedText(text: string, textTransform: CoreTypes.TextTransformType): string {
|
||||
|
||||
Reference in New Issue
Block a user