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

laika.api.Render.scala Maven / Gradle / Ivy

/*
 * Copyright 2013-2016 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.api

import laika.ast._
import laika.config.{OperationConfig, RenderConfigBuilder}
import laika.factory.{RenderFormat, RenderResultProcessor}
import laika.io.Output.Binary
import laika.io.OutputTree._
import laika.io._
import laika.rewrite.TemplateRewriter

/** API for performing a render operation to various types of output using an existing
 *  document tree model. 
 *  
 *  In cases where a render operation follows a parse operation 
 *  immediately, it is more convenient to use the [[laika.api.Transform]] API 
 *  instead which combines a parse and a render operation directly.
 *  
 *  Example for rendering HTML to a file:
 *  
 *  {{{
 *  val doc: Document = ...
 *  
 *  Render as HTML from doc toFile "hello.html"
 *  }}}
 *  
 *  Example for rendering HTML from an entire tree of documents to a directory:
 *  
 *  {{{
 *  val tree: DocumentTree = ...
 *  
 *  Render as HTML from tree toDirectory "path/to/output"
 *  }}}
 *  
 *  Example for rendering PDF from an entire tree of documents to a single target file:
 *  
 *  {{{
 *  val tree: DocumentTree = ...
 *  
 *  Render as PDF from tree toFile "hello.pdf"
 *  }}}
 *  
 *  @tparam Writer the writer API to use which varies depending on the renderer
 * 
 *  @author Jens Halm
 */
abstract class Render[Writer] private (protected[api] val format: RenderFormat[Writer],
                                       val config: OperationConfig) extends RenderConfigBuilder[Writer] {

  protected[this] lazy val theme = config.themeFor(format)


  /** The output operations that can be performed for a single input document.
   */
  type DocOps

  /** The output operations that can be performed for an entire tree of input documents.
   */
  type TreeOps

  /** The concrete implementation of the abstract Render type.
   */
  type ThisType <: Render[Writer]


  @deprecated("renamed to rendering for consistency", "0.9.0")
  def using (render: Writer => RenderFunction): ThisType = rendering(render)

  /** Specifies the element to render. This may be a `RootElement` instance
   *  as well as any other type of `Element`, thus allowing to render document
   *  fragments, too.
   *
   *  @param elem the element to render
   *  @return a new Operation instance that allows to specify the output
   */
  def from (elem: Element): DocOps

  /** Specifies the document to render.
   *
   *  @param doc the document to render
   *  @return a new Operation instance that allows to specify the output
   */
  def from (doc: Document): DocOps

  /** Specifies the document tree to render.
   *
   *  @param tree the document tree to render
   *  @return a new BatchOperation instance that allows to specify the outputs
   */
  def from (tree: DocumentTree): TreeOps


  /** Renders the specified element to the given output.
   * 
   *  @param element the element to render
   *  @param output the output to render to
   *  @param styles the styles to apply to the elements during the render process
   */
  protected[this] def render (element: Element, output: Output, styles: StyleDeclarationSet): Unit = { 
    
    class Renderer (out: Output) extends (Element => Unit) {
      lazy val (writer, renderF) = format.newRenderer(out, element, this, styles, config)

      lazy val mainF: Element => Unit = theme.customRenderer(writer).applyOrElse(_, renderF)

      def apply (element: Element) = mainF(element)
    }

    IO(output) { out =>
      new Renderer(out).apply(element)
      out.flush()
    }
  }
  
  /** Renders the specified document tree to the given output.
   * 
   *  @param docTree the tree to render
   *  @param outputTree the output tree to render to
   */
  protected[this] def render (docTree: DocumentTree, outputTree: OutputTree): Unit = {

    type Operation = () => Unit

    def renderTree (outputTree: OutputTree, styles: StyleDeclarationSet, path: Path, content: RootElement): Operation = {
      val output = outputTree.newOutput(path.basename +"."+ format.fileSuffix)
      () => render(content, output, styles)
    }

    def copy (outputTree: OutputTree)(input: Input): Operation = {
      val output = outputTree.newOutput(input.path.name)
      () => IO.copy(input, output)
    }

    def collectOperations (outputTree: OutputTree, parentStyles: StyleDeclarationSet, docTree: DocumentTree): Seq[Operation] = {

      def isOutputRoot (source: DocumentTree) = (source.sourcePaths.headOption, outputTree) match {
        case (Some(inPath), out: DirectoryOutputTree) => inPath == out.directory.getAbsolutePath
        case _ => false
      }

      val styles = parentStyles ++ docTree.styles(format.fileSuffix)

      (docTree.content flatMap {
        case doc: Document => Seq(renderTree(outputTree, styles, doc.path, doc.content))
        case tree: DocumentTree if !isOutputRoot(tree) => collectOperations(outputTree.newChild(tree.name), styles, tree)
        case _ => Seq()
      }) ++
      (docTree.additionalContent flatMap {
        case doc: DynamicDocument => Seq(renderTree(outputTree, styles, doc.path, doc.content))
        case static: StaticDocument if outputTree.acceptsStaticFiles => Seq(copy(outputTree)(static.input))
        case _ => Seq()
      })
    }

    val templateName = "default.template." + format.fileSuffix
    val treeWithTpl = if (docTree.selectTemplate(Path.Current / templateName).isDefined) docTree
                      else docTree.copy(templates = docTree.templates :+ TemplateDocument(Path.Root / templateName,
                           theme.defaultTemplateOrFallback))
    val treeWithTplApplied = TemplateRewriter.applyTemplates(treeWithTpl, format.fileSuffix)
    val finalTree = theme.staticDocuments.merge(treeWithTplApplied)
    val operations = collectOperations(outputTree, theme.defaultStyles, finalTree)

    Executor.execute(operations, config.parallelConfig.parallelism, config.parallelConfig.threshold)
  }
    

}

/** Serves as an entry point to the Render API.
 * 
 *  @author Jens Halm
 */
object Render {
  
  /** A render operation that maps each input document of a
   *  given input tree to a corresponding output document
   *  in the destination tree.
   *  
   *  @param format the factory for the rendere to use
   *  @param cfg the configuration for the render operation
   */
  class RenderMappedOutput[Writer] (format: RenderFormat[Writer],
                                    cfg: OperationConfig) extends Render[Writer](format, cfg) {

    type DocOps = TextOuputOps
    type TreeOps = OutputTreeOps
    type ThisType = RenderMappedOutput[Writer]

    def withConfig(newConfig: OperationConfig): ThisType =
      new RenderMappedOutput[Writer](format, newConfig)

    def from (element: Element): TextOuputOps = new TextOuputOps {
      def toOutput (out: Output) = render(element, out, theme.defaultStyles)
    }
    
    def from (doc: Document): TextOuputOps = from(doc.content)
    
    def from (tree: DocumentTree): OutputTreeOps = new OutputTreeOps {
      def toOutputTree (out: OutputTree) = render(tree, out)
    }
    
  }
  
  /** A render operation that gathers input from one or more
   *  input documents in an input tree structure to be rendered 
   *  to a single output destination.
   *  
   *  This is necessary for formats like PDF, where the output
   *  will be contained in a single file, but the input can still
   *  be conveniently organized in a full directory structure.
   *  
   *  @param processor the processor that merges the results from the individual render operations into a single output
   *  @param cfg the configuration for the render operation
   */
  class RenderGatheredOutput[Writer] (processor: RenderResultProcessor[Writer],
                                      cfg: OperationConfig) extends Render[Writer](processor.format, cfg) {
    
    type DocOps = BinaryOutputOps
    type TreeOps = BinaryOutputOps
    type ThisType = RenderGatheredOutput[Writer]

    def withConfig(newConfig: OperationConfig): ThisType =
      new RenderGatheredOutput[Writer](processor, newConfig)

    def from (element: Element): BinaryOutputOps =
      from(Document(Path.Root / "target", RootElement(Seq(TemplateRoot(Seq(TemplateElement(element)))))))
    
    def from (doc: Document): BinaryOutputOps =
      from(DocumentTree(Path.Root, Seq(doc)))
    
    def from (tree: DocumentTree): BinaryOutputOps = new BinaryOutputOps {
      def toBinaryOutput (out: Output with Binary) =
        processor.process(tree, render, theme.defaultTemplateOrFallback, out.asBinaryOutput)
    }
    
  }
  

  /** Returns a new Render instance for the specified render format.
   *  This factory is usually an object provided by the library
   *  or a plugin that is capable of rendering a specific output. 
   * 
   *  @param format the renderer factory responsible for creating the final renderer
   */
  def as [Writer] (format: RenderFormat[Writer]): RenderMappedOutput[Writer] =
    new RenderMappedOutput(format, OperationConfig.default)
  
  /** Returns a new Render instance for the specified processor.
   *  This instance is usually an object provided by the library
   *  or a plugin that is capable of rendering a specific output. 
   * 
   *  @param processor the processor responsible for processing the renderer result
   */
  def as [Writer] (processor: RenderResultProcessor[Writer]): RenderGatheredOutput[Writer] =
    new RenderGatheredOutput(processor, OperationConfig.default)
  
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy