Skip to content

Commit 5e2f541

Browse files
pergamon:0.3.2 (#3172)
1 parent 0910999 commit 5e2f541

File tree

13 files changed

+3653
-0
lines changed

13 files changed

+3653
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Alexander Koller
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Pergamon: BibLaTeX-style bibliographies for Typst
2+
3+
Pergamon is a package for typesetting bibliographies in Typst.
4+
It is inspired by [BibLaTeX](https://ctan.org/pkg/biblatex), in that
5+
the way in which it typesets bibliographies can be easily customized
6+
through Typst code. Like Typst's regular bibliography management model,
7+
Pergamon can be configured to use different styles for typesetting
8+
references and citations; unlike it, these styles are all defined through
9+
Typst code, rather than CSL.
10+
11+
Pergamon is documented in the [user guide](https://github.com/alexanderkoller/pergamon/blob/main/docs/pergamon-0.3.2.pdf).
12+
See a somewhat complex example: [Typst](https://github.com/alexanderkoller/pergamon/blob/main/example.typ), [PDF](https://github.com/alexanderkoller/pergamon/blob/main/example.pdf).
13+
14+
Pergamon has a number of advantages over the builtin Typst bibliographies:
15+
16+
- Pergamon styles are simply pieces of Typst code and can be easily configured or modified.
17+
- The document can be easily split into different `refsection`s, each of which can have its own bibliography
18+
(similar to [Alexandria](https://typst.app/universe/package/alexandria/)).
19+
- Paper titles can be automatically made into hyperlinks - as in [blinky](https://typst.app/universe/package/blinky/), but much more flexibly and correctly.
20+
- Bibliographies can be filtered, and bibliography entries programmatically highlighted, which is useful e.g. for CVs.
21+
- References retain nonstandard Bibtex fields ([unlike in Hayagriva](https://github.com/typst/hayagriva/issues/240)),
22+
making it e.g. possible to split bibliographies based on keywords.
23+
24+
At the same time, Pergamon is very new and has a number of important limitations compared to
25+
the builtin system. I have implemented those parts of Pergamon that I need for my own writing,
26+
but I would welcome your pull request to make it more feature-complete.
27+
28+
- Pergamon currently supports only bibliographies in Bibtex format, not the Hayagriva YAML format.
29+
- Only a handful of styles are supported at this point, in contrast to the large number of available CSL styles. Pergamon comes with implementations of the BibLaTeX styles `numeric`, `alphabetic`, and `authoryear`.
30+
- Pergamon still requires a lot of testing and tweaking.
31+
32+
[Pergamon](https://en.wikipedia.org/wiki/Pergamon) was an ancient Greek city state in Asia Minor.
33+
Its library was second only to the Library of Alexandria around 200 BC.
34+
35+
36+
37+
## Example
38+
39+
The following piece of code typesets a bibliography using Pergamon.
40+
41+
```typ
42+
#import "@preview/pergamon:0.3.2": *
43+
44+
#let style = format-citation-numeric()
45+
46+
#add-bib-resource(read("bibliography.bib"))
47+
48+
#refsection(format-citation: style.format-citation)[
49+
... some text here ...
50+
#cite("bender20:_climb_nlu")
51+
52+
#print-bibliography(
53+
format-reference: format-reference(reference-label: style.reference-label),
54+
label-generator: style.label-generator)
55+
]
56+
```
57+
58+
It generates citations and a bibliography that look like this:
59+
60+
<img src="https://github.com/alexanderkoller/pergamon/blob/main/docs/materials/example-output.png" style='border:1px solid #000000' />
61+
62+
You can try out [a more complex example](https://github.com/alexanderkoller/pergamon/blob/main/example.typ) yourself;
63+
here's [the PDF it generates](https://github.com/alexanderkoller/pergamon/blob/main/example.pdf).
64+
65+
## Documentation
66+
67+
Please see the [Pergamon guide](https://github.com/alexanderkoller/pergamon/blob/main/docs/pergamon-0.3.2.pdf) for more details.
68+
69+
## Contributors
70+
71+
- [@ironupiwada](https://www.github.com/ironupiwada) contributed code for date parsing in 0.2.0.
72+
73+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
#import "src/bibtypst.typ": add-bib-resource, refsection, print-bibliography, if-citation, cite, citet, citep, citen, citeg, citename, citeyear, count-bib-entries
3+
#import "src/bibtypst-styles.typ": format-citation-authoryear, format-citation-alphabetic, format-citation-numeric, format-reference
4+
#import "src/names.typ": family-names
5+
#import "src/bib-util.typ": fd, ifdef, nn
6+
#import "src/bibstrings.typ": default-bibstring
7+
#import "src/content-to-string.typ": content-to-string
8+
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
2+
3+
4+
#let matches-completely(s, re) = {
5+
let result = s.match(re)
6+
7+
if result == none {
8+
return false
9+
} else {
10+
// [#result]
11+
result.start == 0 and result.end == s.len()
12+
}
13+
}
14+
15+
16+
// Checks whether a string can be converted into an int
17+
#let is-integer(s) = {
18+
// s = s.trim()
19+
20+
// TODO: allow negative numbers at some point
21+
matches-completely(s.trim(), regex("\d+"))
22+
}
23+
24+
25+
// Concatenate an array of strings ("A", "B", "C") into "A, B, and C".
26+
#let concatenate-list(names, options) = {
27+
let ret = names.at(0)
28+
29+
for i in range(1, names.len()) {
30+
if type(names.at(i)) != dictionary { // no idea how it would be a dictionary
31+
if names.len() == 2 {
32+
ret = ret + options.list-end-delim-two + names.at(i)
33+
} else if i == names.len()-1 {
34+
ret = ret + options.list-end-delim-many + names.at(i)
35+
} else {
36+
ret = ret + options.list-middle-delim + names.at(i)
37+
}
38+
}
39+
}
40+
41+
ret
42+
}
43+
44+
45+
// Map "modern" Biblatex field names to legacy field names as they
46+
// might appear in the bib file. Should be complete, as per biblatex.def
47+
#let field-aliases = (
48+
"journaltitle": "journal",
49+
"langid": "hyphenation",
50+
"location": "address",
51+
"institution": "school",
52+
"annotation": "annote",
53+
"eprinttype": "archiveprefix",
54+
"eprintclass": "primaryclass",
55+
"sortkey": "key",
56+
"file": "pdf"
57+
)
58+
59+
60+
// Map legacy Bibtex entry types to their "modern" Biblatex names.
61+
#let type-aliases = (
62+
"conference": reference => { reference.insert("entry_type", "inproceedings"); return reference },
63+
"electronic": reference => { reference.insert("entry_type", "online"); return reference },
64+
"www": reference => { reference.insert("entry_type", "online"); return reference },
65+
"mastersthesis": reference => {
66+
reference.insert("entry_type", "thesis")
67+
if not "type" in reference.fields {
68+
reference.fields.insert("type", "mathesis")
69+
}
70+
return reference
71+
},
72+
"phdthesis": reference => {
73+
reference.insert("entry_type", "thesis")
74+
if not "type" in reference.fields {
75+
reference.fields.insert("type", "phdthesis")
76+
}
77+
return reference
78+
},
79+
"techreport": reference => {
80+
reference.insert("entry_type", "report")
81+
reference.fields.insert("type", "techreport")
82+
return reference
83+
},
84+
)
85+
86+
87+
88+
#let fd(reference, field, options, format: x => x) = {
89+
let legacy-field = field-aliases.at(field, default: "dummy-field-name")
90+
91+
if field in options.at("suppressed-fields", default: ()) {
92+
return none
93+
} else if field in reference.fields {
94+
return format(reference.fields.at(field))
95+
} else if legacy-field in reference.fields {
96+
return format(reference.fields.at(legacy-field))
97+
} else {
98+
return none
99+
}
100+
}
101+
102+
103+
#let ifdef(reference, field, options, fn) = {
104+
let value = fd(reference, field, options)
105+
106+
if value == none { none } else { fn(value) }
107+
}
108+
109+
// Convert an array of (key, value) pairs into a "multimap":
110+
// a dictionary in which each key is assigned to an array of all
111+
// the values with which it appeared.
112+
//
113+
// Example: (("a", 1), ("a", 2), ("b", 3)) -> (a: (1, 2), b: (3,))
114+
#let collect-deduplicate(pairs) = {
115+
let ret = (:)
116+
117+
for (key, value) in pairs {
118+
if key in ret {
119+
ret.at(key).push(value)
120+
} else {
121+
ret.insert(key, (value,))
122+
}
123+
}
124+
125+
return ret
126+
}
127+
128+
129+
/// Wraps a function in `none`-handling code.
130+
/// `nn(func)` is a function that
131+
/// behaves like `func` on arguments that are not `none`,
132+
/// but if the argument is `none`, it simply returns `none`.
133+
/// Only works for functions `func` that have a single argument.
134+
/// -> function
135+
#let nn(func) = {
136+
it => if it == none { none } else { func(it) }
137+
}
138+
139+
140+

0 commit comments

Comments
 (0)