% zhspacing.sty		version 3.5
% Simple macro for typesetting mixed Chinese documents in XeTeX
%	with punctuation space adjustment and prohibitions
% coded by YIN Dian (yindian@ustc)
% Licensed under GPL
% History:	070804	First usable version.
%		070805	Several line breaking bug fixes.
%		070807	\zhinteropenskip changed to 0.6em. \enfont
%			TODO list written.
%		070808	Use macros instead of skips to fit different font size.
%		070809	Tried to use myclass to manage classes, but failed.
%			Then I found it works when \relax is added.
%		070810	Changed skip parameters. Removed \relax, only register
%			non-empty tokens instead. Now it seems to work right.
%			Several bugs fixed. CJK Ext-A/B support added.
%			Added three skip schemes.
%		070812	Fixed font switch bug between CJK Ext-A and Ext-B chars.
%			Obsoleted the use of \enfont.
%		070822	Fixed bug of unbalanced group when using ulem? Cleared
%			XeTeX's pre-defined inter-char tokens. Use seperate
%			font for punctuations.
%			Deprecated the use of begin/end-groups in LaTeX, in
%			order to work properly with ulem. Coded zhulem.sty.
%		070823	Use more flexible way to save font. Changed space
%			ignoring mechanism after hanzi to allow \skipenzh added
%			between hanzi and boundary if the next non-space token
%			is a char and is not fullwidth nor in PUA.
%		070824	Enabled automatic skip between math formulas and CJK
%			chars. Changed skip schemes. Added \zhspacingrevision.
%		071008	Fixed bug of missing \skipzh when hanzi next to space.
%			Found bug of \zhs@skipspaces --- \iffalse error.
%			Found bug?? in 10pt article:
%			\fbox{\vbox{\hsize=12.2001pt\scriptsize \parindent=0pt
%			\parskip=0pt《口口》口:“}}
%		071014	Found bug introduced by the last bug fix, which breaks
%			the punctuation prohibitions. (Fixed on 071020)
%		071015	Fixed bug of not changing punctuation font after space.
%		071019	Fixed the \iffalse bug. Use \the\XeTeXcharclass
%			instead of ifcjkchar.
%                       Defined token patterns to simplify settings, haha.
%		071020	Added ability to deal with space seperated characters.
%			Added XeTeX existence test. More intelligent parindent
%			setting. Fixed bug of English font change through not
%			using \getinterclasstoks, hmmm.
%                       Seperated fullstop and halfstop skips. They were both
%		        treated as judou before. Added \halthalfskipscheme.
%		071027	Fixed bug of \halthalfskipscheme. Added class setting
%			for some punctuations.
%		071103	Fixed bug of no \skipzh between CJK Ext-A/B chars.
%			However, due to the current bug of XeTeX, CJK Ext-B
%			chars may not have a correct \meaning, so having them
%			after spaces may result in a compiling failure.
%		071117	Use \ifzhs@isvalidchar to avoid the XeTeX \meaning
%			bug. Added \ProvidesPackage when under LaTeX.
%		071202	Fixed bug of wrong \ProvidesPackage in plain TeX.
%		071210	Changed usefulmacros.sty. Removed \@foreach because I
%			find they are redundant --- \@for already has
%			\expandafter #2. Improved LaTeX detecting.
%		071211	Minor change on \simsunskipscheme.
%		071229	Added a few punctuation classification settings.
%			Refined code. Added active hanzi hook. Removed some
%			debug code for compiling speed. Not thoroughly tested.
%		071231	Use \zhs@@a to reduce memory usage. Removed
%			\zhs@active@alphabound because it's not effective.
%			Use \lastnodetype and \spacefactor to determine whether
%			or not to insert \skipenzh. Added a few punctuation
%			classification settings.
%		080102	No \enableactivehanzi by default. Changed \zhhanzihook.
%			Use hex number to represent hanzi. Added \zhs@font.
%			Added ambiwide and ambinarrow options. Made active
%			hanzi protected.
%		090720  Rename usefulmacros.sty and myclass.sty to
%			zhsusefulmacros.sty and zhsmyclass.sty.
%		2012/03/14  Fix bug for Plain format.
%		2016/02/20  Compatible with LaTeX2e 2016/02/01.
%		2016/02/10 The boundary class is changed from 255 to 4095
%			since XeTeX 0.99994.
% Note:	1.	Catcode test requires letter 'a' to be in category 11, and '!'
%		to be in category 12. So don't change the default catcodes.
%	2.	To speed up processing long Chinese documents, set \skipzh to
%		empty and \XeTeXinterchartoks 1 1={}. This can save 1/4 time.
%	3.	\zhspacing will generate a few spaces. Using it in horizontal
%		mode will produce unwanted skips.
\catcode`\@=11
\ifx\XeTeXrevision\@undefined
  \errmessage{XeTeX required to use zhspacing}%
\fi
\ifx\XeTeXinterchartokenstate\@undefined
  \errmessage{XeTeX 0.997 or above required to use zhspacing}%
\fi
\ifx\zhspacingrevision\@undefined
\def\zhspacingrevision{2016/05/14}
\input zhsusefulmacros.sty
\newif\ifzhs@ambiwide
\zhs@ambiwidetrue
\ifLaTeX@e
  \ProvidesPackage{zhspacing}[\zhspacingrevision]
  \DeclareOption{ambiwide}{\zhs@ambiwidetrue}
  \DeclareOption{ambinarrow}{\zhs@ambiwidefalse}
  \ProcessOptions\relax
\fi
\XeTeXlinebreaklocale="zh"
\XeTeXlinebreakskip=0pt plus 0.2em minus 0.1em
\def\zhnobreak{\nobreak}
\def\simsunskipscheme{% should be fit for sim-xxx fonts
  % my skip
  \def\skipzh{\hskip 0em plus 0.2em minus 0.1em}
  \def\skipenzh{\hskip 0.25em plus 0.15em minus 0.05em}
  \def\skipzhopen{\hskip -0.0em plus 0.0em minus 0.3em}
  \def\skipzhinteropen{\hskip -0.3em plus 0.1em minus 0.1em}
  \def\skipzhlinestartopen{\hskip -0.35em}
  \def\skipzhclose{\hskip -0.0em  plus 0.0em minus 0.3em}
  \def\skipzhinterclose{\hskip -0.3em plus 0.1em minus 0.1em}
  \def\skipzhlineendclose{\hskip -0.35em}
  \def\skipzhhalfstop{\hskip -0.0em  plus 0.0em minus 0.5em}
  \def\skipzhinterhalfstop{\hskip -0.3em plus 0.1em minus 0.1em}
  \def\skipzhlineendhalfstop{\hskip -0.6em}
  \def\skipzhfullstop{\hskip -0.0em  plus 0.0em minus 0.5em}
  \def\skipzhinterfullstop{\hskip -0.3em plus 0.1em minus 0.1em}
  \def\skipzhlineendfullstop{\hskip -0.575em}
  % neg
  \def\skipnegzhlinestartopen{\hskip 0.35em}
  \def\skipnegzhlineendclose{\hskip 0.35em}
  \def\skipnegzhlineendhalfstop{\hskip 0.6em}
  \def\skipnegzhlineendfullstop{\hskip 0.575em}
}
\def\emptyskipscheme{% just for test use
  % my skip
  \def\skipzh{\hskip 0em plus 0.1em}
  \def\skipenzh{\hskip 0em plus 0.2em}
  \def\skipzhopen{\hskip 0pt}
  \def\skipzhinteropen{\hskip 0pt}
  \def\skipzhlinestartopen{\hskip 0pt}
  \def\skipzhclose{\hskip 0pt}
  \def\skipzhinterclose{\hskip 0pt}
  \def\skipzhlineendclose{\hskip 0pt}
  \def\skipzhhalfstop{\hskip 0pt}
  \def\skipzhinterhalfstop{\hskip 0pt}
  \def\skipzhlineendhalfstop{\hskip 0pt}
  \def\skipzhfullstop{\hskip 0pt}
  \def\skipzhinterfullstop{\hskip 0pt}
  \def\skipzhlineendfullstop{\hskip 0pt}
  % neg
  \def\skipnegzhlinestartopen{\hskip 0pt}
  \def\skipnegzhlineendclose{\hskip 0pt}
  \def\skipnegzhlineendhalfstop{\hskip 0pt}
  \def\skipnegzhlineendfullstop{\hskip 0pt}
}
\def\haltfullskipscheme{% should be fit for Adobe opentype fonts with halfwidth alternative enabled
  % my skip
  \def\skipzh{\hskip 0em plus 0.2em minus 0.1em}
  \def\skipenzh{\hskip 0.25em plus 0.15em minus 0.05em}
  \def\skipzhopen{\hskip 0.3em minus 0.3em}
  \def\skipzhinteropen{\hskip 0em plus 0.1em }
  \def\skipzhlinestartopen{\hskip 0em}
  \def\skipzhclose{\hskip 0.3em  minus 0.3em}
  \def\skipzhinterclose{\hskip 0em plus 0.1em}
  \def\skipzhlineendclose{\hskip 0em}
  \def\skipzhhalfstop{\hskip 0.4em  minus 0.4em}
  \def\skipzhinterhalfstop{\hskip 0em plus 0.1em}
  \def\skipzhlineendhalfstop{\hskip 0em}
  \def\skipzhfullstop{\hskip 0.4em  minus 0.4em}
  \def\skipzhinterfullstop{\hskip 0em plus 0.1em}
  \def\skipzhlineendfullstop{\hskip 0em}
  % neg
  \def\skipnegzhlinestartopen{\hskip 0em}
  \def\skipnegzhlineendclose{\hskip 0em}
  \def\skipnegzhlineendhalfstop{\hskip 0em}
  \def\skipnegzhlineendfullstop{\hskip 0em}
}
\let\haltskipscheme\haltfullskipscheme % for backward compatibility
\def\halthalfskipscheme{% should be fit for Adobe opentype fonts with halfwidth alternative enabled
  % my skip
  \def\skipzh{\hskip 0em plus 0.2em minus 0.1em}
  \def\skipenzh{\hskip 0.25em plus 0.15em minus 0.05em}
  \def\skipzhopen{\hskip 0.0em plus 0.3em}
  \def\skipzhinteropen{\hskip 0em plus 0.1em }
  \def\skipzhlinestartopen{\hskip 0em}
  \def\skipzhclose{\hskip 0.0em  plus 0.3em}
  \def\skipzhinterclose{\hskip 0em plus 0.1em}
  \def\skipzhlineendclose{\hskip 0em}
  \def\skipzhhalfstop{\hskip 0.0em  plus 0.4em}
  \def\skipzhinterhalfstop{\hskip 0em plus 0.1em}
  \def\skipzhlineendhalfstop{\hskip -0.15em}
  \def\skipzhfullstop{\hskip 0.0em  plus 0.4em}
  \def\skipzhinterfullstop{\hskip 0em plus 0.1em}
  \def\skipzhlineendfullstop{\hskip -0.15em}
  % neg
  \def\skipnegzhlinestartopen{\hskip 0em}
  \def\skipnegzhlineendclose{\hskip 0em}
  \def\skipnegzhlineendhalfstop{\hskip 0.15em}
  \def\skipnegzhlineendfullstop{\hskip 0.15em}
}
\simsunskipscheme
%\let\mydbgmessage\message
\def\mydbgmessage#1{}

% font save and restore
\def\zhs@oldf@encoding{}
\def\zhs@oldf@family{}
\def\zhs@oldf@series{}
\def\zhs@oldf@shape{}
\def\zhs@oldf@size{}
\def\zhs@savef@nt#1{%
  \expandafter\xdef\csname zhs@#1f@encoding\endcsname{\f@encoding}%
  \expandafter\xdef\csname zhs@#1f@family\endcsname{\f@family}%
  \expandafter\xdef\csname zhs@#1f@series\endcsname{\f@series}%
  \expandafter\xdef\csname zhs@#1f@shape\endcsname{\f@shape}%
  \expandafter\xdef\csname zhs@#1f@size\endcsname{\f@size}%
}
\def\zhs@restoref@nt#1{%
  \edef\f@encoding{\csname zhs@#1f@encoding\endcsname}%
  \edef\f@family{\csname zhs@#1f@family\endcsname}%
  \edef\f@series{\csname zhs@#1f@series\endcsname}%
  \edef\f@shape{\csname zhs@#1f@shape\endcsname}%
  \edef\f@size{\csname zhs@#1f@size\endcsname}%
  \selectfont
}
\def\zhs@printf@nt#1{%
  \immediate\write16{Font #1 is: \csname zhs@#1f@encoding\endcsname/\csname zhs@#1f@family\endcsname/\csname
  zhs@#1f@series\endcsname/\csname zhs@#1f@shape\endcsname/\csname zhs@#1f@size\endcsname}%
}
\def\zhgroupsavefont{%
  \let\zhs@savefont=\begingroup
  \let\zhs@restorefont=\endgroup
}
\def\zhnfsssavefont{%
  \def\zhs@savefont{\zhs@savef@nt{old}}
  \def\zhs@restorefont{\zhs@restoref@nt{old}}
}
\ifLaTeX@e
  \zhnfsssavefont
\else
  \zhgroupsavefont
\fi

% test CJK char
\def\chartonum#1#2{% #1: an explicit or implicit char; #2: return macro
  \ifcat#1a%
    \edef#2{\expandafter\@lettertonum\meaning #1\@end}%
    \edef#2{\expandafter\number\expandafter\lq #2}%
  \else
    \ifcat#1!%
      \edef#2{\expandafter\@chartonum\meaning #1\@end}%
      \edef#2{\expandafter\number\expandafter\lq #2}%
    \else
      \errmessage{In \string\chartonum, parameter not a letter, nor a character}%
    \fi
  \fi
}
{\escapechar=-1
 \expandafter\expandafter\expandafter\gdef
 \expandafter\expandafter\expandafter\@lettertonum
 \expandafter\string\csname the letter \endcsname#1\@end{#1}
 \expandafter\expandafter\expandafter\gdef
 \expandafter\expandafter\expandafter\@chartonum
 \expandafter\string\csname the character \endcsname#1\@end{#1}
}
\let\zhs@tmpchar\relax
\def\zhs@skipspaces{\futurenonspacelet\zhs@tmpchar\zhs@skipsp@ces}
\def\zhs@skipsp@ces{%
  %\expandafter\mydbgmessage\expandafter{\meaning\zhs@tmpchar}%
  \ifcat a\noexpand\zhs@tmpchar % catcode 11
    \zhs@testskipenzh
  \else
    \ifcat !\noexpand\zhs@tmpchar % catcode 12
      \zhs@testskipenzh
    \else
      \ifcat $\noexpand\zhs@tmpchar % catcode 3
        \skipenzh\relax
      \fi
    \fi
  \fi
}
\def\zhs@tmpnum{-1}
\def\ifzhs@isvalidchar#1??#2\ok{\if!#2!}
\def\zhs@testskipenzh{%
  \chartonum\zhs@tmpchar\zhs@tmpnum
  \expandafter\ifzhs@isvalidchar\zhs@tmpnum??\ok
    \edef\zhs@tmpnum{\the\XeTeXcharclass\zhs@tmpnum}%
  \else
    \edef\zhs@tmpnum{\getclassnum{cjkextb}}%
  \fi
  \ifnum\zhs@tmpnum = \getclassnum{alphanum}%
    \skipenzh\relax
  \else
    \ifnum\zhs@tmpnum = \getclassnum{hanzi}%
      \skipzh\relax
    \else
      \ifnum\zhs@tmpnum = \getclassnum{closefw}%
        %\getinterclasstoks{hanzi}{closefw}%
	\zhnobreak
      \else
        \ifnum\zhs@tmpnum = \getclassnum{halfstop}%
          %\getinterclasstoks{hanzi}{halfstop}%
	  \zhnobreak
        \else
	  \ifnum\zhs@tmpnum = \getclassnum{fullstop}%
	    \zhnobreak
	  \else
            \ifnum\zhs@tmpnum = \getclassnum{openfw}%
              \zhnobreak\skipnegzhlinestartopen\relax % compensate
            \else
              \ifnum\zhs@tmpnum = \getclassnum{cjkexta}%
                \skipzh\relax
              \else
                \ifnum\zhs@tmpnum = \getclassnum{cjkextb}%
                  \skipzh\relax
                \fi
              \fi
            \fi
	  \fi
        \fi
      \fi
    \fi
  \fi
}
\def\zhs@aftermathskip{%
  \ifnum\lastnodetype=10 % math node
    \skipenzh\relax
  \fi
}
% Character class settings.
\input zhsmyclass.sty
% three base classes: boundary, halfwidth and fullwidth
\newclass{boundary}

\ifnum\strcmp{\number\XeTeXversion\XeTeXrevision}{0.99993} > 0 %
  \chardef\zhs@boundaryclassnum = 4095 %
\else
  \chardef\zhs@boundaryclassnum = 255 %
\fi
\setclassnum{boundary}{\zhs@boundaryclassnum}
\newclass{halfwidth}
\newclass{fullwidth}
\setinterclasstoks{boundary}{fullwidth}{\mydbgmessage{^^JZ}\zhs@savefont\zhfont}
\setinterclasstoks{fullwidth}{boundary}{\mydbgmessage{^^Jz}\zhs@restorefont\ignorespaces}
\setinterclasstoks{halfwidth}{fullwidth}{\mydbgmessage{^^JP}\zhs@savefont\zhfont}
\setinterclasstoks{fullwidth}{halfwidth}{\mydbgmessage{^^Jp}\zhs@restorefont}
% derived class alphanum and hanzi
\newclass[halfwidth]{alphanum}
\setclassnum{alphanum}{0}
\newclass[fullwidth]{hanzi}
\setclassnum{hanzi}{1}
\setinterclasstoks{hanzi}{hanzi}{\skipzh\relax}
%\appendinterclasstoks{alphanum}{hanzi}{\mydbgmessage{^^JQ}\skipenzh\relax}
%\prependinterclasstoks{hanzi}{alphanum}{\mydbgmessage{^^Jq}\skipenzh\relax}
\setinterclasstoks{alphanum}{hanzi}{\mydbgmessage{^^JQ}\skipenzh\relax\zhs@savefont\zhfont}
\setinterclasstoks{hanzi}{alphanum}{\mydbgmessage{^^Jq}\zhs@restorefont\skipenzh\relax}
\setinterclasstoks{hanzi}{boundary}{\mydbgmessage{^^Ji}\zhs@restorefont\zhs@skipspaces}
\setinterclasstoks{boundary}{hanzi}{\mydbgmessage{^^JI}\zhs@aftermathskip\zhs@savefont\zhfont}
% derived class from fullwidth
\newclass[fullwidth]{openfw} % ‘, (, etc
\setclassnum{openfw}{2}
\newclass[fullwidth]{closefw} % ’, ), etc
\setclassnum{closefw}{3}
\newclass[fullwidth]{halfstop} % ,, , etc
\setclassnum{halfstop}{4}
\newclass[fullwidth]{fullstop} %  。 etc
\setclassnum{fullstop}{10}
\newclass[fullwidth]{fwpunct} % extra fullwidth punctuations
\setclassnum{fwpunct}{7}
% patterns
\def\zhs@punct@inter#1{%
  \zhnobreak\csname skipzhinter#1\endcsname\relax
}
\def\zhs@leftpunct@after#1{%
  \allowbreak\csname skipzh#1\endcsname\nobreak\csname skipnegzhlinestart#1\endcsname\vadjust{}\zhnobreak\csname skipzhlinestart#1\endcsname\relax
}
\def\zhs@leftpunct@after@boundary#1{%
  \zhnobreak\csname skipzhlinestart#1\endcsname\relax
}
\let\zhs@tmp\relax
\def\zhs@leftpunct@before@boundary#1{\def\zhs@tmp{#1}\futurenonspacelet\zhs@tmpchar\zhs@leftpunct@before@boundary@}
\def\zhs@leftpunct@before@boundary@{%
  %\mydbgmessage{left punct before boundary}%
  %\expandafter\mydbgmessage\expandafter{\meaning\zhs@tmpchar}%
  \ifcat a\noexpand\zhs@tmpchar % catcode 11
    \zhs@leftpunct@before@boundary@@
  \else
    \ifcat !\noexpand\zhs@tmpchar % catcode 12
      \zhs@leftpunct@before@boundary@@
    \fi
  \fi
}
\def\zhs@leftpunct@before@boundary@@{%
  \chartonum\zhs@tmpchar\zhs@tmpnum
  \expandafter\ifzhs@isvalidchar\zhs@tmpnum??\ok
    \edef\zhs@tmpnum{\the\XeTeXcharclass\zhs@tmpnum}%
  \else
    \edef\zhs@tmpnum{\getclassnum{cjkextb}}%
  \fi
  \zhs@tmp
}
\def\zhs@leftpunct@after@right#1#2{%
  \zhnobreak\csname skipzhlineend#2\endcsname\allowbreak\csname skipnegzhlineend#2\endcsname\csname skipzhinter#2\endcsname\csname skipzhinter#1\endcsname\csname skipnegzhlinestart#1\endcsname\vadjust{}\zhnobreak\csname skipzhlinestart#1\endcsname\relax
}
\def\zhs@rightpunct@before#1{%
  \zhnobreak\csname skipzhlineend#1\endcsname\allowbreak\csname skipnegzhlineend#1\endcsname\csname skipzh#1\endcsname\relax
}
\def\zhs@rightpunct@before@boundary#1{\def\zhs@tmp{#1}\futurenonspacelet\zhs@tmpchar\zhs@rightpunct@before@boundary@}
\def\zhs@rightpunct@before@boundary@{%
  %\mydbgmessage{right punct before boundary}%
  %\expandafter\mydbgmessage\expandafter{\meaning\zhs@tmpchar}%
  \ifcat a\noexpand\zhs@tmpchar % catcode 11
    \zhs@rightpunct@before@boundary@@
  \else
    \ifcat !\noexpand\zhs@tmpchar % catcode 12
      \zhs@rightpunct@before@boundary@@
    \fi
  \fi
}
\def\zhs@rightpunct@before@boundary@@{%
  \chartonum\zhs@tmpchar\zhs@tmpnum
  \expandafter\ifzhs@isvalidchar\zhs@tmpnum??\ok
    \edef\zhs@tmpnum{\the\XeTeXcharclass\zhs@tmpnum}%
  \else
    \edef\zhs@tmpnum{\getclassnum{cjkextb}}%
  \fi
  \zhs@tmp
}
% open fullwidth punctuation after other characters
\@for\zhs@class:={hanzi,fwpunct,alphanum,halfwidth}\do{%,boundary
  \prependinterclasstoks{\zhs@class}{openfw}{\mydbgmessage{^^JA1}\zhs@leftpunct@after{open}}
}
\setinterclasstoks{openfw}{openfw}{\mydbgmessage{^^JA2}\zhs@punct@inter{open}}
\setinterclasstoks{closefw}{openfw}{\mydbgmessage{^^JA3}\zhs@leftpunct@after@right{open}{close}}
\setinterclasstoks{halfstop}{openfw}{\mydbgmessage{^^JA4}\zhs@leftpunct@after@right{open}{halfstop}}
\setinterclasstoks{fullstop}{openfw}{\mydbgmessage{^^JA4}\zhs@leftpunct@after@right{open}{fullstop}}
% close fullwidth punctuation before other characters
\@for\zhs@class:={hanzi,fwpunct,alphanum,halfwidth}\do{%,boundary
  \prependinterclasstoks{closefw}{\zhs@class}{\mydbgmessage{^^JB1}\zhs@rightpunct@before{close}}
}
\setinterclasstoks{closefw}{closefw}{\mydbgmessage{^^JB2}\zhs@punct@inter{close}}
\setinterclasstoks{closefw}{halfstop}{\mydbgmessage{^^JB3}\zhs@punct@inter{close}}
\setinterclasstoks{closefw}{fullstop}{\mydbgmessage{^^JB3}\zhs@punct@inter{close}}
% halfstop punctuation before other characters
\@for\zhs@class:={hanzi,fwpunct,alphanum,halfwidth}\do{%,boundary
  \prependinterclasstoks{halfstop}{\zhs@class}{\mydbgmessage{^^JC1}\zhs@rightpunct@before{halfstop}}
}
\setinterclasstoks{halfstop}{closefw}{\mydbgmessage{^^Jb2}\zhs@punct@inter{halfstop}}
\setinterclasstoks{halfstop}{halfstop}{\mydbgmessage{^^JC2}\zhs@punct@inter{halfstop}}
\setinterclasstoks{halfstop}{fullstop}{\mydbgmessage{^^JC2}\zhs@punct@inter{halfstop}}
% fullstop punctuation before other characters
\@for\zhs@class:={hanzi,fwpunct,alphanum,halfwidth}\do{%,boundary
  \prependinterclasstoks{fullstop}{\zhs@class}{\mydbgmessage{^^JC1}\zhs@rightpunct@before{fullstop}}
}
\setinterclasstoks{fullstop}{closefw}{\mydbgmessage{^^Jb2}\zhs@punct@inter{fullstop}}
\setinterclasstoks{fullstop}{halfstop}{\mydbgmessage{^^JC2}\zhs@punct@inter{fullstop}}
\setinterclasstoks{fullstop}{fullstop}{\mydbgmessage{^^JC2}\zhs@punct@inter{fullstop}}
% open fullwidth punctuation before other characters
\@for\zhs@class:={hanzi,fwpunct,alphanum,halfwidth,closefw,halfstop,fullstop}\do{%,boundary
  \prependinterclasstoks{openfw}{\zhs@class}{\mydbgmessage{^^Ja}\zhnobreak}
}
% close fullwidth punctuation after other characters
\@for\zhs@class:={hanzi,fwpunct,alphanum,halfwidth,boundary}\do{%
  \prependinterclasstoks{\zhs@class}{closefw}{\mydbgmessage{^^Jb}\zhnobreak}
}
% halfstop punctuation after other characters
\@for\zhs@class:={hanzi,fwpunct,alphanum,halfwidth,boundary}\do{%
  \prependinterclasstoks{\zhs@class}{halfstop}{\mydbgmessage{^^Jc}\zhnobreak}
}
% fullstop punctuation after other characters
\@for\zhs@class:={hanzi,fwpunct,alphanum,halfwidth,boundary}\do{%
  \prependinterclasstoks{\zhs@class}{fullstop}{\mydbgmessage{^^Jc}\zhnobreak}
}
% boundary special treat
\prependinterclasstoks{boundary}{openfw}{\mydbgmessage{^^JA0}\zhs@leftpunct@after@boundary{open}}
\appendinterclasstoks{closefw}{boundary}{\mydbgmessage{^^JB0}\zhs@rightpunct@before@boundary{%
  \ifnum\zhs@tmpnum = \getclassnum{closefw}%
    %\getinterclasstoks{closefw}{closefw}%
    \zhs@punct@inter{close}%
  \else
    \ifnum\zhs@tmpnum = \getclassnum{halfstop}%
      %\getinterclasstoks{closefw}{halfstop}%
      \zhs@punct@inter{close}%
    \else
      \ifnum\zhs@tmpnum = \getclassnum{fullstop}%
	\zhs@punct@inter{close}%
      \else
        \ifnum\zhs@tmpnum = \getclassnum{openfw}%
          %\getinterclasstoks{closefw}{openfw}%
          \zhs@leftpunct@after@right{open}{close}%
          \zhnobreak\skipnegzhlinestartopen\relax % compensate
        \else
          \zhs@rightpunct@before{close}%
        \fi
      \fi
    \fi
  \fi
}}
\appendinterclasstoks{halfstop}{boundary}{\mydbgmessage{^^JC0}\zhs@rightpunct@before@boundary{%
  \ifnum\zhs@tmpnum = \getclassnum{closefw}%
    %\getinterclasstoks{halfstop}{closefw}%
    \zhs@punct@inter{halfstop}%
  \else
    \ifnum\zhs@tmpnum = \getclassnum{halfstop}%
      %\getinterclasstoks{halfstop}{halfstop}%
      \zhs@punct@inter{halfstop}%
    \else
      \ifnum\zhs@tmpnum = \getclassnum{fullstop}%
        \zhs@punct@inter{halfstop}%
      \else
        \ifnum\zhs@tmpnum = \getclassnum{openfw}%
          %\getinterclasstoks{halfstop}{openfw}%
          \zhs@leftpunct@after@right{open}{halfstop}%
          \zhnobreak\skipnegzhlinestartopen\relax % compensate
        \else
          \zhs@rightpunct@before{halfstop}%
        \fi
      \fi
    \fi
  \fi
}}
\appendinterclasstoks{fullstop}{boundary}{\mydbgmessage{^^JC0}\zhs@rightpunct@before@boundary{%
  \ifnum\zhs@tmpnum = \getclassnum{closefw}%
    \zhs@punct@inter{fullstop}%
  \else
    \ifnum\zhs@tmpnum = \getclassnum{halfstop}%
      \zhs@punct@inter{fullstop}%
    \else
      \ifnum\zhs@tmpnum = \getclassnum{fullstop}%
        \zhs@punct@inter{fullstop}%
      \else
        \ifnum\zhs@tmpnum = \getclassnum{openfw}%
          \zhs@leftpunct@after@right{open}{fullstop}%
          \zhnobreak\skipnegzhlinestartopen\relax % compensate
        \else
          \zhs@rightpunct@before{fullstop}%
        \fi
      \fi
    \fi
  \fi
}}
\appendinterclasstoks{openfw}{boundary}{\mydbgmessage{^^Ja0}\zhs@leftpunct@before@boundary{%
  \ifnum\zhs@tmpnum = \getclassnum{openfw}%
    %\getinterclasstoks{openfw}{openfw}%
    \zhs@punct@inter{open}%
    \zhnobreak\skipnegzhlinestartopen\relax % compensate
  \fi
}}
% punctuation font change
\@for\zhs@class:={openfw,closefw,halfstop,fullstop,fwpunct}\do{%
  \appendinterclasstoks{hanzi}{\zhs@class}{\zhpunctfont}
  \appendinterclasstoks{\zhs@class}{hanzi}{\zhfont}
  \appendinterclasstoks{boundary}{\zhs@class}{\zhpunctfont}
  \appendinterclasstoks{halfwidth}{\zhs@class}{\zhpunctfont}
}
% CJK Ext-A/B support
\newclass[hanzi]{cjkexta}
\setclassnum{cjkexta}{8}
\newclass[hanzi]{cjkextb}
\setclassnum{cjkextb}{9}
\@for\zhs@class:={hanzi,fwpunct,openfw,closefw,halfstop,fullstop,alphanum,halfwidth,boundary}\do{%
  \appendinterclasstoks{\zhs@class}{cjkexta}{\zhcjkextafont}
  \appendinterclasstoks{\zhs@class}{cjkextb}{\zhcjkextbfont}
  \prependinterclasstoks{cjkexta}{\zhs@class}{\zhfont}
  \prependinterclasstoks{cjkextb}{\zhs@class}{\zhfont}
}
\setinterclasstoks{cjkexta}{cjkexta}{\skipzh\relax}
\setinterclasstoks{cjkexta}{cjkextb}{\zhcjkextbfont\skipzh\relax}
\setinterclasstoks{cjkextb}{cjkexta}{\zhcjkextafont\skipzh\relax}
\setinterclasstoks{cjkextb}{cjkextb}{\skipzh\relax}

% long fullwidth punctuations
\newclass[fwpunct]{longpunct} % … etc
\setclassnum{longpunct}{5}
\setinterclasstoks{longpunct}{longpunct}{\zhnobreak}
% derived class from halfwidth
\newclass[halfwidth]{hwpunct} % (, ., etc
\setclassnum{hwpunct}{6}

% Font settings
\ifLaTeX@e
  \RequirePackage{fontspec}
  \@ifundefined{zhfont}{\newfontfamily\zhfont[BoldFont=SimHei]{SimSun}}{}
  \@ifundefined{zhpunctfont}{\newfontfamily\zhpunctfont{SimSun}}{}
  \@ifundefined{zhcjkextafont}{\def\zhcjkextafont{\message{CJK Ext-A}}}{}
  \@ifundefined{zhcjkextbfont}{\def\zhcjkextbfont{\message{CJK Ext-B}}}{}
\else
  \@ifundefined{zhfont}{\font\zhfont="SimSun" at 10pt}{}
  \@ifundefined{zhpunctfont}{\font\zhpunctfont="SimSun" at 10pt}{}
  \@ifundefined{zhcjkextafont}{\def\zhcjkextafont{\message{CJK Ext-A}}}{}
  \@ifundefined{zhcjkextbfont}{\def\zhcjkextbfont{\message{CJK Ext-B}}}{}
\fi

% code range utils
\newif\ifzhs@result
\newcount\zhs@tmpcnt
\def\@ifnuminrange#1#2#3#4{% #1: num, #2: range with format a->b,c->d
				% #3: true block, #4: false block
  \zhs@resultfalse
  \@for\zhs@range:=#2\do{%
    \expandafter\@ifrangecontains\expandafter[\zhs@range]{#1}{\zhs@resulttrue}{}%
  }%
  \ifzhs@result #3\else #4\fi
}

\def\@ifrangecontains[#1->#2]#3#4#5{%
  \ifnum#3<#1\relax
    #5%
  \else
    \ifnum#3>#2\relax
      #5%
    \else
      #4%
    \fi
  \fi
}

\def\@fornuminrange#1:=#2\do#3{% #1: count register, #2: range
  \@for\zhs@range:=#2\do{%
    \expandafter\@@fornuminrange\expandafter[\zhs@range]{#1}{#3}%
  }%
}
\def\@@fornuminrange[#1->#2]#3#4{%
  #3=#1%
  \loop
    \unless\ifnum#3>#2\relax
    #4\relax
    \advance #3by 1%
  \repeat
}

\def\zhs@hanzirange{% data from unicodeletters.tex
  "2E80->"2E99,
  "2E9B->"2EF3,
  "2F00->"2FD5,
  "2FF0->"2FFB,
  "3000->"3000,
  "3003->"3004,
  "3006->"3007,
  "3012->"3013,
  "3020->"3029,
  "3030->"303A,
  "303D->"303F,
  "3042->"3042,
  "3044->"3044,
  "3046->"3046,
  "3048->"3048,
  "304A->"3062,
  "3064->"3082,
  "3084->"3084,
  "3086->"3086,
  "3088->"308D,
  "308F->"3094,
  "309F->"309F,
  "30A2->"30A2,
  "30A4->"30A4,
  "30A6->"30A6,
  "30A8->"30A8,
  "30AA->"30C2,
  "30C4->"30E2,
  "30E4->"30E4,
  "30E6->"30E6,
  "30E8->"30ED,
  "30EF->"30F4,
  "30F7->"30FA,
  "30FF->"30FF,
  "3105->"312C,
  "3131->"318E,
  "3190->"31B7,
  "31C0->"31CF,
  "3200->"321E,
  "3220->"3243,
  "3250->"32FE,
  "3300->"33FF,
  "3400->"4DB5,
  "4E00->"9FBB,
  "A000->"A014,
  "A016->"A48C,
  "A490->"A4C6,
  "F900->"FA2D,
  "FA30->"FA6A,
  "FA70->"FAD9,
  "FE30->"FE34,
  "FE45->"FE46,
  "FE49->"FE4F,
  "FE51->"FE51,
  "FE58->"FE58,
  "FE5F->"FE66,
  "FE68->"FE68,
  "FE6B->"FE6B,
  "FF02->"FF03,
  "FF06->"FF07,
  "FF0A->"FF0B,
  "FF0D->"FF0D,
  "FF0F->"FF19,
  "FF1C->"FF1E,
  "FF20->"FF3A,
  "FF3C->"FF3C,
  "FF3E->"FF5A,
  "FF5C->"FF5C,
  "FF5E->"FF5E,
  "FFE2->"FFE4,
  "20000->"2A6D6,
  "2F800->"2FA1D
}
\def\zhs@extarange{"3400->"4DB5}
\def\zhs@extbrange{"20000->"2FA1D}

% set active hanzi for hooking
\def\makehanziglobalactive{%
  \@fornuminrange\zhs@tmpcnt:=\zhs@hanzirange\do{%
    \global\catcode\zhs@tmpcnt=\active}%
}

\def\makehanzigloballetter{%
  \@fornuminrange\zhs@tmpcnt:=\zhs@hanzirange\do{%
    \global\catcode\zhs@tmpcnt=11}%
}

\def\zhhanzihook#1{{\XeTeXinterchartokenstate=0\zhs@font #1}}

\def\zhs@h@x#1{\ifcase#1 0\or 1\or 2\or 3\or 4\or 5\or 6\or 7\or 8\or
 9\or A\or B\or C\or D\or E\or F\fi}
\def\zhs@hex#1{\if!#1!\else\ifnum#1<16 \zhs@h@x{#1}\else
  \expandafter\zhs@hex\expandafter{\number\numexpr(#1-8)/16}%
  \expandafter\zhs@h@x\expandafter{\number\numexpr#1-(#1-8)/16*16}\fi\fi}

\let\zhs@font\zhfont
\def\enableactivehanzi{%
  \def\zhs@@a##1{\zhs@afteralphamathskip
    \expandafter\@ifrangecontains\expandafter[\zhs@extarange]{"##1}{\global
      \let\zhs@font\zhcjkextafont}{\expandafter\@ifrangecontains
      \expandafter[\zhs@extbrange]{"##1}{\global\let\zhs@font\zhcjkextbfont
      }{\global\let\zhs@font\zhfont}}%
    \zhhanzihook{\char"##1}\zhs@active@lookafter}%
  \@fornuminrange\zhs@tmpcnt:=\zhs@hanzirange\do{%
    \lccode`\~=\zhs@tmpcnt
    \lowercase{\protected\xdef~{\noexpand\zhs@@a{\expandafter\zhs@hex
      \expandafter{\number\zhs@tmpcnt}}}}%
  }%
}

\def\zhs@afteralphamathskip{%
  \ifhmode
    \ifnum\lastnodetype=10 % math node
      \skipenzh\relax
    \else\ifnum\lastnodetype=0
      \ifnum\spacefactor=999
        \skipenzh
      \else\ifnum\spacefactor=1000
        \skipenzh
      \fi\fi
    \fi\fi
  \fi
}

{\escapechar=-1
 \xdef\zhs@active@ident{\string\zhs@@a}
}
\expandafter\def\expandafter\ifzhs@notactivehanzi\expandafter#\expandafter1%
  \zhs@active@ident#2\ok{\if!#2!}
\def\zhs@active@lookafter{\futurenonspacelet\zhs@tmpchar\zhs@active@look@fter}
\def\zhs@active@look@fter{%
  %\expandafter\mydbgmessage\expandafter{\meaning\zhs@tmpchar}%
  \if\relax\noexpand\zhs@tmpchar % control sequence
    \zhs@beforeactivehanziskip
  \else
    \ifcat a\noexpand\zhs@tmpchar % catcode 11
      \zhs@testskipenzh
    \else
      \ifcat !\noexpand\zhs@tmpchar % catcode 12
        \zhs@testskipenzh
      \else
        \ifcat $\noexpand\zhs@tmpchar % catcode 3
          \skipenzh\relax
        \fi
      \fi
    \fi
  \fi
}

\def\zhs@beforeactivehanziskip{%
  %\let\zhs@tmp\iffalse % for \if match
  \edef\zhs@tmp{\meaning\zhs@tmpchar}%
  \expandafter\expandafter\expandafter\ifzhs@notactivehanzi
  \expandafter\zhs@tmp\zhs@active@ident\ok
  \else
    \skipzh\relax
  \fi
}

\def\zhs@loadxetexclasses{%
  \chardef\XeTeXcharclassID = 1 %
  \chardef\XeTeXcharclassCJ = 1 %
  \chardef\XeTeXcharclassOP = 2 %
  \chardef\XeTeXcharclassCL = 3 %
  \chardef\XeTeXcharclassEX = 3 %
  \chardef\XeTeXcharclassIS = 3 %
  \chardef\XeTeXcharclassNS = 3 %
  \chardef\XeTeXcharclassCM = \numexpr \zhs@boundaryclassnum + 1 \relax
  \input load-unicode-xetex-classes %
}

\def\zhspacing{%
  \ifnum\XeTeXcharclass"4E00 = 1 %
    % clear XeTeX pre-defined toks
    \XeTeXinterchartoks 0 1 = {}
    \XeTeXinterchartoks 0 2 = {}
    \XeTeXinterchartoks 0 3 = {}
    \XeTeXinterchartoks 1 0 = {}
    \XeTeXinterchartoks 2 0 = {}
    \XeTeXinterchartoks 3 0 = {}
    \XeTeXinterchartoks 1 1 = {}
    \XeTeXinterchartoks 1 2 = {}
    \XeTeXinterchartoks 1 3 = {}
    \XeTeXinterchartoks 2 1 = {}
    \XeTeXinterchartoks 2 2 = {}
    \XeTeXinterchartoks 2 3 = {}
    \XeTeXinterchartoks 3 1 = {}
    \XeTeXinterchartoks 3 2 = {}
    \XeTeXinterchartoks 3 3 = {}
  \else
    \expandafter\zhs@loadxetexclasses %
  \fi
  \zhs@tmpcnt "3400 %
  \chardef\zhs@tmpnum = \getclassnum{cjkexta}%
  \loop
    \ifnum\zhs@tmpcnt < "4E00 %
      \XeTeXcharclass\zhs@tmpcnt = \zhs@tmpnum
      \advance\zhs@tmpcnt by 1 %
  \repeat
  \zhs@tmpcnt "20000 %
  \chardef\zhs@tmpnum = \getclassnum{cjkextb}%
  \loop
    \ifnum\zhs@tmpcnt < "30000 %
      \XeTeXcharclass\zhs@tmpcnt = \zhs@tmpnum
      \advance\zhs@tmpcnt by 1 %
  \repeat
  \@for\zhs@char:={`:,`,,`、}\do{\XeTeXcharclass\zhs@char=4}
  \@for\zhs@char:={`。,`.,`;}\do{\XeTeXcharclass\zhs@char=10}
  \ifzhs@ambiwide
    \@for\zhs@char:={`“,`‘}\do{\XeTeXcharclass\zhs@char=2}
    \@for\zhs@char:={`”,`’}\do{\XeTeXcharclass\zhs@char=3}
    \@for\zhs@char:={`—,`…}\do{\XeTeXcharclass\zhs@char=5}
  \else
    \@for\zhs@char:={`“,`‘,`”,`’,`—,`…}\do{\XeTeXcharclass\zhs@char=6}
  \fi
  \@for\zhs@char:={`\!,`\",`\',`\(,`\),`\,,`\-,`\.,`\:,`\;,`\<,`\>,`\?,`\[,`\],`\`,`\{,`\},`\\,`\/}\do{\XeTeXcharclass\zhs@char=6}
  \@for\zhs@char:={`℃,`·,`$,`%}\do{\XeTeXcharclass\zhs@char=7}
  \registerXeTeXclasstoks
  \XeTeXinterchartokenstate=1\relax
  \ifLaTeX@e
    \setbox0=\hbox{\normalsize\def\zhs@tmp{\kern 2em}\zhs@tmp}%
    \parindent=\wd0
  \else
    \parindent=2em\relax
  \fi
  \message{zhspacing installed.^^J}
}

\fi