texlive[50924] Master/texmf-dist: luatexko (2may19)

commits+karl at tug.org commits+karl at tug.org
Thu May 2 23:10:43 CEST 2019


Revision: 50924
          http://tug.org/svn/texlive?view=revision&revision=50924
Author:   karl
Date:     2019-05-02 23:10:43 +0200 (Thu, 02 May 2019)
Log Message:
-----------
luatexko (2may19)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/luatex/luatexko/ChangeLog
    trunk/Master/texmf-dist/doc/luatex/luatexko/README
    trunk/Master/texmf-dist/doc/luatex/luatexko/luatexko-doc.pdf
    trunk/Master/texmf-dist/doc/luatex/luatexko/luatexko-doc.tex
    trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-normalize.lua
    trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-uhc2utf8.lua
    trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko.lua
    trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko.sty

Removed Paths:
-------------
    trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-core.sty

Modified: trunk/Master/texmf-dist/doc/luatex/luatexko/ChangeLog
===================================================================
--- trunk/Master/texmf-dist/doc/luatex/luatexko/ChangeLog	2019-05-01 23:38:43 UTC (rev 50923)
+++ trunk/Master/texmf-dist/doc/luatex/luatexko/ChangeLog	2019-05-02 21:10:43 UTC (rev 50924)
@@ -1,3 +1,14 @@
+2019-05-01      Dohyun Kim <nomos at ktug org>
+
+	Version 2.0
+
+	* luatexko-core.sty: removed.
+	* luatexko.lua: totally rewritten.
+	* luatexko.sty: Package option `nofontspec' removed.
+	Font options `CompressPunctuations' `RemoveClassicSpaces'
+	and `InterCharacter' added.
+	`\registerbreakablebefore' and `\registerbreakableafter' added.
+
 2019-04-06      Dohyun Kim <nomos at ktug org>
 
 	Version 1.24

Modified: trunk/Master/texmf-dist/doc/luatex/luatexko/README
===================================================================
--- trunk/Master/texmf-dist/doc/luatex/luatexko/README	2019-05-01 23:38:43 UTC (rev 50923)
+++ trunk/Master/texmf-dist/doc/luatex/luatexko/README	2019-05-02 21:10:43 UTC (rev 50924)
@@ -1,17 +1,16 @@
-LuaTeX-ko Package version 1.24 (2019/04/06)
+LuaTeX-ko Package version 2.0 (2019/05/01)
 ===========================================
 
 This is a Lua(La)TeX macro package that supports typesetting Korean
 documents including Old Hangul texts. As LuaTeX has opened up access to
 almost all the hidden routines of TeX engine, users can obtain more
-beautiful outcome using this package rather than other Hangul macros
+beautiful outcome using this package rather than other Hangul packages
 operating on other engines.
 
-LuaTeX version 0.95+ and luaotfload package version 2.7+ are required for
+LuaTeX version 1.10+ and luaotfload package version 2.96+ are required for
 this package to run.
 
-This package also requires both cjk-ko and xetexko packages for its full
-functionality.
+This package also requires cjk-ko package for its full functionality.
 
 
 License
@@ -40,7 +39,6 @@
 TeXinputs
 
     luatexko.sty            -> tex/luatex/luatexko/
-    luatexko-core.sty       -> tex/luatex/luatexko/
     luatexko.lua            -> tex/luatex/luatexko/
     luatexko-normalize.lua  -> tex/luatex/luatexko/
     luatexko-uhc2utf8.lua   -> tex/luatex/luatexko/
@@ -80,11 +78,7 @@
     [hanja]
 Load Hanja captions. Also apply other settings as [hangul] option does.
 
-    [nofontspec]
-Prevents loading of fontspec package. Entire document will be typeset
-with Type1 fonts.
 
-
 Hangul Font Commands
 --------------------
 
@@ -114,8 +108,8 @@
     \hanjafontspec    \newhanjafontfamily    \addhanjafontfeature
     \fallbackfontspec \newfallbackfontfamily \addfallbackfontfeature
 
-If any of these CJK fonts are not specified, Nanum Type1 fonts from
-nanumtype1 package will be used for typesetting CJK characters.
+If any of these CJK fonts are not specified, UnBatang/UnDotum TrueType
+fonts will be used for typesetting CJK characters.
 
 
 Hangul Font Options
@@ -134,13 +128,9 @@
 Other User Commands
 -------------------
 
-    \luatexuhcinputencoding=<number>
-When <number> is 1 or greater, UHC (aka. Windows CP949) input encoding
-is allowed. <number> 0 restores UTF-8, the default input encoding.
-
     \hangulpunctuations=<number>
-When <number> is 1 or greater, latin punctuations will be typeset with
-hangul fonts.
+When <number> is 1 or greater (being default value), latin punctuations
+will be typeset with hangul fonts.
 
     \dotemph{...}
 Emphasise Hangul or Hanja by putting dot above.

Modified: trunk/Master/texmf-dist/doc/luatex/luatexko/luatexko-doc.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/luatex/luatexko/luatexko-doc.tex
===================================================================
--- trunk/Master/texmf-dist/doc/luatex/luatexko/luatexko-doc.tex	2019-05-01 23:38:43 UTC (rev 50923)
+++ trunk/Master/texmf-dist/doc/luatex/luatexko/luatexko-doc.tex	2019-05-02 21:10:43 UTC (rev 50924)
@@ -12,7 +12,6 @@
 \documentclass[a4paper]{article}
 \usepackage[hmargin=3.8cm]{geometry}
 \usepackage[hangul]{luatexko}
