From d6b2eaba6fe47c24603040934903c73535ff09ba Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Fri, 11 Jun 2021 03:00:56 +0000 Subject: CTAN sync 202106110300 --- macros/luatex/generic/luatexko/ChangeLog | 17 + macros/luatex/generic/luatexko/README | 2 +- macros/luatex/generic/luatexko/luatexko-doc.pdf | Bin 326736 -> 338396 bytes macros/luatex/generic/luatexko/luatexko-doc.tex | 179 +++++-- .../luatex/generic/luatexko/luatexko-normalize.lua | 4 +- .../luatex/generic/luatexko/luatexko-uhc2utf8.lua | 4 +- macros/luatex/generic/luatexko/luatexko.lua | 195 ++++--- macros/luatex/generic/luatexko/luatexko.sty | 344 ++++++------ macros/luatex/generic/minim-xmp/EUPL-1.2-EN.txt | 287 ++++++++++ macros/luatex/generic/minim-xmp/README | 30 ++ macros/luatex/generic/minim-xmp/minim-xmp.doc | 196 +++++++ macros/luatex/generic/minim-xmp/minim-xmp.lua | 583 +++++++++++++++++++++ macros/luatex/generic/minim-xmp/minim-xmp.pdf | Bin 0 -> 61507 bytes macros/luatex/generic/minim-xmp/minim-xmp.tex | 34 ++ 14 files changed, 1587 insertions(+), 288 deletions(-) create mode 100644 macros/luatex/generic/minim-xmp/EUPL-1.2-EN.txt create mode 100644 macros/luatex/generic/minim-xmp/README create mode 100644 macros/luatex/generic/minim-xmp/minim-xmp.doc create mode 100644 macros/luatex/generic/minim-xmp/minim-xmp.lua create mode 100644 macros/luatex/generic/minim-xmp/minim-xmp.pdf create mode 100644 macros/luatex/generic/minim-xmp/minim-xmp.tex (limited to 'macros/luatex') diff --git a/macros/luatex/generic/luatexko/ChangeLog b/macros/luatex/generic/luatexko/ChangeLog index 87afc21887..07a26993c6 100644 --- a/macros/luatex/generic/luatexko/ChangeLog +++ b/macros/luatex/generic/luatexko/ChangeLog @@ -1,3 +1,20 @@ +2021-06-10 Dohyun Kim + + Version 3.2 + + * luatexko.lua: + - support automatic josa selection after hanja characters + - dotemph/uline/ruby routines are now called from hpack_filter, + pre_linebreak_filter, and post_linebreak_filter callbacks. + + * luatexko.sty: + - use LaTeX's selectfont hook which require LaTeX 2021/06/01 or later + - a new LaTeX environment: verticaltypesetting + - new plain TeX commands: \sethangulfont \sethanjafont \setfallbackfont + \beginverticaltypesetting \endverticaltypesetting + - vertical/horizontal environments now honor empty argument, which means + natural width. + 2021-04-20 Dohyun Kim Version 3.1 diff --git a/macros/luatex/generic/luatexko/README b/macros/luatex/generic/luatexko/README index 1b9329ffca..f3e1feb636 100644 --- a/macros/luatex/generic/luatexko/README +++ b/macros/luatex/generic/luatexko/README @@ -1,4 +1,4 @@ -LuaTeX-ko Package version 3.1 (2021/04/20) +LuaTeX-ko Package version 3.2 (2021/06/10) =========================================== This is a Lua(La)TeX macro package that supports typesetting Korean diff --git a/macros/luatex/generic/luatexko/luatexko-doc.pdf b/macros/luatex/generic/luatexko/luatexko-doc.pdf index dc87222ab2..7b4c4336e6 100644 Binary files a/macros/luatex/generic/luatexko/luatexko-doc.pdf and b/macros/luatex/generic/luatexko/luatexko-doc.pdf differ diff --git a/macros/luatex/generic/luatexko/luatexko-doc.tex b/macros/luatex/generic/luatexko/luatexko-doc.tex index d23b010e8f..9153b572ed 100644 --- a/macros/luatex/generic/luatexko/luatexko-doc.tex +++ b/macros/luatex/generic/luatexko/luatexko-doc.tex @@ -41,7 +41,6 @@ } } \defaultfontfeatures+{Renderer=HarfBuzz} -\setmainfont{Latin Modern Roman}[Expansion] % for hb tlig \setmainhangulfont{Noto Serif CJK KR}[ Scale=0.98, AutoFakeSlant, @@ -50,9 +49,9 @@ UprightFont=* Light, BoldFont=* Bold, InterLatinCJK=.125em, + CompressPunctuations, Expansion, Protrusion=notoserifcjk, ] -\setsansfont{Latin Modern Sans} \setsanshangulfont{Noto Sans CJK KR}[ Scale=0.98, Script=Hangul, @@ -74,7 +73,8 @@ CharRaise=-.125ex, Color=MidnightBlue, ] -\setmathhangulfont{Noto Sans CJK KR}[ +\setmathhangulfont{Noto Sans CJK KR} +[ Script=Hangul, Language=Korean, SizeFeatures={ @@ -110,7 +110,7 @@ \AddToHook{env/itemize/begin}{\small} \AddToHook{env/verbatim/begin}{\linespread{1.1}} -\catcode`|=13 \protected\def|{\verb|\let\\=\textbackslash} +\catcode`|=13 \protected\def|{\verb|\let\\=\textbackslash} %| \def\nohyphen{\language\csname l@nohyphenation\endcsname} \protected\def\cs#1{\texttt{\nohyphen \textbackslash\detokenize{#1}}} \protected\def\pkgkwd#1#{\leavevmode @@ -125,6 +125,14 @@ \def\kotex{\logoko\kern-.1ex .\kern-.1667em \TeX} \def\hemph#1{\textsf{\bfseries #1}} \def\ldots{$\dots$} +\newenvironment{plaintex}{% + \par\bigskip + \begin{narrower}\small\parindent=0pt + \leavevmode\llap{\color{gray}plain \TeX\quad}\ignorespaces +}{ + \par + \end{narrower}% +} \widowpenalty5000 \clubpenalty5000 @@ -134,7 +142,7 @@ \author{Dohyun Kim \normalsize || \and Soojin Nam \normalsize || \and \normalsize <\url{http://github.com/dohyunkim/luatexko}>} -\date{Version 3.1\quad 2021/04/20} +\date{Version 3.2\quad 2021/06/10} \maketitle \begin{quote} @@ -181,13 +189,7 @@ fontspec 문서 참조. 첫 두 줄, 즉 main/sans 글꼴들에는 |Ligatures=TeX| 옵션이 자동으로 주어진다.% \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 @@ -198,6 +200,27 @@ fontspec 문서 참조. \hangulfontspec \hanjafontspec \fallbackfontspec \end{verbatim} +\begin{plaintex} + 플레인텍에서 한글 글꼴 설정은 다음과 같이 할 수 있다: +\begin{verbatim} + \hangulfont=UnDotum\relax + \hanjafont="Noto Sans CJK KR" at 10pt + \fallbackfont=HanaMinA at 10pt +\end{verbatim} + + v3.2부터는 한글폰트의 기본값으로 \hemph{은바탕}을 로드한다. + 또한 다음 명령들도 제공한다: +\begin{verbatim} + \sethangulfont\taza=UnTaza at 12pt + \sethanjafont\dotum=UnDotum at 12pt + \setfallbackfont\noto={Noto Sans CJK KR} at 12pt +\end{verbatim} + 그리하여 다음처럼 지시할 수 있게 된다: +\begin{verbatim} + \expandafter\def\expandafter\tt\expandafter{\tt\taza\dotum\noto} +\end{verbatim} +\end{plaintex} + \section{글꼴 대체}\label{sec:fontswitch} 원칙적으로 \luatexko는 지시된 글자가 영문폰트에 없으면 한글폰트에서, @@ -219,15 +242,15 @@ fontspec 문서 참조. |0|을 지시해야 이 기능이 비로소 꺼진다. verbatim 코드를 식자할 때는 이 기능이 작동하지 않으며,% \footnote{% - 정확히 말하자면 |\\language=\\l@nohyphenation|, 즉 하이프네이션이 - 허용 안 되는 상황에서는 작동하지 않는다. + 정확히 말하자면 |\\language=\\l@nohyphenation| (플레인텍에서는 |\\language=-1|인 경우), + 즉 하이프네이션이 허용 안 되는 상황에서는 작동하지 않는다. 부연하건대, |\\language=\\l@nohyphenation| 상황에서 작동하지 않는 그밖의 기능은 다음과 같다: 줄바꿈 허용, InterCharacter, InterHangul, InterLatinCJK, CompressPunctuations, RemoveClassicSpaces, 한글^^b7한자 폰트 문자 다음의 공백 크기 조정 등. 마지막의 한글^^b7한자 다음의 공백 크기 조정은 영문자가 고정폭 글꼴일 때에도 작동하지 않는다. } -문장부호가 고정폭 글꼴일 때에도 작동하지 않는다. +문장부호가 고정폭 글꼴이거나 tfm 폰트일 때에도 작동하지 않는다. 영향 받는 문장부호들의 기본값은 다음과 같다: \begin{verbatim} [0x21] ! [0x27] ' [0x28] ( [0x29] ) @@ -374,7 +397,9 @@ fontspec의 글꼴 옵션 외에 \luatexko가 따로 제공하는 것들이 있 |Vertical=HalfMetrics|와 거의 같은 기능을 한다.% \footnote{% 플레인텍에서는 각각 |+halt| 및 |+vhal|이다. 이들과 완전 동일하진 - 않다. 가령 U+3002 (\char"3002) 처리의 경우.} + 않다. 가령 U+00B7\;(^^b7)의 경우 및 고문헌조판 모드에서 + U+2018\;(^^^^2018) U+2019\;(^^^^2019) U+201C\;(^^^^201c) U+201D\;(^^^^201d) + 처리의 경우.} \item[Protrusion] \pkgkwd*{Protrusion}% 특정 글자가 행 끝에 왔을 때 판면 바깥으로 끌어내는 @@ -417,12 +442,16 @@ fontspec의 글꼴 옵션 외에 \luatexko가 따로 제공하는 것들이 있 }\label{fig:gomun} \end{figure} -고문헌 조판을 위해 \pkgkwd{typesetclassic} 환경을 제공한다.% +고문헌 조판을 위해 \pkgkwd{typesetclassic} 환경을 제공한다. +전처리부에 \cs{typesetclassic}을 선언할 수도 있다.% \footnote{% 플레인텍에서는 (문서 전체에 적용하지 않는다면 그룹 안에서) - \cs{typesetclassic}을 사용한다. + \cs{typesetclassic}을 사용한다. }\,% +\footnote{% |japanese|, |chinese|, |Schinese|, |Tchinese|, |korean| 따위의 환경은 - 지난 버전과의 호환성을 위해 당분간 제공되고 있을 뿐이다. } + 지난 버전과의 호환성을 위해 제공되고 있을 뿐이다. + 그렇더라도 중국어나 일본어 폰트를 사용한다면 이들 환경을 사용하는 것이 좋다. + |chinese|는 |Schinese|와 동의어이다. } 그림~\ref{fig:gomun} 참조. v2.0부터는 고문헌 조판에서 한글^^b7한자와 영문자가 붙어 나오더라도 자동으로 @@ -464,6 +493,13 @@ v2.0부터는 고문헌 조판에서 한글^^b7한자와 영문자가 붙어 나 세로쓰기에서 |CharRaise| 옵션의 효과에 대해서는 제\ref{sec:fontoption}절을 참조. +문서의 일부를 세로쓰기하려면 +|\begin{|\pkgkwd{vertical}|}{}| \ldots\ |\end{vertical}| +환경을 이용한다. 인자 ||으로 세로쓰기 박스의 높이를 지시한다. +그림~\ref{fig:vertical},~\ref{fig:vertical3} 및 \ref{fig:vertical2} 참조. +만약 인자가 비어있거나 \cs{empty}이면 본문을 한 줄짜리 박스로 식자하며, +이때 높이는 natural height가 된다. + \begin{figure} \fboxsep=5pt \framebox[\linewidth]{ @@ -472,7 +508,9 @@ v2.0부터는 고문헌 조판에서 한글^^b7한자와 영문자가 붙어 나 \hunmintxt \end{vertical} } - \caption{세로쓰기의 예. 박스 높이 |20em|을 지시했다.}\label{fig:vertical} + \caption{세로쓰기의 예. 박스 높이 |20em|을 지시했다. + 아직 HarfBuzz 모드를 쓸 수 없으므로 폰트에 |Renderer=Node| 옵션을 명시했다. + }\label{fig:vertical} \end{figure} \begin{figure} @@ -494,24 +532,6 @@ v2.0부터는 고문헌 조판에서 한글^^b7한자와 영문자가 붙어 나 }\label{fig:vertical3} \end{figure} -문서의 일부를 세로쓰기하려면 -|\begin{|\pkgkwd{vertical}|}{}| \ldots\ |\end{vertical}| -환경을 이용한다. ||으로 세로쓰기 박스의 높이를 지시한다.% -\footnote{% - 플레인텍에서는 \cs{vertical{}} \ldots\ \cs{endvertical}. } -그림~\ref{fig:vertical},~\ref{fig:vertical3} 및 \ref{fig:vertical2} 참조. - -문서 전체를 세로쓰기한다면 이 환경을 쓰는 대신 -\pkgkwd{\verticaltypesetting} 명령을 전처리부에 선언한다. -이때 면주는 가로로 식자되며 면주 폰트의 설정은 사용자의 몫이다. - -세로쓰기 도중에 문서의 일부를 가로쓰기하려면 -|\begin{|\pkgkwd{horizontal}|}{}| \ldots\ |\end{horizontal}| -환경을 이용한다. ||은 가로쓰기 박스의 너비를 지시한다.% -\footnote{% - 플레인텍에서는 \cs{horizontal{}} \ldots\ \cs{endhorizontal}. } -가로쓰기 영역의 폰트 설정은 사용자의 몫이다. - \begin{figure} \fboxsep=5pt \framebox[\linewidth]{ @@ -541,6 +561,33 @@ v2.0부터는 고문헌 조판에서 한글^^b7한자와 영문자가 붙어 나 }\label{fig:vertical2} \end{figure} +문서 전체를 세로쓰기한다면 이 환경을 쓰는 대신 +\pkgkwd{\verticaltypesetting} 명령을 전처리부에 선언한다. +이때 면주는 가로로 식자되며 면주 폰트의 설정은 사용자의 몫이다. + +몇몇 페이지를 세로쓰기로 식자하려면 본문 중에 +|\begin{|\pkgkwd{verticaltypesetting}|}| \ldots\ |\end{verticaltypesetting}| +환경을 이용한다. +이때도 면주는 가로로 식자되며, 환경의 시작과 끝에서 쪽나눔이 행해진다. + +세로쓰기 도중에 문서의 일부를 가로쓰기하려면 +|\begin{|\pkgkwd{horizontal}|}{}| \ldots\ |\end{horizontal}| +환경을 이용한다. ||은 가로쓰기 박스의 너비를 지시한다. +만약 인자가 비어있거나 \cs{empty}이면 본문을 한 줄짜리 박스로 식자하며, +이때 너비는 당연히 natural width가 된다. + +\begin{plaintex} + 문서의 일부를 세로쓰기하려면 |\vertical{}| \ldots\ |\endvertical|. + 인자는 비워둘 수 있다. + + 문서 전체를 세로쓰기하려면 문서 첫머리에 \cs{verticaltypesetting}. + + 몇몇 페이지를 세로쓰기하려면 \cs{beginverticaltypesetting} \ldots\ \cs{endverticaltypesetting}. + + 세로쓰기 도중에 가로쓰기를 넣으려면 |\horizontal{}| \ldots\ |\endhorizontal|. + 인자는 비워둘 수 있다. +\end{plaintex} + \section{드러냄표}\label{sec:dotemph} \pkgkwd{\dotemph} 명령으로 \dotemph{드러냄표}% @@ -575,9 +622,7 @@ v2.0부터는 고문헌 조판에서 한글^^b7한자와 영문자가 붙어 나 \footnote{% 플레인텍에서는 루비의 글꼴크기를 따로 조정해주지 않는다. 사용자가 \cs{rubyfont}를 설정할 때 크기도 함께 지시하도록 한다. - 가령:\par - \leftskip=3em\noindent - |\\font\\rubyfont=UnBatang at \\rubysize em| } + 가령: |\\font\\rubyfont=UnBatang at \\rubysize em| } ③~|\def|\pkgkwd{\rubysep}|{0.1ex}|: 루비와 본문 글자 사이의 간격을 지정 @@ -635,12 +680,12 @@ ulem 패키지가 \luatexko와 궁합이 맞지 않아{\small (줄바꿈에 문 \begin{verbatim} \def\ymarker{% \markoverwith{% - \hbox{{\color{yellow!50}\vrule width 1pt height 9pt depth 2pt}}% + \hbox{\color{yellow!50}\vrule width 1pt height 9pt depth 2pt}% }} \end{verbatim} \def\ymarker{\markoverwith{% - \hbox{{\color{yellow!50}\vrule width 1pt height 9pt depth 2pt}}}} + \hbox{\color{yellow!50}\vrule width 1pt height 9pt depth 2pt}}} \cs{ymarker{노란색을 칠해서 강조한다}}\quad$\Rightarrow$\quad \ymarker{노란색을 칠해서 강조한다}. @@ -679,23 +724,26 @@ ulem 패키지가 \luatexko와 궁합이 맞지 않아{\small (줄바꿈에 문 수식 한글 폰트를 설정하면 수식 모드에서도 한글을 {\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| } + |\\hbox|를 쓰지 않고 직접 입력하는 수식 한글이 + \uline{디스플레이 수식} 안에 사용된 경우, + 앞서 기술한 드러냄표 강조, 밑줄 강조, 한글 글꼴 옵션 등의 기능이 현재로는 + 전혀 작동하지 않는다. 구현은 비교적 간단하지만, 과연 수식에서 + 저러한 기능을 써야 할 경우가 있을까 의문이기 때문이다. + 사용자의 요청이 있으면 즉시 반영하겠다. +} v2.0부터는 자동으로 수식 한글을 잡아주지 않는다.% \pkgkwd*{\setmathhangulfont} \begin{verbatim} - \setmathhangulfont{Noto Sans CJK KR}[ + \setmathhangulfont{Noto Sans CJK KR} + [ SizeFeatures={ {Size=-6, Font=* Medium}, {Size=6-9, Font=* Regular}, {Size=9-, Font=* DemiLight}, - } ] + } + ] \end{verbatim} + 현재 한글만 쓸 수 있게 설정되어 있다. 한자도 수식에 직접 입력하려면 사용자는 다음 명령으로 유니코드 블럭을 추가 지정해야 한다.% \pkgkwd*{\setmathhangulblock} @@ -703,6 +751,18 @@ v2.0부터는 자동으로 수식 한글을 잡아주지 않는다.% \setmathhangulblock{4E00}{9FC3} \end{verbatim} +\begin{plaintex} + 플레인텍에서는 \cs{mathhangulfont=UnBatang}과 같이 설정한다. + + 또는 좀 더 그럴듯하게 하려면: +\begin{verbatim} + \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 +\end{verbatim} +\end{plaintex} + \section{한국어 항목 번호}\label{sec:hangulnums} \kotex과 동일하게 항목 번호를 한국어 기호로 붙일 수 있다.% @@ -719,7 +779,9 @@ v2.0부터는 자동으로 수식 한글을 잡아주지 않는다.% 유사하다.% \footnote{% 변환기능은 |lua-uni-algos| 패키지를 이용하므로 - 이 패키지를 설치해두어야 한다. 또한 HarfBuzz 모드에서는 + 이 패키지를 설치해두어야 한다. }\,% +\footnote{% + HarfBuzz 모드에서는 첫가끝 자모로의 입력변환이 효과를 못볼 수도 있으니 유의할 것. } @@ -732,7 +794,8 @@ v2.0부터는 자동으로 수식 한글을 잡아주지 않는다.% |0|을 할당하면 다시 UTF-8 입력으로 간주한다. \XeTeX의 \cs{XeTeXinputencoding} 명령과 유사하나, 오직 한국어 문자만 처리할 수 있다.% \footnote{% - 윈도 운영체제에서의 한글 파일이름 불러오기 기능은 v2.0부터는 제공하지 않는다. + CP949 인코딩 파일시스템을 사용하는 윈도 운영체제에서의 + 한글 파일이름 불러오기 기능은 v2.0부터는 제공하지 않는다. 대신 |luatex -cmdx ...|, |lualatex -cmdx ...|와 같이 컴파일시 명령행에 |-cmdx| 옵션을 주면 된다고 하니 테스트해 보시기 바란다. } @@ -743,6 +806,14 @@ v2.0부터는 자동으로 수식 한글을 잡아주지 않는다.% \section*{주요 변경 이력} \begin{itemize}\linespread{1.1}\selectfont + \item[v3.2] ------ + \item 한자 뒤에서도 자동조사 기능이 대체로 동작하게끔 하였다. + \XeTeX-\logoko{} 패키지가 설치돼 있을 것을 요구한다. + \item \hyperref[sec:verttype]{|vertical| 및 |horizontal| 환경의 인자를 비워둘 수 있다}. + \item \hyperref[sec:verttype]{|verticaltypesetting|을 환경으로도 쓸 수 있다. + 플레인텍에서는 \cs{beginverticaltypesetting} \cs{endverticaltypesetting}}. + \item \hyperref[sec:fontcmds]{플레인텍 명령 + \cs{sethangulfont} \cs{sethanjafont} \cs{setfallbackfont} 추가} \item[v3.0] ------ \item \hyperref[sec:fontoption]{세로쓰기에서 |CharRaise| 옵션은 이제 글자들의 \dotemph{추가적인} 이동 길이를 나타낸다.} diff --git a/macros/luatex/generic/luatexko/luatexko-normalize.lua b/macros/luatex/generic/luatexko/luatexko-normalize.lua index a86b709390..96a0d12cf3 100644 --- a/macros/luatex/generic/luatexko/luatexko-normalize.lua +++ b/macros/luatex/generic/luatexko/luatexko-normalize.lua @@ -13,8 +13,8 @@ luatexbase.provides_module({ name = "luatexko-normalize", - version = "3.1", - date = "2021/04/20", + version = "3.2", + date = "2021/06/10", author = "Dohyun Kim, Soojin Nam", description = "Hangul normalization", license = "LPPL v1.3+", diff --git a/macros/luatex/generic/luatexko/luatexko-uhc2utf8.lua b/macros/luatex/generic/luatexko/luatexko-uhc2utf8.lua index d3d86fcb25..2c5fd64f9e 100644 --- a/macros/luatex/generic/luatexko/luatexko-uhc2utf8.lua +++ b/macros/luatex/generic/luatexko/luatexko-uhc2utf8.lua @@ -13,8 +13,8 @@ luatexbase.provides_module({ name = "luatexko-uhc2utf8", - version = "3.1", - date = "2021/04/20", + version = "3.2", + date = "2021/06/10", author = "Dohyun Kim, Soojin Nam", description = "UHC (CP949) input encoding", license = "LPPL v1.3+", diff --git a/macros/luatex/generic/luatexko/luatexko.lua b/macros/luatex/generic/luatexko/luatexko.lua index d0360a8b72..aa1aa4149f 100644 --- a/macros/luatex/generic/luatexko/luatexko.lua +++ b/macros/luatex/generic/luatexko/luatexko.lua @@ -13,8 +13,8 @@ luatexbase.provides_module { name = 'luatexko', - date = '2021/04/20', - version = '3.1', + date = '2021/06/10', + version = '3.2', description = 'typesetting Korean with LuaTeX', author = 'Dohyun Kim, Soojin Nam', license = 'LPPL v1.3+', @@ -72,6 +72,7 @@ local attributes = luatexbase.attributes local call_callback = luatexbase.call_callback local create_callback = luatexbase.create_callback local module_warning = luatexbase.module_warning +local new_attribute = luatexbase.new_attribute local new_user_whatsit = luatexbase.new_user_whatsit local registernumber = luatexbase.registernumber @@ -85,6 +86,7 @@ local glueid = node.id"glue" local glyphid = node.id"glyph" local hlistid = node.id"hlist" local kernid = node.id"kern" +local localparid = node.id"local_par" local mathid = node.id"math" local penaltyid = node.id"penalty" local ruleid = node.id"rule" @@ -98,6 +100,7 @@ local italcorr = 3 local lua_number = 100 local lua_value = 108 local spaceskip = 13 +local indentbox = 3 local nohyphen = registernumber"l@nohyphenation" or -1 -- verbatim local langkor = registernumber"koreanlanguage" or 16383 @@ -107,11 +110,12 @@ local fallbackfontattr = attributes.luatexkofallbackfontattr local autojosaattr = attributes.luatexkoautojosaattr local classicattr = attributes.luatexkoclassicattr local dotemphattr = attributes.luatexkodotemphattr -local ulineattr = attributes.luatexkoulineattr local rubyattr = attributes.luatexkorubyattr local hangulbyhangulattr = attributes.luatexkohangulbyhangulattr local hanjabyhanjaattr = attributes.luatexkohanjabyhanjaattr +local unicodeattr = new_attribute"luatexkounicodeattr" + local stretch_f = 5/100 -- should be consistent for ruby local function get_font_data (fontid) @@ -537,12 +541,15 @@ local special_classes = { [0xFF01] = 4, [0xFF1A] = 2, [0xFF1B] = 2, [0xFF1F] = 4, }, { __index = charclass }), setmetatable({ -- TC - [0x3001] = 3, [0x3002] = 3, [0xFF0C] = 3, [0xFF0E] = 3, + [0x3001] = 3, [0x3002] = 5, [0xFF0C] = 3, [0xFF0E] = 5, }, { __index = charclass }), setmetatable({ -- TC vert - [0x3001] = 3, [0x3002] = 3, [0xFF0C] = 3, [0xFF0E] = 3, + [0x3001] = 3, [0x3002] = 5, [0xFF0C] = 3, [0xFF0E] = 5, [0xFF1A] = 7, [0xFF1B] = 7, -- 0xFE13, 0xFE14 }, { __index = charclass }), + setmetatable({ -- JP vert + [0xFF1B] = 7, -- 0xFE14 + }, { __index = charclass }), } local function get_char_class (c, classic) @@ -684,8 +691,8 @@ local function process_fonts (head) while curr do local id = curr.id if id == glyphid then - local props = my_node_props(curr) - if not props.unicode then + if curr.font ~= 0 -- exclude nullfont + and not has_attribute(curr, unicodeattr) then local c = curr.char @@ -735,7 +742,7 @@ local function process_fonts (head) end end - props.unicode = c + set_attribute(curr, unicodeattr, c) end elseif id == discid then curr.pre = process_fonts(curr.pre) @@ -779,11 +786,11 @@ local function hbox_char_font (box, init, glyfonly) while curr do local id = curr.id if id == glyphid then - local c = my_node_props(curr).unicode or curr.char + local c = has_attribute(curr, unicodeattr) or curr.char if c and not is_cjk_combining(c) then return c, curr.font end - elseif id == hlistid and curr.list then + elseif curr.list then return hbox_char_font(curr, init, glyfonly) elseif not glyfonly and is_blocking_node(curr) then return @@ -858,7 +865,7 @@ local function process_linebreak (head, par) while curr do local id = curr.id if id == glyphid then - local c = my_node_props(curr).unicode or curr.char + local c = has_attribute(curr, unicodeattr) or curr.char if c and not is_unicode_var_sel(c) then local old = has_attribute(curr, classicattr) head, pc, pcl = maybe_linebreak(head, curr, pc, pcl, c, old, curr.font, par) @@ -913,7 +920,7 @@ local function process_interhangul (head, par) while curr do local id = curr.id if id == glyphid then - local c = my_node_props(curr).unicode or curr.char + local c = has_attribute(curr, unicodeattr) or curr.char if c and not is_unicode_var_sel(c) then head, pc = do_interhangul_option(head, curr, pc, c, curr.font, par) @@ -975,7 +982,7 @@ local function process_interlatincjk (head, par) while curr do local id = curr.id if id == glyphid then - local c = my_node_props(curr).unicode or curr.char + local c = has_attribute(curr, unicodeattr) or curr.char if c and not is_unicode_var_sel(c) then head, pc, pf, pcl = do_interlatincjk_option(head, curr, pc, pf, pcl, c, curr.font, par) pc = breakable_after[c] and pc or 0 @@ -1033,7 +1040,7 @@ local function process_glyph_width (head) if curr.lang ~= nohyphen and fontoptions.compresspunctuations[curr.font] then - local cc = my_node_props(curr).unicode or curr.char + local cc = has_attribute(curr, unicodeattr) or curr.char local old = has_attribute(curr, classicattr) local class = get_char_class(cc, old) if class >= 1 and class <= 4 and @@ -1087,11 +1094,11 @@ local function process_remove_spaces (head) local vchar, vfont if id == glyphid and v.lang ~= nohyphen then - local c = my_node_props(v).unicode or v.char or 0 + local c = has_attribute(v, unicodeattr) or v.char or 0 if is_unicode_var_sel(c) then v = getprev(v) or v end - vchar, vfont = my_node_props(v).unicode or v.char, v.font + vchar, vfont = has_attribute(v, unicodeattr) or v.char, v.font elseif id == hlistid and v.list then vchar, vfont = hbox_char_font(v, k == "n") end @@ -1131,6 +1138,23 @@ local josa_table = { [0xC774] = {0xC774, nil, 0xC774}, -- 이(라) = 이, , 이 } +local hanja2hangul = { } + +local function add_to_hanja2hangul (filename, i, last) + local f = kpse.find_file(filename) + if f then + for c in io.lines(f) do + hanja2hangul[i] = tonumber(c) + i = i + 1 + end + else + warning("cannot find %s", filename) + for c = i, last do + hanja2hangul[c] = c + end + end +end + local josa_code = setmetatable({ [0x30] = 3, [0x31] = 1, [0x33] = 3, [0x36] = 3, [0x37] = 1, [0x38] = 1, [0x4C] = 1, [0x4D] = 3, @@ -1156,16 +1180,37 @@ local josa_code = setmetatable({ [0xFF13] = 3, [0xFF16] = 3, [0xFF17] = 1, [0xFF18] = 1, [0xFF2C] = 1, [0xFF2D] = 3, [0xFF2E] = 3, [0xFF4C] = 1, [0xFF4D] = 3, [0xFF4E] = 3, -},{ __index = function(_,c) +},{ __index = function(t, cc) + local c = cc + -- xetexko에 포함된 .tab 파일들을 이용해 한자를 한글로 변환 + if c >= 0x4E00 and c <= 0x9FA5 then + if not hanja2hangul[c] then + add_to_hanja2hangul("hanja_hangul.tab", 0x4E00, 0x9FA5) + end + c = hanja2hangul[c] + elseif c >= 0xF900 and c <= 0xFA2D then + if not hanja2hangul[c] then + add_to_hanja2hangul("hanjacom_hangul.tab", 0xF900, 0xFA2D) + end + c = hanja2hangul[c] + elseif c >= 0x3400 and c <= 0x4DB5 then + if not hanja2hangul[c] then + add_to_hanja2hangul("hanjaexa_hangul.tab", 0x3400, 0x4DB5) + end + c = hanja2hangul[c] + end if is_hangul(c) then c = (c - 0xAC00) % 28 + 0x11A7 end if is_chosong(c) then - return c == 0x1105 and 1 or 3 + c = c == 0x1105 and 1 or 3 + t[cc] = c; return c elseif is_jungsong(c) then - return c ~= 0x1160 and 2 + c = c ~= 0x1160 and 2 + t[cc] = c; return c elseif is_jongsong(c) then - return c == 0x11AF and 1 or 3 + c = c == 0x11AF and 1 or 3 + t[cc] = c; return c elseif is_noncjk_char(c) and c <= 0x7A or c >= 0x2160 and c <= 0x217F -- roman or c >= 0x2460 and c <= 0x24E9 -- ① @@ -1175,11 +1220,13 @@ local josa_code = setmetatable({ or c >= 0xFF10 and c <= 0xFF19 -- 0 or c >= 0xFF21 and c <= 0xFF3A -- A or c >= 0xFF41 and c <= 0xFF5A -- a - then return 2 + then + t[cc] = 2; 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 + then + t[cc] = 3; return 3 end end }) @@ -1188,7 +1235,7 @@ local function prevjosacode (n, parenlevel, ignore_parens) while n do local id = n.id if id == glyphid then - local c = my_node_props(n).unicode or n.char -- beware hlist/vlist + local c = has_attribute(n, unicodeattr) or n.char -- beware hlist/vlist if ignore_parens and c == 0x29 then -- ) parenlevel = parenlevel + 1 elseif ignore_parens and c == 0x28 then -- ( @@ -1287,8 +1334,8 @@ local function process_dotemph (head) elseif curr.id == glyphid then local dotattr = has_attribute(curr, dotemphattr) - if dotattr then - local c = my_node_props(curr).unicode or curr.char + if dotattr and dotemphbox[dotattr] then + local c = has_attribute(curr, unicodeattr) or curr.char if is_hangul(c) or is_compat_jamo(c) or is_chosong(c) or @@ -1425,7 +1472,7 @@ end local ulitems = {} local function process_uline (head, parent, level) - local curr, level, attr = head, level or 0 + local curr, level = head, level or 0 while curr do if curr.list then curr.list = process_uline(curr.list, curr, level+1) @@ -1439,33 +1486,28 @@ local function process_uline (head, parent, level) list = list, subtype = subtype, level = level, + start = getnext(curr) or curr, } else local item = ulitems[value] if item then head = draw_uline(head, curr, parent, item, true) ulitems[value] = nil - attr = nil end end - else - local currattr = has_attribute(curr, ulineattr) - if currattr then - local item = ulitems[currattr] - if item and not item.start then - item.start = curr - attr = currattr - end - end + local to_free = curr + head, curr = noderemove(head, curr) + nodefree(to_free) + goto nextnode end curr = getnext(curr) + ::nextnode:: end - if attr then - local item = ulitems[attr] - if item and item.level == level then + for _, item in pairs(ulitems) do + if item.level == level then head = draw_uline(head, nodeslide(head), parent, item) item.start = nil end @@ -1496,13 +1538,23 @@ local function process_ruby_pre_linebreak (head) local rubyid = has_attribute(curr, rubyattr) if rubyid then local ruby_t = rubybox[rubyid] - if ruby_t[3] then -- rubyoverlap + if ruby_t and 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) + + local prev = curr.prev -- 문단 첫머리에 루비 돌출 방지 + if prev then + if prev.id == localparid or + prev.id == hlistid and prev.subtype == indentbox and prev.width == 0 + then + k.kern = 0 + end + end + head = insert_before(head, curr, k) head = insert_before(head, curr, r) head, curr = insert_after(head, curr, r2) @@ -1540,17 +1592,19 @@ local function process_ruby_post_linebreak (head) -- consider charraise local shift = shift_put_top(curr, ruby) - local _, f = hbox_char_font(curr, true, true) - local ascender = tableunpack(fontoptions.asc_desc[f]) or curr.height - ruby.shift = shift - ascender - ruby.depth - ruby_t[2] -- rubysep + local f, ascender, descender + _, f = hbox_char_font(curr, true, true) + ascender = fontoptions.asc_desc[f][1] or curr.height + _, f = hbox_char_font(ruby, true, true) + descender = fontoptions.asc_desc[f][2] or ruby.depth + + ruby.shift = shift - ascender - descender - ruby_t[2] -- rubysep head = insert_before(head, curr, ruby) end - ruby_t = nil - elseif curr.list then - curr.list = process_ruby_post_linebreak(curr.list) + rubybox[rubyid] = nil end - elseif curr.list then - curr.list = process_ruby_post_linebreak(curr.list) + elseif id == mathid then + curr = end_of_math(curr) end curr = getnext(curr) end @@ -1600,7 +1654,7 @@ local function process_reorder_tonemarks (head) fontoptions.is_not_harf[curr.font] and fontoptions.is_hangulscript[curr.font] then - local uni = my_node_props(curr).unicode or curr.char + local uni = has_attribute(curr, unicodeattr) or curr.char if is_hangul(uni) or is_chosong(uni) or uni == 0x25CC then init = curr elseif is_jungsong(uni) or is_jongsong(uni) then @@ -1609,7 +1663,7 @@ local function process_reorder_tonemarks (head) local n, syllable = init, { init = init } while n do if n.id == glyphid then - local u = my_node_props(n).unicode or n.char + local u = has_attribute(n, unicodeattr) or n.char if u then tableinsert(syllable, u) end end if n == curr then break end @@ -1912,7 +1966,7 @@ end -- charraise -local raiseattr = luatexbase.new_attribute"luatexkoraiseattr" +local raiseattr = new_attribute"luatexkoraiseattr" local function process_charraise (head) local curr = head @@ -1957,7 +2011,7 @@ local function process_fake_slant_corr (head) -- for font fallback tableinsert(t, char_in_font(p.font, p.char).italic or 0) end - local c = my_node_props(p).unicode or p.char + local c = has_attribute(p, unicodeattr) or p.char if is_jungsong(c) or is_jongsong(c) or hangul_tonemark[c] then else break @@ -2034,13 +2088,6 @@ end, local pass_fun = function(...) return ... end create_callback("luatexko_prelinebreak_first", "data", pass_fun) create_callback("luatexko_prelinebreak_second", "data", pass_fun) -create_callback("luatexko_do_atbegshi", "data", pass_fun) - -function luatexko.process_atbegshi (box) - if box and box.list then - box.list = call_callback("luatexko_do_atbegshi", box.list) - end -end add_to_callback("pre_shaping_filter", function(h, gc) local par = gc == "" @@ -2250,14 +2297,20 @@ otfregister { local auxiliary_procs = { dotemph = { - luatexko_do_atbegshi = process_dotemph, + post_linebreak_filter = process_dotemph, + hpack_filter = process_dotemph, }, uline = { - luatexko_do_atbegshi = process_uline, + post_linebreak_filter = function(h) return process_uline(h) end, + hpack_filter = function(h) return process_uline(h) end, }, ruby = { - pre_linebreak_filter = process_ruby_pre_linebreak, - luatexko_do_atbegshi = process_ruby_post_linebreak, + pre_linebreak_filter = function(h) + h = process_ruby_pre_linebreak(h) + h = process_ruby_post_linebreak(h) + return h + end, + hpack_filter = process_ruby_post_linebreak, }, autojosa = { luatexko_prelinebreak_first = process_josa, @@ -2267,9 +2320,16 @@ local auxiliary_procs = { }, } +-- dotemph 등을 수식 한글에서도 작동하게 하려면 +-- post_mlist_to_hlist_filter 콜백을 이용해야 한다. + function luatexko.activate (name) for cbnam, cbfun in pairs( auxiliary_procs[name] ) do - add_to_callback(cbnam, cbfun, "luatexko."..cbnam.."."..name) + local prior + if cbnam == "hpack_filter" then + prior = luatexbase.priority_in_callback(cbnam, "luaotfload.color_handler") or nil + end + add_to_callback(cbnam, cbfun, "luatexko."..cbnam.."."..name, prior) end active_processes[name] = true end @@ -2281,8 +2341,9 @@ function luatexko.deactivateall (str) for _, name in ipairs{ "pre_shaping_filter", "post_shaping_filter", "pre_linebreak_filter", + "hpack_filter", "hyphenate", - "luatexko_do_atbegshi", -- might not work properly + "post_linebreak_filter", } do local t = {} for i, v in ipairs( luatexbase.callback_descriptions(name) ) do @@ -2304,7 +2365,3 @@ function luatexko.reactivateall () luatexko.deactivated = nil end -function luatexko.currenthashangulchars (cnt) - texcount[cnt] = char_in_font(fontcurrent(), 0xAC00) and 1 or 0 -end - diff --git a/macros/luatex/generic/luatexko/luatexko.sty b/macros/luatex/generic/luatexko/luatexko.sty index c44ccedcbe..b3f78f4ba4 100644 --- a/macros/luatex/generic/luatexko/luatexko.sty +++ b/macros/luatex/generic/luatexko/luatexko.sty @@ -12,9 +12,9 @@ % version 2006/05/20 or later. \ifdefined\luatexkohangulfontattr \endinput\fi -\ifdefined\selectfont - \NeedsTeXFormat{LaTeX2e}[2020/10/01] - \ProvidesPackage{luatexko}[2021/04/20 v3.1 typesetting Korean with LuaTeX] +\ifdefined\ProvidesPackage + \NeedsTeXFormat{LaTeX2e}[2021/06/01] + \ProvidesPackage{luatexko}[2021/06/10 v3.2 typesetting Korean with LuaTeX] \RequirePackage{luatexbase} \RequirePackage{fontspec}[2020/02/03] \else @@ -25,52 +25,43 @@ \catcode`\@=11 \fi \newlanguage\koreanlanguage -\protected\def\pdfliteral{\pdfextension literal} \newattribute\luatexkohangulfontattr \newattribute\luatexkohanjafontattr \newattribute\luatexkofallbackfontattr \newattribute\luatexkoautojosaattr \newattribute\luatexkoclassicattr \newattribute\luatexkodotemphattr -\newattribute\luatexkoulineattr \newattribute\luatexkorubyattr \chardef\luatexkorubyalloc\allocationnumber \newattribute\luatexkohangulbyhangulattr \newattribute\luatexkohanjabyhanjaattr \directlua{ require"luatexko" } -\ifdefined\AtBegShi@Hook - \edef\AtBegShi@Hook{% occupy the first position - \unexpanded{% - \AtBegShi@Item{% - \directlua{% - luatexko.process_atbegshi(tex.getbox(\the\AtBeginShipoutBox)) - }% - }% - }% - \unexpanded\expandafter{\AtBegShi@Hook}% - } -\else - \AddToHook{shipout/before}{% - \directlua{ luatexko.process_atbegshi(tex.getbox(\the\ShipoutBox)) }% - } - \DeclareHookRule{shipout/before}{.}{before}{luacolor} -\fi -% classic -\protected\def\typesetclassic{% 0 +% classics +% 0: horizontal KO, JP +% 1: vertical KO, SC +% 2: horizontal SC +% 3: horizontal TC +% 4: vertical TC +% 5: vertical JP +\chardef\luatexkolangCJK=\z@ % 0:KO, 1:JP, 2:SC, 3:TC +\protected\def\typesetclassic{% \ifnum\luatexkoclassicattr<\z@ \luatexkoclassicattr\z@ \parindent1em \fi } -\protected\def\typesetvertical{% 1 - \ifcase\luatexkoclassicattr +\protected\def\typesetvertical{% 1, 4, 5 + \ifcase\luatexkolangCJK \luatexkoclassicattr\@ne \parindent1em \or + \luatexkoclassicattr=5\relax + \parindent1em \or \luatexkoclassicattr\@ne + \parindent2em \or \luatexkoclassicattr=4\relax - \or + \parindent2em \else \luatexkoclassicattr\@ne \parindent1em @@ -78,7 +69,25 @@ } \protected\def\typesetmodern{\unsetattribute\luatexkoclassicattr} \protected\def\inhibitglue{\hskip\z@skip} +\protected\def\japanese{% 0, 5 + \chardef\luatexkolangCJK=\@ne + \ifcase\luatexkoclassicattr + \or + \luatexkoclassicattr=5\relax + \or + \luatexkoclassicattr\z@ + \or + \luatexkoclassicattr\z@ + \or + \luatexkoclassicattr=5\relax + \or + \else + \luatexkoclassicattr\z@ + \fi + \parindent1em + } \protected\def\Schinese{% 2, 1 + \chardef\luatexkolangCJK=\tw@ \ifcase\luatexkoclassicattr \luatexkoclassicattr\tw@ \or @@ -87,12 +96,15 @@ \luatexkoclassicattr\tw@ \or \luatexkoclassicattr\@ne + \or + \luatexkoclassicattr\@ne \else \luatexkoclassicattr\tw@ \fi \parindent2em } \protected\def\Tchinese{% 3, 4 + \chardef\luatexkolangCJK=\thr@@ \ifcase\luatexkoclassicattr \luatexkoclassicattr\thr@@ \or @@ -101,12 +113,14 @@ \luatexkoclassicattr\thr@@ \or \or + \or + \luatexkoclassicattr=4\relax \else \luatexkoclassicattr\thr@@ \fi \parindent2em } -\let\korean\typesetmodern \let\japanese\typesetclassic \let\chinese\Schinese +\let\korean\typesetmodern \let\chinese\Schinese % josa \count@"AC00 \loop @@ -189,19 +203,16 @@ % dotemph \newcount\luatexkodotemphcount \def\dotemphraise{1ex } -\unless\ifdefined\bfseries\let\bfseries\bf\fi -\def\dotemphchar{\bfseries\char"02D9 } +\edef\dotemphchar{\expandafter\noexpand\ifdefined\bfseries\bfseries\else\bf\fi ^^^^02d9} \protected\def\dotemph#1{% \global\advance\luatexkodotemphcount\@ne + \ifnum\luatexkodotemphcount=\@ne \directlua{ luatexko.activate"dotemph" }\fi \begingroup + \count@=\luatexkodotemphcount \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]) - }\luatexkodotemphattr\luatexkodotemphcount #1% - \directlua{ - luatexko.dotemphboundary(\the\luatexkodotemphattr) - }% + \directlua{ luatexko.dotemphbox[\number\count@] = node.copy(tex.box[0]) }% + \luatexkodotemphattr\count@ #1\relax + \directlua{ luatexko.dotemphboundary(\number\count@) }% \endgroup } % uline \newcount\luatexkoulinecount @@ -215,19 +226,15 @@ \luatexkomarkoverwith } \def\luatexkomarkoverwith#1#2{% \global\advance\luatexkoulinecount\@ne + \ifnum\luatexkoulinecount=\@ne \directlua{ luatexko.activate"uline" }\fi \begingroup + \count@=\luatexkoulinecount \leavevmode \setbox\z@\hbox{#1}% - \directlua{ - if \the\luatexkoulinecount == 1 then luatexko.activate("uline") end - luatexko.ulboundary(\the\luatexkoulinecount, - tex.box[0].list, - \luatexkoleaderstype) - }% - \luatexkoulineattr\luatexkoulinecount #2\relax - \directlua{ - luatexko.ulboundary(\the\luatexkoulineattr) - }\endgroup } + \directlua{ luatexko.ulboundary(\number\count@, tex.box[0].list, \luatexkoleaderstype) }% + #2\relax + \directlua{ luatexko.ulboundary(\number\count@) }% + \endgroup } \protected\def\uline{\markoverwith{% \vrule width\z@ height-\ulinedown depth\dimexpr\ulinedown+\ulinewidth\relax }} \protected\def\dashuline{\markoverwith*{% @@ -271,15 +278,16 @@ \let\rubynooverlap\ruby@overlapfalse \rubyoverlap % ruby may hangover neighboring chars \newcount\luatexkorubycount -\def\luatexkorubybasehbox{\hbox attr\luatexkorubyalloc=\luatexkorubycount} \protected\def\ruby#1#2{% \global\advance\luatexkorubycount\@ne + \ifnum\luatexkorubycount=\@ne \directlua{ luatexko.activate"ruby" }\fi \begingroup + \count@=\luatexkorubycount \leavevmode - \setbox\z@\luatexkorubybasehbox{#1}% base + \setbox\z@\hbox attr\luatexkorubyalloc=\count@{#1}% base \setbox\tw@\hbox{% ruby \ifdefined\rubyfont \rubyfont \fi - \ifdefined\selectfont % <- latex + \ifcsname ver@luatexko.sty\endcsname % <- latex \dimen@\dimexpr\rubysize em\relax \fontsize\dimen@\dimen@\selectfont \fi @@ -291,13 +299,13 @@ \ifruby@overlap \dimen@\dimexpr\wd\tw@-\wd\z@-\dimen@ii\relax % overhang .5 rubysize \ifdim\dimen@ > \z@ - \setbox\z@\luatexkorubybasehbox spread\dimen@{% + \setbox\z@\hbox attr\luatexkorubyalloc=\count@ spread\dimen@{% \hskip\z@ plus \luatexkostretchfactor em\relax \unhbox\z@ \hskip\z@ plus \luatexkostretchfactor em\relax }% \fi \else - \setbox\z@\luatexkorubybasehbox to\wd\tw@{% + \setbox\z@\hbox attr\luatexkorubyalloc=\count@ to\wd\tw@{% \hskip\z@ plus \luatexkostretchfactor em\relax \unhbox\z@ \hskip\z@ plus \luatexkostretchfactor em\relax }% @@ -316,8 +324,7 @@ \fi \fi \directlua{ - if \the\luatexkorubycount == 1 then luatexko.activate("ruby") end - luatexko.rubybox[\the\luatexkorubycount] = { + luatexko.rubybox[\number\count@] = { node.copy(tex.box[2]), \number\numexpr\dimexpr\rubysep\relax\relax, \ifruby@overlap true\else false\fi, }}% @@ -337,56 +344,62 @@ % vertical writing \protected\def\vertical#1{% \leavevmode - \setbox\z@\vbox\bgroup - \hsize#1\relax - \typesetvertical } + \ifx\empty#1\empty + \setbox\z@\hbox\bgroup + \else + \setbox\z@\vbox\bgroup \hsize#1\relax + \fi + \typesetvertical } \protected\def\endvertical{% \egroup \luatexkorotatebox\z@ \box\z@ } \protected\def\luatexkorotatebox#1{% - \setbox#1\hbox to\dimexpr\ht#1+\dp#1{% + \dimen@\dimexpr\ht#1+\dp#1\relax + \setbox#1\hbox to\dimen@\bgroup \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 }}} + \vbox to\wd#1\bgroup + \wd#1\z@ + \pdfextension save\relax + \pdfextension setmatrix{0 -1 1 0}\relax + \box#1\relax + \kern-\dimen@ + \pdfextension restore\relax + \vfil + \egroup + \egroup +} % horizontal box inside vertical writing \def\luatexkohorizboxmoveright{0.5ex} \protected\def\horizontal#1{% \directlua{ luatexko.gethorizboxmoveright() }% \leavevmode - \setbox\z@\vbox\bgroup - \hsize#1\relax - \typesetmodern } + \ifx\empty#1\empty + \setbox\z@\hbox\bgroup + \else + \setbox\z@\vbox\bgroup \hsize#1\relax + \fi + \typesetmodern } \protected\def\endhorizontal{% \egroup - \dimen@=\dimexpr.5\wd\z@-\luatexkohorizboxmoveright\relax \luatexkounrotatebox\z@ - \lower\dimen@\box\z@ } + \raise\luatexkohorizboxmoveright + \box\z@ } \protected\def\luatexkounrotatebox#1{% - \setbox#1\hbox to\dimexpr\ht#1+\dp#1{% - \vbox to\wd#1{% + \dimen@\dimexpr\ht#1+\dp#1\relax + \setbox#1\hbox to\dimen@\bgroup + \lower.5\wd#1\vbox to\wd#1\bgroup \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 + \wd#1\z@ + \pdfextension save\relax + \pdfextension setmatrix{0 1 -1 0}\relax + \box#1\relax + \kern-\dimen@ + \pdfextension restore\relax + \egroup + \hfil + \egroup +} % hangul normalize \def\luatexhangulnormalize{% \directlua{require"luatexko-normalize"}% @@ -443,16 +456,51 @@ \fi \fi } % plain -\ifdefined\selectfont\else +\ifcsname ver@luatexko.sty\endcsname\else + % do not verticalize headline/footline. + \def\verticaltypesetting{% + \dimen@\hsize \hsize\vsize \vsize\dimen@ + \edef\plainoutput{\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 + \let\verticaltypesetting\relax % prevent multiple running + } + \def\beginverticaltypesetting{\vfill\supereject \begingroup \verticaltypesetting} + \def\endverticaltypesetting{\vfill\supereject \endgroup} + % hangul fonts \protected\def\hangulfont{% \afterassignment\luatexkohangulselectfont\font\luatexkohangulfont} \protected\def\hanjafont{% \afterassignment\luatexkohanjaselectfont\font\luatexkohanjafont} \protected\def\fallbackfont{% \afterassignment\luatexkofallbackselectfont\font\luatexkofallbackfont} - \newfam\symmathhangul + \def\sethangulfont#1{% + \def\@tempa{#1}\edef\@tempb{\csstring#1}% + \expandafter\afterassignment\expandafter\luatexko@sethangulfont + \expandafter\font\csname luatexko.hangulfont.\@tempb\endcsname } + \def\luatexko@sethangulfont{% + \expandafter\protected\expandafter\edef\@tempa{% + \luatexkohangulfontattr\fontid\csname luatexko.hangulfont.\@tempb\endcsname\relax }} + \def\sethanjafont#1{% + \def\@tempa{#1}\edef\@tempb{\csstring#1}% + \expandafter\afterassignment\expandafter\luatexko@sethanjafont + \expandafter\font\csname luatexko.hanjafont.\@tempb\endcsname } + \def\luatexko@sethanjafont{% + \expandafter\protected\expandafter\edef\@tempa{% + \luatexkohanjafontattr\fontid\csname luatexko.hanjafont.\@tempb\endcsname\relax }} + \def\setfallbackfont#1{% + \def\@tempa{#1}\edef\@tempb{\csstring#1}% + \expandafter\afterassignment\expandafter\luatexko@setfallbackfont + \expandafter\font\csname luatexko.fallbackfont.\@tempb\endcsname } + \def\luatexko@setfallbackfont{% + \expandafter\protected\expandafter\edef\@tempa{% + \luatexkofallbackfontattr\fontid\csname luatexko.fallbackfont.\@tempb\endcsname\relax }} + \hangulfont={UnBatang:mode=node;script=hang;+tlig} at 10pt % default: unbatang \protected\def\setmathhangulfonts#1#2#3{% font identifiers - \ifnum\Umathcharfam"AC00=\symmathhangul\else + \ifnum\Umathcodenum"AC00="AC00\relax + \csname newfam\endcsname\symmathhangul \setmathhangulblock{AC00}{D7A3}% \fi \textfont\symmathhangul=#1\relax @@ -471,17 +519,41 @@ \catcode`\@=\luatexkoatcatcode \endinput\fi % latex -\IfHookExistsTF{selectfont}{ - \AddToHook{selectfont}{% - \luatexkohangulselectfont - \luatexkohanjaselectfont - \luatexkofallbackselectfont } -}{ - \RequirePackage{everysel} - \EverySelectfont{% - \luatexkohangulselectfont - \luatexkohanjaselectfont - \luatexkofallbackselectfont } +% do not verticalize headline/footline +\def\verticaltypesetting{% + \ifx\@nodocument\relax % not in the preamble + \clearpage + \begingroup + \fi + \hsize\textheight \vsize\textwidth + \textwidth\hsize \textheight\vsize + \@colroom\vsize \@colht\vsize + \if@twocolumn + \columnwidth=.5\dimexpr\textwidth-\columnsep\relax + \linewidth\columnwidth \hsize\columnwidth + \else + \linewidth\hsize \columnwidth\hsize + \fi + \expandafter\def\expandafter\@outputpage\expandafter{% + \expandafter\luatexkorotatebox\expandafter\@outputbox + \expandafter\textwidth\expandafter\textheight + \@outputpage + }% + \maxdepth\z@ \typesetvertical + \let\verticaltypesetting\relax % prevent multiple running +} +\def\endverticaltypesetting{% + \clearpage + \endgroup + \global\@colroom\textheight + \global\@colht\textheight + \global\vsize\textheight +} +% hangul fonts +\AddToHook{selectfont}{% + \luatexkohangulselectfont + \luatexkohanjaselectfont + \luatexkofallbackselectfont } \AddToHook{rmfamily}{% \let\luatexkohangulfont \luatexkomainhangulfont @@ -505,26 +577,9 @@ } % fontspec-like \ExplSyntaxOn -\cs_new_nopar:Npn \l_luatexko_silent_upshape { - \tl_map_inline:nn { \mddefault \bfdefault } - { - \cs_if_exist:cF - { \g_fontspec_encoding_tl/\l_fontspec_family_tl/##1/\updefault } - { - \cs_if_exist:cT - { \g_fontspec_encoding_tl/\l_fontspec_family_tl/##1/\shapedefault } - { - \cs_set_eq:cc - { \g_fontspec_encoding_tl/\l_fontspec_family_tl/##1/\updefault } - { \g_fontspec_encoding_tl/\l_fontspec_family_tl/##1/\shapedefault } - } - } - } -} \DeclareDocumentCommand \setmainhangulfont { O{} m O{} } { \fontspec_set_family:Nnn \luatexkomainhangulfamily { Ligatures=TeX, #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn \luatexkomainhangulfont { \fontencoding \g_fontspec_encoding_tl @@ -541,7 +596,6 @@ \DeclareDocumentCommand \setsanshangulfont { O{} m O{} } { \fontspec_set_family:Nnn \luatexkosanshangulfamily { Ligatures=TeX, #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn \luatexkosanshangulfont { \fontencoding \g_fontspec_encoding_tl @@ -558,7 +612,6 @@ \DeclareDocumentCommand \setmonohangulfont { O{} m O{} } { \fontspec_set_family:Nnn \luatexkomonohangulfamily { #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn \luatexkomonohangulfont { \fontencoding \g_fontspec_encoding_tl @@ -575,7 +628,6 @@ \DeclareDocumentCommand \newhangulfontfamily { m O{} m O{} } { \fontspec_set_family:cnn { luatexko_user_family_ \cs_to_str:N #1 } { #2, #4 } { #3 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn #1 { \cs_set_nopar:Npn \luatexkohangulfont @@ -594,7 +646,6 @@ \DeclareDocumentCommand \hangulfontspec { O{} m O{} } { \fontspec_set_family:Nnn \luatexkohangulfontfamily { #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_nopar:Npn \luatexkohangulfont { \fontencoding \g_fontspec_encoding_tl @@ -606,7 +657,6 @@ \DeclareDocumentCommand \setmainhanjafont { O{} m O{} } { \fontspec_set_family:Nnn \luatexkomainhanjafamily { Ligatures=TeX, #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn \luatexkomainhanjafont { \fontencoding \g_fontspec_encoding_tl @@ -623,7 +673,6 @@ \DeclareDocumentCommand \setsanshanjafont { O{} m O{} } { \fontspec_set_family:Nnn \luatexkosanshanjafamily { Ligatures=TeX, #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn \luatexkosanshanjafont { \fontencoding \g_fontspec_encoding_tl @@ -640,7 +689,6 @@ \DeclareDocumentCommand \setmonohanjafont { O{} m O{} } { \fontspec_set_family:Nnn \luatexkomonohanjafamily { #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn \luatexkomonohanjafont { \fontencoding \g_fontspec_encoding_tl @@ -657,7 +705,6 @@ \DeclareDocumentCommand \newhanjafontfamily { m O{} m O{} } { \fontspec_set_family:cnn { luatexko_user_family_ \cs_to_str:N #1 } { #2, #4 } { #3 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn #1 { \cs_set_nopar:Npn \luatexkohanjafont @@ -676,7 +723,6 @@ \DeclareDocumentCommand \hanjafontspec { O{} m O{} } { \fontspec_set_family:Nnn \luatexkohanjafontfamily { #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_nopar:Npn \luatexkohanjafont { \fontencoding \g_fontspec_encoding_tl @@ -689,7 +735,6 @@ \DeclareDocumentCommand \setmainfallbackfont { O{} m O{} } { \fontspec_set_family:Nnn \luatexkomainfallbackfamily { Ligatures=TeX, #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn \luatexkomainfallbackfont { \fontencoding \g_fontspec_encoding_tl @@ -706,7 +751,6 @@ \DeclareDocumentCommand \setsansfallbackfont { O{} m O{} } { \fontspec_set_family:Nnn \luatexkosansfallbackfamily { Ligatures=TeX, #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn \luatexkosansfallbackfont { \fontencoding \g_fontspec_encoding_tl @@ -723,7 +767,6 @@ \DeclareDocumentCommand \setmonofallbackfont { O{} m O{} } { \fontspec_set_family:Nnn \luatexkomonofallbackfamily { #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn \luatexkomonofallbackfont { \fontencoding \g_fontspec_encoding_tl @@ -740,7 +783,6 @@ \DeclareDocumentCommand \newfallbackfontfamily { m O{} m O{} } { \fontspec_set_family:cnn { luatexko_user_family_ \cs_to_str:N #1 } { #2, #4 } { #3 } - \l_luatexko_silent_upshape \cs_set_protected_nopar:Npn #1 { \cs_set_nopar:Npn \luatexkofallbackfont @@ -759,7 +801,6 @@ \DeclareDocumentCommand \fallbackfontspec { O{} m O{} } { \fontspec_set_family:Nnn \luatexkofallbackfontfamily { #1, #3 } { #2 } - \l_luatexko_silent_upshape \cs_set_nopar:Npn \luatexkofallbackfont { \fontencoding \g_fontspec_encoding_tl @@ -771,9 +812,8 @@ \DeclareDocumentCommand \setmathhangulfont { O{} m O{} } { \fontspec_set_family:Nnn \luatexkomathhangulfamily { #1, #3 } { #2 } - \l_luatexko_silent_upshape \DeclareSymbolFont { mathhangul } - \g_fontspec_encoding_tl \luatexkomathhangulfamily \mddefault \updefault + \g_fontspec_encoding_tl \luatexkomathhangulfamily \seriesdefault \shapedefault \int_compare:nNnF { \Umathcharfam"AC00 } = { \symmathhangul } { \setmathhangulblock{AC00}{D7A3} @@ -785,22 +825,11 @@ \let\adhochanjafont\hanjafontspec \let\adhocfallbackfont\fallbackfontspec % interhangul, interlatincjk, charraise -\def\luatexkosilentupshape{% - \@tfor \@tempa := \mddefault \bfdefault \do {% - \ifcsname \f@encoding/\f@family/\@tempa/\updefault\endcsname \else - \ifcsname \f@encoding/\f@family/\@tempa/\shapedefault\endcsname - \global \expandafter\let - \csname \f@encoding/\f@family/\@tempa/\updefault \expandafter\endcsname - \csname \f@encoding/\f@family/\@tempa/\shapedefault\endcsname - \fi\fi - }% -} \protected\def\addhangulfontfeature#1{% \begingroup \ifdefined\luatexkohangulfont - \fontseries\mddefault\fontshape\shapedefault \luatexkohangulfont \fi + \fontseries\seriesdefault\fontshape\shapedefault \luatexkohangulfont \fi \addfontfeature{#1}% - \luatexkosilentupshape \edef\x{\endgroup \def\noexpand\luatexkohangulfont{% \noexpand\fontencoding{\UTFencname}% @@ -811,9 +840,8 @@ \protected\def\addhanjafontfeature#1{% \begingroup \ifdefined\luatexkohanjafont - \fontseries\mddefault\fontshape\shapedefault \luatexkohanjafont \fi + \fontseries\seriesdefault\fontshape\shapedefault \luatexkohanjafont \fi \addfontfeature{#1}% - \luatexkosilentupshape \edef\x{\endgroup \def\noexpand\luatexkohanjafont{% \noexpand\fontencoding{\UTFencname}% @@ -824,9 +852,8 @@ \protected\def\addfallbackfontfeature#1{% \begingroup \ifdefined\luatexkofallbackfont - \fontseries\mddefault\fontshape\shapedefault \luatexkofallbackfont \fi + \fontseries\seriesdefault\fontshape\shapedefault \luatexkofallbackfont \fi \addfontfeature{#1}% - \luatexkosilentupshape \edef\x{\endgroup \def\noexpand\luatexkofallbackfont{% \noexpand\fontencoding{\UTFencname}% @@ -854,25 +881,22 @@ \AtBeginDocument{ % default hangul fonts \ifdefined\luatexkomainhangulfont\else - \begingroup\rmfamily - \directlua{ luatexko.currenthashangulchars(255) }\expandafter\endgroup - \ifnum\count@=\z@ + \begingroup\rmfamily\expandafter\endgroup + \iffontchar\font"AC00 \else \IfFontExistsTF{UnBatang.ttf} {\setmainhangulfont{UnBatang}[Script=Hangul,Language=Korean]} {} \fi \fi \ifdefined\luatexkosanshangulfont\else - \begingroup\sffamily - \directlua{ luatexko.currenthashangulchars(255) }\expandafter\endgroup - \ifnum\count@=\z@ + \begingroup\sffamily\expandafter\endgroup + \iffontchar\font"AC00 \else \IfFontExistsTF{UnDotum.ttf}{\setsanshangulfont{UnDotum}}{} \fi \fi \ifdefined\luatexkomonohangulfont\else - \begingroup\ttfamily - \directlua{ luatexko.currenthashangulchars(255) }\expandafter\endgroup - \ifnum\count@=\z@ + \begingroup\ttfamily\expandafter\endgroup + \iffontchar\font"AC00 \else \def\@tempa{lmtt}\def\@tempb{LatinModernMono(0)} \ifnum 0\ifx\ttdefault\@tempa 1\else\ifx\ttdefault\@tempb 1\fi\fi >\z@ \IfFontExistsTF{UnDotum.ttf} diff --git a/macros/luatex/generic/minim-xmp/EUPL-1.2-EN.txt b/macros/luatex/generic/minim-xmp/EUPL-1.2-EN.txt new file mode 100644 index 0000000000..4153cd3775 --- /dev/null +++ b/macros/luatex/generic/minim-xmp/EUPL-1.2-EN.txt @@ -0,0 +1,287 @@ + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined +below) which is provided under the terms of this Licence. Any use of the Work, +other than as authorised under this Licence is prohibited (to the extent such +use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- ‘The Licence’: this Licence. + +- ‘The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- ‘Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This Licence + does not define the extent of modification or dependence on the Original Work + required in order to classify a work as a Derivative Work; this extent is + determined by copyright law applicable in the country mentioned in Article 15. + +- ‘The Work’: the Original Work or its Derivative Works. + +- ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- ‘The Executable Code’: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- ‘Contributor(s)’: any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case may + be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Work, of the exhaustion of those rights or of other applicable limitations +thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of the +Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions on +the Work or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed under +a Compatible Licence, this Distribution or Communication can be done under the +terms of this Compatible Licence. For the sake of this clause, ‘Compatible +Licence’ refers to the licences listed in the appendix attached to this Licence. +Should the Licensee's obligations under the Compatible Licence conflict with +his/her obligations under this Licence, the obligations of the Compatible +Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, +the Licensee will provide a machine-readable copy of the Source Code or indicate +a repository where this Source will be easily and freely available for as long +as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such damage. +However, the Licensor will be liable under statutory product liability laws as +far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional agreement, +defining obligations or services consistent with this Licence. However, if +accepting obligations, You may act only on your own behalf and on your sole +responsibility, not on behalf of the original Licensor or any other Contributor, +and only if You agree to indemnify, defend, and hold each Contributor harmless +for any liability incurred by, or claims asserted against such Contributor by +the fact You have accepted any warranty or additional liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of electronic +communication by You (for example, by offering to download the Work from a +remote location) the distribution channel or media (for example, a website) must +at least provide to the public the information requested by the applicable law +regarding the Licensor, the Licence and the way it may be accessible, concluded, +stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions of +this Licence or updated versions of the Appendix, so far this is required and +reasonable, without reducing the scope of the rights granted by the Licence. New +versions of the Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty on + the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive jurisdiction + of the competent court where the Licensor resides or conducts its primary + business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the above +licences without producing a new version of the EUPL, as long as they provide +the rights granted in Article 2 of this Licence and protect the covered Source +Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a new +EUPL version. diff --git a/macros/luatex/generic/minim-xmp/README b/macros/luatex/generic/minim-xmp/README new file mode 100644 index 0000000000..0383085ee2 --- /dev/null +++ b/macros/luatex/generic/minim-xmp/README @@ -0,0 +1,30 @@ +Version: 2021/1.0 + +SUMMARY + +This package enables the inclusion of XMP (eXtensible Metadata Platform) data +in the pdf output generated by (plain) LuaTeX. The use of XMP is required by +PDF standards such as PDF/A. + +In-line definition of new metadata schemas is supported, as well as setting +and querying metadata values programmatically. For PDF/A documents, the +necessary PDF/A extension schema definitions will be generated and included +automatically. + + +DOCUMENTATION + +In order to obtain the typeset manual of this package, simply say: + + luatex minim-xmp.doc + + +COPYING + +(c) 2021 Esger Renkema + +These files may be distributed under the terms of the European Union Public +Licence (EUPL) version 1.2 or later. A copy can be obtained at: + +https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + diff --git a/macros/luatex/generic/minim-xmp/minim-xmp.doc b/macros/luatex/generic/minim-xmp/minim-xmp.doc new file mode 100644 index 0000000000..01d2084f6c --- /dev/null +++ b/macros/luatex/generic/minim-xmp/minim-xmp.doc @@ -0,0 +1,196 @@ + +\input minim-doc.sty + +\manual{minim-xmp} + +\ifchapter \chapter Metadata + +\else \startmetadata + author {Esger Renkema} + title {minim-xmp} + date {2021-06-01} + version {2021/1.0} + keywords {LuaTeX; Plain TeX; XMP; metadata; PDF/A;} + stopmetadata +\maketitle \fi + +This package enables simple XMP (eXtensible Metadata Platform) packet inclusion +and will automatically generate pdf/a extension schemas. Use it by saying +⟦\input minim-xmp.tex⟧. +Thereafter, you can use ⟦*\setmetadata key {value}⟧ and ⟦*\getmetadata key⟧ for +setting and retrieving document-level metadata values. + +You do not need this package if you have your metadata ready-made in a separate +file, for then you can simply say +\startelement{Code} +\startsinglecontentitem$$\halign{#\hfil\cr +⟦\immediate\pdfextension obj uncompressed⟧ \cr +\qquad⟦stream attr {/Type/Metadata /Subtype/XML}⟧ \cr +\qquad⟦file {your-file.xmp}⟧ \cr +⟦\pdfextension catalog⟧ \cr +\qquad⟦{/Metadata \pdffeedback lastobj 0 R}⟧\cr +}$$\stopsinglecontentitem +\vskip-\belowdisplayskip + +\section Setting metadata + +Metadata fields that contain (ordered or unordered) lists will be split on the +⟦*\metadataseparator⟧ character; this is a semicolon by default. +Alternatively, you can just make multiple assignments: these will be +concatenated. + +Where applicable, language alternatives can be given like +⟦\setmetadata /de⟧ ⟦dc:title {...}⟧ or +⟦\setmetadata /{de_DE} dc:title {...}⟧. +Braces are necessary in the second case because the catcode of the underscore +is not 11~or~12. +When no alternative is given, the value ⟦*x-default⟧ will be used. + +Instead of using ⟦*\setmetadata⟧, multiple fields can be set in one go with +⟦*\startmetadata⟧. +This way is particularly useful when assigning structured data to a key (see +later on). +In this example, ⟦key1⟧ contains a normal value, ⟦key2⟧ language alternatives +and ⟦key3⟧ structured data: + +⟦\startmetadata + key1 {...} + key2 {... (default) ...} + /alt1 {...} + /alt2 {...} + key3 + /field1 {...} + /field2 {...} + stopmetadata⟧ + +Since metadata values are read by lua as text, linebreaks and paragraphs are +not preserved. You can work around this by saying +⟦{\def\par{\Uchar"A\Uchar"A} \setmetadata abstract {...}}⟧. + +\section Getting metadata + +Metadata values can be retrieved again with ⟦*\getmetadata key⟧. This command +is fully expandible. + +If the data is a list, it will be returned according to the current value of +⟦\metadataseparator⟧. +If the data has language alternatives, the ⟦x-default⟧ value will be returned: +the alternatives are accessible by ⟦*\getmetadata /lang key⟧. + +For structured types (discussed below), ⟦\getmetadata /field key⟧ will return +the value of a single field and ⟦\getmetadata key⟧ will return all fields as +⟦/{field1} {value1} /{field2} {value2} ...⟧ (this can be used again as input to +⟦\startmetadata⟧). + +\section Supported metadata keys + +Initially, the ⟦\setmetadata⟧ and ⟦\getmetadata⟧ recognise all pdf/a compatible +fields in the ⟦pdf⟧, ⟦pdfaid⟧, ⟦dc⟧, ⟦xmp⟧, ⟦xmpMM⟧ and ⟦xmpRights⟧ namespaces. +Keys should be prefixed with their namespace, e.g. ⟦dc:creator⟧ or +⟦xmp:CreatorTool⟧. +Note that the ⟦dc⟧ namespace has lower-case fields; field names are +case-sensitive. + +Because the precise details of the above metadata namespaces can be confusing, +you might want use one of the aliases +⟦author⟧ (dc:creator), ⟦title⟧ (dc:title), ⟦date⟧ (dc:date and xmp:CreateDate), +⟦language⟧ (dc:language), ⟦keywords⟧ (dc:subject and pdf:Keywords), ⟦publisher⟧ +(dc:publisher), ⟦abstract⟧ (dc:description), ⟦copyright⟧ (dc:rights), ⟦version⟧ +(xmpMM:VersionID) and ⟦identifier⟧ (dc:identifier). +New aliases can be defined in the ⟦aliases⟧ table of the lua module. + + +\section Adding new keys and schemas + +\directlua{require('minim-xmp').add_namespace( + 'Example namespace', 'colours', + 'http://example.com/minim/colours/', { + Favourite = { 'Colour', 'The author’s favourite colour' }, + }, { + Colour = { 'RGB Colour', { + R = { 'Integer', 'Red component' }, + G = { 'Integer', 'Green component' }, + B = { 'Integer', 'Blue component' } + }, prefix = 'c' }, + })} +\startmetadata colours:Favourite /R 0 /G 0 /B 255 stopmetadata + +New metadata namespaces can be added in the following way: + +⟦require('minim-xmp').add_namespace( + 'Example namespace', 'colours', + 'http://example.com/minim/colours/', { + -- metadata keys + Favourite = { 'Colour', 'The author’s favourite colour' }, + }, { + -- value types + Colour = { 'RGB Colour', { + R = { 'Integer', 'Red component' }, + G = { 'Integer', 'Green component' }, + B = { 'Integer', 'Blue component' } + }, prefix = 'c' }, + })⟧ + +This will setup a namespace with prefix ⟦colours⟧ and one key: ⟦Favourite⟧, of +type ⟦Colour⟧. That value type happens to be a structured type with three +fields, which are also described. +You can now use this namespace as + +⟦\startmetadata colours:Favourite /R 0 /G 0 /B 255 stopmetadata⟧ + +or the equivalent but more verbose + +⟦\setmetadata/R colours:Favourite 0 +\setmetadata/G colours:Favourite 0 +\setmetadata/B colours:Favourite 255⟧ + +after which the generated XMP code will be + +⟦ + + 255 + 0 + 0 + + ⟧ + +You probably will not need defining your own value types, +so in most cases you should omit the fifth argument to ⟦add_namespace⟧. +If you do define a new value type, you can specify its prefix if it differs +from the namespace prefix (as is done above) and likewise its ⟦uri⟧ identifier +if it differs from the namespace URI. + +List types can be given as ⟦'Bag TypeName'⟧ or ⟦'Seq TypeName'⟧; language +alternatives as ⟦'Lang Alt'⟧. All other types will be treated as ⟦'Text'⟧, +though for ⟦'Boolean'⟧, ⟦'Integer'⟧ and ⟦'Date'⟧ some validation is performed +when setting values. + +Additional metadata value type handling can be defined in the ⟦getters⟧ and +⟦setters⟧ tables of the lua module. +Appropriate entries to those tables will be generated automatically for new +structured types (which is why you could set the colour like we did above). +Value types without fields, however, will be stored and retrieved as if they +were ⟦Text⟧ until you provide another way. + + +\section Generated XMP + +All metadata will be written to the PDF file uncompressed. + +The ⟦*\metadatamodification⟧ setting controls whether XMP packets will be +marked as read-only (value~0; default) or writeable (value~1). +Writeable XMP packets will be padded with about 2\thinspace kB of whitespace. + +If the document-level metadata contains values in the ⟦pdfaid⟧ namespace, +metadata extension schemas will be appended to the document-level metadata +packet automatically. +These extension schema’s will only include keys that have been set somewhere, +though they need not have been set in the document-level metadata. +No extension schemas are generated for the built-in namespaces, as they are +already included in the pdf/a standards. + + +\endmanual + diff --git a/macros/luatex/generic/minim-xmp/minim-xmp.lua b/macros/luatex/generic/minim-xmp/minim-xmp.lua new file mode 100644 index 0000000000..ba01344ccd --- /dev/null +++ b/macros/luatex/generic/minim-xmp/minim-xmp.lua @@ -0,0 +1,583 @@ + +local M = { } +local alloc = require('minim-alloc') +alloc.remember('minim-xmp') + +local function sorted_pairs(t) + local keys, i = { }, 0 + for k, _ in pairs(t) do table.insert(keys, k) end + table.sort(keys) + return function() + i = i + 1 + local k = keys[i] + if k then + return k, t[k] + end + end +end + +-- 1 Reading and writing metadata values + +M.getters = { + Text = function(v) return v or '' end, + ['Lang Alt'] = function(t, a) return t[a or 'x-default'] or '' end, +} + +M.setters = { + Text = function(new, orig, opt, k) + if opt then alloc.msg('Warning: no alternative for %s', k) end + if orig then alloc.msg('Warning: overwriting metadata field %s', k) end + return new + end, + ['Lang Alt'] = function(value, orig, lang) + orig = orig or { } + orig[lang or 'x-default'] = value + return orig + end, + Integer = function(new, orig, opt, k) + if opt then alloc.msg('Warning: no alternative for %s', k) end + if orig then alloc.msg('Warning: overwriting metadata field %s', k) end + return tonumber(new) and new or alloc.err('Not a number: %s', new) + end, + Date = function(new, orig, opt, k) + if opt then alloc.msg('Warning: no alternative for %s', k) end + if orig then alloc.msg('Warning: overwriting metadata field %s', k) end + return string.match(new, '^%d%d%d%d') + and new or alloc.err('Date must start with year') + end, + Boolean = function(s) + return s == 'True' + or s == 'False' + and s or alloc.err('Boolean value must be True or False') + end, +} + +-- 1 Namespace definitions + +local namespace_info = alloc.saved_table ('metadata namespace info') + +function M.add_key(prefix, name, t) + namespace_info[prefix].keys[name] = { + type = t.type or t[1], + description = t.description or t[2], + isInternal = t.isInternal or t[3] or false, + inUse = false, + } +end + +function M.add_valuetype(prefix, name, t) + local ns = namespace_info[prefix] + ns.valuetypes = ns.valuetypes or { } + ns.valuetypes[name] = { + description = t.description or t[1], + uri = t.uri or ns.uri, + prefix = t.prefix or ns.prefix, + inUse = false, + } + t.fields = t.fields or t[2] + if t.fields then + local fields = { } + ns.valuetypes[name].fields = fields + for n, f in pairs(t.fields) do + fields[n] = { type = f.type or f[1], description = f.description or f[2] } + end + M.getters[name] = function(t, k) + if k then + return (fields[k] or alloc.err('Unknown field ‘%s’', k)) + and t and t[k] or 'not found' + else + local res = { } + for k, v in sorted_pairs(t) do + table.insert(res, string.format('/{%s} {%s}', k, v)) + end + return table.concat(res, ' ') + end + end + M.setters[name] = function(value, orig, key, k) + if not key then + alloc.err('Missing key for metadata field %s', k) + elseif not fields[key] then + alloc.err('Unknown field ‘%s’', key) + else + orig = orig or { } + local setter = M.setters[fields[key].type] or M.setters.Text + orig[key] = setter(value, orig[key], nil, k) + end + return orig + end + end +end + +function M.add_namespace(description, prefix, uri, keys, valuetypes, predefined) + namespace_info[prefix] = { + uri = uri, + description = description, + keys = { }, + predefined = predefined or false, + inUse = false, + } + for name, t in pairs(keys) do + M.add_key(prefix, name, t) + end + for name, t in pairs(valuetypes or { }) do + M.add_valuetype(prefix, name, t) + end +end + +local function get_resource_type(ns, vtype) + local vtypes = namespace_info[ns].valuetypes + return vtypes and vtypes[vtype] and vtypes[vtype].fields + and vtypes[vtype] or false +end + +local function parse_type_name(vtype) + local c = string.sub(vtype, 1, 4) + local t = string.sub(vtype, 5) + if c == 'Bag ' then + return t, 'Bag' + elseif c == 'Seq ' then + return t, 'Seq' + else + return vtype, false + end +end + +-- 1 Built-in namespaces + +M.add_namespace('Adobe PDF', 'pdf', 'http://ns.adobe.com/pdf/1.3/', { + Keywords = { 'Text', 'Keywords' }, + PDFVersion = { 'Text', 'PDF version ', true}, + Producer = { 'AgentName', 'Creator tool', true }, +}, nil, true) + +M.add_namespace('PDF/A Identification', 'pdfaid', 'http://www.aiim.org/pdfa/ns/id/', { + amd = { 'Text', 'Amendment identifier', true }, + corr = { 'Text', 'Corrigendum identifier', true }, + conformance = { 'Text', 'Conformance level', true }, + part = { 'Integer', 'Version identifier', true }, +}, nil, true) + +M.add_namespace('Dublin Core', 'dc', 'http://purl.org/dc/elements/1.1/', { + contributor = { 'Bag ProperName', 'Other contributors' }, + coverage = { 'Text', 'Extent or scope' }, + creator = { 'Seq ProperName', 'Authors' }, + date = { 'Seq Date', 'Relevant dates' }, + description = { 'Lang Alt', 'Description of contents' }, + format = { 'MIMEType', 'File format (application/pdf)', true }, + identifier = { 'Text', 'Unique identifier' }, + language = { 'Bag Locale', 'Used languages', true }, + publisher = { 'Bag ProperName', 'Publishers' }, + relation = { 'Bag Text', 'Relationships to other documents', true }, + rights = { 'Lang Alt', 'Copyright statement' }, + source = { 'Text', 'Document source unique identifier' }, + subject = { 'Bag Text', 'Keywords' }, + title = { 'Lang Alt', 'Title' }, + type = { 'Bag Text', 'Document type' }, +}, nil, true) + +M.add_namespace('XMP Basic', 'xmp', 'http://ns.adobe.com/xap/1.0/', { + BaseUrl = { 'URL', 'Base for relative URLs in the document', true }, + CreateDate = { 'Date', 'Creation date' }, + CreatorTool = { 'AgentName', 'Creator tool', true }, + Identifier = { 'Bag Text', 'Multiple identifiers' }, + MetadataDate = { 'Date', 'Metadata modification date', true }, + ModifyDate = { 'Date', 'Document modification date', true }, + Nickname = { 'Text', 'Informal name' }, +}, nil, true) + +M.add_namespace('XMP Rights Management', 'xmpRights', 'http://ns.adobe.com/xap/1.0/rights/', { + Certificate = { 'URL', 'Online rights management cerfificate' }, + Marked = { 'Boolean', 'Indicates this is a rights-managed resource' }, + Owner = { 'Bag ProperName', 'Right holders' }, + UsageTerms = { 'Lang Alt', 'Instructions on usage' }, + WebStatement = { 'URL', 'Location of rights statement' }, +}, nil, true) + +M.add_namespace('XMP Media Managament', 'xmpMM', 'http://ns.adobe.com/xap/1.0/mm/', { + DerivedFrom = { 'ResourceRef', 'Reference to original document', true }, + DocumentID = { 'URI', 'UUID-based identifier across versions', true }, + History = { 'Seq ResourceEvent', 'Human-readable history of changes', true }, + ManagedFrom = { 'ResourceRef', 'Reference to unmanaged document', true }, + Manager = { 'AgentName', 'Asset management system', true }, + ManagerVariant = { 'Text', 'Particular variant', true }, + ManageTo = { 'URI', 'Managed resource identifier', true }, + ManageUI = { 'URI', 'Web browser interface', true }, + RenditionClass = { 'RenditionClass', 'Rendition class name', true }, + RenditionParams = { 'Text', 'Additional rendition parameters', true }, + VersionID = { 'Text', 'Document version identifier', true }, + Versions = { 'Seq Version', 'Version history', true }, +}, { + ResourceEvent = { 'Reference to a resource', { + action = { 'Text', 'Action that occurred' }, + instanceID = { 'URI', 'Instance ID of modifier resource' }, + parameters = { 'Text', 'Additional description' }, + softwareAgent = { 'AgentName', 'Software agent performing the action' }, + when = { 'Date', 'Optional timestamp of the action' }, + }, prefix = 'stEvt', uri = 'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#' }, + ResourceRef = { 'Reference to a resource', { + instanceID = { 'URI', 'referenced xmpMM:instanceID' }, + documentID = { 'URI', 'referenced xmpMM:documentID' }, + versionID = { 'Text', 'referenced xmpMM:versionID' }, + renditionClass = { 'RenditionClass', 'referenced xmpMM:renditionClass' }, + renditionParams = { 'Text', 'referenced xmpMM:renditionParams' }, + manager = { 'AgentName', 'referenced xmpMM:manager' }, + managerVariant = { 'Text', 'referenced xmpMM:managerVariant' }, + manageTo = { 'URI', 'referenced xmpMM:manageTo' }, + manageUI = { 'URI', 'referenced xmpMM:manageUI' }, + }, prefix = 'stRef', uri = 'http://ns.adobe.com/xap/1.0/sType/ResourceRef#' }, + Version = { 'Document version', { + comments = { 'Text', 'Description of changes' }, + event = { 'Text', 'Description of changes' }, + modifyDate = { 'Date', 'Check-in date' }, + modifier = { 'ProperName', 'Person making the modifications' }, + version = { 'Text', 'New version number' }, + }, prefix = 'stVer', uri = 'http://ns.adobe.com/xap/1.0/sType/Version#' }, +}, true) + +-- 1 Writing metadata packets + +local XMP = alloc.saved_table('metadata values') +XMP[1] = XMP[1] or { dc = { format = 'application/pdf' } } -- document-level metadata + +function M.push_metadata() + XMP[#XMP+1] = { } +end + +local function get_xmp() + return XMP[#XMP] +end + +local function escape_xml_content(s) + -- NB not to be used for attribute values + s = string.gsub(s, '&', '&') + return string.gsub(s, '<', '<') +end + +-- 2 extension schemas + +local function get_metadata_extensions() + local namespaces, anyInUse = { }, false + for prefix, ns in pairs(namespace_info) do + if not ns.predefined and ns.inUse then + namespaces[prefix] = ns + anyInUse = true + end + end + if not anyInUse then return false end + local rv = { [[ + + ]] } + local add = function (...) table.insert(rv, string.format(...)) end + for prefix, ns in sorted_pairs(namespaces) do + add(' ') + add(' %s', ns.description) + add(' %s', ns.uri) + add(' %s', prefix) + table.insert(rv, ' \n ') + for name, key in sorted_pairs(ns.keys) do if key.inUse then + add(' ') + add(' %s', name) + add(' %s', key.type) + add(' %s', key.isInternal and 'internal' or 'external') + add(' %s', key.description) + add(' ') + end end + add(' \n ') + if ns.valuetypes then + add(' \n ') + for name, vtype in sorted_pairs(ns.valuetypes) do + if vtype.inUse then + add(' ') + add(' %s', name) + if vtype.fields then + add(' %s', vtype.uri) + add(' %s', vtype.prefix) + end + add(' %s', vtype.description) + if vtype.fields then + add(' \n ') + for name, field in sorted_pairs(vtype.fields) do + add(' ') + add(' %s', name) + add(' %s', field.type) + add(' %s', field.description) + add(' ') + end + add(' \n ') + end + add(' ') + end + end + add(' \n ') + end + add(' ') + end + add(' \n \n ') + return table.concat(rv, '\n') +end + +-- 2 main xmp packet + +local function write_fields(add, res, value, pre, name, indent) + indent = indent or '' + if res then + add('%s <%s:%s rdf:parseType="Resource">', indent, pre, name) + for k, _ in sorted_pairs(res.fields) do + if value[k] then + add('%s <%s:%s>%s', indent, res.prefix, k, value[k], res.prefix, k) + end + end + add('%s ', indent, pre, name) + else + add('%s <%s:%s>%s', indent, pre, name, escape_xml_content(value), pre, name) + end +end + +local function make_xmp_packet(xmp) + local rv = { } ; local add = function(...) table.insert(rv, string.format(...)) end + -- wrapper and opening tags + add('') + add('') + add('') + -- namespaces + for id, keys in sorted_pairs(xmp) do + local info = namespace_info[id] + -- xml namespaces in enclosing description element + add(' ' + -- metadata namespace contents + for key, value in sorted_pairs(keys) do + local vtype, lst = parse_type_name(info.keys[key].type) + local res = get_resource_type(id, vtype) + -- list values + if lst then + add(' <%s:%s>\n ', id, key, lst) + for _, v in ipairs(value) do + write_fields(add, res, v, 'rdf', 'li', ' ') + end + add(' \n ', lst, id, key) + -- language alternatives + elseif vtype == 'Lang Alt' then + add(' <%s:%s>\n ', id, key) + for lang, v in sorted_pairs(value) do + add(' %s', lang, escape_xml_content(v)) + end + add(' \n ', id, key) + -- simple values + else + if lst then value = value[1] end + write_fields(add, res, value, id, key) + end + end + add(' ') + end + -- extension schemas + if #XMP == 1 and xmp.pdfaid then + local schemas = get_metadata_extensions() + if schemas then add(schemas) end + end + -- closing tags and wrapper + local editable = tex.count['metadatamodification'] > 0 + add('\n') + if editable then add(string.rep(string.rep(' ', 80), 25, '\n')) end + add('', editable and 'w' or 'r') + return table.concat(rv, '\n') +end + +-- 2 + +function M.write_metadata() + local xmp = make_xmp_packet(XMP[#XMP]) + local nr = pdf.obj { + type = 'stream', + attr = '/Type/Metadata /Subtype/XML', + immediate = true, + compresslevel = 0, + string = xmp, + } + XMP[#XMP] = nil + return nr +end + +-- 1 Getting and setting metadata values + +local function get_metadata_info(fullkey) + local id, k = string.match(fullkey, '([^:]+):(.+)') + if not id then return alloc.err('Malformed metadata key %s', fullkey) end + local ns = namespace_info[id] + if not ns then return alloc.err('Unknown namespace %s', id) end + local key = ns.keys[k] + if not key then return alloc.err('Unknown key %s in namespace %s', k, id) end + local vtype, lst = parse_type_name(key.type) + ns.inUse, key.inUse = true, true + if ns.valuetypes and ns.valuetypes[vtype] then ns.valuetypes[vtype].inUse = true end + return id, k, lst, vtype +end + +local function get_separator() + return string.utfcharacter(tex.count['metadataseparator']) +end + +function M.get_metadata(k, a) + local xmp = get_xmp() + local ns, key, lst, vtype = get_metadata_info(k) + local getter = M.getters[vtype] or M.getters.Text + if not ns or not xmp[ns] then + return '' + elseif lst then + local rv = { } + for _, v in ipairs(xmp[ns][key]) do + table.insert(rv, getter(v, a)) + end + return table.concat(rv, get_separator()) + else + return getter(xmp[ns][key], a) + end +end + +local function split_metadata_input(s) + local rv = string.explode(s, get_separator()) + for i = 1, #rv do + rv[i] = string.gsub(rv[i], '^ *', '') + rv[i] = string.gsub(rv[i], ' *$', '') + end + return rv +end + +function M.set_metadata(k, v, o) + local ns, key, lst, vtype = get_metadata_info(k) + if not ns then return end -- an error has already occurred + local xmp = get_xmp() + xmp[ns] = xmp[ns] or { } + local setter = M.setters[vtype] or M.setters.Text + if not v or v == '' then + xmp[ns][key] = nil + elseif lst then + local l = xmp[ns][key] or { } + xmp[ns][key] = l + local nst = namespace_info[ns].valuetypes + local f = nst and nst[vtype] and nst[vtype].fields + if o and #l>0 and f and f[o] and not l[#l][o] then + -- special case: add to property type + l[#l] = setter(v, l[#l], o, l) + else + for _, val in ipairs(split_metadata_input(v)) do + table.insert(l, setter(val, nil, o, k)) + end + end + else + xmp[ns][key] = setter(v, xmp[ns][key], o, k) + end +end + +-- 1 Callbacks and tex interface + +M.aliases = { + author = { 'dc:creator' }, + title = { 'dc:title' }, + date = { 'dc:date', 'xmp:CreateDate', 'xmp:ModifyDate' }, + language = { 'dc:language' }, + keywords = { 'dc:subject', + also = function(values) + local kw = M.get_metadata('pdf:Keywords') + if kw ~= '' then kw = kw..', ' end + M.set_metadata('pdf:Keywords') -- prevent warning + M.set_metadata('pdf:Keywords', kw..table.concat(split_metadata_input(values), ', ')) + end }, + publisher = { 'dc:publisher' }, + abstract = { 'dc:description' }, + copyright = { 'dc:rights' }, + identifier = { 'dc:identifier' }, + version = { 'xmpMM:VersionID' }, +} + +callback.register('finish_pdffile', function() + if #XMP > 1 then + alloc.err('Not all metadata has been written out.') + XMP = { [1] = XMP[1] } + elseif #XMP > 0 then + local metadata_obj = M.write_metadata() + local catalog = pdf.getcatalog() or '' + pdf.setcatalog(catalog..string.format('/Metadata %s 0 R', metadata_obj)) + end +end) + +alloc.luadef('getmetadata', function() + local option = token.scan_keyword('/') and token.scan_string() + local keyname = token.scan_word() + local alias = M.aliases[keyname] + local key = alias and alias[1] or keyname + tex.sprint(M.get_metadata(key, option)) +end) + +local function set_aliased_metadata(keyname, value, option) + local keys = M.aliases[keyname] or { keyname } + for _, key in ipairs(keys) do + M.set_metadata(key, value, option) + end + if keys.also then keys.also(value, option) end +end + +alloc.luadef('setmetadata', function() + local option = token.scan_keyword('/') and token.scan_string() + local keyname = token.scan_word() + local value = token.scan_string() + set_aliased_metadata(keyname, value, option) +end, 'protected') + +alloc.luadef('startmetadata', function() + local key = token.scan_word() + local opt = token.scan_keyword('/') and token.scan_string() + repeat + repeat + set_aliased_metadata(key, token.scan_string(), opt) + opt = token.scan_keyword('/') and token.scan_string() + until not opt + key = token.scan_word() + opt = token.scan_keyword('/') and token.scan_string() + until key == 'stopmetadata' +end) + +alloc.luadef('newmetadata', function() + M.push_metadata() +end, 'protected') + +alloc.luadef('writemetadata', function() + tex.sprint(M.write_metadata()) +end) + +alloc.luadef('includemetadata', function() + local file = kpse.find_file(token.scan_string()) + if not file then + alloc.err('Metadata file not found'); + tex.sprint(0); + else + tex.sprint( pdf.obj { + type = 'stream', + attr = '/Type/Metadata /Subtype/XML', + immediate = true, + compresslevel = 0, + file = file, + }) + end +end) + +--  + +return M + diff --git a/macros/luatex/generic/minim-xmp/minim-xmp.pdf b/macros/luatex/generic/minim-xmp/minim-xmp.pdf new file mode 100644 index 0000000000..fdf048a857 Binary files /dev/null and b/macros/luatex/generic/minim-xmp/minim-xmp.pdf differ diff --git a/macros/luatex/generic/minim-xmp/minim-xmp.tex b/macros/luatex/generic/minim-xmp/minim-xmp.tex new file mode 100644 index 0000000000..2ec4adc895 --- /dev/null +++ b/macros/luatex/generic/minim-xmp/minim-xmp.tex @@ -0,0 +1,34 @@ + +\ifdefined\minimxmploaded + \message{(skipped)} + \expandafter\endinput\fi +\chardef\minimxmploaded=\catcode`\: +\catcode`\:=11 + +\input minim-alloc +\directlua { require('minim-xmp') } + +% do not add pdf metadata by default +\pdfvariable suppressoptionalinfo = 255 + +% \metadatamodification +\newcount \metadatamodification +\metadatamodification = 0 + +% \metadataseparator +\newcount \metadataseparator +\metadataseparator = `\; + +% Defined from the lua side: +% +% \getmetadata ns:key +% \setmetadata ns:key {value} +% \startmetadata +% +% idem, but undocumented as of yet: +% \newmetadata +% \writemetadata (expands to pdf object number) +% \includemetadata{filename} (expands to pdf object number) + +\catcode`\:=\minimxmploaded + -- cgit v1.2.3