...
 
Commits (3)
<!DOCTYPE html PUBLIC> <!DOCTYPE html PUBLIC>
<html lang="en" lang="en"> <html lang="en" lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" /> <meta http-equiv="Content-Style-Type" content="text/css" />
<title> <title>
Break inside avoid Break inside avoid
</title> </title>
<script src="../../../../dist/paged.polyfill.js"></script>
<script src="../../../../../dist/paged.polyfill.js"></script>
<style> <style>
@page { @page {
size: 6in 8in; size: 6in 8in;
margin: 20mm 20mm; margin: 20mm 20mm;
...@@ -21,14 +17,11 @@ ...@@ -21,14 +17,11 @@
} }
} }
h1, p { h1, p {
break-inside: avoid; break-inside: avoid;
} }
/* style and interface */ /* style and interface */
* { font-family: Times New Roman, sans-serif; } * { font-family: Times New Roman, sans-serif; }
:root { font-size: 18px; } :root { font-size: 18px; }
...@@ -59,22 +52,10 @@ ...@@ -59,22 +52,10 @@
margin-top: 10mm; margin-top: 10mm;
margin-left: 10mm; margin-left: 10mm;
} }
} }
</style> </style>
</head> </head>
<body> <body>
<section> <section>
<p>Nam eros tellus, hendrerit ut rhoncus sed, aliquet et felis. Cras a ex malesuada, fringilla magna elementum, cursus purus. Etiam fringilla leo non diam congue tempor. Ut laoreet, est eget blandit congue, mauris magna cursus sapien, dictum tempus sapien diam et dolor. Vestibulum vel egestas velit. Pellentesque vel consectetur urna, eu consequat odio. Sed ac pretium magna, ut ultrices tortor.</p> <p>Nam eros tellus, hendrerit ut rhoncus sed, aliquet et felis. Cras a ex malesuada, fringilla magna elementum, cursus purus. Etiam fringilla leo non diam congue tempor. Ut laoreet, est eget blandit congue, mauris magna cursus sapien, dictum tempus sapien diam et dolor. Vestibulum vel egestas velit. Pellentesque vel consectetur urna, eu consequat odio. Sed ac pretium magna, ut ultrices tortor.</p>
<h1>Cras ut augue condimentum, egestas nisi in, dictum erat. Nullam tincidunt tincidunt tempor. Sed in eleifend nibh, sit amet feugiat nisi. Cras at ante ut urna sagittis dictum ut nec elit. In feugiat euismod massa sagittis dictum. Nullam eu nisl eu elit laoreet tincidunt id sed ligula. Praesent vulputate faucibus nibh, ut ultrices nunc aliquam nec. Mauris et condimentum ligula. Vestibulum nec tortor quis urna dictum luctus. Cras quis suscipit metus. Ut dignissim ullamcorper aliquam. Donec condimentum eu tellus at interdum.</h1> <h1>Cras ut augue condimentum, egestas nisi in, dictum erat. Nullam tincidunt tincidunt tempor. Sed in eleifend nibh, sit amet feugiat nisi. Cras at ante ut urna sagittis dictum ut nec elit. In feugiat euismod massa sagittis dictum. Nullam eu nisl eu elit laoreet tincidunt id sed ligula. Praesent vulputate faucibus nibh, ut ultrices nunc aliquam nec. Mauris et condimentum ligula. Vestibulum nec tortor quis urna dictum luctus. Cras quis suscipit metus. Ut dignissim ullamcorper aliquam. Donec condimentum eu tellus at interdum.</h1>
...@@ -83,8 +64,5 @@ ...@@ -83,8 +64,5 @@
<p>Nam eros tellus, hendrerit ut rhoncus sed, aliquet et felis. Cras a ex malesuada, fringilla magna elementum, cursus purus. Etiam fringilla leo non diam congue tempor. Ut laoreet, est eget blandit congue, mauris magna cursus sapien, dictum tempus sapien diam et dolor. Vestibulum vel egestas velit. Pellentesque vel consectetur urna, eu consequat odio. Sed ac pretium magna, ut ultrices tortor.</p> <p>Nam eros tellus, hendrerit ut rhoncus sed, aliquet et felis. Cras a ex malesuada, fringilla magna elementum, cursus purus. Etiam fringilla leo non diam congue tempor. Ut laoreet, est eget blandit congue, mauris magna cursus sapien, dictum tempus sapien diam et dolor. Vestibulum vel egestas velit. Pellentesque vel consectetur urna, eu consequat odio. Sed ac pretium magna, ut ultrices tortor.</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. Donec laoreet eleifend purus ut sagittis. Nunc consequat vel sapien at convallis. Maecenas sollicitudin quis justo non varius.</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. Donec laoreet eleifend purus ut sagittis. Nunc consequat vel sapien at convallis. Maecenas sollicitudin quis justo non varius.</p>
</section> </section>
</body> </body>
</html> </html>
<!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>
Long nested section
</title>
<script src="../../../../dist/paged.polyfill.js"></script>
<style>
@page {
size: 140mm 150mm;
margin: 0px;
padding: 0px;
}
.related {
break-inside: avoid;
}
@media screen {
body {
background-color: whitesmoke;
}
.pagedjs_page {
background-color: white;
box-shadow: 0 0 0 1px #bfbfbf;
margin-top: 10mm;
margin-left: 10mm;
}
}
</style>
</head>
<body>
<section>
<h3>Section title</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum vel tortor at suscipit. Donec augue leo, viverra sit amet mauris eget, blandit vestibulum lacus. Quisque
a nibh ut ligula elementum efficitur et molestie mauris. Suspendisse potenti. Nam dapibus orci nec vestibulum volutpat. Nunc magna nibh, tristique eget ornare vel, convallis
eget quam. Donec risus neque, mattis ac finibus at, ullamcorper vitae tellus. Aenean faucibus turpis quis ligula tincidunt consectetur. Ut sed tortor urna. Vivamus volutpat
turpis vitae tortor tempor, in luctus arcu fringilla. Quisque hendrerit ligula vitae nunc porta elementum.</p>
<p>Etiam euismod interdum tellus, vitae semper nisi semper in. Curabitur tempus enim vel lectus laoreet ornare. Quisque venenatis ante in maximus scelerisque. Quisque auctor
volutpat nibh, ac finibus dui. Integer et nisi sed odio rhoncus rhoncus. Nunc auctor mi a erat pretium, sit amet placerat felis congue. Integer id mattis mauris. Etiam suscipit
pulvinar enim, sed finibus tortor. Nulla purus nisi, malesuada at orci in, fringilla aliquet mi.</p>
</section>
<section>
<div class="related">
<div class="first">
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>
</div>
<div class="second">
<p>7</p>
<p>8</p>
<p>9</p>
<p>10</p>
<p>11</p>
<p>12</p>
</div>
<div class="third">
<p>13</p>
<p>14</p>
<p>15</p>
<p>16</p>
<p>17</p>
<p>18</p>
</div>
</div>
</section>
</body>
</html>
const TIMEOUT = 10000;
describe("long nested section", () => {
let page;
beforeAll(async () => {
page = await loadPage("breaks/break-inside/break-inside-avoid/long-nested-section.html");
return page.rendered;
}, TIMEOUT);
afterAll(async () => {
if (!DEBUG) {
await page.close();
}
});
// it should ignore the rule "break-inside: avoid" because the section does not fit on the next page
it("should ignore break-inside:avoid when the element (section) does not fit on a page", async () => {
let pages = await page.$$eval(".pagedjs_page", (r) => r.length);
expect(pages).toBe(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>
Long section break before
</title>
<script src="../../../../dist/paged.polyfill.js"></script>
<style>
@page {
size: 140mm 150mm;
margin: 0px;
padding: 0px;
}
section {
break-before: page;
break-inside: avoid;
}
@media screen {
body {
background-color: whitesmoke;
}
.pagedjs_page {
background-color: white;
box-shadow: 0 0 0 1px #bfbfbf;
margin-top: 10mm;
margin-left: 10mm;
}
}
</style>
</head>
<body>
<section>
<h3>Section title</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum vel tortor at suscipit. Donec augue leo, viverra sit amet mauris eget, blandit vestibulum lacus. Quisque
a nibh ut ligula elementum efficitur et molestie mauris. Suspendisse potenti. Nam dapibus orci nec vestibulum volutpat. Nunc magna nibh, tristique eget ornare vel, convallis
eget quam. Donec risus neque, mattis ac finibus at, ullamcorper vitae tellus. Aenean faucibus turpis quis ligula tincidunt consectetur. Ut sed tortor urna. Vivamus volutpat
turpis vitae tortor tempor, in luctus arcu fringilla. Quisque hendrerit ligula vitae nunc porta elementum.</p>
<p>Etiam euismod interdum tellus, vitae semper nisi semper in. Curabitur tempus enim vel lectus laoreet ornare. Quisque venenatis ante in maximus scelerisque. Quisque auctor
volutpat nibh, ac finibus dui. Integer et nisi sed odio rhoncus rhoncus. Nunc auctor mi a erat pretium, sit amet placerat felis congue. Integer id mattis mauris. Etiam suscipit
pulvinar enim, sed finibus tortor. Nulla purus nisi, malesuada at orci in, fringilla aliquet mi.</p>
</section>
<section>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>
<p>7</p>
<p>8</p>
<p>9</p>
<p>10</p>
<p>11</p>
<p>12</p>
<p>13</p>
<p>14</p>
<p>15</p>
<p>16</p>
<p>17</p>
<p>18</p>
</section>
</body>
</html>
const TIMEOUT = 10000;
describe("long section break before", () => {
let page;
beforeAll(async () => {
page = await loadPage("breaks/break-inside/break-inside-avoid/long-section-break-before.html");
return page.rendered;
}, TIMEOUT);
afterAll(async () => {
if (!DEBUG) {
await page.close();
}
});
it("should ignore comply with break-before:page (precedence over break-inside:avoid)", async () => {
let pages = await page.$$eval(".pagedjs_page", (r) => r.length);
expect(pages).toBe(3);
});
if (!DEBUG) {
it("should create a pdf", async () => {
let pdf = await page.pdf(PDF_SETTINGS);
expect(pdf).toMatchPDFSnapshot(1);
expect(pdf).toMatchPDFSnapshot(2);
expect(pdf).toMatchPDFSnapshot(3);
});
}
});
<!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>
Long section
</title>
<script src="../../../../dist/paged.polyfill.js"></script>
<style>
@page {
size: 140mm 150mm;
margin: 0px;
padding: 0px;
}
section {
break-inside: avoid;
}
@media screen {
body {
background-color: whitesmoke;
}
.pagedjs_page {
background-color: white;
box-shadow: 0 0 0 1px #bfbfbf;
margin-top: 10mm;
margin-left: 10mm;
}
}
</style>
</head>
<body>
<section>
<h3>Section title</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum vel tortor at suscipit. Donec augue leo, viverra sit amet mauris eget, blandit vestibulum lacus. Quisque
a nibh ut ligula elementum efficitur et molestie mauris. Suspendisse potenti. Nam dapibus orci nec vestibulum volutpat. Nunc magna nibh, tristique eget ornare vel, convallis
eget quam. Donec risus neque, mattis ac finibus at, ullamcorper vitae tellus. Aenean faucibus turpis quis ligula tincidunt consectetur. Ut sed tortor urna. Vivamus volutpat
turpis vitae tortor tempor, in luctus arcu fringilla. Quisque hendrerit ligula vitae nunc porta elementum.</p>
<p>Etiam euismod interdum tellus, vitae semper nisi semper in. Curabitur tempus enim vel lectus laoreet ornare. Quisque venenatis ante in maximus scelerisque. Quisque auctor
volutpat nibh, ac finibus dui. Integer et nisi sed odio rhoncus rhoncus. Nunc auctor mi a erat pretium, sit amet placerat felis congue. Integer id mattis mauris. Etiam suscipit
pulvinar enim, sed finibus tortor. Nulla purus nisi, malesuada at orci in, fringilla aliquet mi.</p>
</section>
<section>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>
<p>7</p>
<p>8</p>
<p>9</p>
<p>10</p>
<p>11</p>
<p>12</p>
<p>13</p>
<p>14</p>
<p>15</p>
<p>16</p>
<p>17</p>
<p>18</p>
</section>
</body>
</html>
const TIMEOUT = 10000;
describe("long section", () => {
let page;
beforeAll(async () => {
page = await loadPage("breaks/break-inside/break-inside-avoid/long-section.html");
return page.rendered;
}, TIMEOUT);
afterAll(async () => {
if (!DEBUG) {
await page.close();
}
});
// it should ignore the rule "break-inside: avoid" because the section does not fit on the next page
it("should ignore break-inside:avoid when the element (section) does not fit on a page", async () => {
let pages = await page.$$eval(".pagedjs_page", (r) => r.length);
expect(pages).toBe(2);
});
if (!DEBUG) {
it("should create a pdf", async () => {
let pdf = await page.pdf(PDF_SETTINGS);
expect(pdf).toMatchPDFSnapshot(1);
expect(pdf).toMatchPDFSnapshot(2);
});
}
});
...@@ -6,16 +6,31 @@ ...@@ -6,16 +6,31 @@
<title> <title>
Long table Long table
</title> </title>
<script src="../../../dist/paged.polyfill.js"></script> <script src="../../../../dist/paged.polyfill.js"></script>
<style> <style>
@page { @page {
size: 140mm 150mm; size: 140mm 120mm;
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
} }
table { table {
break-inside: avoid; break-inside: avoid;
} }
@media screen {
body {
background-color: whitesmoke;
}
.pagedjs_page {
background-color: white;
box-shadow: 0 0 0 1px #bfbfbf;
margin-top: 10mm;
margin-left: 10mm;
}
}
</style> </style>
</head> </head>
<body> <body>
...@@ -126,22 +141,6 @@ ...@@ -126,22 +141,6 @@
<td>1</td> <td>1</td>
<td>v</td> <td>v</td>
</tr> </tr>
<tr>
<td>1</td>
<td>w</td>
</tr>
<tr>
<td>1</td>
<td>x</td>
</tr>
<tr>
<td>1</td>
<td>y</td>
</tr>
<tr>
<td>1</td>
<td>z</td>
</tr>
</tbody> </tbody>
</table> </table>
</section> </section>
......
...@@ -3,7 +3,7 @@ const TIMEOUT = 10000; ...@@ -3,7 +3,7 @@ const TIMEOUT = 10000;
describe("long table", () => { describe("long table", () => {
let page; let page;
beforeAll(async () => { beforeAll(async () => {
page = await loadPage("splits/tables/long-table.html"); page = await loadPage("breaks/break-inside/break-inside-avoid/long-table.html");
return page.rendered; return page.rendered;
}, TIMEOUT); }, TIMEOUT);
...@@ -13,11 +13,17 @@ describe("long table", () => { ...@@ -13,11 +13,17 @@ describe("long table", () => {
} }
}); });
// TODO: the following test will put the table on the next page but it should ignore the rule break-inside: avoid because the table does not fit on the next page // it should ignore the rule "break-inside: avoid" because the table does not fit on the next page
// this issue can be reproduced on v0.1.40 it("should ignore break-inside:avoid when the element (table) does not fit on a page", async () => {
it.skip("should ignore break-inside:avoid when the element (table) does not fit on a page", async () => {
let pages = await page.$$eval(".pagedjs_page", (r) => r.length); let pages = await page.$$eval(".pagedjs_page", (r) => r.length);
expect(pages).toBe(2); expect(pages).toBe(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">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>
Long table
</title>
<script src="../../../dist/paged.polyfill.js"></script>
<style>
@page {
size: 140mm 120mm;
margin: 0;
padding: 0;
}
h2 {
font-size: 2em;
break-after: avoid;
}
table {
break-inside: avoid;
}
@media screen {
body {
background-color: whitesmoke;
}
.pagedjs_page {
background-color: white;
box-shadow: 0 0 0 1px #bfbfbf;
margin-top: 10mm;
margin-left: 10mm;
}
}
</style>
</head>
<body>
<section>
<h2>Table</h2>
<div class="content">
<table>
<thead>
<tr>
<th>#</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>a</td>
</tr>
<tr>
<td>1</td>
<td>b</td>
</tr>
<tr>
<td>1</td>
<td>c</td>
</tr>
<tr>
<td>1</td>
<td>d</td>
</tr>
<tr>
<td>1</td>
<td>e</td>
</tr>
<tr>
<td>1</td>
<td>f</td>
</tr>
<tr>
<td>1</td>
<td>g</td>
</tr>
<tr>
<td>1</td>
<td>h</td>
</tr>
<tr>
<td>1</td>
<td>i</td>
</tr>
<tr>
<td>1</td>
<td>j</td>
</tr>
<tr>
<td>1</td>
<td>k</td>
</tr>
<tr>
<td>1</td>
<td>l</td>
</tr>
<tr>
<td>1</td>
<td>m</td>
</tr>
<tr>
<td>1</td>
<td>n</td>
</tr>
<tr>
<td>1</td>
<td>o</td>
</tr>
<tr>
<td>1</td>
<td>p</td>
</tr>
<tr>
<td>1</td>
<td>q</td>
</tr>
<tr>
<td>1</td>
<td>r</td>
</tr>
<tr>
<td>1</td>
<td>s</td>
</tr>
<tr>
<td>1</td>
<td>t</td>
</tr>
<tr>
<td>1</td>
<td>u</td>
</tr>
<tr>
<td>1</td>
<td>v</td>
</tr>
</tbody>
</table>
</div>
</section>
</body>
</html>
const TIMEOUT = 10000; // Some book might take longer than this to renderer
describe("unforced-breaks-drop-break-after-then-break-inside", () => {
let page;
beforeAll(async () => {
page = await loadPage("breaks/unforced-breaks/long-table.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);
});
}
});
...@@ -26,4 +26,4 @@ class BreakToken { ...@@ -26,4 +26,4 @@ class BreakToken {
} }
export default BreakToken; export default BreakToken;
\ No newline at end of file
...@@ -3,9 +3,8 @@ import ContentParser from "./parser"; ...@@ -3,9 +3,8 @@ import ContentParser from "./parser";
import EventEmitter from "event-emitter"; import EventEmitter from "event-emitter";
import Hook from "../utils/hook"; import Hook from "../utils/hook";
import Queue from "../utils/queue"; import Queue from "../utils/queue";
import { import {requestIdleCallback} from "../utils/utils";
requestIdleCallback import { breakInsideAvoidNode, breakAfterAvoidNode, breakInsideAvoidParentNode, breakAfterAvoidParentNode, nextSignificantNode } from "../utils/dom";
} from "../utils/utils";
const MAX_PAGES = false; const MAX_PAGES = false;
const MAX_LAYOUTS = false; const MAX_LAYOUTS = false;
...@@ -173,7 +172,6 @@ class Chunker { ...@@ -173,7 +172,6 @@ class Chunker {
this.emit("rendered", this.pages); this.emit("rendered", this.pages);
return this; return this;
} }
...@@ -213,9 +211,11 @@ class Chunker { ...@@ -213,9 +211,11 @@ class Chunker {
let loops = 0; let loops = 0;
while (!done) { while (!done) {
result = await this.q.enqueue(() => { return this.renderAsync(renderer); }); result = await this.q.enqueue(() => {
return this.renderAsync(renderer);
});
done = result.done; done = result.done;
if(MAX_LAYOUTS) { if (MAX_LAYOUTS) {
loops += 1; loops += 1;
if (loops >= MAX_LAYOUTS) { if (loops >= MAX_LAYOUTS) {
this.stop(); this.stop();
...@@ -241,11 +241,11 @@ class Chunker { ...@@ -241,11 +241,11 @@ class Chunker {
return new Promise(resolve => { return new Promise(resolve => {
requestIdleCallback(async () => { requestIdleCallback(async () => {
if (this.stopped) { if (this.stopped) {
return resolve({ done: true, canceled: true }); return resolve({done: true, canceled: true});
} }
let result = await renderer.next(); let result = await renderer.next();
if (this.stopped) { if (this.stopped) {
resolve({ done: true, canceled: true }); resolve({done: true, canceled: true});
} else { } else {
resolve(result); resolve(result);
} }
...@@ -255,16 +255,62 @@ class Chunker { ...@@ -255,16 +255,62 @@ class Chunker {
async renderAsync(renderer) { async renderAsync(renderer) {
if (this.stopped) { if (this.stopped) {
return { done: true, canceled: true }; return {done: true, canceled: true};
} }
let result = await renderer.next(); let result = await renderer.next();
if (this.stopped) { if (this.stopped) {
return { done: true, canceled: true }; return {done: true, canceled: true};
} else { } else {
return result; return result;
} }
} }
async handleOverruledBreakAfter(node) {
const breakAfterNode = breakAfterAvoidNode(node);
if (breakAfterNode) {
const nextNode = nextSignificantNode(node);
if (nextNode) {
const page = this.addPage();
// QUESTION: should we emit the temporary page?
//this.emit("page", page);
const newBreakToken = await page.layout(node.parentNode, null, this.maxChars);
let overruled = false;
// indicates that content does not fit into the page
if (newBreakToken && typeof newBreakToken.node === "undefined") {
// we need to overrule this definition, remove "break-after: avoid" directive and reflow the previous page
delete breakAfterNode.dataset.breakAfter;
breakAfterNode.style = breakAfterNode.style + ";break-after: auto;";
delete nextNode.dataset.previousBreakAfter;
overruled = true;
}
// remove the temporary page
this.removePages(this.pages.length - 1);
return overruled;
}
}
}
async handleOverruledBreakInside(node) {
const breakInsideNode = breakInsideAvoidNode(node);
if (breakInsideNode) {
const page = this.addPage();
// QUESTION: should we emit the temporary page?
//this.emit("page", page);
const newBreakToken = await page.layout(breakInsideNode, null, this.maxChars);
const breakInsideAvoidParent = newBreakToken && newBreakToken.node && breakInsideAvoidParentNode(newBreakToken.node);
// we are breaking inside a "break-inside: avoid" node which mean that we cannot comply with this rule!
const overruleBreakInside = breakInsideAvoidParent && breakInsideAvoidParent.dataset && breakInsideNode.dataset && breakInsideNode.dataset.ref === breakInsideAvoidParent.dataset.ref;
if (overruleBreakInside) {
// we need to overrule this definition, remove "break-inside: avoid" directive and reflow the previous page
delete breakInsideNode.dataset.breakInside;
breakInsideNode.style = breakInsideNode.style + ";break-inside: auto;";
}
// remove the temporary page
this.removePages(this.pages.length - 1);
return typeof overruleBreakInside !== "undefined";
}
}
async handleBreaks(node) { async handleBreaks(node) {
let currentPage = this.total + 1; let currentPage = this.total + 1;
let currentPosition = currentPage % 2 === 0 ? "left" : "right"; let currentPosition = currentPage % 2 === 0 ? "left" : "right";
...@@ -279,32 +325,32 @@ class Chunker { ...@@ -279,32 +325,32 @@ class Chunker {
} }
if (node && if (node &&
typeof node.dataset !== "undefined" && typeof node.dataset !== "undefined" &&
typeof node.dataset.previousBreakAfter !== "undefined") { typeof node.dataset.previousBreakAfter !== "undefined") {
previousBreakAfter = node.dataset.previousBreakAfter; previousBreakAfter = node.dataset.previousBreakAfter;
} }
if (node && if (node &&
typeof node.dataset !== "undefined" && typeof node.dataset !== "undefined" &&
typeof node.dataset.breakBefore !== "undefined") { typeof node.dataset.breakBefore !== "undefined") {
breakBefore = node.dataset.breakBefore; breakBefore = node.dataset.breakBefore;
} }
if( previousBreakAfter && if (previousBreakAfter &&
(previousBreakAfter === "left" || previousBreakAfter === "right") && (previousBreakAfter === "left" || previousBreakAfter === "right") &&
previousBreakAfter !== currentPosition) { previousBreakAfter !== currentPosition) {
page = this.addPage(true); page = this.addPage(true);
} else if( previousBreakAfter && } else if (previousBreakAfter &&
(previousBreakAfter === "verso" || previousBreakAfter === "recto") && (previousBreakAfter === "verso" || previousBreakAfter === "recto") &&
previousBreakAfter !== currentSide) { previousBreakAfter !== currentSide) {
page = this.addPage(true); page = this.addPage(true);
} else if( breakBefore && } else if (breakBefore &&
(breakBefore === "left" || breakBefore === "right") && (breakBefore === "left" || breakBefore === "right") &&
breakBefore !== currentPosition) { breakBefore !== currentPosition) {
page = this.addPage(true); page = this.addPage(true);
} else if( breakBefore && } else if (breakBefore &&
(breakBefore === "verso" || breakBefore === "recto") && (breakBefore === "verso" || breakBefore === "recto") &&
breakBefore !== currentSide) { breakBefore !== currentSide) {
page = this.addPage(true); page = this.addPage(true);
} }
...@@ -317,18 +363,31 @@ class Chunker { ...@@ -317,18 +363,31 @@ class Chunker {
} }
} }
async *layout(content, startAt) { async* layout(content, startAt) {
let breakToken = startAt || false; let breakToken = startAt || false;
let page;
while (breakToken !== undefined && (MAX_PAGES ? this.total < MAX_PAGES : true)) { while (breakToken !== undefined && (MAX_PAGES ? this.total < MAX_PAGES : true)) {
let reflowCurrentPage = false;
if (breakToken && breakToken.node) { if (breakToken && breakToken.node) {
await this.handleBreaks(breakToken.node); await this.handleBreaks(breakToken.node);
if (await this.handleOverruledBreakAfter(breakToken.node)) {
reflowCurrentPage = typeof page !== "undefined";
breakToken = page && page.startToken;
} else if (await this.handleOverruledBreakInside(breakToken.node)) {
reflowCurrentPage = typeof page !== "undefined";
breakToken = page && page.startToken;
}
} else { } else {
await this.handleBreaks(content.firstChild); await this.handleBreaks(content.firstChild);
} }
let page = this.addPage(); if (reflowCurrentPage) {
// reflow the current page because a "break" definition was overruled!
} else {
page = this.addPage();
}
await this.hooks.beforePageLayout.trigger(page, content, breakToken, this); await this.hooks.beforePageLayout.trigger(page, content, breakToken, this);
this.emit("page", page); this.emit("page", page);
...@@ -364,7 +423,7 @@ class Chunker { ...@@ -364,7 +423,7 @@ class Chunker {
this.maxChars = this.charsPerBreak.reduce((a, b) => a + b, 0) / (this.charsPerBreak.length); this.maxChars = this.charsPerBreak.reduce((a, b) => a + b, 0) / (this.charsPerBreak.length);
} }
removePages(fromIndex=0) { removePages(fromIndex = 0) {
if (fromIndex >= this.pages.length) { if (fromIndex >= this.pages.length) {
return; return;
...@@ -446,6 +505,7 @@ class Chunker { ...@@ -446,6 +505,7 @@ class Chunker {
return page; return page;
} }
/* /*
insertPage(index, blank) { insertPage(index, blank) {
let lastPage = this.pages[index]; let lastPage = this.pages[index];
...@@ -486,7 +546,6 @@ class Chunker { ...@@ -486,7 +546,6 @@ class Chunker {
*/ */
loadFonts() { loadFonts() {
let fontPromises = []; let fontPromises = [];
(document.fonts || []).forEach((fontFace) => { (document.fonts || []).forEach((fontFace) => {
......
...@@ -67,13 +67,13 @@ class Layout { ...@@ -67,13 +67,13 @@ class Layout {
let next; let next;
let hasRenderedContent = false; let hasRenderedContent = false;
let newBreakToken; let newBreakToken = {node: undefined};
let length = 0; let length = 0;
let prevBreakToken = breakToken || new BreakToken(start); let prevBreakToken = breakToken || new BreakToken(start);
while (!done && !newBreakToken) { while (!done && typeof newBreakToken.node !== undefined) {
next = walker.next(); next = walker.next();
prevNode = node; prevNode = node;
node = next.value; node = next.value;
...@@ -90,8 +90,10 @@ class Layout { ...@@ -90,8 +90,10 @@ class Layout {
newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken); newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken);
if (newBreakToken && newBreakToken.equals(prevBreakToken)) { if (newBreakToken && newBreakToken.equals(prevBreakToken)) {
// TODO: we should probably return an objet (BreakToken?) with additional info
console.warn("Unable to layout item: ", prevNode); console.warn("Unable to layout item: ", prevNode);
return undefined; delete newBreakToken.node;
return newBreakToken;
} }
return newBreakToken; return newBreakToken;
} }
...@@ -115,7 +117,8 @@ class Layout { ...@@ -115,7 +117,8 @@ class Layout {
if (newBreakToken && newBreakToken.equals(prevBreakToken)) { if (newBreakToken && newBreakToken.equals(prevBreakToken)) {
console.warn("Unable to layout item: ", node); console.warn("Unable to layout item: ", node);
return undefined; delete newBreakToken.node;
return newBreakToken;
} }
length = 0; length = 0;
...@@ -169,7 +172,8 @@ class Layout { ...@@ -169,7 +172,8 @@ class Layout {
if (newBreakToken && newBreakToken.equals(prevBreakToken)) { if (newBreakToken && newBreakToken.equals(prevBreakToken)) {
console.warn("Unable to layout item: ", node); console.warn("Unable to layout item: ", node);
return undefined; delete newBreakToken.node;
return newBreakToken;
} }
if (newBreakToken) { if (newBreakToken) {
...@@ -293,29 +297,6 @@ class Layout { ...@@ -293,29 +297,6 @@ class Layout {
}); });
} }
avoidBreakInside(node, limiter) {
let breakNode;
if (node === limiter) {
return;
}
while (node.parentNode) {
node = node.parentNode;
if (node === limiter) {
break;
}
if (window.getComputedStyle(node)["break-inside"] === "avoid") {
breakNode = node;
break;
}
}
return breakNode;
}
createBreakToken(overflow, rendered, source) { createBreakToken(overflow, rendered, source) {
let container = overflow.startContainer; let container = overflow.startContainer;
let offset = overflow.startOffset; let offset = overflow.startOffset;
......
...@@ -39,7 +39,9 @@ class Breaks extends Handler { ...@@ -39,7 +39,9 @@ class Breaks extends Handler {
if (property === "break-before" || if (property === "break-before" ||
property === "break-after" || property === "break-after" ||
property === "page-break-before" || property === "page-break-before" ||
property === "page-break-after" property === "page-break-after" ||
property === "page-break-inside" ||
property === "break-inside"
) { ) {
let child = declaration.value.children.first(); let child = declaration.value.children.first();
let value = child.name; let value = child.name;
...@@ -49,6 +51,8 @@ class Breaks extends Handler { ...@@ -49,6 +51,8 @@ class Breaks extends Handler {
property = "break-before"; property = "break-before";
} else if (property === "page-break-after") { } else if (property === "page-break-after") {
property = "break-after"; property = "break-after";
} else if (property === "page-break-inside") {
property = "break-inside";
} }
let breaker = { let breaker = {
...@@ -66,7 +70,9 @@ class Breaks extends Handler { ...@@ -66,7 +70,9 @@ class Breaks extends Handler {
}); });
// Remove from CSS -- handle right / left in module // Remove from CSS -- handle right / left in module
dList.remove(dItem); if (property !== "break-inside") {
dList.remove(dItem);
}
} }
} }
......
...@@ -601,6 +601,56 @@ export function previousSignificantNode(sib) { ...@@ -601,6 +601,56 @@ export function previousSignificantNode(sib) {
return null; return null;
} }
export function breakInsideAvoidParentNode(node) {
while ((node = node.parentNode)) {
if (node && node.dataset && node.dataset.breakInside === "avoid") {
return node;
}
}
return null;
}
export function breakAfterAvoidParentNode(node) {
while ((node = node.parentNode)) {
if (node && node.dataset && node.dataset.breakAfter === "avoid") {
return node;
}
}
return null;
}
export function breakInsideAvoidNode(node) {
if (node && node.dataset && node.dataset.breakInside === "avoid") {
return node;
}
let children = node.children;
if (children) {
for (const child of children) {
if (child && child.dataset && child.dataset.breakInside === "avoid") {
return child;
}
breakInsideAvoidNode(child);
}
}
return null;
}
export function breakAfterAvoidNode(node) {
if (node && node.dataset && node.dataset.breakAfter === "avoid") {
return node;
}
let children = node.children;
if (children) {
for (const child of children) {
if (child && child.dataset && child.dataset.breakAfter === "avoid") {
return child;
}
breakAfterAvoidNode(child);
}
}
return null;
}
/** /**
* Version of |nextSibling| that skips nodes that are entirely * Version of |nextSibling| that skips nodes that are entirely
* whitespace or comments. * whitespace or comments.
......