Compare commits

..

23 Commits

Author SHA1 Message Date
Tobias Schmitz
d35640bf8e
refactor: revert some changes to FrameItem::Link 2025-06-25 15:10:54 +02:00
Tobias Schmitz
c20732bdbf
refactor: [no ci] derive(Cast) for ArtifactKind 2025-06-25 15:10:54 +02:00
Tobias Schmitz
4031811fc7
fix: [no ci] avoid empty marked-content sequences 2025-06-25 15:10:54 +02:00
Tobias Schmitz
03a73284da
feat: [no ci] generate tags for tables 2025-06-25 15:10:54 +02:00
Tobias Schmitz
8266f321f4
feat: [no ci] use local krilla version 2025-06-25 15:10:53 +02:00
Tobias Schmitz
a629f4cf94
feat: [no ci] pdf.tag function to manually create pdf tags 2025-06-25 15:09:56 +02:00
Tobias Schmitz
b2979fcb61
feat: [no ci] write tags for more elements 2025-06-25 15:09:56 +02:00
Tobias Schmitz
7d67db2346
feat: [no ci] write tags for links and use quadpoints in link annotations 2025-06-25 15:09:56 +02:00
Tobias Schmitz
ce997cf8b4
feat: pdf.artifact element 2025-06-25 15:09:56 +02:00
Tobias Schmitz
365dd69028
feat: [no ci] mark artifacts 2025-06-25 15:09:56 +02:00
Tobias Schmitz
cf15c62256
feat: [WIP] allow specifying alt text for links
skip-checks:true

# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# Date:      Wed May 28 17:47:35 2025 +0200
#
# On branch pdf-accessibility
# Your branch and 'origin/pdf-accessibility' have diverged,
# and have 11 and 5 different commits each, respectively.
#
# Changes to be committed:
#	modified:   crates/typst-ide/src/jump.rs
#	modified:   crates/typst-layout/src/flow/distribute.rs
#	modified:   crates/typst-layout/src/modifiers.rs
#	modified:   crates/typst-library/src/foundations/content.rs
#	modified:   crates/typst-library/src/layout/frame.rs
#	modified:   crates/typst-library/src/model/bibliography.rs
#	modified:   crates/typst-library/src/model/footnote.rs
#	modified:   crates/typst-library/src/model/link.rs
#	modified:   crates/typst-library/src/model/outline.rs
#	modified:   crates/typst-library/src/model/reference.rs
#	modified:   crates/typst-pdf/src/convert.rs
#	modified:   crates/typst-pdf/src/link.rs
#	modified:   crates/typst-render/src/lib.rs
#	modified:   crates/typst-svg/src/lib.rs
#	modified:   tests/src/run.rs
#
2025-06-25 15:09:56 +02:00
Tobias Schmitz
af34645518
feat: [WIP] include links in tag tree
skip-checks:true
2025-06-25 15:09:56 +02:00
Tobias Schmitz
c17d24c335
feat: [WIP] write tags
skip-checks:true
2025-06-25 15:09:56 +02:00
Tobias Schmitz
9809112630
feat: [WIP] make more things locatable
skip-checks:true
2025-06-25 15:09:56 +02:00
Tobias Schmitz
0b3aa10bb8
feat: [draft] generate accessibility tag tree for headings
skip-checks:true
2025-06-25 15:09:56 +02:00
Laurenz
f2f527c451
Also fix encoding of <textarea> (#6497) 2025-06-24 15:52:15 +00:00
Laurenz
9e3c1199ed
Check that git tree is clean after build (#6495) 2025-06-24 15:05:02 +00:00
Tobias Schmitz
70399a94fd
Bump krilla to current Git version (#6488)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
2025-06-24 13:23:37 +00:00
Andrew Voynov
d4be7c4ca5
Add page reference customization example (#6480)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
2025-06-24 13:00:51 +00:00
Andrew Voynov
f162c37101
Improve equation reference example (#6481) 2025-06-24 12:49:28 +00:00
Andrew Voynov
87c5686560
Add docs for std module (#6407)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
2025-06-24 10:22:55 +00:00
Max
899de6d5d5
Use ICU data to check if accent is bottom (#6393)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
2025-06-24 10:03:10 +00:00
Andrew Voynov
24293a6c12
Rewrite outline.indent example (#6383)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
2025-06-24 09:56:58 +00:00
22 changed files with 252 additions and 187 deletions

View File

@ -81,6 +81,7 @@ jobs:
- run: cargo clippy --workspace --all-targets --no-default-features
- run: cargo fmt --check --all
- run: cargo doc --workspace --no-deps
- run: git diff --exit-code
min-version:
name: Check minimum Rust version

View File

@ -113,7 +113,7 @@ fn write_element(w: &mut Writer, element: &HtmlElement) -> SourceResult<()> {
/// Encodes the children of an element.
fn write_children(w: &mut Writer, element: &HtmlElement) -> SourceResult<()> {
// See HTML spec § 13.1.2.5.
if element.tag == tag::pre && starts_with_newline(element) {
if matches!(element.tag, tag::pre | tag::textarea) && starts_with_newline(element) {
w.buf.push('\n');
}

View File

@ -36,9 +36,9 @@ pub fn jump_from_click(
) -> Option<Jump> {
// Try to find a link first.
for (pos, item) in frame.items() {
if let FrameItem::Link(link, size) = item {
if let FrameItem::Link(dest, size) = item {
if is_in_rect(*pos, *size, click) {
return Some(match &link.dest {
return Some(match dest {
Destination::Url(url) => Jump::Url(url.clone()),
Destination::Position(pos) => Jump::Position(*pos),
Destination::Location(loc) => {

View File

@ -1,6 +1,6 @@
use typst_library::foundations::{LinkMarker, Packed, StyleChain};
use typst_library::foundations::StyleChain;
use typst_library::layout::{Abs, Fragment, Frame, FrameItem, HideElem, Point, Sides};
use typst_library::model::ParElem;
use typst_library::model::{Destination, LinkElem, ParElem};
/// Frame-level modifications resulting from styles that do not impose any
/// layout structure.
@ -20,7 +20,7 @@ use typst_library::model::ParElem;
#[derive(Debug, Clone)]
pub struct FrameModifiers {
/// A destination to link to.
link: Option<Packed<LinkMarker>>,
dest: Option<Destination>,
/// Whether the contents of the frame should be hidden.
hidden: bool,
}
@ -28,9 +28,8 @@ pub struct FrameModifiers {
impl FrameModifiers {
/// Retrieve all modifications that should be applied per-frame.
pub fn get_in(styles: StyleChain) -> Self {
// TODO: maybe verify that an alt text was provided here
Self {
link: LinkMarker::current_in(styles),
dest: LinkElem::current_in(styles),
hidden: HideElem::hidden_in(styles),
}
}
@ -95,7 +94,7 @@ fn modify_frame(
modifiers: &FrameModifiers,
link_box_outset: Option<Sides<Abs>>,
) {
if let Some(link) = &modifiers.link {
if let Some(dest) = &modifiers.dest {
let mut pos = Point::zero();
let mut size = frame.size();
if let Some(outset) = link_box_outset {
@ -103,7 +102,7 @@ fn modify_frame(
pos.x -= outset.left;
size += outset.sum_by_axis();
}
frame.push(pos, FrameItem::Link(link.clone(), size));
frame.push(pos, FrameItem::Link(dest.clone(), size));
}
if modifiers.hidden {
@ -130,8 +129,8 @@ where
let reset;
let outer = styles;
let mut styles = styles;
if modifiers.link.is_some() {
reset = LinkMarker::set_current(None).wrap();
if modifiers.dest.is_some() {
reset = LinkElem::set_current(None).wrap();
styles = outer.chain(&reset);
}

View File

@ -21,7 +21,7 @@ use crate::foundations::{
};
use crate::introspection::{Locatable, Location};
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
use crate::model::{Destination, EmphElem, StrongElem};
use crate::model::{Destination, EmphElem, LinkElem, StrongElem};
use crate::pdf::{ArtifactElem, ArtifactKind};
use crate::text::UnderlineElem;
@ -506,11 +506,10 @@ impl Content {
/// Link the content somewhere.
pub fn linked(self, dest: Destination, alt: Option<EcoString>) -> Self {
let span = self.span();
let link = Packed::new(LinkMarker::new(self, dest, alt));
link.clone()
LinkMarker::new(self, dest.clone(), alt)
.pack()
.spanned(span)
.styled(LinkMarker::set_current(Some(link)))
.styled(LinkElem::set_current(Some(dest)))
}
/// Set alignments for this content.
@ -1002,11 +1001,6 @@ pub struct LinkMarker {
pub dest: Destination,
#[required]
pub alt: Option<EcoString>,
/// A link style that should be applied to elements.
#[internal]
#[ghost]
pub current: Option<Packed<LinkMarker>>,
}
impl Show for Packed<LinkMarker> {

View File

@ -7,9 +7,10 @@ use std::sync::Arc;
use typst_syntax::Span;
use typst_utils::{LazyHash, Numeric};
use crate::foundations::{cast, dict, Dict, Label, LinkMarker, Packed, Value};
use crate::foundations::{cast, dict, Dict, Label, Value};
use crate::introspection::{Location, Tag};
use crate::layout::{Abs, Axes, FixedAlignment, Length, Point, Size, Transform};
use crate::model::Destination;
use crate::text::TextItem;
use crate::visualize::{Color, Curve, FixedStroke, Geometry, Image, Paint, Shape};
@ -472,7 +473,7 @@ pub enum FrameItem {
/// An image and its size.
Image(Image, Size, Span),
/// An internal or external link to a destination.
Link(Packed<LinkMarker>, Size),
Link(Destination, Size),
/// An introspectable element that produced something within this frame.
Tag(Tag),
}
@ -484,7 +485,7 @@ impl Debug for FrameItem {
Self::Text(text) => write!(f, "{text:?}"),
Self::Shape(shape, _) => write!(f, "{shape:?}"),
Self::Image(image, _, _) => write!(f, "{image:?}"),
Self::Link(link, _) => write!(f, "Link({:?}, {:?})", link.dest, link.alt),
Self::Link(dest, _) => write!(f, "Link({dest:?})"),
Self::Tag(tag) => write!(f, "{tag:?}"),
}
}

View File

@ -148,7 +148,7 @@ pub struct Library {
/// The default style properties (for page size, font selection, and
/// everything else configurable via set and show rules).
pub styles: Styles,
/// The standard library as a value. Used to provide the `std` variable.
/// The standard library as a value. Used to provide the `std` module.
pub std: Binding,
/// In-development features that were enabled.
pub features: Features,

View File

@ -1,3 +1,10 @@
use std::sync::LazyLock;
use icu_properties::maps::CodePointMapData;
use icu_properties::CanonicalCombiningClass;
use icu_provider::AsDeserializingBufferProvider;
use icu_provider_blob::BlobDataProvider;
use crate::diag::bail;
use crate::foundations::{cast, elem, func, Content, NativeElement, SymbolElem};
use crate::layout::{Length, Rel};
@ -81,17 +88,22 @@ impl Accent {
Self(Self::combine(c).unwrap_or(c))
}
/// List of bottom accents. Currently just a list of ones included in the
/// Unicode math class document.
const BOTTOM: &[char] = &[
'\u{0323}', '\u{032C}', '\u{032D}', '\u{032E}', '\u{032F}', '\u{0330}',
'\u{0331}', '\u{0332}', '\u{0333}', '\u{033A}', '\u{20E8}', '\u{20EC}',
'\u{20ED}', '\u{20EE}', '\u{20EF}',
];
/// Whether this accent is a bottom accent or not.
pub fn is_bottom(&self) -> bool {
Self::BOTTOM.contains(&self.0)
static COMBINING_CLASS_DATA: LazyLock<CodePointMapData<CanonicalCombiningClass>> =
LazyLock::new(|| {
icu_properties::maps::load_canonical_combining_class(
&BlobDataProvider::try_new_from_static_blob(typst_assets::icu::ICU)
.unwrap()
.as_deserializing(),
)
.unwrap()
});
matches!(
COMBINING_CLASS_DATA.as_borrowed().get(self.0),
CanonicalCombiningClass::Below
)
}
}

View File

@ -91,6 +91,11 @@ pub struct LinkElem {
_ => args.expect("body")?,
})]
pub body: Content,
/// A destination style that should be applied to elements.
#[internal]
#[ghost]
pub current: Option<Destination>,
}
impl LinkElem {

View File

@ -226,25 +226,21 @@ pub struct OutlineElem {
/// to just specifying `{2em}`.
///
/// ```example
/// #set heading(numbering: "1.a.")
/// >>> #show heading: none
/// #set heading(numbering: "I-I.")
/// #set outline(title: none)
///
/// #outline(
/// title: [Contents (Automatic)],
/// indent: auto,
/// )
/// #outline()
/// #line(length: 100%)
/// #outline(indent: 3em)
///
/// #outline(
/// title: [Contents (Length)],
/// indent: 2em,
/// )
///
/// = About ACME Corp.
/// == History
/// === Origins
/// #lorem(10)
///
/// == Products
/// #lorem(10)
/// = Software engineering technologies
/// == Requirements
/// == Tools and technologies
/// === Code editors
/// == Analyzing alternatives
/// = Designing software components
/// = Testing and integration
/// ```
pub indent: Smart<OutlineIndent>,
}
@ -462,8 +458,9 @@ impl OutlineEntry {
/// at the same level are aligned.
///
/// If the outline's indent is a fixed value or a function, the prefixes are
/// indented, but the inner contents are simply inset from the prefix by the
/// specified `gap`, rather than aligning outline-wide.
/// indented, but the inner contents are simply offset from the prefix by
/// the specified `gap`, rather than aligning outline-wide. For a visual
/// explanation, see [`outline.indent`]($outline.indent).
#[func(contextual)]
pub fn indented(
&self,

View File

@ -79,6 +79,36 @@ use crate::text::TextElem;
/// reference: `[@intro[Chapter]]`.
///
/// # Customization
/// When you only ever need to reference pages of a figure/table/heading/etc. in
/// a document, the default `form` field value can be changed to `{"page"}` with
/// a set rule. If you prefer a short "p." supplement over "page", the
/// [`page.supplement`]($page.supplement) field can be used for changing this:
///
/// ```example
/// #set page(
/// numbering: "1",
/// supplement: "p.",
/// >>> margin: (bottom: 3em),
/// >>> footer-descent: 1.25em,
/// )
/// #set ref(form: "page")
///
/// #figure(
/// stack(
/// dir: ltr,
/// spacing: 1em,
/// circle(),
/// square(),
/// ),
/// caption: [Shapes],
/// ) <shapes>
///
/// #pagebreak()
///
/// See @shapes for examples
/// of different shapes.
/// ```
///
/// If you write a show rule for references, you can access the referenced
/// element through the `element` field of the reference. The `element` may
/// be `{none}` even if it exists if Typst hasn't discovered it yet, so you
@ -91,16 +121,13 @@ use crate::text::TextElem;
/// #show ref: it => {
/// let eq = math.equation
/// let el = it.element
/// if el != none and el.func() == eq {
/// // Override equation references.
/// link(el.location(),numbering(
/// el.numbering,
/// ..counter(eq).at(el.location())
/// ))
/// } else {
/// // Other references as usual.
/// it
/// }
/// // Skip all other references.
/// if el == none or el.func() != eq { return it }
/// // Override equation references.
/// link(el.location(), numbering(
/// el.numbering,
/// ..counter(eq).at(el.location())
/// ))
/// }
///
/// = Beginnings <beginning>

View File

@ -1,5 +1,5 @@
use ecow::EcoString;
use typst_macros::{cast, elem};
use typst_macros::{cast, elem, Cast};
use crate::diag::SourceResult;
use crate::engine::Engine;
@ -200,7 +200,7 @@ pub struct ArtifactElem {
pub body: Content,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Cast)]
pub enum ArtifactKind {
/// Page header artifacts.
Header,
@ -213,20 +213,6 @@ pub enum ArtifactKind {
Other,
}
cast! {
ArtifactKind,
self => match self {
ArtifactKind::Header => "header".into_value(),
ArtifactKind::Footer => "footer".into_value(),
ArtifactKind::Page => "page".into_value(),
ArtifactKind::Other => "other".into_value(),
},
"header" => Self::Header,
"footer" => Self::Footer,
"page" => Self::Page,
"other" => Self::Other,
}
impl Show for Packed<ArtifactElem> {
#[typst_macros::time(name = "pdf.artifact", span = self.span())]
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {

View File

@ -13,7 +13,7 @@ use krilla::{Document, SerializeSettings};
use krilla_svg::render_svg_glyph;
use typst_library::diag::{bail, error, SourceDiagnostic, SourceResult};
use typst_library::foundations::{NativeElement, Repr};
use typst_library::introspection::{self, Location};
use typst_library::introspection::{Location, Tag};
use typst_library::layout::{
Abs, Frame, FrameItem, GroupItem, PagedDocument, Size, Transform,
};
@ -110,8 +110,6 @@ fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResul
let mut surface = page.surface();
let mut fc = FrameContext::new(typst_page.frame.size());
tags::restart_open(gc, &mut surface);
handle_frame(
&mut fc,
&typst_page.frame,
@ -120,8 +118,6 @@ fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResul
gc,
)?;
tags::end_open(gc, &mut surface);
surface.finish();
tags::add_annotations(gc, &mut page, fc.link_annotations);
@ -285,13 +281,9 @@ pub(crate) fn handle_frame(
FrameItem::Image(image, size, span) => {
handle_image(gc, fc, image, *size, surface, *span)?
}
FrameItem::Link(link, size) => handle_link(fc, gc, link, *size),
FrameItem::Tag(introspection::Tag::Start(elem)) => {
tags::handle_start(gc, surface, elem)
}
FrameItem::Tag(introspection::Tag::End(loc, _)) => {
tags::handle_end(gc, surface, *loc);
}
FrameItem::Link(dest, size) => handle_link(fc, gc, dest, *size),
FrameItem::Tag(Tag::Start(elem)) => tags::handle_start(gc, elem),
FrameItem::Tag(Tag::End(loc, _)) => tags::handle_end(gc, *loc),
}
fc.pop();
@ -306,7 +298,7 @@ pub(crate) fn handle_group(
fc: &mut FrameContext,
group: &GroupItem,
surface: &mut Surface,
context: &mut GlobalContext,
gc: &mut GlobalContext,
) -> SourceResult<()> {
fc.push();
fc.state_mut().pre_concat(group.transform);
@ -322,10 +314,12 @@ pub(crate) fn handle_group(
.and_then(|p| p.transform(fc.state().transform.to_krilla()));
if let Some(clip_path) = &clip_path {
let mut handle = tags::start_marked(gc, surface);
let surface = handle.surface();
surface.push_clip_path(clip_path, &krilla::paint::FillRule::NonZero);
}
handle_frame(fc, &group.frame, None, surface, context)?;
handle_frame(fc, &group.frame, None, surface, gc)?;
if clip_path.is_some() {
surface.pop();

View File

@ -14,6 +14,7 @@ use typst_library::visualize::{
use typst_syntax::Span;
use crate::convert::{FrameContext, GlobalContext};
use crate::tags;
use crate::util::{SizeExt, TransformExt};
#[typst_macros::time(name = "handle image")]
@ -32,6 +33,8 @@ pub(crate) fn handle_image(
gc.image_spans.insert(span);
let mut handle = tags::start_marked(gc, surface);
let surface = handle.surface();
match image.kind() {
ImageKind::Raster(raster) => {
let (exif_transform, new_size) = exif_transform(raster, size);

View File

@ -5,7 +5,6 @@ use krilla::action::{Action, LinkAction};
use krilla::annotation::Target;
use krilla::destination::XyzDestination;
use krilla::geom as kg;
use typst_library::foundations::LinkMarker;
use typst_library::layout::{Abs, Point, Position, Size};
use typst_library::model::Destination;
@ -24,10 +23,10 @@ pub(crate) struct LinkAnnotation {
pub(crate) fn handle_link(
fc: &mut FrameContext,
gc: &mut GlobalContext,
link: &LinkMarker,
dest: &Destination,
size: Size,
) {
let target = match &link.dest {
let target = match dest {
Destination::Url(u) => {
Target::Action(Action::Link(LinkAction::new(u.to_string())))
}
@ -51,14 +50,15 @@ pub(crate) fn handle_link(
};
let entry = gc.tags.stack.last_mut().expect("a link parent");
let StackEntryKind::Link(link_id, _) = entry.kind else {
let StackEntryKind::Link(link_id, link) = &entry.kind else {
unreachable!("expected a link parent")
};
let alt = link.alt.as_ref().map(EcoString::to_string);
let rect = to_rect(fc, size);
let quadpoints = quadpoints(rect);
match fc.link_annotations.entry(link_id) {
match fc.link_annotations.entry(*link_id) {
Entry::Occupied(occupied) => {
// Update the bounding box and add the quadpoints of an existing link annotation.
let annotation = occupied.into_mut();
@ -73,7 +73,7 @@ pub(crate) fn handle_link(
placeholder,
rect,
quad_points: quadpoints.to_vec(),
alt: link.alt.as_ref().map(EcoString::to_string),
alt,
target,
});
}

View File

@ -5,8 +5,8 @@ use typst_library::visualize::{Geometry, Shape};
use typst_syntax::Span;
use crate::convert::{FrameContext, GlobalContext};
use crate::paint;
use crate::util::{convert_path, AbsExt, TransformExt};
use crate::{paint, tags};
#[typst_macros::time(name = "handle shape")]
pub(crate) fn handle_shape(
@ -16,6 +16,9 @@ pub(crate) fn handle_shape(
gc: &mut GlobalContext,
span: Span,
) -> SourceResult<()> {
let mut handle = tags::start_marked(gc, surface);
let surface = handle.surface();
surface.set_location(span.into_raw().get());
surface.push_transform(&fc.state().transform().to_krilla());

View File

@ -45,6 +45,16 @@ pub(crate) enum StackEntryKind {
TableCell(Packed<TableCell>),
}
impl StackEntryKind {
pub(crate) fn as_standard_mut(&mut self) -> Option<&mut Tag> {
if let Self::Standard(v) = self {
Some(v)
} else {
None
}
}
}
pub(crate) struct TableCtx {
table: Packed<TableElem>,
rows: Vec<Vec<Option<(Packed<TableCell>, Tag, Vec<TagNode>)>>>,
@ -144,10 +154,6 @@ impl Tags {
.expect("initialized placeholder node")
}
pub(crate) fn is_root(&self) -> bool {
self.stack.is_empty()
}
/// Returns the current parent's list of children and the structure type ([Tag]).
/// In case of the document root the structure type will be `None`.
pub(crate) fn parent(&mut self) -> (Option<&mut StackEntryKind>, &mut Vec<TagNode>) {
@ -196,25 +202,40 @@ impl Tags {
}
}
/// Marked-content may not cross page boundaries: restart tag that was still open
/// at the end of the last page.
pub(crate) fn restart_open(gc: &mut GlobalContext, surface: &mut Surface) {
// TODO: somehow avoid empty marked-content sequences
if let Some((loc, kind)) = gc.tags.in_artifact {
start_artifact(gc, surface, loc, kind);
} else if let Some(entry) = gc.tags.stack.last_mut() {
let id = surface.start_tagged(ContentTag::Other);
entry.nodes.push(TagNode::Leaf(id));
/// Automatically calls [`Surface::end_tagged`] when dropped.
pub(crate) struct TagHandle<'a, 'b> {
surface: &'b mut Surface<'a>,
}
impl Drop for TagHandle<'_, '_> {
fn drop(&mut self) {
self.surface.end_tagged();
}
}
/// Marked-content may not cross page boundaries: end any open tag.
pub(crate) fn end_open(gc: &mut GlobalContext, surface: &mut Surface) {
if !gc.tags.stack.is_empty() || gc.tags.in_artifact.is_some() {
surface.end_tagged();
impl<'a> TagHandle<'a, '_> {
pub(crate) fn surface<'c>(&'c mut self) -> &'c mut Surface<'a> {
&mut self.surface
}
}
/// Returns a [`TagHandle`] that automatically calls [`Surface::end_tagged`]
/// when dropped.
pub(crate) fn start_marked<'a, 'b>(
gc: &mut GlobalContext,
surface: &'b mut Surface<'a>,
) -> TagHandle<'a, 'b> {
let content = if let Some((_, kind)) = gc.tags.in_artifact {
let ty = artifact_type(kind);
ContentTag::Artifact(ty)
} else {
ContentTag::Other
};
let id = surface.start_tagged(content);
gc.tags.push(TagNode::Leaf(id));
TagHandle { surface }
}
/// Add all annotations that were found in the page frame.
pub(crate) fn add_annotations(
gc: &mut GlobalContext,
@ -232,11 +253,7 @@ pub(crate) fn add_annotations(
}
}
pub(crate) fn handle_start(
gc: &mut GlobalContext,
surface: &mut Surface,
elem: &Content,
) {
pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
if gc.tags.in_artifact.is_some() {
// Don't nest artifacts
return;
@ -245,9 +262,8 @@ pub(crate) fn handle_start(
let loc = elem.location().unwrap();
if let Some(artifact) = elem.to_packed::<ArtifactElem>() {
end_open(gc, surface);
let kind = artifact.kind(StyleChain::default());
start_artifact(gc, surface, loc, kind);
start_artifact(gc, loc, kind);
return;
}
@ -279,79 +295,55 @@ pub(crate) fn handle_start(
} else if let Some(image) = elem.to_packed::<ImageElem>() {
let alt = image.alt(StyleChain::default()).map(|s| s.to_string());
end_open(gc, surface);
let id = surface.start_tagged(ContentTag::Other);
let mut node = TagNode::Leaf(id);
if let Some(StackEntryKind::Standard(parent)) = gc.tags.parent().0 {
if parent.kind == TagKind::Figure && parent.alt_text.is_none() {
// HACK: set alt text of outer figure tag, if the contained image
// has alt text specified
parent.alt_text = alt;
} else {
node = TagNode::Group(TagKind::Figure.with_alt_text(alt), vec![node]);
}
let figure_tag = (gc.tags.parent().0)
.and_then(|parent| parent.as_standard_mut())
.filter(|tag| tag.kind == TagKind::Figure && tag.alt_text.is_none());
if let Some(figure_tag) = figure_tag {
// HACK: set alt text of outer figure tag, if the contained image
// has alt text specified
figure_tag.alt_text = alt;
return;
} else {
node = TagNode::Group(TagKind::Figure.with_alt_text(alt), vec![node]);
TagKind::Figure.with_alt_text(alt)
}
gc.tags.push(node);
return;
} else if let Some(_) = elem.to_packed::<FigureCaption>() {
TagKind::Caption.into()
} else if let Some(link) = elem.to_packed::<LinkMarker>() {
let link_id = gc.tags.next_link_id();
push_stack(gc, surface, loc, StackEntryKind::Link(link_id, link.clone()));
push_stack(gc, loc, StackEntryKind::Link(link_id, link.clone()));
return;
} else if let Some(table) = elem.to_packed::<TableElem>() {
let ctx = TableCtx { table: table.clone(), rows: Vec::new() };
push_stack(gc, surface, loc, StackEntryKind::Table(ctx));
push_stack(gc, loc, StackEntryKind::Table(ctx));
return;
} else if let Some(cell) = elem.to_packed::<TableCell>() {
push_stack(gc, surface, loc, StackEntryKind::TableCell(cell.clone()));
push_stack(gc, loc, StackEntryKind::TableCell(cell.clone()));
return;
} else if let Some(_) = elem.to_packed::<TableHLine>() {
end_open(gc, surface);
start_artifact(gc, surface, loc, ArtifactKind::Other);
start_artifact(gc, loc, ArtifactKind::Other);
return;
} else if let Some(_) = elem.to_packed::<TableVLine>() {
end_open(gc, surface);
start_artifact(gc, surface, loc, ArtifactKind::Other);
start_artifact(gc, loc, ArtifactKind::Other);
return;
} else {
return;
};
push_stack(gc, surface, loc, StackEntryKind::Standard(tag));
push_stack(gc, loc, StackEntryKind::Standard(tag));
}
fn push_stack(
gc: &mut GlobalContext,
surface: &mut Surface,
loc: Location,
kind: StackEntryKind,
) {
fn push_stack(gc: &mut GlobalContext, loc: Location, kind: StackEntryKind) {
if !gc.tags.context_supports(&kind) {
// TODO: error or warning?
}
// close previous marked-content and open a nested tag.
end_open(gc, surface);
let id = surface.start_tagged(krilla::tagging::ContentTag::Other);
gc.tags
.stack
.push(StackEntry { loc, kind, nodes: vec![TagNode::Leaf(id)] });
gc.tags.stack.push(StackEntry { loc, kind, nodes: Vec::new() });
}
pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Location) {
pub(crate) fn handle_end(gc: &mut GlobalContext, loc: Location) {
if let Some((l, _)) = gc.tags.in_artifact {
if l == loc {
gc.tags.in_artifact = None;
surface.end_tagged();
if let Some(entry) = gc.tags.stack.last_mut() {
let id = surface.start_tagged(ContentTag::Other);
entry.nodes.push(TagNode::Leaf(id));
}
}
return;
}
@ -360,8 +352,6 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Loc
return;
};
surface.end_tagged();
let node = match entry.kind {
StackEntryKind::Standard(tag) => TagNode::Group(tag, entry.nodes),
StackEntryKind::Link(_, link) => {
@ -387,30 +377,14 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Loc
table_ctx.insert(cell, entry.nodes);
// TODO: somehow avoid empty marked-content sequences
let id = surface.start_tagged(ContentTag::Other);
gc.tags.push(TagNode::Leaf(id));
return;
}
};
gc.tags.push(node);
if !gc.tags.is_root() {
// TODO: somehow avoid empty marked-content sequences
let id = surface.start_tagged(ContentTag::Other);
gc.tags.push(TagNode::Leaf(id));
}
}
fn start_artifact(
gc: &mut GlobalContext,
surface: &mut Surface,
loc: Location,
kind: ArtifactKind,
) {
let ty = artifact_type(kind);
let id = surface.start_tagged(ContentTag::Artifact(ty));
gc.tags.push(TagNode::Leaf(id));
fn start_artifact(gc: &mut GlobalContext, loc: Location, kind: ArtifactKind) {
gc.tags.in_artifact = Some((loc, kind));
}

View File

@ -11,8 +11,8 @@ use typst_library::visualize::FillRule;
use typst_syntax::Span;
use crate::convert::{FrameContext, GlobalContext};
use crate::paint;
use crate::util::{display_font, AbsExt, TransformExt};
use crate::{paint, tags};
#[typst_macros::time(name = "handle text")]
pub(crate) fn handle_text(
@ -23,6 +23,9 @@ pub(crate) fn handle_text(
) -> SourceResult<()> {
*gc.languages.entry(t.lang).or_insert(0) += t.glyphs.len();
let mut handle = tags::start_marked(gc, surface);
let surface = handle.surface();
let font = convert_font(gc, t.font.clone())?;
let fill = paint::convert_fill(
gc,

View File

@ -137,6 +137,59 @@
In addition to the functions listed below, the `calc` module also defines
the constants `pi`, `tau`, `e`, and `inf`.
- name: std
title: Standard library
category: foundations
path: ["std"]
details: |
A module that contains all globally accessible items.
# Using "shadowed" definitions
The `std` module is useful whenever you overrode a name from the global
scope (this is called _shadowing_). For instance, you might have used the
name `text` for a parameter. To still access the `text` element, write
`std.text`.
```example
>>> #set page(margin: (left: 3em))
#let par = [My special paragraph.]
#let special(text) = {
set std.text(style: "italic")
set std.par.line(numbering: "1")
text
}
#special(par)
#lorem(10)
```
# Conditional access
You can also use this in combination with the [dictionary
constructor]($dictionary) to conditionally access global definitions. This
can, for instance, be useful to use new or experimental functionality when
it is available, while falling back to an alternative implementation if
used on an older Typst version. In particular, this allows us to create
[polyfills](https://en.wikipedia.org/wiki/Polyfill_(programming)).
This can be as simple as creating an alias to prevent warning messages, for
example, conditionally using `pattern` in Typst version 0.12, but using
[`tiling`] in newer versions. Since the parameters accepted by the `tiling`
function match those of the older `pattern` function, using the `tiling`
function when available and falling back to `pattern` otherwise will unify
the usage across all versions. Note that, when creating a polyfill,
[`sys.version`]($category/foundations/sys) can also be very useful.
```typ
#let tiling = if "tiling" in dictionary(std) {
tiling
} else {
pattern
}
...
```
- name: sys
title: System
category: foundations

View File

@ -37,7 +37,7 @@ static GROUPS: LazyLock<Vec<GroupData>> = LazyLock::new(|| {
let mut groups: Vec<GroupData> =
yaml::from_str(load!("reference/groups.yml")).unwrap();
for group in &mut groups {
if group.filter.is_empty() {
if group.filter.is_empty() && group.name != "std" {
group.filter = group
.module()
.scope()

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body><textarea>
enter</textarea></body>
</html>

View File

@ -11,6 +11,9 @@
#html.pre("\nhello")
#html.pre("\n\nhello")
--- html-textarea-starting-with-newline html ---
#html.textarea("\nenter")
--- html-script html ---
// This should be pretty and indented.
#html.script(