Configurable font edges ⚙

Adds top-edge and bottom-edge parameters to the font function. These define how
the box around a word is computed. The possible values are:
- ascender
- cap-height (default top edge)
- x-height
- baseline (default bottom edge)
- descender

The defaults are chosen so that it's easy to create good-looking designs with
vertical alignment. Since they are much tighter than what most other software
uses by default, the default leading had to be increased to 50% of the font size
and paragraph spacing to 100% of the font size.

The values cap-height and x-height fall back to ascender in case they are zero
because this value may occur in fonts that don't have glyphs with cap- or
x-height (like Twitter Color Emoji). Since cap-height is the default top edge,
doing no fallback would break things badly.

Removes softness in favor of a simple boolean for pages and a more finegread u8
for spacing. This is needed to make paragraph spacing consume line spacing
created by hard line breaks.
This commit is contained in:
Laurenz 2021-03-19 13:20:58 +01:00
parent bd12d135ca
commit 54a9ccb1a5
50 changed files with 231 additions and 111 deletions

View File

@ -38,7 +38,7 @@ impl<'a> ExecContext<'a> {
env,
diags: DiagSet::new(),
tree: Tree { runs: vec![] },
page: Some(PageInfo::new(&state, Softness::Hard)),
page: Some(PageInfo::new(&state, true)),
stack: NodeStack::new(&state),
par: NodePar::new(&state),
state,
@ -77,7 +77,8 @@ impl<'a> ExecContext<'a> {
/// Push a layout node into the active paragraph.
///
/// Spacing nodes will be handled according to their [`Softness`].
/// Spacing nodes will be handled according to their
/// [`softness`](NodeSpacing::softness).
pub fn push(&mut self, node: impl Into<Node>) {
push(&mut self.par.children, node.into());
}
@ -87,7 +88,7 @@ impl<'a> ExecContext<'a> {
let em = self.state.font.font_size();
self.push(NodeSpacing {
amount: self.state.par.word_spacing.resolve(em),
softness: Softness::Soft,
softness: 1,
});
}
@ -109,15 +110,19 @@ impl<'a> ExecContext<'a> {
/// Apply a forced line break.
pub fn push_linebreak(&mut self) {
self.finish_par();
let em = self.state.font.font_size();
self.push_into_stack(NodeSpacing {
amount: self.state.par.leading.resolve(em),
softness: 2,
});
}
/// Apply a forced paragraph break.
pub fn push_parbreak(&mut self) {
let em = self.state.font.font_size();
self.push_into_stack(NodeSpacing {
amount: self.state.par.par_spacing.resolve(em),
softness: Softness::Soft,
amount: self.state.par.spacing.resolve(em),
softness: 1,
});
}
@ -163,11 +168,13 @@ impl<'a> ExecContext<'a> {
NodeText {
text,
aligns: self.state.aligns,
dir: self.state.dirs.cross,
font_size: self.state.font.font_size(),
aligns: self.state.aligns,
families: Rc::clone(&self.state.font.families),
variant,
font_size: self.state.font.font_size(),
top_edge: self.state.font.top_edge,
bottom_edge: self.state.font.bottom_edge,
}
}
@ -192,12 +199,12 @@ impl<'a> ExecContext<'a> {
}
/// Finish the active page.
pub fn finish_page(&mut self, keep: bool, new_softness: Softness, source: Span) {
pub fn finish_page(&mut self, keep: bool, hard: bool, source: Span) {
if let Some(info) = &mut self.page {
let info = mem::replace(info, PageInfo::new(&self.state, new_softness));
let info = mem::replace(info, PageInfo::new(&self.state, hard));
let stack = self.finish_stack();
if !stack.children.is_empty() || (keep && info.softness == Softness::Hard) {
if !stack.children.is_empty() || (keep && info.hard) {
self.tree.runs.push(NodePages {
size: info.size,
child: NodePad {
@ -215,7 +222,7 @@ impl<'a> ExecContext<'a> {
/// Finish execution and return the created layout tree.
pub fn finish(mut self) -> Pass<Tree> {
assert!(self.page.is_some());
self.finish_page(true, Softness::Soft, Span::default());
self.finish_page(true, false, Span::default());
Pass::new(self.tree, self.diags)
}
}
@ -223,16 +230,18 @@ impl<'a> ExecContext<'a> {
/// Push a node into a list, taking care of spacing softness.
fn push(nodes: &mut Vec<Node>, node: Node) {
if let Node::Spacing(spacing) = node {
if spacing.softness == Softness::Soft && nodes.is_empty() {
if nodes.is_empty() && spacing.softness > 0 {
return;
}
if let Some(&Node::Spacing(other)) = nodes.last() {
if spacing.softness > other.softness {
nodes.pop();
} else if spacing.softness == Softness::Soft {
if spacing.softness > 0 && spacing.softness >= other.softness {
return;
}
if spacing.softness < other.softness {
nodes.pop();
}
}
}
@ -242,7 +251,7 @@ fn push(nodes: &mut Vec<Node>, node: Node) {
/// Remove trailing soft spacing from a node list.
fn trim(nodes: &mut Vec<Node>) {
if let Some(&Node::Spacing(spacing)) = nodes.last() {
if spacing.softness == Softness::Soft {
if spacing.softness > 0 {
nodes.pop();
}
}
@ -252,15 +261,15 @@ fn trim(nodes: &mut Vec<Node>) {
struct PageInfo {
size: Size,
padding: Sides<Linear>,
softness: Softness,
hard: bool,
}
impl PageInfo {
fn new(state: &State, softness: Softness) -> Self {
fn new(state: &State, hard: bool) -> Self {
Self {
size: state.page.size,
padding: state.page.margins(),
softness,
hard,
}
}
}
@ -281,7 +290,7 @@ impl NodePar {
Self {
dirs: state.dirs,
aligns: state.aligns,
line_spacing: state.par.line_spacing.resolve(em),
line_spacing: state.par.leading.resolve(em),
children: vec![],
}
}

View File

@ -35,15 +35,6 @@ pub fn exec(
ctx.finish()
}
/// Defines how an item interacts with surrounding items.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Softness {
/// A soft item can be skipped in some circumstances.
Soft,
/// A hard item is always retained.
Hard,
}
/// Execute a node.
///
/// This manipulates active styling and document state and produces layout
@ -106,15 +97,15 @@ impl Exec for NodeRaw {
ctx.set_monospace();
let em = ctx.state.font.font_size();
let line_spacing = ctx.state.par.line_spacing.resolve(em);
let leading = ctx.state.par.leading.resolve(em);
let mut children = vec![];
let mut newline = false;
for line in &self.lines {
if newline {
children.push(layout::Node::Spacing(NodeSpacing {
amount: line_spacing,
softness: Softness::Soft,
amount: leading,
softness: 2,
}));
}

View File

@ -2,10 +2,9 @@ use std::rc::Rc;
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};
use crate::geom::{
Align, Dir, LayoutAligns, LayoutDirs, Length, Linear, Relative, Sides, Size,
};
use crate::geom::*;
use crate::paper::{Paper, PaperClass, PAPER_A4};
use crate::shaping::VerticalFontMetric;
/// The evaluation state.
#[derive(Debug, Clone, PartialEq)]
@ -77,20 +76,20 @@ impl Default for PageState {
/// Defines paragraph properties.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ParState {
/// The spacing between paragraphs (dependent on scaled font size).
pub spacing: Linear,
/// The spacing between lines (dependent on scaled font size).
pub leading: Linear,
/// The spacing between words (dependent on scaled font size).
pub word_spacing: Linear,
/// The spacing between lines (dependent on scaled font size).
pub line_spacing: Linear,
/// The spacing between paragraphs (dependent on scaled font size).
pub par_spacing: Linear,
}
impl Default for ParState {
fn default() -> Self {
Self {
spacing: Relative::new(1.0).into(),
leading: Relative::new(0.5).into(),
word_spacing: Relative::new(0.25).into(),
line_spacing: Linear::ZERO,
par_spacing: Relative::new(0.5).into(),
}
}
}
@ -106,6 +105,10 @@ pub struct FontState {
pub size: Length,
/// The linear to apply on the base font size.
pub scale: Linear,
/// The top end of the text bounding box.
pub top_edge: VerticalFontMetric,
/// The bottom end of the text bounding box.
pub bottom_edge: VerticalFontMetric,
/// Whether the strong toggle is active or inactive. This determines
/// whether the next `*` adds or removes font weight.
pub strong: bool,
@ -141,6 +144,8 @@ impl Default for FontState {
stretch: FontStretch::Normal,
},
size: Length::pt(11.0),
top_edge: VerticalFontMetric::CapHeight,
bottom_edge: VerticalFontMetric::Baseline,
scale: Linear::ONE,
strong: false,
emph: false,

View File

@ -180,6 +180,7 @@ impl<'a> PdfExporter<'a> {
Element::Text(shaped) => {
let mut text = content.text();
// Check if we need to issue a font switching action.
if shaped.face != face || shaped.font_size != size {
face = shaped.face;

View File

@ -1,7 +1,6 @@
use std::fmt::{self, Debug, Formatter};
use super::*;
use crate::exec::Softness;
/// A spacing node.
#[derive(Copy, Clone, PartialEq)]
@ -10,13 +9,11 @@ pub struct NodeSpacing {
pub amount: Length,
/// Defines how spacing interacts with surrounding spacing.
///
/// Hard spacing assures that a fixed amount of spacing will always be
/// inserted. Soft spacing will be consumed by previous soft spacing or
/// neighbouring hard spacing and can be used to insert overridable spacing,
/// e.g. between words or paragraphs.
///
/// This field is only used in evaluation, not in layouting.
pub softness: Softness,
/// Hard spacing (`softness = 0`) assures that a fixed amount of spacing
/// will always be inserted. Soft spacing (`softness >= 1`) will be consumed
/// by other spacing with lower softness and can be used to insert
/// overridable spacing, e.g. between words or paragraphs.
pub softness: u8,
}
impl Layout for NodeSpacing {
@ -27,10 +24,7 @@ impl Layout for NodeSpacing {
impl Debug for NodeSpacing {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.softness {
Softness::Soft => write!(f, "Soft({})", self.amount),
Softness::Hard => write!(f, "Hard({})", self.amount),
}
write!(f, "Spacing({}, {})", self.amount, self.softness)
}
}

View File

@ -4,35 +4,41 @@ use std::rc::Rc;
use fontdock::{FallbackTree, FontVariant};
use super::*;
use crate::shaping;
use crate::shaping::{shape, VerticalFontMetric};
/// A text node.
#[derive(Clone, PartialEq)]
pub struct NodeText {
/// The text.
pub text: String,
/// The text direction.
pub dir: Dir,
/// How to align this text node in its parent.
pub aligns: LayoutAligns,
/// The text.
pub text: String,
/// The font size.
pub font_size: Length,
/// The families used for font fallback.
pub families: Rc<FallbackTree>,
/// The font variant,
pub variant: FontVariant,
/// The font size.
pub font_size: Length,
/// The top end of the text bounding box.
pub top_edge: VerticalFontMetric,
/// The bottom end of the text bounding box.
pub bottom_edge: VerticalFontMetric,
}
impl Layout for NodeText {
fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Layouted {
Layouted::Frame(
shaping::shape(
shape(
&self.text,
self.dir,
self.font_size,
&mut ctx.env.fonts,
&self.families,
self.variant,
self.font_size,
self.top_edge,
self.bottom_edge,
&mut ctx.env.fonts,
),
self.aligns,
)

View File

@ -1,6 +1,7 @@
use fontdock::{FontStretch, FontStyle, FontWeight};
use super::*;
use crate::shaping::VerticalFontMetric;
/// `font`: Configure the font.
///
@ -13,6 +14,8 @@ use super::*;
/// - Font Style: `style`, of type `font-style`.
/// - Font Weight: `weight`, of type `font-weight`.
/// - Font Stretch: `stretch`, of type `relative`, between 0.5 and 2.0.
/// - Top edge of the font: `top-edge`, of type `vertical-font-metric`.
/// - Bottom edge of the font: `bottom-edge`, of type `vertical-font-metric`.
/// - Serif family definition: `serif`, of type `font-familiy-list`.
/// - Sans-serif family definition: `sans-serif`, of type `font-familiy-list`.
/// - Monospace family definition: `monospace`, of type `font-familiy-list`.
@ -22,15 +25,15 @@ use super::*;
/// if present.
///
/// # Relevant types and constants
/// - Type `font-family-list`
/// - coerces from `string`
/// - coerces from `array`
/// - coerces from `font-family`
/// - Type `font-family`
/// - `serif`
/// - `sans-serif`
/// - `monospace`
/// - coerces from `string`
/// - Type `font-family-list`
/// - coerces from `string`
/// - coerces from `array`
/// - coerces from `font-family`
/// - Type `font-style`
/// - `normal`
/// - `italic`
@ -46,12 +49,20 @@ use super::*;
/// - `extrabold` (800)
/// - `black` (900)
/// - coerces from `integer`
/// - Type `vertical-font-metric`
/// - `ascender`
/// - `cap-height`
/// - `x-height`
/// - `baseline`
/// - `descender`
pub fn font(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
let size = args.find::<Linear>(ctx);
let list: Vec<_> = args.filter::<FontFamily>(ctx).map(|f| f.to_string()).collect();
let style = args.get(ctx, "style");
let weight = args.get(ctx, "weight");
let stretch = args.get(ctx, "stretch");
let top_edge = args.get(ctx, "top-edge");
let bottom_edge = args.get(ctx, "bottom-edge");
let serif = args.get(ctx, "serif");
let sans_serif = args.get(ctx, "sans-serif");
let monospace = args.get(ctx, "monospace");
@ -87,6 +98,14 @@ pub fn font(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
ctx.state.font.variant.stretch = stretch;
}
if let Some(top_edge) = top_edge {
ctx.state.font.top_edge = top_edge;
}
if let Some(bottom_edge) = bottom_edge {
ctx.state.font.bottom_edge = bottom_edge;
}
for (variant, arg) in &[
(FontFamily::Serif, &serif),
(FontFamily::SansSerif, &sans_serif),
@ -185,3 +204,7 @@ typify! {
};
},
}
typify! {
VerticalFontMetric: "vertical font metric",
}

View File

@ -26,9 +26,9 @@ use std::fmt::{self, Display, Formatter};
use fontdock::{FontStyle, FontWeight};
use crate::eval::{Scope, ValueAny, ValueFunc};
use crate::exec::Softness;
use crate::layout::*;
use crate::prelude::*;
use crate::shaping::VerticalFontMetric;
/// Construct a scope containing all standard library definitions.
pub fn new() -> Scope {
@ -81,6 +81,11 @@ pub fn new() -> Scope {
set!(any: "bold", FontWeight::BOLD);
set!(any: "extrabold", FontWeight::EXTRABOLD);
set!(any: "black", FontWeight::BLACK);
set!(any: "ascender", VerticalFontMetric::Ascender);
set!(any: "cap-height", VerticalFontMetric::CapHeight);
set!(any: "x-height", VerticalFontMetric::XHeight);
set!(any: "baseline", VerticalFontMetric::Baseline);
set!(any: "descender", VerticalFontMetric::Descender);
std
}

View File

@ -95,13 +95,13 @@ pub fn page(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
}
ctx.set_dirs(Gen::new(main, cross));
ctx.finish_page(false, Softness::Hard, span);
ctx.finish_page(false, true, span);
if let Some(body) = &body {
// TODO: Restrict body to a single page?
body.exec(ctx);
ctx.state = snapshot;
ctx.finish_page(true, Softness::Soft, span);
ctx.finish_page(true, false, span);
}
})
}
@ -113,6 +113,6 @@ pub fn page(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
pub fn pagebreak(_: &mut EvalContext, args: &mut ValueArgs) -> Value {
let span = args.span;
Value::template("pagebreak", move |ctx| {
ctx.finish_page(true, Softness::Hard, span);
ctx.finish_page(true, true, span);
})
}

View File

@ -28,7 +28,7 @@ fn spacing(ctx: &mut EvalContext, args: &mut ValueArgs, axis: SpecAxis) -> Value
Value::template("spacing", move |ctx| {
if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.font_size());
let spacing = NodeSpacing { amount, softness: Softness::Hard };
let spacing = NodeSpacing { amount, softness: 0 };
if axis == ctx.state.dirs.main.axis() {
ctx.push_into_stack(spacing);
} else {

View File

@ -4,7 +4,7 @@
//! font for each individual character. When the direction is right-to-left, the
//! word is spelled backwards. Vertical shaping is not supported.
use std::fmt::{self, Debug, Formatter};
use std::fmt::{self, Debug, Display, Formatter};
use fontdock::{FaceId, FaceQuery, FallbackTree, FontVariant};
use ttf_parser::{Face, GlyphId};
@ -58,20 +58,55 @@ impl Debug for Shaped {
}
}
/// Identifies a vertical metric of a font.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum VerticalFontMetric {
/// The distance from the baseline to the typographic ascender.
///
/// Corresponds to the typographic ascender from the `OS/2` table if present
/// and falls back to the ascender from the `hhea` table otherwise.
Ascender,
/// The approximate height of uppercase letters.
CapHeight,
/// The approximate height of non-ascending lowercase letters.
XHeight,
/// The baseline on which the letters rest.
Baseline,
/// The distance from the baseline to the typographic descender.
///
/// Corresponds to the typographic descender from the `OS/2` table if
/// present and falls back to the descender from the `hhea` table otherwise.
Descender,
}
impl Display for VerticalFontMetric {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Ascender => "ascender",
Self::CapHeight => "cap-height",
Self::XHeight => "x-height",
Self::Baseline => "baseline",
Self::Descender => "descender",
})
}
}
/// Shape text into a frame containing [`Shaped`] runs.
pub fn shape(
text: &str,
dir: Dir,
font_size: Length,
loader: &mut FontLoader,
fallback: &FallbackTree,
variant: FontVariant,
font_size: Length,
top_edge: VerticalFontMetric,
bottom_edge: VerticalFontMetric,
loader: &mut FontLoader,
) -> Frame {
let mut frame = Frame::new(Size::new(Length::ZERO, font_size));
let mut frame = Frame::new(Size::new(Length::ZERO, Length::ZERO));
let mut shaped = Shaped::new(FaceId::MAX, font_size);
let mut offset = Length::ZERO;
let mut ascender = Length::ZERO;
let mut descender = Length::ZERO;
let mut width = Length::ZERO;
let mut top = Length::ZERO;
let mut bottom = Length::ZERO;
// Create an iterator with conditional direction.
let mut forwards = text.chars();
@ -86,7 +121,7 @@ pub fn shape(
let query = FaceQuery { fallback: fallback.iter(), variant, c };
if let Some(id) = loader.query(query) {
let face = loader.face(id).get();
let (glyph, width) = match lookup_glyph(face, c) {
let (glyph, glyph_width) = match lookup_glyph(face, c) {
Some(v) => v,
None => continue,
};
@ -96,27 +131,35 @@ pub fn shape(
// Flush the buffer and reset the metrics if we use a new font face.
if shaped.face != id {
place(&mut frame, shaped, offset, ascender, descender);
place(&mut frame, shaped, width, top, bottom);
shaped = Shaped::new(id, font_size);
offset = Length::ZERO;
ascender = convert(f64::from(face.ascender()));
descender = convert(f64::from(face.descender()));
width = Length::ZERO;
top = convert(f64::from(lookup_metric(face, top_edge)));
bottom = convert(f64::from(lookup_metric(face, bottom_edge)));
}
shaped.text.push(c);
shaped.glyphs.push(glyph);
shaped.offsets.push(offset);
offset += convert(f64::from(width));
shaped.offsets.push(width);
width += convert(f64::from(glyph_width));
}
}
// Flush the last buffered parts of the word.
place(&mut frame, shaped, offset, ascender, descender);
place(&mut frame, shaped, width, top, bottom);
frame
}
/// Place shaped text into a frame.
fn place(frame: &mut Frame, shaped: Shaped, width: Length, top: Length, bottom: Length) {
if !shaped.text.is_empty() {
frame.push(Point::new(frame.size.width, top), Element::Text(shaped));
frame.size.width += width;
frame.size.height = frame.size.height.max(top - bottom);
}
}
/// Look up the glyph for `c` and returns its index alongside its advance width.
fn lookup_glyph(face: &Face, c: char) -> Option<(GlyphId, u16)> {
let glyph = face.glyph_index(c)?;
@ -124,18 +167,32 @@ fn lookup_glyph(face: &Face, c: char) -> Option<(GlyphId, u16)> {
Some((glyph, width))
}
/// Place shaped text into a frame.
fn place(
frame: &mut Frame,
shaped: Shaped,
offset: Length,
ascender: Length,
descender: Length,
) {
if !shaped.text.is_empty() {
let pos = Point::new(frame.size.width, ascender);
frame.push(pos, Element::Text(shaped));
frame.size.width += offset;
frame.size.height = frame.size.height.max(ascender - descender);
/// Look up a vertical metric.
fn lookup_metric(face: &Face, metric: VerticalFontMetric) -> i16 {
match metric {
VerticalFontMetric::Ascender => lookup_ascender(face),
VerticalFontMetric::CapHeight => face
.capital_height()
.filter(|&h| h > 0)
.unwrap_or_else(|| lookup_ascender(face)),
VerticalFontMetric::XHeight => face
.x_height()
.filter(|&h| h > 0)
.unwrap_or_else(|| lookup_ascender(face)),
VerticalFontMetric::Baseline => 0,
VerticalFontMetric::Descender => lookup_descender(face),
}
}
/// The ascender of the face.
fn lookup_ascender(face: &Face) -> i16 {
// We prefer the typographic ascender over the Windows ascender because
// it can be overly large if the font has large glyphs.
face.typographic_ascender().unwrap_or_else(|| face.ascender())
}
/// The descender of the face.
fn lookup_descender(face: &Face) -> i16 {
// See `lookup_ascender` for reason.
face.typographic_descender().unwrap_or_else(|| face.descender())
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 746 B

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 929 B

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 827 B

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -17,8 +17,8 @@
---
// Syntax sugar for function definitions.
#let background = #239dad
#let rect(body) = rect(width: 2cm, height: 1cm, fill: background, body)
#let background = #9feb52
#let rect(body) = rect(width: 2cm, fill: background, pad(5pt, body))
#rect[Hi!]
// Error: 13 expected body

View File

@ -30,8 +30,8 @@
// the parentheses.
#align(center)[
// Markdown-like syntax for headings.
==== 3. Übungsblatt Computerorientierte Mathematik II #v(2mm)
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(2mm)
==== 3. Übungsblatt Computerorientierte Mathematik II #v(4mm)
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(4mm)
*Alle Antworten sind zu beweisen.*
]

View File

@ -31,6 +31,20 @@ Emoji: 🐪, 🌋, 🏞
𝛼 + 3𝛽 d𝑡
]
---
// Test top and bottom edge.
#page(width: 170pt)
#let try(top, bottom) = rect(fill: #9feb52)[
#font(top-edge: top, bottom-edge: bottom)
`From `#top` to `#bottom
]
#try(ascender, descender)
#try(ascender, baseline)
#try(cap-height, baseline)
#try(x-height, baseline)
---
// Ref: false

View File

@ -2,7 +2,7 @@
---
// Ends paragraphs.
Tightly #v(-5pt) packed
Tightly #v(0pt) packed
// Eating up soft spacing.
Inv #h(0pt) isible

View File

@ -28,3 +28,7 @@
// Unterminated.
// Error: 6 expected closing brace
\u{41*Bold*
---
// Some code stuff in text.
let f() , ; : | + - /= == 12 "string"

View File

@ -7,7 +7,7 @@
---
// Typst syntax inside.
`#let x = 1` \
`#[f 1]`
`#f(1)`
---
// Multiline block splits paragraphs.

View File

@ -1,8 +1,19 @@
// Test simple text.
---
Hello 🌏!
#page(width: 250pt)
---
// Some code stuff in text.
let f() , ; : | + - /= == 12 "string"
But, soft! what light through yonder window breaks? It is the east, and Juliet
is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and
pale with grief, That thou her maid art far more fair than she: Be not her maid,
since she is envious; Her vestal livery is but sick and green And none but fools
do wear it; cast it off. It is my lady, O, it is my love! O, that she knew she
were! She speaks yet she says nothing: what of that? Her eye discourses; I will
answer it.
I am too bold, 'tis not to me she speaks: Two of the fairest stars in all the
heaven, Having some business, do entreat her eyes To twinkle in their spheres
till they return. What if her eyes were there, they in her head? The brightness
of her cheek would shame those stars, As daylight doth a lamp; her eyes in
heaven Would through the airy region stream so bright That birds would sing and
think it were not night. See, how she leans her cheek upon her hand! O, that I
were a glove upon that hand, That I might touch that cheek!