Getting font name from the file

I have this program that works just fine here, but it crashes at the customer. He was nice enough to save a crash report.

From I gather reading it, it seems to happen in a thread where I gather all the font names within a given folder, and use a declare to get the font name.

What does the + numbers on the right in Thread 6 mean ? For instance

0x0029b7ca Module1.getPostScriptName%s%s + 798

Is it the number of times the method was called ?

Here is the code :

[code]Function getPostScriptName(URL as String) As String
dim f as folderitem = GetFolderItem(URL, FolderItem.PathTypeShell)

if f <> nil then
// - We have a True Type Font file, first step to make a CFURL.
// CFURLRef CFURLCreateFromFSRef (
// CFAllocatorRef allocator,
// const struct FSRef *fsRef
// );

declare function CFURLCreateFromFSRef lib "Cocoa" ( allocator as Ptr, fsRef as Ptr ) as Ptr
Dim CFURLRef as Ptr = CFUrlCreateFromFSRef( nil, f.MacFSRef )

if CFUrlRef = nil then
  'Msgbox "Failed to get the CFURLRef of folder """+ f.nativePath + """"
  return f.name
else
  'SWIFT
  'func CTFontManagerCreateFontDescriptorsFromURL(_ fileURL: CFURL!) -> CFArray!
  
  declare function CTFontManagerCreateFontDescriptorsFromURL lib "Cocoa" ( fontURL as Ptr) as CFTypeRef
  Dim aCFArray As CFTyperef = CTFontManagerCreateFontDescriptorsFromURL( CFURLRef )
  
  'func CTFontDescriptorCopyAttribute(_ descriptor: CTFontDescriptor!,
  '_ attribute: CFString!) -> AnyObject!
  
  declare function CTFontDescriptorCopyAttribute lib "Cocoa" (CTFontDescriptor as Ptr, Attribut as CFStringRef) as CFStringRef
  Dim attribute as CFStringRef = "NSFontVisibleNameAttribute"
  
  declare function CFArrayGetCount lib "CoreFoundation.framework" (theArray as CFTypeRef) as Integer
  declare function CFArrayGetValueAtIndex lib "CoreFoundation.framework" (theArray as CFTypeRef, idx as Integer) as Ptr
  
  Dim ub As Int32 = CFArrayGetCount( aCFArray ) - 1
  For i As Int32 = 0 To ub
    Dim aCTFontDescriptorRef As Ptr = CFArrayGetValueAtIndex( aCFArray, i )
    // then use CTFontDescriptorCopyAttribute() to get the name
    'System.DebugLog CTFontDescriptorCopyAttribute(aCTFontDescriptorRef, attribute)
    Return CTFontDescriptorCopyAttribute(aCTFontDescriptorRef, attribute)
  Next
  // - As we had a valid CFURLRef from a 'Create' or 'Copy' API, we must release it.
  declare sub CFRelease lib "Cocoa" ( ref as ptr )
  CFRelease CFUrlRef
end if

end if

End Function
[/code]

<https://xojo.com/issue/42530>

I am not that comfortable with Mac OS X declares. I wonder if I did it right. Maybe it leaks or something.

Will appreciate any insight.

It could be related to a specific font that may be malformed somehow.

The last thing before the crash in thread 6 is CFArrayGetCount

Looking at the code I see it collects the names as an array, and then you use the arraycount-1 to do a for loop. A malformed font might not return any names, and when you use count-1 it might get -1 as a value. My guess is it would crash when you try to do the for loop 0 To ub when ub = -1

That’s my guess based on my limited knowledge of crash log reading. Send the user a build that logs each font name as it tries, and see if you can catch it crashing on a specific font.

Actually, before I try to get the PostScript name, I validate the font is good by activating it app-wide, then de-activating it.

I do suspect some glitch in my implementation of CTFontManagerCreateFontDescriptorsFromURL.

First thing I’d mention, is the return statement bypasses your CFRelease call… could be an issue. Also, CFUrlCreateFromFSRef is deprecated, maybe try CFURLCreateWithFileSystemPath instead. 32bit deprecated api calls are risky these days since 32 bit doesn’t get much attention from Apple, and deprecated API’s likely get none.

But it looks like the crash is from
Dim aCFArray As ptr = CTFontManagerCreateFontDescriptorsFromURL( CFURLRef )

