github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/creator/division.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  
    11  	"github.com/unidoc/unidoc/common"
    12  )
    13  
    14  // Division is a container component which can wrap across multiple pages (unlike Block).
    15  // It can contain multiple Drawable components (currently supporting Paragraph and Image).
    16  //
    17  // The component stacking behavior is vertical, where the Drawables are drawn on top of each other.
    18  // Also supports horizontal stacking by activating the inline mode.
    19  type Division struct {
    20  	components []VectorDrawable
    21  
    22  	// Positioning: relative / absolute.
    23  	positioning positioning
    24  
    25  	// Margins to be applied around the block when drawing on Page.
    26  	margins margins
    27  
    28  	// Controls whether the components are stacked horizontally
    29  	inline bool
    30  }
    31  
    32  // NewDivision returns a new Division container component.
    33  func NewDivision() *Division {
    34  	return &Division{
    35  		components: []VectorDrawable{},
    36  	}
    37  }
    38  
    39  // Inline returns whether the inline mode of the division is active.
    40  func (div *Division) Inline() bool {
    41  	return div.inline
    42  }
    43  
    44  // SetInline sets the inline mode of the division.
    45  func (div *Division) SetInline(inline bool) {
    46  	div.inline = inline
    47  }
    48  
    49  // Add adds a VectorDrawable to the Division container.
    50  // Currently supported VectorDrawables: *Paragraph, *StyledParagraph, *Image.
    51  func (div *Division) Add(d VectorDrawable) error {
    52  	supported := false
    53  
    54  	switch d.(type) {
    55  	case *Paragraph:
    56  		supported = true
    57  	case *StyledParagraph:
    58  		supported = true
    59  	case *Image:
    60  		supported = true
    61  	}
    62  
    63  	if !supported {
    64  		return errors.New("Unsupported type in Division")
    65  	}
    66  
    67  	div.components = append(div.components, d)
    68  
    69  	return nil
    70  }
    71  
    72  // Height returns the height for the Division component assuming all stacked on top of each other.
    73  func (div *Division) Height() float64 {
    74  	y := 0.0
    75  	yMax := 0.0
    76  	for _, component := range div.components {
    77  		compWidth, compHeight := component.Width(), component.Height()
    78  		switch t := component.(type) {
    79  		case *Paragraph:
    80  			p := t
    81  			compWidth += p.margins.left + p.margins.right
    82  			compHeight += p.margins.top + p.margins.bottom
    83  		}
    84  
    85  		// Vertical stacking.
    86  		y += compHeight
    87  		yMax = y
    88  	}
    89  
    90  	return yMax
    91  }
    92  
    93  // Width is not used. Not used as a Division element is designed to fill into available width depending on
    94  // context.  Returns 0.
    95  func (div *Division) Width() float64 {
    96  	return 0
    97  }
    98  
    99  // GeneratePageBlocks generates the page blocks for the Division component.
   100  // Multiple blocks are generated if the contents wrap over multiple pages.
   101  func (div *Division) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
   102  	pageblocks := []*Block{}
   103  
   104  	origCtx := ctx
   105  
   106  	if div.positioning.isRelative() {
   107  		// Update context.
   108  		ctx.X += div.margins.left
   109  		ctx.Y += div.margins.top
   110  		ctx.Width -= div.margins.left + div.margins.right
   111  		ctx.Height -= div.margins.top
   112  	}
   113  
   114  	// Set the inline mode of the division to the context.
   115  	ctx.Inline = div.inline
   116  
   117  	// Draw.
   118  	divCtx := ctx
   119  	tmpCtx := ctx
   120  	var lineHeight float64
   121  
   122  	for _, component := range div.components {
   123  		if ctx.Inline {
   124  			// Check whether the component fits on the current line.
   125  			if (ctx.X-divCtx.X)+component.Width() <= ctx.Width {
   126  				ctx.Y = tmpCtx.Y
   127  				ctx.Height = tmpCtx.Height
   128  			} else {
   129  				ctx.X = divCtx.X
   130  				ctx.Width = divCtx.Width
   131  
   132  				tmpCtx.Y += lineHeight
   133  				tmpCtx.Height -= lineHeight
   134  				lineHeight = 0
   135  			}
   136  		}
   137  
   138  		newblocks, updCtx, err := component.GeneratePageBlocks(ctx)
   139  		if err != nil {
   140  			common.Log.Debug("Error generating page blocks: %v", err)
   141  			return nil, ctx, err
   142  		}
   143  
   144  		if len(newblocks) < 1 {
   145  			continue
   146  		}
   147  
   148  		if len(pageblocks) > 0 {
   149  			// If there are pageblocks already in place.
   150  			// merge the first block in with current Block and append the rest.
   151  			pageblocks[len(pageblocks)-1].mergeBlocks(newblocks[0])
   152  			pageblocks = append(pageblocks, newblocks[1:]...)
   153  		} else {
   154  			pageblocks = append(pageblocks, newblocks[0:]...)
   155  		}
   156  
   157  		// Apply padding/margins.
   158  		if ctx.Inline {
   159  			// Recalculate positions on page change.
   160  			if ctx.Page != updCtx.Page {
   161  				divCtx.Y = ctx.Margins.top
   162  				divCtx.Height = ctx.PageHeight - ctx.Margins.top
   163  
   164  				tmpCtx.Y = divCtx.Y
   165  				tmpCtx.Height = divCtx.Height
   166  				lineHeight = updCtx.Height - divCtx.Height
   167  			} else {
   168  				// Calculate current line max height.
   169  				if dl := ctx.Height - updCtx.Height; dl > lineHeight {
   170  					lineHeight = dl
   171  				}
   172  			}
   173  		} else {
   174  			updCtx.X = ctx.X
   175  		}
   176  
   177  		ctx = updCtx
   178  	}
   179  
   180  	// Restore the original inline mode of the context.
   181  	ctx.Inline = origCtx.Inline
   182  
   183  	if div.positioning.isRelative() {
   184  		// Move back X to same start of line.
   185  		ctx.X = origCtx.X
   186  	}
   187  
   188  	if div.positioning.isAbsolute() {
   189  		// If absolute: return original context.
   190  		return pageblocks, origCtx, nil
   191  	}
   192  
   193  	return pageblocks, ctx, nil
   194  }