Skip to content
This repository has been archived by the owner on Jan 2, 2023. It is now read-only.

cannot use OpenType fonts if they are installed locally on Windows and same font is loaded via WOFF #1620

Closed
rainabba opened this issue Apr 4, 2014 · 16 comments

Comments

@rainabba
Copy link

rainabba commented Apr 4, 2014

My development environment was working great but when I ran the EXACT same Node.js app using wkhtmltox-win64_0.12.0-03c001d.exe to generate PDFs on my production machine, the PDFs I got were effectively blank. There were hints of structure though so I suspected it was a font issue.

In the end, I found that my workstation started doing the exact same thing right after I installed the fonts locally (c:\windows\fonts) so I uninstalled the fonts locally and the issue went away. When I uninstalled on the production machine (Windows Server 2012 R2), the issue also resolved itself there.

My pages are using .woff (built from the same .ttf files that were installed using the same name) included through CSS. It didn't matter if the declaration was made on the page in the critical rendering path or if it was a linked CSS file. It also didn't matter if I used externally hosted files or local to my site (I tested all this in the hopes something would resolve the issue).

I'm going to test further to see if I can name the local fonts differently without an issue but I can't imagine why this would create a problem. If anything, I'd think that having the fonts installed locally should be better and if such a thing is problematic, why isn't it for default fonts like Arial?

If I can provide any more info to help, please let me know what.

The following sound EXACTLY like my issue though they're all unresolved so I wonder if it is related or not:

https://code.google.com/p/wkhtmltopdf/issues/detail?id=688

@ashkulz
Copy link
Member

ashkulz commented Apr 5, 2014

Can you post your HTML/CSS as a gist?

@rainabba
Copy link
Author

rainabba commented Apr 5, 2014

Pick your flavor. https://www.google.com/fonts#UsePlace:use/Collection:Roboto

The only variable that makes any difference is whether the same font (literally, the .ttfs provided by Google from the same site) exists in c:\Windows\Fonts\ or not. I've confirmed this on Windows 8.1 Enterprise and Server 2012 R2.

@ashkulz
Copy link
Member

ashkulz commented Apr 5, 2014

Woff from google fonts does not work due to css hacks (wkhtmltopdf does not load fallback urls) so I'd prefer a simpler test case.

@rainabba
Copy link
Author

rainabba commented Apr 5, 2014

I would have to disagree because that's ALL I use (and so far, JUST Roboto) and it works fine (though still with SOME kerning issues) unless i also have the .ttf installed in c:\windows\fonts.

In fact, it works better than fine. I HAD to do it because of the letter-spacing bug in wkhtmltopdf. My output was otherwise unreadable and the only reason I am using custom fonts was a desperate idea to find a solution to:

https://code.google.com/p/wkhtmltopdf/issues/detail?id=72
https://code.google.com/p/wkhtmltopdf/issues/detail?id=138
http://stackoverflow.com/questions/10829209/letter-spacing-issue-when-converting-html-to-pdf-using-wkhtmltopdf-through-pdfki
http://stackoverflow.com/questions/12009670/html-to-pdf-with-wicked-pdf-and-letter-spacing-issues
#45
http://unix.stackexchange.com/questions/79755/font-letter-spacing-issue-with-phantomjs-wkhtmltopdf-on-ubuntu
etc.........

@rainabba
Copy link
Author

rainabba commented Apr 5, 2014

Here's full test case with code that demonstrates that WKHTMLPDF 1.12 can and will use Google-hosted .woff files just fine: https://gist.github.com/rainabba/9996443 it includes a link to the following PDF which was created with WKHTMLTOPDF: http://goo.gl/qoc47N

@brmartins
Copy link

@ashkulz , hi friend, i have a grand problem, i am need to convert a file for pdf, but i have a page in PHP that i would like to convert in PDF, whiout the necessary to create at hand because are 60 pages.

@ashkulz
Copy link
Member

ashkulz commented Apr 30, 2014

@brmartins: please post on StackOverflow or the mailing list if you need help on how to use wkhtmltopdf.

@ashkulz
Copy link
Member

ashkulz commented Jul 2, 2014

@rainabba: when the font is installed on your Windows machine, is it an OpenType font?

@ashkulz
Copy link
Member

ashkulz commented Jul 2, 2014

I guess so, as the default Roboto font has OpenType layout:
roboto-opentype
It looks like the following bugs in Qt (which have a patch) are related to it:

QTBUG-12513  Incorrect glyphs printed from OpenType PS font when printing on Windows
QTBUG-11387  Kerning with 'kern' table fails for OpenType PS fonts on Windows
QTBUG-10089  OpenType font embedding in PostScript / PDF

