Search code examples
postscript

How to justify multi-font line in PostScript


Justifying a line in PostScript is possible by calculating the SpaceMod and using widthhow:

/SpaceMod % calculated from line width and stringwidth
/h { 0 32 } def 

/Times-Roman findfont
14 scalefont setfont    
SpaceMod h (This line with single font is justified) widthshow

But how can we use this method for a line with different fonts? A simple example is to have a bold part in the line as

/Times-Roman findfont
14 scalefont setfont    
X h (How to) widthshow

/Times-Roman-Bold findfont
14 scalefont setfont    
X h justify this line) widthshow

/Times-Roman findfont
14 scalefont setfont    
X h (with multi fonts) widthshow

The problem is that we do not have a single string for the entire line to fetch stringwidth. Even if calculating it based on individual strings, how we can calculate/set SpaceMod?

Or we need to use another approach for justification of line with multi-fonts?


Solution

  • Brute force..

    /justifyshow {
        /linerem exch def
        dup length 3 idiv /nsub exch def
        /instring exch def
        instring aload pop
        /spct 0 def
        /setf {3 2 roll findfont 3 2 roll scalefont setfont } def
        nsub {
            setf dup
            stringwidth pop linerem exch sub /linerem exch def
            {32 eq { spct 1 add /spct exch def} if } forall
             } repeat
        /addsp linerem spct div def
        instring aload pop
    %cleanup
        [ /spct /linerem /instring ] { userdict exch undef } forall
    % comment following three lines to reverse argument order
        /ct nsub def
        nsub 1 sub { ct 1 sub /ct exch def ct 1 add 3 mul  3 roll } repeat
        userdict /ct undef
    %
        nsub { setf addsp 0 32 4 -1 roll  widthshow } repeat
    } def
    % usage [ fontn sizen stringn ...  font1 size1 string1 ]
    % linewidth justifyshow
    
    100 100 moveto
    [
    /Helvetica 12 (hot to justify)
    /HelveticaBold 24 ( large bold)
    /Helvetica 12 ( in part of a line)
    ] 400 justifyshow
    

    No doubt this can be tightend up a bit..

    Edit, another take as I said tightend up a good bit.

    % count spaces in a string
    /stringspace {0 exch { 32 eq { 1 add } if}  forall} def
    % set font routine
    /setf {3 2 roll findfont 3 2 roll scalefont setfont } def
    /justifyshow {
    % def array length leave line length and array on stack
    exch dup length 3 idiv /nsub exch def
    aload pop
    % split input array into array of [ font size string ] arrays
    /incc nsub 3 mul 1 add def
    [ nsub { incc 1 roll [ 4 1 roll ] incc 3 sub /incc  exch def } repeat  ] /fstringlist exch def
    % reverse sort string order (can comment out this line if needed )
    [ nsub 1 sub -1 0 { fstringlist exch get} for ] /fstringlist exch def
    % count spaces
    0 fstringlist {aload pop 3 1 roll pop pop stringspace add } forall
    % count total line length
    0 fstringlist {aload pop setf stringwidth pop add } forall
    % at this point the target length, actuial length and space count are on the stack
    % calculate space to add
    3 -1 roll sub neg exch div /addsp exch def
    % show strings
    fstringlist { aload pop setf addsp 0 32 4 -1 roll  widthshow } forall } def
    
    100 100 moveto
    [
    /Helvetica 12 (how to justify)
    /HelveticaBold 24 ( large bold)
    /Helvetica 12 ( in part of a line)
    /Helvetica 50 ( !!!)
    ] 400
    justifyshow