You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

HSSFShape.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.hssf.usermodel;
  16. import java.io.ByteArrayOutputStream;
  17. import java.io.IOException;
  18. import org.apache.logging.log4j.LogManager;
  19. import org.apache.logging.log4j.Logger;
  20. import org.apache.poi.ddf.EscherBoolProperty;
  21. import org.apache.poi.ddf.EscherChildAnchorRecord;
  22. import org.apache.poi.ddf.EscherClientAnchorRecord;
  23. import org.apache.poi.ddf.EscherComplexProperty;
  24. import org.apache.poi.ddf.EscherContainerRecord;
  25. import org.apache.poi.ddf.EscherOptRecord;
  26. import org.apache.poi.ddf.EscherProperty;
  27. import org.apache.poi.ddf.EscherPropertyTypes;
  28. import org.apache.poi.ddf.EscherRGBProperty;
  29. import org.apache.poi.ddf.EscherSimpleProperty;
  30. import org.apache.poi.ddf.EscherSpRecord;
  31. import org.apache.poi.hssf.record.CommonObjectDataSubRecord;
  32. import org.apache.poi.hssf.record.ObjRecord;
  33. import org.apache.poi.ss.usermodel.Shape;
  34. import org.apache.poi.util.LittleEndian;
  35. import org.apache.poi.util.StringUtil;
  36. /**
  37. * An abstract shape.
  38. *
  39. * Note: Microsoft Excel seems to sometimes disallow
  40. * higher y1 than y2 or higher x1 than x2 in the anchor, you might need to
  41. * reverse them and draw shapes vertically or horizontally flipped via
  42. * setFlipVertical() or setFlipHorizontally().
  43. */
  44. public abstract class HSSFShape implements Shape {
  45. private static final Logger LOG = LogManager.getLogger(HSSFShape.class);
  46. public static final int LINEWIDTH_ONE_PT = 12700;
  47. public static final int LINEWIDTH_DEFAULT = 9525;
  48. public static final int LINESTYLE__COLOR_DEFAULT = 0x08000040;
  49. public static final int FILL__FILLCOLOR_DEFAULT = 0x08000009;
  50. public static final boolean NO_FILL_DEFAULT = true;
  51. public static final int LINESTYLE_SOLID = 0; // Solid (continuous) pen
  52. public static final int LINESTYLE_DASHSYS = 1; // PS_DASH system dash style
  53. public static final int LINESTYLE_DOTSYS = 2; // PS_DOT system dash style
  54. public static final int LINESTYLE_DASHDOTSYS = 3; // PS_DASHDOT system dash style
  55. public static final int LINESTYLE_DASHDOTDOTSYS = 4; // PS_DASHDOTDOT system dash style
  56. public static final int LINESTYLE_DOTGEL = 5; // square dot style
  57. public static final int LINESTYLE_DASHGEL = 6; // dash style
  58. public static final int LINESTYLE_LONGDASHGEL = 7; // long dash style
  59. public static final int LINESTYLE_DASHDOTGEL = 8; // dash short dash
  60. public static final int LINESTYLE_LONGDASHDOTGEL = 9; // long dash short dash
  61. public static final int LINESTYLE_LONGDASHDOTDOTGEL = 10; // long dash short dash short dash
  62. public static final int LINESTYLE_NONE = -1;
  63. public static final int LINESTYLE_DEFAULT = LINESTYLE_NONE;
  64. // TODO - make all these fields private
  65. private HSSFShape parent;
  66. HSSFAnchor anchor;
  67. private HSSFPatriarch _patriarch;
  68. private final EscherContainerRecord _escherContainer;
  69. private final ObjRecord _objRecord;
  70. private final EscherOptRecord _optRecord;
  71. public static final int NO_FILLHITTEST_TRUE = 0x00110000;
  72. public static final int NO_FILLHITTEST_FALSE = 0x00010000;
  73. /**
  74. * creates shapes from existing file
  75. * @param spContainer
  76. * @param objRecord
  77. */
  78. public HSSFShape(EscherContainerRecord spContainer, ObjRecord objRecord) {
  79. this._escherContainer = spContainer;
  80. this._objRecord = objRecord;
  81. this._optRecord = spContainer.getChildById(EscherOptRecord.RECORD_ID);
  82. this.anchor = HSSFAnchor.createAnchorFromEscher(spContainer);
  83. }
  84. /**
  85. * Create a new shape with the specified parent and anchor.
  86. */
  87. public HSSFShape(HSSFShape parent, HSSFAnchor anchor) {
  88. this.parent = parent;
  89. this.anchor = anchor;
  90. this._escherContainer = createSpContainer();
  91. _optRecord = _escherContainer.getChildById(EscherOptRecord.RECORD_ID);
  92. _objRecord = createObjRecord();
  93. }
  94. protected abstract EscherContainerRecord createSpContainer();
  95. protected abstract ObjRecord createObjRecord();
  96. /**
  97. * remove escher container from the patriarch.escherAggregate
  98. * remove obj, textObj and note records if it's necessary
  99. * in case of ShapeGroup remove all contained shapes
  100. * @param patriarch
  101. */
  102. protected abstract void afterRemove(HSSFPatriarch patriarch);
  103. /**
  104. * @param shapeId - global shapeId which must be set to EscherSpRecord
  105. */
  106. void setShapeId(int shapeId){
  107. EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID);
  108. spRecord.setShapeId(shapeId);
  109. CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) _objRecord.getSubRecords().get(0);
  110. cod.setObjectId((short) (shapeId%1024));
  111. }
  112. /**
  113. * @return global shapeId(from EscherSpRecord)
  114. */
  115. int getShapeId(){
  116. return ((EscherSpRecord)_escherContainer.getChildById(EscherSpRecord.RECORD_ID)).getShapeId();
  117. }
  118. abstract void afterInsert(HSSFPatriarch patriarch);
  119. protected EscherContainerRecord getEscherContainer() {
  120. return _escherContainer;
  121. }
  122. protected ObjRecord getObjRecord() {
  123. return _objRecord;
  124. }
  125. /**
  126. * Return the low-level EscherOptRecord to read/modify not yet wrapped escher properties
  127. *
  128. * @return the low-level EscherOptRecord
  129. */
  130. public EscherOptRecord getOptRecord() {
  131. return _optRecord;
  132. }
  133. @Override
  134. public HSSFShape getParent() {
  135. return parent;
  136. }
  137. /**
  138. * @return the anchor that is used by this shape.
  139. */
  140. @Override
  141. public HSSFAnchor getAnchor() {
  142. return anchor;
  143. }
  144. /**
  145. * Sets a particular anchor. A top-level shape must have an anchor of
  146. * HSSFClientAnchor. A child anchor must have an anchor of HSSFChildAnchor
  147. *
  148. * @param anchor the anchor to use.
  149. * @throws IllegalArgumentException when the wrong anchor is used for
  150. * this particular shape.
  151. * @see HSSFChildAnchor
  152. * @see HSSFClientAnchor
  153. */
  154. public void setAnchor(HSSFAnchor anchor) {
  155. int i = 0;
  156. int recordId = -1;
  157. if (parent == null) {
  158. if (anchor instanceof HSSFChildAnchor)
  159. throw new IllegalArgumentException("Must use client anchors for shapes directly attached to sheet.");
  160. EscherClientAnchorRecord anch = _escherContainer.getChildById(EscherClientAnchorRecord.RECORD_ID);
  161. if (null != anch) {
  162. for (i=0; i< _escherContainer.getChildRecords().size(); i++){
  163. if (_escherContainer.getChild(i).getRecordId() == EscherClientAnchorRecord.RECORD_ID){
  164. if (i != _escherContainer.getChildRecords().size() -1){
  165. recordId = _escherContainer.getChild(i+1).getRecordId();
  166. }
  167. }
  168. }
  169. _escherContainer.removeChildRecord(anch);
  170. }
  171. } else {
  172. if (anchor instanceof HSSFClientAnchor)
  173. throw new IllegalArgumentException("Must use child anchors for shapes attached to groups.");
  174. EscherChildAnchorRecord anch = _escherContainer.getChildById(EscherChildAnchorRecord.RECORD_ID);
  175. if (null != anch) {
  176. for (i=0; i< _escherContainer.getChildRecords().size(); i++){
  177. if (_escherContainer.getChild(i).getRecordId() == EscherChildAnchorRecord.RECORD_ID){
  178. if (i != _escherContainer.getChildRecords().size() -1){
  179. recordId = _escherContainer.getChild(i+1).getRecordId();
  180. }
  181. }
  182. }
  183. _escherContainer.removeChildRecord(anch);
  184. }
  185. }
  186. if (-1 == recordId){
  187. _escherContainer.addChildRecord(anchor.getEscherAnchor());
  188. } else {
  189. _escherContainer.addChildBefore(anchor.getEscherAnchor(), recordId);
  190. }
  191. this.anchor = anchor;
  192. }
  193. /**
  194. * The color applied to the lines of this shape.
  195. */
  196. public int getLineStyleColor() {
  197. EscherRGBProperty rgbProperty = _optRecord.lookup(EscherPropertyTypes.LINESTYLE__COLOR);
  198. return rgbProperty == null ? LINESTYLE__COLOR_DEFAULT : rgbProperty.getRgbColor();
  199. }
  200. /**
  201. * The color applied to the lines of this shape.
  202. */
  203. public void setLineStyleColor(int lineStyleColor) {
  204. setPropertyValue(new EscherRGBProperty(EscherPropertyTypes.LINESTYLE__COLOR, lineStyleColor));
  205. }
  206. @Override
  207. public void setLineStyleColor(int red, int green, int blue) {
  208. int lineStyleColor = ((blue) << 16) | ((green) << 8) | red;
  209. setPropertyValue(new EscherRGBProperty(EscherPropertyTypes.LINESTYLE__COLOR, lineStyleColor));
  210. }
  211. /**
  212. * The color used to fill this shape.
  213. */
  214. public int getFillColor() {
  215. EscherRGBProperty rgbProperty = _optRecord.lookup(EscherPropertyTypes.FILL__FILLCOLOR);
  216. return rgbProperty == null ? FILL__FILLCOLOR_DEFAULT : rgbProperty.getRgbColor();
  217. }
  218. /**
  219. * The color used to fill this shape.
  220. */
  221. public void setFillColor(int fillColor) {
  222. setPropertyValue(new EscherRGBProperty(EscherPropertyTypes.FILL__FILLCOLOR, fillColor));
  223. }
  224. @Override
  225. public void setFillColor(int red, int green, int blue) {
  226. int fillColor = ((blue) << 16) | ((green) << 8) | red;
  227. setPropertyValue(new EscherRGBProperty(EscherPropertyTypes.FILL__FILLCOLOR, fillColor));
  228. }
  229. /**
  230. * @return returns with width of the line in EMUs. 12700 = 1 pt.
  231. */
  232. public int getLineWidth() {
  233. EscherSimpleProperty property = _optRecord.lookup(EscherPropertyTypes.LINESTYLE__LINEWIDTH);
  234. return property == null ? LINEWIDTH_DEFAULT: property.getPropertyValue();
  235. }
  236. /**
  237. * Sets the width of the line. 12700 = 1 pt.
  238. *
  239. * @param lineWidth width in EMU's. 12700EMU's = 1 pt
  240. * @see HSSFShape#LINEWIDTH_ONE_PT
  241. */
  242. public void setLineWidth(int lineWidth) {
  243. setPropertyValue(new EscherSimpleProperty(EscherPropertyTypes.LINESTYLE__LINEWIDTH, lineWidth));
  244. }
  245. /**
  246. * @return One of the constants in LINESTYLE_*
  247. */
  248. public int getLineStyle() {
  249. EscherSimpleProperty property = _optRecord.lookup(EscherPropertyTypes.LINESTYLE__LINEDASHING);
  250. if (null == property){
  251. return LINESTYLE_DEFAULT;
  252. }
  253. return property.getPropertyValue();
  254. }
  255. /**
  256. * Sets the line style.
  257. *
  258. * @param lineStyle One of the constants in LINESTYLE_*
  259. */
  260. public void setLineStyle(int lineStyle) {
  261. setPropertyValue(new EscherSimpleProperty(EscherPropertyTypes.LINESTYLE__LINEDASHING, lineStyle));
  262. if (getLineStyle() != HSSFShape.LINESTYLE_SOLID) {
  263. setPropertyValue(new EscherSimpleProperty(EscherPropertyTypes.LINESTYLE__LINEENDCAPSTYLE, 0));
  264. if (getLineStyle() == HSSFShape.LINESTYLE_NONE){
  265. setPropertyValue(new EscherBoolProperty( EscherPropertyTypes.LINESTYLE__NOLINEDRAWDASH, 0x00080000));
  266. } else {
  267. setPropertyValue( new EscherBoolProperty( EscherPropertyTypes.LINESTYLE__NOLINEDRAWDASH, 0x00080008));
  268. }
  269. }
  270. }
  271. @Override
  272. public boolean isNoFill() {
  273. EscherBoolProperty property = _optRecord.lookup(EscherPropertyTypes.FILL__NOFILLHITTEST);
  274. return property == null ? NO_FILL_DEFAULT : property.getPropertyValue() == NO_FILLHITTEST_TRUE;
  275. }
  276. @Override
  277. public void setNoFill(boolean noFill) {
  278. setPropertyValue(new EscherBoolProperty(EscherPropertyTypes.FILL__NOFILLHITTEST, noFill ? NO_FILLHITTEST_TRUE : NO_FILLHITTEST_FALSE));
  279. }
  280. protected void setPropertyValue(EscherProperty property){
  281. _optRecord.setEscherProperty(property);
  282. }
  283. /**
  284. * @param value specifies whether this shape is vertically flipped.
  285. */
  286. public void setFlipVertical(boolean value){
  287. EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID);
  288. if (value){
  289. sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPVERT);
  290. } else {
  291. sp.setFlags(sp.getFlags() & (Integer.MAX_VALUE - EscherSpRecord.FLAG_FLIPVERT));
  292. }
  293. }
  294. /**
  295. * @param value specifies whether this shape is horizontally flipped.
  296. */
  297. public void setFlipHorizontal(boolean value){
  298. EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID);
  299. if (value){
  300. sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPHORIZ);
  301. } else {
  302. sp.setFlags(sp.getFlags() & (Integer.MAX_VALUE - EscherSpRecord.FLAG_FLIPHORIZ));
  303. }
  304. }
  305. /**
  306. * @return whether this shape is vertically flipped.
  307. */
  308. public boolean isFlipVertical(){
  309. EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID);
  310. return (sp.getFlags() & EscherSpRecord.FLAG_FLIPVERT) != 0;
  311. }
  312. /**
  313. * @return whether this shape is horizontally flipped.
  314. */
  315. public boolean isFlipHorizontal(){
  316. EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID);
  317. return (sp.getFlags() & EscherSpRecord.FLAG_FLIPHORIZ) != 0;
  318. }
  319. /**
  320. * @return the rotation, in degrees, that is applied to a shape.
  321. */
  322. public int getRotationDegree(){
  323. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  324. EscherSimpleProperty property = getOptRecord().lookup(EscherPropertyTypes.TRANSFORM__ROTATION);
  325. if (null == property){
  326. return 0;
  327. }
  328. try {
  329. LittleEndian.putInt(property.getPropertyValue(), bos);
  330. return LittleEndian.getShort(bos.toByteArray(), 2);
  331. } catch (IOException e) {
  332. LOG.atError().withThrowable(e).log("can't determine rotation degree");
  333. return 0;
  334. }
  335. }
  336. /**
  337. * specifies the rotation, in degrees, that is applied to a shape.
  338. * Positive values specify rotation in the clockwise direction.
  339. * Negative values specify rotation in the counterclockwise direction.
  340. * Rotation occurs around the center of the shape.
  341. * The default value for this property is 0x00000000
  342. * @param value
  343. */
  344. public void setRotationDegree(short value){
  345. setPropertyValue(new EscherSimpleProperty(EscherPropertyTypes.TRANSFORM__ROTATION , (value << 16)));
  346. }
  347. /**
  348. * Count of all children and their children's children.
  349. */
  350. public int countOfAllChildren() {
  351. return 1;
  352. }
  353. protected abstract HSSFShape cloneShape();
  354. protected void setPatriarch(HSSFPatriarch _patriarch) {
  355. this._patriarch = _patriarch;
  356. }
  357. public HSSFPatriarch getPatriarch() {
  358. return _patriarch;
  359. }
  360. protected void setParent(HSSFShape parent) {
  361. this.parent = parent;
  362. }
  363. /**
  364. * @return the name of this shape
  365. */
  366. public String getShapeName() {
  367. EscherOptRecord eor = getOptRecord();
  368. if (eor == null) {
  369. return null;
  370. }
  371. EscherProperty ep = eor.lookup(EscherPropertyTypes.GROUPSHAPE__SHAPENAME);
  372. if (ep instanceof EscherComplexProperty) {
  373. return StringUtil.getFromUnicodeLE(((EscherComplexProperty)ep).getComplexData());
  374. }
  375. return null;
  376. }
  377. }