Skip to content

Commit

Permalink
Fixes for text height rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
buba447 committed Aug 21, 2020
1 parent 8def94c commit ed31fa3
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ final class TextCompositionLayer: CompositionLayer {

// Get Text Attributes
let text = textDocument.value(frame: frame) as! TextDocument
let fillColor = rootNode?.textOutputNode.fillColor ?? text.fillColorData.cgColorValue
let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue
let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0)
let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0
Expand All @@ -111,7 +110,15 @@ final class TextCompositionLayer: CompositionLayer {
textLayer.alignment = text.justification.textAlignment
textLayer.lineHeight = CGFloat(text.lineHeight)
textLayer.tracking = tracking
textLayer.fillColor = fillColor

if let fillColor = rootNode?.textOutputNode.fillColor {
textLayer.fillColor = fillColor
} else if let fillColor = text.fillColorData?.cgColorValue {
textLayer.fillColor = fillColor
} else {
textLayer.fillColor = nil
}

textLayer.preferredSize = text.textFrameSize?.sizeValue
textLayer.strokeOnTop = text.strokeOverFill ?? false
textLayer.strokeWidth = strokeWidth
Expand Down
169 changes: 136 additions & 33 deletions lottie-swift/src/Private/LayerContainers/Utility/TextLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,64 @@ final class TextLayer: CALayer {
return nil
}

// Draws Debug colors for the font alignment.
private func drawDebug(_ ctx: CGContext) {
if let font = font {
let ascent = CTFontGetAscent(font)
let descent = CTFontGetDescent(font)
let capHeight = CTFontGetCapHeight(font)
let leading = CTFontGetLeading(font)

// Ascent Red
if #available(iOS 13.0, *) {
ctx.setFillColor(CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 0.5))
} else {
// Fallback on earlier versions
}
ctx.fill(CGRect(x:0, y:0, width:drawingRect.width, height:ascent))

// Descent Blue
if #available(iOS 13.0, *) {
ctx.setFillColor(CGColor(srgbRed: 0, green: 0, blue: 1, alpha: 0.5))
} else {
// Fallback on earlier versions
}
ctx.fill(CGRect(x:0, y:ascent, width:drawingRect.width, height:descent))

// Leading Yellow
if #available(iOS 13.0, *) {
ctx.setFillColor(CGColor(srgbRed: 1, green: 1, blue: 0, alpha: 0.5))
} else {
// Fallback on earlier versions
}
ctx.fill(CGRect(x:0, y:ascent+descent, width:drawingRect.width, height:leading))

// Cap height Green
if #available(iOS 13.0, *) {
ctx.setFillColor(CGColor(srgbRed: 0, green: 1, blue: 0, alpha: 0.5))
} else {
// Fallback on earlier versions
}
ctx.fill(CGRect(x:0, y:ascent - capHeight, width:drawingRect.width, height:capHeight))

if drawingRect.height - ascent+descent+leading > 0 {
// Remainder
if #available(iOS 13.0, *) {
ctx.setFillColor(CGColor(srgbRed: 0, green: 1, blue: 1, alpha: 0.5))
} else {
// Fallback on earlier versions
}
ctx.fill(CGRect(x:0, y:ascent+descent+leading, width:drawingRect.width, height:drawingRect.height - ascent+descent+leading))
}
}
}

