Thursday, August 28, 2008

Core Text - CFStringGetParagraphBounds and Setting Paragraph Styles

I am working through the text classes writing unit tests. As Cello had no unit tests before I took on the OSX port I have been writing unit tests primarily for the delta, that is the changes I have made, rather than attempting to add unit tests for the all of the historic code. Sure I would love to but that is a project in it's self. I would hope that as I find bugs in the code (sure as are eggs are eggs there will be bugs) that I can add unit tests that demonstrate the bug and the fix after I have made it - but I will have to see.

I am using CFAttributedStringSetAttribute to add a paragraph style to the backing store. The only wrinkle here is that the style should be applied to the whole paragraph. Cello's pre Core Text code ensures that if you apply a paragraph style to a text range that the paragraph style will apply to complete paragraphs.

My hope was that CFAttributedStringSetAttribute with kCTParagraphStyleAttributeName would magically do this. It was a small hope and I quickly unvovered the folly of it when I wrote the unit test. Fortunately Core Foundation has a function CFStringGetParagraphBounds that will calculate the start and end points of a paragraph. The code falls out something like this.


CFRange selectedRange;
CFAttributedString attributedString;
CTParagraphStyleRef paragraphStyle;

// get the underlying string
CFStringRef str = CFAttributedStringGetString(attributedString);

CFIndex parBeginIndex, parEndIndex;

// get the get the bounds of the paragraph
CFStringGetParagraphBounds(str, selectedRange, &parBeginIndex, &parEndIndex, NULL);

// convert it to a CFRange
CFRange paragraphRange = CFRangeMake(parBeginIndex, parEndIndex - parBeginIndex);

// Set the attributes
CFAttributedStringSetAttribute(attributedString, paragraphRange, kCTParagraphStyleAttributeName, paragraphStyle);

Wednesday, August 20, 2008

CoreText, RTF, Unit Testing

I have added the reading and writing of RTF, using the initWithRTF and RTFFromRange cocoa methods. These deal with NS/CFAttriibutedStrings, and back onto NS/CFData. The wrinkle is that NSColor has to be converted to CFColor as I covered before here.

The accompanying Unit Tests were quite simple, Create a CFAttributedstring, add attributes, write as a CFData, read back and compare. They should be equal.

The great thing about unit testing is that it throws up problems - though sometimes I prefer the older ostrich ways. I got the full twelve rounds on his one even the crash just after the test harness says everything is OK and tries to shut shop for the night. The crash turned out to to be an owner-count problem, once the owners drop below zero who owns what, and what is what, is a little ambiguous. I remember in my student days having this kind of ownership ambiguity with the bank - exactly whose money was I spending, and why did they get so upset.

Probably the most unexpected problem was in hindsight the most obvious. If you specify a color as say rgb(0.1, 0.2, 0.3) it comes back from RTF different. there are two parts to the problem. The first is color space (or profile). Colors went in as device color and came back as a NSCalibratedRGBColorSpace. Converting between color spaces (or profiles) changes color values. The other is that RTF stores color values as 0..255 - and of course naturally you loose precision.

Wednesday, August 13, 2008

CoreText and CTMutableParagraphStyle

I have implemented Cello's paragraph and character styles as thin wrappers for CTParagraphStyle and CFDictionary. This corresponds to the way that text styling is handled in core text.

The API for CTParagraphStyle is rather thin. There is, for example, no CTMutableParagraphStyle - which means that API supports the creation of a CTParagraphStyle in one hit - but you can not set properties one by one. Fortunately the CTParagraphStyle is toll-free bridged to NSParagraphStyle which has a far richer interface including the ability to make a mutable copy.

Sunday, August 10, 2008

CoreText - the approach

I have been looking at the text in Cello in some detail. The approach that have decided to take is to change the fundamental text object so that it is a thin wrapper on a NS/CFAttributedString. Doing this gives me RTF import and Unicode support at a basic level. On top of this the CTFrame fits quite naturally into one of the Cello classes.

The fact that the PC version of cello is fundamentally based on RTF and that I can use core text as an RTF engine is a bit of a result. What it means is that I can deploy a unicode text editing engine (unicode character set, bidirectional text). This just has to be projected into the UI - but it a way basing things on core text and doing the UI in cocoa give or take the international input it is probably hard not to do.

The remaining piece in the multi-lingual text equation is vertical text - but this is not hard to do in core text - but it is not pressing.

Within Cello there are also classes that represent character and paragraph styles. I will try and preserve these but make them thin wrappers. The paragraph style would seem to map naturally onto the CTParagrahStyle, and the Character style as a NS/CFDictionary of values for the attributed string.

Wednesday, August 6, 2008

CoreText and RTF

Sample Unicode: 魚もたばません

Cello has text so it has support for line layout and line breaking. It has classes that represent Paragraphs, Styles, Style Runs and Layout blocks etc. Down at the metal the text is serialized (written and read) as RTF. Cello has its own RTF reader and writer. All this has to mesh with CoreText - that is where the glyph curves ultimately come from. Also in the back of my mind is the thought that I need to support Unicode, bidirectional line layout - and ultimately (I hope) vertical text.

I am still exploring the best course of action - but currently the solution that seems to be optimal is to use CoreText for everything.

Styled text is represented in Core Text as a NS/CFAttributedString. The NS/CFAttributedString is a Unicode string with runs of attributes. In the all roads lead to Rome approach of Cocoa and Core Foundation attributes are expressed as NS/CFDictionaries. So, for example, to set a font you would essentially create a dictionary with the kCTFontAttributeName attribute set to the CTFont that expressed the font that you wanted. To make the world a simpler place the API makes it possible to set individual attributes without having to create a dictionary first.

Cocoa also uses NS/CFAttributedString to represent styled text and has the capability to create an attributed string directly from an RTF source. So the following code fragment will read an RTF file from a URL.
CFAttributedStringRef ReadRTF(CFURLRef url)
{
NSData *data = [NSData dataWithContentsOfURL:(NSURL *)url];
NSAttributedString *result = [[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL];
return (CFAttributedStringRef) result;
}
I modified Apple's CoreTextTest sample code to import RTF using the code sinippet above. The resulting CFAttributedString works fine in Core Text except that NSColor is not suported. The console fills with messages:
CGContextSetFillColorWithColor: invalid context
CGContextSetStrokeColorWithColor: invalid context
The reason behind this seems to be that cocoa attributed string uses NSColor, Core Text uses CGColor the two objects are not toll free bridged and are not the same. This does no look like a hard thing to fix up after reading, and before writing.

One of the advantages of using the Cocoa RTF import is that, unlike the Cello import, it supports unicode.

Here is a screenshot of this post saved as RTF being drawn by Core Text with a little unicode.

Sunday, August 3, 2008

Converting Quartz Paths - MoveTo, LineTo, CurveTo

Cello has it's own support for curves - internally it has support for cubic and quadratic splines, as well as lines - the equivalent to what exists in CoreGraphics (Quartz) and generally in vector graphics systems. Within Cello there are classes that represent multi contoured splines and the attendant trickery to draw and manipulate them. They are of significance to me now as the existing Cello code requires the geometry of glyphs in its own internal format. So I needed to write conversion functions from a CGPaths to Cello's internal format.

Cello's curve format is unusual so writing the conversion has taken a little time. Most graphic systems that I have come across describe things in the form of, moveto, lineto and curveto. The idea being that "from where the pen is do this". Cello seems to be the other way round. Instead the curve verbs appear to be of the form linefrom, curvefrom, and movefrom.

My approach to writing the conversion has been first lift some existing code that converts the output of the D-Type font scalar (which is similar to the way that CoreGraphics thinks of curves) and to write a converter from Cello's internal format back to a CGPath. Again for this I could lift some code from Cello's output - again in the output the format paths are similar to CoreGraphics. Using the to and from converters it was possible to write Unit Tests to make sure that an original path is the same as a path converted, and then converted back. CFEqual will let you know if two paths are equal or not.

CPTAssert(::CFEqual(origPath, shape.AsPath()));
Converting and then back converting to, demonstrably, the same thing gives some idea of correctness but it does not guarantee that what you have done is correct, it just asserts that these two components are consistent. When I can look at the glyph shapes in the final output format I will be able to check this.