diff --git a/README.adoc b/README.adoc index 87678613..3aec39a4 100644 --- a/README.adoc +++ b/README.adoc @@ -4,3 +4,5 @@ OpTeX including development versions. See http://petr.olsak.net/optex/ +branch colors + diff --git a/optex/base/colors.opm b/optex/base/colors.opm index 23ec22dd..588be73c 100644 --- a/optex/base/colors.opm +++ b/optex/base/colors.opm @@ -2,16 +2,6 @@ \_codedecl \colordef {Colors <2021-05-28>} % preloaded in format - \_doc ----------------------------- - We declare internal boolean value \`\_iflocalcolor` ad do - \`\localcolor` as default. - \_cod ----------------------------- - -\_newifi \_iflocalcolor \_localcolortrue -\_protected\_def \_localcolor {\_localcolortrue} -\_protected\_def \_nolocalcolor {\_localcolorfalse} -\_public \localcolor \nolocalcolor ; - \_doc ----------------------------- The basic colors in CMYK \`\Blue` \`\Red` \`\Brown` \`\Green` \`\Yellow` \`\Cyan` \`\Magenta` @@ -21,103 +11,101 @@ \_def\Blue {\_setcmykcolor{1 1 0 0}} \_def\Red {\_setcmykcolor{0 1 1 0}} -\_def\Brown {\_setcmykcolor{0 0.67 0.67 0.5}} +\_def\Brown {\_setcmykcolor{0 .67 .67 .5}} \_def\Green {\_setcmykcolor{1 0 1 0}} \_def\Yellow {\_setcmykcolor{0 0 1 0}} \_def\Cyan {\_setcmykcolor{1 0 0 0}} \_def\Magenta {\_setcmykcolor{0 1 0 0}} -\_def\Grey {\_setcmykcolor{0 0 0 0.5}} -\_def\LightGrey {\_setcmykcolor{0 0 0 0.2}} +\_def\Grey {\_setcmykcolor{0 0 0 .5}} +\_def\LightGrey {\_setcmykcolor{0 0 0 .2}} \_def\White {\_setgreycolor{1}} \_def\Black {\_setgreycolor{0}} \_doc ----------------------------- By default, the \`\setcmykcolor` \`\setrgbcolor` and \`\setgreycolor` macros with `{}` parameter - expand to `\_setcolor{}` using \`\_formatcmyk` - or \`\_formatrgb` or \`\_formatgrey` expandable macros. - For example `\setrgbcolor{1 0 0}` expands to `\_setcolor{1 0 0 rg 1 0 0 RG}`.\nl - We set both types of colors (for lines (`K` or `RG` or `G`) and for fills - (`r` or `rg` or `g`) together in the command. - This is the reason why the \`\_fillstroke` uses both its parameters. - If only fills are needed you can do `\def\_fillstroke#1#2{#1}`. - If only strokes are needed you can do `\def\_fillstroke#1#2{#2}`. + expand to \^`\_setcolor``{}{}{}` where + is or or and + is color operator for filling, is color operator + for stroking. \_cod ----------------------------- -\_def\_setcmykcolor#1{\_setcolor{\_formatcmyk{#1}}} -\_def\_setrgbcolor#1{\_setcolor{\_formatrgb{#1}}} -\_def\_setgreycolor#1{\_setcolor{\_formatgrey{#1}}} -\_def\_formatcmyk#1{\_fillstroke{#1 k}{#1 K}} -\_def\_formatrgb#1{\_fillstroke{#1 rg}{#1 RG}} -\_def\_formatgrey#1{\_fillstroke{#1 g}{#1 G}} -\_def\_fillstroke#1#2{#1 #2} +\_def\_setcmykcolor#1{\_setcolor{#1}kK} +\_def\_setrgbcolor#1{\_setcolor{#1}{rg}{RG}} +\_def\_setgreycolor#1{\_setcolor{#1}gG} \_public \setcmykcolor \setrgbcolor \setgreycolor ; \_doc ----------------------------- - The \`\onlyrgb` declaration redefines \^`\_formatcmyk` in order it expands - to its conversion to RGB . This conversion is done by - the \^`\_cmyktorgb` macro. Moreover, `\onlyrgb` re-defines three basic RGB + The \`\onlyrgb` declaration redefines \^`\setcmykcolor` to do conversion + to RGB just before \^`\_setcolor` is used. + The \`\onlycmyk` declaration redefines \^`\setrgbcolor` to do conversion + to CMYK just before \^`\_setcolor` is used. + Moreover, \^`\onlyrgb` re-defines three basic RGB colors for RGB color space and re-declares \^`\colordef` as \^`\rgbcolordef`. - The \hbox{\`\onlycmyk`} macro does similar work, it re-defines \^`\_formatrgb` - macro. The Grey color space is unchanged and works in both main - settings (RGB or CMYK) without collisions. \_cod ----------------------------- \_def\_onlyrgb{\_def\Red{\_setrgbcolor{1 0 0}}% \_def\Green{\_setrgbcolor{0 1 0}}\_def\Blue{\_setrgbcolor{0 0 1}}% \_let\_colordef=\_rgbcolordef - \_def\_formatrgb##1{\_fillstroke{##1 rg}{##1 RG}}% - \_def\_formatcmyk##1{\_fillstroke{\_cmyktorgb ##1 ; rg}{\_cmyktorgb ##1 ; RG}}} -\_def\_onlycmyk{\_def\_formatcmyk##1{\_fillstroke{##1 k}{##1 K}}% - \_def\_formatrgb##1{\_fillstroke{\_rgbtocmyk ##1 ; k}{\_rgbtocmyk ##1 ; K}}} + \_def\_setrgbcolor##1{\_setcolor{##1}{rg}{RG}}% + \_def\_setcmykcolor##1{\_ea\_setcolor\_ea{\_expanded{\_cmyktorgb ##1 ;}}{rg}{RG}}% + \_public \colordef \setrgbcolor \setcmykcolor ;} +\_def\_onlycmyk{% + \_let\_colordef=\_cmykcolordef + \_def\_setrgbcolor##1{\_ea\_setcolor\_ea{\_expanded{\_rgbtocmyk ##1 ;}}kK}% + \_def\_setcmykcolor##1{\_setcolor{##1}kK}% + \_public \colordef \setrgbcolor \setcmykcolor ;} \_public \onlyrgb \onlycmyk ; \_doc ----------------------------- - The \`\_setcolor` macro redefines empty `\_ensureblack` macro (used in - output routine for headers and footers) to `\_ensureblackA` which sets - Black at the start of its parameter and returns to the current color at the - end of its parameter. - - The current color is saved into `\_currentcolor` macro and colorstack is pushed. - Finally, the `\_colorstackpop` is initialized by `\aftergroup` if - `\localcolor` is declared. - - You can save the current color to your macro by `\let\yourmacro=\_currentcolor` - and you can return to this color by the command `\_setcolor\yourmacro`. + The \`\_colorattr` for coloring is allocated and + \`\_setcolor``{}{}{}` is defined here. + This macro does `\_colorattr=`\`\_colorcnt` if the was not used + before and prepare mapping from this integer value to the + and increments \^`\_colorcnt`. + If the were used already, then \^`\_setcolor` + does `\_colorattr=`. + This work is done by the \`\_translatecolor` macro. The following mapping + macros are created: + \begtt \catcode`<=13 + \_color:: ... expands to used + \_color: ... expands to + \_color-s: ... expands to + \endtt + The \`\_resetcolor` un-sets the color attribute, it means that default + color (black) shall be used. \_cod ----------------------------- -\_protected\_def \_setcolor #1{\_global\_let\_ensureblack=\_ensureblackA - \_iflocalcolor \_edef\_currentcolor{#1}\_colorstackpush\_currentcolor - \_aftergroup\_colorstackpop - \_else \_xdef\_currentcolor{#1}\_colorstackset\_currentcolor \_fi +\_newattribute \_colorattr +\_newcount \_colorcnt \_colorcnt=1 % allocations start at 1 +\_protected\_def\_setcolor{\_colorprefix\_colorattr=\_translatecolor} +\_def\_resetcolor{\_colorattr=-"7FFFFFFF } +\_def\_translatecolor#1#2#3{\_ifcsname _color::#1 #2\_endcsname\_lastnamedcs\_relax + \_else + \_colorcnt + \_sxdef{_color::#1 #2}{\_the\_colorcnt}% + \_sxdef{_color:\_the\_colorcnt}{#1 #2}% + \_sxdef{_color-s:\_the\_colorcnt}{#1 #3}% + \_incr \_colorcnt + \_fi } -\_def\_pdfblackcolor{0 g 0 G} -\_edef\_currentcolor{\_pdfblackcolor} -\_def\_ensureblackA#1{\_global\_let\_openfnotestack=\_openfnotestackA - \_colorstackpush\_pdfblackcolor #1\_colorstackpop} - - \_doc ----------------------------- - The colorstack is initialized here and the basic macros - \`\_colorstackpush`, \`\_colorstackpop` and \`\_colorstackset` - are defined here. - \_cod ----------------------------- - -\_mathchardef\_colorstackcnt=0 % Implicit stack usage -\_def\_colorstackpush#1{\_pdfcolorstack\_colorstackcnt push{#1}} -\_def\_colorstackpop{\_pdfcolorstack\_colorstackcnt pop} -\_def\_colorstackset#1{\_pdfcolorstack\_colorstackcnt set{#1}} +% Black is the default color. +\_sdef{_color::0 g}{0} +\_sdef{_color:0}{0 g} +\_sdef{_color-s:0}{0 G} \_doc ----------------------------- - We need to open a special color stack for footnotes because footnotes - can follow on the next pages and their colors are independent of colors - used in the main page-body. The \`\_openfnotestack` is defined as - \`\_openfnotestackA` when the \^`\_setcolor` is used first. - The \`\_fnotestack` is initialized in in `\everyjob` because the - initialization is not saved to the format. + We support concept of non-local color, i.e. all changes of the color + attribute are global by setting \`\_colorprefix` to `\global`. + \`\localcolor` is the default, i.e.\ \^`\_colorprefix` is `\relax`.\nl + You can write `\global\Red` if you want to have global setting of the + color. \_cod ----------------------------- -%\_mathchardef\_fnotestack=\_pdfcolorstackinit page {0 g 0 G} % must be in \everyjob -\_def \_openfnotestackA {\_pdfcolorstack\_fnotestack current} +\_protected\_def \_localcolor {\_let\_colorprefix=\_relax} +\_protected\_def \_nolocalcolor {\_let\_colorprefix=\_global} +\_public \localcolor \nolocalcolor ; +\_localcolor \_doc ----------------------------- We use Lua codes for RGB to CMYK or CMYK to RGB conversions and for @@ -297,7 +285,7 @@ \begtt \def\vr{\vrule height10pt depth2pt width20pt} \def\_showcolor{\hbox{\tt\_bslash\_tmpb: \csname\_tmpb\endcsname \vr}\space\space} - \begmulti 4 \typosize[11/14] + \begmulti 4 \typosize[10/14] \morecolors \endmulti \endtt @@ -319,35 +307,49 @@ \_endcode % ------------------------------------- -The colors have different behavior than fonts. Marks (whatsits) with color -information are stored into PDF output and \TeX/ doesn't interpret them. -The PDF viewer (or PDF interpreter in a printer) reads these marks -and switches colors according to them. This -is independent of \TeX/ group mechanism. You can declare -`\nolocalcolor` at the beginning of the document, if you want this behavior. -In this case, if you set a color then you must return to the black color -using `\Black` manually. - -By default, \OpTeX/ sets `\localcolor`. It means that the typesetting -returns to a previous color at the end of the current group, so you cannot -write `\Black` explicitly. This is implemented using the `\aftergroup` feature. -There is a limitation of this feature: when a color selector is used in a -group of a box, which is saved by `\setbox`, then the activity or -reconstruction of the previous color is processed at `\setbox` time, no in the -box itself. You must correct it by double group: -\begtt -\setbox0=\hbox{\Red text} % bad: \Black is done after \setbox -\setbox0=\hbox{{\Red text}} % good: \Black is done after group inside the box -\endtt - -The implementation of colors is based on colorstack, so the current color -can follow across more pages. It is not so obvious because PDF viewer (or PDF -interpreter) manipulates with colors locally at each PDF page and it -initializes each PDF page with black on white color. +\secc Basic concept + +Setting of color in PDF is handled by graphics operators which change the +graphics context. Colors for fills/strokes are distinguished, but apart from +that, only one color is active at time and is used for all material drawn by +following graphics operators, until next color is set. Each PDF content (e.g. +page or form XObject) has its own graphics context, that is initialized from +zero. Hence we have different concept of selecting fonts in \TeX/ (it +depends on \TeX/ groups but does not depends on pages) and color handling in +PDF. + +\TeX/ itself has no concept of colors. Colors have always been handled by +inserting whatsits (either using `\special` for DVI or using +`\pdfliteral`/`\pdfcolorstack` for PDF). It is very efficient and \TeX/ doesn't +even have to know anything about colors, but it is also problematic in many +ways. + +That is the reason why we decided to change color handling from +`\pdfcolorstack` to \LuaTeX/ attributes in version 1.04 of \OpTeX. Using +attributes, the color setting behaves exactly like font selection from \TeX/ +point of view: it respects \TeX/ groups, colors can span more pages, +independent colors can be set for `\insert`s, etc. Moreover, once a material is +created (using `\setbox` for example) then it has its fonts and its colors +frozen and you can rely on it when you are using e.g. `\unhbox`. There are no +internal whatsits for colors which can interfere with other typesetting +material. In the end something like setting text to red (`{\Red text}`) should +have the same nice behavior like setting text to bold (`{\bf text}`). + +\LuaTeX/ attributes can be set like count register -- one attribute holds one +number at a time. But the value of attribute is propagated to each created +typesetting element until the attribute is unset or set to another value. Very +much like the font property. We use one attribute \^`\_colorattr` for storing +the currently selected color (in number form). Macros \^`\setcmykcolor``{ }` or \^`\setrgbcolor``{ }` -or\nl \^`\setgreycolor``{}` should be used in color selectors or user can -specify these macros explicitly. +or\nl \^`\setgreycolor``{}` are used in color selectors. These +macros expand to internal \^`\_setcolor` macro which sets the \^`\_colorattr` +attribute to an integer value and prepares mapping between this value and the real +color data. This mapping is used just before each `\shipout` in output routine. +The \^`\_preshipout` pseudo-primitive is used here, it converts attribute +values to internal PDF commands for selecting colors. + +\secc Color mixing The color mixing processed by the \^`\colordef` is done in the subtractive color model CMYK. If the result has a component greater than 1 then all @@ -405,6 +407,8 @@ and it is used in \^`\colordef` or if it is printed when \^`\onlycmyk` is declar The CMYK to RGB conversion is invoked when a color is declared using \^`\setcmykcolor` and it is used in \^`\rgbcolordef` or if it is printed when \^`\onlyrgb` is declared. +\secc Implementation + \_endinput 2021-05-28 \rgbcmykmap introduced diff --git a/optex/base/doc.opm b/optex/base/doc.opm index 643a283e..34a00dc1 100644 --- a/optex/base/doc.opm +++ b/optex/base/doc.opm @@ -127,7 +127,7 @@ The lines in the listing mode have a yellow background. \_cod ----------------------------- -\_def\Yellow{\_setcmykcolor{0.0 0.0 0.3 0.03}} +\_def\Yellow{\_setcmykcolor{0 0 .3 .03}} \_def\_printcodeline#1{\_advance \_maxlines by-1 \_ifnum \_maxlines<0 \_ea \_endverbprinting \_fi @@ -137,7 +137,7 @@ \_indent \_printverblinenum #1\par} \_def\_printfilename{\_hbox to0pt{% - \_hskip\_hsize\vbox to0pt{\_vss\_llap{\Brown\docfile}\_kern7.5pt}\_hss}% + \_hskip\_hsize\_vbox to0pt{\_vss\_llap{\Brown\docfile}\_kern7.5pt}\_hss}% \_let\_printfilename=\_relax } \_everytt={\_let\_printverblinenum=\_relax} @@ -153,7 +153,7 @@ \_def\docfile{} \_def\_printdoc #1 {\_par \_def\docfile{#1}% \_everytt={\_ttshift=-15pt \_let\_printverblinenum=\_relax}% - \_ea\_cod \input #1 + \_ea\_cod \_input #1 \_everytt={\_let\_printverblinenum=\_relax}% \_def\docfile{}% } diff --git a/optex/base/fonts-select.opm b/optex/base/fonts-select.opm index 5dab5104..c22853a5 100644 --- a/optex/base/fonts-select.opm +++ b/optex/base/fonts-select.opm @@ -146,6 +146,7 @@ \_directlua{% require('luaotfload-main') luaotfload.main() + optex_hook_into_luaotfload() }% \_gdef\_rfskipatX ##1" ##2\_relax{"##1"}% \_global\_let \_doresizefont=\_doresizeunifont diff --git a/optex/base/graphics.opm b/optex/base/graphics.opm index 1adb444b..aacf239e 100644 --- a/optex/base/graphics.opm +++ b/optex/base/graphics.opm @@ -231,8 +231,8 @@ are parameters, they can be set by user in optional brackets `[...]`. For example `\fcolor=\Red` does `\_let\_fcolorvalue=\Red` and it means filling color.\nl - The \`\_setflcolor` uses the \^`\_fillstroke` macro to separate filling - color and drawing color. + The \`\_setflcolors` uses the \^`\_setcolor` macro to separate filling (non-stroking) + color and stroking color. \_cod ----------------------------- \_newdimen \_lwidth @@ -245,10 +245,9 @@ \_def\_touppervalue#1{\_ifx#1n\_let#1=N\_fi} \_def\_setflcolors#1{% use only in a group - \_def\_setcolor##1{##1}% - \_def\_fillstroke##1##2{##1}% + \_def\_setcolor##1##2##3{##1 ##2}% \_edef#1{\_fcolorvalue}% - \_def\_fillstroke##1##2{##2}% + \_def\_setcolor##1##2##3{##1 ##3}% \_edef#1{#1\_space\_lcolorvalue\_space}% } \_optdef\_inoval[]{\_vbox\_bgroup @@ -260,9 +259,7 @@ \_advance\_hsize by-2\_hhkern \_advance\_hsize by-2\_roundness \_fi \_setbox0=\_hbox\_bgroup\_bgroup \_aftergroup\_inovalA \_kern\_hhkern \_let\_next=% } -\_def\_inovalA{\_isnextchar\_colorstackpop\_inovalB\_inovalC} -\_def\_inovalB#1{#1\_isnextchar\_colorstackpop\_inovalB\_inovalC} -\_def\_inovalC{\_egroup % of \setbox0=\hbox\bgroup +\_def\_inovalA{\_egroup % of \setbox0=\hbox\bgroup \_ifdim\_vvkern=\_zo \_else \_ht0=\_dimexpr\_ht0+\_vvkern \_relax \_dp0=\_dimexpr\_dp0+\_vvkern \_relax \_fi \_ifdim\_hhkern=\_zo \_else \_wd0=\_dimexpr\_wd0+\_hhkern \_relax \_fi @@ -290,9 +287,7 @@ \_touppervalue\_overlapmarginsvalue \_touppervalue\_shadowvalue \_setbox0=\_hbox\_bgroup\_bgroup \_aftergroup\_incircleA \_kern\_hhkern \_let\_next=% } -\_def\_incircleA {\_isnextchar\_colorstackpop\_incircleB\_incircleC} -\_def\_incircleB #1{#1\_isnextchar\_colorstackpop\_incircleB\_incircleC} -\_def\_incircleC {\_egroup % of \setbox0=\hbox\bgroup +\_def\_incircleA {\_egroup % of \setbox0=\hbox\bgroup \_wd0=\_dimexpr \_wd0+\_hhkern \_relax \_ht0=\_dimexpr \_ht0+\_vvkern \_relax \_dp0=\_dimexpr \_dp0+\_vvkern \_relax \_ifdim \_ratiovalue\_dimexpr \_ht0+\_dp0 > \_wd0 @@ -318,6 +313,39 @@ \_public \inoval \incircle \ratio \lwidth \fcolor \lcolor \shadow \overlapmargins ; + \_doc ----------------------------- + Just before defining shadows, which require special graphics states, we + define means for managing these graphics states. This is important, because + otherwise our use of `\pdfpageresources` register might clash with other + packages (TikZ) or even with our other usage (slides). + + The macro \`\addextgstate`` ` shall be used for + adding more graphics states. It must be used {\em after} `\dump`. First use + of it detects PGF/TikZ and either uses its mechanism or defines our own. Our + mechanism is very similar though -- use single `/ExtGState` dictionary for all + pages (`\pdfpageresources` just points to it). + \_cod ----------------------------- + +\_def\_initpageresources{% + \_glet\_initpageresources=\_relax + \_ifcsname pgf@sys@addpdfresource@extgs@plain\_endcsname + % TikZ loaded + \_global\_slet{_addextgstate}{pgf@sys@addpdfresource@extgs@plain}% + \_else + % TikZ not loaded + \_pdfobj reserveobjnum% not to be used in iniTeX + \_xdef\_extgstatesobj{\_the\_pdflastobj}% + \_expanded{\_global\_pdfpageresources={/ExtGState \_extgstatesobj\_space 0 R}}% + \_global\_addto\_byehook{\_immediate\_pdfobj useobjnum\_extgstatesobj {<<\_extgstates>>}}% + \_gdef\_extgstates{}% + \_gdef\_addextgstate##1{\_xdef\_extgstates{\_extgstates\_space##1}}% + \_fi +} +% first initialize page resources, then execute new meaning of itself +\_def\_addextgstate#1{\_initpageresources \_addextgstate{#1}} + +\_public \addextgstate ; + \_doc ----------------------------- A shadow effect is implemented here. The shadow is equal to the silhouette of the given path in a gray-transparent color shifted by @@ -333,27 +361,11 @@ \_def\_shadowmoveto{1.8 -2.5} % vector defines shifting layer (in bp) \_def\_shadowb{1} % 2*shadowb = blurring area thickness - \_doc ----------------------------- - The `\_pdfpageresources` primitive is used to define transparency. - It does not work when used in a box. So, we use it at the beginning of - the output routine. The modification of the output routine is done - using \`\_insertshadowresources` only once when the shadow effect is used first. - \_cod ----------------------------- - \_def\_insertshadowresources{% - \_global\_addto\_begoutput{\_setshadowresources}% - \_xdef\_setshadowresources{% - \_pdfpageresources{/ExtGState - << - /op1 <> - /op2 <> - \_morepgresources - >> - }% - }% - \_global\_let\_insertshadowresources=\_relax + \_addextgstate{/op1 <>}% + \_addextgstate{/op2 <>}% + \_glet\_insertshadowresources=\_relax } -\_def\_morepgresources{} \_doc ----------------------------- The \`\_doshadow``{}` does the shadow effect. diff --git a/optex/base/hisyntax-python.opm b/optex/base/hisyntax-python.opm index 6250eed0..12f7ebee 100644 --- a/optex/base/hisyntax-python.opm +++ b/optex/base/hisyntax-python.opm @@ -69,8 +69,8 @@ } \_gdef\_hipystrpre#1\x#2{\x#2{#1}\x#2} -\_ifx\LightBlue\_undefined \_gdef\LightBlue {\_setcmykcolor{1 0.43 0 0}}\_fi -\_ifx\Orange\_undefined \_gdef\Orange {\_setcmykcolor{0 0.64 1 0}}\_fi +\_ifx\LightBlue\_undefined \_gdef\LightBlue {\_setcmykcolor{1 .43 0 0}}\_fi +\_ifx\Orange\_undefined \_gdef\Orange {\_setcmykcolor{0 .64 1 0}}\_fi \_endcode %------------------------------------------------ diff --git a/optex/base/optex.ini b/optex/base/optex.ini index 7f63aaea..41b8cebc 100644 --- a/optex/base/optex.ini +++ b/optex/base/optex.ini @@ -21,7 +21,7 @@ % OpTeX version -\def\optexversion{1.03+ Jun.2021} +\def\optexversion{1.03^ Jun.2021} \def\fmtname{OpTeX} \let\fmtversion=\optexversion @@ -95,7 +95,6 @@ \_everyjob = {% \_message{This is OpTeX (Olsak's Plain TeX), version <\optexversion>^^J}% - \_mathchardef\_fnotestack=\_pdfcolorstackinit page {0 g 0 G}% \_directlua{lua.bytecode[1]()}% load OpTeX's Lua code \_mathsbon % replaces \int_a^b to \int _a^b \_inputref % inputs \jobname.ref if exists diff --git a/optex/base/optex.lua b/optex/base/optex.lua index 67557998..0c67ecca 100644 --- a/optex/base/optex.lua +++ b/optex/base/optex.lua @@ -2,7 +2,11 @@ -- The basic lua functions and declarations used in \OpTeX/ are here --- GENERAL +-- \medskip\secc General^^M +-- +-- Define namespace where some \OpTeX/ functions will be added. + +optex = optex or {} -- Error function used by following functions for critical errors. local function err(message) @@ -15,7 +19,7 @@ function registernumber(name) return token.create(name).index end -- --- ALLOCATORS +-- \medskip\secc[lua-alloc] Allocators^^M alloc = alloc or {} -- -- An attribute allocator in Lua that cooperates with normal \OpTeX/ allocator. @@ -32,10 +36,20 @@ function alloc.new_attribute(name) end end -- +-- Allocator for Lua functions ("pseudoprimitives"). It passes variadic +-- arguments (\"`...`") like `"global"` to `token.set_lua`. +local function_table = lua.get_functions_table() +local luafnalloc = 0 +function define_lua_command(csname, fn, ...) + luafnalloc = luafnalloc + 1 + token.set_lua(csname, luafnalloc, ...) + function_table[luafnalloc] = fn +end +-- -- `provides_module` is needed by older version of luaotfload provides_module = function() end -- --- CALLBACKS +-- \medskip\secc[callbacks] Callbacks^^M callback = callback or {} -- -- Save `callback.register` function for internal use. @@ -181,6 +195,7 @@ end -- callbacks, that are called manually by user using `call_callback` or for -- standard callbacks that have default functions -- like `mlist_to_hlist` (see -- below). +local call_callback function callback.add_to_callback(name, fn, description) if user_callbacks[name] or callback_functions[name] or default_functions[name] then -- either: @@ -194,7 +209,7 @@ function callback.add_to_callback(name, fn, description) -- when things break, like when callbacks get redefined by future -- luatex. assert(callback_register(name, function(...) - return callback.call_callback(name, ...) + return call_callback(name, ...) end)) else err("cannot add to callback '"..name.."' - no such callback exists") @@ -307,6 +322,7 @@ function callback.call_callback(name, ...) end return not changed or head end +call_callback = callback.call_callback -- -- Create \"virtual" callbacks `pre/post_mlist_to_hlist_filter` by setting -- `mlist_to_hlist` callback. The default behaviour of `mlist_to_hlist` is kept by @@ -317,7 +333,7 @@ callback.create_callback("pre_mlist_to_hlist_filter", "list") callback.create_callback("post_mlist_to_hlist_filter", "reverselist") callback_register("mlist_to_hlist", function(head, ...) -- pre_mlist_to_hlist_filter - local new_head = callback.call_callback("pre_mlist_to_hlist_filter", head, ...) + local new_head = call_callback("pre_mlist_to_hlist_filter", head, ...) if new_head == false then node.flush_list(head) return nil @@ -326,9 +342,9 @@ callback_register("mlist_to_hlist", function(head, ...) end -- mlist_to_hlist means either added functions or standard luatex behavior -- of node.mlist_to_hlist (handled by default function) - head = callback.call_callback("mlist_to_hlist", head, ...) + head = call_callback("mlist_to_hlist", head, ...) -- post_mlist_to_hlist_filter - new_head = callback.call_callback("post_mlist_to_hlist_filter", head, ...) + new_head = call_callback("post_mlist_to_hlist_filter", head, ...) if new_head == false then node.flush_list(head) return nil @@ -338,6 +354,27 @@ callback_register("mlist_to_hlist", function(head, ...) return head end) -- +-- For preprocessing boxes just before shipout we define custom callback. This +-- is used for coloring based on attributes. +-- There is however a challenge - how to call this callback? We could redefine +-- `\shipout` and `\pdfxform` (which both run `ship_out` procedure internally), +-- but they would lose their primtive meaning – i.e. `\immediate` wouldn't work +-- with `\pdfxform`. The compromise is to require anyone to run +-- \`\_preshipout``` just before +-- `\shipout` or `\pdfxform` if they want to call `pre_shipout_filter` (and +-- achieve colors and possibly more). +callback.create_callback("pre_shipout_filter", "list") + +local tex_setbox = tex.setbox +local token_scanint = token.scan_int +local token_scanlist = token.scan_list +define_lua_command("_preshipout", function() + local boxnum = token_scanint() + local head = token_scanlist() + head = call_callback("pre_shipout_filter", head) + tex_setbox(boxnum, head) +end) +-- -- Compatibility with \LaTeX/ through luatexbase namespace. Needed for -- luaotfload. luatexbase = { @@ -364,3 +401,187 @@ callback.add_to_callback("input_level_string", end end ) +-- +-- \medskip\secc[lua-colors] Handling of colors using attributes^^M +-- +-- Because \LuaTeX/ doesn't do anything with attributes, we have to add meaning +-- to them. We do this by intercepting \TeX/ just before it ships out a page and +-- inject PDF literals according to attributes. +-- +local node_id = node.id +local glyph_id = node_id("glyph") +local rule_id = node_id("rule") +local glue_id = node_id("glue") +local hlist_id = node_id("hlist") +local vlist_id = node_id("vlist") +local disc_id = node_id("disc") +local token_getmacro = token.get_macro + +local direct = node.direct +local todirect = direct.todirect +local tonode = direct.tonode +local getfield = direct.getfield +local setfield = direct.setfield +local getwhd = direct.getwhd +local getid = direct.getid +local getlist = direct.getlist +local setlist = direct.setlist +local getleader = direct.getleader +local getattribute = direct.get_attribute +local insertbefore = direct.insert_before +local copy = direct.copy +local traverse = direct.traverse +local one_bp = tex.sp("1bp") +local string_format = string.format +-- +-- The attribute for coloring is allocated in `colors.opm` +local color_attribute = registernumber("_colorattr") +-- +-- Now we define function which creates whatsit nodes with PDF literals. We do +-- this by creating a base literal, which we then copy and customize. +-- +local pdf_base_literal = direct.new("whatsit", "pdf_literal") +setfield(pdf_base_literal, "mode", 2) -- direct mode +local function pdfliteral(str) + local literal = copy(pdf_base_literal) + setfield(literal, "data", str) + return literal +end +optex.directpdfliteral = pdfliteral +-- +-- The function {\Red`colorize`}`(head, current, current_stroke)` goes through +-- a node list and injects PDF literals according to attributes. +-- Its arguments are the head of the list to be colored and the current color +-- for fills and strokes. It is a recursive function – nested +-- horizontal and vertical lists are handled in the same way. Only the +-- attributes of “content” nodes (glyphs, rules, etc.) matter. Users drawing +-- with PDF literals have to set color themselves. +-- +-- Whatsit node with color setting PDF literal is injected only when a +-- different color is needed. Our injection does not care about boxing levels, +-- but this isn't a problem, since PDF literal whatsits just instruct the +-- `\shipout` related procedures to emit the literal. +-- +-- We also set the stroke and non-stroke colors separately. This is because +-- stroke color is not always needed – \LuaTeX/ itself only uses it for rules +-- whose one dimension is less than or equal to 1 bp and for fonts whose `mode` +-- is set to 1 (outline) or 2 (outline and fill). Catching these cases is a +-- little bit involved. For example rules are problematic, because at this +-- point their dimensions can still be running ($-2^{30}$) – they may or may +-- not be below the one big point limit. Also the text direction is involved. +-- Because of the negative value for running dimensions the simplistic check, +-- while not fully correct, should produce the right results. We currently +-- don't check for the font mode at all. +-- +-- Leaders (represented by glue nodes with leader field) are not handled fully. +-- They are problematic, because their content is repeated more times and it +-- would have to be ensured that the coloring would be right even for e.g. +-- leaders that start and end on a different color. We came to conclusion that +-- this is not worth, hence leaders are handled just opaquely and only the +-- attribute of the glue node itself is checked. For setting different colors +-- inside leaders, raw PDF literals have to be used. +-- +-- We use the `node.direct` way of working with nodes. This is less safe, and +-- certainly not idiomatic Lua, but faster and codewise more close to the way +-- \TeX/ works with nodes. +local function is_color_needed(head, n, id, subtype) -- returns non-stroke, stroke color needed + if id == glyph_id then + return true, false + elseif id == glue_id then + n = getleader(n) + if n then + id = getid(n) + if id == hlist_id or id == vlist_id then + -- leaders with hlist/vlist get single color + return true, false + else -- rule + -- stretchy leaders with rules are tricky, + -- just set both colors for safety + return true, true + end + end + elseif id == rule_id then + local width, height, depth = getwhd(n) + if width <= one_bp or height + depth <= one_bp then + -- running (-2^30) may need both + return true, true + end + return true, false + end + return false, false +end + +local function colorize(head, current, current_stroke) + for n, id, subtype in traverse(head) do + if id == hlist_id or id == vlist_id then + -- nested list, just recurse + local list = getlist(n) + list, current, current_stroke = colorize(list, current, current_stroke) + setlist(n, list) + elseif id == disc_id then + -- at this point only no-break (replace) list is of any interest + local replace = getfield(n, "replace") + if replace then + replace, current, current_stroke = colorize(replace, current, current_stroke) + setfield(n, "replace", replace) + end + else + local nonstroke_needed, stroke_needed = is_color_needed(head, n, id, subtype) + local new = getattribute(n, color_attribute) or 0 + local newcolor = nil + if current ~= new and nonstroke_needed then + newcolor = token_getmacro("_color:"..new) + current = new + end + if current_stroke ~= new and stroke_needed then + local stroke_color = token_getmacro("_color-s:"..current) + if stroke_color then + if newcolor then + newcolor = string_format("%s %s", newcolor, stroke_color) + else + newcolor = stroke_color + end + current_stroke = new + end + end + if newcolor then + head = insertbefore(head, n, pdfliteral(newcolor)) + end + end + end + return head, current, current_stroke +end +-- +-- Colorization should be run just before shipout. We use our custom callback +-- for this. See the definition of `pre_shipout_filter` for details on +-- limitations. +callback.add_to_callback("pre_shipout_filter", function(list) + -- By setting initial color to -1 we force initial setting of color on + -- every page. This is useful for transparently supporting other default + -- colors than black (although it has a price for each normal document). + local list = colorize(todirect(list), -1, -1) + return tonode(list) +end, "_colors") +-- +-- We also hook into `luaotfload`'s handling of color. Instead of the default +-- behavior (inserting colorstack whatsits) we set our own attribute. The hook +-- has to be registered {\em after} `luaotfload` is loaded. +function optex_hook_into_luaotfload() + local setattribute = direct.set_attribute + local token_setmacro = token.set_macro + local color_count = registernumber("_colorcnt") + local tex_getcount, tex_setcount = tex.getcount, tex.setcount + luaotfload.set_colorhandler(function(head, n, rgbcolor) -- rgbcolor = "1 0 0 rg" + local attr = tonumber(token_getmacro("_color::"..rgbcolor)) + if not attr then + attr = tex_getcount(color_count) + tex_setcount(color_count, attr + 1) + local strattr = tostring(attr) + token_setmacro("_color::"..rgbcolor, strattr) + token_setmacro("_color:"..strattr, rgbcolor) + -- no stroke color set + end + setattribute(n, color_attribute, attr) + return head, n + end) +end diff --git a/optex/base/output.opm b/optex/base/output.opm index 5bb93c24..e1f3c51d 100644 --- a/optex/base/output.opm +++ b/optex/base/output.opm @@ -3,11 +3,15 @@ \_codedecl \nopagenumbers {Output routine <2021-02-25>} % preloaded in format \_doc ----------------------------- - \`\_optexoutput` is the default output routine. You can create another... + \`\_optexoutput` is the default output routine. You can create another...\nl + The \^`\_preshipout``` used + here behaves similarly like `\setbox` but it does not only copy the box + contents but adds the color literals depending on used attributes. + It is defined using lua code, see section~\ref[lua]. \_cod ----------------------------- \_output={\_optexoutput} -\_def \_optexoutput{\_begoutput \_shipout\_completepage \_endoutput} +\_def \_optexoutput{\_begoutput \_preshipout0\_completepage \_shipout\_box0 \_endoutput} \_doc ----------------------------- Default \`\_begoutput` and \`\_endoutput` is defined. @@ -58,24 +62,19 @@ The \`\_completepage` is similar to what plain \TeX/ does in its output routine. New is only \`\_backgroundbox`. It is `\vbox` with zero height with its contents (from \^`\pgbackground`) extended down. It is shifted directly to the - left-upper corner of the paper. - - The \`\_ensureblack` sets the typesetting of its parameter locally to `\Black` - color. We needn't do this if colors are never used in the document. So, - the default value of the `\_ensureblack` macro is empty. But the first usage of - color macros in the document re-defines `\_ensureblack`. - See the section~\ref[colors] for more details. + left-upper corner of the paper.\nl + The \^`\_resetcolor` used here means that all newly created texts in + output routine (texts used in headline, footline) have default color. \_cod ----------------------------- \_def\_completepage{\_vbox{% + \_resetcolor \_istoksempty \_pgbackground - \_iffalse \_ensureblack{\_backgroundbox{\_the\_pgbackground}}\_nointerlineskip \_fi - \_ensureblack{\_makeheadline}% + \_iffalse \_backgroundbox{\_the\_pgbackground}\_nointerlineskip \_fi + \_makeheadline \_vbox to\_vsize {\_boxmaxdepth=\_maxdepth \_pagecontents}% \pagebody in plainTeX - \_ensureblack{\_makefootline}}% + \_makefootline}% } -\_def \_ensureblack #1{#1} % will be re-defined by color macros -\_let \_openfnotestack = \_relax % will be re-defined by color macros \_def \_backgroundbox #1{\_moveleft\_hoffset\_vbox to\_zo{\_kern-\_voffset #1\_vss}} \_doc ----------------------------- @@ -102,17 +101,16 @@ \_doc ----------------------------- The \`\_pagecontents` is similar as in plain \TeX/. The only differnece is - that the \`\_pagedest` is inserted at the top of `\_pagecontents` and - \^`\_ensureblack` is applied to the \^`\topins` and \^`\footins` material.\nl + that the \`\_pagedest` is inserted at the top of `\_pagecontents`.\nl The \`\_footnoterule` is defined here. \_cod ----------------------------- \_def\_pagecontents{\_pagedest % destination of the page - \_ifvoid\_topins \_else \_ensureblack{\_unvbox\_topins}\_fi + \_ifvoid\_topins \_else \_unvbox\_topins\_fi \_dimen0=\_dp255 \_unvbox255 % open up \box255 \_ifvoid\_footins \_else % footnote info is present \_vskip\_skip\_footins - \_ensureblack{\_footnoterule \_openfnotestack \_unvbox\_footins}\_fi + \_footnoterule \_unvbox\_footins\_fi \_kern-\_dimen0 \_vskip \_pgbottomskip } \_def \_pagedest {{\_def\_destheight{25pt}\_dest[pg:\_the\_gpageno]}} @@ -157,7 +155,7 @@ \_def \_opfootnote #1#2{\_insert\_footins\_bgroup \_interlinepenalty=\_interfootnotelinepenalty \_leftskip=\_zo \_rightskip=\_zo \_spaceskip=\_zo \_xspaceskip=\_zo \_relax - \_let\_colorstackcnt=\_fnotestack % special color stack for footnotes + \_resetcolor #1\_relax % local settings used by \fnote macro \_splittopskip=\_ht\_strutbox % top baseline for broken footnotes \_splitmaxdepth=\_dp\_strutbox \_floatingpenalty=20000 @@ -165,10 +163,8 @@ \_isnextchar \_bgroup {\_bgroup \_aftergroup\_vfootA \_afterassignment\_ignorespaces \_let\_next=}{\_vfootB}% } -\_def\_vfootA{\_unskip\_strut\_isnextchar\_colorstackpop\_closefncolor\_vfootF} -\_def\_vfootB #1{#1\_unskip\_strut\_vfootF} -\_def\_vfootF{\_egroup} % close \_insert\_footins\_bgroup -\_def\_closefncolor#1{#1\_isnextchar\_colorstackpop\_closefncolor\_vfootF} +\_def\_vfootA{\_unskip\_strut\_egroup} +\_def\_vfootB #1{#1\_unskip\_strut\_egroup} \_def \_footstrut {\_vbox to\_splittopskip{}} \_skip\_footins=\_bigskipamount % space added when footnote is present \_count\_footins=1000 % footnote magnification factor (1 to 1) @@ -190,7 +186,7 @@ \_skip\_topins=\_zoskip % no space added when a topinsert is present \_count\_topins=1000 % magnification factor (1 to 1) \_dimen\_topins=\_maxdimen % no limit per page -\_def \_oins {\_par \_begingroup\_setbox0=\_vbox\_bgroup} % start a \_vbox +\_def \_oins {\_par \_begingroup\_setbox0=\_vbox\_bgroup\_resetcolor} % start a \_vbox \_def \_endinsert {\_par\_egroup % finish the \_vbox \_ifumid \_dimen0=\_ht0 \_advance\_dimen0 by\_dp0 \_advance\_dimen0 by\_baselineskip \_advance\_dimen0 by\_pagetotal \_advance\_dimen0 by-\_pageshrink @@ -214,11 +210,11 @@ \_fontdef\_draftfont{\_setfontsize{at10pt}\_bf}% \_global\_let\_draftfont=\_draftfont } -\_def \_draftbox #1{\_setbox0=\_hbox{#1}% +\_def \_draftbox #1{\_setbox0=\_hbox{\_setgreycolor{.8}#1}% \_kern.5\_vsize \_kern\_voffset \_kern4.5\_wd0 \_hbox to0pt{\_kern.5\_xhsize \_kern\_hoffset \_kern-2\_wd0 \_pdfsave \_pdfrotate{55}\_pdfscale{10}{10}% - \_hbox to0pt{\_localcolor\_setgreycolor{.8}\_box0\_hss}% + \_hbox to0pt{\_box0\_hss}% \_pdfrestore \_hss}% } diff --git a/optex/base/slides.opm b/optex/base/slides.opm index a491d1f5..0fed12ac 100644 --- a/optex/base/slides.opm +++ b/optex/base/slides.opm @@ -53,19 +53,20 @@ \_doc ----------------------------- The \`\pshow``` prints the text in invisible (transparent) font when \^`\layernum`\code{<}``. - The transparency is set by `\pdfpageresoyrces` primitive. + For transparency we need to define special graphics states. \_cod ----------------------------- -\pdfpageresources{/ExtGState << /Invisible << /Type /ExtGState /ca 0 /CA 0 >> - /Visible << /Type /ExtGState /ca 1 /CA 1 >> >>} -\addto\_morepgresources{/Invisible << /Type /ExtGState /ca 0 /CA 0 >> - /Visible << /Type /ExtGState /ca 1 /CA 1 >>} -\def\Invisible {\_pdfliteral{/Invisible gs}} -\def\Visible {\_pdfliteral{/Visible gs}} -\def\Transparent {\Invisible \_aftergroup \Visible} +\_addextgstate{/Invisible <>} +\_addextgstate {/Visible <>} + +\_def\_Invisible {\_pdfliteral{/Invisible gs}} +\_def\_Visible {\_pdfliteral{/Visible gs}} +\_def\_Transparent {\_Invisible \_aftergroup \_Visible} + +\_public \Invisible \Visible \Transparent ; \_def\_use#1#2{\_ifnum\_layernum#1\_relax#2\_fi} -\_def\_pshow#1{\_use{=#1}\Red \_use{<#1}\Transparent \_ignorespaces} +\_def\_pshow#1{\_use{=#1}\Red \_use{<#1}\_Transparent \_ignorespaces} \_doc ----------------------------- The main level list of items is activated here. The `\_item:X` and diff --git a/optex/base/table.opm b/optex/base/table.opm index 5369aaac..80dd4317 100644 --- a/optex/base/table.opm +++ b/optex/base/table.opm @@ -84,16 +84,6 @@ If no `pxto` keyword was used, then we print the table using `\halign` directly. The \^`\_tablew` macro is nonempty if the `to` keyword was used. - Because the color selector with `\aftergroup` can be used inside the - table item, we must create the second real group for each table item. - This is reason why we start `` by `\bgroup` and we - end it by `\egroup` in the \`\_tableC` macro. Each `&` character - is stored as `\egroup&\bgroup` in ``. The - `\halign\_tablew\_tableC` really does: - \begtt \catcode`\<=13 - \halign\_tablew{\bgroup\egroup\tabskip=\tabskipr \cr\crcr} - \endtt - \relax The are re-tokenized by `\_scantextokens` in order to be more robust to catcode changing inside the . But inline verbatim cannot work in special cases here like \code{`\{`} for example. @@ -116,8 +106,7 @@ \_halign\_tablew \_tableC \_fi \_egroup } -\_def\_tableC{\_ea{\_ea\_bgroup\_the\_tabdata\_egroup\_tabskip=\_tabskipr\_cr - \_scantextokens\_ea{\_tmpb\_crcr}}} +\_def\_tableC{\_ea{\_the\_tabdata\_tabskip=\_tabskipr\_cr \_scantextokens\_ea{\_tmpb\_crcr}}} \_doc ----------------------------- \`\_tabreplstrings` replaces each `\crl` etc. to `\crcr\crl`. @@ -143,11 +132,11 @@ For example, the following result is generated when `=|cr||cl|`. \begtt - tabdata: \_vrule\_the\_tabiteml\_hfil#\_unsskip\_hfil\_the\_tabitemr\_tabstrutA - &\_the\_tabiteml\_hfil#\_unsskip\_the\_tabitemr + tabdata: \_vrule\_the\_tabiteml{\_hfil#\_unsskip\_hfil}\_the\_tabitemr\_tabstrutA + &\_the\_tabiteml{\_hfil#\_unsskip}\_the\_tabitemr \_vrule\_kern\_vvkern\_vrule\_tabstrutA - &\_the\_tabiteml\_hfil#\_unsskip\_hfil\_the\_tabitemr\_tabstrutA - &\_the\_tabiteml#\_unsskip\_hfil\_the\_tabitemr\_vrule\_tabstrutA + &\_the\_tabiteml{\_hfil#\_unsskip\_hfil}\_the\_tabitemr\_tabstrutA + &\_the\_tabiteml{\_relax#\_unsskip\_hfil}\_the\_tabitemr\_vrule\_tabstrutA ddlinedata: &\_dditem &\_dditem\_vvitem &\_dditem &\_dditem \endtt The second result in the \`\_ddlinedata` macro is a template of one row of the table @@ -200,7 +189,7 @@ \_public \colnum ; \_def\_addtabitemx{\_ifnum\_colnum>0 - \_addtabdata{\_egroup &\_bgroup}\_addto\_ddlinedata{&\_dditem}\_fi + \_addtabdata{&}\_addto\_ddlinedata{&\_dditem}\_fi \_advance\_colnum by1 \_let\_tmpa=\_relax \_ifnum\_colnum>1 \_ea\_addtabdata\_ea{\_ea\_colnum\_the\_colnum\_relax}\_fi} \_def\_addtabdata#1{\_tabdata\_ea{\_the\_tabdata#1}} @@ -227,11 +216,18 @@ letter and `\def\_paramtabdeclare{...}` for a letter with a parameter. The double hash `##` must be in the definition, it is replaced by a real table item data. You can declare more such \"declaration letters" if you want. + + Note, that the `##` with fills are in group. The reason can be explained + by following example: + \begtt + \table{|c|c|}{\crl \Red A & B \crl} + \endtt + We don't want vertical line after red A to be in red. \_cod ----------------------------- -\_def\_tabdeclarec{\_the\_tabiteml\_hfil##\_unsskip\_hfil\_the\_tabitemr} -\_def\_tabdeclarel{\_the\_tabiteml\_relax##\_unsskip\_hfil\_the\_tabitemr} -\_def\_tabdeclarer{\_the\_tabiteml\_hfil##\_unsskip\_the\_tabitemr} +\_def\_tabdeclarec{\_the\_tabiteml{\_hfil##\_unsskip\_hfil}\_the\_tabitemr} +\_def\_tabdeclarel{\_the\_tabiteml{\_relax##\_unsskip\_hfil}\_the\_tabitemr} +\_def\_tabdeclarer{\_the\_tabiteml{\_hfil##\_unsskip}\_the\_tabitemr} \_def\_paramtabdeclarep#1{\_the\_tabiteml\_vtop{\_hsize=#1\_relax \_partabitem{##}}\_the\_tabitemr} \_doc ----------------------------- @@ -335,7 +331,7 @@ \_def\_mspan{\_omit \_afterassignment\_mspanA \_mscount=} \_def\_mspanA[#1]#2{\_loop \_ifnum\_mscount>1 \_cs{_span}\_omit \_advance\_mscount-1 \_repeat \_count1=\_colnum \_colnum=0 \_def\_tmpa{}\_tabdata={}\_scantabdata#1\_relax - \_colnum=\_count1 \_setbox0=\_vbox{\_halign\_ea{\_ea\_bgroup\_the\_tabdata\_egroup\_cr#2\_cr}% + \_colnum=\_count1 \_setbox0=\_vbox{\_halign\_ea{\_the\_tabdata\_cr#2\_cr}% \_global\_setbox8=\_lastbox}% \_setbox0=\_hbox{\_unhbox8 \_unskip \_global\_setbox8=\_lastbox}% \_unhbox8 \_ignorespaces} @@ -558,6 +554,7 @@ after the second one. \_endinput +2021-06-29 grouping in items modified. 2021-06-03 \scantextokens added to \_partabitem, bug fixed. 2021-06-03 \fS corrected, re-implemented. 2021-06-02 \table in \table allowed, bug fixed. diff --git a/optex/doc/optex-techdoc.tex b/optex/doc/optex-techdoc.tex index 31f789d5..4f94e8be 100644 --- a/optex/doc/optex-techdoc.tex +++ b/optex/doc/optex-techdoc.tex @@ -269,6 +269,10 @@ and is actually used in \OpTeX/. Other functions are defined more or less just to suit luaotfload's use. +The allocations are declared in subsection~\ref[lua-alloc], calbacks are +implemented in subsection~\ref[callbacks] and handling with colors can be +found in the subsection~\ref[lua-colors]. + \newtoks \_hisyntaxlua \_hisyntaxlua={% \_hicolor C \Green diff --git a/optex/doc/optex-userdoc.tex b/optex/doc/optex-userdoc.tex index 93dad283..b77bdfe6 100644 --- a/optex/doc/optex-userdoc.tex +++ b/optex/doc/optex-userdoc.tex @@ -1349,14 +1349,16 @@ {`\White`}, {\Grey `\Grey`}, {\LightGrey `\LightGrey`} and -`\Black`. User can define more -such selectors by setting four CMYK components -\new -or three RGB components. For example +`\Black`. More +such selectors can be defined +by setting four CMYK components (using \^`\setcmykcolor`), +or three RGB components (using \^`\setrgbcolor`) +or one grey component (using \^`\setgreycolor`). For example \begtt \def \Orange {\setcmykcolor{0 0.5 1 0}} \def \Purple {\setrgbcolor{1 0 1}} +\def \DarkGrey {\setgreycolor{.1}} \endtt \new @@ -1368,7 +1370,7 @@ `\Chocolate` is Chocolate1, `\ChocolateB` is Chocolate2 etc. \new -The color selectors work locally in groups by default but with limitations. See +The color selectors work locally in groups by default. See the technical documentation, section~\ref[colors] for more information. The basic colors \^`\Blue`, \^`\Red`, \^`\Cyan`, \^`\Yellow` etc.\ are defined @@ -1405,23 +1407,24 @@ emulates color behavior on a painter's palette. You can use \^`\rgbcolordef` instead of \^`\colordef` if you want to mix colors in the additive RGB color space. +If \^`\onlyrgb` is set then \^`\colordef` works like \^`\rgbcolordef`. \def\coloron#1#2#3{% - \setbox0=\hbox{{#2#3}}\leavevmode - \localcolor \rlap{#1\strut \vrule width\wd0}\box0 + \setbox0=\hbox{#2#3}\leavevmode + \rlap{#1\strut \vrule width\wd0}\box0 } -The following example defines the macro for the -\coloron\Yellow\Brown{colored text on the colored background}. Usage: +The following example defines the macro for +\coloron\Yellow\Brown{colored text on colored background}. Usage: `\coloron{}` -The `\coloron` can be defined as follows: +The `\coloron` macro can be defined as follows: \begtt \def\coloron#1#2#3{% - \setbox0=\hbox{{#2#3}}% + \setbox0=\hbox{#2#3}% \leavevmode \rlap{#1\strut \vrule width\wd0}\box0 } -\coloron\Yellow\Brown{The brown text on the yellow background} +\coloron\Yellow\Brown{Brown text on yellow background} \endtt \secc Images diff --git a/web/optex-tricks.html b/web/optex-tricks.html index e7e14be9..cdd3b5cc 100644 --- a/web/optex-tricks.html +++ b/web/optex-tricks.html @@ -62,7 +62,6 @@

