今日もプログラミング

IT技術とかプログラミングのこととか特にJavaを中心に書いていきます

POIの座標について調べてみる

業務システムでは「Excelファイルをアップロードしてデータを入力したい」「検索結果をExcelファイルでダウンロードしたい」というような要求が日常茶飯事だ。

そんなときに役に立つのが、ApacheのPOI。

 

POIではセルの値を読み書きするだけでなく、図形を描くことだってできる。

しかし、どうやって座標を設定すればいいの?と悩んでしまう。

そこで、POIの座標について調べてみた。

 

POIで行の高さと列の幅を取得してみる

まずは、POIで行の高さと列の幅を取得してみた。

    public static void main(String[] args) throws IOException {
        try (Workbook workbook = new XSSFWorkbook()) {
            Sheet sheet = workbook.createSheet();
            Row row = sheet.createRow(0);

            System.out.println("行1の高さ(ポイント)=" + row.getHeightInPoints());
            System.out.println("列Aの幅(ピクセル)=" + sheet.getColumnWidthInPixels(0));
            
            File file = new File("test.xlsx");
            try (OutputStream out = new FileOutputStream(file)) {
                workbook.write(out);
            }
        }
    }

 

実行すると、

行1の高さ(ポイント)=15.0
列Aの幅(ピクセル)=56.0136

となった。

 

Excelと高さが違う?

実際にtest.xlsxを開いてみると…、行1の高さは「13.50(18ピクセル)」、列Aの幅は「8.38(72ピクセル)」と出る。

高さの13.5というのは、ポイント単位らしい。

あれ?15.0ではない?

そこでPOIのXSSFRowクラスのソースを見てみると、

    public float getHeightInPoints() {
        if (this._row.isSetHt()) {
            return (float) this._row.getHt();
        }
        return _sheet.getDefaultRowHeightInPoints();
    }

のようになっている。つまり、行の高さが設定されていない場合は、シートのデフォルト値を見に行くようだ。

 

更に、test.xlsxの拡張子を.zipに変えて展開し、xl/worksheets/sheet1.xmlを見てみる。

<sheetFormatPr defaultRowHeight="15.0"/>

となっていて、この値が返されているというみたいだ。

このデフォルト値は実際にはExcelで使われていないのだろうか?

 

試したところ、デフォルトのままではなく、明示的にrow.setHeightInPointsを指定してxlsxを出力すると、Excelで見える値と合うようだ。

 

Excelと幅も違う?

一方幅の方は、XSSFSheetのソースを見ると、

    public int getColumnWidth(int columnIndex) {
        CTCol col = columnHelper.getColumn(columnIndex, false);
        double width = col == null || !col.isSetWidth() ? getDefaultColumnWidth() : col.getWidth();
        return (int)(width*256);
    }

    @Override
    public float getColumnWidthInPixels(int columnIndex) {
        float widthIn256 = getColumnWidth(columnIndex);
        return (float)(widthIn256/256.0*XSSFWorkbook.DEFAULT_CHARACTER_WIDTH);
    }
    
    public int getDefaultColumnWidth() {
        CTSheetFormatPr pr = worksheet.getSheetFormatPr();
        return pr == null ? 8 : (int)pr.getBaseColWidth();
    }

のようになっている。

DEFAULT_CHARACTER_WIDTHは7.0017fと定義されているので、8 * 256 / 256 * 7.0017f = 56.0136が返されたということか。

こっちも、Excelのデフォルト値と違うなあ…。

 

試しにsheet.setColumnWidth(0, 8*256)でExcelブックを作ると、Excel上では64ピクセルになった。

72ピクセルにするには、9*256を設定する必要があるようだ。

9の意味は不明…。

 

フォントサイズを変えるとどうなる?

でも、たぶんデフォルトの幅とか高さはフォントサイズに依存するよね。

自分の環境のExcelでは11がデフォルトだ。

 

POIのAPIを見たが、デフォルトフォントらしきものは見当たらない。

しかし、例によってxlsxファイルをzipとして展開すると、xl/styles.xml

  ...
  <fonts x14ac:knownfonts="1" count="2">
      <sz val="11"></sz>
      ...
  </fonts>

というのがあった。ということは、

   workbook.getFontAt((short)0).setFontHeightInPoints((short)16); 

でいけるかな?やってみたら、実際デフォルトフォントサイズが16のExcelブックを作れた。

でも、Excelの行の高さは18.75、列幅は8.09(96ピクセル)と大きくなったが、POIから取得したデフォルトの高さと幅は11ポイントの時と同じだった。

 

デフォルトではなく明示的に列幅を sheet.setColumnWidth(0, 9*256) のように設定したらどうなるか?

Excel上では99ピクセルになった。やっぱり、フォントサイズに依存するようだ。

 

いろいろなフォントサイズで試したところ、

Excel上のピクセル数 = columnWidth / 256 * ceil(フォントサイズ / 1.5)

が成り立っているようだ。この1.5の意味も不明…。

 

まとめ

  • デフォルトのままだと、POIから取得した行の高さとか列幅は期待した値にならないみたい。
  • Row.setHeightInPointsを設定すると実際のExcelの行の高さと合う。
  • Sheet.setColumnWidthで設定した値から実際のExcelの列幅が得られるけど、関係式の意味はよく分からなかった。

 

...あまりまとまってないなあ。。

 

OS Windows 7
Office 2010
Java 8u31
Apache POI 3.11