Commit 0f226161 authored by Guillaume's avatar Guillaume
Browse files

Ignore non signifiant siblings

parent 0d70e3b5
......@@ -8,7 +8,7 @@
Break before Avoid
</title>
<script src="../../../../../dist/paged.polyfill.js"></script>
<script src="../../../../dist/paged.polyfill.js"></script>
<style>
......
......@@ -8,7 +8,7 @@
Break before right
</title>
<script src="../../../../../dist/paged.polyfill.js"></script>
<script src="../../../../dist/paged.polyfill.js"></script>
<style>
:root {
......
......@@ -5,7 +5,7 @@
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>
string set specs </title>
<script src="../../../dist/paged.polyfill.js"></script>
<script src="../../dist/paged.polyfill.js"></script>
<style>
:root {
font-size: 18px;
......@@ -35,9 +35,6 @@
}
}
p {
line-height: 22px;
}
......@@ -163,4 +160,4 @@
</section>
</body>
</html>
\ No newline at end of file
</html>
......@@ -117,10 +117,12 @@ describe("string-start", () => {
}
});
// The start value is empty, as the string had not yet been set at the start of the page.
// 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"));
expect(text).toEqual("\"aaa\"");
expect(text).toEqual("\"\"");
});
it("should set the running header as \"fff\" on the third page", async () => {
......
......@@ -36,22 +36,22 @@ describe("whitespaces", () => {
expect(emptyCharCodes).toEqual([]);
const oneSpaceCharCodes = await getCharCodes(page, ".whitespaces-f");
expect(oneSpaceCharCodes).toEqual([]);
expect(oneSpaceCharCodes).toEqual([32]);
const twoSpacesCharCodes = await getCharCodes(page, ".whitespaces-g");
expect(twoSpacesCharCodes).toEqual([]);
expect(twoSpacesCharCodes).toEqual([32]);
const twoThinSpacesCharCodes = await getCharCodes(page, ".whitespaces-h");
expect(twoThinSpacesCharCodes).toEqual([8201, 8201]);
const twoTabsCharCodes = await getCharCodes(page, ".whitespaces-i");
expect(twoTabsCharCodes).toEqual([]);
expect(twoTabsCharCodes).toEqual([32]);
const twoTabsAndNewLineCharCodes = await getCharCodes(page, ".whitespaces-j");
expect(twoTabsAndNewLineCharCodes).toEqual([]);
expect(twoTabsAndNewLineCharCodes).toEqual([32]);
const spacesTabAndNewLineCharCodes = await getCharCodes(page, ".whitespaces-k");
expect(spacesTabAndNewLineCharCodes).toEqual([]);
expect(spacesTabAndNewLineCharCodes).toEqual([32]);
const NonBreakingSpaceAndSpacesCharCodes = await getCharCodes(page, ".whitespaces-l");
expect(NonBreakingSpaceAndSpacesCharCodes).toEqual([160, 32, 32, 32, 32]);
......
import {getBoundingClientRect, getClientRects} from "../utils/utils";
import {
getBoundingClientRect,
getClientRects
} from "../utils/utils";
import {
walk,
nodeAfter,
nodeBefore,
rebuildAncestors,
needsBreakBefore,
needsPreviousBreakAfter,
needsPageBreak,
isElement,
isText,
indexOf,
indexOfTextNode,
child,
cloneNode,
findElement,
child,
isContainer,
hasContent,
validNode,
indexOf,
indexOfTextNode,
isContainer,
isElement,
isText,
letters,
needsBreakBefore,
needsPageBreak,
needsPreviousBreakAfter,
nodeAfter,
nodeBefore, previousSignificantNode,
prevValidNode,
words,
letters
rebuildAncestors,
validNode,
walk,
words
} from "../utils/dom";
import EventEmitter from "event-emitter";
import Hook from "../utils/hook";
......@@ -57,7 +54,7 @@ class Layout {
this.maxChars = this.settings.maxChars || MAX_CHARS_PER_BREAK;
}
async renderTo(wrapper, source, breakToken, bounds=this.bounds) {
async renderTo(wrapper, source, breakToken, bounds = this.bounds) {
let start = this.getStart(source, breakToken);
let walker = walk(start, source);
......@@ -149,7 +146,7 @@ class Layout {
return newBreakToken;
}
breakAt(node, offset=0) {
breakAt(node, offset = 0) {
return {
node,
offset
......@@ -157,7 +154,7 @@ class Layout {
}
shouldBreak(node) {
let previousSibling = node.previousSibling;
let previousSibling = previousSignificantNode(node);
let parentNode = node.parentNode;
let parentBreakBefore = needsBreakBefore(node) && parentNode && !previousSibling && needsBreakBefore(parentNode);
let doubleBreakBefore;
......@@ -182,7 +179,7 @@ class Layout {
return start;
}
append(node, dest, breakToken, shallow=true, rebuild=true) {
append(node, dest, breakToken, shallow = true, rebuild = true) {
let clone = cloneNode(node, !shallow);
......@@ -233,16 +230,16 @@ class Layout {
async awaitImageLoaded(image) {
return new Promise(resolve => {
if (image.complete !== true) {
image.onload = function() {
let { width, height } = window.getComputedStyle(image);
image.onload = function () {
let {width, height} = window.getComputedStyle(image);
resolve(width, height);
};
image.onerror = function(e) {
let { width, height } = window.getComputedStyle(image);
image.onerror = function (e) {
let {width, height} = window.getComputedStyle(image);
resolve(width, height, e);
};
} else {
let { width, height } = window.getComputedStyle(image);
let {width, height} = window.getComputedStyle(image);
resolve(width, height);
}
});
......@@ -262,7 +259,7 @@ class Layout {
break;
}
if(window.getComputedStyle(node)["break-inside"] === "avoid") {
if (window.getComputedStyle(node)["break-inside"] === "avoid") {
breakNode = node;
break;
}
......@@ -292,7 +289,7 @@ class Layout {
const walker = document.createTreeWalker(renderedNodeFromSource, NodeFilter.SHOW_ELEMENT);
const lastChildOfRenderedNodeFromSource = walker.lastChild();
const lastChildOfRenderedNodeMatchingFromRendered = findElement(lastChildOfRenderedNodeFromSource, rendered);
// Check if we found that the last child in source
// Check if we found that the last child in source
if (!lastChildOfRenderedNodeMatchingFromRendered) {
// Pending content to be rendered before virtual break token
return;
......@@ -349,7 +346,7 @@ class Layout {
}
findBreakToken(rendered, source, bounds=this.bounds, extract=true) {
findBreakToken(rendered, source, bounds = this.bounds, extract = true) {
let overflow = this.findOverflow(rendered, bounds);
let breakToken, breakLetter;
......@@ -385,18 +382,18 @@ class Layout {
return breakToken;
}
hasOverflow(element, bounds=this.bounds) {
hasOverflow(element, bounds = this.bounds) {
let constrainingElement = element && element.parentNode; // this gets the element, instead of the wrapper for the width workaround
let { width } = element.getBoundingClientRect();
let {width} = element.getBoundingClientRect();
let scrollWidth = constrainingElement ? constrainingElement.scrollWidth : 0;
return Math.max(Math.floor(width), scrollWidth) > Math.round(bounds.width);
}
findOverflow(rendered, bounds=this.bounds) {
findOverflow(rendered, bounds = this.bounds) {
if (!this.hasOverflow(rendered, bounds)) return;
let start = Math.round(bounds.left);
let end = Math.round(bounds.right);
let end = Math.round(bounds.right);
let range;
let walker = walk(rendered.firstChild, rendered);
......@@ -421,7 +418,7 @@ class Layout {
// Check if it is a float
let isFloat = false;
if (isElement(node) ) {
if (isElement(node)) {
let styles = window.getComputedStyle(node);
isFloat = styles.getPropertyValue("float") !== "none";
skip = styles.getPropertyValue("break-inside") === "avoid";
......@@ -451,8 +448,8 @@ class Layout {
}
if (!range && isText(node) &&
node.textContent.trim().length &&
window.getComputedStyle(node.parentNode)["break-inside"] !== "avoid") {
node.textContent.trim().length &&
window.getComputedStyle(node.parentNode)["break-inside"] !== "avoid") {
let rects = getClientRects(node);
let rect;
......@@ -464,7 +461,7 @@ class Layout {
}
}
if(left >= end) {
if (left >= end) {
range = document.createRange();
offset = this.textBreak(node, start, end);
if (!offset) {
......@@ -496,7 +493,7 @@ class Layout {
}
findEndToken(rendered, source, bounds=this.bounds) {
findEndToken(rendered, source, bounds = this.bounds) {
if (rendered.childNodes.length === 0) {
return;
}
......@@ -508,7 +505,7 @@ class Layout {
if (!validNode(lastChild)) {
// Only get elements with refs
lastChild = lastChild.previousSibling;
} else if(!validNode(lastChild.lastChild)) {
} else if (!validNode(lastChild.lastChild)) {
// Deal with invalid dom items
lastChild = prevValidNode(lastChild.lastChild);
break;
......@@ -605,12 +602,12 @@ class Layout {
hyphenateAtBreak(startContainer, breakLetter) {
if (isText(startContainer)) {
let startText = startContainer.textContent;
let prevLetter = startText[startText.length-1];
let prevLetter = startText[startText.length - 1];
// Add a hyphen if previous character is a letter or soft hyphen
if (
(breakLetter && /^\w|\u00AD$/.test(prevLetter) && /^\w|\u00AD$/.test(breakLetter)) ||
(!breakLetter && /^\w|\u00AD$/.test(prevLetter))
(breakLetter && /^\w|\u00AD$/.test(prevLetter) && /^\w|\u00AD$/.test(breakLetter)) ||
(!breakLetter && /^\w|\u00AD$/.test(prevLetter))
) {
startContainer.parentNode.classList.add("pagedjs_hyphen");
startContainer.textContent += this.settings.hyphenGlyph || "\u2011";
......
import {UUID} from "../utils/utils";
import {isElement} from "../utils/dom";
import {isElement, isIgnorable, nextSignificantNode, previousSignificantNode} from "../utils/dom";
/**
* Render a flow of text offscreen
......@@ -71,22 +71,39 @@ class ContentParser {
}
removeEmpty(content) {
const self = this;
const treeWalker = document.createTreeWalker(
content,
NodeFilter.SHOW_TEXT,
{ acceptNode: function(node) {
// Only remove more than a single space
if (self.isIgnorable(node)) {
if (node.textContent.length > 1 && isIgnorable(node)) {
// Don't touch whitespace if text is pre-formatted
// Do not touch the content if text is pre-formatted
let parent = node.parentNode;
let pre = isElement(parent) && parent.closest("pre");
if (pre) {
return NodeFilter.FILTER_REJECT;
}
// TODO: we also need to ignore spaces when the parent has white-space rule:
const nextSibling = previousSignificantNode(node);
const previousSibling = nextSignificantNode(node);
if (nextSibling === null && previousSibling === null) {
// we should not remove a Node that does not have any siblings.
node.textContent = " ";
return NodeFilter.FILTER_REJECT;
}
if (nextSibling === null) {
// we can safely remove this node
return NodeFilter.FILTER_ACCEPT;
}
if (previousSibling === null) {
// we can safely remove this node
return NodeFilter.FILTER_ACCEPT;
}
// replace the content with a single space
node.textContent = " ";
// TODO: we also need to preserve sequences of white spaces when the parent has "white-space" rule:
// pre
// Sequences of white space are preserved. Lines are only broken at newline characters in the source and at <br> elements.
//
......@@ -104,7 +121,7 @@ class ContentParser {
//
// See: https://developer.mozilla.org/en-US/docs/Web/CSS/white-space#Values
return NodeFilter.FILTER_ACCEPT;
return NodeFilter.FILTER_REJECT;
} else {
return NodeFilter.FILTER_REJECT;
}
......@@ -122,42 +139,6 @@ class ContentParser {
}
}
/**
* Throughout, whitespace is defined as one of the characters
* "\t" TAB \u0009
* "\n" LF \u000A
* "\r" CR \u000D
* " " SPC \u0020
*
* This does not use Javascript's "\s" because that includes non-breaking
* spaces (and also some other characters).
*/
/**
* Determine if a node should be ignored by the iterator functions.
* taken from https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace#Whitespace_helper_functions
*
* @param {Node} node An object implementing the DOM1 |Node| interface.
* @return {boolean} true if the node is:
* 1) A |Text| node that is all whitespace
* 2) A |Comment| node
* and otherwise false.
*/
isIgnorable(node) {
return (node.nodeType === 8) || // A comment node
((node.nodeType === 3) && this.isAllWhitespace(node)); // a text node, all whitespace
}
/**
* Determine whether a node's text content is entirely whitespace.
*
* @param {Node} node A node implementing the |CharacterData| interface (i.e., a |Text|, |Comment|, or |CDATASection| node
* @return {boolean} true if all of the text content of |nod| is whitespace, otherwise false.
*/
isAllWhitespace(node) {
return !(/[^\t\n\r ]/.test(node.textContent));
}
find(ref) {
return this.refs[ref];
}
......
......@@ -39,52 +39,45 @@ export function *walk(start, limiter) {
}
export function nodeAfter(node, limiter) {
let after = node;
if (after.nextSibling) {
if (limiter && node === limiter) {
return;
}
after = after.nextSibling;
} else {
while (after) {
after = after.parentNode;
if (limiter && after === limiter) {
after = undefined;
break;
if (limiter && node === limiter) {
return;
}
let significantNode = nextSignificantNode(node);
if (significantNode) {
return significantNode;
}
if (node.parentNode) {
while ((node = node.parentNode)) {
if (limiter && node === limiter) {
return;
}
if (after && after.nextSibling) {
after = after.nextSibling;
break;
significantNode = nextSignificantNode(node);
if (significantNode) {
return significantNode;
}
}
}
return after;
}
export function nodeBefore(node, limiter) {
let before = node;
if (before.previousSibling) {
if (limiter && node === limiter) {
return;
}
before = before.previousSibling;
} else {
while (before) {
before = before.parentNode;
if (limiter && before === limiter) {
before = undefined;
break;
if (limiter && node === limiter) {
return;
}
let significantNode = previousSignificantNode(node);
if (significantNode) {
return significantNode;
}
if (node.parentNode) {
while ((node = node.parentNode)) {
if (limiter && node === limiter) {
return;
}
if (before && before.previousSibling) {
before = before.previousSibling;
break;
significantNode = previousSignificantNode(node);
if (significantNode) {
return significantNode;
}
}
}
return before;
}
export function elementAfter(node, limiter) {
......@@ -530,3 +523,73 @@ export function indexOfTextNode(node, parent) {
return index;
}
/**
* Throughout, whitespace is defined as one of the characters
* "\t" TAB \u0009
* "\n" LF \u000A
* "\r" CR \u000D
* " " SPC \u0020
*
* This does not use Javascript's "\s" because that includes non-breaking
* spaces (and also some other characters).
*/
/**
* Determine if a node should be ignored by the iterator functions.
* taken from https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace#Whitespace_helper_functions
*
* @param {Node} node An object implementing the DOM1 |Node| interface.
* @return {boolean} true if the node is:
* 1) A |Text| node that is all whitespace
* 2) A |Comment| node
* and otherwise false.
*/
export function isIgnorable(node) {
return (node.nodeType === 8) || // A comment node
((node.nodeType === 3) && isAllWhitespace(node)); // a text node, all whitespace
}
/**
* Determine whether a node's text content is entirely whitespace.
*
* @param {Node} node A node implementing the |CharacterData| interface (i.e., a |Text|, |Comment|, or |CDATASection| node
* @return {boolean} true if all of the text content of |nod| is whitespace, otherwise false.
*/
export function isAllWhitespace(node) {
return !(/[^\t\n\r ]/.test(node.textContent));
}
/**
* Version of |previousSibling| that skips nodes that are entirely
* whitespace or comments. (Normally |previousSibling| is a property
* of all DOM nodes that gives the sibling node, the node that is
* a child of the same parent, that occurs immediately before the
* reference node.)
*
* @param {ChildNode} sib The reference node.
* @return {Node|null} Either:
* 1) The closest previous sibling to |sib| that is not ignorable according to |is_ignorable|, or
* 2) null if no such node exists.
*/
export function previousSignificantNode(sib) {
while ((sib = sib.previousSibling)) {
if (!isIgnorable(sib)) return sib;
}
}
/**
* Version of |nextSibling| that skips nodes that are entirely
* whitespace or comments.
*
* @param {ChildNode} sib The reference node.
* @return {Node|null} Either:
* 1) The closest next sibling to |sib| that is not ignorable according to |is_ignorable|, or
* 2) null if no such node exists.
*/
export function nextSignificantNode(sib) {
while ((sib = sib.nextSibling)) {
if (!isIgnorable(sib)) return sib;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment