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

org.fernice.flare.style.value.specified.Position.kt Maven / Gradle / Ivy

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.fernice.flare.style.value.specified

import org.fernice.std.Err
import org.fernice.std.Ok
import org.fernice.std.Result
import org.fernice.std.unwrapOr
import org.fernice.flare.cssparser.ParseError
import org.fernice.flare.cssparser.Parser
import org.fernice.flare.cssparser.ToCss
import org.fernice.flare.cssparser.Token
import org.fernice.flare.cssparser.newUnexpectedTokenError
import org.fernice.flare.style.AllowQuirks
import org.fernice.flare.style.Parse
import org.fernice.flare.style.ParserContext
import org.fernice.flare.style.value.Context
import org.fernice.flare.style.value.SpecifiedValue
import org.fernice.flare.style.value.computed.CalcLengthOrPercentage
import org.fernice.flare.style.value.computed.Percentage
import java.io.Writer
import org.fernice.flare.style.value.computed.LengthOrPercentage as ComputedLengthOrPercentage
import org.fernice.flare.style.value.computed.Position as ComputedPosition

data class Position(
    val horizontal: HorizontalPosition,
    val vertical: VerticalPosition,
) : SpecifiedValue, ToCss {

    companion object {

        fun parse(context: ParserContext, input: Parser): Result {
            return parseQuirky(context, input, AllowQuirks.No)
        }

        fun parseQuirky(
            context: ParserContext,
            input: Parser,
            allowQuirks: AllowQuirks,
        ): Result {
            when (val xPosResult = input.tryParse { parser -> PositionComponent.parseQuirky(context, parser, allowQuirks, X.Companion) }) {
                is Ok -> {
                    when (val xPos = xPosResult.value) {
                        is PositionComponent.Center -> {
                            val yPosResult = input.tryParse { parser -> PositionComponent.parseQuirky(context, parser, allowQuirks, Y.Companion) }

                            if (yPosResult is Ok) {
                                return Ok(Position(xPos, yPosResult.value))
                            }

                            val xPos = input.tryParse { parser -> PositionComponent.parseQuirky(context, parser, allowQuirks, X.Companion) }
                                .unwrapOr(xPos)
                            val yPos = PositionComponent.Center()

                            return Ok(Position(xPos, yPos))
                        }
                        is PositionComponent.Side -> {
                            if (input.tryParse { parser -> parser.expectIdentifierMatching("center") }.isOk()) {
                                val yPos = PositionComponent.Center()

                                return Ok(Position(xPos, yPos))
                            }

                            val ySide = input.tryParse(Y.Companion::parse)

                            if (ySide is Ok) {
                                val yLop = input.tryParse { parser -> LengthOrPercentage.parseQuirky(context, parser, allowQuirks) }
                                    .ok()

                                val yPos = PositionComponent.Side(ySide.value, yLop)

                                return Ok(Position(xPos, yPos))
                            }


                            val (side, lop) = xPos

                            val xPosSplit = PositionComponent.Side(side, null)
                            val yPos = lop?.let { PositionComponent.Length(it) } ?: PositionComponent.Center()

                            return Ok(Position(xPosSplit, yPos))
                        }
                        is PositionComponent.Length -> {
                            val ySide = input.tryParse(Y.Companion::parse)

                            if (ySide is Ok) {
                                val yPos = PositionComponent.Side(ySide.value, null)

                                return Ok(Position(xPos, yPos))
                            }

                            val yLop = input.tryParse { parser -> LengthOrPercentage.parseQuirky(context, parser, allowQuirks) }
                            if (yLop is Ok) {
                                val yPos = PositionComponent.Length(yLop.value)

                                return Ok(Position(xPos, yPos))
                            }

                            val yPos = PositionComponent.Center()
                            input.tryParse { parser -> parser.expectIdentifierMatching("center") }

                            return Ok(Position(xPos, yPos))
                        }
                    }
                }
                is Err -> {
                    val yKeyword = when (val yKeyword = Y.parse(input)) {
                        is Ok -> yKeyword.value
                        is Err -> return yKeyword
                    }

                    val sideResult: Result>, ParseError> = input.tryParse { parser ->
                        val yLop = input.tryParse { nestedParser -> LengthOrPercentage.parseQuirky(context, nestedParser, allowQuirks) }
                            .ok()

                        val xKeyword = parser.tryParse(X.Companion::parse)
                        if (xKeyword is Ok) {
                            val xLop = input.tryParse { nestedParser -> LengthOrPercentage.parseQuirky(context, nestedParser, allowQuirks) }
                                .ok()
                            val xPos = PositionComponent.Side(xKeyword.value, xLop)

                            return@tryParse Ok(yLop to xPos)
                        }

                        val centerKeyword = parser.expectIdentifierMatching("center")
                        if (centerKeyword is Err) {
                            return@tryParse Err(centerKeyword.value)
                        }

                        val xPos = PositionComponent.Center()
                        return@tryParse Ok(yLop to xPos)
                    }

                    if (sideResult is Ok) {
                        val (yLop, xPos) = sideResult.value

                        val yPos = PositionComponent.Side(yKeyword, yLop)

                        return Ok(Position(xPos, yPos))
                    }

                    val xPos = PositionComponent.Center()
                    val yPos = PositionComponent.Side(yKeyword, null)

                    return Ok(Position(xPos, yPos))
                }
            }
        }

        fun center(): Position {
            return Position(
                PositionComponent.Center(),
                PositionComponent.Center()
            )
        }
    }

    override fun toComputedValue(context: Context): ComputedPosition {
        return ComputedPosition(
            horizontal.toComputedValue(context),
            vertical.toComputedValue(context)
        )
    }

    override fun toCss(writer: Writer) {
        horizontal.toCss(writer)
        writer.append(' ')
        vertical.toCss(writer)
    }
}

typealias HorizontalPosition = PositionComponent

typealias VerticalPosition = PositionComponent

sealed class PositionComponent : SpecifiedValue, ToCss {

    class Center : PositionComponent()
    class Length(val length: LengthOrPercentage) : PositionComponent()
    data class Side(val side: S, val length: LengthOrPercentage?) : PositionComponent()

    override fun toCss(writer: Writer) {
        when (this) {
            is PositionComponent.Center -> writer.write("center")
            is PositionComponent.Length -> length.toCss(writer)
            is PositionComponent.Side -> {
                side.toCss(writer)
                if (length != null) {
                    writer.append(' ')
                    length.toCss(writer)
                }
            }
        }
    }

    companion object {

        fun  parseQuirky(
            context: ParserContext,
            input: Parser,
            allowQuirks: AllowQuirks,
            parse: Parse,
        ): Result, ParseError> {
            if (input.tryParse { parser -> parser.expectIdentifierMatching("center") }.isOk()) {
                return Ok(Center())
            }

            val lopResult = input.tryParse { parser -> LengthOrPercentage.parseQuirky(context, parser, allowQuirks) }

            if (lopResult is Ok) {
                return Ok(Length(lopResult.value))
            }

            val side = when (val side = parse.parse(context, input)) {
                is Ok -> side.value
                is Err -> return side
            }

            val lop = input.tryParse { parser -> LengthOrPercentage.parseQuirky(context, parser, allowQuirks) }.ok()

            return Ok(Side(side, lop))
        }

        fun  zero(): PositionComponent {
            return PositionComponent.Length(LengthOrPercentage.Zero)
        }
    }

    override fun toComputedValue(context: Context): ComputedLengthOrPercentage {
        return when (this) {
            is Center -> ComputedLengthOrPercentage.fifty
            is Side -> {
                if (this.length == null) {
                    val percentage = Percentage(
                        if (this.side.isStart()) {
                            0.0f
                        } else {
                            1.0f
                        }
                    )
                    return ComputedLengthOrPercentage.Percentage(percentage)
                }

                val length = this.length

                if (this.side.isStart()) {
                    length.toComputedValue(context)
                } else {
                    when (val computed = length.toComputedValue(context)) {
                        is ComputedLengthOrPercentage.Length -> {
                            ComputedLengthOrPercentage.Calc(
                                CalcLengthOrPercentage.new(
                                    -computed.length, Percentage.hundred()
                                )
                            )
                        }
                        is ComputedLengthOrPercentage.Percentage -> {
                            ComputedLengthOrPercentage.Percentage(
                                Percentage(
                                    1.0f - computed.percentage.value
                                )
                            )
                        }
                        is ComputedLengthOrPercentage.Calc -> {
                            val p = Percentage(1.0f - (computed.calc.percentage?.value ?: 0.0f))
                            val l = -computed.calc.unclampedLength()
                            ComputedLengthOrPercentage.Calc(CalcLengthOrPercentage.new(l, p))
                        }
                    }
                }
            }
            is Length -> this.length.toComputedValue(context)
        }
    }
}

interface Side : ToCss {

    fun isStart(): Boolean
}

sealed class X : Side {

    object Left : X()

    object Right : X()

    final override fun isStart(): Boolean {
        return when (this) {
            Left -> true
            else -> false
        }
    }

    final override fun toCss(writer: Writer) {
        when (this) {
            is X.Left -> writer.write("left")
            is X.Right -> writer.write("right")
        }
    }

    fun opposite(): X {
        return when (this) {
            is Left -> Right
            is Right -> Left
        }
    }

    companion object : Parse {

        override fun parse(context: ParserContext, input: Parser): Result {
            return parse(input)
        }

        fun parse(input: Parser): Result {
            val location = input.sourceLocation()

            val ident = when (val ident = input.expectIdentifier()) {
                is Ok -> ident.value
                is Err -> return ident
            }

            return when (ident.lowercase()) {
                "left" -> Ok(Left)
                "right" -> Ok(Right)
                else -> Err(location.newUnexpectedTokenError(Token.Identifier(ident)))
            }
        }
    }
}

sealed class Y : Side {

    object Top : Y()

    object Bottom : Y()

    final override fun isStart(): Boolean {
        return when (this) {
            Top -> true
            else -> false
        }
    }

    final override fun toCss(writer: Writer) {
        when (this) {
            is Y.Top -> writer.write("top")
            is Y.Bottom -> writer.write("bottom")
        }
    }

    fun opposite(): Y {
        return when (this) {
            is Top -> Bottom
            is Bottom -> Top
        }
    }

    companion object : Parse {

        override fun parse(context: ParserContext, input: Parser): Result {
            return parse(input)
        }

        fun parse(input: Parser): Result {
            val location = input.sourceLocation()

            val ident = when (val ident = input.expectIdentifier()) {
                is Ok -> ident.value
                is Err -> return ident
            }

            return when (ident.lowercase()) {
                "top" -> Ok(Top)
                "bottom" -> Ok(Bottom)
                else -> Err(location.newUnexpectedTokenError(Token.Identifier(ident)))
            }
        }
    }
}