...
 
Commits (22)
{
"name": "pagedjs",
"version": "0.1.42",
"version": "0.1.43",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......
{
"name": "pagedjs",
"version": "0.1.42",
"version": "0.1.43",
"description": "Chunks up a document into paged media flows and applies print styles",
"author": "Fred Chasen",
"license": "MIT",
......
<!DOCTYPE html PUBLIC>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>break-inside-avoid-table-cell</title>
<script src="../../../../dist/paged.polyfill.js"></script>
<style>
@page {
size: 210mm 100mm;
border: 1px solid #cfc2c2;
}
table {
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
}
tr, td {
break-inside: avoid;
}
</style>
</head>
<body>
<table>
<tbody>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
</tr>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
<td>d</td>
<td>e</td>
</tr>
<tr>
<td>
<p>Sed sollicitudin ac neque at tincidunt. Proin gravida neque sit amet euismod imperdiet.</p>
<p>Nunc eu faucibus mi, nec tincidunt turpis. In mi lacus, sagittis et <em>iaculis</em> id, tincidunt vitae dui. Sed <strong>aliquet ornare ornare</strong>.</p>
<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus iaculis finibus nisl in pharetra.</p>
</td>
<td><p>Lorem ipsum dolor sit amet</p></td>
<td><p>Lorem ipsum dolor sit amet</p></td>
<td><p>Lorem ipsum dolor sit amet</p></td>
<td><p>Lorem ipsum dolor sit amet</p></td>
</tr>
</tbody>
</table>
</body>
</html>
const TIMEOUT = 10000; // Some book might take longer than this to renderer
describe("break-inside-avoid-table-cell", () => {
let page;
beforeAll(async () => {
page = await loadPage("breaks/break-inside/break-inside-avoid/break-inside-avoid-table-cell.html");
return page.rendered;
}, TIMEOUT);
afterAll(async () => {
if (!DEBUG) {
await page.close();
}
});
it("should render 2 pages", async () => {
let pages = await page.$$eval(".pagedjs_page", (r) => {
return r.length;
});
expect(pages).toEqual(2);
});
if (!DEBUG) {
it("should create a pdf", async () => {
let pdf = await page.pdf(PDF_SETTINGS);
expect(pdf).toMatchPDFSnapshot(1);
expect(pdf).toMatchPDFSnapshot(2);
});
}
});
<!DOCTYPE html PUBLIC>
<html lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta http-equiv="Content-Style-Type" content="text/css"/>
<title>
Combined
</title>
<script src="../../../dist/paged.polyfill.js"></script>
<style>
@page {
size: A4;
background-color: #f6f5f5;
}
@media screen {
.pagedjs_page {
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
}
}
</style>
<style>
@page {
size: A5;
background-color: #ffc9c9;
@bottom-center {
content: "Paged.js";
font-size: 0.8em;
font-weight: 800;
color: #973737;
}
}
h1 {
font-size: 40pt;
}
</style>
</head>
<body>
<section>
<h1>Lorem ipsum dolor sit amet</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi non suscipit odio. Aenean ut ligula id mauris efficitur tincidunt vitae non leo. In convallis convallis leo, eget
molestie metus imperdiet eu. Praesent pharetra, leo a laoreet mattis, ligula nisl commodo ante, non vestibulum nisi lacus eget magna. Maecenas scelerisque nibh ac felis
egestas, egestas elementum risus pellentesque. Nulla iaculis ut leo a iaculis. Donec vel sodales dolor, vel fringilla elit. Vivamus aliquam diam eu maximus elementum. Integer
eu urna at felis fermentum hendrerit.
</p>
<p>
Nulla dignissim pellentesque magna ac maximus. Integer id tincidunt erat. Sed elementum posuere augue, quis pharetra mi vehicula in. Nullam rhoncus mi quis lectus gravida
dignissim. Pellentesque a tortor ut leo pretium auctor non in massa. Nunc efficitur vestibulum mi, id mattis quam aliquet id. Ut semper tortor sit amet molestie mattis.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
</p>
</section>
</body>
</html>
const TIMEOUT = 10000;
describe("combined", () => {
let page;
beforeAll(async () => {
page = await loadPage("page-rules/combined/combined.html");
return page.rendered;
}, TIMEOUT);
afterAll(async () => {
if (!DEBUG) {
await page.close();
}
});
it("should render 1 page", async () => {
let pages = await page.$$eval(".pagedjs_page", (r) => {
return r.length;
});
expect(pages).toEqual(1);
});
if (!DEBUG) {
it("should create a pdf", async () => {
let pdf = await page.pdf(PDF_SETTINGS);
expect(pdf).toMatchPDFSnapshot(1);
});
}
});
......@@ -52,7 +52,7 @@
<body>
<section>
<h1>a ' this " ' aa</h1>
<h1>aaa</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi non suscipit odio. Aenean ut ligula id mauris efficitur tincidunt vitae non leo.
</p>
......
......@@ -15,13 +15,13 @@ describe("default", () => {
it("should set the running header as \"a '' this \" ' aa\" on the first page", async () => {
let text = await page.$eval(".pagedjs_first_page", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
expect(text).toEqual("\"a ' this \\\" ' aa\"");
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-first-alphabet"));
expect(text).toEqual("\"aaa\"");
});
it("should set the running header as \"fff\" on the second page", async () => {
let text = await page.$eval("#page-2", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-first-alphabet"));
expect(text).toEqual("\"fff\"");
});
});
......@@ -41,13 +41,13 @@ describe("first", () => {
it("should set the running header as \"aaa\" on the first page", async () => {
let text = await page.$eval(".pagedjs_first_page", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-first-alphabet"));
expect(text).toEqual("\"aaa\"");
});
it("should set the running header as \"fff\" on the second page", async () => {
let text = await page.$eval("#page-2", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-first-alphabet"));
expect(text).toEqual("\"fff\"");
});
});
......@@ -67,13 +67,13 @@ describe("last", () => {
it("should set the running header as \"fff\" on the first page", async () => {
let text = await page.$eval(".pagedjs_first_page", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-last-alphabet"));
expect(text).toEqual("\"fff\"");
});
it("should set the running header as \"fff\" on the second page", async () => {
let text = await page.$eval("#page-2", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-last-alphabet"));
expect(text).toEqual("\"fff\"");
});
});
......@@ -93,13 +93,13 @@ describe("first-except", () => {
it("should set the running header as nothing on the first page", async () => {
let text = await page.$eval(".pagedjs_first_page", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-first-except-alphabet"));
expect(text).toEqual("\"\"");
});
it("should set the running header as \"aaa\" on the second page", async () => {
let text = await page.$eval("#page-2", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-first-except-alphabet"));
expect(text).toEqual("\"aaa\"");
});
});
......@@ -121,18 +121,18 @@ describe("string-start", () => {
// See https://www.w3.org/TR/css-gcpm-3/#string-start
it("should set the running header as nothing on the first page", async () => {
let text = await page.$eval(".pagedjs_first_page", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-start-alphabet"));
expect(text).toEqual("\"\"");
});
it("should set the running header as \"fff\" on the third page", async () => {
let text = await page.$eval("#page-3", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-start-alphabet"));
expect(text).toEqual("\"fff\"");
});
it("should set the running header as \"ggg\" on page 4", async () => {
let text = await page.$eval("#page-4", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-start-alphabet"));
expect(text).toEqual("\"ggg\"");
});
});
......@@ -152,22 +152,22 @@ describe("string-multiple", () => {
it("should set the running header as \"aaa\" on the second page", async () => {
let text = await page.$eval("#page-2", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-first-except-alphabet"));
expect(text).toEqual("\"aaa\"");
});
it("should set the running header as \"1111\" on the second page", async () => {
let text = await page.$eval("#page-2", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabetbis"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-first-except-alphabetbis"));
expect(text).toEqual("\"1111\"");
});
it("should set the running header as \"fff\" on the third page", async () => {
let text = await page.$eval("#page-9", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabet"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-first-except-alphabet"));
expect(text).toEqual("\"bbb\"");
});
it("should set the running header as \"fff\" on the third page", async () => {
let text = await page.$eval("#page-9", (r) =>
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-alphabetbis"));
window.getComputedStyle(r).getPropertyValue("--pagedjs-string-first-except-alphabetbis"));
expect(text).toEqual("\"2222\"");
});
});
......@@ -339,7 +339,7 @@ class Chunker {
await this.hooks.afterPageLayout.trigger(page.element, page, breakToken, this);
this.emit("renderedPage", page);
this.recoredCharLength(page.wrapper.textContent.length);
this.recoredCharLength(page.area.textContent.length);
yield breakToken;
......
import {getBoundingClientRect, getClientRects} from "../utils/utils";
import {
breakInsideAvoidParentNode,
child,
cloneNode,
findElement,
......@@ -15,6 +16,7 @@ import {
needsPreviousBreakAfter,
nodeAfter,
nodeBefore,
parentOf,
previousSignificantNode,
prevValidNode,
rebuildAncestors,
......@@ -480,7 +482,12 @@ class Layout {
// Check if it is a float
let isFloat = false;
if (isElement(node)) {
// Check if the node is inside a break-inside: avoid table cell
const insideTableCell = parentOf(node, "TD", rendered);
if (insideTableCell && window.getComputedStyle(insideTableCell)["break-inside"] === "avoid") {
// breaking inside a table cell produces unexpected result, as a workaround, we forcibly avoid break inside in a cell.
prev = insideTableCell;
} else if (isElement(node)) {
let styles = window.getComputedStyle(node);
isFloat = styles.getPropertyValue("float") !== "none";
skip = styles.getPropertyValue("break-inside") === "avoid";
......@@ -511,7 +518,7 @@ class Layout {
if (!range && isText(node) &&
node.textContent.trim().length &&
window.getComputedStyle(node.parentNode)["break-inside"] !== "avoid") {
!breakInsideAvoidParentNode(node.parentNode)) {
let rects = getClientRects(node);
let rect;
......
......@@ -55,16 +55,6 @@ class Page {
return page;
}
createWrapper() {
let wrapper = document.createElement("div");
this.area.appendChild(wrapper);
this.wrapper = wrapper;
return wrapper;
}
index(pgnum) {
this.position = pgnum;
......@@ -125,8 +115,8 @@ class Page {
this.layoutMethod = new Layout(this.area, this.hooks, maxChars);
let newBreakToken = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken);
let newBreakToken = await this.layoutMethod.renderTo(this.area, contents, breakToken);
this.addListeners(contents);
this.endToken = newBreakToken;
......@@ -140,7 +130,7 @@ class Page {
return this.layout(contents, breakToken);
}
let newBreakToken = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken);
let newBreakToken = await this.layoutMethod.renderTo(this.area, contents, breakToken);
this.endToken = newBreakToken;
......@@ -167,8 +157,11 @@ class Page {
clear() {
this.removeListeners();
this.wrapper && this.wrapper.remove();
this.createWrapper();
const node = this.area;
// clear content
while (node.firstChild) {
node.firstChild.remove();
}
}
addListeners(contents) {
......@@ -210,7 +203,7 @@ class Page {
}
addResizeObserver(contents) {
let wrapper = this.wrapper;
let wrapper = this.area;
let prevHeight = wrapper.getBoundingClientRect().height;
this.ro = new ResizeObserver(entries => {
......@@ -240,7 +233,7 @@ class Page {
return;
}
let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents, this.startToken);
let newBreakToken = this.layoutMethod.findBreakToken(this.area, contents, this.startToken);
if (newBreakToken) {
this.endToken = newBreakToken;
......@@ -253,7 +246,7 @@ class Page {
return;
}
let endToken = this.layoutMethod.findEndToken(this.wrapper, contents);
let endToken = this.layoutMethod.findEndToken(this.area, contents);
if (endToken) {
this._onUnderflow && this._onUnderflow(endToken);
......@@ -267,7 +260,6 @@ class Page {
this.element.remove();
this.element = undefined;
this.wrapper = undefined;
}
}
......
......@@ -43,13 +43,24 @@ class StringSets extends Handler {
funcNode.name = "var";
funcNode.children = new csstree.List();
funcNode.children.append(
funcNode.children.createItem({
type: "Identifier",
loc: null,
name: "--pagedjs-string-" + identifier
})
);
if(this.type === "first" || this.type === "last" || this.type === "start" || this.type === "first-except"){
funcNode.children.append(
funcNode.children.createItem({
type: "Identifier",
loc: null,
name: "--pagedjs-string-" + this.type + "-" + identifier
})
);
}else{
funcNode.children.append(
funcNode.children.createItem({
type: "Identifier",
loc: null,
name: "--pagedjs-string-first-" + identifier
})
);
}
}
}
......@@ -61,59 +72,65 @@ class StringSets extends Handler {
this.pageLastString = {};
}
// get the value of the previous last string
for (let name of Object.keys(this.stringSetSelectors)) {
let set = this.stringSetSelectors[name];
let selected = fragment.querySelectorAll(set.selector);
// let cssVar = previousPageLastString;
// Get the last found string for the current identifier
let cssVar = ( name in this.pageLastString ) ? this.pageLastString[name] : "";
selected.forEach((sel) => {
// push each content into the array to define in the variable the first and the last element of the page.
//this.pageLastString = selected[selected.length - 1].textContent;
// Index by identifier
this.pageLastString[name] = selected[selected.length - 1].textContent;
if (this.type === "first") {
cssVar = selected[0].textContent;
}
else if (this.type === "last") {
cssVar = selected[selected.length - 1].textContent;
}
else if (this.type === "start") {
let stringPrevPage = ( name in this.pageLastString ) ? this.pageLastString[name] : "";
let varFirst, varLast, varStart, varFirstExcept;
if(selected.length == 0){
// if there is no sel. on the page
varFirst = stringPrevPage;
varLast = stringPrevPage;
varStart = stringPrevPage;
varFirstExcept = stringPrevPage;
}else{
selected.forEach((sel) => {
// push each content into the array to define in the variable the first and the last element of the page.
this.pageLastString[name] = selected[selected.length - 1].textContent;
if (sel.parentElement.firstChild === sel) {
cssVar = sel.textContent;
}
}
});
/* FIRST */
else if (this.type === "first-except") {
cssVar = "";
varFirst = selected[0].textContent;
/* LAST */
varLast = selected[selected.length - 1].textContent;
/* START */
// Hack to find if the sel. is the first elem of the page / find a better way
let selTop = selected[0].getBoundingClientRect().top;
let pageContent = selected[0].closest(".pagedjs_page_content");
let pageContentTop = pageContent.getBoundingClientRect().top;
if(selTop == pageContentTop){
varStart = varFirst;
}else{
varStart = stringPrevPage;
}
else {
cssVar = selected[0].textContent;
}
});
fragment.setAttribute("data-string", `string-type-${this.type}-${name}`);
// fragment.style.setProperty(`--pagedjs-string-${name}`, `"${cssVar.replace(/\\([\s\S])|(["|'])/g, "\\$1$2")}"`);
fragment.style.setProperty(`--pagedjs-string-${name}`, `"${cleanPseudoContent(cssVar)}`);
// if there is no new string on the page
if (!fragment.hasAttribute("data-string")) {
fragment.style.setProperty(`--pagedjs-string-${name}`, `"${this.pageLastString}"`);
}
/* FIRST EXCEPT */
varFirstExcept = "";
}
fragment.style.setProperty(`--pagedjs-string-first-${name}`, `"${cleanPseudoContent(varFirst)}`);
fragment.style.setProperty(`--pagedjs-string-last-${name}`, `"${cleanPseudoContent(varLast)}`);
fragment.style.setProperty(`--pagedjs-string-start-${name}`, `"${cleanPseudoContent(varStart)}`);
fragment.style.setProperty(`--pagedjs-string-first-except-${name}`, `"${cleanPseudoContent(varFirstExcept)}`);
}
}
......
......@@ -617,48 +617,41 @@ class AtPage extends Handler {
addPageClasses(pages, ast, sheet) {
// First add * page
if ("*" in pages && !pages["*"].added) {
if ("*" in pages) {
let p = this.createPage(pages["*"], ast.children, sheet);
sheet.insertRule(p);
pages["*"].added = true;
}
// Add :left & :right
if (":left" in pages && !pages[":left"].added) {
if (":left" in pages) {
let left = this.createPage(pages[":left"], ast.children, sheet);
sheet.insertRule(left);
pages[":left"].added = true;
}
if (":right" in pages && !pages[":right"].added) {
if (":right" in pages) {
let right = this.createPage(pages[":right"], ast.children, sheet);
sheet.insertRule(right);
pages[":right"].added = true;
}
// Add :first & :blank
if (":first" in pages && !pages[":first"].first) {
if (":first" in pages) {
let first = this.createPage(pages[":first"], ast.children, sheet);
sheet.insertRule(first);
pages[":first"].added = true;
}
if (":blank" in pages && !pages[":blank"].added) {
if (":blank" in pages) {
let blank = this.createPage(pages[":blank"], ast.children, sheet);
sheet.insertRule(blank);
pages[":blank"].added = true;
}
// Add nth pages
for (let pg in pages) {
if (pages[pg].nth && !pages[pg].added) {
if (pages[pg].nth) {
let nth = this.createPage(pages[pg], ast.children, sheet);
sheet.insertRule(nth);
pages[pg].added = true;
}
}
// Add named pages
for (let pg in pages) {
if (pages[pg].name && !pages[pg].added) {
if (pages[pg].name) {
let named = this.createPage(pages[pg], ast.children, sheet);
sheet.insertRule(named);
pages[pg].added = true;
}
}
......@@ -747,7 +740,7 @@ class AtPage extends Handler {
}
});
list.append(bVar, item);
}
}
}
}
......
......@@ -565,10 +565,6 @@ export default `
content: none !important;
}
img {
height: auto;
}
[data-align-last-split-element='justify'] {
text-align-last: justify;
}
......
......@@ -601,6 +601,40 @@ export function previousSignificantNode(sib) {
return null;
}
export function breakInsideAvoidParentNode(node) {
while ((node = node.parentNode)) {
if (node && node.dataset && node.dataset.breakInside === "avoid") {
return node;
}
}
return null;
}
/**
* Find a parent with a given node name.
* @param {Node} node - initial Node
* @param {string} nodeName - node name (eg. "TD", "TABLE", "STRONG"...)
* @param {Node} limiter - go up to the parent until there's no more parent or the current node is equals to the limiter
* @returns {Node|undefined} - Either:
* 1) The closest parent for a the given node name, or
* 2) undefined if no such node exists.
*/
export function parentOf(node, nodeName, limiter) {
if (limiter && node === limiter) {
return;
}
if (node.parentNode) {
while ((node = node.parentNode)) {
if (limiter && node === limiter) {
return;
}
if (node.nodeName === nodeName) {
return node;
}
}
}
}
/**
* Version of |nextSibling| that skips nodes that are entirely
* whitespace or comments.
......