You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
412 lines
11 KiB
412 lines
11 KiB
--
|
|
-- This is file `babel-bidi-basic.lua',
|
|
-- generated with the docstrip utility.
|
|
--
|
|
-- The original source files were:
|
|
--
|
|
-- babel.dtx (with options: `basic')
|
|
--
|
|
--
|
|
-- Copyright (C) 2012-2020 Javier Bezos and Johannes L. Braams.
|
|
-- Copyright (C) 1989-2012 Johannes L. Braams and
|
|
-- any individual authors listed elsewhere in this file.
|
|
-- All rights reserved.
|
|
--
|
|
-- This file is part of the Babel system.
|
|
-- --------------------------------------
|
|
--
|
|
-- It may be distributed and/or modified under the
|
|
-- conditions of the LaTeX Project Public License, either version 1.3
|
|
-- of this license or (at your option) any later version.
|
|
-- The latest version of this license is in
|
|
-- http://www.latex-project.org/lppl.txt
|
|
-- and version 1.3 or later is part of all distributions of LaTeX
|
|
-- version 2003/12/01 or later.
|
|
--
|
|
-- This work has the LPPL maintenance status "maintained".
|
|
--
|
|
-- The Current Maintainer of this work is Javier Bezos.
|
|
--
|
|
-- The list of derived (unpacked) files belonging to the distribution
|
|
-- and covered by LPPL is defined by the unpacking scripts (with
|
|
-- extension |.ins|) which are part of the distribution.
|
|
--
|
|
|
|
Babel = Babel or {}
|
|
|
|
-- eg, Babel.fontmap[1][<prefontid>]=<dirfontid>
|
|
|
|
Babel.fontmap = Babel.fontmap or {}
|
|
Babel.fontmap[0] = {} -- l
|
|
Babel.fontmap[1] = {} -- r
|
|
Babel.fontmap[2] = {} -- al/an
|
|
|
|
Babel.bidi_enabled = true
|
|
Babel.mirroring_enabled = true
|
|
|
|
require('babel-data-bidi.lua')
|
|
|
|
local characters = Babel.characters
|
|
local ranges = Babel.ranges
|
|
|
|
local DIR = node.id('dir')
|
|
local GLYPH = node.id('glyph')
|
|
|
|
local function insert_implicit(head, state, outer)
|
|
local new_state = state
|
|
if state.sim and state.eim and state.sim ~= state.eim then
|
|
dir = ((outer == 'r') and 'TLT' or 'TRT') -- ie, reverse
|
|
local d = node.new(DIR)
|
|
d.dir = '+' .. dir
|
|
node.insert_before(head, state.sim, d)
|
|
local d = node.new(DIR)
|
|
d.dir = '-' .. dir
|
|
node.insert_after(head, state.eim, d)
|
|
end
|
|
new_state.sim, new_state.eim = nil, nil
|
|
return head, new_state
|
|
end
|
|
|
|
local function insert_numeric(head, state)
|
|
local new
|
|
local new_state = state
|
|
if state.san and state.ean and state.san ~= state.ean then
|
|
local d = node.new(DIR)
|
|
d.dir = '+TLT'
|
|
_, new = node.insert_before(head, state.san, d)
|
|
if state.san == state.sim then state.sim = new end
|
|
local d = node.new(DIR)
|
|
d.dir = '-TLT'
|
|
_, new = node.insert_after(head, state.ean, d)
|
|
if state.ean == state.eim then state.eim = new end
|
|
end
|
|
new_state.san, new_state.ean = nil, nil
|
|
return head, new_state
|
|
end
|
|
|
|
-- TODO - \hbox with an explicit dir can lead to wrong results
|
|
-- <R \hbox dir TLT{<R>}> and <L \hbox dir TRT{<L>}>. A small attempt
|
|
-- was s made to improve the situation, but the problem is the 3-dir
|
|
-- model in babel/Unicode and the 2-dir model in LuaTeX don't fit
|
|
-- well.
|
|
|
|
function Babel.bidi(head, ispar, hdir)
|
|
local d -- d is used mainly for computations in a loop
|
|
local prev_d = ''
|
|
local new_d = false
|
|
|
|
local nodes = {}
|
|
local outer_first = nil
|
|
local inmath = false
|
|
|
|
local glue_d = nil
|
|
local glue_i = nil
|
|
|
|
local has_en = false
|
|
local first_et = nil
|
|
|
|
local ATDIR = luatexbase.registernumber'bbl@attr@dir'
|
|
|
|
local save_outer
|
|
local temp = node.get_attribute(head, ATDIR)
|
|
if temp then
|
|
temp = temp % 3
|
|
save_outer = (temp == 0 and 'l') or
|
|
(temp == 1 and 'r') or
|
|
(temp == 2 and 'al')
|
|
elseif ispar then -- Or error? Shouldn't happen
|
|
save_outer = ('TRT' == tex.pardir) and 'r' or 'l'
|
|
else -- Or error? Shouldn't happen
|
|
save_outer = ('TRT' == hdir) and 'r' or 'l'
|
|
end
|
|
-- when the callback is called, we are just _after_ the box,
|
|
-- and the textdir is that of the surrounding text
|
|
-- if not ispar and hdir ~= tex.textdir then
|
|
-- save_outer = ('TRT' == hdir) and 'r' or 'l'
|
|
-- end
|
|
local outer = save_outer
|
|
local last = outer
|
|
-- 'al' is only taken into account in the first, current loop
|
|
if save_outer == 'al' then save_outer = 'r' end
|
|
|
|
local fontmap = Babel.fontmap
|
|
|
|
for item in node.traverse(head) do
|
|
|
|
-- In what follows, #node is the last (previous) node, because the
|
|
-- current one is not added until we start processing the neutrals.
|
|
|
|
-- three cases: glyph, dir, otherwise
|
|
if item.id == GLYPH
|
|
or (item.id == 7 and item.subtype == 2) then
|
|
|
|
local d_font = nil
|
|
local item_r
|
|
if item.id == 7 and item.subtype == 2 then
|
|
item_r = item.replace -- automatic discs have just 1 glyph
|
|
else
|
|
item_r = item
|
|
end
|
|
local chardata = characters[item_r.char]
|
|
d = chardata and chardata.d or nil
|
|
if not d or d == 'nsm' then
|
|
for nn, et in ipairs(ranges) do
|
|
if item_r.char < et[1] then
|
|
break
|
|
elseif item_r.char <= et[2] then
|
|
if not d then d = et[3]
|
|
elseif d == 'nsm' then d_font = et[3]
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
d = d or 'l'
|
|
|
|
-- A short 'pause' in bidi for mapfont
|
|
d_font = d_font or d
|
|
d_font = (d_font == 'l' and 0) or
|
|
(d_font == 'nsm' and 0) or
|
|
(d_font == 'r' and 1) or
|
|
(d_font == 'al' and 2) or
|
|
(d_font == 'an' and 2) or nil
|
|
if d_font and fontmap and fontmap[d_font][item_r.font] then
|
|
item_r.font = fontmap[d_font][item_r.font]
|
|
end
|
|
|
|
if new_d then
|
|
table.insert(nodes, {nil, (outer == 'l') and 'l' or 'r', nil})
|
|
if inmath then
|
|
attr_d = 0
|
|
else
|
|
attr_d = node.get_attribute(item, ATDIR)
|
|
attr_d = attr_d % 3
|
|
end
|
|
if attr_d == 1 then
|
|
outer_first = 'r'
|
|
last = 'r'
|
|
elseif attr_d == 2 then
|
|
outer_first = 'r'
|
|
last = 'al'
|
|
else
|
|
outer_first = 'l'
|
|
last = 'l'
|
|
end
|
|
outer = last
|
|
has_en = false
|
|
first_et = nil
|
|
new_d = false
|
|
end
|
|
|
|
if glue_d then
|
|
if (d == 'l' and 'l' or 'r') ~= glue_d then
|
|
table.insert(nodes, {glue_i, 'on', nil})
|
|
end
|
|
glue_d = nil
|
|
glue_i = nil
|
|
end
|
|
|
|
elseif item.id == DIR then
|
|
d = nil
|
|
new_d = true
|
|
|
|
elseif item.id == node.id'glue' and item.subtype == 13 then
|
|
glue_d = d
|
|
glue_i = item
|
|
d = nil
|
|
|
|
elseif item.id == node.id'math' then
|
|
inmath = (item.subtype == 0)
|
|
|
|
else
|
|
d = nil
|
|
end
|
|
|
|
-- AL <= EN/ET/ES -- W2 + W3 + W6
|
|
if last == 'al' and d == 'en' then
|
|
d = 'an' -- W3
|
|
elseif last == 'al' and (d == 'et' or d == 'es') then
|
|
d = 'on' -- W6
|
|
end
|
|
|
|
-- EN + CS/ES + EN -- W4
|
|
if d == 'en' and #nodes >= 2 then
|
|
if (nodes[#nodes][2] == 'es' or nodes[#nodes][2] == 'cs')
|
|
and nodes[#nodes-1][2] == 'en' then
|
|
nodes[#nodes][2] = 'en'
|
|
end
|
|
end
|
|
|
|
-- AN + CS + AN -- W4 too, because uax9 mixes both cases
|
|
if d == 'an' and #nodes >= 2 then
|
|
if (nodes[#nodes][2] == 'cs')
|
|
and nodes[#nodes-1][2] == 'an' then
|
|
nodes[#nodes][2] = 'an'
|
|
end
|
|
end
|
|
|
|
-- ET/EN -- W5 + W7->l / W6->on
|
|
if d == 'et' then
|
|
first_et = first_et or (#nodes + 1)
|
|
elseif d == 'en' then
|
|
has_en = true
|
|
first_et = first_et or (#nodes + 1)
|
|
elseif first_et then -- d may be nil here !
|
|
if has_en then
|
|
if last == 'l' then
|
|
temp = 'l' -- W7
|
|
else
|
|
temp = 'en' -- W5
|
|
end
|
|
else
|
|
temp = 'on' -- W6
|
|
end
|
|
for e = first_et, #nodes do
|
|
if nodes[e][1].id == GLYPH then nodes[e][2] = temp end
|
|
end
|
|
first_et = nil
|
|
has_en = false
|
|
end
|
|
|
|
if d then
|
|
if d == 'al' then
|
|
d = 'r'
|
|
last = 'al'
|
|
elseif d == 'l' or d == 'r' then
|
|
last = d
|
|
end
|
|
prev_d = d
|
|
table.insert(nodes, {item, d, outer_first})
|
|
end
|
|
|
|
outer_first = nil
|
|
|
|
end
|
|
|
|
-- TODO -- repeated here in case EN/ET is the last node. Find a
|
|
-- better way of doing things:
|
|
if first_et then -- dir may be nil here !
|
|
if has_en then
|
|
if last == 'l' then
|
|
temp = 'l' -- W7
|
|
else
|
|
temp = 'en' -- W5
|
|
end
|
|
else
|
|
temp = 'on' -- W6
|
|
end
|
|
for e = first_et, #nodes do
|
|
if nodes[e][1].id == GLYPH then nodes[e][2] = temp end
|
|
end
|
|
end
|
|
|
|
-- dummy node, to close things
|
|
table.insert(nodes, {nil, (outer == 'l') and 'l' or 'r', nil})
|
|
|
|
--------------- NEUTRAL -----------------
|
|
|
|
outer = save_outer
|
|
last = outer
|
|
|
|
local first_on = nil
|
|
|
|
for q = 1, #nodes do
|
|
local item
|
|
|
|
local outer_first = nodes[q][3]
|
|
outer = outer_first or outer
|
|
last = outer_first or last
|
|
|
|
local d = nodes[q][2]
|
|
if d == 'an' or d == 'en' then d = 'r' end
|
|
if d == 'cs' or d == 'et' or d == 'es' then d = 'on' end --- W6
|
|
|
|
if d == 'on' then
|
|
first_on = first_on or q
|
|
elseif first_on then
|
|
if last == d then
|
|
temp = d
|
|
else
|
|
temp = outer
|
|
end
|
|
for r = first_on, q - 1 do
|
|
nodes[r][2] = temp
|
|
item = nodes[r][1] -- MIRRORING
|
|
if Babel.mirroring_enabled and item.id == GLYPH
|
|
and temp == 'r' and characters[item.char] then
|
|
local font_mode = font.fonts[item.font].properties.mode
|
|
if font_mode ~= 'harf' and font_mode ~= 'plug' then
|
|
item.char = characters[item.char].m or item.char
|
|
end
|
|
end
|
|
end
|
|
first_on = nil
|
|
end
|
|
|
|
if d == 'r' or d == 'l' then last = d end
|
|
end
|
|
|
|
-------------- IMPLICIT, REORDER ----------------
|
|
|
|
outer = save_outer
|
|
last = outer
|
|
|
|
local state = {}
|
|
state.has_r = false
|
|
|
|
for q = 1, #nodes do
|
|
|
|
local item = nodes[q][1]
|
|
|
|
outer = nodes[q][3] or outer
|
|
|
|
local d = nodes[q][2]
|
|
|
|
if d == 'nsm' then d = last end -- W1
|
|
if d == 'en' then d = 'an' end
|
|
local isdir = (d == 'r' or d == 'l')
|
|
|
|
if outer == 'l' and d == 'an' then
|
|
state.san = state.san or item
|
|
state.ean = item
|
|
elseif state.san then
|
|
head, state = insert_numeric(head, state)
|
|
end
|
|
|
|
if outer == 'l' then
|
|
if d == 'an' or d == 'r' then -- im -> implicit
|
|
if d == 'r' then state.has_r = true end
|
|
state.sim = state.sim or item
|
|
state.eim = item
|
|
elseif d == 'l' and state.sim and state.has_r then
|
|
head, state = insert_implicit(head, state, outer)
|
|
elseif d == 'l' then
|
|
state.sim, state.eim, state.has_r = nil, nil, false
|
|
end
|
|
else
|
|
if d == 'an' or d == 'l' then
|
|
if nodes[q][3] then -- nil except after an explicit dir
|
|
state.sim = item -- so we move sim 'inside' the group
|
|
else
|
|
state.sim = state.sim or item
|
|
end
|
|
state.eim = item
|
|
elseif d == 'r' and state.sim then
|
|
head, state = insert_implicit(head, state, outer)
|
|
elseif d == 'r' then
|
|
state.sim, state.eim = nil, nil
|
|
end
|
|
end
|
|
|
|
if isdir then
|
|
last = d -- Don't search back - best save now
|
|
elseif d == 'on' and state.san then
|
|
state.san = state.san or item
|
|
state.ean = item
|
|
end
|
|
|
|
end
|
|
|
|
return node.prev(head) or head
|
|
end
|
|
|
|
|