On Wednesday, November 5th, 2025 at 12:42 PM, Udi Fogiel via ntg-context 
<[email protected]> wrote:

> 
> 
> On Wednesday, November 5th, 2025 at 12:27 PM, Hans Hagen via ntg-context 
> [email protected] wrote:
> 
> > you need to set up alignment
> > 
> > \setuppapersize[A4]
> > 
> > \mainlanguage[arabic]
> > 
> > \setupdirections[bidi=global,method=unicode]
> > 
> > \definefontfamily [myfont][serif][Amiri] [features=arabic]
> > \definefontfamily [myfont][mono] [Latin Modern Mono]
> > \setupbodyfont [myfont]
> > 
> > % \setupalign[normal,righttoleft,tolerant]
> > 
> > \starttext
> > 
> > \startTEXpage[offset=3ts,frame=on,align={normal,righttoleft,tolerant}]
> > بى 19 az 29 تى 19 29 39 ثى 49-59 سى 69/79 شى 19.29 صى 39,49
> > \stopTEXpage
> > 
> > \stoptext
> 
> 
> But it still gives me the same, unexpected, result, or am I missing something?
> 
> Udi

Hei Hans,

I've done some digging in the code, 
and noticed that the list of nodes are unbalanced with regards to dir nodes as 
a result of the code in typo-duc. I think the main problem is that 
insert_dir_points can only insert one dir point at each entry, but sometimes 
several closures can happen at a time. In addition there are a couple of nil 
variables, and variables shadowing others. I attached a patch for typo-duc.lua 
(which is almost identical to typo-duc.lmt).

Thanks,
Udi
diff --git a/tex/context/base/mkiv/typo-duc.lua b/tex/context/base/mkiv/typo-duc.lua
index f37afa3c3..8d228100a 100644
--- a/tex/context/base/mkiv/typo-duc.lua
+++ b/tex/context/base/mkiv/typo-duc.lua
@@ -324,7 +324,7 @@ local function build_list(head,where)
             local skip = 0
             local last = id
             current    = getnext(current)
-            while n do
+            while current do
                 local id = getid(current)
                 if id ~= glyph_code and id ~= glue_code and id ~= dir_code then
                     skip    = skip + 1
@@ -633,14 +633,14 @@ local function resolve_weak(list,size,start,limit,orderbefore,orderafter)
         if list[i].direction == "et" then
             local runstart = i
             local runlimit = runstart
-            for i=runstart,limit do
-                if list[i].direction == "et" then
-                    runlimit = i
+            for j=runstart,limit do
+                if list[j].direction == "et" then
+                    runlimit = j
                 else
                     break
                 end
             end
-            local rundirection = runstart == start and sor or list[runstart-1].direction
+            local rundirection = runstart == start and orderbefore or list[runstart-1].direction
             if rundirection ~= "en" then
                 rundirection = runlimit == limit and orderafter or list[runlimit+1].direction
             end
@@ -833,70 +833,53 @@ local function resolve_levels(list,size,baselevel,analyze_fences)
     end
 end
 
-local stack = { }
-
 local function insert_dir_points(list,size)
     -- L2, but no actual reversion is done, we simply annotate where
     -- begindir/endddir node will be inserted.
+
+    -- Use direction change lists in case several dirs will
+    -- be in the same entry
+    for i=1,size do
+        list[i].begindirs = {}
+        list[i].enddirs = {}
+    end
+
     local maxlevel = 0
-    local toggle   = true
     for i=1,size do
         local level = list[i].level
         if level > maxlevel then
             maxlevel = level
         end
     end
-    for level=0,maxlevel do
-        local started  -- = false
-        local begindir -- = nil
-        local enddir   -- = nil
-        local prev     -- = nil
-        if toggle then
-            begindir = lefttoright_code
-            enddir   = lefttoright_code
-            toggle   = false
-        else
-            begindir = righttoleft_code
-            enddir   = righttoleft_code
-            toggle   = true
-        end
+
+    for level=1,maxlevel do
+        local started = false
+        local runstart = nil
+        local runlast = nil
+        local dircode = (level % 2 == 1) and righttoleft_code or lefttoright_code
+
         for i=1,size do
             local entry = list[i]
             if entry.level >= level then
                 if not started then
-                    entry.begindir = begindir
-                    started        = true
+                    insert(entry.begindirs, dircode)
+                    runstart = i
+                    started = true
                 end
+                runlast = i
             else
-                if started then
-                    prev.enddir = enddir
-                    started     = false
+                if started and runlast then
+                    insert(list[runlast].enddirs, dircode)
+                    started = false
+                    runstart = nil
+                    runlast = nil
                 end
             end
-            prev = entry
         end
-    end
-    -- make sure to close the run at end of line
-    local last = list[size]
-    if not last.enddir then
-        local n = 0
-        for i=1,size do
-            local entry = list[i]
-            local e = entry.enddir
-            local b = entry.begindir
-            if e then
-                n = n - 1
-            end
-            if b then
-                n = n + 1
-                stack[n] = b
-            end
-        end
-        if n > 0 then
-            if trace_list and n > 1 then
-                report_directions("unbalanced list")
-            end
-            last.enddir = stack[n]
+
+        -- Close at end if still open
+        if started and runlast then
+            insert(list[runlast].enddirs, dircode)
         end
     end
 end
@@ -918,14 +901,30 @@ local function apply_to_list(list,size,head,pardir)
         end
         local id       = getid(current)
         local entry    = list[index]
-        local begindir = entry.begindir
-        local enddir   = entry.enddir
+
         local p = properties[current]
         if p then
             p.directions = true
         else
             properties[current] = { directions = true }
         end
+
+        -- Handle begindirs
+        if entry.begindirs and #entry.begindirs > 0 then
+            if id == par_code and startofpar(current) then
+                -- par should always be the 1st node, insert begindirs AFTER it
+                for _, begindir in ipairs(entry.begindirs) do
+                    head, current = insertnodeafter(head,current,new_direction(begindir))
+                end
+                entry.begindirs = {} -- Clear so we don't insert again
+            else
+                -- Insert begindirs BEFORE current node
+                for _, begindir in ipairs(entry.begindirs) do
+                    head = insertnodebefore(head,current,new_direction(begindir))
+                end
+            end
+        end
+
         if id == glyph_code then
             local mirror = entry.mirror
             if mirror then
@@ -949,8 +948,8 @@ local function apply_to_list(list,size,head,pardir)
             setdirection(current,pardir) -- is this really needed?
         elseif id == glue_code then
             -- Maybe I should also fix dua and dub but on the other hand ... why?
-            if enddir and getsubtype(current) == parfillskip_code then
-                -- insert the last enddir before \parfillskip glue
+            if entry.enddirs and #entry.enddirs > 0 and getsubtype(current) == parfillskip_code then
+                -- insert the enddirs before \parfillskip glue
                 local c = current
                 local p = getprev(c)
                 if p and getid(p) == glue_code and getsubtype(p) == parfillleftskip_code then
@@ -960,20 +959,13 @@ local function apply_to_list(list,size,head,pardir)
                 if p and getid(p) == penalty_code then -- linepenalty
                     c = p
                 end
-                -- there is always a par nodes so head will stay
-                head = insertnodebefore(head,c,new_direction(enddir,true))
-                enddir = false
-            end
-        elseif begindir then
-            if id == par_code and startofpar(current) then
-                -- par should always be the 1st node
-                head, current = insertnodeafter(head,current,new_direction(begindir))
-                begindir = nil
+                for _, enddir in ipairs(entry.enddirs) do
+                    head = insertnodebefore(head,c,new_direction(enddir,true))
+                end
+                entry.enddirs = {} -- Clear so we don't insert again below
             end
         end
-        if begindir then
-            head = insertnodebefore(head,current,new_direction(begindir))
-        end
+
         local skip = entry.skip
         if skip and skip > 0 then
             for i=1,skip do
@@ -986,9 +978,14 @@ local function apply_to_list(list,size,head,pardir)
                 end
             end
         end
-        if enddir then
-            head, current = insertnodeafter(head,current,new_direction(enddir,true))
+
+        -- Insert all enddirs AFTER current node (in reverse order for proper nesting)
+        if entry.enddirs and #entry.enddirs > 0 then
+            for i = #entry.enddirs, 1, -1 do
+                head, current = insertnodeafter(head,current,new_direction(entry.enddirs[i],true))
+            end
         end
+
         if not entry.remove then
             current = getnext(current)
         elseif remove_controls then
___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the 
Wiki!

maillist : [email protected] / 
https://mailman.ntg.nl/mailman3/lists/ntg-context.ntg.nl
webpage  : https://www.pragma-ade.nl / https://context.aanhet.net (mirror)
archive  : https://github.com/contextgarden/context
wiki     : https://wiki.contextgarden.net
___________________________________________________________________________________

Reply via email to