mirror of
https://github.com/unidoc/unipdf.git
synced 2025-05-02 22:17:06 +08:00
Merge branch 'v2' of https://github.com/unidoc/unidoc into up_v2_dev
This commit is contained in:
commit
a9c2a01404
180
pdf/annotator/annotator.go
Normal file
180
pdf/annotator/annotator.go
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package annotator
|
||||
|
||||
import (
|
||||
pdfcore "github.com/unidoc/unidoc/pdf/core"
|
||||
pdf "github.com/unidoc/unidoc/pdf/model"
|
||||
)
|
||||
|
||||
// The currently supported line ending styles are None, Arrow (ClosedArrow) and Butt.
|
||||
type LineEndingStyle int
|
||||
|
||||
const (
|
||||
LineEndingStyleNone LineEndingStyle = 0
|
||||
LineEndingStyleArrow LineEndingStyle = 1
|
||||
LineEndingStyleButt LineEndingStyle = 2
|
||||
)
|
||||
|
||||
// Defines a line between point 1 (X1,Y1) and point 2 (X2,Y2). The line ending styles can be none (regular line),
|
||||
// or arrows at either end. The line also has a specified width, color and opacity.
|
||||
type LineAnnotationDef struct {
|
||||
X1 float64
|
||||
Y1 float64
|
||||
X2 float64
|
||||
Y2 float64
|
||||
LineColor *pdf.PdfColorDeviceRGB
|
||||
Opacity float64 // Alpha value (0-1).
|
||||
LineWidth float64
|
||||
LineEndingStyle1 LineEndingStyle // Line ending style of point 1.
|
||||
LineEndingStyle2 LineEndingStyle // Line ending style of point 2.
|
||||
}
|
||||
|
||||
// Creates a line annotation object that can be added to page PDF annotations.
|
||||
func CreateLineAnnotation(lineDef LineAnnotationDef) (*pdf.PdfAnnotation, error) {
|
||||
// Line annotation.
|
||||
lineAnnotation := pdf.NewPdfAnnotationLine()
|
||||
|
||||
// Line endpoint locations.
|
||||
lineAnnotation.L = pdfcore.MakeArrayFromFloats([]float64{lineDef.X1, lineDef.Y1, lineDef.X2, lineDef.Y2})
|
||||
|
||||
// Line endings.
|
||||
le1 := pdfcore.MakeName("None")
|
||||
if lineDef.LineEndingStyle1 == LineEndingStyleArrow {
|
||||
le1 = pdfcore.MakeName("ClosedArrow")
|
||||
}
|
||||
le2 := pdfcore.MakeName("None")
|
||||
if lineDef.LineEndingStyle2 == LineEndingStyleArrow {
|
||||
le2 = pdfcore.MakeName("ClosedArrow")
|
||||
}
|
||||
lineAnnotation.LE = pdfcore.MakeArray(le1, le2)
|
||||
|
||||
// Opacity.
|
||||
if lineDef.Opacity < 1.0 {
|
||||
lineAnnotation.CA = pdfcore.MakeFloat(lineDef.Opacity)
|
||||
}
|
||||
|
||||
r, g, b := lineDef.LineColor.R(), lineDef.LineColor.G(), lineDef.LineColor.B()
|
||||
lineAnnotation.IC = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) // fill color of line endings, rgb 0-1.
|
||||
lineAnnotation.C = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) // line color, rgb 0-1.
|
||||
bs := pdf.NewBorderStyle()
|
||||
bs.SetBorderWidth(lineDef.LineWidth) // Line width: 3 points.
|
||||
lineAnnotation.BS = bs.ToPdfObject()
|
||||
|
||||
// Make the appearance stream (for uniform appearance).
|
||||
apDict, bbox, err := makeLineAnnotationAppearanceStream(lineDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lineAnnotation.AP = apDict
|
||||
|
||||
// The rect specifies the location and dimensions of the annotation. Technically if the annotation could not
|
||||
// be displayed if it goes outside these bounds, although rarely enforced.
|
||||
lineAnnotation.Rect = pdfcore.MakeArrayFromFloats([]float64{bbox.Llx, bbox.Lly, bbox.Urx, bbox.Ury})
|
||||
|
||||
return lineAnnotation.PdfAnnotation, nil
|
||||
}
|
||||
|
||||
// A rectangle defined with a specified Width and Height and a lower left corner at (X,Y). The rectangle can
|
||||
// optionally have a border and a filling color.
|
||||
// The Width/Height includes the border (if any specified).
|
||||
type RectangleAnnotationDef struct {
|
||||
X float64
|
||||
Y float64
|
||||
Width float64
|
||||
Height float64
|
||||
FillEnabled bool // Show fill?
|
||||
FillColor *pdf.PdfColorDeviceRGB
|
||||
BorderEnabled bool // Show border?
|
||||
BorderWidth float64
|
||||
BorderColor *pdf.PdfColorDeviceRGB
|
||||
Opacity float64 // Alpha value (0-1).
|
||||
}
|
||||
|
||||
// Creates a rectangle annotation object that can be added to page PDF annotations.
|
||||
func CreateRectangleAnnotation(rectDef RectangleAnnotationDef) (*pdf.PdfAnnotation, error) {
|
||||
rectAnnotation := pdf.NewPdfAnnotationSquare()
|
||||
|
||||
if rectDef.BorderEnabled {
|
||||
r, g, b := rectDef.BorderColor.R(), rectDef.BorderColor.G(), rectDef.BorderColor.B()
|
||||
rectAnnotation.C = pdfcore.MakeArrayFromFloats([]float64{r, g, b})
|
||||
bs := pdf.NewBorderStyle()
|
||||
bs.SetBorderWidth(rectDef.BorderWidth)
|
||||
rectAnnotation.BS = bs.ToPdfObject()
|
||||
}
|
||||
|
||||
if rectDef.FillEnabled {
|
||||
r, g, b := rectDef.FillColor.R(), rectDef.FillColor.G(), rectDef.FillColor.B()
|
||||
rectAnnotation.IC = pdfcore.MakeArrayFromFloats([]float64{r, g, b})
|
||||
} else {
|
||||
rectAnnotation.IC = pdfcore.MakeArrayFromIntegers([]int{}) // No fill.
|
||||
}
|
||||
|
||||
if rectDef.Opacity < 1.0 {
|
||||
rectAnnotation.CA = pdfcore.MakeFloat(rectDef.Opacity)
|
||||
}
|
||||
|
||||
// Make the appearance stream (for uniform appearance).
|
||||
apDict, bbox, err := makeRectangleAnnotationAppearanceStream(rectDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rectAnnotation.AP = apDict
|
||||
rectAnnotation.Rect = pdfcore.MakeArrayFromFloats([]float64{bbox.Llx, bbox.Lly, bbox.Urx, bbox.Ury})
|
||||
|
||||
return rectAnnotation.PdfAnnotation, nil
|
||||
|
||||
}
|
||||
|
||||
type CircleAnnotationDef struct {
|
||||
X float64
|
||||
Y float64
|
||||
Width float64
|
||||
Height float64
|
||||
FillEnabled bool // Show fill?
|
||||
FillColor *pdf.PdfColorDeviceRGB
|
||||
BorderEnabled bool // Show border?
|
||||
BorderWidth float64
|
||||
BorderColor *pdf.PdfColorDeviceRGB
|
||||
Opacity float64 // Alpha value (0-1).
|
||||
}
|
||||
|
||||
// Creates a circle/ellipse annotation object with appearance stream that can be added to page PDF annotations.
|
||||
func CreateCircleAnnotation(circDef CircleAnnotationDef) (*pdf.PdfAnnotation, error) {
|
||||
circAnnotation := pdf.NewPdfAnnotationCircle()
|
||||
|
||||
if circDef.BorderEnabled {
|
||||
r, g, b := circDef.BorderColor.R(), circDef.BorderColor.G(), circDef.BorderColor.B()
|
||||
circAnnotation.C = pdfcore.MakeArrayFromFloats([]float64{r, g, b})
|
||||
bs := pdf.NewBorderStyle()
|
||||
bs.SetBorderWidth(circDef.BorderWidth)
|
||||
circAnnotation.BS = bs.ToPdfObject()
|
||||
}
|
||||
|
||||
if circDef.FillEnabled {
|
||||
r, g, b := circDef.FillColor.R(), circDef.FillColor.G(), circDef.FillColor.B()
|
||||
circAnnotation.IC = pdfcore.MakeArrayFromFloats([]float64{r, g, b})
|
||||
} else {
|
||||
circAnnotation.IC = pdfcore.MakeArrayFromIntegers([]int{}) // No fill.
|
||||
}
|
||||
|
||||
if circDef.Opacity < 1.0 {
|
||||
circAnnotation.CA = pdfcore.MakeFloat(circDef.Opacity)
|
||||
}
|
||||
|
||||
// Make the appearance stream (for uniform appearance).
|
||||
apDict, bbox, err := makeCircleAnnotationAppearanceStream(circDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
circAnnotation.AP = apDict
|
||||
circAnnotation.Rect = pdfcore.MakeArrayFromFloats([]float64{bbox.Llx, bbox.Lly, bbox.Urx, bbox.Ury})
|
||||
|
||||
return circAnnotation.PdfAnnotation, nil
|
||||
|
||||
}
|
143
pdf/annotator/circle.go
Normal file
143
pdf/annotator/circle.go
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package annotator
|
||||
|
||||
import (
|
||||
"github.com/unidoc/unidoc/common"
|
||||
|
||||
pdfcontent "github.com/unidoc/unidoc/pdf/contentstream"
|
||||
"github.com/unidoc/unidoc/pdf/contentstream/draw"
|
||||
pdfcore "github.com/unidoc/unidoc/pdf/core"
|
||||
pdf "github.com/unidoc/unidoc/pdf/model"
|
||||
)
|
||||
|
||||
// Make the bezier path with the content creator.
|
||||
func drawBezierPathWithCreator(bpath draw.CubicBezierPath, creator *pdfcontent.ContentCreator) {
|
||||
for idx, c := range bpath.Curves {
|
||||
if idx == 0 {
|
||||
creator.Add_m(c.P0.X, c.P0.Y)
|
||||
}
|
||||
creator.Add_c(c.P1.X, c.P1.Y, c.P2.X, c.P2.Y, c.P3.X, c.P3.Y)
|
||||
}
|
||||
}
|
||||
|
||||
func makeCircleAnnotationAppearanceStream(circDef CircleAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) {
|
||||
form := pdf.NewXObjectForm()
|
||||
form.Resources = pdf.NewPdfPageResources()
|
||||
|
||||
gsName := ""
|
||||
if circDef.Opacity < 1.0 {
|
||||
// Create graphics state with right opacity.
|
||||
gsState := &pdfcore.PdfObjectDictionary{}
|
||||
gsState.Set("ca", pdfcore.MakeFloat(circDef.Opacity))
|
||||
gsState.Set("CA", pdfcore.MakeFloat(circDef.Opacity))
|
||||
err := form.Resources.AddExtGState("gs1", gsState)
|
||||
if err != nil {
|
||||
common.Log.Debug("Unable to add extgstate gs1")
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
gsName = "gs1"
|
||||
}
|
||||
|
||||
content, localBbox, globalBbox, err := drawPdfCircle(circDef, gsName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = form.SetContentStream(content, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Local bounding box for the XObject Form.
|
||||
form.BBox = localBbox.ToPdfObject()
|
||||
|
||||
apDict := &pdfcore.PdfObjectDictionary{}
|
||||
apDict.Set("N", form.ToPdfObject())
|
||||
|
||||
return apDict, globalBbox, nil
|
||||
}
|
||||
|
||||
func drawPdfCircle(circDef CircleAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) {
|
||||
xRad := circDef.Width / 2
|
||||
yRad := circDef.Height / 2
|
||||
if circDef.BorderEnabled {
|
||||
xRad -= circDef.BorderWidth / 2
|
||||
yRad -= circDef.BorderWidth / 2
|
||||
}
|
||||
|
||||
magic := 0.551784
|
||||
xMagic := xRad * magic
|
||||
yMagic := yRad * magic
|
||||
|
||||
bpath := draw.NewCubicBezierPath()
|
||||
bpath = bpath.AppendCurve(draw.NewCubicBezierCurve(-xRad, 0, -xRad, yMagic, -xMagic, yRad, 0, yRad))
|
||||
bpath = bpath.AppendCurve(draw.NewCubicBezierCurve(0, yRad, xMagic, yRad, xRad, yMagic, xRad, 0))
|
||||
bpath = bpath.AppendCurve(draw.NewCubicBezierCurve(xRad, 0, xRad, -yMagic, xMagic, -yRad, 0, -yRad))
|
||||
bpath = bpath.AppendCurve(draw.NewCubicBezierCurve(0, -yRad, -xMagic, -yRad, -xRad, -yMagic, -xRad, 0))
|
||||
bpath = bpath.Offset(xRad, yRad)
|
||||
if circDef.BorderEnabled {
|
||||
bpath = bpath.Offset(circDef.BorderWidth/2, circDef.BorderWidth/2)
|
||||
}
|
||||
|
||||
creator := pdfcontent.NewContentCreator()
|
||||
|
||||
creator.Add_q()
|
||||
|
||||
if circDef.FillEnabled {
|
||||
creator.Add_rg(circDef.FillColor.R(), circDef.FillColor.G(), circDef.FillColor.B())
|
||||
}
|
||||
if circDef.BorderEnabled {
|
||||
creator.Add_RG(circDef.BorderColor.R(), circDef.BorderColor.G(), circDef.BorderColor.B())
|
||||
creator.Add_w(circDef.BorderWidth)
|
||||
}
|
||||
if len(gsName) > 1 {
|
||||
// If a graphics state is provided, use it. (Used for transparency settings here).
|
||||
creator.Add_gs(pdfcore.PdfObjectName(gsName))
|
||||
}
|
||||
|
||||
drawBezierPathWithCreator(bpath, creator)
|
||||
|
||||
if circDef.FillEnabled && circDef.BorderEnabled {
|
||||
creator.Add_B() // fill and stroke.
|
||||
} else if circDef.FillEnabled {
|
||||
creator.Add_f() // Fill.
|
||||
} else if circDef.BorderEnabled {
|
||||
creator.Add_S() // Stroke.
|
||||
}
|
||||
creator.Add_Q()
|
||||
|
||||
// Offsets (needed for placement of annotations bbox).
|
||||
offX := circDef.X
|
||||
offY := circDef.Y
|
||||
|
||||
// Get bounding box.
|
||||
pathBbox := bpath.GetBoundingBox()
|
||||
if circDef.BorderEnabled {
|
||||
// Account for stroke width.
|
||||
pathBbox.Height += circDef.BorderWidth
|
||||
pathBbox.Width += circDef.BorderWidth
|
||||
pathBbox.X -= circDef.BorderWidth / 2
|
||||
pathBbox.Y -= circDef.BorderWidth / 2
|
||||
}
|
||||
|
||||
// Bounding box - local coordinate system (without offset).
|
||||
localBbox := &pdf.PdfRectangle{}
|
||||
localBbox.Llx = pathBbox.X
|
||||
localBbox.Lly = pathBbox.Y
|
||||
localBbox.Urx = pathBbox.X + pathBbox.Width
|
||||
localBbox.Ury = pathBbox.Y + pathBbox.Height
|
||||
|
||||
// Bounding box - global page coordinate system (with offset).
|
||||
globalBbox := &pdf.PdfRectangle{}
|
||||
globalBbox.Llx = offX + pathBbox.X
|
||||
globalBbox.Lly = offY + pathBbox.Y
|
||||
globalBbox.Urx = offX + pathBbox.X + pathBbox.Width
|
||||
globalBbox.Ury = offY + pathBbox.Y + pathBbox.Height
|
||||
|
||||
return creator.Bytes(), localBbox, globalBbox, nil
|
||||
}
|
10
pdf/annotator/doc.go
Normal file
10
pdf/annotator/doc.go
Normal file
@ -0,0 +1,10 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
// The annotator package contains an annotator with a convenient interface for creating annotations with appearance
|
||||
// streams. It goes beyond the models package which includes definitions of basic annotation models, in that it
|
||||
// can create the appearance streams which specify the exact appearance as needed by many pdf viewers for consistent
|
||||
// appearance of the annotations.
|
||||
package annotator
|
229
pdf/annotator/line.go
Normal file
229
pdf/annotator/line.go
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package annotator
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
pdfcontent "github.com/unidoc/unidoc/pdf/contentstream"
|
||||
"github.com/unidoc/unidoc/pdf/contentstream/draw"
|
||||
pdfcore "github.com/unidoc/unidoc/pdf/core"
|
||||
pdf "github.com/unidoc/unidoc/pdf/model"
|
||||
)
|
||||
|
||||
// Make the path with the content creator.
|
||||
func drawPathWithCreator(path draw.Path, creator *pdfcontent.ContentCreator) {
|
||||
for idx, p := range path.Points {
|
||||
if idx == 0 {
|
||||
creator.Add_m(p.X, p.Y)
|
||||
} else {
|
||||
creator.Add_l(p.X, p.Y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeLineAnnotationAppearanceStream(lineDef LineAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) {
|
||||
form := pdf.NewXObjectForm()
|
||||
form.Resources = pdf.NewPdfPageResources()
|
||||
|
||||
gsName := ""
|
||||
if lineDef.Opacity < 1.0 {
|
||||
// Create graphics state with right opacity.
|
||||
gsState := &pdfcore.PdfObjectDictionary{}
|
||||
gsState.Set("ca", pdfcore.MakeFloat(lineDef.Opacity))
|
||||
err := form.Resources.AddExtGState("gs1", gsState)
|
||||
if err != nil {
|
||||
common.Log.Debug("Unable to add extgstate gs1")
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
gsName = "gs1"
|
||||
}
|
||||
|
||||
content, localBbox, globalBbox, err := drawPdfLine(lineDef, gsName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = form.SetContentStream(content, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Local bounding box for the XObject Form.
|
||||
form.BBox = localBbox.ToPdfObject()
|
||||
|
||||
apDict := &pdfcore.PdfObjectDictionary{}
|
||||
apDict.Set("N", form.ToPdfObject())
|
||||
|
||||
return apDict, globalBbox, nil
|
||||
}
|
||||
|
||||
// Draw a line in PDF. Generates the content stream which can be used in page contents or appearance stream of annotation.
|
||||
// Returns the stream content, XForm bounding box (local), Rect bounding box (global/page) and an error if one occurred.
|
||||
func drawPdfLine(lineDef LineAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) {
|
||||
x1, x2 := lineDef.X1, lineDef.X2
|
||||
y1, y2 := lineDef.Y1, lineDef.Y2
|
||||
|
||||
dy := y2 - y1
|
||||
dx := x2 - x1
|
||||
theta := math.Atan2(dy, dx)
|
||||
|
||||
L := math.Sqrt(math.Pow(dx, 2.0) + math.Pow(dy, 2.0))
|
||||
w := lineDef.LineWidth
|
||||
|
||||
pi := math.Pi
|
||||
|
||||
mul := 1.0
|
||||
if dx < 0 {
|
||||
mul *= -1.0
|
||||
}
|
||||
if dy < 0 {
|
||||
mul *= -1.0
|
||||
}
|
||||
|
||||
// Vs.
|
||||
VsX := mul * (-w / 2 * math.Cos(theta+pi/2))
|
||||
VsY := mul * (-w/2*math.Sin(theta+pi/2) + w*math.Sin(theta+pi/2))
|
||||
|
||||
// V1.
|
||||
V1X := VsX + w/2*math.Cos(theta+pi/2)
|
||||
V1Y := VsY + w/2*math.Sin(theta+pi/2)
|
||||
|
||||
// P2.
|
||||
V2X := VsX + w/2*math.Cos(theta+pi/2) + L*math.Cos(theta)
|
||||
V2Y := VsY + w/2*math.Sin(theta+pi/2) + L*math.Sin(theta)
|
||||
|
||||
// P3.
|
||||
V3X := VsX + w/2*math.Cos(theta+pi/2) + L*math.Cos(theta) + w*math.Cos(theta-pi/2)
|
||||
V3Y := VsY + w/2*math.Sin(theta+pi/2) + L*math.Sin(theta) + w*math.Sin(theta-pi/2)
|
||||
|
||||
// P4.
|
||||
V4X := VsX + w/2*math.Cos(theta-pi/2)
|
||||
V4Y := VsY + w/2*math.Sin(theta-pi/2)
|
||||
|
||||
path := draw.NewPath()
|
||||
path = path.AppendPoint(draw.NewPoint(V1X, V1Y))
|
||||
path = path.AppendPoint(draw.NewPoint(V2X, V2Y))
|
||||
path = path.AppendPoint(draw.NewPoint(V3X, V3Y))
|
||||
path = path.AppendPoint(draw.NewPoint(V4X, V4Y))
|
||||
|
||||
lineEnding1 := lineDef.LineEndingStyle1
|
||||
lineEnding2 := lineDef.LineEndingStyle2
|
||||
|
||||
// TODO: Allow custom height/widths.
|
||||
arrowHeight := 3 * w
|
||||
arrowWidth := 3 * w
|
||||
arrowExtruding := (arrowWidth - w) / 2
|
||||
|
||||
if lineEnding2 == LineEndingStyleArrow {
|
||||
// Convert P2, P3
|
||||
p2 := path.GetPointNumber(2)
|
||||
|
||||
va1 := draw.NewVectorPolar(arrowHeight, theta+pi)
|
||||
pa1 := p2.AddVector(va1)
|
||||
|
||||
bVec := draw.NewVectorPolar(arrowWidth/2, theta+pi/2)
|
||||
aVec := draw.NewVectorPolar(arrowHeight, theta)
|
||||
|
||||
va2 := draw.NewVectorPolar(arrowExtruding, theta+pi/2)
|
||||
pa2 := pa1.AddVector(va2)
|
||||
|
||||
va3 := aVec.Add(bVec.Flip())
|
||||
pa3 := pa2.AddVector(va3)
|
||||
|
||||
va4 := bVec.Scale(2).Flip().Add(va3.Flip())
|
||||
pa4 := pa3.AddVector(va4)
|
||||
|
||||
pa5 := pa1.AddVector(draw.NewVectorPolar(w, theta-pi/2))
|
||||
|
||||
newpath := draw.NewPath()
|
||||
newpath = newpath.AppendPoint(path.GetPointNumber(1))
|
||||
newpath = newpath.AppendPoint(pa1)
|
||||
newpath = newpath.AppendPoint(pa2)
|
||||
newpath = newpath.AppendPoint(pa3)
|
||||
newpath = newpath.AppendPoint(pa4)
|
||||
newpath = newpath.AppendPoint(pa5)
|
||||
newpath = newpath.AppendPoint(path.GetPointNumber(4))
|
||||
|
||||
path = newpath
|
||||
}
|
||||
if lineEnding1 == LineEndingStyleArrow {
|
||||
// Get the first and last points.
|
||||
p1 := path.GetPointNumber(1)
|
||||
pn := path.GetPointNumber(path.Length())
|
||||
|
||||
// First three points on arrow.
|
||||
v1 := draw.NewVectorPolar(w/2, theta+pi+pi/2)
|
||||
pa1 := p1.AddVector(v1)
|
||||
|
||||
v2 := draw.NewVectorPolar(arrowHeight, theta).Add(draw.NewVectorPolar(arrowWidth/2, theta+pi/2))
|
||||
pa2 := pa1.AddVector(v2)
|
||||
|
||||
v3 := draw.NewVectorPolar(arrowExtruding, theta-pi/2)
|
||||
pa3 := pa2.AddVector(v3)
|
||||
|
||||
// Last three points
|
||||
v5 := draw.NewVectorPolar(arrowHeight, theta)
|
||||
pa5 := pn.AddVector(v5)
|
||||
|
||||
v6 := draw.NewVectorPolar(arrowExtruding, theta+pi+pi/2)
|
||||
pa6 := pa5.AddVector(v6)
|
||||
|
||||
pa7 := pa1
|
||||
|
||||
newpath := draw.NewPath()
|
||||
newpath = newpath.AppendPoint(pa1)
|
||||
newpath = newpath.AppendPoint(pa2)
|
||||
newpath = newpath.AppendPoint(pa3)
|
||||
for _, p := range path.Points[1 : len(path.Points)-1] {
|
||||
newpath = newpath.AppendPoint(p)
|
||||
}
|
||||
newpath = newpath.AppendPoint(pa5)
|
||||
newpath = newpath.AppendPoint(pa6)
|
||||
newpath = newpath.AppendPoint(pa7)
|
||||
|
||||
path = newpath
|
||||
}
|
||||
|
||||
pathBbox := path.GetBoundingBox()
|
||||
|
||||
creator := pdfcontent.NewContentCreator()
|
||||
|
||||
// Draw line with arrow
|
||||
creator.
|
||||
Add_q().
|
||||
Add_rg(lineDef.LineColor.R(), lineDef.LineColor.G(), lineDef.LineColor.B())
|
||||
if len(gsName) > 1 {
|
||||
// If a graphics state is provided, use it. (Used for transparency settings here).
|
||||
creator.Add_gs(pdfcore.PdfObjectName(gsName))
|
||||
}
|
||||
drawPathWithCreator(path, creator)
|
||||
creator.Add_f().
|
||||
//creator.Add_S().
|
||||
Add_Q()
|
||||
|
||||
// Offsets (needed for placement of annotations bbox).
|
||||
offX := x1 - VsX
|
||||
offY := y1 - VsY
|
||||
|
||||
// Bounding box - local coordinate system (without offset).
|
||||
localBbox := &pdf.PdfRectangle{}
|
||||
localBbox.Llx = pathBbox.X
|
||||
localBbox.Lly = pathBbox.Y
|
||||
localBbox.Urx = pathBbox.X + pathBbox.Width
|
||||
localBbox.Ury = pathBbox.Y + pathBbox.Height
|
||||
|
||||
// Bounding box - global page coordinate system (with offset).
|
||||
globalBbox := &pdf.PdfRectangle{}
|
||||
globalBbox.Llx = offX + pathBbox.X
|
||||
globalBbox.Lly = offY + pathBbox.Y
|
||||
globalBbox.Urx = offX + pathBbox.X + pathBbox.Width
|
||||
globalBbox.Ury = offY + pathBbox.Y + pathBbox.Height
|
||||
|
||||
return creator.Bytes(), localBbox, globalBbox, nil
|
||||
}
|
111
pdf/annotator/rectangle.go
Normal file
111
pdf/annotator/rectangle.go
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package annotator
|
||||
|
||||
import (
|
||||
"github.com/unidoc/unidoc/common"
|
||||
|
||||
pdfcontent "github.com/unidoc/unidoc/pdf/contentstream"
|
||||
"github.com/unidoc/unidoc/pdf/contentstream/draw"
|
||||
pdfcore "github.com/unidoc/unidoc/pdf/core"
|
||||
pdf "github.com/unidoc/unidoc/pdf/model"
|
||||
)
|
||||
|
||||
func makeRectangleAnnotationAppearanceStream(rectDef RectangleAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) {
|
||||
form := pdf.NewXObjectForm()
|
||||
form.Resources = pdf.NewPdfPageResources()
|
||||
|
||||
gsName := ""
|
||||
if rectDef.Opacity < 1.0 {
|
||||
// Create graphics state with right opacity.
|
||||
gsState := &pdfcore.PdfObjectDictionary{}
|
||||
gsState.Set("ca", pdfcore.MakeFloat(rectDef.Opacity))
|
||||
gsState.Set("CA", pdfcore.MakeFloat(rectDef.Opacity))
|
||||
err := form.Resources.AddExtGState("gs1", gsState)
|
||||
if err != nil {
|
||||
common.Log.Debug("Unable to add extgstate gs1")
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
gsName = "gs1"
|
||||
}
|
||||
|
||||
content, localBbox, globalBbox, err := drawPdfRectangle(rectDef, gsName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = form.SetContentStream(content, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Local bounding box for the XObject Form.
|
||||
form.BBox = localBbox.ToPdfObject()
|
||||
|
||||
apDict := &pdfcore.PdfObjectDictionary{}
|
||||
apDict.Set("N", form.ToPdfObject())
|
||||
|
||||
return apDict, globalBbox, nil
|
||||
}
|
||||
|
||||
func drawPdfRectangle(rectDef RectangleAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) {
|
||||
path := draw.NewPath()
|
||||
|
||||
path = path.AppendPoint(draw.NewPoint(0, 0))
|
||||
path = path.AppendPoint(draw.NewPoint(0, rectDef.Height))
|
||||
path = path.AppendPoint(draw.NewPoint(rectDef.Width, rectDef.Height))
|
||||
path = path.AppendPoint(draw.NewPoint(rectDef.Width, 0))
|
||||
path = path.AppendPoint(draw.NewPoint(0, 0))
|
||||
|
||||
creator := pdfcontent.NewContentCreator()
|
||||
|
||||
creator.Add_q()
|
||||
if rectDef.FillEnabled {
|
||||
creator.Add_rg(rectDef.FillColor.R(), rectDef.FillColor.G(), rectDef.FillColor.B())
|
||||
}
|
||||
if rectDef.BorderEnabled {
|
||||
creator.Add_RG(rectDef.BorderColor.R(), rectDef.BorderColor.G(), rectDef.BorderColor.B())
|
||||
creator.Add_w(rectDef.BorderWidth)
|
||||
}
|
||||
if len(gsName) > 1 {
|
||||
// If a graphics state is provided, use it. (Used for transparency settings here).
|
||||
creator.Add_gs(pdfcore.PdfObjectName(gsName))
|
||||
}
|
||||
drawPathWithCreator(path, creator)
|
||||
|
||||
if rectDef.FillEnabled && rectDef.BorderEnabled {
|
||||
creator.Add_B() // fill and stroke.
|
||||
} else if rectDef.FillEnabled {
|
||||
creator.Add_f() // Fill.
|
||||
} else if rectDef.BorderEnabled {
|
||||
creator.Add_S() // Stroke.
|
||||
}
|
||||
creator.Add_Q()
|
||||
|
||||
// Offsets (needed for placement of annotations bbox).
|
||||
offX := rectDef.X
|
||||
offY := rectDef.Y
|
||||
|
||||
// Get bounding box.
|
||||
pathBbox := path.GetBoundingBox()
|
||||
|
||||
// Bounding box - local coordinate system (without offset).
|
||||
localBbox := &pdf.PdfRectangle{}
|
||||
localBbox.Llx = pathBbox.X
|
||||
localBbox.Lly = pathBbox.Y
|
||||
localBbox.Urx = pathBbox.X + pathBbox.Width
|
||||
localBbox.Ury = pathBbox.Y + pathBbox.Height
|
||||
|
||||
// Bounding box - global page coordinate system (with offset).
|
||||
globalBbox := &pdf.PdfRectangle{}
|
||||
globalBbox.Llx = offX + pathBbox.X
|
||||
globalBbox.Lly = offY + pathBbox.Y
|
||||
globalBbox.Urx = offX + pathBbox.X + pathBbox.Width
|
||||
globalBbox.Ury = offY + pathBbox.Y + pathBbox.Height
|
||||
|
||||
return creator.Bytes(), localBbox, globalBbox, nil
|
||||
}
|
154
pdf/contentstream/draw/bezier_curve.go
Normal file
154
pdf/contentstream/draw/bezier_curve.go
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package draw
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/unidoc/unidoc/pdf/model"
|
||||
)
|
||||
|
||||
// Cubic bezier curves are defined by:
|
||||
// R(t) = P0*(1-t)^3 + P1*3*t*(1-t)^2 + P2*3*t^2*(1-t) + P3*t^3
|
||||
// where P0 is the current point, P1, P2 control points and P3 the final point.
|
||||
type CubicBezierCurve struct {
|
||||
P0 Point // Starting point.
|
||||
P1 Point // Control point 1.
|
||||
P2 Point // Control point 2.
|
||||
P3 Point // Final point.
|
||||
}
|
||||
|
||||
func NewCubicBezierCurve(x0, y0, x1, y1, x2, y2, x3, y3 float64) CubicBezierCurve {
|
||||
curve := CubicBezierCurve{}
|
||||
curve.P0 = NewPoint(x0, y0)
|
||||
curve.P1 = NewPoint(x1, y1)
|
||||
curve.P2 = NewPoint(x2, y2)
|
||||
curve.P3 = NewPoint(x3, y3)
|
||||
return curve
|
||||
}
|
||||
|
||||
// Add X,Y offset to all points on a curve.
|
||||
func (curve CubicBezierCurve) AddOffsetXY(offX, offY float64) CubicBezierCurve {
|
||||
curve.P0.X += offX
|
||||
curve.P1.X += offX
|
||||
curve.P2.X += offX
|
||||
curve.P3.X += offX
|
||||
|
||||
curve.P0.Y += offY
|
||||
curve.P1.Y += offY
|
||||
curve.P2.Y += offY
|
||||
curve.P3.Y += offY
|
||||
|
||||
return curve
|
||||
}
|
||||
|
||||
func (curve CubicBezierCurve) GetBounds() model.PdfRectangle {
|
||||
minX := curve.P0.X
|
||||
maxX := curve.P0.X
|
||||
minY := curve.P0.Y
|
||||
maxY := curve.P0.Y
|
||||
|
||||
// 1000 points.
|
||||
for t := 0.0; t <= 1.0; t += 0.001 {
|
||||
Rx := curve.P0.X*math.Pow(1-t, 3) +
|
||||
curve.P1.X*3*t*math.Pow(1-t, 2) +
|
||||
curve.P2.X*3*math.Pow(t, 2)*(1-t) +
|
||||
curve.P3.X*math.Pow(t, 3)
|
||||
Ry := curve.P0.Y*math.Pow(1-t, 3) +
|
||||
curve.P1.Y*3*t*math.Pow(1-t, 2) +
|
||||
curve.P2.Y*3*math.Pow(t, 2)*(1-t) +
|
||||
curve.P3.Y*math.Pow(t, 3)
|
||||
|
||||
if Rx < minX {
|
||||
minX = Rx
|
||||
}
|
||||
if Rx > maxX {
|
||||
maxX = Rx
|
||||
}
|
||||
if Ry < minY {
|
||||
minY = Ry
|
||||
}
|
||||
if Ry > maxY {
|
||||
maxY = Ry
|
||||
}
|
||||
}
|
||||
|
||||
bounds := model.PdfRectangle{}
|
||||
bounds.Llx = minX
|
||||
bounds.Lly = minY
|
||||
bounds.Urx = maxX
|
||||
bounds.Ury = maxY
|
||||
return bounds
|
||||
}
|
||||
|
||||
type CubicBezierPath struct {
|
||||
Curves []CubicBezierCurve
|
||||
}
|
||||
|
||||
func NewCubicBezierPath() CubicBezierPath {
|
||||
bpath := CubicBezierPath{}
|
||||
bpath.Curves = []CubicBezierCurve{}
|
||||
return bpath
|
||||
}
|
||||
|
||||
func (this CubicBezierPath) AppendCurve(curve CubicBezierCurve) CubicBezierPath {
|
||||
this.Curves = append(this.Curves, curve)
|
||||
return this
|
||||
}
|
||||
|
||||
func (bpath CubicBezierPath) Copy() CubicBezierPath {
|
||||
bpathcopy := CubicBezierPath{}
|
||||
bpathcopy.Curves = []CubicBezierCurve{}
|
||||
for _, c := range bpath.Curves {
|
||||
bpathcopy.Curves = append(bpathcopy.Curves, c)
|
||||
}
|
||||
return bpathcopy
|
||||
}
|
||||
|
||||
func (bpath CubicBezierPath) Offset(offX, offY float64) CubicBezierPath {
|
||||
for i, c := range bpath.Curves {
|
||||
bpath.Curves[i] = c.AddOffsetXY(offX, offY)
|
||||
}
|
||||
return bpath
|
||||
}
|
||||
|
||||
func (bpath CubicBezierPath) GetBoundingBox() Rectangle {
|
||||
bbox := Rectangle{}
|
||||
|
||||
minX := 0.0
|
||||
maxX := 0.0
|
||||
minY := 0.0
|
||||
maxY := 0.0
|
||||
for idx, c := range bpath.Curves {
|
||||
curveBounds := c.GetBounds()
|
||||
if idx == 0 {
|
||||
minX = curveBounds.Llx
|
||||
maxX = curveBounds.Urx
|
||||
minY = curveBounds.Lly
|
||||
maxY = curveBounds.Ury
|
||||
continue
|
||||
}
|
||||
|
||||
if curveBounds.Llx < minX {
|
||||
minX = curveBounds.Llx
|
||||
}
|
||||
if curveBounds.Urx > maxX {
|
||||
maxX = curveBounds.Urx
|
||||
}
|
||||
if curveBounds.Lly < minY {
|
||||
minY = curveBounds.Lly
|
||||
}
|
||||
if curveBounds.Ury > maxY {
|
||||
maxY = curveBounds.Ury
|
||||
}
|
||||
}
|
||||
|
||||
bbox.X = minX
|
||||
bbox.Y = minY
|
||||
bbox.Width = maxX - minX
|
||||
bbox.Height = maxY - minY
|
||||
return bbox
|
||||
}
|
9
pdf/contentstream/draw/doc.go
Normal file
9
pdf/contentstream/draw/doc.go
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
// The draw package has handy features for defining paths which can be used to draw content on a PDF page. Handles
|
||||
// defining paths as points, vector calculations and conversion to PDF content stream data which can be used in
|
||||
// page content streams and XObject forms and thus also in annotation appearance streams.
|
||||
package draw
|
103
pdf/contentstream/draw/path.go
Normal file
103
pdf/contentstream/draw/path.go
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package draw
|
||||
|
||||
// A path consists of straight line connections between each point defined in an array of points.
|
||||
type Path struct {
|
||||
Points []Point
|
||||
}
|
||||
|
||||
func NewPath() Path {
|
||||
path := Path{}
|
||||
path.Points = []Point{}
|
||||
return path
|
||||
}
|
||||
|
||||
func (this Path) AppendPoint(point Point) Path {
|
||||
this.Points = append(this.Points, point)
|
||||
return this
|
||||
}
|
||||
|
||||
func (this Path) RemovePoint(number int) Path {
|
||||
if number < 1 || number > len(this.Points) {
|
||||
return this
|
||||
}
|
||||
|
||||
idx := number - 1
|
||||
this.Points = append(this.Points[:idx], this.Points[idx+1:]...)
|
||||
return this
|
||||
}
|
||||
|
||||
func (this Path) Length() int {
|
||||
return len(this.Points)
|
||||
}
|
||||
|
||||
func (this Path) GetPointNumber(number int) Point {
|
||||
if number < 1 || number > len(this.Points) {
|
||||
return Point{}
|
||||
}
|
||||
return this.Points[number-1]
|
||||
}
|
||||
|
||||
func (path Path) Copy() Path {
|
||||
pathcopy := Path{}
|
||||
pathcopy.Points = []Point{}
|
||||
for _, p := range path.Points {
|
||||
pathcopy.Points = append(pathcopy.Points, p)
|
||||
}
|
||||
return pathcopy
|
||||
}
|
||||
|
||||
func (path Path) Offset(offX, offY float64) Path {
|
||||
for i, p := range path.Points {
|
||||
path.Points[i] = p.Add(offX, offY)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (path Path) GetBoundingBox() Rectangle {
|
||||
bbox := Rectangle{}
|
||||
|
||||
minX := 0.0
|
||||
maxX := 0.0
|
||||
minY := 0.0
|
||||
maxY := 0.0
|
||||
for idx, p := range path.Points {
|
||||
if idx == 0 {
|
||||
minX = p.X
|
||||
maxX = p.X
|
||||
minY = p.Y
|
||||
maxY = p.Y
|
||||
continue
|
||||
}
|
||||
|
||||
if p.X < minX {
|
||||
minX = p.X
|
||||
}
|
||||
if p.X > maxX {
|
||||
maxX = p.X
|
||||
}
|
||||
if p.Y < minY {
|
||||
minY = p.Y
|
||||
}
|
||||
if p.Y > maxY {
|
||||
maxY = p.Y
|
||||
}
|
||||
}
|
||||
|
||||
bbox.X = minX
|
||||
bbox.Y = minY
|
||||
bbox.Width = maxX - minX
|
||||
bbox.Height = maxY - minY
|
||||
return bbox
|
||||
}
|
||||
|
||||
type Rectangle struct {
|
||||
X float64
|
||||
Y float64
|
||||
Width float64
|
||||
Height float64
|
||||
}
|
37
pdf/contentstream/draw/point.go
Normal file
37
pdf/contentstream/draw/point.go
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package draw
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Point struct {
|
||||
X float64
|
||||
Y float64
|
||||
}
|
||||
|
||||
func NewPoint(x, y float64) Point {
|
||||
point := Point{}
|
||||
point.X = x
|
||||
point.Y = y
|
||||
return point
|
||||
}
|
||||
|
||||
func (p Point) Add(dx, dy float64) Point {
|
||||
p.X += dx
|
||||
p.Y += dy
|
||||
return p
|
||||
}
|
||||
|
||||
// Add vector to a point.
|
||||
func (this Point) AddVector(v Vector) Point {
|
||||
this.X += v.Dx
|
||||
this.Y += v.Dy
|
||||
return this
|
||||
}
|
||||
|
||||
func (p Point) String() string {
|
||||
return fmt.Sprintf("(%.1f,%.1f)", p.X, p.Y)
|
||||
}
|
85
pdf/contentstream/draw/vector.go
Normal file
85
pdf/contentstream/draw/vector.go
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package draw
|
||||
|
||||
import "math"
|
||||
|
||||
type Vector struct {
|
||||
Dx float64
|
||||
Dy float64
|
||||
}
|
||||
|
||||
func NewVector(dx, dy float64) Vector {
|
||||
v := Vector{}
|
||||
v.Dx = dx
|
||||
v.Dy = dy
|
||||
return v
|
||||
}
|
||||
|
||||
func NewVectorBetween(a Point, b Point) Vector {
|
||||
v := Vector{}
|
||||
v.Dx = b.X - a.X
|
||||
v.Dy = b.Y - a.Y
|
||||
return v
|
||||
}
|
||||
|
||||
func NewVectorPolar(length float64, theta float64) Vector {
|
||||
v := Vector{}
|
||||
|
||||
v.Dx = length * math.Cos(theta)
|
||||
v.Dy = length * math.Sin(theta)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v Vector) Add(other Vector) Vector {
|
||||
v.Dx += other.Dx
|
||||
v.Dy += other.Dy
|
||||
return v
|
||||
}
|
||||
|
||||
func (v Vector) Rotate(phi float64) Vector {
|
||||
mag := v.Magnitude()
|
||||
angle := v.GetPolarAngle()
|
||||
|
||||
return NewVectorPolar(mag, angle+phi)
|
||||
}
|
||||
|
||||
// Change the sign of the vector: -vector.
|
||||
func (this Vector) Flip() Vector {
|
||||
mag := this.Magnitude()
|
||||
theta := this.GetPolarAngle()
|
||||
|
||||
this.Dx = mag * math.Cos(theta+math.Pi)
|
||||
this.Dy = mag * math.Sin(theta+math.Pi)
|
||||
return this
|
||||
}
|
||||
|
||||
func (v Vector) FlipY() Vector {
|
||||
v.Dy = -v.Dy
|
||||
return v
|
||||
}
|
||||
|
||||
func (v Vector) FlipX() Vector {
|
||||
v.Dx = -v.Dx
|
||||
return v
|
||||
}
|
||||
|
||||
func (this Vector) Scale(factor float64) Vector {
|
||||
mag := this.Magnitude()
|
||||
theta := this.GetPolarAngle()
|
||||
|
||||
this.Dx = factor * mag * math.Cos(theta)
|
||||
this.Dy = factor * mag * math.Sin(theta)
|
||||
return this
|
||||
}
|
||||
|
||||
func (this Vector) Magnitude() float64 {
|
||||
return math.Sqrt(math.Pow(this.Dx, 2.0) + math.Pow(this.Dy, 2.0))
|
||||
}
|
||||
|
||||
func (this Vector) GetPolarAngle() float64 {
|
||||
return math.Atan2(this.Dy, this.Dx)
|
||||
}
|
@ -33,17 +33,20 @@ type ContentStreamInlineImage struct {
|
||||
|
||||
// Make a new content stream inline image object from an image.
|
||||
func NewInlineImageFromImage(img Image, encoder StreamEncoder) (*ContentStreamInlineImage, error) {
|
||||
filterName := ""
|
||||
if encoder == nil {
|
||||
encoder = NewRawEncoder()
|
||||
} else {
|
||||
filterName = encoder.GetFilterName()
|
||||
}
|
||||
|
||||
inlineImage := ContentStreamInlineImage{}
|
||||
if img.ColorComponents == 1 {
|
||||
inlineImage.ColorSpace = MakeName("DeviceGray")
|
||||
inlineImage.ColorSpace = MakeName("G") // G short for DeviceGray
|
||||
} else if img.ColorComponents == 3 {
|
||||
inlineImage.ColorSpace = MakeName("DeviceRGB")
|
||||
inlineImage.ColorSpace = MakeName("RGB") // RGB short for DeviceRGB
|
||||
} else if img.ColorComponents == 4 {
|
||||
inlineImage.ColorSpace = MakeName("DeviceCMYK")
|
||||
inlineImage.ColorSpace = MakeName("CMYK") // CMYK short for DeviceCMYK
|
||||
} else {
|
||||
common.Log.Debug("Invalid number of color components for inline image: %d", img.ColorComponents)
|
||||
return nil, errors.New("Invalid number of color components")
|
||||
@ -58,7 +61,6 @@ func NewInlineImageFromImage(img Image, encoder StreamEncoder) (*ContentStreamIn
|
||||
}
|
||||
|
||||
inlineImage.stream = encoded
|
||||
filterName := encoder.GetFilterName()
|
||||
if len(filterName) > 0 {
|
||||
inlineImage.Filter = MakeName(filterName)
|
||||
}
|
||||
@ -110,40 +112,40 @@ func (this *ContentStreamInlineImage) DefaultWriteString() string {
|
||||
s := ""
|
||||
|
||||
if this.BitsPerComponent != nil {
|
||||
s += "BPC " + this.BitsPerComponent.DefaultWriteString() + "\n"
|
||||
s += "/BPC " + this.BitsPerComponent.DefaultWriteString() + "\n"
|
||||
}
|
||||
if this.ColorSpace != nil {
|
||||
s += "CS " + this.ColorSpace.DefaultWriteString() + "\n"
|
||||
s += "/CS " + this.ColorSpace.DefaultWriteString() + "\n"
|
||||
}
|
||||
if this.Decode != nil {
|
||||
s += "D " + this.Decode.DefaultWriteString() + "\n"
|
||||
s += "/D " + this.Decode.DefaultWriteString() + "\n"
|
||||
}
|
||||
if this.DecodeParms != nil {
|
||||
s += "DP " + this.DecodeParms.DefaultWriteString() + "\n"
|
||||
s += "/DP " + this.DecodeParms.DefaultWriteString() + "\n"
|
||||
}
|
||||
if this.Filter != nil {
|
||||
s += "F " + this.Filter.DefaultWriteString() + "\n"
|
||||
s += "/F " + this.Filter.DefaultWriteString() + "\n"
|
||||
}
|
||||
if this.Height != nil {
|
||||
s += "H " + this.Height.DefaultWriteString() + "\n"
|
||||
s += "/H " + this.Height.DefaultWriteString() + "\n"
|
||||
}
|
||||
if this.ImageMask != nil {
|
||||
s += "IM " + this.ImageMask.DefaultWriteString() + "\n"
|
||||
s += "/IM " + this.ImageMask.DefaultWriteString() + "\n"
|
||||
}
|
||||
if this.Intent != nil {
|
||||
s += "Intent " + this.Intent.DefaultWriteString() + "\n"
|
||||
s += "/Intent " + this.Intent.DefaultWriteString() + "\n"
|
||||
}
|
||||
if this.Interpolate != nil {
|
||||
s += "I " + this.Interpolate.DefaultWriteString() + "\n"
|
||||
s += "/I " + this.Interpolate.DefaultWriteString() + "\n"
|
||||
}
|
||||
if this.Width != nil {
|
||||
s += "W " + this.Width.DefaultWriteString() + "\n"
|
||||
s += "/W " + this.Width.DefaultWriteString() + "\n"
|
||||
}
|
||||
output.WriteString(s)
|
||||
|
||||
output.WriteString("ID ")
|
||||
output.Write(this.stream)
|
||||
output.WriteString("\n")
|
||||
output.WriteString("\nEI\n")
|
||||
|
||||
return output.String()
|
||||
}
|
||||
@ -170,6 +172,12 @@ func (this *ContentStreamInlineImage) GetColorSpace(resources *PdfPageResources)
|
||||
} else if *name == "I" {
|
||||
return nil, errors.New("Unsupported Index colorspace")
|
||||
} else {
|
||||
if resources.ColorSpace == nil {
|
||||
// Can also refer to a name in the PDF page resources...
|
||||
common.Log.Debug("Error, unsupported inline image colorspace: %s", *name)
|
||||
return nil, errors.New("Unknown colorspace")
|
||||
}
|
||||
|
||||
cs, has := resources.ColorSpace.Colorspaces[string(*name)]
|
||||
if !has {
|
||||
// Can also refer to a name in the PDF page resources...
|
||||
@ -213,6 +221,7 @@ func (this *ContentStreamInlineImage) ToImage(resources *PdfPageResources) (*Ima
|
||||
return nil, err
|
||||
}
|
||||
common.Log.Trace("encoder: %+v %T", encoder, encoder)
|
||||
common.Log.Trace("inline image: %+v", this)
|
||||
|
||||
decoded, err := encoder.DecodeBytes(this.stream)
|
||||
if err != nil {
|
||||
@ -302,6 +311,7 @@ func (this *ContentStreamParser) ParseInlineImage() (*ContentStreamInlineImage,
|
||||
// Not an operand.. Read key value properties..
|
||||
param, ok := obj.(*PdfObjectName)
|
||||
if !ok {
|
||||
common.Log.Debug("Invalid inline image property (expecting name) - %T", obj)
|
||||
return nil, fmt.Errorf("Invalid inline image property (expecting name) - %T", obj)
|
||||
}
|
||||
|
||||
@ -390,6 +400,7 @@ func (this *ContentStreamParser) ParseInlineImage() (*ContentStreamInlineImage,
|
||||
state = 2
|
||||
} else {
|
||||
im.stream = append(im.stream, skipBytes...)
|
||||
skipBytes = []byte{} // Clear.
|
||||
// Need an extra check to decide if we fall back to state 0 or 1.
|
||||
if IsWhiteSpace(c) {
|
||||
state = 1
|
||||
@ -403,6 +414,7 @@ func (this *ContentStreamParser) ParseInlineImage() (*ContentStreamInlineImage,
|
||||
state = 3
|
||||
} else {
|
||||
im.stream = append(im.stream, skipBytes...)
|
||||
skipBytes = []byte{} // Clear.
|
||||
state = 0
|
||||
}
|
||||
} else if state == 3 {
|
||||
@ -419,6 +431,7 @@ func (this *ContentStreamParser) ParseInlineImage() (*ContentStreamInlineImage,
|
||||
} else {
|
||||
// Seems like "<ws>EI" was part of the data.
|
||||
im.stream = append(im.stream, skipBytes...)
|
||||
skipBytes = []byte{} // Clear.
|
||||
state = 0
|
||||
}
|
||||
}
|
||||
|
@ -901,6 +901,24 @@ func (r *PdfPageResources) ToPdfObject() PdfObject {
|
||||
return d
|
||||
}
|
||||
|
||||
// Add External Graphics State (GState). The gsDict can be specified either directly as a dictionary or an indirect
|
||||
// object containing a dictionary.
|
||||
func (r *PdfPageResources) AddExtGState(gsName PdfObjectName, gsDict PdfObject) error {
|
||||
if r.ExtGState == nil {
|
||||
r.ExtGState = &PdfObjectDictionary{}
|
||||
}
|
||||
|
||||
obj := r.ExtGState
|
||||
dict, ok := TraceToDirectObject(obj).(*PdfObjectDictionary)
|
||||
if !ok {
|
||||
common.Log.Debug("ExtGState type error (got %T/%T)", obj, TraceToDirectObject(obj))
|
||||
return ErrTypeError
|
||||
}
|
||||
|
||||
(*dict)[gsName] = gsDict
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the shading specified by keyName. Returns nil if not existing. The bool flag indicated whether it was found
|
||||
// or not.
|
||||
func (r *PdfPageResources) GetShadingByName(keyName string) (*PdfShading, bool) {
|
||||
@ -1049,8 +1067,10 @@ func (r *PdfPageResources) setXObjectByName(keyName string, stream *PdfObjectStr
|
||||
r.XObject = &PdfObjectDictionary{}
|
||||
}
|
||||
|
||||
xresDict, has := r.XObject.(*PdfObjectDictionary)
|
||||
obj := TraceToDirectObject(r.XObject)
|
||||
xresDict, has := obj.(*PdfObjectDictionary)
|
||||
if !has {
|
||||
common.Log.Debug("Invalid XObject, got %T/%T", r.XObject, obj)
|
||||
return errors.New("Type check error")
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ type PdfShading struct {
|
||||
AntiAlias *PdfObjectBool
|
||||
|
||||
context PdfModel // The sub shading type entry (types 1-7). Represented by PdfShadingType1-7.
|
||||
container PdfObject // The container.
|
||||
container PdfObject // The container. Can be stream, indirect object, or dictionary.
|
||||
}
|
||||
|
||||
func (this *PdfShading) GetContainingPdfObject() PdfObject {
|
||||
@ -58,6 +58,8 @@ func (this *PdfShading) getShadingDict() (*PdfObjectDictionary, error) {
|
||||
return d, nil
|
||||
} else if streamObj, isStream := obj.(*PdfObjectStream); isStream {
|
||||
return streamObj.PdfObjectDictionary, nil
|
||||
} else if d, isDict := obj.(*PdfObjectDictionary); isDict {
|
||||
return d, nil
|
||||
} else {
|
||||
common.Log.Debug("Unable to access shading dictionary")
|
||||
return nil, ErrTypeError
|
||||
@ -150,6 +152,9 @@ func newPdfShadingFromPdfObject(obj PdfObject) (*PdfShading, error) {
|
||||
} else if streamObj, isStream := obj.(*PdfObjectStream); isStream {
|
||||
shading.container = streamObj
|
||||
dict = streamObj.PdfObjectDictionary
|
||||
} else if d, isDict := obj.(*PdfObjectDictionary); isDict {
|
||||
shading.container = d
|
||||
dict = d
|
||||
} else {
|
||||
common.Log.Debug("Object type unexpected (%T)", obj)
|
||||
return nil, ErrTypeError
|
||||
|
@ -145,17 +145,25 @@ func (xform *XObjectForm) GetContentStream() ([]byte, error) {
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// Update the content stream, encode if needed.
|
||||
func (xform *XObjectForm) SetContentStream(content []byte) error {
|
||||
// Update the content stream with specified encoding. If encoding is null, will use the xform.Filter object
|
||||
// or Raw encoding if not set.
|
||||
func (xform *XObjectForm) SetContentStream(content []byte, encoder StreamEncoder) error {
|
||||
encoded := content
|
||||
if xform.Filter != nil {
|
||||
enc, err := xform.Filter.EncodeBytes(encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
if encoder == nil {
|
||||
if xform.Filter != nil {
|
||||
encoder = xform.Filter
|
||||
} else {
|
||||
encoder = NewRawEncoder()
|
||||
}
|
||||
encoded = enc
|
||||
}
|
||||
|
||||
enc, err := encoder.EncodeBytes(encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoded = enc
|
||||
|
||||
xform.Stream = encoded
|
||||
|
||||
return nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user