https://www.mediawiki.org/wiki/Special:Code/MediaWiki/111482

Revision: 111482
Author:   tparscal
Date:     2012-02-14 20:30:40 +0000 (Tue, 14 Feb 2012)
Log Message:
-----------
Changed the way widths are calculated when rendering lines of text. Now we let 
the browser do it's normal layout thing, and use a set of floated divs to 
figure out the effective line width. Using this technique, we pretty much 
support floating content from a rendering perspective. Interaction is still a 
problem however.

Modified Paths:
--------------
    trunk/extensions/VisualEditor/modules/ve/es/styles/ve.es.Content.css
    trunk/extensions/VisualEditor/modules/ve/es/ve.es.Content.js

Modified: trunk/extensions/VisualEditor/modules/ve/es/styles/ve.es.Content.css
===================================================================
--- trunk/extensions/VisualEditor/modules/ve/es/styles/ve.es.Content.css        
2012-02-14 20:24:05 UTC (rev 111481)
+++ trunk/extensions/VisualEditor/modules/ve/es/styles/ve.es.Content.css        
2012-02-14 20:30:40 UTC (rev 111482)
@@ -4,14 +4,14 @@
 }
 
 .es-contentView-line,
-.es-contentView-ruler {
+.es-contentView-ruler-line {
        line-height: 1.5em;
        cursor: text;
        white-space: nowrap;
        color: #000000;
 }
 
-.es-contentView-ruler {
+.es-contentView-ruler-line {
        position: absolute;
        top: 0;
        left: 0;
@@ -19,6 +19,14 @@
        z-index: -1000;
 }
 
+.es-contentView-ruler-right {
+       float: right;
+}
+
+.es-contentView-ruler-left {
+       float: left;
+}
+
 .es-contentView-line.empty {
        display: block;
        width: 0px;

Modified: trunk/extensions/VisualEditor/modules/ve/es/ve.es.Content.js
===================================================================
--- trunk/extensions/VisualEditor/modules/ve/es/ve.es.Content.js        
2012-02-14 20:24:05 UTC (rev 111481)
+++ trunk/extensions/VisualEditor/modules/ve/es/ve.es.Content.js        
2012-02-14 20:30:40 UTC (rev 111482)
@@ -52,7 +52,19 @@
                this.$rangeFill = $( '<div class="es-contentView-range"></div>' 
);
                this.$rangeEnd = $( '<div class="es-contentView-range"></div>' 
);
                this.$.prepend( this.$ranges.append( this.$rangeStart, 
this.$rangeFill, this.$rangeEnd ) );
+               this.$rulers = $( '<div class="es-contentView-rulers"></div>' );
+               this.$rulerLeft = $( '<div 
class="es-contentView-ruler-left"></div>' );
+               this.$rulerRight = $( '<div 
class="es-contentView-ruler-right"></div>' );
+               this.$rulerLine = $( '<div 
class="es-contentView-ruler-line"></div>' );
+               this.$.append( this.$rulers.append( this.$rulerLeft, 
this.$rulerRight, this.$rulerLine ) );
 
+               // Shortcuts to DOM elements
+               this.rulers = {
+                       'left': this.$rulerLeft[0],
+                       'right': this.$rulerRight[0],
+                       'line': this.$rulerLine[0]
+               };
+
                // Initialization
                this.scanBoundaries();
        }
@@ -387,19 +399,19 @@
         * TODO: The offset needs to be chosen based on nearest offset to the 
cursor, not offset before
         * the cursor.
         */
-       var $ruler = $( '<div class="es-contentView-ruler"></div>' ).appendTo( 
this.$ ),
-               ruler = $ruler[0],
-               fit = this.fitCharacters( line.range, ruler, position.left ),
+       var lineRuler = this.rulers.line,
+               fit = this.fitCharacters( line.range, position.left ),
                center;
-       ruler.innerHTML = this.getHtml( new ve.Range( line.range.start, fit.end 
) );
+       lineRuler.innerHTML = this.getHtml( new ve.Range( line.range.start, 
fit.end ) );
        if ( fit.end < this.model.getContentLength() ) {
-               var left = ruler.clientWidth;
-               ruler.innerHTML = this.getHtml( new ve.Range( line.range.start, 
fit.end + 1 ) );
-               center = Math.round( left + ( ( ruler.clientWidth - left ) / 2 
) );
+               var left = lineRuler.clientWidth;
+               lineRuler.innerHTML = this.getHtml( new ve.Range( 
line.range.start, fit.end + 1 ) );
+               center = Math.round( left + ( ( lineRuler.clientWidth - left ) 
/ 2 ) );
        } else {
-               center = ruler.clientWidth;
+               center = lineRuler.clientWidth;
        }
-       $ruler.remove();
+       // Cleanup ruler contents
+       lineRuler.innerHTML = '';
        // Reset RegExp object's state
        this.boundaryTest.lastIndex = 0;
        return Math.min(
@@ -472,11 +484,11 @@
         * measuring for those cases.
         */
        if ( line.range.start < offset ) {
-               var $ruler = $( '<div class="es-contentView-ruler"></div>' 
).appendTo( this.$ ),
-                       ruler = $ruler[0];
-               ruler.innerHTML = this.getHtml( new ve.Range( line.range.start, 
offset ) );
-               position.left = ruler.clientWidth;
-               $ruler.remove();
+               var lineRuler = this.rulers.line;
+               lineRuler.innerHTML = this.getHtml( new ve.Range( 
line.range.start, offset ) );
+               position.left = lineRuler.clientWidth;
+               // Cleanup ruler contents
+               lineRuler.innerHTML = '';
        }
        return position;
 };
@@ -546,7 +558,10 @@
                charFit = null,
                wordCount = this.boundaries.length;
        while ( ++iteration <= limit && rs.wordOffset < wordCount - 1 ) {
-               wordFit = this.fitWords( new ve.Range( rs.wordOffset, wordCount 
- 1 ), rs.ruler, rs.width );
+               // Get the width from the edges of left and right floated DIV 
elements in the container
+               rs.width = rs.rulers.right.offsetLeft - 
rs.rulers.left.offsetLeft;
+               // Fit words on the line
+               wordFit = this.fitWords( new ve.Range( rs.wordOffset, wordCount 
- 1 ), rs.width );
                fractional = false;
                if ( wordFit.width > rs.width ) {
                        // The first word didn't fit, we need to split it up
@@ -555,15 +570,12 @@
                        rs.wordOffset++;
                        lineEnd = this.boundaries[rs.wordOffset];
                        do {
-                               charFit = this.fitCharacters(
-                                       new ve.Range( charOffset, lineEnd ), 
rs.ruler, rs.width
-                               );
+                               charFit = this.fitCharacters( new ve.Range( 
charOffset, lineEnd ), rs.width );
                                // If we were able to get the rest of the 
characters on the line OK
                                if ( charFit.end === lineEnd) {
                                        // Try to fit more words on the line
                                        wordFit = this.fitWords(
                                                new ve.Range( rs.wordOffset, 
wordCount - 1 ),
-                                               rs.ruler,
                                                rs.width - charFit.width
                                        );
                                        if ( wordFit.end > rs.wordOffset ) {
@@ -587,18 +599,20 @@
        }
        // Only perform on actual last iteration
        if ( rs.wordOffset >= wordCount - 1 ) {
-               // Cleanup
-               rs.$ruler.remove();
+               // Cleanup ruler contents
+               rs.rulers.line.innerHTML = '';
+               // Cleanup line meta data
                if ( rs.line < this.lines.length ) {
                        this.lines.splice( rs.line, this.lines.length - rs.line 
);
                }
+               // Cleanup unused lines in the DOM
                this.$.find( '.es-contentView-line[line-index=' + ( 
this.lines.length - 1 ) + ']' )
-                       .nextAll()
+                       .nextAll( '.es-contentView-line' )
                        .remove();
                rs.timeout = undefined;
                this.emit( 'update' );
        } else {
-               rs.ruler.innerHTML = '';
+               rs.rulers.line.innerHTML = '';
                var that = this;
                rs.timeout = setTimeout( function() {
                        that.renderIteration( 3 );
@@ -626,20 +640,19 @@
        if ( rs.timeout !== undefined ) {
                // Cancel the active rendering process
                clearTimeout( rs.timeout );
-               // Cleanup
-               rs.$ruler.remove();
        }
        // Clear caches that were specific to the previous render
        this.widthCache = {};
        // In case of empty content model we still want to display empty with 
non-breaking space inside
        // This is very important for lists
        if(this.model.getContentLength() === 0) {
+               // Remove all lines
+               this.$.children().remove( '.es-contentView-line' );
+               // Create a new line
                var $line = $( '<div class="es-contentView-line" 
line-index="0">&nbsp;</div>' );
-               this.$
-                       .children()
-                               .remove( '.es-contentView-line' )
-                               .end()
-                       .append( $line );
+               // Insert the new line between ranges and rulers
+               this.$rulers.before( $line );
+               // Store meta data about the line for later
                this.lines = [{
                        'text': ' ',
                        'range': new ve.Range( 0,0 ),
@@ -655,12 +668,17 @@
         * Container measurement
         * 
         * To get an accurate measurement of the inside of the container, 
without having to deal with
-        * inconsistencies between browsers and box models, we can just create 
an element inside the
-        * container and measure it.
+        * inconsistencies between browsers and box models, we can just create 
elements inside the
+        * container and measure them. There are three rulers, a line ruler 
used for checking if a line
+        * of text fits or not, and a set of left and right boundary rulers 
which are floated left and
+        * right respectively and get the effective width of a line after the 
browser has done it's
+        * job with laying out floating content.
         */
-       rs.$ruler = $( '<div>&nbsp;</div>' ).appendTo( this.$ );
-       rs.width = rs.$ruler.innerWidth();
-       rs.ruler = rs.$ruler.addClass('es-contentView-ruler')[0];
+       rs.rulers = this.rulers;
+
+       // TODO: Re-implement render-from offset in a way that will take into 
consideration that not all
+       // lines are the same width. This is a very important optimization.
+       /*
        // Ignore offset optimization if the width has changed or the text has 
never been flowed before
        if (this.width !== rs.width) {
                offset = undefined;
@@ -684,10 +702,11 @@
                }
                this.renderIteration( 2 + gap );
        } else {
+       */
                rs.line = 0;
                rs.wordOffset = 0;
                this.renderIteration( 3 );
-       }
+       //}
 };
 
 /**
@@ -704,10 +723,8 @@
        var rs = this.renderState,
                $line = this.$.children( '[line-index=' + rs.line + ']' );
        if ( !$line.length ) {
-               $line = $(
-                       '<div class="es-contentView-line" line-index="' + 
rs.line + '"></div>'
-               );
-               this.$.append( $line );
+               $line = $( '<div class="es-contentView-line" line-index="' + 
rs.line + '"></div>' );
+               this.$rulers.before( $line );
        }
        $line[0].innerHTML = this.getHtml( range );
        // Overwrite/append line information
@@ -748,11 +765,10 @@
  * 
  * @method
  * @param {ve.Range} range Range of data within content model to try to fit
- * @param {HTMLElement} ruler Element to take measurements with
  * @param {Integer} width Maximum width to allow the line to extend to
  * @returns {Integer} Last index within "words" that contains a word that fits
  */
-ve.es.Content.prototype.fitWords = function( range, ruler, width ) {
+ve.es.Content.prototype.fitWords = function( range, width ) {
        var offset = range.start,
                start = range.start,
                end = range.end,
@@ -768,9 +784,9 @@
                // Measure and cache width of substring
                cacheKey = charOffset + ':' + charMiddle;
                // Prepare the line for measurement using pre-escaped HTML
-               ruler.innerHTML = this.getHtml( new ve.Range( charOffset, 
charMiddle ) );
+               this.rulers.line.innerHTML = this.getHtml( new ve.Range( 
charOffset, charMiddle ) );
                // Test for over/under using width of the rendered line
-               this.widthCache[cacheKey] = lineWidth = ruler.clientWidth;
+               this.widthCache[cacheKey] = lineWidth = 
this.rulers.line.clientWidth;
                // Test for over/under using width of the rendered line
                if ( lineWidth > width ) {
                        // Detect impossible fit (the first word won't fit by 
itself)
@@ -789,8 +805,8 @@
        if ( end === middle - 1 ) {
                // A final measurement is required
                var charStart = this.boundaries[start];
-               ruler.innerHTML = this.getHtml( new ve.Range( charOffset, 
charStart ) );
-               lineWidth = this.widthCache[charOffset + ':' + charStart] = 
ruler.clientWidth;
+               this.rulers.line.innerHTML = this.getHtml( new ve.Range( 
charOffset, charStart ) );
+               lineWidth = this.widthCache[charOffset + ':' + charStart] = 
this.rulers.line.clientWidth;
        }
        return { 'end': start, 'width': lineWidth };
 };
@@ -804,11 +820,10 @@
  * 
  * @method
  * @param {ve.Range} range Range of data within content model to try to fit
- * @param {HTMLElement} ruler Element to take measurements with
  * @param {Integer} width Maximum width to allow the line to extend to
  * @returns {Integer} Last index within "text" that contains a character that 
fits
  */
-ve.es.Content.prototype.fitCharacters = function( range, ruler, width ) {
+ve.es.Content.prototype.fitCharacters = function( range, width ) {
        var offset = range.start,
                start = range.start,
                end = range.end,
@@ -824,9 +839,9 @@
                        lineWidth = this.widthCache[cacheKey];
                } else {
                        // Fill the line with a portion of the text, escaped as 
HTML
-                       ruler.innerHTML = this.getHtml( new ve.Range( offset, 
middle ) );
+                       this.rulers.line.innerHTML = this.getHtml( new 
ve.Range( offset, middle ) );
                        // Test for over/under using width of the rendered line
-                       this.widthCache[cacheKey] = lineWidth = 
ruler.clientWidth;
+                       this.widthCache[cacheKey] = lineWidth = 
this.rulers.line.clientWidth;
                }
                if ( lineWidth > width ) {
                        // Detect impossible fit (the first character won't fit 
by itself)
@@ -849,8 +864,8 @@
                        lineWidth = this.widthCache[cacheKey];
                } else {
                        // A final measurement is required
-                       ruler.innerHTML = this.getHtml( new ve.Range( offset, 
start ) );
-                       lineWidth = this.widthCache[cacheKey] = 
ruler.clientWidth;
+                       this.rulers.line.innerHTML = this.getHtml( new 
ve.Range( offset, start ) );
+                       lineWidth = this.widthCache[cacheKey] = 
this.rulers.line.clientWidth;
                }
        }
        return { 'end': start, 'width': lineWidth };


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

Reply via email to