http://www.mediawiki.org/wiki/Special:Code/MediaWiki/90564

Revision: 90564
Author:   tparscal
Date:     2011-06-22 00:23:07 +0000 (Wed, 22 Jun 2011)
Log Message:
-----------
Replaced text flow algorithm with a binary-search approach, lifted from 
jquery.autoEllipsis

Modified Paths:
--------------
    trunk/parsers/wikidom/demos/es/index.html
    trunk/parsers/wikidom/lib/es/es.Block.js
    trunk/parsers/wikidom/lib/es/es.Document.js
    trunk/parsers/wikidom/lib/es/es.ParagraphBlock.js
    trunk/parsers/wikidom/lib/es/es.Surface.css
    trunk/parsers/wikidom/lib/es/es.Surface.js
    trunk/parsers/wikidom/lib/es/es.TextFlow.js

Modified: trunk/parsers/wikidom/demos/es/index.html
===================================================================
--- trunk/parsers/wikidom/demos/es/index.html   2011-06-22 00:16:25 UTC (rev 
90563)
+++ trunk/parsers/wikidom/demos/es/index.html   2011-06-22 00:23:07 UTC (rev 
90564)
@@ -37,7 +37,7 @@
                                var surface = new Surface( $('#es'), doc );
                                
                                $(window).resize( function() {
-                                       surface.reflow();
+                                       surface.render();
                                } );
                        } );
                </script>

Modified: trunk/parsers/wikidom/lib/es/es.Block.js
===================================================================
--- trunk/parsers/wikidom/lib/es/es.Block.js    2011-06-22 00:16:25 UTC (rev 
90563)
+++ trunk/parsers/wikidom/lib/es/es.Block.js    2011-06-22 00:23:07 UTC (rev 
90564)
@@ -74,6 +74,15 @@
 };
 
 /**
+ * Updates the rendered content in a container.
+ * 
+ * @param $container {jQuery Selection} Container to update content in
+ */
+Block.prototype.updateContent = function( $container ) {
+       throw 'Block.updateContent not implemented in this subclass.';
+};
+
+/**
  * Gets the location of a position.
  * 
  * @param position {Position} Position to translate

Modified: trunk/parsers/wikidom/lib/es/es.Document.js
===================================================================
--- trunk/parsers/wikidom/lib/es/es.Document.js 2011-06-22 00:16:25 UTC (rev 
90563)
+++ trunk/parsers/wikidom/lib/es/es.Document.js 2011-06-22 00:23:07 UTC (rev 
90564)
@@ -5,6 +5,9 @@
  */
 function Document( blocks ) {
        this.blocks = blocks || [];
+       this.width = null;
+       this.$ = $( '<div class="editSurface-document"></div>' )
+               .data( 'document', this );
 }
 
 /**
@@ -69,3 +72,27 @@
        this.blocks.splice( block.index(), 1 );
        block.document = null;
 };
+
+Document.prototype.renderBlocks = function() {
+       // Remember width, to avoid updates when without width changes
+       this.width = this.$.innerWidth();
+       // Render blocks
+       for ( var i = 0; i < this.blocks.length; i++ ) {
+               this.$.append( this.blocks[i].$ );
+               this.blocks[i].renderContent();
+       }
+};
+
+Document.prototype.updateBlocks = function() {
+       // Bypass rendering when width has not changed
+       var width = this.$.innerWidth();
+       if ( this.width === width ) {
+               return;
+       }
+       this.width = width;
+       // Render blocks
+       var doc;
+       this.$.children( '.editSurface-block' ).each( function( i ) {
+               $(this).data( 'block' ).renderContent();
+       } );
+};

Modified: trunk/parsers/wikidom/lib/es/es.ParagraphBlock.js
===================================================================
--- trunk/parsers/wikidom/lib/es/es.ParagraphBlock.js   2011-06-22 00:16:25 UTC 
(rev 90563)
+++ trunk/parsers/wikidom/lib/es/es.ParagraphBlock.js   2011-06-22 00:23:07 UTC 
(rev 90564)
@@ -6,7 +6,10 @@
 function ParagraphBlock( lines ) {
        Block.call( this );
        this.lines = lines || [];
-       this.metrics = [];
+       this.lineMetrics = [];
+       this.$ = $( '<div class="editSurface-block 
editSurface-paragraph"></div>' )
+               .data( 'block', this );
+       this.flow = new TextFlow( this.$ );
 }
 
 /**
@@ -78,13 +81,12 @@
  * 
  * @param $container {jQuery Selection} Container to render into
  */
-Block.prototype.renderContent = function( $container ) {
-       var flow = new TextFlow(),
-               text = [];
+Block.prototype.renderContent = function() {
+       var text = [];
        for ( var i = 0; i < this.lines.length; i++ ) {
                text.push( this.lines[i].text );
        }
-       this.metrics = flow.render( $container, text.join( '\n' ) );
+       this.lineMetrics = this.flow.render( text.join( '\n' ) );
 };
 
 /**

Modified: trunk/parsers/wikidom/lib/es/es.Surface.css
===================================================================
--- trunk/parsers/wikidom/lib/es/es.Surface.css 2011-06-22 00:16:25 UTC (rev 
90563)
+++ trunk/parsers/wikidom/lib/es/es.Surface.css 2011-06-22 00:23:07 UTC (rev 
90564)
@@ -3,16 +3,14 @@
        font-size: 0.8em;
 }
 
-.editSurface-container {
-       position: absolute;
-       left: 3%;
-       width: 45%;
-       border: solid 1px red;
+.editSurface-document {
+       border: solid 1px silver;
        cursor: text;
+       width: 50%;
 }
 
 .editSurface-paragraph {
-       padding: 2em;
+       margin: 2em;
 }
 
 .editSurface-line {
@@ -21,6 +19,7 @@
        line-height: 1.5em;
        cursor: text;
        white-space: nowrap;
+       background-color: #eeeeee;
 }
 
 .editSurface-line.empty {

Modified: trunk/parsers/wikidom/lib/es/es.Surface.js
===================================================================
--- trunk/parsers/wikidom/lib/es/es.Surface.js  2011-06-22 00:16:25 UTC (rev 
90563)
+++ trunk/parsers/wikidom/lib/es/es.Surface.js  2011-06-22 00:23:07 UTC (rev 
90564)
@@ -5,9 +5,10 @@
  * @returns {Surface}
  */
 function Surface( $container, document ) {
-       this.$container = $container;
+       this.$ = $container;
        this.document = document;
-       this.reflow();
+       this.rendered = false;
+       this.render();
 }
 
 /**
@@ -167,10 +168,12 @@
  * 
  * @param from Location: Where to start re-flowing from (optional)
  */
-Surface.prototype.reflow = function( from ) {
-       this.$container.empty();
-       for ( var i = 0; i < this.document.blocks.length; i++ ) {
-               $block = $( '<div></div>' ).appendTo( this.$container );
-               this.document.blocks[i].renderContent( $block );
+Surface.prototype.render = function( from ) {
+       if ( !this.rendered ) {
+               this.rendered = true;
+               this.$.append( this.document.$ );
+               this.document.renderBlocks();
+       } else {
+               this.document.updateBlocks();
        }
 };

Modified: trunk/parsers/wikidom/lib/es/es.TextFlow.js
===================================================================
--- trunk/parsers/wikidom/lib/es/es.TextFlow.js 2011-06-22 00:16:25 UTC (rev 
90563)
+++ trunk/parsers/wikidom/lib/es/es.TextFlow.js 2011-06-22 00:23:07 UTC (rev 
90564)
@@ -1,89 +1,90 @@
 /**
  * TODO: Don't re-flow lines beyond a given offset, an optimization for 
insert/delete operations
  */
-function TextFlow() {
-       //
+function TextFlow( $container ) {
+       this.$ = $container;
 }
 
-TextFlow.prototype.render = function( $container, text ) {
-       //console.time( 'TextFlow.render' );
-       var metrics = [],
-               $ruler = $( '<div class="editSurface-line"></div>' ).appendTo( 
$container ),
-               $line = $( '<div class="editSurface-line"></div>' ),
-               lines = this.getLines( this.getWords( text, $ruler[0] ), 
$ruler.innerWidth() );
-       $container.empty();
-       for ( var i = 0; i < lines.length; i++ ) {
-               metrics.push( lines[i] );
-               if ( lines[i].text.length === 1 && lines[1].text.match( /[ 
\-\t\r\n\f]/ ) ) {
-                       $container.append( $line.clone().html( '&nbsp' 
).addClass( 'empty' ) );
-               } else {
-                       $container.append( $line.clone().html( lines[i].html ) 
);
-               }
-       }
-       //console.timeEnd( 'TextFlow.render' );
-       return metrics;
+TextFlow.encodeHtml = function( text ) {
+       return text
+               .replace( /&/g, '&amp;' )
+               .replace( / /g, '&nbsp;' )
+               .replace( /</g, '&lt;' )
+               .replace( />/g, '&gt;' )
+               .replace( /'/g, '&apos;' )
+               .replace( /"/g, '&quot;' )
+               .replace( /\n/g, '<span 
class="editSurface-whitespace">\\n</span>' )
+               .replace( /\t/g, '<span 
class="editSurface-whitespace">\\t</span>' );
 };
 
-TextFlow.prototype.getWord = function( text, ruler ) {
-       var word = {
-               'text': text,
-               'html': text
-                       .replace( /&/g, '&amp;' )
-                       .replace( / /g, '&nbsp;' )
-                       .replace( /</g, '&lt;' )
-                       .replace( />/g, '&gt;' )
-                       .replace( /'/g, '&apos;' )
-                       .replace( /"/g, '&quot;' )
-                       .replace( /\n/g, '<span 
class="editSurface-whitespace">\\n</span>' )
-                       .replace( /\t/g, '<span 
class="editSurface-whitespace">\\t</span>' )
-       };
-       ruler.innerHTML = word.html;
-       word.width = ruler.clientWidth;
-       return word;
-};
-
-TextFlow.prototype.getWords = function( text, ruler ) {
-       var words = [],
-               bounadry = /[ \-\t\r\n\f]/,
+TextFlow.prototype.render = function( text ) {
+       //console.time( 'TextFlow.render' );
+       
+       // Clear all lines -- FIXME: This should adaptively re-use/cleanup 
existing lines
+       this.$.empty();
+       
+       // Measure the container width
+       var $ruler = $( '<div>&nbsp;</div>' ).appendTo( this.$ );
+       var width = $ruler.innerWidth()
+       $ruler.remove();
+       
+       // Build list of line break offsets
+       var words = [0],
+               boundary = /[ \-\t\r\n\f]/,
                left = 0,
                right = 0,
                search = 0;
-       while ( ( search = text.substr( right ).search( bounadry ) ) >= 0 ) {
+       while ( ( search = text.substr( right ).search( boundary ) ) >= 0 ) {
                right += search;
-               words.push( this.getWord( text.substring( left, right ), ruler 
) );
-               if ( right < text.length ) {
-                       words.push( this.getWord( text.substring( right, 
++right ), ruler ) );
-               }
+               words.push( ++right );
                left = right;
        }
-       words.push( this.getWord( text.substring( right, text.length ), ruler ) 
);
-       return words;
-};
-
-TextFlow.prototype.getLines = function( words, width ) {
-       var lines = [],
-               line = {
-                       'text': '',
-                       'html': '',
-                       'width': 0,
-                       'index': lines.length
-               };
-       for ( var i = 0; i < words.length; i++ ) {
-               if ( line.width + words[i].width > width ) {
-                       lines.push( line );
-                       line = {
-                               'text': '',
-                               'html': '',
-                               'width': 0,
-                               'index': lines.length
-                       };
-               }
-               line.text += words[i].text;
-               line.html += words[i].html;
-               line.width += words[i].width;
+       words.push( right );
+       words.push( text.length );
+       
+       // Create lines from text
+       var pos = 0,
+               index = 0,
+               metrics = [];
+       while ( pos < words.length - 1 ) {
+               // Create line
+               var $line = $( '<div class="editSurface-line"></div>' )
+                               .attr( 'line-index', index )
+                               .appendTo( this.$ ),
+                       line = $line[0];
+               
+               // Use binary search-like technique for efficiency
+               var l = pos,
+                       r = words.length,
+                       m;
+               do {
+                       m = Math.ceil( ( l + r ) / 2 );
+                       line.innerHTML = TextFlow.encodeHtml( text.substring( 
words[pos], words[m] ) );
+                       if ( line.clientWidth > width ) {
+                               // Text is too long
+                               r = m - 1;
+                       } else {
+                               l = m;
+                       }
+               } while ( l < r );
+               line.innerHTML = TextFlow.encodeHtml( text.substring( 
words[pos], words[l] ) );
+               
+               // TODO: Check if it fits yet, if not, do binary search within 
the really long word
+               
+               metrics.push({
+                       'text': text.substring( words[pos], words[l] ),
+                       'offset': words[pos],
+                       'length': words[l] - words[pos],
+                       'width': line.clientWidth,
+                       'index': index
+               });
+               
+               // Step forward
+               index++;
+               pos = m;
        }
-       if ( line.text.length ) {
-               lines.push( line );
-       }
-       return lines;
+       
+       //console.timeEnd( 'TextFlow.render' );
+       
+       return metrics;
 };


_______________________________________________
MediaWiki-CVS mailing list
MediaWiki-CVS@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to