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

laika.io.api.BinaryTreeRenderer.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.io.api

import cats.effect.{Async, Resource}
import laika.api.Renderer
import laika.api.builder.{OperationConfig, RendererBuilder}
import laika.ast.DocumentTreeRoot
import laika.factory.{BinaryPostProcessor, BinaryPostProcessorBuilder, TwoPhaseRenderFormat}
import laika.io.api.BinaryTreeRenderer.BinaryRenderer
import laika.io.descriptor.RendererDescriptor
import laika.io.model.{BinaryInput, BinaryOutput, ParsedTree}
import laika.io.ops.BinaryOutputOps
import laika.io.runtime.{Batch, RendererRuntime}
import laika.theme.{Theme, ThemeProvider}

/** Renderer that merges a tree of input documents to a single binary output document.
  *
  * @author Jens Halm
  */
class BinaryTreeRenderer[F[_]: Async: Batch] (renderer: BinaryRenderer[F], theme: Theme[F]) {

  /** Builder step that specifies the root of the document tree to render.
    */
  def from (input: DocumentTreeRoot): BinaryTreeRenderer.OutputOps[F] =
    BinaryTreeRenderer.OutputOps(renderer, theme, input, Nil)

  /** Builder step that specifies the root of the document tree to render
    * and the static documents to copy to the target (if it is file-system based).
    */
  def from (input: ParsedTree[F]): BinaryTreeRenderer.OutputOps[F] =
    BinaryTreeRenderer.OutputOps(renderer, theme, input.root, input.staticDocuments)
}

/** Builder API for constructing a rendering operation for a tree of binary output documents.
  */
object BinaryTreeRenderer {

  /** A renderer that operates with two phases, producing an interim result.
    *
    * Examples for such renderers are EPUB (with XHTML as the interim format)
    * and PDF (with XSL-FO as the interim format).
    *
    * This instance does not come with its own runtime. Instead its need to be passed
    * to a builder API in laika-io that knows how to execute such an operation.
    *
    * @param interimRenderer the renderer for the 1st phase, producing the interim result
    * @param prepareTree a hook with which the interim result can be modified before it gets
    *                    passed to the post processor
    * @param postProcessor the processor taking the interim result and producing the final 
    *                      result, the implementing type may vary from format to format
    * @param description short string describing the output format for tooling and logging 
    */
  case class BinaryRenderer[F[_]: Async] (interimRenderer: Renderer,
                                          prepareTree: DocumentTreeRoot => Either[Throwable, DocumentTreeRoot],
                                          postProcessor: BinaryPostProcessor[F],
                                          description: String)
  
  type BinaryRenderFormat = TwoPhaseRenderFormat[_, BinaryPostProcessorBuilder]

  private[laika] def buildRenderer[F[_]: Async] (format: BinaryRenderFormat, 
                                                config: OperationConfig, 
                                                theme: Theme[F]): Resource[F, BinaryRenderer[F]] = {
    val combinedConfig = config.withBundles(theme.extensions)
    format.postProcessor.build(combinedConfig.baseConfig, theme).map { pp =>
      val targetRenderer = new RendererBuilder(format.interimFormat, combinedConfig).build.skipRewritePhase
      BinaryRenderer(targetRenderer, format.prepareTree, pp, format.description)
    }
  }
  
  /** Builder step that allows to specify the execution context for blocking IO and CPU-bound tasks.
    */
  class Builder[F[_]: Async: Batch] (format: BinaryRenderFormat, config: OperationConfig, theme: Resource[F, Theme[F]]) {

    /** Applies the specified theme to this renderer, overriding any previously specified themes.
      */
    def withTheme (theme: ThemeProvider): Builder[F] = new Builder(format, config, theme.build)

    /** Applies the specified theme to this renderer, overriding any previously specified themes.
      */
    def withTheme (theme: Theme[F]): Builder[F] = new Builder(format, config, Resource.pure[F, Theme[F]](theme))
    
    /** Final builder step that creates a parallel renderer for binary output.
      */
    def build: Resource[F, BinaryTreeRenderer[F]] = for {
      initializedTheme    <- theme
      initializedRenderer <- buildRenderer(format, config, initializedTheme)
    } yield new BinaryTreeRenderer[F](initializedRenderer, initializedTheme)
  }

  /** Builder step that allows to specify the output to render to.
    */
  case class OutputOps[F[_]: Async: Batch] (renderer: BinaryRenderer[F],
                                            theme: Theme[F],
                                            input: DocumentTreeRoot,
                                            staticDocuments: Seq[BinaryInput[F]]) extends BinaryOutputOps[F] {

    val F: Async[F] = Async[F]

    type Result = Op[F]

    /** Copies the specified binary input to the output target,
      * in addition to rendering the document tree.
      */
    def copying (toCopy: Seq[BinaryInput[F]]): OutputOps[F] = copy(staticDocuments = staticDocuments ++ toCopy)

    def toOutput (output: BinaryOutput[F]): Op[F] = Op[F](renderer, theme, input, output, staticDocuments)

  }

  /** Represents a rendering operation for a tree of documents merged into a single binary output document.
    *
    * It can be run by invoking the `render` method which delegates to the library's
    * default runtime implementation or by developing a custom runner that performs
    * the rendering based on this operation's properties.
    */
  case class Op[F[_]: Async: Batch] (renderer: BinaryRenderer[F],
                                     theme: Theme[F],
                                     input: DocumentTreeRoot,
                                     output: BinaryOutput[F],
                                     staticDocuments: Seq[BinaryInput[F]] = Nil) {

    /** The configuration of the renderer for the interim format.
      */
    val config: OperationConfig = renderer.interimRenderer.config

    /** Performs the rendering operation based on the library's
      * default runtime implementation, suspended in the effect F.
      */
    def render: F[Unit] = RendererRuntime.run(this)

    /** Provides a description of this operation, the renderers
      * and extension bundles used, as well as the output target.
      * This functionality is mostly intended for tooling support.
      */
    def describe: F[RendererDescriptor] = RendererDescriptor.create(this)
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy