prawnpdf / ttfunk Goto Github PK
View Code? Open in Web Editor NEWFont Metrics Parser for Prawn
Home Page: http://prawnpdf.org
License: Other
Font Metrics Parser for Prawn
Home Page: http://prawnpdf.org
License: Other
Hey there maintainers! I've been working for the last several months on supporting OpenType fonts, which really just means supporting the CFF font table. This is no small task, so the resulting PR was gigantic (git diff --stat
tells me 46 files changed, 2990 insertions, 296 deletions). For digestibility I've tried to split it up into bitesized chunks (there are 13 of them) that I will work on submitting as long as you have the patience to review them. The branches have been created off each other in cascading fashion. I will have to submit them as individual pull requests one at a time. The following list serves as a manifest of the branches and should let us track what's been done so far:
I have successfully subsetted several of the Noto and STIX fonts, which I think have been pretty good litmus tests as they are very large and contain good examples of the various CFF sub tables and features. I used an open-source tool called Font-Validator (initially written by the folks at Microsoft) to validate the results.
Please let me know how I can help futher!
I was using asciidoctor-pdf but I had a blocking error (asciidoctor/asciidoctor-pdf#2448) due to ttfunk.
It's different form #45 as in both cases I was using a TTF font and not an OTF font. However, is looks similar to #81.
I know very little about fonts but I can read this on the ArchLinux Wiki:
Emoji fonts come in different formats: CBDT/CBLC (Google), SBIX (Apple), COLR/CPAL (Microsoft), SVG (Mozilla/Adobe).
Among the TTF fonts Noto Color Emoji and JoyPixels are using CBDT/CBLC format, Twemoji is available as CBDT/CBLC format or SVG (SVG in OTF, so SVG format is not TTF) format and Symbola is outline only (grayscale).
I'm not really sure if it's a matter of color / grayscale or a matter of format.
The last commit 00cfd96 breaks the font rendering in the Internet Explorer. We had to roll back our production system to version 1.6.0
I have a font file (JosefinSansStd-Light) with kerning information stored with classes to group some characters together. I can't find how to retrieve the groups informations with ttfunk. Any idea?
Hi, PrawnPDF folks,
When using ttfunk 1.5.1 in AsciidoctorJ, I'm getting some "@offset not initialized" warnings.
$ /usr/local/Cellar/asciidoctorj/1.5.7/bin/asciidoctorj -b pdf test.adoc
uri:classloader:/gems/prawn-svg-0.27.1/lib/prawn/svg/color.rb:212: warning: shadowing outer local variable - result
uri:classloader:/gems/prawn-svg-0.27.1/lib/prawn/svg/properties.rb:50: warning: `*' interpreted as argument prefix
uri:classloader:/gems/ttfunk-1.5.1/lib/ttfunk/table.rb:26: warning: instance variable @offset not initialized
uri:classloader:/gems/ttfunk-1.5.1/lib/ttfunk/table.rb:26: warning: instance variable @offset not initialized
uri:classloader:/gems/ttfunk-1.5.1/lib/ttfunk/table.rb:26: warning: instance variable @offset not initialized
uri:classloader:/gems/ttfunk-1.5.1/lib/ttfunk/table.rb:26: warning: instance variable @offset not initialized
uri:classloader:/gems/ttfunk-1.5.1/lib/ttfunk/table.rb:26: warning: instance variable @offset not initialized
uri:classloader:/gems/prawn-2.2.2/lib/prawn/font/ttf.rb:118: warning: instance variable @italic_angle not initialized
uri:classloader:/gems/ttfunk-1.5.1/lib/ttfunk/table.rb:26: warning: instance variable @offset not initialized
uri:classloader:/gems/ttfunk-1.5.1/lib/ttfunk/table.rb:26: warning: instance variable @offset not initialized
uri:classloader:/gems/ttfunk-1.5.1/lib/ttfunk/table.rb:26: warning: instance variable @offset not initialized
uri:classloader:/gems/prawn-2.2.2/lib/prawn/font/ttf.rb:118: warning: instance variable @italic_angle not initialized
uri:classloader:/gems/prawn-2.2.2/lib/prawn/font/ttf.rb:118: warning: instance variable @italic_angle not initialized
It looks like this was fixed in 086b312. But that was after the latest 1.5.1 release. Maybe you could cut a new release so the fix can propagate to users running ttfunk through RubyGems?
I've noticed that TTFunk gem doesn't have a license listed on Rubygems. So it doesn't have it in the gemspec.
LICENSE
states that GPL 2 or GPL 3 applicable, as well as "Matz's terms for Ruby". The LICENSE
is the same as the one we have in Prawn, yet that one says in Readme it's "a slightly modified form of the License of Ruby". And indeed there are more differences than simple wording. This means that we can not use plain Ruby license. Also Ruby has switched to MIT some time ago but that's not really a problem.
Another issue is that we distribute font files with TTFunk and it's not covered by license in any way. Though, original Ruby License does (partially) cover third-party copyrighted assets. For instance, DejaVu Sans' license mandates that the license must be redistributed with the font.
Details documented in google-group:
http://groups.google.com/group/prawn-ruby/browse_thread/thread/6754ada443dd22cd
I would appreciate it if you could consider for releasing the new version.
It is been over a year since the latest version was released.
In the meantime, two pull requests that provide significant performance improvements have been merged.
Releasing these changes is valuable to users and gems that depend on ttfunk.
Thank you for your consideration.
1.8.0
has introduced issues with embedding fonts with prawn
, see prawnpdf/prawn#1346
Minimal reproduction:
#!/usr/bin/env ruby
require "prawn"
pdf = Prawn::Document.new
pdf.font_families.update(
"roboto" => {
normal: "Roboto/Roboto-Regular.ttf",
italic: "Roboto/Roboto-Italic.ttf",
bold: "Roboto/Roboto-Bold.ttf",
bold_italic: "Roboto/Roboto-BoldItalic.ttf",
}
)
pdf.font("roboto")
pdf.text_box("€")
File.open("test.pdf", "w") do |file|
pdf.render(file)
end
system "flatpak run com.adobe.Reader test.pdf"
The Euro symbol seems to trigger the bug.
In a TTF font, ttfunk doesn't seem to always use the notdef glyph when a glyph cannot be found.
This is a tricky bug to reproduce because it largely depends on what other characters are present and what glyphs the font provides. For certain fonts, when certain combinations of characters are used, the notdef glyph gets replaced with what appears to be a random glyph.
Here's an example that uses a font in the prawn data folder:
Prawn::Document.generate 'out.pdf' do
def register_font data
font_families.update(data.each_with_object({}) do |(key, val), accum|
accum[key.to_s] = val
end)
end
register_font Custom: { normal: 'PRAWN_DIR/data/fonts/Dustismo_Roman.ttf' }
font 'Custom' do
text "ĂĄǺ\u0200\u0202ÆǼĆÇĈĊČĎĐÐḌDZDzDŽDžÈÉÊËĒĔĖĘẼĚẸ\u0204\u0206ƏǴĜĞĠĢĤĦḤĬÌÍÎÏİĨĪĮỊ\u0208\u020aĹ"
end
end
In the generated PDF, we see an "L" in the position of the second to last character. If you copy that and paste it into an editor, you'll see the correct glyph. (See https://www.fileformat.info/info/unicode/char/020a/index.htm). So the problem appears to be what's being displayed from the font, not the text that's stored.
The issue prawnpdf/prawn#972 describes a problem with Prawn when subsetting a TTF font into a PDF.
@Siyfion provided a test case for tracking down the bug. If one substitutes "A" for the text to be output (instead of "ABCDEFG...") everything works fine, the subset font is valid. However, if one uses "D", nothing is shown and loading the subset font in FontForge yields an error.
The problem stems from ttfunk supporting the 'short' type for the 'loca' table. Using this type offsets aren't stored directly, only their value divided by two is stored. This works fine as long all glyph data has a length that is even. In case of the "D" glyph, however, the length is odd, leading to an invalid offset and and invalid 'loca' table.
The method Loca.encode
at https://github.com/prawnpdf/ttfunk/blob/master/lib/ttfunk/table/loca.rb#L15 needs to be changed to the following to always output a valid table:
def self.encode(offsets)
{ :type => 1, :table => offsets.pack("N*") }
end
The only disadvantage of this change is that the 'loca' table might be larger than needed.
I set up Prawn to use an OTF font (https://rsms.me/inter/download/) and everything renders fine in the browser.
However, when you go to print this on your printer, everything is garbled.
Using the TTF version of the font from Google Fonts, it prints just fine, so it appears to be a bug in OTF support.
cc @camertron
Should probably be the same license as prawn, but still need to make it explicit
.bundle/gems/ruby/2.3.0/gems/ttfunk-1.5.0/lib/ttfunk/collection.rb:1: warning: loading in progress, circular require considered harmful - .bundle/gems/ruby/2.3.0/gems/ttfunk-1.5.0/lib/ttfunk.rb
from .bundle/gems/ruby/2.3.0/gems/ttfunk-1.5.0/lib/ttfunk.rb:6:in `<top (required)>'
from .bundle/gems/ruby/2.3.0/gems/ttfunk-1.5.0/lib/ttfunk.rb:6:in `require_relative'
from .bundle/gems/ruby/2.3.0/gems/ttfunk-1.5.0/lib/ttfunk/collection.rb:1:in `<top (required)>'
from .bundle/gems/ruby/2.3.0/gems/ttfunk-1.5.0/lib/ttfunk/collection.rb:1:in `require'
Not sure to what extent this would improve performance, but stumbled across this comment while reading the code...
https://github.com/prawnpdf/ttfunk/blob/master/lib/ttfunk/subset_collection.rb#L45
I probably won't be working on this myself, but wanted to drop a note in case someone wants to take a look.
Ruby 3.0.0 was released https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/, but the latest release 1.6.2.1 does not work with it: Bundler shows this error ttfunk-1.6.2.1 requires ruby version ~> 2.4, which is incompatible with the current version, ruby 3.0.0p0
It's apparently already prepared on master with 7db8400 - can a new release be made of that? 😇
If the data in one of the tables is empty, the Subset object fails to encode. This bug was introduced when the checksum method was rewritten prior to 1.5.0.
See 129d423#diff-45b279937f00663adf564be8dead1e57L126
The method no longer accounts for a zero-length string. When the string is empty, the method returns false instead of 0.
Hi,
I am using prawn lately, it works very well.
One problem I have is the amount of warnings though:
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/directory.rb:7: warning: assigned but unused variable - search_range
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/directory.rb:8: warning: assigned but unused variable - entry_selector
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/directory.rb:8: warning: assigned but unused variable - range_shift
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/resource_file.rb:15: warning: assigned but unused variable - data_length
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/cmap/format00.rb:47: warning: assigned but unused variable - length
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/cmap/format06.rb:40: warning: assigned but unused variable - length
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/cmap/format10.rb:40: warning: assigned but unused variable - length
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/cmap/format12.rb:51: warning: assigned but unused variable - length
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/kern/format0.rb:15: warning: assigned but unused variable - search_range
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/kern/format0.rb:15: warning: assigned but unused variable - entry_selector
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/kern/format0.rb:15: warning: assigned but unused variable - range_shift
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/name.rb:56: warning: assigned but unused variable - id
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/name.rb:79: warning: assigned but unused variable - format
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/post.rb:66: warning: assigned but unused variable - end_of_table
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/table/post/format25.rb:17: warning: assigned but unused variable - number_of_glyphs
/Programs/Ruby/2.1.1/lib/ruby/gems/2.1.0/gems/ttfunk-1.1.1/lib/ttfunk/subset/base.rb:34: warning: assigned but unused variable - code
Would it be possible to do something about these variables? I am not sure if fixing it is easy or worth it, but right now I am unsure what to do - I run my ruby code with warnings enabled, and ttfunk is the first gem that really outputs a lot of these things when I require prawn.
I'm trying to get emoji rendering working, and I'm failing. I'm trying to use NotoColorEmoji.ttf and I am experiencing this error:
2020-03-09T00:23:31.723Z 35337 TID-ouxjoy04c WARN: NoMethodError: undefined method `key?' for nil:NilClass
2020-03-09T00:23:31.723Z 35337 TID-ouxjoy04c WARN: /Users/brandoncc/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/ttfunk-1.6.2.1/lib/ttfunk/table/glyf.rb:32:in `for'
/Users/brandoncc/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/ttfunk-1.6.2.1/lib/ttfunk/subset/base.rb:105:in `glyph_for'
Here is my list of fallback fonts:
def fallback_fonts
[
'NotoColorEmoji',
'IPAGothic',
'IPAMincho',
'IPAPMincho',
'IPAPGothic',
'WenQuanYi Zen Hei',
'DejaVuSans',
'DroidSans',
'DroidSerif'
]
end
All of the other fonts work, but many of the emojis just render as boxes.
Any points on either another way, or getting this font to work, would be appreciated. Thanks!
When getting the Unicode character map from the cmap table list, TTFunk only allows a value of 1 for "encoding_id" (which seems to equate to language code although I'm not sure).
According to Apple's spec for the cmap table the language code in Format 4 can be 0 for language independent fonts. This seems to be the case for Microsoft's symbol fonts (Webdings and Wingdings*).
So I think the unicode? method of subtable should allow encoding_id of 1 or 0. Currently symbol fonts' unicode character mapping can't be used. This is preventing me from outputting symbols to PDFs generated by Prawn, even though there's a Unicode mapping to the glyphs.
require 'ttfunk'
cmap = TTFunk::File.open("Webdings.ttf").cmap
cmap.tables[1].platform_id # => 3
cmap.tables[1].encoding_id # => 0
cmap.tables[1].format $ => 4
cmap.unicode # => []
The gem build ttfunk.gemspec
command fails because of the following line:
https://github.com/prawnpdf/ttfunk/blob/master/ttfunk.gemspec#L14
It looks up the signing key in a location that is incompatible with an system based on ruby environment managers such as rvm or rbenv.
It triggers the following error:
ERROR: While executing gem ... (Errno::ENOENT)
No such file or directory @ rb_sysopen - /home/mini-me/.gem/gem-private_key.pem
Maybe it would make sense to gate this feature behind the invocation of some option or flag.
No matter if using ruby-1.9.2 or ree-1.8.7, when ever I try to start my rails app (rails s, rails db:seed, ..) I get a segmentation fault. Removing the gem from the Gemfile makes the seg fault go away. It's strange, since I dont to a single call to TTFunk, just adding it to the Gemfile causes my system to segfault. Plattform is ubuntu 11.04 amd64.
In v1.6.2.1, TTFunk::Subset#encode
takes about 30x longer than v1.5.1.
require 'ttfunk'
require 'benchmark'
# English Font
file = TTFunk::File.open("DejaVuSans.ttf")
# Japanese Font
# file = TTFunk::File.open("GenShinGothic-Normal.ttf")
subset = TTFunk::Subset.for(file, :unicode)
Benchmark.bmbm { |x| x.report('encode') { 10.times { subset.encode } } }
ttfunk version | font | time[s] |
---|---|---|
v1.5.1 | DejaVuSans.ttf | 0.035328 |
v1.6.2.1 | DejaVuSans.ttf | 1.336027 |
v1.5.1 | GenShinGothic-Normal.ttf | 0.161720 |
v1.6.2.1 | GenShinGothic-Normal.ttf | 4.500678 |
TTFunk::Table::OS2.group_original_code_points_by_bit and TTFunk::Table::OS2.group_original_code_points_by_bit
took a lot of time in stackprof profile.
It would be extremely helpful to get documentation to answer basic questions, such as, what units are the ascent and descent in?
This works perfectly:
def width_of( string )
if string.nil? || (string == '')
0
else
string.split('').map{|char| character_width( char )}.inject{|sum, x| sum + x}
end
end
def character_width( character )
width_in_units = ( horizontal_metrics.for( glyph_id( character )).advance_width )
width_in_units.to_f / units_per_em
end
def horizontal_metrics
@hm = file.horizontal_metrics
end
def glyph_id(character)
character_code = character.unpack("U*").first
file.cmap.unicode.first[character_code]
end
def file
@file
end
def filename=(path_to_file)
@file = TTFunk::File.open(path_to_file)
end
as long as I'm not using a different weight. How can I specify a font weight?
In GPLv2 file is this address:
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
But the address changed some time ago and now should be:
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Hello,
Attempting to build ttfunk 1.7.0 from source results in:
starting phase `build'
ERROR: While executing gem ... (Errno::ENOENT)
No such file or directory @ rb_sysopen - /homeless-shelter/.gem/gem-private_key.pem
error: in phase 'build': uncaught exception:
%exception #<&invoke-error program: "gem" arguments: ("build" "./ttfunk.gemspec") exit-status: 1 term-signal: #f stop-signal: #f>
phase `build' failed after 0.5 seconds
This can be worked around by removing the .*spec.signing_key.*
line from the ttfunk.gemspec
file.
Hey guys,
First of all, thanks for all the work you do on Prawn and TTFunk! The ruby community really appreciates it :)
I'm trying to use TTFunk to subset the Noto font, but getting an error with the following snippet (adapted from looking at the tests):
font = TTFunk::File.open('path/to/NotoSansCJKjp-Regular.otf')
subset = TTFunk::Subset.for(font, :unicode)
subset.use(0x304b) # Katakana letter "ka"
subset.encode
I get the following error:
NoMethodError: undefined method `key?' for nil:NilClass
from /Users/cameron/workspace/ttfunk/lib/ttfunk/table/glyf.rb:30:in `for'
from /Users/cameron/workspace/ttfunk/lib/ttfunk/subset/base.rb:158:in `block in collect_glyphs'
from /Users/cameron/workspace/ttfunk/lib/ttfunk/subset/base.rb:157:in `each'
from /Users/cameron/workspace/ttfunk/lib/ttfunk/subset/base.rb:157:in `each_with_object'
from /Users/cameron/workspace/ttfunk/lib/ttfunk/subset/base.rb:157:in `collect_glyphs'
from /Users/cameron/workspace/ttfunk/lib/ttfunk/subset/base.rb:32:in `encode'
It looks like the @cache
variable is nil
.
Any idea why that would be? I would be happy to track down the issue and submit a pull request, but I have no idea where to start. Thanks!
Oh, and here's the font in question:
Yesterday I updated my Rails project to ttfunk 1.6.0. My users have problems with embedded fonts in Adobe Acrobat. German Umlauts are not displayed anymore. Adobe shows the German error: "Die eingebettete Schrift "815ef7+DejaVuSans" konnte nicht entnommen werden. Einige Zeichen werden u. U. nicht korrekt angezeigt bzw. gedruckt."
Today I updated to 1.6.1 and now all chars are not displayed with Adobe.
On my Linux System with xreader I have no problems. In Firefox I got the following errors, but there I see the Umlaut chars:
PDF 509a1cc8bc45a172ea4317787b03c5fd [1.7 itext-paulo-155 (itextpdf.sf.net-lowagie.com) / pdftk 2.02 - www.pdftk.com] (PDF.js: 2.4.43) viewer.js:1228:15
Warning: AcroForm/XFA is not supported viewer.js:1250:17
downloadable font: maxp: Bad maxZones: 0 (font-family: "g_d0_f1" style:normal weight:400 stretch:100 src index:0) source: (invalid URI)
downloadable font: maxp: Bad maxZones: 0 (font-family: "g_d0_f2" style:normal weight:400 stretch:100 src index:0) source: (invalid URI)
downloadable font: OS/2: Error reading more basic table fields (font-family: "g_d0_f2" style:normal weight:400 stretch:100 src index:0) source: (invalid URI)
downloadable font: OS/2: Failed to parse table (font-family: "g_d0_f2" style:normal weight:400 stretch:100 src index:0) source: (invalid URI)
downloadable font: rejected by sanitizer (font-family: "g_d0_f2" style:normal weight:400 stretch:100 src index:0) source: (invalid URI)
downloadable font: font load failed (font-family: "g_d0_f2" style:normal weight:400 stretch:100 src index:0) source: (invalid URI)
Warning: Failed to load font 'g_d0_f2': 'SyntaxError: An invalid or illegal string was specified'. pdf.js:537:13
downloadable font: maxp: Bad maxZones: 0 (font-family: "g_d0_f3" style:normal weight:400 stretch:100 src index:0) source: (invalid URI)
For now I use version 1.5.1 for my project, but I hope you understand the problem. My knowledge of PDF and TTF is not deep enough for this problem.
/home/travis/.rvm/gems/ruby-2.3.1/gems/ttfunk-1.4.0/lib/ttfunk/table.rb:25: warning: instance variable @offset not initialized
Hi all!
I'm facing an issue where the kerning isn't read properly from the font.
file_name = ARGV.first || File.join(__dir__, './my_font.ttf')
file = TTFunk::File.open(file_name)
puts "kerning? : #{file.kerning.exists?}"
result in kerning? : false
The font does contain kerning and I've verified it by installing it in the MacOS Font Book and used the font in TextEdit.
Any thoughts or similar experiences? Or even better, any solution?
http://prawn.majesticseacreature.com in the project description 404s
thanks!
Hi,
I was about to use asciidoctorj-pdf for my project (commercial), but I noticed that although that project is under Apache 2.0 licence, it contains ttfunk gem which seems to be under GPL. That means we cannot really use it without sharing our source code.
Is my understanding correct? In general asciidoctorj-pdf uses jruby which includs prawn which includes ttfunk. Just ttfunk in the whole dependency tree is under GPL and not Ruby license.
Can you please clarify whether this prevents us using the parent library in a commercial project?
For reference: asciidoctor/asciidoctorj-pdf#69
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.