class DymoRender
Constants
- ALIGNS
- DIM_PT_SIZES
fixed number of dots per QR dot in each dimension:
- FONT_DIRS
- FONT_DIRS_LINUX
- FONT_DIRS_MACOS
- HORIZONTAL_LINE_VERTICAL_FUDGE_BY
- OVERFLOWS
- PDF_POINT
72 PDF points per inch
- POINTS_PER_DOT
(72 pts/in) / (300 dots/in)
- TWIP
1440 twips per inch (20 per PDF point)
- VALIGNS
- VERSION
Attributes
doc[R]
font_dirs[R]
params[R]
pdf[R]
qr_level[R]
Public Class Methods
font_file_for_family(font_dirs, family)
click to toggle source
# File lib/dymo_render.rb, line 72 def self.font_file_for_family(font_dirs, family) extensions = [".ttf", ".dfont", ".ttc"] names = extensions.map { |ext| [family, ext].join } font_dirs.each do |dir| names.each do |filename| file = File.join(dir, filename) return file if File.exists?(file) end end nil # not found end
new(xml:, font_dirs: FONT_DIRS, params: {}, qr_level: nil)
click to toggle source
# File lib/dymo_render.rb, line 30 def initialize(xml:, font_dirs: FONT_DIRS, params: {}, qr_level: nil) @xml = xml @font_dirs = font_dirs @params = params @qr_level = qr_level @doc = Nokogiri::XML(xml) end
Public Instance Methods
has_graphics?()
click to toggle source
# File lib/dymo_render.rb, line 47 def has_graphics? !doc.css('BarcodeObject').empty? end
landscape?()
click to toggle source
# File lib/dymo_render.rb, line 61 def landscape? orientation == :landscape end
orientation()
click to toggle source
# File lib/dymo_render.rb, line 51 def orientation @orientation ||= begin if doc.css("PaperOrientation").first&.text == "Landscape" :landscape else :portrait end end end
page_size()
click to toggle source
# File lib/dymo_render.rb, line 65 def page_size @page_size ||= begin elm = doc.css('PaperName').first elm && (PageSize.by_name(elm.text) || raise("unknown paper size #{elm.text}")) end end
render()
click to toggle source
# File lib/dymo_render.rb, line 38 def render build_pdf doc.css('ObjectInfo').each do |object| pdf.go_to_page 1 render_object(object) end pdf.render end
Private Instance Methods
align_from_text_object(text_object)
click to toggle source
# File lib/dymo_render.rb, line 215 def align_from_text_object(text_object) ALIGNS[text_object.css('HorizontalAlignment').first.text] || :left end
build_pdf()
click to toggle source
# File lib/dymo_render.rb, line 99 def build_pdf @pdf = Prawn::Document.new( page_size: page_size.dimension, margin: pdf_margin, page_layout: orientation, ) end
color_from_element(element)
click to toggle source
# File lib/dymo_render.rb, line 192 def color_from_element(element) red = element.attributes['Red'].value.to_i green = element.attributes['Green'].value.to_i blue = element.attributes['Blue'].value.to_i Prawn::Graphics::Color.rgb2hex([red, green, blue]) end
draw_rectangle_from_object(text_object, x, y, width, height)
click to toggle source
# File lib/dymo_render.rb, line 182 def draw_rectangle_from_object(text_object, x, y, width, height) background = text_object.css('BackColor').first return unless background alpha = background.attributes['Alpha'].value.to_i return if alpha.zero? pdf.fill_color color_from_element(background) pdf.rectangle [x, y], width, height pdf.fill end
overflow_from_text_object(text_object)
click to toggle source
# File lib/dymo_render.rb, line 224 def overflow_from_text_object(text_object) OVERFLOWS[text_object.css('TextFitMode').first.text] || :truncate end
pdf_height()
click to toggle source
# File lib/dymo_render.rb, line 91 def pdf_height landscape? ? page_size.dimension[0] : page_size.dimension[1] end
pdf_margin()
click to toggle source
# File lib/dymo_render.rb, line 87 def pdf_margin @pdf_margin ||= landscape? ? page_size.pdf_margin_landscape : page_size.pdf_margin end
pdf_width()
click to toggle source
# File lib/dymo_render.rb, line 95 def pdf_width landscape? ? page_size.dimension[1] : page_size.dimension[0] end
render_barcode_code128(barcode_object, x, y, width, height, type)
click to toggle source
# File lib/dymo_render.rb, line 289 def render_barcode_code128(barcode_object, x, y, width, height, type) content = barcode_object.css('Text').first.text code = Barby::Code128.new(content, type) outputter = Barby::PrawnOutputter.new(code) barcode_height = height barcode_y = y text_position = barcode_object.css("TextPosition").first&.text if %w{Top Bottom}.include?(text_position) font_element = barcode_object.css("TextFont").first font_family = font_element.attribute("Family").value font_size = font_element.attribute("Size").value.to_f color = color_from_element(barcode_object.css("ForeColor").first) valign = VALIGNS[text_position] pdf.fill_color color font_file = self.class.font_file_for_family(font_dirs, font_family) pdf.font(font_file || raise("missing font #{font_family}")) # horizontal padding of 1 point x += 1 width -= 2 (box, actual_size) = text_box_with_font_size( content, size: font_size, character_spacing: 0, at: [x, y], width: width, height: height, overflow: :truncate, align: :center, valign: valign, disable_wrap_by_char: true, single_line: true, ) # on bottom-aligned boxes, Dymo counts the height of character descenders box.at[1] += box.descender if valign == :bottom box.render code_offset = box.height * 1.25 barcode_height -= code_offset barcode_y -= code_offset if valign == :top end num_dots_x = outputter.full_width xdim = width.to_f / num_dots_x center_x = x + width.to_f / 2 # ensure mandatory 10 module widths of space on left and right of barcode: # https://en.wikipedia.org/wiki/Code_128#Quiet_zone max_code_width = width - 20 * xdim if (num_dots_x * xdim) > max_code_width width = max_code_width xdim = max_code_width / num_dots_x end # center in the x direction xpos = center_x - width.to_f / 2 ypos = barcode_y - barcode_height outputter.annotate_pdf(pdf, { x: xpos, y: ypos, xdim: xdim, height: barcode_height }); end
render_barcode_object(barcode_object, x, y, width, height)
click to toggle source
# File lib/dymo_render.rb, line 258 def render_barcode_object(barcode_object, x, y, width, height) case barcode_type = barcode_object.css('Type').first.text when 'QRCode' content = barcode_object.css('Text').first.text code = Barby::QrCode.new(content, level: qr_level) outputter = Barby::PrawnOutputter.new(code) num_dots = outputter.full_width # number of dots in QR size = barcode_object.css('Size').first&.text || "Small" dim = DIM_PT_SIZES[size] # If the resulting size in either dimension is smaller than that # dimension's specified size, adjust so the code is centered in the # bounding box. xadjust = (width - dim * num_dots).to_f / 2 yadjust = (height - dim * num_dots).to_f / 2 xpos = x + xadjust ypos = y - height + yadjust outputter.annotate_pdf(pdf, { x: xpos, y: ypos, xdim: dim, ydim: dim, unbleed: 0.05 }); when 'Code128Auto' render_barcode_code128(barcode_object, x, y, width, height, nil) when 'Code128A', 'Code128B', 'Code128C' type = barcode_type.sub('Code128', '') render_barcode_code128(barcode_object, x, y, width, height, type) else puts "unknown barcode type: #{barcode_type}" end end
render_object(object_info)
click to toggle source
# File lib/dymo_render.rb, line 107 def render_object(object_info) bounds = object_info.css('Bounds').first.attributes x = (bounds['X'].value.to_f * PDF_POINT / TWIP) - pdf_margin[3] y = pdf_height - (bounds['Y'].value.to_f * PDF_POINT / TWIP) - pdf_margin[2] width = (bounds['Width'].value.to_f * PDF_POINT / TWIP) height = (bounds['Height'].value.to_f * PDF_POINT / TWIP) object = object_info.children.find(&:element?) case object.name when 'TextObject' render_text_object(object, x, y, width, height) when 'ShapeObject' render_shape_object(object, x, y, width, height) when 'BarcodeObject' render_barcode_object(object, x, y, width, height) else puts "unsupported object type: #{object&.name}" end end
render_shape_object(shape_object, x, y, width, height)
click to toggle source
# File lib/dymo_render.rb, line 230 def render_shape_object(shape_object, x, y, width, height) case shape_type = shape_object.css('ShapeType').first.text when 'HorizontalLine' pdf.line_width height pdf.horizontal_line x, x + width, at: y + HORIZONTAL_LINE_VERTICAL_FUDGE_BY pdf.stroke when 'Rectangle' top_left = [x, y] line_width = shape_object.css("LineWidth").first.text.to_f / TWIP * PDF_POINT pdf.line_width line_width pdf.rectangle top_left, width, height pdf.stroke else puts "unknown shape type #{shape_type}" end end
render_text_object(text_object, x, y, width, height)
click to toggle source
# File lib/dymo_render.rb, line 127 def render_text_object(text_object, x, y, width, height) foreground = text_object.css('ForeColor').first color = color_from_element(foreground) draw_rectangle_from_object(text_object, x, y, width, height) verticalized = text_object.css('Verticalized').first verticalized &&= verticalized.text == 'True' name = text_object.css('Name').first name &&= name.text elements = text_object.css('StyledText Element') return if elements.empty? font = elements.first.css('Attributes Font').first font_family = font ? font.attributes['Family'].value : 'Helvetica' size = font.attributes['Size'].value.to_i strings = elements.map do |element| string = @params[name] || element.css('String').first.text string = string.each_char.map { |c| [c, "\n"] }.flatten.join if verticalized string end valign = valign_from_text_object(text_object) begin pdf.fill_color color font_file = self.class.font_file_for_family(font_dirs, font_family) pdf.font(font_file || raise("missing font #{font_family}")) # horizontal padding of 1 point x += 1 width -= 2 (box, actual_size) = text_box_with_font_size( strings.join, size: size, character_spacing: 0, at: [x, y], width: width, height: height, overflow: overflow_from_text_object(text_object), align: align_from_text_object(text_object), valign: valign, disable_wrap_by_char: true, single_line: !verticalized ) # on bottom-aligned boxes, Dymo counts the height of character descenders box.at[1] += box.descender if valign == :bottom box.render rescue Prawn::Errors::CannotFit puts 'cannot fit' end end
text_box_with_font_size(text, options = {})
click to toggle source
# File lib/dymo_render.rb, line 175 def text_box_with_font_size(text, options = {}) box = Prawn::Text::Box.new(text, options.merge(document: pdf)) box.render(dry_run: true) size = box.instance_eval { @font_size } [box, size] end
valign_from_text_object(text_object)
click to toggle source
# File lib/dymo_render.rb, line 205 def valign_from_text_object(text_object) VALIGNS[text_object.css('VerticalAlignment').first.text] || :top end