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