diff options
Diffstat (limited to 'lib/epub.js/src/resources.js')
-rw-r--r-- | lib/epub.js/src/resources.js | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/lib/epub.js/src/resources.js b/lib/epub.js/src/resources.js new file mode 100644 index 0000000..b5d77f8 --- /dev/null +++ b/lib/epub.js/src/resources.js @@ -0,0 +1,320 @@ +import {substitute} from "./utils/replacements"; +import {createBase64Url, createBlobUrl, blob2base64} from "./utils/core"; +import Url from "./utils/url"; +import mime from "./utils/mime"; +import Path from "./utils/path"; +import path from "path-webpack"; + +/** + * Handle Package Resources + * @class + * @param {Manifest} manifest + * @param {object} [options] + * @param {string} [options.replacements="base64"] + * @param {Archive} [options.archive] + * @param {method} [options.resolver] + */ +class Resources { + constructor(manifest, options) { + this.settings = { + replacements: (options && options.replacements) || "base64", + archive: (options && options.archive), + resolver: (options && options.resolver), + request: (options && options.request) + }; + + this.process(manifest); + } + + /** + * Process resources + * @param {Manifest} manifest + */ + process(manifest){ + this.manifest = manifest; + this.resources = Object.keys(manifest). + map(function (key){ + return manifest[key]; + }); + + this.replacementUrls = []; + + this.html = []; + this.assets = []; + this.css = []; + + this.urls = []; + this.cssUrls = []; + + this.split(); + this.splitUrls(); + } + + /** + * Split resources by type + * @private + */ + split(){ + + // HTML + this.html = this.resources. + filter(function (item){ + if (item.type === "application/xhtml+xml" || + item.type === "text/html") { + return true; + } + }); + + // Exclude HTML + this.assets = this.resources. + filter(function (item){ + if (item.type !== "application/xhtml+xml" && + item.type !== "text/html") { + return true; + } + }); + + // Only CSS + this.css = this.resources. + filter(function (item){ + if (item.type === "text/css") { + return true; + } + }); + } + + /** + * Convert split resources into Urls + * @private + */ + splitUrls(){ + + // All Assets Urls + this.urls = this.assets. + map(function(item) { + return item.href; + }.bind(this)); + + // Css Urls + this.cssUrls = this.css.map(function(item) { + return item.href; + }); + + } + + /** + * Create a url to a resource + * @param {string} url + * @return {Promise<string>} Promise resolves with url string + */ + createUrl (url) { + var parsedUrl = new Url(url); + var mimeType = mime.lookup(parsedUrl.filename); + + if (this.settings.archive) { + return this.settings.archive.createUrl(url, {"base64": (this.settings.replacements === "base64")}); + } else { + if (this.settings.replacements === "base64") { + return this.settings.request(url, 'blob') + .then((blob) => { + return blob2base64(blob); + }) + .then((blob) => { + return createBase64Url(blob, mimeType); + }); + } else { + return this.settings.request(url, 'blob').then((blob) => { + return createBlobUrl(blob, mimeType); + }) + } + } + } + + /** + * Create blob urls for all the assets + * @return {Promise} returns replacement urls + */ + replacements(){ + if (this.settings.replacements === "none") { + return new Promise(function(resolve) { + resolve(this.urls); + }.bind(this)); + } + + var replacements = this.urls.map( (url) => { + var absolute = this.settings.resolver(url); + + return this.createUrl(absolute). + catch((err) => { + console.error(err); + return null; + }); + }); + + return Promise.all(replacements) + .then( (replacementUrls) => { + this.replacementUrls = replacementUrls.filter((url) => { + return (typeof(url) === "string"); + }); + return replacementUrls; + }); + } + + /** + * Replace URLs in CSS resources + * @private + * @param {Archive} [archive] + * @param {method} [resolver] + * @return {Promise} + */ + replaceCss(archive, resolver){ + var replaced = []; + archive = archive || this.settings.archive; + resolver = resolver || this.settings.resolver; + this.cssUrls.forEach(function(href) { + var replacement = this.createCssFile(href, archive, resolver) + .then(function (replacementUrl) { + // switch the url in the replacementUrls + var indexInUrls = this.urls.indexOf(href); + if (indexInUrls > -1) { + this.replacementUrls[indexInUrls] = replacementUrl; + } + }.bind(this)) + + + replaced.push(replacement); + }.bind(this)); + return Promise.all(replaced); + } + + /** + * Create a new CSS file with the replaced URLs + * @private + * @param {string} href the original css file + * @return {Promise} returns a BlobUrl to the new CSS file or a data url + */ + createCssFile(href){ + var newUrl; + + if (path.isAbsolute(href)) { + return new Promise(function(resolve){ + resolve(); + }); + } + + var absolute = this.settings.resolver(href); + + // Get the text of the css file from the archive + var textResponse; + + if (this.settings.archive) { + textResponse = this.settings.archive.getText(absolute); + } else { + textResponse = this.settings.request(absolute, "text"); + } + + // Get asset links relative to css file + var relUrls = this.urls.map( (assetHref) => { + var resolved = this.settings.resolver(assetHref); + var relative = new Path(absolute).relative(resolved); + + return relative; + }); + + if (!textResponse) { + // file not found, don't replace + return new Promise(function(resolve){ + resolve(); + }); + } + + return textResponse.then( (text) => { + // Replacements in the css text + text = substitute(text, relUrls, this.replacementUrls); + + // Get the new url + if (this.settings.replacements === "base64") { + newUrl = createBase64Url(text, "text/css"); + } else { + newUrl = createBlobUrl(text, "text/css"); + } + + return newUrl; + }, (err) => { + // handle response errors + return new Promise(function(resolve){ + resolve(); + }); + }); + + } + + /** + * Resolve all resources URLs relative to an absolute URL + * @param {string} absolute to be resolved to + * @param {resolver} [resolver] + * @return {string[]} array with relative Urls + */ + relativeTo(absolute, resolver){ + resolver = resolver || this.settings.resolver; + + // Get Urls relative to current sections + return this.urls. + map(function(href) { + var resolved = resolver(href); + var relative = new Path(absolute).relative(resolved); + return relative; + }.bind(this)); + } + + /** + * Get a URL for a resource + * @param {string} path + * @return {string} url + */ + get(path) { + var indexInUrls = this.urls.indexOf(path); + if (indexInUrls === -1) { + return; + } + if (this.replacementUrls.length) { + return new Promise(function(resolve, reject) { + resolve(this.replacementUrls[indexInUrls]); + }.bind(this)); + } else { + return this.createUrl(path); + } + } + + /** + * Substitute urls in content, with replacements, + * relative to a url if provided + * @param {string} content + * @param {string} [url] url to resolve to + * @return {string} content with urls substituted + */ + substitute(content, url) { + var relUrls; + if (url) { + relUrls = this.relativeTo(url); + } else { + relUrls = this.urls; + } + return substitute(content, relUrls, this.replacementUrls); + } + + destroy() { + this.settings = undefined; + this.manifest = undefined; + this.resources = undefined; + this.replacementUrls = undefined; + this.html = undefined; + this.assets = undefined; + this.css = undefined; + + this.urls = undefined; + this.cssUrls = undefined; + } +} + +export default Resources; |