sdk/: - 同步 meow_ui_widget/input/gfx/lazygrid/audio/media/img 等源码与头 (SDK 默认列表/容器控件默认 MD3 — meow_list_set_container 可回退; 全局 App 内底部上滑回桌面 — meow_ui_input_is_home_swipe 系统保留手势; LazyGrid/LazyColumn/LazyRow MD3 化;叠加让位自抑改 app_id 判定; DSD 音频格式等) - 新增独立模块源码 meow_img_color / meow_lyric / meow_lyric_view 及对应宿主 test_*.c - 保留公开仓自包含适配:vendored meow_font.c/meow_font.h/meow_notif.h 不动; build_sdk.sh 自包含版本不动;meow_ui_gfx.c 仅改 meow_font.h include 路径; meow_ui_internal.h 去除 FreeType 内部声明 - meow_ipc.h 同步到主仓 launcher 版本(含 AUDIO_FMT_DOP/媒体歌词/蓝牙输出协议) - 不含 FreeType 矢量字 meow_ft_*(需 freetype vendor,公开仓以位图 meow_font 自包含);不含 mipsel 二进制 / .o / .a / 固件 / 密钥 / devdocs examples/hello/: - 新增 ui/demo/sdk2/sdk2.c;build_hello.sh 纳入该文件(保持自包含) docs / README / .gitignore: - docs/MEOW_APP_SDK.md 补 §12.5(默认 MD3 容器 + 底部上滑回桌面 + 两个新 API) - README 能力清单补「默认 MD3 容器」与「底部上滑回桌面」 - .gitignore 补新增宿主测试二进制 验证:sdk/meow_ui.a 与 examples/hello/hello 交叉编译均 0 warning;工作区无生成物。
206 lines
9.2 KiB
C
206 lines
9.2 KiB
C
/*
|
||
* test_meow_lyric.c — meow_lyric_* 宿主单测(2026-09-03,SDK 通用歌词组件批1)。
|
||
*
|
||
* 编译(宿主,meow_lyric 为纯 libc + pthread,无需 SDK 其它对象):
|
||
* gcc -Wall -Wextra -pthread -o test_meow_lyric test_meow_lyric.c meow_lyric.c -I.
|
||
* ./test_meow_lyric
|
||
*
|
||
* 覆盖矩阵:
|
||
* T1 解析基础 + 乱序自动按时间升序排序(原文行)
|
||
* T2 meow_lyric_line_at 边界:首戳前 / 首戳 / 区间 / 恰逢戳 / 末戳后 / 负值
|
||
* T3 翻译合并:按相同 timestamp 匹配合并;无匹配翻译行忽略
|
||
* T4 服务器字面 \n / \r 当换行(含 \r\n 折叠成单空格)
|
||
* T5 行上限截断:meow_lyric_set_max_lines 下调后 parse 超限行丢弃
|
||
* T6 时间戳小数位自适应:1 位(0.1s) / 2 位(厘秒) / 3 位(网易云毫秒) / >3 位截断到 ms
|
||
* T7 越界访问:line_text/line_tl 越界返回 "";line_time 越界返回 -1
|
||
* T8 meow_lyric_clear:清空后 count=0 / text="" / line_time=-1;空输入 parse 返回 0
|
||
* T9 折行 meow_lyric_wrap:中西文混排断行(英文按词、中文按字符)+ max_rows 截断
|
||
* + 单字符超宽防死循环 + width 回调为 NULL 时按字节近似兜底
|
||
*/
|
||
#include "meow_lyric.h"
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
static int g_fail = 0;
|
||
#define CHECK(cond, msg) do { \
|
||
if (cond) { printf(" PASS: %s\n", msg); } \
|
||
else { printf(" FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); g_fail = 1; } \
|
||
} while (0)
|
||
|
||
/* 折行宽测量:ASCII=1 单位,多字节(CJK 等)=2 单位(用于中西文断行测试)。 */
|
||
static int uw(const char *u, int size, void *ctx)
|
||
{
|
||
(void)size; (void)ctx;
|
||
int w = 0;
|
||
for (const char *p = u; *p; ) {
|
||
unsigned char c = (unsigned char)*p;
|
||
int ch = (c < 0x80) ? 1 : (c < 0xE0) ? 2 : (c < 0xF0) ? 3 : 4;
|
||
w += (c < 0x80) ? 1 : 2;
|
||
p += ch;
|
||
}
|
||
return w;
|
||
}
|
||
|
||
static void t1_basic(void)
|
||
{
|
||
printf("T1 解析基础 + 乱序按时间升序排序\n");
|
||
/* 输入乱序;期望按时间戳升序存(Line B@1000 < Line C@2000 < Line A@3000) */
|
||
const char *lrc = "[00:03.00]Line A\n[00:01.00]Line B\n[00:02.00]Line C\n";
|
||
int n = meow_lyric_parse(lrc, NULL);
|
||
CHECK(n == 3, "count==3");
|
||
CHECK(meow_lyric_count() == 3, "count()==3");
|
||
CHECK(strcmp(meow_lyric_line_text(0), "Line B") == 0, "idx0==Line B (1000ms 升序最前)");
|
||
CHECK(strcmp(meow_lyric_line_text(1), "Line C") == 0, "idx1==Line C");
|
||
CHECK(strcmp(meow_lyric_line_text(2), "Line A") == 0, "idx2==Line A (3000ms 最后)");
|
||
CHECK(meow_lyric_line_time(0) == 1000, "line_time(0)==1000");
|
||
CHECK(meow_lyric_line_time(2) == 3000, "line_time(2)==3000");
|
||
}
|
||
|
||
static void t2_line_at(void)
|
||
{
|
||
printf("T2 meow_lyric_line_at 边界\n");
|
||
const char *lrc = "[00:01.00]a\n[00:02.00]b\n[00:03.00]c\n";
|
||
meow_lyric_parse(lrc, NULL);
|
||
CHECK(meow_lyric_line_at(500) == 0, "首戳前(500ms)→0");
|
||
CHECK(meow_lyric_line_at(1000) == 0, "恰逢首戳→0");
|
||
CHECK(meow_lyric_line_at(1500) == 0, "首区间(1500ms)→0");
|
||
CHECK(meow_lyric_line_at(2500) == 1, "第二区间(2500ms)→1");
|
||
CHECK(meow_lyric_line_at(3000) == 2, "恰逢末戳→2");
|
||
CHECK(meow_lyric_line_at(99999) == 2, "末戳后→n-1");
|
||
CHECK(meow_lyric_line_at(-100) == 0, "负值→0");
|
||
}
|
||
|
||
static void t3_translation(void)
|
||
{
|
||
printf("T3 翻译合并(同 timestamp;无匹配翻译行忽略)\n");
|
||
const char *lrc = "[00:02.00]Hello\n[00:05.00]World\n";
|
||
const char *tlrc = "[00:05.00]你好\n[00:02.00]你好世界\n[01:00.00]no match line\n";
|
||
int n = meow_lyric_parse(lrc, tlrc);
|
||
CHECK(n == 2, "count==2(原文行数)");
|
||
CHECK(strcmp(meow_lyric_line_text(0), "Hello") == 0, "idx0原文 Hello");
|
||
CHECK(strcmp(meow_lyric_line_tl(0), "你好世界") == 0, "idx0翻译 你好世界(按 2000ms 合并)");
|
||
CHECK(strcmp(meow_lyric_line_text(1), "World") == 0, "idx1原文 World");
|
||
CHECK(strcmp(meow_lyric_line_tl(1), "你好") == 0, "idx1翻译 你好(按 5000ms 合并)");
|
||
/* 01:00.00 无对应原文行 → 丢弃,不新增行也不污染其它翻译 */
|
||
CHECK(meow_lyric_count() == 2, "无匹配翻译行被丢弃(仍 2 行)");
|
||
}
|
||
|
||
static void t4_literal_newline(void)
|
||
{
|
||
printf("T4 服务器字面 \\n / \\r 当换行\n");
|
||
/* C 源码里 \\n 是字面反斜杠+n(服务器塞进一行的多行文本) */
|
||
const char *lrc = "[00:01.00]line1\\nline2\r\n[00:02.00]a\\r\\nb\n";
|
||
meow_lyric_parse(lrc, NULL);
|
||
CHECK(strcmp(meow_lyric_line_text(0), "line1 line2") == 0, "字面 \\n → 空格");
|
||
CHECK(strcmp(meow_lyric_line_text(1), "a b") == 0, "字面 \\r\\n 成对 → 单空格");
|
||
}
|
||
|
||
static void t5_limit(void)
|
||
{
|
||
printf("T5 行上限截断(set_max_lines 下调后超限丢弃)\n");
|
||
meow_lyric_set_max_lines(2);
|
||
const char *lrc = "[00:01]a\n[00:02]b\n[00:03]c\n[00:04]d\n";
|
||
int n = meow_lyric_parse(lrc, NULL);
|
||
CHECK(n == 2, "count==2(上限 2)");
|
||
CHECK(strcmp(meow_lyric_line_text(0), "a") == 0, "idx0==a");
|
||
CHECK(strcmp(meow_lyric_line_text(1), "b") == 0, "idx1==b(第三个 c 被截断)");
|
||
meow_lyric_set_max_lines(MEOW_LYRIC_LINES_MAX); /* 恢复默认 */
|
||
n = meow_lyric_parse(lrc, NULL);
|
||
CHECK(n == 4, "恢复上限后 count==4");
|
||
/* set_max_lines 越界 clamp */
|
||
meow_lyric_set_max_lines(0);
|
||
n = meow_lyric_parse(lrc, NULL);
|
||
CHECK(n == 1, "set_max_lines(0)→clamp=1,count==1");
|
||
meow_lyric_set_max_lines(MEOW_LYRIC_LINES_MAX);
|
||
}
|
||
|
||
static void t6_timefrac(void)
|
||
{
|
||
printf("T6 时间戳小数位自适应\n");
|
||
const char *lrc =
|
||
"[00:01.5]a\n" /* 1 位 → 1500ms */
|
||
"[00:02.25]b\n" /* 2 位 → 2250ms */
|
||
"[00:03.456]c\n" /* 3 位(网易云毫秒) → 3456ms */
|
||
"[00:04.1234]d\n"; /* >3 位截断到 ms → 4123ms */
|
||
meow_lyric_parse(lrc, NULL);
|
||
CHECK(meow_lyric_line_time(0) == 1500, "1 位小数 → 1500ms");
|
||
CHECK(meow_lyric_line_time(1) == 2250, "2 位小数 → 2250ms");
|
||
CHECK(meow_lyric_line_time(2) == 3456, "3 位小数 → 3456ms");
|
||
CHECK(meow_lyric_line_time(3) == 4123, ">3 位小数截断 → 4123ms");
|
||
/* 元数据行 [ti:..] 应跳过,不计入行(与歌词须在独立行;ncm 语义:首个非时间标签即令该行失效) */
|
||
meow_lyric_parse("[ti:Test]\n[ar:Someone]\n[00:01.00]x\n", NULL);
|
||
CHECK(meow_lyric_count() == 1, "元数据 [ti:..]/[ar:..] 行不被当作歌词");
|
||
}
|
||
|
||
static void t7_oob(void)
|
||
{
|
||
printf("T7 越界访问\n");
|
||
meow_lyric_parse("[00:01.00]x\n[00:02.00]y\n", NULL);
|
||
CHECK(strcmp(meow_lyric_line_text(-1), "") == 0, "line_text(-1)==\"\"");
|
||
CHECK(strcmp(meow_lyric_line_text(99), "") == 0, "line_text(99)==\"\"");
|
||
CHECK(strcmp(meow_lyric_line_tl(-1), "") == 0, "line_tl(-1)==\"\"");
|
||
CHECK(strcmp(meow_lyric_line_tl(99), "") == 0, "line_tl(99)==\"\"");
|
||
CHECK(meow_lyric_line_time(-1) == -1, "line_time(-1)==-1");
|
||
CHECK(meow_lyric_line_time(99) == -1, "line_time(99)==-1");
|
||
}
|
||
|
||
static void t8_clear_and_empty(void)
|
||
{
|
||
printf("T8 clear / 空输入\n");
|
||
meow_lyric_parse("[00:01.00]x\n", NULL);
|
||
meow_lyric_clear();
|
||
CHECK(meow_lyric_count() == 0, "clear 后 count==0");
|
||
CHECK(strcmp(meow_lyric_line_text(0), "") == 0, "clear 后 line_text(0)==\"\"");
|
||
CHECK(meow_lyric_line_time(0) == -1, "clear 后 line_time(0)==-1");
|
||
CHECK(meow_lyric_parse(NULL, NULL) == 0, "parse(NULL,NULL)→0");
|
||
CHECK(meow_lyric_count() == 0, "空解析后 count==0");
|
||
}
|
||
|
||
static void t9_wrap(void)
|
||
{
|
||
printf("T9 折行 meow_lyric_wrap\n");
|
||
char seg[8][MEOW_LYRIC_SEG_SZ];
|
||
|
||
/* 中西文:英文按词断行、中文按字符断行 → "Hello" / "世界" 两段 */
|
||
int r = meow_lyric_wrap("Hello 世界", 28, 6, seg, 8, uw, NULL);
|
||
CHECK(r == 2, "中西文折为 2 段");
|
||
CHECK(strcmp(seg[0], "Hello") == 0, "seg0==Hello(词边界断)");
|
||
CHECK(strcmp(seg[1], "世界") == 0, "seg1==世界(中文按字符)");
|
||
|
||
/* max_rows 截断:每单词一 bar,max_rows=3 → 只出 3 段 */
|
||
r = meow_lyric_wrap("a b c d e f g", 28, 1, seg, 3, uw, NULL);
|
||
CHECK(r == 3, "max_rows=3 截断为 3 段");
|
||
|
||
/* 单字符超宽(maxw=0):能放一个字符且不死循环,返回完整逐字符段 */
|
||
r = meow_lyric_wrap("abc", 28, 0, seg, 5, uw, NULL);
|
||
CHECK(r == 3, "maxw=0 逐字符折为 3 段(无死循环)");
|
||
CHECK(strcmp(seg[0], "a") == 0 && strcmp(seg[2], "c") == 0, "seg0==a / seg2==c");
|
||
|
||
/* width 回调 NULL → 字节近似兜底(maxw 作字节预算) */
|
||
r = meow_lyric_wrap("abcd", 28, 2, seg, 4, NULL, NULL);
|
||
CHECK(r == 2, "width=NULL 按字节近似折为 2 段");
|
||
CHECK(strcmp(seg[0], "ab") == 0 && strcmp(seg[1], "cd") == 0, "seg0==ab / seg1==cd");
|
||
|
||
/* 空/超短:txt 空 → 1 段空串 */
|
||
r = meow_lyric_wrap("", 28, 10, seg, 4, uw, NULL);
|
||
CHECK(r == 1 && seg[0][0] == 0, "空文本 → 1 段空串");
|
||
CHECK(meow_lyric_wrap(NULL, 28, 10, seg, 4, uw, NULL) == 1, "NULL 文本 → 1 段");
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
printf("== meow_lyric 宿主单测 ==\n");
|
||
t1_basic();
|
||
t2_line_at();
|
||
t3_translation();
|
||
t4_literal_newline();
|
||
t5_limit();
|
||
t6_timefrac();
|
||
t7_oob();
|
||
t8_clear_and_empty();
|
||
t9_wrap();
|
||
printf("%s\n", g_fail ? "== FAIL ==" : "== PASS ==");
|
||
return g_fail ? 1 : 0;
|
||
}
|