github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/creator/chapters.go (about)

     1  /*
     2   * This file is subject to the terms and conditions defined in
     3   * file 'LICENSE.md', which is part of this source code package.
     4   */
     5  
     6  package creator
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  
    12  	"github.com/unidoc/unidoc/common"
    13  	"github.com/unidoc/unidoc/pdf/model/fonts"
    14  )
    15  
    16  // Chapter is used to arrange multiple drawables (paragraphs, images, etc) into a single section. The concept is
    17  // the same as a book or a report chapter.
    18  type Chapter struct {
    19  	number  int
    20  	title   string
    21  	heading *Paragraph
    22  
    23  	subchapters int
    24  
    25  	contents []Drawable
    26  
    27  	// Show chapter numbering
    28  	showNumbering bool
    29  
    30  	// Include in TOC.
    31  	includeInTOC bool
    32  
    33  	// Positioning: relative / absolute.
    34  	positioning positioning
    35  
    36  	// Absolute coordinates (when in absolute mode).
    37  	xPos, yPos float64
    38  
    39  	// Margins to be applied around the block when drawing on Page.
    40  	margins margins
    41  
    42  	// Reference to the creator's TOC.
    43  	toc *TableOfContents
    44  }
    45  
    46  // NewChapter creates a new chapter with the specified title as the heading.
    47  func (c *Creator) NewChapter(title string) *Chapter {
    48  	chap := &Chapter{}
    49  
    50  	c.chapters++
    51  	chap.number = c.chapters
    52  	chap.title = title
    53  
    54  	chap.showNumbering = true
    55  	chap.includeInTOC = true
    56  
    57  	heading := fmt.Sprintf("%d. %s", c.chapters, title)
    58  	p := NewParagraph(heading)
    59  	p.SetFontSize(16)
    60  	p.SetFont(fonts.NewFontHelvetica()) // bold?
    61  
    62  	chap.heading = p
    63  	chap.contents = []Drawable{}
    64  
    65  	// Keep a reference for toc.
    66  	chap.toc = c.toc
    67  
    68  	return chap
    69  }
    70  
    71  // SetShowNumbering sets a flag to indicate whether or not to show chapter numbers as part of title.
    72  func (chap *Chapter) SetShowNumbering(show bool) {
    73  	if show {
    74  		heading := fmt.Sprintf("%d. %s", chap.number, chap.title)
    75  		chap.heading.SetText(heading)
    76  	} else {
    77  		heading := fmt.Sprintf("%s", chap.title)
    78  		chap.heading.SetText(heading)
    79  	}
    80  	chap.showNumbering = show
    81  }
    82  
    83  // SetIncludeInTOC sets a flag to indicate whether or not to include in tOC.
    84  func (chap *Chapter) SetIncludeInTOC(includeInTOC bool) {
    85  	chap.includeInTOC = includeInTOC
    86  }
    87  
    88  // GetHeading returns the chapter heading paragraph. Used to give access to address style: font, sizing etc.
    89  func (chap *Chapter) GetHeading() *Paragraph {
    90  	return chap.heading
    91  }
    92  
    93  // SetMargins sets the Chapter margins: left, right, top, bottom.
    94  // Typically not needed as the creator's page margins are used.
    95  func (chap *Chapter) SetMargins(left, right, top, bottom float64) {
    96  	chap.margins.left = left
    97  	chap.margins.right = right
    98  	chap.margins.top = top
    99  	chap.margins.bottom = bottom
   100  }
   101  
   102  // GetMargins returns the Chapter's margin: left, right, top, bottom.
   103  func (chap *Chapter) GetMargins() (float64, float64, float64, float64) {
   104  	return chap.margins.left, chap.margins.right, chap.margins.top, chap.margins.bottom
   105  }
   106  
   107  // Add adds a new Drawable to the chapter.
   108  func (chap *Chapter) Add(d Drawable) error {
   109  	if Drawable(chap) == d {
   110  		common.Log.Debug("ERROR: Cannot add itself")
   111  		return errors.New("Range check error")
   112  	}
   113  
   114  	switch d.(type) {
   115  	case *Chapter:
   116  		common.Log.Debug("Error: Cannot add chapter to a chapter")
   117  		return errors.New("Type check error")
   118  	case *Paragraph, *Image, *Block, *Subchapter, *Table, *PageBreak:
   119  		chap.contents = append(chap.contents, d)
   120  	default:
   121  		common.Log.Debug("Unsupported: %T", d)
   122  		return errors.New("Type check error")
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  // GeneratePageBlocks generate the Page blocks.  Multiple blocks are generated if the contents wrap over
   129  // multiple pages.
   130  func (chap *Chapter) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
   131  	origCtx := ctx
   132  
   133  	if chap.positioning.isRelative() {
   134  		// Update context.
   135  		ctx.X += chap.margins.left
   136  		ctx.Y += chap.margins.top
   137  		ctx.Width -= chap.margins.left + chap.margins.right
   138  		ctx.Height -= chap.margins.top
   139  	}
   140  
   141  	blocks, ctx, err := chap.heading.GeneratePageBlocks(ctx)
   142  	if err != nil {
   143  		return blocks, ctx, err
   144  	}
   145  	if len(blocks) > 1 {
   146  		ctx.Page++ // Did not fit, moved to new Page block.
   147  	}
   148  
   149  	if chap.includeInTOC {
   150  		// Add to TOC.
   151  		chap.toc.add(chap.title, chap.number, 0, ctx.Page)
   152  	}
   153  
   154  	for _, d := range chap.contents {
   155  		newBlocks, c, err := d.GeneratePageBlocks(ctx)
   156  		if err != nil {
   157  			return blocks, ctx, err
   158  		}
   159  		if len(newBlocks) < 1 {
   160  			continue
   161  		}
   162  
   163  		// The first block is always appended to the last..
   164  		blocks[len(blocks)-1].mergeBlocks(newBlocks[0])
   165  		blocks = append(blocks, newBlocks[1:]...)
   166  
   167  		ctx = c
   168  	}
   169  
   170  	if chap.positioning.isRelative() {
   171  		// Move back X to same start of line.
   172  		ctx.X = origCtx.X
   173  	}
   174  
   175  	if chap.positioning.isAbsolute() {
   176  		// If absolute: return original context.
   177  		return blocks, origCtx, nil
   178  
   179  	}
   180  
   181  	return blocks, ctx, nil
   182  }