From bd2804d772b7cb7a8bc0344e88327bccfd05f4a0 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 1 Oct 2024 20:26:00 +0300 Subject: [PATCH] Move svg to features --- CHANGELOG.md | 1 + build.gradle.kts | 2 +- demo/scheme/src/jvmMain/kotlin/Main.kt | 17 +- .../kscience/maps/compose/MapCanvasState.kt | 2 +- .../kscience/maps/compose/mapExportToSvg.kt | 27 +++ maps-kt-features/build.gradle.kts | 4 + .../space/kscience/maps/svg/SvgCanvas.kt | 0 .../space/kscience/maps/svg/SvgDrawScope.kt | 28 +-- .../space/kscience/maps/svg/exportToSvg.kt | 54 +++++ maps-kt-scheme/build.gradle.kts | 7 +- .../kscience/maps/scheme/XYCanvasState.kt | 2 +- .../space/kscience/maps/svg/exportToSvg.kt | 194 ------------------ .../kscience/maps/svg/schemeExportToSvg.kt | 25 +++ 13 files changed, 138 insertions(+), 225 deletions(-) create mode 100644 maps-kt-compose/src/jvmMain/kotlin/space/kscience/maps/compose/mapExportToSvg.kt rename {maps-kt-scheme => maps-kt-features}/src/jvmMain/kotlin/space/kscience/maps/svg/SvgCanvas.kt (100%) rename {maps-kt-scheme => maps-kt-features}/src/jvmMain/kotlin/space/kscience/maps/svg/SvgDrawScope.kt (95%) create mode 100644 maps-kt-features/src/jvmMain/kotlin/space/kscience/maps/svg/exportToSvg.kt delete mode 100644 maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/exportToSvg.kt create mode 100644 maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/schemeExportToSvg.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 87da9c0..7cfd7af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Changed - avoid drawing features with VisibleAttribute false +- Move SVG export to `features` and make it usable for maps as well ### Deprecated diff --git a/build.gradle.kts b/build.gradle.kts index 6a759b9..7f76889 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ val kmathVersion: String by extra("0.4.0") allprojects { group = "space.kscience" - version = "0.4.0-dev-2" + version = "0.4.0-dev-3" repositories { mavenLocal() diff --git a/demo/scheme/src/jvmMain/kotlin/Main.kt b/demo/scheme/src/jvmMain/kotlin/Main.kt index efb18d8..297bd98 100644 --- a/demo/scheme/src/jvmMain/kotlin/Main.kt +++ b/demo/scheme/src/jvmMain/kotlin/Main.kt @@ -17,7 +17,6 @@ import space.kscience.maps.features.ViewConfig import space.kscience.maps.features.ViewPoint import space.kscience.maps.features.color import space.kscience.maps.scheme.* -import space.kscience.maps.svg.FeatureStateSnapshot import space.kscience.maps.svg.exportToSvg import space.kscience.maps.svg.snapshot import java.awt.Desktop @@ -58,22 +57,18 @@ fun App() { var viewPoint: ViewPoint by remember { mutableStateOf(initialViewPoint) } - var snapshot: FeatureStateSnapshot? by remember { mutableStateOf(null) } - - if (snapshot == null) { - snapshot = features.snapshot() + val snapshot = key(features) { + features.snapshot() } ContextMenuArea( items = { listOf( ContextMenuItem("Export to SVG") { - snapshot?.let { - val path = Files.createTempFile("scheme-kt-", ".svg") - it.exportToSvg(viewPoint, 800.0, 800.0, path) - println(path.toFile()) - Desktop.getDesktop().browse(path.toFile().toURI()) - } + val path = Files.createTempFile("scheme-kt-", ".svg") + snapshot.exportToSvg(viewPoint, 800.0, 800.0, path) + println(path.toFile()) + Desktop.getDesktop().browse(path.toFile().toURI()) }, ) } diff --git a/maps-kt-compose/src/commonMain/kotlin/space/kscience/maps/compose/MapCanvasState.kt b/maps-kt-compose/src/commonMain/kotlin/space/kscience/maps/compose/MapCanvasState.kt index 0a5cb20..8f21602 100644 --- a/maps-kt-compose/src/commonMain/kotlin/space/kscience/maps/compose/MapCanvasState.kt +++ b/maps-kt-compose/src/commonMain/kotlin/space/kscience/maps/compose/MapCanvasState.kt @@ -15,7 +15,7 @@ import space.kscience.maps.features.* import kotlin.math.* -public class MapCanvasState private constructor( +public class MapCanvasState internal constructor( public val mapTileProvider: MapTileProvider, config: ViewConfig, ) : CanvasState(config) { diff --git a/maps-kt-compose/src/jvmMain/kotlin/space/kscience/maps/compose/mapExportToSvg.kt b/maps-kt-compose/src/jvmMain/kotlin/space/kscience/maps/compose/mapExportToSvg.kt new file mode 100644 index 0000000..8b15723 --- /dev/null +++ b/maps-kt-compose/src/jvmMain/kotlin/space/kscience/maps/compose/mapExportToSvg.kt @@ -0,0 +1,27 @@ +package space.kscience.maps.compose + +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import org.jfree.svg.SVGUtils +import space.kscience.maps.coordinates.Gmc +import space.kscience.maps.features.ViewConfig +import space.kscience.maps.features.ViewPoint +import space.kscience.maps.svg.FeatureSetSnapshot +import space.kscience.maps.svg.generateSvg +import java.nio.file.Path + +public fun FeatureSetSnapshot.exportToSvg( + mapTileProvider: MapTileProvider, + viewPoint: ViewPoint, + width: Double, + height: Double, + path: Path, +) { + val mapCanvasState: MapCanvasState = MapCanvasState(mapTileProvider, ViewConfig()).apply { + this.viewPoint = viewPoint + this.canvasSize = DpSize(width.dp, height.dp) + } + + val svgString: String = generateSvg(mapCanvasState) + SVGUtils.writeToSVG(path.toFile(), svgString) +} \ No newline at end of file diff --git a/maps-kt-features/build.gradle.kts b/maps-kt-features/build.gradle.kts index bd3b35c..9b41e1d 100644 --- a/maps-kt-features/build.gradle.kts +++ b/maps-kt-features/build.gradle.kts @@ -37,4 +37,8 @@ kscience { api("io.github.oshai:kotlin-logging:6.0.3") api("com.benasher44:uuid:0.8.4") } + + jvmMain{ + api("org.jfree:org.jfree.svg:5.0.4") + } } \ No newline at end of file diff --git a/maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/SvgCanvas.kt b/maps-kt-features/src/jvmMain/kotlin/space/kscience/maps/svg/SvgCanvas.kt similarity index 100% rename from maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/SvgCanvas.kt rename to maps-kt-features/src/jvmMain/kotlin/space/kscience/maps/svg/SvgCanvas.kt diff --git a/maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/SvgDrawScope.kt b/maps-kt-features/src/jvmMain/kotlin/space/kscience/maps/svg/SvgDrawScope.kt similarity index 95% rename from maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/SvgDrawScope.kt rename to maps-kt-features/src/jvmMain/kotlin/space/kscience/maps/svg/SvgDrawScope.kt index ac67577..940e7ad 100644 --- a/maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/SvgDrawScope.kt +++ b/maps-kt-features/src/jvmMain/kotlin/space/kscience/maps/svg/SvgDrawScope.kt @@ -14,19 +14,21 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import org.jfree.svg.SVGGraphics2D import space.kscience.attributes.Attributes -import space.kscience.maps.features.* -import space.kscience.maps.scheme.XY +import space.kscience.maps.features.CanvasState +import space.kscience.maps.features.ColorAttribute +import space.kscience.maps.features.FeatureDrawScope +import space.kscience.maps.features.PainterFeature import java.awt.BasicStroke import java.awt.geom.* import java.awt.image.AffineTransformOp import java.awt.Color as AWTColor -public class SvgDrawScope( - state: CanvasState, +public class SvgDrawScope( + state: CanvasState, private val graphics: SVGGraphics2D, - private val painterCache: Map, Painter>, + private val painterCache: Map, Painter>, private val defaultStrokeWidth: Float = 1f, -) : FeatureDrawScope(state) { +) : FeatureDrawScope(state) { override val layoutDirection: LayoutDirection get() = LayoutDirection.Ltr @@ -466,14 +468,14 @@ public class SvgDrawScope( } } - public fun renderText( - textFeature: TextFeature, - ) { - textFeature.color?.let { setupColor(it) } - graphics.drawString(textFeature.text, textFeature.position.x, textFeature.position.y) - } +// public fun renderText( +// textFeature: TextFeature, +// ) { +// textFeature.color?.let { setupColor(it) } +// graphics.drawString(textFeature.text, textFeature.position.x, textFeature.position.y) +// } - override fun painterFor(feature: PainterFeature): Painter { + override fun painterFor(feature: PainterFeature): Painter { return painterCache[feature]!! } diff --git a/maps-kt-features/src/jvmMain/kotlin/space/kscience/maps/svg/exportToSvg.kt b/maps-kt-features/src/jvmMain/kotlin/space/kscience/maps/svg/exportToSvg.kt new file mode 100644 index 0000000..2cf2cf3 --- /dev/null +++ b/maps-kt-features/src/jvmMain/kotlin/space/kscience/maps/svg/exportToSvg.kt @@ -0,0 +1,54 @@ +package space.kscience.maps.svg + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import org.jfree.svg.SVGGraphics2D +import space.kscience.attributes.Attributes +import space.kscience.attributes.plus +import space.kscience.maps.features.* + + +public class FeatureSetSnapshot( + public val features: Map>, + internal val painterCache: Map, Painter>, +) + +@Composable +public fun FeatureSet.snapshot(): FeatureSetSnapshot = FeatureSetSnapshot( + features, + features.values.filterIsInstance>().associateWith { it.getPainter() } +) + + +public fun FeatureSetSnapshot.generateSvg( + canvasState: CanvasState, + id: String? = null, +): String { + val svgGraphics2D: SVGGraphics2D = SVGGraphics2D( + canvasState.canvasSize.width.value.toDouble(), + canvasState.canvasSize.height.value.toDouble() + ) + val svgScope = SvgDrawScope(canvasState, svgGraphics2D, painterCache) + + svgScope.apply { + features.entries.sortedBy { it.value.z } + .filter { state.viewPoint.zoom in it.value.zoomRange } + .forEach { (id, feature) -> + val attributesCache = mutableMapOf, Attributes>() + + fun computeGroupAttributes(path: List): Attributes = attributesCache.getOrPut(path) { + if (path.isEmpty()) return Attributes.EMPTY + else if (path.size == 1) { + features[path.first()]?.attributes ?: Attributes.EMPTY + } else { + computeGroupAttributes(path.dropLast(1)) + (features[path.first()]?.attributes + ?: Attributes.EMPTY) + } + } + + val path = id.split("/") + drawFeature(feature, computeGroupAttributes(path.dropLast(1))) + } + } + return svgGraphics2D.getSVGElement(id) +} \ No newline at end of file diff --git a/maps-kt-scheme/build.gradle.kts b/maps-kt-scheme/build.gradle.kts index e9adb9d..4b128c0 100644 --- a/maps-kt-scheme/build.gradle.kts +++ b/maps-kt-scheme/build.gradle.kts @@ -5,16 +5,15 @@ plugins { `maven-publish` } -kscience{ +kscience { jvm() // js() wasm() - commonMain{ + commonMain { api(projects.mapsKtFeatures) } - jvmMain{ - implementation("org.jfree:org.jfree.svg:5.0.4") + jvmMain { api(compose.desktop.currentOs) } } diff --git a/maps-kt-scheme/src/commonMain/kotlin/space/kscience/maps/scheme/XYCanvasState.kt b/maps-kt-scheme/src/commonMain/kotlin/space/kscience/maps/scheme/XYCanvasState.kt index efc7879..54267b5 100644 --- a/maps-kt-scheme/src/commonMain/kotlin/space/kscience/maps/scheme/XYCanvasState.kt +++ b/maps-kt-scheme/src/commonMain/kotlin/space/kscience/maps/scheme/XYCanvasState.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.unit.dp import space.kscience.maps.features.* import kotlin.math.min -public class XYCanvasState( +public class XYCanvasState internal constructor( config: ViewConfig, ) : CanvasState(config) { override val space: CoordinateSpace diff --git a/maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/exportToSvg.kt b/maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/exportToSvg.kt deleted file mode 100644 index 3f6993f..0000000 --- a/maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/exportToSvg.kt +++ /dev/null @@ -1,194 +0,0 @@ -package space.kscience.maps.svg - -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.dp -import org.jfree.svg.SVGGraphics2D -import org.jfree.svg.SVGUtils -import space.kscience.attributes.Attributes -import space.kscience.attributes.plus -import space.kscience.maps.features.* -import space.kscience.maps.scheme.XY -import space.kscience.maps.scheme.XYCanvasState - - -public class FeatureStateSnapshot( - public val features: Map>, - internal val painterCache: Map, Painter>, -) - -@Composable -public fun FeatureSet.snapshot(): FeatureStateSnapshot = FeatureStateSnapshot( - features, - features.values.filterIsInstance>().associateWith { it.getPainter() } -) - - -public fun FeatureStateSnapshot.generateSvg( - viewPoint: ViewPoint, - width: Double, - height: Double, - id: String? = null, -): String { - -// fun XY.toOffset(): Offset = Offset( -// (width / 2 + (x - viewPoint.focus.x) * viewPoint.zoom).toFloat(), -// (height / 2 + (viewPoint.focus.y - y) * viewPoint.zoom).toFloat() -// ) -// -// -// fun SvgDrawScope.drawFeature(scale: Float, feature: Feature) { -// -// val color = feature.color ?: Color.Red -// val alpha = feature.attributes[AlphaAttribute] ?: 1f -// -// when (feature) { -// is ScalableImageFeature -> { -// val offset = XY(feature.rectangle.left, feature.rectangle.top).toOffset() -// val backgroundSize = Size( -// (feature.rectangle.width * scale), -// (feature.rectangle.height * scale) -// ) -// -// translate(offset.x, offset.y) { -// with(painterCache[feature]!!) { -// draw(backgroundSize) -// } -// } -// } -// -// is FeatureSelector -> drawFeature(scale, feature.selector(scale)) -// -// is CircleFeature -> drawCircle( -// color, -// feature.radius.toPx(), -// center = feature.center.toOffset(), -// alpha = alpha -// ) -// -// is LineFeature -> drawLine( -// color, -// feature.a.toOffset(), -// feature.b.toOffset(), -// alpha = alpha -// ) -// -// is PointsFeature -> { -// val points = feature.points.map { it.toOffset() } -// drawPoints( -// points = points, -// color = color, -// strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth, -// pointMode = PointMode.Points, -// pathEffect = feature.attributes[PathEffectAttribute], -// alpha = alpha -// ) -// } -// -// is MultiLineFeature -> { -// val points = feature.points.map { it.toOffset() } -// drawPoints( -// points = points, -// color = color, -// strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth, -// pointMode = PointMode.Polygon, -// pathEffect = feature.attributes[PathEffectAttribute], -// alpha = alpha -// ) -// } -// -// is ArcFeature -> { -// val topLeft = feature.oval.leftTop.toOffset() -// val bottomRight = feature.oval.rightBottom.toOffset() -// -// val size = Size(abs(topLeft.x - bottomRight.x), abs(topLeft.y - bottomRight.y)) -// -// drawArc( -// color = color, -// startAngle = feature.startAngle.degrees.toFloat(), -// sweepAngle = feature.arcLength.degrees.toFloat(), -// useCenter = false, -// topLeft = topLeft, -// size = size, -// style = Stroke(), -// alpha = alpha -// ) -// } -// -// is BitmapIconFeature -> drawImage(feature.image, feature.center.toOffset()) -// -// is VectorIconFeature -> { -// val offset = feature.center.toOffset() -// val imageSize = feature.size.toSize() -// translate(offset.x - imageSize.width / 2, offset.y - imageSize.height / 2) { -// with(painterCache[feature]!!) { -// draw(imageSize, alpha = alpha) -// } -// } -// } -// -// is TextFeature -> drawIntoCanvas { _ -> -// val offset = feature.position.toOffset() -// drawText( -// feature.text, -// offset.x + 5, -// offset.y - 5, -// java.awt.Font(null, PLAIN, 16), -// color -// ) -// } -// -// is DrawFeature -> { -// val offset = feature.position.toOffset() -// translate(offset.x, offset.y) { -// feature.drawFeature(this) -// } -// } -// -// is FeatureGroup -> { -// feature.featureMap.values.forEach { -// drawFeature(scale, it) -// } -// } -// } -// } - - val svgGraphics2D: SVGGraphics2D = SVGGraphics2D(width, height) - val svgCanvasState: XYCanvasState = XYCanvasState(ViewConfig()).apply { - this.viewPoint = viewPoint - this.canvasSize = DpSize(width.dp, height.dp) - } - val svgScope = SvgDrawScope(svgCanvasState, svgGraphics2D, painterCache) - - svgScope.apply { - features.entries.sortedBy { it.value.z } - .filter { state.viewPoint.zoom in it.value.zoomRange } - .forEach { (id, feature) -> - val attributesCache = mutableMapOf, Attributes>() - - fun computeGroupAttributes(path: List): Attributes = attributesCache.getOrPut(path){ - if (path.isEmpty()) return Attributes.EMPTY - else if (path.size == 1) { - features[path.first()]?.attributes ?: Attributes.EMPTY - } else { - computeGroupAttributes(path.dropLast(1)) + (features[path.first()]?.attributes ?: Attributes.EMPTY) - } - } - - val path = id.split("/") - drawFeature(feature, computeGroupAttributes(path.dropLast(1))) - } - } - return svgGraphics2D.getSVGElement(id) -} - -public fun FeatureStateSnapshot.exportToSvg( - viewPoint: ViewPoint, - width: Double, - height: Double, - path: java.nio.file.Path, -) { - val svgString: String = generateSvg(viewPoint, width, height) - SVGUtils.writeToSVG(path.toFile(), svgString) -} \ No newline at end of file diff --git a/maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/schemeExportToSvg.kt b/maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/schemeExportToSvg.kt new file mode 100644 index 0000000..0383659 --- /dev/null +++ b/maps-kt-scheme/src/jvmMain/kotlin/space/kscience/maps/svg/schemeExportToSvg.kt @@ -0,0 +1,25 @@ +package space.kscience.maps.svg + +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import org.jfree.svg.SVGUtils +import space.kscience.maps.features.ViewConfig +import space.kscience.maps.features.ViewPoint +import space.kscience.maps.scheme.XY +import space.kscience.maps.scheme.XYCanvasState +import java.nio.file.Path + +public fun FeatureSetSnapshot.exportToSvg( + viewPoint: ViewPoint, + width: Double, + height: Double, + path: Path, +) { + val svgCanvasState: XYCanvasState = XYCanvasState(ViewConfig()).apply { + this.viewPoint = viewPoint + this.canvasSize = DpSize(width.dp, height.dp) + } + + val svgString: String = generateSvg(svgCanvasState) + SVGUtils.writeToSVG(path.toFile(), svgString) +} \ No newline at end of file