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