diff --git a/examples/assets/styles/mody-dick.css b/examples/assets/styles/moby-dick.css similarity index 97% rename from examples/assets/styles/mody-dick.css rename to examples/assets/styles/moby-dick.css index d5ed17a5f900bc317e35469c8e8550834a83bb4f..f955bb16f1c7f1c32aba171dfee39c797252a6c6 100755 --- a/examples/assets/styles/mody-dick.css +++ b/examples/assets/styles/moby-dick.css @@ -2,7 +2,7 @@ size: 8.5in 11in; margin: 20mm 25mm; marks: crop; - + @footnote { margin: 0.6em 0 0 0; padding: 0.3em 0 0 0; @@ -48,10 +48,16 @@ @bottom-right-corner { content: counter(page); } + + @top-center{ + content: string(booktitle); + } } @page cover { - size: 'letter'; /* measured from scanned book */ + @top-center{ + content: none; + } } @@ -79,8 +85,8 @@ font-weight: bold; font-style: italic; } -#c001s0000 { - string-set: booktitle "hi"; +section:nth-child(1) h1 { + string-set: booktitle content(text); } diff --git a/examples/aurorae/index.html b/examples/aurorae/index.html index f3d855ee5c2f06f3076908640998656c0ee0f982..6725c4d2c5f7d36ac48028ffbb7f54222572cde7 100644 --- a/examples/aurorae/index.html +++ b/examples/aurorae/index.html @@ -50,20 +50,23 @@ preview = (pair[1] === "true"); } } + let flowText = document.querySelector("#flow"); // styles let styles = new Paged.Styler(); - let moby = await styles.add("book.css", "paged-js/layout-pagedjs.css"); + let styleText = await styles.add("book.css", "paged-js/layout-pagedjs.css"); + styles.contents(flowText.content); // console.log(moby); - let flowText = document.querySelector("#flow"); - let t0 = performance.now(); let flow = new Paged.Chunker(flowText.content, undefined, styles.breaks, preview).then((flow) => { let t1 = performance.now(); console.log("Rendering " + flow.total + " pages took " + (t1 - t0) + " milliseconds."); + + // let pages = document.querySelector(".pages"); + // styles.counters(pages); }); let resizer = () => { diff --git a/examples/aurorae/paged-js/layout-pagedjs.css b/examples/aurorae/paged-js/layout-pagedjs.css index 18618c810f27fc31f1e8a432d65cda1a21359481..3fbbb5376e59ad5a4506a56135b6601065a7c5ee 100644 --- a/examples/aurorae/paged-js/layout-pagedjs.css +++ b/examples/aurorae/paged-js/layout-pagedjs.css @@ -20,11 +20,14 @@ html{ @page { size: 148mm 210mm portait; + } - background-image: repeating-linear-gradient(180deg, transparent 0, transparent 14px , rgba(0,255,0,0.7) var(--baseline)) ; - background-size: cover; - background-position: 0 -3px , 0 0 - + @media screen { + .page { + background-image: repeating-linear-gradient(180deg, transparent 0, transparent 14px , rgba(0,255,0,0.7) var(--baseline)) ; + background-size: cover; + background-position: 0 -3px , 0 0 + } } /*@page:first { @@ -212,7 +215,7 @@ html{ .toc-chap a[href]::before, .toc-part-app a[href]::before, .toc-app a[href]::before { - content: target-text(attr(href), content); + content: target-text(attr(href url), content); } .toc-chap{ diff --git a/examples/epub.html b/examples/epub.html index 6d16b08e11cef20b20e9a8dc75a482ba1d694c32..4b0914d6eab387510f2e21c2d35e15228fd0f3db 100644 --- a/examples/epub.html +++ b/examples/epub.html @@ -10,7 +10,7 @@ - + - + + + + + + + +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +

The aurora as known
to the ancients

+

The aurora as known to the ancients

+ + +

