CATextLayer Animate individual characters - core-graphics

I am trying to find an elegant way to animate frame & opacity of individual characters in CATextLayer. However to make the completion of animation smooth I am unable to find a way to determine the position of each character in the text. The text as well are font are dynamic so statically determining kerning or spacing is not possible.

CATextLayer accepts NSAttributedString for string property.
Then, you can try to create multiple same text layers, and set some characters to transparent color.

Related

NSString drawInRect: is clipping ascenders

I want to draw blocks of text using [NSString drawInRect: withAttributes:]. However I found that when the font has ascenders that extend more than the default line height (some fonts have that, often the decorative ones), they are clipped at the top of the rect.
I can't use drawAtPoint: as far as I know, because I need line wrapping.
I played with various attributes like NSBaselineOffsetAttributeName to move the text down a bit in the rect, but nothing worked.
With a specific font that I have to work with, even the normal digits extend beyon the rect that way, and so every digit is clipped.
How can I solve this problem?

Custom text style drawing in an NSTextView

How would I go about drawing an NSTextView's text to look similar to: http://i.imgur.com/R2yPflH.png
I would normally use an NSAttributedString but I can't produce the rounded stroke properly.
I have code that converts an NSString to an NSBezierPath which works, but I am unsure how to draw it in an NSTextView.
Thank you in advance!
For custom drawing in an NSTextView, you will need to subclass NSLayoutManager and handle the drawing there. What you want to do won't be easy, though, I don't think. I've subclassed NSLayoutManager in the past to draw extra marks behind characters and between characters, but you will actually need to draw the affected ranges of text yourself, by overriding -drawGlyphsForGlyphRange:atPoint:. You can use the range passed in, convert it to a character range using -characterRangeForGlyphRange:actualGlyphRange:, and check the characters in the range are ones to which you wish to apply your custom drawing. For such ranges, you'll do the drawing yourself. The tricky part will be ensuring your string is drawn at the right location, taking into consideration line spacing and such. -lineFragmentRectForGlyphAtIndex:... and -locationForGlyphAtIndex: should be your friends here.

Drawing two shadows on text (Core Graphics)

