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.
@ -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![],
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
115
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())
|
||||
}
|
||||
|
Before Width: | Height: | Size: 746 B After Width: | Height: | Size: 706 B |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 929 B After Width: | Height: | Size: 880 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 522 B After Width: | Height: | Size: 512 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 827 B After Width: | Height: | Size: 776 B |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 214 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 840 B After Width: | Height: | Size: 803 B |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 36 KiB |
@ -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
|
||||
|
@ -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.*
|
||||
]
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
// Ends paragraphs.
|
||||
Tightly #v(-5pt) packed
|
||||
Tightly #v(0pt) packed
|
||||
|
||||
// Eating up soft spacing.
|
||||
Inv #h(0pt) isible
|
||||
|
@ -28,3 +28,7 @@
|
||||
// Unterminated.
|
||||
// Error: 6 expected closing brace
|
||||
\u{41*Bold*
|
||||
|
||||
---
|
||||
// Some code stuff in text.
|
||||
let f() , ; : | + - /= == 12 "string"
|
||||
|
@ -7,7 +7,7 @@
|
||||
---
|
||||
// Typst syntax inside.
|
||||
`#let x = 1` \
|
||||
`#[f 1]`
|
||||
`#f(1)`
|
||||
|
||||
---
|
||||
// Multiline block splits paragraphs.
|
||||
|
@ -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!
|
||||
|