This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'afm'
|
||||
require 'pdf/reader/synchronized_cache'
|
||||
|
||||
class PDF::Reader
|
||||
module WidthCalculator
|
||||
|
||||
# Type1 fonts can be one of 14 "built in" standard fonts. In these cases,
|
||||
# the reader is expected to have it's own copy of the font metrics.
|
||||
# see Section 9.6.2.2, PDF 32000-1:2008, pp 256
|
||||
class BuiltIn
|
||||
|
||||
BUILTINS = [
|
||||
:Courier, :"Courier-Bold", :"Courier-BoldOblique", :"Courier-Oblique",
|
||||
:Helvetica, :"Helvetica-Bold", :"Helvetica-BoldOblique", :"Helvetica-Oblique",
|
||||
:Symbol,
|
||||
:"Times-Roman", :"Times-Bold", :"Times-BoldItalic", :"Times-Italic",
|
||||
:ZapfDingbats
|
||||
]
|
||||
|
||||
def initialize(font)
|
||||
@font = font
|
||||
@@all_metrics ||= PDF::Reader::SynchronizedCache.new
|
||||
|
||||
basefont = extract_basefont(font.basefont)
|
||||
metrics_path = File.join(File.dirname(__FILE__), "..","afm","#{basefont}.afm")
|
||||
|
||||
if File.file?(metrics_path)
|
||||
@metrics = @@all_metrics[metrics_path] ||= AFM::Font.new(metrics_path)
|
||||
else
|
||||
raise ArgumentError, "No built-in metrics for #{font.basefont}"
|
||||
end
|
||||
end
|
||||
|
||||
def glyph_width(code_point)
|
||||
return 0 if code_point.nil? || code_point < 0
|
||||
|
||||
names = @font.encoding.int_to_name(code_point)
|
||||
metrics = names.map { |name|
|
||||
@metrics.char_metrics[name.to_s]
|
||||
}.compact.first
|
||||
|
||||
if metrics
|
||||
metrics[:wx]
|
||||
else
|
||||
@font.widths[code_point - 1] || 0
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def control_character?(code_point)
|
||||
match = @font.encoding.int_to_name(code_point).first.to_s[/\Acontrol..\Z/]
|
||||
match ? true : false
|
||||
end
|
||||
|
||||
def extract_basefont(font_name)
|
||||
if BUILTINS.include?(font_name)
|
||||
font_name.to_s
|
||||
else
|
||||
"Times-Roman"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
module WidthCalculator
|
||||
# CIDFontType0 or CIDFontType2 use DW (integer) and W (array) to determine
|
||||
# codepoint widths, note that CIDFontType2 will contain a true type font
|
||||
# program which could be used to calculate width, however, a conforming writer
|
||||
# is supposed to convert the widths for the codepoints used into the W array
|
||||
# so that it can be used.
|
||||
# see Section 9.7.4.1, PDF 32000-1:2008, pp 269-270
|
||||
class Composite
|
||||
|
||||
def initialize(font)
|
||||
@font = font
|
||||
@widths = PDF::Reader::CidWidths.new(@font.cid_default_width, @font.cid_widths)
|
||||
end
|
||||
|
||||
def glyph_width(code_point)
|
||||
return 0 if code_point.nil? || code_point < 0
|
||||
|
||||
w = @widths[code_point]
|
||||
# 0 is a valid width
|
||||
if w
|
||||
w.to_f
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
module WidthCalculator
|
||||
# Calculates the width of a glyph in a TrueType font
|
||||
class TrueType
|
||||
|
||||
def initialize(font)
|
||||
@font = font
|
||||
|
||||
if fd = @font.font_descriptor
|
||||
@missing_width = fd.missing_width
|
||||
else
|
||||
@missing_width = 0
|
||||
end
|
||||
end
|
||||
|
||||
def glyph_width(code_point)
|
||||
return 0 if code_point.nil? || code_point < 0
|
||||
glyph_width_from_font(code_point) || glyph_width_from_descriptor(code_point) || 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
#TODO convert Type3 units 1000 units => 1 text space unit
|
||||
def glyph_width_from_font(code_point)
|
||||
return if @font.widths.nil? || @font.widths.count == 0
|
||||
|
||||
# in ruby a negative index is valid, and will go from the end of the array
|
||||
# which is undesireable in this case.
|
||||
first_char = @font.first_char
|
||||
if first_char && first_char <= code_point
|
||||
@font.widths.fetch(code_point - first_char, @missing_width.to_i).to_f
|
||||
else
|
||||
@missing_width.to_f
|
||||
end
|
||||
end
|
||||
|
||||
def glyph_width_from_descriptor(code_point)
|
||||
# true type fonts will have most of their information contained
|
||||
# with-in a program inside the font descriptor, however the widths
|
||||
# may not be in standard PDF glyph widths (1000 units => 1 text space unit)
|
||||
# so this width will need to be scaled
|
||||
if fd = @font.font_descriptor
|
||||
if w = fd.glyph_width(code_point)
|
||||
w.to_f * fd.glyph_to_pdf_scale_factor.to_f
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
module WidthCalculator
|
||||
# Calculates the width of a glyph in a Type One or Type Three
|
||||
class TypeOneOrThree
|
||||
|
||||
def initialize(font)
|
||||
@font = font
|
||||
|
||||
if fd = @font.font_descriptor
|
||||
@missing_width = fd.missing_width
|
||||
else
|
||||
@missing_width = 0
|
||||
end
|
||||
end
|
||||
|
||||
def glyph_width(code_point)
|
||||
return 0 if code_point.nil? || code_point < 0
|
||||
return 0 if @font.widths.nil? || @font.widths.count == 0
|
||||
|
||||
# in ruby a negative index is valid, and will go from the end of the array
|
||||
# which is undesireable in this case.
|
||||
first_char = @font.first_char
|
||||
if first_char && first_char <= code_point
|
||||
@font.widths.fetch(code_point - first_char, @missing_width.to_i).to_f
|
||||
else
|
||||
@missing_width.to_f
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
module WidthCalculator
|
||||
# Type0 (or Composite) fonts are a "root font" that rely on a "descendant font"
|
||||
# to do the heavy lifting. The "descendant font" is a CID-Keyed font.
|
||||
# see Section 9.7.1, PDF 32000-1:2008, pp 267
|
||||
# so if we are calculating a Type0 font width, we just pass off to
|
||||
# the descendant font
|
||||
class TypeZero
|
||||
|
||||
def initialize(font)
|
||||
@font = font
|
||||
end
|
||||
|
||||
def glyph_width(code_point)
|
||||
return 0 if code_point.nil? || code_point < 0
|
||||
|
||||
if descendant_font = @font.descendantfonts.first
|
||||
descendant_font.glyph_width(code_point).to_f
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user