node-webodf
Version:
WebODF - JavaScript Document Engine http://webodf.org/
167 lines (154 loc) • 7.14 kB
JavaScript
/**
* Copyright (C) 2010-2014 KO GmbH <copyright@kogmbh.com>
*
* @licstart
* This file is part of WebODF.
*
* WebODF is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License (GNU AGPL)
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* WebODF is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
* @licend
*
* @source: http://www.webodf.org/
* @source: https://github.com/kogmbh/WebODF/
*/
/*global runtime, odf, gui, core, Node*/
/**
* Helper functions to retrieve information about an ODF document using a step iterator
* @constructor
*/
gui.GuiStepUtils = function GuiStepUtils() {
"use strict";
var odfUtils = odf.OdfUtils,
stepUtils = new odf.StepUtils(),
domUtils = core.DomUtils,
NEXT = core.StepDirection.NEXT,
LEFT_TO_RIGHT = gui.StepInfo.VisualDirection.LEFT_TO_RIGHT,
RIGHT_TO_LEFT = gui.StepInfo.VisualDirection.RIGHT_TO_LEFT;
/**
* Returns the client rectangle for the content bounds at the step iterator's current position.
* Note, if the selected content is really collapsed whitespace, this function will return null.
*
* @param {!core.StepIterator} stepIterator
* @return {?ClientRect}
*/
function getContentRect(stepIterator) {
var bounds = stepUtils.getContentBounds(stepIterator),
range,
rect = null;
if (bounds) {
if (bounds.container.nodeType === Node.TEXT_NODE) {
range = bounds.container.ownerDocument.createRange();
range.setStart(bounds.container, bounds.startOffset);
range.setEnd(bounds.container, bounds.endOffset);
// *MUST* use the BCR here rather than the individual client rects, as the individual client rects
// don't support subpixel accuracy. Most browsers *do* support subpixel values for the BCR though
// (FF, Chrome + IE!!)
rect = range.getClientRects().length > 0 ? range.getBoundingClientRect() : null;
if (rect
&& /**@type{!Text}*/(bounds.container).data.substring(bounds.startOffset, bounds.endOffset) === " "
&& rect.width <= 1) {
// In Chrome, collapsed whitespace still reports a width of 1px. In FF, they report as 0px.
// Consumers of this function are really wanting the cursor position for a given
// step, which will actually be the next step in this instance.
rect = null;
}
range.detach();
} else if (odfUtils.isCharacterElement(bounds.container) || odfUtils.isCharacterFrame(bounds.container)) {
// Want to ignore some invisible document content elements such as annotation anchors.
rect = domUtils.getBoundingClientRect(bounds.container);
}
}
return rect;
}
this.getContentRect = getContentRect;
/**
* Advance the step iterator in the specified direction until an accepted step is identified
* by a token scanner.
*
* @param {!core.StepIterator} stepIterator
* @param {!core.StepDirection} direction
* @param {!Array.<!gui.VisualStepScanner>} scanners
* @return {!boolean} Return true if a step was found that satisfied one of the scanners
*/
function moveToFilteredStep(stepIterator, direction, scanners) {
var isForward = direction === NEXT,
leftRect,
rightRect,
previousRect,
nextRect,
/**@type{?core.StepIterator.StepSnapshot}*/
destinationToken,
// Just in case no destination is found, the iterator will reset back to the initial position
initialToken = stepIterator.snapshot(),
wasTerminated = false,
/**@type{!gui.StepInfo}*/
stepInfo;
/**
* @param {!boolean} terminated
* @param {!gui.VisualStepScanner} scanner
* @return {!boolean};
*/
function process(terminated, scanner) {
// Multiple token scanners might be complete in a single step
if (scanner.process(stepInfo, previousRect, nextRect)) {
terminated = true;
// A scanner might indicate iteration as complete without specifying a token
// if no available steps exist in the specified direction.
if (!destinationToken && scanner.token) {
// Scanners that terminate the iteration get the first chance to specify the destination token
destinationToken = scanner.token;
}
}
return terminated;
}
do {
// TODO Optimize performance by re-using the left/right rect from the last step (depending on direction)
leftRect = getContentRect(stepIterator);
stepInfo = /**@type{!gui.StepInfo}*/({
token: stepIterator.snapshot(),
container: stepIterator.container,
offset: stepIterator.offset,
direction: direction,
// TODO account for right-to-left languages
visualDirection: direction === NEXT ? LEFT_TO_RIGHT : RIGHT_TO_LEFT
});
if (stepIterator.nextStep()) {
rightRect = getContentRect(stepIterator);
} else {
rightRect = null;
}
stepIterator.restore(stepInfo.token);
if (isForward) {
previousRect = leftRect;
nextRect = rightRect;
} else {
previousRect = rightRect;
nextRect = leftRect;
}
wasTerminated = scanners.reduce(process, false);
} while (!wasTerminated && stepIterator.advanceStep(direction));
if (!wasTerminated) {
// If no token scanner has terminated the iteration, then check each
// token scanner for the last identified potential step
// and take the first specified token.
scanners.forEach(function(scanner) {
if (!destinationToken && scanner.token) {
destinationToken = scanner.token;
}
});
}
stepIterator.restore(destinationToken || initialToken);
return Boolean(destinationToken);
}
this.moveToFilteredStep = moveToFilteredStep;
};