Fix SVG rendering for scheme

This commit is contained in:
Alexander Nozik 2022-10-09 17:11:03 +03:00
parent 77e27c7eb6
commit 737bbbde6a
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
4 changed files with 165 additions and 101 deletions

View File

@ -10,7 +10,7 @@ val ktorVersion by extra("2.0.3")
allprojects { allprojects {
group = "center.sciprog" group = "center.sciprog"
version = "0.1.0-dev-11" version = "0.1.0-dev-12"
} }
ksciencePublish{ ksciencePublish{

View File

@ -172,7 +172,12 @@ public fun SchemeView(
center = feature.center.toOffset() 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 -> { is SchemeArcFeature -> {
val topLeft = feature.oval.leftTop.toOffset() val topLeft = feature.oval.leftTop.toOffset()
val bottomRight = feature.oval.rightBottom.toOffset() val bottomRight = feature.oval.rightBottom.toOffset()

View File

@ -9,10 +9,14 @@ import androidx.compose.ui.graphics.drawscope.DrawContext
import androidx.compose.ui.graphics.drawscope.DrawTransform import androidx.compose.ui.graphics.drawscope.DrawTransform
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import org.jfree.svg.SVGGraphics2D
import java.awt.BasicStroke
import java.awt.Graphics2D import java.awt.Graphics2D
import java.awt.geom.Arc2D
internal fun Paint.toAwt(): java.awt.Paint { internal fun Graphics2D.setupPaint(p: Paint){
return java.awt.Color(color.toArgb()) 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) { override fun clipPath(path: Path, clipOp: ClipOp) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
@ -112,19 +116,15 @@ internal class SvgCanvas(val graphics: Graphics2D) : Canvas {
useCenter: Boolean, useCenter: Boolean,
paint: Paint, paint: Paint,
) { ) {
graphics.paint = paint.toAwt() graphics.setupPaint(paint)
graphics.drawArc( val arc = Arc2D.Float(
top.toInt(), left, top, (right - left), (top - bottom), -startAngle, -sweepAngle, Arc2D.OPEN
left.toInt(),
(right - left).toInt(),
(top - bottom).toInt(),
startAngle.toInt(),
sweepAngle.toInt()
) )
graphics.draw(arc)
} }
override fun drawCircle(center: Offset, radius: Float, paint: Paint) { override fun drawCircle(center: Offset, radius: Float, paint: Paint) {
graphics.paint = paint.toAwt() graphics.setupPaint(paint)
graphics.drawOval( graphics.drawOval(
(center.x - radius).toInt(), (center.x - radius).toInt(),
(center.y - 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) { 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()) 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) { 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()) 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) { override fun drawOval(left: Float, top: Float, right: Float, bottom: Float, paint: Paint) {
graphics.paint = paint.toAwt() graphics.setupPaint(paint)
graphics.drawOval( graphics.drawOval(
left.toInt(), left.toInt(),
top.toInt(), top.toInt(),
@ -165,13 +165,14 @@ internal class SvgCanvas(val graphics: Graphics2D) : Canvas {
} }
override fun drawPath(path: Path, paint: Paint) { override fun drawPath(path: Path, paint: Paint) {
graphics.setupPaint(paint)
val skiaPath = path.asSkiaPath() val skiaPath = path.asSkiaPath()
val points: List<Offset> = skiaPath.points.mapNotNull { it?.let { Offset(it.x, it.y) } } val points: List<Offset> = skiaPath.points.mapNotNull { it?.let { Offset(it.x, it.y) } }
drawPoints(PointMode.Lines, points, paint) drawPoints(PointMode.Lines, points, paint)
} }
override fun drawPoints(pointMode: PointMode, points: List<Offset>, paint: Paint) { override fun drawPoints(pointMode: PointMode, points: List<Offset>, paint: Paint) {
graphics.paint = paint.toAwt() graphics.setupPaint(paint)
val xs = IntArray(points.size) { points[it].x.toInt() } val xs = IntArray(points.size) { points[it].x.toInt() }
val ys = IntArray(points.size) { points[it].y.toInt() } val ys = IntArray(points.size) { points[it].y.toInt() }
when (pointMode) { 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) { override fun drawRect(left: Float, top: Float, right: Float, bottom: Float, paint: Paint) {
graphics.paint = paint.toAwt() graphics.setupPaint(paint)
graphics.drawRect( graphics.drawRect(
left.toInt(), left.toInt(),
top.toInt(), top.toInt(),
@ -236,7 +237,7 @@ internal class SvgCanvas(val graphics: Graphics2D) : Canvas {
radiusY: Float, radiusY: Float,
paint: Paint, paint: Paint,
) { ) {
graphics.paint = paint.toAwt() graphics.setupPaint(paint)
graphics.drawRoundRect( graphics.drawRoundRect(
left.toInt(), left.toInt(),
top.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 canvas: Canvas = SvgCanvas(graphics)
override val transform: DrawTransform = asDrawTransform() override val transform: DrawTransform = asDrawTransform()

View File

@ -8,17 +8,19 @@ import androidx.compose.ui.graphics.drawscope.*
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import org.jfree.svg.SVGGraphics2D
import java.awt.BasicStroke import java.awt.BasicStroke
import java.awt.Font import java.awt.Font
import java.awt.Graphics2D
import java.awt.geom.AffineTransform import java.awt.geom.AffineTransform
import java.awt.geom.Arc2D
import java.awt.image.AffineTransformOp import java.awt.image.AffineTransformOp
import java.awt.Color as AWTColor import java.awt.Color as AWTColor
private fun Color.toAWT(): java.awt.Color = AWTColor(toArgb()) public class SvgDrawScope(
private fun Brush.toAWT(): java.awt.Paint = TODO() private val graphics: SVGGraphics2D,
size: Size,
public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawScope { val defaultStrokeWidth: Float = 1f,
) : DrawScope {
override val layoutDirection: LayoutDirection override val layoutDirection: LayoutDirection
get() = LayoutDirection.Ltr get() = LayoutDirection.Ltr
@ -27,6 +29,42 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
override val fontScale: Float get() = 1f 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( override fun drawArc(
brush: Brush, brush: Brush,
startAngle: Float, startAngle: Float,
@ -39,7 +77,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = brush.toAWT() setupColor(brush)
when (style) { when (style) {
Fill -> graphics.fillArc( Fill -> graphics.fillArc(
topLeft.x.toInt(), topLeft.x.toInt(),
@ -50,7 +88,9 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
sweepAngle.toInt() sweepAngle.toInt()
) )
is Stroke -> graphics.drawArc( is Stroke -> {
setupStroke(style)
graphics.drawArc(
topLeft.x.toInt(), topLeft.x.toInt(),
topLeft.y.toInt(), topLeft.y.toInt(),
size.width.toInt(), size.width.toInt(),
@ -60,6 +100,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
) )
} }
} }
}
override fun drawArc( override fun drawArc(
color: Color, color: Color,
@ -73,25 +114,24 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = color.toAWT() setupColor(color)
when (style) { when (style) {
Fill -> graphics.fillArc( Fill -> graphics.fillArc(
topLeft.x.toInt(), topLeft.x.toInt(),
topLeft.y.toInt(), topLeft.y.toInt(),
size.width.toInt(), size.width.toInt(),
size.height.toInt(), size.height.toInt(),
startAngle.toInt(), -startAngle.toInt(),
sweepAngle.toInt() -sweepAngle.toInt()
) )
is Stroke -> graphics.drawArc( is Stroke -> {
topLeft.x.toInt(), setupStroke(style)
topLeft.y.toInt(), val arc = Arc2D.Float(
size.width.toInt(), topLeft.x, topLeft.y, size.width, size.height, -startAngle, -sweepAngle, Arc2D.OPEN
size.height.toInt(),
startAngle.toInt(),
sweepAngle.toInt()
) )
graphics.draw(arc)
}
} }
} }
@ -105,7 +145,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = brush.toAWT() setupColor(brush)
when (style) { when (style) {
Fill -> graphics.fillOval( Fill -> graphics.fillOval(
(center.x - radius).toInt(), (center.x - radius).toInt(),
@ -114,13 +154,16 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
(radius * 2).toInt() (radius * 2).toInt()
) )
is Stroke -> graphics.drawOval( is Stroke -> {
setupStroke(style)
graphics.drawOval(
(center.x - radius).toInt(), (center.x - radius).toInt(),
(center.y - radius).toInt(), (center.y - radius).toInt(),
(radius * 2).toInt(), (radius * 2).toInt(),
(radius * 2).toInt() (radius * 2).toInt()
) )
} }
}
} }
@ -133,7 +176,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = color.toAWT() setupColor(color)
when (style) { when (style) {
Fill -> graphics.fillOval( Fill -> graphics.fillOval(
(center.x - radius).toInt(), (center.x - radius).toInt(),
@ -142,7 +185,9 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
(radius * 2).toInt() (radius * 2).toInt()
) )
is Stroke -> graphics.drawOval( is Stroke -> {
setupStroke(style)
graphics.drawOval(
(center.x - radius).toInt(), (center.x - radius).toInt(),
(center.y - radius).toInt(), (center.y - radius).toInt(),
(radius * 2).toInt(), (radius * 2).toInt(),
@ -150,6 +195,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
) )
} }
} }
}
override fun drawImage( override fun drawImage(
image: ImageBitmap, image: ImageBitmap,
@ -214,8 +260,8 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = brush.toAWT() setupColor(brush)
graphics.stroke = BasicStroke(strokeWidth) setupStroke(strokeWidth, cap)
graphics.drawLine(start.x.toInt(), start.y.toInt(), end.x.toInt(), end.y.toInt()) 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?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = color.toAWT() setupColor(color)
graphics.stroke = BasicStroke(strokeWidth) setupStroke(strokeWidth, cap)
graphics.drawLine(start.x.toInt(), start.y.toInt(), end.x.toInt(), end.y.toInt()) 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?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = brush.toAWT() setupColor(brush)
when (style) { when (style) {
Fill -> graphics.fillOval(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt()) Fill -> graphics.fillOval(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt())
is Stroke -> graphics.drawOval( is Stroke -> graphics.drawOval(
@ -265,7 +311,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = color.toAWT() setupColor(color)
when (style) { when (style) {
Fill -> graphics.fillOval(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt()) Fill -> graphics.fillOval(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt())
is Stroke -> graphics.drawOval( is Stroke -> graphics.drawOval(
@ -314,7 +360,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = brush.toAWT() setupColor(brush)
graphics.stroke = BasicStroke(strokeWidth) graphics.stroke = BasicStroke(strokeWidth)
val xs = IntArray(points.size) { points[it].x.toInt() } val xs = IntArray(points.size) { points[it].x.toInt() }
val ys = IntArray(points.size) { points[it].y.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?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = color.toAWT() setupColor(color)
graphics.stroke = BasicStroke(strokeWidth) graphics.stroke = BasicStroke(strokeWidth)
val xs = IntArray(points.size) { points[it].x.toInt() } val xs = IntArray(points.size) { points[it].x.toInt() }
val ys = IntArray(points.size) { points[it].y.toInt() } val ys = IntArray(points.size) { points[it].y.toInt() }
@ -348,16 +394,19 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = brush.toAWT() setupColor(brush)
when (style) { when (style) {
Fill -> graphics.fillRect(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt()) Fill -> graphics.fillRect(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt())
is Stroke -> graphics.drawRect( is Stroke -> {
setupStroke(style)
graphics.drawRect(
topLeft.x.toInt(), topLeft.x.toInt(),
topLeft.y.toInt(), topLeft.y.toInt(),
size.width.toInt(), size.width.toInt(),
size.height.toInt() size.height.toInt()
) )
} }
}
} }
@ -370,10 +419,12 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = color.toAWT() setupColor(color)
when (style) { when (style) {
Fill -> graphics.fillRect(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt()) Fill -> graphics.fillRect(topLeft.x.toInt(), topLeft.y.toInt(), size.width.toInt(), size.height.toInt())
is Stroke -> graphics.drawRect( is Stroke -> {
setupStroke(style)
graphics.drawRect(
topLeft.x.toInt(), topLeft.x.toInt(),
topLeft.y.toInt(), topLeft.y.toInt(),
size.width.toInt(), size.width.toInt(),
@ -381,6 +432,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
) )
} }
} }
}
override fun drawRoundRect( override fun drawRoundRect(
brush: Brush, brush: Brush,
@ -392,7 +444,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = brush.toAWT() setupColor(brush)
when (style) { when (style) {
Fill -> graphics.fillRoundRect( Fill -> graphics.fillRoundRect(
topLeft.x.toInt(), topLeft.x.toInt(),
@ -403,7 +455,9 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
cornerRadius.y.toInt() cornerRadius.y.toInt()
) )
is Stroke -> graphics.drawRoundRect( is Stroke -> {
setupStroke(style)
graphics.drawRoundRect(
topLeft.x.toInt(), topLeft.x.toInt(),
topLeft.y.toInt(), topLeft.y.toInt(),
size.width.toInt(), size.width.toInt(),
@ -412,6 +466,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
cornerRadius.y.toInt() cornerRadius.y.toInt()
) )
} }
}
} }
@ -425,7 +480,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
blendMode: BlendMode, blendMode: BlendMode,
) { ) {
graphics.paint = color.toAWT() setupColor(color)
when (style) { when (style) {
Fill -> graphics.fillRoundRect( Fill -> graphics.fillRoundRect(
topLeft.x.toInt(), topLeft.x.toInt(),
@ -436,7 +491,9 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
cornerRadius.y.toInt() cornerRadius.y.toInt()
) )
is Stroke -> graphics.drawRoundRect( is Stroke -> {
setupStroke(style)
graphics.drawRoundRect(
topLeft.x.toInt(), topLeft.x.toInt(),
topLeft.y.toInt(), topLeft.y.toInt(),
size.width.toInt(), size.width.toInt(),
@ -446,6 +503,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
) )
} }
} }
}
fun drawText( fun drawText(
text: String, text: String,
@ -454,7 +512,7 @@ public class SvgDrawScope(public val graphics: Graphics2D, size: Size) : DrawSco
font: Font, font: Font,
color: Color, color: Color,
) { ) {
graphics.paint = color.toAWT() setupColor(color)
graphics.font = font graphics.font = font
graphics.drawString(text, x, y) graphics.drawString(text, x, y)
} }