Compare commits

...

3 Commits

Author SHA1 Message Date
Johann Birnick
ef0fc6146a
Merge 73340f70a6f36bc46514a81b220373112fc7399e into 627f5b9d4f39f91affc529f276bb500d42887dae 2025-07-17 20:14:36 +02:00
Lachlan Kermode
627f5b9d4f
Add show rule for smallcaps in HTML (#6600) 2025-07-17 16:09:13 +00:00
Johann Birnick
73340f70a6 initial draft 2025-01-19 04:42:47 -08:00
6 changed files with 178 additions and 3 deletions

View File

@ -14,8 +14,8 @@ use typst_library::model::{
RefElem, StrongElem, TableCell, TableElem, TermsElem,
};
use typst_library::text::{
HighlightElem, LinebreakElem, OverlineElem, RawElem, RawLine, SpaceElem, StrikeElem,
SubElem, SuperElem, UnderlineElem,
HighlightElem, LinebreakElem, OverlineElem, RawElem, RawLine, SmallcapsElem,
SpaceElem, StrikeElem, SubElem, SuperElem, UnderlineElem,
};
use typst_library::visualize::ImageElem;
@ -47,6 +47,7 @@ pub fn register(rules: &mut NativeRuleMap) {
rules.register(Html, OVERLINE_RULE);
rules.register(Html, STRIKE_RULE);
rules.register(Html, HIGHLIGHT_RULE);
rules.register(Html, SMALLCAPS_RULE);
rules.register(Html, RAW_RULE);
rules.register(Html, RAW_LINE_RULE);
@ -390,6 +391,20 @@ const STRIKE_RULE: ShowFn<StrikeElem> =
const HIGHLIGHT_RULE: ShowFn<HighlightElem> =
|elem, _, _| Ok(HtmlElem::new(tag::mark).with_body(Some(elem.body.clone())).pack());
const SMALLCAPS_RULE: ShowFn<SmallcapsElem> = |elem, _, styles| {
Ok(HtmlElem::new(tag::span)
.with_attr(
attr::style,
if elem.all.get(styles) {
"font-variant-caps: all-small-caps"
} else {
"font-variant-caps: small-caps"
},
)
.with_body(Some(elem.body.clone()))
.pack())
};
const RAW_RULE: ShowFn<RawElem> = |elem, _, styles| {
let lines = elem.lines.as_deref().unwrap_or_default();

View File

@ -0,0 +1,92 @@
use comemo::Tracked;
use crate::diag::HintedStrResult;
use crate::engine::Engine;
use crate::foundations::{func, Array, Context, IntoValue, LocatableSelector, Selector};
/// Counts element in the document.
///
/// The `count` function lets you count elements in your document of a
/// particular type or with a particular label. It also allows to count them
/// in a hierachical way. To use it, you first need to ensure that [context]
/// is available.
///
/// Always returns an array of integers, even if called with just a single
/// target.
/// # Counting elements - simple
///
/// To just count elements of a single type/label up to the current location,
/// pass the selector you want to count:
/// ```example
/// = Heading
///
/// = Another Heading
///
/// #context count(heading)
///
/// = Third Heading
/// ```
///
/// Note that it will not return an integer, but an array with a single integer
/// entry.
/// # Counting elements - hierarchical
///
/// If you pass multiple targets, then it starts by counting the first target.
/// Then the second target is counted _starting only from the last counted
/// element of the first target_.
///
/// ```example
/// = Some Heading
///
/// == Some Subheading
///
/// = Another Heading
///
/// == Some Subheading
///
/// #context count(heading.where(level: 1), heading.where(level: 2))
///
/// == Another Subheading
/// ```
#[func(contextual)]
pub fn count(
/// The engine.
engine: &mut Engine,
/// The callsite context.
context: Tracked<Context>,
/// Each target can be
/// - an element function like a `heading` or `figure`,
/// - a `{<label>}`,
/// - a more complex selector like `{heading.where(level: 1)}`,
/// - or `{selector(heading).before(here())}`.
///
/// Only [locatable]($location/#locatable) element functions are supported.
#[variadic]
targets: Vec<LocatableSelector>,
/// When passing this argument, it will count everything only from a
/// certain location on.
/// The selector must match exactly one element in the document. The most
/// useful kinds of selectors for this are [labels]($label) and
/// [locations]($location).
// TODO remove Option as soon as there is a special `start` location
#[named]
after: Option<LocatableSelector>,
) -> HintedStrResult<Array> {
// NOTE this could be made more efficient
// one could directly get a slice &[Selector] from Vec<LocatableSelector>
// by using #[repr(transparent)] on LocatableSelector
let selectors: Vec<Selector> = targets.into_iter().map(|sel| sel.0).collect();
// TODO add argument "at" with default value "here"
// and compute `before` accordingly
let before = Some(context.location()?);
let after = match after {
Some(selector) => Some(selector.resolve_unique(engine.introspector, context)?),
None => None,
};
let nums = engine.introspector.count(&selectors, after, before);
Ok(nums.into_iter().map(IntoValue::into_value).collect())
}

View File

@ -254,6 +254,60 @@ impl Introspector {
}
}
/// Count hierarchically.
pub fn count(
&self,
selectors: &[Selector],
// TODO remove Option as soon as there is a special `start` location
after: Option<Location>,
// TODO remove Option as soon as there is a special `end` location
before: Option<Location>,
) -> SmallVec<[usize; 3]> {
let mut nums = SmallVec::with_capacity(selectors.len());
// can't write `mut` directly into function parameters because not supported by comemo
let mut after = after;
for selector in selectors.iter() {
let list = self.query(selector);
// count how many elements in `list` are between after and end
let index_start = if let Some(after) = after {
if let Some(after) = self.get_by_loc(&after) {
match self.binary_search(&list, after) {
Ok(i) => i, // TODO if after is exclusive then must add 1 here
Err(i) => i,
}
} else {
0
}
} else {
0
};
let index_end = if let Some(before) = before {
if let Some(before) = self.get_by_loc(&before) {
match self.binary_search(&list, before) {
Ok(i) => i + 1,
Err(i) => i,
}
} else {
list.len()
}
} else {
list.len()
};
nums.push(index_end - index_start);
// the next level should be counted only from
// the last element of the previous level on
if index_end != 0 {
after = Some(list[index_end - 1].location().unwrap());
}
}
nums
}
/// The total number pages.
pub fn pages(&self) -> NonZeroUsize {
NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE)

View File

@ -1,5 +1,7 @@
//! Interaction between document parts.
#[path = "count.rs"]
mod count_;
mod counter;
#[path = "here.rs"]
mod here_;
@ -14,6 +16,7 @@ mod query_;
mod state;
mod tag;
pub use self::count_::*;
pub use self::counter::*;
pub use self::here_::*;
pub use self::introspector::*;
@ -36,6 +39,7 @@ pub fn define(global: &mut Scope) {
global.define_elem::<MetadataElem>();
global.define_func::<here>();
global.define_func::<query>();
global.define_func::<count>();
global.define_func::<locate>();
global.reset_category();
}

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>
<p><span style="font-variant-caps: small-caps">Test 012</span><br><span style="font-variant-caps: all-small-caps">Test 012</span></p>
</body>
</html>

View File

@ -11,6 +11,6 @@
#show smallcaps: set text(fill: red)
#smallcaps[Smallcaps]
--- smallcaps-all ---
--- smallcaps-all render html ---
#smallcaps(all: false)[Test 012] \
#smallcaps(all: true)[Test 012]