nehan
Version:
Html layout engine for paged-media written in Typescript
219 lines • 8.85 kB
JavaScript
import { LogicalRect, LogicalCursorPos } from "./public-api";
export class FloatRegion {
constructor(flowRootSize, before) {
this.flowRootRegion = new LogicalRect(LogicalCursorPos.zero, flowRootSize);
this.cursorBefore = before;
this.startRects = [];
this.endRects = [];
this.startLedgePositions = new Set();
this.endLedgePositions = new Set();
}
toString() {
return `FloatRegion(${this.flowRootRegion.toString()})`;
}
isEmpty() {
return this.startRects.length === 0 && this.endRects.length === 0;
}
get maxRegionExtent() {
return Math.max(this.maxStartRegionExtent, this.maxEndRegionExtent);
}
pushStart(before, size, contextMeasure) {
if (this.flowRootRegion.canContain(size) === false) {
throw new Error("FloatRegion:too large size");
}
this.cursorBefore = Math.max(this.cursorBefore, before);
const restMeasure = this.getSpaceMeasureAt(this.cursorBefore, contextMeasure);
if (this.startRects.length === 0 && this.endRects.length === 0 && size.measure > restMeasure) {
throw new Error("FloatRegion:too large size");
}
if (size.measure < restMeasure) {
const rect = this.getStartSideRect(this.cursorBefore);
const start = rect ? rect.end : 0;
const before = this.cursorBefore;
const pos = new LogicalCursorPos({ start, before });
return this.pushStartRect(new LogicalRect(pos, size));
}
const skipExtent = this.getSkipExtentAt(this.cursorBefore);
this.cursorBefore += skipExtent;
if (this.cursorBefore + size.extent >= this.flowRootRegion.extent) {
throw new Error("FloatRegion:no more space left.");
}
return this.pushStart(this.cursorBefore, size);
}
pushEnd(before, size, contextMeasure) {
if (this.flowRootRegion.canContain(size) === false) {
throw new Error("FloatRegion:too large size");
}
this.cursorBefore = Math.max(this.cursorBefore, before);
const restMeasure = this.getSpaceMeasureAt(this.cursorBefore, contextMeasure);
if (this.startRects.length === 0 && this.endRects.length === 0 && size.measure > restMeasure) {
throw new Error("FloatRegion:too large size");
}
if (size.measure < restMeasure) {
const rect = this.getEndSideRect(this.cursorBefore);
const maxMeasure = this.getMaxMeasure(contextMeasure);
const start = rect ? rect.start - size.measure : maxMeasure - size.measure;
const pos = new LogicalCursorPos({ start: start, before: this.cursorBefore });
return this.pushEndRect(new LogicalRect(pos, size));
}
const skipExtent = this.getSkipExtentAt(this.cursorBefore);
this.cursorBefore += skipExtent;
if (this.cursorBefore + size.extent >= this.flowRootRegion.extent) {
throw new Error("FloatRegion:no more space left.");
}
return this.pushEnd(this.cursorBefore, size);
}
clearBoth() {
const clearedExtent = Math.max(this.maxStartRegionExtent, this.maxEndRegionExtent);
this.startRects = [];
this.endRects = [];
this.startLedgePositions.clear();
this.endLedgePositions.clear();
this.cursorBefore = clearedExtent;
return clearedExtent;
}
clearStart() {
const clearedExtent = this.maxStartRegionExtent;
this.startRects = [];
this.startLedgePositions.clear();
this.cursorBefore = clearedExtent;
return clearedExtent;
}
clearEnd() {
const clearedExtent = this.maxEndRegionExtent;
this.endRects = [];
this.endLedgePositions.clear();
this.cursorBefore = clearedExtent;
return clearedExtent;
}
getSpacePosFromStartBound(before) {
let rect = this.getStartSideRect(before);
return rect ? rect.end : 0;
}
getSpaceMeasureAt(before, contextMeasure) {
return this.getMaxMeasure(contextMeasure) - this.getSideRectMeasureAt(before);
}
hasSpaceForSize(before, wantedSize, contextMeasure) {
const spaceMeasure = this.getSpaceMeasureAt(before, contextMeasure);
if (spaceMeasure < wantedSize.measure) {
return false;
}
const wantedCursor = new LogicalCursorPos({ start: this.getSpacePosFromStartBound(before), before });
const wantedSpace = new LogicalRect(wantedCursor, wantedSize);
return this.allRects.filter(rect => rect.after > before).every(rect => !wantedSpace.collideWith(rect));
}
findSpace(before, wantedSize, contextMeasure) {
if (this.hasSpaceForSize(before, wantedSize)) {
const start = this.getSpacePosFromStartBound(before);
return new LogicalCursorPos({ before, start });
}
const foundBefore = this.ledgePositions
.filter(pos => pos > before)
.find(pos => this.hasSpaceForSize(pos, wantedSize, contextMeasure));
if (foundBefore === undefined) {
return undefined;
}
const foundStart = this.getSpacePosFromStartBound(foundBefore);
return new LogicalCursorPos({ before: foundBefore, start: foundStart });
}
getSideRectMeasureAt(before) {
return this.getStartSideRectMeasure(before) + this.getEndSideRectMeasure(before);
}
getMaxMeasure(contextMeasure) {
return Math.min(this.flowRootRegion.measure, contextMeasure || Infinity);
}
getSkipExtentAt(before) {
if (this.startRects.length === 0 && this.endRects.length === 0) {
return 0;
}
const lastStartRect = this.startRects[this.startRects.length - 1];
const lastEndRect = this.endRects[this.endRects.length - 1];
if (!lastStartRect) {
return lastEndRect.extent;
}
if (!lastEndRect) {
return lastStartRect.extent;
}
return (lastStartRect.after < lastEndRect.after) ? lastStartRect.extent : lastEndRect.extent;
}
get ledgePositions() {
const tmp = new Set();
this.startLedgePositions.forEach(pos => tmp.add(pos));
this.endLedgePositions.forEach(pos => tmp.add(pos));
const positions = [];
tmp.forEach(pos => positions.push(pos));
return positions.sort();
}
addLedgePos(sideLedgePositions, lastRect, newRect) {
if (!lastRect) {
sideLedgePositions.add(0);
sideLedgePositions.add(newRect.after);
}
else if (lastRect.before === newRect.before) {
if (newRect.extent > lastRect.extent) {
sideLedgePositions.delete(lastRect.after);
}
sideLedgePositions.add(newRect.after);
}
else {
if (lastRect.measure === newRect.measure) {
sideLedgePositions.delete(lastRect.after);
}
sideLedgePositions.add(newRect.after);
}
}
pushStartRect(rect) {
const lastRect = this.startRects[this.startRects.length - 1];
this.addLedgePos(this.startLedgePositions, lastRect, rect);
this.startRects.push(rect);
return rect;
}
pushEndRect(rect) {
const lastRect = this.endRects[this.endRects.length - 1];
this.addLedgePos(this.endLedgePositions, lastRect, rect);
this.endRects.push(rect);
return rect;
}
get allRects() {
return this.startRects.concat(this.endRects);
}
get maxStartRegionExtent() {
return this.getMaxSideCursorBeforeFrom(this.startRects);
}
get maxEndRegionExtent() {
return this.getMaxSideCursorBeforeFrom(this.endRects);
}
getStartSideRect(before_pos) {
return this.getSideRect(this.startRects, before_pos);
}
getEndSideRect(before_pos) {
return this.getSideRect(this.endRects, before_pos);
}
getStartSideRectMeasure(before_pos) {
let rect = this.getStartSideRect(before_pos);
if (!rect) {
return 0;
}
return rect.end;
}
getEndSideRectMeasure(before_pos) {
let rect = this.getEndSideRect(before_pos);
if (!rect) {
return 0;
}
return this.flowRootRegion.measure - rect.start;
}
getMaxSideCursorBeforeFrom(rects) {
return rects.reduce((max, rect) => Math.max(max, rect.after), this.cursorBefore);
}
getSideRect(floats, before_pos) {
for (let i = floats.length - 1; i >= 0; i--) {
let rect = floats[i];
if (rect.before <= before_pos && before_pos < rect.after) {
return rect;
}
}
return null;
}
}
//# sourceMappingURL=float-region.js.map