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,
Udidiff --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
___________________________________________________________________________________