Skip to content

Add NSAttributedString text measurement and drawing implementation - #947

Merged
Kyle-Ye merged 6 commits into
mainfrom
feature/text_drawing
Jul 25, 2026
Merged

Add NSAttributedString text measurement and drawing implementation#947
Kyle-Ye merged 6 commits into
mainfrom
feature/text_drawing

Conversation

@Kyle-Ye

@Kyle-Ye Kyle-Ye commented Jul 25, 2026

Copy link
Copy Markdown
Member

Summary

Implements the NSAttributedString-based text measurement and drawing path used by Text, plus the supporting CoreText/UIFoundation SPI shims and a test suite covering the new behavior.

Changes

  • Text+StringDrawingContext.swift (new): ResolvedStyledText.StringDrawing implementation, including the NSAttributedString measure path (maxFontMetrics, Metrics, layout margin and pixel-length rounding).
  • Text+Spacing.swift (new): Spacing.textSpacing(maxFontMetrics:idealMetrics:layoutProperties:), covering standard / uniform line height sizing and horizontal / vertical writing modes.
  • CoreFont.swift (new): CoreText font helpers used by the measurement path.
  • Text+Measure.swift: removed, superseded by the string-drawing context.
  • SPI shims: added CTFontSPI.h and NSStringDrawing_Private.h declarations required by the above.

Tests

NSAttributedStringTests (11 tests across 4 suites):

  • MaxFontMetricsTests — font run aggregation, missing font attributes, and clipping outsets gated on Semantics.TextRenderingMetrics.
  • MetricsTests — single line, wrapped lines, line limit, layout margins, and baseline rounding to the pixel length.
  • TextSpacingTests — standard sizing under both Semantics.TextSpacingUIKit0059v2 states, uniform line height half-leading split, and vertical writing mode edge mapping.

Assertions use absolute reference values measured from Helvetica 16 / 24 rather than re-deriving the implementation's math, and all CGFloat / Double comparisons go through Numerics.isApproximatelyEqual.

@github-actions github-actions Bot added area: tests Test suites, UI tests, fixtures, snapshots, and test support code. area: text Text, labels, line style, and text rendering APIs. type: feature New API, behavior, platform support, or user-facing capability. labels Jul 25, 2026
@augmentcode

augmentcode Bot commented Jul 25, 2026

Copy link
Copy Markdown

PR Risk Analyzer Agent🛡️

👀 Human Input Needed
A pair-review briefing is ready for you.

→ Pair Review Briefing

@augmentcode

augmentcode Bot commented Jul 25, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: This PR implements the NSAttributedString-backed text measurement + drawing path used by Text, including spacing computation and platform shims needed for CoreText/UIFoundation internals.

Changes:

  • Added Text+StringDrawingContext.swift with a full ResolvedStyledText.StringDrawing implementation (measurement cache, baseline/pixel rounding, layout margins, drawing via NSStringDrawingContext).
  • Added Text+Spacing.swift to compute text-to-text / baseline / edge spacing for horizontal and vertical writing modes, including uniform line-height behavior.
  • Added CoreFont.swift with CoreText font helpers used by the measurement path (including limited-line height calculation).
  • Removed the older Text+Measure.swift WIP implementation in favor of the new string-drawing context.
  • Extended SPI shims (CTFontSPI.h, NSStringDrawing_Private.h) to access required private CoreText/UIFoundation properties.
  • Updated Text+View.swift to construct styled text via ResolvedStyledText.styledText(...).
  • Expanded NSAttributedStringTests with suites covering max font metrics, metrics rounding/margins, and text spacing semantics across feature flags.

Technical Notes: Measurement/drawing relies on CoreText/UIFoundation availability on Darwin and uses cached layouts + pixel-length rounding for stable baseline/height behavior.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.


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.

}

if widthIsFlexible {
width = requestedSize.width

@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.

When widthIsFlexible is true, width is set to requestedSize.width even if it’s .infinity (e.g. sizeThatFits uses .infinity for unspecified dimensions), which can leak an infinite width into Metrics.size. Consider only forcing the width when the requested width is finite, so flexible justification doesn’t produce an invalid/infinite size in unconstrained measurement.

Severity: medium

Fix This in Augment

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

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deep Code Review Agent🐛

Review completed with 1 suggestions.

Fix in Cosmos

wantsNumberOfLineFragments: Bool,
context: TextDrawingContext
) -> Metrics {
for (cachedSize, cachedMetrics) in entries {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

MetricsCache reuses entries solely by requested size, but each cached Metrics already has the caller's layoutMargins baked into size and baselines. A later metrics(in:layoutMargins:) call with different margins can hit this entry and return dimensions or line counts for the wrong content rectangle, especially because the measurement width subtracts the margins before layout.

Severity: medium


🤖 Was this useful? React with 👍 or 👎

@Kyle-Ye
Kyle-Ye merged commit 7b7eb56 into main Jul 25, 2026
7 of 8 checks passed
@Kyle-Ye
Kyle-Ye deleted the feature/text_drawing branch July 25, 2026 17:32
@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 41.97002% with 271 lines in your changes missing coverage. Please review.
✅ Project coverage is 26.50%. Comparing base (cc9f9b0) to head (c43b9f0).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ore/View/Text/Text/Text+StringDrawingContext.swift 36.08% 248 Missing ⚠️
...rces/OpenSwiftUICore/View/Text/Font/CoreFont.swift 0.00% 22 Missing ⚠️
...ces/OpenSwiftUICore/View/Text/Text/Text+View.swift 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #947      +/-   ##
==========================================
+ Coverage   26.27%   26.50%   +0.23%     
==========================================
  Files         726      728       +2     
  Lines       51669    52108     +439     
==========================================
+ Hits        13575    13812     +237     
- Misses      38094    38296     +202     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: tests Test suites, UI tests, fixtures, snapshots, and test support code. area: text Text, labels, line style, and text rendering APIs. type: feature New API, behavior, platform support, or user-facing capability.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant