☰
SAP Smart Forms 中文字体重叠:打印语言与31字符压缩排查
2026/11/2 16:14:12 网站建设 项目流程

简介:这份文档面向使用SAP Smart forms输出中文报表的ABAP开发与实施人员,聚焦表单打印预览中中文字体重叠、长文本被压缩的典型故障,给出从语言环境设置入手的排查与修复思路。压缩包仅含1个doc文件,约28KB,篇幅精炼,属于可直接查阅的排错笔记型资料。文中围绕FM 'SSFRT_SET_LANGUAGES'中control_parameters-langu的赋值逻辑展开,说明%header-langu如何决定表单打印的语言版本,并提示英文登录环境下借用中文版本表单导致重叠的成因;同时关联SAP Note 808326、908442与1007575,分别对应Unicode打印预览异常与超过31字符的亚洲字符压缩现象。读者可据此快速定位问题所在,掌握在FM中显式传入langu = 1的处置方式,并判断系统是否已打相应补丁。目前已有1088人学习,适合需要处理多语言打印兼容问题的SAP从业者参考。

1. 英文账号打中文单据:Smart Forms 中文字体重叠的第一现场

月末出库那天,一位同事用英文账号登录,打一张中文送货单,预览里两行汉字直接叠成了一团——这就是很典型的 Smart Forms 中文字体重叠。他截图发过来时,我第一反应是打印机驱动,查了半小时才发现根子在语言版本上。这篇笔记整理的就是我自己从现象走到修复的那条链路:登录语言E和1的差别、SSFCTRLOP-LANGU这个字段、生成的 FM 里那段SSFRT_SET_LANGUAGES调用,以及超过 31 个字符被压缩的第二个坑。适合正在做单据中文化、被预览重叠或乱码折腾过的 ABAP 从业者,也适合接手别人单据程序、需要判断「该改代码还是打 Note」的人。

2. 追根因:从 SSFCTRLOP 到 SSFRT_SET_LANGUAGES 的语言决策链

2.1 登录语言与打印语言不是一回事

很多人把这两件事混成一个,于是排错方向从一开始就偏了。登录语言是会话级的sy-langu,你在 SAP GUI 里用E登录,sy-langu就是E;用1登录,sy-langu就是1。它影响的是界面文本、消息、日期格式这些东西。

而打印语言是单据级的,它决定系统去取这个 Smart Form 的哪个语言版本、按哪套字符宽度规则排版。这两者默认会挂钩——你不显式指定,系统就拿sy-langu去顶——但完全可以也应该拆开。一张给中国客户看的中文送货单,不管是谁、用哪个语言登录、在哪个时区跑后台作业,打出来都必须是中文版式,这才叫行为确定。

我一般把这件事讲成一句话:登录语言是「谁在看屏幕」,打印语言是「纸上印什么字」。单据程序里凡是把这两者划等号的写法,早晚要出事。

这里还有个前置条件容易被忽略:Smart Form 本身要有中文语言版本。表单在创建时带语言属性,跨语言要靠翻译流程把文本元素、样式迁过去。如果在E环境里编辑表单、直接把中文写进文本元素,存下来的东西在打印时经常变乱码,这个我后面在避坑那章细说。原文作者也提到过这个现象,成因没查明,但规避办法很明确——中文内容就在中文环境里编辑。

提示:判断表单有哪些语言版本,最直接的办法是在中文环境和英文环境里分别打开同一个表单,看文本元素是否都有内容;缺失的那一边通常就是空壳。

2.2 生成的 FM 里那 350 行:一次断点省下三天

Smart Form 保存激活后会被生成成一个 ABAP 函数模块,名字形如/1BCDWB/SF00000xxx。业务程序一般不直接调它,而是先SSF_FUNCTION_MODULE_NAME把表单名换成 FM 名,再调这个 FM。要看清楚内部逻辑,就把这个 FM 名拿到 SE37 里打开源码。

生成的 FM 主流程前面一大段是初始化、取全局设置,真正跟语言相关的代码大概在 350 行往后(具体行号随表单节点数量浮动)。我把它贴出来,这段是整个问题的核心:

" 生成的 Smart Form FM 主流程中,决定打印语言版本的代码段 " 位置通常在 350 行之后,随表单节点数量上下浮动 call function 'SSFRT_SET_LANGUAGES' exporting i_control_parameters = control_parameters " 调用方传进来的控制参数 i_masterlanguage = '1' " 表单主语言:1 = 中文 i_thrulang = ' ' " 直通语言,通常留空 i_install_lang = ' ' " 安装语言,通常留空 importing o_languages = %languages. " 返回可用的语言集合 %header-langu = %languages-langu1. " 关键赋值:写进输出头

逐项说一下。i_control_parameters就是你从调用方传进来的SSFCTRLOP结构,它是外部唯一能影响语言决策的入口。i_masterlanguage = '1'表示这个表单的主语言是中文,这个值在表单生成时就固化了,不要动。i_thrulang和i_install_lang是给特殊场景用的透传和安装语言,一般留空即可。

真正起作用的是最后一行:%header-langu。这个变量类型是SSFCTRLOP里的语言字段,它决定了后续所有输出节点按哪套规则算字符宽度、去取哪个语言版本的文本。换句话说,前面那一堆参数都是输入,%header-langu才是那个拍板的。

2.3 三元表达式:为什么你的赋值「没生效」

再往里走一层,看SSFRT_SET_LANGUAGES内部是怎么算langu1的。逻辑很朴素,等价写法如下:

" SSFRT_SET_LANGUAGES 内部的语言取值逻辑(结构等价写法,便于理解) if control_parameters is initial. " 调用方没传,或者传了个空结构:退回当前会话登录语言 o_languages-langu1 = sy-langu. else. " 调用方传了具体语言:用调用方指定的 o_languages-langu1 = control_parameters-langu. endif.

问题就出在这个is initial判断上。control_parameters是SSFCTRLOP结构,判断「是否初始」看的是整个结构的所有字段是不是都为空。而很多业务程序在调表单时,压根没给control_parameters-langu赋过值——结构里其他字段(设备、预览标志之类)可能有值,但langu是空的。严格说这种情况结构整体不为初始,走的是else分支,但control_parameters-langu本身还是空,%header-langu拿到一个空值后系统会再做一次兜底,最后还是落回sy-langu。

所以表现出来的现象就是:你以为control_parameters传了,语言就定了;实际上langu那个字段从头到尾没人管,系统一路兜底兜到了登录语言上。在1环境里兜回1,一切正常;在E环境里兜回E,系统去找表单的英文版本,找不到就退回唯一可用的中文版本,但排版仍然按目标语言的宽度规则算——一个汉字的宽度没有按双字节处理,后面的字往前挤,就叠在一起了。

这也是为什么「在中文环境打印没事,在英文环境打印重叠」这种看似玄学的现象能稳定复现:不是随机 bug,是语言字段在不同环境里兜底兜出了不同结果。

2.4 重叠和乱码是两码事,别混着治

排这类问题时,先把症状分成两类,能省掉大量试错。

乱码是字符集层面的问题:字形映射错了,屏幕上出现方块、问号、乱七八糟的符号。根因通常在代码页、字体、前端 GUI 的字符集设置,或者表单的文本元素本身存坏了。

重叠是排版层面的问题:字形是对的,你认得出来那是哪些字,只是位置叠在一起、挤成一坨。根因在字符宽度计算——排版引擎认为这个字符占一个单位宽,实际渲染占两个,于是后续内容全往前挪。

这两种症状的处理路径完全不同。乱码要去查字体和代码页,重叠要去查打印语言和宽度计算。我见过有人为了治重叠去换打印机驱动、改前端字体,折腾两天没进展,就是因为从一开始就把症状归错了类。看到「字认得出来但位置不对」,直接往语言字段上想。

3. 改代码:control_parameters-langu 该在哪一行赋值

3.1 首选在调用点赋值,不要改生成的 FM

最直接的想法是打开那个生成的 FM,把control_parameters-langu = '1'写死在里面。能跑通,但这是个陷阱:表单每次在事务码里激活,生成的 FM 都会重新生成,你手改的代码会被整个覆盖掉。下一次有人动了表单布局、重新激活,问题原样回来,而且没人记得曾经改过。

正确的做法是在调用方赋值。业务程序在调表单之前,显式把langu钉死:

