Module: ZplRender::Font

Defined in:
lib/zpl_render/font.rb

Overview

Text rendering with metrics and shapes matching Zebra output.

Glyphs come from the bundled Liberation Sans Bold (SIL OFL), whose metrics match Arial/Helvetica Bold, rasterized by the pure-Ruby TrueType engine. Zebra's scalable font 0 is CG Triumvirate Bold Condensed; the calibration constants below (measured against Labelary reference renders) reproduce its geometry:

  • :scalable (font 0 and other scalable designators): proportional advances = hmtx/upm * ADVANCE_SCALE * w; cap height = CAP_SCALE * h.
  • :bitmap (fonts A-H): fixed-pitch cells, advance = cell width, glyph condensed to fit the cell. Font B is uppercase-only, like the hardware. (Real bitmap fonts are chunky dot-matrix faces; we render them with the vector face at cell metrics, like Labelary does.)

If the bundled TTF cannot be loaded, an embedded public-domain 8x8 bitmap font (font8x8, IBM VGA derived) is used as a fallback.

Defined Under Namespace

Classes: Face

Constant Summary collapse

BASE_CELLS =

designator => [base height, base width, inter-character gap] in dots (per the ZPL manual's bitmapped-fonts matrix table)

{
  "A" => [9, 5, 1], "B" => [11, 7, 2], "C" => [18, 10, 2],
  "D" => [18, 10, 2], "E" => [28, 15, 5], "F" => [26, 13, 3],
  "G" => [60, 40, 8], "H" => [21, 13, 6]
}.freeze
SCALABLE_BASE =
[15, 12, 0].freeze
ADVANCE_SCALE =

em width per ^A w unit, measured against Labelary

0.86
CAP_SCALE =

cap height per ^A h unit, measured against Labelary

0.76
BITMAP_CAP_SCALE =

matrix fonts reserve more cell for descenders

0.72
GLYPH_EXTENTS =

Per-glyph [leftmost ink column, ink width in columns], nil for blank.

FONT8X8.map do |glyph|
  min = 8
  max = -1
  glyph.each do |row|
    next if row.zero?

    8.times do |bit|
      next unless (row >> bit) & 1 == 1

      min = bit if bit < min
      max = bit if bit > max
    end
  end
  max >= 0 ? [min, max - min + 1].freeze : nil
end.freeze

Class Method Summary collapse

Class Method Details

.advance(ch, face) ⇒ Object

Pen advance for one character, in dots.



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/zpl_render/font.rb', line 85

def advance(ch, face)
  return face.char_w + face.gap if face.style == :bitmap

  code = printable_code(ch)
  if (tt = vector)
    [(tt.advance(code) / tt.units_per_em.to_f * ADVANCE_SCALE * face.char_w).round, 1].max
  else
    afm = HELVETICA_WIDTHS[code - 32] || 556
    [(afm / 1000.0 * ADVANCE_SCALE * face.char_w).round, 1].max
  end
end

.baseline(face) ⇒ Object

Baseline offset from the top of the field, in dots.



108
109
110
111
# File 'lib/zpl_render/font.rb', line 108

def baseline(face)
  scale = face.style == :bitmap ? BITMAP_CAP_SCALE : CAP_SCALE
  (scale * face.char_h).round
end

.draw_char(canvas, x, y, ch, face, adv) ⇒ Object



141
142
143
144
145
146
147
148
149
# File 'lib/zpl_render/font.rb', line 141

def draw_char(canvas, x, y, ch, face, adv)
  code = printable_code(ch)
  tt = face.style == :bitmap ? mono_vector : vector
  if tt
    draw_vector_char(canvas, x, y, code, face, adv, tt)
  else
    draw_fallback_char(canvas, x, y, code, face, adv)
  end
end

.draw_fallback_char(canvas, x, y, code, face, adv) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/zpl_render/font.rb', line 190

def draw_fallback_char(canvas, x, y, code, face, adv)
  glyph = FONT8X8[code]
  extents = GLYPH_EXTENTS[code]
  return unless glyph && extents

  cell_h = (face.char_h * 0.99).round
  bearing = [(adv * 0.1).round, 1].max
  target_w = [adv - 2 * bearing, 1].max
  left, src_w = face.style == :bitmap ? [0, 8] : extents
  (0...cell_h).each do |py|
    row = glyph[py * 8 / cell_h]
    next if row.zero?

    (0...target_w).each do |px|
      bit = left + px * src_w / target_w
      canvas.set(x + bearing + px, y + py) if (row >> bit) & 1 == 1
    end
  end
end

.draw_line(canvas, x, y, text, face) ⇒ Object



131
132
133
134
135
136
137
138
139
# File 'lib/zpl_render/font.rb', line 131

def draw_line(canvas, x, y, text, face)
  pen = x
  prepare(text, face).each_char do |ch|
    adv = advance(ch, face)
    draw_char(canvas, pen, y, ch, face, adv)
    pen += adv
  end
  pen - x
end

.draw_vector_char(canvas, x, y, code, face, adv, tt) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/zpl_render/font.rb', line 151

def draw_vector_char(canvas, x, y, code, face, adv, tt)
  upm = tt.units_per_em.to_f
  scale_y = baseline(face) / tt.cap_height.to_f
  if face.style == :scalable
    scale_x = ADVANCE_SCALE * face.char_w / upm
    pen_x = x
  else
    # scale the mono advance to the matrix cell width, glyph centered
    natural = tt.advance(code) / upm # em units
    scale_x = face.char_w / natural / upm
    glyph_adv = (tt.advance(code) * scale_x).round
    pen_x = x + (adv - face.gap - glyph_adv) / 2
  end
  glyph = tt.glyph_bitmap(code, scale_x, scale_y)
  return unless glyph

  base_y = y + baseline(face)
  canvas.blit(glyph.canvas, pen_x + glyph.left, base_y - glyph.top)
end

.face_for(designator, height, width) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/zpl_render/font.rb', line 63

def face_for(designator, height, width)
  name = designator.to_s.upcase
  name = "0" if name.empty?
  style = BASE_CELLS.key?(name) ? :bitmap : :scalable
  base_h, base_w, base_gap = BASE_CELLS[name] || SCALABLE_BASE

  h = height && height.positive? ? height : nil
  w = width && width.positive? ? width : nil
  if style == :scalable
    h ||= w || base_h
    w ||= h
    gap = 0
  else
    h ||= w ? (w * base_h / base_w) : base_h
    w ||= (h * base_w.to_f / base_h).round
    gap = [(base_gap.to_f * w / base_w).round, 1].max
  end
  Face.new(style: style, char_h: h.clamp(2, 32_000), char_w: w.clamp(2, 32_000),
           gap: gap, upcase: name == "B")
end

.line_height(face) ⇒ Object

Height of one text line (canvas height and stacking step), in dots.



103
104
105
# File 'lib/zpl_render/font.rb', line 103

def line_height(face)
  face.char_h
end

.measure(text, face) ⇒ Object

Rendered width of a line of text, in dots.



98
99
100
# File 'lib/zpl_render/font.rb', line 98

def measure(text, face)
  prepare(text, face).each_char.sum { |ch| advance(ch, face) }
end

.mono_vectorObject



53
54
55
56
57
58
59
60
61
# File 'lib/zpl_render/font.rb', line 53

def mono_vector
  return @mono_vector if defined?(@mono_vector)

  @mono_vector = begin
    TrueType.mono
  rescue StandardError
    vector
  end
end

.prepare(text, face) ⇒ Object



113
114
115
# File 'lib/zpl_render/font.rb', line 113

def prepare(text, face)
  face.upcase ? text.to_s.upcase : text.to_s
end

.printable_code(ch) ⇒ Object



117
118
119
120
# File 'lib/zpl_render/font.rb', line 117

def printable_code(ch)
  code = ch.ord
  code < 32 || code == 127 ? "?".ord : code
end

.render_line(text, face) ⇒ Object

Render a single line of text into a fresh Canvas.



123
124
125
126
127
128
129
# File 'lib/zpl_render/font.rb', line 123

def render_line(text, face)
  text = prepare(text, face)
  width = [measure(text, face), 1].max
  canvas = Canvas.new(width, [line_height(face), 1].max)
  draw_line(canvas, 0, 0, text, face)
  canvas
end

.vectorObject



43
44
45
46
47
48
49
50
51
# File 'lib/zpl_render/font.rb', line 43

def vector
  return @vector if defined?(@vector)

  @vector = begin
    TrueType.default
  rescue StandardError
    nil
  end
end