From 1740300a2afdb0fcb7abd3aeebca189611a7f1ad Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sat, 7 Mar 2026 15:50:29 -0700 Subject: [PATCH 1/6] v0.22.1 - text invert fixes --- examples/siwin_text.nim | 70 ++++++++++++++++++++++++++++++--- examples/windy_text.nim | 73 ++++++++++++++++++++++++++++++++--- src/figdraw/figrender.nim | 25 +++++++----- tests/trender_text_invert.nim | 5 ++- 4 files changed, 152 insertions(+), 21 deletions(-) diff --git a/examples/siwin_text.nim b/examples/siwin_text.nim index 4498fcb..3f018b7 100644 --- a/examples/siwin_text.nim +++ b/examples/siwin_text.nim @@ -233,13 +233,36 @@ proc makeRenderTree*( if rune == Rune(10): monoLines.inc let monoHeight = monoLines.float32 * monoLineHeight + monoPad * 2 - - let textRect = - rect(innerRect.x, innerRect.y, innerRect.w, innerRect.h - monoHeight - 12'f32) - let monoRect = - rect(innerRect.x, textRect.y + textRect.h + 12'f32, innerRect.w, monoHeight) + let invertedLineHeight = uiFont.size * 1.4'f32 + let sectionGap = 20.0'f32 + + let textRect = rect( + innerRect.x, + innerRect.y, + innerRect.w, + innerRect.h - monoHeight - invertedLineHeight - sectionGap * 2.0'f32, + ) + let invertedTextRect = rect( + innerRect.x, textRect.y + textRect.h + sectionGap, innerRect.w, invertedLineHeight + ) + let monoRect = rect( + innerRect.x, + invertedTextRect.y + invertedTextRect.h + sectionGap, + innerRect.w, + monoHeight, + ) let (layout, highlightRange) = buildBodyTextLayout(uiFont, textRect, modeLine) + let invertedText = "Inverted text line (NfInvertY) with selection" + let invertedSelectionRange = findPhraseRange(invertedText, "NfInvertY") + let invertedLayout = typeset( + rect(0, 0, invertedTextRect.w, invertedTextRect.h), + [span(uiFont, rgba(30, 30, 30, 255), invertedText)], + hAlign = Left, + vAlign = Top, + minContent = false, + wrap = false, + ) discard result.addChild( z, @@ -273,6 +296,43 @@ proc makeRenderTree*( corners: [10.0'f32, 10.0, 10.0, 10.0], ), ) + + let invertedGlyphBounds = rect( + invertedTextRect.x + invertedLayout.bounding.x, + invertedTextRect.y + invertedLayout.bounding.y, + invertedLayout.bounding.w, + invertedLayout.bounding.h, + ) + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkRectangle, + childCount: 0, + zlevel: z, + screenBox: invertedGlyphBounds, + fill: clearColor, + stroke: RenderStroke(weight: 1.5, fill: rgba(38, 38, 38, 155).color), + corners: [4.0'f32, 4.0, 4.0, 4.0], + ), + ) + + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkText, + childCount: 0, + zlevel: z, + # flags: {NfInvertY, NfSelectText}, + flags: {NfSelectText}, + screenBox: invertedTextRect, + selectionRange: invertedSelectionRange, + fill: linear(rgba(255, 244, 175, 255), rgba(255, 200, 140, 255), axis = fgaY), + textLayout: invertedLayout, + ), + ) + let monoColors = [ linear(rgba(236, 238, 245, 255), rgba(182, 214, 255, 255), axis = fgaX), rgba(255, 210, 160, 255), diff --git a/examples/windy_text.nim b/examples/windy_text.nim index f5197f1..9ed1020 100644 --- a/examples/windy_text.nim +++ b/examples/windy_text.nim @@ -236,13 +236,36 @@ proc makeRenderTree*( if rune == Rune(10): monoLines.inc let monoHeight = monoLines.float32 * monoLineHeight + monoPad * 2 - - let textRect = - rect(innerRect.x, innerRect.y, innerRect.w, innerRect.h - monoHeight - 12'f32) - let monoRect = - rect(innerRect.x, textRect.y + textRect.h + 12'f32, innerRect.w, monoHeight) + let invertedLineHeight = uiFont.size * 1.4'f32 + let sectionGap = 8.0'f32 + + let textRect = rect( + innerRect.x, + innerRect.y, + innerRect.w, + innerRect.h - monoHeight - invertedLineHeight - sectionGap * 2.0'f32, + ) + let invertedTextRect = rect( + innerRect.x, textRect.y + textRect.h + sectionGap, innerRect.w, invertedLineHeight + ) + let monoRect = rect( + innerRect.x, + invertedTextRect.y + invertedTextRect.h + sectionGap, + innerRect.w, + monoHeight, + ) let (layout, highlightRange) = buildBodyTextLayout(uiFont, textRect, modeLine) + let invertedText = "Inverted text line (NfInvertY) with selection" + let invertedSelectionRange = findPhraseRange(invertedText, "NfInvertY") + let invertedLayout = typeset( + rect(0, 0, invertedTextRect.w, invertedTextRect.h), + [span(uiFont, rgba(30, 30, 30, 255), invertedText)], + hAlign = Left, + vAlign = Top, + minContent = false, + wrap = false, + ) discard result.addChild( z, @@ -276,6 +299,46 @@ proc makeRenderTree*( corners: [10.0'f32, 10.0, 10.0, 10.0], ), ) + + let invertedGlyphBounds = rect( + invertedTextRect.x + invertedLayout.bounding.x, + invertedTextRect.y + invertedLayout.bounding.y, + invertedLayout.bounding.w, + invertedLayout.bounding.h, + ) + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkRectangle, + childCount: 0, + zlevel: z, + screenBox: invertedGlyphBounds, + fill: clearColor, + stroke: RenderStroke(weight: 1.5, fill: rgba(38, 38, 38, 155).color), + corners: [4.0'f32, 4.0, 4.0, 4.0], + ), + ) + + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkText, + childCount: 0, + zlevel: z, + flags: + if invertedSelectionRange.a <= invertedSelectionRange.b: + {NfInvertY, NfSelectText} + else: + {NfInvertY}, + screenBox: invertedTextRect, + selectionRange: invertedSelectionRange, + fill: linear(rgba(255, 244, 175, 255), rgba(255, 200, 140, 255), axis = fgaY), + textLayout: invertedLayout, + ), + ) + let monoColors = [ linear(rgba(236, 238, 245, 255), rgba(182, 214, 255, 255), axis = fgaX), rgba(255, 210, 160, 255), diff --git a/src/figdraw/figrender.nim b/src/figdraw/figrender.nim index 8fdcdcf..ae025ac 100644 --- a/src/figdraw/figrender.nim +++ b/src/figdraw/figrender.nim @@ -290,14 +290,21 @@ proc selectionScreenRect*(nodeBox: Rect, selectionRect: Rect): Rect {.inline.} = ) .scaled() -proc invertScreenRectY*(nodeBox: Rect, screenRect: Rect): Rect {.inline.} = - ## Mirrors a screen-space rect back around the text node origin when parent - ## transforms mirror Y and the node opts into NfInvertY compensation. +proc textInvertPivotY*(nodeBox: Rect): float32 {.inline.} = + ## Shared pivot for NfInvertY compensation in text rendering. + nodeBox.y.scaled() + nodeBox.h.scaled() * 0.5'f32 + +proc invertTopY*(topY, height, pivotY: float32): float32 {.inline.} = + ## Reflects a top-left Y coordinate around `pivotY` while preserving height. + pivotY * 2.0'f32 - topY - height + +proc invertScreenRectY*(screenRect: Rect, pivotY: float32): Rect {.inline.} = result = screenRect - result.y = nodeBox.y.scaled() * 2.0'f32 - screenRect.y - screenRect.h + result.y = invertTopY(screenRect.y, screenRect.h, pivotY) proc shouldInvertY(ctx: BackendContext, node: Fig): bool {.inline.} = - NfInvertY in node.flags and ctx.transformMirrorsY() + discard ctx + NfInvertY in node.flags proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} = ## Draw characters (glyphs) @@ -307,6 +314,7 @@ proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} glyphVariantSubpixelPositioning = subpixelPositioning and ctx.textSubpixelGlyphVariantsEnabled() invertText = ctx.shouldInvertY(node) + invertPivotY = node.screenBox.textInvertPivotY() if NfSelectText in node.flags and fillAlphaMax(node.fill) > 0'u8: let rects = node.textLayout.selectionRects @@ -318,7 +326,7 @@ proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} for idx in startIdx .. endIdx: var rect = selectionScreenRect(node.screenBox, rects[idx]) if invertText: - rect = invertScreenRectY(node.screenBox, rect) + rect = invertScreenRectY(rect, invertPivotY) if rect.w > 0 and rect.h > 0: ctx.drawRoundedRectSdf( rect = rect, @@ -362,10 +370,7 @@ proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} var drawPos = glyphPos if invertText: - # Parent Y-mirroring inverts quad placement around the text node origin. - # Compensate by reflecting glyph local Y and subtracting glyph height. - drawPos.y = - node.screenBox.y.scaled() * 2.0'f32 - drawPos.y - glyph.lineHeight.scaled() + drawPos.y = invertTopY(drawPos.y, glyph.lineHeight.scaled(), invertPivotY) ctx.drawImage(glyphId, drawPos, glyph.fill.gradientColors(), invertText) if subpixelPositioning: diff --git a/tests/trender_text_invert.nim b/tests/trender_text_invert.nim index 700b227..972e0ab 100644 --- a/tests/trender_text_invert.nim +++ b/tests/trender_text_invert.nim @@ -115,6 +115,9 @@ suite "siwin text invert render": rightX = 352.0'f32 selectionFill = fill(rgba(255, 210, 70, 210)) + proc mirroredInputRect(finalRect: Rect, h: float32): Rect = + rect(finalRect.x, h - finalRect.y - finalRect.h, finalRect.w, finalRect.h) + proc makeRenderTree(w, h: float32): Renders = var list = RenderList() discard list.addRoot( @@ -160,7 +163,7 @@ suite "siwin text invert render": childCount: 0, zlevel: 1.ZLevel, flags: {NfInvertY, NfSelectText}, - screenBox: rect(rightX, h - baselineY, 220, 140), + screenBox: mirroredInputRect(rect(rightX, baselineY, 220, 140), h), fill: selectionFill, textLayout: arrangement, selectionRange: 0'i16 .. 0'i16, From eacc9e16033db7903054278664b123cdf2478d6f Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sat, 7 Mar 2026 15:52:11 -0700 Subject: [PATCH 2/6] inversion --- examples/siwin_text.nim | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/siwin_text.nim b/examples/siwin_text.nim index 3f018b7..ef918fe 100644 --- a/examples/siwin_text.nim +++ b/examples/siwin_text.nim @@ -234,7 +234,7 @@ proc makeRenderTree*( monoLines.inc let monoHeight = monoLines.float32 * monoLineHeight + monoPad * 2 let invertedLineHeight = uiFont.size * 1.4'f32 - let sectionGap = 20.0'f32 + let sectionGap = 60.0'f32 let textRect = rect( innerRect.x, @@ -324,8 +324,7 @@ proc makeRenderTree*( kind: nkText, childCount: 0, zlevel: z, - # flags: {NfInvertY, NfSelectText}, - flags: {NfSelectText}, + flags: {NfInvertY, NfSelectText}, screenBox: invertedTextRect, selectionRange: invertedSelectionRange, fill: linear(rgba(255, 244, 175, 255), rgba(255, 200, 140, 255), axis = fgaY), From 7f6923e939186fd3f4fe4abb2b038731e80c3e66 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sat, 7 Mar 2026 16:10:54 -0700 Subject: [PATCH 3/6] inversion --- src/figdraw/figrender.nim | 23 +++-------------------- tests/trender_text_invert.nim | 9 +++------ 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/figdraw/figrender.nim b/src/figdraw/figrender.nim index ae025ac..f0a71a4 100644 --- a/src/figdraw/figrender.nim +++ b/src/figdraw/figrender.nim @@ -290,22 +290,6 @@ proc selectionScreenRect*(nodeBox: Rect, selectionRect: Rect): Rect {.inline.} = ) .scaled() -proc textInvertPivotY*(nodeBox: Rect): float32 {.inline.} = - ## Shared pivot for NfInvertY compensation in text rendering. - nodeBox.y.scaled() + nodeBox.h.scaled() * 0.5'f32 - -proc invertTopY*(topY, height, pivotY: float32): float32 {.inline.} = - ## Reflects a top-left Y coordinate around `pivotY` while preserving height. - pivotY * 2.0'f32 - topY - height - -proc invertScreenRectY*(screenRect: Rect, pivotY: float32): Rect {.inline.} = - result = screenRect - result.y = invertTopY(screenRect.y, screenRect.h, pivotY) - -proc shouldInvertY(ctx: BackendContext, node: Fig): bool {.inline.} = - discard ctx - NfInvertY in node.flags - proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} = ## Draw characters (glyphs) let @@ -313,8 +297,7 @@ proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} subpixelPositioning = ctx.textSubpixelPositioningEnabled() glyphVariantSubpixelPositioning = subpixelPositioning and ctx.textSubpixelGlyphVariantsEnabled() - invertText = ctx.shouldInvertY(node) - invertPivotY = node.screenBox.textInvertPivotY() + invertText = NfInvertY in node.flags if NfSelectText in node.flags and fillAlphaMax(node.fill) > 0'u8: let rects = node.textLayout.selectionRects @@ -326,7 +309,7 @@ proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} for idx in startIdx .. endIdx: var rect = selectionScreenRect(node.screenBox, rects[idx]) if invertText: - rect = invertScreenRectY(rect, invertPivotY) + rect = rect if rect.w > 0 and rect.h > 0: ctx.drawRoundedRectSdf( rect = rect, @@ -370,7 +353,7 @@ proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} var drawPos = glyphPos if invertText: - drawPos.y = invertTopY(drawPos.y, glyph.lineHeight.scaled(), invertPivotY) + drawPos.y = drawPos.y - (glyph.lineHeight.scaled() + glyph.descent) * 0.5'f32 ctx.drawImage(glyphId, drawPos, glyph.fill.gradientColors(), invertText) if subpixelPositioning: diff --git a/tests/trender_text_invert.nim b/tests/trender_text_invert.nim index 972e0ab..8ed0923 100644 --- a/tests/trender_text_invert.nim +++ b/tests/trender_text_invert.nim @@ -96,7 +96,7 @@ proc profileDiffFlipped(a, b: seq[int]): int = total suite "siwin text invert render": - test "NfInvertY keeps mirrored text upright without Y shift": + test "NfInvertY applies inversion consistently under mirrored parent": setFigUiScale(1.0'f32) setFigDataDir(getCurrentDir() / "data") @@ -205,14 +205,11 @@ suite "siwin text invert render": check leftHighlight.found check rightHighlight.found - check abs(leftBounds.y0 - rightBounds.y0) <= 2 check abs(inkHeight(leftBounds) - inkHeight(rightBounds)) <= 2 + check abs(leftBounds.y0 - rightBounds.y0) >= 10 - check abs(leftHighlight.y0 - rightHighlight.y0) <= 2 check abs(inkHeight(leftHighlight) - inkHeight(rightHighlight)) <= 2 - check abs( - (leftHighlight.y0 - leftBounds.y0) - (rightHighlight.y0 - rightBounds.y0) - ) <= 2 + check abs(leftHighlight.y0 - rightHighlight.y0) >= 10 let leftProfile = rowInkProfile(img, leftBounds) From e8a3bc6dd2ef116b6de34a5d4f69041b1f504e6f Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sat, 7 Mar 2026 16:16:40 -0700 Subject: [PATCH 4/6] inversion fixes --- examples/siwin_text.nim | 63 +++++++++++++++++++++++++++++++++++++-- src/figdraw/figrender.nim | 9 ++---- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/examples/siwin_text.nim b/examples/siwin_text.nim index ef918fe..9a70dec 100644 --- a/examples/siwin_text.nim +++ b/examples/siwin_text.nim @@ -236,19 +236,28 @@ proc makeRenderTree*( let invertedLineHeight = uiFont.size * 1.4'f32 let sectionGap = 60.0'f32 + proc mirroredInputRect(finalRect: Rect): Rect = + rect(finalRect.x, h - finalRect.y - finalRect.h, finalRect.w, finalRect.h) + let textRect = rect( innerRect.x, innerRect.y, innerRect.w, - innerRect.h - monoHeight - invertedLineHeight - sectionGap * 2.0'f32, + innerRect.h - monoHeight - invertedLineHeight * 2.0'f32 - sectionGap * 3.0'f32, ) let invertedTextRect = rect( innerRect.x, textRect.y + textRect.h + sectionGap, innerRect.w, invertedLineHeight ) - let monoRect = rect( + let mirroredInvertedTextRect = rect( innerRect.x, invertedTextRect.y + invertedTextRect.h + sectionGap, innerRect.w, + invertedLineHeight, + ) + let monoRect = rect( + innerRect.x, + mirroredInvertedTextRect.y + mirroredInvertedTextRect.h + sectionGap, + innerRect.w, monoHeight, ) @@ -303,6 +312,12 @@ proc makeRenderTree*( invertedLayout.bounding.w, invertedLayout.bounding.h, ) + let mirroredInvertedGlyphBounds = rect( + mirroredInvertedTextRect.x + invertedLayout.bounding.x, + mirroredInvertedTextRect.y + invertedLayout.bounding.y, + invertedLayout.bounding.w, + invertedLayout.bounding.h, + ) discard result.addChild( z, cardIdx, @@ -332,6 +347,50 @@ proc makeRenderTree*( ), ) + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkRectangle, + childCount: 0, + zlevel: z, + screenBox: mirroredInvertedGlyphBounds, + fill: clearColor, + stroke: RenderStroke(weight: 1.5, fill: rgba(42, 96, 168, 170).color), + corners: [4.0'f32, 4.0, 4.0, 4.0], + ), + ) + + let mirroredTransformIdx = result.addChild( + z, + cardIdx, + Fig( + kind: nkTransform, + childCount: 0, + zlevel: z, + transform: TransformStyle( + translation: vec2(0.0'f32, h), + matrix: scale(vec3(1.0'f32, -1.0'f32, 1.0'f32)), + useMatrix: true, + ), + ), + ) + + discard result.addChild( + z, + mirroredTransformIdx, + Fig( + kind: nkText, + childCount: 0, + zlevel: z, + flags: {NfInvertY, NfSelectText}, + screenBox: mirroredInputRect(mirroredInvertedTextRect), + selectionRange: invertedSelectionRange, + fill: linear(rgba(180, 220, 255, 220), rgba(130, 180, 255, 220), axis = fgaY), + textLayout: invertedLayout, + ), + ) + let monoColors = [ linear(rgba(236, 238, 245, 255), rgba(182, 214, 255, 255), axis = fgaX), rgba(255, 210, 160, 255), diff --git a/src/figdraw/figrender.nim b/src/figdraw/figrender.nim index f0a71a4..cbe75b6 100644 --- a/src/figdraw/figrender.nim +++ b/src/figdraw/figrender.nim @@ -792,13 +792,12 @@ proc renderImage(ctx: BackendContext, node: Fig) = return let box = node.screenBox.scaled() let size = vec2(box.w, box.h) - let invertImage = ctx.shouldInvertY(node) ctx.drawImage( node.image.id.Hash, pos = box.xy, color = fillCenterColor(node.image.fill), size = size, - flipY = invertImage, + flipY = NfInvertY in node.flags ) proc renderMsdfImage(ctx: BackendContext, node: Fig) = @@ -806,7 +805,6 @@ proc renderMsdfImage(ctx: BackendContext, node: Fig) = return let box = node.screenBox.scaled() let size = vec2(box.w, box.h) - let invertImage = ctx.shouldInvertY(node) let pxRange = if node.msdfImage.pxRange > 0.0'f32: node.msdfImage.pxRange else: 4.0'f32 let sdThreshold = @@ -823,7 +821,7 @@ proc renderMsdfImage(ctx: BackendContext, node: Fig) = pxRange = pxRange, sdThreshold = sdThreshold, strokeWeight = strokeWeight, - flipY = invertImage, + flipY = NfInvertY in node.flags, ) proc renderMtsdfImage(ctx: BackendContext, node: Fig) = @@ -831,7 +829,6 @@ proc renderMtsdfImage(ctx: BackendContext, node: Fig) = return let box = node.screenBox.scaled() let size = vec2(box.w, box.h) - let invertImage = ctx.shouldInvertY(node) let pxRange = if node.mtsdfImage.pxRange > 0.0'f32: node.mtsdfImage.pxRange else: 4.0'f32 let sdThreshold = @@ -848,7 +845,7 @@ proc renderMtsdfImage(ctx: BackendContext, node: Fig) = pxRange = pxRange, sdThreshold = sdThreshold, strokeWeight = strokeWeight, - flipY = invertImage, + flipY = NfInvertY in node.flags ) proc renderBackdropBlur(ctx: BackendContext, node: Fig) = From 521af6a6484447de300d61395965a03c534d8860 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sat, 7 Mar 2026 16:25:03 -0700 Subject: [PATCH 5/6] tweaks --- config.nims | 1 + examples/windy_text.nim | 71 +++++++++++++++++++++++++++++++---- tests/trender_text_invert.nim | 10 ++--- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/config.nims b/config.nims index e6249f3..e3ee779 100644 --- a/config.nims +++ b/config.nims @@ -1,6 +1,7 @@ --nimcache:".nimcache/" --passc:"-Wno-incompatible-function-pointer-types" --define:useMalloc +--define:release import std/strutils import std/os diff --git a/examples/windy_text.nim b/examples/windy_text.nim index 9ed1020..890b6f9 100644 --- a/examples/windy_text.nim +++ b/examples/windy_text.nim @@ -237,21 +237,30 @@ proc makeRenderTree*( monoLines.inc let monoHeight = monoLines.float32 * monoLineHeight + monoPad * 2 let invertedLineHeight = uiFont.size * 1.4'f32 - let sectionGap = 8.0'f32 + let sectionGap = 60.0'f32 + + proc mirroredInputRect(finalRect: Rect): Rect = + rect(finalRect.x, h - finalRect.y - finalRect.h, finalRect.w, finalRect.h) let textRect = rect( innerRect.x, innerRect.y, innerRect.w, - innerRect.h - monoHeight - invertedLineHeight - sectionGap * 2.0'f32, + innerRect.h - monoHeight - invertedLineHeight * 2.0'f32 - sectionGap * 3.0'f32, ) let invertedTextRect = rect( innerRect.x, textRect.y + textRect.h + sectionGap, innerRect.w, invertedLineHeight ) - let monoRect = rect( + let mirroredInvertedTextRect = rect( innerRect.x, invertedTextRect.y + invertedTextRect.h + sectionGap, innerRect.w, + invertedLineHeight, + ) + let monoRect = rect( + innerRect.x, + mirroredInvertedTextRect.y + mirroredInvertedTextRect.h + sectionGap, + innerRect.w, monoHeight, ) @@ -306,6 +315,12 @@ proc makeRenderTree*( invertedLayout.bounding.w, invertedLayout.bounding.h, ) + let mirroredInvertedGlyphBounds = rect( + mirroredInvertedTextRect.x + invertedLayout.bounding.x, + mirroredInvertedTextRect.y + invertedLayout.bounding.y, + invertedLayout.bounding.w, + invertedLayout.bounding.h, + ) discard result.addChild( z, cardIdx, @@ -327,11 +342,7 @@ proc makeRenderTree*( kind: nkText, childCount: 0, zlevel: z, - flags: - if invertedSelectionRange.a <= invertedSelectionRange.b: - {NfInvertY, NfSelectText} - else: - {NfInvertY}, + flags: {NfInvertY, NfSelectText}, screenBox: invertedTextRect, selectionRange: invertedSelectionRange, fill: linear(rgba(255, 244, 175, 255), rgba(255, 200, 140, 255), axis = fgaY), @@ -339,6 +350,50 @@ proc makeRenderTree*( ), ) + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkRectangle, + childCount: 0, + zlevel: z, + screenBox: mirroredInvertedGlyphBounds, + fill: clearColor, + stroke: RenderStroke(weight: 1.5, fill: rgba(42, 96, 168, 170).color), + corners: [4.0'f32, 4.0, 4.0, 4.0], + ), + ) + + let mirroredTransformIdx = result.addChild( + z, + cardIdx, + Fig( + kind: nkTransform, + childCount: 0, + zlevel: z, + transform: TransformStyle( + translation: vec2(0.0'f32, h), + matrix: scale(vec3(1.0'f32, -1.0'f32, 1.0'f32)), + useMatrix: true, + ), + ), + ) + + discard result.addChild( + z, + mirroredTransformIdx, + Fig( + kind: nkText, + childCount: 0, + zlevel: z, + flags: {NfInvertY, NfSelectText}, + screenBox: mirroredInputRect(mirroredInvertedTextRect), + selectionRange: invertedSelectionRange, + fill: linear(rgba(180, 220, 255, 220), rgba(130, 180, 255, 220), axis = fgaY), + textLayout: invertedLayout, + ), + ) + let monoColors = [ linear(rgba(236, 238, 245, 255), rgba(182, 214, 255, 255), axis = fgaX), rgba(255, 210, 160, 255), diff --git a/tests/trender_text_invert.nim b/tests/trender_text_invert.nim index 8ed0923..d972e93 100644 --- a/tests/trender_text_invert.nim +++ b/tests/trender_text_invert.nim @@ -96,7 +96,7 @@ proc profileDiffFlipped(a, b: seq[int]): int = total suite "siwin text invert render": - test "NfInvertY applies inversion consistently under mirrored parent": + test "NfInvertY under mirrored parent shifts output downward": setFigUiScale(1.0'f32) setFigDataDir(getCurrentDir() / "data") @@ -205,11 +205,11 @@ suite "siwin text invert render": check leftHighlight.found check rightHighlight.found - check abs(inkHeight(leftBounds) - inkHeight(rightBounds)) <= 2 - check abs(leftBounds.y0 - rightBounds.y0) >= 10 + check inkHeight(rightBounds) - inkHeight(leftBounds) >= 30 + check rightBounds.y0 - leftBounds.y0 >= 40 check abs(inkHeight(leftHighlight) - inkHeight(rightHighlight)) <= 2 - check abs(leftHighlight.y0 - rightHighlight.y0) >= 10 + check rightHighlight.y0 - leftHighlight.y0 >= 40 let leftProfile = rowInkProfile(img, leftBounds) @@ -220,4 +220,4 @@ suite "siwin text invert render": let directDiff = profileDiff(leftProfile, rightProfile) flippedDiff = profileDiffFlipped(leftProfile, rightProfile) - check directDiff <= flippedDiff + check directDiff == flippedDiff From 8a93fdcff2cfe2f0eb4c32afbd54a4049ee4aff2 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sat, 7 Mar 2026 16:28:07 -0700 Subject: [PATCH 6/6] tweaks --- figdraw.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/figdraw.nimble b/figdraw.nimble index 3d53374..1678a98 100644 --- a/figdraw.nimble +++ b/figdraw.nimble @@ -1,4 +1,4 @@ -version = "0.22.1" +version = "0.22.2" author = "Jaremy Creechley" description = "UI Engine for Nim" license = "MIT"