1 /** 2 * This file is part of the smilText parser implemented in JavaScript, 3 * 4 * Copyright (C) 2003-2009 Stichting CWI, 5 * Science Park 123, 1098 XG Amsterdam, The Netherlands. 6 * 7 * smilText parser in JavaScript is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU Lesser General Public License as published by 9 * the Free Software Foundation; either version 2.1 of the License, or 10 * (at your option) any later version. 11 * 12 * smilText parser in JavaScript is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public License 18 * along with smilText parser in JavaScript; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22 /** 23 @name cwi.smilText 24 @namespace Hold all smilText functionalities. 25 @version 1.0 26 @author <a href="mailto:rlaiola@cwi.nl">Rodrigo Laiola Guimaraes</a> 27 */ 28 Namespace("cwi.smilText"); 29 30 31 32 33 34 /* Solve library dependencies */ 35 Import("cwi.adt.Hashtable"); 36 Import("cwi.adt.PriorityQueue"); 37 Import("cwi.adt.DoubleLinkedList"); 38 Import("cwi.util"); 39 Import("cwi.smilText.Time.Playable"); 40 41 /** 42 * Implementation of a SmilText Document. 43 * @constructor 44 * @augments cwi.smilText.Time.Playable 45 * @param {string} region the id of the region where the smilText document will be rendered. 46 * @author <a href="mailto:rlaiola@cwi.nl">Rodrigo Laiola Guimaraes</a> 47 */ 48 cwi.smilText.STDocument = function(region) 49 { 50 var self = JSINER.extend(this, "Playable"); 51 52 /** 53 * Variables 54 * @private 55 */ 56 this.layout = region; // Hold the rendering region 57 this.stylingHash = new Hashtable(); // Hold styling attributes 58 59 this.translateFunc = null; // Keep translation function. 60 this.dictionary = null; // Keep translated sentences. 61 62 this.hasFillSemantics = true; // Keep the Fill semantics associated to the display of text when duration ends. 63 64 this.timingQueue = new PriorityQueue(); // Keep a priority queue of timing occurrencies. 65 this.doneQueue = new PriorityQueue(); // Keep a priority queue of occurred statements. 66 67 return self; 68 } 69 70 /** 71 * Return the value of a stored style attribute. 72 * @param {string} layout the region id 73 * @param {string} attr the attribute name 74 * @return {string} val the attribute value 75 */ 76 cwi.smilText.STDocument.prototype.getStoredAttribute = function(layout, attr) 77 { 78 return this.stylingHash.get(layout + attr); 79 } 80 81 /** 82 * Save a style attribute of a given layout element. 83 * @param {string} layout the region id 84 * @param {string} attr the attribute name 85 * @param {string} val the attribute value 86 */ 87 cwi.smilText.STDocument.prototype.updateAttribute = function(layout, attr, val) 88 { 89 this.stylingHash.put(layout + attr, val); 90 } 91 92 /** 93 * Transfer the properties from a given style to a layout. 94 * @private 95 */ 96 cwi.smilText.STDocument.prototype.applyStyle = function(typeLayout, id, style) 97 { 98 switch(typeLayout) 99 { 100 case 'smiltext': 101 this.updateAttribute(id, 'dur', this.getStoredAttribute(style, 'dur')); 102 this.updateAttribute(id, 'width', this.getStoredAttribute(style, 'width')); 103 this.updateAttribute(id, 'height', this.getStoredAttribute(style, 'height')); 104 case 'textstyle': 105 this.updateAttribute(id, 'textmode', this.getStoredAttribute(style, 'textmode')); 106 this.updateAttribute(id, 'textplace', this.getStoredAttribute(style, 'textplace')); 107 this.updateAttribute(id, 'textconceal', this.getStoredAttribute(style, 'textconceal')); 108 this.updateAttribute(id, 'textrate', this.getStoredAttribute(style, 'textrate')); 109 case 'div': 110 this.updateAttribute(id, 'textalign', this.getStoredAttribute(style, 'textalign')); 111 case 'p': 112 this.updateAttribute(id, 'xml:space', this.getStoredAttribute(style, 'xml:space')); 113 this.updateAttribute(id, 'textwrapoption', this.getStoredAttribute(style, 'textwrapoption')); 114 this.updateAttribute(id, 'textwritingmode', this.getStoredAttribute(style, 'textwritingmode')); 115 case 'span': 116 this.updateAttribute(id, 'textbackgroundcolor', this.getStoredAttribute(style, 'textbackgroundcolor')); 117 this.updateAttribute(id, 'textcolor', this.getStoredAttribute(style, 'textcolor')); 118 this.updateAttribute(id, 'textfontfamily', this.getStoredAttribute(style, 'textfontfamily')); 119 this.updateAttribute(id, 'textfontsize', this.getStoredAttribute(style, 'textfontsize')); 120 this.updateAttribute(id, 'textfontstyle', this.getStoredAttribute(style, 'textfontstyle')); 121 this.updateAttribute(id, 'textfontweight', this.getStoredAttribute(style, 'textfontweight')); 122 this.updateAttribute(id, 'textstyle', this.getStoredAttribute(style, 'textstyle')); 123 124 if (typeLayout != 'span' && typeLayout != 'textstyle') 125 this.updateAttribute(id, 'textdirection', this.getStoredAttribute(style, 'textdirection')); 126 } 127 } 128 129 /** 130 * Return the style properties of a given layout (xml). 131 * @private 132 */ 133 cwi.smilText.STDocument.prototype.getStyleXML = function(typeLayout, id) 134 { 135 var str = ""; 136 137 switch(typeLayout) 138 { 139 case 'smiltext': 140 if (this.getStoredAttribute(id, 'dur')) 141 str += " dur=\'" + this.getStoredAttribute(id, 'dur') + "\'"; 142 if (this.getStoredAttribute(id, 'width')) 143 str += " width=\'" + this.getStoredAttribute(id, 'width') + "px\'"; 144 if (this.getStoredAttribute(id, 'height')) 145 str += " height=\'" + this.getStoredAttribute(id, 'height') + "px\'"; 146 case 'textstyle': 147 if (this.getStoredAttribute(id, 'textmode')) 148 str += " textMode=\'" + this.getStoredAttribute(id, 'textmode') + "\'"; 149 if (this.getStoredAttribute(id, 'textplace')) 150 str += " textPlace=\'" + this.getStoredAttribute(id, 'textplace') + "\'"; 151 if (this.getStoredAttribute(id, 'textconceal')) 152 str += " textConceal=\'" + this.getStoredAttribute(id, 'textconceal') + "\'"; 153 if (this.getStoredAttribute(id, 'textrate')) 154 str += " textRate=\'" + this.getStoredAttribute(id, 'textrate') + "\'"; 155 case 'div': 156 if (this.getStoredAttribute(id, 'textalign')) 157 str += " textAlign=\'" + this.getStoredAttribute(id, 'textalign') + "\'"; 158 case 'p': 159 if (this.getStoredAttribute(id, 'xml:space')) 160 str += " xml:space=\'" + this.getStoredAttribute(id, 'xml:space') + "\'"; 161 if (this.getStoredAttribute(id, 'textwrapoption')) 162 str += " textWrapOption=\'" + this.getStoredAttribute(id, 'textwrapoption') + "\'"; 163 if (this.getStoredAttribute(id, 'textwritingmode')) 164 str += " textWritingMode=\'" + this.getStoredAttribute(id, 'textwritingmode') + "\'"; 165 case 'span': 166 if (this.getStoredAttribute(id, 'textbackgroundcolor')) 167 str += " textBackgroundColor=\'" + this.getStoredAttribute(id, 'textbackgroundcolor') + "\'"; 168 if (this.getStoredAttribute(id, 'textcolor')) 169 str += " textColor=\'" + this.getStoredAttribute(id, 'textcolor') + "\'"; 170 if (this.getStoredAttribute(id, 'textfontfamily')) 171 str += " textFontFamily=\'" + this.getStoredAttribute(id, 'textfontfamily') + "\'"; 172 if (this.getStoredAttribute(id, 'textfontsize')) 173 str += " textFontSize=\'" + this.getStoredAttribute(id, 'textfontsize') + "\'"; 174 if (this.getStoredAttribute(id, 'textfontstyle')) 175 str += " textFontStyle=\'" + this.getStoredAttribute(id, 'textfontstyle') + "\'"; 176 if (this.getStoredAttribute(id, 'textfontweight')) 177 str += " textFontWeight=\'" + this.getStoredAttribute(id, 'textfontweight') + "\'"; 178 if (this.getStoredAttribute(id, 'textstyle')) 179 str += " textStyle=\'" + this.getStoredAttribute(id, 'textstyle') + "\'"; 180 181 if (typeLayout != 'span' && typeLayout != 'textstyle') { 182 if (this.getStoredAttribute(id, 'textdirection')) 183 str += " textDirection=\'" + this.getStoredAttribute(id, 'textdirection') + "\'"; 184 } 185 } 186 187 return str; 188 } 189 190 /** 191 * Return the string representation of the smilText document. 192 * @return {string} 193 */ 194 cwi.smilText.STDocument.prototype.getXML = function() 195 { 196 var header = "<smilText>"; 197 var str = ""; 198 var endTag = "\n</smilText>"; 199 var smilTextFound = false; 200 var lastType = -1; // 0: tag; 1: text 201 var currentTime = 0; 202 var restorePlay = false; 203 var dur = null; // explicit duration of the smiltext container 204 205 if (this.isPlaying()) { 206 this.pause(); 207 restorePlay = true; 208 } 209 210 var tempQ = new PriorityQueue(); 211 while (this.doneQueue && !this.doneQueue.isEmpty()) 212 { 213 var t = this.doneQueue.lookFirst(); 214 var st = this.doneQueue.pop(); 215 216 switch(st[0]) 217 { 218 case cwi.smilText.Render.appendContainer: 219 var id = st[1][1].charAt(0); 220 if (st[1][1].length > 8 && st[1][1].substring(0,8) == 'smiltext') { 221 id = 'smiltext'; 222 smilTextFound = true; 223 if (st[1][1] != '') 224 header = "<smilText id=\'" + st[1][1] + "\' " + this.getStyleXML("smiltext", st[1][1]) + " >"; 225 } 226 227 //if (smilTextFound) { 228 switch(id) 229 { 230 case 'smiltext': // smiltext tag 231 //str += "<smilText "; 232 //endTag = "\n</smilText>" + endTag; 233 break; 234 case 'd': // div 235 str += "\n<div "; 236 str += "id=\'" + st[1][1] + "\' " + this.getStyleXML("div", st[1][1]) + " >"; 237 endTag = "\n</div>" + endTag; 238 break; 239 case 'p': // p 240 str += "\n<p "; 241 str += "id=\'" + st[1][1] + "\' " + this.getStyleXML("p", st[1][1]) + " >"; 242 endTag = "\n</p>" + endTag; 243 break; 244 case 's': // span 245 str += "\n<span "; 246 str += "id=\'" + st[1][1] + "\' " + this.getStyleXML("span", st[1][1]) + " >"; 247 endTag = "\n</span>" + endTag; 248 break; 249 // } 250 251 lastType = 0; 252 } 253 254 break; 255 case cwi.smilText.Render.appendLineBreak: 256 //if (smilTextFound) { 257 str += "\n<br/>"; 258 lastType = 0; 259 //} 260 break; 261 case cwi.smilText.Render.appendText: 262 if (t != currentTime) { 263 //if (smilTextFound && t != currentTime) { 264 currentTime = t; 265 str += "\n<tev begin=\'" + t/1000.0 + "s\' />"; 266 lastType = 0; 267 } 268 269 //if (smilTextFound) { 270 if (lastType == 1) 271 str += " "; // Add white space 272 else str += "\n"; 273 str += st[1][1]; 274 lastType = 1; 275 //} 276 break; 277 case cwi.smilText.Render.clearLayout: 278 //if (smilTextFound) { 279 str += "\n<clear begin=\'" + t/1000.0 + "s\' />"; 280 lastType = 0; 281 currentTime = t; 282 //} 283 break; 284 case cwi.smilText.Render.closeContainer: 285 // skipped: got at getStyleXML 286 //dur = t; 287 break; 288 } 289 290 tempQ.push(t, st); 291 } 292 this.doneQueue = tempQ; 293 tempQ = new PriorityQueue(); 294 295 while (this.timingQueue && !this.timingQueue.isEmpty()) 296 { 297 var t = this.timingQueue.lookFirst(); 298 var st = this.timingQueue.pop(); 299 300 switch(st[0]) 301 { 302 case cwi.smilText.Render.appendContainer: 303 var id = st[1][1].charAt(0); 304 if (!smilTextFound && st[1][1].length > 8 && st[1][1].substring(0,8) == 'smiltext') { 305 id = 'smiltext'; 306 smilTextFound = true; 307 if (st[1][1] != '') 308 header = "<smilText id=\'" + st[1][1] + "\' " + this.getStyleXML("smiltext", st[1][1]) + " >"; 309 } 310 311 //if (smilTextFound) { 312 switch(id) 313 { 314 case 'smiltext': // smiltext tag 315 //str += "<smilText "; 316 //endTag = "\n</smilText>" + endTag; 317 break; 318 case 'd': // div 319 str += "\n<div "; 320 str += "id=\'" + st[1][1] + "\' " + this.getStyleXML("div", st[1][1]) + " >"; 321 endTag = "\n</div>" + endTag; 322 break; 323 case 'p': // p 324 str += "\n<p "; 325 str += "id=\'" + st[1][1] + "\' " + this.getStyleXML("p", st[1][1]) + " >"; 326 endTag = "\n</p>" + endTag; 327 break; 328 case 's': // span 329 str += "\n<span "; 330 str += "id=\'" + st[1][1] + "\' " + this.getStyleXML("span", st[1][1]) + " >"; 331 endTag = "\n</span>" + endTag; 332 break; 333 } 334 335 lastType = 0; 336 //} 337 338 break; 339 case cwi.smilText.Render.appendLineBreak: 340 //if (smilTextFound) { 341 str += "\n<br/>"; 342 lastType = 0; 343 //} 344 break; 345 case cwi.smilText.Render.appendText: 346 if (t != currentTime) { 347 //if (smilTextFound && t != currentTime) { 348 currentTime = t; 349 str += "\n<tev begin=\'" + t/1000.0 + "s\' />"; 350 lastType = 0; 351 } 352 353 //if (smilTextFound) { 354 if (lastType == 1) 355 str += " "; // Add white space 356 else str += "\n"; 357 str += st[1][1]; 358 lastType = 1; 359 //} 360 break; 361 case cwi.smilText.Render.clearLayout: 362 //if (smilTextFound) { 363 str += "\n<clear begin=\'" + t/1000.0 + "s\' />"; 364 lastType = 0; 365 currentTime = t; 366 //} 367 break; 368 case cwi.smilText.Render.closeContainer: 369 // skipped: got at getStyleXML 370 //dur = t; 371 break; 372 } 373 // st[0] : function | st[1] : array of arguments 374 //str = st[0] + st[1]; 375 376 tempQ.push(t, st); 377 } 378 this.timingQueue = tempQ; 379 380 if (dur) { 381 // it has an explicit duration 382 header = header.substring(0, header.length - 2) + " dur=\'" + (dur/1000.0) + "s\' >"; 383 } 384 str = header + str; 385 386 // Issue end tag 387 if (endTag) { 388 str += endTag; 389 } 390 391 if (restorePlay) { 392 this.play(); 393 } 394 395 return str; 396 } 397 398 /****************************************************************** 399 * Time Engine 400 ******************************************************************/ 401 402 /** 403 * Add a rendering primitive to the scheduler. 404 * @param {string} entry The smilText rendering primitive. 405 * @param {Array[]} args An array of arguments to the rendering primitive. 406 * @param {integer} t The occurring time (in milliseconds) relative to the smilText container. 407 * @see cwi.smilText.Render.appendContainer 408 * @see cwi.smilText.Render.appendLineBreak 409 * @see cwi.smilText.Render.appendText 410 * @see cwi.smilText.Render.clearLayout 411 * @see cwi.smilText.Render.closeContainer 412 */ 413 cwi.smilText.STDocument.prototype.addTimingEntry = function(func, args, t) 414 { 415 if (func == cwi.smilText.Render.scrollticker) 416 this.motionEntry = new Array(0, func, args); 417 else this.timingQueue.push(t, new Array(func, args)); 418 } 419 420 /** 421 * Remove all rendering primitives from the scheduler. 422 * @see cwi.smilText.Render.appendContainer 423 * @see cwi.smilText.Render.appendLineBreak 424 * @see cwi.smilText.Render.appendText 425 * @see cwi.smilText.Render.clearLayout 426 * @see cwi.smilText.Render.closeContainer 427 */ 428 cwi.smilText.STDocument.prototype.clearTimingEntries = function() 429 { 430 this.doneQueue.clear(); 431 this.timingQueue.clear(); 432 this.motionEntry = null; 433 } 434 435 /** 436 * Return an array containing the timing entries. Each element of this array is an array containing: 437 * [the occurency time, the rendering function, array of parameters to the rendering function]. 438 * @return {Array[ [integer; string; args] ]} 439 * @see cwi.smilText.Render.appendContainer 440 * @see cwi.smilText.Render.appendLineBreak 441 * @see cwi.smilText.Render.appendText 442 * @see cwi.smilText.Render.clearLayout 443 * @see cwi.smilText.Render.closeContainer 444 */ 445 cwi.smilText.STDocument.prototype.getTimingEntries = function() 446 { 447 var l = new DoubleLinkedList(); 448 449 this.pause(); 450 451 var tempQ = new PriorityQueue(); 452 while (this.doneQueue && !this.doneQueue.isEmpty()) 453 { 454 var t = this.doneQueue.lookFirst(); 455 var st = this.doneQueue.pop(); 456 457 l.insert(new Array(t, st)); 458 459 tempQ.push(t, st); 460 } 461 this.doneQueue = tempQ; 462 tempQ = new PriorityQueue(); 463 464 while (this.timingQueue && !this.timingQueue.isEmpty()) 465 { 466 var t = this.timingQueue.lookFirst(); 467 var st = this.timingQueue.pop(); 468 469 l.insert(new Array(t, st)); 470 471 tempQ.push(t, st); 472 } 473 this.timingQueue = tempQ; 474 475 return l; 476 } 477 478 /** 479 * Update the current time of the smilText document. 480 * @param {integer} t new time moment (in milliseconds). 481 */ 482 cwi.smilText.STDocument.prototype.setTime = function(t) 483 { 484 cwi.smilText.STDocument.superClass.setTime.call(this, t); 485 486 this.processTimingQueue(); 487 } 488 489 /** 490 * Process all entries at once. 491 * @private 492 */ 493 cwi.smilText.STDocument.prototype.batchAllEntries = function() 494 { 495 // Return to the beginnig. 496 this.mergeTimingQueues(); 497 498 while(true) 499 { 500 // Process command. 501 if (!this.timingQueue.isEmpty()) { 502 503 var t = this.timingQueue.lookFirst(); 504 var st = this.timingQueue.pop(); 505 506 // st[0] : function | st[1] : array of arguments 507 st[0].apply(this, st[1]); 508 509 this.doneQueue.push(t, st); 510 } 511 else break; 512 } 513 514 // Return to the beginnig again. 515 this.mergeTimingQueues(); 516 } 517 518 /** 519 * Process the timing queue based on a given time moment. 520 * @private 521 */ 522 cwi.smilText.STDocument.prototype.processTimingQueue = function() 523 { 524 while(true) 525 { 526 if (this.motionEntry && this.motionEntry[0] <= this.timeNow && this.isPlaying()) { 527 // this.motionEntry[1] : function | this.motionEntry[2] : array of arguments 528 this.motionEntry[1].apply(this, this.motionEntry[2]); 529 this.motionEntry[0] =+ 50; 530 } 531 532 // Process command. 533 if (this.timingQueue && !this.timingQueue.isEmpty()) { 534 var t = this.timingQueue.lookFirst(); 535 536 if (t <= this.timeNow) { 537 538 var st = this.timingQueue.pop(); 539 540 // st[0] : function | st[1] : array of arguments 541 st[0].apply(this, st[1]); 542 543 this.doneQueue.push(t, st); 544 545 // If it is animated the document never ends. 546 if (this.timingQueue.isEmpty() && !this.motionEntry) { 547 // notify event listeners: this was the last entry 548 cwi.smilText.STDocument.superClass.fireEvent.call(this, cwi.smilText.Time.EVENT_END); 549 } 550 } else break; 551 } 552 else break; 553 } 554 } 555 556 /** 557 * @private 558 */ 559 cwi.smilText.STDocument.prototype.mergeTimingQueues = function() 560 { 561 // Quick merging 562 this.doneQueue.merge(this.timingQueue); 563 this.timingQueue = this.doneQueue; 564 this.doneQueue = new PriorityQueue(); 565 } 566 567 /** 568 * Perform the seek operation to a given time moment. 569 * @param {integer} t The desired time instant (in milliseconds). 570 */ 571 cwi.smilText.STDocument.prototype.seekTo = function(t) 572 { 573 cwi.smilText.STDocument.superClass.seekTo.call(this, t); 574 575 // merge queues 576 this.mergeTimingQueues(); 577 } 578 579 /** 580 * Stop the smilText object. 581 */ 582 cwi.smilText.STDocument.prototype.stop = function() 583 { 584 cwi.smilText.STDocument.superClass.stop.call(this); 585 586 cwi.smilText.Render.clearLayout(this, this.layout, true); 587 if (this.motionEntry) 588 this.motionEntry[0] = 0; // reset time 589 } 590 591 /****************************************************************** 592 * Language Functions 593 ******************************************************************/ 594 595 /** 596 * Return the translation of a sentence. 597 * @param {string} s The original sentence. 598 * @return {string} the translated equivalent. 599 */ 600 cwi.smilText.STDocument.prototype.getTranslatedSentence = function(s) 601 { 602 if (this.dictionary) { 603 var t = this.dictionary.get(s); 604 if (!t || t == "") { 605 this.updateDictionary(s, s); 606 if (this.translateFunc) this.translateFunc(s); 607 return s; 608 } 609 return t; 610 } 611 return s; 612 } 613 614 /** 615 * Save a sentence and its translation. 616 * @param {string} k The original sentence. 617 * @param {string} val The translation in some language. 618 */ 619 cwi.smilText.STDocument.prototype.updateDictionary = function(k, val) 620 { 621 if (k && val) 622 this.dictionary.put(k, val); 623 } 624 625 /** 626 * Set the translation function. 627 * @param {string} func A translation function. 628 */ 629 cwi.smilText.STDocument.prototype.setTranslateFunction = function(func) 630 { 631 this.translateFunc = func; 632 this.dictionary = new Hashtable(); 633 if (func) this.batchAllEntries(); 634 } 635 636 637 638 639 640 /** 641 @name cwi.smilText.Parser 642 @namespace Hold all smilText parsing functionalities. 643 @version 1.0 644 @author <a href="mailto:rlaiola@cwi.nl">Rodrigo Laiola Guimaraes</a> 645 */ 646 Namespace("cwi.smilText.Parser"); 647 648 /* Solve library dependency */ 649 Import("cwi.smilText.STDocument"); 650 651 /** 652 * Parse the html source in order to find smilText tags, and render them 653 * into the respective enclosing regions. 654 * @return {Array[cwi.smilText.STDocument]} 655 * @see cwi.smilText.STDocument 656 */ 657 cwi.smilText.Parser.parseHTML = function() 658 { 659 var docs = new Array(); 660 var doc = new STDocument(null); // created here just to hold styles' hash 661 662 // Read textStyle elements. 663 var style = document.getElementsByTagName("textstyle"); 664 665 for (var k=0; k<style.length; k++) 666 { 667 var id; 668 669 for( var j = 0; j < style[k].attributes.length; j++ ) { 670 if ( style[k].attributes[j].nodeName.toLowerCase() == 'id' ) { 671 id = style[k].attributes[j].nodeValue; 672 break; 673 } 674 } 675 676 // check whether the attribute exists 677 if (id == undefined) { 678 alert("Error: a textStyle element must have an ID!"); 679 } else { 680 cwi.smilText.Parser.parseAttributes(doc, id, style[k]); 681 } 682 } 683 684 // Process smilText tags 685 var ti; 686 var t=document.getElementsByTagName("smiltext"); 687 for(ti=0; ti<t.length; ti++) 688 { 689 var dad = t[ti].parentNode; 690 var layoutId; 691 // check whether the attribute exists 692 for( var i = 0; i < dad.attributes.length; i++ ) { 693 if ( dad.attributes[i].nodeName.toLowerCase() == 'id' ) { 694 layoutId = dad.attributes[i].nodeValue; 695 break; 696 } 697 } 698 699 if (layoutId == undefined) { 700 alert("Error: All smilText containers must have an ID!"); 701 } 702 703 // Trick code: process the smilText first, and then dont display specification in the html file. 704 var stdoc = new STDocument(layoutId); 705 stdoc.stylingHash = doc.stylingHash; // Share styles 706 cwi.smilText.Parser.parseSmilTextElem(stdoc, t[ti], layoutId, 0, undefined); 707 docs[ti] = stdoc; 708 709 // Keep smilText tag inside a hidden DIV 710 var wrapper = document.createElement('div'); 711 wrapper.appendChild(t[ti].cloneNode(false)); 712 t[ti].parentNode.replaceChild(wrapper, t[ti]); 713 wrapper.style.visibility='hidden'; 714 } 715 716 return docs; 717 } 718 719 /** 720 * Parse a smilText file and return a smilText object. 721 * @param {string} xmlFile The path of the file containing the smilText specification. 722 * @param {string} region The id of the rendering region. 723 * @return {cwi.smilText.STDocument} 724 * @see cwi.smilText.STDocument 725 */ 726 cwi.smilText.Parser.parseFile = function(xmlFile, region) 727 { 728 var xmlDoc; 729 730 if (document.getElementById(region) == null) 731 alert("Error: smilText content must be encapsulated in a container. Region \'" + region + "\' not found!"); 732 733 // FF, Opera, Safari, others 734 if (document.implementation && document.implementation.createDocument) 735 { 736 xmlDoc = document.implementation.createDocument("", "", null); 737 xmlDoc.async = false; 738 var loaded = xmlDoc.load(xmlFile); 739 if (loaded) { 740 return cwi.smilText.Parser.parseDoc(xmlDoc, region); 741 } else return null; 742 } 743 // IE 744 else if (window.ActiveXObject) 745 { 746 xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); 747 xmlDoc.async = false; 748 xmlDoc.preserveWhiteSpace = true; 749 //xmlDoc.onreadystatechange = function () {if (xmlDoc.readyState == 4) cwi.smilText.Parser.parseDoc(xmlDoc, region); else return null;}; 750 xmlDoc.load(xmlFile); 751 // Laiola: Added to make IE return a document. 752 //return cwi.smilText.Parser.parseDoc(xmlDoc, region); 753 while(xmlDoc.readyState != 4); 754 return cwi.smilText.Parser.parseDoc(xmlDoc, region); 755 } 756 else 757 { 758 alert('Your browser can\'t handle this script'); 759 return null; 760 } 761 } 762 763 /** 764 * Parse an XML string and return a smilText object. 765 * @param {string} xmlStr The smilText string. 766 * @param {string} region The id of the rendering region. 767 * @return {cwi.smilText.STDocument} 768 * @see cwi.smilText.STDocument 769 */ 770 cwi.smilText.Parser.parseString = function(xmlStr, region) 771 { 772 var xmlDoc; 773 774 if (document.getElementById(region) == null) 775 alert("Error: smilText content must be encapsulated in a container. Region \'" + region + "\' not found!"); 776 777 try //Internet Explorer 778 { 779 xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); 780 xmlDoc.async = "false"; 781 xmlDoc.loadXML(xmlStr); 782 } 783 catch(e) 784 { 785 try //Firefox, Mozilla, Opera, etc. 786 { 787 var parser = new DOMParser(); 788 xmlDoc = parser.parseFromString(xmlStr,"text/xml"); 789 } 790 catch(e) { 791 alert(e.message); 792 return null; 793 } 794 } 795 796 return cwi.smilText.Parser.parseDoc(xmlDoc, region); 797 } 798 799 /** 800 * Parse a HMTL element and return a smilText object. 801 * @param {string} element The HTML element containing the smilText specification. 802 * @param {string} region The id of the rendering region. 803 * @return {cwi.smilText.STDocument} 804 * @see cwi.smilText.STDocument 805 */ 806 cwi.smilText.Parser.parseHTMLElement = function(element, region) 807 { 808 var stdoc = new STDocument(region); 809 810 if (element) { 811 cwi.smilText.Parser.parseSmilTextElem(stdoc, element, region, 0, undefined); 812 } 813 814 return stdoc; 815 } 816 817 /** 818 * Parse a given xml document. So far, the tags are case sensitive. 819 * @private 820 */ 821 cwi.smilText.Parser.parseDoc = function(xmlDoc, region) 822 { 823 var stdoc = new STDocument(region); 824 825 // Read textStyle elements 826 var style = xmlDoc.getElementsByTagName('textStyle'); 827 828 for (var i=0;i<style.length;i++) 829 { 830 var id; 831 832 for( var j = 0; j < style[i].attributes.length; j++ ) { 833 if ( style[i].attributes[j].nodeName.toLowerCase() == 'id' ) { 834 id = style[i].attributes[j].nodeValue; 835 break; 836 } 837 } 838 839 // It should be inside a textStyling tag 840 if (style[i].parentNode.nodeName.toLowerCase() != 'textstyling') { 841 alert("Warning: a textStyle element should be inside a textStyling tag"); 842 } 843 844 // check whether the attribute exists 845 if (id == undefined) { 846 alert("Error: a textStyle element must have an ID!"); 847 } else { 848 cwi.smilText.Parser.parseAttributes(stdoc, id, style[i]); 849 } 850 } 851 852 // Process smilText elements 853 var x = xmlDoc.getElementsByTagName('smilText'); 854 855 for (var i=0;i<x.length;i++) 856 { 857 cwi.smilText.Parser.parseSmilTextElem(stdoc, x[i], region, 0, undefined); 858 } 859 860 return stdoc; 861 } 862 863 /** 864 * Parse a smilText elem. 865 * @private 866 */ 867 cwi.smilText.Parser.parseSmilTextElem = function(doc, elem, layout, currentTime, dur) 868 { 869 var setEnd = false; 870 871 // Make sure that a tag will be inserted. 872 var force; 873 if (dur == undefined || currentTime <= dur) 874 force = true; 875 else force = false; 876 877 // Put smilText in a DIV 878 if (elem.nodeName.toLowerCase() == 'smiltext' || elem.nodeName.toLowerCase() == 'smil:smiltext') { 879 var smiltextID = "smiltext" + Math.random()*10000; 880 881 // Clear parent container before hand. 882 doc.addTimingEntry(cwi.smilText.Render.clearLayout, new Array(doc, layout, force), currentTime * 1000); 883 884 cwi.smilText.Parser.parseAttributes(doc, smiltextID, elem); 885 doc.addTimingEntry(cwi.smilText.Render.appendContainer, new Array(doc, smiltextID, layout, force), currentTime * 1000); 886 layout = smiltextID; 887 888 if (doc.getStoredAttribute(smiltextID, 'dur') != undefined && doc.getStoredAttribute(smiltextID, 'dur') != null) 889 { 890 dur = parseFloat(doc.getStoredAttribute(smiltextID, 'dur')); 891 doc.addTimingEntry(cwi.smilText.Render.closeContainer, new Array(doc), dur * 1000); 892 } 893 } 894 895 for (var j=0;j<elem.childNodes.length;j++) 896 { 897 898 /* string */ 899 if (elem.childNodes[j].nodeType != 1) { 900 901 var str; 902 903 // follow xml:space 904 if (doc.getStoredAttribute(layout, 'xml:space') == 'preserve') 905 { 906 str = elem.childNodes[j].nodeValue; 907 908 var lines = str.split(/\n/g); // line breaks become <br/> 909 for (var z = 0; z < lines.length; z++) { 910 //lines[z] = lines[z].replace(new RegExp( "\\'", "g" ), "\\'"); // replace single quotation mark 911 doc.addTimingEntry(cwi.smilText.Render.appendText, new Array(doc, lines[z], layout, force), currentTime * 1000); 912 913 if (doc.getStoredAttribute(layout, 'textmode') != "crawl") { 914 if (z != lines.length-1) doc.addTimingEntry(cwi.smilText.Render.appendLineBreak, new Array(doc, layout, force), currentTime * 1000); 915 } 916 } 917 } 918 else { 919 str = (cwi.util.trim(elem.childNodes[j].nodeValue)); 920 str = str.replace(new RegExp( "\\n", "g" ), ""); // line breaks discarded. 921 str = str.replace(new RegExp( "\\t", "g" ), ""); // tabs discarded. 922 str = str.replace(new RegExp( "\ +", "g" ), " "); // extra white-spaces discarded. 923 //str = str.replace(new RegExp( "\\'", "g" ), "\\'"); // replace single quotation mark 924 925 /* test if non-blank */ 926 if (str.length != 0) { 927 doc.addTimingEntry(cwi.smilText.Render.appendText, new Array(doc, str, layout, force), currentTime * 1000); 928 } 929 else{ 930 // Blank string 931 } 932 933 } 934 } 935 936 /* smilText tags */ 937 else { 938 var tag = elem.childNodes[j].nodeName.toLowerCase(); 939 940 switch(tag) 941 { 942 943 /********************/ 944 /* Parse BasicText Module 945 /********************/ 946 947 case 'smil:tev': 948 case 'tev': 949 var b = null; 950 var n = null; 951 952 // All timing markup to be ignored according to text motion specification. 953 if ( (doc.getStoredAttribute(layout, 'textmode') == "crawl" || 954 doc.getStoredAttribute(layout, 'textmode') == "scroll" || 955 doc.getStoredAttribute(layout, 'textmode') == "jump") && 956 (doc.getStoredAttribute(layout, 'textrate') == null || 957 doc.getStoredAttribute(layout, 'textrate') == undefined || 958 doc.getStoredAttribute(layout, 'textrate') == "auto") ) { 959 break; 960 } 961 962 // check whether the attribute exists before setting a value 963 for( var i = 0; i < elem.childNodes[j].attributes.length; i++ ) { 964 if ( elem.childNodes[j].attributes[i].nodeName.toLowerCase() == 'begin' ) { 965 b = parseFloat(elem.childNodes[j].attributes[i].nodeValue); 966 break; 967 } 968 969 else if ( elem.childNodes[j].attributes[i].nodeName.toLowerCase() == 'next' ) { 970 n = parseFloat(elem.childNodes[j].attributes[i].nodeValue); 971 break; 972 } 973 } 974 975 // test begin and next attributes. 976 if (n != null){ 977 currentTime += n; 978 } 979 else if (b != null){ 980 // test whether the begin time has elapsed. 981 if (b > currentTime) 982 currentTime = b; 983 } 984 985 // Update force variable 986 if (dur == undefined || currentTime <= dur) 987 force = true; 988 else force = false; 989 990 // Apply append attribute. 991 if (doc.getStoredAttribute(layout, 'textmode') == "replace") { 992 doc.addTimingEntry(cwi.smilText.Render.clearLayout, new Array(doc, layout, force), currentTime * 1000); 993 } 994 995 break; 996 case 'smil:clear': 997 case 'clear': 998 var b = null; 999 var n = null; 1000 1001 // All timing markup to be ignored according to text motion specification. 1002 if ( (doc.getStoredAttribute(layout, 'textmode') == "crawl" || 1003 doc.getStoredAttribute(layout, 'textmode') == "scroll" || 1004 doc.getStoredAttribute(layout, 'textmode') == "jump") && 1005 (doc.getStoredAttribute(layout, 'textrate') == null || 1006 doc.getStoredAttribute(layout, 'textrate') == undefined || 1007 doc.getStoredAttribute(layout, 'textrate') == "auto") ) { 1008 break; 1009 } 1010 1011 // check whether the attribute exists before setting a value 1012 for( var i = 0; i < elem.childNodes[j].attributes.length; i++ ) { 1013 if ( elem.childNodes[j].attributes[i].nodeName.toLowerCase() == 'begin' ) { 1014 b = parseFloat(elem.childNodes[j].attributes[i].nodeValue); 1015 break; 1016 } 1017 1018 else if ( elem.childNodes[j].attributes[i].nodeName.toLowerCase() == 'next' ) { 1019 n = parseFloat(elem.childNodes[j].attributes[i].nodeValue); 1020 break; 1021 } 1022 } 1023 1024 // test begin and next attributes. 1025 if (n != null){ 1026 currentTime += n; 1027 } 1028 else if (b != null){ 1029 // test whether the begin time has elapsed. 1030 if (b > currentTime) 1031 currentTime = b; 1032 } 1033 1034 // Update force variable 1035 if (dur == undefined || currentTime <= dur) 1036 force = true; 1037 else force = false; 1038 1039 // Clear container. 1040 doc.addTimingEntry(cwi.smilText.Render.clearLayout, new Array(doc, layout, force), currentTime * 1000); 1041 1042 break; 1043 case 'smil:br': 1044 case 'br': 1045 if (doc.getStoredAttribute(layout, 'textmode') != "crawl") { 1046 doc.addTimingEntry(cwi.smilText.Render.appendLineBreak, new Array(doc, layout, force), currentTime * 1000); 1047 } 1048 break; 1049 1050 /********************/ 1051 /* Parse TextStyling Module 1052 /********************/ 1053 1054 case 'smil:div': 1055 case 'div': 1056 // check whether attributes exist 1057 1058 // a new div will be created to render the content 1059 var divID = "div" + Math.random()*10000; 1060 cwi.smilText.Parser.parseAttributes(doc, divID, elem.childNodes[j]); 1061 1062 // Setup xml:space 1063 if (doc.getStoredAttribute(divID, 'xml:space') == undefined || doc.getStoredAttribute(divID, 'xml:space') == null || 1064 (doc.getStoredAttribute(divID, 'xml:space') != 'default' && doc.getStoredAttribute(divID, 'xml:space') != 'preserve')) { 1065 if (doc.getStoredAttribute(layout, 'xml:space') != undefined && doc.getStoredAttribute(layout, 'xml:space') != null && 1066 (doc.getStoredAttribute(layout, 'xml:space') == 'default' || doc.getStoredAttribute(layout, 'xml:space') == 'preserve')) 1067 { 1068 doc.updateAttribute(divID, 'xml:space', doc.getStoredAttribute(layout, 'xml:space')); 1069 } else doc.updateAttribute(divID, 'xml:space', 'default'); 1070 } 1071 1072 doc.updateAttribute(divID, 'textmode', doc.getStoredAttribute(layout, 'textmode')); 1073 doc.updateAttribute(divID, 'textrate', doc.getStoredAttribute(layout, 'textrate')); 1074 doc.addTimingEntry(cwi.smilText.Render.appendContainer, new Array(doc, divID, layout, force), currentTime * 1000); 1075 1076 currentTime = cwi.smilText.Parser.parseSmilTextElem(doc, elem.childNodes[j], divID, currentTime, dur); 1077 break; 1078 case 'smil:p': 1079 case 'p': 1080 // check whether attributes exist 1081 1082 // a new p will be created to render the content 1083 var pID = "p" + Math.random()*10000; 1084 cwi.smilText.Parser.parseAttributes(doc, pID, elem.childNodes[j]); 1085 1086 // Setup xml:space 1087 if (doc.getStoredAttribute(pID, 'xml:space') == undefined || doc.getStoredAttribute(pID, 'xml:space') == null || 1088 (doc.getStoredAttribute(pID, 'xml:space') != 'default' && doc.getStoredAttribute(pID, 'xml:space') != 'preserve')) { 1089 if (doc.getStoredAttribute(layout, 'xml:space') != undefined && doc.getStoredAttribute(layout, 'xml:space') != null && 1090 (doc.getStoredAttribute(layout, 'xml:space') == 'default' || doc.getStoredAttribute(layout, 'xml:space') == 'preserve')) 1091 { 1092 doc.updateAttribute(pID, 'xml:space', doc.getStoredAttribute(layout, 'xml:space')); 1093 } else doc.updateAttribute(pID, 'xml:space', 'default'); 1094 } 1095 1096 doc.updateAttribute(pID, 'textmode', doc.getStoredAttribute(layout, 'textmode')); 1097 doc.updateAttribute(pID, 'textrate', doc.getStoredAttribute(layout, 'textrate')); 1098 doc.addTimingEntry(cwi.smilText.Render.appendContainer, new Array(doc, pID, layout, force), currentTime * 1000); 1099 1100 currentTime = cwi.smilText.Parser.parseSmilTextElem(doc, elem.childNodes[j], pID, currentTime, dur); 1101 break; 1102 case 'smil:span': 1103 case 'span': 1104 // check whether attributes exist 1105 1106 // a new span will be created to render the content 1107 var spanID = "span" + Math.random()*10000; 1108 cwi.smilText.Parser.parseAttributes(doc, spanID, elem.childNodes[j]); 1109 1110 // Setup xml:space 1111 if (doc.getStoredAttribute(spanID, 'xml:space') == undefined || doc.getStoredAttribute(spanID, 'xml:space') == null || 1112 (doc.getStoredAttribute(spanID, 'xml:space') != 'default' && doc.getStoredAttribute(spanID, 'xml:space') != 'preserve')) { 1113 if (doc.getStoredAttribute(layout, 'xml:space') != undefined && doc.getStoredAttribute(layout, 'xml:space') != null && 1114 (doc.getStoredAttribute(layout, 'xml:space') == 'default' || doc.getStoredAttribute(layout, 'xml:space') == 'preserve')) 1115 { 1116 doc.updateAttribute(spanID, 'xml:space', doc.getStoredAttribute(layout, 'xml:space')); 1117 } else doc.updateAttribute(spanID, 'xml:space', 'default'); 1118 } 1119 1120 doc.updateAttribute(spanID, 'textmode', doc.getStoredAttribute(layout, 'textmode')); 1121 doc.updateAttribute(spanID, 'textrate', doc.getStoredAttribute(layout, 'textrate')); 1122 doc.addTimingEntry(cwi.smilText.Render.appendContainer, new Array(doc, spanID, layout, force), currentTime * 1000); 1123 1124 currentTime = cwi.smilText.Parser.parseSmilTextElem(doc, elem.childNodes[j], spanID, currentTime, dur); 1125 break; 1126 default: 1127 alert("Unknown smilText tag: " + tag); 1128 } 1129 } 1130 } 1131 1132 return currentTime; 1133 } 1134 1135 /** 1136 * Check the attributes of an element. 1137 * @private 1138 */ 1139 cwi.smilText.Parser.parseAttributes = function(doc, id, elem) 1140 { 1141 var elemName = elem.nodeName.toLowerCase(); 1142 1143 // Make sure we can recover it on applyAttributes method. 1144 if (elemName == 'textstyle') { 1145 id = 'textstyle' + id; 1146 } 1147 else { 1148 // check whether the textStyle attribute was defined. 1149 for( var i = 0; i < elem.attributes.length; i++ ) { 1150 var attr = elem.attributes[i].nodeName.toLowerCase(); 1151 var val = elem.attributes[i].nodeValue; 1152 1153 if (attr == 'textstyle') { 1154 doc.applyStyle(elemName, id, 'textstyle' + val); 1155 break; 1156 } 1157 } 1158 } 1159 1160 // check whether the attribute exists before setting a value 1161 for( var i = 0; i < elem.attributes.length; i++ ) { 1162 1163 var found = false; 1164 var attr = elem.attributes[i].nodeName.toLowerCase(); 1165 var val = elem.attributes[i].nodeValue; 1166 1167 switch(elemName) 1168 { 1169 case 'smil:smiltext': 1170 case 'smiltext': 1171 switch(attr) 1172 { 1173 case 'dur': 1174 found = true; 1175 break; 1176 case 'width': 1177 found = true; 1178 break; 1179 case 'height': 1180 found = true; 1181 break; 1182 default: 1183 } 1184 if (found){ 1185 doc.updateAttribute(id, attr, val); 1186 break; 1187 } 1188 case 'textstyle': 1189 switch(attr) 1190 { 1191 case 'textmode': 1192 found = true; 1193 break; 1194 case 'textplace': 1195 found = true; 1196 break; 1197 case 'textconceal': 1198 found = true; 1199 break; 1200 case 'textrate': 1201 found = true; 1202 break; 1203 default: 1204 } 1205 if (found){ 1206 doc.updateAttribute(id, attr, val); 1207 break; 1208 } 1209 case 'smil:div': 1210 case 'div': 1211 switch(attr) 1212 { 1213 case 'textalign': 1214 found = true; 1215 break; 1216 default: 1217 } 1218 if (found){ 1219 doc.updateAttribute(id, attr, val); 1220 break; 1221 } 1222 case 'smil:p': 1223 case 'p': 1224 switch(attr) 1225 { 1226 case 'xml:space': 1227 case 'textwrapoption': 1228 case 'textwritingmode': 1229 //case 'textstyle': 1230 found = true; 1231 break; 1232 default: 1233 } 1234 if (found){ 1235 doc.updateAttribute(id, attr, val); 1236 break; 1237 } 1238 case 'smil:span': 1239 case 'span': 1240 switch(attr) 1241 { 1242 case 'textbackgroundcolor': 1243 case 'textcolor': 1244 case 'textfontfamily': 1245 case 'textfontsize': 1246 case 'textfontstyle': 1247 case 'textfontweight': 1248 case 'textstyle': 1249 found = true; 1250 break; 1251 case 'textdirection': 1252 // Confirm it is a span element. Otherwise jump out. 1253 if (elemName != 'span' && elemName != 'textstyle') break; 1254 found = true; 1255 break; 1256 default: 1257 } 1258 if (found){ 1259 doc.updateAttribute(id, attr, val); 1260 } 1261 break; 1262 default: 1263 return; 1264 } 1265 } 1266 1267 } 1268 1269 1270 1271 1272 1273 /** 1274 @name cwi.smilText.Render 1275 @namespace Hold all smilText rendering functionalities. 1276 @version 1.0 1277 @author <a href="mailto:rlaiola@cwi.nl">Rodrigo Laiola Guimaraes</a> 1278 */ 1279 Namespace("cwi.smilText.Render"); 1280 1281 /* Solve library dependency */ 1282 Import("cwi.smilText.STDocument"); 1283 1284 /** 1285 * Append text to a given region. 1286 * @param {cwi.smilText.STDocument} doc the smilText document. 1287 * @param {string} str the string to be appended. 1288 * @param {string} layout the id of the rendering region. 1289 * @param {string} force true to guaratee that the primitive will be issued. 1290 * @see cwi.smilText.STDocument 1291 */ 1292 cwi.smilText.Render.appendText = function(doc, str, layout, force) 1293 { 1294 if (doc == null || doc == undefined) 1295 return; 1296 1297 // Test whether smilText duration has elapsed. 1298 if (!force) 1299 return; 1300 1301 str = doc.getTranslatedSentence(str); 1302 1303 // Apply textWrapOption 1304 switch(doc.getStoredAttribute(layout, 'textwrapoption')) 1305 { 1306 case 'noWrap': 1307 break; 1308 case 'inherit': 1309 break; 1310 case 'wrap': 1311 default: 1312 str = str.replace(/(.*?)/g, "<wbr/>"); 1313 } 1314 1315 var wrapper = document.createElement('span'); 1316 if (document.getElementById(layout) != null) 1317 document.getElementById(layout).appendChild(wrapper); 1318 wrapper.innerHTML = str; 1319 //document.getElementById(layout).innerHTML += str; 1320 } 1321 1322 /** 1323 * Append a linebreak to a given region. 1324 * @param {cwi.smilText.STDocument} doc the smilText document. 1325 * @param {string} layout the id of the rendering region. 1326 * @param {string} force true to guaratee that the primitive will be issued. 1327 * @see cwi.smilText.STDocument 1328 */ 1329 cwi.smilText.Render.appendLineBreak = function(doc, layout, force) 1330 { 1331 if (doc == null || doc == undefined) 1332 return; 1333 1334 // Test whether smilText duration has elapsed. 1335 if (!force) 1336 return; 1337 1338 var theData = document.createElement('BR'); 1339 if (document.getElementById(layout) != null) 1340 document.getElementById(layout).appendChild(theData); 1341 } 1342 1343 /** 1344 * Clear a given region. 1345 * @param {cwi.smilText.STDocument} doc the smilText document. 1346 * @param {string} layout the id of the rendering region. 1347 * @param {string} force true to guaratee that the primitive will be issued. 1348 * @see cwi.smilText.STDocument 1349 */ 1350 cwi.smilText.Render.clearLayout = function(doc, layout, force) 1351 { 1352 if (doc == null || doc == undefined) 1353 return; 1354 1355 // Test whether smilText duration has elapsed. 1356 if (!force) 1357 return; 1358 1359 var layoutElem = document.getElementById(layout); 1360 if (layoutElem) { 1361 while (child = layoutElem.firstChild) 1362 layoutElem.removeChild(child); 1363 } 1364 } 1365 1366 /** 1367 * Append a container to a given region. 1368 * @param {cwi.smilText.STDocument} doc the smilText document. 1369 * @param {string} containerName the name (id) of the new container. It must be unique 1370 * and start with smiltext, div, p or span. 1371 * @param {string} layout the id of the rendering region. 1372 * @param {string} force true to guaratee that the primitive will be issued. 1373 * @see cwi.smilText.STDocument 1374 */ 1375 cwi.smilText.Render.appendContainer = function(doc, containerName, layout, force) 1376 { 1377 if (doc == null || doc == undefined) 1378 return; 1379 1380 // Test whether smilText duration has elapsed. 1381 if (!force) 1382 return; 1383 1384 // create a new div to render the content 1385 var wrapper; 1386 var id = containerName.charAt(0); 1387 if (containerName.length > 8 && containerName.substring(0,8) == 'smiltext') { 1388 id = 'smiltext'; 1389 } 1390 1391 switch(id) 1392 { 1393 case 'smiltext': // smiltext tag 1394 case 'd': // div 1395 wrapper = document.createElement('div'); 1396 try { 1397 wrapper.style.display='block'; 1398 } catch (e) {} 1399 break; 1400 case 'p': // p 1401 wrapper = document.createElement('div'); 1402 try { 1403 wrapper.style.display='block'; 1404 } catch (e) {} 1405 break; 1406 case 's': // span 1407 wrapper = document.createElement('div'); 1408 try { 1409 wrapper.style.display='inline'; 1410 } catch (e) {} 1411 break; 1412 } 1413 1414 // Setup text wrap option 1415 if (doc.getStoredAttribute(containerName, 'textwrapoption') == undefined || 1416 doc.getStoredAttribute(containerName, 'textwrapoption') == null) 1417 { 1418 doc.updateAttribute(containerName, 'textwrapoption', 'wrap'); 1419 } 1420 else if (doc.getStoredAttribute(containerName, 'textwrapoption') == 'inherit') 1421 { 1422 if (doc.getStoredAttribute(layout, 'textwrapoption') != undefined && 1423 doc.getStoredAttribute(layout, 'textwrapoption') != null) 1424 { 1425 doc.updateAttribute(containerName, 'textwrapoption', doc.getStoredAttribute(layout, 'textwrapoption')); 1426 } else doc.updateAttribute(containerName, 'textwrapoption', 'wrap'); 1427 } 1428 1429 // Crawl mode: disable line breaks 1430 if (doc.getStoredAttribute(containerName, 'textmode') == "crawl") { 1431 doc.updateAttribute(containerName, 'textwrapoption', 'noWrap'); 1432 } 1433 1434 // Apply textWrapOption 1435 if (doc.getStoredAttribute(containerName, 'xml:space') == 'preserve') 1436 { 1437 switch(doc.getStoredAttribute(containerName, 'textwrapoption')) 1438 { 1439 case 'noWrap': 1440 try { 1441 wrapper.style.whiteSpace='pre'; 1442 } catch (e) {} 1443 break; 1444 case 'wrap': 1445 default: 1446 try { 1447 wrapper.style.whiteSpace='pre-wrap'; 1448 } catch (e) {} 1449 } 1450 } else { 1451 switch(doc.getStoredAttribute(containerName, 'textwrapoption')) 1452 { 1453 case 'noWrap': 1454 try { 1455 wrapper.style.whiteSpace='nowrap'; 1456 } catch (e) {} 1457 break; 1458 case 'wrap': 1459 default: 1460 try { 1461 wrapper.style.whiteSpace='normal'; 1462 } catch (e) {} 1463 } 1464 } 1465 1466 // Discard implicit line breaks on crawl mode 1467 if (doc.getStoredAttribute(containerName, 'textmode') == "crawl" && id != 'smiltext') { 1468 try { 1469 wrapper.style.display='inline'; 1470 } catch (e) {} 1471 } 1472 1473 if (document.getElementById(layout) != null) 1474 document.getElementById(layout).appendChild(wrapper); 1475 id = document.createAttribute("id"); 1476 id.nodeValue=containerName; 1477 wrapper.setAttributeNode(id); 1478 1479 cwi.smilText.Render.applyAttributes(doc, containerName, wrapper); 1480 } 1481 1482 /** 1483 * Set the end of the life cycle of a smilText tag. 1484 * @param {cwi.smilText.STDocument} doc the smilText document. 1485 * @see cwi.smilText.STDocument 1486 */ 1487 cwi.smilText.Render.closeContainer = function(doc) 1488 { 1489 if (!doc.hasFillSemantics) cwi.smilText.Render.clearLayout(doc, doc.layout, true); 1490 } 1491 1492 /** 1493 * Apply the stored attributes in a given layout. 1494 * @private 1495 */ 1496 cwi.smilText.Render.applyAttributes = function(doc, id, layout) 1497 { 1498 1499 var elemName; 1500 if (id.length > 8 && id.substring(0,8) == 'smiltext'){ 1501 elemName = 'smiltext'; 1502 } 1503 else if (id.length > 9 && id.substring(0,9) == 'textstyle'){ 1504 elemName = 'textstyle'; 1505 1506 // get the layout id 1507 for( var i = 0; i < layout.attributes.length; i++ ) { 1508 1509 if ( layout.attributes[i].nodeName.toLowerCase() == 'id' ) { 1510 var container = layout.attributes[i].nodeValue; 1511 1512 if (container.length > 8 && container.substring(0,8) == 'smiltext'); 1513 else if (container.length > 3 && container.substring(0,3) == 'div') 1514 elemName += '1'; 1515 else if (container.length > 1 && container.substring(0,1) == 'p') 1516 elemName += '2'; 1517 else if (container.length > 4 && container.substring(0,4) == 'span') 1518 elemName += '3'; 1519 1520 break; 1521 } 1522 1523 } 1524 1525 } 1526 else elemName = id.charAt(0); 1527 1528 // Apply textStyle 1529 if (elemName != 'textstyle' && doc.getStoredAttribute(id, 'textstyle') != undefined && 1530 doc.getStoredAttribute(id, 'textstyle') != null) { 1531 cwi.smilText.Render.applyAttributes(doc, 'textstyle'+ doc.getStoredAttribute(id, 'textstyle'), layout); 1532 } 1533 1534 switch(elemName) 1535 { 1536 case 'smiltext': 1537 if (doc.getStoredAttribute(id, 'width') != undefined && doc.getStoredAttribute(id, 'width') != null) 1538 { 1539 try { 1540 layout.style.width = parseFloat(doc.getStoredAttribute(id, 'width')); 1541 layout.style.overflow='hidden'; 1542 } catch (e) {} 1543 } 1544 if (doc.getStoredAttribute(id, 'height') != undefined && doc.getStoredAttribute(id, 'height') != null) 1545 { 1546 try { 1547 layout.style.height = parseFloat(doc.getStoredAttribute(id, 'height')); 1548 layout.style.overflow='hidden'; 1549 } catch (e) {} 1550 } 1551 case 'textstyle': 1552 if (doc.getStoredAttribute(id, 'textplace') != undefined && doc.getStoredAttribute(id, 'textplace') != null) 1553 { 1554 // TODO 1555 } 1556 if (doc.getStoredAttribute(id, 'textmode') != undefined && doc.getStoredAttribute(id, 'textmode') != null) 1557 { 1558 switch(doc.getStoredAttribute(id, 'textmode')) 1559 { 1560 case 'replace': 1561 break; 1562 case 'crawl': 1563 doc.addTimingEntry(cwi.smilText.Render.scrollticker, new Array(doc, id, '1'), 0); 1564 break; 1565 case 'scroll': 1566 doc.addTimingEntry(cwi.smilText.Render.scrollticker, new Array(doc, id, '2'), 0); 1567 break; 1568 case 'jump': 1569 // TODO 1570 break; 1571 case 'append': 1572 default: 1573 } 1574 } 1575 if (doc.getStoredAttribute(id, 'textconceal') != undefined && doc.getStoredAttribute(id, 'textconceal') != null) 1576 { 1577 switch(doc.getStoredAttribute(id, 'textconceal')) 1578 { 1579 case 'none': 1580 // TODO 1581 break; 1582 case 'initial': 1583 // TODO 1584 break; 1585 case 'final': 1586 // TODO 1587 break; 1588 case 'both': 1589 // TODO 1590 break; 1591 case 'inherit': 1592 default: 1593 } 1594 } 1595 if (doc.getStoredAttribute(id, 'textrate') != undefined && doc.getStoredAttribute(id, 'textrate') != null) 1596 { 1597 // TODO 1598 } 1599 case 'textstyle1': 1600 case 'd': // div 1601 if (doc.getStoredAttribute(id, 'textalign') != undefined && doc.getStoredAttribute(id, 'textalign') != null) 1602 { 1603 switch(doc.getStoredAttribute(id, 'textalign')) 1604 { 1605 case 'inherit': 1606 case 'start': 1607 case 'end': 1608 case 'left': 1609 case 'right': 1610 case 'center': 1611 try { 1612 layout.style.textAlign = doc.getStoredAttribute(id, 'textalign'); 1613 } catch (e) {} 1614 break; 1615 default: 1616 } 1617 } 1618 case 'textstyle2': 1619 case 'p': // p 1620 if (doc.getStoredAttribute(id, 'textwritingmode') != undefined && doc.getStoredAttribute(id, 'textwritingmode') != null) 1621 { 1622 switch(doc.getStoredAttribute(id, 'textwritingmode')) 1623 { 1624 case 'lr-tb': 1625 case 'rl-tb': 1626 case 'tb-rl': 1627 case 'tb-lr': 1628 case 'rl': 1629 case 'lr': 1630 case 'inherit': 1631 try { 1632 layout.style.writingMode = doc.getStoredAttribute(id, 'textwritingmode'); 1633 } catch (e) {} 1634 break; 1635 default: 1636 } 1637 } 1638 case 'textstyle3': 1639 case 's': // span 1640 if (doc.getStoredAttribute(id, 'textbackgroundcolor') != undefined && doc.getStoredAttribute(id, 'textbackgroundcolor') != null) 1641 { 1642 try { 1643 layout.style.backgroundColor = doc.getStoredAttribute(id, 'textbackgroundcolor'); 1644 } catch (e) {} 1645 } 1646 if (doc.getStoredAttribute(id, 'textcolor') != undefined && doc.getStoredAttribute(id, 'textcolor') != null) 1647 { 1648 try { 1649 layout.style.color = doc.getStoredAttribute(id, 'textcolor'); 1650 } catch (e) {} 1651 } 1652 if (doc.getStoredAttribute(id, 'textfontfamily') != undefined && doc.getStoredAttribute(id, 'textfontfamily') != null) 1653 { 1654 try { 1655 layout.style.fontFamily = doc.getStoredAttribute(id, 'textfontfamily'); 1656 } catch (e) {} 1657 } 1658 if (doc.getStoredAttribute(id, 'textfontsize') != undefined && doc.getStoredAttribute(id, 'textfontsize') != null) 1659 { 1660 try { 1661 layout.style.fontSize = doc.getStoredAttribute(id, 'textfontsize'); 1662 } catch (e) {} 1663 } 1664 if (doc.getStoredAttribute(id, 'textfontstyle') != undefined && doc.getStoredAttribute(id, 'textfontstyle') != null) 1665 { 1666 try { 1667 layout.style.fontStyle = doc.getStoredAttribute(id, 'textfontstyle'); 1668 } catch (e) {} 1669 } 1670 if (doc.getStoredAttribute(id, 'textfontweight') != undefined && doc.getStoredAttribute(id, 'textfontweight') != null) 1671 { 1672 try { 1673 layout.style.fontWeight = doc.getStoredAttribute(id, 'textfontweight'); 1674 } catch (e) {} 1675 } 1676 if (doc.getStoredAttribute(id, 'textdirection') != undefined && doc.getStoredAttribute(id, 'textdirection') != null) 1677 { 1678 // Confirm it is a span element. Otherwise jump out. 1679 if (elemName == 's') 1680 { 1681 if (doc.getStoredAttribute(id, 'textdirection') == 'rtlo' || 1682 doc.getStoredAttribute(id, 'textdirection') == 'rtl') { 1683 try { 1684 layout.style.direction = 'rtl'; 1685 layout.style.unicodeBidi = 'bidi-override'; 1686 } catch (e) {} 1687 } else if (doc.getStoredAttribute(id, 'textdirection') == 'ltro' || 1688 doc.getStoredAttribute(id, 'textdirection') == 'ltro') { 1689 try { 1690 layout.style.direction = 'ltr'; 1691 layout.style.unicodeBidi = 'bidi-override'; 1692 } catch (e) {} 1693 } 1694 } 1695 } 1696 break; 1697 default: 1698 } 1699 1700 } 1701 1702 /****************************************************************** 1703 * Text Motion 1704 ******************************************************************/ 1705 1706 /** 1707 * @private 1708 */ 1709 cwi.smilText.Render.cps = 1; // 20px/s 1710 1711 /** 1712 * Move layout content in a given direction. 1713 * @private 1714 */ 1715 cwi.smilText.Render.scrollticker = function(doc, layout, dir) 1716 { 1717 // there is no layout .... yet 1718 if (!document.getElementById(layout)) 1719 return; 1720 1721 var mq = document.getElementById(layout).parentNode; 1722 mq.style.overflow='hidden'; 1723 var mqPosition = mq.style.position; 1724 mq.style.position='relative'; 1725 1726 for (var j = 0; j < mq.childNodes.length; j++) 1727 { 1728 var moveElem = mq.childNodes[j]; 1729 1730 var direction; 1731 1732 if (dir == '1') { // move against the first writing direction 1733 if (doc.getStoredAttribute(layout, 'textwritingmode') != undefined && doc.getStoredAttribute(layout, 'textwritingmode') != null) 1734 { 1735 switch(doc.getStoredAttribute(layout, 'textwritingmode')) 1736 { 1737 case 'lr-tb': 1738 case 'lr': 1739 direction = 'left'; 1740 break; 1741 case 'rl-tb': 1742 case 'rl': 1743 direction = 'right'; 1744 break; 1745 case 'tb-rl': 1746 case 'tb-lr': 1747 direction = 'up'; 1748 case 'inherit': 1749 if (cwi.util.getStyleByElem(mq, 'writingMode') != undefined && 1750 cwi.util.getStyleByElem(mq, 'writingMode') != null && 1751 cwi.util.getStyleByElem(mq, 'writingMode').length > 0) { 1752 if (cwi.util.getStyleByElem(mq, 'writingMode').charAt(0) == 'r') 1753 direction = 'right'; 1754 else if (cwi.util.getStyleByElem(mq, 'writingMode').charAt(0) == 't') 1755 direction = 'up'; 1756 else direction = 'left'; 1757 } 1758 else direction = 'left'; 1759 break; 1760 } 1761 } 1762 else{ 1763 if (cwi.util.getStyleByElem(mq, 'writingMode') != undefined && 1764 cwi.util.getStyleByElem(mq, 'writingMode') != null && 1765 cwi.util.getStyleByElem(mq, 'writingMode').length > 0) { 1766 if (cwi.util.getStyleByElem(mq, 'writingMode').charAt(0) == 'r') 1767 direction = 'right'; 1768 else if (cwi.util.getStyleByElem(mq, 'writingMode').charAt(0) == 't') 1769 direction = 'up'; 1770 else direction = 'left'; 1771 } 1772 else direction = 'left'; 1773 } 1774 } 1775 else if (dir == '2') { // move against the 2nd writing direction 1776 if (doc.getStoredAttribute(layout, 'textwritingmode') != undefined && doc.getStoredAttribute(layout, 'textwritingmode') != null) 1777 { 1778 switch(doc.getStoredAttribute(layout, 'textwritingmode')) 1779 { 1780 case 'lr-tb': 1781 case 'lr': 1782 case 'rl-tb': 1783 case 'rl': 1784 direction = 'up'; 1785 break; 1786 case 'tb-rl': 1787 direction = 'right'; 1788 break; 1789 case 'tb-lr': 1790 direction = 'left'; 1791 break; 1792 case 'inherit': 1793 if (cwi.util.getStyleByElem(mq, 'writingMode') != undefined && 1794 cwi.util.getStyleByElem(mq, 'writingMode') != null && 1795 cwi.util.getStyleByElem(mq, 'writingMode').length > 0) { 1796 if (cwi.util.getStyleByElem(mq, 'writingMode').charAt(0) == 'r' || 1797 cwi.util.getStyleByElem(mq, 'writingMode').charAt(0) == 'l') 1798 direction = 'up'; 1799 else { 1800 if (cwi.util.getStyleByElem(mq, 'writingMode') == 'tb-lr') 1801 direction = 'left'; 1802 else direction = 'right'; 1803 } 1804 } 1805 else direction = 'left'; 1806 break; 1807 } 1808 } 1809 else{ 1810 if (cwi.util.getStyleByElem(mq, 'writingMode') != undefined && 1811 cwi.util.getStyleByElem(mq, 'writingMode') != null && 1812 cwi.util.getStyleByElem(mq, 'writingMode').length > 0) { 1813 if (cwi.util.getStyleByElem(mq, 'writingMode').charAt(0) == 'r' || 1814 cwi.util.getStyleByElem(mq, 'writingMode').charAt(0) == 'l') 1815 direction = 'up'; 1816 else { 1817 if (cwi.util.getStyleByElem(mq, 'writingMode') == 'tb-lr') 1818 direction = 'left'; 1819 else direction = 'right'; 1820 } 1821 } 1822 else direction = 'up'; 1823 } 1824 } 1825 1826 // has textRate? 1827 var currentCPS = cwi.smilText.Render.cps; 1828 if (doc.getStoredAttribute(layout, 'textrate') != undefined && 1829 doc.getStoredAttribute(layout, 'textrate') != null && 1830 doc.getStoredAttribute(layout, 'textrate') != 'auto') 1831 { 1832 currentCPS = 0.05 * parseInt(doc.getStoredAttribute(layout, 'textrate')); 1833 } 1834 1835 if (moveElem.nodeType != 1); 1836 else 1837 { 1838 var w = parseInt(cwi.util.getStyleByElem(mq, 'width')); 1839 var h = parseInt(cwi.util.getStyleByElem(mq, 'height')); 1840 moveElem.style.position = 'absolute'; 1841 var aw = moveElem.offsetWidth > w ? moveElem.offsetWidth : w; 1842 var ah = moveElem.offsetHeight; 1843 moveElem.style.position = 'relative'; 1844 1845 if (!aw) 1846 { 1847 aw = moveElem.offsetWidth; 1848 } 1849 1850 if (!ah) 1851 { 1852 ah = moveElem.offsetHeight; 1853 } 1854 1855 if (!w) { 1856 w = aw; 1857 } 1858 if (!h) { 1859 h = ah; 1860 } 1861 1862 switch(direction) 1863 { 1864 case 'left': 1865 if (!moveElem.style.left) 1866 { 1867 moveElem.style.left = 0; 1868 } 1869 1870 moveElem.style.left = parseInt(moveElem.style.left) > parseInt(-aw) ? 1871 parseFloat(moveElem.style.left) - currentCPS : w; 1872 break; 1873 case 'up': 1874 if (!moveElem.style.top) 1875 { 1876 moveElem.style.top = 0; 1877 } 1878 1879 moveElem.style.top = parseInt(moveElem.style.top) > parseInt(-ah) ? 1880 parseFloat(moveElem.style.top) - currentCPS : h; 1881 break; 1882 case 'right': 1883 if (!moveElem.style.left) 1884 { 1885 //moveElem.style.left = parseInt(getStyleByElem(moveElem, 'left')) + w; 1886 moveElem.style.left = 0; 1887 } 1888 1889 moveElem.style.left = parseInt(moveElem.style.left) < parseInt(w) ? 1890 parseFloat(moveElem.style.left) + currentCPS : -aw; 1891 break; 1892 case 'down': // not used 1893 break; 1894 } 1895 } 1896 1897 } 1898 1899 mq.style.position=mqPosition; 1900 1901 } 1902 1903 1904 1905 1906 1907 /** 1908 * Hold the SmilText Document Style 1909 * @private 1910 * @constructor 1911 * @augments cwi.smilText.Style 1912 * @author <a href="mailto:rlaiola@cwi.nl">Rodrigo Laiola Guimaraes</a> 1913 */ 1914 cwi.smilText.Style = function() 1915 { 1916 var self = JSINER.extend(this); 1917 1918 /** 1919 * Variables 1920 * @private 1921 */ 1922 this.stylingHash = new Hashtable(); // Hold styling attributes 1923 1924 return self; 1925 } 1926 1927 /** 1928 * Return the value of a stored style attribute. 1929 * @private 1930 * @param {string} layout the region id 1931 * @param {string} attr the attribute name 1932 * @return {string} val the attribute value 1933 */ 1934 cwi.smilText.Style.prototype.getAttribute = function(layout, attr) 1935 { 1936 return this.stylingHash.get(layout + attr); 1937 } 1938 1939 /** 1940 * Save a style attribute of a given layout element. 1941 * @private 1942 * @param {string} layout the region id 1943 * @param {string} attr the attribute name 1944 * @param {string} val the attribute value 1945 */ 1946 cwi.smilText.Style.prototype.updateAttribute = function(layout, attr, val) 1947 { 1948 this.stylingHash.put(layout + attr, val); 1949 } 1950 1951 /** 1952 * Transfer the properties from a given style to a layout. 1953 * @private 1954 */ 1955 cwi.smilText.Style.prototype.toXML = function(typeLayout, id, style) 1956 { 1957 var str = ""; 1958 1959 switch(typeLayout) 1960 { 1961 case 'smiltext': 1962 this.updateAttribute(id, 'dur', this.getStoredAttribute(style, 'dur')); 1963 this.updateAttribute(id, 'width', this.getStoredAttribute(style, 'width')); 1964 this.updateAttribute(id, 'height', this.getStoredAttribute(style, 'height')); 1965 case 'textstyle': 1966 this.updateAttribute(id, 'textmode', this.getStoredAttribute(style, 'textmode')); 1967 this.updateAttribute(id, 'textplace', this.getStoredAttribute(style, 'textplace')); 1968 this.updateAttribute(id, 'textconceal', this.getStoredAttribute(style, 'textconceal')); 1969 this.updateAttribute(id, 'textrate', this.getStoredAttribute(style, 'textrate')); 1970 case 'div': 1971 this.updateAttribute(id, 'textalign', this.getStoredAttribute(style, 'textalign')); 1972 case 'p': 1973 this.updateAttribute(id, 'xml:space', this.getStoredAttribute(style, 'xml:space')); 1974 this.updateAttribute(id, 'textwrapoption', this.getStoredAttribute(style, 'textwrapoption')); 1975 this.updateAttribute(id, 'textwritingmode', this.getStoredAttribute(style, 'textwritingmode')); 1976 case 'span': 1977 this.updateAttribute(id, 'textbackgroundcolor', this.getStoredAttribute(style, 'textbackgroundcolor')); 1978 this.updateAttribute(id, 'textcolor', this.getStoredAttribute(style, 'textcolor')); 1979 this.updateAttribute(id, 'textfontfamily', this.getStoredAttribute(style, 'textfontfamily')); 1980 this.updateAttribute(id, 'textfontsize', this.getStoredAttribute(style, 'textfontsize')); 1981 this.updateAttribute(id, 'textfontstyle', this.getStoredAttribute(style, 'textfontstyle')); 1982 this.updateAttribute(id, 'textfontweight', this.getStoredAttribute(style, 'textfontweight')); 1983 this.updateAttribute(id, 'textstyle', this.getStoredAttribute(style, 'textstyle')); 1984 1985 if (typeLayout != 'span' && typeLayout != 'textstyle') 1986 this.updateAttribute(id, 'textdirection', this.getStoredAttribute(style, 'textdirection')); 1987 } 1988 1989 return str; 1990 } 1991