Angular 7 AmCharts PDF 字体

4

我正在使用Angular 7和AmCharts,导出PDF(使用pdfmake库),但我无法更改生成的PDF的默认字体。 我已经使用需要的字体创建了vfs_fonts.js文件,并在angular.json中的scripts部分包含它。 在生成报告的组件中,我执行以下操作:

let pdfMake = res[0];

pdfMake.fonts = {
    "Open Sans": {
        "normal": 'OpenSans-Regular.ttf',
        "bold": 'OpenSans-Bold.ttf',
        "italics": 'OpenSans-Italic.ttf',
        "bolditalics": 'OpenSans-BoldItalic.ttf'
    }
}

let doc = {
    header: this.drawHeader(),
    footer: this.drawFooter(),
    pageSize: "A4",
    pageOrientation: "portrait",
    pageMargins: [40, 80, 40, 60],
    content: [],
    defaultStyle: {
        font: "Open Sans"
    }
};

但是我遇到了以下错误:
ERROR Error: Uncaught (in promise): Error: Font 'Open Sans' in style 'normal' is not defined in the font section of the document definition.
Error: Font 'Open Sans' in style 'normal' is not defined in the font section of the document definition

我该如何解决?谢谢


请尝试访问 https://github.com/bpampuch/pdfmake/issues/864#issuecomment-298341323。 - Ajit Panigrahi
我这里有一个直觉。你能否尝试从"Open Sans"、"normal"、"bold"、"italics"和"bolditalics"中删除双引号?如果出现错误,也请尝试删除Open和Sans之间的空格,将其变为OpenSans。顺便说一下,这个问题已经被报告了,但没有解决方案:https://github.com/bpampuch/pdfmake/issues/1169#issue-270877961 - ruth
删除引号后仍然存在相同的问题 - Gianluca Paris
1
请放置 vfs_fonts.js 文件内容。 - Amirhossein Mehrvarzi
4个回答

2
我曾经面临同样的问题,需要更改pdfmake库中的字体。我通过以下步骤解决了这个问题。
首先,下载所需字体的.ttf文件。
第一步: 进入以下路径 -> node_modules -> pdfmake 第二步: 在pdfmake目录下创建名为“examples”的文件夹。进入“examples”文件夹,在其中创建一个名为“fonts”的文件夹。
第三步: 现在将下载的.ttf文件粘贴到字体文件夹中。(ttf文件的路径:node_modules/pdfmake/examples/fonts)
一旦你把文件粘贴到那个文件夹里,继续执行第四步。
第四步: 按照终端中提到的路径进入字体文件夹。参考下面的终端路径图像。
第五步: 完成第四步后,运行gulb buildFonts。输出结果如下图所示。
一旦成功构建字体,就进入你生成pdf的组件的.ts文件。
在.ts文件内部。
import pdfMake from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';
pdfMake.vfs = pdfFonts.pdfMake.vfs;


async generatePdf(value){
        pdfMake.fonts = {
            Roboto: {
                normal: 'Roboto-Regular.ttf',
                bold: 'Roboto-Medium.ttf',
                italics: 'Roboto-Italic.ttf',
                bolditalics: 'Roboto-MediumItalic.ttf'
            }
        };
        const documentDefinition = await this.getDocumentDefinition();
        pdfMake.createPdf(documentDefinition).open();
    }
    getDocumentDefinition(){
        return {
            content: (...),
              defaultStyle: {
                    font: 'yourFontName' //in this case it's roboto.
            }
        }               
    }

这解决了我的问题。如果您遇到任何困难,请告诉我。
要了解更多信息,请查看官方文档

再次强调,我不是使用独立的pdfmake库,而是使用AMCharts库内置的pdfmake库,因此我无法这样做。 - Gianluca Paris
但毕竟你关注的是pdfmake库字体,为什么不使用它呢? - Pushprajsinh Chudasama

0

这可能是由于Open Sans中的空格。尝试将其更改为OpenSans


0

关于如何在客户端使用自定义字体的Pdfmake文档 在这里


vfs_fonts.js 文件格式类似于:

