mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-24 13:48:49 +08:00
Paragraph components fixes (#294)
* Take spaces into account when wrapping styled paragraph text * Fix starting position of styled paragraphs * Wrap text unconditionally when style paragraph height is requested * Fix styled paragraph text wrapping edge case * Fix text chunk wrapping edge case * Adapt text chunk test * Wrap text unconditionally when paragraph height is requested * Add text wrapping test case for styled paragraph using character spacing
This commit is contained in:
parent
29efa30439
commit
5a21f16e83
@ -186,10 +186,7 @@ func (p *Paragraph) Width() float64 {
|
||||
// Height returns the height of the Paragraph. The height is calculated based on the input text and
|
||||
// how it is wrapped within the container. Does not include Margins.
|
||||
func (p *Paragraph) Height() float64 {
|
||||
if p.textLines == nil || len(p.textLines) == 0 {
|
||||
p.wrapText()
|
||||
}
|
||||
|
||||
p.wrapText()
|
||||
return float64(len(p.textLines)) * p.lineHeight * p.fontSize
|
||||
}
|
||||
|
||||
|
@ -206,9 +206,7 @@ func (p *StyledParagraph) Width() float64 {
|
||||
// Height returns the height of the Paragraph. The height is calculated based on the input text and how it is wrapped
|
||||
// within the container. Does not include Margins.
|
||||
func (p *StyledParagraph) Height() float64 {
|
||||
if p.lines == nil || len(p.lines) == 0 {
|
||||
p.wrapText()
|
||||
}
|
||||
p.wrapText()
|
||||
|
||||
var height float64
|
||||
for _, line := range p.lines {
|
||||
@ -296,7 +294,7 @@ func (p *StyledParagraph) getTextWidth() float64 {
|
||||
width += style.FontSize * metrics.Wx
|
||||
|
||||
// Do not add character spacing for the last character of the line.
|
||||
if i != lenChunks-1 || j != lenRunes-1 {
|
||||
if r != ' ' && (i != lenChunks-1 || j != lenRunes-1) {
|
||||
width += style.CharSpacing * 1000.0
|
||||
}
|
||||
}
|
||||
@ -331,7 +329,7 @@ func (p *StyledParagraph) getTextLineWidth(line []*TextChunk) float64 {
|
||||
width += style.FontSize * metrics.Wx
|
||||
|
||||
// Do not add character spacing for the last character of the line.
|
||||
if i != lenChunks-1 || j != lenRunes-1 {
|
||||
if r != ' ' && (i != lenChunks-1 || j != lenRunes-1) {
|
||||
width += style.CharSpacing * 1000.0
|
||||
}
|
||||
}
|
||||
@ -425,15 +423,19 @@ func (p *StyledParagraph) wrapText() error {
|
||||
widths = nil
|
||||
continue
|
||||
}
|
||||
isSpace := r == ' '
|
||||
|
||||
metrics, found := style.Font.GetRuneMetrics(r)
|
||||
if !found {
|
||||
common.Log.Debug("Rune char metrics not found! %v\n", r)
|
||||
return errors.New("glyph char metrics missing")
|
||||
}
|
||||
|
||||
w := style.FontSize * metrics.Wx
|
||||
charWidth := w + style.CharSpacing*1000.0
|
||||
|
||||
charWidth := w
|
||||
if !isSpace {
|
||||
charWidth = w + style.CharSpacing*1000.0
|
||||
}
|
||||
|
||||
if lineWidth+w > p.wrapWidth*1000.0 {
|
||||
// Goes out of bounds: Wrap.
|
||||
@ -441,10 +443,12 @@ func (p *StyledParagraph) wrapText() error {
|
||||
// TODO: when goes outside: back up to next space,
|
||||
// otherwise break on the character.
|
||||
idx := -1
|
||||
for j := len(part) - 1; j >= 0; j-- {
|
||||
if part[j] == ' ' {
|
||||
idx = j
|
||||
break
|
||||
if !isSpace {
|
||||
for j := len(part) - 1; j >= 0; j-- {
|
||||
if part[j] == ' ' {
|
||||
idx = j
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -462,9 +466,15 @@ func (p *StyledParagraph) wrapText() error {
|
||||
lineWidth += width
|
||||
}
|
||||
} else {
|
||||
lineWidth = charWidth
|
||||
part = []rune{r}
|
||||
widths = []float64{charWidth}
|
||||
if isSpace {
|
||||
lineWidth = 0
|
||||
part = []rune{}
|
||||
widths = []float64{}
|
||||
} else {
|
||||
lineWidth = charWidth
|
||||
part = []rune{r}
|
||||
widths = []float64{charWidth}
|
||||
}
|
||||
}
|
||||
|
||||
line = append(line, &TextChunk{
|
||||
@ -589,13 +599,19 @@ func drawStyledParagraphOnBlock(blk *Block, p *StyledParagraph, ctx DrawContext)
|
||||
// Add the fonts of all chunks to the page resources.
|
||||
var fonts [][]core.PdfObjectName
|
||||
|
||||
for _, line := range p.lines {
|
||||
var yOffset float64
|
||||
for i, line := range p.lines {
|
||||
var fontLine []core.PdfObjectName
|
||||
|
||||
for _, chunk := range line {
|
||||
style := chunk.Style
|
||||
if i == 0 && style.FontSize > yOffset {
|
||||
yOffset = style.FontSize
|
||||
}
|
||||
|
||||
fontName = core.PdfObjectName(fmt.Sprintf("Font%d", num))
|
||||
|
||||
err := blk.resources.SetFontByName(fontName, chunk.Style.Font.ToPdfObject())
|
||||
err := blk.resources.SetFontByName(fontName, style.Font.ToPdfObject())
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
@ -611,7 +627,7 @@ func drawStyledParagraphOnBlock(blk *Block, p *StyledParagraph, ctx DrawContext)
|
||||
cc := contentstream.NewContentCreator()
|
||||
cc.Add_q()
|
||||
|
||||
yPos := ctx.PageHeight - ctx.Y - defaultFontSize*p.lineHeight
|
||||
yPos := ctx.PageHeight - ctx.Y - yOffset*p.lineHeight
|
||||
cc.Translate(ctx.X, yPos)
|
||||
|
||||
if p.angle != 0 {
|
||||
|
@ -8,6 +8,7 @@ package creator
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/unidoc/unipdf/v3/model"
|
||||
)
|
||||
|
||||
@ -861,3 +862,26 @@ func TestStyledParagraphTableVerticalAlignment(t *testing.T) {
|
||||
// Write output file.
|
||||
testWriteAndRender(t, c, "styled_paragraph_table_vertical_align.pdf")
|
||||
}
|
||||
|
||||
func TestStyledParagraphCharacterSpaceWrapping(t *testing.T) {
|
||||
c := New()
|
||||
var posX, posY, width float64 = 10, 10, 120
|
||||
|
||||
// Draw paragraph.
|
||||
p := c.NewStyledParagraph()
|
||||
p.SetPos(posX, posY)
|
||||
p.SetWidth(width)
|
||||
|
||||
chunk := p.Append("s o m e t e x t s o m e t e x t s o m e t e x t s o m e t e x t s o m e t e x t")
|
||||
chunk.Style.FontSize = 8
|
||||
chunk.Style.CharSpacing = 5
|
||||
require.NoError(t, c.Draw(p))
|
||||
|
||||
// Draw border.
|
||||
border := c.NewRectangle(posX, posY, p.Width(), p.Height())
|
||||
border.SetBorderColor(ColorRed)
|
||||
require.NoError(t, c.Draw(border))
|
||||
|
||||
// Write output file.
|
||||
testWriteAndRender(t, c, "styled_paragraph_character_space_wrapping.pdf")
|
||||
}
|
||||
|
@ -540,7 +540,7 @@ func (table *Table) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
|
||||
}
|
||||
|
||||
// Account for the top offset the paragraph adds.
|
||||
vertOffset = lineCapHeight - t.defaultStyle.FontSize*t.lineHeight
|
||||
vertOffset = lineCapHeight - lineHeight
|
||||
|
||||
switch cell.verticalAlignment {
|
||||
case CellVerticalAlignmentTop:
|
||||
|
@ -68,6 +68,7 @@ func (tc *TextChunk) Wrap(width float64) ([]string, error) {
|
||||
widths = nil
|
||||
continue
|
||||
}
|
||||
isSpace := r == ' '
|
||||
|
||||
metrics, found := style.Font.GetRuneMetrics(r)
|
||||
if !found {
|
||||
@ -77,21 +78,29 @@ func (tc *TextChunk) Wrap(width float64) ([]string, error) {
|
||||
common.Log.Trace("Encoder: %#v", style.Font.Encoder())
|
||||
return nil, errors.New("glyph char metrics missing")
|
||||
}
|
||||
|
||||
w := style.FontSize * metrics.Wx
|
||||
charWidth := w + style.CharSpacing*1000.0
|
||||
|
||||
charWidth := w
|
||||
if !isSpace {
|
||||
charWidth = w + style.CharSpacing*1000.0
|
||||
}
|
||||
|
||||
if lineWidth+w > width*1000.0 {
|
||||
// Goes out of bounds. Break on the character.
|
||||
idx := -1
|
||||
for i := len(line) - 1; i >= 0; i-- {
|
||||
if line[i] == ' ' {
|
||||
idx = i
|
||||
break
|
||||
if !isSpace {
|
||||
for i := len(line) - 1; i >= 0; i-- {
|
||||
if line[i] == ' ' {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text := string(line)
|
||||
if idx > 0 {
|
||||
// Back up to last space.
|
||||
lines = append(lines, strings.TrimRightFunc(string(line[0:idx+1]), unicode.IsSpace))
|
||||
text = string(line[0 : idx+1])
|
||||
|
||||
// Remainder of line.
|
||||
line = append(line[idx+1:], r)
|
||||
@ -102,11 +111,18 @@ func (tc *TextChunk) Wrap(width float64) ([]string, error) {
|
||||
lineWidth += width
|
||||
}
|
||||
} else {
|
||||
lines = append(lines, strings.TrimRightFunc(string(line), unicode.IsSpace))
|
||||
line = []rune{r}
|
||||
widths = []float64{charWidth}
|
||||
lineWidth = charWidth
|
||||
if isSpace {
|
||||
line = []rune{}
|
||||
widths = []float64{}
|
||||
lineWidth = 0
|
||||
} else {
|
||||
line = []rune{r}
|
||||
widths = []float64{charWidth}
|
||||
lineWidth = charWidth
|
||||
}
|
||||
}
|
||||
|
||||
lines = append(lines, strings.TrimRightFunc(text, unicode.IsSpace))
|
||||
} else {
|
||||
line = append(line, r)
|
||||
lineWidth += charWidth
|
||||
|
@ -58,8 +58,8 @@ func TestTextChunkWrap(t *testing.T) {
|
||||
"irure dolor in",
|
||||
"reprehenderit in",
|
||||
"voluptate velit esse",
|
||||
"cillum dolore eu",
|
||||
"fugiat nulla pariatur.",
|
||||
"cillum dolore eu fugiat",
|
||||
"nulla pariatur.",
|
||||
"Excepteur sint",
|
||||
"occaecat cupidatat",
|
||||
"non proident, sunt in",
|
||||
|
Loading…
x
Reference in New Issue
Block a user