Anonymous
Как я могу гарантировать, что смайлы отображаются правильно в именах файлов и именах пользователей?
Сообщение
Anonymous » 30 сен 2024, 22:13
Я использую iText для обработки различных шрифтов и смайлов, но смайлы не отображаются должным образом. Вместо этого они полностью удаляются.
Это мои зависимости
Код: Выделить всё
com.itextpdf
kernel
7.0.4
com.itextpdf
io
7.0.4
com.itextpdf
layout
7.0.4
com.itextpdf
forms
7.0.4
com.itextpdf
pdfa
7.0.4
com.itextpdf
pdftest
7.0.4
Код: Выделить всё
public class ChatTranscriptServiceImpl implements ChatTranscriptService {
private final ChatParticipantClient chatParticipantClient;
private static final float MARGIN_X = 50;
private static final float MARGIN_Y = 50;
private static final float FONT_SIZE = 12;
private static final float LINE_HEIGHT = 1.5f * FONT_SIZE;
private List messages;
private String ticketNumber;
private String issuedTime;
private String closedTime;
private String clientWhatsAppNumber;
private String customerWhatsAppNumber;
private String agentName;
private String customerName;
private String lastOutgoingMessageTime;
private String fileName;
private PDFont emojiFont;
private PDFont fallbackFont;
private static final PDFont BOLD_FONT = PDType1Font.HELVETICA_BOLD;
private static final PDFont ITALIC_FONT = PDType1Font.TIMES_ITALIC;
private static final PDFont COURIER_FONT = PDType1Font.COURIER;
private static final String NORMAL_FONT = "normal";
public ChatTranscriptServiceImpl(ChatParticipantClient chatParticipantClient) {
this.chatParticipantClient = chatParticipantClient;
}
@Override
public ChatTranscriptResponse downloadChatTranscript(String chatParticipantId) {
this.messages = chatParticipantClient.getMessagesByChatParticipant(chatParticipantId);
byte[] pdfBytes = processMessages();
ChatTranscriptResponse response = new ChatTranscriptResponse();
response.setPdfBytes(pdfBytes);
response.setFilename(fileName);
return response;
}
private byte[] processMessages() {
Pattern ticketNumberPattern = Pattern.compile("Your (?:previous )?ticket number is (.*?) please retain the ticket number to reference this chat.", Pattern.DOTALL);
Pattern ratingMessagePattern = Pattern.compile("Please rate our service.");
for (Message message : this.messages) {
processTicketNumber(ticketNumberPattern, message);
processOutgoingMessage(message);
processRatingMessage(ratingMessagePattern, message);
extractWhatsAppNumbers(message);
}
return finalizeTranscript();
}
private void processTicketNumber(Pattern pattern, Message message) {
String text = message.getText();
if (text != null) {
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
ticketNumber = matcher.group(1).trim();
issuedTime = formatDateTime(message.getCreatedTime());
} else if (issuedTime == null) {
issuedTime = formatDateTime(message.getCreatedTime());
}
} else if (issuedTime == null) {
issuedTime = formatDateTime(message.getCreatedTime());
}
}
private void processOutgoingMessage(Message message) {
if (Pl4ChatMessageDirection.OUTGOING.equals(message.getDirection())) {
lastOutgoingMessageTime = formatDateTime(message.getCreatedTime());
if (message.getAgent() != null) {
agentName = getAgentName(message.getAgent());
} else {
agentName = "Bot";
}
if (message.getChatParticipant() != null) {
customerName = getCustomerName(message.getChatParticipant());
}
}
}
private void processRatingMessage(Pattern pattern, Message message) {
Matcher matcher = pattern.matcher(message.getText());
if (matcher.find()) {
closedTime = formatDateTime(message.getCreatedTime());
} else if (message.getChatParticipant() != null && message.getChatParticipant().isConversationClosed()) {
closedTime = lastOutgoingMessageTime != null ? lastOutgoingMessageTime : "Open";
} else {
closedTime = "Open";
}
}
private void extractWhatsAppNumbers(Message message) {
if (clientWhatsAppNumber == null && message.getChatParticipant() != null && message.getChatParticipant().getWhatsAppClientNumber() != null) {
clientWhatsAppNumber = message.getChatParticipant().getWhatsAppClientNumber();
}
if (customerWhatsAppNumber == null && message.getChatParticipant() != null && message.getChatParticipant().getPhoneNumber() != null) {
customerWhatsAppNumber = message.getChatParticipant().getPhoneNumber();
}
}
private String getAgentName(SystemUser agent) {
if (agent.getFirstName() != null && agent.getLastName() != null) {
return agent.getFirstName() + " " + agent.getLastName();
} else if (agent.getFirstName() != null) {
return agent.getFirstName();
} else if (agent.getLastName() != null) {
return agent.getLastName();
}
return "Bot";
}
private String getCustomerName(ChatParticipant participant) {
if (participant.getFullName() != null) {
return participant.getFullName();
} else if (participant.getFirstName() != null && participant.getLastName() != null) {
return participant.getFirstName() + " " + participant.getLastName();
} else if (participant.getFirstName() != null) {
return participant.getFirstName();
} else if (participant.getLastName() != null) {
return participant.getLastName();
}
return "Unknown";
}
private byte[] finalizeTranscript() {
try {
return this.generatePdfTranscript();
} catch (IOException e) {
log.error("Error generating file: {}", e.getMessage());
throw CustomParameterizedException.singleParameterProblem(
String.format("Error generating transcript... %n%s", e.getMessage()),
Status.BAD_REQUEST
);
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
private byte[] generatePdfTranscript() throws IOException, DocumentException {
if (ticketNumber != null) {
fileName = sanitizeFilename(ticketNumber + "_transcript.pdf");
} else {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
fileName = sanitizeFilename(customerName + "_" + LocalDateTime.now().format(formatter) + "_transcript.pdf");
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Document document = new Document(PageSize.A4, MARGIN_X, MARGIN_X, MARGIN_Y, MARGIN_Y);
PdfWriter writer = PdfWriter.getInstance(document, byteArrayOutputStream);
document.open();
PDDocument pdDocument = new PDDocument();
initializeFonts(pdDocument);
setupPdfHeader(document);
addTranscriptTitle(document);
for (Message message : messages) {
addMessage(document, writer, message);
}
document.close();
pdDocument.close();
return byteArrayOutputStream.toByteArray();
}
private void setupPdfHeader(Document document) throws DocumentException {
Font headerFont = new Font(Font.FontFamily.HELVETICA, FONT_SIZE, Font.BOLD);
Paragraph header = new Paragraph();
header.setAlignment(Element.ALIGN_LEFT);
if (ticketNumber != null) {
header.add(new Chunk("Ticket Number: " + ticketNumber + "\n", headerFont));
header.add(new Chunk("Date & Time Ticket Issued: " + issuedTime + "\n", headerFont));
header.add(new Chunk("Date & Time Ticket Closed: " + closedTime + "\n", headerFont));
} else {
header.add(new Chunk("No ticket issued\n", headerFont));
}
if (clientWhatsAppNumber != null) {
header.add(new Chunk("Service's WhatsApp number: " + clientWhatsAppNumber + "\n", headerFont));
}
if (customerWhatsAppNumber != null) {
header.add(new Chunk("Customer's WhatsApp number: " + customerWhatsAppNumber + "\n", headerFont));
}
header.add(new Chunk("Agent's Name & Surname: " + agentName + "\n", headerFont));
header.add(new Chunk("Customer's Name & Surname: " + customerName + "\n", headerFont));
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100);
PdfPCell cell = new PdfPCell(header);
cell.setBorder(Rectangle.BOX);
cell.setPadding(10);
table.addCell(cell);
document.add(table);
document.add(Chunk.NEWLINE);
}
private void addTranscriptTitle(Document document) throws DocumentException {
Font titleFont = new Font(Font.FontFamily.HELVETICA, FONT_SIZE + 2, Font.BOLD);
Paragraph title = new Paragraph("Ticket Transcript:", titleFont);
title.setAlignment(Element.ALIGN_LEFT);
document.add(title);
document.add(Chunk.NEWLINE);
}
private void addMessage(Document document, PdfWriter writer, Message message) throws DocumentException, IOException {
String sender = message.getDirection().equals(Pl4ChatMessageDirection.OUTGOING) ? agentName : customerName;
if (message.getContentType().equals(Pl4ContentType.MEDIA)) {
addMediaMessage(document, writer, message, sender);
} else {
String messageText = sender + ": " + message.getText();
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100);
PdfPCell cell = new PdfPCell();
cell.setPadding(10);
cell.setBorder(Rectangle.BOX);
cell.setBackgroundColor(message.getDirection().equals(Pl4ChatMessageDirection.OUTGOING) ?
new BaseColor(220, 220, 220) : new BaseColor(220, 248, 198));
Paragraph paragraph = new Paragraph();
addMixedTextToParagraph(paragraph, messageText);
cell.addElement(paragraph);
table.addCell(cell);
document.add(table);
addTimestamp(document, message);
}
}
private void addMediaMessage(Document document, PdfWriter writer, Message message, String sender) throws DocumentException {
String mediaURL = message.getMediaURL();
String linkText = message.getText() != null ? message.getText() : "View Media";
String senderText = sender + ": ";
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100);
PdfPCell cell = new PdfPCell();
cell.setPadding(10);
cell.setBorder(Rectangle.BOX);
cell.setBackgroundColor(message.getDirection().equals(Pl4ChatMessageDirection.OUTGOING) ?
new BaseColor(220, 220, 220) : new BaseColor(220, 248, 198));
Paragraph paragraph = new Paragraph();
paragraph.add(new Chunk(senderText, new Font(Font.FontFamily.HELVETICA, FONT_SIZE)));
Anchor anchor = new Anchor(linkText, new Font(Font.FontFamily.HELVETICA, FONT_SIZE, Font.UNDERLINE, BaseColor.BLUE));
anchor.setReference(mediaURL);
paragraph.add(anchor);
cell.addElement(paragraph);
table.addCell(cell);
document.add(table);
addTimestamp(document, message);
}
private void addTimestamp(Document document, Message message) throws DocumentException {
String timeString = formatDateTime(message.getCreatedTime());
Paragraph timestamp = new Paragraph(timeString, new Font(Font.FontFamily.HELVETICA, 8));
timestamp.setAlignment(Element.ALIGN_RIGHT);
document.add(timestamp);
document.add(Chunk.NEWLINE);
}
private String formatDateTime(LocalDateTime dateTime) {
return dateTime != null ? dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : "N/A";
}
private void initializeFonts(PDDocument document) throws IOException {
fallbackFont = PDType1Font.HELVETICA;
try (InputStream emojiFontStream = getClass().getClassLoader().getResourceAsStream("static/NotoColorEmoji-Regular.ttf")) {
if (emojiFontStream == null) {
throw new FileNotFoundException("Emoji font file not found in classpath.");
}
emojiFont = PDType0Font.load(document, emojiFontStream);
}
}
private void addMixedTextToParagraph(Paragraph paragraph, String text) {
for (int i = 0; i < text.length(); i++) {
int codePoint = text.codePointAt(i);
String charString = new String(Character.toChars(codePoint));
Font font = Character.isSupplementaryCodePoint(codePoint) ? new Font(Font.FontFamily.UNDEFINED) : new Font(Font.FontFamily.HELVETICA);
paragraph.add(new Chunk(charString, font));
if (Character.isSupplementaryCodePoint(codePoint)) {
i++;
}
}
}
private List
splitTextToLines(String text, float maxWidth) {
List processedParts = processText(text);
List lines = new ArrayList();
StringBuilder currentLine = new StringBuilder();
String currentStyle = "";
for (ProcessedPart part : processedParts) {
String[] words = part.text.split("\\s+");
for (String word : words) {
if (fitsInCurrentLine(currentLine, word, maxWidth)) {
appendWordToLine(currentLine, word);
} else {
addLineIfNotEmpty(lines, currentLine, currentStyle);
currentLine = new StringBuilder(word);
}
}
if (part.lineBreak > 0) {
addLineIfNotEmpty(lines, currentLine, currentStyle);
addLineBreak(lines, part.lineBreak);
}
currentStyle = part.style;
}
addLineIfNotEmpty(lines, currentLine, currentStyle);
return lines;
}
private boolean fitsInCurrentLine(StringBuilder currentLine, String word, float maxWidth) {
String testLine = currentLine.length() > 0 ? currentLine + " " + word : word;
return calculateWidth(testLine) 0) {
line.append(" ");
}
line.append(word);
}
private void addLineIfNotEmpty(List lines, StringBuilder line, String style) {
if (line.length() > 0) {
lines.add(new ProcessedPart(line.toString(), style, 0));
line.setLength(0);
}
}
private void addLineBreak(List lines, int lineBreak) {
lines.add(new ProcessedPart("", "", lineBreak));
}
private float calculateWidth(String text) {
float width = 0;
for (int i = 0; i < text.length(); i++) {
int codePoint = text.codePointAt(i);
PDFont currentFont = Character.isSupplementaryCodePoint(codePoint) ? emojiFont : fallbackFont;
try {
String charString = new String(Character.toChars(codePoint));
width += currentFont.getStringWidth(charString) / 1000 * FONT_SIZE;
} catch (IllegalArgumentException | IOException e) {
log.warn("Unable to calculate width for character: {} ({}). Using fallback width.",
String.format("U+%04X", codePoint), new String(Character.toChars(codePoint)));
width += fallbackFont.getAverageFontWidth() / 1000 * FONT_SIZE;
}
if (Character.isSupplementaryCodePoint(codePoint)) {
i++;
}
}
return width;
}
public static class ProcessedPart {
String text;
String style;
int lineBreak;
public ProcessedPart(String text, String style, int lineBreak) {
this.text = text;
this.style = style;
this.lineBreak = lineBreak;
}
@Override
public String toString() {
return "ProcessedPart{" +
"text='" + text + '\'' +
", style='" + style + '\'' +
", lineBreak=" + lineBreak +
'}';
}
}
private List processText(String messageText) {
List partsList = new ArrayList();
String regex = "(.*?[/b]|[i].*?[/i]|.*?|.*?
|
|\\n)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(messageText);
int lastEnd = 0;
while (matcher.find()) {
if (matcher.start() > lastEnd) {
partsList.add(new ProcessedPart(messageText.substring(lastEnd, matcher.start()), NORMAL_FONT, 0));
}
String part = matcher.group();
if ("
".equals(part)) {
partsList.add(new ProcessedPart("", NORMAL_FONT, 1));
} else if ("\n".equals(part)) {
partsList.add(new ProcessedPart("", NORMAL_FONT, 1));
} else {
handleFormatters(partsList, part);
}
lastEnd = matcher.end();
}
if (lastEnd < messageText.length()) {
partsList.add(new ProcessedPart(messageText.substring(lastEnd), NORMAL_FONT, 0));
}
return partsList;
}
private void handleFormatters(List partsList, String part) {
String substring = part.substring(3, part.length() - 4);
if (part.startsWith("") && part.endsWith(" ")) {
partsList.add(new ProcessedPart(substring, "bold", 0));
} else if (part.startsWith("
") && part.endsWith(" ")) {
partsList.add(new ProcessedPart(substring, "italic", 0));
} else if (part.startsWith("") && part.endsWith("")) {
partsList.add(new ProcessedPart(substring, "strikethrough", 0));
} else if (part.startsWith("
")) {
partsList.add(new ProcessedPart(part.substring(6, part.length() - 7), "monospace", 0));
}
}
private String sanitizeFilename(String filename) {
// Remove or replace characters that are not allowed in filenames
return filename.replaceAll("[\\\\/:*?\"|]", "_");
}
}
Я пробовал отображать смайлы как они есть, но имя клиента перенастраивается без смайликов «Esphi N» вместо этого «
Esphi N
», моя цель — отображать их такими, какие они есть, как для имени файла, так и для имени пользователя.
Подробнее здесь:
https://stackoverflow.com/questions/790 ... -usernames
1727723602
Anonymous
Я использую iText для обработки различных шрифтов и смайлов, но смайлы не отображаются должным образом. Вместо этого они полностью удаляются.[b]Это мои зависимости [code] com.itextpdf kernel 7.0.4 com.itextpdf io 7.0.4 com.itextpdf layout 7.0.4 com.itextpdf forms 7.0.4 com.itextpdf pdfa 7.0.4 com.itextpdf pdftest 7.0.4 [/code] [code]public class ChatTranscriptServiceImpl implements ChatTranscriptService { private final ChatParticipantClient chatParticipantClient; private static final float MARGIN_X = 50; private static final float MARGIN_Y = 50; private static final float FONT_SIZE = 12; private static final float LINE_HEIGHT = 1.5f * FONT_SIZE; private List messages; private String ticketNumber; private String issuedTime; private String closedTime; private String clientWhatsAppNumber; private String customerWhatsAppNumber; private String agentName; private String customerName; private String lastOutgoingMessageTime; private String fileName; private PDFont emojiFont; private PDFont fallbackFont; private static final PDFont BOLD_FONT = PDType1Font.HELVETICA_BOLD; private static final PDFont ITALIC_FONT = PDType1Font.TIMES_ITALIC; private static final PDFont COURIER_FONT = PDType1Font.COURIER; private static final String NORMAL_FONT = "normal"; public ChatTranscriptServiceImpl(ChatParticipantClient chatParticipantClient) { this.chatParticipantClient = chatParticipantClient; } @Override public ChatTranscriptResponse downloadChatTranscript(String chatParticipantId) { this.messages = chatParticipantClient.getMessagesByChatParticipant(chatParticipantId); byte[] pdfBytes = processMessages(); ChatTranscriptResponse response = new ChatTranscriptResponse(); response.setPdfBytes(pdfBytes); response.setFilename(fileName); return response; } private byte[] processMessages() { Pattern ticketNumberPattern = Pattern.compile("Your (?:previous )?ticket number is (.*?) please retain the ticket number to reference this chat.", Pattern.DOTALL); Pattern ratingMessagePattern = Pattern.compile("Please rate our service."); for (Message message : this.messages) { processTicketNumber(ticketNumberPattern, message); processOutgoingMessage(message); processRatingMessage(ratingMessagePattern, message); extractWhatsAppNumbers(message); } return finalizeTranscript(); } private void processTicketNumber(Pattern pattern, Message message) { String text = message.getText(); if (text != null) { Matcher matcher = pattern.matcher(text); if (matcher.find()) { ticketNumber = matcher.group(1).trim(); issuedTime = formatDateTime(message.getCreatedTime()); } else if (issuedTime == null) { issuedTime = formatDateTime(message.getCreatedTime()); } } else if (issuedTime == null) { issuedTime = formatDateTime(message.getCreatedTime()); } } private void processOutgoingMessage(Message message) { if (Pl4ChatMessageDirection.OUTGOING.equals(message.getDirection())) { lastOutgoingMessageTime = formatDateTime(message.getCreatedTime()); if (message.getAgent() != null) { agentName = getAgentName(message.getAgent()); } else { agentName = "Bot"; } if (message.getChatParticipant() != null) { customerName = getCustomerName(message.getChatParticipant()); } } } private void processRatingMessage(Pattern pattern, Message message) { Matcher matcher = pattern.matcher(message.getText()); if (matcher.find()) { closedTime = formatDateTime(message.getCreatedTime()); } else if (message.getChatParticipant() != null && message.getChatParticipant().isConversationClosed()) { closedTime = lastOutgoingMessageTime != null ? lastOutgoingMessageTime : "Open"; } else { closedTime = "Open"; } } private void extractWhatsAppNumbers(Message message) { if (clientWhatsAppNumber == null && message.getChatParticipant() != null && message.getChatParticipant().getWhatsAppClientNumber() != null) { clientWhatsAppNumber = message.getChatParticipant().getWhatsAppClientNumber(); } if (customerWhatsAppNumber == null && message.getChatParticipant() != null && message.getChatParticipant().getPhoneNumber() != null) { customerWhatsAppNumber = message.getChatParticipant().getPhoneNumber(); } } private String getAgentName(SystemUser agent) { if (agent.getFirstName() != null && agent.getLastName() != null) { return agent.getFirstName() + " " + agent.getLastName(); } else if (agent.getFirstName() != null) { return agent.getFirstName(); } else if (agent.getLastName() != null) { return agent.getLastName(); } return "Bot"; } private String getCustomerName(ChatParticipant participant) { if (participant.getFullName() != null) { return participant.getFullName(); } else if (participant.getFirstName() != null && participant.getLastName() != null) { return participant.getFirstName() + " " + participant.getLastName(); } else if (participant.getFirstName() != null) { return participant.getFirstName(); } else if (participant.getLastName() != null) { return participant.getLastName(); } return "Unknown"; } private byte[] finalizeTranscript() { try { return this.generatePdfTranscript(); } catch (IOException e) { log.error("Error generating file: {}", e.getMessage()); throw CustomParameterizedException.singleParameterProblem( String.format("Error generating transcript... %n%s", e.getMessage()), Status.BAD_REQUEST ); } catch (DocumentException e) { throw new RuntimeException(e); } } private byte[] generatePdfTranscript() throws IOException, DocumentException { if (ticketNumber != null) { fileName = sanitizeFilename(ticketNumber + "_transcript.pdf"); } else { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); fileName = sanitizeFilename(customerName + "_" + LocalDateTime.now().format(formatter) + "_transcript.pdf"); } ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Document document = new Document(PageSize.A4, MARGIN_X, MARGIN_X, MARGIN_Y, MARGIN_Y); PdfWriter writer = PdfWriter.getInstance(document, byteArrayOutputStream); document.open(); PDDocument pdDocument = new PDDocument(); initializeFonts(pdDocument); setupPdfHeader(document); addTranscriptTitle(document); for (Message message : messages) { addMessage(document, writer, message); } document.close(); pdDocument.close(); return byteArrayOutputStream.toByteArray(); } private void setupPdfHeader(Document document) throws DocumentException { Font headerFont = new Font(Font.FontFamily.HELVETICA, FONT_SIZE, Font.BOLD); Paragraph header = new Paragraph(); header.setAlignment(Element.ALIGN_LEFT); if (ticketNumber != null) { header.add(new Chunk("Ticket Number: " + ticketNumber + "\n", headerFont)); header.add(new Chunk("Date & Time Ticket Issued: " + issuedTime + "\n", headerFont)); header.add(new Chunk("Date & Time Ticket Closed: " + closedTime + "\n", headerFont)); } else { header.add(new Chunk("No ticket issued\n", headerFont)); } if (clientWhatsAppNumber != null) { header.add(new Chunk("Service's WhatsApp number: " + clientWhatsAppNumber + "\n", headerFont)); } if (customerWhatsAppNumber != null) { header.add(new Chunk("Customer's WhatsApp number: " + customerWhatsAppNumber + "\n", headerFont)); } header.add(new Chunk("Agent's Name & Surname: " + agentName + "\n", headerFont)); header.add(new Chunk("Customer's Name & Surname: " + customerName + "\n", headerFont)); PdfPTable table = new PdfPTable(1); table.setWidthPercentage(100); PdfPCell cell = new PdfPCell(header); cell.setBorder(Rectangle.BOX); cell.setPadding(10); table.addCell(cell); document.add(table); document.add(Chunk.NEWLINE); } private void addTranscriptTitle(Document document) throws DocumentException { Font titleFont = new Font(Font.FontFamily.HELVETICA, FONT_SIZE + 2, Font.BOLD); Paragraph title = new Paragraph("Ticket Transcript:", titleFont); title.setAlignment(Element.ALIGN_LEFT); document.add(title); document.add(Chunk.NEWLINE); } private void addMessage(Document document, PdfWriter writer, Message message) throws DocumentException, IOException { String sender = message.getDirection().equals(Pl4ChatMessageDirection.OUTGOING) ? agentName : customerName; if (message.getContentType().equals(Pl4ContentType.MEDIA)) { addMediaMessage(document, writer, message, sender); } else { String messageText = sender + ": " + message.getText(); PdfPTable table = new PdfPTable(1); table.setWidthPercentage(100); PdfPCell cell = new PdfPCell(); cell.setPadding(10); cell.setBorder(Rectangle.BOX); cell.setBackgroundColor(message.getDirection().equals(Pl4ChatMessageDirection.OUTGOING) ? new BaseColor(220, 220, 220) : new BaseColor(220, 248, 198)); Paragraph paragraph = new Paragraph(); addMixedTextToParagraph(paragraph, messageText); cell.addElement(paragraph); table.addCell(cell); document.add(table); addTimestamp(document, message); } } private void addMediaMessage(Document document, PdfWriter writer, Message message, String sender) throws DocumentException { String mediaURL = message.getMediaURL(); String linkText = message.getText() != null ? message.getText() : "View Media"; String senderText = sender + ": "; PdfPTable table = new PdfPTable(1); table.setWidthPercentage(100); PdfPCell cell = new PdfPCell(); cell.setPadding(10); cell.setBorder(Rectangle.BOX); cell.setBackgroundColor(message.getDirection().equals(Pl4ChatMessageDirection.OUTGOING) ? new BaseColor(220, 220, 220) : new BaseColor(220, 248, 198)); Paragraph paragraph = new Paragraph(); paragraph.add(new Chunk(senderText, new Font(Font.FontFamily.HELVETICA, FONT_SIZE))); Anchor anchor = new Anchor(linkText, new Font(Font.FontFamily.HELVETICA, FONT_SIZE, Font.UNDERLINE, BaseColor.BLUE)); anchor.setReference(mediaURL); paragraph.add(anchor); cell.addElement(paragraph); table.addCell(cell); document.add(table); addTimestamp(document, message); } private void addTimestamp(Document document, Message message) throws DocumentException { String timeString = formatDateTime(message.getCreatedTime()); Paragraph timestamp = new Paragraph(timeString, new Font(Font.FontFamily.HELVETICA, 8)); timestamp.setAlignment(Element.ALIGN_RIGHT); document.add(timestamp); document.add(Chunk.NEWLINE); } private String formatDateTime(LocalDateTime dateTime) { return dateTime != null ? dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : "N/A"; } private void initializeFonts(PDDocument document) throws IOException { fallbackFont = PDType1Font.HELVETICA; try (InputStream emojiFontStream = getClass().getClassLoader().getResourceAsStream("static/NotoColorEmoji-Regular.ttf")) { if (emojiFontStream == null) { throw new FileNotFoundException("Emoji font file not found in classpath."); } emojiFont = PDType0Font.load(document, emojiFontStream); } } private void addMixedTextToParagraph(Paragraph paragraph, String text) { for (int i = 0; i < text.length(); i++) { int codePoint = text.codePointAt(i); String charString = new String(Character.toChars(codePoint)); Font font = Character.isSupplementaryCodePoint(codePoint) ? new Font(Font.FontFamily.UNDEFINED) : new Font(Font.FontFamily.HELVETICA); paragraph.add(new Chunk(charString, font)); if (Character.isSupplementaryCodePoint(codePoint)) { i++; } } } private List splitTextToLines(String text, float maxWidth) { List processedParts = processText(text); List lines = new ArrayList(); StringBuilder currentLine = new StringBuilder(); String currentStyle = ""; for (ProcessedPart part : processedParts) { String[] words = part.text.split("\\s+"); for (String word : words) { if (fitsInCurrentLine(currentLine, word, maxWidth)) { appendWordToLine(currentLine, word); } else { addLineIfNotEmpty(lines, currentLine, currentStyle); currentLine = new StringBuilder(word); } } if (part.lineBreak > 0) { addLineIfNotEmpty(lines, currentLine, currentStyle); addLineBreak(lines, part.lineBreak); } currentStyle = part.style; } addLineIfNotEmpty(lines, currentLine, currentStyle); return lines; } private boolean fitsInCurrentLine(StringBuilder currentLine, String word, float maxWidth) { String testLine = currentLine.length() > 0 ? currentLine + " " + word : word; return calculateWidth(testLine) 0) { line.append(" "); } line.append(word); } private void addLineIfNotEmpty(List lines, StringBuilder line, String style) { if (line.length() > 0) { lines.add(new ProcessedPart(line.toString(), style, 0)); line.setLength(0); } } private void addLineBreak(List lines, int lineBreak) { lines.add(new ProcessedPart("", "", lineBreak)); } private float calculateWidth(String text) { float width = 0; for (int i = 0; i < text.length(); i++) { int codePoint = text.codePointAt(i); PDFont currentFont = Character.isSupplementaryCodePoint(codePoint) ? emojiFont : fallbackFont; try { String charString = new String(Character.toChars(codePoint)); width += currentFont.getStringWidth(charString) / 1000 * FONT_SIZE; } catch (IllegalArgumentException | IOException e) { log.warn("Unable to calculate width for character: {} ({}). Using fallback width.", String.format("U+%04X", codePoint), new String(Character.toChars(codePoint))); width += fallbackFont.getAverageFontWidth() / 1000 * FONT_SIZE; } if (Character.isSupplementaryCodePoint(codePoint)) { i++; } } return width; } public static class ProcessedPart { String text; String style; int lineBreak; public ProcessedPart(String text, String style, int lineBreak) { this.text = text; this.style = style; this.lineBreak = lineBreak; } @Override public String toString() { return "ProcessedPart{" + "text='" + text + '\'' + ", style='" + style + '\'' + ", lineBreak=" + lineBreak + '}'; } } private List processText(String messageText) { List partsList = new ArrayList(); String regex = "(.*?[/b]|[i].*?[/i]|.*?|.*?[/code]|[b]|\\n)"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(messageText); int lastEnd = 0; while (matcher.find()) { if (matcher.start() > lastEnd) { partsList.add(new ProcessedPart(messageText.substring(lastEnd, matcher.start()), NORMAL_FONT, 0)); } String part = matcher.group(); if (" ".equals(part)) { partsList.add(new ProcessedPart("", NORMAL_FONT, 1)); } else if ("\n".equals(part)) { partsList.add(new ProcessedPart("", NORMAL_FONT, 1)); } else { handleFormatters(partsList, part); } lastEnd = matcher.end(); } if (lastEnd < messageText.length()) { partsList.add(new ProcessedPart(messageText.substring(lastEnd), NORMAL_FONT, 0)); } return partsList; } private void handleFormatters(List partsList, String part) { String substring = part.substring(3, part.length() - 4); if (part.startsWith("") && part.endsWith("[/b]")) { partsList.add(new ProcessedPart(substring, "bold", 0)); } else if (part.startsWith("[i]") && part.endsWith("[/i]")) { partsList.add(new ProcessedPart(substring, "italic", 0)); } else if (part.startsWith("") && part.endsWith("")) { partsList.add(new ProcessedPart(substring, "strikethrough", 0)); } else if (part.startsWith("[code]") && part.endsWith("[/code]")) { partsList.add(new ProcessedPart(part.substring(6, part.length() - 7), "monospace", 0)); } } private String sanitizeFilename(String filename) { // Remove or replace characters that are not allowed in filenames return filename.replaceAll("[\\\\/:*?\"|]", "_"); } } Я пробовал отображать смайлы как они есть, но имя клиента перенастраивается без смайликов «Esphi N» вместо этого «🌹🦬🪶🐓Esphi N🤣😂», моя цель — отображать их такими, какие они есть, как для имени файла, так и для имени пользователя. Подробнее здесь: [url]https://stackoverflow.com/questions/79040757/how-can-i-ensure-that-emojis-are-displayed-correctly-in-filenames-or-usernames[/url]