diff --git a/.gitattributes b/.gitattributes
index ce92280..34c4409 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -157,3 +157,10 @@ docs/assets/accessible-showcase-alpha6/fonts/*.ttf binary
examples/component-notebook-alpha6-r1/** text eol=lf
docs/assets/component-notebook-alpha6-r1/** -text
docs/assets/component-notebook-alpha6-r1/*.pdf binary
+
+# Alpha 7 source kits and both PDF/UA receipts retain identical bytes on every OS.
+examples/accessible-showcase-alpha7/** -text
+examples/accessible-showcase-alpha7/fonts/*.ttf binary
+docs/assets/accessible-showcase-alpha7/** -text whitespace=cr-at-eol
+docs/assets/accessible-showcase-alpha7/**/*.pdf binary
+docs/assets/accessible-showcase-alpha7/fonts/*.ttf binary
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 6747bf2..132c6dc 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -21,7 +21,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
- channel: [alpha4, alpha5, alpha6]
+ channel: [alpha4, alpha5, alpha6, alpha7]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
@@ -34,12 +34,24 @@ jobs:
python tools/build_accessible_showcase.py --channel ${{ matrix.channel }}
git diff --exit-code -- docs/assets/accessible-showcase${{ matrix.channel != 'alpha4' && format('-{0}', matrix.channel) || '' }}/project.zip docs/assets/accessible-showcase${{ matrix.channel != 'alpha4' && format('-{0}', matrix.channel) || '' }}/source.json
python tools/verify_accessible_showcase.py --channel ${{ matrix.channel }} --out target/accessible-showcase
+
+ - name: Verify the PDF/UA-2 source specimen
+ if: matrix.channel == 'alpha7'
+ run: python tools/verify_accessible_showcase.py --channel alpha7 --pdf-profile pdfua2 --out target/accessible-showcase-pdfua2
- uses: actions/upload-artifact@v4
if: always()
with:
name: accessible-showcase-${{ matrix.channel }}-${{ matrix.os }}
path: target/accessible-showcase/
retention-days: 14
+
+ - name: Retain PDF/UA-2 source verification
+ if: always() && matrix.channel == 'alpha7'
+ uses: actions/upload-artifact@v7
+ with:
+ name: accessible-showcase-alpha7-${{ matrix.os }}-pdfua2
+ path: target/accessible-showcase-pdfua2/
+ retention-days: 14
showcase-authoring:
name: Showcase authoring comparison (${{ matrix.channel }}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
diff --git a/docs/assets/accessible-showcase-alpha7/a11y-source.json b/docs/assets/accessible-showcase-alpha7/a11y-source.json
new file mode 100644
index 0000000..bd4b251
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/a11y-source.json
@@ -0,0 +1,9 @@
+{
+ "diagnostics": [],
+ "error_count": 0,
+ "errors": [],
+ "mode": "raise",
+ "ok": true,
+ "warning_count": 0,
+ "warnings": []
+}
diff --git a/docs/assets/accessible-showcase-alpha7/browser-verification.json b/docs/assets/accessible-showcase-alpha7/browser-verification.json
new file mode 100644
index 0000000..1f28d6d
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/browser-verification.json
@@ -0,0 +1,51 @@
+{
+ "ok": true,
+ "browser": "155.0.8059.40",
+ "cases": [
+ {
+ "width": 1440,
+ "guide": true,
+ "download": true,
+ "pdf_hash": true,
+ "pdf_profiles": [
+ "pdfua1",
+ "pdfua2"
+ ],
+ "responsive_html": true,
+ "unstyled_html": true,
+ "keyboard_link": true
+ },
+ {
+ "width": 390,
+ "guide": true,
+ "download": true,
+ "pdf_hash": true,
+ "pdf_profiles": [
+ "pdfua1",
+ "pdfua2"
+ ],
+ "responsive_html": true,
+ "unstyled_html": true,
+ "keyboard_link": true
+ },
+ {
+ "width": 320,
+ "guide": true,
+ "download": true,
+ "pdf_hash": true,
+ "pdf_profiles": [
+ "pdfua1",
+ "pdfua2"
+ ],
+ "responsive_html": true,
+ "unstyled_html": true,
+ "keyboard_link": true
+ }
+ ],
+ "pdf_sha256": "faaf5e56e4bc962f4096d6a77deaad176266089548984eda7f45091339ba73d4",
+ "zip_sha256": "34b115996ede1274895cd0d81df39f37c65f5851ffbcb28dc09e52d9dd4087ca",
+ "pdfs": {
+ "pdfua1": "faaf5e56e4bc962f4096d6a77deaad176266089548984eda7f45091339ba73d4",
+ "pdfua2": "b38e8d6008c5474ba8b37ce952a9e65603c03d722390af3273ff048a59af0779"
+ }
+}
diff --git a/docs/assets/accessible-showcase-alpha7/common-ground-guide.pdf b/docs/assets/accessible-showcase-alpha7/common-ground-guide.pdf
new file mode 100644
index 0000000..8aae502
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/common-ground-guide.pdf differ
diff --git a/docs/assets/accessible-showcase-alpha7/fonts/DMSerifDisplay-OFL.txt b/docs/assets/accessible-showcase-alpha7/fonts/DMSerifDisplay-OFL.txt
new file mode 100644
index 0000000..aea163a
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/fonts/DMSerifDisplay-OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2014-2018 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. Copyright 2019 Google LLC.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+
+This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/docs/assets/accessible-showcase-alpha7/fonts/DMSerifDisplay-Regular.ttf b/docs/assets/accessible-showcase-alpha7/fonts/DMSerifDisplay-Regular.ttf
new file mode 100644
index 0000000..bc5ed0c
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/fonts/DMSerifDisplay-Regular.ttf differ
diff --git a/docs/assets/accessible-showcase-alpha7/fonts/Inter-OFL.txt b/docs/assets/accessible-showcase-alpha7/fonts/Inter-OFL.txt
new file mode 100644
index 0000000..909a6bf
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/fonts/Inter-OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/docs/assets/accessible-showcase-alpha7/fonts/Inter-Variable.ttf b/docs/assets/accessible-showcase-alpha7/fonts/Inter-Variable.ttf
new file mode 100644
index 0000000..047c92f
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/fonts/Inter-Variable.ttf differ
diff --git a/docs/assets/accessible-showcase-alpha7/garden.svg b/docs/assets/accessible-showcase-alpha7/garden.svg
new file mode 100644
index 0000000..31a0f3f
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/garden.svg
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/accessible-showcase-alpha7/guide-unstyled.html b/docs/assets/accessible-showcase-alpha7/guide-unstyled.html
new file mode 100644
index 0000000..67b3458
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/guide-unstyled.html
@@ -0,0 +1 @@
+
Common Ground: A place to grow, a place to belong Common Ground
Community field guide / Autumn 2026
A place to grow. A place to belong. Good things happen when we make a little room for one another. Come for a quiet hour, a shared meal or something new to learn.
Find your starting point Grow something A few seeds. A pair of hands. A garden we tend together.
Share a table Pull up a chair for conversation and a warm bowl of soup.
Make it last Swap a skill, mend a favorite thing or try a small project.
Every visit starts with hello All sessions are free. No membership, equipment or experience is needed. Come on your own or bring someone with you.
Your next Saturday
Make room for a good day. Stay for one session or linger a little longer. You decide how to take part.
Autumn 2026 · Weekly sessions at the Common Ground garden Program When What to expect Garden together Saturdays, 10–11:30 am Plant, water or harvest. Seated tasks are available. The shared table Saturdays, noon–1 pm Bring your lunch or share the seasonal soup. Ingredients are listed. Make & mend First Saturday, 2–3:30 pm Bring a small repair or try a simple craft. Materials are provided.
Plan a comfortable visit An easy arrival Enter through the wide gate beside the greenhouse. The main path is firm and level. Seating is available along the way. A welcome volunteer can show you the garden and the accessible restroom. Choose your pace Take part seated or standing, or simply watch. Use the quiet corner whenever you would like a break. Ask for written instructions or a demonstration. Both are welcome. A small first step is enough You do not have to know anyone, know what to say or stay all day. There is a place for you here.
\ No newline at end of file
diff --git a/docs/assets/accessible-showcase-alpha7/guide.html b/docs/assets/accessible-showcase-alpha7/guide.html
new file mode 100644
index 0000000..e813ca3
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/guide.html
@@ -0,0 +1,64 @@
+Common Ground: A place to grow, a place to belong Common Ground
Community field guide / Autumn 2026
A place to grow. A place to belong. Good things happen when we make a little room for one another. Come for a quiet hour, a shared meal or something new to learn.
Find your starting point Grow something A few seeds. A pair of hands. A garden we tend together.
Share a table Pull up a chair for conversation and a warm bowl of soup.
Make it last Swap a skill, mend a favorite thing or try a small project.
Every visit starts with hello All sessions are free. No membership, equipment or experience is needed. Come on your own or bring someone with you.
Your next Saturday
Make room for a good day. Stay for one session or linger a little longer. You decide how to take part.
Autumn 2026 · Weekly sessions at the Common Ground garden Program When What to expect Garden together Saturdays, 10–11:30 am Plant, water or harvest. Seated tasks are available. The shared table Saturdays, noon–1 pm Bring your lunch or share the seasonal soup. Ingredients are listed. Make & mend First Saturday, 2–3:30 pm Bring a small repair or try a simple craft. Materials are provided.
Plan a comfortable visit An easy arrival Enter through the wide gate beside the greenhouse. The main path is firm and level. Seating is available along the way. A welcome volunteer can show you the garden and the accessible restroom. Choose your pace Take part seated or standing, or simply watch. Use the quiet corner whenever you would like a break. Ask for written instructions or a demonstration. Both are welcome. A small first step is enough You do not have to know anyone, know what to say or stay all day. There is a place for you here.
\ No newline at end of file
diff --git a/docs/assets/accessible-showcase-alpha7/page-1.png b/docs/assets/accessible-showcase-alpha7/page-1.png
new file mode 100644
index 0000000..180311b
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/page-1.png differ
diff --git a/docs/assets/accessible-showcase-alpha7/page-2.png b/docs/assets/accessible-showcase-alpha7/page-2.png
new file mode 100644
index 0000000..ef10978
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/page-2.png differ
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua1-page-1.png b/docs/assets/accessible-showcase-alpha7/pdfua1-page-1.png
new file mode 100644
index 0000000..e4612a4
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/pdfua1-page-1.png differ
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua1-page-2.png b/docs/assets/accessible-showcase-alpha7/pdfua1-page-2.png
new file mode 100644
index 0000000..ef6005b
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/pdfua1-page-2.png differ
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua1.verapdf.json b/docs/assets/accessible-showcase-alpha7/pdfua1.verapdf.json
new file mode 100644
index 0000000..a95d798
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/pdfua1.verapdf.json
@@ -0,0 +1,63 @@
+{"report":{"buildInformation":{"releaseDetails":[{
+ "id" : "core",
+ "version" : "1.30.3",
+ "buildDate" : 1791385320000
+},{
+ "id" : "validation-model",
+ "version" : "1.30.3",
+ "buildDate" : 1791398640000
+},{
+ "id" : "apps",
+ "version" : "1.30.3",
+ "buildDate" : 1791399120000
+}]},"jobs":[{"itemDetails":{
+ "name" : "C:\\dev\\workbench\\fullbleed-official\\target\\growth\\alpha-caption-release-20261018\\public-accessible\\pdfua1\\common-ground-guide.pdf",
+ "size" : 141678
+},"validationResult":[ {
+ "details" : {
+ "passedRules" : 106,
+ "failedRules" : 0,
+ "passedChecks" : 5512,
+ "failedChecks" : 0,
+ "ruleSummaries" : [ ]
+ },
+ "jobEndStatus" : "normal",
+ "profileName" : "PDF/UA-1 validation profile",
+ "statement" : "PDF file is compliant with Validation Profile requirements.",
+ "compliant" : true
+} ],"processingTime":{
+ "start" : 1792357017152,
+ "finish" : 1792357021701,
+ "difference" : 4549,
+ "duration" : "00:00:04.549"
+}}],"batchSummary":{
+ "duration" : {
+ "start" : 1792357016462,
+ "finish" : 1792357022057,
+ "difference" : 5595,
+ "duration" : "00:00:05.595"
+ },
+ "totalJobs" : 1,
+ "outOfMemory" : 0,
+ "veraExceptions" : 0,
+ "featuresSummary" : {
+ "totalJobCount" : 0,
+ "failedJobCount" : 0,
+ "successfulJobCount" : 0
+ },
+ "repairSummary" : {
+ "totalJobCount" : 0,
+ "failedJobCount" : 0,
+ "successfulJobCount" : 0
+ },
+ "multiJob" : false,
+ "failedEncryptedJobs" : 0,
+ "failedParsingJobs" : 0,
+ "validationSummary" : {
+ "compliantPdfaCount" : 1,
+ "totalJobCount" : 1,
+ "failedJobCount" : 0,
+ "nonCompliantPdfaCount" : 0,
+ "successfulJobCount" : 1
+ }
+}}}
\ No newline at end of file
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua1.verapdf.stderr.txt b/docs/assets/accessible-showcase-alpha7/pdfua1.verapdf.stderr.txt
new file mode 100644
index 0000000..e69de29
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua2-page-1.png b/docs/assets/accessible-showcase-alpha7/pdfua2-page-1.png
new file mode 100644
index 0000000..e4612a4
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/pdfua2-page-1.png differ
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua2-page-2.png b/docs/assets/accessible-showcase-alpha7/pdfua2-page-2.png
new file mode 100644
index 0000000..ef6005b
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/pdfua2-page-2.png differ
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua2/common-ground-guide.pdf b/docs/assets/accessible-showcase-alpha7/pdfua2/common-ground-guide.pdf
new file mode 100644
index 0000000..ad1f7a8
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/pdfua2/common-ground-guide.pdf differ
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua2/pdfua2.verapdf.json b/docs/assets/accessible-showcase-alpha7/pdfua2/pdfua2.verapdf.json
new file mode 100644
index 0000000..7245184
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/pdfua2/pdfua2.verapdf.json
@@ -0,0 +1,63 @@
+{"report":{"buildInformation":{"releaseDetails":[{
+ "id" : "core",
+ "version" : "1.30.3",
+ "buildDate" : 1791385320000
+},{
+ "id" : "validation-model",
+ "version" : "1.30.3",
+ "buildDate" : 1791398640000
+},{
+ "id" : "apps",
+ "version" : "1.30.3",
+ "buildDate" : 1791399120000
+}]},"jobs":[{"itemDetails":{
+ "name" : "C:\\dev\\workbench\\fullbleed-official\\target\\growth\\alpha-caption-release-20261018\\public-accessible\\pdfua2\\common-ground-guide.pdf",
+ "size" : 141897
+},"validationResult":[ {
+ "details" : {
+ "passedRules" : 1728,
+ "failedRules" : 0,
+ "passedChecks" : 8147,
+ "failedChecks" : 0,
+ "ruleSummaries" : [ ]
+ },
+ "jobEndStatus" : "normal",
+ "profileName" : "PDF/UA-2 + Tagged PDF validation profile",
+ "statement" : "PDF file is compliant with Validation Profile requirements.",
+ "compliant" : true
+} ],"processingTime":{
+ "start" : 1792357017149,
+ "finish" : 1792357022281,
+ "difference" : 5132,
+ "duration" : "00:00:05.132"
+}}],"batchSummary":{
+ "duration" : {
+ "start" : 1792357016462,
+ "finish" : 1792357022455,
+ "difference" : 5993,
+ "duration" : "00:00:05.993"
+ },
+ "totalJobs" : 1,
+ "outOfMemory" : 0,
+ "veraExceptions" : 0,
+ "failedParsingJobs" : 0,
+ "failedEncryptedJobs" : 0,
+ "validationSummary" : {
+ "nonCompliantPdfaCount" : 0,
+ "compliantPdfaCount" : 1,
+ "totalJobCount" : 1,
+ "failedJobCount" : 0,
+ "successfulJobCount" : 1
+ },
+ "repairSummary" : {
+ "totalJobCount" : 0,
+ "failedJobCount" : 0,
+ "successfulJobCount" : 0
+ },
+ "featuresSummary" : {
+ "totalJobCount" : 0,
+ "failedJobCount" : 0,
+ "successfulJobCount" : 0
+ },
+ "multiJob" : false
+}}}
\ No newline at end of file
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua2/pdfua2.verapdf.stderr.txt b/docs/assets/accessible-showcase-alpha7/pdfua2/pdfua2.verapdf.stderr.txt
new file mode 100644
index 0000000..e69de29
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua2/render-report.json b/docs/assets/accessible-showcase-alpha7/pdfua2/render-report.json
new file mode 100644
index 0000000..2f78827
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/pdfua2/render-report.json
@@ -0,0 +1,27 @@
+{
+ "schema": "fullbleed.accessible_showcase.v1",
+ "ok": true,
+ "runtime": "2.6.0a7",
+ "pdf_profile": "pdfua2",
+ "pdf": "common-ground-guide.pdf",
+ "sha256": "b38e8d6008c5474ba8b37ce952a9e65603c03d722390af3273ff048a59af0779",
+ "page_count": 2,
+ "deterministic_replay": true,
+ "html_css_pdf_byte_parity": true,
+ "native_diagnostic_gate": true,
+ "previews": [
+ "preview/guide_page1.png",
+ "preview/guide_page2.png"
+ ],
+ "fonts": [
+ {
+ "file": "Inter-Variable.ttf",
+ "sha256": "29160a80ff49ddcab2c97711247e08b1fab27a484a329ce8b813d820dc559031"
+ },
+ {
+ "file": "DMSerifDisplay-Regular.ttf",
+ "sha256": "8cc3643535edf039aa5d95440a8542735e9197e4f4b8d9303e980fefbf5ab616"
+ }
+ ],
+ "external_pdfua_validation": "Run veraPDF separately; see README.md."
+}
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua2/structure.json b/docs/assets/accessible-showcase-alpha7/pdfua2/structure.json
new file mode 100644
index 0000000..9736e27
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/pdfua2/structure.json
@@ -0,0 +1,1126 @@
+{
+ "ok": true,
+ "title": "Common Ground: A place to grow, a place to belong",
+ "language": "en-US",
+ "page_count": 2,
+ "roles": {
+ "/Document": 1,
+ "/P": 19,
+ "/H1": 1,
+ "/Figure": 1,
+ "/H2": 6,
+ "/H3": 5,
+ "/Table": 1,
+ "/Caption": 1,
+ "/THead": 1,
+ "/TR": 4,
+ "/TH": 3,
+ "/TBody": 1,
+ "/TD": 9,
+ "/Span": 11,
+ "/L": 2,
+ "/LI": 6,
+ "/Lbl": 6,
+ "/LBody": 6
+ },
+ "headings": [
+ {
+ "role": "/H1",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "A place to grow. A place to belong."
+ },
+ {
+ "role": "/H2",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Find your starting point"
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Grow something"
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Share a table"
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Make it last"
+ },
+ {
+ "role": "/H2",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Every visit starts with hello"
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Make room for a good day."
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Plan a comfortable visit"
+ },
+ {
+ "role": "/H3",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "An easy arrival"
+ },
+ {
+ "role": "/H3",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Choose your pace"
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "A small first step is enough"
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "About this guide"
+ }
+ ],
+ "nodes": [
+ {
+ "role": "/Document",
+ "page": null,
+ "parent_role": null,
+ "child_roles": [
+ "/P",
+ "/P",
+ "/H1",
+ "/P",
+ "/Figure",
+ "/H2",
+ "/H3",
+ "/P",
+ "/H3",
+ "/P",
+ "/H3",
+ "/P",
+ "/H2",
+ "/P",
+ "/P",
+ "/H2",
+ "/P",
+ "/Table",
+ "/H2",
+ "/H3",
+ "/L",
+ "/H3",
+ "/L",
+ "/H2",
+ "/P",
+ "/H2",
+ "/P",
+ "/P"
+ ],
+ "text": "Common Ground COMMUNITY FIELD GUIDE / AUTUMN 2026 A place to grow. A place to belong. Good things happen when we make a little room for one another. Come for a quiet hour, a shared meal or something new to learn. Find your starting point Grow something A few seeds. A pair of hands. A garden we tend together. Share a table Pull up a chair for conversation and a warm bowl of soup. Make it last Swap a skill, mend a favorite thing or try a small project. Every visit starts with hello All sessions are free. No membership, equipment or experience is needed. Come on your own or bring someone with you. YOUR NEXT SATURDAY Make room for a good day. Stay for one session or linger a little longer. You decide how to take part. Autumn 2026 \u00b7 Weekly sessions at the Common Ground garden Program When What to expect Garden together Saturdays, 10\u201311:30 am Plant, water or harvest. Seated tasks are available. The shared table Saturdays, noon\u20131 pm Bring your lunch or share the seasonal soup. Ingredients are listed. Make & mend First Saturday, 2\u20133:30 pm Bring a small repair or try a simple craft. Materials are provided. Plan a comfortable visit An easy arrival Enter through the wide gate beside the greenhouse. The main path is firm and level. Seating is available along the way. A welcome volunteer can show you the garden and the accessible restroom. Choose your pace Take part seated or standing, or simply watch. Use the quiet corner whenever you would like a break. Ask for written instructions or a demonstration. Both are welcome. A small first step is enough You do not have to know anyone, know what to say or stay all day. There is a place for you here. About this guide Common Ground and its programs are fictional. This sample shows how a thoughtfully designed document can carry its structure into PDF and HTML. Fullbleed documentation: docs.fullbleed.dev"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Common Ground"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "COMMUNITY FIELD GUIDE / AUTUMN 2026"
+ },
+ {
+ "role": "/H1",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "A place to grow. A place to belong."
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Good things happen when we make a little room for one another. Come for a quiet hour, a shared meal or something new to learn."
+ },
+ {
+ "role": "/Figure",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "",
+ "alt": "Illustration of a shared garden: three raised planting beds beside a broad, level path, with a bench and a greenhouse."
+ },
+ {
+ "role": "/H2",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Find your starting point"
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Grow something"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "A few seeds. A pair of hands. A garden we tend together."
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Share a table"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Pull up a chair for conversation and a warm bowl of soup."
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Make it last"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Swap a skill, mend a favorite thing or try a small project."
+ },
+ {
+ "role": "/H2",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Every visit starts with hello"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "All sessions are free. No membership, equipment or experience is needed. Come on your own or bring someone with you."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "YOUR NEXT SATURDAY"
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Make room for a good day."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Stay for one session or linger a little longer. You decide how to take part."
+ },
+ {
+ "role": "/Table",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [
+ "/Caption",
+ "/THead",
+ "/TBody"
+ ],
+ "text": "Autumn 2026 \u00b7 Weekly sessions at the Common Ground garden Program When What to expect Garden together Saturdays, 10\u201311:30 am Plant, water or harvest. Seated tasks are available. The shared table Saturdays, noon\u20131 pm Bring your lunch or share the seasonal soup. Ingredients are listed. Make & mend First Saturday, 2\u20133:30 pm Bring a small repair or try a simple craft. Materials are provided."
+ },
+ {
+ "role": "/Caption",
+ "page": 2,
+ "parent_role": "/Table",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "Autumn 2026 \u00b7 Weekly sessions at the Common Ground garden"
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/Caption",
+ "child_roles": [],
+ "text": "Autumn 2026 \u00b7 Weekly sessions at the Common Ground garden"
+ },
+ {
+ "role": "/THead",
+ "page": 2,
+ "parent_role": "/Table",
+ "child_roles": [
+ "/TR"
+ ],
+ "text": "Program When What to expect"
+ },
+ {
+ "role": "/TR",
+ "page": 2,
+ "parent_role": "/THead",
+ "child_roles": [
+ "/TH",
+ "/TH",
+ "/TH"
+ ],
+ "text": "Program When What to expect"
+ },
+ {
+ "role": "/TH",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Program",
+ "attributes": {
+ "/O": "/Table",
+ "/Scope": "/Column"
+ }
+ },
+ {
+ "role": "/TH",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "When",
+ "attributes": {
+ "/O": "/Table",
+ "/Scope": "/Column"
+ }
+ },
+ {
+ "role": "/TH",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "What to expect",
+ "attributes": {
+ "/O": "/Table",
+ "/Scope": "/Column"
+ }
+ },
+ {
+ "role": "/TBody",
+ "page": 2,
+ "parent_role": "/Table",
+ "child_roles": [
+ "/TR",
+ "/TR",
+ "/TR"
+ ],
+ "text": "Garden together Saturdays, 10\u201311:30 am Plant, water or harvest. Seated tasks are available. The shared table Saturdays, noon\u20131 pm Bring your lunch or share the seasonal soup. Ingredients are listed. Make & mend First Saturday, 2\u20133:30 pm Bring a small repair or try a simple craft. Materials are provided."
+ },
+ {
+ "role": "/TR",
+ "page": 2,
+ "parent_role": "/TBody",
+ "child_roles": [
+ "/TD",
+ "/TD",
+ "/TD"
+ ],
+ "text": "Garden together Saturdays, 10\u201311:30 am Plant, water or harvest. Seated tasks are available."
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [
+ "/Span",
+ "/Span"
+ ],
+ "text": "Garden together"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "Garden"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "together"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Saturdays, 10\u201311:30 am"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Plant, water or harvest. Seated tasks are available."
+ },
+ {
+ "role": "/TR",
+ "page": 2,
+ "parent_role": "/TBody",
+ "child_roles": [
+ "/TD",
+ "/TD",
+ "/TD"
+ ],
+ "text": "The shared table Saturdays, noon\u20131 pm Bring your lunch or share the seasonal soup. Ingredients are listed."
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [
+ "/Span",
+ "/Span",
+ "/Span"
+ ],
+ "text": "The shared table"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "The"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "shared"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "table"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Saturdays, noon\u20131 pm"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Bring your lunch or share the seasonal soup. Ingredients are listed."
+ },
+ {
+ "role": "/TR",
+ "page": 2,
+ "parent_role": "/TBody",
+ "child_roles": [
+ "/TD",
+ "/TD",
+ "/TD"
+ ],
+ "text": "Make & mend First Saturday, 2\u20133:30 pm Bring a small repair or try a simple craft. Materials are provided."
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [
+ "/Span",
+ "/Span",
+ "/Span"
+ ],
+ "text": "Make & mend"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "Make"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "&"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "mend"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "First Saturday, 2\u20133:30 pm"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Bring a small repair or try a simple craft. Materials are provided."
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Plan a comfortable visit"
+ },
+ {
+ "role": "/H3",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "An easy arrival"
+ },
+ {
+ "role": "/L",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [
+ "/LI",
+ "/LI",
+ "/LI"
+ ],
+ "text": "Enter through the wide gate beside the greenhouse. The main path is firm and level. Seating is available along the way. A welcome volunteer can show you the garden and the accessible restroom.",
+ "attributes": {
+ "/O": "/List",
+ "/ListNumbering": "/Disc"
+ }
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "Enter through the wide gate beside the greenhouse."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "Enter through the wide gate beside the greenhouse."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "Enter through the wide gate beside the greenhouse."
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "The main path is firm and level. Seating is available along the way."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "The main path is firm and level. Seating is available along the way."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "The main path is firm and level. Seating is available along the way."
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "A welcome volunteer can show you the garden and the accessible restroom."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "A welcome volunteer can show you the garden and the accessible restroom."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "A welcome volunteer can show you the garden and the accessible restroom."
+ },
+ {
+ "role": "/H3",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Choose your pace"
+ },
+ {
+ "role": "/L",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [
+ "/LI",
+ "/LI",
+ "/LI"
+ ],
+ "text": "Take part seated or standing, or simply watch. Use the quiet corner whenever you would like a break. Ask for written instructions or a demonstration. Both are welcome.",
+ "attributes": {
+ "/O": "/List",
+ "/ListNumbering": "/Disc"
+ }
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "Take part seated or standing, or simply watch."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "Take part seated or standing, or simply watch."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "Take part seated or standing, or simply watch."
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "Use the quiet corner whenever you would like a break."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "Use the quiet corner whenever you would like a break."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "Use the quiet corner whenever you would like a break."
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "Ask for written instructions or a demonstration. Both are welcome."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "Ask for written instructions or a demonstration. Both are welcome."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "Ask for written instructions or a demonstration. Both are welcome."
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "A small first step is enough"
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "You do not have to know anyone, know what to say or stay all day. There is a place for you here."
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "About this guide"
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [],
+ "text": "Common Ground and its programs are fictional. This sample shows how a thoughtfully designed document can carry its structure into PDF and HTML."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/Document",
+ "child_roles": [
+ "/Span",
+ "/Span",
+ "/Span"
+ ],
+ "text": "Fullbleed documentation: docs.fullbleed.dev"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/P",
+ "child_roles": [],
+ "text": "Fullbleed"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/P",
+ "child_roles": [],
+ "text": "documentation:"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/P",
+ "child_roles": [],
+ "text": "docs.fullbleed.dev"
+ }
+ ],
+ "reading_order": [
+ {
+ "page": 1,
+ "mcid": 0,
+ "text": "Common Ground"
+ },
+ {
+ "page": 1,
+ "mcid": 1,
+ "text": "COMMUNITY FIELD GUIDE / AUTUMN 2026"
+ },
+ {
+ "page": 1,
+ "mcid": 2,
+ "text": "A place to grow. A place to belong."
+ },
+ {
+ "page": 1,
+ "mcid": 3,
+ "text": "Good things happen when we make a little room for one another. Come for a quiet hour, a shared meal or something new to learn."
+ },
+ {
+ "page": 1,
+ "mcid": 4,
+ "text": ""
+ },
+ {
+ "page": 1,
+ "mcid": 5,
+ "text": "Find your starting point"
+ },
+ {
+ "page": 1,
+ "mcid": 6,
+ "text": "Grow something"
+ },
+ {
+ "page": 1,
+ "mcid": 7,
+ "text": "A few seeds. A pair of hands. A garden we tend together."
+ },
+ {
+ "page": 1,
+ "mcid": 8,
+ "text": "Share a table"
+ },
+ {
+ "page": 1,
+ "mcid": 9,
+ "text": "Pull up a chair for conversation and a warm bowl of soup."
+ },
+ {
+ "page": 1,
+ "mcid": 10,
+ "text": "Make it last"
+ },
+ {
+ "page": 1,
+ "mcid": 11,
+ "text": "Swap a skill, mend a favorite thing or try a small project."
+ },
+ {
+ "page": 1,
+ "mcid": 12,
+ "text": "Every visit starts with hello"
+ },
+ {
+ "page": 1,
+ "mcid": 13,
+ "text": "All sessions are free. No membership, equipment or experience is needed. Come on your own or bring someone with you."
+ },
+ {
+ "page": 2,
+ "mcid": 0,
+ "text": "YOUR NEXT SATURDAY"
+ },
+ {
+ "page": 2,
+ "mcid": 1,
+ "text": "Make room for a good day."
+ },
+ {
+ "page": 2,
+ "mcid": 2,
+ "text": "Stay for one session or linger a little longer. You decide how to take part."
+ },
+ {
+ "page": 2,
+ "mcid": 3,
+ "text": "Autumn 2026 \u00b7 Weekly sessions at the Common Ground garden"
+ },
+ {
+ "page": 2,
+ "mcid": 4,
+ "text": "Program"
+ },
+ {
+ "page": 2,
+ "mcid": 5,
+ "text": "When"
+ },
+ {
+ "page": 2,
+ "mcid": 6,
+ "text": "What to expect"
+ },
+ {
+ "page": 2,
+ "mcid": 7,
+ "text": "Garden"
+ },
+ {
+ "page": 2,
+ "mcid": 8,
+ "text": "together"
+ },
+ {
+ "page": 2,
+ "mcid": 9,
+ "text": "Saturdays, 10\u201311:30 am"
+ },
+ {
+ "page": 2,
+ "mcid": 10,
+ "text": "Plant, water or harvest. Seated tasks are available."
+ },
+ {
+ "page": 2,
+ "mcid": 11,
+ "text": "The"
+ },
+ {
+ "page": 2,
+ "mcid": 12,
+ "text": "shared"
+ },
+ {
+ "page": 2,
+ "mcid": 13,
+ "text": "table"
+ },
+ {
+ "page": 2,
+ "mcid": 14,
+ "text": "Saturdays, noon\u20131 pm"
+ },
+ {
+ "page": 2,
+ "mcid": 15,
+ "text": "Bring your lunch or share the seasonal soup. Ingredients are listed."
+ },
+ {
+ "page": 2,
+ "mcid": 16,
+ "text": "Make"
+ },
+ {
+ "page": 2,
+ "mcid": 17,
+ "text": "&"
+ },
+ {
+ "page": 2,
+ "mcid": 18,
+ "text": "mend"
+ },
+ {
+ "page": 2,
+ "mcid": 19,
+ "text": "First Saturday, 2\u20133:30 pm"
+ },
+ {
+ "page": 2,
+ "mcid": 20,
+ "text": "Bring a small repair or try a simple craft. Materials are provided."
+ },
+ {
+ "page": 2,
+ "mcid": 21,
+ "text": "Plan a comfortable visit"
+ },
+ {
+ "page": 2,
+ "mcid": 22,
+ "text": "An easy arrival"
+ },
+ {
+ "page": 2,
+ "mcid": 23,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 24,
+ "text": "Enter through the wide gate beside the greenhouse."
+ },
+ {
+ "page": 2,
+ "mcid": 25,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 26,
+ "text": "The main path is firm and level. Seating is available along the way."
+ },
+ {
+ "page": 2,
+ "mcid": 27,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 28,
+ "text": "A welcome volunteer can show you the garden and the accessible restroom."
+ },
+ {
+ "page": 2,
+ "mcid": 29,
+ "text": "Choose your pace"
+ },
+ {
+ "page": 2,
+ "mcid": 30,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 31,
+ "text": "Take part seated or standing, or simply watch."
+ },
+ {
+ "page": 2,
+ "mcid": 32,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 33,
+ "text": "Use the quiet corner whenever you would like a break."
+ },
+ {
+ "page": 2,
+ "mcid": 34,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 35,
+ "text": "Ask for written instructions or a demonstration. Both are welcome."
+ },
+ {
+ "page": 2,
+ "mcid": 36,
+ "text": "A small first step is enough"
+ },
+ {
+ "page": 2,
+ "mcid": 37,
+ "text": "You do not have to know anyone, know what to say or stay all day. There is a place for you here."
+ },
+ {
+ "page": 2,
+ "mcid": 38,
+ "text": "About this guide"
+ },
+ {
+ "page": 2,
+ "mcid": 39,
+ "text": "Common Ground and its programs are fictional. This sample shows how a thoughtfully designed document can carry its structure into PDF and HTML."
+ },
+ {
+ "page": 2,
+ "mcid": 40,
+ "text": "Fullbleed"
+ },
+ {
+ "page": 2,
+ "mcid": 41,
+ "text": "documentation:"
+ },
+ {
+ "page": 2,
+ "mcid": 42,
+ "text": "docs.fullbleed.dev"
+ }
+ ],
+ "decorative_text_omitted": true,
+ "marked_content_nesting_valid": true,
+ "caption_table_ownership_valid": true,
+ "pdf_link_annotations": 0,
+ "scope": "Actual PDF structure, MCID text and semantic order; not an assistive-technology user test.",
+ "pdf_sha256": "b38e8d6008c5474ba8b37ce952a9e65603c03d722390af3273ff048a59af0779"
+}
diff --git a/docs/assets/accessible-showcase-alpha7/pdfua2/validation.json b/docs/assets/accessible-showcase-alpha7/pdfua2/validation.json
new file mode 100644
index 0000000..45fcdf6
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/pdfua2/validation.json
@@ -0,0 +1,37 @@
+{
+ "ok": true,
+ "pdf_sha256": "b38e8d6008c5474ba8b37ce952a9e65603c03d722390af3273ff048a59af0779",
+ "pdf_profile": "pdfua2",
+ "structure_passed": true,
+ "external_validation": {
+ "profile": "PDF/UA-2 + Tagged PDF validation profile",
+ "compliant": true,
+ "stderr_bytes": 0,
+ "details": {
+ "passedRules": 1728,
+ "failedRules": 0,
+ "passedChecks": 8147,
+ "failedChecks": 0,
+ "ruleSummaries": []
+ },
+ "build": {
+ "releaseDetails": [
+ {
+ "id": "core",
+ "version": "1.30.3",
+ "buildDate": 1791385320000
+ },
+ {
+ "id": "validation-model",
+ "version": "1.30.3",
+ "buildDate": 1791398640000
+ },
+ {
+ "id": "apps",
+ "version": "1.30.3",
+ "buildDate": 1791399120000
+ }
+ ]
+ }
+ }
+}
diff --git a/docs/assets/accessible-showcase-alpha7/project.zip b/docs/assets/accessible-showcase-alpha7/project.zip
new file mode 100644
index 0000000..d529dd1
Binary files /dev/null and b/docs/assets/accessible-showcase-alpha7/project.zip differ
diff --git a/docs/assets/accessible-showcase-alpha7/render-report.json b/docs/assets/accessible-showcase-alpha7/render-report.json
new file mode 100644
index 0000000..261d901
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/render-report.json
@@ -0,0 +1,27 @@
+{
+ "schema": "fullbleed.accessible_showcase.v1",
+ "ok": true,
+ "runtime": "2.6.0a7",
+ "pdf_profile": "pdfua1",
+ "pdf": "common-ground-guide.pdf",
+ "sha256": "faaf5e56e4bc962f4096d6a77deaad176266089548984eda7f45091339ba73d4",
+ "page_count": 2,
+ "deterministic_replay": true,
+ "html_css_pdf_byte_parity": true,
+ "native_diagnostic_gate": true,
+ "previews": [
+ "preview/guide_page1.png",
+ "preview/guide_page2.png"
+ ],
+ "fonts": [
+ {
+ "file": "Inter-Variable.ttf",
+ "sha256": "29160a80ff49ddcab2c97711247e08b1fab27a484a329ce8b813d820dc559031"
+ },
+ {
+ "file": "DMSerifDisplay-Regular.ttf",
+ "sha256": "8cc3643535edf039aa5d95440a8542735e9197e4f4b8d9303e980fefbf5ab616"
+ }
+ ],
+ "external_pdfua_validation": "Run veraPDF separately; see README.md."
+}
diff --git a/docs/assets/accessible-showcase-alpha7/source.json b/docs/assets/accessible-showcase-alpha7/source.json
new file mode 100644
index 0000000..fd2ef75
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/source.json
@@ -0,0 +1,41 @@
+{
+ "repository": "https://github.com/fullbleed-engine/fullbleed-official",
+ "commit": "2be0f5a1e04088d129ea194803b05a04b526a889",
+ "path": "examples/accessible_showcase",
+ "runtime": "2.6.0a7",
+ "source_files": {
+ ".gitignore": "64e094a74cb73573fee01036fc1dd5a4d0ade22c2252264d437b370406e40278",
+ "LICENSE": "36a2115938f6e68cf0d87eccff7a6a3e57c17017e909cbd640d51aff1fa0c5a9",
+ "README.md": "9d431237c06af961c055bbd9df9e723d2479fc005133da3b7e153030e9171a5c",
+ "browser.css": "59876dbb2ddce6f7fc94a435584bc430ce714fc1cc6095adaa4f9e58351369a1",
+ "fonts/DMSerifDisplay-OFL.txt": "aa49c876a28d8c0b0a3027d6c25b9c607eee8c0fb96165011d42a6ace1408fe1",
+ "fonts/DMSerifDisplay-Regular.ttf": "8cc3643535edf039aa5d95440a8542735e9197e4f4b8d9303e980fefbf5ab616",
+ "fonts/Inter-OFL.txt": "5dd548d31a85f756e01d63e00d7faf1e324103ed3e9102fcbbabf2cc2db6dd39",
+ "garden.svg": "b55568259e14cca65522b69dc18c4987b399a1862ec9d99ed1b647b97996043c",
+ "guide.css": "29cb17b5b0992607a22258a43964828ce1d6ae31c0402d648ff7479fdb5c4e07",
+ "guide.py": "dfb0777721ce6eb2563941f05f9f1fc3d4dc8346672f20e405702267ee534837",
+ "render.py": "950c43070153997a6c6fe5c863a6519b705d07d2ca850dbc4932413d4214aaa9",
+ "requirements-check.txt": "4eadc7023aba8e289cb2e9b4f46eed8c350d9c55500f98c19d938010d46add8d",
+ "requirements.txt": "5a911ba7f5842c48fc5e844c219f36b2bbdfe7a71e54753a9a897f533a971b14",
+ "verify.py": "1add674fb7a9609072875e704d96a5e7d1cc6bf9f6cb977226332ec89a417942"
+ },
+ "zip_sha256": "34b115996ede1274895cd0d81df39f37c65f5851ffbcb28dc09e52d9dd4087ca",
+ "zip_bytes": 122873,
+ "package_files": {
+ ".gitignore": "64e094a74cb73573fee01036fc1dd5a4d0ade22c2252264d437b370406e40278",
+ "LICENSE": "36a2115938f6e68cf0d87eccff7a6a3e57c17017e909cbd640d51aff1fa0c5a9",
+ "README.md": "9d431237c06af961c055bbd9df9e723d2479fc005133da3b7e153030e9171a5c",
+ "SOURCE.json": "c8f74eb8f295d34d181b6bce51a443d373a5bbcecbfce038cd91fa47365bdeb0",
+ "browser.css": "59876dbb2ddce6f7fc94a435584bc430ce714fc1cc6095adaa4f9e58351369a1",
+ "fonts/DMSerifDisplay-OFL.txt": "aa49c876a28d8c0b0a3027d6c25b9c607eee8c0fb96165011d42a6ace1408fe1",
+ "fonts/DMSerifDisplay-Regular.ttf": "8cc3643535edf039aa5d95440a8542735e9197e4f4b8d9303e980fefbf5ab616",
+ "fonts/Inter-OFL.txt": "5dd548d31a85f756e01d63e00d7faf1e324103ed3e9102fcbbabf2cc2db6dd39",
+ "garden.svg": "b55568259e14cca65522b69dc18c4987b399a1862ec9d99ed1b647b97996043c",
+ "guide.css": "29cb17b5b0992607a22258a43964828ce1d6ae31c0402d648ff7479fdb5c4e07",
+ "guide.py": "dfb0777721ce6eb2563941f05f9f1fc3d4dc8346672f20e405702267ee534837",
+ "render.py": "950c43070153997a6c6fe5c863a6519b705d07d2ca850dbc4932413d4214aaa9",
+ "requirements-check.txt": "4eadc7023aba8e289cb2e9b4f46eed8c350d9c55500f98c19d938010d46add8d",
+ "requirements.txt": "5a911ba7f5842c48fc5e844c219f36b2bbdfe7a71e54753a9a897f533a971b14",
+ "verify.py": "1add674fb7a9609072875e704d96a5e7d1cc6bf9f6cb977226332ec89a417942"
+ }
+}
diff --git a/docs/assets/accessible-showcase-alpha7/structure.json b/docs/assets/accessible-showcase-alpha7/structure.json
new file mode 100644
index 0000000..a2a0e03
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/structure.json
@@ -0,0 +1,1089 @@
+{
+ "ok": true,
+ "title": "Common Ground: A place to grow, a place to belong",
+ "language": "en-US",
+ "page_count": 2,
+ "roles": {
+ "/P": 19,
+ "/H1": 1,
+ "/Figure": 1,
+ "/H2": 6,
+ "/H3": 5,
+ "/Table": 1,
+ "/Caption": 1,
+ "/THead": 1,
+ "/TR": 4,
+ "/TH": 3,
+ "/TBody": 1,
+ "/TD": 9,
+ "/Span": 11,
+ "/L": 2,
+ "/LI": 6,
+ "/Lbl": 6,
+ "/LBody": 6
+ },
+ "headings": [
+ {
+ "role": "/H1",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "A place to grow. A place to belong."
+ },
+ {
+ "role": "/H2",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Find your starting point"
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Grow something"
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Share a table"
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Make it last"
+ },
+ {
+ "role": "/H2",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Every visit starts with hello"
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Make room for a good day."
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Plan a comfortable visit"
+ },
+ {
+ "role": "/H3",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "An easy arrival"
+ },
+ {
+ "role": "/H3",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Choose your pace"
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "A small first step is enough"
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "About this guide"
+ }
+ ],
+ "nodes": [
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Common Ground"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "COMMUNITY FIELD GUIDE / AUTUMN 2026"
+ },
+ {
+ "role": "/H1",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "A place to grow. A place to belong."
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Good things happen when we make a little room for one another. Come for a quiet hour, a shared meal or something new to learn."
+ },
+ {
+ "role": "/Figure",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "",
+ "alt": "Illustration of a shared garden: three raised planting beds beside a broad, level path, with a bench and a greenhouse."
+ },
+ {
+ "role": "/H2",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Find your starting point"
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Grow something"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "A few seeds. A pair of hands. A garden we tend together."
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Share a table"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Pull up a chair for conversation and a warm bowl of soup."
+ },
+ {
+ "role": "/H3",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Make it last"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Swap a skill, mend a favorite thing or try a small project."
+ },
+ {
+ "role": "/H2",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Every visit starts with hello"
+ },
+ {
+ "role": "/P",
+ "page": 1,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "All sessions are free. No membership, equipment or experience is needed. Come on your own or bring someone with you."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "YOUR NEXT SATURDAY"
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Make room for a good day."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Stay for one session or linger a little longer. You decide how to take part."
+ },
+ {
+ "role": "/Table",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [
+ "/Caption",
+ "/THead",
+ "/TBody"
+ ],
+ "text": "Autumn 2026 \u00b7 Weekly sessions at the Common Ground garden Program When What to expect Garden together Saturdays, 10\u201311:30 am Plant, water or harvest. Seated tasks are available. The shared table Saturdays, noon\u20131 pm Bring your lunch or share the seasonal soup. Ingredients are listed. Make & mend First Saturday, 2\u20133:30 pm Bring a small repair or try a simple craft. Materials are provided."
+ },
+ {
+ "role": "/Caption",
+ "page": 2,
+ "parent_role": "/Table",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "Autumn 2026 \u00b7 Weekly sessions at the Common Ground garden"
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/Caption",
+ "child_roles": [],
+ "text": "Autumn 2026 \u00b7 Weekly sessions at the Common Ground garden"
+ },
+ {
+ "role": "/THead",
+ "page": 2,
+ "parent_role": "/Table",
+ "child_roles": [
+ "/TR"
+ ],
+ "text": "Program When What to expect"
+ },
+ {
+ "role": "/TR",
+ "page": 2,
+ "parent_role": "/THead",
+ "child_roles": [
+ "/TH",
+ "/TH",
+ "/TH"
+ ],
+ "text": "Program When What to expect"
+ },
+ {
+ "role": "/TH",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Program",
+ "attributes": {
+ "/O": "/Table",
+ "/Scope": "/Column"
+ }
+ },
+ {
+ "role": "/TH",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "When",
+ "attributes": {
+ "/O": "/Table",
+ "/Scope": "/Column"
+ }
+ },
+ {
+ "role": "/TH",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "What to expect",
+ "attributes": {
+ "/O": "/Table",
+ "/Scope": "/Column"
+ }
+ },
+ {
+ "role": "/TBody",
+ "page": 2,
+ "parent_role": "/Table",
+ "child_roles": [
+ "/TR",
+ "/TR",
+ "/TR"
+ ],
+ "text": "Garden together Saturdays, 10\u201311:30 am Plant, water or harvest. Seated tasks are available. The shared table Saturdays, noon\u20131 pm Bring your lunch or share the seasonal soup. Ingredients are listed. Make & mend First Saturday, 2\u20133:30 pm Bring a small repair or try a simple craft. Materials are provided."
+ },
+ {
+ "role": "/TR",
+ "page": 2,
+ "parent_role": "/TBody",
+ "child_roles": [
+ "/TD",
+ "/TD",
+ "/TD"
+ ],
+ "text": "Garden together Saturdays, 10\u201311:30 am Plant, water or harvest. Seated tasks are available."
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [
+ "/Span",
+ "/Span"
+ ],
+ "text": "Garden together"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "Garden"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "together"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Saturdays, 10\u201311:30 am"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Plant, water or harvest. Seated tasks are available."
+ },
+ {
+ "role": "/TR",
+ "page": 2,
+ "parent_role": "/TBody",
+ "child_roles": [
+ "/TD",
+ "/TD",
+ "/TD"
+ ],
+ "text": "The shared table Saturdays, noon\u20131 pm Bring your lunch or share the seasonal soup. Ingredients are listed."
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [
+ "/Span",
+ "/Span",
+ "/Span"
+ ],
+ "text": "The shared table"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "The"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "shared"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "table"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Saturdays, noon\u20131 pm"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Bring your lunch or share the seasonal soup. Ingredients are listed."
+ },
+ {
+ "role": "/TR",
+ "page": 2,
+ "parent_role": "/TBody",
+ "child_roles": [
+ "/TD",
+ "/TD",
+ "/TD"
+ ],
+ "text": "Make & mend First Saturday, 2\u20133:30 pm Bring a small repair or try a simple craft. Materials are provided."
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [
+ "/Span",
+ "/Span",
+ "/Span"
+ ],
+ "text": "Make & mend"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "Make"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "&"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/TD",
+ "child_roles": [],
+ "text": "mend"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "First Saturday, 2\u20133:30 pm"
+ },
+ {
+ "role": "/TD",
+ "page": 2,
+ "parent_role": "/TR",
+ "child_roles": [],
+ "text": "Bring a small repair or try a simple craft. Materials are provided."
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Plan a comfortable visit"
+ },
+ {
+ "role": "/H3",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "An easy arrival"
+ },
+ {
+ "role": "/L",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [
+ "/LI",
+ "/LI",
+ "/LI"
+ ],
+ "text": "Enter through the wide gate beside the greenhouse. The main path is firm and level. Seating is available along the way. A welcome volunteer can show you the garden and the accessible restroom.",
+ "attributes": {
+ "/O": "/List",
+ "/ListNumbering": "/Disc"
+ }
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "Enter through the wide gate beside the greenhouse."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "Enter through the wide gate beside the greenhouse."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "Enter through the wide gate beside the greenhouse."
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "The main path is firm and level. Seating is available along the way."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "The main path is firm and level. Seating is available along the way."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "The main path is firm and level. Seating is available along the way."
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "A welcome volunteer can show you the garden and the accessible restroom."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "A welcome volunteer can show you the garden and the accessible restroom."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "A welcome volunteer can show you the garden and the accessible restroom."
+ },
+ {
+ "role": "/H3",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Choose your pace"
+ },
+ {
+ "role": "/L",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [
+ "/LI",
+ "/LI",
+ "/LI"
+ ],
+ "text": "Take part seated or standing, or simply watch. Use the quiet corner whenever you would like a break. Ask for written instructions or a demonstration. Both are welcome.",
+ "attributes": {
+ "/O": "/List",
+ "/ListNumbering": "/Disc"
+ }
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "Take part seated or standing, or simply watch."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "Take part seated or standing, or simply watch."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "Take part seated or standing, or simply watch."
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "Use the quiet corner whenever you would like a break."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "Use the quiet corner whenever you would like a break."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "Use the quiet corner whenever you would like a break."
+ },
+ {
+ "role": "/LI",
+ "page": 2,
+ "parent_role": "/L",
+ "child_roles": [
+ "/Lbl",
+ "/LBody"
+ ],
+ "text": "Ask for written instructions or a demonstration. Both are welcome."
+ },
+ {
+ "role": "/Lbl",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [],
+ "text": ""
+ },
+ {
+ "role": "/LBody",
+ "page": 2,
+ "parent_role": "/LI",
+ "child_roles": [
+ "/P"
+ ],
+ "text": "Ask for written instructions or a demonstration. Both are welcome."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": "/LBody",
+ "child_roles": [],
+ "text": "Ask for written instructions or a demonstration. Both are welcome."
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "A small first step is enough"
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "You do not have to know anyone, know what to say or stay all day. There is a place for you here."
+ },
+ {
+ "role": "/H2",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "About this guide"
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [],
+ "text": "Common Ground and its programs are fictional. This sample shows how a thoughtfully designed document can carry its structure into PDF and HTML."
+ },
+ {
+ "role": "/P",
+ "page": 2,
+ "parent_role": null,
+ "child_roles": [
+ "/Span",
+ "/Span",
+ "/Span"
+ ],
+ "text": "Fullbleed documentation: docs.fullbleed.dev"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/P",
+ "child_roles": [],
+ "text": "Fullbleed"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/P",
+ "child_roles": [],
+ "text": "documentation:"
+ },
+ {
+ "role": "/Span",
+ "page": 2,
+ "parent_role": "/P",
+ "child_roles": [],
+ "text": "docs.fullbleed.dev"
+ }
+ ],
+ "reading_order": [
+ {
+ "page": 1,
+ "mcid": 0,
+ "text": "Common Ground"
+ },
+ {
+ "page": 1,
+ "mcid": 1,
+ "text": "COMMUNITY FIELD GUIDE / AUTUMN 2026"
+ },
+ {
+ "page": 1,
+ "mcid": 2,
+ "text": "A place to grow. A place to belong."
+ },
+ {
+ "page": 1,
+ "mcid": 3,
+ "text": "Good things happen when we make a little room for one another. Come for a quiet hour, a shared meal or something new to learn."
+ },
+ {
+ "page": 1,
+ "mcid": 4,
+ "text": ""
+ },
+ {
+ "page": 1,
+ "mcid": 5,
+ "text": "Find your starting point"
+ },
+ {
+ "page": 1,
+ "mcid": 6,
+ "text": "Grow something"
+ },
+ {
+ "page": 1,
+ "mcid": 7,
+ "text": "A few seeds. A pair of hands. A garden we tend together."
+ },
+ {
+ "page": 1,
+ "mcid": 8,
+ "text": "Share a table"
+ },
+ {
+ "page": 1,
+ "mcid": 9,
+ "text": "Pull up a chair for conversation and a warm bowl of soup."
+ },
+ {
+ "page": 1,
+ "mcid": 10,
+ "text": "Make it last"
+ },
+ {
+ "page": 1,
+ "mcid": 11,
+ "text": "Swap a skill, mend a favorite thing or try a small project."
+ },
+ {
+ "page": 1,
+ "mcid": 12,
+ "text": "Every visit starts with hello"
+ },
+ {
+ "page": 1,
+ "mcid": 13,
+ "text": "All sessions are free. No membership, equipment or experience is needed. Come on your own or bring someone with you."
+ },
+ {
+ "page": 2,
+ "mcid": 0,
+ "text": "YOUR NEXT SATURDAY"
+ },
+ {
+ "page": 2,
+ "mcid": 1,
+ "text": "Make room for a good day."
+ },
+ {
+ "page": 2,
+ "mcid": 2,
+ "text": "Stay for one session or linger a little longer. You decide how to take part."
+ },
+ {
+ "page": 2,
+ "mcid": 3,
+ "text": "Autumn 2026 \u00b7 Weekly sessions at the Common Ground garden"
+ },
+ {
+ "page": 2,
+ "mcid": 4,
+ "text": "Program"
+ },
+ {
+ "page": 2,
+ "mcid": 5,
+ "text": "When"
+ },
+ {
+ "page": 2,
+ "mcid": 6,
+ "text": "What to expect"
+ },
+ {
+ "page": 2,
+ "mcid": 7,
+ "text": "Garden"
+ },
+ {
+ "page": 2,
+ "mcid": 8,
+ "text": "together"
+ },
+ {
+ "page": 2,
+ "mcid": 9,
+ "text": "Saturdays, 10\u201311:30 am"
+ },
+ {
+ "page": 2,
+ "mcid": 10,
+ "text": "Plant, water or harvest. Seated tasks are available."
+ },
+ {
+ "page": 2,
+ "mcid": 11,
+ "text": "The"
+ },
+ {
+ "page": 2,
+ "mcid": 12,
+ "text": "shared"
+ },
+ {
+ "page": 2,
+ "mcid": 13,
+ "text": "table"
+ },
+ {
+ "page": 2,
+ "mcid": 14,
+ "text": "Saturdays, noon\u20131 pm"
+ },
+ {
+ "page": 2,
+ "mcid": 15,
+ "text": "Bring your lunch or share the seasonal soup. Ingredients are listed."
+ },
+ {
+ "page": 2,
+ "mcid": 16,
+ "text": "Make"
+ },
+ {
+ "page": 2,
+ "mcid": 17,
+ "text": "&"
+ },
+ {
+ "page": 2,
+ "mcid": 18,
+ "text": "mend"
+ },
+ {
+ "page": 2,
+ "mcid": 19,
+ "text": "First Saturday, 2\u20133:30 pm"
+ },
+ {
+ "page": 2,
+ "mcid": 20,
+ "text": "Bring a small repair or try a simple craft. Materials are provided."
+ },
+ {
+ "page": 2,
+ "mcid": 21,
+ "text": "Plan a comfortable visit"
+ },
+ {
+ "page": 2,
+ "mcid": 22,
+ "text": "An easy arrival"
+ },
+ {
+ "page": 2,
+ "mcid": 23,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 24,
+ "text": "Enter through the wide gate beside the greenhouse."
+ },
+ {
+ "page": 2,
+ "mcid": 25,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 26,
+ "text": "The main path is firm and level. Seating is available along the way."
+ },
+ {
+ "page": 2,
+ "mcid": 27,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 28,
+ "text": "A welcome volunteer can show you the garden and the accessible restroom."
+ },
+ {
+ "page": 2,
+ "mcid": 29,
+ "text": "Choose your pace"
+ },
+ {
+ "page": 2,
+ "mcid": 30,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 31,
+ "text": "Take part seated or standing, or simply watch."
+ },
+ {
+ "page": 2,
+ "mcid": 32,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 33,
+ "text": "Use the quiet corner whenever you would like a break."
+ },
+ {
+ "page": 2,
+ "mcid": 34,
+ "text": ""
+ },
+ {
+ "page": 2,
+ "mcid": 35,
+ "text": "Ask for written instructions or a demonstration. Both are welcome."
+ },
+ {
+ "page": 2,
+ "mcid": 36,
+ "text": "A small first step is enough"
+ },
+ {
+ "page": 2,
+ "mcid": 37,
+ "text": "You do not have to know anyone, know what to say or stay all day. There is a place for you here."
+ },
+ {
+ "page": 2,
+ "mcid": 38,
+ "text": "About this guide"
+ },
+ {
+ "page": 2,
+ "mcid": 39,
+ "text": "Common Ground and its programs are fictional. This sample shows how a thoughtfully designed document can carry its structure into PDF and HTML."
+ },
+ {
+ "page": 2,
+ "mcid": 40,
+ "text": "Fullbleed"
+ },
+ {
+ "page": 2,
+ "mcid": 41,
+ "text": "documentation:"
+ },
+ {
+ "page": 2,
+ "mcid": 42,
+ "text": "docs.fullbleed.dev"
+ }
+ ],
+ "decorative_text_omitted": true,
+ "marked_content_nesting_valid": true,
+ "caption_table_ownership_valid": true,
+ "pdf_link_annotations": 0,
+ "scope": "Actual PDF structure, MCID text and semantic order; not an assistive-technology user test.",
+ "pdf_sha256": "faaf5e56e4bc962f4096d6a77deaad176266089548984eda7f45091339ba73d4"
+}
diff --git a/docs/assets/accessible-showcase-alpha7/validation.json b/docs/assets/accessible-showcase-alpha7/validation.json
new file mode 100644
index 0000000..85760ff
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/validation.json
@@ -0,0 +1,37 @@
+{
+ "ok": true,
+ "pdf_sha256": "faaf5e56e4bc962f4096d6a77deaad176266089548984eda7f45091339ba73d4",
+ "pdf_profile": "pdfua1",
+ "structure_passed": true,
+ "external_validation": {
+ "profile": "PDF/UA-1 validation profile",
+ "compliant": true,
+ "stderr_bytes": 0,
+ "details": {
+ "passedRules": 106,
+ "failedRules": 0,
+ "passedChecks": 5512,
+ "failedChecks": 0,
+ "ruleSummaries": []
+ },
+ "build": {
+ "releaseDetails": [
+ {
+ "id": "core",
+ "version": "1.30.3",
+ "buildDate": 1791385320000
+ },
+ {
+ "id": "validation-model",
+ "version": "1.30.3",
+ "buildDate": 1791398640000
+ },
+ {
+ "id": "apps",
+ "version": "1.30.3",
+ "buildDate": 1791399120000
+ }
+ ]
+ }
+ }
+}
diff --git a/docs/assets/accessible-showcase-alpha7/verification-guard.json b/docs/assets/accessible-showcase-alpha7/verification-guard.json
new file mode 100644
index 0000000..1025d91
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/verification-guard.json
@@ -0,0 +1,32 @@
+{
+ "ok": true,
+ "runtime": "2.6.0a7",
+ "source_commit": "2be0f5a1e04088d129ea194803b05a04b526a889",
+ "nested_mcid_fixture_sha256": "587eab84291c3e4af0e1bbfd7ed42100d2b4cd1a8cf6b8c0ba237b58d69548ae",
+ "duplicate_caption_fixture_sha256": "e59754b06ebd3229b35033c74b7c08175543396b48d9c152cc3419c7e58ba17d",
+ "profiles": {
+ "pdfua1": {
+ "ok": true,
+ "source_files": 14,
+ "pdf_sha256": "faaf5e56e4bc962f4096d6a77deaad176266089548984eda7f45091339ba73d4",
+ "pdf_profile": "pdfua1",
+ "matches_externally_validated_pdf": true,
+ "external_validator_rerun": false,
+ "marked_content_nesting_valid": true,
+ "negative_control_rejection": "Nested MCID on page 2: 7 -> 8",
+ "caption_negative_control_rejection": "Expected one Caption; got 2"
+ },
+ "pdfua2": {
+ "ok": true,
+ "source_files": 14,
+ "pdf_sha256": "b38e8d6008c5474ba8b37ce952a9e65603c03d722390af3273ff048a59af0779",
+ "pdf_profile": "pdfua2",
+ "matches_externally_validated_pdf": true,
+ "external_validator_rerun": false,
+ "marked_content_nesting_valid": true,
+ "negative_control_rejection": "Nested MCID on page 2: 7 -> 8",
+ "caption_negative_control_rejection": "Expected one Caption; got 2"
+ }
+ },
+ "public_runtime_reproduced": true
+}
diff --git a/docs/assets/accessible-showcase-alpha7/visual-review.json b/docs/assets/accessible-showcase-alpha7/visual-review.json
new file mode 100644
index 0000000..5186bc6
--- /dev/null
+++ b/docs/assets/accessible-showcase-alpha7/visual-review.json
@@ -0,0 +1,66 @@
+{
+ "ok": true,
+ "method": "Independent PDFium rendering at 144 DPI",
+ "profiles": {
+ "pdfua1": {
+ "pdf_sha256": "faaf5e56e4bc962f4096d6a77deaad176266089548984eda7f45091339ba73d4",
+ "pages": [
+ {
+ "page": 1,
+ "size": [
+ 1191,
+ 1684
+ ],
+ "sha256": "37dd5d5d49bd28e1d4099e2f55def5799b4d9ac534731f83825730f43d575b9e",
+ "pixels_sha256": "5287fcde7630001344414fe6ab9ac9f1c68cd2ee34f3b87b9cae9aff8a6f51b5",
+ "path": "pdfua1-page-1.png"
+ },
+ {
+ "page": 2,
+ "size": [
+ 1191,
+ 1684
+ ],
+ "sha256": "79d597f2f3c323aed9d379275356dbf88d04bd9461ca3df69f9def2ed097cf78",
+ "pixels_sha256": "d022e7280cf4bd3b8bb270e426a38b84e672ff02818c0ef7885a81340df55014",
+ "path": "pdfua1-page-2.png"
+ }
+ ]
+ },
+ "pdfua2": {
+ "pdf_sha256": "b38e8d6008c5474ba8b37ce952a9e65603c03d722390af3273ff048a59af0779",
+ "pages": [
+ {
+ "page": 1,
+ "size": [
+ 1191,
+ 1684
+ ],
+ "sha256": "37dd5d5d49bd28e1d4099e2f55def5799b4d9ac534731f83825730f43d575b9e",
+ "pixels_sha256": "5287fcde7630001344414fe6ab9ac9f1c68cd2ee34f3b87b9cae9aff8a6f51b5",
+ "path": "pdfua2-page-1.png"
+ },
+ {
+ "page": 2,
+ "size": [
+ 1191,
+ 1684
+ ],
+ "sha256": "79d597f2f3c323aed9d379275356dbf88d04bd9461ca3df69f9def2ed097cf78",
+ "pixels_sha256": "d022e7280cf4bd3b8bb270e426a38b84e672ff02818c0ef7885a81340df55014",
+ "path": "pdfua2-page-2.png"
+ }
+ ]
+ }
+ },
+ "both_profiles_have_identical_pixels": true,
+ "visual_review": "Both independent PDFium page renders inspected by Codex.",
+ "reviewed_at": "2026-10-18T19:47:17.485309+00:00",
+ "observations": [
+ "Both pages retain the complete composition, readable type and all authored content.",
+ "The garden illustration, three program cards, schedule and visit-planning lists are visible without clipping or text collisions.",
+ "The weekly-session caption is visible immediately above its table.",
+ "The PDF/UA-1 and PDF/UA-2 specimens have identical rendered pixels on both pages."
+ ],
+ "scope": "Visual layout inspection and exact pixel comparison; no human assistive-technology evaluation or general accessibility claim."
+}
diff --git a/docs/guides/accessible-showcase.md b/docs/guides/accessible-showcase.md
index b05cabd..346eaa3 100644
--- a/docs/guides/accessible-showcase.md
+++ b/docs/guides/accessible-showcase.md
@@ -1,6 +1,6 @@
---
title: An accessible document, authored with components
-description: A designed two-page Common Ground guide with Python component source, PDF/UA-1 validation evidence, and styled or unstyled HTML exports.
+description: A designed two-page Common Ground guide with Python component source, PDF/UA-1 and PDF/UA-2 validation evidence, and styled or unstyled HTML exports.
---
# An accessible document, authored with components
@@ -10,20 +10,21 @@ reusable program cards, a captioned schedule and practical visitor information.
Its Python source carries headings, table headers, lists and an image description
through to the final PDF.
-[Read the component source](https://github.com/fullbleed-engine/fullbleed-official/blob/9da6a672e81fabebb5733aadcdd932355188bac5/examples/accessible_showcase/guide.py).
+[Read the component source](https://github.com/fullbleed-engine/fullbleed-official/blob/v2.6.0a7/examples/accessible_showcase/guide.py).
-[Read the PDF](../assets/accessible-showcase-alpha6/common-ground-guide.pdf){ .md-button .md-button--primary }
-[Download the source kit](../assets/accessible-showcase-alpha6/project.zip){ .md-button }
-[Open the responsive HTML](../assets/accessible-showcase-alpha6/guide.html){ .md-button }
+[Read PDF/UA-1](../assets/accessible-showcase-alpha7/common-ground-guide.pdf){ .md-button .md-button--primary }
+[Read PDF/UA-2](../assets/accessible-showcase-alpha7/pdfua2/common-ground-guide.pdf){ .md-button }
+[Download the source kit](../assets/accessible-showcase-alpha7/project.zip){ .md-button }
+[Open the responsive HTML](../assets/accessible-showcase-alpha7/guide.html){ .md-button }
-
+
-[View the schedule and visitor-information page](../assets/accessible-showcase-alpha6/page-2.png).
+[View the schedule and visitor-information page](../assets/accessible-showcase-alpha7/page-2.png).
!!! info "Pinned component alpha"
- The kit uses `fullbleed.ui.experimental` from **2.6.0a6**. Its API may change
- before stabilization. The PDF and all verification receipts below refer to
- this specific specimen.
+ The kit uses `fullbleed.ui.experimental` from **2.6.0a7**. Its API may change
+ before stabilization. The PDFs and verification receipts below refer to
+ these exact specimens.
## Build it and make it yours
@@ -51,6 +52,15 @@ card = ProgramCard(
`BulletList` preserves list semantics; the illustration uses `Image(alt=...)`.
The source needs no raw HTML strings.
+The default is PDF/UA-1. To render the same component design as PDF/UA-2:
+
+```sh
+python render.py --pdf-profile pdfua2 --out output-ua2
+```
+
+Both the component and exported HTML/CSS paths use that profile. The render
+report records it; the HTML exports retain the same semantic content and styling.
+
## Styling and HTML export
`class_name="program-card"` emits the authored CSS class. Its appearance comes
@@ -68,53 +78,70 @@ styled = document.to_html(include_styles=True, a11y_mode="raise")
unstyled = document.to_html(include_styles=False, a11y_mode="raise")
```
-[Open the unstyled HTML](../assets/accessible-showcase-alpha6/guide-unstyled.html) to see
+[Open the unstyled HTML](../assets/accessible-showcase-alpha7/guide-unstyled.html) to see
the semantic document without CSS. It retains authored class names, text, alt
text and the link, so another application can supply its own presentation.
The render command copies the local fonts and illustration beside both exports.
## What was checked
-The final two-page PDF passed **veraPDF 1.30.3's PDF/UA-1 validation profile:
-106 passed rules, 5,512 passed checks, zero failures and no diagnostic warnings**. The receipt identifies
-the exact PDF by SHA-256.
+Both final two-page specimens passed **veraPDF 1.30.3**, with zero failed
+checks and empty stderr. Each receipt identifies its exact PDF by SHA-256.
+
+| Specimen | Passed rules | Passed checks |
+| --- | ---: | ---: |
+| PDF/UA-1 | 106 | 5,512 |
+| PDF/UA-2 | 1,728 | 8,147 |
| Check | Result |
| --- | --- |
| Actual PDF structure and marked-content text | One H1, continuous H2/H3 hierarchy, expected logical reading order |
| Marked-content boundaries | Balanced tags, no nested IDs; retained faulty PDF rejected |
| Image | One tagged figure with the authored description |
-| Schedule | One table, three column headers with `/Scope /Column`, nine data cells |
+| Schedule | One table with one directly owned caption, three column headers with `/Scope /Column`, nine data cells |
| Lists | Two lists containing six items |
| Fonts, metadata and geometry | Embedded fonts, English language, document title, two A4 pages; no overflow or missing-glyph/font-substitution gate failures |
-| Reproduction | Identical PDF bytes on repeat rendering and through the exported HTML/CSS path |
+| Reproduction | Identical PDF bytes on repeat rendering and through the exported HTML/CSS path, checked separately for each profile |
| Visual review | Both pages inspected in native and independent PDFium previews |
| Styled HTML | Checked at 1440, 390 and 320 pixels; no horizontal overflow; keyboard link focus verified |
| Unstyled HTML | Semantic elements and authored classes retained; no CSS or inline style declarations |
| Authored text colors | Minimum contrast ratio 7.45:1 across the design's text/background pairs |
-[Validation receipt](../assets/accessible-showcase-alpha6/validation.json) ·
-[veraPDF report](../assets/accessible-showcase-alpha6/pdfua1.verapdf.json) ·
-[PDF tag and reading-order report](../assets/accessible-showcase-alpha6/structure.json) ·
-[Verifier failure control](../assets/accessible-showcase-alpha6/verification-guard.json) ·
-[Browser checks](../assets/accessible-showcase-alpha6/browser-verification.json) ·
-[Source manifest](../assets/accessible-showcase-alpha6/source.json)
+[PDF/UA-1 receipt](../assets/accessible-showcase-alpha7/validation.json) ·
+[PDF/UA-1 veraPDF report](../assets/accessible-showcase-alpha7/pdfua1.verapdf.json) ·
+[PDF/UA-2 receipt](../assets/accessible-showcase-alpha7/pdfua2/validation.json) ·
+[PDF/UA-2 veraPDF report](../assets/accessible-showcase-alpha7/pdfua2/pdfua2.verapdf.json) ·
+[PDF tag and reading-order report](../assets/accessible-showcase-alpha7/structure.json) ·
+[Verifier failure control](../assets/accessible-showcase-alpha7/verification-guard.json) ·
+[Visual review](../assets/accessible-showcase-alpha7/visual-review.json) ·
+[Browser checks](../assets/accessible-showcase-alpha7/browser-verification.json) ·
+[Source manifest](../assets/accessible-showcase-alpha7/source.json)
These checks are **not a screen-reader user test or blanket WCAG certification**.
The kit includes `verify.py` to inspect the actual output again after edits.
-It rejects nested marked-content IDs and unbalanced tag boundaries even without
-veraPDF. Alpha 5 corrects the [overlapping content tags in rich table
-cells](https://github.com/fullbleed-engine/fullbleed-official/issues/97). The program
-column now returns `Strong(row["program"])` directly, preserving that component's
-content inside its table cell. The kit still rejects the retained defective
-alpha 4 PDF. Alpha 6 also corrects the oversized-cell fixture's column-consistency
-failure: both direct and styled-export PDFs now pass the PDF/UA-1 checks.
-Caption-containment findings in PDF/UA-2 remain a separate limitation.
-For an external check, run [veraPDF's UA-1 profile](https://docs.verapdf.org/cli/validation/)
+It rejects nested marked-content IDs, unbalanced tag boundaries and duplicate
+or detached captions even without veraPDF. Alpha 7 keeps the schedule caption
+directly under its Table, with its text and reading order preserved. The verifier
+rejects both the retained nested-MCID defect and the published alpha6
+duplicate-caption specimen.
+
+To check your PDF/UA-2 output with the independent reader:
+
+```sh
+python -m pip install -r requirements-check.txt
+python verify.py --pdf-profile pdfua2 --out output-ua2
+```
+
+The program column returns `Strong(row["program"])` directly, retaining rich
+component content inside its table cell. The earlier rich-cell tagging and
+pagination corrections remain included.
+
+For an external check, run the matching [veraPDF profile](https://docs.verapdf.org/cli/validation/)
on the final PDF:
```sh
verapdf --format json -f ua1 output/common-ground-guide.pdf
+verapdf --format json -f ua2 output-ua2/common-ground-guide.pdf
```
The pinned runtime exports HTML links as plain PDF text, so this document also
@@ -129,3 +156,5 @@ For another way to author the same design, see the
The [alpha 4 guide](https://github.com/fullbleed-engine/docs/blob/fff03de311627b15291a527996b94fd3626cd79d/docs/guides/accessible-showcase.md), [PDF](../assets/accessible-showcase/common-ground-guide.pdf) and [source kit](../assets/accessible-showcase/project.zip) retain their original bytes, validation receipt and plain-text table workaround.
The [alpha 5 guide](https://github.com/fullbleed-engine/docs/blob/9aa0b2d9b26d398df58f55c8dd3b240a2457e96e/docs/guides/accessible-showcase.md) retains its original runtime pins and download links.
+
+The [alpha 6 guide](https://github.com/fullbleed-engine/docs/blob/2a4f3ca9cca015f59e53e8aac6e1721ebb67922a/docs/guides/accessible-showcase.md), [PDF](../assets/accessible-showcase-alpha6/common-ground-guide.pdf), and [source kit](../assets/accessible-showcase-alpha6/project.zip) retain their original bytes and PDF/UA-1 receipt.
diff --git a/docs/guides/bootstrap-components-preview.md b/docs/guides/bootstrap-components-preview.md
index a178d93..c4ca66f 100644
--- a/docs/guides/bootstrap-components-preview.md
+++ b/docs/guides/bootstrap-components-preview.md
@@ -269,7 +269,9 @@ can continue across pages. The retained checks cover visible page margins,
repeated headings, and content preserved through direct rendering, prepared
documents, and both HTML exports. Rich-cell continuation with middle or bottom
alignment remains unsupported; layout errors retain the component source ID.
-PDF/UA-2 caption containment remains an open issue, so these checks do not
+This alpha 6 kit retains the earlier PDF/UA-2 caption-containment limitation.
+The [alpha 7 accessible showcase](accessible-showcase.md) includes the correction
+and retains validation for both PDF/UA profiles. These specimen checks do not
establish general table conformance.
Document class rules, inherited colors and named-part opacity reach supported
diff --git a/examples/accessible-showcase-alpha7/.gitignore b/examples/accessible-showcase-alpha7/.gitignore
new file mode 100644
index 0000000..4512242
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/.gitignore
@@ -0,0 +1,3 @@
+output/
+output-*/
+__pycache__/
diff --git a/examples/accessible-showcase-alpha7/LICENSE b/examples/accessible-showcase-alpha7/LICENSE
new file mode 100644
index 0000000..4c99bbd
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Fullbleed Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/examples/accessible-showcase-alpha7/README.md b/examples/accessible-showcase-alpha7/README.md
new file mode 100644
index 0000000..26d0d64
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/README.md
@@ -0,0 +1,121 @@
+# Common Ground: an accessible authored showcase
+
+A two-page community-program guide built with reusable Python components, a
+local SVG illustration and embedded fonts. All program information is fictional.
+The source contains no HTML templates or raw HTML strings.
+
+This source uses the experimental component API in **Fullbleed 2.6.0a7**.
+Pin the version supplied with each downloaded source kit; the API may change
+before stabilization. Historical downloads retain their original pins.
+
+## Render and edit
+
+```sh
+python -m pip install -r requirements.txt
+python render.py
+```
+
+Open `output/common-ground-guide.pdf`. The output directory also contains a
+responsive `guide.html`, semantic `guide-unstyled.html`, the PDF's separate
+`source.html` and `source.css`, PNG previews and diagnostic reports. Keep the
+exported `fonts/` folder and `garden.svg` beside the HTML files.
+
+The default PDF profile is PDF/UA-1. To render the same design as PDF/UA-2:
+
+```sh
+python render.py --pdf-profile pdfua2 --out output-ua2
+```
+
+Both authoring paths use the selected profile. `render-report.json` records it.
+
+- `guide.py` composes `ProgramCard`, `ProgramSchedule`, headings, lists and an image.
+- `SCHEDULE` supplies the table records. `Column` supplies labels and cell callbacks.
+- `guide.css` provides the document design. For example,
+ `class_name="program-card"` emits a class selected by `.program-card`; it does
+ not discover or load a stylesheet. `Document(styles=...)` loads that CSS.
+- `browser.css` adds the responsive browser presentation only. It is deliberately
+ excluded from the PDF source. `Page(...)` owns the PDF's A4 geometry.
+- `render.py` registers local fonts and artwork and applies the selected PDF profile.
+
+```python
+from guide import build_document, ProgramCard
+
+card = ProgramCard("Grow something", "A garden we tend together.")
+document = build_document(browser=True)
+styled_html = document.to_html(include_styles=True, a11y_mode="raise")
+semantic_html = document.to_html(include_styles=False, a11y_mode="raise")
+```
+
+The unstyled export keeps semantic elements, text, alt text and authored class
+names while removing CSS and inline style declarations. It can be styled by a
+different host application.
+
+## Accessibility and verification
+
+The guide authors a document title and language, one H1 and a continuous H2/H3
+hierarchy, an informative image description, a captioned table with scoped column
+headers, and two semantic lists. Column content remains in authored reading
+order. Colors are not used as the only way to identify any information.
+
+`render.py` checks deterministic PDF replay, exact PDF byte parity with the
+exported HTML/CSS path, two-page pagination, and native overflow, missing-glyph
+and font-substitution gates. `verify.py` uses an independent PDF parser to inspect
+the resulting structure tree and text associated with its marked-content IDs:
+
+```sh
+python -m pip install -r requirements-check.txt
+python verify.py
+```
+
+This check also rejects nested marked-content IDs, unbalanced tag boundaries and
+duplicate or detached table captions without requiring an external validator.
+It checks the caption's text and its place in the table's reading order.
+The schedule uses a Strong component
+inside its program cells. Alpha 5 carries the correction for the
+[table-cell tagging issue](https://github.com/fullbleed-engine/fullbleed-official/issues/97)
+that required plain text in the alpha 4 specimen. Edited documents must pass
+the same saved-PDF check. Alpha 7 also attaches the schedule caption to its Table
+in PDF/UA-2, including correct caption text and reading order across page breaks.
+
+For the PDF/UA-2 output, use the same profile and output directory when checking:
+
+```sh
+python verify.py --pdf-profile pdfua2 --out output-ua2
+```
+
+For an external standards check, install [veraPDF](https://verapdf.org/software/)
+and run the matching profile against the **final** PDF:
+
+```sh
+verapdf --format json -f ua1 output/common-ground-guide.pdf
+verapdf --format json -f ua2 output-ua2/common-ground-guide.pdf
+```
+
+Alternatively, `python verify.py --verapdf-cp PATH_TO_CLASSPATH` runs the
+`org.verapdf.apps.GreenfieldCliWrapper` Java entry point. Add
+`--pdf-profile pdfua2 --out output-ua2` for that output. The script retains the
+profile-specific JSON and stderr and requires a compliant result with empty
+stderr. Without the classpath argument, it explicitly records that external
+validation did not run. Java and the independent readers are verification tools,
+not dependencies of the document renderer.
+
+Validation receipts identify the exact PDF by SHA-256. Review both rendered
+pages along with their tag order, image description and table header scope.
+These checks are **not a screen-reader user test or blanket WCAG certification**.
+Changed content or styling needs fresh rendering, inspection and validation.
+
+Two current runtime boundaries are visible in this example: HTML links become
+plain PDF text, so the destination is also written out; and decorative paragraph
+text cannot be reliably excluded using HTML ARIA attributes in this pinned alpha,
+so redundant folios and card numbers are omitted. The exported HTML retains its
+descriptively named, keyboard-focusable link.
+
+The composition has a fixed two-page print layout. It is a showcase to adapt,
+not an arbitrary-length program-directory template.
+
+## Assets and license
+
+Python, CSS and SVG source are MIT licensed. DM Serif Display is vendored under
+the SIL Open Font License; Inter comes from the pinned Fullbleed wheel and is
+copied beside the HTML for browser use. Both font licenses are in `fonts/`.
+The renderer records the exact font hashes in `output/render-report.json`.
diff --git a/examples/accessible-showcase-alpha7/SOURCE.json b/examples/accessible-showcase-alpha7/SOURCE.json
new file mode 100644
index 0000000..351acf7
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/SOURCE.json
@@ -0,0 +1,22 @@
+{
+ "repository": "https://github.com/fullbleed-engine/fullbleed-official",
+ "commit": "2be0f5a1e04088d129ea194803b05a04b526a889",
+ "path": "examples/accessible_showcase",
+ "runtime": "2.6.0a7",
+ "source_files": {
+ ".gitignore": "64e094a74cb73573fee01036fc1dd5a4d0ade22c2252264d437b370406e40278",
+ "LICENSE": "36a2115938f6e68cf0d87eccff7a6a3e57c17017e909cbd640d51aff1fa0c5a9",
+ "README.md": "9d431237c06af961c055bbd9df9e723d2479fc005133da3b7e153030e9171a5c",
+ "browser.css": "59876dbb2ddce6f7fc94a435584bc430ce714fc1cc6095adaa4f9e58351369a1",
+ "fonts/DMSerifDisplay-OFL.txt": "aa49c876a28d8c0b0a3027d6c25b9c607eee8c0fb96165011d42a6ace1408fe1",
+ "fonts/DMSerifDisplay-Regular.ttf": "8cc3643535edf039aa5d95440a8542735e9197e4f4b8d9303e980fefbf5ab616",
+ "fonts/Inter-OFL.txt": "5dd548d31a85f756e01d63e00d7faf1e324103ed3e9102fcbbabf2cc2db6dd39",
+ "garden.svg": "b55568259e14cca65522b69dc18c4987b399a1862ec9d99ed1b647b97996043c",
+ "guide.css": "29cb17b5b0992607a22258a43964828ce1d6ae31c0402d648ff7479fdb5c4e07",
+ "guide.py": "dfb0777721ce6eb2563941f05f9f1fc3d4dc8346672f20e405702267ee534837",
+ "render.py": "950c43070153997a6c6fe5c863a6519b705d07d2ca850dbc4932413d4214aaa9",
+ "requirements-check.txt": "4eadc7023aba8e289cb2e9b4f46eed8c350d9c55500f98c19d938010d46add8d",
+ "requirements.txt": "5a911ba7f5842c48fc5e844c219f36b2bbdfe7a71e54753a9a897f533a971b14",
+ "verify.py": "1add674fb7a9609072875e704d96a5e7d1cc6bf9f6cb977226332ec89a417942"
+ }
+}
diff --git a/examples/accessible-showcase-alpha7/browser.css b/examples/accessible-showcase-alpha7/browser.css
new file mode 100644
index 0000000..1516d1f
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/browser.css
@@ -0,0 +1,13 @@
+/* Only the styled browser export loads this responsive presentation layer. */
+@media screen {
+ body { background: #e1e5dd; }
+ .sheet { margin: 20px auto; }
+}
+@media screen and (max-width: 800px) {
+ .sheet { width: auto; height: auto; margin: 0; padding: 24px; }
+ .cover-title { font-size: 36pt; }
+ .garden { width: 100%; height: auto; }
+ .programs, .visit { grid-template-columns: 1fr !important; }
+ .schedule { table-layout: auto; }
+ .schedule th, .schedule td { width: auto !important; padding: 8px 5px; font-size: 9pt; }
+}
diff --git a/examples/accessible-showcase-alpha7/fonts/DMSerifDisplay-OFL.txt b/examples/accessible-showcase-alpha7/fonts/DMSerifDisplay-OFL.txt
new file mode 100644
index 0000000..aea163a
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/fonts/DMSerifDisplay-OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2014-2018 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. Copyright 2019 Google LLC.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+
+This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/examples/accessible-showcase-alpha7/fonts/DMSerifDisplay-Regular.ttf b/examples/accessible-showcase-alpha7/fonts/DMSerifDisplay-Regular.ttf
new file mode 100644
index 0000000..bc5ed0c
Binary files /dev/null and b/examples/accessible-showcase-alpha7/fonts/DMSerifDisplay-Regular.ttf differ
diff --git a/examples/accessible-showcase-alpha7/fonts/Inter-OFL.txt b/examples/accessible-showcase-alpha7/fonts/Inter-OFL.txt
new file mode 100644
index 0000000..909a6bf
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/fonts/Inter-OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/examples/accessible-showcase-alpha7/garden.svg b/examples/accessible-showcase-alpha7/garden.svg
new file mode 100644
index 0000000..31a0f3f
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/garden.svg
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/accessible-showcase-alpha7/guide.css b/examples/accessible-showcase-alpha7/guide.css
new file mode 100644
index 0000000..fc4500d
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/guide.css
@@ -0,0 +1,38 @@
+/* The same authored class names work in PDF and static HTML exports. */
+@font-face { font-family: Inter; src: url('fonts/Inter-Variable.ttf'); }
+@font-face { font-family: 'DM Serif Display'; src: url('fonts/DMSerifDisplay-Regular.ttf'); }
+html, body { margin: 0; padding: 0; }
+body { font-family: Inter; font-size: 10.5pt; line-height: 1.5; color: #17382e; }
+h1, h2, h3, p, ul { margin: 0; }
+h1, h2, h3 { font-weight: 400; }
+h1, h2 { font-family: 'DM Serif Display'; }
+h2 { font-size: 22pt; line-height: 1.15; margin-bottom: 11pt; }
+h3 { font-size: 11pt; font-weight: 700; line-height: 1.35; margin-bottom: 7pt; }
+strong { font-weight: 700; }
+.sheet { position: relative; width: 210mm; height: 296.9mm; padding: 15mm 17mm 17mm; background: #f8f5ec; break-after: page; }
+.sheet:last-child { break-after: auto; }
+.brand { font-family: 'DM Serif Display'; font-size: 21pt; line-height: 1.1; border-bottom: 1pt solid #17382e; padding-bottom: 10pt; margin-bottom: 21pt; }
+.eyebrow { font-size: 9pt; font-weight: 600; letter-spacing: .8pt; text-transform: uppercase; margin-bottom: 10pt; }
+.cover-title { font-size: 46pt; line-height: 1.03; max-width: 460pt; margin-bottom: 14pt; }
+.intro { font-size: 12pt; line-height: 1.55; max-width: 460pt; }
+.garden { display: block; width: 176mm; height: 53mm; margin: 14pt 0 18pt; }
+.programs { gap: 14pt; margin-bottom: 19pt; }
+.program-card { border-top: 1.5pt solid #a23e24; padding-top: 8pt; }
+.program-card p { font-size: 10pt; line-height: 1.5; }
+.welcome { padding: 14pt 17pt; background: #e3eadc; }
+.welcome h2 { font-size: 19pt; margin-bottom: 6pt; }
+.details .eyebrow { padding-top: 2pt; border-bottom: 1pt solid #17382e; padding-bottom: 12pt; margin-bottom: 24pt; }
+.page-title { font-size: 35pt; line-height: 1.08; margin-bottom: 13pt; }
+.schedule { margin: 16pt 0 20pt; font-size: 10pt; }
+.schedule caption { font-size: 9pt; color: #3c5548; padding-bottom: 10pt; }
+.schedule th { padding: 10pt 8pt; font-size: 9pt; color: #f8f5ec; background: #17382e; border-bottom: 1pt solid #17382e; }
+.schedule td { padding: 10pt 8pt; line-height: 1.5; border-bottom: .6pt solid #738577; }
+.schedule tbody tr:nth-child(even) { background: #e9eddf; }
+.visit { gap: 24pt; margin-bottom: 18pt; }
+ul { padding-left: 13pt; }
+li { padding-left: 2pt; margin-bottom: 6pt; }
+.closing { padding: 14pt 17pt; background: #17382e; color: #f8f5ec; margin-bottom: 18pt; }
+.closing h2 { font-size: 23pt; margin-bottom: 8pt; }
+.about h2 { font-size: 17pt; margin-bottom: 7pt; }
+.about p { font-size: 9pt; line-height: 1.5; margin-bottom: 8pt; }
+a { color: #17382e; text-decoration: underline; text-underline-offset: 2pt; }
diff --git a/examples/accessible-showcase-alpha7/guide.py b/examples/accessible-showcase-alpha7/guide.py
new file mode 100644
index 0000000..0b48d8d
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/guide.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+"""A component-authored, tagged community guide. Requires fullbleed==2.6.0a7."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+from fullbleed.ui.experimental import (
+ Box, Column, DataTable, Document, Grid, Heading, Image, Node,
+ Page, Paragraph, Section, Strong, component, mm,
+)
+
+ROOT = Path(__file__).resolve().parent
+TITLE = "Common Ground: A place to grow, a place to belong"
+IMAGE_ALT = (
+ "Illustration of a shared garden: three raised planting beds beside a broad, "
+ "level path, with a bench and a greenhouse."
+)
+SCHEDULE = (
+ {"program": "Garden together", "when": "Saturdays, 10–11:30 am",
+ "expect": "Plant, water or harvest. Seated tasks are available."},
+ {"program": "The shared table", "when": "Saturdays, noon–1 pm",
+ "expect": "Bring your lunch or share the seasonal soup. Ingredients are listed."},
+ {"program": "Make & mend", "when": "First Saturday, 2–3:30 pm",
+ "expect": "Bring a small repair or try a simple craft. Materials are provided."},
+)
+
+
+@component
+def ProgramCard(title, description):
+ """One reusable card, with its heading in the document's reading order."""
+ return Section(
+ Heading(title, level=3),
+ Paragraph(description),
+ class_name="program-card",
+ )
+
+
+@component
+def BulletList(*items):
+ # Node is the public escape hatch for semantic elements without a shortcut.
+ return Node("ul", tuple(Node("li", (item,)) for item in items))
+
+
+@component
+def TextLink(label, href):
+ return Node("a", (label,), (("href", href),))
+
+
+@component
+def ProgramSchedule(rows):
+ return DataTable(
+ rows,
+ columns=(
+ Column("Program", lambda row: Strong(row["program"]), width=mm(38)),
+ Column("When", lambda row: row["when"], width=mm(43)),
+ Column("What to expect", lambda row: row["expect"]),
+ ),
+ caption="Autumn 2026 · Weekly sessions at the Common Ground garden",
+ class_name="schedule",
+ )
+
+
+def build_document(*, browser=False):
+ styles = (ROOT / "guide.css").read_text(encoding="utf-8")
+ if browser:
+ styles += "\n" + (ROOT / "browser.css").read_text(encoding="utf-8")
+ return Document(
+ Section(
+ Paragraph("Common Ground", class_name="brand"),
+ Paragraph("Community field guide / Autumn 2026", class_name="eyebrow"),
+ Heading("A place to grow.", Node("br"), "A place to belong.", level=1,
+ class_name="cover-title"),
+ Paragraph(
+ "Good things happen when we make a little room for one another. "
+ "Come for a quiet hour, a shared meal or something new to learn.",
+ class_name="intro",
+ ),
+ Image(src="garden.svg", alt=IMAGE_ALT, class_name="garden"),
+ Heading("Find your starting point", level=2),
+ Grid(
+ ProgramCard("Grow something",
+ "A few seeds. A pair of hands. A garden we tend together."),
+ ProgramCard("Share a table",
+ "Pull up a chair for conversation and a warm bowl of soup."),
+ ProgramCard("Make it last",
+ "Swap a skill, mend a favorite thing or try a small project."),
+ columns=3, class_name="programs",
+ ),
+ Box(
+ Heading("Every visit starts with hello", level=2),
+ Paragraph("All sessions are free. No membership, equipment or experience is needed. "
+ "Come on your own or bring someone with you."),
+ class_name="welcome",
+ ),
+ class_name="sheet cover",
+ ),
+ Section(
+ Paragraph("Your next Saturday", class_name="eyebrow"),
+ Heading("Make room for a good day.", level=2, class_name="page-title"),
+ Paragraph("Stay for one session or linger a little longer. You decide how to take part.",
+ class_name="intro"),
+ ProgramSchedule(SCHEDULE),
+ Heading("Plan a comfortable visit", level=2),
+ Grid(
+ Section(
+ Heading("An easy arrival", level=3),
+ BulletList(
+ "Enter through the wide gate beside the greenhouse.",
+ "The main path is firm and level. Seating is available along the way.",
+ "A welcome volunteer can show you the garden and the accessible restroom.",
+ ),
+ ),
+ Section(
+ Heading("Choose your pace", level=3),
+ BulletList(
+ "Take part seated or standing, or simply watch.",
+ "Use the quiet corner whenever you would like a break.",
+ "Ask for written instructions or a demonstration. Both are welcome.",
+ ),
+ ),
+ columns=2, class_name="visit",
+ ),
+ Box(
+ Heading("A small first step is enough", level=2),
+ Paragraph("You do not have to know anyone, know what to say or stay all day. "
+ "There is a place for you here."),
+ class_name="closing",
+ ),
+ Section(
+ Heading("About this guide", level=2),
+ Paragraph("Common Ground and its programs are fictional. This sample shows how "
+ "a thoughtfully designed document can carry its structure into PDF and HTML."),
+ Paragraph(TextLink("Fullbleed documentation: docs.fullbleed.dev",
+ "https://docs.fullbleed.dev/")),
+ class_name="about",
+ ),
+ class_name="sheet details",
+ ),
+ title=TITLE,
+ lang="en-US",
+ page=Page(size="A4", margin=mm(0)),
+ styles=styles,
+ )
diff --git a/examples/accessible-showcase-alpha7/render.py b/examples/accessible-showcase-alpha7/render.py
new file mode 100644
index 0000000..86d1aba
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/render.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+"""Render and export the accessible showcase using only Fullbleed and stdlib."""
+
+from __future__ import annotations
+
+import argparse
+import hashlib
+from importlib import metadata, resources
+import json
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+
+import fullbleed
+from fullbleed.ui.experimental import Renderer
+from guide import ROOT, build_document
+
+
+def sha(path):
+ return hashlib.sha256(Path(path).read_bytes()).hexdigest()
+
+
+class GuideRenderer(Renderer):
+ """Register the same named illustration for each isolated engine."""
+
+ def engine_for(self, document):
+ engine = super().engine_for(document)
+ assets = fullbleed.AssetBundle()
+ assets.add_file(str(ROOT / "garden.svg"), "svg", name="garden.svg")
+ engine.register_bundle(assets)
+ return engine
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--out", type=Path, default=ROOT / "output")
+ parser.add_argument("--pdf-profile", choices=("pdfua1", "pdfua2"), default="pdfua1",
+ help="PDF/UA profile for both component and HTML/CSS output (default: pdfua1)")
+ args = parser.parse_args()
+ out = args.out.resolve()
+ out.mkdir(parents=True, exist_ok=True)
+ inter = Path(str(resources.files("fullbleed_assets").joinpath("fonts/Inter-Variable.ttf")))
+ serif = ROOT / "fonts/DMSerifDisplay-Regular.ttf"
+ fonts = (inter, serif)
+ document = build_document()
+ source = document.compile(a11y_mode="raise", target="pdf")
+ renderer = GuideRenderer(font_files=[str(font) for font in fonts], pdf_profile=args.pdf_profile)
+ prepared = document.prepare(renderer, a11y_mode="raise")
+ pdf = out / "common-ground-guide.pdf"
+ pdf.write_bytes(prepared.render_pdf())
+ replay = document.render_pdf(renderer, a11y_mode="raise")
+ if replay != pdf.read_bytes():
+ raise RuntimeError("PDF replay was not byte-for-byte deterministic")
+ (out / "source.html").write_text(source.html, encoding="utf-8", newline="\n")
+ (out / "source.css").write_text(source.css, encoding="utf-8", newline="\n")
+ browser_document = build_document(browser=True)
+ for styled, filename in ((True, "guide.html"), (False, "guide-unstyled.html")):
+ (out / filename).write_text(browser_document.to_html(include_styles=styled, a11y_mode="raise"),
+ encoding="utf-8", newline="\n")
+ shutil.copyfile(ROOT / "garden.svg", out / "garden.svg")
+ (out / "fonts").mkdir(exist_ok=True)
+ for font in fonts:
+ shutil.copyfile(font, out / "fonts" / font.name)
+ for license_file in (ROOT / "fonts").glob("*-OFL.txt"):
+ shutil.copyfile(license_file, out / "fonts" / license_file.name)
+ engine = renderer.engine_for(document)
+ previews = engine.render_finalized_pdf_image_pages_to_dir(str(pdf), str(out / "preview"), 144, "guide")
+ inspection = fullbleed.inspect_pdf(str(pdf))
+ for name, value in (("a11y-source.json", source.a11y_report),
+ ("inspection.json", inspection),
+ ("pagination.json", prepared.export_render_time_pagination_trace())):
+ (out / name).write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8")
+ # CLI diagnostics independently exercise the exported HTML/CSS authoring path.
+ command = [sys.executable, "-I", "-m", "fullbleed", "--json-only", "verify",
+ "--html", str(out / "source.html"), "--css", str(out / "source.css"),
+ "--document-title", document.title, "--document-lang", document.lang,
+ "--pdf-profile", args.pdf_profile, "--emit-pdf", str(out / "verified.pdf"),
+ "--fail-on", "overflow", "--fail-on", "missing-glyphs", "--fail-on", "font-subst"]
+ for asset in (*fonts, ROOT / "garden.svg"):
+ command.extend(("--asset", str(asset)))
+ checked = subprocess.run(command, capture_output=True, text=True, encoding="utf-8")
+ (out / "verify.json").write_text(checked.stdout, encoding="utf-8")
+ (out / "verify.stderr.txt").write_text(checked.stderr, encoding="utf-8")
+ verification = json.loads(checked.stdout)
+ parity = sha(pdf) == sha(out / "verified.pdf") if (out / "verified.pdf").exists() else False
+ result = dict(schema="fullbleed.accessible_showcase.v1",
+ ok=checked.returncode == 0 and bool(verification.get("ok")) and parity
+ and inspection.get("page_count") == 2,
+ runtime=metadata.version("fullbleed"), pdf_profile=args.pdf_profile,
+ pdf=pdf.name, sha256=sha(pdf),
+ page_count=inspection.get("page_count"), deterministic_replay=True,
+ html_css_pdf_byte_parity=parity, native_diagnostic_gate=verification.get("ok"),
+ previews=[Path(path).relative_to(out).as_posix() for path in previews],
+ fonts=[dict(file=font.name, sha256=sha(font)) for font in fonts],
+ external_pdfua_validation="Run veraPDF separately; see README.md.")
+ (out / "render-report.json").write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
+ print(json.dumps(result), flush=True)
+ return 0 if result["ok"] else 1
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/examples/accessible-showcase-alpha7/requirements-check.txt b/examples/accessible-showcase-alpha7/requirements-check.txt
new file mode 100644
index 0000000..16ec1fd
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/requirements-check.txt
@@ -0,0 +1,4 @@
+-r requirements.txt
+pypdf==6.19.0
+pypdfium2==5.14.0
+Pillow==12.3.0
diff --git a/examples/accessible-showcase-alpha7/requirements.txt b/examples/accessible-showcase-alpha7/requirements.txt
new file mode 100644
index 0000000..266be3b
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/requirements.txt
@@ -0,0 +1 @@
+fullbleed==2.6.0a7
diff --git a/examples/accessible-showcase-alpha7/verify.py b/examples/accessible-showcase-alpha7/verify.py
new file mode 100644
index 0000000..7aff569
--- /dev/null
+++ b/examples/accessible-showcase-alpha7/verify.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+"""Inspect the actual PDF's structure and optionally run a veraPDF PDF/UA profile."""
+
+from __future__ import annotations
+
+import argparse
+from collections import Counter, defaultdict
+import hashlib
+import json
+from pathlib import Path
+import subprocess
+
+from pypdf import PdfReader
+from guide import IMAGE_ALT, TITLE
+
+
+def normalize(text):
+ return " ".join(text.split())
+
+
+def inspect_structure(pdf):
+ reader = PdfReader(pdf)
+ catalog = reader.trailer["/Root"]
+ assert len(reader.pages) == 2
+ assert reader.metadata.title == TITLE
+ assert catalog["/Lang"] == "en-US"
+ assert catalog["/MarkInfo"]["/Marked"]
+ assert catalog["/ViewerPreferences"]["/DisplayDocTitle"]
+ chunks = defaultdict(list)
+ page_numbers = {}
+ artifact_text = []
+ for page_number, page in enumerate(reader.pages, 1):
+ page_numbers[page.indirect_reference.idnum] = page_number
+ stack = []
+
+ def before(op, args, cm, tm):
+ if op == b"BDC":
+ props = args[1].get_object()
+ mcid = int(props["/MCID"]) if "/MCID" in props else None
+ if mcid is not None:
+ parent = next((value for _, value in reversed(stack) if value is not None), None)
+ assert parent is None, f"Nested MCID on page {page_number}: {parent} -> {mcid}"
+ stack.append((str(args[0]), mcid))
+ elif op == b"BMC":
+ stack.append((str(args[0]), None))
+ elif op == b"EMC":
+ assert stack, f"Unmatched marked-content end on page {page_number}"
+ stack.pop()
+
+ def text(value, cm, tm, font, size):
+ if any(role == "/Artifact" for role, _ in stack):
+ artifact_text.append(value)
+ else:
+ for _, mcid in reversed(stack):
+ if mcid is not None:
+ chunks[(page_number, mcid)].append(value)
+ break
+
+ page.extract_text(visitor_operand_before=before, visitor_text=text)
+ assert not stack, f"Unclosed marked content on page {page_number}"
+ roles = Counter()
+ nodes = []
+ leaves = []
+
+ def walk(value, page_number=None, parent_role=None):
+ value = value.get_object() if hasattr(value, "get_object") else value
+ if isinstance(value, list):
+ return " ".join(walk(item, page_number, parent_role) for item in value)
+ if isinstance(value, int):
+ text = normalize("".join(chunks[(page_number, value)]))
+ leaves.append(dict(page=page_number, mcid=value, text=text))
+ return text
+ if not isinstance(value, dict):
+ return ""
+ if "/Pg" in value:
+ page_number = page_numbers[value.raw_get("/Pg").idnum]
+ role = str(value.get("/S", ""))
+ roles[role] += 1
+ children = value.get("/K", [])
+ children = children if isinstance(children, list) else [children]
+ child_roles = []
+ for child in children:
+ child = child.get_object() if hasattr(child, "get_object") else child
+ if isinstance(child, dict) and "/S" in child:
+ child_roles.append(str(child["/S"]))
+ node = dict(role=role, page=page_number, parent_role=parent_role, child_roles=child_roles)
+ nodes.append(node)
+ node["text"] = normalize(walk(value.get("/K", []), page_number, role))
+ if "/Alt" in value:
+ node["alt"] = str(value["/Alt"])
+ if "/A" in value:
+ node["attributes"] = {str(k): str(v) for k, v in value["/A"].items()}
+ return node["text"]
+
+ walk(catalog["/StructTreeRoot"]["/K"])
+ headings = [node for node in nodes if node["role"] in {"/H1", "/H2", "/H3"}]
+ expected = [
+ ("/H1", "A place to grow. A place to belong."),
+ ("/H2", "Find your starting point"),
+ ("/H3", "Grow something"), ("/H3", "Share a table"), ("/H3", "Make it last"),
+ ("/H2", "Every visit starts with hello"),
+ ("/H2", "Make room for a good day."), ("/H2", "Plan a comfortable visit"),
+ ("/H3", "An easy arrival"), ("/H3", "Choose your pace"),
+ ("/H2", "A small first step is enough"), ("/H2", "About this guide"),
+ ]
+ assert [(node["role"], node["text"]) for node in headings] == expected, headings
+ assert roles["/Figure"] == 1
+ assert next(node["alt"] for node in nodes if node["role"] == "/Figure") == IMAGE_ALT
+ assert roles["/Table"] == 1 and roles["/TH"] == 3 and roles["/TD"] == 9
+ assert roles["/Caption"] == 1, f"Expected one Caption; got {roles['/Caption']}"
+ caption = next(node for node in nodes if node["role"] == "/Caption")
+ table = next(node for node in nodes if node["role"] == "/Table")
+ assert caption["parent_role"] == "/Table", "Caption is not a direct Table child"
+ assert table["child_roles"][0] == "/Caption", "Caption is not first in table reading order"
+ assert caption["child_roles"] == ["/P"], "Caption should contain one paragraph"
+ assert caption["text"] == "Autumn 2026 · Weekly sessions at the Common Ground garden"
+ headers = [node for node in nodes if node["role"] == "/TH"]
+ assert [node["text"] for node in headers] == ["Program", "When", "What to expect"]
+ assert all(node["attributes"]["/Scope"] == "/Column" for node in headers)
+ assert roles["/L"] == 2 and roles["/LI"] == 6
+ reading = normalize(" ".join(leaf["text"] for leaf in leaves))
+ assert "Community field guide · 1" not in reading and "Community field guide · 2" not in reading
+ assert not any(leaf["text"] in {"01", "02", "03"} for leaf in leaves)
+ markers = ["Garden together", "The shared table", "Make & mend", "An easy arrival",
+ "Enter through the wide gate", "A welcome volunteer", "Choose your pace",
+ "Take part seated", "Ask for written instructions", "A small first step",
+ "About this guide", "Fullbleed documentation"]
+ positions = [reading.index(marker) for marker in markers]
+ assert positions == sorted(positions), "The logical reading order changed"
+ return dict(ok=True, title=reader.metadata.title, language=catalog["/Lang"],
+ page_count=len(reader.pages), roles=dict(roles), headings=headings,
+ nodes=nodes, reading_order=leaves, decorative_text_omitted=True,
+ marked_content_nesting_valid=True, caption_table_ownership_valid=True,
+ pdf_link_annotations=sum(len(page.get("/Annots", [])) for page in reader.pages),
+ scope="Actual PDF structure, MCID text and semantic order; not an assistive-technology user test.")
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--out", type=Path, default=Path(__file__).parent / "output")
+ parser.add_argument("--pdf-profile", choices=("pdfua1", "pdfua2"), default="pdfua1",
+ help="Profile to validate; use the same value passed to render.py")
+ parser.add_argument("--verapdf-cp", type=Path,
+ help="Classpath containing org.verapdf.apps.GreenfieldCliWrapper")
+ args = parser.parse_args()
+ out = args.out.resolve()
+ pdf = out / "common-ground-guide.pdf"
+ report = inspect_structure(pdf)
+ report["pdf_sha256"] = hashlib.sha256(pdf.read_bytes()).hexdigest()
+ (out / "structure.json").write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
+ summary = dict(ok=True, pdf_sha256=report["pdf_sha256"], pdf_profile=args.pdf_profile,
+ structure_passed=True,
+ external_validation="not run")
+ if args.verapdf_cp:
+ command = ["java", "-cp", str(args.verapdf_cp), "org.verapdf.apps.GreenfieldCliWrapper"]
+ result = subprocess.run(command + ["--format", "json", "--maxfailuresdisplayed", "-1",
+ "-f", args.pdf_profile.removeprefix("pdf"), str(pdf)], capture_output=True)
+ (out / f"{args.pdf_profile}.verapdf.json").write_bytes(result.stdout)
+ (out / f"{args.pdf_profile}.verapdf.stderr.txt").write_bytes(result.stderr)
+ parsed = json.loads(result.stdout)
+ validated = parsed["report"]["jobs"][0]["validationResult"][0]
+ assert result.returncode == 0 and validated["compliant"], validated
+ assert not result.stderr, result.stderr.decode(errors="replace")
+ summary["external_validation"] = dict(profile=validated["profileName"],
+ compliant=validated["compliant"],
+ stderr_bytes=len(result.stderr),
+ details=validated["details"],
+ build=parsed["report"]["buildInformation"])
+ (out / "validation.json").write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8")
+ print(json.dumps(summary), flush=True)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/build_accessible_showcase.py b/tools/build_accessible_showcase.py
index 87b5edd..89cbf32 100644
--- a/tools/build_accessible_showcase.py
+++ b/tools/build_accessible_showcase.py
@@ -36,5 +36,5 @@ def main(channel='alpha4'):
if __name__=='__main__':
parser=argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--channel',choices=('alpha4','alpha5','alpha6'),default='alpha4')
+ parser.add_argument('--channel',choices=('alpha4','alpha5','alpha6','alpha7'),default='alpha4')
main(parser.parse_args().channel)
diff --git a/tools/check_accessible_showcase_ui.py b/tools/check_accessible_showcase_ui.py
index bdff5fa..9d772e3 100644
--- a/tools/check_accessible_showcase_ui.py
+++ b/tools/check_accessible_showcase_ui.py
@@ -6,6 +6,7 @@
import json
from pathlib import Path
import threading
+from urllib.parse import urljoin
from playwright.sync_api import sync_playwright
ROOT=Path(__file__).resolve().parents[1]
@@ -26,9 +27,11 @@ def main():
server=ThreadingHTTPServer(('127.0.0.1',0),partial(Quiet,directory=str(args.site.resolve())))
threading.Thread(target=server.serve_forever,daemon=True).start()
base=f'http://127.0.0.1:{server.server_port}'
- assets=ROOT/'docs/assets/accessible-showcase-alpha6'
+ assets=ROOT/'docs/assets/accessible-showcase-alpha7'
manifest=json.loads((assets/'source.json').read_text(encoding='utf-8'))
validation=json.loads((assets/'validation.json').read_text(encoding='utf-8'))
+ validations={'pdfua1':validation,
+ 'pdfua2':json.loads((assets/'pdfua2/validation.json').read_text(encoding='utf-8'))}
cases=[]
try:
with sync_playwright() as p:
@@ -49,8 +52,11 @@ def main():
target=args.out/f'project-{width}.zip'
downloaded.value.save_as(str(target))
assert sha256(target.read_bytes()).hexdigest()==manifest['zip_sha256']
- pdf=context.request.get(base+'/assets/accessible-showcase-alpha6/common-ground-guide.pdf')
- assert pdf.status==200 and sha256(pdf.body()).hexdigest()==validation['pdf_sha256']
+ for profile,label in (('pdfua1','Read PDF/UA-1'),('pdfua2','Read PDF/UA-2')):
+ link=page.get_by_role('link',name=label,exact=True)
+ assert link.is_visible()
+ pdf=context.request.get(urljoin(page.url,link.get_attribute('href')))
+ assert pdf.status==200 and sha256(pdf.body()).hexdigest()==validations[profile]['pdf_sha256']
page.get_by_role('link',name='Open the responsive HTML',exact=True).click()
page.wait_for_load_state('networkidle');page.evaluate('document.fonts.ready')
assert page.evaluate('document.documentElement.scrollWidth<=innerWidth+1')
@@ -61,11 +67,12 @@ def main():
page.keyboard.press('Tab')
assert page.evaluate('document.activeElement.tagName')=='A'
page.screenshot(path=str(args.out/f'html-{width}.png'),full_page=True)
- page.goto(base+'/assets/accessible-showcase-alpha6/guide-unstyled.html',wait_until='networkidle')
+ page.goto(base+'/assets/accessible-showcase-alpha7/guide-unstyled.html',wait_until='networkidle')
assert page.locator('style,[style],link[rel=stylesheet]').count()==0
assert page.locator('.program-card').count()==3 and page.locator('th[scope=col]').count()==3
assert not errors,errors
cases.append({'width':width,'guide':True,'download':True,'pdf_hash':True,
+ 'pdf_profiles':['pdfua1','pdfua2'],
'responsive_html':True,'unstyled_html':True,'keyboard_link':True})
finally:
context.close()
@@ -74,7 +81,9 @@ def main():
browser.close()
finally:
if server: server.shutdown();server.server_close()
- report={'ok':True,'browser':version,'cases':cases,'pdf_sha256':validation['pdf_sha256']}
+ report={'ok':True,'browser':version,'cases':cases,'pdf_sha256':validation['pdf_sha256'],
+ 'zip_sha256':manifest['zip_sha256'],
+ 'pdfs':{profile:receipt['pdf_sha256'] for profile,receipt in validations.items()}}
(args.out/'verification.json').write_text(json.dumps(report,indent=2)+'\n',encoding='utf-8')
print(json.dumps(report))
diff --git a/tools/verify_accessible_showcase.py b/tools/verify_accessible_showcase.py
index 6f3a979..af92ddf 100644
--- a/tools/verify_accessible_showcase.py
+++ b/tools/verify_accessible_showcase.py
@@ -14,8 +14,11 @@
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--out',type=Path,default=ROOT/'target/accessible-showcase')
- parser.add_argument('--channel',choices=('alpha4','alpha5','alpha6'),default='alpha4')
+ parser.add_argument('--channel',choices=('alpha4','alpha5','alpha6','alpha7'),default='alpha4')
+ parser.add_argument('--pdf-profile',choices=('pdfua1','pdfua2'),default='pdfua1')
args=parser.parse_args()
+ if args.pdf_profile=='pdfua2' and args.channel!='alpha7':
+ parser.error('The PDF/UA-2 source specimen requires --channel alpha7')
suffix='' if args.channel=='alpha4' else '-'+args.channel
assets=ROOT/('docs/assets/accessible-showcase'+suffix)
archive_root='fullbleed-accessible-showcase'+suffix
@@ -34,8 +37,9 @@ def main():
assert path.is_relative_to(project.resolve())
path.parent.mkdir(parents=True,exist_ok=True);path.write_bytes(data)
output=out/'rendered'
+ profile_args=['--pdf-profile',args.pdf_profile] if args.channel=='alpha7' else []
for script in ('render.py','verify.py'):
- subprocess.run([sys.executable,str(project/script),'--out',str(output)],cwd=project,check=True)
+ subprocess.run([sys.executable,str(project/script),'--out',str(output),*profile_args],cwd=project,check=True)
structure=json.loads((output/'structure.json').read_text(encoding='utf-8'))
assert structure['marked_content_nesting_valid']
# This is the real pre-refinement PDF that passed the profile boolean but
@@ -52,14 +56,28 @@ def main():
assert rejection.startswith('Nested MCID on page 2:'),rejection
else:
raise AssertionError('The source kit accepted the known nested-MCID PDF')
+ caption_rejection=None
+ if args.channel=='alpha7':
+ caption_negative=ROOT/'docs/assets/accessible-showcase-alpha6/common-ground-guide.pdf'
+ assert sha256(caption_negative.read_bytes()).hexdigest()=='e59754b06ebd3229b35033c74b7c08175543396b48d9c152cc3419c7e58ba17d'
+ try:
+ inspect(caption_negative)
+ except AssertionError as error:
+ caption_rejection=str(error)
+ assert caption_rejection=='Expected one Caption; got 2',caption_rejection
+ else:
+ raise AssertionError('The source kit accepted the published alpha6 detached-caption PDF')
actual=sha256((output/'common-ground-guide.pdf').read_bytes()).hexdigest()
- validated=json.loads((assets/'validation.json').read_text(encoding='utf-8'))
+ specimen=assets if args.pdf_profile=='pdfua1' else assets/'pdfua2'
+ validated=json.loads((specimen/'validation.json').read_text(encoding='utf-8'))
assert validated['external_validation']['compliant']
assert validated['external_validation']['stderr_bytes']==0
- assert actual==validated['pdf_sha256']==sha256((assets/'common-ground-guide.pdf').read_bytes()).hexdigest()
+ assert actual==validated['pdf_sha256']==sha256((specimen/'common-ground-guide.pdf').read_bytes()).hexdigest()
report={'ok':True,'source_files':len(manifest['source_files']),'pdf_sha256':actual,
+ 'pdf_profile':args.pdf_profile,
'matches_externally_validated_pdf':True,'external_validator_rerun':False,
- 'marked_content_nesting_valid':True,'negative_control_rejection':rejection}
+ 'marked_content_nesting_valid':True,'negative_control_rejection':rejection,
+ 'caption_negative_control_rejection':caption_rejection}
(out/'verification.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps(report))