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 }