Document text category

This commit is contained in:
Laurenz 2022-12-21 00:16:07 +01:00
parent 15cd273c82
commit ee732468c7
27 changed files with 894 additions and 73 deletions

View File

@ -67,6 +67,10 @@ impl TableNode {
fn field(&self, name: &str) -> Option<Value> {
match name {
"columns" => Some(TrackSizing::encode_slice(&self.tracks.x)),
"rows" => Some(TrackSizing::encode_slice(&self.tracks.y)),
"column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)),
"row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)),
"cells" => Some(Value::Array(
self.cells.iter().cloned().map(Value::Content).collect(),
)),

View File

@ -65,6 +65,14 @@ impl ColumnsNode {
}
.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"count" => Some(Value::Int(self.count.get() as i64)),
"body" => Some(Value::Content(self.body.clone())),
_ => None,
}
}
}
impl Layout for ColumnsNode {
@ -186,6 +194,13 @@ impl ColbreakNode {
let weak = args.named("weak")?.unwrap_or(false);
Ok(Self { weak }.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"weak" => Some(Value::Bool(self.weak)),
_ => None,
}
}
}
impl Behave for ColbreakNode {

View File

@ -50,6 +50,13 @@ impl BoxNode {
let body = args.eat::<Content>()?.unwrap_or_default();
Ok(Self { sizing: Axes::new(width, height), body }.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.body.clone())),
_ => None,
}
}
}
impl Layout for BoxNode {
@ -163,6 +170,13 @@ impl BlockNode {
args.named("below")?.map(VNode::block_around).or(spacing),
);
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.0.clone())),
_ => None,
}
}
}
impl Layout for BlockNode {

View File

@ -120,6 +120,19 @@ impl GridNode {
}
.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"columns" => Some(TrackSizing::encode_slice(&self.tracks.x)),
"rows" => Some(TrackSizing::encode_slice(&self.tracks.y)),
"column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)),
"row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)),
"cells" => Some(Value::Array(
self.cells.iter().cloned().map(Value::Content).collect(),
)),
_ => None,
}
}
}
impl Layout for GridNode {
@ -157,6 +170,20 @@ pub enum TrackSizing {
Fractional(Fr),
}
impl TrackSizing {
pub fn encode(self) -> Value {
match self {
Self::Auto => Value::Auto,
Self::Relative(rel) => Spacing::Relative(rel).encode(),
Self::Fractional(fr) => Spacing::Fractional(fr).encode(),
}
}
pub fn encode_slice(vec: &[TrackSizing]) -> Value {
Value::Array(vec.iter().copied().map(Self::encode).collect())
}
}
impl From<Spacing> for TrackSizing {
fn from(spacing: Spacing) -> Self {
match spacing {

View File

@ -30,6 +30,13 @@ impl HideNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.0.clone())),
_ => None,
}
}
}
impl Layout for HideNode {

View File

@ -67,6 +67,13 @@ impl PadNode {
let padding = Sides::new(left, top, right, bottom);
Ok(Self { padding, body }.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.body.clone())),
_ => None,
}
}
}
impl Layout for PadNode {

View File

@ -64,6 +64,13 @@ impl PageNode {
styles.set(Self::HEIGHT, Smart::Custom(paper.height().into()));
}
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.0.clone())),
_ => None,
}
}
}
impl PageNode {
@ -190,6 +197,13 @@ impl PagebreakNode {
let weak = args.named("weak")?.unwrap_or(false);
Ok(Self { weak }.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"weak" => Some(Value::Bool(self.weak)),
_ => None,
}
}
}
/// A header, footer, foreground or background definition.

View File

@ -467,7 +467,7 @@ fn collect<'a>(
Segment::Text(c.len_utf8())
} else if let Some(node) = child.to::<SmartQuoteNode>() {
let prev = full.len();
if styles.get(TextNode::SMART_QUOTES) {
if styles.get(SmartQuoteNode::ENABLED) {
let lang = styles.get(TextNode::LANG);
let region = styles.get(TextNode::REGION);
let quotes = Quotes::from_lang(lang, region);

View File

@ -33,6 +33,13 @@ impl PlaceNode {
let out_of_flow = aligns.y.is_some();
Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns), out_of_flow).pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.0.clone())),
_ => None,
}
}
}
impl Layout for PlaceNode {

View File

@ -19,6 +19,13 @@ impl RepeatNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.0.clone())),
_ => None,
}
}
}
impl Layout for RepeatNode {

View File

@ -54,10 +54,18 @@ pub struct HNode {
#[node]
impl HNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let amount = args.expect("spacing")?;
let amount = args.expect("amount")?;
let weak = args.named("weak")?.unwrap_or(false);
Ok(Self { amount, weak }.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"amount" => Some(self.amount.encode()),
"weak" => Some(Value::Bool(self.weak)),
_ => None,
}
}
}
impl HNode {
@ -159,6 +167,14 @@ impl VNode {
};
Ok(node.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"amount" => Some(self.amount.encode()),
"weak" => Some(Value::Bool(self.weakness != 0)),
_ => None,
}
}
}
impl VNode {
@ -220,6 +236,22 @@ impl Spacing {
pub fn is_fractional(self) -> bool {
matches!(self, Self::Fractional(_))
}
/// Encode into a value.
pub fn encode(self) -> Value {
match self {
Self::Relative(rel) => {
if rel.rel.is_zero() {
Value::Length(rel.abs)
} else if rel.abs.is_zero() {
Value::Ratio(rel.rel)
} else {
Value::Relative(rel)
}
}
Self::Fractional(fr) => Value::Fraction(fr),
}
}
}
impl From<Abs> for Spacing {

View File

@ -40,6 +40,26 @@ impl StackNode {
}
.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"dir" => Some(Value::dynamic(self.dir)),
"spacing" => Some(match self.spacing {
Some(spacing) => spacing.encode(),
None => Value::None,
}),
"items" => Some(Value::Array(
self.children
.iter()
.map(|child| match child {
StackChild::Spacing(spacing) => spacing.encode(),
StackChild::Block(content) => Value::Content(content.clone()),
})
.collect(),
)),
_ => None,
}
}
}
impl Layout for StackNode {

View File

@ -31,6 +31,7 @@ use crate::prelude::*;
/// ### Example
/// ```
/// Hello, world!#move(dy: -2pt)[!]#move(dy: 2pt)[!]
/// ```
///
/// - dx: Rel<Length> (named)
/// The horizontal displacement of the content.
@ -61,6 +62,13 @@ impl MoveNode {
}
.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.body.clone())),
_ => None,
}
}
}
impl Layout for MoveNode {
@ -130,6 +138,13 @@ impl RotateNode {
}
.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.body.clone())),
_ => None,
}
}
}
impl Layout for RotateNode {
@ -209,6 +224,13 @@ impl ScaleNode {
}
.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.body.clone())),
_ => None,
}
}
}
impl Layout for ScaleNode {

View File

@ -22,6 +22,15 @@ impl VecNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"elements" => {
Some(Value::Array(self.0.iter().cloned().map(Value::Content).collect()))
}
_ => None,
}
}
}
impl Texify for VecNode {
@ -89,6 +98,15 @@ impl CasesNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"branches" => {
Some(Value::Array(self.0.iter().cloned().map(Value::Content).collect()))
}
_ => None,
}
}
}
impl Texify for CasesNode {

View File

@ -5,7 +5,9 @@ use crate::text::TextNode;
/// Link to a URL or another location in the document.
///
/// The link function makes its positional `body` argument clickable and links
/// it to the destination specified by the `dest` argument.
/// it to the destination specified by the `dest` argument. By default, links
/// are not styled any different from normal text. However, you can easily apply
/// a style of your choice with a show rule.
///
/// ## Example
/// ```
@ -85,7 +87,7 @@ impl LinkNode {
fn field(&self, name: &str) -> Option<Value> {
match name {
"url" => Some(match &self.dest {
"dest" => Some(match &self.dest {
Destination::Url(url) => Value::Str(url.clone().into()),
Destination::Internal(loc) => Value::Dict(loc.encode()),
}),

View File

@ -27,12 +27,41 @@ impl Behave for SpaceNode {
}
/// # Line Break
/// A line break.
/// Inserts a line break.
///
/// Advances the paragraph to the next line. A single trailing linebreak at the
/// end of a paragraph is ignored, but more than one creates additional empty
/// lines.
///
/// ## Example
/// ```
/// *Date:* 26.12.2022 \
/// *Topic:* Infrastructure Test \
/// *Severity:* High \
/// ```
///
/// ## Syntax
/// This function also has dedicated syntax: To insert a linebreak, simply write
/// a backslash followed by whitespace. This always creates an unjustified
/// break.
///
/// ## Parameters
/// - justify: bool (named)
/// Whether to justify the line before the break.
///
/// This is useful if you found a better line break opportunity in your
/// justified text than Typst did.
///
/// ### Example
/// ```
/// #set par(justify: true)
/// #let jb = linebreak(justify: true)
///
/// I have manually tuned the #jb
/// linebreaks in this paragraph #jb
/// for an _interesting_ result. #jb
/// ```
///
/// ## Category
/// text
#[func]
@ -48,6 +77,13 @@ impl LinebreakNode {
let justify = args.named("justify")?.unwrap_or(false);
Ok(Self { justify }.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"justify" => Some(Value::Bool(self.justify)),
_ => None,
}
}
}
impl Behave for LinebreakNode {
@ -59,6 +95,23 @@ impl Behave for LinebreakNode {
/// # Strong Emphasis
/// Strongly emphasizes content by increasing the font weight.
///
/// Increases the current font weight by a given `delta`.
///
/// ## Example
/// ```
/// This is *strong.* \
/// This is #strong[too.] \
///
/// #show strong: set text(red)
/// And this is *evermore.*
/// ```
///
/// ## Syntax
/// This function also has dedicated syntax: To strongly emphasize content,
/// simply enclose it in stars/asterisks (`*`). Note that this only works at
/// word boundaries. To strongly emphasize part of a word, you have to use the
/// function.
///
/// ## Parameters
/// - body: Content (positional, required)
/// The content to strongly emphasize.
@ -73,6 +126,12 @@ pub struct StrongNode(pub Content);
#[node]
impl StrongNode {
/// The delta to apply on the font weight.
///
/// # Example
/// ```
/// #set strong(delta: 0)
/// No *effect!*
/// ```
pub const DELTA: i64 = 300;
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
@ -111,7 +170,29 @@ impl Fold for Delta {
}
/// # Emphasis
/// Emphasizes content by flipping the italicness.
/// Emphasizes content by setting it in italics.
///
/// - If the current [text](@text) style is `{"normal"}`,
/// this turns it into `{"italic"}`.
/// - If it is already `{"italic"}` or `{"oblique"}`,
/// it turns it back to `{"normal"}`.
///
/// ## Example
/// ```
/// This is _emphasized._ \
/// This is #emph[too.]
///
/// #show emph: it => {
/// text(blue, it.body)
/// }
///
/// This is _emphasized_ differently.
/// ```
///
/// ## Syntax
/// This function also has dedicated syntax: To emphasize content, simply
/// enclose it in underscores (`_`). Note that this only works at word
/// boundaries. To emphasize part of a word, you have to use the function.
///
/// ## Parameters
/// - body: Content (positional, required)
@ -159,6 +240,13 @@ impl Fold for Toggle {
/// # Lowercase
/// Convert text or content to lowercase.
///
/// ## Example
/// ```
/// #lower("ABC") \
/// #lower[*My Text*] \
/// #lower[already low]
/// ```
///
/// ## Parameters
/// - text: ToCase (positional, required)
/// The text to convert to lowercase.
@ -173,6 +261,13 @@ pub fn lower(args: &mut Args) -> SourceResult<Value> {
/// # Uppercase
/// Convert text or content to uppercase.
///
/// ## Example
/// ```
/// #upper("abc") \
/// #upper[*my text*] \
/// #upper[ALREADY HIGH]
/// ```
///
/// ## Parameters
/// - text: ToCase (positional, required)
/// The text to convert to uppercase.
@ -225,6 +320,29 @@ impl Case {
/// # Small Capitals
/// Display text in small capitals.
///
/// _Note:_ This enables the OpenType `smcp` feature for the font. Not all fonts
/// support this feature (including Typst's current default font,
/// unfortunately). Sometimes smallcaps are part of a dedicated font and
/// sometimes they are not available at all. In the future, this function will
/// support selecting a dedicated smallcaps font as well as synthesizing
/// smallcaps from normal letters, but this is not yet implemented.
///
/// ## Example
/// ```
/// #set par(justify: true)
/// #set text(family: "Noto Serif")
/// #set heading(numbering: "I.")
///
/// #show heading: it => {
/// set block(below: 10pt)
/// set text(weight: "regular")
/// align(center, smallcaps(it))
/// }
///
/// = Introduction
/// #lorem(40)
/// ```
///
/// ## Parameters
/// - text: Content (positional, required)
/// The text to display to small capitals.

View File

@ -26,14 +26,45 @@ use crate::layout::ParNode;
use crate::prelude::*;
/// # Text
/// Stylable text.
/// Customize the look and layout of text in a variety of ways.
///
/// This function is used often, both with set rules and directly. While the set
/// rule is often the simpler choice, calling the text function directly can be
/// useful when passing text as an argument to another function.
///
/// ## Example
/// ```
/// #set text(18pt)
/// With a set rule.
///
/// #emph(text(blue)[
/// With a function call.
/// ])
/// ```
///
/// ## Parameters
/// - family: EcoString (positional, variadic, settable)
/// A prioritized sequence of font families.
/// - family: EcoString (positional, variadic, settable) A prioritized sequence
/// of font families.
///
/// - body: Content (positional, required)
/// Content in which all text is styled according to the other arguments.
/// When processing text, Typst tries all specified font families in order
/// until it finds a font that has the necessary glyphs. In the example below,
/// the font `Inria Serif` is preferred, but since it does not contain Arabic
/// glyphs, the arabic text uses `Noto Sans Arabic` instead.
///
/// ### Example
/// ```
/// #set text(
/// "Inria Serif",
/// "Noto Sans Arabic",
/// )
///
/// This is Latin. \
/// هذا عربي.
///
/// ```
///
/// - body: Content (positional, required) Content in which all text is styled
/// according to the other arguments.
///
/// ## Category
/// text
@ -54,75 +85,383 @@ impl TextNode {
/// A prioritized sequence of font families.
#[property(skip, referenced)]
pub const FAMILY: FallbackList = FallbackList(vec![FontFamily::new("IBM Plex Sans")]);
/// Whether to allow font fallback when the primary font list contains no
/// match.
/// Whether to allow last resort font fallback when the primary font list
/// contains no match. This lets Typst search through all available fonts
/// for the most similar one that has the necessary glyphs.
///
/// _Note:_ Currently, there are no warnings when fallback is disabled and
/// no glyphs are found. Instead, your text shows up in the form of "tofus":
/// Small boxes that indicate the lack of an appropriate glyph. In the
/// future, you will be able to instruct Typst to issue warnings so you know
/// something is up.
///
/// # Example
/// ```
/// #set text(family: "Inria Serif")
/// هذا عربي
///
/// #set text(fallback: false)
/// هذا عربي
/// ```
pub const FALLBACK: bool = true;
/// How the font is styled.
/// The desired font style.
///
/// When an italic style is requested and only an oblique one is available,
/// it is used. Similarly, the other way around, an italic style can stand
/// in for an oblique one. When neither an italic nor an oblique style is
/// available, Typst selects the normal style. Since most fonts are only
/// available either in an italic or oblique style, the difference between
/// italic and oblique style is rarely observable.
///
/// If you want to emphasize your text, you should do so using the
/// [emph](@emph) function instead. This makes it easy to adapt the style
/// later if you change your mind about how to signify the emphasis.
///
/// # Example
/// ```
/// #text("IBM Plex Sans", style: "italic")[Italic]
/// #text("DejaVu Sans", style: "oblique")[Oblique]
/// ```
pub const STYLE: FontStyle = FontStyle::Normal;
/// The boldness / thickness of the font's glyphs.
/// The desired thickness of the font's glyphs. Accepts an integer between
/// `{100}` and `{900}` or one of the predefined weight names. When the
/// desired weight is not available, Typst selects the font from the family
/// that is closest in weight.
///
/// If you want to strongly emphasize your text, you should do so using the
/// [strong](@strong) function instead. This makes it easy to adapt the
/// style later if you change your mind about how to signify the strong
/// emphasis.
///
/// # Example
/// ```
/// #text(weight: "light")[Light] \
/// #text(weight: "regular")[Regular] \
/// #text(weight: "medium")[Medium] \
/// #text(weight: "bold")[Bold]
/// ```
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
/// The width of the glyphs.
/// The desired width of the glyphs. Accepts a ratio between `{50%}` and
/// `{200%}`. When the desired weight is not available, Typst selects the
/// font from the family that is closest in stretch.
///
/// # Example
/// ```
/// #text(stretch: 75%)[Condensed] \
/// #text(stretch: 100%)[Normal]
/// ```
pub const STRETCH: FontStretch = FontStretch::NORMAL;
/// The size of the glyphs.
/// The size of the glyphs. This value forms the basis of the `em` unit:
/// `{1em}` is equivalent to the font size.
///
/// You can also give the font size itself in `em` units. Then, it is
/// relative to the previous font size.
///
/// # Example
/// ```
/// #set text(size: 20pt)
/// very #text(1.5em)[big] text
/// ```
#[property(shorthand, fold)]
pub const SIZE: TextSize = Abs::pt(11.0);
/// The glyph fill color.
///
/// # Example
/// ```
/// #set text(fill: red)
/// This text is red.
/// ```
#[property(shorthand)]
pub const FILL: Paint = Color::BLACK.into();
/// The amount of space that should be added between characters.
///
/// # Example
/// ```
/// #set text(tracking: 1.5pt)
/// Distant text.
/// ```
#[property(resolve)]
pub const TRACKING: Length = Length::zero();
/// The width of spaces relative to the font's space width.
/// The amount of space between words.
///
/// Can be given as an absolute length, but also relative to the width of
/// the space character in the font.
///
/// # Example
/// ```
/// #set text(spacing: 200%)
/// Text with distant words.
/// ```
#[property(resolve)]
pub const SPACING: Rel<Length> = Rel::one();
/// The offset of the baseline.
/// An amount to shift the text baseline by.
///
/// # Example
/// ```
/// A #text(baseline: 3pt)[lowered]
/// word.
/// ```
#[property(resolve)]
pub const BASELINE: Length = Length::zero();
/// Whether certain glyphs can hang over into the margin.
/// Whether certain glyphs can hang over into the margin in justified text.
/// This can make justification visually more pleasing.
///
/// # Example
/// ```
/// #set par(justify: true)
/// In this particular text, the
/// justification produces a hyphen
/// in the first line. Letting this
/// hyphen hang slightly into the
/// margin makes for a clear
/// paragraph edge.
///
/// #set text(overhang: false)
/// In this particular text, the
/// justification produces a hyphen
/// in the first line. This time the
/// hyphen does not hang into the
/// margin, making the paragraph's
/// edge less clear.
/// ```
pub const OVERHANG: bool = true;
/// The top end of the text bounding box.
/// The top end of the conceptual frame around the text used for layout and
/// positioning. This affects the size of containers that hold text.
///
/// # Example
/// ```
/// #set text(size: 20pt)
/// #set text(top-edge: "ascender")
/// #rect(fill: aqua)[Typst]
///
/// #set text(top-edge: "cap-height")
/// #rect(fill: aqua)[Typst]
/// ```
pub const TOP_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::CapHeight);
/// The bottom end of the text bounding box.
/// The bottom end of the conceptual frame around the text used for layout
/// and positioning. This affects the size of containers that hold text.
///
/// # Example
/// ```
/// #set text(size: 20pt)
/// #set text(bottom-edge: "baseline")
/// #rect(fill: aqua)[Typst]
///
/// #set text(bottom-edge: "descender")
/// #rect(fill: aqua)[Typst]
/// ```
pub const BOTTOM_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::Baseline);
/// An ISO 639-1/2/3 language code.
/// An [ISO 639-1/2/3 language code.](https://en.wikipedia.org/wiki/ISO_639)
///
/// Setting the correct language affects various parts of Typst:
///
/// - The text processing pipeline can make more informed choices.
/// - Hyphenation will use the correct patterns for the language.
/// - [Smart quotes](@smartquote) turns into the correct quotes for the
/// language.
/// - And all other things which are language-aware.
///
/// # Example
/// ```
/// #set text(lang: "de")
/// #outline()
///
/// = Einleitung
/// In diesem Dokument, ...
/// ```
pub const LANG: Lang = Lang::ENGLISH;
/// An ISO 3166-1 alpha-2 region code.
/// An [ISO 3166-1 alpha-2 region code.](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
///
/// This lets the text processing pipeline make more informed choices.
pub const REGION: Option<Region> = None;
/// The direction for text and inline objects. When `auto`, the direction is
/// automatically inferred from the language.
/// The dominant direction for text and inline objects. Possible values are:
///
/// - `{auto}`: Automatically infer the direction from the `lang` property.
/// - `{ltr}`: Layout text from left to right.
/// - `{rtl}`: Layout text from right to left.
///
/// When writing in right-to-left scripts like Arabic or Hebrew, you should
/// set the language or direction. While individual runs of text are
/// automatically layouted in the correct direction, setting the dominant
/// direction gives the bidirectional reordering algorithm the necessary
/// information to correctly place punctuation and inline objects.
/// Furthermore, setting the direction affects the alignment values `start`
/// and `end`, which are equivalent to `left` and `right` in `ltr` text and
/// the other way around in `rtl` text.
///
/// If you set this to `rtl` and experience bugs or in some way bad looking
/// output, please do get in touch with us through the [contact
/// form](/contact) or our [Discord server](/docs/community/#discord)!
///
/// # Example
/// ```
/// #set text(dir: rtl)
/// هذا عربي.
/// ```
#[property(resolve)]
pub const DIR: HorizontalDir = HorizontalDir(Smart::Auto);
/// Whether to hyphenate text to improve line breaking. When `auto`, words
/// will will be hyphenated if and only if justification is enabled.
/// Whether to hyphenate text to improve line breaking. When `{auto}`, text
/// will be hyphenated if and only if justification is enabled.
///
/// # Example
/// ```
/// #set par(justify: true)
/// This text illustrates how
/// enabling hyphenation can
/// improve justification.
///
/// #set text(hyphenate: false)
/// This text illustrates how
/// enabling hyphenation can
/// improve justification.
/// ```
#[property(resolve)]
pub const HYPHENATE: Hyphenate = Hyphenate(Smart::Auto);
/// Whether to apply smart quotes.
pub const SMART_QUOTES: bool = true;
/// Whether to apply kerning ("kern").
/// Whether to apply kerning.
///
/// When enabled, specific letter pairings move closer together or further
/// apart for a more visually pleasing result. The example below
/// demonstrates how decreasing the gap between the "T" and "o" results in a
/// more natural look. Setting this to `{false}` disables kerning by turning
/// off the OpenType `kern` font feature.
///
/// # Example
/// ```
/// #set text(size: 25pt)
/// Totally
///
/// #set text(kerning: false)
/// Totally
/// ```
pub const KERNING: bool = true;
/// Whether to apply stylistic alternates. ("salt")
/// Whether to apply stylistic alternates.
///
/// Sometimes fonts contain alternative glyphs for the same codepoint.
/// Setting this to `{true}` switches to these by enabling the OpenType
/// `salt` font feature.
///
/// # Example
/// ```
/// #set text(size: 20pt)
/// 0, a, g, ß
///
/// #set text(alternates: true)
/// 0, a, g, ß
/// ```
pub const ALTERNATES: bool = false;
/// Which stylistic set to apply. ("ss01" - "ss20")
/// Which stylistic set to apply. Font designers can categorize alternative
/// glyphs forms into stylistic sets. As this value is highly font-specific,
/// you need to consult your font to know which sets are available. When set
/// to an integer between `{1}` and `{20}`, enables the corresponding
/// OpenType font feature from `ss01`, ..., `ss20`.
pub const STYLISTIC_SET: Option<StylisticSet> = None;
/// Whether standard ligatures are active. ("liga", "clig")
/// Whether standard ligatures are active.
///
/// Certain letter combinations like "fi" are often displayed as a single
/// merged glyph called a _ligature._ Setting this to `{false}` disables
/// these ligatures by turning off the OpenType `liga` and `clig` font
/// features.
///
/// # Example
/// ```
/// #set text(size: 20pt)
/// A fine ligature.
///
/// #set text(ligatures: false)
/// A fine ligature.
/// ```
pub const LIGATURES: bool = true;
/// Whether ligatures that should be used sparingly are active. ("dlig")
/// Whether ligatures that should be used sparingly are active. Setting this
/// to `{true}` enables the OpenType `dlig` font feature.
pub const DISCRETIONARY_LIGATURES: bool = false;
/// Whether historical ligatures are active. ("hlig")
/// Whether historical ligatures are active. Setting this to `{true}`
/// enables the OpenType `hlig` font feature.
pub const HISTORICAL_LIGATURES: bool = false;
/// Which kind of numbers / figures to select.
/// Which kind of numbers / figures to select. When set to `{auto}`, the
/// default numbers for the font are used.
///
/// # Example
/// ```
/// #set text(20pt, "Noto Sans")
/// #set text(number-type: "lining")
/// Number 9.
///
/// #set text(number-type: "old-style")
/// Number 9.
/// ```
pub const NUMBER_TYPE: Smart<NumberType> = Smart::Auto;
/// The width of numbers / figures.
/// The width of numbers / figures. When set to `{auto}`, the default
/// numbers for the font are used.
///
/// # Example
/// ```
/// #set text(20pt, "Noto Sans")
/// #set text(number-width: "proportional")
/// A 12 B 34. \
/// A 56 B 78.
///
/// #set text(number-width: "tabular")
/// A 12 B 34. \
/// A 56 B 78.
/// ```
pub const NUMBER_WIDTH: Smart<NumberWidth> = Smart::Auto;
/// Whether to have a slash through the zero glyph. ("zero")
/// Whether to have a slash through the zero glyph. Setting this to `{true}`
/// enables the OpenType `zero` font feature.
///
/// # Example
/// ```
/// 0, #text(slashed-zero: true)[0]
/// ```
pub const SLASHED_ZERO: bool = false;
/// Whether to convert fractions. ("frac")
/// Whether to turns numbers into fractions. Setting this to `{true}`
/// enables the OpenType `frac` font feature.
///
/// # Example
/// ```
/// 1/2 \
/// #text(fractions: true)[1/2]
/// ```
pub const FRACTIONS: bool = false;
/// Raw OpenType features to apply.
///
/// - If given an array of strings, sets the features identified by the
/// strings to `{1}`.
/// - If given a dictionary mapping to numbers, sets the features
/// identified by the keys to the values.
///
/// # Example
/// ```
/// // Enable the `frac` feature manually.
/// #set text(features: ("frac",))
/// 1/2
/// ```
#[property(fold)]
pub const FEATURES: FontFeatures = FontFeatures(vec![]);
@ -272,7 +611,7 @@ impl TextEdge {
castable! {
TextEdge,
v: Length => Self::Length(v),
/// The distance from the baseline to the ascender.
/// The font's ascender, which typically exceeds the height of all glyphs.
"ascender" => Self::Metric(VerticalFontMetric::Ascender),
/// The approximate height of uppercase letters.
"cap-height" => Self::Metric(VerticalFontMetric::CapHeight),
@ -280,7 +619,7 @@ castable! {
"x-height" => Self::Metric(VerticalFontMetric::XHeight),
/// The baseline on which the letters rest.
"baseline" => Self::Metric(VerticalFontMetric::Baseline),
/// The distance from the baseline to the descender.
/// The font's ascender, which typically exceeds the depth of all glyphs.
"descender" => Self::Metric(VerticalFontMetric::Descender),
}
@ -364,9 +703,11 @@ pub enum NumberType {
castable! {
NumberType,
/// Numbers that fit well with capital text.
/// Numbers that fit well with capital text (the OpenType `lnum`
/// font feature).
"lining" => Self::Lining,
/// Numbers that fit well into a flow of upper- and lowercase text.
/// Numbers that fit well into a flow of upper- and lowercase text (the
/// OpenType `onum` font feature).
"old-style" => Self::OldStyle,
}
@ -381,9 +722,9 @@ pub enum NumberWidth {
castable! {
NumberWidth,
/// Number widths are glyph specific.
/// Numbers with glyph-specific widths (the OpenType `pnum` font feature).
"proportional" => Self::Proportional,
/// All numbers are of equal width / monospaced.
/// Numbers of equal width (the OpenType `tnum` font feature).
"tabular" => Self::Tabular,
}

View File

@ -3,11 +3,29 @@ use typst::syntax::is_newline;
use crate::prelude::*;
/// # Smart Quote
/// A smart quote.
/// A language-aware quote that reacts to its context.
///
/// Automatically turns into an appropriate opening or closing quote based on
/// the active [text](@text) language.
///
/// ## Syntax
/// This function also has dedicated syntax: The normal quote characters
/// (`'` and `"`). Typst automatically makes your quotes smart.
///
/// ## Example
/// ```
/// "This is in quotes."
///
/// #set text(lang: "de")
/// "Das ist in Anführungszeichen."
///
/// #set text(lang: "fr")
/// "C'est entre guillemets."
/// ```
///
/// ## Parameters
/// - double: bool (named)
/// Whether to produce a smart double quote.
/// Whether this should be a double quote.
///
/// ## Category
/// text
@ -20,10 +38,30 @@ pub struct SmartQuoteNode {
#[node]
impl SmartQuoteNode {
/// Whether smart quotes are enabled.
///
/// To disable smartness for a single quote, you can also escape it with a
/// backslash.
///
/// # Example
/// ```
/// #set smartquote(enabled: false)
///
/// These are "dumb" quotes.
/// ```
pub const ENABLED: bool = true;
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let double = args.named("double")?.unwrap_or(true);
Ok(Self { double }.pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"double" => Some(Value::Bool(self.double)),
_ => None,
}
}
}
/// State machine for smart quote subtitution.

View File

@ -2,24 +2,93 @@ use once_cell::sync::Lazy;
use syntect::highlighting as synt;
use typst::syntax::{self, LinkedNode};
use super::{FontFamily, Hyphenate, LinebreakNode, TextNode};
use super::{FontFamily, Hyphenate, LinebreakNode, SmartQuoteNode, TextNode};
use crate::layout::BlockNode;
use crate::prelude::*;
/// # Raw Text
/// # Raw Text / Code
/// Raw text with optional syntax highlighting.
///
/// Displays the text verbatim and in a monospace font. This is typically used
/// to embed computer code into your document.
///
/// ## Syntax
/// This function also has dedicated syntax. You can enclose text in 1 or 3+
/// backticks (`` ` ``) to make it raw. Two backticks produce empty raw text.
/// When you use three or more backticks, you can additionally specify a
/// language tag for syntax highlighting directly after the opening backticks.
/// Within raw blocks, everything is rendered as is, in particular, there are no
/// escape sequences.
///
/// ## Example
/// ````
/// Adding `rbx` to `rcx` gives
/// the desired result.
///
/// ```rust
/// fn main() {
/// println!("Hello World!");
/// }
/// ```
/// ````
///
/// ## Parameters
/// - text: EcoString (positional, required)
/// The raw text.
///
/// You can also use raw blocks creatively to create custom syntaxes for
/// your automations.
///
/// ### Example
/// ````
/// // Parse numbers in raw blocks with the `mydsl` tag and
/// // sum them up.
/// #show raw.where(lang: "mydsl"): it => {
/// let sum = 0
/// for part in it.text.split("+") {
/// sum += int(part.trim())
/// }
/// sum
/// }
///
/// ```mydsl
/// 1 + 2 + 3 + 4 + 5
/// ```
/// ````
///
/// - block: bool (named)
/// Whether the raw text is displayed as a separate block.
///
/// ### Example
/// ````
/// // Display inline code in a small box
/// // that retains the correct baseline.
/// #show raw.where(block: false): rect.with(
/// fill: luma(240),
/// inset: (x: 3pt),
/// outset: (y: 3pt),
/// radius: 2pt,
/// )
///
/// // Display block code in a larger box
/// // with more padding.
/// #show raw.where(block: true): rect.with(
/// fill: luma(240),
/// inset: 10pt,
/// radius: 4pt,
/// )
///
/// With `rg`, you can search through your files quickly.
///
/// ```bash
/// rg "Hello World"
/// ```
/// ````
///
/// ## Category
/// text
#[func]
#[capable(Show)]
#[capable(Show, Prepare)]
#[derive(Debug, Hash)]
pub struct RawNode {
/// The raw text.
@ -31,6 +100,17 @@ pub struct RawNode {
#[node]
impl RawNode {
/// The language to syntax-highlight in.
///
/// Apart from typical language tags known from Markdown, this supports the
/// `{"typ"}` and `{"typc"}` tags for Typst markup and Typst code,
/// respectively.
///
/// # Example
/// ````
/// ```typ
/// This is *Typst!*
/// ```
/// ````
#[property(referenced)]
pub const LANG: Option<EcoString> = None;
@ -121,7 +201,7 @@ impl Show for RawNode {
let mut map = StyleMap::new();
map.set(TextNode::OVERHANG, false);
map.set(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false)));
map.set(TextNode::SMART_QUOTES, false);
map.set(SmartQuoteNode::ENABLED, false);
map.set_family(FontFamily::new("IBM Plex Mono"), styles);
Ok(realized.styled_with_map(map))

View File

@ -4,9 +4,49 @@ use crate::text::TextNode;
/// # Symbol
/// A symbol identified by symmie notation.
///
/// Symmie is Typst's notation for Unicode symbols. It is based on the idea of
/// _modifiers._ Many symbols in Unicode are very similar. In symmie, such
/// groups of symbols share a common name. To distinguish between the symbols
/// within a group, we use one or multiple modifiers that are separated from the
/// name by colons.
///
/// There is currently no easily viewable list of all names, but in the
/// meantime you can rely on the autocompletion in Typst's web editor.
///
/// ## Syntax
/// This function also has dedicated syntax: In markup, you can enclose symmie
/// notation within colons to produce a symbol. And in math, you can just write
/// the notation directly. There, all letter sequence of length at least two are
/// automatically parsed as symbols (unless a variable of that name is defined).
///
/// ## Example
/// ```
/// // In text, with colons.
/// :arrow:l: \
/// :arrow:r: \
/// :arrow:t: \
/// :turtle: \
/// :face:halo: \
/// :woman:old:
///
/// // In math, directly.
/// $f : NN -> RR$ \
/// $A sub:eq B without C$ \
/// $a times:div b eq:not c$
/// ```
///
/// ## Parameters
/// - notation: EcoString (positional, required)
/// The symbols symmie notation.
/// The symbol's symmie notation.
///
/// Consists of a name, followed by a number colon-separated modifiers
/// in no particular order.
///
/// ### Example
/// ```
/// #symbol("NN") \
/// #symbol("face:grin")
/// ```
///
/// ## Category
/// text

View File

@ -94,6 +94,16 @@ impl<const S: ShapeKind> ShapeNode<S> {
styles.set_opt(Self::RADIUS, args.named("radius")?);
}
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => match &self.0 {
Some(body) => Some(Value::Content(body.clone())),
None => Some(Value::None),
},
_ => None,
}
}
}
impl<const S: ShapeKind> Layout for ShapeNode<S> {

View File

@ -109,15 +109,13 @@ pub fn section(docs: &mut String, title: &str, level: usize) -> Option<String> {
/// Parse the example section.
pub fn example(docs: &mut String, level: usize) -> Option<String> {
Some(
section(docs, "Example", level)?
.lines()
.skip_while(|line| !line.contains("```"))
.skip(1)
.take_while(|line| !line.contains("```"))
.collect::<Vec<_>>()
.join("\n"),
)
let section = section(docs, "Example", level)?;
let mut s = unscanny::Scanner::new(&section);
let count = s.eat_while('`').len();
let term = "`".repeat(count);
let text = s.eat_until(term.as_str()).trim();
s.expect(term.as_str());
Some(text.into())
}
/// Parse the parameter section.

View File

@ -254,7 +254,7 @@ pub struct LineMetrics {
/// Identifies a vertical metric of a font.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum VerticalFontMetric {
/// The distance from the baseline to the typographic ascender.
/// The typographic ascender.
///
/// Corresponds to the typographic ascender from the `OS/2` table if present
/// and falls back to the ascender from the `hhea` table otherwise.
@ -265,7 +265,7 @@ pub enum VerticalFontMetric {
XHeight,
/// The baseline on which the letters rest.
Baseline,
/// The distance from the baseline to the typographic descender.
/// The typographic descender.
///
/// Corresponds to the typographic descender from the `OS/2` table if
/// present and falls back to the descender from the `hhea` table otherwise.

View File

@ -32,11 +32,11 @@ impl Debug for FontVariant {
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum FontStyle {
/// The default style.
/// The default, typically upright style.
Normal,
/// A cursive style.
/// A cursive style with custom letterform.
Italic,
/// A slanted style.
/// Just a slanted version of the normal style.
Oblique,
}

View File

@ -293,11 +293,11 @@ castable! {
castable! {
FontStyle,
/// The default style.
/// The default, typically upright style.
"normal" => Self::Normal,
/// A cursive style.
/// A cursive style with custom letterform.
"italic" => Self::Italic,
/// A slanted style.
/// Just a slanted version of the normal style.
"oblique" => Self::Oblique,
}

View File

@ -32,5 +32,5 @@ A #h(1fr) B
---
// Missing spacing.
// Error: 11-13 missing argument: spacing
// Error: 11-13 missing argument: amount
Totally #h() ignored

View File

@ -43,11 +43,11 @@ The 5\'11\" 'quick\' brown fox jumps over the \"lazy" dog\'s ear.
// Test turning smart quotes off.
He's told some books contain questionable "example text".
#set text(smart-quotes: false)
#set smartquote(enabled: false)
He's told some books contain questionable "example text".
---
// Test changing properties within text.
"She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me.
Some people's thought on this would be #text(smart-quotes: false)["strange."]
Some people's thought on this would be [#set smartquote(enabled: false); "strange."]