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
- IDENTITY =
Array.new(256) { |code| code }.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 =
"mono" must not swallow the Monotype foundry prefix: MonotypeCorsiva is a proportional script face, not a typewriter one.
/courier|mono(?!type)/- BOLD =
Likewise "black" is a weight only at the end of a word — Blackadder and Blackoak are display faces, Arial-Black and Roboto-Black are weights.
/bold|black(?![a-z])|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, build_font(WIDTHS[name], asc, desc, top, bottom)] end.freeze
Class Method Summary collapse
-
.build_font(widths, ascender, descender, top, bottom, remap = nil) ⇒ Object
A frozen Font, with its code-space width table derived once.
-
.canonical_name(base_font) ⇒ Object
Maps any BaseFont name onto one of the twelve standard-14 text cuts.
-
.code_width_table(widths, remap) ⇒ Object
Width of the glyph this font draws at each of the 256 byte codes.
-
.font_for(base_font) ⇒ Object
Metrics for a BaseFont name.
-
.remap_for(differences) ⇒ Object
A 256-entry code translation table for a font whose /Encoding moves glyphs off their WinAnsi codes, or nil when nothing moves.
-
.standard_font(base_font) ⇒ Object
Metrics for a name that is one of the standard 14, or nil.
-
.string_width(str, widths, size) ⇒ Object
Width of
stratsizepoints. - .widths_for(base_font) ⇒ Object
Class Method Details
.build_font(widths, ascender, descender, top, bottom, remap = nil) ⇒ Object
A frozen Font, with its code-space width table derived once.
228 229 230 231 |
# File 'lib/acrofill/metrics.rb', line 228 def self.build_font(widths, ascender, descender, top, bottom, remap = nil) Font.new(widths, ascender, descender, top, bottom, remap, code_width_table(widths, remap)).freeze end |
.canonical_name(base_font) ⇒ Object
Maps any BaseFont name onto one of the twelve standard-14 text cuts.
320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/acrofill/metrics.rb', line 320 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 |
.code_width_table(widths, remap) ⇒ Object
Width of the glyph this font draws at each of the 256 byte codes.
widths is indexed by WinAnsi code, but /Differences can move a glyph
onto any code — including one below 32, which no WinAnsi-indexed table
can express — and the appearance stream emits the moved code. Measuring
in the font's own code space is what keeps rendering and metrics in
step; a code the font draws nothing at is worth nothing, not the
average glyph width the old WinAnsi lookup fell back to.
240 241 242 243 244 245 246 247 |
# File 'lib/acrofill/metrics.rb', line 240 def self.code_width_table(widths, remap) table = Array.new(256, 0) (FIRST_CODE..LAST_CODE).each do |code| width = widths[code - FIRST_CODE] || DEFAULT_WIDTH table[remap ? remap[code] : code] = width end table.freeze 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.
304 305 306 |
# File 'lib/acrofill/metrics.rb', line 304 def self.font_for(base_font) FONTS.fetch(canonical_name(base_font)) end |
.remap_for(differences) ⇒ Object
A 256-entry code translation table for a font whose /Encoding moves
glyphs off their WinAnsi codes, or nil when nothing moves. differences
is => glyph name, as /Differences declares it.
265 266 267 268 269 270 271 272 273 274 |
# File 'lib/acrofill/metrics.rb', line 265 def self.remap_for(differences) return nil if differences.empty? at = drawn_at(differences) table = IDENTITY.dup WIN_ANSI_GLYPHS.each_with_index do |glyph, index| table[index + FIRST_CODE] = at[glyph] if at[glyph] end table == IDENTITY ? nil : table.freeze 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.
315 316 317 |
# File 'lib/acrofill/metrics.rb', line 315 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 256-entry code-space
table from #code_width_table, or a BaseFont name (resolved here, at
the cost of the lookup).
256 257 258 259 260 |
# File 'lib/acrofill/metrics.rb', line 256 def self.string_width(str, widths, size) widths = font_for(widths).code_widths unless widths.is_a?(Array) units = to_win_ansi(str).each_byte.sum { |code| widths[code] } units * size / 1000.0 end |
.widths_for(base_font) ⇒ Object
308 309 310 |
# File 'lib/acrofill/metrics.rb', line 308 def self.widths_for(base_font) font_for(base_font).widths end |