Files
tougao/application/api/controller/Latex.php
2025-11-18 16:26:10 +08:00

341 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\api\controller;
class Latex extends Base
{
public function __construct(\think\Request $request = null)
{
parent::__construct($request);
}
public function generateLatexPdf()
{
$data = $this->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('/<img[^>]*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('/<table[^>]*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;
}
}