UNPKG

nehan

Version:

Html layout engine for paged-media written in Typescript

219 lines 8.85 kB
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