struct_tree_layer_builder.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. /* Copyright 2021 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. const PDF_ROLE_TO_HTML_ROLE = {
  16. // Document level structure types
  17. Document: null, // There's a "document" role, but it doesn't make sense here.
  18. DocumentFragment: null,
  19. // Grouping level structure types
  20. Part: "group",
  21. Sect: "group", // XXX: There's a "section" role, but it's abstract.
  22. Div: "group",
  23. Aside: "note",
  24. NonStruct: "none",
  25. // Block level structure types
  26. P: null,
  27. // H<n>,
  28. H: "heading",
  29. Title: null,
  30. FENote: "note",
  31. // Sub-block level structure type
  32. Sub: "group",
  33. // General inline level structure types
  34. Lbl: null,
  35. Span: null,
  36. Em: null,
  37. Strong: null,
  38. Link: "link",
  39. Annot: "note",
  40. Form: "form",
  41. // Ruby and Warichu structure types
  42. Ruby: null,
  43. RB: null,
  44. RT: null,
  45. RP: null,
  46. Warichu: null,
  47. WT: null,
  48. WP: null,
  49. // List standard structure types
  50. L: "list",
  51. LI: "listitem",
  52. LBody: null,
  53. // Table standard structure types
  54. Table: "table",
  55. TR: "row",
  56. TH: "columnheader",
  57. TD: "cell",
  58. THead: "columnheader",
  59. TBody: null,
  60. TFoot: null,
  61. // Standard structure type Caption
  62. Caption: null,
  63. // Standard structure type Figure
  64. Figure: "figure",
  65. // Standard structure type Formula
  66. Formula: null,
  67. // standard structure type Artifact
  68. Artifact: null,
  69. };
  70. const HEADING_PATTERN = /^H(\d+)$/;
  71. class StructTreeLayerBuilder {
  72. #treeDom = undefined;
  73. get renderingDone() {
  74. return this.#treeDom !== undefined;
  75. }
  76. render(structTree) {
  77. if (this.#treeDom !== undefined) {
  78. return this.#treeDom;
  79. }
  80. const treeDom = this.#walk(structTree);
  81. treeDom?.classList.add("structTree");
  82. return (this.#treeDom = treeDom);
  83. }
  84. #setAttributes(structElement, htmlElement) {
  85. if (structElement.alt !== undefined) {
  86. htmlElement.setAttribute("aria-label", structElement.alt);
  87. }
  88. if (structElement.id !== undefined) {
  89. htmlElement.setAttribute("aria-owns", structElement.id);
  90. }
  91. if (structElement.lang !== undefined) {
  92. htmlElement.setAttribute("lang", structElement.lang);
  93. }
  94. }
  95. #walk(node) {
  96. if (!node) {
  97. return null;
  98. }
  99. const element = document.createElement("span");
  100. if ("role" in node) {
  101. const { role } = node;
  102. const match = role.match(HEADING_PATTERN);
  103. if (match) {
  104. element.setAttribute("role", "heading");
  105. element.setAttribute("aria-level", match[1]);
  106. } else if (PDF_ROLE_TO_HTML_ROLE[role]) {
  107. element.setAttribute("role", PDF_ROLE_TO_HTML_ROLE[role]);
  108. }
  109. }
  110. this.#setAttributes(node, element);
  111. if (node.children) {
  112. if (node.children.length === 1 && "id" in node.children[0]) {
  113. // Often there is only one content node so just set the values on the
  114. // parent node to avoid creating an extra span.
  115. this.#setAttributes(node.children[0], element);
  116. } else {
  117. for (const kid of node.children) {
  118. element.append(this.#walk(kid));
  119. }
  120. }
  121. }
  122. return element;
  123. }
  124. }
  125. export { StructTreeLayerBuilder };