diff --git a/.github/workflows/push-actions.yml b/.github/workflows/push-actions.yml index 32f094ad..6ab5d60d 100644 --- a/.github/workflows/push-actions.yml +++ b/.github/workflows/push-actions.yml @@ -45,14 +45,14 @@ jobs: architecture: x64 - name: Install Lynx dependency run: sudo apt install lynx - - name: Install PHP8.1 - run: sudo apt install php8.1 + - name: Install PHP + run: sudo apt install php - name: Install latest Chrome to match chromedriver package version # Note: you should see if the version of Chrome matches the latest listed on # https://mirror.cs.uchicago.edu/google-chrome/pool/main/g/google-chrome-stable/ run: | apt search '^google-chrome.*' \ - && wget -q -O /tmp/chrome.deb http://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_130.0.6723.58-1_amd64.deb \ + && wget -q -O /tmp/chrome.deb https://mirror.cs.uchicago.edu/google-chrome/pool/main/g/google-chrome-stable/google-chrome-stable_132.0.6834.110-1_amd64.deb \ && sudo apt install -y /tmp/chrome.deb --allow-downgrades \ && rm /tmp/chrome.deb - name: Log system details diff --git a/.stylelintrc.js b/.stylelintrc.js new file mode 100644 index 00000000..d5c40744 --- /dev/null +++ b/.stylelintrc.js @@ -0,0 +1,55 @@ +module.exports = { + "customSyntax": "postcss-less", + "extends": [ "stylelint-config-standard"], + "ignoreFiles": [ + "**/*.js", + "enable-node-libs/**/*.css", + "js/out/**/*.css", + "js/enable-libs/**/*.css" + ], + "plugins": ["@double-great/stylelint-a11y"], + "rules": { + "a11y/no-text-align-justify": true, + "alpha-value-notation": null, + "at-rule-empty-line-before": null, + "at-rule-no-unknown": null, + "color-function-notation": null, + "color-hex-length": null, + "comment-empty-line-before": null, + "comment-whitespace-inside": null, + "custom-property-empty-line-before": null, + "custom-property-pattern": null, + "declaration-empty-line-before": null, + "declaration-block-no-duplicate-properties": null, + "declaration-block-no-redundant-longhand-properties": null, + "declaration-block-no-shorthand-property-overrides": null, + "declaration-block-single-line-max-declarations": null, + "font-family-name-quotes": null, + "font-family-no-missing-generic-family-keyword": null, + "function-calc-no-unspaced-operator": null, + "function-name-case": null, + "function-no-unknown": null, + "function-url-quotes": null, + "hue-degree-notation": null, + "import-notation": null, + "keyframes-name-pattern": null, + "length-zero-no-unit": null, + "media-feature-range-notation": null, + "media-query-no-invalid": null, + "no-empty-source": null, + "no-descending-specificity": null, + "no-duplicate-selectors": null, + "number-max-precision": null, + "property-no-unknown": null, + "property-no-vendor-prefix": null, + "rule-empty-line-before": null, + "selector-class-pattern": null, + "selector-id-pattern": null, + "selector-no-vendor-prefix": null, + "selector-pseudo-element-colon-notation": null, + "shorthand-property-no-redundant-values": null, + "string-no-newline": null, + "value-keyword-case": null, + "value-no-vendor-prefix": null + } +} diff --git a/.stylelintrc.json b/.stylelintrc.json index 315b554b..c017f0c6 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -7,7 +7,9 @@ "js/out/**/*.css", "js/enable-libs/**/*.css" ], + "plugins": ["@double-great/stylelint-a11y"], "rules": { + "a11y/no-text-align-justify": true, "alpha-value-notation": null, "at-rule-empty-line-before": null, "at-rule-no-unknown": null, diff --git a/bin/checkHTML.sh b/bin/checkHTML.sh index 90f85252..7216100d 100755 --- a/bin/checkHTML.sh +++ b/bin/checkHTML.sh @@ -86,6 +86,12 @@ If that does not work, you may need to do a global update of @axe-core/cli: sudo npm update -g @axe-core/cli +******************************************************************************** +* NOTE: If you are seeing this message in GitHub Automated Tests, you need to +* update push-actions.yml to ensure the version of Chrome being downloaded there +* matches the one in your package.json +******************************************************************************** + ' 1>&2 exit 1; diff --git a/content/body/accessible-pdf-generation.php b/content/body/accessible-pdf-generation.php new file mode 100644 index 00000000..c44c2c9c --- /dev/null +++ b/content/body/accessible-pdf-generation.php @@ -0,0 +1,361 @@ +

