-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathlib.typ
More file actions
271 lines (260 loc) · 9.96 KB
/
lib.typ
File metadata and controls
271 lines (260 loc) · 9.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#import "@preview/cetz:0.4.2"
#import "src/default.typ": default
#import "src/utils/utils.typ"
#import "src/drawer.typ"
#import "src/drawer.typ": skeletize, draw-skeleton, skeletize-config, draw-skeleton-config, hide-drawables
#import "src/elements/links.typ": *
#import "src/elements/fragment.typ": *
#import "src/elements/lewis.typ": *
#let transparent = color.rgb(100%, 0, 0, 0)
#let name = "alchemist"
/// === Fragment function
/// Build a fragment group based on mol
/// Each fragment is represented as an optional count followed by a fragment name
/// starting by a capital letter followed by an optional exponent followed by an optional indice.
/// #example(```
/// #skeletize({
/// fragment("H_2O")
/// })
///```)
/// #example(```
/// #skeletize({
/// fragment("H^A_EF^5_4")
/// })
/// ```)
/// It is possible to use an equation as a fragment. In this case, the splitting of the equation uses the same rules as in the string case. However, you get some advantages over the string version:
/// - You can use parenthesis to group elements together.
/// - You have no restriction about what you can put in exponent or indice.
/// #example(```
/// #skeletize({
/// fragment($C(C H_3)_3$)
/// })
///```)
/// - name (content): The name of the fragment. It is used as the cetz name of the fragment and to link other fragments to it.
/// - links (dictionary): The links between this fragment and previous fragments or hooks. The key is the name of the fragment or hook and the value is the link function. See @links.
///
/// Note that the atom-sep and angle arguments are ignored
/// - lewis (list): The list of lewis structures to draw around the fragments. See @lewis
/// - mol (string, equation): The string representing the fragment or an equation of the fragment
/// - vertical (boolean): If true, the fragment is drawn vertically
/// #example(```
/// #skeletize({
/// fragment("ABCD", vertical: true)
/// })
///```)
/// - colors (color|list): The color of the fragment. If a list is provided, it colors each group of the fragment with the corresponding color from right to left. If the number of colors is less than the number of groups, the last color is used for the remaining groups. If the number of colors is greater than the number of groups, the extra colors are ignored.
/// #example(```
/// #skeletize({
/// fragment("ABCD", colors: (red, green, blue))
/// single()
/// fragment("EFGH", colors: (orange))
/// })
/// ```)
/// -> drawable
#let fragment(name: none, links: (:), lewis: (), vertical: false, colors: none, mol) = {
let atoms = if type(mol) == str {
split-string(mol)
} else if mol.func() == math.equation {
split-equation(mol, equation: true)
} else {
panic("Invalid fragment content")
}
if type(lewis) != array {
panic("Lewis formulae elements must be in a list")
}
(
(
type: "fragment",
name: name,
atoms: atoms,
colors: colors,
links: links,
lewis: lewis,
vertical: vertical,
count: atoms.len(),
),
)
}
#let molecule(name: none, links: (:), lewis: (), vertical: false, mol) = fragment(name: name, links: links, lewis: lewis, vertical: vertical, mol)
/// === Hooks
/// Create a hook in the fragment. It allows to connect links to the place where the hook is.
/// Hooks are placed at the end of links or at the beginning of the fragment.
/// - name (string): The name of the hook
/// -> drawable
#let hook(name) = {
(
(
type: "hook",
name: name,
),
)
}
/// === Branch and cycles
/// Create a branch from the current fragment, the first element
/// of the branch has to be a link.
///
/// You can specify an angle argument like for links. This angle will be then
/// used as the `base-angle` for the branch.
///
/// #example(```
/// #skeletize({
/// fragment("A")
/// branch({
/// single(angle:1)
/// fragment("B")
/// })
/// branch({
/// double(angle: -1)
/// fragment("D")
/// })
/// single()
/// double()
/// single()
/// fragment("C")
/// })
///```)
/// - body (drawable): the body of the branch. It must start with a link.
/// -> drawable
#let branch(body, ..args) = {
if args.pos().len() != 0 {
panic("Branch takes one positional argument: the body of the branch")
}
((type: "branch", body: body, args: args.named()),)
}
/// Create a regular cycle of fragments
/// You can specify an angle argument like for links. This angle will be then
/// the angle of the first link of the cycle.
///
/// The argument `align` can be used to force align the cycle according to the
/// relative angle of the previous link.
///
/// #example(```
/// #skeletize({
/// cycle(5, {
/// single()
/// double()
/// single()
/// double()
/// single()
/// })
/// })
///```)
/// - faces (int): the number of faces of the cycle
/// - body (drawable): the body of the cycle. It must start and end with a fragment or a link.
/// -> drawable
#let cycle(faces, body, ..args) = {
if args.pos().len() != 0 {
panic("Cycle takes two positional arguments: number of faces and body")
}
if faces < 3 {
panic("A cycle must have at least 3 faces")
}
(
(
type: "cycle",
faces: faces,
body: body,
args: args.named(),
),
)
}
/// === Parenthesis
/// Encapsulate a drawable between two parenthesis. The left parenthesis is placed at the left of the first element of the body and by default the right parenthesis is placed at the right of the last element of the body.
///
/// #example(```
/// #skeletize(
/// config: (
/// angle-increment: 30deg
/// ), {
/// parenthesis(
/// l:"[", r:"]",
/// br: $n$, {
/// single(angle: 1)
/// single(angle: -1)
/// single(angle: 1)
/// })
/// })
/// ```)
/// For more examples, see @examples
///
/// - body (drawable): the body of the parenthesis. It must start and end with a fragment or a link.
/// - l (string): the left parenthesis
/// - r (string): the right parenthesis
/// - align (true): if true, the parenthesis will have the same y position. They will also get sized and aligned according to the body height. If false, they are not aligned and the height argument must be specified.
/// - resonance (boolean): if true, the parenthesis will be drawn in resonance mode. This means that the left and right parenthesis will be placed outside the molecule. Also, the parenthesis will be separated from the previous and next molecule. This can be true only if the parenthesis is the first element of the skeletal formula or if the previous element is an operator. See @resonance for more details.
/// - height (float, length): the height of the parenthesis. If align is true, this argument is optional.
/// - yoffset (float, length, list): the vertical offset of parenthesis. You can also provide a tuple for left and right parenthesis
/// - xoffset (float, length, list): the horizontal offset of parenthesis. You can also provide a tuple for left and right parenthesis
/// - right (string): Sometime, it is not convenient to place the right parenthesis at the end of the body. In this case, you can specify the name of the fragment or link where the right parenthesis should be placed. It is especially useful when the body end by a cycle. See @polySulfide
/// - tr (content): the exponent content of the right parenthesis
/// - br (content): the indice content of the right parenthesis
/// -> drawable
#let parenthesis(body, l: "(", r: ")", align: true, resonance: false, height: none, yoffset: none, xoffset: none, right: none, tr: none, br: none) = {
if l.len() > 2 {
panic("Left can be at most 2 characters")
}
let l = eval(l, mode: "math")
if r.len() > 2 {
panic("Right can be at most 2 characters")
}
let r = eval(r, mode: "math")
(
(
type: "parenthesis",
body: body,
calc: calc,
l: l,
r: r,
align: align,
tr: tr,
br: br,
height: height,
xoffset: xoffset,
yoffset: yoffset,
right: right,
resonance: resonance,
),
)
}
/// === Operator
/// Create an operator between two fragments. Creating an operator "reset" the placement of the next fragment.
/// This allow to add multiple molecules in the same skeletal formula. Without this, the next fragment would be placed at the end of the previous one.
/// An important point is that you can't use previous hooks to link two molecules separate by an operator.
/// This element is used in resonance structures (@resonance) and in some cases to put multiples molecules in the same skeletal formula (as you can set op to none).
///
/// - op (content | string | none): The operator content. It can be a string or a content. A none value won't display anything.
/// #example(```
/// #skeletize({
/// fragment("A")
/// operator($->$, margin: 1em)
/// fragment("B")
/// })
/// ```)
/// See @resonance for more examples.
/// - name (string): The name of the operator.
/// - margin (float, length): The margin between the operator and previous / next molecule.
/// -> drawable
#let operator(name: none, margin: 1em, op) = {
(
(
type: "operator",
name: name,
op: op,
margin: margin,
),
)
}
/// === Hiding part of the molecule
/// This element allows to hide part of the molecule. It can be used to hide the part of the molecule that is not relevant for the current discussion. It can also be used to create some animation effects by hiding and showing different parts of the molecule. Note that the hidden part is still present in the drawing and can be linked to other fragments. This means that you can hide a part of the molecule and still link it to other fragments or hooks. The hidden part is also still present in the cetz record, which means that you can still use it in the cetz drawing. The only thing that is hidden is the drawing of the hidden part.
/// - bounds (boolean): If true, the hidden part keep the same bounding box as if it was not hidden. If false, the hidden part doesn't take any space in the drawing.
/// - body (drawable): The body to hide. It can be any drawable.
/// -> drawable
#let hide(bounds: true, body) = {
(
(
type: "hide",
bounds: bounds,
body: body,
),
)
}