# -*- mode: Perl -*-
# /=====================================================================\ #
# | enumitem | #
# | Implementation for LaTeXML | #
# |=====================================================================| #
# | Part of LaTeXML: | #
# | Public domain software, produced as part of work done by the | #
# | United States Government & not subject to copyright in the US. | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
# | http://dlmf.nist.gov/LaTeXML/ (o o) | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# TODO Deal with Scads of options!
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Package Options:
DeclareOption('shortlabels', sub { AssignValue('enumitem@shortlabels' => 1); return; });
DeclareOption('inline', sub { AssignValue('enumitem@inline' => 1); return; });
DeclareOption('loadonly', sub { AssignValue('enumitem@loadonly' => 1); return; });
# sizes requires a peculiar dimension parser...
# ignoredisplayed
# series=override
# ignoredisplayed, includedisplayed; affects redefinition of trivlist, which we haven't done.
ProcessOptions();
#======================================================================
# Labelling:
DefKeyVal('enumitem', 'label', 'UndigestedKey');
DefKeyVal('enumitem', 'label*', 'UndigestedKey');
DefKeyVal('enumitem', 'ref', 'UndigestedKey');
DefKeyVal('enumitem', 'font', 'UndigestedKey');
DefKeyVal('enumitem', 'format', 'UndigestedKey');
# Numbering
DefKeyVal('enumitem', 'start', 'Number');
DefKeyVal('enumitem', 'series', 'UndigestedKey');
DefKeyVal('enumitem', 'resume', '', 'noseries');
DefKeyVal('enumitem', 'resume*', '', 'noseries'); # NOTE: not complete; we don't reuse options
# Description styles
# style=(standard|unboxed|nextline|sameline|multiline)
DefKeyVal('enumitem', 'style', 'UndigestedKey'); # Not done
# Inline lists
DefKeyVal('enumitem', 'itemjoin', 'UndigestedKey'); # Not done
DefKeyVal('enumitem', 'itemjoin*', 'UndigestedKey'); # Not done
DefKeyVal('enumitem', 'afterlabel', 'UndigestedKey'); # Not done [only applies to inline!]
DefKeyVal('enumitem', 'mode', 'UndigestedKey'); # =(boxed|unboxed) ignored
# IGNORED: Alignment, Positioning, penalties
DefKeyVal('enumitem', 'align', 'UndigestedKey'); # =(left|right|parleft|SetLabelAlign-name)
DefKeyVal('enumitem', 'labelindent', 'Dimension');
DefKeyVal('enumitem', 'left', 'Dimension');
DefKeyVal('enumitem', 'leftmargin', 'UndigestedKey');
DefKeyVal('enumitem', 'itemindent', 'Dimension');
DefKeyVal('enumitem', 'labelsep', 'Dimension');
DefKeyVal('enumitem', 'labelwidth', 'Dimension');
DefKeyVal('enumitem', 'labelindent', 'Dimension');
DefKeyVal('enumitem', 'widest', 'UndigestedKey');
DefKeyVal('enumitem', 'beginpenalty', 'Number');
DefKeyVal('enumitem', 'midpenalty', 'Number');
DefKeyVal('enumitem', 'endpenalty', 'Number');
DefKeyVal('enumitem', 'noitemsep', '', 'true');
DefKeyVal('enumitem', 'nolistsep', '', 'true');
# Code
# before=code, after=code
DefKeyVal('enumitem', 'before', 'UndigestedKey'); # TODO ?
DefKeyVal('enumitem', 'after', 'UndigestedKey'); # TODO ?
# \EnumitemId
# \SetEnumerateShortLabel{key}{replacement}
# Extends LaTeX's beginItemize, with modifications...
sub beginEnumItemize {
my ($type, $counter, $keys) = @_;
$counter = '@item' unless $counter;
my $level = (LookupValue($counter . 'level') || 0) + 1;
my $hash = merged_enumitem_keyvals($type, $level, $keys);
my $postfix = ToString(Tokens(roman($level)));
my $usecounter = $counter . $postfix;
# Deal with label formatting [Seems this could be cleaned up & standardized a bit more]
my ($firstkey, $firstvalue) = $keys && $keys->getPairs;
if ($firstkey && !$firstvalue # 1st key has no value
&& LookupValue('enumitem@shortlabels') # and shortlabels enabled
&& !LookupDefinition(T_CS('\KV@enumitem@' . $firstkey))) { # AND unknown!!!
setEnumerationStyle(TokenizeInternal($firstkey), $level); } # alas already turned to string
if (my $label = $$hash{label} || $$hash{'label*'}) {
my $llabel = replace_star($label, T_OTHER($usecounter));
if ($$hash{'label*'} && ($level > 1)) {
$llabel = Tokens(T_CS('\label' . $counter . ToString(Tokens(roman($level - 1)))), $llabel); }
DefMacroI('\the' . $usecounter, undef, $llabel);
DefMacroI('\label' . $usecounter, undef, $llabel);
DefMacroI('\fnum@' . $usecounter, undef,
Tokens(T_BEGIN, T_CS('\makelabel'), T_BEGIN, T_CS('\label' . $usecounter), T_END, T_END)); }
if (my $label = $$hash{ref}) {
my $llabel = replace_star($label, T_OTHER($usecounter));
# This is a Hotfix.
# DG: what is the correct general implementation here?
# if $llabel contains "\the$usecounter", expand it, to avoid the inf. loop.
if (ToString($llabel) =~ "\Q\\the$usecounter\E") {
$llabel = Expand($llabel); }
DefMacroI('\the' . $usecounter, undef, $llabel); }
if (my $font = $$hash{font} || $$hash{format}) {
DefMacroI('\fnum@font@' . $usecounter, undef, $font); }
return beginItemize($type, $counter, %$hash); }
sub replace_star {
my ($tokens, $replacement) = @_;
my @t = ();
foreach my $t ($tokens->unlist) {
push(@t, ($t->equals(T_OTHER('*')) ? $replacement : $t)); }
return Tokens(@t); }
sub endEnumItemize {
my ($whatsit) = @_;
if (my $series = $whatsit->getProperty('series')) {
if (my $ctr = $whatsit->getProperty('counter')) {
AssignValue('enumitem_series_' . $series . '_last' => CounterValue($ctr), 'global'); } }
return; }
DefMacro('\restartlist{}', sub {
my ($gullet, $listname) = @_;
$listname = ToString($listname);
my $counter = ($listname eq 'enumerate' ? 'enum'
: ($listname eq 'itemize' ? '@item'
: ($listname eq 'description' ? '@desc'
: $listname)));
# Assme we reset all levels? Assume no more than 6 levels?
for (my $i = 0 ; $i < 6 ; $i++) {
my $r = ToString(Tokens(roman($i)));
if (LookupDefinition('\c@' . $counter . $r)) {
SetCounter($counter . $r, Number(0)); } }
return; });
#======================================================================
if (!LookupValue('enumitem@loadonly')) {
DefEnvironment('{itemize} OptionalKeyVals:enumitem',
"<ltx:itemize xml:id='#id'>#body</ltx:itemize>",
properties => sub { beginEnumItemize('itemize', '@item', $_[1]); },
beforeDigestEnd => sub { Digest('\par'); },
afterDigestBody => sub { endEnumItemize($_[1]); },
locked => 1, mode => 'text');
DefEnvironment('{enumerate} OptionalKeyVals:enumitem',
"<ltx:enumerate xml:id='#id'>#body</ltx:enumerate>",
properties => sub { beginEnumItemize('enumerate', 'enum', $_[1]); },
beforeDigestEnd => sub { Digest('\par'); },
afterDigestBody => sub { endEnumItemize($_[1]); },
locked => 1, mode => 'text');
DefEnvironment('{description} OptionalKeyVals:enumitem',
"<ltx:description xml:id='#id'>#body</ltx:description>",
beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); },
properties => sub { beginEnumItemize('description', '@desc', $_[1]); },
beforeDigestEnd => sub { Digest('\par'); },
afterDigestBody => sub { endEnumItemize($_[1]); },
locked => 1, mode => 'text');
}
if (LookupValue('enumitem@inline')) {
DefEnvironment('{itemize*} OptionalKeyVals:enumitem',
"<ltx:inline-itemize xml:id='#id'>#body</ltx:inline-itemize>",
properties => sub { beginEnumItemize('inline@itemize', '@item', $_[1]); },
afterDigestBody => sub { endEnumItemize($_[1]); },);
DefEnvironment('{enumerate*} OptionalKeyVals:enumitem',
"<ltx:inline-enumerate xml:id='#id'>#body</ltx:inline-enumerate>",
properties => sub { beginEnumItemize('inline@enumerate', 'enum', $_[1]); },
afterDigestBody => sub { endEnumItemize($_[1]); },);
DefEnvironment('{description*} OptionalKeyVals:enumitem',
"<ltx:inline-description xml:id='#id'>#body</ltx:inline-description>",
properties => sub { beginEnumItemize('inline@description', '@desc', $_[1]); },
afterDigestBody => sub { endEnumItemize($_[1]); },);
}
DefPrimitive('\newlist{}{}{}', sub {
my ($stomach, $listname, $listtype, $maxdepth) = @_;
$listname = ToString($listname);
$listtype = ToString($listtype);
$maxdepth = ToString($maxdepth); # Or should it be a Number?
my ($basetype, $inline) = ($listtype, '');
if ($listtype =~ /^(.*)\*$/) { $basetype = $1; $inline = 1; }
my $elementname = ($inline ? 'inline-' . $basetype : $basetype);
# Officially only supposed to define counters for enumerate, but we need them (no prefix?)
for (my $d = 1 ; $d <= $maxdepth ; $d++) {
NewCounter($listname . ToString(Tokens(roman($d)))); }
# Need to hook-up to \itemize@item, inline, etc
Let(T_CS('\\' . $listname . '@item'),
T_CS('\\' . ($inline ? 'inline@' : '') . $basetype . '@item'),
'global');
DefEnvironmentI($listname, "OptionalKeyVals:enumitem",
"<ltx:$elementname xml:id='#id'>#body</ltx:$elementname>",
properties => sub { beginEnumItemize($listname, $listname, $_[1]); },
beforeDigestEnd => sub { Digest('\par'); },
afterDigestBody => sub { endEnumItemize($_[1]); },
locked => 1, mode => 'text');
return; });
Let('\renewlist', '\newlist');
# Convoluted: optionally applies to all lists, all levels of one list or a single level
DefPrimitive('\setlist Optional RequiredKeyVals:enumitem', sub {
my ($stomach, $names, $kv) = @_;
if (!$names) { # No names? applies to ALL lists
store_enumitem_defaults('enumitem_defaults', $kv); }
else {
my ($name, @levels) = split(/\s*,\s*/, ToString($names));
if (!@levels) { # No levels? applies to ALL levels of this list
store_enumitem_defaults('enumitem_' . $name . '_defaults', $kv); }
else {
foreach my $level (@levels) {
store_enumitem_defaults('enumitem_' . $name . $level . '_defaults', $kv); } } }
return; });
# These are obsolete
DefMacro('\setitemize Optional {}', '\setlist[itemize,#1]{#2}');
DefMacro('\setenumerate Optional {}', '\setlist[enumerate,#1]{#2}');
DefMacro('\setdescription Optional {}', '\setlist[description,#1]{#2}');
sub store_enumitem_defaults {
my ($name, $kv) = @_;
my $kvdef = LookupValue($name);
if (!$kvdef) {
$kvdef = LaTeXML::Core::KeyVals->new('KV', 'enumitem');
AssignValue($name, $kvdef, 'global'); }
my $hash = $kv->getKeyVals;
foreach my $key (keys %$hash) {
$kvdef->setValue($key, $$hash{$key}); }
return; }
sub merged_enumitem_keyvals {
my ($name, $level, $argkv) = @_;
my @defaults = map { LookupValue($_); }
'enumitem_defaults', 'enumitem_' . $name . '_defaults', 'enumitem_' . $name . $level . '_defaults';
my $hash = {};
foreach my $kv (@defaults, $argkv) {
if ($kv) {
my $defhash = $kv->getKeyVals;
foreach my $key (keys %$defhash) {
$$hash{$key} = $$defhash{$key}; } } }
return $hash; }
#======================================================================
# Not yet handled bits
DefMacro('\SetLabelAlign{}{}', '');
DefMacro('\EnumitemId', '');
DefMacro('\SetEnumitemKey{}{}', '');
DefMacro('\SetEnumerateShortLabel{}{}', '');
DefMacro('\SetEnumitemValue{}{}{}', '');
DefMacro('\SetEnumitemSize{}{}', '');
DefMacro('\AddEnumerateCounter{}{}{}', '');
#======================================================================
1;