
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)))
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy