NSTextField weird left margins - objective-c

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.

Related

NSTextField vertically misaligns text when using non-default typeface

I am having trouble making text fields look acceptable when using different typefaces.
See this screenshot taken from a test app I made to demonstrate the problem. It consists of a single XIB, with no code in the delegate or anywhere else. This is on OSX Mavericks with Xcode 5.1.1 but I haven't tried on other versions.
The default system font looks fine in the top text field, as you'd expect. Compare this to the one below: same exact size and shape, all I did differently was change the typeface in Interface Builder. Text is pushed downwards, and descending letters (lowercase pqjg) are clipped. The Menlo example below is also pushed downwards, though not quite as badly.
How do I fix this?
A quick test shows Lucida Grande and Helvetica require 17 pixels, and Menlo requires 19 pixels:
Note that this excludes the border and shadows, so you need your text view to be significantly larger than that to guarantee it will fit.
Interface Builder has been specifically designed for Lucida Grande, it knows it can get away with being too small because that font doesn't use all the space it has available. Doesn't work well with other fonts.
You can resolve this a couple of different ways:
• Enable "Uses Single Line Mode" on the Text Field Panel. When I first
encountered this issue it wasn't obvious that this would automatically
align the text vertically within an NSTextField.
or...
• Simply adjust the size of the NSTextField using the adjustment boxes
that appear after you click on it. You can also adjust them through
the "Control" properties.

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.

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.