Better value representations, type function 🌐

This commit is contained in:
Laurenz 2021-01-04 21:29:15 +01:00
parent 77c06ebc24
commit 2e77b1c836
16 changed files with 227 additions and 173 deletions

View File

@ -1,6 +1,6 @@
//! Color handling.
use std::fmt::{self, Debug, Formatter};
use std::fmt::{self, Debug, Display, Formatter};
use std::str::FromStr;
/// A color in a dynamic format.
@ -10,10 +10,18 @@ pub enum Color {
Rgba(RgbaColor),
}
impl Display for Color {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Rgba(c) => Display::fmt(c, f),
}
}
}
impl Debug for Color {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Rgba(c) => c.fmt(f),
Self::Rgba(c) => Debug::fmt(c, f),
}
}
}
@ -76,6 +84,16 @@ impl FromStr for RgbaColor {
}
}
impl Display for RgbaColor {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?;
if self.a != 255 {
write!(f, "{:02x}", self.a)?;
}
Ok(())
}
}
impl Debug for RgbaColor {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if f.alternate() {
@ -83,14 +101,10 @@ impl Debug for RgbaColor {
f,
"rgba({:02}, {:02}, {:02}, {:02})",
self.r, self.g, self.b, self.a,
)?;
)
} else {
write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?;
if self.a != 255 {
write!(f, "{:02x}", self.a)?;
}
Display::fmt(self, f)
}
Ok(())
}
}

View File

