Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding font weight support #402

Closed
lukepighetti opened this issue Nov 30, 2018 · 16 comments
Closed

Adding font weight support #402

lukepighetti opened this issue Nov 30, 2018 · 16 comments

Comments

@lukepighetti
Copy link

lukepighetti commented Nov 30, 2018

Hi there,

I'd like to add font weight support to react-pdf and I was hoping a contributor could recommend an efficient strategy or inform me of any existing work before I dive in.

Cheers
Luke

@diegomura
Copy link
Owner

Hey.
Font weight is not as simple as it might look at first glance.
Currently the way to support different font weights it's by loading different font files per each font-weight needed, and styling the text based on your needs.

Font.register(src1, { fontFamily: 'Roboto-Regular' }); 
Font.register(src2, { fontFamily: 'Roboto-Bold' });

const styles = StyleSheet.create({
  title: { fontFamily: 'Roboto-Bold' },
  paragraph: { fontFamily: 'Roboto-Regular' },
})

Or you can also choose one of the default fonts shipped with the lib:

  'Courier',
  'Courier-Bold',
  'Courier-Oblique',
  'Helvetica',
  'Helvetica-Bold',
  'Helvetica-Oblique',
  'Times-Roman',
  'Times-Bold',
  'Times-Italic',

The API could change, but you still have to provide different files and change the font-family for each chunk you want styled differently. I know it's not ideal, but it's the only way

@diegomura
Copy link
Owner

Closing this, since there's nothing else to do about it

@lukepighetti
Copy link
Author

lukepighetti commented Nov 30, 2018

Hi there, this wasn't a request for support, but instead a request for conversation about how one might implement font weights in react-pdf so that if I find myself with some spare time in the next few weeks I could take a stab at it armed with a little information from people who understand it much more intimately than I currently do. 😄

I noticed that Google Fonts has moved over to bundled woff2 and MacOS uses bundled ttc. Has anyone looked into what's required to support these types of fonts?

@lukepighetti
Copy link
Author

lukepighetti commented Nov 30, 2018

Just as a note, I am interested in contributing on this feature but if the issue remains closed I will just assume that you are not looking for this particular contribution.

It looks like you're using a modified version of fontkit so I suspect most of what we need to handle woff2 and ttc files already exists, and would just need to be wired up. Has the API been modified enough that https://github.com/foliojs/fontkit can no longer be substituted in?

@diegomura
Copy link
Owner

My apologizes! I thought you were just asking how to implement font-weights 😄 I wasn't thinking that you were requesting a change haha. Even if you did, I truly don't see any other alternative rather than loading different font files.

You are very welcomed to contribute! Would be awesome if you can came up with a nicer solution to this feature

Yes. I'm using a fontkit fork, for some reasons:

  • To make fontkit run in the browsr without needing to shim some node objects
  • To expose stringIndices of the calculated glyhps, which are needed for the text layout (made by textkit)

About the first point, I do remember having trouble with some fontkit dependencies so I had to disable some font extensions. I never had time to look back at that part of the project, and that was some time ago, so I don't remember the details exactly right now. I would very pleased to assist you on this fix though.

So those are basically the differences from the original fontkit package. And because of that, we can't use ir directly

@DuncanMacWeb
Copy link
Contributor

DuncanMacWeb commented Dec 5, 2018

