Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FAQ: custom external font faces #429

Open
danielweck opened this issue Nov 30, 2017 · 0 comments
Open

FAQ: custom external font faces #429

danielweck opened this issue Nov 30, 2017 · 0 comments

Comments

@danielweck
Copy link
Member

danielweck commented Nov 30, 2017

In the reader's settings, the fontSelection index 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"):

/**
*
* @property fontSelection
* @type number
*/
this.fontSelection = 0;

The reader's array of custom fonts is provided at construction time:

this.fonts = options.fonts;

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 url field of each JSON font object can be a relative path, in fact the getFontFaces() function at the bottom of the Javascript source file takes a URLprefix parameter 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 fontFamily string of characters in the JSON font object is meant to be used as-is in a CSS font-family statement, and will be applied automatically by the readium-shared-js layout / rendering engine when a the fontSelection setting changes:

/**
* Updates reader view based on the settings specified in settingsData object
*
* @param {Globals.Views.ReaderView.SettingsData} settingsData Settings data
* @fires Globals.Events.SETTINGS_APPLIED
*/
this.updateSettings = function (settingsData) {
//console.debug("UpdateSettings: " + JSON.stringify(settingsData));
_viewerSettings.update(settingsData);
if (_mediaOverlayPlayer) {
_mediaOverlayPlayer.setAutomaticNextSmil(_viewerSettings.mediaOverlaysAutomaticPageTurn ? true : false);
}
if (_currentView && !settingsData.doNotUpdateView) {
var bookMark = _currentView.bookmarkCurrentPage();
if (bookMark && bookMark.idref) {
var wasPlaying = false;
if (_currentView.isReflowable && _currentView.isReflowable()) {
wasPlaying = self.isPlayingMediaOverlay();
if (wasPlaying) {
self.pauseMediaOverlay();
}
}
var spineItem = _spine.getItemById(bookMark.idref);
initViewForItem(spineItem, function (isViewChanged) {
if (!isViewChanged) {
var docWillChange = false;
_currentView.setViewSettings(_viewerSettings, docWillChange);
}
self.once(ReadiumSDK.Events.PAGINATION_CHANGED, function (pageChangeData)
{
var cfi = new BookmarkData(bookMark.idref, bookMark.contentCFI);
self.debugBookmarkData(cfi);
});
self.openSpineItemElementCfi(bookMark.idref, bookMark.contentCFI, self);
if (wasPlaying) {
self.playMediaOverlay();
// setTimeout(function()
// {
// }, 60);
}
Globals.logEvent("SETTINGS_APPLIED 1 (view update)", "EMIT", "reader_view.js");
self.emit(Globals.Events.SETTINGS_APPLIED);
});
return;
}
}
Globals.logEvent("SETTINGS_APPLIED 2 (no view update)", "EMIT", "reader_view.js");
self.emit(Globals.Events.SETTINGS_APPLIED);
};

The actual DOM/CSS logic is implemented in:

/**
*
* @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_();
}
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants