001package com.hfg.xml.msofficexml.docx.wordprocessingml.style;
002
003import com.hfg.css.*;
004import com.hfg.graphics.units.GfxSize;
005import com.hfg.graphics.units.GfxUnits;
006import com.hfg.graphics.units.Pixels;
007import com.hfg.graphics.units.Points;
008import com.hfg.html.attribute.HTMLColor;
009import com.hfg.graphics.ColorUtil;
010import com.hfg.util.StringUtil;
011import com.hfg.xml.XMLName;
012import com.hfg.xml.XMLTag;
013import com.hfg.xml.msofficexml.docx.DocxException;
014import com.hfg.xml.msofficexml.docx.wordprocessingml.WmlXML;
015
016import java.awt.Color;
017import java.util.ArrayList;
018import java.util.List;
019import java.util.regex.Matcher;
020import java.util.regex.Pattern;
021
022
023// Section 17.3.4 (pg. 351) of Ecma Office Open XML Part 1
024
025public class WmlTableCellBorder
026{
027   private Position     mPosition;
028   private WmlLineBorderStyle mStyle;
029   private Integer      mSize;
030   private Integer      mSpace;
031   private Color        mColor;
032   private Boolean      mAutoColor;
033
034   public static enum Position {
035      top,
036      bottom,
037      left,
038      right,
039      start,
040      end,
041      insideH,
042      insideV,
043      tl2br,    // Top left to bottom right (diagonal)
044      tr2bl     // Top right to bottom left (diagonal)
045   }
046
047   private static final Pattern sizePattern = Pattern.compile("(\\d+)(px)?");
048
049   //---------------------------------------------------------------------------
050   public WmlTableCellBorder()
051   {
052      init();
053   }
054
055   //---------------------------------------------------------------------------
056   public WmlTableCellBorder(CSSDeclaration inCSSDeclaration)
057   {
058      this();
059
060      if (inCSSDeclaration.getProperty().equals(CSSProperty.border))
061      {
062         // Don't set a value for position. It will be expanded into top, bottom, left, right
063      }
064      else if (inCSSDeclaration.getProperty().equals(CSSProperty.border_top))
065      {
066         setPosition(Position.top);
067      }
068      else if (inCSSDeclaration.getProperty().equals(CSSProperty.border_bottom))
069      {
070         setPosition(Position.bottom);
071      }
072      else if (inCSSDeclaration.getProperty().equals(CSSProperty.border_left))
073      {
074         setPosition(Position.left);
075      }
076      else if (inCSSDeclaration.getProperty().equals(CSSProperty.border_right))
077      {
078         setPosition(Position.right);
079      }
080      else
081      {
082         throw new CSSException("The CSS declaration " + StringUtil.singleQuote(inCSSDeclaration.toString())
083                                + " cannot be used to construct a " + this.getClass().getSimpleName() + " object!");
084      }
085
086      // Ex: '1px solid blue'
087
088      boolean findSize  = true;
089      boolean findStyle = true;
090      boolean findColor = true;
091
092      String[] valuePieces = inCSSDeclaration.getValue().split("\\s+");
093      for (int i = 0; i < valuePieces.length; i++)
094      {
095         String token = valuePieces[i];
096
097         if (findSize)
098         {
099            findSize = false; // Size should always come first
100            Matcher m = sizePattern.matcher(token);
101            if (m.matches())
102            {
103               setSize(new Pixels(Integer.parseInt(m.group(1))));
104               continue;
105            }
106         }
107
108         if (findStyle)
109         {
110            findStyle = false;
111            CSSBorderStyle style = CSSBorderStyle.valueOf(token);
112            if (style != null)
113            {
114               WmlLineBorderStyle wmlStyle = WmlLineBorderStyle.valueOf(style);
115               if (wmlStyle != null)
116               {
117                  setStyle(wmlStyle);
118               }
119               continue;
120            }
121
122         }
123
124         if (findColor)
125         {
126            findColor = false;
127            if (token.equals("transparent"))
128            {
129               // I would set this to null but that seems to result in a black border
130               setColor(Color.WHITE);
131            }
132            else
133            {
134               HTMLColor color = HTMLColor.valueOf(token);
135               if (color != null)
136               {
137                  setColor(color);
138               }
139            }
140         }
141      }
142   }
143
144   //---------------------------------------------------------------------------
145   private void init()
146   {
147      // Set some default values
148      setStyle(WmlLineBorderStyle.single);
149      setSize(new Points(1));
150      setColor(Color.black);
151   }
152
153   //---------------------------------------------------------------------------
154   /**
155    * If a position is not specified via this method, a complete border set (top, end, bottom, and start) will be generated.
156    * @param inValue the border position
157    */
158   public WmlTableCellBorder setPosition(Position inValue)
159   {
160      mPosition = inValue;
161      return this;
162   }
163
164   //---------------------------------------------------------------------------
165   /**
166    * Specifies the width of the border in eighths of a point, with a minimum value
167    * of 1/4 of a point and a maximum value of twelve points.
168    * @param inValue width of the border
169    */
170   public WmlTableCellBorder setSize(GfxSize inValue)
171   {
172      int eighthOfAPointValue = (int) (inValue.to(GfxUnits.points) * 8);
173      if (eighthOfAPointValue < 2)
174      {
175         throw new DocxException("The border size must be between 1/4 to 12 points!");
176      }
177
178      mSize = eighthOfAPointValue;
179      return this;
180   }
181
182   //---------------------------------------------------------------------------
183   public WmlTableCellBorder setStyle(WmlLineBorderStyle inValue)
184   {
185      mStyle = inValue;
186      return this;
187   }
188
189   //---------------------------------------------------------------------------
190   public WmlTableCellBorder setColor(Color inValue)
191   {
192      mColor = inValue;
193      return this;
194   }
195
196   //---------------------------------------------------------------------------
197   public WmlTableCellBorder setAutoColor(boolean inValue)
198   {
199      mAutoColor = inValue;
200      return this;
201   }
202
203   //---------------------------------------------------------------------------
204   /**
205    * The padding (in points) between the text and the border.
206    * @param inValue padding (in points)
207    */
208   public WmlTableCellBorder setSpace(int inValue)
209   {
210      mSpace = inValue;
211      return this;
212   }
213
214   //---------------------------------------------------------------------------
215   public List<XMLTag> toXMLTags()
216   {
217      List<XMLTag> tags = new ArrayList<XMLTag>(4);
218
219      List<Position> positions =  new ArrayList<Position>(4);
220      if (mPosition != null)
221      {
222         positions.add(mPosition);
223      }
224      else
225      {
226         positions.add(Position.top);
227         // The spec says to use start & end but those seem to cause errors
228//         positions.add(Position.start);
229//         positions.add(Position.end);
230         positions.add(Position.left);
231         positions.add(Position.right);
232         positions.add(Position.bottom);
233      }
234
235      for (Position position : positions)
236      {
237         XMLTag tag = new XMLTag(new XMLName(position.name(), WmlXML.WORDPROCESSINGML_NAMESPACE));
238
239         if (mSize != null)
240         {
241            tag.setAttribute(WmlXML.SIZE_ATT, mSize);
242         }
243
244         if (mStyle != null)
245         {
246            tag.setAttribute(WmlXML.VALUE_ATT, mStyle.name());
247         }
248
249         if (mSpace != null)
250         {
251            tag.setAttribute(WmlXML.SPACE_ATT, mSpace);
252         }
253
254         if (mColor != null)
255         {
256            tag.setAttribute(WmlXML.COLOR_ATT, ColorUtil.colorToHex(mColor));
257         }
258         else if (mAutoColor != null
259               && mAutoColor)
260         {
261            tag.setAttribute(WmlXML.COLOR_ATT, "auto");
262         }
263
264         tags.add(tag);
265      }
266
267      return tags;
268   }
269}