@sahabaplus/mushaf-engine
Version:
TypeScript implementation of a Quran Mushaf navigation engine
268 lines (267 loc) • 11.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseMushafEngine = void 0;
const mushaf_1 = require("../mushaf");
const navigation_1 = require("../navigation");
/**
* Basic implementation of the Mushaf navigation engine
*/
class BaseMushafEngine {
/**
* Create a new BaseMushafEngine with the given Mushaf
*
* @param mushaf - The Mushaf to navigate through
*/
constructor(mushaf) {
this.mushaf = mushaf;
this.quranMetadata = mushaf_1.QuranMetadata.fromMushaf(mushaf);
}
/**
* Get metadata about a specific Sura
*
* @param suraNumber - Sura number (1-114)
* @returns Information about the specified Sura, or undefined if not found
*/
getSuraInfo(suraNumber) {
return this.quranMetadata.getSuraInfo(suraNumber);
}
/**
* Find the next verse from a given verse in the specified direction
*
* @param params - Object containing sura_number, verse_number, and direction
* @returns The next verse or undefined if at the end
*/
nextVerse({ sura_number, verse_number, direction, }) {
const navigator = new navigation_1.VersesNavigator(this.mushaf, this.quranMetadata);
navigator.resetPosition(sura_number, verse_number);
return navigator.nextVerse(direction);
}
/**
* Find a verse in the mushaf and return its location (Verse, page, index_of_verse)
*
* @param suraNumber - Sura number (1-114)
* @param verseNumber - Verse number within the sura
* @returns The verse and its location, or undefined if not found
*/
findVerse(suraNumber, verseNumber) {
const sura = this.quranMetadata.getSuraInfo(suraNumber);
if (sura) {
const pages = this.mushaf.pages;
for (let i = sura.startPage; i <= sura.endPage; i++) {
const page = pages[i - 1];
for (let j = 0; j < page.verses().length; j++) {
const verse = page.verses()[j];
if (verse.sura === suraNumber && verse.number === verseNumber) {
return [verse, page.number, j];
}
}
}
}
return undefined;
}
/**
* Calculate the lines between start and end verses
*
* @param startSura - Start sura number
* @param startVerse - Start verse number
* @param endSura - End sura number
* @param endVerse - End verse number
* @param direction - Direction of navigation
* @returns Number of lines between the two verses
*/
calculateLines(startSura, startVerse, endSura, endVerse, direction) {
// Assert direction is correct for the sura ordering
if (!((startSura < endSura && direction === navigation_1.Direction.Downwards) ||
(startSura > endSura && direction === navigation_1.Direction.Upwards) ||
startSura === endSura)) {
throw new Error("Invalid direction for sura ordering");
}
if (startSura === endSura && startVerse > endVerse) {
throw new Error("Start verse should be smaller than end verse");
}
const startVerseResult = this.findVerse(startSura, startVerse);
const endVerseResult = this.findVerse(endSura, endVerse);
if (!startVerseResult || !endVerseResult) {
throw new Error("Verse not found");
}
const startVerseObj = startVerseResult[0];
const endVerseObj = endVerseResult[0];
const navigator = new navigation_1.VersesNavigator(this.mushaf, this.quranMetadata);
navigator.resetPosition(startVerseObj.sura, startVerseObj.number);
let lines = 0;
do {
lines =
Math.round((lines + this.calculateVerseLines(navigator.currentVerse())) * 100) / 100;
if (navigator.currentVerse().sura === endVerseObj.sura &&
navigator.currentVerse().number === endVerseObj.number) {
break;
}
} while (navigator.nextVerse(direction));
return lines;
}
/**
* Calculate the lines taken by a verse including any sura headers
*
* @param verse - The verse to calculate lines for
* @returns The total number of lines taken by the verse
*/
calculateVerseLines(verse) {
let totalLines = verse.lines;
// Add lines for sura headers if this is the first verse of a sura
if (verse.number === 1) {
// All suras except At-Tawbah (9) have bismillah
if (verse.sura !== 9) {
totalLines += 2.0; // Sura title + bismillah
}
else {
totalLines += 1.0; // Only sura title for At-Tawbah
}
}
return totalLines;
}
/**
* Check if verses are in opposite direction
*
* @param startSura - Start sura number
* @param endSura - End sura number
* @param direction - Direction of navigation
* @returns True if verses are in opposite direction
*/
static isInOppositeDirection(startSura, endSura, direction) {
return !((startSura < endSura && direction === navigation_1.Direction.Downwards) ||
(startSura > endSura && direction === navigation_1.Direction.Upwards) ||
startSura === endSura);
}
/**
* Determine which last of sura result to prefer
*
* @param lastOfSura - Existing last of sura result, if any
* @param currentVerse - Current verse being processed
* @param direction - Direction of navigation
* @returns The preferred last of sura result
*/
preferLastOfSura(lastOfSura, currentVerse, direction) {
if (lastOfSura && currentVerse.sura === lastOfSura.lastVerse.sura) {
return lastOfSura;
}
const sura = this.quranMetadata.getSuraInfo(currentVerse.sura);
if (!sura) {
throw new Error(`Sura info not found for sura ${currentVerse.sura}`);
}
const lastVerseResult = this.findVerse(sura.number, sura.totalVerses);
if (!lastVerseResult) {
throw new Error(`Last verse not found for sura ${sura.number}`);
}
const lastVerse = lastVerseResult[0];
const linesDistance = this.calculateLines(currentVerse.sura, currentVerse.number, lastVerse.sura, lastVerse.number, direction) - currentVerse.lines;
if (lastOfSura &&
Math.abs(lastOfSura.linesDistance) + 3.0 < Math.abs(linesDistance)) {
// Added 3 to prefer the new last
return lastOfSura;
}
else {
return new navigation_1.LastVerseResult(linesDistance, lastVerse);
}
}
/**
* Determine which last of page result to prefer
*
* @param lastOfPage - Existing last of page result, if any
* @param currentVerse - Current verse being processed
* @param direction - Direction of navigation
* @returns The preferred last of page result
*/
preferLastOfPage(lastOfPage, currentVerse, direction) {
if (lastOfPage && currentVerse.equals(lastOfPage.lastVerse)) {
return lastOfPage;
}
const verseInfo = this.findVerse(currentVerse.sura, currentVerse.number);
if (!verseInfo) {
throw new Error(`Verse info not found for sura ${currentVerse.sura}, verse ${currentVerse.number}`);
}
const page = this.mushaf.getPage(verseInfo[1]);
if (!page) {
throw new Error(`Page not found for page number ${verseInfo[1]}`);
}
const newLastOfPage = page.verses()[page.verses().length - 1];
if (BaseMushafEngine.isInOppositeDirection(currentVerse.sura, newLastOfPage.sura, direction)) {
return lastOfPage;
}
const linesDistance = this.calculateLines(currentVerse.sura, currentVerse.number, newLastOfPage.sura, newLastOfPage.number, direction);
if (lastOfPage &&
Math.abs(lastOfPage.linesDistance) + 1.0 < Math.abs(linesDistance)) {
// Added 1 to prefer the new last
return lastOfPage;
}
else {
return new navigation_1.LastVerseResult(linesDistance, newLastOfPage);
}
}
/**
* Navigate from a verse by a specified number of lines in the given direction
*
* @param params - Navigation parameters
* @returns Navigation result containing the verse at the destination after navigation
*/
navigate(params) {
const { lines, fromSura, fromVerse, direction } = params;
// Validate inputs
if (lines < 0) {
throw new Error("Lines should be positive or zero");
}
// If no lines to navigate, find and return the current verse
if (lines === 0) {
const verseResult = this.findVerse(fromSura, fromVerse);
if (verseResult) {
return navigation_1.NavigationResult.newNormal(verseResult[0], 0);
}
throw new Error("Verse Not Found!");
}
// Find starting position
const verseResult = this.findVerse(fromSura, fromVerse);
if (!verseResult) {
throw new Error("Verse Not Found!");
}
const [verse, page, verseIdx] = verseResult;
let remainingLines = lines;
const navigator = new navigation_1.VersesNavigator(this.mushaf, this.quranMetadata);
navigator.resetPosition(verse.sura, verse.number);
let overflow;
let previousVerse = navigator.currentVerse();
let lastOfPage;
let lastOfSura;
while (true) {
const currentVerse = navigator.currentVerse();
const currentSuraInfo = this.quranMetadata.getSuraInfo(currentVerse.sura);
if (!currentSuraInfo) {
throw new Error(`Sura info not found for sura ${currentVerse.sura}`);
}
const verseLines = this.calculateVerseLines(currentVerse);
const diff = Math.round((remainingLines - verseLines) * 100) / 100;
if (currentVerse.isLastOfPage()) {
lastOfPage = new navigation_1.LastVerseResult(-1.0 * diff, currentVerse);
}
if (currentSuraInfo.totalVerses === currentVerse.number) {
lastOfSura = new navigation_1.LastVerseResult(-1.0 * diff, currentVerse);
}
if (diff < 0.0) {
overflow = new navigation_1.OverflowResult(-1.0 * diff, currentVerse);
break;
}
remainingLines = diff;
previousVerse = currentVerse;
if (remainingLines > 0.0) {
const nextVerse = navigator.nextVerse(direction);
if (!nextVerse)
break; // End of navigation
}
else {
break;
}
}
const finalLastOfSura = this.preferLastOfSura(lastOfSura, previousVerse, direction);
const finalLastOfPage = this.preferLastOfPage(lastOfPage, previousVerse, direction);
return new navigation_1.NavigationResult(previousVerse, overflow, finalLastOfPage, finalLastOfSura, lines - remainingLines);
}
}
exports.BaseMushafEngine = BaseMushafEngine;