-
-
Notifications
You must be signed in to change notification settings - Fork 818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add fields and methods to several primitives #790
Conversation
By the way, I wonder in which |
There are a few stupid track_callers in the code base for some reason. We only need it if the handler panics, not if it returns a string. |
Ohhh alright, thanks for the insight. I might be updating this PR soon with some changes, and I'll remove the unneeded track_callers from my code lol. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one thing I'm overall also not sure about is fields vs methods. I feel like most of these should be methods. the thing I can see being fields the most is color
and thickness
. maybe the policy could be which are conversion vs. which are just accessors. the color
field has the additional problem that it won't necessarily be a color anymore once we have gradients.
src/geom/length.rs
Outdated
|
||
/// Get a field from this length. | ||
pub fn at(&self, field: &str) -> StrResult<Value> { | ||
let round_four_digits = |n| ((n as f64) * 1e4).round() / 1e4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was because floating point imprecision was causing issues - 3cm wouldn't be equal to (3cm).cm
because the latter would return something like 3.000000004
. However, I think it would be better if we turned .cm
and other conversions into methods where you can specify the precision.
src/eval/value.rs
Outdated
d @ Self::Dyn(dynamic) => { | ||
if let Some(stroke) = dynamic.downcast::<PartialStroke>() { | ||
stroke.at(field) | ||
} else { | ||
Err(eco_format!("cannot access fields on type {}", d.type_name())) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
d @ Self::Dyn(dynamic) => { | |
if let Some(stroke) = dynamic.downcast::<PartialStroke>() { | |
stroke.at(field) | |
} else { | |
Err(eco_format!("cannot access fields on type {}", d.type_name())) | |
} | |
} | |
Self::Dyn(dynamic) => { | |
if let Some(stroke) = dynamic.downcast::<PartialStroke>() { | |
stroke.at(field) | |
} else { | |
Err(eco_format!("cannot access fields on type {}", dynamic.type_name())) | |
} | |
} |
src/geom/length.rs
Outdated
#[cold] | ||
#[track_caller] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#[cold] | |
#[track_caller] |
src/geom/paint.rs
Outdated
@@ -1,3 +1,5 @@ | |||
use crate::eval::{Array, Str}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crate imports should be below, together with the super
src/geom/paint.rs
Outdated
"cmyk" => match self { | ||
Self::Cmyk(cmyk) => cmyk.to_array().into(), | ||
Self::Luma(luma) => luma.to_cmyk().to_array().into(), | ||
_ => Value::None, // no rgba -> cmyk conversion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure about just returning none
here to be honest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was worried because you wouldn't be able to prevent an error if the color type were unexpected (note that I'm basically trying to avoid many usages of repr
here)... Perhaps we can add a color-type
method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^As an update to this specifically: I moved this to a method to-cmyk()
, which now errors on the previously "None" case. In order to be able to prepare for the error, I added the .kind
field method to colors (though I might also move that to a method, but not 100% sure yet). (EDIT: .kind()
is now a method.)
Yes, for sure! Thanks for the feedback. Indeed, I actually intend to change most of these to methods; I mostly wanted to see how far I could get with those attributes and stuff
Makes sense to me.
Well, I guess the implementation here will depend on how we handle gradients. Perhaps have some sort of |
102fa05
to
b79121d
Compare
2ac6fa7
to
17ba178
Compare
Alright, so, updated the branch to latest In the last few commits, it's worth saying that I made a few sorta significant changes. In particular, I created To be honest, I'd normally prefer some sort of trait system to be able to have value types implement their fields by themselves, though I acknowledge this would be hard to do (perhaps in a future PR or something, if it ever happens - maybe if the 'methods-as-fields' idea goes forward). So this should work. |
Regarding colors, I made the following changes: added the Though, I feel like returning the CMYK ratios is hacky, as obtaining the original ratios given is not 100% possible. I think we should just accept both ratios and ints as valid inputs to Finally, we should probably decide on some documentation format for type fields, preferably similar to their methods. Feel free to send suggestions; I will try to take a look on this later (not too worried about this right now). |
Great to see work on this resuming. If there are many fields, an extra module certainly makes sense although I share your desire for having this more trait based in the future. One big question I have: Didn't we want to make most of these methods? |
Yes! Slowly working on that, haha (sorry, forgot to mention this explicitly in my update). Currently I have only done this for colors so far: Though, I'm guessing that Either way, I'll be working on moving other types' stuff to methods next ;) |
dc5b1fb
to
2c74112
Compare
Alright! Today's update:
And that's mostly all for today (besides other minor changes). (Just sharing this to make it easier to stay in touch with this PR's progress.) |
|
Thanks for the valuable input!! Will work on your suggestions later today. 👍 |
@laurmaedje the PR seems to be mostly complete now. What's truly missing is documentation, and, for that, there are two blockers (I've added what I could so far though):
# Stroke
(...)
## Fields
### paint
The color that a line with this stroke would use.
- type: color
## Methods
(... none in this case so pretend this isn't here ...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the code is pretty much already ready for review though, I'll leave some comments on parts I think would be of most interest during a review. Of course, you're free to leave other comments in other parts (and also to change documentation / comments at will).
Feel free to suggest any missing tests as well.
@@ -322,6 +322,7 @@ impl Resolve for DashPattern { | |||
// https://tex.stackexchange.com/questions/45275/tikz-get-values-for-predefined-dash-patterns | |||
cast! { | |||
DashPattern, | |||
self => dict! { "array" => self.array, "phase" => self.phase }.into_value(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made dash pattern (returned via .dash
field of a stroke
) be converted to a dict with the same structure as the DashPattern
struct. I thought of maybe comparing with the values for "solid"
, "dotted"
etc. to return those strings if applicable, but I settled with this in the end (it's a simpler solution, at least, and encompasses all cases). Feel free to suggest any changes.
Note that we can't directly test this conversion yet, as you can't mutate fields for now (this would be handled by a follow-up PR), and the stroke
type only appears directly for the user through expressions like 2pt + blue
- anything more complicated (with a different dash pattern than the default none
) would be expressed by a dict, whose values the user can retrieve directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can observe it by doing something like #rect(stroke: (thickness: 1pt, dash: "solid")).stroke
. Looking at the current output from that, some Debug
impl is suboptimal (it contains Some
). I'm not super happy with the stroke setup in any case. A stroke
constructor function instead of the dictionary would maybe make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not returning strings like "solid"
is the right call. The structure should be uniform and "canoncalized" to to speak. Just like grid(columns: 2).columns == (auto, auto)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can observe it by doing something like
#rect(stroke: (thickness: 1pt, dash: "solid")).stroke
.
Oh, didn't think of that. I'll add some tests using this. Thanks!
Looking at the current output from that, some
Debug
impl is suboptimal (it containsSome
).
The following lines seem to be the case:
- Debugging
Option<DashPattern>
directly:
typst/crates/typst/src/geom/stroke.rs
Line 162 in 507efc3
write!(f, "{}dash: {:?}", sep, dash)?; |
- Deriving
Debug
forDashLength
:
typst/crates/typst/src/geom/stroke.rs
Lines 351 to 352 in 507efc3
#[derive(Debug, Clone, Eq, PartialEq, Hash)] | |
pub enum DashLength<T = Length> { |
I'll take a look at those. Perhaps Typst should get its own Debug
-like (e.g. Repr
) trait at some point though, to avoid this kind of issue (since we can't re-implement Debug
for Option
, a built-in type). But that's just a thought, and perhaps a very wrong one :p
I'm not super happy with the stroke setup in any case. A
stroke
constructor function instead of the dictionary would maybe make sense.
I agree; this could be implemented in the type rework.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree on the Repr
trait, I've actually implemented it at one point. I don't know what stopped from merging it anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, fixed debugging, and added some more tests too. 👍
let not_supported = || Err(no_fields(name)); | ||
let missing = || Err(missing_field(name, field)); | ||
|
||
// Special cases, such as module and dict, are handled by Value itself |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not handle everything here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did that initially, but it felt a bit inconsistent with the fact that fields_on
only returns a list of static fields (not dynamic like dict's fields), much like methods_on
and methods.rs
don't handle methods in a module / function scope. I can move things back here if you prefer, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leave it as-is then.
crates/typst/src/eval/methods.rs
Outdated
@@ -152,6 +179,19 @@ pub fn call( | |||
_ => return missing(), | |||
}, | |||
|
|||
Value::Length(length) => match method { | |||
"cm" => length.abs.to_cm().into_value(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think pt
should be here to and then we drop the abs
field. There is no reason why pt
should be special.
It could be confusing that this just ignores the em
part, but not much we can do about it (without future lazy magic get rules). But probably em
should then also be a method here and we drop all fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also keep the abs
field, add pt()
here and have these functions throw if em
is not 0
. then you can do length.abs.cm()
, but 2em.cm()
fails instead of giving 0.0
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also keep the
abs
field, addpt()
here and have these functions throw ifem
is not0
. then you can dolength.abs.cm()
, but2em.cm()
fails instead of giving0.0
.
This sounds more appropriate to me. Perhaps we should add this as a hint as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Make sure to check the error/hint messages and the docs for any changes you might deem necessary.
Currently Adding a new section to With my unmerged type rework, the |
Hmm. Perhaps I can just add this info in a summarized manner then in the docs for each of those "minor types". E.g. "the method
Sure, sounds good to me.
I agree it probably makes sense. I think there should still be a way to list the currently available scripting types, however, in some sort of "index", as this has been pretty useful to me. (Perhaps this idea could be extended to elements as well, in a separate page.) |
Alright, I believe I've added docs for everything now. All that's left is mostly reviewing stuff, adapting docs as needed, etc. I'll be undrafting the PR then, since the main goals were already achieved; feel free to re-draft if you feel that's appropriate, though. |
Thank you! :) |
Closes #753.
This PR adds the following fields and methods to primitive types (please feel free to suggest more):
length
: fieldsem
(getem
part),abs
(getpt
part); methodscm
(convertspt
to a floating pointcm
),mm
(same but tomm
),inches
(same but toinches
).relative length
: fieldsratio
(get ratio part) andlength
(get absolute/length
part)color
: methodskind
(the constructor used:rgba()
|cmyk()
|luma()
),rgba
(converts to array of rgba values),hex
(converts any color to a RGBA hex string prefixed by#
),cmyk
(converts a luma or cmyk color to an array of cmyk ratio values, or throws an error if it's an rgba color as there is no such conversion),luma
(returns the value of a luma color, or throws an error if it's an rgba or cmyk color)stroke
: fieldspaint
,thickness
,cap
,join
,dash
,miter-limit
direction
: now has methods.axis()
("horizontal"
for rtl and ltr, or"vertical"
for btt and ttb),.start()
(left
for ltr,right
for rtl,top
for ttb,bottom
for btt),.end()
(same idea, but inverted, e.g.right
for ltr), and.inv()
(rtl for ltr, ltr for rtl, etc.)alignment
: now has methods.axis()
("horizontal"
for left, right, start, end, center;"vertical"
for top, bottom, horizon) and.inv()
(left<->right; top<->bottom; start<->end; center<->center; horizon<->horizon)2d alignment
: now has fields.x
and.y
(they return thex
andy
components of the 2d alignment, respectively; e.g.,(left + top).x == left
and(left + top).y == top
), and method.inv()
(inverts both components; e.g.(right + bottom).inv() == left + top
, while(center + horizon).inv() == center + horizon
).angle
: now has the methodsrad
anddeg
(return a float with the conversion of this angle to radians and degrees respectively).Tests were also added. (Still missing docs for all this stuff, though.)
I'm marking this PR as draft so I can have time to add fields for the new stroke attributes (which were pushed recently - this branch isn't rebased yet); I just wanted to show what I have so far so we can discuss it properly.
Any thoughts? Any other type I should add fields for? Also, I wonder if it would be appropriate if some of these fields were converted to methods (if so, which? Maybe
cm
,mm
,inches
, and the color conversion ones?).