Transform high level headings to HTML (#5525)

This commit is contained in:
Johann Birnick 2024-12-16 10:22:00 -08:00 committed by GitHub
parent 8b1e0d3a23
commit 75273937f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 33 additions and 9 deletions

View File

@ -122,8 +122,8 @@ impl HtmlTag {
let bytes = string.as_bytes();
let mut i = 0;
while i < bytes.len() {
if !bytes[i].is_ascii_alphanumeric() {
panic!("constant tag name must be ASCII alphanumeric");
if !bytes[i].is_ascii() || !charsets::is_valid_in_tag_name(bytes[i] as char) {
panic!("not all characters are valid in a tag name");
}
i += 1;
}
@ -220,8 +220,10 @@ impl HtmlAttr {
let bytes = string.as_bytes();
let mut i = 0;
while i < bytes.len() {
if !bytes[i].is_ascii_alphanumeric() {
panic!("constant attribute name must be ASCII alphanumeric");
if !bytes[i].is_ascii()
|| !charsets::is_valid_in_attribute_name(bytes[i] as char)
{
panic!("not all characters are valid in an attribute name");
}
i += 1;
}
@ -621,5 +623,9 @@ pub mod attr {
href
name
value
role
}
#[allow(non_upper_case_globals)]
pub const aria_level: HtmlAttr = HtmlAttr::constant("aria-level");
}

View File

@ -1,14 +1,15 @@
use std::num::NonZeroUsize;
use ecow::eco_format;
use typst_utils::NonZeroExt;
use crate::diag::SourceResult;
use crate::diag::{warning, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
Styles, Synthesize, TargetElem,
};
use crate::html::{tag, HtmlElem};
use crate::html::{attr, tag, HtmlElem};
use crate::introspection::{
Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
};
@ -272,9 +273,26 @@ impl Show for Packed<HeadingElem> {
// Meanwhile, a level 1 Typst heading is a section heading. For this
// reason, levels are offset by one: A Typst level 1 heading becomes
// a `<h2>`.
let level = self.resolve_level(styles);
let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level.get().min(5) - 1];
HtmlElem::new(t).with_body(Some(realized)).pack().spanned(span)
let level = self.resolve_level(styles).get();
if level >= 6 {
engine.sink.warn(warning!(span,
"heading of level {} was transformed to \
<div role=\"heading\" aria-level=\"{}\">, which is not \
supported by all assistive technology",
level, level + 1;
hint: "HTML only supports <h1> to <h6>, not <h{}>", level + 1;
hint: "you may want to restructure your document so that \
it doesn't contain deep headings"));
HtmlElem::new(tag::div)
.with_body(Some(realized))
.with_attr(attr::role, "heading")
.with_attr(attr::aria_level, eco_format!("{}", level + 1))
.pack()
.spanned(span)
} else {
let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level - 1];
HtmlElem::new(t).with_body(Some(realized)).pack().spanned(span)
}
} else {
let realized = BlockBody::Content(realized);
BlockElem::new().with_body(Some(realized)).pack().spanned(span)