mirror of
				https://github.com/NativeScript/NativeScript.git
				synced 2025-11-04 21:06:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			534 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			534 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// Types
 | 
						|
import { getClosestPropertyValue } from './text-base-common';
 | 
						|
import { ShadowCSSValues } from '../styling/css-shadow';
 | 
						|
 | 
						|
// Requires
 | 
						|
import { Font } from '../styling/font';
 | 
						|
import { iosAccessibilityAdjustsFontSizeProperty, iosAccessibilityMaxFontScaleProperty, iosAccessibilityMinFontScaleProperty } from '../../accessibility/accessibility-properties';
 | 
						|
import { TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, textShadowProperty, textStrokeProperty, letterSpacingProperty, lineHeightProperty, maxLinesProperty, resetSymbol } from './text-base-common';
 | 
						|
import { Color } from '../../color';
 | 
						|
import { FormattedString } from './formatted-string';
 | 
						|
import { Span } from './span';
 | 
						|
import { colorProperty, fontInternalProperty, fontScaleInternalProperty, Length } from '../styling/style-properties';
 | 
						|
import { StrokeCSSValues } from '../styling/css-stroke';
 | 
						|
import { isString, isNullOrUndefined } from '../../utils/types';
 | 
						|
import { iOSNativeHelper, layout } from '../../utils';
 | 
						|
import { Trace } from '../../trace';
 | 
						|
import { CoreTypes } from '../../core-types';
 | 
						|
 | 
						|
export * from './text-base-common';
 | 
						|
 | 
						|
const majorVersion = iOSNativeHelper.MajorVersion;
 | 
						|
 | 
						|
@NativeClass
 | 
						|
