UNPKG

@waiting/idcard-reader-base

Version:
431 lines (424 loc) 13.6 kB
/** * @waiting/idcard-reader-base * 二代机具读卡 * * @version 2.0.1 * @author waiting * @license MIT * @link https://github.com/waitingsong/node-idcard-reader-base#readme */ import { join, tmpdir, normalize, unlinkAsync, isFileExists, isDirExists, createDirAsync, createFileAsync } from '@waiting/shared-core'; import { mapTo, tap, concatMap } from 'rxjs/operators'; import { run } from 'rxrunscript'; const config = { appDir: '', tmpDir: join(tmpdir(), 'idcard-reader'), }; /** 二代证信息 */ const initialDataBase = { name: '', gender: 0, genderName: '', nation: '', nationName: '', birth: '', address: '', idc: '', regorg: '', startdate: '', enddate: '', }; const initialCompositeOpts = { useComposite: false, compositeDir: config.tmpDir, compositeQuality: 35, compositeType: 'jpg', textColor: '#303030', fontHwxhei: 'c:/Windows/Fonts/hwxhei.ttf', fontOcrb: 'c:/Windows/Fonts/ocrb10bt.ttf', fontSimhei: 'c:/Windows/Fonts/simhei.ttf', }; /** 默认初始化参数 */ const initialDeviceOpts = { debug: false, dllTxt: '', dllImage: '', findCardRetryTimes: 1, imgSaveDir: config.tmpDir, port: 0, searchAll: false, }; /** 默认初始化参数 */ const initialOpts = { ...initialDeviceOpts, ...initialCompositeOpts, }; const initialIDData = { compositePath: '', base: null, imagePath: '', }; /** 民族列表 */ const nationMap = new Map([ ['01', '汉'], ['02', '蒙古'], ['03', '回'], ['04', '藏'], ['05', '维吾尔'], ['06', '苗'], ['07', '彝'], ['08', '壮'], ['09', '布依'], ['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', '其它'], ['98', '外国人入籍'], ]); /** * Convert avatar bmp to png with transparent background * Return avatar path */ function handleAvatar(path) { // magick avatar.bmp -resize x353 -fuzz 9% -transparent '#FEFEFE' avatar.png const target = normalize(join(config.tmpDir, 'avatar-' + Math.random().toString() + '.png')).replace(/\\/ug, '/'); const src = normalize(path).replace(/\\/ug, '/'); const cmd = `magick "${src}" -resize x353 -fuzz 9% -transparent "#FEFEFE" "${target}"`; const ret = run(cmd, null, { maxCmdLength: 4096 }).pipe(mapTo(target)); return ret; } /** * Fill base info text */ function handleBaseInfo(data, avatarPath, options) { /* istanbul ignore else */ if (!data) { throw new TypeError('base value invalid'); } /* istanbul ignore else */ if (!options.fontHwxhei || !options.fontOcrb || !options.fontSimhei) { throw new TypeError('font value invalid'); } const assetsDir = join(config.appDir, 'assets'); const tpl = join(assetsDir, 'tpl.png'); const target = join(options.compositeDir, 'composite-' + Math.random().toString() + `.${options.compositeType}`) .replace(/\\/ug, '/'); const txtColor = options.textColor; const hwxhei = normalize(options.fontHwxhei).replace(/\\/ug, '/'); const orcb = normalize(options.fontOcrb).replace(/\\/ug, '/'); const simhei = normalize(options.fontSimhei).replace(/\\/ug, '/'); const ps = [ genParamName(data.name, txtColor, hwxhei), genParamGender(data.genderName, txtColor, hwxhei), genParamNation(data.nationName, txtColor, hwxhei), genParamBirth(data.birth, txtColor, hwxhei), genParamIdc(data.idc, txtColor, orcb), genParamAddress(data.address, txtColor, hwxhei), genParamRegOrg(data.regorg, txtColor, simhei), genParamValidDate(data.startdate, data.enddate, txtColor, hwxhei), ]; const cmd = `magick ${tpl} ` + ps.join(' ') + ` -compose src-over "${avatarPath}" -geometry +619+125 -quality ${options.compositeQuality} -composite "${target}"`; const ret = run(cmd, null, { maxCmdLength: 4096 }).pipe(mapTo(target), tap(() => unlinkAsync(avatarPath))); return ret; } function genParamName(value, txtColor, font) { const val = value ? value.trim() : ''; /* istanbul ignore else */ if (!val) { throw new TypeError('value invalid'); } return `-fill "${txtColor}" -font "${font}" -pointsize 42 -draw "text 208,146 '${val}'"`; } function genParamGender(value, txtColor, font) { const val = value ? value.trim() : ''; /* istanbul ignore else */ if (!val) { throw new TypeError('value invalid'); } return `-fill "${txtColor}" -font "${font}" -pointsize 34 -draw "text 208,220 '${val}'"`; } function genParamNation(value, txtColor, font) { const val = value ? value.trim() : ''; /* istanbul ignore else */ if (!val) { throw new TypeError('value invalid'); } return `-fill "${txtColor}" -font "${font}" -pointsize 34 -draw "text 395,220 '${val}'"`; } function genParamBirth(value, txtColor, font) { const val = value ? value.trim() : ''; /* istanbul ignore else */ if (!val) { throw new TypeError('value invalid'); } const year = val.slice(0, 4); let month = val.slice(4, 6); let day = val.slice(6, 8); /* istanbul ignore else */ if (month.startsWith('0')) { month = ' ' + month[1]; } /* istanbul ignore else */ if (day.startsWith('0')) { day = ' ' + day[1]; } let ret = `-fill "${txtColor}" -font "${font}" -pointsize 33 -kerning 1 -draw "text 210,295 '${year}'"`; ret += ` -fill "${txtColor}" -font "${font}" -pointsize 33 -draw "text 350,295 '${month}'"`; ret += ` -fill "${txtColor}" -font "${font}" -pointsize 33 -draw "text 435,295 '${day}'"`; return ret; } function genParamIdc(value, txtColor, font) { const val = value ? value.trim() : ''; /* istanbul ignore else */ if (!val) { throw new TypeError('value invalid'); } return `-fill "${txtColor}" -font "${font}" -pointsize 45 -kerning 1 -draw "text 355,561 '${val}'"`; } function genParamAddress(value, txtColor, font) { const val = value ? value.trim() : ''; /* istanbul ignore else */ if (!val) { throw new TypeError('value invalid'); } const len = val.length; let ret = ''; let pos = 0; let line = ''; let lineY = 368; const lineHeight = 50; do { line = retrieveAddressLine(val, pos); if (line.length) { ret += ` -fill "${txtColor}" -font "${font}" -pointsize 33 -kerning 3 -draw "text 208,${lineY} '${line}'"`; pos += line.length; lineY += lineHeight; } else { break; } } while (pos <= len); return ret; } /* 11 Chinese every line */ function retrieveAddressLine(value, startPos) { /* istanbul ignore else */ if (value.length <= startPos) { return ''; } const txt = value.slice(startPos); /* istanbul ignore else */ if (txt.length > 10) { if (/[\d\w-]/u.test(txt.slice(11, 12))) { // p11 is number or letter const p12 = txt.slice(12, 13); if (p12 && /[\d\w-]/u.test(p12)) { return txt.slice(0, 10); } } } return txt.slice(0, 11); } function genParamRegOrg(value, txtColor, font) { const val = value ? value.trim() : ''; /* istanbul ignore else */ if (!val) { throw new TypeError('value invalid'); } return `-fill "${txtColor}" -font "${font}" -pointsize 32 -kerning 3 -draw "text 413,1138 '${val}'"`; } function genParamValidDate(start, end, txtColor, font) { const p1 = start ? start.slice(0, 4) + '.' + start.slice(4, 6) + '.' + start.slice(6, 8) : ''; /* istanbul ignore else */ if (!p1) { throw new TypeError('value invalid'); } const p2 = Number.isNaN(+end) ? end : end.slice(0, 4) + '.' + end.slice(4, 6) + '.' + end.slice(6, 8); return `-fill "${txtColor}" -font "${font}" -pointsize 32 -kerning 1.6 -draw "text 413,1215 '${p1}-${p2}'"`; } function formatBaseData(data) { const ret = { ...data }; if (ret.gender) { switch (ret.gender) { case 1: ret.genderName = '男'; break; case 2: ret.genderName = '女'; break; default: ret.genderName = '未知'; break; } } else if (ret.genderName && !ret.gender) { switch (ret.genderName) { case '男': ret.gender = 1; break; case '女': ret.gender = 2; break; default: ret.gender = 0; break; } } if (ret.nation) { const str = nationMap.get(ret.nation); ret.nationName = str ? str.trim() : '未知'; ret.startdate = ret.startdate.trim(); ret.enddate = ret.enddate.trim(); } else if (ret.nationName && !ret.nation) { for (const [key, val] of nationMap) { if (val === ret.nationName) { ret.nation = key; break; } } } return ret; } /* Composite image form base data. Return imagePath */ function composite(avatarPath, base, options) { if (!base) { throw new TypeError('base data value empty'); } const ret$ = handleAvatar(avatarPath).pipe(concatMap((avatarPNG) => { return handleBaseInfo(base, avatarPNG, options); })); return ret$; } async function validateDllFile(path) { if (!await isFileExists(path)) { throw new Error('File not exists: ' + path); } } async function testWrite(dir) { if (!dir) { throw new Error('value of imgSaveDir empty'); } if (!await isDirExists(dir)) { await createDirAsync(dir); await createFileAsync(join(dir, '.test'), 'idctest'); // 创建测试文件 } } function parseDeviceOpts(options) { const deviceOpts = { ...initialDeviceOpts }; if (options.dllTxt) { deviceOpts.dllTxt = normalize(options.dllTxt); } else { throw new Error('params dllTxt undefined or blank'); } if (typeof options.dllImage === 'string' && options.dllImage) { deviceOpts.dllImage = normalize(options.dllImage); } if (typeof options.imgSaveDir === 'string' && options.imgSaveDir) { deviceOpts.imgSaveDir = normalize(options.imgSaveDir); } if (typeof options.debug === 'boolean') { deviceOpts.debug = options.debug; } if (typeof options.searchAll === 'boolean') { deviceOpts.searchAll = options.searchAll; } if (typeof options.findCardRetryTimes === 'number') { deviceOpts.findCardRetryTimes = options.findCardRetryTimes; } if (Number.isNaN(deviceOpts.findCardRetryTimes) || deviceOpts.findCardRetryTimes < 0) { deviceOpts.findCardRetryTimes = initialOpts.findCardRetryTimes; } if (typeof options.port === 'number' && options.port > 0) { deviceOpts.port = options.port; } return deviceOpts; } function parseCompositeOpts(options) { const compositeOpts = { ...initialCompositeOpts }; if (options.dllImage && options.useComposite) { compositeOpts.useComposite = true; } if (options.compositeDir && typeof options.compositeDir === 'string') { compositeOpts.compositeDir = normalize(options.compositeDir); } if (typeof options.compositeQuality === 'number' && options.compositeQuality >= 1 && options.compositeQuality <= 100) { compositeOpts.compositeQuality = options.compositeQuality; } if (options.textColor) { compositeOpts.textColor = options.textColor; } if (options.compositeType) { if (!['bmp', 'gif', 'jpg', 'png', 'webp'].includes(options.compositeType)) { throw new TypeError('compositeType value invalid'); } compositeOpts.compositeType = options.compositeType; } else { compositeOpts.compositeType = initialOpts.compositeType; } if (options.fontHwxhei) { compositeOpts.fontHwxhei = options.fontHwxhei; } if (options.fontOcrb) { compositeOpts.fontOcrb = options.fontOcrb; } if (options.fontSimhei) { compositeOpts.fontSimhei = options.fontSimhei; } return compositeOpts; } // base directory of this module config.appDir = join(__dirname, '/..'); export { composite, config, formatBaseData, handleAvatar, handleBaseInfo, initialCompositeOpts, initialDataBase, initialDeviceOpts, initialIDData, initialOpts, nationMap, parseCompositeOpts, parseDeviceOpts, testWrite, validateDllFile }; //# sourceMappingURL=index.esm.js.map