문자열 관련 검색 및 교체 작업을 수행하는 일부 lua 코드와 글꼴 사양 간의 잘못된 상호 작용

문자열 관련 검색 및 교체 작업을 수행하는 일부 lua 코드와 글꼴 사양 간의 잘못된 상호 작용

업데이트 2013/05/28:셀놀릭패키지가 이제 CTAN에 있습니다. 댓글과 비판은 언제나 환영입니다! 패키지에 관해 저에게 연락하려면 패키지 사용자 가이드 제목 페이지 하단에 제공된 이메일 주소를 사용하세요. (아래 질문에 설명된 패키지 상태와 관련하여 저는 적어도 하나의 버그를 처리했으며 나머지 버그에 대한 더 나은 해결 방법을 제안합니다. 적어도 제가 알고 있는 버그입니다!)


저는 CTAN에 대한 "공식" 릴리스를 위해 LuaLaTeX 패키지를 준비하는 중이지만 먼저 몇 가지 남은 버그를 처리해야 합니다. 이 질문에 설명된 버그는 내 패키지의 잘못된 동작과 관련이 있습니다.만약에패키지 fontspec가 로드되었습니다. 로드되지 않은 경우 fontspec여기에 설명된 문제가 발생하지 않습니다. 분명히, 내 패키지의 잠재적 사용자에게 물어보는 것입니다.~ 아니다로드하는 fontspec것은 옵션이 아닙니다. 참고로 내 시스템의 LuaTeX 버전 식별자 문자열은 beta-0.70.2-2012062819MacTeX2012와 함께 배포되는 " "입니다. selnolig인쇄 합자를 자동으로 선택적으로 억제하는 전체 패키지에 대한 자세한 내용은 다음을 참조하세요.활자체 합자 억제를 자동화하는 새로운 패키지 selnolig.

MWE(아래 이미지 참조, 아래 이미지에 제공된 코드)는 다음과 같은 경우에 합자 억제를 수행하지 못한 여러 사례를 보여줍니다.경우에만-- fontspec로드되었습니다. 특히 다음의 경우 합자 억제가 실패합니다.

  • 바로 뒤에 주석( %) 기호가 오는 단어
  • \footnote및와 같은 명령 인수의 마지막 단어\section
  • enumerate및 와 같은 환경의 시작 바로 앞에 오는 단어itemize
  • 명령문의 마지막 단어 , 즉 다음 명령문 및/또는 환경의 종료 지시문 \item앞의 마지막 단어\item\end{enumerate/itemize}

이러한 문제의 일반적인 주제는 문제의 단어(및 후행 구두점 문자 포함)가 일부 환경, 그룹 또는 일부 매크로에 대한 인수의 맨 끝에 있는 경우 발생한다는 것입니다. 모든 경우에 일종의 "해결책"은 공백, 빈 줄 또는 공백에 \vphantom{x}[!]와 같은 것을 삽입하는 것입니다. 분명히 이러한 치료법은~ 아니다실제 솔루션이지만 단지 난잡한 해킹일 뿐이므로 내 패키지 사용자에게 이러한 해킹을 구현하도록 요청하는 것은 확실히 고려하지 않을 것입니다.

내 질문그렇다면:

  • fontspec패키지(또는 에서 로드한 일부 패키지 ) 가 수행하는 모든 작업에 대해 Lua 코드를 더욱 강력하게 만들려면 어떻게 해야 합니까 fontspec?

  • 내 Lua 코드와의 간섭을 억제하기 위해 둘 중 하나 fontspec(또는 에서 호출한 패키지 중 일부 ) 를 로드할 수 있는 방법이 있습니까 ?fontspec

  • 아니면 어쨌든 수정해야 할 버그 fontspec(또는 에서 로드한 하나 이상의 패키지 ) 를 발견했습니까 ?fontspec

여기에 이미지 설명을 입력하세요

% !TEX TS-program = lualatex
\documentclass{article}
% If the next line is commented out, everything works fine!
\usepackage{fontspec} 

  \RequirePackage{luatexbase,luacode,expl3}
  % Load lua code
  \directlua{  require("ld-orig.lua")  }  % see below for contents of ld-orig.lua
  % Define the user macro "nolig"
  \providecommand\nolig[2]{ \directlua{
        suppress_liga( "\luatexluaescapestring{#1}",
                       "\luatexluaescapestring{#2}" )}}
  % Provide a ligature suppression rule
  %  (the full package obviously provides many more such macros)
  \nolig{lfful}{lf|ful} % shelfful -> shelf|ful

