All Downloads are FREE. Search and download functionalities are using the official Maven repository.

laika.helium.config.settings.scala Maven / Gradle / Ivy

/*
 * Copyright 2012-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package laika.helium.config

import java.time.OffsetDateTime
import laika.ast.Path.Root
import laika.ast.{DocumentMetadata, Image, Length, Path, Span, TemplateElement, TemplateSpan, TemplateSpanSequence, TemplateString}
import laika.helium.Helium
import laika.rewrite.Versions
import laika.rewrite.nav.CoverImage
import laika.theme.config.{BookConfig, Color, FontDefinition}

private[helium] trait CommonSettings {
  def themeFonts: ThemeFonts
  def fontSizes: FontSizes
  def colors: ColorSet
  def metadata: DocumentMetadata
  def layout: CommonLayout
}

private[helium] trait DarkModeSupport extends CommonSettings {
  def darkMode: Option[ColorSet]
}
  
private[helium] case class SiteSettings (fontResources: Seq[FontDefinition],
                                         themeFonts: ThemeFonts,
                                         fontSizes: FontSizes,
                                         colors: ColorSet,
                                         darkMode: Option[ColorSet],
                                         layout: WebLayout,
                                         content: WebContent,
                                         metadata: DocumentMetadata,
                                         versions: Option[Versions] = None,
                                         baseURL: Option[String] = None) extends DarkModeSupport

private[helium] case class PDFSettings (bookConfig: BookConfig,
                                        themeFonts: ThemeFonts,
                                        fontSizes: FontSizes,
                                        colors: ColorSet,
                                        layout: PDFLayout,
                                        coverImages: Seq[CoverImage]) extends CommonSettings {
  val metadata: DocumentMetadata = bookConfig.metadata
}

private[helium] case class EPUBSettings (bookConfig: BookConfig,
                                         themeFonts: ThemeFonts,
                                         fontSizes: FontSizes,
                                         colors: ColorSet,
                                         darkMode: Option[ColorSet],
                                         htmlIncludes: HTMLIncludes,
                                         layout: EPUBLayout,
                                         coverImages: Seq[CoverImage]) extends DarkModeSupport {
  val metadata: DocumentMetadata = bookConfig.metadata
}

private[helium] trait CommonConfigOps {

  /** Adds one or more font resources to the theme, either based on a local classpath or file system resource,
    * or a web font URL, or both.
    *
    * E-book formats like EPUB or PDF require a local font file to be available for embedding.
    * A web font URL can only be used for website generation.
    * 
    * When using this method, all default fonts of the Helium theme will be de-registered.
    */
  def fontResources (defn: FontDefinition*): Helium

  /** Specifies which font family to use for the body text, for headlines
    * and for inline code and code blocks.
    * 
    * All specified fonts need to be made available for the theme first by using the `fontResources` method.
    * The body font needs to be available at least as regular, italic, bold and bold italic.
    * The headline font needs to support bold and bold italic.
    * For the code font a regular font weight is sufficient.
    */
  def fontFamilies (body: String, headlines: String, code: String): Helium

  /** Configure the theme's font sizes.
    * Most property names are self-explanatory, the small font is currently only used for footnote references 
    * in PDF output.
    */
  def fontSizes (body: Length,
                 code: Length,
                 title: Length,
                 header2: Length,
                 header3: Length,
                 header4: Length,
                 small: Length): Helium

  /** Configures the four main colors used by the theme.
    * 
    * @param primary      this color is used for headlines, navigation highlights and other decorative elements
    * @param primaryMedium is supposed to be a shade between primary and primaryLight and is used for borders
    *                      and text on the landing page (on dark background)
    * @param primaryLight is supposed to be a lighter shade of the primary color and is used for the background
    *                     color of sidebars and other decorated blocks; ensure that the text in the `primary` color
    *                     is readable when placed on a `primaryLight` background
    * @param secondary    this color is used for navigation headers and links
    * @param text         the color of the body text
    * @param background   the background color of the pages
    * @param bgGradient   specifies two colors to be used as background gradient, currently only used on the landing page
    */
  def themeColors (primary: Color,
                   primaryMedium: Color,
                   primaryLight: Color,
                   secondary: Color,
                   text: Color,
                   background: Color,
                   bgGradient: (Color, Color)): Helium

  /** Configures the colors of runtime messages embedded in the rendered result.
    * Warnings and errors will only be rendered if you change the configuration to visual debugging,
    * by default the presence of errors will lead to the abortion of the process.
    * See the section "Visual Debugging" on the Configuration page in the manual for details.
    * 
    * @param info          the text color for info-level messages
    * @param infoLight     the background color for info-level messages
    * @param warning       the text color for warning-level messages
    * @param warningLight  the background color for warning-level messages
    * @param error         the text color for error-level messages
    * @param errorLight    the background color for error-level messages
    * @return
    */
  def messageColors (info: Color,
                     infoLight: Color,
                     warning: Color,
                     warningLight: Color,
                     error: Color,
                     errorLight: Color): Helium

  /** Specifies two sets of five colors each to be used by Laika's support for syntax highlighting.
    *
    * If you use the built-in highlighters (which are based on Laika's own parsers) the display is based
    * on a 10-color scheme with 5 base colors which are usually grayish/low saturation and 5 "wheel" colors
    * which are usually placed around the color wheel.
    * 
    * If you use external tools like `highlight.js` these settings will have no effect and you need to use
    * the styling mechanism of that 3rd-party tool.
    */
  def syntaxHighlightingColors (base: ColorQuintet, wheel: ColorQuintet): Helium

  /** Allows to define a small set of metadata that describes the output.
    * 
    * In the generated site it will be used to populate the `` section of the HTML output,
    * for EPUB and PDF it will be used to embed the information into the generated files 
    * in a way that the respective readers understand.
    *
    * When using the sbt plugin the `title`, `description` and `version` metadata will be pre-populated by the standard
    * sbt settings `name`, `description` and `version` respectively. 
    * When using the library API no medata will be defined by default.
    * It is recommended to always define the language and title as the minimum set of metadata.
    * 
    * @param title         the title of the site and/or e-book
    * @param description   a short description of the site and/or e-book
    * @param identifier    a unique identifier for the e-book, not used for site generation
    * @param authors       one or more author names
    * @param language      the language of the site and/or e-book, should always be defined
    * @param datePublished the publication date as a UTC date-time
    * @param dateModified  the modification date as a UTC date-time
    * @param version       the version string for the output
    */
  def metadata (title: Option[String] = None,
                description: Option[String] = None,
                identifier: Option[String] = None,
                authors: Seq[String] = Nil,
                language: Option[String] = None,
                datePublished: Option[OffsetDateTime] = None,
                dateModified: Option[OffsetDateTime] = None,
                version: Option[String] = None): Helium

  /** Adds a dedicated page for a table of content, in addition to the reader-native navigation structure.
    *
    * @param title the title to display on the page and in navigation that links to the page
    * @param depth the navigation depth which may be different than the one for the reader-native navigation structure
    */
  def tableOfContent (title: String, depth: Int): Helium

}

private[helium] trait ColorOps {

  protected def currentColors: ColorSet
  protected def withColors (colors: ColorSet): Helium

  def themeColors (primary: Color,
                   primaryMedium: Color,
                   primaryLight: Color,
                   secondary: Color,
                   text: Color,
                   background: Color,
                   bgGradient: (Color, Color)): Helium = withColors(currentColors.copy(theme = ThemeColors(
    primary = primary, primaryMedium = primaryMedium, primaryLight = primaryLight,
    secondary = secondary, text = text, background = background, bgGradient = bgGradient
  )))
  def messageColors (info: Color,
                     infoLight: Color,
                     warning: Color,
                     warningLight: Color,
                     error: Color,
                     errorLight: Color): Helium = withColors(currentColors.copy(messages =
    MessageColors(info, infoLight, warning, warningLight, error, errorLight)
  ))
  def syntaxHighlightingColors (base: ColorQuintet, wheel: ColorQuintet): Helium =
    withColors(currentColors.copy(syntaxHighlighting =
      SyntaxColors(base, wheel)
    ))
}

private[helium] trait DarkModeOps extends ColorOps {
  
  def disabled: Helium
  
}

private[helium] trait SingleConfigOps extends CommonConfigOps with ColorOps {
  
  protected def currentMetadata: DocumentMetadata
  protected def withFontFamilies (fonts: ThemeFonts): Helium
  protected def withFontSizes (sizes: FontSizes): Helium
  protected def withColors (colors: ColorSet): Helium
  protected def withMetadata (metadata: DocumentMetadata): Helium

  def fontResources (defn: FontDefinition*): Helium
  def fontFamilies (body: String, headlines: String, code: String): Helium =
    withFontFamilies(ThemeFonts(body, headlines, code))
  def fontSizes (body: Length,
                 code: Length,
                 title: Length,
                 header2: Length,
                 header3: Length,
                 header4: Length,
                 small: Length): Helium =
    withFontSizes(FontSizes(body, code, title, header2, header3, header4, small))

  def metadata (title: Option[String] = None,
                description: Option[String] = None,
                identifier: Option[String] = None,
                authors: Seq[String] = Nil,
                language: Option[String] = None,
                datePublished: Option[OffsetDateTime] = None,
                dateModified: Option[OffsetDateTime] = None,
                version: Option[String] = None): Helium = {
    val current = currentMetadata
    withMetadata(DocumentMetadata(
      title.orElse(current.title), 
      description.orElse(current.description), 
      identifier.orElse(current.identifier),
      current.authors ++ authors, 
      language.orElse(current.language), 
      datePublished.orElse(current.datePublished), 
      dateModified.orElse(current.dateModified), 
      version
    ))
  }
}

