NSString drawInRect: is clipping ascenders - objective-c

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?

Related

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

UILabel with no whitespace above or below text

I have this UILabel in an app where the bottom of the text needs to (apppear to) rest on the edge of another uiview. The label also gets scaled by arbitrary amounts. The problem is that applying a scale to the label also scales the whitespace below the text in the label. So for instance scaling by 2.0f makes the whitespace twice as big, pushing the text farther away from the edge.
Is there a simple way of making a label perfectly fit the text's size so that the bottom pixel of the text is at the very bottom of the label view?
Let me present you, the most useful method for these situations:
[myString sizeWithFont: ....];
This method (and its multiple variations) return the size that a NSString instance will use, therefore, you can scale the UILabel to your needs. iOS lacks a "Vertical Alignment" option.
Here is a similar stackoverflow question, if you're still in doubt.

NSString sizeWithAttributes: Inaccuracy

I want to know the width of an NSString displayed on the screen in pixels. So I can fit an NSTextField its bounds to be exactly the length of the string itself. So I used IB's "Label" NSTextField and for those who don't know what I mean, I got a label with title "Label", font "Lucida Grande 13px", not selectable, not editable, regular size, no background and according to IB its width is 38px wide.
If I want to get its width programatically I use
[#"Label" sizeWithAttributes: [NSDictionary dictionaryWithObject: [NSFont fontWithName: #"Lucida Grande" size: 13] forKey: NSFontAttributeName]].width
Which will give me 33.293457 . So that's about 5 px of the real width..
I believe what you are noticing is the difference between the frame of the control and it's layout frame. See Frame vs Layout Frame for a good explanation.
You are doing the right thing in computing the width. For a Label, there is no extra padding at the top or bottom of the control, which is why you saw no problems with the height of your control. However, on the left and right of the control, there are an additional three pixels. This can be verified by looking at the Frame and Layout of a Label control in IB.
So, the ~5 pixels you noticed is actually exactly 6. Once you take into account this padding, you should have no further trouble.
Unfortunately, there is no API to determine what the padding is for various controls (Push Buttons have an additional 6 pixels on each side). I would suggest filing a bug report at http://bugreport.apple.com - Apple does base what APIs they provide in part on the number of requests for them. While IB will tell you, you will need to code those values yourself. If they change in the next OS release, you will need to update your application.
That actually sounds reasonable. You are comparing two different widths; the width of a raw string vs. the width of a string contained in an NSTextField. The contained string likely has just a tad bit of padding on either end, among other minor differences.