Re: [Pyobjc-dev] Call for help
Brought to you by:
ronaldoussoren
|
From: Nico W. <nic...@gm...> - 2009-04-04 17:52:41
|
Hi, I sent this a few days before this list became more active again, so I dare to repost my question (sorry). I found the question "How do I handle an FSRef in pyobjc?" in several other places of the web, so I guess other people would benefit from an answer too. Nico On 31.01.2009, at 20:00, Nico Weber wrote: > Hi, > > I want to load a font from a ttf file and then draw some text using > this font, and I want to do that in Python using the PyObjC bridge. > My program already does some drawing; it uses NSBitmapImageReps and > NSGraphicsContext for that. The font I'm trying to load is http://damieng.com/blog/2008/05/26/envy-code-r-preview-7-coding-font-released > . > > If someone has an idea how I could get this to work, please speak up. > > Here's what I tried so far: > > > 1.) Activate the font through ATS, then load it through NSFont > through its postscript name. > > This is what most programs written in ObjC do – they call > ATSFontActivateFromFileReference to load a ttf file and then can use > NSFont to load the font given its postscript name. The problem is > that there seems to be no built-in python binding for ATS. This fails: > > import ATS > > Now, ATS.framework is a subframework in the ApplicationServices > framework, but this fails as well: > > import ApplicationServices > > Fair enough. The pyobjc documentation shows how to write your own > wrapper: > > import objc > b = objc.loadBundle("ApplicationServices", globals(), > bundle_path=objc.pathForFramework(u'/System/Library/Frameworks/ > ApplicationServices.framework/')) > > For reasons I don't quite understand, this doesn't load the ATS > subframework methods, though (might be related that there is no > ATS.bridgesupport in /System/Library/Frameworks/ > ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/ > Versions/A/Resources/BridgeSupport). I tried to create the > bridgesupport file on my own, using > > gen_bridge_metadata -f /System/Library/Frameworks/ > ApplicationServices.framework/Frameworks/ATS.framework -o > ATS.bridgesupport > > This gave some linking errors, and didn't work. `man > gen_bridge_metadata` even says that there might be problems if you > want to create bridgesupport files for subframeworks of umbrella > frameworks, but doesn't really say how to work around them. I tried > explicitly setting special compiler flags with > > gen_bridge_metadata -f /System/Library/Frameworks/ > ApplicationServices.framework/Frameworks/ATS.framework -o > ATS.bridgesupport -c ' -F /System/Library/Frameworks/ > ApplicationServices.framework/Frameworks/ATS.framework -framework > ApplicationServices' > > , but in vain. > > Now, pyobjc has a `loadBundleFunctions` function that can be used to > load functions from a bundle. One of the examples in /Developer/ > Examples/Python/PyObjC/Cocoa/AppKit/PyObjCLauncher/LaunchServices.py > does use this function to load a specific function from a bundle. So > I tried loading ATSFontActivateFromFileReference thusly (in its own > file `myfile.py`): > > import objc > > __bundle__ = objc.loadBundle("ApplicationServices", globals(), > bundle_path=objc.pathForFramework(u'/System/Library/ > Frameworks/ApplicationServices.framework/')) > functions = [( u'ATSFontActivateFromFileReference', > 's^{FSRef=[80C]}II^vIo^I' )] > objc.loadBundleFunctions(__bundle__, globals(), functions) > > (See http://developer.apple.com/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/chapter_7_section_1.html > for how the type encodings work; you can use `NSLog(@"FSRef*: %s", > @encode(FSRef*));` to let gcc show you the type encoding for a given > type). > > To some extent, this even worked. After `import myfile`, I can call > ATSFontActivateFromFileReference on the `myfile` module. Cool. Now I > need an FSRef of my font to pass it to the function. I tried: > > from Cocoa import * > from CoreFoundation import * > import myfile > > url = NSURL.fileURLWithPath_(u'Envy Code R Bold.ttf') > status, fsref = CFURLGetFSRef(url, None) > > kATSFontContextLocal = 2 > kATSFontFormatUnspecified = 0 > kATSOptionFlagsDefault = 0 > print myfile.ATSFontActivateFromFileReference(fsref, > kATSFontContextLocal, kATSFontFormatUnspecified, None, > kATSOptionFlagsDefault, None) > > However, this fails with the error message "# ValueError: > depythonifying 'pointer', got 'Carbon.File.FSRef'". The problem is > that I claimed that ATSFontActivate... takes a > '^{FSRef=[80C]}' ("pointer to a struct FSRef which is 80 bytes"), > but CFURLGetFSRef returns a Carbon.File.FSRef object instead (which, > as far as I understand, is something from the days before pyobjc). > Now, this object has a `.data()` function that returns the raw 80 > bytes that make up the FSRef as a str, but there seems to be no way > to pass raw data to a function that was loaded through > loadBundleFunctions – so I have a reference to the function and all > data I need to call it, but I can't get my data through the bridge. > > Looks like a dead end (and http://osdir.com/ml/python.pyobjc.devel/2006-12/msg00024.html > seems to agree. Then again, that is a pre-Leopard post). > > > 2.) Load the font through CoreGraphics, then convert it to a > CoreText CTFont, which is toll-free bridged to NSFont, and use that > for drawing. > > CoreGraphics provides a function to load a font from a file, which I > can then convert to a CTFont, which is toll-free bridged to an > NSFont. So I should be able to load a font this way and then use > Cocoa's normal drawInRect_withAttributes_ function to draw it. I > tried: > > from CoreGraphics import * > from CoreText import * > > fontDataProvider = CGDataProviderCreateWithFilename('Envy Code > R.ttf') > customFont = CGFontCreateWithDataProvider(fontDataProvider) > print customFont # looks good so far! > > ctFont = CTFontCreateWithGraphicsFont(customFont, 12.0, None, None) > print CTFontCopyPostScriptName(ctFont) # looks still good > print ctFont > > However, printing the ctFont or using it as an NSFont gives the > error message "<Error>: can't find font object for font id > 13828539." – and after that, even printing the postscript name > returns "(null)". This does also happen when I call the same > functions in an Objective-C program, so this is a limitation of the > Cocoa runtime. Another dead end. > > > 3.) Load the font through CoreGraphics, and also draw the text > through CoreGraphics > > It's possible to get a CoreGraphics context from an > NSGraphicsContext. Then I can set the current font of the cg context > to the font I loaded from the ttf file. The first few steps even work: > > from CoreGraphics import * > from CoreText import * # contains CGContextSetFont for some reason > > fontDataProvider = CGDataProviderCreateWithFilename('Envy Code > R.ttf') > customFont = CGFontCreateWithDataProvider(fontDataProvider) > > cgContext = NSGraphicsContext.currentContext().graphicsPort() > CGContextSetFont(cgContext, customFont) > > Now, CG has a function CGContextShowTextAtPoint that draws a given > string (char*) at a given position into a cg context. Sounds great, > but as it turns out this function doesn't work for fonts loaded from > a file – it only works for activated system fonts loaded through > CGContextSelectFont (source: http://developer.apple.com/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_text/chapter_18_section_4.html > ). There is a function CGContextShowGlyphsAtPoint that works, but > it takes a list of glyphs (indices into the font's glyph table – > which glyph corresponds to which character is font-dependent!). > There is no official function that converts a string to a list of > glyphs for a given font, however. But there is an undocumented > function: CGFontGetGlyphsForUnichars. As this function is > undocumented, we have to load it ourselves again: > > # CoreGraphics is part of the ApplicationServices umbrella > framework > functions = [ > # bool CGFontGetGlyphsForUnichars(CGFontRef, unichar[], > CGGlyph[], size_t); > ( u'CGFontGetGlyphsForUnichars', 'B^{CGFont=}^So^SI'), > ] > > # This gives a bus error btw: > #import CoreGraphics > #objc.loadBundleFunctions(CoreGraphics, globals(), functions) > > AppServices = objc.loadBundle("ApplicationServices", globals(), > bundle_path=objc.pathForFramework(u'/System/Library/ > Frameworks/ApplicationServices.framework/')) > objc.loadBundleFunctions(AppServices, globals(), functions) > > Now, let's try to call it: > > print CGFontGetGlyphsForUnichars(customFont, 'py', None, 2) > > This gives "ValueError: depythonifying 'pointer', got 'str'" as > error. I couldn't figure out how to pass python text to a function > that takes `unichar*`. This is probably possible somehow, but since > this needs undocumented functions anyways, I'm not too happy with > this approach anyway. > > > 4.) Give up, write a small C-based module that calls > ATSFontActivateFromFileReference given an string, call that from > Python, and call it a day > > That's what I ended up doing. It works, but it is strange that this > seems necessary for such mundane a task. > > > Thanks, > Nico |