if not modules then modules = { } end modules ['typo-itc'] = { version = 1.001, comment = "companion to typo-itc.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local utfchar = utf.char local trace_italics = false trackers.register("typesetters.italics", function(v) trace_italics = v end) local report_italics = logs.reporter("nodes","italics") local threshold = 0.5 trackers.register("typesetters.threshold", function(v) threshold = v == true and 0.5 or tonumber(v) end) typesetters.italics = typesetters.italics or { } local italics = typesetters.italics local nodecodes = nodes.nodecodes local glyph_code = nodecodes.glyph local kern_code = nodecodes.kern local glue_code = nodecodes.glue local disc_code = nodecodes.disc local math_code = nodecodes.math local tasks = nodes.tasks local nuts = nodes.nuts local nodepool = nuts.pool local tonode = nuts.tonode local tonut = nuts.tonut local getfield = nuts.getfield local getprev = nuts.getprev local getnext = nuts.getnext local getid = nuts.getid local getfont = nuts.getfont local getchar = nuts.getchar local getattr = nuts.getattr local setattr = nuts.setattr local setfield = nuts.setfield local insert_node_after = nuts.insert_after local delete_node = nuts.delete local end_of_math = nuts.end_of_math local find_tail = nuts.tail local texgetattribute = tex.getattribute local texsetattribute = tex.setattribute local a_italics = attributes.private("italics") local unsetvalue = attributes.unsetvalue local new_correction_kern = nodepool.fontkern local new_correction_glue = nodepool.glue local fonthashes = fonts.hashes local fontdata = fonthashes.identifiers local italicsdata = fonthashes.italics local exheights = fonthashes.exheights local implement = interfaces.implement local forcedvariant = false function typesetters.italics.forcevariant(variant) forcedvariant = variant end local function setitalicinfont(font,char) local tfmdata = fontdata[font] local character = tfmdata.characters[char] if character then local italic = character.italic_correction if not italic then local autoitalicamount = tfmdata.properties.autoitalicamount or 0 if autoitalicamount ~= 0 then local description = tfmdata.descriptions[char] if description then italic = description.italic if not italic then local boundingbox = description.boundingbox italic = boundingbox[3] - description.width + autoitalicamount if italic < 0 then -- < 0 indicates no overshoot or a very small auto italic italic = 0 end end if italic ~= 0 then italic = italic * tfmdata.parameters.hfactor end end end if trace_italics then report_italics("setting italic correction of %C of font %a to %p",char,font,italic) end character.italic_correction = italic or 0 end return italic else return 0 end end -- todo: clear attribute local function okay(data,current,font,prevchar,previtalic,char,what) if not data then if trace_italics then report_italics("ignoring %p between %s italic %C and italic %C",previtalic,what,prevchar,char) end return false end if threshold then local ht = getfield(current,"height") local ex = exheights[font] local th = threshold * ex if ht <= th then if trace_italics then report_italics("ignoring correction between %s italic %C and regular %C, height %p less than threshold %p",prevchar,what,char,ht,th) end return false end end if trace_italics then report_italics("inserting %p between %s italic %C and regular %C",previtalic,what,prevchar,char) end return true end -- maybe: with_attributes(current,n) : -- -- local function correction_kern(kern,n) -- return with_attributes(new_correction_kern(kern),n) -- end local function correction_kern(kern,n) local k = new_correction_kern(kern) if n then local a = getfield(n,"attr") if a then -- maybe not setfield(k,"attr",a) -- can be a marked content (border case) end end return k end local function correction_glue(glue,n) local g = new_correction_glue(glue) if n then local a = getfield(n,"attr") if a then -- maybe not setfield(g,"attr",a) -- can be a marked content (border case) end end return g end function italics.handler(head) local prev = nil local prevchar = nil local prevhead = tonut(head) local previtalic = 0 local previnserted = nil local replace = nil local replacechar = nil local replacehead = nil local replaceitalic = 0 local replaceinserted = nil local post = nil local postchar = nil local posthead = nil local postitalic = 0 local postinserted = nil local current = prevhead local done = false local lastfont = nil local lastattr = nil while current do local id = getid(current) if id == glyph_code then local font = getfont(current) local char = getchar(current) local data = italicsdata[font] if font ~= lastfont then if previtalic ~= 0 then if okay(data,current,font,prevchar,previtalic,char,"glyph") then insert_node_after(prevhead,prev,correction_kern(previtalic,current)) done = true end elseif previnserted and data then if trace_italics then report_italics("deleting last correction before %s %C",char,"glyph") end delete_node(prevhead,previnserted) else -- if replaceitalic ~= 0 then if okay(data,replace,font,replacechar,replaceitalic,char,"replace") then insert_node_after(replacehead,replace,correction_kern(replaceitalic,current)) done = true end replaceitalic = 0 elseif replaceinserted and data then if trace_italics then report_italics("deleting last correction before %s %C","replace",char) end delete_node(replacehead,replaceinserted) end -- if postitalic ~= 0 then if okay(data,post,font,postchar,postitalic,char,"post") then insert_node_after(posthead,post,correction_kern(postitalic,current)) done = true end postitalic = 0 elseif postinserted and data then if trace_italics then report_italics("deleting last correction before %s %C","post",char) end delete_node(posthead,postinserted) end end -- lastfont = font end if data then local attr = forcedvariant or getattr(current,a_italics) if attr and attr > 0 then local cd = data[char] if not cd then -- this really can happen previtalic = 0 else previtalic = cd.italic or cd.italic_correction if not previtalic then previtalic = setitalicinfont(font,char) -- calculated once -- previtalic = 0 end if previtalic ~= 0 then lastfont = font lastattr = attr prev = current -- prevhead = head prevchar = char end end else previtalic = 0 end else previtalic = 0 end previnserted = nil replaceinserted = nil postinserted = nil elseif id == disc_code then previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 replace = getfield(current,"replace") if replace then local current = find_tail(replace) if getid(current) ~= glyph_code then current = getprev(current) end if current and getid(current) == glyph_code then local font = getfont(current) local char = getchar(current) local data = italicsdata[font] if data then local attr = forcedvariant or getattr(current,a_italics) if attr and attr > 0 then local cd = data[char] if not cd then -- this really can happen replaceitalic = 0 else replaceitalic = cd.italic or cd.italic_correction if not replaceitalic then replaceitalic = setitalicinfont(font,char) -- calculated once -- replaceitalic = 0 end if replaceitalic ~= 0 then lastfont = font lastattr = attr replacechar = char replacehead = replace replace = current end end -- else -- replaceitalic = 0 end -- else -- replaceitalic = 0 end -- else -- replaceitalic = 0 end -- replaceinserted = nil -- else -- replaceitalic = 0 -- replaceinserted = nil end post = getfield(current,"post") if post then local current = find_tail(post) if getid(current) ~= glyph_code then current = getprev(current) end if current and getid(current) == glyph_code then local font = getfont(current) local char = getchar(current) local data = italicsdata[font] if data then local attr = forcedvariant or getattr(current,a_italics) if attr and attr > 0 then local cd = data[char] if not cd then -- this really can happen -- postitalic = 0 else postitalic = cd.italic or cd.italic_correction if not postitalic then postitalic = setitalicinfont(font,char) -- calculated once -- postitalic = 0 end if postitalic ~= 0 then lastfont = font lastattr = attr postchar = char posthead = post post = current end end -- else -- postitalic = 0 end -- else -- postitalic = 0 end -- else -- postitalic = 0 end -- postinserted = nil -- else -- postitalic = 0 -- postinserted = nil end elseif id == kern_code then -- how about fontkern ? previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 elseif id == glue_code then if previtalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and glue",previtalic,"glyph",prevchar) end previnserted = correction_glue(previtalic,current) -- maybe just add ? else problem with penalties previtalic = 0 done = true insert_node_after(prevhead,prev,previnserted) else if replaceitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and glue",replaceitalic,"replace",replacechar) end replaceinserted = correction_kern(replaceitalic,current) -- needs to be a kern replaceitalic = 0 done = true insert_node_after(replacehead,replace,replaceinserted) end if postitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and glue",postitalic,"post",postchar) end postinserted = correction_kern(postitalic,current) -- needs to be a kern postitalic = 0 done = true insert_node_after(posthead,post,postinserted) end end elseif id == math_code then current = end_of_math(current) previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 else if previtalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and whatever",previtalic,"glyph",prevchar) end insert_node_after(prevhead,prev,correction_kern(previtalic,current)) previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 done = true else if replaceitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and whatever",replaceritalic,"replace",replacechar) end insert_node_after(replacehead,replace,correction_kern(replaceitalic,current)) previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 done = true end if postitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and whatever",postitalic,"post",postchar) end insert_node_after(posthead,post,correction_kern(postitalic,current)) previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 done = true end end end current = getnext(current) end if lastattr and lastattr > 1 then -- more control is needed here if previtalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and end of list",previtalic,"glyph",prevchar) end insert_node_after(prevhead,prev,correction_kern(previtalic,current)) done = true else if replaceitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and end of list",replaceitalic,"replace",replacechar) end insert_node_after(replacehead,replace,correction_kern(replaceitalic,current)) done = true end if postitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and end of list",postitalic,"post",postchar) end insert_node_after(posthead,post,correction_kern(postitalic,current)) done = true end end end return head, done end local enable enable = function() tasks.enableaction("processors","typesetters.italics.handler") if trace_italics then report_italics("enabling text italics") end enable = false end function italics.set(n) if enable then enable() end if n == variables.reset then texsetattribute(a_italics,unsetvalue) else texsetattribute(a_italics,tonumber(n) or unsetvalue) end end function italics.reset() texsetattribute(a_italics,unsetvalue) end implement { name = "setitaliccorrection", actions = italics.set, arguments = "string" } implement { name = "resetitaliccorrection", actions = italics.reset, } local variables = interfaces.variables local settings_to_hash = utilities.parsers.settings_to_hash local function setupitaliccorrection(option) -- no grouping ! if enable then enable() end local options = settings_to_hash(option) local variant = unsetvalue if options[variables.text] then variant = 1 elseif options[variables.always] then variant = 2 end -- maybe also keywords for threshold if options[variables.global] then forcedvariant = variant texsetattribute(a_italics,unsetvalue) else forcedvariant = false texsetattribute(a_italics,variant) end if trace_italics then report_italics("forcing %a, variant %a",forcedvariant,variant ~= unsetvalue and variant) end end implement { name = "setupitaliccorrection", actions = setupitaliccorrection, arguments = "string" } -- for manuals: local stack = { } implement { name = "pushitaliccorrection", actions = function() table.insert(stack,{forcedvariant, texgetattribute(a_italics) }) end } implement { name = "popitaliccorrection", actions = function() local top = table.remove(stack) forcedvariant = top[1] texsetattribute(a_italics,top[2]) end }