blikblum / pdfkit-webpack-example

Simple example of using PdfKit with webpack
25 stars 14 forks source link

File 'data/Helvetica.afm' not found in virtual file system #1

Closed thetw closed 5 years ago

thetw commented 5 years ago

I've used your webpack example to extend my create-react-app setup. I've added your rules to my webpack.config.js but there must a problem with the asset inlining.

in fact this does nothing in my generated webpack chunk: { loader: 'raw-loader', test: /\.afm$/ }

this is applied to the webpack output, which indicates that this a problem on the loader side and not a problem with your registerAFMFonts:

var map = {
    "./Helvetica.afm": "./node_modules/pdfkit/js/data/Helvetica.afm"
};

But when i double check the chunk contents there are no inlined glyphs. That's a pity because i really appreciated the approach with kicking out all the unnecessary embedded fonts. In fact everything up the stack trace works well until the PDFDocument constructor tries to load the standard Helvetica font file, then the app crashes with an uncaught error...

The only thing i modified is the pattern which chooses the font variants. I only want the standard Helvetica to be inlined, like this:

registerAFMFonts(require.context('pdfkit/js/data', false, /Helvetica\.afm$/));

Here's the trace:

Uncaught (in promise) Error: File 'data/Helvetica.afm' not found in virtual file system
    at VirtualFileSystem.readFileSync (virtual-fs.js:22)
    at Object.Helvetica (pdfkit.es5.js:2658)
    at new StandardFont (pdfkit.es5.js:2700)
    at Function.open (pdfkit.es5.js:3096)
    at PDFDocument.font (pdfkit.es5.js:3170)
    at PDFDocument.initFonts (pdfkit.es5.js:3132)
    at new PDFDocument (pdfkit.es5.js:4890)
    at new PDFBuilder (PDFBuilder.js:42)
    at PDFCreator.createID (index.jsx:40)
    at PDFCreator.componentDidMount (index.jsx:72)

Any ideas how to ensure that the file will be inlined into the chunk or safely referenced in the virtual file system? Cheers and thx in advance

blikblum commented 5 years ago

What version of pdfkit are you using?

Try:

import Helvetica from 'pdfkit/js/data/Helvetica.afm'
import fs from 'fs'

//some debug:
console.log(Helvetica)
console.log(typeof  Helvetica)

fs.writeFileSync('data/Helvetica.afm', Helvetica)
thetw commented 5 years ago

I'm using the same version as in your example: pdfkit v.0.9.1

I've tried your snippet with the following results:

console.log(Helvetica)
// Output: /static/media/Helvetica.d6455828.afm
console.log(typeof  Helvetica)
// Output: string

The error changed to:


Uncaught (in promise) TypeError: Cannot read property 'split' of undefined
    at new AFMFont (pdfkit.es5.js:2453)
    at new StandardFont (pdfkit.es5.js:2700)
    at Function.open (pdfkit.es5.js:3096)
    at PDFDocument.font (pdfkit.es5.js:3170)
    at PDFDocument.initFonts (pdfkit.es5.js:3132)
    at new PDFDocument (pdfkit.es5.js:4890)
    at new PDFBuilder (PDFBuilder.js:42)
    at PDFCreator.createID (index.jsx:40)
    at PDFCreator.componentDidMount (index.jsx:72)

In comparison to the last one, now the error is happening while trying to create the AFMFont invoked by StandardFont.

This line from the source causes the error:

this.bbox = this.attributes['FontBBox'].split(/\s+/).map(function (e) {
      return +e;
    });

The font is still not inlined... Instead webpack only creates the reference to the now bundled asset (as we see in the log above):

/***/ "./node_modules/pdfkit/js/data/Helvetica.afm":
/*!***************************************************!*\
  !*** ./node_modules/pdfkit/js/data/Helvetica.afm ***!
  \***************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

module.exports = __webpack_require__.p + "static/media/Helvetica.d6455828.afm";

/***/ }),
blikblum commented 5 years ago

Some other loader is taking precedence over raw-loader. From raw-loader docs try:

import Helvetica from '!!raw-loader!pdfkit/js/data/Helvetica.afm'

blikblum commented 5 years ago

Some other loader is taking precedence over raw-loader.

Probably https://webpack.js.org/loaders/file-loader

thetw commented 5 years ago

Thanks for your quick response. I think we are now on good way! Webpack inlined the raw contents of the Helvetica file, but there's still the same error from above... Uncaught (in promise) TypeError: Cannot read property 'split' of undefined

I did some research in the code:

class AFMFont {
  static open(filename) {
    return new AFMFont(fs.readFileSync(filename, 'utf8')); // this normally should return the raw content
  }

  constructor(contents) {
    this.contents = contents;
    this.attributes = {};
    this.glyphWidths = {};
    this.boundingBoxes = {};
    this.kernPairs = {};

    this.parse(); // here the raw content should be parsed and set to the properties above
    this.charWidths = new Array(256);
    for (let char = 0; char <= 255; char++) {
      this.charWidths[char] = this.glyphWidths[characters[char]];
    }

    this.bbox = this.attributes['FontBBox'].split(/\s+/).map(e => +e); // this fails because FontBBox is undefined, attributes must be empty
    // ...
  }

So why does webpack not forward the raw source to the static opener? Maybe there is an issue with the fs implementation? Maybe with context? The console statements from the beginning are still producing the same output – is this intended?

I'm really stuck, any ideas?

blikblum commented 5 years ago

So why does webpack not forward the raw source to the static opener?

The config added by CRA is somehow interfering

Maybe there is an issue with the fs implementation?

No

Maybe with context?

No. Because would work with direct import

The console statements from the beginning are still producing the same output – is this intended?

No. It should display the afm file content:

StartFontMetrics 4.1
Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated.  All Rights Reserved.
Comment Creation Date: Thu May  1 12:38:23 1997
Comment UniqueID 43054
...

Try:

import fs from 'fs'
import Helvetica from '!!raw-loader!pdfkit/js/data/Helvetica.afm'

fs.writeFileSync('data/Helvetica.afm', Helvetica)

An then run webpack with --display-modules option should get something like: [./node_modules/raw-loader/index.js!./node_modules/pdfkit/js/data/Helvetica.afm] ./node_modules/raw-loader!./node_modules/pdfkit/js/data/Helvetica.afm 81.5 KiB {main} [built]

thetw commented 5 years ago

Sorry for the late answer. This issue can be closed, it was an issue with accidentally calling the require context function on the Helvetica font after force importing the raw file. Thanks for your help! πŸ˜„

GO-gyan commented 5 years ago

This issue can be closed, it was an issue with accidentally calling the require context function on the Helvetica font after force importing the raw file.

Hi @thetw I am facing the same problem. Could you please explain above sentence.

thetw commented 5 years ago

@GO-gyan You don't need to use the registerFiles.js file, if you only want to inline the basic Helvetica font. To achieve this, just do this:

import fs from 'fs'
import Helvetica from '!!raw-loader!pdfkit/js/data/Helvetica.afm'

fs.writeFileSync('data/Helvetica.afm', Helvetica)

This will add the raw content of the .afm font to the Virtual file system of PDFKit. I've used Create React App with the custom webpack config provided by this repo.

Please mention, that you have to register a custom font in order to make another fonts working with PDFKit. I did this with fetching an external font file in order to keep bundle size small and then passing the response ArrayBuffer to the doc.registerFont('FontName', <ArrayBuffer>); function of PDFKit.

GO-gyan commented 5 years ago

@GO-gyan You don't need to use the registerFiles.js file, if you only want to inline the basic Helvetica font. To achieve this, just do this:

import fs from 'fs'
import Helvetica from '!!raw-loader!pdfkit/js/data/Helvetica.afm'

fs.writeFileSync('data/Helvetica.afm', Helvetica)

This will add the raw content of the .afm font to the Virtual file system of PDFKit. I've used Create React App with the custom webpack config provided by this repo.

Please mention, that you have to register a custom font in order to make another fonts working with PDFKit. I did this with fetching an external font file in order to keep bundle size small and then passing the response ArrayBuffer to the doc.registerFont('FontName', <ArrayBuffer>); function of PDFKit.

Thanks for your quick reply. It is working fine now.

wworrall commented 5 years ago

@GO-gyan You don't need to use the registerFiles.js file, if you only want to inline the basic Helvetica font. To achieve this, just do this:

import fs from 'fs'
import Helvetica from '!!raw-loader!pdfkit/js/data/Helvetica.afm'

fs.writeFileSync('data/Helvetica.afm', Helvetica)

This will add the raw content of the .afm font to the Virtual file system of PDFKit. I've used Create React App with the custom webpack config provided by this repo.

Please mention, that you have to register a custom font in order to make another fonts working with PDFKit. I did this with fetching an external font file in order to keep bundle size small and then passing the response ArrayBuffer to the doc.registerFont('FontName', <ArrayBuffer>); function of PDFKit.

Hi, I am also facing this issue and have little experience with Webpack. My setup uses CRA and customize-cra in order to add the module loader rules. Fundamentally it seems the rule: { test: /\.afm$/, loader: 'raw-loader' } is ignored as suggested by @blikblum. Are there any further ideas on how to get this to work without @GO-gyan 's work around?

Apologies for my lack of understanding and thank you for the content so far - the work around has helped greatly.

jarommadsen commented 5 years ago

@wworrall Make certain that the raw-loader and all other loaders are placed above the file-loader if you are using that. Also, if you are not replacing register-files.js with the single Helvetica register, make sure to update the registerAFMFonts function ctx(key) to ctx(key).default

jakubsuplicki commented 4 years ago

Just a heads up for people who struggle to implement pdfkit in Nuxt.js / Vue.js:

  1. Install all required dependencies the same as in the example.
  2. In your nuxt.config.js write the following:
    extend(config, ctx) {
      const alias = config.resolve.alias = config.resolve.alias || {}
      alias['fs'] = 'pdfkit/js/virtual-fs.js'
      config.module.rules.push(
        { enforce: 'post', test: /fontkit[/\\]index.js$/, loader: "transform-loader?brfs" },
        { enforce: 'post', test: /unicode-properties[/\\]index.js$/, loader: "transform-loader?brfs" },
        { enforce: 'post', test: /linebreak[/\\]src[/\\]linebreaker.js/, loader: "transform-loader?brfs" },
        { test: /src[/\\]assets/, loader: 'arraybuffer-loader'},
        { test: /\.afm$/, loader: 'raw-loader'}
      )
    }
  1. In your Vue template (note that I am importing only one font so there is no need to use the register-files.js as mentioned above by @thetw ):
    <script>
    import fs from 'fs'
    import Helvetica from '!!raw-loader!pdfkit/js/data/Helvetica.afm'
    if(process.browser){
      fs.writeFileSync('data/Helvetica.afm', Helvetica)
      var PDFDocument = require('pdfkit').default;
      var blobStream = require('blob-stream');
      var ace = require('brace');
      require('brace/mode/javascript');
      require('brace/theme/monokai');
    }
    </script>

By doing it this way, you will avoid having problems with server-side rendering.

Thanks everyone, hope it helps.

suryasarafe commented 4 years ago

import fs from 'fs' import Helvetica from '!!raw-loader!pdfkit/js/data/Helvetica.afm'

fs.writeFileSync('data/Helvetica.afm', Helvetica)

i have problem to find file to put above code, and never use the registerFiles.js, can you be more specify about how to solve it.?

i just import pdfmake.min.js and vfs_fonts.js.


const font = {
  Helvetica: {
    normal: 'Helvetica',
    bold: 'Helvetica-Bold',
    italics: 'Helvetica-Oblique',
    bolditalics: 'Helvetica-BoldOblique'
  },
}
pdfMake.createPdf({
  content: [
    'First paragrap',
    'Second one'
  ],
  defaultStyle: {
    font: 'Helvetica'
  }
}, null, font).open()

Thank you.
blikblum commented 4 years ago

You must use webpack. See the code in this repo

blikblum commented 4 years ago

BTW: this repository is for pdfkit, not pdfmake

ivanvladimirov commented 4 years ago

I went through this problem and now I am trying to add my custom font. What I tried is adding the font with fs, just like the Helvetica one, but from a local folder. It obviously didn't work - the error I got was: Error: File 'static/media/Roboto-Regular.11eabca2.ttf' not found in virtual file system

I know I have to use arrayBuffer somehow, but I have no idea what I should do. Please help!

EDIT: I imported Roboto in the following way: import Roboto from "../../assets/fonts/Roboto-Regular.ttf" Then I tried adding my font like so: const buffer = new Buffer(Roboto) doc.registerFont('Roboto', buffer) and now the error is: Error: Unknown font format, also tried with .otf and .afm - same response

@thetw, @blikblum, you think you can help me with that? :(

seyfer commented 1 year ago

If somebody comes here with this issue on AWS Lambda and your error looks like /var/task/data/Helvetica.afm not found this SO answer helped me https://stackoverflow.com/questions/72775287/cdk-lambda-nodejsfunction-pdfmake-enoent-error basically, you need to not bundle pdfkit, but include it fully in node_modules when deploying to AWS Lambda

zvizesna commented 1 year ago

I managed to solve this properly for create-react-app without ejecting or triggering the ESLint error import/no-webpack-loader-syntax.

I used CRACO and the following craco.config.js:

const webpack = require("webpack");

module.exports = {
    webpack: {
        configure: (webpackConfig, { env, paths }) => {

            // Modifying webpack.module.rules with CRACO is only possible using the `configure` function,
            // not using the `configure` object literal.
            // See supported config for webpack: https://craco.js.org/docs/configuration/webpack/

            webpackConfig.module.rules = [
                ...webpackConfig.module.rules,
                // bundle and load afm files verbatim
                { test: /\.afm$/, type: 'asset/source' },
                // bundle and load binary files inside static-assets folder as base64
                {
                    test: /src[/\\]static-assets/,
                    type: 'asset/inline',
                    generator: {
                        dataUrl: content => {
                            return content.toString('base64');
                        },
                    },
                },
                // load binary files inside lazy-assets folder as an URL
                {
                    test: /src[/\\]lazy-assets/,
                    type: 'asset/resource'
                },
                // convert to base64 and include inline file system binary files used by fontkit and linebreak
                {
                    enforce: 'post',
                    test: /fontkit[/\\]index.js$/,
                    loader: 'transform-loader',
                    options: {
                        brfs: {}
                    },
                },
                {
                    enforce: 'post',
                    test: /linebreak[/\\]src[/\\]linebreaker.js/,
                    loader: 'transform-loader',
                    options: {
                        brfs: {}
                    },
                },
            ];

            webpackConfig.resolve.alias = {
                ...webpackConfig.resolve.alias,

                // maps fs to a virtual one allowing to register file content dynamically
                fs: 'pdfkit/js/virtual-fs.js',
                // iconv-lite is used to load cid less fonts (not spec compliant)
                'iconv-lite': false,
            };

            webpackConfig.resolve.fallback = {
                ...webpackConfig.resolve.fallback,

                // crypto module is not necessary at browser
                crypto: false,
                // fallbacks for native node libraries
                process: require.resolve("process/browser"),
                zlib: require.resolve("browserify-zlib"),
                stream: require.resolve("stream-browserify"),
                util: require.resolve("util"),
                buffer: require.resolve("buffer"),
                assert: require.resolve("assert"),
            };

            webpackConfig.plugins = [
                ...webpackConfig.plugins,

                new webpack.ProvidePlugin({
                    Buffer: ["buffer", "Buffer"],
                    process: "process/browser",
                }),
            ];

            return webpackConfig;
        }
    }
}

Then I followed the webpack example, copied the src/registerStaticFiles.js to my project, created a src/static-assets folder, and added import './registerStaticFiles' where I create the PDFDocument . Now it works without having to explicitly import and register Helvetica.

7iomka commented 1 year ago

@zvizesna

in next.js I have an issue

 β¨― ../../node_modules/process/browser.js
@steklo24/store:dev-clean: TypeError: Property left of AssignmentExpression expected node to be of a type ["LVal"] but instead got "BooleanLiteral"
@steklo24/store:dev-clean: Import trace for requested module:
@steklo24/store:dev-clean: ../../node_modules/process/browser.js
seport commented 9 months ago

Commenting here in case it helps anyone else. I was having the same problem and for me it boiled down to this line in my webpack.config.js:

module.exports = {
...
resolve: {
    extensions: ['', ...],
...
}

this was the result of me copying and pasting webpack configs from moldy blogs, documentation, or stack overflow threads that warned:

Setting this option will override the default, meaning that webpack will no longer try to resolve modules using the default extensions. If you want modules that were required with their extension (e.g. require('./somefile.ext')) to be properly resolved, you must include an empty string in your array. Similarly, if you want modules that were required without extensions (e.g. require('underscore')) to be resolved to files with β€œ.js” extensions, you must include ".js" in your array.

However, in modern webpack this is no longer necessary, and even used to throw an error warning you not to do that anymore (https://github.com/webpack/webpack/issues/3043) (webpack migration notes)

in modern webpack versions, it seems the empty string in resolve.extensions can cause errors like these and afaik you can just remove it and it should all work πŸ‘

(resolve.extensions documentation)