summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-03-19 13:20:58 +0100
committerLaurenz <laurmaedje@gmail.com>2021-03-19 13:20:58 +0100
commit54a9ccb1a5e9f1f1e5d2538d2f4ce3d4f7afc4ae (patch)
tree3a0f45af5d1cd6d65420887f8412a5594b6300bd
parentbd12d135cab32d61b32945433e77579d04298d52 (diff)
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.
-rw-r--r--src/exec/context.rs51
-rw-r--r--src/exec/mod.rs15
-rw-r--r--src/exec/state.rs23
-rw-r--r--src/export/pdf.rs1
-rw-r--r--src/layout/spacing.rs18
-rw-r--r--src/layout/text.rs22
-rw-r--r--src/library/font.rs31
-rw-r--r--src/library/mod.rs7
-rw-r--r--src/library/page.rs6
-rw-r--r--src/library/spacing.rs2
-rw-r--r--src/shaping.rs115
-rw-r--r--tests/ref/comment.pngbin746 -> 706 bytes
-rw-r--r--tests/ref/control/for.pngbin2787 -> 2724 bytes
-rw-r--r--tests/ref/control/if.pngbin1727 -> 1637 bytes
-rw-r--r--tests/ref/control/invalid.pngbin3772 -> 3642 bytes
-rw-r--r--tests/ref/control/let.pngbin2089 -> 1961 bytes
-rw-r--r--tests/ref/control/while.pngbin929 -> 880 bytes
-rw-r--r--tests/ref/expand.pngbin1419 -> 1372 bytes
-rw-r--r--tests/ref/expr/array.pngbin3317 -> 3396 bytes
-rw-r--r--tests/ref/expr/block-invalid.pngbin522 -> 512 bytes
-rw-r--r--tests/ref/expr/block.pngbin1453 -> 1428 bytes
-rw-r--r--tests/ref/expr/call-invalid.pngbin2789 -> 2866 bytes
-rw-r--r--tests/ref/expr/call.pngbin5863 -> 5991 bytes
-rw-r--r--tests/ref/expr/dict.pngbin1627 -> 1612 bytes
-rw-r--r--tests/ref/expr/ops.pngbin827 -> 776 bytes
-rw-r--r--tests/ref/full/coma.pngbin59486 -> 59286 bytes
-rw-r--r--tests/ref/library/font.pngbin4554 -> 8878 bytes
-rw-r--r--tests/ref/library/image.pngbin219392 -> 219513 bytes
-rw-r--r--tests/ref/library/pad.pngbin1254 -> 1226 bytes
-rw-r--r--tests/ref/library/page.pngbin8417 -> 8290 bytes
-rw-r--r--tests/ref/library/pagebreak.pngbin840 -> 803 bytes
-rw-r--r--tests/ref/library/shapes.pngbin3266 -> 3136 bytes
-rw-r--r--tests/ref/library/spacing.pngbin3960 -> 3833 bytes
-rw-r--r--tests/ref/markup/emph.pngbin3079 -> 3053 bytes
-rw-r--r--tests/ref/markup/escape.pngbin4197 -> 5339 bytes
-rw-r--r--tests/ref/markup/heading.pngbin4656 -> 4540 bytes
-rw-r--r--tests/ref/markup/linebreak.pngbin3864 -> 3770 bytes
-rw-r--r--tests/ref/markup/nbsp.pngbin1818 -> 1763 bytes
-rw-r--r--tests/ref/markup/raw.pngbin7282 -> 7291 bytes
-rw-r--r--tests/ref/markup/strong.pngbin2847 -> 2824 bytes
-rw-r--r--tests/ref/repr.pngbin10195 -> 10139 bytes
-rw-r--r--tests/ref/spacing.pngbin5494 -> 5212 bytes
-rw-r--r--tests/ref/text.pngbin1880 -> 36910 bytes
-rw-r--r--tests/typ/control/let.typ4
-rw-r--r--tests/typ/full/coma.typ4
-rw-r--r--tests/typ/library/font.typ14
-rw-r--r--tests/typ/library/spacing.typ2
-rw-r--r--tests/typ/markup/escape.typ4
-rw-r--r--tests/typ/markup/raw.typ2
-rw-r--r--tests/typ/text.typ21
50 files changed, 231 insertions, 111 deletions
diff --git a/src/exec/context.rs b/src/exec/context.rs
index 6a1c2416..7fd72fe3 100644
--- a/src/exec/context.rs
+++ b/src/exec/context.rs
@@ -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![],
}
}
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index 45abca02..35ffa2b6 100644
--- a/src/exec/mod.rs
+++ b/src/exec/mod.rs
@@ -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,
}));
}
diff --git a/src/exec/state.rs b/src/exec/state.rs
index 65f26439..aa2dde1c 100644
--- a/src/exec/state.rs
+++ b/src/exec/state.rs
@@ -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,
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index f86f977c..43c58403 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -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;
diff --git a/src/layout/spacing.rs b/src/layout/spacing.rs
index 4b564a2b..2bcb7ac1 100644
--- a/src/layout/spacing.rs
+++ b/src/layout/spacing.rs
@@ -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)
}
}
diff --git a/src/layout/text.rs b/src/layout/text.rs
index 256a6e6d..7b4eb08e 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -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,
)
diff --git a/src/library/font.rs b/src/library/font.rs
index 460cfba1..ecc15d96 100644
--- a/src/library/font.rs
+++ b/src/library/font.rs
@@ -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",
+}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 09fda20f..453eab26 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -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
}
diff --git a/src/library/page.rs b/src/library/page.rs
index 17021607..f7d76eaf 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -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);
})
}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index d8f18f41..4965a220 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -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 {
diff --git a/src/shaping.rs b/src/shaping.rs
index a8d2b2bf..41119639 100644
--- a/src/shaping.rs
+++ b/src/shaping.rs
@@ -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())
+}
diff --git a/tests/ref/comment.png b/tests/ref/comment.png
index 1fad97fe..6d5723ef 100644
--- a/tests/ref/comment.png
+++ b/tests/ref/comment.png
Binary files differ
diff --git a/tests/ref/control/for.png b/tests/ref/control/for.png
index d04ea62c..f3830b34 100644
--- a/tests/ref/control/for.png
+++ b/tests/ref/control/for.png
Binary files differ
diff --git a/tests/ref/control/if.png b/tests/ref/control/if.png
index 99bde49e..0527754e 100644
--- a/tests/ref/control/if.png
+++ b/tests/ref/control/if.png
Binary files differ
diff --git a/tests/ref/control/invalid.png b/tests/ref/control/invalid.png
index ff4c6987..e9c64b8a 100644
--- a/tests/ref/control/invalid.png
+++ b/tests/ref/control/invalid.png
Binary files differ
diff --git a/tests/ref/control/let.png b/tests/ref/control/let.png
index 5fb1c992..20aad953 100644
--- a/tests/ref/control/let.png
+++ b/tests/ref/control/let.png
Binary files differ
diff --git a/tests/ref/control/while.png b/tests/ref/control/while.png
index 890131f1..2ea1bcb0 100644
--- a/tests/ref/control/while.png
+++ b/tests/ref/control/while.png
Binary files differ
diff --git a/tests/ref/expand.png b/tests/ref/expand.png
index fc1f5de9..f18e929b 100644
--- a/tests/ref/expand.png
+++ b/tests/ref/expand.png
Binary files differ
diff --git a/tests/ref/expr/array.png b/tests/ref/expr/array.png
index 784553b3..b1e20348 100644
--- a/tests/ref/expr/array.png
+++ b/tests/ref/expr/array.png
Binary files differ
diff --git a/tests/ref/expr/block-invalid.png b/tests/ref/expr/block-invalid.png
index b380defc..d1e85402 100644
--- a/tests/ref/expr/block-invalid.png
+++ b/tests/ref/expr/block-invalid.png
Binary files differ
diff --git a/tests/ref/expr/block.png b/tests/ref/expr/block.png
index d5ad52ae..d8ea84fb 100644
--- a/tests/ref/expr/block.png
+++ b/tests/ref/expr/block.png
Binary files differ
diff --git a/tests/ref/expr/call-invalid.png b/tests/ref/expr/call-invalid.png
index 88f13780..eb192357 100644
--- a/tests/ref/expr/call-invalid.png
+++ b/tests/ref/expr/call-invalid.png
Binary files differ
diff --git a/tests/ref/expr/call.png b/tests/ref/expr/call.png
index 7197bb90..26f1cb03 100644
--- a/tests/ref/expr/call.png
+++ b/tests/ref/expr/call.png
Binary files differ
diff --git a/tests/ref/expr/dict.png b/tests/ref/expr/dict.png
index 28a7cb3b..1caef1e0 100644
--- a/tests/ref/expr/dict.png
+++ b/tests/ref/expr/dict.png
Binary files differ
diff --git a/tests/ref/expr/ops.png b/tests/ref/expr/ops.png
index 95a24c0e..2f3a28ea 100644
--- a/tests/ref/expr/ops.png
+++ b/tests/ref/expr/ops.png
Binary files differ
diff --git a/tests/ref/full/coma.png b/tests/ref/full/coma.png
index f5db682d..5fd6d801 100644
--- a/tests/ref/full/coma.png
+++ b/tests/ref/full/coma.png
Binary files differ
diff --git a/tests/ref/library/font.png b/tests/ref/library/font.png
index 8e611b16..199a15f2 100644
--- a/tests/ref/library/font.png
+++ b/tests/ref/library/font.png
Binary files differ
diff --git a/tests/ref/library/image.png b/tests/ref/library/image.png
index af497c8d..0a45e11b 100644
--- a/tests/ref/library/image.png
+++ b/tests/ref/library/image.png
Binary files differ
diff --git a/tests/ref/library/pad.png b/tests/ref/library/pad.png
index a655061b..3536db5d 100644
--- a/tests/ref/library/pad.png
+++ b/tests/ref/library/pad.png
Binary files differ
diff --git a/tests/ref/library/page.png b/tests/ref/library/page.png
index 1537fad4..7d1ff96f 100644
--- a/tests/ref/library/page.png
+++ b/tests/ref/library/page.png
Binary files differ
diff --git a/tests/ref/library/pagebreak.png b/tests/ref/library/pagebreak.png
index b1ee0eef..f0052f9e 100644
--- a/tests/ref/library/pagebreak.png
+++ b/tests/ref/library/pagebreak.png
Binary files differ
diff --git a/tests/ref/library/shapes.png b/tests/ref/library/shapes.png
index 8ace6fd6..a244feb1 100644
--- a/tests/ref/library/shapes.png
+++ b/tests/ref/library/shapes.png
Binary files differ
diff --git a/tests/ref/library/spacing.png b/tests/ref/library/spacing.png
index 0842d409..6a234a8a 100644
--- a/tests/ref/library/spacing.png
+++ b/tests/ref/library/spacing.png
Binary files differ
diff --git a/tests/ref/markup/emph.png b/tests/ref/markup/emph.png
index f002610e..75231a62 100644
--- a/tests/ref/markup/emph.png
+++ b/tests/ref/markup/emph.png
Binary files differ
diff --git a/tests/ref/markup/escape.png b/tests/ref/markup/escape.png
index 3fa44c6d..8e946279 100644
--- a/tests/ref/markup/escape.png
+++ b/tests/ref/markup/escape.png
Binary files differ
diff --git a/tests/ref/markup/heading.png b/tests/ref/markup/heading.png
index 9f2ce6ec..3473f798 100644
--- a/tests/ref/markup/heading.png
+++ b/tests/ref/markup/heading.png
Binary files differ
diff --git a/tests/ref/markup/linebreak.png b/tests/ref/markup/linebreak.png
index fec65248..4e4702f9 100644
--- a/tests/ref/markup/linebreak.png
+++ b/tests/ref/markup/linebreak.png
Binary files differ
diff --git a/tests/ref/markup/nbsp.png b/tests/ref/markup/nbsp.png
index 3e1f0ee7..89f75f14 100644
--- a/tests/ref/markup/nbsp.png
+++ b/tests/ref/markup/nbsp.png
Binary files differ
diff --git a/tests/ref/markup/raw.png b/tests/ref/markup/raw.png
index ce833428..59a4e3dd 100644
--- a/tests/ref/markup/raw.png
+++ b/tests/ref/markup/raw.png
Binary files differ
diff --git a/tests/ref/markup/strong.png b/tests/ref/markup/strong.png
index ac38c436..7e62f0f3 100644
--- a/tests/ref/markup/strong.png
+++ b/tests/ref/markup/strong.png
Binary files differ
diff --git a/tests/ref/repr.png b/tests/ref/repr.png
index 2f5697fd..ad10705c 100644
--- a/tests/ref/repr.png
+++ b/tests/ref/repr.png
Binary files differ
diff --git a/tests/ref/spacing.png b/tests/ref/spacing.png
index 8bdc9885..454b154f 100644
--- a/tests/ref/spacing.png
+++ b/tests/ref/spacing.png
Binary files differ
diff --git a/tests/ref/text.png b/tests/ref/text.png
index 38ae364d..1dd70c1c 100644
--- a/tests/ref/text.png
+++ b/tests/ref/text.png
Binary files differ
diff --git a/tests/typ/control/let.typ b/tests/typ/control/let.typ
index dd971ee5..4f84aa67 100644
--- a/tests/typ/control/let.typ
+++ b/tests/typ/control/let.typ
@@ -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
diff --git a/tests/typ/full/coma.typ b/tests/typ/full/coma.typ
index 243c667f..619843d7 100644
--- a/tests/typ/full/coma.typ
+++ b/tests/typ/full/coma.typ
@@ -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.*
]
diff --git a/tests/typ/library/font.typ b/tests/typ/library/font.typ
index c00bff5a..ecea303f 100644
--- a/tests/typ/library/font.typ
+++ b/tests/typ/library/font.typ
@@ -32,6 +32,20 @@ Emoji: 🐪, 🌋, 🏞
]
---
+// 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
// Error: 7-12 unexpected argument
diff --git a/tests/typ/library/spacing.typ b/tests/typ/library/spacing.typ
index a34840c6..6d50f0dc 100644
--- a/tests/typ/library/spacing.typ
+++ b/tests/typ/library/spacing.typ
@@ -2,7 +2,7 @@
---
// Ends paragraphs.
-Tightly #v(-5pt) packed
+Tightly #v(0pt) packed
// Eating up soft spacing.
Inv #h(0pt) isible
diff --git a/tests/typ/markup/escape.typ b/tests/typ/markup/escape.typ
index eeac4997..b0a809f9 100644
--- a/tests/typ/markup/escape.typ
+++ b/tests/typ/markup/escape.typ
@@ -28,3 +28,7 @@
// Unterminated.
// Error: 6 expected closing brace
\u{41*Bold*
+
+---
+// Some code stuff in text.
+let f() , ; : | + - /= == 12 "string"
diff --git a/tests/typ/markup/raw.typ b/tests/typ/markup/raw.typ
index f5074c8e..005d413e 100644
--- a/tests/typ/markup/raw.typ
+++ b/tests/typ/markup/raw.typ
@@ -7,7 +7,7 @@
---
// Typst syntax inside.
`#let x = 1` \
-`#[f 1]`
+`#f(1)`
---
// Multiline block splits paragraphs.
diff --git a/tests/typ/text.typ b/tests/typ/text.typ
index d86f4895..b424cee8 100644
--- a/tests/typ/text.typ
+++ b/tests/typ/text.typ
@@ -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!