Add NSAttributedString text measurement and drawing implementation - #947
Conversation
|
👀 Human Input Needed → Pair Review Briefing |
🤖 Augment PR SummarySummary: This PR implements the NSAttributedString-backed text measurement + drawing path used by Changes:
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 👎 |
|
|
||
| extension NSAttributedString { | ||
| package func kitFont() -> NSObject? { | ||
| attribute(.kitFont, at: 0, effectiveRange: nil) as? NSObject |
There was a problem hiding this comment.
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
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| } | ||
|
|
||
| if widthIsFlexible { | ||
| width = requestedSize.width |
There was a problem hiding this comment.
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
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| wantsNumberOfLineFragments: Bool, | ||
| context: TextDrawingContext | ||
| ) -> Metrics { | ||
| for (cachedSize, cachedMetrics) in entries { |
There was a problem hiding this comment.
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 👎
Codecov Report❌ Patch coverage is 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. 🚀 New features to boost your workflow:
|
Summary
Implements the
NSAttributedString-based text measurement and drawing path used byText, plus the supporting CoreText/UIFoundation SPI shims and a test suite covering the new behavior.Changes
Text+StringDrawingContext.swift(new):ResolvedStyledText.StringDrawingimplementation, including theNSAttributedStringmeasure 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.CTFontSPI.handNSStringDrawing_Private.hdeclarations required by the above.Tests
NSAttributedStringTests(11 tests across 4 suites):MaxFontMetricsTests— font run aggregation, missing font attributes, and clipping outsets gated onSemantics.TextRenderingMetrics.MetricsTests— single line, wrapped lines, line limit, layout margins, and baseline rounding to the pixel length.TextSpacingTests— standard sizing under bothSemantics.TextSpacingUIKit0059v2states, 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/Doublecomparisons go throughNumerics.isApproximatelyEqual.