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:
parent
eb7df53eff
commit
8224cd02f5
|
|
@ -10,8 +10,11 @@ RUN gradle --no-daemon bootJar
|
||||||
FROM eclipse-temurin:21-jre
|
FROM eclipse-temurin:21-jre
|
||||||
WORKDIR /app
|
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
|
COPY --from=builder /workspace/build/libs/*.jar /app/app.jar
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,14 @@ import com.hanwha.nexacrodemo.consolidation.ConsolidationPayload;
|
||||||
import com.hanwha.nexacrodemo.minio.ObjectStorageService;
|
import com.hanwha.nexacrodemo.minio.ObjectStorageService;
|
||||||
import com.hanwha.nexacrodemo.minio.StoredObject;
|
import com.hanwha.nexacrodemo.minio.StoredObject;
|
||||||
import com.lowagie.text.Document;
|
import com.lowagie.text.Document;
|
||||||
|
import com.lowagie.text.DocumentException;
|
||||||
import com.lowagie.text.Element;
|
import com.lowagie.text.Element;
|
||||||
import com.lowagie.text.Font;
|
import com.lowagie.text.Font;
|
||||||
import com.lowagie.text.FontFactory;
|
|
||||||
import com.lowagie.text.PageSize;
|
import com.lowagie.text.PageSize;
|
||||||
import com.lowagie.text.Paragraph;
|
import com.lowagie.text.Paragraph;
|
||||||
import com.lowagie.text.Phrase;
|
import com.lowagie.text.Phrase;
|
||||||
import com.lowagie.text.Rectangle;
|
import com.lowagie.text.Rectangle;
|
||||||
|
import com.lowagie.text.pdf.BaseFont;
|
||||||
import com.lowagie.text.pdf.PdfPCell;
|
import com.lowagie.text.pdf.PdfPCell;
|
||||||
import com.lowagie.text.pdf.PdfPTable;
|
import com.lowagie.text.pdf.PdfPTable;
|
||||||
import com.lowagie.text.pdf.PdfWriter;
|
import com.lowagie.text.pdf.PdfWriter;
|
||||||
|
|
@ -22,10 +23,13 @@ import java.awt.Color;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -43,6 +47,7 @@ import org.springframework.core.io.ByteArrayResource;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
|
@ -55,15 +60,19 @@ public class ReportService {
|
||||||
private final ReportMapper reportMapper;
|
private final ReportMapper reportMapper;
|
||||||
private final ConsolidationMapper consolidationMapper;
|
private final ConsolidationMapper consolidationMapper;
|
||||||
private final ObjectStorageService objectStorageService;
|
private final ObjectStorageService objectStorageService;
|
||||||
|
private final String pdfFontPath;
|
||||||
|
private volatile BaseFont pdfBaseFont;
|
||||||
|
|
||||||
public ReportService(
|
public ReportService(
|
||||||
ReportMapper reportMapper,
|
ReportMapper reportMapper,
|
||||||
ConsolidationMapper consolidationMapper,
|
ConsolidationMapper consolidationMapper,
|
||||||
ObjectStorageService objectStorageService
|
ObjectStorageService objectStorageService,
|
||||||
|
@Value("${app.report.pdf-font-path:}") String pdfFontPath
|
||||||
) {
|
) {
|
||||||
this.reportMapper = reportMapper;
|
this.reportMapper = reportMapper;
|
||||||
this.consolidationMapper = consolidationMapper;
|
this.consolidationMapper = consolidationMapper;
|
||||||
this.objectStorageService = objectStorageService;
|
this.objectStorageService = objectStorageService;
|
||||||
|
this.pdfFontPath = pdfFontPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxResponse loadRunOverview() {
|
public TxResponse loadRunOverview() {
|
||||||
|
|
@ -358,7 +367,7 @@ public class ReportService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPdfTitle(Document document, String text, int size, Color color) {
|
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 paragraph = new Paragraph(text, font);
|
||||||
paragraph.setAlignment(Element.ALIGN_LEFT);
|
paragraph.setAlignment(Element.ALIGN_LEFT);
|
||||||
paragraph.setSpacingAfter(4f);
|
paragraph.setSpacingAfter(4f);
|
||||||
|
|
@ -380,14 +389,14 @@ public class ReportService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDetailTable(Document document, String title, List<Map<String, Object>> rows, int maxRows) {
|
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);
|
Paragraph section = new Paragraph(title, sectionFont);
|
||||||
section.setSpacingBefore(8f);
|
section.setSpacingBefore(8f);
|
||||||
section.setSpacingAfter(6f);
|
section.setSpacingAfter(6f);
|
||||||
document.add(section);
|
document.add(section);
|
||||||
|
|
||||||
if (rows.isEmpty()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -412,13 +421,13 @@ public class ReportService {
|
||||||
if (rows.size() > maxRows) {
|
if (rows.size() > maxRows) {
|
||||||
document.add(new Paragraph(
|
document.add(new Paragraph(
|
||||||
"전체 " + rows.size() + "건 중 상위 " + maxRows + "건만 표시합니다.",
|
"전체 " + 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) {
|
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.setHorizontalAlignment(Element.ALIGN_CENTER);
|
||||||
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
|
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
|
||||||
cell.setBackgroundColor(new Color(243, 127, 32));
|
cell.setBackgroundColor(new Color(243, 127, 32));
|
||||||
|
|
@ -428,7 +437,7 @@ public class ReportService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private PdfPCell pdfBodyCell(String text, int alignment) {
|
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.setHorizontalAlignment(alignment);
|
||||||
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
|
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
|
||||||
cell.setBorderColor(new Color(226, 229, 233));
|
cell.setBorderColor(new Color(226, 229, 233));
|
||||||
|
|
@ -436,6 +445,53 @@ public class ReportService {
|
||||||
return cell;
|
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) {
|
private boolean isNumericValue(Object value) {
|
||||||
return value instanceof Number || value instanceof BigDecimal;
|
return value instanceof Number || value instanceof BigDecimal;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ app:
|
||||||
enabled: false
|
enabled: false
|
||||||
poll-interval-ms: ${BATCH_POLL_INTERVAL_MS:5000}
|
poll-interval-ms: ${BATCH_POLL_INTERVAL_MS:5000}
|
||||||
worker-name: ${BATCH_WORKER_NAME:batch-1}
|
worker-name: ${BATCH_WORKER_NAME:batch-1}
|
||||||
|
report:
|
||||||
|
pdf-font-path: ${PDF_FONT_PATH:}
|
||||||
storage:
|
storage:
|
||||||
provider: ${STORAGE_PROVIDER:minio}
|
provider: ${STORAGE_PROVIDER:minio}
|
||||||
bucket: ${MINIO_BUCKET:reports}
|
bucket: ${MINIO_BUCKET:reports}
|
||||||
|
|
@ -57,4 +59,3 @@ logging:
|
||||||
level:
|
level:
|
||||||
root: INFO
|
root: INFO
|
||||||
com.hanwha.nexacrodemo: INFO
|
com.hanwha.nexacrodemo: INFO
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue