Module: Acrofill::Metrics

Defined in:
lib/acrofill/metrics.rb

Overview

Glyph widths (1/1000 em) for the standard-14 text fonts, indexed by WinAnsiEncoding code minus 32 (appearance streams are written as Windows-1252 bytes against a /WinAnsiEncoding font, so the byte emitted is the byte measured). Extracted from Adobe AFM metrics.

Only six width tables are stored: the oblique cuts of Helvetica have the same widths as the upright face, and the whole Courier family is monospaced. Vertical metrics (ascender, descender, FontBBox extent) are per cut, since they differ where widths do not.

Defined Under Namespace

Classes: Font

Constant Summary collapse

DEFAULT_WIDTH =
556
COURIER_WIDTH =
600
FIRST_CODE =
32
LAST_CODE =
255
WINDOWS_1252 =
Encoding::Windows_1252
BYTE_ENCODINGS =

Encodings whose bytes are already the codes the font is drawn with.

[Encoding::Windows_1252, Encoding::BINARY].freeze
CUTS =

The standard-14 text cuts, indexed by bold + 2 * italic per family.

{
  helvetica: %w[Helvetica Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique].freeze,
  times: %w[Times-Roman Times-Bold Times-Italic Times-BoldItalic].freeze,
  courier: %w[Courier Courier-Bold Courier-Oblique Courier-BoldOblique].freeze
}.freeze
SERIF =

Family and weight hints for BaseFont names outside the standard 14.

/times|roman|serif|georgia|garamond/
SANS =
/sans/
FIXED =
/courier|mono/
BOLD =
/bold|black|heavy/
ITALIC =
/italic|oblique/
TABLES =
{
  'Helvetica' => [
    278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556,
    556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667,
    667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722,
    667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500,
    556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278,
    556, 500, 722, 500, 500, 500, 334, 260, 334, 584, nil, 556, nil, 222, 556, 333, 1000,
    556, 556, 333, 1000, 667, 333, 1000, nil, 611, nil, nil, 222, 222, 333, 333, 350, 556,
    1000, 333, 1000, 500, 333, 944, nil, 500, 667, 278, 333, 556, 556, 556, 556, 260, 556,
    333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 556, 537, 278, 333,
    333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667,
    667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722,
    722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556,
    278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556,
    500, 556, 500
  ].freeze,
  'Helvetica-Bold' => [
    278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556,
    556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722,
    722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722,
    667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556,
    611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333,
    611, 556, 778, 556, 556, 500, 389, 280, 389, 584, nil, 556, nil, 278, 556, 500, 1000,
    556, 556, 333, 1000, 667, 333, 1000, nil, 611, nil, nil, 278, 278, 500, 500, 350, 556,
    1000, 333, 1000, 556, 333, 944, nil, 500, 667, 278, 333, 556, 556, 556, 556, 280, 556,
    333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 611, 556, 278, 333,
    333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667,
    667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722,
    722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556,
    278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 584, 611, 611, 611, 611, 611,
    556, 611, 556
  ].freeze,
  'Times-Roman' => [
    250, 333, 408, 500, 500, 833, 778, 180, 333, 333, 500, 564, 250, 333, 250, 278, 500,
    500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 564, 564, 564, 444, 921, 722,
    667, 667, 722, 611, 556, 722, 722, 333, 389, 722, 611, 889, 722, 722, 556, 722, 667,
    556, 611, 722, 722, 944, 722, 722, 611, 333, 278, 333, 469, 500, 333, 444, 500, 444,
    500, 444, 333, 500, 500, 278, 278, 500, 278, 778, 500, 500, 500, 500, 333, 389, 278,
    500, 500, 722, 500, 500, 444, 480, 200, 480, 541, nil, 500, nil, 333, 500, 444, 1000,
    500, 500, 333, 1000, 556, 333, 889, nil, 611, nil, nil, 333, 333, 444, 444, 350, 500,
    1000, 333, 980, 389, 333, 722, nil, 444, 722, 250, 333, 500, 500, 500, 500, 200, 500,
    333, 760, 276, 500, 564, 333, 760, 333, 400, 564, 300, 300, 333, 500, 453, 250, 333,
    300, 310, 500, 750, 750, 750, 444, 722, 722, 722, 722, 722, 722, 889, 667, 611, 611,
    611, 611, 333, 333, 333, 333, 722, 722, 722, 722, 722, 722, 722, 564, 722, 722, 722,
    722, 722, 722, 556, 500, 444, 444, 444, 444, 444, 444, 667, 444, 444, 444, 444, 444,
    278, 278, 278, 278, 500, 500, 500, 500, 500, 500, 500, 564, 500, 500, 500, 500, 500,
    500, 500, 500
  ].freeze,
  'Times-Bold' => [
    250, 333, 555, 500, 500, 1000, 833, 278, 333, 333, 500, 570, 250, 333, 250, 278, 500,
    500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 930, 722,
    667, 722, 722, 667, 611, 778, 778, 389, 500, 778, 667, 944, 722, 778, 611, 778, 722,
    556, 667, 722, 722, 1000, 722, 722, 667, 333, 278, 333, 581, 500, 333, 500, 556, 444,
    556, 444, 333, 500, 556, 278, 333, 556, 278, 833, 556, 500, 556, 556, 444, 389, 333,
    556, 500, 722, 500, 500, 444, 394, 220, 394, 520, nil, 500, nil, 333, 500, 500, 1000,
    500, 500, 333, 1000, 556, 333, 1000, nil, 667, nil, nil, 333, 333, 500, 500, 350, 500,
    1000, 333, 1000, 389, 333, 722, nil, 444, 722, 250, 333, 500, 500, 500, 500, 220, 500,
    333, 747, 300, 500, 570, 333, 747, 333, 400, 570, 300, 300, 333, 556, 540, 250, 333,
    300, 330, 500, 750, 750, 750, 500, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667,
    667, 667, 389, 389, 389, 389, 722, 722, 778, 778, 778, 778, 778, 570, 778, 722, 722,
    722, 722, 722, 611, 556, 500, 500, 500, 500, 500, 500, 722, 444, 444, 444, 444, 444,
    278, 278, 278, 278, 500, 556, 500, 500, 500, 500, 500, 570, 500, 556, 556, 556, 556,
    500, 556, 500
  ].freeze,
  'Times-Italic' => [
    250, 333, 420, 500, 500, 833, 778, 214, 333, 333, 500, 675, 250, 333, 250, 278, 500,
    500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 675, 675, 675, 500, 920, 611,
    611, 667, 722, 611, 611, 722, 722, 333, 444, 667, 556, 833, 667, 722, 611, 722, 611,
    500, 556, 722, 611, 833, 611, 556, 556, 389, 278, 389, 422, 500, 333, 500, 500, 444,
    500, 444, 278, 500, 500, 278, 278, 444, 278, 722, 500, 500, 500, 500, 389, 389, 278,
    500, 444, 667, 444, 444, 389, 400, 275, 400, 541, nil, 500, nil, 333, 500, 556, 889,
    500, 500, 333, 1000, 500, 333, 944, nil, 556, nil, nil, 333, 333, 556, 556, 350, 500,
    889, 333, 980, 389, 333, 667, nil, 389, 556, 250, 389, 500, 500, 500, 500, 275, 500,
    333, 760, 276, 500, 675, 333, 760, 333, 400, 675, 300, 300, 333, 500, 523, 250, 333,
    300, 310, 500, 750, 750, 750, 500, 611, 611, 611, 611, 611, 611, 889, 667, 611, 611,
    611, 611, 333, 333, 333, 333, 722, 667, 722, 722, 722, 722, 722, 675, 722, 722, 722,
    722, 722, 556, 611, 500, 500, 500, 500, 500, 500, 500, 667, 444, 444, 444, 444, 444,
    278, 278, 278, 278, 500, 500, 500, 500, 500, 500, 500, 675, 500, 500, 500, 500, 500,
    444, 500, 444
  ].freeze,
  'Times-BoldItalic' => [
    250, 389, 555, 500, 500, 833, 778, 278, 333, 333, 500, 570, 250, 333, 250, 278, 500,
    500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 832, 667,
    667, 667, 722, 667, 667, 722, 778, 389, 500, 667, 611, 889, 722, 722, 611, 722, 667,
    556, 611, 722, 667, 889, 667, 611, 611, 333, 278, 333, 570, 500, 333, 500, 500, 444,
    500, 444, 333, 500, 556, 278, 278, 500, 278, 778, 556, 500, 500, 500, 389, 389, 278,
    556, 444, 667, 500, 444, 389, 348, 220, 348, 570, nil, 500, nil, 333, 500, 500, 1000,
    500, 500, 333, 1000, 556, 333, 944, nil, 611, nil, nil, 333, 333, 500, 500, 350, 500,
    1000, 333, 1000, 389, 333, 722, nil, 389, 611, 250, 389, 500, 500, 500, 500, 220, 500,
    333, 747, 266, 500, 606, 333, 747, 333, 400, 570, 300, 300, 333, 576, 500, 250, 333,
    300, 300, 500, 750, 750, 750, 500, 667, 667, 667, 667, 667, 667, 944, 667, 667, 667,
    667, 667, 389, 389, 389, 389, 722, 722, 722, 722, 722, 722, 722, 570, 722, 722, 722,
    722, 722, 611, 611, 500, 500, 500, 500, 500, 500, 500, 722, 444, 444, 444, 444, 444,
    278, 278, 278, 278, 500, 556, 500, 500, 500, 500, 500, 570, 500, 556, 556, 556, 556,
    444, 500, 444
  ].freeze,
  'Courier' => Array.new(LAST_CODE - FIRST_CODE + 1, COURIER_WIDTH).freeze
}.freeze
WIDTHS =

Cuts that share a width table with another cut.

TABLES.merge(
  'Helvetica-Oblique' => TABLES['Helvetica'],
  'Helvetica-BoldOblique' => TABLES['Helvetica-Bold'],
  'Courier-Bold' => TABLES['Courier'],
  'Courier-Oblique' => TABLES['Courier'],
  'Courier-BoldOblique' => TABLES['Courier']
).freeze
VERTICAL =

[ascender, descender, FontBBox top, FontBBox bottom] in 1/1000 em, from the same Adobe AFM data as the widths. pdftk places baselines from the ascender and spaces multiline rows by the FontBBox extent, so these drive vertical geometry.

{
  'Helvetica' => [718, -207, 931, -225].freeze,
  'Helvetica-Bold' => [718, -207, 962, -228].freeze,
  'Helvetica-Oblique' => [718, -207, 931, -225].freeze,
  'Helvetica-BoldOblique' => [718, -207, 962, -228].freeze,
  'Times-Roman' => [683, -217, 898, -218].freeze,
  'Times-Bold' => [683, -217, 935, -218].freeze,
  'Times-Italic' => [683, -217, 883, -217].freeze,
  'Times-BoldItalic' => [683, -217, 921, -218].freeze,
  'Courier' => [629, -157, 805, -250].freeze,
  'Courier-Bold' => [629, -157, 801, -250].freeze,
  'Courier-Oblique' => [629, -157, 805, -250].freeze,
  'Courier-BoldOblique' => [629, -157, 801, -250].freeze
}.freeze
WIN_ANSI_GLYPHS =

WinAnsiEncoding glyph names, index = code - 32. Used to re-encode a value for a font whose /Encoding remaps codes: the value is turned into glyph names, then into the codes this font draws them at.

