Search

Java๋กœ PDF ๊ทธ๋ฆฌ๊ธฐ

ย ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์กฐ์‚ฌ

Java๋ฅผ ์ด์šฉํ•ด์„œ PDF๋ฅผ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋Œ€ํ‘œ์ ์œผ๋กœ iText์™€ PDFBox๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.
์œ„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ชจ๋‘ PDF๋ฅผ ๊ทธ๋ฆฌ๊ธฐ์— ์ถฉ๋ถ„ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ง€๋งŒ ๋ผ์ด์„ผ์Šค์—์„œ์˜ ์•ฝ๊ฐ„์˜ ์ฐจ์ด๋ฅผ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
iText๋Š” AGPL ๋ผ์ด์„ผ์Šค๋ฅผ ๊ฐ–๊ณ  ์žˆ์œผ๋ฉฐ, AGPL ๋ผ์ด์„ผ์Šค๋ž€ GNU Affero General Public License์˜ ์•ฝ์ž๋กœ, ์ž์œ  ์†Œํ”„ํŠธ์›จ์–ด ์žฌ๋‹จ(FSF)์—์„œ ๋งŒ๋“  ์˜คํ”ˆ ์†Œ์Šค ๋ผ์ด์„ผ์Šค์ž…๋‹ˆ๋‹ค. ์ด ๋ผ์ด์„ผ์Šค๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์›น ์„œ๋น„์Šค๋ฅผ ํ†ตํ•ด ํ”„๋กœ๊ทธ๋žจ์„ ์‚ฌ์šฉํ•  ๋•Œ์—๋„ ํ•ด๋‹น ํ”„๋กœ๊ทธ๋žจ์˜ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๊ณต๊ฐœํ•ด์•ผ ํ•จ์„ ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค.
Apache PDFBox๋Š” Apache ๋ผ์ด์„ผ์Šค 2.0์„ ๊ฐ–๊ณ  ์žˆ์œผ๋ฉฐ, Apache ๋ผ์ด์„ผ์Šค 2.0์ด๋ž€ ์˜คํ”ˆ ์†Œ์Šค ์†Œํ”„ํŠธ์›จ์–ด ๋ผ์ด์„ผ์Šค ์ค‘ ํ•˜๋‚˜๋กœ, ์•„ํŒŒ์น˜ ์†Œํ”„ํŠธ์›จ์–ด ์žฌ๋‹จ(Apache Software Foundation)์—์„œ ๊ฐœ๋ฐœํ•œ ๋ผ์ด์„ผ์Šค์ž…๋‹ˆ๋‹ค. ์ด ๋ผ์ด์„ผ์Šค๋Š” ์ƒ์—…์  ๋ฐ ๋น„์ƒ์—…์  ๋ชฉ์ ์œผ๋กœ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉํ•˜๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
์ €๋Š” ์ด ๊ธฐ๋Šฅ์„ ํ”„๋กœ๋•์…˜ ๋ ˆ๋ฒจ์— ์‚ฌ์šฉํ•  ์˜ˆ์ •์ด๋ฏ€๋กœ Apache PDFBox๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

ย ๋””์ž์ธ

๋””์ž์ด๋„ˆ๋ถ„๊ป˜ PDF ํ…œํ”Œ๋ฆฟ์— ๋Œ€ํ•œ ๋””์ž์ธ์„ ๋ฐ›์€ ํ›„ ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

ย ๊ฐœ๋ฐœ

build.gradle ์˜์กด์„ฑ ์„ค์ •

//Apache PDFBox implementation 'org.apache.pdfbox:pdfbox:2.0.29'
Java
๋ณต์‚ฌ
Maven Repository์— ๊ฒ€์ƒ‰ ํ›„ Vulnerabilities ์™€ Usages๋ฅผ ํ™•์ธํ•˜์—ฌ ๊ฐ€์žฅ ์ตœ์‹  ๋ฒ„์ „์ด๋ฉด์„œ ์•ˆ์ •์ ์ธ ๋ฒ„์ „์„ ์ฐพ์•„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

Controller

