package cmarkit

  1. Overview
  2. Docs

Renderer abstraction.

Stateful renderer abstraction to render documents in Stdlib.Buffer.t values.

Note. This is a low-level interface. For quick and standard renderings see Cmarkit_html.of_doc, Cmarkit_latex.of_doc and Cmarkit_commonmark.of_doc. If you want to extend them, see this example.

Rendering

type t

The type for renderers.

val doc_to_string : t -> Cmarkit.Doc.t -> string

doc_to_string r d renders document d to a string using renderer r.

val buffer_add_doc : t -> Buffer.t -> Cmarkit.Doc.t -> unit

buffer_add_doc r b d renders document d on buffer b using renderer r.

Renderers

type context

The type for rendering contexts, holds a renderer, a Stdlib.Buffer.t value to act on and rendering state.

type inline = context -> Cmarkit.Inline.t -> bool

The type for inline renderers.

Return false if you are not interested in rendering the given inline. Use Context.inline and Context.block on the given context if you need to invoke the renderer recursively.

type block = context -> Cmarkit.Block.t -> bool

The type for block renderers.

Return false if you are not interested in rendering the given block. Use Context.inline and Context.block with the given context if you need to invoke the renderer recursively.

type doc = context -> Cmarkit.Doc.t -> bool

The type for document renderers.

Return false if you are not interested in rendering the given document. Use Context.inline, Context.block and Context.doc with the given context if you need to invoke the renderer recursively.

val make : ?init_context:(context -> Cmarkit.Doc.t -> unit) -> ?inline:inline -> ?block:block -> ?doc:doc -> unit -> t

make ?init_context ?inline ?block ?doc () is a renderer using inline, block, doc to render documents. They all default to (fun _ _ -> false), which means that by default they defer to next renderer (see compose).

init_context is used to initialize the context for the renderer before a document render. It defaults to fun _ _ -> ().

val compose : t -> t -> t

compose g f renders first with f and if a renderer returns false, falls back on its counterpart in g.

The init_context of the result calls g's initialization context function first, followed by the one of f. This means f's initialization function can assume the context is already setup for g.

Accessors

Normally you should not need these but you may want to peek into other renderers.

val init_context : t -> context -> Cmarkit.Doc.t -> unit

init_context r is the context initalization function for r.

val inline : t -> inline

inline r is the inline renderer of r.

val block : t -> block

block_renderer r is the block renderer of r.

val doc : t -> doc

doc_renderer r is the documentation renderer of r.

Rendering contexts

module Context : sig ... end

Rendering contexts.

Extending renderers

This example extends the Cmarkit_html.renderer but it applies mutatis mutandis to the other backend document renderers.

Let's assume you want to:

  • Extend the abstract syntax tree with a Doc block which allows to splice documents in another one (note that splicing is already built-in via the Cmarkit.Block.Blocks block case).
  • Change the rendering of Cmarkit.Inline.Image inlines to render HTML video or audio elements depending on the link's destination suffix.
  • For the rest use the built-in Cmarkit_html.renderer renderer as it exists.

This boils down to:

  1. Add a new case to the abstract syntax tree.
  2. Define a custom_html renderer which treats Cmarkit.Inline.Image and the new Doc case the way we see it fit and return false otherwise to use the built-in renderer.
  3. compose custom_html with Cmarkit_html.renderer
type Cmarkit.Block.t += Doc of Cmarkit.Doc.t (* 1 *)

let media_link c l =
  let has_ext s ext = String.ends_with ~suffix:ext s in
  let is_video s = List.exists (has_ext s) [".mp4"; ".webm"] in
  let is_audio s = List.exists (has_ext s) [".mp3"; ".flac"] in
  let defs = Cmarkit_renderer.Context.get_defs c in
  match Cmarkit.Inline.Link.reference_definition defs l with
  | Some Cmarkit.Link_definition.Def (ld, _) ->
      let start_tag = match Cmarkit.Link_definition.dest ld with
      | Some (src, _) when is_video src -> Some ("<video", src)
      | Some (src, _) when is_audio src -> Some ("<audio", src)
      | None | Some _ -> None
      in
      begin match start_tag with
      | None -> false (* let the default HTML renderer handle that *)
      | Some (start_tag, src) ->
          (* More could be done with the reference title and link text *)
          Cmarkit_renderer.Context.string c start_tag;
          Cmarkit_renderer.Context.string c {| src="|};
          Cmarkit_html.pct_encoded_string c src;
          Cmarkit_renderer.Context.string c {|" />|};
          true
      end
  | None | Some _ -> false (* let the default HTML renderer that *)

let custom_html =
  let inline c = function
  | Cmarkit.Inline.Image (l, _) -> media_link c l
  | _ -> false (* let the default HTML renderer handle that *)
  in
  let block c = function
  | Doc d ->
      (* It's important to recurse via Cmarkit_renderer.Context.block *)
      Cmarkit_renderer.Context.block c (Cmarkit.Doc.block d); true
  | _ -> false (* let the default HTML renderer handle that *)
  in
  Cmarkit_renderer.make ~inline ~block () (* 2 *)

let custom_html_of_doc ~safe doc =
  let default = Cmarkit_html.renderer ~safe () in
  let r = Cmarkit_renderer.compose default custom_html in (* 3 *)
  Cmarkit_renderer.doc_to_string r doc

The custom_html_of_doc function performs your extended renderings.

OCaml

Innovation. Community. Security.