gettalong / hexapdf

Versatile PDF creation and manipulation for Ruby
https://hexapdf.gettalong.org
Other
1.21k stars 69 forks source link

TextLayout raises "No font set" for leading and double newlines #207

Closed nlinn closed 1 year ago

nlinn commented 1 year ago

Thank you for your work on hexapdf. I am currently migrating reports from prawn to hexapdf and I am using TextLayouter a lot, since I need to be in control of page breaks and how to exactly distribute contents on pages.

I came along the problem, that leading and double line breaks result in HexaPDF::Error "No font set" when calling .fit

require "hexapdf"

doc = HexaPDF::Document.new
layouter = HexaPDF::Layout::TextLayouter.new
doc.fonts.add("Helvetica")

examples = [
  "ordinary text",
  " ",
  "trailing newline\n",
  "newline\n between",

  # problematic variations
  "\nleading newline",
  "double\n\nnewline",
  "double\n   \n newline with space between",
  "    \nleading spaces and newline"
]

examples.each_with_index do |example, index|
  fragment = HexaPDF::Layout::TextFragment.create(example, {font: doc.fonts.add("Helvetica")})
  layouter.fit([fragment], 100, 500)
  puts "Example #{index} passed"
rescue => e
  puts "Example #{index} raises #{e.class} #{e.message}"
end

It would be no problem for me to strip leading newlines, but the texts itself are user generated and sometimes double newlines are used intentionally to seperate paragraphs by the users. Also "No font set" seems to be more of an internal problem.

Ruby 3.0.4 hexapdf 0.26.2

gettalong commented 1 year ago

Thanks!

You need to assign a style to the TextLayouter itself since it uses the information in the style for several things, e.g. first line indentation, selection of the text segmentation algorithm and for determining the size of empty lines.

Here is the adapted code:

require "hexapdf"

doc = HexaPDF::Document.new
# Not needed to add font beforehand
# doc.fonts.add("Helvetica")

# Create the style here and use it for the text layouter and fragments
style = HexaPDF::Layout::Style.create(font: doc.fonts.add("Helvetica"))
layouter = HexaPDF::Layout::TextLayouter.new(style)

examples = [
  "ordinary text",
  " ",
  "trailing newline\n",
  "newline\n between",

  # problematic variations
  "\nleading newline",
  "double\n\nnewline",
  "double\n   \n newline with space between",
  "    \nleading spaces and newline"
]

examples.each_with_index do |example, index|
  fragment = HexaPDF::Layout::TextFragment.create(example, style)
  layouter.fit([fragment], 100, 500)
  puts "Example #{index} passed"
rescue => e
  puts "Example #{index} raises #{e.class} #{e.message}"
end
nlinn commented 1 year ago

Many thanks for the quick response, I can confirm that adding a style to layouter itself solves the problem!