Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Re-displaying the current heading after a page break

I'm creating a document with WeasyPrint. I have sections that have names, some of which might span across multiple pages. When a section is too long, a page break occurs. What I am trying to do is to re-display the current section's name, ideally with the same formatting.

The following MWE shows how the section title is not displayed after a page break:

<html>
    <body>
        <h1>First section</h1>
        <p>Lorem ipsum...</p>
        <p>Lorem ipsum...</p>
        <p>Lorem ipsum...</p>
        <p>Lorem ipsum...</p>

        <p style="break-after: always;"></p>

        <p>Lorem ipsum...</p>
    </body>
</html>

Output of weasyprint example.html example.pdf:

enter image description here

I want First section to be displayed, as a <h1> tag, at the top on the left page.

I would like to do as this tex.stackexchange post which, as I understand it, basically consists in checking if the current page number exceeds the current total page count, and if it does, inserting the last section title encountered.

I'm not aware of the possibility to do so in HTML, does it exist? Is there any workaround to do this? If not, is it possible to have WeasyPrint execute custom Python code on some page-break hook?

like image 390
Right leg Avatar asked Nov 15 '18 15:11

Right leg


1 Answers

While it is not possible at this time, you can still use an awesome workaround.

When printing, only three kind of elements are automatically reproduced on each page:

  • elements with fixed position
  • headers, declared with @top-left, @top-center, @top-right etc.
  • footers, declared with @bottom-left etc.

We have to use one of them to build up a css-only solution: we will choose headers. So, the first part of the question is: how can I set a different header for each page? Or, in other words, how can I set a chapter's header?

Achieving this goal is quite simple: once decided which tag or class should contain the chapter's title, set a new CSS string for it:

h1 {
  string-set: doctitle content();
}

Then display the string in the header:

@page {
  size: A4;
  margin: 1.6cm .6cm 1.2cm .6cm;

  @top-center {
    content: string(doctitle);
  }
}

Now you will have something like this:

pages with chapter's headers

Let me add some code to your:

<html>
    <body>
        <h1>First section</h1>
        <p>Lorem ipsum...</p>
        <p>Lorem ipsum...</p>
        <p>Lorem ipsum...</p>
        <p>Lorem ipsum...</p>

        <p style="break-after: always;"></p>

        <p>Lorem ipsum...</p>

        <h1>Second section</h1>
        <p>Lorem ipsum 2...</p>
        <p>Lorem ipsum 2...</p>
        <p>Lorem ipsum 2...</p>
        <p>Lorem ipsum 2...</p>

        <p style="break-after: always;"></p>

        <p>Lorem ipsum 2...</p>
    </body>
</html>

In this case your headers will be:

  • 1st page: "First section"
  • 2nd page: "First section"… then will start the second section in the same page, with his own title
  • 3rd page: "Second section"

chapter headers on 3 pages

Next step: set same style for headers and chapter's titles, so headers can have the same appearance as titles:

h1 {
  string-set: doctitle content();
  font-family: 'Liberation Serif';
  font-size: 28pt;
  line-height: 1.2em;
}

@page {
  size: A4;
  margin: 1.6cm .6cm 1.2cm .6cm;

  @top-left {
    content: string(doctitle);
    font-family: 'Liberation Serif';
    font-size: 28pt;
    line-height: 1.2em;
  }
}

Then you will have something like this:

pages with chapter's headers styled as titles

Now, we need to fix the latest issue: the title in the first page looks like duplicated, because of the presence of both title and header.

Fixing it is quite simple:

body h1:first-of-type {
  position: absolute;
  left: -30cm;
}

I have positioned the first title outside the printing area. Unfortunately setting it to display: none will cause that not even the header will be displayed. You have other alternatives, such as visibility: hidden or font-size: 0 or color: transparent, but these three options will always let some blank space between the header and the first paragraph.

Now probably it's time to increase the header's height, adding top-padding to @top-left; The result should look like this:

chapter's headers and hidden title

This technique is not 100% safe: if a chapter that is not the first one will coincidentally start in a new page, both header and title will be shown, one close to the other. In any case this is not a frequent scenario.

Further improvements can consider a different approach to page breaks.

<html>
    <body>
        <section class="chapter">
            <h1>First section</h1>
            <p>Lorem ipsum...</p>
            <p>Lorem ipsum...</p>
            <p>Lorem ipsum...</p>
            <p>Lorem ipsum...</p>
            <p>Lorem ipsum...</p>

            <h1>Second section</h1>
            <p>Lorem ipsum 2...</p>
            <p>Lorem ipsum 2...</p>
            <p>Lorem ipsum 2...</p>
        </section>
        <section class="chapter">
            <h1>Third section</h1>
            <p>Lorem ipsum 3...</p>
            <p>Lorem ipsum 3...</p>
            <p>Lorem ipsum 3...</p>
            <p>Lorem ipsum 3...</p>
        </section>
    </body>
</html>

Styling page-breaks on chapters, and managing title hiding for the first child of any chapter:

section.chapter {
    break-after: always;
}
section.chapter h1:first-of-type {
    position: absolute;
    left: -30cm;
}
like image 103
Kalamun Avatar answered Nov 08 '22 00:11

Kalamun