-\usepackage{luamplib}
 \setmainhangulfont{Noto Serif CJK KR}[
   AutoFakeSlant,
   Script=Hangul,
@@ -36,29 +35,39 @@
 \setmonohangulfont{Noto Sans CJK KR}[
   UprightFont=* DemiLight,
   BoldFont=* Bold,
-  CharRaise=-.25ex,
+  CharRaise=-.2ex,
   Color=191970, % MidnightBlue
 ]
+\setmathhangulfont{Noto Sans CJK KR}[
+  Script=Hangul,
+  Language=Korean,
+  SizeFeatures={
+    {Size=-6,  Font=* Medium},
+    {Size=6-9, Font=* Regular},
+    {Size=9-,  Font=* DemiLight},
+  },
+]
+\newhangulfontface\rubyfont{Noto Serif CJK KR Medium}[
+  Script=Hangul,
+  Language=Korean,
+]
 \usepackage{hologo}
 \def\luatex{\hologo{LuaTeX}}
-\def\MP{\hologo{METAPOST}}
 \def\XeTeX{\hologo{XeTeX}}
 \usepackage[pdfencoding=auto,bookmarksnumbered,hidelinks]{hyperref}
 
 \edef\verbatim{\unexpanded\expandafter{\verbatim\linespread{1.1}\selectfont}}
 \edef\itemize{\unexpanded\expandafter{\itemize\small}}
-\catcode`|=13 \def|{\verb|\let\\=\textbackslash} %|
+\catcode`|=13 \protected\def|{\verb|\let\\=\textbackslash} %|
+\protected\def\cs#1{\texttt{\language=1 \textbackslash\detokenize{#1}}}
 \def\logoko{\textsf{k}\kern-.0625em\textit{o}}
 \def\luatexko{\luatex-\logoko}
 \def\texlive{\TeX\ Live}
 \def\kotex{\logoko\kern-.1ex .\kern-.1667em \TeX}
-\edef\section#1{\unexpanded\expandafter{\section{\empty}\vskip-5pt\hrule\nobreak\vskip5pt}}
 \def\hemph#1{\textsf{\bfseries #1}}
-\def\cs#1{\texttt{\textbackslash\detokenize{#1}}}
 \def\ldots{$\dots$}
 \registerpunctuations{`-}
-\widowpenalty10000 \clubpenalty10000
-\automatichyphenmode=1 % for emdash in tl2019 pretest
+\automatichyphenmode=1 % for emdash in tl2019
 
 \begin{document}
 \title{\luatexko\ 간단 매뉴얼}
@@ -65,7 +74,7 @@
 \author{Dohyun Kim \normalsize |<nomos at ktug org>| \and
         Soojin Nam \normalsize |<jsunam at gmail com>| \and
   \normalsize <\url{http://github.com/dohyunkim/luatexko}>}
-\date{Version 1.24\quad 2019/04/06}
+\date{Version 2.0\quad 2019/05/01}
 \maketitle
 
 \begin{quote}\small
@@ -72,228 +81,261 @@
   For a summary introduction in English, please see |README| file.
 
 \begin{itemize}\linespread{1.1}\selectfont
-    \item[v1.0] ------
-    \item \hyperref[sec:loading]{플레인텍에서도 luatexko.sty을 불러야 한다.}
-    \item \hyperref[sec:fontoption]{글꼴 옵션값을 |<dimen>|으로 지시한다.}
-    \item \hyperref[sec:metapost]{\MP\ 코드를 넣으려면 luamplib 패키지를
-      불러야 한다.}
-    \item[v1.3] ------
-    \item \hyperref[sec:fontcmds]{fallbackfont 계열의 명령이 추가되었다.}
-    \item \hyperref[sec:fontswitch]{\cs{hangulpunctuations} 선언 추가,
-      |[QuoteRaise]| 옵션 제거}
-    \item \hyperref[sec:autojosa]{\cs{josaignoreparens=1}이 선언되면
-      자동조사 결정시 괄호 부분을 무시한다.}
-    \item[v1.5] ------
-    \item \hyperref[sec:verttype]{세로쓰기를 지원하기 위한 실험적 코드가
-      들어갔다.}
-    \item \hyperref[sec:actualtext]{\cs{actualtext} 명령 추가}
-    \item \hyperref[sec:fontswitch]{한글·한자 글꼴의 지정이 없으면
-      나눔 Type1을 이용한다.}
-    \item \hyperref[sec:packageopt]{패키지 옵션 |[nofontspec]| 추가}
-    \item[v1.6] ------
-    \item \hyperref[sec:fontoption]{글꼴 옵션 |[NoEmbedding]| 추가}
-    \item \hyperref[sec:fontcmds]{main/sans hangul/hanja/fallback 폰트의
-      디폴트 옵션으로 |[Ligatures=TeX]|이 주어진다.}
-    \item[v1.7] ------
-    \item 패키지 옵션 |[unfonts]|를 더는 지원하지 않는다. 이 옵션을 사용한
-      기존 문서는 기본 글꼴인 나눔 Type1으로 식자될 것이다.
-    \item[v1.8] ------
-    \item \hyperref[sec:ruby]{\cs{xxruby} 명령 추가}
-    \item[v1.9] ------
-    \item \hyperref[sec:verttype]{|horizontal| 환경 추가}
-    \item[v1.22] ------
-    \item \hyperref[sec:fontswitch]{\cs{hangulbyhangulfont} 명령 추가}
+    \item[v2.0] ------
+    \item \hyperref[sec:packageopt]{패키지 옵션 |nofontspec| 제거}
+    \item \hyperref[sec:fontoption]{글꼴옵션 |PunctRaise|, |NoEmbeding| 제거}
+    \item \hyperref[sec:mathhangul]{수식 한글 글꼴을 자동으로 잡아주지 않음}
+    \item \cs{actualtext} 명령 작동 않음. 대신, 옛한글 텍스트 추출 자동 지원%
+      \footnote{%
+        PDF 리더 어플리케이션이 ActualText 기능을 지원해야 가능하다.
+        어도비 리더는 확실히 가능할 것이다. }
+    \item \hyperref[sec:fontoption]{글꼴옵션 |CompressPunctuations|,
+      |RemoveClassicSpaces|, |InterCharacter| 추가}
+    \item \hyperref[sec:break]{\cs{registerbreakableafter},
+      \cs{registerbreakablebefore} 명령 추가}
   \end{itemize}
 \end{quote}
 
 \smallskip
 
-\section{}
-\hemph{\texlive\ 2016 이상}{\small (\luatex\ v0.95 이상)}을 사용해야 한다.
-아직 \luatex은 베타 상태로 개발 중에 있으므로 안정적인 동작을 보장하지 않는다.
+\section{부르기}\label{sec:loading}
+\hemph{\texlive\ 2019 이상}{ \small (\luatex\ v1.10 이상)}을 사용해야 한다.
 
-\section{}\label{sec:loading}
-\luatexko\ 로드하기: \cs{usepackage{luatexko}} 혹은 \cs{usepackage{kotex}}.
-플레인텍에서는 \cs{input luatexko.sty} 혹은 \cs{input kotex.sty}.
+\luatexko\ 부르기: \cs{usepackage{luatexko}} 혹은 \cs{usepackage{kotex}}.%
+\footnote{%
+  플레인텍에서는 \cs{input luatexko.sty} 혹은 \cs{input kotex.sty}. }
+
 입력은 원칙적으로 UTF-8으로 한다.
 BOM (Byte Order Mark)은 있어도 좋고 없어도 좋다.
 CP949 인코딩에 관해서는 제\ref{sec:uhcencoding}절 참조.
 
-\section{}\label{sec:packageopt}
-패키지 옵션으로 |[hangul]|과 |[hanja]|가 제공된다.  행간격 조정이 행해지며
-장 제목이나 표·그림 캡션 따위가 한글 또는 한자 방식으로 표시된다.  다만
+\section{패키지 옵션}\label{sec:packageopt}
+패키지 옵션으로 |[hangul]|과 |[hanja]|가 제공된다.%
+\footnote{%
+  |[nofontspec]| 옵션은 v2.0부터 더는 제공되지 않는다. }
+행간격 조정이 행해지며
+장 제목이나 표·그림 캡션 따위가 한글 또는 한자 방식으로 표시된다.
 장(chapter) 제목과 편(part) 제목에만 ``제(第)''가 붙어 ``제~1 편''
 ``제~3 장''과 같은 방식으로 표시되며 절 제목 이하에는 붙지 않는다.
 
-|[nofontspec]| 옵션을 주면 fontspec을 부르지 않는다.
-따라서 아래 \ref{sec:fontcmds}, \ref{sec:fontswitch}, \ref{sec:fontoption},
-\ref{sec:verttype}, \ref{sec:mathhangul}절의 유니코드 한국어 글꼴 설정
-명령들도 사용할 수 없다.
+\section{글꼴명령}\label{sec:fontcmds}
+\luatexko를 로드하면 fontspec 패키지를 자동으로 불러온다. 글꼴 설정에
+대해서는 fontspec 문서를 참조.
 
-\section{}\label{sec:fontcmds}
-\luatexko를 로드하면 fontspec 패키지를 자동으로 불러온다.  글꼴 설정에
-대해서는 fontspec 문서를 참조하라.
-
-한국어 글꼴을 위해 새로 마련한 명령은 다음과 같다.
+한국어 글꼴을 위해 새로 마련한 명령은 다음과 같다.%
+\footnote{%
+  \cs{adhochangulfont} \cs{adhochanjafont} \cs{addhocfallbackfont}는
+  마지막 줄 명령들의 다른 이름들이다. }\,%
+\footnote{%
+  플레인텍에서 한글 글꼴 설정은 다음과 같이 한다:\par
+  \leftskip=3em\noindent
+  \cs{hangulfont=UnDotum\relax}\\
+  \cs{hanjafont=UnDotum at 14pt}\\
+  \cs{fallbackfont=HanaMinB at 12pt} }
 \begin{verbatim}
   \setmainhangulfont     \setmainhanjafont    \setmainfallbackfont
   \setsanshangulfont     \setsanshanjafont    \setsansfallbackfont
   \setmonohangulfont     \setmonohanjafont    \setmonofallbackfont
   \newhangulfontfamily   \newhanjafontfamily  \newfallbackfontfamily
+  \newhangulfontface     \newhanjafontface    \newfallbackfontface
   \addhangulfontfeature  \addhanjafontfeature \addfallbackfontfeature
   \hangulfontspec        \hanjafontspec       \fallbackfontspec
 \end{verbatim}
-\cs{adhochangulfont} \cs{adhochanjafont} \cs{addhocfallbackfont}는
-마지막 줄 명령들의 다른 이름들이다.
 첫 두 줄, 즉 main/sans hangul/hanja/fallback 글꼴에는
 |Ligatures=TeX| 옵션이 자동으로 주어진다.
-\begin{itemize}\item[]
-플레인텍에서 한글 글꼴 설정은 영문 글꼴 지정하는 방식과 거의 같다.
-\begin{verbatim}
-  \hangulfont=UnDotum\relax
-  \hanjafont=UnDotum at 14pt
-  \fallbackfont=HanaMinB at 12pt
-  \hangulfont=UnDotum scaled 2000
-  \hanjafont="HCR Batang LVT"\relax
-  \hangulfont={HCR Batang LVT:script=hang;+dlig} at 12pt
-\end{verbatim}
-\end{itemize}
 
-\section{}\label{sec:fontswitch}
+\section{글꼴 대체}\label{sec:fontswitch}
 원칙적으로 \luatexko는 지시된 글자가 영문폰트에 없으면 한글폰트에서,
 한글폰트에도 없으면 한자폰트에서, 한자폰트에도 없으면 fallback
-폰트에서 글자를 찾아 찍는다. 세 가지 \hemph{모두 지정되지 않았다면 나눔 Type1
-폰트를} 이용한다. 기존 \kotex과는 글꼴 대체 방식이 다르므로 주의해야 한다.
+폰트에서 글자를 찾아 찍는다.
+한글폰트가 지정되지 않았고 기본폰트에 한글 글리프가 없다면
+\hemph{은바탕, 은돋움 트루타입}을 자동으로 불러온다.%
+\footnote{%
+  은글꼴 트루타입은 \texlive에 포함되어 있다. }
 
 하지만 \cs{hangulbyhangulfont=1}을 선언하면 한글은 우선 한글폰트로
-식자한다. 또한
-\cs{hanjabyhanjafont=1}을 선언하면 한자는 우선 한자폰트로
-식자한다.  둘 다 |0|을 선언하면 원래 방식으로 되돌아간다. 이 명령들은 문단
-중간에서 사용하지 않도록 주의해야 한다. 문단 중에 사용되었다면
-그 문단의 처음부터 효력이 발생한다.
+식자한다. 또한 \cs{hanjabyhanjafont=1}을 선언하면 한자는 우선 한자폰트로
+식자한다. 두 경우 모두 |0|을 선언하면 원래 방식으로 되돌아간다.
+문단 중간에서 사용해도 동작한다. 그러나 루아코드가 실행되므로
+텍의 그룹에 의해 영향받지 않는 전역적 효과를 가진다.
 
-\cs{hangulpunctuations=1}을 선언하면 영문 \hemph{문장부호들을 한글 폰트로}
-식자한다. |0|을 지시하면 원래 상태로 되돌린다.
-|[hangul]| 옵션을 주면 \cs{hangulpunctuations=1}이 자동으로
-선언된다.\footnote{나눔 Type1 상태면 그러하지 아니하다.}
-이 선언에 의해 영향 받는 문장부호들을 다음처럼 지정할 수 있다.
+나아가 일정한 영문 문장부호들은 한글 폰트로 식자된다.
+즉, \cs{hangulpunctuations=1}이 기본으로 작동하고,
+|0|을 지시해야 이 기능이 비로소 꺼진다.
+verbatim 코드를 식자할 때는 이 기능이 작동하지 않는다.%
+\footnote{%
+  좀 더 정확히 말하면 \cs{language=1} 상황에서는 작동하지 않는다.
+  부연하건대, \cs{language=1}인 상황에서 작동하지 않는 그밖의 기능들은
+  다음과 같다: 줄바꿈 허용, InterCharacter, InterHangul, InterLatinCJK,
+  CompressPunctuations, RemoveClassicSpaces,
+  한글^^b7한자 폰트 문자 다음에 오는 공백의 크기 조정 등. }\,%
+\footnote{%
+  또한 플레인텍에서는 영문글꼴을 트루타입/오픈타입으로 지정해야만 작동한다. }
+영향 받는 문장부호들의 기본값은 다음과 같다:
+\begin{verbatim}
+  [0x21]    !    [0x27]    '   [0x28]    (   [0x29]    )
+  [0x2C]    ,    [0x2E]    .   [0x3A]    :   [0x3B]    ;
+  [0x3F]    ?    [0x60]    `   [0xB7]    ·   [0x2014]  —
+  [0x2015]  ―   [0x2018]  ‘   [0x2019]  ’   [0x201C]  “
+  [0x201D]  ”    [0x2026]  …   [0x203B]  ※
+\end{verbatim}
+다음과 같은 명령으로 이 목록에 문자를 추가하거나 제거할 수 있다.
 인자는 콤마로 분리된 숫자 형식으로서 유니코드 코드포인트를 뜻한다.
 \begin{verbatim}
   \registerpunctuations{45, "2D, `-}
   \unregisterpunctuations{"2D, "2015}
 \end{verbatim}
-이들 두 명령은 항상 전역적 효과를 가진다.
+이들 명령은 문단 중간에서도 쓸 수 있지만, 전역적 효과를 가진다.
 
+\section{줄바꿈 허용}\label{sec:break}
+어떤 글자 앞이나 뒤에서 줄바꿈을 허용하고 싶을 때 아래와 같이 명령을 준다.
+\begin{verbatim}
+\registerbreakableafter{"2460, "2461}
+\registerbreakablebefore{"2460, "2461}
+\end{verbatim}
+\luatexko는 다분히 보수적으로 줄바꿈을 허용하고 있다.
+예시한 유니코드는 `①'과 `②'인데 기본적으로
+이들 문자 앞뒤 모두에서 줄바꿈이 되지 않는다.
 
-\section{}\label{sec:fontoption}
-fontspec의 글꼴 옵션 외에 \luatexko가 따로 제공하는 것들이
-있다.\footnote{ 옛한글 식자를 위해서는
-  |[Script=Hangul]| 옵션을 사용하는 것으로 충분하다.
-  플레인텍이라면 |script=hang|.}
+\section{글꼴 옵션}\label{sec:fontoption}
+fontspec의 글꼴 옵션 외에 \luatexko가 따로 제공하는 것들이 있다.%
+\footnote{%
+  옛한글 식자를 위해서는 적어도 |Script=Hangul|
+  (플레인텍이라면 |script=hang|)을 주어야 한다.
+  옛한글이 아니더라도 한글을 쓴다면 이 옵션은 주는 것이 좋다.
+  또한 폰트가 지원한다면 |LocalForms=On| (플레인텍이라면 |+locl|)과
+  함께 쓰는 것이 권장된다. }\,%
+\footnote{%
+  |PunctRaise|, |NoEmbedding| 옵션은 v2.0부터 더는 제공하지 않는다. }
 
+\def\hunmintxt{%
+  \noindent\textbf{%
+  世솅〮宗조ᇰ御ᅌᅥᆼ〮製졩〮訓훈〮民민正져ᇰ〮音ᅙᅳᆷ
+  }\par
+  製졩〮ᄂᆞᆫ〮글〮지ᅀᅳᆯ〮씨〮니〮御ᅌᅥᆼ〮製졩〮ᄂᆞᆫ〮님〯금〮지〯ᅀᅳ샨〮그〮리라〮訓훈〮은〮ᄀᆞᄅᆞ칠〮씨〮오〮民%
+  민ᄋᆞᆫ〮百ᄇᆡᆨ〮姓셔ᇰ〮이〮오〮音ᅙᅳᆷ은〮소리〮니〮訓훈〮民%
+  민正져ᇰ〮音ᅙᅳᆷ은〮百ᄇᆡᆨ〮姓셔ᇰ〮ᄀᆞᄅᆞ치〮시논〮正져ᇰ〮ᄒᆞᆫ소리〮라〮
+  \par\medskip
+  \noindent\textbf{%
+  國귁〮之징語ᅌᅥᆼ〯音ᅙᅳᆷ이〮
+  }\par
+  國귁〮ᄋᆞᆫ〮나라〮히라〮之징ᄂᆞᆫ〮입〮겨〮지라〮語ᅌᅥᆼ〯는말〯ᄊᆞ미라〮
+  \par
+  나랏〮말〯ᄊᆞ미〮
+  \par\medskip
+  \noindent\textbf{%
+  異잉〮乎ᅘᅩᆼ中듀ᇰ國귁〮ᄒᆞ〮야〮
+  }\par
+  異잉〮ᄂᆞᆫ〮다ᄅᆞᆯ씨라乎ᅘᅩᆼᄂᆞᆫ〮아〯모〮그ᅌᅦᄒᆞ〮논겨〮체ᄡᅳ〮는字ᄍᆞᆼ〮ㅣ라〮中듀ᇰ國귁〮ᄋᆞᆫ〮%
+  皇ᅘᅪᇰ帝뎽〮겨〯신나라〮히니〮우〮리〮나랏〮常쌰ᇰ談땀애〮江가ᇰ南남이〮라〮ᄒᆞ〮ᄂᆞ〮니라〮
+  \par
+  中듀ᇰ國귁〮에〮달아〮
+}
+
 \begin{figure}
   \fboxsep=1em
-  \fbox{\parbox{\dimexpr\linewidth-2.1em\relax}{%
-      \sffamily \leftskip2.5cm
-뎨 가ᄂᆞᆫ 뎌 각시 본 듯도 ᄒᆞᆫ뎌이고.\\
-天텬上상白ᄇᆡᆨ玉옥京경을 엇디ᄒᆞ야 離니別별ᄒᆞ고\\
-ᄒᆡ 다 져믄 날의 눌을 보라 가시ᄂᆞᆫ고
-
-\medskip
-
-어와 네여이고 내 ᄉᆞ셜 드러보오.\\
-내 얼굴 이 거동이 님 괴얌즉 ᄒᆞᆫ가마ᄂᆞᆫ\\
-엇딘디 날 보시고 네로다 녀기실ᄉᆡ\\
-나도 님을 미더 군 ᄠᅥ디 전혀 업서\\
-이ᄅᆡ야 교ᄐᆡ야 어ᄌᆞ러이 구돗ᄯᅥᆫ디\\
-반기시ᄂᆞᆫ ᄂᆞᆺ비치 녜와 엇디 다ᄅᆞ신고.%\\
-%누어 ᄉᆡᆼ각ᄒᆞ고 니러 안자 혜여ᄒᆞ니\\
-%내 몸의 지은 죄 뫼ᄀᆞ티 ᄡᅡ혀시니\\
-%하ᄂᆞᆯ히라 원망ᄒᆞ고 사ᄅᆞᆷ이라 허믈ᄒᆞ랴\\
-%셜워 플텨 혜니 造조物믈의 타시로다.
+  \fbox{\parbox{\dimexpr\linewidth-2.1em\relax}{\sffamily
+  \parindent1em
+  \hunmintxt
 }}
 \caption{옛한글 조판 보기}\label{fig:yethangul}
 \end{figure}
 
-그러나 \cs{defaulthangulfontfeatures} 따위 명령은 따로 구현돼 있지 않으며,
-설혹 사용하더라도 fontspec의 \cs{defaultfontfeatures}와 같은 효과를 가진다.
-다시 말해 한글 폰트 옵션과 라틴 폰트 옵션에 차이는 없다.
-
 \begin{description}
   \item[InterHangul] {\addhangulfontfeature{InterHangul=.5ex}한글 글자
-    사이의 자간}.  아래는 $-0.04$em 만큼 한글
-  자간을 지시한다. 플레인텍에서는 |interhangul|.
+    사이의 자간}.  아래는 $-0.04$em 만큼 한글 자간을 지시한다.%
+    \footnote{%
+      플레인텍에서는 |interhangul=<dimen>|. }
 \begin{verbatim}
   [InterHangul=-0.04em]
 \end{verbatim}
 
-\item[InterLatinCJK] 한글 또는 한자와 라틴 문자 사이의 자간을
-  설정한다. 플레인텍에서는 |interlatincjk|.
+\item[InterLatinCJK] 한글 또는 한자와 Latin문자 사이의 자간을 설정한다.%
+    \footnote{%
+      플레인텍에서는 |interlatincjk=<dimen>|. }
+    한글^^b7한자 다음에 라틴 문자가 오는 경우 원래는 줄바꿈이 되지 않지만
+    이 옵션을 주면 줄바꿈이 가능해진다.
 \begin{verbatim}
-  [InterLatinCJK=0.125em]
+  [InterLatinCJK=0.25em]
 \end{verbatim}
 
-\item[CharRaise] 글자의 세로 위치를
-  {\addhangulfontfeature{CharRaise=.3em} 조절}할 수 있는 기능이다.
-  이로써 주변에 식자되는 다른 글꼴과 조화를 이루게 한다.
-  따라서 영문폰트에는 작동하지 않는다.
-  플레인텍에서는 |charraise|.
+\item[InterCharacter] CJK 글자들 사이의 자간을 지시한다.%
+  \footnote{%
+    플레인텍에서는 |intercharacter=<dimen>|. }
+  fontspec의 |LetterSpace| 옵션과 기능이 유사하지만, 옛한글을 깨뜨리는 등의
+  오작동을 하지 않는다. 전술한 자간 옵션들보다 후순위로 동작한다.
 \begin{verbatim}
-  [CharRaise=0.1em]
+  [InterCharacter=.125em]
 \end{verbatim}
 
-\item[PunctRaise] 한글 또는 한자 다음에 라틴 구두점---마침표에
-  한한다---이 왔을 때 그 세로 위치를 조절할 수 있다.
-  \cs{hangulpunctuations}로 인해 거의 쓸 일이 없게 되었다.
-  플레인텍에서는 |punctraise|.
+\item[CharRaise] 글자의 세로 위치를
+  {\addhangulfontfeature{CharRaise=.3em} 조절}할 수 있는 기능이다.
+  이로써 주변에 식자되는 다른 글꼴과 조화를 이루게 한다.%
+  \footnote{%
+    플레인텍에서는 |charraise=<dimen>|. }
 \begin{verbatim}
-  [PunctRaise=-0.125em]
+  [CharRaise=2pt]
 \end{verbatim}
+  세로쓰기에서는 모든 글자들을 지정한 값만큼 오른쪽으로 이동시킨다.
+  세로쓰기에서 이 옵션 값을 고정길이{\small(가령 |pt| 단위)}로 주면
+  크기가 다른 폰트들간에 중앙정렬이 이루어진다.
+  세로쓰기에서 이 옵션을 주지 않으면 |0.5ex|가 기본값으로 동작한다.
 
-\item[NoEmbedding] |NoEmbed|와 동의어.
-  폰트를 내장하지 않으므로 결과물의 크기가 대폭 줄어든다.
-  하지만 대외적 배포에 적합하지 않음에 유의.\footnote{%
-    Adobe Reader에 번들된 폰트를 쓴다면 대외적 배포도 대체로 무난하다.}
-  플레인텍에서는 |embedding=no|.
-\begin{verbatim}
-  [NoEmbedding]
-\end{verbatim}
-
 \item[Protrusion] 특정 글자가 행 끌에 왔을 때 판면 바깥으로 끌어내는
-  기능이다.  Lua\LaTeX의 기본값은 구두점들만 완전히 글자내밀기 한다. 즉
-  hanging punctuation이 작동한다.
-\begin{verbatim}
-  [Protrusion]
-\end{verbatim}
-  이는 |Protrusion=default|와 같은 뜻이다.
-  플레인텍에서는 |protrusion=default|.
-  마이크로타입에 관심 있으면 자신만의 설정을 만들어 지정할 수 있다.\footnote{%
+  기능이다. Lua\TeX은 기본값으로 온점 반점 등을 완전 글자내밀기 한다.
+  |Protrusion|은 |Protrusion=default|와 같은 뜻이다.%
+  \footnote{%
+    플레인텍에서는 |protrusion=default|. }
+  자신만의 설정을 만들어 지정할 수 있다.%
+  \footnote{%
     default 설정을 고치는 방법은 이를테면 다음과 같다:\par
-    \cs{directlua{ fonts.protrusions.setups.default[0x201C] = { 1, 0 } }}
-  }
+    \leftskip=3em\noindent
+    \cs{directlua{ fonts.protrusions.setups.default[0x201C] = { 1, 0 } }} }
 
 \item[Expansion] 판면의 균일한 조판을 위해 글자들을 미세하게 늘이거나
-  줄이는 기능이다.
-\begin{verbatim}
-  [Expansion]
-\end{verbatim}
-  이는 |Expansion=default|와 마찬가지 뜻이다.
-  플레인텍에서는 |expansion=default|.
+  줄이는 기능이다. |Expansion|은 |Expansion=default|와 마찬가지 뜻이다.%
+  \footnote{%
+    플레인텍에서는 |expansion=default|. }
 
+\item[RemoveClassicSpaces] 고문헌 조판시에 CJK 글자들 사이의 공백을 없애준다.%
+  \footnote{%
+    플레인텍에서는 |+removeclassicspaces|. }
+
+\item[CompressPunctuations] CJK 구두점 {\small (낫표 따위)}의 글자폭을
+  반각으로 만든다. v2.0부터는 사용자가 지시하지 않는 한 자동으로 글자폭을
+  줄여주지 않는다.%
+  \footnote{%
+    플레인텍에서는 |+compresspunctuations|. }
+  이 옵션은 |CharacterWidth=AlternateHalf| 내지 {\small (세로쓰기의 경우)}
+  |Vertical=HalfMetrics|와 거의 같은 기능을 한다.%
+  \footnote{%
+    플레인텍에서는 각각 |+halt| 및 |+vhal|이다. }
 \end{description}
 
-\section{}
-고문헌·일본어·중국어 조판을 위해 japanese, Schinese, Tchinese 환경을
-제공한다.  chinese는 Schinese의 다른 이름이다.  korean 환경도
-마련했는데 이들 환경 안에서 잠깐 한국어를 조판하는 데 사용한다.
-일반적으로 우리 고문헌 조판에는 japanese 환경을 이용하면 무난하다.
-그림~\ref{fig:ancientdoc} 참조.
-플레인텍에서는 {\small(문서 전체에 적용하지 않는다면 그룹을 열고)}
-\cs{chinese} \cs{japanese} 따위를 사용한다.
+\section{고문헌}\label{sec:classic}
+고문헌 조판을 위해 |typesetclassic| 환경을 제공한다.%
+\footnote{%
+  플레인텍에서는 (문서 전체에 적용하지 않는다면 그룹 안에서)
+  \cs{typesetclassic}을 사용한다.
+  |japanese|, |chinese|, |Schinese|, |Tchinese|, |korean| 따위의 환경은
+  지난 버전과의 호환성을 위해 당분간 제공되고 있을 뿐이다. }
+그림~\ref{fig:gomun} 참조.
 
+v2.0부터는 고문헌 조판에서 한글^^b7한자와 영문자가 붙어 나오더라도 자동으로
+간격을 잡아주지 않으므로 이런 경우 사용자가 |InterLatinCJK| 옵션을
+지정해야 한다.
+또한 불필요한 공백이 오더라도 자동으로 제거해주지 않으므로 이런 경우
+사용자가 |RemoveClassicSpaces| 옵션을 주어야 한다.
+
+|typesetmodern|은 고문헌 조판 중에 잠깐 현대 한국어를 조판하는 데 사용한다.%
+\footnote{%
+  플레인텍에서는 (문서 전체에 적용하지 않는다면 그룹 안에서)
+  \cs{typesetmodern}을 사용한다. }
+
 \begin{figure}
   \centering
-  \fbox{\parbox{37em}{\japanese\sffamily
+  \fbox{\parbox{37em}{\typesetclassic\sffamily
+  \addhangulfontfeature{CompressPunctuations}
 子曰:「學而時習之,不亦說乎?有朋自遠方來,不亦樂乎?人不知而不慍,
 不亦君子乎?」\par
 有子曰:「其爲人也孝弟,而好犯上者,鮮矣!不好犯上,而好作亂者,未之有也!
@@ -304,52 +346,69 @@
 子曰:「道千乘之國,敬事而信,節用而愛人,使民以時。」\par
 子曰:「弟子入則孝,出則弟;謹而信,汎愛衆;而親仁,行有餘力,則以學文。」
   }}
-\caption{고문헌 조판 보기. japanese 환경을 이용했다.}\label{fig:ancientdoc}
+\caption{고문헌 조판 보기. typesetclassic 환경을 이용했다.}\label{fig:gomun}
 \end{figure}
 
-일본어·중국어라도 글꼴 설정은 \cs{newhangulfontfamily}
-\cs{newhanjafontfamily} 따위를 이용한다.
+고문헌 조판 중 \luatexko가 글자 사이에 삽입하는 미세간격을
+사용자가 영{\small(zero)}으로 강제하기 위해선 \cs{inhibitglue} 명령을 이용한다.
 
-\luatexko가 글자 사이에 삽입하는 미세간격을 사용자가 영(zero)으로
-강제하기 위해선 \cs{inhibitglue} 명령을 이용한다.
-대체로 일본어·중국어 환경에서만 문제된다.
-
-\section{}\label{sec:verttype}
+\section{세로쓰기}\label{sec:verttype}
 세로쓰기는 폰트의 고급 오픈타입 속성을 이용하므로 폰트가 이를 지원해야
-가능한 일이다. 폰트에 |Vertical=RotatedGlyphs| 옵션을 준다.  이는
-플레인텍에서 |vertical;+vrt2| 옵션을 주는 것과 같다.\footnote{|vmtx| 정보를
-  가지고 있지 않은 글꼴은 세로쓰기에 적합치 않은 글꼴이다.
-  |otfinfo -t <파일>| 명령으로 글꼴에 들어있는 테이블 정보를 알 수 있다.}
+가능한 일이다. 폰트에 |Vertical=Alternates|와 |RawFeature=+vertical| 옵션을
+준다.%
+\footnote{%
+  이는 플레인텍에서 |+vertical;+vert| 옵션을 주는 것과 같다.
+  사실 |+vertical|을 선언하면 |vert|는 자동으로 켜지도록 해 두었다. }\,%
+\footnote{%
+  |vmtx| 정보를 가지고 있지 않은 글꼴은 세로쓰기에 적합치 않은 글꼴이다.
+  |otfinfo -t <파일>| 명령으로 글꼴에 들어있는 테이블 정보를 알 수 있다. }
+세로쓰기에서 |CharRaise| 옵션의 효과에 대해서는 제\ref{sec:fontoption}절을
+참조할 것.
 
+세로쓰기에서는 \cs{typesetclassic}의 아종인 \cs{typesetvertical}이
+기본으로 셋팅되어 있다. 일종의 고문헌 조판으로 보는 것이다.
+
 문서의 일부를 세로쓰기하려면 \cs{begin{vertical}{<dimen>}} \ldots\ \cs{end{vertical}}
-환경을 이용하라. |<dimen>|으로 세로쓰기 박스의 높이를 지시한다.
-그림~\ref{fig:vertical} 참조.
-플레인텍에서는 \cs{vertical{<dimen>}} \ldots\ \cs{endvertical}.
+환경을 이용한다. |<dimen>|으로 세로쓰기 박스의 높이를 지시한다.%
+\footnote{%
+  플레인텍에서는 \cs{vertical{<dimen>}} \ldots\ \cs{endvertical}. }
+그림~\ref{fig:vertical} 및 그림~\ref{fig:vertical2} 참조.
 
 \begin{figure}
-\framebox[\linewidth]{\begin{vertical}{17em}
-    \sffamily
-    \addhangulfontfeature{Vertical=RotatedGlyphs,
-      RawFeature=vertical,
-      CharacterWidth=Full, CharRaise=0.1ex, }
-\parindent-1em\leftskip1em \linespread{1.5}\selectfont
-\noindent 님의 침묵(The Silent Beloved)
-\smallbreak
-\hfil\hfil 한 용 운\hfil
-\bigbreak
-님은 갓슴니다 아아 사랑하는나의님은 갓슴니다\par
-푸른산빗을깨치고 단풍나무숩을향하야난 적은길을 거러서 참어떨치고
-갓슴니다\par
-黃金의꽃가티 굿고빗나든 옛盟誓는 차듸찬띠끌이되야서 한숨의 微風에
-나러갓슴니다\par
-날카로은 첫〈키쓰〉의追憶은 나의運命의指針을 돌너노코 뒷거름처서 사러젓슴니다\par
-\hellipsis\par
-아아 님은갓지마는 나는 님을보내지 아니하얏슴니다\par
-제곡조를못이기는 사랑의노래는 님의沈默을 휩싸고돔니다\par
+\framebox[\linewidth]{\begin{vertical}{20em}\sffamily
+  \addhangulfontfeature{Vertical=Alternates, RawFeature=+vertical}
+  \linespread{1.5}\selectfont
+  \hunmintxt
 \end{vertical}}
-\caption{세로쓰기의 예. 박스 높이 \texttt{17em}을 지시했다.}\label{fig:vertical}
+\caption{세로쓰기의 예. 박스 높이 |20em|을 지시했다.}\label{fig:vertical}
 \end{figure}
 
+\begin{figure}
+\framebox[\linewidth]{\begin{vertical}{17em}\sffamily
+  \addhangulfontfeature{Vertical=Alternates, CharRaise=3.2pt,
+  CompressPunctuations, CharacterWidth=Full, RawFeature=+vertical}
+  \parindent-1em\leftskip1em \linespread{1.5}\selectfont
+  \spaceskip=.5em plus.25em minus.125em
+  \noindent 님의 침묵(The Silent Beloved)
+  \smallbreak
+  \hfil\hfil 한 용 운\hfil
+  \bigbreak
+  님은 갓슴니다 아아 사랑하는나의님은 갓슴니다\par
+  푸른산빗을깨치고 단풍나무숩을향하야난 적은길을 거러서 참어떨치고
+  갓슴니다\par
+  黃金의꽃가티 굿고빗나든 옛盟誓는 차듸찬띠끌이되야서 한숨의 微風에
+  나러갓슴니다\par
+  날카로은 첫〈키쓰〉의追憶은 나의運命의指針을 돌너노코 뒷거름처서 사러젓슴니다\par
+  \hellipsis\par
+  아아 님은갓지마는 나는 님을보내지 아니하얏슴니다\par
+  제곡조를못이기는 사랑의노래는 님의沈默을 휩싸고돔니다\par
+\end{vertical}}
+\caption{세로쓰기의 또 다른 예.  박스 높이 |17em|을 지시했다.
+  Noto Sans CJK 글꼴의 세로쓰기 공백은 원래 |1em|,
+  즉 전각크기로 매우 크기 때문에, 여기서는 \cs{spaceskip} 명령을 이용하여
+  반각 크기로 강제했다.}\label{fig:vertical2}
+\end{figure}
+
 문서 전체를 세로쓰기한다면 이 환경을 쓰는 대신
 \cs{verticaltypesetting} 명령을 전처리부에 선언한다.
 이때 면주는 가로로 식자되며 면주 폰트의 설정은 사용자의 몫이다.
@@ -356,49 +415,33 @@
 
 세로쓰기 도중에 문서의 일부를 가로쓰기하려면
 \cs{begin{horizontal}{<dimen>}} \ldots\ \cs{end{horizontal}}
-환경을 이용하라. |<dimen>|은 가로쓰기 박스의 너비를 지시한다.
-플레인텍에서는 \cs{horizontal{<dimen>}} \ldots\ \cs{endhorizontal}.
+환경을 이용한다. |<dimen>|은 가로쓰기 박스의 너비를 지시한다.%
+\footnote{%
+  플레인텍에서는 \cs{horizontal{<dimen>}} \ldots\ \cs{endhorizontal}. }
 가로쓰기 영역의 폰트 설정은 사용자의 몫이다.
 
-\section{}\label{sec:metapost}
-전처리부에서 \cs{usepackage{luamplib}}을 선언하면 \MP\ 코드를 문서 중간에
-삽입할 수 있다.  한글이나 수식은 |btex| \ldots\ |etex| 안에 넣어야 한다.
-그림~\ref{fig:mplib} 참조. 상세한 것은 luamplib 패키지 문서를 참조하라.
-
-\begin{figure}
-\setbox0\vbox{\kern10pt
-\begin{verbatim}
-  \usepackage{luamplib}
-  ...
-  \begin{mplibcode}
-    beginfig(1);
-      draw fullcircle scaled 2cm;
-      dotlabel.bot(btex \TeX etex, origin);
-      dotlabel.rt(btex 루아 etex, dir45*1cm);
-    endfig;
-  \end{mplibcode}
-\end{verbatim}}
-\begin{mplibcode}
-beginfig(1);
-  draw btex \copy0 etex shifted (-\mpdim{\textwidth-3cm},-\mpdim{.5\ht0});
-  draw fullcircle scaled 2cm;
-  dotlabel.bot(btex\TeX etex, origin);
-  dotlabel.rt(btex 루아 etex, dir 45*1cm);
-  bboxmargin:=0; draw bbox currentpicture;
-endfig;
-\end{mplibcode}
-\caption{mplib 용례}\label{fig:mplib}
-\end{figure}
-
-\section{}\label{sec:mathhangul}
+\section{수식 한글}\label{sec:mathhangul}
 \begin{quote}
   |$가^{나^다}$|\quad$\Rightarrow\quad가^{나^다}$
 \end{quote}
-수식 모드에서도 한글을 {\small(hbox로 감싸지 않고)} 직접 입력할 수
-있다.  문서의 기본 한글 글꼴이 자동으로 수식 한글에도 적용되므로 따로
-설정할 것이 없지만 굳이 한다면 다음처럼 지시한다.
+수식 한글 폰트를 설정하면 수식 모드에서도 한글을 {\small(hbox로 감싸지 않고)}
+직접 입력할 수 있다.%
+\footnote{%
+  플레인텍에서는 \cs{mathhangulfont=UnBatang}과 같이 설정한다.
+  또는 좀 더 그럴듯하게 하려면:\par
+  \leftskip=3em\noindent
+  |\\font\\texthangul="Noto Sans CJK KR DemiLight" at 10pt|\\
+  |\\font\\scripthangul="Noto Sans CJK KR Regular" at 7pt|\\
+  |\\font\\scriptscripthangul="Noto Sans CJK KR Medium" at 5pt|\\
+  |\\setmathhangulfonts\\texthangul\\scripthangul\\scriptscripthangul| }
+v2.0부터는 자동으로 수식 한글을 잡아주지 않는다.
 \begin{verbatim}
-  \setmathhangulfont{HCRBatang}
+  \setmathhangulfont{Noto Sans CJK KR}[
+    SizeFeatures={
+      {Size=-6,  Font=* Medium},
+      {Size=6-9, Font=* Regular},
+      {Size=9-,  Font=* DemiLight},
+    } ]
 \end{verbatim}
 현재 한글만 쓸 수 있게 설정되어 있다.
 한자도 수식에 직접 입력하려면 사용자는
@@ -407,21 +450,20 @@
 \end{verbatim}
 명령으로 유니코드 블럭을 추가 지정해야 한다.
 
-\section{}
+\section{드러냄표}\label{sec:dotemph}
 \cs{dotemph} 명령으로 \dotemph{드러냄표}%
 를 이용한 강조를 할 수 있다.  기본은 글자 위에 점을 찍는 형태이나
 다음과 같이 명령을 주어 개인적으로 선호하는 기호를 드러냄표로 쓸 수
 있다.
 
+\medskip
 ①~|\def\dotemphraise{0.4em }|: 드러냄표를 피강조 글자 위로 끌어올리는 길이
 
 ②~|\def\dotemphchar{\bfseries ^^^^02d9}|: 드러냄표 기호 자체를 정의.
-|^^^^02d9|는 유니코드 코드포인트를 뜻하는 16진수이고 소문자로만 써야 한다.
-숫자 대신 직접 문자를 입력해도 된다. 플레인텍에서도 쓸 수 있다.
 
-\section{}\label{sec:ruby}
+\section{루비}\label{sec:ruby}
 루비를 달 수 있다. ruby 패키지가 이미 존재하지만 \luatexko와 궁합이 잘
-맞지 않아 새로 매크로를 제공한다.  플레인텍도 지원한다.
+맞지 않아 새로 매크로를 제공한다.
 \begin{quote}
   \cs{ruby{漢字}{한자}}\quad$\Rightarrow$\quad\ruby{漢字}{한자}
 \end{quote}
@@ -428,10 +470,20 @@
 이처럼 글자별로 따로 루비를 달 필요가 없다.  관련 설정은 다음처럼
 한다.
 
-①~\cs{rubyfont}: 루비를 식자할 폰트를 지시해 둔다. 기본값은 현재 폰트.
+\medskip
+①~\cs{rubyfont}: 루비를 식자할 폰트를 지시해 둔다. 기본값은 현재 폰트%
+\footnote{%
+  루비는 글자 크기가 작기 때문에 본문 폰트보다 약간 굵은 폰트로
+  지정하는 것이 좋다. }
 
 ②~|\def\rubysize{0.6}|: 루비 글자 크기를 본문 글자 크기에 대한 비율로
-지정
+지정%
+\footnote{%
+  플레인텍에서는 루비의 글꼴크기를 따로 조정해주지 않는다.
+  사용자가 \cs{rubyfont}를 설정할 때 크기도 함께 지시하도록 한다.
+  가령:\par
+  \leftskip=3em\noindent
+  |\\font\\rubyfont=UnBatang at \\rubysize em| }
 
 ③~|\def\rubysep{0.2ex}|: 루비와 본문 글자 사이의 간격을 지정
 
@@ -439,36 +491,37 @@
 글자들 위로 삐져나가지 못하게 한다. 본문 글자의 흐름을 중시하여
 \cs{rubyoverlap}을 기본값으로 하였으므로 이는 따로 선언할 필요가 없다.
 
+\medskip
 한편, 연속된 본문 글자들에 각각 한 글자씩 루비를 달고자 한다면
-|\xxruby{...}{...}| 명령을 사용하라. 글자들 사이에 줄바꿈이 허용된다.
-다만 두 인자의 글자 수가 동일해야 한다.
+\cs{xxruby{...}{...}} 명령을 사용한다. 글자들 사이에 줄바꿈이 허용된다.
+두 인자의 글자 수가 동일해야 한다.
 
-\section{}
+\section{밑줄긋기}\label{sec:uline}
 ulem 패키지가 \luatexko와 궁합이 잘 맞지 않아{\small (줄바꿈에 문제가 있음)}
-명령을 따로 제공한다. 플레인텍에서도 쓸 수 있다.
+명령을 따로 제공한다.
 
-\bigskip
-\begin{tabular}{l@{\quad$\Rightarrow$\quad}l}
-\cs{uline{밑줄을 그을 수 있다}}&\uline{밑줄을 그을 수 있다}\\
-\cs{sout{취소선을 그을 수 있다}}&\sout{취소선을 그을 수 있다}\\
-\cs{uuline{밑줄을 두 줄 긋는다}}&\uuline{밑줄을 두 줄 긋는다}\\
-\cs{xout{빗금으로 취소할 수 있다}}&\xout{빗금으로 취소할 수 있다}\\
-\cs{uwave{물결표로 밑줄을 삼는다}}&\uwave{물결표로 밑줄을 삼는다}\\
-\cs{dashuline{대시로 밑줄을 삼는다}}&\dashuline{대시로 밑줄을 삼는다}\\
-\cs{dotuline{밑줄을 점선으로 긋는다}}&\dotuline{밑줄을 점선으로 긋는다}\\
-\end{tabular}
-\bigskip
+\def\RA{\>$\Rightarrow$\>}
+\begin{tabbing}
+\cs{dotuline{밑줄을 점선으로 긋는다}}\quad\=$\Rightarrow$\quad\= \kill
+\cs{uline{밑줄을 그을 수 있다}}\RA\uline{밑줄을 그을 수 있다}\\
+\cs{sout{취소선을 그을 수 있다}}\RA\sout{취소선을 그을 수 있다}\\
+\cs{uuline{밑줄을 두 줄 긋는다}}\RA\uuline{밑줄을 두 줄 긋는다}\\
+\cs{xout{빗금으로 취소할 수 있다}}\RA\xout{빗금으로 취소할 수 있다}\\
+\cs{uwave{물결표로 밑줄을 삼는다}}\RA\uwave{물결표로 밑줄을 삼는다}\\
+\cs{dashuline{대시로 밑줄을 삼는다}}\RA\dashuline{대시로 밑줄을 삼는다}\\
+\cs{dotuline{밑줄을 점선으로 긋는다}}\RA\dotuline{밑줄을 점선으로 긋는다}
+\end{tabbing}
 
 관련하여 다음 설정을 할 수 있다.
 
-①~|\def\ulinedown{0.24em}|: 밑줄을 베이스라인 아래로 끌어내리는 정도
+\medskip
+①~|\def\ulinedown{0.5ex}|: 밑줄을 베이스라인 아래로 끌어내리는 정도
 
 ②~|\def\ulinewidth{0.04em}|: 밑줄의 굵기
 
-\section{}\label{sec:autojosa}
+\section{자동조사}\label{sec:autojosa}
 자동조사는 \kotex 과 동일하게 \cs{은} \cs{는} \cs{이} \cs{가} \cs{을} \cs{를}
 \cs{와} \cs{과} \cs{로} \cs{으로} \cs{라} \cs{이라} 따위를 사용한다.
-문장 중에서도 작동할 뿐만 아니라 플레인텍도 지원한다.
 버전 1.3부터는 \cs{josaignoreparens=1}이 선언되어 있으면 자동조사는
 \hemph{괄호 부분을 건너뛰고} 그 앞 글자에 매칭한다.
 |0|이 선언되면 원래 방식으로 돌아간다.
@@ -481,39 +534,32 @@
   |홍길동(2001)\로|\quad$\Rightarrow$\quad 홍길동(2001)\로
 \end{quote}
 
-\section{}
-항목 번호를 한국어 기호로 붙일 수 있다. \kotex과 동일하게 \cs{jaso} \cs{gana}
-\cs{ojaso} \cs{ogana} \cs{pjaso} \cs{pgana} \cs{onum} \cs{pnum} \cs{oeng}
-\cs{peng} \cs{hnum} \cs{Hnum} \cs{hroman} \cs{hRoman} \cs{hNum} \cs{hanjanum}
-따위를 사용한다.
+\section{한국어 항목 번호}\label{sec:hangulnums}
+\kotex과 동일하게 항목 번호를 한국어 기호로 붙일 수 있다.%
+\footnote{%
+  \cs{jaso} \cs{gana} \cs{ojaso} \cs{ogana} \cs{pjaso} \cs{pgana}
+  \cs{onum} \cs{pnum} \cs{oeng} \cs{peng} \cs{hnum} \cs{Hnum}
+  \cs{hroman} \cs{hRoman} \cs{hNum} \cs{hanjanum} 따위를 사용한다. }
 
-\section{}\label{sec:actualtext}
-\cs{actualtext{...}} 명령은 인자를 식자함과 동시에, \hemph{입력한 문자 그대로}
-PDF에서 텍스트로 추출할 수 있게 해준다. 인자가 두 페이지에 나눠지지 않도록
-유의한다.  모든 PDF 리더가 이를 지원하는 것은 아니다. 예:
-$\actualtext{\sqrt 2}$,
-{\sffamily \actualtext{ᄆᆞᄎᆞᆷ〮내〯}}.
-인자가 글자 없이 그림으로만 돼있다면 \cs{actualtext*{...}} 방식을 이용한다.
-
-\section{}
+\section{입력 변환}\label{sec:noramlize}
 \cs{luatexhangulnormalize=1}이라 지시하면 첫가끝 자모를 완성형 음절로,
 |2|라면 완성형 음절을 첫가끝 자모로 인코딩 변환한다. |0|이 할당되면
 인코딩 변환 기능이 꺼진다. \XeTeX의 \cs{XeTeXinputnormalization} 명령과
-유사하나 오직 한글과 일부 한자에 대해서만 정규화가 작동하는 점에서
-\XeTeX의 그것에 비해 기능이 한참 모자란다.
+유사하나, 오직 한글과 일부 한자에 대해서만 정규화가 작동할 뿐이다.
 
-\section{}\label{sec:uhcencoding}
-권장하지 않지만 불가피하게 입력 인코딩이 UHC (Unified Hangul Code)\footnote{%
-  CP949라고도 하며 EUC-KR을 포함한다}로 되어 있는 파일을 처리할 때는
+\section{UHC 입력 인코딩}\label{sec:uhcencoding}
+권장하지 않지만 불가피하게 입력 인코딩이 UHC (Unified Hangul Code)%
+\footnote{%
+  CP949라고도 하며 EUC-KR을 포함한다. }%
+로 되어 있는 파일을 처리할 때는
 \cs{luatexuhcinputencoding=1}을 선언한다.
 |0|을 할당하면 다시 UTF-8 입력으로 간주한다.
-\XeTeX의 \cs{XeTeXinputencoding} 명령과 유사하나 오직 한국어 문자만 처리할 수
-있어 \XeTeX의 그것에 비해 기능이 한참 모자란다.
-
-\section{}
-마찬가지로 바람직하지는 않지만 불가피하게 파일 이름이 UHC로 인코딩되어
-있다면 \cs{luatexuhcfilenames=1}을 선언한다. |0|을 할당하면 다시 UTF-8
-이름으로 간주한다.  윈도 계열 운영체제에서만 문제될 것이다.
+\XeTeX의 \cs{XeTeXinputencoding} 명령과 유사하나, 오직 한국어 문자만 처리할 수
+있다.%
+\footnote{%
+  윈도 운영체제에서의 한글 파일이름 불러오기 기능은 v2.0부터는 제공하지 않는다.
+  대신 |luatex -cmdx ...|, |lualatex -cmdx ...|와 같이 컴파일시 명령행에
+  |-cmdx| 옵션을 주면 된다고 하니 테스트해 보시기 바란다. }
 \hfill \fboxsep=-\fboxrule \fbox{\vbox to 1em{\hbox to 1em{\hss}\vss}}
 
 \end{document}

Deleted: trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-core.sty
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-core.sty	2019-05-01 23:38:43 UTC (rev 50923)
+++ trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-core.sty	2019-05-02 21:10:43 UTC (rev 50924)
@@ -1,506 +0,0 @@
-% luatexko-core.sty
-%
-% Copyright (c) 2013-2019 Dohyun Kim  <nomos at ktug org>
-%                         Soojin Nam  <jsunam at gmail com>
-%
-% This work may be distributed and/or modified under the
-% conditions of the LaTeX Project Public License, either version 1.3c
-% 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.3c or later is part of all distributions of LaTeX
-% version 2006/05/20 or later.
-
-\begingroup\expandafter\expandafter\expandafter\endgroup
-\expandafter\ifx\csname selectfont\endcsname\relax
-  \input luaotfload.sty
-  \ifcsname ifpdf\endcsname\else \input ifpdf.sty \fi
-  \catcode`@ = 11
-\else
-  \ProvidesPackage{luatexko-core}[2019/03/24 v1.23 Typesetting Korean with LuaTeX]
-  \RequirePackage{luaotfload}[2016/04/21]
-  \RequirePackage{ifpdf}
-\fi
-
-\ifdefined\fontid\else
-  \let\fontid\luatexfontid % latex  2015/10/01
-\fi
-\ifdefined\newattribute\else % luaotfload 2.6
-  \let\newattribute\newluatexattribute
-  \let\unsetattribute\unsetluatexattribute
-  \let\scantextokens\luatexscantextokens
-  \let\luaescapestring\luatexluaescapestring
-\fi
-\ifdefined\pdfliteral\else % luatex 0.87
-  \protected\def\pdfliteral{\pdfextension literal}
-\fi
-
-%% make hangul catcode 12
-\count@"1100
-\loop \catcode\count@ 12
-  \ifnum\count@<"115F \advance\count@\@ne \repeat
-\count@"3131
-\loop \catcode\count@ 12
-  \ifnum\count@<"318E \advance\count@\@ne \repeat
-\count@"A960
-\loop \catcode\count@ 12
-  \ifnum\count@<"A97C \advance\count@\@ne \repeat
-\count@"AC00
-\loop \catcode\count@ 12
-  \ifnum\count@<"D7A3 \advance\count@\@ne \repeat
-
-%% attributes
-\newattribute\finemathattr \finemathattr\tw@ % finemath on by default
-\newattribute\cjtypesetattr
-\newattribute\dotemphattr
-\newattribute\autojosaattr
-\newattribute\luakorubyattr \chardef\luakorubyattrnum\the\allocationnumber
-\newattribute\hangulfntattr
-\newattribute\hanjafntattr
-\newattribute\fallbackfntattr
-\newattribute\hangulpunctsattr
-
-%% load lua modules
-\directlua{require "luatexko"}
-
-%% japanese/chinese/pre-modern
-\protected\def\japanese{\frenchspacing\cjtypesetattr\@ne  \parindent1em }
-\protected\def\Schinese{\frenchspacing\cjtypesetattr\tw@  \parindent2em }
-\protected\def\Tchinese{\frenchspacing\cjtypesetattr\thr@@\parindent2em }
-\let\chinese\Schinese
-% korean in japanese/chinese env.
-\protected\def\korean{\unsetattribute\cjtypesetattr}
-
-% inhibitglue
-\protected\def\inhibitglue{{\unsetattribute\cjtypesetattr\hskip\z at skip}}
-
-%% auto josa
-\def\josaignoreparens{\afterassignment\josa at ignore@parens\count@}
-\def\josa at ignore@parens{%
-  \ifcase\count@  \chardef\josa at attr@level\@ne
-  \else           \chardef\josa at attr@level\tw@
-  \fi
-}
-\josaignoreparens=\z@
-
-\protected\def\은{{\autojosaattr\josa at attr@level 은}}
-\let\는\은
-
-\protected\def\을{{\autojosaattr\josa at attr@level 을}}
-\let\를\을
-
-\protected\def\와{{\autojosaattr\josa at attr@level 와}}
-\let\과\와
-
-\protected\def\가{{\autojosaattr\josa at attr@level 가}}
-\protected\def\이{{\autojosaattr\josa at attr@level 이}}
-\protected\def\라{\이라}
-
-\protected\def\으{{\autojosaattr\josa at attr@level 으}}
-\protected\def\로{\으로}
-
-%% dot-emphasis
-\def\dotemphraise{0.4em }
-\unless\ifdefined\bfseries\let\bfseries\bf\fi
-\def\dotemphchar{\bfseries ^^^^02d9}
-\newcount\luakodotemphcnt \luakodotemphcnt\z@
-\protected\def\dotemph#1{{%
-  \ifnum\luakodotemphcnt > 65535 \luakodotemphcnt\z@ \fi
-  \global\advance\luakodotemphcnt\@ne
-  \setbox\z@\hbox{\raise\dotemphraise\hbox{\dotemphchar}}\ht\z@\z@\dp\z@\z@
-  \directlua{luatexko.dotemphnode[\the\luakodotemphcnt] = node.copy_list(tex.box[0])}%
-  \dotemphattr\luakodotemphcnt  #1}}
-
-%% ruby : ruby.sty by Lemberg is excellent; but
-%%        it doesn't match well with luatexko, so implemented anew
-%%        with plain tex support and enhanced features.
-\def\rubysize{0.6}  % size of ruby compared to base character
-\def\rubysep{0.1ex} % distance between base character and ruby
-% users can define \rubyfont : eg. \font\rubyfont={foo:+ruby}
-\newif\ifruby at overlap
-\let\rubyoverlap\ruby at overlaptrue
-\let\rubynooverlap\ruby at overlapfalse
-\rubyoverlap       % ruby may protrude over adjacent chars
-\newcount\luakorubyattrcnt\luakorubyattrcnt\z@
-\protected\def\ruby#1#2{%
-  \ifnum\luakorubyattrcnt > 65535 \luakorubyattrcnt\z@ \fi
-  \global\advance\luakorubyattrcnt\@ne
-  \begingroup
-  \setbox\z@\hbox attr\luakorubyattrnum\luakorubyattrcnt{% base character
-    \vrule width\z@ height.88em depth\z@\relax #1}%
-  \setbox\tw@\hbox{% ruby
-    \ifdefined\rubyfont \rubyfont \fi
-    \ifcsname selectfont\endcsname % latex
-      \dimen@\dimexpr\rubysize em\relax
-      \fontsize\dimen@\dimen@\selectfont
-    \else % plain
-      \luatexko at resize@font\font\resized at font\font\rubysize
-      \ifdefined\hangul at font
-        \luatexko at resize@font\hangulfont{}\hangul at font\rubysize
-      \fi
-      \ifdefined\hanja at font
-        \luatexko at resize@font\hanjafont{}\hanja at font\rubysize
-      \fi
-      \ifdefined\fallback at font
-        \luatexko at resize@font\fallbackfont{}\fallback at font\rubysize
-      \fi
-    \fi #2}\ht\tw@\z@\dp\tw@\z@
-  \directlua{%
-    luatexko.rubynode[\the\luakorubyattrcnt] = {
-      node.copy_list(tex.box[2]),
-      \number\numexpr\dimexpr\rubysep\relax\relax,
-      \ifruby at overlap true\else false\fi}}%
-  \leavevmode\box\z@
-  \endgroup}
-
-\unless\ifcsname selectfont\endcsname
-% font resize under plain tex
-\begingroup\edef\x{\endgroup
-  \def\noexpand\rem at vesizepart##1\detokenize{ at}##2\noexpand\end{##1}}\x
-\def\luatexko at resize@font#1#2#3#4{%
-  \begingroup
-  \edef\x@{\noexpand\rem at vesizepart\fontname#3\detokenize{ at}\noexpand\end}%
-  \edef\y@{\endgroup#1\noexpand#2 \x@\space at#4\dimexpr\fontdimen6 #3\relax}%
-  \y@#2}
-\fi
-
-% xxruby: consecutive ruby annotations
-\protected\def\xxruby#1#2{%
-  \begingroup\def\basestr{#1}\def\rubystr{#2}\luako at xxrubyxx\endgroup
-}
-\def\luako at xxrubyxx{%
-  \ifx\empty\basestr\else
-    \expandafter\luako at ruby@get at base@char\basestr\end
-    \expandafter\luako at ruby@get at ruby@char\rubystr\end
-    \ruby\basechar\rubychar
-    \expandafter\luako at xxrubyxx
-  \fi
-}
-\def\luako at ruby@get at base@char#1#2\end{\def\basechar{#1}\def\basestr{#2}}
-\def\luako at ruby@get at ruby@char#1#2\end{\def\rubychar{#1}\def\rubystr{#2}}
-
-%% vertical typesetting
-%% EXPERIMENTAL code
-\def\luatexko at vertical{%
-  \ifnum\cjtypesetattr<\@ne
-    \cjtypesetattr\z@ % korean-vertical
-    \spaceskip 0.5em plus 0.15em minus 0.06em
-    \parindent 1em
-  \fi
-}
-\protected\def\vertical#1{%
-  \leavevmode
-  \setbox\z@\vbox\bgroup
-      \hsize#1\relax
-      \luatexko at vertical
-      \everypar{}%
-      \leftskip\z@
-      \rightskip\z@ }
-\protected\def\endvertical{\egroup\luatexkorotatebox\z@\box\z@}
-\protected\def\luatexkorotatebox#1{%
-  \ifvbox#1\count@\@ne\else\count@\z@\fi
-  \dimen@\wd#1\dimen at ii\dimexpr\ht#1+\dp#1\relax
-  \setbox#1\ifnum\count@=\@ne
-  \vbox to\dimen@\bgroup\hbox to\dimen at ii\bgroup\hfil\else
-  \hbox to\dimen at ii\bgroup\vbox to\dimen@\bgroup\fi
-    \wd#1\z@ \ht#1\z@ \dp#1\z@
-    \luatexko at rotate@clockwise{\box#1}%
-    \ifnum\count@=\@ne\egroup\vfil\else\vfil\egroup\hfil\fi\egroup}
-\def\luatexko at rotate@clockwise#1{%
-  \ifpdf\pdfliteral{q 0 -1 1 0 0 0 cm}#1\pdfliteral{Q}%
-  \else\special{pdf:btrans rotate -90}#1\special{pdf:etrans}%
-  \fi
-}
-
-% do not veticalize headline/footline
-\ifcsname selectfont\endcsname % latex
-  \def\verticaltypesetting{%
-    \maxdepth\z@ \luatexko at vertical
-    \dimen@\textwidth \textwidth\textheight \textheight\dimen@
-    \edef\@outputpage{\unexpanded{\luatexkorotatebox\@outputbox
-    \textwidth\textheight}\unexpanded\expandafter{\@outputpage}}}
-\else % plain
-  \def\verticaltypesetting{%
-    \dimen@\hsize\hsize\vsize\vsize\dimen@
-    \edef\plainoutput{\unexpanded{\dimen@\hsize\hsize\vsize\vsize\dimen@}%
-      \unexpanded\expandafter{\plainoutput}}%
-    \def\pagebody{\setbox\z@\vbox to\hsize{\boxmaxdepth=\maxdepth
-      \pagecontents}\luatexkorotatebox\z@\box\z@}%
-    \maxdepth\z@ \luatexko at vertical}
-\fi
-
-% horizontal box inside vertical typesetting
-\protected\def\horizontal#1{%
-  \setbox\z@\vbox\bgroup
-    \ifnum\cjtypesetattr<\@ne
-      \unsetattribute\cjtypesetattr
-      \spaceskip\fontdimen2\font
-                plus\fontdimen3\font
-                minus\fontdimen4\font
-    \fi
-    \hsize#1\relax
-    \everypar{}%
-    \leftskip\z@
-    \rightskip\z@
-    \parindent\z@
-}
-\protected\def\endhorizontal{\egroup\luatexkounrotatebox\z@\box\z@}
-\protected\def\luatexkounrotatebox#1{%
-  \leavevmode
-  \vbox to\wd#1\bgroup
-    \vfill
-    \hbox to\dimexpr\ht#1+\dp#1\bgroup
-      \wd#1\z@\ht#1\z@\dp#1\z@
-      \luatexko at rotate@counterclockwise{\box#1}%
-      \hfill
-    \egroup
-  \egroup
-}
-\def\luatexko at rotate@counterclockwise#1{%
-  \ifpdf\pdfliteral{q 0 1 -1 0 0 0 cm}#1\pdfliteral{Q}%
-  \else \special{pdf:btrans rotate 90}#1\special{pdf:etrans}%
-  \fi
-}
-
-%% underline using post_linebreak_filter
-%%  ulem doesn't work well with luatexko;
-%%  especially linebreak does not occur inside cjk words.
-\def\ulinewidth{0.04em }
-\def\ulinedown {0.24em }
-\newcount\luakoulineboxcnt\luakoulineboxcnt\z@
-\chardef\luako at uline@level\z@ % for nested uline
-\def\luako at nested@ulinedown{% must be used in a group
-  \ifcase\luako at uline@level\else
-    \edef\ulinedown{\the\dimexpr\ulinedown+0.1em\relax}%
-  \fi
-  \chardef\luako at uline@level\@ne}
-\def\luako at uline@code at common{%
-  \begingroup
-  \ifnum\luakoulineboxcnt > 65535 \luakoulineboxcnt\z@ \fi
-  \global\advance\luakoulineboxcnt\@ne
-  \count@\luakoulineboxcnt
-  \leavevmode}
-\protected\def\ulineluatexko#1{%
-  \luako at uline@code at common
-  \luako at nested@ulinedown
-  \dimen@\dimexpr\ulinedown+\ulinewidth\relax
-  \dimen at ii-\dimexpr\ulinedown\relax
-  \setbox\z@\hbox{\vrule width\z@ height\dimen at ii depth\dimen@}%
-  \directlua{ luatexko.uline_boundary(\the\count@,tex.box[0].head) }%
-  #1\relax
-  \directlua{ luatexko.uline_boundary(\the\count@) }%
-  \endgroup}
-\let\uline\ulineluatexko
-\protected\def\sout#1{{\def\ulinedown{-0.5ex-\ulinewidth/2}\ulineluatexko{#1}}}
-\protected\def\uuline#1{\ulineluatexko{\ulineluatexko{#1}}}
-% ulem with arbitray box
-\protected\def\markoverwith#1#2{%
-  \luako at uline@code at common
-  \setbox\z@\hbox{#1}%
-  \directlua{ luatexko.uline_boundary(\the\count@,tex.box[0]) }%
-  #2\relax
-  \directlua{ luatexko.uline_boundary(\the\count@) }%
-  \endgroup}
-\protected\def\uwave{\ifx\ltxko at sixly\undefined \font\ltxko at sixly=lasy6 \fi
-  \markoverwith{\lower4\p@\hbox{\ltxko at sixly\char58}}}
-\protected\def\xout {\markoverwith{\hbox to.35em{/\hss}}}
-\protected\def\dashuline#1{\begingroup
-  \luako at nested@ulinedown
-  \markoverwith{\kern.125em\vtop{\kern\ulinedown
-  \hrule width.3em height\z@ depth\ulinewidth}\kern.125em}{#1}\endgroup}
-\protected\def\dotuline#1{\begingroup
-  \luako at nested@ulinedown
-  \dimen@\dimexpr\ulinedown+0.1ex\relax
-  \markoverwith{\lower\dimen@\hbox{\kern.1em .\kern.04em}}{#1}\endgroup}
-
-%% hangul font -- core
-\def\luatexko at hangul@selectfont{%
-  \ifdefined\hangul at font
-  \begingroup
-  \let\luatexko at hangul@selectfont\relax
-  \let\luatexko at hanja@selectfont\relax
-  \let\luatexko at fallback@selectfont\relax
-  \hangul at font
-  \expandafter\expandafter\expandafter
-  \endgroup
-  \expandafter\hangulfntattr\fontid\font\relax
-  \fi
-}
-\def\luatexko at hanja@selectfont{%
-  \ifdefined\hanja at font
-  \begingroup
-  \let\luatexko at hangul@selectfont\relax
-  \let\luatexko at hanja@selectfont\relax
-  \let\luatexko at fallback@selectfont\relax
-  \hanja at font
-  \expandafter\expandafter\expandafter
-  \endgroup
-  \expandafter\hanjafntattr\fontid\font\relax
-  \fi
-}
-\def\luatexko at fallback@selectfont{%
-  \ifdefined\fallback at font
-  \begingroup
-  \let\luatexko at hangul@selectfont\relax
-  \let\luatexko at hanja@selectfont\relax
-  \let\luatexko at fallback@selectfont\relax
-  \fallback at font
-  \expandafter\expandafter\expandafter
-  \endgroup
-  \expandafter\fallbackfntattr\fontid\font\relax
-  \fi
-}
-
-% user interface for plain tex
-\protected\def\hangulfont
-  {\afterassignment\luatexko at hangul@selectfont\font\hangul at font}
-\protected\def\hanjafont
-  {\afterassignment\luatexko at hanja@selectfont \font\hanja at font }
-\protected\def\fallbackfont
-  {\afterassignment\luatexko at fallback@selectfont\font\fallback at font}
-
-%% hangul in math mode -- general
-\def\setmathhangulblock#1#2{%
-  \count@="#1
-  \loop
-    \Umathcode\count@ = 7 \symmathhangul \count@
-    \ifnum\count@<"#2
-      \advance\count@\@ne
-  \repeat}
-\unless\ifcsname selectfont\endcsname
-  % for plain; see luatexko.sty for latex
-  \newfam\symmathhangul
-  \def\setmathhangulfont#1#{\setmathhangul at font@}
-  \def\setmathhangul at font@#1{%
-    \font\tenhangul   = "#1" at 10pt
-    \font\sevenhangul = "#1" at 7pt
-    \font\fivehangul  = "#1" at 5pt
-    \textfont\symmathhangul         = \tenhangul
-    \scriptfont\symmathhangul       = \sevenhangul
-    \scriptscriptfont\symmathhangul = \fivehangul}
-  \setmathhangulblock{AC00}{D7A3}
-\fi
-
-%% hangul normalization
-\def\luatexhangulnormalize{\directlua{require "luatexko-normalize"}%
-  \afterassignment\luatexh at ngulnorm@lize\count@}
-\def\luatexh at ngulnorm@lize{%
-  \ifcase\count@ % 0: none
-    \directlua{ luatexkonormalize.unload() }%
-  \or % 1: nfc
-    \directlua{ luatexkonormalize.compose() }%
-  \else % 2: nfd
-    \directlua{ luatexkonormalize.decompose() }%
-  \fi}
-
-%% uhc to utf8
-\def\luatexuhcinputencoding{\directlua{require "luatexko-uhc2utf8"}%
-  \afterassignment\luatexuhcinput at ncoding\count@}
-\def\luatexuhcinput at ncoding{%
-  \ifcase\count@
-    \directlua{ luatexkouhc2utf8.stopconvert() }%
-  \else
-    \directlua{ luatexkouhc2utf8.startconvert() }%
-  \fi}
-
-%% uhc file names
-\def\luatexuhcfilenames{\directlua{require "luatexko-uhc2utf8"}%
-  \afterassignment\luatexuhcfilen at mes\count@}
-\def\luatexuhcfilen at mes{%
-  \ifcase\count@
-    \directlua{ luatexkouhc2utf8.stop_uhc_filename() }%
-  \else
-    \directlua{ luatexkouhc2utf8.start_uhc_filename() }%
-  \fi}
-
-%% use hangul font for hangul chars
-\def\hangulfontforhangul{\afterassignment\h at ngul@fontforh at ngul\count@}
-\let\hangulbyhangulfont\hangulfontforhangul
-\def\h at ngul@fontforh at ngul{%
-  \ifcase\count@
-    \directlua{ luatexko.hangulfontforhangul = false }%
-  \else
-    \directlua{ luatexko.hangulfontforhangul = true }%
-  \fi}
-
-%% use hanja font for hanja chars
-\def\hanjafontforhanja{\afterassignment\h at nj@fontforh at nj@\count@}
-\let\hanjabyhanjafont\hanjafontforhanja
-\def\h at nj@fontforh at nj@{%
-  \ifcase\count@
-    \directlua{ luatexko.hanjafontforhanja = false }%
-  \else
-    \directlua{ luatexko.hanjafontforhanja = true }%
-  \fi}
-
-% latin punctuations typeset with hangul fonts
-\def\hangulpunctuations{\afterassignment\h at ngulpunctu@tions\count@}
-\def\h at ngulpunctu@tions{%
-  \ifcase\count@
-    \unsetattribute\hangulpunctsattr
-  \else
-    \hangulpunctsattr\@ne
-  \fi
-}
-\def\registerpunctuations#1{\registerpunctu at tions#1,*,}
-\def\registerpunctu at tions#1,{%
-  \ifx\empty#1\empty
-    \expandafter\registerpunctu at tions
-  \else\ifx*#1\else
-    \directlua{ luatexko.hangulpunctuations[\number#1]=true }
-    \expandafter\expandafter\expandafter\registerpunctu at tions
-  \fi\fi
-}
-\def\unregisterpunctuations#1{\unregisterpunctu at tions#1,*,}
-\def\unregisterpunctu at tions#1,{%
-  \ifx\empty#1\empty
-    \expandafter\unregisterpunctu at tions
-  \else\ifx*#1\else
-    \directlua{ luatexko.hangulpunctuations[\number#1]=nil }
-    \expandafter\expandafter\expandafter\unregisterpunctu at tions
-  \fi\fi
-}
-
-% Actual Text: beware that #2 should not be divided into separate pages.
-% \actualtext* variant inserts space char from phvr8r. see accsupp manual
-\protected\def\actualtext#1#{%
-  \ifx\empty#1\empty
-    \expandafter\actualtext@\expandafter!%
-  \else
-    \expandafter\actualtext@\expandafter*%
-  \fi
-}
-\def\actualtext@{%
-  \begingroup
-  \catcode`\\=12 \catcode`\$=12 \catcode`\&=12 \catcode`\#=12
-  \catcode`\^=12 \catcode`\_=12 \catcode`\~=12 %catcode`\%=12
-  \actualtext@@
-}
-\def\actualtext@@#1#2{%
-  \endgroup
-  \luatexko at pdfspecial{/Span<</ActualText\directlua{
-    luatexko.actualtext("\luaescapestring{\detokenize{#2}}")
-  }>>BDC}%
-  \scantextokens{#2}%
-  \ifx*#1%
-    \begingroup
-    \ifdefined\actualtext at spacefont\else
-      \global\font\actualtext at spacefont=phvr8r\relax
-    \fi
-    \setbox\z@\hbox{\actualtext at spacefont\char32}%
-    \wd\z@\z@\ht\z@\z@\dp\z@\z@
-    \leavevmode\copy\z@
-    \endgroup
-  \fi
-  \luatexko at pdfspecial{EMC}%
-}
-\def\luatexko at pdfspecial#1{%
-  \ifpdf\pdfliteral page{#1}%
-  \else\special{pdf:content #1}%
-  \fi
-}
-
-%% 말줄임표
-\def\hellipsis{^^^^2026^^^^2026}
-
-\endinput

Modified: trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-normalize.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-normalize.lua	2019-05-01 23:38:43 UTC (rev 50923)
+++ trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-normalize.lua	2019-05-02 21:10:43 UTC (rev 50924)
@@ -13,8 +13,8 @@
 
 luatexbase.provides_module({
   name        = "luatexko-normalize",
-  version     = "1.23",
-  date        = "2019/03/24",
+  version     = "2.0",
+  date        = "2019/05/01",
   author      = "Dohyun Kim, Soojin Nam",
   description = "Hangul normalization",
   license     = "LPPL v1.3+",
@@ -408,7 +408,6 @@
 local char = unicodeutf8.char
 local find = unicodeutf8.find
 local concat = table.concat
-local floor = math.floor
 local add_to_callback = luatexbase.add_to_callback
 local remove_from_callback = luatexbase.remove_from_callback
 
@@ -426,8 +425,8 @@
 local syllable2jamo = function(s)
     s = byte(s) - 0xac00
     local t = {}
-    t[1] = char(floor(s / 588) + 0x1100)
-    t[2] = char(floor(s % 588 / 28) + 0x1161)
+    t[1] = char(s // 588 + 0x1100)
+    t[2] = char(s % 588 // 28 + 0x1161)
     local jong = s % 28
     t[3] = jong > 0 and char(jong + 0x11a7) or nil
     return concat(t)
@@ -434,7 +433,7 @@
 end
 
 local hanguldecompose = function(buffer)
-  return gsub(buffer, "[가-힣]", syllable2jamo)
+  return gsub(buffer, "[\234\176\128-\237\158\163]", syllable2jamo)
 end
 
 local function hanjanormalize(c)
@@ -457,7 +456,7 @@
 end
 
 local hangulcompose = function(buffer)
-  buffer = gsub(buffer, "[가-힣]"..jong, hanguldecompose)
+  buffer = gsub(buffer, "[\234\176\128-\237\158\163]"..jong, hanguldecompose)
   buffer = gsub(buffer, "("..ncho..")("..njung..")("..jong.."?)", jamo2syllable)
   buffer = gsub(buffer,
     "([\225\132\128-\225\133\153])\225\133\160", jamo2cjamocho)

Modified: trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-uhc2utf8.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-uhc2utf8.lua	2019-05-01 23:38:43 UTC (rev 50923)
+++ trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko-uhc2utf8.lua	2019-05-02 21:10:43 UTC (rev 50924)
@@ -13,8 +13,8 @@
 
 luatexbase.provides_module({
   name        = "luatexko-uhc2utf8",
-  version     = "1.23",
-  date        = "2019/03/24",
+  version     = "2.0",
+  date        = "2019/05/01",
   author      = "Dohyun Kim, Soojin Nam",
   description = "UHC (CP949) input encoding",
   license     = "LPPL v1.3+",
@@ -24,12 +24,12 @@
 local luatexkouhc2utf8 = luatexkouhc2utf8
 
 local format = string.format
+local utf8len = utf8.len
 require "unicode"
 local unicodeutf8 = unicode.utf8
 local ugsub = unicodeutf8.gsub
 local ubyte = unicodeutf8.byte
 local uchar = unicodeutf8.char
-local floor = math.floor
 local kpse_find_file = kpse.find_file
 local add_to_callback = luatexbase.add_to_callback
 local remove_from_callback = luatexbase.remove_from_callback
@@ -58,38 +58,10 @@
 
 local t_uhc2ucs = t_uhc2ucs or get_uhc_uni_table()
 
-local function not_utf8lowbyte(t)
-  for _,v in ipairs(t) do
-    -- rough checking
-    if v < 0x80 or v > 0xBF then return true end
-  end
-  return false
-end
-
 local uhc_to_utf8 = function(buffer)
   if not buffer then return end
-  -- check if buffer is already utf-8; better solution?
-  local i, buflen = 1, buffer:len()+1
-  while i < buflen do
-    local a = buffer:byte(i)
-    if a < 0x80 then
-      i = i + 1
-    elseif a < 0xC2 then
-      break
-    elseif a < 0xE0 then
-      if not_utf8lowbyte({buffer:byte(i+1)}) then break end
-      i = i + 2
-    elseif a < 0xF0 then
-      if not_utf8lowbyte({buffer:byte(i+1,i+2)}) then break end
-      i = i + 3
-    elseif a < 0xF5 then
-      if not_utf8lowbyte({buffer:byte(i+1,i+3)}) then break end
-      i = i + 4
-    else
-      break
-    end
-  end
-  if i == buflen then return nil end
+  -- check if buffer is already utf-8
+  if utf8len(buffer) then return nil end
   -- now convert to utf8
   buffer = buffer:gsub("([\129-\253])([\65-\254])",
   function(a, b)
@@ -128,7 +100,7 @@
   function(u)
     local c = t_ucs2uhc[ubyte(u)]
     if c then
-      return format("%c%c", floor(c/256), c%256)
+      return format("%c%c", c//256, c%256)
     end
   end)
   return name

Modified: trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko.lua	2019-05-01 23:38:43 UTC (rev 50923)
+++ trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko.lua	2019-05-02 21:10:43 UTC (rev 50924)
@@ -1,7 +1,7 @@
 -- luatexko.lua
 --
--- Copyright (c) 2013-2019  Dohyun Kim  <nomos at ktug org>
---                          Soojin Nam  <jsunam at gmail com>
+-- Copyright (c) 2013-2019 Dohyun Kim <nomos at ktug org>
+--                         Soojin Nam <jsunam at gmail com>
 --
 -- This work may be distributed and/or modified under the
 -- conditions of the LaTeX Project Public License, either version 1.3c
@@ -13,1902 +13,1307 @@
 
 luatexbase.provides_module {
   name        = 'luatexko',
-  date        = '2019/04/06',
-  version     = '1.24',
-  description = 'Korean linebreaking and font-switching',
+  date        = '2019/05/01',
+  version     = '2.0',
+  description = 'typesetting Korean with LuaTeX',
   author      = 'Dohyun Kim, Soojin Nam',
   license     = 'LPPL v1.3+',
 }
 
-luatexko        = luatexko or {}
-local luatexko  = luatexko
+luatexko = luatexko or {}
+local luatexko = luatexko
 
-local dotemphnode,rubynode  = {},{}
-luatexko.dotemphnode        = dotemphnode
-luatexko.rubynode           = rubynode
+local dimensions      = node.dimensions
+local end_of_math     = node.end_of_math
+local getglue         = node.getglue
+local getnext         = node.getnext
+local getprev         = node.getprev
+local getproperty     = node.getproperty
+local has_attribute   = node.has_attribute
+local has_glyph       = node.has_glyph
+local insert_after    = node.insert_after
+local insert_before   = node.insert_before
+local nodecopy        = node.copy
+local nodecount       = node.count
+local nodefree        = node.free
+local nodeid          = node.id
+local nodenew         = node.new
+local noderemove      = node.remove
+local nodeslide       = node.slide
+local nodesubtype     = node.subtype
+local nodewrite       = node.write
+local rangedimensions = node.rangedimensions
+local setglue         = node.setglue
+local setproperty     = node.setproperty
+local unset_attribute = node.unset_attribute
 
-local stringbyte    = string.byte
-local stringchar    = string.char
-local stringformat  = string.format
-local mathfloor     = math.floor
-local tex_round     = tex.round
-local tex_sp        = tex.sp
-local texcount      = tex.count
-local fontdefine    = font.define
+local fontcurrent   = font.current
+local fontfonts     = font.fonts
+local fontgetfont   = font.getfont
+local getparameters = font.getparameters
 
-local kpse_find_file = kpse.find_file
+local texcount = tex.count
+local texround = tex.round
+local texset   = tex.set
+local texsp    = tex.sp
 
-local fontdata = fonts.hashes.identifiers
+local stringformat = string.format
 
-local finemathattr      = luatexbase.attributes.finemathattr
-local cjtypesetattr     = luatexbase.attributes.cjtypesetattr
-local dotemphattr       = luatexbase.attributes.dotemphattr
-local autojosaattr      = luatexbase.attributes.autojosaattr
-local luakorubyattr     = luatexbase.attributes.luakorubyattr
-local hangulfntattr     = luatexbase.attributes.hangulfntattr
-local hanjafntattr      = luatexbase.attributes.hanjafntattr
-local fallbackfntattr   = luatexbase.attributes.fallbackfntattr
-local hangulpunctsattr  = luatexbase.attributes.hangulpunctsattr
-local luakoglueattr     = luatexbase.new_attribute("luakoglueattr")
-local luakounicodeattr  = luatexbase.new_attribute("luakounicodeattr")
-local luakoulineattr    = luatexbase.new_attribute("luakoulineattr")
+local tableconcat = table.concat
+local tableinsert = table.insert
 
-local function warn (...)
-  return luatexbase.module_warning("luatexko", stringformat(...))
+local add_to_callback     = luatexbase.add_to_callback
+local attributes          = luatexbase.attributes
+local call_callback       = luatexbase.call_callback
+local create_callback     = luatexbase.create_callback
+local module_warning      = luatexbase.module_warning
+local new_user_whatsit    = luatexbase.new_user_whatsit
+local new_user_whatsit_id = luatexbase.new_user_whatsit_id
+local registernumber      = luatexbase.registernumber
+
+local function warning (...)
+  return module_warning("luatexko", stringformat(...))
 end
 
-local function add_to_callback (name, func, desc, pos)
-  local t = {{func, desc}}
-  if pos then
-    for i,v in ipairs(luatexbase.callback_descriptions(name)) do
-      if i >= pos then
-        t[#t+1] = {luatexbase.remove_from_callback(name, v)}
-      end
-    end
-  end
-  for i,v in ipairs(t) do
-    luatexbase.add_to_callback(name, v[1], v[2])
-  end
+local dirid     = nodeid"dir"
+local discid    = nodeid"disc"
+local glueid    = nodeid"glue"
+local glyphid   = nodeid"glyph"
+local hlistid   = nodeid"hlist"
+local kernid    = nodeid"kern"
+local mathid    = nodeid"math"
+local penaltyid = nodeid"penalty"
+local ruleid    = nodeid"rule"
+local vlistid   = nodeid"vlist"
+local whatsitid = nodeid"whatsit"
+local literal_whatsit = nodesubtype"pdf_literal"
+local user_whatsit    = nodesubtype"user_defined"
+local directmode = 2
+local fontkern   = 0
+local userkern   = 1
+local italcorr   = 3
+local lua_number = 100
+local lua_value  = 108
+local spaceskip  = 13
+local xleaders   = 102
+local nohyphen = registernumber"l at nohyphenation" or -1 -- verbatim
+
+local hangulfontattr   = attributes.luatexkohangulfontattr
+local hanjafontattr    = attributes.luatexkohanjafontattr
+local fallbackfontattr = attributes.luatexkofallbackfontattr
+local autojosaattr     = attributes.luatexkoautojosaattr
+local classicattr      = attributes.luatexkoclassicattr
+local dotemphattr      = attributes.luatexkodotemphattr
+local rubyattr         = attributes.luatexkorubyattr
+
+local vert_classic = 1
+local SC_classic   = 2
+
+local function is_hanja (c)
+  return (c >= 0x3400 and c <= 0xA4C6)
+  or     (c >= 0xF900 and c <= 0xFAD9)
+  or     (c >= 0x20000 and c <= 0x2FFFD)
+  or     (c >= 0x2E81 and c <= 0x2FD5)
 end
 
-local glue_type_space = 13
-local kern_type_itlc  = 3
+local function is_hangul (c)
+  return (c >= 0xAC00 and c <= 0xD7A3)
+end
 
-local discnode        = node.id("disc")
-local gluenode        = node.id("glue")
-local glyphnode       = node.id("glyph")
-local mathnode        = node.id("math")
-local hlistnode       = node.id("hlist")
-local vlistnode       = node.id("vlist")
-local kernnode        = node.id("kern")
-local penaltynode     = node.id("penalty")
-local rulenode        = node.id("rule")
-local whatsitnode     = node.id("whatsit")
-local user_whatsit    = node.subtype("user_defined")
-local n_copy_list     = node.copy_list
+local function is_chosong (c)
+  return (c >= 0x1100 and c <= 0x115F)
+  or     (c >= 0xA960 and c <= 0xA97C)
+end
 
-local nodedirect        = node.direct
-local d_getdisc         = nodedirect.getdisc
-local d_todirect        = nodedirect.todirect
-local d_tonode          = nodedirect.tonode
-local d_has_field       = nodedirect.has_field
-local d_copy_node       = nodedirect.copy
-local d_getid           = nodedirect.getid
-local d_getfield        = nodedirect.getfield
-local d_setfield        = nodedirect.setfield
-local d_getsubtype      = nodedirect.getsubtype
-local d_setsubtype      = nodedirect.setsubtype
-local d_getchar         = nodedirect.getchar
-local d_setchar         = nodedirect.setchar
-local d_getfont         = nodedirect.getfont
-local d_getlist         = nodedirect.getlist
-local d_setlist         = nodedirect.setlist
-local d_getnext         = nodedirect.getnext
-local d_setnext         = nodedirect.setnext
-local d_getprev         = nodedirect.getprev
-local d_setprev         = nodedirect.setprev
-local d_setleader       = nodedirect.setleader
-local d_getglue         = nodedirect.getglue
-local d_setglue         = nodedirect.setglue
-local d_has_attribute   = nodedirect.has_attribute
-local d_set_attribute   = nodedirect.set_attribute
-local d_unset_attribute = nodedirect.unset_attribute
-local d_traverse        = nodedirect.traverse
-local d_traverse_id     = nodedirect.traverse_id
-local d_insert_before   = nodedirect.insert_before
-local d_insert_after    = nodedirect.insert_after
-local d_remove_node     = nodedirect.remove
-local d_nodenew         = nodedirect.new
-local d_nodecount       = nodedirect.count
-local d_end_of_math     = nodedirect.end_of_math
-local d_nodetail        = nodedirect.slide -- tail seems to be no good
-local d_nodedimensions  = nodedirect.dimensions
-local d_nodefree        = nodedirect.free
-local d_nodewrite       = nodedirect.write
-local d_node_zero_glue  = nodedirect.is_zero_glue
+local function is_jungsong (c)
+  return (c >= 0x1160 and c <= 0x11A7)
+  or     (c >= 0xD7B0 and c <= 0xD7C6)
+end
 
-local emsize = 655360
+local function is_jongsong (c)
+  return (c >= 0x11A8 and c <= 0x11FF)
+  or     (c >= 0xD7CB and c <= 0xD7FB)
+end
 
-local cjkclass = {
-  [0x2018] = 1, -- ‘
-  [0x201C] = 1, -- “
-  [0x2329] = 1, -- 〈
-  [0x3008] = 1, -- 〈
-  [0x300A] = 1, -- 《
-  [0x300C] = 1, -- 「
-  [0x300E] = 1, -- 『
-  [0x3010] = 1, -- 【
-  [0x3014] = 1, -- 〔
-  [0x3016] = 1, -- 〖
-  [0x3018] = 1, -- 〘
-  [0x301A] = 1, -- 〚
-  [0x301D] = 1, -- 〝
-  [0xFE17] = 1, -- ︗
-  [0xFE35] = 1, -- ︵
-  [0xFE37] = 1, -- ︷
-  [0xFE39] = 1, -- ︹
-  [0xFE3B] = 1, -- ︻
-  [0xFE3D] = 1, -- ︽
-  [0xFE3F] = 1, -- ︿
-  [0xFE41] = 1, -- ﹁
-  [0xFE43] = 1, -- ﹃
-  [0xFE47] = 1, -- ﹇
-  [0xFF08] = 1, -- (
-  [0xFF3B] = 1, -- [
-  [0xFF5B] = 1, -- {
-  [0xFF5F] = 1, -- ⦅
-  [0xFF62] = 1, -- 「 half
-  [0x2019] = 2, -- ’
-  [0x201D] = 2, -- ”
-  [0x232A] = 2, -- 〉
-  [0x3001] = 2, -- 、
-  [0x3009] = 2, -- 〉
-  [0x300B] = 2, -- 》
-  [0x300D] = 2, -- 」
-  [0x300F] = 2, -- 』
-  [0x3011] = 2, -- 】
-  [0x3015] = 2, -- 〕
-  [0x3017] = 2, -- 〗
-  [0x3019] = 2, -- 〙
-  [0x301B] = 2, -- 〛
-  [0x301E] = 2, -- 〞
-  [0x301F] = 2, -- 〟
-  [0xFE10] = 2, -- ︐
-  [0xFE11] = 2, -- ︑
-  [0xFE18] = 2, -- ︘
-  [0xFE36] = 2, -- ︶
-  [0xFE38] = 2, -- ︸
-  [0xFE3A] = 2, -- ︺
-  [0xFE3C] = 2, -- ︼
-  [0xFE3E] = 2, -- ︾
-  [0xFE40] = 2, -- ﹀
-  [0xFE42] = 2, -- ﹂
-  [0xFE44] = 2, -- ﹄
-  [0xFE48] = 2, -- ﹈
-  [0xFF09] = 2, -- )
-  [0xFF0C] = 2, -- ,
-  [0xFF3D] = 2, -- ]
-  [0xFF5D] = 2, -- }
-  [0xFF60] = 2, -- ⦆
-  [0xFF63] = 2, -- 」 half
-  [0xFF64] = 2, -- 、 half
-  [0x00B7] = 3, -- ·
-  [0x30FB] = 3, -- ・
-  [0xFF1A] = 3, -- :
-  [0xFF1B] = 3, -- ;
-  [0xFF65] = 3, -- ・ half
-  [0x3002] = 4, -- 。
-  [0xFE12] = 4, -- ︒
-  [0xFF0E] = 4, -- .
-  [0xFF61] = 4, -- 。 half
-  [0x2015] = 5, -- ―
-  [0x2025] = 5, -- ‥
-  [0x2026] = 5, -- …
-  [0xFE19] = 5, -- ︙
-  [0xFE30] = 5, -- ︰
-  [0xFE31] = 5, -- ︱
-  [0xFE15] = 6, -- ︕
-  [0xFE16] = 6, -- ︖
-  [0xFF01] = 6, -- !
-  [0xFF1F] = 6, -- ?
+local hangul_tonemark = {
+  [0x302E] = true, [0x302F] = true,
 }
 
-local inhibitxspcode = setmetatable({
-  [0x002D] = 0, -- - hyphen minus
-  [0x003C] = 0, -- <
-  [0x003E] = 1, -- >
-  [0x00B0] = 1, -- °
-  [0x2032] = 1, -- ′
-  [0x2033] = 1, -- ″
-  [0x25CC] = 2, -- dotted circle
-  [0x3012] = 2, -- 〒
-  [0x301C] = 0, -- 〜
-  [0xFE14] = 1, -- ︔
-  [0xFF1B] = 1, -- ;
-  [0xFFE5] = 0, -- ¥
-},{
-  __index = function (_,c)
-    if cjkclass[c] == 1 then
-      return 2
-    elseif cjkclass[c] == 2 or cjkclass[c] == 4 or cjkclass[c] == 6 then
-      return 1
-    elseif cjkclass[c] == 5 then
-      return 0
-    end
+local function is_unicode_vs (c)
+  return (c >= 0xFE00  and c <= 0xFE0F)
+  or     (c >= 0xE0100 and c <= 0xE01EF)
+end
+
+local function is_cjk_combining (c)
+  return (c >= 0x302A and c <= 0x302F)
+  or     (c >= 0x3099 and c <= 0x309C)
+  or     (c >= 0xFF9E and c <= 0xFF9F)
+  or     is_unicode_vs(c)
+end
+
+local function is_noncjkletter (c)
+  return (c >= 0x30 and c <= 0x39)
+  or     (c >= 0x41 and c <= 0x5A)
+  or     (c >= 0x61 and c <= 0x7A)
+  or     (c >= 0xC0 and c <= 0xD6)
+  or     (c >= 0xD8 and c <= 0xF6)
+  or     (c >= 0xF8 and c <= 0x10FC)
+  or     (c >= 0x1200 and c <= 0x1FFE)
+  or     (c >= 0xA4D0 and c <= 0xA877)
+  or     (c >= 0xAB01 and c <= 0xABBF)
+  or     (c >= 0xFB00 and c <= 0xFDFD)
+  or     (c >= 0xFE70 and c <= 0xFEFC)
+end
+
+local function is_kana (c)
+  return (c >= 0x3041 and c <= 0x3096)
+  or     (c >= 0x30A1 and c <= 0x30FA)
+  or     (c >= 0x31F0 and c <= 0x31FF)
+  or     (c >= 0xFF66 and c <= 0xFF6F)
+  or     (c >= 0xFF71 and c <= 0xFF9D)
+  or      c == 0x309F or c == 0x30FF
+  or     (c >= 0x1B000 and c <= 0x1B16F)
+end
+
+local function is_hangul_jamo (c)
+  return is_hangul(c)
+  or     is_chosong(c)
+  or     is_jungsong(c)
+  or     is_jongsong(c)
+  or     (c >= 0x3131 and c <= 0x318E)
+end
+
+local stretch_f, shrink_f = 5/100, 3/100 -- should be consistent for ruby
+
+local function get_font_data (fontid)
+  return fontgetfont(fontid) or fontfonts[fontid] or {}
+end
+
+local function get_en_size (f)
+  local pm
+  if type(f) == "number" then
+    pm = getparameters(f)
+  elseif type(f) == "table" then
+    pm = f.parameters
   end
-})
+  return pm and pm.quad and pm.quad/2 or 327680
+end
 
-local postbreakpenalty = setmetatable({
-  [0x0023] = 500,
-  [0x0024] = 500,
-  [0x0025] = 500,
-  [0x0026] = 500,
-  [0x0028] = 10000,
-  [0x003C] = 10000, -- <
-  [0x005B] = 10000,
-  [0x0060] = 10000,
-  [0x007B] = 10000, -- {
-  [0x00AB] = 10000, -- «
-  [0x2013] = 50, -- en-dash
-  [0x2014] = 50, -- em-dash
-  [0xFF03] = 200,
-  [0xFF04] = 200,
-  [0xFF05] = 200,
-  [0xFF06] = 200,
-  [0xFF40] = 10000, -- `
-},{
-  __index = function (_,c)
-    if cjkclass[c] == 1 then
-      return 10000
-    end
+local function char_in_font(fontdata, char)
+  if type(fontdata) == "number" then
+    fontdata = get_font_data(fontdata)
   end
-})
+  if fontdata.characters then
+    return fontdata.characters[char]
+  end
+end
 
-local prebreakpenalty = setmetatable({
-  [0x0021] = 10000,
-  [0x0022] = 10000,
-  [0x0027] = 10000,
-  [0x0029] = 10000,
-  [0x002A] = 5000, -- 500, -- *
-  [0x002B] = 5000, -- 500, -- +
-  [0x002C] = 10000,
-  [0x002D] = 10000,
-  [0x002E] = 10000,
-  [0x002F] = 5000, -- 500, -- /
-  [0x003A] = 10000,
-  [0x003B] = 10000,
-  [0x003E] = 10000, -- >
-  [0x003F] = 10000,
-  [0x005D] = 10000,
-  [0x007D] = 10000, -- }
-  [0x00B4] = 10000, -- ´
-  [0x00BB] = 10000, -- »
-  [0x2013] = 10000, -- –
-  [0x2014] = 10000, -- —
-  [0x2025] = 5000, -- 250, -- ‥
-  [0x2026] = 5000, -- 250, -- …
-  [0x2212] = 5000, -- 200, -- −  minus sign
-  [0x3005] = 10000, -- 々
-  [0x301C] = 10000, -- 〜
-  [0x3035] = 10000, -- 〵
-  [0x303B] = 10000, -- 〻
-  [0x3041] = 150,
-  [0x3043] = 150,
-  [0x3045] = 150,
-  [0x3047] = 150,
-  [0x3049] = 150,
-  [0x3063] = 150,
-  [0x3083] = 150,
-  [0x3085] = 150,
-  [0x3087] = 150,
-  [0x308E] = 150,
-  [0x3095] = 150,
-  [0x3096] = 150,
-  [0x309B] = 10000, -- ゛
-  [0x309C] = 10000, -- ゜
-  [0x309D] = 10000, -- ゝ
-  [0x309E] = 10000, -- ゞ
-  [0x30A0] = 10000, -- ゠
-  [0x30A1] = 150,
-  [0x30A3] = 150,
-  [0x30A5] = 150,
-  [0x30A7] = 150,
-  [0x30A9] = 150,
-  [0x30C3] = 150,
-  [0x30E3] = 150,
-  [0x30E5] = 150,
-  [0x30E7] = 150,
-  [0x30EE] = 150,
-  [0x30F5] = 150,
-  [0x30F6] = 150,
-  [0x30FC] = 10000, -- ー
-  [0x30FD] = 10000, -- ヽ
-  [0x30FE] = 10000, -- ヾ
-  [0xFE13] = 10000, -- ︓
-  [0xFE14] = 10000, -- ︔
-  [0xFE19] = 5000, -- 250, -- ︙
-  [0xFE30] = 5000, -- 250, -- ︰
-  [0xFE32] = 10000, -- ︲
-  [0xFF0B] = 5000, -- 200, -- +
-  [0xFF1D] = 5000, -- 200, -- =
-  [0xFF9E] = 10000, -- ゙
-  [0xFF9F] = 10000, -- ゚
-},{
-  __index = function (_,c)
-    if cjkclass[c] and cjkclass[c] > 1 then
-      return 10000
-    end
+local function option_in_font (fontdata, optionname)
+  if type(fontdata) == "number" then
+    fontdata = get_font_data(fontdata)
   end
-})
+  if fontdata.shared then
+    return fontdata.shared.features[optionname]
+  end
+end
 
-local xspcode = {
-  [0x0027] = 2,
-  [0x0028] = 1,
-  [0x0029] = 2,
-  [0x002C] = 2,
-  [0x002E] = 2,
-  [0x003B] = 2,
-  [0x005B] = 1,
-  [0x005D] = 2,
-  [0x0060] = 1,
-}
+local function my_node_props (n)
+  local t = getproperty(n)
+  if not t then
+    t = {}
+    setproperty(n, t)
+  end
+  t.luatexko = t.luatexko or {}
+  return t.luatexko
+end
 
-local cjk_glue_spec = { [0] =
---     한자      (         )         ·         .         —         ?         한글      초성      중종성    latin
-{[0] = nil,      {.5,.5},  nil,      {.25,.25},nil,      nil,      nil,      nil,      nil,      nil,      nil,      }, --한자
-{[0] = nil,      nil,      nil,      {.25,.25},nil,      nil,      nil,      nil,      nil,      nil,      nil,      }, -- (
-{[0] = {.5,.5},  {.5,.5},  nil,      {.25,.25},nil,      {.5,.5},  {.5,.5},  {.25,.25},{.25,.25},{.25,.25},{.5,.5},  }, -- )
-{[0] = {.25,.25},{.25,.25},{.25,.25},{.5,.25}, {.25,.25},{.25,.25},{.25,.25},{.25,.25},{.25,.25},{.25,.25},{.25,.25},}, -- ·
-{[0] = {.5,0},   {.5,0},   nil,      {.75,.25},nil,      {.5,0},   {.5,0},   {.5,0},   {.5,0},   {.5,0},   {.5,0},   }, -- .
-{[0] = nil,      {.5,.5},  nil,      {.25,.25},nil,      nil,      nil,      nil,      nil,      nil,      nil,      }, -- —
-{[0] = {.5,.5},  {.5,.5},  nil,      {.25,.25},nil,      nil,      nil,      {.5,.5},  {.5,.5},  {.5,.5},  {.5,.5},  }, -- ?
---
-{[0] = nil,      {.25,.25},nil,      {.25,.25},nil,      nil,      nil,      nil,      nil,      nil,      nil,      }, --한글
-{[0] = nil,      {.25,.25},nil,      {.25,.25},nil,      nil,      nil,      nil,      nil,      nil,      nil,      }, --초성
-{[0] = nil,      {.25,.25},nil,      {.25,.25},nil,      nil,      nil,      nil,      nil,      nil,      nil,      }, --중종성
-{[0] = nil,      {.5,.5},  nil,      {.25,.25},nil,      nil,      nil,      nil,      nil,      nil,      nil,      }, --latin
-}
+-- font fallback
 
-local latin_fullstop = {
-  [0x2e] = 1, -- .
-  --  [0x21] = 2, -- !
-  --  [0x2c] = 2, -- ,
-  --  [0x3f] = 2, -- ?
-  --  [0x2026] = 1, -- \ldots
-}
-
-local hangulpunctuations = luatexko.hangulpunctuations or {
+local force_hangul = {
   [0x21] = true, -- !
   [0x27] = true, -- '
   [0x28] = true, -- (
   [0x29] = true, -- )
   [0x2C] = true, -- ,
-  -- [0x2D] = true, -- -
   [0x2E] = true, -- .
   [0x3A] = true, -- :
   [0x3B] = true, -- ;
-  [0x3C] = true, -- <
-  [0x3E] = true, -- >
   [0x3F] = true, -- ?
-  [0x5B] = true, -- [
-  [0x5D] = true, -- ]
   [0x60] = true, -- `
-  [0x7B] = true, -- {
-  [0x7D] = true, -- }
-  [0xB7] = true, -- periodcentered
-  -- [0x2014] = true, -- emdash
-  -- [0x2015] = true, -- horizontal bar
-  [0x2018] = true, -- quoteleft
-  [0x2019] = true, -- quoteright
-  [0x201C] = true, -- quotedblleft
-  [0x201D] = true, -- quotedblright
-  [0x2026] = true, -- ellipsis
+  [0xB7] = true, -- ·
+  [0x2014] = true, -- —
+  [0x2015] = true, -- ―
+  [0x2018] = true, -- ‘
+  [0x2019] = true, -- ’
+  [0x201C] = true, -- “
+  [0x201D] = true, -- ”
+  [0x2026] = true, -- …
   [0x203B] = true, -- ※
 }
-luatexko.hangulpunctuations = hangulpunctuations
+luatexko.forcehangulchars = force_hangul
 
-local josa_list = {
-  --          리을,   중성,   종성
-  [0xAC00] = {0xC774, 0xAC00, 0xC774}, -- 가 = 이, 가, 이
-  [0xC740] = {0xC740, 0xB294, 0xC740}, -- 은 = 은, 는, 은
-  [0xC744] = {0xC744, 0xB97C, 0xC744}, -- 을 = 을, 를, 을
-  [0xC640] = {0xACFC, 0xC640, 0xACFC}, -- 와 = 과, 와, 과
-  [0xC73C] = {-1,     -1,     0xC73C}, -- 으(로) =   ,  , 으
-  [0xC774] = {0xC774, -1,     0xC774}, -- 이(라) = 이,  , 이
-}
+local forcehf_f, forcehf_id = new_user_whatsit("forcehf","luatexko")
 
-local josa_code = {
-  [0x30]    = 3, -- 0
-  [0x31]    = 1, -- 1
-  [0x33]    = 3, -- 3
-  [0x36]    = 3, -- 6
-  [0x37]    = 1, -- 7
-  [0x38]    = 1, -- 8
-  [0x4C]    = 1, -- L
-  [0x4D]    = 3, -- M
-  [0x4E]    = 3, -- N
-  [0x6C]    = 1, -- l
-  [0x6D]    = 3, -- m
-  [0x6E]    = 3, -- n
-  [0xFB02]  = 1, -- fl
-  [0xFB04]  = 1, -- ffl
-  ng        = 3,
-  ap        = 3,
-  up        = 3,
-  at        = 3,
-  et        = 3,
-  it        = 3,
-  ot        = 3,
-  ut        = 3,
-  ok        = 3,
-  ic        = 3,
-  le        = 1,
-  ime       = 3,
-  ine       = 3,
-  ack       = 3,
-  ick       = 3,
-  oat       = 2,
-  TEX       = 3,
-  -- else 2
-}
-
-local function d_get_gluenode (...)
-  local g = d_nodenew(gluenode)
-  d_setglue(g, ...)
-  return g
+local function update_force_hangul (value)
+  local what = forcehf_f()
+  what.type  = lua_value -- function
+  what.value = value
+  nodewrite(what)
 end
+luatexko.updateforcehangul = update_force_hangul
 
-local function d_get_penaltynode (n)
-  local p = d_nodenew(penaltynode)
-  d_setfield(p,"penalty", n or 0)
-  return p
-end
+local active_processes = {}
 
-local function d_get_kernnode (n)
-  local k = d_nodenew(kernnode,1)
-  d_setfield(k,"kern", n or 0)
-  return k
-end
+local char_font_options ={
+  interhangul     = {},
+  interlatincjk   = {},
+  intercharacter  = {},
+  hangulspaceskip = {},
+  tonemarkwidth   = {},
+}
 
-local function d_get_rulenode (w,h,d)
-  local r = d_nodenew(rulenode)
-  d_setfield(r,"width",  w or 0)
-  d_setfield(r,"height", h or 0)
-  d_setfield(r,"depth",  d or 0)
-  return r
-end
+local function hangul_space_skip (curr, newfont)
+  if curr.lang ~= nohyphen then
+    local n = getnext(curr)
+    if n and n.id == glueid and n.subtype == spaceskip then
+      local params = getparameters(curr.font)
+      local oldwd, oldst, oldsh, oldsto, oldsho = getglue(n)
+      if oldwd == params.space
+        and oldst == params.space_stretch
+        and oldsh == params.space_shrink
+        and oldsto == 0
+        and oldsho == 0 then -- not user's spaceskip
 
-local function d_make_luako_glue(...)
-  local glue = d_get_gluenode(...)
-  d_set_attribute(glue,luakoglueattr,1)
-  return glue
+        local newwd = char_font_options.hangulspaceskip[newfont]
+        if newwd == nil then
+          local newsp = nodecopy(curr)
+          newsp.char, newsp.font = 32, newfont
+          newsp = nodes.simple_font_handler(newsp)
+          newwd = newsp and newsp.width or false
+          if newwd then
+            newwd = { texround(newwd), texround(newwd/2), texround(newwd/3) }
+          end
+          char_font_options.hangulspaceskip[newfont] = newwd
+          if newsp then
+            nodefree(newsp)
+          end
+        end
+        if newwd then
+          setglue(n, newwd[1], newwd[2], newwd[3])
+        end
+      end
+    end
+  end
 end
 
-local function is_cjk_k (c)
-  return (c >= 0x2E80  and c <= 0x9FFF )
-  or (c >= 0xF900  and c <= 0xFAFF )
-  or (c >= 0xFE10  and c <= 0xFE1F )
-  or (c >= 0xFE30  and c <= 0xFE4F )
-  or (c >= 0xFF00  and c <= 0xFFEF )
-  or (c >= 0x20000 and c <= 0x2FFFD)
-  or  c == 0x00B0  or  c == 0x2015
-  --  or  c == 0x2018  or  c == 0x2019
-  --  or  c == 0x201C  or  c == 0x201D
-  or  c == 0x2026  or  c == 0x2032
-  or  c == 0x2033
-end
+local function process_fonts (head)
+  local curr = head
+  while curr do
+    local id = curr.id
+    if id == glyphid then
+      local props = my_node_props(curr)
+      if not props.unicode then
 
-local function is_hanja (c)
-  return (c >= 0x3400 and c <= 0x9FFF )
-  or (c >= 0xF900 and c <= 0xFAFF )
-  or (c >= 0x20000 and c <= 0x2FFFD)
-end
+        local c = curr.char
 
-local function is_hangul (c)
-  return (c >= 0xAC00 and c <= 0xD7A3)
-end
+        if is_unicode_vs(c) then
+          local p = getprev(curr)
+          if p.id == glyphid and curr.font ~= p.font then
+            hangul_space_skip(curr, p.font)
+            curr.font = p.font
+          end
 
-local function is_chosong (c)
-  return (c >= 0x1100 and c <= 0x115F)
-  or (c >= 0xA960 and c <= 0xA97C)
-end
+        else
+          local hf  = has_attribute(curr, hangulfontattr) or false
+          local hjf = has_attribute(curr, hanjafontattr)  or false
+          local fontdata  = get_font_data(curr.font)
+          local format    = fontdata.format
+          local no_legacy = format == "opentype" or format == "truetype"
+          if hf and no_legacy and force_hangul[c] and curr.lang ~= nohyphen then
+            curr.font = hf
+          elseif hf and luatexko.hangulbyhangulfont and is_hangul_jamo(c) then
+            hangul_space_skip(curr, hf)
+            curr.font = hf
+          elseif hjf and luatexko.hanjabyhanjafont and is_hanja(c) then
+            hangul_space_skip(curr, hjf)
+            curr.font = hjf
+          elseif not char_in_font(fontdata, c) then
+            local fbf = has_attribute(curr, fallbackfontattr) or false
+            for _,f in ipairs{ hf, hjf, fbf } do
+              if f and char_in_font(f, c) then
+                hangul_space_skip(curr, f)
+                curr.font = f
+                break
+              end
+            end
+          end
+        end
 
-local function is_jungjongsong (c)
-  return (c >= 0x1160 and c <= 0x11FF)
-  or (c >= 0xD7B0 and c <= 0xD7Fb)
-  or c == 0x302E  or  c == 0x302F -- tone marks
-end
+        if not active_processes.reorderTM
+          and hangul_tonemark[c]
+          and option_in_font(curr.font, "script") == "hang" then
+          luatexko.activate("reorderTM") -- activate reorderTM here
+          active_processes.reorderTM = true
+        end
 
-local function is_unicode_vs (c)
-  return (c >= 0xFE00  and c <= 0xFE0F )
-      or (c >= 0xE0100 and c <= 0xE01EF)
-end
+        props.unicode = c
+      end
+    elseif id == mathid then
+      curr = end_of_math(curr)
+    elseif id == whatsitid
+      and curr.subtype == user_whatsit
+      and curr.user_id == forcehf_id
+      and curr.type == lua_value then
 
-local function get_cjk_class (ch, cjtype)
-  if ch then
-    if is_hangul(ch) then return 7 end        -- hangul = 7
-    if is_chosong(ch) then return 8 end       -- jamo LC = 8
-    if is_jungjongsong(ch) then return 9 end  -- jamo VL, TC, TM = 9
-    local c = is_cjk_k(ch) and 0 or 10        -- hanja = 0; latin = 10
-    if cjkclass[ch] then c = cjkclass[ch] end -- cjkclass 1 .. 6
-    if cjtype then
-      if cjtype == 2 and
-        (ch == 0xFF1F or ch == 0xFF01 or ch == 0xFF1A or ch == 0xFF1B) then
-        c = 4 -- simplified chinese ? !
+      local value = curr.value
+      if type(value) == "function" then
+        value()
       end
-    else
-      if ch == 0x2018 or ch == 0x2019 or ch == 0x201C or ch == 0x201D then
-        c = 10 -- korean “ ” ‘ ’
-      end
     end
-    return c
+    curr = getnext(curr)
   end
+  return head
 end
 
-local type1fonts = {}
+-- linebreak
 
-local function get_font_table (fid)
-  if fid then
-    if fontdata[fid] then
-      return fontdata[fid]
-    elseif type1fonts[fid] then
-      return type1fonts[fid]
-    else
-      return font.fonts[fid]
-    end
+local intercharclass = { [0] =
+  { [0] = nil,    {1,1},  nil,    {.5,.5} },
+  { [0] = nil,    nil,    nil,    {.5,.5} },
+  { [0] = {1,1},  {1,1},  nil,    {.5,.5}, nil,    {1,1},  {1,1} },
+  { [0] = {.5,.5},{.5,.5},{.5,.5},{1,.5},  {.5,.5},{.5,.5},{.5,.5} },
+  { [0] = {1,0},  {1,0},  nil,    {1.5,.5},nil,    {1,0},  {1,0} },
+  { [0] = nil,    {1,1},  nil,    {.5,.5} },
+  { [0] = {1,1},  {1,1},  nil,    {.5,.5} },
+}
+
+local charclass = setmetatable({
+  [0x2018] = 1, [0x201C] = 1, [0x2329] = 1, [0x3008] = 1,
+  [0x300A] = 1, [0x300C] = 1, [0x300E] = 1, [0x3010] = 1,
+  [0x3014] = 1, [0x3016] = 1, [0x3018] = 1, [0x301A] = 1,
+  [0x301D] = 1, [0xFE17] = 1, [0xFE35] = 1, [0xFE37] = 1,
+  [0xFE39] = 1, [0xFE3B] = 1, [0xFE3D] = 1, [0xFE3F] = 1,
+  [0xFE41] = 1, [0xFE43] = 1, [0xFE47] = 1, [0xFF08] = 1,
+  [0xFF3B] = 1, [0xFF5B] = 1, [0xFF5F] = 1, [0xFF62] = 1,
+  --
+  [0x2019] = 2, [0x201D] = 2, [0x232A] = 2, [0x3001] = 2,
+  [0x3009] = 2, [0x300B] = 2, [0x300D] = 2, [0x300F] = 2,
+  [0x3011] = 2, [0x3015] = 2, [0x3017] = 2, [0x3019] = 2,
+  [0x301B] = 2, [0x301E] = 2, [0x301F] = 2, [0xFE10] = 2,
+  [0xFE11] = 2, [0xFE18] = 2, [0xFE36] = 2, [0xFE38] = 2,
+  [0xFE3A] = 2, [0xFE3C] = 2, [0xFE3E] = 2, [0xFE40] = 2,
+  [0xFE42] = 2, [0xFE44] = 2, [0xFE48] = 2, [0xFF09] = 2,
+  [0xFF0C] = 2, [0xFF3D] = 2, [0xFF5D] = 2, [0xFF60] = 2,
+  [0xFF63] = 2, [0xFF64] = 2,
+  --
+  [0x00B7] = 3, [0x30FB] = 3, [0xFF1A] = 3, [0xFF1B] = 3,
+  [0xFF65] = 3,
+  --
+  [0x3002] = 4, [0xFE12] = 4, [0xFF0E] = 4, [0xFF61] = 4,
+  --
+  [0x2015] = 5, [0x2025] = 5, [0x2026] = 5, [0xFE19] = 5,
+  [0xFE30] = 5, [0xFE31] = 5,
+  --
+  [0xFE15] = 6, [0xFE16] = 6, [0xFF01] = 6, [0xFF1F] = 6,
+}, { __index = function() return 0 end })
+
+local SC_charclass = setmetatable({
+  [0xFF01] = 4, [0xFF1A] = 4, [0xFF1B] = 4, [0xFF1F] = 4,
+}, { __index = function(_,c) return charclass[c] end })
+
+local vert_charclass = setmetatable({
+  [0xFF1A] = 5, -- 0xFE13
+  [0xFF1B] = 5, -- 0xFE14
+}, { __index = function(_,c) return charclass[c] end })
+
+local function get_char_class (c, classic)
+  if classic == vert_classic then
+    return vert_charclass[c]
+  elseif classic == SC_classic then
+    return SC_charclass[c]
   end
+  return charclass[c]
 end
 
-local function get_font_char (fid, chr)
-  local f = get_font_table(fid)
-  return chr and f and f.characters and f.characters[chr]
-end
+local breakable_after = setmetatable({
+  [0x21] = true,   [0x22] = true,   [0x25] = true,   [0x27] = true,
+  [0x29] = true,   [0x2C] = true,   [0x2D] = true,   [0x2E] = true,
+  [0x3A] = true,   [0x3B] = true,   [0x3E] = true,   [0x3F] = true,
+  [0x5D] = true,   [0x7D] = true,   [0x7E] = true,   [0xBB] = true,
+  [0x2013] = true, [0x2014] = true, [0x25A1] = true, [0x25CB] = true,
+  [0x2E80] = true, [0x3003] = true, [0x3005] = true, [0x3007] = true,
+  [0x301C] = true, [0x3035] = true, [0x303B] = true, [0x303C] = true,
+  [0x309D] = true, [0x309E] = true, [0x30A0] = true, [0x30FC] = true,
+  [0x30FD] = true, [0x30FE] = true, [0xFE13] = true, [0xFE14] = true,
+  [0xFE32] = true, [0xFE50] = true, [0xFE51] = true, [0xFE52] = true,
+  [0xFE54] = true, [0xFE55] = true, [0xFE57] = true, [0xFE57] = true,
+  [0xFE58] = true, [0xFE5A] = true, [0xFE5C] = true, [0xFE5E] = true,
+  [0xFF1E] = true, [0xFF5E] = true, [0xFF70] = true,
+},{ __index = function (_,c)
+  return (is_hangul_jamo(c) and not is_chosong(c))
+  or is_noncjkletter(c)
+  or is_hanja(c)
+  or is_cjk_combining(c)
+  or is_kana(c)
+  or charclass[c] >= 2
+end })
+luatexko.breakableafter = breakable_after
 
-local function get_font_emsize(fid)
-  local f = get_font_table(fid)
-  return (f and f.parameters and f.parameters.quad) or emsize
+local breakable_before = setmetatable({
+  [0x28] = true,   [0x3C] = true,   [0x5B] = true,   [0x60] = true,
+  [0x7B] = true,   [0xAB] = true,   [0x25A1] = true, [0x25CB] = true,
+  [0x3007] = true, [0xFE59] = true, [0xFE5B] = true, [0xFE5D] = true,
+  [0xFF1C] = true,
+  -- small kana
+  [0x3041] = 1000, [0x3043] = 1000, [0x3045] = 1000, [0x3047] = 1000,
+  [0x3049] = 1000, [0x3063] = 1000, [0x3083] = 1000, [0x3085] = 1000,
+  [0x3087] = 1000, [0x308E] = 1000, [0x3095] = 1000, [0x3096] = 1000,
+  [0x30A1] = 1000, [0x30A3] = 1000, [0x30A5] = 1000, [0x30A7] = 1000,
+  [0x30A9] = 1000, [0x30C3] = 1000, [0x30E3] = 1000, [0x30E5] = 1000,
+  [0x30E7] = 1000, [0x30EE] = 1000, [0x30F5] = 1000, [0x30F6] = 1000,
+  [0x31F0] = 1000, [0x31F1] = 1000, [0x31F2] = 1000, [0x31F3] = 1000,
+  [0x31F4] = 1000, [0x31F5] = 1000, [0x31F6] = 1000, [0x31F7] = 1000,
+  [0x31F8] = 1000, [0x31F9] = 1000, [0x31FA] = 1000, [0x31FB] = 1000,
+  [0x31FC] = 1000, [0x31FD] = 1000, [0x31FE] = 1000, [0x31FF] = 1000,
+  [0xFF67] = 1000, [0xFF68] = 1000, [0xFF69] = 1000, [0xFF6A] = 1000,
+  [0xFF6B] = 1000, [0xFF6C] = 1000, [0xFF6D] = 1000, [0xFF6E] = 1000,
+  [0xFF6F] = 1000,  [0x1B150] = 1000, [0x1B151] = 1000, [0x1B152] = 1000,
+  [0x1B164] = 1000, [0x1B165] = 1000, [0x1B166] = 1000, [0x1B167] = 1000,
+},{ __index = function(_,c)
+  return (is_hangul_jamo(c) and not is_jungsong(c) and not is_jongsong(c))
+  or is_hanja(c)
+  or is_kana(c)
+  or charclass[c] == 1
 end
+})
+luatexko.breakablebefore = breakable_before
 
-local function get_font_feature (fid, name)
-  local f = get_font_table(fid)
-  return f and f.shared and f.shared.features and f.shared.features[name]
+local allowbreak_false_nodes = {
+  [hlistid]   = true,
+  [vlistid]   = true,
+  [ruleid]    = true,
+  [discid]    = true,
+  [glueid]    = true,
+  [penaltyid] = true,
+}
+
+local function is_blocking_node (curr)
+  local id, subtype = curr.id, curr.subtype
+  return allowbreak_false_nodes[id] or (id == kernid and subtype == userkern)
 end
 
-local function get_char_boundingbox(fid, chr)
-  local f = get_font_table(fid)
-  local glbox = f and f.shared
-  glbox = glbox and glbox.rawdata
-  glbox = glbox and glbox.descriptions
-  glbox = glbox and glbox[chr] and glbox[chr].boundingbox
-  if glbox then
-    local factor, bbox = f.parameters and f.parameters.factor or 655.36, {}
-    for i,v in ipairs(glbox) do
-      bbox[i] = v * factor
+local function ruby_char_font (rb)
+  local n, c, f = has_glyph(rb.list), 0, 0
+  if n then
+    c, f = my_node_props(n).unicode or n.char, n.font or 0
+    if is_chosong(c) or hangul_tonemark[c] then
+      c = 0xAC00
     end
-    return bbox
   end
+  return c, f
 end
 
-local function d_get_unicode_char(curr)
-  local uni = d_getchar(curr) or 0
-  if (uni > 0xFF and uni < 0xE000) or (uni > 0xF8FF and uni < 0xF0000) then
-    return uni -- no pua. no nanumgtm
+local function get_actualtext (curr)
+  local actual = my_node_props(curr).startactualtext
+  if type(actual) == "table" then
+     return actual[0], actual[1], actual[#actual]
   end
-  if uni < 0xE000 or (uni > 0xF8FF and uni < 0xF0000) then -- no pua
-    local uchr = get_font_char(d_getfont(curr), uni)
-    uchr = uchr and uchr.tounicode
-    uchr = uchr and uchr:sub(1,4) -- seems ok for old hangul
-    if uchr then return tonumber(uchr,16) end
-  end
-  local uatt = d_has_attribute(curr, luakounicodeattr)
-  if uatt then return uatt end
-  return uni
 end
 
-local function d_get_hlist_char_first (hlist)
-  local curr = d_getlist(hlist)
-  while curr do
-    local currid = d_getid(curr)
-    if currid == glyphnode then
-      local c,f = d_get_unicode_char(curr), d_getfont(curr)
-      if c then return c,f end
-    elseif currid == hlistnode or currid == vlistnode then
-      local c,f = d_get_hlist_char_first(curr)
-      if c then return c,f end
-    elseif currid == gluenode then
-      if not d_node_zero_glue(curr) then return end
+local function goto_end_actualtext (curr)
+  local n = getnext(curr)
+  while n do
+    if n.id == whatsitid
+      and n.mode == directmode
+      and my_node_props(n).endactualtext then
+      curr = n
+      break
     end
-    curr = d_getnext(curr)
+    n = getnext(n)
   end
+  return curr
 end
 
-local function d_get_hlist_char_last (hlist,c,f)
-  local curr = d_nodetail(d_getlist(hlist))
-  while curr do
-    local currid = d_getid(curr)
-    if currid == glyphnode then
-      c, f = d_get_unicode_char(curr), d_getfont(curr)
-      if c and not is_unicode_vs(c) then break end -- bypass VS
-    elseif currid == hlistnode or currid == vlistnode then
-      c, f = d_get_hlist_char_last(curr)
-      if c then break end
-    elseif currid == gluenode then
-      if not d_node_zero_glue(curr) then break end
+local function fontdata_opt_dim (fd, optname)
+  local dim = option_in_font(fd, optname)
+  if dim then
+    local m, u = dim:match"(.+)(e[mx])%s*$"
+    if m and u then
+      if u == "em" then
+        dim = m * fd.parameters.quad
+      else
+        dim = m * fd.parameters.x_height
+      end
+    else
+      dim = texsp(dim)
     end
-    curr = d_getprev(curr)
+    return dim
   end
-  return c, f
 end
 
-----------------------------
--- cjk linebreak and spacing
-----------------------------
-local function kanjiskip (head,curr)
-  d_insert_before(head,curr,d_make_luako_glue(0, emsize*0.1, emsize*0.02))
+local function get_font_opt_dimen (fontid, option_name)
+  local fop_t = char_font_options[option_name]
+  local dim = fop_t and fop_t[fontid]
+  if dim == nil then
+    local fd = get_font_data(fontid)
+    dim = fontdata_opt_dim(fd, option_name)
+    if dim then
+      fop_t[fontid] = dim -- cache
+    else
+      fop_t[fontid] = false
+    end
+  end
+  return dim
 end
 
-local function xkanjiskip (head,curr)
-  if d_has_attribute(curr,finemathattr) == 0 then -- ttfamily
-    kanjiskip(head,curr)
+local function insert_glue_before (head, curr, par, br, brb, classic, ict, dim)
+  local pn = nodenew(penaltyid)
+  if not br then
+    pn.penalty = 10000
+  elseif type(brb) == "number" then
+    pn.penalty = brb
+  elseif par and nodecount(glyphid, curr) <= 2 then
+    pn.penalty = 1000 -- supress orphan
   else
-    d_insert_before(head,curr,d_make_luako_glue(0.25*emsize, emsize*0.15, emsize*0.06))
+    pn.penalty = 50
   end
-end
 
-local function interhangulskip (head,curr,currfont,prevfont,was_penalty)
-  local width = 0
-  local interhangul = get_font_feature(currfont, "interhangul")
-  if interhangul and currfont == prevfont then
-    width = tex_sp(interhangul)
+  dim = dim or 0
+  local gl = nodenew(glueid)
+  local en = get_en_size(curr.font)
+  if ict then
+    en = classic and en or en/4
+    setglue(gl, en * ict[1] + dim, nil, en * ict[2])
+  else
+    setglue(gl, dim, en * stretch_f, en * shrink_f)
   end
-  if not was_penalty then
-    d_insert_before(head,curr,d_get_penaltynode(50))
-  end
-  d_insert_before(head,curr,d_make_luako_glue(width, emsize*0.04, emsize*0.02))
-end
 
-local function interhanjaskip (head,curr,was_penalty)
-  if not was_penalty then
-    d_insert_before(head,curr,d_get_penaltynode(50))
-  end
-  d_insert_before(head,curr,d_make_luako_glue(0, emsize*0.04, emsize*0.02))
+  head = insert_before(head, curr, pn)
+  return insert_before(head, curr, gl)
 end
 
-local function koreanlatinskip (head,curr,currfont,prevfont,was_penalty)
-  local width = 0 -- default: 0em
-  if (d_has_attribute(curr,finemathattr) or 0) > 0 then -- not ttfamily
-    local latincjk = get_font_feature(currfont, "interlatincjk")
-    if not latincjk then
-      latincjk = get_font_feature(prevfont, "interlatincjk")
+local function maybe_linebreak (head, curr, pc, pcl, cc, gomun, fid, par)
+  local ccl = get_char_class(cc, gomun)
+  if pc and cc and curr.lang ~= nohyphen then
+    local ict = intercharclass[pcl][ccl]
+    local brb = breakable_before[cc]
+    local br  = brb and breakable_after[pc]
+    local dim = get_font_opt_dimen(fid, "intercharacter")
+    if ict or br or dim and (pcl >= 1 or ccl >= 1) then
+      head = insert_glue_before(head, curr, par, br, brb, gomun, ict, dim)
     end
-    if latincjk then
-      width = tex_sp(latincjk)
-    end
   end
-  if not was_penalty then
-    d_insert_before(head,curr,d_get_penaltynode(50))
-  end
-  d_insert_before(head,curr,d_make_luako_glue(width, emsize*0.04, emsize*0.02))
+  return head, cc, ccl
 end
 
-local function cjk_insert_nodes(head,curr,currchar,currfont,prevchar,prevfont,was_penalty)
-  local currentcjtype = d_has_attribute(curr,cjtypesetattr)
-  local p = get_cjk_class(prevchar, currentcjtype)
-  local c = get_cjk_class(currchar, currentcjtype)
-  ---[[raise latin puncts
-  if d_getid(curr) == glyphnode and (d_has_attribute(curr,finemathattr) or 0) > 0 and c < 10 then -- not ttfamily
-    local nn, raise = d_getnext(curr), nil
-    while nn do
-      local nnid = d_getid(nn)
-      if nnid == glyphnode and latin_fullstop[d_getchar(nn)] then
-        if not raise then
-          raise = get_font_feature(currfont, "punctraise")
-          raise = raise and tex_sp(raise)
-        end
-        if raise then
-          local yoff = d_getfield(nn,"yoffset") or 0
-          d_setfield(nn,"yoffset", yoff + raise)
-        end
-        nn = d_getnext(nn)
-      elseif nnid == kernnode then
-        nn = d_getnext(nn)
-      else
-        break
+local function process_linebreak (head, par)
+  local curr, pc, pcl = head, false, 0
+  while curr do
+    local id = curr.id
+    if id == glyphid then
+      local cc = my_node_props(curr).unicode or curr.char
+      local gomun = has_attribute(curr, classicattr)
+
+      head, pc, pcl = maybe_linebreak(head, curr, pc, pcl, cc, gomun, curr.font, par)
+
+    elseif id == hlistid and has_attribute(curr, rubyattr) then
+      local cc, fi = ruby_char_font(curr) -- rubybase
+      local gomun = has_attribute(curr, classicattr)
+      head, pc, pcl = maybe_linebreak(head, curr, pc, pcl, cc, gomun, fi, par)
+
+    elseif id == whatsitid and curr.mode == directmode then
+      local glyf, cc, fin = get_actualtext(curr)
+      if cc and fin and glyf then
+        local gomun = has_attribute(glyf, classicattr)
+        head = maybe_linebreak(head, curr, pc, pcl, cc, gomun, glyf.font, par)
+        pc, pcl, curr = fin, 0, goto_end_actualtext(curr)
       end
+
+    elseif id == mathid then
+      pc, pcl, curr = 0x30, 0, end_of_math(curr)
+    elseif id == dirid then
+      pc, pcl = curr.dir:sub(1,1) == "-" and 0x30, 0 -- pop dir
+    elseif is_blocking_node(curr) then
+      pc, pcl = false, 0
     end
+    curr = getnext(curr)
   end
-  --raise latin puncts]]
-  if c == 5 and p == 5 then -- skip ------ ......
-    return currchar,currfont
-  end
-  if p and p < 10 and prebreakpenalty[currchar] then
-    was_penalty = true
-    d_insert_before(head,curr,d_get_penaltynode(prebreakpenalty[currchar]))
-  elseif c and c < 10 and postbreakpenalty[prevchar] then
-    was_penalty = true
-    d_insert_before(head,curr,d_get_penaltynode(postbreakpenalty[prevchar]))
-  end
-  ---[[ kern is a breakpoint if followed by a glue: protrusion and compress_fullwidth_punctuations
-  if (c and c == 1) or (p and p >= 2 and p <= 6) then was_penalty = true end
-  --]]
-  if p and c then
-    if currentcjtype then
-      if cjk_glue_spec[p] and cjk_glue_spec[p][c] then
-        local width   = emsize * cjk_glue_spec[p][c][1]
-        local stretch = 0
-        local shrink  = emsize * cjk_glue_spec[p][c][2]
-        d_insert_before(head,curr,d_make_luako_glue(width, stretch, shrink))
-      elseif p < 10 and c < 9 then -- break between chosong and chosong
-        kanjiskip(head,curr)
-      elseif (p < 10 and c == 10) or (p == 10 and c < 10) then
-        if xspcode[currchar] then
-          if xspcode[currchar] % 2 == 1 then
-            xkanjiskip(head,curr)
-          end
-        elseif xspcode[prevchar] then
-          if xspcode[prevchar] > 1 then
-            xkanjiskip(head,curr)
-          end
-        elseif inhibitxspcode[currchar] then -- 3, 2
-          if inhibitxspcode[currchar] > 1 then
-            xkanjiskip(head,curr)
-          end
-        elseif inhibitxspcode[prevchar] then -- 3, 1
-          if inhibitxspcode[prevchar] % 2 == 1 then
-            xkanjiskip(head,curr)
-          end
-        else
-          xkanjiskip(head,curr)
-        end
-      end
-    else
-      if (p < 10 and c == 10) or (p == 10 and c < 10) then
-        if xspcode[currchar] then
-          if xspcode[currchar] % 2 == 1 then
-            koreanlatinskip(head,curr,currfont,prevfont,was_penalty)
-          end
-        elseif xspcode[prevchar] then
-          if xspcode[prevchar] > 1 then
-            koreanlatinskip(head,curr,currfont,prevfont,was_penalty)
-          end
-        elseif inhibitxspcode[currchar] then -- 3, 2
-          if inhibitxspcode[currchar] > 1 then
-            koreanlatinskip(head,curr,currfont,prevfont,was_penalty)
-          end
-        elseif inhibitxspcode[prevchar] then -- 3, 1
-          if inhibitxspcode[prevchar] % 2 == 1 then
-            koreanlatinskip(head,curr,currfont,prevfont,was_penalty)
-          end
-        else
-          koreanlatinskip(head,curr,currfont,prevfont,was_penalty)
-        end
-      elseif cjk_glue_spec[p] and cjk_glue_spec[p][c] then
-        koreanlatinskip(head,curr,currfont,prevfont,was_penalty)
-      elseif p == 7 and c == 7 then
-        interhangulskip(head,curr,currfont,prevfont,was_penalty)
-      elseif p < 10 and c < 9 then -- break between chosong and chosong
-        interhanjaskip(head,curr,was_penalty)
-      end
-    end
-  end
-  return currchar,currfont
+  return head
 end
 
-local function cjk_spacing_linebreak (head)
-  local prevchar,prevfont,was_penalty,prevfine = nil,nil,nil,nil
+-- compress punctuations
+
+local function process_glyph_width (head)
   local curr = head
   while curr do
-    local currfine = d_has_attribute(curr,finemathattr)
-    if currfine then
-      local currid = d_getid(curr)
-      if currid == gluenode then
-        prevchar,prevfont,prevfine = nil,nil,nil
-        d_unset_attribute(curr,finemathattr)
-      elseif currid == glyphnode then
-        local currfont = d_getfont(curr)
-        emsize = get_font_emsize(currfont)
-        local uni = d_get_unicode_char(curr)
-        if uni and not is_unicode_vs(uni) then -- bypass VS
-          if prevfine and prevfine > 0 and currfine == 0 then
-            d_set_attribute(curr, finemathattr, 1)
+    local id = curr.id
+    if id == glyphid then
+      if curr.lang ~= nohyphen
+        and option_in_font(curr.font, "compresspunctuations") then
+
+        local cc = my_node_props(curr).unicode or curr.char
+        local gomun = has_attribute(curr, classicattr)
+        local class = get_char_class(cc, gomun)
+        if (gomun or cc < 0x2000 or cc > 0x202F) -- exclude general puncts
+          and class >= 1 and class <= 4 then
+
+          local gpos
+          if class ~= 1 then
+            gpos = getnext(curr)
+          else
+            gpos = getprev(curr)
           end
-          prevchar,prevfont = cjk_insert_nodes(head,curr,uni,currfont,prevchar,prevfont,was_penalty)
-          prevfine = currfine
-        end
-        d_unset_attribute(curr,finemathattr)
-      elseif currid == mathnode then
-        local currchar = 0
-        local currsurround, currsubtype = d_getfield(curr,"surround"), d_getsubtype(curr)
-        if currsurround and currsurround > 0 then
-          currchar = 0x4E00
-        end
-        if currsubtype == 0 then
-          cjk_insert_nodes(head,curr,currchar,nil,prevchar,prevfont,was_penalty)
-          curr = d_end_of_math(curr)
-          if not curr then break end
-          prevchar,prevfont,prevfine = currchar,nil,nil
-        end
-        d_unset_attribute(curr,finemathattr)
-      elseif currid == hlistnode or currid == vlistnode then
-        local firstchr, firstfid = d_get_hlist_char_first(curr)
-        if firstchr then
-          cjk_insert_nodes(head,curr,firstchr,firstfid,prevchar,prevfont,was_penalty)
-        end
-        prevchar,prevfont = d_get_hlist_char_last(curr,prevchar,prevfont)
-        if d_getlist(curr) then prevfine = nil end -- empty box preserves prevfine.
-        d_unset_attribute(curr,finemathattr)
-      end
-      was_penalty = currid == penaltynode
-    else
-      if d_getid(curr) == glyphnode and prevfine and prevfine > 0 then -- fitst glyph in verb
-        d_set_attribute(curr, finemathattr, 1)
-        cjk_insert_nodes(head,curr,0,nil,prevchar,prevfont,was_penalty)
-        d_unset_attribute(curr,finemathattr)
-        prevchar,prevfont,prevfine = 0,nil,nil -- treat \verb as latin character.
-      end
-    end
-    curr = d_getnext(curr)
-  end
-end
+          gpos = gpos and gpos.id == kernid and gpos.subtype == fontkern
 
-------------------------------------
--- remove japanese/chinese spaceskip
-------------------------------------
-local function remove_cj_spaceskip (head)
-  local curr, prevfont = head, nil
-  while curr do
-    local currid,currsubtype = d_getid(curr), d_getsubtype(curr)
-    if currid == mathnode and currsubtype == 0 then
-      curr = d_end_of_math(curr)
-    elseif currid == gluenode and currsubtype == glue_type_space then -- do not touch on xspaceskip for now
-      local cjattr = d_has_attribute(curr,cjtypesetattr)
-      local prv, nxt = d_getprev(curr), d_getnext(curr)
-      if cjattr and cjattr > 0 and prv and nxt then
-        local prevclass, prevchar, nextclass
-        local prvid, nxtid = d_getid(prv), d_getid(nxt)
-        if prvid == hlistnode or prvid == vlistnode then
-          prevclass = get_cjk_class(d_get_hlist_char_last(prv), cjattr)
-        else
-          -- what is this strange kern before \text??
-          if prvid == kernnode and d_getfield(prv,"kern") == 0 then
-            prv = d_getprev(prv)
+          if not gpos then
+            local wd = get_en_size(curr.font) - curr.width
+            if wd ~= 0 then
+              local k = nodenew(kernid) -- fontkern (subtype 0) is default
+              k.kern = class == 3 and wd/2 or wd
+              if class == 1 then
+                head = insert_before(head, curr, k)
+              elseif class == 2 or class == 4 then
+                head, curr = insert_after(head, curr, k)
+              else
+                local k2 = nodecopy(k)
+                head = insert_before(head, curr, k)
+                head, curr = insert_after(head, curr, k2)
+              end
+            end
           end
-          if prvid == glyphnode then
-            prevchar, prevfont = d_getchar(prv), d_getfont(prv)
-            if is_unicode_vs(prevchar) then prv = d_getprev(prv) end -- bypass VS
-            prevclass = get_cjk_class(d_get_unicode_char(prv), cjattr)
-          end
         end
-        if nxtid == glyphnode then
-          nextclass = get_cjk_class(d_get_unicode_char(nxt), cjattr)
-        elseif nxtid == hlistnode or nxtid == vlistnode then
-          nextclass = get_cjk_class(d_get_hlist_char_first(nxt), cjattr)
-        end
-        if (prevclass and prevclass < 10) or (nextclass and nextclass < 10) then
-          d_remove_node(head,curr)
-          d_nodefree(curr)
-          curr = nxt
-        end
       end
+    elseif id == mathid then
+      curr = end_of_math(curr)
     end
-    curr = d_getnext(curr)
+    curr = getnext(curr)
   end
+  return head
 end
 
-----------------------------------
--- compress fullwidth punctuations
-----------------------------------
-local function check_next_halt_kern (head, curr)
-  -- glyph halt_kern glue -> glyph halt_kern rule glue
-  local nn = d_getnext(curr)
-  if nn and d_getid(nn) == kernnode and d_getnext(nn) then
-    d_insert_after(head, nn, d_get_rulenode(0))
-  end
+-- interhangul & interlatincjk
+
+local function is_cjk (c)
+  return is_hangul_jamo(c)
+  or     is_hanja(c)
+  or     is_cjk_combining(c)
+  or     is_kana(c)
+  or     charclass[c] >= 1
+  or     (rawget(breakable_before, c) and c >= 0x2000)
+  or     (rawget(breakable_after,  c) and c >= 0x2000)
 end
 
-local function check_prev_halt_kern (head, curr)
-  local pn = d_getprev(curr)
-  if pn and d_getid(pn) == kernnode and d_getprev(pn) then
-    -- glue halt_kern glyph -> glue rule halt_kern glyph
-    head = d_insert_before(head, pn, d_get_rulenode(0))
-  elseif pn and d_getid(pn) == gluenode and d_has_attribute(pn, luakoglueattr) then
-    -- halt_kern ko_glue glyph -> ko_glue rule halt_kern glyph
-    local ppn = d_getprev(pn)
-    if d_getid(ppn) == kernnode then
-      head = d_remove_node(head, ppn)
-      head = d_insert_before(head, curr, ppn)
-      head = d_insert_before(head, ppn, d_get_rulenode(0))
+local function do_interhangul_option (head, curr, pc, c, fontid, par)
+  local cc = (is_hangul(c) or is_chosong(c)) and 1 or 0
+
+  if cc*pc == 1 and curr.lang ~= nohyphen then
+    local dim = get_font_opt_dimen(fontid, "interhangul")
+    if dim then
+      head = insert_glue_before(head, curr, par, true, true, false, false, dim)
     end
   end
-  return head
+
+  return head, cc
 end
 
-local function compress_fullwidth_punctuations (head)
-  for curr in d_traverse_id(glyphnode,head) do
-    local currfont, currchar = d_getfont(curr), d_getchar(curr)
-    local cjtype = d_has_attribute(curr, cjtypesetattr)
-    local uni = d_get_unicode_char(curr)
-    local class = uni and get_cjk_class(uni, cjtype or 0) -- include U+201C ...
-    local chr = get_font_char(currfont, currchar)
-    if chr and class and class >= 1 and class <= 4 then
-      local halt_on = get_font_feature(currfont,'halt') or get_font_feature(currfont,'vhal')
-      local width = d_getfield(curr,"width") or 655360
-      emsize = get_font_emsize(currfont)
-      local ensize = emsize/2
-      local oneoften = emsize/10
-      local bbox = get_char_boundingbox(currfont, currchar)
-      bbox = bbox or {ensize-oneoften, ensize-oneoften, ensize+oneoften, ensize+oneoften}
-      if class == 2 or class == 4 then
-        local wd
-        if get_font_feature(currfont,'vertical') then
-          wd = ensize<width and ensize-width or 0
-        else
-          wd = (bbox[3] < ensize) and ensize-width or bbox[3]-width+oneoften
-          if not cjtype and wd > 0 then wd = 0 end
-        end
-        if chr.right_protruding then
-          -- kern is a breakpoint if followed by a glue
-          if not halt_on then
-            d_insert_after(head, curr, d_get_kernnode(wd))
-          end
-        else
-          if halt_on then
-            check_next_halt_kern(head, curr)
-          else
-            d_insert_after(head, curr, d_get_rulenode(wd))
-          end
-        end
-      elseif class == 1 then
-        local wd
-        if get_font_feature(currfont,'vertical') then
-          wd = ensize<width and ensize-width or 0
-        else
-          wd = (width-bbox[1] < ensize) and ensize-width or -bbox[1]+oneoften
-          if not cjtype and wd > 0 then wd = 0 end
-        end
-        if chr.left_protruding then
-          if not halt_on then
-            head = d_insert_before(head, curr, d_get_kernnode(wd))
-          end
-        else
-          if halt_on then
-            head = check_prev_halt_kern(head, curr)
-          else
-            head = d_insert_before(head, curr, d_get_rulenode(wd))
-          end
-        end
-      elseif class == 3 then
-        local lwd, rwd
-        local quarter, thirdquarter, halfwd = ensize/2, ensize*1.5, width/2
-        if get_font_feature(currfont,'vertical') then
-          rwd = quarter<halfwd and quarter-halfwd or 0
-          lwd = rwd
-        else
-          rwd = (bbox[3] < thirdquarter) and quarter-halfwd or bbox[3]-width
-          lwd = (width-bbox[1] < thirdquarter) and quarter-halfwd or -bbox[1]
-        end
-        if chr.left_protruding then
-          if not halt_on then
-            head = d_insert_before(head, curr, d_get_kernnode(lwd))
-          end
-        else
-          if halt_on then -- there's already penalty 10000
-            head = check_prev_halt_kern(head, curr)
-          else
-            head = d_insert_before(head, curr, d_get_rulenode(lwd))
-          end
-        end
-        if chr.right_protruding then
-          if not halt_on then
-            d_insert_after (head, curr, d_get_kernnode(rwd))
-          end
-        else
-          if halt_on then
-            check_next_halt_kern(head, curr)
-          else
-            d_insert_after (head, curr, d_get_rulenode(rwd))
-          end
-        end
+local function process_interhangul (head, par)
+  local curr, pc = head, 0
+  while curr do
+    local id = curr.id
+    if id == glyphid then
+      local c = my_node_props(curr).unicode or curr.char
+      head, pc = do_interhangul_option(head, curr, pc, c, curr.font, par)
+
+      if is_chosong(c) then
+        pc = 0
+      elseif is_jungsong(c) or is_jongsong(c) or hangul_tonemark[c] then
+        pc = 1
       end
-    end
-  end
-  return head -- should be returned!
-end
 
-------------------------------
--- ruby: pre-linebreak routine
-------------------------------
-local function spread_ruby_box(head,extrawidth)
-  local to_free = {}
-  for curr in d_traverse_id(gluenode,head) do
-    local wd = d_getglue(curr) or 0
-    if d_has_attribute(curr,luakoglueattr) then
-      d_setglue(curr, wd + extrawidth)
-    else
-      head = d_insert_before(head,curr,d_get_kernnode(wd+extrawidth))
-      head = d_remove_node(head,curr)
-      to_free[#to_free + 1] = curr
+    elseif id == hlistid and has_attribute(curr, rubyattr) then
+      local c, fi = ruby_char_font(curr)
+      head, pc = do_interhangul_option(head, curr, pc, c, fi, par)
+
+    elseif id == whatsitid and curr.mode == directmode then
+      local glyf, c = get_actualtext(curr)
+      if c and glyf then
+        head = do_interhangul_option(head, curr, pc, c, glyf.font, par)
+        pc, curr = 1, goto_end_actualtext(curr)
+      end
+
+    elseif id == mathid then
+      pc, curr = 0, end_of_math(curr)
+    elseif is_blocking_node(curr) or id == dirid then
+      pc = 0
     end
+    curr = getnext(curr)
   end
-  if #to_free > 0 then
-    for _,v in ipairs(to_free) do d_nodefree(v) end
-  end
   return head
 end
 
-local function spread_ruby_base_box (head)
-  for curr in d_traverse_id(hlistnode,head) do
-    local attr = d_has_attribute(curr,luakorubyattr)
-    local rubyoverlap = attr and rubynode[attr][3]
-    if attr and not rubyoverlap then
-      local ruby = d_todirect(rubynode[attr][1])
-      local currwidth, rubywidth = d_getfield(curr,"width"), d_getfield(ruby,"width")
-      if ruby and rubywidth > currwidth then
-        local basehead = d_getlist(curr)
-        local numofglues = d_nodecount(gluenode,basehead)
-        local extrawidth = (rubywidth - currwidth)/(numofglues + 1)
-        if numofglues > 0 then
-          basehead = spread_ruby_box(basehead,extrawidth)
-        end
-        local leading = d_get_kernnode(extrawidth/2)
-        d_setfield(curr,"width", rubywidth)
-        d_setnext(leading, basehead)
-        d_setlist(curr, leading)
-      end
+local function do_interlatincjk_option (head, curr, pc, pf, c, cf, par)
+  local cc = (is_cjk(c) and 1) or (is_noncjkletter(c) and 2) or 0
+  local brb = cc == 2 or breakable_before[c] -- numletter != br_before
+
+  if brb and cc*pc == 2 and curr.lang ~= nohyphen then
+    local fontid = cc == 1 and cf or pf
+    local dim = get_font_opt_dimen(fontid, "interlatincjk")
+    if dim then
+      head = insert_glue_before(head, curr, par, true, brb, false, false, dim)
     end
   end
+
+  return head, cc, cf
 end
 
-local function get_ruby_side_width (basewidth,rubywidth,adjacent)
-  local width,margin = (rubywidth-basewidth)/2, 0
-  if adjacent then
-    if d_getid(adjacent) == glyphnode then
-      local uni = d_get_unicode_char(adjacent)
-      if is_unicode_vs(uni) then -- bypass VS
-        uni = d_get_unicode_char(d_getprev(adjacent))
+local function process_interlatincjk (head, par)
+  local curr, pc, pf = head, 0, 0
+  while curr do
+    local id = curr.id
+    if id == glyphid then
+      local c = my_node_props(curr).unicode or curr.char
+      head, pc, pf = do_interlatincjk_option(head, curr, pc, pf, c, curr.font, par)
+      pc = breakable_after[c] and pc or 0
+
+    elseif id == hlistid and has_attribute(curr, rubyattr) then
+      local c, cf = ruby_char_font(curr)
+      head, pc, pf = do_interlatincjk_option(head, curr, pc, pf, c, cf, par)
+
+    elseif id == whatsitid and curr.mode == directmode then
+      local glyf, c = get_actualtext(curr)
+      if c and glyf then
+        head, pc, pf = do_interlatincjk_option(head, curr, pc, pf, c, glyf.font, par)
+        curr = goto_end_actualtext(curr)
       end
-      if not is_hanja(uni) then
-        width = (rubywidth-basewidth-emsize)/2
-        if width > 0 then
-          margin = emsize/2
-        else
-          width,margin = 0,(rubywidth-basewidth)/2
-        end
+
+    elseif id == mathid then
+      if pc == 1 then
+        head = do_interlatincjk_option(head, curr, pc, pf, 0x30, pf, par)
       end
+      pc, curr = 2, end_of_math(curr)
+
+    elseif id == dirid then
+      if pc == 1 and curr.dir:sub(1,1) == "+" then
+        head = do_interlatincjk_option(head, curr, pc, pf, 0x30, pf, par)
+        pc = 0
+      end
+
+    elseif is_blocking_node(curr) then
+      pc = 0
     end
+    curr = getnext(curr)
   end
-  return width,margin
+  return head
 end
 
-local function get_ruby_side_kern (head)
-  for curr in d_traverse_id(hlistnode,head) do
-    local attr = d_has_attribute(curr,luakorubyattr)
-    local rubyoverlap = attr and rubynode[attr][3]
-    if rubyoverlap then
-      local currwidth = d_getfield(curr,"width")
-      local basewidth = currwidth
-      local ruby = d_todirect(rubynode[attr][1])
-      local rubywidth = d_getfield(ruby,"width")
-      if currwidth < rubywidth then
-        local _,fid = d_get_hlist_char_first(curr)
-        emsize = get_font_emsize(fid)
-        local leftwidth,leftmargin = get_ruby_side_width(basewidth,rubywidth,d_getprev(curr))
-        if leftwidth > 0 then
-          currwidth = currwidth + leftwidth
-          d_setfield(curr,"width", currwidth)
-        end
-        if leftmargin > 0 then
-          rubynode[attr].leftmargin = leftmargin
-        end
-        local rightwidth,rightmargin = get_ruby_side_width(basewidth,rubywidth,d_getnext(curr))
-        if rightwidth > 0 then
-          currwidth = currwidth + rightwidth
-          d_setfield(curr,"width", currwidth)
-        end
-        if rightmargin > 0 then
-          rubynode[attr].rightmargin = rightmargin
-        end
-        rubynode[attr].rightshift = rightwidth - leftwidth
-        local totalspace = leftwidth+rightwidth
-        if totalspace > 0 then
-          local currhead = d_getlist(curr)
-          local numofglues = d_nodecount(gluenode,currhead)
-          local extrawidth = totalspace/(numofglues + 1)
-          if numofglues > 0 then
-            currhead = spread_ruby_box(currhead,extrawidth)
+-- remove classic spaces
+
+local function process_remove_spaces (head)
+  local curr, opt_name, to_free = head, "removeclassicspaces", {}
+  while curr do
+    local id = curr.id
+    if id == glueid then
+      if curr.subtype == spaceskip and has_attribute(curr, classicattr) then
+
+        for k, v in pairs{ p = getprev(curr), n = getnext(curr) } do
+          local ok
+          while v do
+            local vid = v.id
+            if vid ~= whatsitid -- skip whatsit or kern except userkern
+              and ( vid ~= kernid or v.subtype == userkern ) then
+
+              local vchar, vfont
+              if vid == glyphid and v.lang ~= nohyphen then
+                vchar, vfont = v.char, v.font
+              elseif vid == hlistid and has_attribute(v, rubyattr) then
+                vchar, vfont = ruby_char_font(v)
+              end
+              if vchar and vfont and option_in_font(vfont, opt_name) then
+                ok = is_cjk(vchar)
+              end
+
+              break
+            end
+            v = k == "p" and getprev(v) or getnext(v)
           end
-          local leading = d_get_kernnode(extrawidth*(leftwidth/totalspace))
-          d_setnext(leading, currhead)
-          d_setlist(curr, leading)
+          if ok then
+            head = noderemove(head, curr)
+            tableinsert(to_free, curr)
+            break
+          end
         end
       end
+    elseif id == mathid then
+      curr = end_of_math(curr)
     end
+    curr = getnext(curr)
   end
+  for _,v in ipairs(to_free) do nodefree(v) end
+  return head
 end
 
-local function zero_width_rule_with_dir (head,curr,before)
-  local rule = d_get_rulenode(0)
-  d_setfield(rule,"dir", d_getfield(curr,"dir"))
-  if before then
-    head = d_insert_before(head,curr,rule)
-  else
-    d_insert_after(head,curr,rule)
+-- josa
+
+local josa_table = {
+    --          리을,   중성,   종성
+    [0xAC00] = {0xC774, 0xAC00, 0xC774}, -- 가 = 이, 가, 이
+    [0xC740] = {0xC740, 0xB294, 0xC740}, -- 은 = 은, 는, 은
+    [0xC744] = {0xC744, 0xB97C, 0xC744}, -- 을 = 을, 를, 을
+    [0xC640] = {0xACFC, 0xC640, 0xACFC}, -- 와 = 과, 와, 과
+    [0xC73C] = {nil,    nil,    0xC73C}, -- 으(로) =   ,  , 으
+    [0xC774] = {0xC774, nil,    0xC774}, -- 이(라) = 이,  , 이
+}
+
+local josa_code = setmetatable({
+    [0x30] = 3,   [0x31] = 1,   [0x33] = 3,   [0x36] = 3,
+    [0x37] = 1,   [0x38] = 1,   [0x4C] = 1,   [0x4D] = 3,
+    [0x4E] = 3,   [0x6C] = 1,   [0x6D] = 3,   [0x6E] = 3,
+    [0x2160] = 1, [0x2162] = 3, [0x2165] = 3, [0x2166] = 1,
+    [0x2167] = 1, [0x2169] = 3, [0x216A] = 1, [0x216C] = 3,
+    [0x216D] = 3, [0x216E] = 3, [0x216F] = 3, [0x2170] = 1,
+    [0x2172] = 3, [0x2175] = 3, [0x2176] = 1, [0x2177] = 1,
+    [0x2179] = 3, [0x217A] = 1, [0x217C] = 3, [0x217D] = 3,
+    [0x217E] = 3, [0x217F] = 3, [0x2460] = 1, [0x2462] = 3,
+    [0x2465] = 3, [0x2466] = 1, [0x2467] = 1, [0x2469] = 3,
+    [0x246A] = 1, [0x246C] = 3, [0x246F] = 3, [0x2470] = 1,
+    [0x2471] = 1, [0x2473] = 3, [0x2474] = 1, [0x2476] = 3,
+    [0x2479] = 3, [0x247A] = 1, [0x247B] = 1, [0x247D] = 3,
+    [0x247E] = 1, [0x2480] = 3, [0x2483] = 3, [0x2484] = 1,
+    [0x2485] = 1, [0x2487] = 3, [0x2488] = 1, [0x248A] = 3,
+    [0x248D] = 3, [0x248E] = 1, [0x248F] = 1, [0x2491] = 3,
+    [0x2492] = 1, [0x2494] = 3, [0x2497] = 3, [0x2498] = 1,
+    [0x2499] = 1, [0x249B] = 3, [0x24A7] = 1, [0x24A8] = 3,
+    [0x24A9] = 3, [0x24C1] = 1, [0x24C2] = 3, [0x24C3] = 3,
+    [0x24DB] = 1, [0x24DC] = 3, [0x24DD] = 3, [0x3139] = 1,
+    [0x3203] = 1, [0x3263] = 1, [0xFF10] = 3, [0xFF11] = 1,
+    [0xFF13] = 3, [0xFF16] = 3, [0xFF17] = 1, [0xFF18] = 1,
+    [0xFF2C] = 1, [0xFF2D] = 3, [0xFF2E] = 3, [0xFF4C] = 1,
+    [0xFF4D] = 3, [0xFF4E] = 3,
+},{ __index = function(_,c)
+  if is_hangul(c) then
+    c = (c - 0xAC00) % 28 + 0x11A7
   end
-  return head
-end
+  if is_chosong(c) then
+    return c == 0x1105 and 1 or 3
+  elseif is_jungsong(c) then
+    return c ~= 0x1160 and 2
+  elseif is_jongsong(c) then
+    return c == 0x11AF and 1 or 3
+  elseif (is_noncjkletter(c) and c <= 0x7A )
+    or (c >= 0x2160 and c <= 0x217F) -- roman
+    or (c >= 0x2460 and c <= 0x24E9) -- ①
+    or (c >= 0x314F and c <= 0x3163) or (c >= 0x3187 and c <= 0x318E) -- ㅏ
+    or (c >= 0x320E and c <= 0x321E) -- ㈎
+    or (c >= 0x326E and c <= 0x327F) -- ㉮
+    or (c >= 0xFF10 and c <= 0xFF19) -- 0
+    or (c >= 0xFF21 and c <= 0xFF3A) -- A
+    or (c >= 0xFF41 and c <= 0xFF5A) -- a
+    then return 2
+  elseif (c >= 0x3131 and c <= 0x314E) or (c >= 0x3165 and c <= 0x3186) -- ㄱ
+    or (c >= 0x3200 and c <= 0x320D) -- ㈀
+    or (c >= 0x3260 and c <= 0x326D) -- ㉠
+    then return 3
+  end
+end })
 
-local function no_ruby_at_margin(head)
-  for curr in d_traverse_id(hlistnode,head) do
-    local attr = d_has_attribute(curr,luakorubyattr)
-    if attr then
-      local margin = rubynode[attr].leftmargin
-      if margin then
-        head = d_insert_before(head,curr,d_get_kernnode(-margin))
-        head = zero_width_rule_with_dir(head,curr,true) -- before
-        head = d_insert_before(head,curr,d_get_kernnode(margin))
+local ignore_parens = false
+
+local function prevjosacode (n, parenlevel)
+  local parenlevel, josacode = parenlevel or 0
+  while n do
+    local id = n.id
+    if id == glyphid then
+      local c = my_node_props(n).unicode or n.char -- beware hlist/vlist
+      if ignore_parens and c == 0x29 then
+        parenlevel = parenlevel + 1
+      elseif ignore_parens and c == 0x28 then
+        parenlevel = parenlevel - 1
+      elseif parenlevel <= 0 then
+        josacode = josa_code[c]
+        if josacode then break end
       end
-      margin = rubynode[attr].rightmargin
-      if margin then
-        local nn = d_getnext(curr)
-        if nn then
-          local nnid = d_getid(nn)
-          if nnid == gluenode then
-            d_insert_after(head,curr,d_get_kernnode(-margin))
-            head = zero_width_rule_with_dir(head,curr)
-            d_insert_after(head,curr,d_get_kernnode(margin))
-          elseif nnid == penaltynode and d_getfield(nn,"penalty") < 10000 then
-            d_insert_after(head,nn,d_get_kernnode(-margin))
-            head = zero_width_rule_with_dir(head,curr)
-            d_insert_after(head,curr,d_get_kernnode(margin))
-          end
-        end
+    elseif id == hlistid or id == vlistid then
+      local list = n.list
+      if list then
+        josacode, parenlevel = prevjosacode(nodeslide(list), parenlevel)
+        if josacode then break end
       end
     end
+    n = getprev(n)
   end
-  return head
+  return josacode, parenlevel
 end
 
-------------------------------
--- discourage character orphan
-------------------------------
-local function inject_char_widow_penalty (head,curr,uni,cjattr)
-  if uni and prebreakpenalty[uni] ~= 10000 then
-    local class = get_cjk_class(uni, cjattr)
-    if class and class < 9 then
-      local pv = cjattr and 500 or 5000
-      local np = d_getprev(curr)
-      if np and d_getid(np) == rulenode then
-        curr = np; np = d_getprev(curr)
-      end
-      if np and d_getid(np) == gluenode then
-        curr = np; np = d_getprev(curr)
-      end
-      if np and d_getid(np) == penaltynode then
-        curr = np
-        local currpenalty = d_getfield(curr,"penalty")
-        if currpenalty < pv and currpenalty > 0 then -- bypass \\ case
-          d_setfield(curr,"penalty", pv)
+local function process_josa (head)
+  local curr, tofree = head, {}
+  while curr do
+    local id = curr.id
+    if id == glyphid then
+      local autojosaattr = has_attribute(curr, autojosaattr)
+      if autojosaattr then
+        local cc = curr.char
+        if cc == 0xC774 then
+          local n = getnext(curr)
+          if n and n.char and is_hangul(n.char) then
+          else
+            cc = 0xAC00
+          end
         end
-      elseif curr and d_getid(curr) == gluenode then
-        head, curr = d_insert_before(head,curr,d_get_penaltynode(pv))
+        local t = josa_table[cc]
+        if t then
+          ignore_parens = autojosaattr > 0
+          cc = t[prevjosacode(getprev(curr)) or 3]
+          if cc then
+            curr.char = cc
+          else
+            head = noderemove(head, curr)
+            tableinsert(tofree, curr)
+          end
+        end
+        unset_attribute(curr, autojosaattr)
       end
+    elseif id == mathid then
+      curr = end_of_math(curr)
     end
+    curr = getnext(curr)
   end
-  return curr
+  for _,v in ipairs(tofree) do nodefree(v) end
+  return head
 end
 
-local function discourage_char_widow (head,curr)
+-- dotemph
+
+local dotemphbox = {}
+luatexko.dotemphbox = dotemphbox
+
+local function process_dotemph (head, tofree)
+  local curr, outer, tofree = head, not tofree, tofree or {}
   while curr do
-    if curr == head then return end
-    local currid = d_getid(curr)
-    if currid == glyphnode then
-      emsize = get_font_emsize(d_getfont(curr))
-      local remwd = d_nodedimensions(curr)
-      if remwd > 2*emsize then return end
-      local cjattr = d_has_attribute(curr,cjtypesetattr)
-      local uni = d_get_unicode_char(curr)
-      curr = inject_char_widow_penalty(head,curr,uni,cjattr)
-    elseif currid == hlistnode and currid == vlistnode then
-      local remwd = d_nodedimensions(curr)
-      local uni,fid = d_get_hlist_char_first(curr)
-      emsize = get_font_emsize(fid)
-      if remwd > 2*emsize then return end
-      local cjattr = d_has_attribute(curr,cjtypesetattr)
-      curr = inject_char_widow_penalty(head,curr,uni,cjattr)
+    local id = curr.id
+    if id == glyphid then
+      local dotattr = has_attribute(curr, dotemphattr)
+      if dotattr then
+        local c = my_node_props(curr).unicode or curr.char
+        if is_hangul(c) or is_hanja(c) or is_chosong(c) or is_kana(c) then
+          local currwd = curr.width
+          if currwd >= get_en_size(curr.font) then
+            local box = nodecopy(dotemphbox[dotattr])
+            local shift = (currwd - box.width)/2
+            if shift ~= 0 then
+              local list = box.list
+              local k = nodenew(kernid)
+              k.kern = shift
+              box.list = insert_before(list, list, k)
+            end
+            box.width = 0
+            head = insert_before(head, curr, box)
+            tofree[dotattr] = true
+          end
+        end
+        unset_attribute(curr, dotemphattr)
+      end
+    elseif id == hlistid then
+      local list = curr.list
+      if list then
+        curr.list, tofree = process_dotemph(list, tofree)
+      end
     end
-    curr = d_getprev(curr)
+    curr = getnext(curr)
   end
+  if outer then
+    for k in pairs(tofree) do nodefree(dotemphbox[k]) end
+  end
+  return head, tofree
 end
 
----------------------------
--- automatic josa selection
----------------------------
-local function syllable2jamo (code)
-  local code = code - 0xAC00
-  local L = 0x1100 + mathfloor(code / 588)
-  local V = 0x1161 + mathfloor((code % 588) / 28)
-  local T = 0x11A7 + code % 28
-  if T == 0x11A7 then T = nil end
-  return L, V, T
-end
+-- uline
 
-local function number2josacode (n)
-  n = n % 10 + stringbyte("0")
-  if josa_code[n] then return josa_code[n] end
-  return nil -- 2
-end
+local uline_f, uline_id = new_user_whatsit("uline","luatexko")
+local no_uline_id = new_user_whatsit_id("no_uline","luatexko")
 
-local function latin2josacode (n)
-  n = n + stringbyte("a")
-  if josa_code[n] then return josa_code[n] end
-  return nil -- 2
+local function ulboundary (i, n)
+  local what = uline_f()
+  if n then
+    what.type  = lua_value -- table
+    what.value = { i, nodecopy(n) }
+  else
+    what.type  = lua_number
+    what.value = i
+  end
+  nodewrite(what)
 end
+luatexko.ulboundary = ulboundary
 
-for c = 0x2160, 0x216B do -- Ⅰ
-  josa_code[c] = number2josacode(c - 0x215F)
-end
-for c = 0x2170, 0x217B do -- ⅰ
-  josa_code[c] = number2josacode(c - 0x216F)
-end
-for c = 0x2460, 0x2473 do -- ①
-  josa_code[c] = number2josacode(c - 0x245F)
-end
-for c = 0x2474, 0x2487 do -- ⑴
-  josa_code[c] = number2josacode(c - 0x2473)
-end
-for c = 0x2488, 0x249B do -- ⒈
-  josa_code[c] = number2josacode(c - 0x2487)
-end
-for c = 0x249C, 0x24B5 do -- ⒜
-  josa_code[c] = latin2josacode(c - 0x249C)
-end
-for c = 0x24B6, 0x24CF do --  Ⓐ
-  josa_code[c] = latin2josacode(c - 0x24B6)
-end
-for c = 0x24D0, 0x24E9 do --  ⓐ
-  josa_code[c] = latin2josacode(c - 0x24D0)
-end
-for c = 0x3131, 0x314E do -- ㄱ
-  josa_code[c] = 3
-end
-josa_code[0x3139] = 1 -- ㄹ
-for c = 0x3165, 0x3186 do
-  josa_code[c] = 3
-end
-for c = 0x3200, 0x320D do -- ㈀
-  josa_code[c] = 3
-end
-josa_code[0x3203] = 1 -- ㈃
-for c = 0x3260, 0x327F do -- ㉠
-  josa_code[c] = 3
-end
-josa_code[0x3263] = 1 -- ㉣
-for c = 0xFF10, 0xFF19 do -- 0
-  josa_code[c] = number2josacode(c - 0xFF10)
-end
-for c = 0xFF21, 0xFF3A do -- A
-  josa_code[c] = latin2josacode(c - 0xFF21)
-end
-for c = 0xFF41, 0xFF5A do -- a
-  josa_code[c] = latin2josacode(c - 0xFF41)
-end
+local white_nodes = {
+  [glueid]    = true,
+  [penaltyid] = true,
+  [kernid]    = true,
+  [whatsitid] = true,
+}
 
-local function jamo2josacode(code)
-  if code and code > 0x11A7 then
-    if code == 0x11AF then return 1 end
-    return 3
+function skip_white_nodes (n, ltr)
+  local nextnode = ltr and getnext or getprev
+  while n do
+    if not white_nodes[n.id] then break end
+    n = nextnode(n)
   end
-  return 2
+  return n
 end
 
-local function get_hanja_hangul_table (table,file,init)
-  local i = 0
-  local file = kpse_find_file(file)
-  if not file then return table end
-  file = io.open(file, "r")
-  if not file then return table end
-  while true do
-    local d = file:read("*number")
-    if not d then break end
-    table[init + i] = d
-    i = i + 1
+local function draw_uline (head, curr, parent, t, final)
+  local start, list = t.start or head, t.list
+  start = skip_white_nodes(start, true)
+  nodeslide(start) -- to get correct getprev.
+  curr  = skip_white_nodes(curr)
+  if getnext(curr) then curr = getnext(curr) end
+  local len = parent and rangedimensions(parent, start, curr)
+                     or  dimensions(start, curr) -- it works?!
+  if len and len ~= 0 then
+    local g = nodenew(glueid)
+    setglue(g, len)
+    g.subtype = xleaders
+    g.leader = final and list or nodecopy(list)
+    local k = nodenew(kernid)
+    k.kern = -len
+    head = insert_before(head, start, g)
+    head = insert_before(head, start, k)
   end
-  file:close()
-  return table
+  return head
 end
 
-local hanja2hangul
-local function initialize_hanja2hangul ()
-  hanja2hangul = {}
-  hanja2hangul = get_hanja_hangul_table(hanja2hangul,"hanja_hangul.tab",0x4E00)
-  hanja2hangul = get_hanja_hangul_table(hanja2hangul,"hanjaexa_hangul.tab",0x3400)
-  hanja2hangul = get_hanja_hangul_table(hanja2hangul,"hanjacom_hangul.tab",0xF900)
-end
+local function process_uline (head, parent, ulitems, level)
+  local curr, ulitems, level = head, ulitems or {}, level or 0
+  while curr do
+    local id = curr.id
+    if id == whatsitid
+      and curr.subtype == user_whatsit
+      and curr.user_id == uline_id then
 
--- 1 : 리을,  2 : 중성,    3 : 종성
-local function get_josacode (prevs)
-  local code = prevs[1] -- last char
-  if not code then return 2 end
-  if is_hangul(code) then -- hangul syllable
-    local _, _, T = syllable2jamo(code)
-    return jamo2josacode(T)
-  end
-  if is_jungjongsong(code) then return jamo2josacode(code) end
-  if (code >= 0x3400 and code <= 0x9FA5)
-    or (code >= 0xF900 and code <= 0xFA2D) then
-    if not hanja2hangul then initialize_hanja2hangul() end
-    local _, _, T = syllable2jamo(hanja2hangul[code])
-    return jamo2josacode(T)
-  end
-  -- latin
-  if prevs[1] < 0x80
-    and prevs[2] and prevs[2] < 0x80
-    and prevs[3] and prevs[3] < 0x80 then
-    local liii = stringchar(prevs[3], prevs[2], prevs[1])
-    if josa_code[liii] then return josa_code[liii] end
-  end
-  if prevs[1] < 0x80
-    and prevs[2] and prevs[2] < 0x80 then
-    local lii = stringchar(prevs[2], prevs[1])
-    if josa_code[lii] then return josa_code[lii] end
-  end
-  if josa_code[code] then return josa_code[code] end
-  return 2
-end
+      local value = curr.value
+      if curr.type == lua_value then
+        local count, list = value[1], value[2]
+        ulitems[count] = { start = curr, list = list, level = level }
+      elseif ulitems[value] then
+        head = draw_uline(head, curr, parent, ulitems[value], true)
+        ulitems[value] = nil
+      end
 
-local function get_josaprevs(curr,josaprev,ignoreparens,halt)
-  if type(halt) ~= "number" then halt = 0 end
-  while curr do
-    local currid = d_getid(curr)
-    if currid == glyphnode then
-      local chr = d_get_unicode_char(curr)
-      -- ignore chars inside parentheses (KTS workshop 2013.11.09)
-      if ignoreparens and chr == 0x29 then -- right parenthesis
-        halt = halt + 1
-      elseif ignoreparens and chr == 0x28 then -- left parenthesis
-        halt = halt - 1
-      elseif xspcode[chr]
-        or inhibitxspcode[chr]
-        or prebreakpenalty[chr]
-        or postbreakpenalty[chr]
-        or is_unicode_vs(chr) -- bypass VS
-        or chr == 0x302E  -- tone mark
-        or chr == 0x302F then  -- tone mark
-        --skip
-      elseif halt <= 0 then
-        josaprev[#josaprev + 1] = chr
+      curr.user_id = no_uline_id -- avoid multiple run
+    elseif id == hlistid then
+      local list = curr.list
+      if list then
+        curr.list, ulitems = process_uline(list, curr, ulitems, level+1)
       end
-    elseif currid == hlistnode or currid == vlistnode then
-      josaprev = get_josaprevs(d_nodetail(d_getlist(curr)),josaprev,ignoreparens,halt)
     end
-    if #josaprev == 3 then break end
-    curr = d_getprev(curr)
+    curr = getnext(curr)
   end
-  return josaprev
-end
 
-local function korean_autojosa (head)
-  local to_free = {}
-  for curr in d_traverse_id(glyphnode,head) do
-    local josaattr = d_has_attribute(curr,autojosaattr)
-    if josaattr and d_has_attribute(curr,finemathattr) then
-      local ignoreparens = josaattr > 1 and true or false
-      local josaprev = {}
-      josaprev = get_josaprevs(d_getprev(curr),josaprev,ignoreparens)
-      local josacode = get_josacode(josaprev)
-      local thischar = d_get_unicode_char(curr)
-      if thischar == 0xC774 then
-        local nn, nc = d_getnext(curr)
-        if nn and d_getid(nn) == glyphnode then
-          nc = d_get_unicode_char(nn)
-        end
-        if nc and nc >= 0xAC00 and nc <= 0xD7A3 then
-          d_setchar(curr, josa_list[0xC774][josacode])
-        else
-          d_setchar(curr, josa_list[0xAC00][josacode])
-        end
-      elseif thischar and josa_list[thischar] then
-        d_setchar(curr, josa_list[thischar][josacode])
-      end
+  curr = nodeslide(head)
+  for i, t in pairs(ulitems) do
+    if level == t.level then
+      head = draw_uline(head, curr, parent, t)
+      t.start = nil
     end
-    if d_getchar(curr) < 0 then
-      d_remove_node(head,curr)
-      to_free[#to_free + 1] = curr
-    end
   end
-  if #to_free > 0 then
-    for _,v in ipairs(to_free) do d_nodefree(v) end
-  end
+  return head, ulitems
 end
 
-------------------------------
--- switch to hangul/hanja font
-------------------------------
-local function hangulspaceskip (engfont, hfontid, glue)
-  local eng = engfont.parameters
-  if not eng then return end
-  local gsp, gst, gsh, gsto, gsho = d_getglue(glue)
-  if gsto ~= 0 or gsho ~= 0 then return end
-  local esp, est, esh = eng.space, eng.space_stretch, eng.space_shrink
-  esp = esp and tex_round(esp)
-  est = est and tex_round(est)
-  esh = esh and tex_round(esh)
-  if esp == gsp and est == gst and esh == gsh then else return end
-  local hf = get_font_table(hfontid)
---  if hf and hf.encodingbytes == 2 then else return end
-  return gsp, hf
-end
+-- ruby
 
-local function virtual_unifont (prefix,size)
-  local data = {
-    name       = prefix.."vf",
-    type       = "virtual",
-    fonts      = {},
-    characters = {},
-  }
-  local fonts = data.fonts
-  local chars = data.characters
-  for index = 0, 255 do
-    local name = string.format(prefix.."%02x", index)
-    if kpse_find_file(name,"tfm") then
-      local tfm = font.read_tfm(name, size)
-      data.parameters = data.parameters or tfm.parameters
-      fonts[#fonts+1] = { name=name, size=size }
-      local fid = #fonts
-      local zro = 256 * index
-      for i,v in pairs(tfm.characters) do
-        local slot = zro + i
-        chars[slot] = v
-        chars[slot].commands = { { "slot", fid, i } }
-      end
-    end
-  end
-  return data
-end
+local rubybox = {}
+luatexko.rubybox = rubybox
 
-local function nanumtype1font(curr)
-  local currchar, currfont = d_getchar(curr), d_getfont(curr)
-  if currchar > 0xFFFF then return end
-  local fnt_t  = get_font_table(currfont)
-  if not fnt_t then return end
-  local family = (d_has_attribute(curr,finemathattr) or 0) > 1 and not is_hanja(currchar) and "nanummj" or "nanumgt"
-  local series = fnt_t.shared and
-                 fnt_t.shared.rawdata and
-                 fnt_t.shared.rawdata.metadata and
-                 fnt_t.shared.rawdata.metadata.pfmweight
-  if series then
-    series = series > 500 and "b" or "m"
-  else
-    series = fnt_t.name:find("^cmb") and "b" or "m"
-  end
-  local shape  = fnt_t.parameters.slant > 0 and "o" or ""
-  local prefix = stringformat("%s%s%s",family,series,shape)
-  local fsize  = fnt_t.size or 655360
-  local fspec  = stringformat("%s@%d",prefix,fsize)
-  local fontid = type1fonts[fspec]
-  if fontid == nil then -- bypass false
-    local fontdata = virtual_unifont(prefix, fsize)
-    fontid = fontdata and fontdefine(fontdata)
-    if fontid then
-      type1fonts[fspec]  = fontid
-      type1fonts[fontid] = fontdata
-    else
-      type1fonts[fspec] = false
-    end
-  end
-  return fontid
-end
-
-local function font_substitute(head)
+local function process_ruby_pre_linebreak (head)
   local curr = head
   while curr do
-    local currid = d_getid(curr)
-    if currid == glyphnode then
-      local currchar, currfont = d_getchar(curr), d_getfont(curr)
-      if currchar then
-        if is_unicode_vs(currchar) then -- ideograph variation selectors
-          local prev = d_getprev(curr)
-          local prevfont = d_getid(prev) == glyphnode and d_getfont(prev)
-          if prevfont ~= currfont then
-            d_setfield(curr, "font", prevfont) -- sync font
+    local id = curr.id
+    if id == hlistid then
+      local rubyid = has_attribute(curr, rubyattr)
+      if rubyid then
+        local ruby_t = rubybox[rubyid]
+        if ruby_t[3] then -- rubyoverlap
+          local side = (ruby_t[1].width - curr.width)/2
+          if side > 0 then -- ruby is wide
+            local k, r = nodenew(kernid), nodenew(ruleid)
+            k.subtype, k.kern = userkern, -side
+            r.width, r.height, r.depth = side, 0, 0
+            local k2, r2 = nodecopy(k), nodecopy(r)
+            head = insert_before(head, curr, k)
+            head = insert_before(head, curr, r)
+            head, curr = insert_after(head, curr, r2)
+            head, curr = insert_after(head, curr, k2)
           end
-        else
-          local myfontchar, forcehangul, forcehanja
-          local eng = currfont and get_font_table(currfont)
-          if eng and eng.encodingbytes and eng.encodingbytes == 2 -- exclude type1
-            and hangulpunctuations[currchar] and d_has_attribute(curr, hangulpunctsattr)
-            and (d_has_attribute(curr, finemathattr) or 0) > 0 -- not ttfamily
-            and not get_font_char(currfont, 0xAC00) then -- exclude hangul font
-          else
-            if luatexko.hangulfontforhangul or luatexko.hanjafontforhanja then
-              local uni = d_get_unicode_char(curr)
-              uni = uni and get_cjk_class(uni) or 10
-              if uni < 7 then
-                forcehanja = luatexko.hanjafontforhanja
-              elseif uni < 10 then
-                forcehangul = luatexko.hangulfontforhangul
-              end
-            end
-            if not forcehangul and not forcehanja then
-              myfontchar = get_font_char(currfont, currchar)
-            end
-          end
-          if not myfontchar then
-            local hangul    = d_has_attribute(curr, hangulfntattr)
-            local hanja     = d_has_attribute(curr, hanjafntattr)
-            local fallback  = d_has_attribute(curr, fallbackfntattr)
-            local ftable    = {hangul, hanja, fallback}
-            if forcehanja then ftable = {hanja, hangul, fallback} end
-            local fid
-            for i = 1,3 do
-              fid = ftable[i]
-              myfontchar = fid and get_font_char(fid, currchar)
-              if myfontchar then break end
-            end
-            if not myfontchar and not hangulpunctuations[currchar] then
-              fid = nanumtype1font(curr)
-              myfontchar = fid and get_font_char(fid, currchar)
-            end
-            if myfontchar then
-              d_setfield(curr,"font",fid)
-              --- charraise option charraise
-              local charraise = get_font_feature(fid, "charraise")
-              if charraise then
-                charraise = tex_sp(charraise)
-                local curryoffset = d_getfield(curr,"yoffset") or 0
-                d_setfield(curr,"yoffset", charraise + curryoffset)
-              end
-              ---
-              local nxt = d_getnext(curr)
-              local nxtid = nxt and d_getid(nxt)
-              if nxtid == glyphnode and is_unicode_vs(d_getchar(nxt)) then
-                nxt = d_getnext(nxt)
-                nxtid = nxt and d_getid(nxt)
-              end
-              if eng and nxtid then
-                local nxtsubtype = d_getsubtype(nxt)
-                -- adjust next glue by hangul font space
-                if nxtid == gluenode and nxtsubtype == glue_type_space and get_font_char(fid,32) then
-                  local oldwd, ft = hangulspaceskip(eng, fid, nxt)
-                  if oldwd and ft then
-                    local newwd = ft.space_char_width
-                    if not newwd then
-                      local newsp = d_tonode(d_copy_node(curr))
-                      newsp.char = 32
-                      newsp = nodes.simple_font_handler(newsp)
-                      newwd = newsp and newsp.width
-                      ft.space_char_width = newwd
-                      node.free(newsp)
-                    end
-                    if newwd and oldwd ~= newwd then
-                      d_setglue(nxt, newwd, newwd/2, newwd/3)
-                    end
-                  end
-                -- adjust next italic correction kern
-                elseif nxtid == kernnode and nxtsubtype == kern_type_itlc and d_getfield(nxt,"kern") == 0 then
-                  local ksl = get_font_table(fid).parameters.slant
-                  if ksl and ksl > 0 then
-                    d_setfield(nxt,"kern", myfontchar.italic or 0)
-                  end
-                end
-              end
-            end
-          end
+          ruby_t[3] = false
         end
       end
-    elseif currid == mathnode then
-      if d_getsubtype(curr) == 0 then
-        curr = d_end_of_math(curr)
-      end
-    elseif currid == discnode then
-      local pre, post, replace = d_getdisc(curr)
-      if pre     then font_substitute(pre)     end
-      if post    then font_substitute(post)    end
-      if replace then font_substitute(replace) end
+    elseif id == mathid then
+      curr = end_of_math(curr)
     end
-    curr = d_getnext(curr)
+    curr = getnext(curr)
   end
+  return head
 end
 
------------------------------
--- reserve unicode code point
------------------------------
-local function assign_unicode_codevalue (head)
-  for curr in d_traverse_id(glyphnode, head) do
-    d_set_attribute(curr, luakounicodeattr, d_getchar(curr))
-  end
-end
-
------------------------------
--- reorder hangul tone marks
------------------------------
-local function reorderTM (head)
-  local curr, todo = head, nil
+local function process_ruby_post_linebreak (head)
+  local curr = head
   while curr do
-    local nid = d_getid(curr)
-    if nid == glyphnode then
-      local uni = d_get_unicode_char(curr)
-      if uni == 0x302E or uni == 0x302F then
-        local tmgl = get_font_char(d_getfont(curr), uni)
-        local tmwidth = tmgl and tmgl.width or 0
-        if todo then
-          if tmwidth > 0 then
-            local tm = curr
-            head, curr = d_remove_node(head, curr)
-            d_setprev(tm, nil) -- prev might survive!
-            head = d_insert_before(head, todo, tm)
-            curr = d_getprev(curr)
+    if curr.id == hlistid then
+      local rubyid = has_attribute(curr, rubyattr)
+      if rubyid then
+        local ruby_t = rubybox[rubyid]
+        local ruby = ruby_t and ruby_t[1]
+        if ruby then
+          local side = (curr.width - ruby.width)/2
+          if side ~= 0 then
+            local list = ruby.list
+            local k = nodenew(kernid)
+            k.kern = side
+            ruby.list = insert_before(list, list, k)
           end
-        elseif get_font_char(d_getfont(curr), 0x25CC) then
-          local u25CC = d_copy_node(curr)
-          d_setchar(u25CC, 0x25CC)
-          if tmwidth > 0 then
-            head, curr = d_insert_after(head, curr, u25CC)
-          else
-            head = d_insert_before(head, curr, u25CC)
-          end
+          ruby.width = 0
+          ruby.shift = -(curr.height + ruby_t[2]) -- rubysep
+          head = insert_before(head, curr, ruby)
         end
-        todo = nil
-      elseif is_hangul(uni) or is_chosong(uni) or uni == 0x25CC then
-        todo = curr
-      elseif is_jungjongsong(uni) then
+        ruby_t = nil
+        unset_attribute(curr, rubyattr)
       else
-        todo = nil
+        local list = curr.list
+        if list then
+          curr.list = process_ruby_post_linebreak(list)
+        end
       end
-    elseif nid == kernnode then
-    else
-      todo = nil
     end
-    curr = d_getnext(curr)
+    curr = getnext(curr)
   end
   return head
 end
 
-----------------------------------
--- add to callback : pre-linebreak
-----------------------------------
+-- reorder tone marks
 
-add_to_callback("hpack_filter", function(head)
-  head = d_todirect(head)
-  assign_unicode_codevalue(head)
-  korean_autojosa(head)
-  remove_cj_spaceskip(head)
-  font_substitute(head)
-  return d_tonode(head)
-end, 'luatexko.hpack_filter_first', 1)
+local function conv_tounicode (uni)
+  if uni < 0x10000 then
+    return stringformat("%04X", uni)
+  else -- surrogate
+    uni = uni - 0x10000
+    local high = uni // 0x400 + 0xD800
+    local low  = uni %  0x400 + 0xDC00
+    return stringformat("%04X%04X", high, low)
+  end
+end
 
-add_to_callback('hpack_filter', function(head)
-  head = d_todirect(head)
-  if texcount["luakorubyattrcnt"]>0 then get_ruby_side_kern(head) end
-  cjk_spacing_linebreak(head)
-  if texcount["luakorubyattrcnt"]>0 then spread_ruby_base_box(head) end
-  head = compress_fullwidth_punctuations(head)
-  -- head = no_ruby_at_margin(head)
-  head = reorderTM(head)
-  return d_tonode(head)
-end, 'luatexko.hpack_filter')
-
-add_to_callback('pre_linebreak_filter', function(head)
-  head = d_todirect(head)
-  assign_unicode_codevalue(head)
-  korean_autojosa(head)
-  remove_cj_spaceskip(head)
-  font_substitute(head)
-  return d_tonode(head)
-end, 'luatexko.pre_linebreak_filter_first', 1)
-
-add_to_callback('pre_linebreak_filter', function(head)
-  head = d_todirect(head)
-  if texcount["luakorubyattrcnt"]>0 then get_ruby_side_kern(head) end
-  cjk_spacing_linebreak(head)
-  if texcount["luakorubyattrcnt"]>0 then spread_ruby_base_box(head) end
-  head = compress_fullwidth_punctuations(head)
-  discourage_char_widow(head, d_nodetail(head))
-  if texcount["luakorubyattrcnt"]>0 then head = no_ruby_at_margin(head) end
-  head = reorderTM(head)
-  return d_tonode(head)
-end, 'luatexko.pre_linebreak_filter')
-
-
---------------------------
--- dot emphasis (드러냄표)
---------------------------
-local function after_linebreak_dotemph (head, to_free)
-  local outer = not to_free
-  to_free = to_free or {}
-  for curr in d_traverse(head) do
-    local currid = d_getid(curr)
-    if currid == hlistnode then -- hlist may be nested!!!
-      d_setlist(curr, after_linebreak_dotemph(d_getlist(curr), to_free))
-    elseif currid == glyphnode then
-      local attr = d_has_attribute(curr,dotemphattr)
-      if attr and attr > 0 then
-        local cc = get_cjk_class(d_get_unicode_char(curr))
-        if cc and (cc == 0 or cc == 7 or cc == 8) then
-          local basewd = d_getfield(curr,"width") or 0
-          if cc == 8 then -- check next char for old hangul jung/jongseong
-            local nn = d_getnext(curr)
-            while nn do
-              if d_getid(nn) ~= glyphnode then break end
-              local uni = d_get_unicode_char(nn)
-              local nc = get_cjk_class(uni)
-              if nc and nc == 9 and uni ~= 0x302E and uni ~= 0x302F then
-                basewd = basewd + (d_getfield(nn,"width") or 0)
-              else
-                break
-              end
-              nn = d_getnext(nn)
-            end
-          end
-          local dbox = d_todirect(dotemphnode[attr])
-          local d = d_copy_node(dbox)
-          local dwidth = d_getfield(d,"width")
-          local dot = d_get_kernnode(basewd/2-dwidth/2)
-          d_setnext(dot, d_getlist(d))
-          d_setlist(d, dot)
-          d_setfield(d,"width", 0)
-          head = d_insert_before(head,curr,d)
-          to_free[dbox] = true
-        end
-        d_unset_attribute(curr,dotemphattr)
-      end
+local function pdfliteral_direct_actual (syllable)
+  local data
+  if syllable then
+    local t = {}
+    for _,v in ipairs(syllable) do
+      tableinsert(t, conv_tounicode(v))
     end
+    data = stringformat("/Span<</ActualText<FEFF%s>>>BDC", tableconcat(t))
+  else
+    data = "EMC"
   end
-  if outer then
-    for k in pairs(to_free) do d_nodefree(k) end
+  local what = nodenew(whatsitid, literal_whatsit)
+  what.mode = directmode
+  what.data = data
+  if syllable then
+    my_node_props(what).startactualtext = syllable
+  else
+    my_node_props(what).endactualtext = true
   end
-  return head
+  return what
 end
 
--------------------------------
--- ruby: post-linebreak routine
--------------------------------
-local function after_linebreak_ruby (head)
-  for curr in d_traverse_id(hlistnode,head) do
-    after_linebreak_ruby(d_getlist(curr)) -- hlist may be nested!!!
-    local attr = d_has_attribute(curr,luakorubyattr)
-    if attr then
-      local ruby = rubynode[attr] and d_todirect(rubynode[attr][1])
-      if ruby then
-        local currwidth, rubywidth = d_getfield(curr,"width"), d_getfield(ruby,"width")
-        local currheight = d_getfield(curr,"height")
-        if rubywidth < currwidth then
-          local rubyhead = d_getlist(ruby)
-          local numofglues = d_nodecount(gluenode,rubyhead)
-          local extrawidth = (currwidth - rubywidth)/(numofglues + 1)
-          d_setfield(ruby,"width", currwidth - extrawidth/2)
-          if numofglues > 0 then
-            d_setlist(ruby, spread_ruby_box(rubyhead,extrawidth))
-          end
-        else
-          local right = rubynode[attr].rightshift or 0
-          d_setfield(ruby,"width", currwidth + (rubywidth-currwidth+right)/2)
-        end
-        d_setfield(ruby,"shift", -currheight-rubynode[attr][2])
-        d_insert_after(head,curr,ruby)
-        d_insert_after(head,curr,d_get_kernnode(-d_getfield(ruby,"width")))
-      end
-      rubynode[attr] = nil
-      d_unset_attribute(curr,luakorubyattr)
-    end
+local function get_tonemark_width (curr, uni)
+  local fontid = curr.font
+  local hwidth = char_font_options.tonemarkwidth[fontid]
+  if not hwidth then
+    -- check horizontal width; vertical width is mostly non-zero
+    local fontdata     = get_font_data(fontid)
+    local shared       = fontdata.shared      or {}
+    local rawdata      = shared.rawdata       or {}
+    local descriptions = rawdata.descriptions or {}
+    local description  = descriptions[uni]    or {}
+    hwidth = description.width or 0
+    char_font_options.tonemarkwidth[fontid] = hwidth
   end
+  return hwidth
 end
 
----------------------
--- underline emphasis
----------------------
+local function process_reorder_tonemarks (head)
+  local curr = head
+  while curr do
+    local id = curr.id
+    if id == glyphid and option_in_font(curr.font, "script") == "hang" then
+      local fontdata = get_font_data(curr.font)
+      local uni = my_node_props(curr).unicode or curr.char
+      if is_hangul(uni) or is_chosong(uni) or uni == 0x25CC then
 
-local uline_start_id = luatexbase.new_whatsit("luako_uline_start")
-local uline_stop_id  = luatexbase.new_whatsit("luako_uline_stop")
-local function uline_boundary (num, list)
-  local n = d_nodenew(whatsitnode, user_whatsit)
-  d_setfield(n, "user_id", list and uline_start_id or uline_stop_id)
-  d_setfield(n, "type", 110) -- node list
-  d_setfield(n, "value", n_copy_list(list))
-  d_set_attribute(n, luakoulineattr, num)
-  d_nodewrite(n)
-end
-luatexko.uline_boundary = uline_boundary
+        local syllable = { [0]=curr, uni }
 
-local skippable = {
-  [gluenode   ] = true,
-  [penaltynode] = true,
-  [kernnode   ] = true,
-  [whatsitnode] = true,
-}
-local function draw_underline(head,curr,glueset,gluesign,glueorder,ubox,start)
-  start = start or head
-  while start do
-    if not skippable[d_getid(start)] then break end
-    start = d_getnext(start)
-  end
-  while curr do
-    local p = d_getprev(curr)
-    if not skippable[d_getid(p)] then break end
-    curr = p
-  end
-  local width = d_nodedimensions(glueset,gluesign,glueorder,start,curr)
-  if width and width > 0 then
-    local glue = d_get_gluenode(width)
-    d_setsubtype(glue, 101) -- cleaders
-    d_setleader(glue, d_copy_node(ubox))
-    head = d_insert_before(head, start, glue)
-    head = d_insert_before(head, start, d_get_kernnode(-width))
-  end
-  return head
-end
+        local n = getnext(curr)
+        while n do
+          local nid = n.id
+          if nid == glyphid then
+            local u = my_node_props(n).unicode or n.char
+            if   is_jungsong(u)
+              or is_jongsong(u)
+              or hangul_tonemark[u] then
+              tableinsert(syllable, u)
+              curr, uni = n, u
+            else
+              break
+            end
+          elseif nid ~= kernid or n.subtype == userkern then
+            break
+          end
+          n = getnext(n)
+        end
 
-local function after_linebreak_underline(head,glueorder,glueset,gluesign,ulstart,level)
-  ulstart, level = ulstart or {}, level or 0
+        if #syllable > 1
+          and hangul_tonemark[uni]
+          and get_tonemark_width(curr, uni) ~= 0 then
 
-  for curr in d_traverse(head) do
-    local currid = d_getid(curr)
-    if currid == hlistnode then
-      local newhead
-      newhead, ulstart = after_linebreak_underline(
-        d_getlist(curr),
-        d_getfield(curr,"glue_order"),
-        d_getfield(curr,"glue_set"),
-        d_getfield(curr,"glue_sign"),
-        ulstart,
-        level+1)
-      d_setlist(curr, newhead)
-    elseif currid == whatsitnode and d_getsubtype(curr) == user_whatsit then
-      local attr = d_has_attribute(curr, luakoulineattr)
-      local whatsit_type = d_getfield(curr, "type")
-      if attr and whatsit_type == 110 then
-        local user_id = d_getfield(curr, "user_id")
-        if user_id == uline_start_id then
-          local list = d_getfield(curr, "value")
-          ulstart[attr] = { start = curr, list = list, level = level }
-        elseif user_id == uline_stop_id and ulstart[attr] then
-          local ubox, start = ulstart[attr].list, ulstart[attr].start
-          head = draw_underline(head,curr,glueset,gluesign,glueorder,ubox,start)
-          ulstart[attr] = nil
+          local ini, fin = syllable[0], curr
+          local actual = pdfliteral_direct_actual(syllable)
+          local endactual = pdfliteral_direct_actual()
+          head = insert_before(head, ini, actual)
+          head, curr = insert_after(head, curr, endactual)
+
+          head = noderemove(head, fin)
+          head = insert_before(head, ini, fin)
         end
-        d_unset_attribute(curr,luakoulineattr)
-      end
-    end
-  end
 
-  local curr = d_nodetail(head)
-  for i, v in pairs(ulstart) do
-    local ubox, start = v.list, v.start
-    if ubox and level == v.level then
-      head = draw_underline(head,curr,glueset,gluesign,glueorder,ubox,start)
-      ulstart[i].start = nil
-    end
-  end
+      elseif hangul_tonemark[uni] -- isolated tone mark
+        and char_in_font(fontdata, 0x25CC) then
 
-  return head, ulstart
-end
+        local dotcircle = nodecopy(curr)
+        dotcircle.char = 0x25CC
+        if get_tonemark_width(curr, uni) ~= 0 then
+          local actual = pdfliteral_direct_actual{ [0]=curr, uni }
+          local endactual = pdfliteral_direct_actual()
+          head = insert_before(head, curr, actual)
+          head, curr = insert_after(head, curr, dotcircle)
+          head, curr = insert_after(head, curr, endactual)
+        else
+          head = insert_before(head, curr, dotcircle)
+        end
+      end
+    elseif id == whatsitid
+      and curr.mode == directmode
+      and my_node_props(curr).startactualtext then
 
------------------------------------
--- add to callback : post-linebreak
------------------------------------
-add_to_callback('vpack_filter', function(head)
-  head = d_todirect(head)
-  if texcount["luakodotemphcnt"]>0 then head = after_linebreak_dotemph(head) end
-  if texcount["luakorubyattrcnt"]>0 then after_linebreak_ruby(head) end
-  if texcount["luakoulineboxcnt"]>0 then head = after_linebreak_underline(head) end
-  return d_tonode(head)
-end, 'luatexko.vpack_filter')
+      curr = goto_end_actualtext(curr)
 
-add_to_callback('hpack_filter', function(head, groupcode)
-  if groupcode == "align_set" then
-    head = d_todirect(head)
-    if texcount["luakodotemphcnt"]>0 then head = after_linebreak_dotemph(head) end
-    if texcount["luakorubyattrcnt"]>0 then after_linebreak_ruby(head) end
-    if texcount["luakoulineboxcnt"]>0 then head = after_linebreak_underline(head) end
-    head = d_tonode(head)
+    elseif id == mathid then
+      curr = end_of_math(curr)
+    end
+    curr = getnext(curr)
   end
   return head
-end, 'luatexko.hpack_filter.postlinebreak')
+end
 
+-- vertical font
 
-------------------------------------
--- vertical typesetting: EXPERIMENTAL
-------------------------------------
 local streamreader = utilities.files
 local openfile     = streamreader.open
 local closefile    = streamreader.close
@@ -1919,7 +1324,7 @@
 local readshort    = streamreader.readinteger2
 local setpos       = streamreader.setposition
 
-local function getotftables (f, subfont)
+local function get_otf_tables (f, subfont)
   if f then
     local sfntversion = readstring(f,4)
     if sfntversion == "ttcf" then
@@ -1953,7 +1358,7 @@
   end
 end
 
-local function readmaxp (f, t)
+local function read_maxp (f, t)
   if f and t then
     setpos(f, t.offset)
     return {
@@ -1963,7 +1368,7 @@
   end
 end
 
-local function readvhea (f, t)
+local function read_vhea (f, t)
   if f and t then
     setpos(f, t.offset)
     return {
@@ -1988,7 +1393,7 @@
   end
 end
 
-local function readvmtx (f, t, numofheights, numofglyphs)
+local function read_vmtx (f, t, numofheights, numofglyphs)
   if f and t and numofheights and numofglyphs then
     setpos(f, t.offset)
     local vmtx = {}
@@ -2010,85 +1415,82 @@
   end
 end
 
-local tsbtable
+local tsb_font_data = {}
 
 local function get_tsb_table (filename, subfont)
   subfont = tonumber(subfont) or 1
   local key = stringformat("%s::%s", filename, subfont)
-  if tsbtable[key] then
-    return tsbtable[key]
+  if tsb_font_data[key] then
+    return tsb_font_data[key]
   end
   local f = openfile(filename, true) -- true: zero-based
   if f then
     local vmtx
-    local tables = getotftables(f, subfont)
+    local tables = get_otf_tables(f, subfont)
     if tables then
-      local vhea = readvhea(f, tables.vhea)
+      local vhea = read_vhea(f, tables.vhea)
       local numofheights = vhea and vhea.numheights
-      local maxp = readmaxp(f, tables.maxp)
+      local maxp = read_maxp(f, tables.maxp)
       local numofglyphs = maxp and maxp.numglyphs
-      vmtx = readvmtx(f, tables.vmtx, numofheights, numofglyphs)
+      vmtx = read_vmtx(f, tables.vmtx, numofheights, numofglyphs)
     end
     closefile(f)
-    tsbtable[key] = vmtx
+    tsb_font_data[key] = vmtx
     return vmtx
   end
 end
 
-local function cjk_vertical_font (vf)
-  if not vf.shared then return end
-  if not vf.shared.features then return end
-  if not vf.shared.features["vertical"] then return end
-  if vf.type == "virtual" then return end
-
-  local subfont  = vf.specification and vf.specification.sub
-  local tsbtable = get_tsb_table(vf.filename, subfont)
-  if not tsbtable then
-    warn("%s does not have vertical metric info", vf.fontname)
+local function process_vertical_font (fontdata)
+  local subfont = fontdata.specification and fontdata.specification.sub
+  local tsb_tab = get_tsb_table(fontdata.filename, subfont)
+  if not tsb_tab then
+    warning("Vertical metrics table (vmtx) not found in the font\n"..
+    "`%s'", fontdata.fontname)
     return
   end
 
-  local params    = vf.parameters   or {}
-  local shared    = vf.shared       or {}
-  local quad      = params.quad     or 655360
-  local ascender  = params.ascender or quad*0.8
-  local descender = params.descender or quad*0.2
-  local factor    = params.factor   or 655.36
-  local xheight   = params.x_height or quad/2
-  local goffset   = xheight/2 - quad/2
-  local descriptions = shared.rawdata and shared.rawdata.descriptions
-  local spbp = 65536*(7227/7200)
-  for i,v in pairs(vf.characters) do
-    local dsc     = descriptions[i]
-    local gl      = v.index
-    local tsb_gl  = tsbtable and tsbtable[gl]
-    local vw      = tsb_gl and tsb_gl.ht; vw = vw and vw * factor or quad
-    local tsb     = tsb_gl and tsb_gl.tsb
-    local bb4     = dsc and dsc.boundingbox and dsc.boundingbox[4]
-    local asc     = bb4 and tsb and (bb4+tsb)*factor or ascender
-    local hw      = v.width or quad
-    local offset  = hw/2 + goffset
-    local vh      = hw > 0 and hw or 0
-    asc = asc/spbp; offset = offset/spbp
+  local shared       = fontdata.shared or {}
+  local descriptions = shared.rawdata and shared.rawdata.descriptions or {}
+  local parameters   = fontdata.parameters or {}
+  local scale    = parameters.factor or 655.36
+  local quad     = parameters.quad or 655360
+  local ascender = parameters.ascender or quad*0.8
+  local goffset  = fontdata_opt_dim(fontdata, "charraise")
+  if not goffset then
+    goffset  = (parameters.x_height or quad/2) / 2
+  end
+  for i,v in pairs(fontdata.characters) do
+    local voff = goffset - (v.width or 0)/2
+    local bbox = descriptions[i] and descriptions[i].boundingbox or {0,0,0,0}
+    local gid  = v.index
+    local tsb  = tsb_tab[gid].tsb
+    local hoff = tsb and (bbox[4] + tsb) * scale or ascender
     v.commands = {
-      {'special', stringformat('pdf:q 0 1 -1 0 %.3f %.3f cm', asc, -offset)},
-      {'push'},
-      {'char',    i},
-      {'pop'},
-      {'special', 'pdf:Q'},
+      { "down", -voff },
+      { "right", hoff },
+      { "pdf", "q 0 1 -1 0 0 0 cm" },
+      { "push" },
+      { "char", i },
+      { "pop" },
+      { "pdf", "Q" },
     }
-    v.width   = vw
-    v.height  = vh*(ascender/quad)
-    v.depth   = vh*(descender/quad)
-    v.italic  = nil
+    local vw = tsb_tab[gid].ht
+    v.width  = vw and vw * scale or quad
+    local ht = bbox[3] * scale + voff
+    local dp = bbox[1] * scale + voff
+    v.height = ht > 0 and  ht or nil
+    v.depth  = dp < 0 and -dp or nil
   end
-  --- vertical gpos
-  local res = vf.resources or {}
-  if res.verticalgposhack then
-    return -- avoid multiple running
+  local spacechar = char_in_font(fontdata, 32)
+  if spacechar then
+    parameters.space         = spacechar.width
+    parameters.space_stretch = spacechar.width/2
+    parameters.space_shrink  = spacechar.width/2
   end
+
+  local res = fontdata.resources or {}
   local fea = shared.features or {}
-  fea.kern = nil  -- only for horizontal typesetting
+  fea.kern = nil  -- only for horizontal writing
   fea.vert = true -- should be activated by default
   local seq = res.sequences or {}
   for _,v in ipairs(seq) do
@@ -2095,11 +1497,11 @@
     local fea = v.features or {}
     if fea.vhal or fea.vkrn or fea.valt or fea.vpal or fea.vert then
       if v.type == "gpos_single" then
-        for _,vv in pairs(v.steps or{}) do
-          local cover = vv.coverage or {}
-          for iii,vvv in pairs(cover) do
+        for _,vv in pairs(v.steps or {}) do
+          for _,vvv in pairs(vv.coverage or {}) do
             if type(vvv) == "table" and #vvv == 4 then
-              cover[iii] = { -vvv[2], vvv[1], vvv[4], vvv[3], 0 }
+              vvv[1], vvv[2], vvv[3], vvv[4], vvv[5] =
+              -vvv[2], vvv[1], vvv[4], vvv[3], 0 -- last 0 to avoid multiple run
             end
           end
         end
@@ -2107,10 +1509,10 @@
         for _,vv in pairs(v.steps or {}) do
           for _,vvv in pairs(vv.coverage or {}) do
             for _,vvvv in pairs(vvv) do
-              for iiiii,vvvvv in pairs(vvvv) do
+              for _,vvvvv in pairs(vvvv) do
                 if type(vvvvv) == "table" and #vvvvv == 4 then
-                  -- last 0 to avoid multiple round
-                  vvvv[iiiii] = { -vvvvv[2], vvvvv[1], vvvvv[4], vvvvv[3], 0 }
+                  vvvvv[1], vvvvv[2], vvvvv[3], vvvvv[4], vvvvv[5] =
+                  -vvvvv[2], vvvvv[1], vvvvv[4], vvvvv[3], 0
                 end
               end
             end
@@ -2119,72 +1521,228 @@
       end
     end
   end
-  res.verticalgposhack = true
 end
 
-local otffeatures = fonts.constructors.newfeatures("otf")
-otffeatures.register {
-  name         = "vertical",
-  description  = "vertical typesetting",
-  initializers = {
-    node = function (tfmdata,value)
-      if value and not tsbtable then
-        tsbtable  = {}
-        add_to_callback("luaotfload.patch_font",cjk_vertical_font,"luatexko.vertical_virtual_font")
+-- charraise
+
+local function process_charriase_font (fontdata)
+  local raise = fontdata_opt_dim(fontdata, "charraise")
+  if raise then
+    local shared = fontdata.shared or {}
+    local descriptions = shared.rawdata and shared.rawdata.descriptions or {}
+    local scale = fontdata.parameters.factor or 655.36
+    for i, v in pairs( fontdata.characters ) do
+      v.commands = {
+        {"down", -raise },
+        {"char", i},
+      }
+      local bbox = descriptions[i] and descriptions[i].boundingbox or {0,0,0,0}
+      local ht = bbox[4] * scale + raise
+      local dp = bbox[2] * scale + raise
+      v.height = ht > 0 and  ht or nil
+      v.depth  = dp < 0 and -dp or nil
+    end
+  end
+end
+
+-- fake italic correctioin
+
+local function process_fake_slant_corr (head) -- for font fallback
+  local curr = head
+  while curr do
+    local id = curr.id
+    if id == kernid then
+      if curr.subtype == italcorr and curr.kern == 0 then
+        local p = getprev(curr)
+        while p do -- skip jungsong/jongsong
+          if p.id == glyphid and p.width < get_en_size(p.font) then
+            local c = my_node_props(p).unicode or p.char
+            if not is_jungsong(c) and not is_jongsong(c) then
+              break
+            end
+          elseif p.id == whatsitid
+            and p.mode == directmode
+            and my_node_props(p).endactualtext then -- skip
+          else
+            break
+          end
+          p = getprev(p)
+        end
+        if p.id == glyphid then
+          local fontdata = get_font_data(p.font)
+          if fontdata.slant and fontdata.slant > 0 then
+            local italic = char_in_font(fontdata, p.char).italic
+            if italic then
+              curr.kern = italic
+            end
+          end
+        end
       end
+    elseif id == mathid then
+      curr = end_of_math(curr)
     end
-  }
-}
+    curr = getnext(curr)
+  end
+  return head
+end
 
-------------------------------------
--- no embedding
-------------------------------------
-local function dontembedthisfont (tfmdata, value)
-  if value == "no" then
-    fonts.constructors.dontembed[tfmdata.properties.filename] = 1
+local function process_fake_slant_font (fontdata)
+  local fsl = fontdata.slant
+  if fsl and fsl > 0 then
+    fsl = fsl/1000
+    local params = fontdata.parameters or {}
+    params.slant = (params.slant or 0) + fsl*65536 -- slant per point
+    local scale  = params.factor or 655.36
+    local shared = fontdata.shared or {}
+    local descriptions = shared.rawdata and shared.rawdata.descriptions or {}
+    for i, v in pairs(fontdata.characters) do
+      local bbox = descriptions[i] and descriptions[i].boundingbox or {0,0,0,0}
+      local italic = (v.height or 0) * fsl - (v.width or 0) + bbox[3]*scale
+      if italic > 0 then
+        v.italic = italic
+      end
+    end
   end
 end
 
-otffeatures.register {
-  name        = "embedding",
-  description = "dont embed this font",
-  initializers = {
-    base = dontembedthisfont,
-    node = dontembedthisfont,
-  }
+-- wrap up
+
+local pass_fun = function(...) return ... end
+create_callback("luatexko_pre_hpack", "data", pass_fun)
+create_callback("luatexko_pre_prelinebreak", "data", pass_fun)
+create_callback("luatexko_post_hpack", "data", pass_fun)
+create_callback("luatexko_post_prelinebreak", "data", pass_fun)
+
+add_to_callback("hpack_filter", function(h)
+  h = process_fonts(h)
+  h = call_callback("luatexko_pre_hpack", h)
+  h = call_callback("luatexko_post_hpack", h)
+  return process_linebreak(h)
+end, "luatexko.hpack_filter.pre_rendering", 1)
+
+add_to_callback("pre_linebreak_filter", function(h)
+  h = process_fonts(h)
+  h = call_callback("luatexko_pre_prelinebreak", h, true)
+  h = call_callback("luatexko_post_prelinebreak", h, true)
+  return process_linebreak(h, true)
+end, "luatexko.pre_linebreak_filter.pre_rendering", 1)
+
+local font_opt_procs = {
+  removeclassicspaces = {
+    luatexko_pre_hpack        = process_remove_spaces,
+    luatexko_pre_prelinebreak = process_remove_spaces,
+  },
+  interhangul = {
+    luatexko_post_hpack        = process_interhangul,
+    luatexko_post_prelinebreak = process_interhangul,
+  },
+  interlatincjk = {
+    luatexko_post_hpack        = process_interlatincjk,
+    luatexko_post_prelinebreak = process_interlatincjk,
+  },
+  compresspunctuations = {
+    hpack_filter         = process_glyph_width,
+    pre_linebreak_filter = process_glyph_width,
+  },
+  slant = {
+    hpack_filter         = process_fake_slant_corr,
+    pre_linebreak_filter = process_fake_slant_corr,
+  },
 }
 
-------------------------------------
--- italic correction for fake-slant font
-------------------------------------
-local function fakeslant_itlc (tfmdata)
-  local slfactor = tfmdata.parameters.slantfactor
-  if slfactor and slfactor > 0 then else return end
-  tfmdata.parameters.slant = slfactor * 65536
-  local chrs = tfmdata.characters
-  for i,v in pairs(chrs) do
-    local italic = v.height * slfactor
-    if italic > 0 then
-      chrs[i].italic = italic
+local function process_patch_font (fontdata)
+  for name, procs in pairs( font_opt_procs ) do
+    if not active_processes[name] and option_in_font(fontdata, name) then
+      for cbnam, cbfun in pairs( procs ) do
+        add_to_callback(cbnam, cbfun, "luatexko."..cbnam.."."..name)
+      end
+      active_processes[name] = true
     end
   end
+
+  if not active_processes.expansion
+    and option_in_font(fontdata, "expansion") then
+    texset("global", "adjustspacing", 2)
+    active_processes.expansion = true
+  end
+
+  if option_in_font(fontdata, "protrusion") then
+    if not active_processes.protrusion then
+      texset("global", "protrudechars", 2)
+      active_processes.protrusion = true
+    end
+    if not active_processes[fontdata.fontname] and
+      option_in_font(fontdata, "compresspunctuations") then
+      warning("Both `compresspunctuations' and `protrusion' are\n"..
+      "enabled for the font `%s'.\n"..
+      "Beware that this could result in bad justifications.\n",
+      fontdata.fontname)
+      active_processes[fontdata.fontname] = true
+    end
+  end
+
+  if option_in_font(fontdata, "vertical") then
+    process_vertical_font(fontdata)
+  elseif option_in_font(fontdata, "charraise") then
+    process_charriase_font(fontdata)
+  end
+
+  if option_in_font(fontdata, "slant") then
+    process_fake_slant_font(fontdata)
+  end
 end
 
-add_to_callback("luaotfload.patch_font", fakeslant_itlc, "luatexko.fakeslant_itlc")
+add_to_callback("luaotfload.patch_font", process_patch_font,
+"luatexko.patch_font")
 
-------------------------------------
--- Actual Text
-------------------------------------
-local function actualtext (str)
-  local t = {}
-  for uni in string.utfvalues(str) do
-    if uni < 0x10000 then
-      t[#t+1] = stringformat("%04X",uni)
-    else -- surrogate
-      uni = uni - 0x10000
-      t[#t+1] = stringformat("%04X%04X", uni/0x400+0xD800, uni%0x400+0xDC00)
+local auxiliary_procs = {
+  dotemph = {
+    hpack_filter = process_dotemph,
+    vpack_filter = process_dotemph,
+  },
+  uline   = {
+    hpack_filter = process_uline,
+    vpack_filter = process_uline,
+  },
+  ruby    = {
+    pre_linebreak_filter = process_ruby_pre_linebreak,
+    hpack_filter         = process_ruby_post_linebreak,
+    vpack_filter         = process_ruby_post_linebreak,
+  },
+  autojosa = {
+    luatexko_pre_hpack        = process_josa,
+    luatexko_pre_prelinebreak = process_josa,
+  },
+  reorderTM = {
+    luatexko_pre_hpack        = process_reorder_tonemarks,
+    luatexko_pre_prelinebreak = process_reorder_tonemarks,
+  },
+}
+
+local function activate (name)
+  for cbnam, cbfun in pairs( auxiliary_procs[name] ) do
+    local fun
+    if cbnam == "hpack_filter" then
+      fun = function(h, gc)
+        if gc == "align_set" then
+          h = cbfun(h)
+        end
+        return h
+      end
+    else
+      fun = function(h)
+        h = cbfun(h)
+        return h
+      end
     end
+    add_to_callback(cbnam, fun, "luatexko."..cbnam.."."..name)
   end
-  tex.sprint(stringformat("<FEFF%s>", table.concat(t)))
 end
-luatexko.actualtext = actualtext
+luatexko.activate = activate
+
+-- default hangul font
+
+local function current_has_hangul_chars (cnt)
+  texcount[cnt] = char_in_font(fontcurrent(), 0xAC00) and 1 or 0
+end
+luatexko.currenthashangulchars = current_has_hangul_chars

Modified: trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko.sty
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko.sty	2019-05-01 23:38:43 UTC (rev 50923)
+++ trunk/Master/texmf-dist/tex/luatex/luatexko/luatexko.sty	2019-05-02 21:10:43 UTC (rev 50924)
@@ -1,7 +1,7 @@
 % luatexko.sty
 %
-% Copyright (c) 2013-2019 Dohyun Kim  <nomos at ktug org>
-%                         Soojin Nam  <jsunam at gmail com>
+% Copyright (c) 2013-2019 Dohyun Kim <nomos at ktug org>
+%                         Soojin Nam <jsunam at gmail com>
 %
 % This work may be distributed and/or modified under the
 % conditions of the LaTeX Project Public License, either version 1.3c
@@ -11,450 +11,707 @@
 % and version 1.3c or later is part of all distributions of LaTeX
 % version 2006/05/20 or later.
 
-\ifx\luatexkocatcodeofatchar\undefined\else\endinput\fi % no multiple loading
-
-\begingroup\expandafter\expandafter\expandafter\endgroup
-\expandafter\ifx\csname selectfont\endcsname\relax % plain tex
-  \edef\luatexkocatcodeofatchar{\catcode`@=\the\catcode`@}
-  \input luatexko-core.sty
-  %\hangulfont{name:NanumGothic:interlatincjk=0.125em} at 10pt
-  %\setmathhangulfont{NanumGothic}
-  \luatexkocatcodeofatchar
-  \expandafter\endinput
+\ifdefined\luatexkohangulfontattr \endinput\fi
+\ifdefined\selectfont
+  \ProvidesPackage{luatexko}[2019/05/01 v2.0 typesetting Korean with LuaTeX]
+  \RequirePackage{luatexbase}
+  \RequirePackage{fontspec}
+\else
+  \input luatexbase.sty
+  \input luaotfload.sty
+  \chardef\luatexkoatcatcode=\catcode`\@
+  \catcode`\@=11
 \fi
-
-\ProvidesPackage{luatexko}[2019/04/06 v1.24 Typesetting Korean with LuaLaTeX]
-
-\ifdefined\adjustspacing\else % luatex < 0.87
-  \let\adjustspacing\pdfadjustspacing
-  \let\protrudechars\pdfprotrudechars
+\protected\def\pdfliteral{\pdfextension literal}
+\newattribute\luatexkohangulfontattr
+\newattribute\luatexkohanjafontattr
+\newattribute\luatexkofallbackfontattr
+\newattribute\luatexkoautojosaattr
+\newattribute\luatexkoclassicattr
+\newattribute\luatexkodotemphattr
+\newattribute\luatexkorubyattr \chardef\luatexkorubyalloc\allocationnumber
+\directlua{ require"luatexko.lua" }
+% classic
+\protected\def\typesetclassic{\luatexkoclassicattr\z@\parindent1em }
+\protected\def\typesetvertical{\luatexkoclassicattr\@ne\parindent1em }
+\protected\def\typesetmodern{\unsetattribute\luatexkoclassicattr}
+\protected\def\inhibitglue{\hskip\z at skip}
+\protected\def\Schinese{\luatexkoclassicattr\tw@\parindent2em }
+\protected\def\Tchinese{\luatexkoclassicattr\thr@@\parindent2em } % +halt
+\let\korean\typesetmodern \let\japanese\typesetclassic \let\chinese\Schinese
+% josa
+\count@"AC00
+\loop
+  \catcode\count@ 12
+  \ifnum\count@<"D7A3 \advance\count@\@ne \repeat
+\newcount\josaignoreparens
+\def\luatexkojosaactivate{%
+  \ifx\luatexkojosaactive\relax
+  \else
+    \directlua{ luatexko.activate("autojosa") }%
+    \global\let\luatexkojosaactive\relax
+  \fi
+  \luatexkoautojosaattr\josaignoreparens }
+\protected\def\은{\begingroup\luatexkojosaactivate 은\endgroup}
+\let\는\은
+\protected\def\을{\begingroup\luatexkojosaactivate 을\endgroup}
+\let\를\을
+\protected\def\와{\begingroup\luatexkojosaactivate 와\endgroup}
+\let\과\와
+\protected\def\가{\begingroup\luatexkojosaactivate 가\endgroup}
+\protected\def\이{\begingroup\luatexkojosaactivate 이\endgroup}
+\protected\def\라{\이라}
+\protected\def\으{\begingroup\luatexkojosaactivate 으\endgroup}
+\protected\def\로{\으로}
+% force hangul font
+\def\luatexkotoks at appendchars#1,{%
+  \ifx\empty#1\empty
+    \expandafter\luatexkotoks at appendchars
+  \else
+    \ifx*#1\else
+      \expandafter\ifx\expandafter\empty\the\toks@\empty
+        \etoksapp0{\number#1}%
+      \else
+        \etoksapp0{,\number#1}%
+      \fi
+      \expandafter\expandafter\expandafter\luatexkotoks at appendchars
+    \fi
+  \fi }
+\def\luatexkodoluacode#1{\directlua{
+  \ifhmode luatexko.updateforcehangul(function() \fi
+  #1
+  \ifhmode end) \fi }}
+\protected\def\registerpunctuations#1{%
+  \toks@{}\luatexkotoks at appendchars#1,*,\luatexkodoluacode{
+    local t = luatexko.forcehangulchars
+    for _,v in ipairs{\the\toks@} do t[v]=true end }}
+\let\registerhangulpunctuations\registerpunctuations
+\protected\def\unregisterpunctuations#1{%
+  \toks@{}\luatexkotoks at appendchars#1,*,\luatexkodoluacode{
+    local t = luatexko.forcehangulchars
+    for _,v in ipairs{\the\toks@} do t[v]=nil end }}
+\let\unregisterhangulpunctuations\unregisterpunctuations
+\protected\def\hangulpunctuations{%
+  \afterassignment\luatexkohangulpunctuations\count@ }
+\def\luatexkohangulpunctuations{\luatexkodoluacode{
+  local t = luatexko.forcehangulchars
+  for i in pairs(t) do t[i]=\ifcase\count@ false \else true \fi end }}
+\protected\def\registerbreakableafter#1{%
+  \toks@{}\luatexkotoks at appendchars#1,*,\luatexkodoluacode{
+    local t = luatexko.breakableafter
+    for _,v in ipairs{\the\toks@} do t[v]=true end }}
+\protected\def\registerbreakablebefore#1{%
+  \toks@{}\luatexkotoks at appendchars#1,*,\luatexkodoluacode{
+    local t = luatexko.breakablebefore
+    for _,v in ipairs{\the\toks@} do t[v]=true end }}
+\protected\def\hangulbyhangulfont{%
+  \afterassignment\luatexkohangulbyhangulfont\count@ }
+\def\luatexkohangulbyhangulfont{\luatexkodoluacode{
+  luatexko.hangulbyhangulfont=\ifcase\count@ false \else true \fi }}
+\protected\def\hanjabyhanjafont{%
+  \afterassignment\luatexkohanjabyhanjafont\count@ }
+\def\luatexkohanjabyhanjafont{\luatexkodoluacode{
+  luatexko.hanjabyhanjafont=\ifcase\count@ false \else true \fi }}
+% dotemph
+\newcount\luatexkodotemphcount
+\def\dotemphraise{1ex }
+\unless\ifdefined\bfseries\let\bfseries\bf\fi
+\def\dotemphchar{\bfseries\char"02D9 }
+\protected\def\dotemph#1{%
+  \global\advance\luatexkodotemphcount\@ne
+  \begingroup
+  \setbox\z@\hbox{\raise\dotemphraise\hbox{\dotemphchar}}%
+  \directlua{
+    if \the\luatexkodotemphcount == 1 then luatexko.activate("dotemph") end
+    luatexko.dotemphbox[\the\luatexkodotemphcount] = node.copy(tex.box[0].list)
+  }\luatexkodotemphattr\luatexkodotemphcount #1%
+  \endgroup }
+% uline
+\newcount\luatexkoulinecount
+\def\ulinedown{0.5ex }\def\ulinewidth{0.04em }
+\protected\def\markoverwith#1#2{%
+  \global\advance\luatexkoulinecount\@ne
+  \begingroup
+  \count@\luatexkoulinecount
+  \ifx\luatexkoulinerunning\relax
+    \edef\ulinedown{\the\dimexpr\ulinedown+\ulinewidth+.15ex\relax}\fi
+  \let\luatexkoulinerunning\relax
+  \leavevmode
+  \setbox\z@\hbox{#1}%
+  \directlua{
+    if \the\luatexkoulinecount == 1 then luatexko.activate("uline") end
+    luatexko.ulboundary(\the\count@, tex.box[0].list)
+  }#2\directlua{
+    luatexko.ulboundary(\the\count@)
+  }\endgroup }
+\protected\def\uline{\markoverwith{%
+  \vrule width\z@ height-\ulinedown depth\dimexpr\ulinedown+\ulinewidth\relax }}
+\protected\def\dashuline{\markoverwith{%
+  \hbox{\kern.125em
+    \vrule width.3em height-\ulinedown depth\dimexpr\ulinedown+\ulinewidth\relax
+    \kern.125em }}}
+\protected\def\dotuline{\markoverwith{%
+  \lower\dimexpr\ulinedown+.1ex\relax\hbox{\kern.07em .\kern.07em }}}
+\protected\def\uuline#1{\uline{\uline{#1}}}
+\protected\def\sout#1{\begingroup
+  \def\ulinedown{-.5\dimexpr\ulinewidth+1ex\relax}%
+  \uline{#1}\endgroup}
+\protected\def\xout{\markoverwith{\hbox to.4em{\hss/\hss}}}
+\protected\def\uwave{\font\luatexkofontsixly=lasy6\relax
+  \markoverwith{\lower4\p@\hbox{\luatexkofontsixly\char58}}}
+% ruby
+\def\rubysize{0.6}  % size of ruby compared to base chars
+\def\rubysep{0.2ex} % distance between base chars and ruby
+\def\luatexkostretchfactor{.0125} % .5em/20/2 is hard coded.
+\newif\ifruby at overlap
+\let\rubyoverlap\ruby at overlaptrue
+\let\rubynooverlap\ruby at overlapfalse
+\rubyoverlap % ruby may hangover neighboring chars
+\newcount\luatexkorubycount
+\def\luatexkorubybasehbox{\hbox attr\luatexkorubyalloc=\luatexkorubycount}
+\protected\def\ruby#1#2{%
+  \global\advance\luatexkorubycount\@ne
+  \begingroup
+  \leavevmode
+  \setbox\z@\luatexkorubybasehbox{% base
+    \vrule width\z@ height 2ex depth\z@ #1}%
+  \setbox\tw@\hbox{% ruby
+    \ifdefined\rubyfont \rubyfont \fi
+    \ifdefined\selectfont % <- latex
+      \dimen@\dimexpr\rubysize em\relax
+      \fontsize\dimen@\dimen@\selectfont
+    \fi
+    #2}%
+  \hbadness\@M % supress underfull warning
+  \dimen at ii\dimexpr\rubysize em\relax % ruby font size
+  \ifdim\wd\z@ < \wd\tw@ % ruby is wider
+    \ifruby at overlap
+      \dimen@\dimexpr\wd\tw at -\wd\z at -\dimen at ii\relax % overhang .5 rubysize
+      \ifdim\dimen@ > \z@
+        \setbox\z@\luatexkorubybasehbox spread\dimen@{%
+          \hskip\z@ plus \luatexkostretchfactor em\relax
+          \unhbox\z@
+          \hskip\z@ plus \luatexkostretchfactor em\relax }%
+      \fi
+    \else
+      \setbox\z@\luatexkorubybasehbox to\wd\tw@{%
+        \hskip\z@ plus \luatexkostretchfactor em\relax
+        \unhbox\z@
+        \hskip\z@ plus \luatexkostretchfactor em\relax }%
+    \fi
+  \else
+    \ifdim\rubysize\wd\z@ > 2\wd\tw@ % ruby is far shorter
+      \setbox\tw@\hbox to\wd\z@{%
+        \hskip\dimen at ii\relax
+        \unhbox\tw@
+        \hskip\dimen at ii\relax }%
+    \else
+      \setbox\tw@\hbox to\wd\z@{%
+        \hskip\z@ plus \luatexkostretchfactor \dimen at ii\relax
+        \unhbox\tw@
+        \hskip\z@ plus \luatexkostretchfactor \dimen at ii\relax }%
+    \fi
+  \fi
+  \directlua{
+    if \the\luatexkorubycount == 1 then luatexko.activate("ruby") end
+    luatexko.rubybox[\the\luatexkorubycount] = {
+      node.copy(tex.box[2]),
+      \number\numexpr\dimexpr\rubysep\relax\relax,
+      \ifruby at overlap true\else false\fi, }}%
+  \box\z@
+  \endgroup }
+\protected\def\xxruby#1#2{%
+  \begingroup\def\basestr{#1}\def\rubystr{#2}\luatexkoxxruby\endgroup }
+\def\luatexkoxxruby{%
+  \ifx\empty\basestr\else
+    \expandafter\luatexkogetrubybasechar\basestr\end
+    \expandafter\luatexkogetrubyrubychar\rubystr\end
+    \ruby\basechar\rubychar
+    \expandafter\luatexkoxxruby
+  \fi }
+\def\luatexkogetrubybasechar#1#2\end{\def\basechar{#1}\def\basestr{#2}}
+\def\luatexkogetrubyrubychar#1#2\end{\def\rubychar{#1}\def\rubystr{#2}}
+% vertical writing
+\protected\def\vertical#1{%
+  \leavevmode
+  \setbox\z@\vbox\bgroup
+    \hsize#1\relax
+    \typesetvertical }
+\protected\def\endvertical{%
+  \egroup
+  \luatexkorotatebox\z@
+  \box\z@ }
+\protected\def\luatexkorotatebox#1{%
+  \setbox#1\hbox to\dimexpr\ht#1+\dp#1{%
+    \hfil
+    \vbox to\wd#1{%
+      \wd#1\z@ \ht#1\z@ \dp#1\z@
+      \pdfliteral{q 0 -1 1 0 0 0 cm}\box#1\pdfliteral{Q}%
+      \vfil }}}
+% horizontal box inside vertical writing
+\protected\def\horizontal#1{%
+  \leavevmode
+  \setbox\z@\vbox\bgroup
+    \hsize#1\relax
+    \typesetmodern }
+\protected\def\endhorizontal{%
+  \egroup
+  \luatexkounrotatebox\z@
+  \box\z@ }
+\protected\def\luatexkounrotatebox#1{%
+  \setbox#1\hbox to\dimexpr\ht#1+\dp#1{%
+    \vbox to\wd#1{%
+      \vfil
+      \wd#1\z@ \ht#1\z@ \dp#1\z@
+      \pdfliteral{q 0 1 -1 0 0 0 cm}\box#1\pdfliteral{Q}}%
+    \hfil }}
+% do not veticalize headline/footline
+\ifdefined\selectfont
+  \def\verticaltypesetting{%
+    \maxdepth\z@ \typesetvertical
+    \dimen@\textwidth \textwidth\textheight \textheight\dimen@
+    \edef\@outputpage{\unexpanded{\luatexkorotatebox\@outputbox
+    \textwidth\textheight}\unexpanded\expandafter{\@outputpage}}}
+\else
+  \def\verticaltypesetting{%
+    \dimen@\hsize \hsize\vsize \vsize\dimen@
+    \edef\plainoutput{\unexpanded{\dimen@\hsize \hsize\vsize \vsize\dimen@}%
+      \unexpanded\expandafter{\plainoutput}}%
+    \def\pagebody{\setbox\z@\vbox to\hsize{\boxmaxdepth=\maxdepth
+      \pagecontents}\luatexkorotatebox\z@\box\z@}%
+    \maxdepth\z@ \typesetvertical }
 \fi
