http://www.mediawiki.org/wiki/Special:Code/MediaWiki/89832
Revision: 89832
Author: tparscal
Date: 2011-06-10 16:44:47 +0000 (Fri, 10 Jun 2011)
Log Message:
-----------
* Rewrote jquery.flow-a - Now uses character and word measurement caching!
* Modified selection code to select word boundaries first, then characters,
greatly improving accuracy
* Removed jquery.flow-b
* Renamed flow-a to jquery.flow
Modified Paths:
--------------
trunk/parsers/wikidom/demos/surface/index.html
trunk/parsers/wikidom/lib/jquery.editSurface.js
Added Paths:
-----------
trunk/parsers/wikidom/lib/jquery.flow.js
Removed Paths:
-------------
trunk/parsers/wikidom/lib/jquery.flow-a.js
trunk/parsers/wikidom/lib/jquery.flow-b.js
Modified: trunk/parsers/wikidom/demos/surface/index.html
===================================================================
--- trunk/parsers/wikidom/demos/surface/index.html 2011-06-10 16:40:29 UTC
(rev 89831)
+++ trunk/parsers/wikidom/demos/surface/index.html 2011-06-10 16:44:47 UTC
(rev 89832)
@@ -13,7 +13,7 @@
<!-- EditSurface -->
<script type="text/javascript"
src="../../lib/jquery.js"></script>
<script type="text/javascript"
src="../../lib/jquery.closestToOffset.js"></script>
- <script type="text/javascript"
src="../../lib/jquery.flow-a.js"></script>
+ <script type="text/javascript"
src="../../lib/jquery.flow.js"></script>
<script type="text/javascript"
src="../../lib/jquery.editSurface.js"></script>
<!-- Demo -->
@@ -28,7 +28,7 @@
"Word wrap following hyphens is
sometimes not desired, and can be avoided by using a so-called non-breaking
hyphen instead of a regular hyphen. On the other hand, when using word
processors, invisible hyphens, called soft hyphens, can also be inserted inside
words so that word wrap can occur following the soft hyphens.",
"Sometimes, word wrap is not desirable
between words. In such cases, word wrap can usually be avoided by using a hard
space or non-breaking space between the words, instead of regular spaces.",
"OccasionallyThereAreWordsThatAreSoLongTheyExceedTheWidthOfTheLineAndEndUpWrappingBetweenMultipleLines.",
- ].join( '\n\n' );
+ ].join( ' ' );
$( '#es' ).editSurface( {
'document': { 'blocks': [ {
'type': 'paragraph',
Modified: trunk/parsers/wikidom/lib/jquery.editSurface.js
===================================================================
--- trunk/parsers/wikidom/lib/jquery.editSurface.js 2011-06-10 16:40:29 UTC
(rev 89831)
+++ trunk/parsers/wikidom/lib/jquery.editSurface.js 2011-06-10 16:44:47 UTC
(rev 89832)
@@ -49,7 +49,7 @@
.mouseup( function( e ) {
if ( sel.active ) {
if ( !sel.from || !sel.to
- || ( sel.from.line ===
sel.to.line && sel.from.index === sel.to.index ) ) {
+ || ( sel.from.line ===
sel.to.line && sel.from.char === sel.to.char ) ) {
sel.from = null;
sel.to = null;
sel.start = null;
@@ -71,9 +71,12 @@
);
}
sel.end = getCursorPosition( e.pageX, e.pageY,
$target );
+ //console.log( [sel.start.char, sel.end.char] );
+ //console.log( [sel.start.word, sel.end.word] );
+ //console.log( [sel.start.line, sel.end.line] );
if ( sel.start.line < sel.end.line
|| ( sel.start.line ===
sel.end.line
- &&
sel.start.index < sel.end.index ) ) {
+ &&
sel.start.char < sel.end.char ) ) {
sel.from = sel.start;
sel.to = sel.end;
} else {
@@ -85,7 +88,6 @@
}
} );
-
// Shortcuts
var $document = $this.find( '.editSurface-document' );
var ranges = {
@@ -100,47 +102,56 @@
var text;
if ( sel.from && sel.to ) {
if ( sel.from.line === sel.to.line ) {
- text = sel.from.$target.data( 'text' ).substr(
- sel.from.index, sel.to.index -
sel.from.index
+ text = sel.from.$target.data( 'flow'
).text.substr(
+ sel.from.char, sel.to.char -
sel.from.char
);
} else {
- text = sel.from.$target.data( 'text' ).substr(
sel.from.index );
+ text = sel.from.$target.data( 'flow'
).text.substr( sel.from.char );
var $sibling = sel.from.$target.next();
for ( var i = sel.from.line + 1; i <
sel.to.line; i++ ) {
- text += $sibling.data( 'text' )
+ text += $sibling.data( 'flow' ).text
$sibling = $sibling.next();
}
- text += sel.to.$target.data( 'text' ).substr(
0, sel.to.index );
+ text += sel.to.$target.data( 'flow'
).text.substr( 0, sel.to.char );
}
}
return text;
}
function getCursorPosition( x, y, $target ) {
- var metrics = $target.data( 'metrics' );
- var text = $target.data( 'text' );
- var line = $target.data( 'line' );
- if ( !$.isArray( metrics ) || metrics.length === 0 ) {
- throw "Missing metrics data error"
- }
- var to = metrics.length - 1;
- var a;
- var b = { 'l': 0, 'c': 0, 'r': 0 };
- var c = x - $target.offset().left;
- for ( var i = 0; i <= to; i++ ) {
- a = b;
- b = { 'l': a.r, 'c': a.r + ( metrics[i] / 2 ), 'r': a.r
+ metrics[i] };
- if ( ( i === 0 && c <= a.l ) || ( c >= a.c && c <= b.c
) || i === to ) {
- var offset = $target.offset();
- var height = $target.height();
- return {
- '$target': $target,
- 'index': i,
- 'line': line,
- 'x': offset.left + b.l,
- 'top': offset.top,
- 'bottom': offset.top + height,
- 'height': height
- };
+ var line = $target.data( 'flow' ),
+ offset = $target.offset(),
+ height = $target.height(),
+ l,
+ r = 0,
+ cur = x - offset.left;
+ for ( var w = 0, eol = line.metrics.length; w <= eol; w++ ) {
+ var wi = Math.min( w, eol - 1 );
+ l = r;
+ r += line.metrics[wi];
+ if ( ( w === 0 && cur <= l ) || ( cur >= l && cur <= r
) || ( w === eol ) ) {
+ var word = line.words[wi],
+ a,
+ b = { 'l': l, 'c': l, 'r': l };
+ for ( var c = 0, eow = word.metrics.length; c
<= eow; c++ ) {
+ a = b;
+ b = {
+ 'l': a.r,
+ 'c': a.r + ( word.metrics[c] /
2 ),
+ 'r': a.r + word.metrics[c]
+ };
+ if ( ( c === 0 && cur <= a.l ) || ( cur
>= a.c && cur <= b.c ) || c === eow ) {
+ return {
+ '$target': $target,
+ 'char': word.offset +
Math.min( c, word.text.length - 1 ),
+ 'word': word.index,
+ 'line': line.index,
+ 'x': offset.left + ( c
< eow ? b.l : a.l ),
+ 'top': offset.top,
+ 'bottom': offset.top +
height,
+ 'height': height
+ };
+ }
+ }
}
}
}
@@ -153,7 +164,7 @@
if ( sel.from && sel.to ) {
if ( sel.from.line === sel.to.line ) {
// 1 line
- if ( sel.from.index !== sel.to.index ) {
+ if ( sel.from.char !== sel.to.char ) {
ranges.$first.show().css( {
'left': sel.from.x,
'top': sel.from.top,
Deleted: trunk/parsers/wikidom/lib/jquery.flow-a.js
===================================================================
--- trunk/parsers/wikidom/lib/jquery.flow-a.js 2011-06-10 16:40:29 UTC (rev
89831)
+++ trunk/parsers/wikidom/lib/jquery.flow-a.js 2011-06-10 16:44:47 UTC (rev
89832)
@@ -1,97 +0,0 @@
-/*
- * Flow jQuery plugin
- */
-
-$.flow = { 'cache': { 'chars': {}, 'words': {} } };
-
-$.fn.flow = function( text ) {
- console.time( 'flow' );
-
- var $this = $(this);
- var lineLimit = $this.innerWidth();
-
- // Wordify
- var words = [],
- word = { 'text': '', 'width': 0, 'metrics': [] };
- for ( var i = 0; i < text.length; i++ ) {
- var char = text[i];
- // Boundary detection
- var boundary = String( ' -\t\r\n\f' ).indexOf( char ) >= 0;
- // Encoding
- var charHtml = char
- .replace( '&', '&' )
- .replace( ' ', ' ' )
- .replace( '<', '<' )
- .replace( '>', '>' )
- .replace( '\'', ''' )
- .replace( '"', '"' );
- // Measurement
- var charWidth;
- if ( typeof $.flow.cache.chars[char] === 'undefined' ) {
- charWidth = $.flow.cache.chars[char] =
- $( '<div class="editSurface-line">' + charHtml
+ '</div>' )
- .appendTo( $this ).width();
- } else {
- charWidth = $.flow.cache.chars[char];
- }
- // Virtual boundary
- if ( word.width + charWidth >= lineLimit ) {
- words[words.length] = word;
- word = { 'text': '', 'width': 0, 'metrics': [] };
- }
- // Append
- if ( boundary ) {
- if ( word.text.length ) {
- words[words.length] = word;
- word = { 'text': '', 'width': 0, 'metrics': []
};
- }
- words[words.length] = { 'text': char, 'width':
charWidth, 'metrics': [charWidth] };
- } else {
- word.text += char;
- word.width += charWidth;
- word.metrics[word.metrics.length] = charWidth;
- }
- }
- if ( word.text.length ) {
- words[words.length] = word;
- }
-
- // Lineify
- var lines = [],
- line = { 'text': '', 'width': 0, 'metrics': [] };
- for ( var i = 0; i < words.length; i++ ) {
- var hardReturn = String( '\r\n\f' ).indexOf( words[i].text ) >=
0;
- if ( line.width + words[i].width > lineLimit || hardReturn ) {
- lines[lines.length] = line;
- line = { 'text': '', 'width': 0, 'metrics': [] };
- }
- if ( !hardReturn && ( line.width > 0 || words[i].text !== ' ' )
) {
- line.text += words[i].text;
- line.width += words[i].width;
- line.metrics = line.metrics.concat( words[i].metrics );
- }
- }
- if ( line.text.length ) {
- lines[lines.length] = line;
- }
-
- // Flow
- $this.empty();
- for ( var i = 0; i < lines.length; i++ ) {
- var $line = $( '<div class="editSurface-line"></div>' )
- .data( 'metrics', lines[i].metrics )
- .data( 'text', lines[i].text )
- .data( 'line', i );
- if ( lines[i].text.length ) {
- $line.text( lines[i].text );
- } else {
- $line.html( ' ' );
- $line.addClass( 'empty' );
- }
- $this.append( $line );
- }
-
- console.timeEnd( 'flow' );
-
- return $this;
-};
\ No newline at end of file
Deleted: trunk/parsers/wikidom/lib/jquery.flow-b.js
===================================================================
--- trunk/parsers/wikidom/lib/jquery.flow-b.js 2011-06-10 16:40:29 UTC (rev
89831)
+++ trunk/parsers/wikidom/lib/jquery.flow-b.js 2011-06-10 16:44:47 UTC (rev
89832)
@@ -1,94 +0,0 @@
-/*
- * Flow jQuery plugin
- */
-
-$.flow = { 'widthCache': {} };
-
-$.fn.flow = function( text ) {
- console.time( 'flow' );
-
- function encodeHtml( c ) {
- return c.replace( /\&/g, '&' )
- .replace( /</g, '<' )
- .replace( />/g, '>' );
- }
-
- var breakableRe = /[\s\r\n\f]/;
-
- var $this = $(this);
-
- $this.empty();
-
- var width = $this.innerWidth();
- var pos = 0;
- var line = 0;
-
- while( pos < text.length ) {
- var lineStartPos = pos;
- var breakPos = pos;
-
- var $line = $( '<div class="editSurface-line"></div>'
).appendTo( $this );
- var lineElem = $line.get(0);
- var lineText = '';
- var lineMetrics = [];
- var lineWidth = 0;
- var lastLineWidth = 0;
-
- while ( pos < text.length && lineWidth < width ) {
- // Append text
- var c = text.charAt( pos );
- lineText += c;
- lineElem.innerHTML = encodeHtml( lineText );
-
- // Get new line width from DOM
- lastLineWidth = lineWidth;
- // $.innerWidth call is expensive. Assume padding and
border = 0 and this should be okay
- lineWidth = lineElem.offsetWidth;
- // Push difference (character width)
- lineMetrics.push( lineWidth - lastLineWidth );
-
- if ( breakableRe( c ) ) {
- breakPos = pos;
- }
-
- pos++;
- }
-
- if ( lineWidth >= width ) {
- if ( breakPos === lineStartPos ) {
- // There was no breakable position between the
start of the line and here, so we
- // have some kind of long word. Or, the line
width is very small. Break at the
- // previous character.
- pos -= 1;
- breakPos = pos;
- } else {
- // Include the breaking character in the
previous line
- // TODO: How does this work with hyphens? Won't
they be to far right?
- breakPos++;
- }
- // Move the position back to the last safe location
- pos = breakPos;
- // Truncate characters that won't fit
- lineText = text.substring( lineStartPos, breakPos );
- lineElem.innerHTML = encodeHtml( lineText );
- // Don't leave metrics from truncated characters around
- lineMetrics = lineMetrics.slice( 0, pos - lineStartPos
);
- }
-
- $line
- .data( 'metrics', lineMetrics )
- .data( 'text', lineText )
- .data( 'line', line );
-
- if ( lineStartPos === pos ) {
- lineElem.innerHtml = ' ';
- }
-
- line++;
- }
-
- console.timeEnd( 'flow' );
-
- return $this;
-};
-
Added: trunk/parsers/wikidom/lib/jquery.flow.js
===================================================================
--- trunk/parsers/wikidom/lib/jquery.flow.js (rev 0)
+++ trunk/parsers/wikidom/lib/jquery.flow.js 2011-06-10 16:44:47 UTC (rev
89832)
@@ -0,0 +1,185 @@
+/*
+ * Flow jQuery plugin
+ *
+ * Each line has data in the following structure, embedded as $line.data(
'flow' )
+ * {
+ * index: 0,
+ * text: 'abc 123',
+ * metrics: [9,4,9]
+ * width: 22,
+ * words: [
+ * {
+ * text: 'abc',
+ * html: 'abc',
+ * metrics: [3,3,3],
+ * width: 9,
+ * index: 0,
+ * offset: 0
+ * },
+ * {
+ * text: ' ',
+ * html: ' ',
+ * metrics: [4],
+ * width: 4,
+ * index: 1,
+ * offset: 3
+ * },
+ * {
+ * text: '123',
+ * html: '123',
+ * metrics: [3,3,3]
+ * width: 9,
+ * index: 2,
+ * offset: 4
+ * }
+ * ]
+ * }
+ */
+
+function copy( from, to ) {
+ if ( to === undefined ) {
+ to = {};
+ }
+ if ( from == null || typeof from != 'object' ) {
+ return from;
+ }
+ if ( from.constructor != Object && from.constructor != Array ) {
+ return from;
+ }
+ if ( from.constructor == Date
+ || from.constructor == RegExp
+ || from.constructor == Function
+ || from.constructor == String
+ || from.constructor == Number
+ || from.constructor == Boolean ) {
+ return new from.constructor( from );
+ }
+ to = to || new from.constructor();
+ for ( var name in from ) {
+ to[name] = typeof to[name] == 'undefined' ? copy( from[name],
null ) : to[name];
+ }
+ return to;
+}
+
+$.flow = {
+ 'charCache': {},
+ 'wordCache': {},
+ 'measureWord': function( text, ruler ) {
+ if ( $.flow.wordCache[text] === undefined ) {
+ // Cache miss
+ var word = { 'text': text, 'html': '', 'metrics': [] };
+ for ( var i = 0; i < text.length; i++ ) {
+ var char = text[i],
+ charHtml = char
+ .replace( '&', '&' )
+ .replace( ' ', ' ' )
+ .replace( '<', '<' )
+ .replace( '>', '>' )
+ .replace( '\'', ''' )
+ .replace( '"', '"' );
+ word.html += charHtml;
+ if ( $.flow.charCache[char] === undefined ) {
+ // Cache miss
+ ruler.innerHTML = charHtml;
+ word.metrics.push(
$.flow.charCache[char] = ruler.clientWidth );
+ continue;
+ }
+ // Cache hit
+ word.metrics.push( $.flow.charCache[char] );
+ }
+ ruler.innerHTML = word.html;
+ word.width = ruler.clientWidth;
+ $.flow.wordCache[text] = copy( word );
+ return word;
+ }
+ // Cache hit
+ return copy( $.flow.wordCache[text] );
+ },
+ 'getWords': function( text, ruler ) {
+ var words = [],
+ bounadry = /[ \-\t\r\n\f]/,
+ left = 0,
+ right = 0,
+ search = 0;
+ while ( ( search = text.substr( right ).search( bounadry ) ) >=
0 ) {
+ right += search;
+ words.push( $.flow.measureWord( text.substring( left,
right ), ruler ) );
+ if ( right < text.length ) {
+ words.push( $.flow.measureWord( text.substring(
right, ++right ), ruler ) );
+ }
+ left = right;
+ }
+ words.push( $.flow.measureWord( text.substring( right,
text.length ), ruler ) );
+ return words;
+ },
+ 'getLines': function( words, width ) {
+ // Lineify
+ var lineCount = 0,
+ charCount = 0,
+ wordCount = 0,
+ lines = [],
+ line = {
+ 'text': '',
+ 'html': '',
+ 'width': 0,
+ 'metrics': [],
+ 'words': [],
+ 'index': lineCount
+ };
+ for ( var i = 0; i < words.length; i++ ) {
+ if ( line.width + words[i].width > width ) {
+ lines.push( line );
+ charCount = 0;
+ wordCount = 0;
+ lineCount++;
+ line = {
+ 'text': '',
+ 'html': '',
+ 'width': 0,
+ 'metrics': [],
+ 'words': [],
+ 'index': lineCount
+ };
+ }
+ words[i].index = wordCount;
+ wordCount++;
+ words[i].offset = charCount;
+ charCount += words[i].text.length;
+ line.words.push( words[i] );
+ line.text += words[i].text;
+ line.html += words[i].html;
+ line.width += words[i].width;
+ line.metrics.push( words[i].width );
+ }
+ if ( line.text.length ) {
+ lines.push( line );
+ }
+ return lines;
+ }
+};
+
+$.fn.flow = function( text ) {
+ console.time( 'flow' );
+
+ var $this = $(this),
+ lines = $.flow.getLines(
+ $.flow.getWords( text, $( '<div
class="editSurface-line"></div>' ).appendTo( $this )[0] ),
+ $this.innerWidth()
+ );
+
+ // Flow
+ $this.empty();
+ for ( var i = 0; i < lines.length; i++ ) {
+ var $line = $( '<div class="editSurface-line"></div>' ).data(
'flow', lines[i] );
+ if ( lines[i].text.length === 1 && lines[1].text.match( /[
\-\t\r\n\f]/ ) ) {
+ $line.html( ' ' );
+ $line.addClass( 'empty' );
+ } else {
+ $line.html( lines[i].html );
+ }
+ $this.append( $line );
+ }
+
+ console.timeEnd( 'flow' );
+ return $this;
+};
Property changes on: trunk/parsers/wikidom/lib/jquery.flow.js
___________________________________________________________________
Added: svn:mime-type
+ text/plain
Added: svn:eol-style
+ native
_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs