-- folarrow.lua:LuaLaTeXでOpenTypeの数式フォントを使うとき,矢印のグリフの外接矩形から -- 矢頭の幅と線の太さを直接求める. -- -- folarrow.extent(boxnum, vertical) -- 箱の中(入れ子の箱やリーダーも含む)のグリフのうち,矢印の向きに直交する方向の -- 広がりが最大のものを矢頭とみなし,その広がりを矢頭の幅とする. -- 線の太さは,同じフォントのマイナス記号(U+2212)の外接矩形の高さとする. -- Computer Modernなどでは,これが矢印の軸の太さと一致する. -- 結果を「幅 太さ 鍵 中心」(幅と太さはpt単位の数,鍵はフォント名を小文字の英数字だけに -- したもの,中心は矢頭の直交方向の中心(横向きならベースラインからの高さ,pt))として -- TeXに返す.グリフの形の情報がない -- フォント(TFMだけのType 1フォントなど)では何も返さない. -- -- Author: Yuwsuke Kieda -- Copyright (c) 2026 Yuwsuke Kieda -- This file is part of the folarrow package, released under the MIT License -- (see the file LICENSE). The arrow fonts of the package keep the licenses of -- their original fonts (see licenses/FONTS.md). -- folarrow = folarrow or {} local GLYPH = node.id('glyph') local HLIST = node.id('hlist') local VLIST = node.id('vlist') local GLUE = node.id('glue') -- マイナス記号の太さが矢印の軸の太さと異なるフォントの補正(実測:軸の太さ/マイナス記号の太さ) -- horizontal:\rightarrowなど,vertical:\Big\downarrowなどの縦の字形 local thickness_correction = { horizontal = { asanamath = 1.058, gfsneohellenicmath = 0.815 }, vertical = { minionmath = 1.16, kpmath = 1.087, gfsneohellenicmath = 0.79 }, } local function fontdata(fid) local h = fonts and fonts.hashes and fonts.hashes.identifiers local tfm = h and h[fid] if not tfm then return nil end local desc = tfm.descriptions or (tfm.shared and tfm.shared.rawdata and tfm.shared.rawdata.descriptions) local p = tfm.parameters if not desc or not p or not p.factor then return nil end return tfm, desc, p.factor -- factor:フォント単位1あたりのsp end -- 添字用の字形(OpenTypeのssty):数式の添字の大きさのフォントにはsstyが指定されて -- いて,最終的には置換後の字形が使われる.箱の中のグリフはまだ置換前なので,ここで -- フォントの置換表(gsub_alternate)を引いて置換後の字形にする. local ssty_cache = {} local function ssty_glyph(fid, tfm, char) local sp = tfm.specification local n = sp and sp.features and sp.features.normal and sp.features.normal.ssty n = tonumber(n) if not n or n < 1 then return char end local key = fid .. ':' .. char if ssty_cache[key] then return ssty_cache[key] end local out = char local seqs = tfm.resources and tfm.resources.sequences for _, s in ipairs(seqs or {}) do if s.features and s.features.ssty and s.type == 'gsub_alternate' then for _, st in ipairs(s.steps or {}) do local alts = st.coverage and st.coverage[char] if type(alts) == 'table' and #alts > 0 then out = alts[math.min(n, #alts)] end end end end ssty_cache[key] = out return out end -- usessty:添字用の字形に置き換えて測るか(falseなら元の字形) local function bbox_extent(fid, char, vertical, usessty) local tfm, desc, f = fontdata(fid) if not desc then return nil end local g = (usessty ~= false and desc[ssty_glyph(fid, tfm, char)]) or desc[char] local bb = g and g.boundingbox if not bb then return nil end -- 2つ目の値:直交方向の中心(横向きならベースラインからの高さ,縦向きなら左端からの位置) if vertical then return (bb[3] - bb[1]) * f, (bb[3] + bb[1]) / 2 * f else return (bb[4] - bb[2]) * f, (bb[4] + bb[2]) / 2 * f end end local function correction(tfm, vertical) local pr = tfm.properties or {} local nm = (pr.psname or pr.fontname or pr.fullname or pr.name or '') nm = nm:lower():gsub('[^%w]', '') -- 縦向きは縦向き用の補正を優先し,なければ横向きの補正を使う if vertical then for k, v in pairs(thickness_correction.vertical) do if nm:find(k, 1, true) then return v end end end for k, v in pairs(thickness_correction.horizontal) do if nm:find(k, 1, true) then return v end end return 1 end function folarrow.extent(boxnum, vertical) local box = tex.getbox(boxnum) if not box then return end local best, bestfont, bestchar, bestmid local function walk(head) for n in node.traverse(head) do local id = n.id if id == GLYPH then local e, mid = bbox_extent(n.font, n.char, vertical) if e and (not best or e > best) then best, bestfont, bestchar, bestmid = e, n.font, n.char, mid end elseif id == HLIST or id == VLIST then walk(n.head) elseif id == GLUE and n.leader then walk(n.leader) end end end walk(box.head) if not best or best <= 0 then return end local tfm = fontdata(bestfont) -- 線の太さはマイナス記号から測る.矢印自体が添字用の字形に置き換わるときだけ, -- マイナス記号も添字用の字形で測る(矢印に添字用の字形がなく,マイナス記号にだけ -- 太めの添字用の字形があるフォント(New Computer Modern Math,KpMath)があるため). local arrow_ssty = ssty_glyph(bestfont, tfm, bestchar) ~= bestchar local t = bbox_extent(bestfont, 0x2212, false, arrow_ssty) if not t or t <= 0 then return end t = t * correction(tfm, vertical) -- 3つ目はフォント名の鍵(尻の形の表を引くため.小文字の英数字だけにしたもの) local pr = tfm.properties or {} local key = (pr.psname or pr.fontname or pr.fullname or pr.name or ''):lower():gsub('[^%w]', '') -- 4つ目は矢頭の直交方向の中心(pt).横向きの矢印の3本線の高さに使う tex.sprint(string.format('%.5f %.5f %s %.5f', best / 65536, t / 65536, key, (bestmid or 0) / 65536)) end -- folarrow.hink(boxnum) -- 横向きの矢印の箱(\longrightarrowや,マイナス記号・リーダー・→からなる伸びる矢印)の -- 字面の左端と右端(箱の基準点からの位置,pt)を「左 右」としてTeXに返す.箱の直下の -- 最初のグリフの外接矩形の左端と,最後のグリフの外接矩形の右端から求める(添字用の字形にも -- 対応する).グリフの形の情報がなければ何も返さない. function folarrow.hink(boxnum) local box = tex.getbox(boxnum) if not box then return end local first, last for n in node.traverse_id(GLYPH, box.head) do first = first or n last = n end if not first then return end local function edge(n, right) local tfm, desc, f = fontdata(n.font) if not desc then return nil end local g = desc[ssty_glyph(n.font, tfm, n.char)] or desc[n.char] local bb = g and g.boundingbox if not bb then return nil end local x = node.dimensions(box.glue_set, box.glue_sign, box.glue_order, box.head, n) return x + (right and bb[3] or bb[1]) * f end local l, r = edge(first, false), edge(last, true) if not l or not r then return end tex.sprint(string.format('%.5f %.5f', l / 65536, r / 65536)) end return folarrow