+ There are many times where a website may want to generate on-the-fly PDF documents. A few examples are: +

+ + + +

+ Unfortunately, the average person tasked to generate these reports may not realize that screen readers have problems parsing the vast majority of PDFs, due to them not being tagged. Tagging a PDF is similar to marking up an HTML document with semantic HTML tags — it allows screen readers to travese the document landmarks, navigate through interactive elements with a keyboard, and so forth. In order to be considered accessible, Tagged PDFs must conform to the PDF/UA standard. +

+ +

+ We have been searching a long time for a solution for creating PDF/UA compliant PDFs that was both inexpensive and open source. In 2023, we stumbled upon Open HTML to PDF, which claimed it could create PDF/UA compliant accessible PDFs. After testing it out ourselves and confirming it can work, we wanted to share how we implemented this ourselves for those of you who want to as well. This guide will demonstrate creating a SpringBoot app with Open HTML to PDF, and will also cover how an HTML author can troubleshoot common problems using the tool. +

+ + +

Background

+

+ The application that we wanted to integrate the PDF generator into was written in node. Since Open HTML to PDF is a Java library, we decided to build a separate SpringBoot app that our node application could connect to via HTTP to create these accessible PDFs. This article will walk you through how you too can create a Java SpringBoot app that integrates the OpenHTMLToPDF library.

+ + +

Pre-Requisites

+

You will need the following installed on your machine:

+ +

We will be using SpringBoot, a Java Framework, to develop this app. If you are unfamiliar with this technology, +please see here for more information about SpringBoot. +We will be using the Open HTML to PDF library to create an accessible PDF. +The repository for Open HTML to PDF for can be found here. +We will provide references to specific pages that are of interest in the tutorial as applicable.

+ + +

Step 1: Initialize Spring Project

+

We will use the Spring initializer to create a new SpringBoot project. +The Spring initializer tool can be found here. +This will generate a new SpringBoot project that we will use as the basis for our accessible PDF app. Open HTML to PDF is injected as a maven dependency, so select “Maven” under project. +We will be using Java as the language. Select the latest version of SpringBoot. In the metadata, add a name, artifact, name, and description relevant to your project (package name is automatically generated, but you can change this to suit your needs). +Under packaging, we will be selecting Jar, as it works best for our deployment configuration, but please select the packaging that is relevant to your project. Open HTML to PDF is compatible with, at latest, Java 17, so select Java 17. The screenshot below describes the configuration that we used. +Once you have selected your desired configuration, click on generate, and uncompress the generated zip file. Open this folder in an IDE.

+A screenshot of the Spring Initializer tool.  The settings are set to those described in the paragraph about Step 1. + + +

Step 2: Add Dependencies to pom.xml

+

Navigate to the pom.xml file. This file controls the dependencies in the code. This is where we will be adding in the dependency for the Open HTML to PDF. Following the +implementation guide in the Open HTML to PDF repository, +we will add the necessary dependencies to the pom.xml file. If your PDF does not contain images, right to left test, SVGs, or MathML, you will only need to add the first 2 dependencies found under the “MAVEN ARTIFACTS” section of the article. We will also need to add the Open HTML to PDF version under the properties section. +After adding the necessary dependencies and properties for our project, the properties and dependencies tags will similar to below.

+ + + + + +

Step 3: Create Controller File

+

We are going to begin to build out a Java file to act as our endpoint to generate a PDF. To do this, first create a new file at the same level as the automatically generated java file under src/main/java/[your package name]. In our case, it is at the same level as “AccessiblePDFSpringApplication.java”. +We will call our new file AccessiblePDFController.java. We will be creating a Controller file, would marks a class as a web request handler.

