Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions accessibility/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ The examples in this section are showcasing how to work with accessibility featu

## Examples

- [pdf_add_image_alt_text.go](pdf_add_image_alt_text.go) showcases how to constuct a `StructTreeRoot` object and add alternate text for images.
- [pdf_add_image_alt_text.go](pdf_add_image_alt_text.go) showcases how to construct a `StructTreeRoot` object and add alternate text for images.
- [pdf_copy_page_with_accessibility.go](pdf_copy_page_with_accessibility.go) demonstrates how to copy pages from an existing PDF file to a new PDF file and preserve the structure tree information.
- [pdf_set_language_identifier.go](pdf_set_language_identifier.go) demonstrates how to set the language identifier for the document and its content.
- [pdf_copy_page_with_accessibility.go](pdf_copy_page_with_accessibility.go) demonstrates how to copy pages from an existing PDF file to a new PDF file and preserve the structure tree information.
- [pdf_tag_annots.go](pdf_tag_annots.go) demonstrates how to create a PDF with text annotations that are properly tagged in the document structure tree for accessibility compliance.
- [pdf_tag_form.go](pdf_tag_form.go) demonstrates how to create a PDF with form fields (text fields, submit and reset buttons) that are properly tagged in the document structure tree for accessibility compliance.
- [pdf_tag_grid.go](pdf_tag_grid.go) demonstrates how to create a tagged PDF with a grid (table) that is properly tagged in the document structure tree for accessibility compliance.
- [pdf_tag_link_annot.go](pdf_tag_link_annot.go) demonstrates how to create accessible links in PDF documents following best practices for PDF/UA compliance.
- [pdf_tag_list.go](pdf_tag_list.go) demonstrates how to create a tagged PDF document with nested lists using proper accessibility tags and document structure tree.
- [pdf_tag_table.go](pdf_tag_table.go) demonstrates how to create a tagged PDF with a table that includes proper tagging structure for accessibility compliance.
127 changes: 127 additions & 0 deletions accessibility/pdf_tag_annots.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// This example demonstrates how to create a PDF with text annotations
// using the UniPDF library. The PDF will contain multiple text annotations
// with different properties, and the annotations will be properly tagged
// in the document structure tree for accessibility compliance.
//

package main

import (
"fmt"
"os"

"github.com/unidoc/unipdf/v4/common/license"
"github.com/unidoc/unipdf/v4/core"
"github.com/unidoc/unipdf/v4/creator"
"github.com/unidoc/unipdf/v4/model"
)

func init() {
// Make sure to load your metered License API key prior to using the library.
// If you need a key, you can sign up and create a free one at https://cloud.unidoc.io
err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
if err != nil {
panic(err)
}
}

func main() {
outputPath := "pdf_tag_annots.pdf"

err := createPdfWithTextAnnotations(outputPath)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}

fmt.Printf("Complete, see output file: %s\n", outputPath)
}

