/*************************************************
*    The PMW Music Typesetter - 3rd incarnation  *
*************************************************/

/* Copyright (c) Philip Hazel, 1991 - 2020 */

/* Written by Philip Hazel, starting November 1991 */
/* This file last modified: November 2020 */


/* This file contains code for outputting text items */

#include "pmwhdr.h"
#include "pagehdr.h"
#include "outhdr.h"




/*************************************************
*           Output vocal underlay extension      *
*************************************************/

/* If the font has no underline, do nothing. There's also a global flag that
suppresses extenders.

Arguments:
  x0          x coordinate of start of extension
  x1          x coordinate of end of extension
  y           y coordinate of the extension
  fontsize    the size of font

Returns:      nothing
*/

void
out_extension(int x0, int x1, int y, int fontsize)
{
int i;
int uwidth = font_stringwidth(US"_", font_rm, fontsize);
int length = x1 - x0;
int count, remain;
uschar s[256];

if (!curmovt->underlayextenders || uwidth <= 0) return;
count = length/uwidth;
remain = length - count*uwidth;

if (count <= 0)
  {
  if (length > (uwidth*3)/4) { count = 1; remain = 0; } else return;
  }

y = out_ystave - ((y + curmovt->extenderlevel) * main_stavemagn)/1000;
x0 += main_stavemagn;

for (i = 0; i < count; i++) s[i] = '_';
s[count] = 0;
ps_string(s, font_rm, fontsize, &x0, &y, FALSE);

/* Deal with a final part-line */

if (remain >= uwidth/5)
  {
  x1 -= uwidth;
  ps_string(US"_", font_rm, fontsize, &x1, &y, FALSE);
  }
}


/*************************************************
*           Output vocal underlay hyphens        *
*************************************************/

/* This is very heuristic. If the width is very small, output nothing. If less
than the threshold, output a single, centred hyphen. Otherwise output hyphens
spaced at 1/3 of the threshold, centred in the space. Take care not to have
spaces that are too small at the ends. Ensure that a continuation hyphen at the
start of a line is always printed, moving it left if necessary.

Arguments:
  x0          x coordinate of start of extension
  x1          x coordinate of end of extension
  y           y coordinate of the extension
  fontsize    the size of font
  contflag    TRUE if continuing from the previous line

Returns:      nothing
*/

void
out_hyphens(int x0, int x1, int y, int fontsize, BOOL contflag)
{
uschar *hyphen = curmovt->hyphenstring;
int unit = curmovt->hyphenthreshold/3;
int hwidth = string_width(hyphen, font_rm, fontsize);
int width = x1 - x0;
int minwidth = 800 + (hwidth - font_stringwidth(US"-", font_rm, fontsize));

DEBUG(("out_hyphens() start\n"));

if (contflag) minwidth += 3200;

if (width < minwidth)
  {
  if (!contflag) return;
  width = minwidth;
  x0 = x1 - width;
  }

y = out_ystave - (y * main_stavemagn)/1000;

/* Deal with the case when the width is less than the threshold */

if (width < curmovt->hyphenthreshold)
  {
  if (contflag)
    out_string(hyphen, font_rm, fontsize, x0, y, 0);

  if (!contflag || width > unit)
    {
    out_string(hyphen, font_rm, fontsize, (x0 + x1 - hwidth)/2, y, 0);
    }
  }

/* Deal with widths greater than the threshold */

else
  {
  int i;
  int count = width/unit;      /* count is the number of gaps */

  if (width - count*unit < unit/2) count -= 2; else count--;
  if (width - count*unit > unit) unit += unit/(3*count);

  if (contflag)
    {
    if (width - count*unit > (3*unit)/2) count++;
    }
  else x0 += (width - count*unit - hwidth)/2;

  /* We have special code that generates a "widthshow" command to do the whole
  thing with one string. However, we can do this only if the hyphen string
  contains no escapes. One day, extend this to expand the escapes; for the
  moment, just do it if the hyphen string is a single minus. */

  if (Ustrcmp(hyphen, "-") == 0)
    {
    int hylen = Ustrlen(hyphen);
    int swidth = font_stringwidth(US" ", font_rm, fontsize);
    uschar s[256];
    uschar *pp = s;
    while (count-- >= 0)
      {
      Ustrcpy(pp, hyphen);
      pp += hylen;
      *pp++ = ' ';
      }
    *pp = 0;
    ps_wtext(s, font_rm, fontsize, x0, y, unit - hwidth - swidth);
    }

  /* Otherwise we have to output each hyphen individually */

  else for (i = 0; i <= count; i++)
    {
    out_string(hyphen, font_rm, fontsize, x0, y, 0);
    x0 += unit;
    }
  }

DEBUG(("out_hyphens() end\n"));
}


/*************************************************
*           Output repeating string              *
*************************************************/

/* This function fills up the space with repeat copies of the string from the
htypestr. These are customized hyphen fillers, specified as extra strings for
"underlay" or "overlay" that is in reality something else, such as a 8va
marking.

Arguments:
  x0          x coordinate of start of extension
  x1          x coordinate of end of extension
  y           y coordinate of the extension
  contflag    TRUE if continuing from the previous line
  eolflag     TRUE if continuing on to the next line
  htype       the number of the htypestr that has the relevant strings

Returns:      nothing
*/

void
out_repeatstring(int x0, int x1, int y, BOOL contflag, BOOL eolflag, int htype)
{
htypestr *h = main_htypes;
int width = x1 - x0;
int count, scount, slen, swidth;
int unscaled_fontsize, fontsize;
uschar *s;
uschar buff[256];

DEBUG(("out_repeatstring() start\n"));

while (--htype > 0 && h != NULL) h = h->next;
if (h == NULL) error_moan(ERR91, out_bar, out_stave);  /* Hard error */

unscaled_fontsize = ((curmovt->fontsizes)->fontsize_text)[h->size1];
fontsize = mac_muldiv(main_stavemagn, unscaled_fontsize, 1000);

s = h->string1;
slen = Ustrlen(s);
swidth = string_width(s, h->font, fontsize);

/* Deal with special string at continuation line start */

if (contflag && h->string2 != NULL)
  {
  int unscaled_fontsize2 = ((curmovt->fontsizes)->fontsize_text)[h->size2];
  int fontsize2 = mac_muldiv(main_stavemagn, unscaled_fontsize2, 1000);
  int xw = string_width(h->string2, h->font, fontsize2);
  out_string(h->string2, h->font, fontsize2, x0,
    out_ystave - (y * main_stavemagn)/1000, 0);
  width -= xw;
  x0 += xw;
  }

/* Allow for special terminating string (but not at eol) */

if (!eolflag && h->string3 != NULL)
  width -= string_width(h->string3, h->font, fontsize);

count = width/swidth;
scount = 255/slen;
y = out_ystave - ((y + h->adjust) * main_stavemagn)/1000;

while (count > 0)
  {
  uschar *p = buff;
  int nx0 = x0;
  int i;

  for (i = 0; i < scount; i++)
    {
    Ustrcpy(p, s);
    p += slen;
    nx0 += swidth;
    if (--count <= 0) break;
    }

  out_string(buff, h->font, fontsize, x0, y, 0);
  x0 = nx0;
  }

if (!eolflag && h->string3 != NULL)
  out_string(h->string3, h->font, fontsize, x0, y, 0);

DEBUG(("out_repeatstring() end\n"));
}


/*************************************************
*            Handle a text item                  *
*************************************************/

/* This procedure is called after the note has been set up for printing, so
that the max && min pitches etc. are known. It must be called before out_moff
is reset! When text is at the end of a bar, "atbar" is true.

Arguments:
  p             the text item
  atbar         TRUE if at end of a bar

Returns:        nothing
*/

void
out_text(b_textstr *p, BOOL atbar)
{
uschar ss[256];
uschar *s = p->string;

int flags = p->flags;
BOOL above = (flags & text_above) != 0;
BOOL endalign = (flags & text_endalign) != 0;
BOOL postscript = (flags & text_ps) != 0;
BOOL rehearse = (flags & text_rehearse) != 0;
int stringwidth;
int six = 6*main_stavemagn;
int x = n_x - out_Xadjustment;
int y = above? 20000 : -10000;
int unscaled_fontsize = ((curmovt->fontsizes)->fontsize_text)[p->size];
int fontsize = mac_muldiv(main_stavemagn, unscaled_fontsize, 1000);
int *matrix = rehearse? (curmovt->fontsizes)->fontmatrix_rehearse :
  ((curmovt->fontsizes)->fontmatrix_text)[p->size];

DEBUG(("out_text()%s: %s\n", ((flags & text_ul) != 0)? " underlay" :"", s));

/* If font is transformed, set the matrix, and then find the complete string
width before any rotation (used for centring and right-aligning). */

if (matrix != NULL) memcpy(font_transform, matrix, 4*sizeof(int));
stringwidth = string_width(s, p->font, fontsize);

/* Handle special positioning and rotation. If the "halfway" value is set,
adjust the x coordinate if there is a note that follows in the bar. Otherwise,
if a note-related offset is set, adjust the x coordinate by looking for the
relevant musical offset. */

if (out_textX != NULL)
  {
  if (out_textX->halfway != 0)
    {
    if (out_moff < out_poslast->moff)
      x += (endalign? 0 : ((flags & text_centre) != 0)? six/2 : six) +
        mac_muldiv(out_barx + out_findXoffset(out_moff + n_length) -
          n_x - six, out_textX->halfway, 1000);
    }

  /* The offset value units are crotchets. */

  else if (out_textX->offset != 0)
    {
    int offset = mac_muldiv(len_crotchet, out_textX->offset, 1000);
    x = out_barx + out_findAoffset(out_moff + offset);
    }

  /* No rotation for underlay or overlay */

  if ((flags & text_ul) == 0 && out_textX->rotate != 0)
    font_rotate(out_textX->rotate);
  }


/* ======================= Underlay and Overlay ======================= */

/* {Und,Ov}erlay text is centred. If it ends in '=' the position is remembered
for starting the extender; if it ends in '-' the position is remembered for
starting the row of hyphens.

Note that an underlay string is represented as a pointer and a count, not as a
C string, because the pointer may point into the longer, original input string.

If an underlay string consists solely of '=' and an extender is outstanding,
an extender is drawn to the current position and the start extender position is
updated. If we are at the start of a line (indicated by a zero xstart value),
draw the line at the current level, not the saved one.

Note that # characters in the underlay string are converted to spaces. */

if ((flags & text_ul) != 0)
  {
  int i, j;
  uschar *pp, *qq, *cc;

  ulaystr **uu = &bar_cont->ulay;
  ulaystr *u = *uu;

  /* Find pending extension or hyphen data */

  while (u != NULL && u->level != p->ulevel)
    {
    uu = &u->next;
    u = *uu;
    }

  /* Compute vertical level for this syllable */

  y = ((flags & text_above) == 0)?
    out_sysblock->ulevel[out_stave] - p->ulevel * curmovt->underlaydepth :
    out_sysblock->olevel[out_stave] +
      (p->ulevel - olay_offset) * curmovt->overlaydepth;


  /* Deal with underlay extension line. There should always be an extension
  block, but we check, just in case. */

  if (s[0] == '=' && p->ulen == 1)
    {
    if (u != NULL && u->type == '=')
      {
      int xx, yy;

      if (u->x == 0)
        {
        xx = out_sysblock->firstnoteposition + out_sysblock->xjustify - 4000;
        yy = y;
        }
      else
        {
        xx = u->x;
        yy = u->y;
        }

      out_extension(xx, x + 5*main_stavemagn, yy, fontsize);
      if (x > u->x) u->x = x;  /* Set up for next part */
      u->y = yy;
      }

    return;
    }

  /* Not an extension line - there is text to be output. Copy it into a working
  string, turning # characters into spaces, and stopping at the character '^',
  which indicates the end of the text to be centred, or the start of it, if
  there are two '^' characters in the string. In the latter case, find the
  length of the left hang, set the start of the centering bit, and read to the
  next '^'. However, don't mess with characters that are part of an escape
  sequence. */

  pp = s;
  cc = qq = ss;
  i = 0;

  for (j = 0; j < 2; j++)
    {
    int k;

    /* Search for next '^' in string */

    for (; i < p->ulen && *pp != '^'; i++)
      {
      if (*pp == '\\')
        {
        int dummy1, dummy2, len;
        uschar *rr = pp;
        uschar dummy3[80];
        pp = string_escape(pp+1, dummy3, &dummy1, &dummy2);
        len = pp - rr;
        Ustrncpy(qq, rr, len);
        qq += len;
        i += len - 1;
        }
      else if (*pp == '#') { *qq++ = ' '; pp++; } else *qq++ = *pp++;
      }
    *qq = 0;

    /* If hit end of string or the second '^', break */

    if (i >= p->ulen || j == 1) break;

    /* See if there's another '^' in the string; if not, break */

    for (k = i+1; k < p->ulen; k++) if (s[k] == '^') break;
    if (k >= p->ulen) break;

    /* Left shift by the left hand width, adjust the start of the centred
    string, and continue on to the second '^'. */

    x -= string_width(ss, p->font, fontsize);
    cc = qq;
    pp++;
    i++;
    }

  /* There are two underlay styles. In style 0, all syllables are centred, with
  ^ indicating the end of the centred text. In style 1, syllables that extend
  over more than one note are left-justified, unless they contain ^, which
  indicates centring. */

  if (curmovt->underlaystyle == 0 ||
       (*pp != '=' && (*pp != '-' || pp[1] != '=')))
    {
    int xorig = x;
    x -= string_width(cc, p->font, fontsize)/2;

    /* We have calculated a centring position for the underlay string, based on
    the normal position, which is the left-hand edge of the note. If in fact
    there has been no change to the position, (i.e. the centred part of the
    string has zero width), leave the position as it is, for left-alignment. If
    there has been a change, however, we must add 3 points to make the centring
    relative to the middle of the notehead. However, if this is a grace note in
    default format, always left-align. */

    if (x != xorig && (n_length != 0 || curmovt->gracestyle != 0))
      x += 3*main_stavemagn;

    /* Copy the rest of the string if stopped at '^'; else leave pp pointing at
    the following character. */

    if (*pp == '^')
      {
      pp++;
      for (i++; i < p->ulen; i++)
        {
        if (*pp == '\\')
          {
          int dummy1, dummy2, len;
          uschar *rr = pp;
          uschar dummy3[80];
          pp = string_escape(pp+1, dummy3, &dummy1, &dummy2);
          len = pp - rr;
          Ustrncpy(qq, rr, len);
          qq += len;
          i += len - 1;
          }
        else if (*pp == '#') { *qq++ = ' '; pp++; } else *qq++ = *pp++;
        }
      *qq = 0;
      }
    }

  /* Deal with printing a row of hyphens up to this syllable. If continuing at
  the start of a new line, use the current level rather than the saved level.
  Remember to take note of any leading spaces at the start of the current text.
  */

  if (u != NULL && u->type == '-')
    {
    BOOL contflag;
    int x1 = x;
    int xx, yy;

    if (ss[0] == ' ')
      {
      int k;
      uschar spaces[80];

      for (k = 0; ss[k] == ' '; k++) spaces[k] = ss[k];
      spaces[k] = 0;
      x1 += font_stringwidth(spaces, p->font, fontsize);
      }

    if (u->x == 0)
      {
      xx = out_sysblock->firstnoteposition + out_sysblock->xjustify - 4000;
      yy = y + p-> y;
      contflag = TRUE;
      }
    else
      {
      xx = u->x;
      yy = u->y;
      contflag = FALSE;
      }

    if (u->htype == 0)
      out_hyphens(xx, x1 + p->x, yy, fontsize, contflag);
    else
      out_repeatstring(xx, x1 + p->x, yy, contflag, FALSE, u->htype);
    }

  /* Free up the hyphen or extender block. Extender blocks live till the next
  non "=" syllable, but are not drawn that far. */

  if (u != NULL)
    {
    *uu = u->next;
    store_free(u);
    }

  /* Set up a new hyphen or extender block if required. We need to find the end
  of the current syllable, excluding any trailing spaces. There is no harm in
  just removing the trailing spaces now - if they are not printed, it won't be
  visible! */

  if (*pp == '=' || *pp == '-')
    {
    u = store_Xget(sizeof(ulaystr));
    u->next = bar_cont->ulay;
    bar_cont->ulay = u;

    while (qq > ss && qq[-1] == ' ') qq--;   /* Find first trailing space */
    *qq = 0;                                 /* Terminate the string there */

    /* Set up the data values */

    u->x = x + p->x + string_width(ss, p->font, fontsize);
    u->y = y + p->y;
    u->type = *pp;
    u->level = p->ulevel;
    u->htype = p->htype;
    }

  /* The string to be printed has been built in ss */

  s = ss;
  }


/* ======================= General text ======================= */

/* Deal with not underlay. Adjust the x position if end or bar alignment is
required, and adjust level according to the pitch of the just printed
note/chord, if any, for the first text for any given note. Otherwise, the
position is above or below the previous. Non-underlay text can be rotated;
this was handled above, but we retained the string width before rotating the
font so that centring and end-alignment are done first. */

else if ((flags & text_followon) == 0)
  {
  BOOL baralign = (flags & text_baralign) != 0;
  BOOL timealign = (flags & text_timealign) != 0;
  BOOL startbar = baralign || timealign;

  if (baralign) x = out_startlinebar?
    (out_sysblock->startxposition + out_sysblock->xjustify) : out_lastbarlinex;

  /* Time signature alignment. If not found, use the first musical event in the
  bar. */

  else if (timealign)
    {
    if (out_startlinebar && mac_anystave(out_sysblock->showtimes))
      x = out_sysblock->timexposition + out_sysblock->xjustify;

    /* In mid-line, or if no stave is printing a time signature, search for an
    appropriate position, defaulting to the first thing in the bar that can
    follow a time signature. There must be something! */

    else
      {
      posstr *pt = out_postable;
      while (pt < out_poslast)       /* Just for safety */
        {
        if (pt->moff >= posx_timefirst)
          { x =  out_barx + pt->xoff; break; }
        pt++;
        }
      }
    }

  /* Handle /e and /c. Note that stringwidth is the horizontal width, ignoring
  any font rotation. This means that rotation occurs after the text is
  positioned. */

  if (endalign)
    x += ((atbar || startbar)? 1000 : six) - stringwidth;
  else if ((flags & text_centre) != 0)
    x += ((atbar || startbar)? 500 : six/2) - stringwidth/2;

  /* At the bar end, we adjust as for the last note or rest if end-aligned,
  because usually such text sticks back as far as the last note. Don't adjust
  for the next note when printing a rehearsal mark at the left of the stave. */

  if (above)     /* text above */
    {
    if (out_textnextabove) y = out_textnextabove;
    else if ((!atbar || endalign) && (!rehearse || !curmovt->rehearsallsleft))
      {
      int pt  = misc_ybound(FALSE, n_nexttie, TRUE, TRUE);  /* ties going out */
      int ppt = misc_ybound(FALSE, n_prevtie, TRUE, TRUE);  /* ties coming in */
      if (ppt > pt) pt = ppt;
      if (pt + 2000 > y) y = pt + 2000;
      }

    /* Deal with "above at overlay level" */

    if ((flags & text_atulevel) != 0)
      {
      y = out_sysblock->olevel[out_stave];
      }

    /* Deal with text at absolute position above the stave */

    else if ((flags & text_absolute) != 0) y = 16000;

    /* Save for repeated text */

    if (!postscript) out_textnextabove = y + p->y + unscaled_fontsize;
    }

  else           /* text below */
    {
    if (out_textnextbelow) y = out_textnextbelow;
    else if (!atbar || endalign)
      {
      int pb  = misc_ybound(TRUE, n_nexttie, TRUE, TRUE);
      int ppb = misc_ybound(TRUE, n_prevtie, TRUE, TRUE);
      if (ppb < pb) pb = ppb;
      if (pb - fontsize + 1000 < y) y = pb - fontsize + 1000;
      }

    /* Deal with "middle" text */

    if ((flags & text_middle) != 0 && out_stave < out_laststave)
      {
      int my;
      int gap = out_sysblock->stavespacing[out_stave];
      int st = out_stave;

      while (gap == 0 && ++st < out_laststave)
        if (mac_teststave(out_sysblock->notsuspend, st))
          gap = out_sysblock->stavespacing[st];

      my = - (gap/2 - 6000);
      if (my < y) y = my;
      }

    /* Deal with "below at underlay level" */

    else if ((flags & text_atulevel) != 0)
      y = out_sysblock->ulevel[out_stave];

    /* Deal with text at absolute position below the stave */

    else if ((flags & text_absolute) != 0) y = 0;

    /* Save value for repeated text */

    if (!postscript) out_textnextbelow = y + p->y - unscaled_fontsize;
    }
  }


/* ======================= Generate follow-on text ======================= */

/* This outputs at the ending point of the previous text string, which has
already been through the stave magnification, though any adjustment must be so
scaled. */

else
  {
  out_string(s, p->font, fontsize, out_string_endx + p->x,
    out_string_endy - (p->y * main_stavemagn)/1000, 0);
  s = US"";   /* Stops normal output */
  }


/* ======================= Generate output ======================= */

/* Parameters are now set up -- print, but omit null strings (to keep the
PostScript smaller). */

if (*s != 0)
  {
  DEBUG(("string: %s\n", s));

  /* Deal with rehearsal letters */

  if (rehearse)
    {
    int style = curmovt->rehearsalstyle;
    int yextra = (style == text_box)? 2000 : (style == text_ring)? 4000 : 0;

    /* At the start of a bar, unless an offset is provided, if we are at the
    start of a line, align with the very start if rehearsallsleft is set or
    else with the first note. If not the start of a line, align with the
    previous bar line. Relative p->x is then added. */

    if (out_moff == 0 && (out_textX == NULL || out_textX->offset == 0))
      {
      if (out_startlinebar)
        {
        if (curmovt->rehearsallsleft)
          {
          x = out_sysblock->startxposition + out_sysblock->xjustify;
          switch (bar_cont->clef)
            {
            case clef_trebledescant:
            x += 15000;
            /* Fall through */
            case clef_trebletenor:
            case clef_trebletenorB:
            case clef_treble:
            yextra += 3000;
            if (style == text_box) yextra += 1000;
            break;

            case clef_soprabass:
            x += 9000;
            yextra += 1000;
            break;

            default: break;
            }
          }
        else x = out_sysblock->firstnoteposition + out_sysblock->xjustify;
        }
      else x = out_lastbarlinex;
      }

    out_string(s, p->font,
      mac_muldiv(main_stavemagn,
        (curmovt->fontsizes)->fontsize_rehearse, 1000),
           x + p->x,
             out_ystave - ((y + yextra + p->y)*main_stavemagn)/1000, style);
    }

  /* Deal with included PostScript text */

  else if (postscript)
    ps_pstext(s, x + p->x, out_ystave - (p->y*main_stavemagn)/1000);

  /* Deal with normal text */

  else
    {
    if ((flags & (text_box | text_ring)) != 0)
      y += (above? 2 : (-2))*main_stavemagn;
    out_string(s, p->font, fontsize, x + p->x,
      out_ystave - ((y + p->y)*main_stavemagn)/1000,
        flags & (text_box | text_ring));
    }
  }

/* Reset font transformation */

font_reset();

DEBUG(("out_text() end\n", s));
}

/* End of settext.c */