+A screenshot of the file structure of the project, showing a new file created in the java/com/publicissapient/aid/accessiblepdfspring called AccessiblePDFController.java. + + +

Step 4: Create Controller Class

+

Then, we will start to add to this file to build out our endpoint. First, we will mark the class as a REST controller by adding @RestController above the class declaration.

+ + + + +

Step 5: Add Method to Controller

+

Then, we will add a method and have it return a ResponseEntity<byte[]>, since we will be sending the response body as byte array. This method will need to throw an exception. You will see an error as we do not have a ResponseEntity as a return value yet, but we will add this in a later step.

+ + + + +

Step 6: Create New File for Request Body

+

We will now create a new file so that we can properly parse the body that is sent to the endpoint in the request. This will also be at the same level as the Controller file. We have named ours “HTMLBody.java”. See below for the structure.

+A screenshot of the file structure of the project, showing a new file created in the java/com/publicissapient/aid/accessiblepdfspring called HTMLBody.java. +

For our purposes, we will send the HTML to the SpringBoot app as a string, and we will also send the filename, which will be the name of the file that we want to generate. To consume this information properly, we will create a class that has 2 properties, a String html and a String filename. The code will look like this:

+ + + + +

Step 7: Create Request Body

+

We will now add the request body as an argument to our method. This will use the class that we created in the previous step. In addition to this, we will add the @CrossOrigin annotation, which enables cross-origin resource sharing only for this specific method. We will also be adding the @PostMapping annotation, which binds the method to an endpoint. +In the @PostMapping annotation, we will be adding the destination that we want the endpoint at, as well as the format of the body of the request, which in our case is JSON.

+ + + + +

Step 8: Add Fonts

+

Now, we will add fonts to our project. Note that this is an extremely important step as the PDF will not generate if you do not provide fonts. First, download the fonts that you would like. We found ours on +Google Fonts. +Be sure that the fonts that you select are free for commercial use. We downloaded 2 fonts, one for normal text and another for code snippets. These files should be in .ttf format. After downloading these fonts, we will add them to the resources folder.

+A screenshot of the file structure of the project, showing 2 .ttf files added to the src/main/resources folder. +

We will add these fonts as resources in our pom.xml file so that we can access them in our Java code. In your pom.xml add the fonts under <build>, then <plugins>. You will add a <resources> tag, with a <resource> tag, then a <directory> tag, with the <includes> and <include> tag that specifies the name of the files that you would like to access in your Java code.

+ + + + +

Step 9: Handle Font Files in Code

+

We will now add code to deal with the font files. The Open HTML to PDF library needs the files to be in Java File format, so we will add code to do this. Since the files are under the resource folder, they will be loaded as resources rather than files. We will convert the resource into a steam and then write that to a file that is accessible from our controller class. +We will delete the temporary font files that we’ve generated from the resource stream after we’re done converting the PDF, so we will add code to handle this as well. The code below is added to our controller file to handle the fonts.

+ + +

With the copyInputStreamToFile helper class:

+ + + + +

Step 10: Implement Open HTML to PDF Library

+

