Hi Karl,

many thanks for the analysis and the proposed patch. I will take a close look and get back soon.

Regards,
Thanh


On Tue, Mar 24, 2020 at 10:29 PM Karl Berry <karl@freefriends.org> wrote:
Hello Thanh (please ack :), and everyone else - Robert (cc'd) recently
reported an "interesting" pdftex bug related to font expansion of vf
files on the tex-live list. For the record, his original report is here:
  https://tug.org/pipermail/tex-live/2020-March/045099.html
but I'll quote as needed to make this email self-contained. Sorry, it's
unavoidably long.

I have a proposed fix (at the end) and am hoping for some kind of
confirmation that it makes sense. (I am definitely not planning to
make any change here for TL20. It's not that urgent, and testing is needed.)

So, Robert found a case where pdftex mixes up two expanded fonts, when
virtual fonts are involved. Here is a test file vfexp.tex, slightly
modified from his original so it will run with -ini:

-----------------------------------------------------------------------------
\catcode`\{=1 \catcode`\}=2 \pdfoutput=1 \nonstopmode \hbadness=10000
\vsize=20pc \hsize=30pt \parfillskip=0pt plus1fil
\font\1=cmr10
\font\2=bchr8t
%\font\2=bchr8r
\pdfmapfile{}
\pdfmapline{+cmr10 CMR10 <cmr10.pfb}
\pdfmapline{+bchr8r CharterBT-Roman " TeXBase1Encoding ReEncodeFont " <8r.enc <bchr8a.pfb}
\pdfmapline{+bchr8r+20 CharterBT-Roman " 1.020 ExtendFont TeXBase1Encoding ReEncodeFont " <8r.enc <bchr8a.pfb}
\pdfmapline{+bchr8r-20 CharterBT-Roman " 0.980 ExtendFont TeXBase1Encoding ReEncodeFont " <8r.enc <bchr8a.pfb}
\pdffontexpand\1 100 100 5 %autoexpand
\pdffontexpand\2 20  20 20
\pdfadjustspacing=2
\1 ABC

\2 This is a paragraph with font expansion.
\end
-----------------------------------------------------------------------------

bchr8t is a virtual font (otherwise the problem does not happen, per the
commented-out \font\2 above), and we are not using autoexpand here (bug
also goes away with autoexpand), and so Robert has actual tfm files for
bchr8[rt][+-]20.tfm. (I'll also attach an archive with all files to
reproduce.)

Here are the errors from running (avoiding useless mktextfm calls)
  env TEXFONTS=.: MKTEXTFM=0 pdftex -ini -file-line-error vfexp.tex
now:
./vfexp.tex:12:! Font \csname\endcsname=cmr10+100 at 10.0pt not loadable: Metric (TFM) file no
./vfexp.tex:12:! Font \csname\endcsname=cmr10-100 at 10.0pt not loadable: Metric (TFM) file no
./vfexp.tex:17:! Font \csname\endcsname=bchr8r+20-100 at 10.0pt not loadable: Metric (TFM) fil
./vfexp.tex:17:! Font \csname\endcsname=bchr8r-20-100 at 10.0pt not loadable: Metric (TFM) fil

The first two, about failing to make cmr10+100 and cmr10-100 at the
first \pdffontexpand, are expected.  (Robert, to answer one of your
questions: although you're right that those fonts will never be used,
and the doc is not exactly clear on this point [I'll try to improve it],
the reality is that pdftex immediately tries to make fonts for the
stretch/shrink limit when it processes \pdffontexpand. I don't think
this could/should be changed.)

At the second \pdffontexpand line, it finds bchr8t.vf, and hence
bchr8r.tfm, and and the +20/-20.tfm files. All that is fine.

But then, when pdftex is generating the output (pdf_hlist_out, etc.), it
needs to use the expanded bchr8r. The necessary tfms all exist (included
in the attached) but it erroneously uses the max shrink (-100) from the
cmr10 font \1, and tries to make the nonsensical bchr8r+20-100.

I don't have any particular familiarity with this part of the code, but
tracing through the execution in the debugger, it seems the bug is
in vf_def_font, which calls set_expand_params (which makes the fonts)
with this call:
         set_expand_params(k, pdf_font_auto_expand[f],
                           pdf_font_expand_ratio[pdf_font_stretch[f]],
                           -pdf_font_expand_ratio[pdf_font_shrink[f]],
                           pdf_font_step[f], pdf_font_expand_ratio[f]);

The problem is that pdf_font_stretch[f] (and _shrink[f]) are both zero
at this point. These are references to another font, i.e.,
null_font. But the zeroth entry of pdf_font_expand_ratio is not the zero
for null_font, but the -100 for the specified cmr10 (which doesn't make
sense to me, but I took it as given).

FWIW, here are the various arrays at the critical point:
(gdb) ppool fontname[0]
$91 = "nullfont"
(gdb) ppool fontname[1]
$92 = "cmr10"
(gdb) ppool fontname[2]
$93 = "bchr8t"
(gdb) ppool fontname[3]
$94 = "bchr8t+20"
(gdb) ppool fontname[4]
$95 = "bchr8t-20"
(gdb) p pdffontstretch[0]@5
$96 = {0, 0, 3, 0, 0}
(gdb) p pdffontshrink[0]@5
$97 = {0, 0, 4, 0, 0}
(gdb) p pdffontexpandratio[0]@5
$98 = {-100, 0, 0, 20, -20}

In other places in the code, e.g., check_expand_pars, there is an
explicit check for pdf_font_stretch and _shrink being null_font (= 0),
so I added the same explicit checks to vf_def_font, which seemed to
solve the problem. pdffonts reports the expanded/shrunken fonts used:
$ pdffonts vfexp.pdf
name                                 type              emb sub uni object ID
------------------------------------ ----------------- --- --- --- ---------
QBBONQ+CMR10                         Type 1            yes yes no       4  0
DCAQPT+CharterBT-Roman-Extend_1020   Type 1            yes yes no       5  0
OXEYKB+CharterBT-Roman-Extend_980    Type 1            yes yes no       6  0

The same call to set_expand_params is in vf_expand_local_fonts, so
if the above is right, the same fix would be needed there. I could not
easily figure out how to exercise that code, though. Something like a vf
that refers to another vf?

I'll append the full patch I have.
Thanh, anyone else, confirm/deny/comments?

Robert, if you're willing, I could send you a new binary (x86_64-linux?)
for you to try. I presume your microtype work has more tests that could
go some way toward ensuring there's no unexpected side effect.

Thanks,
Karl

--- pdftex.web  (revision 822)
+++ pdftex.web  (working copy)
@@ -17519,13 +17519,21 @@
 procedure vf_expand_local_fonts(f: internal_font_number);
 var lf: internal_font_number;
     k: integer;
+    shrink_limit, stretch_limit: integer;
 begin
     pdfassert(pdf_font_type[f] = virtual_font_type);
     for k := 0 to vf_local_font_num[f] - 1 do begin
         lf := vf_i_fnts[vf_default_font[f] + k];
+       {Find stretch and shrink values, if any.}
+        if pdf_font_stretch[f] = null_font then stretch_limit := 0
+        else stretch_limit := pdf_font_expand_ratio[pdf_font_stretch[f]];
+       {Ditto for shrink.}
+        if pdf_font_shrink[f] = null_font then shrink_limit := 0
+        else shrink_limit := pdf_font_expand_ratio[pdf_font_stretch[f]];
+       {Now make the font(s) for |lf|.}
         set_expand_params(lf, pdf_font_auto_expand[f],
-                          pdf_font_expand_ratio[pdf_font_stretch[f]],
-                          -pdf_font_expand_ratio[pdf_font_shrink[f]],
+                          stretch_limit,
+                          -shrink_limit,
                           pdf_font_step[f], pdf_font_expand_ratio[f]);
         if pdf_font_type[lf] = virtual_font_type then
             vf_expand_local_fonts(lf);
@@ -17962,6 +17970,7 @@ vf_def_font
     s: str_number;
     ds, fs: scaled;
     cs: four_quarters;
+    stretch_limit, shrink_limit: integer;
 begin
     cs.b0 := vf_byte; cs.b1 := vf_byte; cs.b2 := vf_byte; cs.b3 := vf_byte;
     fs := store_scaled_f(vf_read_signed(4), font_size[f]);
@@ -17992,9 +18001,16 @@
             vf_local_font_warning(f, k, "design size mismatch");
     end;
     if (pdf_font_step[f] <> 0) then
+       {Find stretch and shrink values, if any.}
+        if pdf_font_stretch[f] = null_font then stretch_limit := 0
+        else stretch_limit := pdf_font_expand_ratio[pdf_font_stretch[f]];
+       {Ditto for shrink.}
+        if pdf_font_shrink[f] = null_font then shrink_limit := 0
+        else shrink_limit := pdf_font_expand_ratio[pdf_font_stretch[f]];
+       {Now make the font(s) for |k|.}
         set_expand_params(k, pdf_font_auto_expand[f],
-                          pdf_font_expand_ratio[pdf_font_stretch[f]],
-                          -pdf_font_expand_ratio[pdf_font_shrink[f]],
+                          stretch_limit,
+                          -shrink_limit,
                           pdf_font_step[f], pdf_font_expand_ratio[f]);
     vf_def_font := k;
 end;