Add a nil check there and I’ll bet you’ll find a bad font (or maybe an alias to a font?)

This line in the crash log tells me the api was given a nil ptr
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000000

I was able to reproduce the crash by adding a considerable number of fonts (18000).

The issue is definitely in getPostScriptName. If instead of going through the declare, I simply return the file name, no crash.

Now I wish I knew what to do to clean up my method.

I did what was advised. Now the CFURL is cleaned and I test for aCTFontDescriptorRef being nil. Yet, it still crashes. I have attached the new crash report to the bug report in Feedback.

[code] dim f as folderitem = GetFolderItem(URL, FolderItem.PathTypeShell)
dim result as string
'return f.name

if f <> nil then
// - We have a True Type Font file, first step to make a CFURL.
// CFURLRef CFURLCreateFromFSRef (
// CFAllocatorRef allocator,
// const struct FSRef *fsRef
// );

declare function CFURLCreateFromFSRef lib "Cocoa" ( allocator as Ptr, fsRef as Ptr ) as Ptr
Dim CFURLRef as Ptr = CFUrlCreateFromFSRef( nil, f.MacFSRef )

if CFUrlRef = nil then
  Msgbox "Failed to get the CFURLRef of folder """+ f.nativePath + """"
else
  'SWIFT
  'func CTFontManagerCreateFontDescriptorsFromURL(_ fileURL: CFURL!) -> CFArray!
  
  declare function CTFontManagerCreateFontDescriptorsFromURL lib "Cocoa" ( fontURL as Ptr) as CFTypeRef
  Dim aCFArray As CFTyperef = CTFontManagerCreateFontDescriptorsFromURL( CFURLRef )
  
  'func CTFontDescriptorCopyAttribute(_ descriptor: CTFontDescriptor!,
  '_ attribute: CFString!) -> AnyObject!
  
  declare function CTFontDescriptorCopyAttribute lib "Cocoa" (CTFontDescriptor as Ptr, Attribut as CFStringRef) as CFStringRef
  Dim attribute as CFStringRef = "NSFontVisibleNameAttribute"
  
  declare function CFArrayGetCount lib "CoreFoundation.framework" (theArray as CFTypeRef) as Integer
  declare function CFArrayGetValueAtIndex lib "CoreFoundation.framework" (theArray as CFTypeRef, idx as Integer) as Ptr
  
  Dim ub As Int32 = CFArrayGetCount( aCFArray ) - 1
  
  'For i As Int32 = 0 To ub
  if ub > -1 then
    Dim aCTFontDescriptorRef As Ptr = CFArrayGetValueAtIndex( aCFArray, 0 )
    // then use CTFontDescriptorCopyAttribute() to get the name
    'System.DebugLog CTFontDescriptorCopyAttribute(aCTFontDescriptorRef, attribute)
    if aCTFontDescriptorRef <> nil then
      result = CTFontDescriptorCopyAttribute(aCTFontDescriptorRef, attribute)
    end if
    'Next
  end if
  // - As we had a valid CFURLRef from a 'Create' or 'Copy' API, we must release it.
  declare sub CFRelease lib "Cocoa" ( ref as ptr )
  CFRelease CFUrlRef
end if

end if

if Result <> “” then
Return Result
else
Return f.name
end if[/code]

Since these are single font name per file, I have removed the loop at the end.

I beleive it’s the CFArray that is causing the crash…

Add a check after

     Dim aCFArray As CFTyperef = CTFontManagerCreateFontDescriptorsFromURL( CFURLRef )

if aCFArray=nil then msgBox "Could not create descripors array for"+ f.nativepath return "" end if

Or if CFTyperef is integer, then

if aCFArray=0

Actually I tried to move the code to Console, and indeed the mere declaration of a CFArray creates a segmentation fault there.

I use the MacOSLib CFArray and CFTypeRef classes in CoreFoundation but have to admit I did not dare doing my own.

I am going to message Christian to see if he got a plugin that can do the same. It is probably the easiest solution if he has that.

Here’s a cleaned up version that returns a string array of font names in the file. Should work in console too. I think the console version crashed because console apps don’t use the Cocoa framework, so it’s not loaded. I changed it to use CFURLCreateWithFileSystemPath to be safe.

[code]Function getPostScriptNames(URL as String) As String()
const kCFURLPOSIXPathStyle=0
Dim CFURLRef as Ptr

//input should be folderitem.nativepath
declare function CFURLCreateWithFileSystemPath lib “CoreFoundation” ( allocator as ptr, fontURL as CFStringRef, style as uint32, isdirectory as boolean) as ptr
CFURLRef=CFURLCreateWithFileSystemPath(nil,URL,kCFURLPOSIXPathStyle,false)

//get all of the fonts in this file
declare function CTFontManagerCreateFontDescriptorsFromURL lib “CoreText” ( fontURL as Ptr) as ptr
Dim aCFArray As ptr = CTFontManagerCreateFontDescriptorsFromURL( CFURLRef )

declare sub CFRelease lib “Cocoa” ( ref as ptr )
CFRelease CFUrlRef //release this as soon as we’re done with it.

//no descriptors creates
if aCFArray=nil then Return nil

declare function CTFontDescriptorCopyAttribute lib “CoreText” (CTFontDescriptor as Ptr, Attribut as CFStringRef) as CFStringRef
Dim attribute as CFStringRef = “NSFontVisibleNameAttribute”

declare function CFArrayGetValueAtIndex lib “CoreFoundation.framework” (theArray as ptr, idx as Integer) as Ptr
declare function CFArrayGetCount lib “CoreFoundation.framework” (theArray as ptr) as integer
dim ub as Integer=CFArrayGetCount(aCFArray)-1

dim res() as String
for i as integer=0 to ub
//add the next name to the results
Dim aCTFontDescriptorRef As Ptr = CFArrayGetValueAtIndex( aCFArray, i )
res.Append CTFontDescriptorCopyAttribute(aCTFontDescriptorRef, attribute)
next

CFRelease(aCFArray) //the array was created and must be released.
Return res()
End Function
[/code]

Superb. I am going to try right away.

Thank you so much Jim.

I don’t understand. It seems res() returns nil.

I double checked the path passed was a valid shellpath to a ttf font file.

Oh. Replaced by NativePath and it works.

Christian of course had the plugins. I am putting this here so anybody looking for the same solutions will know where to look :

See CoreTextMBS class with those methods:

• shared method CreateFontDescriptorsFromURL(URL as string) as CTFontDescriptorMBS()
• shared method CreateFontDescriptorsFromFile(file as folderitem) as CTFontDescriptorMBS()

A couple of things here.

[quote=246877:@Michel Bujardet] declare function CFURLCreateFromFSRef lib “Cocoa” ( allocator as Ptr, fsRef as Ptr ) as Ptr
Dim CFURLRef as Ptr = CFUrlCreateFromFSRef( nil, f.MacFSRef )[/quote]
FSRef was buggered with Yosemite, in some cases the functions simply don’t work any more.

You can simply hard code into the loop as it’s the only place you use it.

This will causes issues if you try to compile 64-Bit, the result from the declare is a integer ( 4 bytes on 32-Bit, 8 on 64-Bit) but you’re pushing it into a 32-Bit integer.

Pay attention to the library names such as “CoreFoundation.framework” and “Cocoa”, whilst for the most part you can get away with simply using “Cocoa”. IMHO, if you can use “Cocoa” use “Cocoa”, the reason being is that I hard coded declares against their respective libraries… Only to find that Apple changed the library, a prime example is in OS X 10.6, CoreGraphics functions were under “ApplicationServices” now they’re under “CoreGraphics”, but they work on both 10.6 and 10.7 with “Cocoa”.

There are some frameworks which are not under the “Cocoa” umbrella and you have to address specifically.[quote=246985:@jim mckay] declare function CFURLCreateWithFileSystemPath lib “CoreFoundation” ( allocator as ptr, fontURL as CFStringRef, style as uint32, isdirectory as boolean) as ptr[/quote]
CFURLPathStyle is a long, not an Int. Therefore the correct translation is integer not Int32, this will cause issues when compiling for 64-Bit.

[quote=246985:@jim mckay] //no descriptors creates
if aCFArray=nil then Return nil[/quote]
Watch out for things like this, it can be helpful, but it can also bite you in arse.

I see it already did. There’s two different returns for failures, which can be helpful in figuring out what the error was, you can get a nil array (same a nil object) or a zero count array.

A safer way to prevent against these kind of issues is to extract the correct path within the function.

Function getPostScriptNames( file as folderitem ) As String() declare function CFURLCreateWithFileSystemPath lib "CoreFoundation" ( allocator as ptr, fontURL as CFStringRef, CFURLPathStyle as integer, isdirectory as boolean) as ptr Dim CFURLRef as Ptr = CFURLCreateWithFileSystemPath( nil, file.nativePath, 0, file.directory )
Also, as you have the actual folderitem, you can extract if it’s a folder or not and pass it to the CFURL creation method, this way if you copy and paste the code into a different function, it will reduce errors.

Lastly I would suggest using NSURLs over CFURLs, as NSURLs are automatically released at the end of the run cycle (in case you forget).

Function NSURLRef(f as folderItem) As Ptr if f <> nil then #if TargetCocoa then Declare function NSClassFromString lib "Foundation" ( classname as CFStringRef ) as Ptr declare function fileURLWithPath lib "Foundation" selector "fileURLWithPath:isDirectory:" ( NSURLClassRef as Ptr, filePath as CFStringRef, isDir as boolean ) as Ptr return fileURLWithPath( NSClassFromString( "NSURL" ), f.nativePath, f.directory ) #endif end if End Function
Put the code in a shared module and when you need a NSURL, you simply call NSURLRef( myfile ).

declare function CTFontManagerCreateFontDescriptorsFromURL lib "CoreText" ( fontURL as Ptr) as ptr Dim aCFArray As ptr = CTFontManagerCreateFontDescriptorsFromURL( NSURLRef( file ) )

Hope this helps.

Thank you Sam :slight_smile:

Great ideas Sam, thanks. but I’m curious about this one…

Watch out for things like this, it can be helpful, but it can also bite you in !@#$%.[/quote]
How can that bite me?

[quote=247071:@Sam Rowlands]@jim mckay //no descriptors creates
if aCFArray=nil then Return nil
Watch out for things like this, it can be helpful, but it can also bite you in !@#$%.

[/quote]
From Apple’s docs “This function returns a retained reference to a CFArray of CTFontDescriptorRef objects, or NULL on error”
So I’m not sure why nil could be a bad test to do. Seems like the right way to do it. Or do you mean returning a nil array? Probably should just return an empty array to be safer.

[quote=247071:@Sam Rowlands]CFURLPathStyle is a long, not an Int. Therefore the correct translation is integer not Int32, this will cause issues when compiling for 64-Bit.

[/quote]
Get’s me every time! I still wish we could define type aliases in Xojo… :confused:

[quote=247097:@Will Shank]Great ideas Sam, thanks. but I’m curious about this one…
How can that bite me?[/quote]
Comes down the electricity cables through the wireless and shocks you where you sit!

Because you can have 3 outcomes, an array with strings, and array with zero content and notAnArray. Providing the user of this code snippet is prepared for notAnArray, this can be used to handle error messages with finer granularity than just an array with zero content. Otherwise for some users of the code snippet, under the right circumstance, their app will get a unhandled NilObjectExecption error.

It’s one of those things; you’re not technically wrong, as it can be beneficial to those who care, but for those who just copy and paste this snippet it’s potentially dangerous.

A lot of Apple’s API includes a ptr to a NSError, so if the function fails there is a useable error message.

If the Obj-C world, an NSArray is an object, so you have to check it’s validity before and it’s something that we’ve been trained to do. With Xojo (and RB before) an array is not an object and there was a time when an array couldn’t be nil, it had to contain something.

[quote=247105:@jim mckay]@Sam Rowlands CFURLPathStyle is a long, not an Int. Therefore the correct translation is integer not Int32, this will cause issues when compiling for 64-Bit.
Get’s me every time! I still wish we could define type aliases in Xojo… :/[/quote]
Again you really need to be careful with stuff like this, I recently translated someone code that was crashing on 64-Bit and they’d mixed and matched Int32 with integer all over the place. It works on 32-Bit fine, and without 64-Bit debugging, it took some time to track down.

Next time you post a declare code snippet, just double check the numeric data types. Remember most people who copy and paste this snippet, don’t know declares like you do and until there’s 64-Bit debugging, they may not even know it’s this tiny little thing that’s preventing them compiling 64-Bit.

A case where I think this returns strange results would be a file named something.ttf that does not contain a font.

When I was using my own imperfect declare with such an invalid font file, it crashed silently the app. Since then, I validate every single file to verify it is a font, before calling CTFontManagerCreateFontDescriptorsFromURL, to avoid a similar occurence.