TrueType字体解析实战:从表目录到字形轮廓的完整拆解
简介这是一份面向字体技术开发者与图形编程爱好者的TrueType格式解析类源码聚焦于将复杂的TTF字体文件结构封装为可复用的C类帮助读者在游戏开发、图形设计软件或文本渲染引擎中集成自定义字体处理能力。资源包共2个文件包含1个cpp实现文件与1个h头文件压缩包约42KB其中cpp承载读取字体文件、解析头部信息、提取字形数据、解码Glyf表与计算字距等核心逻辑h则声明类接口与相关数据结构。内容围绕Head、Maxp、Hhea、Hmtx、CFF、Glyf、Loca等关键表展开涉及立方与二次贝塞尔曲线轮廓描述读者可借此理解字形加载、宽度获取、字号调整与字形查找的实现思路。目前已有605人学习下载适合希望深入字体解析内部机制、提升字体编程专业素养的中高级开发者参考。1. 字体文件里的黑匣子为什么值得自己拆一遍 TrueType做过前端或桌面端的人大概率都遇到过这种场景设计给了一个.ttf文件要求把里面某个字的轮廓取出来做矢量裁剪或者把字体子集化后嵌进 PDF又或者要判断某个字符到底有没有对应字形。调库当然能跑但一旦出现「字宽对不上」「渲染出来缺笔画」「子集化后部分字变方框」这类问题就只能干瞪眼因为字体文件在大多数人眼里是个黑匣子。TrueType 格式解析这件事本质上是把.ttf当成一个有明确规范的结构化二进制文件来读。它由一张张「表」拼成每张表管一件事head管全局度量cmap管字符到字形的映射glyf存轮廓loca存轮廓偏移hmtx管水平度量。把这几个表读通你就能自己回答「这个字长什么样」「它有多宽」「它有没有被定义」这三个最核心的问题。适合谁做字体子集化、PDF 嵌入、矢量图形处理、嵌入式字库裁剪的工程师以及任何不想再被字体库的报错信息牵着走的人。下面我按自己实际拆文件的顺序把这条路走一遍。2. 先看懂表目录TrueType 的骨架与最小读取路径2.1 文件头与表目录的二进制布局.ttf文件开头是 12 字节的偏移表头Offset Table结构固定前 4 字节是版本号常见0x00010000接着 2 字节是表数量numTables再往后 6 字节是搜索参数searchRange、entrySelector、rangeShift这三个值只影响二分查找效率解析时可以忽略但必须跳过。紧接着是表目录Table Directory每张表占 16 字节4 字节标签如glyf、cmap、4 字节校验和、4 字节偏移量、4 字节长度。所有数值都是大端序big-endian这是第一个容易翻车的地方——Python 里读二进制默认按机器字节序必须显式指定I这类大端格式符。import struct def read_table_directory(path): with open(path, rb) as f: data f.read() # 前4字节版本接着2字节表数量 version, num_tables struct.unpack(IH, data[:6]) tables {} offset 12 # 表目录从第12字节开始 for i in range(num_tables): base offset i * 16 tag, checksum, off, length struct.unpack(4sIII, data[base:base16]) # 标签可能是带空格的统一去掉尾部空格 tables[tag.decode(latin-1).strip()] { offset: off, length: length, checksum: checksum } return version, tables, data version, tables, raw read_table_directory(demo.ttf) print(hex(version), sorted(tables.keys()))这段代码做了三件事一次性把文件读进内存字体文件通常几 MB完全放得下、按大端解析表头、遍历表目录建立「标签 → 偏移和长度」的字典。参数上唯一要盯的是4sIII这个格式串4s读 4 字节字符串三个I读无符号 32 位整数强制大端。跑完你会看到类似[cmap, glyf, head, hhea, hmtx, loca, maxp, name, post]的列表这就是后续所有操作的入口。2.2 用 head 和 maxp 确定解析边界拿到表目录后不要急着读glyf先读head和maxp因为它们决定了后面所有数组的长度和偏移计算方式。head表固定 54 字节关键字段是unitsPerEm第 18 字节起2 字节它定义了字体设计坐标系常见值 1000 或 2048。indexToLocFormat第 50 字节起2 字节决定loca表用短格式偏移除以 22 字节一项还是长格式4 字节一项这个字段读错后面取轮廓必然错位。maxp表前 6 字节是版本和numGlyphs字形总数它告诉你loca表有多少项、glyf里最多有多少个字形。def read_head_maxp(tables, data): head_off tables[head][offset] # unitsPerEm 在 head 表内偏移 18indexToLocFormat 在偏移 50 units_per_em, struct.unpack(H, data[head_off18:head_off20]) index_to_loc, struct.unpack(h, data[head_off50:head_off52]) maxp_off tables[maxp][offset] num_glyphs, struct.unpack(H, data[maxp_off4:maxp_off6]) return units_per_em, index_to_loc, num_glyphs upem, loc_format, num_glyphs read_head_maxp(tables, raw) print(funitsPerEm{upem}, locFormat{loc_format}, glyphs{num_glyphs})indexToLocFormat用h有符号 16 位读因为规范里它可能是 0 或 1但用有符号更保险。numGlyphs是后续遍历的硬边界任何越界访问都应该在这里被拦住。我一般会先打印这三个值确认文件正常再往下走。2.3 读 loca 与 glyf把轮廓偏移算准loca表是一个偏移数组第 i 项是第 i 个字形的轮廓数据在glyf表中的起始偏移第 i1 项是结束偏移。如果两项相等说明这个字形是空的比如空格。短格式下存储的是「实际偏移除以 2」读出来要乘 2长格式直接就是字节偏移。def read_loca(tables, data, num_glyphs, loc_format): loca_off tables[loca][offset] offsets [] if loc_format 0: # 短格式每项2字节实际偏移 值 * 2 for i in range(num_glyphs 1): v, struct.unpack(H, data[loca_offi*2:loca_offi*22]) offsets.append(v * 2) else: # 长格式每项4字节 for i in range(num_glyphs 1): v, struct.unpack(I, data[loca_offi*4:loca_offi*44]) offsets.append(v) return offsets loca read_loca(tables, raw, num_glyphs, loc_format) # 取第5个字形的轮廓字节范围 gid 5 glyf_off tables[glyf][offset] start, end loca[gid], loca[gid1] glyph_data raw[glyf_offstart: glyf_offend] print(fglyph {gid} 轮廓字节数: {len(glyph_data)})这里有个细节loca有numGlyphs 1项最后一项是glyf表的总长度哨兵。取轮廓时用glyf表偏移加上loca里的相对偏移才是文件里的绝对位置。如果start end直接判定为空字形不要再去解析glyph_data否则会读到下一个字形的数据出现「轮廓串位」这种玄学问题。3. 从字符到轮廓cmap 映射与 glyf 轮廓解码3.1 cmap 子表选择别拿到第一个就用cmap表负责把 Unicode 码点映射到字形索引glyph id。它内部有多个子表subtable每个子表针对不同的编码平台和格式。常见格式有 4段映射覆盖 BMP、12分段覆盖支持增补平面、6修剪映射、0字节映射。选子表的优先级我一般这样定优先找平台 3Windows、编码 10UCS-4的格式 12 子表没有就找平台 3、编码 1BMP的格式 4再没有就找平台 0Unicode的任意格式。直接拿第一个子表是新手最容易踩的坑因为有些字体的第一个子表是给旧编码用的查中文会全部落空。def pick_cmap_subtable(tables, data): cmap_off tables[cmap][offset] num_subtables, struct.unpack(H, data[cmap_off2:cmap_off4]) best None for i in range(num_subtables): rec cmap_off 4 i * 8 platform, encoding, sub_off struct.unpack(HHI, data[rec:rec8]) fmt, struct.unpack(H, data[cmap_offsub_off:cmap_offsub_off2]) # 按优先级打分分数越高越优先 score 0 if platform 3 and encoding 10 and fmt 12: score 100 elif platform 3 and encoding 1 and fmt 4: score 80 elif platform 0: score 60 if best is None or score best[0]: best (score, cmap_off sub_off, fmt) return best[1], best[2] sub_offset, sub_format pick_cmap_subtable(tables, raw) print(f选中子表格式: {sub_format})打分逻辑是经验值不是规范强制。实际项目里如果发现某个字查不到先把所有子表都试一遍看哪个能查到再回头调整优先级。sub_offset是子表在文件里的绝对偏移后面解析格式 4 或格式 12 都从这里开始。3.2 格式 4 查表段映射的二分与边界格式 4 是 BMP 范围内最常用的子表。它的结构是若干段segment每段用endCode、startCode、idDelta、idRangeOffset四个数组描述。查一个码点c时先找到第一个endCode c的段再检查startCode c然后根据idRangeOffset是否为零决定是直接加idDelta还是去glyphIdArray里取。def lookup_format4(data, sub_off, codepoint): seg_count_x2, struct.unpack(H, data[sub_off6:sub_off8]) seg_count seg_count_x2 // 2 # 各数组起始位置 end_base sub_off 14 start_base end_base seg_count_x2 2 # 跳过 reservedPad delta_base start_base seg_count_x2 range_base delta_base seg_count_x2 for i in range(seg_count): end_code, struct.unpack(H, data[end_basei*2:end_basei*22]) if codepoint end_code: continue start_code, struct.unpack(H, data[start_basei*2:start_basei*22]) if codepoint start_code: return 0 # 落在段间空隙无字形 delta, struct.unpack(h, data[delta_basei*2:delta_basei*22]) range_off, struct.unpack(H, data[range_basei*2:range_basei*22]) if range_off 0: return (codepoint delta) 0xFFFF # 否则去 glyphIdArray 取地址计算是格式4最容易错的地方 addr range_base i*2 range_off (codepoint - start_code) * 2 gid, struct.unpack(H, data[addr:addr2]) return (gid delta) 0xFFFF if gid ! 0 else 0 return 0 0xFFFF是必须的因为idDelta是有符号的相加后可能为负规范要求对 65536 取模。glyphIdArray的地址计算range_base i*2 range_off (codepoint - start_code)*2是格式 4 最反直觉的一步range_off是相对于当前idRangeOffset项自身的偏移不是相对于数组开头写错就会查到隔壁段的数据。3.3 简单字形轮廓标志位与坐标增量glyf里的字形分两类简单字形和复合字形。简单字形的结构是10 字节头numberOfContours、xMin/yMin/xMax/yMax接着是轮廓结束点索引数组endPtsOfContours再是指令长度和指令最后是标志位数组和坐标增量。坐标不是绝对值而是增量编码。每个点有一个标志字节最低位表示 x 增量是否为 0次低位表示 y 增量是否为 0第 2 位表示增量是 1 字节还是 2 字节第 4 位表示这个增量是正还是负。这套编码是为了压缩体积但解析时必须严格按位判断。def parse_simple_glyph(data, glyph_off, num_contours): # 跳过10字节头 p glyph_off 10 end_pts [] for i in range(num_contours): v, struct.unpack(H, data[p:p2]); p 2 end_pts.append(v) num_points end_pts[-1] 1 instr_len, struct.unpack(H, data[p:p2]); p 2 p instr_len # 跳过指令 flags [] while len(flags) num_points: f data[p]; p 1 flags.append(f) if f 0x08: # 重复标志 repeat data[p]; p 1 flags.extend([f] * repeat) xs, ys [], [] x 0 for f in flags: if f 0x02: # x增量为0 dx 0 elif f 0x10: # 正增量 dx data[p]; p 1 else: dx -struct.unpack(b, data[p:p1])[0]; p 1 x dx; xs.append(x) y 0 for f in flags: if f 0x04: dy 0 elif f 0x20: dy data[p]; p 1 else: dy -struct.unpack(b, data[p:p1])[0]; p 1 y dy; ys.append(y) return list(zip(xs, ys)), end_pts标志位的重复机制0x08要特别注意一个标志字节后面可能跟一个重复次数表示这个标志连续作用于后续多个点。漏掉这个机制点数就会对不上轮廓直接错乱。坐标增量里负增量用有符号字节读再取负正增量用无符号字节读这个不对称是规范定的不是笔误。4. 度量与复合字形hmtx 宽度和组件递归4.1 hmtx 与 hhea字宽从哪来hmtx表存每个字形的水平度量advanceWidth前进宽度和 leftSideBearing左侧留白。但为了省空间它用了「压缩」存储前numberOfHMetrics个字形存完整的 4 字节2 字节宽度 2 字节留白后面的字形只存 2 字节留白宽度沿用最后一个完整项的值。numberOfHMetrics在hhea表末尾。def read_hmtx(tables, data, num_glyphs): hhea_off tables[hhea][offset] num_hmetrics, struct.unpack(H, data[hhea_off34:hhea_off36]) hmtx_off tables[hmtx][offset] metrics [] last_adv 0 for i in range(num_glyphs): if i num_hmetrics: adv, lsb struct.unpack(Hh, data[hmtx_offi*4:hmtx_offi*44]) last_adv adv else: lsb, struct.unpack(h, data[hmtx_offnum_hmetrics*4(i-num_hmetrics)*2: hmtx_offnum_hmetrics*4(i-num_hmetrics)*22]) adv last_adv metrics.append((adv, lsb)) return metricsnumberOfHMetrics在hhea表偏移 34 处这个位置是固定的。如果numGlyphs大于numberOfHMetrics后面的字形宽度全部继承最后一个完整项这是规范允许的压缩不是文件损坏。做子集化时如果只保留部分字形必须重新计算这个值否则宽度会串。4.2 复合字形递归解析与变换矩阵复合字形不直接存轮廓而是引用其他字形并施加变换。它的头 10 字节和简单字形一样但numberOfContours为负通常是 -1。之后是一系列组件记录每个组件以标志位开头标志位决定后面跟的是 2 字节还是 4 字节参数、是否有缩放或 2x2 变换矩阵。def parse_composite(data, glyph_off, depth0): if depth 5: # 防御性限制避免恶意文件无限递归 return [] p glyph_off 10 points [] while True: flags, gid struct.unpack(HH, data[p:p4]); p 4 if flags 0x0001: # ARG_1_AND_2_ARE_WORDS arg1, arg2 struct.unpack(hh, data[p:p4]); p 4 else: arg1, arg2 struct.unpack(bb, data[p:p2]); p 2 # 这里省略变换矩阵读取按 flags 的 0x0008/0x0040/0x0080 分支处理 # 递归取子字形轮廓后按 arg1/arg2 平移 points.extend(parse_composite(data, glyph_off, depth1)) if not (flags 0x0020): # MORE_COMPONENTS break return points递归深度限制是血泪经验正常字体复合层级不会超过两三层但畸形文件可能构造循环引用不加限制会直接栈溢出。变换矩阵的分支WE_HAVE_A_SCALE、WE_HAVE_AN_X_AND_Y_SCALE、WE_HAVE_A_TWO_BY_TWO要按标志位逐个处理漏一个就会导致某些带旋转的字形渲染错位。5. 避坑与排查解析 TrueType 时最容易翻车的五件事5.1 现象中文全部查不到字形英文正常原因cmap子表选错选中了只覆盖 Latin 的格式 4 子表而中文在格式 12 子表里。解决按 3.1 的优先级重新选子表或者遍历所有子表逐个测试目标码点确认哪个能返回非零 gid。5.2 现象轮廓点坐标整体偏移图形变形原因loca表格式判断错误短格式没乘 2或者glyf表偏移没加上loca的相对偏移。解决打印loca[0]确认是否为 0打印loca[-1]确认是否等于glyf表长度两个都对上再解析轮廓。5.3 现象部分字形解析时报数组越界原因把空字形loca[i] loca[i1]当成有轮廓的字形去解析读到了下一个字形的数据。解决取轮廓前先判断起止偏移是否相等相等直接返回空点集不要进入解析函数。5.4 现象字宽和设计稿对不上整体偏窄或偏宽原因hmtx的压缩存储没处理numGlyphs numberOfHMetrics时后面的字形宽度取错。解决按 4.1 的逻辑维护last_adv超出numberOfHMetrics的字形沿用最后一个完整项的宽度。5.5 现象复合字形渲染时缺笔画或位置错乱原因变换矩阵分支没读全或者组件参数用了错误的字节宽度ARG_1_AND_2_ARE_WORDS标志没判断。解决严格按标志位逐位判断0x0001决定参数是 2 字节还是 1 字节0x0008/0x0040/0x0080决定矩阵形态一个都不能漏。6. 进阶子集化裁剪与解析结果的验证方法走到这里你已经能把一个.ttf拆成表、查到字形、取出轮廓、读出宽度。接下来最有价值的进阶方向是子集化只保留用到的字符把文件体积压下来。核心思路是重建loca、glyf、cmap、hmtx四张表其余表可以原样拷贝或按需裁剪。子集化的关键步骤先收集目标码点集合通过cmap映射到 gid 集合复合字形要递归展开把引用的子字形 gid 也加进集合然后按新顺序重新分配 gid重建loca偏移数组注意短格式要求偏移是偶数必要时补零对齐cmap重建时只保留目标码点的映射hmtx按新 gid 顺序重排并重新计算numberOfHMetrics。验证解析结果是否正确的办法我一般用两条一是把解析出的轮廓点按坐标画成 SVG 路径和字体渲染引擎的输出叠在一起比对肉眼能看出偏差二是做往返测试把子集化后的字体再用同一套解析代码读一遍确认每个目标码点都能查到非零 gid、轮廓点数一致、宽度一致。往返测试能抓出绝大多数重建时的对齐和偏移错误。有个习惯我保持了挺久每解析一张新表先把它的关键字段打印出来和规范对照一遍确认无误再写下一层逻辑。字体解析的坑几乎都出在「假设某个字段是某个值」上而规范里偏偏留了很多可选分支。宁可多打几行日志也别让一个字节的误读毁掉整个轮廓。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

基于OpenCV与pyautogui的屏幕理解与自动控制框架解析

基于OpenCV与pyautogui的屏幕理解与自动控制框架解析

简介:这套基于Python的王者荣耀自动游戏脚本,面向对游戏自动化感兴趣的Python学习者与开发者,聚焦图像识别、界面交互、事件监听、多线程协同及反作弊意识等技术点的综合实战。资源共62个文件、667KB,核心包括6个py源码、8个pyc编…

2026/10/14 15:36:31 阅读更多 →
Java Swing实时粒子系统:可嵌入GUI的烟花动效模块

Java Swing实时粒子系统:可嵌入GUI的烟花动效模块

简介:本资源是一份基于Java实现的轻量级烟花特效完整源码包,面向Java初学者、GUI编程入门者及图形动画兴趣开发者,旨在帮助理解动态视觉效果背后的多线程控制、图形绘制与物理模拟逻辑。压缩包共8个文件(48KB)&#xf…

2026/10/14 15:36:31 阅读更多 →
CMSSW框架入门:从SCRAM到cmsRun的工程解析

CMSSW框架入门:从SCRAM到cmsRun的工程解析

简介:CMSSW(Compact Muon Solenoid Software)是欧洲核子研究组织大型强子对撞机CMS实验的离线数据分析软件框架,面向粒子物理研究人员、高能数据处理开发者及相关专业学生,用于对CMS探测器产生的海量碰撞事件进行读取、…

2026/10/14 15:36:31 阅读更多 →

最新新闻

ITE V12.4 Update固件包刷写指南:增量升级、参数配置与故障排查

ITE V12.4 Update固件包刷写指南:增量升级、参数配置与故障排查

简介:ITE_V12.4_Update.rar 是嵌入式控制器(EC)V12.4 版本源代码,主要面向嵌入式系统开发者、软硬件协同工程师及相关专业学生,用于剖析 EC 在电源管理、风扇控制、键盘输入、系统安全和主处理器通信等底层硬件管理中的…

2026/10/14 16:22:34 阅读更多 →
Java GUI实战:Swing实现算术题生成与在线批改完整方案

Java GUI实战:Swing实现算术题生成与在线批改完整方案

简介:这是一套基于Java GUI技术开发的算术题在线练习与批改项目,适合Java初学者、学生或课程设计者用于理解图形界面编程、随机出题、文件读写与数据持久化。压缩包共55个文件,主体包含33个class编译文件与17个java源文件,另有2个…

2026/10/14 16:22:34 阅读更多 →
Android多列表嵌套滑动冲突终结者:ConsecutiveScrollerLayout原理与实战

Android多列表嵌套滑动冲突终结者:ConsecutiveScrollerLayout原理与实战

1. 这个布局到底解决了什么痛点做过Android复杂详情页或者多列表嵌套的兄弟应该都有过这种体验:一个页面里塞了Banner轮播、横向商品列表、纵向评论列表、底部推荐流,每个模块单独拿出来滑动都没问题,可一旦拼在同一个页面里,手指…

