firefoxxy: it's not too complicated to add my modifications to the code, and the source code itself is already included in the "Generate Cover.zip" file you can download. Most of the modification goes in
draw.py, which is responsible for (unsurprisingly) doing all the text layout and actually drawing it. I will make the code available on GitHub, plus as good a tutorial as I can come up with in the time I can spare.
kovidgoyal: yes,
QPainterPath absolutely does have a
boundingRect() method, which gives you the bounding rectangle it ends up with after you create it and do something like
QPainterPath.addText(). But what it doesn't have, and what would be useful for what we want to do here, is a
setBoundingRect() method, or a parameter for
QPainterPath.addText() that would be equivalent.
A bit more detail for anybody curious: currently, the
Generate Cover plugin uses a method called
QPainter.drawText() to do the text layout and then actually drawing it. And
QPainter.drawText() will let you specify a bounding rectangle as one of its parameters (it also has parameters for the text to draw, the font to use, the font size to use, the alignment of the text, whether wrapping is allowed, etc.). Then
QPainter.drawText() handles the details of text layout automatically, fitting the text into the specified bounding rectangle and using the specified alignment and wrapping settings.
So, to make it work with
Generate Cover's options, you just have to make a bounding rectangle from the cover size and the desired margins, and then let
QPainter.drawText() handle all the layout for you.
Unfortunately,
QPainter.drawText() doesn't have any way to stroke/outline the text it draws. So, to make the stroke option work again, we have to convert the text to a path, which is just a representation of the outline of the text. Then we can draw the resulting path, using whatever color and thickness we want, and we can also fill the path, so we can get a stroke and a fill of different colors. Just what we want!
So the way we make our text into a path we can draw is to use a method called
QPainterPath.addText(). But it is not as sophisticated as
QPainter.drawText(). The big difference for our purposes is that
QPainterPath.addText() doesn't let you specify a bounding rectangle. You can only specify the starting point for the path (and it isn't even a bounding rectangle point -- it's the point where the
baseline of the text will start. What that means is explained in the
documentation for QPainterPath.addText()).
And
QPainterPath.addText() is also not good at text alignment (left, center, right) or wrapping, so you have to figure out where the given text will be wrapped and then calculate the starting point for each line. It's a lot more intricate than just letting the
QPainter.drawText() function handle it automatically.
Fortunately, you can do a version of what
QPainter.drawText() does internally (I believe -- haven't examined the Qt / PyQt source code yet to verify this) and use the
QTextLayout class to figure out where the text will wrap and what the starting points of each line will be. Then you can plug those into
QPainterPath.addText(), then add those paths to the
QPainter object with
QPainter.addPath(), run a fill with
QPainter.fillPath(), and the words are drawn just as if you were using
QPainter.drawText() (but with the stroke showing up again! Hooray!).
There's a minor issue currently, where some characters in some fonts are not processed properly.
For instance, one of my books has a title that includes a literal HTML tag,
</span>, and the slash is normally seen as part of the text path, as it should be. But if there's an "s" right after it, then suddenly the slash is
not seen, and it doesn't get a stroke drawn for it, even when all the other characters do.
In another case, titles that have colons in them, where the font being used is the "Buffied" font (which I use for anything spooky ... and, of course, for anything
Buffy the Vampire Slayer related), are working fine for the most part, but for some reason
QPainter.fillPath() doesn't realize that the colon's two circles are fillable paths. So it draws the stroke normally, and the colon shows up as two circles stacked vertically ... but they are left as empty outlines, even though all the other characters are filled in properly.
Because of this issue, right now I still have the original
QPainter.drawText() code running first, and then the stroke path just draws over the top of it, rather than drawing everything itself. That at least leaves characters visible, even when the new
QPainterPath.addText() technique is having problems. I suspect I need to do a little string sanitizing to fix this weird bug. I am taking a look at it after I post this. (On the plus side, doing it this way allows me to quickly verify that I got the path position calculations right, since it's easy and obvious if the stroke is not in precisely the right place, outlining each character.)
Finally, just as a brief aside,
kovidgoyal, thanks for all the time and effort you put into Calibre and helping us!