From d6307ad1aac73908b208ae37ed7bf743a38eed1f Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Thu, 7 Oct 2010 13:55:46 +0000 Subject: [PATCH] support for BorderCode in HWPF, see Bugzilla 49919 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1005447 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 1 + .../apache/poi/hwpf/usermodel/BorderCode.java | 133 +++++++++++++++++- .../poi/hwpf/usermodel/CharacterRun.java | 5 + .../apache/poi/hwpf/usermodel/TableRow.java | 30 +++- .../poi/hwpf/usermodel/TestBorderCode.java | 113 +++++++++++++++ test-data/document/Bug49919.doc | Bin 0 -> 28672 bytes 6 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestBorderCode.java create mode 100644 test-data/document/Bug49919.doc diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 94691ec8f..c2e4bee92 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 49919 - support for BorderCode in HWPF 49908 - support for processing of symbols in HWPF 50022 - support for retrieving pictures from HSSF workbooks 50020 - Avoid IllegalStateException when creating Data validation in sheet with macro diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BorderCode.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BorderCode.java index 52747c17e..f705cc0a4 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BorderCode.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BorderCode.java @@ -17,13 +17,29 @@ package org.apache.poi.hwpf.usermodel; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.LittleEndian; +/** + * Mapping class for BRC80 structure (Border Code for Word 97) + * + *

Comments are copied out from the binary format specification. + */ public final class BorderCode implements Cloneable { + public static final int SIZE = 4; + private short _info; + private static final BitField _dptLineWidth = BitFieldFactory.getInstance(0x00ff); + private static final BitField _brcType = BitFieldFactory.getInstance(0xff00); + private short _info2; - + private static final BitField _ico = BitFieldFactory.getInstance(0x00ff); + private static final BitField _dptSpace = BitFieldFactory.getInstance(0x1f00); + private static final BitField _fShadow = BitFieldFactory.getInstance(0x2000); + private static final BitField _fFrame = BitFieldFactory.getInstance(0x4000); + public BorderCode() { } @@ -63,4 +79,119 @@ public final class BorderCode implements Cloneable { { return super.clone(); } + + /** + * Width of a single line in 1/8 pt, max of 32 pt. + */ + public int getLineWidth() { + return _dptLineWidth.getShortValue(_info); + } + + public void setLineWidth(int lineWidth) { + _dptLineWidth.setValue(_info, lineWidth); + } + + /** + * Border type code: + *

  • 0 none + *
  • 1 single + *
  • 2 thick + *
  • 3 double + *
  • 5 hairline + *
  • 6 dot + *
  • 7 dash large gap + *
  • 8 dot dash + *
  • 9 dot dot dash + *
  • 10 triple + *
  • 11 thin-thick small gap + *
  • 12 thick-thin small gap + *
  • 13 thin-thick-thin small gap + *
  • 14 thin-thick medium gap + *
  • 15 thick-thin medium gap + *
  • 16 thin-thick-thin medium gap + *
  • 17 thin-thick large gap + *
  • 18 thick-thin large gap + *
  • 19 thin-thick-thin large gap + *
  • 20 wave + *
  • 21 double wave + *
  • 22 dash small gap + *
  • 23 dash dot stroked + *
  • 24 emboss 3D + *
  • 25 engrave 3D + *
  • codes 64 – 230 represent border art types and are used only for page borders + */ + public int getBorderType() { + return _brcType.getShortValue(_info); + } + + public void setBorderType(int borderType) { + _brcType.setValue(_info, borderType); + } + + /** + * Color: + *
  • 0 Auto + *
  • 1 Black + *
  • 2 Blue + *
  • 3 Cyan + *
  • 4 Green + *
  • 5 Magenta + *
  • 6 Red + *
  • 7 Yellow + *
  • 8 White + *
  • 9 DkBlue + *
  • 10 DkCyan + *
  • 11 DkGreen + *
  • 12 DkMagenta + *
  • 13 DkRed + *
  • 14 DkYellow + *
  • 15 DkGray + *
  • 16 LtGray + */ + public short getColor() { + return _ico.getShortValue(_info2); + } + + public void setColor(short color) { + _ico.setValue(_info2, color); + } + + /** + * Width of space to maintain between border and text within border. + * + *

    Must be 0 when BRC is a substructure of TC. + * + *

    Stored in points. + */ + public int getSpace() { + return _dptSpace.getShortValue(_info2); + } + + public void setSpace(int space) { + _dptSpace.setValue(_info2, space); + } + + /** + * When true, border is drawn with shadow + * Must be false when BRC is a substructure of the TC. + */ + public boolean isShadow() { + return _fShadow.getValue(_info2) != 0; + } + + public void setShadow(boolean shadow) { + _fShadow.setValue(_info2, shadow ? 1 : 0); + } + + /** + * Don‘t reverse the border. + */ + public boolean isFrame() { + return _fFrame.getValue(_info2) != 0; + } + + public void setFrame(boolean frame) { + _fFrame.setValue(_info2, frame ? 1 : 0); + } + } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/CharacterRun.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/CharacterRun.java index 076b0029a..ccd5f81c2 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/CharacterRun.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/CharacterRun.java @@ -603,5 +603,10 @@ public final class CharacterRun } else throw new IllegalStateException("Not a symbol CharacterRun"); } + + public BorderCode getBorder() { + return _props.getBrc(); + } + } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java index a2a8d4676..97698ef6f 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java @@ -17,7 +17,6 @@ package org.apache.poi.hwpf.usermodel; -import org.apache.poi.hwpf.model.PropertyNode; import org.apache.poi.hwpf.sprm.TableSprmUncompressor; public final class TableRow @@ -140,4 +139,33 @@ public final class TableRow { return _cells[index]; } + + public BorderCode getTopBorder() { + return _tprops.getBrcBottom(); + } + + public BorderCode getBottomBorder() { + return _tprops.getBrcBottom(); + } + + public BorderCode getLeftBorder() { + return _tprops.getBrcLeft(); + } + + public BorderCode getRightBorder() { + return _tprops.getBrcRight(); + } + + public BorderCode getHorizontalBorder() { + return _tprops.getBrcHorizontal(); + } + + public BorderCode getVerticalBorder() { + return _tprops.getBrcVertical(); + } + + public BorderCode getBarBorder() { + throw new UnsupportedOperationException("not applicable for TableRow"); + } + } diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestBorderCode.java b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestBorderCode.java new file mode 100644 index 000000000..4adf93996 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestBorderCode.java @@ -0,0 +1,113 @@ +/* ==================================================================== + 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. +==================================================================== */ + +package org.apache.poi.hwpf.usermodel; + +import junit.framework.TestCase; + +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.hwpf.HWPFTestDataSamples; + +/** + * API for BorderCode, see Bugzill 49919 + */ +public final class TestBorderCode extends TestCase { + + private int pos = 0; + private Range range; + + public void test() { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile("Bug49919.doc"); + range = doc.getRange(); + + Paragraph par = findParagraph("Paragraph normal\r"); + assertEquals(0, par.getLeftBorder().getBorderType()); + assertEquals(0, par.getRightBorder().getBorderType()); + assertEquals(0, par.getTopBorder().getBorderType()); + assertEquals(0, par.getBottomBorder().getBorderType()); + + par = findParagraph("Paragraph with border\r"); + assertEquals(18, par.getLeftBorder().getBorderType()); + assertEquals(17, par.getRightBorder().getBorderType()); + assertEquals(18, par.getTopBorder().getBorderType()); + assertEquals(17, par.getBottomBorder().getBorderType()); + assertEquals(15, par.getLeftBorder().getColor()); + + par = findParagraph("Paragraph with red border\r"); + assertEquals(1, par.getLeftBorder().getBorderType()); + assertEquals(1, par.getRightBorder().getBorderType()); + assertEquals(1, par.getTopBorder().getBorderType()); + assertEquals(1, par.getBottomBorder().getBorderType()); + assertEquals(6, par.getLeftBorder().getColor()); + + par = findParagraph("Paragraph with bordered words.\r"); + assertEquals(0, par.getLeftBorder().getBorderType()); + assertEquals(0, par.getRightBorder().getBorderType()); + assertEquals(0, par.getTopBorder().getBorderType()); + assertEquals(0, par.getBottomBorder().getBorderType()); + + assertEquals(3, par.numCharacterRuns()); + CharacterRun chr = par.getCharacterRun(0); + assertEquals(0, chr.getBorder().getBorderType()); + chr = par.getCharacterRun(1); + assertEquals(1, chr.getBorder().getBorderType()); + assertEquals(0, chr.getBorder().getColor()); + chr = par.getCharacterRun(2); + assertEquals(0, chr.getBorder().getBorderType()); + + while (pos < range.numParagraphs() - 1) { + par = range.getParagraph(pos++); + if (par.isInTable()) + break; + } + + assertEquals(true, par.isInTable()); + Table tab = range.getTable(par); + + // Border could be defined for the entire row, or for each cell, with the same visual appearance. + assertEquals(2, tab.numRows()); + TableRow row = tab.getRow(0); + assertEquals(1, row.getLeftBorder().getBorderType()); + assertEquals(1, row.getRightBorder().getBorderType()); + assertEquals(1, row.getTopBorder().getBorderType()); + assertEquals(1, row.getBottomBorder().getBorderType()); + + assertEquals(2, row.numCells()); + TableCell cell = row.getCell(1); + assertEquals(3, cell.getBrcTop().getBorderType()); + + row = tab.getRow(1); + cell = row.getCell(0); + // 255 clears border inherited from row + assertEquals(255, cell.getBrcBottom().getBorderType()); + } + + private Paragraph findParagraph(String expectedText) { + while (pos < range.numParagraphs() - 1) { + Paragraph par = range.getParagraph(pos); + pos++; + if (par.text().equals(expectedText)) + return par; + } + + fail("Expected paragraph not found"); + + // should never come here + throw null; + } + +} diff --git a/test-data/document/Bug49919.doc b/test-data/document/Bug49919.doc new file mode 100644 index 0000000000000000000000000000000000000000..656872c948c1875d1479e0224fb8d0862c626429 GIT binary patch literal 28672 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 llpSBdBs6~W5yVoH48vTAP-3|5