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.

EscherAggregate.java 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  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.record;
  16. import static org.apache.poi.hssf.record.RecordInputStream.MAX_RECORD_DATA_SIZE;
  17. import java.io.ByteArrayOutputStream;
  18. import java.io.IOException;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.Iterator;
  24. import java.util.LinkedHashMap;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.function.Supplier;
  28. import org.apache.poi.ddf.DefaultEscherRecordFactory;
  29. import org.apache.poi.ddf.EscherClientDataRecord;
  30. import org.apache.poi.ddf.EscherContainerRecord;
  31. import org.apache.poi.ddf.EscherDgRecord;
  32. import org.apache.poi.ddf.EscherRecord;
  33. import org.apache.poi.ddf.EscherSerializationListener;
  34. import org.apache.poi.ddf.EscherSpRecord;
  35. import org.apache.poi.ddf.EscherSpgrRecord;
  36. import org.apache.poi.ddf.EscherTextboxRecord;
  37. import org.apache.poi.util.GenericRecordXmlWriter;
  38. import org.apache.poi.util.IOUtils;
  39. import org.apache.poi.util.RecordFormatException;
  40. /**
  41. * This class is used to aggregate the MSODRAWING and OBJ record
  42. * combinations. This is necessary due to the bizare way in which
  43. * these records are serialized. What happens is that you get a
  44. * combination of MSODRAWING -> OBJ -> MSODRAWING -> OBJ records
  45. * but the escher records are serialized _across_ the MSODRAWING
  46. * records.
  47. * <p>
  48. * It gets even worse when you start looking at TXO records.
  49. * <p>
  50. * So what we do with this class is aggregate lazily. That is
  51. * we don't aggregate the MSODRAWING -&gt; OBJ records unless we
  52. * need to modify them.
  53. * <p>
  54. * At first document contains 4 types of records which belong to drawing layer.
  55. * There are can be such sequence of record:
  56. * <p>
  57. * DrawingRecord
  58. * ContinueRecord
  59. * ...
  60. * ContinueRecord
  61. * ObjRecord | TextObjectRecord
  62. * .....
  63. * ContinueRecord
  64. * ...
  65. * ContinueRecord
  66. * ObjRecord | TextObjectRecord
  67. * NoteRecord
  68. * ...
  69. * NoteRecord
  70. * <p>
  71. * To work with shapes we have to read data from Drawing and Continue records into single array of bytes and
  72. * build escher(office art) records tree from this array.
  73. * Each shape in drawing layer matches corresponding ObjRecord
  74. * Each textbox matches corresponding TextObjectRecord
  75. * <p>
  76. * ObjRecord contains information about shape. Thus each ObjRecord corresponds EscherContainerRecord(SPGR)
  77. * <p>
  78. * EscherAggrefate contains also NoteRecords
  79. * NoteRecords must be serial
  80. */
  81. public final class EscherAggregate extends AbstractEscherHolderRecord {
  82. // not a real sid - dummy value
  83. public static final short sid = 9876;
  84. //arbitrarily selected; may need to increase
  85. private static final int MAX_RECORD_LENGTH = 100_000_000;
  86. public static final short ST_MIN = (short) 0;
  87. public static final short ST_NOT_PRIMATIVE = ST_MIN;
  88. public static final short ST_RECTANGLE = (short) 1;
  89. public static final short ST_ROUNDRECTANGLE = (short) 2;
  90. public static final short ST_ELLIPSE = (short) 3;
  91. public static final short ST_DIAMOND = (short) 4;
  92. public static final short ST_ISOCELESTRIANGLE = (short) 5;
  93. public static final short ST_RIGHTTRIANGLE = (short) 6;
  94. public static final short ST_PARALLELOGRAM = (short) 7;
  95. public static final short ST_TRAPEZOID = (short) 8;
  96. public static final short ST_HEXAGON = (short) 9;
  97. public static final short ST_OCTAGON = (short) 10;
  98. public static final short ST_PLUS = (short) 11;
  99. public static final short ST_STAR = (short) 12;
  100. public static final short ST_ARROW = (short) 13;
  101. public static final short ST_THICKARROW = (short) 14;
  102. public static final short ST_HOMEPLATE = (short) 15;
  103. public static final short ST_CUBE = (short) 16;
  104. public static final short ST_BALLOON = (short) 17;
  105. public static final short ST_SEAL = (short) 18;
  106. public static final short ST_ARC = (short) 19;
  107. public static final short ST_LINE = (short) 20;
  108. public static final short ST_PLAQUE = (short) 21;
  109. public static final short ST_CAN = (short) 22;
  110. public static final short ST_DONUT = (short) 23;
  111. public static final short ST_TEXTSIMPLE = (short) 24;
  112. public static final short ST_TEXTOCTAGON = (short) 25;
  113. public static final short ST_TEXTHEXAGON = (short) 26;
  114. public static final short ST_TEXTCURVE = (short) 27;
  115. public static final short ST_TEXTWAVE = (short) 28;
  116. public static final short ST_TEXTRING = (short) 29;
  117. public static final short ST_TEXTONCURVE = (short) 30;
  118. public static final short ST_TEXTONRING = (short) 31;
  119. public static final short ST_STRAIGHTCONNECTOR1 = (short) 32;
  120. public static final short ST_BENTCONNECTOR2 = (short) 33;
  121. public static final short ST_BENTCONNECTOR3 = (short) 34;
  122. public static final short ST_BENTCONNECTOR4 = (short) 35;
  123. public static final short ST_BENTCONNECTOR5 = (short) 36;
  124. public static final short ST_CURVEDCONNECTOR2 = (short) 37;
  125. public static final short ST_CURVEDCONNECTOR3 = (short) 38;
  126. public static final short ST_CURVEDCONNECTOR4 = (short) 39;
  127. public static final short ST_CURVEDCONNECTOR5 = (short) 40;
  128. public static final short ST_CALLOUT1 = (short) 41;
  129. public static final short ST_CALLOUT2 = (short) 42;
  130. public static final short ST_CALLOUT3 = (short) 43;
  131. public static final short ST_ACCENTCALLOUT1 = (short) 44;
  132. public static final short ST_ACCENTCALLOUT2 = (short) 45;
  133. public static final short ST_ACCENTCALLOUT3 = (short) 46;
  134. public static final short ST_BORDERCALLOUT1 = (short) 47;
  135. public static final short ST_BORDERCALLOUT2 = (short) 48;
  136. public static final short ST_BORDERCALLOUT3 = (short) 49;
  137. public static final short ST_ACCENTBORDERCALLOUT1 = (short) 50;
  138. public static final short ST_ACCENTBORDERCALLOUT2 = (short) 51;
  139. public static final short ST_ACCENTBORDERCALLOUT3 = (short) 52;
  140. public static final short ST_RIBBON = (short) 53;
  141. public static final short ST_RIBBON2 = (short) 54;
  142. public static final short ST_CHEVRON = (short) 55;
  143. public static final short ST_PENTAGON = (short) 56;
  144. public static final short ST_NOSMOKING = (short) 57;
  145. public static final short ST_SEAL8 = (short) 58;
  146. public static final short ST_SEAL16 = (short) 59;
  147. public static final short ST_SEAL32 = (short) 60;
  148. public static final short ST_WEDGERECTCALLOUT = (short) 61;
  149. public static final short ST_WEDGERRECTCALLOUT = (short) 62;
  150. public static final short ST_WEDGEELLIPSECALLOUT = (short) 63;
  151. public static final short ST_WAVE = (short) 64;
  152. public static final short ST_FOLDEDCORNER = (short) 65;
  153. public static final short ST_LEFTARROW = (short) 66;
  154. public static final short ST_DOWNARROW = (short) 67;
  155. public static final short ST_UPARROW = (short) 68;
  156. public static final short ST_LEFTRIGHTARROW = (short) 69;
  157. public static final short ST_UPDOWNARROW = (short) 70;
  158. public static final short ST_IRREGULARSEAL1 = (short) 71;
  159. public static final short ST_IRREGULARSEAL2 = (short) 72;
  160. public static final short ST_LIGHTNINGBOLT = (short) 73;
  161. public static final short ST_HEART = (short) 74;
  162. public static final short ST_PICTUREFRAME = (short) 75;
  163. public static final short ST_QUADARROW = (short) 76;
  164. public static final short ST_LEFTARROWCALLOUT = (short) 77;
  165. public static final short ST_RIGHTARROWCALLOUT = (short) 78;
  166. public static final short ST_UPARROWCALLOUT = (short) 79;
  167. public static final short ST_DOWNARROWCALLOUT = (short) 80;
  168. public static final short ST_LEFTRIGHTARROWCALLOUT = (short) 81;
  169. public static final short ST_UPDOWNARROWCALLOUT = (short) 82;
  170. public static final short ST_QUADARROWCALLOUT = (short) 83;
  171. public static final short ST_BEVEL = (short) 84;
  172. public static final short ST_LEFTBRACKET = (short) 85;
  173. public static final short ST_RIGHTBRACKET = (short) 86;
  174. public static final short ST_LEFTBRACE = (short) 87;
  175. public static final short ST_RIGHTBRACE = (short) 88;
  176. public static final short ST_LEFTUPARROW = (short) 89;
  177. public static final short ST_BENTUPARROW = (short) 90;
  178. public static final short ST_BENTARROW = (short) 91;
  179. public static final short ST_SEAL24 = (short) 92;
  180. public static final short ST_STRIPEDRIGHTARROW = (short) 93;
  181. public static final short ST_NOTCHEDRIGHTARROW = (short) 94;
  182. public static final short ST_BLOCKARC = (short) 95;
  183. public static final short ST_SMILEYFACE = (short) 96;
  184. public static final short ST_VERTICALSCROLL = (short) 97;
  185. public static final short ST_HORIZONTALSCROLL = (short) 98;
  186. public static final short ST_CIRCULARARROW = (short) 99;
  187. public static final short ST_NOTCHEDCIRCULARARROW = (short) 100;
  188. public static final short ST_UTURNARROW = (short) 101;
  189. public static final short ST_CURVEDRIGHTARROW = (short) 102;
  190. public static final short ST_CURVEDLEFTARROW = (short) 103;
  191. public static final short ST_CURVEDUPARROW = (short) 104;
  192. public static final short ST_CURVEDDOWNARROW = (short) 105;
  193. public static final short ST_CLOUDCALLOUT = (short) 106;
  194. public static final short ST_ELLIPSERIBBON = (short) 107;
  195. public static final short ST_ELLIPSERIBBON2 = (short) 108;
  196. public static final short ST_FLOWCHARTPROCESS = (short) 109;
  197. public static final short ST_FLOWCHARTDECISION = (short) 110;
  198. public static final short ST_FLOWCHARTINPUTOUTPUT = (short) 111;
  199. public static final short ST_FLOWCHARTPREDEFINEDPROCESS = (short) 112;
  200. public static final short ST_FLOWCHARTINTERNALSTORAGE = (short) 113;
  201. public static final short ST_FLOWCHARTDOCUMENT = (short) 114;
  202. public static final short ST_FLOWCHARTMULTIDOCUMENT = (short) 115;
  203. public static final short ST_FLOWCHARTTERMINATOR = (short) 116;
  204. public static final short ST_FLOWCHARTPREPARATION = (short) 117;
  205. public static final short ST_FLOWCHARTMANUALINPUT = (short) 118;
  206. public static final short ST_FLOWCHARTMANUALOPERATION = (short) 119;
  207. public static final short ST_FLOWCHARTCONNECTOR = (short) 120;
  208. public static final short ST_FLOWCHARTPUNCHEDCARD = (short) 121;
  209. public static final short ST_FLOWCHARTPUNCHEDTAPE = (short) 122;
  210. public static final short ST_FLOWCHARTSUMMINGJUNCTION = (short) 123;
  211. public static final short ST_FLOWCHARTOR = (short) 124;
  212. public static final short ST_FLOWCHARTCOLLATE = (short) 125;
  213. public static final short ST_FLOWCHARTSORT = (short) 126;
  214. public static final short ST_FLOWCHARTEXTRACT = (short) 127;
  215. public static final short ST_FLOWCHARTMERGE = (short) 128;
  216. public static final short ST_FLOWCHARTOFFLINESTORAGE = (short) 129;
  217. public static final short ST_FLOWCHARTONLINESTORAGE = (short) 130;
  218. public static final short ST_FLOWCHARTMAGNETICTAPE = (short) 131;
  219. public static final short ST_FLOWCHARTMAGNETICDISK = (short) 132;
  220. public static final short ST_FLOWCHARTMAGNETICDRUM = (short) 133;
  221. public static final short ST_FLOWCHARTDISPLAY = (short) 134;
  222. public static final short ST_FLOWCHARTDELAY = (short) 135;
  223. public static final short ST_TEXTPLAINTEXT = (short) 136;
  224. public static final short ST_TEXTSTOP = (short) 137;
  225. public static final short ST_TEXTTRIANGLE = (short) 138;
  226. public static final short ST_TEXTTRIANGLEINVERTED = (short) 139;
  227. public static final short ST_TEXTCHEVRON = (short) 140;
  228. public static final short ST_TEXTCHEVRONINVERTED = (short) 141;
  229. public static final short ST_TEXTRINGINSIDE = (short) 142;
  230. public static final short ST_TEXTRINGOUTSIDE = (short) 143;
  231. public static final short ST_TEXTARCHUPCURVE = (short) 144;
  232. public static final short ST_TEXTARCHDOWNCURVE = (short) 145;
  233. public static final short ST_TEXTCIRCLECURVE = (short) 146;
  234. public static final short ST_TEXTBUTTONCURVE = (short) 147;
  235. public static final short ST_TEXTARCHUPPOUR = (short) 148;
  236. public static final short ST_TEXTARCHDOWNPOUR = (short) 149;
  237. public static final short ST_TEXTCIRCLEPOUR = (short) 150;
  238. public static final short ST_TEXTBUTTONPOUR = (short) 151;
  239. public static final short ST_TEXTCURVEUP = (short) 152;
  240. public static final short ST_TEXTCURVEDOWN = (short) 153;
  241. public static final short ST_TEXTCASCADEUP = (short) 154;
  242. public static final short ST_TEXTCASCADEDOWN = (short) 155;
  243. public static final short ST_TEXTWAVE1 = (short) 156;
  244. public static final short ST_TEXTWAVE2 = (short) 157;
  245. public static final short ST_TEXTWAVE3 = (short) 158;
  246. public static final short ST_TEXTWAVE4 = (short) 159;
  247. public static final short ST_TEXTINFLATE = (short) 160;
  248. public static final short ST_TEXTDEFLATE = (short) 161;
  249. public static final short ST_TEXTINFLATEBOTTOM = (short) 162;
  250. public static final short ST_TEXTDEFLATEBOTTOM = (short) 163;
  251. public static final short ST_TEXTINFLATETOP = (short) 164;
  252. public static final short ST_TEXTDEFLATETOP = (short) 165;
  253. public static final short ST_TEXTDEFLATEINFLATE = (short) 166;
  254. public static final short ST_TEXTDEFLATEINFLATEDEFLATE = (short) 167;
  255. public static final short ST_TEXTFADERIGHT = (short) 168;
  256. public static final short ST_TEXTFADELEFT = (short) 169;
  257. public static final short ST_TEXTFADEUP = (short) 170;
  258. public static final short ST_TEXTFADEDOWN = (short) 171;
  259. public static final short ST_TEXTSLANTUP = (short) 172;
  260. public static final short ST_TEXTSLANTDOWN = (short) 173;
  261. public static final short ST_TEXTCANUP = (short) 174;
  262. public static final short ST_TEXTCANDOWN = (short) 175;
  263. public static final short ST_FLOWCHARTALTERNATEPROCESS = (short) 176;
  264. public static final short ST_FLOWCHARTOFFPAGECONNECTOR = (short) 177;
  265. public static final short ST_CALLOUT90 = (short) 178;
  266. public static final short ST_ACCENTCALLOUT90 = (short) 179;
  267. public static final short ST_BORDERCALLOUT90 = (short) 180;
  268. public static final short ST_ACCENTBORDERCALLOUT90 = (short) 181;
  269. public static final short ST_LEFTRIGHTUPARROW = (short) 182;
  270. public static final short ST_SUN = (short) 183;
  271. public static final short ST_MOON = (short) 184;
  272. public static final short ST_BRACKETPAIR = (short) 185;
  273. public static final short ST_BRACEPAIR = (short) 186;
  274. public static final short ST_SEAL4 = (short) 187;
  275. public static final short ST_DOUBLEWAVE = (short) 188;
  276. public static final short ST_ACTIONBUTTONBLANK = (short) 189;
  277. public static final short ST_ACTIONBUTTONHOME = (short) 190;
  278. public static final short ST_ACTIONBUTTONHELP = (short) 191;
  279. public static final short ST_ACTIONBUTTONINFORMATION = (short) 192;
  280. public static final short ST_ACTIONBUTTONFORWARDNEXT = (short) 193;
  281. public static final short ST_ACTIONBUTTONBACKPREVIOUS = (short) 194;
  282. public static final short ST_ACTIONBUTTONEND = (short) 195;
  283. public static final short ST_ACTIONBUTTONBEGINNING = (short) 196;
  284. public static final short ST_ACTIONBUTTONRETURN = (short) 197;
  285. public static final short ST_ACTIONBUTTONDOCUMENT = (short) 198;
  286. public static final short ST_ACTIONBUTTONSOUND = (short) 199;
  287. public static final short ST_ACTIONBUTTONMOVIE = (short) 200;
  288. public static final short ST_HOSTCONTROL = (short) 201;
  289. public static final short ST_TEXTBOX = (short) 202;
  290. public static final short ST_NIL = (short) 0x0FFF;
  291. /**
  292. * Maps shape container objects to their {@link TextObjectRecord} or {@link ObjRecord}
  293. */
  294. private final Map<EscherRecord, Record> shapeToObj = new HashMap<>();
  295. /**
  296. * list of "tail" records that need to be serialized after all drawing group records
  297. */
  298. private final Map<Integer, NoteRecord> tailRec = new LinkedHashMap<>();
  299. /**
  300. * create new EscherAggregate
  301. * @param createDefaultTree if true creates base tree of the escher records, see EscherAggregate.buildBaseTree()
  302. * else return empty escher aggregate
  303. */
  304. public EscherAggregate(boolean createDefaultTree) {
  305. if (createDefaultTree){
  306. buildBaseTree();
  307. }
  308. }
  309. public EscherAggregate(EscherAggregate other) {
  310. super(other);
  311. // shallow copy, because the aggregates doesn't own the records
  312. shapeToObj.putAll(other.shapeToObj);
  313. tailRec.putAll(other.tailRec);
  314. }
  315. /**
  316. * @return Returns the current sid.
  317. */
  318. public short getSid() {
  319. return sid;
  320. }
  321. /**
  322. * Calculates the xml representation of this record. This is
  323. * simply a dump of all the records.
  324. * @param tab - string which must be added before each line (used by default '\t')
  325. * @return xml representation of the all aggregated records
  326. */
  327. public String toXml(String tab) {
  328. return GenericRecordXmlWriter.marshal(this);
  329. }
  330. /**
  331. * Collapses the drawing records into an aggregate.
  332. * read Drawing, Obj, TxtObj, Note and Continue records into single byte array,
  333. * create Escher tree from byte array, create map &lt;EscherRecord, Record&gt;
  334. *
  335. * @param records - list of all records inside sheet
  336. * @param locFirstDrawingRecord - location of the first DrawingRecord inside sheet
  337. * @return new EscherAggregate create from all aggregated records which belong to drawing layer
  338. */
  339. public static EscherAggregate createAggregate(final List<RecordBase> records, final int locFirstDrawingRecord) {
  340. EscherAggregate agg = new EscherAggregate(false);
  341. ShapeCollector recordFactory = new ShapeCollector();
  342. List<Record> objectRecords = new ArrayList<>();
  343. int nextIdx = locFirstDrawingRecord;
  344. for (RecordBase rb : records.subList(locFirstDrawingRecord, records.size())) {
  345. nextIdx++;
  346. switch (sid(rb)) {
  347. case DrawingRecord.sid:
  348. recordFactory.addBytes(((DrawingRecord)rb).getRecordData());
  349. continue;
  350. case ContinueRecord.sid:
  351. recordFactory.addBytes(((ContinueRecord)rb).getData());
  352. continue;
  353. case ObjRecord.sid:
  354. case TextObjectRecord.sid:
  355. objectRecords.add((org.apache.poi.hssf.record.Record)rb);
  356. continue;
  357. case NoteRecord.sid:
  358. // any NoteRecords that follow the drawing block must be aggregated and saved in the tailRec collection
  359. NoteRecord r = (NoteRecord)rb;
  360. agg.tailRec.put(r.getShapeId(), r);
  361. continue;
  362. default:
  363. nextIdx--;
  364. break;
  365. }
  366. break;
  367. }
  368. // replace drawing block with the created EscherAggregate
  369. records.set(locFirstDrawingRecord, agg);
  370. if (locFirstDrawingRecord+1 <= nextIdx) {
  371. records.subList(locFirstDrawingRecord + 1, nextIdx).clear();
  372. }
  373. // Decode the shapes
  374. Iterator<EscherRecord> shapeIter = recordFactory.parse(agg).iterator();
  375. // Associate the object records with the shapes
  376. objectRecords.forEach(or -> agg.shapeToObj.put(shapeIter.next(), or));
  377. return agg;
  378. }
  379. private static class ShapeCollector extends DefaultEscherRecordFactory {
  380. final List<EscherRecord> objShapes = new ArrayList<>();
  381. final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  382. void addBytes(byte[] data) {
  383. try {
  384. buffer.write(data);
  385. } catch (IOException e) {
  386. throw new RuntimeException("Couldn't get data from drawing/continue records", e);
  387. }
  388. }
  389. public EscherRecord createRecord(byte[] data, int offset) {
  390. EscherRecord r = super.createRecord(data, offset);
  391. short rid = r.getRecordId();
  392. if (rid == EscherClientDataRecord.RECORD_ID || rid == EscherTextboxRecord.RECORD_ID) {
  393. objShapes.add(r);
  394. }
  395. return r;
  396. }
  397. List<EscherRecord> parse(EscherAggregate agg) {
  398. byte[] buf = buffer.toByteArray();
  399. for (int pos = 0, bytesRead; pos < buf.length; pos += bytesRead) {
  400. EscherRecord r = createRecord(buf, pos);
  401. bytesRead = r.fillFields(buf, pos, this);
  402. agg.addEscherRecord(r);
  403. }
  404. return objShapes;
  405. }
  406. }
  407. /**
  408. * Serializes this aggregate to a byte array. Since this is an aggregate
  409. * record it will effectively serialize the aggregated records.
  410. *
  411. * @param offset The offset into the start of the array.
  412. * @param data The byte array to serialize to.
  413. * @return The number of bytes serialized.
  414. */
  415. public int serialize(final int offset, final byte[] data) {
  416. // Determine buffer size
  417. List <EscherRecord>records = getEscherRecords();
  418. int size = getEscherRecordSize(records);
  419. byte[] buffer = new byte[size];
  420. // Serialize escher records into one big data structure and keep note of ending offsets.
  421. final List <Integer>spEndingOffsets = new ArrayList<>();
  422. final List <EscherRecord> shapes = new ArrayList<>();
  423. int pos = 0;
  424. for (Object record : records) {
  425. EscherRecord e = (EscherRecord) record;
  426. pos += e.serialize(pos, buffer, new EscherSerializationListener() {
  427. public void beforeRecordSerialize(int offset, short recordId, EscherRecord record) {
  428. }
  429. public void afterRecordSerialize(int offset, short recordId, int size, EscherRecord record) {
  430. if (recordId == EscherClientDataRecord.RECORD_ID || recordId == EscherTextboxRecord.RECORD_ID) {
  431. spEndingOffsets.add(offset);
  432. shapes.add(record);
  433. }
  434. }
  435. });
  436. }
  437. shapes.add(0, null);
  438. spEndingOffsets.add(0, 0);
  439. // Split escher records into separate MSODRAWING and OBJ, TXO records. (We don't break on
  440. // the first one because it's the patriach).
  441. pos = offset;
  442. int writtenEscherBytes = 0;
  443. boolean isFirst = true;
  444. int endOffset = 0;
  445. for (int i = 1; i < shapes.size(); i++) {
  446. int startOffset = endOffset;
  447. endOffset = spEndingOffsets.get(i);
  448. byte[] drawingData = Arrays.copyOfRange(buffer, startOffset, endOffset);
  449. pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, isFirst);
  450. writtenEscherBytes += drawingData.length;
  451. // Write the matching OBJ record
  452. Record obj = shapeToObj.get(shapes.get(i));
  453. pos += obj.serialize(pos, data);
  454. isFirst = false;
  455. }
  456. if (endOffset < buffer.length - 1) {
  457. byte[] drawingData = Arrays.copyOfRange(buffer, endOffset, buffer.length);
  458. pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, isFirst);
  459. }
  460. for (NoteRecord noteRecord : tailRec.values()) {
  461. pos += noteRecord.serialize(pos, data);
  462. }
  463. int bytesWritten = pos - offset;
  464. if (bytesWritten != getRecordSize()) {
  465. throw new RecordFormatException(bytesWritten + " bytes written but getRecordSize() reports " + getRecordSize());
  466. }
  467. return bytesWritten;
  468. }
  469. /**
  470. * @param drawingData - escher records saved into single byte array
  471. * @param writtenEscherBytes - count of bytes already saved into drawing records (we should know it to decide create
  472. * drawing or continue record)
  473. * @param pos current position of data array
  474. * @param data - array of bytes where drawing records must be serialized
  475. * @param isFirst - is it the first shape, saved into data array
  476. * @return offset of data array after serialization
  477. */
  478. private int writeDataIntoDrawingRecord(final byte[] drawingData, final int writtenEscherBytes, final int pos, final byte[] data, final boolean isFirst) {
  479. int temp = 0;
  480. //First record in drawing layer MUST be DrawingRecord
  481. boolean useDrawingRecord = isFirst || (writtenEscherBytes + drawingData.length) <= MAX_RECORD_DATA_SIZE;
  482. for (int j = 0; j < drawingData.length; j += MAX_RECORD_DATA_SIZE) {
  483. byte[] buf = Arrays.copyOfRange(drawingData, j, Math.min(j+MAX_RECORD_DATA_SIZE, drawingData.length));
  484. Record drawing = (useDrawingRecord) ? new DrawingRecord(buf) : new ContinueRecord(buf);
  485. temp += drawing.serialize(pos + temp, data);
  486. useDrawingRecord = false;
  487. }
  488. return temp;
  489. }
  490. /**
  491. * How many bytes do the raw escher records contain.
  492. *
  493. * @param records List of escher records
  494. * @return the number of bytes
  495. */
  496. private int getEscherRecordSize(List<EscherRecord> records) {
  497. int size = 0;
  498. for (EscherRecord record : records){
  499. size += record.getRecordSize();
  500. }
  501. return size;
  502. }
  503. /**
  504. * @return record size, including header size of obj, text, note, drawing, continue records
  505. */
  506. public int getRecordSize() {
  507. // To determine size of aggregate record we have to know size of each DrawingRecord because if DrawingRecord
  508. // is split into several continue records we have to add header size to total EscherAggregate size
  509. int continueRecordsHeadersSize = 0;
  510. // Determine buffer size
  511. List<EscherRecord> records = getEscherRecords();
  512. int rawEscherSize = getEscherRecordSize(records);
  513. byte[] buffer = IOUtils.safelyAllocate(rawEscherSize, MAX_RECORD_LENGTH);
  514. final List<Integer> spEndingOffsets = new ArrayList<>();
  515. int pos = 0;
  516. for (EscherRecord e : records) {
  517. pos += e.serialize(pos, buffer, new EscherSerializationListener() {
  518. public void beforeRecordSerialize(int offset, short recordId, EscherRecord record) {
  519. }
  520. public void afterRecordSerialize(int offset, short recordId, int size, EscherRecord record) {
  521. if (recordId == EscherClientDataRecord.RECORD_ID || recordId == EscherTextboxRecord.RECORD_ID) {
  522. spEndingOffsets.add(offset);
  523. }
  524. }
  525. });
  526. }
  527. spEndingOffsets.add(0, 0);
  528. for (int i = 1; i < spEndingOffsets.size(); i++) {
  529. if (i == spEndingOffsets.size() - 1 && spEndingOffsets.get(i) < pos) {
  530. continueRecordsHeadersSize += 4;
  531. }
  532. if (spEndingOffsets.get(i) - spEndingOffsets.get(i - 1) <= MAX_RECORD_DATA_SIZE) {
  533. continue;
  534. }
  535. continueRecordsHeadersSize += ((spEndingOffsets.get(i) - spEndingOffsets.get(i - 1)) / MAX_RECORD_DATA_SIZE) * 4;
  536. }
  537. int drawingRecordSize = rawEscherSize + (shapeToObj.size()) * 4;
  538. if (rawEscherSize != 0 && spEndingOffsets.size() == 1) {
  539. // EMPTY
  540. continueRecordsHeadersSize += 4;
  541. }
  542. int objRecordSize = 0;
  543. for (org.apache.poi.hssf.record.Record r : shapeToObj.values()) {
  544. objRecordSize += r.getRecordSize();
  545. }
  546. int tailRecordSize = 0;
  547. for (NoteRecord noteRecord : tailRec.values()) {
  548. tailRecordSize += noteRecord.getRecordSize();
  549. }
  550. return drawingRecordSize + objRecordSize + tailRecordSize + continueRecordsHeadersSize;
  551. }
  552. /**
  553. * Associates an escher record to an OBJ record or a TXO record.
  554. * @param r - ClientData or Textbox record
  555. * @param objRecord - Obj or TextObj record
  556. */
  557. public void associateShapeToObjRecord(EscherRecord r, Record objRecord) {
  558. shapeToObj.put(r, objRecord);
  559. }
  560. /**
  561. * Remove echerRecord and associated to it Obj or TextObj record
  562. * @param rec - clientData or textbox record to be removed
  563. */
  564. public void removeShapeToObjRecord(EscherRecord rec) {
  565. shapeToObj.remove(rec);
  566. }
  567. /**
  568. * @return "ESCHERAGGREGATE"
  569. */
  570. protected String getRecordName() {
  571. return "ESCHERAGGREGATE";
  572. }
  573. // =============== Private methods ========================
  574. /**
  575. * create base tree with such structure:
  576. * EscherDgContainer
  577. * -EscherSpgrContainer
  578. * --EscherSpContainer
  579. * ---EscherSpRecord
  580. * ---EscherSpgrRecord
  581. * ---EscherSpRecord
  582. * -EscherDgRecord
  583. *
  584. * id of DgRecord and SpRecord are empty and must be set later by HSSFPatriarch
  585. */
  586. private void buildBaseTree() {
  587. EscherContainerRecord dgContainer = new EscherContainerRecord();
  588. EscherContainerRecord spgrContainer = new EscherContainerRecord();
  589. EscherContainerRecord spContainer1 = new EscherContainerRecord();
  590. EscherSpgrRecord spgr = new EscherSpgrRecord();
  591. EscherSpRecord sp1 = new EscherSpRecord();
  592. dgContainer.setRecordId(EscherContainerRecord.DG_CONTAINER);
  593. dgContainer.setOptions((short) 0x000F);
  594. EscherDgRecord dg = new EscherDgRecord();
  595. dg.setRecordId(EscherDgRecord.RECORD_ID);
  596. short dgId = 1;
  597. dg.setOptions((short) (dgId << 4));
  598. dg.setNumShapes(0);
  599. dg.setLastMSOSPID(1024);
  600. spgrContainer.setRecordId(EscherContainerRecord.SPGR_CONTAINER);
  601. spgrContainer.setOptions((short) 0x000F);
  602. spContainer1.setRecordId(EscherContainerRecord.SP_CONTAINER);
  603. spContainer1.setOptions((short) 0x000F);
  604. spgr.setRecordId(EscherSpgrRecord.RECORD_ID);
  605. spgr.setOptions((short) 0x0001); // version
  606. spgr.setRectX1(0);
  607. spgr.setRectY1(0);
  608. spgr.setRectX2(1023);
  609. spgr.setRectY2(255);
  610. sp1.setRecordId(EscherSpRecord.RECORD_ID);
  611. sp1.setOptions((short) 0x0002);
  612. sp1.setVersion((short) 0x2);
  613. sp1.setShapeId(-1);
  614. sp1.setFlags(EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_PATRIARCH);
  615. dgContainer.addChildRecord(dg);
  616. dgContainer.addChildRecord(spgrContainer);
  617. spgrContainer.addChildRecord(spContainer1);
  618. spContainer1.addChildRecord(spgr);
  619. spContainer1.addChildRecord(sp1);
  620. addEscherRecord(dgContainer);
  621. }
  622. /**
  623. * EscherDgContainer
  624. * -EscherSpgrContainer
  625. * -EscherDgRecord - set id for this record
  626. * set id for DgRecord of DgContainer
  627. * @param dgId - id which must be set
  628. */
  629. public void setDgId(short dgId) {
  630. EscherContainerRecord dgContainer = getEscherContainer();
  631. EscherDgRecord dg = dgContainer.getChildById(EscherDgRecord.RECORD_ID);
  632. if (dg != null) {
  633. dg.setOptions((short) (dgId << 4));
  634. }
  635. }
  636. /**
  637. * EscherDgContainer
  638. * -EscherSpgrContainer
  639. * --EscherSpContainer
  640. * ---EscherSpRecord -set id for this record
  641. * ---***
  642. * --***
  643. * -EscherDgRecord
  644. * set id for the sp record of the first spContainer in main spgrConatiner
  645. * @param shapeId - id which must be set
  646. */
  647. public void setMainSpRecordId(int shapeId) {
  648. EscherContainerRecord dgContainer = getEscherContainer();
  649. EscherContainerRecord spgrContainer = dgContainer.getChildById(EscherContainerRecord.SPGR_CONTAINER);
  650. if (spgrContainer != null) {
  651. EscherContainerRecord spContainer = (EscherContainerRecord) spgrContainer.getChild(0);
  652. EscherSpRecord sp = spContainer.getChildById(EscherSpRecord.RECORD_ID);
  653. if (sp != null) {
  654. sp.setShapeId(shapeId);
  655. }
  656. }
  657. }
  658. /**
  659. * @param record the record to look into
  660. * @return sid of the record
  661. */
  662. private static short sid(RecordBase record) {
  663. // Aggregates don't have a sid
  664. // We could step into them, but for these needs we don't care
  665. return (record instanceof org.apache.poi.hssf.record.Record)
  666. ? ((org.apache.poi.hssf.record.Record)record).getSid()
  667. : -1;
  668. }
  669. /**
  670. * @return unmodifiable copy of the mapping of {@link EscherClientDataRecord} and {@link EscherTextboxRecord}
  671. * to their {@link TextObjectRecord} or {@link ObjRecord} .
  672. * <p>
  673. * We need to access it outside of EscherAggregate when building shapes
  674. */
  675. public Map<EscherRecord, Record> getShapeToObjMapping() {
  676. return Collections.unmodifiableMap(shapeToObj);
  677. }
  678. /**
  679. * @return unmodifiable copy of tail records. We need to access them when building shapes.
  680. * Every HSSFComment shape has a link to a NoteRecord from the tailRec collection.
  681. */
  682. public Map<Integer, NoteRecord> getTailRecords() {
  683. return Collections.unmodifiableMap(tailRec);
  684. }
  685. /**
  686. * @param obj - ObjRecord with id == NoteRecord.id
  687. * @return null if note record is not found else returns note record with id == obj.id
  688. */
  689. public NoteRecord getNoteRecordByObj(ObjRecord obj) {
  690. CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) obj.getSubRecords().get(0);
  691. return tailRec.get(cod.getObjectId());
  692. }
  693. /**
  694. * Add tail record to existing map
  695. * @param note to be added
  696. */
  697. public void addTailRecord(NoteRecord note) {
  698. tailRec.put(note.getShapeId(), note);
  699. }
  700. /**
  701. * Remove tail record from the existing map
  702. * @param note to be removed
  703. */
  704. public void removeTailRecord(NoteRecord note) {
  705. tailRec.remove(note.getShapeId());
  706. }
  707. @Override
  708. public EscherAggregate copy() {
  709. return new EscherAggregate(this);
  710. }
  711. @Override
  712. public HSSFRecordTypes getGenericRecordType() {
  713. return HSSFRecordTypes.ESCHER_AGGREGATE;
  714. }
  715. @Override
  716. public Map<String, Supplier<?>> getGenericProperties() {
  717. return null;
  718. }
  719. }