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:
Adrian-George Bostan 2020-04-07 02:58:22 +03:00 committed by GitHub
parent 29efa30439
commit 5a21f16e83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 35 deletions

View File

@ -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
}

View File

@ -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 {

View File

@ -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")
}

View File

@ -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:

View File

@ -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

View File

@ -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",