unipdf/creator/division.go
Adrian-George Bostan 30db3448f7
Add support for multi-block styled paragraphs (#331)
* Add support for multi-block styled paragraphs

* Fix context space when drawing division inside tables

* Update context height when drawing tables

* Update advanced invoice test case

* Add basic multi-block styled paragraph test case
2020-04-29 19:22:00 +00:00

199 lines
4.8 KiB
Go

/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package creator
import (
"errors"
"github.com/unidoc/unipdf/v3/common"
)
// Division is a container component which can wrap across multiple pages (unlike Block).
// It can contain multiple Drawable components (currently supporting Paragraph and Image).
//
// The component stacking behavior is vertical, where the Drawables are drawn on top of each other.
// Also supports horizontal stacking by activating the inline mode.
type Division struct {
components []VectorDrawable
// Positioning: relative / absolute.
positioning positioning
// Margins to be applied around the block when drawing on Page.
margins margins
// Controls whether the components are stacked horizontally
inline bool
}
// newDivision returns a new Division container component.
func newDivision() *Division {
return &Division{
components: []VectorDrawable{},
}
}
// Inline returns whether the inline mode of the division is active.
func (div *Division) Inline() bool {
return div.inline
}
// SetInline sets the inline mode of the division.
func (div *Division) SetInline(inline bool) {
div.inline = inline
}
// Add adds a VectorDrawable to the Division container.
// Currently supported VectorDrawables: *Paragraph, *StyledParagraph, *Image.
func (div *Division) Add(d VectorDrawable) error {
supported := false
switch d.(type) {
case *Paragraph:
supported = true
case *StyledParagraph:
supported = true
case *Image:
supported = true
}
if !supported {
return errors.New("unsupported type in Division")
}
div.components = append(div.components, d)
return nil
}
// Height returns the height for the Division component assuming all stacked on top of each other.
func (div *Division) Height() float64 {
y := 0.0
yMax := 0.0
for _, component := range div.components {
compWidth, compHeight := component.Width(), component.Height()
switch t := component.(type) {
case *Paragraph:
p := t
compWidth += p.margins.left + p.margins.right
compHeight += p.margins.top + p.margins.bottom
case *StyledParagraph:
p := t
compWidth += p.margins.left + p.margins.right
compHeight += p.margins.top + p.margins.bottom
}
// Vertical stacking.
y += compHeight
yMax = y
}
return yMax
}
// Width is not used. Not used as a Division element is designed to fill into available width depending on
// context. Returns 0.
func (div *Division) Width() float64 {
return 0
}
// GeneratePageBlocks generates the page blocks for the Division component.
// Multiple blocks are generated if the contents wrap over multiple pages.
func (div *Division) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
var pageblocks []*Block
origCtx := ctx
if div.positioning.isRelative() {
// Update context.
ctx.X += div.margins.left
ctx.Y += div.margins.top
ctx.Width -= div.margins.left + div.margins.right
ctx.Height -= div.margins.top + div.margins.bottom
}
// Set the inline mode of the division to the context.
ctx.Inline = div.inline
// Draw.
divCtx := ctx
tmpCtx := ctx
var lineHeight float64
for _, component := range div.components {
if ctx.Inline {
// Check whether the component fits on the current line.
if (ctx.X-divCtx.X)+component.Width() <= ctx.Width {
ctx.Y = tmpCtx.Y
ctx.Height = tmpCtx.Height
} else {
ctx.X = divCtx.X
ctx.Width = divCtx.Width
tmpCtx.Y += lineHeight
tmpCtx.Height -= lineHeight
lineHeight = 0
}
}
newblocks, updCtx, err := component.GeneratePageBlocks(ctx)
if err != nil {
common.Log.Debug("Error generating page blocks: %v", err)
return nil, ctx, err
}
if len(newblocks) < 1 {
continue
}
if len(pageblocks) > 0 {
// If there are pageblocks already in place.
// merge the first block in with current Block and append the rest.
pageblocks[len(pageblocks)-1].mergeBlocks(newblocks[0])
pageblocks = append(pageblocks, newblocks[1:]...)
} else {
pageblocks = append(pageblocks, newblocks[0:]...)
}
// Apply padding/margins.
if ctx.Inline {
// Recalculate positions on page change.
if ctx.Page != updCtx.Page {
divCtx.Y = ctx.Margins.top
divCtx.Height = ctx.PageHeight - ctx.Margins.top
tmpCtx.Y = divCtx.Y
tmpCtx.Height = divCtx.Height
lineHeight = updCtx.Height - divCtx.Height
} else {
// Calculate current line max height.
if dl := ctx.Height - updCtx.Height; dl > lineHeight {
lineHeight = dl
}
}
} else {
updCtx.X = ctx.X
}
ctx = updCtx
}
// Restore the original inline mode of the context.
ctx.Inline = origCtx.Inline
if div.positioning.isRelative() {
// Move back X to same start of line.
ctx.X = origCtx.X
}
if div.positioning.isAbsolute() {
// If absolute: return original context.
return pageblocks, origCtx, nil
}
return pageblocks, ctx, nil
}