@ashkulz ashkulz changed the title Cannot use Google Roboto font when it's installed on the Windows Machine running WKHTMLTOPDF 1.12 cannot use OpenType fonts if they are installed locally on Windows and same font is loaded via WOFF Jul 2, 2014
@ashkulz ashkulz added this to the 0.12.2 milestone Jul 2, 2014
@rainabba
Copy link
Author

rainabba commented Jul 2, 2014

It is installed as an OpenType font and I'm seeing at least 2 of those 3
bugs. font-weight and kerning both misbehave in wkhtmltopdf, but not when I
"print to pdf" using the Adobe print driver from other applications such as
FireFox.

@ashkulz
Copy link
Member

ashkulz commented Jul 3, 2014

Looks like the patch attached to QTBUG-11387 does not help, I tried using

diff --git a/src/gui/text/qfontengine_win.cpp b/src/gui/text/qfontengine_win.cpp
index 66a741c..2381241 100644
--- a/src/gui/text/qfontengine_win.cpp
+++ b/src/gui/text/qfontengine_win.cpp
@@ -196,12 +196,14 @@ void QFontEngineWin::getCMap()
     HDC hdc = shared_dc();
     SelectObject(hdc, hfont);
     bool symb = false;
-    if (ttf) {
-        cmapTable = getSfntTable(qbswap<quint32>(MAKE_TAG('c', 'm', 'a', 'p')));
-        int size = 0;
-        cmap = QFontEngine::getCMap(reinterpret_cast<const uchar *>(cmapTable.constData()),
-                       cmapTable.size(), &symb, &size);
-    }
+
+    // Need to retrieve cmap table for OpenType PS as well as TTF fonts - these share the sfnt format.
+    // This should fail safely if the font doesn't have a cmap.
+    cmapTable = getSfntTable(qbswap<quint32>(MAKE_TAG('c', 'm', 'a', 'p')));
+    int size = 0;
+    cmap = QFontEngine::getCMap(reinterpret_cast<const uchar *>(cmapTable.constData()),
+                   cmapTable.size(), &symb, &size);
+
     if (!cmap) {
         ttf = false;
         symb = false;
@@ -243,14 +245,14 @@ int QFontEngineWin::getGlyphIndexes(const QChar *str, int numChars, QGlyphLayout
 #if defined(Q_WS_WINCE)
         {
 #else
-        if (symbol) {
+        if (symbol && cmap) {
             for (; i < numChars; ++i, ++glyph_pos) {
                 unsigned int uc = getChar(str, i, numChars);
                 glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc);
                 if (!glyphs->glyphs[glyph_pos] && uc < 0x100)
                     glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc + 0xf000);
             }
-        } else if (ttf) {
+        } else if (cmap) {
             for (; i < numChars; ++i, ++glyph_pos) {
                 unsigned int uc = getChar(str, i, numChars);
                 glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, QChar::mirroredChar(uc));
@@ -276,14 +278,14 @@ int QFontEngineWin::getGlyphIndexes(const QChar *str, int numChars, QGlyphLayout
 #if defined(Q_WS_WINCE)
         {
 #else
-        if (symbol) {
+        if (symbol && cmap) {
             for (; i < numChars; ++i, ++glyph_pos) {
                 unsigned int uc = getChar(str, i, numChars);
                 glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc);
                 if(!glyphs->glyphs[glyph_pos] && uc < 0x100)
                     glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc + 0xf000);
             }
-        } else if (ttf) {
+        } else if (cmap) {
             for (; i < numChars; ++i, ++glyph_pos) {
                 unsigned int uc = getChar(str, i, numChars);
                 glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc);
@@ -407,7 +409,7 @@ void QFontEngineWin::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFla
 {
     HGDIOBJ oldFont = 0;
     HDC hdc = shared_dc();
-    if (ttf && (flags & QTextEngine::DesignMetrics)) {
+    if (cmap && (flags & QTextEngine::DesignMetrics)) {
         for(int i = 0; i < glyphs->numGlyphs; i++) {
             unsigned int glyph = glyphs->glyphs[i];
             if(int(glyph) >= designAdvancesSize) {
@@ -451,7 +453,7 @@ void QFontEngineWin::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFla
                 if (!oldFont)
                     oldFont = SelectObject(hdc, hfont);

-                if (!ttf) {
+                if (!cmap) {
                     QChar ch[2] = { ushort(glyph), 0 };
                     int chrLen = 1;
                     if (glyph > 0xffff) {
@@ -486,7 +488,7 @@ glyph_metrics_t QFontEngineWin::boundingBox(const QGlyphLayout &glyphs)
     for (int i = 0; i < glyphs.numGlyphs; ++i)
         w += glyphs.effectiveAdvance(i);

-    return glyph_metrics_t(0, -tm.tmAscent, w - lastRightBearing(glyphs), tm.tmHeight, w, 0);
+    return glyph_metrics_t(0, -ascent(), w - lastRightBearing(glyphs), tm.tmHeight, w, 0);
 }

 #ifndef Q_WS_WINCE
@@ -521,7 +523,7 @@ bool QFontEngineWin::getOutlineMetrics(glyph_t glyph, const QTransform &t, glyph
     }

     uint format = GGO_METRICS;
-    if (ttf)
+    if (cmap)
         format |= GGO_GLYPH_INDEX;
     res = GetGlyphOutline(hdc, glyph, format, &gm, 0, 0, &mat);

@@ -559,7 +561,7 @@ glyph_metrics_t QFontEngineWin::boundingBox(glyph_t glyph, const QTransform &t)
         GetCharABCWidthsFloat(hdc, ch, ch, &abc);
         int width = qRound(abc.abcfB);

-        return glyph_metrics_t(QFixed::fromReal(abc.abcfA), -tm.tmAscent, width, tm.tmHeight, width, 0).transformed(t);
+        return glyph_metrics_t(QFixed::fromReal(abc.abcfA), -ascent(), width, tm.tmHeight, width, 0).transformed(t);
     }

     return glyphMetrics;
@@ -589,7 +591,7 @@ glyph_metrics_t QFontEngineWin::boundingBox(glyph_t glyph, const QTransform &t)
     }

     SelectObject(hdc, oldFont);
-    return glyph_metrics_t(0, -tm.tmAscent, width, tm.tmHeight, advance, 0).transformed(t);
+    return glyph_metrics_t(0, -ascent(), width, tm.tmHeight, advance, 0).transformed(t);
 #endif
 }

@@ -802,7 +804,7 @@ bool QFontEngineWin::canRender(const QChar *string,  int len)
                 }
             }
         }
-    } else if (ttf) {
+    } else if (cmap) {
         for (int i = 0; i < len; ++i) {
             unsigned int uc = getChar(string, i, len);
             if (getTrueTypeGlyphIndex(cmap, uc) == 0)
@@ -835,7 +837,7 @@ static inline QPointF qt_to_qpointf(const POINTFX &pt, qreal scale) {
 #endif

 static bool addGlyphToPath(glyph_t glyph, const QFixedPoint &position, HDC hdc,
-                           QPainterPath *path, bool ttf, glyph_metrics_t *metric = 0, qreal scale = 1)
+                           QPainterPath *path, bool cmap, glyph_metrics_t *metric = 0, qreal scale = 1)
 {
 #if defined(Q_WS_WINCE)
     Q_UNUSED(glyph);
@@ -848,7 +850,7 @@ static bool addGlyphToPath(glyph_t glyph, const QFixedPoint &position, HDC hdc,
     mat.eM21.fract = mat.eM12.fract = 0;
     uint glyphFormat = GGO_NATIVE;

-    if (ttf)
+    if (cmap)
         glyphFormat |= GGO_GLYPH_INDEX;

     GLYPHMETRICS gMetric;
@@ -956,7 +958,7 @@ void QFontEngineWin::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, in
     HGDIOBJ oldfont = SelectObject(hdc, hf);

     for(int i = 0; i < nglyphs; ++i) {
-        if (!addGlyphToPath(glyphs[i], positions[i], hdc, path, ttf, /*metric*/0,
+        if (!addGlyphToPath(glyphs[i], positions[i], hdc, path, cmap, /*metric*/0,
                             qreal(fontDef.pixelSize) / unitsPerEm)) {
             // Some windows fonts, like "Modern", are vector stroke
             // fonts, which are reported as TMPF_VECTOR but do not
@@ -1000,7 +1002,7 @@ int QFontEngineWin::synthesized() const
 {
     if(synthesized_flags == -1) {
         synthesized_flags = 0;
-        if(ttf) {
+        if(cmap) {
             const DWORD HEAD = MAKE_TAG('h', 'e', 'a', 'd');
             HDC hdc = shared_dc();
             SelectObject(hdc, hfont);
@@ -1009,7 +1011,7 @@ int QFontEngineWin::synthesized() const
             USHORT macStyle = getUShort(data);
             if (tm.tmItalic && !(macStyle & 2))
                 synthesized_flags = SynthesizedItalic;
-            if (fontDef.stretch != 100 && ttf)
+            if (fontDef.stretch != 100)
                 synthesized_flags |= SynthesizedStretch;
             if (tm.tmWeight >= 500 && !(macStyle & 1))
                 synthesized_flags |= SynthesizedBold;
@@ -1066,14 +1068,14 @@ void QFontEngineWin::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_m
     QFixedPoint p;
     p.x = 0;
     p.y = 0;
-    addGlyphToPath(glyph, p, hdc, path, ttf, metrics);
+    addGlyphToPath(glyph, p, hdc, path, cmap, metrics);
     DeleteObject(SelectObject(hdc, oldfont));
 }

 bool QFontEngineWin::getSfntTableData(uint tag, uchar *buffer, uint *length) const
 {
-    if (!ttf)
-        return false;
+    // Need to retrieve tables for OpenType as well as TTF fonts - these share the sfnt format.
+    // This should fail safely if the font isn't hin sfnt format.
     HDC hdc = shared_dc();
     SelectObject(hdc, hfont);
     DWORD t = qbswap<quint32>(tag);
@@ -1106,7 +1108,7 @@ QNativeImage *QFontEngineWin::drawGDIGlyph(HFONT font, glyph_t glyph, int margin
     bool has_transformation = t.type() > QTransform::TxTranslate;

 #ifndef Q_WS_WINCE
-    unsigned int options = ttf ? ETO_GLYPH_INDEX : 0;
+    unsigned int options = cmap ? ETO_GLYPH_INDEX : 0;
     XFORM xform;

     if (has_transformation) {
@@ -1124,7 +1126,7 @@ QNativeImage *QFontEngineWin::drawGDIGlyph(HFONT font, glyph_t glyph, int margin
         SetWorldTransform(hdc, &xform);
         HGDIOBJ old_font = SelectObject(hdc, font);

-        int ggo_options = GGO_METRICS | (ttf ? GGO_GLYPH_INDEX : 0);
+        int ggo_options = GGO_METRICS | (cmap ? GGO_GLYPH_INDEX : 0);
         GLYPHMETRICS tgm;
         MAT2 mat;
         memset(&mat, 0, sizeof(mat));

So it looks like an issue with Qt itself. I'll investigate a bit further, but I don't think that I'll be able to come up with a solution on my own.

@ashkulz
Copy link
Member

ashkulz commented Jul 3, 2014

@rainabba: can you show screenshots of the kerning bug, so that I can figure out if a patch fixes the issue? For the font-weight not being recognized, your sample HTML comes out like this without it installed:

not-installed

and with the font installed it comes out as:

installed

@fcool
Copy link

fcool commented Sep 15, 2014

A very similar, (if not the same) issue happens on linux, if a font is installed locally:
With wkhtmltopdf 0.10 and unpatched qt (which has its own sets of problems of course) everything worked fine with locally (only) installed fonts.

Trying the same template with 0.12.1 and patched qt the bold or expaneded fonts won't get loaded. Even if using font-face and loading the font with url(path/to/font).

But after deinstalling the font from global font-directory, everything works as expected.

@ashkulz ashkulz modified the milestones: future, 0.12.2 Nov 14, 2014
@ashkulz
Copy link
Member

ashkulz commented Jan 9, 2015

Can you verify if the output is better with 0.13-alpha available from the downloads page?

@rainabba
Copy link
Author

The following screenshot show the same test code in Chrome 41 dev-m (looks as I'd expect) and a PDF generated with alpha-13.0 (note the highlighted areas). It looks like there has been some improvement from the previous version, but there are still some issues in both Arial and Roboto. I also undid my "rename-font.ttf" trick and reinstalled the Roboto fonts locally and I'm not getting blank pages now so I think the original bug has been resolved in 0.13, but not the kerning one that came up later in this discussion.

Now that I look back at my last screenshot ( http://goo.gl/qoc47N ), it looks like overall, the kerning has just been spaced out more on pretty much everything so the things that were too close in Arial are now decent, the things that where decent are too spaced and on Roboto, things appear too spaced, but the culprit may just be the 'o' which appears frequently in my example. Here is the newer pdf: http://goo.gl/og4CTd

image

@ashkulz ashkulz modified the milestones: 0.13.x, future Jan 22, 2015
@ashkulz ashkulz removed the NeedInfo label Jan 22, 2015
@ashkulz
Copy link
Member

ashkulz commented Jan 22, 2015

Kerning issue is tracked in #1527, marking this issue closed for 0.13.x

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

No branches or pull requests

4 participants