2026/10/14 16:22:34 阅读更多 →
八皇后问题的工程化实现:DFS剪枝与Java性能优化

八皇后问题的工程化实现:DFS剪枝与Java性能优化

1. 项目概述:一道经典题目的真实分量“八皇后”这三个字,在算法入门圈子里,几乎等同于“递归初体验”“回溯第一课”“DFS手把手教学”。但很多人刷完洛谷P1219后只记得“输出前三个解总解数”,却没意识到——这道标着“java&…

2026/10/14 16:22:34 阅读更多 →
Grin 区块与区块头处理全解析:正常接收、追赶同步与分叉重组的源码级指南

Grin 区块与区块头处理全解析:正常接收、追赶同步与分叉重组的源码级指南

区块链Web3 【免费下载链接】grin Minimal implementation of the Mimblewimble protocol. 项目地址: https://gitcode.com/gh_mirrors/gr/grin 点击查看 免费下载 本篇技术指南以 Grin 仓库中的《Blocks and Block headers》文档为骨架,系统讲解 Mimbl…

2026/10/14 16:22:34 阅读更多 →
如何靠真实预览图选风格:ppt-image-first首页、目录页、正文页三页预览法完全解析

如何靠真实预览图选风格:ppt-image-first首页、目录页、正文页三页预览法完全解析

AI 技能工作流自动化AI 应用 【免费下载链接】ppt-image-first PPT image-first skill for Codex/Claude Code/Opencode CLI 项目地址: https://gitcode.com/gh_mirrors/ppt/ppt-image-first 点击查看 免费下载 ppt-image-first 是一个 image-first 的 PPT 工作流 …

2026/10/14 16:21:33 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →