SAP Smart Forms中文打印重叠:语言版本与31字符压缩定位

发布时间:2026/11/2 7:17:44
SAP Smart Forms中文打印重叠:语言版本与31字符压缩定位

简介:该资源聚焦 SAP Smart Forms 中文字体重叠问题,面向需要在 Unicode 系统中输出中文表单的 ABAP 开发与报表实施人员,属于偏进阶的排错型参考资料。压缩包共 1 个 doc 文件,约 28KB,以文档形式记录了问题现象、成因分析与处理办法,便于随查随用。已有 1088 人学习,说明该问题在实际项目中较为常见。内容从英文环境与中文环境的差异切入,指出重叠往往源于表单语言版本未正确指定,可通过在调用 FM 'SSFRT_SET_LANGUAGES' 前为 control_parameters-langu 赋值来规避;同时提及 Note 808326、908442 关于 Unicode 打印预览重叠的说明,以及 Note 1007575 涉及亚洲字符超过 31 字符被压缩的现象,并附有 Debug 表单对应 FM 的排查思路。读者可据此快速定位是程序赋值缺失还是补丁未打,少走重复排查的弯路。

1. 中文打印重叠:一个只在英文登录下发作的 Smart Forms 故障

「SMART 中文 字体重叠」这个问题,最麻烦的地方是它看上去像玄学。同一张 Smart Forms,同一个单据号,中文环境登录预览,中文老老实实按预留空间换行;换成英文登录再预览,字段里的中文全糊成一团,字符彼此压着,打印到纸上更糟——超出预留宽度的部分不会换行,而是展开去和后面的内容交杂在一起。很多人第一反应是去查 Notes,症状确实对得上,补丁也确实打过了,问题却纹丝不动,于是开始怀疑是系统 bug、是 Unicode 的坑、是打印机的锅。

真正卡住人的不是修复动作,而是定位路径:这类故障的效果与运行环境强相关,等你发现「换个登录语言就复现或消失」这条线索时,往往已经在一堆不相干的 Note 上耗掉半天。这篇排错笔记写给两类人——手上有具体重叠单据、想照着步骤一次性定位的开发者,以及想把这类环境相关问题变成可复用排查套路的熟手。

2. 先判环境还是先判代码:E 环境与 1 环境的差异

打印重叠这件事,最忌讳上来就改代码。我踩过的坑是:一看中文糊了就怀疑字段宽度、怀疑窗口尺寸、怀疑输出设备,改了一圈发现全白改。后来养成一个习惯——先回答一个问题:这个故障是跟着数据走,还是跟着环境走。跟着环境走的故障,改代码之前必须先想清楚「环境到底改变了什么」。

2.1 登录语言不同,取到的 form 语言版本就不是同一份

先说清两个术语。「英文环境」指登录语言(login language)为 E 的系统会话,「中文环境」指登录语言为 1 的会话。这里的 1 不是序号,是系统内部的单字符语言代码,对应语言表里的中文;E 同理对应英文。判断当前会话处在哪个环境,最直接的办法是看系统状态栏里的语言标识,或者用 SU3 查个人主数据的登录语言,别凭界面上显示的是中文就下结论——界面文本语言和登录语言是两回事。

Smart Forms 打印时不是「把 form 原样画出来」,而是先确定要用这份 form 的哪个语言版本,再按那个语言版本的文本资源、字符宽度规则去排版。决定这件事的关键变量是 form 生成函数模块里的%header-langu,而%header的类型是SSFCTRLOP,也就是平时调用打印时传的那个控制结构。当会话登录语言是 1,系统取到的是中文版本,中文字符按双宽计算,超宽换行一切正常;当会话登录语言是 E,而你并没有一份英文版本的 form,系统就会把中文版本的那份 form 当成英文版本来处理,排版引擎按单字节字符去估算宽度,中文自然就压到一起,到了纸上再展开成一片交杂。

这里有个非常划算的判断方法,几乎不用任何工具:拿同一张单据、同一份数据,在中文环境和英文环境各打一次预览。如果重叠只在其中一个环境出现、另一个环境完全正常,那数据、字段长度、窗口尺寸全都是无辜的,问题一定在「选了哪个语言版本」这条链路上。反过来,如果两个环境都重叠,才轮到怀疑字段宽度、字体、输出设备这些因素。我一般会把这条判据写在排查记录的第一行,因为它能直接砍掉一半的排查分支。

顺带记一笔一个至今没完全搞明白的现象:如果是在英文环境下编辑 form,里面的中文描述文字在英文环境打印出来会变成乱码。成因没找到明确结论,但从工程角度处理很干脆——要维护中文内容的 form,就在中文环境里编辑,至少中文文本部分不要在英文会话里动。这不是玄学,是少给自己制造一个叠加变量。

观察点中文环境(登录语言 1)英文环境(登录语言 E)
预览表现中文按预留宽度自动换行中文字符互相重叠
打印到纸与预览一致超出预留宽度的部分展开,与后续内容交杂
取到的 form 语言版本中文版本无英文版本时按英文规则处理中文版本
结论form 与数据本身没问题故障与登录语言强相关

2.2 用 Notes 排除法把「补丁缺失」这条路堵死

症状这么典型,先查 Notes 是合理动作,我也是这么做的。按「Unicode 打印预览 + 亚洲文本重叠」这条线索检索,能捞到两条高度相似的:

  • Note 808326:Unicode 系统下打印预览排版不正确,字符可能重叠,也可能间距过大。
  • Note 908442:Unicode 系统下打印预览中亚洲文本重叠。

两条 Note 的症状描述几乎逐字命中现场。到这里很多人就收工了——打补丁、重启、验证,然后发现没用。我当时也踩了这一脚,直到对比 Note 的修正说明(Corrections Instructions)里列的代码对象,才发现手上的 ECC6.4 系统早就把这两条都实施过了。也就是说,补丁这一层根本没有缺口,继续在 Notes 里翻只会越翻越远。

补丁状态的确认动作很轻,但必须做,因为它是一个「排除项」而不是「解决项」。常规做法是在 SNOTE 里按编号检索,看这条 Note 的状态是已实施、可实施还是与系统不相关;也可以从已实施列表里反查编号。重点不是把 Note 打上,而是得到一个明确结论:这条线索断了,该去 Debug 了。这一步的价值在于止损——不做这个确认,你就会反复怀疑「是不是补丁没打全」,然后在系统升级、传输、重启之间兜圈子。

提示:症状匹配不等于根因匹配。同一个「重叠」表现,可能来自语言版本选错、字符长度压缩、输出设备字体缺失三条完全不同的路径,Note 只描述表现,不告诉你当前系统走的是哪条路径。

排除补丁之后,方向就清晰了:既然系统层面该修的都修了,那就只能是自己调用 form 的方式有问题。接下来要做的不是继续读文档,而是进调试器,直接看%header-langu到底被赋成了什么值。

3. 根因在 SSFRT_SET_LANGUAGES:%header-langu 被 sy-langu 顶替了

进调试器之前得先知道看哪里。Smart Forms 不是你画的那个样子在跑,保存激活时系统会为它生成一个函数模块,所有排版逻辑、文本取值、输出控制都在这个生成的 FM 里。打印时真正被调用的就是这个 FM,而不是 form 本身。所以问题的答案不在画布上,在这个 FM 的源码里。

3.1 在生成的 FM 里找到那段语言设置调用

定位生成 FM 的方式有两种。一种是在调用程序里通过SSF_FUNCTION_MODULE_NAME拿到 FM 名后,用 SE37 或 SE80 打开;另一种更省事,直接在调用程序里下断点,单步步入打印函数。FM 打开后,往下翻到 350 行之后,会看到这么一段:

" 生成函数模块中的原始片段(ECC6.4),位置通常在 350 行之后 CALL FUNCTION 'SSFRT_SET_LANGUAGES' EXPORTING i_control_parameters = control_parameters " 调用方传进来的 SSFCTRLOP i_masterlanguage = '1' " form 的主语言 i_thrulang = ' ' i_install_lang = ' ' IMPORTING o_languages = %languages. " 回传的语言清单 %header-langu = %languages-langu1. " 关键:决定打印哪一版 form

这段代码的含义值得逐行看。i_masterlanguage = '1'是 form 定义时指定的主语言,这里是中文,说明 form 本身是中文版;i_thrulang和i_install_lang传空,表示不额外指定穿透语言和安装语言。真正拍板的是最后一行:%header-langu取的是%languages-langu1,而%languages是SSFRT_SET_LANGUAGES根据入参算出来的结果。换句话说,form 主语言是中文这件事,并不能保证打印就用中文版本,中间还有一层翻译。

3.2 control_parameters 为空时,谁在替你做决定

把SSFRT_SET_LANGUAGES打开看内部逻辑,语言取值的规则可以简化成这样:

" SSFRT_SET_LANGUAGES 内部语言取值的简化表达 IF control_parameters IS INITIAL. o_languages-langu1 = sy-langu. " 没传,就用当前会话登录语言 ELSE. o_languages-langu1 = control_parameters-langu. ENDIF.

规则本身很朴素:调用方传了control_parameters-langu,就听调用方的;没传(结构为空或字段为空),就退回当前会话的登录语言sy-langu。而我在打印程序里恰恰只填了输出设备、没填语言,于是control_parameters里语言字段是空的,%header-langu就被赋成了sy-langu。登录语言是 E,%header-langu就是 E,系统按英文版本去找这份 form 的文本资源;找不到英文版本,就退而用中文版本顶替,但排版规则仍按英文(单字节)走。中文字符被当成半角计算宽度,字符间距、换行点全错,重叠就是这么来的。

这也解释了为什么中文环境一切正常:登录语言是 1,sy-langu恰好等于1,%header-langu蒙对了,排版走中文规则。它是一个「碰巧正确」的状态,不是「被正确设置」的状态。凡是靠巧合跑通的代码,换个环境必然翻车,这是血泪经验。

用一段更直观的取值链把因果串起来:

环节值说明
调用程序control_parameters-langu空根因起点,没赋值
SSFRT_SET_LANGUAGES判断initial走sy-langu分支
%languages-langu1E跟随登录语言
%header-languE系统按英文版找 form
实际排版规则单字节宽度中文被压窄,出现重叠

3.3 改法:control_parameters-langu = 1 并透传到 FM

根因清楚之后,修复动作只有一行是关键的,但上下文必须配对,否则改了不生效。正确写法是在调用打印函数模块之前,把control_parameters-langu明确赋成1,并把这个结构体完整传给生成的 FM:

DATA: ls_control TYPE ssfctrlop, " 打印控制结构,含语言、对话框开关 ls_output TYPE ssfcompop, " 输出控制,设备、份数等 lv_fm TYPE rs38l_fnam, " form 生成的函数模块名 ls_info TYPE ssfcrescl. " 打印结果与返回信息 " 1) 明确锁死语言版本:1 = 中文,不要把决定权留给 sy-langu ls_control-langu = '1'. ls_control-no_dialog = 'X'. " 后台/批量时不弹打印对话框 ls_control-preview = 'X'. " 需要先看预览时置 X " 2) 取到 form 对应的函数模块名 CALL FUNCTION 'SSF_FUNCTION_MODULE_NAME' EXPORTING formname = 'ZSF_DELIVERY_NOTE' IMPORTING fm_name = lv_fm EXCEPTIONS no_form = 1 no_function_module = 2 OTHERS = 3. IF sy-subrc <> 0. MESSAGE '取函数模块名失败,先确认 form 已激活' TYPE 'E'. ENDIF. " 3) 关键:把 control_parameters 透传进去,%header-langu 才会等于 1 CALL FUNCTION lv_fm EXPORTING control_parameters = ls_control output_options = ls_output user_settings = ' ' IMPORTING job_output_info = ls_info EXCEPTIONS formatting_error = 1 internal_error = 2 send_error = 3 user_canceled = 4 OTHERS = 5. IF sy-subrc <> 0. MESSAGE '打印失败,先看 job_output_info 里的协议行' TYPE 'E'. ENDIF.

几个参数的意义需要说透,不然照抄也容易改错。ls_control-langu填的是系统内部单字符语言代码,中文是'1',不是'ZH'——ZH是两字符的 ISO 代码,两个体系不要混用。no_dialog = 'X'的意义是把打印对话框关掉,避免批量跑的时候挂在弹窗上;调试阶段想观察预览效果,就把preview = 'X'置上,此时系统会直接给出预览而不是发到打印机。第三步的control_parameters必须传,这是整段代码的核心:只要这个入参到位,SSFRT_SET_LANGUAGES就走ELSE分支,%header-langu稳定等于1,跟操作员用什么语言登录彻底解耦。

改完之后不要只在一台机器、一个账号上验证。至少要用英文登录的账号跑一次同样的单据,确认预览里中文换行正常、打印到纸的内容不再交杂。这一步才算真正闭环,因为原来的正确是巧合,现在的正确是显式指定。

3.4 三处语言设置必须对齐

修完之后我回头捋了一遍,这类问题其实有三个方面需要对齐,只改一处往往还是会出问题。第一处是调用程序里的control_parameters-langu,它决定%header-langu;第二处是 form 的主语言,在 form 的属性里,决定有没有对应版本的文本资源可用;第三处是数据本身,&...&取出来的业务字段如果是中文,就必须靠前两处保证排版按双字节走。

三处不一致的典型后果是这样的:调用程序锁了中文,但 form 从来没有中文语言版本,那打印出来会是空白或回退到别的东西;form 有中文版本,但调用程序没锁语言,就退回到本篇描述的重叠;两处都对了,但某一版 form 里有人用英文字体写死了样式,中文照样难看。所以每次改完,我习惯把这三处挨个对一遍,再看预览。

注意:不要试图通过修改生成的函数模块来修这个问题。生成 FM 在 form 每次激活时都会被重新生成,手工改动会被覆盖,而且会让后续的传输和比对彻底失效。

4. 另一种重叠:超过 31 字符后被压缩,Note 1007575

语言版本修好之后,我以为收工了,结果回归测试又冒出一个新现象:中文不再互相压着了,但一段比较长的中文取出后,字符被挤成一团,行尾该换行的地方没换,整段缩在一起。这个表现和之前的重叠很像,但触发条件完全不同,如果不加区分地按同一个思路去治,会一直在原地打转。

4.1 重叠和压缩是两种故障,别混着治

先把两种表现分开描述,这是判断的前提。语言版本导致的重叠,特征是「跟着登录语言变」,中文环境正常、英文环境异常,字符是互相压着的,边界模糊,打印到纸上会向外展开去和后面的内容打架。字符压缩的特征是「跟着文本长度变」,和登录语言无关,短文本完全正常,长文本开始挤,且挤压是从某个固定长度开始的,边界相对整齐。

我做的第一件事是找阈值:拿同一张单据,把要打印的中文描述从短改到长,每次增加几个字,看从第几个字开始出问题。测下来是 31 个字符——不超过 31 个字符时排版正常,一旦超过,系统就把整段压缩在原本给一行的空间里。有了这个数字,检索就有了精准的落点,很快命中 Note 1007575:Unicode 系统下打印亚洲语言的 Smart Form,表格行内空间足够显示内容,但超过 31 个字符的文本会被系统压缩。

这条 Note 和前面两条的区别很关键。808326 和 908442 描述的是 Unicode 打印预览里的排版错乱与亚洲文本重叠,属于「字符位置算错」;1007575 描述的是「本来该换行的地方没换行,被压缩」,属于「换行决策出错」。同一个「看着挤在一起」的观感,背后是两套完全不同的机制。如果 31 字符这个特征没被抓到,就会一直在重叠那条线上打补丁,怎么打都不对。

4.2 复现与判定:31 字符是分水岭

要把这个判断做实,最省事的办法是造几组定长中文串,直接打到一张测试 form 上对比。常规做法是写个临时程序生成 30、31、32、40 个字符的四组文本,分别塞进同一个表格单元格里预览:

" 造定长中文测试串,验证 31 字符是不是临界点 DATA: lv_text TYPE string, lv_len TYPE i. DO 40 TIMES. lv_len = sy-index. CLEAR lv_text. DO lv_len TIMES. lv_text = |{ lv_text }中|. " 每个循环追加一个中文字符 ENDDO. WRITE: / lv_len, lv_text. ENDDO.

这段代码的意义不在于输出结果,而在于给出四组长度明确、内容可辨识的输入。把它们分别作为业务字段喂给 form,逐个看预览:30 和 31 正常,32 开始压缩,结论就锁定了。反过来,如果 32 个字符也正常、40 个字符才压缩,那就不是 31 字符阈值问题,得回到语言版本或字段宽度上重新判断。

这里有个容易踩的细节:长度的计算口径。中文在不同编码下字节数不同,按字节数判断长度会得出错的结论,必须按字符数算。Unicode 系统里对string类型用STRLEN得到的是字符数,可以放心用;非 Unicode 系统不能这么算,这条路径要单独验证。另外,如果文本里有全角空格、制表符、换行符,实际显示宽度和字符数也不完全等价,测试串尽量保持干净,别给自己加变量。

4.3 打补丁前后的兜底方案

确认是 1007575 之后,处理方式就是按流程实施这条 Note,然后重新验证。这里没有代码要改,属于系统层面的修正,实施后同样要在英文登录环境下再跑一次长文本预览,确认超过 31 字符的段落能正常换行。

但如果补丁一时上不去——比如测试系统和生产系统的补丁窗口对不上、或者这条 Note 和现有其他修正有冲突需要排队——业务又不能停,就得有兜底。兜底思路不是去骗过系统,而是在数据进 form 之前先把长文本手工折行,让每一行的字符数都落在阈值以内:

" 兜底:按字符数手工折行,保证每行不超过 p_len 个字符 FORM wrap_text USING p_src TYPE string p_len TYPE i CHANGING p_out TYPE string. DATA: lv_rest TYPE string, lv_line TYPE string. lv_rest = p_src. WHILE strlen( lv_rest ) > 0. lv_line = lv_rest(p_len). " 取前 p_len 个字符 lv_rest = lv_rest+p_len. " 剩余部分继续切 IF p_out IS INITIAL. p_out = lv_line. ELSE. p_out = p_out && cl_abap_char_utilities=>cr_lf && lv_line. ENDIF. ENDWHILE. ENDFORM.

p_len建议填 30 而不是 31,留一个字符的余量,避免边界情况和尾部空格叠加后再次触发压缩。cl_abap_char_utilities=>cr_lf是换行常量,用它而不是硬写字符,可以避免不同平台下的换行差异。这个方案的本质是「把排版决策从系统手里拿回到程序里」,代价是折行位置不一定在最合适的词边界上,中文没有空格分词问题不大,中英混排时可能出现英文单词被从中间切开,所以要评估一下业务能否接受。

折行方案的另一个代价是维护成本:一旦 Note 打上,这段兜底代码就变成了冗余逻辑,如果没清理,将来的折行位置会叠加系统的自动换行,看起来像新的排版 bug。我的处理习惯是,兜底代码一律加注释标明「因 Note 1007575 临时折行,补丁实施后移除」,并在补丁实施后立刻安排一次清理。

5. 避坑清单:这几处不改,改完还会复发

这类故障最气人的地方是「修好了又回来」。下面几条是反复验证过的高频复发点,每条都按现象、原因、解决写清楚,对号入座比从头排查快得多。

5.1 现象:改完调用程序,别的单据还是重叠

原因很常见——打印逻辑散落在多个程序里。有的走SSF_FUNCTION_MODULE_NAME动态取 FM,有的直接调用硬编码的 form 函数模块,只有你改的那一处补了control_parameters-langu,其他入口仍然留空,于是同一批单据里一半正常一半重叠,看起来像「偶发」。解决方式不是挨个去补,而是把打印收敛成一个公共方法:入参包含 form 名、语言、单据数据,内部统一填control_parameters,调用方不许自己拼结构。收敛之后,「忘记设语言」这件事在代码层面就不可能发生。

5.2 现象:补丁打过了,重叠照旧

原因是把表现当成根因。Note 808326 和 908442 修的是 Unicode 打印预览的排版与字符位置,而当前系统的实际问题在于语言版本选错——%header-langu被sy-langu顶替。补丁把一个并不存在的缺口补上了,真正的问题还在原地。解法是在 SNOTE 里确认状态之后果断停止在补丁上投入,进调试器看%header-langu的实际值,一分钟就能分辨。这条坑的本质是「症状匹配不等于根因匹配」,凡是靠关键词搜出来的 Note,都要用代码对象和服务端实际行为去验一遍。

5.3 现象:中文描述变成乱码

这是我在英文环境编辑 form 时遇到的,中文文本在英文环境打印出来是乱码。成因当时没找到明确结论,但处理方式很干脆:维护带中文内容的 form,就在中文环境里编辑;如果必须在英文会话里改,至少中文文本不要在那个会话里新增或修改。乱码和重叠是两件事,但会互相掩护——先看到乱码就容易忽略重叠,先把乱码修了才发现文字还是叠着,来回折腾。

5.4 现象:长文本挤成一团,按重叠去治没效果

原因是没量阈值。31 个字符是个硬分水岭,超过就压缩,这一点和语言版本无关,用「锁语言」的招数当然治不好。正确路径是先造 30、31、32、40 四组长度的测试串,看压缩从哪一组开始,确认命中 Note 1007575 之后走补丁流程,上不了补丁就用手工折行兜底。判断顺序反了,时间就白花了。

5.5 现象:SAPscript 表单出现同样的重叠

原因是只记住了 Smart Forms 的修法。SAPscript 打印同样要限制 form 的语言版本,只是入口不同——OPEN_FORM里有language参数。写法如下:

" SAPscript:同样要显式指定语言版本,别留给登录语言 CALL FUNCTION 'OPEN_FORM' EXPORTING form = 'ZSCRIPT_FORM' " 表单名 language = '1' " 1 = 中文,与 Smart Forms 口径一致 device = 'PRINTER' dialog = 'X' EXCEPTIONS canceled = 1 device = 2 form = 3 OTHERS = 4. IF sy-subrc <> 0. MESSAGE 'OPEN_FORM 失败,检查表单名与设备' TYPE 'E'. ENDIF.

language参数的口径和control_parameters-langu完全一致,都是系统内部单字符代码。凡是打印类程序,语言都应该是显式入参,而不是从会话里隐式继承——这是我从这堆坑里提炼出的唯一一条硬规矩。

6. 快速定位:三分钟把「重叠」拆成三条路径

再遇到中文打印重叠,我不会先搜 Notes,也不会先改代码,而是按下面这张对照表把现象先归类。归类错了,后面所有动作都是浪费。

现象特征第一嫌疑立刻要查的东西
中文环境正常、英文环境重叠语言版本未锁定生成 FM 里%header-langu的实际值
与登录语言无关,超 31 字符才挤Note 1007575 未实施用 30、31、32 三组串对比预览
中文变成乱码或方块编辑环境或输出字体form 的编辑会话语言、输出设备字体
同一批单据部分正常部分重叠多个打印入口参数不一致所有调用 form 的程序是否都传了control_parameters

归类之后就是验证。验证的核心动作只有一个:在调用程序里下断点,单步进入SSFRT_SET_LANGUAGES,看两件事——调用方传进来的control_parameters-langu是空还是有值,出来之后的%header-langu是什么。这两个值一对比,语言版本这条线立刻就能定性:入参空、出参等于sy-langu,就是本篇说的根因;入参有值、出参也一致,就别在这条线上耗,转去查字符长度阈值。

如果不想每次都单步,可以在SSFRT_SET_LANGUAGES上设一个外部断点,只在调试账号下触发,跑一次打印就能拿到现场。这里要提醒一句:这是标准函数模块,断点会影响所有经过它的调用,验证完必须立刻删掉,否则会把别人的打印业务挂住,这种事故比原故障更难解释。

最后一条习惯,也是被这些坑磨出来的:凡是打印类需求,从设计阶段就把语言当成必填参数写进接口,调用方不传就报错,绝不允许「不传就跟随登录语言」这种隐式默认存在。当年那句「中文环境打印就正常」,本质是sy-langu恰好等于 1 的巧合,靠巧合跑通的逻辑换个账号就会翻车,而且翻车时的现象和原故障长得一模一样,排查成本翻倍。从那以后我每次交付打印功能,都会强制走一遍「英文登录跑同一张单据」的验证,哪怕业务方说他们只用中文环境。希望帮到你。

本文还有配套的精品资源,点击获取