I generate a rather complex NSAttributedString in my iOS 3.2 application (iPad), including formatting options of type CTParagraphStyleSetting, in particular with values for kCTParagraphStyleSpecifierMinimumLineHeight and kCTParagraphStyleSpecifierParagraphSpacing.
When I try to draw this attributed string into a non-rectangular CGPath, Core Text draws it but without the line spacing defined; that is, all text appears crammed in paragraphs without line spacing. Needless to say, it does not look as pretty as if the CGPath was simply defined using a single call to CGPathAddRect()!
Is there any setting I can specify (to my CTFramesetterRef or to the CTFrameRef associated to the culprit CGPath) to avoid losing all line height information?
Thanks!
The CoreText programming guide indicates that only a rectangular CGPath is allowed:
http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/CoreText_Programming/Overview/Overview.html#//apple_ref/doc/uid/TP40005533-CH3-SW1
As of iOS 4.2, Core Text accepts non-rectangular paths. See the docs for CTFramesetterCreateFrame:
http://developer.apple.com/library/iOS/documentation/Carbon/Reference/CTFramesetterRef/Reference/reference.html#//apple_ref/c/func/CTFramesetterCreateFrame
For a sample, see "Displaying Text in a Nonrectangular Region":
https://developer.apple.com/library/iOS/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW9
Related
I am trying to draw an NSAttributedString (actually, a constructed NSMutableAttributedString) where the "original" text has been struck and replacement text inserted above it (I'm trying to replicate the look/feel of an Ancient Greek manuscript).
My technique is a combination of NSBaselineOffsetAttributeName with NSKernAttributeName, but it appears that using a negative value for NSKernAttributeName "wipes away" the strikethrough of the text, even if the characters don't overlap.
If I put an extra space after the "A" character (in the original text), the "A" gets the strikethrough, but the "EI" is also offset to the right. So, it appears that the offset/kerning of the "EI" text affects how much of the strikethrough actually occurs.
Here's what I'd like to reproduce (I don't care about the angle; it's not about a picture-perfect reproduction; just the gist):
Here's what is currently happening:
This is when I add an extra space after the strikethrough:
So, the only other thing I can think of would be to render a separate NSAttributedString in the correct place, separate from the current one, but I have no idea how to calculate the location of a specific character in an NSAttributedString when it's drawn. I'm drawing to a PDF, not to any on-screen control like a UILabel. Alternatively, I could draw the "strikethrough" myself as a line, but that seems to still require knowing the coordinates for the text in question, which is calculated on-the-fly, and I hope to use this method to reproduce a large sample of ancient texts, which means doing it by hand just isn't a good answer here.
Anything I'm missing, or any out-of-the-box ideas to try?
I'm trying to find the height of a single line of text using Text Kit. The Calculating Text Height documentation says
Note: You don’t need to use this technique to find the height of a single line of text. The NSLayoutManager method
defaultLineHeightForFont: returns that value. The default line
height is the sum of a font’s tallest ascender, plus the absolute
value of its deepest descender, plus its leading.
However, when I checked the NSLayoutManager Class Reference (and also testing in Xcode) I didn't see a defaultLineHeightForFont: method. Why is that? Is there an alternative?
Notes:
I'm not trying to set the line height.
There are ways to do this apparently that do not use Text Kit. However, since I already have my Text Kit stack set up and the documentation seems to indicate that it is possible, I would like to use Text Kit.
I prefer Swift answers but Objective-C is ok, too.
Update
I did not realize that the documentation I linked to was for OS X until I read #JoshCaswell's comment. I am specifically looking for how to do this in iOS.
In TextKit, you can use the layout manager to get the line height using
- lineFragmentRectForGlyphAtIndex:effectiveRange:. As you notice from the name of this function, it is named "line fragment". Why? Because it is not always the case that your layout has a complete line, but instead you might have an exclusion path (that contains a shape or image) where the text is wrapped around it. In this case you have part of the line (line fragment) where your text is laid out.
In the delegate of the layout manager, you would implement a function that might look as follows:
-(void)layoutManager:(NSLayoutManager *)layoutManager didCompleteLayoutForTextContainer:(NSTextContainer *)textContainer atEnd:(BOOL)layoutFinishedFlag{
//This is only for first Glyph
CGRect lineFragmentRect = [layoutManager lineFragmentUsedRectForGlyphAtIndex:0 effectiveRange:nil];
// The height of this rect is the line height.
}
I would like to implement karaoke-like progress highlight for iOS.
I know I could use NSAttributedString and highlight the text character by character. However, I would like the highlight to progress pixel by pixel, not character by character.
Any ideas?
P.S. No need for sample code, just point me to the right direction.
Here is an example:
I can't think of any automatic way to do that. There would be several problems to solve. It would be pretty hard, I think.
The hardest would probably be figuring out the pixel position of each word so you can pace the coloring to match the timing within the music. With text and attributed layout, you could probably get the text engine to give you the boundaries of each word and then apply the color attribute to each word as it's spoken/sung. You'd have to have data about the time offset for the beginning and end of each word's being sung.
You might have to use Core Text to get layout information about the bounding rectangles of each word.
Once you get that you could build a path (UIBezierPath or CGPath; they're pretty interchangeable) that follows the flow of the text, and then install that path in to a shape layer. You could then make the text transparent, make the shape layer a colored background that shows through, and animate the shape layer's strokeStart and/or strokeEnd properties to make it fill the text. You might need to do it word by word with a short animation that interpolates between one word and the next to get the timing right.
You probably want to have a look at Core Text, which is the lower level framework used for laying out text, using this you can obtain necessary paths that you need to render said effect (I suggest starting from answers similar to this)
There are plenty of answers for alternative, perhaps simpler answers, for example character by character or word-by-word, which may be easier to implement.
I'm attempting to draw a richly laid out text view on iPhone that features:
Custom paragraph spacing (kCTParagraphStyleSpecifierParagraphSpacing)
Custom paragraph first-line indentation (kCTParagraphStyleSpecifierFirstLineHeadIndent)
Justified alignment (kCTParagraphStyleSpecifierAlignment)
Finally, a drop cap on my first paragraph
I'm using OHAttributedLabel. The first three points I achieved without much trouble by just setting some paragraph style attributes on my NSAttributedString.
The drop cap I managed to implement by hacking OHAttributedLabel:
Cut out a rectangular region out of the main paragraph's CGMutablePathRef the size of the drop cap by adding an extra CGPathAddRect, as detailed in this excellent blog post.
Drawing the large character in this region with an extra CTFrameDraw call.
My problem: The paragraph styles and the custom text path are incompatible. When I cut a rectangular chunk out of the main text's path, all the paragraph styles seem to get thrown away.
Does anyone know a way to make them work together? Or can anyone think of another way to implement drop caps? (Short of using a UIWebView + CSS, which I'd rather not have the overhead of!)
Thanks!
You can use straight Core Text to achieve this, in the following post I explain the use of 2 framesetters to lay out text with drop caps in a UIView. In the code example (there's also a link to a github repo) you'll be able to see where the paragraph styles are created and applied to the main text view.
https://stackoverflow.com/a/14639864/1218605
Does the API support this? If not, how could I do it?
There is CTFontCreatePathForGlyph in Core Text, which can translate a single character into a path. I could use that in a loop to create my string as a path, but I'd have to deal with spacing and kerning and all the other nasty things. I'm looking for a string path that would looks the same if it was drawn in a UILabel with same font and size.
There is an article called Low-level text rendering by Ohmu that covers this pretty extensively. I have tried this code and can confirm that it works.
It does use Core Text, though, so the rendering is probably not exactly like that of a UILabel. Also, the sample article only deals with a single line. To extend it to multiple lines, you have to setup the complete Core Text system. Instead of:
CTLineRef line = CTLineCreateWithAttributedString( attStr ) ;
you need to set up a CTFramesetterRef (CTFramesetterCreateWithAttributedString) and CTFrameRef (CTFramesetterCreateFrame) as described in the Core Text Programming Guide. You can then get all lines in the frame with CTFrameGetLines().
You would then wrap the for loop in the sample article:
CFArrayRef runArray = CTLineGetGlyphRuns(line);
// for each RUN
for (CFIndex runIndex = 0; runIndex < CFArrayGetCount(runArray); runIndex++)
with another loop that iterates over all lines. The inner core of the loop should be identical.
The sample article creates a graphics context and adds the paths for the single glyphs to this context's path, but you can just as easily create a CGMutablePathRef or UIBezierPath and add the single glyph's paths to that object.
One thing that is not 100% clear to me without testing this is how to adjust the vertical position of the glyphs in the final path. You probably have to call CTFrameGetLineOrigins() to get the position of each line and add this position to each rendered glyph (possibly after transforming it with the text matrix).
Look at the UIBezierPath+TextPaths category in this project: https://github.com/aderussell/string-to-CGPathRef.
It shows how to create paths for single and multi-line strings and how to use them with CAShapeLayers.
The category is just a wrapper and the internal functions can be used in iOS or Mac OS X, you just need to link your project to the CoreText framework.