protected override Size MeasureOverride(Size size) { var hasSameDesiredSize = !_measureInvalidated && _previousAvailableSize != null && _previousDesiredSize.Width == size.Width && _previousDesiredSize.Height == size.Height; var isSingleLineNarrower = !_measureInvalidated && _previousAvailableSize != null && _previousDesiredSize.Width <= size.Width && _previousDesiredSize.Height == size.Height; if (hasSameDesiredSize || isSingleLineNarrower) { return(_previousDesiredSize); } else { _previousAvailableSize = size; _measureInvalidated = false; UpdateTypography(); var horizontalPadding = Padding.Left + Padding.Right; var verticalPadding = Padding.Top + Padding.Bottom; // available size considering padding size.Width -= horizontalPadding; size.Height -= verticalPadding; var result = LayoutTypography(size); if (result.Height == 0) // this can happen when Text is null or empty { // This measures the height correctly, even if the Text is null or empty // This matches Windows where empty TextBlocks still have a height (especially useful when measuring ListView items with no DataContext) var font = NSFontHelper.TryGetFont((float)FontSize * 2, FontWeight, FontStyle, FontFamily); var str = new NSAttributedString(Text, font); var rect = str.BoundingRectWithSize(size, NSStringDrawingOptions.UsesDeviceMetrics); result = new Size(rect.Width, rect.Height); } result.Width += horizontalPadding; result.Height += verticalPadding; return(_previousDesiredSize = new CGSize(Math.Ceiling(result.Width), Math.Ceiling(result.Height))); } }
protected override Size MeasureOverride(Size size) { // `size` is used to compare with the previous one `_previousDesiredSize` // We need to apply `Math.Ceiling` to compare them correctly var isSameOrNarrower = !_measureInvalidated && _previousAvailableSize != null && _previousDesiredSize.Width <= Math.Ceiling(size.Width) && _previousDesiredSize.Height == Math.Ceiling(size.Height); if (isSameOrNarrower) { return(_previousDesiredSize); } else { _previousAvailableSize = size; _measureInvalidated = false; UpdateTypography(); var padding = Padding; // available size considering padding size = size.Subtract(padding); var result = LayoutTypography(size); if (result.Height == 0) // this can happen when Text is null or empty { // This measures the height correctly, even if the Text is null or empty // This matches Windows where empty TextBlocks still have a height (especially useful when measuring ListView items with no DataContext) var font = NSFontHelper.TryGetFont((float)FontSize * 2, FontWeight, FontStyle, FontFamily); using var str = new NSAttributedString(Text, font); var rect = str.BoundingRectWithSize(size, NSStringDrawingOptions.UsesDeviceMetrics); result = new Size(rect.Width, rect.Height); } result = result.Add(padding); return(_previousDesiredSize = new Size(Math.Ceiling(result.Width), Math.Ceiling(result.Height))); } }
private NSStringAttributes GetAttributes() { var attributes = new NSStringAttributes(); var font = NSFontHelper.TryGetFont((float)FontSize, FontWeight, FontStyle, FontFamily); attributes.Font = font; attributes.ForegroundColor = Brush.GetColorWithOpacity(Foreground, Colors.Transparent).Value; if (TextDecorations != TextDecorations.None) { attributes.UnderlineStyle = (int)((TextDecorations & TextDecorations.Underline) == TextDecorations.Underline ? NSUnderlineStyle.Single : NSUnderlineStyle.None); attributes.StrikethroughStyle = (int)((TextDecorations & TextDecorations.Strikethrough) == TextDecorations.Strikethrough ? NSUnderlineStyle.Single : NSUnderlineStyle.None); } var paragraphStyle = new NSMutableParagraphStyle() { MinimumLineHeight = (nfloat)LineHeight, Alignment = TextAlignment.ToNativeTextAlignment(), LineBreakMode = GetLineBreakMode(), }; // For unknown reasons, the LineBreakMode must be set to WordWrap // when applied to a NSTextStorage for text to wrap. if (UseLayoutManager) { paragraphStyle.LineBreakMode = NSLineBreakMode.ByWordWrapping; } if (LineStackingStrategy != LineStackingStrategy.MaxHeight) { paragraphStyle.MaximumLineHeight = (nfloat)LineHeight; } attributes.ParagraphStyle = paragraphStyle; if (LineHeight != 0 && font != null) { // iOS puts text at the bottom of the line box, whereas Windows puts it at the top. // Empirically this offset gives similar positioning to Windows. // Note: Descender is typically a negative value. var verticalOffset = LineHeight - font.XHeight /* MACOS TODO XHeight ? */ + font.Descender; // Because we're trying to move the text up (toward the top of the line box), // we only set BaselineOffset to a positive value. // A negative value indicates that the the text is already bottom-aligned. attributes.BaselineOffset = Math.Max(0, (float)verticalOffset); } if (CharacterSpacing != 0) { //CharacterSpacing is in 1/1000 of an em, iOS KerningAdjustment is in points. 1 em = 12 points attributes.KerningAdjustment = (CharacterSpacing / 1000f) * 12; } return(attributes); }