Fix RTL drawing

This commit is contained in:
Peter 2019-08-13 20:10:56 +03:00
parent 5278c52729
commit dd64c3dec1

View File

@ -110,7 +110,7 @@ public final class TextNodeLayoutArguments {
} }
public final class TextNodeLayout: NSObject { public final class TextNodeLayout: NSObject {
fileprivate let attributedString: NSAttributedString? public let attributedString: NSAttributedString?
fileprivate let maximumNumberOfLines: Int fileprivate let maximumNumberOfLines: Int
fileprivate let truncationType: CTLineTruncationType fileprivate let truncationType: CTLineTruncationType
fileprivate let backgroundColor: UIColor? fileprivate let backgroundColor: UIColor?
@ -210,9 +210,82 @@ public final class TextNodeLayout: NSObject {
} }
} }
public func attributesAtPoint(_ point: CGPoint) -> (Int, [NSAttributedString.Key: Any])? { public func attributesAtPoint(_ point: CGPoint, orNearest: Bool) -> (Int, [NSAttributedString.Key: Any])? {
if let attributedString = self.attributedString { if let attributedString = self.attributedString {
let transformedPoint = CGPoint(x: point.x - self.insets.left, y: point.y - self.insets.top) let transformedPoint = CGPoint(x: point.x - self.insets.left, y: point.y - self.insets.top)
if orNearest {
var lineIndex = -1
var closestLine: (Int, CGRect, CGFloat)?
for line in self.lines {
lineIndex += 1
var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size)
switch self.alignment {
case .center:
lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0)
case .natural:
if line.isRTL {
lineFrame.origin.x = self.size.width - lineFrame.size.width
}
lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout)
default:
break
}
let currentDistance = (lineFrame.center.y - point.y) * (lineFrame.center.y - point.y)
if let current = closestLine {
if current.2 > currentDistance {
closestLine = (lineIndex, lineFrame, currentDistance)
}
} else {
closestLine = (lineIndex, lineFrame, currentDistance)
}
}
if let (index, lineFrame, _) = closestLine {
let line = self.lines[index]
let lineRange = CTLineGetStringRange(line.line)
var index: Int
if transformedPoint.x <= lineFrame.minX {
index = lineRange.location
} else if transformedPoint.x >= lineFrame.maxX {
index = lineRange.location + lineRange.length
} else {
index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: floor(lineFrame.height / 2.0)))
if index != 0 {
var glyphStart: CGFloat = 0.0
CTLineGetOffsetForStringIndex(line.line, index, &glyphStart)
if transformedPoint.x < glyphStart {
var closestLowerIndex: Int?
let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray
if glyphRuns.count != 0 {
for run in glyphRuns {
let run = run as! CTRun
let glyphCount = CTRunGetGlyphCount(run)
for i in 0 ..< glyphCount {
var glyphIndex: CFIndex = 0
CTRunGetStringIndices(run, CFRangeMake(i, 1), &glyphIndex)
if glyphIndex < index {
if let closestLowerIndexValue = closestLowerIndex {
if closestLowerIndexValue < glyphIndex {
closestLowerIndex = glyphIndex
}
} else {
closestLowerIndex = glyphIndex
}
}
}
}
}
if let closestLowerIndex = closestLowerIndex {
index = closestLowerIndex
}
}
}
}
return (index, [:])
}
}
var lineIndex = -1 var lineIndex = -1
for line in self.lines { for line in self.lines {
lineIndex += 1 lineIndex += 1
@ -231,12 +304,58 @@ public final class TextNodeLayout: NSObject {
if lineFrame.contains(transformedPoint) { if lineFrame.contains(transformedPoint) {
var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY))
if index == attributedString.length { if index == attributedString.length {
index -= 1 var closestLowerIndex: Int?
let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray
if glyphRuns.count != 0 {
for run in glyphRuns {
let run = run as! CTRun
let glyphCount = CTRunGetGlyphCount(run)
for i in 0 ..< glyphCount {
var glyphIndex: CFIndex = 0
CTRunGetStringIndices(run, CFRangeMake(i, 1), &glyphIndex)
if glyphIndex < index {
if let closestLowerIndexValue = closestLowerIndex {
if closestLowerIndexValue < glyphIndex {
closestLowerIndex = glyphIndex
}
} else {
closestLowerIndex = glyphIndex
}
}
}
}
}
if let closestLowerIndex = closestLowerIndex {
index = closestLowerIndex
}
} else if index != 0 { } else if index != 0 {
var glyphStart: CGFloat = 0.0 var glyphStart: CGFloat = 0.0
CTLineGetOffsetForStringIndex(line.line, index, &glyphStart) CTLineGetOffsetForStringIndex(line.line, index, &glyphStart)
if transformedPoint.x < glyphStart { if transformedPoint.x < glyphStart {
index -= 1 var closestLowerIndex: Int?
let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray
if glyphRuns.count != 0 {
for run in glyphRuns {
let run = run as! CTRun
let glyphCount = CTRunGetGlyphCount(run)
for i in 0 ..< glyphCount {
var glyphIndex: CFIndex = 0
CTRunGetStringIndices(run, CFRangeMake(i, 1), &glyphIndex)
if glyphIndex < index {
if let closestLowerIndexValue = closestLowerIndex {
if closestLowerIndexValue < glyphIndex {
closestLowerIndex = glyphIndex
}
} else {
closestLowerIndex = glyphIndex
}
}
}
}
}
if let closestLowerIndex = closestLowerIndex {
index = closestLowerIndex
}
} }
} }
if index >= 0 && index < attributedString.length { if index >= 0 && index < attributedString.length {
@ -263,12 +382,58 @@ public final class TextNodeLayout: NSObject {
if lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.size.height).insetBy(dx: -3.0, dy: -3.0).contains(transformedPoint) { if lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.size.height).insetBy(dx: -3.0, dy: -3.0).contains(transformedPoint) {
var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY))
if index == attributedString.length { if index == attributedString.length {
index -= 1 var closestLowerIndex: Int?
let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray
if glyphRuns.count != 0 {
for run in glyphRuns {
let run = run as! CTRun
let glyphCount = CTRunGetGlyphCount(run)
for i in 0 ..< glyphCount {
var glyphIndex: CFIndex = 0
CTRunGetStringIndices(run, CFRangeMake(i, 1), &glyphIndex)
if glyphIndex < index {
if let closestLowerIndexValue = closestLowerIndex {
if closestLowerIndexValue < glyphIndex {
closestLowerIndex = glyphIndex
}
} else {
closestLowerIndex = glyphIndex
}
}
}
}
}
if let closestLowerIndex = closestLowerIndex {
index = closestLowerIndex
}
} else if index != 0 { } else if index != 0 {
var glyphStart: CGFloat = 0.0 var glyphStart: CGFloat = 0.0
CTLineGetOffsetForStringIndex(line.line, index, &glyphStart) CTLineGetOffsetForStringIndex(line.line, index, &glyphStart)
if transformedPoint.x < glyphStart { if transformedPoint.x < glyphStart {
index -= 1 var closestLowerIndex: Int?
let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray
if glyphRuns.count != 0 {
for run in glyphRuns {
let run = run as! CTRun
let glyphCount = CTRunGetGlyphCount(run)
for i in 0 ..< glyphCount {
var glyphIndex: CFIndex = 0
CTRunGetStringIndices(run, CFRangeMake(i, 1), &glyphIndex)
if glyphIndex < index {
if let closestLowerIndexValue = closestLowerIndex {
if closestLowerIndexValue < glyphIndex {
closestLowerIndex = glyphIndex
}
} else {
closestLowerIndex = glyphIndex
}
}
}
}
}
if let closestLowerIndex = closestLowerIndex {
index = closestLowerIndex
}
} }
} }
if index >= 0 && index < attributedString.length { if index >= 0 && index < attributedString.length {
@ -430,6 +595,41 @@ public final class TextNodeLayout: NSObject {
} }
return nil return nil
} }
public func rangeRects(in range: NSRange) -> [CGRect]? {
guard let _ = self.attributedString, range.length != 0 else {
return nil
}
var rects: [(CGRect, CGRect)] = []
for line in self.lines {
let lineRange = NSIntersectionRange(range, line.range)
if lineRange.length != 0 {
var leftOffset: CGFloat = 0.0
if lineRange.location != line.range.location || line.isRTL {
leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil))
}
var rightOffset: CGFloat = line.frame.width
if lineRange.location + lineRange.length != line.range.upperBound || line.isRTL {
var secondaryOffset: CGFloat = 0.0
let rawOffset = CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, &secondaryOffset)
rightOffset = ceil(rawOffset)
if !rawOffset.isEqual(to: secondaryOffset) {
rightOffset = ceil(secondaryOffset)
}
}
var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size)
lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout)
let width = max(0.0, abs(rightOffset - leftOffset))
rects.append((lineFrame, CGRect(origin: CGPoint(x: lineFrame.minX + (leftOffset < rightOffset ? leftOffset : rightOffset) + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: width, height: lineFrame.size.height))))
}
}
if !rects.isEmpty {
return rects.map { $1 }
}
return nil
}
} }
private final class TextAccessibilityOverlayElement: UIAccessibilityElement { private final class TextAccessibilityOverlayElement: UIAccessibilityElement {
@ -550,9 +750,9 @@ public class TextNode: ASDisplayNode {
self.clipsToBounds = false self.clipsToBounds = false
} }
public func attributesAtPoint(_ point: CGPoint) -> (Int, [NSAttributedString.Key: Any])? { public func attributesAtPoint(_ point: CGPoint, orNearest: Bool = false) -> (Int, [NSAttributedString.Key: Any])? {
if let cachedLayout = self.cachedLayout { if let cachedLayout = self.cachedLayout {
return cachedLayout.attributesAtPoint(point) return cachedLayout.attributesAtPoint(point, orNearest: orNearest)
} else { } else {
return nil return nil
} }
@ -574,6 +774,14 @@ public class TextNode: ASDisplayNode {
} }
} }
public func rangeRects(in range: NSRange) -> [CGRect]? {
if let cachedLayout = self.cachedLayout {
return cachedLayout.rangeRects(in: range)
} else {
return nil
}
}
public func lineAndAttributeRects(name: String, at index: Int) -> [(CGRect, CGRect)]? { public func lineAndAttributeRects(name: String, at index: Int) -> [(CGRect, CGRect)]? {
if let cachedLayout = self.cachedLayout { if let cachedLayout = self.cachedLayout {
return cachedLayout.lineAndAttributeRects(name: name, at: index) return cachedLayout.lineAndAttributeRects(name: name, at: index)
@ -861,7 +1069,15 @@ public class TextNode: ASDisplayNode {
lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: bounds.size), cutout: layout.cutout) lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: bounds.size), cutout: layout.cutout)
} }
context.textPosition = CGPoint(x: lineFrame.minX, y: lineFrame.minY) context.textPosition = CGPoint(x: lineFrame.minX, y: lineFrame.minY)
CTLineDraw(line.line, context)
let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray
if glyphRuns.count != 0 {
for run in glyphRuns {
let run = run as! CTRun
let glyphCount = CTRunGetGlyphCount(run)
CTRunDraw(run, context, CFRangeMake(0, glyphCount))
}
}
if !line.strikethroughs.isEmpty { if !line.strikethroughs.isEmpty {
for strikethrough in line.strikethroughs { for strikethrough in line.strikethroughs {