github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/annotator/circle.go (about)

     1  /*
     2   * This file is subject to the terms and conditions defined in
     3   * file 'LICENSE.md', which is part of this source code package.
     4   */
     5  
     6  package annotator
     7  
     8  import (
     9  	"github.com/unidoc/unidoc/common"
    10  
    11  	"github.com/unidoc/unidoc/pdf/contentstream/draw"
    12  	pdfcore "github.com/unidoc/unidoc/pdf/core"
    13  	pdf "github.com/unidoc/unidoc/pdf/model"
    14  )
    15  
    16  type CircleAnnotationDef struct {
    17  	X             float64
    18  	Y             float64
    19  	Width         float64
    20  	Height        float64
    21  	FillEnabled   bool // Show fill?
    22  	FillColor     *pdf.PdfColorDeviceRGB
    23  	BorderEnabled bool // Show border?
    24  	BorderWidth   float64
    25  	BorderColor   *pdf.PdfColorDeviceRGB
    26  	Opacity       float64 // Alpha value (0-1).
    27  }
    28  
    29  // Creates a circle/ellipse annotation object with appearance stream that can be added to page PDF annotations.
    30  func CreateCircleAnnotation(circDef CircleAnnotationDef) (*pdf.PdfAnnotation, error) {
    31  	circAnnotation := pdf.NewPdfAnnotationCircle()
    32  
    33  	if circDef.BorderEnabled {
    34  		r, g, b := circDef.BorderColor.R(), circDef.BorderColor.G(), circDef.BorderColor.B()
    35  		circAnnotation.C = pdfcore.MakeArrayFromFloats([]float64{r, g, b})
    36  		bs := pdf.NewBorderStyle()
    37  		bs.SetBorderWidth(circDef.BorderWidth)
    38  		circAnnotation.BS = bs.ToPdfObject()
    39  	}
    40  
    41  	if circDef.FillEnabled {
    42  		r, g, b := circDef.FillColor.R(), circDef.FillColor.G(), circDef.FillColor.B()
    43  		circAnnotation.IC = pdfcore.MakeArrayFromFloats([]float64{r, g, b})
    44  	} else {
    45  		circAnnotation.IC = pdfcore.MakeArrayFromIntegers([]int{}) // No fill.
    46  	}
    47  
    48  	if circDef.Opacity < 1.0 {
    49  		circAnnotation.CA = pdfcore.MakeFloat(circDef.Opacity)
    50  	}
    51  
    52  	// Make the appearance stream (for uniform appearance).
    53  	apDict, bbox, err := makeCircleAnnotationAppearanceStream(circDef)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	circAnnotation.AP = apDict
    59  	circAnnotation.Rect = pdfcore.MakeArrayFromFloats([]float64{bbox.Llx, bbox.Lly, bbox.Urx, bbox.Ury})
    60  
    61  	return circAnnotation.PdfAnnotation, nil
    62  
    63  }
    64  
    65  func makeCircleAnnotationAppearanceStream(circDef CircleAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) {
    66  	form := pdf.NewXObjectForm()
    67  	form.Resources = pdf.NewPdfPageResources()
    68  
    69  	gsName := ""
    70  	if circDef.Opacity < 1.0 {
    71  		// Create graphics state with right opacity.
    72  		gsState := pdfcore.MakeDict()
    73  		gsState.Set("ca", pdfcore.MakeFloat(circDef.Opacity))
    74  		gsState.Set("CA", pdfcore.MakeFloat(circDef.Opacity))
    75  		err := form.Resources.AddExtGState("gs1", gsState)
    76  		if err != nil {
    77  			common.Log.Debug("Unable to add extgstate gs1")
    78  			return nil, nil, err
    79  		}
    80  
    81  		gsName = "gs1"
    82  	}
    83  
    84  	content, localBbox, globalBbox, err := drawPdfCircle(circDef, gsName)
    85  	if err != nil {
    86  		return nil, nil, err
    87  	}
    88  
    89  	err = form.SetContentStream(content, nil)
    90  	if err != nil {
    91  		return nil, nil, err
    92  	}
    93  
    94  	// Local bounding box for the XObject Form.
    95  	form.BBox = localBbox.ToPdfObject()
    96  
    97  	apDict := pdfcore.MakeDict()
    98  	apDict.Set("N", form.ToPdfObject())
    99  
   100  	return apDict, globalBbox, nil
   101  }
   102  
   103  func drawPdfCircle(circDef CircleAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) {
   104  	// The annotation is drawn locally in a relative coordinate system with 0,0 as the origin rather than an offset.
   105  	circle := draw.Circle{
   106  		X:             circDef.X,
   107  		Y:             circDef.Y,
   108  		Width:         circDef.Width,
   109  		Height:        circDef.Height,
   110  		FillEnabled:   circDef.FillEnabled,
   111  		FillColor:     circDef.FillColor,
   112  		BorderEnabled: circDef.BorderEnabled,
   113  		BorderWidth:   circDef.BorderWidth,
   114  		BorderColor:   circDef.BorderColor,
   115  		Opacity:       circDef.Opacity,
   116  	}
   117  
   118  	content, localBbox, err := circle.Draw(gsName)
   119  	if err != nil {
   120  		return nil, nil, nil, err
   121  	}
   122  
   123  	// Bounding box - global page coordinate system (with offset).
   124  	globalBbox := &pdf.PdfRectangle{}
   125  	globalBbox.Llx = circDef.X + localBbox.Llx
   126  	globalBbox.Lly = circDef.Y + localBbox.Lly
   127  	globalBbox.Urx = circDef.X + localBbox.Urx
   128  	globalBbox.Ury = circDef.Y + localBbox.Ury
   129  
   130  	return content, localBbox, globalBbox, nil
   131  }