From 50cae6994585a3a923cf57211a56657fdfb364e5 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Wed, 20 Feb 2013 18:37:17 -0500 Subject: [PATCH] define pdfviewer.js, and use from static_pdfbook.html --- common/static/css/vendor/pdfjs/myviewer.css | 99 +++---- common/static/js/pdfviewer.js | 276 ++++++++++++++++++++ lms/templates/static_pdfbook.html | 165 ++++++------ 3 files changed, 383 insertions(+), 157 deletions(-) create mode 100644 common/static/js/pdfviewer.js diff --git a/common/static/css/vendor/pdfjs/myviewer.css b/common/static/css/vendor/pdfjs/myviewer.css index 0630cae0d3..3bb3005129 100644 --- a/common/static/css/vendor/pdfjs/myviewer.css +++ b/common/static/css/vendor/pdfjs/myviewer.css @@ -95,26 +95,17 @@ select { */ /* outer/inner center provides horizontal center */ -html[dir='ltr'] .outerCenter { +.outerCenter { float: right; position: relative; right: 50%; } -html[dir='rtl'] .outerCenter { - float: left; - position: relative; - left: 50%; -} -html[dir='ltr'] .innerCenter { + +.innerCenter { float: right; position: relative; right: -50%; } -html[dir='rtl'] .innerCenter { - float: left; - position: relative; - left: -50%; -} #outerContainer { width: 100%; @@ -228,9 +219,9 @@ html[dir='rtl'] #sidebarContent { } .toolbar { -/* position: absolute; + position: absolute; left: 0; - right: 0; */ + right: 0; height: 32px; z-index: 9999; cursor: default; @@ -367,11 +358,17 @@ html[dir='rtl'] .doorHanger:before { background-color: rgb(255, 137, 153); } -html[dir='ltr'] #toolbarViewerLeft { +#toolbarViewerLeft { margin-left: -1px; + position: absolute; + top: 0; + left: 0; } -html[dir='rtl'] #toolbarViewerRight { - margin-left: -1px; + +#toolbarViewerRight { + position: absolute; + top: 0; + right: 0; } /* @@ -387,35 +384,21 @@ html[dir='rtl'] #toolbarViewerLeft { top: 0; right: 0; } -*/html[dir='ltr'] #toolbarViewerLeft > *, -html[dir='ltr'] #toolbarViewerMiddle > *, -html[dir='ltr'] #toolbarViewerRight > *, -html[dir='ltr'] .findbar > * { +*/ +#toolbarViewerLeft > *, +#toolbarViewerMiddle > *, +#toolbarViewerRight > * { float: left; } -html[dir='rtl'] #toolbarViewerLeft > *, -html[dir='rtl'] #toolbarViewerMiddle > *, -html[dir='rtl'] #toolbarViewerRight > *, -html[dir='rtl'] .findbar > * { - float: right; -} -html[dir='ltr'] .splitToolbarButton { +.splitToolbarButton { margin: 3px 2px 4px 0; display: inline-block; } -html[dir='rtl'] .splitToolbarButton { - margin: 3px 0 4px 2px; - display: inline-block; -} -html[dir='ltr'] .splitToolbarButton > .toolbarButton { +.splitToolbarButton > .toolbarButton { border-radius: 0; float: left; } -html[dir='rtl'] .splitToolbarButton > .toolbarButton { - border-radius: 0; - float: right; -} .toolbarButton { border: 0 none; @@ -487,8 +470,7 @@ html[dir='rtl'] .splitToolbarButton > .toolbarButton { 0 0 1px hsla(0,0%,0%,.05); z-index: 199; } -html[dir='ltr'] .splitToolbarButton > .toolbarButton:first-child, -html[dir='rtl'] .splitToolbarButton > .toolbarButton:last-child { +.splitToolbarButton > .toolbarButton:first-child { position: relative; margin: 0; margin-right: -1px; @@ -496,8 +478,7 @@ html[dir='rtl'] .splitToolbarButton > .toolbarButton:last-child { border-bottom-left-radius: 2px; border-right-color: transparent; } -html[dir='ltr'] .splitToolbarButton > .toolbarButton:last-child, -html[dir='rtl'] .splitToolbarButton > .toolbarButton:first-child { +.splitToolbarButton > .toolbarButton:last-child { position: relative; margin: 0; margin-left: -1px; @@ -513,12 +494,8 @@ html[dir='rtl'] .splitToolbarButton > .toolbarButton:first-child { box-shadow: 0 0 0 1px hsla(0,0%,100%,.08); display: inline-block; margin: 5px 0; -} -html[dir='ltr'] .splitToolbarButtonSeparator { float:left; } -html[dir='rtl'] .splitToolbarButtonSeparator { - float:right; } .splitToolbarButton:hover > .splitToolbarButtonSeparator, .splitToolbarButton.toggled > .splitToolbarButtonSeparator { @@ -573,14 +550,9 @@ html[dir='rtl'] .splitToolbarButtonSeparator { transition-timing-function: ease; } -html[dir='ltr'] .toolbarButton, -html[dir='ltr'] .dropdownToolbarButton { +.toolbarButton { margin: 3px 2px 4px 0; } -html[dir='rtl'] .toolbarButton, -html[dir='rtl'] .dropdownToolbarButton { - margin: 3px 0 4px 2px; -} .toolbarButton:hover, .toolbarButton:focus, @@ -671,13 +643,8 @@ html[dir='rtl'] .dropdownToolbarButton { padding: 3px 2px 2px; overflow: hidden; background: url(images/toolbarButton-menuArrows.png) no-repeat; -} -html[dir='ltr'] .dropdownToolbarButton { background-position: 95%; } -html[dir='rtl'] .dropdownToolbarButton { - background-position: 5%; -} .dropdownToolbarButton > select { -webkit-appearance: none; @@ -703,18 +670,12 @@ html[dir='rtl'] .dropdownToolbarButton { border-bottom: 1px rgba(255, 255, 255, .5) solid; } -html[dir='ltr'] .splitToolbarButton:first-child, -html[dir='ltr'] .toolbarButton:first-child, -html[dir='rtl'] .splitToolbarButton:last-child, -html[dir='rtl'] .toolbarButton:last-child { +.splitToolbarButton:first-child, +.toolbarButton:first-child, +.splitToolbarButton:last-child, +.toolbarButton:last-child { margin-left: 4px; } -html[dir='ltr'] .splitToolbarButton:last-child, -html[dir='ltr'] .toolbarButton:last-child, -html[dir='rtl'] .splitToolbarButton:first-child, -html[dir='rtl'] .toolbarButton:first-child { - margin-right: 4px; -} .toolbarButtonSpacer { width: 30px; @@ -1047,7 +1008,7 @@ canvas { display: block; } -.page { +.pdfpage { direction: ltr; width: 816px; height: 1056px; @@ -1060,12 +1021,12 @@ canvas { background-color: white; } -.page > a { +.pdfpage > a { display: block; /* position: absolute; */ } -.page > a:hover { +.pdfpage > a:hover { opacity: 0.2; background: #ff0; -webkit-box-shadow: 0px 2px 10px #ff0; diff --git a/common/static/js/pdfviewer.js b/common/static/js/pdfviewer.js new file mode 100644 index 0000000000..be5ee6c4e2 --- /dev/null +++ b/common/static/js/pdfviewer.js @@ -0,0 +1,276 @@ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modified (and JQuerified) from PDF-JS sample code (viewer.js) + */ +/* globals: PDFJS as defined in pdf.js. Also assumes that jquery is included. */ + +// +// Disable workers to avoid yet another cross-origin issue (workers need the URL of +// the script to be loaded, and currently do not allow cross-origin scripts) +// +PDFJS.disableWorker = true; + +(function($) { + $.fn.PDFViewer = function(options) { + var pdfViewer = this; + + var pdfDocument = null; + var url = options['url']; + var pageNum = 1; + if (options.pageNum) { + pageNum = int(options.pageNum); + } + var currentScale = 1.0; + var currentScaleValue = "1"; + + var viewerElement = document.getElementById('viewer'); + var ANNOT_MIN_SIZE = 10; + var DEFAULT_SCALE_DELTA = 1.1; + var UNKNOWN_SCALE = 0; + var MIN_SCALE = 0.25; + var MAX_SCALE = 4.0; + + var setupText = function setupText(textdiv, content, viewport) { + function bindLink(link, dest) { + // TODO: get this to work for document-internal links + // link.href = PDFView.getDestinationHash(dest); + // link.onclick = function pageViewSetupLinksOnclick() { + // if (dest) + // PDFView.navigateTo(dest); + // return false; + // }; + } + + function createElementWithStyle(tagName, item, rect) { + if (!rect) { + rect = viewport.convertToViewportRectangle(item.rect); + rect = PDFJS.Util.normalizeRect(rect); + } + var element = document.createElement(tagName); + element.style.left = Math.floor(rect[0]) + 'px'; + element.style.top = Math.floor(rect[1]) + 'px'; + element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'; + element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'; + // BW: my additions here, but should use css: + // TODO: move these to css + element.style.position = 'absolute'; + element.style.cursor = 'auto'; + + return element; + } + + function createTextAnnotation(item) { + var container = document.createElement('section'); + container.className = 'annotText'; + var rect = viewport.convertToViewportRectangle(item.rect); + rect = PDFJS.Util.normalizeRect(rect); + // sanity check because of OOo-generated PDFs + if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) { + rect[3] = rect[1] + ANNOT_MIN_SIZE; + } + if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) { + rect[2] = rect[0] + (rect[3] - rect[1]); + // make it square + } + var image = createElementWithStyle('img', item, rect); + var iconName = item.name; + } + + + content.getAnnotations().then(function(items) { + for (var i = 0; i < items.length; i++) { + var item = items[i]; + switch (item.type) { + case 'Link': + var link = createElementWithStyle('a', item); + link.href = item.url || ''; + if (!item.url) + bindLink(link, ('dest' in item) ? item.dest : null); + textdiv.appendChild(link); + break; + case 'Text': + var textAnnotation = createTextAnnotation(item); + if (textAnnotation) + textdiv.appendChild(textAnnotation); + break; + } + } + }); + } + // + // Get page info from document, resize canvas accordingly, and render page + // + // function renderPage(num) { + renderPage = function(num) { + // Update logging: + log_event("book", { "type" : "gotopage", "old" : pageNum, "new" : num }); + + parentElement = viewerElement; + while (parentElement.hasChildNodes()) + parentElement.removeChild(parentElement.lastChild); + + // Using promise to fetch the page + pdfDocument.getPage(num).then(function(page) { + var viewport = page.getViewport(currentScale); + + var pageDisplayWidth = viewport.width; + var pageDisplayHeight = viewport.height; + + var pageDivHolder = document.createElement('div'); + pageDivHolder.className = 'pdfpage'; + pageDivHolder.style.width = pageDisplayWidth + 'px'; + pageDivHolder.style.height = pageDisplayHeight + 'px'; + parentElement.appendChild(pageDivHolder); + + // Prepare canvas using PDF page dimensions + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + canvas.width = pageDisplayWidth; + canvas.height = pageDisplayHeight; + pageDivHolder.appendChild(canvas); + + // Render PDF page into canvas context + var renderContext = { + canvasContext : context, + viewport : viewport + }; + page.render(renderContext); + + // Prepare and populate text elements layer + setupText(pageDivHolder, page, viewport); + + }); + pageNum = num; + + // Update page counters + document.getElementById('numPages').textContent = 'of ' + pdfDocument.numPages; + $("#pageNumber").max = pdfDocument.numPages; + $("#pageNumber").val(pageNum); + } + + // Go to previous page + prevPage = function prev_page() { + if (pageNum <= 1) + return; + renderPage(pageNum - 1); + log_event("book", { "type" : "prevpage", "new" : pageNum }); + } + + // Go to next page + nextPage = function next_page() { + if (pageNum >= pdfDocument.numPages) + return; + renderPage(pageNum + 1); + log_event("book", { "type" : "nextpage", "new" : pageNum }); + } + + // Check to see if + selectScaleOption = function(value) { + var options = $('#scaleSelect').options; + var predefinedValueFound = false; + for (var i = 0; i < options.length; i++) { + var option = options[i]; + if (option.value != value) { + option.selected = false; + continue; + } + option.selected = true; + predefinedValueFound = true; + } + return predefinedValueFound; + } + + setScale = function pdfViewSetScale(val, resetAutoSettings, noScroll) { + if (val == currentScale) + return; + currentScale = val; + var customScaleOption = $('#customScaleOption')[0]; + customScaleOption.selected = false + var predefinedValueFound = selectScaleOption('' + currentScale); + if (!predefinedValueFound) { + customScaleOption.textContent = Math.round(currentScale * 10000) / 100 + '%'; + customScaleOption.selected = true; + } + $('#zoom_in').disabled = (currentScale === MAX_SCALE); + $('#zoom_out').disabled = (currentScale === MIN_SCALE); + + // Just call renderPage once the scale + // has been changed. If we were saving information about + // the rendering of other pages, we would need + // to reset those as well. + renderPage(pageNum); + }; + + parseScale = function pdfViewParseScale(value, resetAutoSettings, noScroll) { + // we shouldn't be choosing the 'custom' value -- it's only for display. + // Check, just in case. + if ('custom' == value) + return; + + var scale = parseFloat(value); + if (scale) { + currentScaleValue = value; + setScale(scale, true, noScroll); + return; + } + }; + + zoomIn = function pdfViewZoomIn() { + var newScale = (currentScale * DEFAULT_SCALE_DELTA).toFixed(2); + newScale = Math.min(MAX_SCALE, newScale); + parseScale(newScale, true); + }; + + zoomOut = function pdfViewZoomOut() { + var newScale = (currentScale / DEFAULT_SCALE_DELTA).toFixed(2); + newScale = Math.max(MIN_SCALE, newScale); + parseScale(newScale, true); + }; + + // + // Asynchronously download PDF as an ArrayBuffer + // + PDFJS.getDocument(url).then( + function getDocument(_pdfDocument) { + pdfDocument = _pdfDocument; + renderPage(pageNum); + }, + function getDocumentError(message, exception) { + // placeholder: don't expect errors :) + }, + function getDocumentProgress(progressData) { + // placeholder: not yet ready to display loading progress + }); + + $("#previous").click(function(event) { + prevPage(); + }); + + $("#next").click(function(event) { + nextPage(); + }); + + $('#zoom_in').click(function(event) { + zoomIn(); + }); + $('#zoom_out').click(function(event) { + zoomOut(); + }); + + $('#scaleSelect').change(function(event) { + parseScale(this.value); + }); + } +})(jQuery); diff --git a/lms/templates/static_pdfbook.html b/lms/templates/static_pdfbook.html index 0b7a7d38da..17b166b71b 100644 --- a/lms/templates/static_pdfbook.html +++ b/lms/templates/static_pdfbook.html @@ -1,109 +1,98 @@ <%inherit file="main.html" /> <%namespace name='static' file='static_content.html'/> -<%block name="title">${course.number} Textbook +<%block name="title"> + + + ${course.number} Textbook + <%block name="headextra"> <%static:css group='course'/> <%static:js group='courseware'/> + + + <%block name="js_extra"> - - - <%include file="/courseware/course_navigation.html" args="active_page='pdftextbook/{0}'.format(book_index)" /> -
-
-
-
-
- - -     - Page: / -
+
+
+
+
+
+
+
+ +
+ +
+ + + + +
-
- -
+
+
+
+ +
+ +
+ + + +
+
+
+
+
-
-
-
-
+
+
+
+ + + + +