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 }