Update color, fix typo

This commit is contained in:
2021-04-09 19:41:48 +02:00
parent edd33b41a5
commit 57065b5f4b
8 changed files with 716 additions and 589 deletions

View File

@@ -1,10 +1,10 @@
plugins {
kotlin("multiplatform") version "1.4.30"
kotlin("multiplatform") version "1.4.31"
`maven-publish`
}
group = "nl.astraeus"
version = "0.3.11"
version = "0.4.11"
repositories {
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
@@ -75,10 +75,12 @@ publishing {
}
}
}
/*
publications {
val kotlinMultiplatform by getting {
//artifactId = "kotlin-css-generator"
artifactId = "kotlin-css-generator"
}
}
*/
}

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,194 +1,500 @@
package nl.astraeus.css.properties
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
open class Color(
value: String,
val readOnly: Boolean = false
) : CssProperty(value) {
/**
* See [CSS Color Module Level 3](https://www.w3.org/TR/2018/REC-css-color-3-20180619/)
*
* This class represents a CSS color value. String parameters to the constructor argument
* can take one of the following forms:
*
* * HTML color name, e.g. ``Red``, ``DarkSalmon`` (case-insensitive), though in this case the use of the pre-defined constants is recommended.
* * ``#rgb`` or ``#rrggbb``
* * ``rgb(0..255, 0..255, 0..255)``, ``rgb(0..100%, 0..100%, 0..100%)``, ``rgb(0..100%, 0..100%, 0..100%, 0..1)``, ``rgba(0..255, 0..255, 0..255, 0..1)``
* * ``hsl(0..360, 0-100%, 0..100%)`` or ``hsla(0..360, 0-100%, 0..100%, 0..1)``
*
* Technically, the Hue parameter to ``hsl`` or ``hsla`` can exceed ``360``, because it represents a *degree* (angle) on
* the color wheel. But as per the algorithm proposed by the W3C, the value will ultimately be capped to ``360`` through
* a series of modulus operations; see section *4.2.4. HSL color values* of the above specification.
*
* Taken from: https://github.com/JetBrains/kotlin-wrappers/tree/master/kotlin-css
*/
@Suppress("SpellCheckingInspection")
class Color(value: String) : CssProperty(value) {
private var rgb: String? = null
companion object {
val auto = Color("auto", true)
val transparant = Color("transparant", true)
val initial = Color("initial", true)
val inherit = Color("inherit", true)
fun hex(hex: String) = Color("#$hex")
fun rgb(
red: Int,
green: Int,
blue: Int
) = RgbaColor(red, green, blue, 1.0)
fun rgba(
red: Int,
green: Int,
blue: Int,
alpha: Double
) = RgbaColor(red, green, blue, alpha)
fun hsl(
hue: Int,
saturation: Int,
lightness: Int
) = HslaColor(hue, saturation, lightness, 1.0)
fun hsla(
hue: Int,
saturation: Int,
lightness: Int,
alpha: Double
) = HslaColor(hue, saturation, lightness, alpha)
val aliceBlue = Color("#F0F8FF")
val antiqueWhite = Color("#FAEBD7")
val aqua = Color("#00FFFF")
val aquamarine = Color("#7FFFD4")
val azure = Color("#F0FFFF")
val beige = Color("#F5F5DC")
val bisque = Color("#FFE4C4")
val black = Color("#000000")
val blanchedAlmond = Color("#FFEBCD")
val blue = Color("#0000FF")
val blueViolet = Color("#8A2BE2")
val brown = Color("#A52A2A")
val burlyWood = Color("#DEB887")
val cadetBlue = Color("#5F9EA0")
val chartreuse = Color("#7FFF00")
val chocolate = Color("#D2691E")
val coral = Color("#FF7F50")
val cornflowerBlue = Color("#6495ED")
val cornsilk = Color("#FFF8DC")
val crimson = Color("#DC143C")
val cyan = Color("#00FFFF")
val darkBlue = Color("#00008B")
val darkCyan = Color("#008B8B")
val darkGoldenRod = Color("#B8860B")
val darkGray = Color("#A9A9A9")
val darkGrey = Color("#A9A9A9")
val darkGreen = Color("#006400")
val darkKhaki = Color("#BDB76B")
val darkMagenta = Color("#8B008B")
val darkOliveGreen = Color("#556B2F")
val darkorange = Color("#FF8C00")
val darkOrchid = Color("#9932CC")
val darkRed = Color("#8B0000")
val darkSalmon = Color("#E9967A")
val darkSeaGreen = Color("#8FBC8F")
val darkSlateBlue = Color("#483D8B")
val darkSlateGray = Color("#2F4F4F")
val darkSlateGrey = Color("#2F4F4F")
val darkTurquoise = Color("#00CED1")
val darkViolet = Color("#9400D3")
val deepPink = Color("#FF1493")
val deepSkyBlue = Color("#00BFFF")
val dimGray = Color("#696969")
val dimGrey = Color("#696969")
val dodgerBlue = Color("#1E90FF")
val fireBrick = Color("#B22222")
val floralWhite = Color("#FFFAF0")
val forestGreen = Color("#228B22")
val fuchsia = Color("#FF00FF")
val gainsboro = Color("#DCDCDC")
val ghostWhite = Color("#F8F8FF")
val gold = Color("#FFD700")
val goldenRod = Color("#DAA520")
val gray = Color("#808080")
val grey = Color("#808080")
val green = Color("#008000")
val greenYellow = Color("#ADFF2F")
val honeyDew = Color("#F0FFF0")
val hotPink = Color("#FF69B4")
val indianRed = Color(" #CD5C5C")
val indigo = Color(" #4B0082")
val ivory = Color("#FFFFF0")
val khaki = Color("#F0E68C")
val lavender = Color("#E6E6FA")
val lavenderBlush = Color("#FFF0F5")
val lawnGreen = Color("#7CFC00")
val lemonChiffon = Color("#FFFACD")
val lightBlue = Color("#ADD8E6")
val lightCoral = Color("#F08080")
val lightCyan = Color("#E0FFFF")
val lightGoldenRodYellow = Color("#FAFAD2")
val lightGray = Color("#D3D3D3")
val lightGrey = Color("#D3D3D3")
val lightGreen = Color("#90EE90")
val lightPink = Color("#FFB6C1")
val lightSalmon = Color("#FFA07A")
val lightSeaGreen = Color("#20B2AA")
val lightSkyBlue = Color("#87CEFA")
val lightSlateGray = Color("#778899")
val lightSlateGrey = Color("#778899")
val lightSteelBlue = Color("#B0C4DE")
val lightYellow = Color("#FFFFE0")
val lime = Color("#00FF00")
val limeGreen = Color("#32CD32")
val linen = Color("#FAF0E6")
val magenta = Color("#FF00FF")
val maroon = Color("#800000")
val mediumAquaMarine = Color("#66CDAA")
val mediumBlue = Color("#0000CD")
val mediumOrchid = Color("#BA55D3")
val mediumPurple = Color("#9370D8")
val mediumSeaGreen = Color("#3CB371")
val mediumSlateBlue = Color("#7B68EE")
val mediumSpringGreen = Color("#00FA9A")
val mediumTurquoise = Color("#48D1CC")
val mediumVioletRed = Color("#C71585")
val midnightBlue = Color("#191970")
val mintCream = Color("#F5FFFA")
val mistyRose = Color("#FFE4E1")
val moccasin = Color("#FFE4B5")
val navajoWhite = Color("#FFDEAD")
val navy = Color("#000080")
val oldLace = Color("#FDF5E6")
val olive = Color("#808000")
val oliveDrab = Color("#6B8E23")
val orange = Color("#FFA500")
val orangeRed = Color("#FF4500")
val orchid = Color("#DA70D6")
val paleGoldenRod = Color("#EEE8AA")
val paleGreen = Color("#98FB98")
val paleTurquoise = Color("#AFEEEE")
val paleVioletRed = Color("#D87093")
val papayaWhip = Color("#FFEFD5")
val peachPuff = Color("#FFDAB9")
val peru = Color("#CD853F")
val pink = Color("#FFC0CB")
val plum = Color("#DDA0DD")
val powderBlue = Color("#B0E0E6")
val purple = Color("#800080")
val red = Color("#FF0000")
val rosyBrown = Color("#BC8F8F")
val royalBlue = Color("#4169E1")
val saddleBrown = Color("#8B4513")
val salmon = Color("#FA8072")
val sandyBrown = Color("#F4A460")
val seaGreen = Color("#2E8B57")
val seaShell = Color("#FFF5EE")
val sienna = Color("#A0522D")
val silver = Color("#C0C0C0")
val skyBlue = Color("#87CEEB")
val slateBlue = Color("#6A5ACD")
val slateGray = Color("#708090")
val slateGrey = Color("#708090")
val snow = Color("#FFFAFA")
val springGreen = Color("#00FF7F")
val steelBlue = Color("#4682B4")
val tan = Color("#D2B48C")
val teal = Color("#008080")
val thistle = Color("#D8BFD8")
val tomato = Color("#FF6347")
val turquoise = Color("#40E0D0")
val violet = Color("#EE82EE")
val wheat = Color("#F5DEB3")
val white = Color("#FFFFFF")
val whiteSmoke = Color("#F5F5F5")
val yellow = Color("#FFFF00")
val yellowGreen = Color("#9ACD32")
private constructor(value: String, rgb: String) : this(value) {
this.rgb = rgb
}
companion object {
val initial = Color("initial")
val inherit = Color("inherit")
val unset = Color("unset")
val transparent = Color("transparent")
val currentColor = Color("currentColor")
// W3C predefined HTML colors (147), see the referenced specification above.
val aliceBlue = Color("aliceblue", "#f0f8ff")
val antiqueWhite = Color("antiquewhite", "#faebd7")
val aqua = Color("aqua", "#00ffff")
val aquamarine = Color("aquamarine", "#7fffd4")
val azure = Color("azure", "#f0ffff")
val beige = Color("beige", "#f5f5dc")
val bisque = Color("bisque", "#ffe4c4")
val black = Color("black", "#000000")
val blanchedAlmond = Color("blanchedalmond", "#ffebcd")
val blue = Color("blue", "#0000ff")
val blueViolet = Color("blueviolet", "#8a2be2")
val brown = Color("brown", "#a52a2a")
val burlyWood = Color("burlywood", "#deb887")
val cadetBlue = Color("cadetblue", "#5f9ea0")
val chartreuse = Color("chartreuse", "#7fff00")
val chocolate = Color("chocolate", "#d2691e")
val coral = Color("coral", "#ff7f50")
val cornflowerBlue = Color("cornflowerblue", "#6495ed")
val cornsilk = Color("cornsilk", "#fff8dc")
val crimson = Color("crimson", "#dc143c")
val cyan = Color("cyan", "#00ffff")
val darkBlue = Color("darkblue", "#00008b")
val darkCyan = Color("darkcyan", "#008b8b")
val darkGoldenrod = Color("darkgoldenrod", "#b8860b")
val darkGray = Color("darkgray", "#a9a9a9")
val darkGreen = Color("darkgreen", "#006400")
val darkGrey = Color("darkgrey", "#a9a9a9")
val darkKhaki = Color("darkkhaki", "#bdb76b")
val darkMagenta = Color("darkmagenta", "#8b008b")
val darkOliveGreen = Color("darkolivegreen", "#556b2f")
val darkOrange = Color("darkorange", "#ff8c00")
val darkOrchid = Color("darkorchid", "#9932cc")
val darkRed = Color("darkred", "#8b0000")
val darkSalmon = Color("darksalmon", "#e9967a")
val darkSeaGreen = Color("darkseagreen", "#8fbc8f")
val darkSlateBlue = Color("darkslateblue", "#483d8b")
val darkSlateGray = Color("darkslategray", "#2f4f4f")
val darkSlateGrey = Color("darkslategrey", "#2f4f4f")
val darkTurquoise = Color("darkturquoise", "#00ced1")
val darkViolet = Color("darkviolet", "#9400d3")
val deepPink = Color("deeppink", "#ff1493")
val deepSkyBlue = Color("deepskyblue", "#00bfff")
val dimGray = Color("dimgray", "#696969")
val dimGrey = Color("dimgrey", "#696969")
val dodgerBlue = Color("dodgerblue", "#1e90ff")
val firebrick = Color("firebrick", "#b22222")
val floralWhite = Color("floralwhite", "#fffaf0")
val forestGreen = Color("forestgreen", "#228b22")
val fuchsia = Color("fuchsia", "#ff00ff")
val gainsboro = Color("gainsboro", "#dcdcdc")
val ghostWhite = Color("ghostwhite", "#f8f8ff")
val gold = Color("gold", "#ffd700")
val goldenrod = Color("goldenrod", "#daa520")
val gray = Color("gray", "#808080")
val green = Color("green", "#008000")
val greenYellow = Color("greenyellow", "#adff2f")
val grey = Color("grey", "#808080")
val honeydew = Color("honeydew", "#f0fff0")
val hotPink = Color("hotpink", "#ff69b4")
val indianRed = Color("indianred", "#cd5c5c")
val indigo = Color("indigo", "#4b0082")
val ivory = Color("ivory", "#fffff0")
val khaki = Color("khaki", "#f0e68c")
val lavender = Color("lavender", "#e6e6fa")
val lavenderBlush = Color("lavenderblush", "#fff0f5")
val lawnGreen = Color("lawngreen", "#7cfc00")
val lemonChiffon = Color("lemonchiffon", "#fffacd")
val lightBlue = Color("lightblue", "#add8e6")
val lightCoral = Color("lightcoral", "#f08080")
val lightCyan = Color("lightcyan", "#e0ffff")
val lightGoldenrodYellow = Color("lightgoldenrodyellow", "#fafad2")
val lightGray = Color("lightgray", "#d3d3d3")
val lightGreen = Color("lightgreen", "#90ee90")
val lightGrey = Color("lightgrey", "#d3d3d3")
val lightPink = Color("lightpink", "#ffb6c1")
val lightSalmon = Color("lightsalmon", "#ffa07a")
val lightSeaGreen = Color("lightseagreen", "#20b2aa")
val lightSkyBlue = Color("lightskyblue", "#87cefa")
val lightSlateGray = Color("lightslategray", "#778899")
val lightSlateGrey = Color("lightslategrey", "#778899")
val lightSteelBlue = Color("lightsteelblue", "#b0c4de")
val lightYellow = Color("lightyellow", "#ffffe0")
val lime = Color("lime", "#00ff00")
val limeGreen = Color("limegreen", "#32cd32")
val linen = Color("linen", "#faf0e6")
val magenta = Color("magenta", "#ff00ff")
val maroon = Color("maroon", "#800000")
val mediumAquamarine = Color("mediumaquamarine", "#66cdaa")
val mediumBlue = Color("mediumblue", "#0000cd")
val mediumOrchid = Color("mediumorchid", "#ba55d3")
val mediumPurple = Color("mediumpurple", "#9370d8")
val mediumSeaGreen = Color("mediumseagreen", "#3cb371")
val mediumSlateBlue = Color("mediumslateblue", "#7b68ee")
val mediumSpringGreen = Color("mediumspringgreen", "#00fa9a")
val mediumTurquoise = Color("mediumturquoise", "#48d1cc")
val mediumVioletRed = Color("mediumvioletred", "#c71585")
val midnightBlue = Color("midnightblue", "#191970")
val mintCream = Color("mintcream", "#f5fffa")
val mistyRose = Color("mistyrose", "#ffe4e1")
val moccasin = Color("moccasin", "#ffe4b5")
val navajoWhite = Color("navajowhite", "#ffdead")
val navy = Color("navy", "#000080")
val oldLace = Color("oldlace", "#fdf5e6")
val olive = Color("olive", "#808000")
val oliveDrab = Color("olivedrab", "#6b8e23")
val orange = Color("orange", "#ffa500")
val orangeRed = Color("orangered", "#ff4500")
val orchid = Color("orchid", "#da70d6")
val paleGoldenrod = Color("palegoldenrod", "#eee8aa")
val paleGreen = Color("palegreen", "#98fb98")
val paleTurquoise = Color("paleturquoise", "#afeeee")
val paleVioletRed = Color("palevioletred", "#db7093")
val papayaWhip = Color("papayawhip", "#ffefd5")
val peachPuff = Color("peachpuff", "#ffdab9")
val peru = Color("peru", "#cd853f")
val pink = Color("pink", "#ffc0cb")
val plum = Color("plum", "#dda0dd")
val powderBlue = Color("powderblue", "#b0e0e6")
val purple = Color("purple", "#800080")
val red = Color("red", "#ff0000")
val rosyBrown = Color("rosybrown", "#bc8f8f")
val royalBlue = Color("royalblue", "#4169e1")
val saddleBrown = Color("saddlebrown", "#8b4513")
val salmon = Color("salmon", "#fa8072")
val sandyBrown = Color("sandybrown", "#f4a460")
val seaGreen = Color("seagreen", "#2e8b57")
val seaShell = Color("seashell", "#fff5ee")
val sienna = Color("sienna", "#a0522d")
val silver = Color("silver", "#c0c0c0")
val skyBlue = Color("skyblue", "#87ceeb")
val slateBlue = Color("slateblue", "#6a5acd")
val slateGray = Color("slategray", "#708090")
val slateGrey = Color("slategrey", "#708090")
val snow = Color("snow", "#fffafa")
val springGreen = Color("springgreen", "#00ff7f")
val steelBlue = Color("steelblue", "#4682b4")
val tan = Color("tan", "#d2b48c")
val teal = Color("teal", "#008080")
val thistle = Color("thistle", "#d8bfd8")
val tomato = Color("tomato", "#ff6347")
val turquoise = Color("turquoise", "#40e0d0")
val violet = Color("violet", "#ee82ee")
val wheat = Color("wheat", "#f5deb3")
val white = Color("white", "#ffffff")
val whiteSmoke = Color("whitesmoke", "#f5f5f5")
val yellow = Color("yellow", "#ffff00")
val yellowGreen = Color("yellowgreen", "#9acd32")
fun normalizeFractionalPercent(value: Double): Double =
value.coerceIn(minimumValue = 0.0, maximumValue = 1.0)
fun normalizePercent(value: Int): Int =
value.coerceIn(minimumValue = 0, maximumValue = 100)
fun normalizeRGB(value: Int): Int =
value.coerceIn(minimumValue = 0, maximumValue = 255)
// algorithm for capping from W3C
fun normalizeHue(value: Double): Int =
(((value % 360) + 360) % 360).roundToInt()
fun normalizeAlpha(value: Double): Double =
normalizeFractionalPercent(value)
// Match for hsl(int, int%, int%) | hsla(int, int%, int%, 0.5) | etc.
private val HSLA_REGEX by lazy {
Regex(
"^hsla?\\((-?[0-9]+\\.?[0-9]*(?:deg|grad|rad|turn)?)\\s*[, ]?\\s*(\\d{1,3})%\\s*[, ]\\s*(\\d{1,3})%\\s*[, ]?\\s*(\\d|(?:\\d?\\.\\d+))?\\)\$",
RegexOption.IGNORE_CASE
)
}
// Match for rgb(255, 255, 255) | rgba(255, 255, 255, 0.5) | rgb(100% 100% 100%) | etc.
private val RGBA_REGEX by lazy {
Regex(
"^rgba?\\((\\d{1,3}%?)\\s*[, ]\\s*(\\d{1,3}%?)\\s*[, ]\\s*(\\d{1,3}%?)[, ]?\\s*(\\d|(?:\\d?\\.\\d+))?\\)\$",
RegexOption.IGNORE_CASE
)
}
}
/**
* withAlpha preserves existing alpha value: rgba(0, 0, 0, 0.5).withAlpha(0.1) = rgba(0, 0, 0, 0.05)
*/
fun withAlpha(alpha: Double) =
when {
value.startsWith("hsl", true) -> with(fromHSLANotation()) { hsla(hue, saturation, lightness, normalizeAlpha(alpha) * this.alpha) }
else -> with(toRGBA()) { rgba(red, green, blue, normalizeAlpha(alpha) * this.alpha) }
}
/**
* changeAlpha rewrites existing alpha value: rgba(0, 0, 0, 0.5).withAlpha(0.1) = rgba(0, 0, 0, 0.1)
*/
fun changeAlpha(alpha: Double) =
when {
value.startsWith("hsl", true) -> with(fromHSLANotation()) { hsla(hue, saturation, lightness, normalizeAlpha(alpha)) }
else -> with(toRGBA()) { rgba(red, green, blue, normalizeAlpha(alpha)) }
}
// https://stackoverflow.com/questions/2049230/convert-rgba-color-to-rgb
fun blend(backgroundColor: Color): Color {
val source = this.toRGBA()
val background = backgroundColor.toRGBA()
val targetR = ((1 - source.alpha) * background.red) + (source.alpha * source.red)
val targetG = ((1 - source.alpha) * background.green) + (source.alpha * source.green)
val targetB = ((1 - source.alpha) * background.blue) + (source.alpha * source.blue)
return rgb(targetR.roundToInt(), targetG.roundToInt(), targetB.roundToInt())
}
/**
* Lighten the color by the specified percent (between 0-100), returning a new instance of Color.
*
* @param percent the percent to lighten the Color
* @return a new lightened version of this color
*/
fun lighten(percent: Int): Color {
val isHSLA = value.startsWith("hsl", ignoreCase = true)
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
val lightness = hsla.lightness + (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
val newHSLa = hsla.copy(lightness = normalizePercent(lightness))
return if (isHSLA) {
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
} else {
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
}
}
/**
* Darken the color by the specified percent (between 0-100), returning a new instance of Color.
*
* @param percent the percent to darken the Color
* @return a new darkened version of this color
*/
fun darken(percent: Int): Color {
val isHSLA = value.startsWith("hsl", ignoreCase = true)
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
val darkness = hsla.lightness - (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
val newHSLa = hsla.copy(lightness = normalizePercent(darkness))
return if (isHSLA) {
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
} else {
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
}
}
/**
* Increase contrast, if lightness > 50 then darken else lighten
*
* @param percent the percent to lighten/darken the Color
* @return a new ligtened/darkened version of this color
*/
fun contrast(percent: Int): Color {
val isHSLA = value.startsWith("hsl", ignoreCase = true)
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
val darkness = if (hsla.lightness > 50) {
hsla.lightness - (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
} else {
hsla.lightness + (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
}
val newHSLa = hsla.copy(lightness = normalizePercent(darkness))
return if (isHSLA) {
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
} else {
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
}
}
/**
* Saturate the color by the specified percent (between 0-100), returning a new instance of Color.
*
* @param percent the percent to saturate the Color
* @return a new saturated version of this color
*/
fun saturate(percent: Int): Color {
val isHSLA = value.startsWith("hsl", ignoreCase = true)
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
val saturation = hsla.saturation + (hsla.saturation * (normalizePercent(percent) / 100.0)).roundToInt()
val newHSLa = hsla.copy(saturation = normalizePercent(saturation))
return if (isHSLA) {
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
} else {
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
}
}
/**
* Desaturate the color by the specified percent (between 0-100), returning a new instance of Color.
*
* @param percent the percent to desaturate the Color
* @return a new desaturated version of this color
*/
fun desaturate(percent: Int): Color {
val isHSLA = value.startsWith("hsl", ignoreCase = true)
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
val desaturation = hsla.saturation - (hsla.saturation * (normalizePercent(percent) / 100.0)).roundToInt()
val newHSLa = hsla.copy(saturation = normalizePercent(desaturation))
return if (isHSLA) {
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
} else {
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
}
}
internal data class RGBA(
val red: Int,
val green: Int,
val blue: Int,
val alpha: Double = 1.0
) {
// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
fun asHSLA(): HSLA {
// scale R, G, B values into 0..1 fractions
val r = red / 255.0
val g = green / 255.0
val b = blue / 255.0
val cMax = maxOf(r, g, b)
val cMin = minOf(r, g, b)
val chroma = cMax - cMin
val lg = normalizeFractionalPercent((cMax + cMin) / 2)
val s = if (chroma != 0.0) normalizeFractionalPercent(chroma / (1.0 - abs((2.0 * lg) - 1.0))) else 0.0
val h = when (cMax) {
cMin -> 0.0
r -> 60 * (((g - b) / chroma) % 6.0)
g -> 60 * (((b - r) / chroma) + 2)
b -> 60 * (((r - g) / chroma) + 4)
else -> error("Unexpected value for max") // theoretically unreachable bc maxOf(r, g, b) above
}
return HSLA(normalizeHue(h), (s * 100).roundToInt(), (lg * 100).roundToInt(), alpha)
}
}
internal data class HSLA(
val hue: Int,
val saturation: Int,
val lightness: Int,
val alpha: Double = 1.0
) {
// Algorithm from W3C link referenced in class comment (section 4.2.4. HSL color values)
fun asRGBA(): RGBA {
fun hueToRGB(m1: Double, m2: Double, h: Double): Double {
val hu = if (h < 0) h + 1 else if (h > 1) h - 1 else h
return when {
(hu < 1.0 / 6) -> m1 + (m2 - m1) * 6 * hu
(hu < 1.0 / 2) -> m2
(hu < 2.0 / 3) -> m1 + ((m2 - m1) * 6 * (2.0 / 3 - hu))
else -> m1
}
}
if (saturation == 0) return RGBA(lightness, lightness, lightness)
// scale H, S, V values into 0..1 fractions
val h = (hue % 360.0) / 360.0
val s = saturation / 100.0
val lg = lightness / 100.0
val m2 = if (lg < 0.5) lg * (1 + s) else (lg + s - lg * s)
val m1 = 2 * lg - m2
val r = normalizeFractionalPercent(hueToRGB(m1, m2, h + (1.0 / 3)))
val g = normalizeFractionalPercent(hueToRGB(m1, m2, h))
val b = normalizeFractionalPercent(hueToRGB(m1, m2, h - (1.0 / 3)))
return RGBA((r * 255).roundToInt(), (g * 255).roundToInt(), (b * 255).roundToInt(), alpha)
}
}
internal fun fromHSLANotation(): HSLA {
val match = HSLA_REGEX.find(value)
fun getHSLParameter(index: Int) =
match?.groups?.get(index)?.value
?: throw IllegalArgumentException("Expected hsl or hsla notation, got $value")
val hueShape = getHSLParameter(1)
val hue = normalizeHue(
when {
hueShape.endsWith("grad", true) -> hueShape.substringBefore("grad").toDouble() * (9.0 / 10)
hueShape.endsWith("rad", true) -> (hueShape.substringBefore("rad").toDouble() * 180) / PI
hueShape.endsWith("turn", true) -> hueShape.substringBefore("turn").toDouble() * 360.0
hueShape.endsWith("deg", true) -> hueShape.substringBefore("deg").toDouble()
else -> hueShape.toDouble()
}
)
val saturation = normalizePercent(getHSLParameter(2).toInt())
val lightness = normalizePercent(getHSLParameter(3).toInt())
val alpha = normalizeAlpha(match?.groups?.get(4)?.value?.toDouble() ?: 1.0)
return HSLA(hue, saturation, lightness, alpha)
}
internal fun fromRGBANotation(): RGBA {
val match = RGBA_REGEX.find(value)
fun getRGBParameter(index: Int): Int {
val group = match?.groups?.get(index)?.value
?: throw IllegalArgumentException("Expected rgb or rgba notation, got $value")
return when {
(group.endsWith('%')) -> (normalizeFractionalPercent(group.substringBefore('%').toDouble() / 100.0) * 255.0).toInt()
else -> normalizeRGB(group.toInt())
}
}
val red = getRGBParameter(1)
val green = getRGBParameter(2)
val blue = getRGBParameter(3)
val alpha = normalizeAlpha(match?.groups?.get(4)?.value?.toDouble() ?: 1.0)
return RGBA(red, green, blue, alpha)
}
internal fun toRGBA(): RGBA {
val v = rgb ?: value
return when {
v.startsWith("rgb") -> fromRGBANotation()
// Matches #rgb
v.startsWith("#") && v.length == 4 -> RGBA(
"${v[1]}${v[1]}".toInt(16),
"${v[2]}${v[2]}".toInt(16),
"${v[3]}${v[3]}".toInt(16)
)
// Matches both #rrggbb and #rrggbbaa
v.startsWith("#") && (v.length == 7 || v.length == 9) -> RGBA(
(v.substring(1..2)).toInt(16),
(v.substring(3..4)).toInt(16),
(v.substring(5..6)).toInt(16)
)
else -> throw IllegalArgumentException("Only hexadecimal, rgb, and rgba notations are accepted, got $v")
}
}
}
private fun String.withZeros() = this + "0".repeat(max(0, 3 - this.length))
fun hex(value: Int) = Color("#${value.toString(16).withZeros()}")
fun rgb(red: Int, green: Int, blue: Int) = Color("rgb($red, $green, $blue)")
fun rgba(red: Int, green: Int, blue: Int, alpha: Double) = Color("rgba($red, $green, $blue, ${formatAlpha(alpha)})")
fun hsl(hue: Int, saturation: Int, lightness: Int) = Color("hsl($hue, $saturation%, $lightness%)")
fun hsla(hue: Int, saturation: Int, lightness: Int, alpha: Double) = Color("hsla($hue, $saturation%, $lightness%, ${formatAlpha(alpha)})")
fun blackAlpha(alpha: Double) = Color.black.withAlpha(alpha)
fun whiteAlpha(alpha: Double) = Color.white.withAlpha(alpha)
private fun formatAlpha(alpha: Double): String =
alpha.toString().let {
if ("." in it) it else "$it.0"
}

View File

@@ -1,168 +0,0 @@
package nl.astraeus.css.properties
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
class HslaColor(
val hue: Int,
val saturation: Int,
val lightness: Int,
val alpha: Double = 1.0
): Color("hsla($hue,$saturation%,$lightness%,$alpha)") {
fun darken(amount: Int): HslaColor = HslaColor(
hue,
saturation,
max(0, lightness - amount),
alpha
)
fun lighten(amount: Int): HslaColor = HslaColor(
hue,
saturation,
min(100, lightness + amount),
alpha
)
fun saturize(amount: Int): HslaColor = HslaColor(
hue,
min(100, saturation + amount),
lightness,
alpha
)
fun desaturize(amount: Int): HslaColor = HslaColor(
hue,
max(0, saturation - amount),
lightness,
alpha
)
fun adjustHue(amount: Int): HslaColor {
var hue = this.hue + amount
while (hue < 0) {
hue += 360
}
while(hue > 359) {
hue -= 360
}
return HslaColor(
hue,
saturation,
lightness,
alpha
)
}
fun toRgba() = RgbaColor.fromHsla(this)
companion object {
private fun findValues(value: String, expectedNumber: Int): List<String> {
check(value.contains('(') && value.contains(')'))
val parts = value.split('(')[1].split(')')[0].split(',')
check(parts.size == expectedNumber)
return parts
}
fun fromString(value: String): HslaColor = when {
value.trim().startsWith("#") -> {
fromHex(value.substringAfterLast('#'))
}
value.trim().startsWith("hsla") -> {
val values = findValues(value, 4)
HslaColor(
values[0].trim().toInt(),
values[1].trim().toInt(),
values[2].trim().toInt(),
values[3].trim().toDouble()
)
}
value.trim().startsWith("hsl") -> {
val values = findValues(value, 3)
HslaColor(
values[0].trim().toInt(),
values[1].trim().toInt(),
values[2].trim().toInt()
)
}
value.trim().startsWith("rgba") -> {
val values = findValues(value, 4)
fromRgba(
values[0].trim().toInt(),
values[1].trim().toInt(),
values[2].trim().toInt(),
values[3].trim().toDouble()
)
}
value.trim().startsWith("rgb") -> {
val values = findValues(value, 3)
fromRgb(
values[0].trim().toInt(),
values[1].trim().toInt(),
values[2].trim().toInt()
)
}
else -> {
throw IllegalArgumentException("Unable to parse $value as color")
}
}
fun fromHex(value: String): HslaColor = fromRgba(RgbaColor.fromHex(value))
fun fromRgba(rgba: RgbaColor): HslaColor = fromRgba(rgba.red, rgba.green, rgba.blue, rgba.alpha)
fun fromRgb(
red: Int,
green: Int,
blue: Int,
): HslaColor {
return fromRgba(red, green, blue, 1.0)
}
fun fromRgba(
red: Int,
green: Int,
blue: Int,
alpha: Double
): HslaColor {
val r = red / 255.0
val g = green / 255.0
val b = blue / 255.0
val v = max(r,max(g,b))
val c = v - min(r,min(g,b))
val f = (1 - abs(v+v-c-1))
var h = if (c > 0) {
when (v) {
r -> {
(g-b)/c
}
g -> {
2+(b-r)/c
}
else -> {
2+(b-r)/c
}
}
} else {
0.0
}
if (h < 0) {
h += 6
}
return HslaColor(
(60*h).toInt(),
if (f>0) { ((c * 100)/f).toInt() } else { 0 },
(100*((v+v-c)/2)).toInt(),
alpha
)
}
}
}

View File

@@ -1,107 +0,0 @@
package nl.astraeus.css.properties
import kotlin.math.max
import kotlin.math.min
class RgbaColor(
val red: Int,
val green: Int,
val blue: Int,
val alpha: Double = 1.0
): Color("rgba($red,$green,$blue,$alpha)") {
fun toHsla(): HslaColor = HslaColor.fromRgba(this)
companion object {
private fun findValues(value: String, expectedNumber: Int): List<String> {
check(value.contains('(') && value.contains(')'))
val parts = value.split('(')[1].split(')')[0].split(',')
check(parts.size == expectedNumber)
return parts
}
fun fromString(value: String): RgbaColor = when {
value.trim().startsWith("#") -> {
fromHex(value.substringAfterLast('#'))
}
value.trim().startsWith("hsla") -> {
val values = findValues(value, 4)
fromHsla(
values[0].toInt(),
values[1].toInt(),
values[2].toInt(),
values[3].toDouble()
)
}
value.trim().startsWith("hsl") -> {
val values = findValues(value, 3)
fromHsl(
values[0].toInt(),
values[1].toInt(),
values[2].toInt()
)
}
value.trim().startsWith("rgba") -> {
val values = findValues(value, 4)
RgbaColor(
values[0].toInt(),
values[1].toInt(),
values[2].toInt(),
values[3].toDouble()
)
}
value.trim().startsWith("rgb") -> {
val values = findValues(value, 3)
RgbaColor(
values[0].toInt(),
values[1].toInt(),
values[2].toInt()
)
}
else -> {
throw IllegalArgumentException("Unable to parse $value as color")
}
}
fun fromHex(value: String): RgbaColor {
TODO("Implement parsing hex to rgba")
}
fun fromHsla(hsla: HslaColor): RgbaColor = fromHsla(
hsla.hue,
hsla.saturation,
hsla.lightness,
hsla.alpha
)
fun fromHsl(
hue: Int,
saturation: Int,
lightness: Int
): RgbaColor {
return fromHsla(hue, saturation, lightness, 1.0)
}
fun fromHsla(
hue: Int,
saturation: Int,
lightness: Int,
alpha: Double
): RgbaColor {
val h = hue
val s = saturation / 100.0
val l = lightness / 100.0
val a = s * min(l, 1 - l)
fun f(n: Int): Int {
val k: Int = (n + h / 30) % 12
return (255 * (l - a * max(min(k - 3, min(9 - k, 1)), -1))).toInt()
}
return RgbaColor(f(0), f(8), f(4), alpha)
}
}
}

View File

@@ -30,7 +30,7 @@ private fun prp(vararg css: String): List<CssProperty> {
}
abstract class CssGenerator {
val definitions: MutableMap<String, MutableList<Css>> = mutableMapOf()
val definitions: MutableMap<String, MutableList<Css>> = LinkedHashMap()
val props: MutableMap<String, List<CssProperty>> = mutableMapOf()
abstract fun getValidator(name: String): List<Validator>?
@@ -123,7 +123,7 @@ abstract class CssGenerator {
}
}
for (name in definitions.keys.sorted()) {
for (name in definitions.keys) {
val props = definitions[name]!!
val css = StringBuilder()
@@ -140,14 +140,10 @@ abstract class CssGenerator {
css.append(finalStyle.generatePropertyCss(" $indent"))
if (css.isNotBlank()) {
if (name.startsWith(":")) {
builder.append("\n$namespace$name".trim())
} else {
builder.append("\n$namespace $name".trim())
}
builder.append("\n$namespace$name".trim())
builder.append(" $indent")
builder.append("{\n")
//builder.append(" $indent")
builder.append(" {\n")
finalStyle.fontFace?.let { ff ->
builder.append(" $indent")
@@ -192,7 +188,7 @@ abstract class CssGenerator {
builder.append("}\n\n")
}
builder.append(finalStyle.generateCss("$namespace $name".trim(), indent))
builder.append(finalStyle.generateCss("$namespace$name".trim(), indent))
}
return if (minified) {
@@ -210,6 +206,35 @@ abstract class CssGenerator {
}
}
interface DescriptionProvider {
fun description(): String
}
class ValueDescriptionProvider(
val value: String
) : DescriptionProvider {
override fun description() = value
}
fun id(name: String) : DescriptionProvider = ValueDescriptionProvider("#$name")
fun cls(name: String) : DescriptionProvider = ValueDescriptionProvider(".$name")
fun attr(name: String) : DescriptionProvider = ValueDescriptionProvider("[$name]")
fun attrEquals(name: String, value: String) : DescriptionProvider = ValueDescriptionProvider("[$name=$value]")
fun attrContains(name: String, value: String) : DescriptionProvider = ValueDescriptionProvider("[$name*=$value]")
fun attrEntriesContain(name: String, value: String) : DescriptionProvider = ValueDescriptionProvider("[$name~=$value]")
fun attrEndsWith(name: String, value: String) : DescriptionProvider = ValueDescriptionProvider("[$name$=$value]")
fun id(name: DescriptionProvider) = ValueDescriptionProvider("#${name.description()}")
fun cls(name: DescriptionProvider) : DescriptionProvider = ValueDescriptionProvider(".${name.description()}")
fun attr(name: DescriptionProvider) : DescriptionProvider = ValueDescriptionProvider("[${name.description()}]")
fun attrEquals(name: DescriptionProvider, value: String) : DescriptionProvider = ValueDescriptionProvider("[${name.description()}=$value]")
fun attrContains(name: DescriptionProvider, value: String) : DescriptionProvider = ValueDescriptionProvider("[${name.description()}*=$value]")
fun attrEntriesContain(name: DescriptionProvider, value: String) : DescriptionProvider = ValueDescriptionProvider("[${name.description()}~=$value]")
fun attrEndsWith(name: DescriptionProvider, value: String) : DescriptionProvider = ValueDescriptionProvider("[${name.description()}$=$value]")
@CssTagMarker
open class Style : CssGenerator() {
var fontFace: FontFace? = null
@@ -238,20 +263,56 @@ open class Style : CssGenerator() {
definitions[selector]?.add(style)
}
fun select(selector: String, style: Css) {
/**
* like the sccs &
* @param selector blabla
*/
fun and(selector: DescriptionProvider, style: Css) {
addStyle(selector.description(), style)
}
fun and(selector: String, style: Css) {
addStyle(selector, style)
}
fun descendant(descName: String, style: Css) {
addStyle(descName, style)
fun select(selector: DescriptionProvider, style: Css) {
addStyle(" ${selector.description()}", style)
}
fun cls(className: String, style: Css) {
addStyle(".$className", style)
fun select(selector: String, style: Css) {
addStyle(" $selector", style)
}
fun descendant(descName: DescriptionProvider, style: Css) {
addStyle(" ${descName.description()}", style)
}
fun descendant(descName: String, style: Css) {
addStyle(" $descName", style)
}
fun child(childName: DescriptionProvider, style: Css) {
addStyle(" > ${childName.description()}", style)
}
fun child(childName: String, style: Css) {
addStyle("> $childName", style)
addStyle(" > $childName", style)
}
fun sibling(childName: DescriptionProvider, style: Css) {
addStyle(" ~ ${childName.description()}", style)
}
fun sibling(childName: String, style: Css) {
addStyle(" ~ $childName", style)
}
fun adjSibling(childName: DescriptionProvider, style: Css) {
addStyle(" + ${childName.description()}", style)
}
fun adjSibling(childName: String, style: Css) {
addStyle(" + $childName", style)
}
fun active(style: Css) {
@@ -274,6 +335,10 @@ open class Style : CssGenerator() {
addStyle(":visited", style)
}
fun not(selector: DescriptionProvider, style: Css) {
addStyle(":not(${selector.description()})", style)
}
fun plain(name: String, value: String) {
props[name] = prp(value)
}
@@ -374,7 +439,7 @@ open class Style : CssGenerator() {
fun borderTopWidth(width: Measurement) { props["border-top-width"] = prp(width) }
fun bottom(measurement: Measurement) { props["bottom"] = prp(measurement) }
fun boxDecorationBreak(brk: BoxDecorationBreak) { props["box-decoration-break"] = prp(brk) }
fun boxShaduw(shadow: BoxShadow) { props["box-shaduw"] = prp(shadow) }
fun boxShadow(shadow: BoxShadow) { props["box-shadow"] = prp(shadow) }
fun boxSizing(sizing: BoxSizing) { props["box-sizing"] = prp(sizing) }
fun breakAfter(brk: Break) { props["break-after"] = prp(brk) }
fun breakBefore(brk: Break) { props["break-before"] = prp(brk) }

View File

@@ -1,71 +0,0 @@
package nl.astraeus.css
import nl.astraeus.css.properties.*
import kotlin.test.Test
class TestColor {
@Test
fun testBuilder() {
val css = style {
val baseColor = HslaColor(20, 50, 50)
val baseColorRgb = RgbaColor.fromHsla(baseColor)
println("BaseColor: ${baseColor.value} - ${baseColorRgb.value}")
val darkerColor = baseColor.darken(10)
val darkerColorRgb = RgbaColor.fromHsla(darkerColor)
println("DarkerColor: ${darkerColor.value} - ${darkerColorRgb.value}")
val saturizedColor = baseColor.saturize(10)
val saturizedColorRgb = RgbaColor.fromHsla(saturizedColor)
println("SaturizedColor: ${saturizedColor.value} - ${saturizedColorRgb.value}")
val redColor = RgbaColor(0,255,0,1.0)
val redColorHsla = redColor.toHsla()
val shiftedColor = redColorHsla.adjustHue(120)
val shiftedColorRgb = shiftedColor.toRgba()
println("RedColor: ${redColor.value} - ${redColorHsla.value}")
println("ShiftedColor: ${shiftedColor.value} - ${shiftedColorRgb.value}")
select(".test") {
top(10.px)
left(4.em)
backgroundColor(
RgbaColor.fromHsla(baseColor.darken(10))
)
animationIterationMode(
Count.auto,
Count.auto,
Count.auto,
Count.auto,
Count.auto
)
child("li") {
color(Color.hsl(200, 50, 50))
}
select("> a") {
color(Color.hsl(200, 50, 50))
}
}
}
println(css.generateCss())
val css2 = style {
cls("button") {
fontSize(12.px)
color(Color.hsl(200, 50, 50))
}
}
println(css2.generateCss())
}
}

View File

@@ -1,51 +1,151 @@
package nl.astraeus.css
import nl.astraeus.css.properties.Color.Companion.hsl
import nl.astraeus.css.properties.Color.Companion.rgba
import nl.astraeus.css.properties.Count
import nl.astraeus.css.properties.em
import nl.astraeus.css.properties.px
import nl.astraeus.css.properties.*
import nl.astraeus.css.style.attr
import nl.astraeus.css.style.attrEquals
import nl.astraeus.css.style.cls
import nl.astraeus.css.style.id
import kotlin.test.Test
object TestCssBuilder {
class TestCssBuilder {
//@Test
fun testBuilder() {
val css = css {
@Test
fun testBuilder() {
val css = style {
select(".test") {
top(10.px)
left(4.em)
backgroundColor(rgba(255, 255, 255, 0.75))
animationIterationMode(
Count.auto,
Count.auto,
Count.auto,
Count.auto,
Count.auto
)
select(".test") {
top(10.px)
left(4.em)
backgroundColor(rgba(255, 255, 255, 0.75))
animationIterationMode(
Count.auto,
Count.auto,
Count.auto,
Count.auto,
Count.auto
)
child("li") {
color(hsl(200, 50, 50))
}
select("> a") {
color(hsl(200, 50, 50))
}
hover {
color(Color.red)
}
child("li") {
listStyle("none")
child("ul") {
opacity(0.0)
display(Display.none)
paddingLeft(20.px)
child("li") {
listStyle("none")
child("ul") {
paddingLeft(30.px)
child("li") {
color(hsl(200,50,50))
}
select("> a") {
color(hsl(200, 50, 50))
listStyle("none")
}
}
}
}
}
println(css)
val css2 = css {
cls("button") {
fontSize(12.px)
color(hsl(200, 50, 50))
hover {
child("ul") {
opacity(1.0)
display(Display.block)
}
}
}
println(css2)
}
}
println(css.generateCss())
}
@Test
fun testClass() {
val css2 = style {
select(id("my-label")) {
color(Color.antiqueWhite)
}
select(cls("my-label")) {
color(Color.aliceBlue)
}
// tr.even {}
select("tr") {
and(cls("even")) {
color(Color.gray)
}
/*
nthChild(2) {
}
// not(bla) {
not("bla") {
}
*/
}
// table .even {}
select("tr") {
select(cls("even")) {
color(Color.green)
}
// [type]
select(attr("type")) {
}
// [type="checkbox"]
select(attrEquals("type", "checkbox")) {
}
// table > .odd
child(cls("odd")) {
}
//adjSibling()
}
select(cls("button")) {
fontSize(12.px)
color(hsl(200, 50, 50))
// .button:hover
hover {
color(hsl(200, 40, 40))
}
child(".green") {
color(Color.green)
}
sibling(".red") {
color(Color.red)
}
adjSibling(".blue") {
color(Color.blue)
}
}
}
println(css2.generateCss())
}
}