1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
# frozen_string_literal: true
require 'stringio'
require_relative 'placeholder'
module TTFunk
# Risen when the final encoded string was requested but there were some
# unresolved placeholders in it.
class UnresolvedPlaceholderError < StandardError
end
# Risen when a placeholder is added to an Encoded String but it already
# contains a placeholder with the same name.
class DuplicatePlaceholderError < StandardError
end
# Encoded string takes care of placeholders in binary strings. Placeholders
# are used when bytes need to be placed in the stream before their value is
# known.
#
# @api private
class EncodedString
# @yieldparam [self]
def initialize
yield(self) if block_given?
end
# Append to string.
#
# @param obj [String, Placeholder, EncodedString]
# @return [self]
def <<(obj)
case obj
when String
io << obj
when Placeholder
add_placeholder(obj)
io << ("\0" * obj.length)
when self.class
# adjust placeholders to be relative to the entire encoded string
obj.placeholders.each_pair do |_, placeholder|
add_placeholder(placeholder.dup, placeholder.position + io.length)
end
io << obj.unresolved_string
end
self
end
# Append multiple objects.
#
# @param objs [Array<String, Placeholder, EncodedString>]
# @return [self]
def concat(*objs)
objs.each do |obj|
self << obj
end
self
end
# Append padding to align string to the specified word width.
#
# @param width [Integer]
# @return [self]
def align!(width = 4)
if (length % width).positive?
self << ("\0" * (width - (length % width)))
end
self
end
# Length of this string.
#
# @return [Integer]
def length
io.length
end
# Raw string.
#
# @return [String]
# @raise [UnresolvedPlaceholderError] if there are any unresolved
# placeholders left.
def string
unless placeholders.empty?
raise UnresolvedPlaceholderError,
"string contains #{placeholders.size} unresolved placeholder(s)"
end
io.string
end
# Raw bytes.
#
# @return [Array<Integer>]
# @raise [UnresolvedPlaceholderError] if there are any unresolved
# placeholders left.
def bytes
string.bytes
end
# Unresolved raw string.
#
# @return [String]
def unresolved_string
io.string
end
# Resolve placeholder.
#
# @param name [Symbol]
# @param value [String]
# @return [void]
def resolve_placeholder(name, value)
last_pos = io.pos
if (placeholder = placeholders[name])
io.seek(placeholder.position)
io.write(value[0..placeholder.length])
placeholders.delete(name)
end
ensure
io.seek(last_pos)
end
# Plaholders
#
# @return [Hash{Symbol => Plaholder}]
def placeholders
@placeholders ||= {}
end
private
def add_placeholder(new_placeholder, pos = io.pos)
if placeholders.include?(new_placeholder.name)
raise DuplicatePlaceholderError,
"placeholder #{new_placeholder.name} already exists"
end
new_placeholder.position = pos
placeholders[new_placeholder.name] = new_placeholder
end
def io
@io ||= StringIO.new(''.b).binmode
end
end
end
|