class UILabelClickHandlerImpl extends NSObject {
 | 
						|
	private _owner: WeakRef<TextBase>;
 | 
						|
 | 
						|
	public static initWithOwner(owner: WeakRef<TextBase>): UILabelClickHandlerImpl {
 | 
						|
		const handler = <UILabelClickHandlerImpl>UILabelClickHandlerImpl.new();
 | 
						|
		handler._owner = owner;
 | 
						|
 | 
						|
		return handler;
 | 
						|
	}
 | 
						|
 | 
						|
	public linkTap(tapGesture: UITapGestureRecognizer) {
 | 
						|
		const owner = this._owner?.deref();
 | 
						|
		if (owner) {
 | 
						|
			const nativeView = owner.nativeTextViewProtected instanceof UIButton ? owner.nativeTextViewProtected.titleLabel : owner.nativeTextViewProtected;
 | 
						|
 | 
						|
			// This offset along with setting paragraph style alignment will achieve perfect horizontal alignment for NSTextContainer
 | 
						|
			let offsetXMultiplier: number;
 | 
						|
			switch (owner.textAlignment) {
 | 
						|
				case 'center':
 | 
						|
					offsetXMultiplier = 0.5;
 | 
						|
					break;
 | 
						|
				case 'right':
 | 
						|
					offsetXMultiplier = 1.0;
 | 
						|
					break;
 | 
						|
				default:
 | 
						|
					offsetXMultiplier = 0.0;
 | 
						|
					break;
 | 
						|
			}
 | 
						|
			const offsetYMultiplier: number = 0.5; // Text is vertically aligned to center
 | 
						|
 | 
						|
			const layoutManager = NSLayoutManager.alloc().init();
 | 
						|
			const textContainer = NSTextContainer.alloc().initWithSize(CGSizeZero);
 | 
						|
			const textStorage = NSTextStorage.alloc().initWithAttributedString(nativeView.attributedText);
 | 
						|
 | 
						|
			layoutManager.addTextContainer(textContainer);
 | 
						|
			textStorage.addLayoutManager(layoutManager);
 | 
						|
 | 
						|
			textContainer.lineFragmentPadding = 0;
 | 
						|
 | 
						|
			if (nativeView instanceof UITextView) {
 | 
						|
				textContainer.lineBreakMode = nativeView.textContainer.lineBreakMode;
 | 
						|
				textContainer.maximumNumberOfLines = nativeView.textContainer.maximumNumberOfLines;
 | 
						|
			} else {
 | 
						|
				if (!(nativeView instanceof UITextField)) {
 | 
						|
					textContainer.lineBreakMode = nativeView.lineBreakMode;
 | 
						|
					textContainer.maximumNumberOfLines = nativeView.numberOfLines;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			const labelSize = nativeView.bounds.size;
 | 
						|
			textContainer.size = labelSize;
 | 
						|
 | 
						|
			const locationOfTouchInLabel = tapGesture.locationInView(nativeView);
 | 
						|
			const textBoundingBox = layoutManager.usedRectForTextContainer(textContainer);
 | 
						|
 | 
						|
			const textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * offsetXMultiplier - textBoundingBox.origin.x, (labelSize.height - textBoundingBox.size.height) * offsetYMultiplier - textBoundingBox.origin.y);
 | 
						|
			const locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x, locationOfTouchInLabel.y - textContainerOffset.y);
 | 
						|
 | 
						|
			// Check if tap was inside text bounding rect
 | 
						|
			if (CGRectContainsPoint(textBoundingBox, locationOfTouchInTextContainer)) {
 | 
						|
				// According to Apple docs, if no glyph is under point, the nearest glyph is returned
 | 
						|
				const glyphIndex = layoutManager.glyphIndexForPointInTextContainerFractionOfDistanceThroughGlyph(locationOfTouchInTextContainer, textContainer, null);
 | 
						|
				// In order to determine whether the tap point actually lies within the bounds
 | 
						|
				// of the glyph returned, we call the method below and test
 | 
						|
				// whether the point falls in the rectangle returned by that method
 | 
						|
				const glyphRect = layoutManager.boundingRectForGlyphRangeInTextContainer(
 | 
						|
					{
 | 
						|
						location: glyphIndex,
 | 
						|
						length: 1,
 | 
						|
					},
 | 
						|
					textContainer,
 | 
						|
				);
 | 
						|
 | 
						|
				// Ensure that an actual glyph was tapped
 | 
						|
				if (CGRectContainsPoint(glyphRect, locationOfTouchInTextContainer)) {
 | 
						|
					const indexOfCharacter = layoutManager.characterIndexForGlyphAtIndex(glyphIndex);
 | 
						|
 | 
						|
					let span: Span = null;
 | 
						|
					// Try to find the corresponding span using the spanRanges
 | 
						|
					for (let i = 0; i < owner._spanRanges.length; i++) {
 | 
						|
						const range = owner._spanRanges[i];
 | 
						|
						if (range.location <= indexOfCharacter && range.location + range.length > indexOfCharacter) {
 | 
						|
							if (owner.formattedText && owner.formattedText.spans.length > i) {
 | 
						|
								span = owner.formattedText.spans.getItem(i);
 | 
						|
							}
 | 
						|
							break;
 | 
						|
						}
 | 
						|
					}
 | 
						|
 | 
						|
					if (span && span.tappable) {
 | 
						|
						// if the span is found and tappable emit the linkTap event
 | 
						|
						span._emit(Span.linkTapEvent);
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public static ObjCExposedMethods = {
 | 
						|
		linkTap: { returns: interop.types.void, params: [interop.types.id] },
 | 
						|
	};
 | 
						|
}
 | 
						|
 | 
						|
export class TextBase extends TextBaseCommon {
 | 
						|
	public nativeViewProtected: UITextField | UITextView | UILabel | UIButton;
 | 
						|
	public _spanRanges: NSRange[];
 | 
						|
 | 
						|
	private _tappable = false;
 | 
						|
	private _linkTapHandler: UILabelClickHandlerImpl;
 | 
						|
	private _tapGestureRecognizer: UITapGestureRecognizer;
 | 
						|
 | 
						|
	get nativeTextViewProtected(): UITextField | UITextView | UILabel | UIButton {
 | 
						|
		return super.nativeTextViewProtected;
 | 
						|
	}
 | 
						|
 | 
						|
	public initNativeView(): void {
 | 
						|
		super.initNativeView();
 | 
						|
		this._setTappableState(false);
 | 
						|
	}
 | 
						|
 | 
						|
	public disposeNativeView(): void {
 | 
						|
		super.disposeNativeView();
 | 
						|
 | 
						|
		this._tappable = false;
 | 
						|
		this._linkTapHandler = null;
 | 
						|
		this._tapGestureRecognizer = null;
 | 
						|
	}
 | 
						|
 | 
						|
	_setTappableState(tappable: boolean) {
 | 
						|
		if (this._tappable !== tappable) {
 | 
						|
			const nativeTextView = this.nativeTextViewProtected;
 | 
						|
 | 
						|
			this._tappable = tappable;
 | 
						|
			if (this._tappable) {
 | 
						|
				const tapHandler = UILabelClickHandlerImpl.initWithOwner(new WeakRef(this));
 | 
						|
				// Associate handler with menuItem or it will get collected by JSC
 | 
						|
				this._linkTapHandler = tapHandler;
 | 
						|
 | 
						|
				this._tapGestureRecognizer = UITapGestureRecognizer.alloc().initWithTargetAction(this._linkTapHandler, 'linkTap');
 | 
						|
				nativeTextView.addGestureRecognizer(this._tapGestureRecognizer);
 | 
						|
			} else {
 | 
						|
				nativeTextView.removeGestureRecognizer(this._tapGestureRecognizer);
 | 
						|
 | 
						|
				this._linkTapHandler = null;
 | 
						|
				this._tapGestureRecognizer = null;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	[textProperty.getDefault](): number | symbol {
 | 
						|
		return resetSymbol;
 | 
						|
	}
 | 
						|
	[textProperty.setNative](value: string | number | symbol) {
 | 
						|
		const reset = value === resetSymbol;
 | 
						|
		if (!reset && this.formattedText) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		this._setNativeText(reset);
 | 
						|
		this._requestLayoutOnTextChanged();
 | 
						|
	}
 | 
						|
 | 
						|
	[formattedTextProperty.setNative](value: FormattedString) {
 | 
						|
		this._setNativeText();
 | 
						|
		this._setTappableState(isStringTappable(value));
 | 
						|
		textProperty.nativeValueChange(this, !value ? '' : value.toString());
 | 
						|
		this._requestLayoutOnTextChanged();
 | 
						|
	}
 | 
						|
 | 
						|
	[colorProperty.getDefault](): UIColor {
 | 
						|
		const nativeView = this.nativeTextViewProtected;
 | 
						|
		if (nativeView instanceof UIButton) {
 | 
						|
			return nativeView.titleColorForState(UIControlState.Normal);
 | 
						|
		} else {
 | 
						|
			return nativeView.textColor;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	[colorProperty.setNative](value: Color | UIColor) {
 | 
						|
		const color = value instanceof Color ? value.ios : value;
 | 
						|
		this._setColor(color);
 | 
						|
	}
 | 
						|
 | 
						|
	[fontInternalProperty.getDefault](): UIFont {
 | 
						|
		let nativeView = this.nativeTextViewProtected;
 | 
						|
		nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
 | 
						|
 | 
						|
		return nativeView.font;
 | 
						|
	}
 | 
						|
	[fontInternalProperty.setNative](value: Font | UIFont) {
 | 
						|
		if (!(value instanceof Font) || !this.formattedText) {
 | 
						|
			let nativeView = this.nativeTextViewProtected;
 | 
						|
			nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
 | 
						|
			nativeView.font = value instanceof Font ? value.getUIFont(nativeView.font) : value;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	[fontScaleInternalProperty.setNative](value: number) {
 | 
						|
		const nativeView = this.nativeTextViewProtected instanceof UIButton ? this.nativeTextViewProtected.titleLabel : this.nativeTextViewProtected;
 | 
						|
		const font = this.style.fontInternal || Font.default.withFontSize(nativeView.font.pointSize);
 | 
						|
		const finalValue = adjustMinMaxFontScale(value, this);
 | 
						|
 | 
						|
		// Request layout on font scale as it's not done automatically
 | 
						|
		if (font.fontScale !== finalValue) {
 | 
						|
			this.style.fontInternal = font.withFontScale(finalValue);
 | 
						|
			this.requestLayout();
 | 
						|
		} else {
 | 
						|
			if (!this.style.fontInternal) {
 | 
						|
				this.style.fontInternal = font;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	[iosAccessibilityAdjustsFontSizeProperty.setNative](value: boolean) {
 | 
						|
		this[fontScaleInternalProperty.setNative](this.style.fontScaleInternal);
 | 
						|
	}
 | 
						|
 | 
						|
	[iosAccessibilityMinFontScaleProperty.setNative](value: number) {
 | 
						|
		this[fontScaleInternalProperty.setNative](this.style.fontScaleInternal);
 | 
						|
	}
 | 
						|
 | 
						|
	[iosAccessibilityMaxFontScaleProperty.setNative](value: number) {
 | 
						|
		this[fontScaleInternalProperty.setNative](this.style.fontScaleInternal);
 | 
						|
	}
 | 
						|
 | 
						|
	[textAlignmentProperty.setNative](value: CoreTypes.TextAlignmentType) {
 | 
						|
		const nativeView = <UITextField | UITextView | UILabel>this.nativeTextViewProtected;
 | 
						|
		switch (value) {
 | 
						|
			case 'initial':
 | 
						|
			case 'left':
 | 
						|
				nativeView.textAlignment = NSTextAlignment.Left;
 | 
						|
				break;
 | 
						|
			case 'center':
 | 
						|
				nativeView.textAlignment = NSTextAlignment.Center;
 | 
						|
				break;
 | 
						|
			case 'right':
 | 
						|
				nativeView.textAlignment = NSTextAlignment.Right;
 | 
						|
				break;
 | 
						|
			case 'justify':
 | 
						|
				nativeView.textAlignment = NSTextAlignment.Justified;
 | 
						|
				break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	[textDecorationProperty.setNative](value: CoreTypes.TextDecorationType) {
 | 
						|
		this._setNativeText();
 | 
						|
	}
 | 
						|
 | 
						|
	[textTransformProperty.setNative](value: CoreTypes.TextTransformType) {
 | 
						|
		this._setNativeText();
 | 
						|
	}
 | 
						|
 | 
						|
	[textStrokeProperty.setNative](value: StrokeCSSValues) {
 | 
						|
		this._setNativeText();
 | 
						|
	}
 | 
						|
 | 
						|
	[letterSpacingProperty.setNative](value: number) {
 | 
						|
		this._setNativeText();
 | 
						|
	}
 | 
						|
 | 
						|
	[lineHeightProperty.setNative](value: number) {
 | 
						|
		this._setNativeText();
 | 
						|
	}
 | 
						|
 | 
						|
	[textShadowProperty.setNative](value: ShadowCSSValues) {
 | 
						|
		this._setShadow(value);
 | 
						|
	}
 | 
						|
 | 
						|
	[maxLinesProperty.setNative](value: CoreTypes.MaxLinesType) {
 | 
						|
		const nativeTextViewProtected = this.nativeTextViewProtected;
 | 
						|
		const numberOfLines = this.whiteSpace !== CoreTypes.WhiteSpace.nowrap ? value : 1;
 | 
						|
		if (nativeTextViewProtected instanceof UITextView) {
 | 
						|
			nativeTextViewProtected.textContainer.maximumNumberOfLines = numberOfLines;
 | 
						|
 | 
						|
			if (value !== 0) {
 | 
						|
				nativeTextViewProtected.textContainer.lineBreakMode = NSLineBreakMode.ByTruncatingTail;
 | 
						|
			} else {
 | 
						|
				nativeTextViewProtected.textContainer.lineBreakMode = NSLineBreakMode.ByWordWrapping;
 | 
						|
			}
 | 
						|
		} else if (nativeTextViewProtected instanceof UILabel) {
 | 
						|
			nativeTextViewProtected.numberOfLines = numberOfLines;
 | 
						|
			nativeTextViewProtected.lineBreakMode = NSLineBreakMode.ByTruncatingTail;
 | 
						|
		} else if (nativeTextViewProtected instanceof UIButton) {
 | 
						|
			nativeTextViewProtected.titleLabel.numberOfLines = numberOfLines;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	_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;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	_animationWrap(fn: () => void) {
 | 
						|
		const shouldAnimate = this.iosTextAnimation === 'inherit' ? TextBase.iosTextAnimationFallback : this.iosTextAnimation;
 | 
						|
		if (shouldAnimate) {
 | 
						|
			fn();
 | 
						|
		} else {
 | 
						|
			UIView.performWithoutAnimation(fn);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	_setNativeText(reset = false): void {
 | 
						|
		this._animationWrap(() => {
 | 
						|
			if (reset) {
 | 
						|
				const nativeView = this.nativeTextViewProtected;
 | 
						|
				if (nativeView instanceof UIButton) {
 | 
						|
					// Clear attributedText or title won't be affected.
 | 
						|
					nativeView.setAttributedTitleForState(null, UIControlState.Normal);
 | 
						|
					nativeView.setTitleForState(null, UIControlState.Normal);
 | 
						|
				} else {
 | 
						|
					// Clear attributedText or text won't be affected.
 | 
						|
					nativeView.attributedText = null;
 | 
						|
					nativeView.text = null;
 | 
						|
				}
 | 
						|
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			const letterSpacing = this.style.letterSpacing ? this.style.letterSpacing : 0;
 | 
						|
			const lineHeight = this.style.lineHeight ? this.style.lineHeight : 0;
 | 
						|
			if (this.formattedText) {
 | 
						|
				this.nativeTextViewProtected.nativeScriptSetFormattedTextDecorationAndTransformLetterSpacingLineHeight(this.getFormattedStringDetails(this.formattedText) as any, letterSpacing, lineHeight);
 | 
						|
			} else {
 | 
						|
				// console.log('setTextDecorationAndTransform...')
 | 
						|
				const text = getTransformedText(isNullOrUndefined(this.text) ? '' : `${this.text}`, this.textTransform);
 | 
						|
				this.nativeTextViewProtected.nativeScriptSetTextDecorationAndTransformTextDecorationLetterSpacingLineHeight(text, this.style.textDecoration || '', letterSpacing, lineHeight);
 | 
						|
 | 
						|
				if (!this.style?.color && majorVersion >= 13 && UIColor.labelColor) {
 | 
						|
					this._setColor(UIColor.labelColor);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (this.style?.textStroke) {
 | 
						|
				this.nativeTextViewProtected.nativeScriptSetFormattedTextStrokeColor(Length.toDevicePixels(this.style.textStroke.width, 0), this.style.textStroke.color.ios);
 | 
						|
			}
 | 
						|
		});
 | 
						|
	}
 | 
						|
 | 
						|
	createFormattedTextNative(value: FormattedString) {
 | 
						|
		return NativeScriptUtils.createMutableStringWithDetails(<any>this.getFormattedStringDetails(value));
 | 
						|
	}
 | 
						|
 | 
						|
	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);
 | 
						|
				}
 | 
						|
 | 
						|
				details.spans.push(this.createMutableStringDetails(span, spanText, spanStart));
 | 
						|
				this._spanRanges.push({
 | 
						|
					location: spanStart,
 | 
						|
					length: spanText.length,
 | 
						|
				});
 | 
						|
				spanStart += spanText.length;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return details;
 | 
						|
	}
 | 
						|
 | 
						|
	createMutableStringDetails(span: Span, text: string, index?: number): any {
 | 
						|
		const fontScale = adjustMinMaxFontScale(span.style.fontScaleInternal, span);
 | 
						|
		const font = new Font(span.style.fontFamily, span.style.fontSize, span.style.fontStyle, span.style.fontWeight, fontScale, span.style.fontVariationSettings);
 | 
						|
		const iosFont = font.getUIFont(this.nativeTextViewProtected.font);
 | 
						|
		// Use span or formatted string color
 | 
						|
		const backgroundColor = span.style.backgroundColor || span.parent.backgroundColor;
 | 
						|
 | 
						|
		return {
 | 
						|
			text,
 | 
						|
			iosFont,
 | 
						|
			color: span.color ? span.color.ios : null,
 | 
						|
			backgroundColor: backgroundColor ? backgroundColor.ios : null,
 | 
						|
			textDecoration: getClosestPropertyValue(textDecorationProperty, span),
 | 
						|
			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;
 | 
						|
		}
 | 
						|
 | 
						|
		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;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	_setShadow(value: ShadowCSSValues): void {
 | 
						|
		const layer: CALayer = this.nativeTextViewProtected.layer;
 | 
						|
 | 
						|
		if (isNullOrUndefined(value)) {
 | 
						|
			// clear the text shadow
 | 
						|
			layer.shadowOpacity = 0;
 | 
						|
			layer.shadowRadius = 0;
 | 
						|
			layer.shadowColor = UIColor.clearColor;
 | 
						|
			layer.shadowOffset = CGSizeMake(0, 0);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		// shadow opacity is handled on the shadow's color instance
 | 
						|
		layer.shadowOpacity = value.color?.a ? value.color.a / 255 : 1;
 | 
						|
		layer.shadowColor = value.color.ios.CGColor;
 | 
						|
		layer.shadowRadius = layout.toDeviceIndependentPixels(Length.toDevicePixels(value.blurRadius, 0));
 | 
						|
 | 
						|
		// prettier-ignore
 | 
						|
		layer.shadowOffset = CGSizeMake(
 | 
						|
			layout.toDeviceIndependentPixels(Length.toDevicePixels(value.offsetX, 0)),
 | 
						|
			layout.toDeviceIndependentPixels(Length.toDevicePixels(value.offsetY, 0))
 | 
						|
		);
 | 
						|
 | 
						|
		layer.masksToBounds = false;
 | 
						|
 | 
						|
		// NOTE: generally should not need shouldRasterize
 | 
						|
		// however for various detailed animation work which involves text-shadow applicable layers, we may want to give users the control of enabling this with text-shadow
 | 
						|
		// if (!(this.nativeTextViewProtected instanceof UITextView)) {
 | 
						|
		//   layer.shouldRasterize = true;
 | 
						|
		// }
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
export function getTransformedText(text: string, textTransform: CoreTypes.TextTransformType): string {
 | 
						|
	if (!text || !isString(text)) {
 | 
						|
		return '';
 | 
						|
	}
 | 
						|
 | 
						|
	switch (textTransform) {
 | 
						|
		case 'uppercase':
 | 
						|
			return NSStringFromNSAttributedString(text).uppercaseString;
 | 
						|
		case 'lowercase':
 | 
						|
			return NSStringFromNSAttributedString(text).lowercaseString;
 | 
						|
		case 'capitalize':
 | 
						|
			return NSStringFromNSAttributedString(text).capitalizedString;
 | 
						|
		default:
 | 
						|
			return text;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function NSStringFromNSAttributedString(source: NSAttributedString | string): NSString {
 | 
						|
	return NSString.stringWithString((source instanceof NSAttributedString && source.string) || <string>source);
 | 
						|
}
 | 
						|
 | 
						|
function isStringTappable(formattedString: FormattedString) {
 | 
						|
	if (!formattedString) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	for (let i = 0, length = formattedString.spans.length; i < length; i++) {
 | 
						|
		const span = formattedString.spans.getItem(i);
 | 
						|
		if (span.tappable) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
function adjustMinMaxFontScale(value: number, view: TextBase | Span) {
 | 
						|
	let finalValue;
 | 
						|
	if (view.iosAccessibilityAdjustsFontSize) {
 | 
						|
		finalValue = value;
 | 
						|
 | 
						|
		if (view.iosAccessibilityMinFontScale && view.iosAccessibilityMinFontScale > value) {
 | 
						|
			finalValue = view.iosAccessibilityMinFontScale;
 | 
						|
		}
 | 
						|
		if (view.iosAccessibilityMaxFontScale && view.iosAccessibilityMaxFontScale < value) {
 | 
						|
			finalValue = view.iosAccessibilityMaxFontScale;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		finalValue = 1.0;
 | 
						|
	}
 | 
						|
	return finalValue;
 | 
						|
}
 |