/* | |
* ==================================================================== | |
* Licensed to the Apache Software Foundation (ASF) under one or more | |
* contributor license agreements. See the NOTICE file distributed with | |
* this work for additional information regarding copyright ownership. | |
* The ASF licenses this file to You under the Apache License, Version 2.0 | |
* (the "License"); you may not use this file except in compliance with | |
* the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* ==================================================================== | |
*/ | |
package org.apache.poi.xslf.usermodel; | |
import java.awt.Color; | |
import org.apache.poi.sl.draw.DrawPaint; | |
import org.apache.poi.sl.usermodel.ColorStyle; | |
import org.apache.poi.sl.usermodel.PresetColor; | |
import org.apache.poi.util.Beta; | |
import org.apache.poi.util.Internal; | |
import org.apache.poi.util.POILogFactory; | |
import org.apache.poi.util.POILogger; | |
import org.apache.xmlbeans.XmlObject; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTColor; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTFontReference; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTHslColor; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveFixedPercentage; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTPresetColor; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTScRgbColor; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTSchemeColor; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTSystemColor; | |
import org.w3c.dom.Node; | |
/** | |
* Encapsulates logic to read color definitions from DrawingML and convert them to java.awt.Color | |
*/ | |
@Beta | |
@Internal | |
public class XSLFColor { | |
private final static POILogger LOGGER = POILogFactory.getLogger(XSLFColor.class); | |
private XmlObject _xmlObject; | |
private Color _color; | |
private CTSchemeColor _phClr; | |
public XSLFColor(XmlObject obj, XSLFTheme theme, CTSchemeColor phClr) { | |
_xmlObject = obj; | |
_phClr = phClr; | |
_color = toColor(obj, theme); | |
} | |
@Internal | |
public XmlObject getXmlObject() { | |
return _xmlObject; | |
} | |
/** | |
* | |
* @return the displayed color as a Java Color. | |
* If not color information was found in the supplied xml object then a null is returned. | |
*/ | |
public Color getColor() { | |
return DrawPaint.applyColorTransform(getColorStyle()); | |
} | |
public ColorStyle getColorStyle() { | |
return new ColorStyle() { | |
public Color getColor() { | |
return _color; | |
} | |
public int getAlpha() { | |
return getRawValue("alpha"); | |
} | |
public int getHueOff() { | |
return getRawValue("hueOff"); | |
} | |
public int getHueMod() { | |
return getRawValue("hueMod"); | |
} | |
public int getSatOff() { | |
return getRawValue("satOff"); | |
} | |
public int getSatMod() { | |
return getRawValue("satMod"); | |
} | |
public int getLumOff() { | |
return getRawValue("lumOff"); | |
} | |
public int getLumMod() { | |
return getRawValue("lumMod"); | |
} | |
public int getShade() { | |
return getRawValue("shade"); | |
} | |
public int getTint() { | |
return getRawValue("tint"); | |
} | |
}; | |
} | |
Color toColor(XmlObject obj, XSLFTheme theme) { | |
Color color = null; | |
for (XmlObject ch : obj.selectPath("*")) { | |
if (ch instanceof CTHslColor) { | |
CTHslColor hsl = (CTHslColor)ch; | |
int h = hsl.getHue2(); | |
int s = hsl.getSat2(); | |
int l = hsl.getLum2(); | |
color = DrawPaint.HSL2RGB(h / 60000d, s / 1000d, l / 1000d, 1d); | |
} else if (ch instanceof CTPresetColor) { | |
CTPresetColor prst = (CTPresetColor)ch; | |
String colorName = prst.getVal().toString(); | |
PresetColor pc = PresetColor.valueOfOoxmlId(colorName); | |
if (pc != null) { | |
color = pc.color; | |
} | |
} else if (ch instanceof CTSchemeColor) { | |
CTSchemeColor schemeColor = (CTSchemeColor)ch; | |
String colorRef = schemeColor.getVal().toString(); | |
if(_phClr != null) { | |
// context color overrides the theme | |
colorRef = _phClr.getVal().toString(); | |
} | |
// find referenced CTColor in the theme and convert it to java.awt.Color via a recursive call | |
CTColor ctColor = theme.getCTColor(colorRef); | |
if(ctColor != null) color = toColor(ctColor, null); | |
} else if (ch instanceof CTScRgbColor) { | |
// color in percentage is in linear RGB color space, i.e. needs to be gamma corrected for AWT color | |
CTScRgbColor scrgb = (CTScRgbColor)ch; | |
color = new Color(DrawPaint.lin2srgb(scrgb.getR()), DrawPaint.lin2srgb(scrgb.getG()), DrawPaint.lin2srgb(scrgb.getB())); | |
} else if (ch instanceof CTSRgbColor) { | |
// color in sRGB color space, i.e. same as AWT Color | |
CTSRgbColor srgb = (CTSRgbColor)ch; | |
byte[] val = srgb.getVal(); | |
color = new Color(0xFF & val[0], 0xFF & val[1], 0xFF & val[2]); | |
} else if (ch instanceof CTSystemColor) { | |
CTSystemColor sys = (CTSystemColor)ch; | |
if(sys.isSetLastClr()) { | |
byte[] val = sys.getLastClr(); | |
color = new Color(0xFF & val[0], 0xFF & val[1], 0xFF & val[2]); | |
} else { | |
String colorName = sys.getVal().toString(); | |
PresetColor pc = PresetColor.valueOfOoxmlId(colorName); | |
if (pc != null) { | |
color = pc.color; | |
} | |
if (color == null) { | |
color = Color.black; | |
} | |
} | |
} else if (ch instanceof CTFontReference) { | |
// try next ... | |
continue; | |
} else { | |
throw new IllegalArgumentException("Unexpected color choice: " + ch.getClass()); | |
} | |
} | |
return color; | |
} | |
/** | |
* Sets the solid color | |
* | |
* @param color solid color | |
*/ | |
@Internal | |
protected void setColor(Color color) { | |
if (!(_xmlObject instanceof CTSolidColorFillProperties)) { | |
LOGGER.log(POILogger.ERROR, "XSLFColor.setColor currently only supports CTSolidColorFillProperties"); | |
return; | |
} | |
CTSolidColorFillProperties fill = (CTSolidColorFillProperties)_xmlObject; | |
if (fill.isSetSrgbClr()) { | |
fill.unsetSrgbClr(); | |
} | |
if (fill.isSetScrgbClr()) { | |
fill.unsetScrgbClr(); | |
} | |
if (fill.isSetHslClr()) { | |
fill.unsetHslClr(); | |
} | |
if (fill.isSetPrstClr()) { | |
fill.unsetPrstClr(); | |
} | |
if (fill.isSetSchemeClr()) { | |
fill.unsetSchemeClr(); | |
} | |
if (fill.isSetSysClr()) { | |
fill.unsetSysClr(); | |
} | |
float[] rgbaf = color.getRGBComponents(null); | |
boolean addAlpha = (rgbaf.length == 4 && rgbaf[3] < 1f); | |
CTPositiveFixedPercentage alphaPct; | |
// see office open xml part 4 - 5.1.2.2.30 and 5.1.2.2.32 | |
if (isInt(rgbaf[0]) && isInt(rgbaf[1]) && isInt(rgbaf[2])) { | |
// sRGB has a gamma of 2.2 | |
CTSRgbColor rgb = fill.addNewSrgbClr(); | |
byte rgbBytes[] = { (byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue() }; | |
rgb.setVal(rgbBytes); | |
alphaPct = (addAlpha) ? rgb.addNewAlpha() : null; | |
} else { | |
CTScRgbColor rgb = fill.addNewScrgbClr(); | |
rgb.setR(DrawPaint.srgb2lin(rgbaf[0])); | |
rgb.setG(DrawPaint.srgb2lin(rgbaf[1])); | |
rgb.setB(DrawPaint.srgb2lin(rgbaf[2])); | |
alphaPct = (addAlpha) ? rgb.addNewAlpha() : null; | |
} | |
// alpha (%) | |
if (alphaPct != null) { | |
alphaPct.setVal((int)(100000 * rgbaf[3])); | |
} | |
} | |
/** | |
* @return true, if this is an integer color value | |
*/ | |
private static boolean isInt(float f) { | |
return Math.abs((f*255f) - Math.rint(f*255f)) < 0.00001f; | |
} | |
private int getRawValue(String elem) { | |
String query = "declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' $this//a:" + elem; | |
XmlObject[] obj; | |
// first ask the context color and if not found, ask the actual color bean | |
if (_phClr != null){ | |
obj = _phClr.selectPath(query); | |
if (obj.length == 1){ | |
Node attr = obj[0].getDomNode().getAttributes().getNamedItem("val"); | |
if(attr != null) { | |
return Integer.parseInt(attr.getNodeValue()); | |
} | |
} | |
} | |
obj = _xmlObject.selectPath(query); | |
if (obj.length == 1){ | |
Node attr = obj[0].getDomNode().getAttributes().getNamedItem("val"); | |
if(attr != null) { | |
return Integer.parseInt(attr.getNodeValue()); | |
} | |
} | |
return -1; | |
} | |
/** | |
* Read a perecentage value from the supplied xml bean. | |
* Example: | |
* <a:tint val="45000"/> | |
* | |
* the returned value is 45 | |
* | |
* @return the percentage value in the range [0 .. 100] | |
*/ | |
private int getPercentageValue(String elem){ | |
int val = getRawValue(elem); | |
return (val == -1) ? val : (val / 1000); | |
} | |
private int getAngleValue(String elem){ | |
int val = getRawValue(elem); | |
return (val == -1) ? val : (val / 60000); | |
} | |
/** | |
* the opacity as expressed by a percentage value | |
* | |
* @return opacity in percents in the range [0..100] | |
* or -1 if the value is not set | |
*/ | |
int getAlpha(){ | |
return getPercentageValue("alpha"); | |
} | |
/** | |
* the opacity as expressed by a percentage relative to the input color | |
* | |
* @return opacity in percents in the range [0..100] | |
* or -1 if the value is not set | |
*/ | |
int getAlphaMod(){ | |
return getPercentageValue("alphaMod"); | |
} | |
/** | |
* the opacity as expressed by a percentage offset increase or decrease relative to | |
* the input color. Increases will never increase the opacity beyond 100%, decreases will | |
* never decrease the opacity below 0%. | |
* | |
* @return opacity shift in percents in the range [0..100] | |
* or -1 if the value is not set | |
*/ | |
int getAlphaOff(){ | |
return getPercentageValue("alphaOff"); | |
} | |
int getHue(){ | |
return getAngleValue("hue"); | |
} | |
int getHueMod(){ | |
return getPercentageValue("hueMod"); | |
} | |
int getHueOff(){ | |
return getPercentageValue("hueOff"); | |
} | |
/** | |
* specifies the input color with the specified luminance, | |
* but with its hue and saturation unchanged. | |
* | |
* @return luminance in percents in the range [0..100] | |
* or -1 if the value is not set | |
*/ | |
int getLum(){ | |
return getPercentageValue("lum"); | |
} | |
/** | |
* the luminance as expressed by a percentage relative to the input color | |
* | |
* @return luminance in percents in the range [0..100] | |
* or -1 if the value is not set | |
*/ | |
int getLumMod(){ | |
return getPercentageValue("lumMod"); | |
} | |
/** | |
* the luminance shift as expressed by a percentage relative to the input color | |
* | |
* @return luminance shift in percents in the range [0..100] | |
* or -1 if the value is not set | |
*/ | |
int getLumOff(){ | |
return getPercentageValue("lumOff"); | |
} | |
/** | |
* specifies the input color with the specified saturation, | |
* but with its hue and luminance unchanged. | |
* | |
* @return saturation in percents in the range [0..100] | |
* or -1 if the value is not set | |
*/ | |
int getSat(){ | |
return getPercentageValue("sat"); | |
} | |
/** | |
* the saturation as expressed by a percentage relative to the input color | |
* | |
* @return saturation in percents in the range [0..100] | |
* or -1 if the value is not set | |
*/ | |
int getSatMod(){ | |
return getPercentageValue("satMod"); | |
} | |
/** | |
* the saturation shift as expressed by a percentage relative to the input color | |
* | |
* @return saturation shift in percents in the range [0..100] | |
* or -1 if the value is not set | |
*/ | |
int getSatOff(){ | |
return getPercentageValue("satOff"); | |
} | |
/** | |
* specifies the input color with the specific red component, but with the blue and green color | |
* components unchanged | |
* | |
* @return the value of the red component specified as a | |
* percentage with 0% indicating minimal blue and 100% indicating maximum | |
* or -1 if the value is not set | |
*/ | |
int getRed(){ | |
return getPercentageValue("red"); | |
} | |
int getRedMod(){ | |
return getPercentageValue("redMod"); | |
} | |
int getRedOff(){ | |
return getPercentageValue("redOff"); | |
} | |
/** | |
* specifies the input color with the specific green component, but with the red and blue color | |
* components unchanged | |
* | |
* @return the value of the green component specified as a | |
* percentage with 0% indicating minimal blue and 100% indicating maximum | |
* or -1 if the value is not set | |
*/ | |
int getGreen(){ | |
return getPercentageValue("green"); | |
} | |
int getGreenMod(){ | |
return getPercentageValue("greenMod"); | |
} | |
int getGreenOff(){ | |
return getPercentageValue("greenOff"); | |
} | |
/** | |
* specifies the input color with the specific blue component, but with the red and green color | |
* components unchanged | |
* | |
* @return the value of the blue component specified as a | |
* percentage with 0% indicating minimal blue and 100% indicating maximum | |
* or -1 if the value is not set | |
*/ | |
int getBlue(){ | |
return getPercentageValue("blue"); | |
} | |
int getBlueMod(){ | |
return getPercentageValue("blueMod"); | |
} | |
int getBlueOff(){ | |
return getPercentageValue("blueOff"); | |
} | |
/** | |
* specifies a darker version of its input color. | |
* A 10% shade is 10% of the input color combined with 90% black. | |
* | |
* @return the value of the shade specified as a | |
* percentage with 0% indicating minimal shade and 100% indicating maximum | |
* or -1 if the value is not set | |
*/ | |
public int getShade(){ | |
return getPercentageValue("shade"); | |
} | |
/** | |
* specifies a lighter version of its input color. | |
* A 10% tint is 10% of the input color combined with 90% white. | |
* | |
* @return the value of the tint specified as a | |
* percentage with 0% indicating minimal tint and 100% indicating maximum | |
* or -1 if the value is not set | |
*/ | |
public int getTint(){ | |
return getPercentageValue("tint"); | |
} | |
} |