dompdf导出pdf中文乱码显示问号?、换行问题、设置图片大小

环境:PHP 8.0 框架:ThinkPHP 8 软件包:phpoffice/phpword 、dompdf/dompdf

看了很多教程(包括GitHub的issue、stackoverflow)都没有解决、最终找到解决问题的根本!

背景:用Word模板做转PDF的时候,中文乱码,做法是先用模板替换好变量以后,转成HTML,再转成PDF。

解决方案:

1、先将load_font.php放在项目根目录,跟vendor同级

A、GITHUB下载地址: load_font.php

B、新建文件load_font.php复制下面代码

php 复制代码
<?php
// 1. [Required] Point to the composer or dompdf autoloader
require_once "vendor/autoload.php";

// 2. [Optional] Set the path to your font directory
//    By default dompdf loads fonts to dompdf/lib/fonts
//    If you have modified your font directory set this
//    variable appropriately.
//$fontDir = "lib/fonts";


// *** DO NOT MODIFY BELOW THIS POINT ***

use Dompdf\Dompdf;
use Dompdf\CanvasFactory;
use Dompdf\Exception;
use Dompdf\FontMetrics;
use Dompdf\Options;

use FontLib\Font;

/**
 * Display command line usage
 */
function usage() {
  echo <<<EOD

Usage: {$_SERVER["argv"][0]} font_family [n_file [b_file] [i_file] [bi_file]]

font_family:      the name of the font, e.g. Verdana, 'Times New Roman',
                  monospace, sans-serif. If it equals to "system_fonts", 
                  all the system fonts will be installed.

n_file:           the .ttf or .otf file for the normal, non-bold, non-italic
                  face of the font.

{b|i|bi}_file:    the files for each of the respective (bold, italic,
                  bold-italic) faces.

If the optional b|i|bi files are not specified, load_font.php will search
the directory containing normal font file (n_file) for additional files that
it thinks might be the correct ones (e.g. that end in _Bold or b or B).  If
it finds the files they will also be processed.  All files will be
automatically copied to the DOMPDF font directory, and afm files will be
generated using php-font-lib (https://github.com/PhenX/php-font-lib).

Examples:

./load_font.php silkscreen /usr/share/fonts/truetype/slkscr.ttf
./load_font.php 'Times New Roman' /mnt/c_drive/WINDOWS/Fonts/times.ttf

EOD;
exit;
}

if ( $_SERVER["argc"] < 3 && @$_SERVER["argv"][1] != "system_fonts" ) {
  usage();
}

$dompdf = new Dompdf();
if (isset($fontDir) && realpath($fontDir) !== false) {
  $dompdf->getOptions()->set('fontDir', $fontDir);
}

/**
 * Installs a new font family
 * This function maps a font-family name to a font.  It tries to locate the
 * bold, italic, and bold italic versions of the font as well.  Once the
 * files are located, ttf versions of the font are copied to the fonts
 * directory.  Changes to the font lookup table are saved to the cache.
 *
 * @param Dompdf $dompdf      dompdf main object 
 * @param string $fontname    the font-family name
 * @param string $normal      the filename of the normal face font subtype
 * @param string $bold        the filename of the bold face font subtype
 * @param string $italic      the filename of the italic face font subtype
 * @param string $bold_italic the filename of the bold italic face font subtype
 *
 * @throws Exception
 */
function install_font_family($dompdf, $fontname, $normal, $bold = null, $italic = null, $bold_italic = null) {
  $fontMetrics = $dompdf->getFontMetrics();
  
  // Check if the base filename is readable
  if ( !is_readable($normal) )
    throw new Exception("Unable to read '$normal'.");

  $dir = dirname($normal);
  $basename = basename($normal);
  $last_dot = strrpos($basename, '.');
  if ($last_dot !== false) {
    $file = substr($basename, 0, $last_dot);
    $ext = strtolower(substr($basename, $last_dot));
  } else {
    $file = $basename;
    $ext = '';
  }

  if ( !in_array($ext, array(".ttf", ".otf")) ) {
    throw new Exception("Unable to process fonts of type '$ext'.");
  }

  // Try $file_Bold.$ext etc.
  $path = "$dir/$file";
  
  $patterns = array(
    "bold"        => array("_Bold", "b", "B", "bd", "BD"),
    "italic"      => array("_Italic", "i", "I"),
    "bold_italic" => array("_Bold_Italic", "bi", "BI", "ib", "IB"),
  );
  
  foreach ($patterns as $type => $_patterns) {
    if ( !isset($$type) || !is_readable($$type) ) {
      foreach($_patterns as $_pattern) {
        if ( is_readable("$path$_pattern$ext") ) {
          $$type = "$path$_pattern$ext";
          break;
        }
      }
      
      if ( is_null($$type) )
        echo ("Unable to find $type face file.\n");
    }
  }

  $fonts = compact("normal", "bold", "italic", "bold_italic");
  $entry = array();

  // Copy the files to the font directory.
  foreach ($fonts as $var => $src) {
    if ( is_null($src) ) {
      $entry[$var] = $dompdf->getOptions()->get('fontDir') . '/' . mb_substr(basename($normal), 0, -4);
      continue;
    }

    // Verify that the fonts exist and are readable
    if ( !is_readable($src) )
      throw new Exception("Requested font '$src' is not readable");

    $dest = $dompdf->getOptions()->get('fontDir') . '/' . basename($src);

    if ( !is_writeable(dirname($dest)) )
      throw new Exception("Unable to write to destination '$dest'.");

    echo "Copying $src to $dest...\n";

    if ( !copy($src, $dest) )
      throw new Exception("Unable to copy '$src' to '$dest'");
    
    $entry_name = mb_substr($dest, 0, -4);
    
    echo "Generating Adobe Font Metrics for $entry_name...\n";
    
    $font_obj = Font::load($dest);
    $font_obj->saveAdobeFontMetrics("$entry_name.ufm");
    $font_obj->close();

    $entry[$var] = $entry_name;
  }

  // Store the fonts in the lookup table
  $fontMetrics->setFontFamily($fontname, $entry);

  // Save the changes
  $fontMetrics->saveFontFamilies();
}

// If installing system fonts (may take a long time)
if ( $_SERVER["argv"][1] === "system_fonts" ) {
  $fontMetrics = $dompdf->getFontMetrics();
  $files = glob("/usr/share/fonts/truetype/*.ttf") +
    glob("/usr/share/fonts/truetype/*/*.ttf") +
    glob("/usr/share/fonts/truetype/*/*/*.ttf") +
    glob("C:\\Windows\\fonts\\*.ttf") +
    glob("C:\\WinNT\\fonts\\*.ttf") +
    glob("/mnt/c_drive/WINDOWS/Fonts/");
  $fonts = array();
  foreach ($files as $file) {
      $font = Font::load($file);
      $records = $font->getData("name", "records");
      $type = $fontMetrics->getType($records[2]);
      $fonts[mb_strtolower($records[1])][$type] = $file;
      $font->close();
  }
  
  foreach ( $fonts as $family => $files ) {
    echo " >> Installing '$family'... \n";
    
    if ( !isset($files["normal"]) ) {
      echo "No 'normal' style font file\n";
    }
    else {
      install_font_family($dompdf, $family, @$files["normal"], @$files["bold"], @$files["italic"], @$files["bold_italic"]);
      echo "Done !\n";
    }
    
    echo "\n";
  }
}
else {
  call_user_func_array("install_font_family", array_merge( array($dompdf), array_slice($_SERVER["argv"], 1) ));
}

2、下载配置字体

下载地址:simsun

下载之后将ttf字体文件放到项目根目录,跟load_font、vendor同级,这里我改名改成了SimSun.ttf

执行PHP命令:

php 复制代码
php load_font.php "SimSun" SimSun.ttf

显示如下:

bash 复制代码
php load_font.php "SimSun" SimSun.ttf
Unable to find bold face file.
Unable to find italic face file.
Unable to find bold_italic face file.
Copying SimSun.ttf to D:\phpstudy_pro\WWW\newcrm.com\vendor\dompdf\dompdf/lib/fonts/SimSun.ttf...
Generating Adobe Font Metrics for D:\phpstudy_pro\WWW\newcrm.com\vendor\dompdf\dompdf/lib/fonts/SimSun...

如果php命令有问题,检查一下是不是没有配置环境变量,没有配置的话另行寻找配置教程

3、PHP代码如下:

php 复制代码
    public function test()
    {
        $path = '/storage/contract/' . date('Ymd');
        $directoryPath = public_path() . $path;
        if (!file_exists($directoryPath)) {
            mkdir($directoryPath, 0755, true);
        }

        $options = new Options();
        $options->set('isRemoteEnabled', true);
        // 重点设置字体
        $options->setDefaultFont('SimSun');
        $dompdf = new Dompdf($options);

        $htmlFile = $directoryPath . '/index.html';

        $htmlContent = file_get_contents($htmlFile);

        $dompdf->loadHtml($htmlContent,'UTF-8');
        $dompdf->setPaper('A4');
        $dompdf->render();

        $pdfName = 'index.pdf';
        $pathToSavePdf = $directoryPath . '/' . $pdfName;
        $output = $dompdf->output();
        file_put_contents($pathToSavePdf, $output);
    }
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
</head>
<body>
<div>
    世界和平
</div>
</body>
</html>

生成PDF后

下面配一个WORD模板(动态变量)->转HTML->生成PDF文件

php 复制代码
    public function generateContract($param): array
    {
        $contract = $this->contractModel
            ->with(['customer','contacts'])
            ->where('id', $param['id'])
            ->find();

        if (!$contract) {
            throw new BusinessException(Code::NOT_FOUND, '合同订单不存在');
        }

        $contract = $contract->toArray();

        $file = public_path() . '/static/template/contract/2024.docx';

        $templateProcessor = new TemplateProcessor($file);

        $templateProcessor->setValue('customer', $contract['customer_name']);
        $templateProcessor->setValue('address', $contract['customer_city'] . $contract['customer_address']);

        $path = '/storage/contract/' . date('Ymd');
        $directoryPath = public_path() . $path;
        if (!file_exists($directoryPath)) {
            mkdir($directoryPath, 0755, true);
        }

        $name = $contract['code'] . mt_rand(1000, 9999);
        $wordName = $name . '.docx';
        $pathToSave = $directoryPath . '/' . $wordName;
        $templateProcessor->saveAs($pathToSave);

        // 转换 Word 文件为 HTML
        $phpWord = IOFactory::load($pathToSave);
        $htmlWriter = IOFactory::createWriter($phpWord, 'HTML');
        $htmlFile = $directoryPath . '/' . $name . '.html';
        $htmlWriter->save($htmlFile);

        // 使用 Dompdf 将 HTML 转换为 PDF
        $options = new Options();
        $options->set('isRemoteEnabled', true);
        $options->setDefaultFont('SimSun');
        $dompdf = new Dompdf($options);

        $htmlContent = file_get_contents($htmlFile);

        $dompdf->loadHtml($htmlContent,'UTF-8');
        $dompdf->setPaper('A4');
        $dompdf->render();

        $pdfName = $name . '.pdf';
        $pathToSavePdf = $directoryPath . '/' . $pdfName;
        $output = $dompdf->output();
        file_put_contents($pathToSavePdf, $output);

        // 删除临时 HTML 文件
        unlink($htmlFile);

        return [
            'url' => $path . '/' . $pdfName
        ];
    }

注:doc文件不兼容,用docx模板文件

换行问题可以修改:vendor\phpoffice\phpword\src\PhpWord\Writer\HTML\Part\Head.php

复制代码
writeStyles方法
php 复制代码
        $astarray = [
            'font-family' => $this->getFontFamily(Settings::getDefaultFontName(), $this->getParentWriter()->getDefaultGenericFont()),
            'font-size' => Settings::getDefaultFontSize() . 'pt',
            'word-wrap' => 'break-word',
            'overflow-wrap' => 'break-word'
        ];

设置图片大小(Dpi默认是96 值越大图片越小):

php 复制代码
            $options = new Options();
            $options->set('isRemoteEnabled', true);
            $options->setDefaultFont('SimSun');
            $options->setDpi(168);
            $dompdf = new Dompdf($options);
相关推荐
一只花里胡哨的程序猿12 小时前
odoo打印pdf速度慢问题
pdf·odoo
灵海之森15 小时前
Python将md转html,转pdf
pdf
阿幸软件杂货间18 小时前
最新PDF版本!Acrobat Pro DC 2025,解压即用版
pdf·adobe acrobat·acrobat
星空的资源小屋20 小时前
网易UU远程,免费电脑远程控制软件
人工智能·python·pdf·电脑
会飞的小菠菜1 天前
如何一次性将多个PPT幻灯片批量转换成PDF文档
pdf·powerpoint·ppt·批量·格式转换
somethingGoWay1 天前
wpf .netcore 导出pdf文件
pdf·wpf·.netcore
小白电脑技术1 天前
PDF教程|如何把想要的网页保存下来?
pdf·电脑
我没想到原来他们都是一堆坏人2 天前
通过Gen AI SDK调用gemini 2.5 pro,单独上传pdf文件 | ai agent 开发笔记 2025.9.2 Day 2
ai·google·pdf·sdk·gemini
AI视觉网奇2 天前
麒麟系统 doc转pdf
linux·运维·pdf
CodeCraft Studio2 天前
国产化PDF处理控件Spire.PDF教程:如何在 Java 中通过模板生成 PDF
java·python·pdf·spire.pdf·java创建pdf·从html创建pdf