Add TOCLine component

This commit is contained in:
Adrian-George Bostan 2018-09-27 20:55:58 +03:00
parent 6ff5fa3c02
commit 138b240ae2
4 changed files with 203 additions and 6 deletions

View File

@ -63,6 +63,9 @@ type StyledParagraph struct {
// Text chunk lines after wrapping to available width.
lines [][]TextChunk
// Before render callback
beforeRender func(p *StyledParagraph, ctx DrawContext)
}
// NewStyledParagraph creates a new styled paragraph.
@ -104,6 +107,23 @@ func (p *StyledParagraph) Append(text string, style TextStyle) {
p.wrapText()
}
// Insert adds a new text chunk at the specified position in the paragraph.
func (p *StyledParagraph) Insert(index uint, text string, style TextStyle) {
l := uint(len(p.chunks))
if index > l {
index = l
}
chunk := TextChunk{
Text: text,
Style: style,
}
chunk.Style.Font.SetEncoder(p.encoder)
p.chunks = append(p.chunks[:index], append([]TextChunk{chunk}, p.chunks[index:]...)...)
p.wrapText()
}
// Reset sets the entire text and also the style of the paragraph
// to those specified. It behaves as if the paragraph was a new one.
func (p *StyledParagraph) Reset(text string, style TextStyle) {
@ -238,6 +258,41 @@ func (p *StyledParagraph) getTextWidth() float64 {
return width
}
// getTextLineWidth calculates the text width of a provided collection of text chunks
func (p *StyledParagraph) getTextLineWidth(line []TextChunk) float64 {
var width float64
for _, chunk := range line {
style := &chunk.Style
for _, rune := range chunk.Text {
glyph, found := p.encoder.RuneToGlyph(rune)
if !found {
common.Log.Debug("Error! Glyph not found for rune: %s\n", rune)
// XXX/FIXME: return error.
return -1
}
// Ignore newline for this.. Handles as if all in one line.
if glyph == "controlLF" {
continue
}
metrics, found := style.Font.GetGlyphCharMetrics(glyph)
if !found {
common.Log.Debug("Glyph char metrics not found! %s\n", glyph)
// XXX/FIXME: return error.
return -1
}
width += style.FontSize * metrics.Wx
}
}
return width
}
// getTextHeight calculates the text height as if all in one line (not taking wrapping into account).
func (p *StyledParagraph) getTextHeight() float64 {
var height float64
@ -415,6 +470,10 @@ func (p *StyledParagraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawCon
ctx.Y = p.yPos
}
if p.beforeRender != nil {
p.beforeRender(p, ctx)
}
// Place the Paragraph on the template at position (x,y) based on the ctx.
ctx, err := drawStyledParagraphOnBlock(blk, p, ctx)
if err != nil {

15
pdf/creator/text_chunk.go Normal file
View File

@ -0,0 +1,15 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package creator
// TextChunk represents a chunk of text along with a particular style.
type TextChunk struct {
// The text that is being rendered in the PDF.
Text string
// The style of the text being rendered.
Style TextStyle
}

View File

@ -35,9 +35,3 @@ func NewTextStyle() TextStyle {
FontSize: 10,
}
}
// TextChunk represents a chunk of text along with a particular style.
type TextChunk struct {
Text string
Style TextStyle
}

129
pdf/creator/toc_line.go Normal file
View File

@ -0,0 +1,129 @@
/*
* 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 (
"strings"
)
type TOCLine struct {
sp *StyledParagraph
number TextChunk
title TextChunk
page TextChunk
separator TextChunk
level uint
levelOffset float64
}
func NewTOCLine(number, title, page string, level uint) *TOCLine {
style := NewTextStyle()
return NewStyledTOCLine(
TextChunk{Text: number, Style: style},
TextChunk{Text: title, Style: style},
TextChunk{Text: page, Style: style},
level,
)
}
func NewStyledTOCLine(number, title, page TextChunk, level uint) *TOCLine {
style := NewTextStyle()
sp := NewStyledParagraph("", style)
sp.SetEnableWrap(true)
sp.SetTextAlignment(TextAlignmentLeft)
tl := &TOCLine{
sp: sp,
number: number,
title: title,
page: page,
separator: TextChunk{
Text: ".",
Style: style,
},
level: level,
levelOffset: 10,
}
sp.margins.left = float64(level) * tl.levelOffset
sp.beforeRender = tl.prepareParagraph
return tl
}
func (tl *TOCLine) prepareParagraph(sp *StyledParagraph, ctx DrawContext) {
if tl.number.Text != "" {
tl.title.Text = " " + tl.title.Text
}
tl.title.Text += " "
if tl.page.Text != "" {
tl.page.Text = " " + tl.page.Text
}
sp.chunks = []TextChunk{
tl.number,
tl.title,
tl.page,
}
sp.SetEncoder(sp.encoder)
sp.wrapText()
l := len(sp.lines)
if l == 0 {
return
}
// Insert separator
availWidth := ctx.Width*1000 - sp.getTextLineWidth(sp.lines[l-1])
sepWidth := sp.getTextLineWidth([]TextChunk{tl.separator})
sepCount := int(availWidth / sepWidth)
sepText := strings.Repeat(tl.separator.Text, sepCount)
sp.Insert(2, sepText, tl.separator.Style)
// Push page numbers to the end of the line
availWidth = availWidth - float64(sepCount)*sepWidth
if availWidth > 500 {
spaceMetrics, found := tl.separator.Style.Font.GetGlyphCharMetrics("space")
if found && availWidth > spaceMetrics.Wx {
spaces := int(availWidth / spaceMetrics.Wx)
if spaces > 0 {
style := tl.separator.Style
style.FontSize = 1
sp.Insert(2, strings.Repeat(" ", spaces), style)
}
}
}
}
func (tl *TOCLine) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
origCtx := ctx
blocks, ctx, err := tl.sp.GeneratePageBlocks(ctx)
if err != nil {
return blocks, ctx, err
}
if len(blocks) > 1 {
// Did not fit, moved to new Page block.
ctx.Page++
}
if tl.sp.positioning.isRelative() {
// Move back X to same start of line.
ctx.X = origCtx.X
}
if tl.sp.positioning.isAbsolute() {
// If absolute: return original context.
return blocks, origCtx, nil
}
return blocks, ctx, nil
}