mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-27 13:48:51 +08:00
Add TOCLine component
This commit is contained in:
parent
6ff5fa3c02
commit
138b240ae2
@ -63,6 +63,9 @@ type StyledParagraph struct {
|
|||||||
|
|
||||||
// Text chunk lines after wrapping to available width.
|
// Text chunk lines after wrapping to available width.
|
||||||
lines [][]TextChunk
|
lines [][]TextChunk
|
||||||
|
|
||||||
|
// Before render callback
|
||||||
|
beforeRender func(p *StyledParagraph, ctx DrawContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStyledParagraph creates a new styled paragraph.
|
// NewStyledParagraph creates a new styled paragraph.
|
||||||
@ -104,6 +107,23 @@ func (p *StyledParagraph) Append(text string, style TextStyle) {
|
|||||||
p.wrapText()
|
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
|
// 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.
|
// to those specified. It behaves as if the paragraph was a new one.
|
||||||
func (p *StyledParagraph) Reset(text string, style TextStyle) {
|
func (p *StyledParagraph) Reset(text string, style TextStyle) {
|
||||||
@ -238,6 +258,41 @@ func (p *StyledParagraph) getTextWidth() float64 {
|
|||||||
return width
|
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).
|
// getTextHeight calculates the text height as if all in one line (not taking wrapping into account).
|
||||||
func (p *StyledParagraph) getTextHeight() float64 {
|
func (p *StyledParagraph) getTextHeight() float64 {
|
||||||
var height float64
|
var height float64
|
||||||
@ -415,6 +470,10 @@ func (p *StyledParagraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawCon
|
|||||||
ctx.Y = p.yPos
|
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.
|
// Place the Paragraph on the template at position (x,y) based on the ctx.
|
||||||
ctx, err := drawStyledParagraphOnBlock(blk, p, ctx)
|
ctx, err := drawStyledParagraphOnBlock(blk, p, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
15
pdf/creator/text_chunk.go
Normal file
15
pdf/creator/text_chunk.go
Normal 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
|
||||||
|
}
|
@ -35,9 +35,3 @@ func NewTextStyle() TextStyle {
|
|||||||
FontSize: 10,
|
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
129
pdf/creator/toc_line.go
Normal 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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user