diff --git a/build.gradle b/build.gradle index aee5410..b6610d1 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,20 @@ // Новая версия JasperReports с другим экспортером PDF implementation("net.sf.jasperreports:jasperreports:6.21.5") + // Шрифты для PDF + implementation 'org.apache.xmlgraphics:batik-bridge:1.16' + implementation 'org.apache.xmlgraphics:batik-extension:1.16' + implementation 'org.apache.xmlgraphics:batik-gvt:1.16' + implementation 'org.apache.xmlgraphics:batik-svg-dom:1.16' + + // Шрифты DejaVu (рекомендуемые для русского) +// implementation 'com.github.olivierlemasle:java-dejavu:2.3.0' + + // Или альтернативные шрифты +// implementation 'org.webjars.npm:font-awesome:6.4.0' +// implementation 'net.sf.jasperreports:jasperreports-fonts:6.20.0' + implementation("net.sf.jasperreports:jasperreports-fonts:7.0.3") + // Инструменты разработки developmentOnly 'org.springframework.boot:spring-boot-devtools' // developmentOnly 'org.springframework.boot:spring-boot-docker-compose' diff --git a/src/main/java/ru/mcs/genealogy/controller/TreeController.java b/src/main/java/ru/mcs/genealogy/controller/TreeController.java index a686e08..9f199c6 100644 --- a/src/main/java/ru/mcs/genealogy/controller/TreeController.java +++ b/src/main/java/ru/mcs/genealogy/controller/TreeController.java @@ -55,30 +55,17 @@ Person selectedPerson = personService.getPersonById(id).orElseThrow(); List tree = treeService.buildFullTree(selectedPerson); - // Преобразуем дерево в список для отчета с указанием отношений - List> reportData = new ArrayList<>(); + // Рассчитываем позиции для каждого узла дерева + List> reportData = calculateTreePositions(tree, selectedPerson.getId()); - for (TreeNode node : tree) { - Map row = new HashMap<>(); - row.put("fullName", node.getPerson().getFullName()); - row.put("level", node.getLevel()); - - // Определяем отношение к выбранному человеку - String relationship; - if (node.getLevel() == 0) { - relationship = "Выбранный человек"; - } else if (node.getLevel() < 0) { - relationship = "Предок (" + Math.abs(node.getLevel()) + " поколение)"; - } else { - relationship = "Потомок (" + node.getLevel() + " поколение)"; - } - - row.put("relationship", relationship); - reportData.add(row); + // Загружаем шаблон отчета + InputStream reportStream = getClass().getClassLoader().getResourceAsStream("reports/family-tree2.jrxml"); + if (reportStream == null) { + throw new RuntimeException("Файл шаблона не найден"); } - // Получаем или компилируем отчет - JasperReport jasperReport = getCompiledReport("family-tree"); + // Компилируем отчет + JasperReport jasperReport = JasperCompileManager.compileReport(reportStream); // Создаем datasource JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(reportData); @@ -86,6 +73,7 @@ // Параметры отчета Map parameters = new HashMap<>(); parameters.put("title", "Генеалогическое древо: " + selectedPerson.getFullName()); + parameters.put("selectedPersonId", selectedPerson.getId()); // Заполняем отчет JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource); @@ -96,36 +84,66 @@ // Экспортируем в PDF JasperExportManager.exportReportToPdfStream(jasperPrint, response.getOutputStream()); + + // Закрываем поток + reportStream.close(); } catch (Exception e) { throw new RuntimeException("Ошибка при генерации PDF", e); } } - private JasperReport getCompiledReport(String reportName) throws JRException { - // Проверяем, есть ли уже скомпилированный отчет в кэше - if (compiledReports.containsKey(reportName)) { - return compiledReports.get(reportName); + // Метод для расчета позиций узлов дерева + private List> calculateTreePositions(List tree, Long selectedPersonId) { + List> result = new ArrayList<>(); + + // Группируем узлы по уровням + Map> levels = new HashMap<>(); + for (TreeNode node : tree) { + levels.computeIfAbsent(node.getLevel(), k -> new ArrayList<>()).add(node); } - try { - // Простая загрузка файла шаблона из classpath - InputStream reportStream = getClass().getClassLoader().getResourceAsStream("reports/" + reportName + ".jrxml"); + // Рассчитываем позиции для каждого уровня + int yBase = 100; // Начальная Y-позиция + int levelHeight = 100; // Высота между уровнями - if (reportStream == null) { - throw new JRException("Файл шаблона не найден: reports/" + reportName + ".jrxml"); + for (Map.Entry> entry : levels.entrySet()) { + int level = entry.getKey(); + List nodes = entry.getValue(); + + // Рассчитываем X-позиции для узлов на этом уровне + int levelWidth = 1960; // Ширина уровня + int nodeWidth = 200; // Ширина узла + int spacing = (levelWidth - (nodes.size() * nodeWidth)) / (nodes.size() + 1); + + for (int i = 0; i < nodes.size(); i++) { + TreeNode node = nodes.get(i); + Map row = new HashMap<>(); + + row.put("personId", node.getPerson().getId()); + row.put("fullName", node.getPerson().getFullName()); + row.put("level", node.getLevel()); + row.put("hasChildren", node.isHasChildren()); + + // Определяем ID родителя + if (node.getPerson().getFather() != null) { + row.put("parentId", node.getPerson().getFather().getId()); + } else if (node.getPerson().getMother() != null) { + row.put("parentId", node.getPerson().getMother().getId()); + } else { + row.put("parentId", null); + } + + // Рассчитываем позицию + int xPosition = spacing + i * (nodeWidth + spacing); + int yPosition = yBase + level * levelHeight; + + row.put("xPosition", xPosition); + row.put("yPosition", yPosition); + + result.add(row); } - - // Компилируем отчет - JasperReport jasperReport = JasperCompileManager.compileReport(reportStream); - - // Закрываем поток - reportStream.close(); - - // Кэшируем скомпилированный отчет - compiledReports.put(reportName, jasperReport); - return jasperReport; - } catch (Exception e) { - throw new JRException("Не удалось загрузить или скомпилировать отчет: " + reportName, e); } + + return result; } } \ No newline at end of file diff --git a/src/main/java/ru/mcs/genealogy/dto/TreeNode.java b/src/main/java/ru/mcs/genealogy/dto/TreeNode.java index 7768808..3e5fe0f 100644 --- a/src/main/java/ru/mcs/genealogy/dto/TreeNode.java +++ b/src/main/java/ru/mcs/genealogy/dto/TreeNode.java @@ -26,6 +26,6 @@ } public String getFullName() { - return person.getFullName(); + return person != null ? person.getFullName() : "Неизвестно"; } } \ No newline at end of file diff --git a/src/main/java/ru/mcs/genealogy/service/TreeService.java b/src/main/java/ru/mcs/genealogy/service/TreeService.java index 9e34c18..87f0cd4 100644 --- a/src/main/java/ru/mcs/genealogy/service/TreeService.java +++ b/src/main/java/ru/mcs/genealogy/service/TreeService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; @Service @@ -13,14 +14,19 @@ public List buildFullTree(Person person) { List tree = new ArrayList<>(); - // Добавляем предков (отрицательные уровни) - addAncestors(person, -1, tree); + if (person != null) { + // Добавляем предков (отрицательные уровни) + addAncestors(person, -1, tree); - // Добавляем выбранного человека (уровень 0) - tree.add(new TreeNode(person, 0, !person.getAllChildren().isEmpty())); + // Добавляем выбранного человека (уровень 0) + tree.add(new TreeNode(person, 0, !person.getAllChildren().isEmpty())); - // Добавляем потомков (положительные уровни) - addDescendants(person, 1, tree); + // Добавляем потомков (положительные уровни) + addDescendants(person, 1, tree); + } + + // Сортируем по уровням для правильного отображения + tree.sort(Comparator.comparingInt(TreeNode::getLevel)); return tree; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8974ba4..26b2081 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,7 +2,6 @@ application: name: genealogy - # ???????????? ???? ?????? postgres (??? ??????????) datasource: url: "jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5439}/${DB_DATABASE:genealogy}" username: postgres @@ -13,7 +12,7 @@ hibernate: ddl-auto: update - # ???????????? JPA + # JPA # jpa: # database-platform: org.hibernate.dialect.H2Dialect # hibernate: diff --git a/src/main/resources/reports/family-tree.jrxml b/src/main/resources/reports/family-tree.jrxml index 3604d45..e32c27d 100644 --- a/src/main/resources/reports/family-tree.jrxml +++ b/src/main/resources/reports/family-tree.jrxml @@ -1,15 +1,7 @@ - + + + @@ -17,7 +9,10 @@ <band height="50"> <textField> - <reportElement x="0" y="0" width="555" height="30"/> + <reportElement x="0" y="0" width="555" height="30" uuid="809f711b-e0fa-4125-b896-4ecbae36e95e"/> + <textElement> + <font fontName="DejaVu Sans" pdfFontName="Times-Roman" pdfEncoding="Identity-H" isPdfEmbedded="true"/> + </textElement> <textFieldExpression><![CDATA[$P{title}]]></textFieldExpression> </textField> </band> @@ -25,13 +20,19 @@ <detail> <band height="20"> <textField> - <reportElement x="20" y="0" width="200" height="20"/> + <reportElement x="20" y="0" width="200" height="20" uuid="ca5d6722-1b2e-4e47-97b7-559816ecac27"/> + <textElement> + <font fontName="DejaVu Sans" pdfFontName="Times-Roman" pdfEncoding="Identity-H" isPdfEmbedded="true"/> + </textElement> <textFieldExpression><![CDATA[$F{relationship}]]></textFieldExpression> </textField> - <textField> - <reportElement x="220" y="0" width="300" height="20"/> + <textField textAdjust="ScaleFont"> + <reportElement x="220" y="0" width="300" height="20" uuid="346a9737-3f53-437f-9a0b-26f41653fc0b"/> + <textElement> + <font fontName="DejaVu Sans" pdfFontName="Times-Roman" pdfEncoding="Identity-H" isPdfEmbedded="true"/> + </textElement> <textFieldExpression><![CDATA[$F{fullName}]]></textFieldExpression> </textField> </band> </detail> -</jasperReport> \ No newline at end of file +</jasperReport> diff --git a/src/main/resources/reports/family-tree2.jrxml b/src/main/resources/reports/family-tree2.jrxml new file mode 100644 index 0000000..1ea47d8 --- /dev/null +++ b/src/main/resources/reports/family-tree2.jrxml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Created with Jaspersoft Studio version 6.20.6.final using JasperReports Library version 6.20.6-5c96b6aa8a39ac1dc6b6bea4b81168e16dd39231 --> +<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="family-tree" pageWidth="2000" pageHeight="10000" whenNoDataType="AllSectionsNoDetail" columnWidth="1960" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" isIgnorePagination="true" uuid="7e8cbfee-7c8b-44c3-90c4-a73a43803221"> + <style name="personBox" mode="Opaque" backcolor="#FFFFFF"> + <box> + <pen lineWidth="1.0" lineColor="#000000"/> + </box> + </style> + <style name="selectedPersonBox" mode="Opaque" backcolor="#E3F2FD"> + <box> + <pen lineWidth="2.0" lineColor="#2196F3"/> + </box> + </style> + <parameter name="title" class="java.lang.String"/> + <parameter name="selectedPersonId" class="java.lang.Long"/> + <field name="personId" class="java.lang.Long"/> + <field name="fullName" class="java.lang.String"/> + <field name="level" class="java.lang.Integer"/> + <field name="parentId" class="java.lang.Long"/> + <field name="xPosition" class="java.lang.Integer"/> + <field name="yPosition" class="java.lang.Integer"/> + <field name="hasChildren" class="java.lang.Boolean"/> + <title> + <band height="60"> + <textField> + <reportElement x="0" y="0" width="1960" height="30" forecolor="#000000" uuid="87fb0f90-95d0-404b-b9f8-3cdf7724b4a0"/> + <textElement textAlignment="Center" verticalAlignment="Middle"> + <font fontName="DejaVu Sans" size="20" isBold="true" pdfFontName="Times-Roman" pdfEncoding="Identity-H" isPdfEmbedded="true"/> + </textElement> + <textFieldExpression><![CDATA[$P{title}]]></textFieldExpression> + </textField> + <line> + <reportElement x="0" y="40" width="1960" height="1" uuid="6f7a17a0-d11d-499a-9bdb-c7b4acd827ff"/> + <graphicElement> + <pen lineWidth="2.0" lineColor="#CCCCCC"/> + </graphicElement> + </line> + </band> + + + + + + + + + + + + +