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 = new 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 = clonedNode.getElementsByTagNameNS(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 = rootElement.getElementsByTagNameNS(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 = odffragment.getElementsByTagNameNS(tablens, 'table-cell'); 461 for (i = 0; i < tableCells.length; i += 1) { 462 node = /**@type{!Element}*/(tableCells.item(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 pageNumber = 0, 585 i, 586 element, 587 elementToClone, 588 document = odfContainer.rootElement.ownerDocument; 589 590 element = odfbody.firstElementChild; 591 // no master pages to expect? 592 if (!(element && element.namespaceURI === officens && 593 (element.localName === "presentation" || element.localName === "drawing"))) { 594 return; 595 } 596 597 element = element.firstElementChild; 598 while (element) { 599 // If there was a master-page-name attribute, then we are dealing with a draw:page. 600 // Get the referenced master page element from the master styles 601 masterPageName = element.getAttributeNS(drawns, 'master-page-name'); 602 masterPageElement = masterPageName ? formatting.getMasterPageElement(masterPageName) : null; 603 604 // If the referenced master page exists, create a new page and copy over it's contents into the new page, 605 // except for the ones that are placeholders. Also, call setDrawElementPosition on each of those child frames. 606 if (masterPageElement) { 607 styleId = element.getAttributeNS(webodfhelperns, 'styleid'); 608 clonedPageElement = document.createElementNS(drawns, 'draw:page'); 609 610 elementToClone = masterPageElement.firstElementChild; 611 i = 0; 612 while (elementToClone) { 613 if (elementToClone.getAttributeNS(presentationns, 'placeholder') !== 'true') { 614 clonedElement = /**@type{!Element}*/(elementToClone.cloneNode(true)); 615 clonedPageElement.appendChild(clonedElement); 616 setDrawElementPosition(styleId + '_' + i, clonedElement, stylesheet); 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 // Append the cloned master page to the "Shadow Content" element outside the main ODF dom 625 shadowContent.appendChild(clonedPageElement); 626 627 // Get the page number by counting the number of previous master pages in this shadowContent 628 pageNumber = String(shadowContent.getElementsByTagNameNS(drawns, 'page').length); 629 // Get the page-number tag in the cloned master page and set the text content to the calculated number 630 setContainerValue(clonedPageElement, textns, 'page-number', pageNumber); 631 632 // Care for header 633 setContainerValue(clonedPageElement, presentationns, 'header', getHeaderFooter(odfContainer, /**@type{!Element}*/(element), 'header')); 634 // Care for footer 635 setContainerValue(clonedPageElement, presentationns, 'footer', getHeaderFooter(odfContainer, /**@type{!Element}*/(element), 'footer')); 636 637 // Now call setDrawElementPosition on this new page to set the proper dimensions 638 setDrawElementPosition(styleId, clonedPageElement, stylesheet); 639 // And finally, add an attribute referring to the master page, so the CSS targeted for that master page will style this 640 clonedPageElement.setAttributeNS(drawns, 'draw:master-page-name', masterPageElement.getAttributeNS(stylens, 'name')); 641 } 642 643 element = element.nextElementSibling; 644 } 645 } 646 647 /** 648 * @param {!odf.OdfContainer} container 649 * @param {!Element} plugin 650 * @return {undefined} 651 **/ 652 function setVideo(container, plugin) { 653 var video, source, url, doc = plugin.ownerDocument, 654 /**@type{!odf.OdfPart}*/ 655 part; 656 657 url = plugin.getAttributeNS(xlinkns, 'href'); 658 659 /** 660 * @param {?string} url 661 * @param {string} mimetype 662 * @return {undefined} 663 */ 664 function callback(url, mimetype) { 665 var ns = doc.documentElement.namespaceURI; 666 // test for video mimetypes 667 if (mimetype.substr(0, 6) === 'video/') { 668 video = doc.createElementNS(ns, "video"); 669 video.setAttribute('controls', 'controls'); 670 671 source = doc.createElementNS(ns, 'source'); 672 if (url) { 673 source.setAttribute('src', url); 674 } 675 source.setAttribute('type', mimetype); 676 677 video.appendChild(source); 678 plugin.parentNode.appendChild(video); 679 } else { 680 plugin.innerHtml = 'Unrecognised Plugin'; 681 } 682 } 683 /** 684 * @param {!odf.OdfPart} p 685 */ 686 function onchange(p) { 687 callback(p.url, p.mimetype); 688 } 689 // look for a office:binary-data 690 if (url) { 691 try { 692 part = container.getPart(url); 693 part.onchange = onchange; 694 part.load(); 695 } catch (/**@type{*}*/e) { 696 runtime.log('slight problem: ' + String(e)); 697 } 698 } else { 699 // this will fail atm - following function assumes PNG data] 700 runtime.log('using MP4 data fallback'); 701 url = getUrlFromBinaryDataElement(plugin); 702 callback(url, 'video/mp4'); 703 } 704 } 705 706 /** 707 * @param {!HTMLHeadElement} head 708 * @return {?HTMLStyleElement} 709 */ 710 function findWebODFStyleSheet(head) { 711 var style = head.firstElementChild; 712 while (style && !(style.localName === "style" 713 && style.hasAttribute("webodfcss"))) { 714 style = style.nextElementSibling; 715 } 716 return /**@type{?HTMLStyleElement}*/(style); 717 } 718 719 /** 720 * @param {!Document} document 721 * @return {!HTMLStyleElement} 722 */ 723 function addWebODFStyleSheet(document) { 724 var head = /**@type{!HTMLHeadElement}*/(document.getElementsByTagName('head')[0]), 725 css, 726 /**@type{?HTMLStyleElement}*/ 727 style, 728 href, 729 count = document.styleSheets.length; 730 // make sure this is only added once per HTML document, e.g. in case of 731 // multiple odfCanvases 732 style = findWebODFStyleSheet(head); 733 if (style) { 734 count = parseInt(style.getAttribute("webodfcss"), 10); 735 style.setAttribute("webodfcss", count + 1); 736 return style; 737 } 738 if (String(typeof webodf_css) === "string") { 739 css = /**@type{!string}*/(webodf_css); 740 } else { 741 href = "webodf.css"; 742 if (runtime.currentDirectory) { 743 href = runtime.currentDirectory(); 744 if (href.length > 0 && href.substr(-1) !== "/") { 745 href += "/"; 746 } 747 href += "../webodf.css"; 748 } 749 css = /**@type{!string}*/(runtime.readFileSync(href, "utf-8")); 750 } 751 style = /**@type{!HTMLStyleElement}*/(document.createElementNS(head.namespaceURI, 'style')); 752 style.setAttribute('media', 'screen, print, handheld, projection'); 753 style.setAttribute('type', 'text/css'); 754 style.setAttribute('webodfcss', '1'); 755 style.appendChild(document.createTextNode(css)); 756 head.appendChild(style); 757 return style; 758 } 759 760 /** 761 * @param {!HTMLStyleElement} webodfcss 762 * @return {undefined} 763 */ 764 function removeWebODFStyleSheet(webodfcss) { 765 var count = parseInt(webodfcss.getAttribute("webodfcss"), 10); 766 if (count === 1) { 767 webodfcss.parentNode.removeChild(webodfcss); 768 } else { 769 webodfcss.setAttribute("count", count - 1); 770 } 771 } 772 773 /** 774 * @param {!Document} document Put and ODF Canvas inside this element. 775 * @return {!HTMLStyleElement} 776 */ 777 function addStyleSheet(document) { 778 var head = /**@type{!HTMLHeadElement}*/(document.getElementsByTagName('head')[0]), 779 style = document.createElementNS(head.namespaceURI, 'style'), 780 /**@type{string}*/ 781 text = ''; 782 style.setAttribute('type', 'text/css'); 783 style.setAttribute('media', 'screen, print, handheld, projection'); 784 odf.Namespaces.forEachPrefix(function(prefix, ns) { 785 text += "@namespace " + prefix + " url(" + ns + ");\n"; 786 }); 787 text += "@namespace webodfhelper url(" + webodfhelperns + ");\n"; 788 style.appendChild(document.createTextNode(text)); 789 head.appendChild(style); 790 return /**@type {!HTMLStyleElement}*/(style); 791 } 792 /** 793 * This class manages a loaded ODF document that is shown in an element. 794 * It takes care of giving visual feedback on loading, ensures that the 795 * stylesheets are loaded. 796 * @constructor 797 * @implements {gui.AnnotatableCanvas} 798 * @implements {ops.Canvas} 799 * @implements {core.Destroyable} 800 * @param {!HTMLElement} element Put and ODF Canvas inside this element. 801 */ 802 odf.OdfCanvas = function OdfCanvas(element) { 803 runtime.assert((element !== null) && (element !== undefined), 804 "odf.OdfCanvas constructor needs DOM element"); 805 runtime.assert((element.ownerDocument !== null) && (element.ownerDocument !== undefined), 806 "odf.OdfCanvas constructor needs DOM"); 807 var self = this, 808 doc = /**@type{!Document}*/(element.ownerDocument), 809 /**@type{!odf.OdfContainer}*/ 810 odfcontainer, 811 /**@type{!odf.Formatting}*/ 812 formatting = new odf.Formatting(), 813 /**@type{!PageSwitcher}*/ 814 pageSwitcher, 815 /**@type{HTMLDivElement}*/ 816 sizer = null, 817 /**@type{HTMLDivElement}*/ 818 annotationsPane = null, 819 allowAnnotations = false, 820 showAnnotationRemoveButton = false, 821 /**@type{gui.AnnotationViewManager}*/ 822 annotationViewManager = null, 823 /**@type{!HTMLStyleElement}*/ 824 webodfcss, 825 /**@type{!HTMLStyleElement}*/ 826 fontcss, 827 /**@type{!HTMLStyleElement}*/ 828 stylesxmlcss, 829 /**@type{!HTMLStyleElement}*/ 830 positioncss, 831 shadowContent, 832 /**@type{!Object.<string,!Array.<!Function>>}*/ 833 eventHandlers = {}, 834 waitingForDoneTimeoutId, 835 /**@type{!core.ScheduledTask}*/redrawContainerTask, 836 shouldRefreshCss = false, 837 shouldRerenderAnnotations = false, 838 loadingQueue = new LoadingQueue(), 839 /**@type{!gui.ZoomHelper}*/ 840 zoomHelper = new gui.ZoomHelper(); 841 842 /** 843 * Load all the images that are inside an odf element. 844 * @param {!odf.OdfContainer} container 845 * @param {!Element} odffragment 846 * @param {!CSSStyleSheet} stylesheet 847 * @return {undefined} 848 */ 849 function loadImages(container, odffragment, stylesheet) { 850 var i, 851 images, 852 node; 853 /** 854 * Do delayed loading for all the images 855 * @param {string} name 856 * @param {!odf.OdfContainer} container 857 * @param {!Element} node 858 * @param {!CSSStyleSheet} stylesheet 859 * @return {undefined} 860 */ 861 function loadImage(name, container, node, stylesheet) { 862 // load image with a small delay to give the html ui a chance to 863 // update 864 loadingQueue.addToQueue(function () { 865 setImage(name, container, node, stylesheet); 866 }); 867 } 868 images = odffragment.getElementsByTagNameNS(drawns, 'image'); 869 for (i = 0; i < images.length; i += 1) { 870 node = /**@type{!Element}*/(images.item(i)); 871 loadImage('image' + String(i), container, node, stylesheet); 872 } 873 } 874 /** 875 * Load all the video that are inside an odf element. 876 * @param {!odf.OdfContainer} container 877 * @param {!Element} odffragment 878 * @return {undefined} 879 */ 880 function loadVideos(container, odffragment) { 881 var i, 882 plugins, 883 node; 884 /** 885 * Do delayed loading for all the videos 886 * @param {!odf.OdfContainer} container 887 * @param {!Element} node 888 * @return {undefined} 889 */ 890 function loadVideo(container, node) { 891 // load video with a small delay to give the html ui a chance to 892 // update 893 loadingQueue.addToQueue(function () { 894 setVideo(container, node); 895 }); 896 } 897 // embedded video is stored in a draw:plugin element 898 plugins = odffragment.getElementsByTagNameNS(drawns, 'plugin'); 899 for (i = 0; i < plugins.length; i += 1) { 900 node = /**@type{!Element}*/(plugins.item(i)); 901 loadVideo(container, node); 902 } 903 } 904 905 /** 906 * Register an event handler 907 * @param {!string} eventType 908 * @param {!Function} eventHandler 909 * @return {undefined} 910 */ 911 function addEventListener(eventType, eventHandler) { 912 var handlers; 913 if (eventHandlers.hasOwnProperty(eventType)) { 914 handlers = eventHandlers[eventType]; 915 } else { 916 handlers = eventHandlers[eventType] = []; 917 } 918 if (eventHandler && handlers.indexOf(eventHandler) === -1) { 919 handlers.push(eventHandler); 920 } 921 } 922 /** 923 * Fire an event 924 * @param {!string} eventType 925 * @param {Array.<Object>=} args 926 * @return {undefined} 927 */ 928 function fireEvent(eventType, args) { 929 if (!eventHandlers.hasOwnProperty(eventType)) { 930 return; 931 } 932 var handlers = eventHandlers[eventType], i; 933 for (i = 0; i < handlers.length; i += 1) { 934 handlers[i].apply(null, args); 935 } 936 } 937 938 /** 939 * @return {undefined} 940 */ 941 function fixContainerSize() { 942 var minHeight, 943 odfdoc = sizer.firstChild, 944 zoomLevel = zoomHelper.getZoomLevel(); 945 946 if (!odfdoc) { 947 return; 948 } 949 950 // All zooming of the sizer within the canvas 951 // is done relative to the top-left corner. 952 sizer.style.WebkitTransformOrigin = "0% 0%"; 953 sizer.style.MozTransformOrigin = "0% 0%"; 954 sizer.style.msTransformOrigin = "0% 0%"; 955 sizer.style.OTransformOrigin = "0% 0%"; 956 sizer.style.transformOrigin = "0% 0%"; 957 958 if (annotationViewManager) { 959 minHeight = annotationViewManager.getMinimumHeightForAnnotationPane(); 960 if (minHeight) { 961 sizer.style.minHeight = minHeight; 962 } else { 963 sizer.style.removeProperty('min-height'); 964 } 965 } 966 967 element.style.width = Math.round(zoomLevel * sizer.offsetWidth) + "px"; 968 element.style.height = Math.round(zoomLevel * sizer.offsetHeight) + "px"; 969 // Re-apply inline-block to canvas element on resizing. 970 // Chrome tends to forget this property after a relayout 971 element.style.display = "inline-block"; 972 } 973 974 /** 975 * @return {undefined} 976 */ 977 function redrawContainer() { 978 if (shouldRefreshCss) { 979 handleStyles(odfcontainer, formatting, stylesxmlcss); 980 shouldRefreshCss = false; 981 // different styles means different layout, thus different sizes 982 } 983 if (shouldRerenderAnnotations) { 984 if (annotationViewManager) { 985 annotationViewManager.rerenderAnnotations(); 986 } 987 shouldRerenderAnnotations = false; 988 } 989 fixContainerSize(); 990 } 991 992 /** 993 * A new content.xml has been loaded. Update the live document with it. 994 * @param {!odf.OdfContainer} container 995 * @param {!odf.ODFDocumentElement} odfnode 996 * @return {undefined} 997 **/ 998 function handleContent(container, odfnode) { 999 var css = /**@type{!CSSStyleSheet}*/(positioncss.sheet); 1000 // only append the content at the end 1001 clear(element); 1002 1003 sizer = /**@type{!HTMLDivElement}*/(doc.createElementNS(element.namespaceURI, 'div')); 1004 sizer.style.display = "inline-block"; 1005 sizer.style.background = "white"; 1006 // When the window is shrunk such that the 1007 // canvas container has a horizontal scrollbar, 1008 // zooming out seems to not make the scrollable 1009 // width disappear. This extra scrollable 1010 // width seems to be proportional to the 1011 // annotation pane's width. Setting the 'float' 1012 // of the sizer to 'left' fixes this in webkit. 1013 sizer.style.setProperty("float", "left", "important"); 1014 sizer.appendChild(odfnode); 1015 element.appendChild(sizer); 1016 1017 // An annotations pane div. Will only be shown when annotations are enabled 1018 annotationsPane = /**@type{!HTMLDivElement}*/(doc.createElementNS(element.namespaceURI, 'div')); 1019 annotationsPane.id = "annotationsPane"; 1020 // A "Shadow Content" div. This will contain stuff like pages 1021 // extracted from <style:master-page>. These need to be nicely 1022 // styled, so we will populate this in the ODF body first. Once the 1023 // styling is handled, it can then be lifted out of the 1024 // ODF body and placed beside it, to not pollute the ODF dom. 1025 shadowContent = doc.createElementNS(element.namespaceURI, 'div'); 1026 shadowContent.id = "shadowContent"; 1027 shadowContent.style.position = 'absolute'; 1028 shadowContent.style.top = 0; 1029 shadowContent.style.left = 0; 1030 container.getContentElement().appendChild(shadowContent); 1031 1032 modifyDrawElements(odfnode.body, css); 1033 cloneMasterPages(formatting, container, shadowContent, odfnode.body, css); 1034 modifyTables(odfnode.body, element.namespaceURI); 1035 modifyLineBreakElements(odfnode.body); 1036 expandSpaceElements(odfnode.body); 1037 expandTabElements(odfnode.body); 1038 loadImages(container, odfnode.body, css); 1039 loadVideos(container, odfnode.body); 1040 1041 sizer.insertBefore(shadowContent, sizer.firstChild); 1042 zoomHelper.setZoomableElement(sizer); 1043 } 1044 1045 /** 1046 * Wraps all annotations and renders them using the Annotation View Manager. 1047 * @param {!Element} odffragment 1048 * @return {undefined} 1049 */ 1050 function modifyAnnotations(odffragment) { 1051 var annotationNodes = /**@type{!Array.<!odf.AnnotationElement>}*/(domUtils.getElementsByTagNameNS(odffragment, officens, 'annotation')); 1052 1053 annotationNodes.forEach(annotationViewManager.addAnnotation); 1054 annotationViewManager.rerenderAnnotations(); 1055 } 1056 1057 /** 1058 * This should create an annotations pane if non existent, and then populate it with annotations 1059 * If annotations are disallowed, it should remove the pane and all annotations 1060 * @param {!odf.ODFDocumentElement} odfnode 1061 */ 1062 function handleAnnotations(odfnode) { 1063 if (allowAnnotations) { 1064 if (!annotationsPane.parentNode) { 1065 sizer.appendChild(annotationsPane); 1066 } 1067 if (annotationViewManager) { 1068 annotationViewManager.forgetAnnotations(); 1069 } 1070 annotationViewManager = new gui.AnnotationViewManager(self, odfnode.body, annotationsPane, showAnnotationRemoveButton); 1071 modifyAnnotations(odfnode.body); 1072 fixContainerSize(); 1073 } else { 1074 if (annotationsPane.parentNode) { 1075 sizer.removeChild(annotationsPane); 1076 annotationViewManager.forgetAnnotations(); 1077 fixContainerSize(); 1078 } 1079 } 1080 } 1081 1082 /** 1083 * @param {boolean} suppressEvent Suppress the statereadychange event from firing. Used for refreshing the OdtContainer 1084 * @return {undefined} 1085 **/ 1086 function refreshOdf(suppressEvent) { 1087 1088 // synchronize the object a window.odfcontainer with the view 1089 function callback() { 1090 // clean up 1091 clearCSSStyleSheet(fontcss); 1092 clearCSSStyleSheet(stylesxmlcss); 1093 clearCSSStyleSheet(positioncss); 1094 1095 clear(element); 1096 1097 // setup 1098 element.style.display = "inline-block"; 1099 var odfnode = odfcontainer.rootElement; 1100 element.ownerDocument.importNode(odfnode, true); 1101 1102 formatting.setOdfContainer(odfcontainer); 1103 handleFonts(odfcontainer, fontcss); 1104 handleStyles(odfcontainer, formatting, stylesxmlcss); 1105 // do content last, because otherwise the document is constantly 1106 // updated whenever the css changes 1107 handleContent(odfcontainer, odfnode); 1108 handleAnnotations(odfnode); 1109 1110 if (!suppressEvent) { 1111 loadingQueue.addToQueue(function () { 1112 fireEvent("statereadychange", [odfcontainer]); 1113 }); 1114 } 1115 } 1116 1117 if (odfcontainer.state === odf.OdfContainer.DONE) { 1118 callback(); 1119 } else { 1120 // so the ODF is not done yet. take care that we'll 1121 // do the work once it is done: 1122 1123 // FIXME: use callback registry instead of replacing the onchange 1124 runtime.log("WARNING: refreshOdf called but ODF was not DONE."); 1125 1126 waitingForDoneTimeoutId = runtime.setTimeout(function later_cb() { 1127 if (odfcontainer.state === odf.OdfContainer.DONE) { 1128 callback(); 1129 } else { 1130 runtime.log("will be back later..."); 1131 waitingForDoneTimeoutId = runtime.setTimeout(later_cb, 500); 1132 } 1133 }, 100); 1134 } 1135 } 1136 1137 /** 1138 * Updates the CSS rules to match the ODF document styles and also 1139 * updates the size of the canvas to match the new layout. 1140 * Needs to be called after changes to the styles of the ODF document. 1141 * @return {undefined} 1142 */ 1143 this.refreshCSS = function () { 1144 shouldRefreshCss = true; 1145 redrawContainerTask.trigger(); 1146 }; 1147 1148 /** 1149 * Updates the size of the canvas to the size of the content. 1150 * Needs to be called after changes to the content of the ODF document. 1151 * @return {undefined} 1152 */ 1153 this.refreshSize = function () { 1154 redrawContainerTask.trigger(); 1155 }; 1156 /** 1157 * @return {!odf.OdfContainer} 1158 */ 1159 this.odfContainer = function () { 1160 return odfcontainer; 1161 }; 1162 /** 1163 * Set a odfcontainer manually. 1164 * @param {!odf.OdfContainer} container 1165 * @param {boolean=} suppressEvent Default value is false 1166 * @return {undefined} 1167 */ 1168 this.setOdfContainer = function (container, suppressEvent) { 1169 odfcontainer = container; 1170 refreshOdf(suppressEvent === true); 1171 }; 1172 /** 1173 * @param {string} url 1174 * @return {undefined} 1175 */ 1176 function load(url) { 1177 // clean up 1178 loadingQueue.clearQueue(); 1179 1180 // FIXME: We need to support parametrized strings, because 1181 // drop-in word replacements are inadequate for translations; 1182 // see http://techbase.kde.org/Development/Tutorials/Localization/i18n_Mistakes#Pitfall_.232:_Word_Puzzles 1183 element.innerHTML = runtime.tr('Loading') + ' ' + url + '...'; 1184 element.removeAttribute('style'); 1185 // open the odf container 1186 odfcontainer = new odf.OdfContainer(url, function (container) { 1187 // assignment might be necessary if the callback 1188 // fires before the assignment above happens. 1189 odfcontainer = container; 1190 refreshOdf(false); 1191 }); 1192 } 1193 this["load"] = load; 1194 this.load = load; 1195 1196 /** 1197 * @param {function(?string):undefined} callback 1198 * @return {undefined} 1199 */ 1200 this.save = function (callback) { 1201 odfcontainer.save(callback); 1202 }; 1203 1204 /** 1205 * @param {!string} eventName 1206 * @param {!function(*)} handler 1207 * @return {undefined} 1208 */ 1209 this.addListener = function (eventName, handler) { 1210 switch (eventName) { 1211 case "click": 1212 listenEvent(element, eventName, handler); break; 1213 default: 1214 addEventListener(eventName, handler); break; 1215 } 1216 }; 1217 1218 /** 1219 * @return {!odf.Formatting} 1220 */ 1221 this.getFormatting = function () { 1222 return formatting; 1223 }; 1224 1225 /** 1226 * @return {gui.AnnotationViewManager} 1227 */ 1228 this.getAnnotationViewManager = function () { 1229 return annotationViewManager; 1230 }; 1231 1232 /** 1233 * Unstyles and untracks all annotations present in the document, 1234 * and then tracks them again with fresh rendering 1235 * @return {undefined} 1236 */ 1237 this.refreshAnnotations = function () { 1238 handleAnnotations(odfcontainer.rootElement); 1239 }; 1240 1241 /** 1242 * Re-renders all annotations if enabled 1243 * @return {undefined} 1244 */ 1245 this.rerenderAnnotations = function () { 1246 if (annotationViewManager) { 1247 shouldRerenderAnnotations = true; 1248 redrawContainerTask.trigger(); 1249 } 1250 }; 1251 1252 /** 1253 * This returns the element inside the canvas which can be zoomed with 1254 * CSS and which contains the ODF document and the annotation sidebar. 1255 * @return {!HTMLElement} 1256 */ 1257 this.getSizer = function () { 1258 return /**@type{!HTMLElement}*/(sizer); 1259 }; 1260 1261 /** Allows / disallows annotations 1262 * @param {!boolean} allow 1263 * @param {!boolean} showRemoveButton 1264 * @return {undefined} 1265 */ 1266 this.enableAnnotations = function (allow, showRemoveButton) { 1267 if (allow !== allowAnnotations) { 1268 allowAnnotations = allow; 1269 showAnnotationRemoveButton = showRemoveButton; 1270 if (odfcontainer) { 1271 handleAnnotations(odfcontainer.rootElement); 1272 } 1273 } 1274 }; 1275 1276 /** 1277 * Adds an annotation for the annotaiton manager to track 1278 * and wraps and highlights it 1279 * @param {!odf.AnnotationElement} annotation 1280 * @return {undefined} 1281 */ 1282 this.addAnnotation = function (annotation) { 1283 if (annotationViewManager) { 1284 annotationViewManager.addAnnotation(annotation); 1285 fixContainerSize(); 1286 } 1287 }; 1288 1289 /** 1290 * Stops annotations and unwraps it 1291 * @return {undefined} 1292 */ 1293 this.forgetAnnotations = function () { 1294 if (annotationViewManager) { 1295 annotationViewManager.forgetAnnotations(); 1296 fixContainerSize(); 1297 } 1298 }; 1299 1300 /** 1301 * @return {!gui.ZoomHelper} 1302 */ 1303 this.getZoomHelper = function () { 1304 return zoomHelper; 1305 }; 1306 1307 /** 1308 * @param {!number} zoom 1309 * @return {undefined} 1310 */ 1311 this.setZoomLevel = function (zoom) { 1312 zoomHelper.setZoomLevel(zoom); 1313 }; 1314 /** 1315 * @return {!number} 1316 */ 1317 this.getZoomLevel = function () { 1318 return zoomHelper.getZoomLevel(); 1319 }; 1320 /** 1321 * @param {!number} width 1322 * @param {!number} height 1323 * @return {undefined} 1324 */ 1325 this.fitToContainingElement = function (width, height) { 1326 var zoomLevel = zoomHelper.getZoomLevel(), 1327 realWidth = element.offsetWidth / zoomLevel, 1328 realHeight = element.offsetHeight / zoomLevel, 1329 zoom; 1330 1331 zoom = width / realWidth; 1332 if (height / realHeight < zoom) { 1333 zoom = height / realHeight; 1334 } 1335 zoomHelper.setZoomLevel(zoom); 1336 }; 1337 /** 1338 * @param {!number} width 1339 * @return {undefined} 1340 */ 1341 this.fitToWidth = function (width) { 1342 var realWidth = element.offsetWidth / zoomHelper.getZoomLevel(); 1343 zoomHelper.setZoomLevel(width / realWidth); 1344 }; 1345 /** 1346 * @param {!number} width 1347 * @param {!number} height 1348 * @return {undefined} 1349 */ 1350 this.fitSmart = function (width, height) { 1351 var realWidth, realHeight, newScale, 1352 zoomLevel = zoomHelper.getZoomLevel(); 1353 1354 realWidth = element.offsetWidth / zoomLevel; 1355 realHeight = element.offsetHeight / zoomLevel; 1356 1357 newScale = width / realWidth; 1358 if (height !== undefined) { 1359 if (height / realHeight < newScale) { 1360 newScale = height / realHeight; 1361 } 1362 } 1363 1364 zoomHelper.setZoomLevel(Math.min(1.0, newScale)); 1365 }; 1366 /** 1367 * @param {!number} height 1368 * @return {undefined} 1369 */ 1370 this.fitToHeight = function (height) { 1371 var realHeight = element.offsetHeight / zoomHelper.getZoomLevel(); 1372 zoomHelper.setZoomLevel(height / realHeight); 1373 }; 1374 /** 1375 * @return {undefined} 1376 */ 1377 this.showFirstPage = function () { 1378 pageSwitcher.showFirstPage(); 1379 }; 1380 /** 1381 * @return {undefined} 1382 */ 1383 this.showNextPage = function () { 1384 pageSwitcher.showNextPage(); 1385 }; 1386 /** 1387 * @return {undefined} 1388 */ 1389 this.showPreviousPage = function () { 1390 pageSwitcher.showPreviousPage(); 1391 }; 1392 /** 1393 * @param {!number} n number of the page 1394 * @return {undefined} 1395 */ 1396 this.showPage = function (n) { 1397 pageSwitcher.showPage(n); 1398 fixContainerSize(); 1399 }; 1400 1401 /** 1402 * @return {!HTMLElement} 1403 */ 1404 this.getElement = function () { 1405 return element; 1406 }; 1407 1408 /** 1409 * Add additional css rules for newly inserted draw:frame and draw:image. eg. position, dimensions and background image 1410 * @param {!Element} frame 1411 */ 1412 this.addCssForFrameWithImage = function (frame) { 1413 // TODO: frameid and imageid generation here is better brought in sync with that for the images on loading of a odf file. 1414 var frameName = frame.getAttributeNS(drawns, 'name'), 1415 fc = frame.firstElementChild; 1416 setDrawElementPosition(frameName, frame, 1417 /**@type{!CSSStyleSheet}*/(positioncss.sheet)); 1418 if (fc) { 1419 setImage(frameName + 'img', odfcontainer, fc, 1420 /**@type{!CSSStyleSheet}*/( positioncss.sheet)); 1421 } 1422 }; 1423 /** 1424 * @param {!function(!Error=)} callback, passing an error object in case of error 1425 * @return {undefined} 1426 */ 1427 this.destroy = function(callback) { 1428 var head = /**@type{!HTMLHeadElement}*/(doc.getElementsByTagName('head')[0]), 1429 cleanup = [pageSwitcher.destroy, redrawContainerTask.destroy]; 1430 1431 runtime.clearTimeout(waitingForDoneTimeoutId); 1432 // TODO: anything to clean with annotationViewManager? 1433 if (annotationsPane && annotationsPane.parentNode) { 1434 annotationsPane.parentNode.removeChild(annotationsPane); 1435 } 1436 1437 zoomHelper.destroy(function () { 1438 if (sizer) { 1439 element.removeChild(sizer); 1440 sizer = null; 1441 } 1442 }); 1443 1444 // remove all styles 1445 removeWebODFStyleSheet(webodfcss); 1446 head.removeChild(fontcss); 1447 head.removeChild(stylesxmlcss); 1448 head.removeChild(positioncss); 1449 1450 // TODO: loadingQueue, make sure it is empty 1451 core.Async.destroyAll(cleanup, callback); 1452 }; 1453 1454 function init() { 1455 webodfcss = addWebODFStyleSheet(doc); 1456 pageSwitcher = new PageSwitcher(addStyleSheet(doc)); 1457 fontcss = addStyleSheet(doc); 1458 stylesxmlcss = addStyleSheet(doc); 1459 positioncss = addStyleSheet(doc); 1460 redrawContainerTask = core.Task.createRedrawTask(redrawContainer); 1461 zoomHelper.subscribe(gui.ZoomHelper.signalZoomChanged, fixContainerSize); 1462 } 1463 1464 init(); 1465 }; 1466 }()); 1467