From: Simon Steiner Date: Tue, 29 Dec 2020 13:33:34 +0000 (+0000) Subject: FOP-2536: Allow overpaint of PDF border X-Git-Tag: fop-2_6~5 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=1f2a414bb711508e158f9704cb6b5320cfd848e3;p=xmlgraphics-fop.git FOP-2536: Allow overpaint of PDF border Thanks to Jan Tošovský and Matthias Reischenbacher git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1884907 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/fop-core/src/main/java/org/apache/fop/apps/FOUserAgent.java b/fop-core/src/main/java/org/apache/fop/apps/FOUserAgent.java index 9b1566d2e..ffb92b027 100644 --- a/fop-core/src/main/java/org/apache/fop/apps/FOUserAgent.java +++ b/fop-core/src/main/java/org/apache/fop/apps/FOUserAgent.java @@ -831,4 +831,8 @@ public class FOUserAgent { return true; } } + + public boolean isTableBorderOverpaint() { + return factory.isTableBorderOverpaint(); + } } diff --git a/fop-core/src/main/java/org/apache/fop/apps/FopConfParser.java b/fop-core/src/main/java/org/apache/fop/apps/FopConfParser.java index 04b020277..f125dc6a3 100644 --- a/fop-core/src/main/java/org/apache/fop/apps/FopConfParser.java +++ b/fop-core/src/main/java/org/apache/fop/apps/FopConfParser.java @@ -56,6 +56,7 @@ import org.apache.fop.util.LogUtil; public class FopConfParser { private static final String PREFER_RENDERER = "prefer-renderer"; + private static final String TABLE_BORDER_OVERPAINT = "table-border-overpaint"; private final Log log = LogFactory.getLog(FopConfParser.class); @@ -265,6 +266,15 @@ public class FopConfParser { } } + if (cfg.getChild(TABLE_BORDER_OVERPAINT, false) != null) { + try { + fopFactoryBuilder.setTableBorderOverpaint( + cfg.getChild(TABLE_BORDER_OVERPAINT).getValueAsBoolean()); + } catch (ConfigurationException e) { + LogUtil.handleException(log, e, false); + } + } + // configure font manager new FontManagerConfigurator(cfg, baseURI, fopFactoryBuilder.getBaseURI(), resourceResolver) .configure(fopFactoryBuilder.getFontManager(), strict); diff --git a/fop-core/src/main/java/org/apache/fop/apps/FopFactory.java b/fop-core/src/main/java/org/apache/fop/apps/FopFactory.java index 35a0bc373..6708f2113 100644 --- a/fop-core/src/main/java/org/apache/fop/apps/FopFactory.java +++ b/fop-core/src/main/java/org/apache/fop/apps/FopFactory.java @@ -207,6 +207,10 @@ public final class FopFactory implements ImageContext { return userAgent.newFop(outputFormat, null); } + boolean isTableBorderOverpaint() { + return config.isTableBorderOverpaint(); + } + /** * Returns a new {@link Fop} instance. FOP will be configured with a default user agent * instance. Use this factory method if your output type requires an output stream. diff --git a/fop-core/src/main/java/org/apache/fop/apps/FopFactoryBuilder.java b/fop-core/src/main/java/org/apache/fop/apps/FopFactoryBuilder.java index 3fd8414fb..0e7498df2 100644 --- a/fop-core/src/main/java/org/apache/fop/apps/FopFactoryBuilder.java +++ b/fop-core/src/main/java/org/apache/fop/apps/FopFactoryBuilder.java @@ -327,6 +327,11 @@ public final class FopFactoryBuilder { return this; } + public FopFactoryBuilder setTableBorderOverpaint(boolean b) { + fopFactoryConfigBuilder.setTableBorderOverpaint(b); + return this; + } + public static class FopFactoryConfigImpl implements FopFactoryConfig { private final EnvironmentProfile enviro; @@ -368,6 +373,8 @@ public final class FopFactoryBuilder { private Map hyphPatNames; + private boolean tableBorderOverpaint; + private static final class ImageContextImpl implements ImageContext { private final FopFactoryConfig config; @@ -484,6 +491,10 @@ public final class FopFactoryBuilder { return isComplexScript; } + public boolean isTableBorderOverpaint() { + return tableBorderOverpaint; + } + public Map getHyphenationPatternNames() { return hyphPatNames; } @@ -529,6 +540,8 @@ public final class FopFactoryBuilder { void setComplexScriptFeaturesEnabled(boolean csf); void setHyphPatNames(Map hyphPatNames); + + void setTableBorderOverpaint(boolean b); } private static final class CompletedFopFactoryConfigBuilder implements FopFactoryConfigBuilder { @@ -613,6 +626,9 @@ public final class FopFactoryBuilder { throwIllegalStateException(); } + public void setTableBorderOverpaint(boolean b) { + throwIllegalStateException(); + } } private static final class ActiveFopFactoryConfigBuilder implements FopFactoryConfigBuilder { @@ -697,6 +713,10 @@ public final class FopFactoryBuilder { public void setHyphPatNames(Map hyphPatNames) { config.hyphPatNames = hyphPatNames; } + + public void setTableBorderOverpaint(boolean b) { + config.tableBorderOverpaint = b; + } } } diff --git a/fop-core/src/main/java/org/apache/fop/apps/FopFactoryConfig.java b/fop-core/src/main/java/org/apache/fop/apps/FopFactoryConfig.java index 69b7b25e9..cda0d2c10 100644 --- a/fop-core/src/main/java/org/apache/fop/apps/FopFactoryConfig.java +++ b/fop-core/src/main/java/org/apache/fop/apps/FopFactoryConfig.java @@ -163,6 +163,8 @@ public interface FopFactoryConfig { boolean isComplexScriptFeaturesEnabled(); + boolean isTableBorderOverpaint(); + /** @return the hyphenation pattern names */ Map getHyphenationPatternNames(); diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/OverPaintBorders.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/OverPaintBorders.java new file mode 100644 index 000000000..d1ebbd595 --- /dev/null +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/OverPaintBorders.java @@ -0,0 +1,273 @@ +/* + * 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. + */ + +/* $Id$ */ +package org.apache.fop.layoutmgr.table; + +import java.awt.Point; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.xmlgraphics.java2d.color.ColorUtil; + +import org.apache.fop.area.Block; +import org.apache.fop.area.Trait; +import org.apache.fop.traits.BorderProps; + +public class OverPaintBorders { + protected OverPaintBorders(Block curBlockArea) { + List newBlocks = new ArrayList(); + List childAreas = new ArrayList(curBlockArea.getChildAreas()); + Collections.sort(childAreas, new SortBlocksByXOffset()); + mergeBordersOfType(newBlocks, childAreas, new int[]{Trait.BORDER_BEFORE, Trait.BORDER_AFTER}); + Collections.sort(childAreas, new SortBlocksByYOffset()); + mergeBordersOfType(newBlocks, childAreas, new int[]{Trait.BORDER_START, Trait.BORDER_END}); + for (Block borderBlock : newBlocks) { + curBlockArea.addBlock(borderBlock); + } + } + + static class SortBlocksByXOffset implements Comparator, Serializable { + private static final long serialVersionUID = 5368454957520223766L; + public int compare(Object o1, Object o2) { + Block b1 = (Block) o1; + Block b2 = (Block) o2; + Integer paddingStart1 = (Integer) b1.getTrait(Trait.PADDING_START); + Integer paddingStart2 = (Integer) b2.getTrait(Trait.PADDING_START); + int x1 = b1.getXOffset() - (paddingStart1 != null ? paddingStart1 : 0); + int x2 = b2.getXOffset() - (paddingStart2 != null ? paddingStart2 : 0); + if (x1 > x2) { + return 1; + } else if (x1 < x2) { + return -1; + } else { + return Integer.compare(b1.getYOffset(), b2.getYOffset()); + } + } + } + + static class SortBlocksByYOffset implements Comparator, Serializable { + private static final long serialVersionUID = -1166133555737149237L; + public int compare(Object o1, Object o2) { + Block b1 = (Block) o1; + Block b2 = (Block) o2; + Integer paddingStart1 = (Integer) b1.getTrait(Trait.PADDING_START); + Integer paddingStart2 = (Integer) b2.getTrait(Trait.PADDING_START); + int x1 = b1.getXOffset() - (paddingStart1 != null ? paddingStart1 : 0); + int x2 = b2.getXOffset() - (paddingStart2 != null ? paddingStart2 : 0); + if (b1.getYOffset() > b2.getYOffset()) { + return 1; + } else if (b1.getYOffset() < b2.getYOffset()) { + return -1; + } else { + return Integer.compare(x1, x2); + } + } + } + + private void mergeBordersOfType(List newBlocks, List childAreas, int[] borderTraits) { + Map> mergeMap = new HashMap>(); + for (int traitType : borderTraits) { + mergeMap.put(traitType, null); + } + for (Object child : childAreas) { + Block childBlock = (Block) child; + BorderProps startBps = (BorderProps) childBlock.getTrait(Trait.BORDER_START); + BorderProps endBps = (BorderProps) childBlock.getTrait(Trait.BORDER_END); + BorderProps beforeBps = (BorderProps) childBlock.getTrait(Trait.BORDER_BEFORE); + BorderProps afterBps = (BorderProps) childBlock.getTrait(Trait.BORDER_AFTER); + for (int traitType : borderTraits) { + Block currBlock = childBlock; + BorderProps borderProps = (BorderProps) currBlock.getTrait(traitType); + if (borderProps == null) { + continue; + } + Map currTraitMap = mergeMap.get(traitType); + Point endPoint = getEndMiddlePoint(currBlock, traitType, startBps, endBps, beforeBps, afterBps); + BorderProps bpsCurr = (BorderProps) currBlock.getTrait(traitType); + Block prevBlock = null; + if (currTraitMap == null) { + currTraitMap = new HashMap(); + mergeMap.put(traitType, currTraitMap); + } else { + Point startPoint = getStartMiddlePoint(currBlock, traitType, startBps, endBps, beforeBps, afterBps); + for (Map.Entry entry : currTraitMap.entrySet()) { + Point prevEndPoint = entry.getKey(); + boolean isVertical = traitType == Trait.BORDER_START || traitType == Trait.BORDER_END; + boolean isHorizontal = traitType == Trait.BORDER_BEFORE || traitType == Trait.BORDER_AFTER; + if ((isHorizontal && prevEndPoint.y == startPoint.y && prevEndPoint.x >= startPoint.x) + || (isVertical && prevEndPoint.x == startPoint.x && prevEndPoint.y >= startPoint.y)) { + Block prevBlockCurr = entry.getValue(); + currTraitMap.remove(prevEndPoint); + BorderProps bpsPrev = (BorderProps) prevBlockCurr.getTrait(traitType); + if (canMergeBorders(bpsPrev, bpsCurr)) { + prevBlock = prevBlockCurr; + } + break; + } + } + } + Block borderBlock; + if (prevBlock != null && newBlocks.contains(prevBlock)) { + borderBlock = prevBlock; + } else { + borderBlock = new Block(); + borderBlock.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); + borderBlock.setPositioning(Block.ABSOLUTE); + borderBlock.setBidiLevel(currBlock.getBidiLevel()); + newBlocks.add(borderBlock); + BorderProps prevBeforeBps = (BorderProps) currBlock.getTrait(Trait.BORDER_BEFORE); + int prevBefore = prevBeforeBps != null ? prevBeforeBps.width : 0; + Integer prevPaddingStart = (Integer) currBlock.getTrait(Trait.PADDING_START); + Integer prevPaddingEnd = (Integer) currBlock.getTrait(Trait.PADDING_END); + Integer prevPaddingBefore = (Integer) currBlock.getTrait(Trait.PADDING_BEFORE); + Integer prevPaddingAfter = (Integer) currBlock.getTrait(Trait.PADDING_AFTER); + if (traitType == Trait.BORDER_START) { + borderBlock.setYOffset(currBlock.getYOffset() + prevBefore); + borderBlock.setXOffset(currBlock.getXOffset() + - (prevPaddingStart != null ? prevPaddingStart : 0)); + } else if (traitType == Trait.BORDER_END) { + borderBlock.setYOffset(currBlock.getYOffset() + prevBefore); + borderBlock.setXOffset(currBlock.getXOffset() + - (prevPaddingStart != null ? prevPaddingStart : 0)); + borderBlock.setIPD(currBlock.getIPD() + + (prevPaddingStart != null ? prevPaddingStart : 0) + + (prevPaddingEnd != null ? prevPaddingEnd : 0)); + } else if (traitType == Trait.BORDER_BEFORE) { + borderBlock.setYOffset(currBlock.getYOffset()); + borderBlock.setXOffset(currBlock.getXOffset() + - (prevPaddingStart != null ? prevPaddingStart : 0)); + } else if (traitType == Trait.BORDER_AFTER) { + borderBlock.setYOffset(currBlock.getYOffset() + prevBefore); + borderBlock.setXOffset(currBlock.getXOffset() + - (prevPaddingStart != null ? prevPaddingStart : 0)); + borderBlock.setBPD(currBlock.getBPD() + + (prevPaddingBefore != null ? prevPaddingBefore : 0) + + (prevPaddingAfter != null ? prevPaddingAfter : 0)); + } + } + Integer paddingEnd = (Integer) currBlock.getTrait(Trait.PADDING_END); + Integer paddingAfter = (Integer) currBlock.getTrait(Trait.PADDING_AFTER); + if (traitType == Trait.BORDER_BEFORE || traitType == Trait.BORDER_AFTER) { + int newEndPoint = currBlock.getXOffset() + currBlock.getIPD() + + (paddingEnd != null ? paddingEnd : 0); + borderBlock.setIPD(newEndPoint - borderBlock.getXOffset()); + } else if (traitType == Trait.BORDER_START || traitType == Trait.BORDER_END) { + int newEndPoint = currBlock.getYOffset() + currBlock.getBPD() + + currBlock.getBorderAndPaddingWidthBefore() + + (paddingAfter != null ? paddingAfter : 0); + borderBlock.setBPD(newEndPoint - borderBlock.getYOffset()); + } + BorderProps newBps = new BorderProps(bpsCurr.style, bpsCurr.width, 0, 0, + bpsCurr.color, bpsCurr.getMode()); + borderBlock.addTrait(traitType, newBps); + currBlock = borderBlock; + currTraitMap.put(endPoint, currBlock); + } + } + } + + private boolean canMergeBorders(BorderProps bpsPrev, BorderProps bpsCurr) { + return bpsPrev.style == bpsCurr.style + && ColorUtil.isSameColor(bpsPrev.color, bpsCurr.color) + && bpsPrev.width == bpsCurr.width + && bpsPrev.getMode() == bpsPrev.getMode() + && bpsPrev.getRadiusEnd() == 0 + && bpsCurr.getRadiusStart() == 0; + } + + private Point getEndMiddlePoint(Block block, int borderTrait, BorderProps startBps, + BorderProps endBps, BorderProps beforeBps, BorderProps afterBps) { + int x; + int y; + if (borderTrait == Trait.BORDER_START) { + Integer paddingStart = (Integer) block.getTrait(Trait.PADDING_START); + x = block.getXOffset() + - (paddingStart != null ? paddingStart : 0) + - BorderProps.getClippedWidth(startBps); + y = block.getYOffset() + block.getBPD() + + block.getBorderAndPaddingWidthBefore() + + block.getBorderAndPaddingWidthAfter(); + } else if (borderTrait == Trait.BORDER_END) { + Integer paddingEnd = (Integer) block.getTrait(Trait.PADDING_END); + x = block.getXOffset() + block.getIPD() + + (paddingEnd != null ? paddingEnd : 0) + + BorderProps.getClippedWidth(endBps); + y = block.getYOffset() + block.getBPD() + + block.getBorderAndPaddingWidthBefore() + + block.getBorderAndPaddingWidthAfter(); + } else if (borderTrait == Trait.BORDER_AFTER) { + Integer paddingEnd = (Integer) block.getTrait(Trait.PADDING_END); + x = block.getXOffset() + block.getIPD() + + (paddingEnd != null ? paddingEnd : 0) + + BorderProps.getClippedWidth(endBps); + Integer paddingAfter = (Integer) block.getTrait(Trait.PADDING_AFTER); + y = block.getYOffset() + block.getBPD() + + block.getBorderAndPaddingWidthBefore() + + (paddingAfter != null ? paddingAfter : 0) + + BorderProps.getClippedWidth(afterBps); + } else if (borderTrait == Trait.BORDER_BEFORE) { + Integer paddingEnd = (Integer) block.getTrait(Trait.PADDING_END); + x = block.getXOffset() + block.getIPD() + + (paddingEnd != null ? paddingEnd : 0) + + BorderProps.getClippedWidth(endBps); + y = block.getYOffset() + + BorderProps.getClippedWidth(beforeBps); + } else { + throw new IllegalArgumentException("Invalid trait: " + borderTrait); + } + return new Point(x, y); + } + + private Point getStartMiddlePoint(Block block, int borderTrait, BorderProps startBps, BorderProps endBps, + BorderProps beforeBps, BorderProps afterBps) { + int x; + int y; + if (borderTrait == Trait.BORDER_START) { + Integer paddingStart = (Integer) block.getTrait(Trait.PADDING_START); + x = block.getXOffset() + - (paddingStart != null ? paddingStart : 0) + - BorderProps.getClippedWidth(startBps); + y = block.getYOffset(); + } else if (borderTrait == Trait.BORDER_BEFORE) { + x = block.getXOffset() - block.getBorderAndPaddingWidthStart(); + y = block.getYOffset() + + BorderProps.getClippedWidth(beforeBps); + } else if (borderTrait == Trait.BORDER_END) { + Integer paddingEnd = (Integer) block.getTrait(Trait.PADDING_END); + x = block.getXOffset() + block.getIPD() + + (paddingEnd != null ? paddingEnd : 0) + + BorderProps.getClippedWidth(endBps); + y = block.getYOffset(); + } else if (borderTrait == Trait.BORDER_AFTER) { + x = block.getXOffset() - block.getBorderAndPaddingWidthStart(); + Integer paddingAfter = (Integer) block.getTrait(Trait.PADDING_AFTER); + y = block.getYOffset() + block.getBorderAndPaddingWidthBefore() + + block.getBPD() + + (paddingAfter != null ? paddingAfter : 0) + + BorderProps.getClippedWidth(afterBps); + } else { + throw new IllegalArgumentException("Invalid trait: " + borderTrait); + } + return new Point(x, y); + } +} diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java index 453f169e1..b4b49b705 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java @@ -381,6 +381,11 @@ public class TableLayoutManager extends SpacedBorderedPaddedBlockLayoutManager lc.setRefIPD(getContentAreaIPD()); contentLM.setStartXOffset(startXOffset); contentLM.addAreas(parentIter, lc); + + if (fobj.getUserAgent().isTableBorderOverpaint()) { + new OverPaintBorders(curBlockArea); + } + tableHeight += contentLM.getUsedBPD(); curBlockArea.setBPD(tableHeight); diff --git a/fop-core/src/main/java/org/apache/fop/traits/BorderProps.java b/fop-core/src/main/java/org/apache/fop/traits/BorderProps.java index d230a02b2..06812ed84 100644 --- a/fop-core/src/main/java/org/apache/fop/traits/BorderProps.java +++ b/fop-core/src/main/java/org/apache/fop/traits/BorderProps.java @@ -120,6 +120,13 @@ public class BorderProps implements Serializable { return radiusEnd; } + /** + * @return the border mode ((one of SEPARATE, COLLAPSE_INNER and COLLAPSE_OUTER) + */ + public Mode getMode() { + return mode; + } + /** * @param bp the border properties or null * @return the effective width of the clipped part of the border diff --git a/fop-core/src/test/java/org/apache/fop/apps/MutableConfig.java b/fop-core/src/test/java/org/apache/fop/apps/MutableConfig.java index 2f70f4124..04b760e74 100644 --- a/fop-core/src/test/java/org/apache/fop/apps/MutableConfig.java +++ b/fop-core/src/test/java/org/apache/fop/apps/MutableConfig.java @@ -133,6 +133,10 @@ public final class MutableConfig implements FopFactoryConfig { return delegate.isComplexScriptFeaturesEnabled(); } + public boolean isTableBorderOverpaint() { + return delegate.isTableBorderOverpaint(); + } + public Map getHyphenationPatternNames() { return delegate.getHyphenationPatternNames(); } diff --git a/fop-core/src/test/java/org/apache/fop/intermediate/TestAssistant.java b/fop-core/src/test/java/org/apache/fop/intermediate/TestAssistant.java index d13f88959..4f56f9c7d 100644 --- a/fop-core/src/test/java/org/apache/fop/intermediate/TestAssistant.java +++ b/fop-core/src/test/java/org/apache/fop/intermediate/TestAssistant.java @@ -122,6 +122,7 @@ public class TestAssistant { FopFactoryBuilder builder = new FopFactoryBuilder(envProfile); builder.setStrictFOValidation(isStrictValidation(testDoc)); builder.getFontManager().setBase14KerningEnabled(isBase14KerningEnabled(testDoc)); + builder.setTableBorderOverpaint(isTableBorderOverpaint(testDoc)); return builder.build(); } @@ -144,6 +145,16 @@ public class TestAssistant { } } + private boolean isTableBorderOverpaint(Document testDoc) { + try { + XObject xo = XPathAPI.eval(testDoc, "/testcase/cfg/table-border-overpaint"); + String s = xo.str(); + return "true".equalsIgnoreCase(s); + } catch (TransformerException e) { + throw new RuntimeException("Error while evaluating XPath expression", e); + } + } + /** * Loads a test case into a DOM document. * @param testFile the test file diff --git a/fop/test/layoutengine/standard-testcases/table-border-rendering-mode-overpaint.xml b/fop/test/layoutengine/standard-testcases/table-border-rendering-mode-overpaint.xml new file mode 100644 index 000000000..cd0f91395 --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/table-border-rendering-mode-overpaint.xml @@ -0,0 +1,180 @@ + + + + + +

+ This test checks if table borders are overpainted. +

+
+ + true + + + + + + + + + + + + + + + + + + + + + + + Color Name + + + Color Value + + + + + RGB + + + CMYK + + + + + R + + + G + + + B + + + C + + + M + + + Y + + + K + + + + + + + Red + + + 255 + + + 0 + + + 0 + + + 0.00 + + + 1.00 + + + 1.00 + + + 0.00 + + + + + Green + + + 0 + + + 255 + + + 0 + + + 1.00 + + + 0.00 + + + 1.00 + + + 0.00 + + + + + Blue + + + 0 + + + 0 + + + 255 + + + 1.00 + + + 1.00 + + + 0.00 + + + 0.00 + + + + + + + + + + + + + + + + +
diff --git a/fop/test/layoutengine/standard-testcases/table-border-rendering-mode-standard.xml b/fop/test/layoutengine/standard-testcases/table-border-rendering-mode-standard.xml new file mode 100644 index 000000000..397f0fe15 --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/table-border-rendering-mode-standard.xml @@ -0,0 +1,173 @@ + + + + + +

+ This test checks if table borders are rendered without overpainting. +

+
+ + + + + + + + + + + + + + + + + + + + + + Color Name + + + Color Value + + + + + RGB + + + CMYK + + + + + R + + + G + + + B + + + C + + + M + + + Y + + + K + + + + + + + Red + + + 255 + + + 0 + + + 0 + + + 0.00 + + + 1.00 + + + 1.00 + + + 0.00 + + + + + Green + + + 0 + + + 255 + + + 0 + + + 1.00 + + + 0.00 + + + 1.00 + + + 0.00 + + + + + Blue + + + 0 + + + 0 + + + 255 + + + 1.00 + + + 1.00 + + + 0.00 + + + 0.00 + + + + + + + + + + + + +