Merge branch 'main' into bibliography-entry

This commit is contained in:
Kevin K. 2025-02-25 14:04:37 +01:00 committed by GitHub
commit 76004a39d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 265 additions and 118 deletions

View File

@ -30,7 +30,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.83.0
- uses: dtolnay/rust-toolchain@1.85.0
- uses: Swatinem/rust-cache@v2
- run: cargo test --workspace --no-run
- run: cargo test --workspace --no-fail-fast
@ -59,7 +59,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.83.0
- uses: dtolnay/rust-toolchain@1.85.0
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.80.0
- uses: dtolnay/rust-toolchain@1.83.0
- uses: Swatinem/rust-cache@v2
- run: cargo check --workspace

View File

@ -44,7 +44,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.83.0
- uses: dtolnay/rust-toolchain@1.85.0
with:
target: ${{ matrix.target }}

View File

@ -5,7 +5,7 @@ resolver = "2"
[workspace.package]
version = "0.13.0"
rust-version = "1.80" # also change in ci.yml
rust-version = "1.83" # also change in ci.yml
authors = ["The Typst Project Developers"]
edition = "2021"
homepage = "https://typst.app"

View File

@ -350,7 +350,7 @@ fn export_image(
.iter()
.enumerate()
.filter(|(i, _)| {
config.pages.as_ref().map_or(true, |exported_page_ranges| {
config.pages.as_ref().is_none_or(|exported_page_ranges| {
exported_page_ranges.includes_page_index(*i)
})
})

View File

@ -55,11 +55,11 @@ pub fn watch(timer: &mut Timer, command: &WatchCommand) -> StrResult<()> {
// Perform initial compilation.
timer.record(&mut world, |world| compile_once(world, &mut config))??;
// Watch all dependencies of the initial compilation.
watcher.update(world.dependencies())?;
// Recompile whenever something relevant happens.
loop {
// Watch all dependencies of the most recent compilation.
watcher.update(world.dependencies())?;
// Wait until anything relevant happens.
watcher.wait()?;
@ -71,9 +71,6 @@ pub fn watch(timer: &mut Timer, command: &WatchCommand) -> StrResult<()> {
// Evict the cache.
comemo::evict(10);
// Adjust the file watching.
watcher.update(world.dependencies())?;
}
}
@ -204,6 +201,10 @@ impl Watcher {
let event = event
.map_err(|err| eco_format!("failed to watch dependencies ({err})"))?;
if !is_relevant_event_kind(&event.kind) {
continue;
}
// Workaround for notify-rs' implicit unwatch on remove/rename
// (triggered by some editors when saving files) with the
// inotify backend. By keeping track of the potentially
@ -224,7 +225,17 @@ impl Watcher {
}
}
relevant |= self.is_event_relevant(&event);
// Don't recompile because the output file changed.
// FIXME: This doesn't work properly for multifile image export.
if event
.paths
.iter()
.all(|path| is_same_file(path, &self.output).unwrap_or(false))
{
continue;
}
relevant = true;
}
// If we found a relevant event or if any of the missing files now
@ -234,32 +245,23 @@ impl Watcher {
}
}
}
}
/// Whether a watch event is relevant for compilation.
fn is_event_relevant(&self, event: &notify::Event) -> bool {
// Never recompile because the output file changed.
if event
.paths
.iter()
.all(|path| is_same_file(path, &self.output).unwrap_or(false))
{
return false;
}
match &event.kind {
notify::EventKind::Any => true,
notify::EventKind::Access(_) => false,
notify::EventKind::Create(_) => true,
notify::EventKind::Modify(kind) => match kind {
notify::event::ModifyKind::Any => true,
notify::event::ModifyKind::Data(_) => true,
notify::event::ModifyKind::Metadata(_) => false,
notify::event::ModifyKind::Name(_) => true,
notify::event::ModifyKind::Other => false,
},
notify::EventKind::Remove(_) => true,
notify::EventKind::Other => false,
}
/// Whether a kind of watch event is relevant for compilation.
fn is_relevant_event_kind(kind: &notify::EventKind) -> bool {
match kind {
notify::EventKind::Any => true,
notify::EventKind::Access(_) => false,
notify::EventKind::Create(_) => true,
notify::EventKind::Modify(kind) => match kind {
notify::event::ModifyKind::Any => true,
notify::event::ModifyKind::Data(_) => true,
notify::event::ModifyKind::Metadata(_) => false,
notify::event::ModifyKind::Name(_) => true,
notify::event::ModifyKind::Other => false,
},
notify::EventKind::Remove(_) => true,
notify::EventKind::Other => false,
}
}

View File

@ -83,8 +83,8 @@ fn html_document_impl(
)?;
let output = handle_list(&mut engine, &mut locator, children.iter().copied())?;
let introspector = Introspector::html(&output);
let root = root_element(output, &info)?;
let introspector = Introspector::html(&root);
Ok(HtmlDocument { info, root, introspector })
}
@ -307,18 +307,18 @@ fn head_element(info: &DocumentInfo) -> HtmlElement {
/// Determine which kind of output the user generated.
fn classify_output(mut output: Vec<HtmlNode>) -> SourceResult<OutputKind> {
let len = output.len();
let count = output.iter().filter(|node| !matches!(node, HtmlNode::Tag(_))).count();
for node in &mut output {
let HtmlNode::Element(elem) = node else { continue };
let tag = elem.tag;
let mut take = || std::mem::replace(elem, HtmlElement::new(tag::html));
match (tag, len) {
match (tag, count) {
(tag::html, 1) => return Ok(OutputKind::Html(take())),
(tag::body, 1) => return Ok(OutputKind::Body(take())),
(tag::html | tag::body, _) => bail!(
elem.span,
"`{}` element must be the only element in the document",
elem.tag
elem.tag,
),
_ => {}
}

View File

@ -1455,7 +1455,7 @@ impl<'a> CompletionContext<'a> {
let mut defined = BTreeMap::<EcoString, Option<Value>>::new();
named_items(self.world, self.leaf.clone(), |item| {
let name = item.name();
if !name.is_empty() && item.value().as_ref().map_or(true, filter) {
if !name.is_empty() && item.value().as_ref().is_none_or(filter) {
defined.insert(name.clone(), item.value());
}

View File

@ -1377,7 +1377,7 @@ impl<'a> GridLayouter<'a> {
.footer
.as_ref()
.and_then(Repeatable::as_repeated)
.map_or(true, |footer| footer.start != header.end)
.is_none_or(|footer| footer.start != header.end)
&& self.lrows.last().is_some_and(|row| row.index() < header.end)
&& !in_last_with_offset(
self.regions,
@ -1446,7 +1446,7 @@ impl<'a> GridLayouter<'a> {
.iter_mut()
.filter(|rowspan| (rowspan.y..rowspan.y + rowspan.rowspan).contains(&y))
.filter(|rowspan| {
rowspan.max_resolved_row.map_or(true, |max_row| y > max_row)
rowspan.max_resolved_row.is_none_or(|max_row| y > max_row)
})
{
// If the first region wasn't defined yet, it will have the
@ -1494,7 +1494,7 @@ impl<'a> GridLayouter<'a> {
// laid out at the first frame of the row).
// Any rowspans ending before this row are laid out even
// on this row's first frame.
if laid_out_footer_start.map_or(true, |footer_start| {
if laid_out_footer_start.is_none_or(|footer_start| {
// If this is a footer row, then only lay out this rowspan
// if the rowspan is contained within the footer.
y < footer_start || rowspan.y >= footer_start
@ -1580,5 +1580,5 @@ pub(super) fn points(
/// our case, headers).
pub(super) fn in_last_with_offset(regions: Regions<'_>, offset: Abs) -> bool {
regions.backlog.is_empty()
&& regions.last.map_or(true, |height| regions.size.y + offset == height)
&& regions.last.is_none_or(|height| regions.size.y + offset == height)
}

View File

@ -463,7 +463,7 @@ pub fn hline_stroke_at_column(
// region, we have the last index, and (as a failsafe) we don't have the
// last row of cells above us.
let use_bottom_border_stroke = !in_last_region
&& local_top_y.map_or(true, |top_y| top_y + 1 != grid.rows.len())
&& local_top_y.is_none_or(|top_y| top_y + 1 != grid.rows.len())
&& y == grid.rows.len();
let bottom_y =
if use_bottom_border_stroke { grid.rows.len().saturating_sub(1) } else { y };

View File

@ -588,7 +588,7 @@ impl GridLayouter<'_> {
measurement_data: &CellMeasurementData<'_>,
) -> bool {
if sizes.len() <= 1
&& sizes.first().map_or(true, |&first_frame_size| {
&& sizes.first().is_none_or(|&first_frame_size| {
first_frame_size <= measurement_data.height_in_this_region
})
{

View File

@ -154,7 +154,7 @@ pub fn line<'a>(
let mut items = collect_items(engine, p, range, trim);
// Add a hyphen at the line start, if a previous dash should be repeated.
if pred.map_or(false, |pred| should_repeat_hyphen(pred, full)) {
if pred.is_some_and(|pred| should_repeat_hyphen(pred, full)) {
if let Some(shaped) = items.first_text_mut() {
shaped.prepend_hyphen(engine, p.config.fallback);
}
@ -406,7 +406,7 @@ fn should_repeat_hyphen(pred_line: &Line, text: &str) -> bool {
//
// See § 4.1.1.1.2.e on the "Ortografía de la lengua española"
// https://www.rae.es/ortografía/como-signo-de-división-de-palabras-a-final-de-línea
Lang::SPANISH => text.chars().next().map_or(false, |c| !c.is_uppercase()),
Lang::SPANISH => text.chars().next().is_some_and(|c| !c.is_uppercase()),
_ => false,
}

View File

@ -290,7 +290,7 @@ fn linebreak_optimized_bounded<'a>(
}
// If this attempt is better than what we had before, take it!
if best.as_ref().map_or(true, |best| best.total >= total) {
if best.as_ref().is_none_or(|best| best.total >= total) {
best = Some(Entry { pred: pred_index, total, line: attempt, end });
}
}
@ -423,7 +423,7 @@ fn linebreak_optimized_approximate(
let total = pred.total + line_cost;
// If this attempt is better than what we had before, take it!
if best.as_ref().map_or(true, |best| best.total >= total) {
if best.as_ref().is_none_or(|best| best.total >= total) {
best = Some(Entry {
pred: pred_index,
total,

View File

@ -465,7 +465,7 @@ impl<'a> ShapedText<'a> {
None
};
let mut chain = families(self.styles)
.filter(|family| family.covers().map_or(true, |c| c.is_match("-")))
.filter(|family| family.covers().is_none_or(|c| c.is_match("-")))
.map(|family| book.select(family.as_str(), self.variant))
.chain(fallback_func.iter().map(|f| f()))
.flatten();
@ -570,7 +570,7 @@ impl<'a> ShapedText<'a> {
// for the next line.
let dec = if ltr { usize::checked_sub } else { usize::checked_add };
while let Some(next) = dec(idx, 1) {
if self.glyphs.get(next).map_or(true, |g| g.range.start != text_index) {
if self.glyphs.get(next).is_none_or(|g| g.range.start != text_index) {
break;
}
idx = next;
@ -812,7 +812,7 @@ fn shape_segment<'a>(
.nth(1)
.map(|(i, _)| offset + i)
.unwrap_or(text.len());
covers.map_or(true, |cov| cov.is_match(&text[offset..end]))
covers.is_none_or(|cov| cov.is_match(&text[offset..end]))
};
// Collect the shaped glyphs, doing fallback and shaping parts again with

View File

@ -34,7 +34,7 @@ pub fn layout_accent(
// Try to replace accent glyph with flattened variant.
let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height);
if base.height() > flattened_base_height {
if base.ascent() > flattened_base_height {
glyph.make_flattened_accent_form(ctx);
}
@ -50,7 +50,7 @@ pub fn layout_accent(
// minus the accent base height. Only if the base is very small, we need
// a larger gap so that the accent doesn't move too low.
let accent_base_height = scaled!(ctx, styles, accent_base_height);
let gap = -accent.descent() - base.height().min(accent_base_height);
let gap = -accent.descent() - base.ascent().min(accent_base_height);
let size = Size::new(base.width(), accent.height() + gap + base.height());
let accent_pos = Point::with_x(base_attach - accent_attach);
let base_pos = Point::with_y(accent.height() + gap);

View File

@ -437,10 +437,10 @@ impl PartialEq for Func {
}
}
impl PartialEq<&NativeFuncData> for Func {
fn eq(&self, other: &&NativeFuncData) -> bool {
impl PartialEq<&'static NativeFuncData> for Func {
fn eq(&self, other: &&'static NativeFuncData) -> bool {
match &self.repr {
Repr::Native(native) => native.function == other.function,
Repr::Native(native) => *native == Static(*other),
_ => false,
}
}

View File

@ -21,6 +21,7 @@ use crate::foundations::{
/// be accessed using [field access notation]($scripting/#fields):
///
/// - General symbols are defined in the [`sym` module]($category/symbols/sym)
/// and are accessible without the `sym.` prefix in math mode.
/// - Emoji are defined in the [`emoji` module]($category/symbols/emoji)
///
/// Moreover, you can define custom symbols with this type's constructor
@ -410,7 +411,7 @@ fn find<'a>(
}
let score = (matching, Reverse(total));
if best_score.map_or(true, |b| score > b) {
if best_score.is_none_or(|b| score > b) {
best = Some(candidate.1);
best_score = Some(score);
}

View File

@ -10,7 +10,7 @@ use typst_utils::NonZeroExt;
use crate::diag::{bail, StrResult};
use crate::foundations::{Content, Label, Repr, Selector};
use crate::html::{HtmlElement, HtmlNode};
use crate::html::HtmlNode;
use crate::introspection::{Location, Tag};
use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
use crate::model::Numbering;
@ -55,8 +55,8 @@ impl Introspector {
/// Creates an introspector for HTML.
#[typst_macros::time(name = "introspect html")]
pub fn html(root: &HtmlElement) -> Self {
IntrospectorBuilder::new().build_html(root)
pub fn html(output: &[HtmlNode]) -> Self {
IntrospectorBuilder::new().build_html(output)
}
/// Iterates over all locatable elements.
@ -392,9 +392,9 @@ impl IntrospectorBuilder {
}
/// Build an introspector for an HTML document.
fn build_html(mut self, root: &HtmlElement) -> Introspector {
fn build_html(mut self, output: &[HtmlNode]) -> Introspector {
let mut elems = Vec::new();
self.discover_in_html(&mut elems, root);
self.discover_in_html(&mut elems, output);
self.finalize(elems)
}
@ -434,16 +434,16 @@ impl IntrospectorBuilder {
}
/// Processes the tags in the HTML element.
fn discover_in_html(&mut self, sink: &mut Vec<Pair>, elem: &HtmlElement) {
for child in &elem.children {
match child {
fn discover_in_html(&mut self, sink: &mut Vec<Pair>, nodes: &[HtmlNode]) {
for node in nodes {
match node {
HtmlNode::Tag(tag) => self.discover_in_tag(
sink,
tag,
Position { page: NonZeroUsize::ONE, point: Point::zero() },
),
HtmlNode::Text(_, _) => {}
HtmlNode::Element(elem) => self.discover_in_html(sink, elem),
HtmlNode::Element(elem) => self.discover_in_html(sink, &elem.children),
HtmlNode::Frame(frame) => self.discover_in_frame(
sink,
frame,

View File

@ -1387,7 +1387,7 @@ impl<'a> CellGrid<'a> {
// Include the gutter right before the footer, unless there is
// none, or the gutter is already included in the header (no
// rows between the header and the footer).
if header_end.map_or(true, |header_end| header_end != footer.start) {
if header_end != Some(footer.start) {
footer.start = footer.start.saturating_sub(1);
}
}
@ -1526,11 +1526,7 @@ impl<'a> CellGrid<'a> {
self.entry(x, y).map(|entry| match entry {
Entry::Cell(_) => Axes::new(x, y),
Entry::Merged { parent } => {
let c = if self.has_gutter {
1 + self.cols.len() / 2
} else {
self.cols.len()
};
let c = self.non_gutter_column_count();
let factor = if self.has_gutter { 2 } else { 1 };
Axes::new(factor * (*parent % c), factor * (*parent / c))
}
@ -1602,6 +1598,21 @@ impl<'a> CellGrid<'a> {
cell.rowspan.get()
}
}
#[inline]
pub fn non_gutter_column_count(&self) -> usize {
if self.has_gutter {
// Calculation: With gutters, we have
// 'cols = 2 * (non-gutter cols) - 1', since there is a gutter
// column between each regular column. Therefore,
// 'floor(cols / 2)' will be equal to
// 'floor(non-gutter cols - 1/2) = non-gutter-cols - 1',
// so 'non-gutter cols = 1 + floor(cols / 2)'.
1 + self.cols.len() / 2
} else {
self.cols.len()
}
}
}
/// Given a cell's requested x and y, the vector with the resolved cell

View File

@ -282,7 +282,7 @@ fn show_cell_html(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content {
fn show_cellgrid_html(grid: CellGrid, styles: StyleChain) -> Content {
let elem = |tag, body| HtmlElem::new(tag).with_body(Some(body)).pack();
let mut rows: Vec<_> = grid.entries.chunks(grid.cols.len()).collect();
let mut rows: Vec<_> = grid.entries.chunks(grid.non_gutter_column_count()).collect();
let tr = |tag, row: &[Entry]| {
let row = row

View File

@ -160,7 +160,7 @@ impl FontBook {
current.variant.weight.distance(variant.weight),
);
if best_key.map_or(true, |b| key < b) {
if best_key.is_none_or(|b| key < b) {
best = Some(id);
best_key = Some(key);
}

View File

@ -159,7 +159,7 @@ fn is_shapable(engine: &Engine, text: &str, styles: StyleChain) -> bool {
{
let covers = family.covers();
return text.chars().all(|c| {
covers.map_or(true, |cov| cov.is_match(c.encode_utf8(&mut [0; 4])))
covers.is_none_or(|cov| cov.is_match(c.encode_utf8(&mut [0; 4])))
&& font.ttf().glyph_index(c).is_some()
});
}

View File

@ -130,7 +130,7 @@ static TO_SRGB: LazyLock<qcms::Transform> = LazyLock::new(|| {
///
/// # Predefined color maps
/// Typst also includes a number of preset color maps that can be used for
/// [gradients]($gradient.linear). These are simply arrays of colors defined in
/// [gradients]($gradient/#stops). These are simply arrays of colors defined in
/// the module `color.map`.
///
/// ```example

View File

@ -70,6 +70,9 @@ use crate::visualize::{Color, ColorSpace, WeightedColor};
/// the offsets when defining a gradient. In this case, Typst will space all
/// stops evenly.
///
/// Typst predefines color maps that you can use as stops. See the
/// [`color`]($color/#predefined-color-maps) documentation for more details.
///
/// # Relativeness
/// The location of the `{0%}` and `{100%}` stops depends on the dimensions
/// of a container. This container can either be the shape that it is being
@ -157,10 +160,6 @@ use crate::visualize::{Color, ColorSpace, WeightedColor};
/// )
/// ```
///
/// # Presets
/// Typst predefines color maps that you can use with your gradients. See the
/// [`color`]($color/#predefined-color-maps) documentation for more details.
///
/// # Note on file sizes
///
/// Gradients can be quite large, especially if they have many stops. This is
@ -288,7 +287,7 @@ impl Gradient {
/// )),
/// )
/// ```
#[func]
#[func(title = "Radial Gradient")]
fn radial(
span: Span,
/// The color [stops](#stops) of the gradient.
@ -402,7 +401,7 @@ impl Gradient {
/// )),
/// )
/// ```
#[func]
#[func(title = "Conic Gradient")]
pub fn conic(
span: Span,
/// The color [stops](#stops) of the gradient.

View File

@ -70,7 +70,7 @@ pub(crate) fn write_outline(
// (not exceeding whichever is the most restrictive depth limit
// of those two).
while children.last().is_some_and(|last| {
last_skipped_level.map_or(true, |l| last.level < l)
last_skipped_level.is_none_or(|l| last.level < l)
&& last.level < leaf.level
}) {
children = &mut children.last_mut().unwrap().children;
@ -83,7 +83,7 @@ pub(crate) fn write_outline(
// needed, following the usual rules listed above.
last_skipped_level = None;
children.push(leaf);
} else if last_skipped_level.map_or(true, |l| leaf.level < l) {
} else if last_skipped_level.is_none_or(|l| leaf.level < l) {
// Only the topmost / lowest-level skipped heading matters when you
// have consecutive skipped headings (since none of them are being
// added to the bookmark tree), hence the condition above.

View File

@ -753,7 +753,7 @@ impl<'a> LinkedNode<'a> {
// sibling's span number is larger than the target span's number.
if children
.peek()
.map_or(true, |next| next.span().number() > span.number())
.is_none_or(|next| next.span().number() > span.number())
{
if let Some(found) = child.find(span) {
return Some(found);

View File

@ -327,8 +327,8 @@ impl PackageVersion {
/// missing in the bound are ignored.
pub fn matches_eq(&self, bound: &VersionBound) -> bool {
self.major == bound.major
&& bound.minor.map_or(true, |minor| self.minor == minor)
&& bound.patch.map_or(true, |patch| self.patch == patch)
&& bound.minor.is_none_or(|minor| self.minor == minor)
&& bound.patch.is_none_or(|patch| self.patch == patch)
}
/// Performs a `>` match with the given version bound. The match only

View File

@ -271,10 +271,11 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
}
SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::MathShorthand => {
continuable = matches!(
math_class(p.current_text()),
None | Some(MathClass::Alphabetic)
);
continuable = !p.at(SyntaxKind::MathShorthand)
&& matches!(
math_class(p.current_text()),
None | Some(MathClass::Alphabetic)
);
if !maybe_delimited(p) {
p.eat();
}

View File

@ -360,6 +360,21 @@ pub fn default_math_class(c: char) -> Option<MathClass> {
// https://github.com/typst/typst/pull/5714
'\u{22A5}' => Some(MathClass::Normal),
// Used as a binary connector in linear logic, where it is referred to
// as "par".
// https://github.com/typst/typst/issues/5764
'⅋' => Some(MathClass::Binary),
// Those overrides should become the default in the next revision of
// MathClass.txt.
// https://github.com/typst/typst/issues/5764#issuecomment-2632435247
'⎰' | '⟅' => Some(MathClass::Opening),
'⎱' | '⟆' => Some(MathClass::Closing),
// Both and ⟑ are classified as Binary.
// https://github.com/typst/typst/issues/5764
'⟇' => Some(MathClass::Binary),
c => unicode_math_class::class(c),
}
}

View File

@ -28,7 +28,7 @@ impl Scalar {
///
/// If the value is NaN, then it is set to `0.0` in the result.
pub const fn new(x: f64) -> Self {
Self(if is_nan(x) { 0.0 } else { x })
Self(if x.is_nan() { 0.0 } else { x })
}
/// Gets the value of this [`Scalar`].
@ -37,17 +37,6 @@ impl Scalar {
}
}
// We have to detect NaNs this way since `f64::is_nan` isnt const
// on stable yet:
// ([tracking issue](https://github.com/rust-lang/rust/issues/57241))
#[allow(clippy::unusual_byte_groupings)]
const fn is_nan(x: f64) -> bool {
// Safety: all bit patterns are valid for u64, and f64 has no padding bits.
// We cannot use `f64::to_bits` because it is not const.
let x_bits = unsafe { std::mem::transmute::<f64, u64>(x) };
(x_bits << 1 >> (64 - 12 + 1)) == 0b0_111_1111_1111 && (x_bits << 12) != 0
}
impl Numeric for Scalar {
fn zero() -> Self {
Self(0.0)

View File

@ -56,7 +56,7 @@ requirements with examples.
Typst's default page size is A4 paper. Depending on your region and your use
case, you will want to change this. You can do this by using the
[`{page}`]($page) set rule and passing it a string argument to use a common page
size. Options include the complete ISO 216 series (e.g. `"iso-a4"`, `"iso-c2"`),
size. Options include the complete ISO 216 series (e.g. `"a4"` and `"iso-c2"`),
customary US formats like `"us-legal"` or `"us-letter"`, and more. Check out the
reference for the [page's paper argument]($page.paper) to learn about all
available options.

View File

@ -170,8 +170,8 @@
category: symbols
path: ["emoji"]
details: |
Named emoji.
Named emojis.
For example, `#emoji.face` produces the 😀 emoji. If you frequently use
certain emojis, you can also import them from the `emoji` module (`[#import
emoji: face]`) to use them without the `#emoji.` prefix.
emoji: face]`) to use them without the `emoji.` prefix.

6
flake.lock generated
View File

@ -112,13 +112,13 @@
"rust-manifest": {
"flake": false,
"locked": {
"narHash": "sha256-Yqu2/i9170R7pQhvOCR1f5SyFr7PcFbO6xcMr9KWruQ=",
"narHash": "sha256-irgHsBXecwlFSdmP9MfGP06Cbpca2QALJdbN4cymcko=",
"type": "file",
"url": "https://static.rust-lang.org/dist/channel-rust-1.83.0.toml"
"url": "https://static.rust-lang.org/dist/channel-rust-1.85.0.toml"
},
"original": {
"type": "file",
"url": "https://static.rust-lang.org/dist/channel-rust-1.83.0.toml"
"url": "https://static.rust-lang.org/dist/channel-rust-1.85.0.toml"
}
},
"systems": {

View File

@ -10,7 +10,7 @@
inputs.nixpkgs.follows = "nixpkgs";
};
rust-manifest = {
url = "https://static.rust-lang.org/dist/channel-rust-1.83.0.toml";
url = "https://static.rust-lang.org/dist/channel-rust-1.85.0.toml";
flake = false;
};
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<table>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
</tr>
<tr>
<td>d</td>
<td>e</td>
<td>f</td>
</tr>
<tr>
<td>g</td>
<td>h</td>
<td>i</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<table>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
</tr>
<tr>
<td>d</td>
<td>e</td>
<td>f</td>
</tr>
<tr>
<td>g</td>
<td>h</td>
<td>i</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,2 @@
<!DOCTYPE html>
<html></html>

View File

@ -0,0 +1,2 @@
<!DOCTYPE html>
<html>Hi</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<table>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
</tr>
<tr>
<td>d</td>
<td>e</td>
<td>f</td>
</tr>
<tr>
<td>g</td>
<td>h</td>
<td>i</td>
</tr>
</table>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 B

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 B

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -149,7 +149,7 @@ impl Collector {
for entry in walkdir::WalkDir::new(crate::SUITE_PATH).sort_by_file_name() {
let entry = entry.unwrap();
let path = entry.path();
if !path.extension().is_some_and(|ext| ext == "typ") {
if path.extension().is_none_or(|ext| ext != "typ") {
continue;
}
@ -168,7 +168,7 @@ impl Collector {
for entry in walkdir::WalkDir::new(crate::REF_PATH).sort_by_file_name() {
let entry = entry.unwrap();
let path = entry.path();
if !path.extension().is_some_and(|ext| ext == "png") {
if path.extension().is_none_or(|ext| ext != "png") {
continue;
}

View File

@ -161,7 +161,7 @@ impl<'a> Runner<'a> {
// Compare against reference output if available.
// Test that is ok doesn't need to be updated.
if ref_data.as_ref().map_or(false, |r| D::matches(&live, r)) {
if ref_data.as_ref().is_ok_and(|r| D::matches(&live, r)) {
return;
}

15
tests/suite/html/elem.typ Normal file
View File

@ -0,0 +1,15 @@
--- html-elem-alone-context html ---
#context html.elem("html")
--- html-elem-not-alone html ---
// Error: 2-19 `<html>` element must be the only element in the document
#html.elem("html")
Text
--- html-elem-metadata html ---
#html.elem("html", context {
let val = query(<l>).first().value
test(val, "Hi")
val
})
#metadata("Hi") <l>

View File

@ -30,3 +30,30 @@
[row],
),
)
--- col-gutter-table html ---
#table(
columns: 3,
column-gutter: 3pt,
[a], [b], [c],
[d], [e], [f],
[g], [h], [i]
)
--- row-gutter-table html ---
#table(
columns: 3,
row-gutter: 3pt,
[a], [b], [c],
[d], [e], [f],
[g], [h], [i]
)
--- col-row-gutter-table html ---
#table(
columns: 3,
gutter: 3pt,
[a], [b], [c],
[d], [e], [f],
[g], [h], [i]
)

View File

@ -13,6 +13,11 @@ $ underline(f' : NN -> RR) \
1 - 0 thick &...,
) $
--- math-shorthands-noncontinuable ---
// Test that shorthands are not continuable.
$ x >=(y) / z \
x >= (y) / z $
--- math-common-symbols ---
// Test common symbols.
$ dot \ dots \ ast \ tilde \ star $