2017-07-05 23:10:57 +00:00
|
|
|
/*
|
|
|
|
* 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 (
|
2017-07-07 15:08:43 +00:00
|
|
|
"errors"
|
2017-07-05 23:10:57 +00:00
|
|
|
"fmt"
|
2018-10-03 18:32:42 +03:00
|
|
|
"strconv"
|
2017-07-05 23:10:57 +00:00
|
|
|
|
2019-05-16 23:08:40 +03:00
|
|
|
"github.com/unidoc/unipdf/v3/common"
|
2019-05-16 23:44:51 +03:00
|
|
|
"github.com/unidoc/unipdf/v3/model"
|
2017-07-05 23:10:57 +00:00
|
|
|
)
|
|
|
|
|
2018-08-15 09:32:13 +10:00
|
|
|
// Chapter is used to arrange multiple drawables (paragraphs, images, etc) into a single section.
|
|
|
|
// The concept is the same as a book or a report chapter.
|
2017-07-06 16:26:22 +00:00
|
|
|
type Chapter struct {
|
2019-03-06 19:41:33 +02:00
|
|
|
// The number of the chapter.
|
|
|
|
number int
|
|
|
|
|
|
|
|
// The title of the chapter.
|
|
|
|
title string
|
|
|
|
|
|
|
|
// The heading paragraph of the chapter.
|
|
|
|
heading *Paragraph
|
|
|
|
|
|
|
|
// The content components of the chapter.
|
2019-03-05 21:39:55 +02:00
|
|
|
contents []Drawable
|
2017-07-05 23:10:57 +00:00
|
|
|
|
2019-03-05 21:39:55 +02:00
|
|
|
// The number of subchapters the chapter has.
|
2017-07-05 23:10:57 +00:00
|
|
|
subchapters int
|
|
|
|
|
2017-07-06 16:26:22 +00:00
|
|
|
// Show chapter numbering
|
|
|
|
showNumbering bool
|
|
|
|
|
|
|
|
// Include in TOC.
|
|
|
|
includeInTOC bool
|
|
|
|
|
2017-07-05 23:10:57 +00:00
|
|
|
// Positioning: relative / absolute.
|
|
|
|
positioning positioning
|
|
|
|
|
|
|
|
// Absolute coordinates (when in absolute mode).
|
|
|
|
xPos, yPos float64
|
|
|
|
|
2017-07-06 16:26:22 +00:00
|
|
|
// Margins to be applied around the block when drawing on Page.
|
2017-07-05 23:10:57 +00:00
|
|
|
margins margins
|
|
|
|
|
2019-03-05 21:39:55 +02:00
|
|
|
// Reference to the parent chapter the current chapter belongs to.
|
|
|
|
parent *Chapter
|
|
|
|
|
2019-01-16 21:50:10 +02:00
|
|
|
// Reference to the TOC of the creator.
|
2018-10-03 18:32:42 +03:00
|
|
|
toc *TOC
|
2019-01-16 21:50:10 +02:00
|
|
|
|
|
|
|
// Reference to the outline of the creator.
|
|
|
|
outline *model.Outline
|
|
|
|
|
|
|
|
// The item of the chapter in the outline.
|
|
|
|
outlineItem *model.OutlineItem
|
2019-03-05 21:39:55 +02:00
|
|
|
|
|
|
|
// The level of the chapter in the chapters hierarchy.
|
|
|
|
level uint
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-10-12 23:00:02 +03:00
|
|
|
// newChapter creates a new chapter with the specified title as the heading.
|
2019-03-05 21:39:55 +02:00
|
|
|
func newChapter(parent *Chapter, toc *TOC, outline *model.Outline, title string, number int, style TextStyle) *Chapter {
|
|
|
|
var level uint = 1
|
|
|
|
if parent != nil {
|
|
|
|
level = parent.level + 1
|
|
|
|
}
|
2017-07-06 16:26:22 +00:00
|
|
|
|
2019-03-05 21:39:55 +02:00
|
|
|
chapter := &Chapter{
|
2018-10-12 23:00:02 +03:00
|
|
|
number: number,
|
|
|
|
title: title,
|
|
|
|
showNumbering: true,
|
|
|
|
includeInTOC: true,
|
2019-03-05 21:39:55 +02:00
|
|
|
parent: parent,
|
2018-10-12 23:00:02 +03:00
|
|
|
toc: toc,
|
2019-01-16 21:50:10 +02:00
|
|
|
outline: outline,
|
2018-10-12 23:00:02 +03:00
|
|
|
contents: []Drawable{},
|
2019-03-05 21:39:55 +02:00
|
|
|
level: level,
|
2018-10-12 23:00:02 +03:00
|
|
|
}
|
2019-03-05 21:39:55 +02:00
|
|
|
|
|
|
|
p := newParagraph(chapter.headingText(), style)
|
|
|
|
p.SetFont(style.Font)
|
|
|
|
p.SetFontSize(style.FontSize)
|
|
|
|
|
|
|
|
chapter.heading = p
|
|
|
|
return chapter
|
|
|
|
}
|
|
|
|
|
2019-03-06 19:41:33 +02:00
|
|
|
// NewSubchapter creates a new child chapter with the specified title.
|
2019-03-05 21:39:55 +02:00
|
|
|
func (chap *Chapter) NewSubchapter(title string) *Chapter {
|
|
|
|
style := newTextStyle(chap.heading.textFont)
|
|
|
|
style.FontSize = 14
|
|
|
|
|
|
|
|
chap.subchapters++
|
|
|
|
subchapter := newChapter(chap, chap.toc, chap.outline, title, chap.subchapters, style)
|
|
|
|
chap.Add(subchapter)
|
|
|
|
|
|
|
|
return subchapter
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2017-07-31 13:07:02 +00:00
|
|
|
// SetShowNumbering sets a flag to indicate whether or not to show chapter numbers as part of title.
|
2017-07-06 16:26:22 +00:00
|
|
|
func (chap *Chapter) SetShowNumbering(show bool) {
|
2020-01-13 21:00:11 +02:00
|
|
|
chap.showNumbering = show
|
2019-03-05 21:39:55 +02:00
|
|
|
chap.heading.SetText(chap.headingText())
|
2017-07-06 16:26:22 +00:00
|
|
|
}
|
|
|
|
|
2017-07-31 13:07:02 +00:00
|
|
|
// SetIncludeInTOC sets a flag to indicate whether or not to include in tOC.
|
2017-07-06 16:26:22 +00:00
|
|
|
func (chap *Chapter) SetIncludeInTOC(includeInTOC bool) {
|
|
|
|
chap.includeInTOC = includeInTOC
|
|
|
|
}
|
|
|
|
|
2017-07-31 13:07:02 +00:00
|
|
|
// GetHeading returns the chapter heading paragraph. Used to give access to address style: font, sizing etc.
|
|
|
|
func (chap *Chapter) GetHeading() *Paragraph {
|
2017-07-06 16:26:22 +00:00
|
|
|
return chap.heading
|
|
|
|
}
|
|
|
|
|
2017-07-31 13:07:02 +00:00
|
|
|
// SetMargins sets the Chapter margins: left, right, top, bottom.
|
|
|
|
// Typically not needed as the creator's page margins are used.
|
2017-07-06 16:26:22 +00:00
|
|
|
func (chap *Chapter) SetMargins(left, right, top, bottom float64) {
|
2017-07-05 23:10:57 +00:00
|
|
|
chap.margins.left = left
|
|
|
|
chap.margins.right = right
|
|
|
|
chap.margins.top = top
|
|
|
|
chap.margins.bottom = bottom
|
|
|
|
}
|
|
|
|
|
2017-07-31 13:07:02 +00:00
|
|
|
// GetMargins returns the Chapter's margin: left, right, top, bottom.
|
2017-07-06 16:26:22 +00:00
|
|
|
func (chap *Chapter) GetMargins() (float64, float64, float64, float64) {
|
2017-07-05 23:10:57 +00:00
|
|
|
return chap.margins.left, chap.margins.right, chap.margins.top, chap.margins.bottom
|
|
|
|
}
|
|
|
|
|
2017-07-31 13:07:02 +00:00
|
|
|
// Add adds a new Drawable to the chapter.
|
2017-07-07 15:08:43 +00:00
|
|
|
func (chap *Chapter) Add(d Drawable) error {
|
2017-07-05 23:10:57 +00:00
|
|
|
if Drawable(chap) == d {
|
|
|
|
common.Log.Debug("ERROR: Cannot add itself")
|
2018-12-08 19:16:52 +02:00
|
|
|
return errors.New("range check error")
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch d.(type) {
|
2019-03-13 21:13:21 +02:00
|
|
|
case *Paragraph, *StyledParagraph, *Image, *Block, *Table, *PageBreak, *Chapter:
|
2017-07-05 23:10:57 +00:00
|
|
|
chap.contents = append(chap.contents, d)
|
|
|
|
default:
|
|
|
|
common.Log.Debug("Unsupported: %T", d)
|
2018-12-08 19:16:52 +02:00
|
|
|
return errors.New("type check error")
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2017-07-07 15:08:43 +00:00
|
|
|
|
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2019-03-06 19:41:33 +02:00
|
|
|
// headingNumber returns the chapter heading number based on the chapter
|
|
|
|
// hierarchy and the showNumbering property.
|
2019-03-05 21:39:55 +02:00
|
|
|
func (chap *Chapter) headingNumber() string {
|
|
|
|
var chapNumber string
|
|
|
|
if chap.showNumbering {
|
|
|
|
if chap.number != 0 {
|
|
|
|
chapNumber = strconv.Itoa(chap.number) + "."
|
|
|
|
}
|
|
|
|
|
|
|
|
if chap.parent != nil {
|
|
|
|
parentChapNumber := chap.parent.headingNumber()
|
|
|
|
if parentChapNumber != "" {
|
|
|
|
chapNumber = parentChapNumber + chapNumber
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return chapNumber
|
|
|
|
}
|
|
|
|
|
2019-03-06 19:41:33 +02:00
|
|
|
// headingText returns the chapter heading text content.
|
2019-03-05 21:39:55 +02:00
|
|
|
func (chap *Chapter) headingText() string {
|
|
|
|
heading := chap.title
|
|
|
|
if chapNumber := chap.headingNumber(); chapNumber != "" {
|
|
|
|
heading = fmt.Sprintf("%s %s", chapNumber, heading)
|
|
|
|
}
|
|
|
|
|
|
|
|
return heading
|
|
|
|
}
|
|
|
|
|
2018-08-15 09:32:13 +10:00
|
|
|
// GeneratePageBlocks generate the Page blocks. Multiple blocks are generated if the contents wrap
|
|
|
|
// over multiple pages.
|
2017-07-06 16:26:22 +00:00
|
|
|
func (chap *Chapter) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
|
2017-07-14 13:55:17 +00:00
|
|
|
origCtx := ctx
|
|
|
|
|
|
|
|
if chap.positioning.isRelative() {
|
|
|
|
// Update context.
|
|
|
|
ctx.X += chap.margins.left
|
|
|
|
ctx.Y += chap.margins.top
|
|
|
|
ctx.Width -= chap.margins.left + chap.margins.right
|
|
|
|
ctx.Height -= chap.margins.top
|
|
|
|
}
|
|
|
|
|
2019-03-06 19:25:04 +02:00
|
|
|
blocks, c, err := chap.heading.GeneratePageBlocks(ctx)
|
2017-07-05 23:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return blocks, ctx, err
|
|
|
|
}
|
2019-03-06 19:25:04 +02:00
|
|
|
ctx = c
|
2017-07-06 16:26:22 +00:00
|
|
|
|
2019-01-16 21:50:10 +02:00
|
|
|
// Generate chapter title and number.
|
2019-03-06 19:25:04 +02:00
|
|
|
posX := ctx.X
|
|
|
|
posY := ctx.Y - chap.heading.Height()
|
2019-01-16 21:50:10 +02:00
|
|
|
page := int64(ctx.Page)
|
2019-03-06 19:25:04 +02:00
|
|
|
|
2019-03-05 21:39:55 +02:00
|
|
|
chapNumber := chap.headingNumber()
|
|
|
|
chapTitle := chap.headingText()
|
2019-01-16 21:50:10 +02:00
|
|
|
|
|
|
|
// Add to TOC.
|
|
|
|
if chap.includeInTOC {
|
2019-03-05 21:39:55 +02:00
|
|
|
line := chap.toc.Add(chapNumber, chap.title, strconv.FormatInt(page, 10), chap.level)
|
2019-01-11 17:10:00 +02:00
|
|
|
if chap.toc.showLinks {
|
2019-03-06 19:25:04 +02:00
|
|
|
line.SetLink(page, posX, posY)
|
2019-01-11 17:10:00 +02:00
|
|
|
}
|
2017-07-06 16:26:22 +00:00
|
|
|
}
|
2017-07-05 23:10:57 +00:00
|
|
|
|
2019-01-16 21:50:10 +02:00
|
|
|
// Add to outline.
|
|
|
|
if chap.outlineItem == nil {
|
|
|
|
chap.outlineItem = model.NewOutlineItem(
|
|
|
|
chapTitle,
|
2019-03-06 19:25:04 +02:00
|
|
|
model.NewOutlineDest(page-1, posX, posY),
|
2019-01-16 21:50:10 +02:00
|
|
|
)
|
2019-03-05 21:39:55 +02:00
|
|
|
|
|
|
|
if chap.parent != nil {
|
|
|
|
chap.parent.outlineItem.Add(chap.outlineItem)
|
|
|
|
} else {
|
|
|
|
chap.outline.Add(chap.outlineItem)
|
|
|
|
}
|
2019-01-16 21:50:10 +02:00
|
|
|
} else {
|
|
|
|
outlineDest := &chap.outlineItem.Dest
|
|
|
|
outlineDest.Page = page - 1
|
2019-03-06 19:25:04 +02:00
|
|
|
outlineDest.X = posX
|
|
|
|
outlineDest.Y = posY
|
2019-01-16 21:50:10 +02:00
|
|
|
}
|
|
|
|
|
2017-07-05 23:10:57 +00:00
|
|
|
for _, d := range chap.contents {
|
|
|
|
newBlocks, c, err := d.GeneratePageBlocks(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return blocks, ctx, err
|
|
|
|
}
|
|
|
|
if len(newBlocks) < 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// The first block is always appended to the last..
|
|
|
|
blocks[len(blocks)-1].mergeBlocks(newBlocks[0])
|
|
|
|
blocks = append(blocks, newBlocks[1:]...)
|
|
|
|
|
|
|
|
ctx = c
|
|
|
|
}
|
|
|
|
|
2017-07-14 13:55:17 +00:00
|
|
|
if chap.positioning.isRelative() {
|
|
|
|
// Move back X to same start of line.
|
|
|
|
ctx.X = origCtx.X
|
|
|
|
}
|
|
|
|
|
|
|
|
if chap.positioning.isAbsolute() {
|
|
|
|
// If absolute: return original context.
|
|
|
|
return blocks, origCtx, nil
|
|
|
|
}
|
|
|
|
|
2017-07-05 23:10:57 +00:00
|
|
|
return blocks, ctx, nil
|
|
|
|
}
|