Skip to content

Instantly share code, notes, and snippets.

@secretpray
Last active March 25, 2023 08:56
Show Gist options
  • Save secretpray/4d30af1562f1de51d048597c82d97744 to your computer and use it in GitHub Desktop.
Save secretpray/4d30af1562f1de51d048597c82d97744 to your computer and use it in GitHub Desktop.

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment