Compare commits
31 Commits
function-b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e1c1cd99f | |||
| 996a8f385e | |||
| d2349c9308 | |||
| e0a4ff54de | |||
| 3f5f1c6543 | |||
| ac491761d9 | |||
| 0d8157cabf | |||
| 1e37ac07ba | |||
| 780dd0782a | |||
| 70a52a3bb5 | |||
| 68ce4ffa16 | |||
| 9f3be7cadd | |||
| 9217476ae7 | |||
| 7e86441d84 | |||
| 6375a0d78d | |||
| b2c112f711 | |||
| 95c190defd | |||
| 4137427989 | |||
| ca68871eca | |||
| d4a8b18ec2 | |||
| 1dc6f6cf9f | |||
| adec3b21b0 | |||
| 9e43f5b746 | |||
| 57edc59853 | |||
| 536d095b96 | |||
| 95fb7ec923 | |||
| c5ba12032d | |||
| 2b725f6af2 | |||
| 46d99d4122 | |||
| d1094bddef | |||
| 4730e6d3d7 |
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Rien
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
123
build.gradle.kts
123
build.gradle.kts
@@ -1,36 +1,48 @@
|
||||
@file:OptIn(ExperimentalWasmDsl::class)
|
||||
|
||||
import com.vanniktech.maven.publish.SonatypeHost
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "1.4.32"
|
||||
`maven-publish`
|
||||
kotlin("multiplatform") version "2.1.10"
|
||||
id("com.vanniktech.maven.publish") version "0.31.0"
|
||||
signing
|
||||
id("org.jetbrains.dokka") version "2.0.0"
|
||||
}
|
||||
|
||||
group = "nl.astraeus"
|
||||
version = "0.4.19"
|
||||
version = "1.1.0"
|
||||
|
||||
repositories {
|
||||
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js(BOTH) {
|
||||
js(IR) {
|
||||
browser {
|
||||
testTask {
|
||||
useKarma {
|
||||
useFirefox()
|
||||
//useChrome()
|
||||
/* testTask {
|
||||
// work around, browser test is broken atm
|
||||
enabled = false
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
wasmJs {
|
||||
//moduleName = project.name
|
||||
browser()
|
||||
|
||||
mavenPublication {
|
||||
groupId = group as String
|
||||
pom { name = "${project.name}-wasm-js" }
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {}
|
||||
val commonMain by getting
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
implementation(kotlin("test"))
|
||||
}
|
||||
}
|
||||
val jvmTest by getting {
|
||||
@@ -38,46 +50,69 @@ kotlin {
|
||||
implementation(kotlin("test-junit"))
|
||||
}
|
||||
}
|
||||
val jsTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-js"))
|
||||
}
|
||||
}
|
||||
val jsMain by getting
|
||||
val wasmJsMain by getting
|
||||
}
|
||||
}
|
||||
|
||||
val javadocJar by tasks.registering(Jar::class) {
|
||||
archiveClassifier.set("javadoc")
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
name = "releases"
|
||||
// change to point to your repo, e.g. http://my.org/repo
|
||||
url = uri("http://nexus.astraeus.nl/nexus/content/repositories/releases")
|
||||
credentials {
|
||||
val nexusUsername: String by project
|
||||
val nexusPassword: String by project
|
||||
name = "gitea"
|
||||
setUrl("https://gitea.astraeus.nl/api/packages/rnentjes/maven")
|
||||
|
||||
username = nexusUsername
|
||||
password = nexusPassword
|
||||
}
|
||||
}
|
||||
maven {
|
||||
name = "snapshots"
|
||||
// change to point to your repo, e.g. http://my.org/repo
|
||||
url = uri("http://nexus.astraeus.nl/nexus/content/repositories/snapshots")
|
||||
credentials {
|
||||
val nexusUsername: String by project
|
||||
val nexusPassword: String by project
|
||||
credentials() {
|
||||
val giteaUsername: kotlin.String? by project
|
||||
val giteaPassword: kotlin.String? by project
|
||||
|
||||
username = nexusUsername
|
||||
password = nexusPassword
|
||||
username = giteaUsername
|
||||
password = giteaPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
publications {
|
||||
val kotlinMultiplatform by getting {
|
||||
artifactId = "kotlin-css-generator"
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
signing {
|
||||
sign(publishing.publications)
|
||||
}
|
||||
|
||||
|
||||
tasks.withType<AbstractPublishToMaven> {
|
||||
dependsOn(tasks.withType<Sign>())
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
|
||||
|
||||
signAllPublications()
|
||||
|
||||
coordinates(group.toString(), name, version.toString())
|
||||
|
||||
pom {
|
||||
name = "kotlin-css-generator"
|
||||
description = "Kotlin css generator"
|
||||
inceptionYear = "2020"
|
||||
url = "https://github.com/rnentjes/kotlin-css-generator"
|
||||
licenses {
|
||||
license {
|
||||
name = "MIT"
|
||||
url = "https://opensource.org/licenses/MIT"
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = "rnentjes"
|
||||
name = "Rien Nentjes"
|
||||
email = "info@nentjes.com"
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = "https://github.com/rnentjes/kotlin-css-generator"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.js.compiler=both
|
||||
|
||||
nexusUsername=deployment
|
||||
nexusPassword=bGtci5GLFbqORmruICmTWFmGLY
|
||||
11
gradle.properties.example
Normal file
11
gradle.properties.example
Normal file
@@ -0,0 +1,11 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.js.compiler=both
|
||||
|
||||
nexusUsername=deployment
|
||||
nexusPassword=
|
||||
|
||||
signingKeyId=
|
||||
signingPassword=
|
||||
signingSecretKeyRingFile=
|
||||
ossrhUsername=
|
||||
ossrhPassword=
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
98
publish/build.gradle.kts
Normal file
98
publish/build.gradle.kts
Normal file
@@ -0,0 +1,98 @@
|
||||
import java.util.*
|
||||
|
||||
plugins {
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
project.extra.set("PUBLISH_GROUP_ID", "nl.astraeus")
|
||||
project.extra.set("PUBLISH_VERSION", "1.0.0")
|
||||
project.extra.set("PUBLISH_ARTIFACT_ID", "kotlin-css-generator")
|
||||
|
||||
apply(from = "${rootProject.projectDir}/build.gradle.kts")
|
||||
|
||||
// Stub secrets to let the project sync and build without the publication values set up
|
||||
extra["signing.keyId"] = null
|
||||
extra["signing.password"] = null
|
||||
extra["signing.secretKeyRingFile"] = null
|
||||
extra["ossrhUsername"] = null
|
||||
extra["ossrhPassword"] = null
|
||||
|
||||
// Grabbing secrets from local.properties file or from environment variables, which could be used on CI
|
||||
val secretPropsFile = project.rootProject.file("local.properties")
|
||||
if (secretPropsFile.exists()) {
|
||||
secretPropsFile.reader().use {
|
||||
Properties().apply {
|
||||
load(it)
|
||||
}
|
||||
}.onEach { (name, value) ->
|
||||
extra[name.toString()] = value
|
||||
}
|
||||
} else {
|
||||
extra["signing.keyId"] = System.getenv("SIGNING_KEY_ID")
|
||||
extra["signing.password"] = System.getenv("SIGNING_PASSWORD")
|
||||
extra["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE")
|
||||
extra["ossrhUsername"] = System.getenv("OSSRH_USERNAME")
|
||||
extra["ossrhPassword"] = System.getenv("OSSRH_PASSWORD")
|
||||
}
|
||||
|
||||
val javadocJar by tasks.registering(Jar::class) {
|
||||
archiveClassifier.set("javadoc")
|
||||
}
|
||||
|
||||
fun getExtraString(name: String) = extra[name]?.toString()
|
||||
|
||||
publishing {
|
||||
// Configure maven central repository
|
||||
repositories {
|
||||
maven {
|
||||
name = "sonatype"
|
||||
setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||
credentials {
|
||||
username = getExtraString("ossrhUsername")
|
||||
password = getExtraString("ossrhPassword")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure all publications
|
||||
publications.withType<MavenPublication> {
|
||||
|
||||
// Stub javadoc.jar artifact
|
||||
artifact(javadocJar.get())
|
||||
|
||||
// Provide artifacts information requited by Maven Central
|
||||
pom {
|
||||
name.set("kotlin-css-generator")
|
||||
description.set("Kotlin css generator")
|
||||
url.set("https://github.com/rnentjes/kotlin-css-generator")
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name.set("MIT")
|
||||
url.set("https://opensource.org/licenses/MIT")
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id.set("rnentjes")
|
||||
name.set("Rien Nentjes")
|
||||
email.set("info@nentjes.com")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url.set("https://github.com/rnentjes/kotlin-css-generator")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Signing artifacts. Signing.* extra properties values will be used
|
||||
|
||||
signing {
|
||||
sign(publishing.publications)
|
||||
}
|
||||
121
publish/publish-mavencentral.gradle
Normal file
121
publish/publish-mavencentral.gradle
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
task publishSourcesJar(type: Jar) {
|
||||
archiveClassifier.set('sources')
|
||||
|
||||
// For pure Kotlin libraries, in case you have them
|
||||
from sourceSets.main.java.srcDirs
|
||||
//from sourceSets.main.kotlin.srcDirs
|
||||
}
|
||||
|
||||
task packageJavadoc(type: Jar) {
|
||||
from javadoc
|
||||
classifier = 'javadoc'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives publishSourcesJar
|
||||
}
|
||||
|
||||
File secretPropsFile = project.rootProject.file('local.properties')
|
||||
|
||||
ext["signing.keyId"] = ''
|
||||
ext["signing.password"] = ''
|
||||
ext["signing.secretKeyRingFile"] = ''
|
||||
ext["ossrhUsername"] = ''
|
||||
ext["ossrhPassword"] = ''
|
||||
ext["sonatypeStagingProfileId"] = ''
|
||||
|
||||
if (secretPropsFile.exists()) {
|
||||
Properties p = new Properties()
|
||||
|
||||
new FileInputStream(secretPropsFile).withCloseable { is ->
|
||||
p.load(is)
|
||||
}
|
||||
|
||||
p.each { name, value ->
|
||||
ext[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
release(MavenPublication) {
|
||||
// The coordinates of the library, being set from variables that
|
||||
// we'll set up later
|
||||
groupId PUBLISH_GROUP_ID
|
||||
artifactId PUBLISH_ARTIFACT_ID
|
||||
version PUBLISH_VERSION
|
||||
|
||||
// Two artifacts, the `aar` (or `jar`) and the sources
|
||||
if (project.plugins.findPlugin("com.android.library")) {
|
||||
artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
|
||||
} else {
|
||||
artifact("$buildDir/libs/${project.getName()}-${version}.jar")
|
||||
}
|
||||
artifact publishSourcesJar
|
||||
artifact packageJavadoc
|
||||
|
||||
// Mostly self-explanatory metadata
|
||||
pom {
|
||||
name = PUBLISH_ARTIFACT_ID
|
||||
description = 'Simple JDBC wrapper for query statistics'
|
||||
url = 'https://github.com/rnentjes/Simple-jdbc-statistics'
|
||||
licenses {
|
||||
license {
|
||||
name = 'MIT License'
|
||||
url = 'https://github.com/rnentjes/Simple-jdbc-statistics/blob/master/LICENCE.txt'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'rnentjes'
|
||||
name = 'Rien Nentjes'
|
||||
email = 'info@nentjes.com'
|
||||
}
|
||||
// Add all other devs here...
|
||||
}
|
||||
// Version control info - if you're using GitHub, follow the format as seen here
|
||||
scm {
|
||||
connection = 'scm:git:github.com/rnentjes/Simple-jdbc-statistics.git'
|
||||
developerConnection = 'scm:git:ssh://github.com/rnentjes/Simple-jdbc-statistics.git'
|
||||
url = 'https://github.com/rnentjes/Simple-jdbc-statistics.git/tree/main'
|
||||
}
|
||||
// A slightly hacky fix so that your POM will include any transitive dependencies
|
||||
// that your library builds upon
|
||||
/*
|
||||
witXml {
|
||||
def dependenciesNode = asNode().appendNode('dependencies')
|
||||
|
||||
project.configurations.implementation.allDependencies.each {
|
||||
def dependencyNode = dependenciesNode.appendNode('dependency')
|
||||
dependencyNode.appendNode('groupId', it.group)
|
||||
dependencyNode.appendNode('artifactId', it.name)
|
||||
dependencyNode.appendNode('version', it.version)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
// The repository to publish to, Sonatype/MavenCentral
|
||||
repositories {
|
||||
maven {
|
||||
// This is an arbitrary name, you may also use "mavencentral" or
|
||||
// any other name that's descriptive for you
|
||||
name = "sonatype"
|
||||
url = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
|
||||
credentials {
|
||||
username ossrhUsername
|
||||
password ossrhPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
sign publishing.publications
|
||||
}
|
||||
12
publish/settings.gradle.kts
Normal file
12
publish/settings.gradle.kts
Normal file
@@ -0,0 +1,12 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
maven { setUrl("https://plugins.gradle.org/m2/") }
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "publish-kotlin-css-generator"
|
||||
|
||||
//enableFeaturePreview("GRADLE_METADATA")
|
||||
//include(":publish")
|
||||
176
readme.md
Normal file
176
readme.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Css generator like less/sass in kotlin multiplatform
|
||||
|
||||
This library is for generating css from a kotlin dsl.
|
||||
It can be used as an alternative to less/sass or as a runtime library to generate css on-the-fly.
|
||||
|
||||
Tools like less and sass are often used as a build step and take some time.
|
||||
This library is meant to be fast enough to generate the css on the fly either from the server or directly in the browser.
|
||||
|
||||
## Usage
|
||||
|
||||
Include in build.gradle.kts:
|
||||
|
||||
```kotlin
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api("nl.astraeus:kotlin-css-generator:1.0.9")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
## Nesting / colors / variables
|
||||
|
||||
```kotlin
|
||||
val color = hsla(0, 50, 50, 1.0)
|
||||
val backgroundColor = Color.white
|
||||
|
||||
val css = style {
|
||||
select(cls("button")) {
|
||||
padding(5.px)
|
||||
|
||||
select("a") {
|
||||
color(color)
|
||||
backgroundColor(backgroundColor)
|
||||
|
||||
hover {
|
||||
color(color.lighten(10))
|
||||
backgroundColor(backgroundColor.darken(10))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To generate the css call get generateCss function:
|
||||
|
||||
```kotlin
|
||||
val cssString: String = css.generateCss()
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```css
|
||||
.button {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.button a {
|
||||
color: hsla(0, 50%, 50%, 1.0);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.button a:hover {
|
||||
color: hsla(0, 50%, 55%, 1.0);
|
||||
background-color: rgba(229, 229, 229, 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
There are several options when generating the css, for example minified:
|
||||
|
||||
|
||||
```kotlin
|
||||
val cssString: String = css.generateCss(minified = true)
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```css
|
||||
.button{padding:5px;}.buttona{color:hsla(0,50%,50%,1.0);background-color:white;}.buttona:hover{color:hsla(0,50%,55%,1.0);background-color:rgba(229,229,229,1.0);}
|
||||
```
|
||||
|
||||
## Mixins
|
||||
|
||||
As it's all just kotlin code, includes and mixins etc. are just functions calls.
|
||||
|
||||
```kotlin
|
||||
fun Style.borderStyles(borderWidth: Measurement = 2.px) {
|
||||
borderWidth(borderWidth)
|
||||
borderColor(Color.aquamarine)
|
||||
borderStyle(BorderStyle.solid)
|
||||
}
|
||||
|
||||
val css = style {
|
||||
select(txt("a"), cls("button")) {
|
||||
borderStyles()
|
||||
|
||||
color(Color.white)
|
||||
}
|
||||
|
||||
select(cls("btn-primary")) {
|
||||
borderStyles(3.px)
|
||||
color(Color.blue)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```css
|
||||
a {
|
||||
border-width: 2px;
|
||||
border-color: aquamarine;
|
||||
border-style: solid;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button {
|
||||
border-width: 2px;
|
||||
border-color: aquamarine;
|
||||
border-style: solid;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
border-width: 3px;
|
||||
border-color: aquamarine;
|
||||
border-style: solid;
|
||||
color: blue;
|
||||
}
|
||||
```
|
||||
|
||||
Giving the option combineEqualBlocks to the generateCss call will combine the a and .button blocks with the following result:
|
||||
|
||||
```css
|
||||
a,
|
||||
.button {
|
||||
border-width: 2px;
|
||||
border-color: aquamarine;
|
||||
border-style: solid;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
border-width: 3px;
|
||||
border-color: aquamarine;
|
||||
border-style: solid;
|
||||
color: blue;
|
||||
}
|
||||
```
|
||||
|
||||
## Measurements
|
||||
|
||||
Sizes and widths are given in measurements, there are extension variables to help with these:
|
||||
|
||||
```kotlin
|
||||
select("body") {
|
||||
fontSize(1.2.em)
|
||||
borderWidth(3.px)
|
||||
width(75.prc)
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```css
|
||||
body {
|
||||
font-size: 1.2em;
|
||||
border-width: 3px;
|
||||
width: 75%;
|
||||
}
|
||||
```
|
||||
@@ -1,13 +1,20 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
|
||||
|
||||
google()
|
||||
mavenCentral()
|
||||
|
||||
maven { setUrl("https://plugins.gradle.org/m2/") }
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "kotlin-css-generator"
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
enableFeaturePreview("GRADLE_METADATA")
|
||||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
|
||||
}
|
||||
|
||||
rootProject.name = "kotlin-css-generator"
|
||||
|
||||
@@ -2,7 +2,7 @@ package nl.astraeus.css.properties
|
||||
|
||||
class AlignItems(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val stretch = AlignItems("stretch")
|
||||
|
||||
@@ -2,7 +2,7 @@ package nl.astraeus.css.properties
|
||||
|
||||
class AlignSelf(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val auto = AlignSelf("auto")
|
||||
|
||||
@@ -2,7 +2,7 @@ package nl.astraeus.css.properties
|
||||
|
||||
class All(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val unset = All("unset")
|
||||
|
||||
@@ -30,7 +30,7 @@ class AnimationFillMode(
|
||||
|
||||
class AnimationFrame(
|
||||
value: String = ""
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
fun name(name: String) = AnimationFrame(name)
|
||||
|
||||
@@ -1,26 +1,8 @@
|
||||
package nl.astraeus.css.properties
|
||||
|
||||
class BorderRadius(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
companion object {
|
||||
fun px(nr: Int) = BorderRadius("${nr}px")
|
||||
fun em(nr: Int) = BorderRadius("${nr}em")
|
||||
fun em(nr: Double) = BorderRadius("${nr}em")
|
||||
fun perc(nr: Int) = BorderRadius("${nr}%")
|
||||
fun perc(nr: Double) = BorderRadius("${nr}%")
|
||||
fun pc(nr: Int) = BorderRadius("${nr}pc")
|
||||
fun pc(nr: Double) = BorderRadius("${nr}pc")
|
||||
fun cm(nr: Int) = BorderRadius("${nr}cm")
|
||||
fun cm(nr: Double) = BorderRadius("${nr}cm")
|
||||
val initial = BorderRadius("initial")
|
||||
val inherit = BorderRadius("inherit")
|
||||
}
|
||||
}
|
||||
|
||||
class BorderStyle(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val none = BorderStyle("none")
|
||||
@@ -40,7 +22,7 @@ class BorderStyle(
|
||||
|
||||
class BorderWidth(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val thin = BorderWidth("thin")
|
||||
@@ -53,7 +35,7 @@ class BorderWidth(
|
||||
|
||||
class BorderCollapse(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val separate = BorderCollapse("separate")
|
||||
@@ -61,9 +43,9 @@ class BorderCollapse(
|
||||
}
|
||||
}
|
||||
|
||||
class BorderImageWidth (
|
||||
class BorderImageWidth(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
fun px(nr: Int) = BorderImageWidth("${nr}px")
|
||||
@@ -78,7 +60,7 @@ class BorderImageWidth (
|
||||
|
||||
class BorderSpacing(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
fun px(nr: Int) = BorderSpacing("${nr}px")
|
||||
|
||||
@@ -2,7 +2,7 @@ package nl.astraeus.css.properties
|
||||
|
||||
class BoxDecorationBreak(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val slice = BoxDecorationBreak("slice")
|
||||
@@ -15,7 +15,7 @@ class BoxDecorationBreak(
|
||||
|
||||
class BoxShadow(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val none = BoxShadow("none")
|
||||
@@ -29,7 +29,7 @@ class BoxShadow(
|
||||
|
||||
class BoxSizing(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val contextBox = BoxSizing("content-box")
|
||||
|
||||
@@ -3,7 +3,7 @@ package nl.astraeus.css.properties
|
||||
|
||||
class Break(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val auto = Break("auto")
|
||||
|
||||
31
src/commonMain/kotlin/nl/astraeus/css/properties/Calc.kt
Normal file
31
src/commonMain/kotlin/nl/astraeus/css/properties/Calc.kt
Normal file
@@ -0,0 +1,31 @@
|
||||
package nl.astraeus.css.properties
|
||||
|
||||
fun calc(expression: CalcExpression) = Calc(expression)
|
||||
|
||||
infix operator fun CalcExpression.plus(other: CalcExpression) = CompoundCalcExpression(
|
||||
this,
|
||||
"+",
|
||||
other
|
||||
)
|
||||
|
||||
infix operator fun CalcExpression.minus(other: CalcExpression) = CompoundCalcExpression(
|
||||
this,
|
||||
"-",
|
||||
other
|
||||
)
|
||||
|
||||
interface CalcExpression
|
||||
|
||||
class CompoundCalcExpression(
|
||||
val left: CalcExpression,
|
||||
val operator: String,
|
||||
val right: CalcExpression
|
||||
): CalcExpression {
|
||||
override fun toString(): String {
|
||||
return "$left $operator $right"
|
||||
}
|
||||
}
|
||||
|
||||
class Calc(
|
||||
expression: CalcExpression
|
||||
) : CssProperty(expression.toString())
|
||||
@@ -2,7 +2,7 @@ package nl.astraeus.css.properties
|
||||
|
||||
class CaptionSide(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val top = CaptionSide("top")
|
||||
|
||||
@@ -3,7 +3,7 @@ package nl.astraeus.css.properties
|
||||
|
||||
class Clear(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val none = Clear("none")
|
||||
|
||||
@@ -5,6 +5,25 @@ import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private val hexString = "0123456789abcdef"
|
||||
|
||||
private fun Int.toColorHex(minimumDigits: Int = 2): String {
|
||||
val result = StringBuilder()
|
||||
var value = this
|
||||
|
||||
while(value > 0) {
|
||||
result.append(hexString[value%16])
|
||||
|
||||
value /= 16
|
||||
}
|
||||
|
||||
while(result.length < minimumDigits) {
|
||||
result.append("0")
|
||||
}
|
||||
|
||||
return result.reverse().toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* See [CSS Color Module Level 3](https://www.w3.org/TR/2018/REC-css-color-3-20180619/)
|
||||
*
|
||||
@@ -30,6 +49,345 @@ class Color(value: String) : CssProperty(value) {
|
||||
this.rgb = rgb
|
||||
}
|
||||
|
||||
fun hasAlpha(): Boolean = isRgba() || isHexa() || isHsla()
|
||||
|
||||
fun getAlpha(): Double = when {
|
||||
isHexa() || isRgba() -> {
|
||||
toRGBA().alpha
|
||||
}
|
||||
isHsla() -> {
|
||||
fromHSLANotation().alpha
|
||||
}
|
||||
else -> {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
fun toHex(): String = if (isHsla() || isHsl()) {
|
||||
fromHSLANotation().asRGBA().asHex()
|
||||
} else {
|
||||
toRGBA().asHex()
|
||||
}
|
||||
|
||||
fun isHsla(): Boolean {
|
||||
val v = rgb ?: value
|
||||
|
||||
return v.startsWith("hsla")
|
||||
}
|
||||
|
||||
fun isHsl(): Boolean {
|
||||
val v = rgb ?: value
|
||||
|
||||
return v.startsWith("hsl(")
|
||||
}
|
||||
|
||||
fun isRgba(): Boolean {
|
||||
val v = rgb ?: value
|
||||
|
||||
return v.startsWith("rgba(")
|
||||
}
|
||||
|
||||
fun isRgb(): Boolean {
|
||||
val v = rgb ?: value
|
||||
|
||||
return v.startsWith("rgb(")
|
||||
}
|
||||
|
||||
fun isHex(): Boolean {
|
||||
val v = rgb ?: value
|
||||
|
||||
return v.startsWith("#") && v.length < 8
|
||||
}
|
||||
|
||||
fun isHexa(): Boolean {
|
||||
val v = rgb ?: value
|
||||
|
||||
return v.startsWith("#") && v.length > 7
|
||||
}
|
||||
|
||||
/**
|
||||
* withAlpha preserves existing alpha value: rgba(0, 0, 0, 0.5).withAlpha(0.1) = rgba(0, 0, 0, 0.05)
|
||||
*/
|
||||
fun withAlpha(alpha: Double) =
|
||||
when {
|
||||
value.startsWith("hsl", true) -> with(fromHSLANotation()) { hsla(hue, saturation, lightness, normalizeAlpha(alpha) * this.alpha) }
|
||||
else -> with(toRGBA()) { rgba(red, green, blue, normalizeAlpha(alpha) * this.alpha) }
|
||||
}
|
||||
|
||||
/**
|
||||
* changeAlpha rewrites existing alpha value: rgba(0, 0, 0, 0.5).withAlpha(0.1) = rgba(0, 0, 0, 0.1)
|
||||
*/
|
||||
fun changeAlpha(alpha: Double) =
|
||||
when {
|
||||
value.startsWith("hsl", true) -> with(fromHSLANotation()) { hsla(hue, saturation, lightness, normalizeAlpha(alpha)) }
|
||||
else -> with(toRGBA()) { rgba(red, green, blue, normalizeAlpha(alpha)) }
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/2049230/convert-rgba-color-to-rgb
|
||||
fun blend(backgroundColor: Color): Color {
|
||||
val source = this.toRGBA()
|
||||
val background = backgroundColor.toRGBA()
|
||||
|
||||
val targetR = ((1 - source.alpha) * background.red) + (source.alpha * source.red)
|
||||
val targetG = ((1 - source.alpha) * background.green) + (source.alpha * source.green)
|
||||
val targetB = ((1 - source.alpha) * background.blue) + (source.alpha * source.blue)
|
||||
|
||||
return rgb(targetR.roundToInt(), targetG.roundToInt(), targetB.roundToInt())
|
||||
}
|
||||
|
||||
/**
|
||||
* Lighten the color by the specified percent (between 0-100), returning a new instance of Color.
|
||||
*
|
||||
* @param percent the percent to lighten the Color
|
||||
* @return a new lightened version of this color
|
||||
*/
|
||||
fun lighten(percent: Int): Color {
|
||||
val isHSLA = value.startsWith("hsl", ignoreCase = true)
|
||||
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
|
||||
|
||||
val lightness = hsla.lightness + (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
val newHSLa = hsla.copy(lightness = normalizePercent(lightness))
|
||||
return if (isHSLA) {
|
||||
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
|
||||
} else {
|
||||
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Darken the color by the specified percent (between 0-100), returning a new instance of Color.
|
||||
*
|
||||
* @param percent the percent to darken the Color
|
||||
* @return a new darkened version of this color
|
||||
*/
|
||||
fun darken(percent: Int): Color {
|
||||
val isHSLA = value.startsWith("hsl", ignoreCase = true)
|
||||
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
|
||||
|
||||
val darkness = hsla.lightness - (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
val newHSLa = hsla.copy(lightness = normalizePercent(darkness))
|
||||
return if (isHSLA) {
|
||||
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
|
||||
} else {
|
||||
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase contrast, if lightness > 50 then darken else lighten
|
||||
*
|
||||
* @param percent the percent to lighten/darken the Color
|
||||
* @return a new ligtened/darkened version of this color
|
||||
*/
|
||||
fun contrast(percent: Int): Color {
|
||||
val isHSLA = value.startsWith("hsl", ignoreCase = true)
|
||||
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
|
||||
|
||||
val darkness = if (hsla.lightness > 50) {
|
||||
hsla.lightness - (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
} else {
|
||||
hsla.lightness + (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
}
|
||||
|
||||
val newHSLa = hsla.copy(lightness = normalizePercent(darkness))
|
||||
return if (isHSLA) {
|
||||
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
|
||||
} else {
|
||||
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saturate the color by the specified percent (between 0-100), returning a new instance of Color.
|
||||
*
|
||||
* @param percent the percent to saturate the Color
|
||||
* @return a new saturated version of this color
|
||||
*/
|
||||
fun saturate(percent: Int): Color {
|
||||
val isHSLA = value.startsWith("hsl", ignoreCase = true)
|
||||
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
|
||||
|
||||
val saturation = hsla.saturation + (hsla.saturation * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
val newHSLa = hsla.copy(saturation = normalizePercent(saturation))
|
||||
return if (isHSLA) {
|
||||
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
|
||||
} else {
|
||||
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Desaturate the color by the specified percent (between 0-100), returning a new instance of Color.
|
||||
*
|
||||
* @param percent the percent to desaturate the Color
|
||||
* @return a new desaturated version of this color
|
||||
*/
|
||||
fun desaturate(percent: Int): Color {
|
||||
val isHSLA = value.startsWith("hsl", ignoreCase = true)
|
||||
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
|
||||
|
||||
val desaturation = hsla.saturation - (hsla.saturation * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
val newHSLa = hsla.copy(saturation = normalizePercent(desaturation))
|
||||
return if (isHSLA) {
|
||||
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
|
||||
} else {
|
||||
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
|
||||
}
|
||||
}
|
||||
|
||||
internal data class RGBA(
|
||||
val red: Int,
|
||||
val green: Int,
|
||||
val blue: Int,
|
||||
val alpha: Double = 1.0
|
||||
) {
|
||||
|
||||
// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
||||
fun asHSLA(): HSLA {
|
||||
// scale R, G, B values into 0..1 fractions
|
||||
val r = red / 255.0
|
||||
val g = green / 255.0
|
||||
val b = blue / 255.0
|
||||
|
||||
val cMax = maxOf(r, g, b)
|
||||
val cMin = minOf(r, g, b)
|
||||
val chroma = cMax - cMin
|
||||
|
||||
val lg = normalizeFractionalPercent((cMax + cMin) / 2)
|
||||
val s = if (chroma != 0.0) normalizeFractionalPercent(chroma / (1.0 - abs((2.0 * lg) - 1.0))) else 0.0
|
||||
val h = when (cMax) {
|
||||
cMin -> 0.0
|
||||
r -> 60 * (((g - b) / chroma) % 6.0)
|
||||
g -> 60 * (((b - r) / chroma) + 2)
|
||||
b -> 60 * (((r - g) / chroma) + 4)
|
||||
else -> error("Unexpected value for max") // theoretically unreachable bc maxOf(r, g, b) above
|
||||
}
|
||||
|
||||
return HSLA(normalizeHue(h), (s * 100).roundToInt(), (lg * 100).roundToInt(), alpha)
|
||||
}
|
||||
|
||||
fun asHex(): String {
|
||||
val result = StringBuilder()
|
||||
|
||||
result.append(red.toColorHex(2))
|
||||
result.append(green.toColorHex(2))
|
||||
result.append(blue.toColorHex(2))
|
||||
|
||||
return result.toString()
|
||||
}
|
||||
}
|
||||
|
||||
internal data class HSLA(
|
||||
val hue: Int,
|
||||
val saturation: Int,
|
||||
val lightness: Int,
|
||||
val alpha: Double = 1.0
|
||||
) {
|
||||
|
||||
// Algorithm from W3C link referenced in class comment (section 4.2.4. HSL color values)
|
||||
fun asRGBA(): RGBA {
|
||||
fun hueToRGB(m1: Double, m2: Double, h: Double): Double {
|
||||
val hu = if (h < 0) h + 1 else if (h > 1) h - 1 else h
|
||||
return when {
|
||||
(hu < 1.0 / 6) -> m1 + (m2 - m1) * 6 * hu
|
||||
(hu < 1.0 / 2) -> m2
|
||||
(hu < 2.0 / 3) -> m1 + ((m2 - m1) * 6 * (2.0 / 3 - hu))
|
||||
else -> m1
|
||||
}
|
||||
}
|
||||
|
||||
val lightness255 = lightness * 255 / 100
|
||||
if (saturation == 0) return RGBA(lightness255, lightness255, lightness255)
|
||||
|
||||
// scale H, S, V values into 0..1 fractions
|
||||
val h = (hue % 360.0) / 360.0
|
||||
val s = saturation / 100.0
|
||||
val lg = lightness / 100.0
|
||||
|
||||
val m2 = if (lg < 0.5) lg * (1 + s) else (lg + s - lg * s)
|
||||
val m1 = 2 * lg - m2
|
||||
val r = normalizeFractionalPercent(hueToRGB(m1, m2, h + (1.0 / 3)))
|
||||
val g = normalizeFractionalPercent(hueToRGB(m1, m2, h))
|
||||
val b = normalizeFractionalPercent(hueToRGB(m1, m2, h - (1.0 / 3)))
|
||||
return RGBA((r * 255).roundToInt(), (g * 255).roundToInt(), (b * 255).roundToInt(), alpha)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun fromHSLANotation(): HSLA {
|
||||
val match = HSLA_REGEX.find(value)
|
||||
|
||||
fun getHSLParameter(index: Int) =
|
||||
match?.groups?.get(index)?.value
|
||||
?: throw IllegalArgumentException("Expected hsl or hsla notation, got $value")
|
||||
|
||||
val hueShape = getHSLParameter(1)
|
||||
val hue = normalizeHue(
|
||||
when {
|
||||
hueShape.endsWith("grad", true) -> hueShape.substringBefore("grad").toDouble() * (9.0 / 10)
|
||||
hueShape.endsWith("rad", true) -> (hueShape.substringBefore("rad").toDouble() * 180) / PI
|
||||
hueShape.endsWith("turn", true) -> hueShape.substringBefore("turn").toDouble() * 360.0
|
||||
hueShape.endsWith("deg", true) -> hueShape.substringBefore("deg").toDouble()
|
||||
else -> hueShape.toDouble()
|
||||
}
|
||||
)
|
||||
val saturation = normalizePercent(getHSLParameter(2).toInt())
|
||||
val lightness = normalizePercent(getHSLParameter(3).toInt())
|
||||
val alpha = normalizeAlpha(match?.groups?.get(4)?.value?.toDouble() ?: 1.0)
|
||||
|
||||
return HSLA(hue, saturation, lightness, alpha)
|
||||
}
|
||||
|
||||
internal fun fromRGBANotation(): RGBA {
|
||||
val match = RGBA_REGEX.find(value)
|
||||
|
||||
fun getRGBParameter(index: Int): Int {
|
||||
val group = match?.groups?.get(index)?.value
|
||||
?: throw IllegalArgumentException("Expected rgb or rgba notation, got $value")
|
||||
|
||||
return when {
|
||||
(group.endsWith('%')) -> (normalizeFractionalPercent(group.substringBefore('%').toDouble() / 100.0) * 255.0).toInt()
|
||||
else -> normalizeRGB(group.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
val red = getRGBParameter(1)
|
||||
val green = getRGBParameter(2)
|
||||
val blue = getRGBParameter(3)
|
||||
val alpha = normalizeAlpha(match?.groups?.get(4)?.value?.toDouble() ?: 1.0)
|
||||
|
||||
return RGBA(red, green, blue, alpha)
|
||||
}
|
||||
|
||||
internal fun toRGBA(): RGBA {
|
||||
val v = rgb ?: value
|
||||
return when {
|
||||
v.startsWith("rgb") -> fromRGBANotation()
|
||||
|
||||
// Matches #rgb
|
||||
v.startsWith("#") && v.length == 4 -> RGBA(
|
||||
"${v[1]}${v[1]}".toInt(16),
|
||||
"${v[2]}${v[2]}".toInt(16),
|
||||
"${v[3]}${v[3]}".toInt(16)
|
||||
)
|
||||
|
||||
// Matches both #rrggbb
|
||||
v.startsWith("#") && v.length == 7 -> RGBA(
|
||||
(v.substring(1..2)).toInt(16),
|
||||
(v.substring(3..4)).toInt(16),
|
||||
(v.substring(5..6)).toInt(16)
|
||||
)
|
||||
|
||||
// Matches both #rrggbbaa
|
||||
v.startsWith("#") && v.length == 9 -> RGBA(
|
||||
(v.substring(1..2)).toInt(16),
|
||||
(v.substring(3..4)).toInt(16),
|
||||
(v.substring(5..6)).toInt(16),
|
||||
(v.substring(7..8)).toInt(16) / 255.0
|
||||
)
|
||||
else -> throw IllegalArgumentException("Only hexadecimal, rgb, and rgba notations are accepted, got $v")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val initial = Color("initial")
|
||||
val inherit = Color("inherit")
|
||||
@@ -219,270 +577,6 @@ class Color(value: String) : CssProperty(value) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* withAlpha preserves existing alpha value: rgba(0, 0, 0, 0.5).withAlpha(0.1) = rgba(0, 0, 0, 0.05)
|
||||
*/
|
||||
fun withAlpha(alpha: Double) =
|
||||
when {
|
||||
value.startsWith("hsl", true) -> with(fromHSLANotation()) { hsla(hue, saturation, lightness, normalizeAlpha(alpha) * this.alpha) }
|
||||
else -> with(toRGBA()) { rgba(red, green, blue, normalizeAlpha(alpha) * this.alpha) }
|
||||
}
|
||||
|
||||
/**
|
||||
* changeAlpha rewrites existing alpha value: rgba(0, 0, 0, 0.5).withAlpha(0.1) = rgba(0, 0, 0, 0.1)
|
||||
*/
|
||||
fun changeAlpha(alpha: Double) =
|
||||
when {
|
||||
value.startsWith("hsl", true) -> with(fromHSLANotation()) { hsla(hue, saturation, lightness, normalizeAlpha(alpha)) }
|
||||
else -> with(toRGBA()) { rgba(red, green, blue, normalizeAlpha(alpha)) }
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/2049230/convert-rgba-color-to-rgb
|
||||
fun blend(backgroundColor: Color): Color {
|
||||
val source = this.toRGBA()
|
||||
val background = backgroundColor.toRGBA()
|
||||
|
||||
val targetR = ((1 - source.alpha) * background.red) + (source.alpha * source.red)
|
||||
val targetG = ((1 - source.alpha) * background.green) + (source.alpha * source.green)
|
||||
val targetB = ((1 - source.alpha) * background.blue) + (source.alpha * source.blue)
|
||||
|
||||
return rgb(targetR.roundToInt(), targetG.roundToInt(), targetB.roundToInt())
|
||||
}
|
||||
|
||||
/**
|
||||
* Lighten the color by the specified percent (between 0-100), returning a new instance of Color.
|
||||
*
|
||||
* @param percent the percent to lighten the Color
|
||||
* @return a new lightened version of this color
|
||||
*/
|
||||
fun lighten(percent: Int): Color {
|
||||
val isHSLA = value.startsWith("hsl", ignoreCase = true)
|
||||
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
|
||||
|
||||
val lightness = hsla.lightness + (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
val newHSLa = hsla.copy(lightness = normalizePercent(lightness))
|
||||
return if (isHSLA) {
|
||||
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
|
||||
} else {
|
||||
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Darken the color by the specified percent (between 0-100), returning a new instance of Color.
|
||||
*
|
||||
* @param percent the percent to darken the Color
|
||||
* @return a new darkened version of this color
|
||||
*/
|
||||
fun darken(percent: Int): Color {
|
||||
val isHSLA = value.startsWith("hsl", ignoreCase = true)
|
||||
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
|
||||
|
||||
val darkness = hsla.lightness - (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
val newHSLa = hsla.copy(lightness = normalizePercent(darkness))
|
||||
return if (isHSLA) {
|
||||
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
|
||||
} else {
|
||||
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase contrast, if lightness > 50 then darken else lighten
|
||||
*
|
||||
* @param percent the percent to lighten/darken the Color
|
||||
* @return a new ligtened/darkened version of this color
|
||||
*/
|
||||
fun contrast(percent: Int): Color {
|
||||
val isHSLA = value.startsWith("hsl", ignoreCase = true)
|
||||
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
|
||||
|
||||
val darkness = if (hsla.lightness > 50) {
|
||||
hsla.lightness - (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
} else {
|
||||
hsla.lightness + (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
}
|
||||
|
||||
val newHSLa = hsla.copy(lightness = normalizePercent(darkness))
|
||||
return if (isHSLA) {
|
||||
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
|
||||
} else {
|
||||
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saturate the color by the specified percent (between 0-100), returning a new instance of Color.
|
||||
*
|
||||
* @param percent the percent to saturate the Color
|
||||
* @return a new saturated version of this color
|
||||
*/
|
||||
fun saturate(percent: Int): Color {
|
||||
val isHSLA = value.startsWith("hsl", ignoreCase = true)
|
||||
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
|
||||
|
||||
val saturation = hsla.saturation + (hsla.saturation * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
val newHSLa = hsla.copy(saturation = normalizePercent(saturation))
|
||||
return if (isHSLA) {
|
||||
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
|
||||
} else {
|
||||
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Desaturate the color by the specified percent (between 0-100), returning a new instance of Color.
|
||||
*
|
||||
* @param percent the percent to desaturate the Color
|
||||
* @return a new desaturated version of this color
|
||||
*/
|
||||
fun desaturate(percent: Int): Color {
|
||||
val isHSLA = value.startsWith("hsl", ignoreCase = true)
|
||||
val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA()
|
||||
|
||||
val desaturation = hsla.saturation - (hsla.saturation * (normalizePercent(percent) / 100.0)).roundToInt()
|
||||
val newHSLa = hsla.copy(saturation = normalizePercent(desaturation))
|
||||
return if (isHSLA) {
|
||||
hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha)
|
||||
} else {
|
||||
with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) }
|
||||
}
|
||||
}
|
||||
|
||||
internal data class RGBA(
|
||||
val red: Int,
|
||||
val green: Int,
|
||||
val blue: Int,
|
||||
val alpha: Double = 1.0
|
||||
) {
|
||||
|
||||
// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
||||
fun asHSLA(): HSLA {
|
||||
// scale R, G, B values into 0..1 fractions
|
||||
val r = red / 255.0
|
||||
val g = green / 255.0
|
||||
val b = blue / 255.0
|
||||
|
||||
val cMax = maxOf(r, g, b)
|
||||
val cMin = minOf(r, g, b)
|
||||
val chroma = cMax - cMin
|
||||
|
||||
val lg = normalizeFractionalPercent((cMax + cMin) / 2)
|
||||
val s = if (chroma != 0.0) normalizeFractionalPercent(chroma / (1.0 - abs((2.0 * lg) - 1.0))) else 0.0
|
||||
val h = when (cMax) {
|
||||
cMin -> 0.0
|
||||
r -> 60 * (((g - b) / chroma) % 6.0)
|
||||
g -> 60 * (((b - r) / chroma) + 2)
|
||||
b -> 60 * (((r - g) / chroma) + 4)
|
||||
else -> error("Unexpected value for max") // theoretically unreachable bc maxOf(r, g, b) above
|
||||
}
|
||||
|
||||
return HSLA(normalizeHue(h), (s * 100).roundToInt(), (lg * 100).roundToInt(), alpha)
|
||||
}
|
||||
}
|
||||
|
||||
internal data class HSLA(
|
||||
val hue: Int,
|
||||
val saturation: Int,
|
||||
val lightness: Int,
|
||||
val alpha: Double = 1.0
|
||||
) {
|
||||
|
||||
// Algorithm from W3C link referenced in class comment (section 4.2.4. HSL color values)
|
||||
fun asRGBA(): RGBA {
|
||||
fun hueToRGB(m1: Double, m2: Double, h: Double): Double {
|
||||
val hu = if (h < 0) h + 1 else if (h > 1) h - 1 else h
|
||||
return when {
|
||||
(hu < 1.0 / 6) -> m1 + (m2 - m1) * 6 * hu
|
||||
(hu < 1.0 / 2) -> m2
|
||||
(hu < 2.0 / 3) -> m1 + ((m2 - m1) * 6 * (2.0 / 3 - hu))
|
||||
else -> m1
|
||||
}
|
||||
}
|
||||
|
||||
if (saturation == 0) return RGBA(lightness, lightness, lightness)
|
||||
|
||||
// scale H, S, V values into 0..1 fractions
|
||||
val h = (hue % 360.0) / 360.0
|
||||
val s = saturation / 100.0
|
||||
val lg = lightness / 100.0
|
||||
|
||||
val m2 = if (lg < 0.5) lg * (1 + s) else (lg + s - lg * s)
|
||||
val m1 = 2 * lg - m2
|
||||
val r = normalizeFractionalPercent(hueToRGB(m1, m2, h + (1.0 / 3)))
|
||||
val g = normalizeFractionalPercent(hueToRGB(m1, m2, h))
|
||||
val b = normalizeFractionalPercent(hueToRGB(m1, m2, h - (1.0 / 3)))
|
||||
return RGBA((r * 255).roundToInt(), (g * 255).roundToInt(), (b * 255).roundToInt(), alpha)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun fromHSLANotation(): HSLA {
|
||||
val match = HSLA_REGEX.find(value)
|
||||
|
||||
fun getHSLParameter(index: Int) =
|
||||
match?.groups?.get(index)?.value
|
||||
?: throw IllegalArgumentException("Expected hsl or hsla notation, got $value")
|
||||
|
||||
val hueShape = getHSLParameter(1)
|
||||
val hue = normalizeHue(
|
||||
when {
|
||||
hueShape.endsWith("grad", true) -> hueShape.substringBefore("grad").toDouble() * (9.0 / 10)
|
||||
hueShape.endsWith("rad", true) -> (hueShape.substringBefore("rad").toDouble() * 180) / PI
|
||||
hueShape.endsWith("turn", true) -> hueShape.substringBefore("turn").toDouble() * 360.0
|
||||
hueShape.endsWith("deg", true) -> hueShape.substringBefore("deg").toDouble()
|
||||
else -> hueShape.toDouble()
|
||||
}
|
||||
)
|
||||
val saturation = normalizePercent(getHSLParameter(2).toInt())
|
||||
val lightness = normalizePercent(getHSLParameter(3).toInt())
|
||||
val alpha = normalizeAlpha(match?.groups?.get(4)?.value?.toDouble() ?: 1.0)
|
||||
|
||||
return HSLA(hue, saturation, lightness, alpha)
|
||||
}
|
||||
|
||||
internal fun fromRGBANotation(): RGBA {
|
||||
val match = RGBA_REGEX.find(value)
|
||||
|
||||
fun getRGBParameter(index: Int): Int {
|
||||
val group = match?.groups?.get(index)?.value
|
||||
?: throw IllegalArgumentException("Expected rgb or rgba notation, got $value")
|
||||
|
||||
return when {
|
||||
(group.endsWith('%')) -> (normalizeFractionalPercent(group.substringBefore('%').toDouble() / 100.0) * 255.0).toInt()
|
||||
else -> normalizeRGB(group.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
val red = getRGBParameter(1)
|
||||
val green = getRGBParameter(2)
|
||||
val blue = getRGBParameter(3)
|
||||
val alpha = normalizeAlpha(match?.groups?.get(4)?.value?.toDouble() ?: 1.0)
|
||||
|
||||
return RGBA(red, green, blue, alpha)
|
||||
}
|
||||
|
||||
internal fun toRGBA(): RGBA {
|
||||
val v = rgb ?: value
|
||||
return when {
|
||||
v.startsWith("rgb") -> fromRGBANotation()
|
||||
|
||||
// Matches #rgb
|
||||
v.startsWith("#") && v.length == 4 -> RGBA(
|
||||
"${v[1]}${v[1]}".toInt(16),
|
||||
"${v[2]}${v[2]}".toInt(16),
|
||||
"${v[3]}${v[3]}".toInt(16)
|
||||
)
|
||||
|
||||
// Matches both #rrggbb and #rrggbbaa
|
||||
v.startsWith("#") && (v.length == 7 || v.length == 9) -> RGBA(
|
||||
(v.substring(1..2)).toInt(16),
|
||||
(v.substring(3..4)).toInt(16),
|
||||
(v.substring(5..6)).toInt(16)
|
||||
)
|
||||
else -> throw IllegalArgumentException("Only hexadecimal, rgb, and rgba notations are accepted, got $v")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.withZeros() = this + "0".repeat(max(0, 3 - this.length))
|
||||
@@ -491,6 +585,8 @@ fun rgb(red: Int, green: Int, blue: Int) = Color("rgb($red, $green, $blue)")
|
||||
fun rgba(red: Int, green: Int, blue: Int, alpha: Double) = Color("rgba($red, $green, $blue, ${formatAlpha(alpha)})")
|
||||
fun hsl(hue: Int, saturation: Int, lightness: Int) = Color("hsl($hue, $saturation%, $lightness%)")
|
||||
fun hsla(hue: Int, saturation: Int, lightness: Int, alpha: Double) = Color("hsla($hue, $saturation%, $lightness%, ${formatAlpha(alpha)})")
|
||||
fun oklch(lightness: Int, chroma: Double, hue: Double) = Color("oklch($lightness% $chroma $hue)")
|
||||
fun oklch(lightness: Int, chroma: Double, hue: Double, alpha: Double) = Color("oklch($lightness% $chroma $hue / ${formatAlpha(alpha)})")
|
||||
fun blackAlpha(alpha: Double) = Color.black.withAlpha(alpha)
|
||||
fun whiteAlpha(alpha: Double) = Color.white.withAlpha(alpha)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package nl.astraeus.css.properties
|
||||
|
||||
class Length(
|
||||
value: String
|
||||
): CssProperty(value) {
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
fun px(nr: Int) = Length("${nr}px")
|
||||
|
||||
@@ -6,14 +6,15 @@ interface CssValue {
|
||||
|
||||
open class CssProperty(
|
||||
var value: String
|
||||
): CssValue {
|
||||
) : CssValue {
|
||||
|
||||
override fun css(): String = value
|
||||
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
||||
fun text(value: String) = TextProperty(value)
|
||||
|
||||
class TextProperty(
|
||||
value: String
|
||||
): CssProperty(value)
|
||||
) : CssProperty(value)
|
||||
|
||||
@@ -58,14 +58,14 @@ class TemplateRowColumn(
|
||||
) : CssProperty(value) {
|
||||
|
||||
companion object {
|
||||
val none = GridValue("none")
|
||||
val auto = GridValue("auto")
|
||||
val maxContent = GridValue("max-content")
|
||||
val minContent = GridValue("min-content")
|
||||
val initial = GridValue("initial")
|
||||
val inherit = GridValue("inherit")
|
||||
val none = TemplateRowColumn("none")
|
||||
val auto = TemplateRowColumn("auto")
|
||||
val maxContent = TemplateRowColumn("max-content")
|
||||
val minContent = TemplateRowColumn("min-content")
|
||||
val initial = TemplateRowColumn("initial")
|
||||
val inherit = TemplateRowColumn("inherit")
|
||||
|
||||
fun length(length: Measurement) = GridValue(length.value)
|
||||
fun length(length: Measurement) = TemplateRowColumn(length.value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
package nl.astraeus.css.properties
|
||||
|
||||
enum class MeasurementUoM {
|
||||
NONE,
|
||||
PX,
|
||||
EM,
|
||||
REL,
|
||||
REM,
|
||||
PC,
|
||||
PRC,
|
||||
CM,
|
||||
FR,
|
||||
VH,
|
||||
VW
|
||||
}
|
||||
|
||||
open class Measurement(
|
||||
value: String
|
||||
) : CssProperty(value) {
|
||||
value: String,
|
||||
val uom: MeasurementUoM = MeasurementUoM.NONE
|
||||
) : CssProperty(value), CalcExpression {
|
||||
|
||||
override fun toString(): String = super.value
|
||||
|
||||
companion object {
|
||||
val auto = Measurement("auto")
|
||||
@@ -10,46 +27,81 @@ open class Measurement(
|
||||
val inherit = Measurement("inherit")
|
||||
val normal = Measurement("normal")
|
||||
|
||||
fun px(nr: Int) = if (nr == 0) { Measurement("0") } else { Measurement("${nr}px") }
|
||||
fun px(nr: Double) = nr.px
|
||||
fun em(nr: Int) = nr.em
|
||||
fun em(nr: Double) = nr.em
|
||||
fun prc(nr: Int) = nr.prc
|
||||
fun prc(nr: Double) = nr.prc
|
||||
fun pc(nr: Int) = nr.pc
|
||||
fun pc(nr: Double) = nr.pc
|
||||
fun cm(nr: Int) = nr.cm
|
||||
fun cm(nr: Double) = nr.cm
|
||||
fun fromString(value: String): Measurement = when {
|
||||
value == "0" -> Measurement("0", MeasurementUoM.PX)
|
||||
value.endsWith("px") -> Measurement(value.slice(0..(value.length - 2)), MeasurementUoM.PX)
|
||||
value.endsWith("rel") -> Measurement(value.slice(0..(value.length - 3)), MeasurementUoM.REL)
|
||||
else -> {
|
||||
TODO("Unable to parse $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Int.px: Measurement
|
||||
get() = Measurement("${this}${if (this == 0) { "" } else { "px"}}")
|
||||
get() = Measurement(
|
||||
"${this}${
|
||||
if (this == 0) {
|
||||
""
|
||||
} else {
|
||||
"px"
|
||||
}
|
||||
}",
|
||||
MeasurementUoM.PX
|
||||
)
|
||||
val Int.em: Measurement
|
||||
get() = Measurement("${this}${if (this == 0) { "" } else { "em"}}")
|
||||
get() = Measurement(
|
||||
"${this}${
|
||||
if (this == 0) {
|
||||
""
|
||||
} else {
|
||||
"em"
|
||||
}
|
||||
}",
|
||||
MeasurementUoM.EM
|
||||
)
|
||||
val Int.rem: Measurement
|
||||
get() = Measurement("${this}${if (this == 0) { "" } else { "rem"}}")
|
||||
get() = Measurement(
|
||||
"${this}${
|
||||
if (this == 0) {
|
||||
""
|
||||
} else {
|
||||
"rem"
|
||||
}
|
||||
}",
|
||||
MeasurementUoM.REM
|
||||
)
|
||||
val Int.prc: Measurement
|
||||
get() = Measurement("${this}%")
|
||||
get() = Measurement("${this}%", MeasurementUoM.PRC)
|
||||
val Int.pc: Measurement
|
||||
get() = Measurement("${this}pc")
|
||||
get() = Measurement("${this}pc", MeasurementUoM.PC)
|
||||
val Int.cm: Measurement
|
||||
get() = Measurement("${this}cm")
|
||||
fun Int.px(): Measurement = Measurement.px(this)
|
||||
get() = Measurement("${this}cm", MeasurementUoM.CM)
|
||||
val Int.fr: Measurement
|
||||
get() = Measurement("${this}fr", MeasurementUoM.FR)
|
||||
val Int.vw: Measurement
|
||||
get() = Measurement("${this}vw", MeasurementUoM.VW)
|
||||
val Int.vh: Measurement
|
||||
get() = Measurement("${this}vh", MeasurementUoM.VH)
|
||||
|
||||
val Double.px: Measurement
|
||||
get() = Measurement("${this}px")
|
||||
get() = Measurement("${this}px", MeasurementUoM.PX)
|
||||
val Double.em: Measurement
|
||||
get() = Measurement("${this}em")
|
||||
get() = Measurement("${this}em", MeasurementUoM.EM)
|
||||
val Double.rem: Measurement
|
||||
get() = Measurement("${this}rem")
|
||||
get() = Measurement("${this}rem", MeasurementUoM.REM)
|
||||
val Double.prc: Measurement
|
||||
get() = Measurement("${this}%")
|
||||
get() = Measurement("${this}%", MeasurementUoM.PRC)
|
||||
val Double.pc: Measurement
|
||||
get() = Measurement("${this}pc")
|
||||
get() = Measurement("${this}pc", MeasurementUoM.PC)
|
||||
val Double.cm: Measurement
|
||||
get() = Measurement("${this}cm")
|
||||
fun Double.px(): Measurement = Measurement.px(this)
|
||||
get() = Measurement("${this}cm", MeasurementUoM.CM)
|
||||
val Double.fr: Measurement
|
||||
get() = Measurement("${this}fr", MeasurementUoM.FR)
|
||||
val Double.vw: Measurement
|
||||
get() = Measurement("${this}vw", MeasurementUoM.VW)
|
||||
val Double.vh: Measurement
|
||||
get() = Measurement("${this}vh", MeasurementUoM.VH)
|
||||
|
||||
open class LineHeight(value: String) : CssProperty(value) {
|
||||
companion object {
|
||||
|
||||
@@ -15,7 +15,16 @@ class TimingFunction(
|
||||
val initial = TimingFunction("initial")
|
||||
val inherit = TimingFunction("inherit")
|
||||
|
||||
fun steps(steps: Int, start: Boolean) = TimingFunction("steps($steps, ${if (start) { "start" } else { "end" }}")
|
||||
fun steps(steps: Int, start: Boolean) = TimingFunction(
|
||||
"steps($steps, ${
|
||||
if (start) {
|
||||
"start"
|
||||
} else {
|
||||
"end"
|
||||
}
|
||||
}"
|
||||
)
|
||||
|
||||
fun cubicBezier(n1: Double, n2: Double, n3: Double, n4: Double) = TimingFunction("cubic-bezier($n1, $n2, $n3, $n4)")
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ class Transform(
|
||||
n5: Double,
|
||||
n6: Double
|
||||
) = Transform("matrix($n1, $n2, $n3, $n4, $n5, $n6)")
|
||||
|
||||
fun matrix3d(
|
||||
n01: Double, n02: Double, n03: Double, n04: Double,
|
||||
n05: Double, n06: Double, n07: Double, n08: Double,
|
||||
@@ -25,6 +26,7 @@ class Transform(
|
||||
) = Transform(
|
||||
"matrix3d($n01, $n02, $n03, $n04, $n05, $n06, $n07, $n08, $n09, $n10, $n11, $n12, $n13, $n14, $n15, $n16)"
|
||||
)
|
||||
|
||||
fun translate(x: Double, y: Double) = Transform("translate($x, $y)")
|
||||
fun translate3d(x: Double, y: Double, z: Double) = Transform("translate3d($x, $y, $z)")
|
||||
fun translateX(x: Double) = Transform("translateX($x)")
|
||||
|
||||
6
src/commonMain/kotlin/nl/astraeus/css/style/CssBlock.kt
Normal file
6
src/commonMain/kotlin/nl/astraeus/css/style/CssBlock.kt
Normal file
@@ -0,0 +1,6 @@
|
||||
package nl.astraeus.css.style
|
||||
|
||||
data class CssBlock(
|
||||
val selector: String,
|
||||
val content: String
|
||||
)
|
||||
@@ -1,11 +0,0 @@
|
||||
package nl.astraeus.css.style
|
||||
|
||||
import nl.astraeus.css.properties.Color
|
||||
|
||||
object CssFunctions {
|
||||
|
||||
fun darken(color: Color, percentage: Int): Color {
|
||||
return color
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package nl.astraeus.css.style
|
||||
|
||||
import nl.astraeus.css.properties.*
|
||||
import nl.astraeus.css.properties.CssProperty
|
||||
import nl.astraeus.css.properties.FontSize
|
||||
import nl.astraeus.css.properties.FontStretch
|
||||
import nl.astraeus.css.properties.FontStyle
|
||||
import nl.astraeus.css.properties.FontWeight
|
||||
|
||||
@CssTagMarker
|
||||
open class FontFace : CssGenerator() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ abstract class Validator {
|
||||
|
||||
class MaxCountValidator(
|
||||
val number: Int
|
||||
): Validator() {
|
||||
) : Validator() {
|
||||
|
||||
override fun validate(property: List<CssProperty>): Boolean = property.size <= number
|
||||
|
||||
@@ -20,7 +20,7 @@ class MaxCountValidator(
|
||||
|
||||
}
|
||||
|
||||
class InitialInheritSingleValue: Validator() {
|
||||
class InitialInheritSingleValue : Validator() {
|
||||
|
||||
override fun validate(properties: List<CssProperty>): Boolean {
|
||||
for (prop in properties) {
|
||||
|
||||
20
src/commonTest/kotlin/nl/astraeus/css/ColorTest.kt
Normal file
20
src/commonTest/kotlin/nl/astraeus/css/ColorTest.kt
Normal file
@@ -0,0 +1,20 @@
|
||||
package nl.astraeus.css
|
||||
|
||||
import nl.astraeus.css.properties.oklch
|
||||
import kotlin.test.Test
|
||||
|
||||
class ColorTest {
|
||||
|
||||
@Test
|
||||
fun testColor() {
|
||||
val css = style {
|
||||
select("body") {
|
||||
color(oklch(100, 0.5, 0.5))
|
||||
backgroundColor(oklch(100, 0.5, 0.5, 0.25))
|
||||
}
|
||||
}
|
||||
|
||||
println(css.generateCss())
|
||||
}
|
||||
|
||||
}
|
||||
43
src/commonTest/kotlin/nl/astraeus/css/CssNameExample.kt
Normal file
43
src/commonTest/kotlin/nl/astraeus/css/CssNameExample.kt
Normal file
@@ -0,0 +1,43 @@
|
||||
package nl.astraeus.css
|
||||
|
||||
import nl.astraeus.css.properties.Color
|
||||
import nl.astraeus.css.style.DescriptionProvider
|
||||
import kotlin.test.Test
|
||||
|
||||
private val CAPITAL_LETTER by lazy { Regex("[A-Z]") }
|
||||
|
||||
fun String.hyphenize(): String =
|
||||
replace(CAPITAL_LETTER) {
|
||||
"-${it.value.lowercase()}"
|
||||
}
|
||||
|
||||
open class CssName(name: String? = null) : DescriptionProvider {
|
||||
val name: String = if (name != null) {
|
||||
"css-$name"
|
||||
} else{
|
||||
"css${this::class.simpleName?.hyphenize() ?: this::class}"
|
||||
}
|
||||
|
||||
override fun description() = name
|
||||
}
|
||||
|
||||
object MainTitle : CssName()
|
||||
object SectionTitle : CssName("sct-title")
|
||||
|
||||
class CssNameExample {
|
||||
|
||||
@Test
|
||||
fun testCssName() {
|
||||
val css = style {
|
||||
select(MainTitle) {
|
||||
color(Color.white)
|
||||
}
|
||||
|
||||
select(SectionTitle) {
|
||||
color(Color.red)
|
||||
}
|
||||
}
|
||||
|
||||
println(css.generateCss())
|
||||
}
|
||||
}
|
||||
157
src/commonTest/kotlin/nl/astraeus/css/Examples.kt
Normal file
157
src/commonTest/kotlin/nl/astraeus/css/Examples.kt
Normal file
@@ -0,0 +1,157 @@
|
||||
package nl.astraeus.css
|
||||
|
||||
import nl.astraeus.css.properties.BorderStyle
|
||||
import nl.astraeus.css.properties.Color
|
||||
import nl.astraeus.css.properties.FontStyle
|
||||
import nl.astraeus.css.properties.FontWeight
|
||||
import nl.astraeus.css.properties.Measurement
|
||||
import nl.astraeus.css.properties.em
|
||||
import nl.astraeus.css.properties.hsla
|
||||
import nl.astraeus.css.properties.prc
|
||||
import nl.astraeus.css.properties.px
|
||||
import nl.astraeus.css.style.Style
|
||||
import nl.astraeus.css.style.cls
|
||||
import nl.astraeus.css.style.txt
|
||||
import kotlin.test.Test
|
||||
|
||||
class Examples {
|
||||
|
||||
@Test
|
||||
fun testColor() {
|
||||
val color = hsla(0, 50, 50, 1.0)
|
||||
val backgroundColor = Color.white
|
||||
|
||||
val css = style {
|
||||
select(cls("button")) {
|
||||
padding(5.px)
|
||||
|
||||
select("a") {
|
||||
color(color)
|
||||
backgroundColor(backgroundColor)
|
||||
|
||||
hover {
|
||||
color(color.lighten(10))
|
||||
backgroundColor(backgroundColor.darken(10))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println(css.generateCss(minified = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMixins() {
|
||||
fun Style.borderStyles(borderWidth: Measurement = 2.px) {
|
||||
borderWidth(borderWidth)
|
||||
borderColor(Color.aquamarine)
|
||||
borderStyle(BorderStyle.solid)
|
||||
}
|
||||
|
||||
val css = style {
|
||||
select(txt("a"), cls("button")) {
|
||||
borderStyles()
|
||||
|
||||
color(Color.white)
|
||||
}
|
||||
|
||||
select(cls("btn-primary")) {
|
||||
borderStyles(3.px)
|
||||
color(Color.blue)
|
||||
}
|
||||
}
|
||||
|
||||
println(css.generateCss())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMeasurements() {
|
||||
val css = style {
|
||||
select("body") {
|
||||
fontSize(1.2.em)
|
||||
borderWidth(3.px)
|
||||
width(75.prc)
|
||||
}
|
||||
}
|
||||
|
||||
println(css.generateCss())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGeneration() {
|
||||
val color = hsla(0, 50, 50, 1.0)
|
||||
val backgroundColor = Color.white
|
||||
|
||||
val css = style {
|
||||
select(cls("button")) {
|
||||
padding(5.px)
|
||||
|
||||
select("a", "span") {
|
||||
color(color)
|
||||
backgroundColor(backgroundColor)
|
||||
|
||||
hover {
|
||||
color(color.lighten(10))
|
||||
backgroundColor(backgroundColor.darken(10))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println(css.generateCss(
|
||||
minified = false,
|
||||
sortProperties = true,
|
||||
combineEqualBlocks = false
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMediaQueries() {
|
||||
val css = style {
|
||||
media("screen and (min-width: 30em)") {
|
||||
select("html", "body") {
|
||||
backgroundColor(Color.purple)
|
||||
color(Color.blue)
|
||||
}
|
||||
}
|
||||
|
||||
media("print") {
|
||||
select("html", "body") {
|
||||
backgroundColor(Color.white)
|
||||
color(Color.darkGrey)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
println(css.generateCss(
|
||||
minified = false,
|
||||
sortProperties = true,
|
||||
combineEqualBlocks = true
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFontFace() {
|
||||
val css = style {
|
||||
|
||||
select("*") {
|
||||
fontFace {
|
||||
fontFamily("UbuntuCondensed")
|
||||
fontStyle(FontStyle.normal)
|
||||
fontWeight(FontWeight.normal)
|
||||
//fontDisplay("auto")
|
||||
src("fonts/ubuntu.condensed.ttf")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
val cssTxt = css.generateCss(
|
||||
minified = false,
|
||||
sortProperties = true,
|
||||
combineEqualBlocks = true
|
||||
)
|
||||
println(cssTxt)
|
||||
}
|
||||
}
|
||||
18
src/commonTest/kotlin/nl/astraeus/css/TestCalcExpression.kt
Normal file
18
src/commonTest/kotlin/nl/astraeus/css/TestCalcExpression.kt
Normal file
@@ -0,0 +1,18 @@
|
||||
package nl.astraeus.css
|
||||
|
||||
import nl.astraeus.css.properties.calc
|
||||
import nl.astraeus.css.properties.em
|
||||
import nl.astraeus.css.properties.minus
|
||||
import nl.astraeus.css.properties.plus
|
||||
import nl.astraeus.css.properties.px
|
||||
import kotlin.test.Test
|
||||
|
||||
class TestCalcExpression {
|
||||
|
||||
@Test
|
||||
fun testCalcExpression() {
|
||||
val a = calc(10.px + 20.px - 5.em)
|
||||
|
||||
println(a)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,23 @@
|
||||
package nl.astraeus.css
|
||||
|
||||
import nl.astraeus.css.properties.*
|
||||
import nl.astraeus.css.properties.BoxSizing
|
||||
import nl.astraeus.css.properties.Color
|
||||
import nl.astraeus.css.properties.Count
|
||||
import nl.astraeus.css.properties.Display
|
||||
import nl.astraeus.css.properties.em
|
||||
import nl.astraeus.css.properties.hsl
|
||||
import nl.astraeus.css.properties.hsla
|
||||
import nl.astraeus.css.properties.px
|
||||
import nl.astraeus.css.properties.rgb
|
||||
import nl.astraeus.css.properties.rgba
|
||||
import nl.astraeus.css.style.attr
|
||||
import nl.astraeus.css.style.attrEquals
|
||||
import nl.astraeus.css.style.cls
|
||||
import nl.astraeus.css.style.id
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class TestCssBuilder {
|
||||
|
||||
@@ -13,6 +25,32 @@ class TestCssBuilder {
|
||||
fun testBuilder() {
|
||||
val css = style {
|
||||
|
||||
select("*", "*::before", "*::after") {
|
||||
boxSizing(BoxSizing.borderBox)
|
||||
}
|
||||
|
||||
select("html") {
|
||||
transition("background-color 1s ease")
|
||||
|
||||
margin(0.px)
|
||||
padding(0.px)
|
||||
|
||||
focus {
|
||||
backgroundColor(Color.blue)
|
||||
}
|
||||
}
|
||||
|
||||
select("body") {
|
||||
margin(0.px)
|
||||
|
||||
padding(0.px)
|
||||
|
||||
focus {
|
||||
backgroundColor(Color.blue)
|
||||
}
|
||||
transition("background-color 1s ease")
|
||||
}
|
||||
|
||||
select(".test") {
|
||||
top(10.px)
|
||||
left(4.em)
|
||||
@@ -66,7 +104,7 @@ class TestCssBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
println(css.generateCss())
|
||||
println(css.generateCss(combineEqualBlocks = true, sortProperties = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -148,4 +186,82 @@ class TestCssBuilder {
|
||||
|
||||
println(css2.generateCss())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOr() {
|
||||
val css = style {
|
||||
select("h1") {
|
||||
color(Color.blue)
|
||||
|
||||
select("table") {
|
||||
color(Color.red)
|
||||
|
||||
select("th", "td") {
|
||||
color(Color.green)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println(css.generateCss())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOrWithComma() {
|
||||
var excepted = false
|
||||
try {
|
||||
val css = style {
|
||||
select("h1") {
|
||||
color(Color.blue)
|
||||
|
||||
select("table") {
|
||||
color(Color.red)
|
||||
|
||||
select("th, td") {
|
||||
color(Color.green)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println(css.generateCss())
|
||||
} catch (e: Exception) {
|
||||
excepted = true
|
||||
assertTrue {
|
||||
e is IllegalStateException
|
||||
}
|
||||
assertTrue {
|
||||
e.message?.contains("Comma is not allowed in selector") ?: false
|
||||
}
|
||||
}
|
||||
assertTrue {
|
||||
excepted
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAlphaFunctions() {
|
||||
val hsl = hsl(1, 50, 50)
|
||||
val hsla = hsla(1, 50, 50, 0.5)
|
||||
val rgb = rgb(101, 111, 121)
|
||||
val rgba = rgba(100, 110, 120, 0.4)
|
||||
val hex = Color("#88ff44")
|
||||
val hexa = Color("#88ff4466")
|
||||
|
||||
assertFalse { hsl.hasAlpha() }
|
||||
assertFalse { rgb.hasAlpha() }
|
||||
assertFalse { hex.hasAlpha() }
|
||||
|
||||
assertTrue { hsla.hasAlpha() }
|
||||
assertTrue { rgba.hasAlpha() }
|
||||
assertTrue { hexa.hasAlpha() }
|
||||
|
||||
assertEquals(0.5, hsla.getAlpha())
|
||||
assertEquals(0.4, rgba.getAlpha())
|
||||
assertEquals(0.5, hsla.getAlpha())
|
||||
assertEquals(0.4, hexa.getAlpha())
|
||||
assertEquals("646e78", rgba.toHex())
|
||||
assertEquals("bf4240", hsla.toHex())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user