I'm tying to draw two different shadows on some text to create an embossed effect. Here's the portion of my drawInRect where I draw the text with the first shadow (all the variables used are already defined):
CGContextSetShadowWithColor(context, textInnerShadowOffset, textInnerShadowBlurRadius, textInnerShadowColor.CGColor);
[textColor setFill];
[self.text drawInRect:rect withFont:self.font lineBreakMode:self.lineBreakMode alignment:self.textAlignment];
But now I'm faced with the problem of drawing the second shadow. I assume I'll need to change the shadow and draw the text again, but I need to do so without adding another copy of the text.
How can I draw text without really drawing the text itself? Changing the fill color to clearColor doesn't work. I've seen people use clipping masks for this, but AFAICT that will only work for simple shapes, not text.
Alternatively, is there an easier way to draw two shadows on the same text?
Two options, depending on the exact effect you want:
If you want the first, "upper" shadow to also contribute to the second, "lower" shadow underneath it, use a transparency layer.
Set your CGContext's shadow for the "lower" shadow
Create a transparency layer using CGContextBeginTransparencyLayer
Set the context's shadow for the "upper" shadow
Draw your text
End the transparency layer using CGContextEndTransparencyLayer
(Note that transparency layers can be quite expensive. It's best to call CGContextBeginTransparencyLayerWithRect and pass in as small a rect as you can.)
If you want the shadows to be independent -- the only thing that contributes to each shadow is the text -- you'll need to use a trick.
Set up the shadow with an additional large offset, big enough so that you can draw the text outside of the bounds of your context and have the shadow land in the correct place. That way you'll see only the shadow, but not the text.
Figure out what offset is "big enough". It will probably depend on the size of the context you're drawing into (based on your view), and maybe the bounds of the text.
Or, just fudge it: pick an absurdly large value like 5000 pt.
Set up your shadow. Add the big offset to its normal y offset.
Draw the text, offset vertically by the big offset.
Repeat 1-3 for each "lower" shadow, from back to front. Afterwards, draw the text and the "uppermost" shadow last, without the offset.

NSTextField weird left margins

I've been looking for a solution for this one all day.
I have 4 NSTextFields (actually subclassed for a few custom operations), which all share the same X position.
The problem is, some have different styles (light, regular, bold) and might have different sizes.
What happens is that, even though the X origin is the same, the 1st letter always has a bit of (consistently different) left margins.
Please see pic: https://dl.dropbox.com/u/1977230/Screen%20Shot%202012-12-11%20at%2017.55.58.png
I want to make sure that all lines start exactly at the same point, say 100px from the left.
Any idea how to override that weird padding?
Cheers
The margin you're talking about I'm pretty sure is the lineFragmentPadding on the NSTextContainer that is used by the NSTextField.
See the NSTextContainer reference:
http://developer.apple.com/library/Mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSTextContainer_Class/Reference/Reference.html
And here's a page from the tutorial on Text Layout:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/TextLayout/Concepts/CalcTextLayout.html
It states in that article:
The typesetter makes one final adjustment when it actually fits text
into the rectangle. This adjustment is a small amount fixed by the
NSTextContainer object, called the line fragment padding, which
defines the portion on each end of the line fragment rectangle left
blank. Text is inset within the line fragment rectangle by this amount
(the rectangle itself is unaffected). Padding allows for small-scale
adjustment of the text container’s region at the edges and around any
holes and keeps text from directly abutting any other graphics
displayed near the region. You can change the padding from its default
value with the setLineFragmentPadding: method. Note that line fragment
padding isn’t a suitable means for expressing margins; you should set
the NSTextView object’s position and size for document margins or the
paragraph margin attributes for text margins.
Unfortunately, it looks like NSTextField's NSTextContainer and NSLayoutManager are private and inaccessible, but it appears they are accessible in an NSTextView:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSTextView_Class/Reference/Reference.html#//apple_ref/occ/cl/NSTextView
So that may be the class you need to subclass if you want to have minute control over this kind of functionality.
Have you looked into CoreText? I think it may provide the facilities to do what you're looking for. From the docs...
The Core Text layout engine is designed specifically to make simple text layout operations easy to do and to avoid side effects.
You are able to access "font metrics," which enable you to (from the docs)...
For every font, glyph designers provide a set of measurements, called metrics, which describe the spacing around each glyph in the font. The typesetter uses these metrics to determine glyph placement. Font metrics are parameters such as ascent, descent, leading, cap height, x-height, and so on.
EDIT:
It just may be that NSTextField was not designed for what you are trying to do. NSTextField does custom layout apart from a NSLayoutManager.
You may need to upgrade to a NSTextView, which always has a dedicated NSLayoutManager attached. Apple has some example projects you could search for using NSLayoutManager and NSTextView.
If you're using NSTextField to draw simple static text, take a look at AppKit additions to NSString. Use sizeWithAttributes: to get size of the "text" image. Then use the size to calculate rects for drawing. Finally use one of draw methods to actually draw text. Don't forget to "round" result of sizeWithAttributes! It's not pixel aligned.
But if you need to draw something more complex than simple label, use Core Text. You can find very good example of how to use it in twui source code.

Draw bordered text efficiently in iOS

I have a view with custom drawRect method which has two text lines drawn in fixed width.
This view is being redrawn constantly at rate of about 16 timer per second with position of text and content of text changing all the time.
I also need my text to be drawn in such a way that it is clearly visible at any background, and for that purpose I do the following:
CGContextSetTextDrawingMode(ctx, kCGTextStroke); // Border mode
[string drawAtPoint:point withFont:font];
CGContextSetTextDrawingMode(ctx, kCGTextFill); // Text mode
[string drawAtPoint:point withFont:font];
This code draws the text in border mode with fixed line width, and then draws the text again at same position but in fill mode. In this way I get a blue text with white border around each letter.
The result is absolutely satisfactory for me except the performance.
Using Time Profiler I've noticed that about 70% of time spent for drawing the whole view is spent on execution of drawing the text in border (stroke) mode. But drawing in fill mode takes only 3% of the whole view drawing time. I think that this is not efficient considering the frequency of redrawing of the view.
So does anybody know how to draw the text with border around each letter in more efficient way?
Two options:
Use a shadow instead of drawing the outline: CGContextSetShadowWithColor
Cache the image of the string and it's outline using a CGLayer: CGLayerCreateWithContext
Explanation for 2:
Drawing text is highly optimized for the standard case where the letters are just filled with one color. Individual glyphs (letters) are not rendered from the outline each time. Instead the glyphs are drawn only once into buffered images which are cached and reused.
Since drawing outlines is seldom there's probably no caching of glyphs or other optimizations for this mode. So the idea is to do the caching yourself: Draw the whole string into one image, keep that image around and draw it instead of the text from within your drawRect: method.
There are several options how to do that:
Use a CGBitmapContext
Use UIGraphicsBeginImageContext
Use a CGLayer