@ -119,15 +119,17 @@ impl EvalContext {
if !children.is_empty() || keep_empty(group.softness) {
self.runs.push(NodePages {
size: group.size,
child: Node::any(NodePad {
child: NodePad {
padding: group.padding,
child: Node::any(NodeStack {
child: NodeStack {
dirs: group.dirs,
align: group.align,
expansion: Gen::uniform(Expansion::Fill),
children,
}),
}),
}
.into(),
}
.into(),
})
}
group.softness

View File

@ -128,7 +128,22 @@ impl StateFont {
impl Default for StateFont {
fn default() -> Self {
Self {
families: Rc::new(default_font_families()),
/// The default tree of font fallbacks.
families: Rc::new(fallback! {
list: ["sans-serif"],
classes: {
"serif" => ["source serif pro", "noto serif"],
"sans-serif" => ["source sans pro", "noto sans"],
"monospace" => ["source code pro", "noto sans mono"],
},
base: [
"source sans pro",
"noto sans",
"segoe ui emoji",
"noto emoji",
"latin modern math",
],
}),
variant: FontVariant {
style: FontStyle::Normal,
weight: FontWeight::REGULAR,
@ -141,22 +156,3 @@ impl Default for StateFont {
}
}
}
/// The default tree of font fallbacks.
fn default_font_families() -> FallbackTree {
fallback! {
list: ["sans-serif"],
classes: {
"serif" => ["source serif pro", "noto serif"],
"sans-serif" => ["source sans pro", "noto sans"],
"monospace" => ["source code pro", "noto sans mono"],
},
base: [
"source sans pro",
"noto sans",
"segoe ui emoji",
"noto emoji",
"latin modern math",
],
}
}

View File

@ -1,6 +1,6 @@
use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Deref;
use std::rc::Rc;
@ -45,14 +45,6 @@ pub enum Value {
}
impl Value {
/// Create a new dynamic value.
pub fn any<T>(any: T) -> Self
where
T: Type + Debug + Clone + PartialEq + 'static,
{
Self::Any(ValueAny::new(any))
}
/// Try to cast the value into a specific type.
pub fn cast<T>(self) -> CastResult<T, Self>
where
@ -68,8 +60,8 @@ impl Value {
Self::Bool(_) => bool::TYPE_NAME,
Self::Int(_) => i64::TYPE_NAME,
Self::Float(_) => f64::TYPE_NAME,
Self::Relative(_) => Relative::TYPE_NAME,
Self::Length(_) => Length::TYPE_NAME,
Self::Relative(_) => Relative::TYPE_NAME,
Self::Linear(_) => Linear::TYPE_NAME,
Self::Color(_) => Color::TYPE_NAME,
Self::Str(_) => String::TYPE_NAME,
@ -88,16 +80,24 @@ impl Eval for &Value {
/// Evaluate everything contained in this value.
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match self {
// Don't print out none values.
Value::None => {}
// Pass through.
Value::Content(tree) => tree.eval(ctx),
// Format with debug.
val => ctx.push(ctx.make_text_node(format!("{:?}", val))),
}
ctx.push(ctx.make_text_node(match self {
Value::None => return,
Value::Bool(v) => v.to_string(),
Value::Int(v) => v.to_string(),
Value::Float(v) => v.to_string(),
Value::Length(v) => v.to_string(),
Value::Relative(v) => v.to_string(),
Value::Linear(v) => v.to_string(),
Value::Color(v) => v.to_string(),
Value::Str(v) => v.clone(),
// TODO: Find good representation for composite types.
Value::Array(_v) => "(array)".into(),
Value::Dict(_v) => "(dictionary)".into(),
Value::Content(tree) => return tree.eval(ctx),
Value::Func(v) => v.to_string(),
Value::Any(v) => v.to_string(),
Value::Error => "(error)".into(),
}));
}
}
@ -110,21 +110,21 @@ impl Default for Value {
impl Debug for Value {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::None => f.pad("none"),
Self::Bool(v) => v.fmt(f),
Self::Int(v) => v.fmt(f),
Self::Float(v) => v.fmt(f),
Self::Length(v) => v.fmt(f),
Self::Relative(v) => v.fmt(f),
Self::Linear(v) => v.fmt(f),
Self::Color(v) => v.fmt(f),
Self::Str(v) => v.fmt(f),
Self::Array(v) => v.fmt(f),
Self::Dict(v) => v.fmt(f),
Self::Content(v) => v.fmt(f),
Self::Func(v) => v.fmt(f),
Self::Any(v) => v.fmt(f),
Self::Error => f.pad("<error>"),
Self::None => f.pad("None"),
Self::Bool(v) => Debug::fmt(v, f),
Self::Int(v) => Debug::fmt(v, f),
Self::Float(v) => Debug::fmt(v, f),
Self::Length(v) => Debug::fmt(v, f),
Self::Relative(v) => Debug::fmt(v, f),
Self::Linear(v) => Debug::fmt(v, f),
Self::Color(v) => Debug::fmt(v, f),
Self::Str(v) => Debug::fmt(v, f),
Self::Array(v) => Debug::fmt(v, f),
Self::Dict(v) => Debug::fmt(v, f),
Self::Content(v) => Debug::fmt(v, f),
Self::Func(v) => Debug::fmt(v, f),
Self::Any(v) => Debug::fmt(v, f),
Self::Error => f.pad("Error"),
}
}
}
@ -140,15 +140,18 @@ pub type ValueContent = Tree;
/// A wrapper around a reference-counted executable function.
#[derive(Clone)]
pub struct ValueFunc(Rc<dyn Fn(&mut EvalContext, &mut Args) -> Value>);
pub struct ValueFunc {
name: String,
f: Rc<dyn Fn(&mut EvalContext, &mut Args) -> Value>,
}
impl ValueFunc {
/// Create a new function value from a rust function or closure.
pub fn new<F>(func: F) -> Self
pub fn new<F>(name: impl Into<String>, f: F) -> Self
where
F: Fn(&mut EvalContext, &mut Args) -> Value + 'static,
{
Self(Rc::new(func))
Self { name: name.into(), f: Rc::new(f) }
}
}
@ -162,13 +165,18 @@ impl Deref for ValueFunc {
type Target = dyn Fn(&mut EvalContext, &mut Args) -> Value;
fn deref(&self) -> &Self::Target {
self.0.as_ref()
self.f.as_ref()
}
}
impl Display for ValueFunc {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "<function {}>", self.name)
}
}
impl Debug for ValueFunc {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad("<function>")
Display::fmt(self, f)
}
}
@ -179,7 +187,7 @@ impl ValueAny {
/// Create a new instance from any value that satisifies the required bounds.
pub fn new<T>(any: T) -> Self
where
T: Type + Debug + Clone + PartialEq + 'static,
T: Type + Debug + Display + Clone + PartialEq + 'static,
{
Self(Box::new(any))
}
@ -221,13 +229,19 @@ impl PartialEq for ValueAny {
}
}
impl Debug for ValueAny {
impl Display for ValueAny {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.0.fmt(f)
Display::fmt(&self.0, f)
}
}
trait Bounds: Debug + 'static {
impl Debug for ValueAny {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
trait Bounds: Debug + Display + 'static {
fn as_any(&self) -> &dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
fn dyn_eq(&self, other: &ValueAny) -> bool;
@ -237,7 +251,7 @@ trait Bounds: Debug + 'static {
impl<T> Bounds for T
where
T: Type + Debug + Clone + PartialEq + 'static,
T: Type + Debug + Display + Clone + PartialEq + 'static,
{
fn as_any(&self) -> &dyn Any {
self
@ -304,6 +318,16 @@ impl<T, V> CastResult<T, V> {
}
}
impl Type for Value {
const TYPE_NAME: &'static str = "value";
}
impl Cast<Value> for Value {
fn cast(value: Value) -> CastResult<Self, Value> {
CastResult::Ok(value)
}
}
impl<T> Cast<Spanned<Value>> for T
where
T: Cast<Value>,
@ -390,15 +414,6 @@ impl From<&str> for Value {
}
}
impl<F> From<F> for Value
where
F: Fn(&mut EvalContext, &mut Args) -> Value + 'static,
{
fn from(func: F) -> Self {
Self::Func(ValueFunc::new(func))
}
}
impl From<ValueAny> for Value {
fn from(v: ValueAny) -> Self {
Self::Any(v)
@ -407,9 +422,8 @@ impl From<ValueAny> for Value {
/// Make a type usable as a [`Value`].
///
/// Given a type `T`, this implements the following traits:
/// Given a type `T`, this always implements the following traits:
/// - [`Type`] for `T`,
/// - [`From<T>`](From) for [`Value`],
/// - [`Cast<Value>`](Cast) for `T`.
#[macro_export]
macro_rules! impl_type {
@ -423,12 +437,6 @@ macro_rules! impl_type {
const TYPE_NAME: &'static str = $type_name;
}
impl From<$type> for $crate::eval::Value {
fn from(any: $type) -> Self {
$crate::eval::Value::any(any)
}
}
impl $crate::eval::Cast<$crate::eval::Value> for $type {
fn cast(
value: $crate::eval::Value,

View File

@ -90,7 +90,7 @@ impl Display for Length {
} else {
(self.to_cm(), Unit::Cm)
};
write!(f, "{:.2}{}", val, unit)
write!(f, "{}{}", (val * 100.0).round() / 100.0, unit)
}
}
@ -214,8 +214,8 @@ mod tests {
#[test]
fn test_length_formatting() {
assert_eq!(Length::pt(-28.34).to_string(), "-1.00cm".to_string());
assert_eq!(Length::pt(23.0).to_string(), "23.00pt".to_string());
assert_eq!(Length::pt(-28.34).to_string(), "-1cm".to_string());
assert_eq!(Length::pt(23.0).to_string(), "23pt".to_string());
assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string());
}
}

View File

@ -32,7 +32,7 @@ impl Relative {
impl Display for Relative {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:.2}%", self.0)
write!(f, "{}%", (self.0 * 10000.0).round() / 100.0)
}
}

View File

@ -25,8 +25,8 @@ impl Layout for NodeFixed {
}
}
impl From<NodeFixed> for Node {
impl From<NodeFixed> for NodeAny {
fn from(fixed: NodeFixed) -> Self {
Self::any(fixed)
Self::new(fixed)
}
}

View File

@ -14,16 +14,6 @@ pub enum Node {
Any(NodeAny),
}
impl Node {
/// Create a new dynamic node.
pub fn any<T>(any: T) -> Self
where
T: Layout + Debug + Clone + PartialEq + 'static,
{
Self::Any(NodeAny::new(any))
}
}
impl Layout for Node {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
match self {
@ -81,9 +71,12 @@ impl Debug for NodeAny {
}
}
impl From<NodeAny> for Node {
fn from(dynamic: NodeAny) -> Self {
Self::Any(dynamic)
impl<T> From<T> for Node
where
T: Into<NodeAny>,
{
fn from(t: T) -> Self {
Self::Any(t.into())
}
}

View File

@ -29,9 +29,9 @@ impl Layout for NodePad {
}
}
impl From<NodePad> for Node {
impl From<NodePad> for NodeAny {
fn from(pad: NodePad) -> Self {
Self::any(pad)
Self::new(pad)
}
}

View File

@ -36,9 +36,9 @@ impl Layout for NodePar {
}
}
impl From<NodePar> for Node {
impl From<NodePar> for NodeAny {
fn from(par: NodePar) -> Self {
Self::any(par)
Self::new(par)
}
}

View File

@ -34,9 +34,9 @@ impl Layout for NodeStack {
}
}
impl From<NodeStack> for Node {
impl From<NodeStack> for NodeAny {
fn from(stack: NodeStack) -> Self {
Self::any(stack)
Self::new(stack)
}
}

16
src/library/extend.rs Normal file
View File

@ -0,0 +1,16 @@
use crate::prelude::*;
/// `type`: Find out the name of a value's type.
///
/// # Positional arguments
/// - Any value.
///
/// # Return value
/// The name of the value's type as a string.
pub fn type_(ctx: &mut EvalContext, args: &mut Args) -> Value {
if let Some(value) = args.require::<Value>(ctx, "value") {
value.type_name().into()
} else {
Value::Error
}
}

View File

@ -83,8 +83,8 @@ impl Layout for NodeImage {
}
}
impl From<NodeImage> for Node {
impl From<NodeImage> for NodeAny {
fn from(image: NodeImage) -> Self {
Self::any(image)
Self::new(image)
}
}

View File

@ -1,3 +1,5 @@
use std::fmt::{self, Display, Formatter};
use crate::eval::Softness;
use crate::layout::{Expansion, NodeFixed, NodeSpacing, NodeStack};
use crate::paper::{Paper, PaperClass};
@ -153,7 +155,19 @@ impl Switch for Alignment {
}
impl_type! {
Alignment: "alignment"
Alignment: "alignment",
}
impl Display for Alignment {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Left => "left",
Self::Center => "center",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
})
}
}
/// `box`: Layout content into a box.
@ -161,7 +175,7 @@ impl_type! {
/// # Named arguments
/// - Width of the box: `width`, of type `linear` relative to parent width.
/// - Height of the box: `height`, of type `linear` relative to parent height.
pub fn boxed(ctx: &mut EvalContext, args: &mut Args) -> Value {
pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value {
let snapshot = ctx.state.clone();
let width = args.get(ctx, "width");
@ -189,7 +203,7 @@ pub fn boxed(ctx: &mut EvalContext, args: &mut Args) -> Value {
ctx.push(NodeFixed {
width,
height,
child: Node::any(NodeStack { dirs, align, expansion, children }),
child: NodeStack { dirs, align, expansion, children }.into(),
});
ctx.state = snapshot;

View File

@ -1,67 +1,78 @@
//! The standard library.
mod extend;
mod insert;
mod layout;
mod style;
pub use extend::*;
pub use insert::*;
pub use layout::*;
pub use style::*;
use fontdock::{FontStretch, FontStyle, FontWeight};
use crate::eval::Scope;
use crate::eval::{Scope, ValueAny, ValueFunc};
use crate::geom::Dir;
/// The scope containing the standard library.
pub fn _std() -> Scope {
let mut std = Scope::new();
macro_rules! set {
(func: $name:expr, $func:expr) => {
std.set($name, ValueFunc::new($name, $func))
};
(any: $var:expr, $any:expr) => {
std.set($var, ValueAny::new($any))
};
}
// Functions.
std.set("align", align);
std.set("box", boxed);
std.set("font", font);
std.set("h", h);
std.set("image", image);
std.set("page", page);
std.set("pagebreak", pagebreak);
std.set("rgb", rgb);
std.set("v", v);
set!(func: "align", align);
set!(func: "box", box_);
set!(func: "font", font);
set!(func: "h", h);
set!(func: "image", image);
set!(func: "page", page);
set!(func: "pagebreak", pagebreak);
set!(func: "rgb", rgb);
set!(func: "type", type_);
set!(func: "v", v);
// Constants.
std.set("left", Alignment::Left);
std.set("center", Alignment::Center);
std.set("right", Alignment::Right);
std.set("top", Alignment::Top);
std.set("bottom", Alignment::Bottom);
std.set("ltr", Dir::LTR);
std.set("rtl", Dir::RTL);
std.set("ttb", Dir::TTB);
std.set("btt", Dir::BTT);
std.set("serif", FontFamily::Serif);
std.set("sans-serif", FontFamily::SansSerif);
std.set("monospace", FontFamily::Monospace);
std.set("normal", FontStyle::Normal);
std.set("italic", FontStyle::Italic);
std.set("oblique", FontStyle::Oblique);
std.set("thin", FontWeight::THIN);
std.set("extralight", FontWeight::EXTRALIGHT);
std.set("light", FontWeight::LIGHT);
std.set("regular", FontWeight::REGULAR);
std.set("medium", FontWeight::MEDIUM);
std.set("semibold", FontWeight::SEMIBOLD);
std.set("bold", FontWeight::BOLD);
std.set("extrabold", FontWeight::EXTRABOLD);
std.set("black", FontWeight::BLACK);
std.set("ultra-condensed", FontStretch::UltraCondensed);
std.set("extra-condensed", FontStretch::ExtraCondensed);
std.set("condensed", FontStretch::Condensed);
std.set("semi-condensed", FontStretch::SemiCondensed);
std.set("normal", FontStretch::Normal);
std.set("semi-expanded", FontStretch::SemiExpanded);
std.set("expanded", FontStretch::Expanded);
std.set("extra-expanded", FontStretch::ExtraExpanded);
std.set("ultra-expanded", FontStretch::UltraExpanded);
set!(any: "left", Alignment::Left);
set!(any: "center", Alignment::Center);
set!(any: "right", Alignment::Right);
set!(any: "top", Alignment::Top);
set!(any: "bottom", Alignment::Bottom);
set!(any: "ltr", Dir::LTR);
set!(any: "rtl", Dir::RTL);
set!(any: "ttb", Dir::TTB);
set!(any: "btt", Dir::BTT);
set!(any: "serif", FontFamily::Serif);
set!(any: "sans-serif", FontFamily::SansSerif);
set!(any: "monospace", FontFamily::Monospace);
set!(any: "normal", FontStyle::Normal);
set!(any: "italic", FontStyle::Italic);
set!(any: "oblique", FontStyle::Oblique);
set!(any: "thin", FontWeight::THIN);
set!(any: "extralight", FontWeight::EXTRALIGHT);
set!(any: "light", FontWeight::LIGHT);
set!(any: "regular", FontWeight::REGULAR);
set!(any: "medium", FontWeight::MEDIUM);
set!(any: "semibold", FontWeight::SEMIBOLD);
set!(any: "bold", FontWeight::BOLD);
set!(any: "extrabold", FontWeight::EXTRABOLD);
set!(any: "black", FontWeight::BLACK);
set!(any: "ultra-condensed", FontStretch::UltraCondensed);
set!(any: "extra-condensed", FontStretch::ExtraCondensed);
set!(any: "condensed", FontStretch::Condensed);
set!(any: "semi-condensed", FontStretch::SemiCondensed);
set!(any: "normal", FontStretch::Normal);
set!(any: "semi-expanded", FontStretch::SemiExpanded);
set!(any: "expanded", FontStretch::Expanded);
set!(any: "extra-expanded", FontStretch::ExtraExpanded);
set!(any: "ultra-expanded", FontStretch::UltraExpanded);
std
}

View File

@ -269,14 +269,14 @@ impl Location {
}
}
impl Debug for Location {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl Display for Location {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
impl Debug for Location {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}