@GetMapping(value = "/{resumeId}/pdf", produces = MediaType.APPLICATION_PDF_VALUE) public ResponseEntity<ByteArrayResource> createPDF(HttpServletRequest request, @PathVariable Long resumeId) { PdfDTO pdf = resumeService.createResumePDF(request, resumeId); HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=" + URLEncoder.encode(pdf.getFileName(), StandardCharsets.UTF_8)); return new ResponseEntity<>(pdf.getPdf(), headers, HttpStatus.OK); }
Java
๋ณต์‚ฌ
ํ•ด๋‹น API์˜ Response์— ๋Œ€ํ•œ Content-Type์„ application/pdf๋กœ ์„ค์ •ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ž์›์„ ๋ณด๋‚ผ ๋•Œ ์ด ์ž์›์ด pdf file ํƒ€์ž…์ด๋ผ๋Š” ๊ฒƒ์„ ๋ช…์‹œํ•ด์ค๋‹ˆ๋‹ค.
๋˜ํ•œ Content-Disposition๋ฅผ ํ†ตํ•ด ์ƒ์„ฑ๋œ PDF์˜ ํŒŒ์ผ ์ด๋ฆ„์„ ๋ช…์‹œํ•ด์ฃผ๋Š”๋ฐ ์ด ์†์„ฑ์˜ default ๊ฐ’์€ inline;์œผ๋กœ ์ด๊ฑธ attachment;๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด PDF ํŒŒ์ผ์„ ๋‹ค์šด๋ฐ›๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด ๋•Œ ํŒŒ์ผ ์ด๋ฆ„์„ UTF-8๋กœ URLEncodeํ•ด์ฃผ์ง€ ์•Š์œผ๋ฉด ํ•œ๊ธ€์˜ ๊ฒฝ์šฐ ํ‘œ์‹œ๊ฐ€ ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

DTO

@Getter @Setter @NoArgsConstructor public class PdfDTO { private String fileName; private ByteArrayResource pdf; @Builder public PdfDTO(String fileName, ByteArrayResource pdf) { this.fileName = fileName; this.pdf = pdf; } }
Java
๋ณต์‚ฌ
Service ๋‹จ์—์„œ ์ƒ์„ฑํ•œ PDF byte[]์™€ ํ•ด๋‹น ํŒŒ์ผ์˜ ์ด๋ฆ„์„ ํ•จ๊ป˜ ๋‹ด์•„ Controller๋‹จ๊นŒ์ง€ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ DTO๋ฅผ ์ •์˜ํ•ด์ค๋‹ˆ๋‹ค.

Service

public PdfDTO createResumePDF(HttpServletRequest request, Long resumeId) { String accessToken = authenticationProvider.resolveAccessToken(request); Long userId = authenticationProvider.getId(accessToken); User user = userRepository.findById(userId).orElseThrow(() -> new CustomException(ErrorCode.BLANK_MEMBER)); Resume resume = resumeRepository.findResumeById(resumeId).orElseThrow(() -> new CustomException(ErrorCode.BLANK_RESUME)); if (!resume.getUser().getId().equals(user.getId())) { throw new CustomException(ErrorCode.ACCESS_DENIED); } if (resume.getUser().getUserProfile() == null && (resume.getEducations() == null || resume.getEducations().isEmpty())) { throw new CustomException(ErrorCode.BLANK_REQUIRED); } byte[] bytes; try { PDDocument document = pdfUtil.createPDFDocument(); pdfUtil.setDocumentInfo(document, resume); pdfUtil.initFonts(document); int height = (int) (document.getPage(0).getMediaBox().getHeight() - 84); height = pdfUtil.setProfile(document, resume, height); height = pdfUtil.setEducations(document, resume, height); height = pdfUtil.setCareers(document, resume, height); height = pdfUtil.setCertifications(document, resume, height); height = pdfUtil.setFluencies(document, resume, height); height = pdfUtil.setActivities(document, resume, height); pdfUtil.setPageNumber(document); pdfUtil.setWaterMark(document); bytes = pdfUtil.pdfToBytes(document); document.close(); } catch (IOException e) { throw new CustomException(ErrorCode.IO_ERROR); } return PdfDTO.builder() .fileName("[๊ณ ์ดˆ๋Œ€์กธ๋‹ท์ปด_์ด๋ ฅ์„œ]_" + resume.getUser().getUserProfile().getName() + "_" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ".pdf") .pdf(new ByteArrayResource(bytes)) .build(); }
Java
๋ณต์‚ฌ
Service๋‹จ์—์„  ๋จผ์ € ํ•ด๋‹น ์œ ์ €์˜ ์ด๋ ฅ์„œ๊ฐ€ ๋งž๋Š”์ง€ ๊ทธ๋ฆฌ๊ณ  ์ด๋ ฅ์„œ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ•„์ˆ˜ ํ•ญ๋ชฉ๋“ค์ด ๋‹ค ์กด์žฌํ•˜๋Š”์ง€๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
์ดํ›„ PDF๋ฅผ ๊ทธ๋ฆฌ๊ธฐ ์œ„ํ•œ ๋ฉ”์„œ๋“œ๋“ค์„ ์ •์˜ ํ•ด๋†“์€ Util ํด๋ž˜์Šค๋ฅผ ์ฃผ์ž… ๋ฐ›์•„ ๋นˆ PDF ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•œ ํ›„ ๊ฐ ํ•ญ๋ชฉ๋“ค์„ ๊ทธ๋ ค์ค€ ํ›„ DTO์— ๋‹ด์•„ return ํ•ด์ค๋‹ˆ๋‹ค.

PDFUtil.createPDFDocument()

public PDDocument createPDFDocument() { PDDocument document = new PDDocument(); PDPage page = new PDPage(PDRectangle.A4); document.addPage(page); return document; }
Java
๋ณต์‚ฌ
๋นˆ PDF ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•œ ํ›„ ํ•ด๋‹น ๋ฌธ์„œ์— A4 ํฌ๊ธฐ์˜ ๋นˆ ํŽ˜์ด์ง€๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฆฌํ„ด ํ•ด์ค๋‹ˆ๋‹ค.

PDFUtil.createPDFPage()

private PDPage createPDFPage() { PDPage page = new PDPage(PDRectangle.A4); return page; }
Java
๋ณต์‚ฌ
PDF๋ฅผ ๊ทธ๋ฆฌ๋‹ค๊ฐ€ ํŽ˜์ด์ง€๊ฐ€ ๊ฝ‰ ์ฐฐ ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ ํŽ˜์ด์ง€๊ฐ€ ์ถ”๊ฐ€๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

PDFUtil.needNewPage()

private boolean needNewPage(PDDocument document, int height, int needHeight) { if (height - needHeight < 84) { PDPage newPage = createPDFPage(); document.addPage(newPage); return true; } return false; }
Java
๋ณต์‚ฌ
๋ฌธ์ž๋‚˜ ๋ผ์ธ์„ ์ถ”๊ฐ€ํ•˜๋Š” ์ค‘ ํ•„์š”ํ•œ ๋†’์ด๊ฐ€ ๊ธฐ๋ณธ ํŽ˜์ด์ง€ ๊ฐ„๊ฒฉ์„ ๋„˜์–ด๊ฐ€๋Š”์ง€๋ฅผ ํ™•์ธํ•ด ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€๋ฅผ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

PDFUtil.initFonts()

public void initFonts(PDDocument document) throws IOException { Map<String, PDType0Font> map = new HashMap<>(); ClassPathResource extraBoldFontPath = new ClassPathResource("fonts/Pretendard-ExtraBold.ttf"); ClassPathResource mediumFontPath = new ClassPathResource("fonts/Pretendard-Medium.ttf"); ClassPathResource regularFontPath = new ClassPathResource("fonts/Pretendard-Regular.ttf"); map.put("extraBold", PDType0Font.load(document, extraBoldFontPath.getInputStream())); map.put("medium", PDType0Font.load(document, mediumFontPath.getInputStream())); map.put("regular", PDType0Font.load(document, regularFontPath.getInputStream())); fonts = map; }
Java
๋ณต์‚ฌ
๋ฌธ์ž๋ฅผ ๊ทธ๋ฆฌ๊ธฐ ์œ„ํ•œ ํฐํŠธ ํŒŒ์ผ์„ ์ฝ์–ด๋“ค์—ฌ ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•˜๋„๋ก Map์— ๋‹ด์•„ ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ดˆ๊ธฐํ™” ํ•ด์ค๋‹ˆ๋‹ค.
์ด ๋•Œ ์ค‘์š”ํ•œ ์ ์€ resources ๊ฒฝ๋กœ์— ์žˆ๋Š” ํŒŒ์ผ๋“ค์€ ๋กœ์ปฌ๊ณผ ์„œ๋ฒ„์—์„œ ๊ฐ๊ฐ ๋‹ค๋ฅธ ๊ฒฝ๋กœ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ClassPathResource ๊ฐ™์€ ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•ด ์ ˆ๋Œ€๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.

PDFUtil.setText()

private int setText(PDDocument document, PDType0Font font, int fontSize, int r, int g, int b, int x, int y, String text, int maxLength) throws IOException { PDPageContentStream contentStream = new PDPageContentStream(document, document.getPage(document.getPages().getCount() - 1), PDPageContentStream.AppendMode.APPEND, true); if (needNewPage(document, y, 22)) { contentStream.close(); contentStream = new PDPageContentStream(document, document.getPage(document.getPages().getCount() - 1), PDPageContentStream.AppendMode.APPEND, true); y = (int) (document.getPage(document.getPages().getCount() - 1).getMediaBox().getHeight() - 84); } contentStream.setFont(font, fontSize); contentStream.setNonStrokingColor(r, g, b); contentStream.beginText(); contentStream.newLineAtOffset(x, y); if (maxLength != 0 && text.length() > maxLength) { for (int i = 0; i < text.length(); i += maxLength) { int endIndex = Math.min(i + maxLength, text.length()); contentStream.showText(text.substring(i, endIndex)); contentStream.newLineAtOffset(0, -18); y = i != maxLength ? y - 18 : y; } } else { contentStream.showText(text); } contentStream.endText(); contentStream.close(); return y; }
Java
๋ณต์‚ฌ
PDF์— ๋“ค์–ด๊ฐ€๋Š” ์š”์†Œ๋“ค์„ ๊ทธ๋ฆฌ๊ธฐ ์œ„ํ•œ PDPageContentStream์„ ๋งŒ๋“ค์–ด์ฃผ์–ด์•ผํ•˜๋Š”๋ฐ ๊ทธ๋ฆด ๊ณณ์˜ ๋ฌธ์„œ์™€ ํŽ˜์ด์ง€๋ฅผ ์•Œ๋ ค์ฃผ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ธฐ ์ „์— needNewPage๋ฅผ ์ด์šฉํ•ด ํ˜„์žฌ ์œ„์น˜์—์„œ ๋ฌธ์ž๋ฅผ ์ถ”๊ฐ€ํ–ˆ์„ ๋•Œ ๊ธฐ๋ณธ ํŽ˜์ด์ง€ ๊ฐ„๊ฒฉ์„ ๋„˜์–ด๊ฐ€๋Š”์ง€ ํ™•์ธํ•˜์—ฌ ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€๋กœ ์ „ํ™˜ํ•˜๊ฑฐ๋‚˜ ํ˜„์žฌ ํŽ˜์ด์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
stream์— ์‚ฌ์šฉํ•  ํฐํŠธ์™€ ์ƒ‰, ์œ„์น˜ ๋“ฑ์„ ์ง€์ •ํ•œ ํ›„ ์ž…๋ ฅํ•  ๋ฌธ์ž๋ฅผ showText()์— ๋„˜๊ฒจ์ค˜ ๊ทธ๋ฆฌ๊ฒŒ๋” ํ•ฉ๋‹ˆ๋‹ค.
์ด ๋•Œ PDF์˜ x์ถ•์œผ๋กœ ๊ธ€์ž๊ฐ€ ๋„˜์น  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ตœ๋Œ€ ๊ธ€์ž๋ฅผ ๋„˜๋Š”๋‹ค๋ฉด ์ค„๋ฐ”๊ฟˆ์„ ํ•ด์ฃผ๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.
stream์˜ newLineAtOffset() ๋ฉ”์„œ๋“œ๋Š” beginText()๋ฅผ ํ•œ ํ›„ ์ฒซ ์œ„์น˜๋Š” ์ ˆ๋Œ€์ขŒํ‘œ๋กœ ๊ทธ ์ดํ›„ ์œ„์น˜๋Š” endText()๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ „๊นŒ์ง€ ์ƒ๋Œ€์ขŒํ‘œ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

PDFUtil.setImage()

private void setImage(PDDocument document, int x, int y, int width, int height, BufferedImage bufferedImage) throws IOException { PDPageContentStream contentStream = new PDPageContentStream(document, document.getPage(document.getPages().getCount() - 1), PDPageContentStream.AppendMode.APPEND, true); if (needNewPage(document, y, height)) { contentStream.close(); contentStream = new PDPageContentStream(document, document.getPage(document.getPages().getCount() - 1), PDPageContentStream.AppendMode.APPEND, true); y = (int) (document.getPage(document.getPages().getCount() - 1).getMediaBox().getHeight() - 84); } PDImageXObject image = LosslessFactory.createFromImage(document, bufferedImage); contentStream.drawImage(image, x, y, width, height); contentStream.close(); }
Java
๋ณต์‚ฌ
์•ž๋ถ€๋ถ„์—์„  ๋˜‘๊ฐ™์ด Stream์„ ๋งŒ๋“  ํ›„ ํŽ˜์ด์ง€ ์ „ํ™˜์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฏธ์ง€ ์ถ”๊ฐ€๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ BufferedImage๋กœ ImageObject๋ฅผ ๋งŒ๋“  ํ›„ ๊ทธ๋ฆด x, y ์ขŒํ‘œ์™€ ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ๋ฅผ ์ง€์ •ํ•˜์—ฌ ๊ทธ๋ ค์ค๋‹ˆ๋‹ค.

PDFUtil.getImageByURL()

private BufferedImage getImageByURL(String imagePath) throws IOException { InputStream inputStream = new ByteArrayInputStream(s3Util.getObject(imagePath.substring(imagePath.indexOf("/", 8) + 1)));; BufferedImage image = ImageIO.read(inputStream); inputStream.close(); return image; }
Java
๋ณต์‚ฌ
์ €ํฌ ์„œ๋น„์Šค์—์„œ๋Š” ์ด๋ ฅ์„œ์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋ฅผ S3์— ์ €์žฅํ•œ ํ›„ CDN URL์„ ํ†ตํ•ด Deliveryํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— URL์˜ key๋ฅผ ํ†ตํ•ด S3์—์„œ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

PDFUtil.getImageByResource()

private BufferedImage getImageByResource(ClassPathResource resource) throws IOException { InputStream inputStream = resource.getInputStream(); BufferedImage image = ImageIO.read(inputStream); inputStream.close(); return image; }
Java
๋ณต์‚ฌ
ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์›Œํ„ฐ๋งˆํฌ ๋“ฑ ์ด๋ ฅ์„œ์—์„œ ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์ด๋ฏธ์ง€๋“ค์€ /resources/images์— ๊ฐ–๊ณ  ์žˆ๋‹ค๊ฐ€ ์ ˆ๋Œ€๊ฒฝ๋กœ๋ฅผ ํ†ตํ•ด ๋ถˆ๋Ÿฌ์™€์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

PDFUtil.setLine()

private int setHorizontalLine(PDDocument document, int lineWidth, int r, int g, int b, int startX, int startY, int endX, int endY) throws IOException { PDPageContentStream contentStream = new PDPageContentStream(document, document.getPage(document.getPages().getCount() - 1), PDPageContentStream.AppendMode.APPEND, true); if (needNewPage(document, startY, 1)) { contentStream.close(); contentStream = new PDPageContentStream(document, document.getPage(document.getPages().getCount() - 1), PDPageContentStream.AppendMode.APPEND, true); startY = (int) (document.getPage(document.getPages().getCount() - 1).getMediaBox().getHeight() - 84); endY = (int) (document.getPage(document.getPages().getCount() - 1).getMediaBox().getHeight() - 84); } contentStream.setLineWidth(lineWidth); contentStream.setStrokingColor(r, g, b); contentStream.moveTo(startX, startY); contentStream.lineTo(endX, endY); contentStream.stroke(); contentStream.close(); return startY - 34; }
Java
๋ณต์‚ฌ
Stream์˜ stroke()๋ฅผ ํ†ตํ•ด ๋ผ์ธ์„ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋Š”๋ฐ ์„ ๊ตต๊ธฐ, ์ƒ‰, ์‹œ์ž‘์œ„์น˜, ์ข…๋ฃŒ์œ„์น˜๋ฅผ ์„ค์ •ํ•˜์—ฌ ์„ ์„ ๊ทธ์–ด์ค๋‹ˆ๋‹ค.

PDFUtil.calculatePixel()

private int calculatePixel(String fontName, int fontSize, String text) throws IOException { PDType0Font font = fonts.get(fontName); float pixel = font.getStringWidth(text); return (int) ((pixel / 1000 * fontSize) + 8); }
Java
๋ณต์‚ฌ
ํฐํŠธ๋งˆ๋‹ค width์— ๋Œ€ํ•œ ํ”ฝ์…€์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๊ฒฉ์„ ์กฐ์ ˆํ•˜๋ ค๋ฉด ๋ฌธ์ž์˜ ํ”ฝ์…€์„ ๊ณ„์‚ฐํ•ด์ฃผ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
PDFBox์˜ Fontํƒ€์ž…์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” getStringWidth()๋ฅผ ํ†ตํ•ด ๊ฐ€๋กœ ๊ธธ์ด๋ฅผ ๊ตฌํ•œ ํ›„ pixcel / 1000 * fontSize ๋กœ ํ•ด๋‹น ๋ฌธ์ž์˜ ๊ฐ€๋กœ ๊ธธ์ด ํ”ฝ์…€์„ ๊ตฌํ•ด์ค๋‹ˆ๋‹ค.
๋งˆ์ง€๋ง‰์—” 8์„ ๋”ํ•ด์ฃผ์–ด ๋„์–ด์“ฐ๊ธฐ๋ฅผ ํ•ด์ค๋‹ˆ๋‹ค.

PDFUtil.pdfToBytes()

public byte[] pdfToBytes(PDDocument document) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); document.save(outputStream); document.close(); return outputStream.toByteArray(); }
Java
๋ณต์‚ฌ
์ด๋ ‡๊ฒŒ ๊ทธ๋ ค์ง„ PDF ๋ฌธ์„œ๋ฅผ OutputStream์— ๋‹ด์€ ํ›„ byte[]๋กœ ๋ณ€ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.

์ด์Šˆ1. ํ˜„์žฌ ์œ„์น˜๋ฅผ ๊ธฐ์–ตํ•˜์ง€ ๋ชปํ•œ๋‹ค.

Apache PDFBox์€ ContentStream์„ ์—ด์–ด ๋ฌธ์ž, ์ด๋ฏธ์ง€, ๋„ํ˜•, ์„  ๋“ฑ์„ ๊ทธ๋ ค๋„ฃ๋Š” ๋ฐฉ์‹์œผ๋กœ PDF๋ฅผ ๊ทธ๋ฆฌ๋Š”๋ฐ
๊ฐ ์š”์†Œ๋ฅผ ๊ทธ๋ฆด ๋•Œ๋งˆ๋‹ค x, y ์ขŒํ‘œ๋ฅผ ์•Œ๋ ค์ฃผ์–ด์•ผํ•˜๊ณ  ContentStream์„ close() ํ•ด๋ฒ„๋ฆฐ๋‹ค๋ฉด ์ƒ๋Œ€ ์ขŒํ‘œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด ํ˜„์žฌ ์œ„์น˜๋ฅผ ์žŠ์–ด๋ฒ„๋ฆฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
public int setFluencies(PDDocument document, Resume resume, int height) throws IOException { if (resume.getFluencies() == null || resume.getFluencies().isEmpty()) { return height; } PDPage page = document.getPage(document.getPages().getCount() - 1); int width = (int) page.getMediaBox().getWidth(); resume.getFluencies().sort((c1, c2) -> c2.getAcquisitionDate().compareTo(c1.getAcquisitionDate())); height = setText(document, fonts.get("extraBold"), 14, blueGray400[0], blueGray400[1], blueGray400[2], margin, height, "์–ดํ•™", 0); for (int i = 0; i < resume.getFluencies().size(); i++) { height = setText(document, fonts.get("medium"), 14, black[0], black[1], black[2], margin + padding, height, resume.getFluencies().get(i).getName(), 0); height = setText(document, fonts.get("regular"), 12, black[0], black[1], black[2], margin + padding + calculatePixel("medium", 14, resume.getFluencies().get(i).getName()), height, resume.getFluencies().get(i).getLanguageType().getName(), 0); height = setText(document, fonts.get("regular"), 12, black[0], black[1], black[2], width - margin - calculatePixel("regular", 12, resume.getFluencies().get(i).getAcquisitionDate().format(DateTimeFormatter.ofPattern("yyyy.MM"))), height, resume.getFluencies().get(i).getAcquisitionDate().format(DateTimeFormatter.ofPattern("yyyy.MM")), 0); height -= 26; height = setText(document, fonts.get("regular"), 12, black[0], black[1], black[2], margin + padding, height, resume.getFluencies().get(i).getGrade(), 0); if (i != resume.getFluencies().size() - 1) { height -= 21; height = setHorizontalLine(document, 1, gray200[0], gray200[1], gray200[2], margin + padding, height, width - margin, height); } } height -= 21; height = setHorizontalLine(document, 1, gray200[0], gray200[1], gray200[2], margin, height, width - margin, height); return height; }
Java
๋ณต์‚ฌ
์œ„ ์ฝ”๋“œ๋Š” ์ด๋ ฅ์„œ ์ค‘ ์–ดํ•™์— ๊ด€ํ•œ ํ•ญ๋ชฉ์„ PDF์— ๊ทธ๋ฆฌ๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.
์ €๋Š” ์ด ์ด์Šˆ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ heigth๋ฅผ ์ง์ ‘ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐฉ์‹์„ ์„ ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.
๋ฉ”์„œ๋“œ ์ธ์ž๋กœ ๋ฐ›๋Š” document๋ฅผ ํ†ตํ•ด์„œ๋Š” PDF ๋ฌธ์„œ ์ค‘ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๋ฅผ ์•Œ ์ˆ˜ ์žˆ๊ณ  height๋ฅผ ํ†ตํ•ด์„œ๋Š” ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ์˜ ํ˜„์žฌ ์œ„์น˜๋ฅผ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
setText() ๋“ฑ์„ ํ†ตํ•ด PDF์— ์š”์†Œ๋“ค์„ ๊ทธ๋ ค๋„ฃ์œผ๋ฉด์„œ๋„ heigth๋ฅผ ๊ณ„์‚ฐํ•ด๊ฐ€๋ฉด์„œ ์ตœ์ข…์ ์ธ ์œ„์น˜๋ฅผ ๋ฆฌํ„ดํ•ด์ค๋‹ˆ๋‹ค.
@Slf4j @Component public class PDFUtil { private int margin = 52; private int padding = 67; private Map<String, PDType0Font> fonts; private int[] black = {28, 28, 28}; private int[] blueGray400 = {107, 118, 132}; private int[] gray200 = {226, 228, 230}; private int[] gray300 = {155, 160, 169}; private int[] blue200 = {232, 243, 255}; private int[] blue300 = {41, 97, 205}; private ClassPathResource logo = new ClassPathResource("images/logo.png"); private ClassPathResource uturn = new ClassPathResource("images/uturn.png");
Java
๋ณต์‚ฌ
๋˜ํ•œ ์ด๋Ÿฐ์‹์œผ๋กœ margin๊ณผ padding์„ ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•ด๋‘๊ณ  ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ๋†’ํžˆ๊ณ  ์ „์—ญ์œผ๋กœ ์„ค์ •์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

ย ์ด์Šˆ2. ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋„ˆ๋ฌด ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.

๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ์œผ๋กœ ์ธํ•ด ์„œ๋น„์Šค์— ์˜ํ–ฅ์ด ๊ฐ€์ง€ ์•Š๋„๋ก ์ด๋ ฅ์„œ๋ฅผ PDF๋กœ ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ๋งŒ์„ ๋”ฐ๋กœ AWS Lambda๋กœ Generate ์„œ๋ฒ„๋ฅผ ๋ถ„๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.

ย ์ด์Šˆ3. ๋Œ€์šฉ๋Ÿ‰ ์ด๋ฏธ์ง€ ์กฐํšŒ ์‹œ ์†๋„๊ฐ€ ๋„ˆ๋ฌด ๋Š๋ฆฌ๋‹ค.

private BufferedImage getImageByResource(ClassPathResource resource) throws IOException { InputStream inputStream = resource.getInputStream(); BufferedImage image = ImageIO.read(inputStream); inputStream.close(); return image; }
Java
๋ณต์‚ฌ
์ด ๋ถ€๋ถ„์—์„œ ImageIO.read()๋กœ ์ด๋ฏธ์ง€์˜ ์ „์ฒด ๋ฐ”์ดํŠธ๋ฅผ ์ฝ๋Š” ๊ณผ์ •์—์„œ ์†๋„๊ฐ€ ๋Š๋ ค์ง€๋Š”๊ฑธ ๋””๋ฒ„๊น…์„ ํ†ตํ•ด ํ™•์ธํ•˜์˜€์Šต๋‹ˆ๋‹ค.
if(file.getSize() > 1024 * 1024 * 5) { throw new CustomException(ErrorCode.EXCEED_SIZE_FILE); } try { try(InputStream inputStream = new ByteArrayInputStream(file.getBytes())) { BufferedImage buff = ImageIO.read(inputStream); float width = buff.getWidth(); float height = buff.getHeight(); if (width > 450) { int resizedWidth = 450; int resizedHeight = (int) (height / (width / 450)); file = imageUtil.imageResize(file, randomName, extension, resizedWidth, resizedHeight); } } } catch (IOException e) { throw new CustomException(ErrorCode.IO_ERROR); }
Java
๋ณต์‚ฌ
์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ฐ€๋Šฅ ์šฉ๋Ÿ‰์„ 5MB๋กœ ์ œํ•œํ•˜๊ณ  ๊ถŒ์žฅ ์‚ฌ์ด์ฆˆ๋ฅผ 450*600๋กœ ์ง€์ •ํ•œ ํ›„ ํด๋ผ์ด์–ธํŠธ์ธก์—๊ฒŒ 3:4 ํฌ๋กญ์„ ๋ถ€ํƒ๋“œ๋ ค ์ตœ๋Œ€ํ•œ ์ด๋ฏธ์ง€๋ฅผ ์ตœ์ ํ™”ํ•˜๊ธฐ๋กœ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๊ถŒ์žฅ ์‚ฌ์ด์ฆˆ์ธ width 450์ด ๋„˜์–ด๊ฐ€๋ฉด ๊ถŒ์žฅ ์‚ฌ์ด์ฆˆ์— ๋งž๊ฒŒ๋” ๋ฆฌ์‚ฌ์ด์ง•์„ ์ง„ํ–‰ํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.
public MultipartFile imageResize(MultipartFile file, String fileName, String fileFormat, int width, int height) { ByteArrayOutputStream outputStream = null; try { int orientation = getOrientation(file); //MultipartFile to BufferedImage BufferedImage originalImage = ImageIO.read(file.getInputStream()); //Rotate Image if (orientation != 1) { originalImage = rotateImage(originalImage, orientation); } //Resize Image BufferedImage resizedImage = Scalr.resize(originalImage, Scalr.Method.AUTOMATIC, Scalr.Mode.FIT_TO_WIDTH, width, height); outputStream = new ByteArrayOutputStream(); ImageIO.write(resizedImage, fileFormat, outputStream); outputStream.flush(); } catch (Exception e) { throw new CustomException(ErrorCode.IO_ERROR); } return new MockMultipartFile(fileName, fileName, file.getContentType(), outputStream.toByteArray()); }
Java
๋ณต์‚ฌ
๋ฆฌ์‚ฌ์ด์ง• ์ฝ”๋“œ๋Š” ์œ„์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

ย ๊ฒฐ๊ณผ๋ฌผ

Loading PDFโ€ฆ