neiam
/diogramos
diogramos
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
Match the web's text, connector and fill rendering
10ab3fb · Gabriel Morell · 2026-09-16 03:37
Message
{commit_body(@commit)}
Files changed
modified
android/app/src/main/java/org/neiam/diogramos/app/canvas/CanvasDocument.kt
+17
−6
@@ -147,13 +147,15 @@ data class Style(
| 147 | 147 | val stroke: String? = null, |
| 148 | 148 | val shadow: Boolean = false, |
| 149 | 149 | val dash: String = "solid", |
| 150 | − val strokeWidth: Double = 1.0, | |
| 150 | + val strokeWidth: Double = 1.5, | |
| 151 | 151 | val fontFamily: String? = null, |
| 152 | 152 | val fontSize: Double? = null, |
| 153 | 153 | val fontBold: Boolean = false, |
| 154 | 154 | val fontItalic: Boolean = false, |
| 155 | − val labelPosition: String = "center", | |
| 156 | − val labelSize: String = "md", | |
| 155 | + // Null, not "center": label_geom/1 defaults a circle to center and | |
| 156 | + // everything else to top-left, which only the renderer knows. | |
| 157 | + val labelPosition: String? = null, | |
| 158 | + val labelSize: String? = null, | |
| 157 | 159 | val labelColor: String? = null, |
| 158 | 160 | val labelFontFamily: String? = null, |
| 159 | 161 | val labelBold: Boolean = false, |
@@ -167,13 +169,13 @@ data class Style(
| 167 | 169 | stroke = json["stroke"].asStringOrNull(), |
| 168 | 170 | shadow = json["shadow"].asBooleanOrNull() ?: false, |
| 169 | 171 | dash = json["dash"].asStringOrNull() ?: "solid", |
| 170 | − strokeWidth = json["stroke_width"].asDoubleOrNull() ?: 1.0, | |
| 172 | + strokeWidth = json["stroke_width"].asDoubleOrNull() ?: 1.5, | |
| 171 | 173 | fontFamily = json["font_family"].asStringOrNull(), |
| 172 | 174 | fontSize = json["font_size"].asDoubleOrNull(), |
| 173 | 175 | fontBold = json["font_bold"].asBooleanOrNull() ?: false, |
| 174 | 176 | fontItalic = json["font_italic"].asBooleanOrNull() ?: false, |
| 175 | − labelPosition = json["label_position"].asStringOrNull() ?: "center", | |
| 176 | − labelSize = json["label_size"].asStringOrNull() ?: "md", | |
| 177 | + labelPosition = json["label_position"].asStringOrNull(), | |
| 178 | + labelSize = json["label_size"].asStringOrNull(), | |
| 177 | 179 | labelColor = json["label_color"].asStringOrNull(), |
| 178 | 180 | labelFontFamily = json["label_font_family"].asStringOrNull(), |
| 179 | 181 | labelBold = json["label_bold"].asBooleanOrNull() ?: false, |
@@ -188,6 +190,14 @@ data class Anchor(
| 188 | 190 | val side: String = "auto", |
| 189 | 191 | val x: Double? = null, |
| 190 | 192 | val y: Double? = null, |
| 193 | + /** | |
| 194 | + * The element at the *other* end, written by the web editor when a | |
| 195 | + * connector is created. An "auto" anchor picks the edge facing this | |
| 196 | + * element; without it there is nothing to aim at and the server's | |
| 197 | + * own fallback compares an element's centre with itself, which | |
| 198 | + * always resolves east. | |
| 199 | + */ | |
| 200 | + val other: String? = null, | |
| 191 | 201 | ) { |
| 192 | 202 | companion object { |
| 193 | 203 | fun parse(json: JsonObject?): Anchor? { |
@@ -198,6 +208,7 @@ data class Anchor(
| 198 | 208 | side = json["anchor"].asStringOrNull() ?: "auto", |
| 199 | 209 | x = json["x"].asDoubleOrNull(), |
| 200 | 210 | y = json["y"].asDoubleOrNull(), |
| 211 | + other = json["_other"].asStringOrNull(), | |
| 201 | 212 | ) |
| 202 | 213 | } |
| 203 | 214 | } |
modified
android/app/src/main/java/org/neiam/diogramos/app/canvas/CanvasScreen.kt
+35
−6
@@ -91,6 +91,7 @@ fun CanvasScreen(
| 91 | 91 | var live by remember { mutableStateOf(LiveState.Connecting) } |
| 92 | 92 | var peers by remember { mutableStateOf<List<Peer>>(emptyList()) } |
| 93 | 93 | var sender by remember { mutableStateOf<CanvasSender?>(null) } |
| 94 | + var liveError by remember { mutableStateOf<String?>(null) } | |
| 94 | 95 | var selectedId by remember { mutableStateOf<String?>(null) } |
| 95 | 96 | var drag by remember { mutableStateOf<DragState?>(null) } |
| 96 | 97 | var showAddSheet by remember { mutableStateOf(false) } |
@@ -142,7 +143,19 @@ fun CanvasScreen(
| 142 | 143 | val job: Job = scope.launch { |
| 143 | 144 | CanvasSocket(creds).subscribe(canvasId).collectLatest { event -> |
| 144 | 145 | when (event) { |
| 145 | − is CanvasEvent.Connected -> Unit // Wait for the join. | |
| 146 | + // The first connect waits for the join and its | |
| 147 | + // snapshot. A later one is a reconnect: the client | |
| 148 | + // rejoins the channel by itself without firing the | |
| 149 | + // join callback again, so this is the only signal | |
| 150 | + // that traffic is flowing again -- and the only | |
| 151 | + // chance to resync, since that silent rejoin brings | |
| 152 | + // no snapshot with it. | |
| 153 | + is CanvasEvent.Connected -> { | |
| 154 | + if (live != LiveState.Connecting) { | |
| 155 | + live = LiveState.Live | |
| 156 | + load() | |
| 157 | + } | |
| 158 | + } | |
| 146 | 159 | |
| 147 | 160 | is CanvasEvent.Disconnected -> { |
| 148 | 161 | live = LiveState.Offline |
@@ -163,6 +176,10 @@ fun CanvasScreen(
| 163 | 176 | } |
| 164 | 177 | |
| 165 | 178 | is CanvasEvent.Op -> { |
| 179 | + // An op is proof the channel is up, whatever the | |
| 180 | + // banner last heard. | |
| 181 | + live = LiveState.Live | |
| 182 | + | |
| 166 | 183 | when (session.onRemoteOp(event.op, event.version, event.actorRef)) { |
| 167 | 184 | CanvasSession.Outcome.Applied -> revision++ |
| 168 | 185 |
@@ -177,12 +194,18 @@ fun CanvasScreen(
| 177 | 194 | } |
| 178 | 195 | } |
| 179 | 196 | |
| 180 | − is CanvasEvent.Peers -> peers = event.peers | |
| 197 | + is CanvasEvent.Peers -> { | |
| 198 | + peers = event.peers | |
| 199 | + live = LiveState.Live | |
| 200 | + } | |
| 181 | 201 | |
| 182 | 202 | is CanvasEvent.Failed -> { |
| 183 | 203 | live = LiveState.Offline |
| 184 | 204 | // A failure here costs liveness, not the diagram, |
| 185 | − // so the fetched document stays on screen. | |
| 205 | + // so the fetched document stays on screen -- but | |
| 206 | + // the reason belongs on screen too, or "Offline" | |
| 207 | + // is all anyone can report back. | |
| 208 | + liveError = event.message | |
| 186 | 209 | } |
| 187 | 210 | } |
| 188 | 211 | } |
@@ -288,7 +311,7 @@ fun CanvasScreen(
| 288 | 311 | }, |
| 289 | 312 | ) { padding -> |
| 290 | 313 | Column(modifier = Modifier.fillMaxSize().padding(padding)) { |
| 291 | − LiveBanner(live, peers, meta?.canWrite ?: true) | |
| 314 | + LiveBanner(live, liveError, peers, meta?.canWrite ?: true) | |
| 292 | 315 | |
| 293 | 316 | Box(modifier = Modifier.fillMaxSize()) { |
| 294 | 317 | when { |
@@ -487,10 +510,16 @@ private suspend fun SnackbarHostState.showMessage(message: String) {
| 487 | 510 | } |
| 488 | 511 | |
| 489 | 512 | @Composable |
| 490 | −private fun LiveBanner(live: LiveState, peers: List<Peer>, canWrite: Boolean) { | |
| 513 | +private fun LiveBanner( | |
| 514 | + live: LiveState, | |
| 515 | + error: String?, | |
| 516 | + peers: List<Peer>, | |
| 517 | + canWrite: Boolean, | |
| 518 | +) { | |
| 491 | 519 | val message = when { |
| 492 | 520 | live == LiveState.Connecting -> "Connecting…" |
| 493 | − live == LiveState.Offline -> "Offline — this view won't update until it reconnects." | |
| 521 | + live == LiveState.Offline -> | |
| 522 | + error ?: "Offline — this view won't update until it reconnects." | |
| 494 | 523 | !canWrite -> "Read-only" + peersSuffix(peers) |
| 495 | 524 | peers.isNotEmpty() -> peers.joinToString(", ") { it.name } + " also here" |
| 496 | 525 | else -> null |
modified
android/app/src/main/java/org/neiam/diogramos/app/canvas/ConnectorGeometry.kt
+13
−6
@@ -122,12 +122,19 @@ object ConnectorGeometry {
| 122 | 122 | |
| 123 | 123 | "n", "e", "s", "w" -> edge(element.bbox, anchor.side) |
| 124 | 124 | |
| 125 | − // "auto" on the server resolves against `_other`, which nothing | |
| 126 | − // populates, so `best_edge` compares the element's centre with | |
| 127 | − // itself and always lands on the east edge. Reproduced | |
| 128 | − // deliberately: the fixtures encode it, and the port has to | |
| 129 | − // match the web until the server side is fixed. | |
| 130 | − "auto" -> bestEdge(element.bbox, Point(element.bbox.centerX, element.bbox.centerY)) | |
| 125 | + // Aim at the other endpoint, which is what `_other` carries. | |
| 126 | + // When it is absent or names an element that is gone, the | |
| 127 | + // server compares the element's centre with itself and the | |
| 128 | + // first branch of best_edge wins, so every such anchor | |
| 129 | + // resolves east -- matched here rather than second-guessed. | |
| 130 | + "auto" -> { | |
| 131 | + val target = anchor.other | |
| 132 | + ?.let { document.elements[it] } | |
| 133 | + ?.let { Point(it.bbox.centerX, it.bbox.centerY) } | |
| 134 | + ?: Point(element.bbox.centerX, element.bbox.centerY) | |
| 135 | + | |
| 136 | + bestEdge(element.bbox, target) | |
| 137 | + } | |
| 131 | 138 | |
| 132 | 139 | else -> null |
| 133 | 140 | } |
modified
android/app/src/main/java/org/neiam/diogramos/app/canvas/Renderer.kt
+136
−96
@@ -15,8 +15,6 @@ import androidx.compose.ui.text.TextStyle
| 15 | 15 | import androidx.compose.ui.text.font.FontFamily |
| 16 | 16 | import androidx.compose.ui.text.font.FontStyle |
| 17 | 17 | import androidx.compose.ui.text.font.FontWeight |
| 18 | −import androidx.compose.ui.text.style.TextAlign | |
| 19 | −import androidx.compose.ui.unit.Constraints | |
| 20 | 18 | |
| 21 | 19 | import org.neiam.diogramos.app.ui.theme.AppTheme |
| 22 | 20 | import org.neiam.diogramos.app.ui.theme.B612 |
@@ -110,9 +108,11 @@ class Renderer(
| 110 | 108 | |
| 111 | 109 | // A shape someone else is dragging is ghosted rather than drawn |
| 112 | 110 | // at a stale position, the same way the web editor does it. |
| 113 | − val alpha = if (dimmed) 0.35f else 1f | |
| 114 | − val fill = element.style.fill?.let { theme.token(it).copy(alpha = alpha) } | |
| 115 | − val stroke = element.style.stroke?.let { theme.token(it).copy(alpha = alpha) } | |
| 111 | + // `shape_style/1` fills an unstyled shape with base-200 and | |
| 112 | + // strokes it with base-content; leaving them unpainted here drew | |
| 113 | + // outlines where the web draws solid boxes. | |
| 114 | + val fill = theme.token(element.style.fill ?: "base-200").ghost(dimmed) | |
| 115 | + val stroke = theme.token(element.style.stroke ?: "base-content").ghost(dimmed) | |
| 116 | 116 | val strokeWidth = (element.style.strokeWidth * viewport.scale).toFloat() |
| 117 | 117 | val dash = dashEffect(element.style.dash, viewport.scale) |
| 118 | 118 |
@@ -121,44 +121,43 @@ class Renderer(
| 121 | 121 | val radius = ((element.radius ?: 0.0) * viewport.scale).toFloat() |
| 122 | 122 | val center = viewport.toScreen(element.bbox.centerX, element.bbox.centerY) |
| 123 | 123 | |
| 124 | − fill?.let { scope.drawCircle(it, radius, center) } | |
| 125 | − stroke?.let { | |
| 126 | − scope.drawCircle( | |
| 127 | − it, | |
| 128 | − radius, | |
| 129 | − center, | |
| 130 | − style = Stroke(width = strokeWidth, pathEffect = dash), | |
| 131 | − ) | |
| 132 | − } | |
| 124 | + scope.drawCircle(fill, radius, center) | |
| 125 | + scope.drawCircle( | |
| 126 | + stroke, | |
| 127 | + radius, | |
| 128 | + center, | |
| 129 | + style = Stroke(width = strokeWidth, pathEffect = dash), | |
| 130 | + ) | |
| 133 | 131 | } |
| 134 | 132 | |
| 135 | 133 | ElementType.ROUNDED -> { |
| 136 | 134 | val corner = (8f * viewport.scale).coerceAtMost(size.minDimension / 2) |
| 137 | 135 | val radius = androidx.compose.ui.geometry.CornerRadius(corner, corner) |
| 138 | 136 | |
| 139 | − fill?.let { scope.drawRoundRect(it, topLeft, size, radius) } | |
| 140 | − stroke?.let { | |
| 141 | − scope.drawRoundRect( | |
| 142 | − it, | |
| 143 | − topLeft, | |
| 144 | − size, | |
| 145 | − radius, | |
| 146 | − style = Stroke(width = strokeWidth, pathEffect = dash), | |
| 147 | − ) | |
| 148 | − } | |
| 137 | + scope.drawRoundRect(fill, topLeft, size, radius) | |
| 138 | + scope.drawRoundRect( | |
| 139 | + stroke, | |
| 140 | + topLeft, | |
| 141 | + size, | |
| 142 | + radius, | |
| 143 | + style = Stroke(width = strokeWidth, pathEffect = dash), | |
| 144 | + ) | |
| 149 | 145 | } |
| 150 | 146 | |
| 151 | − ElementType.RECT, ElementType.TEXT -> { | |
| 152 | − fill?.let { scope.drawRect(it, topLeft, size) } | |
| 153 | − stroke?.let { | |
| 154 | − scope.drawRect( | |
| 155 | − it, | |
| 156 | − topLeft, | |
| 157 | − size, | |
| 158 | − style = Stroke(width = strokeWidth, pathEffect = dash), | |
| 159 | − ) | |
| 160 | − } | |
| 147 | + ElementType.RECT -> { | |
| 148 | + scope.drawRect(fill, topLeft, size) | |
| 149 | + scope.drawRect( | |
| 150 | + stroke, | |
| 151 | + topLeft, | |
| 152 | + size, | |
| 153 | + style = Stroke(width = strokeWidth, pathEffect = dash), | |
| 154 | + ) | |
| 161 | 155 | } |
| 156 | + | |
| 157 | + // A text element is a bare <text> on the web -- its box is | |
| 158 | + // only an anchor, never painted, and `fill` is the colour of | |
| 159 | + // the glyphs rather than of a rectangle. | |
| 160 | + ElementType.TEXT -> Unit | |
| 162 | 161 | } |
| 163 | 162 | |
| 164 | 163 | if (element.label.isNotEmpty()) { |
@@ -167,8 +166,13 @@ class Renderer(
| 167 | 166 | } |
| 168 | 167 | |
| 169 | 168 | /** |
| 170 | − * A text element's body fills its box; every other type carries a | |
| 171 | − * label positioned in one of the nine slots the web offers. | |
| 169 | + * Text, laid out the way the web's SVG does it. | |
| 170 | + * | |
| 171 | + * The critical part is that there is no wrapping: `<text>` is a | |
| 172 | + * single line that overflows its box when it is too long. Measuring | |
| 173 | + * against the box width instead made every label that didn't fit | |
| 174 | + * break between characters and render as a vertical column of | |
| 175 | + * letters. | |
| 172 | 176 | */ |
| 173 | 177 | private fun drawLabel( |
| 174 | 178 | scope: DrawScope, |
@@ -177,81 +181,99 @@ class Renderer(
| 177 | 181 | size: Size, |
| 178 | 182 | viewport: Viewport, |
| 179 | 183 | ) { |
| 180 | − val isText = element.type == ElementType.TEXT | |
| 181 | 184 | val style = element.style |
| 182 | 185 | |
| 183 | − // Document units scaled by the zoom, so this is device pixels. | |
| 184 | − val fontSizePx = when { | |
| 185 | − isText -> (style.fontSize ?: 16.0) * viewport.scale | |
| 186 | − else -> labelSizePx(style.labelSize) * viewport.scale | |
| 187 | − } | |
| 188 | − if (fontSizePx < 4f) return // Below this it is noise, not text. | |
| 189 | − | |
| 190 | − // px -> sp, deliberately. `sp` is scaled by both display density | |
| 191 | − // and the reader's font-size setting, so handing it a pixel count | |
| 192 | − // renders the text density-times too large -- and since the wrap | |
| 193 | − // width below is in real pixels, every word then overflows its box | |
| 194 | − // and the label wraps after each character. | |
| 195 | − // | |
| 196 | − // This is also why canvas text must not follow the system font | |
| 197 | − // scale: a diagram's text has to fit the box it was drawn in, the | |
| 198 | − // same way `font_size` is in user units on the web. | |
| 199 | − val fontSize = with(scope) { fontSizePx.toFloat().toSp() } | |
| 200 | − | |
| 201 | − val color = when { | |
| 202 | − isText -> style.fill?.let { theme.token(it) } ?: theme.baseContent | |
| 203 | − else -> style.labelColor?.let { theme.token(it) } ?: theme.baseContent | |
| 186 | + if (element.type == ElementType.TEXT) { | |
| 187 | + // text_element_style/1: fill defaults to base-content, size to 14. | |
| 188 | + val fontSizePx = (style.fontSize ?: DEFAULT_TEXT_SIZE) * viewport.scale | |
| 189 | + if (fontSizePx < MIN_LEGIBLE_PX) return | |
| 190 | + | |
| 191 | + val layout = measure( | |
| 192 | + scope = scope, | |
| 193 | + text = element.label, | |
| 194 | + fontSizePx = fontSizePx, | |
| 195 | + color = theme.token(style.fill ?: "base-content"), | |
| 196 | + family = style.fontFamily, | |
| 197 | + bold = style.fontBold, | |
| 198 | + italic = style.fontItalic, | |
| 199 | + ) | |
| 200 | + | |
| 201 | + // text_baseline/1 puts the baseline at y + round(size * 0.85) | |
| 202 | + // so the top of the glyph lands on y. | |
| 203 | + val baselineY = element.bbox.y + Math.round((style.fontSize ?: DEFAULT_TEXT_SIZE) * 0.85) | |
| 204 | + val anchor = viewport.toScreen(element.bbox.x, baselineY.toDouble()) | |
| 205 | + | |
| 206 | + scope.drawText( | |
| 207 | + layout, | |
| 208 | + topLeft = Offset(anchor.x, anchor.y - layout.firstBaseline), | |
| 209 | + ) | |
| 210 | + return | |
| 204 | 211 | } |
| 205 | 212 | |
| 206 | − val layout = textMeasurer.measure( | |
| 213 | + // label_geom/1 for a shape. | |
| 214 | + val fontSizePx = labelSize(style.labelSize) * viewport.scale | |
| 215 | + if (fontSizePx < MIN_LEGIBLE_PX) return | |
| 216 | + | |
| 217 | + val layout = measure( | |
| 218 | + scope = scope, | |
| 207 | 219 | text = element.label, |
| 208 | − style = TextStyle( | |
| 209 | − color = color, | |
| 210 | − fontSize = fontSize, | |
| 211 | − fontFamily = fontFamily(if (isText) style.fontFamily else style.labelFontFamily), | |
| 212 | − fontWeight = if (if (isText) style.fontBold else style.labelBold) { | |
| 213 | − FontWeight.Bold | |
| 214 | − } else { | |
| 215 | − FontWeight.Normal | |
| 216 | − }, | |
| 217 | − fontStyle = if (if (isText) style.fontItalic else style.labelItalic) { | |
| 218 | − FontStyle.Italic | |
| 219 | − } else { | |
| 220 | − FontStyle.Normal | |
| 221 | − }, | |
| 222 | − textAlign = TextAlign.Center, | |
| 223 | − ), | |
| 224 | − constraints = Constraints(maxWidth = size.width.toInt().coerceAtLeast(1)), | |
| 220 | + fontSizePx = fontSizePx, | |
| 221 | + color = theme.token(style.labelColor ?: "base-content"), | |
| 222 | + family = style.labelFontFamily, | |
| 223 | + bold = style.labelBold, | |
| 224 | + italic = style.labelItalic, | |
| 225 | 225 | ) |
| 226 | 226 | |
| 227 | − val position = if (isText) "center" else style.labelPosition | |
| 228 | − scope.drawText(layout, topLeft = labelOffset(position, topLeft, size, layout)) | |
| 229 | − } | |
| 230 | − | |
| 231 | − private fun labelOffset( | |
| 232 | − position: String, | |
| 233 | − topLeft: Offset, | |
| 234 | − size: Size, | |
| 235 | − layout: TextLayoutResult, | |
| 236 | − ): Offset { | |
| 237 | − val textWidth = layout.size.width.toFloat() | |
| 238 | − val textHeight = layout.size.height.toFloat() | |
| 227 | + // A circle's label centres; everything else sits top-left. | |
| 228 | + val position = style.labelPosition | |
| 229 | + ?: if (element.type == ElementType.CIRCLE) "center" else "tl" | |
| 230 | + val pad = (LABEL_PADDING * viewport.scale).toFloat() | |
| 239 | 231 | |
| 240 | 232 | val x = when (position) { |
| 241 | − "tl", "ml", "bl" -> topLeft.x | |
| 242 | − "tr", "mr", "br" -> topLeft.x + size.width - textWidth | |
| 243 | − else -> topLeft.x + (size.width - textWidth) / 2 | |
| 233 | + "tl", "ml", "bl" -> topLeft.x + pad | |
| 234 | + "tr", "mr", "br" -> topLeft.x + size.width - pad - layout.size.width | |
| 235 | + else -> topLeft.x + (size.width - layout.size.width) / 2 | |
| 244 | 236 | } |
| 245 | 237 | |
| 246 | 238 | val y = when (position) { |
| 247 | − "tl", "tc", "tr" -> topLeft.y | |
| 248 | − "bl", "bc", "br" -> topLeft.y + size.height - textHeight | |
| 249 | − else -> topLeft.y + (size.height - textHeight) / 2 | |
| 239 | + // "hanging": the top of the text sits on the anchor. | |
| 240 | + "tl", "tc", "tr" -> topLeft.y + pad | |
| 241 | + // "auto": the alphabetic baseline sits on the anchor. | |
| 242 | + "bl", "bc", "br" -> topLeft.y + size.height - pad - layout.firstBaseline | |
| 243 | + else -> topLeft.y + (size.height - layout.size.height) / 2 | |
| 250 | 244 | } |
| 251 | 245 | |
| 252 | − return Offset(x, y) | |
| 246 | + scope.drawText(layout, topLeft = Offset(x, y)) | |
| 253 | 247 | } |
| 254 | 248 | |
| 249 | + private fun measure( | |
| 250 | + scope: DrawScope, | |
| 251 | + text: String, | |
| 252 | + fontSizePx: Double, | |
| 253 | + color: Color, | |
| 254 | + family: String?, | |
| 255 | + bold: Boolean, | |
| 256 | + italic: Boolean, | |
| 257 | + ): TextLayoutResult = textMeasurer.measure( | |
| 258 | + text = text, | |
| 259 | + style = TextStyle( | |
| 260 | + color = color, | |
| 261 | + // px -> sp, deliberately. `sp` is scaled by display density | |
| 262 | + // and by the reader's font-size setting, so handing it a pixel | |
| 263 | + // count renders text density-times too large. Canvas text must | |
| 264 | + // not follow the system font scale either: it has to fit the | |
| 265 | + // box it was drawn in, as `font_size` does in user units on | |
| 266 | + // the web. | |
| 267 | + fontSize = with(scope) { fontSizePx.toFloat().toSp() }, | |
| 268 | + fontFamily = fontFamily(family), | |
| 269 | + fontWeight = if (bold) FontWeight.Bold else FontWeight.Normal, | |
| 270 | + fontStyle = if (italic) FontStyle.Italic else FontStyle.Normal, | |
| 271 | + ), | |
| 272 | + // SVG <text> is one line that overflows rather than wrapping. | |
| 273 | + softWrap = false, | |
| 274 | + maxLines = 1, | |
| 275 | + ) | |
| 276 | + | |
| 255 | 277 | private fun fontFamily(token: String?): FontFamily = when (token) { |
| 256 | 278 | "serif" -> FontFamily.Serif |
| 257 | 279 | "mono" -> FontFamily.Monospace |
@@ -259,7 +281,7 @@ class Renderer(
| 259 | 281 | else -> FontFamily.SansSerif |
| 260 | 282 | } |
| 261 | 283 | |
| 262 | − private fun labelSizePx(token: String): Double = when (token) { | |
| 284 | + private fun labelSize(token: String?): Double = when (token) { | |
| 263 | 285 | "sm" -> 11.0 |
| 264 | 286 | "lg" -> 18.0 |
| 265 | 287 | "xl" -> 24.0 |
@@ -393,9 +415,27 @@ class Renderer(
| 393 | 415 | |
| 394 | 416 | private companion object { |
| 395 | 417 | const val ARROW_SPREAD = 0.5f |
| 418 | + | |
| 419 | + /** `text_element_style/1` and `label_geom/1`. */ | |
| 420 | + const val DEFAULT_TEXT_SIZE = 14.0 | |
| 421 | + const val LABEL_PADDING = 8.0 | |
| 422 | + | |
| 423 | + /** Below this a glyph is noise; skip it rather than draw fuzz. */ | |
| 424 | + const val MIN_LEGIBLE_PX = 4.0 | |
| 396 | 425 | } |
| 397 | 426 | } |
| 398 | 427 | |
| 428 | +/** | |
| 429 | + * Fades a colour for a shape someone else is dragging. | |
| 430 | + * | |
| 431 | + * Scales the alpha it already has rather than replacing it, because | |
| 432 | + * `Color.Transparent` is black at zero alpha: setting alpha to 1 on it | |
| 433 | + * turns "no fill" into a solid black box, which is exactly what every | |
| 434 | + * `fill: "transparent"` container rendered as. | |
| 435 | + */ | |
| 436 | +private fun Color.ghost(dimmed: Boolean): Color = | |
| 437 | + if (dimmed) copy(alpha = alpha * 0.35f) else this | |
| 438 | + | |
| 399 | 439 | /** Screen-space radius of a corner handle: a finger, not a pixel. */ |
| 400 | 440 | const val HANDLE_RADIUS_PX = 14f |
| 401 | 441 |
modified
android/app/src/test/resources/connector_geometry.json
+1545
−33
@@ -814,6 +814,132 @@
| 814 | 814 | }, |
| 815 | 815 | "name": "rects-apart/straight/e->point" |
| 816 | 816 | }, |
| 817 | + { | |
| 818 | + "connector": { | |
| 819 | + "color": "base-content", | |
| 820 | + "dash": "solid", | |
| 821 | + "end_gap": 6, | |
| 822 | + "from": { | |
| 823 | + "_other": "b", | |
| 824 | + "anchor": "auto", | |
| 825 | + "element": "a" | |
| 826 | + }, | |
| 827 | + "id": "k", | |
| 828 | + "marker_end": "arrow", | |
| 829 | + "marker_start": "none", | |
| 830 | + "routing": "straight", | |
| 831 | + "start_gap": 6, | |
| 832 | + "stroke_width": 2, | |
| 833 | + "to": { | |
| 834 | + "_other": "a", | |
| 835 | + "anchor": "auto", | |
| 836 | + "element": "b" | |
| 837 | + } | |
| 838 | + }, | |
| 839 | + "document": "rects-apart", | |
| 840 | + "expected": { | |
| 841 | + "d": "M 106 30 L 294 240", | |
| 842 | + "dash_array": null, | |
| 843 | + "marker_end": "arrow", | |
| 844 | + "marker_start": "none", | |
| 845 | + "stroke_width": 2 | |
| 846 | + }, | |
| 847 | + "name": "rects-apart/straight/auto->auto" | |
| 848 | + }, | |
| 849 | + { | |
| 850 | + "connector": { | |
| 851 | + "color": "base-content", | |
| 852 | + "dash": "solid", | |
| 853 | + "end_gap": 6, | |
| 854 | + "from": { | |
| 855 | + "_other": "b", | |
| 856 | + "anchor": "auto", | |
| 857 | + "element": "a" | |
| 858 | + }, | |
| 859 | + "id": "k", | |
| 860 | + "marker_end": "arrow", | |
| 861 | + "marker_start": "none", | |
| 862 | + "routing": "straight", | |
| 863 | + "start_gap": 6, | |
| 864 | + "stroke_width": 2, | |
| 865 | + "to": { | |
| 866 | + "anchor": "w", | |
| 867 | + "element": "b" | |
| 868 | + } | |
| 869 | + }, | |
| 870 | + "document": "rects-apart", | |
| 871 | + "expected": { | |
| 872 | + "d": "M 106 30 L 294 240", | |
| 873 | + "dash_array": null, | |
| 874 | + "marker_end": "arrow", | |
| 875 | + "marker_start": "none", | |
| 876 | + "stroke_width": 2 | |
| 877 | + }, | |
| 878 | + "name": "rects-apart/straight/auto->w" | |
| 879 | + }, | |
| 880 | + { | |
| 881 | + "connector": { | |
| 882 | + "color": "base-content", | |
| 883 | + "dash": "solid", | |
| 884 | + "end_gap": 6, | |
| 885 | + "from": { | |
| 886 | + "anchor": "n", | |
| 887 | + "element": "a" | |
| 888 | + }, | |
| 889 | + "id": "k", | |
| 890 | + "marker_end": "arrow", | |
| 891 | + "marker_start": "none", | |
| 892 | + "routing": "straight", | |
| 893 | + "start_gap": 6, | |
| 894 | + "stroke_width": 2, | |
| 895 | + "to": { | |
| 896 | + "_other": "a", | |
| 897 | + "anchor": "auto", | |
| 898 | + "element": "b" | |
| 899 | + } | |
| 900 | + }, | |
| 901 | + "document": "rects-apart", | |
| 902 | + "expected": { | |
| 903 | + "d": "M 50 -6 L 294 240", | |
| 904 | + "dash_array": null, | |
| 905 | + "marker_end": "arrow", | |
| 906 | + "marker_start": "none", | |
| 907 | + "stroke_width": 2 | |
| 908 | + }, | |
| 909 | + "name": "rects-apart/straight/n->auto" | |
| 910 | + }, | |
| 911 | + { | |
| 912 | + "connector": { | |
| 913 | + "color": "base-content", | |
| 914 | + "dash": "solid", | |
| 915 | + "end_gap": 6, | |
| 916 | + "from": { | |
| 917 | + "_other": "ghost", | |
| 918 | + "anchor": "auto", | |
| 919 | + "element": "a" | |
| 920 | + }, | |
| 921 | + "id": "k", | |
| 922 | + "marker_end": "arrow", | |
| 923 | + "marker_start": "none", | |
| 924 | + "routing": "straight", | |
| 925 | + "start_gap": 6, | |
| 926 | + "stroke_width": 2, | |
| 927 | + "to": { | |
| 928 | + "_other": "a", | |
| 929 | + "anchor": "auto", | |
| 930 | + "element": "b" | |
| 931 | + } | |
| 932 | + }, | |
| 933 | + "document": "rects-apart", | |
| 934 | + "expected": { | |
| 935 | + "d": "M 106 30 L 294 240", | |
| 936 | + "dash_array": null, | |
| 937 | + "marker_end": "arrow", | |
| 938 | + "marker_start": "none", | |
| 939 | + "stroke_width": 2 | |
| 940 | + }, | |
| 941 | + "name": "rects-apart/straight/auto->auto" | |
| 942 | + }, | |
| 817 | 943 | { |
| 818 | 944 | "connector": { |
| 819 | 945 | "color": "base-content", |
@@ -1628,6 +1754,132 @@
| 1628 | 1754 | }, |
| 1629 | 1755 | "name": "rects-apart/orthogonal/e->point" |
| 1630 | 1756 | }, |
| 1757 | + { | |
| 1758 | + "connector": { | |
| 1759 | + "color": "base-content", | |
| 1760 | + "dash": "solid", | |
| 1761 | + "end_gap": 6, | |
| 1762 | + "from": { | |
| 1763 | + "_other": "b", | |
| 1764 | + "anchor": "auto", | |
| 1765 | + "element": "a" | |
| 1766 | + }, | |
| 1767 | + "id": "k", | |
| 1768 | + "marker_end": "arrow", | |
| 1769 | + "marker_start": "none", | |
| 1770 | + "routing": "orthogonal", | |
| 1771 | + "start_gap": 6, | |
| 1772 | + "stroke_width": 2, | |
| 1773 | + "to": { | |
| 1774 | + "_other": "a", | |
| 1775 | + "anchor": "auto", | |
| 1776 | + "element": "b" | |
| 1777 | + } | |
| 1778 | + }, | |
| 1779 | + "document": "rects-apart", | |
| 1780 | + "expected": { | |
| 1781 | + "d": "M 106 30 L 200 30 L 200 240 L 294 240", | |
| 1782 | + "dash_array": null, | |
| 1783 | + "marker_end": "arrow", | |
| 1784 | + "marker_start": "none", | |
| 1785 | + "stroke_width": 2 | |
| 1786 | + }, | |
| 1787 | + "name": "rects-apart/orthogonal/auto->auto" | |
| 1788 | + }, | |
| 1789 | + { | |
| 1790 | + "connector": { | |
| 1791 | + "color": "base-content", | |
| 1792 | + "dash": "solid", | |
| 1793 | + "end_gap": 6, | |
| 1794 | + "from": { | |
| 1795 | + "_other": "b", | |
| 1796 | + "anchor": "auto", | |
| 1797 | + "element": "a" | |
| 1798 | + }, | |
| 1799 | + "id": "k", | |
| 1800 | + "marker_end": "arrow", | |
| 1801 | + "marker_start": "none", | |
| 1802 | + "routing": "orthogonal", | |
| 1803 | + "start_gap": 6, | |
| 1804 | + "stroke_width": 2, | |
| 1805 | + "to": { | |
| 1806 | + "anchor": "w", | |
| 1807 | + "element": "b" | |
| 1808 | + } | |
| 1809 | + }, | |
| 1810 | + "document": "rects-apart", | |
| 1811 | + "expected": { | |
| 1812 | + "d": "M 106 30 L 200 30 L 200 240 L 294 240", | |
| 1813 | + "dash_array": null, | |
| 1814 | + "marker_end": "arrow", | |
| 1815 | + "marker_start": "none", | |
| 1816 | + "stroke_width": 2 | |
| 1817 | + }, | |
| 1818 | + "name": "rects-apart/orthogonal/auto->w" | |
| 1819 | + }, | |
| 1820 | + { | |
| 1821 | + "connector": { | |
| 1822 | + "color": "base-content", | |
| 1823 | + "dash": "solid", | |
| 1824 | + "end_gap": 6, | |
| 1825 | + "from": { | |
| 1826 | + "anchor": "n", | |
| 1827 | + "element": "a" | |
| 1828 | + }, | |
| 1829 | + "id": "k", | |
| 1830 | + "marker_end": "arrow", | |
| 1831 | + "marker_start": "none", | |
| 1832 | + "routing": "orthogonal", | |
| 1833 | + "start_gap": 6, | |
| 1834 | + "stroke_width": 2, | |
| 1835 | + "to": { | |
| 1836 | + "_other": "a", | |
| 1837 | + "anchor": "auto", | |
| 1838 | + "element": "b" | |
| 1839 | + } | |
| 1840 | + }, | |
| 1841 | + "document": "rects-apart", | |
| 1842 | + "expected": { | |
| 1843 | + "d": "M 50 -6 L 50 117 L 294 117 L 294 240", | |
| 1844 | + "dash_array": null, | |
| 1845 | + "marker_end": "arrow", | |
| 1846 | + "marker_start": "none", | |
| 1847 | + "stroke_width": 2 | |
| 1848 | + }, | |
| 1849 | + "name": "rects-apart/orthogonal/n->auto" | |
| 1850 | + }, | |
| 1851 | + { | |
| 1852 | + "connector": { | |
| 1853 | + "color": "base-content", | |
| 1854 | + "dash": "solid", | |
| 1855 | + "end_gap": 6, | |
| 1856 | + "from": { | |
| 1857 | + "_other": "ghost", | |
| 1858 | + "anchor": "auto", | |
| 1859 | + "element": "a" | |
| 1860 | + }, | |
| 1861 | + "id": "k", | |
| 1862 | + "marker_end": "arrow", | |
| 1863 | + "marker_start": "none", | |
| 1864 | + "routing": "orthogonal", | |
| 1865 | + "start_gap": 6, | |
| 1866 | + "stroke_width": 2, | |
| 1867 | + "to": { | |
| 1868 | + "_other": "a", | |
| 1869 | + "anchor": "auto", | |
| 1870 | + "element": "b" | |
| 1871 | + } | |
| 1872 | + }, | |
| 1873 | + "document": "rects-apart", | |
| 1874 | + "expected": { | |
| 1875 | + "d": "M 106 30 L 200 30 L 200 240 L 294 240", | |
| 1876 | + "dash_array": null, | |
| 1877 | + "marker_end": "arrow", | |
| 1878 | + "marker_start": "none", | |
| 1879 | + "stroke_width": 2 | |
| 1880 | + }, | |
| 1881 | + "name": "rects-apart/orthogonal/auto->auto" | |
| 1882 | + }, | |
| 1631 | 1883 | { |
| 1632 | 1884 | "connector": { |
| 1633 | 1885 | "color": "base-content", |
@@ -2446,37 +2698,163 @@
| 2446 | 2698 | "connector": { |
| 2447 | 2699 | "color": "base-content", |
| 2448 | 2700 | "dash": "solid", |
| 2449 | − "end_gap": 0, | |
| 2701 | + "end_gap": 6, | |
| 2450 | 2702 | "from": { |
| 2451 | − "anchor": "e", | |
| 2703 | + "_other": "b", | |
| 2704 | + "anchor": "auto", | |
| 2452 | 2705 | "element": "a" |
| 2453 | 2706 | }, |
| 2454 | 2707 | "id": "k", |
| 2455 | 2708 | "marker_end": "arrow", |
| 2456 | 2709 | "marker_start": "none", |
| 2457 | − "routing": "straight", | |
| 2458 | − "start_gap": 0, | |
| 2710 | + "routing": "curve", | |
| 2711 | + "start_gap": 6, | |
| 2459 | 2712 | "stroke_width": 2, |
| 2460 | 2713 | "to": { |
| 2461 | − "anchor": "w", | |
| 2714 | + "_other": "a", | |
| 2715 | + "anchor": "auto", | |
| 2462 | 2716 | "element": "b" |
| 2463 | 2717 | } |
| 2464 | 2718 | }, |
| 2465 | 2719 | "document": "rects-apart", |
| 2466 | 2720 | "expected": { |
| 2467 | − "d": "M 100 30 L 300 240", | |
| 2721 | + "d": "M 106 30 C 218.74 30, 181.26 240, 294 240", | |
| 2468 | 2722 | "dash_array": null, |
| 2469 | 2723 | "marker_end": "arrow", |
| 2470 | 2724 | "marker_start": "none", |
| 2471 | 2725 | "stroke_width": 2 |
| 2472 | 2726 | }, |
| 2473 | − "name": "rects-apart/straight/e->w" | |
| 2727 | + "name": "rects-apart/curve/auto->auto" | |
| 2474 | 2728 | }, |
| 2475 | 2729 | { |
| 2476 | 2730 | "connector": { |
| 2477 | 2731 | "color": "base-content", |
| 2478 | 2732 | "dash": "solid", |
| 2479 | − "end_gap": 3.5, | |
| 2733 | + "end_gap": 6, | |
| 2734 | + "from": { | |
| 2735 | + "_other": "b", | |
| 2736 | + "anchor": "auto", | |
| 2737 | + "element": "a" | |
| 2738 | + }, | |
| 2739 | + "id": "k", | |
| 2740 | + "marker_end": "arrow", | |
| 2741 | + "marker_start": "none", | |
| 2742 | + "routing": "curve", | |
| 2743 | + "start_gap": 6, | |
| 2744 | + "stroke_width": 2, | |
| 2745 | + "to": { | |
| 2746 | + "anchor": "w", | |
| 2747 | + "element": "b" | |
| 2748 | + } | |
| 2749 | + }, | |
| 2750 | + "document": "rects-apart", | |
| 2751 | + "expected": { | |
| 2752 | + "d": "M 106 30 C 218.74 30, 181.26 240, 294 240", | |
| 2753 | + "dash_array": null, | |
| 2754 | + "marker_end": "arrow", | |
| 2755 | + "marker_start": "none", | |
| 2756 | + "stroke_width": 2 | |
| 2757 | + }, | |
| 2758 | + "name": "rects-apart/curve/auto->w" | |
| 2759 | + }, | |
| 2760 | + { | |
| 2761 | + "connector": { | |
| 2762 | + "color": "base-content", | |
| 2763 | + "dash": "solid", | |
| 2764 | + "end_gap": 6, | |
| 2765 | + "from": { | |
| 2766 | + "anchor": "n", | |
| 2767 | + "element": "a" | |
| 2768 | + }, | |
| 2769 | + "id": "k", | |
| 2770 | + "marker_end": "arrow", | |
| 2771 | + "marker_start": "none", | |
| 2772 | + "routing": "curve", | |
| 2773 | + "start_gap": 6, | |
| 2774 | + "stroke_width": 2, | |
| 2775 | + "to": { | |
| 2776 | + "_other": "a", | |
| 2777 | + "anchor": "auto", | |
| 2778 | + "element": "b" | |
| 2779 | + } | |
| 2780 | + }, | |
| 2781 | + "document": "rects-apart", | |
| 2782 | + "expected": { | |
| 2783 | + "d": "M 50 -6 C 50 -144.59, 155.41 240, 294 240", | |
| 2784 | + "dash_array": null, | |
| 2785 | + "marker_end": "arrow", | |
| 2786 | + "marker_start": "none", | |
| 2787 | + "stroke_width": 2 | |
| 2788 | + }, | |
| 2789 | + "name": "rects-apart/curve/n->auto" | |
| 2790 | + }, | |
| 2791 | + { | |
| 2792 | + "connector": { | |
| 2793 | + "color": "base-content", | |
| 2794 | + "dash": "solid", | |
| 2795 | + "end_gap": 6, | |
| 2796 | + "from": { | |
| 2797 | + "_other": "ghost", | |
| 2798 | + "anchor": "auto", | |
| 2799 | + "element": "a" | |
| 2800 | + }, | |
| 2801 | + "id": "k", | |
| 2802 | + "marker_end": "arrow", | |
| 2803 | + "marker_start": "none", | |
| 2804 | + "routing": "curve", | |
| 2805 | + "start_gap": 6, | |
| 2806 | + "stroke_width": 2, | |
| 2807 | + "to": { | |
| 2808 | + "_other": "a", | |
| 2809 | + "anchor": "auto", | |
| 2810 | + "element": "b" | |
| 2811 | + } | |
| 2812 | + }, | |
| 2813 | + "document": "rects-apart", | |
| 2814 | + "expected": { | |
| 2815 | + "d": "M 106 30 C 218.74 30, 181.26 240, 294 240", | |
| 2816 | + "dash_array": null, | |
| 2817 | + "marker_end": "arrow", | |
| 2818 | + "marker_start": "none", | |
| 2819 | + "stroke_width": 2 | |
| 2820 | + }, | |
| 2821 | + "name": "rects-apart/curve/auto->auto" | |
| 2822 | + }, | |
| 2823 | + { | |
| 2824 | + "connector": { | |
| 2825 | + "color": "base-content", | |
| 2826 | + "dash": "solid", | |
| 2827 | + "end_gap": 0, | |
| 2828 | + "from": { | |
| 2829 | + "anchor": "e", | |
| 2830 | + "element": "a" | |
| 2831 | + }, | |
| 2832 | + "id": "k", | |
| 2833 | + "marker_end": "arrow", | |
| 2834 | + "marker_start": "none", | |
| 2835 | + "routing": "straight", | |
| 2836 | + "start_gap": 0, | |
| 2837 | + "stroke_width": 2, | |
| 2838 | + "to": { | |
| 2839 | + "anchor": "w", | |
| 2840 | + "element": "b" | |
| 2841 | + } | |
| 2842 | + }, | |
| 2843 | + "document": "rects-apart", | |
| 2844 | + "expected": { | |
| 2845 | + "d": "M 100 30 L 300 240", | |
| 2846 | + "dash_array": null, | |
| 2847 | + "marker_end": "arrow", | |
| 2848 | + "marker_start": "none", | |
| 2849 | + "stroke_width": 2 | |
| 2850 | + }, | |
| 2851 | + "name": "rects-apart/straight/e->w" | |
| 2852 | + }, | |
| 2853 | + { | |
| 2854 | + "connector": { | |
| 2855 | + "color": "base-content", | |
| 2856 | + "dash": "solid", | |
| 2857 | + "end_gap": 3.5, | |
| 2480 | 2858 | "from": { |
| 2481 | 2859 | "anchor": "e", |
| 2482 | 2860 | "element": "a" |
@@ -3518,6 +3896,132 @@
| 3518 | 3896 | }, |
| 3519 | 3897 | "name": "circle-and-rect/straight/e->point" |
| 3520 | 3898 | }, |
| 3899 | + { | |
| 3900 | + "connector": { | |
| 3901 | + "color": "base-content", | |
| 3902 | + "dash": "solid", | |
| 3903 | + "end_gap": 6, | |
| 3904 | + "from": { | |
| 3905 | + "_other": "b", | |
| 3906 | + "anchor": "auto", | |
| 3907 | + "element": "a" | |
| 3908 | + }, | |
| 3909 | + "id": "k", | |
| 3910 | + "marker_end": "arrow", | |
| 3911 | + "marker_start": "none", | |
| 3912 | + "routing": "straight", | |
| 3913 | + "start_gap": 6, | |
| 3914 | + "stroke_width": 2, | |
| 3915 | + "to": { | |
| 3916 | + "_other": "a", | |
| 3917 | + "anchor": "auto", | |
| 3918 | + "element": "b" | |
| 3919 | + } | |
| 3920 | + }, | |
| 3921 | + "document": "circle-and-rect", | |
| 3922 | + "expected": { | |
| 3923 | + "d": "M 4 50 L -154 -95", | |
| 3924 | + "dash_array": null, | |
| 3925 | + "marker_end": "arrow", | |
| 3926 | + "marker_start": "none", | |
| 3927 | + "stroke_width": 2 | |
| 3928 | + }, | |
| 3929 | + "name": "circle-and-rect/straight/auto->auto" | |
| 3930 | + }, | |
| 3931 | + { | |
| 3932 | + "connector": { | |
| 3933 | + "color": "base-content", | |
| 3934 | + "dash": "solid", | |
| 3935 | + "end_gap": 6, | |
| 3936 | + "from": { | |
| 3937 | + "_other": "b", | |
| 3938 | + "anchor": "auto", | |
| 3939 | + "element": "a" | |
| 3940 | + }, | |
| 3941 | + "id": "k", | |
| 3942 | + "marker_end": "arrow", | |
| 3943 | + "marker_start": "none", | |
| 3944 | + "routing": "straight", | |
| 3945 | + "start_gap": 6, | |
| 3946 | + "stroke_width": 2, | |
| 3947 | + "to": { | |
| 3948 | + "anchor": "w", | |
| 3949 | + "element": "b" | |
| 3950 | + } | |
| 3951 | + }, | |
| 3952 | + "document": "circle-and-rect", | |
| 3953 | + "expected": { | |
| 3954 | + "d": "M 4 50 L -256 -95", | |
| 3955 | + "dash_array": null, | |
| 3956 | + "marker_end": "arrow", | |
| 3957 | + "marker_start": "none", | |
| 3958 | + "stroke_width": 2 | |
| 3959 | + }, | |
| 3960 | + "name": "circle-and-rect/straight/auto->w" | |
| 3961 | + }, | |
| 3962 | + { | |
| 3963 | + "connector": { | |
| 3964 | + "color": "base-content", | |
| 3965 | + "dash": "solid", | |
| 3966 | + "end_gap": 6, | |
| 3967 | + "from": { | |
| 3968 | + "anchor": "n", | |
| 3969 | + "element": "a" | |
| 3970 | + }, | |
| 3971 | + "id": "k", | |
| 3972 | + "marker_end": "arrow", | |
| 3973 | + "marker_start": "none", | |
| 3974 | + "routing": "straight", | |
| 3975 | + "start_gap": 6, | |
| 3976 | + "stroke_width": 2, | |
| 3977 | + "to": { | |
| 3978 | + "_other": "a", | |
| 3979 | + "anchor": "auto", | |
| 3980 | + "element": "b" | |
| 3981 | + } | |
| 3982 | + }, | |
| 3983 | + "document": "circle-and-rect", | |
| 3984 | + "expected": { | |
| 3985 | + "d": "M 50 4 L -154 -95", | |
| 3986 | + "dash_array": null, | |
| 3987 | + "marker_end": "arrow", | |
| 3988 | + "marker_start": "none", | |
| 3989 | + "stroke_width": 2 | |
| 3990 | + }, | |
| 3991 | + "name": "circle-and-rect/straight/n->auto" | |
| 3992 | + }, | |
| 3993 | + { | |
| 3994 | + "connector": { | |
| 3995 | + "color": "base-content", | |
| 3996 | + "dash": "solid", | |
| 3997 | + "end_gap": 6, | |
| 3998 | + "from": { | |
| 3999 | + "_other": "ghost", | |
| 4000 | + "anchor": "auto", | |
| 4001 | + "element": "a" | |
| 4002 | + }, | |
| 4003 | + "id": "k", | |
| 4004 | + "marker_end": "arrow", | |
| 4005 | + "marker_start": "none", | |
| 4006 | + "routing": "straight", | |
| 4007 | + "start_gap": 6, | |
| 4008 | + "stroke_width": 2, | |
| 4009 | + "to": { | |
| 4010 | + "_other": "a", | |
| 4011 | + "anchor": "auto", | |
| 4012 | + "element": "b" | |
| 4013 | + } | |
| 4014 | + }, | |
| 4015 | + "document": "circle-and-rect", | |
| 4016 | + "expected": { | |
| 4017 | + "d": "M 96 50 L -154 -95", | |
| 4018 | + "dash_array": null, | |
| 4019 | + "marker_end": "arrow", | |
| 4020 | + "marker_start": "none", | |
| 4021 | + "stroke_width": 2 | |
| 4022 | + }, | |
| 4023 | + "name": "circle-and-rect/straight/auto->auto" | |
| 4024 | + }, | |
| 3521 | 4025 | { |
| 3522 | 4026 | "connector": { |
| 3523 | 4027 | "color": "base-content", |
@@ -4332,6 +4836,132 @@
| 4332 | 4836 | }, |
| 4333 | 4837 | "name": "circle-and-rect/orthogonal/e->point" |
| 4334 | 4838 | }, |
| 4839 | + { | |
| 4840 | + "connector": { | |
| 4841 | + "color": "base-content", | |
| 4842 | + "dash": "solid", | |
| 4843 | + "end_gap": 6, | |
| 4844 | + "from": { | |
| 4845 | + "_other": "b", | |
| 4846 | + "anchor": "auto", | |
| 4847 | + "element": "a" | |
| 4848 | + }, | |
| 4849 | + "id": "k", | |
| 4850 | + "marker_end": "arrow", | |
| 4851 | + "marker_start": "none", | |
| 4852 | + "routing": "orthogonal", | |
| 4853 | + "start_gap": 6, | |
| 4854 | + "stroke_width": 2, | |
| 4855 | + "to": { | |
| 4856 | + "_other": "a", | |
| 4857 | + "anchor": "auto", | |
| 4858 | + "element": "b" | |
| 4859 | + } | |
| 4860 | + }, | |
| 4861 | + "document": "circle-and-rect", | |
| 4862 | + "expected": { | |
| 4863 | + "d": "M 4 50 L -75 50 L -75 -95 L -154 -95", | |
| 4864 | + "dash_array": null, | |
| 4865 | + "marker_end": "arrow", | |
| 4866 | + "marker_start": "none", | |
| 4867 | + "stroke_width": 2 | |
| 4868 | + }, | |
| 4869 | + "name": "circle-and-rect/orthogonal/auto->auto" | |
| 4870 | + }, | |
| 4871 | + { | |
| 4872 | + "connector": { | |
| 4873 | + "color": "base-content", | |
| 4874 | + "dash": "solid", | |
| 4875 | + "end_gap": 6, | |
| 4876 | + "from": { | |
| 4877 | + "_other": "b", | |
| 4878 | + "anchor": "auto", | |
| 4879 | + "element": "a" | |
| 4880 | + }, | |
| 4881 | + "id": "k", | |
| 4882 | + "marker_end": "arrow", | |
| 4883 | + "marker_start": "none", | |
| 4884 | + "routing": "orthogonal", | |
| 4885 | + "start_gap": 6, | |
| 4886 | + "stroke_width": 2, | |
| 4887 | + "to": { | |
| 4888 | + "anchor": "w", | |
| 4889 | + "element": "b" | |
| 4890 | + } | |
| 4891 | + }, | |
| 4892 | + "document": "circle-and-rect", | |
| 4893 | + "expected": { | |
| 4894 | + "d": "M 4 50 L -126 50 L -126 -95 L -256 -95", | |
| 4895 | + "dash_array": null, | |
| 4896 | + "marker_end": "arrow", | |
| 4897 | + "marker_start": "none", | |
| 4898 | + "stroke_width": 2 | |
| 4899 | + }, | |
| 4900 | + "name": "circle-and-rect/orthogonal/auto->w" | |
| 4901 | + }, | |
| 4902 | + { | |
| 4903 | + "connector": { | |
| 4904 | + "color": "base-content", | |
| 4905 | + "dash": "solid", | |
| 4906 | + "end_gap": 6, | |
| 4907 | + "from": { | |
| 4908 | + "anchor": "n", | |
| 4909 | + "element": "a" | |
| 4910 | + }, | |
| 4911 | + "id": "k", | |
| 4912 | + "marker_end": "arrow", | |
| 4913 | + "marker_start": "none", | |
| 4914 | + "routing": "orthogonal", | |
| 4915 | + "start_gap": 6, | |
| 4916 | + "stroke_width": 2, | |
| 4917 | + "to": { | |
| 4918 | + "_other": "a", | |
| 4919 | + "anchor": "auto", | |
| 4920 | + "element": "b" | |
| 4921 | + } | |
| 4922 | + }, | |
| 4923 | + "document": "circle-and-rect", | |
| 4924 | + "expected": { | |
| 4925 | + "d": "M 50 4 L 50 -45.5 L -154 -45.5 L -154 -95", | |
| 4926 | + "dash_array": null, | |
| 4927 | + "marker_end": "arrow", | |
| 4928 | + "marker_start": "none", | |
| 4929 | + "stroke_width": 2 | |
| 4930 | + }, | |
| 4931 | + "name": "circle-and-rect/orthogonal/n->auto" | |
| 4932 | + }, | |
| 4933 | + { | |
| 4934 | + "connector": { | |
| 4935 | + "color": "base-content", | |
| 4936 | + "dash": "solid", | |
| 4937 | + "end_gap": 6, | |
| 4938 | + "from": { | |
| 4939 | + "_other": "ghost", | |
| 4940 | + "anchor": "auto", | |
| 4941 | + "element": "a" | |
| 4942 | + }, | |
| 4943 | + "id": "k", | |
| 4944 | + "marker_end": "arrow", | |
| 4945 | + "marker_start": "none", | |
| 4946 | + "routing": "orthogonal", | |
| 4947 | + "start_gap": 6, | |
| 4948 | + "stroke_width": 2, | |
| 4949 | + "to": { | |
| 4950 | + "_other": "a", | |
| 4951 | + "anchor": "auto", | |
| 4952 | + "element": "b" | |
| 4953 | + } | |
| 4954 | + }, | |
| 4955 | + "document": "circle-and-rect", | |
| 4956 | + "expected": { | |
| 4957 | + "d": "M 96 50 L -29 50 L -29 -95 L -154 -95", | |
| 4958 | + "dash_array": null, | |
| 4959 | + "marker_end": "arrow", | |
| 4960 | + "marker_start": "none", | |
| 4961 | + "stroke_width": 2 | |
| 4962 | + }, | |
| 4963 | + "name": "circle-and-rect/orthogonal/auto->auto" | |
| 4964 | + }, | |
| 4335 | 4965 | { |
| 4336 | 4966 | "connector": { |
| 4337 | 4967 | "color": "base-content", |
@@ -5112,7 +5742,133 @@
| 5112 | 5742 | "marker_start": "none", |
| 5113 | 5743 | "stroke_width": 2 |
| 5114 | 5744 | }, |
| 5115 | − "name": "circle-and-rect/curve/point->e" | |
| 5745 | + "name": "circle-and-rect/curve/point->e" | |
| 5746 | + }, | |
| 5747 | + { | |
| 5748 | + "connector": { | |
| 5749 | + "color": "base-content", | |
| 5750 | + "dash": "solid", | |
| 5751 | + "end_gap": 6, | |
| 5752 | + "from": { | |
| 5753 | + "anchor": "e", | |
| 5754 | + "element": "a" | |
| 5755 | + }, | |
| 5756 | + "id": "k", | |
| 5757 | + "marker_end": "arrow", | |
| 5758 | + "marker_start": "none", | |
| 5759 | + "routing": "curve", | |
| 5760 | + "start_gap": 6, | |
| 5761 | + "stroke_width": 2, | |
| 5762 | + "to": { | |
| 5763 | + "anchor": "point", | |
| 5764 | + "element": "b", | |
| 5765 | + "x": 400, | |
| 5766 | + "y": 333.75 | |
| 5767 | + } | |
| 5768 | + }, | |
| 5769 | + "document": "circle-and-rect", | |
| 5770 | + "expected": { | |
| 5771 | + "d": "M 96 50 C 262.34 50, 400 333.75, 400 333.75", | |
| 5772 | + "dash_array": null, | |
| 5773 | + "marker_end": "arrow", | |
| 5774 | + "marker_start": "none", | |
| 5775 | + "stroke_width": 2 | |
| 5776 | + }, | |
| 5777 | + "name": "circle-and-rect/curve/e->point" | |
| 5778 | + }, | |
| 5779 | + { | |
| 5780 | + "connector": { | |
| 5781 | + "color": "base-content", | |
| 5782 | + "dash": "solid", | |
| 5783 | + "end_gap": 6, | |
| 5784 | + "from": { | |
| 5785 | + "_other": "b", | |
| 5786 | + "anchor": "auto", | |
| 5787 | + "element": "a" | |
| 5788 | + }, | |
| 5789 | + "id": "k", | |
| 5790 | + "marker_end": "arrow", | |
| 5791 | + "marker_start": "none", | |
| 5792 | + "routing": "curve", | |
| 5793 | + "start_gap": 6, | |
| 5794 | + "stroke_width": 2, | |
| 5795 | + "to": { | |
| 5796 | + "_other": "a", | |
| 5797 | + "anchor": "auto", | |
| 5798 | + "element": "b" | |
| 5799 | + } | |
| 5800 | + }, | |
| 5801 | + "document": "circle-and-rect", | |
| 5802 | + "expected": { | |
| 5803 | + "d": "M 4 50 C -81.78 50, -68.22 -95, -154 -95", | |
| 5804 | + "dash_array": null, | |
| 5805 | + "marker_end": "arrow", | |
| 5806 | + "marker_start": "none", | |
| 5807 | + "stroke_width": 2 | |
| 5808 | + }, | |
| 5809 | + "name": "circle-and-rect/curve/auto->auto" | |
| 5810 | + }, | |
| 5811 | + { | |
| 5812 | + "connector": { | |
| 5813 | + "color": "base-content", | |
| 5814 | + "dash": "solid", | |
| 5815 | + "end_gap": 6, | |
| 5816 | + "from": { | |
| 5817 | + "_other": "b", | |
| 5818 | + "anchor": "auto", | |
| 5819 | + "element": "a" | |
| 5820 | + }, | |
| 5821 | + "id": "k", | |
| 5822 | + "marker_end": "arrow", | |
| 5823 | + "marker_start": "none", | |
| 5824 | + "routing": "curve", | |
| 5825 | + "start_gap": 6, | |
| 5826 | + "stroke_width": 2, | |
| 5827 | + "to": { | |
| 5828 | + "anchor": "w", | |
| 5829 | + "element": "b" | |
| 5830 | + } | |
| 5831 | + }, | |
| 5832 | + "document": "circle-and-rect", | |
| 5833 | + "expected": { | |
| 5834 | + "d": "M 4 50 C -115.08 50, -375.08 -95, -256 -95", | |
| 5835 | + "dash_array": null, | |
| 5836 | + "marker_end": "arrow", | |
| 5837 | + "marker_start": "none", | |
| 5838 | + "stroke_width": 2 | |
| 5839 | + }, | |
| 5840 | + "name": "circle-and-rect/curve/auto->w" | |
| 5841 | + }, | |
| 5842 | + { | |
| 5843 | + "connector": { | |
| 5844 | + "color": "base-content", | |
| 5845 | + "dash": "solid", | |
| 5846 | + "end_gap": 6, | |
| 5847 | + "from": { | |
| 5848 | + "anchor": "n", | |
| 5849 | + "element": "a" | |
| 5850 | + }, | |
| 5851 | + "id": "k", | |
| 5852 | + "marker_end": "arrow", | |
| 5853 | + "marker_start": "none", | |
| 5854 | + "routing": "curve", | |
| 5855 | + "start_gap": 6, | |
| 5856 | + "stroke_width": 2, | |
| 5857 | + "to": { | |
| 5858 | + "_other": "a", | |
| 5859 | + "anchor": "auto", | |
| 5860 | + "element": "b" | |
| 5861 | + } | |
| 5862 | + }, | |
| 5863 | + "document": "circle-and-rect", | |
| 5864 | + "expected": { | |
| 5865 | + "d": "M 50 4 C 50 -86.7, -63.3 -95, -154 -95", | |
| 5866 | + "dash_array": null, | |
| 5867 | + "marker_end": "arrow", | |
| 5868 | + "marker_start": "none", | |
| 5869 | + "stroke_width": 2 | |
| 5870 | + }, | |
| 5871 | + "name": "circle-and-rect/curve/n->auto" | |
| 5116 | 5872 | }, |
| 5117 | 5873 | { |
| 5118 | 5874 | "connector": { |
@@ -5120,7 +5876,8 @@
| 5120 | 5876 | "dash": "solid", |
| 5121 | 5877 | "end_gap": 6, |
| 5122 | 5878 | "from": { |
| 5123 | − "anchor": "e", | |
| 5879 | + "_other": "ghost", | |
| 5880 | + "anchor": "auto", | |
| 5124 | 5881 | "element": "a" |
| 5125 | 5882 | }, |
| 5126 | 5883 | "id": "k", |
@@ -5130,21 +5887,20 @@
| 5130 | 5887 | "start_gap": 6, |
| 5131 | 5888 | "stroke_width": 2, |
| 5132 | 5889 | "to": { |
| 5133 | − "anchor": "point", | |
| 5134 | − "element": "b", | |
| 5135 | − "x": 400, | |
| 5136 | − "y": 333.75 | |
| 5890 | + "_other": "a", | |
| 5891 | + "anchor": "auto", | |
| 5892 | + "element": "b" | |
| 5137 | 5893 | } |
| 5138 | 5894 | }, |
| 5139 | 5895 | "document": "circle-and-rect", |
| 5140 | 5896 | "expected": { |
| 5141 | − "d": "M 96 50 C 262.34 50, 400 333.75, 400 333.75", | |
| 5897 | + "d": "M 96 50 C 211.6 50, -38.4 -95, -154 -95", | |
| 5142 | 5898 | "dash_array": null, |
| 5143 | 5899 | "marker_end": "arrow", |
| 5144 | 5900 | "marker_start": "none", |
| 5145 | 5901 | "stroke_width": 2 |
| 5146 | 5902 | }, |
| 5147 | − "name": "circle-and-rect/curve/e->point" | |
| 5903 | + "name": "circle-and-rect/curve/auto->auto" | |
| 5148 | 5904 | }, |
| 5149 | 5905 | { |
| 5150 | 5906 | "connector": { |
@@ -6222,6 +6978,132 @@
| 6222 | 6978 | }, |
| 6223 | 6979 | "name": "fractional/straight/e->point" |
| 6224 | 6980 | }, |
| 6981 | + { | |
| 6982 | + "connector": { | |
| 6983 | + "color": "base-content", | |
| 6984 | + "dash": "solid", | |
| 6985 | + "end_gap": 6, | |
| 6986 | + "from": { | |
| 6987 | + "_other": "b", | |
| 6988 | + "anchor": "auto", | |
| 6989 | + "element": "a" | |
| 6990 | + }, | |
| 6991 | + "id": "k", | |
| 6992 | + "marker_end": "arrow", | |
| 6993 | + "marker_start": "none", | |
| 6994 | + "routing": "straight", | |
| 6995 | + "start_gap": 6, | |
| 6996 | + "stroke_width": 2, | |
| 6997 | + "to": { | |
| 6998 | + "_other": "a", | |
| 6999 | + "anchor": "auto", | |
| 7000 | + "element": "b" | |
| 7001 | + } | |
| 7002 | + }, | |
| 7003 | + "document": "fractional", | |
| 7004 | + "expected": { | |
| 7005 | + "d": "M 40.5 10.75 L 95.33 11", | |
| 7006 | + "dash_array": null, | |
| 7007 | + "marker_end": "arrow", | |
| 7008 | + "marker_start": "none", | |
| 7009 | + "stroke_width": 2 | |
| 7010 | + }, | |
| 7011 | + "name": "fractional/straight/auto->auto" | |
| 7012 | + }, | |
| 7013 | + { | |
| 7014 | + "connector": { | |
| 7015 | + "color": "base-content", | |
| 7016 | + "dash": "solid", | |
| 7017 | + "end_gap": 6, | |
| 7018 | + "from": { | |
| 7019 | + "_other": "b", | |
| 7020 | + "anchor": "auto", | |
| 7021 | + "element": "a" | |
| 7022 | + }, | |
| 7023 | + "id": "k", | |
| 7024 | + "marker_end": "arrow", | |
| 7025 | + "marker_start": "none", | |
| 7026 | + "routing": "straight", | |
| 7027 | + "start_gap": 6, | |
| 7028 | + "stroke_width": 2, | |
| 7029 | + "to": { | |
| 7030 | + "anchor": "w", | |
| 7031 | + "element": "b" | |
| 7032 | + } | |
| 7033 | + }, | |
| 7034 | + "document": "fractional", | |
| 7035 | + "expected": { | |
| 7036 | + "d": "M 40.5 10.75 L 95.33 11", | |
| 7037 | + "dash_array": null, | |
| 7038 | + "marker_end": "arrow", | |
| 7039 | + "marker_start": "none", | |
| 7040 | + "stroke_width": 2 | |
| 7041 | + }, | |
| 7042 | + "name": "fractional/straight/auto->w" | |
| 7043 | + }, | |
| 7044 | + { | |
| 7045 | + "connector": { | |
| 7046 | + "color": "base-content", | |
| 7047 | + "dash": "solid", | |
| 7048 | + "end_gap": 6, | |
| 7049 | + "from": { | |
| 7050 | + "anchor": "n", | |
| 7051 | + "element": "a" | |
| 7052 | + }, | |
| 7053 | + "id": "k", | |
| 7054 | + "marker_end": "arrow", | |
| 7055 | + "marker_start": "none", | |
| 7056 | + "routing": "straight", | |
| 7057 | + "start_gap": 6, | |
| 7058 | + "stroke_width": 2, | |
| 7059 | + "to": { | |
| 7060 | + "_other": "a", | |
| 7061 | + "anchor": "auto", | |
| 7062 | + "element": "b" | |
| 7063 | + } | |
| 7064 | + }, | |
| 7065 | + "document": "fractional", | |
| 7066 | + "expected": { | |
| 7067 | + "d": "M 18 -3.75 L 95.33 11", | |
| 7068 | + "dash_array": null, | |
| 7069 | + "marker_end": "arrow", | |
| 7070 | + "marker_start": "none", | |
| 7071 | + "stroke_width": 2 | |
| 7072 | + }, | |
| 7073 | + "name": "fractional/straight/n->auto" | |
| 7074 | + }, | |
| 7075 | + { | |
| 7076 | + "connector": { | |
| 7077 | + "color": "base-content", | |
| 7078 | + "dash": "solid", | |
| 7079 | + "end_gap": 6, | |
| 7080 | + "from": { | |
| 7081 | + "_other": "ghost", | |
| 7082 | + "anchor": "auto", | |
| 7083 | + "element": "a" | |
| 7084 | + }, | |
| 7085 | + "id": "k", | |
| 7086 | + "marker_end": "arrow", | |
| 7087 | + "marker_start": "none", | |
| 7088 | + "routing": "straight", | |
| 7089 | + "start_gap": 6, | |
| 7090 | + "stroke_width": 2, | |
| 7091 | + "to": { | |
| 7092 | + "_other": "a", | |
| 7093 | + "anchor": "auto", | |
| 7094 | + "element": "b" | |
| 7095 | + } | |
| 7096 | + }, | |
| 7097 | + "document": "fractional", | |
| 7098 | + "expected": { | |
| 7099 | + "d": "M 40.5 10.75 L 95.33 11", | |
| 7100 | + "dash_array": null, | |
| 7101 | + "marker_end": "arrow", | |
| 7102 | + "marker_start": "none", | |
| 7103 | + "stroke_width": 2 | |
| 7104 | + }, | |
| 7105 | + "name": "fractional/straight/auto->auto" | |
| 7106 | + }, | |
| 6225 | 7107 | { |
| 6226 | 7108 | "connector": { |
| 6227 | 7109 | "color": "base-content", |
@@ -7002,7 +7884,133 @@
| 7002 | 7884 | "marker_start": "none", |
| 7003 | 7885 | "stroke_width": 2 |
| 7004 | 7886 | }, |
| 7005 | − "name": "fractional/orthogonal/point->e" | |
| 7887 | + "name": "fractional/orthogonal/point->e" | |
| 7888 | + }, | |
| 7889 | + { | |
| 7890 | + "connector": { | |
| 7891 | + "color": "base-content", | |
| 7892 | + "dash": "solid", | |
| 7893 | + "end_gap": 6, | |
| 7894 | + "from": { | |
| 7895 | + "anchor": "e", | |
| 7896 | + "element": "a" | |
| 7897 | + }, | |
| 7898 | + "id": "k", | |
| 7899 | + "marker_end": "arrow", | |
| 7900 | + "marker_start": "none", | |
| 7901 | + "routing": "orthogonal", | |
| 7902 | + "start_gap": 6, | |
| 7903 | + "stroke_width": 2, | |
| 7904 | + "to": { | |
| 7905 | + "anchor": "point", | |
| 7906 | + "element": "b", | |
| 7907 | + "x": 400, | |
| 7908 | + "y": 333.75 | |
| 7909 | + } | |
| 7910 | + }, | |
| 7911 | + "document": "fractional", | |
| 7912 | + "expected": { | |
| 7913 | + "d": "M 40.5 10.75 L 220.25 10.75 L 220.25 333.75 L 400 333.75", | |
| 7914 | + "dash_array": null, | |
| 7915 | + "marker_end": "arrow", | |
| 7916 | + "marker_start": "none", | |
| 7917 | + "stroke_width": 2 | |
| 7918 | + }, | |
| 7919 | + "name": "fractional/orthogonal/e->point" | |
| 7920 | + }, | |
| 7921 | + { | |
| 7922 | + "connector": { | |
| 7923 | + "color": "base-content", | |
| 7924 | + "dash": "solid", | |
| 7925 | + "end_gap": 6, | |
| 7926 | + "from": { | |
| 7927 | + "_other": "b", | |
| 7928 | + "anchor": "auto", | |
| 7929 | + "element": "a" | |
| 7930 | + }, | |
| 7931 | + "id": "k", | |
| 7932 | + "marker_end": "arrow", | |
| 7933 | + "marker_start": "none", | |
| 7934 | + "routing": "orthogonal", | |
| 7935 | + "start_gap": 6, | |
| 7936 | + "stroke_width": 2, | |
| 7937 | + "to": { | |
| 7938 | + "_other": "a", | |
| 7939 | + "anchor": "auto", | |
| 7940 | + "element": "b" | |
| 7941 | + } | |
| 7942 | + }, | |
| 7943 | + "document": "fractional", | |
| 7944 | + "expected": { | |
| 7945 | + "d": "M 40.5 10.75 L 67.92 10.75 L 67.92 11 L 95.33 11", | |
| 7946 | + "dash_array": null, | |
| 7947 | + "marker_end": "arrow", | |
| 7948 | + "marker_start": "none", | |
| 7949 | + "stroke_width": 2 | |
| 7950 | + }, | |
| 7951 | + "name": "fractional/orthogonal/auto->auto" | |
| 7952 | + }, | |
| 7953 | + { | |
| 7954 | + "connector": { | |
| 7955 | + "color": "base-content", | |
| 7956 | + "dash": "solid", | |
| 7957 | + "end_gap": 6, | |
| 7958 | + "from": { | |
| 7959 | + "_other": "b", | |
| 7960 | + "anchor": "auto", | |
| 7961 | + "element": "a" | |
| 7962 | + }, | |
| 7963 | + "id": "k", | |
| 7964 | + "marker_end": "arrow", | |
| 7965 | + "marker_start": "none", | |
| 7966 | + "routing": "orthogonal", | |
| 7967 | + "start_gap": 6, | |
| 7968 | + "stroke_width": 2, | |
| 7969 | + "to": { | |
| 7970 | + "anchor": "w", | |
| 7971 | + "element": "b" | |
| 7972 | + } | |
| 7973 | + }, | |
| 7974 | + "document": "fractional", | |
| 7975 | + "expected": { | |
| 7976 | + "d": "M 40.5 10.75 L 67.92 10.75 L 67.92 11 L 95.33 11", | |
| 7977 | + "dash_array": null, | |
| 7978 | + "marker_end": "arrow", | |
| 7979 | + "marker_start": "none", | |
| 7980 | + "stroke_width": 2 | |
| 7981 | + }, | |
| 7982 | + "name": "fractional/orthogonal/auto->w" | |
| 7983 | + }, | |
| 7984 | + { | |
| 7985 | + "connector": { | |
| 7986 | + "color": "base-content", | |
| 7987 | + "dash": "solid", | |
| 7988 | + "end_gap": 6, | |
| 7989 | + "from": { | |
| 7990 | + "anchor": "n", | |
| 7991 | + "element": "a" | |
| 7992 | + }, | |
| 7993 | + "id": "k", | |
| 7994 | + "marker_end": "arrow", | |
| 7995 | + "marker_start": "none", | |
| 7996 | + "routing": "orthogonal", | |
| 7997 | + "start_gap": 6, | |
| 7998 | + "stroke_width": 2, | |
| 7999 | + "to": { | |
| 8000 | + "_other": "a", | |
| 8001 | + "anchor": "auto", | |
| 8002 | + "element": "b" | |
| 8003 | + } | |
| 8004 | + }, | |
| 8005 | + "document": "fractional", | |
| 8006 | + "expected": { | |
| 8007 | + "d": "M 18 -3.75 L 18 3.63 L 95.33 3.63 L 95.33 11", | |
| 8008 | + "dash_array": null, | |
| 8009 | + "marker_end": "arrow", | |
| 8010 | + "marker_start": "none", | |
| 8011 | + "stroke_width": 2 | |
| 8012 | + }, | |
| 8013 | + "name": "fractional/orthogonal/n->auto" | |
| 7006 | 8014 | }, |
| 7007 | 8015 | { |
| 7008 | 8016 | "connector": { |
@@ -7010,7 +8018,8 @@
| 7010 | 8018 | "dash": "solid", |
| 7011 | 8019 | "end_gap": 6, |
| 7012 | 8020 | "from": { |
| 7013 | − "anchor": "e", | |
| 8021 | + "_other": "ghost", | |
| 8022 | + "anchor": "auto", | |
| 7014 | 8023 | "element": "a" |
| 7015 | 8024 | }, |
| 7016 | 8025 | "id": "k", |
@@ -7020,21 +8029,20 @@
| 7020 | 8029 | "start_gap": 6, |
| 7021 | 8030 | "stroke_width": 2, |
| 7022 | 8031 | "to": { |
| 7023 | − "anchor": "point", | |
| 7024 | − "element": "b", | |
| 7025 | − "x": 400, | |
| 7026 | − "y": 333.75 | |
| 8032 | + "_other": "a", | |
| 8033 | + "anchor": "auto", | |
| 8034 | + "element": "b" | |
| 7027 | 8035 | } |
| 7028 | 8036 | }, |
| 7029 | 8037 | "document": "fractional", |
| 7030 | 8038 | "expected": { |
| 7031 | − "d": "M 40.5 10.75 L 220.25 10.75 L 220.25 333.75 L 400 333.75", | |
| 8039 | + "d": "M 40.5 10.75 L 67.92 10.75 L 67.92 11 L 95.33 11", | |
| 7032 | 8040 | "dash_array": null, |
| 7033 | 8041 | "marker_end": "arrow", |
| 7034 | 8042 | "marker_start": "none", |
| 7035 | 8043 | "stroke_width": 2 |
| 7036 | 8044 | }, |
| 7037 | − "name": "fractional/orthogonal/e->point" | |
| 8045 | + "name": "fractional/orthogonal/auto->auto" | |
| 7038 | 8046 | }, |
| 7039 | 8047 | { |
| 7040 | 8048 | "connector": { |
@@ -7850,6 +8858,132 @@
| 7850 | 8858 | }, |
| 7851 | 8859 | "name": "fractional/curve/e->point" |
| 7852 | 8860 | }, |
| 8861 | + { | |
| 8862 | + "connector": { | |
| 8863 | + "color": "base-content", | |
| 8864 | + "dash": "solid", | |
| 8865 | + "end_gap": 6, | |
| 8866 | + "from": { | |
| 8867 | + "_other": "b", | |
| 8868 | + "anchor": "auto", | |
| 8869 | + "element": "a" | |
| 8870 | + }, | |
| 8871 | + "id": "k", | |
| 8872 | + "marker_end": "arrow", | |
| 8873 | + "marker_start": "none", | |
| 8874 | + "routing": "curve", | |
| 8875 | + "start_gap": 6, | |
| 8876 | + "stroke_width": 2, | |
| 8877 | + "to": { | |
| 8878 | + "_other": "a", | |
| 8879 | + "anchor": "auto", | |
| 8880 | + "element": "b" | |
| 8881 | + } | |
| 8882 | + }, | |
| 8883 | + "document": "fractional", | |
| 8884 | + "expected": { | |
| 8885 | + "d": "M 40.5 10.75 C 62.43 10.75, 73.4 11, 95.33 11", | |
| 8886 | + "dash_array": null, | |
| 8887 | + "marker_end": "arrow", | |
| 8888 | + "marker_start": "none", | |
| 8889 | + "stroke_width": 2 | |
| 8890 | + }, | |
| 8891 | + "name": "fractional/curve/auto->auto" | |
| 8892 | + }, | |
| 8893 | + { | |
| 8894 | + "connector": { | |
| 8895 | + "color": "base-content", | |
| 8896 | + "dash": "solid", | |
| 8897 | + "end_gap": 6, | |
| 8898 | + "from": { | |
| 8899 | + "_other": "b", | |
| 8900 | + "anchor": "auto", | |
| 8901 | + "element": "a" | |
| 8902 | + }, | |
| 8903 | + "id": "k", | |
| 8904 | + "marker_end": "arrow", | |
| 8905 | + "marker_start": "none", | |
| 8906 | + "routing": "curve", | |
| 8907 | + "start_gap": 6, | |
| 8908 | + "stroke_width": 2, | |
| 8909 | + "to": { | |
| 8910 | + "anchor": "w", | |
| 8911 | + "element": "b" | |
| 8912 | + } | |
| 8913 | + }, | |
| 8914 | + "document": "fractional", | |
| 8915 | + "expected": { | |
| 8916 | + "d": "M 40.5 10.75 C 62.43 10.75, 73.4 11, 95.33 11", | |
| 8917 | + "dash_array": null, | |
| 8918 | + "marker_end": "arrow", | |
| 8919 | + "marker_start": "none", | |
| 8920 | + "stroke_width": 2 | |
| 8921 | + }, | |
| 8922 | + "name": "fractional/curve/auto->w" | |
| 8923 | + }, | |
| 8924 | + { | |
| 8925 | + "connector": { | |
| 8926 | + "color": "base-content", | |
| 8927 | + "dash": "solid", | |
| 8928 | + "end_gap": 6, | |
| 8929 | + "from": { | |
| 8930 | + "anchor": "n", | |
| 8931 | + "element": "a" | |
| 8932 | + }, | |
| 8933 | + "id": "k", | |
| 8934 | + "marker_end": "arrow", | |
| 8935 | + "marker_start": "none", | |
| 8936 | + "routing": "curve", | |
| 8937 | + "start_gap": 6, | |
| 8938 | + "stroke_width": 2, | |
| 8939 | + "to": { | |
| 8940 | + "_other": "a", | |
| 8941 | + "anchor": "auto", | |
| 8942 | + "element": "b" | |
| 8943 | + } | |
| 8944 | + }, | |
| 8945 | + "document": "fractional", | |
| 8946 | + "expected": { | |
| 8947 | + "d": "M 18 -3.75 C 18 -35.24, 63.84 11, 95.33 11", | |
| 8948 | + "dash_array": null, | |
| 8949 | + "marker_end": "arrow", | |
| 8950 | + "marker_start": "none", | |
| 8951 | + "stroke_width": 2 | |
| 8952 | + }, | |
| 8953 | + "name": "fractional/curve/n->auto" | |
| 8954 | + }, | |
| 8955 | + { | |
| 8956 | + "connector": { | |
| 8957 | + "color": "base-content", | |
| 8958 | + "dash": "solid", | |
| 8959 | + "end_gap": 6, | |
| 8960 | + "from": { | |
| 8961 | + "_other": "ghost", | |
| 8962 | + "anchor": "auto", | |
| 8963 | + "element": "a" | |
| 8964 | + }, | |
| 8965 | + "id": "k", | |
| 8966 | + "marker_end": "arrow", | |
| 8967 | + "marker_start": "none", | |
| 8968 | + "routing": "curve", | |
| 8969 | + "start_gap": 6, | |
| 8970 | + "stroke_width": 2, | |
| 8971 | + "to": { | |
| 8972 | + "_other": "a", | |
| 8973 | + "anchor": "auto", | |
| 8974 | + "element": "b" | |
| 8975 | + } | |
| 8976 | + }, | |
| 8977 | + "document": "fractional", | |
| 8978 | + "expected": { | |
| 8979 | + "d": "M 40.5 10.75 C 62.43 10.75, 73.4 11, 95.33 11", | |
| 8980 | + "dash_array": null, | |
| 8981 | + "marker_end": "arrow", | |
| 8982 | + "marker_start": "none", | |
| 8983 | + "stroke_width": 2 | |
| 8984 | + }, | |
| 8985 | + "name": "fractional/curve/auto->auto" | |
| 8986 | + }, | |
| 7853 | 8987 | { |
| 7854 | 8988 | "connector": { |
| 7855 | 8989 | "color": "base-content", |
@@ -8886,13 +10020,139 @@
| 8886 | 10020 | }, |
| 8887 | 10021 | "document": "overlapping", |
| 8888 | 10022 | "expected": { |
| 8889 | − "d": "M 12.5 -3 L 126 70", | |
| 10023 | + "d": "M 12.5 -3 L 126 70", | |
| 10024 | + "dash_array": null, | |
| 10025 | + "marker_end": "arrow", | |
| 10026 | + "marker_start": "none", | |
| 10027 | + "stroke_width": 2 | |
| 10028 | + }, | |
| 10029 | + "name": "overlapping/straight/point->e" | |
| 10030 | + }, | |
| 10031 | + { | |
| 10032 | + "connector": { | |
| 10033 | + "color": "base-content", | |
| 10034 | + "dash": "solid", | |
| 10035 | + "end_gap": 6, | |
| 10036 | + "from": { | |
| 10037 | + "anchor": "e", | |
| 10038 | + "element": "a" | |
| 10039 | + }, | |
| 10040 | + "id": "k", | |
| 10041 | + "marker_end": "arrow", | |
| 10042 | + "marker_start": "none", | |
| 10043 | + "routing": "straight", | |
| 10044 | + "start_gap": 6, | |
| 10045 | + "stroke_width": 2, | |
| 10046 | + "to": { | |
| 10047 | + "anchor": "point", | |
| 10048 | + "element": "b", | |
| 10049 | + "x": 400, | |
| 10050 | + "y": 333.75 | |
| 10051 | + } | |
| 10052 | + }, | |
| 10053 | + "document": "overlapping", | |
| 10054 | + "expected": { | |
| 10055 | + "d": "M 106 50 L 400 333.75", | |
| 10056 | + "dash_array": null, | |
| 10057 | + "marker_end": "arrow", | |
| 10058 | + "marker_start": "none", | |
| 10059 | + "stroke_width": 2 | |
| 10060 | + }, | |
| 10061 | + "name": "overlapping/straight/e->point" | |
| 10062 | + }, | |
| 10063 | + { | |
| 10064 | + "connector": { | |
| 10065 | + "color": "base-content", | |
| 10066 | + "dash": "solid", | |
| 10067 | + "end_gap": 6, | |
| 10068 | + "from": { | |
| 10069 | + "_other": "b", | |
| 10070 | + "anchor": "auto", | |
| 10071 | + "element": "a" | |
| 10072 | + }, | |
| 10073 | + "id": "k", | |
| 10074 | + "marker_end": "arrow", | |
| 10075 | + "marker_start": "none", | |
| 10076 | + "routing": "straight", | |
| 10077 | + "start_gap": 6, | |
| 10078 | + "stroke_width": 2, | |
| 10079 | + "to": { | |
| 10080 | + "_other": "a", | |
| 10081 | + "anchor": "auto", | |
| 10082 | + "element": "b" | |
| 10083 | + } | |
| 10084 | + }, | |
| 10085 | + "document": "overlapping", | |
| 10086 | + "expected": { | |
| 10087 | + "d": "M 106 50 L 14 70", | |
| 10088 | + "dash_array": null, | |
| 10089 | + "marker_end": "arrow", | |
| 10090 | + "marker_start": "none", | |
| 10091 | + "stroke_width": 2 | |
| 10092 | + }, | |
| 10093 | + "name": "overlapping/straight/auto->auto" | |
| 10094 | + }, | |
| 10095 | + { | |
| 10096 | + "connector": { | |
| 10097 | + "color": "base-content", | |
| 10098 | + "dash": "solid", | |
| 10099 | + "end_gap": 6, | |
| 10100 | + "from": { | |
| 10101 | + "_other": "b", | |
| 10102 | + "anchor": "auto", | |
| 10103 | + "element": "a" | |
| 10104 | + }, | |
| 10105 | + "id": "k", | |
| 10106 | + "marker_end": "arrow", | |
| 10107 | + "marker_start": "none", | |
| 10108 | + "routing": "straight", | |
| 10109 | + "start_gap": 6, | |
| 10110 | + "stroke_width": 2, | |
| 10111 | + "to": { | |
| 10112 | + "anchor": "w", | |
| 10113 | + "element": "b" | |
| 10114 | + } | |
| 10115 | + }, | |
| 10116 | + "document": "overlapping", | |
| 10117 | + "expected": { | |
| 10118 | + "d": "M 106 50 L 14 70", | |
| 10119 | + "dash_array": null, | |
| 10120 | + "marker_end": "arrow", | |
| 10121 | + "marker_start": "none", | |
| 10122 | + "stroke_width": 2 | |
| 10123 | + }, | |
| 10124 | + "name": "overlapping/straight/auto->w" | |
| 10125 | + }, | |
| 10126 | + { | |
| 10127 | + "connector": { | |
| 10128 | + "color": "base-content", | |
| 10129 | + "dash": "solid", | |
| 10130 | + "end_gap": 6, | |
| 10131 | + "from": { | |
| 10132 | + "anchor": "n", | |
| 10133 | + "element": "a" | |
| 10134 | + }, | |
| 10135 | + "id": "k", | |
| 10136 | + "marker_end": "arrow", | |
| 10137 | + "marker_start": "none", | |
| 10138 | + "routing": "straight", | |
| 10139 | + "start_gap": 6, | |
| 10140 | + "stroke_width": 2, | |
| 10141 | + "to": { | |
| 10142 | + "_other": "a", | |
| 10143 | + "anchor": "auto", | |
| 10144 | + "element": "b" | |
| 10145 | + } | |
| 10146 | + }, | |
| 10147 | + "document": "overlapping", | |
| 10148 | + "expected": { | |
| 10149 | + "d": "M 50 -6 L 14 70", | |
| 8890 | 10150 | "dash_array": null, |
| 8891 | 10151 | "marker_end": "arrow", |
| 8892 | 10152 | "marker_start": "none", |
| 8893 | 10153 | "stroke_width": 2 |
| 8894 | 10154 | }, |
| 8895 | − "name": "overlapping/straight/point->e" | |
| 10155 | + "name": "overlapping/straight/n->auto" | |
| 8896 | 10156 | }, |
| 8897 | 10157 | { |
| 8898 | 10158 | "connector": { |
@@ -8900,7 +10160,8 @@
| 8900 | 10160 | "dash": "solid", |
| 8901 | 10161 | "end_gap": 6, |
| 8902 | 10162 | "from": { |
| 8903 | − "anchor": "e", | |
| 10163 | + "_other": "ghost", | |
| 10164 | + "anchor": "auto", | |
| 8904 | 10165 | "element": "a" |
| 8905 | 10166 | }, |
| 8906 | 10167 | "id": "k", |
@@ -8910,21 +10171,20 @@
| 8910 | 10171 | "start_gap": 6, |
| 8911 | 10172 | "stroke_width": 2, |
| 8912 | 10173 | "to": { |
| 8913 | − "anchor": "point", | |
| 8914 | − "element": "b", | |
| 8915 | − "x": 400, | |
| 8916 | − "y": 333.75 | |
| 10174 | + "_other": "a", | |
| 10175 | + "anchor": "auto", | |
| 10176 | + "element": "b" | |
| 8917 | 10177 | } |
| 8918 | 10178 | }, |
| 8919 | 10179 | "document": "overlapping", |
| 8920 | 10180 | "expected": { |
| 8921 | − "d": "M 106 50 L 400 333.75", | |
| 10181 | + "d": "M 106 50 L 14 70", | |
| 8922 | 10182 | "dash_array": null, |
| 8923 | 10183 | "marker_end": "arrow", |
| 8924 | 10184 | "marker_start": "none", |
| 8925 | 10185 | "stroke_width": 2 |
| 8926 | 10186 | }, |
| 8927 | − "name": "overlapping/straight/e->point" | |
| 10187 | + "name": "overlapping/straight/auto->auto" | |
| 8928 | 10188 | }, |
| 8929 | 10189 | { |
| 8930 | 10190 | "connector": { |
@@ -9740,6 +11000,132 @@
| 9740 | 11000 | }, |
| 9741 | 11001 | "name": "overlapping/orthogonal/e->point" |
| 9742 | 11002 | }, |
| 11003 | + { | |
| 11004 | + "connector": { | |
| 11005 | + "color": "base-content", | |
| 11006 | + "dash": "solid", | |
| 11007 | + "end_gap": 6, | |
| 11008 | + "from": { | |
| 11009 | + "_other": "b", | |
| 11010 | + "anchor": "auto", | |
| 11011 | + "element": "a" | |
| 11012 | + }, | |
| 11013 | + "id": "k", | |
| 11014 | + "marker_end": "arrow", | |
| 11015 | + "marker_start": "none", | |
| 11016 | + "routing": "orthogonal", | |
| 11017 | + "start_gap": 6, | |
| 11018 | + "stroke_width": 2, | |
| 11019 | + "to": { | |
| 11020 | + "_other": "a", | |
| 11021 | + "anchor": "auto", | |
| 11022 | + "element": "b" | |
| 11023 | + } | |
| 11024 | + }, | |
| 11025 | + "document": "overlapping", | |
| 11026 | + "expected": { | |
| 11027 | + "d": "M 106 50 L 60 50 L 60 70 L 14 70", | |
| 11028 | + "dash_array": null, | |
| 11029 | + "marker_end": "arrow", | |
| 11030 | + "marker_start": "none", | |
| 11031 | + "stroke_width": 2 | |
| 11032 | + }, | |
| 11033 | + "name": "overlapping/orthogonal/auto->auto" | |
| 11034 | + }, | |
| 11035 | + { | |
| 11036 | + "connector": { | |
| 11037 | + "color": "base-content", | |
| 11038 | + "dash": "solid", | |
| 11039 | + "end_gap": 6, | |
| 11040 | + "from": { | |
| 11041 | + "_other": "b", | |
| 11042 | + "anchor": "auto", | |
| 11043 | + "element": "a" | |
| 11044 | + }, | |
| 11045 | + "id": "k", | |
| 11046 | + "marker_end": "arrow", | |
| 11047 | + "marker_start": "none", | |
| 11048 | + "routing": "orthogonal", | |
| 11049 | + "start_gap": 6, | |
| 11050 | + "stroke_width": 2, | |
| 11051 | + "to": { | |
| 11052 | + "anchor": "w", | |
| 11053 | + "element": "b" | |
| 11054 | + } | |
| 11055 | + }, | |
| 11056 | + "document": "overlapping", | |
| 11057 | + "expected": { | |
| 11058 | + "d": "M 106 50 L 60 50 L 60 70 L 14 70", | |
| 11059 | + "dash_array": null, | |
| 11060 | + "marker_end": "arrow", | |
| 11061 | + "marker_start": "none", | |
| 11062 | + "stroke_width": 2 | |
| 11063 | + }, | |
| 11064 | + "name": "overlapping/orthogonal/auto->w" | |
| 11065 | + }, | |
| 11066 | + { | |
| 11067 | + "connector": { | |
| 11068 | + "color": "base-content", | |
| 11069 | + "dash": "solid", | |
| 11070 | + "end_gap": 6, | |
| 11071 | + "from": { | |
| 11072 | + "anchor": "n", | |
| 11073 | + "element": "a" | |
| 11074 | + }, | |
| 11075 | + "id": "k", | |
| 11076 | + "marker_end": "arrow", | |
| 11077 | + "marker_start": "none", | |
| 11078 | + "routing": "orthogonal", | |
| 11079 | + "start_gap": 6, | |
| 11080 | + "stroke_width": 2, | |
| 11081 | + "to": { | |
| 11082 | + "_other": "a", | |
| 11083 | + "anchor": "auto", | |
| 11084 | + "element": "b" | |
| 11085 | + } | |
| 11086 | + }, | |
| 11087 | + "document": "overlapping", | |
| 11088 | + "expected": { | |
| 11089 | + "d": "M 50 -6 L 50 32 L 14 32 L 14 70", | |
| 11090 | + "dash_array": null, | |
| 11091 | + "marker_end": "arrow", | |
| 11092 | + "marker_start": "none", | |
| 11093 | + "stroke_width": 2 | |
| 11094 | + }, | |
| 11095 | + "name": "overlapping/orthogonal/n->auto" | |
| 11096 | + }, | |
| 11097 | + { | |
| 11098 | + "connector": { | |
| 11099 | + "color": "base-content", | |
| 11100 | + "dash": "solid", | |
| 11101 | + "end_gap": 6, | |
| 11102 | + "from": { | |
| 11103 | + "_other": "ghost", | |
| 11104 | + "anchor": "auto", | |
| 11105 | + "element": "a" | |
| 11106 | + }, | |
| 11107 | + "id": "k", | |
| 11108 | + "marker_end": "arrow", | |
| 11109 | + "marker_start": "none", | |
| 11110 | + "routing": "orthogonal", | |
| 11111 | + "start_gap": 6, | |
| 11112 | + "stroke_width": 2, | |
| 11113 | + "to": { | |
| 11114 | + "_other": "a", | |
| 11115 | + "anchor": "auto", | |
| 11116 | + "element": "b" | |
| 11117 | + } | |
| 11118 | + }, | |
| 11119 | + "document": "overlapping", | |
| 11120 | + "expected": { | |
| 11121 | + "d": "M 106 50 L 60 50 L 60 70 L 14 70", | |
| 11122 | + "dash_array": null, | |
| 11123 | + "marker_end": "arrow", | |
| 11124 | + "marker_start": "none", | |
| 11125 | + "stroke_width": 2 | |
| 11126 | + }, | |
| 11127 | + "name": "overlapping/orthogonal/auto->auto" | |
| 11128 | + }, | |
| 9743 | 11129 | { |
| 9744 | 11130 | "connector": { |
| 9745 | 11131 | "color": "base-content", |
@@ -10554,6 +11940,132 @@
| 10554 | 11940 | }, |
| 10555 | 11941 | "name": "overlapping/curve/e->point" |
| 10556 | 11942 | }, |
| 11943 | + { | |
| 11944 | + "connector": { | |
| 11945 | + "color": "base-content", | |
| 11946 | + "dash": "solid", | |
| 11947 | + "end_gap": 6, | |
| 11948 | + "from": { | |
| 11949 | + "_other": "b", | |
| 11950 | + "anchor": "auto", | |
| 11951 | + "element": "a" | |
| 11952 | + }, | |
| 11953 | + "id": "k", | |
| 11954 | + "marker_end": "arrow", | |
| 11955 | + "marker_start": "none", | |
| 11956 | + "routing": "curve", | |
| 11957 | + "start_gap": 6, | |
| 11958 | + "stroke_width": 2, | |
| 11959 | + "to": { | |
| 11960 | + "_other": "a", | |
| 11961 | + "anchor": "auto", | |
| 11962 | + "element": "b" | |
| 11963 | + } | |
| 11964 | + }, | |
| 11965 | + "document": "overlapping", | |
| 11966 | + "expected": { | |
| 11967 | + "d": "M 106 50 C 143.66 50, -23.66 70, 14 70", | |
| 11968 | + "dash_array": null, | |
| 11969 | + "marker_end": "arrow", | |
| 11970 | + "marker_start": "none", | |
| 11971 | + "stroke_width": 2 | |
| 11972 | + }, | |
| 11973 | + "name": "overlapping/curve/auto->auto" | |
| 11974 | + }, | |
| 11975 | + { | |
| 11976 | + "connector": { | |
| 11977 | + "color": "base-content", | |
| 11978 | + "dash": "solid", | |
| 11979 | + "end_gap": 6, | |
| 11980 | + "from": { | |
| 11981 | + "_other": "b", | |
| 11982 | + "anchor": "auto", | |
| 11983 | + "element": "a" | |
| 11984 | + }, | |
| 11985 | + "id": "k", | |
| 11986 | + "marker_end": "arrow", | |
| 11987 | + "marker_start": "none", | |
| 11988 | + "routing": "curve", | |
| 11989 | + "start_gap": 6, | |
| 11990 | + "stroke_width": 2, | |
| 11991 | + "to": { | |
| 11992 | + "anchor": "w", | |
| 11993 | + "element": "b" | |
| 11994 | + } | |
| 11995 | + }, | |
| 11996 | + "document": "overlapping", | |
| 11997 | + "expected": { | |
| 11998 | + "d": "M 106 50 C 143.66 50, -23.66 70, 14 70", | |
| 11999 | + "dash_array": null, | |
| 12000 | + "marker_end": "arrow", | |
| 12001 | + "marker_start": "none", | |
| 12002 | + "stroke_width": 2 | |
| 12003 | + }, | |
| 12004 | + "name": "overlapping/curve/auto->w" | |
| 12005 | + }, | |
| 12006 | + { | |
| 12007 | + "connector": { | |
| 12008 | + "color": "base-content", | |
| 12009 | + "dash": "solid", | |
| 12010 | + "end_gap": 6, | |
| 12011 | + "from": { | |
| 12012 | + "anchor": "n", | |
| 12013 | + "element": "a" | |
| 12014 | + }, | |
| 12015 | + "id": "k", | |
| 12016 | + "marker_end": "arrow", | |
| 12017 | + "marker_start": "none", | |
| 12018 | + "routing": "curve", | |
| 12019 | + "start_gap": 6, | |
| 12020 | + "stroke_width": 2, | |
| 12021 | + "to": { | |
| 12022 | + "_other": "a", | |
| 12023 | + "anchor": "auto", | |
| 12024 | + "element": "b" | |
| 12025 | + } | |
| 12026 | + }, | |
| 12027 | + "document": "overlapping", | |
| 12028 | + "expected": { | |
| 12029 | + "d": "M 50 -6 C 50 -39.64, -19.64 70, 14 70", | |
| 12030 | + "dash_array": null, | |
| 12031 | + "marker_end": "arrow", | |
| 12032 | + "marker_start": "none", | |
| 12033 | + "stroke_width": 2 | |
| 12034 | + }, | |
| 12035 | + "name": "overlapping/curve/n->auto" | |
| 12036 | + }, | |
| 12037 | + { | |
| 12038 | + "connector": { | |
| 12039 | + "color": "base-content", | |
| 12040 | + "dash": "solid", | |
| 12041 | + "end_gap": 6, | |
| 12042 | + "from": { | |
| 12043 | + "_other": "ghost", | |
| 12044 | + "anchor": "auto", | |
| 12045 | + "element": "a" | |
| 12046 | + }, | |
| 12047 | + "id": "k", | |
| 12048 | + "marker_end": "arrow", | |
| 12049 | + "marker_start": "none", | |
| 12050 | + "routing": "curve", | |
| 12051 | + "start_gap": 6, | |
| 12052 | + "stroke_width": 2, | |
| 12053 | + "to": { | |
| 12054 | + "_other": "a", | |
| 12055 | + "anchor": "auto", | |
| 12056 | + "element": "b" | |
| 12057 | + } | |
| 12058 | + }, | |
| 12059 | + "document": "overlapping", | |
| 12060 | + "expected": { | |
| 12061 | + "d": "M 106 50 C 143.66 50, -23.66 70, 14 70", | |
| 12062 | + "dash_array": null, | |
| 12063 | + "marker_end": "arrow", | |
| 12064 | + "marker_start": "none", | |
| 12065 | + "stroke_width": 2 | |
| 12066 | + }, | |
| 12067 | + "name": "overlapping/curve/auto->auto" | |
| 12068 | + }, | |
| 10557 | 12069 | { |
| 10558 | 12070 | "connector": { |
| 10559 | 12071 | "color": "base-content", |
modified
docs/android.md
+21
−21
@@ -540,6 +540,10 @@ document parser and viewport maths. **Not verified:** the drawing itself
| 540 | 540 | a diagram on a screen yet. Worth doing before phase 4 puts edits behind |
| 541 | 541 | it. |
| 542 | 542 | |
| 543 | +The `auto`-anchor behaviour described in section 9 was found the hard | |
| 544 | +way after this phase shipped: connectors drew to the wrong edge on the | |
| 545 | +phone until the port learned to read `_other`. | |
| 546 | + | |
| 543 | 547 | Number formatting was the subtle part of the port. Erlang's |
| 544 | 548 | `float_to_binary(n, [:compact, decimals: 2])` prints 1.999 as `"2.0"`, |
| 545 | 549 | not `"2"`, while a genuinely whole value prints as `"30"` — so the Kotlin |
@@ -603,27 +607,23 @@ action that is always refused is worse than no button at all.
| 603 | 607 | |
| 604 | 608 | ## 9. Open questions |
| 605 | 609 | |
| 606 | −- **`auto` anchors always leave the east edge.** `ConnectorGeometry` | |
| 607 | − documents `auto` as picking the cardinal edge whose direction best | |
| 608 | − aligns with the line between the two bounding boxes, and `best_edge/2` | |
| 609 | − implements exactly that — but the `"_other"` key it reads is never | |
| 610 | − written by anything in `lib/`. So `other_center/2` returns nil, | |
| 611 | − `best_edge` compares an element's centre against itself, and the | |
| 612 | − `abs(dx) >= abs(dy) and dx >= 0` branch wins every time. Two shapes | |
| 613 | − side by side get a connector out of both east edges, whatever their | |
| 614 | − relative positions. | |
| 615 | − | |
| 616 | − The fix is small — `render/2` has both endpoints, so it can pass each | |
| 617 | − anchor the *other* endpoint's element id — but it changes how existing | |
| 618 | − diagrams look on the web, so it is a decision rather than a cleanup. | |
| 619 | − The Kotlin port reproduces today's behaviour deliberately, and the | |
| 620 | − fixtures encode it; regenerate them after any fix and the port is held | |
| 621 | − honest either way. | |
| 622 | − | |
| 623 | −- **Text rendering fidelity.** The web renders text elements as SVG/HTML | |
| 624 | − with daisyUI typography; Compose text metrics will not match exactly. | |
| 625 | − Wrapping and vertical centering are the likely visible differences. Do we | |
| 626 | − accept "close", or does the server need to hand back measured line boxes? | |
| 610 | +- ~~**`auto` anchors always leave the east edge.**~~ Wrong, and worth | |
| 611 | + recording as a caution. `ConnectorGeometry` resolves an `auto` anchor | |
| 612 | + against `anchor["_other"]`, and grepping `lib/` for `"_other"` finds | |
| 613 | + only the read — so it looked like a dead code path with the east edge | |
| 614 | + as its permanent fallback. The write is in the editor's colocated | |
| 615 | + JavaScript (`canvas_live/edit.ex`), which stamps each anchor with the | |
| 616 | + *opposite* endpoint's id as the connector is created, and the quoted | |
| 617 | + grep pattern missed the JS object key. | |
| 618 | + | |
| 619 | + The port ignored `_other` on the strength of that reading, so every | |
| 620 | + connector on the phone left its shape's east edge while the web routed | |
| 621 | + to the nearest one. The fixtures agreed with the port because the | |
| 622 | + generator never set `_other` either — a matrix that only covers the | |
| 623 | + fallback proves only that both sides fall back the same way. Fixed, | |
| 624 | + and the matrix now carries the hint, including one pointing at a | |
| 625 | + deleted element. | |
| 626 | + | |
| 627 | 627 | - **Anchor validation is looser than it looks.** `Document.valid_anchor?/1` |
| 628 | 628 | ends in a catch-all `%{}` clause, so any map-shaped anchor is accepted — |
| 629 | 629 | an unrecognised name like `"northwest"`, or `"point"` with no |
modified
lib/mix/tasks/diogramos.android_fixtures.ex
+11
−1
@@ -119,7 +119,17 @@ defmodule Mix.Tasks.Diogramos.AndroidFixtures do
| 119 | 119 | |
| 120 | 120 | point_pairs = [ |
| 121 | 121 | {%{"anchor" => "point", "x" => 12.5, "y" => -3}, %{"anchor" => "e"}}, |
| 122 | − {%{"anchor" => "e"}, %{"anchor" => "point", "x" => 400, "y" => 333.75}} | |
| 122 | + {%{"anchor" => "e"}, %{"anchor" => "point", "x" => 400, "y" => 333.75}}, | |
| 123 | + # What the editor actually stores: the JS that creates a connector | |
| 124 | + # writes `_other` into each anchor naming the opposite endpoint | |
| 125 | + # (canvas_live/edit.ex), and that is what makes "auto" pick the | |
| 126 | + # nearest edge instead of always leaving east. Without these cases | |
| 127 | + # the matrix only ever exercised the fallback. | |
| 128 | + {%{"anchor" => "auto", "_other" => "b"}, %{"anchor" => "auto", "_other" => "a"}}, | |
| 129 | + {%{"anchor" => "auto", "_other" => "b"}, %{"anchor" => "w"}}, | |
| 130 | + {%{"anchor" => "n"}, %{"anchor" => "auto", "_other" => "a"}}, | |
| 131 | + # A hint pointing at an element that no longer exists falls back. | |
| 132 | + {%{"anchor" => "auto", "_other" => "ghost"}, %{"anchor" => "auto", "_other" => "a"}} | |
| 123 | 133 | ] |
| 124 | 134 | |
| 125 | 135 | base = |
Parents: b453bb1