mirror of
https://github.com/typst/typst
synced 2025-08-18 17:08:33 +08:00
Compare commits
7 Commits
56707649a8
...
1b92c15869
Author | SHA1 | Date | |
---|---|---|---|
|
1b92c15869 | ||
|
ac77fdbb6e | ||
|
3aa7e861e7 | ||
|
6daae2e292 | ||
|
7f24cd9253 | ||
|
fea153a6fc | ||
|
cdb8a42c68 |
@ -1,11 +1,72 @@
|
|||||||
//! Conversion from Typst data types into CSS data types.
|
//! Conversion from Typst data types into CSS data types.
|
||||||
|
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display, Write};
|
||||||
|
|
||||||
use typst_library::layout::Length;
|
use ecow::EcoString;
|
||||||
|
use typst_library::html::{attr, HtmlElem};
|
||||||
|
use typst_library::layout::{Length, Rel};
|
||||||
use typst_library::visualize::{Color, Hsl, LinearRgb, Oklab, Oklch, Rgb};
|
use typst_library::visualize::{Color, Hsl, LinearRgb, Oklab, Oklch, Rgb};
|
||||||
use typst_utils::Numeric;
|
use typst_utils::Numeric;
|
||||||
|
|
||||||
|
/// Additional methods for [`HtmlElem`].
|
||||||
|
pub trait HtmlElemExt {
|
||||||
|
/// Adds the styles to an element if the property list is non-empty.
|
||||||
|
fn with_styles(self, properties: Properties) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HtmlElemExt for HtmlElem {
|
||||||
|
/// Adds CSS styles to an element.
|
||||||
|
fn with_styles(self, properties: Properties) -> Self {
|
||||||
|
if let Some(value) = properties.into_inline_styles() {
|
||||||
|
self.with_attr(attr::style, value)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of CSS properties with values.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Properties(EcoString);
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
/// Creates an empty list.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new property to the list.
|
||||||
|
pub fn push(&mut self, property: &str, value: impl Display) {
|
||||||
|
if !self.0.is_empty() {
|
||||||
|
self.0.push_str("; ");
|
||||||
|
}
|
||||||
|
write!(&mut self.0, "{property}: {value}").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new property in builder-style.
|
||||||
|
#[expect(unused)]
|
||||||
|
pub fn with(mut self, property: &str, value: impl Display) -> Self {
|
||||||
|
self.push(property, value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns this into a string suitable for use as an inline `style`
|
||||||
|
/// attribute.
|
||||||
|
pub fn into_inline_styles(self) -> Option<EcoString> {
|
||||||
|
(!self.0.is_empty()).then_some(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rel(rel: Rel) -> impl Display {
|
||||||
|
typst_utils::display(move |f| match (rel.abs.is_zero(), rel.rel.is_zero()) {
|
||||||
|
(false, false) => {
|
||||||
|
write!(f, "calc({}% + {})", rel.rel.get(), length(rel.abs))
|
||||||
|
}
|
||||||
|
(true, false) => write!(f, "{}%", rel.rel.get()),
|
||||||
|
(_, true) => write!(f, "{}", length(rel.abs)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn length(length: Length) -> impl Display {
|
pub fn length(length: Length) -> impl Display {
|
||||||
typst_utils::display(move |f| match (length.abs.is_zero(), length.em.is_zero()) {
|
typst_utils::display(move |f| match (length.abs.is_zero(), length.em.is_zero()) {
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
|
@ -3,12 +3,12 @@ use std::num::NonZeroUsize;
|
|||||||
use ecow::{eco_format, EcoVec};
|
use ecow::{eco_format, EcoVec};
|
||||||
use typst_library::diag::warning;
|
use typst_library::diag::warning;
|
||||||
use typst_library::foundations::{
|
use typst_library::foundations::{
|
||||||
Content, NativeElement, NativeRuleMap, ShowFn, StyleChain, Target,
|
Content, NativeElement, NativeRuleMap, ShowFn, Smart, StyleChain, Target,
|
||||||
};
|
};
|
||||||
use typst_library::html::{attr, tag, HtmlAttrs, HtmlElem, HtmlTag};
|
use typst_library::html::{attr, tag, HtmlAttrs, HtmlElem, HtmlTag};
|
||||||
use typst_library::introspection::{Counter, Locator};
|
use typst_library::introspection::{Counter, Locator};
|
||||||
use typst_library::layout::resolve::{table_to_cellgrid, Cell, CellGrid, Entry};
|
use typst_library::layout::resolve::{table_to_cellgrid, Cell, CellGrid, Entry};
|
||||||
use typst_library::layout::OuterVAlignment;
|
use typst_library::layout::{OuterVAlignment, Sizing};
|
||||||
use typst_library::model::{
|
use typst_library::model::{
|
||||||
Attribution, CiteElem, CiteGroup, Destination, EmphElem, EnumElem, FigureCaption,
|
Attribution, CiteElem, CiteGroup, Destination, EmphElem, EnumElem, FigureCaption,
|
||||||
FigureElem, HeadingElem, LinkElem, LinkTarget, ListElem, ParbreakElem, QuoteElem,
|
FigureElem, HeadingElem, LinkElem, LinkTarget, ListElem, ParbreakElem, QuoteElem,
|
||||||
@ -18,6 +18,9 @@ use typst_library::text::{
|
|||||||
HighlightElem, LinebreakElem, OverlineElem, RawElem, RawLine, SpaceElem, StrikeElem,
|
HighlightElem, LinebreakElem, OverlineElem, RawElem, RawLine, SpaceElem, StrikeElem,
|
||||||
SubElem, SuperElem, UnderlineElem,
|
SubElem, SuperElem, UnderlineElem,
|
||||||
};
|
};
|
||||||
|
use typst_library::visualize::ImageElem;
|
||||||
|
|
||||||
|
use crate::css::{self, HtmlElemExt};
|
||||||
|
|
||||||
/// Register show rules for the [HTML target](Target::Html).
|
/// Register show rules for the [HTML target](Target::Html).
|
||||||
pub fn register(rules: &mut NativeRuleMap) {
|
pub fn register(rules: &mut NativeRuleMap) {
|
||||||
@ -47,6 +50,9 @@ pub fn register(rules: &mut NativeRuleMap) {
|
|||||||
rules.register(Html, HIGHLIGHT_RULE);
|
rules.register(Html, HIGHLIGHT_RULE);
|
||||||
rules.register(Html, RAW_RULE);
|
rules.register(Html, RAW_RULE);
|
||||||
rules.register(Html, RAW_LINE_RULE);
|
rules.register(Html, RAW_LINE_RULE);
|
||||||
|
|
||||||
|
// Visualize.
|
||||||
|
rules.register(Html, IMAGE_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
const STRONG_RULE: ShowFn<StrongElem> = |elem, _, _| {
|
const STRONG_RULE: ShowFn<StrongElem> = |elem, _, _| {
|
||||||
@ -338,7 +344,7 @@ fn show_cellgrid(grid: CellGrid, styles: StyleChain) -> Content {
|
|||||||
fn show_cell(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content {
|
fn show_cell(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content {
|
||||||
let cell = cell.body.clone();
|
let cell = cell.body.clone();
|
||||||
let Some(cell) = cell.to_packed::<TableCell>() else { return cell };
|
let Some(cell) = cell.to_packed::<TableCell>() else { return cell };
|
||||||
let mut attrs = HtmlAttrs::default();
|
let mut attrs = HtmlAttrs::new();
|
||||||
let span = |n: NonZeroUsize| (n != NonZeroUsize::MIN).then(|| n.to_string());
|
let span = |n: NonZeroUsize| (n != NonZeroUsize::MIN).then(|| n.to_string());
|
||||||
if let Some(colspan) = span(cell.colspan.get(styles)) {
|
if let Some(colspan) = span(cell.colspan.get(styles)) {
|
||||||
attrs.push(attr::colspan, colspan);
|
attrs.push(attr::colspan, colspan);
|
||||||
@ -409,3 +415,36 @@ const RAW_RULE: ShowFn<RawElem> = |elem, _, styles| {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const RAW_LINE_RULE: ShowFn<RawLine> = |elem, _, _| Ok(elem.body.clone());
|
const RAW_LINE_RULE: ShowFn<RawLine> = |elem, _, _| Ok(elem.body.clone());
|
||||||
|
|
||||||
|
const IMAGE_RULE: ShowFn<ImageElem> = |elem, engine, styles| {
|
||||||
|
let image = elem.decode(engine, styles)?;
|
||||||
|
|
||||||
|
let mut attrs = HtmlAttrs::new();
|
||||||
|
attrs.push(attr::src, typst_svg::convert_image_to_base64_url(&image));
|
||||||
|
|
||||||
|
if let Some(alt) = elem.alt.get_cloned(styles) {
|
||||||
|
attrs.push(attr::alt, alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inline = css::Properties::new();
|
||||||
|
|
||||||
|
// TODO: Exclude in semantic profile.
|
||||||
|
if let Some(value) = typst_svg::convert_image_scaling(image.scaling()) {
|
||||||
|
inline.push("image-rendering", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Exclude in semantic profile?
|
||||||
|
match elem.width.get(styles) {
|
||||||
|
Smart::Auto => {}
|
||||||
|
Smart::Custom(rel) => inline.push("width", css::rel(rel)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Exclude in semantic profile?
|
||||||
|
match elem.height.get(styles) {
|
||||||
|
Sizing::Auto => {}
|
||||||
|
Sizing::Rel(rel) => inline.push("height", css::rel(rel)),
|
||||||
|
Sizing::Fr(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HtmlElem::new(tag::img).with_attrs(attrs).with_styles(inline).pack())
|
||||||
|
};
|
||||||
|
@ -2,7 +2,7 @@ use comemo::Track;
|
|||||||
use ecow::{eco_vec, EcoString, EcoVec};
|
use ecow::{eco_vec, EcoString, EcoVec};
|
||||||
use typst::foundations::{Label, Styles, Value};
|
use typst::foundations::{Label, Styles, Value};
|
||||||
use typst::layout::PagedDocument;
|
use typst::layout::PagedDocument;
|
||||||
use typst::model::BibliographyElem;
|
use typst::model::{BibliographyElem, FigureElem};
|
||||||
use typst::syntax::{ast, LinkedNode, SyntaxKind};
|
use typst::syntax::{ast, LinkedNode, SyntaxKind};
|
||||||
|
|
||||||
use crate::IdeWorld;
|
use crate::IdeWorld;
|
||||||
@ -75,8 +75,13 @@ pub fn analyze_labels(
|
|||||||
for elem in document.introspector.all() {
|
for elem in document.introspector.all() {
|
||||||
let Some(label) = elem.label() else { continue };
|
let Some(label) = elem.label() else { continue };
|
||||||
let details = elem
|
let details = elem
|
||||||
.get_by_name("caption")
|
.to_packed::<FigureElem>()
|
||||||
.or_else(|_| elem.get_by_name("body"))
|
.and_then(|figure| match figure.caption.as_option() {
|
||||||
|
Some(Some(caption)) => Some(caption.pack_ref()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or(elem)
|
||||||
|
.get_by_name("body")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|field| match field {
|
.and_then(|field| match field {
|
||||||
Value::Content(content) => Some(content),
|
Value::Content(content) => Some(content),
|
||||||
|
@ -378,4 +378,9 @@ mod tests {
|
|||||||
.with_source("other.typ", "#let f = (x) => 1");
|
.with_source("other.typ", "#let f = (x) => 1");
|
||||||
test(&world, -4, Side::After).must_be_code("(..) => ..");
|
test(&world, -4, Side::After).must_be_code("(..) => ..");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tooltip_reference() {
|
||||||
|
test("#figure(caption: [Hi])[]<f> @f", -1, Side::Before).must_be_text("Hi");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
use std::ffi::OsStr;
|
use typst_library::diag::SourceResult;
|
||||||
|
|
||||||
use typst_library::diag::{warning, At, LoadedWithin, SourceResult, StrResult};
|
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{Bytes, Derived, Packed, Smart, StyleChain};
|
use typst_library::foundations::{Packed, StyleChain};
|
||||||
use typst_library::introspection::Locator;
|
use typst_library::introspection::Locator;
|
||||||
use typst_library::layout::{
|
use typst_library::layout::{
|
||||||
Abs, Axes, FixedAlignment, Frame, FrameItem, Point, Region, Size,
|
Abs, Axes, FixedAlignment, Frame, FrameItem, Point, Region, Size,
|
||||||
};
|
};
|
||||||
use typst_library::loading::DataSource;
|
use typst_library::visualize::{Curve, Image, ImageElem, ImageFit};
|
||||||
use typst_library::text::families;
|
|
||||||
use typst_library::visualize::{
|
|
||||||
Curve, ExchangeFormat, Image, ImageElem, ImageFit, ImageFormat, ImageKind,
|
|
||||||
RasterImage, SvgImage, VectorFormat,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Layout the image.
|
/// Layout the image.
|
||||||
#[typst_macros::time(span = elem.span())]
|
#[typst_macros::time(span = elem.span())]
|
||||||
@ -23,53 +16,7 @@ pub fn layout_image(
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
region: Region,
|
region: Region,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Frame> {
|
||||||
let span = elem.span();
|
let image = elem.decode(engine, styles)?;
|
||||||
|
|
||||||
// Take the format that was explicitly defined, or parse the extension,
|
|
||||||
// or try to detect the format.
|
|
||||||
let Derived { source, derived: loaded } = &elem.source;
|
|
||||||
let format = match elem.format.get(styles) {
|
|
||||||
Smart::Custom(v) => v,
|
|
||||||
Smart::Auto => determine_format(source, &loaded.data).at(span)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Warn the user if the image contains a foreign object. Not perfect
|
|
||||||
// because the svg could also be encoded, but that's an edge case.
|
|
||||||
if format == ImageFormat::Vector(VectorFormat::Svg) {
|
|
||||||
let has_foreign_object =
|
|
||||||
memchr::memmem::find(&loaded.data, b"<foreignObject").is_some();
|
|
||||||
|
|
||||||
if has_foreign_object {
|
|
||||||
engine.sink.warn(warning!(
|
|
||||||
span,
|
|
||||||
"image contains foreign object";
|
|
||||||
hint: "SVG images with foreign objects might render incorrectly in typst";
|
|
||||||
hint: "see https://github.com/typst/typst/issues/1421 for more information"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the image itself.
|
|
||||||
let kind = match format {
|
|
||||||
ImageFormat::Raster(format) => ImageKind::Raster(
|
|
||||||
RasterImage::new(
|
|
||||||
loaded.data.clone(),
|
|
||||||
format,
|
|
||||||
elem.icc.get_ref(styles).as_ref().map(|icc| icc.derived.clone()),
|
|
||||||
)
|
|
||||||
.at(span)?,
|
|
||||||
),
|
|
||||||
ImageFormat::Vector(VectorFormat::Svg) => ImageKind::Svg(
|
|
||||||
SvgImage::with_fonts(
|
|
||||||
loaded.data.clone(),
|
|
||||||
engine.world,
|
|
||||||
&families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
.within(loaded)?,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let image = Image::new(kind, elem.alt.get_cloned(styles), elem.scaling.get(styles));
|
|
||||||
|
|
||||||
// Determine the image's pixel aspect ratio.
|
// Determine the image's pixel aspect ratio.
|
||||||
let pxw = image.width();
|
let pxw = image.width();
|
||||||
@ -122,7 +69,7 @@ pub fn layout_image(
|
|||||||
// the frame to the target size, center aligning the image in the
|
// the frame to the target size, center aligning the image in the
|
||||||
// process.
|
// process.
|
||||||
let mut frame = Frame::soft(fitted);
|
let mut frame = Frame::soft(fitted);
|
||||||
frame.push(Point::zero(), FrameItem::Image(image, fitted, span));
|
frame.push(Point::zero(), FrameItem::Image(image, fitted, elem.span()));
|
||||||
frame.resize(target, Axes::splat(FixedAlignment::Center));
|
frame.resize(target, Axes::splat(FixedAlignment::Center));
|
||||||
|
|
||||||
// Create a clipping group if only part of the image should be visible.
|
// Create a clipping group if only part of the image should be visible.
|
||||||
@ -132,25 +79,3 @@ pub fn layout_image(
|
|||||||
|
|
||||||
Ok(frame)
|
Ok(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to determine the image format based on the data.
|
|
||||||
fn determine_format(source: &DataSource, data: &Bytes) -> StrResult<ImageFormat> {
|
|
||||||
if let DataSource::Path(path) = source {
|
|
||||||
let ext = std::path::Path::new(path.as_str())
|
|
||||||
.extension()
|
|
||||||
.and_then(OsStr::to_str)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_lowercase();
|
|
||||||
|
|
||||||
match ext.as_str() {
|
|
||||||
"png" => return Ok(ExchangeFormat::Png.into()),
|
|
||||||
"jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()),
|
|
||||||
"gif" => return Ok(ExchangeFormat::Gif.into()),
|
|
||||||
"svg" | "svgz" => return Ok(VectorFormat::Svg.into()),
|
|
||||||
"webp" => return Ok(ExchangeFormat::Webp.into()),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ImageFormat::detect(data).ok_or("unknown image format")?)
|
|
||||||
}
|
|
||||||
|
@ -165,6 +165,11 @@ cast! {
|
|||||||
pub struct HtmlAttrs(pub EcoVec<(HtmlAttr, EcoString)>);
|
pub struct HtmlAttrs(pub EcoVec<(HtmlAttr, EcoString)>);
|
||||||
|
|
||||||
impl HtmlAttrs {
|
impl HtmlAttrs {
|
||||||
|
/// Creates an empty attribute list.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
/// Add an attribute.
|
/// Add an attribute.
|
||||||
pub fn push(&mut self, attr: HtmlAttr, value: impl Into<EcoString>) {
|
pub fn push(&mut self, attr: HtmlAttr, value: impl Into<EcoString>) {
|
||||||
self.0.push((attr, value.into()));
|
self.0.push((attr, value.into()));
|
||||||
|
@ -8,6 +8,7 @@ pub use self::raster::{
|
|||||||
};
|
};
|
||||||
pub use self::svg::SvgImage;
|
pub use self::svg::SvgImage;
|
||||||
|
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -15,14 +16,16 @@ use ecow::EcoString;
|
|||||||
use typst_syntax::{Span, Spanned};
|
use typst_syntax::{Span, Spanned};
|
||||||
use typst_utils::LazyHash;
|
use typst_utils::LazyHash;
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
use crate::diag::{warning, At, LoadedWithin, SourceResult, StrResult};
|
||||||
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Smart,
|
cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Smart,
|
||||||
|
StyleChain,
|
||||||
};
|
};
|
||||||
use crate::layout::{Length, Rel, Sizing};
|
use crate::layout::{Length, Rel, Sizing};
|
||||||
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
|
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
|
||||||
use crate::model::Figurable;
|
use crate::model::Figurable;
|
||||||
use crate::text::LocalName;
|
use crate::text::{families, LocalName};
|
||||||
|
|
||||||
/// A raster or vector graphic.
|
/// A raster or vector graphic.
|
||||||
///
|
///
|
||||||
@ -217,6 +220,81 @@ impl ImageElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Packed<ImageElem> {
|
||||||
|
/// Decodes the image.
|
||||||
|
pub fn decode(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Image> {
|
||||||
|
let span = self.span();
|
||||||
|
let loaded = &self.source.derived;
|
||||||
|
let format = self.determine_format(styles).at(span)?;
|
||||||
|
|
||||||
|
// Warn the user if the image contains a foreign object. Not perfect
|
||||||
|
// because the svg could also be encoded, but that's an edge case.
|
||||||
|
if format == ImageFormat::Vector(VectorFormat::Svg) {
|
||||||
|
let has_foreign_object =
|
||||||
|
memchr::memmem::find(&loaded.data, b"<foreignObject").is_some();
|
||||||
|
|
||||||
|
if has_foreign_object {
|
||||||
|
engine.sink.warn(warning!(
|
||||||
|
span,
|
||||||
|
"image contains foreign object";
|
||||||
|
hint: "SVG images with foreign objects might render incorrectly in typst";
|
||||||
|
hint: "see https://github.com/typst/typst/issues/1421 for more information"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the image itself.
|
||||||
|
let kind = match format {
|
||||||
|
ImageFormat::Raster(format) => ImageKind::Raster(
|
||||||
|
RasterImage::new(
|
||||||
|
loaded.data.clone(),
|
||||||
|
format,
|
||||||
|
self.icc.get_ref(styles).as_ref().map(|icc| icc.derived.clone()),
|
||||||
|
)
|
||||||
|
.at(span)?,
|
||||||
|
),
|
||||||
|
ImageFormat::Vector(VectorFormat::Svg) => ImageKind::Svg(
|
||||||
|
SvgImage::with_fonts(
|
||||||
|
loaded.data.clone(),
|
||||||
|
engine.world,
|
||||||
|
&families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.within(loaded)?,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Image::new(kind, self.alt.get_cloned(styles), self.scaling.get(styles)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to determine the image format based on the format that was
|
||||||
|
/// explicitly defined, or else the extension, or else the data.
|
||||||
|
fn determine_format(&self, styles: StyleChain) -> StrResult<ImageFormat> {
|
||||||
|
if let Smart::Custom(v) = self.format.get(styles) {
|
||||||
|
return Ok(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
let Derived { source, derived: loaded } = &self.source;
|
||||||
|
if let DataSource::Path(path) = source {
|
||||||
|
let ext = std::path::Path::new(path.as_str())
|
||||||
|
.extension()
|
||||||
|
.and_then(OsStr::to_str)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_lowercase();
|
||||||
|
|
||||||
|
match ext.as_str() {
|
||||||
|
"png" => return Ok(ExchangeFormat::Png.into()),
|
||||||
|
"jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()),
|
||||||
|
"gif" => return Ok(ExchangeFormat::Gif.into()),
|
||||||
|
"svg" | "svgz" => return Ok(VectorFormat::Svg.into()),
|
||||||
|
"webp" => return Ok(ExchangeFormat::Webp.into()),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ImageFormat::detect(&loaded.data).ok_or("unknown image format")?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LocalName for Packed<ImageElem> {
|
impl LocalName for Packed<ImageElem> {
|
||||||
const KEY: &'static str = "figure";
|
const KEY: &'static str = "figure";
|
||||||
}
|
}
|
||||||
|
@ -18,18 +18,24 @@ impl SVGRenderer {
|
|||||||
self.xml.write_attribute("width", &size.x.to_pt());
|
self.xml.write_attribute("width", &size.x.to_pt());
|
||||||
self.xml.write_attribute("height", &size.y.to_pt());
|
self.xml.write_attribute("height", &size.y.to_pt());
|
||||||
self.xml.write_attribute("preserveAspectRatio", "none");
|
self.xml.write_attribute("preserveAspectRatio", "none");
|
||||||
match image.scaling() {
|
if let Some(value) = convert_image_scaling(image.scaling()) {
|
||||||
Smart::Auto => {}
|
self.xml
|
||||||
|
.write_attribute("style", &format_args!("image-rendering: {value}"))
|
||||||
|
}
|
||||||
|
self.xml.end_element();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an image scaling to a CSS `image-rendering` propery value.
|
||||||
|
pub fn convert_image_scaling(scaling: Smart<ImageScaling>) -> Option<&'static str> {
|
||||||
|
match scaling {
|
||||||
|
Smart::Auto => None,
|
||||||
Smart::Custom(ImageScaling::Smooth) => {
|
Smart::Custom(ImageScaling::Smooth) => {
|
||||||
// This is still experimental and not implemented in all major browsers.
|
// This is still experimental and not implemented in all major browsers.
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering#browser_compatibility
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering#browser_compatibility
|
||||||
self.xml.write_attribute("style", "image-rendering: smooth")
|
Some("smooth")
|
||||||
}
|
}
|
||||||
Smart::Custom(ImageScaling::Pixelated) => {
|
Smart::Custom(ImageScaling::Pixelated) => Some("pixelated"),
|
||||||
self.xml.write_attribute("style", "image-rendering: pixelated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.xml.end_element();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ mod paint;
|
|||||||
mod shape;
|
mod shape;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
|
pub use image::{convert_image_scaling, convert_image_to_base64_url};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Display, Formatter, Write};
|
use std::fmt::{self, Display, Formatter, Write};
|
||||||
|
|
||||||
|
@ -84,8 +84,9 @@ meaning in Typst. We can use `=`, `-`, `+`, and `_` to create headings, lists
|
|||||||
and emphasized text, respectively. However, having a special symbol for
|
and emphasized text, respectively. However, having a special symbol for
|
||||||
everything we want to insert into our document would soon become cryptic and
|
everything we want to insert into our document would soon become cryptic and
|
||||||
unwieldy. For this reason, Typst reserves markup symbols only for the most
|
unwieldy. For this reason, Typst reserves markup symbols only for the most
|
||||||
common things. Everything else is inserted with _functions._ For our image to
|
common things. Everything else is inserted with _functions._ For
|
||||||
show up on the page, we use Typst's [`image`] function.
|
[our image](https://github.com/typst/typst-dev-assets/blob/main/files/images/glacier.jpg)
|
||||||
|
to show up on the page, we use Typst's [`image`] function.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#image("glacier.jpg")
|
#image("glacier.jpg")
|
||||||
@ -125,19 +126,38 @@ mode. This means, you now have to remove the hash before the image function call
|
|||||||
The hash is only needed directly in markup (to disambiguate text from function
|
The hash is only needed directly in markup (to disambiguate text from function
|
||||||
calls).
|
calls).
|
||||||
|
|
||||||
The caption consists of arbitrary markup. To give markup to a function, we
|
The caption consists of arbitrary markup, and can also be a string. To give
|
||||||
enclose it in square brackets. This construct is called a _content block._
|
markup to a function, we enclose it in square brackets. This construct is called
|
||||||
|
a _content block._
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#figure(
|
#figure(
|
||||||
image("glacier.jpg", width: 70%),
|
image("glacier.jpg", width: 70%),
|
||||||
caption: [
|
caption: box[
|
||||||
_Glaciers_ form an important part
|
_Glaciers_ form an important part
|
||||||
of the earth's climate system.
|
of the earth's climate system.
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Be careful** about putting the square brackets by themselves on separate
|
||||||
|
lines. This will introduce leading and trailing space around inline text inside
|
||||||
|
the brackets, that is hard to notice. Below are several caption examples: one
|
||||||
|
with extra undesired space, and 3 correct ones.
|
||||||
|
|
||||||
|
```example
|
||||||
|
#show rect: none
|
||||||
|
#figure(rect(), caption: [
|
||||||
|
Caption text
|
||||||
|
])
|
||||||
|
#figure(rect(), caption: box[
|
||||||
|
Caption text
|
||||||
|
])
|
||||||
|
#figure(rect(), caption: [Caption
|
||||||
|
text]) // Many spaces in markup counts as one.
|
||||||
|
#figure(rect(), caption: "Caption text") // Spaces in strings are displayed verbatim.
|
||||||
|
```
|
||||||
|
|
||||||
You continue to write your report and now want to reference the figure. To do
|
You continue to write your report and now want to reference the figure. To do
|
||||||
that, first attach a label to figure. A label uniquely identifies an element in
|
that, first attach a label to figure. A label uniquely identifies an element in
|
||||||
your document. Add one after the figure by enclosing some name in angle
|
your document. Add one after the figure by enclosing some name in angle
|
||||||
|
@ -110,8 +110,8 @@ font. For the purposes of the example, we'll also set another page size.
|
|||||||
margin: (x: 1.8cm, y: 1.5cm),
|
margin: (x: 1.8cm, y: 1.5cm),
|
||||||
)
|
)
|
||||||
#set text(
|
#set text(
|
||||||
|
size: 10pt,
|
||||||
font: "New Computer Modern",
|
font: "New Computer Modern",
|
||||||
size: 10pt
|
|
||||||
)
|
)
|
||||||
#set par(
|
#set par(
|
||||||
justify: true,
|
justify: true,
|
||||||
@ -235,19 +235,27 @@ Instead, you could maybe
|
|||||||
[define a custom function]($function/#defining-functions) that always yields the
|
[define a custom function]($function/#defining-functions) that always yields the
|
||||||
logo with its image. However, there is an even easier way:
|
logo with its image. However, there is an even easier way:
|
||||||
|
|
||||||
With show rules, you can redefine how Typst displays certain elements. You
|
With show rules, you can redefine how Typst displays certain elements. You can
|
||||||
specify which elements Typst should show differently and how they should look.
|
specify which elements Typst should show differently and how they should look.
|
||||||
Show rules can be applied to instances of text, many functions, and even the
|
Show rules can be applied to instances of text, many functions, and even the
|
||||||
whole document.
|
whole document.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#show "ArtosFlow": name => box[
|
// #show "ArtosFlow": name => {
|
||||||
#box(image(
|
// let logo = box(image(
|
||||||
|
// "logo.svg",
|
||||||
|
// height: 0.7em,
|
||||||
|
// ))
|
||||||
|
// [#logo #name]
|
||||||
|
// }
|
||||||
|
#show "ArtosFlow": name => {
|
||||||
|
box(image(
|
||||||
"logo.svg",
|
"logo.svg",
|
||||||
height: 0.7em,
|
height: 0.7em,
|
||||||
))
|
))
|
||||||
#name
|
" "
|
||||||
]
|
name
|
||||||
|
}
|
||||||
|
|
||||||
This report is embedded in the
|
This report is embedded in the
|
||||||
ArtosFlow project. ArtosFlow is a
|
ArtosFlow project. ArtosFlow is a
|
||||||
@ -256,18 +264,18 @@ project of the Artos Institute.
|
|||||||
|
|
||||||
There is a lot of new syntax in this example: We write the `{show}` keyword,
|
There is a lot of new syntax in this example: We write the `{show}` keyword,
|
||||||
followed by a string of text we want to show differently and a colon. Then, we
|
followed by a string of text we want to show differently and a colon. Then, we
|
||||||
write a function that takes the content that shall be shown as an argument.
|
write a function that takes the content as an argument that shall be shown.
|
||||||
Here, we called that argument `name`. We can now use the `name` variable in the
|
Here, we called that argument `name`. We can now use the `name` variable in
|
||||||
function's body to print the ArtosFlow name. Our show rule adds the logo image
|
the function's body to display the ArtosFlow name. Our show rule adds the logo
|
||||||
in front of the name and puts the result into a box to prevent linebreaks from
|
image to the left of the name and inserts a single space between the two.
|
||||||
occurring between logo and name. The image is also put inside of a box, so that
|
The image is put inside of a `box`, so that it does not appear in its own
|
||||||
it does not appear in its own paragraph.
|
paragraph, because `image` is a block-level element.
|
||||||
|
|
||||||
The calls to the first box function and the image function did not require a
|
<!-- The calls to the first box function and the image function did not require a -->
|
||||||
leading `#` because they were not embedded directly in markup. When Typst
|
<!-- leading `#` because they were not embedded directly in markup. When Typst -->
|
||||||
expects code instead of markup, the leading `#` is not needed to access
|
<!-- expects code instead of markup, the leading `#` is not needed to access -->
|
||||||
functions, keywords, and variables. This can be observed in parameter lists,
|
<!-- functions, keywords, and variables. This can be observed in parameter lists, -->
|
||||||
function definitions, and [code blocks]($scripting).
|
<!-- function definitions, and [code blocks]($scripting). -->
|
||||||
|
|
||||||
## Review
|
## Review
|
||||||
You now know how to apply basic formatting to your Typst documents. You learned
|
You now know how to apply basic formatting to your Typst documents. You learned
|
||||||
|
@ -6,7 +6,7 @@ description: Typst's tutorial.
|
|||||||
In the previous two chapters of this tutorial, you have learned how to write a
|
In the previous two chapters of this tutorial, you have learned how to write a
|
||||||
document in Typst and how to change its formatting. The report you wrote
|
document in Typst and how to change its formatting. The report you wrote
|
||||||
throughout the last two chapters got a straight A and your supervisor wants to
|
throughout the last two chapters got a straight A and your supervisor wants to
|
||||||
base a conference paper on it! The report will of course have to comply with the
|
base a conference paper on it! The paper will of course have to comply with the
|
||||||
conference's style guide. Let's see how we can achieve that.
|
conference's style guide. Let's see how we can achieve that.
|
||||||
|
|
||||||
Before we start, let's create a team, invite your supervisor and add them to the
|
Before we start, let's create a team, invite your supervisor and add them to the
|
||||||
@ -30,12 +30,12 @@ to find other users and try teams with them!
|
|||||||
The layout guidelines are available on the conference website. Let's take a look
|
The layout guidelines are available on the conference website. Let's take a look
|
||||||
at them:
|
at them:
|
||||||
|
|
||||||
- The font should be an 11pt serif font
|
- The font should be an 11 pt serif font
|
||||||
- The title should be in 17pt and bold
|
- The title should be in 17 pt and bold
|
||||||
- The paper contains a single-column abstract and two-column main text
|
- The paper contains a single-column abstract and two-column main text
|
||||||
- The abstract should be centered
|
- The abstract should be centered
|
||||||
- The main text should be justified
|
- The main text should be justified
|
||||||
- First level section headings should be 13pt, centered, and rendered in small
|
- First level section headings should be 13 pt, centered, and rendered in small
|
||||||
capitals
|
capitals
|
||||||
- Second level headings are run-ins, italicized and have the same size as the
|
- Second level headings are run-ins, italicized and have the same size as the
|
||||||
body text
|
body text
|
||||||
@ -51,7 +51,6 @@ Let's start by writing some set rules for the document.
|
|||||||
|
|
||||||
```example
|
```example
|
||||||
#set page(
|
#set page(
|
||||||
>>> margin: auto,
|
|
||||||
paper: "us-letter",
|
paper: "us-letter",
|
||||||
header: align(right)[
|
header: align(right)[
|
||||||
A fluid dynamic model for
|
A fluid dynamic model for
|
||||||
@ -61,8 +60,8 @@ Let's start by writing some set rules for the document.
|
|||||||
)
|
)
|
||||||
#set par(justify: true)
|
#set par(justify: true)
|
||||||
#set text(
|
#set text(
|
||||||
font: "Libertinus Serif",
|
|
||||||
size: 11pt,
|
size: 11pt,
|
||||||
|
font: "Libertinus Serif",
|
||||||
)
|
)
|
||||||
|
|
||||||
#lorem(600)
|
#lorem(600)
|
||||||
@ -72,12 +71,12 @@ You are already familiar with most of what is going on here. We set the text
|
|||||||
size to `{11pt}` and the font to Libertinus Serif. We also enable paragraph
|
size to `{11pt}` and the font to Libertinus Serif. We also enable paragraph
|
||||||
justification and set the page size to US letter.
|
justification and set the page size to US letter.
|
||||||
|
|
||||||
The `header` argument is new: With it, we can provide content to fill the top
|
The `header` field is new: with it, we can provide content to fill the top
|
||||||
margin of every page. In the header, we specify our paper's title as requested
|
margin of every page. In the header, we specify our paper's title as requested
|
||||||
by the conference style guide. We use the `align` function to align the text to
|
by the conference style guide. We use the `align` function to align the text to
|
||||||
the right.
|
the right.
|
||||||
|
|
||||||
Last but not least is the `numbering` argument. Here, we can provide a
|
Last but not least is the `numbering` field. Here, we can provide a
|
||||||
[numbering pattern]($numbering) that defines how to number the pages. By
|
[numbering pattern]($numbering) that defines how to number the pages. By
|
||||||
setting it to `{"1"}`, Typst only displays the bare page number. Setting it to
|
setting it to `{"1"}`, Typst only displays the bare page number. Setting it to
|
||||||
`{"(1/1)"}` would have displayed the current page and total number of pages
|
`{"(1/1)"}` would have displayed the current page and total number of pages
|
||||||
@ -89,35 +88,65 @@ Now, let's add a title and an abstract. We'll start with the title. We center
|
|||||||
align it and increase its font weight by enclosing it in `[*stars*]`.
|
align it and increase its font weight by enclosing it in `[*stars*]`.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
>>> #set page(width: 300pt, margin: 30pt)
|
>>> #set page(
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
>>> // paper: "us-letter",
|
||||||
#align(center, text(17pt)[
|
>>> width: 300pt,
|
||||||
|
>>> margin: 30pt,
|
||||||
|
>>> header: align(right)[
|
||||||
|
>>> A fluid dynamic model for
|
||||||
|
>>> glacier flow
|
||||||
|
>>> ],
|
||||||
|
>>> // numbering: "1",
|
||||||
|
>>> )
|
||||||
|
>>> #set par(justify: true)
|
||||||
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
|
<<< ...
|
||||||
|
#align(center, block(text(
|
||||||
|
17pt,
|
||||||
|
hyphenate: false
|
||||||
|
)[
|
||||||
*A fluid dynamic model
|
*A fluid dynamic model
|
||||||
for glacier flow*
|
for glacier flow*
|
||||||
])
|
]))
|
||||||
```
|
```
|
||||||
|
|
||||||
This looks right. We used the `text` function to override the previous text
|
This looks right. We used the `text` function to override the previous text
|
||||||
set rule locally, increasing the size to 17pt for the function's argument. Let's
|
set rule locally, increasing the size to 17 pt.
|
||||||
also add the author list: Since we are writing this paper together with our
|
Add explanation about block+hyphenate, which is pretty convoluted.
|
||||||
supervisor, we'll add our own and their name.
|
Let's also add the author list: Since we are writing this paper together with
|
||||||
|
our supervisor, we'll add our own and their name.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
>>> #set page(width: 300pt, margin: 30pt)
|
>>> #set page(
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
>>> // paper: "us-letter",
|
||||||
|
>>> width: 300pt,
|
||||||
|
>>> margin: 30pt,
|
||||||
|
>>> header: align(right)[
|
||||||
|
>>> A fluid dynamic model for
|
||||||
|
>>> glacier flow
|
||||||
|
>>> ],
|
||||||
|
>>> // numbering: "1",
|
||||||
|
>>> )
|
||||||
|
>>> #set par(justify: true)
|
||||||
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
>>>
|
>>>
|
||||||
>>> #align(center, text(17pt)[
|
>>> #align(center, block(text(
|
||||||
|
>>> 17pt,
|
||||||
|
>>> hyphenate: false
|
||||||
|
>>> )[
|
||||||
>>> *A fluid dynamic model
|
>>> *A fluid dynamic model
|
||||||
>>> for glacier flow*
|
>>> for glacier flow*
|
||||||
>>> ])
|
>>> ]))
|
||||||
|
<<< ...
|
||||||
#grid(
|
#grid(
|
||||||
columns: (1fr, 1fr),
|
columns: (1fr, 1fr),
|
||||||
align(center)[
|
align: center,
|
||||||
|
[
|
||||||
Therese Tungsten \
|
Therese Tungsten \
|
||||||
Artos Institute \
|
Artos Institute \
|
||||||
#link("mailto:tung@artos.edu")
|
#link("mailto:tung@artos.edu")
|
||||||
],
|
],
|
||||||
align(center)[
|
[
|
||||||
Dr. John Doe \
|
Dr. John Doe \
|
||||||
Artos Institute \
|
Artos Institute \
|
||||||
#link("mailto:doe@artos.edu")
|
#link("mailto:doe@artos.edu")
|
||||||
@ -127,45 +156,49 @@ supervisor, we'll add our own and their name.
|
|||||||
|
|
||||||
The two author blocks are laid out next to each other. We use the [`grid`]
|
The two author blocks are laid out next to each other. We use the [`grid`]
|
||||||
function to create this layout. With a grid, we can control exactly how large
|
function to create this layout. With a grid, we can control exactly how large
|
||||||
each column is and which content goes into which cell. The `columns` argument
|
each column is and which content goes into which cell. The `columns` field
|
||||||
takes an array of [relative lengths]($relative) or [fractions]($fraction). In
|
takes the number of columns, or an array of [relative lengths]($relative) or
|
||||||
this case, we passed it two equal fractional sizes, telling it to split the
|
[fractions]($fraction). In this case, we passed it two equal fractional sizes,
|
||||||
available space into two equal columns. We then passed two content arguments to
|
telling it to split the available space into two equal columns. We then passed
|
||||||
the grid function. The first with our own details, and the second with our
|
two content arguments to the grid function --- the first with our own details,
|
||||||
supervisors'. We again use the `align` function to center the content within the
|
and the second with our supervisor's. With grid, we can avoid using `align` on
|
||||||
column. The grid takes an arbitrary number of content arguments specifying the
|
each cell content to center them, and instead use the `align` field to do this
|
||||||
cells. Rows are added automatically, but they can also be manually sized with
|
for all cells automatically. The grid takes an arbitrary number of content
|
||||||
the `rows` argument.
|
arguments specifying the cells. Rows are added automatically, but they can also
|
||||||
|
be manually sized with the `rows` field.
|
||||||
|
|
||||||
Now, let's add the abstract. Remember that the conference wants the abstract to
|
Now, let's add the abstract. Remember that the conference wants the abstract to
|
||||||
be set ragged and centered.
|
be set ragged and centered.
|
||||||
|
|
||||||
```example:0,0,612,317.5
|
```example:0,0,612,317.5
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
|
||||||
>>> #set par(justify: true)
|
|
||||||
>>> #set page(
|
>>> #set page(
|
||||||
>>> "us-letter",
|
>>> paper: "us-letter",
|
||||||
>>> margin: auto,
|
>>> header: align(right)[
|
||||||
>>> header: align(right + horizon)[
|
|
||||||
>>> A fluid dynamic model for
|
>>> A fluid dynamic model for
|
||||||
>>> glacier flow
|
>>> glacier flow
|
||||||
>>> ],
|
>>> ],
|
||||||
>>> numbering: "1",
|
>>> numbering: "1",
|
||||||
>>> )
|
>>> )
|
||||||
|
>>> #set par(justify: true)
|
||||||
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
>>>
|
>>>
|
||||||
>>> #align(center, text(17pt)[
|
>>> #align(center, block(text(
|
||||||
|
>>> 17pt,
|
||||||
|
>>> hyphenate: false
|
||||||
|
>>> )[
|
||||||
>>> *A fluid dynamic model
|
>>> *A fluid dynamic model
|
||||||
>>> for glacier flow*
|
>>> for glacier flow*
|
||||||
>>> ])
|
>>> ]))
|
||||||
>>>
|
>>>
|
||||||
>>> #grid(
|
>>> #grid(
|
||||||
>>> columns: (1fr, 1fr),
|
>>> columns: (1fr, 1fr),
|
||||||
>>> align(center)[
|
>>> align: center,
|
||||||
|
>>> [
|
||||||
>>> Therese Tungsten \
|
>>> Therese Tungsten \
|
||||||
>>> Artos Institute \
|
>>> Artos Institute \
|
||||||
>>> #link("mailto:tung@artos.edu")
|
>>> #link("mailto:tung@artos.edu")
|
||||||
>>> ],
|
>>> ],
|
||||||
>>> align(center)[
|
>>> [
|
||||||
>>> Dr. John Doe \
|
>>> Dr. John Doe \
|
||||||
>>> Artos Institute \
|
>>> Artos Institute \
|
||||||
>>> #link("mailto:doe@artos.edu")
|
>>> #link("mailto:doe@artos.edu")
|
||||||
@ -198,35 +231,33 @@ keyword:
|
|||||||
for glacier flow
|
for glacier flow
|
||||||
]
|
]
|
||||||
|
|
||||||
<<< ...
|
|
||||||
|
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
|
||||||
>>> #set par(justify: true)
|
|
||||||
#set page(
|
#set page(
|
||||||
>>> "us-letter",
|
>>> paper: "us-letter",
|
||||||
>>> margin: auto,
|
<<< ...
|
||||||
header: align(
|
header: align(right, title),
|
||||||
right + horizon,
|
|
||||||
title
|
|
||||||
),
|
|
||||||
<<< ...
|
<<< ...
|
||||||
>>> numbering: "1",
|
>>> numbering: "1",
|
||||||
)
|
)
|
||||||
|
>>> #set par(justify: true)
|
||||||
#align(center, text(17pt)[
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
*#title*
|
|
||||||
])
|
|
||||||
|
|
||||||
<<< ...
|
<<< ...
|
||||||
|
|
||||||
|
#align(center, block(text(
|
||||||
|
17pt,
|
||||||
|
hyphenate: false,
|
||||||
|
strong(title),
|
||||||
|
)))
|
||||||
|
|
||||||
|
<<< ...
|
||||||
>>> #grid(
|
>>> #grid(
|
||||||
>>> columns: (1fr, 1fr),
|
>>> columns: (1fr, 1fr),
|
||||||
>>> align(center)[
|
>>> align: center,
|
||||||
|
>>> [
|
||||||
>>> Therese Tungsten \
|
>>> Therese Tungsten \
|
||||||
>>> Artos Institute \
|
>>> Artos Institute \
|
||||||
>>> #link("mailto:tung@artos.edu")
|
>>> #link("mailto:tung@artos.edu")
|
||||||
>>> ],
|
>>> ],
|
||||||
>>> align(center)[
|
>>> [
|
||||||
>>> Dr. John Doe \
|
>>> Dr. John Doe \
|
||||||
>>> Artos Institute \
|
>>> Artos Institute \
|
||||||
>>> #link("mailto:doe@artos.edu")
|
>>> #link("mailto:doe@artos.edu")
|
||||||
@ -247,55 +278,48 @@ and also within markup (prefixed by `#`, like functions). This way, if we decide
|
|||||||
on another title, we can easily change it in one place.
|
on another title, we can easily change it in one place.
|
||||||
|
|
||||||
## Adding columns and headings { #columns-and-headings }
|
## Adding columns and headings { #columns-and-headings }
|
||||||
The paper above unfortunately looks like a wall of lead. To fix that, let's add
|
The paper above unfortunately looks like a wall of lead(?). To fix that, let's add
|
||||||
some headings and switch our paper to a two-column layout. Fortunately, that's
|
some headings and switch our paper to a two-column layout. Fortunately, that's
|
||||||
easy to do: We just need to amend our `page` set rule with the `columns`
|
easy to do: we just need to amend our `page` set rule with the `columns` field.
|
||||||
argument.
|
|
||||||
|
|
||||||
By adding `{columns: 2}` to the argument list, we have wrapped the whole
|
By adding `{columns: 2}` to the argument list, we have wrapped the whole
|
||||||
document in two columns. However, that would also affect the title and authors
|
document in two columns. However, that would also affect the title and authors
|
||||||
overview. To keep them spanning the whole page, we can wrap them in a function
|
overview. To keep them spanning the whole page, we can wrap them in a function
|
||||||
call to [`{place}`]($place). Place expects an alignment and the content it
|
call to [`{place}`]($place). The `place` expects an alignment and the content it
|
||||||
should place as positional arguments. Using the named `{scope}` argument, we can
|
should place as positional arguments. Using the named `scope` field, we can
|
||||||
decide if the items should be placed relative to the current column or its
|
decide if the items should be placed relative to the current column or its
|
||||||
parent (the page). There is one more thing to configure: If no other arguments
|
parent (the page). There is one more thing to configure: If no other arguments
|
||||||
are provided, `{place}` takes its content out of the flow of the document and
|
are provided, `place` takes its content out of the flow of the document and
|
||||||
positions it over the other content without affecting the layout of other
|
positions it over the other content without affecting the layout of other
|
||||||
content in its container:
|
content in its container:
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#place(
|
#place(top + center, rect())
|
||||||
top + center,
|
|
||||||
rect(fill: black),
|
|
||||||
)
|
|
||||||
#lorem(30)
|
#lorem(30)
|
||||||
```
|
```
|
||||||
|
|
||||||
If we hadn't used `{place}` here, the square would be in its own line, but here
|
If we hadn't used `place` here, the rectangle would be in its own line, but
|
||||||
it overlaps the few lines of text following it. Likewise, that text acts like as
|
here it overlaps the few lines of text following it. Likewise, that text acts
|
||||||
if there was no square. To change this behavior, we can pass the argument
|
like as if there was no rectangle. To change this behavior, we can pass the
|
||||||
`{float: true}` to ensure that the space taken up by the placed item at the top
|
argument `{float: true}` to ensure that the space taken up by the placed item
|
||||||
or bottom of the page is not occupied by any other content.
|
at the top or bottom of the page is not occupied by any other content.
|
||||||
|
|
||||||
```example:single
|
```example:single
|
||||||
>>> #let title = [
|
>>> #let title = [
|
||||||
>>> A fluid dynamic model
|
>>> A fluid dynamic model
|
||||||
>>> for glacier flow
|
>>> for glacier flow
|
||||||
>>> ]
|
>>> ]
|
||||||
>>>
|
<<< ...
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
|
||||||
>>> #set par(justify: true)
|
|
||||||
>>>
|
|
||||||
#set page(
|
#set page(
|
||||||
>>> margin: auto,
|
|
||||||
paper: "us-letter",
|
paper: "us-letter",
|
||||||
header: align(
|
header: align(right, title),
|
||||||
right + horizon,
|
|
||||||
title
|
|
||||||
),
|
|
||||||
numbering: "1",
|
numbering: "1",
|
||||||
columns: 2,
|
columns: 2,
|
||||||
)
|
)
|
||||||
|
>>> #set par(justify: true)
|
||||||
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
|
<<< ...
|
||||||
|
|
||||||
#place(
|
#place(
|
||||||
top + center,
|
top + center,
|
||||||
@ -303,11 +327,7 @@ or bottom of the page is not occupied by any other content.
|
|||||||
scope: "parent",
|
scope: "parent",
|
||||||
clearance: 2em,
|
clearance: 2em,
|
||||||
)[
|
)[
|
||||||
>>> #text(
|
>>> #block(text(17pt, hyphenate: false, strong(title)))
|
||||||
>>> 17pt,
|
|
||||||
>>> weight: "bold",
|
|
||||||
>>> title,
|
|
||||||
>>> )
|
|
||||||
>>>
|
>>>
|
||||||
>>> #grid(
|
>>> #grid(
|
||||||
>>> columns: (1fr, 1fr),
|
>>> columns: (1fr, 1fr),
|
||||||
@ -324,10 +344,9 @@ or bottom of the page is not occupied by any other content.
|
|||||||
>>> )
|
>>> )
|
||||||
<<< ...
|
<<< ...
|
||||||
|
|
||||||
#par(justify: false)[
|
#set par(justify: false) // Put it above to remove hyphenate?
|
||||||
*Abstract* \
|
*Abstract* \
|
||||||
#lorem(80)
|
#lorem(80)
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
|
||||||
= Introduction
|
= Introduction
|
||||||
@ -337,14 +356,14 @@ or bottom of the page is not occupied by any other content.
|
|||||||
#lorem(200)
|
#lorem(200)
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, we also used the `clearance` argument of the `{place}` function
|
In this example, we also used the `clearance` argument of the `place` function
|
||||||
to provide the space between it and the body instead of using the [`{v}`]($v)
|
to provide the space between it and the body instead of using the [`v`]
|
||||||
function. We can also remove the explicit `{align(center, ..)}` calls around the
|
function. We can also remove the explicit `{align(center, ..)}` calls around the
|
||||||
various parts since they inherit the center alignment from the placement.
|
various parts since they inherit the center alignment from the placement.
|
||||||
|
|
||||||
Now there is only one thing left to do: Style our headings. We need to make them
|
Now there is only one thing left to do: style our headings. We need to make them
|
||||||
centered and use small capitals. Because the `heading` function does not offer
|
centered and use small capitals. For centering we can use a show-set rule, but
|
||||||
a way to set any of that, we need to write our own heading show rule.
|
to use small capitals we need to write our own heading show rule.
|
||||||
|
|
||||||
```example:50,250,265,270
|
```example:50,250,265,270
|
||||||
>>> #let title = [
|
>>> #let title = [
|
||||||
@ -352,37 +371,27 @@ a way to set any of that, we need to write our own heading show rule.
|
|||||||
>>> for glacier flow
|
>>> for glacier flow
|
||||||
>>> ]
|
>>> ]
|
||||||
>>>
|
>>>
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
|
||||||
>>> #set par(justify: true)
|
|
||||||
>>> #set page(
|
>>> #set page(
|
||||||
>>> "us-letter",
|
>>> paper: "us-letter",
|
||||||
>>> margin: auto,
|
>>> header: align(right, title),
|
||||||
>>> header: align(
|
|
||||||
>>> right + horizon,
|
|
||||||
>>> title
|
|
||||||
>>> ),
|
|
||||||
>>> numbering: "1",
|
>>> numbering: "1",
|
||||||
>>> columns: 2,
|
>>> columns: 2,
|
||||||
>>> )
|
>>> )
|
||||||
#show heading: it => [
|
>>> #set par(justify: true)
|
||||||
#set align(center)
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
#set text(13pt, weight: "regular")
|
<<< ...
|
||||||
#block(smallcaps(it.body))
|
#show heading: set align(center)
|
||||||
]
|
#show heading: set text(13pt, weight: "regular")
|
||||||
|
#show heading: it => block(smallcaps(it.body))
|
||||||
|
|
||||||
<<< ...
|
<<< ...
|
||||||
>>>
|
|
||||||
>>> #place(
|
>>> #place(
|
||||||
>>> top + center,
|
>>> top + center,
|
||||||
>>> float: true,
|
>>> float: true,
|
||||||
>>> scope: "parent",
|
>>> scope: "parent",
|
||||||
>>> clearance: 2em,
|
>>> clearance: 2em,
|
||||||
>>> )[
|
>>> )[
|
||||||
>>> #text(
|
>>> #block(text(17pt, hyphenate: false, strong(title)))
|
||||||
>>> 17pt,
|
|
||||||
>>> weight: "bold",
|
|
||||||
>>> title,
|
|
||||||
>>> )
|
|
||||||
>>>
|
>>>
|
||||||
>>> #grid(
|
>>> #grid(
|
||||||
>>> columns: (1fr, 1fr),
|
>>> columns: (1fr, 1fr),
|
||||||
@ -398,11 +407,10 @@ a way to set any of that, we need to write our own heading show rule.
|
|||||||
>>> ]
|
>>> ]
|
||||||
>>> )
|
>>> )
|
||||||
>>>
|
>>>
|
||||||
>>> #par(justify: false)[
|
>>> #set par(justify: false)
|
||||||
>>> *Abstract* \
|
>>> *Abstract* \
|
||||||
>>> #lorem(80)
|
>>> #lorem(80)
|
||||||
>>> ]
|
>>> ]
|
||||||
>>> ]
|
|
||||||
>>>
|
>>>
|
||||||
>>> = Introduction
|
>>> = Introduction
|
||||||
>>> #lorem(35)
|
>>> #lorem(35)
|
||||||
@ -411,19 +419,26 @@ a way to set any of that, we need to write our own heading show rule.
|
|||||||
>>> #lorem(45)
|
>>> #lorem(45)
|
||||||
```
|
```
|
||||||
|
|
||||||
This looks great! We used a show rule that applies to all headings. We give it a
|
This looks great! We used a few rules that apply to all headings. First, we
|
||||||
function that gets passed the heading as a parameter. That parameter can be used
|
made headings centered, then we set font size to 13 pt and removed default
|
||||||
as content but it also has some fields like `title`, `numbers`, and `level` from
|
heading boldness by setting `weight` to `{"regular"}`. Lastly, there is a show
|
||||||
which we can compose a custom look. Here, we are center-aligning, setting the
|
rule with a closure, i.e., a callback function. We gave it a function that
|
||||||
font weight to `{"regular"}` because headings are bold by default, and use the
|
passes the heading as argument. That argument can be used as content but it
|
||||||
[`smallcaps`] function to render the heading's title in small capitals.
|
also has some fields like `title`, `numbers`, and `level`, from which we can
|
||||||
|
compose a custom look. Here, we use the [`smallcaps`] function to render the
|
||||||
|
heading's title in small capitals. Note that heading itself is wrapped in a
|
||||||
|
block by default, as it's a block-level element, and by using `{it.body}` we
|
||||||
|
are destroying the default structure of the heading. This means we strip away
|
||||||
|
not only the `block` "shell", but also any potential numbering and other
|
||||||
|
heading features. To restore it's semantic structure, we wrap
|
||||||
|
`{smallcaps(it.body)}` in a `block`. This way it will behave like usual.
|
||||||
|
|
||||||
The only remaining problem is that all headings look the same now. The
|
The only remaining problem is that all headings now look the same. The
|
||||||
"Motivation" and "Problem Statement" subsections ought to be italic run in
|
"Motivation" and "Problem Statement" subsections ought to be italic run-in
|
||||||
headers, but right now, they look indistinguishable from the section headings. We
|
headings, but right now, they look indistinguishable from the section headings.
|
||||||
can fix that by using a `where` selector on our set rule: This is a
|
We can fix that by using a `where` selector on our show rules: this is a
|
||||||
[method]($scripting/#methods) we can call on headings (and other
|
[method]($scripting/#methods) we can call on headings (and other elements) that
|
||||||
elements) that allows us to filter them by their level. We can use it to
|
allows us to filter them by their level (and other fields). We can use it to
|
||||||
differentiate between section and subsection headings:
|
differentiate between section and subsection headings:
|
||||||
|
|
||||||
```example:50,250,265,245
|
```example:50,250,265,245
|
||||||
@ -432,47 +447,31 @@ differentiate between section and subsection headings:
|
|||||||
>>> for glacier flow
|
>>> for glacier flow
|
||||||
>>> ]
|
>>> ]
|
||||||
>>>
|
>>>
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
|
||||||
>>> #set par(justify: true)
|
|
||||||
>>> #set page(
|
>>> #set page(
|
||||||
>>> "us-letter",
|
>>> paper: "us-letter",
|
||||||
>>> margin: auto,
|
>>> header: align(right, title),
|
||||||
>>> header: align(
|
|
||||||
>>> right + horizon,
|
|
||||||
>>> title
|
|
||||||
>>> ),
|
|
||||||
>>> numbering: "1",
|
>>> numbering: "1",
|
||||||
>>> columns: 2,
|
>>> columns: 2,
|
||||||
>>> )
|
>>> )
|
||||||
>>>
|
>>> #set par(justify: true)
|
||||||
#show heading.where(
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
level: 1
|
<<< ...
|
||||||
): it => block(width: 100%)[
|
|
||||||
#set align(center)
|
|
||||||
#set text(13pt, weight: "regular")
|
|
||||||
#smallcaps(it.body)
|
|
||||||
]
|
|
||||||
|
|
||||||
#show heading.where(
|
#show heading.where(level: 1): set align(center)
|
||||||
level: 2
|
#show heading.where(level: 1): set text(13pt, weight: "regular")
|
||||||
): it => text(
|
#show heading.where(level: 1): it => block(smallcaps(it.body))
|
||||||
size: 11pt,
|
|
||||||
weight: "regular",
|
#show heading.where(level: 2): set text(11pt, weight: "regular", style: "italic")
|
||||||
style: "italic",
|
#show heading.where(level: 2): it => [#it.body.]
|
||||||
it.body + [.],
|
|
||||||
)
|
<<< ...
|
||||||
>>>
|
|
||||||
>>> #place(
|
>>> #place(
|
||||||
>>> top + center,
|
>>> top + center,
|
||||||
>>> float: true,
|
>>> float: true,
|
||||||
>>> scope: "parent",
|
>>> scope: "parent",
|
||||||
>>> clearance: 2em,
|
>>> clearance: 2em,
|
||||||
>>> )[
|
>>> )[
|
||||||
>>> #text(
|
>>> #block(text(17pt, hyphenate: false, strong(title)))
|
||||||
>>> 17pt,
|
|
||||||
>>> weight: "bold",
|
|
||||||
>>> title,
|
|
||||||
>>> )
|
|
||||||
>>>
|
>>>
|
||||||
>>> #grid(
|
>>> #grid(
|
||||||
>>> columns: (1fr, 1fr),
|
>>> columns: (1fr, 1fr),
|
||||||
@ -488,11 +487,10 @@ differentiate between section and subsection headings:
|
|||||||
>>> ]
|
>>> ]
|
||||||
>>> )
|
>>> )
|
||||||
>>>
|
>>>
|
||||||
>>> #par(justify: false)[
|
>>> #set par(justify: false)
|
||||||
>>> *Abstract* \
|
>>> *Abstract* \
|
||||||
>>> #lorem(80)
|
>>> #lorem(80)
|
||||||
>>> ]
|
>>> ]
|
||||||
>>> ]
|
|
||||||
>>>
|
>>>
|
||||||
>>> = Introduction
|
>>> = Introduction
|
||||||
>>> #lorem(35)
|
>>> #lorem(35)
|
||||||
@ -501,23 +499,26 @@ differentiate between section and subsection headings:
|
|||||||
>>> #lorem(45)
|
>>> #lorem(45)
|
||||||
```
|
```
|
||||||
|
|
||||||
This looks great! We wrote two show rules that each selectively apply to the
|
Excellent! We wrote several rules that selectively apply to the first and second
|
||||||
first and second level headings. We used a `where` selector to filter the
|
level headings. We used a `where` selector to filter the headings by their
|
||||||
headings by their level. We then rendered the subsection headings as run-ins. We
|
level. We then rendered the subsection headings as run-ins. We also
|
||||||
also automatically add a period to the end of the subsection headings.
|
automatically added a period to the end of the subsection headings. This time
|
||||||
|
we did not wrap result in a `block`, because we need the heading to be inline
|
||||||
|
with the following text.
|
||||||
|
|
||||||
Let's review the conference's style guide:
|
Let's review the conference's style guide:
|
||||||
- The font should be an 11pt serif font ✓
|
- The font should be an 11 pt serif font ✓
|
||||||
- The title should be in 17pt and bold ✓
|
- The title should be in 17 pt and bold ✓
|
||||||
- The paper contains a single-column abstract and two-column main text ✓
|
- The paper contains a single-column abstract and two-column main text ✓
|
||||||
- The abstract should be centered ✓
|
- The abstract should be centered ✓
|
||||||
- The main text should be justified ✓
|
- The main text should be justified ✓
|
||||||
- First level section headings should be centered, rendered in small caps and in
|
- First level section headings should be centered, rendered in small caps and in
|
||||||
13pt ✓
|
13 pt ✓
|
||||||
- Second level headings are run-ins, italicized and have the same size as the
|
- Second level headings are run-ins, italicized and have the same size as the
|
||||||
body text ✓
|
body text ✓
|
||||||
- Finally, the pages should be US letter sized, numbered in the center and the
|
- Finally, the pages should be US letter sized, numbered in the center of the
|
||||||
top right corner of each page should contain the title of the paper ✓
|
footer and the top right corner of each page should contain the title of the
|
||||||
|
paper ✓
|
||||||
|
|
||||||
We are now in compliance with all of these styles and can submit the paper to
|
We are now in compliance with all of these styles and can submit the paper to
|
||||||
the conference! The finished paper looks like this:
|
the conference! The finished paper looks like this:
|
||||||
@ -528,6 +529,62 @@ the conference! The finished paper looks like this:
|
|||||||
style="box-shadow: 0 4px 12px rgb(89 85 101 / 20%); width: 500px; max-width: 100%; display: block; margin: 24px auto;"
|
style="box-shadow: 0 4px 12px rgb(89 85 101 / 20%); width: 500px; max-width: 100%; display: block; margin: 24px auto;"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
Here is a full listing of the finished paper:
|
||||||
|
|
||||||
|
```example
|
||||||
|
#let title = [A fluid dynamic model for glacier flow]
|
||||||
|
|
||||||
|
#set page(
|
||||||
|
paper: "us-letter",
|
||||||
|
header: align(right, title),
|
||||||
|
numbering: "1",
|
||||||
|
columns: 2,
|
||||||
|
)
|
||||||
|
#set par(justify: true)
|
||||||
|
#set text(11pt, font: "Libertinus Serif")
|
||||||
|
|
||||||
|
#show heading.where(level: 1): set align(center)
|
||||||
|
#show heading.where(level: 1): set text(13pt, weight: "regular")
|
||||||
|
#show heading.where(level: 1): it => block(smallcaps(it.body))
|
||||||
|
|
||||||
|
#show heading.where(level: 2): set text(11pt, weight: "regular", style: "italic")
|
||||||
|
#show heading.where(level: 2): it => [#it.body.]
|
||||||
|
|
||||||
|
#place(top + center, float: true, scope: "parent", clearance: 2em)[
|
||||||
|
#block(text(17pt, hyphenate: false, strong(title)))
|
||||||
|
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, 1fr),
|
||||||
|
[
|
||||||
|
Therese Tungsten \
|
||||||
|
Artos Institute \
|
||||||
|
#link("mailto:tung@artos.edu")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Dr. John Doe \
|
||||||
|
Artos Institute \
|
||||||
|
#link("mailto:doe@artos.edu")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
#set par(justify: false)
|
||||||
|
*Abstract* \
|
||||||
|
#lorem(80)
|
||||||
|
]
|
||||||
|
|
||||||
|
= Introduction
|
||||||
|
#lorem(90)
|
||||||
|
|
||||||
|
== Motivation
|
||||||
|
#lorem(140)
|
||||||
|
|
||||||
|
== Problem Statement
|
||||||
|
#lorem(50)
|
||||||
|
|
||||||
|
= Related Work
|
||||||
|
#lorem(200)
|
||||||
|
```
|
||||||
|
|
||||||
## Review
|
## Review
|
||||||
You have now learned how to create headers and footers, how to use functions and
|
You have now learned how to create headers and footers, how to use functions and
|
||||||
scopes to locally override styles, how to create more complex layouts with the
|
scopes to locally override styles, how to create more complex layouts with the
|
||||||
|
@ -25,7 +25,10 @@ You are #amazed[beautiful]!
|
|||||||
|
|
||||||
This function takes a single argument, `term`, and returns a content block with
|
This function takes a single argument, `term`, and returns a content block with
|
||||||
the `term` surrounded by sparkles. We also put the whole thing in a box so that
|
the `term` surrounded by sparkles. We also put the whole thing in a box so that
|
||||||
the term we are amazed by cannot be separated from its sparkles by a line break.
|
the term we are amazed by cannot be separated from its sparkles by a line
|
||||||
|
break. Alternatively, you can use a
|
||||||
|
[shorthand](https://typst.app/docs/reference/symbols/#shorthands)
|
||||||
|
for a no-break space and write `{[✨~#term~✨]}`.
|
||||||
|
|
||||||
Many functions that come with Typst have optional named parameters. Our
|
Many functions that come with Typst have optional named parameters. Our
|
||||||
functions can also have them. Let's add a parameter to our function that lets us
|
functions can also have them. Let's add a parameter to our function that lets us
|
||||||
@ -34,7 +37,7 @@ parameter isn't given.
|
|||||||
|
|
||||||
```example
|
```example
|
||||||
#let amazed(term, color: blue) = {
|
#let amazed(term, color: blue) = {
|
||||||
text(color, box[✨ #term ✨])
|
text(color)[✨~#term~✨]
|
||||||
}
|
}
|
||||||
|
|
||||||
You are #amazed[beautiful]!
|
You are #amazed[beautiful]!
|
||||||
@ -43,7 +46,7 @@ I am #amazed(color: purple)[amazed]!
|
|||||||
|
|
||||||
Templates now work by wrapping our whole document in a custom function like
|
Templates now work by wrapping our whole document in a custom function like
|
||||||
`amazed`. But wrapping a whole document in a giant function call would be
|
`amazed`. But wrapping a whole document in a giant function call would be
|
||||||
cumbersome! Instead, we can use an "everything" show rule to achieve the same
|
cumbersome! Instead, we can use an "global" show rule to achieve the same
|
||||||
with cleaner code. To write such a show rule, put a colon directly after the
|
with cleaner code. To write such a show rule, put a colon directly after the
|
||||||
show keyword and then provide a function. This function is given the rest of the
|
show keyword and then provide a function. This function is given the rest of the
|
||||||
document as a parameter. The function can then do anything with this content.
|
document as a parameter. The function can then do anything with this content.
|
||||||
@ -52,7 +55,7 @@ just pass it by name to the show rule. Let's try it:
|
|||||||
|
|
||||||
```example
|
```example
|
||||||
>>> #let amazed(term, color: blue) = {
|
>>> #let amazed(term, color: blue) = {
|
||||||
>>> text(color, box[✨ #term ✨])
|
>>> text(color)[✨~#term~✨]
|
||||||
>>> }
|
>>> }
|
||||||
#show: amazed
|
#show: amazed
|
||||||
I choose to focus on the good
|
I choose to focus on the good
|
||||||
@ -68,69 +71,56 @@ powerful.
|
|||||||
|
|
||||||
## Embedding set and show rules { #set-and-show-rules }
|
## Embedding set and show rules { #set-and-show-rules }
|
||||||
To apply some set and show rules to our template, we can use `set` and `show`
|
To apply some set and show rules to our template, we can use `set` and `show`
|
||||||
within a content block in our function and then insert the document into
|
within a code block in our function and then insert the document into
|
||||||
that content block.
|
that code block.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#let template(doc) = [
|
#let template(doc) = {
|
||||||
#set text(font: "Inria Serif")
|
set text(font: "Inria Serif")
|
||||||
#show "something cool": [Typst]
|
show "something cool": [Typst]
|
||||||
#doc
|
doc
|
||||||
]
|
}
|
||||||
|
|
||||||
#show: template
|
#show: template
|
||||||
I am learning something cool today.
|
I am learning something cool today.
|
||||||
It's going great so far!
|
It's going great so far!
|
||||||
```
|
```
|
||||||
|
|
||||||
Just like we already discovered in the previous chapter, set rules will apply to
|
Just like we already discovered in the previous chapter, set rules will apply
|
||||||
everything within their content block. Since the everything show rule passes our
|
to everything within their scope. Since the global show rule passes our whole
|
||||||
whole document to the `template` function, the text set rule and string show
|
document to the `template` function, the text set rule and string show rule in
|
||||||
rule in our template will apply to the whole document. Let's use this knowledge
|
our template will apply to the whole document.
|
||||||
to create a template that reproduces the body style of the paper we wrote in the
|
|
||||||
previous chapter.
|
We used a curly-braced code block instead of a content block. This way, we
|
||||||
|
don't need to prefix all set rules and function calls with a `#`. This also
|
||||||
|
removes the implicit spaces that are naturally introduced in the markup mode.
|
||||||
|
In exchange, we cannot write markup directly in the code block anymore.
|
||||||
|
|
||||||
|
Let's use this knowledge to create a template that reproduces the body style of
|
||||||
|
the paper we wrote in the previous chapter.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#let conf(title, doc) = {
|
#let conf(title, doc) = {
|
||||||
set page(
|
set page(
|
||||||
paper: "us-letter",
|
paper: "us-letter",
|
||||||
>>> margin: auto,
|
header: align(right, title),
|
||||||
header: align(
|
|
||||||
right + horizon,
|
|
||||||
title
|
|
||||||
),
|
|
||||||
columns: 2,
|
columns: 2,
|
||||||
<<< ...
|
<<< ...
|
||||||
)
|
)
|
||||||
set par(justify: true)
|
set par(justify: true)
|
||||||
set text(
|
set text(
|
||||||
|
11pt,
|
||||||
font: "Libertinus Serif",
|
font: "Libertinus Serif",
|
||||||
size: 11pt,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Heading show rules.
|
// Heading show rules.
|
||||||
<<< ...
|
<<< ...
|
||||||
>>> show heading.where(
|
>>> show heading.where(level: 1): set align(center)
|
||||||
>>> level: 1
|
>>> show heading.where(level: 1): set text(13pt, weight: "regular")
|
||||||
>>> ): it => block(
|
>>> show heading.where(level: 1): it => block(smallcaps(it.body))
|
||||||
>>> align(center,
|
>>>
|
||||||
>>> text(
|
>>> show heading.where(level: 2): set text(11pt, weight: "regular", style: "italic")
|
||||||
>>> 13pt,
|
>>> show heading.where(level: 2): it => [#it.body.]
|
||||||
>>> weight: "regular",
|
|
||||||
>>> smallcaps(it.body),
|
|
||||||
>>> )
|
|
||||||
>>> ),
|
|
||||||
>>> )
|
|
||||||
>>> show heading.where(
|
|
||||||
>>> level: 2
|
|
||||||
>>> ): it => box(
|
|
||||||
>>> text(
|
|
||||||
>>> 11pt,
|
|
||||||
>>> weight: "regular",
|
|
||||||
>>> style: "italic",
|
|
||||||
>>> it.body + [.],
|
|
||||||
>>> )
|
|
||||||
>>> )
|
|
||||||
|
|
||||||
doc
|
doc
|
||||||
}
|
}
|
||||||
@ -154,24 +144,17 @@ previous chapter.
|
|||||||
>>> #lorem(200)
|
>>> #lorem(200)
|
||||||
```
|
```
|
||||||
|
|
||||||
We copy-pasted most of that code from the previous chapter. The two differences
|
We copied most of that code from the previous chapter. However, now we wrapped
|
||||||
are this:
|
everything in the function `conf` using a global show rule. The function applies
|
||||||
|
a few set and show rules and echoes the content it has been passed at the end.
|
||||||
|
|
||||||
1. We wrapped everything in the function `conf` using an everything show rule.
|
Also note where the title comes from: we previously had it inside of a variable.
|
||||||
The function applies a few set and show rules and echoes the content it has
|
|
||||||
been passed at the end.
|
|
||||||
|
|
||||||
2. Moreover, we used a curly-braced code block instead of a content block. This
|
|
||||||
way, we don't need to prefix all set rules and function calls with a `#`. In
|
|
||||||
exchange, we cannot write markup directly in the code block anymore.
|
|
||||||
|
|
||||||
Also note where the title comes from: We previously had it inside of a variable.
|
|
||||||
Now, we are receiving it as the first parameter of the template function. To do
|
Now, we are receiving it as the first parameter of the template function. To do
|
||||||
so, we passed a closure (that's a function without a name that is used right
|
so, we passed a closure (that's a function without a name that is used right
|
||||||
away) to the everything show rule. We did that because the `conf` function
|
away) to the global show rule. We did that because the `conf` function expects
|
||||||
expects two positional arguments, the title and the body, but the show rule will
|
two positional arguments: the title and the body, but the show rule will only
|
||||||
only pass the body. Therefore, we add a new function definition that allows us
|
pass the body. Therefore, we add a new function definition that allows us to set
|
||||||
to set a paper title and use the single parameter from the show rule.
|
a paper title and use the single parameter from the show rule.
|
||||||
|
|
||||||
## Templates with named arguments { #named-arguments }
|
## Templates with named arguments { #named-arguments }
|
||||||
Our paper in the previous chapter had a title and an author list. Let's add
|
Our paper in the previous chapter had a title and an author list. Let's add
|
||||||
@ -230,6 +213,9 @@ multiple arguments for the grid. We can do that by using the
|
|||||||
[`spread` operator]($arguments). It takes an array and applies each of its items
|
[`spread` operator]($arguments). It takes an array and applies each of its items
|
||||||
as a separate argument to the function.
|
as a separate argument to the function.
|
||||||
|
|
||||||
|
Let's also include some PDF metadata. We can achieve this by using
|
||||||
|
the [`document`] function and specifying fields such as `title` and `author`.
|
||||||
|
|
||||||
The resulting template function looks like this:
|
The resulting template function looks like this:
|
||||||
|
|
||||||
```typ
|
```typ
|
||||||
@ -239,12 +225,15 @@ The resulting template function looks like this:
|
|||||||
abstract: [],
|
abstract: [],
|
||||||
doc,
|
doc,
|
||||||
) = {
|
) = {
|
||||||
|
set document(title: title, author: authors.map(author => author.name))
|
||||||
// Set and show rules from before.
|
// Set and show rules from before.
|
||||||
>>> #set page(columns: 2)
|
|
||||||
<<< ...
|
<<< ...
|
||||||
|
|
||||||
|
{
|
||||||
set align(center)
|
set align(center)
|
||||||
text(17pt, title)
|
set par(justify: false)
|
||||||
|
|
||||||
|
block(text(17pt, strong(title)))
|
||||||
|
|
||||||
let count = authors.len()
|
let count = authors.len()
|
||||||
let ncols = calc.min(count, 3)
|
let ncols = calc.min(count, 3)
|
||||||
@ -258,12 +247,11 @@ The resulting template function looks like this:
|
|||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
par(justify: false)[
|
strong[Abstract]
|
||||||
*Abstract* \
|
linebreak()
|
||||||
#abstract
|
abstract
|
||||||
]
|
}
|
||||||
|
|
||||||
set align(left)
|
|
||||||
doc
|
doc
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -291,49 +279,26 @@ call.
|
|||||||
>>> abstract: [],
|
>>> abstract: [],
|
||||||
>>> doc,
|
>>> doc,
|
||||||
>>> ) = {
|
>>> ) = {
|
||||||
>>> set text(font: "Libertinus Serif", 11pt)
|
>>> set document(title: title, author: authors.map(author => author.name))
|
||||||
>>> set par(justify: true)
|
|
||||||
>>> set page(
|
>>> set page(
|
||||||
>>> "us-letter",
|
>>> "us-letter",
|
||||||
>>> margin: auto,
|
>>> header: align(right, title),
|
||||||
>>> header: align(
|
|
||||||
>>> right + horizon,
|
|
||||||
>>> title
|
|
||||||
>>> ),
|
|
||||||
>>> numbering: "1",
|
>>> numbering: "1",
|
||||||
>>> columns: 2,
|
>>> columns: 2,
|
||||||
>>> )
|
>>> )
|
||||||
|
>>> set par(justify: true)
|
||||||
|
>>> set text(11pt, font: "Libertinus Serif")
|
||||||
>>>
|
>>>
|
||||||
>>> show heading.where(
|
>>> show heading.where(level: 1): set align(center)
|
||||||
>>> level: 1
|
>>> show heading.where(level: 1): set text(13pt, weight: "regular")
|
||||||
>>> ): it => block(
|
>>> show heading.where(level: 1): it => block(smallcaps(it.body))
|
||||||
>>> align(center,
|
|
||||||
>>> text(
|
|
||||||
>>> 13pt,
|
|
||||||
>>> weight: "regular",
|
|
||||||
>>> smallcaps(it.body),
|
|
||||||
>>> )
|
|
||||||
>>> ),
|
|
||||||
>>> )
|
|
||||||
>>> show heading.where(
|
|
||||||
>>> level: 2
|
|
||||||
>>> ): it => box(
|
|
||||||
>>> text(
|
|
||||||
>>> 11pt,
|
|
||||||
>>> weight: "regular",
|
|
||||||
>>> style: "italic",
|
|
||||||
>>> it.body + [.],
|
|
||||||
>>> )
|
|
||||||
>>> )
|
|
||||||
>>>
|
>>>
|
||||||
>>> place(
|
>>> show heading.where(level: 2): set text(11pt, weight: "regular", style: "italic")
|
||||||
>>> top,
|
>>> show heading.where(level: 2): it => [#it.body.]
|
||||||
>>> float: true,
|
>>>
|
||||||
>>> scope: "parent",
|
>>> place(top + center, float: true, scope: "parent", clearance: 2em, {
|
||||||
>>> clearance: 2em,
|
>>> set par(justify: false)
|
||||||
>>> {
|
>>> block(text(17pt, title))
|
||||||
>>> set align(center)
|
|
||||||
>>> text(17pt, title)
|
|
||||||
>>> let count = calc.min(authors.len(), 3)
|
>>> let count = calc.min(authors.len(), 3)
|
||||||
>>> grid(
|
>>> grid(
|
||||||
>>> columns: (1fr,) * count,
|
>>> columns: (1fr,) * count,
|
||||||
@ -344,19 +309,15 @@ call.
|
|||||||
>>> #link("mailto:" + author.email)
|
>>> #link("mailto:" + author.email)
|
||||||
>>> ]),
|
>>> ]),
|
||||||
>>> )
|
>>> )
|
||||||
>>> par(justify: false)[
|
>>> strong[Abstract]
|
||||||
>>> *Abstract* \
|
>>> linebreak()
|
||||||
>>> #abstract
|
>>> abstract
|
||||||
>>> ]
|
>>> })
|
||||||
>>> },
|
|
||||||
>>> )
|
|
||||||
>>> doc
|
>>> doc
|
||||||
>>>}
|
>>> }
|
||||||
<<< #import "conf.typ": conf
|
<<< #import "conf.typ": conf
|
||||||
#show: conf.with(
|
#show: conf.with(
|
||||||
title: [
|
title: [Towards Improved Modelling],
|
||||||
Towards Improved Modelling
|
|
||||||
],
|
|
||||||
authors: (
|
authors: (
|
||||||
(
|
(
|
||||||
name: "Theresa Tungsten",
|
name: "Theresa Tungsten",
|
||||||
@ -397,7 +358,7 @@ that define reusable document styles. You've made it far and learned a lot. You
|
|||||||
can now use Typst to write your own documents and share them with others.
|
can now use Typst to write your own documents and share them with others.
|
||||||
|
|
||||||
We are still a super young project and are looking for feedback. If you have any
|
We are still a super young project and are looking for feedback. If you have any
|
||||||
questions, suggestions or you found a bug, please let us know
|
questions, suggestions, or you found a bug, please let us know
|
||||||
in the [Forum](https://forum.typst.app/),
|
in the [Forum](https://forum.typst.app/),
|
||||||
on our [Discord server](https://discord.gg/2uDybryKPe),
|
on our [Discord server](https://discord.gg/2uDybryKPe),
|
||||||
on [GitHub](https://github.com/typst/typst/),
|
on [GitHub](https://github.com/typst/typst/),
|
||||||
|
8
tests/ref/html/image-jpg-html-base64.html
Normal file
8
tests/ref/html/image-jpg-html-base64.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body><img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAA3ADcAAD/4QBiRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAEAAAITAAMAAAABAAEAAAAAAAAAAADcAAAAAQAAANwAAAAB/8AACwgAUAAwAQERAP/bAEMACAYGBwYFCAcHBwkJCAoMFA0MCwsMGRITDxQdGh8eHRocHCAkLicgIiwjHBwoNyksMDE0NDQfJzk9ODI8LjM0Mv/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/aAAgBAQAAPwD5/ooooooooooooor7+ooor4Bor7+ooor4Booor7+or4Booor7+or4Bor7+ooor4Bor7+ooor4Bor7+ooor4Bor7+ooor/2Q==" alt="The letter F"></body>
|
||||||
|
</html>
|
10
tests/ref/html/image-scaling-methods.html
Normal file
10
tests/ref/html/image-scaling-methods.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="display: flex; flex-direction: row; gap: 4pt"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAKUlEQVR4AQEeAOH/AP8AAAD/AAAA/wCAAAAAgAAAAIAAgIAAAICAgACAcFMHfiTGz0oAAAAASUVORK5CYII=" style="width: 28.346456692913385pt"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAKUlEQVR4AQEeAOH/AP8AAAD/AAAA/wCAAAAAgAAAAIAAgIAAAICAgACAcFMHfiTGz0oAAAAASUVORK5CYII=" style="image-rendering: smooth; width: 28.346456692913385pt"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAKUlEQVR4AQEeAOH/AP8AAAD/AAAA/wCAAAAAgAAAAIAAgIAAAICAgACAcFMHfiTGz0oAAAAASUVORK5CYII=" style="image-rendering: pixelated; width: 28.346456692913385pt"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -9,6 +9,9 @@
|
|||||||
#set page(height: 60pt)
|
#set page(height: 60pt)
|
||||||
#image("/assets/images/tiger.jpg")
|
#image("/assets/images/tiger.jpg")
|
||||||
|
|
||||||
|
--- image-jpg-html-base64 html ---
|
||||||
|
#image("/assets/images/f2t.jpg", alt: "The letter F")
|
||||||
|
|
||||||
--- image-sizing ---
|
--- image-sizing ---
|
||||||
// Test configuring the size and fitting behaviour of images.
|
// Test configuring the size and fitting behaviour of images.
|
||||||
|
|
||||||
@ -128,7 +131,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
width: 1cm,
|
width: 1cm,
|
||||||
)
|
)
|
||||||
|
|
||||||
--- image-scaling-methods ---
|
--- image-scaling-methods render html ---
|
||||||
#let img(scaling) = image(
|
#let img(scaling) = image(
|
||||||
bytes((
|
bytes((
|
||||||
0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
|
0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
|
||||||
@ -144,14 +147,26 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
|
|||||||
scaling: scaling,
|
scaling: scaling,
|
||||||
)
|
)
|
||||||
|
|
||||||
#stack(
|
#let images = (
|
||||||
dir: ltr,
|
|
||||||
spacing: 4pt,
|
|
||||||
img(auto),
|
img(auto),
|
||||||
img("smooth"),
|
img("smooth"),
|
||||||
img("pixelated"),
|
img("pixelated"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#context if target() == "html" {
|
||||||
|
// TODO: Remove this once `stack` is supported in HTML export.
|
||||||
|
html.div(
|
||||||
|
style: "display: flex; flex-direction: row; gap: 4pt",
|
||||||
|
images.join(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
stack(
|
||||||
|
dir: ltr,
|
||||||
|
spacing: 4pt,
|
||||||
|
..images,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
--- image-natural-dpi-sizing ---
|
--- image-natural-dpi-sizing ---
|
||||||
// Test that images aren't upscaled.
|
// Test that images aren't upscaled.
|
||||||
// Image is just 48x80 at 220dpi. It should not be scaled to fit the page
|
// Image is just 48x80 at 220dpi. It should not be scaled to fit the page
|
||||||
|
Loading…
x
Reference in New Issue
Block a user