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( ' ' ).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, '&' ) + .replace( / /g, ' ' ) + .replace( /</g, '<' ) + .replace( />/g, '>' ) + .replace( /'/g, ''' ) + .replace( /"/g, '"' ) + .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, '&' ) - .replace( / /g, ' ' ) - .replace( /</g, '<' ) - .replace( />/g, '>' ) - .replace( /'/g, ''' ) - .replace( /"/g, '"' ) - .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> </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