[Libreoffice-commits] online.git: loleaflet/src
Dennis Francis (via logerrit)
logerrit at kemper.freedesktop.org
Sun Jul 5 07:55:53 UTC 2020
loleaflet/src/layer/tile/CalcTileLayer.js | 841 ++++++++++++++++++++++++++++++
1 file changed, 841 insertions(+)
New commits:
commit b36e069549508e927c75e8000f0cff6024884a05
Author: Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Sat May 9 20:34:37 2020 +0530
Commit: Dennis Francis <dennis.francis at collabora.com>
CommitDate: Sun Jul 5 09:55:33 2020 +0200
add sheet-geometry datastructures
to parse, store the .uno:SheetGeometryData JSON efficiently although it
is optimized for fast querying. L.SheetGeometry is the external class
that exposes all necessary sheet query interfaces.
Change-Id: I24df8d85734a6cdf9c393fd2c3c5ed4de0ea29f3
Reviewed-on: https://gerrit.libreoffice.org/c/online/+/97940
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
Reviewed-by: Dennis Francis <dennis.francis at collabora.com>
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index 06ffa2bef..b0773251d 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -567,3 +567,844 @@ L.CalcTileLayer = L.TileLayer.extend({
this._onUpdateCurrentHeader();
}
});
+
+
+// TODO: Move these somewhere more appropriate.
+
+// Sheet geometry data
+L.SheetGeometry = L.Class.extend({
+
+ // sheetGeomJSON is expected to be the parsed JSON message from core
+ // in response to client command '.uno:SheetGeometryData' with
+ // all flags (ie 'columns', 'rows', 'sizes', 'hidden', 'filtered',
+ // 'groups') enabled.
+ initialize: function (sheetGeomJSON, tileWidthTwips, tileHeightTwips,
+ tileSizeCSSPixels, dpiScale) {
+
+ if (typeof sheetGeomJSON !== 'object' ||
+ typeof tileWidthTwips !== 'number' ||
+ typeof tileHeightTwips !== 'number' ||
+ typeof tileSizeCSSPixels !== 'number' ||
+ typeof dpiScale !== 'number') {
+ console.error('Incorrect constructor argument types or missing required arguments');
+ return;
+ }
+
+ this._columns = new L.SheetDimension();
+ this._rows = new L.SheetDimension();
+ this._unoCommand = '.uno:SheetGeometryData';
+
+ // Set various unit conversion info early on because on update() call below, these info are needed.
+ this.setTileGeometryData(tileWidthTwips, tileHeightTwips, tileSizeCSSPixels,
+ dpiScale, false /* update position info ?*/);
+
+ this.update(sheetGeomJSON, /* checkCompleteness */ true);
+ },
+
+ update: function (sheetGeomJSON, checkCompleteness) {
+
+ if (!this._testValidity(sheetGeomJSON, checkCompleteness)) {
+ return false;
+ }
+
+ var updateOK = true;
+ if (sheetGeomJSON.columns) {
+ if (!this._columns.update(sheetGeomJSON.columns)) {
+ console.error(this._unoCommand + ': columns update failed.');
+ updateOK = false;
+ }
+ }
+
+ if (sheetGeomJSON.rows) {
+ if (!this._rows.update(sheetGeomJSON.rows)) {
+ console.error(this._unoCommand + ': rows update failed.');
+ updateOK = false;
+ }
+ }
+
+ this._columns.setMaxIndex(+sheetGeomJSON.maxtiledcolumn);
+ this._rows.setMaxIndex(+sheetGeomJSON.maxtiledrow);
+
+ return updateOK;
+ },
+
+ setTileGeometryData: function (tileWidthTwips, tileHeightTwips, tileSizeCSSPixels,
+ dpiScale, updatePositions) {
+
+ this._columns.setTileGeometryData(tileWidthTwips, tileSizeCSSPixels, dpiScale, updatePositions);
+ this._rows.setTileGeometryData(tileHeightTwips, tileSizeCSSPixels, dpiScale, updatePositions);
+ },
+
+ setViewArea: function (topLeftTwipsPoint, sizeTwips) {
+
+ if (!(topLeftTwipsPoint instanceof L.Point) || !(sizeTwips instanceof L.Point)) {
+ console.error('invalid argument types');
+ return false;
+ }
+
+ var left = topLeftTwipsPoint.x;
+ var top = topLeftTwipsPoint.y;
+ var right = left + sizeTwips.x;
+ var bottom = top + sizeTwips.y;
+
+ this._columns.setViewLimits(left, right);
+ this._rows.setViewLimits(top, bottom);
+
+ return true;
+ },
+
+ // returns an object with keys 'start' and 'end' indicating the
+ // column range in the current view area.
+ getViewColumnRange: function () {
+ return this._columns.getViewElementRange();
+ },
+
+ // returns an object with keys 'start' and 'end' indicating the
+ // row range in the current view area.
+ getViewRowRange: function () {
+ return this._rows.getViewElementRange();
+ },
+
+ getViewCellRange: function () {
+ return {
+ columnrange: this.getViewColumnRange(),
+ rowrange: this.getViewRowRange()
+ };
+ },
+
+ // Returns an object with the following fields:
+ // columnIndex should be zero based.
+ // 'startpos' (start position of the column in css pixels), 'size' (column size in css pixels).
+ // Note: All these fields are computed by assuming zero sizes for hidden/filtered columns.
+ getColumnData: function (columnIndex) {
+ return this._columns.getElementData(columnIndex);
+ },
+
+ // Returns an object with the following fields:
+ // rowIndex should be zero based.
+ // 'startpos' (start position of the row in css pixels), 'size' (row size in css pixels).
+ // Note: All these fields are computed by assuming zero sizes for hidden/filtered rows.
+ getRowData: function (rowIndex) {
+ return this._rows.getElementData(rowIndex);
+ },
+
+ // Runs the callback for every column in the inclusive range [columnStart, columnEnd].
+ // callback is expected to have a signature of (column, columnData)
+ // where 'column' will contain the column index(zero based) and 'columnData' will be an object with
+ // the same fields as returned by getColumnData().
+ forEachColumnInRange: function (columnStart, columnEnd, callback) {
+ this._columns.forEachInRange(columnStart, columnEnd, callback);
+ },
+
+ // Runs the callback for every row in the inclusive range [rowStart, rowEnd].
+ // callback is expected to have a signature of (row, rowData)
+ // where 'row' will contain the row index(zero based) and 'rowData' will be an object with
+ // the same fields as returned by getRowData().
+ forEachRowInRange: function (rowStart, rowEnd, callback) {
+ this._rows.forEachInRange(rowStart, rowEnd, callback);
+ },
+
+ getColumnGroupLevels: function () {
+ return this._columns.getGroupLevels();
+ },
+
+ getRowGroupLevels: function () {
+ return this._rows.getGroupLevels();
+ },
+
+ getColumnGroupsDataInView: function () {
+ return this._columns.getGroupsDataInView();
+ },
+
+ getRowGroupsDataInView: function () {
+ return this._rows.getGroupsDataInView();
+ },
+
+ _testValidity: function (sheetGeomJSON, checkCompleteness) {
+
+ if (!sheetGeomJSON.hasOwnProperty('commandName')) {
+ console.error(this._unoCommand + ' response has no property named "commandName".');
+ return false;
+ }
+
+ if (sheetGeomJSON.commandName !== this._unoCommand) {
+ console.error('JSON response has wrong commandName: ' +
+ sheetGeomJSON.commandName + ' expected: ' +
+ this._unoCommand);
+ return false;
+ }
+
+ if (typeof sheetGeomJSON.maxtiledcolumn !== 'string' ||
+ !/^\d+$/.test(sheetGeomJSON.maxtiledcolumn)) {
+ console.error('JSON is missing/unreadable maxtiledcolumn property');
+ return false;
+ }
+
+ if (typeof sheetGeomJSON.maxtiledrow !== 'string' ||
+ !/^\d+$/.test(sheetGeomJSON.maxtiledrow)) {
+ console.error('JSON is missing/unreadable maxtiledrow property');
+ return false;
+ }
+
+ if (checkCompleteness) {
+
+ if (!sheetGeomJSON.hasOwnProperty('rows') ||
+ !sheetGeomJSON.hasOwnProperty('columns')) {
+
+ console.error(this._unoCommand + ' response is incomplete.');
+ return false;
+ }
+
+ if (typeof sheetGeomJSON.rows !== 'object' ||
+ typeof sheetGeomJSON.columns !== 'object') {
+
+ console.error(this._unoCommand + ' response has invalid rows/columns children.');
+ return false;
+ }
+
+ var expectedFields = ['sizes', 'hidden', 'filtered'];
+ for (var idx = 0; idx < expectedFields.length; idx++) {
+
+ var fieldName = expectedFields[idx];
+ var encodingForCols = sheetGeomJSON.columns[fieldName];
+ var encodingForRows = sheetGeomJSON.rows[fieldName];
+
+ // Don't accept empty string or any other types.
+ if (typeof encodingForRows !== 'string' || !encodingForRows) {
+ console.error(this._unoCommand + ' response has invalid value for rows.' +
+ fieldName);
+ return false;
+ }
+
+ // Don't accept empty string or any other types.
+ if (typeof encodingForCols !== 'string' || !encodingForCols) {
+ console.error(this._unoCommand + ' response has invalid value for columns.' +
+ fieldName);
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+});
+
+// Used to represent/query geometry data about either rows or columns.
+L.SheetDimension = L.Class.extend({
+
+ initialize: function () {
+
+ this._sizes = new L.SpanList();
+ this._hidden = new L.BoolSpanList();
+ this._filtered = new L.BoolSpanList();
+ this._groups = new L.DimensionOutlines();
+
+ // This is used to store the span-list of sizes
+ // with hidden/filtered elements set to zero size.
+ // This needs to be updated whenever
+ // this._sizes/this._hidden/this._filtered are modified.
+ this._visibleSizes = undefined;
+ },
+
+ update: function (jsonObject) {
+
+ if (typeof jsonObject !== 'object') {
+ return false;
+ }
+
+ var regenerateVisibleSizes = false;
+ var loadsOK = true;
+ if (jsonObject.hasOwnProperty('sizes')) {
+ loadsOK = this._sizes.load(jsonObject.sizes);
+ regenerateVisibleSizes = true;
+ }
+
+ if (jsonObject.hasOwnProperty('hidden')) {
+ var thisLoadOK = this._hidden.load(jsonObject.hidden);
+ loadsOK = loadsOK && thisLoadOK;
+ regenerateVisibleSizes = true;
+ }
+
+ if (jsonObject.hasOwnProperty('filtered')) {
+ thisLoadOK = this._filtered.load(jsonObject.filtered);
+ loadsOK = loadsOK && thisLoadOK;
+ regenerateVisibleSizes = true;
+ }
+
+ if (jsonObject.hasOwnProperty('groups')) {
+ thisLoadOK = this._groups.load(jsonObject.groups);
+ loadsOK = loadsOK && thisLoadOK;
+ }
+
+ if (loadsOK && regenerateVisibleSizes) {
+ this._updateVisible();
+ }
+
+ return loadsOK;
+ },
+
+ setMaxIndex: function (maxIndex) {
+ this._maxIndex = maxIndex;
+ },
+
+ setTileGeometryData: function (tileSizeTwips, tileSizeCSSPixels, dpiScale, updatePositions) {
+ if (updatePositions === undefined) {
+ updatePositions = true;
+ }
+
+ this._twipsPerCSSPixel = tileSizeTwips / tileSizeCSSPixels;
+ this._devPixelsPerCssPixel = dpiScale;
+
+ if (updatePositions) {
+ // We need to compute positions data for every zoom change.
+ this._updatePositions();
+ }
+ },
+
+ _updateVisible: function () {
+
+ var invisibleSpanList = this._hidden.union(this._filtered); // this._hidden is not modified.
+ this._visibleSizes = this._sizes.applyZeroValues(invisibleSpanList); // this._sizes is not modified.
+ this._updatePositions();
+ },
+
+ _updatePositions: function() {
+
+ var posDevPx = 0; // position in device pixels.
+ var dimensionObj = this;
+ this._visibleSizes.addCustomDataForEachSpan(function (
+ index,
+ size, /* size in twips of one element in the span */
+ spanLength /* #elements in the span */) {
+
+ // Important: rounding needs to be done in device pixels exactly like the core.
+ var sizeDevPxOne = Math.floor(size / dimensionObj._twipsPerCSSPixel * dimensionObj._devPixelsPerCssPixel);
+ posDevPx += (sizeDevPxOne * spanLength);
+ var posCssPx = posDevPx / dimensionObj._devPixelsPerCssPixel;
+ // position in device-pixel aligned twips.
+ var posTileTwips = Math.floor(posCssPx * dimensionObj._twipsPerCSSPixel);
+
+ var customData = {
+ sizedev: sizeDevPxOne,
+ posdevpx: posDevPx,
+ poscsspx: posCssPx,
+ postiletwips: posTileTwips
+ };
+
+ return customData;
+ });
+ },
+
+ getElementData: function (index) {
+ var span = this._visibleSizes.getSpanDataByIndex(index);
+ if (span === undefined) {
+ return undefined;
+ }
+
+ return this._getElementDataFromSpanByIndex(index, span);
+ },
+
+ _getElementDataFromSpanByIndex: function (index, span) {
+ if (span === undefined || index < span.start || span.end < index) {
+ return undefined;
+ }
+
+ var numSizes = span.end - index + 1;
+ // all in css pixels.
+ return {
+ startpos: (span.data.posdevpx - span.data.sizedev * numSizes) / this._devPixelsPerCssPixel,
+ size: span.data.sizedev / this._devPixelsPerCssPixel
+ };
+ },
+
+ forEachInRange: function (start, end, callback) {
+
+ var dimensionObj = this;
+ this._visibleSizes.forEachSpanInRange(start, end, function (span) {
+ var first = Math.max(span.start, start);
+ var last = Math.min(span.end, end);
+ for (var index = first; index <= last; ++index) {
+ callback(index, dimensionObj._getElementDataFromSpanByIndex(index, span));
+ }
+ });
+ },
+
+ _getIndexFromTileTwipsPos: function (pos) {
+ var span = this._visibleSizes.getSpanDataByCustomDataField(pos, 'postiletwips');
+ var elementCount = span.end - span.start + 1;
+ var posStart = ((span.data.posdevpx - span.data.sizedev * elementCount) /
+ this._devPixelsPerCssPixel * this._twipsPerCSSPixel);
+ var posEnd = span.data.postiletwips;
+ var sizeOne = (posEnd - posStart) / elementCount;
+ var relativeIndex = Math.round((pos - posStart) / sizeOne);
+
+ return span.start + relativeIndex;
+ },
+
+ setViewLimits: function (startPosTileTwips, endPosTileTwips) {
+
+ // Extend the range a bit, to compensate for rounding errors.
+ this._viewStartIndex = Math.max(0, this._getIndexFromTileTwipsPos(startPosTileTwips) - 2);
+ this._viewEndIndex = Math.min(this._maxIndex, this._getIndexFromTileTwipsPos(endPosTileTwips) + 2);
+ },
+
+ getViewElementRange: function () {
+ return {
+ start: this._viewStartIndex,
+ end: this._viewEndIndex
+ };
+ },
+
+ getGroupLevels: function () {
+ return this._outlines.getLevels();
+ },
+
+ getGroupsDataInView: function () {
+ var groupsData = [];
+ var levels = this._outlines.getLevels();
+ if (!levels) {
+ return groupsData;
+ }
+
+ var dimensionObj = this;
+ this._outlines.forEachGroupInRange(this._viewStartIndex, this._viewEndIndex,
+ function (levelIdx, groupIdx, start, end, hidden) {
+
+ var startElementData = dimensionObj.getElementData(start, true /* device pixels */);
+ var endElementData = dimensionObj.getElementData(end, true /* device pixels */);
+ groupsData.push({
+ level: (levelIdx + 1).toString(),
+ index: groupIdx.toString(),
+ startPos: startElementData.startpos.toString(),
+ endPos: (endElementData.startpos + endElementData.size).toString(),
+ hidden: hidden ? '1' : '0'
+ });
+ });
+
+ return groupsData;
+ },
+
+ getMaxIndex: function () {
+ return this._maxIndex;
+ }
+});
+
+L.SpanList = L.Class.extend({
+
+ initialize: function (encoding) {
+
+ // spans are objects with keys: 'index' and 'value'.
+ // 'index' holds the last element of the span.
+ // Optionally custom data of a span can be added
+ // under the key 'data' via addCustomDataForEachSpan.
+ this._spanlist = [];
+ if (typeof encoding !== 'string') {
+ return;
+ }
+
+ this.load(encoding);
+ },
+
+ load: function (encoding) {
+
+ if (typeof encoding !== 'string') {
+ return false;
+ }
+
+ var result = parseSpanListEncoding(encoding, false /* boolean value ? */);
+ if (result === undefined) {
+ return false;
+ }
+
+ this._spanlist = result.spanlist;
+ return true;
+ },
+
+ // Runs in O(#spans in 'this' + #spans in 'other')
+ applyZeroValues: function (other) {
+
+ if (!(other instanceof L.BoolSpanList)) {
+ return undefined;
+ }
+
+ // Ensure both spanlists have the same total range.
+ if (this._spanlist[this._spanlist.length - 1].index !== other._spanlist[other._spanlist.length - 1]) {
+ return undefined;
+ }
+
+ var maxElement = this._spanlist[this._spanlist.length - 1].index;
+ var result = new L.SpanList();
+
+ var thisIdx = 0;
+ var otherIdx = 0;
+ var zeroBit = other._startBit;
+ var resultValue = zeroBit ? 0 : this._spanlist[thisIdx].value;
+
+ while (thisIdx < this._spanlist.length && otherIdx < other._spanlist.length) {
+
+ // end elements of the current spans of 'this' and 'other'.
+ var thisElement = this._spanlist[thisIdx].index;
+ var otherElement = other._spanlist[otherIdx];
+
+ var lastElement = otherElement;
+ if (thisElement < otherElement) {
+ lastElement = thisElement;
+ ++thisIdx;
+ }
+ else if (otherElement < thisElement) {
+ zeroBit = !zeroBit;
+ ++otherIdx;
+ }
+ else { // both elements are equal.
+ zeroBit = !zeroBit;
+ ++thisIdx;
+ ++otherIdx;
+ }
+
+ var nextResultValue = resultValue;
+ if (thisIdx < this._spanlist.length) {
+ nextResultValue = zeroBit ? 0 : this._spanlist[thisIdx].value;
+ }
+
+ if (resultValue != nextResultValue || lastElement >= maxElement) {
+ // In the result spanlist a new span start from lastElement+1
+ // or reached the maximum possible element.
+ result._spanlist.push({index: lastElement, value: resultValue});
+ resultValue = nextResultValue;
+ }
+ }
+
+ return result;
+ },
+
+ addCustomDataForEachSpan: function (getCustomDataCallback) {
+
+ var prevIndex = -1;
+ this._spanlist.forEach(function (span) {
+ span.data = getCustomDataCallback(
+ span.index, span.value,
+ span.index - prevIndex);
+ prevIndex = span.index;
+ });
+ },
+
+ getSpanDataByIndex: function (index) {
+ var spanid = this._searchByIndex(index);
+ if (spanid == -1) {
+ return undefined;
+ }
+
+ return this._getSpanData(spanid);
+ },
+
+ getSpanDataByCustomDataField: function (value, fieldName) {
+ var spanid = this._searchByCustomDataField(value, fieldName);
+ if (spanid == -1) {
+ return undefined;
+ }
+
+ return this._getSpanData(spanid);
+ },
+
+ forEachSpanInRange: function (start, end, callback) {
+
+ if (start > end) {
+ return;
+ }
+
+ var startId = this._searchByIndex(start);
+ var endId = this._searchByIndex(end);
+
+ if (startId == -1 || endId == -1) {
+ return;
+ }
+
+ for (var id = startId; id <= endId; ++id) {
+ callback(this._getSpanData(id));
+ }
+ },
+
+ _getSpanData: function (spanid) {
+
+ var span = this._spanlist[spanid];
+ var dataClone = undefined;
+ if (span.data) {
+ dataClone = {};
+ Object.keys(span.data).forEach(function (key) {
+ dataClone[key] = span.data[key];
+ });
+ }
+
+ return {
+ start: spanid ? this._spanlist[spanid - 1].index + 1 : 0,
+ end: span.index,
+ size: span.value,
+ data: dataClone
+ };
+ },
+
+ _searchByIndex: function (index) {
+
+ if (index < 0 || index > this._spanlist[this._spanlist.length - 1].index) {
+ return -1;
+ }
+
+ var start = 0;
+ var end = this._spanlist.length - 1;
+ var mid = -1;
+ while (start <= end) {
+ mid = Math.round((start + end) / 2);
+ var spanstart = mid ? this._spanlist[mid - 1].index + 1 : 0;
+ var spanend = this._spanlist[mid].index;
+ if (spanstart <= index && index <= spanend) {
+ break;
+ }
+
+ if (index < spanstart) {
+ end = mid - 1;
+ }
+ else { // spanend < index
+ start = mid + 1;
+ }
+ }
+
+ return mid;
+ },
+
+ _searchByCustomDataField: function (value, fieldName) {
+
+ // All custom searchable data values are assumed to start from 0 at the start of first span.
+ var maxValue = this._spanlist[this._spanlist.length - 1].data[fieldName];
+ if (value < 0 || value > maxValue) {
+ return -1;
+ }
+
+ var start = 0;
+ var end = this._spanlist.length - 1;
+ var mid = -1;
+ while (start <= end) {
+ mid = Math.round((start + end) / 2);
+ var valuestart = mid ? this._spanlist[mid - 1].data[fieldName] + 1 : 0;
+ var valueend = this._spanlist[mid].data[fieldName];
+ if (valuestart <= value && value <= valueend) {
+ break;
+ }
+
+ if (value < valuestart) {
+ end = mid - 1;
+ }
+ else { // valueend < value
+ start = mid + 1;
+ }
+ }
+
+ // may fail for custom data ?
+ return (start <= end) ? mid : -1;
+ }
+
+});
+
+L.BoolSpanList = L.SpanList.extend({
+
+ load: function (encoding) {
+
+ if (typeof encoding !== 'string') {
+ return false;
+ }
+
+ var result = parseSpanListEncoding(encoding, true /* boolean value ? */);
+ if (result === undefined) {
+ return false;
+ }
+
+ this._spanlist = result.spanlist;
+ this._startBit = result.startBit;
+ return true;
+ },
+
+ // Runs in O(#spans in 'this' + #spans in 'other')
+ union: function (other) {
+
+ if (!(other instanceof L.BoolSpanList)) {
+ return undefined;
+ }
+
+ // Ensure both spanlists have the same total range.
+ if (this._spanlist[this._spanlist.length - 1] !== other._spanlist[other._spanlist.length - 1]) {
+ return undefined;
+ }
+
+ var maxElement = this._spanlist[this._spanlist.length - 1];
+
+ var result = new L.BoolSpanList();
+ var thisBit = this._startBit;
+ var otherBit = other._startBit;
+ var resultBit = thisBit || otherBit;
+ result._startBit = resultBit;
+
+ var thisIdx = 0;
+ var otherIdx = 0;
+
+ while (thisIdx < this._spanlist.length && otherIdx < other._spanlist.length) {
+
+ // end elements of the current spans of 'this' and 'other'.
+ var thisElement = this._spanlist[thisIdx];
+ var otherElement = other._spanlist[otherIdx];
+
+ var lastElement = otherElement;
+ if (thisElement < otherElement) {
+ lastElement = thisElement;
+ thisBit = !thisBit;
+ ++thisIdx;
+ }
+ else if (otherElement < thisElement) {
+ otherBit = !otherBit;
+ ++otherIdx;
+ }
+ else { // both elements are equal.
+ thisBit = !thisBit;
+ otherBit = !otherBit;
+ ++thisIdx;
+ ++otherIdx;
+ }
+
+ var nextResultBit = (thisBit || otherBit);
+ if (resultBit != nextResultBit || lastElement >= maxElement) {
+ // In the result spanlist a new span start from lastElement+1
+ // or reached the maximum possible element.
+ result._spanlist.push(lastElement);
+ resultBit = nextResultBit;
+ }
+ }
+
+ return result;
+ }
+});
+
+function parseSpanListEncoding(encoding, booleanValue) {
+
+ var spanlist = [];
+ var splits = encoding.split(' ');
+ if (splits.length < 2) {
+ return undefined;
+ }
+
+ var startBit = false;
+ if (booleanValue) {
+ var parts = splits[0].split(':');
+ if (parts.length != 2) {
+ return undefined;
+ }
+ startBit = parseInt(parts[0]);
+ var first = parseInt(parts[1]);
+ if (isNaN(startBit) || isNaN(first)) {
+ return undefined;
+ }
+ spanlist.push(first);
+ }
+
+ startBit = Boolean(startBit);
+
+ for (var idx = 0; idx < splits.length - 1; ++idx) {
+
+ if (booleanValue) {
+ if (!idx) {
+ continue;
+ }
+
+ var entry = parseInt(splits[idx]);
+ if (isNaN(entry)) {
+ return undefined;
+ }
+
+ spanlist.push(entry);
+ continue;
+ }
+
+ var spanParts = splits[idx].split(':');
+ if (spanParts.length != 2) {
+ return undefined;
+ }
+
+ var span = {
+ index: parseInt(spanParts[1]),
+ value: parseInt(spanParts[0])
+ };
+
+ if (isNaN(span.index) || isNaN(span.value)) {
+ return undefined;
+ }
+
+ spanlist.push(span);
+ }
+
+ var result = {spanlist: spanlist};
+
+ if (booleanValue) {
+ result.startBit = startBit;
+ }
+
+ return result;
+}
+
+L.DimensionOutlines = L.Class.extend({
+
+ initialize: function (encoding) {
+
+ this._outlines = [];
+ if (typeof encoding !== 'string') {
+ return;
+ }
+
+ this.load(encoding);
+ },
+
+ load: function (encoding) {
+
+ if (typeof encoding !== 'string') {
+ return false;
+ }
+
+ var levels = encoding.split(' ');
+ if (levels.length < 2) {
+ // No outline.
+ return true;
+ }
+
+ var outlines = [];
+
+ for (var levelIdx = 0; levelIdx < levels.length - 1; ++levelIdx) {
+ var collectionSplits = levels[levelIdx].split(',');
+ var collections = [];
+ if (collectionSplits.length < 2) {
+ return false;
+ }
+
+ for (var collIdx = 0; collIdx < collectionSplits.length - 1; ++collIdx) {
+ var entrySplits = collectionSplits[collIdx].split(':');
+ if (entrySplits.length < 4) {
+ return false;
+ }
+
+ var olineEntry = {
+ start: parseInt(entrySplits[0]),
+ size: parseInt(entrySplits[1]),
+ hidden: parseInt(entrySplits[2]),
+ visible: parseInt(entrySplits[3])
+ };
+
+ if (isNaN(olineEntry.start) || isNaN(olineEntry.size) ||
+ isNaN(olineEntry.hidden) || isNaN(olineEntry.visible)) {
+ return false;
+ }
+
+ collections.push(olineEntry);
+ }
+
+ outlines.push(collections);
+ }
+
+ this._outlines = outlines;
+ return true;
+ }
+});
More information about the Libreoffice-commits
mailing list