Repository: typst/typst
File: crates/typst-library/src/model/heading.rs (Rust)

Code before:
            realized = numbering + spacing + realized;
        }

        if indent != Abs::zero() && !html {
            realized = realized.styled(ParElem::set_hanging_indent(indent.into()));
        }

        Ok(if html {
            // HTML's h1 is closer to a title element. There should only be one.
            // 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).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 screen readers",
                    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, (level + 1).to_string())
                    .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)
        })
    }
}

impl ShowSet for Packed<HeadingElem> {
    fn show_set(&self, styles: StyleChain) -> Styles {
        let level = (**self).resolve_level(styles).get();
        let scale = match level {
            1 => 1.4,
            2 => 1.2,
            _ => 1.0,
        };

        let size = Em::new(scale);
        let above = Em::new(if level == 1 { 1.8 } else { 1.44 }) / scale;
        let below = Em::new(0.75) / scale;

Review comment (suggestion):
```suggestion
                    .with_attr(attr::aria_level, eco_format!("{}", level + 1))
```

Code after:

            realized = numbering + spacing + realized;
        }

        if indent != Abs::zero() && !html {
            realized = realized.styled(ParElem::set_hanging_indent(indent.into()));
        }

        Ok(if html {
            // HTML's h1 is closer to a title element. There should only be one.
            // 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).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)
        })
    }
}

impl ShowSet for Packed<HeadingElem> {
    fn show_set(&self, styles: StyleChain) -> Styles {
        let level = (**self).resolve_level(styles).get();
        let scale = match level {
            1 => 1.4,
            2 => 1.2,
            _ => 1.0,
        };

        let size = Em::new(scale);
        let above = Em::new(if level == 1 { 1.8 } else { 1.44 }) / scale;