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

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

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


/* This file contains subroutines for the paginating code. */


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




/*************************************************
*       Update times/keys for start of line      *
*************************************************/

/* This function is called at the start of a system, to see if the
first bar contains clef, key, or time items that should be transferred
into the basic setting for the bar.

Arguments: none
Returns:   nothing
*/

void
page_setsignatures(void)
{
int stave;

for (stave = 0; stave <= page_lastwanted; stave++)
  {
  if (mac_teststave(curmovt->staves, stave))
    {
    bstr *p = ((curmovt->stavetable)[stave])->barindex[page_barnumber];

    if (p != NULL)
      {
      BOOL hadclef = FALSE;
      BOOL hadkey  = FALSE;
      BOOL hadtime = FALSE;

      int type = p->type;

      while (type != b_End)
        {
        switch(type)
          {
          case b_Jump:
          p = (bstr *)(((b_Jumpstr *)p)->next);
          break;

          case b_clef:
          if (!hadclef)
            {
            b_clefstr *c = (b_clefstr *)p;
            (page_sysblock->cont[stave]).clef = c->trueclef;
            c->suppress = hadclef = TRUE;
            }
          break;

          case b_key:
          /* if (!hadkey) */
          if (!hadkey || page_sysblock->cont[stave].key == 2 || page_sysblock->cont[stave].key == 21)
            {
            b_keystr *k = (b_keystr *)p;
            if (k->key <= 63 || !k->warn)
              {
              (page_sysblock->cont[stave]).key = k->key;
              hadkey = TRUE;
              }
            k->suppress = TRUE;
            }
          break;

          case b_time:
          if (!hadtime)
            {
            b_timestr *t = (b_timestr *)p;
            (page_sysblock->cont[stave]).time = t->time;
            t->suppress = hadtime = TRUE;
            mac_setstave(page_showtimes, stave);
            }
          break;

          case b_note:
          case b_lrepeat:
          goto NEXTSTAVE;
          }

        p = (bstr *)((uschar *)p + length_table[type]);
        type = p->type;
        }
      }
    }

  NEXTSTAVE: continue;
  }
}



/*************************************************
*          Find width of name string             *
*************************************************/

/* This function is called by page_startwidth() below. Multiple lines in a name
string are indicated by '|'. We return the width of the widest.

Arguments:
  s            the string
  font         the font
  size         the font size

Returns:       a width
*/

static int
page_namestringwidth(uschar *s, int font, int size)
{
int yield = 0;
uschar ss[256];
while (*s)
  {
  int w;
  int i = 0;
  while (*s != 0 && *s != '|') ss[i++] = *s++;
  ss[i] = 0;
  w = string_width(ss, font, size);
  if (w > yield) yield = w;
  if (*s == '|') s++;
  }
return yield;
}



/*************************************************
*          Compute width of stave names          *
*************************************************/

/* This function returns the width needed for names to be printed at the start
of staves on a page. Two stave bitmaps are supplied: the name is counted only
if the stave is selected in both bitmaps. Typically, the first map gives the
staves that are active in a system, and the second is those that are currently
suspended.

Arguments:
  pagedata    the page data block
  map         the first bitmap
  map2        the second bitmap

Returns:      the maximum width needed
*/

int
page_startwidth(pagedatastr *pagedata, usint *map, usint *map2)
{
int x = 0;
int *f = (curmovt->fontsizes)->fontsize_text;
int **m = (curmovt->fontsizes)->fontmatrix_text;

if (pagedata->stavenames != NULL)
  {
  int i;
  snamestr **stavenames = pagedata->stavenames;
  for (i = 1; i <= page_lastwanted; i++)
    {
    if (mac_teststave2(map, map2, i))
      {
      snamestr *s;
      for (s = stavenames[i]; s != NULL; s = s->extra)
        {
        int w = 0;
        if ((s->flags & snf_vertical) != 0) w = 6000; else if (s->text != NULL)
          {
          int *matrix = m[s->offset];
          if (matrix != NULL) memcpy(font_transform, matrix, 4*sizeof(int));
          w = page_namestringwidth(s->text, font_rm, f[s->offset]);
          font_reset();
          if (w > 0) w += 6000;  /* final space if non-null */
          }

        if (w > x) x = w;
        }
      }
    }
  }

/* If there are stave names, add extra if the system is braced or has a thin
bracket, to leave space between them and the names. */

if (x != 0)
  {
  if (curmovt->bracelist != NULL) x += 6500;
    else if (curmovt->thinbracketlist != NULL) x += 4000;
  }

return x;
}





