[cairo] On allowing NULL font options
Behdad Esfahbod
behdad at behdad.org
Fri Jan 25 03:48:22 PST 2008
Ping? What are we going to do with this for 1.6?
On Sat, 2008-01-19 at 22:27 -0500, Behdad Esfahbod wrote:
> On Fri, 2008-01-18 at 07:57 -0800, Carl Worth wrote:
> > On Thu, 17 Jan 2008 20:17:58 -0500, Behdad Esfahbod wrote:
> > > On Thu, 2008-01-17 at 17:39 -0500, Chris Wilson wrote:
> > > >
> > > > [cairo-font-options] Treat NULL as a default cairo_font_options_t
> > > >
> > > > Interpret a NULL cairo_font_options_t as the default values - i.e
> > > > as if it were a fresh pointer returned by
> > > > cairo_font_options_create().
> > >
> > > I thought about this before. I think we shouldn't doing it. Currently
> > > the cairo practice is: no NULL.
> >
> > This one was my fault.
> >
> > The bug report that came in was a segfault due to a NULL
> > cairo_font_options_t* being passed to cairo_scaled_font_create.
> >
> > Chris first fixed this by fixing cairo_font_options_t to return
> > CAIRO_STATUS_NULL_POINTER for NULL which is obviously correct, right?
>
> Given that libraries shouldn't crash, yes.
>
>
> > Detecting NULL and treating it as an error is obviously a good thing.
>
> Right. Even if crashing makes bug fixing easier!
>
>
> > Then, I stepped in and imagined some buggy code doing:
> >
> > cairo_scaled_font_t *scaled_font;
> >
> > scaled_font = cairo_scaled_font_create (font_face,
> > matrix,
> > ctm,
> > NULL);
> >
> > and imagining what the fix would have to be:
> >
> > cairo_scaled_font_t *scaled_font;
> > + cairo_font_options_t *font_options;
> >
> > + font_options = cairo_font_options_create ();
> > scaled_font = cairo_scaled_font_create (font_face,
> > matrix,
> > ctm,
> > font_options);
> > + cairo_font_options_destroy (font_options);
> >
> > And this seemed a nuisance to me from an easy-to-use
> > point-of-view. Why make the user create something just to immediately
> > throw it away, but never putting any information into it?
>
> Well, that's a design problem with cairo_font_options_t. The more
> common annoyance with it is when you want to copy font options from one
> object to another. You end up with:
>
> cairo_font_options_t *font_options;
>
> font_options = cairo_font_options_create ();
>
> cairo_surface_get_font_options (some_surface, font_options);
> cairo_set_font_options (cr, font_options);
>
> cairo_font_options_destroy (font_options);
>
> One object creation and two copies...
>
>
> > But I think I was wrong for several reasons:
> >
> > * This is not the toy API, and if you're setup to do
> > cairo_scaled_font_create then you've already done enough nasty
> > work to create your font_face that this little bit of extra work
> > is not significant. That is, this isn't code that every new user
> > of cairo is going to have to right, so ease-of-use is not as
> > much a priority as it is in other parts of the API.
>
> Yes, that particular API is not interesting at all from an ease-of-use
> point of view. And one really very rarely wants to create a scaled font
> using that api but with default font options.
>
>
> > * The second form of the above is vastly more readable. The bare
> > NULL in the first example communicates nothing about what it is
> > doing. It's just as bad as the bare TRUE and FALSE values that I
> > dislike so much in other APIs. Compare that to the second
> > example where the purpose of "font_options" is quite obvious,
> > and it's also quite obvious where to look for more information
> > if there are options that need to be changed.
>
> Indeed.
>
>
> > * Finally, the reason that was brought up later in this thread
> > suggests that a bare NULL in the code was likely not the problem
> > here in the first place, (as the original author would have
> > found that quite quickly). Instead there's likely some non-cairo
> > function that's returning a NULL cairo_font_options_t* and we
> > definitely don't want to encourage that kind of thing.
>
> Yes. We already have healthy objects, in-error objects, and nil
> objects. The two latter ones are not distinguishable from the user's
> point of view (except that one doesn't leak). User just need to
> condition on cairo_*_status() at any time to differentiate them. Adding
> NULL adds yet another class of objects.
>
> Well, technically speaking, it's kinda useful yet unfortunate that we
> documented all constructors as never returning NULL. We didn't have to.
> We could just say "whatever returned by this constructor can be passed
> to other functions using one of these objects with no fear of bad
> behavior because of errors". We could then for example actually use
> NULL as the magic value for OOM. But we opted for never using NULL,
> leaving that particular value useful for users to be able to tell
> between "I've not created my cairo object yet" without an additional
> state needed.
>
>
> > So I take back my original suggestion, (and I apologize to Chris for
> > the extra work).
> >
> > And that just leaves what to do with NULL. Do we treat this as an
> > error or just immediately abort?
>
> Error and return. Aborts may be easy for debugging, but definitely lose
> user's work. We should avoid that at all expenses in libraries.
>
>
> > Behdad, you said something about wanting to print an error message. I
> > definitely don't have any problem printing a descriptive error just
> > before aborting, (in fact I think cairo should never consciously
> > abort without printing a description). And I think that we should
> > allow people to opt-in (environment variable?) to a detailed print
> > (with a backtrace!) at every cairo error.
>
> Right. We talked about this previously. Lemme recap my current state
> of mind quickly.
>
> Errors are of different kind:
>
> - Programming errors. Like passing NULL where one shouldn't. While
> we should try to be lenient about these and definitely not crash and
> control damage, these are the most interesting ones for developers.
> These should be very easy to spot, by way of printing out messages for
> example, even by default. Bonus for having a mode to make these fatal
> (for use during development).
>
> - Unavoidable errors. Out-of-memory or a full disk fit into this
> category. Some are rare (out-of-memory), others not (full disk). Some
> decide to ignore and crash on the rarer ones. As a library, would be
> nice to handle them gracefully. And we do a good job at this already.
>
> - Avoidable errors. Like passing a degenerate matrix to a function
> where one's not allowed. We can say "don't do that", but that's too
> heavy on the programmer to follow. Do we really expect any matrix
> coming in from an SVG or Flash file to be checked for invertibility
> before being passed to cairo? No. Currently we deal with these the
> same we as we do with unavoidable errors. Why should save();
> scale(0,0); restore() leave you in an error state? One would argue that
> because the drawings after the degenerate scale were erroneous. Sure.
> But what if there was no drawing? What if the drawing was quite
> well-defined with a 0,0 scale? We should push the errors down to where
> they actually happen. And then allow recovery. get_current_point() is
> a great example of that. We handle it gracefully: if there's no current
> point, we don't set cairo_t into error state in the get_current_point()
> call, but we don't allow recovery: we don't return a cairo_status saying
> that there's no current point.
>
>
> So, to summarize, we need to:
>
> - Make programming errors easy to spot, and be cool with them: check
> input at all entry points and put into err state and print out something
> more verbosely than other errors (say, as debug level 1)
>
> - Make avoidable errors really avoidable in an easy-to-use way.
>
> Well, that's all for now. No concrete actions. Already too long. Hope
> that helps :).
>
>
> > Chris, what do you think of all this?
> >
> > -Carl
--
behdad
http://behdad.org/
"Those who would give up Essential Liberty to purchase a little
Temporary Safety, deserve neither Liberty nor Safety."
-- Benjamin Franklin, 1759
More information about the cairo
mailing list