How To Build FriBiDi As A Shared Library And Load It And Use It Inside Tcl Commands On Debian Linux (Updated And Working Solution)

Introduction

Hi.

This will help you build a shared library that will let you display, in the proper visual way, strings that have a mix of languages, like a mix of Arabic and Latin. FriBidi is the library which does it. This page has a C code that you have to build to bind Tcl with FriBidi.

Requirements

  1. apt-get install libfribidi-dev ; This is needed to build and bind the Tcl version of FriBiDi
  2. apt-get install tcl8.6-dev ; This is the Tcl API library. If you have ActiveTcl installed you don't need this. Please don't confine yourself to version 8.6 .. It works too for 8.5. Just change the version number
  3. open a text editor like gEdit or vi or kate. Copy and paste and save this C code in the file tclfribidi.c Thanks to Auriocus for this code. after compiling and building tclfribidi.c, you'll find in the same directory of tclfribidi.c the shared library tclfribidi.so. The library tclfribidi.so is the one that we load and use to run FriBiDi procedure fribidi_log2vis $str .
/* Author: auriocus */
#include <tcl.h>
#include <fribidi.h>

static int
fribidi_log2visCmd(
                ClientData clientData, /* Not used. */
                Tcl_Interp *interp, /* Current interpreter */
                int objc, /* Number of arguments */
                Tcl_Obj *const objv[] /* Argument strings */
                )
{
        if (objc != 2) {
                Tcl_WrongNumArgs (interp, 1, objv, "string");
                return TCL_ERROR;
        }


        /* convert arg1 to FriBidiChar = UTF32 */
        int len;
        char *str = Tcl_GetStringFromObj(objv[1], &len);

        /* the string can have at most len characters */
        FriBidiChar *frstr = ckalloc(sizeof(FriBidiChar)*len);

        int len_res = len, ind = 0;
        while (len_res >0) {
                Tcl_UniChar ch;
                int nbytes = Tcl_UtfToUniChar(str, &ch);
                str += nbytes; len_res -= nbytes;
                frstr[ind++] = ch;
        }

        int nchar = ind; /* nr. of characters in this string */

        /* I don't understand FriBidiCharType. Does it need a chartype for every single char? */
        FriBidiCharType *pbase_dir = ckalloc(sizeof(FriBidiCharType)*(nchar+1));
        for (ind = 0; ind < nchar; ind ++) {
                pbase_dir[ind] = FRIBIDI_TYPE_ON;
        }


        /* alloc buffer for result */
        FriBidiChar *visual_str = ckalloc(sizeof(FriBidiChar)*(nchar+1));

        fribidi_boolean result = fribidi_log2vis(
                        /* input */
                        frstr,
                        nchar,
                        pbase_dir,
                        /* output */
                        visual_str,
                        NULL,
                        NULL,
                        NULL
                        );
        /* free input */
        ckfree(pbase_dir);
        ckfree(frstr);

        /* Does the result indicate failure? */
        if (result) {
                /* Success ? */

                /* copy output to new string in Tcl_UniChar format */
                Tcl_UniChar *uniresult = ckalloc(sizeof(Tcl_UniChar)*(nchar+1));
                for (ind=0; ind < nchar; ind++) {
                        uniresult[ind] = visual_str[ind];
                }

                Tcl_SetObjResult(interp, Tcl_NewUnicodeObj(uniresult, nchar));
                ckfree(uniresult);
                ckfree(visual_str);
                return TCL_OK;

        } else {
                /* Failure ? */
                Tcl_SetResult(interp, "fribidi failed", NULL);
                ckfree(visual_str);
                return TCL_ERROR;
        }

}

int Tclfribidi_Init(Tcl_Interp *interp)
{
        /*
         * This may work with 8.0, but we are using strictly stubs here,
         * which requires 8.1.
         */
        if (Tcl_InitStubs(interp, "8.5", 0) == NULL) {
                return TCL_ERROR;
        }

        Tcl_CreateObjCommand(interp, "fribidi_log2vis", (Tcl_ObjCmdProc *) fribidi_log2visCmd,
                        (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);

        return TCL_OK;
}

Building steps

Before anything: Don't forget to change the Tcl version number from 8.6 to 8.5 in the following gcc commands if you have Tcl8.5 .

32 bit systems and using the debian package tcl8.6-dev, run the following command on the terminal:

   /usr/bin/gcc -shared -DUSE_TCL_STUBS -I/usr/include/fribidi -I/usr/include/tcl8.6  -lfribidi  -o tclfribidi.so tclfribidi.c -ltclstub8.6

32 bit systems and using the ActiveTcl8.6, run the following command on the terminal:

   /usr/bin/gcc -shared -DUSE_TCL_STUBS -I/usr/include/fribidi -I/opt/ActiveTcl-8.6/include/ -lfribidi  -o tclfribidi.so tclfribidi.c -L/opt/ActiveTcl-8.6/lib/ -ltclstub8.6

64 bit systems and using the debian package tcl8.6-dev, run the following command on the terminal:

   /usr/bin/gcc -shared -DUSE_TCL_STUBS -I/usr/include/fribidi -I/usr/include/tcl8.6  -fPIC -lfribidi  -o tclfribidi.so tclfribidi.c -ltclstub8.6

64 bit systems and using the ActiveTcl8.6, run the following command on the terminal:

   /usr/bin/gcc -shared -DUSE_TCL_STUBS -I/usr/include/fribidi -I/opt/ActiveTcl-8.6/include/  -fPIC -lfribidi  -o tclfribidi.so tclfribidi.c -L/opt/ActiveTcl-8.6/lib/ -ltclstub8.6

How To Use

Assume that you wrote a Tcl script in the same directory of the shared library file tclfribidi.so, then:

load ./tclfribidi.so

#the Arabic in following string , when you type in tkcon or tclsh, should look separated and reversed. '''Don't copy the code below.''' Please use your own string.
fribidi_log2vis "Rani fayez ahmad ي ن ا ر  ز ي ا ف  د م ح ا"
#output is : Rani fayez ahmad راني فايز