class OpenXml::Docx::Package

Attributes

document[R]
fonts[R]
footers[R]
headers[R]
image_names[R]
numbering[R]
settings[R]
styles[R]

Public Class Methods

new() click to toggle source
Calls superclass method
# File lib/openxml/docx/package.rb, line 30
def initialize
  super

  rels.add_relationship REL_DOCUMENT, "/word/document.xml"
  @settings = OpenXml::Docx::Parts::Settings.new
  @styles = OpenXml::Docx::Parts::Styles.new
  @fonts = OpenXml::Docx::Parts::Fonts.new
  @numbering = OpenXml::Docx::Parts::Numbering.new
  @document = OpenXml::Docx::Parts::Document.new
  @headers = []
  @footers = []
  @image_names = []

  document.relationships.add_relationship REL_STYLES, "styles.xml"
  document.relationships.add_relationship REL_SETTINGS, "settings.xml"
  document.relationships.add_relationship REL_FONT_TABLE, "fontTable.xml"
  document.relationships.add_relationship REL_NUMBERING, "numbering.xml"

  add_part "word/_rels/document.xml.rels", document.relationships
  add_part "word/_rels/fontTable.xml.rels", fonts.relationships
  add_part "word/document.xml", document
  add_part "word/settings.xml", settings
  add_part "word/styles.xml", styles
  add_part "word/fontTable.xml", fonts
  add_part "word/numbering.xml", numbering
end

Public Instance Methods

add_header(header) click to toggle source
# File lib/openxml/docx/package.rb, line 102
def add_header(header)
  headers << header
  header_name = "header#{headers.count}.xml"
  content_types.add_override "/word/#{header_name}", TYPE_HEADER
  add_part "word/#{header_name}", header
  add_part "word/_rels/#{header_name}.rels", header.relationships
  relationship = document.relationships.add_relationship REL_HEADER, header_name
  relationship.id
end
embed_image(path: nil, content_type: nil, into_part: nil) click to toggle source
# File lib/openxml/docx/package.rb, line 75
def embed_image(path: nil, content_type: nil, into_part: nil)
  return if path.nil?

  extension_match = path.match(/\.(?<extension>[^\.]+?)(?:\?.+)?$/)
  content_type ||= extension_match[:extension] if extension_match
  return if content_type.nil?

  URI.open(path, "rb") do |source_image|
    embed_image_data(data: source_image.read, content_type: content_type, into_part: into_part)
  end
end
embed_image_data(data: nil, content_type: nil, into_part: nil) click to toggle source
# File lib/openxml/docx/package.rb, line 87
def embed_image_data(data: nil, content_type: nil, into_part: nil)
  return if data.nil? || content_type.nil?
  into_part = document unless into_part.respond_to?(:relationships)

  content_type = "jpeg" if content_type == "jpg"
  content_type = content_type.to_sym

  destination_image_name = "image#{image_names.count + 1}.#{content_type}"
  add_part "word/media/#{destination_image_name}", OpenXml::Parts::UnparsedPart.new(data)
  image_names << destination_image_name

  image_relationship = into_part.relationships.add_relationship REL_IMAGE, "media/#{destination_image_name}"
  image_relationship.id
end
embed_truetype_font(path: nil, name: nil) click to toggle source
# File lib/openxml/docx/package.rb, line 57
def embed_truetype_font(path: nil, name: nil)
  URI.open(path, "rb") do |source_font|
    obfuscation_data = obfuscate_font source_font
    data = obfuscation_data[:bytes] << source_font.read
    destination_font_name = "font#{fonts.fonts.count + 1}.odttf"
    add_part "word/fonts/#{destination_font_name}", OpenXml::Parts::UnparsedPart.new(data)
    font_relationship = fonts.relationships.add_relationship REL_FONT, "fonts/#{destination_font_name}"

    font_description = OpenXml::Docx::Elements::Font.new
    font_description.font_name = name
    embed_tag = OpenXml::Docx::Elements::EmbedRegular.new
    embed_tag.font_key = "{#{obfuscation_data[:key]}}"
    embed_tag.relationship_id = font_relationship.id
    font_description << embed_tag
    fonts << font_description
  end
end

Private Instance Methods

obfuscate_font(font) click to toggle source
# File lib/openxml/docx/package.rb, line 124
def obfuscate_font(font)
  # From the OpenXml spec, section 17.8.1, the algorithm for obfuscating a font:
  # - Generate a GUID, which is used and stored as the obfuscation key
  # - Reverse the order of the bytes in the GUID (i.e. Big Endian ordering)
  # - XOR the value with the first 32 bytes of the binary: once against 0-15, once against 16-31
  # - Store the resulting file in the document, and store the obfuscation key in the fontKey attribute

  key = SecureRandom::uuid.upcase # Spec requires hex characters be uppercase
  raw_key = key.gsub("-", "")
  big_endian_key = [raw_key].pack("H*").bytes.reverse
  obfuscated_bytes = []
  2.times do
    bytes = font.read(16).bytes
    (0...16).each do |index|
      obfuscated_bytes << (bytes[index] ^ big_endian_key[index])
    end
  end

  { key: key, bytes: obfuscated_bytes.pack("C*") }
end