Index: trunk/extensions/VisualEditor/demos/ce/main.js |
— | — | @@ -565,7 +565,7 @@ |
566 | 566 | window.documentModel = ve.dm.DocumentNode.newFromPlainObject( wikidoms['Wikipedia article'] ); |
567 | 567 | window.surfaceModel = new ve.dm.Surface( window.documentModel ); |
568 | 568 | window.surfaceView = new ve.es.Surface( $( '#es-editor' ), window.surfaceModel ); |
569 | | - window.toolbarView = new ve.ui.Toolbar( $( '#es-toolbar' ), window.surfaceView ); |
| 569 | + window.toolbarView = new ve.ui.Toolbar( $( '#es-toolbar' ), window.surfaceView, [] ); |
570 | 570 | window.contextView = new ve.ui.Context( window.surfaceView ); |
571 | 571 | window.surfaceModel.select( new ve.Range( 1, 1 ) ); |
572 | 572 | |
Index: trunk/extensions/VisualEditor/modules/ve/ce/ve.es.Content.js |
— | — | @@ -42,17 +42,9 @@ |
43 | 43 | // Events |
44 | 44 | var _this = this; |
45 | 45 | this.model.on( 'update', function( offset ) { |
46 | | - _this.scanBoundaries(); |
47 | 46 | _this.render( offset || 0 ); |
48 | 47 | } ); |
49 | 48 | |
50 | | - // DOM Changes |
51 | | - this.$ranges = $( '<div class="es-contentView-ranges"></div>' ); |
52 | | - this.$rangeStart = $( '<div class="es-contentView-range"></div>' ); |
53 | | - this.$rangeFill = $( '<div class="es-contentView-range"></div>' ); |
54 | | - this.$rangeEnd = $( '<div class="es-contentView-range"></div>' ); |
55 | | - this.$.prepend( this.$ranges.append( this.$rangeStart, this.$rangeFill, this.$rangeEnd ) ); |
56 | | - |
57 | 49 | // Initialization |
58 | 50 | this.scanBoundaries(); |
59 | 51 | } |
— | — | @@ -211,277 +203,6 @@ |
212 | 204 | /* Methods */ |
213 | 205 | |
214 | 206 | /** |
215 | | - * Draws selection around a given range of content. |
216 | | - * |
217 | | - * @method |
218 | | - * @param {ve.Range} range Range to draw selection around |
219 | | - */ |
220 | | -ve.es.Content.prototype.drawSelection = function( range ) { |
221 | | - if ( typeof range === 'undefined' ) { |
222 | | - range = new ve.Range( 0, this.model.getContentLength() ); |
223 | | - } else { |
224 | | - range.normalize(); |
225 | | - } |
226 | | - var fromLineIndex = this.getRenderedLineIndexFromOffset( range.start ), |
227 | | - toLineIndex = this.getRenderedLineIndexFromOffset( range.end ), |
228 | | - fromPosition = this.getRenderedPositionFromOffset( range.start ), |
229 | | - toPosition = this.getRenderedPositionFromOffset( range.end ); |
230 | | - |
231 | | - if ( fromLineIndex === toLineIndex ) { |
232 | | - // Single line selection |
233 | | - if ( toPosition.left - fromPosition.left ) { |
234 | | - this.$rangeStart.css( { |
235 | | - 'top': fromPosition.top, |
236 | | - 'left': fromPosition.left, |
237 | | - 'width': toPosition.left - fromPosition.left, |
238 | | - 'height': fromPosition.bottom - fromPosition.top |
239 | | - } ).show(); |
240 | | - } |
241 | | - this.$rangeFill.hide(); |
242 | | - this.$rangeEnd.hide(); |
243 | | - } else { |
244 | | - // Multiple line selection |
245 | | - var contentWidth = this.$.width(); |
246 | | - if ( contentWidth - fromPosition.left ) { |
247 | | - this.$rangeStart.css( { |
248 | | - 'top': fromPosition.top, |
249 | | - 'left': fromPosition.left, |
250 | | - 'width': contentWidth - fromPosition.left, |
251 | | - 'height': fromPosition.bottom - fromPosition.top |
252 | | - } ).show(); |
253 | | - } else { |
254 | | - this.$rangeStart.hide(); |
255 | | - } |
256 | | - if ( toPosition.left ) { |
257 | | - this.$rangeEnd.css( { |
258 | | - 'top': toPosition.top, |
259 | | - 'left': 0, |
260 | | - 'width': toPosition.left, |
261 | | - 'height': toPosition.bottom - toPosition.top |
262 | | - } ).show(); |
263 | | - } else { |
264 | | - this.$rangeEnd.hide(); |
265 | | - } |
266 | | - if ( fromLineIndex + 1 < toLineIndex ) { |
267 | | - this.$rangeFill.css( { |
268 | | - 'top': fromPosition.bottom, |
269 | | - 'left': 0, |
270 | | - 'width': contentWidth, |
271 | | - 'height': toPosition.top - fromPosition.bottom |
272 | | - } ).show(); |
273 | | - } else { |
274 | | - this.$rangeFill.hide(); |
275 | | - } |
276 | | - } |
277 | | -}; |
278 | | - |
279 | | -/** |
280 | | - * Clears selection if any was drawn. |
281 | | - * |
282 | | - * @method |
283 | | - */ |
284 | | -ve.es.Content.prototype.clearSelection = function() { |
285 | | - this.$rangeStart.hide(); |
286 | | - this.$rangeFill.hide(); |
287 | | - this.$rangeEnd.hide(); |
288 | | -}; |
289 | | - |
290 | | -/** |
291 | | - * Gets the index of the rendered line a given offset is within. |
292 | | - * |
293 | | - * Offsets that are out of range will always return the index of the last line. |
294 | | - * |
295 | | - * @method |
296 | | - * @param {Integer} offset Offset to get line for |
297 | | - * @returns {Integer} Index of rendered lin offset is within |
298 | | - */ |
299 | | -ve.es.Content.prototype.getRenderedLineIndexFromOffset = function( offset ) { |
300 | | - for ( var i = 0; i < this.lines.length; i++ ) { |
301 | | - if ( this.lines[i].range.containsOffset( offset ) ) { |
302 | | - return i; |
303 | | - } |
304 | | - } |
305 | | - return this.lines.length - 1; |
306 | | -}; |
307 | | - |
308 | | -/* |
309 | | - * Gets the index of the rendered line closest to a given position. |
310 | | - * |
311 | | - * If the position is above the first line, the offset will always be 0, and if the position is |
312 | | - * below the last line the offset will always be the content length. All other vertical |
313 | | - * positions will fall inside of one of the lines. |
314 | | - * |
315 | | - * @method |
316 | | - * @returns {Integer} Index of rendered line closest to position |
317 | | - */ |
318 | | -ve.es.Content.prototype.getRenderedLineIndexFromPosition = function( position ) { |
319 | | - var lineCount = this.lines.length; |
320 | | - // Positions above the first line always jump to the first offset |
321 | | - if ( !lineCount || position.top < 0 ) { |
322 | | - return 0; |
323 | | - } |
324 | | - // Find which line the position is inside of |
325 | | - var i = 0, |
326 | | - top = 0; |
327 | | - while ( i < lineCount ) { |
328 | | - top += this.lines[i].height; |
329 | | - if ( position.top < top ) { |
330 | | - break; |
331 | | - } |
332 | | - i++; |
333 | | - } |
334 | | - // Positions below the last line always jump to the last offset |
335 | | - if ( i === lineCount ) { |
336 | | - return i - 1; |
337 | | - } |
338 | | - return i; |
339 | | -}; |
340 | | - |
341 | | -/** |
342 | | - * Gets the range of the rendered line a given offset is within. |
343 | | - * |
344 | | - * Offsets that are out of range will always return the range of the last line. |
345 | | - * |
346 | | - * @method |
347 | | - * @param {Integer} offset Offset to get line for |
348 | | - * @returns {ve.Range} Range of line offset is within |
349 | | - */ |
350 | | -ve.es.Content.prototype.getRenderedLineRangeFromOffset = function( offset ) { |
351 | | - for ( var i = 0; i < this.lines.length; i++ ) { |
352 | | - if ( this.lines[i].range.containsOffset( offset ) ) { |
353 | | - return this.lines[i].range; |
354 | | - } |
355 | | - } |
356 | | - return this.lines[this.lines.length - 1].range; |
357 | | -}; |
358 | | - |
359 | | -/** |
360 | | - * Gets offset within content model closest to of a given position. |
361 | | - * |
362 | | - * Position is assumed to be local to the container the text is being flowed in. |
363 | | - * |
364 | | - * @method |
365 | | - * @param {Object} position Position to find offset for |
366 | | - * @param {Integer} position.left Horizontal position in pixels |
367 | | - * @param {Integer} position.top Vertical position in pixels |
368 | | - * @returns {Integer} Offset within content model nearest the given coordinates |
369 | | - */ |
370 | | -ve.es.Content.prototype.getOffsetFromRenderedPosition = function( position ) { |
371 | | - // Empty content model shortcut |
372 | | - if ( this.model.getContentLength() === 0 ) { |
373 | | - return 0; |
374 | | - } |
375 | | - |
376 | | - // Localize position |
377 | | - position.subtract( ve.Position.newFromElementPagePosition( this.$ ) ); |
378 | | - |
379 | | - // Get the line object nearest the position |
380 | | - var line = this.lines[this.getRenderedLineIndexFromPosition( position )]; |
381 | | - |
382 | | - /* |
383 | | - * Offset finding |
384 | | - * |
385 | | - * Now that we know which line we are on, we can just use the "fitCharacters" method to get the |
386 | | - * last offset before "position.left". |
387 | | - * |
388 | | - * TODO: The offset needs to be chosen based on nearest offset to the cursor, not offset before |
389 | | - * the cursor. |
390 | | - */ |
391 | | - var $ruler = $( '<div class="es-contentView-ruler"></div>' ).appendTo( this.$ ), |
392 | | - ruler = $ruler[0], |
393 | | - fit = this.fitCharacters( line.range, ruler, position.left ), |
394 | | - center; |
395 | | - ruler.innerHTML = this.getHtml( new ve.Range( line.range.start, fit.end ) ); |
396 | | - if ( fit.end < this.model.getContentLength() ) { |
397 | | - var left = ruler.clientWidth; |
398 | | - ruler.innerHTML = this.getHtml( new ve.Range( line.range.start, fit.end + 1 ) ); |
399 | | - center = Math.round( left + ( ( ruler.clientWidth - left ) / 2 ) ); |
400 | | - } else { |
401 | | - center = ruler.clientWidth; |
402 | | - } |
403 | | - $ruler.remove(); |
404 | | - // Reset RegExp object's state |
405 | | - this.boundaryTest.lastIndex = 0; |
406 | | - return Math.min( |
407 | | - // If the position is right of the center of the character it's on top of, increment offset |
408 | | - fit.end + ( position.left >= center ? 1 : 0 ), |
409 | | - // Don't allow the value to be higher than the end |
410 | | - line.range.end |
411 | | - ); |
412 | | -}; |
413 | | - |
414 | | -/** |
415 | | - * Gets position coordinates of a given offset. |
416 | | - * |
417 | | - * Offsets are boundaries between plain or annotated characters within content model. Results are |
418 | | - * given in left, top and bottom positions, which could be used to draw a cursor, highlighting, etc. |
419 | | - * |
420 | | - * @method |
421 | | - * @param {Integer} offset Offset within content model |
422 | | - * @returns {Object} Object containing left, top and bottom properties, each positions in pixels as |
423 | | - * well as a line index |
424 | | - */ |
425 | | -ve.es.Content.prototype.getRenderedPositionFromOffset = function( offset, leftBias ) { |
426 | | - /* |
427 | | - * Range validation |
428 | | - * |
429 | | - * Rather than clamping the range, which can hide errors, exceptions will be thrown if offset is |
430 | | - * less than 0 or greater than the length of the content model. |
431 | | - */ |
432 | | - if ( offset < 0 ) { |
433 | | - throw 'Out of range error. Offset is expected to be greater than or equal to 0.'; |
434 | | - } else if ( offset > this.model.getContentLength() ) { |
435 | | - throw 'Out of range error. Offset is expected to be less than or equal to text length.'; |
436 | | - } |
437 | | - /* |
438 | | - * Line finding |
439 | | - * |
440 | | - * It's possible that a more efficient method could be used here, but the number of lines to be |
441 | | - * iterated through will rarely be over 100, so it's unlikely that any significant gains will be |
442 | | - * had. Plus, as long as we are iterating over each line, we can also sum up the top and bottom |
443 | | - * positions, which is a nice benefit of this method. |
444 | | - */ |
445 | | - var line, |
446 | | - lineCount = this.lines.length, |
447 | | - lineIndex = 0, |
448 | | - position = new ve.Position(); |
449 | | - while ( lineIndex < lineCount ) { |
450 | | - line = this.lines[lineIndex]; |
451 | | - if ( line.range.containsOffset( offset ) || ( leftBias && line.range.end === offset ) ) { |
452 | | - position.bottom = position.top + line.height; |
453 | | - break; |
454 | | - } |
455 | | - position.top += line.height; |
456 | | - lineIndex++; |
457 | | - } |
458 | | - /* |
459 | | - * Virtual n+1 position |
460 | | - * |
461 | | - * To allow access to position information of the right side of the last character on the last |
462 | | - * line, a virtual n+1 position is supported. Offsets beyond this virtual position will cause |
463 | | - * an exception to be thrown. |
464 | | - */ |
465 | | - if ( lineIndex === lineCount ) { |
466 | | - position.bottom = position.top; |
467 | | - position.top -= line.height; |
468 | | - } |
469 | | - /* |
470 | | - * Offset measuring |
471 | | - * |
472 | | - * Since the left position will be zero for the first character in the line, so we can skip |
473 | | - * measuring for those cases. |
474 | | - */ |
475 | | - if ( line.range.start < offset ) { |
476 | | - var $ruler = $( '<div class="es-contentView-ruler"></div>' ).appendTo( this.$ ), |
477 | | - ruler = $ruler[0]; |
478 | | - ruler.innerHTML = this.getHtml( new ve.Range( line.range.start, offset ) ); |
479 | | - position.left = ruler.clientWidth; |
480 | | - $ruler.remove(); |
481 | | - } |
482 | | - return position; |
483 | | -}; |
484 | | - |
485 | | -/** |
486 | 207 | * Updates the word boundary cache, which is used for word fitting. |
487 | 208 | * |
488 | 209 | * @method |
— | — | @@ -525,341 +246,11 @@ |
526 | 247 | } |
527 | 248 | }; |
528 | 249 | |
529 | | -/** |
530 | | - * Renders a batch of lines and then yields execution before rendering another batch. |
531 | | - * |
532 | | - * In cases where a single word is too long to fit on a line, the word will be "virtually" wrapped, |
533 | | - * causing them to be fragmented. Word fragments are rendered on their own lines, except for their |
534 | | - * remainder, which is combined with whatever proceeding words can fit on the same line. |
535 | | - * |
536 | | - * @method |
537 | | - * @param {Integer} limit Maximum number of iterations to render before yeilding |
538 | | - */ |
539 | | -ve.es.Content.prototype.renderIteration = function( limit ) { |
540 | | - var rs = this.renderState, |
541 | | - iteration = 0, |
542 | | - fractional = false, |
543 | | - lineStart = this.boundaries[rs.wordOffset], |
544 | | - lineEnd, |
545 | | - wordFit = null, |
546 | | - charOffset = 0, |
547 | | - charFit = null, |
548 | | - wordCount = this.boundaries.length; |
549 | | - while ( ++iteration <= limit && rs.wordOffset < wordCount - 1 ) { |
550 | | - wordFit = this.fitWords( new ve.Range( rs.wordOffset, wordCount - 1 ), rs.ruler, rs.width ); |
551 | | - fractional = false; |
552 | | - if ( wordFit.width > rs.width ) { |
553 | | - // The first word didn't fit, we need to split it up |
554 | | - charOffset = lineStart; |
555 | | - var lineOffset = rs.wordOffset; |
556 | | - rs.wordOffset++; |
557 | | - lineEnd = this.boundaries[rs.wordOffset]; |
558 | | - do { |
559 | | - charFit = this.fitCharacters( |
560 | | - new ve.Range( charOffset, lineEnd ), rs.ruler, rs.width |
561 | | - ); |
562 | | - // If we were able to get the rest of the characters on the line OK |
563 | | - if ( charFit.end === lineEnd) { |
564 | | - // Try to fit more words on the line |
565 | | - wordFit = this.fitWords( |
566 | | - new ve.Range( rs.wordOffset, wordCount - 1 ), |
567 | | - rs.ruler, |
568 | | - rs.width - charFit.width |
569 | | - ); |
570 | | - if ( wordFit.end > rs.wordOffset ) { |
571 | | - lineOffset = rs.wordOffset; |
572 | | - rs.wordOffset = wordFit.end; |
573 | | - charFit.end = lineEnd = this.boundaries[rs.wordOffset]; |
574 | | - } |
575 | | - } |
576 | | - this.appendLine( new ve.Range( charOffset, charFit.end ), lineOffset, fractional ); |
577 | | - // Move on to another line |
578 | | - charOffset = charFit.end; |
579 | | - // Mark the next line as fractional |
580 | | - fractional = true; |
581 | | - } while ( charOffset < lineEnd ); |
582 | | - } else { |
583 | | - lineEnd = this.boundaries[wordFit.end]; |
584 | | - this.appendLine( new ve.Range( lineStart, lineEnd ), rs.wordOffset, fractional ); |
585 | | - rs.wordOffset = wordFit.end; |
586 | | - } |
587 | | - lineStart = lineEnd; |
588 | | - } |
589 | | - // Only perform on actual last iteration |
590 | | - if ( rs.wordOffset >= wordCount - 1 ) { |
591 | | - // Cleanup |
592 | | - rs.$ruler.remove(); |
593 | | - if ( rs.line < this.lines.length ) { |
594 | | - this.lines.splice( rs.line, this.lines.length - rs.line ); |
595 | | - } |
596 | | - this.$.find( '.es-contentView-line[line-index=' + ( this.lines.length - 1 ) + ']' ) |
597 | | - .nextAll() |
598 | | - .remove(); |
599 | | - rs.timeout = undefined; |
600 | | - this.emit( 'update' ); |
601 | | - } else { |
602 | | - rs.ruler.innerHTML = ''; |
603 | | - var that = this; |
604 | | - rs.timeout = setTimeout( function() { |
605 | | - that.renderIteration( 3 ); |
606 | | - }, 0 ); |
607 | | - } |
608 | | -}; |
609 | | - |
610 | | -/** |
611 | | - * Renders text into a series of HTML elements, each a single line of wrapped text. |
612 | | - * |
613 | | - * The offset parameter can be used to reduce the amount of work involved in re-rendering the same |
614 | | - * text, but will be automatically ignored if the text or width of the container has changed. |
615 | | - * |
616 | | - * Rendering happens asynchronously, and yields execution between iterations. Iterative rendering |
617 | | - * provides the JavaScript engine an ability to process events between rendering batches of lines, |
618 | | - * allowing rendering to be interrupted and restarted if changes to content model are happening before |
619 | | - * rendering of all lines is complete. |
620 | | - * |
621 | | - * @method |
622 | | - * @param {Integer} [offset] Offset to re-render from, if possible |
623 | | - */ |
624 | 250 | ve.es.Content.prototype.render = function( offset ) { |
625 | | - this.$.html(this.getHtml(0, this.model.getContentLength())); |
626 | | - return; |
627 | | - |
628 | | - var rs = this.renderState; |
629 | | - // Check if rendering is currently underway |
630 | | - if ( rs.timeout !== undefined ) { |
631 | | - // Cancel the active rendering process |
632 | | - clearTimeout( rs.timeout ); |
633 | | - // Cleanup |
634 | | - rs.$ruler.remove(); |
635 | | - } |
636 | | - // Clear caches that were specific to the previous render |
637 | | - this.widthCache = {}; |
638 | | - // In case of empty content model we still want to display empty with non-breaking space inside |
639 | | - // This is very important for lists |
640 | | - if(this.model.getContentLength() === 0) { |
641 | | - var $line = $( '<div class="es-contentView-line" line-index="0"> </div>' ); |
642 | | - this.$ |
643 | | - .children() |
644 | | - .remove( '.es-contentView-line' ) |
645 | | - .end() |
646 | | - .append( $line ); |
647 | | - this.lines = [{ |
648 | | - 'text': ' ', |
649 | | - 'range': new ve.Range( 0,0 ), |
650 | | - 'width': 0, |
651 | | - 'height': $line.outerHeight(), |
652 | | - 'wordOffset': 0, |
653 | | - 'fractional': false |
654 | | - }]; |
655 | | - this.emit( 'update' ); |
656 | | - return; |
657 | | - } |
658 | | - /* |
659 | | - * Container measurement |
660 | | - * |
661 | | - * To get an accurate measurement of the inside of the container, without having to deal with |
662 | | - * inconsistencies between browsers and box models, we can just create an element inside the |
663 | | - * container and measure it. |
664 | | - */ |
665 | | - rs.$ruler = $( '<div> </div>' ).appendTo( this.$ ); |
666 | | - rs.width = rs.$ruler.innerWidth(); |
667 | | - rs.ruler = rs.$ruler.addClass('es-contentView-ruler')[0]; |
668 | | - // Ignore offset optimization if the width has changed or the text has never been flowed before |
669 | | - if (this.width !== rs.width) { |
670 | | - offset = undefined; |
671 | | - } |
672 | | - this.width = rs.width; |
673 | | - // Reset the render state |
674 | | - if ( offset ) { |
675 | | - var gap, |
676 | | - currentLine = this.lines.length - 1; |
677 | | - for ( var i = this.lines.length - 1; i >= 0; i-- ) { |
678 | | - var line = this.lines[i]; |
679 | | - if ( line.range.start < offset && line.range.end > offset ) { |
680 | | - currentLine = i; |
681 | | - } |
682 | | - if ( ( line.range.end < offset && !line.fractional ) || i === 0 ) { |
683 | | - rs.line = i; |
684 | | - rs.wordOffset = line.wordOffset; |
685 | | - gap = currentLine - i; |
686 | | - break; |
687 | | - } |
688 | | - } |
689 | | - this.renderIteration( 2 + gap ); |
690 | | - } else { |
691 | | - rs.line = 0; |
692 | | - rs.wordOffset = 0; |
693 | | - this.renderIteration( 3 ); |
694 | | - } |
| 251 | + this.$.html( this.getHtml( 0, this.model.getContentLength() ) ); |
695 | 252 | }; |
696 | 253 | |
697 | 254 | /** |
698 | | - * Adds a line containing a given range of text to the end of the DOM and the "lines" array. |
699 | | - * |
700 | | - * @method |
701 | | - * @param {ve.Range} range Range of data within content model to append |
702 | | - * @param {Integer} start Beginning of text range for line |
703 | | - * @param {Integer} end Ending of text range for line |
704 | | - * @param {Integer} wordOffset Index within this.words which the line begins with |
705 | | - * @param {Boolean} fractional If the line begins in the middle of a word |
706 | | - */ |
707 | | -ve.es.Content.prototype.appendLine = function( range, wordOffset, fractional ) { |
708 | | - var rs = this.renderState, |
709 | | - $line = this.$.children( '[line-index=' + rs.line + ']' ); |
710 | | - if ( !$line.length ) { |
711 | | - $line = $( |
712 | | - '<div class="es-contentView-line" line-index="' + rs.line + '"></div>' |
713 | | - ); |
714 | | - this.$.append( $line ); |
715 | | - } |
716 | | - $line[0].innerHTML = this.getHtml( range ); |
717 | | - // Overwrite/append line information |
718 | | - this.lines[rs.line] = { |
719 | | - 'text': this.model.getContentText( range ), |
720 | | - 'range': range, |
721 | | - 'width': $line.outerWidth(), |
722 | | - 'height': $line.outerHeight(), |
723 | | - 'wordOffset': wordOffset, |
724 | | - 'fractional': fractional |
725 | | - }; |
726 | | - // Disable links within rendered content |
727 | | - $line.find( '.es-contentView-format-object a' ) |
728 | | - .mousedown( function( e ) { |
729 | | - e.preventDefault(); |
730 | | - } ) |
731 | | - .click( function( e ) { |
732 | | - e.preventDefault(); |
733 | | - } ); |
734 | | - rs.line++; |
735 | | -}; |
736 | | - |
737 | | -/** |
738 | | - * Gets the index of the boundary of last word that fits inside the line |
739 | | - * |
740 | | - * The "words" and "boundaries" arrays provide linear access to the offsets around non-breakable |
741 | | - * areas within the text. Using these, we can perform a binary-search for the best fit of words |
742 | | - * within a line, just as we would with characters. |
743 | | - * |
744 | | - * Results are given as an object containing both an index and a width, the later of which can be |
745 | | - * used to detect when the first word was too long to fit on a line. In such cases the result will |
746 | | - * contain the index of the boundary of the first word and it's width. |
747 | | - * |
748 | | - * TODO: Because limit is most likely given as "words.length", it may be possible to improve the |
749 | | - * efficiency of this code by making a best guess and working from there, rather than always |
750 | | - * starting with [offset .. limit], which usually results in reducing the end position in all but |
751 | | - * the last line, and in most cases more than 3 times, before changing directions. |
752 | | - * |
753 | | - * @method |
754 | | - * @param {ve.Range} range Range of data within content model to try to fit |
755 | | - * @param {HTMLElement} ruler Element to take measurements with |
756 | | - * @param {Integer} width Maximum width to allow the line to extend to |
757 | | - * @returns {Integer} Last index within "words" that contains a word that fits |
758 | | - */ |
759 | | -ve.es.Content.prototype.fitWords = function( range, ruler, width ) { |
760 | | - var offset = range.start, |
761 | | - start = range.start, |
762 | | - end = range.end, |
763 | | - charOffset = this.boundaries[offset], |
764 | | - middle, |
765 | | - charMiddle, |
766 | | - lineWidth, |
767 | | - cacheKey; |
768 | | - do { |
769 | | - // Place "middle" directly in the center of "start" and "end" |
770 | | - middle = Math.ceil( ( start + end ) / 2 ); |
771 | | - charMiddle = this.boundaries[middle]; |
772 | | - // Measure and cache width of substring |
773 | | - cacheKey = charOffset + ':' + charMiddle; |
774 | | - // Prepare the line for measurement using pre-escaped HTML |
775 | | - ruler.innerHTML = this.getHtml( new ve.Range( charOffset, charMiddle ) ); |
776 | | - // Test for over/under using width of the rendered line |
777 | | - this.widthCache[cacheKey] = lineWidth = ruler.clientWidth; |
778 | | - // Test for over/under using width of the rendered line |
779 | | - if ( lineWidth > width ) { |
780 | | - // Detect impossible fit (the first word won't fit by itself) |
781 | | - if (middle - offset === 1) { |
782 | | - start = middle; |
783 | | - break; |
784 | | - } |
785 | | - // Words after "middle" won't fit |
786 | | - end = middle - 1; |
787 | | - } else { |
788 | | - // Words before "middle" will fit |
789 | | - start = middle; |
790 | | - } |
791 | | - } while ( start < end ); |
792 | | - // Check if we ended by moving end to the left of middle |
793 | | - if ( end === middle - 1 ) { |
794 | | - // A final measurement is required |
795 | | - var charStart = this.boundaries[start]; |
796 | | - ruler.innerHTML = this.getHtml( new ve.Range( charOffset, charStart ) ); |
797 | | - lineWidth = this.widthCache[charOffset + ':' + charStart] = ruler.clientWidth; |
798 | | - } |
799 | | - return { 'end': start, 'width': lineWidth }; |
800 | | -}; |
801 | | - |
802 | | -/** |
803 | | - * Gets the index of the boundary of the last character that fits inside the line |
804 | | - * |
805 | | - * Results are given as an object containing both an index and a width, the later of which can be |
806 | | - * used to detect when the first character was too long to fit on a line. In such cases the result |
807 | | - * will contain the index of the first character and it's width. |
808 | | - * |
809 | | - * @method |
810 | | - * @param {ve.Range} range Range of data within content model to try to fit |
811 | | - * @param {HTMLElement} ruler Element to take measurements with |
812 | | - * @param {Integer} width Maximum width to allow the line to extend to |
813 | | - * @returns {Integer} Last index within "text" that contains a character that fits |
814 | | - */ |
815 | | -ve.es.Content.prototype.fitCharacters = function( range, ruler, width ) { |
816 | | - var offset = range.start, |
817 | | - start = range.start, |
818 | | - end = range.end, |
819 | | - middle, |
820 | | - lineWidth, |
821 | | - cacheKey; |
822 | | - do { |
823 | | - // Place "middle" directly in the center of "start" and "end" |
824 | | - middle = Math.ceil( ( start + end ) / 2 ); |
825 | | - // Measure and cache width of substring |
826 | | - cacheKey = offset + ':' + middle; |
827 | | - if ( cacheKey in this.widthCache ) { |
828 | | - lineWidth = this.widthCache[cacheKey]; |
829 | | - } else { |
830 | | - // Fill the line with a portion of the text, escaped as HTML |
831 | | - ruler.innerHTML = this.getHtml( new ve.Range( offset, middle ) ); |
832 | | - // Test for over/under using width of the rendered line |
833 | | - this.widthCache[cacheKey] = lineWidth = ruler.clientWidth; |
834 | | - } |
835 | | - if ( lineWidth > width ) { |
836 | | - // Detect impossible fit (the first character won't fit by itself) |
837 | | - if (middle - offset === 1) { |
838 | | - start = middle - 1; |
839 | | - break; |
840 | | - } |
841 | | - // Words after "middle" won't fit |
842 | | - end = middle - 1; |
843 | | - } else { |
844 | | - // Words before "middle" will fit |
845 | | - start = middle; |
846 | | - } |
847 | | - } while ( start < end ); |
848 | | - // Check if we ended by moving end to the left of middle |
849 | | - if ( end === middle - 1 ) { |
850 | | - // Try for cache hit |
851 | | - cacheKey = offset + ':' + start; |
852 | | - if ( cacheKey in this.widthCache ) { |
853 | | - lineWidth = this.widthCache[cacheKey]; |
854 | | - } else { |
855 | | - // A final measurement is required |
856 | | - ruler.innerHTML = this.getHtml( new ve.Range( offset, start ) ); |
857 | | - lineWidth = this.widthCache[cacheKey] = ruler.clientWidth; |
858 | | - } |
859 | | - } |
860 | | - return { 'end': start, 'width': lineWidth }; |
861 | | -}; |
862 | | - |
863 | | -/** |
864 | 255 | * Gets an HTML rendering of a range of data within content model. |
865 | 256 | * |
866 | 257 | * @method |
— | — | @@ -926,4 +317,4 @@ |
927 | 318 | |
928 | 319 | /* Inheritance */ |
929 | 320 | |
930 | | -ve.extendClass( ve.es.Content, ve.EventEmitter ); |
| 321 | +ve.extendClass( ve.es.Content, ve.EventEmitter ); |
\ No newline at end of file |