/*
* Copyright (c) 2001 World Wide Web Consortium,
* (Massachusetts Institute of Technology, Institut National de
* Recherche en Informatique et en Automatique, Keio University). All
* Rights Reserved. This program is distributed under the W3C's Software
* Intellectual Property License. This program is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE.
* See W3C License http://www.w3.org/Consortium/Legal/ for more details.
*
* $Id$
*/
package org.w3c.css.values.color;
import org.w3c.css.util.ApplContext;
import org.w3c.css.util.CssVersion;
import org.w3c.css.util.InvalidParamException;
import org.w3c.css.values.CssCheckableValue;
import org.w3c.css.values.CssColor;
import org.w3c.css.values.CssExpression;
import org.w3c.css.values.CssIdent;
import org.w3c.css.values.CssNumber;
import org.w3c.css.values.CssPercentage;
import org.w3c.css.values.CssTypes;
import org.w3c.css.values.CssValue;
import java.math.BigDecimal;
import static org.w3c.css.values.CssOperator.COMMA;
import static org.w3c.css.values.CssOperator.SPACE;
public class RGBA extends RGB {
static final String functionname = "rgba";
public static final CssIdent r, g, b, a;
static {
r = CssIdent.getIdent("r");
g = CssIdent.getIdent("g");
b = CssIdent.getIdent("b");
a = CssIdent.getIdent("alpha");
}
private String output = null;
String fname;
CssValue va;
boolean isRelative = false;
CssValue fromValue;
public static final CssValue filterAlpha(ApplContext ac, CssValue val)
throws InvalidParamException {
if (val.getRawType() == CssTypes.CSS_CALC) {
// TODO add warning about uncheckability
// might need to extend...
} else {
if (val.getType() == CssTypes.CSS_NUMBER) {
CssCheckableValue v = val.getCheckableValue();
if (!v.isPositive()) {
ac.getFrame().addWarning("out-of-range", val.toString());
CssNumber nb = new CssNumber();
nb.setIntValue(0);
return nb;
}
if (val.getRawType() == CssTypes.CSS_NUMBER) {
BigDecimal pp = val.getNumber().getBigDecimalValue();
if (pp.compareTo(BigDecimal.ONE) > 0) {
ac.getFrame().addWarning("out-of-range", val.toString());
CssNumber nb = new CssNumber();
nb.setIntValue(1);
return nb;
}
}
} else if (val.getType() == CssTypes.CSS_PERCENTAGE) {
// This starts with CSS Color 4
CssCheckableValue v = val.getCheckableValue();
if (!v.isPositive()) {
ac.getFrame().addWarning("out-of-range", val.toString());
CssNumber nb = new CssNumber();
nb.setIntValue(0);
return nb;
}
if (val.getRawType() == CssTypes.CSS_PERCENTAGE) {
float p = ((CssPercentage) val).floatValue();
if (p > 100.) {
ac.getFrame().addWarning("out-of-range", val.toString());
return new CssPercentage(100);
}
}
}
}
return val;
}
public static RGBA parseShortRGBColor(ApplContext ac, String s)
throws InvalidParamException {
int r;
int g;
int b;
int a;
int v;
int idx;
boolean hasAlpha = false;
boolean isCss3;
isCss3 = (ac.getCssVersion().compareTo(CssVersion.CSS3) >= 0);
idx = 1;
a = 0;
switch (s.length()) {
case 5:
a = Character.digit(s.charAt(4), 16);
if (!isCss3 || a < 0) {
throw new InvalidParamException("rgb", s, ac);
}
hasAlpha = true;
case 4:
r = Character.digit(s.charAt(idx++), 16);
g = Character.digit(s.charAt(idx++), 16);
b = Character.digit(s.charAt(idx++), 16);
if (r < 0 || g < 0 || b < 0) {
throw new InvalidParamException("rgb", s, ac);
}
break;
case 9:
a = Character.digit(s.charAt(7), 16);
v = Character.digit(s.charAt(8), 16);
if (!isCss3 || a < 0 || v < 0) {
throw new InvalidParamException("rgb", s, ac);
}
a = (a << 4) + v;
hasAlpha = true;
case 7:
r = Character.digit(s.charAt(idx++), 16);
v = Character.digit(s.charAt(idx++), 16);
if (r < 0 || v < 0) {
throw new InvalidParamException("rgb", s, ac);
}
r = (r << 4) + v;
g = Character.digit(s.charAt(idx++), 16);
v = Character.digit(s.charAt(idx++), 16);
if (g < 0 || v < 0) {
throw new InvalidParamException("rgb", s, ac);
}
g = (g << 4) + v;
b = Character.digit(s.charAt(idx++), 16);
v = Character.digit(s.charAt(idx++), 16);
if (b < 0 || v < 0) {
throw new InvalidParamException("rgb", s, ac);
}
b = (b << 4) + v;
break;
default:
throw new InvalidParamException("rgb", s, ac);
}
RGBA rgba;
if (hasAlpha) {
rgba = new RGBA(isCss3, r, g, b, a);
rgba.setRepresentationString(s);
} else {
rgba = new RGBA(isCss3, r, g, b);
// we force the specific display of it
}
rgba.setRepresentationString(s);
return rgba;
}
/**
* Parse a rgb/rgba color
*
* @param ac
* @param exp
* @param caller
* @param funcname
* @return RGBA
* @throws InvalidParamException
* @spec https://www.w3.org/TR/2026/CRD-css-color-4-20260227/#rgb-functions
* @spec https://www.w3.org/TR/2026/WD-css-color-5-20260227/#relative-RGB
*/
public static RGBA parseModernRGBA(ApplContext ac, CssExpression exp, CssColor caller, String funcname)
throws InvalidParamException {
RGBA rgba = new RGBA();
boolean gotNumber = false;
boolean gotPercentage = false;
if (funcname == null) {
int paren = funcname.lastIndexOf('(');
if (paren > 0) {
rgba.setFunctionName(funcname.substring(0, paren));
} else {
rgba.setFunctionName(funcname);
}
}
if (ac.getCssVersion().compareTo(CssVersion.CSS3) < 0) {
StringBuilder sb = new StringBuilder();
sb.append(funcname).append(exp.toStringFromStart()).append(')');
throw new InvalidParamException("notversion", sb.toString(),
ac.getCssVersionString(), ac);
}
if (exp.hasCssVariable()) {
caller.markCssVariable();
}
CssValue val = exp.getValue();
char op = exp.getOperator();
if (val == null && !exp.hasCssVariable()) {
throw new InvalidParamException("invalid-color", ac);
}
if (val.getType() == CssTypes.CSS_IDENT) {
if (CssColor.relative.equals(val.getIdent())) {
// we need to parse a color now
if ((val == null || op != SPACE || rgba.isRelative) && !exp.hasCssVariable()) {
exp.starts();
throw new InvalidParamException("invalid-color", ac);
}
rgba.isRelative = true; // so that we get only one from
rgba.isModernCss = true; // if we are there we must use the modern syntax
exp.next();
val = exp.getValue();
op = exp.getOperator();
CssExpression nex = new CssExpression();
nex.addValue(val);
CssColor c = new org.w3c.css.properties.css3.CssColor(ac, nex).getColor();
if ((val == null || op != SPACE) && !exp.hasCssVariable()) {
exp.starts();
throw new InvalidParamException("invalid-color", ac);
}
rgba.fromValue = val;
exp.next();
val = exp.getValue();
op = exp.getOperator();
if ((val == null || op != SPACE) && !exp.hasCssVariable()) {
exp.starts();
throw new InvalidParamException("invalid-color", ac);
}
}
}
rgba.setModernStyle(op == SPACE);
// R
switch (val.getType()) {
case CssTypes.CSS_VARIABLE:
exp.markCssVariable();
caller.markCssVariable();
case CssTypes.CSS_NUMBER:
if (gotPercentage) {
throw new InvalidParamException("colorfunc", val, rgba.fname, ac);
}
rgba.setRed(ac, val);
gotNumber = true;
break;
case CssTypes.CSS_PERCENTAGE:
if (gotNumber) {
throw new InvalidParamException("colorfunc", val, rgba.fname, ac);
}
rgba.setRed(ac, val);
gotPercentage = true;
break;
case CssTypes.CSS_IDENT:
if ((CssColor.none.equals(val.getIdent()) && rgba.isModernCss) ||
(rgba.isRelative && r.equals(val.getIdent()))) {
rgba.setRed(ac, val);
break;
}
default:
if (!exp.hasCssVariable()) {
throw new InvalidParamException("colorfunc", val, rgba.fname, ac);
}
}
exp.next();
// G
val = exp.getValue();
op = exp.getOperator();
if (((val == null) ||
(rgba.isModernCss && (op != SPACE)) ||
(!rgba.isModernCss && (op != COMMA))) && !exp.hasCssVariable()) {
exp.starts();
throw new InvalidParamException("invalid-color", ac);
}
switch (val.getType()) {
case CssTypes.CSS_VARIABLE:
exp.markCssVariable();
caller.markCssVariable();
case CssTypes.CSS_NUMBER:
if (gotPercentage) {
throw new InvalidParamException("colorfunc", val, rgba.fname, ac);
}
rgba.setGreen(ac, val);
break;
case CssTypes.CSS_PERCENTAGE:
if (gotNumber) {
throw new InvalidParamException("colorfunc", val, rgba.fname, ac);
}
rgba.setGreen(ac, val);
break;
case CssTypes.CSS_IDENT:
if ((CssColor.none.equals(val.getIdent()) && rgba.isModernCss) ||
(rgba.isRelative && g.equals(val.getIdent()))) {
rgba.setGreen(ac, val);
break;
}
default:
if (!exp.hasCssVariable()) {
throw new InvalidParamException("colorfunc", val, rgba.fname, ac);
}
}
exp.next();
// B
val = exp.getValue();
op = exp.getOperator();
// check only the value as operator will be checked if there are more values
if ((val == null) && !exp.hasCssVariable()) {
exp.starts();
throw new InvalidParamException("invalid-color", ac);
}
switch (val.getType()) {
case CssTypes.CSS_VARIABLE:
exp.markCssVariable();
caller.markCssVariable();
case CssTypes.CSS_NUMBER:
if (gotPercentage) {
throw new InvalidParamException("colorfunc", val, rgba.fname, ac);
}
rgba.setBlue(ac, val);
break;
case CssTypes.CSS_PERCENTAGE:
if (gotNumber) {
throw new InvalidParamException("colorfunc", val, rgba.fname, ac);
}
rgba.setBlue(ac, val);
break;
case CssTypes.CSS_IDENT:
if ((CssColor.none.equals(val.getIdent()) && rgba.isModernCss) ||
(rgba.isRelative && b.equals(val.getIdent()))) {
rgba.setBlue(ac, val);
break;
}
default:
if (!exp.hasCssVariable()) {
throw new InvalidParamException("colorfunc", val, rgba.fname, ac);
}
}
exp.next();
// check for optional alpha
if (!exp.end()) {
if (((rgba.isModernCss && (op != SPACE)) || (!rgba.isModernCss && (op != COMMA)))
&& !exp.hasCssVariable()) {
throw new InvalidParamException("invalid-color", ac);
}
// modern syntax? check for a /
if (rgba.isModernCss) {
// now look for a switch
// now we need an alpha.
val = exp.getValue();
op = exp.getOperator();
if ((val.getType() != CssTypes.CSS_SWITCH) && !exp.hasCssVariable()) {
throw new InvalidParamException("colorfunc", val, "LCH", ac);
}
if ((op != SPACE) && !exp.hasCssVariable()) {
throw new InvalidParamException("invalid-color", ac);
}
exp.next();
}
val = exp.getValue();
op = exp.getOperator();
switch (val.getType()) {
case CssTypes.CSS_VARIABLE:
exp.markCssVariable();
caller.markCssVariable();
case CssTypes.CSS_NUMBER:
case CssTypes.CSS_PERCENTAGE:
rgba.setAlpha(ac, val);
break;
case CssTypes.CSS_IDENT:
if ((CssColor.none.equals(val.getIdent()) && rgba.isModernCss) ||
(rgba.isRelative && a.equals(val.getIdent()))) {
rgba.setAlpha(ac, val);
break;
}
default:
if (!exp.hasCssVariable()) {
throw new InvalidParamException("colorfunc", val, rgba.fname, ac);
}
}
}
return rgba;
}
public final void setAlpha(ApplContext ac, CssValue val)
throws InvalidParamException {
output = null;
va = filterAlpha(ac, val);
}
public boolean equals(RGBA other) {
if (other != null) {
return super.equals(other) && ((va == null && other.va == null) || (va != null && va.equals(other.va)));
}
return false;
}
/**
* Create a new RGBA
*/
public RGBA() {
fname = functionname;
}
/**
* Create new RGBA and with a specific function name
* (like astc-rgba http://www.atsc.org/cms/standards/a100/a_100_2.pdf #5.2.1.8.4.1
*/
public RGBA(String fname) {
this.fname = fname;
}
/**
* Create a new RGBA with default values
*
* @param r the red channel
* @param g the green channel
* @param b the blue channel
* @param a the alpha channel
*/
public RGBA(int r, int g, int b, float a) {
super(r, g, b);
fname = functionname;
CssNumber n = new CssNumber();
n.setFloatValue(a);
va = n;
setPercent(false);
}
/**
* Create a new RGBA with default values
*
* @param r the red channel
* @param g the green channel
* @param b the blue channel
*/
public RGBA(int r, int g, int b) {
super(r, g, b);
fname = functionname;
va = null;
setPercent(false);
}
/**
* Create a new RGBA with default values
*
* @param isModernCss a boolean toggling the output of RGB
* @param r the red channel, an int
* @param g the green channel, an int
* @param b the blue channel, an int
* @param a the alpha channel, an float
*/
public RGBA(boolean isModernCss, int r, int g, int b, float a) {
this(r, g, b, a);
this.isModernCss = isModernCss;
}
/**
* Create a new RGBA with default values
*
* @param isModernCss a boolean toggling the output of RGB
* @param r the red channel, an int
* @param g the green channel, an int
* @param b the blue channel, an int
*/
public RGBA(boolean isModernCss, int r, int g, int b) {
this(r, g, b);
this.isModernCss = isModernCss;
}
/**
* Set function name (basically rgb or rgba for now)
*
* @param fname
*/
public void setFunctionName(String fname) {
this.fname = fname;
}
protected void setRepresentationString(String s) {
output = s;
}
/**
* Returns a string representation of the object.
*/
public String toString() {
if (output == null) {
StringBuilder sb = new StringBuilder();
if (isModernCss) {
if (isRelative) {
sb.append("from ").append(fromValue).append(' ');
}
sb.append(RGB.functionname).append('(');
sb.append(vr).append(' ');
sb.append(vg).append(' ').append(vb);
if (va != null) {
sb.append(" / ").append(va);
}
sb.append(')');
} else {
sb.append(fname).append('(');
sb.append(vr).append(", ");
sb.append(vg).append(", ").append(vb);
if (va != null) {
sb.append(", ").append(va);
}
sb.append(')');
}
output = sb.toString();
}
return output;
}
}