" 在调用 Smart Form 的业务程序里,显式锁定打印语言 data: lv_fm type rs38l_fnam, ls_ctrl type ssfctrlop, ls_opt type ssfcompop, ls_ret type ssfcrescl. " 关键一行:不管当前是谁登录、sy-langu 是什么,都打中文版 ls_ctrl-langu = '1'. ls_ctrl-device = 'PRINTER'. ls_ctrl-no_dialog = 'X'. " 后台/批量场景不弹对话框 ls_ctrl-preview = 'X'. " 测试期打开,上线前按需去掉 ls_opt-tddest = 'LP01'. " 输出设备,按现场实际替换 ls_opt-tdcopies = 1. " 先把表单名换成生成的 FM 名 call function 'SSF_FUNCTION_MODULE_NAME' exporting formname = 'ZSF_DELIVERY_NOTE' importing fm_name = lv_fm exceptions no_form = 1 others = 2. if sy-subrc <> 0. " 表单不存在或未激活,记日志退出,不要静默吞掉 return. endif. call function lv_fm exporting control_parameters = ls_ctrl " 语言就靠这个结构带进去 output_options = ls_opt user_settings = ' ' " 表单接口上如果有自定义参数,在这里一并传 importing job_output_info = ls_ret exceptions formatting_error = 1 internal_error = 2 send_error = 3 user_canceled = 4 others = 5.

几个地方值得多说两句。ls_ctrl-langu = '1'必须写在call function lv_fm之前,因为它在exporting里作为整体结构传进去,调完再改就晚了。no_dialog = 'X'在批量打印和后台作业里是必须的,否则每条单据弹一个对话框,作业直接卡死。preview = 'X'只在验证阶段打开,正式跑批时要去掉,不然 spool 里什么都不出。

SSF_FUNCTION_MODULE_NAME的sy-subrc检查也别省。表单没激活、名字拼错,这个调用会返回非零,后面拿一个空 FM 名去call function会直接 dump,排错时很难一眼看出根子在哪。

3.2 SSFCTRLOP 常用字段对照

SSFCTRLOP是控制参数结构,字段不多,但每个都影响输出行为。下面这张表是我自己整理的高频字段,实际用的时候按场景挑:

字段作用建议值说明
LANGU打印语言版本'1'中文单据的核心字段,必须显式赋值
DEVICE输出设备类型'PRINTER'/'SCREEN'决定走打印通道还是屏幕预览
NO_DIALOG抑制对话框'X'后台作业、批量打印必须置位
PREVIEW预览模式测试期'X'正式跑批时清空,否则不产生 spool
GETOTF返回 OTF 数据调试期'X'置位后可从结果结构里取 OTF 行表,用于离线核对
NEW_LIST_ID新建列表标识按需连续打印多张单据时用于区分

配上SSFCOMPOP(输出选项)一起用,后者管打印机、份数、立即打印这些物理输出参数。两者是分开的两个结构,语言只在SSFCTRLOP里,别找错地方。

3.3 走输出控制(NACE)的场景怎么改

不是所有单据都由自开发的报表程序直接调表单。走输出控制的那类,调用链是「业务凭证保存 → 输出确定 → 打印程序 → Smart Form」,语言是从输出确定里的通信语言带过来的,中间还隔着一段标准逻辑。

这种场景下改法不一样:找到该输出类型对应的打印程序(在输出类型的处理例程里配的那个),在程序开头、调表之前的初始化位置,把control_parameters-langu显式赋值。如果打印程序是抄的标准程序改的,注意看它有没有把外部传进来的SSFCTRLOP一路透传到表单调用,中间任何一层把结构重新初始化,语言就丢了。

如果同一个输出类型要支持多语言客户——比如同一批单据既打中文版又打英文版——那就不能写死'1',而是从业务数据里取语言(客户主数据的语言、或者单据头上的语言字段),赋给control_parameters-langu。这时候表单本身需要有对应语言的翻译版本,否则指定了语言也取不到内容。

3.4 传输与语言版本的维护

代码改完、测试通过,别急着传输。表单的语言版本是跟着表单对象走传输的,如果只在开发机的中文环境里创建了文本元素,传输到测试机后,如果测试机的语言配置跟开发机不一致,可能又变成空壳。

我一般会在传输前做两件事:一是把表单在中文环境下完整打开一遍,确认所有文本元素、窗口、表格都有内容;二是传输到目标系统后,第一时间用英文账号跑一次预览,不要等到业务验收才发现。这一步花五分钟,能挡掉后面一堆返工。

4. 第二个坑:31 字符压缩与三条 Note 的边界

4.1 重叠修好了,长文本又挤成一团

语言字段改完之后,重叠确实没了,但多测几轮又冒出个新问题:字符数少的行正常换行,字符数多的行反而挤在一起,字与字之间几乎没有间隙,看起来像是被横向压扁。

