meta-imp-txt.mkiv / last modification: 2020-01-30 14:16
%D \module
%D   [       file=meta-txt,
%D        version=2000.07.06,
%D          title=\METAPOST\ Graphics,
%D       subtitle=Text Tricks,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

%D In this library some handy text manipulations are defined. Some can and will be
%D improved as soon as the \TEX||\METAPOST\ interface is stable. Some of the
%D solutions may look weird, which is entirely my fault, since I implemented them in
%D the process of getting grip on this kind of manipulations. Undoubtly better
%D \METAPOST\ code is possible, but my way of learning this kind of trickery happens
%D to be by \quote {trial and error} and \quote {look and feel} (as well as
%D identifying tricks in Hobby's code).

% textext ipv btex ... etex

% we need a proper prefix here

\unprotect

\definesystemvariable {sh}  % ShapedText .. todo: commandhandler

\unexpanded\def\setupshapetexts
  {\dodoubleempty\getparameters[\??sh]}

\setupshapetexts
  [\c!bodyfont=]

\startMPextensions
    loadmodule "text" ;
\stopMPextensions

\ifdefined\parwidth \else
    \newdimen\parwidth
    \newdimen\parheight
    \newdimen\parvoffset
    \newdimen\parhoffset
    \newcount\parlines
    \newtoks \partoks
    \newbox  \shapetextbox
    \newcount\parfirst
\fi

\unexpanded\def\startshapetext[#1]%
  {\global\newcounter\currentshapetext
   \global\setbox\shapetextbox\vbox\bgroup
     \switchtobodyfont[\@@shbodyfont]%
     \dontcomplain
     \hsize\parwidth
     \setuptolerance[\v!verytolerant,\v!stretch]%
     \scratchcounter\zerocount
     \scratchtoks\emptytoks
     \def\docommand##1%
       {\setbox\scratchbox\hpack{\useMPgraphic{##1}}%
        \global\parfirst\zerocount
        \getMPdata
        \setshapecharacteristics
        \advance\scratchcounter by \parlines
        \expandafter\appendtoks\the\partoks\to\scratchtoks}%
     \processcommalist[#1]\docommand
     \xdef\totalparlines{\the\scratchcounter}%
     \global\partoks\scratchtoks
     \parshape \the\scratchcounter \the\scratchtoks\relax
     \setshapecharacteristics % extra dummy
     \def\par{\endgraf\adaptparshape}%
     \everypar{\begstrut}}

\unexpanded\def\stopshapetext
  {\endstrut
   \egroup
   \global\newcounter\currentshapetext
   \getshapecharacteristics}

\unexpanded\def\adaptparshape
  {\def\docommand##1%
     {\ifcase\scratchcounter
        \expandafter\appendtoks\space##1 \to\scratchtoks
      \else
        \advance\scratchcounter\minusone
      \fi}%
   \scratchcounter\prevgraf
   \doglobal\decrement(\totalparlines,\scratchcounter)%
   \multiply\scratchcounter\plustwo
   \scratchtoks\emptytoks
   \expanded{\processseparatedlist[\the\partoks][\space]}\docommand
   \global\partoks\scratchtoks
   \parshape\totalparlines\the\partoks\relax}

\unexpanded\def\getshapecharacteristics
  {\doglobal\increment\currentshapetext
   \doifelsedefined{parlines:\currentshapetext}
     {\getvalue{parlines:\currentshapetext}}
     {\global\parlines  \plusone
      \global\parfirst  \zerocount
      \global\parvoffset\zeropoint
      \global\parhoffset\zeropoint
      \global\parwidth  \hsize
      \global\parheight \vsize}}

\unexpanded\def\setshapecharacteristics
  {\doglobal\increment\currentshapetext
   \setxvalue{parlines:\currentshapetext}%
     {\global\parlines  \the\parlines
      \global\parfirst  \the\parfirst
      \global\parvoffset\the\parvoffset
      \global\parhoffset\the\parhoffset
      \global\parwidth  \the\parwidth
      \global\parheight \the\parheight}}

\unexpanded\def\getshapetext % option: unvbox
  {\vbox\bgroup
   \forgetall
   \dontcomplain
   \setbox\scratchbox\vbox to \parheight
     {\switchtobodyfont[\@@shbodyfont]%
      \splittopskip\strutheight
      \vskip\parvoffset
      \ifcase\parfirst\else\vskip\lineheight\fi
      \hskip\parhoffset
      \hbox{\vsplit\shapetextbox to \parlines\lineheight}}%
   \wd\scratchbox\parwidth
   \ht\scratchbox\parheight
   \dp\scratchbox\zeropoint
   \box\scratchbox
   \getshapecharacteristics
   \egroup}

\doifundefined{RotFont}{\definefont[RotFont][RegularBold*default]}

\unexpanded\def\getfollowtoken#1%
 {\hbox\bgroup
    \strut
    \ctxlua{mp.follow_text(#1)}%
  \egroup}

\definefontfeature[mp:tp][liga=no]

\startMPdefinitions
    def mfun_follow_draw (expr alternative) =
        if unknown RotPath  : path    RotPath  ; RotPath  := origin ; fi ;
      % if unknown RotColor : color   RotColor ; RotColor := black  ; fi ;
        if unknown TraceRot : boolean TraceRot ; TraceRot := false  ; fi ;
        if unknown ExtraRot : numeric ExtraRot ; ExtraRot := 0      ; fi ;
        picture pic[] ;
        numeric len[] ; len[0] := 0 ;
        numeric n ; n := lua.mp.follow_size() ;
        for i=1 upto n :
            pic[i] := lua.mp.follow_slot(i) ;
            pic[i] := pic[i] shifted - llcorner pic[i] ;
            len[i] := len[i-1] + lua.mp.follow_width(i) ;
        endfor ;
        numeric al, at, pl, pc, wid, pos ; pair ap, ad ;
        al := arclength RotPath ;
        if al = 0 :
            al := len[n] + ExtraRot ;
            RotPath := origin -- (al,0) ;
        fi ;
        if al < len[n]:
           RotPath := RotPath scaled ((len[n]+ExtraRot)/al) ;
           al := arclength RotPath ;
        fi ;
        if alternative = 1 :
           pl := (al-len[n])/(if n>1 : (n-1) else : 1 fi) ;
           pc := 0 ;
        else : % centered / MP
           pl := 0 ;
           pc := arclength RotPath/2 - len[n]/2 ;
        fi ;
        if TraceRot :
           draw RotPath withpen pencircle scaled 1pt withcolor blue ;
        fi ;
        for i=1 upto n :
          % wid := abs(xpart urcorner pic[i] - xpart llcorner pic[i]) ;
            wid := lua.mp.follow_width(i) ;
            pos := len[i]-wid/2 + (i-1)*pl + pc ;
            at := arctime   pos of RotPath ;
            ap := point     at  of RotPath ;
            ad := direction at  of RotPath ;
            if mfun_trial_run :
                % skip (ok, somewhat inefficient as we can consider a
                % dedicated store and textext variant (todo)
            else :
                pic[i] := pic[i] shifted (-wid/2,0) rotated(angle(ad)) shifted ap ;
                draw pic[i] ; % withcolor RotColor ;
                if TraceRot :
                    draw boundingbox pic[i] withpen pencircle scaled .25pt withcolor red ;
                    draw ap withpen pencircle scaled .50pt withcolor green ;
                fi ;
            fi ;
        endfor ;
        if TraceRot :
            draw boundingbox currentpicture withpen pencircle scaled .25pt withcolor blue ;
        fi ;
    enddef ;
\stopMPdefinitions

\startluacode
    local context   = context

    local nodecodes = nodes.nodecodes
    local kerncodes = nodes.kerncodes

    local visible_code = {
        [nodecodes.glyph] = true,
        [nodecodes.glue]  = true,
        [nodecodes.hlist] = true,
        [nodecodes.vlist] = true,
        [nodecodes.rule]  = true,
    }

    local kern_code  = nodecodes.kern
    local c_userkern = kerncodes.userkern
    local a_fontkern = attributes.private("fontkern")

    local copynode = nodes.copy
    local freenode = nodes.free

    local topoints = number.topoints
    local mpprint  = mp.print

    local n = nil
    local s = 0

    function mp.follow_reset()
        for i=1,#n do
            freenode(n[i])
        end
        n = nil
        s = 0
    end

    function mp.follow_initialize(b)
        if not n then
            local head = tex.takebox(b).list
            if head then
                n = { }
                s = 0
                head = node.flatten_discretionaries(head)
                local current = head
                while current do
                    local id = current.id
                    if visible_code[id] then
                        s = s + 1
                        head, current, n[s] = nodes.remove(head,current)
                    elseif id == kern_code and current.subtype == c_userkern and not current[a_fontkern] then
                        s = s + 1
                        head, current, n[s] = nodes.remove(head,current)
                    else
                        current = current.next
                    end
                end
                nodes.flush_list(head)
            end
        end
    end

    function mp.follow_size()
        mpprint(s)
    end

    function mp.follow_slot(i)
        mpprint('textext("\\getfollowtoken{' .. i .. '}")')
    end

    function mp.follow_text(s)
        context(copynode(n[s]))
    end

    function mp.follow_width(i)
        mpprint(topoints(n[i].width))
    end
\stopluacode

\unexpanded\def\dofollowtokens#1#2%
  {\vbox\bgroup
   \forgetall
   \dontcomplain
   \setbox\scratchbox\hbox{\addff{mp:tp}#2}%
   \ctxlua{mp.follow_initialize(\number\scratchbox)}%
   \startMPcode
     \includeMPgraphic{followtokens} ;
     mfun_follow_draw(\number#1) ;
   \stopMPcode
   \ctxlua{mp.follow_reset()}%
   \egroup}

\unexpanded\def\followtokens        {\dofollowtokens\plusone}
\unexpanded\def\followtokenscentered{\dofollowtokens\zerocount}

% stretched variant:
%
% \followtokens
%   {This is just a dummy text, kerned by T{\kern
%    -.1667em\lower .5ex\hbox {E}}{\kern -.125emX} and typeset
%    in a circle using {\setMFPfont M}{\setMFPfont
%    E}{\setMFPfont T}{\setMFPfont A}{\setMFPfont
%    P}{\setMFPfont O}{\setMFPfont S}{\setMFPfont T}.\quad}

% centered variant:
%
% \def\followtokengraphicscale#1{%%
%   \startuseMPgraphic {followtokens}
%     path RotPath; RotPath :=  reverse halfcircle scaled #1 ;
%     draw RotPath ;
%     setbounds currentpicture to boundingbox fullcircle scaled 12cm ;
%   \stopuseMPgraphic}
%
% \startoverlay
%   {\followtokengraphicscale{12cm}%%
%    \followtokenscentered{There was question on the list about this kind of graphics.}}
%   {\followtokengraphicscale{10cm}%%
%    \followtokenscentered{And Marco patched followingtokens to handle a centered text.}}
%   {\followtokengraphicscale{8cm}%%
%    \followtokenscentered{That ended up as variant branch in the main macro.}}
%   {\followtokengraphicscale{6cm}%%
%    \followtokenscentered{So now we have two commands.}}
% \stopoverlay

% \followtokengraphicscale{6cm}
% \followtokens{Hans Hagen uses {\darkred\TeX}, {\darkgreen\Lua}, {\darkblue \MetaPost} and friends.}

\startuseMPgraphic{fuzzycount}
   begingroup
   save height, span, drift, d, cp ;
   height := 3/ 5 * \baselinedistance ;
   span   := 1/ 3 * height ;
   drift  := 1/10 * height ;
   pickup pencircle scaled (1/12 * height) ;
   def d = (uniformdeviate drift) enddef ;
   for i := 1 upto \MPvar{n} :
     draw
       if (i mod 5)=0 : ((-d-4.5span,d)--(+d-0.5span,height-d))
       else           : ((-d,+d)--(+d,height-d)) fi
       shifted (span*i,d-drift) ;
   endfor;
   picture cp ; cp := currentpicture ; % for readability
   setbounds currentpicture to
     (llcorner cp shifted (0,-ypart llcorner cp) --
      lrcorner cp shifted (0,-ypart lrcorner cp) --
      urcorner cp -- ulcorner cp -- cycle) ;
   endgroup ;
\stopuseMPgraphic

\setupMPvariables
  [fuzzycount]
  [n=10]

\unexpanded\def\fuzzycount#1%
  {{\tx\useMPgraphic{fuzzycount}{n=#1}}}

\defineconversion[fuzzy][\fuzzycount]

%%%%%%%

\setupMPvariables
  [EnglishRule]
  [height=1ex,
   width=\the\localhsize, % without \the, problems in non e-tex
   color=darkgray]

\defineblank
  [EnglishRule]
  [medium]

\startuniqueMPgraphic{EnglishRule}{height,width,color}
    x1 = 0 ; x3 = \MPvar{width} ; x2 = x4 = .5x3 ;
    y1 = y3 = 0 ; y2 = -y4 = \MPvar{height}/2 ;
    fill z1..z2..z3 & z3..z4..z1 & cycle withcolor \MPvar{color} ;
\stopuniqueMPgraphic

\unexpanded\def\EnglishRule
  {\startlinecorrection[EnglishRule]
   \setlocalhsize \noindent \reuseMPgraphic{EnglishRule}
   \stoplinecorrection}

%D The following macro returns a tight bound character sequence.
%D
%D \useMPlibrary[txt]
%D
%D \startlinecorrection
%D \TightText{\ss\bf 123}{0cm}{3cm}{red}
%D \stoplinecorrection

\unexpanded\def\TightText#1#2#3#4%
  {\hpack
     {\startMPcode
        picture p ; p := image (graphictext "#1" withfillcolor red) ;
        draw p xsized #2 ysized #3 withcolor \MPcolor{#4} ;
      \stopMPcode}}

\protect \endinput