OpTeX - tips, tricks, howto

  • Lists @@ -173,6 +173,7 @@

    Contents

  • Sections @@ -440,11 +441,206 @@

    Marking parts of text

  • Background colored (splitabble to more lines) text by OPmac trick 0085 can be printed. Use \coltext\ColorA\ColorB{text}. +
  • See also the following trick (OpTeX trick 0064) + for information about achieving font effects with LuaTeX attributes.

    (0044) -- P. O. 2021-02-08


    +

    Font effects using attributes

    + +

    Setting of color is based on attributes in OpTeX v1.04+. We can use similar +mechanism to achieve other font/rule effects, with different PDF graphics +operators. For example we can achieve transparency or font outlines this +way:

    + +
    +Normal, {\transparency{.5} half transparent,
    +\Red{\transparency{.2}red and more transparent,} back to half,} back to normal.
    +
    +Normal, {\outlinefont{.15}outlined}, {\outlinefont{.3}more outlined}.
    +
    + +

    which produces:

    + +Text with transparency and outlines + +

    To achieve these effects we must first introduce a Lua mechanism:

    + +
    +\directlua{
    +local node_id  = node.id
    +local glyph_id = node_id("glyph")
    +local rule_id  = node_id("rule")
    +local glue_id  = node_id("glue")
    +local hlist_id = node_id("hlist")
    +local vlist_id = node_id("vlist")
    +local disc_id  = node_id("disc")
    +
    +local direct       = node.direct
    +local todirect     = direct.todirect
    +local tonode       = direct.tonode
    +local getfield     = direct.getfield
    +local setfield     = direct.setfield
    +local getlist      = direct.getlist
    +local setlist      = direct.setlist
    +local getleader    = direct.getleader
    +local getattribute = direct.get_attribute
    +local insertbefore = direct.insert_before
    +local copy         = direct.copy
    +local traverse     = direct.traverse
    +
    +local token_getmacro = token.get_macro
    +
    +local pdfliteral = optex.directpdfliteral
    +
    +function register_pre_shipout_injector(name, attribute_name, namespace, default)
    +    local current
    +    local default = default or 0
    +    local attribute = assert(registernumber(attribute_name))
    +    local function injector(head)
    +        for n, id, subtype in traverse(head) do
    +            if id == hlist_id or id == vlist_id then
    +                % nested list, just recurse
    +                setlist(n, injector(getlist(n)))
    +            elseif id == disc_id then
    +                % only replace part is interesting at this point
    +                local replace = getfield(n, "replace")
    +                if replace then
    +                    setfield(n, "replace", injector(replace))
    +                end
    +            elseif id == glyph_id or id == rule_id
    +                    or (id == glue_id and getleader(n)) then
    +                local new = getattribute(n, attribute) or 0
    +                if new ~= current then
    +                    local literal = token_getmacro(namespace..new)
    +                    head = insertbefore(head, n, pdfliteral(literal))
    +                    current = new
    +                end
    +            end
    +        end
    +        return head
    +    end
    +
    +    callback.add_to_callback("pre_shipout_filter", function(list)
    +        current = default
    +        return tonode(injector(todirect(list)))
    +    end, name)
    +end
    +}
    +
    + +

    Then we can define the font effects as we please:

    + +

    Transparency:

    + +
    +\newattribute \transpattr
    +\newcount \transpcnt \transpcnt=1 % allocations start at 1
    +\def\transparency#1{\inittransparency \transpattr=
    +   \ifcsname transp::#1\endcsname \lastnamedcs\relax \else
    +      \transpcnt
    +      \sxdef{transp::#1}{\the\transpcnt}%
    +      \sxdef{transp:\the\transpcnt}{/Tr\the\transpcnt\space gs}%
    +      \addextgstate{/Tr\the\transpcnt <</ca #1 /CA #1>>}%
    +      \incr \transpcnt
    +   \fi
    +}
    +\addto\_resetcolor{\transpattr=-"7FFFFFFF }
    +% Transparency of "1" is the default
    +\sdef{transp::1}{0}
    +\sdef{transp:0}{/Tr0 gs}
    +\def\inittransparency{%
    +   \addextgstate{/Tr0 <</ca 1 /CA 1>>}%
    +   \glet\inittransparency=\relax
    +}
    +\directlua{
    +register_pre_shipout_injector("transp", "transpattr", "transp:")
    +}
    +
    + +

    Font outline:

    + +
    +\newattribute \fntoutattr
    +\newcount \fntoutcnt \fntoutcnt=1 % allocations start at 1
    +\def\outlinefont#1{\fntoutattr=
    +   \ifcsname fntout::#1\endcsname \lastnamedcs\relax \else
    +      \fntoutcnt
    +      \sxdef{fntout::#1}{\the\fntoutcnt}%
    +      \sxdef{fntout:\the\fntoutcnt}{#1 w 1 Tr}%
    +      \incr \fntoutcnt
    +   \fi
    +}
    +\addto\_resetcolor{\fntoutattr=-"7FFFFFFF }
    +\sdef{fntout:0}{0 w 0 Tr}
    +\directlua{
    +register_pre_shipout_injector("fntout", "fntoutattr", "fntout:")
    +}
    +
    + +

    The core idea is the same as with colors (see the OpTeX documentation) – +we use attributes to mark typesetting material and just before shipout we +postprocess, injecting PDF literals where necessary. But unlike with colors, +which are specialized (e.g. stroke vs non-stroke), we define a generic Lua +function generator that can inject any PDF literals we want.

    + +

    Each distinct transparency / font outline width maps to a single number that +is used as the attribute value. Similarly, the inverse mapping maps the +attribute values to PDF literals. Special attribute value of “0” designates the +default effect – this is e.g. what returns the graphics state to normal +after TeX group ends. Other effects are allocated starting at number 1.

    + +

    The TeX user interface consists of \outlinefont and +\transparency, they essentially just set the attribute to the right +attribute value (allocating new one if necessary). \transparency +has a little more work to do, because it has to initialize a new graphics state +for each transparency – even the default one (this happens the first time +transparency is actually used, apart from other things it allows detection of +collision with TikZ).

    + +

    We also add to \_resetcolor which is what the default OpTeX +output routine uses to achieve clean slate for headers / footers.

    + +

    Name of the effect, the attribute name and the macro prefix for PDF literals +are passed to the Lua side in order to generate the associated PDF literal +injector.

    + +

    The literals for transparency activate the prepared graphics states (which +are named “/Tr⟨number”. Literals for font outlines activate +the outline style (“1 Tr”) and set the outline width with +“number⟩ w”.

    + +

    If you want to use other default value of an effect, you need to change the +mapping of attribute value 0 and also need to force inject a PDF +literal on the start of each page (this is normally not done, because it would +be wasteful). The first can be easily done by using a couple of +\sdefs. The second can be achieved by passing an invalid attribute +value (e.g. -1) as the initial (which forces the initial injection +no matter what).

    + +

    For example to achieve half transparency for whole document, we change a few +definitions:

    + +
    +% Transparency of ".5" is the default
    +\sdef{transp::.5}{0}
    +\def\inittransparency{%
    +   \addextgstate{/Tr0 <</ca .5 /CA .5>>}%
    +   \glet\inittransparency=\relax
    +}
    +\directlua{
    +% -1 forces initial setting on each page
    +register_pre_shipout_injector("transp", "transpattr", "transp:", -1)
    +}
    +
    + +

    Sadly outline fonts don't work with colors.

    + +

    (0064) -- M. V. 2021-07-14

    +
    +

    Lists

    List items multi-numbered at arbitrary level

    @@ -1259,9 +1455,9 @@

    Colored cells in the table

    for the background of the cell. For example:
    -   ... & \Blue Text &        ... % blue background and black text 
    -   ... & \Green \Red  Text & ... % green background and red text 
    -   ... & \relax \Blue Text & ... % blue text and default background 
    +   ... & \Blue Text &        ... % blue background and black text
    +   ... & \Green \Red  Text & ... % green background and red text
    +   ... & \relax \Blue Text & ... % blue text and default background
     

    The color of the background will stretch as spaces in the common cells in @@ -1281,8 +1477,8 @@

    Colored cells in the table

    \tabstrut={\lower4pt\vbox to15pt{}} % lines dimensions \tabskip=2pt \everycr={\noalign{\kern2pt}} % spaces between cells \let\cellcolor=\Yellow % default background color - } -\table{clr}{first item & second & \Blue \White third item \cr + } +\table{clr}{first item & second & \Blue \White third item \cr next long item & \Red X & \Green YES } @@ -1290,55 +1486,55 @@

    Colored cells in the table

    Table example -

    Implementation: +

    Implementation:

    -\def\tabdC{\futurelet\next\setcellcolor##\end\hfil\hfil} 
    -\def\tabdL{\futurelet\next\setcellcolor##\end\relax\hfil} 
    +\def\tabdC{\futurelet\next\setcellcolor##\end\hfil\hfil}
    +\def\tabdL{\futurelet\next\setcellcolor##\end\relax\hfil}
     \def\tabdR{\futurelet\next\setcellcolor##\end\hfil\relax}
     \def\tabdP#1{\futurelet\next\setcellcolor##\end\vtop
         {\hsize=#1\relax \baselineskip=\normalbaselineskip
          \lineskiplimit=0pt \noindent\tmp\_unsskip\lower\dp\_tstrutbox\hbox{}}}
     \def\colortab{\tablinespace=0pt \let\_paramtabdeclarep=\tabdP
    -   \let\_tabdeclarec=\tabdC \let\_tabdeclarel=\tabdL \_let\_tabdeclarer=\tabdL}
    +   \let\_tabdeclarec=\tabdC \let\_tabdeclarel=\tabdL \_let\_tabdeclarer=\tabdR}
     \def\setcellcolor{%
    -   \ifx\next\_setcolor \expandafter\setcellcolorC\else \expandafter\setcellcolorD\fi} 
    -\def\setcellcolorC\_setcolor#1#2\end#3#4{% 
    -   \ifx#3\vtop \def\tmp{#2}%
    -      \setbox0=\hbox{\the\tabiteml\vtop{\localcolor#4}\the\tabitemr}%
    -      \the\tabstrut {\localcolor\_setcolor{#1}\vrule width\wd0}\kern-\wd0 \box0
    +   \ifx\next\_setcolor \expandafter\setcellcolorC\else \expandafter\setcellcolorD\fi}
    +\def\setcellcolorC\_setcolor#1#2#3#4\end#5#6{%
    +   \ifx#5\vtop \def\tmp{#4}%
    +      \setbox0=\hbox{\the\tabiteml\vtop{\localcolor#6}\the\tabitemr}%
    +      \the\tabstrut {\localcolor\_setcolor{#1}{#2}{#3}\vrule width\wd0}\kern-\wd0 \box0
        \else
    -      \setbox0=\hbox{{\the\tabiteml\localcolor#2\_unsskip\the\tabitemr}}% 
    -      {\localcolor\_setcolor{#1}%
    -       \the\tabstrut\leaders\vrule\hskip\wd0 \ifx#3\hfil plus1fil\fi}% 
    -      \kern-\wd0 \box0 
    -      \ifx#4\hfil 
    -      {\kern-.2pt \localcolor\_setcolor{#1}\leaders\vrule\hskip.2pt plus1fil}\fi
    -   \fi 
    -} 
    -\def\setcellcolorD{\ifx\cellcolor\undefined \let\next=\setcellcolorN 
    -   \else \def\next{\_ea\_ea\_ea\setcellcolorC\cellcolor}% 
    -   \fi \next 
    -} 
    -\def\setcellcolorN#1\end#2#3{#2\tabiteml{\localcolor#1\unskip}\tabitemr#3}
    -
    -\def\multispanc#1#2#3{\multispan{#1}% 
    -   \_ea\_ea\_ea\cellcolexp#2#3\end\hfil\hfil\ignorespaces} 
    -\def\cellcolexp{\futurelet\next\setcellcolor} 
    +      \setbox0=\hbox{\the\tabiteml\localcolor#4\_unsskip\the\tabitemr}%
    +      {\localcolor\_setcolor{#1}{#2}{#3}%
    +       \the\tabstrut\leaders\vrule\hskip\wd0 \ifx#5\hfil plus1fil\fi}%
    +      \kern-\wd0 \box0
    +      \ifx#6\hfil
    +      {\kern-.3bp \localcolor\_setcolor{#1}{#2}{#3}\leaders\vrule\hskip.3bp plus1fil}\fi
    +   \fi
    +}
    +\def\setcellcolorD{\ifx\cellcolor\undefined \let\next=\setcellcolorN
    +   \else \def\next{\_ea\_ea\_ea\setcellcolorC\cellcolor}%
    +   \fi \next
    +}
    +\def\setcellcolorN#1\end#2#3{#2\the\tabiteml{\localcolor#1\unskip}\the\tabitemr#3}
    +
    +\def\multispanc#1#2#3{\multispan{#1}%
    +   \_ea\_ea\_ea\cellcolexp#2#3\end\hfil\hfil\ignorespaces}
    +\def\cellcolexp{\futurelet\next\setcellcolor}
     

    The \setcellcolor macro scans the first token from the cell data. The macro works in declaration part of the \halign, so the first token is scanned as the first unexpandable token after expansion. This token is \_setcolor when color macro is presented here. If this is true then the -\setcellcolorC macro is executed, #1 is the \_setcolor parameter -#2 is the text of the item, #3 and #4 include the -message about the align style. This macro measures the cell text in the box0 -and does the colored backround by \leaders. If the first token isn't \global -then the macro \setcellcolorD decides if the \cellcolor is defined. If it is -true then \setcellcolorC is execuded (with patrially expanded \cellcolor as -parameter). Else the \setcellcolorN is executed. This macro prints no -background. +\setcellcolorC macro is executed, #1, #2 and #3 are the \_setcolor parameters +#4 is the text of the item, #5 and #6 decide the +align style. This macro measures the cell text in the box0 +and does the colored background using \leaders. If the first token isn't \_setcolor +then the macro \setcellcolorD decides if \cellcolor is defined. If it is +true then \setcellcolorC is execuded (with partially expanded \cellcolor as +parameter). Else \setcellcolorN is executed. This macro doesn't print +any background.

    The \multispanc Num \Color {text} can be used for multispan cells, see OPmac trick 103. @@ -2776,6 +2972,49 @@

    Printing time, date

    (0055) -- P. O., 2021-04-06


    +

    Listings of all nodes in selected pages

    + +

    We create macro "\showpglists list-of-pages" which prints the list of all +nodes in selected pages to the log and to the terminal for debugging +purposes. For example + +

    +\showpglists 3,4,7
    +
    + +

    prints all nodes of pages 3, 4, and 7. + +

    This OpTeX trick is a simple example of usage of the pre_shipout_filter callback +declared in OpTeX from version 1.04. The package nodetree is required for the +listings of nodes. The implemetation: + +

    +\def\addshowpglists{\directlua{
    +   local nodetree = require("nodetree")
    +   callback.add_to_callback("pre_shipout_filter", function(head)
    +         nodetree.print(head)
    +         return head
    +      end, "showpglists") }}
    +\gdef\removeshowpglists{\directlua{
    +   callback.remove_from_callback("pre_shipout_filter", "showpglists") }}
    +
    +\def\showpglists #1 {\addto\showpgs{#1,}}
    +\def\showpgs{,}
    +
    +\addto\_begoutput{%
    +   \ea\isinlist\ea\showpgs\ea{\ea,\the\pageno,}\iftrue
    +      \addshowpglists
    +      \addto\_endoutput{\removeshowpglists}%
    +   \fi
    +}
    +
    + +

    The callback is registered in \_begoutput only if \the\pageno is in the page +list \showpgs. Then it used in \_preshipout and unregistered in \_endoutput. + +

    (0063) -- P. O., 2021-07-03

    +


    +

    Sections

    New level of sections