ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

BallonsTranslator 文本排版引擎解析:从 Qt 富文本到横排/竖排布局的完整契约

BallonsTranslator 文本排版引擎解析:从 Qt 富文本到横排/竖排布局的完整契约 AI 应用计算机视觉图像处理NLP桌面应用【免费下载链接】BallonsTranslator深度学习辅助漫画翻译工具, 支持一键机翻和简单的图像/文本编辑 | Yet another computer-aided comic/manga translation tool powered by deeplearning项目地址https://gitcode.com/gh_mirrors/ba/BallonsTranslator点击查看免费下载BallonsTranslator 的文本排版模块doc/ui/text_layout.md定义了绘制shaping、换行wrapping、竖排流动vertical flow、绘制painting与编辑editing之间共享的行为与归属契约。本文以该文档为主体结合 layout.py、horizontal_layout.py、vertical_layout.py 等源码与配套测试逐层讲解排版心理模型、核心契约、书写模式、字距行距、对齐缩放与失效验证机制。读完你将理解为何竖排、纵中横Tate-chu-yoko、注音Ruby等特性能共享同一套编辑状态机以及修改排版代码时应当遵循哪些不变量。前置阅读Text engine文本引擎总纲。本文只记录各子系统共享的行为与归属实现级别的算法细节按约定放在代码注释与聚焦测试中。心理模型Qt 是唯一的文本模型与整形器text_layout.md首先给出了一条贯穿全文的数据流这也是整个排版子系统的心理模型QTextDocument rich text (Qt UTF-16 positions) - SceneTextLayout fragment metrics - horizontal lines or vertical columns - settled placement - fill, effects, annotations, cursor, selection, and hit testing - TextItemGeometryController bounds and visual mapping这条链路的含义是Qt 始终是可编辑文本模型 整形器shaper。自定义的横排/竖排布局只负责摆放 Qt 的QTextLine绝不会创建第二套文本表示。这一点与text_engine.md中QTextDocument、光标、选区、IME 属于实时编辑布局记录、内边距、映射、预览、位图与缓存都是派生数据、永不持久化的原则完全一致。在实现上这条链路对应SceneTextLayout继承QAbstractTextDocumentLayout见 layout.pyblock_charfmt_lst/block_qcharfmt_lst每个文本块内按 Qt 片段fragment收集的字符格式度量一次重建、整代使用_block_fragment_ends记录每个格式片段独占的排他结束位置用于get_char_fontfmt()与largest_font_format()做bisect二分查找layout_generation布局代数计数器任何重新摆放都会_begin_layout_generation()自增派生缓存必须随之失效x_offset_lst/y_offset_lst横排的每行 y 偏移、竖排的每列 x 偏移size_enlarged信号只在完整版面已就绪后发出避免把半成品几何暴露给外部。reLayoutEverything()layout.py注释明确写道Qt 片段长度是UTF-16 单位而非 Python 字符数每个格式运行只存一个排他结束位置——这是全文反复强调的 UTF-16 坐标纪律的代码起点。核心契约派生数据一起重建、永不持久化text_layout.md的 Core contract 浓缩了六条硬性约定它们直接决定了源码的组织方式FontFormat提供整块级item-wide书写模式、对齐与兼容性默认值QTextDocument格式负责范围级range-bound排版与段落级paragraph-bound行距。对应源码FontFormat 中vertical、alignment、standard_vertical_roman_alignment、line_spacing、letter_spacing、ligature_*、oldstyle_nums等字段全部是块级默认layout.py 构造SceneTextLayout时直接取fontformat.line_spacing / letter_spacing / line_spacing_type。放置记录placement、墨迹边界ink bounds与缓存全部是派生物必须在一次settled layout generation中一起重建永不持久化。TextBlock持久化的只有富文本、逻辑矩形、角度、alpha 蒙版与FontFormat见 textblock.py。填充、效果、注释、光标、选区、命中测试与可视边界必须消费同一套 settled cells 与变换。Qt 位置是 UTF-16 码元只要 Python 字符串与 Qt 位置打交道就必须走共享的 UTF-16 / grapheme 辅助函数光标绝不能落在代理对surrogate pair或组合字串内部。源码中_utf16_length、_utf16_slice、_grapheme_ranges见 rendering/indexing.py正是这套纪律的实现。效果内边距effect padding与可见墨迹溢出属于源几何source geometry不属于持久化的逻辑矩形。SceneTextLayout中的_effect_padding注释明确写道效果内边距是派生布局状态不是富文本内容在受支持的 Qt 绑定下 QTextDocument margin 会创建 undo 条目因此实现用doc.documentMargin()初始化并单独维护而不是写回文档格式。版本化TextBlock.text_layout_versiontextblock.py为整块布局语义版本化。缺失或版本为 0 的竖排块迁移到右对齐以匹配其早期实际放置效果内联 HTML 扩展保持无版本遵循text_engine.md的兼容性规则。选择背景的排除逻辑一条值得注意的实现细节layout.py 中的selection_segments_excluding()与paint_context_without_selection_ranges()是光标/选区/命中测试与 settled cells 一致契约的典型体现竖排空格格子的选中背景由布局自己绘制因此要把这些布局自有的选中格子从交给 Qt 的 PaintContext 中减掉避免双重绘制。文档没有展开但这条实现路径恰恰证明了同一套 cells 供所有消费者复用不是口号而是逐帧生效的代码。书写模式横排与竖排横排HorizontalHorizontalTextDocumentLayouthorizontal_layout.py保留 Qt 的整形、字形运行、光标行为与词边界换行只补充 Qt 不以编辑器所需形式暴露的几何溢出的尾部 U0020 空格Qt 会把悬挂空格挂在它们前面的行上。该布局让这些空格保留在文档里但为它们生成派生的续行格子_space_rows/_relocated_spaces/_space_caret_rects使换行、文本框增长、光标、选区与命中测试保持一致。_trailing_space_layout()horizontal_layout.py先判断这是普通软换行分隔符还是需要搬迁的溢出尾部空格——单个 U0020 分隔符保持 Qt 原生几何多个或行尾空格才获得派生续行几何。其他 Unicode 分隔符保持 Qt 行为。普通连字common ligatures在两个绑定下都可用自由/上下文连字与旧式数字oldstyle figures需要 Qt 6.11对应 annotations.py 的FONT_FEATURES_AVAILABLE QT6 and hasattr(QTextCharFormat, setFontFeatures)Qt 5 会保留其 CSS 但不应用。字距与字体特性按范围per range应用。身份字距identity spacing刻意不设置因为显式的 Qt spacing 属性可能抑制可选连字。版本相关的特性标签处理留在 layout/annotation 边界内。此外该布局为每行维护一个_plain_line_cache标量化的_PlainLineLayout记录在文本与格式未变化时用原始算术重摆原生行_reuse_plain_line()避免重复整形invalidate_native_metrics()在字体度量变化时清空该缓存并整体重排。竖排VerticalVerticalTextDocumentLayoutvertical_layout.py通常每个 grapheme 一个格子cell列从右到左放置。标点朝向与对齐是 vertical_layout.py 顶部附近的语义类别PUNSET_PAUSEORSTOP、PUNSET_BRACKETL/R、PUNSET_COMPACT、PUNSET_INSEPARABLE_REPEAT、PUNSET_VERNEEDROTATE、PUNSET_STANDARD_VERTICAL_ROMAN等文档要求扩展这些类别而不是添加绘制时刻的 glyph 例外。实现中needs_vertical_rotation()、centers_vertical_glyph()全部由这些集合与FontFormat.standard_vertical_roman_alignment驱动。标准罗马模式standard Roman mode保持比例罗马字形竖直且居中_is_non_fullwidth_roman()按 Unicode East Asian Width 与 LATIN / ROMAN NUMERAL 名称判定非全宽罗马字。交替模式alternate mode将它们顺时针旋转并走中文混排标点路径。紧凑标点compact punctuation在不裁剪墨迹的前提下缩短可压缩标点格子的推进量由配置pcfg.compact_vertical_punctuation_spacing开关vertical_layout.py。重复破折号、竖线、引导符与省略号构成不可分割的运行indivisible runs_inseparable_punctuation_run()vertical_layout.py识别连续同一种PUNSET_INSEPARABLE_REPEAT字符如……整个运行作为一个整体换列字距在运行之后应用。空白whitespace贡献流动推进量flow advance但不进入用于把相邻字形在列中居中的墨迹边界。updateDrawOffsets()中有明确注释Whitespace owns vertical cells, never horizontal ink centeringvertical_layout.py。竖排的墨迹边界带缓存_LINE_INK_BOUNDS_CACHE最多 2048 条vertical_layout.py按精确整形签名字体、字形索引、相对位置缓存_line_ink_bounds()同时刻意不持有存活的 Qt layout 句柄只保留标量输入_PlainColumnLayoutNamedTuple。纵中横Tate-chu-yokoTCYTate-chu-yoko 是占据一个竖排流动格子的横向 Qt 运行vertical_layout.py多字符运行按 W3Ctext-combine-upright语义整形为窄体等价形式依据 grapheme 数量选用hwid/twid/qwid特性见_TATE_CHU_YOKO_WIDTH_FEATURES同时保留原始文档文本与 UTF-16 位置单字符运行与无关兼容字符保持不变。归一化运行normalized runs用同一放置用于绘制、效果、选区与命中测试其临时整形布局只包含该运行自身的文本。TateChuYokoRun在 Qt 边界把运行局部run-local的字形与光标索引翻译回块局部block-local的 UTF-16 偏移这些运行随所属竖排布局一起重建。原生 IME 组合在提交前始终是权威_prepare_tate_chu_yoko_line()遇到preeditAreaText()直接返回原行不干预预编辑。TCY忽略作者设置的 letter-spacing改用字体的半宽标点加匹配的半宽/三分之一宽/四分之一宽特性标准罗马模式保持整形运行的自然横向宽度交替模式把剩余超出水平缩放到一 em。可见墨迹居中但不改动存储文本字形墨迹可以超出列宽overhang但该溢出只影响绘制与交互边界绝不挤占相邻列。注音/振假名Ruby/FuriganaRuby 是附着式布局内容attached layout content不是分离的悬浮层Group Ruby 不可分割mono Ruby 只允许在基字/注音对之间换行。每个单元取基字与注音推进量的较大者较短的一侧在该格子内做间距spacing。横排 Ruby 出现在基字上方或下方竖排 Ruby 保持直立、位于右侧或左侧。同一套 cells 同时拥有换行、绘制、选区、光标、命中测试、效果与可见边界横排见source_cursor_rect()、_ruby_hit_test()、_ruby_line_placements()竖排见_vertical_ruby_placements()、_vertical_ruby_unit_cell()、_ruby_hit_test()全部围绕RubyBlockMetrics/RubyUnitMetrics的unit/container边界实现。Ruby 与 Tate-chu-yoko 不能重叠自动 Ruby 溢出automatic Ruby overhang不受支持。流动与间距字距按范围、行距按段落text_layout.md的 Flow and spacing 章节给出了三组精确的归属规则字距character/letter spacing按范围绑定行距line spacing按段落绑定。光标格式化其所在段落选区格式化相交的所有段落回车继承块格式FontFormat为旧的或空富文本提供默认值。实现证据SceneTextLayout.calculate_line_spacing()layout.py按LineSpacingType计算——Proportional比例模式为line_spacing * sizeDistance距离模式为line_spacing * 10 sizeidentity_linespacing()相应返回 1.0 / 0.0。行距值由block_line_spacing()从段落blockFormat()读取并回退到FontFormat默认。空白永远是文档内容必须消费显式的可编辑格子。横排与竖排可以有不同的格子表示但都不允许把空白挪进第二套文本模型也不允许从光标与命中几何中丢掉它。竖排空白贡献流动推进量而非用于居中相邻字形的墨迹边界。字距是受影响字形或连接运行的尾部推进量trailing advanceW3C tate-chu-yoko 组合忽略它。在被压缩成单列的竖排条目上增大字距可能使逻辑高度增长以保住该列多列条目保持固定区域重排自动增长绝不会默默缩小盒子。实现证据VerticalTextDocumentLayout.spacing_change_height_growth()vertical_layout.py正是这一规则的可执行表述——仅当当前内容被挤压到恰好一列时才计算所需高度增长多列时直接返回 0。行距归目的行/列所有第一可视行或列不带前导行距地锚定之后的每个行/列使用其段落的行距值与模式段落边界不会重启这条视觉前导边缘规则。实现证据竖排layoutBlock()中is_first_line block_no 0时用identity_linespacing()后续行用block_line_spacingvertical_layout.py。布局必须作为一次事务 settle换行、空白、注释、片段度量、UTF-16 位置、墨迹边界与交互几何相互耦合只在完整时发布。横排reLayout()在末尾统一documentSizeChanged.emit()竖排同理vertical_layout.py。对齐与缩放只平移已落定的列竖排对齐通过水平平移已落定的列实现文档给出的对照表完整如下Alignment对齐Fixed growth anchor固定增长锚点Added-width movement加宽移动Left左对齐Top-left左上列保持不动向右增长Center居中Top-center顶部居中列移动一半均匀增长Right右对齐Top-right右上列随右边缘移动向左增长实现对应 vertical_layout.py 的_alignment_column_shift()apply_alignment()按TextAlignmentLeft0 / Center1 / Right2见 fontformat.py计算 slack 的分配然后_translate_columns()只平移 x 坐标不重塑文本、不改变文档内容。_translate_columns()有一个细节值得注意它通过QTextLine实际的 26.6 定点移动来读取实际应用位移所有派生坐标x_offset_lst、layout_left都跟随真实移动而非请求的浮点值避免累积舍入误差。对齐改变会同时更新每一条放置与墨迹边界记录apply_alignment()中同步调用_refresh_base_ink_bounds()与_refresh_annotation_ink_bounds()。纯宽度缩放可能复用该平移reLayoutForResize()vertical_layout.py在高度、内边距与流未变化时只计算新的对齐平移并再次_translate_columns()高度、内边距或流动变化则必须完整重排。竖排还会在重排时保留内容最小列宽若可用宽度小于列内容宽度则回退完整reLayout()。几何控制器TextItemGeometryController保留匹配的场景空间锚点因此布局与场景移动不得同时补偿同一次缩放——这与text_engine.md中几何控制器拥有空间之间的映射工具必须通过它、而非只适配单一消费者的约定呼应。绘制与交互共享的放置边界vertical_line_placement()是旋转字形、tate-chu-yoko、强调emphasis、Glyph Slant 与效果的共享边界vertical_layout.py。它返回(line, offset, orientation)三元组普通直立行是单位变换需旋转的标点行返回顺时针 90° 变换TCY 行返回tate_chu_yoko_transform()的 run 级变换。光标、选区与命中测试必须使用同一个 placement——source_cursor_rect()、_source_hit_test()、_tate_chu_yoko_hit_test()、_ruby_hit_test()全部经由它。连字与连接字形可以改变整形但不改变逻辑 UTF-16 编辑范围。文档背景绘制在选区之下字形墨迹绘制在选区之上前景与效果布局必须复用同一套 settled offsets。竖排draw()中对选区行的处理_selection_foreground_context()清除背景 _vertical_selection_backgrounds()由布局自己填格子背景正是该分层的实现。绑定到放置的缓存必须随布局代数失效且不得保留被替换文档布局的记录。竖排的_selection_geometry_cache在每次apply_alignment()/reLayout()开头清空横排的_plain_line_cache在布局上下文_plain_line_context变化时清空。失效与验证一次发布一个 settled generation文档给出的正常路径是document or format change - rebuild fragment metrics and position maps - settle lines or columns - update draw offsets and ink bounds - publish size and refresh geometry/effects这正是SceneTextLayout的调用顺序documentChanged()→reLayoutEverything()重建片段度量与 UTF-16 索引→reLayout()settle 行/列→ 竖排额外updateDrawOffsets()_refresh_base_ink_bounds()_refresh_annotation_ink_bounds()→documentSizeChanged.emit()。横排在_emit_size_enlarged()中通过publishing_size_enlargement标志保证不暴露半落定的绘制几何。验证层面文档明确要求测试关系而非依赖字体的精确像素覆盖受影响的书写模式、对齐、间距、注释、效果、UTF-16 文本、编辑、缩放与模式切换。聚焦测试清单均已在仓库中存在tests/test_horizontal_whitespace.py —— 横排溢出尾部空格的行为与续行格子tests/test_vertical_alignment.py —— 竖排对齐平移与固定锚点tests/test_vertical_interaction.py —— 竖排光标、选区与命中测试tests/test_vertical_roman_alignment.py —— 竖排罗马字的标准/交替对齐模式tests/test_rich_text_annotations.py —— 富文本扩展标记的往返round-triptests/test_ruby_furigana.py —— Ruby/振假名的附着式布局。这些测试与源码中的 doctest示例如selection_segments_excluding(0, 6, ((1, 3), (4, 5)))返回[(0, 1), (3, 4), (5, 6)]、_inseparable_punctuation_run( ……, 0)返回(1, 2)一起构成了关系优先于像素的验证文化。给二次开发者的实践清单结合 text_engine.md 的总纲与本文的布局契约改动排版相关代码时应逐条核对找到第一变更所有者再刷新文本/文档格式变 → 文档与布局度量/间距/书写模式变 → 布局效果栈/alpha 蒙版变 → 效果渲染器效果范围/逻辑矩形变 → 布局与效果更新后的几何视觉变换参数变 → 几何控制器。发布一个 settled generation批量合并瞬态编辑刷新器保持幂等缓存有界且可释放。绝不把派生数据写回持久逻辑矩形效果内边距与可见墨迹溢出属于源几何。所有 Python 字符串与 Qt 位置的边界走 UTF-16 辅助函数光标不得落在代理对或组合字串内部。布局生命周期、整形、光标或绘制改动需要同时通过 PyQt5 与 PyQt6 验证渲染/交互改动还需要一次主题化应用themed-app通过或明确声明限制——这是 text_engine.md 的硬性门槛。这条排版契约的真正价值在于无论你添加新的书写模式、新的标点类别、新的注音策略还是新的效果只要所有消费者都从同一套 settled cells 与vertical_line_placement()变换出发编辑状态机、绘制栈与几何控制器就能继续协同工作而不会被平行的第二套文本表示撕裂。赞分享AI 应用计算机视觉图像处理NLP桌面应用【免费下载链接】BallonsTranslator深度学习辅助漫画翻译工具, 支持一键机翻和简单的图像/文本编辑 | Yet another computer-aided comic/manga translation tool powered by deeplearning项目地址https://gitcode.com/gh_mirrors/ba/BallonsTranslator点击查看免费下载相关推荐MinerU竖排文本中文竖排布局特殊支持MinerU竖排文本中文竖排布局特殊支持 痛点传统OCR工具对竖排文本的识别困境 在中文文档处理领域竖排文本Vertical Text Layout一人工智能大模型OCR计算机视觉搞定Skia文本垂直布局从竖排到旋转的完全指南搞定Skia文本垂直布局从竖排到旋转的完全指南 你还在为Skia文本垂直排列头疼无论是中文竖排需求还是特殊旋转文本效果本文将通过矩阵变换与画布操作教你用图形学图像处理eSearch竖排文本中文竖排文字识别技术eSearch竖排文本中文竖排文字识别技术 痛点传统OCR对竖排文本的识别困境 在日常工作和学习中我们经常会遇到需要识别竖排中文文本的场景古籍文献、传统桌面应用OCR屏幕录制视频处理图像处理上一篇HomeSpan网络配置Wi-Fi配对和OTA更新完全教程下一篇Pluto与Fairwinds生态集成构建完整的Kubernetes治理解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表