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:
Peter Williams 2017-04-13 14:09:09 +10:00
commit 57fcc5bd6f
10 changed files with 604 additions and 50 deletions

View File

@ -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 package annotator
import ( import (
@ -5,6 +10,7 @@ import (
pdf "github.com/unidoc/unidoc/pdf/model" pdf "github.com/unidoc/unidoc/pdf/model"
) )
// The currently supported line ending styles are None, Arrow (ClosedArrow) and Butt.
type LineEndingStyle int type LineEndingStyle int
const ( const (
@ -13,16 +19,18 @@ const (
LineEndingStyleButt LineEndingStyle = 2 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 { type LineAnnotationDef struct {
X1 float64 X1 float64
Y1 float64 Y1 float64
X2 float64 X2 float64
Y2 float64 Y2 float64
LineColor *pdf.PdfColorDeviceRGB LineColor *pdf.PdfColorDeviceRGB
Opacity float64 Opacity float64 // Alpha value (0-1).
LineWidth float64 LineWidth float64
LineEndingStyle1 LineEndingStyle LineEndingStyle1 LineEndingStyle // Line ending style of point 1.
LineEndingStyle2 LineEndingStyle LineEndingStyle2 LineEndingStyle // Line ending style of point 2.
} }
// Creates a line annotation object that can be added to page PDF annotations. // 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 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
View 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
}

View File

@ -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 package annotator
import ( import (

111
pdf/annotator/rectangle.go Normal file
View 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
}

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

View File

@ -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 package draw
import "fmt" // A path consists of straight line connections between each point defined in an array of points.
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)
}
type Path struct { type Path struct {
Points []Point Points []Point
} }
@ -34,13 +16,6 @@ func NewPath() Path {
return 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 { func (this Path) AppendPoint(point Point) Path {
this.Points = append(this.Points, point) this.Points = append(this.Points, point)
return this return this

View 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)
}

View File

@ -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 package draw
import "math" import "math"

View File

@ -33,18 +33,21 @@ type ContentStreamInlineImage struct {
// Make a new content stream inline image object from an image. // Make a new content stream inline image object from an image.
func NewInlineImageFromImage(img Image, encoder StreamEncoder) (*ContentStreamInlineImage, error) { func NewInlineImageFromImage(img Image, encoder StreamEncoder) (*ContentStreamInlineImage, error) {
filterName := ""
if encoder == nil { if encoder == nil {
encoder = NewRawEncoder() encoder = NewRawEncoder()
} else {
filterName = encoder.GetFilterName()
} }
common.Log.Debug("NewInlineImageFromImage: encoder=%T", encoder) common.Log.Debug("NewInlineImageFromImage: encoder=%T", encoder)
inlineImage := ContentStreamInlineImage{} inlineImage := ContentStreamInlineImage{}
if img.ColorComponents == 1 { if img.ColorComponents == 1 {
inlineImage.ColorSpace = MakeName("DeviceGray") inlineImage.ColorSpace = MakeName("G") // G short for DeviceGray
} else if img.ColorComponents == 3 { } else if img.ColorComponents == 3 {
inlineImage.ColorSpace = MakeName("DeviceRGB") inlineImage.ColorSpace = MakeName("RGB") // RGB short for DeviceRGB
} else if img.ColorComponents == 4 { } else if img.ColorComponents == 4 {
inlineImage.ColorSpace = MakeName("DeviceCMYK") inlineImage.ColorSpace = MakeName("CMYK") // CMYK short for DeviceCMYK
} else { } else {
common.Log.Debug("Invalid number of color components for inline image: %d", img.ColorComponents) common.Log.Debug("Invalid number of color components for inline image: %d", img.ColorComponents)
return nil, errors.New("Invalid number of color components") return nil, errors.New("Invalid number of color components")
@ -59,7 +62,6 @@ func NewInlineImageFromImage(img Image, encoder StreamEncoder) (*ContentStreamIn
} }
inlineImage.stream = encoded inlineImage.stream = encoded
filterName := encoder.GetFilterName()
if len(filterName) > 0 { if len(filterName) > 0 {
inlineImage.Filter = MakeName(filterName) inlineImage.Filter = MakeName(filterName)
} }
@ -111,40 +113,40 @@ func (this *ContentStreamInlineImage) DefaultWriteString() string {
s := "" s := ""
if this.BitsPerComponent != nil { if this.BitsPerComponent != nil {
s += "BPC " + this.BitsPerComponent.DefaultWriteString() + "\n" s += "/BPC " + this.BitsPerComponent.DefaultWriteString() + "\n"
} }
if this.ColorSpace != nil { if this.ColorSpace != nil {
s += "CS " + this.ColorSpace.DefaultWriteString() + "\n" s += "/CS " + this.ColorSpace.DefaultWriteString() + "\n"
} }
if this.Decode != nil { if this.Decode != nil {
s += "D " + this.Decode.DefaultWriteString() + "\n" s += "/D " + this.Decode.DefaultWriteString() + "\n"
} }
if this.DecodeParms != nil { if this.DecodeParms != nil {
s += "DP " + this.DecodeParms.DefaultWriteString() + "\n" s += "/DP " + this.DecodeParms.DefaultWriteString() + "\n"
} }
if this.Filter != nil { if this.Filter != nil {
s += "F " + this.Filter.DefaultWriteString() + "\n" s += "/F " + this.Filter.DefaultWriteString() + "\n"
} }
if this.Height != nil { if this.Height != nil {
s += "H " + this.Height.DefaultWriteString() + "\n" s += "/H " + this.Height.DefaultWriteString() + "\n"
} }
if this.ImageMask != nil { if this.ImageMask != nil {
s += "IM " + this.ImageMask.DefaultWriteString() + "\n" s += "/IM " + this.ImageMask.DefaultWriteString() + "\n"
} }
if this.Intent != nil { if this.Intent != nil {
s += "Intent " + this.Intent.DefaultWriteString() + "\n" s += "/Intent " + this.Intent.DefaultWriteString() + "\n"
} }
if this.Interpolate != nil { if this.Interpolate != nil {
s += "I " + this.Interpolate.DefaultWriteString() + "\n" s += "/I " + this.Interpolate.DefaultWriteString() + "\n"
} }
if this.Width != nil { if this.Width != nil {
s += "W " + this.Width.DefaultWriteString() + "\n" s += "/W " + this.Width.DefaultWriteString() + "\n"
} }
output.WriteString(s) output.WriteString(s)
output.WriteString("ID ") output.WriteString("ID ")
output.Write(this.stream) output.Write(this.stream)
output.WriteString("\n") output.WriteString("\nEI\n")
return output.String() return output.String()
} }
@ -171,6 +173,12 @@ func (this *ContentStreamInlineImage) GetColorSpace(resources *PdfPageResources)
} else if *name == "I" { } else if *name == "I" {
return nil, errors.New("Unsupported Index colorspace") return nil, errors.New("Unsupported Index colorspace")
} else { } 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)] cs, has := resources.ColorSpace.Colorspaces[string(*name)]
if !has { if !has {
// Can also refer to a name in the PDF page resources... // 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 return nil, err
} }
common.Log.Trace("encoder: %+v %T", encoder, encoder) common.Log.Trace("encoder: %+v %T", encoder, encoder)
common.Log.Trace("inline image: %+v", this)
decoded, err := encoder.DecodeBytes(this.stream) decoded, err := encoder.DecodeBytes(this.stream)
if err != nil { if err != nil {
@ -303,6 +312,7 @@ func (this *ContentStreamParser) ParseInlineImage() (*ContentStreamInlineImage,
// Not an operand.. Read key value properties.. // Not an operand.. Read key value properties..
param, ok := obj.(*PdfObjectName) param, ok := obj.(*PdfObjectName)
if !ok { 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) return nil, fmt.Errorf("Invalid inline image property (expecting name) - %T", obj)
} }
@ -391,6 +401,7 @@ func (this *ContentStreamParser) ParseInlineImage() (*ContentStreamInlineImage,
state = 2 state = 2
} else { } else {
im.stream = append(im.stream, skipBytes...) im.stream = append(im.stream, skipBytes...)
skipBytes = []byte{} // Clear.
// Need an extra check to decide if we fall back to state 0 or 1. // Need an extra check to decide if we fall back to state 0 or 1.
if IsWhiteSpace(c) { if IsWhiteSpace(c) {
state = 1 state = 1
@ -404,6 +415,7 @@ func (this *ContentStreamParser) ParseInlineImage() (*ContentStreamInlineImage,
state = 3 state = 3
} else { } else {
im.stream = append(im.stream, skipBytes...) im.stream = append(im.stream, skipBytes...)
skipBytes = []byte{} // Clear.
state = 0 state = 0
} }
} else if state == 3 { } else if state == 3 {
@ -420,6 +432,7 @@ func (this *ContentStreamParser) ParseInlineImage() (*ContentStreamInlineImage,
} else { } else {
// Seems like "<ws>EI" was part of the data. // Seems like "<ws>EI" was part of the data.
im.stream = append(im.stream, skipBytes...) im.stream = append(im.stream, skipBytes...)
skipBytes = []byte{} // Clear.
state = 0 state = 0
} }
} }

View File

@ -1067,8 +1067,10 @@ func (r *PdfPageResources) setXObjectByName(keyName string, stream *PdfObjectStr
r.XObject = &PdfObjectDictionary{} r.XObject = &PdfObjectDictionary{}
} }
xresDict, has := r.XObject.(*PdfObjectDictionary) obj := TraceToDirectObject(r.XObject)
xresDict, has := obj.(*PdfObjectDictionary)
if !has { if !has {
common.Log.Debug("Invalid XObject, got %T/%T", r.XObject, obj)
return errors.New("Type check error") return errors.New("Type check error")
} }