ocr.mjs

import tesseract from 'tesseract.js' //要能於nodejs下運行, 故不使用瀏覽器動態加載
import get from 'lodash-es/get.js'
import isestr from './isestr.mjs'
import getGlobal from './getGlobal.mjs'


function getTesseract() {
    let g = getGlobal()
    let x = tesseract || g.Tesseract
    return x
}


/**
 * 基於tesseract.js的文字辨識(OCR),注意瀏覽器版精度較差
 *
 * Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/ocr.test.mjs Github}
 * @memberOf wsemi
 * @param {String} img 輸入圖片網址或base64格式(以data:開頭)字串
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {String} [opt.lang='eng'] 輸入辨識語系字串,可輸入'eng'、'chi_tra'、'chi_sim'等,多語系可用「+」合併,預設'eng+chi_tra+chi_sim'
 * @param {String} [opt.whitelist=undefined] 輸入白名單字串,可指定輸入只辨識之字元,預設undefined
 * @returns {Object} 回傳辨識結果物件
 * @example
 *
 * function getImg() {
 *     function getEng() {
 *         return 'data:image/png;base64,...'
 *     }
 *     function getTra() {
 *         return 'data:image/png;base64,...'
 *     }
 *     function getSim() {
 *         return 'data:image/png;base64,...'
 *     }
 *
 *     let resEng = `
 * Mild Splendour of the various-vested Night!
 * Mother of wildly-working visions! haill
 * I watch thy gliding, while with watery light
 * Thy weak eye glimmers through a fleecy veil;
 * And when thou lovest thy pale orb to shroud
 * Behind the gather’'d blackness lost on high;
 * And when thou dartest from the wind-rent cloud
 * Thy placid lightning o’er the awakend sky.
 * `
 *
 *     let resChiTra = `
 * 齎 家 王 說 闊 夜 又 山 ‧ 碰 巧 撞 上 了 風 妻 綽 約 的 寨 主 鍾 無 艷 , 一 人 一 見 鍾 情 。 鍾 情
 * 於 鍾 無 艷 的 狐 狸 精 被 拒 愛 , 於 是 向 無 艷 施 『 愛 情 咒 」, 使 其 臉 上 忽 然 多 了 塊 大
 * 痧 ‧ 把 齊 宣 王 嚇 得 採 腿 而 逃 ‧ 二 人 媒 情 從 此 障 礙 重 重 。
 * `
 *
 *     let resChiSim = `
 * 狐 狸 精 化 身 为 美 女 夏 迎 春 去 勾 引 齐 王 , 忽 男 忽 女 的 她 竟 同 时 爱 上 齐 王 和 无 艳 ,
 * 硬 是 周 旋 在 二 人 之 间 。 齐 王 被 狐 狸 精 的 美 色 所 诱 , 但 无 艳 仍 对 齐 王 痴 心 一 片 ,
 * 甘 为 齐 王 南 征 北 战 , 冲 锋 陷 阵 、 出 生 入 死 。 无 艳 在 迎 春 多 畴 打 击 下 多 次 入 冷
 * 宫 、 坐 天 牢 仍 无 怨 无 悔 , 对 齐 王 矢 志 不 渝 。
 * `
 *
 *     return {
 *         eng: getEng(),
 *         tra: getTra(),
 *         sim: getSim(),
 *         resEng,
 *         resChiTra,
 *         resChiSim,
 *     }
 * }
 *
 * function replace(c, t, r) {
 *     let o = new RegExp(t, 'g')
 *     let rr = String(c).replace(o, r)
 *     return rr
 * }
 *
 * function p(c) {
 *     c = replace(c, ' ', '')
 *     c = replace(c, '\r', '')
 *     c = replace(c, '\n', '')
 *     return c
 * }
 *
 * async function core() {
 *
 *     //kpImg
 *     let kpImg = getImg()
 *
 *     let rEng = await ocr(kpImg.eng)
 *     console.log(rEng)
 *     console.log('ocr for eng:', p(rEng) === p(kpImg.resEng))
 *     // => Mild Splendour of the various-vested Night!
 *     // Mother of wildly-working visions! haill
 *     // I watch thy gliding, while with watery light
 *     // Thy weak eye glimmers through a fleecy veil;
 *     // And when thou lovest thy pale orb to shroud
 *     // Behind the gather’'d blackness lost on high;
 *     // And when thou dartest from the wind-rent cloud
 *     // Thy placid lightning o’er the awakend sky.
 *     //
 *     // => ocr for chi_tra: true
 *
 *     let rChiTra = await ocr(kpImg.tra, { lang: 'chi_tra' })
 *     console.log(rChiTra)
 *     console.log('ocr for chi_tra:', p(rChiTra) === p(kpImg.resChiTra))
 *     // => 齎 家 王 說 闊 夜 又 山 ‧ 碰 巧 撞 上 了 風 妻 綽 約 的 寨 主 鍾 無 艷 , 一 人 一 見 鍾 情 。 鍾 情
 *     // 於 鍾 無 艷 的 狐 狸 精 被 拒 愛 , 於 是 向 無 艷 施 『 愛 情 咒 」, 使 其 臉 上 忽 然 多 了 塊 大
 *     // 痧 ‧ 把 齊 宣 王 嚇 得 採 腿 而 逃 ‧ 二 人 媒 情 從 此 障 礙 重 重 。
 *     //
 *     // => ocr for chi_tra: true
 *
 *     let rChiSim = await ocr(kpImg.sim, { lang: 'chi_sim' })
 *     console.log(rChiSim)
 *     console.log('ocr for chi_sim:', p(rChiSim) === p(kpImg.resChiSim))
 *     // => 狐 狸 精 化 身 为 美 女 夏 迎 春 去 勾 引 齐 王 , 忽 男 忽 女 的 她 竟 同 时 爱 上 齐 王 和 无 艳 ,
 *     // 硬 是 周 旋 在 二 人 之 间 。 齐 王 被 狐 狸 精 的 美 色 所 诱 , 但 无 艳 仍 对 齐 王 痴 心 一 片 ,
 *     // 甘 为 齐 王 南 征 北 战 , 冲 锋 陷 阵 、 出 生 入 死 。 无 艳 在 迎 春 多 畴 打 击 下 多 次 入 冷
 *     // 宫 、 坐 天 牢 仍 无 怨 无 悔 , 对 齐 王 矢 志 不 渝 。
 *     //
 *     // => ocr for chi_sim: true
 *
 * }
 * core()
 *
 */
