From e484eb6202ed16414a558099c583c5226e2360b5 Mon Sep 17 00:00:00 2001 From: anod <182859762@qq.com> Date: Sun, 25 Sep 2022 12:45:21 +0000 Subject: [PATCH] =?UTF-8?q?!1=20=E6=8A=8Adev=5Fcheat=E7=9A=84=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=94=B9=E5=8A=A8=EF=BC=88=E5=9F=BA=E6=9C=AC=E4=B8=8A?= =?UTF-8?q?=E6=98=AFacf=EF=BC=89=E5=90=88=E5=B9=B6=E5=9B=9E=E5=8E=BB=20*?= =?UTF-8?q?=20=E4=BF=AE=E6=94=B9=E5=A5=BDbug=EF=BC=8C=E6=94=B9=E8=BF=87?= =?UTF-8?q?=E7=9A=84acf=E6=8E=A5=E5=8F=A3=20*=20Merge=20branch=20'dev=5Fch?= =?UTF-8?q?eat'=20of=20https://gitee.com/anod/open=5Fagb=5Ffirm=20into=20d?= =?UTF-8?q?ev=5Fcheat=20*=20acf=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3=20*=20?= =?UTF-8?q?Merge=20branch=20'dev=5Fcheat'=20of=20https://gitee.com/anod/op?= =?UTF-8?q?en=5Fagb=5Ffirm=20into=20dev=5Fcheat=20*=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=EF=BC=9A=E4=B8=8A=E4=B8=AA=E7=89=88=E6=9C=AC=E4=BC=9A=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E9=9D=9E=E6=B3=95=E5=86=85=E5=AD=98=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E6=AE=B5=E9=94=99=E8=AF=AF=20*=20=E8=A1=A5=E6=A1=A3=EF=BC=9A?= =?UTF-8?q?=E5=AD=97=E4=BD=93=E6=96=87=E4=BB=B6=E7=94=9F=E6=88=90=E5=B7=A5?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 + include/arm11/acf.h | 74 +++++-- libn3ds/include/arm11/drivers/gx.h | 2 +- open_agb_firm.firm | Bin 94720 -> 98816 bytes source/arm11/acf.c | 98 +++++---- source/arm11/filebrowser.c | 34 +++- source/arm11/open_agb_firm.c | 6 +- tools/acf-builder/buildacf.js | 310 +++++++++++++++++++++++++++++ 8 files changed, 463 insertions(+), 65 deletions(-) create mode 100644 tools/acf-builder/buildacf.js diff --git a/README.md b/README.md index 598ede9..f2fb6ea 100644 --- a/README.md +++ b/README.md @@ -172,3 +172,7 @@ You may use this under the terms of the GNU General Public License GPL v3 or the * ...everyone who contributed to **3dbrew.org** Copyright (C) 2021 derrek, profi200, d0k3 + + +Known Issue: +盗版卡存档有问题 diff --git a/include/arm11/acf.h b/include/arm11/acf.h index 4280090..f6e921a 100644 --- a/include/arm11/acf.h +++ b/include/arm11/acf.h @@ -6,24 +6,64 @@ #include #include -#define ACFONT_NOT_FOUND 1 -#define ACFONT_INVALID 2 -#define ACFONT_READ_EOF 3 -#define ACFONT_NOT_SUPPORT 4 -#define ACFONT_MEM_EMPTY 5 +#ifdef __cplusplus +extern "C" { +#endif -extern int acf_set_font(const char *); -extern const char *acf_draw(int, int, unsigned, unsigned, unsigned, uint16_t, const char *); +#define INPUT(type) type +#define OUTPUT(type) type* -#define ACF_LIT_POINT(x, y, w, h, islit) \ - { \ - if (0 <= (x) && (x) < (w) && 0 <= (y) && (y) < (h)) \ - { \ - if ((islit)) \ - { \ - frame[ (h)*(x) + (h-1-y) ] = fg; \ - } \ - } \ - } +// error type +#define ACFONT_NOT_FOUND -1 +#define ACFONT_INVALID -2 +#define ACFONT_READ_EOF -3 +#define ACFONT_NOT_SUPPORT -4 +#define ACFONT_MEM_EMPTY -5 + +typedef int acf_error_t; +typedef void * acf_canvas_t; +typedef void * acf_callerdata_t; +typedef uint16_t acf_position_t; +typedef uint16_t acf_rectedge_t; +typedef uint8_t acf_color_t; +typedef const char * acf_text_t; +typedef uint16_t acf_counter_t; + +typedef void (*acf_visitor_t)( + INPUT(acf_callerdata_t) some_data_provide_by_caller, + INPUT(acf_position_t) position_x_of_canvas, + INPUT(acf_position_t) position_y_of_canvas, + INPUT(acf_color_t) color_in_position_xy_of_canvas +); + +extern acf_error_t acf_initialize( + INPUT(acf_text_t) filename_of_acf_font +); + +extern acf_canvas_t acf_get_canvas( + INPUT(acf_rectedge_t) width_in_pixel_of_canvas, + INPUT(acf_text_t) text_painting_in_utf8, + + OUTPUT(acf_rectedge_t) using_width_in_pixel_of_canvas, + OUTPUT(acf_counter_t) rendered_char_counting, + OUTPUT(acf_text_t) text_not_rendered_in_utf8 +); + +extern void acf_recycle( + INPUT(acf_canvas_t) canvas_should_free; +); + +extern acf_canvas_t acf_use_canvas( + INPUT(acf_canvas_t) canvas_should_use, + INPUT(acf_visitor_t) pixel_visitor_for_canvas, + INPUT(acf_callerdata_t) some_data_from_caller +); + +#undef INPUT +#undef OUTPUT + +#ifdef __cplusplus +} +#endif #endif //_ANOD_COMPILED_FONT_H_ diff --git a/libn3ds/include/arm11/drivers/gx.h b/libn3ds/include/arm11/drivers/gx.h index 895552d..de3bee3 100644 --- a/libn3ds/include/arm11/drivers/gx.h +++ b/libn3ds/include/arm11/drivers/gx.h @@ -23,7 +23,7 @@ -#define GX_REGS_BASE (IO_MEM_ARM11_ONLY + 0x200000) +#define GX_REGS_BASE (IO_MEM_ARM11_ONLY + 0x200000) // 10400000H #define REG_GX_GPU_CLK *((vu32*)(GX_REGS_BASE + 0x0004)) // ? // PSC (memory fill) regs. diff --git a/open_agb_firm.firm b/open_agb_firm.firm index 8c545657ec68eddd40201538a2049fb4f38e47d0..1778fd8318441bf984209a084b0cac44a2dfff61 100644 GIT binary patch delta 16911 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 (int)(*width_max))) + if ( cx + bbx[0] >= (int)width ) return 1; // 绘制 @@ -299,11 +292,12 @@ static int render_unicode(FHandle fd, int *x, unsigned width, unsigned height, u { for (int j = 0; j < bbx[0]; ++j) { - if( 0 <= i && i < (int)height && 0 <= j && j < (int)width ) + int tx = cx+j, ty = cy-i; + if( 0 <= ty && ty < (int)height && 0 <= tx && tx < (int)width ) { if( BIT_AT_POS(glyph, bbx[0] * i + j) ) { - SET_AT_POS( ram, PIX_IN_LINE(cx+j, cy-i, width) ); + SET_AT_POS( ram, PIX_IN_LINE(tx, ty, width) ); } } } @@ -314,62 +308,78 @@ static int render_unicode(FHandle fd, int *x, unsigned width, unsigned height, u return 0; } +#define ACFONT_CANVAS_MEMSIZE( w, h ) ( ( (w)*(h) + BIT_PER_BYTE-1 )/BIT_PER_BYTE+sizeof(acf_rectedge_t) ) +#define ACFONT_CANVAS_WIDTH( p ) (*(acf_rectedge_t*)(p)) +#define ACFONT_CANVAS_PIXEL( p ) ((uint8_t*)(p)+sizeof(acf_rectedge_t)) + +void acf_recycle( acf_canvas_t canvas ) +{ + if( canvas != NULL ) + free(canvas); +} + +acf_canvas_t acf_use_canvas(acf_canvas_t canvas, acf_visitor_t pixel_responser, acf_callerdata_t data) +{ + if( canvas == NULL ) return canvas; + + register acf_rectedge_t width = ACFONT_CANVAS_WIDTH(canvas), height = gblfont.height; + for( acf_position_t i=0; i < height; i++ ) + { + acf_position_t dy = height - 1 - i; + for( register acf_position_t j=0; j < width; j++ ){ + uint8_t m = BIT_AT_POS(ACFONT_CANVAS_PIXEL(canvas), PIX_IN_LINE(j, dy, width)); + pixel_responser(data, j, i, m>0 ? 1 : 0); + } + } + return canvas; +} + // 根据字体绘制中文字符 // x,y - 绘制一行字符的基准点 // maxwidth - 最长绘制多少个像素点,填0则忽略此参数 // utf8_line - utf8字符串 // 返回:第一个未绘制的字符的位置,如果width为0,则返回永远是NULL -const char *acf_draw(int x, int y, unsigned width, unsigned height, unsigned maxwidth, uint16_t color, const char *utf8_line) +acf_canvas_t acf_get_canvas(acf_rectedge_t width, acf_text_t text, acf_rectedge_t *realwidth, acf_counter_t *renderedcnt, acf_text_t *rest) { FHandle font; - int linex = 0; if( gblfont.filename == NULL || RES_OK != fOpen(&font, gblfont.filename, FA_OPEN_EXISTING | FA_READ) ) { // log - return utf8_line; + return NULL; } uint32_t unicode; - if( maxwidth == 0 ) maxwidth = width; - unsigned *option = width == 0 ? NULL : &maxwidth; + int linex = 0; + int rendered_count = 0; + const char *utf8_line = text; - const int ramsize = gblfont.height * maxwidth + BIT_PER_BYTE - 1 / BIT_PER_BYTE; - uint8_t *localram = malloc( ramsize ); - memset( localram, 0, ramsize ); + acf_rectedge_t *canvas = malloc( ACFONT_CANVAS_MEMSIZE(width, gblfont.height) ); + memset(canvas, 0, ACFONT_CANVAS_MEMSIZE(width, gblfont.height)); + *canvas = width; for (const char *next = next_unicode(utf8_line, &unicode); next != NULL; next = next_unicode(utf8_line, &unicode)) { - int error = render_unicode(font, &linex, maxwidth, height, unicode, option, localram); - if (error > 0 ) break; - else if(error < 0 ) error = render_unicode(font, &linex, maxwidth, height, '?', option, localram); + int error = render_unicode(font, &linex, width, gblfont.height, unicode, ACFONT_CANVAS_PIXEL(canvas)); + if( error > 0 ) break; + else if( error < 0 ) error = render_unicode(font, &linex, width, gblfont.height, '?', ACFONT_CANVAS_PIXEL(canvas)); if( error ) { - free( localram ); + free( canvas ); fClose(font); - return utf8_line; + return NULL; } utf8_line = next; + ++rendered_count; } - // copy back to canvas - uint16_t *frame = consoleGet()->frameBuffer; - uint16_t fg = color; - for( int i=0; i < gblfont.height; ++i ) - { - for( int j=0; j < (int)maxwidth; ++j ) - { - if( BIT_AT_POS(localram, PIX_IN_LINE(j, i, maxwidth)) ) - { - ACF_LIT_POINT(x+j, y-(gblfont.height-1-i), (int)width, (int)height, 1); - } - } - } - - free( localram ); + if( realwidth != NULL ) *realwidth = linex; + if( renderedcnt != NULL ) *renderedcnt = rendered_count; + if( rest != NULL ) *rest = utf8_line; + fClose(font); - return NULL; + return canvas; } diff --git a/source/arm11/filebrowser.c b/source/arm11/filebrowser.c index aa10ddd..b51d5e0 100644 --- a/source/arm11/filebrowser.c +++ b/source/arm11/filebrowser.c @@ -58,6 +58,38 @@ typedef struct char *ptrs[MAX_DIR_ENTRIES]; // For fast sorting. } DirList; +void set_screen_color( acf_callerdata_t data, acf_position_t tx, acf_position_t ty, acf_color_t b ) +{ + if( b == 0 ) return; + + int *draw_data = data; + u16 *frame = consoleGet()->frameBuffer; + + int x = tx + draw_data[0]; + int y = ty + draw_data[1]; + if( 0 <= x && x < draw_data[2] && 0 <= y && y < draw_data[3] ){ + frame[ x*draw_data[3] + (draw_data[3]-1-y) ] = (u16)draw_data[4]; + } +} + +const char *acf_draw(int x, int y, int width, int height, int maxwidth, u16 color, const char* text) +{ + uint8_t draw_data[sizeof(int)*5]; + + int *draw_data_int = (int*)&draw_data; + draw_data_int[0] = x; + draw_data_int[1] = y; + draw_data_int[2] = width; + draw_data_int[3] = height; + draw_data_int[4] = color; + + const char *retval = text; + acf_canvas_t canvas = acf_get_canvas(maxwidth, text, NULL, NULL, &retval); + if( !canvas ) return retval; + acf_recycle( acf_use_canvas(canvas, set_screen_color, draw_data) ); + return retval; +} + int dlistCompare(const void *a, const void *b) { const char *entA = *(char**)a; @@ -171,7 +203,7 @@ Result browseFiles(const char *const basePath, char selected[512]) if( dList->num > 0 ){ //ee_printf(*dList->ptrs[oldCursorPos] == ENT_TYPE_FILE ? "\x1b[%lu;H\x1b[37m %.51s" : "\x1b[%lu;H\x1b[36m %.51s", oldCursorPos - windowPos, &dList->ptrs[oldCursorPos][1]); // Clear old cursor. //ee_printf("\x1b[%lu;H\x1b[33m>%.51s", cursorPos - windowPos, &dList->ptrs[cursorPos][1]); // Draw cursor. - if( oldCursorPos != cursorPos && windowPos <= oldCursorPos && oldCursorPos < windowPos + SCREEN_ROWS ) + if( oldCursorPos != cursorPos && windowPos <= (u32)oldCursorPos && (u32)oldCursorPos < windowPos + SCREEN_ROWS ) { const uint8_t fg = *dList -> ptrs[oldCursorPos] == ENT_TYPE_FILE ? 7:6; acf_draw( CLEFTMARGIN, LINENO_TO_Y(oldCursorPos-windowPos), CWIDTH, CHEIGHT, CLINELIMIT, consoleGetRGB565Color(fg), &dList->ptrs[oldCursorPos][1] ); diff --git a/source/arm11/open_agb_firm.c b/source/arm11/open_agb_firm.c index e57c2c5..1b3d226 100644 --- a/source/arm11/open_agb_firm.c +++ b/source/arm11/open_agb_firm.c @@ -526,6 +526,8 @@ static void gbaGfxHandler(void *args) } GX_processCommandList(listSize, list); GFX_waitForP3D(); + // 地址0x18180000保存的是360x240大小的贴图数据 + // 地址0x18200000保存的是512x512大小的GBA的240x160贴图数据 GX_displayTransfer((u32*)(0x18180000 + (16 * 240 * 3)), 368u<<16 | 240u, GFX_getFramebuffer(SCREEN_TOP) + (16 * 240 * 3), 368u<<16 | 240u, 1u<<12 | 1u<<8); GFX_waitForPPF(); @@ -684,7 +686,7 @@ Result oafParseConfigEarly(void) // Create the saves folder. if((res = fMkdir(OAF_SAVE_DIR)) != RES_OK && res != RES_FR_EXIST) break; - if((res = acf_set_font("wqy11.fnt")) != RES_OK ) break; + if((res = acf_initialize("wqy11.fnt")) != RES_OK ) break; // Parse the config. res = parseOafConfig("config.ini", true); @@ -743,7 +745,7 @@ Result oafInitAndRun(void) // Initialize the legacy frame buffer and frame handler. const KHandle frameReadyEvent = createEvent(false); - LGYFB_init(frameReadyEvent, g_oafConfig.scaler); // Setup Legacy Framebuffer. + LGYFB_init(frameReadyEvent, g_oafConfig.scaler); // 这里把GBA的输出转换成0x18200000处512x512大小的纹理 patchGbaGpuCmdList(g_oafConfig.scaler); createTask(0x800, 3, gbaGfxHandler, (void*)frameReadyEvent); g_frameReadyEvent = frameReadyEvent; diff --git a/tools/acf-builder/buildacf.js b/tools/acf-builder/buildacf.js new file mode 100644 index 0000000..27bb602 --- /dev/null +++ b/tools/acf-builder/buildacf.js @@ -0,0 +1,310 @@ +const {open} = require("fs/promises"); +const {argv} = require("process"); + +// ACF文件格式: +// 0-3:ACFv ACF为固定字符串,v表示版本号,从1开始 +// 4-7:font bounding 来自bdf文件 +// 8-9: fragment items size 字体数据开始的位置 +// 10-:fragment items 分段描述数据,一个分段的unicode是连续的 +// +// 分段描述数据内容 +// 0-1:start unicode 本分段第一个unicode +// 2-3:end unicode 本分段最后一个unicode +// 5: padding size for unicode 本分段每个unicode的字体数据占用的字节长度 +// 6: bbxd count in this fragment 本分段总共用到的bbxd种类 +// other: 4*(bbxd count) 本分段用到的所有bbxd的原始数据 +// +// bbxd数据格式: +// 4个6字节的整数(-32 ~ 31),表示BBX对应的内容,加上一个6字节的整数表示dwidth的第一个数据 +// +// 字体数据 +// 同一个fragment里面的所有unicode长度都是padding size,如果实际数据少的会补齐0 +// 如果fragment的bbxd count为1,则字体数据就是直接boundingbox的所有点阵数据 +// 如果fragment的bbxd count大于1,则字体数据是1字节的bbxd索引,后接boundingbox的所有点阵数据 + +const helper = () => { + const setBitAt = (bitarr, pos) => { + let byteIndex = Math.floor( pos / 8 ); + let bitOffset = pos % 8; + bitarr[ byteIndex ] |= 1 << bitOffset; + } + const testBit = (byte, pos) => byte & ( 1 << pos); + const putS6 = (arr, val, pos) => { + // sign bit + if( val < 0 ) { + setBitAt( arr, pos ); + val = -val; + } + ++pos; + + // digits + for( let i=1<<4; i > 0; i>>=1, pos++ ){ + if( val & i ) { + setBitAt(arr, pos); + } + } + } + return {setBitAt, testBit, putS6} +} + +const createDescript = () => { + let numChars, boundingbox; + let glyphs = [] + let charGlyph = {} + let fragments = [] + + const apply = async ( cmd, params ) => { + if( cmd == "chars" ) { + [numChars] = params.map( n => Number(n) ); + } + else if( cmd == "fontboundingbox" ) { + boundingbox = params.map( n => Number(n) ); + } + else if( cmd == "startchar" ){ + charGlyph = {}; + + if( fragments.length == 0 ){ + fragments.push({ padding: 0, bbxd: new Map() }); + } + } + else if( cmd == "encoding" ){ + charGlyph.code = Number(params[0]); + } + else if( cmd == "dwidth" ){ + charGlyph.dwidth = params.map( n => Number(n) ); + } + else if( cmd == "bbx" ){ + charGlyph.bbx = params.map( n => Number(n) ); + } + else if( cmd == "bitmap" ){ + charGlyph.recoding = true; + charGlyph.data = []; + } + else if( cmd == "endchar" ){ + const {recoding, code, ...value} = charGlyph; + const [w, h] = charGlyph.bbx; + + let fragment = fragments.at(-1); + if( !("start" in fragment) ){ + fragment.start = code; + } + else if( glyphs.at(-1).code + 1 != code ){// break + fragment.end = glyphs.at(-1).code; + + // new fragment + fragment = { start: code, padding: 0, bbxd: new Map() }; + fragments.push( fragment ); + } + + if( w*h > fragment.padding ){ + fragment.padding = w*h; + } + + const key = JSON.stringify( [...value.bbx, value.dwidth[0]] ) + if( !fragment.bbxd.has(key) ){ + fragment.bbxd.set( key, fragment.bbxd.size ); + } + + charGlyph = {}; + glyphs.push( {...value, code, fragment} ); + } + else if( cmd == "endfont" ){ + const fragment = fragments.at(-1); + fragment.end = glyphs.at(-1).code; + } + else if( charGlyph.recoding ){ + charGlyph.data.push( parseInt(cmd, 16) ); + } + } + const save = async file => { + const { setBitAt, testBit, putS6 } = helper(); + const packHead = () => { + const buffer = new ArrayBuffer(6); + const view = new DataView(buffer); + view.setUint16(0, numChars, true); + boundingbox.forEach( (c, i) => view.setInt8(2+i, c) ) + return new Uint8Array(buffer); + } + const packHeadV2 = fragSize => { + const buffer = new ArrayBuffer(10); + const view = new DataView(buffer); + view.setUint8(0, "A".charCodeAt()) + view.setUint8(1, "C".charCodeAt()) + view.setUint8(2, "F".charCodeAt()) + view.setUint8(3, 2)//v2 + boundingbox.forEach( (c, i) => view.setInt8(4+i, c) ) + view.setUint16(8, fragSize, true) + return new Uint8Array(buffer); + } + const packGlyphs = () => { + return glyphs.map( glyph => [packChar(glyph), packPixel(glyph), glyph] ) + } + const packChar = glyph => { + const buffer = new Uint8Array(4); + const bitwid = 6; + glyph.bbx.forEach( (c,i) => putS6(buffer, c, bitwid*i) ); + putS6( buffer, glyph.dwidth[0], bitwid*4 ); + return buffer; + } + const packPixel = glyph => { + const [w, h] = glyph.bbx; + const {padding} = glyph.fragment; + const nbyte = Math.ceil( padding / 8 ); + const buffer = new Uint8Array( nbyte ); + const base = 8 * ( (7+w) >> 3 ) - 1; + for( let j = 0; j < h; ++j ){ + let linepixel = glyph.data[j]; + for( let i = 0; i < w; ++i ){ + if( testBit(linepixel, base-i) ){ + setBitAt( buffer, j*w+i ) + } + } + } + return buffer; + } + const packIndex = fonts => { + const buffer = new ArrayBuffer( numChars * 5 ); + const view = new DataView( buffer ); + let offset = 0; + glyphs.forEach( (c, i) => { + view.setUint16( i*5, c.code, true ); + view.setUint8( i*5 + 2, offset >> 16 ); + view.setUint8( i*5 + 3, offset & 0xFF ); + view.setUint8( i*5 + 4, (offset & 0xFFFF) >> 8 ); + offset += 4 + fonts[i][1].length; + }) + return new Uint8Array(buffer); + } + const packFragments = () => { + const bitwid = 6; + const serials = fragments.map( frag => { + const buffer = new ArrayBuffer( 6+4*frag.bbxd.size ); + const view = new DataView( buffer ); + const nbyte = Math.ceil( frag.padding / 8 ); + view.setUint16( 0, frag.start, true ); + view.setUint16( 2, frag.end, true ); + view.setUint8( 4, frag.bbxd.size > 1 ? 1+nbyte : nbyte ); + view.setUint8( 5, frag.bbxd.size ); + + frag.bbxd.forEach( (idx, key) => { + const arr = new Uint8Array(buffer, 6+idx*4, 4); + const embeded = JSON.parse( key ); + embeded.forEach( (c,i)=>putS6(arr, c, bitwid*i) ) + } ) + return new Uint8Array(buffer); + }) + return serials; + } + + /* + * old code: pack for acfv1 + const head = packHead(); + const glyph = packGlyphs(); + const index = packIndex( glyph ); + + let out = await open(file, "w"); + await out.write(head); + await out.write(index); + let jobs = Promise.resolve(); + glyph.forEach( ([char, pixel]) => { + const combine = Uint8Array.from([...char, ...pixel]) + jobs = jobs.then( () => out.write(combine) ) + } ) + await jobs; + await out.close(); + */ + + const index = packFragments(); + const head = packHeadV2( index.reduce( (r,b)=>r+b.length, 0 ) ); + const glyph = packGlyphs(); + + let out = await open(file, "w"); + await out.write( head ); + let jobs = Promise.resolve(); + index.forEach( buffer => { + jobs = jobs.then( () => out.write(buffer) ) + } ); + await jobs; + + jobs = Promise.resolve(); + glyph.forEach( ([char, pixel, glyph]) => { + const {fragment} = glyph; + if( fragment.bbxd.size == 1 ) jobs = jobs.then( () => out.write(pixel) ) + else { + const key = JSON.stringify( [...glyph.bbx, glyph.dwidth[0]] ) + const combine = Uint8Array.from( [fragment.bbxd.get(key), ...pixel] ) + jobs = jobs.then( () => out.write(combine) ) + } + } ) + await jobs; + await out.close(); + } + return { apply, save } +} + +const createReader = file => { + const newline = "\n".charCodeAt(); + + let buffer = new Uint8Array(400); + let cursor = buffer.length; + let line = "" + const readline = async () => { + if( cursor == buffer.length ){ + cursor = 0; + await file.read( buffer, cursor, buffer.length ) + } + + let chars = [] + let fullline = null; + while( cursor < buffer.length ){ + const char = buffer[cursor++]; + if( char == newline ) { + fullline = `${line}${String.fromCharCode(...chars)}`; + break; + } + + chars.push( char ) + } + + if( fullline ){ + line = ""; // reset line + return fullline.length > 0 ? fullline.split(" ").map( (word,i) => i==0 ? word.toLowerCase():word ) : [null]; + } + else { + line = `${line}${String.fromCharCode(...chars)}`; + return await readline() + } + } + return readline; +} + +const convert = async fname => { + let bdf = await open( fname, "r" ); + + const readline = createReader( bdf ); + const bdfdesc = createDescript(); + + while( true ){ + let [cmd, ...params] = await readline(); + + await bdfdesc.apply( cmd, params ) + + if( cmd == "endfont" ) break; + } + + await bdf.close(); + await bdfdesc.save( fname.replace(".bdf", ".acf") ); +} + +const start = async params => { + if( params.length < 1 ){ + console.log("Usage: buildact file [file2] [...]") + return; + } + + const formatter = new Intl.DateTimeFormat("zh-CN", {dateStyle: "short", timeStyle: "medium", timeZone: "Asia/Shanghai"}) + globalThis.debug = log => console.log( `[DEBUG][${formatter.format(new Date())}]${log}` ) + await Promise.all( params.map( file => convert(file) ) ) +} + +start( argv.slice(2) ); \ No newline at end of file