on master help sign in
Add support for custom character width table installation

This change introduces a new module, Graphics.Vty.UnicodeWidthTable.Install, that is responsible for installing custom unicode width tables into the process. It does so by allocating memory to hold the table, populating the table's character ranges with widths from the UnicodeWidthTable, and then marking the table as ready for use. That causes future calls to 'wcwidth' to consult the new table rather than relying on the built-in logic to compute widths. Note that this functionality breaks the purity of 'wcwidth': 'wcwidth' calls made before the installation of a custom table may return different values after the table is installed, despite 'wcwidth' being a pure function. This choice was made deliberately to simplify the API and is based on the observation that in practice this will probably never be an issue. It's also worth nothing (as is spelled out in the docs, too) that a custom table installation can only be performed at most once per program lifetime. Once a custom table installation is performed, it cannot be undone and no new table can be installed. This, too, is a simplification based on the assumption that in practice multiple tables will usually not be required.

commit74a7c557fb895e8dc5bd739d2e27cb243f18d5b1 authorJonathan Daugherty <cygnus@foobox.com> dateMon 2 Mar 2020, 14:05 (7 years ago) parentf48e449
3 files changed+186−2
cbits/mk_wcwidth.c+115−1 src/Graphics/Vty/UnicodeWidthTable/Install.hs+69 vty.cabal+2−1
cbits/mk_wcwidth.c+115−1
@@ -60,6 +60,24 @@
6060 */
6161
6262#include <HsFFI.h>
63+#include <stdlib.h>
64+#include <string.h>
65+
66+// The maximum size for a custom character width table is the total
67+// number of possible characters as dictated by the compiler.
68+#define MAX_CUSTOM_TABLE_SIZE (HS_CHAR_MAX + 1)
69+
70+// The pointer to the start of the custom character width table, if any.
71+// If this is NULL, the built-in tree search logic is used.
72+static uint8_t* custom_table = NULL;
73+
74+// The size of the custom table. This should only be set if custom_table
75+// is not NULL. Its value should be the size of the custom_table array.
76+static uint32_t custom_table_size = 0;
77+
78+// A flag indicating whether the custom table is ready for use. This
79+// should only be set once the table has been allocated and initialized.
80+static uint8_t custom_table_ready = 0;
6381
6482struct interval {
6583 int first;
@@ -119,7 +137,7 @@ static int vty_bisearch(HsChar ucs, const struct interval *table, int max) {
119137 * in ISO 10646.
120138 */
121139
122−HsInt vty_mk_wcwidth(HsChar ucs)
140+HsInt builtin_wcwidth(HsChar ucs)
123141{
124142 /* sorted list of non-overlapping intervals of non-spacing characters */
125143 /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
@@ -202,3 +220,99 @@ HsInt vty_mk_wcwidth(HsChar ucs)
202220 (ucs >= 0x20000 && ucs <= 0x2fffd) ||
203221 (ucs >= 0x30000 && ucs <= 0x3fffd)));
204222}
223+
224+// Return the width, in terminal cells, of the specified character.
225+//
226+// If the global custom width table is present, that table will be
227+// consulted for the character's width. If the character is not in
228+// the table, zero will be returned. If the custom width table is not
229+// present, the built-in width table will be used.
230+HsInt vty_mk_wcwidth(HsChar ch)
231+{
232+ if (custom_table != NULL) {
233+ if (ch >= 0 && ch < custom_table_size) {
234+ return custom_table[ch];
235+ } else {
236+ return 0;
237+ }
238+ } else {
239+ return builtin_wcwidth(ch);
240+ }
241+}
242+
243+// Initialize a custom character width table.
244+//
245+// This allocates a new character width table of the specified size
246+// (in characters). If a custom table has already been allocated, this
247+// returns 1. Otherwise it allocates a new table, initializes all of its
248+// entries to zero, and returns zero.
249+//
250+// Note that this does *not* mark the table as ready for use. Until the
251+// table is marked ready, it will not be used by vty_mk_wcwidth. To mark
252+// the table as ready, call vty_activate_custom_table() after the table
253+// has been set up with calls to vty_set_custom_table_range.
254+int vty_init_custom_table(int size)
255+{
256+ if (custom_table == NULL) {
257+ if (size > 0 && size <= MAX_CUSTOM_TABLE_SIZE) {
258+ custom_table_ready = 0;
259+ custom_table = malloc(size);
260+ bzero(custom_table, size);
261+ custom_table_size = size;
262+ return 0;
263+ } else {
264+ return 1;
265+ }
266+ } else {
267+ return 1;
268+ }
269+}
270+
271+// Set the specified character range in the custom width table to the
272+// specified width.
273+//
274+// This function sets 'width' as the character width for all entries
275+// in the custom character table starting at the 'start' entry and
276+// including all entries up to and including 'start + size - 1'.
277+//
278+// If this succeeds, it returns zero. If it fails, it returns 1. It
279+// fails if the table is not allocated, marked as ready (i.e. it is in
280+// use and has already been populated), or if the start or size values
281+// are not in bounds for the table.
282+int vty_set_custom_table_range(uint32_t start, uint32_t size, uint8_t width)
283+{
284+ if (custom_table == NULL) {
285+ return 1;
286+ } else if ((start + size - 1) >= custom_table_size) {
287+ return 1;
288+ } else if (custom_table_ready) {
289+ return 1;
290+ } else {
291+ memset(custom_table + start, (uint8_t) width, size);
292+ return 0;
293+ }
294+}
295+
296+// Mark the allocated custom character width table as ready for use.
297+//
298+// After this call, further calls to vty_set_custom_table_range will
299+// fail.
300+//
301+// This function returns 0 if it succeeds. If it fails, it returns 1.
302+// It fails if the custom table is already ready or if it has not been
303+// allocated.
304+int vty_activate_custom_table()
305+{
306+ if (custom_table_ready || (custom_table == NULL)) {
307+ return 1;
308+ } else {
309+ custom_table_ready = 1;
310+ return 0;
311+ }
312+}
313+
314+// Returns whether a custom character width table has been marked ready.
315+int vty_custom_table_ready()
316+{
317+ return ((custom_table != NULL) && custom_table_ready);
318+}
src/Graphics/Vty/UnicodeWidthTable/Install.hsnew file+69
@@ -0,0 +1,69 @@
1+{-# LANGUAGE ForeignFunctionInterface #-}
2+module Graphics.Vty.UnicodeWidthTable.Install
3+ ( installUnicodeWidthTable
4+
5+ , isCustomTableReady
6+ )
7+where
8+
9+import Control.Monad (when, forM_)
10+import Data.Word (Word8, Word32)
11+
12+import Graphics.Vty.UnicodeWidthTable.Types
13+
14+foreign import ccall unsafe "vty_init_custom_table"
15+ initCustomTable :: Int -> IO Int
16+
17+foreign import ccall unsafe "vty_set_custom_table_range"
18+ setCustomTableRange :: Word32 -> Word32 -> Word8 -> IO Int
19+
20+foreign import ccall unsafe "vty_activate_custom_table"
21+ activateCustomTable :: IO Int
22+
23+-- | Returns 1 if and only if a custom table has been allocated and
24+-- marked as ready for use. Returns 0 otherwise.
25+foreign import ccall unsafe "vty_custom_table_ready"
26+ isCustomTableReady :: IO Int
27+
28+-- This is the size of the allocated custom character width table, in
29+-- character slots. It's important that this be large enough to hold all
30+-- possible Unicode character values. At the time of this writing, the
31+-- valid Unicode range is 0 - 0x10ffff, hence this value.
32+tableSize :: Int
33+tableSize = 0x110000
34+
35+-- | Install a custom unicode character width table.
36+--
37+-- This affects the behavior of the 'wcwidth' function and functions
38+-- that call it. It does so by changing global state available to the C
39+-- implementation of 'wcwidth'. To ensure that your program gets consistent
40+-- results from evaluating calls to 'wcwidth', the installation of a
41+-- custom table should be performed before you call 'wcwidth' in your
42+-- program. This is best done at Vty startup. It's also important to
43+-- note that once a custom table has been installed, it is permanent for
44+-- the life of the process. No new table can be installed, and the new
45+-- custom table cannot be removed.
46+--
47+-- If this function fails for any reason -- if the table cannot be
48+-- installed or is invalid, or if a custom table already exists -- this
49+-- will raise an exception by calling 'error'.
50+installUnicodeWidthTable :: UnicodeWidthTable -> IO ()
51+installUnicodeWidthTable table = do
52+ initResult <- initCustomTable tableSize
53+ when (initResult /= 0) $
54+ error $ "installUnicodeWidthTable: error initializing " <>
55+ "custom table, status " <> show initResult
56+
57+ forM_ (unicodeWidthTableRanges table) $ \r -> do
58+ result <- setCustomTableRange (rangeStart r)
59+ (rangeSize r)
60+ (rangeColumns r)
61+
62+ when (result /= 0) $
63+ error $ "installUnicodeWidthTable: error installing range " <>
64+ show r <> ", status " <> show result
65+
66+ actResult <- activateCustomTable
67+ when (actResult /= 0) $
68+ error $ "installUnicodeWidthTable: error activating custom " <>
69+ "table, status " <> show actResult
vty.cabal+2−1
@@ -104,6 +104,7 @@ library
104104 Graphics.Vty.UnicodeWidthTable.Types
105105 Graphics.Vty.UnicodeWidthTable.IO
106106 Graphics.Vty.UnicodeWidthTable.Query
107+ Graphics.Vty.UnicodeWidthTable.Install
107108
108109 other-modules: Graphics.Vty.Attributes.Color240
109110 Graphics.Vty.Debug.Image
@@ -130,7 +131,7 @@ executable vty-build-table
130131 hs-source-dirs: tools
131132
132133 default-language: Haskell2010
133− ghc-options: -threaded
134+ ghc-options: -threaded -Wall
134135
135136 build-depends: vty,
136137 base >= 4.8 && < 5