From 35b65060576fc9397d1b9e419ec1ea2734109683 Mon Sep 17 00:00:00 2001 From: bgrins Date: Sun, 15 May 2011 21:59:39 -0500 Subject: [PATCH] adding the ugliness needed for loading images --- server/scripts/h2.js | 175 +++++++++++++++----- server/scripts/jquery-1.6.1.min.js | 248 +++++++++++++++++++++++++++++ server/tests/zach/items.html | 2 +- 3 files changed, 388 insertions(+), 37 deletions(-) diff --git a/server/scripts/h2.js b/server/scripts/h2.js index 7bd5210..9462303 100644 --- a/server/scripts/h2.js +++ b/server/scripts/h2.js @@ -119,15 +119,37 @@ function getLetterRect(el, offset) { function el(dom, onready) { this.dom = dom; - this.initializeDOM(); + dom._element = this; - var textNodes = this.textNodes = []; - var childNodes = dom.childNodes; - for (var j = 0, l = childNodes.length; j < l; j++) { - if (childNodes[j].nodeType == 3) { - textNodes.push(childNodes[j]); + this.tagName = dom.tagName.toLowerCase(); + this.isBody = this.tagName == "body"; + + + if (this.isBody) { + var body = this.body = this; + this.pendingResources = 0; + this.checkImages = function() { + if (this.childrenInitialized && this.pendingResources <= 0) { + onready(this); + } + }; + this.loadImage = function(src, useBroken, element) { + this.pendingResources++; + retrieveImage(src, function(img) { + element.loadedImage = img; + log("Set elements loaded image", img); + body.pendingResources--; + body.checkImages(); + }, dom.ownerDocument, useBroken); } } + else { + this.parent = dom.parentNode._element; + this.body = this.parent.body; + } + + this.initializeDOM(); + log("inited el", this); @@ -136,22 +158,24 @@ function el(dom, onready) { return new el(this); }); + this.childrenInitialized = true; - if (onready) { - onready(this); + + if (this.isBody) { + this.checkImages(); } + } el.prototype.initializeDOM = function() { + var dom = this.dom; var $dom = $(this.dom); var css = this.css = { }; - this.clientRects = dom.getClientRects(); - - this.tagName = dom.tagName.toLowerCase(); - this.isBody = this.tagName == "body"; + this.src = this.tagName == 'img' ? $dom.attr("src") : false; + this.shouldRender = (dom.offsetWidth > 0 && dom.offsetHeight > 0); var computedStyleNormal = computedStyle(dom, styleAttributes); for (var i in computedStyleNormal) { @@ -162,11 +186,15 @@ el.prototype.initializeDOM = function() { css[i] = parseInt(computedStylePx[i]) || 0; } - if (css.backgroundColor == "rgba(0, 0, 0, 0)" || css.backgroundColor == "transparent") { css.backgroundColor = false; } + css.font = $.trim( + css.fontStyle + " " + css.fontWeight + " " + + css.fontSize + "px " + css.fontFamily + ); + if (css.zIndex == "auto") { css.zIndex = -1; } @@ -174,19 +202,6 @@ el.prototype.initializeDOM = function() { css.zIndex = parseInt(css.zIndex) || 0; } - css.font = $.trim( - css.fontStyle + " " + css.fontWeight + " " + - css.fontSize + "px " + css.fontFamily - ); - - //css.offset = $dom.offset(); - //css.height = $dom.height(); - //css.width = $dom.width(); - //css.outerHeightMargins = $dom.outerHeight(true); - //css.outerWidthMargins = $dom.outerWidth(true); - //css.scrollWidth = dom.scrollWidth; - //css.scrollHeight = dom.scrollHeight; - if (this.isBody) { var doc = dom.ownerDocument || document; @@ -195,12 +210,30 @@ el.prototype.initializeDOM = function() { }; } + // Collect all of the text nodes for future reference + var textNodes = this.textNodes = []; + var childNodes = dom.childNodes; + for (var j = 0, l = childNodes.length; j < l; j++) { + if (childNodes[j].nodeType == 3) { + textNodes.push(childNodes[j]); + } + } + + // Fetch any images that are necessary for rendering + if (this.shouldRender) { + if (this.tagName == "img") { + this.body.loadImage(this.src, true, this); + } + else if (css.backgroundImage != "none") { + this.body.loadImage(this.css.backgroundImage, false, this); + } + } + }; el.prototype.render = function(ctx) { - - if (this.css.display == 'none') { + if (!this.shouldRender) { return; } @@ -214,9 +247,7 @@ el.prototype.render = function(ctx) { }; -/* - Render borders and background colors / images -*/ +// Render borders and background colors / images el.prototype.renderBox = function(ctx) { var css = this.css; @@ -243,12 +274,12 @@ el.prototype.renderBox = function(ctx) { el.prototype.renderText = function(ctx) { - ctx.font = this.css.font; - ctx.fillStyle = this.css.color; + var css = this.css; + ctx.font = css.font; + ctx.fillStyle = css.color; ctx.textBaseline = "bottom"; var nodes = this.textNodes; - for (var i = 0 ; i < nodes.length; i++) { var text = nodes[i].data; for (var f = 0; f < text.length; f++) { @@ -258,9 +289,7 @@ el.prototype.renderText = function(ctx) { } var rect = getLetterRect(nodes[i], f); - //log(f, text[f], text.length, rect, this.tagName); - ctx.fillText(text[f], rect.left, rect.bottom); } } @@ -370,4 +399,78 @@ if (window.parent != window) { }); } + + + + + +// retrieveImage: a method to interface with image loading, errors, and proxy +function retrieveImageFromCache(src) { + assert(retrieveImage.cache.hasOwnProperty(src), "Error: image has not been loaded into cache"); + return retrieveImage.cache[src]; +} + +function retrieveImage(src, cb, ownerDocument, useBroken) { + + if (!$.isFunction(cb)) { + cb = function() { }; + } + + if (retrieveImage.cache[src]) { + log("Cache hit", src, retrieveImage.cache[src]); + return cb(retrieveImageFromCache(src)); + } + + var loadImageDirectly = true; + + if (src.indexOf("data:") == -1) { + var url = new RegExp(/url\((.*)\)/); + //src = src.replace(/['"]/g,''); trim quotes? + var matched = src.match(url); + if (matched && matched[1]) { + src = matched[1]; + } + + // Convert a relative path into absolute. + var original = new URI(src); + var authority = original.getAuthority(); + + if (!authority) { + var root = new URI((ownerDocument || document).location.href); + src = original.resolve(root).toString(); + } + else if (authority != document.location.host) { + log("Going to need to proxy"); + + } + } + + if (loadImageDirectly) { + var img = new Image(); + img.onload = sendSuccess; + img.onerror = sendError; + img.src = src; + } + else { + + } + + function sendError() { + var img = new Image(); + img.onload = sendSuccess; + img.src = useBroken ? retrieveImage.brokenImage : retrieveImage.transparentImage; + } + + function sendSuccess() { + retrieveImage.cache[src] = this; + cb(this); + } +} + +retrieveImage.cache = { }; +retrieveImage.transparentImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAADLUlEQVR4Ae3QQREAAAiAMPqXVjv4HUeCNXWLAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwENgAfmTAf/IVJfgAAAAAElFTkSuQmCC"; + +retrieveImage.brokenImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAVBAMAAABWJ8jiAAAAIVBMVEUAAAAA//8A/wDAwMD/AP//AACAgIAAAID///8AgAAAAP87p9+eAAAAb0lEQVQImWPogIEGBmQmAwhkgJkcIKE0CNMYCDIyIEzLycYZbRDmpAnGDAwQ5swJxsYNEKYBM5xpbLxqAcQEU2PjhVCmSbDxqgoo09UYqB/CdAnuaIYwDYBOgjKNgSyoYRAngh0JZDBALMbuCzgTAD+sVWJQUviMAAAAAElFTkSuQmCC"; + + })(); \ No newline at end of file diff --git a/server/scripts/jquery-1.6.1.min.js b/server/scripts/jquery-1.6.1.min.js index eb6a596..498c071 100644 --- a/server/scripts/jquery-1.6.1.min.js +++ b/server/scripts/jquery-1.6.1.min.js @@ -1,3 +1,251 @@ + +// Globals we introduce. +var URI; +var URIQuery; + +// Introduce a new scope to define some private helper functions. +(function () { + + //// HELPER FUNCTIONS ///// + + // RFC3986 §5.2.3 (Merge Paths) + function merge(base, rel_path) { + var dirname = /^(.*)\//; + if (base.authority && !base.path) { + return "/" + rel_path; + } + else { + return base.getPath().match(dirname)[0] + rel_path; + } + } + + // Match two path segments, where the second is ".." and the first must + // not be "..". + var DoubleDot = /\/((?!\.\.\/)[^\/]*)\/\.\.\//; + + function remove_dot_segments(path) { + if (!path) { + return ""; + } + // Remove any single dots + var newpath = path.replace(/\/\.\//g, '/'); + // Remove any trailing single dots. + newpath = newpath.replace(/\/\.$/, '/'); + // Remove any double dots and the path previous. NB: We can't use + // the "g", modifier because we are changing the string that we're + // matching over. + while (newpath.match(DoubleDot)) { + newpath = newpath.replace(DoubleDot, '/'); + } + // Remove any trailing double dots. + newpath = newpath.replace(/\/([^\/]*)\/\.\.$/, '/'); + // If there are any remaining double dot bits, then they're wrong + // and must be nuked. Again, we can't use the g modifier. + while (newpath.match(/\/\.\.\//)) { + newpath = newpath.replace(/\/\.\.\//, '/'); + } + return newpath; + } + + // give me an ordered list of keys of this object + function hashkeys(obj) { + var list = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + list.push(key); + } + } + return list.sort(); + } + + // TODO: Make these do something + function uriEscape(source) { + return source; + } + + function uriUnescape(source) { + return source; + } + + + //// URI CLASS ///// + + // Constructor for the URI object. Parse a string into its components. + // note that this 'exports' 'URI' to the 'global namespace' + URI = function (str) { + if (!str) { + str = ""; + } + // Based on the regex in RFC2396 Appendix B. + var parser = /^(?:([^:\/?\#]+):)?(?:\/\/([^\/?\#]*))?([^?\#]*)(?:\?([^\#]*))?(?:\#(.*))?/; + var result = str.match(parser); + + // Keep the results in private variables. + var scheme = result[1] || null; + var authority = result[2] || null; + var path = result[3] || null; + var query = result[4] || null; + var fragment = result[5] || null; + + // Set up accessors. + this.getScheme = function () { + return scheme; + }; + this.setScheme = function (newScheme) { + scheme = newScheme; + }; + this.getAuthority = function () { + return authority; + }; + this.setAuthority = function (newAuthority) { + authority = newAuthority; + }; + this.getPath = function () { + return path; + }; + this.setPath = function (newPath) { + path = newPath; + }; + this.getQuery = function () { + return query; + }; + this.setQuery = function (newQuery) { + query = newQuery; + }; + this.getFragment = function () { + return fragment; + }; + this.setFragment = function (newFragment) { + fragment = newFragment; + }; + }; + + // Restore the URI to it's stringy glory. + URI.prototype.toString = function () { + var str = ""; + if (this.getScheme()) { + str += this.getScheme() + ":"; + } + if (this.getAuthority()) { + str += "//" + this.getAuthority(); + } + if (this.getPath()) { + str += this.getPath(); + } + if (this.getQuery()) { + str += "?" + this.getQuery(); + } + if (this.getFragment()) { + str += "#" + this.getFragment(); + } + return str; + }; + + // RFC3986 §5.2.2. Transform References; + URI.prototype.resolve = function (base) { + var target = new URI(); + if (this.getScheme()) { + target.setScheme(this.getScheme()); + target.setAuthority(this.getAuthority()); + target.setPath(remove_dot_segments(this.getPath())); + target.setQuery(this.getQuery()); + } + else { + if (this.getAuthority()) { + target.setAuthority(this.getAuthority()); + target.setPath(remove_dot_segments(this.getPath())); + target.setQuery(this.getQuery()); + } + else { + // XXX Original spec says "if defined and empty"…; + if (!this.getPath()) { + target.setPath(base.getPath()); + if (this.getQuery()) { + target.setQuery(this.getQuery()); + } + else { + target.setQuery(base.getQuery()); + } + } + else { + if (this.getPath().charAt(0) === '/') { + target.setPath(remove_dot_segments(this.getPath())); + } else { + target.setPath(merge(base, this.getPath())); + target.setPath(remove_dot_segments(target.getPath())); + } + target.setQuery(this.getQuery()); + } + target.setAuthority(base.getAuthority()); + } + target.setScheme(base.getScheme()); + } + + target.setFragment(this.getFragment()); + + return target; + }; + + URI.prototype.parseQuery = function () { + return URIQuery.fromString(this.getQuery(), this.querySeparator); + }; + + //// URIQuery CLASS ///// + + URIQuery = function () { + this.params = {}; + this.separator = "&"; + }; + + URIQuery.fromString = function (sourceString, separator) { + var result = new URIQuery(); + if (separator) { + result.separator = separator; + } + result.addStringParams(sourceString); + return result; + }; + + + // From http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 + // (application/x-www-form-urlencoded). + // + // NB: The user can get this.params and modify it directly. + URIQuery.prototype.addStringParams = function (sourceString) { + var kvp = sourceString.split(this.separator); + var list, key, value; + for (var i = 0; i < kvp.length; i++) { + // var [key,value] = kvp.split("=", 2) only works on >= JS 1.7 + list = kvp[i].split("=", 2); + key = uriUnescape(list[0].replace(/\+/g, " ")); + value = uriUnescape(list[1].replace(/\+/g, " ")); + if (!this.params.hasOwnProperty(key)) { + this.params[key] = []; + } + this.params[key].push(value); + } + }; + + URIQuery.prototype.getParam = function (key) { + if (this.params.hasOwnProperty(key)) { + return this.params[key][0]; + } + return null; + }; + + URIQuery.prototype.toString = function () { + var kvp = []; + var keys = hashkeys(this.params); + var ik, ip; + for (ik = 0; ik < keys.length; ik++) { + for (ip = 0; ip < this.params[keys[ik]].length; ip++) { + kvp.push(keys[ik].replace(/ /g, "+") + "=" + this.params[keys[ik]][ip].replace(/ /g, "+")); + } + } + return kvp.join(this.separator); + }; + +})(); /*! * jQuery JavaScript Library v1.6.1 * http://jquery.com/ diff --git a/server/tests/zach/items.html b/server/tests/zach/items.html index 0cbb1b8..d760a45 100644 --- a/server/tests/zach/items.html +++ b/server/tests/zach/items.html @@ -19,7 +19,7 @@ #header { background:transparent url('header1.5.4.jpg') top left no-repeat; } - body {margin:50px; border:4px solid blue; padding:10px; background:green;} + body {margin:50px; border:4px solid blue; padding:10px;} a { background-color:#ddd; } #footer-text { /*overflow:hidden;*/ } .wrap { word-wrap: break-word; width:100px; }