Enhance PDF report generation with custom font support and update Dockerfile for font installation

- Added support for custom PDF fonts in ReportService, allowing dynamic font resolution based on configuration.
- Introduced a method to handle font candidates and ensure fallback options for PDF generation.
- Updated Dockerfile to install the Nanum font, improving PDF rendering for Korean text.
- Modified application.yml to include a configurable path for the PDF font.
This commit is contained in:
DongHeon Jang 2026-04-17 12:17:06 +09:00
parent eb7df53eff
commit 8224cd02f5
3 changed files with 70 additions and 10 deletions

View File

@ -10,8 +10,11 @@ RUN gradle --no-daemon bootJar
FROM eclipse-temurin:21-jre
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends fonts-nanum \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /workspace/build/libs/*.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

View File

@ -8,13 +8,14 @@ import com.hanwha.nexacrodemo.consolidation.ConsolidationPayload;
import com.hanwha.nexacrodemo.minio.ObjectStorageService;
import com.hanwha.nexacrodemo.minio.StoredObject;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.FontFactory;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
@ -22,10 +23,13 @@ import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -43,6 +47,7 @@ import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
@ -55,15 +60,19 @@ public class ReportService {
private final ReportMapper reportMapper;
private final ConsolidationMapper consolidationMapper;
private final ObjectStorageService objectStorageService;
private final String pdfFontPath;
private volatile BaseFont pdfBaseFont;
public ReportService(
ReportMapper reportMapper,
ConsolidationMapper consolidationMapper,
ObjectStorageService objectStorageService
ObjectStorageService objectStorageService,
@Value("${app.report.pdf-font-path:}") String pdfFontPath
) {
this.reportMapper = reportMapper;
this.consolidationMapper = consolidationMapper;
this.objectStorageService = objectStorageService;
this.pdfFontPath = pdfFontPath;
}
public TxResponse loadRunOverview() {
@ -358,7 +367,7 @@ public class ReportService {
}
private void addPdfTitle(Document document, String text, int size, Color color) {
Font font = FontFactory.getFont(FontFactory.HELVETICA_BOLD, size, color);
Font font = pdfFont(size, Font.BOLD, color);
Paragraph paragraph = new Paragraph(text, font);
paragraph.setAlignment(Element.ALIGN_LEFT);
paragraph.setSpacingAfter(4f);
@ -380,14 +389,14 @@ public class ReportService {
}
private void addDetailTable(Document document, String title, List<Map<String, Object>> rows, int maxRows) {
Font sectionFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 12, new Color(24, 54, 93));
Font sectionFont = pdfFont(12, Font.BOLD, new Color(24, 54, 93));
Paragraph section = new Paragraph(title, sectionFont);
section.setSpacingBefore(8f);
section.setSpacingAfter(6f);
document.add(section);
if (rows.isEmpty()) {
document.add(new Paragraph("데이터가 없습니다.", FontFactory.getFont(FontFactory.HELVETICA_OBLIQUE, 10, new Color(120, 120, 120))));
document.add(new Paragraph("데이터가 없습니다.", pdfFont(10, Font.ITALIC, new Color(120, 120, 120))));
return;
}
@ -412,13 +421,13 @@ public class ReportService {
if (rows.size() > maxRows) {
document.add(new Paragraph(
"전체 " + rows.size() + "건 중 상위 " + maxRows + "건만 표시합니다.",
FontFactory.getFont(FontFactory.HELVETICA_OBLIQUE, 9, new Color(120, 120, 120))
pdfFont(9, Font.ITALIC, new Color(120, 120, 120))
));
}
}
private PdfPCell pdfHeaderCell(String text) {
PdfPCell cell = new PdfPCell(new Phrase(text, FontFactory.getFont(FontFactory.HELVETICA_BOLD, 9, Color.WHITE)));
PdfPCell cell = new PdfPCell(new Phrase(text, pdfFont(9, Font.BOLD, Color.WHITE)));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setBackgroundColor(new Color(243, 127, 32));
@ -428,7 +437,7 @@ public class ReportService {
}
private PdfPCell pdfBodyCell(String text, int alignment) {
PdfPCell cell = new PdfPCell(new Phrase(text, FontFactory.getFont(FontFactory.HELVETICA, 9, Color.DARK_GRAY)));
PdfPCell cell = new PdfPCell(new Phrase(text, pdfFont(9, Font.NORMAL, Color.DARK_GRAY)));
cell.setHorizontalAlignment(alignment);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setBorderColor(new Color(226, 229, 233));
@ -436,6 +445,53 @@ public class ReportService {
return cell;
}
private Font pdfFont(float size, int style, Color color) {
BaseFont baseFont = resolvePdfBaseFont();
if (baseFont == null) {
return new Font(Font.HELVETICA, size, style, color);
}
return new Font(baseFont, size, style, color);
}
private BaseFont resolvePdfBaseFont() {
if (pdfBaseFont != null) {
return pdfBaseFont;
}
synchronized (this) {
if (pdfBaseFont != null) {
return pdfBaseFont;
}
for (String candidate : pdfFontCandidates()) {
try {
pdfBaseFont = BaseFont.createFont(candidate, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
return pdfBaseFont;
} catch (DocumentException | IOException ignored) {
// Fall through to the next candidate. The Docker image installs NanumGothic,
// but local test environments may not have a Korean font available.
}
}
return null;
}
}
private List<String> pdfFontCandidates() {
List<String> candidates = new ArrayList<>();
if (pdfFontPath != null && !pdfFontPath.isBlank()) {
candidates.add(pdfFontPath.trim());
}
addIfExists(candidates, "/usr/share/fonts/truetype/nanum/NanumGothic.ttf");
addIfExists(candidates, "/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf");
addIfExists(candidates, "/workspace/server/api/src/main/resources/fonts/NanumGothic.ttf");
addIfExists(candidates, "/app/resources/fonts/NanumGothic.ttf");
return candidates;
}
private void addIfExists(List<String> candidates, String path) {
if (Files.exists(Path.of(path))) {
candidates.add(path);
}
}
private boolean isNumericValue(Object value) {
return value instanceof Number || value instanceof BigDecimal;
}

View File

@ -43,6 +43,8 @@ app:
enabled: false
poll-interval-ms: ${BATCH_POLL_INTERVAL_MS:5000}
worker-name: ${BATCH_WORKER_NAME:batch-1}
report:
pdf-font-path: ${PDF_FONT_PATH:}
storage:
provider: ${STORAGE_PROVIDER:minio}
bucket: ${MINIO_BUCKET:reports}
@ -57,4 +59,3 @@ logging:
level:
root: INFO
com.hanwha.nexacrodemo: INFO