// Create a new PDF with text annotations.
func createPdfWithTextAnnotations(outputPath string) error {
// Create a new Creator.
c := creator.New()

// Construct the StructTreeRoot.
str := model.NewStructTreeRoot()

// Construct base K dictionary.
docK := model.NewKDictionary()
docK.S = core.MakeName(string(model.StructureTypeDocument))

str.AddKDict(docK)

// Create a new page with standard letter size.
page := model.NewPdfPage()
mediaBox := core.MakeArrayFromFloats([]float64{0, 0, 612, 792})
mediaRect, err := model.NewPdfRectangle(*mediaBox)
if err != nil {
return fmt.Errorf("failed to create MediaBox rectangle: %w", err)
}
page.MediaBox = mediaRect

// Add first text annotation.
textAnnotation1 := model.NewPdfAnnotationText()
textAnnotation1.Contents = core.MakeString("This is a sample text annotation!")
textAnnotation1.Rect = core.MakeArray(
core.MakeInteger(100), // x1
core.MakeInteger(600), // y1 (from bottom of page)
core.MakeInteger(150), // x2 (x1 + width)
core.MakeInteger(650), // y2 (y1 + height)
)
// Set annotation properties.
textAnnotation1.Open = core.MakeBool(false) // Closed by default
textAnnotation1.Name = core.MakeName("Comment")

docK.AddKChild(textAnnotation1.GenerateKDict())

// Add second text annotation.
textAnnotation2 := model.NewPdfAnnotationText()
textAnnotation2.Contents = core.MakeString("Another annotation with more detailed information about this section.")
textAnnotation2.Rect = core.MakeArray(
core.MakeInteger(300), // x1
core.MakeInteger(550), // y1
core.MakeInteger(350), // x2
core.MakeInteger(600), // y2
)
textAnnotation2.Open = core.MakeBool(true) // Open by default
textAnnotation2.Name = core.MakeName("Note")

docK.AddKChild(textAnnotation2.GenerateKDict())

// Add third text annotation.
textAnnotation3 := model.NewPdfAnnotationText()
textAnnotation3.Contents = core.MakeString("You can click on these yellow icons to view the annotation content.")
textAnnotation3.Rect = core.MakeArray(
core.MakeInteger(450), // x1
core.MakeInteger(400), // y1
core.MakeInteger(500), // x2
core.MakeInteger(450), // y2
)
textAnnotation3.Open = core.MakeBool(false)
textAnnotation3.Name = core.MakeName("Help")

docK.AddKChild(textAnnotation3.GenerateKDict())

// Add annotations to the page.
page.AddAnnotation(textAnnotation1.PdfAnnotation)
page.AddAnnotation(textAnnotation2.PdfAnnotation)
page.AddAnnotation(textAnnotation3.PdfAnnotation)

// Add StructTreeRoot to the page.
c.SetStructTreeRoot(str)

// Add the page to the writer.
err = c.AddPage(page)
if err != nil {
return err
}

// Write the PDF to file.
err = c.WriteToFile(outputPath)
if err != nil {
return err
}

return nil
}
190 changes: 190 additions & 0 deletions accessibility/pdf_tag_form.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// This example demonstrates how to create a PDF with form fields
// using the UniPDF library. The PDF will contain multiple text fields
// along with submit and reset buttons, and the form fields will be
// properly tagged in the document structure tree for accessibility compliance.
//
// The example covers best practices for PDF/UA compliance:
// 1. Each form field has an associated label with a tooltip.
// 2. The document structure tree is constructed with K dictionaries
// to represent the hierarchical structure of the form elements.
// 3. Each label is associated with its corresponding form field using
// marked content IDs (MCID).
// 4. The submit button is configured to submit the form data to a specified URL.
// 5. The reset button is configured to reset the specified fields to their default values.

package main

import (
"log"
"os"

"github.com/unidoc/unipdf/v4/annotator"
"github.com/unidoc/unipdf/v4/common/license"
"github.com/unidoc/unipdf/v4/contentstream/draw"
"github.com/unidoc/unipdf/v4/core"
"github.com/unidoc/unipdf/v4/creator"
"github.com/unidoc/unipdf/v4/model"
)

func init() {
// Make sure to load your metered License API key prior to using the library.
// If you need a key, you can sign up and create a free one at https://cloud.unidoc.io
err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
if err != nil {
panic(err)
}
}

