123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- /* ====================================================================
- 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.ddf;
-
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- import java.util.Map;
- import java.util.function.Supplier;
-
- import com.zaxxer.sparsebits.SparseBitSet;
- import org.apache.poi.common.usermodel.GenericRecord;
- import org.apache.poi.util.GenericRecordUtil;
- import org.apache.poi.util.LittleEndian;
- import org.apache.poi.util.RecordFormatException;
-
- /**
- * This record defines the drawing groups used for a particular sheet.
- */
- public final class EscherDggRecord extends EscherRecord {
- public static final short RECORD_ID = EscherRecordTypes.DGG.typeID;
-
- private int field_1_shapeIdMax;
- // for some reason the number of clusters is actually the real number + 1
- // private int field_2_numIdClusters;
- private int field_3_numShapesSaved;
- private int field_4_drawingsSaved;
- private final List<FileIdCluster> field_5_fileIdClusters = new ArrayList<>();
- private int maxDgId;
-
- public static class FileIdCluster implements GenericRecord {
- private int field_1_drawingGroupId;
- private int field_2_numShapeIdsUsed;
-
- public FileIdCluster(FileIdCluster other) {
- field_1_drawingGroupId = other.field_1_drawingGroupId;
- field_2_numShapeIdsUsed = other.field_2_numShapeIdsUsed;
- }
-
- public FileIdCluster( int drawingGroupId, int numShapeIdsUsed ) {
- this.field_1_drawingGroupId = drawingGroupId;
- this.field_2_numShapeIdsUsed = numShapeIdsUsed;
- }
-
- public int getDrawingGroupId() {
- return field_1_drawingGroupId;
- }
-
- public int getNumShapeIdsUsed() {
- return field_2_numShapeIdsUsed;
- }
-
- private void incrementUsedShapeId() {
- field_2_numShapeIdsUsed++;
- }
-
- private static int compareFileIdCluster(FileIdCluster f1, FileIdCluster f2) {
- int dgDif = f1.getDrawingGroupId() - f2.getDrawingGroupId();
- int cntDif = f2.getNumShapeIdsUsed() - f1.getNumShapeIdsUsed();
- return (dgDif != 0) ? dgDif : cntDif;
- }
-
- @Override
- public Map<String, Supplier<?>> getGenericProperties() {
- return GenericRecordUtil.getGenericProperties(
- "drawingGroupId", this::getDrawingGroupId,
- "numShapeIdUsed", this::getNumShapeIdsUsed
- );
- }
- }
-
- public EscherDggRecord() {}
-
- public EscherDggRecord(EscherDggRecord other) {
- super(other);
- field_1_shapeIdMax = other.field_1_shapeIdMax;
- field_3_numShapesSaved = other.field_3_numShapesSaved;
- field_4_drawingsSaved = other.field_4_drawingsSaved;
- other.field_5_fileIdClusters.stream().map(FileIdCluster::new).forEach(field_5_fileIdClusters::add);
- maxDgId = other.maxDgId;
- }
-
- @Override
- public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
- int bytesRemaining = readHeader( data, offset );
- int pos = offset + 8;
- int size = 0;
- field_1_shapeIdMax = LittleEndian.getInt( data, pos + size );size+=4;
- // field_2_numIdClusters = LittleEndian.getInt( data, pos + size );
- size+=4;
- field_3_numShapesSaved = LittleEndian.getInt( data, pos + size );size+=4;
- field_4_drawingsSaved = LittleEndian.getInt( data, pos + size );size+=4;
-
- field_5_fileIdClusters.clear();
- // Can't rely on field_2_numIdClusters
- int numIdClusters = (bytesRemaining-size) / 8;
-
- for (int i = 0; i < numIdClusters; i++) {
- int drawingGroupId = LittleEndian.getInt( data, pos + size );
- int numShapeIdsUsed = LittleEndian.getInt( data, pos + size + 4 );
- FileIdCluster fic = new FileIdCluster(drawingGroupId, numShapeIdsUsed);
- field_5_fileIdClusters.add(fic);
- maxDgId = Math.max(maxDgId, drawingGroupId);
- size += 8;
- }
- bytesRemaining -= size;
- if (bytesRemaining != 0) {
- throw new RecordFormatException("Expecting no remaining data but got " + bytesRemaining + " byte(s).");
- }
- return 8 + size;
- }
-
- @Override
- public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
- listener.beforeRecordSerialize( offset, getRecordId(), this );
-
- int pos = offset;
- LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
- LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
- int remainingBytes = getRecordSize() - 8;
- LittleEndian.putInt( data, pos, remainingBytes ); pos += 4;
-
- LittleEndian.putInt( data, pos, field_1_shapeIdMax ); pos += 4;
- LittleEndian.putInt( data, pos, getNumIdClusters() ); pos += 4;
- LittleEndian.putInt( data, pos, field_3_numShapesSaved ); pos += 4;
- LittleEndian.putInt( data, pos, field_4_drawingsSaved ); pos += 4;
-
- for (FileIdCluster fic : field_5_fileIdClusters) {
- LittleEndian.putInt( data, pos, fic.getDrawingGroupId() ); pos += 4;
- LittleEndian.putInt( data, pos, fic.getNumShapeIdsUsed() ); pos += 4;
- }
-
- listener.afterRecordSerialize( pos, getRecordId(), getRecordSize(), this );
- return getRecordSize();
- }
-
- @Override
- public int getRecordSize() {
- return 8 + 16 + (8 * field_5_fileIdClusters.size());
- }
-
- @Override
- public short getRecordId() {
- return RECORD_ID;
- }
-
- @Override
- public String getRecordName() {
- return EscherRecordTypes.DGG.recordName;
- }
-
- /**
- * Gets the next available shape id
- *
- * @return the next available shape id
- */
- public int getShapeIdMax() {
- return field_1_shapeIdMax;
- }
-
- /**
- * The maximum is actually the next available shape id.
- *
- * @param shapeIdMax the next available shape id
- */
- public void setShapeIdMax(int shapeIdMax) {
- this.field_1_shapeIdMax = shapeIdMax;
- }
-
- /**
- * Number of id clusters + 1
- *
- * @return the number of id clusters + 1
- */
- public int getNumIdClusters() {
- return (field_5_fileIdClusters.isEmpty() ? 0 : field_5_fileIdClusters.size() + 1);
- }
-
- /**
- * Gets the number of shapes saved
- *
- * @return the number of shapes saved
- */
- public int getNumShapesSaved() {
- return field_3_numShapesSaved;
- }
-
- /**
- * Sets the number of shapes saved
- *
- * @param numShapesSaved the number of shapes saved
- */
- public void setNumShapesSaved(int numShapesSaved) {
- this.field_3_numShapesSaved = numShapesSaved;
- }
-
- /**
- * Gets the number of drawings saved
- *
- * @return the number of drawings saved
- */
- public int getDrawingsSaved() {
- return field_4_drawingsSaved;
- }
-
- /**
- * Sets the number of drawings saved
- *
- * @param drawingsSaved the number of drawings saved
- */
- public void setDrawingsSaved(int drawingsSaved) {
- this.field_4_drawingsSaved = drawingsSaved;
- }
-
- /**
- * Gets the maximum drawing group ID
- *
- * @return The maximum drawing group ID
- */
- public int getMaxDrawingGroupId() {
- return maxDgId;
- }
-
- /**
- * @return the file id clusters
- */
- public FileIdCluster[] getFileIdClusters() {
- return field_5_fileIdClusters.toArray(new FileIdCluster[0]);
- }
-
- /**
- * Sets the file id clusters
- *
- * @param fileIdClusters the file id clusters
- */
- public void setFileIdClusters(FileIdCluster[] fileIdClusters) {
- field_5_fileIdClusters.clear();
- if (fileIdClusters != null) {
- field_5_fileIdClusters.addAll(Arrays.asList(fileIdClusters));
- }
- }
-
-
- /**
- * Add a new cluster
- *
- * @param dgId id of the drawing group (stored in the record options)
- * @param numShapedUsed initial value of the numShapedUsed field
- *
- * @return the new {@link FileIdCluster}
- */
- public FileIdCluster addCluster(int dgId, int numShapedUsed) {
- return addCluster(dgId, numShapedUsed, true);
- }
-
- /**
- * Add a new cluster
- *
- * @param dgId id of the drawing group (stored in the record options)
- * @param numShapedUsed initial value of the numShapedUsed field
- * @param sort if true then sort clusters by drawing group id.(
- * In Excel the clusters are sorted but in PPT they are not)
- *
- * @return the new {@link FileIdCluster}
- */
- public FileIdCluster addCluster( int dgId, int numShapedUsed, boolean sort ) {
- FileIdCluster ficNew = new FileIdCluster(dgId, numShapedUsed);
- field_5_fileIdClusters.add(ficNew);
- maxDgId = Math.min(maxDgId, dgId);
-
- if (sort) {
- sortCluster();
- }
-
- return ficNew;
- }
-
- private void sortCluster() {
- field_5_fileIdClusters.sort(FileIdCluster::compareFileIdCluster);
- }
-
-
- /**
- * Finds the next available (1 based) drawing group id
- *
- * @return the next available drawing group id
- */
- public short findNewDrawingGroupId() {
- SparseBitSet bs = new SparseBitSet();
- bs.set(0);
- for (FileIdCluster fic : field_5_fileIdClusters) {
- bs.set(fic.getDrawingGroupId());
- }
- return (short)bs.nextClearBit(0);
- }
-
- /**
- * Allocates new shape id for the drawing group
- *
- * @param dg the EscherDgRecord which receives the new shape
- * @param sort if true then sort clusters by drawing group id.(
- * In Excel the clusters are sorted but in PPT they are not)
- *
- * @return a new shape id.
- */
- public int allocateShapeId(EscherDgRecord dg, boolean sort) {
- final short drawingGroupId = dg.getDrawingGroupId();
- field_3_numShapesSaved++;
-
- // check for an existing cluster, which has space available
- // see 2.2.46 OfficeArtIDCL (cspidCur) for the 1024 limitation
- // multiple clusters can belong to the same drawing group
- FileIdCluster ficAdd = null;
- int index = 1;
- for (FileIdCluster fic : field_5_fileIdClusters) {
- if (fic.getDrawingGroupId() == drawingGroupId
- && fic.getNumShapeIdsUsed() < 1024) {
- ficAdd = fic;
- break;
- }
- index++;
- }
-
- if (ficAdd == null) {
- ficAdd = addCluster( drawingGroupId, 0, sort );
- maxDgId = Math.max(maxDgId, drawingGroupId);
- }
-
- int shapeId = index*1024 + ficAdd.getNumShapeIdsUsed();
- ficAdd.incrementUsedShapeId();
-
- dg.setNumShapes( dg.getNumShapes() + 1 );
- dg.setLastMSOSPID( shapeId );
- field_1_shapeIdMax = Math.max(field_1_shapeIdMax, shapeId + 1);
-
- return shapeId;
- }
-
- @Override
- public Enum getGenericRecordType() {
- return EscherRecordTypes.DGG;
- }
-
- @Override
- public Map<String, Supplier<?>> getGenericProperties() {
- return GenericRecordUtil.getGenericProperties(
- "base", super::getGenericProperties,
- "fileIdClusters", () -> field_5_fileIdClusters,
- "shapeIdMax", this::getShapeIdMax,
- "numIdClusters", this::getNumIdClusters,
- "numShapesSaved", this::getNumShapesSaved,
- "drawingsSaved", this::getDrawingsSaved
- );
- }
-
- @Override
- public EscherDggRecord copy() {
- return new EscherDggRecord(this);
- }
- }
|