-
-%% package options
-\newif\if at hangul
-\newif\if at hanja
-\newif\if at luatexko@loadfontspec\@luatexko at loadfontspectrue
-\DeclareOption{hangul}{\@hangultrue}
-\DeclareOption{hanja}{\@hangultrue\@hanjatrue}
-\DeclareOption{unfonts}{}
-\DeclareOption{nofontspec}{\@luatexko at loadfontspecfalse}
-\ProcessOptions\relax
-
-\if at luatexko@loadfontspec
-  \RequirePackage{fontspec}[2018/07/30]
-\fi
-
-\RequirePackage{luatexko-core}
-
-\if at luatexko@loadfontspec
-  %% fontspec Korean options
-  \newfontfeature{YetHangul}{script=hang}
-  \newfontfeature{InterHangul}{interhangul=#1}
-  \newfontfeature{InterLatinCJK}{interlatincjk=#1}
-  \newfontfeature{PunctRaise}{punctraise=#1}
-  \newfontfeature{CharRaise}{charraise=#1}
-  \newfontfeature{NoEmbed}{embedding=no}
-  \aliasfontfeature{NoEmbed}{NoEmbedding}
-  \newfontfeature{protrusion}{protrusion=#1}
-  \newfontfeature{expansion}{expansion=#1}
-  \ExplSyntaxOn
-  \DeclareDocumentCommand \newhangulfontfeature {mmmm}
+% hangul normalize
+\def\luatexhangulnormalize{%
+  \directlua{require"luatexko-normalize"}%
+  \afterassignment\luatexkohangulnormalize\count@}
+\def\luatexkohangulnormalize{%
+  \ifcase\count@ \directlua{ luatexkonormalize.unload() }% 0: none
+  \or \directlua{ luatexkonormalize.compose() }% 1: nfc
+  \else \directlua{ luatexkonormalize.decompose() }% 2: nfd
+  \fi }
+% convert uhc to utf8
+\def\luatexuhcinputencoding{%
+  \directlua{require"luatexko-uhc2utf8"}%
+  \afterassignment\luatexkouhcinputencoding\count@}
+\def\luatexkouhcinputencoding{%
+  \ifcase\count@ \directlua{ luatexkouhc2utf8.stopconvert() }%
+  \else \directlua{ luatexkouhc2utf8.startconvert() }%
+  \fi}
+% actualtext not provided
+\protected\def\actualtext#1#{\luatexkoactualtext}
+\def\luatexkoactualtext#1{#1}
+% math hangul
+\def\setmathhangulblock#1#2{%
+  \count@="#1
+  \loop
+    \Umathcode\count@ = 7 \symmathhangul\count@
+    \ifnum\count@<"#2 \advance\count@\@ne \repeat}
+% font fallback
+\newif\ifluatexkoselectfontrunning
+\def\luatexkohangulselectfont{%
+  \ifluatexkoselectfontrunning \else
+    \ifdefined\luatexkohangulfont
+      \begingroup
+      \luatexkoselectfontrunningtrue
+      \luatexkohangulfont
+      \expandafter\expandafter\expandafter\endgroup
+      \expandafter\luatexkohangulfontattr\fontid\font\relax
+    \fi
+  \fi }
+\def\luatexkohanjaselectfont{%
+  \ifluatexkoselectfontrunning \else
+    \ifdefined\luatexkohanjafont
+      \begingroup
+      \luatexkoselectfontrunningtrue
+      \luatexkohanjafont
+      \expandafter\expandafter\expandafter\endgroup
+      \expandafter\luatexkohanjafontattr\fontid\font\relax
+    \fi
+  \fi }
+\def\luatexkofallbackselectfont{%
+  \ifluatexkoselectfontrunning \else
+    \ifdefined\luatexkofallbackfont
+      \begingroup
+      \luatexkoselectfontrunningtrue
+      \luatexkofallbackfont
+      \expandafter\expandafter\expandafter\endgroup
+      \expandafter\luatexkofallbackfontattr\fontid\font\relax
+    \fi
+  \fi }
+% plain
+\ifdefined\selectfont\else
+  \protected\def\hangulfont{%
+    \afterassignment\luatexkohangulselectfont\font\luatexkohangulfont}
+  \protected\def\hanjafont{%
+    \afterassignment\luatexkohanjaselectfont\font\luatexkohanjafont}
+  \protected\def\fallbackfont{%
+    \afterassignment\luatexkofallbackselectfont\font\luatexkofallbackfont}
+  \newfam\symmathhangul
+  \protected\def\setmathhangulfonts#1#2#3{% font identifiers
+    \ifnum\Umathcharfam"AC00=\symmathhangul\else
+      \setmathhangulblock{AC00}{D7A3}%
+    \fi
+    \textfont\symmathhangul=#1\relax
+    \scriptfont\symmathhangul=#2\relax
+    \scriptscriptfont\symmathhangul=#3\relax }
+  \protected\def\mathhangulfont{%
+    \afterassignment\luatexkosetmathhangulfonts\font\textmathhangul}
+  \def\luatexkosetmathhangulfonts{%
+    \toks@\expandafter{\directlua{
+      local n = string.gsub("\fontname\textmathhangul", " at .+", "")
+      tex.sprint(n) }}%
+    \dimen@\fontdimen 6 \textmathhangul
+    \font\scriptmathhangul= {\the\toks@} at .7\dimen@
+    \font\scriptscriptmathhangul = {\the\toks@} at .5\dimen@
+    \setmathhangulfonts\textmathhangul\scriptmathhangul\scriptscriptmathhangul}
+  \catcode`\@=\luatexkoatcatcode
+\endinput\fi
+% latex
+\RequirePackage{everysel}
+\EverySelectfont{%
+  \ifx\f at encoding\UTFencname
+    \luatexkohangulselectfont
+    \luatexkohanjaselectfont
+    \luatexkofallbackselectfont
+  \fi }
+\protected\def\fontfamily#1{\edef\f at family{#1}% redefine \fontfamily
+  \ifx\f at encoding\UTFencname
+  \ifluatexkoselectfontrunning \else
+    \ifx\f at family\rmdefault
+      \let\luatexkohangulfont\luatexkomainhangulfont
+      \let\luatexkohanjafont\luatexkomainhanjafont
+      \let\luatexkofallbackfont\luatexkomainfallbackfont
+    \else\ifx\f at family\sfdefault
+      \let\luatexkohangulfont\luatexkosanshangulfont
+      \let\luatexkohanjafont\luatexkosanshanjafont
+      \let\luatexkofallbackfont\luatexkosansfallbackfont
+    \else\ifx\f at family\ttdefault
+      \let\luatexkohangulfont\luatexkomonohangulfont
+      \let\luatexkohanjafont\luatexkomonohanjafont
+      \let\luatexkofallbackfont\luatexkomonofallbackfont
+    \fi\fi\fi
+  \fi\fi }
+% fontspec-like
+\ExplSyntaxOn
+\DeclareDocumentCommand \setmainhangulfont { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkomainhangulfamily { Ligatures=TeX, #1, #3 } { #2 }
+  \DeclareRobustCommand \luatexkomainhangulfont
+  {
+    \fontfamily \luatexkomainhangulfamily \selectfont
+  }
+  \str_if_eq_x:nnT \familydefault \rmdefault
+  {
+    \cs_set_eq:NN \luatexkohangulfont \luatexkomainhangulfont
+    \luatexkohangulselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \setsanshangulfont { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkosanshangulfamily { Ligatures=TeX, #1, #3 } { #2 }
+  \DeclareRobustCommand \luatexkosanshangulfont
+  {
+    \fontfamily \luatexkosanshangulfamily \selectfont
+  }
+  \str_if_eq_x:nnT \familydefault \sfdefault
+  {
+    \cs_set_eq:NN \luatexkohangulfont \luatexkosanshangulfont
+    \luatexkohangulselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \setmonohangulfont { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkomonohangulfamily { #1, #3 } { #2 }
+  \DeclareRobustCommand \luatexkomonohangulfont
+  {
+    \fontfamily \luatexkomonohangulfamily \selectfont
+  }
+  \str_if_eq_x:nnT \familydefault \ttdefault
+  {
+    \cs_set_eq:NN \luatexkohangulfont \luatexkomonohangulfont
+    \luatexkohangulselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \newhangulfontfamily { m O{} m O{} }
+{
+  \fontspec_set_family:cnn { luatexko_user_family_ \cs_to_str:N #1 } { #2, #4 } { #3 }
+  \DeclareRobustCommand #1
+  {
+    \tl_set:Nn \luatexkohangulfont
     {
-      \keys_define:nn { fontspec }
-        {
-          #1 .code:n =
-            {
-              \keys_set:nn { fontspec } { #2 = {##1} } #4
-            },
-          #1 .default:n = {#3}
-        }
+      \exp_args:Nc \fontfamily { luatexko_user_family_ \cs_to_str:N #1 } \selectfont
     }
-  \newhangulfontfeature{Protrusion}{protrusion}{default}
-    {\aftergroup\protrudechars\aftergroup\tw@}
-  \newhangulfontfeature{Expansion}{expansion}{default}
-    {\aftergroup\adjustspacing\aftergroup\tw@}
-  %% default font features
-  \cs_set_eq:NN \defaulthangulfontfeatures    \defaultfontfeatures
-  \cs_set_eq:NN \defaulthanjafontfeatures     \defaultfontfeatures
-  \cs_set_eq:NN \defaultfallbackfontfeatures  \defaultfontfeatures
-  \defaultfontfeatures
-    [
-      \serifhangul at font, \serifhanja at font, \seriffallback at font,
-      \sanshangul at font,  \sanshanja at font,  \sansfallback at font,
-    ]
-    {Ligatures=TeX}
-  %% macros for Korean fonts
-  \DeclareDocumentCommand \setmainhangulfont { O{} m O{} }
+    \luatexkohangulselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \newhangulfontface { m O{} m O{} }
+{
+  \newhangulfontfamily #1 { #3 } [ BoldFont={}, ItalicFont={}, SmallCapsFont={}, #2, #4 ]
+}
+\DeclareDocumentCommand \hangulfontspec { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkohangulfontfamily { #1, #3 } { #2 }
+  \tl_set:Nn \luatexkohangulfont
+  {
+    \fontfamily \luatexkohangulfontfamily \selectfont
+  }
+  \luatexkohangulselectfont
+  \ignorespaces
+}
+\DeclareDocumentCommand \setmainhanjafont { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkomainhanjafamily { Ligatures=TeX, #1, #3 } { #2 }
+  \DeclareRobustCommand \luatexkomainhanjafont
+  {
+    \fontfamily \luatexkomainhanjafamily \selectfont
+  }
+  \str_if_eq_x:nnT \familydefault \rmdefault
+  {
+    \cs_set_eq:NN \luatexkohanjafont \luatexkomainhanjafont
+    \luatexkohanjaselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \setsanshanjafont { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkosanshanjafamily { Ligatures=TeX, #1, #3 } { #2 }
+  \DeclareRobustCommand \luatexkosanshanjafont
+  {
+    \fontfamily \luatexkosanshanjafamily \selectfont
+  }
+  \str_if_eq_x:nnT \familydefault \sfdefault
+  {
+    \cs_set_eq:NN \luatexkohanjafont \luatexkosanshanjafont
+    \luatexkohanjaselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \setmonohanjafont { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkomonohanjafamily { #1, #3 } { #2 }
+  \DeclareRobustCommand \luatexkomonohanjafont
+  {
+    \fontfamily \luatexkomonohanjafamily \selectfont
+  }
+  \str_if_eq_x:nnT \familydefault \ttdefault
+  {
+    \cs_set_eq:NN \luatexkohanjafont \luatexkomonohanjafont
+    \luatexkohanjaselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \newhanjafontfamily { m O{} m O{} }
+{
+  \fontspec_set_family:cnn { luatexko_user_family_ \cs_to_str:N #1 } { #2, #4 } { #3 }
+  \DeclareRobustCommand #1
+  {
+    \tl_set:Nn \luatexkohanjafont
     {
-      \setfontfamily \serifhangul at font [#1,#3] {#2}
-      \str_if_eq_x:nnT \familydefault \rmdefault
-        {
-          \cs_set_eq:NN \hangul at font \serifhangul at font
-          \luatexko at hangul@selectfont
-        }
-      \ignorespaces
+      \exp_args:Nc \fontfamily { luatexko_user_family_ \cs_to_str:N #1 } \selectfont
     }
-  \DeclareDocumentCommand \setmainhanjafont { O{} m O{} }
-    {
-      \setfontfamily \serifhanja at font [#1,#3] {#2}
-      \str_if_eq_x:nnT \familydefault \rmdefault
-        {
-          \cs_set_eq:NN \hanja at font \serifhanja at font
-          \luatexko at hanja@selectfont
-        }
-      \ignorespaces
-    }
-  \DeclareDocumentCommand \setmainfallbackfont { O{} m O{} }
-    {
-      \setfontfamily \seriffallback at font [#1,#3] {#2}
-      \str_if_eq_x:nnT \familydefault \rmdefault
-        {
-          \cs_set_eq:NN \fallback at font \seriffallback at font
-          \luatexko at fallback@selectfont
-        }
-      \ignorespaces
-    }
-  \DeclareDocumentCommand \setsanshangulfont { O{} m O{} }
-    {
-      \setfontfamily \sanshangul at font [#1,#3] {#2}
-      \str_if_eq_x:nnT \familydefault \sfdefault
-        {
-          \cs_set_eq:NN \hangul at font \sanshangul at font
-          \luatexko at hangul@selectfont
-        }
-      \ignorespaces
-    }
-  \DeclareDocumentCommand \setsanshanjafont { O{} m O{} }
-    {
-      \setfontfamily \sanshanja at font [#1,#3] {#2}
-      \str_if_eq_x:nnT \familydefault \sfdefault
-        {
-          \cs_set_eq:NN \hanja at font \sanshanja at font
-          \luatexko at hanja@selectfont
-        }
-      \ignorespaces
-    }
-  \DeclareDocumentCommand \setsansfallbackfont { O{} m O{} }
-    {
-      \setfontfamily \sansfallback at font [#1,#3] {#2}
-      \str_if_eq_x:nnT \familydefault \sfdefault
-        {
-          \cs_set_eq:NN \fallback at font \sansfallback at font
-          \luatexko at fallback@selectfont
-        }
-      \ignorespaces
-    }
-  \DeclareDocumentCommand \setmonohangulfont { O{} m O{} }
-    {
-      \setfontfamily \monohangul at font [#1,#3] {#2}
-      \str_if_eq_x:nnT \familydefault \ttdefault
-        {
-          \cs_set_eq:NN \hangul at font \monohangul at font
-          \luatexko at hangul@selectfont
-        }
-      \ignorespaces
-    }
-  \DeclareDocumentCommand \setmonohanjafont { O{} m O{} }
-    {
-      \setfontfamily \monohanja at font [#1,#3] {#2}
-      \str_if_eq_x:nnT \familydefault \ttdefault
-        {
-          \cs_set_eq:NN \hanja at font \monohanja at font
-          \luatexko at hanja@selectfont
-        }
-      \ignorespaces
-    }
-  \DeclareDocumentCommand \setmonofallbackfont { O{} m O{} }
-    {
-      \setfontfamily \monofallback at font [#1,#3] {#2}
-      \str_if_eq_x:nnT \familydefault \ttdefault
-        {
-          \cs_set_eq:NN \fallback at font \monofallback at font
-          \luatexko at fallback@selectfont
-        }
-      \ignorespaces
-    }
-  \DeclareDocumentCommand \newhangulfontfamily { m O{} m O{} }
-    {
-      \exp_args:Nc \setfontfamily { luatexkofont@ \token_to_str:N #1 } [#2,#4] {#3}
-      \DeclareRobustCommand #1
-        {
-          \cs_set_eq:Nc \hangul at font { luatexkofont@ \token_to_str:N #1 }
-          \luatexko at hangul@selectfont
-        }
-    }
-  \DeclareDocumentCommand \newhanjafontfamily { m O{} m O{} }
-    {
-      \exp_args:Nc \setfontfamily { luatexkofont@ \token_to_str:N #1 } [#2,#4] {#3}
-      \DeclareRobustCommand #1
-        {
-          \cs_set_eq:Nc \hanja at font { luatexkofont@ \token_to_str:N #1 }
-          \luatexko at hanja@selectfont
-        }
-    }
-  \DeclareDocumentCommand \newfallbackfontfamily { m O{} m O{} }
-    {
-      \exp_args:Nc \setfontfamily { luatexkofont@ \token_to_str:N #1 } [#2,#4] {#3}
-      \DeclareRobustCommand #1
-        {
-          \cs_set_eq:Nc \fallback at font { luatexkofont@ \token_to_str:N #1 }
-          \luatexko at fallback@selectfont
-        }
-    }
-  \DeclareDocumentCommand \newhangulfontface { m O{} m O{} }
-    {
-      \exp_args:Nc \newfontface { luatexkofont@ \token_to_str:N #1 } [#2,#4] {#3}
-      \DeclareRobustCommand #1
-        {
-          \cs_set_eq:Nc \hangul at font { luatexkofont@ \token_to_str:N #1 }
-          \luatexko at hangul@selectfont
-        }
-    }
-  \DeclareDocumentCommand \newhanjafontface { m O{} m O{} }
-    {
-      \exp_args:Nc \newfontface { luatexkofont@ \token_to_str:N #1 } [#2,#4] {#3}
-      \DeclareRobustCommand #1
-        {
-          \cs_set_eq:Nc \hanja at font { luatexkofont@ \token_to_str:N #1 }
-          \luatexko at hanja@selectfont
-        }
-    }
-  \DeclareDocumentCommand \newfallbackfontface { m O{} m O{} }
-    {
-      \exp_args:Nc \newfontface { luatexkofont@ \token_to_str:N #1 } [#2,#4] {#3}
-      \DeclareRobustCommand #1
-        {
-          \cs_set_eq:Nc \fallback at font { luatexkofont@ \token_to_str:N #1 }
-          \luatexko at fallback@selectfont
-        }
-    }
-  \DeclareDocumentCommand \hangulfontspec { O{} m O{} }
-    {
-      \setfontfamily \hangul at font [#1,#3] {#2}
-      \luatexko at hangul@selectfont
-      \ignorespaces
-    }
-  \cs_set_eq:NN \adhochangulfont \hangulfontspec
-  \DeclareDocumentCommand \hanjafontspec { O{} m O{} }
-    {
-      \setfontfamily \hanja at font [#1,#3] {#2}
-      \luatexko at hanja@selectfont
-      \ignorespaces
-    }
-  \cs_set_eq:NN \adhochanjafont \hanjafontspec
-  \DeclareDocumentCommand \fallbackfontspec { O{} m O{} }
-    {
-      \setfontfamily \fallback at font [#1,#3] {#2}
-      \luatexko at fallback@selectfont
-      \ignorespaces
-    }
-  \cs_set_eq:NN \adhocfallbackfont \fallbackfontspec
-  \DeclareDocumentCommand \addhangulfontfeature { m }
-    {
-      \group_begin:
-      \cs_if_exist:NT \hangul at font { \hangul at font }
-      \addfontfeature {#1}
-      \use:x
-        {
-          \group_end:
-          \exp_not:N \tl_set:Nn \exp_not:N \hangul at font
-            {
-              \exp_not:N \fontfamily { \f at family } \exp_not:N \selectfont
-            }
-        }
-      \luatexko at hangul@selectfont
-      \ignorespaces
-    }
-  \cs_set_eq:NN \addhangulfontfeatures \addhangulfontfeature
-  \DeclareDocumentCommand \addhanjafontfeature { m }
-    {
-      \group_begin:
-      \cs_if_exist:NT \hanja at font { \hanja at font }
-      \addfontfeature {#1}
-      \use:x
-        {
-          \group_end:
-          \exp_not:N \tl_set:Nn \exp_not:N \hanja at font
-            {
-              \exp_not:N \fontfamily { \f at family } \exp_not:N \selectfont
-            }
-        }
-      \luatexko at hanja@selectfont
-      \ignorespaces
-    }
-  \cs_set_eq:NN \addhanjafontfeatures \addhanjafontfeature
-  \DeclareDocumentCommand \addfallbackfontfeature { m }
-    {
-      \group_begin:
-      \cs_if_exist:NT \fallback at font { \fallback at font }
-      \addfontfeature {#1}
-      \use:x
-        {
-          \group_end:
-          \exp_not:N \tl_set:Nn \exp_not:N \fallback at font
-            {
-              \exp_not:N \fontfamily { \f at family } \exp_not:N \selectfont
-            }
-        }
-      \luatexko at fallback@selectfont
-      \ignorespaces
-    }
-  \cs_set_eq:NN \addfallbackfontfeatures \addfallbackfontfeature
-  % hangul math font command
-  \DeclareDocumentCommand \setmathhangulfont { O{} m O{} }
-    {
-      \fontspec_set_family:Nnn \luatexko at math@hangul at family {#1,#3} {#2}
-    }
-%% fontspec 2018
-\RenewDocumentCommand \setmainfont { O{} m O{} }
+    \luatexkohanjaselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \newhanjafontface { m O{} m O{} }
+{
+  \newhanjafontfamily #1 { #3 } [ BoldFont={}, ItalicFont={}, SmallCapsFont={}, #2, #4 ]
+}
+\DeclareDocumentCommand \hanjafontspec { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkohanjafontfamily { #1, #3 } { #2 }
+  \tl_set:Nn \luatexkohanjafont
   {
-    \__fontspec_main_setmainfont:nn {#1,#3} {#2}
-    \protected\edef\rmfamily{
-      \unexpanded{\luatexko at serif@fonts}
-      \unexpanded\expandafter{\rmfamily}
-    }
-    \ignorespaces
+    \fontfamily \luatexkohanjafontfamily \selectfont
   }
-\RenewDocumentCommand \setsansfont { O{} m O{} }
+  \luatexkohanjaselectfont
+  \ignorespaces
+}
+
+\DeclareDocumentCommand \setmainfallbackfont { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkomainfallbackfamily { Ligatures=TeX, #1, #3 } { #2 }
+  \DeclareRobustCommand \luatexkomainfallbackfont
   {
-    \__fontspec_main_setsansfont:nn {#1,#3} {#2}
-    \protected\edef\sffamily{
-      \unexpanded{\luatexko at sans@fonts}
-      \unexpanded\expandafter{\sffamily}
-    }
-    \ignorespaces
+    \fontfamily \luatexkomainfallbackfamily \selectfont
   }
-\RenewDocumentCommand \setmonofont { O{} m O{} }
+  \str_if_eq_x:nnT \familydefault \rmdefault
   {
-    \__fontspec_main_setmonofont:nn {#1,#3} {#2}
-    \protected\edef\ttfamily{
-      \unexpanded{\luatexko at mono@fonts}
-      \unexpanded\expandafter{\ttfamily}
+    \cs_set_eq:NN \luatexkofallbackfont \luatexkomainfallbackfont
+    \luatexkofallbackselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \setsansfallbackfont { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkosansfallbackfamily { Ligatures=TeX, #1, #3 } { #2 }
+  \DeclareRobustCommand \luatexkosansfallbackfont
+  {
+    \fontfamily \luatexkosansfallbackfamily \selectfont
+  }
+  \str_if_eq_x:nnT \familydefault \sfdefault
+  {
+    \cs_set_eq:NN \luatexkofallbackfont \luatexkosansfallbackfont
+    \luatexkofallbackselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \setmonofallbackfont { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkomonofallbackfamily { #1, #3 } { #2 }
+  \DeclareRobustCommand \luatexkomonofallbackfont
+  {
+    \fontfamily \luatexkomonofallbackfamily \selectfont
+  }
+  \str_if_eq_x:nnT \familydefault \ttdefault
+  {
+    \cs_set_eq:NN \luatexkofallbackfont \luatexkomonofallbackfont
+    \luatexkofallbackselectfont
+  }
+  \ignorespaces
+}
+\DeclareDocumentCommand \newfallbackfontfamily { m O{} m O{} }
+{
+  \fontspec_set_family:cnn { luatexko_user_family_ \cs_to_str:N #1 } { #2, #4 } { #3 }
+  \DeclareRobustCommand #1
+  {
+    \tl_set:Nn \luatexkofallbackfont
+    {
+      \exp_args:Nc \fontfamily { luatexko_user_family_ \cs_to_str:N #1 } \selectfont
     }
-    \ignorespaces
+    \luatexkofallbackselectfont
   }
-\cs_set_eq:NN \setromanfont \setmainfont
-  \ExplSyntaxOff
-\fi
-
-\RequirePackage{everysel}
-\EverySelectfont{%
-  \luatexko at hangul@selectfont
-  \luatexko at hanja@selectfont
-  \luatexko at fallback@selectfont
+  \ignorespaces
 }
-
-% serif    : finemathattr = 2
-% sans     : finemathattr = 1
-% tt       : finemathattr = 0
-% verbatim : finemathattr = nil
-\def\luatexko at serif@fonts{%
-  \let\hangul at font\serifhangul at font
-  \let\hanja at font\serifhanja at font
-  \let\fallback at font\seriffallback at font
-  \finemathattr\tw@
+\DeclareDocumentCommand \newfallbackfontface { m O{} m O{} }
+{
+  \newfallbackfontfamily #1 { #3 } [ BoldFont={}, ItalicFont={}, SmallCapsFont={}, #2, #4 ]
 }
-\def\luatexko at sans@fonts{%
-  \let\hangul at font\sanshangul at font
-  \let\hanja at font\sanshanja at font
-  \let\fallback at font\sansfallback at font
-  \finemathattr\@ne
+\DeclareDocumentCommand \fallbackfontspec { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkofallbackfontfamily { #1, #3 } { #2 }
+  \tl_set:Nn \luatexkofallbackfont
+  {
+    \fontfamily \luatexkofallbackfontfamily \selectfont
+  }
+  \luatexkofallbackselectfont
+  \ignorespaces
 }
-\def\luatexko at mono@fonts{%
-  \let\hangul at font\monohangul at font
-  \let\hanja at font\monohanja at font
-  \let\fallback at font\monofallback at font
-  \finemathattr\z@
+\DeclareDocumentCommand \setmathhangulfont { O{} m O{} }
+{
+  \fontspec_set_family:Nnn \luatexkomathhangulfamily { #1, #3 } { #2 }
+  \DeclareSymbolFont { mathhangul }
+    \g_fontspec_encoding_tl \luatexkomathhangulfamily \mddefault \updefault
+  \int_compare:nNnF { \Umathcharfam"AC00 } = { \symmathhangul }
+  {
+    \setmathhangulblock{AC00}{D7A3}
+  }
+  \ignorespaces
 }
-
-\protected\edef\rmfamily{%
-  \unexpanded{\luatexko at serif@fonts}\unexpanded\expandafter{\rmfamily}}
-\protected\edef\sffamily{%
-  \unexpanded{\luatexko at sans@fonts}\unexpanded\expandafter{\sffamily}}
-\protected\edef\ttfamily{%
-  \unexpanded{\luatexko at mono@fonts}\unexpanded\expandafter{\ttfamily}}
-\edef\verbatim at font{\unexpanded\expandafter{\verbatim at font
-  \unsetattribute\finemathattr}}
-
+\ExplSyntaxOff
+\let\adhochangulfont\hangulfontspec
+\let\adhochanjafont\hanjafontspec
+\let\adhocfallbackfont\fallbackfontspec
+% interhangul, interlatincjk, charraise
+\protected\def\addhangulfontfeature#1{%
+  \begingroup
+  \ifdefined\luatexkohangulfont \luatexkohangulfont \fi
+  \addfontfeature{#1}%
+  \edef\x{\endgroup
+    \def\noexpand\luatexkohangulfont{%
+      \noexpand\fontfamily{\f at family}\noexpand\selectfont}}\x
+  \luatexkohangulselectfont
+  \ignorespaces}
+\let\addhangulfontfeatures\addhangulfontfeature
+\protected\def\addhanjafontfeature#1{%
+  \begingroup
+  \ifdefined\luatexkohanjafont \luatexkohanjafont \fi
+  \addfontfeature{#1}%
+  \edef\x{\endgroup
+    \def\noexpand\luatexkohanjafont{%
+      \noexpand\fontfamily{\f at family}\noexpand\selectfont}}\x
+  \luatexkohanjaselectfont
+  \ignorespaces}
+\let\addhanjafontfeatures\addhanjafontfeature
+\protected\def\addfallbackfontfeature#1{%
+  \begingroup
+  \ifdefined\luatexkofallbackfont \luatexkofallbackfont \fi
+  \addfontfeature{#1}%
+  \edef\x{\endgroup
+    \def\noexpand\luatexkofallbackfont{%
+      \noexpand\fontfamily{\f at family}\noexpand\selectfont}}\x
+  \luatexkofallbackselectfont
+  \ignorespaces}
+\let\addfallbackfontfeatures\addfallbackfontfeature
+\newfontfeature{InterHangul}{interhangul=#1}
+\newfontfeature{InterLatinCJK}{interlatincjk=#1}
+\newfontfeature{CharRaise}{charraise=#1}
+\newfontfeature{RemoveClassicSpaces}{+removeclassicspaces}
+\newfontfeature{CompressPunctuations}{+compresspunctuations}
+\newfontfeature{Expansion}{expansion=\ifx\empty#1\empty default\else #1\fi }
+\newfontfeature{Protrusion}{protrusion=\ifx\empty#1\empty default\else #1\fi }
+\newfontfeature{InterCharacter}{intercharacter=#1}
+% italic correction
+\def\nocorrlist{,.^^^^3001^^^^3002^^^^ff0c^^^^ff0e^^^^ff61^^^^ff64}
+% package options
+\newif\if at hangul
+\newif\if at hanja
+\DeclareOption{hangul}{\@hangultrue}
+\DeclareOption{hanja}{\@hangultrue\@hanjatrue}
+\ProcessOptions\relax
+% default hangul font
 \AtBeginDocument{
-\unless\ifdefined\serifhangul at font\let\serifhangul at font\sanshangul at font\fi
-\unless\ifdefined\sanshangul at font \let\sanshangul at font\serifhangul at font\fi
-\unless\ifdefined\monohangul at font \let\monohangul at font\sanshangul at font \fi
-\unless\ifdefined\serifhangul at font\unless\ifdefined\sanshangul at font
-  \hangulpunctuations=0 % nanumtype1
-\fi\fi
-% don't forget to redefine \normalfont
-\expandafter\ifx\familydefault\rmdefault
-  \protected\edef\normalfont{%
-    \unexpanded{\luatexko at serif@fonts}\unexpanded\expandafter{\normalfont}}
-  % and current hangul font
-  \luatexko at serif@fonts
-\else\expandafter\ifx\familydefault\sfdefault
-  \protected\edef\normalfont{%
-    \unexpanded{\luatexko at sans@fonts}\unexpanded\expandafter{\normalfont}}
-  \luatexko at sans@fonts
-\else
-  \protected\edef\normalfont{%
-    \unexpanded{\luatexko at mono@fonts}\unexpanded\expandafter{\normalfont}}
-  \luatexko at mono@fonts
-\fi\fi
-\let\reset at font\normalfont
-% reset hangul font
-\selectfont
-% for hyperref
-\@ifpackageloaded{hyperref}{\pdfstringdefDisableCommands{%
-  \let\dotemph\@firstofone
-  \let\ruby\@firstoftwo
-  \let\uline\@firstofone
-  \let\sout\@firstofone
-  \let\uuline\@firstofone
-  \let\xout\@firstofone
-  \let\uwave\@firstofone
-  \let\dashuline\@firstofone
-  \let\dotuline\@firstofone
-  \let\actualtext\@firstofone
-  \let\inhibitglue\empty
-  \def\는{는}%
-  \def\은{은}%
-  \def\을{을}%
-  \def\를{를}%
-  \def\와{와}%
-  \def\과{과}%
-  \def\가{가}%
-  \def\이{이}%
-  \def\라{라}%
-  \def\으{으}%
-  \def\로{로}%
-  }}{}
+  \ifdefined\luatexkomainhangulfont\else
+    \begingroup\rmfamily
+    \directlua{ luatexko.currenthashangulchars(255) }\expandafter\endgroup
+    \ifnum\count@=\z@
+      \setmainhangulfont{UnBatang}[Script=Hangul,Language=Korean]\fi\fi
+  \ifdefined\luatexkosanshangulfont\else
+    \begingroup\sffamily
+    \directlua{ luatexko.currenthashangulchars(255) }\expandafter\endgroup
+    \ifnum\count@=\z@
+      \setsanshangulfont{UnDotum}\fi\fi
+  \ifdefined\luatexkomonohangulfont\else
+    \begingroup\ttfamily
+    \directlua{ luatexko.currenthashangulchars(255) }\expandafter\endgroup
+    \ifnum\count@=\z@
+      \let\luatexkomonohangulfont\luatexkosanshangulfont\fi\fi
+  \@ifpackageloaded{hyperref}{\pdfstringdefDisableCommands{%
+    \let\ruby\@firstoftwo \let\xxruby\@firstoftwo
+    \let\dotemph\@firstofone
+    \let\markoverwith\@secondoftwo
+    \let\uline\@firstofone \let\sout\@firstofone
+    \let\uuline\@firstofone \let\xout\@firstofone
+    \let\uwave\@firstofone \let\dashuline\@firstofone
+    \let\dotuline\@firstofone
+    \let\typesetclassic\empty \let\typesetmodern\empty
+    \let\inhibitglue\empty
+    \let\hangulpunctuations\count@
+    \let\registerpunctuations\@gobble \let\unregisterpunctuations\@gobble
+    \let\registerhangulpunctuations\@gobble
+    \let\unregisterhangulpunctuations\@gobble
+    \let\registerbreakableafter\@gobble \let\registerbreakablebefore\@gobble
+    \let\hangulbyhangulfont\count@ \let\hanjabyhanjafont\count@
+    \let\addhangulfontfeature\@gobble \let\addhangulfontfeatures\@gobble
+    \let\addhanjafontfeature\@gobble \let\addhanjafontfeatures\@gobble
+    \let\addfallbackfontfeature\@gobble \let\addfallbackfontfeatures\@gobble
+    \def\는{는}\def\은{은}\def\을{을}\def\를{를}\def\와{와}\def\과{과}%
+    \def\가{가}\def\이{이}\def\라{라}\def\으{으}\def\로{로}%
+    \def\hellipsis{...}}}{}
+  \if at hangul \RequirePackage{konames-utf}\fi
 }
-
-\if at luatexko@loadfontspec
-  %% hangul in math --- latex
-  \AtBeginDocument{
-    \begingroup
-      \unless\ifdefined\luatexko at math@hangul at family
-        \ifdefined\hangul at font \hangul at font \fi
-        \xdef\luatexko at math@hangul at family{\f at family}%
-      \fi
-    \endgroup
-    \DeclareSymbolFont{mathhangul}%
-      \f at encoding\luatexko at math@hangul at family\mddefault\updefault
-    \ifcsname\f at encoding/\luatexko at math@hangul at family/\bfdefault/\updefault\endcsname
-      \SetSymbolFont{mathhangul}{bold}%
-        \f at encoding\luatexko at math@hangul at family\bfdefault\updefault
-    \fi
-    \setmathhangulblock{AC00}{D7A3}
-  }
-\fi
-
+% misc
 \RequirePackage{kolabels-utf}
+\protected\def\hellipsis{\char"2026\char"2026 }
 \if at hangul
-  \AtBeginDocument{\RequirePackage{konames-utf}}
   \linespread{1.3888}
   \footnotesep=1.3888\footnotesep
   \skip\footins=\glueexpr\skip\footins/72*100\relax
-  \hangulpunctuations=1
   \frenchspacing
 \fi
-
 \endinput



More information about the tex-live-commits mailing list