mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-26 13:48:55 +08:00
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:
parent
54e965785b
commit
ca7f479b7c
@ -219,15 +219,15 @@ func genFieldTextAppearance(wa *model.PdfAnnotationWidget, ftxt *model.PdfFieldT
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
width, height := rect.Width(), rect.Height()
|
width, height := rect.Width(), rect.Height()
|
||||||
|
bboxWidth, bboxHeight := width, height
|
||||||
|
|
||||||
var rotation float64
|
mkDict, has := core.GetDict(wa.MK)
|
||||||
if mkDict, has := core.GetDict(wa.MK); has {
|
if has {
|
||||||
bsDict, _ := core.GetDict(wa.BS)
|
bsDict, _ := core.GetDict(wa.BS)
|
||||||
err := style.applyAppearanceCharacteristics(mkDict, bsDict, nil)
|
err := style.applyAppearanceCharacteristics(mkDict, bsDict, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rotation, _ = core.GetNumberAsFloat(mkDict.Get("R"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get and process the default appearance string (DA) operands.
|
// 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_BMC("Tx")
|
||||||
cc.Add_q()
|
cc.Add_q()
|
||||||
|
|
||||||
bboxWidth, bboxHeight := width, height
|
// Apply rotation if present.
|
||||||
if rotation != 0 {
|
// Update width and height, as the appearance is generated based on
|
||||||
// Calculate bounding box before rotation.
|
// the bounding of the annotation with no rotation.
|
||||||
revRotation := -rotation
|
width, height = style.applyRotation(mkDict, width, height, cc)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Graphic state changes.
|
// Graphic state changes.
|
||||||
cc.Add_BT()
|
cc.Add_BT()
|
||||||
@ -513,8 +497,10 @@ func genFieldTextCombAppearance(wa *model.PdfAnnotationWidget, ftxt *model.PdfFi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
width, height := rect.Width(), rect.Height()
|
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)
|
bsDict, _ := core.GetDict(wa.BS)
|
||||||
err := style.applyAppearanceCharacteristics(mkDict, bsDict, nil)
|
err := style.applyAppearanceCharacteristics(mkDict, bsDict, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -551,6 +537,11 @@ func genFieldTextCombAppearance(wa *model.PdfAnnotationWidget, ftxt *model.PdfFi
|
|||||||
cc.Add_BMC("Tx")
|
cc.Add_BMC("Tx")
|
||||||
cc.Add_q()
|
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.
|
// Graphic state changes.
|
||||||
cc.Add_BT()
|
cc.Add_BT()
|
||||||
|
|
||||||
@ -680,7 +671,7 @@ func genFieldTextCombAppearance(wa *model.PdfAnnotationWidget, ftxt *model.PdfFi
|
|||||||
|
|
||||||
xform := model.NewXObjectForm()
|
xform := model.NewXObjectForm()
|
||||||
xform.Resources = resources
|
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())
|
xform.SetContentStream(cc.Bytes(), defStreamEncoder())
|
||||||
|
|
||||||
apDict := core.MakeDict()
|
apDict := core.MakeDict()
|
||||||
@ -702,6 +693,7 @@ func genFieldCheckboxAppearance(wa *model.PdfAnnotationWidget, fbtn *model.PdfFi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
width, height := rect.Width(), rect.Height()
|
width, height := rect.Width(), rect.Height()
|
||||||
|
bboxWidth, bboxHeight := width, height
|
||||||
|
|
||||||
common.Log.Debug("Checkbox, wa BS: %v", wa.BS)
|
common.Log.Debug("Checkbox, wa BS: %v", wa.BS)
|
||||||
|
|
||||||
@ -710,7 +702,8 @@ func genFieldCheckboxAppearance(wa *model.PdfAnnotationWidget, fbtn *model.PdfFi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if mkDict, has := core.GetDict(wa.MK); has {
|
mkDict, has := core.GetDict(wa.MK)
|
||||||
|
if has {
|
||||||
bsDict, _ := core.GetDict(wa.BS)
|
bsDict, _ := core.GetDict(wa.BS)
|
||||||
err := style.applyAppearanceCharacteristics(mkDict, bsDict, zapfdb)
|
err := style.applyAppearanceCharacteristics(mkDict, bsDict, zapfdb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -732,6 +725,11 @@ func genFieldCheckboxAppearance(wa *model.PdfAnnotationWidget, fbtn *model.PdfFi
|
|||||||
drawAlignmentReticle(cc, style2, width, height)
|
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
|
fontsize := style.AutoFontSizeFraction * height
|
||||||
|
|
||||||
checkmetrics, ok := zapfdb.GetRuneMetrics(style.CheckmarkRune)
|
checkmetrics, ok := zapfdb.GetRuneMetrics(style.CheckmarkRune)
|
||||||
@ -767,7 +765,7 @@ func genFieldCheckboxAppearance(wa *model.PdfAnnotationWidget, fbtn *model.PdfFi
|
|||||||
|
|
||||||
xformOn.Resources = model.NewPdfPageResources()
|
xformOn.Resources = model.NewPdfPageResources()
|
||||||
xformOn.Resources.SetFontByName("ZaDb", zapfdb.ToPdfObject())
|
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())
|
xformOn.SetContentStream(cc.Bytes(), defStreamEncoder())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -777,7 +775,7 @@ func genFieldCheckboxAppearance(wa *model.PdfAnnotationWidget, fbtn *model.PdfFi
|
|||||||
if style.BorderSize > 0 {
|
if style.BorderSize > 0 {
|
||||||
drawRect(cc, style, width, height)
|
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())
|
xformOff.SetContentStream(cc.Bytes(), defStreamEncoder())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -813,7 +811,8 @@ func genFieldComboboxAppearance(form *model.PdfAcroForm, wa *model.PdfAnnotation
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if mkDict, has := core.GetDict(wa.MK); has {
|
mkDict, has := core.GetDict(wa.MK)
|
||||||
|
if has {
|
||||||
bsDict, _ := core.GetDict(wa.BS)
|
bsDict, _ := core.GetDict(wa.BS)
|
||||||
err := style.applyAppearanceCharacteristics(mkDict, bsDict, nil)
|
err := style.applyAppearanceCharacteristics(mkDict, bsDict, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -839,7 +838,7 @@ func genFieldComboboxAppearance(form *model.PdfAcroForm, wa *model.PdfAnnotation
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(optstr) > 0 {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -857,8 +856,9 @@ func genFieldComboboxAppearance(form *model.PdfAcroForm, wa *model.PdfAnnotation
|
|||||||
// Make a text-based XObj Form.
|
// Make a text-based XObj Form.
|
||||||
func makeComboboxTextXObjForm(field *model.PdfField, width, height float64,
|
func makeComboboxTextXObjForm(field *model.PdfField, width, height float64,
|
||||||
text string, style AppearanceStyle, daOps *contentstream.ContentStreamOperations,
|
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()
|
resources := model.NewPdfPageResources()
|
||||||
|
bboxWidth, bboxHeight := width, height
|
||||||
|
|
||||||
cc := contentstream.NewContentCreator()
|
cc := contentstream.NewContentCreator()
|
||||||
if style.BorderSize > 0 {
|
if style.BorderSize > 0 {
|
||||||
@ -875,6 +875,11 @@ func makeComboboxTextXObjForm(field *model.PdfField, width, height float64,
|
|||||||
// Graphic state changes.
|
// Graphic state changes.
|
||||||
cc.Add_BT()
|
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.
|
// Process DA operands.
|
||||||
apFont, hasTf, err := style.processDA(field, daOps, dr, resources, cc)
|
apFont, hasTf, err := style.processDA(field, daOps, dr, resources, cc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -950,7 +955,7 @@ func makeComboboxTextXObjForm(field *model.PdfField, width, height float64,
|
|||||||
|
|
||||||
xform := model.NewXObjectForm()
|
xform := model.NewXObjectForm()
|
||||||
xform.Resources = resources
|
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())
|
xform.SetContentStream(cc.Bytes(), defStreamEncoder())
|
||||||
|
|
||||||
return xform, nil
|
return xform, nil
|
||||||
@ -1067,6 +1072,40 @@ func (style *AppearanceStyle) applyAppearanceCharacteristics(mkDict *core.PdfObj
|
|||||||
return nil
|
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
|
// processDA adds the operands found in the field default appearance stream to
|
||||||
// the provided content stream creator. It also provides a fallback font, based
|
// the provided content stream creator. It also provides a fallback font, based
|
||||||
// on the configuration of the AppearanceStyle, if no valid font is specified
|
// on the configuration of the AppearanceStyle, if no valid font is specified
|
||||||
|
Loading…
x
Reference in New Issue
Block a user