override func draw(in ctx: CGContext) {
guard let attributedString = attributedString else { return }
updateTextContent()
guard let setter = fillFrameSetter else { return }

guard fillFrameSetter != nil || strokeFrameSetter != nil else { return }
// guard let font = font else { return }
// drawDebug(ctx)
ctx.textMatrix = .identity
ctx.setAllowsAntialiasing(true)
ctx.setAllowsFontSmoothing(true)
Expand All @@ -127,22 +180,55 @@ final class TextLayer: CALayer {

ctx.translateBy(x: 0, y: drawingRect.height)
ctx.scaleBy(x: 1.0, y: -1.0)

let drawingPath = CGPath(rect: drawingRect, transform: nil)

if !strokeOnTop, let strokeSetter = strokeFrameSetter {
// Draw stroke first
let frame = CTFramesetterCreateFrame(strokeSetter, CFRangeMake(0, attributedString.length), drawingPath, nil)
CTFrameDraw(frame, ctx)
let fillFrame: CTFrame?
if let setter = fillFrameSetter {
fillFrame = CTFramesetterCreateFrame(setter, CFRangeMake(0, attributedString.length), drawingPath, nil)
} else {
fillFrame = nil
}

let frame = CTFramesetterCreateFrame(setter, CFRangeMake(0, attributedString.length), drawingPath, nil)
CTFrameDraw(frame, ctx)
let strokeFrame: CTFrame?
if let setter = strokeFrameSetter {
strokeFrame = CTFramesetterCreateFrame(setter, CFRangeMake(0, attributedString.length), drawingPath, nil)
} else {
strokeFrame = nil
}

// This fixes a vertical padding issue that arises when drawing some fonts.
// For some reason some fonts, such as Helvetica draw with and ascender that is greater than the one reported by CTFontGetAscender.
// I suspect this is actually an issue with the Attributed string, but cannot reproduce.

// if let fillFrame = fillFrame {
// var o = [CGPoint] (repeating: .zero, count: 1)
// let count = CFArrayGetCount(CTFrameGetLines(fillFrame))
// if count > 0 {
// CTFrameGetLineOrigins (fillFrame, CFRange (location: count-1,length: 1), &o)
// let diff = CTFontGetDescent(font) - o[0].y
// ctx.translateBy(x: 0, y: diff)
// }
// } else if let strokeFrame = strokeFrame {
// var o = [CGPoint] (repeating: .zero, count: 1)
// let count = CFArrayGetCount(CTFrameGetLines(strokeFrame))
// if count > 0 {
// CTFrameGetLineOrigins (strokeFrame, CFRange (location: count-1,length: 1), &o)
// let diff = CTFontGetDescent(font) - o[0].y
// ctx.translateBy(x: 0, y: diff)
// }
// }

if !strokeOnTop, let strokeFrame = strokeFrame {
CTFrameDraw(strokeFrame, ctx)
}

if let fillFrame = fillFrame {
CTFrameDraw(fillFrame, ctx)
}

if strokeOnTop, let strokeSetter = strokeFrameSetter {
ctx.translateBy(x: strokeWidth * -0.5, y: strokeWidth * 0.5)
let frame = CTFramesetterCreateFrame(strokeSetter, CFRangeMake(0, attributedString.length), drawingPath, nil)
CTFrameDraw(frame, ctx)
if strokeOnTop, let strokeFrame = strokeFrame {
CTFrameDraw(strokeFrame, ctx)
}
}

Expand All @@ -156,7 +242,7 @@ final class TextLayer: CALayer {
private func updateTextContent() {
guard needsContentUpdate else { return }
needsContentUpdate = false
guard let font = font, let text = text, text.count > 0, let fillColor = fillColor else {
guard let font = font, let text = text, text.count > 0, (fillColor != nil || strokeColor != nil) else {
drawingRect = .zero
drawingAnchor = .zero
attributedString = nil
Expand All @@ -169,38 +255,61 @@ final class TextLayer: CALayer {
let ascent = CTFontGetAscent(font)
let descent = CTFontGetDescent(font)
let capHeight = CTFontGetCapHeight(font)
let leading = floor(max(CTFontGetLeading(font), 0) + 0.5)
let fontLineHeight = floor(ascent + 0.5) + floor(descent + 0.5) + leading
let ascenderDelta = leading > 0 ? 0 : floor (0.2 * fontLineHeight + 0.5)
let minLineHeight = -(fontLineHeight + ascenderDelta)
let leading = CTFontGetLeading(font)
let minLineHeight = -(ascent + descent + leading)

// Calculate line spacing
let lineSpacing = max(CGFloat(minLineHeight) + lineHeight, CGFloat(minLineHeight))

// Build Attributes
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.lineHeightMultiple = 1
paragraphStyle.maximumLineHeight = ascent+descent+leading
paragraphStyle.alignment = alignment
paragraphStyle.lineBreakMode = NSLineBreakMode.byWordWrapping
var attributes: [NSAttributedString.Key : Any] = [
NSAttributedString.Key.ligature: 0,
NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: fillColor,
NSAttributedString.Key.kern: tracking,
NSAttributedString.Key.paragraphStyle: paragraphStyle
]

if let fillColor = fillColor {
attributes[NSAttributedString.Key.foregroundColor] = fillColor
}

let attrString = NSAttributedString(string: text, attributes: attributes)
attributedString = attrString
let setter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString)
fillFrameSetter = setter

// Calculate drawing size

if fillColor != nil {
let setter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString)
fillFrameSetter = setter
} else {
fillFrameSetter = nil
}

if let strokeColor = strokeColor {
attributes[NSAttributedString.Key.foregroundColor] = nil
attributes[NSAttributedString.Key.strokeWidth] = strokeWidth
attributes[NSAttributedString.Key.strokeColor] = strokeColor
let strokeAttributedString = NSAttributedString(string: text, attributes: attributes)
strokeFrameSetter = CTFramesetterCreateWithAttributedString(strokeAttributedString as CFAttributedString)
} else {
strokeFrameSetter = nil
strokeWidth = 0
}

guard let setter = fillFrameSetter ?? strokeFrameSetter else {
return
}

// Calculate drawing size and anchor offset
let textAnchor: CGPoint
if let preferredSize = preferredSize {
drawingRect = CGRect(origin: .zero, size: preferredSize)
drawingRect.size.height += (ascent - capHeight)
textAnchor = CGPoint(x: 0, y: ascent-capHeight)
drawingRect.size.height += descent
textAnchor = CGPoint(x: 0, y: (ascent-capHeight))
} else {
let size = CTFramesetterSuggestFrameSizeWithConstraints(
setter,
Expand All @@ -226,16 +335,10 @@ final class TextLayer: CALayer {
// Now Calculate Anchor
drawingAnchor = CGPoint(x: textAnchor.x.remap(fromLow: 0, fromHigh: drawingRect.size.width, toLow: 0, toHigh: 1),
y: textAnchor.y.remap(fromLow: 0, fromHigh: drawingRect.size.height, toLow: 0, toHigh: 1))

if let strokeColor = strokeColor {
attributes[NSAttributedString.Key.strokeWidth] = strokeWidth
attributes[NSAttributedString.Key.strokeColor] = strokeColor
let strokeAttributedString = NSAttributedString(string: text, attributes: attributes)
strokeFrameSetter = CTFramesetterCreateWithAttributedString(strokeAttributedString as CFAttributedString)

if fillFrameSetter != nil && strokeFrameSetter != nil {
drawingRect.size.width += strokeWidth
drawingRect.size.height += strokeWidth
} else {
strokeFrameSetter = nil
}
}

Expand Down
2 changes: 1 addition & 1 deletion lottie-swift/src/Private/Model/Text/TextDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ final class TextDocument: Codable {
let baseline: Double?

/// Fill Color data
let fillColorData: Color
let fillColorData: Color?

/// Scroke Color data
let strokeColorData: Color?
Expand Down

0 comments on commit ed31fa3

Please sign in to comment.