1 /** 2 * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com> 3 * 4 * @licstart 5 * This file is part of WebODF. 6 * 7 * WebODF is free software: you can redistribute it and/or modify it 8 * under the terms of the GNU Affero General Public License (GNU AGPL) 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * WebODF is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with WebODF. If not, see <http://www.gnu.org/licenses/>. 19 * @licend 20 * 21 * @source: http://www.webodf.org/ 22 * @source: https://github.com/kogmbh/WebODF/ 23 */ 24 25 /*global runtime, odf, xmldom, webodf_css, core, gui */ 26 /*jslint sub: true*/ 27 28 (function () { 29 "use strict"; 30 /** 31 * A loading queue where various tasks related to loading can be placed 32 * and will be run with 10 ms between them. This gives the ui a change to 33 * to update. 34 * @constructor 35 */ 36 function LoadingQueue() { 37 var /**@type{!Array.<!Function>}*/ 38 queue = [], 39 taskRunning = false; 40 /** 41 * @param {!Function} task 42 * @return {undefined} 43 */ 44 function run(task) { 45 taskRunning = true; 46 runtime.setTimeout(function () { 47 try { 48 task(); 49 } catch (/**@type{Error}*/e) { 50 runtime.log(String(e) + "\n" + e.stack); 51 } 52 taskRunning = false; 53 if (queue.length > 0) { 54 run(queue.pop()); 55 } 56 }, 10); 57 } 58 /** 59 * @return {undefined} 60 */ 61 this.clearQueue = function () { 62 queue.length = 0; 63 }; 64 /** 65 * @param {!Function} loadingTask 66 * @return {undefined} 67 */ 68 this.addToQueue = function (loadingTask) { 69 if (queue.length === 0 && !taskRunning) { 70 return run(loadingTask); 71 } 72 queue.push(loadingTask); 73 }; 74 } 75 /** 76 * @constructor 77 * @implements {core.Destroyable} 78 * @param {!HTMLStyleElement} css 79 */ 80 function PageSwitcher(css) { 81 var sheet = /**@type{!CSSStyleSheet}*/(css.sheet), 82 /**@type{number}*/ 83 position = 1; 84 /** 85 * @return {undefined} 86 */ 87 function updateCSS() { 88 while (sheet.cssRules.length > 0) { 89 sheet.deleteRule(0); 90 } 91 // The #shadowContent contains the master pages, with each page in the slideshow 92 // corresponding to a master page in #shadowContent, and in the same order. 93 // So, when showing a page, also make it's master page (behind it) visible. 94 sheet.insertRule('#shadowContent draw|page {display:none;}', 0); 95 sheet.insertRule('office|presentation draw|page {display:none;}', 1); 96 sheet.insertRule("#shadowContent draw|page:nth-of-type(" + 97 position + ") {display:block;}", 2); 98 sheet.insertRule("office|presentation draw|page:nth-of-type(" + 99 position + ") {display:block;}", 3); 100 } 101 /** 102 * @return {undefined} 103 */ 104 this.showFirstPage = function () { 105 position = 1; 106 updateCSS(); 107 }; 108 /** 109 * @return {undefined} 110 */ 111 this.showNextPage = function () { 112 position += 1; 113 updateCSS(); 114 }; 115 /** 116 * @return {undefined} 117 */ 118 this.showPreviousPage = function () { 119 if (position > 1) { 120 position -= 1; 121 updateCSS(); 122 } 123 }; 124 125 /** 126 * @param {!number} n number of the page 127 * @return {undefined} 128 */ 129 this.showPage = function (n) { 130 if (n > 0) { 131 position = n; 132 updateCSS(); 133 } 134 }; 135 136 this.css = css; 137 138 /** 139 * @param {!function(!Error=)} callback, passing an error object in case of error 140 * @return {undefined} 141 */ 142 this.destroy = function (callback) { 143 css.parentNode.removeChild(css); 144 callback(); 145 }; 146 } 147 /** 148 * Register event listener on DOM element. 149 * @param {!Element} eventTarget 150 * @param {!string} eventType 151 * @param {!Function} eventHandler 152 * @return {undefined} 153 */ 154 function listenEvent(eventTarget, eventType, eventHandler) { 155 if (eventTarget.addEventListener) { 156 eventTarget.addEventListener(eventType, eventHandler, false); 157 } else if (eventTarget.attachEvent) { 158 eventType = "on" + eventType; 159 eventTarget.attachEvent(eventType, eventHandler); 160 } else { 161 eventTarget["on" + eventType] = eventHandler; 162 } 163 } 164 165 // variables per class (so not per instance!) 166 var /**@const@type {!string}*/drawns = odf.Namespaces.drawns, 167 /**@const@type {!string}*/fons = odf.Namespaces.fons, 168 /**@const@type {!string}*/officens = odf.Namespaces.officens, 169 /**@const@type {!string}*/stylens = odf.Namespaces.stylens, 170 /**@const@type {!string}*/svgns = odf.Namespaces.svgns, 171 /**@const@type {!string}*/tablens = odf.Namespaces.tablens, 172 /**@const@type {!string}*/textns = odf.Namespaces.textns, 173 /**@const@type {!string}*/xlinkns = odf.Namespaces.xlinkns, 174 /**@const@type {!string}*/presentationns = odf.Namespaces.presentationns, 175 /**@const@type {!string}*/webodfhelperns = "urn:webodf:names:helper", 176 xpath = xmldom.XPath, 177 domUtils = core.DomUtils; 178 179 /** 180 * @param {!Element} element 181 * @return {undefined} 182 */ 183 function clear(element) { 184 while (element.firstChild) { 185 element.removeChild(element.firstChild); 186 } 187 } 188 /** 189 * @param {!HTMLStyleElement} style 190 * @return {undefined} 191 */ 192 function clearCSSStyleSheet(style) { 193 var stylesheet = /**@type{!CSSStyleSheet}*/(style.sheet), 194 cssRules = stylesheet.cssRules; 195 196 while (cssRules.length) { 197 stylesheet.deleteRule(cssRules.length - 1); 198 } 199 } 200 201 /** 202 * A new styles.xml has been loaded. Update the live document with it. 203 * @param {!odf.OdfContainer} odfcontainer 204 * @param {!odf.Formatting} formatting 205 * @param {!HTMLStyleElement} stylesxmlcss 206 * @return {undefined} 207 **/ 208 function handleStyles(odfcontainer, formatting, stylesxmlcss) { 209 // update the css translation of the styles 210 var style2css = new odf.Style2CSS(), 211 list2css = new odf.ListStyleToCss(), 212 styleSheet = /**@type{!CSSStyleSheet}*/(stylesxmlcss.sheet), 213 styleTree = new odf.StyleTree( 214 odfcontainer.rootElement.styles, 215 odfcontainer.rootElement.automaticStyles).getStyleTree(); 216 217 style2css.style2css( 218 odfcontainer.getDocumentType(), 219 odfcontainer.rootElement, 220 styleSheet, 221 formatting.getFontMap(), 222 styleTree 223 ); 224 225 list2css.applyListStyles( 226 styleSheet, 227 styleTree, 228 odfcontainer.rootElement.body); 229 230 } 231 232 /** 233 * @param {!odf.OdfContainer} odfContainer 234 * @param {!HTMLStyleElement} fontcss 235 * @return {undefined} 236 **/ 237 function handleFonts(odfContainer, fontcss) { 238 // update the css references to the fonts 239 var fontLoader = new odf.FontLoader(); 240 fontLoader.loadFonts(odfContainer, 241 /**@type{!CSSStyleSheet}*/(fontcss.sheet)); 242 } 243 244 /** 245 * @param {!Element} clonedNode <draw:page/> 246 * @return {undefined} 247 */ 248 function dropTemplateDrawFrames(clonedNode) { 249 // drop all frames which are just template frames 250 var i, element, presentationClass, 251 clonedDrawFrameElements = domUtils.getElementsByTagNameNS(clonedNode, drawns, 'frame'); 252 for (i = 0; i < clonedDrawFrameElements.length; i += 1) { 253 element = /**@type{!Element}*/(clonedDrawFrameElements[i]); 254 presentationClass = element.getAttributeNS(presentationns, 'class'); 255 if (presentationClass && ! /^(date-time|footer|header|page-number)$/.test(presentationClass)) { 256 element.parentNode.removeChild(element); 257 } 258 } 259 } 260 261 /** 262 * @param {!odf.OdfContainer} odfContainer 263 * @param {!Element} frame 264 * @param {!string} headerFooterId 265 * @return {?string} 266 */ 267 function getHeaderFooter(odfContainer, frame, headerFooterId) { 268 var headerFooter = null, 269 i, 270 declElements = odfContainer.rootElement.body.getElementsByTagNameNS(presentationns, headerFooterId+'-decl'), 271 headerFooterName = frame.getAttributeNS(presentationns, 'use-'+headerFooterId+'-name'), 272 element; 273 274 if (headerFooterName && declElements.length > 0) { 275 for (i = 0; i < declElements.length; i += 1) { 276 element = /**@type{!Element}*/(declElements[i]); 277 if (element.getAttributeNS(presentationns, 'name') === headerFooterName) { 278 headerFooter = element.textContent; 279 break; 280 } 281 } 282 } 283 return headerFooter; 284 } 285 286 /** 287 * @param {!Element} rootElement 288 * @param {string} ns 289 * @param {string} localName 290 * @param {?string} value 291 * @return {undefined} 292 */ 293 function setContainerValue(rootElement, ns, localName, value) { 294 var i, containerList, 295 document = rootElement.ownerDocument, 296 e; 297 298 containerList = domUtils.getElementsByTagNameNS(rootElement, ns, localName); 299 for (i = 0; i < containerList.length; i += 1) { 300 clear(containerList[i]); 301 if (value) { 302 e = /**@type{!Element}*/(containerList[i]); 303 e.appendChild(document.createTextNode(value)); 304 } 305 } 306 } 307 308 /** 309 * @param {string} styleid 310 * @param {!Element} frame 311 * @param {!CSSStyleSheet} stylesheet 312 * @return {undefined} 313 **/ 314 function setDrawElementPosition(styleid, frame, stylesheet) { 315 frame.setAttributeNS(webodfhelperns, 'styleid', styleid); 316 var rule, 317 anchor = frame.getAttributeNS(textns, 'anchor-type'), 318 x = frame.getAttributeNS(svgns, 'x'), 319 y = frame.getAttributeNS(svgns, 'y'), 320 width = frame.getAttributeNS(svgns, 'width'), 321 height = frame.getAttributeNS(svgns, 'height'), 322 minheight = frame.getAttributeNS(fons, 'min-height'), 323 minwidth = frame.getAttributeNS(fons, 'min-width'); 324 325 if (anchor === "as-char") { 326 rule = 'display: inline-block;'; 327 } else if (anchor || x || y) { 328 rule = 'position: absolute;'; 329 } else if (width || height || minheight || minwidth) { 330 rule = 'display: block;'; 331 } 332 if (x) { 333 rule += 'left: ' + x + ';'; 334 } 335 if (y) { 336 rule += 'top: ' + y + ';'; 337 } 338 if (width) { 339 rule += 'width: ' + width + ';'; 340 } 341 if (height) { 342 rule += 'height: ' + height + ';'; 343 } 344 if (minheight) { 345 rule += 'min-height: ' + minheight + ';'; 346 } 347 if (minwidth) { 348 rule += 'min-width: ' + minwidth + ';'; 349 } 350 if (rule) { 351 rule = 'draw|' + frame.localName + '[webodfhelper|styleid="' + styleid + '"] {' + 352 rule + '}'; 353 stylesheet.insertRule(rule, stylesheet.cssRules.length); 354 } 355 } 356 /** 357 * @param {!Element} image 358 * @return {string} 359 **/ 360 function getUrlFromBinaryDataElement(image) { 361 var node = image.firstChild; 362 while (node) { 363 if (node.namespaceURI === officens && 364 node.localName === "binary-data") { 365 // TODO: detect mime-type, assuming png for now 366 // the base64 data can be pretty printed, hence we need remove all the line breaks and whitespaces 367 return "data:image/png;base64," + node.textContent.replace(/[\r\n\s]/g, ''); 368 } 369 node = node.nextSibling; 370 } 371 return ""; 372 } 373 /** 374 * @param {string} id 375 * @param {!odf.OdfContainer} container 376 * @param {!Element} image 377 * @param {!CSSStyleSheet} stylesheet 378 * @return {undefined} 379 **/ 380 function setImage(id, container, image, stylesheet) { 381 image.setAttributeNS(webodfhelperns, 'styleid', id); 382 var url = image.getAttributeNS(xlinkns, 'href'), 383 /**@type{!odf.OdfPart}*/ 384 part; 385 /** 386 * @param {?string} url 387 */ 388 function callback(url) { 389 var rule; 390 if (url) { // if part cannot be loaded, url is null 391 rule = "background-image: url(" + url + ");"; 392 rule = 'draw|image[webodfhelper|styleid="' + id + '"] {' + rule + '}'; 393 stylesheet.insertRule(rule, stylesheet.cssRules.length); 394 } 395 } 396 /** 397 * @param {!odf.OdfPart} p 398 */ 399 function onchange(p) { 400 callback(p.url); 401 } 402 // look for a office:binary-data 403 if (url) { 404 try { 405 part = container.getPart(url); 406 part.onchange = onchange; 407 part.load(); 408 } catch (/**@type{*}*/e) { 409 runtime.log('slight problem: ' + String(e)); 410 } 411 } else { 412 url = getUrlFromBinaryDataElement(image); 413 callback(url); 414 } 415 } 416 /** 417 * @param {!Element} odfbody 418 * @return {undefined} 419 */ 420 function formatParagraphAnchors(odfbody) { 421 var n, 422 i, 423 nodes = xpath.getODFElementsWithXPath(odfbody, 424 ".//*[*[@text:anchor-type='paragraph']]", 425 odf.Namespaces.lookupNamespaceURI); 426 for (i = 0; i < nodes.length; i += 1) { 427 n = nodes[i]; 428 if (n.setAttributeNS) { 429 n.setAttributeNS(webodfhelperns, "containsparagraphanchor", true); 430 } 431 } 432 } 433 /** 434 * Modify tables to support merged cells (col/row span) 435 * @param {!Element} odffragment 436 * @param {!string} documentns 437 * @return {undefined} 438 */ 439 function modifyTables(odffragment, documentns) { 440 var i, 441 tableCells, 442 node; 443 444 /** 445 * @param {!Element} node 446 * @return {undefined} 447 */ 448 function modifyTableCell(node) { 449 // If we have a cell which spans columns or rows, 450 // then add col-span or row-span attributes. 451 if (node.hasAttributeNS(tablens, "number-columns-spanned")) { 452 node.setAttributeNS(documentns, "colspan", 453 node.getAttributeNS(tablens, "number-columns-spanned")); 454 } 455 if (node.hasAttributeNS(tablens, "number-rows-spanned")) { 456 node.setAttributeNS(documentns, "rowspan", 457 node.getAttributeNS(tablens, "number-rows-spanned")); 458 } 459 } 460 tableCells = domUtils.getElementsByTagNameNS(odffragment, tablens, 'table-cell'); 461 for (i = 0; i < tableCells.length; i += 1) { 462 node = /**@type{!Element}*/(tableCells[i]); 463 modifyTableCell(node); 464 } 465 } 466 467 /** 468 * Make the text:line-break elements behave like html br element. 469 * @param {!Element} odffragment 470 * @return {undefined} 471 */ 472 function modifyLineBreakElements(odffragment) { 473 var document = odffragment.ownerDocument, 474 lineBreakElements = domUtils.getElementsByTagNameNS(odffragment, textns, "line-break"); 475 lineBreakElements.forEach(function (lineBreak) { 476 // Make sure we don't add br more than once as this method is executed whenever user undo an operation. 477 if (!lineBreak.hasChildNodes()) { 478 lineBreak.appendChild(document.createElement("br")); 479 } 480 }); 481 } 482 483 /** 484 * Expand ODF spaces of the form <text:s text:c=N/> to N consecutive 485 * <text:s/> elements. This makes things simpler for WebODF during 486 * handling of spaces, in particular during editing. 487 * @param {!Element} odffragment 488 * @return {undefined} 489 */ 490 function expandSpaceElements(odffragment) { 491 var spaces, 492 doc = odffragment.ownerDocument; 493 494 /** 495 * @param {!Element} space 496 * @return {undefined} 497 */ 498 function expandSpaceElement(space) { 499 var j, count; 500 // If the space has any children, remove them and put a " " text 501 // node in place. 502 while (space.firstChild) { 503 space.removeChild(space.firstChild); 504 } 505 space.appendChild(doc.createTextNode(" ")); 506 507 count = parseInt(space.getAttributeNS(textns, "c"), 10); 508 if (count > 1) { 509 // Make it a 'simple' space node 510 space.removeAttributeNS(textns, "c"); 511 // Prepend count-1 clones of this space node to itself 512 for (j = 1; j < count; j += 1) { 513 space.parentNode.insertBefore(space.cloneNode(true), space); 514 } 515 } 516 } 517 518 spaces = domUtils.getElementsByTagNameNS(odffragment, textns, "s"); 519 spaces.forEach(expandSpaceElement); 520 } 521 522 /** 523 * Expand tabs to contain tab characters. This eases cursor behaviour 524 * during editing 525 * @param {!Element} odffragment 526 */ 527 function expandTabElements(odffragment) { 528 var tabs; 529 530 tabs = domUtils.getElementsByTagNameNS(odffragment, textns, "tab"); 531 tabs.forEach(function(tab) { 532 tab.textContent = "\t"; 533 }); 534 } 535 /** 536 * @param {!Element} odfbody 537 * @param {!CSSStyleSheet} stylesheet 538 * @return {undefined} 539 **/ 540 function modifyDrawElements(odfbody, stylesheet) { 541 var node, 542 /**@type{!Array.<!Element>}*/ 543 drawElements = [], 544 i; 545 // find all the draw:* elements 546 node = odfbody.firstElementChild; 547 while (node && node !== odfbody) { 548 if (node.namespaceURI === drawns) { 549 drawElements[drawElements.length] = node; 550 } 551 if (node.firstElementChild) { 552 node = node.firstElementChild; 553 } else { 554 while (node && node !== odfbody && !node.nextElementSibling) { 555 node = /**@type{!Element}*/(node.parentNode); 556 } 557 if (node && node.nextElementSibling) { 558 node = node.nextElementSibling; 559 } 560 } 561 } 562 // adjust all the frame positions 563 for (i = 0; i < drawElements.length; i += 1) { 564 node = drawElements[i]; 565 setDrawElementPosition('frame' + String(i), node, stylesheet); 566 } 567 formatParagraphAnchors(odfbody); 568 } 569 570 /** 571 * @param {!odf.Formatting} formatting 572 * @param {!odf.OdfContainer} odfContainer 573 * @param {!Element} shadowContent 574 * @param {!Element} odfbody 575 * @param {!CSSStyleSheet} stylesheet 576 * @return {undefined} 577 **/ 578 function cloneMasterPages(formatting, odfContainer, shadowContent, odfbody, stylesheet) { 579 var masterPageName, 580 masterPageElement, 581 styleId, 582 clonedPageElement, 583 clonedElement, 584 clonedDrawElements, 585 pageNumber = 0, 586 i, 587 element, 588 elementToClone, 589 document = odfContainer.rootElement.ownerDocument; 590 591 element = odfbody.firstElementChild; 592 // no master pages to expect? 593 if (!(element && element.namespaceURI === officens && 594 (element.localName === "presentation" || element.localName === "drawing"))) { 595 return; 596 } 597 598 element = element.firstElementChild; 599 while (element) { 600 // If there was a master-page-name attribute, then we are dealing with a draw:page. 601 // Get the referenced master page element from the master styles 602 masterPageName = element.getAttributeNS(drawns, 'master-page-name'); 603 masterPageElement = masterPageName ? formatting.getMasterPageElement(masterPageName) : null; 604 605 // If the referenced master page exists, create a new page and copy over it's contents into the new page, 606 // except for the ones that are placeholders. Also, call setDrawElementPosition on each of those child frames. 607 if (masterPageElement) { 608 styleId = element.getAttributeNS(webodfhelperns, 'styleid'); 609 clonedPageElement = document.createElementNS(drawns, 'draw:page'); 610 611 elementToClone = masterPageElement.firstElementChild; 612 i = 0; 613 while (elementToClone) { 614 if (elementToClone.getAttributeNS(presentationns, 'placeholder') !== 'true') { 615 clonedElement = /**@type{!Element}*/(elementToClone.cloneNode(true)); 616 clonedPageElement.appendChild(clonedElement); 617 } 618 elementToClone = elementToClone.nextElementSibling; 619 i += 1; 620 } 621 // TODO: above already do not clone nodes which match the rule for being dropped 622 dropTemplateDrawFrames(clonedPageElement); 623 624 // Position all elements 625 clonedDrawElements = domUtils.getElementsByTagNameNS(clonedPageElement, drawns, '*'); 626 for (i = 0; i < clonedDrawElements.length; i += 1) { 627 setDrawElementPosition(styleId + '_' + i, clonedDrawElements[i], stylesheet); 628 } 629 630 // Append the cloned master page to the "Shadow Content" element outside the main ODF dom 631 shadowContent.appendChild(clonedPageElement); 632 633 // Get the page number by counting the number of previous master pages in this shadowContent 634 pageNumber = String(shadowContent.getElementsByTagNameNS(drawns, 'page').length); 635 // Get the page-number tag in the cloned master page and set the text content to the calculated number 636 setContainerValue(clonedPageElement, textns, 'page-number', pageNumber); 637 638 // Care for header 639 setContainerValue(clonedPageElement, presentationns, 'header', getHeaderFooter(odfContainer, /**@type{!Element}*/(element), 'header')); 640 // Care for footer 641 setContainerValue(clonedPageElement, presentationns, 'footer', getHeaderFooter(odfContainer, /**@type{!Element}*/(element), 'footer')); 642 643 // Now call setDrawElementPosition on this new page to set the proper dimensions 644 setDrawElementPosition(styleId, clonedPageElement, stylesheet); 645 // Add a custom attribute with the style name of the normal page, so the CSS rules created for the styles of the normal page 646 // to display/hide frames of certain classes from the master page can address the cloned master page belonging to that normal page 647 // Cmp. addDrawPageFrameDisplayRules in Style2CSS 648 clonedPageElement.setAttributeNS(webodfhelperns, 'page-style-name', element.getAttributeNS(drawns, 'style-name')); 649 // TODO: investigate if the attributes draw:style-name and style:page-layoutname should be copied over 650 // to the cloned page from the master page as well, or if this one below is enough already 651 // And finally, add an attribute referring to the master page, so the CSS targeted for that master page will style this 652 clonedPageElement.setAttributeNS(drawns, 'draw:master-page-name', masterPageElement.getAttributeNS(stylens, 'name')); 653 } 654 655 element = element.nextElementSibling; 656 } 657 } 658 659 /** 660 * @param {!odf.OdfContainer} container 661 * @param {!Element} plugin 662 * @return {undefined} 663 **/ 664 function setVideo(container, plugin) { 665 var video, source, url, doc = plugin.ownerDocument, 666 /**@type{!odf.OdfPart}*/ 667 part; 668 669 url = plugin.getAttributeNS(xlinkns, 'href'); 670 671 /** 672 * @param {?string} url 673 * @param {string} mimetype 674 * @return {undefined} 675 */ 676 function callback(url, mimetype) { 677 var ns = doc.documentElement.namespaceURI; 678 // test for video mimetypes 679 if (mimetype.substr(0, 6) === 'video/') { 680 video = doc.createElementNS(ns, "video"); 681 video.setAttribute('controls', 'controls'); 682 683 source = doc.createElementNS(ns, 'source'); 684 if (url) { 685 source.setAttribute('src', url); 686 } 687 source.setAttribute('type', mimetype); 688 689 video.appendChild(source); 690 plugin.parentNode.appendChild(video); 691 } else { 692 plugin.innerHtml = 'Unrecognised Plugin'; 693 } 694 } 695 /** 696 * @param {!odf.OdfPart} p 697 */ 698 function onchange(p) { 699 callback(p.url, p.mimetype); 700 } 701 // look for a office:binary-data 702 if (url) { 703 try { 704 part = container.getPart(url); 705 part.onchange = onchange; 706 part.load(); 707 } catch (/**@type{*}*/e) { 708 runtime.log('slight problem: ' + String(e)); 709 } 710 } else { 711 // this will fail atm - following function assumes PNG data] 712 runtime.log('using MP4 data fallback'); 713 url = getUrlFromBinaryDataElement(plugin); 714 callback(url, 'video/mp4'); 715 } 716 } 717 718 /** 719 * @param {!HTMLHeadElement} head 720 * @return {?HTMLStyleElement} 721 */ 722 function findWebODFStyleSheet(head) { 723 var style = head.firstElementChild; 724 while (style && !(style.localName === "style" 725 && style.hasAttribute("webodfcss"))) { 726 style = style.nextElementSibling; 727 } 728 return /**@type{?HTMLStyleElement}*/(style); 729 } 730 731 /** 732 * @param {!Document} document 733 * @return {!HTMLStyleElement} 734 */ 735 function addWebODFStyleSheet(document) { 736 var head = /**@type{!HTMLHeadElement}*/(document.getElementsByTagName('head')[0]), 737 css, 738 /**@type{?HTMLStyleElement}*/ 739 style, 740 href, 741 count = document.styleSheets.length; 742 // make sure this is only added once per HTML document, e.g. in case of 743 // multiple odfCanvases 744 style = findWebODFStyleSheet(head); 745 if (style) { 746 count = parseInt(style.getAttribute("webodfcss"), 10); 747 style.setAttribute("webodfcss", count + 1); 748 return style; 749 } 750 if (String(typeof webodf_css) === "string") { 751 css = /**@type{!string}*/(webodf_css); 752 } else { 753 href = "webodf.css"; 754 if (runtime.currentDirectory) { 755 href = runtime.currentDirectory(); 756 if (href.length > 0 && href.substr(-1) !== "/") { 757 href += "/"; 758 } 759 href += "../webodf.css"; 760 } 761 css = /**@type{!string}*/(runtime.readFileSync(href, "utf-8")); 762 } 763 style = /**@type{!HTMLStyleElement}*/(document.createElementNS(head.namespaceURI, 'style')); 764 style.setAttribute('media', 'screen, print, handheld, projection'); 765 style.setAttribute('type', 'text/css'); 766 style.setAttribute('webodfcss', '1'); 767 style.appendChild(document.createTextNode(css)); 768 head.appendChild(style); 769 return style; 770 } 771 772 /** 773 * @param {!HTMLStyleElement} webodfcss 774 * @return {undefined} 775 */ 776 function removeWebODFStyleSheet(webodfcss) { 777 var count = parseInt(webodfcss.getAttribute("webodfcss"), 10); 778 if (count === 1) { 779 webodfcss.parentNode.removeChild(webodfcss); 780 } else { 781 webodfcss.setAttribute("count", count - 1); 782 } 783 } 784 785 /** 786 * @param {!Document} document Put and ODF Canvas inside this element. 787 * @return {!HTMLStyleElement} 788 */ 789 function addStyleSheet(document) { 790 var head = /**@type{!HTMLHeadElement}*/(document.getElementsByTagName('head')[0]), 791 style = document.createElementNS(head.namespaceURI, 'style'), 792 /**@type{string}*/ 793 text = ''; 794 style.setAttribute('type', 'text/css'); 795 style.setAttribute('media', 'screen, print, handheld, projection'); 796 odf.Namespaces.forEachPrefix(function(prefix, ns) { 797 text += "@namespace " + prefix + " url(" + ns + ");\n"; 798 }); 799 text += "@namespace webodfhelper url(" + webodfhelperns + ");\n"; 800 style.appendChild(document.createTextNode(text)); 801 head.appendChild(style); 802 return /**@type {!HTMLStyleElement}*/(style); 803 } 804 /** 805 * This class manages a loaded ODF document that is shown in an element. 806 * It takes care of giving visual feedback on loading, ensures that the 807 * stylesheets are loaded. 808 * @constructor 809 * @implements {gui.AnnotatableCanvas} 810 * @implements {ops.Canvas} 811 * @implements {core.Destroyable} 812 * @param {!HTMLElement} element Put and ODF Canvas inside this element. 813 * @param {!gui.Viewport=} viewport Viewport used for scrolling elements and ranges into view 814 */ 815 odf.OdfCanvas = function OdfCanvas(element, viewport) { 816 runtime.assert((element !== null) && (element !== undefined), 817 "odf.OdfCanvas constructor needs DOM element"); 818 runtime.assert((element.ownerDocument !== null) && (element.ownerDocument !== undefined), 819 "odf.OdfCanvas constructor needs DOM"); 820 var self = this, 821 doc = /**@type{!Document}*/(element.ownerDocument), 822 /**@type{!odf.OdfContainer}*/ 823 odfcontainer, 824 /**@type{!odf.Formatting}*/ 825 formatting = new odf.Formatting(), 826 /**@type{!PageSwitcher}*/ 827 pageSwitcher, 828 /**@type{HTMLDivElement}*/ 829 sizer = null, 830 /**@type{HTMLDivElement}*/ 831 annotationsPane = null, 832 allowAnnotations = false, 833 showAnnotationRemoveButton = false, 834 /**@type{gui.AnnotationViewManager}*/ 835 annotationViewManager = null, 836 /**@type{!HTMLStyleElement}*/ 837 webodfcss, 838 /**@type{!HTMLStyleElement}*/ 839 fontcss, 840 /**@type{!HTMLStyleElement}*/ 841 stylesxmlcss, 842 /**@type{!HTMLStyleElement}*/ 843 positioncss, 844 shadowContent, 845 /**@type{!Object.<string,!Array.<!Function>>}*/ 846 eventHandlers = {}, 847 waitingForDoneTimeoutId, 848 /**@type{!core.ScheduledTask}*/redrawContainerTask, 849 shouldRefreshCss = false, 850 shouldRerenderAnnotations = false, 851 loadingQueue = new LoadingQueue(), 852 /**@type{!gui.ZoomHelper}*/ 853 zoomHelper = new gui.ZoomHelper(), 854 /**@type{!gui.Viewport}*/ 855 canvasViewport = viewport || new gui.SingleScrollViewport(/**@type{!HTMLElement}*/(element.parentNode)); 856 857 /** 858 * Load all the images that are inside an odf element. 859 * @param {!odf.OdfContainer} container 860 * @param {!Element} odffragment 861 * @param {!CSSStyleSheet} stylesheet 862 * @return {undefined} 863 */ 864 function loadImages(container, odffragment, stylesheet) { 865 var i, 866 images, 867 node; 868 /** 869 * Do delayed loading for all the images 870 * @param {string} name 871 * @param {!odf.OdfContainer} container 872 * @param {!Element} node 873 * @param {!CSSStyleSheet} stylesheet 874 * @return {undefined} 875 */ 876 function loadImage(name, container, node, stylesheet) { 877 // load image with a small delay to give the html ui a chance to 878 // update 879 loadingQueue.addToQueue(function () { 880 setImage(name, container, node, stylesheet); 881 }); 882 } 883 images = odffragment.getElementsByTagNameNS(drawns, 'image'); 884 for (i = 0; i < images.length; i += 1) { 885 node = /**@type{!Element}*/(images.item(i)); 886 loadImage('image' + String(i), container, node, stylesheet); 887 } 888 } 889 /** 890 * Load all the video that are inside an odf element. 891 * @param {!odf.OdfContainer} container 892 * @param {!Element} odffragment 893 * @return {undefined} 894 */ 895 function loadVideos(container, odffragment) { 896 var i, 897 plugins, 898 node; 899 /** 900 * Do delayed loading for all the videos 901 * @param {!odf.OdfContainer} container 902 * @param {!Element} node 903 * @return {undefined} 904 */ 905 function loadVideo(container, node) { 906 // load video with a small delay to give the html ui a chance to 907 // update 908 loadingQueue.addToQueue(function () { 909 setVideo(container, node); 910 }); 911 } 912 // embedded video is stored in a draw:plugin element 913 plugins = odffragment.getElementsByTagNameNS(drawns, 'plugin'); 914 for (i = 0; i < plugins.length; i += 1) { 915 node = /**@type{!Element}*/(plugins.item(i)); 916 loadVideo(container, node); 917 } 918 } 919 920 /** 921 * Register an event handler 922 * @param {!string} eventType 923 * @param {!Function} eventHandler 924 * @return {undefined} 925 */ 926 function addEventListener(eventType, eventHandler) { 927 var handlers; 928 if (eventHandlers.hasOwnProperty(eventType)) { 929 handlers = eventHandlers[eventType]; 930 } else { 931 handlers = eventHandlers[eventType] = []; 932 } 933 if (eventHandler && handlers.indexOf(eventHandler) === -1) { 934 handlers.push(eventHandler); 935 } 936 } 937 /** 938 * Fire an event 939 * @param {!string} eventType 940 * @param {Array.<Object>=} args 941 * @return {undefined} 942 */ 943 function fireEvent(eventType, args) { 944 if (!eventHandlers.hasOwnProperty(eventType)) { 945 return; 946 } 947 var handlers = eventHandlers[eventType], i; 948 for (i = 0; i < handlers.length; i += 1) { 949 handlers[i].apply(null, args); 950 } 951 } 952 953 /** 954 * @return {undefined} 955 */ 956 function fixContainerSize() { 957 var minHeight, 958 odfdoc = sizer.firstChild, 959 zoomLevel = zoomHelper.getZoomLevel(); 960 961 if (!odfdoc) { 962 return; 963 } 964 965 // All zooming of the sizer within the canvas 966 // is done relative to the top-left corner. 967 sizer.style.WebkitTransformOrigin = "0% 0%"; 968 sizer.style.MozTransformOrigin = "0% 0%"; 969 sizer.style.msTransformOrigin = "0% 0%"; 970 sizer.style.OTransformOrigin = "0% 0%"; 971 sizer.style.transformOrigin = "0% 0%"; 972 973 if (annotationViewManager) { 974 minHeight = annotationViewManager.getMinimumHeightForAnnotationPane(); 975 if (minHeight) { 976 sizer.style.minHeight = minHeight; 977 } else { 978 sizer.style.removeProperty('min-height'); 979 } 980 } 981 982 element.style.width = Math.round(zoomLevel * sizer.offsetWidth) + "px"; 983 element.style.height = Math.round(zoomLevel * sizer.offsetHeight) + "px"; 984 // Re-apply inline-block to canvas element on resizing. 985 // Chrome tends to forget this property after a relayout 986 element.style.display = "inline-block"; 987 } 988 989 /** 990 * @return {undefined} 991 */ 992 function redrawContainer() { 993 if (shouldRefreshCss) { 994 handleStyles(odfcontainer, formatting, stylesxmlcss); 995 shouldRefreshCss = false; 996 // different styles means different layout, thus different sizes 997 } 998 if (shouldRerenderAnnotations) { 999 if (annotationViewManager) { 1000 annotationViewManager.rerenderAnnotations(); 1001 } 1002 shouldRerenderAnnotations = false; 1003 } 1004 fixContainerSize(); 1005 } 1006 1007 /** 1008 * A new content.xml has been loaded. Update the live document with it. 1009 * @param {!odf.OdfContainer} container 1010 * @param {!odf.ODFDocumentElement} odfnode 1011 * @return {undefined} 1012 **/ 1013 function handleContent(container, odfnode) { 1014 var css = /**@type{!CSSStyleSheet}*/(positioncss.sheet); 1015 // only append the content at the end 1016 clear(element); 1017 1018 sizer = /**@type{!HTMLDivElement}*/(doc.createElementNS(element.namespaceURI, 'div')); 1019 sizer.style.display = "inline-block"; 1020 sizer.style.background = "white"; 1021 // When the window is shrunk such that the 1022 // canvas container has a horizontal scrollbar, 1023 // zooming out seems to not make the scrollable 1024 // width disappear. This extra scrollable 1025 // width seems to be proportional to the 1026 // annotation pane's width. Setting the 'float' 1027 // of the sizer to 'left' fixes this in webkit. 1028 sizer.style.setProperty("float", "left", "important"); 1029 sizer.appendChild(odfnode); 1030 element.appendChild(sizer); 1031 1032 // An annotations pane div. Will only be shown when annotations are enabled 1033 annotationsPane = /**@type{!HTMLDivElement}*/(doc.createElementNS(element.namespaceURI, 'div')); 1034 annotationsPane.id = "annotationsPane"; 1035 // A "Shadow Content" div. This will contain stuff like pages 1036 // extracted from <style:master-page>. These need to be nicely 1037 // styled, so we will populate this in the ODF body first. Once the 1038 // styling is handled, it can then be lifted out of the 1039 // ODF body and placed beside it, to not pollute the ODF dom. 1040 shadowContent = doc.createElementNS(element.namespaceURI, 'div'); 1041 shadowContent.id = "shadowContent"; 1042 shadowContent.style.position = 'absolute'; 1043 shadowContent.style.top = 0; 1044 shadowContent.style.left = 0; 1045 container.getContentElement().appendChild(shadowContent); 1046 1047 modifyDrawElements(odfnode.body, css); 1048 cloneMasterPages(formatting, container, shadowContent, odfnode.body, css); 1049 modifyTables(odfnode.body, element.namespaceURI); 1050 modifyLineBreakElements(odfnode.body); 1051 expandSpaceElements(odfnode.body); 1052 expandTabElements(odfnode.body); 1053 loadImages(container, odfnode.body, css); 1054 loadVideos(container, odfnode.body); 1055 1056 sizer.insertBefore(shadowContent, sizer.firstChild); 1057 zoomHelper.setZoomableElement(sizer); 1058 } 1059 1060 /** 1061 * Wraps all annotations and renders them using the Annotation View Manager. 1062 * @param {!Element} odffragment 1063 * @return {undefined} 1064 */ 1065 function modifyAnnotations(odffragment) { 1066 var annotationNodes = /**@type{!Array.<!odf.AnnotationElement>}*/(domUtils.getElementsByTagNameNS(odffragment, officens, 'annotation')); 1067 1068 annotationNodes.forEach(annotationViewManager.addAnnotation); 1069 annotationViewManager.rerenderAnnotations(); 1070 } 1071 1072 /** 1073 * This should create an annotations pane if non existent, and then populate it with annotations 1074 * If annotations are disallowed, it should remove the pane and all annotations 1075 * @param {!odf.ODFDocumentElement} odfnode 1076 */ 1077 function handleAnnotations(odfnode) { 1078 if (allowAnnotations) { 1079 if (!annotationsPane.parentNode) { 1080 sizer.appendChild(annotationsPane); 1081 } 1082 if (annotationViewManager) { 1083 annotationViewManager.forgetAnnotations(); 1084 } 1085 annotationViewManager = new gui.AnnotationViewManager(self, odfnode.body, annotationsPane, showAnnotationRemoveButton); 1086 modifyAnnotations(odfnode.body); 1087 fixContainerSize(); 1088 } else { 1089 if (annotationsPane.parentNode) { 1090 sizer.removeChild(annotationsPane); 1091 annotationViewManager.forgetAnnotations(); 1092 fixContainerSize(); 1093 } 1094 } 1095 } 1096 1097 /** 1098 * @param {boolean} suppressEvent Suppress the statereadychange event from firing. Used for refreshing the OdtContainer 1099 * @return {undefined} 1100 **/ 1101 function refreshOdf(suppressEvent) { 1102 1103 // synchronize the object a window.odfcontainer with the view 1104 function callback() { 1105 // clean up 1106 clearCSSStyleSheet(fontcss); 1107 clearCSSStyleSheet(stylesxmlcss); 1108 clearCSSStyleSheet(positioncss); 1109 1110 clear(element); 1111 1112 // setup 1113 element.style.display = "inline-block"; 1114 var odfnode = odfcontainer.rootElement; 1115 element.ownerDocument.importNode(odfnode, true); 1116 1117 formatting.setOdfContainer(odfcontainer); 1118 handleFonts(odfcontainer, fontcss); 1119 handleStyles(odfcontainer, formatting, stylesxmlcss); 1120 // do content last, because otherwise the document is constantly 1121 // updated whenever the css changes 1122 handleContent(odfcontainer, odfnode); 1123 handleAnnotations(odfnode); 1124 1125 if (!suppressEvent) { 1126 loadingQueue.addToQueue(function () { 1127 fireEvent("statereadychange", [odfcontainer]); 1128 }); 1129 } 1130 } 1131 1132 if (odfcontainer.state === odf.OdfContainer.DONE) { 1133 callback(); 1134 } else { 1135 // so the ODF is not done yet. take care that we'll 1136 // do the work once it is done: 1137 1138 // FIXME: use callback registry instead of replacing the onchange 1139 runtime.log("WARNING: refreshOdf called but ODF was not DONE."); 1140 1141 waitingForDoneTimeoutId = runtime.setTimeout(function later_cb() { 1142 if (odfcontainer.state === odf.OdfContainer.DONE) { 1143 callback(); 1144 } else { 1145 runtime.log("will be back later..."); 1146 waitingForDoneTimeoutId = runtime.setTimeout(later_cb, 500); 1147 } 1148 }, 100); 1149 } 1150 } 1151 1152 /** 1153 * Updates the CSS rules to match the ODF document styles and also 1154 * updates the size of the canvas to match the new layout. 1155 * Needs to be called after changes to the styles of the ODF document. 1156 * @return {undefined} 1157 */ 1158 this.refreshCSS = function () { 1159 shouldRefreshCss = true; 1160 redrawContainerTask.trigger(); 1161 }; 1162 1163 /** 1164 * Updates the size of the canvas to the size of the content. 1165 * Needs to be called after changes to the content of the ODF document. 1166 * @return {undefined} 1167 */ 1168 this.refreshSize = function () { 1169 redrawContainerTask.trigger(); 1170 }; 1171 /** 1172 * @return {!odf.OdfContainer} 1173 */ 1174 this.odfContainer = function () { 1175 return odfcontainer; 1176 }; 1177 /** 1178 * Set a odfcontainer manually. 1179 * @param {!odf.OdfContainer} container 1180 * @param {boolean=} suppressEvent Default value is false 1181 * @return {undefined} 1182 */ 1183 this.setOdfContainer = function (container, suppressEvent) { 1184 odfcontainer = container; 1185 refreshOdf(suppressEvent === true); 1186 }; 1187 /** 1188 * @param {string} url 1189 * @return {undefined} 1190 */ 1191 function load(url) { 1192 // clean up 1193 loadingQueue.clearQueue(); 1194 1195 // FIXME: We need to support parametrized strings, because 1196 // drop-in word replacements are inadequate for translations; 1197 // see http://techbase.kde.org/Development/Tutorials/Localization/i18n_Mistakes#Pitfall_.232:_Word_Puzzles 1198 element.innerHTML = runtime.tr('Loading') + ' ' + url + '...'; 1199 element.removeAttribute('style'); 1200 // open the odf container 1201 odfcontainer = new odf.OdfContainer(url, function (container) { 1202 // assignment might be necessary if the callback 1203 // fires before the assignment above happens. 1204 odfcontainer = container; 1205 refreshOdf(false); 1206 }); 1207 } 1208 this["load"] = load; 1209 this.load = load; 1210 1211 /** 1212 * @param {function(?string):undefined} callback 1213 * @return {undefined} 1214 */ 1215 this.save = function (callback) { 1216 odfcontainer.save(callback); 1217 }; 1218 1219 /** 1220 * @param {!string} eventName 1221 * @param {!function(*)} handler 1222 * @return {undefined} 1223 */ 1224 this.addListener = function (eventName, handler) { 1225 switch (eventName) { 1226 case "click": 1227 listenEvent(element, eventName, handler); break; 1228 default: 1229 addEventListener(eventName, handler); break; 1230 } 1231 }; 1232 1233 /** 1234 * @return {!odf.Formatting} 1235 */ 1236 this.getFormatting = function () { 1237 return formatting; 1238 }; 1239 1240 /** 1241 * @return {gui.AnnotationViewManager} 1242 */ 1243 this.getAnnotationViewManager = function () { 1244 return annotationViewManager; 1245 }; 1246 1247 /** 1248 * Unstyles and untracks all annotations present in the document, 1249 * and then tracks them again with fresh rendering 1250 * @return {undefined} 1251 */ 1252 this.refreshAnnotations = function () { 1253 handleAnnotations(odfcontainer.rootElement); 1254 }; 1255 1256 /** 1257 * Re-renders all annotations if enabled 1258 * @return {undefined} 1259 */ 1260 this.rerenderAnnotations = function () { 1261 if (annotationViewManager) { 1262 shouldRerenderAnnotations = true; 1263 redrawContainerTask.trigger(); 1264 } 1265 }; 1266 1267 /** 1268 * This returns the element inside the canvas which can be zoomed with 1269 * CSS and which contains the ODF document and the annotation sidebar. 1270 * @return {!HTMLElement} 1271 */ 1272 this.getSizer = function () { 1273 return /**@type{!HTMLElement}*/(sizer); 1274 }; 1275 1276 /** Allows / disallows annotations 1277 * @param {!boolean} allow 1278 * @param {!boolean} showRemoveButton 1279 * @return {undefined} 1280 */ 1281 this.enableAnnotations = function (allow, showRemoveButton) { 1282 if (allow !== allowAnnotations) { 1283 allowAnnotations = allow; 1284 showAnnotationRemoveButton = showRemoveButton; 1285 if (odfcontainer) { 1286 handleAnnotations(odfcontainer.rootElement); 1287 } 1288 } 1289 }; 1290 1291 /** 1292 * Adds an annotation for the annotaiton manager to track 1293 * and wraps and highlights it 1294 * @param {!odf.AnnotationElement} annotation 1295 * @return {undefined} 1296 */ 1297 this.addAnnotation = function (annotation) { 1298 if (annotationViewManager) { 1299 annotationViewManager.addAnnotation(annotation); 1300 fixContainerSize(); 1301 } 1302 }; 1303 1304 /** 1305 * Stops annotations and unwraps it 1306 * @return {undefined} 1307 */ 1308 this.forgetAnnotations = function () { 1309 if (annotationViewManager) { 1310 annotationViewManager.forgetAnnotations(); 1311 fixContainerSize(); 1312 } 1313 }; 1314 1315 /** 1316 * @return {!gui.ZoomHelper} 1317 */ 1318 this.getZoomHelper = function () { 1319 return zoomHelper; 1320 }; 1321 1322 /** 1323 * @param {!number} zoom 1324 * @return {undefined} 1325 */ 1326 this.setZoomLevel = function (zoom) { 1327 zoomHelper.setZoomLevel(zoom); 1328 }; 1329 /** 1330 * @return {!number} 1331 */ 1332 this.getZoomLevel = function () { 1333 return zoomHelper.getZoomLevel(); 1334 }; 1335 /** 1336 * @param {!number} width 1337 * @param {!number} height 1338 * @return {undefined} 1339 */ 1340 this.fitToContainingElement = function (width, height) { 1341 var zoomLevel = zoomHelper.getZoomLevel(), 1342 realWidth = element.offsetWidth / zoomLevel, 1343 realHeight = element.offsetHeight / zoomLevel, 1344 zoom; 1345 1346 zoom = width / realWidth; 1347 if (height / realHeight < zoom) { 1348 zoom = height / realHeight; 1349 } 1350 zoomHelper.setZoomLevel(zoom); 1351 }; 1352 /** 1353 * @param {!number} width 1354 * @return {undefined} 1355 */ 1356 this.fitToWidth = function (width) { 1357 var realWidth = element.offsetWidth / zoomHelper.getZoomLevel(); 1358 zoomHelper.setZoomLevel(width / realWidth); 1359 }; 1360 /** 1361 * @param {!number} width 1362 * @param {!number} height 1363 * @return {undefined} 1364 */ 1365 this.fitSmart = function (width, height) { 1366 var realWidth, realHeight, newScale, 1367 zoomLevel = zoomHelper.getZoomLevel(); 1368 1369 realWidth = element.offsetWidth / zoomLevel; 1370 realHeight = element.offsetHeight / zoomLevel; 1371 1372 newScale = width / realWidth; 1373 if (height !== undefined) { 1374 if (height / realHeight < newScale) { 1375 newScale = height / realHeight; 1376 } 1377 } 1378 1379 zoomHelper.setZoomLevel(Math.min(1.0, newScale)); 1380 }; 1381 /** 1382 * @param {!number} height 1383 * @return {undefined} 1384 */ 1385 this.fitToHeight = function (height) { 1386 var realHeight = element.offsetHeight / zoomHelper.getZoomLevel(); 1387 zoomHelper.setZoomLevel(height / realHeight); 1388 }; 1389 /** 1390 * @return {undefined} 1391 */ 1392 this.showFirstPage = function () { 1393 pageSwitcher.showFirstPage(); 1394 }; 1395 /** 1396 * @return {undefined} 1397 */ 1398 this.showNextPage = function () { 1399 pageSwitcher.showNextPage(); 1400 }; 1401 /** 1402 * @return {undefined} 1403 */ 1404 this.showPreviousPage = function () { 1405 pageSwitcher.showPreviousPage(); 1406 }; 1407 /** 1408 * @param {!number} n number of the page 1409 * @return {undefined} 1410 */ 1411 this.showPage = function (n) { 1412 pageSwitcher.showPage(n); 1413 fixContainerSize(); 1414 }; 1415 1416 /** 1417 * @return {!HTMLElement} 1418 */ 1419 this.getElement = function () { 1420 return element; 1421 }; 1422 1423 /** 1424 * @return {!gui.Viewport} 1425 */ 1426 this.getViewport = function () { 1427 return canvasViewport; 1428 }; 1429 1430 /** 1431 * Add additional css rules for newly inserted draw:frame and draw:image. eg. position, dimensions and background image 1432 * @param {!Element} frame 1433 */ 1434 this.addCssForFrameWithImage = function (frame) { 1435 // TODO: frameid and imageid generation here is better brought in sync with that for the images on loading of a odf file. 1436 var frameName = frame.getAttributeNS(drawns, 'name'), 1437 fc = frame.firstElementChild; 1438 setDrawElementPosition(frameName, frame, 1439 /**@type{!CSSStyleSheet}*/(positioncss.sheet)); 1440 if (fc) { 1441 setImage(frameName + 'img', odfcontainer, fc, 1442 /**@type{!CSSStyleSheet}*/( positioncss.sheet)); 1443 } 1444 }; 1445 /** 1446 * @param {!function(!Error=)} callback, passing an error object in case of error 1447 * @return {undefined} 1448 */ 1449 this.destroy = function(callback) { 1450 var head = /**@type{!HTMLHeadElement}*/(doc.getElementsByTagName('head')[0]), 1451 cleanup = [pageSwitcher.destroy, redrawContainerTask.destroy]; 1452 1453 runtime.clearTimeout(waitingForDoneTimeoutId); 1454 // TODO: anything to clean with annotationViewManager? 1455 if (annotationsPane && annotationsPane.parentNode) { 1456 annotationsPane.parentNode.removeChild(annotationsPane); 1457 } 1458 1459 zoomHelper.destroy(function () { 1460 if (sizer) { 1461 element.removeChild(sizer); 1462 sizer = null; 1463 } 1464 }); 1465 1466 // remove all styles 1467 removeWebODFStyleSheet(webodfcss); 1468 head.removeChild(fontcss); 1469 head.removeChild(stylesxmlcss); 1470 head.removeChild(positioncss); 1471 1472 // TODO: loadingQueue, make sure it is empty 1473 core.Async.destroyAll(cleanup, callback); 1474 }; 1475 1476 function init() { 1477 webodfcss = addWebODFStyleSheet(doc); 1478 pageSwitcher = new PageSwitcher(addStyleSheet(doc)); 1479 fontcss = addStyleSheet(doc); 1480 stylesxmlcss = addStyleSheet(doc); 1481 positioncss = addStyleSheet(doc); 1482 redrawContainerTask = core.Task.createRedrawTask(redrawContainer); 1483 zoomHelper.subscribe(gui.ZoomHelper.signalZoomChanged, fixContainerSize); 1484 } 1485 1486 init(); 1487 }; 1488 }()); 1489