@airmark/quiver 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/package.json +7 -7
- package/quills/af4141/0.1.0/Quill.yaml +3 -12
- package/quills/af4141/0.1.0/example.md +53 -0
- package/quills/afmc_moa/0.0.1/Quill.yaml +29 -45
- package/quills/afmc_moa/0.0.1/example.md +114 -0
- package/quills/afmc_moa/0.0.1/packages/typst-afmc-moa/src/config.typ +1 -1
- package/quills/classic_resume/0.0.1/Quill.yaml +83 -163
- package/quills/classic_resume/0.0.1/example.md +111 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/components.typ +67 -34
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/config.typ +18 -10
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/layout.typ +36 -7
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/lib.typ +1 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/util.typ +16 -11
- package/quills/classic_resume/0.0.1/plate.typ +54 -54
- package/quills/cyber_ribbon_chart/0.0.1/Quill.yaml +120 -113
- package/quills/cyber_ribbon_chart/0.0.1/example.md +55 -0
- package/quills/cyber_ribbon_chart/0.0.1/plate.typ +506 -325
- package/quills/daf1206/0.1.0/Quill.yaml +14 -16
- package/quills/daf1206/0.1.0/example.md +55 -0
- package/quills/daf4392/0.1.0/Quill.yaml +1 -6
- package/quills/daf4392/0.1.0/example.md +51 -0
- package/quills/usaf_letter/0.1.0/Quill.yaml +7 -42
- package/quills/usaf_letter/0.1.0/example.md +35 -0
- package/quills/{usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/LICENSE → usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/SpectralSC/OFL.txt} +2 -2
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/SpectralSC/SpectralSC-Italic.ttf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/SpectralSC/SpectralSC-Regular.ttf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/frontmatter.typ +1 -1
- package/quills/usaf_letter/0.1.0/plate.typ +1 -4
- package/quills/usaf_memo/0.2.0/Quill.yaml +7 -40
- package/quills/usaf_memo/0.2.0/example.md +45 -0
- package/quills/usaf_memo/0.3.0/Quill.yaml +13 -50
- package/quills/usaf_memo/0.3.0/example.md +48 -0
- package/quills/{usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/Cinzel/LICENSE → usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/SpectralSC/OFL.txt} +2 -2
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/SpectralSC/SpectralSC-Italic.ttf +0 -0
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/SpectralSC/SpectralSC-Regular.ttf +0 -0
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/frontmatter.typ +1 -1
- package/quills/usaf_memo/0.3.0/plate.typ +1 -4
- package/templates/deros_extension.md +40 -0
- package/templates/templates.json +7 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/Cinzel/Cinzel-Regular.ttf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/CopperplateCC/CopperplateCC-Heavy.otf +0 -0
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/Cinzel-Regular.ttf +0 -0
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/CopperplateCC/CopperplateCC-Heavy.otf +0 -0
|
@@ -15,22 +15,26 @@
|
|
|
15
15
|
margin: 0.5in,
|
|
16
16
|
|
|
17
17
|
// Vertical rhythm. `leading` is used for the leading inside a paragraph and
|
|
18
|
-
// for the gap between paragraphs, blocks and list items;
|
|
19
|
-
//
|
|
20
|
-
//
|
|
18
|
+
// for the gap between paragraphs, blocks and list items; each `*-spacing`
|
|
19
|
+
// value is added to it for one wider gap: above a section header, under its
|
|
20
|
+
// rule, between two entries, and under the name and contacts. The gap under
|
|
21
|
+
// the rule is the same whether an entry, a list of items or prose follows it,
|
|
22
|
+
// and an entry or a list of items keeps it from a paragraph above it too.
|
|
21
23
|
leading: 0.5em,
|
|
22
24
|
section-spacing: 5pt,
|
|
25
|
+
rule-spacing: 3.5pt,
|
|
23
26
|
entry-spacing: 5pt,
|
|
27
|
+
header-spacing: 10pt,
|
|
24
28
|
|
|
25
|
-
// Resume header. The separator
|
|
26
|
-
//
|
|
29
|
+
// Resume header. The separator stands between two contacts on one line,
|
|
30
|
+
// never at either end of one, with a word space and this padding on each
|
|
31
|
+
// side of it.
|
|
27
32
|
name-size: 18pt,
|
|
28
33
|
contact-separator: "❖",
|
|
29
34
|
contact-separator-size: 7pt,
|
|
30
|
-
contact-separator-padding: 0.
|
|
35
|
+
contact-separator-padding: 0.25em,
|
|
31
36
|
|
|
32
|
-
// Rule drawn underneath a section header.
|
|
33
|
-
// first entry of the section comes from that entry's `entry-spacing`.
|
|
37
|
+
// Rule drawn underneath a section header.
|
|
34
38
|
rule-stroke: 0.75pt,
|
|
35
39
|
|
|
36
40
|
// Square bullet used by lists inside an entry. The negative baseline lifts
|
|
@@ -41,9 +45,13 @@
|
|
|
41
45
|
marker-indent: 0.8em,
|
|
42
46
|
|
|
43
47
|
// Right-aligned annotation of a linked entry, normally a URL. A font of
|
|
44
|
-
// `auto` keeps the body font.
|
|
48
|
+
// `auto` keeps the body font; an `em` size follows the body size.
|
|
45
49
|
annotation-font: auto,
|
|
46
|
-
annotation-size:
|
|
50
|
+
annotation-size: 0.85em,
|
|
51
|
+
|
|
52
|
+
// Name and page number over every page after the first; `false` leaves
|
|
53
|
+
// those pages bare.
|
|
54
|
+
continuation-header: true,
|
|
47
55
|
)
|
|
48
56
|
|
|
49
57
|
#let config-state = state("ttq-classic-resume:config", default-config)
|
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
|
|
4
4
|
#import "config.typ": config-state, default-config
|
|
5
5
|
|
|
6
|
+
// The name at the left and the page count at the right, over every page after
|
|
7
|
+
// the first, so a page read apart from the first still says whose it is. The
|
|
8
|
+
// name is read back off the document's level 1 heading. Small and italic, it
|
|
9
|
+
// reads as a running head: a 0.5in margin leaves no room to set it further off
|
|
10
|
+
// the page.
|
|
11
|
+
#let _continuation-header(cfg) = context {
|
|
12
|
+
if counter(page).get().first() > 1 {
|
|
13
|
+
set text(size: cfg.annotation-size, style: "italic")
|
|
14
|
+
let names = query(heading.where(level: 1))
|
|
15
|
+
if names.len() > 0 { names.first().body }
|
|
16
|
+
h(1fr)
|
|
17
|
+
counter(page).display("1 of 1", both: true)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
/// Applies the resume styling to the whole document.
|
|
7
22
|
///
|
|
8
23
|
/// Use `#show: resume` for the defaults, or `#show: resume.with(size: 11pt)`
|
|
@@ -22,11 +37,18 @@
|
|
|
22
37
|
|
|
23
38
|
let cfg = default-config + options.named()
|
|
24
39
|
|
|
25
|
-
set page(
|
|
40
|
+
set page(
|
|
41
|
+
paper: cfg.paper,
|
|
42
|
+
margin: cfg.margin,
|
|
43
|
+
header: if cfg.continuation-header { _continuation-header(cfg) },
|
|
44
|
+
)
|
|
26
45
|
set text(font: cfg.font, size: cfg.size)
|
|
27
46
|
set par(leading: cfg.leading, spacing: cfg.leading, justify: false)
|
|
28
47
|
|
|
29
|
-
// One vertical rhythm for every kind of block-level content.
|
|
48
|
+
// One vertical rhythm for every kind of block-level content. Block spacing
|
|
49
|
+
// collapses to the larger of two neighbours' gaps, so a gap wider than
|
|
50
|
+
// `leading` is set as a block's `above` or `below` rather than with `v`,
|
|
51
|
+
// which would add to it.
|
|
30
52
|
set block(above: cfg.leading, below: cfg.leading)
|
|
31
53
|
set list(spacing: cfg.leading)
|
|
32
54
|
|
|
@@ -45,11 +67,18 @@
|
|
|
45
67
|
// The name is enlarged inside the block, not on the heading itself, so that
|
|
46
68
|
// the surrounding `em` spacing keeps resolving against the body size.
|
|
47
69
|
show heading.where(level: 1): it => block(text(size: cfg.name-size, it.body))
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
70
|
+
// A heading keeps with what follows it, so the section's first line is on
|
|
71
|
+
// the page its rule is. Capitals set solid crowd each other, so the title is
|
|
72
|
+
// tracked out a little.
|
|
73
|
+
show heading.where(level: 2): it => block(
|
|
74
|
+
above: cfg.leading + cfg.section-spacing,
|
|
75
|
+
below: cfg.leading + cfg.rule-spacing,
|
|
76
|
+
sticky: true,
|
|
77
|
+
{
|
|
78
|
+
text(tracking: 0.04em, upper(it.body))
|
|
79
|
+
block(above: cfg.leading, line(length: 100%, stroke: cfg.rule-stroke))
|
|
80
|
+
},
|
|
81
|
+
)
|
|
53
82
|
|
|
54
83
|
config-state.update(cfg)
|
|
55
84
|
|
|
@@ -10,23 +10,28 @@
|
|
|
10
10
|
#let _phone = regex("^[+(\\d][\\d\\s()./+-]*\\d$")
|
|
11
11
|
#let _digit = regex("\\d")
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
/// Turns a contact into a link when it looks like an email address, a web
|
|
14
|
+
/// address or a phone number, and returns it unchanged otherwise. A city or a
|
|
15
|
+
/// clearance level is therefore left alone, and so is anything that is already
|
|
16
|
+
/// content, so callers can always pass their own `link(..)` instead.
|
|
17
|
+
///
|
|
18
|
+
/// `body` is what prints, the contact itself unless given: a caller holding the
|
|
19
|
+
/// contact as text and as content of its own reads the one and prints the
|
|
20
|
+
/// other.
|
|
21
|
+
#let auto-link(contact, body: auto) = {
|
|
22
|
+
let body = if body == auto { contact } else { body }
|
|
18
23
|
if type(contact) != str {
|
|
19
|
-
|
|
24
|
+
body
|
|
20
25
|
} else if contact.match(_email) != none {
|
|
21
|
-
link("mailto:" + contact,
|
|
26
|
+
link("mailto:" + contact, body)
|
|
22
27
|
} else if contact.match(_scheme) != none {
|
|
23
28
|
let href = if contact.starts-with("www.") { "https://" + contact } else { contact }
|
|
24
|
-
link(href,
|
|
29
|
+
link(href, body)
|
|
25
30
|
} else if contact.match(_domain) != none {
|
|
26
|
-
link("https://" + contact,
|
|
31
|
+
link("https://" + contact, body)
|
|
27
32
|
} else if contact.match(_phone) != none and contact.matches(_digit).len() >= 7 {
|
|
28
|
-
link("tel:" + contact.replace(regex("[^\\d+]"), ""),
|
|
33
|
+
link("tel:" + contact.replace(regex("[^\\d+]"), ""), body)
|
|
29
34
|
} else {
|
|
30
|
-
|
|
35
|
+
body
|
|
31
36
|
}
|
|
32
37
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#import "@local/quillmark-helper:0.1.0": data
|
|
1
|
+
#import "@local/quillmark-helper:0.1.0": data, ink
|
|
2
2
|
#import "@local/ttq-classic-resume:0.1.0": (
|
|
3
|
-
default-config, entry, item-grid, resume, resume-header, section-header,
|
|
3
|
+
auto-link, default-config, entry, item-grid, resume, resume-header, section-header,
|
|
4
4
|
)
|
|
5
5
|
|
|
6
6
|
// `plaintext`/`richtext` fields lower to content carrying a space element on
|
|
7
|
-
// each side, so a
|
|
8
|
-
//
|
|
7
|
+
// each side, so a filled one would print its padding inside a bold run or an
|
|
8
|
+
// italic one. A blank one lowers to "".
|
|
9
9
|
#let trim-inline(v) = {
|
|
10
10
|
if type(v) != content { return v }
|
|
11
11
|
let kids = v.at("children", default: none)
|
|
@@ -32,14 +32,13 @@
|
|
|
32
32
|
if type(body) == str { none } else { body }
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
35
|
+
// A `string` field is text to compute with: a contact or a project link is read
|
|
36
|
+
// to find its target, and the name reaches `set document(author:)`, which takes
|
|
37
|
+
// no content. What prints is the field's ink twin, `printed`, which keeps the
|
|
38
|
+
// click target a value computed with loses.
|
|
39
|
+
#let linked(value, printed) = {
|
|
40
|
+
if value.trim() == "" { none } else { auto-link(value, body: printed) }
|
|
41
|
+
}
|
|
43
42
|
|
|
44
43
|
#show: resume.with(
|
|
45
44
|
// An enum's blank is authorable even where the schema declares a default.
|
|
@@ -73,13 +72,13 @@
|
|
|
73
72
|
indent: 0em,
|
|
74
73
|
)
|
|
75
74
|
|
|
76
|
-
// `name` and `contacts` are read as `string` rather than `plaintext`, which
|
|
77
|
-
// lowers to content: the name reaches `set document(author:)`, which takes no
|
|
78
|
-
// content, and `link-contacts` linkifies a str while passing content through.
|
|
79
75
|
#resume-header(
|
|
80
|
-
name: data.name,
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
name: if data.name.trim() != "" { ink(data).name },
|
|
77
|
+
title: data.name.trim(),
|
|
78
|
+
author: data.name.trim(),
|
|
79
|
+
contacts: data.contacts.zip(ink(data).contacts)
|
|
80
|
+
.map(((contact, printed)) => linked(contact, printed))
|
|
81
|
+
.filter(contact => contact != none),
|
|
83
82
|
)
|
|
84
83
|
|
|
85
84
|
// The section titles are the PDF's outline, and a `details` cell cannot decline a
|
|
@@ -101,43 +100,44 @@
|
|
|
101
100
|
body: under-entry(details),
|
|
102
101
|
)
|
|
103
102
|
|
|
103
|
+
// What each declared kind sets under its heading and body. A card of a kind
|
|
104
|
+
// the quill does not declare is warned on and left off the page: its fields
|
|
105
|
+
// are not this quill's to read.
|
|
106
|
+
#let rows = (
|
|
107
|
+
summary: card => none,
|
|
108
|
+
experience: card => for job in card.jobs {
|
|
109
|
+
dated(job.company, job.dates, job.role, job.location, job.details)
|
|
110
|
+
},
|
|
111
|
+
education: card => for school in card.schools {
|
|
112
|
+
dated(school.school, school.dates, school.degree, school.location, school.details)
|
|
113
|
+
},
|
|
114
|
+
skills: card => item-grid(
|
|
115
|
+
items: card.skills.map(row => (label: trim-inline(row.label), text: trim-inline(row.items))),
|
|
116
|
+
columns: calc.max(1, card.columns),
|
|
117
|
+
),
|
|
118
|
+
projects: card => for project in card.projects {
|
|
119
|
+
entry(
|
|
120
|
+
heading: trim-inline(project.name),
|
|
121
|
+
form: "linked",
|
|
122
|
+
url: linked(project.link, ink(project).link),
|
|
123
|
+
body: under-entry(project.details),
|
|
124
|
+
)
|
|
125
|
+
},
|
|
126
|
+
certifications: card => item-grid(
|
|
127
|
+
items: card.items.map(trim-inline),
|
|
128
|
+
columns: calc.max(1, card.columns),
|
|
129
|
+
),
|
|
130
|
+
other: card => for row in card.entries {
|
|
131
|
+
dated(row.heading, row.dates, row.subtitle, row.location, row.details)
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
|
|
104
135
|
#for card in data.at("$cards") {
|
|
105
136
|
let kind = card.at("$kind", default: none)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
body-of(card)
|
|
113
|
-
|
|
114
|
-
if kind == "experience" {
|
|
115
|
-
for job in card.jobs {
|
|
116
|
-
dated(job.company, job.dates, job.role, job.location, job.details)
|
|
117
|
-
}
|
|
118
|
-
} else if kind == "education" {
|
|
119
|
-
for school in card.schools {
|
|
120
|
-
dated(school.school, school.dates, school.degree, school.location, school.details)
|
|
121
|
-
}
|
|
122
|
-
} else if kind == "other" {
|
|
123
|
-
for row in card.entries {
|
|
124
|
-
dated(row.heading, row.dates, row.subtitle, row.location, row.details)
|
|
125
|
-
}
|
|
126
|
-
} else if kind == "projects" {
|
|
127
|
-
for project in card.projects {
|
|
128
|
-
entry(
|
|
129
|
-
heading: trim-inline(project.name),
|
|
130
|
-
form: "linked",
|
|
131
|
-
url: if project.url != "" { project.url } else { none },
|
|
132
|
-
body: under-entry(project.details),
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
} else if kind == "skills" {
|
|
136
|
-
item-grid(
|
|
137
|
-
items: card.skills.map(row => (label: trim-inline(row.label), text: trim-inline(row.text))),
|
|
138
|
-
columns: calc.max(1, card.columns),
|
|
139
|
-
)
|
|
140
|
-
} else if kind == "certifications" {
|
|
141
|
-
item-grid(items: card.items.map(trim-inline), columns: calc.max(1, card.columns))
|
|
137
|
+
if type(kind) == str and kind in rows {
|
|
138
|
+
let title = or-none(card.title)
|
|
139
|
+
if title != none { section-header(title) }
|
|
140
|
+
body-of(card)
|
|
141
|
+
rows.at(kind)(card)
|
|
142
142
|
}
|
|
143
143
|
}
|