async function ocr(img, opt = {}) {

    //check img, 雖然tesseract.js可使用多種img, 此處限定只能url或base64圖片
    // => On a browser, an image can be:
    // an img, video, or canvas element
    // a File object (from a file <input>)
    // a Blob object
    // a path or URL to an accessible image
    // a base64 encoded image fits data:image\/([a-zA-Z]*);base64,([^"]*) regexp
    // => In Node.js, an image can be
    // a path to a local image
    // a Buffer storing binary image
    // a base64 encoded image fits data:image\/([a-zA-Z]*);base64,([^"]*) regexp
    if (!isestr(img)) {
        return Promise.reject('img is not an effective string')
    }

    //lang
    let lang = get(opt, 'lang')
    if (!isestr(lang)) {
        lang = 'eng'
    }

    //getTesseract
    let tsr = getTesseract()
    // console.log('tsr', tsr)

    //createWorker
    let worker = await tsr.createWorker()
    // console.log('worker', worker)

    //init
    await worker.load()
    await worker.loadLanguage(lang)
    await worker.initialize(lang)

    //whitelist
    let whitelist = get(opt, 'whitelist')
    if (isestr(whitelist)) {
        await worker.setParameters({
            tessedit_char_whitelist: whitelist,
        })
    }

    //recognize
    let r = await worker.recognize(img)
    let { data: { text } } = r
    // console.log(r)

    //terminate
    await worker.terminate()

    return text
}


export default ocr