Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 72 additions & 80 deletions cache_content_text.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,45 +160,50 @@ func (c *cacheContentText) write(w io.Writer, protection *PDFProtection) error {
if c.txtColorMode == "color" {
c.textColor.write(w, protection)
}
io.WriteString(w, "[<")

unitsPerEm := int(c.fontSubset.ttfp.UnitsPerEm())
var leftRune rune
var leftRuneIndex uint
for i, r := range c.text {

glyphindex, err := c.fontSubset.CharIndex(r)
if err == ErrCharNotFound {
continue
} else if err != nil {
return err
// shape the text and collect glyphs with advances and offsets
glyphs, adv, xoffs, yoffs, err := c.fontSubset.shapeTextMetrics(c.text, c.fontSize, c.charSpacing)
if err != nil {
return err
}
upem := int(c.fontSubset.ttfp.UnitsPerEm())
// Calibrate shaped advances to base widths so totals match even if font scale differs
sumAdv := 0
sumBase := 0
widths := c.fontSubset.ttfp.Widths()
for i := range glyphs {
sumAdv += adv[i]
gi := int(glyphs[i])
if gi < len(widths) {
sumBase += int(widths[gi])
} else if len(widths) > 0 {
sumBase += int(widths[len(widths)-1])
}
}

pairvalPdfUnit := 0
if i > 0 && c.fontSubset.ttfFontOption.UseKerning { //kerning
pairval := kern(c.fontSubset, leftRune, r, leftRuneIndex, glyphindex)
pairvalPdfUnit = convertTTFUnit2PDFUnit(int(pairval), unitsPerEm)
if pairvalPdfUnit != 0 {
fmt.Fprintf(w, ">%d<", (-1)*pairvalPdfUnit)
}
// Strategy: write normal glyphs in a single TJ segment using shaped advances only (no offsets),
// and isolate zero-advance marks to apply X/Y offsets locally with Ts/Td while cancelling width.
n := len(glyphs)

xaccTTF := 0 // accumulate in TTF units
pairs := 0
for i := 0; i < n; i++ {
xAll := xaccTTF + xoffs[i]
xpts := x + float64(convertTTFUnit2PDFUnit(xAll, upem))*(c.fontSize/1000.0) + float64(pairs)*c.charSpacing
ypts := y + float64(convertTTFUnit2PDFUnit(yoffs[i], upem))*(c.fontSize/1000.0)
fmt.Fprintf(w, "1 0 0 1 %s %s Tm <%04X> Tj\n", FormatFloatTrim(xpts), FormatFloatTrim(ypts), glyphs[i])
// accumulate raw advance in TTF units
xaccTTF += adv[i]
if i+1 < n {
pairs++
}

fmt.Fprintf(w, "%04X", glyphindex)
leftRune = r
leftRuneIndex = glyphindex
}

io.WriteString(w, ">] TJ\n")
io.WriteString(w, "ET\n")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dead code

Copy link
Copy Markdown
Author

@netDinger netDinger Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have just fixed that. Kindly review my commit please.


if c.fontStyle&Underline == Underline {
if err := c.underline(w); err != nil {
return err
}
}

c.drawBorder(w)

return nil
}

Expand Down Expand Up @@ -308,39 +313,47 @@ func (c *cacheContentText) createContent() (float64, float64, error) {
}

func createContent(f *SubsetFontObj, text string, fontSize float64, charSpacing float64, rectangle *Rect) (float64, float64, float64, error) {

unitsPerEm := int(f.ttfp.UnitsPerEm())
var leftRune rune
var leftRuneIndex uint
sumWidth := int(0)
//fmt.Printf("unitsPerEm = %d", unitsPerEm)
for i, r := range text {

glyphindex, err := f.CharIndex(r)
if err == ErrCharNotFound {
continue
} else if err != nil {
return 0, 0, 0, err
}

pairvalPdfUnit := 0
if i > 0 && f.ttfFontOption.UseKerning { //kerning
pairval := kern(f, leftRune, r, leftRuneIndex, glyphindex)
pairvalPdfUnit = convertTTFUnit2PDFUnit(int(pairval), unitsPerEm)
}

width, err := f.CharWidth(r)
if err != nil {
return 0, 0, 0, err
// Use shaping to compute precise width (including GPOS kerning). Offsets do not affect advance width.
glyphs, adv, _, _, err := f.shapeTextMetrics(text, fontSize, charSpacing)
if err != nil {
return 0, 0, 0, err
}
// Calibrate shaped advances to base widths to keep placement and width consistent
sumAdv := 0
sumBase := 0
widths := f.ttfp.Widths()
for i := range glyphs {
sumAdv += adv[i]
gi := int(glyphs[i])
if gi < len(widths) {
sumBase += int(widths[gi])
} else if len(widths) > 0 {
sumBase += int(widths[len(widths)-1])
}

}
alpha := 1.0
if sumAdv != 0 {
alpha = float64(sumBase) / float64(sumAdv)
}
// Sum width in PDF units using: sum(adv[0..n-2]) + base[n-1]
unitsPerEm := int(f.ttfp.UnitsPerEm())
advSumPdf := 0
n := len(glyphs)
for i := 0; i < n-1; i++ {
advScaledTTF := int(math.Round(float64(adv[i]) * alpha))
advSumPdf += convertTTFUnit2PDFUnit(advScaledTTF, unitsPerEm)
}
lastBasePdf := 0
if n > 0 {
lastBasePdf = int(f.GlyphIndexToPdfWidth(glyphs[n-1]))
}
sumWidth := advSumPdf + lastBasePdf
// Add charSpacing between glyphs (N-1 times), as applied by the PDF Tc operator
if n > 1 && charSpacing != 0 {
unitsPerPt := float64(unitsPerEm) / fontSize
spaceWidthInPt := unitsPerPt * charSpacing
spaceWidthPdfUnit := convertTTFUnit2PDFUnit(int(spaceWidthInPt), unitsPerEm)

sumWidth += int(width) + int(pairvalPdfUnit) + spaceWidthPdfUnit
leftRune = r
leftRuneIndex = glyphindex
spaceWidthInTtf := unitsPerPt * charSpacing
spaceWidthPdfUnit := convertTTFUnit2PDFUnit(int(spaceWidthInTtf), unitsPerEm)
sumWidth += (n - 1) * spaceWidthPdfUnit
}

cellWidthPdfUnit := float64(0)
Expand All @@ -358,27 +371,6 @@ func createContent(f *SubsetFontObj, text string, fontSize float64, charSpacing
return cellWidthPdfUnit, cellHeightPdfUnit, textWidthPdfUnit, nil
}

func kern(f *SubsetFontObj, leftRune rune, rightRune rune, leftIndex uint, rightIndex uint) int16 {

pairVal := int16(0)
if haveKerning, kval := f.KernValueByLeft(leftIndex); haveKerning {
if ok, v := kval.ValueByRight(rightIndex); ok {
pairVal = v
}
}

if f.funcKernOverride != nil {
pairVal = f.funcKernOverride(
leftRune,
rightRune,
leftIndex,
rightIndex,
pairVal,
)
}
return pairVal
}

// CacheContent Export cacheContent
type CacheContent struct {
cacheContentText
Expand Down
13 changes: 13 additions & 0 deletions cid_font_obj.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,22 @@ func (ci *CIDFontObj) write(w io.Writer, objID int) error {
fmt.Fprintf(w, "/FontDescriptor %d 0 R\n", ci.indexObjSubfontDescriptor+1) //TODO fix
io.WriteString(w, "/Subtype /CIDFontType2\n")
io.WriteString(w, "/Type /Font\n")
// Build width list including shaped-only glyphs
glyphIndexs := ci.PtrToSubsetFontObj.CharacterToGlyphIndex.AllVals()
if extra := ci.PtrToSubsetFontObj.ExtraGlyphs(); extra != nil {
for gid := range extra {
glyphIndexs = append(glyphIndexs, gid)
}
}
// de-duplicate while preserving order (small lists)
seen := make(map[uint]bool)
io.WriteString(w, "/DW 0\n")
io.WriteString(w, "/W [")
for _, v := range glyphIndexs {
if seen[v] {
continue
}
seen[v] = true
width := ci.PtrToSubsetFontObj.GlyphIndexToPdfWidth(v)
fmt.Fprintf(w, "%d[%d]", v, width)
}
Expand Down
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
module github.com/signintech/gopdf

go 1.13
go 1.20

require github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311
require (
github.com/go-text/typesetting v0.3.0
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311
)

require (
github.com/pkg/errors v0.8.1 // indirect
golang.org/x/image v0.23.0 // indirect
)
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 h1:zyWXQ6vu27ETMpYsEMAsisQ+GqJ4e1TPvSNfdOPF0no=
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
6 changes: 6 additions & 0 deletions pdf_dictionary_obj.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ func (p *PdfDictionaryObj) makeGlyfAndLocaTable() ([]byte, []int, error) {
numGlyphs := int(ttfp.NumGlyphs())

glyphArray := p.completeGlyphClosure(p.PtrToSubsetFontObj.CharacterToGlyphIndex)
// also include any shaped-only glyphs collected during layout
if extra := p.PtrToSubsetFontObj.ExtraGlyphs(); extra != nil {
for gid := range extra {
glyphArray = append(glyphArray, int(gid))
}
}
sort.Ints(glyphArray)
glyphArray = p.distinctInts(glyphArray)
glyphCount := len(glyphArray)
Expand Down
6 changes: 6 additions & 0 deletions subset_font_obj.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"io"

"github.com/go-text/typesetting/font"
"github.com/go-text/typesetting/harfbuzz"
"github.com/signintech/gopdf/fontmaker/core"
)

Expand All @@ -26,6 +28,10 @@ type SubsetFontObj struct {
funcKernOverride FuncKernOverride
funcGetRoot func() *GoPdf
addCharsBuff []rune
// harfbuzz/typesetting integration
hbFace *font.Face
hbFont *harfbuzz.Font
extraGlyphs map[uint]rune
}

func (s *SubsetFontObj) init(funcGetRoot func() *GoPdf) {
Expand Down
Binary file removed test/res/LiberationSerif-Regular.ttf
Binary file not shown.
Loading
Loading