From 5617559034643597034c1415a453382ec1dffd97 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sat, 7 Mar 2026 15:26:24 -0700 Subject: [PATCH] add text selection --- src/figdraw/figrender.nim | 10 +++++++- tests/trender_text_invert.nim | 46 +++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/figdraw/figrender.nim b/src/figdraw/figrender.nim index 8affaca..8fdcdcf 100644 --- a/src/figdraw/figrender.nim +++ b/src/figdraw/figrender.nim @@ -290,6 +290,12 @@ 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. + result = screenRect + result.y = nodeBox.y.scaled() * 2.0'f32 - screenRect.y - screenRect.h + proc shouldInvertY(ctx: BackendContext, node: Fig): bool {.inline.} = NfInvertY in node.flags and ctx.transformMirrorsY() @@ -310,7 +316,9 @@ proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} let selectionGradient = node.fill.gradientColors() let zeroRadii = [0.0'f32, 0.0'f32, 0.0'f32, 0.0'f32] for idx in startIdx .. endIdx: - let rect = selectionScreenRect(node.screenBox, rects[idx]) + var rect = selectionScreenRect(node.screenBox, rects[idx]) + if invertText: + rect = invertScreenRectY(node.screenBox, rect) if rect.w > 0 and rect.h > 0: ctx.drawRoundedRectSdf( rect = rect, diff --git a/tests/trender_text_invert.nim b/tests/trender_text_invert.nim index 4f57e09..700b227 100644 --- a/tests/trender_text_invert.nim +++ b/tests/trender_text_invert.nim @@ -19,6 +19,9 @@ type InkBounds = object proc isInk(px: ColorRGBX): bool = px.a >= 20'u8 and (px.r < 220'u8 or px.g < 220'u8 or px.b < 220'u8) +proc isHighlight(px: ColorRGBX): bool = + px.a >= 20'u8 and px.r >= 180'u8 and px.g >= 150'u8 and px.b <= 140'u8 + proc findInkBounds(img: Image, x0, y0, w, h: int): InkBounds = let minX = max(0, x0) @@ -40,6 +43,27 @@ proc findInkBounds(img: Image, x0, y0, w, h: int): InkBounds = result.x1 = max(result.x1, x) result.y1 = max(result.y1, y) +proc findHighlightBounds(img: Image, x0, y0, w, h: int): InkBounds = + let + minX = max(0, x0) + minY = max(0, y0) + maxX = min(img.width - 1, x0 + w - 1) + maxY = min(img.height - 1, y0 + h - 1) + if maxX < minX or maxY < minY: + return InkBounds(found: false) + + result = InkBounds(found: false, x0: maxX, y0: maxY, x1: minX, y1: minY) + for y in minY .. maxY: + for x in minX .. maxX: + if isHighlight(img[x, y]): + if not result.found: + result = InkBounds(found: true, x0: x, y0: y, x1: x, y1: y) + else: + result.x0 = min(result.x0, x) + result.y0 = min(result.y0, y) + result.x1 = max(result.x1, x) + result.y1 = max(result.y1, y) + proc inkHeight(b: InkBounds): int = if not b.found: return 0 @@ -89,6 +113,7 @@ suite "siwin text invert render": baselineY = 120.0'f32 leftX = 96.0'f32 rightX = 352.0'f32 + selectionFill = fill(rgba(255, 210, 70, 210)) proc makeRenderTree(w, h: float32): Renders = var list = RenderList() @@ -107,10 +132,11 @@ suite "siwin text invert render": kind: nkText, childCount: 0, zlevel: 1.ZLevel, + flags: {NfSelectText}, screenBox: rect(leftX, baselineY, 220, 140), - fill: fill(rgba(10, 10, 10, 255)), + fill: selectionFill, textLayout: arrangement, - selectionRange: 0'i16 .. -1'i16, + selectionRange: 0'i16 .. 0'i16, ) ) @@ -133,11 +159,11 @@ suite "siwin text invert render": kind: nkText, childCount: 0, zlevel: 1.ZLevel, - flags: {NfInvertY}, + flags: {NfInvertY, NfSelectText}, screenBox: rect(rightX, h - baselineY, 220, 140), - fill: fill(rgba(10, 10, 10, 255)), + fill: selectionFill, textLayout: arrangement, - selectionRange: 0'i16 .. -1'i16, + selectionRange: 0'i16 .. 0'i16, ), ) @@ -169,12 +195,22 @@ suite "siwin text invert render": let leftBounds = findInkBounds(img, 32, 40, 260, 260) rightBounds = findInkBounds(img, 300, 40, 260, 260) + leftHighlight = findHighlightBounds(img, 32, 40, 260, 260) + rightHighlight = findHighlightBounds(img, 300, 40, 260, 260) check leftBounds.found check rightBounds.found + check leftHighlight.found + check rightHighlight.found check abs(leftBounds.y0 - rightBounds.y0) <= 2 check abs(inkHeight(leftBounds) - inkHeight(rightBounds)) <= 2 + 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 + let leftProfile = rowInkProfile(img, leftBounds) rightProfile = rowInkProfile(img, rightBounds)