我一开始以为是自己改坏了,回滚重测,现象一样。后来拿不同长度的测试数据逐条试,边界很清晰:31 个字符以内正常,超过 31 个字符开始压缩。这个「31」太整齐了,不像业务逻辑算出来的,更像某个缓冲区长度。

到补丁查询平台上按「Asian characters compressed」这个方向找,命中一条:Note 1007575,症状描述是亚洲字符在 Unicode 系统的打印预览中被压缩,条件是单行空间足够显示但字符数超过 31。跟实测完全对得上。

这类问题的判断方法很朴素:先找出准确的触发边界,再拿边界值去反查。与其漫无目的地翻源码,不如花十分钟做一组长度递增的测试数据,把临界点钉死。31 这个数字本身就是最好的检索词。

4.2 三条 Note 各自管什么

把这次涉及的三条 Note 列一下,边界别搞混:

Note症状触发条件处理方式
808326Unicode 系统下打印预览文字格式错乱,字符可能重叠或间距过大Unicode 系统,SAPscript / Smart Forms 预览打补丁;同时检查语言版本设置
908442Unicode 系统下亚洲文字在预览中重叠Unicode 系统,亚洲语言内容打补丁;与上一条常需一起看
1007575亚洲字符在预览中被压缩单行空间充足,字符数超过 31打补丁

需要提醒的是,前两条是「重叠」方向,第三条是「压缩」方向,症状相似但成因不同。我手上的系统在打前两条之前就已经在补丁列表里了,所以重叠问题打补丁没用——这一点很关键,不要看到症状对得上就直接认定是补丁缺失。先确认当前系统的补丁状态,再决定是打补丁还是查自己的代码。

确认补丁状态的办法:在补丁管理相关的事务里按 Note 编号查实施记录,或者在系统里直接看该 Note 的修正对象是否已存在。如果修正代码已经在系统里,那问题百分百不在补丁上,往调用参数上找。

4.3 用 GETOTF 自己验证,不靠肉眼

预览和实际打印的行为有时不完全一致,尤其是换行位置。想拿到确定性结论,把输出结果落成 OTF 数据下来看,比盯着屏幕靠谱:

" 把排版结果抓到内存里,逐行核对换行位置 data: lt_otf type tsfotf. clear ls_ctrl-preview. ls_ctrl-getotf = 'X'. " 置位后结果结构里带 OTF 数据 " ……此处执行 call function lv_fm…… if ls_ret-otfdata is not initial. lt_otf = ls_ret-otfdata. " 逐行看文本内容与换行位置,确认长文本是否被横向压缩 cl_demo_output=>display( lt_otf ). endif.

GETOTF = 'X'之后,job_output_info(类型SSFCRESCL)里的OTFDATA字段会带上完整的 OTF 行表。OTF 里每一行都带命令码和文本内容,长文本在哪断行、断在哪个字符之后,一目了然。这个办法在调试换行和压缩类问题时特别好用,因为它绕过了打印机驱动、前端渲染这些变量,你看到的纯粹是系统算出来的排版结果。

注意:GETOTF只在调试和验证阶段打开,正式跑批时关掉,它会额外占内存,大数据量打印时有影响。

4.4 换行和窗口宽度要配合着调

打完补丁之后,如果还有个别字段换行位置不理想,那多半不是补丁的事,是窗口宽度和文本元素设置的问题。

常见做法是把输出节点的窗口宽度设成能容纳预期字符数的值,同时确认文本元素上「自动换行」这类属性是打开的。窗口太窄,中文按双字节算能放下的字数比预期少;窗口太宽,又容易跟相邻窗口重叠。这块没有通用参数,得按实际单据的版式一点点调,调完用上面那个 OTF 的办法验证断行位置。

5. 避坑与排查:五条血泪记录

5.1 改完代码没反应,重新激活表单后问题回来了

现象:在生成的 FM 里写了control_parameters-langu = '1',测试通过,过了两周有人调了表单布局,问题原样复现。

原因:Smart Form 每次激活都会重新生成对应的 FM,手改的代码被整体覆盖。生成的 FM 是衍生物,不是可维护对象。

解决:所有改动都放在调用方,或者放在不会被覆盖的初始化位置。判断标准很简单——这个位置在表单重新激活后还在不在。改配料表可以,改烤出来的蛋糕不行。

5.2 在英文环境编辑表单,中文内容打出来是乱码