func main() {
textFieldsDef := []struct {
Label string
Name string
Rect []float64
Tooltip string
}{
{Label: "Full Name", Name: "full_name", Rect: []float64{123.97, 619.02, 343.99, 633.6}, Tooltip: "Enter your full name"},
{Label: "Address 1", Name: "address_line_1", Rect: []float64{123.97, 596.82, 343.99, 611.4}, Tooltip: "Enter your primary address"},
{Label: "Address 2", Name: "address_line_2", Rect: []float64{123.97, 574.28, 343.99, 588.86}, Tooltip: "Enter your secondary address (optional)"},
}

c := creator.New()
page := c.NewPage()
_, pageHeight, err := page.Size()
if err != nil {
log.Fatal(err)
}

// Construct the StructTreeRoot.
str := model.NewStructTreeRoot()

// Construct base K dictionary.
docK := model.NewKDictionary()
docK.S = core.MakeName(string(model.StructureTypeDocument))

str.AddKDict(docK)

form := model.NewPdfAcroForm()
fields := core.MakeArray()

// Create text fields and its label
for idx, fdef := range textFieldsDef {
opt := annotator.TextFieldOptions{}
textf, err := annotator.NewTextField(page, fdef.Name, fdef.Rect, opt)
if err != nil {
log.Fatal(err)
}

textf.DV = core.MakeString("") // Set default value for the field.
textf.V = core.MakeString("") // Set current value for the field.
textf.TU = core.MakeString(fdef.Tooltip) // Set tooltip for the field.

*form.Fields = append(*form.Fields, textf.PdfField)
page.AddAnnotation(textf.Annotations[0].PdfAnnotation)

y := pageHeight - fdef.Rect[1]

p := c.NewStyledParagraph()
p.SetText(fdef.Label)
p.SetPos(fdef.Rect[0]-80, y-10)

// Tag the label paragraph and generate its K dictionary.
// This will be used to associate the label with the form field.
p.SetStructureType(model.StructureTypeForm)

// Set unique ID for each field label.
p.SetMarkedContentID(int64(idx))

k, err := p.GenerateKDict()
if err != nil {
log.Fatalf("Error: %v", err)
}

k.Alt = core.MakeString(fdef.Tooltip) // Set alternative text for the label.

docK.AddKChild(k)

err = c.Draw(p)
if err != nil {
log.Fatal(err)
}

line := c.NewLine(fdef.Rect[0], y, fdef.Rect[2], y)
err = c.Draw(line)
if err != nil {
log.Fatal(err)
}

fields.Append(textf.ToPdfObject())
}

err = addSubmitButton(page, form)
if err != nil {
log.Fatal(err)
}

err = addResetButton(page, form, fields)
if err != nil {
log.Fatal(err)
}

c.SetForms(form)
c.SetStructTreeRoot(str)

err = c.WriteToFile("pdf_tag_form.pdf")
if err != nil {
log.Fatal(err)
}
}

// Add Submit button that will submit all field values.
func addSubmitButton(page *model.PdfPage, form *model.PdfAcroForm) error {
optSubmit := annotator.FormSubmitActionOptions{
Url: "https://unidoc.io",
Rectangle: draw.Rectangle{
X: 400.0,
Y: 400.0,
Width: 50.0,
Height: 20.0,
FillColor: model.NewPdfColorDeviceRGB(0.0, 1.0, 0.0),
},
Label: "Submit",
LabelColor: model.NewPdfColorDeviceRGB(1.0, 0.0, 0.0),
}

btnSubmitField, err := annotator.NewFormSubmitButtonField(page, optSubmit)
if err != nil {
return err
}

*form.Fields = append(*form.Fields, btnSubmitField.PdfField)
page.AddAnnotation(btnSubmitField.Annotations[0].PdfAnnotation)

return nil
}

// Add Reset button that would reset the specified fields to its default value.
func addResetButton(page *model.PdfPage, form *model.PdfAcroForm, fields *core.PdfObjectArray) error {
optReset := annotator.FormResetActionOptions{
Rectangle: draw.Rectangle{
X: 100.0,
Y: 400.0,
Width: 50.0,
Height: 20.0,
FillColor: model.NewPdfColorDeviceGray(0.5),
},
Label: "Reset",
LabelColor: model.NewPdfColorDeviceGray(1.0),
Fields: fields,
}

btnResetField, err := annotator.NewFormResetButtonField(page, optReset)
if err != nil {
return err
}

// Add widget to existing form.
*form.Fields = append(*form.Fields, btnResetField.PdfField)
page.AddAnnotation(btnResetField.Annotations[0].PdfAnnotation)

return nil
}
Loading