`
yanfaguanli
  • 浏览: 661125 次
文章分类
社区版块
存档分类
最新评论

Laying out text with Core Text

 
阅读更多

文章转自:http://robnapier.net/blog/laying-out-text-with-coretext-547

I’m back in full book-writing mode, now working with Mugunth Kumar, who is brilliant. Go check out his stuff. Hopefully we’ll have something published and in all your hands by the end of the year. The book has taken up most of my writing time, so the blog will continue to be a bit quiet, but sometimes I like to answer a Stackoverflow question a bit more fully than I can there.

Today’s question is about laying out text without CTFramesetter. We’re going to take a whirlwind tour through some CoreText code to demonstrate this. It’s not quite what the OP was asking about, but it shows some techniques and I had it handy. I’ll be writing a whole chapter on Core Text soon.

The goal of this project was to make “pinch” view. It lays out text in a view, and where ever you touch, the text is pinched towards that point. It’s not meant to be really useful. Everything is done indrawRect:, which is ok in this case, since we only draw when we’re dirty, and when we’re dirty we have to redraw everything anyway. But in many cases, you’d want to do these calculations elsewhere, and only do final drawing indrawRect:.

We start with some basic view layout, and loop until we run out of text or run out of vertical space in the view.

- (void)drawRect:(CGRect)rect {      
  [... Basic view setup and drawing the border ...]

  // Work out the geometry
  CGRect insetBounds = CGRectInset([self bounds], 40.0, 40.0);
  CGPoint textPosition = CGPointMake(floor(CGRectGetMinX(insetBounds)),
                                     floor(CGRectGetMaxY(insetBounds)));
  CGFloat boundsWidth = CGRectGetWidth(insetBounds);

  // Calculate the lines
  CFIndex start = 0;
  NSUInteger length = CFAttributedStringGetLength(attributedString);
  while (start < length && textPosition.y > insetBounds.origin.y)
  {

Now we ask the typesetter to break off a line for us.

    CTTypesetterRef typesetter = self.typesetter;
    CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start, boundsWidth);
    CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, count));

And decide whether to full-justify it or not based on whether it’s at least 85% of a line:

   CGFloat ascent;
    CGFloat descent;
    CGFloat leading;
    double lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);

    // Full-justify if the text isn't too short.
    if ((lineWidth / boundsWidth) > 0.85)
    {
      CTLineRef justifiedLine = CTLineCreateJustifiedLine(line, 1.0, boundsWidth);
      CFRelease(line);
      line = justifiedLine;
    }

Now we start pulling off one CTRun at a time. A run is a series of glyphs within a line that share the same formatting. In our case, we should generally just have one run per line. This is a good point to explain the difference between a glyph and character. A character represents a unit of information. A glyph represents a unit of drawing. In the vast majority of cases in English, these two are identical, but there a few exceptions even in English called ligatures. The most famous is “fi” which in some fonts is drawn as a single glyph. Open TextEdit. Choose Lucida Grande 36 point font. Type “fi” and see for yourself how it’s drawn. Compare it to “ft” if you think it’s just drawing the “f” too wide. The joining is on purpose.

So the thing to keep in mind is that there can be a different number of glyphs than characters. High-level Core Text objects work in characters. Low-level objects work in glyphs. There are functions to convert character indexes into glyph indexes and vice versa. So, let’s back to the code. We’re going to move the Core Graphics text pointer and start looping through ourCTRun objects:

    // Move us forward to the baseline
    textPosition.y -= ceil(ascent);
    CGContextSetTextPosition(context, textPosition.x, textPosition.y);

    // Get the CTRun list
    CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
    CFIndex runCount = CFArrayGetCount(glyphRuns);

    // Saving for later in case we need to use the actual transform. It's faster
    // to just add the translate (see below).
    //      CGAffineTransform textTransform = CGContextGetTextMatrix(context);
    //      CGAffineTransform inverseTextTransform = CGAffineTransformInvert(textTransform);

    for (CFIndex runIndex = 0; runIndex < runCount; ++runIndex)
    {

Now we have our run, and we’re going to work out the font so we can draw it. By definition, the entire run will have the same font and other attributes. Note that the code only handles font changes. It won’t handle decorations like underline (remember: bold is a font, underline is a decoration). You’d need to add more code if you wanted that.

      CTRunRef run = CFArrayGetValueAtIndex(glyphRuns, runIndex);
      CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run),
                                               kCTFontAttributeName);

      // FIXME: We could optimize this by caching fonts we know we use.
      CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL);
      CGContextSetFont(context, cgFont);
      CGContextSetFontSize(context, CTFontGetSize(runFont));
      CFRelease(cgFont);

Now we’re going to pull out all the glyphs so we can lay them out one at a time.CTRun has one of those annoyingGet...Ptr constructs that are common in Core frameworks.CTRunGetPositionsPtr() will very quickly return you the internal pointer to the glyphs locations. But it might fail if theCTRun hasn’t calculating them yet. If that happens, then you have to callCTRunGetPositions() and hand it a buffer to copy into. To handle this, I keep around a buffer that Irealloc() to the largest size I need. This almost never comes up becauseCTRunGetPositionsPtr() almost always returns a result.

Note the comment about being “slightly dangerous.” I’m grabbing the internal location data structures and modifying them. This works out because we are the only user of thisCTRun, but these are really immutable structures. If twoCTRun objects are created from the same data, then Apple is free to return us two pointers to the same object. So it’s within the specs that we’re actually modifying data that some other part of the program is using for a different layout. That’s pretty unlikely, but it’s worth keeping in mind. My early tests of this on a first-generation iPad suggested that this optimization was noticeable in Instruments. On the other hand, I hadn’t applied some other optimizations yet (like reusingpositionsBuffer), so it may be practical to get better safety and performance here. I’ll have to profile further.

CFIndex glyphCount = CTRunGetGlyphCount(run);

      // This is slightly dangerous. We're getting a pointer to the internal
      // data, and yes, we're modifying it. But it avoids copying the memory
      // in most cases, which can get expensive.
      CGPoint *positions = (CGPoint*)CTRunGetPositionsPtr(run);
      if (positions == NULL)
      {
        size_t positionsBufferSize = sizeof(CGPoint) * glyphCount;
        if (malloc_size(positionsBuffer) < positionsBufferSize)
        {
          positionsBuffer = realloc(positionsBuffer, positionsBufferSize);
        }
        CTRunGetPositions(run, kRangeZero, positionsBuffer);
        positions = positionsBuffer;
      }

      // This one is less dangerous since we don't modify it, and we keep the const
      // to remind ourselves that it's not to be modified lightly.
      const CGGlyph *glyphs = CTRunGetGlyphsPtr(run);
      if (glyphs == NULL)
      {
        size_t glyphsBufferSize = sizeof(CGGlyph) * glyphCount;
        if (malloc_size(glyphsBuffer) < glyphsBufferSize)
        {
          glyphsBuffer = realloc(glyphsBuffer, glyphsBufferSize);
        }
        CTRunGetGlyphs(run, kRangeZero, (CGGlyph*)glyphs);
        glyphs = glyphsBuffer;
      }

Now we move around the characters with a little trig. I originally coded this usingCGAffineTransforms, but doing the math by hand turned out to be much faster.

// Squeeze the text towards the touch-point
      if (touchIsActive)
      {
        for (CFIndex glyphIndex = 0; glyphIndex < glyphCount; ++glyphIndex)
        {
          // Text space -> user space
          // Saving the transform in case we ever want it, but just applying
          // the translation by hand is faster.
          // CGPoint viewPosition = CGPointApplyAffineTransform(positions[glyphIndex], textTransform);
          CGPoint viewPosition = positions[glyphIndex];
          viewPosition.x += textPosition.x;
          viewPosition.y += textPosition.y;

          CGFloat r = sqrtf(hypotf(viewPosition.x - touchPoint.x,
                                   viewPosition.y - touchPoint.y)) / 4;
          CGFloat theta = atan2f(viewPosition.y - touchPoint.y, 
                                 viewPosition.x - touchPoint.x);
          CGFloat g = 10;

          viewPosition.x -= floorf(cosf(theta) * r * g);
          viewPosition.y -= floor(sinf(theta) * r * g);

          // User space -> text space
          // Note that this is modifying an internal data structure of the CTRun.
          // positions[glyphIndex] = CGPointApplyAffineTransform(viewPosition, inverseTextTransform);
          viewPosition.x -= textPosition.x;
          viewPosition.y -= textPosition.y;
          positions[glyphIndex] = viewPosition;
        }
      }

Finally, finally, we draw the glyphs and move down a line. We move down by adding the previous-calculated descent, leading and then +1. The “+1″ was added because it matches up with how CTFramesetter lays out. Otherwise the descenders of one line exactly touch the ascenders of the next line.

     CGContextShowGlyphsAtPositions(context, glyphs, positions, glyphCount);
    }

    // Move the index beyond the line break.
    start += count;
    textPosition.y -= ceilf(descent + leading + 1); // +1 matches best to CTFramesetter's behavior  
    CFRelease(line);
  }
  free(positionsBuffer);
  free(glyphsBuffer);
}

So there you have it. It’s a whirlwind tour showing how to lay glyphs out one-by-one. Attached is an example project showing it in real life.


Text Demo点击打开链接


分享到:
评论

相关推荐

    vue3.2+ts+vite+admin 开发的admin管理系统模板

    vue3.2+ts+vite+admin 开发的admin管理系统模板

    chromedriver-win64-124版本浏览器驱动

    自动化测试工具selenium使用的浏览器驱动下载 chromedriver驱动压缩包 Java web 自动化测试工具需要使用的chromedriver浏览器驱动

    node-v8.1.0-darwin-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    2. Power Query 逆透视&分组依据.xlsx

    2. Power Query 逆透视&分组依据.xlsx

    Java 实现微信红包分配算法内含源码以及说明书可以自己运行复现.zip

    Java 实现微信红包分配算法内含源码以及说明书可以自己运行复现.zip

    node-v9.11.1-darwin-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v9.3.0-linux-ppc64le.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    2024-2030中国WiFi连接洗衣机市场现状研究分析与发展前景预测报告.docx

    2024-2030中国WiFi连接洗衣机市场现状研究分析与发展前景预测报告

    node-v8.2.0-linux-ppc64le.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    基于QT+C++实现的多列时间轴控件+源码+文档

    用法链接:https://menghui666.blog.csdn.net/article/details/138544216?spm=1001.2014.3001.5502 Qt 多列时间轴控件。 可与多段字符串格式自由转换,也可手动添加列表项。 专门用来以时间轴作为事件线发展顺序的故事大纲。 特点 时间背包功能:记录所有物品或属性发生的变化,随时回溯 时间可输入任意内容,不限于时间 每一时间段允许多列,即多个文字节点 全自动调整节点大小(宽高),尽量看起来舒服 行与行、列与列 之间任意拖拽更换顺序 可与文字自由转换,默认一段为一个文字节点 方便的多行操作 按需修改快捷键 所有编辑可撤销 美观的调整动画

    基于SSM个人记账本理财管理系统源码+文档说明+数据库(高分项目).zip

    基于SSM个人记账本理财管理系统源码+文档说明+数据库(高分项目).zip 本资源中的源码都是经过本地编译过可运行的,评审分达到95分以上。资源项目的难度比较适中,内容都是经过助教老师审定过的能够满足学习、使用需求,如果有需要的话可以放心下载使用。 基于SSM个人记账本理财管理系统源码+文档说明+数据库(高分项目).zip 本资源中的源码都是经过本地编译过可运行的,评审分达到95分以上。资源项目的难度比较适中,内容都是经过助教老师审定过的能够满足学习、使用需求,如果有需要的话可以放心下载使用。 基于SSM个人记账本理财管理系统源码+文档说明+数据库(高分项目).zip 本资源中的源码都是经过本地编译过可运行的,评审分达到95分以上。资源项目的难度比较适中,内容都是经过助教老师审定过的能够满足学习、使用需求,如果有需要的话可以放心下载使用。 基于SSM个人记账本理财管理系统源码+文档说明+数据库(高分项目).zip 本资源中的源码都是经过本地编译过可运行的,评审分达到95分以上。资源项目的难度比较适中,内容都是经过助教老师审定过的能够满足学习、使用需求,如果有需要的话可以放心下载使用。

    代替AWCC驱动的TCC软件

    代替AWCC驱动的TCC软件

    app.apk

    app.apk

    C++11&14 高速上手教程内含源码以及说明书可以自己运行复现.zip

    C++11&14 高速上手教程内含源码以及说明书可以自己运行复现.zip

    node-v8.11.4-linux-ppc64le.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    国际象棋游戏数据集 CSV 2W+场次 (Lichess)

    这是一组从网站上的精选用户那里收集的 20,000 多款游戏 Lichess.org,以及如何收集更多游戏。我还将在未来收集更多游戏时上传它们。此套装包含:游戏 ID;额定值 (T/F);开始时间;结束时间;匝数;游戏状态;胜利者;时间增量;白色玩家 ID;白人球员评分;黑色玩家 ID;黑人球员评分;所有动作均采用标准国际象棋符号;Opening Eco(任何给定开口的标准化代码,在此处列出); 开场名称;开盘(开盘阶段的移动次数)

    大语言模型高清PDF-人民大学

    大语言模型高清PDF--人民大学。2022 年底,ChatGPT 震撼上线,大语言模型技术迅速“席卷”了整个社会,人工智能技术因此迎来了一次重要进展。面对大语言模型的强大性能,我们不禁要问:支撑这些模型的背后技术究竟是什么?这一问题无疑成为了众多科研人员的思考焦点。 不同综述,本次中文版书籍更注重为大模型技术的入门读者提供讲解,为此我们在内容上进行了大幅度的更新与重组,力图展现一个整体的大模型技术框架和路线图。本书适用于具有深度学习基础的高年级本科生以及低年级研究生使用,可以作为一本入门级的技术书籍

    node-v9.11.2-linux-armv7l.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    2024年中国齿槽扭矩测试仪行业研究报告.docx

    2024年中国齿槽扭矩测试仪行业研究报告

    node-v10.0.0-linux-armv7l.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

Global site tag (gtag.js) - Google Analytics