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
* 'v2' of https://github.com/unidoc/unidoc: annotation fixes for circle, rectangle (fill, stroke) Annotations support for rectangle and ellipses with appearance stream in annotator. Create rectangle annotation with appearance stream Fixes in inline image parsing and outputting.
This commit is contained in:
commit
57fcc5bd6f
@ -1,3 +1,8 @@
|
||||
/*
|
||||
* 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 (
|
||||
@ -5,6 +10,7 @@ import (
|
||||
pdf "github.com/unidoc/unidoc/pdf/model"
|
||||
)
|
||||
|
||||
// The currently supported line ending styles are None, Arrow (ClosedArrow) and Butt.
|
||||
type LineEndingStyle int
|
||||
|
||||
const (
|
||||
@ -13,16 +19,18 @@ const (
|
||||
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
|
||||
Opacity float64 // Alpha value (0-1).
|
||||
LineWidth float64
|
||||
LineEndingStyle1 LineEndingStyle
|
||||
LineEndingStyle2 LineEndingStyle
|
||||
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.
|
||||
@ -69,3 +77,104 @@ func CreateLineAnnotation(lineDef LineAnnotationDef) (*pdf.PdfAnnotation, error)
|
||||
|
||||
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
|
||||
}
|
@ -1,3 +1,8 @@
|
||||
/*
|
||||
* 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 (
|
||||
|
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
|
||||
}
|
@ -1,29 +1,11 @@
|
||||
/*
|
||||
* 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 (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)
|
||||
}
|
||||
|
||||
// A path consists of straight line connections between each point defined in an array of points.
|
||||
type Path struct {
|
||||
Points []Point
|
||||
}
|
||||
@ -34,13 +16,6 @@ func NewPath() Path {
|
||||
return path
|
||||
}
|
||||
|
||||
func NewPoint(x, y float64) Point {
|
||||
point := Point{}
|
||||
point.X = x
|
||||
point.Y = y
|
||||
return point
|
||||
}
|
||||
|
||||
func (this Path) AppendPoint(point Point) Path {
|
||||
this.Points = append(this.Points, point)
|
||||
return this
|
||||
|
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)
|
||||
}
|
@ -1,3 +1,8 @@
|
||||
/*
|
||||
* 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"
|
||||
|
@ -33,18 +33,21 @@ 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()
|
||||
}
|
||||
common.Log.Debug("NewInlineImageFromImage: encoder=%T", encoder)
|
||||
|
||||
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")
|
||||
@ -59,7 +62,6 @@ func NewInlineImageFromImage(img Image, encoder StreamEncoder) (*ContentStreamIn
|
||||
}
|
||||
|
||||
inlineImage.stream = encoded
|
||||
filterName := encoder.GetFilterName()
|
||||
if len(filterName) > 0 {
|
||||
inlineImage.Filter = MakeName(filterName)
|
||||
}
|
||||
@ -111,40 +113,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()
|
||||
}
|
||||
@ -171,6 +173,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...
|
||||
@ -214,6 +222,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 {
|
||||
@ -303,6 +312,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)
|
||||
}
|
||||
|
||||
@ -391,6 +401,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
|
||||
@ -404,6 +415,7 @@ func (this *ContentStreamParser) ParseInlineImage() (*ContentStreamInlineImage,
|
||||
state = 3
|
||||
} else {
|
||||
im.stream = append(im.stream, skipBytes...)
|
||||
skipBytes = []byte{} // Clear.
|
||||
state = 0
|
||||
}
|
||||
} else if state == 3 {
|
||||
@ -420,6 +432,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
|
||||
}
|
||||
}
|
||||
|
@ -1067,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")
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user