NSLayoutManager and avoiding widows and orphans

  • Hi,

    My application needs to avoid widows and orphans when printing. It has code that does this, but the code crashes in certain circumstances, and I am now on my third cycle of development in which I'm having to try to address this bug when I thought I'd fixed it, so I'm hoping someone here can give me some advice.

    Basically, my code just needs to check the line at the bottom of any given text container and check that it's not the last line in the paragraph or the first line in the paragraph. If it is, with a couple of other minor adjustments, it adjusts the height of the text container and text view that contain that text so that it is shorter and pushes that paragraph over into the next text container.

    All this is fine - I have the code up and running to do that. The problem is that NSLayoutManager is very tricky. Do things at the wrong time during layout and it crashes. So the trouble is, where to do this code?

    At first, I did it all in the -didCompleteLayout method. This was a disaster, and caused a lot of crashes.

    So then, I tried the following:
    - During layout, in the -didCompleteLayout method, look for a widow and orphan. If one was noticed, just save the information about it - which container it was in, and how much we would need to adjust the container by.
    - At the end of layout, adjust the frame of the text view and container that was affected.
    - Once this was done, force layout again, so that the next widow or orphan was found.
    - Repeat until all were found.

    This seemed to work fine... But it doesn't. It turns out that if enough widows and orphans are found in a long document, so that another page (and thus another container) gets added to the end of the document, the adding of this page causes havoc and suddenly I'm caught in an infinite loop and a hang.

    So, next I tried waiting until all layout is complete and then enumerating through the text containers, checking for a widow or orphan at the end of each one, adjusting the frame and then forcing layout. But again, if a page gets added because of this, we hit problems - bounds and run storage exceptions, and problems caused by adding a text container whilst enumerating through the text containers.

    All in all, not good.

    Does anyone have any suggestions of the safest place in which to, and how best I could, go through all my text containers and adjust their sizes when necessary? Like I say, it's not the widows and orphans code I need, that bit I have. It's the best place in which actually to resize textviews/containers after layout, one by one, as necessary, without causing crashes and hangs because I've upset the layout manager...

    Thanks in advance to anyone who can help.
    All the best,
    Keith

    __________________________________________________
    Do You Yahoo!?
    Tired of spam?  Yahoo! Mail has the best spam protection around
    http://mail.yahoo.com
  • Hi Keith,

    > So, next I tried waiting until all layout is complete and then
    > enumerating through the text containers, checking for a widow or
    > orphan at the end of each one, adjusting the frame and then forcing
    > layout. But again, if a page gets added because of this, we hit
    > problems - bounds and run storage exceptions, and problems caused
    > by adding a text container whilst enumerating through the text
    > containers.

    Do you trigger this frame adjusting from the NSLayoutManager delegate
    methods? In my experience it is not safe to modify text container
    geometry/size from this callback. Try this and see if it fixes your
    crash: after you detect that layout has completed schedule your
    fixing code via "performSelector:withObject:afterDelay:".

    ~Martin
  • Hi Martin,

    Thanks for your help (again). Unfortunately, no, this frame adjustment isn't triggered from the delegate methods - I tried to shift everything out of there for the exact reasons you describe. The only thing I do in the delegate method is make note of whether I *need* to adjust the frame. Then I wait for all layout to finish, and only once that's done do I adjust the frame. Then I trigger all layout again, at the end of which I check to see if another adjustment is needed. This goes on until the delegate method doesn't return any more adjustments to be made. But I'm careful not to trigger any extra layout from within the delegate method. The hang happens when the frame change forces the delegate method to add another page. For some reason, this seems to cause everything to go crazy, but I'm not sure why...

    Right now I may just have to scrap widow and orphans support from printing in my app altogether. The layout manager just seems too delicate, triggering hangs and crashes that are incredibly difficult to track down.

    Thanks again and all the best,
    Keith

    ----- Original Message ----
    From: Martin Wierschin <martin...>
    To: Keith Blount <keithblount...>
    Cc: <cocoa-dev...>
    Sent: Wednesday, November 7, 2007 1:24:04 AM
    Subject: Re: NSLayoutManager and avoiding widows and orphans

    Hi Keith,

    So, next I tried waiting until all layout is complete and then enumerating through the text containers, checking for a widow or orphan at the end of each one, adjusting the frame and then forcing layout. But again, if a page gets added because of this, we hit problems - bounds and run storage exceptions, and problems caused by adding a text container whilst enumerating through the text containers.


    Do you trigger this frame adjusting from the NSLayoutManager delegate methods? In my experience it is not safe to modify text container geometry/size from this callback. Try this and see if it fixes your crash: after you detect that layout has completed schedule your fixing code via "performSelector:withObject:afterDelay:".

    ~Martin

    __________________________________________________
    Do You Yahoo!?
    Tired of spam?  Yahoo! Mail has the best spam protection around
    http://mail.yahoo.com
  • I got widow-orphan protection working by subclassing the
    NSATSTypesetter and overriding beginParagraph,
    willSetLineFragmentRect:forGlyphRange:usedRect:baselineOffset:, and
    endParagraph. It was kind of complicated, and I don't have the code
    here, and frankly I don't remember how thoroughly I debugged it before
    setting the project aside.

    Basically, instead of modifying the text container height, I used
    those methods to test for a widow/orphan condition and if true set a
    variable specifying the first glyph that should be thrown to the next
    container. In endParagraph, if that variable is set, I forced relayout
    and on the next pass through
    willSetLineFragmentRect:forGlyphRange:usedRect:baselineOffset: I
    increased the the height of lineRect and usedRect so the typesetter on
    its own would throw the line to the next container.

    It was complicated because I had to turn off textView drawing to
    eliminate flicker during relayout, but you might be able to figure out
    a better implementation of this idea.

    Ross

    On Nov 7, 2007, at 6:06 PM, Keith Blount wrote:

    > Hi Martin,
    >
    >
    > Thanks for your help (again). Unfortunately, no, this frame
    > adjustment isn't triggered from the delegate methods - I tried to
    > shift everything out of there for the exact reasons you describe.
    > The only thing I do in the delegate method is make note of whether I
    > *need* to adjust the frame. Then I wait for all layout to finish,
    > and only once that's done do I adjust the frame. Then I trigger all
    > layout again, at the end of which I check to see if another
    > adjustment is needed. This goes on until the delegate method doesn't
    > return any more adjustments to be made. But I'm careful not to
    > trigger any extra layout from within the delegate method. The hang
    > happens when the frame change forces the delegate method to add
    > another page. For some reason, this seems to cause everything to go
    > crazy, but I'm not sure why...
    >
    >
    > Right now I may just have to scrap widow and orphans support from
    > printing in my app altogether. The layout manager just seems too
    > delicate, triggering hangs and crashes that are incredibly difficult
    > to track down.
    >
    >
    > Thanks again and all the best,
    > Keith
    >
    > ----- Original Message ----
    > From: Martin Wierschin <martin...>
    > To: Keith Blount <keithblount...>
    > Cc: <cocoa-dev...>
    > Sent: Wednesday, November 7, 2007 1:24:04 AM
    > Subject: Re: NSLayoutManager and avoiding widows and orphans
    >
    >
    > Hi Keith,
    >
    > So, next I tried waiting until all layout is complete and then
    > enumerating through the text containers, checking for a widow or
    > orphan at the end of each one, adjusting the frame and then forcing
    > layout. But again, if a page gets added because of this, we hit
    > problems - bounds and run storage exceptions, and problems caused by
    > adding a text container whilst enumerating through the text
    > containers.
    >
    >
    > Do you trigger this frame adjusting from the NSLayoutManager
    > delegate methods? In my experience it is not safe to modify text
    > container geometry/size from this callback. Try this and see if it
    > fixes your crash: after you detect that layout has completed
    > schedule your fixing code via
    > "performSelector:withObject:afterDelay:".
    >
    >
    > ~Martin
    >
    >
    >
    >
    >
    >
    >
    > __________________________________________________
    > Do You Yahoo!?
    > Tired of spam?  Yahoo! Mail has the best spam protection around
    > http://mail.yahoo.com
    >
previous month november 2007 next month
MTWTFSS
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    
Go to today