mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Refactor layouting base 🪁
This commit is contained in:
parent
f04ad0ffa5
commit
d3bc4ec073
@ -1,7 +1,5 @@
|
||||
//! Simplifies argument parsing.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use super::{Convert, EvalContext, RefKey, ValueDict};
|
||||
use crate::syntax::{SpanWith, Spanned};
|
||||
|
||||
@ -67,7 +65,7 @@ impl Args {
|
||||
{
|
||||
for (&key, entry) in self.0.v.nums_mut() {
|
||||
let span = entry.value.span;
|
||||
match T::convert(mem::take(&mut entry.value)).0 {
|
||||
match T::convert(std::mem::take(&mut entry.value)).0 {
|
||||
Ok(t) => {
|
||||
self.0.v.remove(key);
|
||||
return Some(t);
|
||||
@ -87,7 +85,7 @@ impl Args {
|
||||
std::iter::from_fn(move || {
|
||||
for (&key, entry) in self.0.v.nums_mut().skip(skip) {
|
||||
let span = entry.value.span;
|
||||
match T::convert(mem::take(&mut entry.value)).0 {
|
||||
match T::convert(std::mem::take(&mut entry.value)).0 {
|
||||
Ok(t) => {
|
||||
self.0.v.remove(key);
|
||||
return Some(t);
|
||||
@ -109,7 +107,7 @@ impl Args {
|
||||
std::iter::from_fn(move || {
|
||||
for (key, entry) in self.0.v.strs_mut().skip(skip) {
|
||||
let span = entry.value.span;
|
||||
match T::convert(mem::take(&mut entry.value)).0 {
|
||||
match T::convert(std::mem::take(&mut entry.value)).0 {
|
||||
Ok(t) => {
|
||||
let key = key.clone();
|
||||
self.0.v.remove(&key);
|
||||
|
@ -16,16 +16,15 @@ pub use state::*;
|
||||
pub use value::*;
|
||||
|
||||
use std::any::Any;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use fontdock::FontStyle;
|
||||
|
||||
use crate::diag::Diag;
|
||||
use crate::diag::{Deco, Feedback, Pass};
|
||||
use crate::geom::{Gen, Length, Relative, Spec, Switch};
|
||||
use crate::geom::{Gen, Length, Relative};
|
||||
use crate::layout::{
|
||||
Document, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text,
|
||||
Document, Expansion, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text,
|
||||
};
|
||||
use crate::syntax::*;
|
||||
|
||||
@ -35,11 +34,9 @@ use crate::syntax::*;
|
||||
/// evaluation.
|
||||
pub fn eval(tree: &SynTree, state: State) -> Pass<Document> {
|
||||
let mut ctx = EvalContext::new(state);
|
||||
|
||||
ctx.start_page_group(false);
|
||||
tree.eval(&mut ctx);
|
||||
ctx.end_page_group();
|
||||
|
||||
ctx.finish()
|
||||
}
|
||||
|
||||
@ -125,7 +122,7 @@ impl EvalContext {
|
||||
/// [`push`]: #method.push
|
||||
/// [`end_group`]: #method.end_group
|
||||
pub fn start_group<T: 'static>(&mut self, meta: T) {
|
||||
self.groups.push((Box::new(meta), mem::take(&mut self.inner)));
|
||||
self.groups.push((Box::new(meta), std::mem::take(&mut self.inner)));
|
||||
}
|
||||
|
||||
/// End a layouting group started with [`start_group`].
|
||||
@ -134,9 +131,15 @@ impl EvalContext {
|
||||
///
|
||||
/// [`start_group`]: #method.start_group
|
||||
pub fn end_group<T: 'static>(&mut self) -> (T, Vec<LayoutNode>) {
|
||||
if let Some(&LayoutNode::Spacing(spacing)) = self.inner.last() {
|
||||
if spacing.softness == Softness::Soft {
|
||||
self.inner.pop();
|
||||
}
|
||||
}
|
||||
|
||||
let (any, outer) = self.groups.pop().expect("no pushed group");
|
||||
let group = *any.downcast::<T>().expect("bad group type");
|
||||
(group, mem::replace(&mut self.inner, outer))
|
||||
(group, std::mem::replace(&mut self.inner, outer))
|
||||
}
|
||||
|
||||
/// Start a page run group based on the active page state.
|
||||
@ -167,9 +170,9 @@ impl EvalContext {
|
||||
padding,
|
||||
child: LayoutNode::dynamic(Stack {
|
||||
dirs,
|
||||
children,
|
||||
aligns,
|
||||
expand: Spec::new(true, true),
|
||||
expansion: Gen::new(Expansion::Fill, Expansion::Fill),
|
||||
children,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
@ -191,13 +194,13 @@ impl EvalContext {
|
||||
if !children.is_empty() {
|
||||
// FIXME: This is a hack and should be superseded by constraints
|
||||
// having min and max size.
|
||||
let expand_cross = self.groups.len() <= 1;
|
||||
let cross_expansion = Expansion::fill_if(self.groups.len() <= 1);
|
||||
self.push(Par {
|
||||
dirs,
|
||||
aligns,
|
||||
cross_expansion,
|
||||
line_spacing,
|
||||
children,
|
||||
aligns,
|
||||
expand: Gen::new(false, expand_cross).switch(dirs),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -222,7 +225,7 @@ impl EvalContext {
|
||||
Text {
|
||||
text,
|
||||
dir: self.state.dirs.cross,
|
||||
size: self.state.font.font_size(),
|
||||
font_size: self.state.font.font_size(),
|
||||
families: Rc::clone(&self.state.font.families),
|
||||
variant,
|
||||
aligns: self.state.aligns,
|
||||
@ -284,26 +287,11 @@ impl Eval for SynNode {
|
||||
ctx.start_par_group();
|
||||
}
|
||||
|
||||
SynNode::Emph => {
|
||||
ctx.state.font.emph ^= true;
|
||||
}
|
||||
|
||||
SynNode::Strong => {
|
||||
ctx.state.font.strong ^= true;
|
||||
}
|
||||
|
||||
SynNode::Heading(heading) => {
|
||||
heading.eval(ctx);
|
||||
}
|
||||
|
||||
SynNode::Raw(raw) => {
|
||||
raw.eval(ctx);
|
||||
}
|
||||
|
||||
SynNode::Expr(expr) => {
|
||||
let value = expr.eval(ctx);
|
||||
value.eval(ctx);
|
||||
}
|
||||
SynNode::Emph => ctx.state.font.emph ^= true,
|
||||
SynNode::Strong => ctx.state.font.strong ^= true,
|
||||
SynNode::Heading(heading) => heading.eval(ctx),
|
||||
SynNode::Raw(raw) => raw.eval(ctx),
|
||||
SynNode::Expr(expr) => expr.eval(ctx).eval(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,9 +327,9 @@ impl Eval for NodeRaw {
|
||||
|
||||
ctx.push(Stack {
|
||||
dirs: ctx.state.dirs,
|
||||
children,
|
||||
aligns: ctx.state.aligns,
|
||||
expand: Spec::new(false, false),
|
||||
expansion: Gen::new(Expansion::Fit, Expansion::Fit),
|
||||
children,
|
||||
});
|
||||
|
||||
ctx.state.font.families = prev;
|
||||
|
@ -142,9 +142,9 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
||||
match element {
|
||||
LayoutElement::Text(shaped) => {
|
||||
// Check if we need to issue a font switching action.
|
||||
if shaped.face != face || shaped.size != size {
|
||||
if shaped.face != face || shaped.font_size != size {
|
||||
face = shaped.face;
|
||||
size = shaped.size;
|
||||
size = shaped.font_size;
|
||||
text.tf(
|
||||
self.to_pdf[&shaped.face] as u32 + 1,
|
||||
size.to_pt() as f32,
|
||||
|
@ -3,6 +3,7 @@ use super::*;
|
||||
/// The top-level layout node.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Document {
|
||||
/// The runs of pages with same properties.
|
||||
pub runs: Vec<Pages>,
|
||||
}
|
||||
|
||||
@ -22,26 +23,17 @@ impl Document {
|
||||
pub struct Pages {
|
||||
/// The size of the pages.
|
||||
pub size: Size,
|
||||
/// The layout node that produces the actual pages.
|
||||
/// The layout node that produces the actual pages (typically a [stack]).
|
||||
///
|
||||
/// [stack]: struct.Stack.html
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
impl Pages {
|
||||
/// Layout the page run.
|
||||
pub async fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
|
||||
let constraints = LayoutConstraints {
|
||||
spaces: vec![LayoutSpace { base: self.size, size: self.size }],
|
||||
repeat: true,
|
||||
};
|
||||
|
||||
self.child
|
||||
.layout(ctx, constraints)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|item| match item {
|
||||
Layouted::Spacing(_) => None,
|
||||
Layouted::Box(layout, _) => Some(layout),
|
||||
})
|
||||
.collect()
|
||||
let areas = Areas::repeat(self.size);
|
||||
let layouted = self.child.layout(ctx, &areas).await;
|
||||
layouted.into_iter().filter_map(Layouted::into_boxed).collect()
|
||||
}
|
||||
}
|
||||
|
@ -4,34 +4,25 @@ use crate::geom::Linear;
|
||||
/// A node that can fix its child's width and height.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Fixed {
|
||||
/// The fixed width, if any.
|
||||
pub width: Option<Linear>,
|
||||
/// The fixed height, if any.
|
||||
pub height: Option<Linear>,
|
||||
/// The child node whose size to fix.
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Layout for Fixed {
|
||||
async fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
constraints: LayoutConstraints,
|
||||
) -> Vec<Layouted> {
|
||||
let space = constraints.spaces[0];
|
||||
async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> {
|
||||
let Area { rem, full } = areas.current;
|
||||
let size = Size::new(
|
||||
self.width
|
||||
.map(|w| w.eval(space.base.width))
|
||||
.unwrap_or(space.size.width),
|
||||
self.height
|
||||
.map(|h| h.eval(space.base.height))
|
||||
.unwrap_or(space.size.height),
|
||||
self.width.map(|w| w.eval(full.width)).unwrap_or(rem.width),
|
||||
self.height.map(|h| h.eval(full.height)).unwrap_or(rem.height),
|
||||
);
|
||||
|
||||
self.child
|
||||
.layout(ctx, LayoutConstraints {
|
||||
spaces: vec![LayoutSpace { base: size, size }],
|
||||
repeat: false,
|
||||
})
|
||||
.await
|
||||
let areas = Areas::once(size);
|
||||
self.child.layout(ctx, &areas).await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,11 +51,86 @@ pub trait Layout {
|
||||
/// constraints: LayoutConstraints,
|
||||
/// ) -> Vec<LayoutItem>;
|
||||
/// ```
|
||||
async fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
constraints: LayoutConstraints,
|
||||
) -> Vec<Layouted>;
|
||||
async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted>;
|
||||
}
|
||||
|
||||
/// A sequence of areas to layout into.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Areas {
|
||||
/// The current area.
|
||||
pub current: Area,
|
||||
/// The backlog of followup areas.
|
||||
///
|
||||
/// _Note_: This works stack-like and not queue-like!
|
||||
pub backlog: Vec<Size>,
|
||||
/// The last area that is repeated when the backlog is empty.
|
||||
pub last: Option<Size>,
|
||||
}
|
||||
|
||||
impl Areas {
|
||||
/// Create a new length-1 sequence of areas with just one `area`.
|
||||
pub fn once(size: Size) -> Self {
|
||||
Self {
|
||||
current: Area::new(size),
|
||||
backlog: vec![],
|
||||
last: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new sequence of areas that repeats `area` indefinitely.
|
||||
pub fn repeat(size: Size) -> Self {
|
||||
Self {
|
||||
current: Area::new(size),
|
||||
backlog: vec![],
|
||||
last: Some(size),
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance to the next area if there is any.
|
||||
pub fn next(&mut self) {
|
||||
if let Some(size) = self.backlog.pop().or(self.last) {
|
||||
self.current = Area::new(size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `current` is a fully sized (untouched) copy of the last area.
|
||||
///
|
||||
/// If this is false calling `next()` will have no effect.
|
||||
pub fn in_full_last(&self) -> bool {
|
||||
self.backlog.is_empty() && self.last.map_or(true, |size| self.current.rem == size)
|
||||
}
|
||||
}
|
||||
|
||||
/// The area into which content can be laid out.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Area {
|
||||
/// The remaining size of this area.
|
||||
pub rem: Size,
|
||||
/// The full size this area once had (used for relative sizing).
|
||||
pub full: Size,
|
||||
}
|
||||
|
||||
impl Area {
|
||||
/// Create a new area.
|
||||
pub fn new(size: Size) -> Self {
|
||||
Self { rem: size, full: size }
|
||||
}
|
||||
}
|
||||
|
||||
/// How to determine a container's size along an axis.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Expansion {
|
||||
/// Fit the content.
|
||||
Fit,
|
||||
/// Fill the available space.
|
||||
Fill,
|
||||
}
|
||||
|
||||
impl Expansion {
|
||||
/// Returns `Fill` if the condition is true and `Fit` otherwise.
|
||||
pub fn fill_if(condition: bool) -> Self {
|
||||
if condition { Self::Fill } else { Self::Fit }
|
||||
}
|
||||
}
|
||||
|
||||
/// An item that is produced by [layouting] a node.
|
||||
@ -65,27 +140,18 @@ pub trait Layout {
|
||||
pub enum Layouted {
|
||||
/// Spacing that should be added to the parent.
|
||||
Spacing(Length),
|
||||
/// A box that should be aligned in the parent.
|
||||
Box(BoxLayout, Gen<Align>),
|
||||
/// A box that should be added to and aligned in the parent.
|
||||
Boxed(BoxLayout, Gen<Align>),
|
||||
}
|
||||
|
||||
/// The constraints for layouting a single node.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LayoutConstraints {
|
||||
/// The spaces to layout into.
|
||||
pub spaces: Vec<LayoutSpace>,
|
||||
/// Whether to spill over into copies of the last space or finish layouting
|
||||
/// when the last space is used up.
|
||||
pub repeat: bool,
|
||||
}
|
||||
|
||||
/// The space into which content is laid out.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct LayoutSpace {
|
||||
/// The full size of this container (the base for relative sizes).
|
||||
pub base: Size,
|
||||
/// The maximum size of the rectangle to layout into.
|
||||
pub size: Size,
|
||||
impl Layouted {
|
||||
/// Return the box if this if its a box variant.
|
||||
pub fn into_boxed(self) -> Option<BoxLayout> {
|
||||
match self {
|
||||
Self::Spacing(_) => None,
|
||||
Self::Boxed(boxed, _) => Some(boxed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A finished box with content at fixed positions.
|
||||
|
@ -18,12 +18,23 @@ pub enum LayoutNode {
|
||||
}
|
||||
|
||||
impl LayoutNode {
|
||||
/// Create a new model node form a type implementing `DynNode`.
|
||||
/// Create a new dynamic node.
|
||||
pub fn dynamic<T: DynNode>(inner: T) -> Self {
|
||||
Self::Dyn(Dynamic::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Layout for LayoutNode {
|
||||
async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> {
|
||||
match self {
|
||||
Self::Spacing(spacing) => spacing.layout(ctx, areas).await,
|
||||
Self::Text(text) => text.layout(ctx, areas).await,
|
||||
Self::Dyn(boxed) => boxed.layout(ctx, areas).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for LayoutNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
@ -34,21 +45,6 @@ impl Debug for LayoutNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Layout for LayoutNode {
|
||||
async fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
constraints: LayoutConstraints,
|
||||
) -> Vec<Layouted> {
|
||||
match self {
|
||||
Self::Spacing(spacing) => spacing.layout(ctx, constraints).await,
|
||||
Self::Text(text) => text.layout(ctx, constraints).await,
|
||||
Self::Dyn(boxed) => boxed.layout(ctx, constraints).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a boxed dynamic node.
|
||||
///
|
||||
/// _Note_: This is needed because the compiler can't `derive(PartialEq)` for
|
||||
@ -80,6 +76,12 @@ impl Debug for Dynamic {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Dynamic> for LayoutNode {
|
||||
fn from(dynamic: Dynamic) -> Self {
|
||||
Self::Dyn(dynamic)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Dynamic {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.dyn_clone())
|
||||
@ -92,12 +94,6 @@ impl PartialEq for Dynamic {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Dynamic> for LayoutNode {
|
||||
fn from(dynamic: Dynamic) -> Self {
|
||||
Self::Dyn(dynamic)
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic node, which can implement custom layouting behaviour.
|
||||
///
|
||||
/// This trait just combines the requirements for types to qualify as dynamic
|
||||
|
@ -4,45 +4,40 @@ use crate::geom::Linear;
|
||||
/// A node that pads its child at the sides.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Pad {
|
||||
/// The amount of padding.
|
||||
pub padding: Sides<Linear>,
|
||||
/// The child node whose sides to pad.
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Layout for Pad {
|
||||
async fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
constraints: LayoutConstraints,
|
||||
) -> Vec<Layouted> {
|
||||
self.child
|
||||
.layout(ctx, LayoutConstraints {
|
||||
spaces: constraints
|
||||
.spaces
|
||||
.into_iter()
|
||||
.map(|space| LayoutSpace {
|
||||
base: space.base - self.padding.eval(space.base).size(),
|
||||
size: space.size - self.padding.eval(space.size).size(),
|
||||
})
|
||||
.collect(),
|
||||
repeat: constraints.repeat,
|
||||
})
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
Layouted::Box(boxed, align) => {
|
||||
let padding = self.padding.eval(boxed.size);
|
||||
let padded = boxed.size + padding.size();
|
||||
async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> {
|
||||
let shrink = |size| size - self.padding.eval(size).size();
|
||||
let areas = Areas {
|
||||
current: Area {
|
||||
rem: shrink(areas.current.rem),
|
||||
full: shrink(areas.current.full),
|
||||
},
|
||||
backlog: areas.backlog.iter().copied().map(shrink).collect(),
|
||||
last: areas.last.map(shrink),
|
||||
};
|
||||
|
||||
let mut outer = BoxLayout::new(padded);
|
||||
let start = Point::new(padding.left, padding.top);
|
||||
outer.push_layout(start, boxed);
|
||||
let mut layouted = self.child.layout(ctx, &areas).await;
|
||||
|
||||
Layouted::Box(outer, align)
|
||||
for item in &mut layouted {
|
||||
if let Layouted::Boxed(boxed, _) = item {
|
||||
let padding = self.padding.eval(boxed.size);
|
||||
let origin = Point::new(padding.left, padding.top);
|
||||
|
||||
boxed.size += padding.size();
|
||||
for (point, _) in &mut boxed.elements {
|
||||
*point += origin;
|
||||
}
|
||||
item => item,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
layouted
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,16 +2,21 @@ use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A node that inserts spacing.
|
||||
/// A spacing node.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct Spacing {
|
||||
/// The amount of spacing to insert.
|
||||
pub amount: Length,
|
||||
/// Spacing interaction, see [Softness's] documentation for more
|
||||
/// information.
|
||||
///
|
||||
/// [Softness's]: enum.Softness.html
|
||||
pub softness: Softness,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Layout for Spacing {
|
||||
async fn layout(&self, _: &mut LayoutContext, _: LayoutConstraints) -> Vec<Layouted> {
|
||||
async fn layout(&self, _: &mut LayoutContext, _: &Areas) -> Vec<Layouted> {
|
||||
vec![Layouted::Spacing(self.amount)]
|
||||
}
|
||||
}
|
||||
|
@ -9,32 +9,36 @@ use crate::shaping;
|
||||
/// A text node.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Text {
|
||||
/// The text.
|
||||
pub text: String,
|
||||
pub size: Length,
|
||||
/// The font size.
|
||||
pub font_size: Length,
|
||||
/// The text direction.
|
||||
pub dir: Dir,
|
||||
/// The families used for font fallback.
|
||||
pub families: Rc<FallbackTree>,
|
||||
/// The font variant,
|
||||
pub variant: FontVariant,
|
||||
/// How to align this text node in its parent.
|
||||
pub aligns: Gen<Align>,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Layout for Text {
|
||||
async fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
_constraints: LayoutConstraints,
|
||||
) -> Vec<Layouted> {
|
||||
async fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Vec<Layouted> {
|
||||
let mut loader = ctx.loader.borrow_mut();
|
||||
let boxed = shaping::shape(
|
||||
&self.text,
|
||||
self.size,
|
||||
self.dir,
|
||||
&mut loader,
|
||||
&self.families,
|
||||
self.variant,
|
||||
)
|
||||
.await;
|
||||
vec![Layouted::Box(boxed, self.aligns)]
|
||||
vec![Layouted::Boxed(
|
||||
shaping::shape(
|
||||
&mut loader,
|
||||
&self.text,
|
||||
self.font_size,
|
||||
self.dir,
|
||||
&self.families,
|
||||
self.variant,
|
||||
)
|
||||
.await,
|
||||
self.aligns,
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::geom::Linear;
|
||||
use crate::layout::{Fixed, Stack};
|
||||
use crate::layout::{Expansion, Fixed, Stack};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// `box`: Layouts its contents into a box.
|
||||
@ -20,9 +20,7 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||
|
||||
ctx.start_group(());
|
||||
ctx.start_par_group();
|
||||
|
||||
body.eval(ctx);
|
||||
|
||||
ctx.end_par_group();
|
||||
let ((), children) = ctx.end_group();
|
||||
|
||||
@ -33,7 +31,11 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||
dirs,
|
||||
children,
|
||||
aligns,
|
||||
expand: Spec::new(width.is_some(), height.is_some()),
|
||||
expansion: Spec::new(
|
||||
Expansion::fill_if(width.is_some()),
|
||||
Expansion::fill_if(height.is_some()),
|
||||
)
|
||||
.switch(dirs),
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
use std::mem;
|
||||
|
||||
use crate::geom::{Length, Linear};
|
||||
use crate::paper::{Paper, PaperClass};
|
||||
use crate::prelude::*;
|
||||
@ -56,7 +54,7 @@ pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||
|
||||
if args.get::<_, bool>(ctx, "flip").unwrap_or(false) {
|
||||
let size = &mut ctx.state.page.size;
|
||||
mem::swap(&mut size.width, &mut size.height);
|
||||
std::mem::swap(&mut size.width, &mut size.height);
|
||||
}
|
||||
|
||||
args.done(ctx);
|
||||
|
@ -22,11 +22,11 @@ pub struct Shaped {
|
||||
pub face: FaceId,
|
||||
/// The shaped glyphs.
|
||||
pub glyphs: Vec<GlyphId>,
|
||||
/// The horizontal offsets of the glyphs. This is indexed parallel to `glyphs`.
|
||||
/// Vertical offets are not yet supported.
|
||||
/// The horizontal offsets of the glyphs. This is indexed parallel to
|
||||
/// `glyphs`. Vertical offets are not yet supported.
|
||||
pub offsets: Vec<Length>,
|
||||
/// The font size.
|
||||
pub size: Length,
|
||||
pub font_size: Length,
|
||||
}
|
||||
|
||||
impl Shaped {
|
||||
@ -37,7 +37,7 @@ impl Shaped {
|
||||
face,
|
||||
glyphs: vec![],
|
||||
offsets: vec![],
|
||||
size,
|
||||
font_size: size,
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,15 +62,15 @@ impl Debug for Shaped {
|
||||
///
|
||||
/// [`Shaped`]: struct.Shaped.html
|
||||
pub async fn shape(
|
||||
text: &str,
|
||||
size: Length,
|
||||
dir: Dir,
|
||||
loader: &mut FontLoader,
|
||||
text: &str,
|
||||
font_size: Length,
|
||||
dir: Dir,
|
||||
fallback: &FallbackTree,
|
||||
variant: FontVariant,
|
||||
) -> BoxLayout {
|
||||
let mut layout = BoxLayout::new(Size::new(Length::ZERO, size));
|
||||
let mut shaped = Shaped::new(FaceId::MAX, size);
|
||||
let mut layout = BoxLayout::new(Size::new(Length::ZERO, font_size));
|
||||
let mut shaped = Shaped::new(FaceId::MAX, font_size);
|
||||
let mut offset = Length::ZERO;
|
||||
|
||||
// Create an iterator with conditional direction.
|
||||
@ -86,7 +86,7 @@ pub async fn shape(
|
||||
let query = FaceQuery { fallback: fallback.iter(), variant, c };
|
||||
if let Some((id, owned_face)) = loader.query(query).await {
|
||||
let face = owned_face.get();
|
||||
let (glyph, width) = match lookup_glyph(face, c, size) {
|
||||
let (glyph, width) = match lookup_glyph(face, c, font_size) {
|
||||
Some(v) => v,
|
||||
None => continue,
|
||||
};
|
||||
@ -96,7 +96,7 @@ pub async fn shape(
|
||||
let pos = Point::new(layout.size.width, Length::ZERO);
|
||||
layout.push(pos, LayoutElement::Text(shaped));
|
||||
layout.size.width += offset;
|
||||
shaped = Shaped::new(FaceId::MAX, size);
|
||||
shaped = Shaped::new(FaceId::MAX, font_size);
|
||||
offset = Length::ZERO;
|
||||
}
|
||||
|
||||
|
@ -198,9 +198,9 @@ fn render_shaped(
|
||||
let path = builder.0.finish();
|
||||
|
||||
let units_per_em = face.units_per_em().unwrap_or(1000);
|
||||
let s = scale * (shaped.size / units_per_em as f64);
|
||||
let s = scale * (shaped.font_size / units_per_em as f64);
|
||||
let x = pos.x + scale * offset;
|
||||
let y = pos.y + scale * shaped.size;
|
||||
let y = pos.y + scale * shaped.font_size;
|
||||
|
||||
let t = Transform::create_scale(s.to_pt() as f32, -s.to_pt() as f32)
|
||||
.post_translate(Vector::new(x.to_pt() as f32, y.to_pt() as f32));
|
||||
|
Loading…
x
Reference in New Issue
Block a user