Skip to content

Commit fc4920e

Browse files
authored
[US-893] Accessibility code examples (#271)
* add example of adding image with alt text * add readme file * add comment * add example for adding language id * add copy page with a11y * fix typo
1 parent f81a2d3 commit fc4920e

File tree

5 files changed

+419
-0
lines changed

5 files changed

+419
-0
lines changed

accessibility/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## PDF Accessibility Examples.
2+
3+
The examples in this section are showcasing how to work with accessibility features.
4+
5+
## Examples
6+
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.
8+
- [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.

accessibility/logo.png

27.3 KB
Loading
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Add image with alternate text to a PDF file.
3+
*
4+
* This example showcases how to add an image to a PDF file and set alternate text for the image.
5+
* Alternate text is useful for screen readers and other assistive technologies to provide a description of the image.
6+
*
7+
* The example demonstrates how to construct a `StructTreeRoot` object and add alternate text for images.
8+
*
9+
* Usage:
10+
* go run pdf_add_image_alt_text.go
11+
*/
12+
13+
package main
14+
15+
import (
16+
"fmt"
17+
"os"
18+
19+
"github.com/unidoc/unipdf/v3/common/license"
20+
"github.com/unidoc/unipdf/v3/core"
21+
"github.com/unidoc/unipdf/v3/creator"
22+
"github.com/unidoc/unipdf/v3/model"
23+
)
24+
25+
func init() {
26+
// Make sure to load your metered License API key prior to using the library.
27+
// If you need a key, you can sign up and create a free one at https://cloud.unidoc.io
28+
err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
29+
if err != nil {
30+
panic(err)
31+
}
32+
}
33+
34+
func main() {
35+
var (
36+
mcid int64 = 0
37+
structTreeRoot = model.NewStructTreeRoot()
38+
imageFile = "logo.png"
39+
kIds = []string{
40+
"86dfd8f4-b09b-41bc-981a-8b77de9aa251",
41+
"7c3ffc57-120a-4b44-827a-3f515ffa87b7",
42+
"b78b019d-cfb1-465f-b025-7dfda40d0b58",
43+
"06296b7a-818b-451a-9b59-5d4a20f59756",
44+
}
45+
outputPath = "image_alt_text.pdf"
46+
)
47+
48+
c := creator.New()
49+
50+
// Construct base K dictionary.
51+
docK := model.NewKDictionary()
52+
docK.S = core.MakeName(model.StructureTypeDocument)
53+
// Manually set optional ID for the K object
54+
docK.ID = core.MakeString(kIds[0])
55+
// Or generate the ID automatically using the following:
56+
// docK.GenerateRandomID()
57+
58+
// Add K dictionary to the struct tree root.
59+
structTreeRoot.AddKDict(docK)
60+
61+
// Create a child K dictionary.
62+
pageMarkedContentSection := model.NewKDictionary()
63+
64+
// Set the structure type to Section.
65+
pageMarkedContentSection.S = core.MakeName(model.StructureTypeSection)
66+
pageMarkedContentSection.ID = core.MakeString(kIds[1])
67+
68+
// Add as a child
69+
docK.AddKChild(pageMarkedContentSection)
70+
71+
// Add first image
72+
err := addImage(c, imageFile, 0, 10, mcid, "An image alt text", kIds[2], pageMarkedContentSection)
73+
if err != nil {
74+
fmt.Printf("Error: %v\n", err)
75+
}
76+
77+
// Update the MCID to be used for the next image.
78+
mcid++
79+
80+
// Add second image.
81+
err = addImage(c, imageFile, 0, 400, mcid, "A second image alt text", kIds[3], pageMarkedContentSection)
82+
if err != nil {
83+
fmt.Printf("Error: %v\n", err)
84+
}
85+
86+
// Set the struct tree root.
87+
c.SetStructTreeRoot(structTreeRoot)
88+
89+
err = c.WriteToFile(outputPath)
90+
if err != nil {
91+
fmt.Printf("Error: %v\n", err)
92+
}
93+
}
94+
95+
func addImage(c *creator.Creator, imageFile string, x, y float64, mcid int64, altText, kID string, pageMarkedContentSection *model.KDict) error {
96+
img, err := c.NewImageFromFile(imageFile)
97+
if err != nil {
98+
return err
99+
}
100+
101+
img.SetPos(x, y)
102+
err = c.Draw(img)
103+
if err != nil {
104+
return err
105+
}
106+
107+
// Add the image to the marked content section.
108+
altKdictEntry := img.SetMarkedContentID(mcid)
109+
110+
// Set the alternate text.
111+
altKdictEntry.Alt = core.MakeString(altText)
112+
113+
// Set the page number.
114+
altKdictEntry.SetPageNumber(int64(c.Context().Page))
115+
116+
// Set the bounding box.
117+
altKdictEntry.SetBoundingBox(x, y, img.Width(), img.Height())
118+
119+
// Set the ID.
120+
altKdictEntry.ID = core.MakeString(kID)
121+
122+
// Add the K dictionary entry to the marked content section.
123+
pageMarkedContentSection.AddKChild(altKdictEntry)
124+
125+
return nil
126+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/**
2+
* This example demonstrates how to copy pages from an existing PDF file to a new PDF file
3+
* and preserve the structure tree information.
4+
*
5+
* Usage:
6+
* go run pdf_copy_page_with_accessibility.go INPUT_PDF_PATH OUTPUT_PDF_PATH SPACE_SEPARATED_LIST_OF_PAGE_NUMBER_TO_COPY
7+
*/
8+
9+
package main
10+
11+
import (
12+
"fmt"
13+
"os"
14+
"strconv"
15+
16+
"github.com/unidoc/unipdf/v3/common/license"
17+
"github.com/unidoc/unipdf/v3/core"
18+
"github.com/unidoc/unipdf/v3/creator"
19+
"github.com/unidoc/unipdf/v3/model"
20+
)
21+
22+
func init() {
23+
// Make sure to load your metered License API key prior to using the library.
24+
// If you need a key, you can sign up and create a free one at https://cloud.unidoc.io
25+
err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
26+
if err != nil {
27+
panic(err)
28+
}
29+
}
30+
31+
func main() {
32+
args := os.Args
33+
if len(args) < 3 {
34+
fmt.Printf("Usage: %s INPUT_PDF_PATH OUTPUT_PDF_PATH SPACE_SEPARATED_LIST_OF_PAGE_NUMBER_TO_COPY\n", os.Args[0])
35+
return
36+
}
37+
38+
inputPath := args[1]
39+
outputPath := args[2]
40+
pageNumbers := args[3:]
41+
42+
c := creator.New()
43+
c.SetLanguage("en-US")
44+
c.SetPdfWriterAccessFunc(func(w *model.PdfWriter) error {
45+
w.SetCatalogMarkInfo(core.MakeDictMap(map[string]core.PdfObject{
46+
"Marked": core.MakeBool(true),
47+
}))
48+
49+
w.SetVersion(1, 5)
50+
51+
return nil
52+
})
53+
54+
// Set the viewer preferences.
55+
vp := model.NewViewerPreferences()
56+
vp.SetDisplayDocTitle(true)
57+
58+
c.SetViewerPreferences(vp)
59+
60+
// Set PDF title.
61+
model.SetPdfTitle("Output Sample PDF")
62+
63+
// Construct the StructTreeRoot.
64+
newStr := model.NewStructTreeRoot()
65+
66+
// Construct base K dictionary.
67+
docK := model.NewKDictionary()
68+
docK.S = core.MakeName(model.StructureTypeDocument)
69+
70+
newStr.AddKDict(docK)
71+
72+
readerOpt := model.ReaderOpts{
73+
ComplianceMode: true,
74+
}
75+
76+
r, _, err := model.NewPdfReaderFromFile(inputPath, &readerOpt)
77+
if err != nil {
78+
fmt.Printf("Error loading input file: %v\n", err)
79+
return
80+
}
81+
82+
// Get the original `StructTreeRoot` object.
83+
strObj, found := r.GetCatalogStructTreeRoot()
84+
if !found {
85+
fmt.Printf("No StructTreeRoot found in the input PDF\n")
86+
return
87+
}
88+
89+
orgStr, err := model.NewStructTreeRootFromPdfObject(strObj)
90+
if err != nil {
91+
fmt.Printf("Error parsing StructTreeRoot object: %v\n", err)
92+
return
93+
}
94+
95+
orgK := orgStr.K[0].GetChildren()
96+
97+
newPageIdx := 0
98+
99+
for _, i := range pageNumbers {
100+
n, err := strconv.Atoi(i)
101+
if err != nil {
102+
fmt.Printf("Invalid page number: %s\n", i)
103+
continue
104+
}
105+
106+
orgPage, err := r.GetPage(n)
107+
if err != nil {
108+
fmt.Printf("Error getting page %d: %v\n", n, err)
109+
continue
110+
}
111+
112+
dupPage := orgPage.Duplicate()
113+
dupPage.SetStructParentsKey(newPageIdx)
114+
c.AddPage(dupPage)
115+
116+
// Add section K object to store all original K objects from template page.
117+
sectK := model.NewKDictionary()
118+
sectK.S = core.MakeName(model.StructureTypeSection)
119+
sectK.T = core.MakeString(fmt.Sprintf("Page %d", n))
120+
sectK.GenerateRandomID()
121+
122+
sectKv := model.KValue{}
123+
sectKv.SetKDict(sectK)
124+
125+
// Copy all K objects from the original page to the new page.
126+
for _, k := range orgK {
127+
copyK := deepCopyKObject(k)
128+
129+
if copyK != nil {
130+
sectK.AddChild(copyK)
131+
}
132+
}
133+
134+
setKPageNumber(&sectKv, int64(n))
135+
136+
docK.AddChild(&sectKv)
137+
138+
newPageIdx++
139+
}
140+
141+
c.SetStructTreeRoot(newStr)
142+
143+
err = c.WriteToFile(outputPath)
144+
if err != nil {
145+
fmt.Printf("Error writing output file: %v\n", err)
146+
}
147+
}
148+
149+
func setKPageNumber(kv *model.KValue, page int64) {
150+
if k := kv.GetKDict(); k != nil {
151+
k.SetPageNumber(page)
152+
153+
for _, child := range k.GetChildren() {
154+
setKPageNumber(child, page)
155+
}
156+
}
157+
}
158+
159+
func deepCopyKObject(origin *model.KValue) *model.KValue {
160+
copyKVal := model.NewKValue()
161+
162+
if oKDict := origin.GetKDict(); oKDict != nil {
163+
copy := model.NewKDictionary()
164+
copy.S = oKDict.S
165+
copy.ID = oKDict.ID
166+
copy.Lang = oKDict.Lang
167+
copy.Alt = oKDict.Alt
168+
copy.T = oKDict.T
169+
copy.Pg = oKDict.Pg
170+
copy.C = oKDict.C
171+
172+
for _, child := range oKDict.GetChildren() {
173+
copy.AddChild(deepCopyKObject(child))
174+
}
175+
176+
copyKVal.SetKDict(copy)
177+
178+
return copyKVal
179+
} else if refObj := origin.GetRefObject(); refObj != nil {
180+
copyKVal.SetRefObject(refObj)
181+
182+
return copyKVal
183+
} else if mcid := origin.GetMCID(); mcid != nil {
184+
copyKVal.SetMCID(*mcid)
185+
186+
return copyKVal
187+
}
188+
189+
return nil
190+
}

0 commit comments

Comments
 (0)