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.
|
||||
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
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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