As far as the interface is concerned, I’d suggest implementing the @font-face interface from the web (as mentioned in #377) — the link talks about the unicode-range use case for @font-face, but this can also support registering different fonts as weights of the same typeface. Then, font-family: Foo + font-weight: bold would just be another way of specifying font-family: Foo-bold. I would be willing to contribute to this but would appreciate pointers about the best place to start with a PR.

@agarant
Copy link
Contributor

agarant commented Dec 6, 2018

If it can help someone, here is a way to work with font-weight. It works great if using inline style. Essentially, it's a Text primitive that wraps the React-Pdf Text component and parses the style prop to add the right font-family. You still have to register the different fonts, but using font-weights as you build your pdf is easier.

import React from 'react';
import { Text as TextPDF } from '@react-pdf/renderer';

class Text extends React.Component {
  _getFontFamily = ({ fontWeight, fontFamily }) => {
    if (fontFamily) return fontFamily;
    if (!fontWeight) return 'Roboto';

    const weight = parseInt(fontWeight, 10);
    if (fontWeight <= 300) {
      return 'Roboto-Light';
    } else if (fontWeight <= 400) {
      return 'Roboto';
    } else if (fontWeight <= 500) {
      return 'Roboto-Medium';
    } else if (fontWeight <= 700) {
      return 'Roboto-Bold';
    } else {
      return 'Roboto-Black';
    }
  };

  render() {
    const { children, className, style = {}, force, debug } = this.props;
    return (
      <TextPDF
        debug={debug}
        className={className}
        style={{
          fontFamily: this._getFontFamily(style),
          ...style,
        }}
      >
        {children}
      </TextPDF>
    );
  }
}

export default Text;

A possible Library side solution might be to register fonts with a font-weight or preferred weight. This way, the style font-weight could be implemented on the library side by following the technique outlined above. @diegomura , what do you think ?

ex.

Font.register(`${public_url}/fonts/Roboto-Light.ttf`, {
  family: 'Roboto-Light',
  weight: '100,200,300',
});
Font.register(`${public_url}/fonts/Roboto-Medium.ttf`, {
  family: 'Roboto-Medium',
  weight: '400,500',
});

@diegomura
Copy link
Owner

diegomura commented Dec 6, 2018

Thanks for pushing this feature forward. Here's what I think:

I always tried to be as close to the web APIs as possible. It's great to take advantage of these, since most of people already know them. So implementing the font-face API as @DuncanMacWeb described it's aligned to that. However, I'm not sure if adding the unicode-range option. Didn't estimated how hard would be to do it (I refactored the entire textkit solution, in order to be more easy to just add stuff), but the font-substitution engine implemented in react-pdf already fallbacks into another font if the given one does not have a glyph for a particular character. So struggling with unicode ranges seems making the api more complex without any visible enhance.

I think @agarant solution really makes a change though. Because would enable to handle the styling of a text as you would do it in the web (using the font-weight attribute), instead of having to set different font-families. However, I don't think that adding the X-Light|Medium|Bold|Whatever is even necessary. It creates an arbitrary pattern that font families names should follow. And also with just family and weight we have all information to switch between font files to each particular case. It's also necessary to fallback to the closest font in case the provided font-weight is no explicitly defined.

In conclusion, I think we can use some kind of mixture of both solutions:

Font.register(`${public_url}/fonts/Roboto-Light.ttf`, {
  family: 'Roboto',
  weight: '100,200,300', // or [100, 200, 300]
});
Font.register(`${public_url}/fonts/Roboto-Medium.ttf`, {
  family: 'Roboto',
  weight: '400,500',
});

Internally we can also map values such as bold, light, etc to the corresponding number representation.

What do you guys think?

@lukepighetti
Copy link
Author

lukepighetti commented Dec 6, 2018

I think this is a fine solution.

My only additional wish would be to support font formats that have multiple weights inside them, as it seems like Google Fonts only provides woff2 anymore. (Just checked again and they are serving me ttf, not sure what changed) But I recognize that it may not be worth the extra difficulty especially since your intermediate approach with ttf fonts would provide so much benefit. @diegomura

@DuncanMacWeb
Copy link
Contributor

DuncanMacWeb commented Dec 7, 2018

I’ve updated the link to @font-face to one that explains using the interface to declare different font weights. Sorry; talking about unicode-range was a distraction!

I like updating Font.register to take font weights into account as you suggest @agarant @diegomura, but I would also like to update the Document component to accept @font-face style declarations, so that we could write:

const Doc = styled.Document`
  @font-face {
    font-family: 'Roboto';
    font-weight: 700;
    src: '${public_url}/fonts/Roboto-Bold.ttf';
  }
`;

/* the above could result in Document being called like:

  <Document style={{
    '@font-face': [{    // because there can be multiple @font-face declarations
      fontFamily: 'Roboto',
      fontWeight: 700,
      src: `${public_url}/fonts/Roboto-Bold.ttf`,
    }],
  }}>
*/

const BoldText = styled.Text`
  font-family: Roboto;
  font-weight: bold;
`;

ReactDOM.render((
  <Doc>
    <Page>
      <BoldText>lovely bold text in Roboto</BoldText>
    </Page>
  </Doc>
), document.getElementById('react-root'));

We could call Font.register from the constructor of Document based on the font-face rules in its stylesheet.

@lukepighetti
Copy link
Author

lukepighetti commented Dec 8, 2018

I've been giving this more thought and Flutter uses a similar method where you register the ttf with a weight and it works very well. Flutter will pick the closest weight if it's not available. Just for reference, this is how they structure it (in a .yaml file). In other words, I think registering the weight with the ttf is a fantastic idea and seems like low hanging fruit which is always a plus! :)

  fonts:
    - family: Trajan Pro
      fonts:
        - asset: fonts/TrajanPro-ExtraLight.ttf
          weight: 200
        - asset: fonts/TrajanPro-Light.ttf
          weight: 300
        - asset: fonts/TrajanPro-Regular.ttf
          weight: 400
        - asset: fonts/TrajanPro-Medium.ttf
          weight: 500
        - asset: fonts/TrajanPro-Bold.ttf
          weight: 600

Note: I don't think we should use a yaml file, this is just an example from outside React which supports the current idea

@DuncanMacWeb
Copy link
Contributor

@lukepighetti for reference, MDN explains the algorithm used on the web to resolve font weight keywords against numeric weights.

@DuncanMacWeb
Copy link
Contributor

@lukepighetti were you able to make any headway with this? I am doing some work related to this over at #508.

@lukepighetti
Copy link
Author

Hi @DuncanMacWeb , I am sorry to report that I haven't been using react-pdfsince I switched my focus to mobile, so I won't be able to contribute on this issue. My apologies!

@DuncanMacWeb
Copy link
Contributor

No worries @lukepighetti!

@jackvik
Copy link

jackvik commented Sep 17, 2021

I am rendering pdf by passing blob to the file prop. Can we change the font size or fontweight of that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants