# -*- encoding: utf-8; frozen_string_literal: true -*-
#
#--
# This file is part of HexaPDF.
#
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
# Copyright (C) 2014-2024 Thomas Leitner
#
# HexaPDF is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License version 3 as
# published by the Free Software Foundation with the addition of the
# following permission added to Section 15 as permitted in Section 7(a):
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
# INFRINGEMENT OF THIRD PARTY RIGHTS.
#
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with HexaPDF. If not, see .
#
# The interactive user interfaces in modified source and object code
# versions of HexaPDF must display Appropriate Legal Notices, as required
# under Section 5 of the GNU Affero General Public License version 3.
#
# In accordance with Section 7(b) of the GNU Affero General Public
# License, a covered work must retain the producer line in every PDF that
# is created or manipulated using HexaPDF.
#
# If the GNU Affero General Public License doesn't fit your need,
# commercial licenses are available at .
#++
require 'set'
require 'hexapdf/cli/command'
module HexaPDF
module CLI
# Lists fonts from a PDF file.
#
# See: HexaPDF::Type::Font
class Fonts < Command
def initialize #:nodoc:
super('fonts', takes_commands: false)
short_desc("List fonts from a PDF file")
long_desc(<<~EOF)
Lists the fonts used in the PDF with additional information, sorted by page number.
EOF
options.on("--password PASSWORD", "-p", String,
"The password for decryption. Use - for reading from standard input.") do |pwd|
@password = (pwd == '-' ? read_password : pwd)
end
options.on("-i", "--pages PAGES", "The subset of pages that should be looked at") do |pages|
@pages = pages
end
@password = nil
@pages = nil
end
def execute(pdf) #:nodoc:
with_document(pdf, password: @password) do |doc|
printf("%5s %-40s %-12s %-10s %-3s %-3s %8s %9s\n",
"page", "name", "type", "encoding", "emb", "sub", "size", "oid")
puts("-" * 97)
each_font(doc) do |pindex, font|
font_type = case font[:Subtype]
when :Type1
if font.embedded? && font[:FontDescriptor][:FontFile3]
"Type 1C"
else
"Type 1"
end
when :Type3 then "Type 3"
when :TrueType then "TrueType"
when :Type0
if font.descendant_font[:Subtype] == :CIDFontType0
"CID CFF"
else
"CID TrueType"
end
else
"Unknown"
end
encoding = font[:Encoding]
encoding = if encoding.kind_of?(Symbol)
encoding.to_s.sub(/Encoding/, '')
elsif encoding.kind_of?(Dictionary) &&
[:Type1, :Type3, :TrueType].include?(font[:Subtype])
"Custom"
else
"Built-in"
end
size = human_readable_file_size(font.embedded? ? font.font_file[:Length] : 0)
embedded = (font.embedded? ? "yes" : "no")
subset = (font[:BaseFont].match?(/\A[A-Z]{6}\+/) ? "yes" : "no")
printf("%5s %-40s %-12s %-10s %-3s %-3s %8s %9s\n",
pindex, font[:BaseFont], font_type, encoding,
embedded, subset, size, "#{font.oid},#{font.gen}")
end
end
end
private
# Iterates over all fonts by page.
def each_font(doc) # :yields: obj, index, page_index
if @pages
pages = parse_pages_specification(@pages, doc.pages.count).each_with_object({}) do |(i, _), h|
h[i] = true
end
end
seen = {}
doc.pages.each_with_index do |page, pindex|
next if pages && !pages[pindex]
font_proc = lambda do |_, font|
next if seen[font]
yield(pindex + 1, font)
seen[font] = true
end
page.resources[:Font]&.each(&font_proc)
page.resources[:XObject]&.each do |_, xobj|
next unless xobj[:Subtype] == :Form
xobj.resources[:Font]&.each(&font_proc)
end
page.each_annotation do |annotation|
appearance = annotation.appearance
next unless appearance
appearance.resources[:Font]&.each(&font_proc)
end
seen.clear if pages
end
end
end
end
end