From 737bbbde6a62290c2e290ab2f658bc0a40d9236e Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 9 Oct 2022 17:11:03 +0300 Subject: [PATCH] Fix SVG rendering for scheme --- build.gradle.kts | 2 +- .../center/sciprog/maps/scheme/SchemeView.kt | 7 +- .../center/sciprog/maps/svg/SvgCanvas.kt | 39 ++-- .../center/sciprog/maps/svg/SvgDrawScope.kt | 218 +++++++++++------- 4 files changed, 165 insertions(+), 101 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 87175b9..e29e6ed 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ val ktorVersion by extra("2.0.3") allprojects { group = "center.sciprog" - version = "0.1.0-dev-11" + version = "0.1.0-dev-12" } ksciencePublish{ diff --git a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt index f161dd4..b7b499a 100644 --- a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt +++ b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt @@ -172,7 +172,12 @@ public fun SchemeView( center = feature.center.toOffset() ) - is SchemeLineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset()) + is SchemeLineFeature -> drawLine( + feature.color, + feature.a.toOffset(), + feature.b.toOffset(), + ) + is SchemeArcFeature -> { val topLeft = feature.oval.leftTop.toOffset() val bottomRight = feature.oval.rightBottom.toOffset() diff --git a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgCanvas.kt b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgCanvas.kt index 9c62028..c6ef8d6 100644 --- a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgCanvas.kt +++ b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgCanvas.kt @@ -9,10 +9,14 @@ import androidx.compose.ui.graphics.drawscope.DrawContext import androidx.compose.ui.graphics.drawscope.DrawTransform import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize +import org.jfree.svg.SVGGraphics2D +import java.awt.BasicStroke import java.awt.Graphics2D +import java.awt.geom.Arc2D -internal fun Paint.toAwt(): java.awt.Paint { - return java.awt.Color(color.toArgb()) +internal fun Graphics2D.setupPaint(p: Paint){ + paint = java.awt.Color(p.color.toArgb()) + stroke = BasicStroke(p.strokeWidth) } @@ -73,7 +77,7 @@ internal fun DrawContext.asDrawTransform(): DrawTransform = object : DrawTransfo } } -internal class SvgCanvas(val graphics: Graphics2D) : Canvas { +internal class SvgCanvas(val graphics: SVGGraphics2D) : Canvas { override fun clipPath(path: Path, clipOp: ClipOp) { TODO("Not yet implemented") } @@ -112,19 +116,15 @@ internal class SvgCanvas(val graphics: Graphics2D) : Canvas { useCenter: Boolean, paint: Paint, ) { - graphics.paint = paint.toAwt() - graphics.drawArc( - top.toInt(), - left.toInt(), - (right - left).toInt(), - (top - bottom).toInt(), - startAngle.toInt(), - sweepAngle.toInt() + graphics.setupPaint(paint) + val arc = Arc2D.Float( + left, top, (right - left), (top - bottom), -startAngle, -sweepAngle, Arc2D.OPEN ) + graphics.draw(arc) } override fun drawCircle(center: Offset, radius: Float, paint: Paint) { - graphics.paint = paint.toAwt() + graphics.setupPaint(paint) graphics.drawOval( (center.x - radius).toInt(), (center.y - radius).toInt(), @@ -134,7 +134,7 @@ internal class SvgCanvas(val graphics: Graphics2D) : Canvas { } override fun drawImage(image: ImageBitmap, topLeftOffset: Offset, paint: Paint) { - graphics.paint = paint.toAwt() + graphics.setupPaint(paint) graphics.drawImage(image.toAwtImage(), null, topLeftOffset.x.toInt(), topLeftOffset.y.toInt()) } @@ -150,12 +150,12 @@ internal class SvgCanvas(val graphics: Graphics2D) : Canvas { } override fun drawLine(p1: Offset, p2: Offset, paint: Paint) { - graphics.paint = paint.toAwt() + graphics.setupPaint(paint) graphics.drawLine(p1.x.toInt(), p1.y.toInt(), p2.x.toInt(), p2.y.toInt()) } override fun drawOval(left: Float, top: Float, right: Float, bottom: Float, paint: Paint) { - graphics.paint = paint.toAwt() + graphics.setupPaint(paint) graphics.drawOval( left.toInt(), top.toInt(), @@ -165,13 +165,14 @@ internal class SvgCanvas(val graphics: Graphics2D) : Canvas { } override fun drawPath(path: Path, paint: Paint) { + graphics.setupPaint(paint) val skiaPath = path.asSkiaPath() val points: List = skiaPath.points.mapNotNull { it?.let { Offset(it.x, it.y) } } drawPoints(PointMode.Lines, points, paint) } override fun drawPoints(pointMode: PointMode, points: List, paint: Paint) { - graphics.paint = paint.toAwt() + graphics.setupPaint(paint) val xs = IntArray(points.size) { points[it].x.toInt() } val ys = IntArray(points.size) { points[it].y.toInt() } when (pointMode) { @@ -218,7 +219,7 @@ internal class SvgCanvas(val graphics: Graphics2D) : Canvas { } override fun drawRect(left: Float, top: Float, right: Float, bottom: Float, paint: Paint) { - graphics.paint = paint.toAwt() + graphics.setupPaint(paint) graphics.drawRect( left.toInt(), top.toInt(), @@ -236,7 +237,7 @@ internal class SvgCanvas(val graphics: Graphics2D) : Canvas { radiusY: Float, paint: Paint, ) { - graphics.paint = paint.toAwt() + graphics.setupPaint(paint) graphics.drawRoundRect( left.toInt(), top.toInt(), @@ -285,7 +286,7 @@ internal class SvgCanvas(val graphics: Graphics2D) : Canvas { } } -internal class SvgDrawContext(val graphics: Graphics2D, override var size: Size) : DrawContext { +internal class SvgDrawContext(val graphics: SVGGraphics2D, override var size: Size) : DrawContext { override val canvas: Canvas = SvgCanvas(graphics) override val transform: DrawTransform = asDrawTransform() diff --git a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgDrawScope.kt b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgDrawScope.kt index ba5323d..7454557 100644 --- a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgDrawScope.kt +++ b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgDrawScope.kt @@ -8,17 +8,19 @@ import androidx.compose.ui.graphics.drawscope.* import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection +import org.jfree.svg.SVGGraphics2D import java.awt.BasicStroke import java.awt.Font -import java.awt.Graphics2D import java.awt.geom.AffineTransform +import java.awt.geom.Arc2D import java.awt.image.AffineTransformOp import java.awt.Color as AWTColor -private fun Color.toAWT(): java.awt.Color = AWTColor(toArgb()) -private fun Brush.toAWT(): java.awt.Paint = TODO() - -public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawScope { +public class SvgDrawScope( + private val graphics: SVGGraphics2D, + size: Size, + val defaultStrokeWidth: Float = 1f, +) : DrawScope { override val layoutDirection: LayoutDirection get() = LayoutDirection.Ltr @@ -27,6 +29,42 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco override val fontScale: Float get() = 1f + private fun setupStroke(strokeWidth: Float, cap: StrokeCap, join: StrokeJoin = StrokeJoin.Miter) { + val width = if (strokeWidth == 0f) defaultStrokeWidth else strokeWidth + val capValue = when (cap) { + StrokeCap.Butt -> BasicStroke.CAP_BUTT + StrokeCap.Round -> BasicStroke.CAP_ROUND + StrokeCap.Square -> BasicStroke.CAP_SQUARE + else -> BasicStroke.CAP_SQUARE + } + val joinValue = when (join) { + StrokeJoin.Bevel -> BasicStroke.JOIN_BEVEL + StrokeJoin.Miter -> BasicStroke.JOIN_MITER + StrokeJoin.Round -> BasicStroke.JOIN_ROUND + else -> BasicStroke.JOIN_MITER + } + graphics.stroke = BasicStroke(width, capValue, joinValue) + } + + private fun setupStroke(stroke: Stroke) { + setupStroke(stroke.width, stroke.cap, stroke.join) + } + + private fun setupColor(color: Color) { + graphics.paint = AWTColor(color.toArgb(), false) + } + + private fun setupColor(brush: Brush) { + when (brush) { + is SolidColor -> { + graphics.paint = AWTColor(brush.value.toArgb(), false) + } + + is ShaderBrush -> TODO() + } + } + + override fun drawArc( brush: Brush, startAngle: Float, @@ -39,7 +77,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = brush.toAWT() + setupColor(brush) when (style) { Fill -> graphics.fillArc( topLeft.x.toInt(), @@ -50,14 +88,17 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco sweepAngle.toInt() ) - is Stroke -> graphics.drawArc( - topLeft.x.toInt(), - topLeft.y.toInt(), - size.width.toInt(), - size.height.toInt(), - startAngle.toInt(), - sweepAngle.toInt() - ) + is Stroke -> { + setupStroke(style) + graphics.drawArc( + topLeft.x.toInt(), + topLeft.y.toInt(), + size.width.toInt(), + size.height.toInt(), + startAngle.toInt(), + sweepAngle.toInt() + ) + } } } @@ -73,25 +114,24 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = color.toAWT() + setupColor(color) when (style) { Fill -> graphics.fillArc( topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt(), - startAngle.toInt(), - sweepAngle.toInt() + -startAngle.toInt(), + -sweepAngle.toInt() ) - is Stroke -> graphics.drawArc( - topLeft.x.toInt(), - topLeft.y.toInt(), - size.width.toInt(), - size.height.toInt(), - startAngle.toInt(), - sweepAngle.toInt() - ) + is Stroke -> { + setupStroke(style) + val arc = Arc2D.Float( + topLeft.x, topLeft.y, size.width, size.height, -startAngle, -sweepAngle, Arc2D.OPEN + ) + graphics.draw(arc) + } } } @@ -105,7 +145,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = brush.toAWT() + setupColor(brush) when (style) { Fill -> graphics.fillOval( (center.x - radius).toInt(), @@ -114,12 +154,15 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco (radius * 2).toInt() ) - is Stroke -> graphics.drawOval( - (center.x - radius).toInt(), - (center.y - radius).toInt(), - (radius * 2).toInt(), - (radius * 2).toInt() - ) + is Stroke -> { + setupStroke(style) + graphics.drawOval( + (center.x - radius).toInt(), + (center.y - radius).toInt(), + (radius * 2).toInt(), + (radius * 2).toInt() + ) + } } } @@ -133,7 +176,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = color.toAWT() + setupColor(color) when (style) { Fill -> graphics.fillOval( (center.x - radius).toInt(), @@ -142,12 +185,15 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco (radius * 2).toInt() ) - is Stroke -> graphics.drawOval( - (center.x - radius).toInt(), - (center.y - radius).toInt(), - (radius * 2).toInt(), - (radius * 2).toInt() - ) + is Stroke -> { + setupStroke(style) + graphics.drawOval( + (center.x - radius).toInt(), + (center.y - radius).toInt(), + (radius * 2).toInt(), + (radius * 2).toInt() + ) + } } } @@ -214,8 +260,8 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = brush.toAWT() - graphics.stroke = BasicStroke(strokeWidth) + setupColor(brush) + setupStroke(strokeWidth, cap) graphics.drawLine(start.x.toInt(), start.y.toInt(), end.x.toInt(), end.y.toInt()) } @@ -230,8 +276,8 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = color.toAWT() - graphics.stroke = BasicStroke(strokeWidth) + setupColor(color) + setupStroke(strokeWidth, cap) graphics.drawLine(start.x.toInt(), start.y.toInt(), end.x.toInt(), end.y.toInt()) } @@ -244,7 +290,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = brush.toAWT() + setupColor(brush) when (style) { Fill -> graphics.fillOval(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt()) is Stroke -> graphics.drawOval( @@ -265,7 +311,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = color.toAWT() + setupColor(color) when (style) { Fill -> graphics.fillOval(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt()) is Stroke -> graphics.drawOval( @@ -314,7 +360,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = brush.toAWT() + setupColor(brush) graphics.stroke = BasicStroke(strokeWidth) val xs = IntArray(points.size) { points[it].x.toInt() } val ys = IntArray(points.size) { points[it].y.toInt() } @@ -332,7 +378,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = color.toAWT() + setupColor(color) graphics.stroke = BasicStroke(strokeWidth) val xs = IntArray(points.size) { points[it].x.toInt() } val ys = IntArray(points.size) { points[it].y.toInt() } @@ -348,15 +394,18 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = brush.toAWT() + setupColor(brush) when (style) { Fill -> graphics.fillRect(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt()) - is Stroke -> graphics.drawRect( - topLeft.x.toInt(), - topLeft.y.toInt(), - size.width.toInt(), - size.height.toInt() - ) + is Stroke -> { + setupStroke(style) + graphics.drawRect( + topLeft.x.toInt(), + topLeft.y.toInt(), + size.width.toInt(), + size.height.toInt() + ) + } } } @@ -370,15 +419,18 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = color.toAWT() + setupColor(color) when (style) { Fill -> graphics.fillRect(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt()) - is Stroke -> graphics.drawRect( - topLeft.x.toInt(), - topLeft.y.toInt(), - size.width.toInt(), - size.height.toInt() - ) + is Stroke -> { + setupStroke(style) + graphics.drawRect( + topLeft.x.toInt(), + topLeft.y.toInt(), + size.width.toInt(), + size.height.toInt() + ) + } } } @@ -392,7 +444,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = brush.toAWT() + setupColor(brush) when (style) { Fill -> graphics.fillRoundRect( topLeft.x.toInt(), @@ -403,14 +455,17 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco cornerRadius.y.toInt() ) - is Stroke -> graphics.drawRoundRect( - topLeft.x.toInt(), - topLeft.y.toInt(), - size.width.toInt(), - size.height.toInt(), - cornerRadius.x.toInt(), - cornerRadius.y.toInt() - ) + is Stroke -> { + setupStroke(style) + graphics.drawRoundRect( + topLeft.x.toInt(), + topLeft.y.toInt(), + size.width.toInt(), + size.height.toInt(), + cornerRadius.x.toInt(), + cornerRadius.y.toInt() + ) + } } } @@ -425,7 +480,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco colorFilter: ColorFilter?, blendMode: BlendMode, ) { - graphics.paint = color.toAWT() + setupColor(color) when (style) { Fill -> graphics.fillRoundRect( topLeft.x.toInt(), @@ -436,14 +491,17 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco cornerRadius.y.toInt() ) - is Stroke -> graphics.drawRoundRect( - topLeft.x.toInt(), - topLeft.y.toInt(), - size.width.toInt(), - size.height.toInt(), - cornerRadius.x.toInt(), - cornerRadius.y.toInt() - ) + is Stroke -> { + setupStroke(style) + graphics.drawRoundRect( + topLeft.x.toInt(), + topLeft.y.toInt(), + size.width.toInt(), + size.height.toInt(), + cornerRadius.x.toInt(), + cornerRadius.y.toInt() + ) + } } } @@ -454,7 +512,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco font: Font, color: Color, ) { - graphics.paint = color.toAWT() + setupColor(color) graphics.font = font graphics.drawString(text, x, y) }