pdfmake icon indicating copy to clipboard operation
pdfmake copied to clipboard

No support for multi-page unbreakableBlocks

Open SteffiPeTaffy opened this issue 11 years ago • 51 comments

I saw the comment in pageElementWriter.js line 95: // no support for multi-page unbreakableBlocks Is there a reason why this is not supported? Any hints how we could fix this?

When I have a table with a lot of content in one or more rows and the dontBreakRows attribute is set to true; the rows will not get rendered at all. Example:

var dd = {
    content: [
        { text: 'Table with a row that is higher than a single page' },
        { table: {
            widths: ['*'],
            dontBreakRows: true,
            body: [
                [{ stack: 
                    [
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                     { text: 'Text'}, 
                     { text: 'Text'},
                    ]
                }]
            ]
        } 
    }]

}

SteffiPeTaffy avatar Feb 19 '15 09:02 SteffiPeTaffy

Is this is planned to be fixed ?

Elyahou avatar Jun 21 '15 11:06 Elyahou

There are no immediate plans to fix it.

Also: It's hard to know when to break a block when the user of the API specified the block as "unbreakable".

On Sun, Jun 21, 2015 at 1:05 PM, Elyahou [email protected] wrote:

Is this is planned to be fixed ?

— Reply to this email directly or view it on GitHub https://github.com/bpampuch/pdfmake/issues/207#issuecomment-113885321.

jthoenes avatar Jun 21 '15 21:06 jthoenes

I have a large table that doesn't fit on one page and as a whole but has reasonable sections where it could be broken. I'd like to set the whole table so it doesn't break, but then set a 'breakHint' or something so the system knows where it is allowed to break.

s7726 avatar May 31 '16 16:05 s7726

Same here for dynamic stack content. I need a way to keep small dynamic stack content together but also render content that does not fit to one page.

// playground requires you to assign document definition to a variable called dd

var dd = {
    content: [{
        unbreakable: true,
        stack: [
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'},
            {text: 'test'}
        ]
    }]
}

kaedwen avatar Sep 02 '18 07:09 kaedwen

Would an acceptable solution for this be to ignore unbreakable if the content is larger than one page?

jwerre avatar Mar 07 '19 23:03 jwerre

I would still prefer a way to hint where the break should end up, but a solution that at the least spent drop the content, would be minimally acceptable.

s7726 avatar Mar 08 '19 03:03 s7726

I'm going to take a stab at fixing this. @jthoenes why do you say this:

Also: It's hard to know when to break a block when the user of the API specified the block as "unbreakable".

It looks like the hight of the block is set in PageElementWriter::commitUnbreakableBlock on line 104

Could we not just add something like this:

if (fragment.height > this.context().getCurrentPage().pageSize) {
    // Content is too big, make content unbreakable then render as normal
    return super.addFragment(fragment);
}

Is the issue that the block height is available only because it's is already rendered at this point? I'd love to get a conversation going about this issue. Any possible solutions or suggestion much appreciated (particularly from @liborm85 ).

jwerre avatar Mar 16 '19 01:03 jwerre

Any preliminary solution like the one you just suggested would be very much appreciated. I would consider it a bug fix while working on more advanced features for page-break corner cases like the hinting suggested further up.

Things not ending up on the pdf at all because they couldn't be placed in the requested pretty way is a bug equivalent to the printer "out of magenta" way of dealing with printouts.

LukasGlader avatar Apr 16 '19 14:04 LukasGlader

I suggest that an unbreakable that has multiple pages, simply start the unbreakable in a new page and then break where it should break, like not being an unbreakable.

Something like "break before if don't fits in the remaining space".

Llorx avatar May 06 '19 14:05 Llorx

@jwerre Did you get around to trying this workaround? I just came across the issue and was planning to fork and patch with something similar but would be great if you could share some code / elaborate on any difficulty you ran into.

ramijarrar avatar Jun 25 '19 23:06 ramijarrar

@ramijarrar I didn't get around to fixing this. It's been a while but I believe the issue is that you can't get the height of the block until it has been rendered on the page. I'm still interested in a fix for this so I'd be eager to know what you come up with.

jwerre avatar Jun 25 '19 23:06 jwerre

I have also come across this issue - it's quite a big blocker for my project. Is there a way of 'catching' the issue (i.e. identifying when an unbreakable block is more than a page long) and then re-rendering without unbreakable:true and just with a pagebreak:before?

paprikati avatar Aug 05 '19 15:08 paprikati

I posted a $500 bounty at BountySource if anyone wants to take a stab at this.

jwerre avatar Sep 05 '19 23:09 jwerre

Hi @jwerre i want to take a look at this, but could be the aproach mentioned by @paprikati and @Llorx an accepted solution ??

I mean if the unbreakable block is larger than page size, then start it in a new page and break it where needs ?

willy2dg avatar Sep 10 '19 12:09 willy2dg

For me the desired behaviour would be - if it hits a block that is too big:

  1. add page break before
  2. respect any unbreakable:true flags on child components while continuing to render

paprikati avatar Sep 10 '19 12:09 paprikati

As it stands now if a block is marked as unbreakable and the block is longer than a page then the entire block is not rendered.

I think the best solution would be to simply ignore the unbreakable flag if the block is longer than the page.

I get why you would add a before break to try and fit it on the next page, but if it doesn't fit on the next page then you end up with unnecessary white space. If the block doesn't fit on the next page either then it should go back to the position before the page break and render from there.

jwerre avatar Sep 10 '19 18:09 jwerre

As it stands now if a block is marked as unbreakable and the block is longer than a page then the entire block is not rendered.

I think the best solution would be to simply ignore the unbreakable flag if the block is longer than the page.

I get why you would add a before break to try and fit it on the next page, but if it doesn't fit on the next page then you end up with unnecessary white space. If the block doesn't fit on the next page either then it should go back to the position before the page break and render from there.

Maybe that can be an option. "Render on next page or ignore unbreakable".

I guess the easy way is to ignore it. We could start with that.

Llorx avatar Sep 10 '19 21:09 Llorx

I would expect that it would incrementally push the unbreakable property to the next largest block until the blocks can fit on a page.

block1 unbreakable
  block1.2
  block1.3
    block1.3.1
  block1.4

if block1 won't fit, becomes

block1
  block1.2 unbreakable
  block1.3 unbreakable
    block1.3.1
  block1.4 unbreakable

if block 1.3 won't fit, becomes

block1
  block1.2 unbreakable
  block1.3 
    block1.3.1 unbreakable
  block1.4 unbreakable

I would also expect that pagebefore type behavior would be triggered by some percentage of the page available... If 50% (configurable) of the page already has content then do a pagebefore if not then don't.

s7726 avatar Sep 10 '19 21:09 s7726

I guess the easy way is to ignore it. We could start with that.

@willy2dg, I agree, let's start with a simple and see where we end up. But I do agree, it would be nice to add a pagebreak before a block if the block can fit on the next page else ignore.

jwerre avatar Sep 10 '19 21:09 jwerre

@s7726 I think you're overcomplicating it a bit. I also think an arbitrary 50% (or any percent for that matter) doesn't make much sense when the following block could be any length.

jwerre avatar Sep 10 '19 21:09 jwerre

@jwerre might be overcomplicating it for the initial cut, but it's always nice to have an idea where the end goal might be.

I agree percentage might not make the most sense, maybe a measurement of some kind (in, cm, etc.). It would be nice to have some control over the allowed amount of blank page at the bottom, if the block is going to be forced to break. i.e. I might not want to start on a fresh page with the whole big block if there is only one line of text on the page it's breaking from.

s7726 avatar Sep 10 '19 21:09 s7726

@jwerre - for me the priority is preserving unbreakable blocks within the larger block - where something like a table row should by 'default' be considered unbreakable. That gives the user the option to sub-divide their larger blocks into smaller ones if they need the kind of control that @s7726 would describe.

paprikati avatar Sep 10 '19 21:09 paprikati

@paprikati That would be better for sure. Sound doable @willy2dg?

jwerre avatar Sep 10 '19 22:09 jwerre

@jwerre I will look into the code more in deep and give you some feedback as soon as possible.

willy2dg avatar Sep 11 '19 03:09 willy2dg

I made a quick fix; still working on it

denim2x avatar Sep 12 '19 13:09 denim2x

@denim2x, for the sake of time, can you explain how you fixed it and what you've done?

jwerre avatar Sep 12 '19 18:09 jwerre

the fix is at an early stage - the table gets rendered all right, but only on the page where it starts

denim2x avatar Sep 12 '19 21:09 denim2x

I've made some progess screenshot

denim2x avatar Sep 12 '19 21:09 denim2x

Next I'll ensure that paragraphs exceeding page height shall be divided into smaller fragments

denim2x avatar Sep 15 '19 17:09 denim2x

For this to work it'd be better if pdfmake was based on coroutines/message queues, at least the modules TableProcessor and PageElementWriter (which contains beginUnbreakableBlock()); that means a lot more working going into it, so it'd be much appreciated if the funding for this issue was increased

denim2x avatar Sep 23 '19 09:09 denim2x