|
/** |
|
* |
|
* @param $epubHtml: The html that is to have font attributes added. |
|
* @param fontSize: The font size that is to be added to the element at all locations. |
|
* @param fontObj: The font Object containing at minimum the URL, and fontFamilyName (In fields url and fontFamily) respectively. Pass in null's on the object's fields to signal no font. |
|
* @param callback: function invoked when "done", which means that if there are asynchronous operations such as font-face loading via injected stylesheets, then the UpdateHtmlFontAttributes() function returns immediately but the caller should wait for the callback function call if fully-loaded font-face *stylesheets* are required on the caller's side (note that the caller's side may still need to detect *actual font loading*, via the FontLoader API or some sort of ResizeSensor to indicate that the updated font-family has been used to render the document). |
|
*/ |
|
|
|
Helpers.UpdateHtmlFontAttributes = function ($epubHtml, fontSize, fontObj, callback) { |
|
|
|
|
|
var FONT_FAMILY_ID = "readium_font_family_link"; |
|
|
|
var docHead = $("head", $epubHtml); |
|
var link = $("#" + FONT_FAMILY_ID, docHead); |
|
|
|
const NOTHING = 0, ADD = 1, REMOVE = 2; //Types for css font family. |
|
var changeFontFamily = NOTHING; |
|
|
|
var fontLoadCallback = function() { |
|
|
|
var perf = false; |
|
|
|
// TODO: very slow on Firefox! |
|
// See https://github.com/readium/readium-shared-js/issues/274 |
|
if (perf) var time1 = window.performance.now(); |
|
|
|
|
|
|
|
if (changeFontFamily != NOTHING) { |
|
var fontFamilyStyle = $("style#readium-fontFamily", docHead); |
|
|
|
if (fontFamilyStyle && fontFamilyStyle[0]) { |
|
// REMOVE, or ADD (because we remove before re-adding from scratch) |
|
docHead[0].removeChild(fontFamilyStyle[0]); |
|
} |
|
if (changeFontFamily == ADD) { |
|
var style = $epubHtml[0].ownerDocument.createElement('style'); |
|
style.setAttribute("id", "readium-fontFamily"); |
|
style.appendChild($epubHtml[0].ownerDocument.createTextNode('html * { font-family: "'+fontObj.fontFamily+'" !important; }')); // this technique works for text-align too (e.g. text-align: justify !important;) |
|
|
|
docHead[0].appendChild(style); |
|
|
|
//fontFamilyStyle = $(style); |
|
} |
|
} |
|
|
|
// The code below does not work because jQuery $element.css() on html.body somehow "resets" the font: CSS directive by removing it entirely (font-family: works with !important, but unfortunately further deep inside the DOM there may be CSS applied with the font: directive, which somehow seems to take precedence! ... as shown in Chrome's developer tools) |
|
// ...thus why we use the above routine instead, to insert a new head>style element |
|
// // var doc = $epubHtml[0].ownerDocument; |
|
// // var body = doc.body; |
|
// var $body = $("body", $epubHtml); |
|
// // $body.css({ |
|
// // "font-size" : fontSize + "%", |
|
// // "font-family" : "" |
|
// // }); |
|
// $body.css("font-family", ""); |
|
// if (changeFontFamily == ADD) { |
|
|
|
// var existing = $body.attr("style"); |
|
// $body[0].setAttribute("style", |
|
// existing + " ; font-family: '" + fontObj.fontFamily + "' !important ;" + " ; font: regular 100% '" + fontObj.fontFamily + "' !important ;"); |
|
// } |
|
|
|
|
|
var factor = fontSize / 100; |
|
var win = $epubHtml[0].ownerDocument.defaultView; |
|
if (!win) { |
|
console.log("NIL $epubHtml[0].ownerDocument.defaultView"); |
|
return; |
|
} |
|
|
|
// TODO: is this a complete list? Is there a better way to do this? |
|
//https://github.com/readium/readium-shared-js/issues/336 |
|
// Note that font-family is handled differently, using an injected stylesheet with a catch-all selector that pushes an "!important" CSS value in the document's cascade. |
|
var $textblocks = $('p, div, span, h1, h2, h3, h4, h5, h6, li, blockquote, td, pre, dt, dd, code, a', $epubHtml); // excludes section, body etc. |
|
|
|
// need to do two passes because it is possible to have nested text blocks. |
|
// If you change the font size of the parent this will then create an inaccurate |
|
// font size for any children. |
|
for (var i = 0; i < $textblocks.length; i++) { |
|
|
|
var ele = $textblocks[i]; |
|
|
|
var fontSizeAttr = ele.getAttribute('data-original-font-size'); |
|
if (fontSizeAttr) { |
|
// early exit, original values already set. |
|
break; |
|
} |
|
|
|
var style = win.getComputedStyle(ele); |
|
|
|
var originalFontSize = parseInt(style.fontSize); |
|
ele.setAttribute('data-original-font-size', originalFontSize); |
|
|
|
var originalLineHeight = parseInt(style.lineHeight); |
|
// getComputedStyle will not calculate the line-height if the value is 'normal'. In this case parseInt will return NaN |
|
if (originalLineHeight) { |
|
ele.setAttribute('data-original-line-height', originalLineHeight); |
|
} |
|
|
|
// var fontFamilyAttr = ele.getAttribute('data-original-font-family'); |
|
// if (!fontFamilyAttr) { |
|
// var originalFontFamily = style.fontFamily; |
|
// if (originalFontFamily) { |
|
// ele.setAttribute('data-original-font-family', originalFontFamily); |
|
// } |
|
// } |
|
} |
|
|
|
for (var i = 0; i < $textblocks.length; i++) { |
|
var ele = $textblocks[i]; |
|
|
|
// TODO: group the 3x potential $(ele).css() calls below to avoid multiple jQuery style mutations |
|
|
|
var fontSizeAttr = ele.getAttribute('data-original-font-size'); |
|
var originalFontSize = Number(fontSizeAttr); |
|
$(ele).css("font-size", (originalFontSize * factor) + 'px'); |
|
|
|
var lineHeightAttr = ele.getAttribute('data-original-line-height'); |
|
var originalLineHeight = lineHeightAttr ? Number(lineHeightAttr) : 0; |
|
if (originalLineHeight) { |
|
$(ele).css("line-height", (originalLineHeight * factor) + 'px'); |
|
} |
|
|
|
// var fontFamilyAttr = ele.getAttribute('data-original-font-family'); |
|
// switch(changeFontFamily){ |
|
// case NOTHING: |
|
// break; |
|
// case ADD: |
|
// $(ele).css("font-family", fontObj.fontFamily); |
|
// break; |
|
// case REMOVE: |
|
// $(ele).css("font-family", fontFamilyAttr); |
|
// break; |
|
// } |
|
} |
|
|
|
$epubHtml.css("font-size", fontSize + "%"); |
|
|
|
|
|
|
|
if (perf) { |
|
var time2 = window.performance.now(); |
|
|
|
// Firefox: 80+ |
|
// Chrome: 4-10 |
|
// Edge: 15-34 |
|
// IE: 10-15 |
|
// https://readium.firebase.com/?epub=..%2Fepub_content%2Faccessible_epub_3&goto=%7B%22idref%22%3A%22id-id2635343%22%2C%22elementCfi%22%3A%22%2F4%2F2%5Bbuilding_a_better_epub%5D%2F10%2F44%2F6%2C%2F1%3A334%2C%2F1%3A335%22%7D |
|
|
|
var diff = time2-time1; |
|
console.log(diff); |
|
|
|
// setTimeout(function(){ |
|
// alert(diff); |
|
// }, 2000); |
|
} |
|
|
|
callback(); |
|
}; |
|
var fontLoadCallback_ = _.once(fontLoadCallback); |
|
|
|
if(fontObj.fontFamily && fontObj.url){ |
|
var dataFontFamily = link.length ? link.attr("data-fontfamily") : undefined; |
|
|
|
if(!link.length){ |
|
changeFontFamily = ADD; |
|
|
|
setTimeout(function(){ |
|
|
|
link = $("<link/>", { |
|
"id" : FONT_FAMILY_ID, |
|
"data-fontfamily" : fontObj.fontFamily, |
|
"rel" : "stylesheet", |
|
"type" : "text/css" |
|
}); |
|
docHead.append(link); |
|
|
|
link.attr({ |
|
"href" : fontObj.url |
|
}); |
|
}, 0); |
|
} |
|
else if(dataFontFamily != fontObj.fontFamily){ |
|
changeFontFamily = ADD; |
|
|
|
link.attr({ |
|
"data-fontfamily" : fontObj.fontFamily, |
|
"href" : fontObj.url |
|
}); |
|
} else { |
|
changeFontFamily = NOTHING; |
|
} |
|
} |
|
else{ |
|
changeFontFamily = REMOVE; |
|
if(link.length) link.remove(); |
|
} |
|
|
|
if (changeFontFamily == ADD) { |
|
// just in case the link@onload does not trigger, we set a timeout |
|
setTimeout(function(){ |
|
fontLoadCallback_(); |
|
}, 100); |
|
} |
|
else { // REMOVE, NOTHING |
|
fontLoadCallback_(); |
|
} |
|
}; |
In the reader's settings, the
fontSelectionindex is zero-based and indicates the chosen item from an array of choices (zero means "default publisher font", other integers mean "custom reading system font"):readium-shared-js/js/models/viewer_settings.js
Lines 44 to 50 in 9bb1df0
The reader's array of custom fonts is provided at construction time:
readium-shared-js/js/views/reader_view.js
Line 77 in 9bb1df0
Here is a concrete example of such array, as configured in ReadiumJS (cloud / web reader + Chrome app / extension):
https://github.com/readium/readium-js-viewer/blob/2a41a144ad75cb33058c0684a817aa2fe332f907/src/fonts/fonts.js#L1-L42
Note that although the
urlfield of each JSON font object can be a relative path, in fact thegetFontFaces()function at the bottom of the Javascript source file takes aURLprefixparameter to complete the final array of fonts with absolute URLs. In other words, a native app must ensure that each font's HTTP URL resolves to the correct payload, which is typically a CSS file that declares the font faces, for example OpenDyslexic:https://github.com/readium/readium-js-viewer/tree/2a41a144ad75cb33058c0684a817aa2fe332f907/src/fonts/OpenDyslexic
The
fontFamilystring of characters in the JSON font object is meant to be used as-is in a CSSfont-familystatement, and will be applied automatically by thereadium-shared-jslayout / rendering engine when a thefontSelectionsetting changes:readium-shared-js/js/views/reader_view.js
Lines 582 to 646 in 9bb1df0
The actual DOM/CSS logic is implemented in:
readium-shared-js/js/helpers.js
Lines 248 to 457 in 9bb1df0