UNPKG

@sahabaplus/mushaf-engine

Version:

TypeScript implementation of a Quran Mushaf navigation engine

268 lines (267 loc) 11.3 kB
"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;