In Seneca’s ‘Quæstiones Naturales,’ Lib. I. c. xiv., we find the following:Seneca’s ‘Quæstiones Naturales,’ Lib. I. c. xiv. Description of Auroræ.—“Tempus est, alios quoque ignes percurrere, + quorum diversæ figuræ sunt. Aliquando emicat stella, aliquando ardores sunt, aliquando fixi et hærentes, + nonnunquam volubiles. Horum plura genera conspiciantur. Sunt Bothynoë βόθυνος, a hollow. Seneca’s ‘Quæstiones Naturales,’ Lib. I. c. xiv. Description of Auroræ., quum velut corona cingente + introrsus igneus cœli recessus est similis effossæ in orbem speluncæ. Sunt Pithitæ πίθος, a cask., quum magnitudo + vasti rotundique ignis dolio similis, vel fertur vel in uno loco flagrat. Sunt Chasmata χάσμς, a chasm., quum aliquod + cœli spatium desedit, et flammam dehiscens, velut in abdito, ostentat. Colores quoque omnium horum plurimi sunt. Quidam ruboris acerrimi, quidam evanidæ ac levis flammæ, quidam candidæ lucis, quidam micantes, quidam æqualiter et sine eruptionibus aut radiis fulvi. +

+ +

+ +

Seneca,c. xv.C. xv. “Inter hæc ponas licet et quod frequenter in historiis legimus, + cœlum ardere visum: cujus nonnunquam tam sublimis ardor est ut inter + ipsa sidera videatur, nonnunquam tam humilis ut speciem longinqui incendii + præbeat.

+ +

“Sub Tiberio Cæsare cohortes in auxilium Ostiensis coloniæ cucurrerunt, + tanquam conflagrantis, quum cœli ardor fuisset per magnam partem noctis, + parum lucidus crassi fumidique ignis.”

+ +

We may translate this:Seneca,c. xv.—“It is time other fires also to describe, of which + there are diverse forms.

+ +

“Sometimes a star shines forth; at times there are fire-glows, sometimes + fixed and persistent, sometimes flitting. Of these many sorts may be distinguished. + There are Bothynoë, when, as within a surrounding corona, the + fiery recess of the sky is like to a cave dug out of space. There

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

are Pithitæ, + when the expanse of a vast and rounded fire similar to a tub (dolium) is either + carried about or glows in one spot.

+ +

“There are Chasmata, when a certain portion of the sky opens, and gaping + displays the flame as in a porch. The colours also of all these are many. + Certain are of the brightest red, some of a flitting and light flame-colour, + some of a white light, others shining, some steadily and yellow without + eruptions or rays.

+ +

+ + +

“Amongst these we may notice, what we frequently read of in history, the + sky is seen to burn, the glow of which is occasionally so high that it may be + seen amongst the stars themselves, sometimes so near the Earth (humilis) that + it assumes the form of a distant fire. Under Tiberius Cæsar the cohorts ran + together in aid of the colony of Ostia as if it were in flames, when the + glowing of the sky lasted through a great part of the night, shining dimly + like a vast and smoking fire.”

+ + +

From the above passages many striking particulars of the Aurora may be + gathered; and by the division of the forms of Aurora into classes it is + evident they were, at that period, the subject of frequent observation. + The expression Auroræ frequently read of in history.“et quod frequenter in historiis legimus” shows, too, that the + phenomena of Auroral displays were a matter of record and discussion with + the writers of the day.

+ +

Various forms of Aurora may be recognized in the passages from Chap. xiv.; + while in those from Chap. xv. a careful distinction is drawn between the + Auroræ seen in the zenith or the upper regions of the sky, and those seen on + the horizon or apparently (and no doubt in some cases actually) near the + Earth’s surface.

+ + +

The description of the cohorts running to the fire only to find it an Aurora, + calls to mind the many similar events happening in our own days. Not, + however, but that a mistake may sometimes occur in an opposite direction. + A spurious Aurora.“In the memoirs of Baron Stockmar an amusing anecdote is related of one + Herr von Radowitz, who was given to making the most of easily picked up + information. A friend of the Baron’s went to an evening party near Frankfort, + where he expected to meet Herr von Radowitz. On his way he saw a + barn burning, stopped his carriage, assisted the people, and waited till the + flames were nearly extinguished. When he arrived at his friend’s house he + found Herr von Radowitz, who had previously taken the party to the top of + the building to see an Aurora, dilating on terrestrial magnetism, electricity, + and so forth. Radowitz asked Stockmar’s friend, “Have you seen the beautiful + Aurora Borealis?” He replied, “Certainly; I was there myself; it will soon + be over.” An explanation followed as to the barn on fire: Radowitz was + silent some ten minutes, then took up his hat and quietly disappeared.

+ + + +
+
+ +
+ + + + + + + + diff --git a/src/chunker/layout.js b/src/chunker/layout.js index 9296b6da7842806ba6d36f7e8b671ad17c64134a..91fc1aedb61452c81202651c825507b4e6c7b61d 100644 --- a/src/chunker/layout.js +++ b/src/chunker/layout.js @@ -248,12 +248,12 @@ class Layout { if (currentNode.childNodes.length === 0) { // Check in original - let original = currentNode.getAttribute("children"); + let original = currentNode.getAttribute("data-children"); if (original != 0) { currentNode.remove(); } } else if (currentNode.textContent.trim().length === 0) { - let original = currentNode.getAttribute("text"); + let original = currentNode.getAttribute("data-text"); if (original != 0) { currentNode.remove(); } diff --git a/src/chunker/parser.js b/src/chunker/parser.js index a6685df7e2d13acddc2780acb88ad7cb68b8c3b9..c7f9c714a015aef28b9105d6d530d516440eb23b 100644 --- a/src/chunker/parser.js +++ b/src/chunker/parser.js @@ -55,9 +55,9 @@ class ContentParser { let uuid = UUID(); node.setAttribute("ref", uuid); - node.setAttribute("children", node.childNodes.length); + node.setAttribute("data-children", node.childNodes.length); - node.setAttribute("text", node.textContent.trim().length); + node.setAttribute("data-text", node.textContent.trim().length); } } diff --git a/src/styles/base.js b/src/styles/base.js index af058e0809bce30d21a44dc4649c29744e9be24e..b4ffb071aa68dc0764b63ace5ccb4474675de4fc 100644 --- a/src/styles/base.js +++ b/src/styles/base.js @@ -1,4 +1,13 @@ export default ` +:root { + --width: 8.5in; + --height: 11in; + --margin-top: 1in; + --margin-right: 1in; + --margin-bottom: 1in; + --margin-left: 1in; +} + .page { box-sizing: border-box; width: var(--width); diff --git a/src/styles/sheet.js b/src/styles/sheet.js index d99d8dd20a27333bc66a9a17663c36b4751a8e7f..ae832d37a2e00def7af2da8be9afa603bc3109f0 100644 --- a/src/styles/sheet.js +++ b/src/styles/sheet.js @@ -27,6 +27,14 @@ class Sheet { this.addRootVars(this.ast, this.pages["*"].width, this.pages["*"].height); this.addRootPage(this.ast, this.pages["*"].width, this.pages["*"].height); } + + this.stringSets = this.getStringSets(this.ast); + + let targets = this.getTargets(this.ast); + this.counterTargets = targets.counterTargets; + this.textTargets = targets.textTargets; + + this.replaceContents(this.ast); } // parse @@ -542,8 +550,172 @@ class Sheet { } } - addPageResets(ast, width, height) { + getStringSets(ast) { + let stringSetSelectors = {}; + csstree.walk(ast, { + visit: 'Rule', + enter: (node, item, list) => { + csstree.walk(node, { + visit: 'Declaration', + enter: (declaration, dItem, dList) => { + if (declaration.property === "string-set") { + let selector = csstree.generate(node.prelude); + + let identifier = declaration.value.children.first().name + + let value; + csstree.walk(declaration, { + visit: 'Function', + enter: (node, item, list) => { + value = csstree.generate(node); + } + }); + + stringSetSelectors[identifier] = { + identifier: identifier, + value: value, + selector: selector + } + } + } + }); + } + }); + return stringSetSelectors; + } + + getTargets(ast) { + let textTargets = {}; + let counterTargets = {}; + csstree.walk(ast, { + visit: 'Rule', + enter: (node, item, list) => { + csstree.walk(node, { + visit: 'Declaration', + enter: (declaration, dItem, dList) => { + if (declaration.property === "content") { + + csstree.walk(declaration, { + visit: 'Function', + enter: (funcNode, fItem, fList) => { + if (funcNode.name === "target-text") { + + let selector = csstree.generate(node.prelude); + let first = funcNode.children.first(); + let last = funcNode.children.last(); + let func = first.name; + + let value = csstree.generate(funcNode); + + let args = [] + + first.children.forEach((child) => { + if (child.type === "Identifier") { + args.push(child.name); + } + }); + + let style; + if (last !== first) { + style = last.name; + } + + selector.split(",").forEach((s) => { + textTargets[s] = { + func: func, + args: args, + value: value, + style: style, + selector: s, + fullSelector: selector + } + }); + + } + + if (funcNode.name === "target-counter") { + let selector = csstree.generate(node.prelude); + let first = funcNode.children.first(); + let last = funcNode.children.last(); + let func = first.name; + + let value = csstree.generate(funcNode); + + let args = [] + + first.children.forEach((child) => { + if (child.type === "Identifier") { + args.push(child.name); + } + }); + + let counter; + if (last !== first) { + counter = last.name; + } + + selector.split(",").forEach((s) => { + counterTargets[s] = { + func: func, + args: args, + value: value, + counter: counter, + selector: s, + fullSelector: selector + } + }); + + } + } + }); + + + } + } + }); + } + }); + + return { + textTargets, + counterTargets + }; + } + + replaceContents(ast) { + csstree.walk(ast, { + visit: 'Declaration', + enter: (declaration, dItem, dList) => { + if (declaration.property === "content") { + csstree.walk(declaration, { + visit: 'Function', + enter: (func, fItem, fList) => { + // console.log(func); + + if (func.name === "string") { + let identifier = func.children && func.children.first().name; + func.name = "var"; + func.children = new csstree.List(); + + func.children.append(func.children.createItem({ + type: "Identifier", + loc: null, + name: "--string-" + identifier + })); + } + if (func.name === "target-text") { + // console.log(func); + } + + if (func.name === "target-counter") { + // console.log(func); + } + } + }); + } + } + }); } addRootVars(ast, width, height) { diff --git a/src/styles/styler.js b/src/styles/styler.js index 60309652e3403ced4b209d725418602b74319c1e..61decfe397bfc60a4907f7b45f44849d9db41532 100644 --- a/src/styles/styler.js +++ b/src/styles/styler.js @@ -1,10 +1,14 @@ -import Sheet from './sheet.js'; -import baseStyles from './base.js'; +import Sheet from './sheet'; +import baseStyles from './base'; +import { UUID } from '../utils/utils'; class Styler { constructor() { this.sheets = []; this.addBase(); + this.styleEl = document.createElement("style"); + document.head.appendChild(this.styleEl); + this.styleSheet = this.styleEl.sheet; } async add() { @@ -20,6 +24,9 @@ class Styler { .then((originals) => { let text = ""; let pageBreaks = {}; + let stringSets = {}; + let textTargets = {}; + let counterTargets = {}; originals.forEach((original, index) => { let href = arguments[index]; @@ -28,6 +35,9 @@ class Styler { this.sheets.push(sheet); this.mergeBreaks(pageBreaks, sheet.pageBreaks); + stringSets = Object.assign(stringSets, sheet.stringSets); + textTargets = Object.assign(textTargets, sheet.textTargets); + counterTargets = Object.assign(counterTargets, sheet.counterTargets); text += sheet.toString(); }) @@ -36,6 +46,11 @@ class Styler { this.breaks = pageBreaks; + this.stringSets = stringSets; + + this.textTargets = textTargets; + this.counterTargets = counterTargets; + return text; }); } @@ -65,6 +80,77 @@ class Styler { head.appendChild(style); } + contents(fragment) { + // console.log(fragment); + for (let name of Object.keys(this.stringSets)) { + let set = this.stringSets[name]; + let selected = fragment.querySelector(set.selector); + + if (selected) { + let cssVar; + if (set.value === "content" || set.value === "content(text)") { + cssVar = selected.textContent.replace(/\\([\s\S])|(["|'])/g,"\\$1$2"); + this.styleSheet.insertRule(`:root { --string-${name}: "${cssVar}"; }`, this.styleSheet.cssRules.length); + } else { + console.log(set.value + "needs css replacement"); + } + } + } + + Object.keys(this.textTargets).forEach((name) => { + let target = this.textTargets[name]; + let split = target.selector.split("::"); + let query = split[0]; + let queried = fragment.querySelectorAll(query); + queried.forEach((selected, index) => { + let val = this.attr(selected, target.args); + let element = fragment.querySelector(val); + + if (element) { + if (target.style === "content") { + let text = element.textContent; + let selector = UUID(); + + selected.setAttribute("data-target-text", selector); + + let psuedo = ""; + if (split.length > 1) { + psuedo += "::" + split[1]; + } + + this.styleSheet.insertRule(`[data-target-text="${selector}"]${psuedo} { content: "${element.textContent}"; }`, this.styleSheet.cssRules.length); + } + } + }); + + }); + } + + counters(root) { + Object.keys(this.counterTargets).forEach((name) => { + let target = this.counterTargets[name]; + let split = target.selector.split("::"); + let query = split[0]; + let queried = root.querySelectorAll(query); + queried.forEach((selected, index) => { + let val = this.attr(selected, target.args); + let element = fragment.querySelector(val); + + if (element) { + console.log("element", element); + } + }); + }); + } + + attr(element, attributes) { + for (var i = 0; i < attributes.length; i++) { + if(element.hasAttribute(attributes[i])) { + return element.getAttribute(attributes[i]); + } + } + } + } export default Styler; diff --git a/src/utils/utils.js b/src/utils/utils.js index 9981b21573d6f42ded1df655a2628536d49aec27..f855e488f7ea6ed311c9699eb6c2757a94fa6d48 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -29,3 +29,87 @@ export function UUID() { return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); }); } + +// From: https://hg.mozilla.org/mozilla-central/file/tip/toolkit/modules/css-selector.js#l52 + +/** + * Find the position of [element] in [nodeList]. + * @returns an index of the match, or -1 if there is no match + */ +export function positionInNodeList(element, nodeList) { + for (let i = 0; i < nodeList.length; i++) { + if (element === nodeList[i]) { + return i; + } + } + return -1; +} + +/** + * Find a unique CSS selector for a given element + * @returns a string such that ele.ownerDocument.querySelector(reply) === ele + * and ele.ownerDocument.querySelectorAll(reply).length === 1 + */ +export function findCssSelector(ele) { + let document = ele.ownerDocument; + // Fred: commented out to allow for parsing in fragments + // if (!document || !document.contains(ele)) { + // throw new Error("findCssSelector received element not inside document"); + // } + + let cssEscape = window.CSS.escape; + + // document.querySelectorAll("#id") returns multiple if elements share an ID + if (ele.id && + document.querySelectorAll("#" + cssEscape(ele.id)).length === 1) { + return "#" + cssEscape(ele.id); + } + + // Inherently unique by tag name + let tagName = ele.localName; + if (tagName === "html") { + return "html"; + } + if (tagName === "head") { + return "head"; + } + if (tagName === "body") { + return "body"; + } + + // We might be able to find a unique class name + let selector, index, matches; + if (ele.classList.length > 0) { + for (let i = 0; i < ele.classList.length; i++) { + // Is this className unique by itself? + selector = "." + cssEscape(ele.classList.item(i)); + matches = document.querySelectorAll(selector); + if (matches.length === 1) { + return selector; + } + // Maybe it's unique with a tag name? + selector = cssEscape(tagName) + selector; + matches = document.querySelectorAll(selector); + if (matches.length === 1) { + return selector; + } + // Maybe it's unique using a tag name and nth-child + index = positionInNodeList(ele, ele.parentNode.children) + 1; + selector = selector + ":nth-child(" + index + ")"; + matches = document.querySelectorAll(selector); + if (matches.length === 1) { + return selector; + } + } + } + + // Not unique enough yet. As long as it's not a child of the document, + // continue recursing up until it is unique enough. + if (ele.parentNode !== document && ele.parentNode.nodeType === 1) { + index = positionInNodeList(ele, ele.parentNode.children) + 1; + selector = findCssSelector(ele.parentNode) + " > " + + cssEscape(tagName) + ":nth-child(" + index + ")"; + } + + return selector; +};