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
-
.advance(ch, face) ⇒ Object
Pen advance for one character, in dots.
-
.baseline(face) ⇒ Object
Baseline offset from the top of the field, in dots.
- .draw_char(canvas, x, y, ch, face, adv) ⇒ Object
- .draw_fallback_char(canvas, x, y, code, face, adv) ⇒ Object
- .draw_line(canvas, x, y, text, face) ⇒ Object
- .draw_vector_char(canvas, x, y, code, face, adv, tt) ⇒ Object
- .face_for(designator, height, width) ⇒ Object
-
.line_height(face) ⇒ Object
Height of one text line (canvas height and stacking step), in dots.
-
.measure(text, face) ⇒ Object
Rendered width of a line of text, in dots.
- .mono_vector ⇒ Object
- .prepare(text, face) ⇒ Object
- .printable_code(ch) ⇒ Object
-
.render_line(text, face) ⇒ Object
Render a single line of text into a fresh Canvas.
- .vector ⇒ Object
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_vector ⇒ Object
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 |