github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/annotator/rectangle.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  // A rectangle defined with a specified Width and Height and a lower left corner at (X,Y).  The rectangle can
    17  // optionally have a border and a filling color.
    18  // The Width/Height includes the border (if any specified).
    19  type RectangleAnnotationDef struct {
    20  	X             float64
    21  	Y             float64
    22  	Width         float64
    23  	Height        float64
    24  	FillEnabled   bool // Show fill?
    25  	FillColor     *pdf.PdfColorDeviceRGB
    26  	BorderEnabled bool // Show border?
    27  	BorderWidth   float64
    28  	BorderColor   *pdf.PdfColorDeviceRGB
    29  	Opacity       float64 // Alpha value (0-1).
    30  }
    31  
    32  // Creates a rectangle annotation object that can be added to page PDF annotations.
    33  func CreateRectangleAnnotation(rectDef RectangleAnnotationDef) (*pdf.PdfAnnotation, error) {
    34  	rectAnnotation := pdf.NewPdfAnnotationSquare()
    35  
    36  	if rectDef.BorderEnabled {
    37  		r, g, b := rectDef.BorderColor.R(), rectDef.BorderColor.G(), rectDef.BorderColor.B()
    38  		rectAnnotation.C = pdfcore.MakeArrayFromFloats([]float64{r, g, b})
    39  		bs := pdf.NewBorderStyle()
    40  		bs.SetBorderWidth(rectDef.BorderWidth)
    41  		rectAnnotation.BS = bs.ToPdfObject()
    42  	}
    43  
    44  	if rectDef.FillEnabled {
    45  		r, g, b := rectDef.FillColor.R(), rectDef.FillColor.G(), rectDef.FillColor.B()
    46  		rectAnnotation.IC = pdfcore.MakeArrayFromFloats([]float64{r, g, b})
    47  	} else {
    48  		rectAnnotation.IC = pdfcore.MakeArrayFromIntegers([]int{}) // No fill.
    49  	}
    50  
    51  	if rectDef.Opacity < 1.0 {
    52  		rectAnnotation.CA = pdfcore.MakeFloat(rectDef.Opacity)
    53  	}
    54  
    55  	// Make the appearance stream (for uniform appearance).
    56  	apDict, bbox, err := makeRectangleAnnotationAppearanceStream(rectDef)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	rectAnnotation.AP = apDict
    62  	rectAnnotation.Rect = pdfcore.MakeArrayFromFloats([]float64{bbox.Llx, bbox.Lly, bbox.Urx, bbox.Ury})
    63  
    64  	return rectAnnotation.PdfAnnotation, nil
    65  
    66  }
    67  
    68  func makeRectangleAnnotationAppearanceStream(rectDef RectangleAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) {
    69  	form := pdf.NewXObjectForm()
    70  	form.Resources = pdf.NewPdfPageResources()
    71  
    72  	gsName := ""
    73  	if rectDef.Opacity < 1.0 {
    74  		// Create graphics state with right opacity.
    75  		gsState := pdfcore.MakeDict()
    76  		gsState.Set("ca", pdfcore.MakeFloat(rectDef.Opacity))
    77  		gsState.Set("CA", pdfcore.MakeFloat(rectDef.Opacity))
    78  		err := form.Resources.AddExtGState("gs1", gsState)
    79  		if err != nil {
    80  			common.Log.Debug("Unable to add extgstate gs1")
    81  			return nil, nil, err
    82  		}
    83  
    84  		gsName = "gs1"
    85  	}
    86  
    87  	content, localBbox, globalBbox, err := drawPdfRectangle(rectDef, gsName)
    88  	if err != nil {
    89  		return nil, nil, err
    90  	}
    91  
    92  	err = form.SetContentStream(content, nil)
    93  	if err != nil {
    94  		return nil, nil, err
    95  	}
    96  
    97  	// Local bounding box for the XObject Form.
    98  	form.BBox = localBbox.ToPdfObject()
    99  
   100  	apDict := pdfcore.MakeDict()
   101  	apDict.Set("N", form.ToPdfObject())
   102  
   103  	return apDict, globalBbox, nil
   104  }
   105  
   106  func drawPdfRectangle(rectDef RectangleAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) {
   107  	// The annotation is drawn locally in a relative coordinate system with 0,0 as the origin rather than an offset.
   108  	rect := draw.Rectangle{
   109  		X:             0,
   110  		Y:             0,
   111  		Width:         rectDef.Width,
   112  		Height:        rectDef.Height,
   113  		FillEnabled:   rectDef.FillEnabled,
   114  		FillColor:     rectDef.FillColor,
   115  		BorderEnabled: rectDef.BorderEnabled,
   116  		BorderWidth:   2 * rectDef.BorderWidth,
   117  		BorderColor:   rectDef.BorderColor,
   118  		Opacity:       rectDef.Opacity,
   119  	}
   120  
   121  	content, localBbox, err := rect.Draw(gsName)
   122  	if err != nil {
   123  		return nil, nil, nil, err
   124  	}
   125  
   126  	// Bounding box - global page coordinate system (with offset).
   127  	globalBbox := &pdf.PdfRectangle{}
   128  	globalBbox.Llx = rectDef.X + localBbox.Llx
   129  	globalBbox.Lly = rectDef.Y + localBbox.Lly
   130  	globalBbox.Urx = rectDef.X + localBbox.Urx
   131  	globalBbox.Ury = rectDef.Y + localBbox.Ury
   132  
   133  	return content, localBbox, globalBbox, nil
   134  }