aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-17 19:01:50 +0200
committerDanilo M. <danix@danix.xyz>2026-09-17 19:01:50 +0200
commitd9fb5c42e8a8deed21d93b6961a0775acea5540a (patch)
treea19aac68ab66f37b6a3e2a20b063598a83f602be
parentdb50ae8b65f6be6b2c6d9e2a9a019c8b21adfaab (diff)
downloadconky-theme-udt-d9fb5c42e8a8deed21d93b6961a0775acea5540a.tar.gz
conky-theme-udt-d9fb5c42e8a8deed21d93b6961a0775acea5540a.zip
fix: restore the line width card.ring sets, add card.truncate
A ring left its line width behind, so the next stroke on the card inherited a hairline several pixels thick. It already restored the cap for the same reason. card.truncate replaces byte slicing, which cuts a multi-byte character in half and emits an invalid sequence Cairo draws as a box. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
-rw-r--r--lib/card.lua28
-rw-r--r--test/test_data.lua28
2 files changed, 54 insertions, 2 deletions
diff --git a/lib/card.lua b/lib/card.lua
index c83e6bc..f25c393 100644
--- a/lib/card.lua
+++ b/lib/card.lua
@@ -188,6 +188,7 @@ function M.ring(cr, cx, cy, r, frac, colour, colors, width)
-- and cairo_arc() joins to it with a straight line, so without this every
-- ring after a label is drawn with a stray chord to the text baseline.
cairo_new_path(cr)
+ local prev_width = cairo_get_line_width(cr)
cairo_set_line_width(cr, width)
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND)
@@ -200,9 +201,11 @@ function M.ring(cr, cx, cy, r, frac, colour, colors, width)
cairo_arc(cr, cx, cy, r, -math.pi / 2, -math.pi / 2 + math.pi * 2 * frac)
cairo_stroke(cr)
end
- -- Leave the cap as it was found: a later stroke inheriting ROUND would get
- -- visibly rounded ends on the card's hairlines.
+ -- Leave the line state as it was found. A later stroke inheriting ROUND gets
+ -- visibly rounded ends on the card's hairlines, and one inheriting this
+ -- width gets a hairline several pixels thick.
cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT)
+ cairo_set_line_width(cr, prev_width)
end
-- The largest base size S at which every row of pieces still fits max_w,
@@ -246,4 +249,25 @@ function M.human(bytes)
return string.format('%.1f%s', n, units[i])
end
+-- Shorten a string to `limit` characters, appending an ellipsis when it cuts.
+--
+-- By character, never by byte: Lua's string.sub counts bytes, so slicing a
+-- UTF-8 name mid-sequence emits an invalid byte, which Cairo draws as a
+-- replacement box. A name long enough to need shortening is exactly the kind
+-- likely to carry an accent.
+--
+-- This counts codepoints, not rendered width, so it does not account for a
+-- double-width CJK glyph. That is the right trade here: the strings it cuts
+-- are cache directory names and hardware model strings. Measure with
+-- M.measure when true rendered width matters.
+function M.truncate(s, limit)
+ s = tostring(s or '')
+ limit = tonumber(limit) or 0
+ if limit <= 0 then return '' end
+ if utf8.len(s) == nil then return s:sub(1, limit) end -- not valid UTF-8: byte-slice
+ if utf8.len(s) <= limit then return s end
+ local cut = utf8.offset(s, limit) -- byte index of the limit'th character
+ return s:sub(1, cut - 1) .. '\u{2026}'
+end
+
return M
diff --git a/test/test_data.lua b/test/test_data.lua
index a90413b..4d3280c 100644
--- a/test/test_data.lua
+++ b/test/test_data.lua
@@ -6,6 +6,13 @@
package.path = './?.lua;' .. package.path
local data = require 'lib.data'
+-- lib.card uses Cairo globals (cairo_*, CAIRO_*) without requiring 'cairo'
+-- itself, so those globals must exist before it loads. Conky provides them;
+-- plain lua needs its loadable Cairo binding on the cpath first, exactly as
+-- test/render.lua does.
+package.cpath = '/usr/lib64/conky/lib?.so;' .. package.cpath
+require 'cairo'
+
local function read(path)
local f = assert(io.open(path, 'r'))
local s = f:read('*a')
@@ -150,4 +157,25 @@ assert(cache.items[1].bytes / cache.total.bytes > 0.5,
assert(data.du_parse('') == nil, 'empty input gives nil')
assert(data.du_parse(nil) == nil, 'nil gives nil')
+-- === card.truncate ========================================================
+-- Truncation is by CHARACTER, not byte: slicing a UTF-8 string mid-sequence
+-- emits an invalid byte that Cairo draws as a replacement box, and the name
+-- that needed shortening is exactly the kind that carries accents.
+local card = require 'lib.card'
+
+assert(card.truncate('short', 10) == 'short', 'a short string is unchanged')
+assert(card.truncate('exactlyten', 10) == 'exactlyten', 'a string at the limit is unchanged')
+assert(card.truncate('abcdefghijkl', 10) == 'abcdefghi\u{2026}',
+ 'a long string is cut to limit-1 plus an ellipsis, got ' .. tostring(card.truncate('abcdefghijkl', 10)))
+
+-- The multi-byte case: ten accented characters are 20 bytes, so a byte-based
+-- slice would cut one in half and produce invalid UTF-8.
+local accented = string.rep('\u{00E9}', 12)
+local cut = card.truncate(accented, 10)
+assert(cut == string.rep('\u{00E9}', 9) .. '\u{2026}',
+ 'accented input must cut on a character boundary, got ' .. tostring(cut))
+
+assert(card.truncate(nil, 10) == '', 'nil truncates to empty')
+assert(card.truncate('abc', 0) == '', 'a zero limit gives empty')
+
print('test_data: all assertions passed')