this.pdfMake = this.pdfMake || {}; this.pdfMake.vfs = {
  "Roboto-Italic.ttf": "AAEAAAASAQAABAAgR0RFRtRX1"
}

因此,您应该像下面这样定义它:

window.pdfMake.vfs["OpenSans-Regular.ttf"] = "BASE 64 HERE";
window.pdfMake.vfs["OpenSans-Bold.ttf"] = "BASE 64 HERE";
window.pdfMake.vfs["OpenSans-Italic.ttf"] = "BASE 64 HERE";
window.pdfMake.vfs["OpenSans-BoldItalic.ttf"] = "BASE 64 HERE";

之后,您仍需要分配pdfMake.fonts:

Open sans变体: https://fonts.google.com/specimen/Open+Sans?selection.family=Open+Sans:400,400i,600i,700

pdfMake.fonts = {
    // Make sure you define all 4 components - normal, bold, italics, bolditalics - (even if they all point to the same font file)
    "Open Sans": {  <======================= TRY WITH "OpenSans" too
        "normal": 'OpenSans-Regular.ttf',
        "bold": 'OpenSans-Bold.ttf',
        "italics": 'OpenSans-Italic.ttf',
        "bolditalics": 'OpenSans-BoldItalic.ttf'
    }
};

另一个可能的解决方案:(不确定是否有效)

let pdfMake = res[0];
let pdfFonts = require('./vfs_fonts');

pdfMake.addVirtualFileSystem(pdfFonts);

0

您可以使用与amcharts相同的pdfmake库,但需要按照上面的答案所述使用gulp生成字体。由于存在承诺问题,因此我替换了语法以进行快速解决,因此您必须转到位于项目根目录的node_modules中的pdfmake文件夹,并在其中运行npm install,之后您需要在示例目录下创建fonts文件夹-> examples/fonts并将您的ttf字体复制到那里,然后运行gulp文件,它将为您提供vfs_fonts.js文件。

您需要编辑此文件并更改

this.pdfMake = this.pdfMake || {}; pdfMake.vfs = {}

export const pdfMake={ vfs : { }}; 如此屏幕截图所示: enter image description here

并适当结束键,如下所示:

enter image description here 而不是这个};

之后,您可以在此处检查我使用Montserrat字体制作的完整工作代码,该代码是通过使用下面的Amchart自定义内容演示而制作的:

import { Component, NgZone } from "@angular/core";
import * as am4core from "@amcharts/amcharts4/core";
import * as am4charts from "@amcharts/amcharts4/charts";
import am4themes_animated from "@amcharts/amcharts4/themes/animated";
import am4themes_dataviz from "@amcharts/amcharts4/themes/dataviz";

import * as pdfFontsVfs from "../../node_modules/pdfmake/build/vfs_fonts";
am4core.useTheme(am4themes_animated);

// Themes
am4core.useTheme(am4themes_animated);
am4core.useTheme(am4themes_dataviz);

@Component({
  selector: "app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})

export class AppComponent {
  private chart: am4charts.XYChart;
  private chart2: am4charts.XYChart;
  private chart3: am4charts.XYChart;
  private chart4: am4charts.PieChart;
  constructor(private zone: NgZone) {}

  ngAfterViewInit() {
    this.zone.runOutsideAngular(() => {

    /**
     * Chart 1
     */

    // Create chart instance
    var chart = am4core.create("chartdiv", am4charts.XYChart);

    // Add data
    chart.data = [{
      "date": new Date(2018, 0, 1),
      "value": 450,
      "value2": 362,
      "value3": 699
    }, {
      "date": new Date(2018, 0, 2),
      "value": 269,
      "value2": 450,
      "value3": 841
    }, {
      "date": new Date(2018, 0, 3),
      "value": 700,
      "value2": 358,
      "value3": 699
    }, {
      "date": new Date(2018, 0, 4),
      "value": 490,
      "value2": 367,
      "value3": 500
    }, {
      "date": new Date(2018, 0, 5),
      "value": 500,
      "value2": 485,
      "value3": 369
    }, {
      "date": new Date(2018, 0, 6),
      "value": 550,
      "value2": 354,
      "value3": 250
    }, {
      "date": new Date(2018, 0, 7),
      "value": 420,
      "value2": 350,
      "value3": 600
    }];

    // Create axes
    var categoryAxis = chart.xAxes.push(new am4charts.DateAxis());
    categoryAxis.renderer.grid.template.location = 0;
    categoryAxis.renderer.labels.template.disabled = true;
    categoryAxis.renderer.minGridDistance = 30;

    var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxis.renderer.labels.template.disabled = true;



    this.createSeries("value", "Series #1",chart);
    this.createSeries("value2", "Series #2",chart);
    this.createSeries("value3", "Series #3",chart);
    this.chart = chart;

    /**
     * Chart 2
     */

    // Create chart instance
    var chart2 = am4core.create("chartdiv2", am4charts.XYChart);
    chart2.paddingBottom = 25;

    // Add data
    chart2.data = [{
      "country": "USA",
      "visits": 3025
    }, {
      "country": "China",
      "visits": 1882
    }, {
      "country": "Japan",
      "visits": 1809
    }, {
      "country": "Germany",
      "visits": 1322
    }, {
      "country": "UK",
      "visits": 1122
    }, {
      "country": "France",
      "visits": 1114
    }, {
      "country": "India",
      "visits": 984
    }];

    // Create axes
    var categoryAxis2 = chart2.xAxes.push(new am4charts.CategoryAxis());
    categoryAxis2.dataFields.category = "country";
    categoryAxis2.renderer.grid.template.location = 0;
    categoryAxis2.renderer.minGridDistance = 30;
    categoryAxis2.renderer.labels.template.disabled = true;

    var valueAxis = chart2.yAxes.push(new am4charts.ValueAxis());
    valueAxis.renderer.labels.template.disabled = true;

    // Create series
    var series = chart2.series.push(new am4charts.ColumnSeries());
    series.sequencedInterpolation = true;
    series.dataFields.valueY = "visits";
    series.dataFields.categoryX = "country";
    series.columns.template.strokeWidth = 0;

    series.columns.template.column.cornerRadiusTopLeft = 10;
    series.columns.template.column.cornerRadiusTopRight = 10;

    // on hover, make corner radiuses bigger
    var hoverState = series.columns.template.column.states.create("hover");
    hoverState.properties.cornerRadiusTopLeft = 0;
    hoverState.properties.cornerRadiusTopRight = 0;
    hoverState.properties.fillOpacity = 1;

    series.columns.template.adapter.add("fill", (fill, target)=>{
      return chart.colors.getIndex(target.dataItem.index);
    });
    this.chart2 = chart2;
    /**
     * Chart 3
     */

    // Create chart instance
    var chart3 = am4core.create("chartdiv3", am4charts.XYChart);
    chart3.paddingBottom = 25;

    // Add percent sign to all numbers
    chart3.numberFormatter.numberFormat = "#.3'%'";

    // Add data
    chart3.data = [{
        "country": "USA",
        "year2004": 3.5,
        "year2005": 4.2
    }, {
        "country": "UK",
        "year2004": 1.7,
        "year2005": 3.1
    }, {
        "country": "Canada",
        "year2004": 2.8,
        "year2005": 2.9
    }, {
        "country": "Japan",
        "year2004": 2.6,
        "year2005": 2.3
    }, {
        "country": "France",
        "year2004": 1.4,
        "year2005": 2.1
    }, {
        "country": "Brazil",
        "year2004": 2.6,
        "year2005": 4.9
    }];

    // Create axes
    var categoryAxis3 = chart3.xAxes.push(new am4charts.CategoryAxis());
    categoryAxis3.dataFields.category = "country";
    categoryAxis3.renderer.grid.template.location = 0;
    categoryAxis3.renderer.minGridDistance = 30;
    categoryAxis3.renderer.labels.template.disabled = true;

    var valueAxis3 = chart3.yAxes.push(new am4charts.ValueAxis());
    valueAxis3.renderer.labels.template.disabled = true;

    // Create series
    var series3 = chart3.series.push(new am4charts.ColumnSeries());
    series3.dataFields.valueY = "year2004";
    series3.dataFields.categoryX = "country";
    series3.clustered = false;

    series3.columns.template.column.cornerRadiusTopLeft = 10;
    series3.columns.template.column.cornerRadiusTopRight = 10;

    var series2 = chart3.series.push(new am4charts.ColumnSeries());
    series2.dataFields.valueY = "year2005";
    series2.dataFields.categoryX = "country";
    series2.clustered = false;
    series2.columns.template.width = am4core.percent(50);

    series2.columns.template.column.cornerRadiusTopLeft = 6;
    series2.columns.template.column.cornerRadiusTopRight = 6;
    this.chart3 = chart3;
    /**
     * Chart 4
     */

    // Create chart
    var chart4 = am4core.create("chartdiv4", am4charts.PieChart);
    chart4.padding(0, 0, 0, 0);

    chart4.data = [
      {
        country: "Lithuania",
        value: 260
      },
      {
        country: "Czech Republic",
        value: 230
      },
      {
        country: "Ireland",
        value: 200
      },
      {
        country: "Germany",
        value: 165
      },
      {
        country: "Australia",
        value: 139
      },
      {
        country: "Austria",
        value: 128
      }
    ];

    var series4 = chart4.series.push(new am4charts.PieSeries());
    series4.dataFields.value = "value";
    series4.dataFields.radiusValue = "value";
    series4.dataFields.category = "country";
    series4.slices.template.cornerRadius = 6;
    series4.colors.step = 3;
    series4.radius = am4core.percent(100);
    series4.labels.template.disabled = true;
    series4.ticks.template.disabled = true;
    this.chart4 = chart4;
    });
  }


  /**
   * Function that exports PDF
   */

  async savePDF() {

      var res = [];
      var fonts =  {
        "Montserrat": {
         normal: "Montserrat-Regular.ttf",
         bold: "Montserrat-Bold.ttf",
         italics: "Montserrat-Italic.ttf",
         bolditalics: "Montserrat-BoldItalic.ttf"
       }
     };
      var PdfMake = await this.chart.exporting.pdfmake;





      PdfMake.vfs = pdfFontsVfs.pdfMake.vfs;
      PdfMake.addVirtualFileSystem = pdfFontsVfs;
      PdfMake.fonts = fonts;

      res[1] = await this.chart.exporting.getImage("png");
      res[2] = await this.chart2.exporting.getImage("png");
      res[3] = await this.chart3.exporting.getImage("png");
      res[4] = await this.chart4.exporting.getImage("png");

      // pdfmake is ready

      // Create document template
      var doc = {
        pageSize: "A4",
        pageOrientation: "portrait",
        pageMargins: [30, 30, 30, 30],
        content: [],
        defaultStyle:{
          font: "Montserrat"
        }
      };

      doc.content.push({
        text: "In accumsan velit in orci tempor",
        fontSize: 20,
        bold: true,
        margin: [0, 20, 0, 15]
      });

      doc.content.push({
        text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sem quam, sodales ac volutpat sed, vestibulum id quam. Sed quis arcu non elit fringilla mattis. Sed auctor mi sed efficitur vehicula. Sed bibendum odio urna, quis lobortis dui luctus ac. Duis eu lacus sodales arcu tincidunt ultrices viverra a risus. Vivamus justo massa, malesuada quis pellentesque ut, placerat in massa. Nunc bibendum diam justo, in consequat ipsum fringilla ac. Praesent porta nibh ac arcu viverra, at scelerisque neque venenatis. Donec aliquam lorem non ultrices ultrices. Aliquam efficitur eros quis tortor condimentum, id pellentesque metus iaculis. Aenean at consequat neque, a posuere lectus. In eu libero magna. Pellentesque molestie tellus nec nisi molestie, eu dignissim lacus tristique. Sed tellus nulla, suscipit a velit non, mattis dictum metus. Curabitur mi mi, convallis nec libero quis, venenatis vestibulum ante.",
        fontSize: 15,
        margin: [0, 0, 0, 15]
      });

      doc.content.push({
        text: "Aliquam lacinia justo",
        fontSize: 20,
        bold: true,
        margin: [0, 20, 0, 15]
      });

      doc.content.push({
        image: res[1],
        width: 530
      });

      doc.content.push({
        text: "Phasellus suscipit in diam a interdum",
        fontSize: 20,
        bold: true,
        margin: [0, 20, 0, 15]
      });

      doc.content.push({
        table: {
          headerRows: 1,
          widths: [ "*", "*", "*", "*" ],
          body: [
            [
              { text: "USA", bold: true },
              { text: "Japan", bold: true },
              { text: "France", bold: true },
              { text: "Mexico", bold: true }
            ],
            [ "2500", "2500", "2200", "1200" ],
            [ "800", "1200", "990", "708" ],
            [ "2100", "2150", "900", "1260" ],
          ]
        }
      });

      doc.content.push({
        text: "Duis sed efficitur mauris",
        fontSize: 20,
        bold: true,
        margin: [0, 20, 0, 15]
      });

      doc.content.push({
        columns: [{
          image: res[2],
          width: 250
        }, {
          image: res[3],
          width: 250
        }],
        columnGap: 30
      });

      doc.content.push({
        text: "Aliquam semper lacinia",
        fontSize: 20,
        bold: true,
        margin: [0, 20, 0, 15]
      });

      doc.content.push({
        columns: [{
          image: res[4],
          width: 150
        }, {
          stack: [{
            text: "Maecenas congue leo vel tortor faucibus, non semper odio viverra. In ac libero rutrum libero elementum blandit vel in orci. Donec sit amet nisl ac eros mollis molestie. Curabitur ut urna vitae turpis bibendum malesuada sit amet imperdiet orci. Etiam pulvinar quam at lorem pellentesque congue. Integer sed odio enim. Maecenas eu nulla justo. Sed quis enim in est sodales facilisis non sed erat. Aenean vel ornare urna. Praesent viverra volutpat ex a aliquet.",
            fontSize: 15,
            margin: [0, 0, 0, 15]
          }, {
       text: "Fusce sed quam pharetra, ornare ligula id, maximus risus. Integer dignissim risus in placerat mattis. Fusce malesuada dui ut lectus ultricies, et sollicitudin nisl placerat. In dignissim elit in pretium lobortis. Fusce ornare enim at metus laoreet, ut convallis elit lacinia. Maecenas pharetra aliquet mi. Nulla orci nunc, egestas id nisi ut, volutpat sollicitudin mi.",
        fontSize: 15,
        margin: [0, 0, 0, 15]
      }],
      width: "*"
    }],
    columnGap: 30
  });

    await  PdfMake.createPdf(doc,null, fonts,pdfFontsVfs.pdfMake.vfs).download("report.pdf");


  }

  // Create series
  createSeries(field, name, chart) {
    console.log(chart);
    var series = chart.series.push(new am4charts.LineSeries());
    series.dataFields.valueY = field;
    series.dataFields.dateX = "date";
    series.name = name;
    series.tooltipText = "{dateX}: [b]{valueY}[/]";
    series.strokeWidth = 3;

    var bullet = series.bullets.push(new am4charts.CircleBullet());
    bullet.circle.stroke = am4core.color("#fff");
    bullet.circle.strokeWidth = 3;
    bullet.circle.radius = 7;
  }
  ngOnDestroy() {
    this.zone.runOutsideAngular(() => {
      if (this.chart) {
        this.chart.dispose();
        ...dispose other charts
      }
    });
  }
}

而 webpack 配置是:

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/main.ts',
    resolve: {
        extensions: ['.ts', '.js']
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: ['ts-loader', 'angular2-template-loader'],
                exclude: [ /node_modules/, /pdfmake.js$/ ]
            },
            {
                test: /\.(html|css)$/,
                use: 'raw-loader'
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({ template: './src/index.html' })
    ],

}

而 HTML 是:

    <div class="main">

  <input type="button" value="Save as PDF" (click)="savePDF()" />

<h1>In accumsan velit in orci tempor</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sem quam, sodales ac volutpat sed, vestibulum id quam. Sed quis arcu non elit fringilla mattis. Sed auctor mi sed efficitur vehicula. Sed bibendum odio urna, quis lobortis dui luctus ac. Duis eu lacus sodales arcu tincidunt ultrices viverra a risus. Vivamus justo massa, malesuada quis pellentesque ut, placerat in massa. Nunc bibendum diam justo, in consequat ipsum fringilla ac. Praesent porta nibh ac arcu viverra, at scelerisque neque venenatis. Donec aliquam lorem non ultrices ultrices. Aliquam efficitur eros quis tortor condimentum, id pellentesque metus iaculis. Aenean at consequat neque, a posuere lectus. In eu libero magna. Pellentesque molestie tellus nec nisi molestie, eu dignissim lacus tristique. Sed tellus nulla, suscipit a velit non, mattis dictum metus. Curabitur mi mi, convallis nec libero quis, venenatis vestibulum ante.</p>
<h2>Aliquam lacinia justo</h2>
<div id="chartdiv" class="chart"></div>

<h2>Phasellus suscipit in diam a interdum</h2>
<table>
  <tr>
    <th>USA</th>
    <th>Japan</th>
    <th>France</th>
    <th>Mexico</th>
  </tr>
  <tr>
    <td>2500</td>
    <td>1900</td>
    <td>2200</td>
    <td>1200</td>
  </tr>
  <tr>
    <td>800</td>
    <td>1200</td>
    <td>990</td>
    <td>708</td>
  </tr>
  <tr>
    <td>2100</td>
    <td>2150</td>
    <td>900</td>
    <td>1260</td>
  </tr>
</table>

<h2>Duis sed efficitur mauris</h2>
<div>
  <div class="col">
    <div id="chartdiv2" class="chart"></div>
  </div>
  <div class="col">
    <div id="chartdiv3" class="chart"></div>
  </div>
</div>

<br>
<h2>Aliquam semper lacinia</h2>
<div id="chartdiv4" class="chart"></div>
<p>Maecenas congue leo vel tortor faucibus, non semper odio viverra. In ac libero rutrum libero elementum blandit vel in orci. Donec sit amet nisl ac eros mollis molestie. Curabitur ut urna vitae turpis bibendum malesuada sit amet imperdiet orci. Etiam pulvinar quam at lorem pellentesque congue. Integer sed odio enim. Maecenas eu nulla justo. Sed quis enim in est sodales facilisis non sed erat. Aenean vel ornare urna. Praesent viverra volutpat ex a aliquet.</p>

<p>Fusce sed quam pharetra, ornare ligula id, maximus risus. Integer dignissim risus in placerat mattis. Fusce malesuada dui ut lectus ultricies, et sollicitudin nisl placerat. In dignissim elit in pretium lobortis. Fusce ornare enim at metus laoreet, ut convallis elit lacinia. Maecenas pharetra aliquet mi. Nulla orci nunc, egestas id nisi ut, volutpat sollicitudin mi.</p>

</div>

而 package.json 是:

    {
    "name": "angular-7-tutorial",
    "version": "1.0.0",
    "scripts": {
        "start": "webpack-dev-server --mode development --open"
    },
    "dependencies": {
        "@amcharts/amcharts4": "^4.9.1",
        "@angular/common": "^7.2.13",
        "@angular/compiler": "^7.2.13",
        "@angular/core": "^7.2.13",
        "@angular/forms": "^7.2.13",
        "@angular/platform-browser": "^7.2.13",
        "@angular/platform-browser-dynamic": "^7.2.13",
        "@angular/router": "^7.2.13",
        "core-js": "^3.0.1",
        "rxjs": "^6.4.0",
        "zone.js": "^0.9.0"
    },
    "devDependencies": {
        "@types/node": "^11.13.5",
        "angular2-template-loader": "^0.6.2",
        "html-webpack-plugin": "^3.2.0",
        "raw-loader": "^1.0.0",
        "ts-loader": "^5.3.3",
        "typescript": "^3.4.4",
        "webpack": "^4.30.0",
        "webpack-cli": "^3.3.0",
        "webpack-dev-server": "^3.3.1"
    }
}

关于文件夹结构: 在此输入图片描述

我理解的问题是语法冲突和未解决的承诺导致图表 PDF 无法加载自定义字体。如果您查看默认的 amcharts vs_fonts 文件,您会看到导出默认语法可能会导致问题。其次,请检查您的字体文件是否损坏。 希望这能帮助到您。谢谢。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接