-
Notifications
You must be signed in to change notification settings - Fork 366
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
SVG text is rendered as shapes instead of glyphs #475
Comments
Are you generating the SVG using the Graphics2D->SVG Batik Adapter? Then the solution should be easy. The underlying problem is, that Batik always renders text as GlyphVectors, i.e. vector shapes. And Batik is used here to render the SVG. So as soon as you are rendering a SVG you have simply lost regarding the text. It will always be a vector shape, which not only is not searchable/selectable but also bloats your PDF massively. The PdfBoxGraphics2D adapter always gets to draws vector shapes and never sees any text in this case. So if your rendering code uses a Graphics2D you can simply use the ObjectDrawer's. There is no wiki page for that yet, but the openhtmltopdf-objects package contains some samples. An ObjectDrawer looks like: A usage example is here (look for You have then to register the object drawer. To do so you must provide a ObjectDrawerFactory. E.g.
This is an example from a commercial project of mine. You can then use this in the HTML like:
While generating the HTML for the PDF for each custom-drawn Graphics2D a unique ID is generated and put into contentid inside the HTML. And the When you then generate the PDF from the HTML the renderer are called at the right spots and get a PdfBoxGraphics2D as Graphics2D. You could even customize the PdfBoxGraphics2D settings if you wish, you just have to cast the given graphics context. The Hope this helps. |
Thanks a lot for this immediate and in-depth answer. I am going to dig into this now. Yes, when I found pdfbox-graphics2d my original approach was to use Java Swing components and layout managers to arrange everything text-related and draw graph connections on top. I had been using Swing a lot around the Millenium and until the midth of the first decade, so it felt a little like coming home. The Swing layout managers are very powerful and it saved a lot of work in my current project. So thanks for realizing pdfbox-graphics2d. Maybe I would have stuck to "printing" customized Swing components onto Graphics2D, but then I would have had to implement page breaking, page numbering, linking and calculating referenced page numbers myself. OpenHtmlToPDF is a great answer to that. But linking is the one thing I still will have to solve with the Graphics2D approach. JLabel is able to render HTML snippets to a Graphics context, but I would not expect hyper links and anchors to be rendered to PDF properly annotated. Or is the ObjectDrawer capable of even that? The plan I have on my mind is to generate customized Swing components with links through a factory and render the active links in a second layer based on the component locations I can determine via SwingUtilities.convertRectangle. That said, until I have tried, I am unsure whether I will be able to realize a multi-layer approach with the ObjectDrawer. Do you think, there is a simpler solution? Thanks again. On to coding this... |
Using Swing components in print is "interesting". But if it works, why not. The drawObject() method of the FSObjectDrawer interface returns a Map<Shape, String>. This map if not null can contain shapes and their target URLs. That can be something like "#section1" to reference |
Thanks for pointing that out. I am already breeding over this part of the code. I hope I'll be able to share some of my results here later, but for this I would first have to generate some data without confidential information. I know, using Swing components for this sounds awkward at first, but it is really useful to have some LayoutManagers ready instead of having to implement all on your own, especially if some absolute positioning is required, which is harder to do in HTML. I am rendering the nodes as JPanels, arranging them according to a customized Sugiyama algorithm and drawing the links on a layer on top. The things most important in that approach are mapping fonts correctly between the UIManager and PDF and setting all containers to non-opaque by default. I still have some problems with clipped texts under some specific conditions, but that is another issue. Back to ObjectDrawer now... |
Ok, my ObjectDrawer is never called.
|
Stupid question, but you registered the ObjectDrawer correctly in the factory and also set the factory into the |
Paves way for text as glyphs rather than vectors in SVG output once Batik is fixed with drawGlyphVectorWorks resolving to false on platforms later than JRE 1.5.
I investigated the source of this issue. It turns out as well as issues in our code which I think I have addressed in 670a386, that Batik has a test as to whether it will use glyphs or vectors: // This is true if GlyphVector.getGlyphOutline returns glyph outlines
// that are positioned (if it is false the outlines are always at 0,0).
private static final boolean outlinesPositioned;
// This is true if Graphics2D.drawGlyphVector works for the
// current JDK/OS combination.
private static final boolean drawGlyphVectorWorks;
// This is true if Graphics2D.drawGlyphVector will correctly
// render Glyph Vectors with per glyph transforms.
private static final boolean glyphVectorTransformWorks;
static {
String s = System.getProperty("java.specification.version");
if ("1.6".compareTo(s) <= 0) {
outlinesPositioned = true;
drawGlyphVectorWorks = false; // [GA] not verified; needs further research
glyphVectorTransformWorks = true;
} else if ("1.4".compareTo(s) <= 0) {
// TODO Java 5
outlinesPositioned = true;
drawGlyphVectorWorks = true;
glyphVectorTransformWorks = true;
} else if (Platform.isOSX) {
outlinesPositioned = true;
drawGlyphVectorWorks = false;
glyphVectorTransformWorks = false;
} else {
outlinesPositioned = false;
drawGlyphVectorWorks = true;
glyphVectorTransformWorks = false;
}
} This test turns off glyph rendering in Java 1.6 and above and uses vectors instead. We could perhaps file an issue with Batik to at least make it configurable. |
As part of work on #472, I noticed I couldn't get custom object drawer to work unless it was in its own layer. Could you try forcing a layer using the <div class="page">
<a name="585-graph">
585-graph
</a>
<div style="position: relative;width:100%;height:100%;-fs-page-break-min-height:800px">
<object type="custom/decisiontreegraph" treeId="585" title="585" style="width:100%;height:100%;position: absolute;">
</div>
</object>
</div> |
Yes. The fact that
First success: , drawObject is invoked now. But nothing becomes visible in the end result. This is how I am drawing the component to the grahics. Sorry, I hope you have no hard time reading Scala. class DecisionTreeObjectDrawer(decisionTreePanels: Map[String, DecisionTreeGraphPanel]) extends FSObjectDrawer with LazyLogging {
override def drawObject(
e: Element,
x: Double,
y: Double,
width: Double,
height: Double,
outputDevice: OutputDevice,
ctx: RenderingContext,
dotsPerPixel: Int
): util.Map[Shape, String] = {
logger.info("drawing tree '{}'", e.getAttribute("treeId"))
Option(e.getAttribute("treeId"))
.flatMap(decisionTreePanels.get)
.map { dtPanel =>
val dotWidth: Float = (width / dotsPerPixel).toFloat
val dotHeight: Float = (height / dotsPerPixel).toFloat
outputDevice.drawWithGraphics(x.toFloat, y.toFloat, dotWidth, dotHeight, (g2d: Graphics2D) => {
SwingHelper.layoutComponent(dtPanel)
val dim = dtPanel.getPreferredSize
val scaleToFitPage = Math.min(dim.getWidth / dim.width, (dim.getHeight - 50) / dim.height)
g2d.scale(scaleToFitPage, scaleToFitPage)
val crp = new CellRendererPane()
SwingUtilities.paintComponent(g2d, dtPanel, crp, 0, 0,dotWidth.toInt, dotHeight.toInt)
})
Map.empty[Shape, String].asJava // not returning links, yet
} match {
case Some(shapeLinks) => shapeLinks
case None =>
throw new Exception(s"Object element in HTML without valid treeId attribute value: $e")
}
}
} I guess, now |
This is just guessing on my side, but:
The swing components may not handle very small width/height correctly, as they only operate on integers. So scaling the component size up may help here. On the other side by scaling the g2d down this scaling is negated. I always use this if I need to draw something using an integer only API. |
As a quick test I have implemented it as follows, but the page remains empty.
I've checked that the g2d does not have a Clip set, so that is not the cause either. Next I am going to try to set an absolute Pixel height in the outer div. |
THAT is it. If I set an absolute pixel height in the outer DIV the content is drawn.
This is comparable to the usual vertical centering hassle in web pages. @danfickle and the wrapping div is not required, if an absolute height is set directly for the object,
This works, too. So having figured this out, what are your suggestions regarding best practice for realizing object rendering in combination with paging? One way is obviously to explicitly define a container DIV explicitly sized to the printable area, but then we lose automatic page distribution for large objects, don't we? And that could mean, the whole page has to be layed out explicitly. Do you see any way around this? |
Do you have an estimate how much content is in your graph? I usually try to estimate the height as much as possible, i.e. calculating the height based on the data. Within a normal page what would 100% mean? The whole page? Or the size of the whole document spanning multiple pages? What should be the maximum here? How many pages should this graph span? You have to somehow set a guideline how height your content should be. Width 100% is no problem, as the page just has its maximum width. I would also suggest to use cm or in (depending on the metrics you use) to specify the height. |
I know the extents of the graph exactly from the following step:
This invokes the LayoutManagers in the containment hierarchy recursively and returns the resulting preferred size of outermost container. This mechanism is the reason, why I chose to use Swing Layouts in the first place. That way I can use GridBagLayout, FlowLayout, BorderLayout, BoxLayout, BorderFactory and so on and get to query pixel exact positions of contained components using the standard From the graph size I can obviously calculate a regular tile split of the graph, if I know the dimensions of the available space. But cutting a large image is not feasible for my use case, because graph nodes are not homgenously distributed over the rectangular bounds. A tree for example has a lot a free space around the trunk, but less around the leaves. And there are several more constraints. You would not want to cut straight through nodes and thus split labels. And you will want to keep strongly related neighbourships together. Some edges consequently have to cross several tiles. You get the idea. I am accounting for that with a custom layered graph layout that is easier to split over rectangles. But the resulting rectangles will be of varying sizes and edges to nodes off the same page will be represented by local proxy nodes displaying pyge references and hyperlinks. So, to calculate good splits of the graph and optimize the distribution of sub graphs over pages, I will have to know how much space is left on a page. I think, I will have to completely keep the graph section separate from the containing chapter and calculate a page separation myself. |
But I recognize all this is leading away from the original question. I consider my issue solved, even though this might still rise a few follow-up questions . To sum it up, for custom rendering involving selectable text:
This is a very powerful feature and absolutely worth its own Wiki page. Thank you very much für your support, the very quick and deep response and all in all for realizing this awesome library. |
@hbergmey does the provided solution explain how to solve this problem (of the issue explanation):
If yes i would love to get a link to a wiki or an example code how to make the svg text selectable, i am struggling with the same. |
You see the recipe in my last post and there are the relevant code snippets in the thread, too. If you render using Graphics2D, the text will remain selectable. I know, it is a bit lenghty, but I am sure you can figure out the important bits for you by reading this from top to bottom. |
I am rendering a document with embedded fonts and SVG. I have managed to generate, place and scale my SVG element correctly and the end result is looking nice. It is displayed with the correct font and is not rasterized but consists of vector shapes.
But unfortunately the literal text is lost. When I open the resulting PDF I can mark, copy and search for every other HTML text, but not for the SVG-Text. I amrendering quite complex graphs and I have to make sure, every node can be found by text searching its label.
Check my attached full example in Scala adapted from the standard red circle example. I am embedding Arial via @font-face using a custom loader.
TestSvgText.zip
I have used pdfbox-graphics2d before and searchable text rendering worked well. But since I have to support hyper linking and flexible page layouts I switched over to openhtmltopdf. I am very happy so far, but if I cannot enable searching of graph labels, it breaks the whole point of my solution.
I'd be happy to contribute to the project, if there's some implementation missing in openhtmltopdf. But I really don't know where to start. Apart from that, I am sure, I simply missing something simple, as the developer of pdfbox-graphics2d @rototor has actually contributed the SVG rendering code here. This makes me very confident, that there is a solution.
Thanks in advance for any kind help you can give me.
The text was updated successfully, but these errors were encountered: