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

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点击打开链接


分享到:
评论

相关推荐

    the google web toolkit(gwt) laying out windows with panels

    This tutorial – Updated from GWT 1.7 to use GWT 2.0 – All examples and test cases use GWT 2.0 approach – UseUse thethe standardstandard PanelPanel typestypes fromfrom GWTGWT 11.77 andand ...

    laying out the website

    Html5 tutorial code by handwritting.

    Effects of methionine source and level on hepatic methionine and lipids metabolism in laying hens fed with low or moderate nutrient level diet under restricted feeding

    限饲条件下饲粮营养水平及蛋氨酸来源和添加水平对商品蛋鸡肝脏蛋氨酸及脂质代谢的影响,万建美,王建萍,本研究的目的是考察限饲条件下饲粮营养水平(低营养水平,LN;中等营养水平,MN)及蛋氨酸来源和添加水平对...

    Pango Reference Manual

    Vertical Text - Laying text out in vertical directions Rendering with Pango Win32 Fonts and Rendering - Functions for shape engines to manipulate Win32 fonts FreeType Fonts and Rendering - ...

    Prentice.Hall.C++.GUI.Programming.with.Qt.4.2nd.Edition.2008.chm

    Laying Out Widgets Using the Reference Documentation Chapter 2. Creating Dialogs Subclassing QDialog Signals and Slots in Depth Rapid Dialog Design Shape-Changing Dialogs Dynamic ...

    Android代码-CreditsRoll

    CreditsRoll ...Custom text measuring and laying out, using a StaticLayout 3D view transform on a Canvas using a Camera Custom view attributes handling Rickroll License This library is re

    Deliver.Audacious.Web.Apps.with.Ember2

    Laying Out a User Interface Chapter 4. Building In Reuse with Components Chapter 5. Modeling Your Data Chapter 6. Reading Nonstandard APIs Chapter 7. Reusing Code in Ember Chapter 8. Building, ...

    Reactive.Web.Applications.Covers.Play.Akka.and.Reactive.Streams

    This book starts by laying out the fundamentals required for writing functional and asynchronous applications and quickly introduces Play as a framework to handle the plumbing of your application....

    DDR2的布线说明zhidao

    Point-to-point designers face many challenges when laying out a new printed circuit board (PCB). The designer may need to arrange groups of devices within a certain area of the PCB, to place ...

    iWork: The Missing Manual

    Beyond Text: Laying Out Documents Chapter 7. Objects Up Close: Adding, Modifying, and More Chapter 8. Building Tables and Charts Chapter 9. Sharing Pages Documents Chapter 10. Creating Templates to ...

    Pro.XAML.with.Csharp

    Packed with real, usable code and expert insights, Pro XAML with C# shows you how to design and build compelling enterprise applications on WPF, Windows Store, or Windows Phone, or any combination of ...

    Qt Designer Windows+Mac

    Qt Designer is a tool for ... It gives you a simple drag-and-drop interface for laying out components such as buttons, text fields, combo boxes and more. Here is a screenshot of Qt Designer on Windows

    Reactive Web Applications(Manning,2016)

    This book starts by laying out the fundamentals required for writing functional and asynchronous applications and quickly introduces Play as a framework to handle the plumbing of your application....

    [Ruby] Ruby 微观本质论 (英文版)

    Author Pat Shaughnessy takes a scientific approach, laying out a series of experiments with Ruby code to take you behind the scenes of how programming languages work. You'll even find information on ...

    HTML A Beginner’s Guide, 5th Edition

    By 作者: Wendy Willard ... You’ll also go beyond the basics and find out how to work with Cascading Style Sheets (CSS), create dynamic web content with JavaScript, upload your site to the web, and code ...

    Flexbox.in.CSS.2017.5.pdf

    Layout designers rejoice: now you can greatly simplify the task of laying out your web page or application with Flexbox, the CSS Flexible Box Module. In this concise guide, author Estelle Weyl shows ...

    DDR package

    Point-to-point designers face many challenges when laying out a new printed circuit board (PCB). The designer may need to arrange groups of devices within a certain area of the PCB, to place ...

    OLM Bar code laying-VH0022-en

    OLM Bar code laying_VH0022_en

    SQL Prompt_9.1.5.4619破解版

    SP-6869 : Error laying out sql - Cannot align to future token CONSTRAINT. SP-6868 : Rule BP018 no longer shows issue on UPDATE/DELETE statements with nested INNER JOIN clause. SP-6881, SP-6764 : '...

    Modern Big Data Processing with Hadoop

    The book begins by quickly laying down the principles of enterprise data architecture and showing how they are related to the Apache Hadoop ecosystem. You will get a complete understanding of data ...

Global site tag (gtag.js) - Google Analytics