/*************************************************
*              Deal with a page's heading        *
*************************************************/

/* This function sets up a headblock and adds it to the chain of headings and
systems for the page. It also subtracts the space needed for the heading lines
from the space left on the page. Remember to include an additional stave depth
+ 1pt after any headings at the top of a page. This function is not called if
there are no headings.

Argument:   the heading structure
Returns:    nothing
*/

void
page_dopageheading(headstr *page_heading)
{
int used = 0;
headblock *h = store_Xget(sizeof(headblock));
h->type = sh_heading;
h->pageheading = (page_heading != curmovt->heading);  /* Not a movt heading */
h->next = NULL;
h->movt = curmovt;
h->headings = page_heading;

*page_sysprevptr = (sysblock *)h;
page_sysprevptr = (sysblock **)(&(h->next));

while (page_heading != NULL)
  {
  used += page_heading->space;
  page_heading = page_heading->next;
  }

if (curpage->spaceleft == main_pagelength) curpage->spaceleft -= 17000;
curpage->spaceleft -= used;
}





/*************************************************
*         Justify a heading or footing           *
*************************************************/

/* This function scans a heading text and splits up the "left hand" part into
two or more heading lines. For all but the last line the stretch field is set
to cause the text to be printed with right justification. (Actually the last
line is justified too, if it is near enough to the end.)

Argument:  a heading structure
Returns:   nothing
*/

void
page_justifyheading(headstr *h)
{
while (h != NULL)
  {
  if (h->size > 0)  /* Text heading (as opposed to drawing or postscript) */
    {
    uschar *lastsplit = NULL;
    uschar *p = h->text;
    int spacecount = 0;
    int splitfont = 0;
    int lastwidth = 0;

    if (h->matrix != NULL) memcpy(font_transform, h->matrix, 4*sizeof(int));

    for (;;)
      {
      if (*p == '|') break;

      if (*p == ' ' || *p == 0)
        {
        int width;
        int c = *p;

        *p = 0;
        width = string_width(h->text, h->font, h->size);
        *p = c;

        if (width > curmovt->linelength)
          {
          headstr *new;
          if (lastsplit == NULL) break;
          p = lastsplit;
          *p++ = 0;
          new = store_Xget(sizeof(headstr));
          memcpy(new, h, sizeof(headstr));
          new->text = p;
          new->font = splitfont;
          new->spaceabove = 0;
          new->stretch = 0;
          if (spacecount > 1)
            h->stretch = (curmovt->linelength - lastwidth)/(spacecount - 1);
          h->next = new;
          h = new;
          spacecount = 0;
          lastsplit = NULL;
          }
        else if (*p == 0)
          {
          if (curmovt->linelength - width < 5000 && spacecount > 0)
            h->stretch = (curmovt->linelength - width)/spacecount;
          break;
          }
        else
          {
          spacecount++;
          lastsplit = p++;
          lastwidth = width;
          splitfont = string_font;   /* Set by string_width */
          while (*p == ' ') { p++; spacecount++; }
          }
        }

      else p++;
      }

    font_reset();        /* To set no transformation */
    }

  h = h->next;           /* Loop through all headings */
  }
}





/*************************************************
*              Set up a new page                 *
*************************************************/

/* This is not called for the very first page; curpage is always set to the
current page.

Arguments:
  heading        a movement heading, or NULL if not a movement start
  page_heading   a page heading, or NULL if no page heading

Returns:         nothing; curpage is updated
*/

