Overriding Renderers

In some cases you might want to override the output of a renderer for a few types of AST nodes only, while keeping the default for the rest. Both the sbt plugin and the library API offer a hook to easily do that without modifying or extending the existing renderer.

In general this processing hook is intended for cases where the customization you intend to do is coupled to a specific output format. For any more generic processing logic, it is recommended to consider AST Rewriting instead, as that hook allows to replace or remove individual nodes in the AST before rendering. This way the same logic can be used for all supported output formats.

Function Signature

This is the signature of a custom renderer hook:

PartialFunction[(Formatter, Element), String]

Formatter is a generic type representing the formatting API which is different for each output format. For HTML, XHTML for EPUB and XSL-FO for PDF it is sub-type TagFormatter. It provides additional convenience APIs for rendering tags, adds the current indentation level after line breaks and knows how to render child elements.

Element is the base type of the document AST and represents the AST node to render, String is the render result in the expected target format.

See The Document AST for more details about the various node types

Defining a Render Function

This section explains how a render function is implemented and the subsequent sections show the three different ways to register such a function.

In the following example only the HTML output for emphasized text will be modified, adding a specific style class:

import laika.ast._
import laika.api.format.TagFormatter

val renderer: PartialFunction[(TagFormatter, Element), String] = {
  case (fmt, e: Emphasized) => 
    fmt.element("em", e, "class" -> "big") 
}

For all node types where the partial function is not defined, the default renderer will be used.

Multiple custom renderers can be specified for the same transformation, they will be tried in the order you added them, falling back to the default in case none is defined for a specific node.

The content value above is of type Seq[Span]. A renderer should only ever render a single node and delegate to the formatter for rendering children. Only the formatter has a list of all installed render extensions as well as the base renderer and will delegate to those functions where the partial function is defined for the child element.

Registering a Render Function

Since it is one of the most likely extension points used in user-code, there is a direct shortcut for passing it to the Laika configuration as shown below.

In case you want to combine it with other extensions, a render override can also be defined as part of an ExtensionBundle (see The ExtensionBundle API for details).

import laika.ast._

laikaExtensions += laikaHtmlRenderer {
  case (fmt, e: Emphasized) => 
    fmt.element("em", e, "class" -> "big")
}

Using the Transformer API

import laika.api._
import laika.ast._
import laika.format._

val transformer = Transformer
  .from(Markdown)
  .to(HTML)
  .rendering {
    case (fmt, e: Emphasized) => 
      fmt.element("em", e, "class" -> "big")
  }.build

Using the Renderer API

def doc: Document = ???

val renderer = Renderer
  .of(HTML)
  .rendering { 
    case (fmt, e: Emphasized) => 
      fmt.element("em", e, "class" -> "big")
  }.build

The Formatter APIs

Each formatter provides functionality that is specific to the target format. On top of that all formatters manage the indentation level after line breaks and know how to delegate to the relevant renderers for child elements.

Formatter

This is the base API supported by all internal renderers.

TagFormatter

This formatter supports all methods of the Formatter API shown above, but adds additional convenience methods for all tag-based formats: HTML, XHTML for EPUB, XSL-FO for PDF. This is the API you would use in almost all cases when writing renderer overrides.

It adds the following methods (amongst others):