Skip to content

Commit 6aa396d

Browse files
authored
Merge pull request #292 from 3ace/us-1113-doc-tag
[US-1113] Add document tagging code examples
2 parents c415379 + 6d3b0d5 commit 6aa396d

File tree

7 files changed

+795
-2
lines changed

7 files changed

+795
-2
lines changed

accessibility/README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ The examples in this section are showcasing how to work with accessibility featu
44

55
## Examples
66

7-
- [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.
7+
- [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.
8+
- [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.
89
- [pdf_set_language_identifier.go](pdf_set_language_identifier.go) demonstrates how to set the language identifier for the document and its content.
9-
- [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.
10+
- [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.
11+
- [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.
12+
- [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.
13+
- [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.
14+
- [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.
15+
- [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.

accessibility/pdf_tag_annots.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// This example demonstrates how to create a PDF with text annotations
2+
// using the UniPDF library. The PDF will contain multiple text annotations
3+
// with different properties, and the annotations will be properly tagged
4+
// in the document structure tree for accessibility compliance.
5+
//
6+
7+
package main
8+
9+
import (
10+
"fmt"
11+
"os"
12+
13+
"github.com/unidoc/unipdf/v4/common/license"
14+
"github.com/unidoc/unipdf/v4/core"
15+
"github.com/unidoc/unipdf/v4/creator"
16+
"github.com/unidoc/unipdf/v4/model"
17+
)
18+
19+
func init() {
20+
// Make sure to load your metered License API key prior to using the library.
21+
// If you need a key, you can sign up and create a free one at https://cloud.unidoc.io
22+
err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
23+
if err != nil {
24+
panic(err)
25+
}
26+
}
27+
28+
func main() {
29+
outputPath := "pdf_tag_annots.pdf"
30+
31+
err := createPdfWithTextAnnotations(outputPath)
32+
if err != nil {
33+
fmt.Printf("Error: %v\n", err)
34+
os.Exit(1)
35+
}
36+
37+
fmt.Printf("Complete, see output file: %s\n", outputPath)
38+
}
39+
40+
// Create a new PDF with text annotations.
41+
func createPdfWithTextAnnotations(outputPath string) error {
42+
// Create a new Creator.
43+
c := creator.New()
44+
45+
// Construct the StructTreeRoot.
46+
str := model.NewStructTreeRoot()
47+
48+
// Construct base K dictionary.
49+
docK := model.NewKDictionary()
50+
docK.S = core.MakeName(string(model.StructureTypeDocument))
51+
52+
str.AddKDict(docK)
53+
54+
// Create a new page with standard letter size.
55+
page := model.NewPdfPage()
56+
mediaBox := core.MakeArrayFromFloats([]float64{0, 0, 612, 792})
57+
mediaRect, err := model.NewPdfRectangle(*mediaBox)
58+
if err != nil {
59+
return fmt.Errorf("failed to create MediaBox rectangle: %w", err)
60+
}
61+
page.MediaBox = mediaRect
62+
63+
// Add first text annotation.
64+
textAnnotation1 := model.NewPdfAnnotationText()
65+
textAnnotation1.Contents = core.MakeString("This is a sample text annotation!")
66+
textAnnotation1.Rect = core.MakeArray(
67+
core.MakeInteger(100), // x1
68+
core.MakeInteger(600), // y1 (from bottom of page)
69+
core.MakeInteger(150), // x2 (x1 + width)
70+
core.MakeInteger(650), // y2 (y1 + height)
71+
)
72+
// Set annotation properties.
73+
textAnnotation1.Open = core.MakeBool(false) // Closed by default
74+
textAnnotation1.Name = core.MakeName("Comment")
75+
76+
docK.AddKChild(textAnnotation1.GenerateKDict())
77+
78+
// Add second text annotation.
79+
textAnnotation2 := model.NewPdfAnnotationText()
80+
textAnnotation2.Contents = core.MakeString("Another annotation with more detailed information about this section.")
81+
textAnnotation2.Rect = core.MakeArray(
82+
core.MakeInteger(300), // x1
83+
core.MakeInteger(550), // y1
84+
core.MakeInteger(350), // x2
85+
core.MakeInteger(600), // y2
86+
)
87+
textAnnotation2.Open = core.MakeBool(true) // Open by default
88+
textAnnotation2.Name = core.MakeName("Note")
89+
90+
docK.AddKChild(textAnnotation2.GenerateKDict())
91+
92+
// Add third text annotation.
93+
textAnnotation3 := model.NewPdfAnnotationText()
94+
textAnnotation3.Contents = core.MakeString("You can click on these yellow icons to view the annotation content.")
95+
textAnnotation3.Rect = core.MakeArray(
96+
core.MakeInteger(450), // x1
97+
core.MakeInteger(400), // y1
98+
core.MakeInteger(500), // x2
99+
core.MakeInteger(450), // y2
100+
)
101+
textAnnotation3.Open = core.MakeBool(false)
102+
textAnnotation3.Name = core.MakeName("Help")
103+
104+
docK.AddKChild(textAnnotation3.GenerateKDict())
105+
106+
// Add annotations to the page.
107+
page.AddAnnotation(textAnnotation1.PdfAnnotation)
108+
page.AddAnnotation(textAnnotation2.PdfAnnotation)
109+
page.AddAnnotation(textAnnotation3.PdfAnnotation)
110+
111+
// Add StructTreeRoot to the page.
112+
c.SetStructTreeRoot(str)
113+
114+
// Add the page to the writer.
115+
err = c.AddPage(page)
116+
if err != nil {
117+
return err
118+
}
119+
120+
// Write the PDF to file.
121+
err = c.WriteToFile(outputPath)
122+
if err != nil {
123+
return err
124+
}
125+
126+
return nil
127+
}

accessibility/pdf_tag_form.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// This example demonstrates how to create a PDF with form fields
2+
// using the UniPDF library. The PDF will contain multiple text fields
3+
// along with submit and reset buttons, and the form fields will be
4+
// properly tagged in the document structure tree for accessibility compliance.
5+
//
6+
// The example covers best practices for PDF/UA compliance:
7+
// 1. Each form field has an associated label with a tooltip.
8+
// 2. The document structure tree is constructed with K dictionaries
9+
// to represent the hierarchical structure of the form elements.
10+
// 3. Each label is associated with its corresponding form field using
11+
// marked content IDs (MCID).
12+
// 4. The submit button is configured to submit the form data to a specified URL.
13+
// 5. The reset button is configured to reset the specified fields to their default values.
14+
15+
package main
16+
17+
import (
18+
"log"
19+
"os"
20+
21+
"github.com/unidoc/unipdf/v4/annotator"
22+
"github.com/unidoc/unipdf/v4/common/license"
23+
"github.com/unidoc/unipdf/v4/contentstream/draw"
24+
"github.com/unidoc/unipdf/v4/core"
25+
"github.com/unidoc/unipdf/v4/creator"
26+
"github.com/unidoc/unipdf/v4/model"
27+
)
28+
29+
func init() {
30+
// Make sure to load your metered License API key prior to using the library.
31+
// If you need a key, you can sign up and create a free one at https://cloud.unidoc.io
32+
err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
33+
if err != nil {
34+
panic(err)
35+
}
36+
}
37+
38+
func main() {
39+
textFieldsDef := []struct {
40+
Label string
41+
Name string
42+
Rect []float64
43+
Tooltip string
44+
}{
45+
{Label: "Full Name", Name: "full_name", Rect: []float64{123.97, 619.02, 343.99, 633.6}, Tooltip: "Enter your full name"},
46+
{Label: "Address 1", Name: "address_line_1", Rect: []float64{123.97, 596.82, 343.99, 611.4}, Tooltip: "Enter your primary address"},
47+
{Label: "Address 2", Name: "address_line_2", Rect: []float64{123.97, 574.28, 343.99, 588.86}, Tooltip: "Enter your secondary address (optional)"},
48+
}
49+
50+
c := creator.New()
51+
page := c.NewPage()
52+
_, pageHeight, err := page.Size()
53+
if err != nil {
54+
log.Fatal(err)
55+
}
56+
57+
// Construct the StructTreeRoot.
58+
str := model.NewStructTreeRoot()
59+
60+
// Construct base K dictionary.
61+
docK := model.NewKDictionary()
62+
docK.S = core.MakeName(string(model.StructureTypeDocument))
63+
64+
str.AddKDict(docK)
65+
66+
form := model.NewPdfAcroForm()
67+
fields := core.MakeArray()
68+
69+
// Create text fields and its label
70+
for idx, fdef := range textFieldsDef {
71+
opt := annotator.TextFieldOptions{}
72+
textf, err := annotator.NewTextField(page, fdef.Name, fdef.Rect, opt)
73+
if err != nil {
74+
log.Fatal(err)
75+
}
76+
77+
textf.DV = core.MakeString("") // Set default value for the field.
78+
textf.V = core.MakeString("") // Set current value for the field.
79+
textf.TU = core.MakeString(fdef.Tooltip) // Set tooltip for the field.
80+
81+
*form.Fields = append(*form.Fields, textf.PdfField)
82+
page.AddAnnotation(textf.Annotations[0].PdfAnnotation)
83+
84+
y := pageHeight - fdef.Rect[1]
85+
86+
p := c.NewStyledParagraph()
87+
p.SetText(fdef.Label)
88+
p.SetPos(fdef.Rect[0]-80, y-10)
89+
90+
// Tag the label paragraph and generate its K dictionary.
91+
// This will be used to associate the label with the form field.
92+
p.SetStructureType(model.StructureTypeForm)
93+
94+
// Set unique ID for each field label.
95+
p.SetMarkedContentID(int64(idx))
96+
97+
k, err := p.GenerateKDict()
98+
if err != nil {
99+
log.Fatalf("Error: %v", err)
100+
}
101+
102+
k.Alt = core.MakeString(fdef.Tooltip) // Set alternative text for the label.
103+
104+
docK.AddKChild(k)
105+
106+
err = c.Draw(p)
107+
if err != nil {
108+
log.Fatal(err)
109+
}
110+
111+
line := c.NewLine(fdef.Rect[0], y, fdef.Rect[2], y)
112+
err = c.Draw(line)
113+
if err != nil {
114+
log.Fatal(err)
115+
}
116+
117+
fields.Append(textf.ToPdfObject())
118+
}
119+
120+
err = addSubmitButton(page, form)
121+
if err != nil {
122+
log.Fatal(err)
123+
}
124+
125+
err = addResetButton(page, form, fields)
126+
if err != nil {
127+
log.Fatal(err)
128+
}
129+
130+
c.SetForms(form)
131+
c.SetStructTreeRoot(str)
132+
133+
err = c.WriteToFile("pdf_tag_form.pdf")
134+
if err != nil {
135+
log.Fatal(err)
136+
}
137+
}
138+
139+
// Add Submit button that will submit all field values.
140+
func addSubmitButton(page *model.PdfPage, form *model.PdfAcroForm) error {
141+
optSubmit := annotator.FormSubmitActionOptions{
142+
Url: "https://unidoc.io",
143+
Rectangle: draw.Rectangle{
144+
X: 400.0,
145+
Y: 400.0,
146+
Width: 50.0,
147+
Height: 20.0,
148+
FillColor: model.NewPdfColorDeviceRGB(0.0, 1.0, 0.0),
149+
},
150+
Label: "Submit",
151+
LabelColor: model.NewPdfColorDeviceRGB(1.0, 0.0, 0.0),
152+
}
153+
154+
btnSubmitField, err := annotator.NewFormSubmitButtonField(page, optSubmit)
155+
if err != nil {
156+
return err
157+
}
158+
159+
*form.Fields = append(*form.Fields, btnSubmitField.PdfField)
160+
page.AddAnnotation(btnSubmitField.Annotations[0].PdfAnnotation)
161+
162+
return nil
163+
}
164+
165+
// Add Reset button that would reset the specified fields to its default value.
166+
func addResetButton(page *model.PdfPage, form *model.PdfAcroForm, fields *core.PdfObjectArray) error {
167+
optReset := annotator.FormResetActionOptions{
168+
Rectangle: draw.Rectangle{
169+
X: 100.0,
170+
Y: 400.0,
171+
Width: 50.0,
172+
Height: 20.0,
173+
FillColor: model.NewPdfColorDeviceGray(0.5),
174+
},
175+
Label: "Reset",
176+
LabelColor: model.NewPdfColorDeviceGray(1.0),
177+
Fields: fields,
178+
}
179+
180+
btnResetField, err := annotator.NewFormResetButtonField(page, optReset)
181+
if err != nil {
182+
return err
183+
}
184+
185+
// Add widget to existing form.
186+
*form.Fields = append(*form.Fields, btnResetField.PdfField)
187+
page.AddAnnotation(btnResetField.Annotations[0].PdfAnnotation)
188+
189+
return nil
190+
}

0 commit comments

Comments
 (0)