1 /** 2 * Copyright (C) 2010-2014 KO GmbH <copyright@kogmbh.com> 3 * 4 * @licstart 5 * This file is part of WebODF. 6 * 7 * WebODF is free software: you can redistribute it and/or modify it 8 * under the terms of the GNU Affero General Public License (GNU AGPL) 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * WebODF is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with WebODF. If not, see <http://www.gnu.org/licenses/>. 19 * @licend 20 * 21 * @source: http://www.webodf.org/ 22 * @source: https://github.com/kogmbh/WebODF/ 23 */ 24 25 /*global gui*/ 26 27 /** 28 * Finds line-wrap points by comparing the visual overlap between visible rectangles. 29 * 30 * @constructor 31 * @implements {gui.VisualStepScanner} 32 */ 33 gui.LineBoundaryScanner = function () { 34 "use strict"; 35 var self = this, 36 lineRect = null, 37 // Minimum amount of overlap between two rectangles to be considered on the same line is arbitrarily 40% 38 /**@const*/ MIN_OVERLAP_THRESHOLD = 0.4; 39 40 /** 41 * Return the fraction of overlap between two rectangles. If there is 42 * no overlap, or either of the rectangles is 0 height, this will 43 * return 0. 44 * 45 * @param {!core.SimpleClientRect} rect1 46 * @param {!core.SimpleClientRect} rect2 47 * @return {!number} 48 */ 49 function verticalOverlapPercent(rect1, rect2) { 50 var rect1Height = rect1.bottom - rect1.top, 51 rect2Height = rect2.bottom - rect2.top, 52 minRectHeight = Math.min(rect1Height, rect2Height), 53 intersectTop = Math.max(rect1.top, rect2.top), 54 intersectBottom = Math.min(rect1.bottom, rect2.bottom), 55 overlapHeight = intersectBottom - intersectTop; 56 57 return minRectHeight > 0 ? (overlapHeight / minRectHeight) : 0; 58 } 59 60 /** 61 * Returns true if the amount of overlap between the known line rectangle and the visible next rectangle 62 * is below the specified MIN_OVERLAP_THRESHOLD. If there is no known line rectangle, this will return false. 63 * 64 * @param {!core.SimpleClientRect} nextRect Client rect of next step (by direction) 65 * @return {!boolean} 66 */ 67 function isLineBoundary(nextRect) { 68 if (lineRect) { 69 // TODO this logic will fail if the caret is between a subscript & superscript char as the overlap will be 0 70 return verticalOverlapPercent(/**@type{!core.SimpleClientRect}*/(lineRect), nextRect) <= MIN_OVERLAP_THRESHOLD; 71 } 72 return false; 73 } 74 75 /** 76 * @param {!core.SimpleClientRect} rect1 77 * @param {!core.SimpleClientRect} rect2 78 * @return {!core.SimpleClientRect} 79 */ 80 function combineRects(rect1, rect2) { 81 return { 82 left: Math.min(rect1.left, rect2.left), 83 right: Math.max(rect1.right, rect2.right), 84 top: Math.min(rect1.top, rect2.top), 85 bottom: Math.min(rect1.bottom, rect2.bottom) 86 }; 87 } 88 89 /** 90 * @param {?core.SimpleClientRect} originalRect 91 * @param {?core.SimpleClientRect} newRect 92 * @return {?core.SimpleClientRect} 93 */ 94 function growRect(originalRect, newRect) { 95 if (originalRect && newRect) { 96 return combineRects(/**@type{!core.SimpleClientRect}*/(originalRect), 97 /**@type{!core.SimpleClientRect}*/(newRect)); 98 } 99 return originalRect || newRect; 100 } 101 102 this.token = undefined; 103 104 /** 105 * @param {!gui.StepInfo} stepInfo 106 * @param {?ClientRect} previousRect 107 * @param {?ClientRect} nextRect 108 * @return {!boolean} 109 */ 110 this.process = function(stepInfo, previousRect, nextRect) { 111 // Can only detect line boundaries when the next rectangle is visible. An invisible next-rect 112 // indicates the next step does not have any visible content attached, so it's location on screen 113 // is impossible to determine accurately. 114 var isOverLineBoundary = nextRect && isLineBoundary(/**@type{!core.SimpleClientRect}*/(nextRect)); 115 116 if (previousRect && (!nextRect || isOverLineBoundary)) { 117 // Detect a possible line wrap point in one of two ways: 118 // 1. Going from a visible to an invisible rectangle. An invisible rectangle can indicate a collapsed 119 // whitespace text node, or an invisible element that the browser may choose to wrap at. 120 // 2. A confirmed wrap point where the nextRect is visible and clearly not on the same line as the previous. 121 self.token = stepInfo.token; 122 } 123 124 if (isOverLineBoundary) { 125 return true; 126 } 127 128 // Grow the current line rectangle by the (now approved) previous rectangle. This allows the line height 129 // to grow naturally. 130 lineRect = growRect(lineRect, previousRect); 131 return false; 132 }; 133 };