Is there any replacement for Windows's CreateFont(..) function in OSX?
I've tried to use NSFontDescriptor and its matchingFontDescriptorsWithMandatoryKeys: method, but this method doesn't find the "closest" font, it finds the matched characteristics and also it can return nil.
Is there any way to find the closest font by specified characteristics (like a CreateFont) in OSX?
UPDATED
Something strange happens with NSFontDescriptor..
I have two pieces of code:
on Cocoa:
NSDictionary* fontTraits = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:NSFontSansSerifClass], NSFontSymbolicTrait,
[NSNumber numberWithFloat:0.4], NSFontWidthTrait,
nil];
NSDictionary* fontAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
fontTraits, NSFontTraitsAttribute,
nil];
NSFontDescriptor* fontDescriptor = [NSFontDescriptor fontDescriptorWithFontAttributes:fontAttributes];
NSArray* matchedDescriptors = [fontDescriptor matchingFontDescriptorsWithMandatoryKeys:nil];
And using CoreText API:
CFMutableDictionaryRef fontTraits = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
float weight = 0.4;
CFNumberRef fontWeight = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloat32Type, &weight);
CFDictionaryAddValue(fontTraits, kCTFontWeightTrait, fontWeight);
int symbolicTraits = kCTFontSansSerifClass;
CFNumberRef fontSymbolicTraits = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &symbolicTraits);
CFDictionaryAddValue(fontTraits, kCTFontSymbolicTrait, fontSymbolicTraits);
CFMutableDictionaryRef fontAttributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(fontAttributes, kCTFontTraitsAttribute, fontTraits);
CTFontDescriptorRef fontDescriptor = CTFontDescriptorCreateWithAttributes(fontAttributes);
CFArrayRef matchedDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(fontDescriptor, 0);
I've created the same font descriptors in both cases, but in first case matchedDescriptors is nil and with CoreText API there are some fonts in matchedDescriptors. Is it a bug?
But in general, if I pass nil to XXXMatchingFontDescriptorsXXX as mandatory attribute, should it return at least one descriptor?
I would please file a Radar if you haven't. I just got bit by this same difference in behavior myself.
A surprising thing while debugging, I found that [fontDescriptor matchingFontDescriptorWithMandatoryKeys:] is actually defined, though not documented, and functions as expected for a single descriptor that is. Meaning, it works just like CTFontDescriptorCreateMatchingFontDescriptor().
While [fontDescriptor matchingFontDescriptorsWithMandatoryKeys:] definitely appears broken to me.
I found this problem when I was using [UIFont fontWithDescriptor:size:], and it was matching things just fine, and as expected. Then I switched to [fontDescriptor matchingFontDescriptorsWithMandatoryKeys:nil], and it returned nil for the same descriptor.
Something felt off, so I compared with CoreText APIs as you did, and came to the conclusion that I am sharing with you here.
I know you are looking at the list versions of these methods, I figured I would share that I went with this method in a UIFontDescriptor category:
- (nullable instancetype)my_matchingFontDescriptorWithMandatoryKeys:(nullable NSSet<UIFontDescriptorAttributeName> *)mandatoryKeys {
return (__bridge_transfer UIFontDescriptor *)CTFontDescriptorCreateMatchingFontDescriptor((__bridge CTFontDescriptorRef)self, (__bridge CFSetRef)mandatoryKeys);
}
Related
I am trying to implement searching in NSTextView with search query coming from my custom NSSearchField.
Sounds pretty simple, but I cannot get it working.
So far I've looked through all the Apple Documentation about NSTextFinder, its client and FindBarContainer. The TextFinder simply provides the FindBarView to the container, and container shows it when you activate searching.
All the communication between the client, container and TextFinder is hidden. It just looks like a black-box that is designed to work "as is" without any customisation or interference.
But what about - (void)performAction:(NSTextFinderAction)op method of NSTextFinder? Isn't it for sending custom commands to the TextFinder?
I was trying to assign a new search string to it with the following:
NSPasteboard* pBoard = [NSPasteboard pasteboardWithName:NSFindPboard];
[pBoard declareTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, NSPasteboardTypeTextFinderOptions, nil] owner:nil];
[pBoard setString:_theView.searchField.stringValue forType:NSStringPboardType];
NSDictionary * options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSTextFinderCaseInsensitiveKey,
[NSNumber numberWithInteger:NSTextFinderMatchingTypeContains], NSTextFinderMatchingTypeKey,
nil];
[pBoard setPropertyList:options forType:NSPasteboardTypeTextFinderOptions];
[textFinder performAction:NSTextFinderActionSetSearchString];
but that doesn't work and simply breaks the normal findBar operation.
I have a strong feeling that I am doing something wrong.
All I want is to have a standard search functionality in my own NSSearchField. Is that possible?
I bet I am not the first one who is not happy with normal findBar.
Your help is very needed and appreciated!
For an NSTextView, NSTextFinder is mostly just a user interface for NSString's func range(of searchString: String, options mask: NSString.CompareOptions = [], range rangeOfReceiverToSearch: NSRange) -> NSRange
If you want to implement your own search on an NSTextView, use that. To search forward, you construct a range starting at the end of the current selections's range and going to the end of the NSTextView's text. To search backward, construct a range starting at 0 and going to the beginning of the current selection's range, and tell NSString to use backwards search.
If NSString returns a .notFound range, implement wrap-around yourself.
If you need startsWith, endsWith or wholeWord you'll need to take the result NSString's func gives you, check to see if it will do, and if not adjust the range and call it again.
You can use NSComboBox. Return search value using below delegate:
- (NSString *)comboBox:(NSComboBox *)aComboBox completedString:(NSString *)substring
{
if ([aComboBox tag] == 101 || [aComboBox tag] == 102) {
NSArray *currentList;
if ([aComboBox tag] == 101) {
NSArray *keyArray = keySuggestions;
currentList = keyArray;
} else {
currentList = [NSArray arrayWithArray:self.valueSuggestions];
}
NSEnumerator *theEnum = [currentList objectEnumerator];
id eachString;
NSInteger maxLength = 0;
NSString *bestMatch = #"";
while (nil != (eachString = [theEnum nextObject])) {
NSString *commonPrefix = [eachString
commonPrefixWithString:substring options:NSCaseInsensitiveSearch];
if ([commonPrefix length] >= [substring length] && [commonPrefix
length] > maxLength)
{
maxLength = [commonPrefix length];
bestMatch = eachString;
break;
}
}
return bestMatch;
}
return substring;
}
I've got an app that displays photos using NSImage – specifically, -[NSImage drawInRect:fromRect:operation:fraction:]. I want to highlight areas of the photo that are completely burned out (maximum values in all components, pure white) using a color like red, as some digital cameras and image processing apps do, to help the user see whether the image is overexposed, and how badly.
I've been scratching my head as to how to do this. Options I've considered:
I could probably write a Core Image filter to do it; none of the built-in filters look up to the task. That seems like overkill, though; I've been reading through the docs, and it looks fairly complicated. Big learning curve.
I could scan through the bitmap data for the image and modify it as necessary. This is easy enough to code for one bitmap format, but the multitude of bitmap formats make it a rather annoying exercise, and speed is important here, so writing general-purpose code that renders the image up to some maximal common format and works on that bitmap would be too big a speed penalty.
As it happens, I am already scanning through images (handling all the different bitmap formats) at an earlier point in the code, to generate histogram data for the images. I could pretty easily add code at that point that would remember the burned-out pixels for later use. I'm not quite sure what the best way is to do that, though. A 1-bit-per-pixel NSBitmapImageRep? How would I draw it later, making the 1-pixels draw red and the 0-pixels draw transparent, for example? I don't want to make a 32-bit NSBitmapImageRep with an alpha channel and everything just for this purpose, as memory is not infinite and images are large. But there must be a way to draw a 1-bit mask in a given color, somehow.
Before forging ahead with one of these approaches, I thought I'd see whether anybody here has a better idea. Or maybe has implemented the CI filter in question already? Apart from the learning curve, that seems like the best approach I've thought of so far – no memory overhead, and probably faster than other options, too.
Thanks...
Ben Haller
Stick Software
OK, I implemented my own Core Image filter to do this. Wasn't as hard as I expected, although the documentation is not great for this stuff. The doc examples all assume you're using ARC, so if you're not, following those examples will give you various retain/release bugs. There was also a little weirdness with the CIFilterConstructor stuff, which did not quite go as documented. But overall pretty easy. CI is cool. My code is below, for anybody who might find it useful:
Header:
#import
#interface SSTintHighlightsFilter : CIFilter
{
CIImage *inputImage;
CIColor *highlightColor;
}
#end
Implementation file:
#import "SSTintHighlightsFilter.h"
static CIKernel *tintHighlightsFilter = nil;
#implementation SSTintHighlightsFilter
+ (void)initialize
{
[CIFilter registerFilterName:#"SSTintHighlightsFilter" constructor:(id )self
classAttributes:[NSDictionary dictionaryWithObjectsAndKeys:#"Tint Highlights", kCIAttributeFilterDisplayName, [NSArray arrayWithObjects:kCICategoryColorAdjustment, kCICategoryStillImage, nil], kCIAttributeFilterCategories, nil]];
}
+ (CIFilter *)filterWithName:(NSString *)name
{
CIFilter *filter = [[self alloc] init];
return [filter autorelease];
}
- (id)init
{
if (!tintHighlightsFilter)
{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *code = [NSString stringWithContentsOfFile:[bundle pathForResource:#"tintHighlightsAndShadows" ofType:#"cikernel"] encoding:NSASCIIStringEncoding error:NULL];
NSArray *kernels = [CIKernel kernelsWithString:code];
tintHighlightsFilter = [[kernels objectAtIndex:0] retain];
}
return [super init];
}
- (NSDictionary *)customAttributes
{
NSDictionary *attrs = #{
#"highlightColor" : #{ kCIAttributeClass : [CIColor class], kCIAttributeType : kCIAttributeTypeOpaqueColor }
};
return attrs;
}
- (CIImage *)outputImage
{
CISampler *src = [CISampler samplerWithImage:inputImage];
return [self apply:tintHighlightsFilter
arguments:[NSArray arrayWithObjects:src, highlightColor, nil]
options:[NSDictionary dictionaryWithObjectsAndKeys:[src definition], kCIApplyOptionDefinition, nil]];
}
#end
tintHighlights.cikernel:
kernel vec4 tintHighlights(sampler inputImage, __color highlightColor)
{
vec4 originalColor, tintedColor;
float sum;
// fetch the source pixel
originalColor = sample(inputImage, samplerCoord(inputImage));
// calculate the color component sum as a way of testing whether we are black or white
sum = originalColor.r + originalColor.g + originalColor.b;
// replace pixels that are white with the highlight color
tintedColor = (sum > 2.99999999999999999999999) ? highlightColor : originalColor;
// preserve alpha
tintedColor.a = originalColor.a;
return tintedColor;
}
using the filter:
+ (NSImage *)showHighlightsInImage:(NSImage *)img dstRect:(NSRect)dstRect
{
NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
NSRect dstRectForCGImage = dstRect; // because the method below wants a pointer, and I don't trust it not to modify my rect...
CGImageRef cgImage = [img CGImageForProposedRect:&dstRectForCGImage context:currentContext hints:nil];
CIImage *inputImage = [[CIImage alloc] initWithCGImage:cgImage];
[SSTintHighlightsFilter class]; // get my filter initialized
CIFilter *highlightFilter = [CIFilter filterWithName:#"SSTintHighlightsFilter"];
[highlightFilter setValue:inputImage forKey:#"inputImage"];
[highlightFilter setValue:[CIColor colorWithRed:1.0 green:0.0 blue:0.0] forKey: #"highlightColor"];
[inputImage release];
CIImage *outputImage = [highlightFilter valueForKey:#"outputImage"];
NSImage *resultImage = [[NSImage alloc] initWithSize:[img size]];
[resultImage addRepresentation:[NSCIImageRep imageRepWithCIImage:outputImage]];
return [resultImage autorelease];
}
I'm not sure that I'm handling the alpha entirely robustly, with premultiplication issues and so forth, but apart from that possible glitch it is working great.
Using the QTKit framework, I'm developing a little app.
In the app, I'm trying to append a movie after a other movie, which in essence is already working (most of the time), but I'm having a little trouble with the appended movie. The movie is which I'm appending to is quite big, like 1920x1080, and the appended movie is usually much smaller, but I never know what size it exactly is. The appended movie sort of stays its own size in the previous 1920x1080 frame, as seen here:
Is there anyone familiar with this? Is there a way I can scale the movie which I need to append to, to the size of the appended movie? There is no reference of such a thing in the documentation.
This is are some relevant methods:
`QTMovie *segmentTwo = [QTMovie movieWithURL:finishedMovie error:nil];
QTTimeRange range = { .time = QTZeroTime, .duration = [segmentTwo duration] };
[segmentTwo setSelection:range];
[leader appendSelectionFromMovie:segmentTwo];
while([[leader attributeForKey:QTMovieLoadStateAttribute] longValue] != 100000L)
{
//wait until QTMovieLoadStateComplete
}
NSDictionary *exportAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], QTMovieExport,
[NSNumber numberWithLong:kQTFileTypeMovie], QTMovieExportType, nil];
NSString *outputFile = [NSString stringWithFormat:#"%#.mov", onderwerp];
NSString *filepath = [[#"~/Desktop" stringByExpandingTildeInPath] stringByAppendingFormat:#"/%#", outputFile];
BOOL succes = [leader writeToFile:filepath withAttributes:exportAttributes error:&theError];
Leader is initialized like this:
NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys:path, QTMovieFileNameAttribute, [NSNumber numberWithBool:YES], QTMovieEditableAttribute, nil];
leader = [QTMovie movieWithAttributes: movieAttributes error:&error];
This contained all the information I need, although without using the QTKit framework. QTKit - Merge two videos with different width and height?
Is there any equivalent method in AppKit (for Cocoa on Mac OS X) that does the same thing as UIKit's [NSString sizeWithFont:constrainedToSize:]?
If not, how could I go about getting the amount of space needed to render a particular string constrained to a width/height?
Update: Below is a snippet of code I'm using that I expect would produce the results I'm after.
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize: [NSFont smallSystemFontSize]], NSFontAttributeName,
[NSParagraphStyle defaultParagraphStyle], NSParagraphStyleAttributeName,
nil];
NSSize size = NSMakeSize(200.0, MAXFLOAT);
NSRect bounds;
bounds = [#"This is a really really really really really really really long string that won't fit on one line"
boundingRectWithSize: size
options: NSStringDrawingUsesFontLeading
attributes: attributes];
NSLog(#"height: %02f, width: %02f", bounds.size.height, bounds.size.width);
I would expect that the output width would be 200 and the height would be something greater than the height of a single line, however it produces:
height: 14.000000, width: 466.619141
Thanks!
Try this one:
bounds = [value boundingRectWithSize:size options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin attributes:attributes];
The newer NSExtendedStringDrawing category API (methods with the NSStringDrawingOptions argument) behaves in the single line mode. If you want to measure/render in multi line mode, want to specify NSStringDrawingUsesLineFragmentOrigin.
EDIT: You should be able to do things the normal way in Lion and later. The problems described below have been fixed.
There is no way to accurately measure text among the current Mac OS X APIs.
There are several APIs that promise to work but don't. That's one of them; Core Text's function for this purpose is another. Some methods return results that are close but wrong; some return results that seem to mean nothing at all. I haven't filed any bugs on these yet, but when I do, I'll edit the Radar numbers into this answer. You should file a bug as well, and include your code in a small sample project.
[Apparently I have already filed the Core Text one: 8666756. It's closed as a duplicate of an unspecified other bug. For Cocoa, I filed 9022238 about the method Dave suggested, and will file two NSLayoutManager bugs tomorrow.]
This is the closest solution I've found.
If you want to constrain the string to a certain size, you use -[NSString boundingRectWithSize:options:attributes:]. The .size of the returned NSRect is the size you're looking for.
Here is a more complete example of how you can do this using boundingRectWithSize.
// get the text field
NSTextField* field = (NSTextField*)view;
// create the new font for the text field
NSFont* newFont = [NSFont fontWithName:#"Trebuchet MS" size:11.0];
// set the font on the text field
[field setFont:newFont];
// calculate the size the textfield needs to be in order to display the text properly
NSString* text = field.stringValue;
NSInteger maxWidth = field.frame.size.width;
NSInteger maxHeight = 20000;
CGSize constraint = CGSizeMake(maxWidth, maxHeight);
NSDictionary* attrs = [NSDictionary dictionaryWithObjectsAndKeys:NSFontAttributeName,newFont, nil];
NSRect newBounds = [text boundingRectWithSize:constraint
options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin
attributes:attrs];
// set the size of the text field to the calculated size
field.frame = NSMakeRect(field.frame.origin.x, field.frame.origin.y, field.frame.size.width, newBounds.size.height);
Of course, for additional info, take a look at the Apple documentation:
Options for the attributes dictionary
boundingRectWithSize
If you search the documentation for NSString, you will find the "NSString Application Kit Additions Reference", which is pretty much analogous to the UIKit counterpart.
-[NSString sizeWithAttributes:]
is the method you are looking for.
Trying to create a Skat-Game, I encountered the following problem:
isBidding is a Boolean value indicationg, the program is in a certain state,
[desk selected] is a method calling returning the current selected player,
chatStrings consists of dictionaries, saving strings with the player, who typed, and what he typed
- (void)drawRect:(NSRect)rect{
NSMutableDictionary * attributes = [NSMutableDictionary dictionary];
[attributes setObject:[NSFont fontWithName:playerFont size:playerFontSize] forKey:NSFontAttributeName];
[attributes setObject:playerFontColor forKey:NSForegroundColorAttributeName];
[[NSString stringWithFormat:#"Player %i",[desk selected] + 1] drawInRect:playerStringRect withAttributes:attributes];
if (isBidding){
[attributes setObject:[NSFont fontWithName:chatFont size:chatFontSize] forKey:NSFontAttributeName];
[attributes setObject:chatFontColor forKey:NSForegroundColorAttributeName];
int i;
for (i = 0; i < [chatStrings count]; i++, yProgress -= 20){
if (isBidding)
[[NSString stringWithFormat:#"Player %i bids: %#",
[[[chatStrings objectAtIndex:i]valueForKey:#"Player"]intValue],
[[chatStrings objectAtIndex:i]valueForKey:#"String"]],
drawAtPoint:NSMakePoint([self bounds].origin.x, yProgress) withAttributes:attributes];
else
[[NSString stringWithFormat:#"Player %i: %#",[[[chatStrings objectAtIndex:i] valueForKey:#"Player"]intValue],
[[chatStrings objectAtIndex:i]valueForKey:#"String"]]
drawAtPoint:NSMakePoint([self bounds].origin.x, yProgress) withAttributes:attributes];
}
}
if (isBidding)
[[NSString stringWithFormat:#"Player %i bids: %#",[desk selected] + 1, displayString]
drawAtPoint:NSMakePoint([self bounds].origin.x, yProgress) withAttributes:attributes];
else
[[NSString stringWithFormat:#"Player %i: %#",[desk selected] + 1, displayString]
drawAtPoint:NSMakePoint([self bounds].origin.x, yProgress) withAttributes:attributes];
yProgress = chatFontBegin;
}
This is the part determining the string's content, the string is contributed by an [event characters] method.
-(void)displayChatString:(NSString *)string{
displayString = [displayString stringByAppendingString:string];
[self setNeedsDisplay:YES];
}
The problem if have is this:
when typing in more than two letters the view displays NSRectSet{{{471, 574},{500, 192}}}
and returns no more discription when I try to print it.
then I get an EXC_BAD_ACCESS message, though I have not released it (as far as I can see) I also created the string with alloc and init, so I cannot be in the autorelease pool.
I also tried to watch the process when it changes with the debugger, but I couldn't find any responsible code.
As you can see I am still a beginner in Cocoa (and programming in general), so I would be really happy if somebody would help me with this.
This code is buggy:
-(void)displayChatString:(NSString *)string{
displayString = [displayString stringByAppendingString:string];
[self setNeedsDisplay:YES];
}
stringByAppendingString: returns an autoreleased object. You need to retain or copy it if you want it to stick around (maybe by using a copying/retaining property like self.displayString = [displayString stringByAppendingString:string]; and the matching property declaration/synth.
So at the moment, you are assigning an object which will be deallocated, but you later access it giving the error.
I can't puzzle out your code but I can tell you something about the odd return.
NSRectSet is a private type inside the Foundation framework. You shouldn't ever see it. Its used internally IIRC to represent nested rectangles such as those of stacks of views.
You're either getting an odd memory problem that causes a string pointer to actually point at the NSRectSet or you've screwed up your method nesting and you're assigning the NSRectSet value to a string.