Merge branch 'main' into bibliography-entry
6
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
||||
|
2
.github/workflows/release.yml
vendored
@ -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 }}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
@ -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: ¬ify::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: ¬ify::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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 };
|
||||
|
@ -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
|
||||
})
|
||||
{
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
});
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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` isn’t 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)
|
||||
|
@ -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.
|
||||
|
@ -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
@ -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": {
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
26
tests/ref/html/col-gutter-table.html
Normal 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>
|
26
tests/ref/html/col-row-gutter-table.html
Normal 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>
|
2
tests/ref/html/html-elem-alone-context.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<html></html>
|
2
tests/ref/html/html-elem-metadata.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<html>Hi</html>
|
26
tests/ref/html/row-gutter-table.html
Normal 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>
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 614 B After Width: | Height: | Size: 625 B |
Before Width: | Height: | Size: 327 B After Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 284 B |
Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 930 B |
BIN
tests/ref/math-shorthands-noncontinuable.png
Normal file
After Width: | Height: | Size: 475 B |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
@ -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>
|
@ -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]
|
||||
)
|
||||
|
@ -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 $
|
||||
|