1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // DJP ToDo: need way of specifying fg/bg colors on ws->DOM
25 
26 package org.openoffice.xmerge.converter.xml;
27 
28 import java.awt.Color;
29 
30 import org.w3c.dom.NodeList;
31 import org.w3c.dom.Node;
32 import org.w3c.dom.NamedNodeMap;
33 import org.w3c.dom.Element;
34 
35 import org.openoffice.xmerge.util.Debug;
36 
37 /**
38  *  Represents a text <code>Style</code> in an OpenOffice document.
39  *
40  *  @author   David Proulx
41  */
42 public class TextStyle extends Style implements Cloneable {
43 
44     final protected static int FIRST_ATTR = 0x01;
45     /** Indicates <i>bold</i> text. */
46     final public static int BOLD        = 0x01;
47     /** Indicates <i>italic</i> text. */
48     final public static int ITALIC      = 0x02;
49     /** Indicates <i>underlined</i> text. */
50     final public static int UNDERLINE   = 0x04;
51     /** Indicates <i>strike-through</i> in the text. */
52     final public static int STRIKETHRU  = 0x08;
53     /** Indicates <i>superscripted</i> text. */
54     final public static int SUPERSCRIPT = 0x10;
55     /** Indicates <i>subscripted</i> text. */
56     final public static int SUBSCRIPT   = 0x20;
57     /** Indicates the last attribute. */
58     final protected static int LAST_ATTR = 0x20;
59 
60     /** Values of text attributes. */
61     protected int values = 0;
62     /** Bitwise mask of text attributes. */
63     protected int mask = 0;
64 
65     /** Font size in points. */
66     protected int sizeInPoints = 0;
67     /** Font name. */
68     protected String fontName = null;
69     /** Font <code>Color</code>. */
70     protected Color fontColor = null;
71     /** Background <code>Color</code>. */
72     protected Color bgColor = null;
73 
74     /**
75      *  Constructor for use when going from DOM to client device format.
76      *
77      *  @param  node  The <i>style:style</i> <code>Node</code> containing
78      *                the <code>Style</code>.  (This <code>Node</code> is
79      *                assumed have a <i>family</i> attribute of <i>text</i>).
80      *  @param  sc    The <code>StyleCatalog</code>, which is used for
81      *                looking up ancestor <code>Style</code> objects.
82      */
TextStyle(Node node, StyleCatalog sc)83     public TextStyle(Node node, StyleCatalog sc) {
84         super(node, sc);
85 
86         // Run through the attributes of this node, saving
87         // the ones we're interested in.
88         NamedNodeMap attrNodes = node.getAttributes();
89         if (attrNodes != null) {
90             int len = attrNodes.getLength();
91             for (int i = 0; i < len; i++) {
92                 Node attr = attrNodes.item(i);
93                 handleAttribute(attr.getNodeName(), attr.getNodeValue());
94             }
95         }
96 
97         // Look for children.  Only ones we care about are "style:properties"
98         // nodes.  If any are found, recursively traverse them, passing
99         // along the style element to add properties to.
100         if (node.hasChildNodes()) {
101             NodeList children = node.getChildNodes();
102             int len = children.getLength();
103             for (int i = 0; i < len; i++) {
104                 Node child = children.item(i);
105                 String name = child.getNodeName();
106                 if (name.equals("style:properties")) {
107                     NamedNodeMap childAttrNodes = child.getAttributes();
108                     if (childAttrNodes != null) {
109                         int nChildAttrNodes = childAttrNodes.getLength();
110                         for (int j = 0; j < nChildAttrNodes; j++) {
111                             Node attr = childAttrNodes.item(j);
112                             handleAttribute(attr.getNodeName(),
113                             attr.getNodeValue());
114                         }
115                     }
116                 }
117             }
118         }
119     }
120 
121 
122     /**
123      *  Constructor for use when going from client device format to DOM
124      *
125      *  @param  name     Name of text <code>Style</code>.  Can be null.
126      *  @param  family   Family of text <code>Style</code> (usually
127      *                   <i>text</i>).  Can be null.
128      *  @param  parent   Name of parent text <code>Style</code>, or null
129      *                   for none.
130      *  @param  mask     Bitwise mask of text attributes that this text
131      *                   <code>Style</code> will specify.  Can be any
132      *                   combination of the following, or'ed together:
133      *                   {@link #BOLD}, {@link #ITALIC}, {@link #UNDERLINE},
134      *                   {@link #STRIKETHRU}, {@link #SUPERSCRIPT},
135      *                   {@link #SUBSCRIPT}.  This parameter determines what
136      *                   attributes this <code>Style</code> will specify.
137      *                   When an attribute is specified in a
138      *                   <code>Style</code>, its value can be either
139      *                   <i>on</i> or <i>off</i>.  The on/off value for
140      *                   each attribute is controlled by the
141      *                   <code>values</code> parameter.
142      *  @param values    Values of text attributes that this text
143      *                   <code>Style</code> will be setting.  Any of the
144      *                   attributes ({@link #BOLD}, etc) listed for
145      *                   <code>mask</code> can be used for this.
146      *  @param fontSize  Font size in points.
147      *  @param fontName  Name of font.
148      *  @param sc        The <code>StyleCatalog</code>, which is used for
149      *                   looking up ancestor <code>Style</code> objects.
150      */
TextStyle(String name, String family, String parent, int mask, int values, int fontSize, String fontName, StyleCatalog sc)151     public TextStyle(String name, String family, String parent,
152     int mask, int values, int fontSize, String fontName, StyleCatalog sc) {
153         super(name, family, parent, sc);
154         this.mask = mask;
155         this.values = values;
156         this.sizeInPoints = fontSize;
157         this.fontName = fontName;
158     }
159 
160 
161     /**
162      *  Parse a color specification of the form <i>#rrggbb</i>
163      *
164      *  @param  value  <code>Color</code> specification to parse.
165      *
166      *  @return  The <code>Color</code> associated the value.
167      */
parseColorString(String value)168     private Color parseColorString(String value) {
169         // Assume color value is of form #rrggbb
170         String r = value.substring(1, 3);
171         String g = value.substring(3, 5);
172         String b = value.substring(5, 7);
173         int red = 0;
174         int green = 0;
175         int blue = 0;
176         try {
177             red = Integer.parseInt(r, 16);
178             green = Integer.parseInt(g, 16);
179             blue = Integer.parseInt(b, 16);
180         } catch (NumberFormatException e) {
181             Debug.log(Debug.ERROR, "Problem parsing a color string", e);
182         }
183         return new Color(red, green, blue);
184     }
185 
186 
187     /**
188      *  Set an attribute.
189      *
190      *  @param  attr   The attribute to set.
191      *  @param  value  The attribute value to set.
192      */
handleAttribute(String attr, String value)193     private void handleAttribute(String attr, String value) {
194 
195         if (attr.equals("fo:font-weight")) {
196             if (value.equals("bold")) turnAttributesOn(BOLD);
197             else if (value.equals("normal")) turnAttributesOff(BOLD);
198         }
199 
200         else if (attr.equals("fo:font-style")) {
201             if (value.equals("italic")) turnAttributesOn(ITALIC);
202             else if (value.equals("oblique")) turnAttributesOn(ITALIC);
203             else if (value.equals("normal")) turnAttributesOff(ITALIC);
204         }
205 
206         else if (attr.equals("style:text-underline")) {
207             if (value.equals("none"))
208                 turnAttributesOff(UNDERLINE);
209             else
210                 turnAttributesOn(UNDERLINE);
211         }
212 
213         else if (attr.equals("style:text-crossing-out")) {
214             if (value.equals("none"))
215                 turnAttributesOff(STRIKETHRU);
216             else
217                 turnAttributesOn(STRIKETHRU);
218         }
219 
220         else if (attr.equals("style:text-position")) {
221             if (value.startsWith("super "))
222                 turnAttributesOn(SUPERSCRIPT);
223             else if (value.startsWith("sub "))
224                 turnAttributesOn(SUBSCRIPT);
225             else if (value.startsWith("0% "))
226                 turnAttributesOff(SUPERSCRIPT | SUBSCRIPT);
227             else {
228                 String firstPart = value.substring(0, value.indexOf(" "));
229                 if (firstPart.endsWith("%")) {
230                     firstPart = firstPart.substring(0, value.indexOf("%"));
231                     int amount;
232                     try {
233                         amount = Integer.parseInt(firstPart);
234                     } catch (NumberFormatException e) {
235                         amount = 0;
236                         Debug.log(Debug.ERROR, "Problem with style:text-position tag", e);
237                     }
238                     if (amount < 0) turnAttributesOn(SUBSCRIPT);
239                     else if (amount > 0) turnAttributesOn(SUPERSCRIPT);
240                 }
241             }
242         }
243 
244         else if (attr.equals("fo:font-size")) {
245             if (value.endsWith("pt")) {
246                 String num = value.substring(0, value.length() - 2);
247                 sizeInPoints = Integer.parseInt(num);
248             }
249         }
250 
251         else if (attr.equals("style:font-name"))
252             fontName = value;
253 
254         else if (attr.equals("fo:color"))
255             fontColor = parseColorString(value);
256 
257         else if (attr.equals("style:text-background-color"))
258             bgColor = parseColorString(value);
259 
260         else if (isIgnored(attr)) {}
261 
262         else {
263             Debug.log(Debug.INFO, "TextStyle Unhandled: " + attr + "=" + value);
264         }
265     }
266 
267 
268     /**
269      *  Return true if text <code>attribute</code> is set in this
270      *  <code>Style</code>.  An attribute that is set may have a
271      *  value of <i>on</i> or <i>off</i>.
272      *
273      *  @param  attribute  The attribute to check ({@link #BOLD},
274      *                     {@link #ITALIC}, etc.).
275      *
276      *  @return  true if text <code>attribute</code> is set in this
277      *           <code>Style</code>, false otherwise.
278      */
isSet(int attribute)279     public boolean isSet(int attribute) {
280         return (!((mask & attribute) == 0));
281     }
282 
283 
284     /**
285      *  Return true if the <code>attribute</code> is set to <i>on</i>
286      *
287      *  @param  attribute  Attribute to check ({@link #BOLD},
288      *                     {@link #ITALIC}, etc.)
289      *
290      *  @return  true if <code>attribute</code> is set to <i>on</i>,
291      *           otherwise false.
292      */
getAttribute(int attribute)293     public boolean getAttribute(int attribute) {
294         if ((mask & attribute) == 0)
295             return false;
296         return (!((values & attribute) == 0));
297     }
298 
299 
300     /**
301      *  Return the font size for this <code>Style</code>.
302      *
303      *  @return  The font size in points
304      */
getFontSize()305     public int getFontSize() {
306         return sizeInPoints;
307     }
308 
309 
310     /**
311      *  Return the name of the font for this <code>Style</code>.
312      *
313      *  @return  Name of font, or null if no font is specified by
314      *          this <code>Style</code>.
315      */
getFontName()316     public String getFontName() {
317         return fontName;
318     }
319 
320 
321     /**
322      *  Return the font <code>Color</code> for this <code>Style</code>.
323      *  Can be null if none was specified.
324      *
325      *  @return  <code>Color</code> value for this <code>Style</code>.
326      *           Can be null.
327      */
getFontColor()328     public Color getFontColor() {
329         return fontColor;
330     }
331 
332 
333     /**
334      *  Return the background <code>Color</code> for this
335      *  <code>Style</code>.  Can be null if none was specified.
336      *
337      *  @return  Background <code>Color</code> value for this
338      *           <code>Style</code>.  Can be null.
339      */
getBackgroundColor()340     public Color getBackgroundColor() {
341         return bgColor;
342     }
343 
344 
345     /**
346      *  Set the font and/or background <code>Color</code> for this
347      *  <code>Style</code>.
348      *
349      *  @param  fontColor        The font <code>Color</code> to set.
350      *  @param  backgroundColor  The background <code>Color</code> to set.
351      */
setColors(Color fontColor, Color backgroundColor)352     public void setColors(Color fontColor, Color backgroundColor) {
353         if (fontColor != null)
354             this.fontColor = fontColor;
355         if (backgroundColor != null)
356             this.bgColor = backgroundColor;
357     }
358 
359 
360     /**
361      *  Return a <code>Style</code> object corresponding to this one,
362      *  but with all of the inherited information from parent
363      *  <code>Style</code> objects filled in.  The object returned will
364      *  be a new object, not a reference to this object, even if it does
365      *  not need any information added.
366      *
367      *  @return  The <code>StyleCatalog</code> in which to look up
368      *           ancestors.
369      */
getResolved()370     public Style getResolved() {
371         // Create a new object to return, which is a clone of this one.
372         TextStyle resolved = null;
373         try {
374             resolved = (TextStyle)this.clone();
375         } catch (Exception e) {
376             Debug.log(Debug.ERROR, "Can't clone", e);
377         }
378 
379         // Look up the parentStyle.  (If there is no style catalog
380         // specified, we can't do any lookups.)
381         TextStyle parentStyle = null;
382         if (sc != null) {
383             if (parent != null) {
384                 parentStyle = (TextStyle)sc.lookup(parent, family, null,
385                            this.getClass());
386                 if (parentStyle == null)
387                     Debug.log(Debug.ERROR, "parent style lookup of "
388                       + parent + " failed!");
389                 else
390                     parentStyle = (TextStyle)parentStyle.getResolved();
391 
392             } else if (!name.equals("DEFAULT_STYLE")) {
393                 parentStyle = (TextStyle)sc.lookup("DEFAULT_STYLE", null,
394                     null, this.getClass());
395             }
396         }
397 
398         // If we found a parent, for any attributes which we don't have
399         // set, try to get the values from the parent.
400         if (parentStyle != null) {
401             parentStyle = (TextStyle)parentStyle.getResolved();
402 
403             if ((sizeInPoints == 0) && (parentStyle.sizeInPoints != 0))
404                 resolved.sizeInPoints = parentStyle.sizeInPoints;
405             if ((fontName == null) && (parentStyle.fontName != null))
406                 resolved.fontName = parentStyle.fontName;
407             if ((fontColor == null) && (parentStyle.fontColor != null))
408                 resolved.fontColor = parentStyle.fontColor;
409             if ((bgColor == null) && (parentStyle.bgColor != null))
410                 resolved.bgColor = parentStyle.bgColor;
411             for (int m = BOLD; m <= SUBSCRIPT; m = m << 1) {
412                 if (((mask & m) == 0) && ((parentStyle.mask & m) != 0)) {
413                     resolved.mask |= m;
414                     resolved.values |= (parentStyle.mask & m);
415                 }
416             }
417 
418         }
419         return resolved;
420     }
421 
422 
423     /**
424      *  Set one or more text attributes to <i>on</i>.
425      *
426      *  @param  flags  Flag values to set <i>on</i>.
427      */
turnAttributesOn(int flags)428     private void turnAttributesOn(int flags) {
429         mask |= flags;
430         values |= flags;
431     }
432 
433 
434     /**
435      *  Set one or more text attributes to <i>off</i>.
436      *
437      *  @param  flags  The flag values to set <i>off</i>.
438      */
turnAttributesOff(int flags)439     private void turnAttributesOff(int flags) {
440         mask |= flags;
441         values &= ~flags;
442     }
443 
444 
445     /**
446      *  Private function to return the value as an element in
447      *  a Comma Separated Value (CSV) format.
448      *
449      *  @param  value The value to format.
450      *
451      *  @return  The formatted value.
452      */
toCSV(String value)453     private static String toCSV(String value) {
454         if (value != null)
455             return "\"" + value + "\",";
456         else
457             return "\"\",";
458     }
459 
460 
461     /**
462      *  Private function to return the value as a last element in
463      *  a Comma Separated Value (CSV) format.
464      *
465      *  @param  value  The value to format.
466      *
467      *  @return  The formatted value.
468      */
toLastCSV(String value)469     private static String toLastCSV(String value) {
470         if (value != null)
471             return "\"" + value + "\"";
472         else
473             return "\"\"";
474     }
475 
476 
477     /**
478      *  Print a Comma Separated Value (CSV) header line for the
479      *  spreadsheet dump.
480      */
dumpHdr()481     public static void dumpHdr() {
482         System.out.println(toCSV("Name") + toCSV("Family") + toCSV("parent")
483         + toCSV("Font") + toCSV("Size")
484         + toCSV("Bold") + toCSV("Italic") + toCSV("Underline")
485         + toCSV("Strikethru") + toCSV("Superscript") + toLastCSV("Subscript"));
486     }
487 
488 
489     /**
490      *  Dump this <code>Style</code> as a Comma Separated Value (CSV) line.
491      */
dumpCSV()492     public void dumpCSV() {
493         String attributes = "";
494         for (int bitVal = 0x01; bitVal <= 0x20; bitVal = bitVal << 1) {
495             if ((bitVal & mask) != 0) {
496                 attributes += toCSV(((bitVal & values) != 0) ? "yes" : "no");
497             } else attributes += toCSV(null);  // unspecified
498         }
499         System.out.println(toCSV(name) + toCSV(family) + toCSV(parent)
500         + toCSV(fontName) + toCSV("" + sizeInPoints) + attributes + toLastCSV(null));
501     }
502 
503 
504     /**
505      *  Create a new <code>Node</code> in the <code>Document</code>, and
506      *  write this <code>Style</code> to it.
507      *
508      *  @param  parentDoc  Parent <code>Document</code> of the
509      *                    <code>Node</code> to create.
510      *  @param  name       Name to use for the new <code>Node</code> (e.g.
511      *                    <i>style:style</i>)
512      *
513      *  @return  Created <code>Node</code>.
514      */
createNode(org.w3c.dom.Document parentDoc, String name)515     public Node createNode(org.w3c.dom.Document parentDoc, String name) {
516         Element node = parentDoc.createElement(name);
517         writeAttributes(node);
518         return node;
519     }
520 
521 
522     /**
523      *  Return true if <code>style</code> specifies as much or less
524      *  than this <code>Style</code>, and nothing it specifies
525      *  contradicts this <code>Style</code>.
526      *
527      *  @param  style  The <code>Style</code> to check.
528      *
529 	 *  @return  true if <code>style</code> is a subset, false
530      *           otherwise.
531      */
isSubset(Style style)532     public boolean isSubset(Style style) {
533         if (style.getClass() != this.getClass())
534                 return false;
535         TextStyle tStyle = (TextStyle)style;
536 
537         if (tStyle.values != values)
538                 return false;
539 
540         if (tStyle.sizeInPoints != 0) {
541             if (sizeInPoints != tStyle.sizeInPoints)
542                 return false;
543         }
544 
545         if (tStyle.fontName != null) {
546             if (fontName == null)
547                 return false;
548             if (!fontName.equals(tStyle.fontName))
549                 return false;
550         }
551 
552         if (tStyle.fontColor != null) {
553             if (fontColor == null)
554                 return false;
555             if (!fontColor.equals(tStyle.fontColor))
556                 return false;
557         }
558 
559         if (tStyle.bgColor != null) {
560             if (bgColor == null)
561                 return false;
562             if (!bgColor.equals(tStyle.bgColor))
563                 return false;
564         }
565 
566         return true;
567     }
568 
569 
570     /**
571      *  Write this <code>Style</code> object's attributes to a
572      *  <code>Node</code> in the <code>Document</code>.
573      *
574      *  @param  node  The <code>Node</code> to add <code>Style</code>
575      *                attributes.
576      */
writeAttributes(Element node)577     public void writeAttributes(Element node) {
578 
579         if ((mask & BOLD) != 0)
580             if ((values & BOLD) != 0)
581                 node.setAttribute("fo:font-weight", "bold");
582 
583         if ((mask & ITALIC) != 0)
584             if ((values & ITALIC) != 0)
585                 node.setAttribute("fo:font-style", "italic");
586 
587         if ((mask & UNDERLINE) != 0)
588             if ((values & UNDERLINE) != 0)
589                 node.setAttribute("style:text-underline", "single");
590 
591         if ((mask & STRIKETHRU) != 0)
592             if ((values & STRIKETHRU) != 0)
593                 node.setAttribute("style:text-crossing-out", "single-line");
594 
595         if ((mask & SUPERSCRIPT) != 0)
596             if ((values & SUPERSCRIPT) != 0)
597                 node.setAttribute("style:text-position", "super 58%");
598 
599         if ((mask & SUBSCRIPT) != 0)
600             if ((values & SUBSCRIPT) != 0)
601                 node.setAttribute("style:text-position", "sub 58%");
602 
603         if (sizeInPoints != 0) {
604             Integer fs = new Integer(sizeInPoints);
605             node.setAttribute("fo:font-size", fs.toString() + "pt");
606         }
607 
608         if (fontName != null)
609             node.setAttribute("style:font-name", fontName);
610 
611         if (fontColor != null)
612             node.setAttribute("fo:color", buildColorString(fontColor));
613 
614         if (bgColor != null)
615             node.setAttribute("style:text-background-color",
616                               buildColorString(bgColor));
617     }
618 
619 
620     /**
621      *  Given a <code>Color</code>, return a string of the form
622      *  <i>#rrggbb</i>.
623      *
624      *  @param  c  The <code>Color</code> value.
625      *
626      *  @return  The <code>Color</code> value in the form <i>#rrggbb</i>.
627      */
buildColorString(Color c)628     private String buildColorString(Color c) {
629         int v[] = new int[3];
630         v[0] = c.getRed();
631         v[1] = c.getGreen();
632         v[2] = c.getBlue();
633         String colorString = new String("#");
634         for (int i = 0; i <= 2; i++) {
635             String xx = Integer.toHexString(v[i]);
636             if (xx.length() < 2)
637         xx = "0" + xx;
638             colorString += xx;
639     }
640         return colorString;
641     }
642 
643 
644     private static String[] ignored = {
645         "style:text-autospace",  "style:text-underline-color",
646         "fo:margin-left", "fo:margin-right", "fo:text-indent",
647         "fo:margin-top", "fo:margin-bottom", "text:line-number",
648         "text:number-lines", "style:country-asian",
649         "style:font-size-asian", "style:font-name-complex",
650         "style:language-complex", "style:country-complex",
651         "style:font-size-complex", "style:punctuation-wrap",
652         "fo:language", "fo:country",
653         "style:font-name-asian", "style:language-asian",
654         "style:line-break", "fo:keep-with-next"
655     };
656 
657 
658     /*
659      * This code checks whether an attribute is one that we
660      * intentionally ignore.
661      *
662      *  @param  attribute  The attribute to check.
663      *
664      *  @return  true if <code>attribute</code> can be ignored,
665      *           otherwise false.
666      */
isIgnored(String attribute)667     private boolean isIgnored(String attribute) {
668         for (int i = 0; i < ignored.length; i++) {
669             if (ignored[i].equals(attribute))
670                 return true;
671         }
672         return false;
673     }
674 }
675 
676