Form fill field rotation (#385)

* Refactor text field rotation

* Add rotation support for checkbox fields

* Add rotation support for combobox fields

* Add rotation support for text combobox fields

* Add documentation for the applyRotation of the AppearanceStyle
This commit is contained in:
Adrian-George Bostan 2020-06-30 21:34:03 +03:00 committed by GitHub
parent 54e965785b
commit ca7f479b7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -219,15 +219,15 @@ func genFieldTextAppearance(wa *model.PdfAnnotationWidget, ftxt *model.PdfFieldT
return nil, err
}
width, height := rect.Width(), rect.Height()
bboxWidth, bboxHeight := width, height
var rotation float64
if mkDict, has := core.GetDict(wa.MK); has {
mkDict, has := core.GetDict(wa.MK)
if has {
bsDict, _ := core.GetDict(wa.BS)
err := style.applyAppearanceCharacteristics(mkDict, bsDict, nil)
if err != nil {
return nil, err
}
rotation, _ = core.GetNumberAsFloat(mkDict.Get("R"))
}
// Get and process the default appearance string (DA) operands.
@ -252,26 +252,10 @@ func genFieldTextAppearance(wa *model.PdfAnnotationWidget, ftxt *model.PdfFieldT
cc.Add_BMC("Tx")
cc.Add_q()
bboxWidth, bboxHeight := width, height
if rotation != 0 {
// Calculate bounding box before rotation.
revRotation := -rotation
bbox := draw.Path{Points: []draw.Point{
draw.NewPoint(0, 0).Rotate(revRotation),
draw.NewPoint(width, 0).Rotate(revRotation),
draw.NewPoint(0, height).Rotate(revRotation),
draw.NewPoint(width, height).Rotate(revRotation),
}}.GetBoundingBox()
// Update width and height, as the appearance is generated based on
// the bounding of the annotation with no rotation.
width = bbox.Width
height = bbox.Height
// Apply rotation.
cc.RotateDeg(rotation)
cc.Translate(bbox.X, bbox.Y)
}
// Apply rotation if present.
// Update width and height, as the appearance is generated based on
// the bounding of the annotation with no rotation.
width, height = style.applyRotation(mkDict, width, height, cc)
// Graphic state changes.
cc.Add_BT()
@ -513,8 +497,10 @@ func genFieldTextCombAppearance(wa *model.PdfAnnotationWidget, ftxt *model.PdfFi
return nil, err
}
width, height := rect.Width(), rect.Height()
bboxWidth, bboxHeight := width, height
if mkDict, has := core.GetDict(wa.MK); has {
mkDict, has := core.GetDict(wa.MK)
if has {
bsDict, _ := core.GetDict(wa.BS)
err := style.applyAppearanceCharacteristics(mkDict, bsDict, nil)
if err != nil {
@ -551,6 +537,11 @@ func genFieldTextCombAppearance(wa *model.PdfAnnotationWidget, ftxt *model.PdfFi
cc.Add_BMC("Tx")
cc.Add_q()
// Apply rotation if present.
// Update width and height, as the appearance is generated based on
// the bounding of the annotation with no rotation.
width, height = style.applyRotation(mkDict, width, height, cc)
// Graphic state changes.
cc.Add_BT()
@ -680,7 +671,7 @@ func genFieldTextCombAppearance(wa *model.PdfAnnotationWidget, ftxt *model.PdfFi
xform := model.NewXObjectForm()
xform.Resources = resources
xform.BBox = core.MakeArrayFromFloats([]float64{0, 0, width, height})
xform.BBox = core.MakeArrayFromFloats([]float64{0, 0, bboxWidth, bboxHeight})
xform.SetContentStream(cc.Bytes(), defStreamEncoder())
apDict := core.MakeDict()
@ -702,6 +693,7 @@ func genFieldCheckboxAppearance(wa *model.PdfAnnotationWidget, fbtn *model.PdfFi
return nil, err
}
width, height := rect.Width(), rect.Height()
bboxWidth, bboxHeight := width, height
common.Log.Debug("Checkbox, wa BS: %v", wa.BS)
@ -710,7 +702,8 @@ func genFieldCheckboxAppearance(wa *model.PdfAnnotationWidget, fbtn *model.PdfFi
return nil, err
}
if mkDict, has := core.GetDict(wa.MK); has {
mkDict, has := core.GetDict(wa.MK)
if has {
bsDict, _ := core.GetDict(wa.BS)
err := style.applyAppearanceCharacteristics(mkDict, bsDict, zapfdb)
if err != nil {
@ -732,6 +725,11 @@ func genFieldCheckboxAppearance(wa *model.PdfAnnotationWidget, fbtn *model.PdfFi
drawAlignmentReticle(cc, style2, width, height)
}
// Apply rotation if present.
// Update width and height, as the appearance is generated based on
// the bounding of the annotation with no rotation.
width, height = style.applyRotation(mkDict, width, height, cc)
fontsize := style.AutoFontSizeFraction * height
checkmetrics, ok := zapfdb.GetRuneMetrics(style.CheckmarkRune)
@ -767,7 +765,7 @@ func genFieldCheckboxAppearance(wa *model.PdfAnnotationWidget, fbtn *model.PdfFi
xformOn.Resources = model.NewPdfPageResources()
xformOn.Resources.SetFontByName("ZaDb", zapfdb.ToPdfObject())
xformOn.BBox = core.MakeArrayFromFloats([]float64{0, 0, width, height})
xformOn.BBox = core.MakeArrayFromFloats([]float64{0, 0, bboxWidth, bboxHeight})
xformOn.SetContentStream(cc.Bytes(), defStreamEncoder())
}
@ -777,7 +775,7 @@ func genFieldCheckboxAppearance(wa *model.PdfAnnotationWidget, fbtn *model.PdfFi
if style.BorderSize > 0 {
drawRect(cc, style, width, height)
}
xformOff.BBox = core.MakeArrayFromFloats([]float64{0, 0, width, height})
xformOff.BBox = core.MakeArrayFromFloats([]float64{0, 0, bboxWidth, bboxHeight})
xformOff.SetContentStream(cc.Bytes(), defStreamEncoder())
}
@ -813,7 +811,8 @@ func genFieldComboboxAppearance(form *model.PdfAcroForm, wa *model.PdfAnnotation
return nil, err
}
if mkDict, has := core.GetDict(wa.MK); has {
mkDict, has := core.GetDict(wa.MK)
if has {
bsDict, _ := core.GetDict(wa.BS)
err := style.applyAppearanceCharacteristics(mkDict, bsDict, nil)
if err != nil {
@ -839,7 +838,7 @@ func genFieldComboboxAppearance(form *model.PdfAcroForm, wa *model.PdfAnnotation
}
if len(optstr) > 0 {
xform, err := makeComboboxTextXObjForm(fch.PdfField, width, height, optstr, style, daOps, form.DR)
xform, err := makeComboboxTextXObjForm(fch.PdfField, width, height, optstr, style, daOps, form.DR, mkDict)
if err != nil {
return nil, err
}
@ -857,8 +856,9 @@ func genFieldComboboxAppearance(form *model.PdfAcroForm, wa *model.PdfAnnotation
// Make a text-based XObj Form.
func makeComboboxTextXObjForm(field *model.PdfField, width, height float64,
text string, style AppearanceStyle, daOps *contentstream.ContentStreamOperations,
dr *model.PdfPageResources) (*model.XObjectForm, error) {
dr *model.PdfPageResources, mkDict *core.PdfObjectDictionary) (*model.XObjectForm, error) {
resources := model.NewPdfPageResources()
bboxWidth, bboxHeight := width, height
cc := contentstream.NewContentCreator()
if style.BorderSize > 0 {
@ -875,6 +875,11 @@ func makeComboboxTextXObjForm(field *model.PdfField, width, height float64,
// Graphic state changes.
cc.Add_BT()
// Apply rotation if present.
// Update width and height, as the appearance is generated based on
// the bounding of the annotation with no rotation.
width, height = style.applyRotation(mkDict, width, height, cc)
// Process DA operands.
apFont, hasTf, err := style.processDA(field, daOps, dr, resources, cc)
if err != nil {
@ -950,7 +955,7 @@ func makeComboboxTextXObjForm(field *model.PdfField, width, height float64,
xform := model.NewXObjectForm()
xform.Resources = resources
xform.BBox = core.MakeArrayFromFloats([]float64{0, 0, width, height})
xform.BBox = core.MakeArrayFromFloats([]float64{0, 0, bboxWidth, bboxHeight})
xform.SetContentStream(cc.Bytes(), defStreamEncoder())
return xform, nil
@ -1067,6 +1072,40 @@ func (style *AppearanceStyle) applyAppearanceCharacteristics(mkDict *core.PdfObj
return nil
}
// applyRotation applies the rotation specified by the MK dictionary,
// if present. The method returns the width and height of the annotation
// rectangle with no rotation.
func (style *AppearanceStyle) applyRotation(mkDict *core.PdfObjectDictionary,
width, height float64, cc *contentstream.ContentCreator) (float64, float64) {
if !style.AllowMK {
return width, height
}
if mkDict == nil {
return width, height
}
// Extract rotation from the MK dictionary.
rotation, _ := core.GetNumberAsFloat(mkDict.Get("R"))
if rotation == 0 {
return width, height
}
// Calculate bounding box before rotation.
revRotation := -rotation
bbox := draw.Path{Points: []draw.Point{
draw.NewPoint(0, 0).Rotate(revRotation),
draw.NewPoint(width, 0).Rotate(revRotation),
draw.NewPoint(0, height).Rotate(revRotation),
draw.NewPoint(width, height).Rotate(revRotation),
}}.GetBoundingBox()
// Apply rotation.
cc.RotateDeg(rotation)
cc.Translate(bbox.X, bbox.Y)
return bbox.Width, bbox.Height
}
// processDA adds the operands found in the field default appearance stream to
// the provided content stream creator. It also provides a fallback font, based
// on the configuration of the AppearanceStyle, if no valid font is specified