1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /** @fileoverview Logic for panning a braille display within a line of braille
6 * content that might not fit on a single display.
9 goog.provide('cvox.PanStrategy');
14 * A stateful class that keeps track of the current 'viewport' of a braille
15 * display in a line of content.
17 cvox.PanStrategy = function() {
22 this.displaySize_ = 0;
27 this.contentLength_ = 0;
29 * Points before which it is desirable to break content if it doesn't fit
31 * @type {!Array<number>}
34 this.breakPoints_ = [];
36 * @type {!cvox.PanStrategy.Range}
39 this.viewPort_ = {start: 0, end: 0};
43 * A range used to represent the viewport with inclusive start and xclusive
45 * @typedef {{start: number, end: number}}
47 cvox.PanStrategy.Range;
49 cvox.PanStrategy.prototype = {
51 * Gets the current viewport which is never larger than the current
52 * display size and whose end points are always within the limits of
53 * the current content.
54 * @type {!cvox.PanStrategy.Range}
57 return this.viewPort_;
61 * Sets the display size. This call may update the viewport.
62 * @param {number} size the new display size, or {@code 0} if no display is
65 setDisplaySize: function(size) {
66 this.displaySize_ = size;
67 this.panToPosition_(this.viewPort_.start);
71 * Sets the current content that panning should happen within. This call may
72 * change the viewport.
73 * @param {!ArrayBuffer} translatedContent The new content.
74 * @param {number} targetPosition Target position. The viewport is changed
75 * to overlap this position.
77 setContent: function(translatedContent, targetPosition) {
78 this.breakPoints_ = this.calculateBreakPoints_(translatedContent);
79 this.contentLength_ = translatedContent.byteLength;
80 this.panToPosition_(targetPosition);
84 * If possible, changes the viewport to a part of the line that follows
85 * the current viewport.
86 * @return {boolean} {@code true} if the viewport was changed.
89 var newStart = this.viewPort_.end;
91 if (newStart + this.displaySize_ < this.contentLength_) {
92 newEnd = this.extendRight_(newStart);
94 newEnd = this.contentLength_;
96 if (newEnd > newStart) {
97 this.viewPort_ = {start: newStart, end: newEnd};
104 * If possible, changes the viewport to a part of the line that precedes
105 * the current viewport.
106 * @return {boolean} {@code true} if the viewport was changed.
108 previous: function() {
109 if (this.viewPort_.start > 0) {
110 var newStart, newEnd;
111 if (this.viewPort_.start <= this.displaySize_) {
113 newEnd = this.extendRight_(newStart);
115 newEnd = this.viewPort_.start;
116 var limit = newEnd - this.displaySize_;
119 while (pos < this.breakPoints_.length &&
120 this.breakPoints_[pos] < limit) {
123 if (pos < this.breakPoints_.length &&
124 this.breakPoints_[pos] < newEnd) {
125 newStart = this.breakPoints_[pos];
128 if (newStart < newEnd) {
129 this.viewPort_ = {start: newStart, end: newEnd};
137 * Finds the end position for a new viewport start position, considering
138 * current breakpoints as well as display size and content length.
139 * @param {number} from Start of the region to extend.
143 extendRight_: function(from) {
144 var limit = Math.min(from + this.displaySize_, this.contentLength_);
147 while (pos < this.breakPoints_.length && this.breakPoints_[pos] <= from) {
150 while (pos < this.breakPoints_.length && this.breakPoints_[pos] <= limit) {
151 result = this.breakPoints_[pos];
158 * Overridden by subclasses to provide breakpoints given translated
159 * braille cell content.
160 * @param {!ArrayBuffer} content New display content.
161 * @return {!Array<number>} The points before which it is desirable to break
162 * content if needed or the empty array if no points are more desirable
166 calculateBreakPoints_: function(content) {return [];},
169 * Moves the viewport so that it overlaps a target position without taking
170 * the current viewport position into consideration.
171 * @param {number} position Target position.
173 panToPosition_: function(position) {
174 if (this.displaySize_ > 0) {
175 this.viewPort_ = {start: 0, end: 0};
176 while (this.next() && this.viewPort_.end <= position) {
180 this.viewPort_ = {start: position, end: position};
186 * A pan strategy that fits as much content on the display as possible, that
187 * is, it doesn't do any wrapping.
189 * @extends {cvox.PanStrategy}
191 cvox.FixedPanStrategy = cvox.PanStrategy;
193 * A pan strategy that tries to wrap 'words' when breaking content.
194 * A 'word' in this context is just a chunk of non-blank braille cells
195 * delimited by blank cells.
197 * @extends {cvox.PanStrategy}
199 cvox.WrappingPanStrategy = function() {
200 cvox.PanStrategy.call(this);
203 cvox.WrappingPanStrategy.prototype = {
204 __proto__: cvox.PanStrategy.prototype,
207 calculateBreakPoints_: function(content) {
208 var view = new Uint8Array(content);
209 var newContentLength = view.length;
211 var lastCellWasBlank = false;
212 for (var pos = 0; pos < view.length; ++pos) {
213 if (lastCellWasBlank && view[pos] != 0) {
216 lastCellWasBlank = (view[pos] == 0);