use std::cell::RefCell;
use std::rc::Rc;
use super::deps::{self, Deps};
use super::xcolormix;
use super::{Following, Native};
use crate::Engine;
use crate::cmd::Cmd;
use crate::eqtb::{Loc, Meaning, Val};
use crate::hash::CsId;
use crate::primitives_gen::assign_int as ip;
use crate::token::{Macro, Token, TokenList};
pub(super) const COL_RLET: Native = Native {
name: "XC@col@rlet",
fingerprints: &[(120, 0xa186_489d_731c_f3fe)],
executes: true,
run,
};
const NAMES: &[&str] = &[
"XC@edef",
"XC@sdef",
"XC@info",
"XC@logdef",
"XC@c@l@rlet",
"@ifundefinedcolor",
"@ifundefined",
"@ifundefin@d@i",
"XC@let@cc",
"XC@let@Nc",
"xglobal@stop",
"@onelevel@sanitize",
"strip@prefix",
"aftergroupdef",
"in@",
"@car",
"@backslashchar",
"@empty",
"@secondoffive",
"XC@@tstfalse",
"XC@@tsttrue",
"c@lor@error",
"@ne",
"@firstoftwo",
"@secondoftwo",
"begingroup",
"endgroup",
"edef",
"def",
"let",
"ifx",
"ifnum",
"in@true",
"in@false",
"if",
"else",
"fi",
"relax",
"expandafter",
"noexpand",
"csname",
"endcsname",
"ifcsname",
"lastnamedcs",
"string",
"meaning",
"catcode",
"global",
"extractcolorspec",
];
const KNOWN: &[u64] = &[0x0ea0_408a_a021_2562];
thread_local! {
static DEPS: RefCell<Deps> = RefCell::new(Deps::new("xcolor", NAMES, KNOWN));
}
fn why(reason: &str) -> bool {
decline::<()>(reason).is_some()
}
fn decline<T>(reason: &str) -> Option<T> {
if super::debug() {
eprintln!("[native] XC@col@rlet declines ({reason})");
}
None
}
struct Plan {
name: Vec<u8>,
action: Action,
global: bool,
xglobal: CsId,
empty: Meaning,
}
enum Action {
Let(Meaning, CsId),
Define(Vec<Token>, Vec<u8>, Vec<Token>),
}
fn run(e: &mut Engine, args: &[TokenList]) -> bool {
if !args[2].is_empty() {
return why("type or model");
}
if !args[0].is_empty()
&& deps::edef(e, &args[0])
.and_then(|t| deps::sanitized(&t))
.as_deref()
!= Some(b"named")
{
return why("type or model");
}
match plan(e, &args[0], &args[1], &args[3]) {
Some(p) => {
commit(e, p);
true
}
None => false,
}
}
fn plan(e: &Engine, cls: &[Token], name: &[Token], color: &[Token]) -> Option<Plan> {
if e.eqtb.int(ip::GLOBALDEFS) != 0 || e.after_token.0 != 0 {
return None;
}
if !DEPS.with(|d| d.borrow_mut().check(e)) {
return decline("dependencies");
}
let lookup = |n: &[u8]| e.hash.lookup(n);
let (Some(tracing), Some(globalcolors), Some(xglobal), Some(empty), Some(global)) = (
lookup(b"XC@tracing"),
lookup(b"ifglobalcolors"),
lookup(b"xglobal@"),
lookup(b"@empty"),
lookup(b"global"),
) else {
return None;
};
match deps::plain_body(e, tracing).and_then(deps::sanitized) {
Some(t) if t == b"0" || t == b"1" => {}
_ => return decline("tracing"),
}
let gm = e.eqtb.meaning(globalcolors);
let global_colors = match (gm.cmd, gm.chr) {
(Cmd::IfTest, 14) => true,
(Cmd::IfTest, 15) => false,
_ => return decline("ifglobalcolors"),
};
let xm = e.eqtb.meaning(xglobal);
let xglobal_is_global = if xm.same_as(e.eqtb.meaning(empty)) {
false
} else if xm.same_as(e.eqtb.meaning(global)) && xm.cmd == Cmd::Prefix && xm.chr == 4 {
true
} else {
return decline("xglobal@");
};
let text = |t: &[Token]| deps::edef(e, t).and_then(|t| deps::sanitized(&t));
let (Some(name), Some(color)) = (text(name), text(color)) else {
return decline("expansion");
};
let mut nname = b"\\color@".to_vec();
nname.extend_from_slice(&name);
let global = global_colors || xglobal_is_global;
let xglobal_plan = |action| {
Some(Plan {
name: nname.clone(),
action,
global,
xglobal,
empty: e.eqtb.meaning(empty).clone(),
})
};
let cls_text = deps::edef(e, cls)?;
if color.is_empty()
|| color[0] == b'-'
|| color.iter().any(|c| b">:!/".contains(c))
|| color == b"."
|| color[0] == b' '
{
if !mix_ok(e) {
return decline("colour expression: settings");
}
let Some(spec) = xcolormix::split(e, &color) else {
if super::debug() {
eprintln!(
"[native] XC@col@rlet: [{}] [{}]",
String::from_utf8_lossy(&name),
String::from_utf8_lossy(&color)
);
}
return decline("colour expression");
};
let body = xcolormix::definition(e, &spec)?;
return xglobal_plan(Action::Define(body, name, cls_text));
}
let mut cname = b"\\color@".to_vec();
cname.extend_from_slice(&color);
let Some(ccs) = lookup(&cname) else {
return decline("undefined colour");
};
let relax = lookup(b"relax").map(|r| e.eqtb.meaning(r).clone());
let cm = e.eqtb.meaning(ccs).clone();
if relax.is_none_or(|r| deps::absent(&cm, &r)) {
return decline("undefined colour");
}
if !cls_text.is_empty() {
let [c, ..] = xcolormix::xcolor_args(e, ccs)?;
if !c.is_empty() || !mix_ok(e) {
return decline("named colour of a class");
}
let spec = xcolormix::named_spec(e, &color)?;
let body = xcolormix::definition(e, &spec)?;
return xglobal_plan(Action::Define(body, name, cls_text));
}
xglobal_plan(Action::Let(cm, ccs))
}
const MIX_NAMES: &[&str] = &[
"XC@definecolor",
"XC@definec@lor",
"XC@getmodclr",
"XC@mdef",
"XC@getmod",
"XC@g@tm@d",
"XC@getm@d",
"XC@getclr",
"XC@clean",
"XC@cle@n",
"XC@coremodel",
"XC@ifxcase",
"XC@calc@",
"XC@calcN",
"XC@calcR",
"XC@c@lcN",
"XC@c@lcR",
"XC@c@@cR",
"color@rgb",
"c@lor@@rgb",
"c@lor@arg",
"color@gray",
"color@cmyk",
"c@lor@@cmyk",
"@namedef",
"@nameuse",
"extractcolorspec",
"extractcolorspecs",
"XC@split",
"XC@split@i",
"XC@split@ii",
"XC@split@vi",
"XC@split@vii",
"XC@split@viii",
"XC@func",
"@xcolor@",
"@xcolor@@",
"XC@inflate",
"XC@mix",
"XC@mix@",
"XC@vec",
"XC@vec@@",
"rrshift",
"rshift",
"rshift@",
"rshift@@",
"strip@pt",
"rem@pt",
"convertcolorspec",
"XC@cnv@gray",
"XC@clr@rgb@white",
"XC@clr@gray@white",
"XC@clr@cmyk@white",
"@ifxempty",
"@@ifxempty",
"remove@to@nnil",
"XC@tgt@mod",
"XC@mod@named",
"XC@mod@ps",
"XC@mod@ignore",
"XC@mod@rgb",
"XC@mod@gray",
"XC@mod@cmyk",
"@ifundefinedmodel",
"color@ignore",
"p@",
"z@",
"maxdimen",
"dimen@",
"toks@",
"@tempdima",
"@tempdimb",
"@undeclaredcolor",
"XC@undeclaredcolor",
"XC@declaredcolor",
"colornameprefix",
"define@color@named",
"c@lor@@named",
"color@named",
"@thirdoffive",
];
const MIX_KNOWN: &[u64] = &[0x7c59_a498_4e98_8ab1];
thread_local! {
static MIX_DEPS: RefCell<Deps> = RefCell::new(Deps::new("xcolor mix", MIX_NAMES, MIX_KNOWN));
}
fn mix_ok(e: &Engine) -> bool {
if !MIX_DEPS.with(|d| d.borrow_mut().check(e)) {
return false;
}
let lookup = |n: &[u8]| e.hash.lookup(n);
let is_if = |n: &[u8], chr: u32| {
lookup(n).is_some_and(|id| {
let m = e.eqtb.meaning(id);
m.cmd == Cmd::IfTest && m.chr == chr && m.mac.is_none()
})
};
let p_at = lookup(b"p@").map(|id| e.eqtb.meaning(id));
let p_ok = p_at.is_some_and(|m| {
m.cmd == Cmd::AssignDimen
&& m.chr >= crate::scan::REG_OFFSET
&& e.eqtb.dimen[(m.chr - crate::scan::REG_OFFSET) as usize].val == 65536
});
let xcp = lookup(b"XC@@xcp@")
.and_then(|id| deps::plain_body(e, id))
.and_then(deps::sanitized);
p_ok && is_if(b"ifconvertcolorsD", 15)
&& is_if(b"ifXC@@getmod", 14)
&& xcp.as_deref() == Some(b"-1")
}
fn commit(e: &mut Engine, p: Plan) {
let ncs = e.hash.lookup_or_insert(&p.name);
match p.action {
Action::Let(m, ccs) => {
e.define(p.global, Loc::Cs(ncs), Val::Meaning(m));
e.last_cs_name = ccs;
}
Action::Define(body, name, cls) => {
let m = Meaning {
cmd: Cmd::Call,
chr: 0,
mac: Some(Rc::new(Macro::new(false, Rc::from(body)))),
};
e.define(p.global, Loc::Cs(ncs), Val::Meaning(m));
if let (Some(def), Some(prefix)) = (
e.hash.lookup(b"XC@definec@lor"),
e.hash.lookup(b"colornameprefix"),
) {
let other = |c: u8| Token::other(u32::from(c));
let mut a = vec![Token::cs(def), other(b'[')];
a.extend_from_slice(&cls);
a.extend([other(b']'), Token::LEFT_BRACE]);
a.extend(
name.iter()
.map(|&c| if c == b' ' { Token::SPACE } else { other(c) }),
);
a.push(Token::RIGHT_BRACE);
let mut b = a.clone();
b.extend([
other(b'['),
Token::LEFT_BRACE,
Token::cs(prefix),
Token::RIGHT_BRACE,
other(b']'),
]);
let _ = ifnextchar_state(e, Token::LEFT_BRACE, a, b);
}
e.last_cs_name = ncs;
}
}
e.eq_define(Loc::Cs(p.xglobal), Val::Meaning(p.empty));
if super::debug() {
eprintln!("[native] XC@col@rlet native");
}
}
pub(super) const COLORLET: Native = Native {
name: "colorlet",
fingerprints: &[(7, 0xc495_819f_bf62_55be)],
executes: true,
run: run_colorlet,
};
const COLORLET_NAMES: &[&str] = &[
"XC@colorlet",
"XC@col@rlet",
"@testopt",
"kernel@ifnextchar",
"@ifnextchar",
"@ifnch",
"@xifnch",
"@sptoken",
];
const COLORLET_KNOWN: &[u64] = &[0xfd71_5e94_25fd_0a12];
thread_local! {
static COLORLET_DEPS: RefCell<Deps> =
RefCell::new(Deps::new("colorlet", COLORLET_NAMES, COLORLET_KNOWN));
}
fn is_space(t: Token) -> bool {
!t.is_cs() && t.cmd_code() == Cmd::Spacer as u32 && t.chr() == u32::from(b' ')
}
fn is_bracket(e: &Engine, t: Token) -> bool {
let bracket = |cmd: u32, chr: u32| cmd == Cmd::OtherChar as u32 && chr == u32::from(b'[');
if t.is_cs() {
let m = e.eqtb.meaning(t.cs_id());
m.mac.is_none() && bracket(m.cmd as u32, m.chr)
} else {
bracket(t.cmd_code(), t.chr())
}
}
fn read_arg(e: &Engine, f: &mut Following, i: &mut usize) -> Option<Vec<Token>> {
skip_spaces(e, f, i);
let t = f.get(e, *i)?;
if !t.is_cs() && t.cmd_code() == Cmd::Spacer as u32 {
return None; }
let par = e.hash.lookup(b"par").map(Token::cs);
let ok = |t: Token| Some(t) != par && (t.is_cs() || t.cmd_code() != Cmd::CarRet as u32);
if !t.is_cs() && t.cmd_code() == Cmd::LeftBrace as u32 {
let mut out = Vec::new();
let mut depth = 0;
loop {
let k = f.get(e, *i)?;
*i += 1;
if !ok(k) {
return None;
}
if !k.is_cs() {
let c = k.cmd_code();
if c == Cmd::LeftBrace as u32 {
depth += 1;
if depth == 1 {
continue;
}
} else if c == Cmd::RightBrace as u32 {
depth -= 1;
if depth == 0 {
return Some(out);
}
}
}
out.push(k);
}
}
if (!t.is_cs() && t.cmd_code() == Cmd::RightBrace as u32) || !ok(t) {
return None;
}
*i += 1;
Some(vec![t])
}
fn skip_spaces(e: &Engine, f: &mut Following, i: &mut usize) {
while f.get(e, *i).is_some_and(is_space) {
*i += 1;
}
}
fn run_colorlet(e: &mut Engine, _args: &[TokenList]) -> bool {
if !COLORLET_DEPS.with(|d| d.borrow_mut().check(e)) {
return why("colorlet dependencies");
}
let Some(mut f) = e.native_following_subst() else {
return why("arguments not in a token list");
};
let mut i = 0;
skip_spaces(e, &mut f, &mut i);
if f.get(e, i).is_none_or(|t| is_bracket(e, t)) {
return why("type");
}
let Some(name) = read_arg(e, &mut f, &mut i) else {
return why("arguments");
};
skip_spaces(e, &mut f, &mut i);
let Some(next) = f.get(e, i) else {
return why("arguments");
};
if !next.is_cs() && next.cmd_code() == Cmd::Spacer as u32 {
return why("arguments");
}
if is_bracket(e, next) {
return why("model");
}
let Some(color) = read_arg(e, &mut f, &mut i) else {
return why("arguments");
};
if f.count(e, i).is_none() {
return why("arguments end inside a parameter");
}
let Some(p) = plan(e, &[], &name, &color) else {
return false;
};
let lookup = |n: &[u8]| e.hash.lookup(n);
let (Some(ra), Some(rb), Some(rc), Some(rd), Some(lt), Some(rlet)) = (
lookup(b"reserved@a"),
lookup(b"reserved@b"),
lookup(b"reserved@c"),
lookup(b"reserved@d"),
lookup(b"@let@token"),
lookup(b"XC@col@rlet"),
) else {
return false;
};
let next_meaning = if next.is_cs() {
e.eqtb.meaning(next.cs_id()).clone()
} else {
Meaning::prim(next.cmd(), next.chr())
};
e.native_consume(&f, i);
let other = |c: u8| Token::other(u32::from(c));
let mut a = vec![Token::END_MATCH, Token::cs(rlet), other(b'['), other(b']')];
a.push(Token::LEFT_BRACE);
a.extend_from_slice(&name);
a.push(Token::RIGHT_BRACE);
let mut b = a.clone();
b.extend([
other(b'['),
Token::LEFT_BRACE,
Token::RIGHT_BRACE,
other(b']'),
]);
let mac = |t: Vec<Token>| Meaning {
cmd: Cmd::Call,
chr: 0,
mac: Some(Rc::new(Macro::new(false, Rc::from(t)))),
};
let bm = mac(b);
let bracket = Meaning::prim(Cmd::OtherChar, u32::from(b'['));
e.eq_define(Loc::Cs(rd), Val::Meaning(bracket));
e.eq_define(Loc::Cs(ra), Val::Meaning(mac(a)));
e.eq_define(Loc::Cs(rb), Val::Meaning(bm.clone()));
e.eq_define(Loc::Cs(lt), Val::Meaning(next_meaning));
e.eq_define(Loc::Cs(rc), Val::Meaning(bm));
commit(e, p);
true
}
pub(super) const COLOR: Native = Native {
name: "color ",
fingerprints: &[(5, 0xf351_72e5_c3fe_f9be)],
executes: true,
run: run_color,
};
const COLOR_NAMES: &[&str] = &[
"@ifnextchar",
"@ifnch",
"@xifnch",
"@sptoken",
"@declaredcolor",
"XC@declaredc@lor",
"XC@display",
"XC@type",
"XC@typ@",
"xcolor@",
"XC@let@Nc",
"XC@let@cN",
"@gobbletwo",
];
const COLOR_KNOWN: &[u64] = &[0x71be_5271_de16_1bee];
thread_local! {
static COLOR_DEPS: RefCell<Deps> = RefCell::new(Deps::new("color", COLOR_NAMES, COLOR_KNOWN));
}
fn ifnextchar_state(e: &mut Engine, next: Token, a: Vec<Token>, b: Vec<Token>) -> Option<()> {
let lookup = |n: &[u8]| e.hash.lookup(n);
let (ra, rb, rc, rd, lt) = (
lookup(b"reserved@a")?,
lookup(b"reserved@b")?,
lookup(b"reserved@c")?,
lookup(b"reserved@d")?,
lookup(b"@let@token")?,
);
let next_meaning = if next.is_cs() {
e.eqtb.meaning(next.cs_id()).clone()
} else {
Meaning::prim(next.cmd(), next.chr())
};
let mac = |mut t: Vec<Token>| {
t.insert(0, Token::END_MATCH);
Meaning {
cmd: Cmd::Call,
chr: 0,
mac: Some(Rc::new(Macro::new(false, Rc::from(t)))),
}
};
let bm = mac(b);
let bracket = Meaning::prim(Cmd::OtherChar, u32::from(b'['));
e.eq_define(Loc::Cs(rd), Val::Meaning(bracket));
e.eq_define(Loc::Cs(ra), Val::Meaning(mac(a)));
e.eq_define(Loc::Cs(rb), Val::Meaning(bm.clone()));
e.eq_define(Loc::Cs(lt), Val::Meaning(next_meaning));
e.eq_define(Loc::Cs(rc), Val::Meaning(bm));
Some(())
}
fn xcolor_type(e: &Engine, m: &Meaning, xcolor_at: CsId) -> Option<(u8, Vec<Token>)> {
if m.cmd != Cmd::Call {
return None;
}
let mac = m.mac.as_ref()?;
let body = mac.tokens.strip_prefix(&[Token::END_MATCH])?;
let (&first, rest) = body.split_first()?;
if first != Token::cs(xcolor_at) {
return None;
}
let mut groups: Vec<&[Token]> = Vec::new();
let mut i = 0;
while i < rest.len() {
let t = rest[i];
if t.is_cs() || t.cmd_code() != Cmd::LeftBrace as u32 {
return None;
}
let start = i + 1;
let mut depth = 0;
loop {
let k = *rest.get(i)?;
if !k.is_cs() {
if k.cmd_code() == Cmd::LeftBrace as u32 {
depth += 1;
} else if k.cmd_code() == Cmd::RightBrace as u32 {
depth -= 1;
if depth == 0 {
break;
}
}
}
i += 1;
}
groups.push(&rest[start..i]);
i += 1;
}
if groups.len() != 4 {
return None;
}
let dollar = |g: &[Token]| g.iter().any(|t| !t.is_cs() && t.chr() == u32::from(b'$'));
if dollar(groups[0]) || dollar(groups[1]) {
return None;
}
let _ = e;
let ty = if !groups[0].is_empty() {
2
} else if !groups[1].is_empty() {
3
} else {
4
};
Some((ty, groups[1].to_vec()))
}
fn bcolor_is_noop(e: &Engine) -> bool {
let lookup = |n: &[u8]| e.hash.lookup(n);
let (Some(b), Some(relax)) = (lookup(b"XC@bcolor"), lookup(b"relax")) else {
return false;
};
let m = e.eqtb.meaning(b);
if m.mac.is_none() && m.cmd == Cmd::Relax && m.same_as(e.eqtb.meaning(relax)) {
return true;
}
let Some(reset) = lookup(b"pgfsys@color@reset") else {
return false;
};
let unprotected = |cs| {
e.eqtb
.meaning(cs)
.mac
.as_ref()
.is_some_and(|m| !m.protected)
};
unprotected(b)
&& unprotected(reset)
&& deps::plain_body(e, b) == Some(&[Token::cs(reset)][..])
&& deps::plain_body(e, reset).is_some_and(<[Token]>::is_empty)
}
fn run_color(e: &mut Engine, _args: &[TokenList]) -> bool {
if e.eqtb.int(ip::GLOBALDEFS) != 0 || e.after_token.0 != 0 {
return false;
}
if !COLOR_DEPS.with(|d| d.borrow_mut().check(e)) || !DEPS.with(|d| d.borrow_mut().check(e)) {
return why("color dependencies");
}
let Some(mut f) = e.native_following_subst() else {
return why("color: arguments not in a token list");
};
let mut i = 0;
skip_spaces(e, &mut f, &mut i);
let Some(next) = f.get(e, i) else {
return why("color: arguments");
};
if is_bracket(e, next) || (!next.is_cs() && next.cmd_code() == Cmd::Spacer as u32) {
return why("color: model");
}
let Some(arg) = read_arg(e, &mut f, &mut i) else {
return why("color: arguments");
};
if f.count(e, i).is_none() {
return why("color: arguments end inside a parameter");
}
if !bcolor_is_noop(e) {
return why("color: \\XC@bcolor");
}
let lookup = |n: &[u8]| e.hash.lookup(n);
for flag in [&b"ifblendcolors"[..], b"ifmaskcolors", b"ifconvertcolorsU"] {
let m = lookup(flag).map(|id| e.eqtb.meaning(id));
if !m.is_some_and(|m| m.cmd == Cmd::IfTest && m.chr == 15) {
return why("color: blend, mask or convert");
}
}
let (Some(tracing), Some(xcolor_at)) = (lookup(b"XC@tracing"), lookup(b"xcolor@")) else {
return false;
};
match deps::plain_body(e, tracing).and_then(deps::sanitized) {
Some(t) if t == b"0" || t == b"1" || t == b"2" => {}
_ => return why("color: tracing"),
}
let Some(name) = deps::edef(e, &arg).and_then(|t| deps::sanitized(&t)) else {
return why("color: expansion");
};
let mut cname = b"\\color@".to_vec();
cname.extend_from_slice(&name);
let relax_m = match lookup(b"relax") {
Some(r) => e.eqtb.meaning(r).clone(),
None => return false,
};
let ccs = lookup(&cname);
let undefined = ccs.is_none_or(|c| deps::absent(e.eqtb.meaning(c), &relax_m));
let (cm, current, tst_true, create, dimen) = if undefined {
if !mix_ok(e) {
return why("color: expression settings");
}
let Some(spec) = xcolormix::split(e, &name) else {
return why("color: expression");
};
if !xcolormix::in_range(&spec) {
return why("color: component out of range");
}
let drv = xcolormix::driver_text(&spec);
let mut body = vec![
Token::END_MATCH,
Token::cs(xcolor_at),
Token::LEFT_BRACE,
Token::RIGHT_BRACE,
Token::LEFT_BRACE,
];
body.extend_from_slice(&drv);
body.push(Token::RIGHT_BRACE);
body.push(Token::LEFT_BRACE);
body.extend(
spec.model
.name()
.iter()
.map(|&c| Token::other(u32::from(c))),
);
body.push(Token::RIGHT_BRACE);
body.push(Token::LEFT_BRACE);
body.extend(xcolormix::clr_tokens(&spec));
body.push(Token::RIGHT_BRACE);
let cm = Meaning {
cmd: Cmd::Call,
chr: 0,
mac: Some(Rc::new(Macro::new(false, Rc::from(body)))),
};
let Some(dreg) = lookup(b"dimen@")
.map(|id| e.eqtb.meaning(id))
.filter(|m| m.cmd == Cmd::AssignDimen && m.chr >= crate::scan::REG_OFFSET)
.map(|m| m.chr - crate::scan::REG_OFFSET)
else {
return false;
};
let Some(dval) = xcolormix::last_arg_dimen(&spec) else {
return false;
};
let create = ccs.is_none_or(|c| e.eqtb.meaning(c).cmd == Cmd::UndefinedCs);
(cm, drv, true, create, Some((dreg, dval)))
} else {
let ccs = ccs.expect("defined");
let cm = e.eqtb.meaning(ccs).clone();
let Some((_, drv)) = xcolor_type(e, &cm, xcolor_at) else {
if super::debug() {
eprintln!(
"[native] color: {} is {:?}",
String::from_utf8_lossy(&name),
cm.cmd
);
}
return why("color: not an xcolor colour");
};
let Some(current) = deps::edef(e, &drv) else {
return why("color: driver text");
};
(cm, current, false, false, None)
};
let (Some(tmp), Some(tst), Some(iffalse), Some(iftrue), Some(cur), Some(curcolor), Some(dot)) = (
lookup(b"XC@@tmp"),
lookup(b"ifXC@@tst"),
lookup(b"iffalse"),
lookup(b"iftrue"),
lookup(b"XC@current@color"),
lookup(b"current@color"),
lookup(b"\\color@."),
) else {
return why("color: control sequences");
};
let (Some(undeclared), Some(declared)) =
(lookup(b"@undeclaredcolor"), lookup(b"@declaredcolor"))
else {
return false;
};
let tail: Vec<Token> = [
&b"XC@mcolor"[..],
b"set@color",
b"XC@ecolor",
b"ignorespaces",
]
.iter()
.filter_map(|n| lookup(n).map(Token::cs))
.collect();
if tail.len() != 4 {
return false;
}
e.native_consume(&f, i);
if ifnextchar_state(
e,
next,
vec![Token::cs(undeclared)],
vec![Token::cs(declared)],
)
.is_none()
{
return true;
}
let mac = |t: Vec<Token>| Meaning {
cmd: Cmd::Call,
chr: 0,
mac: Some(Rc::new(Macro::new(
false,
Rc::from(
std::iter::once(Token::END_MATCH)
.chain(t)
.collect::<Vec<_>>(),
),
))),
};
let text: Vec<Token> = name
.iter()
.map(|&c| {
if c == b' ' {
Token::SPACE
} else {
Token::other(u32::from(c))
}
})
.collect();
e.eq_define(Loc::Cs(tmp), Val::Meaning(mac(text)));
if create {
let c = e.hash.lookup_or_insert(&cname);
e.eq_define(Loc::Cs(c), Val::Meaning(relax_m));
}
let f = e
.eqtb
.meaning(if tst_true { iftrue } else { iffalse })
.clone();
e.eq_define(Loc::Cs(tst), Val::Meaning(f));
if let Some((reg, v)) = dimen {
e.eq_define(Loc::Dimen(reg), Val::Int(i64::from(v)));
}
e.eq_define(Loc::Cs(cur), Val::Meaning(cm.clone()));
e.eq_define(Loc::Cs(curcolor), Val::Meaning(mac(current)));
e.eq_define(Loc::Cs(dot), Val::Meaning(cm));
e.last_cs_name = dot;
e.native_insert(tail);
if super::debug() {
eprintln!("[native] color native");
}
true
}
pub(super) const MCOLOR: Native = Native {
name: "XC@mcolor",
fingerprints: &[(53, 0x8437_fdaf_72cc_7fa7)],
executes: true,
run: run_mcolor,
};
const MCOLOR_NAMES: &[&str] = &[
"colorlet",
"XC@colorlet",
"XC@col@rlet",
"applycolormixins",
"loop",
"repeat",
"pgfutil@namelet",
"pgfutil@@namelet",
"XC@nummixins",
"XC@countmixins",
];
const MCOLOR_KNOWN: &[u64] = &[0xaa4e_78a1_29fa_46a9];
thread_local! {
static MCOLOR_DEPS: RefCell<Deps> =
RefCell::new(Deps::new("mcolor", MCOLOR_NAMES, MCOLOR_KNOWN));
}
fn count_reg(e: &Engine, name: &[u8]) -> Option<u32> {
let m = e.eqtb.meaning(e.hash.lookup(name)?);
(m.cmd == Cmd::AssignInt && m.chr >= crate::scan::REG_OFFSET && m.mac.is_none())
.then(|| m.chr - crate::scan::REG_OFFSET)
}
fn letters(s: &[u8]) -> Vec<Token> {
s.iter()
.map(|&c| {
if c.is_ascii_alphabetic() || c == b'@' {
Token::letter(u32::from(c))
} else {
Token::other(u32::from(c))
}
})
.collect()
}
fn run_mcolor(e: &mut Engine, _args: &[TokenList]) -> bool {
if e.eqtb.int(ip::GLOBALDEFS) != 0 || e.after_token.0 != 0 {
return false;
}
let (Some(nmix), Some(cmix)) = (
count_reg(e, b"XC@nummixins"),
count_reg(e, b"XC@countmixins"),
) else {
return why("mcolor: registers");
};
if !MCOLOR_DEPS.with(|d| d.borrow_mut().check(e))
|| !COLORLET_DEPS.with(|d| d.borrow_mut().check(e))
|| !DEPS.with(|d| d.borrow_mut().check(e))
{
return why("mcolor dependencies");
}
let n = e.eqtb.count[nmix as usize].val;
if n <= 0 {
return true;
}
if n > 16 || !mix_ok(e) {
return why("mcolor: settings");
}
let lookup = |n: &[u8]| e.hash.lookup(n);
let (
Some(tracing),
Some(globalcolors),
Some(xglobal),
Some(empty),
Some(iterate),
Some(relax),
Some(curcolor),
) = (
lookup(b"XC@tracing"),
lookup(b"ifglobalcolors"),
lookup(b"xglobal@"),
lookup(b"@empty"),
lookup(b"iterate"),
lookup(b"relax"),
lookup(b"current@color"),
)
else {
return false;
};
match deps::plain_body(e, tracing).and_then(deps::sanitized) {
Some(t) if t == b"0" || t == b"1" => {}
_ => return why("mcolor: tracing"),
}
let gm = e.eqtb.meaning(globalcolors);
let local = gm.cmd == Cmd::IfTest
&& gm.chr == 15
&& gm.mac.is_none()
&& e.eqtb.meaning(xglobal).same_as(e.eqtb.meaning(empty));
if !local || e.eqtb.int(ip::ESCAPECHAR) != i32::from(b'\\') {
return why("mcolor: global colours");
}
let Some(spec0) = xcolormix::split(e, b".") else {
return why("mcolor: current colour");
};
let Some(body0) = xcolormix::definition(e, &spec0) else {
return false;
};
let mut current = xcolormix::normalized(&spec0);
let mut body = body0;
let mut last_define = true;
for k in 0..n {
let (Some(csname), Some(endcsname)) = (lookup(b"csname"), lookup(b"endcsname")) else {
return false;
};
let mut toks = letters(b"XC@mixtmp");
toks.push(Token::cs(csname));
toks.extend(letters(b"XC@mixin@"));
toks.extend(k.to_string().bytes().map(|b| Token::other(u32::from(b))));
toks.push(Token::cs(endcsname));
let Some(color) = deps::edef(e, &toks).and_then(|t| deps::sanitized(&t)) else {
return why("mcolor: mixin");
};
if color == b"XC@mixtmp" {
last_define = false;
continue;
}
if !color.contains(&b'!') {
return why("mcolor: mixin form");
}
let Some(spec) = xcolormix::split_over(e, &color, Some((b"XC@mixtmp", ¤t))) else {
return why("mcolor: mixin expression");
};
let Some(b) = xcolormix::definition(e, &spec) else {
return false;
};
body = b;
current = xcolormix::normalized(&spec);
last_define = true;
}
let (Some(rlet), Some(def), Some(prefix)) = (
lookup(b"XC@col@rlet"),
lookup(b"XC@definec@lor"),
lookup(b"colornameprefix"),
) else {
return false;
};
let mixtmp = e.hash.lookup_or_insert(b"\\color@XC@mixtmp");
let m = Meaning {
cmd: Cmd::Call,
chr: 0,
mac: Some(Rc::new(Macro::new(false, Rc::from(body)))),
};
e.eq_define(Loc::Cs(mixtmp), Val::Meaning(m.clone()));
if last_define {
let other = |c: u8| Token::other(u32::from(c));
let mut a = vec![Token::cs(def), other(b'['), other(b']'), Token::LEFT_BRACE];
a.extend(b"XC@mixtmp".iter().map(|&c| other(c)));
a.push(Token::RIGHT_BRACE);
let mut b = a.clone();
b.extend([
other(b'['),
Token::LEFT_BRACE,
Token::cs(prefix),
Token::RIGHT_BRACE,
other(b']'),
]);
let _ = ifnextchar_state(e, Token::LEFT_BRACE, a, b);
} else {
let other = |c: u8| Token::other(u32::from(c));
let mut a = vec![Token::cs(rlet), other(b'['), other(b']'), Token::LEFT_BRACE];
a.extend(letters(b"XC@mixtmp"));
a.push(Token::RIGHT_BRACE);
let mut b = a.clone();
b.extend([
other(b'['),
Token::LEFT_BRACE,
Token::RIGHT_BRACE,
other(b']'),
]);
let _ = ifnextchar_state(e, Token::LEFT_BRACE, a, b);
}
e.eq_define(Loc::Count(cmix), Val::Int(i64::from(n)));
let rm = e.eqtb.meaning(relax).clone();
e.eq_define(Loc::Cs(iterate), Val::Meaning(rm));
e.eq_define(Loc::Cs(curcolor), Val::Meaning(m));
e.last_cs_name = mixtmp;
if super::debug() {
eprintln!("[native] mcolor native");
}
true
}