Equivalent of robofab.RFont.update()
OK, then here's an example illustrating my confusion:
import robofab.world f = robofab.world.CurrentFont() g = f["A"] for n in range(0,54): g.rotate(10) f.update()
I expect this code to rotate the A glyph 540 degrees in 10-degree increments, and that each step would be visible in the UI (because I'm calling
Instead I only see the final state (a net rotation of 180 degrees). In other words, the code behaves as if
f.update()doesn't exist, and then the UI updates itself after the script exits.
So is there a flaw in
f.update(), or a flaw in my understanding of what it does?
mmm, when there are several requests to update a view close after each other, cocoa will ignore previous request and refresh / update the view only once.
but you can force the view to update / refresh:
import robofab.world f = robofab.world.CurrentFont() g = f["A"] collectionView = f.document().getGlyphCollection() view = collectionView.getGlyphCellView() # get the glyph cell view for n in range(0,54): g.rotate(10) g.update() view.display() # force the view to redisplay
hope you like the animation :)
I see what you mean Frederik, but I still think this is a flaw, for two reasons.
- The discrepancy between the behavior of
update()in Fontlab vs.
update()in Robofont reduces the portability of Robofab scripts and makes it harder to create modules that can be shared between environments. Robofab becomes incompatible with itself.
If Robofab-in-Robofont has some extras compared to Robofab-in-Fontlab, that's OK, because can ignore those if I need to. But I wouldn't expect major subtractions either.
RFont.update()is critical and frequently used in Robofab-in-Fontlab. Once I add extra code to make
update()work in Robofont the way it did in Fontlab, then I can't use that script in Fontlab anymore.
- More broadly, an environment like Robofont that's built with a focus on scripting should always defer to the scripter. For instance, if I'm making valid
update()requests in the script, why should Cocoa "ignore" them? OK, Cocoa thinks it will be slow. But why does Cocoa get to have an opinion? If it runs slow, that's my problem. Maybe I have my reasons for wanting it that way. It should be WYSIWYG — What You Script Is What You Get. There should never be situations where lines of script are getting silently ignored.
(BTW, the animation is very exciting of course, but
update()has a real practical function, which is to give visual feedback during multi-step operations. It is an excellent debugging and status-reporting tool.)
- The discrepancy between the behavior of
tal last edited by gferreira
I can't answer for RoboFont, but I can explain what is happening at the lower levels in defcon and defconAppKit. I can also give a little background on the history of the update method.
First, the background: the update method in FontLab is purely a UI refresher. When you make a change to the data, the data is changed but the UI is not. For many scripters, this creates a pretty big problem because if update isn't called, the UI is out of sync with the data. That's bad. If you look at the RoboFab docs and examples, you'll see lots of things like
# don't forget to call update!Those are there because we had lots of complaints. The update methods weren't part of RoboFog's scripting API and that's what we were trying to mimic. Almost all of the scripters were familiar with that API (and things happening there automatically) and that made the update calls quite foreign and a source of many complaints. We spent a lot of time trying to make the updates happen automatically, but we never got any responses to our requests to FontLab for information about the optimal times to refresh. For a very short period, we had update happening automatically whenever any data was changed. This was known as the OMG THIS IS SO SLOW version of RoboFab.
To make the RoboFab API consistent, we implemented the update method in the base objects as:
def update(self): pass
It has been that way in NoneLab for a long time.
When I wrote defcon, I wanted to do things in a modern way that allowed for code to be much more flexible and much faster. defcon uses the observer pattern (concept: http://en.wikipedia.org/wiki/Observer_pattern implementation: http://code.typesupply.com/browser/packages/defcon/trunk/Lib/defcon/tools/notifications.py) for keeping data and representations in sync in an abstract way. When you make a change to a defcon object, the object posts notifications through a font level notification dispatcher. Other objects that have subscribed to
(object, notification)pairs will receive the notifications. It makes everything very lightweight and very fast. The various defconAppKit (an extension of vanilla) views use the notification system to be alerted of font object changes. When particular notifications are posted, defconAppKit notices and schedules things to be redrawn the next time Cocoa gives the views permission to redraw. Cocoa does this following its own rules. There are ways to hack into Cocoa to force it to redraw at a precise time, but those are frowned upon. I think this has more to do with the general drawing system that OS X uses than it is that Apple thinks that their code is much faster than everyone else.
It's interesting that you say that this need to update the UI immediately is so critical. In all my years of working on and with RoboFab in FontLab I've never seen a script that needed to update the way that you describe. Do you have a lot of these kinds of scripts?
.update()is a method that was added to RoboFab to work around FontLab UI bugs, I don't think that it's fair to assume that it should work the same way in an environment where those UI bugs don't exist.
If you want to match the behavior of FontLab's
.update()in RoboFont, you can put a test in your script for the enviroment the script is running in (
robofab.world.inRoboFont) and use
view.display()if the script is running in RoboFont.
tal last edited by frederik
I thought of a way that this could be done in the font and glyph objects. Here is the font object version:
def update(self): document = self.document for windowController in document.windowControllers(): window = windowController.window() contentView = window.contentView() contentView.displayIfNeeded()
(Not tested, but it should work. I think. Maybe.)
Is this a good idea? I don't know. It could slow down lots of scripts for the sake of one script.
but it will never make it into RF, sorry :)
import time def update(font): document = font.document() for windowController in document.windowControllers(): window = windowController.window() contentView = window.contentView() contentView.displayIfNeeded() f = CurrentFont() g = f["A"] s = time.time() for angle in range(0, 360, 15): g.rotate(angle) update(f) print time.time() - s s = time.time() for angle in range(0, 360, 15): g.rotate(angle) f.update() print time.time() - s
(with a font window open, glyph window open and Space Center open)
For such a simple task it takes one 1 sec more to update.
>>> 1.04617500305 >>> 0.0473890304565
So this is not a good plan unless you realllllyy like coffee or having days with 25 hours or when the sun is turning half speed....
if this is the case you can install a script during startup that add an observer to a
fontChangednotification and updates all the possible windows and views.
My understanding of why
RFont.update()exists & how to use it was gleaned entirely from the Robofab docs & examples. Some Robofabbers may use it more, some less. But it is part of the standard Robofab-in-Fontlab scripting idiom. If you followed the Robofab docs, your scripts rely on it.
(Tal, since you ask, one example of a script that "needs to update the UI immediately" is EVB's accent builder example on the Robofab site. (http://robofab.com/howto/buildingaccents.html) And I try to learn from the master.)
As a matter of policy, having
update()do nothing in NoneLab makes logical sense because there is no UI to update.
Likewise, my comment is also one of policy: now that Robofab is in another UI environment, how should
update()behave? Does it matter that it behaves differently than it did in the previous UI environment?
Ben says no, because
update()was designed to accommodate FL and I can test for FL in the script. A fair point, but there's another dimension to the problem. The
update()command gives Robofab-in-Fontlab a nice modular division between the Robofab layer and the Fontlab UI layer. Everything gets encapsulated into the
update()command. I don't have to worry about the specifics. Maybe this was an unintentional result, but to the extent that one of Robofab's goals is to insulate me from the FL object model, I always thought it was deliberate.
Above, Frederik showed me how to work with view objects and
view.display()to do my updates. That's fine as far as it goes, but it lacks modularity. I've got a script that would be happy if it could just be in the business of manipulating Robofab objects. Now it has to get into the business of manipulating Robofont UI objects too.
As a scripter, it's not that I mind manipulating UI objects. It's just that ideally, I'd like to avoid mixing UI stuff with data stuff, because it won't scale. For instance, what happens when folks start releasing scripts with new UI widgets? Now my data-manipulation script has to get updated to work with those widgets too. (Tal, perhaps your proposed approach is forward-compatible in that way.)
Nor am I saying that Robofont or defcon does it "wrong".
Again, it's a policy question of how to expose the Robofont UI to scripts, whether to be consistent with FL, or whether to invent a 3rd idiom (i.e., require Robofab-in-Robofont scripters to take more of an interest in Robofont UI objects).
With all apologies, I don't think that this is a policy question.
I think that we can agree that
.update()in both FontLab and RoboFont and Glyphs (for all I know) updates the UI. Code with
.update()works in both FontLab and RoboFont, with the result that at the end of a script, the UI is updated for the font or glyph that it is called on.
In FontLab, it happens that if you call
f.update(), FontLab will refresh the UI for each incremental change if it is called in a loop. This, from a speed of the application viewpoint, is a really dumb thing to do, as it slows everything down to molasses speed (see Frederik's timing and Tal's comment about when RoboFab was too liberal with
.update()— and, if you've used UFO export in FontLab through RoboFab before the
.update()s were taken out, you know that doing so could mean a nice coffee break). The only use of this 'feature' of FontLab, which you mention, that I can see would be for testing one's script. After the testing stage, one would just want their script to run, and run as quickly as possible. Frederik has demonstrated code that one could insert into a script for testing, much like how one inserts several print statements when testing a script, then takes them out when things are working.
You pointed to Erik van Blokland's build accent script, which calls
.update()so that FontLab will refresh the UI and show all the built accents. This script will work the same way in RoboFont, as
.update()will also update the UI. Things work well in both applications. I believe, that what Tal, and for that matter, I, am curious about, is a example where a script depends on showing the UI update for each point in a loop, not just needing to update when all transformations are finished.
I think that we can agree that FontLab isn't always the most consistent of applications, and that a view of consistency of RoboFab meaning that it needs to behave, bugs and all, 100% exactly like RoboFab in FontLab, even if the end result is the same, is both impractical and akin to asking every single web browser to behave just like IE6.
The end result of a
.update()is the same, which is all I think one can expect and ask for. If
.update()'s end result was different in different applications, this would be a problem for RoboFab, but this is not the case here.
Fair enough. At the moment, I don't have another script in my back pocket that illustrates the need for incremental UI updates. If I come across one, I will post it. Like I say, mostly I have used the feature for status reporting and debugging.
update(), my only remaining question is when it actually makes a difference in a Robofont script. For instance, this code snippet (adapted from the EVB script) works the same way with or without the calls to
from robofab.world import CurrentFont f = CurrentFont() for k in ["A", "B", "C", "D"]: f[k].mark = 100 f.update()
from robofab.world import CurrentFont f = CurrentFont() for k in ["A", "B", "C", "D"]: f[k].mark = 100
mbutterick last edited by
(How do I include a tag in a comment? Apparently it's something different than typing )
mbutterick last edited by
(How do I include a pre tag in a comment? Apparently it’s something different than typing greater-than + pre + less-than)
thanks for proving it yourself! ;)
as stated in the documentation
.update()only tells the UI that your object has changed.
I've just edited your post and added
<pre>bla bla bla</pre>for none-editors and none-admins
My guess as to why it's working the same way (Frederik can confirm) is that RoboFont is smarter about updating the UI than FL is. You'd want to use
.update()in the case you want to be absolutely sure the UI updates, and/or your script needs to run in both RoboFont and FontLab, and need it in there to force the FontLab UI to update.
Thanks Ben. You impliedly make a good case that my goal of having FL & RF compatible scripts is impractical. There is something to be said for just rebuilding Robofont-optimized versions.
update()in the middle of a script, my idiomatic use for this technique in Fontlab was when I was working with Robofab pens, specifically scripts that attempted to do pen operations on hundreds of glyphs at a pull. Of all Robofab operations, I found that pens were the most likely to awaken the FL crash monster.
The traditional behavior of FL when it encounters a script error is to hang. So relying on a UI update at the end of the script to show me status didn't work, because it never got there. So I got in the habit of calling
update()during the loops. This was slower, but when FL would hang, I could at least see how far it had gotten, giving some clues about why the script was failing, and whether the error was due to my own abuse of Robofab (which I could fix) or Fontlab's arbitrary refusal to continue (which I could not).
Clearly, Robofont has a better work ethic about finishing scripts.
Two quick things: I don't think that
.update()will slow down RoboFont, as you demonstrated it's smart about updating when need be, so having this in for FontLab won't impact RoboFont.
Second, sometimes a quicker way to do what you wanted with update in FontLab is to just have a print statement for each iteration of a loop. You'll get a running list of what's been done, so you'll know what happens before a crash, even if the glyph your script is working on isn't visible in the font window.
a quick tip if you a really, really want to debug in FL:
redirect the stdout and stderr to a file instead if printing it into FLs output window, cause can FL can quit unexpectedly.
import sys f = open(u"/Users/frederik/Desktop/FLlog.txt", "w") savedstdout = sys.stdout savedstderr = sys.stderr sys.stdout = f sys.stderr = f print "hello world" f.close() sys.stdout = savedstdout sys.stderr = savedstderr
This will also work in RoboFont off course!! your scripts will still be exchangeable.
visual debugging is in most cases a bad idea.... or you should make a .png, .pdf that you can review afterwards. Use DrawBot or the tinyDrawBot extension (will released when 1.2 is out)
In all my years of working on and with RoboFab in FontLab I’ve never seen a script that needed to update the way that you describe.
UFOCentral also relies on this technique, no? When I used it the other day, the glyphs and layers flashed before my eyes as I exported them. (Though I understand that UFOCentral is superfluous inside of Robofont.)
The macro point is that stdout/stderr is useful for debugging only when you can describe the status accurately in text. For instance, plenty of times in FL I've used a pointPen to copy an outline from one font to another, only to find out it's been turned into crumpled space junk along the way. But as far as Robofab or FL was concerned, the pen operation reported no errors.
Therefore, to rely on stdout/stderr only, you'd have to add in an extra step where you programmatically compare the expected and actual result, so the status can be reported accurately ("oops, you got space junk"). Sometimes that's easy to do (e.g., making a straight duplicate) but sometimes not (where there's a transform happening in between).
Either way, you're adding new debugging code, which a) will slow your script (no big deal) and b) may itself have bugs in it (very big deal). For complicated pen operations, your debugging code would have to essentially reimplement the pen. Or you'd need to do something ritzy like apply your pen operation into the actual target glyph, and then also a buffer glyph, and compare the results. But it creates a spiraling need to debug the debugger. So what is the advantage of visual debugging? Like stdout and stderr, it works without creating collateral complications.
Anyways, the vagaries of FL need not be hashed over here. My FL scripts all work fine — I've already absorbed those slings & arrows. When I get to the point where Robofont's
update()is causing a more-than-theoretical problem, I will report back. In text.
tal last edited by
UFOCentral also relies on this technique, no?
No. That's a symptom of FL's interface problem, not a feature of the script.