|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011 |
- /* ====================================================================
- 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.examples.ss;
-
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.net.URL;
- import java.util.Locale;
-
- import org.apache.poi.hssf.usermodel.HSSFSheet;
- import org.apache.poi.hssf.usermodel.HSSFWorkbook;
- import org.apache.poi.ss.usermodel.ClientAnchor;
- import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType;
- import org.apache.poi.ss.usermodel.Drawing;
- import org.apache.poi.ss.usermodel.Row;
- import org.apache.poi.ss.usermodel.Sheet;
- import org.apache.poi.ss.usermodel.Workbook;
- import org.apache.poi.ss.util.CellReference;
- import org.apache.poi.util.IOUtils;
-
-
- /**
- * Demonstrates how to add an image to a worksheet and set that images size
- * to a specific number of millimetres irrespective of the width of the columns
- * or height of the rows. Overridden methods are provided so that the location
- * of the image - the cells row and column coordinates that define the top
- * left hand corners of the image - can be identified either in the familiar
- * Excel manner - A1 for instance - or using POI's methodology of a column and
- * row index where 0, 0 would indicate cell A1.
- *
- * The best way to make use of these techniques is to delay adding the image to
- * the sheet until all other work has been completed. That way, the sizes of
- * all rows and columns will have been adjusted - assuming that step was
- * necessary. Even though the anchors type is set to prevent the image moving
- * or re-sizing, this setting does not have any effect until the sheet is being
- * viewed using the Excel application.
- *
- * The key to the process is the ClientAnchor class. It defines methods that allow
- * us to define the location of an image by specifying the following;
- *
- * * How far - in terms of coordinate positions - the image should be inset
- * from the left hand border of a cell.
- * * How far - in terms of coordinate positions - the image should be inset
- * from the from the top of the cell.
- * * How far - in terms of coordinate positions - the right hand edge of
- * the image should protrude into a cell (measured from the cells left hand
- * edge to the images right hand edge).
- * * How far - in terms of coordinate positions - the bottom edge of the
- * image should protrude into a row (measured from the cells top edge to
- * the images bottom edge).
- * * The index of the column that contains the cell whose top left hand
- * corner should be aligned with the top left hand corner of the image.
- * * The index of the row that contains the cell whose top left hand corner
- * should be aligned with the images top left hand corner.
- * * The index of the column that contains the cell whose top left hand
- * corner should be aligned with the images bottom right hand corner
- * * The index number of the row that contains the cell whose top left
- * hand corner should be aligned with the images bottom right hand corner.
- *
- * It can be used to add an image into cell A1, for example, in the following
- * manner;
- *
- * ClientAnchor anchor = sheet.getWorkbook().getCreationHelper().createClientAnchor();
- *
- * anchor.setDx1(0);
- * anchor.setDy1(0);
- * anchor.setDx2(0);
- * anchor.setDy2(0);
- * anchor.setCol1(0);
- * anchor.setRow1(0);
- * anchor.setCol2(1);
- * anchor.setRow2(1);
- *
- * Taken together, the first four methods define the locations of the top left
- * and bottom right hand corners of the image if you imagine that the image is
- * represented by a simple rectangle. The setDx1() and setDy1() methods locate
- * the top left hand corner of the image while setDx2() and and Dy2() locate the
- * bottom right hand corner of the image. An individual image can be inserted
- * into a single cell or is can lie across many cells and the latter four methods
- * are used to define just where the image should be positioned. They do this by
- * again by identifying where the top left and bottom right hand corners of the
- * image should be located but this time in terms of the indexes of the cells
- * in which those corners should be located. The setCol1() and setRow1() methods
- * together identify the cell that should contain the top left hand corner of
- * the image while setCol2() and setRow2() do the same for the images bottom
- * right hand corner.
- *
- * Knowing that, it is possible to look again at the example above and to see
- * that the top left hand corner of the image will be located in cell A1 (0, 0)
- * and it will be aligned with the very top left hand corner of the cell. Likewise,
- * the bottom right hand corner of the image will be located in cell B2 (1, 1) and
- * it will again be aligned with the top left hand corner of the cell. This has the
- * effect of making the image seem to occupy the whole of cell A1. Interestingly, it
- * also has an effect on the images resizing behaviour because testing has
- * demonstrated that if the image is wholly contained within one cell and is not
- * 'attached' for want of a better word, to a neighbouring cell, then that image
- * will not increase in size in response to the user dragging the column wider
- * or the cell higher.
- *
- * The following example demonstrates a slightly different way to insert an
- * image into cell A1 and to ensure that it occupies the whole of the cell. This
- * is accomplished by specifying the the images bottom right hand corner should be
- * aligned with the bottom right hand corner of the cell. It is also a case
- * where the image will not increase in size if the user increases the size of
- * the enclosing cell - irrespective of the anchors type - but it will reduce in
- * size if the cell is made smaller.
- *
- * ClientAnchor anchor = sheet.getWorkbook().getCreationHelper().createClientAnchor();
- *
- * anchor.setDx1(0);
- * anchor.setDy1(0);
- * anchor.setDx2(1023);
- * anchor.setDy2(255);
- * anchor.setCol1(0);
- * anchor.setRow1(0);
- * anchor.setCol2(0);
- * anchor.setRow2(0);
- *
- * Note that the final four method calls all pass the same value and seem to
- * indicate that the images top left hand corner is aligned with the top left
- * hand corner of cell A1 and that it's bottom right hand corner is also
- * aligned with the top left hand corner of cell A1. Yet, running this code
- * would see the image fully occupying cell A1. That is the result of the
- * values passed to parameters three and four; these I have referred to as
- * determining the images coordinates within the cell. They indicate that the
- * image should occupy - in order - the full width of the column and the full
- * height of the row.
- *
- * The co-ordinate values shown are the maxima; and they are independent of
- * row height/column width and of the font used. Passing 255 will always result
- * in the image occupying the full height of the row and passing 1023 will
- * always result in the image occupying the full width of the column. They help
- * in situations where an image is larger than a column/row and must overlap
- * into the next column/row. Using them does mean, however, that it is often
- * necessary to perform conversions between Excels characters units, points,
- * pixels and millimetres in order to establish how many rows/columns an image
- * should occupy and just what the various insets ought to be.
- *
- * Note that the setDx1(int) and setDy1(int) methods of the ClientAchor class
- * are not made use of in the code that follows. It would be fairly trivial
- * however to extend this example further and provide methods that would centre
- * an image within a cell or allow the user to specify that a plain border a
- * fixed number of millimetres wide should wrap around the image. Those first
- * two parameters would make this sort of functionality perfectly possible.
- *
- * Owing to the various conversions used, the actual size of the image may vary
- * from that required; testing has so far found this to be in the region of
- * plus or minus two millimetres. Most likely by modifying the way the
- * calculations are performed - possibly using double(s) throughout and
- * rounding the values at the correct point - it is likely that these errors
- * could be reduced or removed.
- *
- * A note concerning Excels image resizing behaviour. The ClientAnchor
- * class contains a method called setAnchorType(int) which can be used to
- * determine how Excel will resize an image in response to the user increasing
- * or decreasing the dimensions of the cell containing the image. There are
- * three values that can be passed to this method; 0 = To move and size the
- * image with the cell, 2 = To move but don't size the image with the cell,
- * 3 = To prevent the image from moving or being resized along with the cell. If
- * an image is inserted using this class and placed into a single cell then if
- * the setAnchorType(int) method is called and a value of either 0 or 2 passed
- * to it, the resultant resizing behaviour may be a surprise. The image will not
- * grow in size of the column is made wider or the row higher but it will shrink
- * if the columns width or rows height are reduced.
- *
- * @author Mark Beardsley [msb at apache.org] and Mark Southern [southern at scripps.edu]
- * @version 1.00 5th August 2009.
- * 2.00 26th February 2010.
- * Ported to make use of the the SS usermodel classes.
- * Ability to reuse the Drawing Patriarch so that multiple images
- * can be inserted without unintentionally erasing earlier images.
- * Check on image type added; i.e. jpg, jpeg or png.
- * The String used to contain the files name is now converted
- * into a URL.
- * 2.10 17th May 2012
- * Corrected gross error that occurred when using the code with
- * XSSF or SXSSF workbooks. In short, the code did not correctly
- * calculate the size of the image(s) owing to the use of EMUs
- * within the OOXML file format. That problem has largely been
- * corrected although it should be mentioned that images are not
- * sized with the same level of accuracy. Discrepancies of up to
- * 2mm have been noted in testing. Further investigation will
- * continue to rectify this issue.
- */
- @SuppressWarnings({"java:S106","java:S4823"})
- public class AddDimensionedImage {
-
- // Four constants that determine how - and indeed whether - the rows
- // and columns an image may overlie should be expanded to accommodate that
- // image.
- // Passing EXPAND_ROW will result in the height of a row being increased
- // to accommodate the image if it is not already larger. The image will
- // be layed across one or more columns.
- // Passing EXPAND_COLUMN will result in the width of the column being
- // increased to accommodate the image if it is not already larger. The image
- // will be layed across one or many rows.
- // Passing EXPAND_ROW_AND_COLUMN will result in the height of the row
- // bing increased along with the width of the column to accomdate the
- // image if either is not already larger.
- // Passing OVERLAY_ROW_AND_COLUMN will result in the image being layed
- // over one or more rows and columns. No row or column will be resized,
- // instead, code will determine how many rows and columns the image should
- // overlie.
- public static final int EXPAND_ROW = 1;
- public static final int EXPAND_COLUMN = 2;
- public static final int EXPAND_ROW_AND_COLUMN = 3;
- public static final int OVERLAY_ROW_AND_COLUMN = 7;
-
- // Modified to support EMU - English Metric Units - used within the OOXML
- // workbooks, this multoplier is used to convert between measurements in
- // millimetres and in EMUs
- private static final int EMU_PER_MM = 36000;
-
- /**
- * Add an image to a worksheet.
- *
- * @param cellNumber A String that contains the location of the cell whose
- * top left hand corner should be aligned with the top
- * left hand corner of the image; for example "A1", "A2"
- * etc. This is to support the familiar Excel syntax.
- * Whilst images are are not actually inserted into cells
- * this provides a convenient method of indicating where
- * the image should be positioned on the sheet.
- * @param sheet A reference to the sheet that contains the cell referenced
- * above.
- * @param drawing An instance of the DrawingPatriarch class. This is now
- * passed into the method where it was, previously, recovered
- * from the sheet in order to allow multiple pictures be
- * inserted. If the patriarch was not 'cached in this manner
- * each time it was created any previously positioned images
- * would be simply over-written.
- * @param imageFile An instance of the URL class that encapsulates the name
- * of and path to the image that is to be 'inserted into'
- * the sheet.
- * @param reqImageWidthMM A primitive double that contains the required
- * width of the image in millimetres.
- * @param reqImageHeightMM A primitive double that contains the required
- * height of the image in millimetres.
- * @param resizeBehaviour A primitive int whose value will determine how
- * the code should react if the image is larger than
- * the cell referenced by the cellNumber parameter.
- * Four constants are provided to determine what
- * should happen;
- * AddDimensionedImage.EXPAND_ROW
- * AddDimensionedImage.EXPAND_COLUMN
- * AddDimensionedImage.EXPAND_ROW_AND_COLUMN
- * AddDimensionedImage.OVERLAY_ROW_AND_COLUMN
- * @throws java.io.FileNotFoundException If the file containing the image
- * cannot be located.
- * @throws java.io.IOException If a problem occurs whilst reading the file
- * of image data.
- * @throws java.lang.IllegalArgumentException If an invalid value is passed
- * to the resizeBehaviour
- * parameter.
- */
- public void addImageToSheet(String cellNumber, Sheet sheet, Drawing<?> drawing,
- URL imageFile, double reqImageWidthMM, double reqImageHeightMM,
- int resizeBehaviour) throws IOException, IllegalArgumentException {
- // Convert the String into column and row indices then chain the
- // call to the overridden addImageToSheet() method.
- CellReference cellRef = new CellReference(cellNumber);
- this.addImageToSheet(cellRef.getCol(), cellRef.getRow(), sheet, drawing,
- imageFile, reqImageWidthMM, reqImageHeightMM,resizeBehaviour);
- }
-
- /**
- * Add an image to a worksheet.
- *
- * @param colNumber A primitive int that contains the index number of a
- * column on the worksheet; POI column indices are zero
- * based. Together with the rowNumber parameter's value,
- * this parameter identifies a cell on the worksheet. The
- * images top left hand corner will be aligned with the
- * top left hand corner of this cell.
- * @param rowNumber A primitive int that contains the index number of a row
- * on the worksheet; POI row indices are zero based.
- * Together with the rowNumber parameter's value, this
- * parameter identifies a cell on the worksheet. The
- * images top left hand corner will be aligned with the
- * top left hand corner of this cell.
- * @param sheet A reference to the sheet that contains the cell identified
- * by the two parameters above.
- * @param drawing An instance of the DrawingPatriarch class. This is now
- * passed into the method where it was, previously, recovered
- * from the sheet in order to allow multiple pictures be
- * inserted. If the patriarch was not 'cached in this manner
- * each time it was created any previously positioned images
- * would be simply over-written.
- * @param imageFile An instance of the URL class that encapsulates the name
- * of and path to the image that is to be 'inserted into'
- * the sheet.
- * @param reqImageWidthMM A primitive double that contains the required
- * width of the image in millimetres.
- * @param reqImageHeightMM A primitive double that contains the required
- * height of the image in millimetres.
- * @param resizeBehaviour A primitive int whose value will determine how
- * the code should react if the image is larger than
- * the cell referenced by the colNumber and
- * rowNumber parameters. Four constants are provided
- * to determine what should happen;
- * AddDimensionedImage.EXPAND_ROW
- * AddDimensionedImage.EXPAND_COLUMN
- * AddDimensionedImage.EXPAND_ROW_AND_COLUMN
- * AddDimensionedImage.OVERLAY_ROW_AND_COLUMN
- * @throws java.io.FileNotFoundException If the file containing the image
- * cannot be located.
- * @throws java.io.IOException If a problem occurs whilst reading the file
- * of image data.
- * @throws java.lang.IllegalArgumentException If an invalid value is passed
- * to the resizeBehaviour
- * parameter or if the extension
- * of the image file indicates that
- * it is of a type that cannot
- * currently be added to the worksheet.
- */
- public void addImageToSheet(int colNumber, int rowNumber, Sheet sheet, Drawing<?> drawing,
- URL imageFile, double reqImageWidthMM, double reqImageHeightMM,
- int resizeBehaviour) throws IOException,
- IllegalArgumentException {
- ClientAnchor anchor;
- ClientAnchorDetail rowClientAnchorDetail;
- ClientAnchorDetail colClientAnchorDetail;
- int imageType;
-
- // Validate the resizeBehaviour parameter.
- if((resizeBehaviour != AddDimensionedImage.EXPAND_COLUMN) &&
- (resizeBehaviour != AddDimensionedImage.EXPAND_ROW) &&
- (resizeBehaviour != AddDimensionedImage.EXPAND_ROW_AND_COLUMN) &&
- (resizeBehaviour != AddDimensionedImage.OVERLAY_ROW_AND_COLUMN)) {
- throw new IllegalArgumentException("Invalid value passed to the " +
- "resizeBehaviour parameter of AddDimensionedImage.addImageToSheet()");
- }
-
- // Call methods to calculate how the image and sheet should be
- // manipulated to accommodate the image; columns and then rows.
- colClientAnchorDetail = this.fitImageToColumns(sheet, colNumber,
- reqImageWidthMM, resizeBehaviour);
- rowClientAnchorDetail = this.fitImageToRows(sheet, rowNumber,
- reqImageHeightMM, resizeBehaviour);
-
- // Having determined if and how to resize the rows, columns and/or the
- // image, create the ClientAnchor object to position the image on
- // the worksheet. Note how the two ClientAnchorDetail records are
- // interrogated to recover the row/column co-ordinates and any insets.
- // The first two parameters are not used currently but could be if the
- // need arose to extend the functionality of this code by adding the
- // ability to specify that a clear 'border' be placed around the image.
- anchor = sheet.getWorkbook().getCreationHelper().createClientAnchor();
-
- anchor.setDx1(0);
- anchor.setDy1(0);
- if (colClientAnchorDetail != null) {
- anchor.setDx2(colClientAnchorDetail.getInset());
- anchor.setCol1(colClientAnchorDetail.getFromIndex());
- anchor.setCol2(colClientAnchorDetail.getToIndex());
- }
- if (rowClientAnchorDetail != null) {
- anchor.setDy2(rowClientAnchorDetail.getInset());
- anchor.setRow1(rowClientAnchorDetail.getFromIndex());
- anchor.setRow2(rowClientAnchorDetail.getToIndex());
- }
-
- // For now, set the anchor type to do not move or resize the
- // image as the size of the row/column is adjusted. This could easily
- // become another parameter passed to the method. Please read the note
- // above regarding the behaviour of image resizing.
- anchor.setAnchorType(AnchorType.MOVE_AND_RESIZE);
-
- // Now, add the picture to the workbook. Note that unlike the similar
- // method in the HSSF Examples section, the image type is checked. First,
- // the image files location is identified by interrogating the URL passed
- // to the method, the images type is identified before it is added to the
- // sheet.
- String sURL = imageFile.toString().toLowerCase(Locale.ROOT);
- if( sURL.endsWith(".png") ) {
- imageType = Workbook.PICTURE_TYPE_PNG;
- }
- else if( sURL.endsWith(".jpg") || sURL.endsWith(".jpeg") ) {
- imageType = Workbook.PICTURE_TYPE_JPEG;
- }
- else {
- throw new IllegalArgumentException("Invalid Image file : " +
- sURL);
- }
- int index = sheet.getWorkbook().addPicture(
- IOUtils.toByteArray(imageFile.openStream()), imageType);
- drawing.createPicture(anchor, index);
- }
-
- /**
- * Determines whether the sheets columns should be re-sized to accommodate
- * the image, adjusts the columns width if necessary and creates then
- * returns a ClientAnchorDetail object that facilitates construction of
- * an ClientAnchor that will fix the image on the sheet and establish
- * it's size.
- *
- * @param sheet A reference to the sheet that will 'contain' the image.
- * @param colNumber A primitive int that contains the index number of a
- * column on the sheet.
- * @param reqImageWidthMM A primitive double that contains the required
- * width of the image in millimetres
- * @param resizeBehaviour A primitive int whose value will indicate how the
- * width of the column should be adjusted if the
- * required width of the image is greater than the
- * width of the column.
- * @return An instance of the ClientAnchorDetail class that will contain
- * the index number of the column containing the cell whose top
- * left hand corner also defines the top left hand corner of the
- * image, the index number column containing the cell whose top
- * left hand corner also defines the bottom right hand corner of
- * the image and an inset that determines how far the right hand
- * edge of the image can protrude into the next column - expressed
- * as a specific number of coordinate positions.
- */
- private ClientAnchorDetail fitImageToColumns(Sheet sheet, int colNumber,
- double reqImageWidthMM, int resizeBehaviour) {
-
- double colWidthMM;
- double colCoordinatesPerMM;
- int pictureWidthCoordinates;
- ClientAnchorDetail colClientAnchorDetail = null;
-
- // Get the colum's width in millimetres
- colWidthMM = ConvertImageUnits.widthUnits2Millimetres(
- (short)sheet.getColumnWidth(colNumber));
-
- // Check that the column's width will accommodate the image at the
- // required dimension. If the width of the column is LESS than the
- // required width of the image, decide how the application should
- // respond - resize the column or overlay the image across one or more
- // columns.
- if(colWidthMM < reqImageWidthMM) {
-
- // Should the column's width simply be expanded?
- if((resizeBehaviour == AddDimensionedImage.EXPAND_COLUMN) ||
- (resizeBehaviour == AddDimensionedImage.EXPAND_ROW_AND_COLUMN)) {
- // Set the width of the column by converting the required image
- // width from millimetres into Excel's column width units.
- sheet.setColumnWidth(colNumber,
- ConvertImageUnits.millimetres2WidthUnits(reqImageWidthMM));
- // To make the image occupy the full width of the column, convert
- // the required width of the image into co-ordinates. This value
- // will become the inset for the ClientAnchorDetail class that
- // is then instantiated.
- if(sheet instanceof HSSFSheet) {
- colWidthMM = reqImageWidthMM;
- colCoordinatesPerMM = colWidthMM == 0 ? 0
- : ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS / colWidthMM;
- pictureWidthCoordinates = (int)(reqImageWidthMM * colCoordinatesPerMM);
-
- }
- else {
- pictureWidthCoordinates = (int)reqImageWidthMM * AddDimensionedImage.EMU_PER_MM;
- }
- colClientAnchorDetail = new ClientAnchorDetail(colNumber,
- colNumber, pictureWidthCoordinates);
- }
- // If the user has chosen to overlay both rows and columns or just
- // to expand ONLY the size of the rows, then calculate how to lay
- // the image out across one or more columns.
- else if ((resizeBehaviour == AddDimensionedImage.OVERLAY_ROW_AND_COLUMN) ||
- (resizeBehaviour == AddDimensionedImage.EXPAND_ROW)) {
- colClientAnchorDetail = this.calculateColumnLocation(sheet,
- colNumber, reqImageWidthMM);
- }
- }
- // If the column is wider than the image.
- else {
- if(sheet instanceof HSSFSheet) {
- // Mow many co-ordinate positions are there per millimetre?
- colCoordinatesPerMM = ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS /
- colWidthMM;
- // Given the width of the image, what should be it's co-ordinate?
- pictureWidthCoordinates = (int)(reqImageWidthMM * colCoordinatesPerMM);
- }
- else {
- pictureWidthCoordinates = (int)reqImageWidthMM *
- AddDimensionedImage.EMU_PER_MM;
- }
- colClientAnchorDetail = new ClientAnchorDetail(colNumber,
- colNumber, pictureWidthCoordinates);
- }
- return(colClientAnchorDetail);
- }
-
- /**
- * Determines whether the sheets row should be re-sized to accommodate
- * the image, adjusts the rows height if necessary and creates then
- * returns a ClientAnchorDetail object that facilitates construction of
- * a ClientAnchor that will fix the image on the sheet and establish
- * it's size.
- *
- * @param sheet A reference to the sheet that will 'contain' the image.
- * @param rowNumber A primitive int that contains the index number of a
- * row on the sheet.
- * @param reqImageHeightMM A primitive double that contains the required
- * height of the image in millimetres
- * @param resizeBehaviour A primitive int whose value will indicate how the
- * height of the row should be adjusted if the
- * required height of the image is greater than the
- * height of the row.
- * @return An instance of the ClientAnchorDetail class that will contain
- * the index number of the row containing the cell whose top
- * left hand corner also defines the top left hand corner of the
- * image, the index number of the row containing the cell whose
- * top left hand corner also defines the bottom right hand
- * corner of the image and an inset that determines how far the
- * bottom edge of the image can protrude into the next (lower)
- * row - expressed as a specific number of coordinate positions.
- */
- private ClientAnchorDetail fitImageToRows(Sheet sheet, int rowNumber,
- double reqImageHeightMM, int resizeBehaviour) {
- Row row;
- double rowHeightMM;
- double rowCoordinatesPerMM;
- int pictureHeightCoordinates;
- ClientAnchorDetail rowClientAnchorDetail = null;
-
- // Get the row and it's height
- row = sheet.getRow(rowNumber);
- if(row == null) {
- // Create row if it does not exist.
- row = sheet.createRow(rowNumber);
- }
-
- // Get the row's height in millimetres
- rowHeightMM = row.getHeightInPoints() / ConvertImageUnits.POINTS_PER_MILLIMETRE;
-
- // Check that the row's height will accommodate the image at the required
- // dimensions. If the height of the row is LESS than the required height
- // of the image, decide how the application should respond - resize the
- // row or overlay the image across a series of rows.
- if(rowHeightMM < reqImageHeightMM) {
- if((resizeBehaviour == AddDimensionedImage.EXPAND_ROW) ||
- (resizeBehaviour == AddDimensionedImage.EXPAND_ROW_AND_COLUMN)) {
- row.setHeightInPoints((float)(reqImageHeightMM *
- ConvertImageUnits.POINTS_PER_MILLIMETRE));
- if(sheet instanceof HSSFSheet) {
- rowHeightMM = reqImageHeightMM;
- rowCoordinatesPerMM = rowHeightMM == 0 ? 0
- : ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS / rowHeightMM;
- pictureHeightCoordinates = (int)(reqImageHeightMM *
- rowCoordinatesPerMM);
- }
- else {
- pictureHeightCoordinates = (int)(reqImageHeightMM *
- AddDimensionedImage.EMU_PER_MM);
- }
- rowClientAnchorDetail = new ClientAnchorDetail(rowNumber,
- rowNumber, pictureHeightCoordinates);
- }
- // If the user has chosen to overlay both rows and columns or just
- // to expand ONLY the size of the columns, then calculate how to lay
- // the image out ver one or more rows.
- else if((resizeBehaviour == AddDimensionedImage.OVERLAY_ROW_AND_COLUMN) ||
- (resizeBehaviour == AddDimensionedImage.EXPAND_COLUMN)) {
- rowClientAnchorDetail = this.calculateRowLocation(sheet,
- rowNumber, reqImageHeightMM);
- }
- }
- // Else, if the image is smaller than the space available
- else {
- if(sheet instanceof HSSFSheet) {
- rowCoordinatesPerMM = ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS /
- rowHeightMM;
- pictureHeightCoordinates = (int)(reqImageHeightMM * rowCoordinatesPerMM);
- }
- else {
- pictureHeightCoordinates = (int)(reqImageHeightMM *
- AddDimensionedImage.EMU_PER_MM);
- }
- rowClientAnchorDetail = new ClientAnchorDetail(rowNumber,
- rowNumber, pictureHeightCoordinates);
- }
- return(rowClientAnchorDetail);
- }
-
- /**
- * If the image is to overlie more than one column, calculations need to be
- * performed to determine how many columns and whether the image will
- * overlie just a part of one column in order to be presented at the
- * required size.
- *
- * @param sheet The sheet that will 'contain' the image.
- * @param startingColumn A primitive int whose value is the index of the
- * column that contains the cell whose top left hand
- * corner should be aligned with the top left hand
- * corner of the image.
- * @param reqImageWidthMM A primitive double whose value will indicate the
- * required width of the image in millimetres.
- * @return An instance of the ClientAnchorDetail class that will contain
- * the index number of the column containing the cell whose top
- * left hand corner also defines the top left hand corner of the
- * image, the index number column containing the cell whose top
- * left hand corner also defines the bottom right hand corner of
- * the image and an inset that determines how far the right hand
- * edge of the image can protrude into the next column - expressed
- * as a specific number of coordinate positions.
- */
- private ClientAnchorDetail calculateColumnLocation(Sheet sheet,
- int startingColumn,
- double reqImageWidthMM) {
- ClientAnchorDetail anchorDetail;
- double totalWidthMM = 0.0D;
- double colWidthMM = 0.0D;
- double overlapMM;
- double coordinatePositionsPerMM;
- int toColumn = startingColumn;
- int inset;
-
- // Calculate how many columns the image will have to
- // span in order to be presented at the required size.
- while(totalWidthMM < reqImageWidthMM) {
- colWidthMM = ConvertImageUnits.widthUnits2Millimetres(
- (short)(sheet.getColumnWidth(toColumn)));
- // Note use of the cell border width constant. Testing with an image
- // declared to fit exactly into one column demonstrated that it's
- // width was greater than the width of the column the POI returned.
- // Further, this difference was a constant value that I am assuming
- // related to the cell's borders. Either way, that difference needs
- // to be allowed for in this calculation.
- totalWidthMM += (colWidthMM + ConvertImageUnits.CELL_BORDER_WIDTH_MILLIMETRES);
- toColumn++;
- }
- // De-crement by one the last column value.
- toColumn--;
- // Highly unlikely that this will be true but, if the width of a series
- // of columns is exactly equal to the required width of the image, then
- // simply build a ClientAnchorDetail object with an inset equal to the
- // total number of co-ordinate positions available in a column, a
- // from column co-ordinate (top left hand corner) equal to the value
- // of the startingColumn parameter and a to column co-ordinate equal
- // to the toColumn variable.
- //
- // Convert both values to ints to perform the test.
- if((int)totalWidthMM == (int)reqImageWidthMM) {
- // A problem could occur if the image is sized to fit into one or
- // more columns. If that occurs, the value in the toColumn variable
- // will be in error. To overcome this, there are two options, to
- // ibcrement the toColumn variable's value by one or to pass the
- // total number of co-ordinate positions to the third paramater
- // of the ClientAnchorDetail constructor. For no sepcific reason,
- // the latter option is used below.
- if(sheet instanceof HSSFSheet) {
- anchorDetail = new ClientAnchorDetail(startingColumn,
- toColumn, ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS);
- }
- else {
- anchorDetail = new ClientAnchorDetail(startingColumn,
- toColumn, (int)reqImageWidthMM * AddDimensionedImage.EMU_PER_MM);
- }
- }
- // In this case, the image will overlap part of another column and it is
- // necessary to calculate just how much - this will become the inset
- // for the ClientAnchorDetail object.
- else {
- // Firstly, claculate how much of the image should overlap into
- // the next column.
- overlapMM = reqImageWidthMM - (totalWidthMM - colWidthMM);
-
- // When the required size is very close indded to the column size,
- // the calcaulation above can produce a negative value. To prevent
- // problems occuring in later caculations, this is simply removed
- // be setting the overlapMM value to zero.
- if(overlapMM < 0) {
- overlapMM = 0.0D;
- }
-
- if(sheet instanceof HSSFSheet) {
- // Next, from the columns width, calculate how many co-ordinate
- // positons there are per millimetre
- coordinatePositionsPerMM = (colWidthMM == 0) ? 0
- : ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS / colWidthMM;
- // From this figure, determine how many co-ordinat positions to
- // inset the left hand or bottom edge of the image.
- inset = (int)(coordinatePositionsPerMM * overlapMM);
- }
- else {
- inset = (int)overlapMM * AddDimensionedImage.EMU_PER_MM;
- }
-
- // Now create the ClientAnchorDetail object, setting the from and to
- // columns and the inset.
- anchorDetail = new ClientAnchorDetail(startingColumn, toColumn, inset);
- }
- return(anchorDetail);
- }
-
- /**
- * If the image is to overlie more than one rows, calculations need to be
- * performed to determine how many rows and whether the image will
- * overlie just a part of one row in order to be presented at the
- * required size.
- *
- * @param sheet The sheet that will 'contain' the image.
- * @param startingRow A primitive int whose value is the index of the row
- * that contains the cell whose top left hand corner
- * should be aligned with the top left hand corner of
- * the image.
- * @param reqImageHeightMM A primitive double whose value will indicate the
- * required height of the image in millimetres.
- * @return An instance of the ClientAnchorDetail class that will contain
- * the index number of the row containing the cell whose top
- * left hand corner also defines the top left hand corner of the
- * image, the index number of the row containing the cell whose top
- * left hand corner also defines the bottom right hand corner of
- * the image and an inset that determines how far the bottom edge
- * can protrude into the next (lower) row - expressed as a specific
- * number of co-ordinate positions.
- */
- private ClientAnchorDetail calculateRowLocation(Sheet sheet,
- int startingRow, double reqImageHeightMM) {
- ClientAnchorDetail clientAnchorDetail;
- Row row;
- double rowHeightMM = 0.0D;
- double totalRowHeightMM = 0.0D;
- double overlapMM;
- double rowCoordinatesPerMM;
- int toRow = startingRow;
- int inset;
-
- // Step through the rows in the sheet and accumulate a total of their
- // heights.
- while(totalRowHeightMM < reqImageHeightMM) {
- row = sheet.getRow(toRow);
- // Note, if the row does not already exist on the sheet then create
- // it here.
- if(row == null) {
- row = sheet.createRow(toRow);
- }
- // Get the row's height in millimetres and add to the running total.
- rowHeightMM = row.getHeightInPoints() /
- ConvertImageUnits.POINTS_PER_MILLIMETRE;
- totalRowHeightMM += rowHeightMM;
- toRow++;
- }
- // Owing to the way the loop above works, the rowNumber will have been
- // incremented one row too far. Undo that here.
- toRow--;
- // Check to see whether the image should occupy an exact number of
- // rows. If so, build the ClientAnchorDetail record to point
- // to those rows and with an inset of the total number of co-ordinate
- // position in the row.
- //
- // To overcome problems that can occur with comparing double values for
- // equality, cast both to int(s) to truncate the value; VERY crude and
- // I do not really like it!!
- if((int)totalRowHeightMM == (int)reqImageHeightMM) {
- if(sheet instanceof HSSFSheet) {
- clientAnchorDetail = new ClientAnchorDetail(startingRow, toRow,
- ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS);
- }
- else {
- clientAnchorDetail = new ClientAnchorDetail(startingRow, toRow,
- (int)reqImageHeightMM * AddDimensionedImage.EMU_PER_MM);
- }
- }
- else {
- // Calculate how far the image will project into the next row. Note
- // that the height of the last row assessed is subtracted from the
- // total height of all rows assessed so far.
- overlapMM = reqImageHeightMM - (totalRowHeightMM - rowHeightMM);
-
- // To prevent an exception being thrown when the required width of
- // the image is very close indeed to the column size.
- if(overlapMM < 0) {
- overlapMM = 0.0D;
- }
-
- if(sheet instanceof HSSFSheet) {
- rowCoordinatesPerMM = (rowHeightMM == 0) ? 0
- : ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS / rowHeightMM;
- inset = (int)(overlapMM * rowCoordinatesPerMM);
- }
- else {
- inset = (int)overlapMM * AddDimensionedImage.EMU_PER_MM;
- }
- clientAnchorDetail = new ClientAnchorDetail(startingRow,
- toRow, inset);
- }
- return(clientAnchorDetail);
- }
-
- /**
- * The main entry point to the program. It contains code that demonstrates
- * one way to use the program.
- *
- * Note, the code is not restricted to use on new workbooks only. If an
- * image is to be inserted into an existing workbook. just open that
- * workbook, gat a reference to a sheet and pass that;
- *
- * AddDimensionedImage addImage = new AddDimensionedImage();
- *
- * File file = new File("....... Existing Workbook .......");
- * FileInputStream fis = new FileInputStream(file);
- * Workbook workbook = new HSSFWorkbook(fis);
- * HSSFSheet sheet = workbook.getSheetAt(0);
- * addImage.addImageToSheet("C3", sheet, "image.jpg", 30, 20,
- * AddDimensionedImage.EXPAND.ROW);
- *
- * @param args the command line arguments
- */
- public static void main(String[] args) throws IOException {
- if(args.length < 2){
- System.err.println("Usage: AddDimensionedImage imageFile outputFile");
- return;
- }
-
- final String imageFile = args[0];
- final String outputFile = args[1];
-
- try (final Workbook workbook = new HSSFWorkbook();
- final FileOutputStream fos = new FileOutputStream(outputFile)) { // OR XSSFWorkbook
- Sheet sheet = workbook.createSheet("Picture Test");
- new AddDimensionedImage().addImageToSheet("B5", sheet, sheet.createDrawingPatriarch(),
- new File(imageFile).toURI().toURL(), 100, 40,
- AddDimensionedImage.EXPAND_ROW_AND_COLUMN);
- workbook.write(fos);
- }
- }
-
- /**
- * The HSSFClientAnchor class accepts eight arguments. In order, these are;
- *
- * * How far the left hand edge of the image is inset from the left hand
- * edge of the cell
- * * How far the top edge of the image is inset from the top of the cell
- * * How far the right hand edge of the image is inset from the left
- * hand edge of the cell
- * * How far the bottom edge of the image is inset from the top of the
- * cell.
- * * Together, arguments five and six determine the column and row
- * coordinates of the cell whose top left hand corner will be aligned
- * with the images top left hand corner.
- * * Together, arguments seven and eight determine the column and row
- * coordinates of the cell whose top left hand corner will be aligned
- * with the images bottom right hand corner.
- *
- * An instance of the ClientAnchorDetail class provides three of the eight
- * parameters, one of the coordinates for the images top left hand corner,
- * one of the coordinates for the images bottom right hand corner and
- * either how far the image should be inset from the top or the left hand
- * edge of the cell.
- *
- * @author Mark Beardsley [msb at apache.org]
- * @version 1.00 5th August 2009.
- */
- public class ClientAnchorDetail {
-
- private int fromIndex;
- private int toIndex;
- private int inset;
-
- /**
- * Create a new instance of the ClientAnchorDetail class using the
- * following parameters.
- *
- * @param fromIndex A primitive int that contains one of the
- * coordinates (row or column index) for the top left
- * hand corner of the image.
- * @param toIndex A primitive int that contains one of the
- * coordinates (row or column index) for the bottom
- * right hand corner of the image.
- * @param inset A primitive int that contains a value which indicates
- * how far the image should be inset from the top or the
- * left hand edge of a cell.
- */
- public ClientAnchorDetail(int fromIndex, int toIndex, int inset) {
- this.fromIndex = fromIndex;
- this.toIndex = toIndex;
- this.inset = inset;
- }
-
- /**
- * Get one of the number of the column or row that contains the cell
- * whose top left hand corner will be aligned with the top left hand
- * corner of the image.
- *
- * @return The value - row or column index - for one of the coordinates
- * of the top left hand corner of the image.
- */
- public int getFromIndex() {
- return(this.fromIndex);
- }
-
- /**
- * Get one of the number of the column or row that contains the cell
- * whose top left hand corner will be aligned with the bottom right hand
- * corner of the image.
- *
- * @return The value - row or column index - for one of the coordinates
- * of the bottom right hand corner of the image.
- */
- public int getToIndex() {
- return(this.toIndex);
- }
-
- /**
- * Get the images offset from the edge of a cell.
- *
- * @return How far either the right hand or bottom edge of the image is
- * inset from the left hand or top edge of a cell.
- */
- public int getInset() {
- return(this.inset);
- }
- }
-
- /**
- * Utility methods used to convert Excels character based column and row
- * size measurements into pixels and/or millimetres. The class also contains
- * various constants that are required in other calculations.
- *
- * @author xio[darjino@hotmail.com]
- * @version 1.01 30th July 2009.
- * Added by Mark Beardsley [msb at apache.org].
- * Additional constants.
- * widthUnits2Millimetres() and millimetres2Units() methods.
- */
- public static class ConvertImageUnits {
-
- // Each cell conatins a fixed number of co-ordinate points; this number
- // does not vary with row height or column width or with font. These two
- // constants are defined below.
- public static final int TOTAL_COLUMN_COORDINATE_POSITIONS = 1023;
- public static final int TOTAL_ROW_COORDINATE_POSITIONS = 255;
- // The resoultion of an image can be expressed as a specific number
- // of pixels per inch. Displays and printers differ but 96 pixels per
- // inch is an acceptable standard to beging with.
- public static final int PIXELS_PER_INCH = 96;
- // Cnstants that defines how many pixels and points there are in a
- // millimetre. These values are required for the conversion algorithm.
- public static final double PIXELS_PER_MILLIMETRES = 3.78;
- public static final double POINTS_PER_MILLIMETRE = 2.83;
- // The column width returned by HSSF and the width of a picture when
- // positioned to exactly cover one cell are different by almost exactly
- // 2mm - give or take rounding errors. This constant allows that
- // additional amount to be accounted for when calculating how many
- // celles the image ought to overlie.
- public static final double CELL_BORDER_WIDTH_MILLIMETRES = 2.0D;
- public static final short EXCEL_COLUMN_WIDTH_FACTOR = 256;
- public static final int UNIT_OFFSET_LENGTH = 7;
- private static final int[] UNIT_OFFSET_MAP = { 0, 36, 73, 109, 146, 182, 219 };
-
- /**
- * pixel units to excel width units(units of 1/256th of a character width)
- */
- public static short pixel2WidthUnits(int pxs) {
- short widthUnits = (short) (EXCEL_COLUMN_WIDTH_FACTOR *
- (pxs / UNIT_OFFSET_LENGTH));
- widthUnits += UNIT_OFFSET_MAP[(pxs % UNIT_OFFSET_LENGTH)];
- return widthUnits;
- }
-
- /**
- * excel width units(units of 1/256th of a character width) to pixel
- * units.
- */
- public static int widthUnits2Pixel(short widthUnits) {
- int pixels = (widthUnits / EXCEL_COLUMN_WIDTH_FACTOR)
- * UNIT_OFFSET_LENGTH;
- int offsetWidthUnits = widthUnits % EXCEL_COLUMN_WIDTH_FACTOR;
- pixels += Math.round(offsetWidthUnits /
- ((float) EXCEL_COLUMN_WIDTH_FACTOR / UNIT_OFFSET_LENGTH));
- return pixels;
- }
-
- /**
- * Convert Excels width units into millimetres.
- *
- * @param widthUnits The width of the column or the height of the
- * row in Excels units.
- * @return A primitive double that contains the columns width or rows
- * height in millimetres.
- */
- public static double widthUnits2Millimetres(short widthUnits) {
- return(ConvertImageUnits.widthUnits2Pixel(widthUnits) /
- ConvertImageUnits.PIXELS_PER_MILLIMETRES);
- }
-
- /**
- * Convert into millimetres Excels width units..
- *
- * @param millimetres A primitive double that contains the columns
- * width or rows height in millimetres.
- * @return A primitive int that contains the columns width or rows
- * height in Excels units.
- */
- public static int millimetres2WidthUnits(double millimetres) {
- return(ConvertImageUnits.pixel2WidthUnits((int)(millimetres *
- ConvertImageUnits.PIXELS_PER_MILLIMETRES)));
- }
- }
- }
|