Commit 1c17a550 authored by Dominic Mazzoni's avatar Dominic Mazzoni Committed by Commit Bot

Set PDF structure element node types by name, and set heading levels.

Previously we used an enum to select the structure element node type
when building a tagged PDF. Update this to set the node type by name
(as a string), so that Skia doesn't need to maintain the set of all
possible node types.

Also, export the appropriate specific type for headings with heading
levels where possible, just like in HTML (H1 through H6). This
requires plumbing through one additional accessibility attribute
from blink_ax_tree_source.cc.

Bug: 1100698, 1039816
Change-Id: I8189d6dac41c29cbe8662076b752a9b44901e5c8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2276580
Commit-Queue: Dominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Reviewed-by: default avatarEric Seckler <eseckler@chromium.org>
Cr-Commit-Position: refs/heads/master@{#784456}
parent ed8ccb41
...@@ -562,6 +562,12 @@ void BlinkAXTreeSource::SerializeNode(WebAXObject src, ...@@ -562,6 +562,12 @@ void BlinkAXTreeSource::SerializeNode(WebAXObject src,
if (accessibility_mode_.has_mode(ui::AXMode::kScreenReader) || if (accessibility_mode_.has_mode(ui::AXMode::kScreenReader) ||
accessibility_mode_.has_mode(ui::AXMode::kPDF)) { accessibility_mode_.has_mode(ui::AXMode::kPDF)) {
// Heading level.
if (ui::IsHeading(dst->role) && src.HeadingLevel()) {
dst->AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel,
src.HeadingLevel());
}
SerializeListAttributes(src, dst); SerializeListAttributes(src, dst);
SerializeTableAttributes(src, dst); SerializeTableAttributes(src, dst);
} }
...@@ -1121,11 +1127,7 @@ void BlinkAXTreeSource::SerializeOtherScreenReaderAttributes( ...@@ -1121,11 +1127,7 @@ void BlinkAXTreeSource::SerializeOtherScreenReaderAttributes(
src.ErrorMessage().AxID()); src.ErrorMessage().AxID());
} }
if (ui::IsHeading(dst->role) && src.HeadingLevel()) { if (ui::SupportsHierarchicalLevel(dst->role) && src.HierarchicalLevel()) {
dst->AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel,
src.HeadingLevel());
} else if (ui::SupportsHierarchicalLevel(dst->role) &&
src.HierarchicalLevel()) {
dst->AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel, dst->AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel,
src.HierarchicalLevel()); src.HierarchicalLevel());
} }
......
...@@ -522,7 +522,7 @@ HEADLESS_ASYNC_DEVTOOLED_TEST_F(HeadlessWebContentsPDFPageSizeRoundingTest); ...@@ -522,7 +522,7 @@ HEADLESS_ASYNC_DEVTOOLED_TEST_F(HeadlessWebContentsPDFPageSizeRoundingTest);
const char kExpectedStructTreeJSON[] = R"({ const char kExpectedStructTreeJSON[] = R"({
"type": "Document", "type": "Document",
"~children": [ { "~children": [ {
"type": "H", "type": "H1",
"~children": [ { "~children": [ {
"type": "NonStruct" "type": "NonStruct"
} ] } ]
...@@ -573,6 +573,11 @@ const char kExpectedStructTreeJSON[] = R"({ ...@@ -573,6 +573,11 @@ const char kExpectedStructTreeJSON[] = R"({
} ] } ]
} ] } ]
} ] } ]
}, {
"type": "H2",
"~children": [ {
"type": "NonStruct"
} ]
}, { }, {
"type": "Div", "type": "Div",
"~children": [ { "~children": [ {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<td>Cell 2</td> <td>Cell 2</td>
</tr> </tr>
</table> </table>
<h2>Subheading</h2>
<div> <div>
<img src="svg_example_image.png" alt="Car at the beach"> <img src="svg_example_image.png" alt="Car at the beach">
</div> </div>
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "printing/common/metafile_utils.h" #include "printing/common/metafile_utils.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "printing/buildflags/buildflags.h" #include "printing/buildflags/buildflags.h"
#include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkCanvas.h"
...@@ -20,6 +21,22 @@ ...@@ -20,6 +21,22 @@
namespace { namespace {
#if BUILDFLAG(ENABLE_TAGGED_PDF) #if BUILDFLAG(ENABLE_TAGGED_PDF)
// Table 333 in PDF 32000-1:2008 spec, section 14.8.4.2
const char kPDFStructureTypeDocument[] = "Document";
const char kPDFStructureTypeParagraph[] = "P";
const char kPDFStructureTypeDiv[] = "Div";
const char kPDFStructureTypeHeading[] = "H";
const char kPDFStructureTypeList[] = "L";
const char kPDFStructureTypeListItemLabel[] = "Lbl";
const char kPDFStructureTypeListItemBody[] = "LI";
const char kPDFStructureTypeTable[] = "Table";
const char kPDFStructureTypeTableRow[] = "TR";
const char kPDFStructureTypeTableHeader[] = "TH";
const char kPDFStructureTypeTableCell[] = "TD";
const char kPDFStructureTypeFigure[] = "Figure";
const char kPDFStructureTypeNonStruct[] = "NonStruct";
// Standard attribute owners from PDF 32000-1:2008 spec, section 14.8.5.2 // Standard attribute owners from PDF 32000-1:2008 spec, section 14.8.5.2
// (Attribute owners are kind of like "categories" for structure node // (Attribute owners are kind of like "categories" for structure node
// attributes.) // attributes.)
...@@ -32,6 +49,17 @@ const char kPDFTableCellRowSpanAttribute[] = "RowSpan"; ...@@ -32,6 +49,17 @@ const char kPDFTableCellRowSpanAttribute[] = "RowSpan";
const char kPDFTableHeaderScopeAttribute[] = "Scope"; const char kPDFTableHeaderScopeAttribute[] = "Scope";
const char kPDFTableHeaderScopeColumn[] = "Column"; const char kPDFTableHeaderScopeColumn[] = "Column";
const char kPDFTableHeaderScopeRow[] = "Row"; const char kPDFTableHeaderScopeRow[] = "Row";
SkString GetHeadingStructureType(int heading_level) {
// From Table 333 in PDF 32000-1:2008 spec, section 14.8.4.2,
// "H1"..."H6" are valid structure types.
if (heading_level >= 1 && heading_level <= 6)
return SkString(base::StringPrintf("H%d", heading_level).c_str());
// If we don't have a valid heading level, use the generic heading role.
return SkString(kPDFStructureTypeHeading);
}
#endif // BUILDFLAG(ENABLE_TAGGED_PDF) #endif // BUILDFLAG(ENABLE_TAGGED_PDF)
SkTime::DateTime TimeToSkTime(base::Time time) { SkTime::DateTime TimeToSkTime(base::Time time) {
...@@ -69,47 +97,47 @@ bool RecursiveBuildStructureTree(const ui::AXNode* ax_node, ...@@ -69,47 +97,47 @@ bool RecursiveBuildStructureTree(const ui::AXNode* ax_node,
tag->fNodeId = ax_node->GetIntAttribute(ax::mojom::IntAttribute::kDOMNodeId); tag->fNodeId = ax_node->GetIntAttribute(ax::mojom::IntAttribute::kDOMNodeId);
switch (ax_node->data().role) { switch (ax_node->data().role) {
case ax::mojom::Role::kRootWebArea: case ax::mojom::Role::kRootWebArea:
tag->fType = SkPDF::DocumentStructureType::kDocument; tag->fTypeString = kPDFStructureTypeDocument;
break; break;
case ax::mojom::Role::kParagraph: case ax::mojom::Role::kParagraph:
tag->fType = SkPDF::DocumentStructureType::kP; tag->fTypeString = kPDFStructureTypeParagraph;
break; break;
case ax::mojom::Role::kGenericContainer: case ax::mojom::Role::kGenericContainer:
tag->fType = SkPDF::DocumentStructureType::kDiv; tag->fTypeString = kPDFStructureTypeDiv;
break; break;
case ax::mojom::Role::kHeading: case ax::mojom::Role::kHeading:
// TODO(dmazzoni): heading levels. https://crbug.com/1039816 tag->fTypeString = GetHeadingStructureType(ax_node->GetIntAttribute(
tag->fType = SkPDF::DocumentStructureType::kH; ax::mojom::IntAttribute::kHierarchicalLevel));
break; break;
case ax::mojom::Role::kList: case ax::mojom::Role::kList:
tag->fType = SkPDF::DocumentStructureType::kL; tag->fTypeString = kPDFStructureTypeList;
break; break;
case ax::mojom::Role::kListMarker: case ax::mojom::Role::kListMarker:
tag->fType = SkPDF::DocumentStructureType::kLbl; tag->fTypeString = kPDFStructureTypeListItemLabel;
break; break;
case ax::mojom::Role::kListItem: case ax::mojom::Role::kListItem:
tag->fType = SkPDF::DocumentStructureType::kLI; tag->fTypeString = kPDFStructureTypeListItemBody;
break; break;
case ax::mojom::Role::kTable: case ax::mojom::Role::kTable:
tag->fType = SkPDF::DocumentStructureType::kTable; tag->fTypeString = kPDFStructureTypeTable;
break; break;
case ax::mojom::Role::kRow: case ax::mojom::Role::kRow:
tag->fType = SkPDF::DocumentStructureType::kTR; tag->fTypeString = kPDFStructureTypeTableRow;
break; break;
case ax::mojom::Role::kColumnHeader: case ax::mojom::Role::kColumnHeader:
tag->fType = SkPDF::DocumentStructureType::kTH; tag->fTypeString = kPDFStructureTypeTableHeader;
tag->fAttributes.appendName(kPDFTableAttributeOwner, tag->fAttributes.appendName(kPDFTableAttributeOwner,
kPDFTableHeaderScopeAttribute, kPDFTableHeaderScopeAttribute,
kPDFTableHeaderScopeColumn); kPDFTableHeaderScopeColumn);
break; break;
case ax::mojom::Role::kRowHeader: case ax::mojom::Role::kRowHeader:
tag->fType = SkPDF::DocumentStructureType::kTH; tag->fTypeString = kPDFStructureTypeTableHeader;
tag->fAttributes.appendName(kPDFTableAttributeOwner, tag->fAttributes.appendName(kPDFTableAttributeOwner,
kPDFTableHeaderScopeAttribute, kPDFTableHeaderScopeAttribute,
kPDFTableHeaderScopeRow); kPDFTableHeaderScopeRow);
break; break;
case ax::mojom::Role::kCell: { case ax::mojom::Role::kCell: {
tag->fType = SkPDF::DocumentStructureType::kTD; tag->fTypeString = kPDFStructureTypeTableCell;
// Append an attribute consisting of the string IDs of all of the // Append an attribute consisting of the string IDs of all of the
// header cells that correspond to this table cell. // header cells that correspond to this table cell.
...@@ -128,7 +156,7 @@ bool RecursiveBuildStructureTree(const ui::AXNode* ax_node, ...@@ -128,7 +156,7 @@ bool RecursiveBuildStructureTree(const ui::AXNode* ax_node,
} }
case ax::mojom::Role::kFigure: case ax::mojom::Role::kFigure:
case ax::mojom::Role::kImage: { case ax::mojom::Role::kImage: {
tag->fType = SkPDF::DocumentStructureType::kFigure; tag->fTypeString = kPDFStructureTypeFigure;
std::string alt = std::string alt =
ax_node->GetStringAttribute(ax::mojom::StringAttribute::kName); ax_node->GetStringAttribute(ax::mojom::StringAttribute::kName);
tag->fAlt = SkString(alt.c_str()); tag->fAlt = SkString(alt.c_str());
...@@ -138,11 +166,11 @@ bool RecursiveBuildStructureTree(const ui::AXNode* ax_node, ...@@ -138,11 +166,11 @@ bool RecursiveBuildStructureTree(const ui::AXNode* ax_node,
// Currently we're only marking text content, so we can't generate // Currently we're only marking text content, so we can't generate
// a nonempty structure tree unless we have at least one kStaticText // a nonempty structure tree unless we have at least one kStaticText
// node in the tree. // node in the tree.
tag->fType = SkPDF::DocumentStructureType::kNonStruct; tag->fTypeString = kPDFStructureTypeNonStruct;
valid = true; valid = true;
break; break;
default: default:
tag->fType = SkPDF::DocumentStructureType::kNonStruct; tag->fTypeString = kPDFStructureTypeNonStruct;
} }
if (ui::IsCellOrTableHeader(ax_node->data().role)) { if (ui::IsCellOrTableHeader(ax_node->data().role)) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment