Encoding problem when adding text to existing PDF with CombinePDF - ruby-on-rails

(Rails 6.0.2.2, ruby 2.7.1, combine_pdf 1.0.18)
I'm currently trying to write some text to an existing PDF with the CombinePDF gem. Unfortunately I'm running in some encoding problems.
I'm loading the existing PDF:
pdf = CombinePDF.load "#{Rails.root}/public/pdf/base.pdf"
Then I'm adding text to it:
pdf.pages[0].textbox "Straße", height: 20, width: 160, y: 527, x: 215, font_size: 12, box_color: nil, text_align: :left, text_padding: 0
When generating a new pdf out of it:
send_data pdf.to_pdf, filename: "output.pdf", type: "application/pdf"
the string gets displayed as StraˆŸe, so the ß isn't displayed correctly.
I also tried to replace it with unicode literals (\xc3\x9f) without any effect.
Anybody has an idea what else to try?

If you use HexaPDF you could do something like this:
require 'hexapdf'
require 'stringio'
doc = HexaPDF::Document.open("#{Rails.root}/public/pdf/base.pdf")
doc.pages[0].canvas.font("Helvetica", size: 12).text("Straße", at: [215, 527])
outio = StringIO.new(''.b)
doc.write(outio)
Just make sure that you use a font that contains glyphs for all characters you want to use. So better use a TrueType font instead of the builtin Helvetica (you can just provide the path to a TrueType font to the #font method).

Related

Arabic word in rails prawn shows reverse order

When I try to create a pdf in Arabic Language, I'm getting the letters in reverse order.
I use the following code to generate a pdf file.
note_section = {content: "#{order_item.try(:notes).try(:connect_arabic_letters)}" ,
size: 8,
font: "app/assets/fonts/ufonts.com_arial-unicode-ms.ttf",
text_color: "313131"}
Expected text is:
In prawn it looks like:
Gem version => Prawn version:2.2.2 Rails version:5.1.6
Any help is highly appreciated, thanks!

Prawn::Errors::IncompatibleStringEncoding: Your document includes text that's not compatible with the Windows-1252 character set

Below is my Prawn PDF file to generate a name on the PDF -
def initialize(opportunity_application)
pdf = Prawn::Document.new(:page_size => [1536, 2048], :page_layout => :landscape)
cell_1 = pdf.make_cell(content: "Eylül Çamcı".force_encoding('iso-8859-1').encode('utf-8'), borders: [], size: 66, :text_color => "000000", padding: [0,0,0,700], font: "app/assets/fonts/opensans.ttf")
t = pdf.make_table [[cell_1]]
t.draw
pdf.render_file "tmp/mos_certificates/application_test.pdf"
end
When rendering the name Eylül Çamcı which is Turkish, I get the following error -
Prawn::Errors::IncompatibleStringEncoding: Your document includes text that's not compatible with the Windows-1252 character set.
If you need full UTF-8 support, use TTF fonts instead of PDF's built-in fonts.
I'm already using a TTF font that supports the characters in that name, what can I do to print the name correctly?
It seams Turkish is missing in iso-8859-1.
On the other hand iso-8859-9 should work.
So you may try to change your code like (check the iso number that I changed):
...
cell_1 = pdf.make_cell(content: "Eylül Çamcı".force_encoding('iso-8859-9').encode('utf-8'), borders: [], size: 66, :text_color => "000000", padding: [0,0,0,700], font: "app/assets/fonts/opensans.ttf")
...
And a fun link which is not only related with character set but also other internalisation differences for Turkey.
Edit 1: I made a basic check, it seems the text is already in UTF-8. So why need to change to iso-8859 and come back to UTF-8?
Can you please try "Eylül Çamcı".force_encoding('utf-8') alone?
irb(main):013:0> "Eylül Çamcı".encoding
=> #<Encoding:UTF-8>
irb(main):014:0> "Eylül Çamcı".force_encoding('UTF-8')
=> "Eylül Çamcı"
irb(main):015:0>
Edit 2: Also can you check your font path? Both font exists and the path is proper?
#Rails.root.join('app/assets/fonts/opensans.ttf')
cell_1 = pdf.make_cell(content: "Eylül Çamcı".force_encoding('utf-8'), borders: [], size: 66, :text_color => "000000", padding: [0,0,0,700], font: Rails.root.join('app/assets/fonts/opensans.ttf'))
I'm not sure I remember how Prawn works, but PDF files don't support UTF-8, which is the default Ruby encoding for String objects.
In fact, PDF files only support ASCII encoding using internal fonts - any other encoding requires that you bring your own font (which is also recommended for portability).
The workaround is to either use character maps (CMaps) - either custom CMaps or pre-defined ones (BYO font).
Generally, PDF files include an embedded font (or a subset of a font), and a CMap, mapping the value of a byte (or, a number of bytes) to a desired font glyph. i.e. mapping 97, which is 'a' in ASCII, to the å glyph when using the specified font.
Last time I used Prawn, I think it supported TTF fonts and created font maps automatically using UTF-8 Strings for the text input - but you have to load an appropriate font into Prawn and remember to use it!.
You can see an example in this answer.
Good Luck!
EDIT
I updated the answer to reflect #mkl's comments.
#mkl pointed out that other encodings are supported or possible (BYO font), including predefined some multibyte encoding (which use pre-defined CMaps).
From this anwser about Force strings to UTF-8 from any encoding :
"Forcing" an encoding is easy, however it won't convert the characters
just change the encoding:
str = str.force_encoding("UTF-8")
str.encoding.name # => 'UTF-8'
If you want to perform a conversion,
use encode
Indeed, as #MehmetKaplan said:
It seams Turkish is missing in iso-8859-1.
On the other hand iso-8859-9 should work.
Therefore, you won't need the force_encodinganymore but just encode
[37] pry(main)> "Eylül Çamcı".encode('iso-8859-1')
Encoding::UndefinedConversionError: U+0131 from UTF-8 to ISO-8859-1
from (pry):39:in `encode'
[38] pry(main)> "Eylül Çamcı".encode('iso-8859-9')
=> "Eyl\xFCl \xC7amc\xFD"
This mean you have to drop the UTF-8 entirely in your code.
content: "Eylül Çamcı".encode('iso-8859-9'),

How can I display a svg in prawn without saving a .svg file before?

For this moment, I generate a svg file and then I read it and I display it in my pdf:
qr = Barby::QrCode.new('test')
outputter = Barby::CairoOutputter.new(qr).to_svg
File.open('myfile.svg', 'wb'){|f| f.write outputter }
pdf.svg IO.read('myfile.svg'), width: 50, height: 50
My question is: how can I display my svg without saving a .svg file before. I know there is a css property that allow that but I can't use css with prawn...
Any ideas?
It's working like that:
pdf.svg outputter, width: 50, height: 50
thanks #Mark Thomas

Prawn: how to set document's printable dimensions

I'm generating a pdf with prawn. Basically, I generate the document and I fill it with some images.
The problem comes when I download the file and I try to print it. The dimensions are not set to the ones I previously specified.
pdf = Prawn::Document.new(page_size: "A3", margin: PAGE_MARGIN, page_layout: :landscape)
When I try to print it, the default page size is "A4" instead of "A3"
How can I solve this?
I tried to attach some metadata but it didn't work correctly.
Thanks in advance!
In the case where you're generating the document within its own class, this also works to declare the paper size:
class EnvelopePdf < Prawn::Document
def initialize(_item_array, _type_of_item)
super(:page_size => [324, 684], :page_layout => :landscape) # 4.5" by 9.5", which is No 10 envelopes
... application-specific initialization code here ...
print_the_envelopes
end
Using prawn 1.3.0:
require "prawn"
pdf = Prawn::Document.new(:page_size => 'A3')
pdf.text "Hello World!"
pdf.render_file("export.pdf")
in terminal:
pdfinfo export.pdf
outputs:
Creator: Prawn
Producer: Prawn
Tagged: no
Form: none
Pages: 1
Encrypted: no
Page size: 841.89 x 1190.55 pts
Page rot: 0
File size: 842 bytes
Optimized: no
PDF version: 1.3

Custom font faces in jsPDF?

Is it possible to include custom fonts in jsPDF ?
With the basic library, if I console log 'doc.getFontList()' I get:
Courier, Helvetica, Times, courier, helvetica, times
But, say I want to use 'Comic Sans' ( not that I would ;o) ) can it be done ?
Even better, could I use a font is locally stored and has been declared in the site with #font-face ?
I found this was possible by modifying jsPDF.js to expose the existing addFont method in the public API.
In jsPDF.js, look for:
//---------------------------------------
// Public API
Add the following:
API.addFont = function(postScriptName, fontName, fontStyle) {
addFont(postScriptName, fontName, fontStyle, 'StandardEncoding');
};
I put this method near other font methods for clarity - API.setFont, API.setFontSize, API.setFontType, etc.
Now in your code, use:
doc.addFont('ComicSansMS', 'Comic Sans', 'normal');
doc.setFont('Comic Sans');
doc.text(50,50,'Hello World');
This works for me with #font-face fonts included with css before loading jsPDF, as well as system fonts. There's probably a better way to do this using jsPDF's plugin framework, but this quick and dirty solution should at least get you going.
Note that doc.getFontList() will not show added fonts:
// TODO: iterate over fonts array or return copy of fontmap instead in case more are ever added.
It seems to be a lot easier with the latest version of jsPDF (1.5.3):
If you look in the folder jsPDF-master > fontconverter, there's a file fontconverter.html. Open in your browser and use the Browse... button to navigate to, and select your .ttf font file.
Click 'Create'.
The page will offer a "download" to be saved. This will produce a .js file called [something like] RopaSans-Regular-normal.js. This needs to be included in your page producing the PDF's. Personally, I've done it in the main page's header (and please note the order of the scripts):
<!-- pdf creation -->
<script src="FileSaver.js-master/src/FileSaver.js"></script>
<script src="jsPDF-master/dist/jspdf.debug.js"></script>
<!-- custom font definition -->
<script src="path-to-the-file-just-saved/RopaSans-Regular-normal.js" type="module"></script>
Now in your PDF generation method in js:
doc.setFont('RopaSans-Regular');
doc.setFontType('normal');
Here is the solution I'm using...
First, as others have mentioned - you need these two libraries:
jsPDF: https://github.com/MrRio/jsPDF
jsPDF-CustomFonts-support: https://github.com/sphilee/jsPDF-CustomFonts-support
Next - the second library requires that you provide it with at least one custom font in a file named default_vfs.js. I'm using two custom fonts - Arimo-Regular.ttf and Arimo-Bold.ttf - both from Google Fonts. So, my default_vfs.js file looks like this:
(
(function (jsPDFAPI) {
"use strict";
jsPDFAPI.addFileToVFS('Arimo-Regular.ttf','[Base64-encoded string of your font]');
jsPDFAPI.addFileToVFS('Arimo-Bold.ttf','[Base64-encoded string of your font]');
})(jsPDF.API);
Obviously, you version would look different, depending on the font(s) you're using.
There's a bunch of ways to get the Base64-encoded string for your font, but I used this: https://www.giftofspeed.com/base64-encoder/.
It lets you upload a font .ttf file, and it'll give you the Base64 string that you can paste into default_vfs.js.
You can see what the actual file looks like, with my fonts, here: https://cdn.rawgit.com/stuehler/jsPDF-CustomFonts-support/master/dist/default_vfs.js
So, once your fonts are stored in that file, your HTML should look like this:
<script src="js/jspdf.min.js"></script>
<script src="js/jspdf.customfonts.min.js"></script>
<script src="js/default_vfs.js"></script>
Finally, your JavaScript code looks something like this:
const doc = new jsPDF({
unit: 'pt',
orientation: 'p',
lineHeight: 1.2
});
doc.addFont("Arimo-Regular.ttf", "Arimo", "normal");
doc.addFont("Arimo-Bold.ttf", "Arimo", "bold");
doc.setFont("Arimo");
doc.setFontType("normal");
doc.setFontSize(28);
doc.text("Hello, World!", 100, 100);
doc.setFontType("bold");
doc.text("Hello, BOLD World!", 100, 150);
doc.save("customFonts.pdf");
This is probably obvious to most, but in that addFont() method, the three parameters are:
The font's name you used in the addFileToVFS() function in the default_vfs.js file
The font's name you use in the setFont() function in your JavaScript
The font's style you use in the setFontType() function in your JavaScript
You can see this working here: https://codepen.io/stuehler/pen/pZMdKo
Hope this works as well for you as it did for me.
I'm using Angular 8 and Todd's answer worked for me.
Once you get the .js file from fontconverter.html, you can import it in typescript like so:
import fontref = require('path/to/font/CustomFont-normal.js')
Then all you have to do to load the font is 'call' fontref:
makePdf() {
let doc = new jsPDF();
fontref; // 'call' .js to load font
doc.getFontList(); // contains a key-value pair for CustomFont
doc.setFont("CustomFont"); // set font
doc.setFontType("normal");
doc.setFontSize(28);
doc.text("Hello", 20, 20);
window.open(doc.output('bloburl')); // open pdf in new tab
}
After looking at the fontconverter.html, and seeing that it does nothing more than package the TTF files into a base64 string inside a JS file, I came up with the following method that I call before creating my document. It basically does what the individual files resulting from fontconverter.html do, just on-demand:
async function loadFont(src, name, style, weight) {
const fontBytes = await fetch(src).then(res => res.arrayBuffer());
var filename = src.split('\\').pop().split('/').pop();
var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(fontBytes)));
var callAddFont = function () {
this.addFileToVFS(filename, base64String);
this.addFont(filename, name, style, weight );
};
jsPDF.API.events.push(['addFonts', callAddFont]);
}
Call it like this:
await loadFont("/css/fonts/exo-2-v9-latin-ext_latin-italic.ttf", "Exo-2", "italic", 400);
await loadFont("/css/fonts/exo-2-v9-latin-ext_latin-regular.ttf", "Exo-2", "normal", 400);
await loadFont("/css/fonts/exo-2-v9-latin-ext_latin-500.ttf", "Exo-2", "normal", 500);
await loadFont("/css/fonts/exo-2-v9-latin-ext_latin-500italic.ttf", "Exo-2", "italic", 500);
It loads the font from the URL, and adds it to the VFS and font manager. Important: the font name cannot include spaces. You won't get any warnings, but the resulting PDF will either not open or the text will look funny.
Some of these answers are outdated, so I am linking the readme file from Mr. Rio himself regarding the latest release as of this post. Below is a copy of the paragraph from that readme file followed by a link to the readme file itself. Hope this additional resource is helpful:
Use of UTF-8 / TTF:
The 14 standard fonts in PDF are limited to the
ASCII-codepage. If you want to use UTF-8 you have to to integrate a
custom font, which provides the needed glyphs. jsPDF supports
.ttf-files. So if you want to have for example chinese text in your
pdf, your font has to have the necessary chinese glyphs. So check if
your font supports the wanted glyphs or else it will show a blank
space instead of the text.
To add the font to jsPDF use our fontconverter in
/fontconverter/fontconverter.html . The fontconverter will create a
js-file with the content of the provided ttf-file as base64 encoded
string and additional code for jsPDF. You just have to add this
generated js-File to your project. You are then ready to go to use
setFont-method in your code and write your UTF-8 encoded text.
https://github.com/MrRio/jsPDF/blob/master/README.md#use-of-utf-8--ttf
//use necessary config, read the docs http://raw.githack.com/MrRio/jsPDF/master/docs/jsPDF.html
import MuliSemiB64 from "../functions/MuliSemiB64";
let doc = new jsPDF({
orientation: "p",
unit: "px",
format: "a5",
});
doc.addFileToVFS("MULI-SEMIBOLD.TTF", MuliSemiB64());
//MuliSemiB64() is a function that returns the Muli ttf file in its base64 string format, convert your font ttf file and copy the string, save to a variable and use the function to return the string. Use a site like https://www.giftofspeed.com/base64-encoder/ for the conversion
doc.addFont("MULI-SEMIBOLD.TTF", "Muli-Semi-Bold", "Semi-Bold");
doc.setFont("Muli-Semi-Bold", "Semi-Bold");
doc.text("Have Fun :*", 35, 25);
The easiest way that I have found by far is using the jspdf-customfonts package.
Simply install the package by
npm i jspdf-customfonts
then add the following files in the head tag of your index.html for default configurations
script src="https://unpkg.com/jspdf#latest/dist/jspdf.min.js"></script>
<script src="dist/jspdf.customfonts.min.js"></script>
<script src="dist/default_vfs.js"></script>
Now you can download the ttf file of whichever font you want. Then go to this site, select your font and copy the code, and you are done!

Resources