kokokor
Version:
A lightweight TypeScript library designed to reconstruct paragraphs from OCRed inputs.
558 lines • 21.2 kB
TypeScript
//#region src/types.d.ts
/**
* Represents a rectangular bounding box with position and dimensions.
* Used to define the location and size of text elements and structural components
* within a document coordinate system.
*
* The coordinate system typically uses:
* - Origin (0,0) at the top-left corner of the document
* - X-axis extending rightward (for LTR text) or leftward (for RTL text after normalization)
* - Y-axis extending downward
*/
type BoundingBox = Size & {
/**
* The x-coordinate of the top-left corner of the bounding box in pixels.
* This coordinate may be normalized depending on text direction:
* - For LTR (left-to-right) text: represents distance from left edge
* - For RTL (right-to-left) text: may be adjusted during processing
*/
x: number;
/**
* The y-coordinate of the top-left corner of the bounding box in pixels.
* Represents the distance from the top edge of the document.
*/
y: number;
};
/**
* Configuration options for determining if text content is centered on a page.
* These settings control the tolerance and margin requirements for center detection,
* which is important for identifying titles, headings, and poetic content.
*/
type CenteringOptions = {
/**
* The tolerance for center point alignment as a ratio of image width.
* This determines how precisely text must be centered to be considered "centered".
*
* For example:
* - 0.05 means the observation's center can be within 5% of the page width
* from the true center and still be considered centered
* - 0.02 would require more precise centering (within 2%)
* - 0.1 would allow looser centering requirements (within 10%)
*
* @default 0.05
*/
readonly centerToleranceRatio: number;
/**
* The minimum margin required on each side as a ratio of image width.
* This ensures that "centered" text has adequate whitespace around it.
*
* For example:
* - 0.1 means there must be at least 10% of the page width as whitespace
* on both the left and right sides of the observation
* - 0.2 would require larger margins (20% on each side)
* - 0.05 would allow tighter margins (5% on each side)
*
* @default 0.1
*/
readonly minMarginRatio: number;
};
/**
* Configuration options for the main text line mapping function.
*
* This type combines all the configuration needed to process OCR observations
* into structured text lines, including poetry detection, centering analysis,
* and layout element detection.
*/
type MapObservationsToTextLinesOptions = CenteringOptions & {
/**
* Optional array of horizontal line elements detected in the document.
* These are typically used to identify sections, headers, footers, or decorative elements.
* When provided, text appearing below these lines may be classified as footnotes.
*/
horizontalLines?: BoundingBox[]; /** Are the coordinates from a RTL language? If it is we would flip the x-axis. */
isRTL?: boolean;
/**
* Optional fixed line height factor for grouping observations into lines.
* If not provided, the system will compute an adaptive factor based on document analysis.
*
* Typical values:
* - 0.5: Very tight line grouping
* - 1.0: Standard line height
* - 1.5: Generous line spacing tolerance
*/
lineHeightFactor?: number;
/**
* Optional logging function for debugging and monitoring the text processing pipeline.
* When provided, the system will output detailed information about its decisions
* and intermediate processing steps.
*
* @param message - Primary log message
* @param args - Additional arguments for formatting or context
*/
log?: (message: string, ...args: any[]) => void;
/**
* Additional vertical tolerance in pixels (at 72 DPI) for line grouping.
* This value is automatically scaled based on the document's actual DPI.
*
* Higher values make line grouping more permissive (more text on same line).
* Lower values make line grouping stricter (more separate lines).
*
* @default 5 pixels at 72 DPI
*/
pixelTolerance?: number;
/**
* Configuration options for poetry detection algorithms.
* If not provided, default poetry detection settings will be used.
* Set to null or undefined to disable poetry detection entirely.
*/
poetryDetectionOptions?: Partial<PoetryDetectionOptions>;
/**
* Delimiter used when merging a detected poetry pair (hemistichs) into a single line.
*
* Example: `" ... "` formats a pair as `صدر ... عجز`.
*
* @default " "
*/
poetryPairDelimiter?: string;
/**
* Optional array of rectangular elements detected in the document.
* These are typically used to identify text boxes, highlighted sections, or headers.
* When provided, text within these rectangles may be classified as headings.
*/
rectangles?: BoundingBox[];
};
/**
* Options for grouping text lines into paragraphs.
*
* These options replace positional arguments with named fields so callers can
* tune behavior without relying on parameter ordering.
*/
type ParagraphOptions = {
/**
* Factor for detecting paragraph breaks based on vertical spacing.
*
* Higher values make break detection stricter.
*
* @default 2
*/
verticalJumpFactor?: number;
/**
* Threshold for identifying short lines that indicate paragraph endings.
*
* Lower values mark fewer lines as "short".
*
* @default 0.85
*/
widthTolerance?: number;
};
/**
* Represents a basic text observation extracted from OCR processing.
*
* This is the fundamental unit of OCR output, containing both the recognized
* text content and its precise location within the document. Observations
* typically represent individual words or short phrases as identified by
* the OCR engine.
*/
type Observation = {
/**
* The bounding box defining the exact position and dimensions of the text
* within the document coordinate system. This includes:
* - Position (x, y) of the top-left corner
* - Dimensions (width, height) of the text area
*/
bbox: BoundingBox;
/**
* The recognized text content of the observation.
* This is the actual text string extracted by the OCR engine,
* which may include punctuation, numbers, or special characters.
*/
text: string;
};
/**
* Canonical page metadata required for reconstruction.
*/
type PageContext = {
/**
* Page width in pixels.
*/
width: number;
/**
* Page height in pixels.
*/
height: number;
/**
* Horizontal DPI.
*/
dpiX: number;
/**
* Vertical DPI.
*/
dpiY: number;
};
/**
* Optional layout primitives extracted from a page.
*/
type LayoutElements = {
/**
* Horizontal separators used for footnote boundary detection.
*/
horizontalLines?: BoundingBox[];
/**
* Rectangles used for heading/boxed-content detection.
*/
rectangles?: BoundingBox[];
};
/**
* Input payload for one-shot paragraph reconstruction.
*/
type ReconstructInput = {
/**
* OCR observations to reconstruct.
*/
observations: Observation[];
/**
* Page geometry and DPI context.
*/
page: PageContext;
/**
* Optional structural layout hints.
*/
layout?: LayoutElements;
};
/**
* Optional knobs for one-shot paragraph reconstruction.
*/
type ReconstructOptions = {
/**
* Line-detection options.
*/
line?: Partial<MapObservationsToTextLinesOptions>;
/**
* Paragraph-detection options.
*/
paragraph?: ParagraphOptions;
/**
* Text formatting options.
*/
format?: {
/**
* Optional symbol to insert before first footnote.
*/
footerSymbol?: string;
};
};
/**
* Configuration options to fine-tune the poetry detection algorithm.
*
* Poetry detection uses multiple heuristics to identify poetic content:
* 1. **Paired hemistichs**: Two short lines that appear to be halves of a verse
* 2. **Merged lines**: Single lines with distinctive spacing/density characteristics
* 3. **Centering**: Lines that are centered with appropriate margins
*
* These options allow fine-tuning of each heuristic to balance precision and recall
* for different types of documents and poetry styles.
*/
type PoetryDetectionOptions = Partial<CenteringOptions> & {
/**
* Maximum allowed vertical gap between observations to be considered a poetry pair.
* This controls how close two lines must be to be considered hemistichs (halves of a verse).
*
* The gap is measured as a ratio of the average height of the two observations:
* - 2.0 means the gap can be up to 200% of the average line height
* - 1.5 would require closer spacing (150% of average height)
* - 3.0 would allow wider spacing (300% of average height)
*
* @default 2.0 (200% of average height)
*/
maxVerticalGapRatio: number;
/**
* For merged lines heuristic: The minimum width a line must have to be considered.
* This prevents very short lines (like page numbers) from being analyzed for poetry.
*
* Specified as a ratio of the image width:
* - 0.6 means the line must span at least 60% of the page width
* - 0.4 would include shorter lines (40% of page width)
* - 0.8 would require longer lines (80% of page width)
*
* @default 0.6 (60% of image width)
*/
minWidthRatioForMerged: null | number;
/**
* The minimum number of words a line must contain to be considered poetry.
* This helps filter out noise like page numbers, single-word labels, or artifacts.
*
* Higher values reduce false positives but may miss short poetic lines.
* Lower values increase sensitivity but may include more noise.
*
* @default 2
*/
minWordCount: number;
/**
* For paired lines heuristic: How similar in width two hemistichs must be.
* This determines whether two lines are balanced enough to be verse halves.
*
* The similarity check: `|width1 - width2| / average(width1, width2) < ratio`
* - 0.4 means widths can differ by up to 40% of their average
* - 0.2 would require more similar widths (20% difference)
* - 0.6 would allow more variation (60% difference)
*
* @default 0.4 (40% width difference allowed)
*/
pairWidthSimilarityRatio: number;
/**
* For paired lines heuristic: How similar in word count two hemistichs must be.
* This ensures that verse halves have balanced content length.
*
* The similarity check: `|count1 - count2| / max(count1, count2) < ratio`
* - 0.5 means word counts can differ by up to 50% of the larger count
* - 0.3 would require more similar counts (30% difference)
* - 0.7 would allow more variation (70% difference)
*
* @default 0.5 (50% word count difference allowed)
*/
pairWordCountSimilarityRatio: number;
/**
* For merged lines heuristic: Word density threshold for identifying poetry.
* Poetry typically has lower word density (more spacing) than prose.
*
* A line is considered poetic if its density (words per pixel) is less than
* this ratio multiplied by the average prose density of the document.
*
* - 0.8 means poetic lines should have ≤80% of average prose density
* - 0.9 would require density closer to prose (≤90%)
* - 0.7 would require sparser text (≤70% of prose density)
*
* @default 0.8 (80% of average prose density)
*/
wordDensityComparisonRatio: number;
};
/**
* Represents the dimensions of a rectangular area.
* Used as a base type for defining size properties in bounding boxes and other geometric shapes.
*/
type Size = {
/**
* The height of the rectangular area in pixels.
*/
readonly height: number;
/**
* The width of the rectangular area in pixels.
*/
readonly width: number;
};
/**
* A processed text block reconstructed from raw OCR observations.
*
* TextBlock represents a higher-level text unit (typically a line or paragraph)
* that has been assembled from individual OCR observations and enriched with
* semantic information about its role and characteristics within the document.
*
* This type extends the basic Observation with additional metadata that helps
* in document structure analysis, formatting preservation, and content classification.
*/
type TextBlock = Observation & {
/**
* Indicates whether the text is centered on the page.
*
* This is determined by analyzing the text's position relative to page margins
* and ensuring adequate whitespace on both sides. Centered text often indicates:
* - Document titles and headings
* - Poetry or verse content
* - Section headers
* - Epigraphs or quotes
*
* The centering detection uses configurable tolerance and margin ratios
* to account for slight misalignments and varying document layouts.
*/
isCentered?: boolean;
/**
* Indicates whether this text is identified as a footnote.
*
* Footnotes are typically detected by their position relative to horizontal
* line elements in the document. Text appearing below the last significant
* horizontal line is often classified as footnote content. This classification
* helps in:
* - Separating main content from supplementary information
* - Proper document structure reconstruction
* - Academic and formal document processing
*/
isFootnote?: boolean;
/**
* Indicates whether the text represents a heading or title.
*
* Headings are often identified by their visual presentation, such as:
* - Being enclosed within rectangular borders or boxes
* - Having distinctive spacing or positioning
* - Being centered or specially formatted
*
* This classification helps in:
* - Document outline generation
* - Hierarchical content structuring
* - Navigation and indexing
*/
isHeading?: boolean;
/**
* Indicates whether this text is identified as a line of poetry or verse.
*
* Poetic content is detected using multiple heuristics including:
* - Line length and spacing patterns
* - Word density analysis
* - Centering and alignment characteristics
* - Paired hemistich detection
*
* Poetic lines receive special treatment during processing:
* - They are not merged into standard paragraphs
* - Line breaks are preserved as semantically significant
* - Spacing and formatting are maintained more strictly
*
* This is crucial for preserving the artistic and structural integrity
* of poems, verses, and other formatted literary content.
*/
isPoetic?: boolean;
};
/**
* Output payload from one-shot paragraph reconstruction.
*/
type ReconstructResult = {
/**
* Intermediate line-level blocks.
*/
lines: TextBlock[];
/**
* Final paragraph-level blocks.
*/
paragraphs: TextBlock[];
/**
* Formatted plain text output.
*/
text: string;
};
//#endregion
//#region src/utils/grouping.d.ts
/**
* Merges the group of observations into a single one.
* @param group The group of observations to merge.
* @param delimiter Text delimiter used when concatenating observations.
* @returns A single observation with the text of the group concatenated as well as the bounding box adjusted to fit all of the contents.
*/
declare const mergeObservations: <T extends Observation>(group: T[], delimiter?: string) => T;
//#endregion
//#region src/utils/layout.d.ts
/**
* Filters out horizontal lines that are contained within any of the provided rectangles.
*
* This is useful for removing header/footer lines that appear within document sections
* while preserving lines that mark true document boundaries or section separators.
*
* @param rectangles - Array of rectangles to check containment against
* @param horizontalLines - Array of horizontal lines to filter
* @param tolerance - Pixel tolerance for boundary checking (default: 5)
* @returns Array of horizontal lines that are NOT contained within any rectangle
*/
declare const filterHorizontalLinesOutsideRectangles: (rectangles: BoundingBox[], horizontalLines: BoundingBox[], tolerance?: number) => BoundingBox[];
/**
* Converts bounding box coordinates from array format to object format.
* Transforms [x1, y1, x2, y2] coordinates to {x, y, width, height} format.
*
* @param box - Array containing [x1, y1, x2, y2] coordinates where (x1,y1) is top-left and (x2,y2) is bottom-right
* @returns Bounding box object with x, y, width, and height properties
*/
declare const mapMatrixToBoundingBox: (box: [number, number, number, number]) => {
height: number;
width: number;
x: number;
y: number;
};
//#endregion
//#region src/utils/marking.d.ts
/**
* Calculates the DPI (dots per inch) based on image dimensions and original PDF size.
*
* This utility function helps determine the resolution at which a PDF was rasterized
* by comparing the resulting image dimensions with the original PDF page dimensions.
* The DPI values are essential for proper scaling of pixel-based tolerances and
* measurements throughout the document processing pipeline.
*
* @param imageSize - Dimensions of the rasterized image in pixels
* @param pdfSize - Original dimensions of the PDF page in points (1/72 inch)
* @returns Object containing x and y DPI values
*
* @example
* ```typescript
* const imageSize = { width: 2480, height: 3508 };
* const pdfSize = { width: 595, height: 842 }; // A4 page in points
* const dpi = calculateDPI(imageSize, pdfSize);
* // Result: { x: 300, y: 300 } for a 300 DPI scan
* ```
*/
declare const calculateDPI: (imageSize: Size, pdfSize: Size) => {
x: number;
y: number;
};
//#endregion
//#region src/utils/options.d.ts
/**
* Resolves optional overrides against a default options object.
*
* This is a shallow merge helper intended for option bags where
* top-level keys are merged and nested objects are handled explicitly
* by the caller when needed.
*/
declare const resolveWithDefaults: <T extends object>(defaults: T, overrides?: Partial<T>) => T;
//#endregion
//#region src/utils/paragraphs.d.ts
/**
* Preprocesses observations by filtering noise, flipping coordinates for RTL text,
* and normalizing x-coordinates for proper alignment.
*
* @param observations - Array of text observations to preprocess
* @param imageWidth - Total width of the document/image in pixels
* @param dpiX - Horizontal DPI for coordinate normalization
* @param options - Optional logging configuration
* @returns Preprocessed observations ready for line grouping
*/
declare const flipAndAlignObservations: (observations: Observation[], imageWidth: number, dpiX: number, options?: Partial<Pick<MapObservationsToTextLinesOptions, "isRTL" | "log">>) => Observation[];
/**
* Converts OCR observations into structured text lines with metadata.
*
* Groups observations into lines based on vertical proximity, applies centering detection,
* identifies headings (text within rectangles), footnotes (text below horizontal lines),
* and poetic content. Also performs poetry detection to preserve poetic formatting.
*
* @param observations - Array of OCR text observations
* @param page - Page dimensions and DPI information.
* @param opts - Configuration options for text line processing
* @returns Array of text blocks with metadata (centering, headings, footnotes, poetry)
*/
declare const mapObservationsToTextLines: (observations: Observation[], page: PageContext, opts?: Partial<MapObservationsToTextLinesOptions>) => TextBlock[];
/**
* Groups text lines into coherent paragraphs, handling both prose and poetry.
*
* Prose lines are grouped into paragraphs based on vertical spacing and line width patterns.
* Poetic lines are preserved individually to maintain their formatting.
* Processes body content and footnotes separately.
*
* @param textLines - Array of text lines to group into paragraphs
* @param options - Object-based paragraph detection settings.
* @returns Array of text blocks representing complete paragraphs
*/
declare const mapTextLinesToParagraphs: (textLines: TextBlock[], options?: ParagraphOptions) => TextBlock[];
//#endregion
//#region src/index.d.ts
/**
* Formats an array of text blocks into a readable string with proper paragraph breaks.
*
* @param textBlocks - Array of text blocks to format
* @param footerSymbol - Optional symbol to insert before the first footnote
* @returns Formatted text string with proper line breaks and spacing
*/
declare const formatTextBlocks: (textBlocks: TextBlock[], footerSymbol?: string) => string;
/**
* One-shot API for OCR paragraph reconstruction.
*
* Converts observations into lines, groups lines into paragraphs, then formats text.
*/
declare const reconstructParagraphs: (input: ReconstructInput, options?: ReconstructOptions) => ReconstructResult;
//#endregion
export { BoundingBox, CenteringOptions, LayoutElements, MapObservationsToTextLinesOptions, Observation, PageContext, ParagraphOptions, PoetryDetectionOptions, ReconstructInput, ReconstructOptions, ReconstructResult, Size, TextBlock, calculateDPI, filterHorizontalLinesOutsideRectangles, flipAndAlignObservations, formatTextBlocks, mapMatrixToBoundingBox, mapObservationsToTextLines, mapTextLinesToParagraphs, mergeObservations, reconstructParagraphs, resolveWithDefaults };
//# sourceMappingURL=index.d.ts.map