[
  :space, :exclam, :quotedbl, :numbersign, :dollar, :percent, :ampersand, :quotesingle,
  :parenleft, :parenright, :asterisk, :plus, :comma, :hyphen, :period, :slash, :zero,
  :one, :two, :three, :four, :five, :six, :seven, :eight, :nine, :colon, :semicolon,
  :less, :equal, :greater, :question, :at, :A, :B, :C, :D, :E, :F, :G, :H, :I, :J, :K,
  :L, :M, :N, :O, :P, :Q, :R, :S, :T, :U, :V, :W, :X, :Y, :Z, :bracketleft, :backslash,
  :bracketright, :asciicircum, :underscore, :grave, :a, :b, :c, :d, :e, :f, :g, :h, :i,
  :j, :k, :l, :m, :n, :o, :p, :q, :r, :s, :t, :u, :v, :w, :x, :y, :z, :braceleft, :bar,
  :braceright, :asciitilde, :controlDEL, :Euro, nil, :quotesinglbase, :florin,
  :quotedblbase, :ellipsis, :dagger, :daggerdbl, :circumflex, :perthousand, :Scaron,
  :guilsinglleft, :OE, nil, :Zcaron, nil, nil, :quoteleft, :quoteright, :quotedblleft,
  :quotedblright, :bullet, :endash, :emdash, :tilde, :trademark, :scaron,
  :guilsinglright, :oe, nil, :zcaron, :Ydieresis, :space, :exclamdown, :cent, :sterling,
  :currency, :yen, :brokenbar, :section, :dieresis, :copyright, :ordfeminine,
  :guillemotleft, :logicalnot, :hyphen, :registered, :macron, :degree, :plusminus,
  :twosuperior, :threesuperior, :acute, :mu, :paragraph, :periodcentered, :cedilla,
  :onesuperior, :ordmasculine, :guillemotright, :onequarter, :onehalf, :threequarters,
  :questiondown, :Agrave, :Aacute, :Acircumflex, :Atilde, :Adieresis, :Aring, :AE,
  :Ccedilla, :Egrave, :Eacute, :Ecircumflex, :Edieresis, :Igrave, :Iacute, :Icircumflex,
  :Idieresis, :Eth, :Ntilde, :Ograve, :Oacute, :Ocircumflex, :Otilde, :Odieresis,
  :multiply, :Oslash, :Ugrave, :Uacute, :Ucircumflex, :Udieresis, :Yacute, :Thorn,
  :germandbls, :agrave, :aacute, :acircumflex, :atilde, :adieresis, :aring, :ae,
  :ccedilla, :egrave, :eacute, :ecircumflex, :edieresis, :igrave, :iacute, :icircumflex,
  :idieresis, :eth, :ntilde, :ograve, :oacute, :ocircumflex, :otilde, :odieresis,
  :divide, :oslash, :ugrave, :uacute, :ucircumflex, :udieresis, :yacute, :thorn,
  :ydieresis
].freeze
FONTS =
VERTICAL.to_h do |name, (asc, desc, top, bottom)|
  [name, Font.new(WIDTHS[name], asc, desc, top, bottom).freeze]
end.freeze

Class Method Summary collapse

Class Method Details

.canonical_name(base_font) ⇒ Object

Maps any BaseFont name onto one of the twelve standard-14 text cuts.



250
251
252
253
254
255
256
257
258
259
260
# File 'lib/acrofill/metrics.rb', line 250

def self.canonical_name(base_font)
  name = base_font.to_s
  return name if FONTS.key?(name)

  lower = name.downcase
  family = if FIXED.match?(lower) then :courier
           elsif serif?(lower) then :times
           else :helvetica
           end
  CUTS[family][weight_index(lower)]
end

.font_for(base_font) ⇒ Object

Metrics for a BaseFont name. Standard-14 names resolve directly; anything else (ArialMT, TimesNewRomanPS-BoldMT, a template's embedded face) is classified by family and weight, which is far closer than treating everything as Helvetica.



234
235
236
# File 'lib/acrofill/metrics.rb', line 234

def self.font_for(base_font)
  FONTS.fetch(canonical_name(base_font))
end

.standard_font(base_font) ⇒ Object

Metrics for a name that is one of the standard 14, or nil. Unlike #font_for this does not classify: a template's own face only inherits standard-14 vertical metrics when it actually names one.



245
246
247
# File 'lib/acrofill/metrics.rb', line 245

def self.standard_font(base_font)
  FONTS[base_font.to_s]
end

.string_width(str, widths, size) ⇒ Object

Width of str at size points. widths is a table from #widths_for, or a BaseFont name (resolved here, at the cost of the lookup).



221
222
223
224
225
226
227
228
# File 'lib/acrofill/metrics.rb', line 221

def self.string_width(str, widths, size)
  widths = widths_for(widths) unless widths.is_a?(Array)
  str = to_win_ansi(str)
  units = str.each_byte.sum do |code|
    (code >= FIRST_CODE && widths[code - FIRST_CODE]) || DEFAULT_WIDTH
  end
  units * size / 1000.0
end

.widths_for(base_font) ⇒ Object



238
239
240
# File 'lib/acrofill/metrics.rb', line 238

def self.widths_for(base_font)
  font_for(base_font).widths
end