diff --git a/Cargo.lock b/Cargo.lock index e5daf731f..140dccf74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2822,6 +2822,8 @@ dependencies = [ "typst-assets", "typst-dev-assets", "typst-render", + "typst-utils", + "unicode-math-class", "unscanny", "yaml-front-matter", ] diff --git a/crates/typst-library/src/foundations/mod.rs b/crates/typst-library/src/foundations/mod.rs index c335484fa..8e3aa060d 100644 --- a/crates/typst-library/src/foundations/mod.rs +++ b/crates/typst-library/src/foundations/mod.rs @@ -85,16 +85,9 @@ use crate::engine::Engine; use crate::routines::EvalMode; use crate::{Feature, Features}; -/// Foundational types and functions. -/// -/// Here, you'll find documentation for basic data types like [integers]($int) -/// and [strings]($str) as well as details about core computational functions. -#[category] -pub static FOUNDATIONS: Category; - /// Hook up all `foundations` definitions. pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) { - global.start_category(FOUNDATIONS); + global.start_category(crate::Category::Foundations); global.define_type::(); global.define_type::(); global.define_type::(); @@ -125,6 +118,7 @@ pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) { } global.define("calc", calc::module()); global.define("sys", sys::module(inputs)); + global.reset_category(); } /// Fails with an error. diff --git a/crates/typst-library/src/foundations/scope.rs b/crates/typst-library/src/foundations/scope.rs index d6c5a8d05..e1ce61b8a 100644 --- a/crates/typst-library/src/foundations/scope.rs +++ b/crates/typst-library/src/foundations/scope.rs @@ -1,6 +1,3 @@ -#[doc(inline)] -pub use typst_macros::category; - use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; @@ -8,14 +5,13 @@ use ecow::{eco_format, EcoString}; use indexmap::map::Entry; use indexmap::IndexMap; use typst_syntax::Span; -use typst_utils::Static; use crate::diag::{bail, DeprecationSink, HintedStrResult, HintedString, StrResult}; use crate::foundations::{ Element, Func, IntoValue, NativeElement, NativeFunc, NativeFuncData, NativeType, Type, Value, }; -use crate::Library; +use crate::{Category, Library}; /// A stack of scopes. #[derive(Debug, Default, Clone)] @@ -361,46 +357,6 @@ pub enum Capturer { Context, } -/// A group of related definitions. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct Category(Static); - -impl Category { - /// Create a new category from raw data. - pub const fn from_data(data: &'static CategoryData) -> Self { - Self(Static(data)) - } - - /// The category's name. - pub fn name(&self) -> &'static str { - self.0.name - } - - /// The type's title case name, for use in documentation (e.g. `String`). - pub fn title(&self) -> &'static str { - self.0.title - } - - /// Documentation for the category. - pub fn docs(&self) -> &'static str { - self.0.docs - } -} - -impl Debug for Category { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Category({})", self.name()) - } -} - -/// Defines a category. -#[derive(Debug)] -pub struct CategoryData { - pub name: &'static str, - pub title: &'static str, - pub docs: &'static str, -} - /// The error message when trying to mutate a variable from the standard /// library. #[cold] diff --git a/crates/typst-library/src/foundations/target.rs b/crates/typst-library/src/foundations/target.rs index 5841552e4..2a21fd42b 100644 --- a/crates/typst-library/src/foundations/target.rs +++ b/crates/typst-library/src/foundations/target.rs @@ -3,7 +3,7 @@ use comemo::Tracked; use crate::diag::HintedStrResult; use crate::foundations::{elem, func, Cast, Context}; -/// The compilation target. +/// The export target. #[derive(Debug, Default, Copy, Clone, PartialEq, Hash, Cast)] pub enum Target { /// The target that is used for paged, fully laid-out content. @@ -28,7 +28,49 @@ pub struct TargetElem { pub target: Target, } -/// Returns the current compilation target. +/// Returns the current export target. +/// +/// This function returns either +/// - `{"paged"}` (for PDF, PNG, and SVG export), or +/// - `{"html"}` (for HTML export). +/// +/// The design of this function is not yet finalized and for this reason it is +/// guarded behind the `html` feature. Visit the [HTML documentation +/// page]($html) for more details. +/// +/// # When to use it +/// This function allows you to format your document properly across both HTML +/// and paged export targets. It should primarily be used in templates and show +/// rules, rather than directly in content. This way, the document's contents +/// can be fully agnostic to the export target and content can be shared between +/// PDF and HTML export. +/// +/// # Varying targets +/// This function is [contextual]($context) as the target can vary within a +/// single compilation: When exporting to HTML, the target will be `{"paged"}` +/// while within an [`html.frame`]. +/// +/// # Example +/// ```example +/// #let kbd(it) = context { +/// if target() == "html" { +/// html.elem("kbd", it) +/// } else { +/// set text(fill: rgb("#1f2328")) +/// let r = 3pt +/// box( +/// fill: rgb("#f6f8fa"), +/// stroke: rgb("#d1d9e0b3"), +/// outset: (y: r), +/// inset: (x: r), +/// radius: r, +/// raw(it) +/// ) +/// } +/// } +/// +/// Press #kbd("F1") for help. +/// ``` #[func(contextual)] pub fn target(context: Tracked) -> HintedStrResult { Ok(TargetElem::target_in(context.styles()?)) diff --git a/crates/typst-library/src/html/mod.rs b/crates/typst-library/src/html/mod.rs index c412b4607..1d88781c1 100644 --- a/crates/typst-library/src/html/mod.rs +++ b/crates/typst-library/src/html/mod.rs @@ -6,53 +6,77 @@ pub use self::dom::*; use ecow::EcoString; -use crate::foundations::{category, elem, Category, Content, Module, Scope}; - -/// HTML output. -#[category] -pub static HTML: Category; +use crate::foundations::{elem, Content, Module, Scope}; /// Create a module with all HTML definitions. pub fn module() -> Module { let mut html = Scope::deduplicating(); - html.start_category(HTML); + html.start_category(crate::Category::Html); html.define_elem::(); html.define_elem::(); Module::new("html", html) } -/// A HTML element that can contain Typst content. +/// An HTML element that can contain Typst content. +/// +/// Typst's HTML export automatically generates the appropriate tags for most +/// elements. However, sometimes, it is desirable to retain more control. For +/// example, when using Typst to generate your blog, you could use this function +/// to wrap each article in an `
` tag. +/// +/// Typst is aware of what is valid HTML. A tag and its attributes must form +/// syntactically valid HTML. Some tags, like `meta` do not accept content. +/// Hence, you must not provide a body for them. We may add more checks in the +/// future, so be sure that you are generating valid HTML when using this +/// function. +/// +/// Normally, Typst will generate `html`, `head`, and `body` tags for you. If +/// you instead create them with this function, Typst will omit its own tags. +/// +/// ```typ +/// #html.elem("div", attrs: (style: "background: aqua"))[ +/// A div with _Typst content_ inside! +/// ] +/// ``` #[elem(name = "elem")] pub struct HtmlElem { /// The element's tag. #[required] pub tag: HtmlTag, - /// The element's attributes. + /// The element's HTML attributes. #[borrowed] pub attrs: HtmlAttrs, /// The contents of the HTML element. + /// + /// The body can be arbitrary Typst content. #[positional] #[borrowed] pub body: Option, } impl HtmlElem { - /// Add an atribute to the element. + /// Add an attribute to the element. pub fn with_attr(mut self, attr: HtmlAttr, value: impl Into) -> Self { self.attrs.get_or_insert_with(Default::default).push(attr, value); self } } -/// An element that forces its contents to be laid out. +/// An element that lays out its content as an inline SVG. /// -/// Integrates content that requires layout (e.g. a plot) into HTML output -/// by turning it into an inline SVG. +/// Sometimes, converting Typst content to HTML is not desirable. This can be +/// the case for plots and other content that relies on positioning and styling +/// to convey its message. +/// +/// This function allows you to use the Typst layout engine that would also be +/// used for PDF, SVG, and PNG export to render a part of your document exactly +/// how it would appear when exported in one of these formats. It embeds the +/// content as an inline SVG. #[elem] pub struct FrameElem { - /// The contents that shall be laid out. + /// The content that shall be laid out. #[positional] #[required] pub body: Content, diff --git a/crates/typst-library/src/introspection/mod.rs b/crates/typst-library/src/introspection/mod.rs index d8184330d..995fbd7b5 100644 --- a/crates/typst-library/src/introspection/mod.rs +++ b/crates/typst-library/src/introspection/mod.rs @@ -25,24 +25,11 @@ pub use self::query_::*; pub use self::state::*; pub use self::tag::*; -use crate::foundations::{category, Category, Scope}; - -/// Interactions between document parts. -/// -/// This category is home to Typst's introspection capabilities: With the -/// `counter` function, you can access and manipulate page, section, figure, and -/// equation counters or create custom ones. Meanwhile, the `query` function -/// lets you search for elements in the document to construct things like a list -/// of figures or headers which show the current chapter title. -/// -/// Most of the functions are _contextual._ It is recommended to read the chapter -/// on [context] before continuing here. -#[category] -pub static INTROSPECTION: Category; +use crate::foundations::Scope; /// Hook up all `introspection` definitions. pub fn define(global: &mut Scope) { - global.start_category(INTROSPECTION); + global.start_category(crate::Category::Introspection); global.define_type::(); global.define_type::(); global.define_type::(); @@ -50,4 +37,5 @@ pub fn define(global: &mut Scope) { global.define_func::(); global.define_func::(); global.define_func::(); + global.reset_category(); } diff --git a/crates/typst-library/src/layout/mod.rs b/crates/typst-library/src/layout/mod.rs index 57518fe72..ef1ecdb36 100644 --- a/crates/typst-library/src/layout/mod.rs +++ b/crates/typst-library/src/layout/mod.rs @@ -64,17 +64,11 @@ pub use self::spacing::*; pub use self::stack::*; pub use self::transform::*; -use crate::foundations::{category, Category, Scope}; - -/// Arranging elements on the page in different ways. -/// -/// By combining layout functions, you can create complex and automatic layouts. -#[category] -pub static LAYOUT: Category; +use crate::foundations::Scope; /// Hook up all `layout` definitions. pub fn define(global: &mut Scope) { - global.start_category(LAYOUT); + global.start_category(crate::Category::Layout); global.define_type::(); global.define_type::(); global.define_type::(); @@ -103,4 +97,5 @@ pub fn define(global: &mut Scope) { global.define_elem::(); global.define_func::(); global.define_func::(); + global.reset_category(); } diff --git a/crates/typst-library/src/lib.rs b/crates/typst-library/src/lib.rs index 460321aa3..c39024f71 100644 --- a/crates/typst-library/src/lib.rs +++ b/crates/typst-library/src/lib.rs @@ -29,6 +29,7 @@ pub mod visualize; use std::ops::{Deref, Range}; +use serde::{Deserialize, Serialize}; use typst_syntax::{FileId, Source, Span}; use typst_utils::{LazyHash, SmallBitSet}; @@ -236,31 +237,72 @@ pub enum Feature { Html, } +/// A group of related standard library definitions. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum Category { + Foundations, + Introspection, + Layout, + DataLoading, + Math, + Model, + Symbols, + Text, + Visualize, + Pdf, + Html, + Svg, + Png, +} + +impl Category { + /// The kebab-case name of the category. + pub fn name(&self) -> &'static str { + match self { + Self::Foundations => "foundations", + Self::Introspection => "introspection", + Self::Layout => "layout", + Self::DataLoading => "data-loading", + Self::Math => "math", + Self::Model => "model", + Self::Symbols => "symbols", + Self::Text => "text", + Self::Visualize => "visualize", + Self::Pdf => "pdf", + Self::Html => "html", + Self::Svg => "svg", + Self::Png => "png", + } + } +} + /// Construct the module with global definitions. fn global(math: Module, inputs: Dict, features: &Features) -> Module { let mut global = Scope::deduplicating(); + self::foundations::define(&mut global, inputs, features); self::model::define(&mut global); self::text::define(&mut global); - global.reset_category(); - global.define("math", math); self::layout::define(&mut global); self::visualize::define(&mut global); self::introspection::define(&mut global); self::loading::define(&mut global); self::symbols::define(&mut global); - self::pdf::define(&mut global); - global.reset_category(); + + global.define("math", math); + global.define("pdf", self::pdf::module()); if features.is_enabled(Feature::Html) { global.define("html", self::html::module()); } + prelude(&mut global); + Module::new("global", global) } /// Defines scoped values that are globally available, too. fn prelude(global: &mut Scope) { - global.reset_category(); global.define("black", Color::BLACK); global.define("gray", Color::GRAY); global.define("silver", Color::SILVER); diff --git a/crates/typst-library/src/loading/cbor.rs b/crates/typst-library/src/loading/cbor.rs index bd65e8442..801ca617a 100644 --- a/crates/typst-library/src/loading/cbor.rs +++ b/crates/typst-library/src/loading/cbor.rs @@ -34,9 +34,6 @@ pub fn cbor( #[scope] impl cbor { /// Reads structured data from CBOR bytes. - /// - /// This function is deprecated. The [`cbor`] function now accepts bytes - /// directly. #[func(title = "Decode CBOR")] #[deprecated = "`cbor.decode` is deprecated, directly pass bytes to `cbor` instead"] pub fn decode( diff --git a/crates/typst-library/src/loading/csv.rs b/crates/typst-library/src/loading/csv.rs index d01d687ba..6fdec4459 100644 --- a/crates/typst-library/src/loading/csv.rs +++ b/crates/typst-library/src/loading/csv.rs @@ -96,9 +96,6 @@ pub fn csv( #[scope] impl csv { /// Reads structured data from a CSV string/bytes. - /// - /// This function is deprecated. The [`csv`] function now accepts bytes - /// directly. #[func(title = "Decode CSV")] #[deprecated = "`csv.decode` is deprecated, directly pass bytes to `csv` instead"] pub fn decode( diff --git a/crates/typst-library/src/loading/json.rs b/crates/typst-library/src/loading/json.rs index 52c87371f..185bac143 100644 --- a/crates/typst-library/src/loading/json.rs +++ b/crates/typst-library/src/loading/json.rs @@ -65,9 +65,6 @@ pub fn json( #[scope] impl json { /// Reads structured data from a JSON string/bytes. - /// - /// This function is deprecated. The [`json`] function now accepts bytes - /// directly. #[func(title = "Decode JSON")] #[deprecated = "`json.decode` is deprecated, directly pass bytes to `json` instead"] pub fn decode( diff --git a/crates/typst-library/src/loading/mod.rs b/crates/typst-library/src/loading/mod.rs index c645b691d..c57e02888 100644 --- a/crates/typst-library/src/loading/mod.rs +++ b/crates/typst-library/src/loading/mod.rs @@ -29,19 +29,12 @@ pub use self::yaml_::*; use crate::diag::{At, SourceResult}; use crate::foundations::OneOrMultiple; -use crate::foundations::{cast, category, Bytes, Category, Scope, Str}; +use crate::foundations::{cast, Bytes, Scope, Str}; use crate::World; -/// Data loading from external files. -/// -/// These functions help you with loading and embedding data, for example from -/// the results of an experiment. -#[category] -pub static DATA_LOADING: Category; - /// Hook up all `data-loading` definitions. pub(super) fn define(global: &mut Scope) { - global.start_category(DATA_LOADING); + global.start_category(crate::Category::DataLoading); global.define_func::(); global.define_func::(); global.define_func::(); @@ -49,6 +42,7 @@ pub(super) fn define(global: &mut Scope) { global.define_func::(); global.define_func::(); global.define_func::(); + global.reset_category(); } /// Something we can retrieve byte data from. diff --git a/crates/typst-library/src/loading/toml.rs b/crates/typst-library/src/loading/toml.rs index 456112463..2660e7e7f 100644 --- a/crates/typst-library/src/loading/toml.rs +++ b/crates/typst-library/src/loading/toml.rs @@ -44,9 +44,6 @@ pub fn toml( #[scope] impl toml { /// Reads structured data from a TOML string/bytes. - /// - /// This function is deprecated. The [`toml`] function now accepts bytes - /// directly. #[func(title = "Decode TOML")] #[deprecated = "`toml.decode` is deprecated, directly pass bytes to `toml` instead"] pub fn decode( diff --git a/crates/typst-library/src/loading/xml.rs b/crates/typst-library/src/loading/xml.rs index 0172071be..32ed6f24b 100644 --- a/crates/typst-library/src/loading/xml.rs +++ b/crates/typst-library/src/loading/xml.rs @@ -77,9 +77,6 @@ pub fn xml( #[scope] impl xml { /// Reads structured data from an XML string/bytes. - /// - /// This function is deprecated. The [`xml`] function now accepts bytes - /// directly. #[func(title = "Decode XML")] #[deprecated = "`xml.decode` is deprecated, directly pass bytes to `xml` instead"] pub fn decode( diff --git a/crates/typst-library/src/loading/yaml.rs b/crates/typst-library/src/loading/yaml.rs index 511c676cb..4eeec28f1 100644 --- a/crates/typst-library/src/loading/yaml.rs +++ b/crates/typst-library/src/loading/yaml.rs @@ -55,9 +55,6 @@ pub fn yaml( #[scope] impl yaml { /// Reads structured data from a YAML string/bytes. - /// - /// This function is deprecated. The [`yaml`] function now accepts bytes - /// directly. #[func(title = "Decode YAML")] #[deprecated = "`yaml.decode` is deprecated, directly pass bytes to `yaml` instead"] pub fn decode( diff --git a/crates/typst-library/src/math/mod.rs b/crates/typst-library/src/math/mod.rs index a97a19b09..2e6d42b13 100644 --- a/crates/typst-library/src/math/mod.rs +++ b/crates/typst-library/src/math/mod.rs @@ -27,119 +27,10 @@ pub use self::underover::*; use typst_utils::singleton; use unicode_math_class::MathClass; -use crate::foundations::{ - category, elem, Category, Content, Module, NativeElement, Scope, -}; +use crate::foundations::{elem, Content, Module, NativeElement, Scope}; use crate::layout::{Em, HElem}; use crate::text::TextElem; -/// Typst has special [syntax]($syntax/#math) and library functions to typeset -/// mathematical formulas. Math formulas can be displayed inline with text or as -/// separate blocks. They will be typeset into their own block if they start and -/// end with at least one space (e.g. `[$ x^2 $]`). -/// -/// # Variables -/// In math, single letters are always displayed as is. Multiple letters, -/// however, are interpreted as variables and functions. To display multiple -/// letters verbatim, you can place them into quotes and to access single letter -/// variables, you can use the [hash syntax]($scripting/#expressions). -/// -/// ```example -/// $ A = pi r^2 $ -/// $ "area" = pi dot "radius"^2 $ -/// $ cal(A) := -/// { x in RR | x "is natural" } $ -/// #let x = 5 -/// $ #x < 17 $ -/// ``` -/// -/// # Symbols -/// Math mode makes a wide selection of [symbols]($category/symbols/sym) like -/// `pi`, `dot`, or `RR` available. Many mathematical symbols are available in -/// different variants. You can select between different variants by applying -/// [modifiers]($symbol) to the symbol. Typst further recognizes a number of -/// shorthand sequences like `=>` that approximate a symbol. When such a -/// shorthand exists, the symbol's documentation lists it. -/// -/// ```example -/// $ x < y => x gt.eq.not y $ -/// ``` -/// -/// # Line Breaks -/// Formulas can also contain line breaks. Each line can contain one or multiple -/// _alignment points_ (`&`) which are then aligned. -/// -/// ```example -/// $ sum_(k=0)^n k -/// &= 1 + ... + n \ -/// &= (n(n+1)) / 2 $ -/// ``` -/// -/// # Function calls -/// Math mode supports special function calls without the hash prefix. In these -/// "math calls", the argument list works a little differently than in code: -/// -/// - Within them, Typst is still in "math mode". Thus, you can write math -/// directly into them, but need to use hash syntax to pass code expressions -/// (except for strings, which are available in the math syntax). -/// - They support positional and named arguments, as well as argument -/// spreading. -/// - They don't support trailing content blocks. -/// - They provide additional syntax for 2-dimensional argument lists. The -/// semicolon (`;`) merges preceding arguments separated by commas into an -/// array argument. -/// -/// ```example -/// $ frac(a^2, 2) $ -/// $ vec(1, 2, delim: "[") $ -/// $ mat(1, 2; 3, 4) $ -/// $ mat(..#range(1, 5).chunks(2)) $ -/// $ lim_x = -/// op("lim", limits: #true)_x $ -/// ``` -/// -/// To write a verbatim comma or semicolon in a math call, escape it with a -/// backslash. The colon on the other hand is only recognized in a special way -/// if directly preceded by an identifier, so to display it verbatim in those -/// cases, you can just insert a space before it. -/// -/// Functions calls preceded by a hash are normal code function calls and not -/// affected by these rules. -/// -/// # Alignment -/// When equations include multiple _alignment points_ (`&`), this creates -/// blocks of alternatingly right- and left-aligned columns. In the example -/// below, the expression `(3x + y) / 7` is right-aligned and `= 9` is -/// left-aligned. The word "given" is also left-aligned because `&&` creates two -/// alignment points in a row, alternating the alignment twice. `& &` and `&&` -/// behave exactly the same way. Meanwhile, "multiply by 7" is right-aligned -/// because just one `&` precedes it. Each alignment point simply alternates -/// between right-aligned/left-aligned. -/// -/// ```example -/// $ (3x + y) / 7 &= 9 && "given" \ -/// 3x + y &= 63 & "multiply by 7" \ -/// 3x &= 63 - y && "subtract y" \ -/// x &= 21 - y/3 & "divide by 3" $ -/// ``` -/// -/// # Math fonts -/// You can set the math font by with a [show-set rule]($styling/#show-rules) as -/// demonstrated below. Note that only special OpenType math fonts are suitable -/// for typesetting maths. -/// -/// ```example -/// #show math.equation: set text(font: "Fira Math") -/// $ sum_(i in NN) 1 + i $ -/// ``` -/// -/// # Math module -/// All math functions are part of the `math` [module]($scripting/#modules), -/// which is available by default in equations. Outside of equations, they can -/// be accessed with the `math.` prefix. -#[category] -pub static MATH: Category; - // Spacings. pub const THIN: Em = Em::new(1.0 / 6.0); pub const MEDIUM: Em = Em::new(2.0 / 9.0); @@ -150,7 +41,7 @@ pub const WIDE: Em = Em::new(2.0); /// Create a module with all math definitions. pub fn module() -> Module { let mut math = Scope::deduplicating(); - math.start_category(MATH); + math.start_category(crate::Category::Math); math.define_elem::(); math.define_elem::(); math.define_elem::(); diff --git a/crates/typst-library/src/model/mod.rs b/crates/typst-library/src/model/mod.rs index 586e10ec1..9bdbf0013 100644 --- a/crates/typst-library/src/model/mod.rs +++ b/crates/typst-library/src/model/mod.rs @@ -40,19 +40,11 @@ pub use self::strong::*; pub use self::table::*; pub use self::terms::*; -use crate::foundations::{category, Category, Scope}; - -/// Document structuring. -/// -/// Here, you can find functions to structure your document and interact with -/// that structure. This includes section headings, figures, bibliography -/// management, cross-referencing and more. -#[category] -pub static MODEL: Category; +use crate::foundations::Scope; /// Hook up all `model` definitions. pub fn define(global: &mut Scope) { - global.start_category(MODEL); + global.start_category(crate::Category::Model); global.define_elem::(); global.define_elem::(); global.define_elem::(); @@ -72,4 +64,5 @@ pub fn define(global: &mut Scope) { global.define_elem::(); global.define_elem::(); global.define_func::(); + global.reset_category(); } diff --git a/crates/typst-library/src/pdf/mod.rs b/crates/typst-library/src/pdf/mod.rs index 3bd3b0c52..786a36372 100644 --- a/crates/typst-library/src/pdf/mod.rs +++ b/crates/typst-library/src/pdf/mod.rs @@ -4,21 +4,12 @@ mod embed; pub use self::embed::*; -use crate::foundations::{category, Category, Module, Scope}; - -/// PDF-specific functionality. -#[category] -pub static PDF: Category; - -/// Hook up the `pdf` module. -pub(super) fn define(global: &mut Scope) { - global.start_category(PDF); - global.define("pdf", module()); -} +use crate::foundations::{Module, Scope}; /// Hook up all `pdf` definitions. pub fn module() -> Module { - let mut scope = Scope::deduplicating(); - scope.define_elem::(); - Module::new("pdf", scope) + let mut pdf = Scope::deduplicating(); + pdf.start_category(crate::Category::Pdf); + pdf.define_elem::(); + Module::new("pdf", pdf) } diff --git a/crates/typst-library/src/symbols.rs b/crates/typst-library/src/symbols.rs index 777f8172f..0588ace95 100644 --- a/crates/typst-library/src/symbols.rs +++ b/crates/typst-library/src/symbols.rs @@ -1,19 +1,12 @@ //! Modifiable symbols. -use crate::foundations::{category, Category, Module, Scope, Symbol, Value}; - -/// These two modules give names to symbols and emoji to make them easy to -/// insert with a normal keyboard. Alternatively, you can also always directly -/// enter Unicode symbols into your text and formulas. In addition to the -/// symbols listed below, math mode defines `dif` and `Dif`. These are not -/// normal symbol values because they also affect spacing and font style. -#[category] -pub static SYMBOLS: Category; +use crate::foundations::{Module, Scope, Symbol, Value}; /// Hook up all `symbol` definitions. pub(super) fn define(global: &mut Scope) { - global.start_category(SYMBOLS); + global.start_category(crate::Category::Symbols); extend_scope_from_codex_module(global, codex::ROOT); + global.reset_category(); } /// Hook up all math `symbol` definitions, i.e., elements of the `sym` module. diff --git a/crates/typst-library/src/text/mod.rs b/crates/typst-library/src/text/mod.rs index f506397e1..12f4e4c59 100644 --- a/crates/typst-library/src/text/mod.rs +++ b/crates/typst-library/src/text/mod.rs @@ -45,9 +45,9 @@ use typst_utils::singleton; use crate::diag::{bail, warning, HintedStrResult, SourceResult}; use crate::engine::Engine; use crate::foundations::{ - cast, category, dict, elem, Args, Array, Cast, Category, Construct, Content, Dict, - Fold, IntoValue, NativeElement, Never, NoneValue, Packed, PlainText, Regex, Repr, - Resolve, Scope, Set, Smart, StyleChain, + cast, dict, elem, Args, Array, Cast, Construct, Content, Dict, Fold, IntoValue, + NativeElement, Never, NoneValue, Packed, PlainText, Regex, Repr, Resolve, Scope, Set, + Smart, StyleChain, }; use crate::layout::{Abs, Axis, Dir, Em, Length, Ratio, Rel}; use crate::math::{EquationElem, MathSize}; @@ -55,15 +55,9 @@ use crate::model::ParElem; use crate::visualize::{Color, Paint, RelativeTo, Stroke}; use crate::World; -/// Text styling. -/// -/// The [text function]($text) is of particular interest. -#[category] -pub static TEXT: Category; - /// Hook up all `text` definitions. pub(super) fn define(global: &mut Scope) { - global.start_category(TEXT); + global.start_category(crate::Category::Text); global.define_elem::(); global.define_elem::(); global.define_elem::(); @@ -78,6 +72,7 @@ pub(super) fn define(global: &mut Scope) { global.define_func::(); global.define_func::(); global.define_func::(); + global.reset_category(); } /// Customizes the look and layout of text in a variety of ways. diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index 9306eb6f2..18d40caa8 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -50,6 +50,17 @@ pub struct ImageElem { /// supported [formats]($image.format). /// /// For more details about paths, see the [Paths section]($syntax/#paths). + /// + /// ```example + /// #let original = read("diagram.svg") + /// #let changed = original.replace( + /// "#2B80FF", // blue + /// green.to-hex(), + /// ) + /// + /// #image(bytes(original)) + /// #image(bytes(changed)) + /// ``` #[required] #[parse( let source = args.expect::>("source")?; @@ -156,20 +167,6 @@ pub struct ImageElem { #[allow(clippy::too_many_arguments)] impl ImageElem { /// Decode a raster or vector graphic from bytes or a string. - /// - /// This function is deprecated. The [`image`] function now accepts bytes - /// directly. - /// - /// ```example - /// #let original = read("diagram.svg") - /// #let changed = original.replace( - /// "#2B80FF", // blue - /// green.to-hex(), - /// ) - /// - /// #image.decode(original) - /// #image.decode(changed) - /// ``` #[func(title = "Decode Image")] #[deprecated = "`image.decode` is deprecated, directly pass bytes to `image` instead"] pub fn decode( diff --git a/crates/typst-library/src/visualize/mod.rs b/crates/typst-library/src/visualize/mod.rs index 76849ac86..72a420657 100644 --- a/crates/typst-library/src/visualize/mod.rs +++ b/crates/typst-library/src/visualize/mod.rs @@ -24,19 +24,11 @@ pub use self::shape::*; pub use self::stroke::*; pub use self::tiling::*; -use crate::foundations::{category, Category, Element, Scope, Type}; - -/// Drawing and data visualization. -/// -/// If you want to create more advanced drawings or plots, also have a look at -/// the [CetZ](https://github.com/johannes-wolf/cetz) package as well as more -/// specialized [packages]($universe) for your use case. -#[category] -pub static VISUALIZE: Category; +use crate::foundations::{Element, Scope, Type}; /// Hook up all visualize definitions. pub(super) fn define(global: &mut Scope) { - global.start_category(VISUALIZE); + global.start_category(crate::Category::Visualize); global.define_type::(); global.define_type::(); global.define_type::(); @@ -55,4 +47,5 @@ pub(super) fn define(global: &mut Scope) { global .define("pattern", Type::of::()) .deprecated("the name `pattern` is deprecated, use `tiling` instead"); + global.reset_category(); } diff --git a/crates/typst-library/src/visualize/path.rs b/crates/typst-library/src/visualize/path.rs index 5d3439c08..c1cfde94a 100644 --- a/crates/typst-library/src/visualize/path.rs +++ b/crates/typst-library/src/visualize/path.rs @@ -21,9 +21,6 @@ use crate::visualize::{FillRule, Paint, Stroke}; /// ((50%, 0pt), (40pt, 0pt)), /// ) /// ``` -/// -/// # Deprecation -/// This function is deprecated. The [`curve`] function should be used instead. #[elem(Show)] pub struct PathElem { /// How to fill the path. diff --git a/crates/typst-macros/src/category.rs b/crates/typst-macros/src/category.rs deleted file mode 100644 index 26ec879cc..000000000 --- a/crates/typst-macros/src/category.rs +++ /dev/null @@ -1,59 +0,0 @@ -use heck::{ToKebabCase, ToTitleCase}; -use proc_macro2::TokenStream; -use quote::quote; -use syn::parse::{Parse, ParseStream}; -use syn::{Attribute, Ident, Result, Token, Type, Visibility}; - -use crate::util::{documentation, foundations}; - -/// Expand the `#[category]` macro. -pub fn category(_: TokenStream, item: syn::Item) -> Result { - let syn::Item::Verbatim(stream) = item else { - bail!(item, "expected bare static"); - }; - - let BareStatic { attrs, vis, ident, ty, .. } = syn::parse2(stream)?; - - let name = ident.to_string().to_kebab_case(); - let title = name.to_title_case(); - let docs = documentation(&attrs); - - Ok(quote! { - #(#attrs)* - #[allow(rustdoc::broken_intra_doc_links)] - #vis static #ident: #ty = { - static DATA: #foundations::CategoryData = #foundations::CategoryData { - name: #name, - title: #title, - docs: #docs, - }; - #foundations::Category::from_data(&DATA) - }; - }) -} - -/// Parse a bare `pub static CATEGORY: Category;` item. -#[allow(dead_code)] -pub struct BareStatic { - pub attrs: Vec, - pub vis: Visibility, - pub static_token: Token![static], - pub ident: Ident, - pub colon_token: Token![:], - pub ty: Type, - pub semi_token: Token![;], -} - -impl Parse for BareStatic { - fn parse(input: ParseStream) -> Result { - Ok(Self { - attrs: input.call(Attribute::parse_outer)?, - vis: input.parse()?, - static_token: input.parse()?, - ident: input.parse()?, - colon_token: input.parse()?, - ty: input.parse()?, - semi_token: input.parse()?, - }) - } -} diff --git a/crates/typst-macros/src/lib.rs b/crates/typst-macros/src/lib.rs index 578389c7f..82e63ddc8 100644 --- a/crates/typst-macros/src/lib.rs +++ b/crates/typst-macros/src/lib.rs @@ -5,7 +5,6 @@ extern crate proc_macro; #[macro_use] mod util; mod cast; -mod category; mod elem; mod func; mod scope; @@ -266,15 +265,6 @@ pub fn scope(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { .into() } -/// Defines a category of definitions. -#[proc_macro_attribute] -pub fn category(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { - let item = syn::parse_macro_input!(item as syn::Item); - category::category(stream.into(), item) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - /// Implements `Reflect`, `FromValue`, and `IntoValue` for a type. /// /// - `Reflect` makes Typst's runtime aware of the type's characteristics. diff --git a/docs/Cargo.toml b/docs/Cargo.toml index 41a5645e8..acc551754 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -17,6 +17,8 @@ cli = ["clap", "typst-render", "serde_json"] [dependencies] typst = { workspace = true } +typst-render = { workspace = true, optional = true } +typst-utils = { workspace = true } typst-assets = { workspace = true, features = ["fonts"] } typst-dev-assets = { workspace = true } clap = { workspace = true, optional = true } @@ -28,7 +30,7 @@ serde_json = { workspace = true, optional = true } serde_yaml = { workspace = true } syntect = { workspace = true, features = ["html"] } typed-arena = { workspace = true } -typst-render = { workspace = true, optional = true } +unicode-math-class = { workspace = true } unscanny = { workspace = true } yaml-front-matter = { workspace = true } diff --git a/docs/guides/guide-for-latex-users.md b/docs/guides/guide-for-latex-users.md index 743afa5a6..5137ae1a9 100644 --- a/docs/guides/guide-for-latex-users.md +++ b/docs/guides/guide-for-latex-users.md @@ -657,7 +657,8 @@ applicable, contains possible workarounds. - **Well-established plotting ecosystem.** LaTeX users often create elaborate charts along with their documents in PGF/TikZ. The Typst ecosystem does not yet offer the same breadth of available options, but the ecosystem around the - [`cetz`](https://github.com/cetz-package/cetz) package is catching up quickly. + [`cetz` package](https://typst.app/universe/package/cetz) is catching up + quickly. - **Change page margins without a pagebreak.** In LaTeX, margins can always be adjusted, even without a pagebreak. To change margins in Typst, you use the @@ -670,4 +671,6 @@ applicable, contains possible workarounds. format, but you can easily convert both into SVG files with [online tools](https://cloudconvert.com/pdf-to-svg) or [Inkscape](https://inkscape.org/). The web app will automatically convert PDF - files to SVG files upon uploading them. + files to SVG files upon uploading them. You can also use the + community-provided [`muchpdf` package](https://typst.app/universe/package/muchpdf) + to embed PDFs. It internally converts PDFs to SVGs on-the-fly. diff --git a/docs/reference/export/html.md b/docs/reference/export/html.md new file mode 100644 index 000000000..330c2e136 --- /dev/null +++ b/docs/reference/export/html.md @@ -0,0 +1,61 @@ +
+ +Typst's HTML export is currently under active development. The feature is still +very incomplete and only available for experimentation behind a feature flag. Do +not use this feature for production use cases. In the CLI, you can experiment +with HTML export by passing `--features html` or setting the `TYPST_FEATURES` +environment variables to `html`. In the web app, HTML export is not available at +this time. Visit the [tracking issue](https://github.com/typst/typst/issues/5512) +to follow progress on HTML export and learn more about planned features. +
+ +HTML files describe a document structurally. The aim of Typst's HTML export is +to capture the structure of an input document and produce semantically rich HTML +that retains this structure. The resulting HTML should be accessible, +human-readable, and editable by hand and downstream tools. + +PDF, PNG, and SVG export, in contrast, all produce _visual_ representations of a +fully-laid out document. This divergence in the formats' intents means that +Typst cannot simply produce perfect HTML for your existing Typst documents. It +cannot always know what the best semantic HTML representation of your content +is. + +Instead, it gives _you_ full control: You can check the current export format +through the [`target`] function and when it is set to HTML, generate [raw HTML +elements]($html.elem). The primary intended use of these elements is in +templates and show rules. This way, the document's contents can be fully +agnostic to the export target and content can be shared between PDF and HTML +export. + +Currently, Typst will always output a single HTML file. Support for outputting +directories with multiple HTML documents and assets, as well as support for +outputting fragments that can be integrated into other HTML documents is +planned. + +Typst currently does not output CSS style sheets, instead focussing on emitting +semantic markup. You can of course write your own CSS styles and still benefit +from sharing your _content_ between PDF and HTML. For the future, we plan to +give you the option of automatically emitting CSS, taking more of your existing +set rules into account. + +# Exporting as HTML +## Command Line +Pass `--format html` to the `compile` or `watch` subcommand or provide an output +file name that ends with `.html`. Note that you must also pass `--features html` +or set `TYPST_FEATURES=html` to enable this experimental export target. + +When using `typst watch`, Typst will spin up a live-reloading HTTP server. You +can configure it as follows: + +- Pass `--port` to change the port. (Defaults to the first free port in the + range 3000-3005.) +- Pass `--no-reload` to disable injection of a live reload script. (The HTML + that is written to disk isn't affected either way.) +- Pass `--no-serve` to disable the server altogether. + +## Web App +Not currently available. + +# HTML-specific functionality +Typst exposes HTML-specific functionality in the global `html` module. See below +for the definitions it contains. diff --git a/docs/reference/export/pdf.md b/docs/reference/export/pdf.md new file mode 100644 index 000000000..b220ae946 --- /dev/null +++ b/docs/reference/export/pdf.md @@ -0,0 +1,71 @@ +PDF files focus on accurately describing documents visually, but also have +facilities for annotating their structure. This hybrid approach makes +them a good fit for document exchange: They render exactly the same on every +device, but also support extraction of a document's content and structure (at +least to an extent). Unlike PNG files, PDFs are not bound to a specific +resolution. Hence, you can view them at any size without incurring a loss of +quality. + +# PDF standards +The International Standards Organization (ISO) has published the base PDF +standard and various standards that extend it to make PDFs more suitable for +specific use-cases. By default, Typst exports PDF 1.7 files. Adobe Acrobat 8 and +later as well as all other commonly used PDF viewers are compatible with this +PDF version. + +## PDF/A +Typst optionally supports emitting PDF/A-conformant files. PDF/A files are +geared towards maximum compatibility with current and future PDF tooling. They +do not rely on difficult-to-implement or proprietary features and contain +exhaustive metadata. This makes them suitable for long-term archival. + +The PDF/A Standard has multiple versions (_parts_ in ISO terminology) and most +parts have multiple profiles that indicate the file's conformance level. +Currently, Typst supports these PDF/A output profiles: + +- PDF/A-2b: The basic conformance level of ISO 19005-2. This version of PDF/A is + based on PDF 1.7 and results in self-contained, archivable PDF files. + +- PDF/A-3b: The basic conformance level of ISO 19005-3. This version of PDF/A is + based on PDF 1.7 and results in archivable PDF files that can contain + arbitrary other related files as [attachments]($pdf.embed). The only + difference between it and PDF/A-2b is the capability to embed + non-PDF/A-conformant files within. + +When choosing between exporting PDF/A and regular PDF, keep in mind that PDF/A +files contain additional metadata, and that some readers will prevent the user +from modifying a PDF/A file. Some features of Typst may be disabled depending on +the PDF standard you choose. + +# Exporting as PDF +## Command Line +PDF is Typst's default export format. Running the `compile` or `watch` +subcommand without specifying a format will create a PDF. When exporting to PDF, +you have the following configuration options: + +- Which PDF standards Typst should enforce conformance with by specifying + `--pdf-standard` followed by one or multiple comma-separated standards. Valid + standards are `1.7`, `a-2b`, and `a-3b`. By default, Typst outputs + PDF-1.7-compliant files. + +- Which pages to export by specifying `--pages` followed by a comma-separated + list of numbers or dash-separated number ranges. Ranges can be half-open. + Example: `2,3,7-9,11-`. + +## Web App +Click the quick download button at the top right to export a PDF with default +settings. For further configuration, click "File" > "Export as" > "PDF" or click +the downwards-facing arrow next to the quick download button and select "Export +as PDF". When exporting to PDF, you have the following configuration options: + +- Which PDF standards Typst should enforce conformance with. By default, Typst + outputs PDF-1.7-compliant files. Valid additional standards are `A-2b` and + `A-3b`. + +- Which pages to export. Valid options are "All pages", "Current page", and + "Custom ranges". Custom ranges are a comma-separated list of numbers or + dash-separated number ranges. Ranges can be half-open. Example: `2,3,7-9,11-`. + +# PDF-specific functionality +Typst exposes PDF-specific functionality in the global `pdf` module. See below +for the definitions it contains. diff --git a/docs/reference/export/png.md b/docs/reference/export/png.md new file mode 100644 index 000000000..fe122f4d3 --- /dev/null +++ b/docs/reference/export/png.md @@ -0,0 +1,61 @@ +Instead of creating a PDF, Typst can also directly render pages to PNG raster +graphics. PNGs are losslessly compressed images that can contain one page at a +time. When exporting a multi-page document, Typst will emit multiple PNGs. PNGs +are a good choice when you want to use Typst's output in an image editing +software or when you can use none of Typst's other export formats. + +In contrast to Typst's other export formats, PNGs are bound to a specific +resolution. When exporting to PNG, you can configure the resolution as pixels +per inch (PPI). If the medium you view the PNG on has a finer resolution than +the PNG you exported, you will notice a loss of quality. Typst calculates the +resolution of your PNGs based on each page's physical dimensions and the PPI. If +you need guidance for choosing a PPI value, consider the following: + +- A DPI value of 300 or 600 is typical for desktop printing. +- Professional prints of detailed graphics can go up to 1200 PPI. +- If your document is only viewed at a distance, e.g. a poster, you may choose a + smaller value than 300. +- If your document is viewed on screens, a typical PPI value for a smartphone is + 400-500. + +Because PNGs only contain a pixel raster, the text within cannot be extracted +automatically (without OCR), for example by copy/paste or a screen reader. If +you need the text to be accessible, export a PDF or HTML file instead. + +PNGs can have transparent backgrounds. By default, Typst will output a PNG with +an opaque white background. You can make the background transparent using +`[#set page(fill: none)]`. Learn more on the +[`page` function's reference page]($page.fill). + +# Exporting as PNG +## Command Line +Pass `--format png` to the `compile` or `watch` subcommand or provide an output +file name that ends with `.png`. + +If your document has more than one page, Typst will create multiple image files. +The output file name must then be a template string containing at least one of +- `[{p}]`, which will be replaced by the page number +- `[{0p}]`, which will be replaced by the zero-padded page number (so that all + numbers have the same length) +- `[{t}]`, which will be replaced by the total number of pages + +When exporting to PNG, you have the following configuration options: + +- Which resolution to render at by specifying `--ppi` followed by a number of + pixels per inch. The default is `144`. + +- Which pages to export by specifying `--pages` followed by a comma-separated + list of numbers or dash-separated number ranges. Ranges can be half-open. + Example: `2,3,7-9,11-`. + +## Web App +Click "File" > "Export as" > "PNG" or click the downwards-facing arrow next to +the quick download button and select "Export as PNG". When exporting to PNG, you +have the following configuration options: + +- The resolution at which the pages should be rendered, as a number of pixels + per inch. The default is `144`. + +- Which pages to export. Valid options are "All pages", "Current page", and + "Custom ranges". Custom ranges are a comma-separated list of numbers or + dash-separated number ranges. Ranges can be half-open. Example: `2,3,7-9,11-`. diff --git a/docs/reference/export/svg.md b/docs/reference/export/svg.md new file mode 100644 index 000000000..630ab8452 --- /dev/null +++ b/docs/reference/export/svg.md @@ -0,0 +1,48 @@ +Instead of creating a PDF, Typst can also directly render pages to scalable +vector graphics (SVGs), which are the preferred format for embedding vector +graphics in web pages. Like PDF files, SVGs display your document exactly how +you have laid it out in Typst. Likewise, they share the benefit of not being +bound to a specific resolution. Hence, you can print or view SVG files on any +device without incurring a loss of quality. (Note that font printing quality may +be better with a PDF.) In contrast to a PDF, an SVG cannot contain multiple +pages. When exporting a multi-page document, Typst will emit multiple SVGs. + +SVGs can represent text in two ways: By embedding the text itself and rendering +it with the fonts available on the viewer's computer or by embedding the shapes +of each glyph in the font used to create the document. To ensure that the SVG +file looks the same across all devices it is viewed on, Typst chooses the latter +method. This means that the text in the SVG cannot be extracted automatically, +for example by copy/paste or a screen reader. If you need the text to be +accessible, export a PDF or HTML file instead. + +SVGs can have transparent backgrounds. By default, Typst will output an SVG with +an opaque white background. You can make the background transparent using +`[#set page(fill: none)]`. Learn more on the +[`page` function's reference page]($page.fill). + +# Exporting as SVG +## Command Line +Pass `--format svg` to the `compile` or `watch` subcommand or provide an output +file name that ends with `.svg`. + +If your document has more than one page, Typst will create multiple image files. +The output file name must then be a template string containing at least one of +- `[{p}]`, which will be replaced by the page number +- `[{0p}]`, which will be replaced by the zero-padded page number (so that all + numbers have the same length) +- `[{t}]`, which will be replaced by the total number of pages + +When exporting to SVG, you have the following configuration options: + +- Which pages to export by specifying `--pages` followed by a comma-separated + list of numbers or dash-separated number ranges. Ranges can be half-open. + Example: `2,3,7-9,11-`. + +## Web App +Click "File" > "Export as" > "SVG" or click the downwards-facing arrow next to +the quick download button and select "Export as SVG". When exporting to SVG, you +have the following configuration options: + +- Which pages to export. Valid options are "All pages", "Current page", and + "Custom ranges". Custom ranges are a comma-separated list of numbers or + dash-separated number ranges. Ranges can be half-open. Example: `2,3,7-9,11-`. diff --git a/docs/reference/context.md b/docs/reference/language/context.md similarity index 100% rename from docs/reference/context.md rename to docs/reference/language/context.md diff --git a/docs/reference/scripting.md b/docs/reference/language/scripting.md similarity index 100% rename from docs/reference/scripting.md rename to docs/reference/language/scripting.md diff --git a/docs/reference/styling.md b/docs/reference/language/styling.md similarity index 100% rename from docs/reference/styling.md rename to docs/reference/language/styling.md diff --git a/docs/reference/syntax.md b/docs/reference/language/syntax.md similarity index 100% rename from docs/reference/syntax.md rename to docs/reference/language/syntax.md diff --git a/docs/reference/library/data-loading.md b/docs/reference/library/data-loading.md new file mode 100644 index 000000000..659a8cccc --- /dev/null +++ b/docs/reference/library/data-loading.md @@ -0,0 +1,4 @@ +Data loading from external files. + +These functions help you with loading and embedding data, for example from the +results of an experiment. diff --git a/docs/reference/library/foundations.md b/docs/reference/library/foundations.md new file mode 100644 index 000000000..738c3789d --- /dev/null +++ b/docs/reference/library/foundations.md @@ -0,0 +1,4 @@ +Foundational types and functions. + +Here, you'll find documentation for basic data types like [integers]($int) and +[strings]($str) as well as details about core computational functions. diff --git a/docs/reference/library/introspection.md b/docs/reference/library/introspection.md new file mode 100644 index 000000000..f48a9937c --- /dev/null +++ b/docs/reference/library/introspection.md @@ -0,0 +1,10 @@ +Interactions between document parts. + +This category is home to Typst's introspection capabilities: With the `counter` +function, you can access and manipulate page, section, figure, and equation +counters or create custom ones. Meanwhile, the `query` function lets you search +for elements in the document to construct things like a list of figures or +headers which show the current chapter title. + +Most of the functions are _contextual._ It is recommended to read the chapter on +[context] before continuing here. diff --git a/docs/reference/library/layout.md b/docs/reference/library/layout.md new file mode 100644 index 000000000..450058d4c --- /dev/null +++ b/docs/reference/library/layout.md @@ -0,0 +1,3 @@ +Arranging elements on the page in different ways. + +By combining layout functions, you can create complex and automatic layouts. diff --git a/docs/reference/library/math.md b/docs/reference/library/math.md new file mode 100644 index 000000000..61f2bb58f --- /dev/null +++ b/docs/reference/library/math.md @@ -0,0 +1,101 @@ +Typst has special [syntax]($syntax/#math) and library functions to typeset +mathematical formulas. Math formulas can be displayed inline with text or as +separate blocks. They will be typeset into their own block if they start and end +with at least one space (e.g. `[$ x^2 $]`). + +# Variables +In math, single letters are always displayed as is. Multiple letters, however, +are interpreted as variables and functions. To display multiple letters +verbatim, you can place them into quotes and to access single letter variables, +you can use the [hash syntax]($scripting/#expressions). + +```example +$ A = pi r^2 $ +$ "area" = pi dot "radius"^2 $ +$ cal(A) := + { x in RR | x "is natural" } $ +#let x = 5 +$ #x < 17 $ +``` + +# Symbols +Math mode makes a wide selection of [symbols]($category/symbols/sym) like `pi`, +`dot`, or `RR` available. Many mathematical symbols are available in different +variants. You can select between different variants by applying +[modifiers]($symbol) to the symbol. Typst further recognizes a number of +shorthand sequences like `=>` that approximate a symbol. When such a shorthand +exists, the symbol's documentation lists it. + +```example +$ x < y => x gt.eq.not y $ +``` + +# Line Breaks +Formulas can also contain line breaks. Each line can contain one or multiple +_alignment points_ (`&`) which are then aligned. + +```example +$ sum_(k=0)^n k + &= 1 + ... + n \ + &= (n(n+1)) / 2 $ +``` + +# Function calls +Math mode supports special function calls without the hash prefix. In these +"math calls", the argument list works a little differently than in code: + +- Within them, Typst is still in "math mode". Thus, you can write math directly + into them, but need to use hash syntax to pass code expressions (except for + strings, which are available in the math syntax). +- They support positional and named arguments, as well as argument spreading. +- They don't support trailing content blocks. +- They provide additional syntax for 2-dimensional argument lists. The semicolon + (`;`) merges preceding arguments separated by commas into an array argument. + +```example +$ frac(a^2, 2) $ +$ vec(1, 2, delim: "[") $ +$ mat(1, 2; 3, 4) $ +$ mat(..#range(1, 5).chunks(2)) $ +$ lim_x = + op("lim", limits: #true)_x $ +``` + +To write a verbatim comma or semicolon in a math call, escape it with a +backslash. The colon on the other hand is only recognized in a special way if +directly preceded by an identifier, so to display it verbatim in those cases, +you can just insert a space before it. + +Functions calls preceded by a hash are normal code function calls and not +affected by these rules. + +# Alignment +When equations include multiple _alignment points_ (`&`), this creates blocks of +alternatingly right- and left-aligned columns. In the example below, the +expression `(3x + y) / 7` is right-aligned and `= 9` is left-aligned. The word +"given" is also left-aligned because `&&` creates two alignment points in a row, +alternating the alignment twice. `& &` and `&&` behave exactly the same way. +Meanwhile, "multiply by 7" is right-aligned because just one `&` precedes it. +Each alignment point simply alternates between right-aligned/left-aligned. + +```example +$ (3x + y) / 7 &= 9 && "given" \ + 3x + y &= 63 & "multiply by 7" \ + 3x &= 63 - y && "subtract y" \ + x &= 21 - y/3 & "divide by 3" $ +``` + +# Math fonts +You can set the math font by with a [show-set rule]($styling/#show-rules) as +demonstrated below. Note that only special OpenType math fonts are suitable for +typesetting maths. + +```example +#show math.equation: set text(font: "Fira Math") +$ sum_(i in NN) 1 + i $ +``` + +# Math module +All math functions are part of the `math` [module]($scripting/#modules), which +is available by default in equations. Outside of equations, they can be accessed +with the `math.` prefix. diff --git a/docs/reference/library/model.md b/docs/reference/library/model.md new file mode 100644 index 000000000..e433ed53b --- /dev/null +++ b/docs/reference/library/model.md @@ -0,0 +1,5 @@ +Document structuring. + +Here, you can find functions to structure your document and interact with that +structure. This includes section headings, figures, bibliography management, +cross-referencing and more. diff --git a/docs/reference/library/symbols.md b/docs/reference/library/symbols.md new file mode 100644 index 000000000..2e6f48cdb --- /dev/null +++ b/docs/reference/library/symbols.md @@ -0,0 +1,5 @@ +These two modules give names to symbols and emoji to make them easy to insert +with a normal keyboard. Alternatively, you can also always directly enter +Unicode symbols into your text and formulas. In addition to the symbols listed +below, math mode defines `dif` and `Dif`. These are not normal symbol values +because they also affect spacing and font style. diff --git a/docs/reference/library/text.md b/docs/reference/library/text.md new file mode 100644 index 000000000..239c0b265 --- /dev/null +++ b/docs/reference/library/text.md @@ -0,0 +1,3 @@ +Text styling. + +The [text function]($text) is of particular interest. diff --git a/docs/reference/library/visualize.md b/docs/reference/library/visualize.md new file mode 100644 index 000000000..9259401f8 --- /dev/null +++ b/docs/reference/library/visualize.md @@ -0,0 +1,5 @@ +Drawing and data visualization. + +If you want to create more advanced drawings or plots, also have a look at the +[CetZ](https://github.com/johannes-wolf/cetz) package as well as more +specialized [packages]($universe) for your use case. diff --git a/docs/reference/packages.md b/docs/reference/packages.md deleted file mode 100644 index bfd1ef580..000000000 --- a/docs/reference/packages.md +++ /dev/null @@ -1,6 +0,0 @@ -Typst [packages]($scripting/#packages) encapsulate reusable building blocks -and make them reusable across projects. Below is a list of Typst packages -created by the community. Due to the early and experimental nature of Typst's -package management, they all live in a `preview` namespace. Click on a package's -name to view its documentation and use the copy button on the right to get a -full import statement for it. diff --git a/docs/src/html.rs b/docs/src/html.rs index 4eb3954c3..9077d5c47 100644 --- a/docs/src/html.rs +++ b/docs/src/html.rs @@ -301,7 +301,10 @@ impl<'a> Handler<'a> { return; } - let default = self.peeked.as_ref().map(|text| text.to_kebab_case()); + let body = self.peeked.as_ref(); + let default = body.map(|text| text.to_kebab_case()); + let has_id = id_slot.is_some(); + let id: &'a str = match (&id_slot, default) { (Some(id), default) => { if Some(*id) == default.as_deref() { @@ -316,10 +319,10 @@ impl<'a> Handler<'a> { *id_slot = (!id.is_empty()).then_some(id); // Special case for things like "v0.3.0". - let name = if id.starts_with('v') && id.contains('.') { - id.into() - } else { - id.to_title_case().into() + let name = match &body { + _ if id.starts_with('v') && id.contains('.') => id.into(), + Some(body) if !has_id => body.as_ref().into(), + _ => id.to_title_case().into(), }; let mut children = &mut self.outline; diff --git a/docs/src/lib.rs b/docs/src/lib.rs index ff745c9c2..f9ee05bbd 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -12,27 +12,20 @@ pub use self::model::*; use std::collections::HashSet; use ecow::{eco_format, EcoString}; +use heck::ToTitleCase; use serde::Deserialize; use serde_yaml as yaml; use std::sync::LazyLock; use typst::diag::{bail, StrResult}; -use typst::foundations::Binding; use typst::foundations::{ - AutoValue, Bytes, CastInfo, Category, Func, Module, NoneValue, ParamInfo, Repr, - Scope, Smart, Type, Value, FOUNDATIONS, + AutoValue, Binding, Bytes, CastInfo, Func, Module, NoneValue, ParamInfo, Repr, Scope, + Smart, Type, Value, }; -use typst::html::HTML; -use typst::introspection::INTROSPECTION; -use typst::layout::{Abs, Margin, PageElem, PagedDocument, LAYOUT}; -use typst::loading::DATA_LOADING; -use typst::math::MATH; -use typst::model::MODEL; -use typst::pdf::PDF; -use typst::symbols::SYMBOLS; -use typst::text::{Font, FontBook, TEXT}; +use typst::layout::{Abs, Margin, PageElem, PagedDocument}; +use typst::text::{Font, FontBook}; use typst::utils::LazyHash; -use typst::visualize::VISUALIZE; -use typst::{Feature, Library, LibraryBuilder}; +use typst::{Category, Feature, Library, LibraryBuilder}; +use unicode_math_class::MathClass; macro_rules! load { ($path:literal) => { @@ -64,9 +57,10 @@ static LIBRARY: LazyLock> = LazyLock::new(|| { let scope = lib.global.scope_mut(); // Add those types, so that they show up in the docs. - scope.start_category(FOUNDATIONS); + scope.start_category(Category::Foundations); scope.define_type::(); scope.define_type::(); + scope.reset_category(); // Adjust the default look. lib.styles @@ -155,21 +149,24 @@ fn reference_pages(resolver: &dyn Resolver) -> PageModel { let mut page = md_page(resolver, resolver.base(), load!("reference/welcome.md")); let base = format!("{}reference/", resolver.base()); page.children = vec![ - md_page(resolver, &base, load!("reference/syntax.md")).with_part("Language"), - md_page(resolver, &base, load!("reference/styling.md")), - md_page(resolver, &base, load!("reference/scripting.md")), - md_page(resolver, &base, load!("reference/context.md")), - category_page(resolver, FOUNDATIONS).with_part("Library"), - category_page(resolver, MODEL), - category_page(resolver, TEXT), - category_page(resolver, MATH), - category_page(resolver, SYMBOLS), - category_page(resolver, LAYOUT), - category_page(resolver, VISUALIZE), - category_page(resolver, INTROSPECTION), - category_page(resolver, DATA_LOADING), - category_page(resolver, PDF), - category_page(resolver, HTML), + md_page(resolver, &base, load!("reference/language/syntax.md")) + .with_part("Language"), + md_page(resolver, &base, load!("reference/language/styling.md")), + md_page(resolver, &base, load!("reference/language/scripting.md")), + md_page(resolver, &base, load!("reference/language/context.md")), + category_page(resolver, Category::Foundations).with_part("Library"), + category_page(resolver, Category::Model), + category_page(resolver, Category::Text), + category_page(resolver, Category::Math), + category_page(resolver, Category::Symbols), + category_page(resolver, Category::Layout), + category_page(resolver, Category::Visualize), + category_page(resolver, Category::Introspection), + category_page(resolver, Category::DataLoading), + category_page(resolver, Category::Pdf).with_part("Export"), + category_page(resolver, Category::Html), + category_page(resolver, Category::Png), + category_page(resolver, Category::Svg), ]; page } @@ -219,14 +216,16 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { let mut markup = vec![]; let mut math = vec![]; - let (module, path): (&Module, &[&str]) = if category == MATH { - (&LIBRARY.math, &["math"]) - } else { - (&LIBRARY.global, &[]) + let docs = category_docs(category); + let (module, path): (&Module, &[&str]) = match category { + Category::Math => (&LIBRARY.math, &["math"]), + Category::Pdf => (get_module(&LIBRARY.global, "pdf").unwrap(), &["pdf"]), + Category::Html => (get_module(&LIBRARY.global, "html").unwrap(), &["html"]), + _ => (&LIBRARY.global, &[]), }; // Add groups. - for group in GROUPS.iter().filter(|g| g.category == category.name()).cloned() { + for group in GROUPS.iter().filter(|g| g.category == category).cloned() { if matches!(group.name.as_str(), "sym" | "emoji") { let subpage = symbols_page(resolver, &route, &group); let BodyModel::Symbols(model) = &subpage.body else { continue }; @@ -243,7 +242,7 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { items.push(CategoryItem { name: group.name.clone(), route: subpage.route.clone(), - oneliner: oneliner(category.docs()).into(), + oneliner: oneliner(docs).into(), code: true, }); children.push(subpage); @@ -256,15 +255,15 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { } // Add symbol pages. These are ordered manually. - if category == SYMBOLS { + if category == Category::Symbols { shorthands = Some(ShorthandsModel { markup, math }); } let mut skip = HashSet::new(); - if category == MATH { + if category == Category::Math { skip = GROUPS .iter() - .filter(|g| g.category == category.name()) + .filter(|g| g.category == category) .flat_map(|g| &g.filter) .map(|s| s.as_str()) .collect(); @@ -273,6 +272,11 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { skip.insert("text"); } + // Tiling would be duplicate otherwise. + if category == Category::Visualize { + skip.insert("pattern"); + } + // Add values and types. let scope = module.scope(); for (name, binding) in scope.iter() { @@ -287,8 +291,8 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { match binding.read() { Value::Func(func) => { let name = func.name().unwrap(); - - let subpage = func_page(resolver, &route, func, path); + let subpage = + func_page(resolver, &route, func, path, binding.deprecation()); items.push(CategoryItem { name: name.into(), route: subpage.route.clone(), @@ -311,31 +315,39 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { } } - if category != SYMBOLS { + if category != Category::Symbols { children.sort_by_cached_key(|child| child.title.clone()); items.sort_by_cached_key(|item| item.name.clone()); } - let name = category.title(); - let details = Html::markdown(resolver, category.docs(), Some(1)); + let title = EcoString::from(match category { + Category::Pdf | Category::Html | Category::Png | Category::Svg => { + category.name().to_uppercase() + } + _ => category.name().to_title_case(), + }); + + let details = Html::markdown(resolver, docs, Some(1)); let mut outline = vec![OutlineItem::from_name("Summary")]; outline.extend(details.outline()); - outline.push(OutlineItem::from_name("Definitions")); + if !items.is_empty() { + outline.push(OutlineItem::from_name("Definitions")); + } if shorthands.is_some() { outline.push(OutlineItem::from_name("Shorthands")); } PageModel { route, - title: name.into(), + title: title.clone(), description: eco_format!( - "Documentation for functions related to {name} in Typst." + "Documentation for functions related to {title} in Typst." ), part: None, outline, body: BodyModel::Category(CategoryModel { name: category.name(), - title: category.title(), + title, details, items, shorthands, @@ -344,14 +356,34 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { } } +/// Retrieve the docs for a category. +fn category_docs(category: Category) -> &'static str { + match category { + Category::Foundations => load!("reference/library/foundations.md"), + Category::Introspection => load!("reference/library/introspection.md"), + Category::Layout => load!("reference/library/layout.md"), + Category::DataLoading => load!("reference/library/data-loading.md"), + Category::Math => load!("reference/library/math.md"), + Category::Model => load!("reference/library/model.md"), + Category::Symbols => load!("reference/library/symbols.md"), + Category::Text => load!("reference/library/text.md"), + Category::Visualize => load!("reference/library/visualize.md"), + Category::Pdf => load!("reference/export/pdf.md"), + Category::Html => load!("reference/export/html.md"), + Category::Svg => load!("reference/export/svg.md"), + Category::Png => load!("reference/export/png.md"), + } +} + /// Create a page for a function. fn func_page( resolver: &dyn Resolver, parent: &str, func: &Func, path: &[&str], + deprecation: Option<&'static str>, ) -> PageModel { - let model = func_model(resolver, func, path, false); + let model = func_model(resolver, func, path, false, deprecation); let name = func.name().unwrap(); PageModel { route: eco_format!("{parent}{}/", urlify(name)), @@ -370,6 +402,7 @@ fn func_model( func: &Func, path: &[&str], nested: bool, + deprecation: Option<&'static str>, ) -> FuncModel { let name = func.name().unwrap(); let scope = func.scope().unwrap(); @@ -383,7 +416,11 @@ fn func_model( } let mut returns = vec![]; - casts(resolver, &mut returns, &mut vec![], func.returns().unwrap()); + let mut strings = vec![]; + casts(resolver, &mut returns, &mut strings, func.returns().unwrap()); + if !strings.is_empty() && !returns.contains(&"str") { + returns.push("str"); + } returns.sort_by_key(|ty| type_index(ty)); if returns == ["none"] { returns.clear(); @@ -401,6 +438,7 @@ fn func_model( oneliner: oneliner(details), element: func.element().is_some(), contextual: func.contextual().unwrap_or(false), + deprecation, details: Html::markdown(resolver, details, nesting), example: example.map(|md| Html::markdown(resolver, md, None)), self_, @@ -483,7 +521,7 @@ fn scope_models(resolver: &dyn Resolver, name: &str, scope: &Scope) -> Vec() else { panic!("not a function") }; - let func = func_model(resolver, func, &path, true); + let binding = group.module().scope().get(name).unwrap(); + let Ok(ref func) = binding.read().clone().cast::() else { + panic!("not a function") + }; + let func = func_model(resolver, func, &path, true, binding.deprecation()); let id_base = urlify(&eco_format!("functions-{}", func.name)); let children = func_outline(&func, &id_base); outline_items.push(OutlineItem { @@ -628,7 +668,7 @@ fn type_model(resolver: &dyn Resolver, ty: &Type) -> TypeModel { constructor: ty .constructor() .ok() - .map(|func| func_model(resolver, &func, &[], true)), + .map(|func| func_model(resolver, &func, &[], true, None)), scope: scope_models(resolver, ty.short_name(), ty.scope()), } } @@ -682,10 +722,19 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s) }; + let name = complete(variant); + let deprecation = match name.as_str() { + "integral.sect" => { + Some("`integral.sect` is deprecated, use `integral.inter` instead") + } + _ => binding.deprecation(), + }; + list.push(SymbolModel { - name: complete(variant), + name, markup_shorthand: shorthand(typst::syntax::ast::Shorthand::LIST), math_shorthand: shorthand(typst::syntax::ast::MathShorthand::LIST), + math_class: typst_utils::default_math_class(c).map(math_class_name), codepoint: c as _, accent: typst::math::Accent::combine(c).is_some(), alternates: symbol @@ -693,6 +742,7 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { .filter(|(other, _)| other != &variant) .map(|(other, _)| complete(other)) .collect(), + deprecation, }); } } @@ -769,12 +819,32 @@ const TYPE_ORDER: &[&str] = &[ "stroke", ]; +fn math_class_name(class: MathClass) -> &'static str { + match class { + MathClass::Normal => "Normal", + MathClass::Alphabetic => "Alphabetic", + MathClass::Binary => "Binary", + MathClass::Closing => "Closing", + MathClass::Diacritic => "Diacritic", + MathClass::Fence => "Fence", + MathClass::GlyphPart => "Glyph Part", + MathClass::Large => "Large", + MathClass::Opening => "Opening", + MathClass::Punctuation => "Punctuation", + MathClass::Relation => "Relation", + MathClass::Space => "Space", + MathClass::Unary => "Unary", + MathClass::Vary => "Vary", + MathClass::Special => "Special", + } +} + /// Data about a collection of functions. #[derive(Debug, Clone, Deserialize)] struct GroupData { name: EcoString, title: EcoString, - category: EcoString, + category: Category, #[serde(default)] path: Vec, #[serde(default)] diff --git a/docs/src/link.rs b/docs/src/link.rs index c55261b84..2e836b6ce 100644 --- a/docs/src/link.rs +++ b/docs/src/link.rs @@ -44,6 +44,8 @@ fn resolve_known(head: &str, base: &str) -> Option { "$styling" => format!("{base}reference/styling"), "$scripting" => format!("{base}reference/scripting"), "$context" => format!("{base}reference/context"), + "$html" => format!("{base}reference/html"), + "$pdf" => format!("{base}reference/pdf"), "$guides" => format!("{base}guides"), "$changelog" => format!("{base}changelog"), "$universe" => "https://typst.app/universe".into(), @@ -73,11 +75,14 @@ fn resolve_definition(head: &str, base: &str) -> StrResult { // Handle grouped functions. if let Some(group) = GROUPS.iter().find(|group| { - group.category == category.name() && group.filter.iter().any(|func| func == name) + group.category == category && group.filter.iter().any(|func| func == name) }) { let mut route = format!( "{}reference/{}/{}/#functions-{}", - base, group.category, group.name, name + base, + group.category.name(), + group.name, + name ); if let Some(param) = parts.next() { route.push('-'); diff --git a/docs/src/model.rs b/docs/src/model.rs index b222322a7..801c60c7f 100644 --- a/docs/src/model.rs +++ b/docs/src/model.rs @@ -64,7 +64,7 @@ pub enum BodyModel { #[derive(Debug, Serialize)] pub struct CategoryModel { pub name: &'static str, - pub title: &'static str, + pub title: EcoString, pub details: Html, pub items: Vec, pub shorthands: Option, @@ -89,6 +89,7 @@ pub struct FuncModel { pub oneliner: &'static str, pub element: bool, pub contextual: bool, + pub deprecation: Option<&'static str>, pub details: Html, /// This example is only for nested function models. Others can have /// their example directly in their details. @@ -163,6 +164,8 @@ pub struct SymbolModel { pub alternates: Vec, pub markup_shorthand: Option<&'static str>, pub math_shorthand: Option<&'static str>, + pub math_class: Option<&'static str>, + pub deprecation: Option<&'static str>, } /// Shorthands listed on a category page.