///|
// The title page (asciidoctor-pdf's `ink_title_page`), styled by the
// theme's `title_page_*` keys.
///|
/// asciidoctor-pdf's `resolve_top`: a position down the page from the
/// cursor: `vh` of the page height (from its top edge), `%` of the content
/// area's height, or a length.
fn Flow::resolve_top(self : Flow, value : @theme.Value) -> Double {
let s = value.to_ruby_s()
if s.has_suffix("vh") {
page_height() * (@theme.ruby_string_to_f(s) / 100.0)
} else if s.has_suffix("%") {
let setup = page_setup.val
let effective = setup.height - setup.margin_top - setup.margin_bottom
self.y + effective * (@theme.ruby_string_to_f(s) / 100.0)
} else {
self.y + @theme.str_to_pt(s)
}
}
///|
/// asciidoctor-pdf's `with_author`: `body` with the document's author
/// attributes (`author`, `authorinitials`, `firstname`, `middlename`,
/// `lastname`, `email`) set to those of `author` (the primary author's are
/// the document's own) and `url` to its email (`mailto:` when it has an
/// `@`); restored afterwards.
fn[T] with_author(
doc : @core.Node,
author : @core.Author,
primary : Bool,
body : () -> T,
) -> T {
let original_url = doc.attr("url")
if original_url is Some(_) {
ignore(doc.remove_attr("url"))
}
let url_of = (email : String) => {
if email.contains("@") {
"mailto:\{email}"
} else {
email
}
}
let mut email : String? = None
let result = if primary {
email = doc.attr("email")
match email {
Some(e) => ignore(doc.set_attr("url", value=Str(url_of(e))))
None => ()
}
body()
} else {
let names = [
("author", author.name),
("authorinitials", author.initials),
("firstname", author.firstname),
("middlename", author.middlename),
("lastname", author.lastname),
("email", author.email),
]
let saved = names.map(n => (n.0, doc.attr(n.0)))
for entry in names {
match entry.1 {
Some(v) => ignore(doc.set_attr(entry.0, value=Str(v)))
None => ignore(doc.remove_attr(entry.0))
}
}
email = author.email
match email {
Some(e) => ignore(doc.set_attr("url", value=Str(url_of(e))))
None => ()
}
let result = body()
for s in saved {
match s.1 {
Some(v) => ignore(doc.set_attr(s.0, value=Str(v)))
None => ignore(doc.remove_attr(s.0))
}
}
result
}
match original_url {
Some(url) => ignore(doc.set_attr("url", value=Str(url)))
None => if email is Some(_) { ignore(doc.remove_attr("url")) }
}
result
}
///|
/// The title page logo (the start of `ink_title_page`): `title-logo-image`,
/// else the theme's `title_page_logo_image`, a block image pinned to the
/// title page (from `top`, else `title_page_logo_top`, down the page),
/// aligned by its `align`, else `title_page_logo_align`, else the title
/// page's alignment. An image macro from the document resolves against the
/// images directory, a bare path against the base directory; a value from
/// the theme is substituted and resolved against the theme's directory.
fn Converter::ink_title_page_logo(
self : Converter,
title_align : Alignment,
) -> Unit {
let doc = self.doc
let flow = self.flow
if t_str("title_page_logo_display") == Some("none") {
return
}
let (value, from_theme) = match doc.attr("title-logo-image") {
Some(v) => (v, false)
None =>
match t_str("title_page_logo_image") {
Some(v) => (v, true)
None => return
}
}
let themesdir = current_theme.val.dir()
let from_theme_path = (path : String) => {
let path = with_attrs(doc, [("imagesdir", themesdir)], () => {
apply_subs_discretely(doc, path)
})
if path.has_prefix("/") || @core.is_uriish(path) {
path
} else {
@theme.resolve_theme_asset(theme_files(doc), path, theme_dir=themesdir)
}
}
let attrs = @core.Attributes::new()
let (path, relative_to_imagesdir) = match image_macro(value) {
Some((target, attrlist)) => {
attrs.update(
@core.parse_attribute_list(attrlist, positional_attrs=[
Some("alt"),
Some("width"),
Some("height"),
]),
)
if from_theme {
(from_theme_path(target), false)
} else {
(target, true)
}
}
None => (if from_theme { from_theme_path(value) } else { value }, false)
}
if image_target_and_format(path, attrs_str(attrs, "format")).1 == "pdf" {
report(
"ERROR " + "PDF format not supported for title page logo image: \{path}",
)
return
}
attrs.set_str("target", path)
let valid = (a : String?) => a is Some("left" | "center" | "right")
let align = attrs_str(attrs, "align")
ignore(attrs.remove("align"))
let align = if valid(align) {
align
} else if valid(t_str("title_page_logo_align")) {
t_str("title_page_logo_align")
} else {
Some(
match title_align {
Center => "center"
Right => "right"
_ => "left"
},
)
}
match align {
Some(a) => attrs.set_str("align", a)
None => ()
}
let initial_y = flow.y
let top = match attrs.get("top") {
Some(Str(t)) => Some(@theme.Value::Str(t))
_ =>
match tv("title_page_logo_top") {
Null => None
v => Some(v)
}
}
match top {
Some(t) => flow.y = flow.resolve_top(t)
None => ()
}
let logo = @core.Node::new_block(
doc,
Image,
content_model=Empty,
attributes=attrs,
)
self.pinned = true
flow.indent(
t_num_or("title_page_logo_margin_left", 0.0),
t_num_or("title_page_logo_margin_right", 0.0),
() => self.convert_image(logo, relative_to_imagesdir~),
)
self.pinned = false
if top is Some(_) {
flow.y = initial_y
}
}
///|
/// `title_page__display: none` hides a part.
fn title_page_shows(part : String) -> Bool {
t_str("title_page_\{part}_display") != Some("none")
}
///|
/// Ink one part of the title page (`title`, `subtitle`, `authors`,
/// `revision`) in its theme font, between its margins.
fn Converter::ink_title_page_part(
self : Converter,
part : String,
text : String,
base : Style,
line_height : Double,
align : Alignment,
normalize? : Bool = true,
) -> Unit {
let flow = self.flow
let key = "title_page_\{part}"
flow.move_down(t_num_or(key + "_margin_top", 0.0))
flow.indent(
t_num_or(key + "_margin_left", 0.0),
t_num_or(key + "_margin_right", 0.0),
() => {
let (style, line_height) = theme_font(base, line_height, key)
self.ink_prose(
text,
style~,
align~,
margin_bottom=0.0,
line_height~,
normalize~,
)
},
)
flow.move_down(t_num_or(key + "_margin_bottom", 0.0))
}
///|
/// The title page of a book (asciidoctor-pdf's `ink_title_page`): the
/// title from `title_page_title_top` down the page, then the subtitle,
/// authors and revision, each in its `title_page_*` font, aligned by
/// `title_page_text_align`.
fn Converter::ink_title_page(self : Converter) -> Unit {
let doc = self.doc
let flow = self.flow
let align = t_align("title_page_text_align", self.text_align)
self.ink_title_page_logo(align)
let (base, line_height) = theme_font(
base_style(),
base_line_height(),
"title_page",
)
match tv("title_page_title_top") {
Null => ()
top => flow.y = flow.resolve_top(top)
}
let (main, subtitle) = match doc.doctitle_partitioned() {
Some(t) => (t.main, t.subtitle)
None => (doc.doctitle().unwrap_or(""), None)
}
if title_page_shows("title") {
self.ink_title_page_part("title", main, base, line_height, align)
}
match subtitle {
Some(sub) if title_page_shows("subtitle") =>
self.ink_title_page_part("subtitle", sub, base, line_height, align)
_ => ()
}
let authors = doc.authors()
if title_page_shows("authors") && doc.has_attr("authors") {
// each author by `title_page_authors_content` (`_name_only`,
// `_with_email`, `_with_url` by the author's url), else the name
let generic = t_str("title_page_authors_content")
let content = (key : String) => {
t_str("title_page_authors_content_\{key}").or_some(generic)
}
let themesdir = current_theme.val.dir()
let texts = authors.mapi((index, author) => {
with_author(doc, author, index == 0, () => {
let key = match doc.attr("url") {
Some(url) if url.has_prefix("mailto:") => "with_email"
Some(_) => "with_url"
None => "name_only"
}
match content(key) {
Some(c) =>
with_attrs(doc, [("imagesdir", themesdir)], () => {
apply_subs_discretely(doc, c)
})
None => doc.attr("author").unwrap_or("")
}
})
})
let delimiter = t_str("title_page_authors_delimiter").unwrap_or(", ")
self.ink_title_page_part(
"authors",
texts.join(delimiter),
base,
line_height,
align,
)
}
let revision = []
match doc.attr("revnumber") {
Some(n) =>
revision.push("\{doc.attr("version-label").unwrap_or("Version")} \{n}")
None => ()
}
match doc.attr("revdate") {
Some(d) => revision.push(d)
None => ()
}
if title_page_shows("revision") && !revision.is_empty() {
let delimiter = t_str("title_page_revision_delimiter").unwrap_or(", ")
let text = match doc.attr("revremark") {
Some(remark) => "\{revision.join(delimiter)}: \{remark}"
None => revision.join(delimiter)
}
self.ink_title_page_part(
"revision",
text,
base,
line_height,
align,
normalize=false,
)
}
}