diff --git a/Dockerfile.gdal b/Dockerfile.gdal
index 0fcd129..8d4f7ce 100644
--- a/Dockerfile.gdal
+++ b/Dockerfile.gdal
@@ -45,8 +45,36 @@ RUN apt-get update && \
libreoffice \
libreoffice-calc \
libreoffice-writer \
+ libreoffice-java-common \
+ fonts-ipafont \
+ fonts-ipafont-gothic \
+ fonts-ipafont-mincho \
+ language-pack-ja \
+ fontconfig \
+ locales \
python3-uno # LibreOffice Python バインディング
+
+# 日本語ロケールの設定
+RUN locale-gen ja_JP.UTF-8
+ENV LANG=ja_JP.UTF-8
+ENV LC_ALL=ja_JP.UTF-8
+ENV LANGUAGE=ja_JP:ja
+
+# フォント設定ファイルをコピー
+COPY config/fonts.conf /etc/fonts/local.conf
+
+# フォントキャッシュの更新
+RUN fc-cache -f -v
+
+# LibreOfficeの作業ディレクトリを作成
+RUN mkdir -p /var/cache/libreoffice && \
+ chmod 777 /var/cache/libreoffice
+
+# フォント設定の権限を設定
+RUN chmod 644 /etc/fonts/local.conf
+
+
# 作業ディレクトリとパーミッションの設定
RUN mkdir -p /app/docbase /tmp/libreoffice && \
chmod -R 777 /app/docbase /tmp/libreoffice
@@ -66,6 +94,8 @@ RUN apt-get update
COPY ./requirements.txt /app/requirements.txt
+RUN pip install boto3==1.26.137
+
# Install Gunicorn
RUN pip install gunicorn
diff --git a/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py b/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py
index 98cea28..b85ef32 100644
--- a/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py
+++ b/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py
@@ -305,6 +305,12 @@ class SumasenExcel:
logging.error(f"Error in proceed_group: {str(e)}")
return {"status": False, "message": f"Exception in proceed_group : Error generating report: {str(e)}"}
+ def pixels_to_EMU(pixels):
+ """
+ Convert pixels to EMU (English Metric Units)
+ EMU = pixels * 9525
+ """
+ return int(pixels * 9525)
def format_cell_value(self, field_value, cell):
"""セルの値を適切な形式に変換する
@@ -369,7 +375,8 @@ class SumasenExcel:
# ワークシートを取得
worksheet = cell.parent
-
+
+ logging.info('step-1')
try:
# 列の幅を取得(文字単位からピクセルに変換)
column_letter = get_column_letter(cell.column)
@@ -378,6 +385,7 @@ class SumasenExcel:
except Exception:
cell_width = 100 # デフォルト値
+ logging.info('step-2')
try:
# 行の高さを取得(ポイント単位からピクセルに変換)
row_height = worksheet.row_dimensions[cell.row].height
@@ -385,6 +393,7 @@ class SumasenExcel:
except Exception:
cell_height = 20 # デフォルト値
+ logging.info('step-3')
# マージンの設定(ピクセル単位)
margin_horizontal = 4 # 左右のマージン
margin_vertical = 2 # 上下のマージン
@@ -393,6 +402,7 @@ class SumasenExcel:
effective_cell_width = cell_width - (margin_horizontal * 2)
effective_cell_height = cell_height - (margin_vertical * 2)
+ logging.info('step-4')
# 最小サイズを設定(マージンを考慮)
effective_cell_width = max(effective_cell_width, 92) # 100 - (4 * 2)
effective_cell_height = max(effective_cell_height, 16) # 20 - (2 * 2)
@@ -403,6 +413,7 @@ class SumasenExcel:
effective_cell_width = min(effective_cell_width, max_width)
effective_cell_height = min(effective_cell_height, max_height)
+ logging.info('step-5')
# アスペクト比を保持しながらリサイズ
img_width, img_height = img.size
img_aspect = img_width / img_height
@@ -414,7 +425,11 @@ class SumasenExcel:
else:
height = effective_cell_height
width = int(height * img_aspect)
-
+
+ # 画像処理部分の修正
+ #from openpyxl.utils.units import pixels_to_EMU
+
+ logging.info('step-6')
# 画像をリサイズ
img_resized = img.resize((width, height), Image.BICUBIC)
@@ -422,29 +437,47 @@ class SumasenExcel:
img_byte_arr = io.BytesIO()
img_resized.save(img_byte_arr, format='JPEG', quality=85, optimize=True)
img_byte_arr.seek(0)
-
- # OpenPyXLのImageオブジェクトを作成
- from openpyxl.drawing.image import Image as XLImage
- excel_image = XLImage(img_byte_arr)
-
- # 画像のオフセット位置を設定(マージンを適用)
- from openpyxl.drawing.spreadsheet_drawing import OneCellAnchor, AnchorMarker
- from openpyxl.utils.units import pixels_to_EMU
- # セルの左上を基準に、マージン分オフセットした位置に配置
- col_offset = pixels_to_EMU(margin_horizontal)
- row_offset = pixels_to_EMU(margin_vertical)
-
- # マージンを考慮した配置
- marker = AnchorMarker(col=cell.column - 1, colOff=col_offset,
- row=cell.row - 1, rowOff=row_offset)
- anchor = OneCellAnchor(_from=marker, ext=None)
+
+ logging.info('step-7')
+ # OpenPyXLのImageオブジェクトを作成
+ excel_image = XLImage(img_byte_arr)
+
+ # EMUユニットでのサイズを設定
+ excel_image.width = pixels_to_EMU(width)
+ excel_image.height = pixels_to_EMU(height)
+
+ # 正しいアンカー設定
+ from openpyxl.drawing.spreadsheet_drawing import OneCellAnchor, AnchorMarker
+ from openpyxl.drawing.xdr import XDRPositiveSize2D
+
+ logging.info('step-8')
+ marker = AnchorMarker(
+ col=cell.column - 1,
+ colOff=pixels_to_EMU(margin_horizontal),
+ row=cell.row - 1,
+ rowOff=pixels_to_EMU(margin_vertical)
+ )
+
+ # XDRPositiveSize2Dを使用して画像サイズを設定
+ size = XDRPositiveSize2D(
+ cx=pixels_to_EMU(width),
+ cy=pixels_to_EMU(height)
+ )
+
+ anchor = OneCellAnchor(_from=marker, ext=size)
excel_image.anchor = anchor
-
+
+ logging.info('step-9')
# 画像をワークシートに追加
worksheet.add_image(excel_image)
+ #cell.parent.add_image(excel_image)
+ logging.info('step-A')
+ # メモリ解放
+ #img_byte_arr.close()
+
return ""
except Exception as e:
@@ -467,7 +500,28 @@ class SumasenExcel:
# その他の場合は文字列に変換
return str(field_value)
+ def verify_image_insertion(self, worksheet, cell, image):
+ """画像の挿入を検証するためのヘルパーメソッド"""
+ try:
+ # 画像が実際にワークシートに追加されているか確認
+ images_in_sheet = worksheet._images
+ if not images_in_sheet:
+ logging.warning(f"No images found in worksheet at cell {cell.coordinate}")
+ return False
+ # 画像のアンカー位置を確認
+ last_image = images_in_sheet[-1]
+ anchor = last_image.anchor
+ if not anchor:
+ logging.warning(f"Image anchor not set properly at cell {cell.coordinate}")
+ return False
+
+ logging.info(f"Image successfully inserted at cell {cell.coordinate}")
+ return True
+
+ except Exception as e:
+ logging.error(f"Error verifying image insertion: {str(e)}")
+ return False
def proceed_one_record(self,table:str,where:str,group_range:str,variables: Dict[str, Any]):
diff --git a/config/fonts.conf b/config/fonts.conf
new file mode 100644
index 0000000..b6cac51
--- /dev/null
+++ b/config/fonts.conf
@@ -0,0 +1,69 @@
+
+
+
+ /usr/share/fonts
+
+
+
+
+ sans-serif
+
+
+ IPAexGothic
+
+
+
+
+
+
+ serif
+
+
+ IPAexMincho
+
+
+
+
+
+
+ MS Gothic
+
+
+ IPAexGothic
+
+
+
+
+
+
+ MS Mincho
+
+
+ IPAexMincho
+
+
+
+
+
+
+ false
+
+
+
+
+
+
+ hintslight
+
+
+ rgb
+
+
+
+
+
+
+ true
+
+
+
diff --git a/rog/utils.py b/rog/utils.py
index 222d0f0..c2182ff 100644
--- a/rog/utils.py
+++ b/rog/utils.py
@@ -1,5 +1,5 @@
import os
-from aiohttp import ClientError
+from botocore.exceptions import ClientError
from django.template.loader import render_to_string
from django.conf import settings
import logging
@@ -193,12 +193,12 @@ class S3Bucket:
s3_key = os.path.basename(file_path)
# S3クライアントが指定されていない場合は新規作成
- if s3_client is None:
- s3_client = self.connect()
+ if self.s3_client is None:
+ self.s3_client = self.connect()
# ファイルのアップロード
logger.info(f"アップロード開始: {file_path} → s3://{self.bucket_name}/{s3_key}")
- s3_client.upload_file(file_path, self.bucket_name, s3_key)
+ self.s3_client.upload_file(file_path, self.bucket_name, s3_key)
logger.info("アップロード完了")
return True
@@ -313,11 +313,11 @@ class S3Bucket:
try:
# S3クライアントが指定されていない場合は新規作成
- if s3_client is None:
- s3_client = self.connect()
+ if self.s3_client is None:
+ self.s3_client = self.connect()
# プレフィックスに一致するオブジェクトをリスト
- paginator = s3_client.get_paginator('list_objects_v2')
+ paginator = self.s3_client.get_paginator('list_objects_v2')
pages = paginator.paginate(Bucket=self.bucket_name, Prefix=prefix)
for page in pages:
@@ -361,12 +361,12 @@ class S3Bucket:
try:
# S3クライアントが指定されていない場合は新規作成
- if s3_client is None:
- s3_client = self.connect()
+ if self.s3_client is None:
+ self.s3_client = self.connect()
# オブジェクトの削除
logger.info(f"削除開始: s3://{self.bucket_name}/{s3_key}")
- s3_client.delete_object(Bucket=self.bucket_name, Key=s3_key)
+ self.s3_client.delete_object(Bucket=self.bucket_name, Key=s3_key)
logger.info("削除完了")
return True
diff --git a/rog/views.py b/rog/views.py
index 2f9d6da..2fa76a4 100644
--- a/rog/views.py
+++ b/rog/views.py
@@ -2764,14 +2764,111 @@ def export_excel(request, zekken_number, event_code):
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
+
+ # 一時ディレクトリを作成(ASCII文字のみのパスを使用)
+ temp_dir = tempfile.mkdtemp(prefix='lo-')
+ logger.debug(f"Created temp directory: {temp_dir}")
+
+ # ASCII文字のみの作業ディレクトリを作成
+ work_dir = os.path.join(temp_dir, 'work')
+ output_dir = os.path.join(temp_dir, 'output')
+ os.makedirs(work_dir, exist_ok=True)
+ os.makedirs(output_dir, exist_ok=True)
+
+ # すべてのディレクトリに適切な権限を設定
+ for directory in [temp_dir, work_dir, output_dir]:
+ os.chmod(directory, 0o777)
+ logger.debug(f"Set permissions for directory: {directory}")
+
+ # ASCII文字のみの一時ファイル名を使用
+ temp_excel_name = f"certificate_{zekken_number}.xlsx"
+ temp_excel_path = os.path.join(work_dir, temp_excel_name)
+
+ # 元のExcelファイルを作業ディレクトリにコピー
+ shutil.copy2(excel_path, temp_excel_path)
+ os.chmod(temp_excel_path, 0o666)
+ logger.debug(f"Copied Excel file to: {temp_excel_path}")
+
+
+ # LibreOffice設定ディレクトリを作成
+ libreoffice_config_dir = os.path.join(temp_dir, 'libreoffice')
+ os.makedirs(libreoffice_config_dir, exist_ok=True)
+
+ # フォント設定ディレクトリを作成
+ font_conf_dir = os.path.join(temp_dir, 'fonts')
+ os.makedirs(font_conf_dir, exist_ok=True)
+
+ # フォント設定ファイルを作成
+ fonts_conf_content = '''
+
+
+ /usr/share/fonts
+
+
+ sans-serif
+
+
+ IPAexGothic
+
+
+
+
+ serif
+
+
+ IPAexMincho
+
+
+'''
+
+ font_conf_path = os.path.join(libreoffice_config_dir, 'fonts.conf')
+ with open(font_conf_path, 'w') as f:
+ f.write(fonts_conf_content)
+
+
+ # LibreOfficeのフォント設定を作成
+ registry_dir = os.path.join(libreoffice_config_dir, 'registry')
+ os.makedirs(registry_dir, exist_ok=True)
+
+ # フォント埋め込み設定を作成
+ pdf_export_config = '''
+
+
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+
+'''
+
+ pdf_config_path = os.path.join(registry_dir, 'pdf_export.xcu')
+ with open(pdf_config_path, 'w') as f:
+ f.write(pdf_export_config)
+
+ # すべてのディレクトリに適切な権限を設定
+ for directory in [temp_dir, work_dir, output_dir,registry_dir]:
+ os.chmod(directory, 0o777)
+ logger.debug(f"Set permissions for directory: {directory}")
+
+ os.chmod(temp_excel_path, 0o666)
+ os.chmod(font_conf_path, 0o666)
+ os.chmod(pdf_config_path, 0o666)
+
# フォーマット指定(excel or pdf)
format_type = request.query_params.get('format', 'pdf')
if format_type.lower() == 'pdf':
try:
# パスとファイル名に分離
- file_dir = os.path.dirname(excel_path) # パス部分の取得
- file_name = os.path.basename(excel_path) # ファイル名部分の取得
+ file_dir = os.path.dirname(temp_excel_path) # パス部分の取得
+ file_name = os.path.basename(temp_excel_path) # ファイル名部分の取得
# ファイル名の拡張子をpdfに変更
base_name = os.path.splitext(file_name)[0] # 拡張子を除いたファイル名
@@ -2786,11 +2883,10 @@ def export_excel(request, zekken_number, event_code):
conversion_command = [
'soffice', # LibreOfficeの代替コマンド
'--headless',
- '--convert-to',
- 'pdf',
- '--outdir',
- file_dir,
- excel_path
+ '--convert-to', 'pdf:writer_pdf_Export',
+ '--outdir',file_dir,
+ '-env:UserInstallation=file://' + libreoffice_config_dir,
+ temp_excel_path
]
logger.debug(f"Running conversion command: {' '.join(conversion_command)}")
@@ -2798,6 +2894,11 @@ def export_excel(request, zekken_number, event_code):
# 環境変数を設定
env = os.environ.copy()
#env['HOME'] = temp_dir # LibreOfficeの設定ディレクトリを一時ディレクトリに設定
+ env['HOME'] = temp_dir
+ env['LANG'] = 'ja_JP.UTF-8' # 日本語環境を設定
+ env['LC_ALL'] = 'ja_JP.UTF-8'
+ env['FONTCONFIG_FILE'] = font_conf_path
+ env['FONTCONFIG_PATH'] = font_conf_dir
# 変換プロセスを実行
process = subprocess.run(
@@ -2805,7 +2906,9 @@ def export_excel(request, zekken_number, event_code):
env=env,
capture_output=True,
text=True,
- check=True
+ cwd=work_dir,
+ check=True,
+ timeout=30
)
logger.debug(f"Conversion output: {process.stdout}")
@@ -2819,9 +2922,10 @@ def export_excel(request, zekken_number, event_code):
)
s3 = S3Bucket('sumasenrogaining')
- s3.upload_file(pdf_path, f'{event_code}/scoreboard', f'certificate_{zekken_number}.pdf')
- s3.upload_file(excel_path, f'{event_code}/scoreboard_excel', f'certificate_{zekken_number}.xlsx')
+ s3.upload_file(pdf_path, f'{event_code}/scoreboard/certificate_{zekken_number}.pdf')
+ s3.upload_file(excel_path, f'{event_code}/scoreboard_excel/certificate_{zekken_number}.xlsx')
+ os.remove(temp_excel_path)
os.remove(excel_path)
os.remove(pdf_path)
@@ -2833,7 +2937,7 @@ def export_excel(request, zekken_number, event_code):
return Response(
{"error": f"PDF conversion failed: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
+ )
finally:
# 一時ディレクトリの削除
if temp_dir and os.path.exists(temp_dir):
diff --git a/supervisor/html/index.html b/supervisor/html/index.html
index 59fd871..fcfebb7 100755
--- a/supervisor/html/index.html
+++ b/supervisor/html/index.html
@@ -1196,67 +1196,9 @@ async function saveGoalTime(goalTimeStr, zekkenNumber, eventCode) {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
-
- // Blobとしてレスポンスを取得
- const blob = await response.blob();
-
- // BlobをURLに変換
- const url = window.URL.createObjectURL(blob);
-
- // 印刷方法の選択を提供する関数
- const printPDF = () => {
- // IEとその他のブラウザで異なる処理を行う
- if (window.navigator.msSaveOrOpenBlob) {
- // IEの場合
- window.navigator.msSaveOrOpenBlob(blob, `通過証明書_${zekkenNumber}_${eventCode}.pdf`);
- } else {
- // その他のブラウザの場合
- // iframeを作成して印刷用のコンテナとして使用
- const printFrame = document.createElement('iframe');
- printFrame.style.display = 'none';
- printFrame.src = url;
-
- printFrame.onload = () => {
- try {
- // iframe内のPDFを印刷
- printFrame.contentWindow.print();
- } catch (error) {
- console.error('印刷プロセス中にエラーが発生しました:', error);
- // 印刷に失敗した場合、新しいタブでPDFを開く
- window.open(url, '_blank');
- } finally {
- // 少し遅延してからクリーンアップ
- setTimeout(() => {
- document.body.removeChild(printFrame);
- window.URL.revokeObjectURL(url);
- }, 1000);
- }
- };
-
- document.body.appendChild(printFrame);
- }
- };
-
- // 確認ダイアログを表示
- const userChoice = window.confirm('PDFを印刷しますか?\n「キャンセル」を選択すると保存できます。');
-
- if (userChoice) {
- // 印刷を実行
- printPDF();
- } else {
- // PDFを保存
- const a = document.createElement('a');
- a.href = url;
- a.download = `通過証明書_${zekkenNumber}_${eventCode}.pdf`;
- document.body.appendChild(a);
- a.click();
-
- // クリーンアップ
- setTimeout(() => {
- document.body.removeChild(a);
- window.URL.revokeObjectURL(url);
- }, 100);
- }
+ // 確認ダイアログを表示
+ const userChoice = window.confirm('PDFを印刷に回しました。');
+ return
} catch (error) {
console.error('エクスポート中にエラーが発生しました:', error);