@sahabaplus/mushaf-engine
Version:
TypeScript implementation of a Quran Mushaf navigation engine
193 lines (192 loc) • 8.22 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NavigationResult = exports.CycleInfo = void 0;
/**
* Information about navigation cycles and boundary crossings
*
* This class tracks how many times navigation passed through the starting verse
* and provides information about cycle distances for bounded navigation.
*/
class CycleInfo {
/**
* Create a new CycleInfo with the specified cycle tracking data
*
* @param cyclesCompleted - Number of cycles through the starting verse
* @param crossedBoundaries - Whether navigation wrapped around boundaries
* @param cycleDistance - Distance in lines for one full cycle
*/
constructor(cyclesCompleted = 0, crossedBoundaries = false, cycleDistance = 0.0) {
this.cyclesCompleted = cyclesCompleted;
this.crossedBoundaries = crossedBoundaries;
this.cycleDistance = cycleDistance;
}
/**
* Create a new CycleInfo with no cycles
*
* @returns A CycleInfo instance with all fields set to default values
*/
static none() {
return new CycleInfo(0, false, 0.0);
}
/**
* Reconstruct total navigated distance from direct distance
*
* This helper method implements the formula:
* ```text
* total_distance = (cycles_completed - 1) * cycle_distance + direct_distance
* ```
*
* This formula works because:
* - `cyclesCompleted` counts passes through the start verse
* - Each pass adds one `cycleDistance` worth of lines
* - The current (partial or complete) cycle contributes `directDistance`
* - So we have (N-1) complete previous cycles + current cycle
*
* @param directDistance - The direct distance from start to end (from calculateLines)
* @returns The total distance including cycles
*
* @example
* ```typescript
* // If we completed 3 cycles with cycleDistance=100 and directDistance=25
* const cycleInfo = new CycleInfo(3, true, 100.0);
* const total = cycleInfo.reconstructDistance(25.0);
* // total = (3-1)*100 + 25 = 225.0
* ```
*/
reconstructDistance(directDistance) {
if (this.cyclesCompleted > 0) {
return (this.cyclesCompleted - 1) * this.cycleDistance + directDistance;
}
return directDistance;
}
}
exports.CycleInfo = CycleInfo;
/**
* Comprehensive result of a navigation operation in the Quran
*
* This class contains the primary verse reached through navigation,
* as well as optional information about any overflow conditions or boundary
* verses (last verse of page or sura) encountered during navigation.
*/
class NavigationResult {
/**
* Create a new NavigationResult with all possible parameters
*
* @param verse - The verse reached through navigation
* @param overflow - Optional information about overflow if navigation exceeded boundaries
* @param endOfPage - Optional information about the last verse of the page if encountered
* @param endOfSura - Optional information about the last verse of the sura if encountered
* @param distanceMoved - The actual distance moved during navigation in lines
* @param cycleInfo - Information about navigation cycles and boundary crossings
*/
constructor(verse, overflow, endOfPage, endOfSura, distanceMoved = 0, cycleInfo = CycleInfo.none()) {
this.verse = verse;
this.overflow = overflow;
this.endOfPage = endOfPage;
this.endOfSura = endOfSura;
this.distanceMoved = distanceMoved;
this.cycleInfo = cycleInfo;
// Calculate remaining distance
const remainingDistance = overflow
? overflow.overflowedVerse.lines - overflow.overflowLines
: 0.0;
this.remainingDistance = Math.round(remainingDistance * 100) / 100;
}
/**
* Create a new NavigationResult for a normal navigation with no boundary conditions
*
* @param verse - The verse reached through navigation
* @param distanceMoved - The actual distance moved during navigation in lines
* @param cycleInfo - Optional cycle information
* @returns A new NavigationResult instance with only the target verse and no boundary information
*/
static newNormal(verse, distanceMoved, cycleInfo = CycleInfo.none()) {
return new NavigationResult(verse, undefined, undefined, undefined, distanceMoved, cycleInfo);
}
/**
* Create a new NavigationResult for a navigation that includes overflow
*
* @param verse - The verse reached through navigation
* @param overflow - Information about overflow during navigation
* @param distanceMoved - The actual distance moved during navigation in lines
* @param cycleInfo - Optional cycle information
* @returns A new NavigationResult instance with the target verse and overflow information
*/
static newOverflowed(verse, overflow, distanceMoved, cycleInfo = CycleInfo.none()) {
return new NavigationResult(verse, overflow, undefined, undefined, distanceMoved, cycleInfo);
}
/**
* Create a new NavigationResult that reached the last verse of a page
*
* @param verse - The verse reached through navigation
* @param lastOfPage - Information about the last verse of the page
* @param distanceMoved - The actual distance moved during navigation in lines
* @param cycleInfo - Optional cycle information
* @returns A new NavigationResult instance with the target verse and last-of-page information
*/
static newPageBoundary(verse, lastOfPage, distanceMoved, cycleInfo = CycleInfo.none()) {
return new NavigationResult(verse, undefined, lastOfPage, undefined, distanceMoved, cycleInfo);
}
/**
* Create a new NavigationResult that reached the last verse of a sura
*
* @param verse - The verse reached through navigation
* @param lastOfSura - Information about the last verse of the sura
* @param distanceMoved - The actual distance moved during navigation in lines
* @param cycleInfo - Optional cycle information
* @returns A new NavigationResult instance with the target verse and last-of-sura information
*/
static newSuraBoundary(verse, lastOfSura, distanceMoved, cycleInfo = CycleInfo.none()) {
return new NavigationResult(verse, undefined, undefined, lastOfSura, distanceMoved, cycleInfo);
}
/**
* Check if this navigation result encountered any boundaries
*
* @returns true if the navigation hit the last verse of page, sura, or had an overflow
*/
hasBoundaries() {
return this.overflow !== undefined ||
this.endOfPage !== undefined ||
this.endOfSura !== undefined;
}
/**
* Check if this navigation result encountered an overflow condition
*
* @returns true if the navigation exceeded available boundaries
*/
hasOverflow() {
return this.overflow !== undefined;
}
/**
* Get the total overflow lines if any exist
*
* @returns The number of overflow lines, or 0.0 if no overflow occurred
*/
overflowLines() {
return this.overflow ? this.overflow.overflowLines : 0.0;
}
/**
* Create a string representation of the navigation result
*/
toString() {
let result = `Target verse: ${this.verse}\n`;
result += `Distance Moved: ${this.distanceMoved}\n`;
result += `Remaining Distance: ${this.remainingDistance}\n`;
if (this.cycleInfo.cyclesCompleted > 0) {
result += `Cycles Completed: ${this.cycleInfo.cyclesCompleted}\n`;
result += `Cycle Distance: ${this.cycleInfo.cycleDistance}\n`;
result += `Crossed Boundaries: ${this.cycleInfo.crossedBoundaries}\n`;
}
if (this.overflow) {
result += `Overflow: ${this.overflow}\n`;
}
if (this.endOfPage) {
result += `End of page: ${this.endOfPage}\n`;
}
if (this.endOfSura) {
result += `End of sura: ${this.endOfSura}\n`;
}
return result;
}
}
exports.NavigationResult = NavigationResult;