diegomura / react-pdf

📄 Create PDF files using React
https://react-pdf.org
MIT License
14.34k stars 1.13k forks source link

Working Font example #1304

Closed grahampcharles closed 5 months ago

grahampcharles commented 3 years ago

Does anyone have working code that changes the font? Ideally, from fonts.gstatic.com or hosted locally? I'm spending hours on what should be really trivial; specifically:

Tearing my hair out over this. Does react-pdf just not do fonts?

diegomura commented 3 years ago

Loading the font over https fixed for me. See here. The REPL is a little buggy lately though. Does this fix your issue?

JuhoKon commented 3 years ago

This is the way we are currently dealing with a locally hosted font:

import BoldArial from "../../assets/fonts/ArialBold.ttf";
import Arial from "../../assets/fonts/Arial.ttf";

Font.register({
  family: "Arial",
  fontStyle: "normal",
  fontWeight: "normal",
  fonts: [
    {
      src: Arial,
    },
    {
      src: BoldArial,
      fontWeight: "bold",
    },
  ],
});

Then to use it:

const styles = StyleSheet.create({
  normalText: {
   fontFamily: "Arial",
  },
  boldExample: {
    fontWeight: "bold",
    fontFamily: "Arial",
  },
});
ankush981 commented 3 years ago

I'm also having serious troubles over what seems to be a tiny tasking of locating and loading font files. I'm on Next, and among Next, React, Webpack 4 and react-pdf, I've given up and finally moved the fonts to the back end. Now, I'm battling weird CORS issues in Chrome, but at least that's a more well-known problem. 😅 Not saying it's the library author's fault . . . it's just that the number of tools and possible configurations has grown so much that we are always stuck in this hole or another.

wenscl commented 3 years ago

I keep getting Unknown font format error if I try to load Roboto from google fonts or any other page, either from local or the url.

import RobotoRegular from "../../../../../static/frontend/fonts/Roboto-Regular.ttf";

Font.register({
  family: "Roboto",
  // src: "https://fonts.googleapis.com/css2?family=Roboto&display=swap",
  src: RobotoRegular,
});

export const styles = StyleSheet.create({
  page: {
    fontFamily: "Roboto",
  },
});
tobua commented 3 years ago

Check out my demo with webpack and @react-pdf version 2 here. It's dynamically loading various fonts including Roboto. Here's the source.

I remember also having issues with hasGlyphForCodePoint at first.

ghood97 commented 2 years ago

I am also still having an issue trying to register a font.

Currently my code looks like this

Font.register({
    family: 'Mosaic',
    src: require("../../fonts/PaletteMosaic-Regular.ttf"),
})

I am getting "Unhandled Rejection (TypeError): dataUrl.split is not a function" in font.js whenever I try to view the document in the PDFViewer. I tested in both Edge and Chrome.

I feel like I am missing something but all of the examples look similar to what I have above. Has anyone found other solutions that have worked for them?

diegomura commented 2 years ago

Are you running react-pdf in the browser or node? To what resolves require("../../fonts/PaletteMosaic-Regular.ttf") in your project?

ghood97 commented 2 years ago

I am running it in the browser. That path resolves to /src/fonts in my React app

diegomura commented 2 years ago

As a url? It seems that the resolve is not returning either a url of the font, or base64 encoded data or buffer. Try console logging the require and check that

ghood97 commented 2 years ago

This is what I get in the Edge console when I log require("../../fonts/PaletteMosaic-Regular.ttf"):

Screenshot 2021-08-17 152119

Module {default: "/static/media/PaletteMosaic-Regular.d47d626b.ttf", __esModule: true, Symbol(Symbol.toStringTag): "Module"}
default: "/static/media/PaletteMosaic-Regular.d47d626b.ttf"
Symbol(Symbol.toStringTag): "Module"
__esModule: true
[[Prototype]]: Object
tobua commented 2 years ago

@ghood97 With this error it looks like the url property expects a string, but you're passing an object with the string under the default attribute (the font is exported as an ES Module). Therefore you get the dataUrl.split error where the plugin treats the object as a string, which of course doesn't work. I would expect the following to work:

const font = require("../../fonts/PaletteMosaic-Regular.ttf")
Font.register({
    family: 'Mosaic',
    src: font.default,
})
diegomura commented 2 years ago

That's exactly right @tobua , thanks! The snippet above should work, but only if the font is being served as an asset on the web server you're using. Don't forget you're in a web environment so you cannot load things from your file system with just their path

ghood97 commented 2 years ago

@tobua @diegomura Thanks for pointing that out to me, that was a good teaching moment!

That worked to clear up the "dataUrl.split is not a function" error. Now I am getting TypeError: Cannot read property 'hasGlyphForCodePoint' of null. I saw this was mentioned in a couple of other issues as well. Screenshot 2021-08-17 154854

tobua commented 2 years ago

Well, now it's less simple. The error basically means that the font it's trying to render hasn't been registered or the registration failed. A few tips:

ghood97 commented 2 years ago

Here is some more info after testing:

At least I can see the the font is being loaded and displayed even if it is just briefly. What else could cause this error?

diegomura commented 2 years ago

What react-pdf version are you using? It's hard to tell without a way to replicate it or more context. Font.register can and should be outside any component

ghood97 commented 2 years ago

I just updated to @react-pdf/renderer v2.0.18. I was a couple of minor versions behind. I am no longer getting errors and the document loads, but now the font is not being applied to the text.

Just to be clear this is the code I am currently using:

const font = require("../../fonts/OpenSans-Regular.ttf").default
Font.register({
  family: 'OpenSans',
  src: font,
})

const titleStyles = StyleSheet.create({
    title: {
      textAlign: 'center',
      fontSize: '32',
      fontFamily: 'OpenSans'
    }
})

return (
   <Text style={titleStyles.title}>{props.title}</Text>
)

I also tried to change the font src in the REPL with "https://fonts.gstatic.com/s/stixtwotext/v1/YA9Gr02F12Xkf5whdwKf11l0jbKkeidMTtZ5Yihg2ROfURA.woff2" and that didn't work either.

Is there anything else I should try?

ghood97 commented 2 years ago

UPDATE: The font is applied to the PDF when I download it and open it., but it does not show in the PDFViewer.

tobua commented 2 years ago

Had no luck with woff2 either, I don't think it's supported. Since you mentioned the REPL the following did work well there:

Font.register({
  family: 'CyrBit', 
  src: 'https://fonts.cdnfonts.com/s/7818/CyrBit.woff',
})

const Quixote = () => (
  <Document>
    <Page>
      <Text style={{fontFamily: 'CyrBit'}}>hello</Text>
    </Page>
  </Document>
);

ReactPDF.render(<Quixote />);

Since in your case downloading works while the PDFViewer fails I assume you're rendering before registering the font. Downloading works, as this only renders once the user downloads.

The PDFViewer will render as soon as displayed on the page, therefore you basically need to ensure to register before even rendering any React with react-dom.render.

Without a reproducible example all this is just a guess.

ghood97 commented 2 years ago

That makes sense. I will play around with it a little more. Thanks for your help on this.

ghood97 commented 2 years ago

The last thing I will add about this is that when the PDFViewer JSX is before the PDFDownloadLink, the font is applied in the viewer. But when the PDFViewer JSX is after the PDFDownloadLink, it does not apply the font in the viewer.

Example: This does NOT apply the font

<PDFDownloadLink document={doc} fileName={filename} style={{ display: 'block' }}>
   {({ loading }) => (loading ? loadingJsx : downloadReadyJsx)}
</PDFDownloadLink>

<Container fluid>
   <PDFViewer style={{ display: 'block', width: '100%', height: '100vh' }}>
      {doc}
   </PDFViewer>
