This is the javascript file that alters your EPUB HTML
this.BKDOMCleanup = {}; | |
/*** Utility functions ***/ | |
BKDOMCleanup.getEffectiveWritingMode = function() { | |
// If body has one DIV child, use that, else use the body | |
var mode = null; | |
if (document && document.body) { | |
var element = document.body.firstElementChild || document.body; | |
mode = window.getComputedStyle(element).getPropertyValue("-webkit-writing-mode"); | |
} | |
return mode; | |
} | |
/*** Cleanup functions ***/ | |
// <rdar://problem/13722139> iBooks should not respect text entry | |
BKDOMCleanup._disableContentEditableModes = function() { | |
var nodes = document.querySelectorAll("*[contenteditable]") | |
for (var i = 0; i < nodes.length; i++) { | |
nodes[i].contentEditable = "false"; | |
} | |
} | |
BKDOMCleanup._disableTextFields = function() { | |
var textTypes = {"text": true, "search": true, "tel": true, "url": true, "email": true, "password": true, "number": true}; | |
var nodes = document.querySelectorAll("input, textarea"); | |
for (var i = 0; i < nodes.length; i++) { | |
var node = nodes[i]; | |
if (node instanceof HTMLInputElement) { | |
if (textTypes[node.type]) { | |
// it's a text-type input | |
node.disabled = true; | |
} | |
} else { | |
// textarea | |
node.disabled = true; | |
} | |
} | |
} | |
BKDOMCleanup._recordWritingMode = function() { | |
// Always run this for presenting content and pagination | |
var root = document.documentElement; | |
if (root) { | |
root.setAttribute("__ibooks_writing_mode", BKDOMCleanup.getEffectiveWritingMode()); | |
} | |
} | |
BKDOMCleanup._hoistAttributeName = "__ibooks_hoist_writing_mode"; | |
BKDOMCleanup._unhoistAttributeName = "__ibooks_unhoist_writing_mode"; | |
BKDOMCleanup._recordHoistedWritingMode = function() | |
{ | |
var body = document.body; | |
if (body) | |
{ | |
var child = body.firstElementChild; | |
if (child) | |
{ | |
var unhoistedWritingMode = window.getComputedStyle(body).getPropertyValue("-webkit-writing-mode"); | |
var hoistedWritingMode = window.getComputedStyle(child).getPropertyValue("-webkit-writing-mode"); | |
body.setAttribute(BKDOMCleanup._unhoistAttributeName, unhoistedWritingMode); | |
body.setAttribute(BKDOMCleanup._hoistAttributeName, hoistedWritingMode); | |
} | |
} | |
} | |
BKDOMCleanup._hoistWritingMode = function(hoist) { | |
var mode = document.body.getAttribute(hoist ? BKDOMCleanup._hoistAttributeName : BKDOMCleanup._unhoistAttributeName); | |
var updated = false; | |
// Always run this for pagination to get correct page counts. | |
if (document && mode) | |
{ | |
var root = document.documentElement; | |
if (root) { | |
if (window.getComputedStyle(root).getPropertyValue("-webkit-writing-mode") != mode) | |
{ | |
root.style.setProperty("-webkit-writing-mode", mode); | |
updated = true; | |
} | |
} | |
var body = document.body; | |
if (body) { | |
if (window.getComputedStyle(body).getPropertyValue("-webkit-writing-mode") != mode) | |
{ | |
body.style.setProperty("-webkit-writing-mode", mode); | |
updated = true; | |
} | |
} | |
} | |
return updated; | |
} | |
// Returns an array of DOMElements that should be marked for display overrides | |
// Keep this in sync with the stylesheet | |
BKDOMCleanup._inlineBlockTagName = "__ibooks_has_inline_block"; | |
BKDOMCleanup._multiplePageTagName = "__ibooks_has_multiple_pages"; | |
BKDOMCleanup._markInlineBlockElements = function () { | |
var updated = false; | |
var element = document.querySelectorAll("body > div:only-child > span:empty:first-child + div:last-child")[0]; | |
if (element) | |
{ | |
var elementsToMark = []; | |
var displayMode = window.getComputedStyle(element).getPropertyValue("display"); | |
if (displayMode == "inline-block" || element.getAttribute(BKDOMCleanup._inlineBlockTagName) == "true") | |
{ | |
elementsToMark.push(element); | |
var spanElement = element.previousElementSibling; | |
if (spanElement) | |
{ | |
var spanDisplayMode = window.getComputedStyle(spanElement).getPropertyValue("display"); | |
if (spanDisplayMode == "inline-block" || element.getAttribute(BKDOMCleanup._inlineBlockTagName) == "true") | |
{ | |
elementsToMark.push(spanElement); | |
} | |
} | |
} | |
for (var i = 0; i < elementsToMark.length; i++) | |
{ | |
var elementToMark = elementsToMark[i]; | |
elementsToMark[i].setAttribute(BKDOMCleanup._inlineBlockTagName, "true"); | |
} | |
// mark the document element as well so we know we did this | |
if (elementsToMark.length > 0) | |
{ | |
document.documentElement.setAttribute(BKDOMCleanup._inlineBlockTagName, "true"); | |
updated = true; | |
} | |
} | |
return updated; | |
} | |
BKDOMCleanup._forceStyleRecalc = function() { | |
// force a style recalc | |
var styleElement = document.createElement("style"); | |
styleElement.appendChild(document.createTextNode("*{}")); | |
document.body.appendChild(styleElement); | |
document.body.removeChild(styleElement); | |
var waitForLayoutToFinish = document.body.offsetTop; | |
} | |
BKDOMCleanup._removeBodyInlineStyle = function () { | |
var root = document.body || document.documentElement; | |
if (root) | |
{ | |
var displayMode = window.getComputedStyle(root); | |
if (displayMode == "inline-block") | |
{ | |
root.style.setProperty("display", "block"); | |
} | |
} | |
} | |
BKDOMCleanup._tagAsMultiplePages = function (mark) { | |
var root = document.documentElement; | |
// Right now, we only care about this if we have inline-block in the document | |
var updated = false; | |
if (root && root.getAttribute(BKDOMCleanup._inlineBlockTagName) == "true") | |
{ | |
if (mark && root.getAttribute(BKDOMCleanup._multiplePageTagName) != "true") | |
{ | |
root.setAttribute(BKDOMCleanup._multiplePageTagName, "true"); | |
updated = true; | |
} | |
else if (!mark && root.getAttribute(BKDOMCleanup._multiplePageTagName) == "true") | |
{ | |
root.removeAttribute(BKDOMCleanup._multiplePageTagName); | |
updated = true; | |
} | |
} | |
return updated; | |
} | |
BKDOMCleanup._fixupBodyLang = function(language) { | |
if (language != null && language != "") { | |
var body = document.body; | |
if (!body) return; | |
// check for existing lang | |
var elts = [body, document.documentElement]; | |
for (var i = 0; i < elts.length; i++) { | |
var elt = elts[i]; | |
var xlang = elt.getAttribute("xml:lang"); | |
if ((elt.lang != null && elt.lang != "") || (xlang != null && xlang != "")) { | |
// found a lang | |
return; | |
} | |
} | |
// found no lang; set book's lang on the body | |
body.lang = language; | |
} | |
} | |
BKDOMCleanup._modifyAutoplayNodes = function() { | |
var elts = document.querySelectorAll("video[autoplay], audio[autoplay]"); | |
for (var i = 0; i < elts.length; i++) { | |
var node = elts[i]; | |
node.removeAttribute("autoplay"); | |
node.setAttribute("ibooksautoplay", "true"); | |
} | |
} | |
// <rdar://problem/14120624> OSX: Unable to bring images to expanded mode | |
// | |
// Simple workaround: Make an <a> tag to navigate with that will trigger | |
// the same handler as clicking on a link to the IMG | |
// if was inside a link. | |
BKDOMCleanup._isContainedInAnchor = function(node) { | |
var parent = node.parentElement; | |
var result = false; | |
while (parent && !result) | |
{ | |
if ((parent.tagName || "").toLowerCase() == "a") | |
{ | |
// Anchor tags much have a href to be considered a link | |
if (parent.hasAttribute("href")) | |
{ | |
result = true; | |
} | |
else | |
{ | |
parent = parent.parentElement; | |
} | |
} | |
else | |
{ | |
parent = parent.parentElement; | |
} | |
} | |
return result; | |
} | |
BKDOMCleanup._makeImagesBringUpExpandedOverlay = function (evt) { | |
var img = evt.target; | |
// Make sure the img isn't contained in an <a> tag | |
// If not contained, then trigger the overlay. | |
if (img && !img.hasAttribute("__ibooks_respect_image_size") && !BKDOMCleanup._isContainedInAnchor(img)) | |
{ | |
var src = img.src; | |
var a = document.createElement("a"); | |
a.href = src; | |
a.click(); | |
} | |
} | |
BKDOMCleanup._uniqueCount = 0; | |
BKDOMCleanup._makeTablesBringUpExpandedOverlay = function (evt) { | |
var tbl = evt.target; | |
while (tbl && !(tbl instanceof HTMLTableElement)) | |
{ | |
tbl = tbl.parentElement; | |
} | |
// Make sure the table isn't contained in an <a> tag | |
var body = document.body; | |
if (tbl && body) | |
{ | |
// If not contained, then trigger the overlay. | |
// - only do this if table is wider than body; ideally this would be done based on where the tbl spilled outside the page | |
// but getBoundingClientRects() doesn't return something useful (it returns the rect for the first page) | |
if (!BKDOMCleanup._isContainedInAnchor(tbl) && tbl.scrollWidth > body.clientWidth) | |
{ | |
var a = document.createElement("a"); | |
var range = document.createRange(); | |
range.setStart(tbl, 0); | |
range.setEndAfter(tbl); | |
var cfi = CFI.computeCFI(null, null, null, range); | |
// unless there's changing part of the URL webkit doesn't feel like passing on the navigation events | |
a.href = "#-ibooks-expanded-" + cfi + "?q=" + BKDOMCleanup._uniqueCount; | |
BKDOMCleanup._uniqueCount++; | |
a.click(); | |
} | |
} | |
} | |
BKDOMCleanup._setupImagePreview = function() { | |
var images = document.images; | |
var length = images.length; | |
for (var i = 0; i < length; i++) | |
{ | |
var img = images[i]; | |
img.__bk_original_addEventListener("dblclick", BKDOMCleanup._makeImagesBringUpExpandedOverlay); | |
} | |
} | |
BKDOMCleanup._setupTablePreview = function() { | |
var tables = document.querySelectorAll("table"); | |
var length = tables.length; | |
for (var i = 0; i < length; i++) | |
{ | |
var tbl = tables[i]; | |
tbl.__bk_original_addEventListener("click", BKDOMCleanup._makeTablesBringUpExpandedOverlay); | |
} | |
} | |
BKDOMCleanup._XHTMLNamespace = "http://www.w3.org/1999/xhtml"; | |
BKDOMCleanup._tagImagesThatAreRespectedInSize = function(bookInfo) | |
{ | |
// test if this is an image size we're respecting | |
var images = document.images; | |
for (var i=0; i < images.length; i++) | |
{ | |
var image = images[i]; | |
if (bookInfo && bookInfo.respectImageSizeClass) { | |
var selector; | |
if (bookInfo.respectImageSizeClassIsPrefix) { | |
selector = "*[class|=" + bookInfo.respectImageSizeClass + "]"; | |
} else { | |
selector = "*[class~=" + bookInfo.respectImageSizeClass + "]"; | |
} | |
if (image.webkitMatchesSelector(selector)) { | |
isRespecting = true; | |
image.setAttribute("__ibooks_respect_image_size", "true"); // Tag so we can't expand it. | |
} | |
} | |
} | |
} | |
BKDOMCleanup._wrapImagesInDivs = function(bookInfo) { | |
var imageShouldBeWrapped = function(image, computedStyle) { | |
var shouldWrap = (computedStyle.getPropertyValue("display") == "block"); | |
if (!shouldWrap) { | |
if (image.namespaceURI == BKDOMCleanup._XHTMLNamespace) { | |
// See https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute | |
if (image.hasAttribute("width") && image.hasAttribute("height")) { | |
var width = image.getAttribute("width"), height = image.getAttribute("height"); | |
shouldWrap = (width.length && width != "100%" && height.length && height != "100%"); | |
} | |
} | |
} | |
return shouldWrap; | |
}; | |
var wrapOneImage = function(image, computedStyle) { | |
// Create a new div that contains the equivalent computed style of the original image. | |
var div = document.createElement("div"); | |
div.style.cssText = computedStyle.cssText; | |
var isRespecting = image.hasAttribute("__ibooks_respect_image_size"); | |
if (!isRespecting) { | |
// if we're not obeying image dimensions, then the image must have been forced to auto | |
// so force that on the div too, so we behave better when rotating | |
div.style.setProperty("width", "auto", ""); | |
div.style.setProperty("height", "auto", ""); | |
} | |
// if the image was inline already, then we want the div to be inline-block | |
if (div.style.display == "inline") { | |
// we tested div.style because if its display is inline, that's because it got it from cssText | |
// We can't test the image directly without getting its computed style, and that's not necessary | |
div.style.setProperty("display", "inline-block", ""); | |
} else { | |
// Change the image to be inline... | |
image.style.setProperty("display", "inline", ""); | |
} | |
// The image shouldn't float since the div wrapper will take care of any necessary floating. | |
image.style.setProperty("float", "none", ""); | |
// We want to force the image to auto-size | |
image.style.setProperty("width", "auto", "!important"); | |
image.style.setProperty("height", "auto", "!important"); | |
// page-break-inside: avoid; on the image can cause problems | |
image.style.setProperty("page-break-inside", "auto", "!important"); | |
// remove it on the div too | |
div.style.setProperty("page-break-inside", "auto", "!important"); | |
// wrap the image in the div. | |
image.parentElement.replaceChild(div, image); | |
div.appendChild(image); | |
}; | |
var scalesWithText = function(str) { | |
var scales = false; | |
if (str.length > 2) | |
{ | |
var units = str.substr(-2); | |
scales = units == 'em' || units == 'ex'; | |
} | |
return scales; | |
} | |
var wrapImages = function(images) { | |
var length = images.length; | |
for (var i = 0; i < length; i++) { | |
var image = images[i]; | |
if (image.nodeType == document.ELEMENT_NODE) { | |
var computedStyle = window.getComputedStyle(image); | |
if (imageShouldBeWrapped(image, computedStyle)) { | |
wrapOneImage(image, computedStyle); | |
} else { | |
var isRespecting = image.hasAttribute("__ibooks_respect_image_size"); | |
if (isRespecting == false) { | |
var clone = StyledClone.cloneElementWithStyle(image); | |
var width = clone.style.width; | |
var height = clone.style.height; | |
if (scalesWithText(width) == false && scalesWithText(height) == false) { | |
if (image.hasAttribute("width") == false) { | |
image.style.width = 'auto'; | |
} | |
if (image.hasAttribute("height") == false) { | |
image.style.height = 'auto'; | |
} | |
} | |
} | |
} | |
} | |
} | |
}; | |
// wrap HTML images. | |
wrapImages(document.images); | |
// wrap SVG images. | |
wrapImages(document.querySelectorAll("svg image")); | |
} | |
BKDOMCleanup._checkParagrahElements = function(fontFamily) { | |
var tags = new Array("p","div","span"); | |
var textContainersToForce = new Array(); | |
var textContainersToAlign = new Array(); | |
var tagContainersToForce = new Array(); | |
var i, j; | |
for (i = 0; i < tags.length; i++) | |
{ | |
var tag = tags[i]; | |
var textContainers = document.querySelectorAll(tag); | |
for (j = 0; j < textContainers.length; j++) | |
{ | |
var textContainer = textContainers[j]; | |
if (textContainer.nodeType == document.ELEMENT_NODE) | |
{ | |
// don't try to override <span>s inside of <ruby><rt> | |
if (tag == "span") | |
{ | |
if (textContainer.webkitMatchesSelector("ruby > rt *")) | |
{ | |
continue; | |
} | |
} | |
var computedTextStyle = window.getComputedStyle(textContainer); | |
var computedTextFontValue = computedTextStyle.getPropertyCSSValue("font-family"); | |
var computedTextFont = computedTextStyle.getPropertyValue("font-family"); | |
while (computedTextFontValue.cssValueType == CSSValue.CSS_VALUE_LIST) | |
{ | |
if (computedTextFontValue.length > 0) | |
{ | |
computedTextFontValue = computedTextFontValue[0] | |
}else | |
{ | |
computedTextFontValue = null; | |
} | |
} | |
if (computedTextFontValue.cssValueType == CSSValue.CSS_PRIMITIVE_VALUE) | |
{ | |
if (computedTextFontValue.primitiveType == CSSPrimitiveValue.CSS_STRING || computedTextFontValue.primitiveType == CSSPrimitiveValue.CSS_IDENT) | |
{ | |
computedTextFont = computedTextFontValue.getStringValue(); | |
} | |
} | |
//add if they have a different font | |
if (computedTextFont != fontFamily) | |
{ | |
tagContainersToForce.push(textContainer); | |
} | |
var computedTextAlign = (computedTextStyle.getPropertyValue("text-align") || "").toLowerCase(); | |
if (computedTextAlign == "start" || computedTextAlign == "left" || computedTextAlign == "-webkit-auto" || computedTextAlign == "justify") | |
{ | |
textContainersToAlign.push(textContainer); | |
} | |
} | |
} | |
// If there are only FORCE_FONTS_THRESHOLD% of the paragraphs that are in a different font, do nothing... | |
if (textContainers.length > 0 && tagContainersToForce.length / textContainers.length < 0.8) | |
{ | |
}else | |
{ | |
textContainersToForce = textContainersToForce.concat(tagContainersToForce); | |
} | |
tagContainersToForce.length = 0; | |
// Don't implement a threshold on alignment because we're only justifying left-aligned content | |
// And this gives us a far less predictable measurement for the number of divs we're asked to modify. | |
} | |
return new Array(textContainersToForce, textContainersToAlign); | |
} | |
BKDOMCleanup._forceFontsOnParagraphs = function (items) { | |
var textContainersToForce = items[0]; | |
var textContainersToAlign = items[1]; | |
var i; | |
for (i = 0; i < textContainersToForce.length; i++) | |
{ | |
var element = textContainersToForce[i]; | |
element.setAttribute("__ibooks_font_override", true); | |
} | |
for (i = 0; i < textContainersToAlign.length; i++) | |
{ | |
var element = textContainersToAlign[i]; | |
element.setAttribute("__ibooks_align_override", true); | |
} | |
} | |
BKDOMCleanup._shouldRemoveDocumentElementWritingMode = function() { | |
var body = document.body; | |
if (!body) | |
return false; | |
var rootWritingMode = window.getComputedStyle(document.documentElement).getPropertyValue("-webkit-writing-mode"); | |
var bodyWritingMode = window.getComputedStyle(body).getPropertyValue("-webkit-writing-mode"); | |
return rootWritingMode != bodyWritingMode; | |
} | |
BKDOMCleanup._removeDocumentWritingMode = function() { | |
var body = document.body; | |
if (!body) | |
return; | |
var writingMode = window.getComputedStyle(body).getPropertyValue("-webkit-writing-mode"); | |
if (!writingMode) | |
return; | |
document.documentElement.style.setProperty("-webkit-writing-mode", writingMode); | |
} | |
BKDOMCleanup._shouldAdjustWritingMode = function() { | |
var body = document.body; | |
if (!body) | |
return false; | |
var child = body.firstElementChild; | |
if (!child) | |
return false; | |
if (child.nextElementSibling) | |
return false; | |
var bodyMode = window.getComputedStyle(body).getPropertyValue("-webkit-writing-mode"); | |
var childMode = window.getComputedStyle(child).getPropertyValue("-webkit-writing-mode"); | |
return bodyMode != childMode; | |
} | |
/*** Hooks ***/ | |
// didFinishLoad is fired by BKEpubWebProcessPlugIn in didFinishLoadForFrame() | |
BKDOMCleanup.didFinishLoad = function(bookInfo) { | |
BKDOMCleanup._disableContentEditableModes(); | |
BKDOMCleanup._disableTextFields(); | |
if (!bookInfo.isFixedLayout) { | |
var adjustWritingMode = BKDOMCleanup._shouldAdjustWritingMode(); | |
var removeDocumentWritingMode = BKDOMCleanup._shouldRemoveDocumentElementWritingMode(); | |
var forceStyleRecalc = false; | |
if (adjustWritingMode) | |
{ | |
forceStyleRecalc = true; | |
BKDOMCleanup._recordHoistedWritingMode(); | |
} | |
if (BKDOMCleanup._markInlineBlockElements()) | |
{ | |
forceStyleRecalc = true; | |
} | |
if (removeDocumentWritingMode) | |
{ | |
BKDOMCleanup._removeDocumentWritingMode(); | |
} | |
BKDOMCleanup._removeBodyInlineStyle(); | |
BKDOMCleanup._setupImagePreview(); | |
BKDOMCleanup._setupTablePreview(); | |
BKDOMCleanup._tagImagesThatAreRespectedInSize(bookInfo); | |
BKDOMCleanup._wrapImagesInDivs(bookInfo); | |
if (bookInfo.fontFamily) | |
{ | |
BKDOMCleanup._forceFontsOnParagraphs(BKDOMCleanup._checkParagrahElements(bookInfo.fontFamily)); | |
} | |
// must occur after recording hoisted writing mode | |
if (BKDOMCleanup._hoistWritingMode(true)) | |
{ | |
forceStyleRecalc = true; | |
} | |
// must come after mark inline | |
if (BKDOMCleanup._tagAsMultiplePages(true)) | |
{ | |
forceStyleRecalc = true; | |
} | |
if (forceStyleRecalc) | |
{ | |
BKDOMCleanup._forceStyleRecalc(); | |
} | |
} | |
}; | |
// didFinishDocumentLoad is fired by BKEpubWebProcessPlugIn in didFinishDocumentLoadForFrame() | |
// Equivalent to DOMContentLoaded | |
BKDOMCleanup.didFinishDocumentLoad = function(bookInfo) { | |
BKDOMCleanup._fixupBodyLang(bookInfo.language); | |
BKDOMCleanup._modifyAutoplayNodes(); | |
// soundtrack is handled in readAloud.js | |
// footnotes are handled elsewhere | |
} | |
BKDOMCleanup.unhoistWritingModeIfNeeded = function(pageWidth, gap) | |
{ | |
var width = document.body.scrollWidth; | |
var pageCount = Math.ceil(width / (pageWidth + gap)); | |
if (pageCount <= 1) | |
{ | |
var forceStyleRecalc = false; | |
if (BKDOMCleanup._hoistWritingMode(false)) | |
{ | |
forceStyleRecalc = true; | |
} | |
if (BKDOMCleanup._tagAsMultiplePages(false)) | |
{ | |
forceStyleRecalc = true; | |
} | |
if (forceStyleRecalc) | |
{ | |
BKDOMCleanup._forceStyleRecalc(); | |
} | |
} | |
BKDOMCleanup._recordWritingMode(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
tofi86 commentedJul 27, 2014
can you name the file with ".js" at the end for JavaScript Syntax Highlighting?!