We will now implement the Open HTML to PDF library in our controller file to convert the HTML we have sent as a string in the request body to PDF format. We will be following the article in the repository called +PDF Accessibility (PDF UA, WCAG, Section 508 Support. +In the section “Builder Example”, you will see the code that we will need to add to our project. Adapting this to the steps before, we will add the following to our code:

+ + +

IMPORTANT: please see the lines with builder.useFont(…). The second argument in this method MUST match the font family that you have in the CSS that you send in the HTML string, and this MUST NOT match any of the font families that you would typically expect to see (such as serif, sans-serif, or monospace), but rather must be unique. +If this font convention is not followed, your PDF will not generate properly, and you will likely get an extremely vague error message, usually from the Open HTML to PDF library saying that you did not provide fonts. If you get that error message, come back to this step and ensure that you have added the fonts properly and that the font families specified in your CSS match those that you have specified in your Java code.

+ + +

Step 11: Convert PDF to Byte Array

+

We will now add the code to convert the PDF to a byte array, as this is the format that we will be sending back in the response.

+ + + + +

Step 12: Create Response

+

We will now add the code needed to send a response. This will involve creating the headers needed and then setting the ResponseEntity. We will add this for both the case where we are successful in generating a PDF, sending an OK status, and in the catch block that we will be in if we are not successful in generating a PDF, in which case we will send a 400 error. We will also send that the content form of the response is a PDF. See how we implemented this in the code below.

+ + + + +

Step 13: Run App Locally

+

We are now able to run this app locally. Use the command mvn spring-boot:run to run the program. The endpoint will usually be at http://localhost/yourEndpointName.

+ + +

Step 14: Integrate Endpoint

+

Integrate the endpoint into your existing code. We have included a JavaScript example to convert a byte array into a PDF and to automatically save the resulting PDF.

+ + + + +

Troublshooting

+

You may run into some issues once you have integrated the new endpoint in with your code. This is most likely because you have elements in your HTML that the Open HTML to PDF library is not able to parse. Though you may get an error indicating that you did not provide a font or that there is an element that the library does not recognize or is malformed, we found that most of these errors were caused by having elements in our HTML that the library could not parse properly. +To fix this, we removed all buttons, all images (there is a way to keep images, the +documentation for SVG images can be found here +and +more documentation for Java 2D images here +, but the only images in our PDF were indicators that a link would open in a new tab, so we elected to remove them), all lists (both ordered and unordered), which were replaced with paragraph tags in the order that we wanted the information displayed, and all code tags which we replaced with <p> tags with corresponding CSS that had the font family set to the code font that we provided the Spring app with. +There may be other tags that are not recognized that we have not yet come across.

+

We also had to replace any tags that we wanted to display as text with "<" and ">", and any spaces within link tags (<a>) with " ".

+

Most errors from the library are unfortunately extremely vague. We have found that all the ones that we have found are fixed by following the advice above, as well as double checking the work that you did in Step 10 in adding the fonts. To troubleshoot further, we have found that, though slow, the best method to find what HTML is causing the error is to start with small amounts of HTML and then slowly add more in until you see where the problem is. For example, we would start with the following HTML string:

+ + +

And then slowly add body elements until we found one that caused the bug. So, for the next test we would add in the header to our report, then the table, then the column headers, then a single row, etc., until we isolated where the error was coming from. For us, this was usually a tag that was not able to be parsed by the library, so we would either remove this tag if it was not necessary or find a way to express the information using other tags if it needed to be included.

+ + +

Feedback from a Screen Reader User

+

Vishnu Ramchandani, an experienced screen reader user, was kind enough to help us test the PDFs that we generated with Open HTML to PDF. Vishnu confirmed that the generated PDFs were accessible for screen reader users but provided valuable UX feedback to improve the user experience. Below is his feedback and the actions we took to address the issues:

+ +
    +
  1. Page Title Consistency +
    +
    Feedback:
    +
    The document title and filename should be consistent and meaningful to ensure clarity for users.
    +
    Action Taken:
    +
    We updated the title of the PDF to match the filename. This adjustment improves consistency and makes the document more user-friendly. When implementing your own solution, remember that this is not a default behavior in the Open HTML to PDF library and should be explicitly configured.
    +
    +
  2. +
  3. Reading Behavior of Lengthy Paragraphs in Tables + +
    +
    Feedback:
    +
    Screen readers encounter issues when lengthy paragraphs within table columns are broken into lines. This disrupts the reading flow, making it difficult for users to follow the content. To address this, lengthy paragraphs should ideally be displayed in a linear, single-column format.
    +
    Action Taken:
    +
    Unfortunately, this behavior seems to be a limitation of PDF technology. While we cannot fully resolve this issue, we recommend keeping table row text concise and avoiding lengthy paragraphs. If you are aware of any workaround to mitigate this issue, please share your insights with us, and we will update our recommendations accordingly.
    +
    +
  4. +
  5. Issues with Splitting URLs and Links + +
    +
    Feedback:
    +
    When URLs or links break across multiple lines in the PDF, screen readers interpret them as multiple links, leading to confusion. To prevent this: +
      +
    1. Use non-breaking spaces ( ) in link text to avoid line breaks.
    2. +
    3. Apply CSS styles such as white-space: nowrap; to keep links on a single line.
    4. +
    +
    +
    Action Taken:
    +
    This issue also appears to be a limitation inherent to PDF technology, as even advanced tools like Adobe InDesign exhibit this behavior. As a best practice, we recommend keeping link labels concise but descriptive to minimize splitting. If you have additional solutions or tools to address this, please reach out to us — we are eager to incorporate any new strategies.
    +
    +
  6. +
diff --git a/content/body/acknowledgements.php b/content/body/acknowledgements.php index 698c6f3b..6cb4a750 100644 --- a/content/body/acknowledgements.php +++ b/content/body/acknowledgements.php @@ -1,29 +1,43 @@

- Originally, Enable started out as a small personal website to help me show other developers how accessible code is - structured. - Some of the solutions are my own, and some I have borrowed from others (because why reinvent the wheel, especially when - you have already learned from the best?) + Originally, Enable started out as a small personal website made by Zoltan Hawryluk to show other developers how to make web-related code accessible to people with disabalities. Some of the solutions were his own, and some he have borrowed, with citations, from others. Zoltan is still the lead developer of this project and often contributes his own code to it, as well as reviewing all contributions from the community.

+

Today, Enable is now sponsored by The Publicis Sapient Accessibility Center of Excellence, to help not only its developers make accessible things, but also to give back to the accessibility and front-end web development communities. It contains contributions from developers within Publicis Sapient as well as others, and we hope to continue to grow this ongoing collaboration.

+

- What follows are not just acknowledgements to existing accessible code examples used in Enable, but also to other code I have + What follows are not just acknowledgements to existing accessible code examples used in Enable, but also to other code we have built on that I have accessibility features to.

Direct Contributers

+

- The following people have contributed directly to the Enable project by adding code/content via pull requests. + The following people have contributed directly to the Enable project by adding code/content via pull requests or by direct collaboration.

Code Used By Enable

@@ -33,15 +47,7 @@

Icons

diff --git a/content/body/combobox.php b/content/body/combobox.php index d8e7887a..823172dd 100644 --- a/content/body/combobox.php +++ b/content/body/combobox.php @@ -471,22 +471,7 @@ + +

Solution 2: Image Gallery with Caption

+

This is an example of a image gallery where there are captions underneath the image. Note that in some image galleries on the web, captions don't always describe what is inside the image completely and may just have some additional information (a great example of this is Instagram). For this reason, it is usually a great idea for screen readers to announce both the image alt text and the caption together when the user elects to display a new image in the gallery.

+ true]); ?> + false]); ?> + true]); ?> + +
+ +
+ + + + + + diff --git a/content/body/index.php b/content/body/index.php index 4b420abf..11c47ce4 100644 --- a/content/body/index.php +++ b/content/body/index.php @@ -2,7 +2,17 @@

Enable

-

A space for developers to learn and collaborate on making the web accessible.

+

A space for developers to learn and collaborate on making the web accessible.

+ +

+ Proudly sponsored by The +
+ + + + + + Accessibility Centre of Excellence.

@@ -60,7 +70,7 @@
  • Not only do we want to help developers, but we want the help from developers to contribute their accessible code. - If you have a component that you'd like to submit - please put in a PR. + If you have a component that you'd like to submit, please feel free to create a PR.
  • diff --git a/content/body/tooltip.php b/content/body/tooltip.php index 9a9921c6..245d8bb9 100644 --- a/content/body/tooltip.php +++ b/content/body/tooltip.php @@ -22,8 +22,6 @@ This solution can be styled exactly as wanted and uses the maximum value of a z-index in the document. We show different types of tooltips below, based on how they are triggered. It will disappear when keyboard users press the Escape key. - It doesn't work in mobile, which while consistent with other tooltip solutions, - is something that we are still looking to fix. If anyone has any ideas, please feel free to reach out to me on Twitter.

    Clickable tooltip

    @@ -39,21 +37,21 @@ Vehicle Inspection Form
    -
    -