Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Controlling page and column breaks using paged.js

I have a 2 column layout that is destined for print. h2–5 are within the column and h1s span both columns. I'm using Paged.js for pagination.

These two pages show acceptable layouts:

enter image description here

But If there's a situation where a heading is at the bottom of a column, I'd like the heading to be forced to the top of next column.

enter image description here

If a new article starts in the bottom 40% of the page, I'd like that to be forced to the next page too.

enter image description here

For h2 and below I inject a div above the heading that, if the heading is at the bottom of its column, I inflate it to push into the next column.

enter image description here

This seems to do the job for h2–5 because they're inside a flow that's managed by the browser. But only if they're in the left column; if they're in the right, they get bumped off the page. E.g.:

enter image description here

Bumping H1s off the page causes issues, the shim inflates, but pushes the heading into an invisible area, or a few other strange situations.

enter image description here enter image description here

The first image shows the heading that is present in the second image, shimmed off the page.

The following (slightly simplified) markup is what generates page 11 (right, second image)

<div class="the-articles">
  <article class="architectural-review.com paper-story noted">
    <p>
      No question, Rem is a genius. Nonetheless, his wake is toxic: stained by
      Randian egos (both triumphal and crushed), the intense interpersonal
      competition, and the exploitation of intellectual and manual labour. How
      does it all end, you wonder. In some ways, Tomas Koolhaas’s documentary
      was a preemptive eulogy. Death is present in every shot, tugging at the
      great man’s sleeve. The film is also suffused by an intense melancholy. It
      is the peculiar sadness of endings: when a family line is extinguished,
      when change erases beauty and meaning, when an entire world order
      disintegrates.
    </p>
    <p>
      Starchitects are still with us, even though their era is over. Koolhaas
      himself called time on it in the mid-2000s. It is no contradiction to
      honour them, while admitting that we must give ourselves permission to
      abandon the figure of the heroic architect, and along with it the Western
      blueprint for greatness that Koolhaas has so relentlessly and obsessively
      perfected.
    </p>
    <div class="tail-meta">
      From:
      <span class="url"
        >https://www.architectural‑review.com/essays/reputations/rem‑koolhaas‑1944/10037224.article</span
      >
    </div>
    <!-- SHIM IS HERE -->
    <div class="shim-between-articles" style="height: 181.944px;"></div>
  </article>
  <article id="2415273718" class="newyorker.com paper-story noted">
    <h1 class="article-title" id="h2415273718">
      Love Is Not a Permanent State of Enthusiasm: An Interview with Esther
      Perel
    </h1>
  </article>
</div>

I'm doing the shim-inflation inside afterPageLayout, it calls this function:

function identifyTooCloseToBottomHeading(
  theHeading,
  pageCutoffPercentage,
  page,
  pageFragment
) {
  try {
    if (theHeading !== null && theHeading !== undefined) {
      const pageBox = page.element.getBoundingClientRect();
      const headingBox = theHeading.getBoundingClientRect();
      const pdelta = pageBox.bottom - headingBox.bottom;
      const botPC = pdelta / pageBox.height; //Gives a % up the page. I.e. 100% is the top
      const cutPC = pageCutoffPercentage[theHeading.tagName.toLowerCase()];
      const oneRem = parseFloat(
        getComputedStyle(document.documentElement).fontSize
      );
      if (botPC < cutPC) {
        console.log("at risk", theHeading);
        if (theHeading.previousElementSibling) {
          AdjustShimSize(oneRem);
        } else {
          const thisArticle = theHeading.parentElement;
          const prevArticle = thisArticle.previousElementSibling;
          const shim = prevArticle.querySelector(".shim-between-articles");
          const topMetaBox = thisArticle
            .querySelector(".top-meta")
            .getBoundingClientRect();
          shim.style.height = `${
            theHeading.getBoundingClientRect().height + topMetaBox.height
          }px`;
        }
      }
    }
  } catch (e) {
    console.log(e, theHeading);
  }

  function AdjustShimSize(oneRem) {
    const shim = theHeading.previousElementSibling;
    // calculate the height that it needs to be
    try {
      const shimBox = shim.getBoundingClientRect();
      const container = shim.closest(".the-articles");
      const containerBox = container.getBoundingClientRect();
      // logShimDetails(theHeading, container, shimBox, nextElBox, containerBox, oneRem);
      shim.style.height = `${containerBox.bottom - shimBox.bottom - oneRem}px`;
      console.log("shimmed", theHeading);
    } catch {
      console.log("trouble with", theHeading);
    }
  }
}

This seems like it ought to work, but it's not very elegant. I suspect that there's something that I could be doing with the breaktoken? Is there a correct way to do this?


for the H1s, I have also tried adding a class to the article above, depending on how many lines would be needed to bump the heading to the next page. I.e. a series of css rules:

.n-line-fat-bottom-20 {
  margin-bottom: calc(var(--wp-line-height) * 20) !important;
}
.n-line-fat-bottom-22 {
  margin-bottom: calc(var(--wp-line-height) * 24) !important;
}

and then applying the class at the same point in the Paged.js lifecycle. It has similalry unhelpful results.


If I knew ahead of time which headings would need to break, I could give them a break-before: column; or break-before: page; rule, but by content isn't known ahead of time. Adding these rules after the fact doesn't seem to work with the lifecycle of paged.js. I suspect that it is a lifecycle issue, as pushing the content off the page should cause a reflow of the next page if I was doing it at the right point/correct handler.

like image 725
Ben Avatar asked Jun 07 '20 04:06

Ben


People also ask

What is the use of page-break in CSS?

It is CSS property that help to define how a elements on a page will look when printed. This makes the print of the document more book-like. page-break isn’t a directly usable property but it contains three properties that can be used as per requirement: page-break-before: adds a page break before an element

What is the purpose of adding page breaks to reports?

Adding page breaks also improves the performance of large reports when they are processed. A rendered page is displayed while the rest of the pages are rendered in the background. This allows you to begin viewing the initial pages of the report while waiting for additional pages to become available.

What is the use of page-break-inside?

This makes the print of the document more book-like. page-break-inside: sets whether the page break should be avoided or not inside an element. It can be applied wherever required, inside the table, before or after the table or before or after a row, and even within a row.

How do I extend the functionality of paged JS?

When creating a script or library that is specifically aimed at extending the functionality of paged.js, it is best to use hooks and a handler class. Paged.js has various points in the parsing of content, transforming of CSS, rendering, and layout of HTML that you can hook into and make changes to before further code is run.


Video Answer


1 Answers

Hi to solve this problem I added a new component and gave it the necessary height to send the content in new page/column this is my raw implementation for my use case:

  const attribute = node?.getAttribute('data-pdf-break-past');
  const pageHeight = 967;
  let maxHeightValueBeforeBreak = pageHeight;
  if (attribute) {
    maxHeightValueBeforeBreak = (pageHeight * +attribute) / 100;
  }
  const distanceToTop = node.getBoundingClientRect()?.top + window.scrollY;

  const contentHeightInPage = distanceToTop % pageHeight;
  const marginToSet = pageHeight - contentHeightInPage;
  if (
    (attribute && contentHeightInPage > maxHeightValueBeforeBreak) ||
    node.className.includes('pdf-break-before')
  ) {
    const newNode = document.createElement('div');
    newNode.style.height = `${marginToSet}px`;

    node.parentNode?.insertBefore(newNode, node);
  }
  

I've used the Dom manipulation after that Paged.js has parsed the content.

note that margin or padding are not used because can cause issues with the library(weird behaviour while page break occurs).

like image 63
Yi Zhang Avatar answered Sep 28 '22 01:09

Yi Zhang