Header + footer + pagy (start form second page) based on https://github.com/ravi4rails/blogging-app-with-prawn
require 'open-uri'
require 'zip'
class Post < ApplicationRecord
validates :title, presence: true, uniqueness: true
validates :description, presence: true
has_one_attached :cover_image
def self.download_font(name, path) # need to prevent 1252 and UTF-8 conversion error only
url = "https://fonts.google.com/download?family=#{URI.encode_www_form_component(name)}"
downloaded_file = URI.open(url).read
temp_zip_file = Tempfile.new(["#{name}-fonts", ".zip"])
File.open(temp_zip_file.path, "wb") do |file|
file.write(downloaded_file)
end
Zip::File.open(temp_zip_file.path) do |zip_file|
zip_file.each do |entry| #zip_file.glob('static/**/*').each if /Static/*.ttf
if !entry.directory?
# get filename without dir
filename = File.basename(entry.name)
entry.extract("#{path}/#{filename}") { true }
end
end
end
temp_zip_file.close
end
def generate_post_pdf
Prawn::Font::AFM.hide_m17n_warning = true
post_pdf = Prawn::Document.new
font_path = "#{Rails.root}/app/assets/fonts"
FileUtils.mkdir_p(font_path) unless File.directory?(font_path)
unless File.exist?("#{font_path}/Roboto-Regular.ttf") # Montserrat-Regular.ttf
puts "Font is not found. Downloading..."
self.class.download_font("Roboto", font_path)
puts "Font downloaded"
end
# post_pdf.font_families.update("Montserrat" => {
# normal: "#{Rails.root}/app/assets/fonts/Montserrat-Regular.ttf",
# bold: "#{Rails.root}/app/assets/fonts/Montserrat-Bold.ttf",
# italic: "#{Rails.root}/app/assets/fonts/Montserrat-Italic.ttf",
# bold_italic: "#{Rails.root}/app/assets/fonts/Montserrat-BoldItalic.ttf"
# })
post_pdf.font_families.update("Roboto" => {
normal: "#{Rails.root}/app/assets/fonts/Roboto-Regular.ttf",
bold: "#{Rails.root}/app/assets/fonts/Roboto-Bold.ttf",
italic: "#{Rails.root}/app/assets/fonts/Roboto-Italic.ttf",
bold_italic: "#{Rails.root}/app/assets/fonts/Roboto-BlackItalic.ttf"
})
post_pdf.font('Roboto') do
post_pdf.repeat(:all, dynamic: true) do
post_pdf.bounding_box([post_pdf.bounds.left, post_pdf.bounds.top], width: post_pdf.bounds.width) do
post_pdf.text 'Blogging App', size: 40, style: :bold, align: :center
post_pdf.stroke_horizontal_rule
end
post_pdf.bounding_box([post_pdf.bounds.left, post_pdf.bounds.bottom + 30 ], width: post_pdf.bounds.width) do
post_pdf.stroke_horizontal_rule
post_pdf.move_down 5
post_pdf.text 'Blogging App Footer', size: 20, style: :bold, align: :center
end
unless post_pdf.page_number == 1
post_pdf.bounding_box([post_pdf.bounds.left, post_pdf.bounds.bottom + 40], width: post_pdf.bounds.width) do
# post_pdf.stroke_horizontal_rule
post_pdf.move_up 5
post_pdf.text "Page #{post_pdf.page_number }", size: 10, align: :center
end
end
end
post_pdf.bounding_box([post_pdf.bounds.left, post_pdf.bounds.top - 60], width: post_pdf.bounds.width, height: post_pdf.bounds.height - 100) do
post_pdf.font('Roboto', size: 30, style: :bold) do
post_pdf.text title, align: :center
end
post_pdf.image StringIO.open(cover_image.download), at: [35, 600], width: 500, height: 220
post_pdf.move_down 250
post_pdf.text description, line_height: 8
end
end
post_pdf.number_pages '<page> of <total>', {
start_count_at: 2,
page_filter: lambda{ |pg| pg != 1 },
at: [post_pdf.bounds.right - 50, 0],
align: :right,
size: 10
}
post_pdf
end
end
PostsController
require 'open-uri'
class PostsController < ApplicationController
before_action :set_post, only: %i[ show edit update destroy download ]
# GET /posts or /posts.json
def index
@posts = Post.all
end
# GET /posts/1 or /posts/1.json
def show
end
# GET /posts/new
def new
@post = Post.new
end
# GET /posts/1/edit
def edit
end
# POST /posts or /posts.json
def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to post_url(@post), notice: "Post was successfully created." }
format.json { render :show, status: :created, location: @post }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /posts/1 or /posts/1.json
def update
respond_to do |format|
if @post.update(post_params)
format.html { redirect_to post_url(@post), notice: "Post was successfully updated." }
format.json { render :show, status: :ok, location: @post }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1 or /posts/1.json
def destroy
@post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: "Post was successfully destroyed." }
format.json { head :no_content }
end
end
def download
post_pdf = @post.generate_post_pdf
if params[:preview].present?
send_data(post_pdf.render, filename: "#{@post.title}.pdf", type: 'application/pdf', disposition: 'inline')
else
send_data(post_pdf.render, filename: "#{@post.title}.pdf", type: 'application/pdf')
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Only allow a list of trusted parameters through.
def post_params
params.require(:post).permit(:title, :description, :cover_image)
end
end
posts/show.html.erb
<p style="color: green"><%= notice %></p>
<div class="col-lg-10 mx-auto">
<%= render @post %>
<%= link_to 'Download as PDF', download_post_path(@post), class: "btn btn-primary mt-3 mb-3" %>
<%= link_to 'Preview and Download', download_post_path(@post, preview: true), class: "btn btn-primary mt-3 mb-3" %>
<div>
<%= link_to "Edit this post", edit_post_path(@post) %> |
<%= link_to "Back to posts", posts_path %>
<%= button_to "Destroy this post", @post, method: :delete %>
</div>
</div>