I have an application that generates PDF reports (using JasperReports), however if I run my application on my development laptop the text fields have a slightly different size than when I generate the exact same report on the server. I eventually reduced the issue to the following code:
final Font font = Font.createFont(
Font.TRUETYPE_FONT,
MyTest.class.getResourceAsStream("/fonts/europa/Europa-Bold.otf")
).deriveFont(10f);
System.out.println(font);
System.out.println(font.getStringBounds(
"Text",
0,
4,
new FontRenderContext(null, true, true)
));
On my Laptop this prints:
java.awt.Font[family=Europa-Bold,name=Europa-Bold,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-9.90999,w=20.080002,h=12.669988]
On the server this prints:
java.awt.Font[family=Europa-Bold,name=Europa-Bold,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-7.6757812,w=20.06897,h=10.094452]
As you can see I actually ship the font file with the application, so I believe that there is no chance that both machines actually work with a different font.
I would have guessed that under these conditions the output of getStringBounds is system-independent. Obviously it is not. What could possibly cause the difference?
Disclaimer! : I'm not a font dev expert, just sharing my experience.
Yes, it's kind of native. Even new JavaFX web view for example, is depends on webkit.
If you dive into debugging for getStringBounds, you will realize it reaches a point, where font manager should decide to load a concreted font manager, where the class name is supposed to be system property sun.font.fontmanager.
Source code of sun.font.FontManagerFactory
...
private static final String DEFAULT_CLASS;
static {
if (FontUtilities.isWindows) {
DEFAULT_CLASS = "sun.awt.Win32FontManager";
} else if (FontUtilities.isMacOSX) {
DEFAULT_CLASS = "sun.font.CFontManager";
} else {
DEFAULT_CLASS = "sun.awt.X11FontManager";
}
}
...
public static synchronized FontManager getInstance() {
...
String fmClassName = System.getProperty("sun.font.fontmanager", DEFAULT_CLASS);
}
Those DEFAULT_CLASS values could validate your Obviously it is not. What could possibly cause the difference? mark.
The val for sun.font.fontmanager may be sun.awt.X11FontManager for some nix systems, but could be null for windows for instance, so the manager will be sun.awt.Win32FontManager.
Now each manager, may depends on vary underlying shaping/rendering engine/impl for sure(this may help).
Main reason could be the nature of fonts. As they are mostly vector stuffs. So based on platform/env, a rendered text could be bigger, or smaller. e.g., maybe windows apply desktop cleartype, and screen text size(DPI) on requested text rendering.
It seems, even if you have exactly two sun.awt.X11FontManager manager, the results will be vary. this may help too
If you just try out the sample code, on online compilers, you will face with vary results for sure.
Result of ideaone (https://ideone.com/AuQvMV), could not be happened, stderr has some interesting info
java.lang.UnsatisfiedLinkError: /opt/jdk/lib/libfontmanager.so: libfreetype.so.6: cannot open shared object file: No such file or directory
at java.base/java.lang.ClassLoader$NativeLibrary.load0(Native Method)
at java.base/java.lang.ClassLoader$NativeLibrary.load(ClassLoader.java:2430)
at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2487)
at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2684)
at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2638)
at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:827)
at java.base/java.lang.System.loadLibrary(System.java:1902)
at java.desktop/sun.font.FontManagerNativeLibrary$1.run(FontManagerNativeLibrary.java:57)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:310)
at java.desktop/sun.font.FontManagerNativeLibrary.<clinit>(FontManagerNativeLibrary.java:32)
at java.desktop/sun.font.SunFontManager$1.run(SunFontManager.java:270)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:310)
at java.desktop/sun.font.SunFontManager.<clinit>(SunFontManager.java:266)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:415)
at java.desktop/sun.font.FontManagerFactory$1.run(FontManagerFactory.java:82)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:310)
at java.desktop/sun.font.FontManagerFactory.getInstance(FontManagerFactory.java:74)
at java.desktop/java.awt.Font.getFont2D(Font.java:497)
at java.desktop/java.awt.Font.getFamily(Font.java:1410)
at java.desktop/java.awt.Font.getFamily_NoClientCode(Font.java:1384)
at java.desktop/java.awt.Font.getFamily(Font.java:1376)
at java.desktop/java.awt.Font.toString(Font.java:1869)
at java.base/java.lang.String.valueOf(String.java:3042)
at java.base/java.io.PrintStream.println(PrintStream.java:897)
at Ideone.main(Main.java:19)
Note the failed/missed load of libfreetype which is a native/C font rendering app
Result of coding ground (https://www.tutorialspoint.com/compile_java_online.php)
fnt manager: sun.awt.X11FontManager
java.awt.Font[family=Dialog,name=tahoma,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-9.282227,w=22.09961,h=11.640625]
Result of jdoodle (https://www.jdoodle.com/online-java-compiler/)
fnt manager: sun.awt.X11FontManager
java.awt.Font[family=Dialog,name=tahoma,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-9.839991,w=24.0,h=12.569988]
My machine
fnt manager: null
java.awt.Font[family=Tahoma,name=tahoma,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-10.004883,w=19.399414,h=12.0703125]
My Story (may help, you may try)
I had similar issue back in some years ago, where text rendering using exactly same embedded font failed on macOs/jdk8, for complex text rendering(lots of ligatures). Not just sizes, but also broken ligatures, kerning, etc...
I could fix my issue(cannot remember if fixed the sizing, but no broken ligatures for sure), using another workground, as following
InputStream is = Main.class.getResourceAsStream(fontFile);
Font newFont = Font.createFont(Font.TRUETYPE_FONT, is);
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(newFont);
//later load the font by constructing a Font ins
Font f = new Font(name/*name of the embedded font*/, style, size);
Registering font using GraphicsEnvironment, and then instancing it using Font fixed our issue. So you may also give it a try.
Solution
Finally, I just step-down the jdk stuffs(it's really great pain in neck) for good, and came up with harfbuzz(shaping) + freetype(rendering) native impl that indeed was a peace in mind.
So...
• You may consider your production server(easy way) as reference for rendered font advance and rendering, and validate the result based on it(rather than dev machine)
• Or, use a cross and standalone (and probably native) shaping/rendering font engine/impl to make sure dev and production results will be the same.
Related
In a Nutshell
I've been working on a program that gets a pdf, highlights some words (via pdfbox Mark Annotation) and saves the new pdf.
In order to these annotations be visible on some viewers like pdf.js, it's needed to call the pdAnnotationTextMarkup.constructAppearances() before adding the mark annotation into the page Annotation list.
However, by doing so, I get an OutOfMemoryError when dealing with huge documents that would contain thousands of mark annotations.
I'd like to know if there's a way to prevent this from happening.
(this is a kind of a sequel of this ticket, but that's not much relevant for this one)
Technical Specification:
PDFBox 2.0.17
Java 11.0.6+10, AdoptOpenJDK
MacOS Catalina 10.15.2, 16gb, x86_64
My Code
//my pdf has 216 pages
for (int pageIndex = 0; pageIndex < numberOfPages; pageIndex++) {
PDPage page = document.getPage(pageIndex);
List<PDAnnotation> annotations = page.getAnnotations();
// each coordinate obj represents a hl annotation. crashing with 7.816 elements
for (CoordinatePoint coordinate : coordinates) {
PDAnnotationTextMarkup txtMark = new PDAnnotationTextMarkup(PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT);
txtMark.setRectangle(pdRectangle);
txtMark.setQuadPoints(quadPoints);
txtMark.setColor(getColor());
txtMark.setTitlePopup(coordinate.getHintDescription());
txtMark.setReadOnly(true);
// this is what makes everything visible on pdf.js and what causes the Java heap space error
txtMark.constructAppearances();
annotations.add(txtMark);
}
}
Current Result
This is the heavy pdf doc that is leading to the issue:
https://pdfhost.io/v/I~nu~.6G_French_Intensive_Care_Society_International_congress_Ranimation_2016.pdf
My program tries to add 7.816 annotations to it throughout 216 pages.
and the stacktrace:
[main] INFO highlight.PDFAnnotation - Highlighting 13613_2016_Article_114.pdf...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at org.apache.pdfbox.io.ScratchFile.<init>(ScratchFile.java:128)
at org.apache.pdfbox.io.ScratchFile.getMainMemoryOnlyInstance(ScratchFile.java:143)
at org.apache.pdfbox.cos.COSStream.<init>(COSStream.java:61)
at org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDAbstractAppearanceHandler.createCOSStream(PDAbstractAppearanceHandler.java:106)
at org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDHighlightAppearanceHandler.generateNormalAppearance(PDHighlightAppearanceHandler.java:136)
at org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDHighlightAppearanceHandler.generateAppearanceStreams(PDHighlightAppearanceHandler.java:59)
at org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup.constructAppearances(PDAnnotationTextMarkup.java:175)
at org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup.constructAppearances(PDAnnotationTextMarkup.java:147)
at highlight.PDFAnnotation.drawHLAnnotations(PDFAnnotation.java:288)
I've already tried to increase my jvm xmx and xms parameters to like -Xmx10g -Xms10g, which only postponed the crash a little bit.
What I Want
I want to prevent this memory issue and still be able to see my annotations in pdf.js viewer. Without calling constructAppearances the process is much more faster, I don't have this issue, but the annotations can only be seen on some pdf viewers, like Adobe.
Any suggestions? Am I doing anything wrong here or missing something?
In the upcoming version 2.0.19, construct the appearances like this:
annotation.constructAppearances(document);
In 2.0.18 and earlier, you need to initialize the appearance handler yourself:
setCustomAppearanceHandler(new PDHighlightAppearanceHandler(annotation, document));
That line can be removed in 2.0.19 as this is the default appearance handler.
Why all this? So that the document common memory space ("scratch file") is used in the annotation handler instead to create a new one each time (which is big). The later is done when calling new COSStream() instead of document.getDocument().createCOSStream().
All this is of course only important when doing many annotations.
related PDFBox issues: PDFBOX-4772 and PDFBOX-4080
I need to remove some content from an existing pdf created with Jasper Reports in iText 5.5.11 but after running PdfCleanUpProcessor all bold text is blurry.
This is the code I'm using:
PdfReader reader = new PdfReader("input.pdf");
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("output.pdf"));
List<PdfCleanUpLocation> cleanUpLocations = new ArrayList<PdfCleanUpLocation>();
cleanUpLocations.add(new PdfCleanUpLocation(1, new Rectangle(0f, 0f, 595f, 680f)));
PdfCleanUpProcessor cleaner = new PdfCleanUpProcessor(cleanUpLocations, stamper);
cleaner.cleanUp();
stamper.close();
reader.close();
As already discussed here downgrading to itext-5.5.4 solves the problem, but in my case itext-5.5.11 is already in use for other reasons and so downgrading is not an option.
Is there another solution or workaround?
This are the pdf files before and after cleaning: BEFORE - AFTER
By comparing the before and after files it becomes clear that for some reason the PdfCleanUpProcessor falsely drops general graphics state operations (at least w, J, and d).
In your before document in particular the w operation is important for the text because a poor man's bold variant is used, i.e. instead of using an actual bold font the normal font is used and the text rendering mode is set to not only fill the glyph contours but also draw a line along it giving it a bold'ish appearance.
The width of that line is set to 0.23333 using a w operation. As that operation is missing in the after document, the default width value of 1 is used. Thus, the line along the contour now is 4 times as big as before resulting in a very fat appearance.
This issue has been introduced in commit d5abd23 (dated May 4th, 2015) which (among other things) added this block to PdfCleanUpContentOperator.invoke:
} else if (lineStyleOperators.contains(operatorStr)) {
if ("w" == operatorStr) {
cleanUpStrategy.getContext().setLineWidth(((PdfNumber) operands.get(0)).floatValue());
} else if ("J" == operatorStr) {
cleanUpStrategy.getContext().setLineCapStyle(((PdfNumber) operands.get(0)).intValue());
} else if ("j" == operatorStr) {
cleanUpStrategy.getContext().setLineJoinStyle(((PdfNumber) operands.get(0)).intValue());
} else if ("M" == operatorStr) {
cleanUpStrategy.getContext().setMiterLimit(((PdfNumber) operands.get(0)).floatValue());
} else if ("d" == operatorStr) {
cleanUpStrategy.getContext().setLineDashPattern(new LineDashPattern(((PdfArray) operands.get(0)),
((PdfNumber) operands.get(1)).floatValue()));
}
disableOutput = true;
This causes all lineStyleOperators to be dropped while at the same time an attempt was made to store the changed values in the cleanup strategy context. But of course using == for String comparisons in Java usually is a very bad idea, so since this version the line style operators were dropped for good in iText.
Actually this code had been ported from iTextSharp, and in C# == for the string type works entirely different; nonetheless, even in the iTextSharp version these stored values at first glance only seem to have been taken into account if paths were stroked, not if text rendering included stroking along the contour.
Later on in commit 9967627 (on the same day as the commit above) the inner if..else if..else.. has been removed with the comment Replaced PdfCleanUpGraphicsState with existing GraphicsState from itext.pdf.parser package, added missing parameters into the latter, only the disableOutput = true remained. This (also at first glance) appears to have fixed the difference between iText/Java and iTextSharp/.Net, but the line style values still are not considered if text rendering included stroking along the contour.
As a work-around consider removing the lines
} else if (lineStyleOperators.contains(operatorStr)) {
disableOutput = true;
from PdfCleanUpContentOperator.invoke. Now the line style operators are not dropped anymore and the text in your PDF after redaction looks like before. I have not checked for any side effects, though, so please test with a number of documents before even considering using that work-around in production.
I want to know whether the user launched our Java-based application from a read-only file system like from a .dmg, so functions like auto-update will be able to show meaningful information instead of aborting with an error. I first thought checking the .app's path would be sufficient (when launched from a .dmg it is something like /Volumes/MyApp 1.2.3/MyApp.app, but this won't work, because the user might have installed the application on a different partition. What other things may I check?
You can use -[NSURL getResourceValue:forKey:error:] with the key NSURLVolumeIsReadOnlyKey. You would apply this to the app bundle URL as returned by [[NSBundle mainBundle] bundleURL]. So:
NSBundle* bundle = [NSBundle mainBundle];
NSURL* bundleURL = bundle.bundleURL;
NSNumber* readOnly;
NSError* error;
if ([bundleURL getResourceValue:&readOnly forKey:NSURLVolumeIsReadOnlyKey error:&error])
{
BOOL isReadOnly = [readOnly boolValue];
// act on isReadOnly value
}
else
{
// Handle error
}
If OSX is POSIX compliant, to determine if filesystem is mounted R/O, You can use statvfs() or fstatvfs(), returned struct statvfs field f_flag should have ST_RDONLY bit set for R/O filesystem.
As it was pointed in comments, check if this information is correctly provided by OS.
JNA and this question may be usefull for Java.
A few more ideas, which may be usefull here (access(), open(), utime() ).
OS X specific statfs() may be used too, but this function is not portable (Linux and *BSD have slightly different statfs() functions).
You can also check directly from Java whether a certain path points to something within a read-only directory by querying the FileStore associated with your path:
File classpathRoot = new File(MyClass.class.getClassLoader().getResource("").getPath());
/* getPath() actually returns a String instead of a Path object,
* so we need to take this little detour */
Path yourAppPath = classpathRoot.toPath();
boolean isReadOnly = Files.getFileStore(yourAppPath).isReadOnly();
As background, I realize there are about 5 other posts on Stack Overflow about this, bur I've looked at the responses and researched this for countless hours with no real solution. Otherwise I wouldn't have posted here.
I'm new to JavaFX, and I like Clojure, so I'm using chrisx's clj-javafx project from Github as a Clojure wrapper for JavaFX. Most operations work great, such as placing components on the scene/stage and styling them with CSS. There's just one issue: I want to import a custom font, and so far I haven't been able to.
I've tried multiple methods. The Java version of the first thing I tried is:
Font.loadFont(<myClass>.class.getResource("mpsesb.ttf").toExternalForm(), 14);
I tried to do that in Clojure using Java interop but I'm not sure how Clojure handles Java classes. Instead I used clojure.java.io.resource:
(use '[clojure.java.io :only [resource]])
(resource "mpsesb.tff")
And it prints the URL of the .ttf file just fine.
Then I tried to use that URL and use it as a parameter in the Font.loadFont method, which didn't work. After playing around with seriously 100 permutations of dot operators and Java methods and classes, I got the following to not print any errors, unlike the other combinations I tried:
(Font/loadFont "fonts/ttf/mpsesb.ttf")
But it just returns nil, which according to the JavaFX API means the font didn't actually load. I even tried fake pathnames and they also returned nil without errors, so the Font/loadFont function seems less promising than I thought.
I finally tried using CSS to load the font instead of using Java to import it:
#font-face {
font-family: 'MyrProSemiExtBold';
src: url('file:/Users/<restOfPath>/mpsesb.ttf');
}
.label {
-fx-font-size: 14px;
-fx-font-weight: normal;
-fx-font-family: 'MyrProSemiExtBold';
}
But no luck. The labels just show up as the default font (Lucida Grande) at 14 pt.
Thank you ahead of time for any suggestions you may have.
EDIT:
Thanks, #jewelsea. The first thing I did after reading what you wrote is get Java 8 + JDK 8 (build b101). I'm using Sublime Text, so I got SublimeREPL to recognize JavaFX using Maven like so in Terminal:
$ mvn deploy:deploy-file -DgroupId=local.oracle -DartifactId=javafxrt -Dversion=8.0 -Dpackaging=jar -Dfile=/System/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/ext/jfxrt.jar -Durl=file:/Users/vaniificat/.m2/repository
Most of the JavaFX-referent code worked, but now the whole javafx.scene.control package doesn't work, which means no Labels, no Buttons, no TextFields, etc.... :
> (import javafx.scene.control.Button)
: NoClassDefFoundError Could not initialize class javafx.scene.control.Button
java.lang.Class.forName0 (Class.java:-2)
> (import javafx.scene.control.Label)
: NoClassDefFoundError javafx.scene.control.Labeled
java.lang.Class.forName0 (Class.java:-2)
> (import javafx.scene.control.TextField)
: NoClassDefFoundError javafx.scene.control.Control
java.lang.Class.forName0 (Class.java:-2)
> (import '(javafx.scene.control Button Label PasswordField TextField))
: NoClassDefFoundError Could not initialize class javafx.scene.control.Button
java.lang.Class.forName0 (Class.java:-2)
A possible reference to these errors may be found at https://forums.oracle.com/thread/2526150. Basically the gist is that JavaFX 8 is only in beta so we can't expect it to work 100%. Hmm!
I opened an issue on JIRA: https://bugs.openjdk.java.net/browse/JDK-8093894. Once this gets addressed I can more fully explore #jewelsea 's other suggestions.
These are mostly suggestions rather than definitive solutions
On #font-face
Use Java 8 if you can. It has support for the #font-face css notation (which earlier versions of JavaFX such as 2.2 do not). It also has a revamped font system which might be a bit more robust. Because the font face can be loaded via css in Java 8, you may not need to solve your loading resources relative to the class location issue.
On Font.loadFont
You should be able to use the Font.loadFont method as you are trying to use it. JavaFX 2.x has supported custom fonts from it's initial release. There are some steps for loading fonts in JavaFX in plain Java: How to embed .ttf fonts is JavaFx 2.2?. I know you have already looked at that and it hasn't helped - just including it here for reference so that somebody who really knows clojure but not JavaFX may help to troublehoot the issue.
Check that your target font is compatible
Double-check that the particular font that you are using loads and is used in a regular Java based JavaFX application (just to ensure that there is not something incompatible between the font file and the JavaFX system on your platform).
You may need further help
I don't know how loading relative resources works in clojure either (or maybe there is some other issue with your code) - but perhaps a clojure expert can post another answer that allows it to work. You may want to edit your question to include an sscce.
Update: As was pointed out, the original answer did not directly answer the OP's question. I've created a blog post about the original technique in case anyone is interested.
Here is a short program that does what you want.
(ns demo.core
(:gen-class
:extends javafx.application.Application)
(:import
[javafx.application Application]
[javafx.event EventHandler]
[javafx.scene Scene]
[javafx.scene.control Button]
[javafx.scene.layout StackPane]
[javafx.scene.text Font])
(:require [clojure.java.io :as jio]))
(defn- get-font-from-resource
"Load the named font from a resource."
[font-name]
(let [prefix "demo/resources/"
url (jio/resource (str prefix font-name))
fnt (Font/loadFont (.toExternalForm url) 20.0)]
fnt))
(defn -start
"Build the application interface and start it up."
[this stage]
(let [root (StackPane.)
scene (Scene. root 600 400)
fnt (get-font-from-resource "ITCBLKAD.TTF")
btn (Button. "Press Me!")]
(.setOnAction btn
(reify EventHandler
(handle [this event]
(doto btn
(.setText (str "This is " (.getName fnt)))
(.setFont fnt)))))
(.add (.getChildren root) btn)
(doto stage
(.setTitle "Font Loading Demo")
(.setScene scene)
(.show))))
(defn -main
[& args]
(Application/launch demo.core args))
In this project, I placed the font file in resources, a sub-directory of demo - where the Clojure source is stored - hence the "prefix" in the function get-font-from-resource.
It looks like the problem you might have been having with loadFont was in your conversion from the URL to the String form. The external form is an absolute path starting at the root directory on the drive.
Tip: You probably know this, but one thing that continually screws me up are methods in JavaFX that require double parameters, like Font/loadFont. I'm used to Java just promoting integer arguments to double. In Clojure, if you use an integer where a double is required, the program fails with a less than useful error message.
I need programmatically extract frames from mp4 video file, so each frame goes into a separate file. Please advise on a library that will allow to get result similar to the following VLC command (http://www.videolan.org/vlc/):
vlc v1.mp4 --video-filter=scene --vout=dummy --start-time=1 --stop-time=5 --scene-ratio=1 --scene-prefix=img- --scene-path=./images vlc://quit
Library for any of these Java / Python / Erlang / Haskell will do the job for me.
Consider using the following class by Popscan. The usage is as follows:
VideoSource vs = new VideoSource("file://c:\test.avi");
vs.initialize();
...
int frameIndex = 12345; // any frame
BufferedImage frame = vs.getFrame(frameIndex);
I would personally look for libraries that wrap ffmpeg/libavcodec (the understands-most-things library that many encoders and players use).
I've not really tried any yet so can't say anything about code quality and ease, but the five-line pyffmpeg example suggests it's an easy option - though it may well be *nix-only.