Text generating operators in content creator. Some refactoring of annotator.

This commit is contained in:
Gunnsteinn Hall 2017-05-24 21:43:32 +00:00
parent 36aaf59139
commit 5a7469958d
9 changed files with 379 additions and 181 deletions

View File

@ -10,171 +10,8 @@ import (
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
}

View File

@ -14,14 +14,53 @@ import (
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)
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
}
func makeCircleAnnotationAppearanceStream(circDef CircleAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) {

View File

@ -15,15 +15,72 @@ import (
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)
}
// 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
}
func makeLineAnnotationAppearanceStream(lineDef LineAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) {

View File

@ -14,6 +14,58 @@ import (
pdf "github.com/unidoc/unidoc/pdf/model"
)
// 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
}
func makeRectangleAnnotationAppearanceStream(rectDef RectangleAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) {
form := pdf.NewXObjectForm()
form.Resources = pdf.NewPdfPageResources()

27
pdf/annotator/utils.go Normal file
View File

@ -0,0 +1,27 @@
package annotator
import (
pdfcontent "github.com/unidoc/unidoc/pdf/contentstream"
"github.com/unidoc/unidoc/pdf/contentstream/draw"
)
// 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)
}
}
}
// 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)
}
}

View File

@ -414,7 +414,6 @@ func (this *ContentCreator) Add_k(c, m, y, k float64) *ContentCreator {
/* Shading operators. */
// sh: Paint the shape and color described by a shading dictionary.
func (this *ContentCreator) Add_sh(name PdfObjectName) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "sh"
@ -422,3 +421,163 @@ func (this *ContentCreator) Add_sh(name PdfObjectName) *ContentCreator {
this.operands = append(this.operands, &op)
return this
}
/* Text related operators */
/* Text state operators */
// BT: Begin text.
func (this *ContentCreator) Add_BT() *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "BT"
this.operands = append(this.operands, &op)
return this
}
// ET: End text.
func (this *ContentCreator) Add_ET() *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "ET"
this.operands = append(this.operands, &op)
return this
}
// Tc: Set character spacing.
func (this *ContentCreator) Add_Tc(charSpace float64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "Tc"
op.Params = makeParamsFromFloats([]float64{charSpace})
this.operands = append(this.operands, &op)
return this
}
// Tw: Set word spacing.
func (this *ContentCreator) Add_Tw(wordSpace float64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "Tw"
op.Params = makeParamsFromFloats([]float64{wordSpace})
this.operands = append(this.operands, &op)
return this
}
// Tz: Set horizontal scaling.
func (this *ContentCreator) Add_Tz(scale float64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "Tz"
op.Params = makeParamsFromFloats([]float64{scale})
this.operands = append(this.operands, &op)
return this
}
// TL: Set leading.
func (this *ContentCreator) Add_Tl(leading float64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "Tl"
op.Params = makeParamsFromFloats([]float64{leading})
this.operands = append(this.operands, &op)
return this
}
// Tf: Set font and font size.
func (this *ContentCreator) Add_Tf(fontName PdfObjectName, fontSize float64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "Tf"
op.Params = makeParamsFromNames([]PdfObjectName{fontName})
op.Params = append(op.Params, makeParamsFromFloats([]float64{fontSize})...)
this.operands = append(this.operands, &op)
return this
}
// Tr: Set text rendering mode.
func (this *ContentCreator) Add_Tr(render int64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "Tr"
op.Params = makeParamsFromInts([]int64{render})
this.operands = append(this.operands, &op)
return this
}
// Ts: Set text rise.
func (this *ContentCreator) Add_Ts(rise float64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "Ts"
op.Params = makeParamsFromFloats([]float64{rise})
this.operands = append(this.operands, &op)
return this
}
/* Text positioning operators. */
// Td: Move to start of next line with offset (tx, ty).
func (this *ContentCreator) Add_Td(tx, ty float64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "Td"
op.Params = makeParamsFromFloats([]float64{tx, ty})
this.operands = append(this.operands, &op)
return this
}
// TD: Move to start of next line with offset (tx, ty).
func (this *ContentCreator) Add_TD(tx, ty float64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "TD"
op.Params = makeParamsFromFloats([]float64{tx, ty})
this.operands = append(this.operands, &op)
return this
}
// Tm: Set the text line matrix.
func (this *ContentCreator) Add_Tm(a, b, c, d, e, f float64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "Tm"
op.Params = makeParamsFromFloats([]float64{a, b, c, d, e, f})
this.operands = append(this.operands, &op)
return this
}
// T*: Move to the start of next line.
func (this *ContentCreator) Add_Tstar() *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "T*"
this.operands = append(this.operands, &op)
return this
}
/* Text showing operators */
// Tj: Show a text string.
func (this *ContentCreator) Add_Tj(textstr PdfObjectString) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "Tj"
op.Params = makeParamsFromStrings([]PdfObjectString{textstr})
this.operands = append(this.operands, &op)
return this
}
// ': Move to next line and show a string.
func (this *ContentCreator) Add_quote(textstr PdfObjectString) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "'"
op.Params = makeParamsFromStrings([]PdfObjectString{textstr})
this.operands = append(this.operands, &op)
return this
}
// '': Move to next line and show a string, using aw and ac as word and character spacing respectively.
func (this *ContentCreator) Add_quotes(textstr PdfObjectString, aw, ac float64) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "''"
op.Params = makeParamsFromFloats([]float64{aw, ac})
op.Params = append(op.Params, makeParamsFromStrings([]PdfObjectString{textstr})...)
this.operands = append(this.operands, &op)
return this
}
// TJ. Show one or more text string. Array of numbers (displacement) and strings.
func (this *ContentCreator) Add_TJ(vals ...PdfObject) *ContentCreator {
op := ContentStreamOperation{}
op.Operand = "TJ"
op.Params = vals
this.operands = append(this.operands, &op)
return this
}

View File

@ -23,6 +23,14 @@ func makeParamsFromNames(vals []PdfObjectName) []PdfObject {
return params
}
func makeParamsFromStrings(vals []PdfObjectString) []PdfObject {
params := []PdfObject{}
for _, val := range vals {
params = append(params, MakeString(string(val)))
}
return params
}
func makeParamsFromInts(vals []int64) []PdfObject {
params := []PdfObject{}
for _, val := range vals {

View File

@ -554,6 +554,26 @@ func (this *PdfPage) HasImageResource(name PdfObjectName) bool {
} else {
return false
}
}
// Check if has font resource by name.
func (this *PdfPage) HasFontByName(name PdfObjectName) bool {
resources, err := this.GetResources()
if err != nil {
return false
}
fontDict, has := resources.Font.(*PdfObjectDictionary)
if !has {
return false
}
if _, has := (*fontDict)[name]; has {
return true
} else {
return false
}
}
// Add a graphics state to the XObject resources.

View File

@ -15,8 +15,7 @@ import (
// Page resources model.
// Implements PdfModel.
type PdfPageResources struct {
ExtGState PdfObject
//ColorSpace PdfObject
ExtGState PdfObject
ColorSpace *PdfPageResourcesColorspaces
Pattern PdfObject
Shading PdfObject