</Container>

This DOES apply the font

<Container fluid>
   <PDFViewer style={{ display: 'block', width: '100%', height: '100vh' }}>
      {doc}
   </PDFViewer>
</Container>

<PDFDownloadLink document={doc} fileName={filename} style={{ display: 'block' }}>
   {({ loading }) => (loading ? loadingJsx : downloadReadyJsx)}
</PDFDownloadLink>

So I know that the font is registering before the render, but I have no idea why the above example would make a difference. It is consistent every time

tobua commented 2 years ago

Good point, glad it's now working. Just updated my example and the viewer fails completely if the download link is rendered before. I even left a note there before to only render either one, as at first they couldn't be rendered together.

Might very well be an issue with the plugin, but in my opinion not directly related to the fonts. Since all browser viewers already have download options it's probably not a priority.

@diegomura to close this.

carlobeltrame commented 2 years ago

@ghood97 I debugged the font loading today, and I at least found a workaround for the ordering problem. See https://github.com/diegomura/react-pdf/issues/1494#issuecomment-918359220

ludovv commented 2 years ago

This is the way we are currently dealing with a locally hosted font:

import BoldArial from "../../assets/fonts/ArialBold.ttf";
import Arial from "../../assets/fonts/Arial.ttf";

Font.register({
  family: "Arial",
  fontStyle: "normal",
  fontWeight: "normal",
  fonts: [
    {
      src: Arial,
    },
    {
      src: BoldArial,
      fontWeight: "bold",
    },
  ],
});

Then to use it:

const styles = StyleSheet.create({
  normalText: {
   fontFamily: "Arial",
  },
  boldExample: {
    fontWeight: "bold",
    fontFamily: "Arial",
  },
});

Thanks!! It really works!

carlobeltrame commented 2 years ago

It does work this way in the browser in development for me as well. But sometimes the font is not loaded when the PDF is rendered for the first time after refreshing, which is why I have to resort to the await trick I described in https://github.com/diegomura/react-pdf/issues/1494#issuecomment-918359220. And when bundling for production (using vite in my case), I always get the Unknown font format error. Will have to debug that one soon.

carlobeltrame commented 2 years ago

The reason for my problem on production was that vite refused to bundle the font files in a production build. When react-pdf fetched the fonts, the server would then fall back to serving the index.html of my vite app. This file of course is not a font file, and so react-pdf / fontkit rightfully reported Unknown font format.

zavbala commented 2 years ago

The reason for my problem on production was that vite refused to bundle the font files in a production build. When react-pdf fetched the fonts, the server would then fall back to serving the index.html of my vite app. This file of course is not a font file, and so react-pdf / fontkit rightfully reported Unknown font format.

same problem :(

carlobeltrame commented 2 years ago

@zavbala I doubt you have the exact same problem as me, since I am using a rather nonstandard setup of running a Vue.js app using Vite, which only uses react for PDF rendering, nothing else. The problem in my case was not with react-pdf itself, but rather with Vite. React-pdf / fontkit could have provided a more helpful error message, but that's about it.

In case you do have a very similar setup (especially if you use Vite), I solved the problem by forcing Vite to include the font files in the production bundle, see here. This line in my case MUST be in a Vue.js file, it won't work if it's in a .jsx file or if it's only imported by a .jsx file.

But I suspect you probably experience the other problem that is detailed in this issue, for which a workaround already exists: You can await a Font.load({ fontFamily: 'MyCustomFont' }) call before rendering the PDF, and then it works.

paschaldev commented 5 months ago

Anyone still experiencing this issue should check my solution for Next.JS, but it works with React too. The solution for local font is to make sure the font is publicly available from your app URL.

https://github.com/diegomura/react-pdf/issues/1954#issuecomment-1891009006

diegomura commented 5 months ago

Will close this as it derail a lot from original post and several fixes were proposed. Thanks so much all for replying!