1

METAPOST cutbefore and cutafter are not perfectly reliable in where they cut, if the path to be cut crosses the path where it must cut multiple times. To solve that' I've created two primarydefs (maxcutbefore and maxcutafter) that can be used just as cutbefore and cutafter, but that remove as much as possible. This works. This minimal example shows it:

primarydef inPath mycutafter cutPath =
  begingroup
  save resultPath; path resultPath;
  save tmpPath, checkPath; path tmpPath, checkPath;
  save aT;
  resultPath := inPath; % Algo: we return resultPath unless checkPath is an improvement
  forever:
    aT := arctime ((arclength resultPath)-1) of resultPath; % t where length is length minus 1bp
    checkPath := subpath( 0, aT) of resultPath;
    tmpPath := checkPath cutafter cutPath;
    if (arclength tmpPath) < (arclength checkPath):
      % we have actually cut the path further
      resultPath := tmpPath; % set current result value and try again
    else:
      % we did not cut the path further, we have our result
      exitif true;
    fi
  endfor;
  resultPath
  endgroup
enddef;

vardef TEST( expr inputConn, fromPicOutline, toPicOutline) =
  save resultConn, workingConn; path resultConn, workingConn;
  workingConn := inputConn;
%  if true:
    resultConn := (inputConn cutbefore fromPicOutline) mycutafter toPicOutline;
%  else:
%    show "HELLO";
%  fi
  resultConn
enddef;

path line, ca, cb;
ca := fullcircle scaled 2 shifted (-5,0);
cb := fullcircle scaled 2 shifted (5,0);
line := (-10,0)--(10,0);
line := TEST( line, ca, cb);
draw ca;
draw cb;
draw line;

As you can see, I'm using mycutafter. If you change that to cutafter, it also works, but the line is cut to the right of the right circle, not to the left.

But when I uncomment the if:--else:--fi lines, the vardef TEST fails with:

metapost log    > ! Extra 'else'.
metapost log    > TEST->...2)cutbefore(EXPR3))mycutafter(EXPR4);else
metapost log    >                                                   :show"HELLO";fi.resultConn...
metapost log    > <*> ...-10,0)--(10,0); line := TEST( line, ca, cb)
metapost log    >                                                   ; draw ca; draw cb; draw l...

But if I uncomment the lines and use the original cutafter, it also works. There must be something I do not understand regarding the grammar of METAFONT that makes my primarydef interfere with my vardef code. But what? And why?

4

We can reduce the problem to a much simpler file:

tracingall;
if true:
  forever:
    if false:
    else:
      exitif true;
    fi
  endfor;
else:
fi

results in

! Extra else

Why?

exitif true terminates the current loop immediately. Given that MetaPost is not parsing the file beforehand but picking up tokens at it goes, this means that the remaining part of the loop, including the inner fi, is never execute. So as far as MetaPost is concerned, the inner if false was never terminated by a fi so we are still in the else: block when the outer else: is seen. This of course is not allowed, leading to the error Extra else.

How can you fix that? As mentioned by Scott H. in a comment, you can use the same tricks as in MetaPost - How do I turn off 'end occured when if...' messages? to insert a fi before the exitif which is only seen if the else block is actually executed.

But the design of MetaPost actually anticipated such a need, that's why exitif accepts a condition: You can avoid the if by writing that break condition directly after exitif:

primarydef inPath mycutafter cutPath =
  begingroup
  save resultPath; path resultPath;
  save tmpPath, checkPath; path tmpPath, checkPath;
  save aT;
  resultPath := inPath; % Algo: we return resultPath unless checkPath is an improvement
  forever:
    aT := arctime ((arclength resultPath)-1) of resultPath; % t where length is length minus 1bp
    checkPath := subpath( 0, aT) of resultPath;
    tmpPath := checkPath cutafter cutPath;
    exitif length cuttings = 0;
    % if we reach this point we have actually cut the path further
    resultPath := tmpPath; % set current result value and try again
  endfor;
  resultPath
  endgroup
enddef;

vardef TEST( expr inputConn, fromPicOutline, toPicOutline) =
  save resultConn, workingConn; path resultConn, workingConn;
  workingConn := inputConn;
  if true:
    resultConn := (inputConn cutbefore fromPicOutline) mycutafter toPicOutline;
  else:
    show "HELLO";
  fi
  resultConn
enddef;

path line, ca, cb;
ca := fullcircle scaled 2 shifted (-5,0);
cb := fullcircle scaled 2 shifted (5,0);
line := (-10,0)--(10,0);

beginfig(0);
line := TEST( line, ca, cb);
draw ca;
draw cb;
draw line;
endfig;
end;

enter image description here

| improve this answer | |
  • Thank you! It remains difficult to wrap your mind around this kind of 'inline macro expansion' if you have been doing functions for much of the last 20 years. – gctwnl Apr 12 at 12:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.