% Just for this MWE:
   \usepackage[textheight=8cm]{geometry}
   \setlength\parindent{0pt}
   \pagestyle{empty} 

\begin{document}    
Two shelffuls of \TeX-related books: it works!

\bigskip
% word to be de-ligated is followed immediately by % (comment character)
Ligature suppression doesn't work here: shelfful% 

% leaving a space between word and % makes it work even if fontspec is loaded
But it does work in this case: shelfful % 

\bigskip
bad\footnote{This doesn't work: shelfful.} % w/o space 
good\footnote{But this does work: shelfful. \vphantom{x}} % w/ space and \vphantom directive

\bigskip
% Two more problem cases: (i) last word before start of an
% itemize/enumerate environment, (ii) last word of an \item
one shelfful, two shelffuls % no ligature suppression for "shelffuls"
\begin{itemize}
\item shelfful              % no ligature suppression here either
\item shelfful \vphantom{x} % inserting space and \vphantom does the trick...
\end{itemize}

% problem also occurs in arguments of sectioning commands
\section*{sad shelfful}         % again no ligature suppression
\subsection*{happy shelfful }   % adding space at end of argument makes it work!
\end{document}

ld-orig.lua의 내용:

--- Credits to Patrick Gundlach, Taco Hoekwater, and Steffen Hildebrandt!
local glyph = node.id('glyph')
local glue = node.id("glue")
local whatsit = node.id("whatsit")
local userdefined

for n,v in pairs(node.whatsits()) do
  if v == 'user_defined' then userdefined = n end
end

local identifier = 123456  -- any unique identifier 
local noliga={}
debug=false  -- default: don't write debugging info to log file

function debug_info(s)
  if debug then
    texio.write_nl(s)
  end
end

local blocknode = node.new(whatsit, userdefined)
blocknode.type = 100
blocknode.user_id = identifier

function process_ligatures(nodes,tail)
  local s={}
  local current_node=nodes
  local build_liga_table =  function(strlen,t)
    local p={}
    for i = 1, strlen do
      p[i]=0
    end
    for k,v in pairs(t) do
      -- debug_info("Match: "..v[3])
      local c= string.find(noliga[v[3]],"|")
      local correction=1
      while c~=nil do
         -- debug_info("Position "..(v[1]+c))
         p[v[1]+c-correction] = 1
         c = string.find(noliga[v[3]],"|",c+1)  
         correction=correction+1
      end   
    end
    -- debug_info("Liga table: "..table.concat(p, ""))
    return p
  end
  local apply_ligatures=function(head,ligatures)
     local i=1
     local hh=head
     local last=node.tail(head)
     for curr in node.traverse_id(glyph,head) do
       if ligatures[i]==1 then
         -- debug_info("Current glyph: "..unicode.utf8.char(curr.char))
         node.insert_before(hh,curr, node.copy(blocknode))
         hh=curr
       end 
       last=curr
       if i==#ligatures then 
         -- debug_info("Leave node list on position: "..i)
         break 
       end
       i=i+1
     end
     if(last~=nil) then
       -- debug_info("Last char: "..unicode.utf8.char(last.char))
     end--]]
  end
  for t in node.traverse(nodes) do
    if t.id==glyph then

      s[#s+1]=string.lower(unicode.utf8.char(t.char))
    elseif t.id== glue then
      local f=string.gsub(table.concat(s,""),"[\\?!,\\.]+","") -- add all interpunction
      local throwliga={}    
      for k, v in pairs(noliga) do
        local count=1
        local match= string.find(f,k)
        while match do
          count=match
          -- debug_info("pattern match: "..f .." - "..k)  
          local n = match + string.len(k)-1
          table.insert(throwliga,{match,n,k})
          match= string.find(f,k,count+1)
        end
      end
      if #throwliga==0 then 
        -- debug_info("No ligature substitution for: "..f)  
      else
        -- debug_info("Do ligature substitution for: "..f)  
        local ligabreaks=build_liga_table(f:len(),throwliga)
        apply_ligatures(current_node,ligabreaks)
      end
      s={}
      current_node=t
    end    
  end
end

function suppress_liga(s,t)
  noliga[s]=t
end

function drop_special_nodes (nodes,tail)
  for t in node.traverse(nodes) do
     if t.id == whatsit and t.subtype == userdefined and t.user_id == identifier then
        node.remove(nodes,t)
        node.free(t)
     end
  end
end

luatexbase.add_to_callback("ligaturing", process_ligatures,"Filter ligatures", 1) 

추신: 이 게시물에 설명된 버그에 대한 해결책입니다. 위에 제공된 lua 코드의 주요 시퀀스는 버그의 원인입니다.

  for t in node.traverse(nodes) do
     if t.id==glyph then
        s[#s+1]=string.lower(unicode.utf8.char(t.char))
     elseif t.id==glue then
        ...

버그를 수정하는 데 필요한 것은 이 코드 조각을 다음과 같이 변경하는 것뿐입니다.

  for t in node.traverse(nodes) do
     if t.id==glyph then
        s[#s+1]=string.lower(unicode.utf8.char(t.char))
     end
     if ( t.id==glue or t.next==nil or t.id==kern or t.id==rule ) then
        ...

요점은 selnolig에 의해 처리되어야 하는 문자 시퀀스가 ​​일정량의 (TeX) "접착제"(예: 공백)가 아닌 다른 방식으로 끝날 수 있다는 것입니다. 단어가 처리되는 가장 마지막 항목인 경우 시퀀스를 종료하는 또 다른 방법입니다. 예를 들어 \section{}; 그렇다면 변수는 t.next와 같습니다 nil. 마지막으로, 사용자가 "커른" 또는 "규칙" 항목을 수동으로 삽입한 경우를 대비하여 나머지 두 if조건( t.id==kern및 )이 제공됩니다.t.id==rule

버그 수정은 패키지 버전 0.220에 통합되었습니다.

답변1

문제를 분석해 보겠습니다. ligaturing계속해서 호출하지만 콜백이 아무런 효과가 없는 것 같은 경우도 있습니다. 나는 두 가지 사례를 살펴보고 싶습니다: 각주:

bad\footnote{This doesn't work: shelfful.} % w/o space 
good\footnote{But this does work: shelfful. \vphantom{x}} % w/ space and \vphantom directive

작은 모듈을 사용하여 합자 콜백에 전달되는 노드 목록을 살펴보겠습니다. viznodelist.

입구 지점에서 루아 코드를 다음과 같이 약간 변경합니다 process_ligatures().

...
require("viznodelist")
function process_ligatures(nodes,tail)
  counter = counter or 0
  counter = counter + 1
  viznodelist.nodelist_visualize(nodes,string.format("liga%d.gv",counter))

첫 번째 각주('bad')는 다음과 같습니다.

노드 목록, 완료

세부 사항 포함 (오른쪽 상단)

여기에 이미지 설명을 입력하세요

"좋은" 노드 목록은 다음과 같습니다:

여기에 이미지 설명을 입력하세요

이제 코드를 살펴보면 다음과 같습니다.

  for t in node.traverse(nodes) do
    if t.id==glyph then

      s[#s+1]=string.lower(unicode.utf8.char(t.char))
    elseif t.id== glue then
    ...
      (process ligatures)
    ...
    end
  end

gluea만이 합자 처리를 활성화한다는 점을 분명히 합니다 .

합자 처리를 위해 다른 종류의 루핑을 사용하는 것이 좋습니다.

활성화 여부 의 차이점 fontspec은 다음과 같습니다. 글꼴 사양이 비활성화되면 합자 콜백이 모든 합자를 비활성화합니다. 당신이 보는 것은 명령의 효과가 아니라 \nolig일반적인 "합자 없음" 모드입니다. 다음과 같은 단어를 사용해 fluffiest fish보면 알 수 있습니다. 글꼴 사양을 활성화하면 사용하는 코드로 차단하지 않는 한 결과는 "항상 합자"입니다.

따라서 합자 콜백은 상황을 처리하는 완벽한 방법이 아닙니다. 그러나 node.ligaturing()콜백 시작 부분에 전화를 걸고 수행 중인 작업을 수행할 수 있습니다. 그러나 이는 아마도 글꼴 사양을 방해할 것입니다.

관련 정보