package open.dolphin.letter; import com.lowagie.text.*; import com.lowagie.text.pdf.*; import java.awt.Color; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.net.MalformedURLException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.List; import open.dolphin.client.ClientContext; import open.dolphin.infomodel.*; import open.dolphin.project.Project; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; /** * PDFKarteMaker.java * カルテをPDFにエクスポートする。KarteRenderer2から主なコード拝借 * * @author masuda, Masuda Naika */ public class KartePDFMaker extends AbstractPDFMaker { // from KarteRenderer_2.java private static final String COMPONENT_ELEMENT_NAME = "component"; private static final String STAMP_HOLDER = "stampHolder"; private static final String SCHEMA_HOLDER = "schemaHolder"; private static final int TT_SECTION = 0; private static final int TT_PARAGRAPH = 1; private static final int TT_CONTENT = 2; private static final int TT_ICON = 3; private static final int TT_COMPONENT = 4; private static final int TT_PROGRESS_COURSE = 5; private static final String SECTION_NAME = "section"; private static final String PARAGRAPH_NAME = "paragraph"; private static final String CONTENT_NAME = "content"; private static final String COMPONENT_NAME = "component"; private static final String ICON_NAME = "icon"; private static final String ALIGNMENT_NAME = "Alignment"; private static final String FOREGROUND_NAME = "foreground"; private static final String SIZE_NAME = "size"; private static final String BOLD_NAME = "bold"; private static final String ITALIC_NAME = "italic"; private static final String UNDERLINE_NAME = "underline"; private static final String TEXT_NAME = "text"; private static final String NAME_NAME = "name"; private static final String PROGRESS_COURSE_NAME = "kartePane"; //private static final String[] REPLACES = new String[]{"<", ">", "&", "'", "\""}; private static final String[] REPLACES = new String[]{"&", "<", ">", "'", "\""}; //private static final String[] MATCHES = new String[]{"<", ">", "&", "'", """}; private static final String[] MATCHES = new String[]{"&", "<", ">", "'", """}; private static final int KARTE_FONT_SIZE = 9; private static final int STAMP_FONT_SIZE = 8; private static final int PERCENTAGE_IMAGE_WIDTH = 25; // ページ幅の1/4 private static final Color STAMP_TITLE_BACKGROUND = new Color(200, 200, 200); // グレー //private static final String UNDER_TMP_SAVE = " - 仮保存中"; //private static final String DOC_TITLE = "カルテ"; private List<DocumentModel> docList; private boolean ascending; private int bookmarkNumber; // しおりの内部番号 @Override protected final String getPatientName() { return (context != null) ? context.getPatient().getFullName() : null; } @Override protected final String getPatientId() { return (context != null) ? context.getPatient().getPatientId() : null; } private String getPatientBirthday() { return (context != null) ? context.getPatient().getBirthday() : null; } // 文書名を返す @Override protected String getTitle() { return ClientContext.getMyBundle(KartePDFMaker.class).getString("text.karte"); } // PDFに出力する @Override public boolean makePDF(String filePath) { boolean result = false; marginLeft = 20; marginRight = 20; marginTop = 20; marginBottom = 30; titleFontSize = 10; // 昇順・降順にソート if (ascending) { Collections.sort(docList); } else { Collections.sort(docList, Collections.reverseOrder()); } // 用紙サイズを設定 Document document = new Document(PageSize.A4, marginLeft, marginRight, marginTop, marginBottom); try { // Font baseFont = getGothicFont(); Font font = new Font(baseFont, KARTE_FONT_SIZE); // PdfWriterの設定 //minagawa^ mac jdk7 // writer = PdfWriter.getInstance(document, new FileOutputStream(filePath)); Path path = Paths.get(filePath); writer = PdfWriter.getInstance(document, Files.newOutputStream(path)); //minagawa$ writer.setStrictImageSequence(true); writer.setViewerPreferences(PdfWriter.PageModeUseOutlines); java.util.ResourceBundle bundle = ClientContext.getMyBundle(KartePDFMaker.class); // フッターに名前とIDを入れる StringBuilder sb = new StringBuilder(); sb.append(getPatientId()).append(" "); sb.append(getPatientName()).append(bundle.getString("text.space_personTitle")); sb.append(FRMT_DATE_WITH_TIME.format(new Date())); sb.append(" Page "); HeaderFooter footer = new HeaderFooter(new Phrase(sb.toString(), font), true); footer.setAlignment(Element.ALIGN_CENTER); footer.setBorder(Rectangle.NO_BORDER); document.setFooter(footer); // 製作者と文書タイトルを設定 String author = Project.getUserModel().getFacilityModel().getFacilityName(); document.addAuthor(author); document.addTitle(getPatientName() + bundle.getString("text.space_personTitle_karte")); document.open(); for (DocumentModel docModel : docList) { // DocumentModelからschema, moduleを取り出す List<SchemaModel> schemas = docModel.getSchema(); List<ModuleModel> modules = docModel.getModules(); List<ModuleModel> soaModules = new ArrayList<>(); List<ModuleModel> pModules = new ArrayList<>(); String soaSpec = null; String pSpec = null; for (ModuleModel bean : modules) { String role = bean.getModuleInfoBean().getStampRole(); switch (role) { case IInfoModel.ROLE_SOA: soaModules.add(bean); break; case IInfoModel.ROLE_SOA_SPEC: soaSpec = ((ProgressCourse) bean.getModel()).getFreeText(); break; case IInfoModel.ROLE_P: pModules.add(bean); break; case IInfoModel.ROLE_P_SPEC: pSpec = ((ProgressCourse) bean.getModel()).getFreeText(); break; } } // 念のためソート Collections.sort(soaModules); Collections.sort(pModules); // テーブルを作成する KarteTable table; DocInfoModel docInfo = docModel.getDocInfoModel(); if (docInfo != null && docInfo.getDocType().equals(IInfoModel.DOCTYPE_S_KARTE)) { table = createTable(docModel, 1); PdfPCell cell = new PdfCellRenderer().render(soaSpec, soaModules, schemas, table); cell.setColspan(2); table.addCell(cell); } else { table = createTable(docModel, 2); PdfPCell cell = new PdfCellRenderer().render(soaSpec, soaModules, schemas, table); table.addCell(cell); cell = new PdfCellRenderer().render(pSpec, pModules, schemas, table); table.addCell(cell); } // PdfDocumentに追加する document.add(table); } result = true; } catch (IOException ex) { java.util.logging.Logger.getLogger(this.getClass().getName()).warning(ex.getMessage()); throw new RuntimeException(ERROR_IO); } catch (DocumentException ex) { java.util.logging.Logger.getLogger(this.getClass().getName()).warning(ex.getMessage()); throw new RuntimeException(ERROR_PDF); } finally { if (document.isOpen()) { document.close(); } if (writer != null) { writer.close(); } } return result; } public void setDocumentList(List<DocumentModel> docList) { this.docList = docList; } public void setAscending(boolean b) { this.ascending = b; } private KarteTable createTable(DocumentModel model, int col) { // タイトルのフォント Font font = new Font(baseFont, titleFontSize); String title = createTitle(model); // しおりのタイトルは日付とDocInfo.tilte StringBuilder sb = new StringBuilder(); sb.append(FRMT_DATE_WITH_TIME.format(model.getDocInfoModel().getFirstConfirmDate())); sb.append("\n"); sb.append(model.getDocInfoModel().getTitle()); String bookmark = sb.toString(); // タイトルにしおりを登録する String mark = String.valueOf(++bookmarkNumber); Chunk chunk = new Chunk(title, font).setLocalDestination(mark); PdfOutline root = writer.getDirectContent().getRootOutline(); new PdfOutline(root, PdfAction.gotoLocalPage(mark, false), bookmark); // テーブルを作成する PdfPCell titleCell = new PdfPCell(new Paragraph(chunk)); titleCell.setHorizontalAlignment(Element.ALIGN_CENTER); KarteTable table = new KarteTable(col); titleCell.setColspan(col); table.addCell(titleCell); table.setWidthPercentage(100); table.setSpacingAfter(2); // ヘッダー行を指定 //table.setHeaderRows(1); // 改頁で表の分割を許可 table.setSplitLate(false); return table; } // カルテのタイトルを作成する private String createTitle(DocumentModel docModel) { StringBuilder sb = new StringBuilder(); java.util.ResourceBundle bundle = ClientContext.getMyBundle(KartePDFMaker.class); if (IInfoModel.STATUS_DELETE.equals(docModel.getDocInfoModel().getStatus())) { sb.append(bundle.getString("text.doneDelete_slash")); } else if (IInfoModel.STATUS_MODIFIED.equals(docModel.getDocInfoModel().getStatus())) { sb.append(bundle.getString("text.modify_colon")); sb.append(docModel.getDocInfoModel().getVersionNumber().replace(".0", "")); sb.append("/"); } // 確定日を分かりやすい表現に変える String dateFmt = ClientContext.getBundle().getString("KARTE_DATE_FORMAT"); sb.append(ModelUtils.getDateAsFormatString( docModel.getDocInfoModel().getFirstConfirmDate(), dateFmt)); // 当時の年齢を表示する String mmlDate = ModelUtils.getDateAsString(docModel.getDocInfoModel().getFirstConfirmDate()); if (getPatientBirthday() != null) { sb.append("[").append(ModelUtils.getAge2(getPatientBirthday(), mmlDate)).append(bundle.getString("text.age")); } // 仮保存 if (docModel.getDocInfoModel().getStatus().equals(IInfoModel.STATUS_TMP)) { sb.append(bundle.getString("text.space_underTemporalSave")); } // 保険 公費が見えるのは気分良くないだろうから、表示しない // SPC区切りの保険者番号・保険者名称・公費のフォーマットである String ins = docModel.getDocInfoModel().getHealthInsuranceDesc().trim(); if (ins != null && !ins.isEmpty()) { String items[] = docModel.getDocInfoModel().getHealthInsuranceDesc().split(" "); List<String> itemList = new ArrayList<>(); for (String item : items) { if (!item.isEmpty()) { itemList.add(item); } } switch (itemList.size()) { case 1: sb.append("/"); sb.append(ins); break; case 2: case 3: sb.append("/"); sb.append(itemList.get(1)); break; } } // KarteViewerで日付の右Dr名を表示する sb.append("/"); sb.append(docModel.getUserModel().getCommonName()); return sb.toString(); } private String parseBundleNum(String str) { java.util.ResourceBundle bundle = ClientContext.getMyBundle(KartePDFMaker.class); int len = str.length(); int pos = str.indexOf("/"); StringBuilder sb = new StringBuilder(); sb.append(bundle.getString("text.numTimes_colon")); sb.append(str.substring(0, pos)); sb.append(bundle.getString("text.space_performDate_colon")); sb.append(str.substring(pos + 1, len)); sb.append(bundle.getString("text.day")); return sb.toString(); } private class KarteTable extends PdfPTable { private int col; // カラム数 private KarteTable(int col) { super(col); this.col = col; } private int getColumnCount() { return col; } } private class PdfCellRenderer { private PdfPCell cell; private Paragraph theParagraph; private List<ModuleModel> modules; private List<SchemaModel> schemas; private KarteTable karteTable; private PdfPCell render(String xml, List<ModuleModel> modules, List<SchemaModel> schemas, KarteTable karteTable) { this.modules = modules; this.schemas = schemas; this.karteTable = karteTable; // SoaPane, Ppaneが収まるセル cell = new PdfPCell(); SAXBuilder docBuilder = new SAXBuilder(); try { StringReader sr = new StringReader(xml); org.jdom.Document doc = docBuilder.build(new BufferedReader(sr)); org.jdom.Element root = doc.getRootElement(); writeChildren(root); } catch (JDOMException e) { e.printStackTrace(System.err); } catch (IOException e) { e.printStackTrace(System.err); } return cell; } private void writeChildren(org.jdom.Element current) { int eType = -1; String eName = current.getName(); if (eName.equals(PARAGRAPH_NAME)) { eType = TT_PARAGRAPH; startParagraph(current.getAttributeValue(ALIGNMENT_NAME)); } else if (eName.equals(CONTENT_NAME) && (current.getChild(TEXT_NAME) != null)) { eType = TT_CONTENT; startContent(current.getAttributeValue(FOREGROUND_NAME), current.getAttributeValue(SIZE_NAME), current.getAttributeValue(BOLD_NAME), current.getAttributeValue(ITALIC_NAME), current.getAttributeValue(UNDERLINE_NAME), current.getChildText(TEXT_NAME)); } else if (eName.equals(COMPONENT_NAME)) { eType = TT_COMPONENT; startComponent(current.getAttributeValue(NAME_NAME), // compoenet=number current.getAttributeValue(COMPONENT_ELEMENT_NAME)); } else if (eName.equals(ICON_NAME)) { eType = TT_ICON; startIcon(current); } else if (eName.equals(PROGRESS_COURSE_NAME)) { eType = TT_PROGRESS_COURSE; startProgressCourse(); } else if (eName.equals(SECTION_NAME)) { eType = TT_SECTION; startSection(); } // 子を探索するのはパラグフとトップ要素のみ if (eType == TT_PARAGRAPH || eType == TT_PROGRESS_COURSE || eType == TT_SECTION) { List children = current.getChildren(); Iterator iterator = children.iterator(); while (iterator.hasNext()) { org.jdom.Element child = (org.jdom.Element) iterator.next(); writeChildren(child); } } switch (eType) { case TT_PARAGRAPH: endParagraph(); break; case TT_CONTENT: endContent(); break; case TT_ICON: endIcon(); break; case TT_COMPONENT: endComponent(); break; case TT_PROGRESS_COURSE: endProgressCourse(); break; case TT_SECTION: endSection(); break; } } private void startParagraph(String alignStr) { theParagraph = createNewParagraph(); cell.addElement(theParagraph); if (alignStr != null) { if (alignStr.equals("0")) { theParagraph.setAlignment(Element.ALIGN_LEFT); } else if (alignStr.equals("1")) { theParagraph.setAlignment(Element.ALIGN_CENTER); } else if (alignStr.equals("2")) { theParagraph.setAlignment(Element.ALIGN_RIGHT); } } } private Paragraph createNewParagraph() { Paragraph p = new Paragraph(); p.setFont(new Font(baseFont, KARTE_FONT_SIZE)); //p.setLeading(KARTE_FONT_SIZE + 2); return p; } private void endParagraph() { } private void startContent( String foreground, String size, String bold, String italic, String underline, String text) { // 特殊文字を戻す for (int i = 0; i < REPLACES.length; i++) { text = text.replaceAll(MATCHES[i], REPLACES[i]); } Font font = theParagraph.getFont(); // foreground 属性を設定する if (foreground != null) { StringTokenizer stk = new StringTokenizer(foreground, ","); if (stk.hasMoreTokens()) { int r = Integer.parseInt(stk.nextToken()); int g = Integer.parseInt(stk.nextToken()); int b = Integer.parseInt(stk.nextToken()); theParagraph.getFont().setColor(new Color(r, g, b)); } } // size 属性を設定する if (size != null) { font.setSize(Float.valueOf(size)); } // bold 属性を設定する if (bold != null) { font.setStyle(Font.BOLD); } // italic 属性を設定する if (italic != null) { font.setStyle(Font.ITALIC); } // underline 属性を設定する if (underline != null) { font.setStyle(Font.UNDERLINE); } // テキストを挿入する if (!text.trim().equals("")) { // スタンプで改行されないために theParagraph.add(new Chunk(text)); } } private void endContent() { } // スタンプとシェーマを配置する private void startComponent(String name, String number) { int index = Integer.valueOf(number); PdfPTable pTable = null; if (name != null && name.equals(STAMP_HOLDER)) { ModuleModel stamp = modules.get(index); pTable = createStampTable(stamp); } else if (name != null && name.equals(SCHEMA_HOLDER)) { SchemaModel schema = schemas.get(index); pTable = createImageTable(schema); } // cellにスタンプを追加する if (pTable == null) { return; } cell.addElement(pTable); // スタンプを挿入した後はParagraphを作り直してcellに追加 Paragraph p = createNewParagraph(); // フォント・アライメントを引き継ぐ p.setFont(theParagraph.getFont()); p.setAlignment(theParagraph.getAlignment()); theParagraph = p; cell.addElement(theParagraph); } private void startSection() { } private void endSection() { } private void endComponent() { } private void startProgressCourse() { } private void endProgressCourse() { } private void startIcon(org.jdom.Element current) { } private void endIcon() { } // SchemaをImage入りのテーブルとして作成する private PdfPTable createImageTable(SchemaModel schema) { try { // Schemaはカラム数1のテーブル PdfPTable table = new PdfPTable(1); table.setSpacingBefore(1); //table.setSpacingAfter(1); table.setHorizontalAlignment(Element.ALIGN_LEFT); // イメージのパーセントを設定 int percentage = Math.min(PERCENTAGE_IMAGE_WIDTH * karteTable.getColumnCount(), 100); table.setWidthPercentage(percentage); // SchemaModelからjpeg imageを取得 Image image = Image.getInstance(schema.getJpegByte()); // セルにimageを設定 PdfPCell pcell = new PdfPCell(image, true); pcell.setBorder(Rectangle.NO_BORDER); // テーブルに追加 table.addCell(pcell); return table; } catch (BadElementException ex) { } catch (MalformedURLException ex) { } catch (IOException ex) { } return null; } // スタンプをテーブルとして作成する private PdfPTable createStampTable(ModuleModel stamp) { try { // スタンプのテーブルを作成、カラム数3 PdfPTable table = new PdfPTable(3); table.setWidthPercentage(100); table.setWidths(new int[]{7, 1, 2}); // てきとー table.setHorizontalAlignment(Element.ALIGN_LEFT); table.setSpacingAfter(5); // スタンプの種類別に処理する String entity = stamp.getModuleInfoBean().getEntity(); java.util.ResourceBundle bundle = ClientContext.getMyBundle(KartePDFMaker.class); if (IInfoModel.ENTITY_LABO_TEST.equals(entity)) { // 検体検査スタンプ BundleDolphin model = (BundleDolphin) stamp.getModel(); // スタンプ名 StringBuilder sb = new StringBuilder(); sb.append(model.getOrderName()); sb.append("("); sb.append(stamp.getModuleInfoBean().getStampName()); sb.append(")"); table.addCell(createStampCell(sb.toString(), 2, true)); // ClassCode table.addCell(createStampCell(model.getClassCode(), 1, true)); // 項目 table.addCell(createStampCell(model.getItemNames(), 3, false)); // bundleNumber String number = model.getBundleNumber(); if (number != null && number.startsWith("*")) { String str = parseBundleNum(number); table.addCell(createStampCell(str, 3, false)); } else if (number != null && !number.trim().isEmpty() && !"1".equals(number)) { table.addCell(createStampCell(bundle.getString("cellText.dot_numTimes"), 1, false)); table.addCell(createStampCell(number, 1, false)); table.addCell(createStampCell(bundle.getString("celText.space_times"), 1, false)); } // メモ String memo = model.getMemo(); if (memo != null && !memo.trim().isEmpty()) { table.addCell(createStampCell(memo, 3, false)); } } else if (IInfoModel.ENTITY_MED_ORDER.equals(entity)) { // 処方スタンプ BundleMed model = (BundleMed) stamp.getModel(); // スタンプ名 StringBuilder sb = new StringBuilder(); sb.append("RP)"); sb.append(stamp.getModuleInfoBean().getStampName()); table.addCell(createStampCell(sb.toString(), 2, true)); // 院内・院外、ClassCode String str = model.getMemo().replace(bundle.getString("claimText.rp"), "") + "/" + model.getClassCode(); table.addCell(createStampCell(str, 2, true)); // 薬剤項目 for (ClaimItem ci : model.getClaimItem()) { // コメントコードでは・とxは表示しない if (!ci.getCode().matches(ClaimConst.REGEXP_COMMENT_MED)) { table.addCell(createStampCell("・" + ci.getName(), 1, false)); table.addCell(createStampCell(" x " + ci.getNumber(), 1, false)); table.addCell(createStampCell(ci.getUnit(), 1, false)); } else { table.addCell(createStampCell(ci.getName(), 1, false)); table.addCell(createStampCell(" ", 1, false)); table.addCell(createStampCell(" ", 1, false)); } } // 用法 table.addCell(createStampCell(model.getAdminDisplayString(), 3, false)); } else { // その他スタンプ BundleDolphin model = (BundleDolphin) stamp.getModel(); // スタンプ名 StringBuilder sb = new StringBuilder(); sb.append(model.getOrderName()); sb.append("("); sb.append(stamp.getModuleInfoBean().getStampName()); sb.append(")"); table.addCell(createStampCell(sb.toString(), 2, true)); // ClassCode table.addCell(createStampCell(model.getClassCode(), 1, true)); // 項目 for (ClaimItem ci : model.getClaimItem()) { if (ci.getNumber() != null) { table.addCell(createStampCell("・" + ci.getName(), 1, false)); table.addCell(createStampCell(" x " + ci.getNumber(), 1, false)); table.addCell(createStampCell(ci.getUnit(), 1, false)); } else { table.addCell(createStampCell("・" + ci.getName(), 1, false)); table.addCell(createStampCell(" ", 1, false)); table.addCell(createStampCell(" ", 1, false)); } } // bundleNumber String number = model.getBundleNumber(); if (number != null && number.startsWith("*")) { String str = parseBundleNum(number); table.addCell(createStampCell(str, 3, false)); } else if (number != null && !number.trim().isEmpty() && !"1".equals(number)) { table.addCell(createStampCell(bundle.getString("cellText.dot_numTimes"), 1, false)); table.addCell(createStampCell(number, 1, false)); table.addCell(createStampCell(bundle.getString("celText.space_times"), 1, false)); } // メモ String memo = model.getMemo(); if (memo != null && !memo.trim().isEmpty()) { table.addCell(createStampCell(memo, 3, false)); } } return table; } catch (DocumentException ex) { } return null; } // スタンプ表示に使うPdfPCellを作成する private PdfPCell createStampCell(String str, int colSpan, boolean setBackground) { if (str == null) { str = ""; } PdfPCell pcell = new PdfPCell(); pcell.setColspan(colSpan); pcell.setPadding(0); pcell.setBorder(Rectangle.NO_BORDER); if (setBackground) { pcell.setBackgroundColor(STAMP_TITLE_BACKGROUND); } pcell.addElement(new Chunk(str, new Font(baseFont, STAMP_FONT_SIZE))); return pcell; } } }