现象:登录语言是E,在表单里直接输中文文本元素,保存激活都没报错,打印预览时部分中文变成方块或乱码。

原因:表单编辑器按当前登录语言的代码页解析输入内容,非当前语言的字符在存储和回读时可能失真。具体成因我没完全查清,但复现是稳定的。

解决:中文文本内容一律在中文环境下编辑。如果表单需要在多个语言环境里维护,就按语言版本分别处理,不要在一个环境里混着录。这条当成硬规矩执行,别图省事。

5.3 打了补丁还是重叠,白折腾半天

现象:症状跟 Note 描述一模一样,申请打补丁、重启、重测,一点变化没有。

原因:补丁早就打过了。系统的补丁级别比你以为的新,问题根本不在标准代码上,在自己的调用参数里。

解决:动手之前先查补丁状态。花两分钟确认 Note 的修正对象是否已存在于系统,能避免一整轮无效操作。这个顺序我后来固定下来了:先确认补丁、再查调用参数、最后才怀疑标准逻辑。

5.4 预览正常,实际打印出来错位

现象:屏幕上看着好好的,纸打出来字符间距不对,或者换行位置跟预览不一样。

原因:预览走的是前端渲染,实际打印走的是 spool 加打印机设备类型,两者的字符宽度处理可能不同。设备类型选错、字符集不匹配,都会让预览和实际输出产生偏差。

解决:验证必须覆盖实际打印这一环,不能只看预览。用GETOTF把排版结果抓下来核对,能排除掉渲染层的影响,看到的才是系统真实算出来的东西。同时确认输出设备类型跟现场打印机匹配。

5.5 后台作业打印全部叠在一起,前台单跑没事

现象:手工执行单据打印完全正常,挂到后台作业里批量跑,中文全部重叠。

原因:后台作业的会话语言跟交互会话不一样,sy-langu可能取到跟预期不同的值。如果代码里没显式指定langu,兜底逻辑在后台环境下兜出来的结果跟前台不一致。

解决:还是那句话,打印语言永远显式指定,不依赖任何兜底。这也是我把ls_ctrl-langu = '1'放在所有表单调用之前的原因——它必须是代码里写死的常量,不能是环境推导出来的变量。顺带把no_dialog = 'X'也一起带上,避免后台作业卡在对话框上。

6. 把验证做成清单:一次改完,三处回归

改这种跨环境的排版问题,最怕的是「在我这儿好了」。同一条代码,中文账号跑正常、英文账号跑重叠、后台作业跑又是另一个结果,三个通道各测一遍才算数。我后来固定了一套回归清单,改动完成后照着一项项勾:

验证项操作方式通过标准
中文账号预览sy-langu = '1'登录,preview = 'X'执行中文不重叠,超宽自动换行
英文账号预览sy-langu = 'E'登录,同样执行与中文账号结果一致,不再出现宽度挤压
后台批量打印no_dialog = 'X',挂作业跑 20 条以上spool 中逐条检查,无重叠无压缩
长文本边界构造 30、31、32、40 字符的中文数据各一条31 以内正常,超过 31 不出现横向压缩
实际纸张输出挑一张最长的单据真打断行位置与 OTF 结果一致

清单里最容易被跳过的是最后一项。预览看着好、OTF 看着好,纸打出来不对的情况我遇到过不止一次,设备类型和字符集这两个变量只有真打才能暴露。所以哪怕只打一张,也要打。

另外一个习惯是:验证长文本时不要只用一条数据,按 30、31、32 这几个临界值各造一条。31 这个数字是边界,边界附近的行为最容易反复,补丁打没打全、窗口宽度调没调到位,在这几个值上会立刻现形。这比随机抓几条业务数据去试靠谱得多,因为业务数据的长度分布是随机的,你未必能撞上临界点。

GETOTF那一段代码我一般会留一个临时版本在程序里,用注释块包起来,验证时放开、上线前注释掉。这样下次有人报同类问题,不用重新写一遍调试代码,改个开关就能抓到排版结果。

说到底,这个问题的根子不在什么高深机制上,就在于一个语言字段没人管、系统一路兜底兜到了登录语言。但正因为它藏得深、表现又随环境变化,光靠看代码很难发现,必须靠断点跟进去、靠边界值试出来、靠清单回归住。从那以后我每次接手带打印输出的单据程序,第一件事都是翻到表单调用那一行,确认control_parameters-langu有没有被显式赋值——不管当前环境看起来多正常,这一步都强制走一遍。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询