private[helium] trait AllFormatsOps extends CommonConfigOps {
  protected def helium: Helium
  
  private val formats: Seq[Helium => CommonConfigOps] = Seq(_.site, _.epub, _.pdf)
  def fontResources (defn: FontDefinition*): Helium = formats.foldLeft(helium) {
    case (helium, format) => format(helium).fontResources(defn:_*)
  }
  def fontFamilies (body: String, headlines: String, code: String): Helium = formats.foldLeft(helium) {
    case (helium, format) => format(helium).fontFamilies(body, headlines, code)
  }
  def fontSizes (body: Length,
                 code: Length,
                 title: Length,
                 header2: Length,
                 header3: Length,
                 header4: Length,
                 small: Length): Helium = formats.foldLeft(helium) {
    case (helium, format) => format(helium).fontSizes(body, code, title, header2, header3, header4, small)
  }
  def themeColors (primary: Color,
                   primaryMedium: Color,
                   primaryLight: Color,
                   secondary: Color,
                   text: Color,
                   background: Color,
                   bgGradient: (Color, Color)): Helium = formats.foldLeft(helium) {
    case (helium, format) =>
      format(helium).themeColors(primary, primaryMedium, primaryLight, secondary, text, background, bgGradient)
  }
  def messageColors (info: Color,
                     infoLight: Color,
                     warning: Color,
                     warningLight: Color,
                     error: Color,
                     errorLight: Color): Helium = formats.foldLeft(helium) {
    case (helium, format) =>
      format(helium).messageColors(info, infoLight, warning, warningLight, error, errorLight)
  }
  def syntaxHighlightingColors (base: ColorQuintet, wheel: ColorQuintet): Helium = formats.foldLeft(helium) {
    case (helium, format) =>
      format(helium).syntaxHighlightingColors(base, wheel)
  }
  def metadata (title: Option[String] = None,
                description: Option[String] = None,
                identifier: Option[String] = None,
                authors: Seq[String] = Nil,
                language: Option[String] = None,
                datePublished: Option[OffsetDateTime] = None,
                dateModified: Option[OffsetDateTime] = None,
                version: Option[String] = None): Helium = formats.foldLeft(helium) {
    case (helium, format) =>
      format(helium).metadata(title, description, identifier, authors, language, datePublished, dateModified, version)
  }
  def tableOfContent (title: String, depth: Int): Helium = formats.foldLeft(helium) {
    case (helium, format) =>
      format(helium).tableOfContent(title, depth) 
  }
}

private[helium] trait CopyOps {
  protected def helium: Helium

  protected def copyWith (siteSettings: SiteSettings): Helium = new Helium(siteSettings, helium.epubSettings, helium.pdfSettings, helium.extensions)
  protected def copyWith (epubSettings: EPUBSettings): Helium = new Helium(helium.siteSettings, epubSettings, helium.pdfSettings, helium.extensions)
  protected def copyWith (pdfSettings: PDFSettings): Helium   = new Helium(helium.siteSettings, helium.epubSettings, pdfSettings, helium.extensions)
}

private[helium] trait SiteOps extends SingleConfigOps with CopyOps {
  
  protected def currentContent: WebContent = helium.siteSettings.content
  protected def currentLayout: WebLayout = helium.siteSettings.layout
  protected def currentMetadata: DocumentMetadata = helium.siteSettings.metadata
  protected def currentColors: ColorSet = helium.siteSettings.colors
  def fontResources (defn: FontDefinition*): Helium =  copyWith(helium.siteSettings.copy(fontResources = defn))
  protected def withFontFamilies (fonts: ThemeFonts): Helium = copyWith(helium.siteSettings.copy(themeFonts = fonts))
  protected def withFontSizes (sizes: FontSizes): Helium = copyWith(helium.siteSettings.copy(fontSizes = sizes))
  protected def withColors (colors: ColorSet): Helium = copyWith(helium.siteSettings.copy(colors = colors))
  protected def withMetadata (metadata: DocumentMetadata): Helium = 
    copyWith(helium.siteSettings.copy(metadata = metadata.withDefaults(helium.siteSettings.metadata)))

  /** Allows to add a second color set for dark mode.
    * The implementation is based on the `prefers-color-scheme` media query and requires browsers supporting
    * dark mode.
    */
  def darkMode: DarkModeOps = new DarkModeOps {
    def disabled: Helium = copyWith(helium.siteSettings.copy(darkMode = None))
    protected def currentColors: ColorSet = helium.siteSettings.darkMode.getOrElse(helium.siteSettings.colors)
    protected def withColors (colors: ColorSet): Helium = copyWith(helium.siteSettings.copy(darkMode = Some(colors)))
  }
  
  /** Auto-links CSS documents from the specified paths.
    * By default all CSS documents found anywhere in the input tree will be linked in HTML files.
    * This setting allows to narrow it down to one or more dedicated paths within the virtual tree,
    * which might be useful when your input contains CSS files unrelated to the pages rendered by Laika.
    */
  def autoLinkCSS (paths: Path*): Helium = {
    val newContent = currentContent.copy(htmlIncludes = currentContent.htmlIncludes.copy(includeCSS = paths))
    copyWith(helium.siteSettings.copy(content = newContent))
  }

  /** Auto-links JavaScript documents from the specified paths.
    * By default all JavaScript documents found anywhere in the input tree will be linked in HTML files.
    * This setting allows to narrow it down to one or more dedicated paths within the virtual tree,
    * which might be useful when your input contains JavaScript files unrelated to the pages rendered by Laika.
    */
  def autoLinkJS (paths: Path*): Helium = {
    val newContent = currentContent.copy(htmlIncludes = currentContent.htmlIncludes.copy(includeJS = paths))
    copyWith(helium.siteSettings.copy(content = newContent))
  }

  /** Allows to override the defaults for Helium's layout.
    * You can use the constructors found in the `LengthUnit` companion to create length values,
    * e.g. `LengthUnit.px(12)`.
    * It's usually most convenient to import `laika.ast.LengthUnit._` for your configuration code.
    *  
    * @param contentWidth        the maximum width of the main content column 
    * @param navigationWidth     the width of the left navigation sidebar
    * @param topBarHeight        the height of the top navigation bar
    * @param defaultBlockSpacing the default space between block elements
    * @param defaultLineHeight   the default line height
    * @param anchorPlacement     the placement of anchors for copying the links of section headlines (left, right or none)
    */
  def layout (contentWidth: Length = currentLayout.contentWidth,
              navigationWidth: Length = currentLayout.navigationWidth,
              topBarHeight: Length = currentLayout.topBarHeight,
              defaultBlockSpacing: Length = currentLayout.defaultBlockSpacing,
              defaultLineHeight: Double = currentLayout.defaultLineHeight,
              anchorPlacement: AnchorPlacement = currentLayout.anchorPlacement): Helium = {
    val newLayout = currentLayout.copy(
      contentWidth = contentWidth,
      navigationWidth = navigationWidth,
      topBarHeight = topBarHeight,
      defaultBlockSpacing = defaultBlockSpacing,
      defaultLineHeight = defaultLineHeight,
      anchorPlacement = anchorPlacement
    )
    copyWith(helium.siteSettings.copy(layout = newLayout))
  }

  /** Defines a footer as a sequence of AST elements.
    * 
    * A horizontal rule will be inserted by the template and should not be part of this sequence.
    * An empty parameter list will only remove the default Laika footer.
    */
  def footer (spans: Span*): Helium = {
    val footerSpan = if (spans.isEmpty) None else Some(TemplateSpanSequence.adapt(spans))
    val newContent = currentContent.copy(footer = footerSpan)
    copyWith(helium.siteSettings.copy(content = newContent))
  }

  /** Defines a footer as raw HTML output.
    * 
    * A horizontal rule will be inserted by the template and should not be part of this sequence.
    */
  def footer (html: String): Helium = footer(TemplateString(html))
  
  /** Adds one or more favicons which can be an internal resource or an external URL.
    */
  def favIcons (icons: Favicon*): Helium = {
    val newContent = currentContent.copy(favIcons = currentContent.favIcons ++ icons)
    copyWith(helium.siteSettings.copy(content = newContent))
  }

  /** Configures the main (left) navigation pane.
    * 
    * By default the navigation is auto-generated from the available sources to a navigation depth of 2.
    * This API allows to override some defaults and to prepend or append additional navigation links.
    * 
    * If the `includePageSections` parameter is set to true, the navigation structure will not only
    * reflect the directory structure and the documents within, but also the section headers on individual pages.
    * This option may be attractive if you only have very few pages in your site and want to make better use
    * of the space in the left navigation pane. 
    * Note that sections may still be filtered based on your setting for the overall navigation depth. 
    * 
    * You can also append or prepend additional sections of links, each with a section header and one or more
    * links. Prepending may not be desirable if you also configure a table of content or a download page,
    * as the prepended links would appear before them.
    * 
    * If you increase the depth to a value higher than 3 you might want to include custom CSS for the
    * levels 4 and higher as Laika comes with styling for three layers out of the box.
    * 
    * @param depth               the depth of the navigation structure, two by default
    * @param includePageSections indicates whether sections on pages should be included (false by default)
    * @param prependLinks        navigation sections to place above the auto-generated navigation structure
    * @param appendLinks         navigation sections to place below the auto-generated navigation structure
    */
  def mainNavigation (depth: Int = currentContent.mainNavigation.depth, 
                      includePageSections: Boolean = currentContent.mainNavigation.includePageSections, 
                      prependLinks: Seq[ThemeNavigationSection] = Nil, 
                      appendLinks: Seq[ThemeNavigationSection] = Nil): Helium = {
    val newContent = currentContent.copy(
      mainNavigation = MainNavigation(
        depth, 
        includePageSections, 
        currentContent.mainNavigation.prependLinks ++ prependLinks,
        currentContent.mainNavigation.appendLinks ++ appendLinks
      )
    )
    copyWith(helium.siteSettings.copy(content = newContent))
  }

  /** Configures the top navigation bar of the main content pages.
    * 
    * @param homeLink the link to the homepage, by default pointing to `index.html` and using the Helium home icon.
    * @param navLinks an optional set of links to be placed at the right side of the bar, supported link
    *                 types are `IconLink`, `ButtonLink`, `ImageLink` and a plain `TextLink`
    * @param versionMenu defines labels and optionally additional links for the version switcher menu
    * @param highContrast indicates whether the background color should have a high contrast to the background
    *                     of the page (darker in light mode and lighter in dark mode).
    */
  def topNavigationBar (homeLink: ThemeLink = currentContent.topNavigationBar.homeLink, 
                        navLinks: Seq[ThemeLink] = Nil,
                        versionMenu: VersionMenu = currentContent.topNavigationBar.versionMenu,
                        highContrast: Boolean = currentContent.topNavigationBar.highContrast): Helium = {
    val newContent = currentContent.copy(
      topNavigationBar = TopNavigationBar(
        homeLink,
        currentContent.topNavigationBar.navLinks ++ navLinks, 
        versionMenu, 
        highContrast
      )
    )
    copyWith(helium.siteSettings.copy(content = newContent))
  }
  
  def pageNavigation (enabled: Boolean = currentContent.pageNavigation.enabled, 
                      depth: Int = currentContent.pageNavigation.depth,
                      sourceBaseURL: Option[String] = currentContent.pageNavigation.sourceBaseURL,
                      sourceLinkText: String = currentContent.pageNavigation.sourceLinkText): Helium = {
    val newContent = helium.siteSettings.content
      .copy(pageNavigation = PageNavigation(enabled, depth, sourceBaseURL, sourceLinkText))
    copyWith(helium.siteSettings.copy(content = newContent))
  }

  /** Adds a dedicated page for a table of content, in addition to the left navigation bar.
    * 
    * @param title the title to display on the page and in navigation that links to the page
    * @param depth the navigation depth which may be different than the one for the navigation bar
    */
  def tableOfContent (title: String, depth: Int): Helium = {
    val newContent = helium.siteSettings.content.copy(tableOfContent = Some(TableOfContent(title, depth)))
    copyWith(helium.siteSettings.copy(content = newContent))
  }

  /** Adds a download page to the generated site that contains links to EPUB and PDF versions of the site.
    * When the sbt plugin is used, this setting will automatically trigger the rendering of the corresponding
    * EPUB and PDF documents when running `laikaSite`.
    * When the library API is used, this setting only causes the inclusion of the download page itself,
    * the actual EPUB and PDF content must be generated by running the respective renderers manually.
    * 
    * @param title         the title to display on the page and in navigation that links to the page
    * @param description   a short description that appears on the download page right under the title
    * @param downloadPath  the virtual path the EPUB and PDF documents will be generated into
    * @param includeEPUB   whether EPUB documents will be automatically generated (only having an effect when using the sbt plugin)
    * @param includePDF    whether PDF documents will be automatically generated (only having an effect when using the sbt plugin)
    */
  def downloadPage (title: String, description: Option[String], downloadPath: Path = Root / "downloads",
                    includeEPUB: Boolean = true, includePDF: Boolean = true): Helium = {
    val newContent = helium.siteSettings.content
      .copy(downloadPage = Some(DownloadPage(title, description, downloadPath, includeEPUB, includePDF)))
    copyWith(helium.siteSettings.copy(content = newContent))
  }

  @deprecated("use the corresponding properties of the new pageNavigation method", "0.19.0")
  def markupEditLinks (text: String, baseURL: String): Helium = pageNavigation(
    sourceLinkText = text,
    sourceBaseURL = Some(baseURL)
  )

  /** Adds a dedicated landing page to the site that is tailored for software documentation sites.
    * By default no landing page will be included and the site will render the homepage (if present)
    * with the same default template as the main content pages.
    * 
    * See the section "Website Landing Page" in the "Theme Settings" chapter for visual guide to the
    * layout of the page.
    * 
    * All arguments of this method are optional, but the page would look strange if you omit most of them.
    * 
    * Additionally or alternatively you can also add a regular markup document called `landing-page.` to one
    * of your input directories and its content will be inserted at the bottom of this page.
    * This is in case you want to stick with the traditional approach of some sites, which give you 'Getting Started'
    * style content right on the start page.
    * It can also be used to list adopters, provide a feature overview or links to presentations or videos.
    * 
    * @param logo                a logo to be placed on the left hand side of the header
    * @param title               a title to be placed right under the logo
    * @param subtitle            a subtitle to be place right under the title
    * @param latestReleases      a set of release versions to display on the right side of the header
    * @param license             the license info to render right under the release info
    * @param titleLinks          a row of links to render beneath the subtitle on the left side of the header
    * @param documentationLinks  a set of documentation links to render in a dedicated panel on the right side of the header
    * @param projectLinks        a set of project links to render at the bottom of the right side of the header
    * @param teasers             a set of teasers containing of headline and description to render below the header
    * @param styles              internal paths to additional CSS files that should be linked to the landing page
    */
  def landingPage (logo: Option[Image] = None,
                   title: Option[String] = None,
                   subtitle: Option[String] = None,
                   latestReleases: Seq[ReleaseInfo] = Nil,
                   license: Option[String] = None,
                   titleLinks: Seq[ThemeLink] = Nil,
                   documentationLinks: Seq[TextLink] = Nil,
                   projectLinks: Seq[ThemeLinkSpan] = Nil,
                   teasers: Seq[Teaser] = Nil,
                   styles: Seq[Path] = Nil): Helium = {
    val page = LandingPage(logo, title, subtitle, latestReleases, license, titleLinks, documentationLinks, projectLinks, teasers, styles)
    val newContent = currentContent.copy(landingPage = Some(page))
    copyWith(helium.siteSettings.copy(content = newContent))
  }

  /** Specify the configuration for versioned documentation, a core Laika feature simply exposed via the Helium Config API.
    * 
    * The specified configuration allows to define the current version as well as any older or newer versions.
    * For each version the `pathSegment` property holds the value that should use as part of URLs 
    * (e.g. `/0.18/...`) whereas the `displayValue` property holds the text that should be shown in the dropdown menu.
    * 
    * If the output destination of the render operation contains existing, older versions in sub-folders,
    * those will be scanned to produce additional version information in the JSON loaded by the site.
    * This will be used for "smart linking" where the drop-down will link to the same page of a different version
    * if it exists.
    * 
    * The corresponding version switcher component of the theme can be configured with `topNavigationBar.versionMenu`.
    * That setting allows to specify the labels and optionally additional link entries for the menu.
    * Additional version switcher components can be added using the `VersionMenu` class, for example for
    * adding the menu to the landing page where it is not included by default.
    */
  def versions (versions: Versions): Helium = copyWith(helium.siteSettings.copy(versions = Some(versions)))

  /** Specifies the base URL where the rendered site will be hosted.
    * This configuration option allows to turn internal links into external ones for documents which will be
    * part of the rendered site, but are not included in other formats like EPUB or PDF.
    */
  def baseURL (url: String): Helium = copyWith(helium.siteSettings.copy(baseURL = Some(url)))

  /** Resets the specified UI elements receiving a true flag to the defaults.
    * 
    * This method can be used in rare cases where a configuration that has been pre-populated with some UI elements
    * beyond the built-in defaults of Helium should get reset without falling back to the basic `Helium.defaults`.
    * This cannot be done with the regular configuration methods as some properties of type `Seq[A]` behave in 
    * an additive way, meaning that if you call `.site.topNavigationBar(navLinks = Seq(...))` those links will 
    * be added to those already present.
    * 
    * If starting from `Helium.defaults` directly this method has no effect as Laika does not pre-populate those
    * `Seq[A]` settings. But 3-rd party tools might provide a Helium instance with additional elements.
    * 
    * @param mainNavigation indicates whether the main (left) navigation pane should reset to the auto-generated list
    * @param topNavigation indicates whether the top navigation bar should remove any pre-populated links
    * @param favIcons indicates that the list of favicons should be cleared
    * @return
    */
  def resetDefaults (mainNavigation: Boolean = false, topNavigation: Boolean = false, favIcons: Boolean = false): Helium = {
    val base = currentContent
    val newMainNav = if (mainNavigation) MainNavigation() else base.mainNavigation
    val newTopNav = if (topNavigation) TopNavigationBar.default else base.topNavigationBar
    val newFavIcons = if (favIcons) Nil else base.favIcons
    val newContent = currentContent.copy(mainNavigation = newMainNav, topNavigationBar = newTopNav, favIcons = newFavIcons)
    copyWith(helium.siteSettings.copy(content = newContent))
  }
    
}

private[helium] trait EPUBOps extends SingleConfigOps with CopyOps {

  protected def currentMetadata: DocumentMetadata = helium.epubSettings.metadata
  protected def currentColors: ColorSet = helium.epubSettings.colors
  protected def currentLayout: EPUBLayout = helium.epubSettings.layout
  
  def fontResources (defn: FontDefinition*): Helium =
    copyWith(helium.epubSettings.copy(bookConfig = helium.epubSettings.bookConfig.copy(fonts = defn)))
  protected def withFontFamilies (fonts: ThemeFonts): Helium =
    copyWith(helium.epubSettings.copy(themeFonts = fonts))
  protected def withFontSizes (sizes: FontSizes): Helium =
    copyWith(helium.epubSettings.copy(fontSizes = sizes))
  protected def withColors (colors: ColorSet): Helium =
    copyWith(helium.epubSettings.copy(colors = colors))
  protected def withMetadata (metadata: DocumentMetadata): Helium =
    copyWith(helium.epubSettings.copy(bookConfig = helium.epubSettings.bookConfig.copy(metadata = metadata)))

  /** Allows to add a second color set for dark mode.
    * The implementation is based on the `prefers-color-scheme` media query and requires e-book readers supporting
    * dark mode.
    */
  def darkMode: DarkModeOps = new DarkModeOps {
    def disabled: Helium = copyWith(helium.siteSettings.copy(darkMode = None))
    protected def currentColors: ColorSet = helium.epubSettings.darkMode.getOrElse(helium.epubSettings.colors)
    protected def withColors (colors: ColorSet): Helium = copyWith(helium.epubSettings.copy(darkMode = Some(colors)))
  }
  
  /** The navigation depth of the main navigation structure provided to the EPUB reader.
    * The depth value counts all elements that form the hierarchy, directories, documents and sections within
    * documents.
    */
  def navigationDepth (depth: Int): Helium =
    copyWith(helium.epubSettings.copy(bookConfig = helium.epubSettings.bookConfig.copy(navigationDepth = Some(depth))))

  /** Allows to override the defaults for Helium's EPUB layout.
    *
    * You can use the constructors found in the `LengthUnit` companion to create length values,
    * e.g. `LengthUnit.px(12)`.
    * It's usually most convenient to import `laika.ast.LengthUnit._` for your configuration code.
    *
    * Most arguments should be self-explanatory.
    * The `keepTogetherDecoratedLines` value controls the number of lines for decorated blocks like code examples 
    * or callouts that should always be kept on the same page. 
    * With a setting of `12` for example only blocks with more than 12 lines are allowed to be split across multiple pages.
    * If you choose very high numbers for this setting you might see pages with a lot of blank space when it has
    * to move a large block to the next page.
    */
  def layout (defaultBlockSpacing: Length = currentLayout.defaultBlockSpacing,
              defaultLineHeight: Double = currentLayout.defaultLineHeight,
              keepTogetherDecoratedLines: Int = currentLayout.keepTogetherDecoratedLines): Helium =
    copyWith(helium.epubSettings.copy(layout = EPUBLayout(
      defaultBlockSpacing, defaultLineHeight, keepTogetherDecoratedLines
    )))
  
  /** Adds a dedicated page for a table of content, in addition to the left navigation bar.
    *
    * @param title the title to display on the page and in navigation that links to the page
    * @param depth the navigation depth which may be different than the one for the navigation bar
    */
  def tableOfContent (title: String, depth: Int): Helium = {
    val newLayout = helium.epubSettings.layout.copy(tableOfContent = Some(TableOfContent(title, depth)))
    copyWith(helium.epubSettings.copy(layout = newLayout))
  }
  
  /** Auto-links CSS documents from the specified paths.
    * By default all CSS documents found anywhere in the input tree will be linked in HTML files.
    * This setting allows to narrow it down to one or more dedicated paths within the virtual tree,
    * which might be useful when your input contains CSS files unrelated to the pages rendered by Laika.
    * 
    * For inclusion in EPUB document a special suffix is expected:
    * 
    * - `.shared.css` for CSS documents to be auto-linked for EPUB and the website
    * - `.epub.css` for CSS document to be auto-linked only for EPUB, but not the website
    */
  def autoLinkCSS (paths: Path*): Helium =
    copyWith(helium.epubSettings.copy(htmlIncludes = helium.epubSettings.htmlIncludes.copy(includeCSS = paths)))

  /** Auto-links JavaScript documents from the specified paths.
    * By default all JavaScript documents found anywhere in the input tree will be linked in HTML files.
    * This setting allows to narrow it down to one or more dedicated paths within the virtual tree,
    * which might be useful when your input contains JavaScript files unrelated to the pages rendered by Laika.
    */
  def autoLinkJS (paths: Path*): Helium =
    copyWith(helium.epubSettings.copy(htmlIncludes = helium.epubSettings.htmlIncludes.copy(includeJS = paths)))

  /** Specifies one or more cover images for the EPUB document.
    * 
    * Multiple cover images are only relevant when the `@:select` directive is used to generate different 
    * versions of the same e-book which can all have their own cover image.
    * See the documentation for the `@:select` directive in the chapter "Standard Directives" in the manual
    * for details.
    */
  def coverImages (images: CoverImage*): Helium =
    copyWith(helium.epubSettings.copy(coverImages = images))
}

