All files / src/lib OcrService.ts

100% Statements 20/20
70% Branches 7/10
100% Functions 7/7
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97                                                                7x 7x 7x 7x 7x 7x 7x                               10x 8x 8x   8x 4x   4x       9x             4x         10x     4x                     1x       5x  
import { recognize, type OcrWord } from './Ocr';
 
/**
 * Represents a single recognized text region normalized to image dimensions.
 */
export interface RecognizedTextBlock {
  text: string;
  /** Normalized coordinates (0 to 1) relative to the source image dimensions */
  bbox: {
    x: number;
    y: number;
    width: number;
    height: number;
  };
  confidence?: number;
}
 
export interface OcrServiceResult {
  text: string;
  blocks: RecognizedTextBlock[];
  words: OcrWord[];
}
 
export class OcrService {
  /**
   * Normalizes pixel bounding box to percentage coordinates (0..1) relative to image dimensions.
   */
  static normalizeBbox(
    bbox: { x0: number; y0: number; x1: number; y1: number },
    imageWidth: number,
    imageHeight: number,
  ): RecognizedTextBlock['bbox'] {
    const w = Math.max(1, imageWidth);
    const h = Math.max(1, imageHeight);
    const x0 = Math.min(Math.max(bbox.x0, 0), w);
    const y0 = Math.min(Math.max(bbox.y0, 0), h);
    const x1 = Math.min(Math.max(bbox.x1, 0), w);
    const y1 = Math.min(Math.max(bbox.y1, 0), h);
    return {
      x: x0 / w,
      y: y0 / h,
      width: Math.max(0, (x1 - x0) / w),
      height: Math.max(0, (y1 - y0) / h),
    };
  }
 
  /**
   * Recognizes text from a canvas using on-device OCR.
   * Returns normalized bounding boxes for recognized text blocks.
   */
  static async recognize(
    canvas: HTMLCanvasElement,
    progress?: (p: number) => void,
  ): Promise<OcrServiceResult> {
    const res = await recognize(canvas, progress);
    const w = canvas.width;
    const h = canvas.height;
 
    if (res.blocks && res.blocks.length > 0) {
      return {
        text: res.text.normalize('NFC'),
        blocks: res.blocks.map((b) => ({
          ...b,
          text: b.text.normalize('NFC'),
        })),
        words: (res.words ?? []).map((w) => ({
          ...w,
          text: w.text.normalize('NFC'),
        })),
      };
    }
 
    const blocks: RecognizedTextBlock[] = (res.words ?? []).map((word) => ({
      text: word.text.normalize('NFC'),
      bbox: OcrService.normalizeBbox(word, w, h),
    }));
 
    return {
      text: res.text.normalize('NFC'),
      blocks,
      words: (res.words ?? []).map((w) => ({
        ...w,
        text: w.text.normalize('NFC'),
      })),
    };
  }
 
  async recognize(
    canvas: HTMLCanvasElement,
    progress?: (p: number) => void,
  ): Promise<OcrServiceResult> {
    return OcrService.recognize(canvas, progress);
  }
}
 
export const ocrService = new OcrService();