Skip to content

Commit

Permalink
Update AcroForm appearance generator to support fallback glyphs
Browse files Browse the repository at this point in the history
  • Loading branch information
gettalong committed Dec 23, 2023
1 parent 9efae9c commit 605e823
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 13 deletions.
23 changes: 14 additions & 9 deletions lib/hexapdf/type/acro_form/appearance_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -361,12 +361,16 @@ def draw_single_line_text(canvas, width, height, style, padding)
value, text_color = apply_javascript_formatting(@field.field_value)
style.fill_color = text_color if text_color
calculate_and_apply_font_size(value, style, width, height, padding)
fragment = HexaPDF::Layout::TextFragment.create(value, style)
line = HexaPDF::Layout::Line.new(@document.layout.text_fragments(value, style: style))

if @field.concrete_field_type == :comb_text_field
if @field.concrete_field_type == :comb_text_field && !value.empty?
unless @field.key?(:MaxLen)
raise HexaPDF::Error, "Missing or invalid dictionary field /MaxLen for comb text field"
end
unless line.items.size == 1
raise HexaPDF::Error, "Fallback glyphs are not yet supported with comb text fields"
end
fragment = line.items[0]
new_items = []
cell_width = width.to_f / @field[:MaxLen]
scaled_cell_width = cell_width / style.scaled_font_size.to_f
Expand All @@ -376,6 +380,7 @@ def draw_single_line_text(canvas, width, height, style, padding)
new_items << fragment.items.last
fragment.items.replace(new_items)
fragment.clear_cache
line.clear_cache
# Adobe always seems to add 1 to the first offset...
x_offset = 1 + (cell_width - style.scaled_item_width(fragment.items[0])) / 2.0
x = case @field.text_alignment
Expand All @@ -387,8 +392,8 @@ def draw_single_line_text(canvas, width, height, style, padding)
# Adobe seems to be left/right-aligning based on twice the border width
x = case @field.text_alignment
when :left then 2 * padding
when :right then [width - 2 * padding - fragment.width, 2 * padding].max
when :center then [(width - fragment.width) / 2.0, 2 * padding].max
when :right then [width - 2 * padding - line.width, 2 * padding].max
when :center then [(width - line.width) / 2.0, 2 * padding].max
end
end

Expand All @@ -400,12 +405,12 @@ def draw_single_line_text(canvas, width, height, style, padding)
style.font_size
y = padding + (height - 2 * padding - cap_height) / 2.0
y = padding - style.scaled_font_descender if y < 0
fragment.draw(canvas, x, y)
line.each {|fragment, fx, _| fragment.draw(canvas, x + fx, y) }
end

# Draws multiple lines of text inside the widget's rectangle.
def draw_multiline_text(canvas, width, height, style, padding)
items = [Layout::TextFragment.create(@field.field_value, style)]
items = @document.layout.text_fragments(@field.field_value, style: style)
layouter = Layout::TextLayouter.new(style)
layouter.style.text_align(@field.text_alignment).line_spacing(:proportional, 1.25)

Expand Down Expand Up @@ -437,7 +442,7 @@ def draw_list_box(canvas, width, height, style, padding)

option_items = @field.option_items
top_index = @field.list_box_top_index
items = [Layout::TextFragment.create(option_items[top_index..-1].join("\n"), style)]
items = @document.layout.text_fragments(option_items[top_index..-1].join("\n"), style: style)
# Should use /I but if it differs from /V, we need to use /V; so just use /V...
indices = [@field.field_value].flatten.compact.map {|val| option_items.index(val) }

Expand Down Expand Up @@ -490,8 +495,8 @@ def calculate_and_apply_font_size(value, style, width, height, padding)
font.scaling_factor / 1000.0
# The constant factor was found empirically by checking what Adobe Reader etc. do
style.font_size = (height - 2 * padding) / unit_font_size * 0.85
fragment = HexaPDF::Layout::TextFragment.create(value, style)
style.font_size = [style.font_size, style.font_size * (width - 4 * padding) / fragment.width].min
calc_width = @document.layout.text_fragments(value, style: style).sum(&:width)
style.font_size = [style.font_size, style.font_size * (width - 4 * padding) / calc_width].min
style.clear_cache
end

Expand Down
35 changes: 31 additions & 4 deletions test/hexapdf/type/acro_form/test_appearance_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -598,20 +598,29 @@ def assert_format(arg_string, result, range)
end

it "creates the /N appearance stream according to the set string" do
@field.field_value = 'Text'
@doc.config['font.on_invalid_glyph'] = lambda do |codepoint, _|
[@doc.fonts.add('ZapfDingbats').decode_codepoint(codepoint)]
end
@field.set_default_appearance_string(font_color: "red")
@field[:V] = 'Te ✂ xt'
@generator.create_appearances
assert_operators(@widget[:AP][:N].stream,
[[:begin_marked_content, [:Tx]],
[:save_graphics_state],
[:append_rectangle, [1, 1, 98, 9.25]],
[:clip_path_non_zero],
[:end_path],
[:set_font_and_size, [:F1, 6.801471]],
[:set_font_and_size, [:F1, 10]],
[:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
[:begin_text],
[:set_text_matrix, [1, 0, 0, 1, 2, 3.183272]],
[:show_text, ["Text"]],
[:set_text_matrix, [1, 0, 0, 1, 2, 2.035]],
[:show_text, ["Te "]],
[:set_font_and_size, [:F2, 10]],
[:move_text, [14.45, 0]],
[:show_text, ["\""]],
[:set_font_and_size, [:F1, 10]],
[:move_text, [9.61, 0]],
[:show_text, [" xt"]],
[:end_text],
[:restore_graphics_state],
[:end_marked_content]])
Expand Down Expand Up @@ -774,11 +783,29 @@ def assert_format(arg_string, result, range)
[:end_marked_content]])
end

it "works for empty strings" do
@field[:V] = ''
@generator.create_appearances
assert_operators(@widget[:AP][:N].stream,
[[:begin_text],
[:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
[:end_text]], range: 6..8)
end

it "fails if the /MaxLen key is not set" do
@field.delete(:MaxLen)
@field[:V] = 't'
assert_raises(HexaPDF::Error) { @generator.create_appearances }
end

it "fails if fallback glyphs are needed for rendering" do
@doc.config['font.on_invalid_glyph'] = lambda do |codepoint, _|
[@doc.fonts.add('ZapfDingbats').decode_codepoint(codepoint)]
end
@field[:V] = 'Test ✂'
ex = assert_raises(HexaPDF::Error) { @generator.create_appearances }
assert_match(/Fallback glyphs/, ex.message)
end
end

describe "choice fields" do
Expand Down

0 comments on commit 605e823

Please sign in to comment.