Bug 31302 - Add WOFF support for @font-face
Summary: Add WOFF support for @font-face
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: New Bugs (show other bugs)
Version: 528+ (Nightly build)
Hardware: All All
: P2 Normal
Assignee: mitz
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2009-11-10 09:41 PST by Sam Weinig
Modified: 2010-07-31 22:05 PDT (History)
21 users (show)

See Also:


Attachments
Add woffToSfnt() and use it on Mac and Windows (70.29 KB, patch)
2010-07-28 21:28 PDT, mitz
oliver: review-
Details | Formatted Diff | Diff
Add woffToSfnt() and use it on Mac and Windows (70.04 KB, patch)
2010-07-28 21:49 PDT, mitz
darin: review-
Details | Formatted Diff | Diff
Add ConvertWOFFToSfnt() and use it on Mac and Windows (70.43 KB, patch)
2010-07-28 23:59 PDT, mitz
no flags Details | Formatted Diff | Diff
Add ConvertWOFFToSfnt() and use it on Mac and Windows (70.42 KB, patch)
2010-07-29 00:13 PDT, mitz
darin: review+
Details | Formatted Diff | Diff
Add ConvertWOFFToSfnt() and use it on Mac and Windows (69.91 KB, patch)
2010-07-30 00:38 PDT, mitz
darin: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Sam Weinig 2009-11-10 09:41:43 PST
Mozilla has added support for WOFF (https://bugzilla.mozilla.org/show_bug.cgi?id=507970).  More information on the format is available in the WOFF spec - http://people.mozilla.org/~jkew/woff/woff-spec-latest.html.
Comment 1 John Lascurettes 2010-03-01 09:26:59 PST
If it helps the decision making process:

As stated, Firefox 3.6 (and other Gecko 1.9.2 browsers) support WOFF. And according to this page, Microsoft is considering adding future support for WOFF: http://en.wikipedia.org/wiki/Web_typography#Web_Open_Font_Format . I could find no work on Presto/Opera outside of speculations. 

Services such as Typekit are also supporting WOFF and offer it as a solution for supporting custom fonts on Mobile WebKit browsers over SVG: 
http://getsatisfaction.com/typekit/topics/typekit_support_for_the_iphone 
and http://blog.typekit.com/2009/07/16/why-we-support-the-new-webfont-proposal/
Comment 2 Stephen Weiss 2010-04-14 15:04:26 PDT
Just tacking on here that if Webkit would only support this feature, embedded, license-friendly fonts could actually be feasible on the web for most users (since IE and most font makers already support EOT, WOFF closes the circle for FF and hopefully also Safari/Chrome)... It would be an incredible boon at the right time.  I know we could really use it... sooner rather than later...  so please give it serious consideration!   Thanks!
Comment 3 Adam Langley 2010-04-23 06:37:44 PDT
Chiming in for Chromium here.

Here's our tracking bug: http://code.google.com/p/chromium/issues/detail?id=25543

It looks like we're going to try and do this for milestone 6. We already have a transcoder (http://code.google.com/p/ots/) to protect the font libraries on our various platforms from arbitrary input. I plan on adding WOFF support to that so that the font appears to be a TTF to WebKit.

Thus our plans are mostly independent of WebKit support for WOFF. (I feel a little bad that I can't lift all ships with this work but, from a design point of view, OTS is the correct place for Chromium to add support.)

Other WebKit ports are welcome to use OTS of course.
Comment 4 Maciej Stachowiak 2010-04-23 15:24:11 PDT
(In reply to comment #3)
> Chiming in for Chromium here.
> 
> Here's our tracking bug:
> http://code.google.com/p/chromium/issues/detail?id=25543
> 
> It looks like we're going to try and do this for milestone 6. We already have a
> transcoder (http://code.google.com/p/ots/) to protect the font libraries on our
> various platforms from arbitrary input. I plan on adding WOFF support to that
> so that the font appears to be a TTF to WebKit.
> 
> Thus our plans are mostly independent of WebKit support for WOFF. (I feel a
> little bad that I can't lift all ships with this work but, from a design point
> of view, OTS is the correct place for Chromium to add support.)
> 
> Other WebKit ports are welcome to use OTS of course.

It would be much better to find a way to do this that works for all WebKit ports, since we probably want WOFF --> TTF/OTF translation to work consistently.
Comment 5 Adam Langley 2010-04-27 15:33:36 PDT
> It would be much better to find a way to do this that works for all WebKit
> ports, since we probably want WOFF --> TTF/OTF translation to work
> consistently.

I agree in principle. However, Chromium isn't willing to expose the system TrueType renderer to the outside world on any platform. (OS X's may be great, but Windows and Linux have a history of issues.)

Thus, in order to transcode the WOFF, we need to uncompress it in the transcoder. From there, it doesn't make sense for us to recompress in order to pass the data to a WebKit generic WOFF handler. We are just going to carry the data as TTF from that point onwards.

As https://bugs.webkit.org/show_bug.cgi?id=38217 shows, the WebKit change for Chromium is basically a single line.
Comment 6 mitz 2010-07-28 21:28:38 PDT
Created attachment 62915 [details]
Add woffToSfnt() and use it on Mac and Windows

The test remains disabled on Windows because zlib isn’t included in the WebKit auxiliary libraries for Windows.
Comment 7 WebKit Review Bot 2010-07-28 21:33:44 PDT
Attachment 62915 [details] did not pass style-queue:

Failed to run "['WebKitTools/Scripts/check-webkit-style']" exit_code: 1
WebCore/platform/graphics/chromium/FontCustomPlatformData.cpp:210:  Line contains only semicolon. If this should be an empty statement, use { } instead.  [whitespace/semicolon] [5]
WebCore/platform/graphics/mac/FontCustomPlatformData.cpp:94:  Use 0 instead of NULL.  [readability/null] [5]
WebCore/platform/graphics/WOFFToSfnt.cpp:1:  One or more unexpected \r (^M) found; better to use only a \n  [whitespace/carriage_return] [1]
Suppressing further [whitespace/carriage_return] reports for this file.
Total errors found: 244 in 27 files


If any of these errors are false positives, please file a bug against check-webkit-style.
Comment 8 Eric Seidel (no email) 2010-07-28 21:39:44 PDT
Attachment 62915 [details] did not build on mac:
Build output: http://queues.webkit.org/results/3574635
Comment 9 Oliver Hunt 2010-07-28 21:42:37 PDT
Comment on attachment 62915 [details]
Add woffToSfnt() and use it on Mac and Windows



WebCore/platform/graphics/WOFFToSfnt.cpp:65
 +      if (buffer->size() - offset < sizeof(value))
SharedBuffer::size() and offset are unsigned, if offset is greater than buffer->size() you overflow and get a very large value

WebCore/platform/graphics/WOFFToSfnt.cpp:77
 +      if (buffer->size() - offset < sizeof(value))
ditto

WebCore/platform/graphics/WOFFToSfnt.cpp:89
 +      if (vector.size() > vector.capacity() - sizeof(value))
Why do you need this check? why not just just use vectors automagic growing?

WebCore/platform/graphics/WOFFToSfnt.cpp:103
 +          return false;
Same question again

WebCore/platform/graphics/WOFFToSfnt.cpp:150
 +      if (woff->size() - offset < sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t))
Overflow again -- unsigned maths is dangerous if you checking for overflow :-/
Comment 10 mitz 2010-07-28 21:46:29 PDT
(In reply to comment #9)
> (From update of attachment 62915 [details])
> 
> 
> WebCore/platform/graphics/WOFFToSfnt.cpp:65
>  +      if (buffer->size() - offset < sizeof(value))
> SharedBuffer::size() and offset are unsigned, if offset is greater than buffer->size() you overflow and get a very large value

See assertion in the line above. offset can never exceed the buffer size.

> WebCore/platform/graphics/WOFFToSfnt.cpp:77
>  +      if (buffer->size() - offset < sizeof(value))
> ditto

Ditto.

> 
> WebCore/platform/graphics/WOFFToSfnt.cpp:89
>  +      if (vector.size() > vector.capacity() - sizeof(value))
> Why do you need this check? why not just just use vectors automagic growing?

The draft specification requires that the output not exceed totalSfntSize.

> WebCore/platform/graphics/WOFFToSfnt.cpp:103
>  +          return false;
> Same question again

Ditto.

> WebCore/platform/graphics/WOFFToSfnt.cpp:150
>  +      if (woff->size() - offset < sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t))
> Overflow again -- unsigned maths is dangerous if you checking for overflow :-/

offset can never exceed the buffer size.
Comment 11 mitz 2010-07-28 21:49:26 PDT
Created attachment 62916 [details]
Add woffToSfnt() and use it on Mac and Windows

Trying to fix build error on Leopard.
Comment 12 Darin Adler 2010-07-28 22:47:07 PDT
Comment on attachment 62916 [details]
Add woffToSfnt() and use it on Mac and Windows

> +    while (searchRange < numTables) {
> +        entrySelector++;
> +        searchRange <<= 1;
> +    }

If numTables is a the maximum value, this will be an infinite loop.

> +    searchRange <<= 4;

If numTables is a large value, this could overflow.

> +    uint16_t rangeShift = (numTables << 4) - searchRange;

If numTables is a large value, this could overflow.

> +    if ((totalSfntSize - sfnt.size()) / (4 * sizeof(uint32_t)) < numTables)
> +        return false;

What guarantees that totalSfntSize is >= sfnt.size()? Since numTables is restricted to 16 bits, you could do this math with multiplication instead of divison.

> +        uint32_t tableCompLength;
> +        if (!readUInt32(woff, offset, tableCompLength))
> +            return false;

How about "compressed" instead of "comp"?

> +        uint32_t tableOrigLength;
> +        if (!readUInt32(woff, offset, tableOrigLength) || tableCompLength > tableOrigLength)
> +            return false;

How about "original" instead of "orig"?

> +// Returns false if woff is not a valid WOFF font. Otherwise returns true and writes
> +// the sfnt payload into sfnt.
> +bool woffToSfnt(SharedBuffer* woff, Vector<uint8_t>& sfnt);

How about a verb? Maybe convertWOFFToSfnt?

> +#if OS(LINUX)
> +class String;
> +#endif

I think you can make the forward declaration unconditional.

> +static void freeSfntData(void*, const void*data, size_t)
> +{
> +    fastFree(const_cast<void*>(data));
> +}

Missing space after the "*" before data.

> +    Vector<uint8_t> sfnt;
> +    RefPtr<SharedBuffer> sfntBuffer;
> +    if (woffToSfnt(buffer, sfnt)) {
> +        sfntBuffer = SharedBuffer::adoptVector(*reinterpret_cast<Vector<char>*>(&sfnt));
> +        buffer = sfntBuffer.get();
> +    }

The reinterpret_cast to a Vector<char>* seems really ugly and dangerous. Is there any way around this? Perhaps we could just write into a Vector<char>. When reading, the fact that we don't know if it's signed or unsigned could be an issue. But when writing, it should not.

review- because you should defend against crazy values for numTables and totalSfntSize.
Comment 13 mitz 2010-07-28 23:08:00 PDT
(In reply to comment #12)

Thanks for the review!

> > +    if ((totalSfntSize - sfnt.size()) / (4 * sizeof(uint32_t)) < numTables)
> > +        return false;

> What guarantees that totalSfntSize is >= sfnt.size()?

sfnt’s capacity is set to totalSfntSize, and earlier calls to write*() don’t change that; and a Vector’s size never exceeds its capacity.

I am going to deal with large numTables values in all of the above and use multiplication instead of division for that last comparison.

> How about "compressed" instead of "comp"?
> How about "original" instead of "orig"?

I have named the variables after the fields in the draft specification <http://www.w3.org/TR/2010/WD-WOFF-20100727/#TableDirectory>.

> > +bool woffToSfnt(SharedBuffer* woff, Vector<uint8_t>& sfnt);
> 
> How about a verb? Maybe convertWOFFToSfnt?

Sounds good. I will change that. Should I rename the files accordingly?

> > +#if OS(LINUX)
> > +class String;
> > +#endif
> 
> I think you can make the forward declaration unconditional.

OK.

> 
> > +static void freeSfntData(void*, const void*data, size_t)
> > +{
> > +    fastFree(const_cast<void*>(data));
> > +}
> 
> Missing space after the "*" before data.

Will fix.

> > +    Vector<uint8_t> sfnt;
> > +    RefPtr<SharedBuffer> sfntBuffer;
> > +    if (woffToSfnt(buffer, sfnt)) {
> > +        sfntBuffer = SharedBuffer::adoptVector(*reinterpret_cast<Vector<char>*>(&sfnt));
> > +        buffer = sfntBuffer.get();
> > +    }
> 
> The reinterpret_cast to a Vector<char>* seems really ugly and dangerous. Is there any way around this? Perhaps we could just write into a Vector<char>. When reading, the fact that we don't know if it's signed or unsigned could be an issue. But when writing, it should not.

I am going to see if I can make sfnt a Vector<char> :-(
Comment 14 mitz 2010-07-28 23:59:19 PDT
Created attachment 62920 [details]
Add ConvertWOFFToSfnt() and use it on Mac and Windows

In addition to addressing Darin’s comments, corrected the calculation of searchRange and entrySelector.
Comment 15 WebKit Review Bot 2010-07-29 00:02:03 PDT
Attachment 62920 [details] did not pass style-queue:

Failed to run "['WebKitTools/Scripts/check-webkit-style']" exit_code: 1
WebCore/platform/graphics/chromium/FontCustomPlatformData.cpp:210:  Line contains only semicolon. If this should be an empty statement, use { } instead.  [whitespace/semicolon] [5]
WebCore/platform/graphics/mac/FontCustomPlatformData.cpp:94:  Use 0 instead of NULL.  [readability/null] [5]
Total errors found: 2 in 27 files


If any of these errors are false positives, please file a bug against check-webkit-style.
Comment 16 Eric Seidel (no email) 2010-07-29 00:06:06 PDT
Attachment 62920 [details] did not build on mac:
Build output: http://queues.webkit.org/results/3567616
Comment 17 mitz 2010-07-29 00:13:11 PDT
Created attachment 62922 [details]
Add ConvertWOFFToSfnt() and use it on Mac and Windows

Trying to fix build error on Leopard.
Comment 18 Darin Adler 2010-07-29 10:25:53 PDT
(In reply to comment #13)
> > > +bool woffToSfnt(SharedBuffer* woff, Vector<uint8_t>& sfnt);
> > 
> > How about a verb? Maybe convertWOFFToSfnt?
> 
> Sounds good. I will change that. Should I rename the files accordingly?

I think that file names should be noun phrases, and function names should be verb phrases. So I would not name a file after a function.
Comment 19 Darin Adler 2010-07-29 10:27:00 PDT
(In reply to comment #18)
> I think that file names should be noun phrases, and function names should be verb phrases. So I would not name a file after a function.

I see that you have done this, and I think it’s OK, but the above does represent my normal feelings on this.
Comment 20 mitz 2010-07-29 10:28:05 PDT
(In reply to comment #18)
> (In reply to comment #13)
> > > > +bool woffToSfnt(SharedBuffer* woff, Vector<uint8_t>& sfnt);
> > > 
> > > How about a verb? Maybe convertWOFFToSfnt?
> > 
> > Sounds good. I will change that. Should I rename the files accordingly?
> 
> I think that file names should be noun phrases, and function names should be verb phrases. So I would not name a file after a function.

Well, but “accordingly” i didn’t necessarily mean “identically” (it was just my default choice). I will think of a different name.
Comment 21 Darin Adler 2010-07-29 10:36:56 PDT
Comment on attachment 62922 [details]
Add ConvertWOFFToSfnt() and use it on Mac and Windows

> +    uint32_t totalSfntSize;
> +    if (!readUInt32(woff, offset, totalSfntSize))
> +        return false;
> +
> +    if (!sfnt.tryReserveCapacity(totalSfntSize))
> +        return false;

Reading a number from a file and then immediately trying to allocate a buffer of that size seems too trusting, even if we do use the version of the memory allocation function that fails if memory is exhausted. Are there any reality checks we can do on this size that can eliminate pathological cases?

>  #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
> -    RetainPtr<CFDataRef> bufferData(AdoptCF, buffer->createCFData());
> -    RetainPtr<CGDataProviderRef> dataProvider(AdoptCF, CGDataProviderCreateWithCFData(bufferData.get()));
> +    RetainPtr<CGDataProviderRef> dataProvider;
> +#if !HAVE(WOFF)
> +    Vector<char> sfnt;
> +    if (convertWOFFToSfnt(buffer, sfnt)) {
> +        size_t sfntSize = sfnt.size();
> +        dataProvider.adoptCF(CGDataProviderCreateWithData(0, sfnt.releaseBuffer(), sfntSize, freeSfntData));
> +    } else
> +#endif // !HAVE(WOFF)
> +    {
> +        RetainPtr<CFDataRef> bufferData(AdoptCF, buffer->createCFData());
> +        dataProvider.adoptCF(CGDataProviderCreateWithCFData(bufferData.get()));
> +    }

I think it would be better encapsulation to put this code into a function. For one thing, the WOFF part could be an early return rather than an else with braces outside the #if, which would be less subtle. Sure, the lack of PassRetainPtr gives you a way to blame this all on me, but I still think a function would be great, since it's just SharedBuffer in and CFDataRef out.

This code treats a non-WOFF font and a malformed WOFF font, including cases of failure due to memory exhaustion, the same. Is that really the intended algorithm? If the WOFF fails for any reason, then try to treat it as another file format? That's OK, I guess, if it's really what you intend.

> +bool convertWOFFToSfnt(SharedBuffer* woff, Vector<char>& sfnt);

Maybe I should have suggested adding a try prefix on this function name. Not sure that would be an improvement, though. It would be nice some day to figure out a good naming scheme for "return a boolean to indicate success" functions.

> +    Vector<uint8_t> sfnt;
> +    if (convertWOFFToSfnt(buffer, sfnt)) {

I don't think this will compile any more, because you changed the vector type at my suggestion.

r=me, but consider improvements too
Comment 22 mitz 2010-07-30 00:38:21 PDT
Created attachment 63035 [details]
Add ConvertWOFFToSfnt() and use it on Mac and Windows

Addressed some of the comments. The sfnt vector now grows incrementally as it is being written into, however for each font table, memory allocation is still attempted for whatever uncompressed size the WOFF table directory claims that the table has, as uncompress() offers no way to verify the claim without uncompressing into a buffer.
Comment 23 WebKit Review Bot 2010-07-30 00:43:11 PDT
Attachment 63035 [details] did not pass style-queue:

Failed to run "['WebKitTools/Scripts/check-webkit-style']" exit_code: 1
WebCore/platform/graphics/chromium/FontCustomPlatformData.cpp:210:  Line contains only semicolon. If this should be an empty statement, use { } instead.  [whitespace/semicolon] [5]
Total errors found: 1 in 27 files


If any of these errors are false positives, please file a bug against check-webkit-style.
Comment 24 Darin Adler 2010-07-30 10:14:40 PDT
Comment on attachment 63035 [details]
Add ConvertWOFFToSfnt() and use it on Mac and Windows

> +            if (!sfnt.tryReserveCapacity(sfnt.size() + tableOrigLength))
> +                return false;

What guarantees this won't overflow? On a 32-bit system if tableOrigLength was 0xFFFFFFFF it seems like we could end up not reserving any capacity and decoding past the end of the buffer.
Comment 25 mitz 2010-07-31 19:42:47 PDT
(In reply to comment #24)
> (From update of attachment 63035 [details])
> > +            if (!sfnt.tryReserveCapacity(sfnt.size() + tableOrigLength))
> > +                return false;
> 
> What guarantees this won't overflow? On a 32-bit system if tableOrigLength was 0xFFFFFFFF it seems like we could end up not reserving any capacity and decoding past the end of the buffer.

This earlier check:

        if (tableOrigLength > totalSfntSize || sfnt.size() > totalSfntSize - tableOrigLength)
            return false;

guarantees that sfnt.size() + tableOrigLength doesn’t overflow and is no more than totalSfntSize.
Comment 26 mitz 2010-07-31 22:05:27 PDT
Fixed in <http://trac.webkit.org/projects/webkit/changeset/64434>.