private[helium] trait PDFOps extends SingleConfigOps with CopyOps {

  protected def currentMetadata: DocumentMetadata = helium.pdfSettings.metadata
  protected def currentColors: ColorSet = helium.pdfSettings.colors
  
  def fontResources (defn: FontDefinition*): Helium =
    copyWith(helium.pdfSettings.copy(bookConfig = helium.pdfSettings.bookConfig.copy(fonts = defn)))
  protected def withFontFamilies (fonts: ThemeFonts): Helium = copyWith(helium.pdfSettings.copy(themeFonts = fonts))
  protected def withFontSizes (sizes: FontSizes): Helium = copyWith(helium.pdfSettings.copy(fontSizes = sizes))
  protected def withColors (colors: ColorSet): Helium = copyWith(helium.pdfSettings.copy(colors = colors))
  protected def withMetadata (metadata: DocumentMetadata): Helium =
    copyWith(helium.pdfSettings.copy(bookConfig = helium.pdfSettings.bookConfig.copy(metadata = metadata)))

  /** The navigation depth of the main navigation structure provided to the PDF reader.
    * 
    * This is a navigation structure that will usually be displayed in a separate pane by PDF reader software.
    * For including a table of content right in the page flow, so that is also available when printing the document,
    * see the `tableOfContent` method.
    * 
    * The depth value counts all elements that form the hierarchy, directories, documents and sections within
    * documents.
    */
  def navigationDepth (depth: Int): Helium =
    copyWith(helium.pdfSettings.copy(bookConfig = helium.pdfSettings.bookConfig.copy(navigationDepth = Some(depth))))

  /** Allows to override the defaults for Helium's PDF layout.
    * 
    * You can use the constructors found in the `LengthUnit` companion to create length values,
    * e.g. `LengthUnit.px(12)`.
    * It's usually most convenient to import `laika.ast.LengthUnit._` for your configuration code.
    * 
    * Most arguments should be self-explanatory.
    * The `keepTogetherDecoratedLines` value controls the number of lines for decorated blocks like code examples 
    * or callouts that should always be kept on the same page. 
    * With a setting of `12` for example only blocks with more than 12 lines are allowed to be split across multiple pages.
    * If you choose very high numbers for this setting you might see pages with a lot of blank space when it has
    * to move a large block to the next page.
    */
  def layout (pageWidth: Length, pageHeight: Length,
              marginTop: Length, marginRight: Length, marginBottom: Length, marginLeft: Length,
              defaultBlockSpacing: Length,
              defaultLineHeight: Double,
              keepTogetherDecoratedLines: Int): Helium =
    copyWith(helium.pdfSettings.copy(layout = PDFLayout(
      pageWidth, pageHeight, marginTop, marginRight, marginBottom, marginLeft,
      defaultBlockSpacing, defaultLineHeight, keepTogetherDecoratedLines
    )))

  /** Adds a dedicated page for a table of content, in addition to the reader-native navigation structure.
    *
    * @param title the title to display on the page and in navigation that links to the page
    * @param depth the navigation depth which may be different than the one for the reader-native navigation structure
    */
  def tableOfContent (title: String, depth: Int): Helium = {
    val newLayout = helium.pdfSettings.layout.copy(tableOfContent = Some(TableOfContent(title, depth)))
    copyWith(helium.pdfSettings.copy(layout = newLayout))
  }

  /** Specifies one or more cover images for the PDF document.
    *
    * Multiple cover images are only relevant when the `@:select` directive is used to generate different 
    * versions of the same e-book which can all have their own cover image.
    * See the documentation for the `@:select` directive in the chapter "Standard Directives" in the manual
    * for details.
    */
  def coverImages (images: CoverImage*): Helium =
    copyWith(helium.pdfSettings.copy(coverImages = images))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy