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

scalacss.Renderer.scala Maven / Gradle / Ivy

There is a newer version: 0.5.6
Show newest version
package scalacss

/**
 * Render [[Css]], an internal representation of CSS, into a different format; usually real CSS text.
 */
trait Renderer[Out] {
  def apply(css: Css): Out
}

object StringRenderer {
  type Format = StringBuilder => FormatSB

  /**
   * Default CSS generator.
   *
   * Merges CSS with the same media query into a single media query clause.
   */
  final class Default(format: Format) extends Renderer[String] {
    override def apply(css: Css): String = {
      val sb = new StringBuilder
      val fmt = format(sb)
      val (styles, keyframes, fontFaces) = Css.separateStylesAndKeyframes(css)
      val byMQ = Css.mapByMediaQuery(styles)

      fontFaces.foreach(fmt(_))                              // Render font faces
      keyframes.foreach(fmt(_))                              // Render keyframes
      byMQ.foreach(t => if (t._1.isEmpty)   fmt(None, t._2)) // Render styles without MQs
      byMQ.foreach(t => if (t._1.isDefined) fmt(t._1, t._2)) // Render styles with MQs
      fmt.done()

      sb.toString()
    }
  }

  /**
   * Generates CSS however it's received in. No pre-processing.
   */
  final class Raw(format: Format) extends Renderer[String] {
    override def apply(css: Css): String = {
      val sb = new StringBuilder
      val fmt = format(sb)

      css foreach (fmt(_))

      sb.toString()
    }
  }

  implicit def autoFormatToRenderer(f: Format): Renderer[String] =
    new Default(f)

  type KeyframeSelectorO = Option[KeyframeSelector]

  case class FormatSB(kfStart : String                                     => Unit,
                      kfsStart: Value                                      => Unit,
                      mqStart : CssMediaQuery                              => Unit,
                      selStart: (CssMediaQueryO, CssSelector)              => Unit,
                      kv1     : (KeyframeSelectorO, CssMediaQueryO, CssKV) => Unit,
                      kvn     : (KeyframeSelectorO, CssMediaQueryO, CssKV) => Unit,
                      selEnd  : (CssMediaQueryO, CssSelector)              => Unit,
                      mqEnd   : CssMediaQuery                              => Unit,
                      kfsEnd  : KeyframeSelector                           => Unit,
                      kfEnd   : KeyframeAnimationName                      => Unit,
                      ff      : CssFontFace                                => Unit,
                      done    : ()                                         => Unit) {

    def apply(mq: CssMediaQueryO, data: Css.ValuesByMediaQuery): Unit = {
      mq foreach mqStart
      for ((sel, kvs) <- data.whole)
        apply(None, mq, sel, kvs)
      mq foreach mqEnd
    }

    def apply(cssEntry: CssEntry): Unit =
      cssEntry match {

        case e: CssStyleEntry =>
          e.mq foreach mqStart
          apply(None, e.mq, e.sel, e.content)
          e.mq foreach mqEnd

        case e: CssKeyframesEntry =>
          kfStart(e.name.value)
          for ((sel, styles) <- e.frames) {
            kfsStart(sel.value)
            val selO = Some(sel)
            for (s <- styles)
              printCssKV(selO, s.mq, s.content)
            kfsEnd(sel)
          }
          kfEnd(e.name)

        case e: CssFontFace =>
          ff(e)
      }

    def apply(kf: KeyframeSelectorO, mq: CssMediaQueryO, sel: CssSelector, kvs: NonEmptyVector[CssKV]): Unit = {
      selStart(mq, sel)
      printCssKV(kf, mq, kvs)
      selEnd(mq, sel)
    }

    def printCssKV(kf: KeyframeSelectorO, mq: CssMediaQueryO, kvs: NonEmptyVector[CssKV]): Unit = {
      kv1(kf, mq, kvs.head)
      kvs.tail.foreach(kvn(kf, mq, _))
    }
  }

  def printFontFace(fontface: CssFontFace,
                    start   : ()                        => Unit,
                    kv      : (String, String, Boolean) => Unit, //Key, value, wrap
                    end     : ()                        => Unit) = {
    start()
    kv("font-family", fontface.fontFamily, true)
    kv("src", fontface.src.toStream.mkString(","), false)
    for (v <- fontface.fontStretch ) kv("font-stretch" , v         , false)
    for (v <- fontface.fontStyle   ) kv("font-style"   , v         , false)
    for (v <- fontface.fontWeight  ) kv("font-weight"  , v         , false)
    for (v <- fontface.unicodeRange) kv("unicode-range", v.toString, false)
    end()
  }

  private def quoteIfNeeded(sb: StringBuilder, s: String, quote: Boolean): Unit =
    if (quote && s.contains(" ")) {
      sb append '"'
      sb append s
      sb append '"'
    } else
      sb append s

  /**
   * Generates tiny CSS intended for browsers. No unnecessary whitespace or colons.
   */
  val formatTiny: Format = sb => {
    def kv(c: CssKV, quote: Boolean = false): Unit = {
      sb append c.key
      sb append ':'
      quoteIfNeeded(sb, c.value, quote)
    }
    FormatSB(
      kfStart  = n         => { sb append "@keyframes "; sb append n; sb append '{' },
      kfsStart = s         => { sb append s; sb append '{' },
      mqStart  = m         => { sb append m; sb append '{' },
      selStart = (_, s)    => { sb append s; sb append '{' },
      kv1      = (_, _, c) => kv(c),
      kvn      = (_, _, c) => { sb append ';'; kv (c) },
      selEnd   = (_, _)    => sb append '}',
      mqEnd    = _         => sb append '}',
      kfsEnd   = _         => sb append '}',
      kfEnd    = _         => sb append '}',
      ff       = fontface  =>
        printFontFace(
          fontface,
          () => sb append "@font-face {",
          (key: String, value: String, quote: Boolean) => kv(CssKV(key, value), quote),
          () => sb append "}"
        ),
      done     = ()        => ())
  }

  /**
   * Generates CSS intended for humans.
   */
  def formatPretty(indent: String = "  ", postColon: String = " "): Format = sb => {
    def mqIndent(mq: CssMediaQueryO): Unit =
      if (mq.isDefined) sb append indent
    def kfIndent(kf: KeyframeSelectorO): Unit =
      if (kf.isDefined) sb append indent
    def kv(kf: KeyframeSelectorO, mq: CssMediaQueryO, c: CssKV, quote: Boolean = false) = {
      kfIndent(kf)
      mqIndent(mq)
      sb append indent
      sb append c.key
      sb append ':'
      sb append postColon
      quoteIfNeeded(sb, c.value, quote)
      sb append ";\n"
    }
    FormatSB(
      kfStart  = n => { sb append "@keyframes "; sb append n; sb append " {\n" },
      kfsStart = s => { sb append indent; sb append s; sb append " {\n" },
      mqStart  = mq => {
                   sb append mq
                   sb append " {\n"
                 },
      selStart = (mq, sel) => {
                   mqIndent(mq)
                   sb append sel
                   sb append " {\n"
                 },
      kv1      = (kf: KeyframeSelectorO, mq: CssMediaQueryO, c: CssKV) => kv(kf, mq, c),
      kvn      = (kf: KeyframeSelectorO, mq: CssMediaQueryO, c: CssKV) => kv(kf, mq, c),
      selEnd   = (mq, _) => {
                   mqIndent(mq)
                   sb append "}\n"
                   if (mq.isEmpty) sb append '\n'
                 },
      mqEnd    = _ => sb append "}\n\n",
      kfsEnd   = _ => { sb append indent; sb append "}\n\n" },
      kfEnd    = _ => sb append "}\n\n",
      ff       = fontface =>
        printFontFace(
          fontface,
          () => sb append "@font-face {\n",
          (key: String, value: String, quote: Boolean) => kv(None, None, CssKV(key, value), quote),
          () => sb append "}\n\n"
        ),
      done     = () => ())
  }

  val defaultPretty: Renderer[String] =
    formatPretty()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy