├── settings.gradle ├── .gitignore ├── gradle.properties ├── src ├── main │ └── azadev │ │ └── kotlin │ │ └── css │ │ ├── colors │ │ ├── colors.kt │ │ ├── HSLValues.kt │ │ └── Color.kt │ │ ├── dimens │ │ ├── LinearUnits.kt │ │ ├── dimens.kt │ │ ├── BoxDimensions.kt │ │ └── LinearDimension.kt │ │ ├── PropertyHandler.kt │ │ ├── css.kt │ │ ├── ContentPropertyHandler.kt │ │ ├── ColorPropertyHandler.kt │ │ ├── ASelector.kt │ │ ├── AttrFilter.kt │ │ ├── Selector.kt │ │ ├── keywords.kt │ │ ├── properties.kt │ │ └── Stylesheet.kt └── test │ └── azadev │ └── kotlin │ └── css │ └── test │ ├── ATest.kt │ ├── RenderToFileTest.kt │ ├── IncludeTest.kt │ └── RenderTest.kt ├── LICENSE.txt └── README.md /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'AzaKotlinCSS' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | build/ 3 | .idea/ 4 | *.iml 5 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # suppress inspection "UnusedProperty" for whole file 2 | 3 | library_version=1.0 4 | 5 | # https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib 6 | kotlin_version=1.0.6 7 | junit_version=4.9 8 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/colors/colors.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package azadev.kotlin.css.colors 4 | 5 | 6 | fun hex(hex: String) = Color.fromHex(hex)!! 7 | fun hex(hex: Int) = Color.fromHex(hex)!! 8 | fun rgb(r: Int, g: Int, b: Int) = Color.fromRgb(r, g, b) 9 | fun rgba(r: Int, g: Int, b: Int, a: Number = 1f) = Color.fromRgb(r, g, b, a.toFloat()) 10 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/colors/HSLValues.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css.colors 2 | 3 | 4 | class HSLValues( 5 | var hue: Float, 6 | var saturation: Float, 7 | var lightness: Float 8 | ) { 9 | fun setLightnessSafe(l: Float): HSLValues { 10 | lightness = Math.min(1f, Math.max(0f, l)) 11 | return this 12 | } 13 | 14 | fun setSaturationSafe(l: Float): HSLValues { 15 | saturation = Math.min(1f, Math.max(0f, l)) 16 | return this 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/dimens/LinearUnits.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css.dimens 2 | 3 | 4 | enum class LinearUnits( 5 | val value: String 6 | ) { 7 | // Relative units 8 | PX("px"), 9 | EM("em"), 10 | PERCENT("%"), 11 | EX("ex"), // The height of 'x' char 12 | 13 | // Absolute units 14 | INCH("in"), // 2.54 cm 15 | CM("cm"), 16 | MM("mm"), 17 | PT("pt"), // 1/72 in 18 | PC("pc"); // 12 pt; 19 | 20 | 21 | override fun toString() = value 22 | } 23 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/PropertyHandler.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css 2 | 3 | import kotlin.reflect.KProperty 4 | 5 | 6 | class PropertyHandler( 7 | val name: String 8 | ) { 9 | @Suppress("USELESS_CAST") 10 | operator fun getValue(stylesheet: Stylesheet, property: KProperty<*>) 11 | = stylesheet.getProperty(name) as Any? 12 | 13 | operator fun setValue(stylesheet: Stylesheet, property: KProperty<*>, value: Any?) { 14 | stylesheet.setProperty(name, value) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/azadev/kotlin/css/test/ATest.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css.test 2 | 3 | import azadev.kotlin.css.Stylesheet 4 | import org.junit.Assert.* 5 | 6 | 7 | interface ATest 8 | { 9 | fun testRender(expected: String, callback: Stylesheet.()->Unit) { 10 | val stylesheet = Stylesheet(callback) 11 | assertEquals(expected, stylesheet.render()) 12 | } 13 | 14 | fun testRender(expected: String, stylesheet: Stylesheet) { 15 | assertEquals(expected, stylesheet.render()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/css.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package azadev.kotlin.css 4 | 5 | import java.text.DecimalFormat 6 | import java.text.DecimalFormatSymbols 7 | import java.util.* 8 | 9 | 10 | fun stylesheet(body: Stylesheet.()->Unit) = body 11 | 12 | fun Stylesheet.url(str: String) = "url($str)" 13 | 14 | 15 | private val symbols = DecimalFormatSymbols(Locale.ROOT).apply { decimalSeparator = '.' } 16 | val cssDecimalFormat = DecimalFormat("#", symbols).apply { maximumFractionDigits = 5 } 17 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/ContentPropertyHandler.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css 2 | 3 | import kotlin.reflect.KProperty 4 | 5 | 6 | class ContentPropertyHandler( 7 | val name: String 8 | ) { 9 | @Suppress("USELESS_CAST") 10 | operator fun getValue(stylesheet: Stylesheet, property: KProperty<*>) 11 | = stylesheet.getProperty(name) as Any? 12 | 13 | operator fun setValue(stylesheet: Stylesheet, property: KProperty<*>, value: Any?) { 14 | stylesheet.setProperty(name, "\"${value.toString().replace("\"", "\\\"")}\"") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/ColorPropertyHandler.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css 2 | 3 | import azadev.kotlin.css.colors.hex 4 | import kotlin.reflect.KProperty 5 | 6 | 7 | class ColorPropertyHandler( 8 | val name: String 9 | ) { 10 | @Suppress("USELESS_CAST") 11 | operator fun getValue(stylesheet: Stylesheet, property: KProperty<*>) 12 | = stylesheet.getProperty(name) as Any? 13 | 14 | operator fun setValue(stylesheet: Stylesheet, property: KProperty<*>, value: Any?) { 15 | stylesheet.setProperty(name, when (value) { 16 | is Int -> hex(value) 17 | else -> value 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/ASelector.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css 2 | 3 | 4 | interface ASelector : CharSequence 5 | { 6 | override val length: Int get() = throw UnsupportedOperationException() 7 | override fun get(index: Int): Char { throw UnsupportedOperationException("not implemented") } 8 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { throw UnsupportedOperationException() } 9 | 10 | 11 | fun custom(selector: String, _spaceBefore: Boolean = true, _spaceAfter: Boolean = true, body: (Stylesheet.()->Unit)? = null): Selector 12 | 13 | fun pseudo(selector: String, body: (Stylesheet.()->Unit)? = null) = custom(selector, false, true, body) 14 | 15 | fun pseudoFn(selector: String, body: (Stylesheet.()->Unit)? = null) = custom(selector, false, true, body) 16 | } 17 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/AttrFilter.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css 2 | 3 | 4 | enum class AttrFilter( 5 | val symbol: Char? 6 | ) { 7 | EQUALS(null), // [attribute=value] 8 | 9 | CONTAINS('*'), // [attribute*=value] 10 | CONTAINS_WORD('~'), // [attribute~=value] 11 | 12 | STARTS_WITH('^'), // [attribute^=value] 13 | STARTS_WITH_WORD('|'), // [attribute|=value] 14 | 15 | ENDS_WITH('$'); // [attribute$=value] 16 | 17 | 18 | override fun toString() = symbol?.toString() ?: "" 19 | } 20 | 21 | val equals = AttrFilter.EQUALS 22 | val contains = AttrFilter.CONTAINS 23 | val containsWord = AttrFilter.CONTAINS_WORD 24 | val startsWith = AttrFilter.STARTS_WITH 25 | val startsWithWord = AttrFilter.STARTS_WITH_WORD 26 | val endsWith = AttrFilter.ENDS_WITH 27 | -------------------------------------------------------------------------------- /src/test/azadev/kotlin/css/test/RenderToFileTest.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css.test 2 | 3 | import azadev.kotlin.css.* 4 | import org.junit.* 5 | import org.junit.Assert.* 6 | import java.io.File 7 | import java.util.* 8 | 9 | 10 | class RenderToFileTest : ATest 11 | { 12 | @Test fun test() { 13 | val file = File("AzaKotlinCSS_TestFile_${Date().time}.css") 14 | file.delete() 15 | 16 | val css1 = Stylesheet { 17 | div { color = 0xffffff } 18 | } 19 | val css2 = Stylesheet { 20 | a { top = 0 } 21 | } 22 | 23 | assertFalse(file.exists()) 24 | 25 | css1.renderToFile(file) 26 | assertTrue(file.exists()) 27 | assertEquals("div{color:#fff}", file.readText()) 28 | 29 | css2.renderToFile(file.path) 30 | assertTrue(file.exists()) 31 | assertEquals("a{top:0}", file.readText()) 32 | 33 | file.delete() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/dimens/dimens.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package azadev.kotlin.css.dimens 4 | 5 | 6 | val Number.px: LinearDimension get() = dimen(this, LinearUnits.PX) 7 | val Number.em: LinearDimension get() = dimen(this, LinearUnits.EM) 8 | val Number.percent: LinearDimension get() = dimen(this, LinearUnits.PERCENT) 9 | val Number.ex: LinearDimension get() = dimen(this, LinearUnits.EX) 10 | val Number.inch: LinearDimension get() = dimen(this, LinearUnits.INCH) 11 | val Number.cm: LinearDimension get() = dimen(this, LinearUnits.CM) 12 | val Number.mm: LinearDimension get() = dimen(this, LinearUnits.MM) 13 | val Number.pt: LinearDimension get() = dimen(this, LinearUnits.PT) 14 | val Number.pc: LinearDimension get() = dimen(this, LinearUnits.PC) 15 | 16 | @Suppress("NOTHING_TO_INLINE") 17 | inline private fun dimen(value: Number, units: LinearUnits) = LinearDimension(value.toFloat(), units) 18 | 19 | 20 | fun box(vararg args: Any) = BoxDimensions.from(*args) 21 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/dimens/BoxDimensions.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css.dimens 2 | 3 | import azadev.kotlin.css.dimens.LinearDimension.Companion.from as dimen 4 | 5 | 6 | class BoxDimensions( 7 | var top: LinearDimension = 0.px, 8 | var right: LinearDimension = top, 9 | var bottom: LinearDimension = top, 10 | var left: LinearDimension = right 11 | ) { 12 | override fun toString(): String { 13 | return when { 14 | top == right && top == bottom && top == left -> top.toString() 15 | top == bottom && left == right -> "$top $right" 16 | left == right -> "$top $right $bottom" 17 | else -> "$top $right $bottom $left" 18 | } 19 | } 20 | 21 | 22 | companion object 23 | { 24 | fun from(vararg args: Any): BoxDimensions { 25 | return when (args.size) { 26 | 1 -> BoxDimensions(dimen(args[0])) 27 | 2 -> BoxDimensions(dimen(args[0]), dimen(args[1])) 28 | 3 -> BoxDimensions(dimen(args[0]), dimen(args[1]), dimen(args[2])) 29 | else -> BoxDimensions(dimen(args[0]), dimen(args[1]), dimen(args[2]), dimen(args[3])) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Oleg Cherr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/azadev/kotlin/css/test/IncludeTest.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css.test 2 | 3 | import azadev.kotlin.css.* 4 | import org.junit.* 5 | 6 | 7 | class IncludeTest : ATest 8 | { 9 | @Test fun basic() { 10 | val css1 = Stylesheet { 11 | a { color = 0xffffff } 12 | } 13 | val css2 = Stylesheet { 14 | a { color = 0xf2cacf } 15 | } 16 | 17 | testRender("a{color:#fff}a{color:#f2cacf}", css1.include(css2)) 18 | } 19 | 20 | @Test fun mixins() { 21 | val hoverLinks = stylesheet { 22 | a.hover { color = 0xf2cacf } 23 | } 24 | 25 | val css = Stylesheet { 26 | a { color = 0xffffff } 27 | hoverLinks() 28 | a.active { color = 0xff0000 } 29 | } 30 | 31 | testRender("a{color:#fff}a:hover{color:#f2cacf}a:active{color:#f00}", css) 32 | } 33 | 34 | @Test fun mixins2() { 35 | val clrfix = stylesheet { 36 | zoom = 1 37 | after { 38 | content = " " 39 | display = "block" 40 | clear = "both" 41 | } 42 | } 43 | 44 | val css = Stylesheet { 45 | div { 46 | margin = 0 47 | clrfix() 48 | } 49 | } 50 | 51 | testRender("div{margin:0;zoom:1}div:after{content:\" \";display:block;clear:both}", css) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/dimens/LinearDimension.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package azadev.kotlin.css.dimens 4 | 5 | import azadev.kotlin.css.cssDecimalFormat 6 | 7 | 8 | class LinearDimension( 9 | var value: Float, 10 | var units: LinearUnits 11 | ) { 12 | override fun toString(): String { 13 | val str = cssDecimalFormat.format(value)!! 14 | return if (str == "0") str else "$str$units" 15 | } 16 | 17 | 18 | companion object 19 | { 20 | fun from(value: Any): LinearDimension { 21 | return when (value) { 22 | is Number -> value.px 23 | is String -> fromString(value) 24 | is LinearDimension -> value 25 | else -> throw IllegalArgumentException("Cannot create LinearDimension from ${value.javaClass.simpleName}") 26 | } 27 | } 28 | 29 | fun fromString(s: String): LinearDimension { 30 | if (s.endsWith('%')) 31 | return LinearDimension(s.dropLast(1).toFloat(), LinearUnits.PERCENT) 32 | 33 | val units = when { 34 | s.endsWith("px") -> LinearUnits.PX 35 | s.endsWith("em") -> LinearUnits.EM 36 | s.endsWith("ex") -> LinearUnits.EX 37 | 38 | s.endsWith("in") -> LinearUnits.INCH 39 | s.endsWith("cm") -> LinearUnits.CM 40 | s.endsWith("mm") -> LinearUnits.MM 41 | s.endsWith("pt") -> LinearUnits.PT 42 | s.endsWith("pc") -> LinearUnits.PC 43 | 44 | else -> null 45 | } 46 | 47 | if (units != null) 48 | return LinearDimension(s.dropLast(2).toFloat(), units) 49 | 50 | return LinearDimension(s.toFloat(), LinearUnits.PX) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/Selector.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css 2 | 3 | import java.util.* 4 | 5 | 6 | class Selector( 7 | var stylesheet: Stylesheet 8 | ) : ASelector 9 | { 10 | var rows = ArrayList(1) 11 | 12 | 13 | operator fun invoke(body: Stylesheet.()->Unit): Stylesheet { 14 | stylesheet.body() 15 | return stylesheet 16 | } 17 | 18 | override fun custom(selector: String, _spaceBefore: Boolean, _spaceAfter: Boolean, body: (Stylesheet.() -> Unit)?): Selector { 19 | if (rows.isEmpty()) 20 | rows.add(Row(selector, _spaceBefore, _spaceAfter)) 21 | 22 | else for (row in rows) 23 | row.append(selector, _spaceBefore, _spaceAfter) 24 | 25 | body?.invoke(stylesheet) 26 | return this 27 | } 28 | 29 | fun append(obj: ASelector): Selector { 30 | when (obj) { 31 | is Selector -> { 32 | val newRows = ArrayList(rows.size * obj.rows.size) 33 | for (r1 in rows) 34 | for (r2 in obj.rows) { 35 | val r = Row(r1.sb, r1.spaceBefore, r1.spaceAfter) 36 | r.append(r2.sb, r2.spaceBefore, r2.spaceAfter) 37 | newRows.add(r) 38 | } 39 | rows = newRows 40 | obj.stylesheet.moveDataTo(stylesheet) 41 | } 42 | is Stylesheet -> { 43 | append(obj.selector!!) 44 | obj.moveDataTo(stylesheet) 45 | } 46 | } 47 | return this 48 | } 49 | 50 | 51 | fun toList(selectorPrefix: CharSequence, _spaceBefore: Boolean) = rows.map { it.toString(selectorPrefix, _spaceBefore) } 52 | 53 | fun toString(selectorPrefix: CharSequence, _spaceBefore: Boolean) = toList(selectorPrefix, _spaceBefore).joinToString(",") 54 | override fun toString() = toString("", true) 55 | 56 | 57 | class Row( 58 | str: CharSequence, 59 | var spaceBefore: Boolean = true, 60 | var spaceAfter: Boolean = true 61 | ) { 62 | val sb = StringBuilder(str) 63 | 64 | 65 | fun append(str: CharSequence, _spaceBefore: Boolean, _spaceAfter: Boolean) { 66 | if (sb.isEmpty()) 67 | spaceBefore = _spaceBefore 68 | else if (_spaceBefore && spaceAfter) 69 | sb.append(' ') 70 | 71 | sb.append(str) 72 | 73 | spaceAfter = _spaceAfter 74 | } 75 | 76 | 77 | fun toString(selectorPrefix: CharSequence, _spaceBefore: Boolean): String { 78 | return buildString { 79 | if (selectorPrefix.isNotEmpty()) { 80 | append(selectorPrefix) 81 | if (_spaceBefore && spaceBefore) append(' ') 82 | } 83 | append(sb) 84 | } 85 | } 86 | 87 | override fun toString() = sb.toString() 88 | } 89 | 90 | 91 | companion object 92 | { 93 | fun createEmpty(stylesheet: Stylesheet) = Selector(stylesheet).apply { rows.add(Row("", false, true)) } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/colors/Color.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package azadev.kotlin.css.colors 4 | 5 | import azadev.kotlin.css.cssDecimalFormat 6 | 7 | 8 | class Color( 9 | var red: Float, 10 | var green: Float, 11 | var blue: Float, 12 | var alpha: Float = 1f 13 | ) { 14 | var redInt: Int 15 | get() = (red * 255f).toInt() 16 | set(value) { red = value.toFloat() / 255f } 17 | 18 | var greenInt: Int 19 | get() = (green * 255f).toInt() 20 | set(value) { green = value.toFloat() / 255f } 21 | 22 | var blueInt: Int 23 | get() = (blue * 255f).toInt() 24 | set(value) { blue = value.toFloat() / 255f } 25 | 26 | var alphaInt: Int 27 | get() = (alpha * 255f).toInt() 28 | set(value) { alpha = value.toFloat() / 255f } 29 | 30 | 31 | fun copy() = Color(red, green, blue) 32 | 33 | 34 | fun toHexString(): String { 35 | val res = "#${redInt.twoDigitHex()}${greenInt.twoDigitHex()}${blueInt.twoDigitHex()}" 36 | var i = 1 37 | 38 | while (i <= 5) { 39 | if (res[i] != res[i+1]) return res 40 | i += 2 41 | } 42 | 43 | return "#${res[1]}${res[3]}${res[5]}" 44 | } 45 | 46 | override fun toString() = when { 47 | alpha < 1f -> "rgba($redInt,$greenInt,$blueInt,${cssDecimalFormat.format(alpha)})" 48 | else -> toHexString() 49 | } 50 | 51 | 52 | private fun Int.twoDigitHex() 53 | = (if (this < 16) "0" else "") + Integer.toHexString(this) 54 | 55 | 56 | fun toHSL(): HSLValues { 57 | val max = Math.max(Math.max(red, green), blue) 58 | val min = Math.min(Math.min(red, green), blue) 59 | val avg = (max + min) / 2 60 | val hsl = HSLValues(avg, avg, avg) 61 | 62 | if (max == min) { 63 | // achromatic 64 | hsl.hue = 0f 65 | hsl.saturation = 0f 66 | } 67 | else { 68 | val d = max - min 69 | if (hsl.lightness > .5f) 70 | hsl.saturation = d / (2 - max - min) 71 | else 72 | hsl.saturation = d / (max + min) 73 | 74 | when (max) { 75 | red -> { 76 | hsl.hue = (green - blue) / d 77 | if (green < blue) 78 | hsl.hue += 6f 79 | } 80 | green -> hsl.hue = (blue - red) / d + 2 81 | blue -> hsl.hue = (red - green) / d + 4 82 | } 83 | 84 | hsl.hue /= 6f 85 | } 86 | 87 | return hsl 88 | } 89 | 90 | fun setHsl(hsl: HSLValues): Color { 91 | if (hsl.saturation == 0f) { 92 | // achromatic 93 | red = hsl.lightness 94 | green = hsl.lightness 95 | blue = hsl.lightness 96 | } 97 | else { 98 | val q = if (hsl.lightness < .5f) 99 | hsl.lightness * (1f + hsl.saturation) 100 | else 101 | hsl.lightness + hsl.saturation - hsl.lightness * hsl.saturation 102 | 103 | val p = 2f * hsl.lightness - q 104 | red = hue2rgb(p, q, hsl.hue + 1f / 3f) 105 | green = hue2rgb(p, q, hsl.hue) 106 | blue = hue2rgb(p, q, hsl.hue - 1f / 3f) 107 | } 108 | 109 | return this 110 | } 111 | 112 | private fun hue2rgb(p: Float, q: Float, _t: Float): Float { 113 | var t = _t 114 | if (t < 0f) 115 | t += 1f 116 | if (t > 1f) 117 | t -= 1f 118 | if (t < 1f / 6f) 119 | return p + (q - p) * 6f * t 120 | if (t < .5f) 121 | return q 122 | if (t < 2f / 3f) 123 | return p + (q - p) * (2f / 3f - t) * 6f 124 | return p 125 | } 126 | 127 | 128 | // Color adjustment (values should be between 0 and 1) 129 | 130 | fun lighten(dl: Float): Color { 131 | val hsl = toHSL() 132 | hsl.setLightnessSafe(hsl.lightness + dl) 133 | return setHsl(hsl) 134 | } 135 | 136 | fun darken(dl: Float): Color { 137 | val hsl = toHSL() 138 | hsl.setLightnessSafe(hsl.lightness - dl) 139 | return setHsl(hsl) 140 | } 141 | 142 | fun saturate(dl: Float): Color { 143 | val hsl = toHSL() 144 | hsl.setSaturationSafe(hsl.lightness + dl) 145 | return setHsl(hsl) 146 | } 147 | 148 | fun desaturate(dl: Float): Color { 149 | val hsl = toHSL() 150 | hsl.setSaturationSafe(hsl.lightness - dl) 151 | return setHsl(hsl) 152 | } 153 | 154 | 155 | companion object 156 | { 157 | fun fromRgb(red: Int, green: Int, blue: Int, alpha: Float = 1f) 158 | = Color(red.toFloat() / 255f, green.toFloat() / 255f, blue.toFloat() / 255f, alpha) 159 | 160 | fun fromHex(_s: String): Color? { 161 | val s = if (_s[0] == '#') _s.drop(1) else _s 162 | 163 | // In CSS4 the alpha channel comes last: 164 | // https://www.w3.org/TR/css-color-4/#hex-notation 165 | return when (s.length) { 166 | 1 -> { // 0x00f 167 | val b = Integer.parseInt(s[0].toString(), 16) 168 | return fromRgb(0, 0, b * 16 + b) 169 | } 170 | 2 -> { // 0x0f0 171 | val g = Integer.parseInt(s[0].toString(), 16) 172 | val b = Integer.parseInt(s[1].toString(), 16) 173 | return fromRgb(0, g * 16 + g, b * 16 + b) 174 | } 175 | 3 -> { 176 | val r = Integer.parseInt(s[0].toString(), 16) 177 | val g = Integer.parseInt(s[1].toString(), 16) 178 | val b = Integer.parseInt(s[2].toString(), 16) 179 | return fromRgb(r * 16 + r, g * 16 + g, b * 16 + b) 180 | } 181 | 4 -> { 182 | val r = Integer.parseInt(s[0].toString(), 16) 183 | val g = Integer.parseInt(s[1].toString(), 16) 184 | val b = Integer.parseInt(s[2].toString(), 16) 185 | val a = Integer.parseInt(s[3].toString(), 16) 186 | return fromRgb(r * 16 + r, g * 16 + g, b * 16 + b, 255f / a * 16 + a) 187 | } 188 | 5 -> { // 0x0faabb 189 | val r = Integer.parseInt(s.substring(0, 1), 16) 190 | val g = Integer.parseInt(s.substring(1, 3), 16) 191 | val b = Integer.parseInt(s.substring(3, 5), 16) 192 | return fromRgb(r, g, b) 193 | } 194 | 6 -> { 195 | val r = Integer.parseInt(s.substring(0, 2), 16) 196 | val g = Integer.parseInt(s.substring(2, 4), 16) 197 | val b = Integer.parseInt(s.substring(4, 6), 16) 198 | return fromRgb(r, g, b) 199 | } 200 | 7 -> { // 0x0faabbcc 201 | val r = Integer.parseInt(s.substring(0, 1), 16) 202 | val g = Integer.parseInt(s.substring(1, 3), 16) 203 | val b = Integer.parseInt(s.substring(3, 5), 16) 204 | val a = Integer.parseInt(s.substring(5, 7), 16) 205 | return fromRgb(r, g, b, a / 255f) 206 | } 207 | 8 -> { 208 | val r = Integer.parseInt(s.substring(0, 2), 16) 209 | val g = Integer.parseInt(s.substring(2, 4), 16) 210 | val b = Integer.parseInt(s.substring(4, 6), 16) 211 | val a = Integer.parseInt(s.substring(6, 8), 16) 212 | return fromRgb(r, g, b, a / 255f) 213 | } 214 | else -> null 215 | } 216 | } 217 | fun fromHex(value: Int) = fromHex(Integer.toHexString(value).padStart(6, '0')) 218 | 219 | fun fromHSL(hsl: HSLValues): Color { 220 | val color = Color(0f, 0f, 0f) 221 | color.setHsl(hsl) 222 | return color 223 | } 224 | } 225 | } 226 | 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/keywords.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package azadev.kotlin.css 4 | 5 | 6 | // https://www.w3.org/TR/CSS21/propidx.html 7 | // https://www.w3.org/TR/CSS21/syndata.html#color-units 8 | // https://hackage.haskell.org/package/highlighting-kate-0.6.2.1/src/xml/css.xml 9 | 10 | const val EVEN = "even" 11 | const val ODD = "odd" 12 | 13 | const val _EPUB_HYPHENS = "-epub-hyphens" 14 | const val ABOVE = "above" 15 | const val ABSOLUTE = "absolute" 16 | const val ALWAYS = "always" 17 | const val ARMENIAN = "armenian" 18 | const val AUTO = "auto" 19 | const val AVOID = "avoid" 20 | const val BASELINE = "baseline" 21 | const val BELOW = "below" 22 | const val BIDI_OVERRIDE = "bidi-override" 23 | const val BLINK = "blink" 24 | const val BLOCK = "block" 25 | const val BOLD = "bold" 26 | const val BOLDER = "bolder" 27 | const val BORDER_BOX = "border-box" 28 | const val BOTH = "both" 29 | const val BOTTOM = "bottom" 30 | const val BOX = "box" 31 | const val BREAK = "break" 32 | const val CAPITALIZE = "capitalize" 33 | const val CAPTION = "caption" 34 | const val CENTER = "center" 35 | const val CIRCLE = "circle" 36 | const val CJK_IDEOGRAPHIC = "cjk-ideographic" 37 | const val CLIP = "clip" 38 | const val CLOSE_QUOTE = "close-quote" 39 | const val COLLAPSE = "collapse" 40 | const val COMPACT = "compact" 41 | const val CONDENSED = "condensed" 42 | const val CONTENT_BOX = "content-box" 43 | const val CROP = "crop" 44 | const val CROSS = "cross" 45 | const val CROSSHAIR = "crosshair" 46 | const val CURSIVE = "cursive" 47 | const val DASHED = "dashed" 48 | const val DECIMAL = "decimal" 49 | const val DECIMAL_LEADING_ZERO = "decimal-leading-zero" 50 | const val DEFAULT = "default" 51 | const val DISC = "disc" 52 | const val DOTTED = "dotted" 53 | const val DOUBLE = "double" 54 | const val E_RESIZE = "e-resize" 55 | const val ELLIPSIS = "ellipsis" 56 | const val ELLIPSIS_WORD = "ellipsis-word" 57 | const val EMBED = "embed" 58 | const val EXPANDED = "expanded" 59 | const val EXTRA_CONDENSED = "extra-condensed" 60 | const val EXTRA_EXPANDED = "extra-expanded" 61 | const val FANTASY = "fantasy" 62 | const val FIXED = "fixed" 63 | const val GEORGIAN = "georgian" 64 | const val GROOVE = "groove" 65 | const val HAND = "hand" 66 | const val HEBREW = "hebrew" 67 | const val HELP = "help" 68 | const val HIDDEN = "hidden" 69 | const val HIDE = "hide" 70 | const val HIGHER = "higher" 71 | const val HIRAGANA = "hiragana" 72 | const val HIRAGANA_IROHA = "hiragana-iroha" 73 | const val ICON = "icon" 74 | const val INHERIT = "inherit" 75 | const val INLINE = "inline" 76 | const val INLINE_BLOCK = "inline-block" 77 | const val INLINE_TABLE = "inline-table" 78 | const val INSET = "inset" 79 | const val INSIDE = "inside" 80 | const val INVERT = "invert" 81 | const val ITALIC = "italic" 82 | const val JUSTIFY = "justify" 83 | const val KATAKANA = "katakana" 84 | const val KATAKANA_IROHA = "katakana-iroha" 85 | const val KONQ_CENTER = "konq-center" 86 | const val LANDSCAPE = "landscape" 87 | const val LARGE = "large" 88 | const val LARGER = "larger" 89 | const val LEFT = "left" 90 | const val LEVEL = "level" 91 | const val LIGHT = "light" 92 | const val LIGHTER = "lighter" 93 | const val LINE_THROUGH = "line-through" 94 | const val LIST_ITEM = "list-item" 95 | const val LOUD = "loud" 96 | const val LOWER = "lower" 97 | const val LOWER_ALPHA = "lower-alpha" 98 | const val LOWER_GREEK = "lower-greek" 99 | const val LOWER_LATIN = "lower-latin" 100 | const val LOWER_ROMAN = "lower-roman" 101 | const val LOWERCASE = "lowercase" 102 | const val LTR = "ltr" 103 | const val MARKER = "marker" 104 | const val MEDIUM = "medium" 105 | const val MENU = "menu" 106 | const val MESSAGE_BOX = "message-box" 107 | const val MIDDLE = "middle" 108 | const val MIX = "mix" 109 | const val MONOSPACE = "monospace" 110 | const val MOVE = "move" 111 | const val N_RESIZE = "n-resize" 112 | const val NARROWER = "narrower" 113 | const val NE_RESIZE = "ne-resize" 114 | const val NO_CLOSE_QUOTE = "no-close-quote" 115 | const val NO_OPEN_QUOTE = "no-open-quote" 116 | const val NO_REPEAT = "no-repeat" 117 | const val NONE = "none" 118 | const val NORMAL = "normal" 119 | const val NOWRAP = "nowrap" 120 | const val NW_RESIZE = "nw-resize" 121 | const val OBLIQUE = "oblique" 122 | const val OPEN_QUOTE = "open-quote" 123 | const val OUTSET = "outset" 124 | const val OUTSIDE = "outside" 125 | const val OVERLINE = "overline" 126 | const val POINTER = "pointer" 127 | const val PORTRAIT = "portrait" 128 | const val PRE = "pre" 129 | const val PRE_LINE = "pre-line" 130 | const val PRE_WRAP = "pre-wrap" 131 | const val RELATIVE = "relative" 132 | const val REPEAT = "repeat" 133 | const val REPEAT_X = "repeat-x" 134 | const val REPEAT_Y = "repeat-y" 135 | const val RIDGE = "ridge" 136 | const val RIGHT = "right" 137 | const val RTL = "rtl" 138 | const val RUN_IN = "run-in" 139 | const val S_RESIZE = "s-resize" 140 | const val SANS_SERIF = "sans-serif" 141 | const val SCROLL = "scroll" 142 | const val SE_RESIZE = "se-resize" 143 | const val SEMI_CONDENSED = "semi-condensed" 144 | const val SEMI_EXPANDED = "semi-expanded" 145 | const val SEPARATE = "separate" 146 | const val SERIF = "serif" 147 | const val SHOW = "show" 148 | const val SMALL = "small" 149 | const val SMALL_CAPS = "small-caps" 150 | const val SMALL_CAPTION = "small-caption" 151 | const val SMALLER = "smaller" 152 | const val SOLID = "solid" 153 | const val SQUARE = "square" 154 | const val STATIC = "static" 155 | const val STATIC_POSITION = "static-position" 156 | const val STATUS_BAR = "status-bar" 157 | const val SUB = "sub" 158 | const val SUPER = "super" 159 | const val SW_RESIZE = "sw-resize" 160 | const val TABLE = "table" 161 | const val TABLE_CAPTION = "table-caption" 162 | const val TABLE_CELL = "table-cell" 163 | const val TABLE_COLUMN = "table-column" 164 | const val TABLE_COLUMN_GROUP = "table-column-group" 165 | const val TABLE_FOOTER_GROUP = "table-footer-group" 166 | const val TABLE_HEADER_GROUP = "table-header-group" 167 | const val TABLE_ROW = "table-row" 168 | const val TABLE_ROW_GROUP = "table-row-group" 169 | const val TEXT = "text" 170 | const val TEXT_BOTTOM = "text-bottom" 171 | const val TEXT_TOP = "text-top" 172 | const val THICK = "thick" 173 | const val THIN = "thin" 174 | const val TOP = "top" 175 | const val TRANSPARENT = "transparent" 176 | const val ULTRA_CONDENSED = "ultra-condensed" 177 | const val ULTRA_EXPANDED = "ultra-expanded" 178 | const val UNDERLINE = "underline" 179 | const val UPPER_ALPHA = "upper-alpha" 180 | const val UPPER_LATIN = "upper-latin" 181 | const val UPPER_ROMAN = "upper-roman" 182 | const val UPPERCASE = "uppercase" 183 | const val VISIBLE = "visible" 184 | const val W_RESIZE = "w-resize" 185 | const val WAIT = "wait" 186 | const val WIDER = "wider" 187 | const val X_LARGE = "x-large" 188 | const val X_SMALL = "x-small" 189 | const val XX_LARGE = "xx-large" 190 | const val XX_SMALL = "xx-small" 191 | 192 | const val AQUA = "aqua" 193 | const val BLACK = "black" 194 | const val BLUE = "blue" 195 | const val CYAN = "cyan" 196 | const val FUCHSIA = "fuchsia" 197 | const val GRAY = "gray" 198 | const val GREEN = "green" 199 | const val LIME = "lime" 200 | const val MAROON = "maroon" 201 | const val NAVY = "navy" 202 | const val OLIVE = "olive" 203 | const val PURPLE = "purple" 204 | const val RED = "red" 205 | const val SILVER = "silver" 206 | const val TEAL = "teal" 207 | const val WHITE = "white" 208 | const val YELLOW = "yellow" 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AzaKotlinCSS 2 | 3 | AzaKotlinCSS is a DSL (Domain-specific language) designed for writing CSS using Kotlin – the greatest programming language in the World! :boom::fire::+1: 4 | 5 | ### Installation 6 | 7 | ```gradle 8 | repositories { 9 | jcenter() 10 | } 11 | 12 | dependencies { 13 | compile 'azadev.kotlin:aza-kotlin-css:1.0' 14 | } 15 | ``` 16 | 17 | ### Usage 18 | 19 | ```kotlin 20 | val css = Stylesheet { 21 | a { 22 | width = 10.px 23 | color = 0xffffff 24 | opacity = .8 25 | 26 | hover { 27 | color = 0xf2cacf 28 | } 29 | } 30 | } 31 | 32 | css.render() 33 | // This will produce the following CSS: 34 | // a{width:10px;color:#fff;opacity:.8}a:hover{color:#f2cacf} 35 | ``` 36 | 37 | In addition, there are 2 other rendering methods: `renderTo(StringBuilder)` and `renderToFile(File|String)`: 38 | 39 | ```kotlin 40 | css.renderTo(builder) 41 | // Will append CSS to an exsisting StringBuilder 42 | 43 | css.renderToFile("style.css") 44 | css.renderToFile(File("style.css")) 45 | // Will render CSS to the file 46 | ``` 47 | 48 | ## Selectors 49 | 50 | AzaKotlinCSS is able to construct very complex selectors with a huge portion of syntax sugar. 51 | 52 | ```kotlin 53 | Stylesheet { 54 | div { top = 0 } 55 | // div{top:0} 56 | 57 | a.hover { top = 0 } 58 | // a:hover{top:0} 59 | 60 | div and span { top = 0 } 61 | // div,span{top:0} 62 | 63 | li.nthChild(2) { top = 0 } 64 | // li:nth-child(2){top:0} 65 | 66 | input["disabled"] { top = 0 } 67 | // input[disabled]{top:0} 68 | } 69 | ``` 70 | 71 | Below I'll show you more detailed examples of building CSS selectors using the DSL. 72 | 73 | ### Tags 74 | 75 | ```kotlin 76 | Stylesheet { 77 | a { top = 0 } 78 | // a{top:0} 79 | 80 | div.span.a { top = 0 } 81 | // div span a{top:0} 82 | 83 | div and span and ul.li { top = 0 } 84 | // div,span,ul li{top:0} 85 | } 86 | ``` 87 | 88 | ### Classes and Ids 89 | 90 | You can define class-selectors in several ways: 91 | 92 | ```kotlin 93 | Stylesheet { 94 | ".logo" { top = 0 } 95 | c("logo") { top = 0 } 96 | } 97 | // .logo{top:0} 98 | ``` 99 | 100 | Id-properties can be declared similarly: 101 | 102 | ```kotlin 103 | Stylesheet { 104 | "#logo" { top = 0 } 105 | id("logo") { top = 0 } 106 | } 107 | // #logo{top:0} 108 | ``` 109 | 110 | Of cource you can combine them as you need: 111 | 112 | ```kotlin 113 | Stylesheet { 114 | ".class1.class2" { top = 0 } 115 | // .class1.class2{top:0} 116 | 117 | "#logo.class1" { top = 0 } 118 | // #logo.class1{top:0} 119 | 120 | "#logo"..".class1"..span { top = 0 } 121 | // #logo .class1 span{top:0} 122 | } 123 | ``` 124 | 125 | ### Pseudo classes and elements 126 | 127 | ```kotlin 128 | Stylesheet { 129 | a.hover { top = 0 } 130 | // a:hover{top:0} 131 | 132 | "#logo".firstLetter { top = 0 } 133 | // #logo:first-letter{top:0} 134 | } 135 | ``` 136 | 137 | By now AzaKotlinCSS is using single `:` for all the pseudo elements to be friendly with IE8. But in the future it will be replaced with `::`. 138 | 139 | Some more examples: 140 | 141 | ```kotlin 142 | Stylesheet { 143 | div.nthChild(2) { top = 0 } 144 | // div:nth-child(2){top:0} 145 | 146 | div.nthChild(EVEN) { top = 0 } 147 | // div:nth-child(even){top:0} 148 | 149 | any.not(lastChild) { top = 0 } 150 | // *:not(:last-child){top:0} 151 | 152 | "items".not(li) { top = 0 } 153 | // .items:not(li){top:0} 154 | } 155 | ``` 156 | 157 | ### Traversing 158 | 159 | ```kotlin 160 | Stylesheet { 161 | div.span { top = 0 } 162 | div..span { top = 0 } 163 | div.children.span { top = 0 } 164 | // div span{top:0} 165 | 166 | div / span { top = 0 } 167 | div.child.span { top = 0 } 168 | // div>span{top:0} 169 | 170 | div % span { top = 0 } 171 | div.next.span { top = 0 } 172 | // div+span{top:0} 173 | 174 | div - span { top = 0 } 175 | div.nextAll.span { top = 0 } 176 | // div~span{top:0} 177 | } 178 | ``` 179 | 180 | ### Attributes 181 | 182 | ```kotlin 183 | Stylesheet { 184 | input["disabled"] { top = 0 } 185 | // input[disabled]{top:0} 186 | 187 | input["type", "hidden"] { top = 0 } 188 | // input[type=hidden]{top:0} 189 | 190 | input["type", "hidden"]["disabled"] { top = 0 } 191 | // input[type=hidden][disabled]{top:0} 192 | 193 | a["href", startsWith, "http://"] { top = 0 } 194 | // a[href^="http://"]{top:0} 195 | 196 | "#logo"["type", "main"] { top = 0 } 197 | // #logo[type=main]{top:0} 198 | 199 | attr("disabled") { top = 0 } 200 | // [disabled]{top:0} 201 | } 202 | ``` 203 | 204 | ### Nesting 205 | 206 | AzaKotlinCSS supports any nesting you can even imagine. 207 | 208 | ```kotlin 209 | Stylesheet { 210 | div { 211 | width = AUTO 212 | 213 | a { 214 | color = 0xffffff 215 | 216 | hover { 217 | color = 0xff0000 218 | } 219 | } 220 | } 221 | } 222 | // div{width:auto}div a{color:#fff}div a:hover{color:#f00} 223 | ``` 224 | 225 | ```kotlin 226 | Stylesheet { 227 | div { 228 | color = 0xffffff 229 | 230 | b and strong { 231 | color = 0xff0000 232 | } 233 | 234 | child.span { 235 | color = 0x00ff00 236 | } 237 | } 238 | } 239 | // div{color:#fff}div b,div strong{color:#f00}div>span{color:#0f0} 240 | ``` 241 | 242 | ## Dimensions 243 | 244 | This DSL provides all the major dimension units: `px`, `em`, `percent`, `ex`, `inch`, `cm`, `mm`, `pt`, `pc`. 245 | 246 | ```kotlin 247 | Stylesheet { 248 | width = AUTO 249 | // width:auto 250 | 251 | width = 10.px 252 | // width:10px 253 | 254 | width = .2.em 255 | // width:.2em 256 | 257 | width = 50.percent 258 | // width:50% 259 | 260 | width = 17.257.ex 261 | // width:17.257ex 262 | 263 | width = 1.55555.inch 264 | // width:1.55555in 265 | } 266 | ``` 267 | 268 | AzaKotlinCSS also has the convenient `box` helper: 269 | 270 | ```kotlin 271 | Stylesheet { 272 | padding = box(10, 5, 0, 20) 273 | // padding:10px 5px 0 20px 274 | 275 | padding = box(10.ex, 5.percent) 276 | // padding:10ex 5% 277 | 278 | padding = box(10, 10, 10, 10) 279 | // padding:10px 280 | 281 | padding = box(10) 282 | // padding:10px 283 | } 284 | ``` 285 | 286 | As you can see, values without an explicitly defined dimension will be treated as `px`. 287 | 288 | Also don't forget that you can use dimensions directly, without `box`. For example: `padding = 10.px`. 289 | 290 | ## Colors 291 | 292 | To define a color you'll probably will be glad to use a hexademical notation of `Integer`. It's really convinient and looks almost exacly like CSS: 293 | 294 | ```kotlin 295 | Stylesheet { 296 | a { color = 0xf2cacf } 297 | // a{color:#f2cacf} 298 | 299 | a { color = 0xffffff } 300 | // a{color:#fff} 301 | } 302 | ``` 303 | 304 | Note that 3-digit hex-values will be considered as 6-digit hex with 3 zeros at the beginning. 305 | For example `0xfff` will be treated as `0x000fff`. There is nothing we can do with it since, as you remember, it's a hex representation of `Integer`. 306 | 307 | AzaKotlinCSS also provides `rgb(a)` and `hex` color-helpers: 308 | 309 | ```kotlin 310 | Stylesheet { 311 | a { color = rgb(0,10,255) } 312 | // a{color:#000aff} 313 | 314 | a { color = rgba(255, 255, 255, .47) } 315 | // a{color:rgba(255,255,255,.47)} 316 | 317 | a { color = hex(0xf2cacf) } 318 | // a{color:#f2cacf} 319 | 320 | a { color = hex("#f00") } 321 | // a{color:#f00} 322 | } 323 | ``` 324 | 325 | As the example shows, the `hex` helper supports shorthand color notations. 326 | 327 | ## @ At-rules 328 | 329 | AzaKotlinCSS lets you create at-rules using (guess what!) the `at` helper: 330 | 331 | ```kotlin 332 | Stylesheet { 333 | at("keyframes animation1") { 334 | "from" { top = 0 } 335 | "30%" { top = 50.px } 336 | "68%,72%" { top = 70.px } 337 | "to" { top = 100.px } 338 | } 339 | } 340 | // @keyframes animation1{from{top:0}30%{top:50px}68%,72%{top:70px}to{top:100px}} 341 | ``` 342 | 343 | ```kotlin 344 | Stylesheet { 345 | at("font-face") { 346 | fontFamily = "Bitstream Vera Serif Bold" 347 | src = url("VeraSeBd.ttf") 348 | fontWeight = BOLD 349 | } 350 | } 351 | // @font-face{font-family:Bitstream Vera Serif Bold;src:url(VeraSeBd.ttf);font-weight:bold} 352 | ``` 353 | 354 | For media queries the DSL provides convenient `media` helper, that joins all the passed arguments using the `and` operator: 355 | 356 | ```kotlin 357 | Stylesheet { 358 | media("min-width: 100px", "orientation: landscape") { 359 | div { top = 0 } 360 | } 361 | } 362 | // @media (min-width: 100px) and (orientation: landscape){div{top:0}} 363 | ``` 364 | 365 | But of course, you can still use the `at` helper for complex rules: 366 | 367 | ```kotlin 368 | Stylesheet { 369 | at("media not screen and (color), print and (color)") { 370 | div { top = 0 } 371 | } 372 | } 373 | // @media not screen and (color), print and (color){div{top:0}} 374 | ``` 375 | 376 | Also note that you can easily use `media` as a nested rule. It will be pushed to the top of its hierarchy and will have all the selectors it was called within: 377 | 378 | ```kotlin 379 | Stylesheet { 380 | div { 381 | top = 0 382 | 383 | media("min-width: 100px") { 384 | top = 1 385 | } 386 | } 387 | } 388 | // div{top:0}@media (min-width: 100px){div{top:1}} 389 | ``` 390 | 391 | And one more useful tip. Didn't you forget that all this stuff is written on Kotlin? This means that you can bravely create any extension-methods you need. 392 | 393 | For example, let's create a custom media-rule, to use it later in several places and be able to change the rule any time you want: 394 | 395 | ```kotlin 396 | fun Stylesheet.myMediaQuery(body: Stylesheet.()->Unit) 397 | = media("min-width: 100px", "orientation: landscape").invoke(body) 398 | 399 | Stylesheet { 400 | myMediaQuery { 401 | div { top = 0 } 402 | } 403 | } 404 | // @media (min-width: 100px) and (orientation: landscape){div{top:0}} 405 | ``` 406 | 407 | ## Includes and mixins 408 | 409 | To combine several `Stylesheet`s together you can use the `include` method: 410 | 411 | ```kotlin 412 | val css = Stylesheet { 413 | a { color = 0xffffff } 414 | } 415 | val css_a = Stylesheet { 416 | a.hover { color = 0xff0000 } 417 | } 418 | val css_b = Stylesheet { 419 | a.active { color = 0x00ff00 } 420 | } 421 | 422 | css.include(css_a).include(css_b).render() 423 | // a{color:#fff}a:hover{color:#f00}a:active{color:#0f0} 424 | ``` 425 | 426 | If you want to add an in-place mixin, then use the lowercased `stylesheet` helper: 427 | 428 | ```kotlin 429 | val clrfix = stylesheet { 430 | zoom = 1 431 | after { 432 | content = " " 433 | display = BLOCK 434 | clear = BOTH 435 | } 436 | } 437 | 438 | Stylesheet { 439 | div { 440 | margin = 0 441 | clrfix() 442 | } 443 | } 444 | 445 | // div{margin:0;zoom:1}div::after{content:" ";display:block;clear:both} 446 | ``` 447 | 448 | ## Issues 449 | 450 | For now AzaKotlinCSS doesn't optimize the `and` selector, so be aware of possible code redundancies: 451 | 452 | ```kotlin 453 | Stylesheet { 454 | b and strong { 455 | color = 0xffffff 456 | 457 | span { 458 | color = 0xff0000 459 | } 460 | } 461 | } 462 | 463 | // This will produce: 464 | // b,strong{color:#fff}b span{color:#f00}strong span{color:#f00} 465 | 466 | // But the better way would be: 467 | // b,strong{color:#fff}b span,strong span{color:#f00} 468 | ``` 469 | 470 | ## License 471 | 472 | This software is released under the MIT License. 473 | See [LICENSE.txt](LICENSE.txt) for details. 474 | -------------------------------------------------------------------------------- /src/test/azadev/kotlin/css/test/RenderTest.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css.test 2 | 3 | import azadev.kotlin.css.* 4 | import azadev.kotlin.css.colors.* 5 | import azadev.kotlin.css.dimens.* 6 | import org.junit.* 7 | import org.junit.Assert.* 8 | 9 | 10 | // CSS Selectors: 11 | // http://www.w3schools.com/cssref/css_selectors.asp 12 | 13 | class RenderTest : ATest 14 | { 15 | @Test fun selectors() { 16 | testRender("", { 17 | div {} 18 | }) 19 | testRender("div{width:1}a{width:1}", { 20 | div { width = 1 } 21 | a { width = 1 } 22 | }) 23 | testRender("div a{width:1}a:hover{width:1}", { 24 | div.a { width = 1 } 25 | a.hover { width = 1 } 26 | }) 27 | testRender("div span{width:1}div span a{width:1}", { 28 | div.span { 29 | width = 1 30 | a { width = 1 } 31 | } 32 | }) 33 | testRender("div:hover{width:1}div *:hover{width:1}", { 34 | div { 35 | hover { width = 1 } 36 | any.hover { width = 1 } 37 | } 38 | }) 39 | testRender("div{width:1}div:hover{width:1}div:hover div{width:1}", { 40 | div { 41 | width = 1 42 | hover { 43 | width = 1 44 | div { width = 1 } 45 | } 46 | } 47 | }) 48 | } 49 | 50 | @Test fun selectors_multiple() { 51 | testRender("a,div:hover,span{width:1}a,div:hover,span{width:1}", { 52 | a and div.hover and span { width = 1 } 53 | (a and div.hover and span) { width = 1 } 54 | }) 55 | testRender("a:hover,div{width:1}a+span,div{width:1}a+span~a,div{width:1}", { 56 | a.hover and div { width = 1 } 57 | a % span and div { width = 1 } 58 | a % span - a and div { width = 1 } 59 | }) 60 | testRender("div>a,div>span{width:1}div>a:hover,div>span:hover{width:1}div>a:hover,div>span:hover{width:1}", { 61 | div / (a and span) { width = 1 } 62 | div / (a and span).hover { width = 1 } 63 | (div / (a and span)).hover { width = 1 } 64 | }) 65 | testRender("a,div,span{width:1}a:hover{width:1}a:hover a,a:hover div{width:1}div:hover{width:1}div:hover a,div:hover div{width:1}span:hover{width:1}span:hover a,span:hover div{width:1}", { 66 | a and div and span { 67 | width = 1 68 | hover { 69 | width = 1 70 | (a and div) { width = 1 } 71 | } 72 | } 73 | }) 74 | 75 | testRender("a:hover{width:1}div:hover{width:1}span:hover{width:1}a>li{width:2}div>li{width:2}span>li{width:2}", { 76 | a and div and span { 77 | hover { width = 1 } 78 | child.li { width = 2 } 79 | } 80 | }) 81 | testRender("a:hover,div:hover,span:hover{width:1}", { 82 | (a and div and span).hover { width = 1 } 83 | }) 84 | } 85 | 86 | @Test fun selectors_traversing() { 87 | testRender("div>a{width:1}span>a{width:1}span a{width:1}", { 88 | div.child.a { width = 1 } 89 | span / a { width = 1 } 90 | span..a { width = 1 } 91 | }) 92 | 93 | testRender("div+a{width:1}span+a{width:1}div+a{width:1}span+a{width:1}", { 94 | div.next.a { width = 1 } 95 | span % a { width = 1 } 96 | (div and span) { 97 | next.a { width = 1 } 98 | } 99 | }) 100 | 101 | testRender("div>a{width:1}span>a{width:1}div span a,div>a{width:1}", { 102 | div and span { 103 | child.a { width = 1 } 104 | } 105 | div { 106 | (span and child).a { width = 1 } 107 | } 108 | }) 109 | 110 | testRender("div>a:hover,div>span:hover{width:1}", { 111 | div.child { 112 | (a and span).hover { width = 1 } 113 | } 114 | }) 115 | testRender("div>a+span:hover{width:1}", { 116 | div.child { 117 | a % span.hover { width = 1 } 118 | } 119 | }) 120 | } 121 | 122 | @Test fun selectors_classesAndIds() { 123 | testRender(".class1{width:1}#id1{width:2}") { 124 | c("class1") { width = 1 } 125 | id("id1") { width = 2 } 126 | } 127 | 128 | testRender(".class1{width:1}#id1{width:2}") { 129 | ".class1" { width = 1 } 130 | "#id1" { width = 2 } 131 | } 132 | 133 | testRender(".class1.class2{width:1}#id1.class1{width:2}") { 134 | ".class1.class2" { width = 1 } 135 | "#id1.class1" { width = 2 } 136 | } 137 | 138 | testRender(".class1:hover{width:1}") { 139 | ".class1".hover { width = 1 } 140 | } 141 | testRender(".class1>a{width:1}") { 142 | ".class1" / a { width = 1 } 143 | } 144 | testRender(".class1+a{width:1}") { 145 | ".class1" % a { width = 1 } 146 | } 147 | testRender(".class1~a{width:1}") { 148 | ".class1" - a { width = 1 } 149 | } 150 | 151 | testRender("div#id1>a.class1{width:1}") { 152 | div.id("id1") { 153 | child.a.c("class1") { width = 1 } 154 | } 155 | } 156 | 157 | testRender("div,.class1{width:1}div a{width:2}.class1 a{width:2}") { 158 | div and ".class1" { 159 | width = 1 160 | a { width = 2 } 161 | } 162 | } 163 | testRender(".class1,.class2{width:1}.class1 a{width:2}.class2 a{width:2}") { 164 | ".class1" and ".class2" { 165 | width = 1 166 | a { width = 2 } 167 | } 168 | } 169 | 170 | testRender(".class1 div{width:1}.class1 .class2{width:2}") { 171 | ".class1".div { width = 1 } 172 | ".class1".children.c("class2") { width = 2 } 173 | } 174 | testRender(".class1 .class2{width:1}.class1 div{width:2}div #id1{width:3}") { 175 | ".class1"..".class2" { width = 1 } 176 | ".class1"..div { width = 2 } 177 | div.."#id1" { width = 3 } 178 | } 179 | testRender(".class1 .class2{width:1}.class1 .class2 .class3{width:2}.class1 .class2 .class3 .class4{width:3}") { 180 | ".class1"..c("class2") { width = 1 } 181 | ".class1"..c("class2")..c("class3") { width = 2 } 182 | ".class1"..c("class2")..c("class3")..".class4" { width = 3 } 183 | } 184 | 185 | testRender(".class1 .class2 span{top:0}.class1 .class2 span .class3{top:1}") { 186 | ".class1"..".class2"..span { top = 0 } 187 | ".class1"..".class2"..span..".class3" { top = 1 } 188 | } 189 | testRender(".class1 .class2~span{top:0}#id1+.class2 span>.class3{top:1}") { 190 | ".class1"..".class2" - span { top = 0 } 191 | "#id1" % ".class2"..span / ".class3" { top = 1 } 192 | } 193 | testRender(".class1,.class2,.class3{top:0}") { 194 | ".class1" and ".class2" and ".class3" { top = 0 } 195 | } 196 | } 197 | 198 | @Test fun selectors_pseudoFn() { 199 | testRender("div:not(span){width:1}div:not(a:hover) span{width:1}", { 200 | div.not(span) { width = 1 } 201 | div.not(a.hover).span { width = 1 } 202 | }) 203 | testRender("*:nth-child(even){width:1}div a:nth-child(2),span{width:1}", { 204 | any.nthChild(EVEN) { width = 1 } 205 | div.a.nthChild(2) and span { width = 1 } 206 | }) 207 | testRender(".items:not(li){width:1}.items:not(li),span{width:1}", { 208 | ".items".not(li) { width = 1 } 209 | ".items".not(li) and span { width = 1 } 210 | }) 211 | testRender(".items:some{width:1}.items:some(){width:1}", { 212 | ".items".pseudo(":some") { width = 1 } 213 | ".items".pseudoFn(":some()") { width = 1 } 214 | }) 215 | } 216 | 217 | @Test fun selectors_attributes() { 218 | testRender("input[disabled]{width:1}input[disabled=true]:hover{width:1}", { 219 | input["disabled"] { width = 1 } 220 | input["disabled", true].hover { width = 1 } 221 | }) 222 | 223 | testRender("input[disabled],textarea[disabled]{width:1}input[disabled][type=hidden],textarea[disabled][type=hidden]{width:1}", { 224 | (input and textarea)["disabled"] { width = 1 } 225 | (input and textarea)["disabled"]["type", "hidden"] { width = 1 } 226 | }) 227 | 228 | testRender("input[disabled][type*=dd],textarea[type*=dd]{width:1}", { 229 | (input["disabled"] and textarea)["type", contains, "dd"] { width = 1 } 230 | }) 231 | testRender("a[type=hidden]{width:10px}a[href^=https]{width:10px}", { 232 | a["type", equals, "hidden"] { width = 10.px } 233 | a["href", startsWith, "https"] { width = 10.px } 234 | }) 235 | 236 | testRender("[disabled]{width:1}[disabled][hidden]{width:1}", { 237 | attr("disabled") { width = 1 } 238 | attr("disabled")["hidden"] { width = 1 } 239 | }) 240 | 241 | testRender("#logo[type=main]{width:1}", { 242 | "#logo"["type", "main"] { width = 1 } 243 | }) 244 | 245 | testRender("""a[href^="http://"]{width:10px}""", { 246 | a["href", startsWith, "http://"] { width = 10.px } 247 | }) 248 | } 249 | 250 | @Test fun selectors_custom() { 251 | testRender("aside{top:0}aside:hover{top:1}aside div{top:2}aside .class1{top:3}") { 252 | "aside" { top = 0 } 253 | "aside".hover { top = 1 } 254 | "aside".div { top = 2 } 255 | "aside"..".class1" { top = 3 } 256 | } 257 | testRender("""_::selection, .selector:not([attr*='']){top:0}.selector\{top:1}""") { 258 | "_::selection, .selector:not([attr*=''])" { top = 0 } 259 | ".selector\\" { top = 1 } 260 | } 261 | } 262 | 263 | @Test fun selectors_atRules() { 264 | testRender("@media (min-width: 100px){div{width:1}}@media (min-width: 100px) and (orientation: landscape){div{width:2}}") { 265 | media("min-width: 100px") { 266 | div { width = 1 } 267 | } 268 | media("min-width: 100px", "orientation: landscape") { 269 | div { width = 2 } 270 | } 271 | } 272 | testRender("input{width:1}@media (min-width: 100px){div{width:2}div a{width:3}span{width:4}}textarea{width:5}") { 273 | input { width = 1 } 274 | media("min-width: 100px") { 275 | div { 276 | width = 2 277 | a { width = 3 } 278 | } 279 | span { width = 4 } 280 | } 281 | textarea { width = 5 } 282 | } 283 | 284 | testRender("div{width:1}@media (min-width: 100px){div a{width:2}div:hover{width:3}div *:hover{width:4}div *:hover:not(1){width:5}}") { 285 | div { 286 | width = 1 287 | media("min-width: 100px") { 288 | a { width = 2 } 289 | hover { width = 3 } 290 | any.hover { 291 | width = 4 292 | not(1) { width = 5 } 293 | } 294 | } 295 | } 296 | } 297 | testRender("@media (min-width: 100px){div{width:1}}@media (min-width: 100px){a{width:1}}") { 298 | (div and a) { 299 | media("min-width: 100px") { 300 | width = 1 301 | } 302 | } 303 | } 304 | testRender("@media (min-width: 100px){div{width:1}}@media (min-width: 100px){a{width:1}}@media (min-width: 200px){div{width:2}}@media (min-width: 200px){a{width:2}}") { 305 | (div and a) { 306 | media("min-width: 100px") { 307 | width = 1 308 | } 309 | media("min-width: 200px") { 310 | width = 2 311 | } 312 | } 313 | } 314 | 315 | testRender("@-webkit-keyframes animation1{from{top:0}30%{top:50px}68%,72%{top:70px}to{top:100px}}") { 316 | at("-webkit-keyframes animation1") { 317 | "from" { top = 0.px } 318 | "30%" { top = 50.px } 319 | "68%,72%" { top = 70.px } 320 | "to" { top = 100.px } 321 | } 322 | } 323 | testRender("@font-face{font-family:Bitstream Vera Serif Bold;src:url(VeraSeBd.ttf);font-weight:bold}@font-face{font-family:Graublau Web;src:url(GraublauWeb.eot);src:local('☺'), url('GraublauWeb.woff') format('woff'), url('GraublauWeb.ttf') format('truetype')}") { 324 | at("font-face") { 325 | fontFamily = "Bitstream Vera Serif Bold" 326 | src = url("VeraSeBd.ttf") 327 | fontWeight = BOLD 328 | } 329 | at("font-face") { 330 | fontFamily = "Graublau Web" 331 | src = url("GraublauWeb.eot") 332 | src = "local('☺'), url('GraublauWeb.woff') format('woff'), url('GraublauWeb.ttf') format('truetype')" 333 | } 334 | } 335 | 336 | testRender("@media (min-width: 100px) and (orientation: landscape){div{width:1}}") { 337 | myMediaQuery { 338 | div { width = 1 } 339 | } 340 | } 341 | } 342 | 343 | private fun Stylesheet.myMediaQuery(body: Stylesheet.()->Unit) 344 | = media("min-width: 100px", "orientation: landscape").invoke(body) 345 | 346 | 347 | @Test fun properties() { 348 | testRender("a{width:auto;height:10;font-size:14px;line-height:0;opacity:.2}") { 349 | a { 350 | width = AUTO 351 | height = 10 352 | color = null 353 | fontSize = 14.px 354 | lineHeight = 0 355 | background = null 356 | opacity = .2 357 | } 358 | } 359 | testRender("div{background:-moz-linear-gradient(top, #000 0%, #fff 100%);background:-webkit-linear-gradient(top, #000 0%, #fff 100%);background:linear-gradient(top bottom, #000 0%, #fff 100%)}") { 360 | div { 361 | background = "-moz-linear-gradient(top, #000 0%, #fff 100%)" // FF3.6+ 362 | background = "-webkit-linear-gradient(top, #000 0%, #fff 100%)" // Chrome10+, Safari5.1+ 363 | background = "linear-gradient(top bottom, #000 0%, #fff 100%)" // W3C 364 | } 365 | } 366 | testRender("""a:after{content:" ";content:"you're";content:"\"he said \"nice\"\""}""") { 367 | a.after { 368 | content = " " 369 | content = "you're" 370 | content = "\"he said \"nice\"\"" 371 | } 372 | } 373 | } 374 | 375 | 376 | @Test fun dimensions() { 377 | testRender("a{width:auto}a{width:1px}a{width:.2em}a{width:50%}a{width:17.257ex}a{width:1.55555in}") { 378 | a { width = AUTO } 379 | a { width = 1.px } 380 | a { width = .2.em } 381 | a { width = 50f.percent } 382 | a { width = 17.257.ex } 383 | a { width = 1.55555f.inch } 384 | } 385 | testRender("a{padding:10px}a{padding:10px}a{padding:10px 20%}a{padding:10px 0 5px}a{padding:10px 0 5px 20px}a{padding:10px 10px 5px 0}") { 386 | a { padding = box(10.px) } 387 | a { padding = box(10) } 388 | a { padding = box(10.px, 20.percent) } 389 | a { padding = box(10.px, 0, "5px") } 390 | a { padding = box(10.px, 0, 5.px, 20.px) } 391 | a { padding = box(10, 10, 5, 0) } 392 | } 393 | } 394 | 395 | 396 | @Test fun colors() { 397 | assertEquals("#fff", hex("#fff").toString()) 398 | assertEquals("#fff", hex("#ffffff").toString()) 399 | assertEquals("#00000f", hex(0x00000f).toString()) 400 | assertEquals("#001", hex(0x000011).toString()) 401 | assertEquals("#000fff", hex(0x000FFf).toString()) 402 | assertEquals("#011111", hex(0x011111).toString()) 403 | assertEquals("#000aff", rgb(0,10,255).toString()) 404 | assertEquals("#0ff", rgb(0,255,255).toString()) 405 | assertEquals("rgba(0,10,255,0)", rgba(0,10,255,0).toString()) 406 | assertEquals("rgba(255,255,255,.47)", rgba(255,255,255,0.47).toString()) 407 | assertEquals("rgba(0,0,0,.019)", rgba(0,0,0,0.019).toString()) 408 | 409 | testRender("a{color:#fff}a{color:rgba(255,100,0,.196)}a{color:#f00}a{color:#00000f}a{color:#f2cacf}") { 410 | a { color = hex("#fff") } 411 | a { color = rgba(255,100,0,0.196) } 412 | a { color = "#f00" } 413 | a { color = 0x00000f } 414 | a { color = 0xf2cacf } 415 | } 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/properties.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package azadev.kotlin.css 4 | 5 | // CSS Reference 6 | // http://www.w3schools.com/cssref/default.asp 7 | 8 | 9 | // Color Properties 10 | var Stylesheet.color by ColorPropertyHandler("color") 11 | var Stylesheet.opacity by PropertyHandler("opacity") 12 | 13 | // Background and Border Properties 14 | var Stylesheet.background by PropertyHandler("background") 15 | var Stylesheet.backgroundAttachment by PropertyHandler("background-attachment") 16 | var Stylesheet.backgroundBlendMode by PropertyHandler("background-blend-mode") 17 | var Stylesheet.backgroundColor by ColorPropertyHandler("background-color") 18 | var Stylesheet.backgroundImage by PropertyHandler("background-image") 19 | var Stylesheet.backgroundPosition by PropertyHandler("background-position") 20 | var Stylesheet.backgroundRepeat by PropertyHandler("background-repeat") 21 | var Stylesheet.backgroundClip by PropertyHandler("background-clip") 22 | var Stylesheet.backgroundOrigin by PropertyHandler("background-origin") 23 | var Stylesheet.backgroundSize by PropertyHandler("background-size") 24 | var Stylesheet.border by PropertyHandler("border") 25 | var Stylesheet.borderBottom by PropertyHandler("border-bottom") 26 | var Stylesheet.borderBottomColor by ColorPropertyHandler("border-bottom-color") 27 | var Stylesheet.borderBottomLeftRadius by PropertyHandler("border-bottom-left-radius") 28 | var Stylesheet.borderBottomRightRadius by PropertyHandler("border-bottom-right-radius") 29 | var Stylesheet.borderBottomStyle by PropertyHandler("border-bottom-style") 30 | var Stylesheet.borderBottomWidth by PropertyHandler("border-bottom-width") 31 | var Stylesheet.borderColor by ColorPropertyHandler("border-color") 32 | var Stylesheet.borderImage by PropertyHandler("border-image") 33 | var Stylesheet.borderImageOutset by PropertyHandler("border-image-outset") 34 | var Stylesheet.borderImageRepeat by PropertyHandler("border-image-repeat") 35 | var Stylesheet.borderImageSlice by PropertyHandler("border-image-slice") 36 | var Stylesheet.borderImageSource by PropertyHandler("border-image-source") 37 | var Stylesheet.borderImageWidth by PropertyHandler("border-image-width") 38 | var Stylesheet.borderLeft by PropertyHandler("border-left") 39 | var Stylesheet.borderLeftColor by ColorPropertyHandler("border-left-color") 40 | var Stylesheet.borderLeftStyle by PropertyHandler("border-left-style") 41 | var Stylesheet.borderLeftWidth by PropertyHandler("border-left-width") 42 | var Stylesheet.borderRadius by PropertyHandler("border-radius") 43 | var Stylesheet.borderRight by PropertyHandler("border-right") 44 | var Stylesheet.borderRightColor by ColorPropertyHandler("border-right-color") 45 | var Stylesheet.borderRightStyle by PropertyHandler("border-right-style") 46 | var Stylesheet.borderRightWidth by PropertyHandler("border-right-width") 47 | var Stylesheet.borderStyle by PropertyHandler("border-style") 48 | var Stylesheet.borderTop by PropertyHandler("border-top") 49 | var Stylesheet.borderTopColor by ColorPropertyHandler("border-top-color") 50 | var Stylesheet.borderTopLeftRadius by PropertyHandler("border-top-left-radius") 51 | var Stylesheet.borderTopRightRadius by PropertyHandler("border-top-right-radius") 52 | var Stylesheet.borderTopStyle by PropertyHandler("border-top-style") 53 | var Stylesheet.borderTopWidth by PropertyHandler("border-top-width") 54 | var Stylesheet.borderWidth by PropertyHandler("border-width") 55 | var Stylesheet.boxDecorationBreak by PropertyHandler("box-decoration-break") 56 | var Stylesheet.boxShadow by PropertyHandler("box-shadow") 57 | 58 | // Basic Box Properties 59 | var Stylesheet.bottom by PropertyHandler("bottom") 60 | var Stylesheet.clear by PropertyHandler("clear") 61 | var Stylesheet.clip by PropertyHandler("clip") 62 | var Stylesheet.display by PropertyHandler("display") 63 | var Stylesheet.float by PropertyHandler("float") 64 | var Stylesheet.height by PropertyHandler("height") 65 | var Stylesheet.left by PropertyHandler("left") 66 | var Stylesheet.margin by PropertyHandler("margin") 67 | var Stylesheet.marginBottom by PropertyHandler("margin-bottom") 68 | var Stylesheet.marginLeft by PropertyHandler("margin-left") 69 | var Stylesheet.marginRight by PropertyHandler("margin-right") 70 | var Stylesheet.marginTop by PropertyHandler("margin-top") 71 | var Stylesheet.maxHeight by PropertyHandler("max-height") 72 | var Stylesheet.maxWidth by PropertyHandler("max-width") 73 | var Stylesheet.minHeight by PropertyHandler("min-height") 74 | var Stylesheet.minWidth by PropertyHandler("min-width") 75 | var Stylesheet.overflow by PropertyHandler("overflow") 76 | var Stylesheet.overflowX by PropertyHandler("overflow-x") 77 | var Stylesheet.overflowY by PropertyHandler("overflow-y") 78 | var Stylesheet.padding by PropertyHandler("padding") 79 | var Stylesheet.paddingBottom by PropertyHandler("padding-bottom") 80 | var Stylesheet.paddingLeft by PropertyHandler("padding-left") 81 | var Stylesheet.paddingRight by PropertyHandler("padding-right") 82 | var Stylesheet.paddingTop by PropertyHandler("padding-top") 83 | var Stylesheet.position by PropertyHandler("position") 84 | var Stylesheet.right by PropertyHandler("right") 85 | var Stylesheet.top by PropertyHandler("top") 86 | var Stylesheet.visibility by PropertyHandler("visibility") 87 | var Stylesheet.width by PropertyHandler("width") 88 | var Stylesheet.verticalAlign by PropertyHandler("vertical-align") 89 | var Stylesheet.zIndex by PropertyHandler("z-index") 90 | 91 | // Flexible Box Layout 92 | var Stylesheet.alignContent by PropertyHandler("align-content") 93 | var Stylesheet.alignItems by PropertyHandler("align-items") 94 | var Stylesheet.alignSelf by PropertyHandler("align-self") 95 | var Stylesheet.flex by PropertyHandler("flex") 96 | var Stylesheet.flexBasis by PropertyHandler("flex-basis") 97 | var Stylesheet.flexDirection by PropertyHandler("flex-direction") 98 | var Stylesheet.flexFlow by PropertyHandler("flex-flow") 99 | var Stylesheet.flexGrow by PropertyHandler("flex-grow") 100 | var Stylesheet.flexShrink by PropertyHandler("flex-shrink") 101 | var Stylesheet.flexWrap by PropertyHandler("flex-wrap") 102 | var Stylesheet.justifyContent by PropertyHandler("justify-content") 103 | var Stylesheet.order by PropertyHandler("order") 104 | 105 | // Text Properties 106 | var Stylesheet.hangingPunctuation by PropertyHandler("hanging-punctuation") 107 | var Stylesheet.hyphens by PropertyHandler("hyphens") 108 | var Stylesheet.letterSpacing by PropertyHandler("letter-spacing") 109 | var Stylesheet.lineBreak by PropertyHandler("line-break") 110 | var Stylesheet.lineHeight by PropertyHandler("line-height") 111 | var Stylesheet.overflowWrap by PropertyHandler("overflow-wrap") 112 | var Stylesheet.tabSize by PropertyHandler("tab-size") 113 | var Stylesheet.textAlign by PropertyHandler("text-align") 114 | var Stylesheet.textAlignLast by PropertyHandler("text-align-last") 115 | var Stylesheet.textIndent by PropertyHandler("text-indent") 116 | var Stylesheet.textJustify by PropertyHandler("text-justify") 117 | var Stylesheet.textTransform by PropertyHandler("text-transform") 118 | var Stylesheet.whiteSpace by PropertyHandler("white-space") 119 | var Stylesheet.wordBreak by PropertyHandler("word-break") 120 | var Stylesheet.wordSpacing by PropertyHandler("word-spacing") 121 | var Stylesheet.wordWrap by PropertyHandler("word-wrap") 122 | 123 | // Text Decoration Properties 124 | var Stylesheet.textDecoration by PropertyHandler("text-decoration") 125 | var Stylesheet.textDecorationColor by ColorPropertyHandler("text-decoration-color") 126 | var Stylesheet.textDecorationLine by PropertyHandler("text-decoration-line") 127 | var Stylesheet.textDecorationStyle by PropertyHandler("text-decoration-style") 128 | var Stylesheet.textShadow by PropertyHandler("text-shadow") 129 | var Stylesheet.textUnderlinePosition by PropertyHandler("text-underline-position") 130 | 131 | // Font Properties 132 | var Stylesheet.font by PropertyHandler("font") 133 | var Stylesheet.fontFamily by PropertyHandler("font-family") 134 | var Stylesheet.fontFeatureSettings by PropertyHandler("font-feature-settings") 135 | var Stylesheet.fontKerning by PropertyHandler("font-kerning") 136 | var Stylesheet.fontLanguageOverride by PropertyHandler("font-language-override") 137 | var Stylesheet.fontSize by PropertyHandler("font-size") 138 | var Stylesheet.fontSizeAdjust by PropertyHandler("font-size-adjust") 139 | var Stylesheet.fontStretch by PropertyHandler("font-stretch") 140 | var Stylesheet.fontStyle by PropertyHandler("font-style") 141 | var Stylesheet.fontSynthesis by PropertyHandler("font-synthesis") 142 | var Stylesheet.fontVariant by PropertyHandler("font-variant") 143 | var Stylesheet.fontVariantAlternates by PropertyHandler("font-variant-alternates") 144 | var Stylesheet.fontVariantCaps by PropertyHandler("font-variant-caps") 145 | var Stylesheet.fontVariantEastAsian by PropertyHandler("font-variant-east-asian") 146 | var Stylesheet.fontVariantLigatures by PropertyHandler("font-variant-ligatures") 147 | var Stylesheet.fontVariantNumeric by PropertyHandler("font-variant-numeric") 148 | var Stylesheet.fontVariantPosition by PropertyHandler("font-variant-position") 149 | var Stylesheet.fontWeight by PropertyHandler("font-weight") 150 | 151 | // Writing Modes Properties 152 | var Stylesheet.direction by PropertyHandler("direction") 153 | var Stylesheet.textOrientation by PropertyHandler("text-orientation") 154 | var Stylesheet.textCombineUpright by PropertyHandler("text-combine-upright") 155 | var Stylesheet.unicodeBidi by PropertyHandler("unicode-bidi") 156 | var Stylesheet.writingMode by PropertyHandler("writing-mode") 157 | 158 | // Table Properties 159 | var Stylesheet.borderCollapse by PropertyHandler("border-collapse") 160 | var Stylesheet.borderSpacing by PropertyHandler("border-spacing") 161 | var Stylesheet.captionSide by PropertyHandler("caption-side") 162 | var Stylesheet.emptyCells by PropertyHandler("empty-cells") 163 | var Stylesheet.tableLayout by PropertyHandler("table-layout") 164 | 165 | // Lists and Counters Properties 166 | var Stylesheet.counterIncrement by PropertyHandler("counter-increment") 167 | var Stylesheet.counterReset by PropertyHandler("counter-reset") 168 | var Stylesheet.listStyle by PropertyHandler("list-style") 169 | var Stylesheet.listStyleImage by PropertyHandler("list-style-image") 170 | var Stylesheet.listStylePosition by PropertyHandler("list-style-position") 171 | var Stylesheet.listStyleType by PropertyHandler("list-style-type") 172 | 173 | // Animation Properties 174 | var Stylesheet.animation by PropertyHandler("animation") 175 | var Stylesheet.animationDelay by PropertyHandler("animation-delay") 176 | var Stylesheet.animationDirection by PropertyHandler("animation-direction") 177 | var Stylesheet.animationDuration by PropertyHandler("animation-duration") 178 | var Stylesheet.animationFillMode by PropertyHandler("animation-fill-mode") 179 | var Stylesheet.animationIterationCount by PropertyHandler("animation-iteration-count") 180 | var Stylesheet.animationName by PropertyHandler("animation-name") 181 | var Stylesheet.animationPlayState by PropertyHandler("animation-play-state") 182 | var Stylesheet.animationTimingFunction by PropertyHandler("animation-timing-function") 183 | 184 | // Transform Properties 185 | var Stylesheet.backfaceVisibility by PropertyHandler("backface-visibility") 186 | var Stylesheet.perspective by PropertyHandler("perspective") 187 | var Stylesheet.perspectiveOrigin by PropertyHandler("perspective-origin") 188 | var Stylesheet.transform by PropertyHandler("transform") 189 | var Stylesheet.transformOrigin by PropertyHandler("transform-origin") 190 | var Stylesheet.transformStyle by PropertyHandler("transform-style") 191 | 192 | // Transitions Properties 193 | var Stylesheet.transition by PropertyHandler("transition") 194 | var Stylesheet.transitionProperty by PropertyHandler("transition-property") 195 | var Stylesheet.transitionDuration by PropertyHandler("transition-duration") 196 | var Stylesheet.transitionTimingFunction by PropertyHandler("transition-timing-function") 197 | var Stylesheet.transitionDelay by PropertyHandler("transition-delay") 198 | 199 | // Basic User Interface Properties 200 | var Stylesheet.boxSizing by PropertyHandler("box-sizing") 201 | var Stylesheet.content by ContentPropertyHandler("content") 202 | var Stylesheet.cursor by PropertyHandler("cursor") 203 | var Stylesheet.imeMode by PropertyHandler("ime-mode") 204 | var Stylesheet.navDown by PropertyHandler("nav-down") 205 | var Stylesheet.navIndex by PropertyHandler("nav-index") 206 | var Stylesheet.navLeft by PropertyHandler("nav-left") 207 | var Stylesheet.navRight by PropertyHandler("nav-right") 208 | var Stylesheet.navUp by PropertyHandler("nav-up") 209 | var Stylesheet.outline by PropertyHandler("outline") 210 | var Stylesheet.outlineColor by ColorPropertyHandler("outline-color") 211 | var Stylesheet.outlineOffset by PropertyHandler("outline-offset") 212 | var Stylesheet.outlineStyle by PropertyHandler("outline-style") 213 | var Stylesheet.outlineWidth by PropertyHandler("outline-width") 214 | var Stylesheet.resize by PropertyHandler("resize") 215 | var Stylesheet.textOverflow by PropertyHandler("text-overflow") 216 | 217 | // Multi-column Layout Properties 218 | var Stylesheet.breakAfter by PropertyHandler("break-after") 219 | var Stylesheet.breakBefore by PropertyHandler("break-before") 220 | var Stylesheet.breakInside by PropertyHandler("break-inside") 221 | var Stylesheet.columnCount by PropertyHandler("column-count") 222 | var Stylesheet.columnFill by PropertyHandler("column-fill") 223 | var Stylesheet.columnGap by PropertyHandler("column-gap") 224 | var Stylesheet.columnRule by PropertyHandler("column-rule") 225 | var Stylesheet.columnRuleColor by ColorPropertyHandler("column-rule-color") 226 | var Stylesheet.columnRuleStyle by PropertyHandler("column-rule-style") 227 | var Stylesheet.columnRuleWidth by PropertyHandler("column-rule-width") 228 | var Stylesheet.columnSpan by PropertyHandler("column-span") 229 | var Stylesheet.columnWidth by PropertyHandler("column-width") 230 | var Stylesheet.columns by PropertyHandler("columns") 231 | var Stylesheet.widows by PropertyHandler("widows") 232 | 233 | // Paged Media 234 | var Stylesheet.orphans by PropertyHandler("orphans") 235 | var Stylesheet.pageBreakAfter by PropertyHandler("page-break-after") 236 | var Stylesheet.pageBreakBefore by PropertyHandler("page-break-before") 237 | var Stylesheet.pageBreakInside by PropertyHandler("page-break-inside") 238 | 239 | // Generated Content for Paged Media 240 | var Stylesheet.marks by PropertyHandler("marks") 241 | var Stylesheet.quotes by PropertyHandler("quotes") 242 | 243 | // Filter Effects Properties 244 | var Stylesheet.filter by PropertyHandler("filter") 245 | 246 | // Image Values and Replaced Content 247 | var Stylesheet.imageOrientation by PropertyHandler("image-orientation") 248 | var Stylesheet.imageRendering by PropertyHandler("image-rendering") 249 | var Stylesheet.imageResolution by PropertyHandler("image-resolution") 250 | var Stylesheet.objectFit by PropertyHandler("object-fit") 251 | var Stylesheet.objectPosition by PropertyHandler("object-position") 252 | 253 | // Masking Properties 254 | var Stylesheet.mask by PropertyHandler("mask") 255 | var Stylesheet.maskType by PropertyHandler("mask-type") 256 | 257 | // Speech Properties 258 | var Stylesheet.mark by PropertyHandler("mark") 259 | var Stylesheet.markAfter by PropertyHandler("mark-after") 260 | var Stylesheet.markBefore by PropertyHandler("mark-before") 261 | var Stylesheet.phonemes by PropertyHandler("phonemes") 262 | var Stylesheet.rest by PropertyHandler("rest") 263 | var Stylesheet.restAfter by PropertyHandler("rest-after") 264 | var Stylesheet.restBefore by PropertyHandler("rest-before") 265 | var Stylesheet.voiceBalance by PropertyHandler("voice-balance") 266 | var Stylesheet.voiceDuration by PropertyHandler("voice-duration") 267 | var Stylesheet.voicePitch by PropertyHandler("voice-pitch") 268 | var Stylesheet.voicePitchRange by PropertyHandler("voice-pitch-range") 269 | var Stylesheet.voiceRate by PropertyHandler("voice-rate") 270 | var Stylesheet.voiceStress by PropertyHandler("voice-stress") 271 | var Stylesheet.voiceVolume by PropertyHandler("voice-volume") 272 | 273 | // Marquee Properties 274 | var Stylesheet.marqueeDirection by PropertyHandler("marquee-direction") 275 | var Stylesheet.marqueePlayCount by PropertyHandler("marquee-play-count") 276 | var Stylesheet.marqueeSpeed by PropertyHandler("marquee-speed") 277 | var Stylesheet.marqueeStyle by PropertyHandler("marquee-style") 278 | 279 | // Exotic 280 | var Stylesheet.zoom by PropertyHandler("zoom") 281 | var Stylesheet.src by PropertyHandler("src") // @font-face 282 | -------------------------------------------------------------------------------- /src/main/azadev/kotlin/css/Stylesheet.kt: -------------------------------------------------------------------------------- 1 | package azadev.kotlin.css 2 | 3 | import java.io.File 4 | import java.util.* 5 | 6 | 7 | // CSS Selector Reference 8 | // http://www.w3schools.com/cssref/css_selectors.asp 9 | 10 | @Suppress("unused") 11 | class Stylesheet( 12 | callback: (Stylesheet.()->Unit)? = null 13 | ) : ASelector 14 | { 15 | var selector: Selector? = null 16 | var atRule: String? = null 17 | 18 | val properties = ArrayList(2) 19 | 20 | val children = ArrayList() 21 | 22 | 23 | init { callback?.invoke(this) } 24 | 25 | 26 | fun include(stylesheet: Stylesheet): Stylesheet { 27 | children.add(stylesheet) 28 | return this 29 | } 30 | 31 | override fun custom(selector: String, _spaceBefore: Boolean, _spaceAfter: Boolean, body: (Stylesheet.() -> Unit)?): Selector { 32 | val stylesheet = Stylesheet() 33 | val sel = Selector(stylesheet) 34 | sel.custom(selector, _spaceBefore, _spaceAfter) 35 | 36 | stylesheet.selector = sel 37 | include(stylesheet) 38 | 39 | body?.invoke(stylesheet) 40 | 41 | return sel 42 | } 43 | 44 | 45 | fun getProperty(name: String) = properties.find { it.name == name } 46 | 47 | fun setProperty(name: String, value: Any?) { 48 | properties.add(Property(name, value)) 49 | } 50 | 51 | 52 | fun moveDataTo(stylesheet: Stylesheet) { 53 | stylesheet.properties.addAll(properties) 54 | properties.clear() 55 | 56 | stylesheet.children.addAll(children) 57 | children.clear() 58 | } 59 | 60 | 61 | fun render() = buildString { render(this) } 62 | fun renderTo(sb: StringBuilder) = render(sb) 63 | 64 | fun renderToFile(file: File) { 65 | file.delete() 66 | file.writeText(render()) // "writeText" is a really clever helper 67 | } 68 | fun renderToFile(path: String) = renderToFile(File(path)) 69 | 70 | private fun render(sb: StringBuilder, selectorPrefix: CharSequence = "", _spaceBefore: Boolean = true) { 71 | val selector = selector 72 | val atRule = atRule 73 | 74 | 75 | if (atRule != null) 76 | sb.append(atRule).append('{') 77 | 78 | 79 | if (properties.isNotEmpty()) { 80 | val selectorStr = selector?.toString(selectorPrefix, _spaceBefore) 81 | val hasSelector = !selectorStr.isNullOrEmpty() 82 | 83 | if (hasSelector) 84 | sb.append(selectorStr).append('{') 85 | 86 | val lastIdx = properties.lastIndex 87 | properties.forEachIndexed { i, property -> 88 | val value = property.value 89 | if (value != null) 90 | sb.run { 91 | append(property.name) 92 | append(":") 93 | append(if (value is Number) cssDecimalFormat.format(value.toFloat()) else value) 94 | 95 | if (i < lastIdx) 96 | append(";") 97 | } 98 | else if (i == lastIdx && sb.last() == ';') 99 | sb.setLength(sb.length-1) 100 | } 101 | 102 | if (hasSelector) 103 | sb.append("}") 104 | } 105 | 106 | 107 | for (child in children) { 108 | val rows = selector?.rows 109 | if (rows != null && rows.isNotEmpty()) 110 | rows.forEach { child.render(sb, it.toString(selectorPrefix, _spaceBefore), it.spaceAfter) } 111 | else 112 | child.render(sb, selectorPrefix) 113 | } 114 | 115 | 116 | if (atRule != null) 117 | sb.append('}') 118 | } 119 | 120 | 121 | override fun toString() = "Stylesheet(sel:$selector; props:${properties.size}; childs:${children.size})" 122 | 123 | 124 | class Property( 125 | val name: String, 126 | val value: Any? 127 | ) { 128 | override fun toString() = "$name:$value" 129 | } 130 | 131 | 132 | // 133 | // AT-RULES 134 | // 135 | fun at(rule: Any, body: (Stylesheet.()->Unit)? = null): Stylesheet { 136 | val stylesheet = Stylesheet(body) 137 | stylesheet.selector = Selector.createEmpty(stylesheet) 138 | stylesheet.atRule = "@$rule" 139 | include(stylesheet) 140 | return stylesheet 141 | } 142 | 143 | fun media(vararg conditions: Any, body: (Stylesheet.()->Unit)? = null) 144 | = at("media (${conditions.joinToString(") and (")})", body) 145 | 146 | 147 | // 148 | // MAIN COMMANDS 149 | // 150 | operator fun CharSequence.invoke(body: Stylesheet.()->Unit) = toSelector().invoke(body) 151 | 152 | fun CharSequence.custom(selector: String, _spaceBefore: Boolean = true, _spaceAfter: Boolean = true, body: (Stylesheet.()->Unit)? = null): Selector { 153 | return when (this) { 154 | is ASelector -> custom(selector, _spaceBefore, _spaceAfter, body) 155 | else -> toSelector().custom(selector, _spaceBefore, _spaceAfter, body) 156 | } 157 | } 158 | fun CharSequence.pseudo(selector: String, body: (Stylesheet.()->Unit)? = null): Selector { 159 | return when (this) { 160 | is ASelector -> pseudo(selector, body) 161 | else -> toSelector().pseudo(selector, body) 162 | } 163 | } 164 | fun CharSequence.pseudoFn(selector: String, body: (Stylesheet.()->Unit)? = null): Selector { 165 | return when (this) { 166 | is ASelector -> pseudoFn(selector, body) 167 | else -> toSelector().pseudoFn(selector, body) 168 | } 169 | } 170 | 171 | infix fun CharSequence.and(obj: CharSequence): Selector { 172 | val sel = toSelector() 173 | when (obj) { 174 | is Selector -> { 175 | for (row in obj.rows) 176 | sel.rows.add(row) 177 | return sel 178 | } 179 | is Stylesheet -> { 180 | val selector = obj.selector!! 181 | selector.rows.addAll(0, sel.rows) 182 | return selector 183 | } 184 | else -> return and(obj.toSelector()) 185 | } 186 | } 187 | 188 | private fun CharSequence.toSelector() = when (this) { 189 | is Selector -> this 190 | is Stylesheet -> this.selector!! 191 | else -> when (this[0]) { 192 | '.' -> this@Stylesheet.c(this.drop(1)) 193 | '#' -> this@Stylesheet.id(this.drop(1)) 194 | '@' -> this@Stylesheet.at(this.drop(1)).selector!! 195 | else -> this@Stylesheet.custom(this.toString()) 196 | } 197 | } 198 | 199 | 200 | // 201 | // TRAVERSING 202 | // 203 | val CharSequence.children: Selector get() = custom(" ", false, false) 204 | val CharSequence.child: Selector get() = custom(">", false, false) 205 | val CharSequence.next: Selector get() = custom("+", false, false) 206 | val CharSequence.nextAll: Selector get() = custom("~", false, false) 207 | operator fun CharSequence.div(obj: CharSequence) = child.append(obj.toSelector()) 208 | operator fun CharSequence.mod(obj: CharSequence) = next.append(obj.toSelector()) 209 | operator fun CharSequence.minus(obj: CharSequence) = nextAll.append(obj.toSelector()) 210 | operator fun CharSequence.rangeTo(obj: CharSequence) = children.append(obj.toSelector()) 211 | 212 | 213 | // 214 | // ATTRIBUTES 215 | // 216 | /* 217 | TODO: Escape and add braces ? 218 | https://mathiasbynens.be/notes/css-escapes 219 | http://stackoverflow.com/questions/13987979/how-to-properly-escape-attribute-values-in-css-js-selector-attr-value 220 | https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape 221 | */ 222 | fun CharSequence.attr(attrName: Any, body: (Stylesheet.()->Unit)? = null): Selector { 223 | return when (this) { 224 | is ASelector -> custom("[$attrName]", false, true, body) 225 | else -> toSelector().attr(attrName, body) 226 | } 227 | } 228 | fun CharSequence.attr(attrName: Any, attrValue: Any, body: (Stylesheet.()->Unit)? = null): Selector { 229 | return when (this) { 230 | is ASelector -> custom("[$attrName=${escapeAttrValue(attrValue.toString())}]", false, true, body) 231 | else -> toSelector().attr(attrName, attrValue, body) 232 | } 233 | } 234 | fun CharSequence.attr(attrName: Any, attrValue: Any, attrFiler: AttrFilter, body: (Stylesheet.()->Unit)? = null): Selector { 235 | return when (this) { 236 | is ASelector -> custom("[$attrName$attrFiler=${escapeAttrValue(attrValue.toString())}]", false, true, body) 237 | else -> toSelector().attr(attrName, attrValue, attrFiler, body) 238 | } 239 | } 240 | 241 | operator fun CharSequence.get(attrName: Any, body: (Stylesheet.()->Unit)? = null) = attr(attrName, body) 242 | operator fun CharSequence.get(attrName: Any, attrValue: Any, body: (Stylesheet.()->Unit)? = null) = attr(attrName, attrValue, body) 243 | operator fun CharSequence.get(attrName: Any, attrFiler: AttrFilter, attrValue: Any, body: (Stylesheet.()->Unit)? = null) = attr(attrName, attrValue, attrFiler, body) 244 | 245 | private fun escapeAttrValue(str: String): String { 246 | // http://stackoverflow.com/questions/5578845/css-attribute-selectors-the-rules-on-quotes-or-none 247 | val isIdentifier = str.all { it >= '0' && it <= '9' || it >= 'a' && it <= 'z' || it >= 'A' && it <= 'Z' || it == '-' || it == '_' } 248 | return if (isIdentifier) str else "\"${str.replace("\"", "\\\"")}\"" 249 | } 250 | 251 | 252 | // 253 | // CLASSES AND IDs 254 | // 255 | fun CharSequence.c(selector: Any, body: (Stylesheet.()->Unit)? = null) = custom(".$selector", false, true, body) 256 | fun CharSequence.id(selector: Any, body: (Stylesheet.()->Unit)? = null) = custom("#$selector", false, true, body) 257 | 258 | 259 | // 260 | // TAGS 261 | // 262 | val CharSequence.any: Selector get() = custom("*") 263 | 264 | val CharSequence.a: Selector get() = custom("a") // Defines a hyperlink. 265 | val CharSequence.abbr: Selector get() = custom("abbr") // Defines an abbreviation or an acronym. 266 | val CharSequence.acronym: Selector get() = custom("acronym") // Defines an acronym. Not supported in HTML5. Use instead. 267 | val CharSequence.address: Selector get() = custom("address") // Defines contact information for the author/owner of a document. 268 | val CharSequence.applet: Selector get() = custom("applet") // Defines an embedded applet. Not supported in HTML5. Use or instead. 269 | val CharSequence.area: Selector get() = custom("area") // Defines an area inside an image-map. 270 | val CharSequence.article: Selector get() = custom("article") // Defines an article. 271 | val CharSequence.aside: Selector get() = custom("aside") // Defines content aside from the page content. 272 | val CharSequence.audio: Selector get() = custom("audio") // Defines sound content. 273 | val CharSequence.b: Selector get() = custom("b") // Defines bold text. 274 | val CharSequence.base: Selector get() = custom("base") // Specifies the base URL/target for all relative URLs in a document. 275 | val CharSequence.basefont: Selector get() = custom("basefont") // Specifies a default color, size, and font for all text in a document. Not supported in HTML5. Use CSS instead. 276 | val CharSequence.bdi: Selector get() = custom("bdi") // Isolates a part of text that might be formatted in a different direction from other text outside it. 277 | val CharSequence.bdo: Selector get() = custom("bdo") // Overrides the current text direction. 278 | val CharSequence.big: Selector get() = custom("big") // Defines big text. Not supported in HTML5. Use CSS instead. 279 | val CharSequence.blockquote: Selector get() = custom("blockquote") // Defines a section that is quoted from another source. 280 | val CharSequence.body: Selector get() = custom("body") // Defines the document's body. 281 | val CharSequence.br: Selector get() = custom("br") // Defines a single line break. 282 | val CharSequence.button: Selector get() = custom("button") // Defines a clickable button. 283 | val CharSequence.canvas: Selector get() = custom("canvas") // Used to draw graphics, on the fly, via scripting (usually JavaScript). 284 | val CharSequence.caption: Selector get() = custom("caption") // Defines a table caption. 285 | val CharSequence.center: Selector get() = custom("center") // Defines centered text. Not supported in HTML5. Use CSS instead. 286 | val CharSequence.cite: Selector get() = custom("cite") // Defines the title of a work. 287 | val CharSequence.code: Selector get() = custom("code") // Defines a piece of computer code. 288 | val CharSequence.col: Selector get() = custom("col") // Specifies column properties for each column within a element. 289 | val CharSequence.colgroup: Selector get() = custom("colgroup") // Specifies a group of one or more columns in a table for formatting. 290 | val CharSequence.datalist: Selector get() = custom("datalist") // Specifies a list of pre-defined options for input controls. 291 | val CharSequence.dd: Selector get() = custom("dd") // Defines a description/value of a term in a description list. 292 | val CharSequence.del: Selector get() = custom("del") // Defines text that has been deleted from a document. 293 | val CharSequence.details: Selector get() = custom("details") // Defines additional details that the user can view or hide. 294 | val CharSequence.dfn: Selector get() = custom("dfn") // Represents the defining instance of a term. 295 | val CharSequence.dialog: Selector get() = custom("dialog") // Defines a dialog box or window. 296 | val CharSequence.dir: Selector get() = custom("dir") // Defines a directory list. Not supported in HTML5. Use
    instead. 297 | val CharSequence.div: Selector get() = custom("div") // Defines a section in a document. 298 | val CharSequence.dl: Selector get() = custom("dl") // Defines a description list. 299 | val CharSequence.dt: Selector get() = custom("dt") // Defines a term/name in a description list. 300 | val CharSequence.em: Selector get() = custom("em") // Defines emphasized text. 301 | val CharSequence.embed: Selector get() = custom("embed") // Defines a container for an external (non-HTML) application. 302 | val CharSequence.fieldset: Selector get() = custom("fieldset") // Groups related elements in a form. 303 | val CharSequence.figcaption: Selector get() = custom("figcaption") // Defines a caption for a
    element. 304 | val CharSequence.figure: Selector get() = custom("figure") // Specifies self-contained content. 305 | val CharSequence.font: Selector get() = custom("font") // Defines font, color, and size for text. Not supported in HTML5. Use CSS instead. 306 | val CharSequence.footer: Selector get() = custom("footer") // Defines a footer for a document or section. 307 | val CharSequence.form: Selector get() = custom("form") // Defines an HTML form for user input. 308 | val CharSequence.frame: Selector get() = custom("frame") // Defines a window (a frame) in a frameset. Not supported in HTML5. 309 | val CharSequence.frameset: Selector get() = custom("frameset") // Defines a set of frames. Not supported in HTML5. 310 | val CharSequence.h1: Selector get() = custom("h1") // Defines HTML headings. 311 | val CharSequence.h2: Selector get() = custom("h2") // Defines HTML headings. 312 | val CharSequence.h3: Selector get() = custom("h3") // Defines HTML headings. 313 | val CharSequence.h4: Selector get() = custom("h4") // Defines HTML headings. 314 | val CharSequence.h5: Selector get() = custom("h5") // Defines HTML headings. 315 | val CharSequence.h6: Selector get() = custom("h6") // Defines HTML headings. 316 | val CharSequence.head: Selector get() = custom("head") // Defines information about the document. 317 | val CharSequence.header: Selector get() = custom("header") // Defines a header for a document or section. 318 | val CharSequence.hr: Selector get() = custom("hr") // Defines a thematic change in the content. 319 | val CharSequence.html: Selector get() = custom("html") // Defines the root of an HTML document. 320 | val CharSequence.i: Selector get() = custom("i") // Defines a part of text in an alternate voice or mood. 321 | val CharSequence.iframe: Selector get() = custom("iframe") // Defines an inline frame. 322 | val CharSequence.img: Selector get() = custom("img") // Defines an image. 323 | val CharSequence.input: Selector get() = custom("input") // Defines an input control. 324 | val CharSequence.ins: Selector get() = custom("ins") // Defines a text that has been inserted into a document. 325 | val CharSequence.kbd: Selector get() = custom("kbd") // Defines keyboard input. 326 | val CharSequence.keygen: Selector get() = custom("keygen") // Defines a key-pair generator field (for forms). 327 | val CharSequence.label: Selector get() = custom("label") // Defines a label for an element. 328 | val CharSequence.legend: Selector get() = custom("legend") // Defines a caption for a
    element. 329 | val CharSequence.li: Selector get() = custom("li") // Defines a list item. 330 | val CharSequence.link: Selector get() = custom("link") // Defines the relationship between a document and an external resource (most used to link to style sheets). 331 | val CharSequence.main: Selector get() = custom("main") // Specifies the main content of a document. 332 | val CharSequence.map: Selector get() = custom("map") // Defines a client-side image-map. 333 | val CharSequence.mark: Selector get() = custom("mark") // Defines marked/highlighted text. 334 | val CharSequence.menu: Selector get() = custom("menu") // Defines a list/menu of commands. 335 | val CharSequence.menuitem: Selector get() = custom("menuitem") // Defines a command/menu item that the user can invoke from a popup menu. 336 | val CharSequence.meta: Selector get() = custom("meta") // Defines metadata about an HTML document. 337 | val CharSequence.meter: Selector get() = custom("meter") // Defines a scalar measurement within a known range (a gauge). 338 | val CharSequence.nav: Selector get() = custom("nav") // Defines navigation links. 339 | val CharSequence.noframes: Selector get() = custom("noframes") // Defines an alternate content for users that do not support frames. Not supported in HTML5. 340 | val CharSequence.noscript: Selector get() = custom("noscript") // Defines an alternate content for users that do not support client-side scripts. 341 | val CharSequence.`object`: Selector get() = custom("object") // Defines an embedded object. 342 | val CharSequence.ol: Selector get() = custom("ol") // Defines an ordered list. 343 | val CharSequence.optgroup: Selector get() = custom("optgroup") // Defines a group of related options in a drop-down list. 344 | val CharSequence.option: Selector get() = custom("option") // Defines an option in a drop-down list. 345 | val CharSequence.output: Selector get() = custom("output") // Defines the result of a calculation. 346 | val CharSequence.p: Selector get() = custom("p") // Defines a paragraph. 347 | val CharSequence.param: Selector get() = custom("param") // Defines a parameter for an object. 348 | val CharSequence.pre: Selector get() = custom("pre") // Defines preformatted text. 349 | val CharSequence.progress: Selector get() = custom("progress") // Represents the progress of a task. 350 | val CharSequence.q: Selector get() = custom("q") // Defines a short quotation. 351 | val CharSequence.rp: Selector get() = custom("rp") // Defines what to show in browsers that do not support ruby annotations. 352 | val CharSequence.rt: Selector get() = custom("rt") // Defines an explanation/pronunciation of characters (for East Asian typography). 353 | val CharSequence.ruby: Selector get() = custom("ruby") // Defines a ruby annotation (for East Asian typography). 354 | val CharSequence.s: Selector get() = custom("s") // Defines text that is no longer correct. 355 | val CharSequence.samp: Selector get() = custom("samp") // Defines sample output from a computer program. 356 | val CharSequence.script: Selector get() = custom("script") // Defines a client-side script. 357 | val CharSequence.section: Selector get() = custom("section") // Defines a section in a document. 358 | val CharSequence.select: Selector get() = custom("select") // Defines a drop-down list. 359 | val CharSequence.small: Selector get() = custom("small") // Defines smaller text. 360 | val CharSequence.source: Selector get() = custom("source") // Defines multiple media resources for media elements (