request->post(); $rule = new Validate([ 'p_article_id' => 'require|number' ]); if (!$rule->check($data)) { return jsonError($rule->getError()); } try { // 获取文章信息 $p_info = $this->production_article_obj->where('p_article_id', $data['p_article_id'])->find(); if (!$p_info) { return jsonError('文章实例不存在'); } // 获取作者信息 $authors = $this->production_article_author_obj->where('p_article_id', $data['p_article_id'])->where('state', 0)->select(); // 获取机构信息 $organs = $this->production_article_organ_obj->where('p_article_id', $data['p_article_id'])->where('state', 0)->select(); // 获取正文内容 $main_contents = $this->production_article_main_obj->where('p_article_id', $data['p_article_id'])->where('state', 0)->order('p_main_id')->select(); // 获取参考文献 $references = $this->production_article_refer_obj->where('p_article_id', $data['p_article_id'])->where('state', 0)->order('index')->select(); // 构建LaTeX内容 $latex_content = $this->buildLatexContent($p_info, $authors, $organs, $main_contents, $references); // 创建临时目录 $temp_dir = ROOT_PATH . 'public' . DS . 'temp_latex' . DS; if (!is_dir($temp_dir)) { mkdir($temp_dir, 0755, true); } // 生成唯一的文件名 $filename = 'article_' . $p_info['p_article_id'] . '_' . time(); $tex_file = $temp_dir . $filename . '.tex'; $pdf_file = $temp_dir . $filename . '.pdf'; // 写入LaTeX文件 file_put_contents($tex_file, $latex_content); // 执行LaTeX编译命令生成PDF $command = sprintf( 'cd %s && pdflatex -interaction=nonstopmode -output-directory=%s %s', escapeshellarg($temp_dir), escapeshellarg($temp_dir), escapeshellarg($tex_file) ); // 执行命令 $output = []; $return_var = 0; exec($command, $output, $return_var); // 检查PDF是否生成成功 if (!file_exists($pdf_file) || filesize($pdf_file) == 0) { return jsonError('PDF生成失败,请检查LaTeX内容或系统环境'); } // 移动PDF到正式目录 $pdf_dir = ROOT_PATH . 'public' . DS . 'latex_pdfs' . DS; if (!is_dir($pdf_dir)) { mkdir($pdf_dir, 0755, true); } $final_pdf_path = $pdf_dir . $filename . '.pdf'; rename($pdf_file, $final_pdf_path); // 清理临时文件 if (file_exists($tex_file)) { unlink($tex_file); } // 清理辅助文件(.aux, .log等) $aux_file = $temp_dir . $filename . '.aux'; $log_file = $temp_dir . $filename . '.log'; if (file_exists($aux_file)) unlink($aux_file); if (file_exists($log_file)) unlink($log_file); // 返回PDF路径 $relative_path = 'latex_pdfs/' . $filename . '.pdf'; return jsonSuccess([ 'pdf_url' => $relative_path, 'message' => 'PDF生成成功' ]); } catch (\Exception $e) { return jsonError('生成过程中发生错误: ' . $e->getMessage()); } } /** * 构建LaTeX文档内容 * @param $p_info * @param $authors * @param $organs * @param $main_contents * @param $references * @return string */ private function buildLatexContent($p_info, $authors, $organs, $main_contents, $references) { // 构建机构映射 $organ_map = []; foreach ($organs as $index => $organ) { $organ_map[$organ['p_article_organ_id']] = $index + 1; } // LaTeX文档开始 $latex = '\documentclass[twocolumn]{article}' . "\n"; $latex .= '\usepackage[utf8]{inputenc}' . "\n"; $latex .= '\usepackage[T1]{fontenc}' . "\n"; $latex .= '\usepackage{geometry}' . "\n"; $latex .= '\usepackage{graphicx}' . "\n"; $latex .= '\usepackage{amsmath}' . "\n"; $latex .= '\usepackage{amsfonts}' . "\n"; $latex .= '\usepackage{amssymb}' . "\n"; $latex .= '\usepackage{setspace}' . "\n"; $latex .= '\usepackage{titlesec}' . "\n"; $latex .= '\usepackage{hyperref}' . "\n"; $latex .= '\usepackage{caption}' . "\n"; $latex .= '\usepackage{multicol}' . "\n"; $latex .= '\usepackage{abstract}' . "\n"; $latex .= '\geometry{left=1.5cm,right=1.5cm,top=2.5cm,bottom=2.5cm}' . "\n"; $latex .= '\setstretch{1.5}' . "\n"; $latex .= '\title{' . $this->escapeLatexText($p_info['title']) . '}' . "\n"; // 处理作者和机构 if (!empty($authors)) { $author_text = ''; $affiliations = []; foreach ($authors as $author) { if ($author_text !== '') { $author_text .= ', '; } $author_text .= $this->escapeLatexText($author['first_name'] . ' ' . $author['last_name']); // 获取作者的机构 $author_organs = $this->production_article_author_to_organ_obj ->where('p_article_author_id', $author['p_article_author_id']) ->where('state', 0) ->select(); foreach ($author_organs as $ao) { if (isset($organ_map[$ao['p_article_organ_id']])) { $author_text .= '$^{' . $organ_map[$ao['p_article_organ_id']] . '}$'; } } if ($author['is_report'] == 1) { $author_text .= '$^{*}$'; } } $latex .= '\author{' . $author_text . "\n"; // 添加机构信息 foreach ($organs as $index => $organ) { $latex .= '\\\\$' . ($index + 1) . '$ ' . $this->escapeLatexText($organ['organ_name']) . "\n"; } if (!empty($authors)) { // 查找通讯作者 $corresponding_authors = array_filter($authors, function($author) { return $author['is_report'] == 1; }); if (!empty($corresponding_authors)) { $corr_author = reset($corresponding_authors); $latex .= '\\\\$*$ Corresponding author: ' . $this->escapeLatexText($corr_author['email']) . "\n"; } } $latex .= '}' . "\n"; } $latex .= '\date{}' . "\n"; $latex .= '\begin{document}' . "\n"; $latex .= '\maketitle' . "\n"; // 摘要 if (!empty($p_info['abstract'])) { $latex .= '\begin{abstract}' . "\n"; $latex .= $this->escapeLatexText($p_info['abstract']) . "\n"; $latex .= '\end{abstract}' . "\n"; } // 关键词 if (!empty($p_info['keywords'])) { $latex .= '\textbf{Keywords:} ' . $this->escapeLatexText($p_info['keywords']) . "\n"; $latex .= '\sectionbreak' . "\n"; } // 正文内容 $in_section = false; $section_count = 0; foreach ($main_contents as $content) { $text = trim($content['content']); // 跳过空内容 if (empty($text)) { continue; } // 检查是否是标题 $clean_text = strip_tags($text); $lower_text = strtolower($clean_text); // 判断是否为章节标题 if (stripos($lower_text, 'introduction') === 0 || stripos($lower_text, 'methods') === 0 || stripos($lower_text, 'results') === 0 || stripos($lower_text, 'discussion') === 0 || stripos($lower_text, 'conclusion') === 0 || stripos($lower_text, 'references') === 0) { // 结束前一节 if ($in_section) { $latex .= "\n" . '\sectionbreak' . "\n"; } // 开始新节 $section_count++; $section_title = $this->escapeLatexText($clean_text); $latex .= '\section{' . $section_title . '}' . "\n"; $in_section = true; } // 检查是否是图片或表格占位符 else if (preg_match('/]*imageId=[\'"]([^\'"]+)[\'"][^>]*>/i', $text, $img_matches)) { $image_id = $img_matches[1]; // 这里可以添加图片处理逻辑 $latex .= '% 图片插入位置 (ID: ' . $image_id . ')' . "\n"; $latex .= '\begin{figure}[htbp]' . "\n"; $latex .= '\centering' . "\n"; $latex .= '\includegraphics[width=0.8\textwidth]{image_' . $image_id . '}' . "\n"; $latex .= '\caption{图片说明}' . "\n"; $latex .= '\end{figure}' . "\n"; } else if (preg_match('/]*tableId=[\'"]([^\'"]+)[\'"][^>]*>/i', $text, $table_matches)) { $table_id = $table_matches[1]; // 这里可以添加表格处理逻辑 $latex .= '% 表格插入位置 (ID: ' . $table_id . ')' . "\n"; $latex .= '\begin{table}[htbp]' . "\n"; $latex .= '\centering' . "\n"; $latex .= '\caption{表格说明}' . "\n"; $latex .= '\begin{tabular}{|c|c|c|}' . "\n"; $latex .= '\hline' . "\n"; $latex .= '列1 & 列2 & 列3 \\\\' . "\n"; $latex .= '\hline' . "\n"; $latex .= '% 表格数据' . "\n"; $latex .= '\hline' . "\n"; $latex .= '\end{tabular}' . "\n"; $latex .= '\end{table}' . "\n"; } // 普通段落文本 else { // 移除HTML标签并转义特殊字符 $plain_text = $this->escapeLatexText(strip_tags($text)); $latex .= $plain_text . "\n\n"; } } // 参考文献 if (!empty($references)) { $latex .= '\section*{References}' . "\n"; $latex .= '\begin{thebibliography}{99}' . "\n"; foreach ($references as $index => $ref) { $ref_text = isset($ref['refer_frag']) ? $ref['refer_frag'] : $ref['refer_content']; $latex .= '\bibitem{} ' . $this->escapeLatexText($ref_text) . "\n"; } $latex .= '\end{thebibliography}' . "\n"; } $latex .= '\end{document}' . "\n"; return $latex; } /** * 转义LaTeX特殊字符 * @param string $text * @return string */ private function escapeLatexText($text) { // 移除HTML标签 $text = strip_tags($text); // 转义LaTeX特殊字符 $escape_chars = [ '\\' => '\\textbackslash{}', '&' => '\\&', '%' => '\\%', '$' => '\\$', '#' => '\\#', '_' => '\\_', '{' => '\\{', '}' => '\\}', '~' => '\\textasciitilde{}', '^' => '\\textasciicircum{}', '"' => '\\"{}', '\'' => '\\\'{}', '<' => '\\textless{}', '>' => '\\textgreater{}', ]; foreach ($escape_chars as $char => $replacement) { $text = str_replace($char, $replacement, $text); } return $text; } }