CoreText 学习2

作者: 老初 | 来源:发表于2017-03-14 17:05 被阅读29次

本文主要是用Swift重写了巧哥博客中的 Demo,博客的原始链接如下:

唐巧:
基于 CoreText 的排版引擎:基础
基于 CoreText 的排版引擎:进阶

1、支持图文(链接)混排的排版引擎

content.json文件修改为:

[
  {
    "type": "img",
    "width": 200,
    "height": 108,
    "name": "coretext-image-1.jpg"
  },
  {
    "color": "blue",
    "content": " 更进一步地,实际工作中,我们更希望通过一个排版文件,来设置需要排版的文字的 ",
    "size": 16,
    "type": "txt"
  },
  {
    "color": "red",
    "content": " 内容、颜色、字体 ",
    "size": 22,
    "type": "txt"
  },
  {
    "color": "black",
    "content": " 大小等信息。\n",
    "size": 16,
    "type": "txt"
  },
  {
    "type": "img",
    "width": 200,
    "height": 130,
    "name": "coretext-image-2.jpg"
  },
  {
    "color": "default",
    "content": " 我在开发猿题库应用时,自己定义了一个基于 UBB 的排版模版,但是实现该排版文件的解析器要花费大量的篇幅,考虑到这并不是本章的重点,所以我们以一个较简单的排版文件来讲解其思想。",
    "type": "txt"
  },
  {
    "color": "default",
    "content": " 这在这里尝试放一个参考链接:",
    "type": "txt"
  },
  {
    "color": "blue",
    "content": " 链接文字 ",
    "url": "http://blog.devtang.com",
    "type": "link"
  },
  {
    "color": "default",
    "content": " 大家可以尝试点击一下 ",
    "type": "txt"
  }
]

修改parseTemplateFile方法,增加一个名为imageArray的参数来保存解析的图片信息,增加一个名为linkArray的参数来保存解析的链接信息:

/// 解析模板文件
class func parseTemplateFile(path: String, config: CTFrameParserConfig) -> CoreTextData {
    var imageArray = [CoreTextImageData]()
    var linkArray  = [CoreTextLinkData]()
    
    let content = self.loadTemplateFile(path: path, config: config, imageArray: &imageArray, linkArray: &linkArray)
    
    let coreTextData = self.parse(content: content, config: config)
    
    coreTextData.imageArray = imageArray
    coreTextData.linkArray = linkArray
    
    return coreTextData
}

修改loadTemplateFile方法,增加了对于typeimglink的节点处理逻辑:

/// 加载模板文件
class func loadTemplateFile(path: String, config: CTFrameParserConfig, imageArray: inout [CoreTextImageData], linkArray: inout [CoreTextLinkData]) -> NSAttributedString {
    
    let result = NSMutableAttributedString()
    
    let url = URL(fileURLWithPath: Bundle.main.path(forResource: path, ofType: "json")!)
    if let data = try? Data(contentsOf: url) {
        
        if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: .allowFragments), let array = jsonObject as? [[String: String]] {
            for item in array {
                let type = item["type"]
                
                if type == "txt" {
                    let subStr = self.parseAttributedCotnentFromDictionary(dict: item, config: config)
                    result.append(subStr)
                }
                
                if type == "image" {
                    let imageData = CoreTextImageData()
                    imageData.name = item["name"]
                    imageData.imagePosition = CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0)
                    imageArray.append(imageData)
                    
                    let subStr = self.parseImageAttributedCotnentFromDictionary(dict: item, config: config)
                    result.append(subStr)
                }
                
                if type == "link" {
                    let startPosition = result.length
                    let subStr = self.parseAttributedCotnentFromDictionary(dict: item, config: config)
                    result.append(subStr)
                    
                    var linkData = CoreTextLinkData()
                    linkData.title = item["content"]
                    linkData.url   = item["url"]
                    linkData.range = NSMakeRange(startPosition, result.length - startPosition)
                    linkArray.append(linkData)
                }
            }
        }
    }
    
    return result
}

最后我们新建一个最关键的方法:parseImageAttributedCotnentFromDictionary,生成图片空白的占位符,并且设置其CTRunDelegate信息。其代码如下:

/// 从字典中解析图片富文本信息
///
/// - Parameters:
///   - dict: 文字属性字典
///   - config: 配置信息
/// - Returns: 图片富文本
class func parseImageAttributedCotnentFromDictionary(dict: [String: String], config: CTFrameParserConfig) -> NSAttributedString {
    var ascender: CGFloat = 0.0
    if let height = (dict["height"] as AnyObject).floatValue {
        ascender = CGFloat(height)
    }
    var width: CGFloat = 0.0
    if let w = (dict["width"] as AnyObject).floatValue {
        width = CGFloat(w)
    }
    let pic = PictureRunInfo(ascender: ascender, descender: 0.0, width: width)
    
    var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { refCon in
        print("RunDelegate dealloc!")
    }, getAscent: { (refCon) -> CGFloat in
        let pictureRunInfo = unsafeBitCast(refCon, to: PictureRunInfo.self)
        return pictureRunInfo.ascender
    }, getDescent: { (refCon) -> CGFloat in
        return 0
    }, getWidth: { (refCon) -> CGFloat in
        
        let pictureRunInfo = unsafeBitCast(refCon, to: PictureRunInfo.self)
        return pictureRunInfo.width
    })
    
    let selfPtr = UnsafeMutableRawPointer(Unmanaged.passRetained(pic).toOpaque())
    
    // 创建 RunDelegate, delegate决定留给图片的空间大小
    let runDelegate = CTRunDelegateCreate(&callbacks, selfPtr)
    
    let attributes : Dictionary = self.attributes(config: config)
    // 创建一个空白的占位符
    let space = NSMutableAttributedString(string: " ", attributes: attributes)
    
    CFAttributedStringSetAttribute(space, CFRangeMake(0, 1), kCTRunDelegateAttributeName, runDelegate)
    return space
}

接着我们对CoreTextData进行改造:

// 用于保存由 CTFrameParser 类生成的 CTFrame 实例以及 CTFrame 实际绘制需要的高度
class CoreTextData: NSObject {
    
    var ctFrame: CTFrame
    var height: CGFloat
    var imageArray: [CoreTextImageData] = [CoreTextImageData]() {

        willSet {
            fillImagePosition(imageArray: newValue)
        }
        
    }
    var linkArray: [CoreTextLinkData]?
    
    init(ctFrame: CTFrame, height: CGFloat) {
        self.ctFrame = ctFrame
        self.height = height
    }
     
    private func fillImagePosition(imageArray: [CoreTextImageData]) {
        if imageArray.count == 0 {
            return
        }
        
        let lines = CTFrameGetLines(ctFrame) as Array
        var originsArray = [CGPoint](repeating: CGPoint.zero, count:lines.count)
        // 把 CTFrame 里每一行的初始坐标写到数组里
        CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), &originsArray)
        
        var imgIndex : Int = 0
        var imageData: CoreTextImageData? = imageArray[0]
        
        for index in 0..<lines.count {
            
            guard imageData != nil else {
                    return
            }
            
            let line = lines[index] as! CTLine
            let runObjArray = CTLineGetGlyphRuns(line) as Array
            
            for runObj in runObjArray {
                let run = runObj as! CTRun
                let runAttributes = CTRunGetAttributes(run) as NSDictionary
                let delegate = runAttributes.value(forKey: kCTRunDelegateAttributeName as String)
                
                if delegate == nil {
                    continue
                }
                
                var runBounds = CGRect()
                var ascent: CGFloat = 0
                var descent: CGFloat = 0
                
                runBounds.size.width = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, nil))
                runBounds.size.height = ascent + descent
                
                let xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil)
                runBounds.origin.x = originsArray[index].x + xOffset
                runBounds.origin.y = originsArray[index].y
                runBounds.origin.y -= descent
                
                let path = CTFrameGetPath(ctFrame)
                
                let colRect = path.boundingBox
                
                let delegateBounds = runBounds.offsetBy(dx: colRect.origin.x, dy: colRect.origin.y)
                
                imageData!.imagePosition = delegateBounds
                
                imgIndex += 1
                if imgIndex == imageArray.count {
                    imageData = nil
                    break
                } else {
                    imageData = imageArray[imgIndex]
                }
            }
        }
    }
}

2、添加对图片的点击支持

CTDisplayView类增加:

private func setupEvents() {
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(userTapGestureDetected(recognizer:)))
    self.addGestureRecognizer(tapGestureRecognizer)
    self.isUserInteractionEnabled = true
}

func userTapGestureDetected(recognizer: UITapGestureRecognizer) {
    let point = recognizer.location(in: self)

    if let imageArray = data?.imageArray {
        for imageData in imageArray {
            // 翻转坐标系,因为 imageData 中的坐标是 CoreText 的坐标系
            let imageRect = imageData.imagePosition
            var imagePosition = imageRect.origin
            imagePosition.y = self.bounds.size.height - imageRect.origin.y - imageRect.size.height
            let rect = CGRect(x: imagePosition.x, y: imagePosition.y, width: imageRect.size.width, height: imageRect.size.height)
            if rect.contains(point) {
                print("\(imageData.name)")
                
                break
            }
        }
    }
}
最终展示.png

Github地址:
https://github.com/GuiminChu/JianshuExamples/tree/master/CoreTextDemo

相关文章

  • CoreText 学习2

    本文主要是用Swift重写了巧哥博客中的 Demo,博客的原始链接如下: 唐巧:基于 CoreText 的排版引擎...

  • CoreText

    目录 1.CoreText框架概述 一、CoreText框架概述 1.CoreText框架图 2.Coretext...

  • iOS 获取一段文本占UILabel多少行

    1,导入头文件 import 2,

  • YYText

    1.YYKit之YYText2.CoreText实现图文混排和点击事件3.认识 TextKit4.CoreText...

  • CoreText的认识(三)

    1.CoreText的图片点击 2.CoreText的文字点击 遍历CTLine,获取每一个CTLine的fram...

  • CoreText 学习笔记(下)

    唐巧原博客地址:基于 CoreText 的排版引擎:进阶 | 唐巧的博客 前篇写了CoreText绘制纯文本的学习...

  • CoreText2

    blog.csdn.net CoreText实现图文混排 - 博客频道 CoreText实现图文混排 也好久没来写...

  • CoreText 学习总结

    === 需求 给出一段多行文本,要求给前面部分文字添加圆角边框(下面简称这个为标签),要求能够控制标签和文字之间的...

  • CoreText 学习1

    本文主要是用Swift重写了巧哥博客中的 Demo,博客的原始链接如下: 唐巧:基于 CoreText 的排版引擎...

  • CoreText学习探究

    一、核心文本 Core Text是一种用于布置文字和处理字体的先进的低级技术。在Mac OS X v10.5和iO...

网友评论

    本文标题:CoreText 学习2

    本文链接:https://www.haomeiwen.com/subject/urdhnttx.html