mirror of
https://github.com/unidoc/unipdf.git
synced 2025-05-02 22:17:06 +08:00

* Add render package * Add text state * Add more text operators * Remove unnecessary files * Add text font * Add custom text render method * Improve text rendering method * Rename text state methods * Refactor and document context interface * Refact text begin/end operators * Fix graphics state transformations * Keep original font when doing font substitution * Take page cropbox into account * Revert to substitution font if original font measurement is 0 * Add font substitution package * Implement addition transform.Point methods * Use transform.Point in the image context package * Remove unneeded functionality from the render image package * Fix golint notices in the image rendering package * Fix go vet notices in the render package * Fix golint notices in the top-level render package * Improve render context package documentation * Document context text state struct. * Document context text font struct. * Minor logging improvements * Add license disclaimer to the render package files * Avoid using package aliases where possible * Change style of section comments * Adapt render package import style to follow the developer guide * Improve documentation for the internal matrix implementation * Update render package dependency versions * Apply crop box post render * Account for offseted media boxes * Improve metrics of rendered characters * Fix text matrix translation * Change priority of fonts used for measuring rendered characters * Skip invalid m and l operators on image rendering * Small fix for v operator * Fix rendered characters spacing issues * Refactor naming of internal render packages
171 lines
3.6 KiB
Go
171 lines
3.6 KiB
Go
/*
|
|
* This file is subject to the terms and conditions defined in
|
|
* file 'LICENSE.md', which is part of this source code package.
|
|
*/
|
|
|
|
package imagerender
|
|
|
|
import (
|
|
"math"
|
|
|
|
"github.com/golang/freetype/raster"
|
|
"golang.org/x/image/math/fixed"
|
|
|
|
"github.com/unidoc/unipdf/v3/internal/transform"
|
|
)
|
|
|
|
func flattenPath(p raster.Path) [][]transform.Point {
|
|
var result [][]transform.Point
|
|
var path []transform.Point
|
|
var cx, cy float64
|
|
for i := 0; i < len(p); {
|
|
switch p[i] {
|
|
case 0:
|
|
if len(path) > 0 {
|
|
result = append(result, path)
|
|
path = nil
|
|
}
|
|
x := unfix(p[i+1])
|
|
y := unfix(p[i+2])
|
|
path = append(path, transform.NewPoint(x, y))
|
|
cx, cy = x, y
|
|
i += 4
|
|
case 1:
|
|
x := unfix(p[i+1])
|
|
y := unfix(p[i+2])
|
|
path = append(path, transform.NewPoint(x, y))
|
|
cx, cy = x, y
|
|
i += 4
|
|
case 2:
|
|
x1 := unfix(p[i+1])
|
|
y1 := unfix(p[i+2])
|
|
x2 := unfix(p[i+3])
|
|
y2 := unfix(p[i+4])
|
|
points := quadraticBezier(cx, cy, x1, y1, x2, y2)
|
|
path = append(path, points...)
|
|
cx, cy = x2, y2
|
|
i += 6
|
|
case 3:
|
|
x1 := unfix(p[i+1])
|
|
y1 := unfix(p[i+2])
|
|
x2 := unfix(p[i+3])
|
|
y2 := unfix(p[i+4])
|
|
x3 := unfix(p[i+5])
|
|
y3 := unfix(p[i+6])
|
|
points := cubicBezier(cx, cy, x1, y1, x2, y2, x3, y3)
|
|
path = append(path, points...)
|
|
cx, cy = x3, y3
|
|
i += 8
|
|
default:
|
|
panic("bad path")
|
|
}
|
|
}
|
|
if len(path) > 0 {
|
|
result = append(result, path)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func dashPath(paths [][]transform.Point, dashes []float64, offset float64) [][]transform.Point {
|
|
var result [][]transform.Point
|
|
if len(dashes) == 0 {
|
|
return paths
|
|
}
|
|
if len(dashes) == 1 {
|
|
dashes = append(dashes, dashes[0])
|
|
}
|
|
for _, path := range paths {
|
|
if len(path) < 2 {
|
|
continue
|
|
}
|
|
previous := path[0]
|
|
pathIndex := 1
|
|
dashIndex := 0
|
|
segmentLength := 0.0
|
|
|
|
// offset
|
|
if offset != 0 {
|
|
var totalLength float64
|
|
for _, dashLength := range dashes {
|
|
totalLength += dashLength
|
|
}
|
|
offset = math.Mod(offset, totalLength)
|
|
if offset < 0 {
|
|
offset += totalLength
|
|
}
|
|
for i, dashLength := range dashes {
|
|
offset -= dashLength
|
|
if offset < 0 {
|
|
dashIndex = i
|
|
segmentLength = dashLength + offset
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
var segment []transform.Point
|
|
segment = append(segment, previous)
|
|
for pathIndex < len(path) {
|
|
dashLength := dashes[dashIndex]
|
|
point := path[pathIndex]
|
|
d := previous.Distance(point)
|
|
maxd := dashLength - segmentLength
|
|
if d > maxd {
|
|
t := maxd / d
|
|
p := previous.Interpolate(point, t)
|
|
segment = append(segment, p)
|
|
if dashIndex%2 == 0 && len(segment) > 1 {
|
|
result = append(result, segment)
|
|
}
|
|
segment = nil
|
|
segment = append(segment, p)
|
|
segmentLength = 0
|
|
previous = p
|
|
dashIndex = (dashIndex + 1) % len(dashes)
|
|
} else {
|
|
segment = append(segment, point)
|
|
previous = point
|
|
segmentLength += d
|
|
pathIndex++
|
|
}
|
|
}
|
|
if dashIndex%2 == 0 && len(segment) > 1 {
|
|
result = append(result, segment)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func rasterPath(paths [][]transform.Point) raster.Path {
|
|
var result raster.Path
|
|
for _, path := range paths {
|
|
var previous fixed.Point26_6
|
|
for i, point := range path {
|
|
f := fixedPoint(point)
|
|
if i == 0 {
|
|
result.Start(f)
|
|
} else {
|
|
dx := f.X - previous.X
|
|
dy := f.Y - previous.Y
|
|
if dx < 0 {
|
|
dx = -dx
|
|
}
|
|
if dy < 0 {
|
|
dy = -dy
|
|
}
|
|
if dx+dy > 8 {
|
|
// TODO: this is a hack for cases where two points are
|
|
// too close - causes rendering issues with joins / caps
|
|
result.Add1(f)
|
|
}
|
|
}
|
|
previous = f
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func dashed(path raster.Path, dashes []float64, offset float64) raster.Path {
|
|
return rasterPath(dashPath(flattenPath(path), dashes, offset))
|
|
}
|