Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions Sources/OpenSwiftUICore/View/Text/Font/CoreFont.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// CoreFont.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete

package import Foundation
import OpenSwiftUI_SPI

#if canImport(CoreText)
package import CoreText
package import CoreText_Private
#endif
package typealias CoreFont = CTFont

extension NSAttributedString {
package func kitFont() -> NSObject? {
attribute(.kitFont, at: 0, effectiveRange: nil) as? NSObject

@augmentcode augmentcode Bot Jul 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kitFont() calls attribute(_:at:0,...) unconditionally, which will trap for empty attributed strings (length == 0). Consider guarding on length > 0 (or otherwise handling empty strings) since kitFont()/limitedFontHeight(by:) are package and could be called on empty storage.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

}

package func limitedFontHeight(by lineLimit: Int) -> CGFloat? {
#if canImport(CoreText)
guard let font = kitFont() else {
return nil
}
return CoreFont.limitedHeight(
by: lineLimit,
lineHeight: CoreFontGetLineHeight(.default, font),
leading: CoreFontGetLeading(.default, font)
)
#else
return nil
#endif
}
}

extension CTFont {
package static func make(platformFont: AnyObject) -> Self? {
#if canImport(CoreText)
guard CFGetTypeID(platformFont) == CTFontGetTypeID() else {
return nil
}
let coreFont: Self = platformFont as! Self
return coreFont
#else
_openSwiftUIPlatformUnimplementedWarning()
return nil
#endif
}

/// Calculates the height occupied by a limited number of lines.
///
/// Each line contributes `lineHeight`, while every line after the first
/// contributes an additional `leading`:
///
/// height = lineHeight * lineLimit + leading * (lineLimit - 1)
///
/// For example, three lines with a line height of `20` and leading of `2`
/// occupy `64` points: `(20 * 3) + (2 * 2)`.
///
/// - Parameters:
/// - lineLimit: The number of lines to include.
/// - lineHeight: The height of each line.
/// - leading: The spacing between adjacent lines.
/// - Returns: The calculated height, or zero when `lineLimit` is less than
/// one.
package static func limitedHeight(
by lineLimit: Int,
lineHeight: CGFloat,
leading: CGFloat
) -> CGFloat {
guard lineLimit >= 1 else {
return 0
}
return CGFloat(lineLimit) * lineHeight + CGFloat(lineLimit - 1) * leading
}
}
122 changes: 0 additions & 122 deletions Sources/OpenSwiftUICore/View/Text/Text/Text+Measure.swift

This file was deleted.

75 changes: 75 additions & 0 deletions Sources/OpenSwiftUICore/View/Text/Text/Text+Spacing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// Text+Spacing.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete

import Foundation

// MARK: - Spacing + Text

extension Spacing {
static func textSpacing(
maxFontMetrics: NSAttributedString.EncodedFontMetrics,
idealMetrics: NSAttributedString.Metrics,
layoutProperties: TextLayoutProperties
) -> Spacing {
let (
firstEdge,
secondEdge,
firstEdgeCategory,
secondEdgeCategory,
firstBaselineCategory,
secondBaselineCategory
): (AbsoluteEdge, AbsoluteEdge, Category, Category, Category, Category) = {
switch layoutProperties.writingMode.storage {
case .horizontalTopToBottom:
(.top, .bottom, .edgeAboveText, .edgeBelowText, .textBaseline, .textBaseline)
case .verticalRightToLeft:
(.right, .left, .edgeRightText, .edgeLeftText, .rightTextBaseline, .leftTextBaseline)
}
}()

let fontLineHeight = maxFontMetrics.ascender - maxFontMetrics.descender
var defaultTextSpacing = fontLineHeight * 0.1
defaultTextSpacing.round(
.up,
toMultipleOf: Semantics.TextSpacingUIKit0059v2.isEnabled ? layoutProperties.pixelLength : 4.0
)

// Uniform line height moves the font leading into equal half-leading
// contributions above and below the baseline.
let uniformLeading = layoutProperties.textSizing == .uniformLineHeight
? maxFontMetrics.leading
: 0
let halfUniformLeading = uniformLeading * 0.5
let textMetrics = TextMetrics(
ascend: maxFontMetrics.ascender + halfUniformLeading,
descend: halfUniformLeading - maxFontMetrics.descender,
leading: maxFontMetrics.leading - uniformLeading,
pixelLength: layoutProperties.pixelLength
)
let defaultLineSpacing = fontLineHeight + defaultTextSpacing

return Spacing(minima: [
Key(category: .textToText, edge: firstEdge): .bottomTextMetrics(textMetrics),
Key(category: .textToText, edge: secondEdge): .topTextMetrics(textMetrics),
Key(category: secondBaselineCategory, edge: secondEdge): .distance(
idealMetrics.lastBaseline - idealMetrics.size.height
),
Key(category: firstBaselineCategory, edge: firstEdge): .distance(
-idealMetrics.firstBaseline
),
Key(category: firstEdgeCategory, edge: firstEdge): .distance(
defaultLineSpacing - textMetrics.ascend
),
Key(category: secondEdgeCategory, edge: secondEdge): .distance(
max(
defaultLineSpacing - maxFontMetrics.capHeight,
defaultTextSpacing + textMetrics.descend
)
),
])
}
}
Loading
Loading