void
page_newpage(headstr *heading, headstr *page_heading)
{
pagestr *newpage = store_Xget(sizeof(pagestr));
curpage->next = newpage;
newpage->number = main_lastpage = curpage->number + main_pageinc;

curpage = newpage;
curpage->next = NULL;
curpage->spaceleft = main_pagelength;
curpage->overrun = 0;

curpage->sysblocks = NULL;
curpage->footing = NULL;
page_sysprevptr = &(curpage->sysblocks);
page_countsystems = 0;
page_lastsystem = NULL;
page_footnotes = NULL;
page_footnotedepth = 0;

page_justify = curmovt->justify;
page_topmargin = curmovt->topmargin;
page_botmargin = curmovt->botmargin;

if (page_heading != NULL) page_dopageheading(page_heading);
if (heading != NULL) page_dopageheading(heading);
}



/*************************************************
*           Finish off a page                    *
*************************************************/

/* Correct the final spaceleft value (for information use), then justify
the page if necessary, and set up the footnotes & footing.

Argument:   TRUE for the final page; causes it to use lastfooting
Returns:    nothing
*/

void
page_endpage(BOOL final)
{
headstr *footing;
int justbits = page_justify & (just_top + just_bottom);
int spaceleft = (curpage->spaceleft +=
  ((page_lastsystem == NULL)? 0 : page_lastsystem->systemgap));

/* Set up any footnotes, making sure the systemgap field on the last system is
the last accepted spacing below stave value, plus 10 points. Adjust the
spaceleft to allow for footnotes when justifying. */

if (page_footnotes != NULL)
  {
  headblock *h = store_Xget(sizeof(headblock));
  h->type = sh_heading;
  h->pageheading = FALSE;
  h->next = NULL;
  h->movt = curmovt;
  h->headings = page_footnotes;
  *page_sysprevptr = (sysblock *)h;
  page_lastsystem->systemgap = page_footnotespacing + 10000;
  spaceleft -= page_footnotedepth + page_footnotespacing;
  }

if (spaceleft < 0) spaceleft = 0;    /* can happen on deep systems */

/* Deal with vertical justification data. If there are no spreading points
top+bottom is the same as bottom. There is a limit to the distance between
systems. */

if (justbits == just_top + just_bottom)
  {
  int topmargin = page_topmargin;
  int botmargin = page_botmargin;
  int margin = topmargin + botmargin;
  if (margin > spaceleft)
    {
    topmargin = mac_muldiv(topmargin, spaceleft, margin);
    botmargin = mac_muldiv(botmargin, spaceleft, margin);
    }
  spaceleft -= botmargin + topmargin;
  curpage->topspace = topmargin;

  if (spaceleft <= main_pagelength/2 && page_countsystems > 1)
    {
    int count = page_countsystems - 1;
    int insert = spaceleft/count;
    sysblock *s = curpage->sysblocks;

    if (insert > main_maxvertjustify) insert = main_maxvertjustify;

    while (s != NULL)             /* defensive programming */
      {
      if (s->type != sh_heading && (s->flags & sysblock_noadvance) == 0)
        {
        s->systemgap += insert;
        if (--count <= 0) break;
        }
      s = s->next;
      }
    }
  }


/* Top-only justification */

else if (justbits == just_top)
  curpage->topspace = (page_topmargin > spaceleft)? spaceleft : page_topmargin;

/* Bottom-only justification */

else if (justbits == just_bottom)
  {
  curpage->topspace = spaceleft - page_botmargin;
  if (curpage->topspace < 0) curpage->topspace = 0;
  }

/* No justification => centred vertically */

else curpage->topspace = spaceleft/2;

/* Set up the page footing */

if (page_footing != NULL)     /* Start of movement page footing */
  {
  footing = page_footing;
  page_footing = NULL;
  }
else
  {
  footing = (final && curmovt->lastfooting != NULL)?
    curmovt->lastfooting : curmovt->pagefooting;
  }

if (footing != NULL)
  {
  headblock *f = store_Xget(sizeof(headblock));
  f->next = NULL;
  f->movt = curmovt;
  f->headings = footing;
  curpage->footing = f;
  }
}


/* End of pagesubs.c */
