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

Rotate text for table header #1140

Open
Tomnl opened this issue Mar 27, 2024 · 13 comments
Open

Rotate text for table header #1140

Tomnl opened this issue Mar 27, 2024 · 13 comments

Comments

@Tomnl
Copy link

Tomnl commented Mar 27, 2024

Please explain your intent
I would like to rotate text for a table header. In particular, have the heading text rotated so that it vertical in the table.

As far as I can tell this not currently possible.

Describe the solution you'd like
When using the pdf.table functionality as described in the docs, it would be great if there was a way to rotate the text in the cells (particulary for the table header).

Wishful thinking... but perhaps defined something like this

with pdf.table() as table:
    headings = table.row()
    headings.cell("Long heading name 1", rotate=90)
    headings.cell("Long heading name 2", rotate=90)
    headings.cell("Age")
    headings.cell("City")

I have looked into using the pdf.rotation approach described here but it does not seem compatible with pdf.table

e.g. the following row.cell will not be changed by the pdf.rotation (and would be difficult to know the x and y coordinates to use anyway)

with pdf.table() as table:
    row = table.row()
    with pdf.rotation(angle=90, x=10, y=300):
        row.cell(text='Long heading name 1') 

Additional context
See below for example format (not produced by fpdf2) to clarify the format I am referring to.

Untitled
ref

@Lucas-C
Copy link
Member

Lucas-C commented Mar 27, 2024

Hi @Tomnl

Thank you for reaching out and suggesting this feature.

I think it could be a handy addition.
Would you like to work on adding this feature to fpdf2 yourself?

Regarding how this feature would operate, I'm not sure that pdf.rotation() would be the best choice.
Maybe an optional rotate=<number> parameter passed to Row.cell() or Table.row() would be better.

In order to build the final table-related feature, a first "building-block" would be to be able to render some text block (with FPDF.write(), FPDF.text(), FPDF.cell() or FPDF.multi_cell()) at a given (X, Y) top-left corner position, with a given rotation applied to text. We will especially need that in FPDF.multi_cell(), so that when .multi_cell(..., output=MethodReturnValue.PAGE_BREAK | MethodReturnValue.HEIGHT) is called in Table._render_table_cell(), we can retrieve the cell height.

Also, we actually have too few unit tests regarding the rotation of text content, i.e. combining FPDF.rotation() with FPDF.write(), FPDF.text(), FPDF.cell() & FPDF.multi_cell().
Any addition of tests regarding those cases would be welcome!

@Tomnl
Copy link
Author

Tomnl commented Mar 27, 2024

Thanks for the quick reply @Lucas-C.

Maybe an optional rotate= parameter passed to Row.cell() or Table.row() would be better.

I agree that seems to be the logical place to add it

In order to build the final table-related feature, a first "building-block" would be to be able to render some text block (with FPDF.write(), FPDF.text(), FPDF.cell() or FPDF.multi_cell()) at a given (X, Y) top-left corner position, with a given rotation applied to text. We will especially need that in FPDF.multi_cell(), so that when .multi_cell(..., output=MethodReturnValue.PAGE_BREAK | MethodReturnValue.HEIGHT) is called in Table._render_table_cell(), we can retrieve the cell height.

So am I correct in thinking that within the .multi_cell() (and more generally FPDF.write(), FPDF.text(), FPDF.cell()) there should be an option to rotate the text and output the correct height? Would that still be using FPDF.rotation() or a new function?

Also, we actually have too few unit tests regarding the rotation of text content, i.e. combining FPDF.rotation() with FPDF.write(), FPDF.text(), FPDF.cell() & FPDF.multi_cell().

Not sure how much help I can bring. But maybe I can contribute a unit test first to help me understand the code base a bit better and then see what I could contribute!

@Lucas-C
Copy link
Member

Lucas-C commented Mar 27, 2024

So am I correct in thinking that within the .multi_cell() (and more generally FPDF.write(), FPDF.text(), FPDF.cell()) there should be an option to rotate the text and output the correct height?

Yes, I think that could be handy.
I'd be curious to know @gmischler opinion on that.

Would that still be using FPDF.rotation() or a new function?

Yes, that would be the simplest way to implement it, and I do not see any issue with using this approach.

Not sure how much help I can bring. But maybe I can contribute a unit test first to help me understand the code base a bit better and then see what I could contribute!

That would be very welcome! 🙂
You can start by reading this page: https://py-pdf.github.io/fpdf2/Development.html

@gmischler
Copy link
Collaborator

There's quite a few challenges here.

First, there's the general question of supporting vertically arranged text, which may or may not get handled by the same code.
There are at least two methods to do this. One group of scripts (eg. chinese and japanese) keep the orientation of the individual glyphs, and just stack them on top of each other. In those cases (not sure if there are exceptions) both writing directions are possible.
grafik
Another group (eg. Mongolian) are actually written horizontally, and the software then needs to rotate the result 90° clockwise. Displaying the text in horizontal orientation is typographically wrong, but common in mixed language text.
grafik

The other general question is, if we want to support other rotation angles than 90 degrees. Spreadsheet software usually allows rotating the text in a cell within a wide gamut.

grafik
LibreOffice supports the full 360°, but rotates wrapped text as a block, lifting some lines above the baseline.

grafik
Excel can only support +/- 90° of rotation, but it staggers wrapped line of text to follow the baseline. (It also sometimes gets confused about which border lines to draw, but that's besides the point here.)

And the third general question: Where do we want to support the rotation of text.
The original suggestion here was in table headers, which implies table cells in general. And since table cells are soon going to be reimplemented as text regions, it's probably a good idea to start there. And once we're looking at that, should whole regions support vertical and/or rotated text, or does it make more sense to add this functionality to individual paragraphs?

Besides the general questions, there's also quite a few details to consider.

line wrapping angled text

  • as block
  • staggered
    With our current table code, creating slanted cell outlines as the spreadsheets do is definitively not trivial. Maybe someone will eventually come up with an idea for that anyway...

Width and height determination:

  • conventionally: width fixed, height variable
  • vertical text: height fixed, width variable?
  • angled text: how to limit extents? May need both width and height fixed (for rectangular outlines).

Now obvously, this isn't a catalog of what sould get implemented right now. I just tried to collect as many of the poential features we might want to consider. The goal is, if you implement something, try to do so in a way that doesn't stand in the way of other features later.
I'm perfectly fine if you only want to consider horizontal and vertical text within orthogonal shapes for now. text regions will offer tools to fill text into other types of shape, which may lend a hand in allowing it to be at any arbitrary angle.

I'm sure there are other aspects that I can't think of right now...

@Lucas-C
Copy link
Member

Lucas-C commented Apr 30, 2024

Now obvously, this isn't a catalog of what sould get implemented right now. I just tried to collect as many of the poential features we might want to consider. The goal is, if you implement something, try to do so in a way that doesn't stand in the way of other features later.
I'm perfectly fine if you only want to consider horizontal and vertical text within orthogonal shapes for now. text regions will offer tools to fill text into other types of shape, which may lend a hand in allowing it to be at any arbitrary angle.

I fully agree: we can start with a basic (limited) implementation for now, but keep in mind how it could be generalized later on 😊

@Lucas-C
Copy link
Member

Lucas-C commented May 24, 2024

Hi @Tomnl

We have given lengthy answers, I hope they did not discourage you to contribute a PR to fpdf2 😅

Are you still willing to work on this feature?
If not, or if you don't have time for this anymore, that's totally OK 🙂

@Lucas-C
Copy link
Member

Lucas-C commented Jun 13, 2024

Closing this issue for now.
Please add a comment if you want this to be reopened 🙂

@NickFabry
Copy link

NickFabry commented Jul 25, 2024

Hi @Lucas-C and @gmischler ; I stumbled across this issue when I was trying to do exactly what @Tomnl was suggesting - rotating table headers vertically. I deal very often with data where the column/field names are quite long, but the data itself is quite short, sometimes only a single character (e.g. a boolean.) Being able to effectively narrow a column to the maximum data width rather than the field name would be very helpful in making tables that fit on a single page of width.

I would be very interested in this feature, and I saw your suggestions to @Tomnl about possibly how to implement it and submit a PR. I'd like to give it a go. However... I haven't contributed much to projects before, and I'm quite unfamiliar with this project in particular. I work a lot with pandas and sqlite and matplotlib - dealing with generating PDFs is new for me, so I might be a bit slow (well, very slow) and need a bit of handholding with how to submit a PR that's appropriate and useful.

I think it would be best to narrow the scope to start - maybe restrict the rotation angle to ± 90º, and rotate the entire block of text, not each individual character. But, for the future, let's take in two parameters: angle, rot_style. Angle is a float (default 0.0), rot_style a string. rot_style would (eventually) accept either 'all' (the default) or 'by_char' (as some Asian scripts are supposed to be rendered.) Probably these parameters would be passed as arguments to Row.cell() or Table.row()

Let me know if you would still be interested in adding this, if I was able to do some of the work.

@Lucas-C
Copy link
Member

Lucas-C commented Aug 19, 2024

Sorry for the delay in answering you @NickFabry 😢
Comments on closed issues are very often missed...
I'm reopening this issue now.

I would be very interested in this feature, and I saw your suggestions to @Tomnl about possibly how to implement it and submit a PR. I'd like to give it a go.

That's great!
Thanks for offering your help!

However... I haven't contributed much to projects before, and I'm quite unfamiliar with this project in particular. I work a lot with pandas and sqlite and matplotlib - dealing with generating PDFs is new for me, so I might be a bit slow (well, very slow) and need a bit of handholding with how to submit a PR that's appropriate and useful.

That's perfectly fine for us in the maintainers team, as long as you are not in a urge to get feedbacks / you PR merged,
we are very happy to welcome casual FLOSS contributors, and to help them as much as we can!

I think it would be best to narrow the scope to start - maybe restrict the rotation angle to ± 90º, and rotate the entire block of text, not each individual character. But, for the future, let's take in two parameters: angle, rot_style. Angle is a float (default 0.0), rot_style a string. rot_style would (eventually) accept either 'all' (the default) or 'by_char' (as some Asian scripts are supposed to be rendered.) Probably these parameters would be passed as arguments to Row.cell() or Table.row()

It's good to discuss the API / interface beforehand.
I'm not really sure what would be the best there...
Given that @gmischler already provided a lentghy answer, it may be interesting to get some insights from him 😊
However I agree that we could (and probably should) aim to implement this feature progressively.

Let me know if you would still be interested in adding this, if I was able to do some of the work.

Yes! 👍

@NickFabry
Copy link

Hi @Lucas-C - sorry for the long delay getting back. This is a 'keep alive' reply - I'm hoping I can make a go of this in mid-October; I have some known time free then. In the meanwhile - thanks for reopening this idea!

@sanketjainindian
Copy link

I would like to contribute to this specific issue and project as well, kindly let me know if i could be any help

@Lucas-C
Copy link
Member

Lucas-C commented Oct 14, 2024

Hi @NickFabry

It's mid-october, so I was wondering if you were still planning to tackle this issue? 🙂

@NickFabry
Copy link

...yes. But it won't be mid-October! I got a ton of work this week. So... another keep-alive post. I'll need a couple of free days to sit down, think about the API seriously and learn where and how to poke things. If it becomes completely infeasible for me to do, I'll reach out explicitly and let you know so you're not waiting forever. Thanks for your patience!

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

No branches or pull requests

5 participants