Skip to content

Commit

Permalink
Fix CRLF Line Ending Typesetting (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecoolwinter authored Feb 15, 2024
1 parent cf4ee3b commit f2f9d93
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 3 deletions.
29 changes: 26 additions & 3 deletions Sources/CodeEditTextView/TextLine/Typesetter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,18 @@ final class Typesetter {
startingOffset: Int,
constrainingWidth: CGFloat
) -> Int {
let breakIndex = startingOffset + CTTypesetterSuggestClusterBreak(typesetter, startingOffset, constrainingWidth)
if breakIndex >= string.length || (breakIndex - 1 > 0 && ensureCharacterCanBreakLine(at: breakIndex - 1)) {
var breakIndex = startingOffset + CTTypesetterSuggestClusterBreak(typesetter, startingOffset, constrainingWidth)

let isBreakAtEndOfString = breakIndex >= string.length

let isNextCharacterCarriageReturn = checkIfLineBreakOnCRLF(breakIndex)
if isNextCharacterCarriageReturn {
breakIndex += 1
}

let canLastCharacterBreak = (breakIndex - 1 > 0 && ensureCharacterCanBreakLine(at: breakIndex - 1))

if isBreakAtEndOfString || canLastCharacterBreak {
// Breaking either at the end of the string, or on a whitespace.
return breakIndex
} else if breakIndex - 1 > 0 {
Expand All @@ -208,7 +218,20 @@ final class Typesetter {
let set = CharacterSet(
charactersIn: string.attributedSubstring(from: NSRange(location: index, length: 1)).string
)
return set.isSubset(of: .whitespaces) || set.isSubset(of: .punctuationCharacters)
return set.isSubset(of: .whitespacesAndNewlines) || set.isSubset(of: .punctuationCharacters)
}

/// Check if the break index is on a CRLF (`\r\n`) character, indicating a valid break position.
/// - Parameter breakIndex: The index to check in the string.
/// - Returns: True, if the break index lies after the `\n` character in a `\r\n` sequence.
private func checkIfLineBreakOnCRLF(_ breakIndex: Int) -> Bool {
guard breakIndex - 1 > 0 && breakIndex + 1 <= string.length else {
return false
}
let substringRange = NSRange(location: breakIndex - 1, length: 2)
let substring = string.attributedSubstring(from: substringRange).string

return substring == LineEnding.carriageReturnLineFeed.rawValue
}

deinit {
Expand Down
74 changes: 74 additions & 0 deletions Tests/CodeEditTextViewTests/TypesetterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import XCTest
@testable import CodeEditTextView

// swiftlint:disable all

class TypesetterTests: XCTestCase {
let limitedLineWidthDisplayData = TextLine.DisplayData(maxWidth: 150, lineHeightMultiplier: 1.0, estimatedLineHeight: 20.0)
let unlimitedLineWidthDisplayData = TextLine.DisplayData(maxWidth: .infinity, lineHeightMultiplier: 1.0, estimatedLineHeight: 20.0)

func test_LineFeedBreak() {
let typesetter = Typesetter()
typesetter.typeset(
NSAttributedString(string: "testline\n"),
displayData: unlimitedLineWidthDisplayData,
breakStrategy: .word,
markedRanges: nil
)

XCTAssertEqual(typesetter.lineFragments.count, 1, "Typesetter typeset incorrect number of lines.")

typesetter.typeset(
NSAttributedString(string: "testline\n"),
displayData: unlimitedLineWidthDisplayData,
breakStrategy: .character,
markedRanges: nil
)

XCTAssertEqual(typesetter.lineFragments.count, 1, "Typesetter typeset incorrect number of lines.")
}

func test_carriageReturnBreak() {
let typesetter = Typesetter()
typesetter.typeset(
NSAttributedString(string: "testline\r"),
displayData: unlimitedLineWidthDisplayData,
breakStrategy: .word,
markedRanges: nil
)

XCTAssertEqual(typesetter.lineFragments.count, 1, "Typesetter typeset incorrect number of lines.")

typesetter.typeset(
NSAttributedString(string: "testline\r"),
displayData: unlimitedLineWidthDisplayData,
breakStrategy: .character,
markedRanges: nil
)

XCTAssertEqual(typesetter.lineFragments.count, 1, "Typesetter typeset incorrect number of lines.")
}

func test_carriageReturnLineFeedBreak() {
let typesetter = Typesetter()
typesetter.typeset(
NSAttributedString(string: "testline\r\n"),
displayData: unlimitedLineWidthDisplayData,
breakStrategy: .word,
markedRanges: nil
)

XCTAssertEqual(typesetter.lineFragments.count, 1, "Typesetter typeset incorrect number of lines.")

typesetter.typeset(
NSAttributedString(string: "testline\r\n"),
displayData: unlimitedLineWidthDisplayData,
breakStrategy: .character,
markedRanges: nil
)

XCTAssertEqual(typesetter.lineFragments.count, 1, "Typesetter typeset incorrect number of lines.")
}
}

// swiftlint:enable all

0 comments on commit f2f9d93

Please sign in to comment.