You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

XSLFColor.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. /*
  2. * ====================================================================
  3. * Licensed to the Apache Software Foundation (ASF) under one or more
  4. * contributor license agreements. See the NOTICE file distributed with
  5. * this work for additional information regarding copyright ownership.
  6. * The ASF licenses this file to You under the Apache License, Version 2.0
  7. * (the "License"); you may not use this file except in compliance with
  8. * the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. * ====================================================================
  18. */
  19. package org.apache.poi.xslf.usermodel;
  20. import java.awt.Color;
  21. import javax.xml.namespace.QName;
  22. import org.apache.poi.sl.draw.DrawPaint;
  23. import org.apache.poi.sl.usermodel.AbstractColorStyle;
  24. import org.apache.poi.sl.usermodel.ColorStyle;
  25. import org.apache.poi.sl.usermodel.PresetColor;
  26. import org.apache.poi.util.Beta;
  27. import org.apache.poi.util.Internal;
  28. import org.apache.poi.util.POILogFactory;
  29. import org.apache.poi.util.POILogger;
  30. import org.apache.xmlbeans.XmlCursor;
  31. import org.apache.xmlbeans.XmlObject;
  32. import org.openxmlformats.schemas.drawingml.x2006.main.CTColor;
  33. import org.openxmlformats.schemas.drawingml.x2006.main.CTFontReference;
  34. import org.openxmlformats.schemas.drawingml.x2006.main.CTHslColor;
  35. import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveFixedPercentage;
  36. import org.openxmlformats.schemas.drawingml.x2006.main.CTPresetColor;
  37. import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor;
  38. import org.openxmlformats.schemas.drawingml.x2006.main.CTScRgbColor;
  39. import org.openxmlformats.schemas.drawingml.x2006.main.CTSchemeColor;
  40. import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties;
  41. import org.openxmlformats.schemas.drawingml.x2006.main.CTSystemColor;
  42. /**
  43. * Encapsulates logic to read color definitions from DrawingML and convert them to java.awt.Color
  44. */
  45. @Beta
  46. @Internal
  47. public class XSLFColor {
  48. private static final POILogger LOGGER = POILogFactory.getLogger(XSLFColor.class);
  49. private static final QName VAL_ATTR = new QName("val");
  50. private XmlObject _xmlObject;
  51. private Color _color;
  52. private CTSchemeColor _phClr;
  53. private XSLFSheet _sheet;
  54. @SuppressWarnings("WeakerAccess")
  55. public XSLFColor(XmlObject obj, XSLFTheme theme, CTSchemeColor phClr, XSLFSheet sheet) {
  56. _xmlObject = obj;
  57. _phClr = phClr;
  58. _sheet = sheet;
  59. _color = toColor(obj, theme);
  60. }
  61. @Internal
  62. public XmlObject getXmlObject() {
  63. return _xmlObject;
  64. }
  65. /**
  66. *
  67. * @return the displayed color as a Java Color.
  68. * If not color information was found in the supplied xml object then a null is returned.
  69. */
  70. public Color getColor() {
  71. return DrawPaint.applyColorTransform(getColorStyle());
  72. }
  73. @SuppressWarnings("WeakerAccess")
  74. public ColorStyle getColorStyle() {
  75. return new XSLFColorStyle(_xmlObject, _color, _phClr);
  76. }
  77. private Color toColor(CTHslColor hsl) {
  78. return DrawPaint.HSL2RGB(
  79. hsl.getHue2() / 60000d,
  80. hsl.getSat2() / 1000d,
  81. hsl.getLum2() / 1000d,
  82. 1d);
  83. }
  84. private Color toColor(CTPresetColor prst) {
  85. String colorName = prst.getVal().toString();
  86. PresetColor pc = PresetColor.valueOfOoxmlId(colorName);
  87. return (pc != null) ? pc.color : null;
  88. }
  89. private Color toColor(CTSchemeColor schemeColor, XSLFTheme theme) {
  90. String colorRef = schemeColor.getVal().toString();
  91. if(_phClr != null) {
  92. // context color overrides the theme
  93. colorRef = _phClr.getVal().toString();
  94. }
  95. // find referenced CTColor in the theme and convert it to java.awt.Color via a recursive call
  96. CTColor ctColor = theme == null ? null : theme.getCTColor(_sheet.mapSchemeColor(colorRef));
  97. return (ctColor != null) ? toColor(ctColor, null) : null;
  98. }
  99. private Color toColor(CTScRgbColor scrgb) {
  100. // percental [0..100000] scRGB color space needs to be gamma corrected for AWT/sRGB colorspace
  101. return DrawPaint.SCRGB2RGB(scrgb.getR()/100_000d,scrgb.getG()/100_000d,scrgb.getB()/100_000d);
  102. }
  103. private Color toColor(CTSRgbColor srgb) {
  104. // color in sRGB color space, i.e. same as AWT Color
  105. byte[] val = srgb.getVal();
  106. return new Color(0xFF & val[0], 0xFF & val[1], 0xFF & val[2]);
  107. }
  108. private Color toColor(CTSystemColor sys) {
  109. if (sys.isSetLastClr()) {
  110. byte[] val = sys.getLastClr();
  111. return new Color(0xFF & val[0], 0xFF & val[1], 0xFF & val[2]);
  112. } else {
  113. String colorName = sys.getVal().toString();
  114. PresetColor pc = PresetColor.valueOfOoxmlId(colorName);
  115. return (pc != null && pc.color != null) ? pc.color : Color.black;
  116. }
  117. }
  118. private Color toColor(XmlObject obj, XSLFTheme theme) {
  119. if (obj == null) {
  120. return _phClr == null ? null : toColor(_phClr, theme);
  121. }
  122. final XmlCursor cur = obj.newCursor();
  123. Color color = null;
  124. try {
  125. XmlObject ch;
  126. for (int idx=0; color == null && (ch = nextObject(obj, cur, idx)) != null; idx++) {
  127. if (ch instanceof CTHslColor) {
  128. color = toColor((CTHslColor)ch);
  129. } else if (ch instanceof CTPresetColor) {
  130. color = toColor((CTPresetColor)ch);
  131. } else if (ch instanceof CTSchemeColor) {
  132. color = toColor((CTSchemeColor)ch, theme);
  133. } else if (ch instanceof CTScRgbColor) {
  134. color = toColor((CTScRgbColor)ch);
  135. } else if (ch instanceof CTSRgbColor) {
  136. color = toColor((CTSRgbColor)ch);
  137. } else if (ch instanceof CTSystemColor) {
  138. color = toColor((CTSystemColor)ch);
  139. } else if (!(ch instanceof CTFontReference) && idx > 0) {
  140. throw new IllegalArgumentException("Unexpected color choice: " + ch.getClass());
  141. }
  142. }
  143. } finally {
  144. cur.dispose();
  145. }
  146. return color;
  147. }
  148. private static XmlObject nextObject(XmlObject obj, XmlCursor cur, int idx) {
  149. switch (idx) {
  150. case 0:
  151. return obj;
  152. case 1:
  153. return cur.toFirstChild() ? cur.getObject() : null;
  154. default:
  155. return cur.toNextSibling() ? cur.getObject() : null;
  156. }
  157. }
  158. /**
  159. * Sets the solid color
  160. *
  161. * @param color solid color
  162. */
  163. @Internal
  164. protected void setColor(Color color) {
  165. if (!(_xmlObject instanceof CTSolidColorFillProperties)) {
  166. LOGGER.log(POILogger.ERROR, "XSLFColor.setColor currently only supports CTSolidColorFillProperties");
  167. return;
  168. }
  169. CTSolidColorFillProperties fill = (CTSolidColorFillProperties)_xmlObject;
  170. if (fill.isSetSrgbClr()) {
  171. fill.unsetSrgbClr();
  172. }
  173. if (fill.isSetScrgbClr()) {
  174. fill.unsetScrgbClr();
  175. }
  176. if (fill.isSetHslClr()) {
  177. fill.unsetHslClr();
  178. }
  179. if (fill.isSetPrstClr()) {
  180. fill.unsetPrstClr();
  181. }
  182. if (fill.isSetSchemeClr()) {
  183. fill.unsetSchemeClr();
  184. }
  185. if (fill.isSetSysClr()) {
  186. fill.unsetSysClr();
  187. }
  188. float[] rgbaf = color.getRGBComponents(null);
  189. boolean addAlpha = (rgbaf.length == 4 && rgbaf[3] < 1f);
  190. CTPositiveFixedPercentage alphaPct;
  191. // see office open xml part 4 - 5.1.2.2.30 and 5.1.2.2.32
  192. if (isInt(rgbaf[0]) && isInt(rgbaf[1]) && isInt(rgbaf[2])) {
  193. // sRGB has a gamma of 2.2
  194. CTSRgbColor rgb = fill.addNewSrgbClr();
  195. byte[] rgbBytes = {(byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()};
  196. rgb.setVal(rgbBytes);
  197. alphaPct = (addAlpha) ? rgb.addNewAlpha() : null;
  198. } else {
  199. CTScRgbColor rgb = fill.addNewScrgbClr();
  200. double[] scRGB = DrawPaint.RGB2SCRGB(color);
  201. rgb.setR((int)Math.rint(scRGB[0]*100_000d));
  202. rgb.setG((int)Math.rint(scRGB[1]*100_000d));
  203. rgb.setB((int)Math.rint(scRGB[2]*100_000d));
  204. alphaPct = (addAlpha) ? rgb.addNewAlpha() : null;
  205. }
  206. // alpha (%)
  207. if (alphaPct != null) {
  208. alphaPct.setVal((int)Math.rint(rgbaf[3]*100_000));
  209. }
  210. }
  211. /**
  212. * @return true, if this is an integer color value
  213. */
  214. private static boolean isInt(float f) {
  215. return Math.abs((f*255d) - Math.rint(f*255d)) < 0.00001;
  216. }
  217. private static int getRawValue(CTSchemeColor phClr, XmlObject xmlObject, String elem) {
  218. for (XmlObject obj : new XmlObject[]{xmlObject,phClr}) {
  219. if (obj == null) {
  220. continue;
  221. }
  222. XmlCursor cur = obj.newCursor();
  223. try {
  224. if (!(
  225. cur.toChild(XSLFRelation.NS_DRAWINGML, elem) ||
  226. (cur.toFirstChild() && cur.toChild(XSLFRelation.NS_DRAWINGML, elem))
  227. )) {
  228. continue;
  229. }
  230. String str = cur.getAttributeText(VAL_ATTR);
  231. if (str != null && !"".equals(str)) {
  232. return Integer.parseInt(str);
  233. }
  234. } finally {
  235. cur.dispose();
  236. }
  237. }
  238. return -1;
  239. }
  240. /**
  241. * Read a perecentage value from the supplied xml bean.
  242. * Example:
  243. * <a:tint val="45000"/>
  244. *
  245. * the returned value is 45
  246. *
  247. * @return the percentage value in the range [0 .. 100]
  248. */
  249. private int getPercentageValue(String elem){
  250. int val = getRawValue(_phClr, _xmlObject, elem);
  251. return (val == -1) ? val : (val / 1000);
  252. }
  253. /**
  254. * the opacity as expressed by a percentage value
  255. *
  256. * @return opacity in percents in the range [0..100]
  257. * or -1 if the value is not set
  258. */
  259. int getAlpha(){
  260. return getPercentageValue("alpha");
  261. }
  262. /**
  263. * the opacity as expressed by a percentage relative to the input color
  264. *
  265. * @return opacity in percents in the range [0..100]
  266. * or -1 if the value is not set
  267. */
  268. int getAlphaMod(){
  269. return getPercentageValue("alphaMod");
  270. }
  271. /**
  272. * the opacity as expressed by a percentage offset increase or decrease relative to
  273. * the input color. Increases will never increase the opacity beyond 100%, decreases will
  274. * never decrease the opacity below 0%.
  275. *
  276. * @return opacity shift in percents in the range [0..100]
  277. * or -1 if the value is not set
  278. */
  279. int getAlphaOff(){
  280. return getPercentageValue("alphaOff");
  281. }
  282. @SuppressWarnings("unused")
  283. int getHue(){
  284. int val = getRawValue(_phClr, _xmlObject, "hue");
  285. return (val == -1) ? val : (val / 60000);
  286. }
  287. @SuppressWarnings("unused")
  288. int getHueMod(){
  289. return getPercentageValue("hueMod");
  290. }
  291. @SuppressWarnings("unused")
  292. int getHueOff(){
  293. return getPercentageValue("hueOff");
  294. }
  295. /**
  296. * specifies the input color with the specified luminance,
  297. * but with its hue and saturation unchanged.
  298. *
  299. * @return luminance in percents in the range [0..100]
  300. * or -1 if the value is not set
  301. */
  302. @SuppressWarnings("unused")
  303. int getLum(){
  304. return getPercentageValue("lum");
  305. }
  306. /**
  307. * the luminance as expressed by a percentage relative to the input color
  308. *
  309. * @return luminance in percents in the range [0..100]
  310. * or -1 if the value is not set
  311. */
  312. int getLumMod(){
  313. return getPercentageValue("lumMod");
  314. }
  315. /**
  316. * the luminance shift as expressed by a percentage relative to the input color
  317. *
  318. * @return luminance shift in percents in the range [0..100]
  319. * or -1 if the value is not set
  320. */
  321. int getLumOff(){
  322. return getPercentageValue("lumOff");
  323. }
  324. /**
  325. * specifies the input color with the specified saturation,
  326. * but with its hue and luminance unchanged.
  327. *
  328. * @return saturation in percents in the range [0..100]
  329. * or -1 if the value is not set
  330. */
  331. int getSat(){
  332. return getPercentageValue("sat");
  333. }
  334. /**
  335. * the saturation as expressed by a percentage relative to the input color
  336. *
  337. * @return saturation in percents in the range [0..100]
  338. * or -1 if the value is not set
  339. */
  340. int getSatMod(){
  341. return getPercentageValue("satMod");
  342. }
  343. /**
  344. * the saturation shift as expressed by a percentage relative to the input color
  345. *
  346. * @return saturation shift in percents in the range [0..100]
  347. * or -1 if the value is not set
  348. */
  349. int getSatOff(){
  350. return getPercentageValue("satOff");
  351. }
  352. /**
  353. * specifies the input color with the specific red component, but with the blue and green color
  354. * components unchanged
  355. *
  356. * @return the value of the red component specified as a
  357. * percentage with 0% indicating minimal blue and 100% indicating maximum
  358. * or -1 if the value is not set
  359. */
  360. int getRed(){
  361. return getPercentageValue("red");
  362. }
  363. @SuppressWarnings("unused")
  364. int getRedMod(){
  365. return getPercentageValue("redMod");
  366. }
  367. @SuppressWarnings("unused")
  368. int getRedOff(){
  369. return getPercentageValue("redOff");
  370. }
  371. /**
  372. * specifies the input color with the specific green component, but with the red and blue color
  373. * components unchanged
  374. *
  375. * @return the value of the green component specified as a
  376. * percentage with 0% indicating minimal blue and 100% indicating maximum
  377. * or -1 if the value is not set
  378. */
  379. int getGreen(){
  380. return getPercentageValue("green");
  381. }
  382. @SuppressWarnings("unused")
  383. int getGreenMod(){
  384. return getPercentageValue("greenMod");
  385. }
  386. @SuppressWarnings("unused")
  387. int getGreenOff(){
  388. return getPercentageValue("greenOff");
  389. }
  390. /**
  391. * specifies the input color with the specific blue component, but with the red and green color
  392. * components unchanged
  393. *
  394. * @return the value of the blue component specified as a
  395. * percentage with 0% indicating minimal blue and 100% indicating maximum
  396. * or -1 if the value is not set
  397. */
  398. int getBlue(){
  399. return getPercentageValue("blue");
  400. }
  401. @SuppressWarnings("unused")
  402. int getBlueMod(){
  403. return getPercentageValue("blueMod");
  404. }
  405. @SuppressWarnings("unused")
  406. int getBlueOff(){
  407. return getPercentageValue("blueOff");
  408. }
  409. /**
  410. * specifies a darker version of its input color.
  411. * A 10% shade is 10% of the input color combined with 90% black.
  412. *
  413. * @return the value of the shade specified as a
  414. * percentage with 0% indicating minimal shade and 100% indicating maximum
  415. * or -1 if the value is not set
  416. */
  417. @SuppressWarnings("WeakerAccess")
  418. public int getShade(){
  419. return getPercentageValue("shade");
  420. }
  421. /**
  422. * specifies a lighter version of its input color.
  423. * A 10% tint is 10% of the input color combined with 90% white.
  424. *
  425. * @return the value of the tint specified as a
  426. * percentage with 0% indicating minimal tint and 100% indicating maximum
  427. * or -1 if the value is not set
  428. */
  429. public int getTint(){
  430. return getPercentageValue("tint");
  431. }
  432. private static class XSLFColorStyle extends AbstractColorStyle {
  433. private XmlObject xmlObject;
  434. private Color color;
  435. private CTSchemeColor phClr;
  436. XSLFColorStyle(XmlObject xmlObject, Color color, CTSchemeColor phClr) {
  437. this.xmlObject = xmlObject;
  438. this.color = color;
  439. this.phClr = phClr;
  440. }
  441. @Override
  442. public Color getColor() {
  443. return color;
  444. }
  445. @Override
  446. public int getAlpha() {
  447. return getRawValue(phClr, xmlObject, "alpha");
  448. }
  449. @Override
  450. public int getHueOff() {
  451. return getRawValue(phClr, xmlObject, "hueOff");
  452. }
  453. @Override
  454. public int getHueMod() {
  455. return getRawValue(phClr, xmlObject, "hueMod");
  456. }
  457. @Override
  458. public int getSatOff() {
  459. return getRawValue(phClr, xmlObject, "satOff");
  460. }
  461. @Override
  462. public int getSatMod() {
  463. return getRawValue(phClr, xmlObject, "satMod");
  464. }
  465. @Override
  466. public int getLumOff() {
  467. return getRawValue(phClr, xmlObject, "lumOff");
  468. }
  469. @Override
  470. public int getLumMod() {
  471. return getRawValue(phClr, xmlObject, "lumMod");
  472. }
  473. @Override
  474. public int getShade() {
  475. return getRawValue(phClr, xmlObject, "shade");
  476. }
  477. @Override
  478. public int getTint() {
  479. return getRawValue(phClr, xmlObject, "tint");
  480. }
  481. }
  482. }