This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
||||
# Copyright (C) 2011 James Healy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
require 'stringio'
|
||||
|
||||
module PDF
|
||||
################################################################################
|
||||
# The Reader class serves as an entry point for parsing a PDF file.
|
||||
#
|
||||
# PDF is a page based file format. There is some data associated with the
|
||||
# document (metadata, bookmarks, etc) but all visible content is stored
|
||||
# under a Page object.
|
||||
#
|
||||
# In most use cases for extracting and examining the contents of a PDF it
|
||||
# makes sense to traverse the information using page based iteration.
|
||||
#
|
||||
# In addition to the documentation here, check out the
|
||||
# PDF::Reader::Page class.
|
||||
#
|
||||
# == File Metadata
|
||||
#
|
||||
# reader = PDF::Reader.new("somefile.pdf")
|
||||
#
|
||||
# puts reader.pdf_version
|
||||
# puts reader.info
|
||||
# puts reader.metadata
|
||||
# puts reader.page_count
|
||||
#
|
||||
# == Iterating over page content
|
||||
#
|
||||
# reader = PDF::Reader.new("somefile.pdf")
|
||||
#
|
||||
# reader.pages.each do |page|
|
||||
# puts page.fonts
|
||||
# puts page.images
|
||||
# puts page.text
|
||||
# end
|
||||
#
|
||||
# == Extracting all text
|
||||
#
|
||||
# reader = PDF::Reader.new("somefile.pdf")
|
||||
#
|
||||
# reader.pages.map(&:text)
|
||||
#
|
||||
# == Extracting content from a single page
|
||||
#
|
||||
# reader = PDF::Reader.new("somefile.pdf")
|
||||
#
|
||||
# page = reader.page(1)
|
||||
# puts page.fonts
|
||||
# puts page.images
|
||||
# puts page.text
|
||||
#
|
||||
# == Low level callbacks (ala current version of PDF::Reader)
|
||||
#
|
||||
# reader = PDF::Reader.new("somefile.pdf")
|
||||
#
|
||||
# page = reader.page(1)
|
||||
# page.walk(receiver)
|
||||
#
|
||||
# == Encrypted Files
|
||||
#
|
||||
# Depending on the algorithm it may be possible to parse an encrypted file.
|
||||
# For standard PDF encryption you'll need the :password option
|
||||
#
|
||||
# reader = PDF::Reader.new("somefile.pdf", :password => "apples")
|
||||
#
|
||||
class Reader
|
||||
|
||||
# lowlevel hash-like access to all objects in the underlying PDF
|
||||
attr_reader :objects
|
||||
|
||||
# creates a new document reader for the provided PDF.
|
||||
#
|
||||
# input can be an IO-ish object (StringIO, File, etc) containing a PDF
|
||||
# or a filename
|
||||
#
|
||||
# reader = PDF::Reader.new("somefile.pdf")
|
||||
#
|
||||
# File.open("somefile.pdf","rb") do |file|
|
||||
# reader = PDF::Reader.new(file)
|
||||
# end
|
||||
#
|
||||
# If the source file is encrypted you can provide a password for decrypting
|
||||
#
|
||||
# reader = PDF::Reader.new("somefile.pdf", :password => "apples")
|
||||
#
|
||||
# Using this method directly is supported, but it's more common to use
|
||||
# `PDF::Reader.open`
|
||||
#
|
||||
def initialize(input, opts = {})
|
||||
@cache = PDF::Reader::ObjectCache.new
|
||||
opts.merge!(:cache => @cache)
|
||||
@objects = PDF::Reader::ObjectHash.new(input, opts)
|
||||
end
|
||||
|
||||
# Return a Hash with some basic information about the PDF file
|
||||
#
|
||||
def info
|
||||
dict = @objects.deref_hash(@objects.trailer[:Info]) || {}
|
||||
doc_strings_to_utf8(dict)
|
||||
end
|
||||
|
||||
# Return a String with extra XML metadata provided by the author of the PDF file. Not
|
||||
# always present.
|
||||
#
|
||||
def metadata
|
||||
stream = @objects.deref_stream(root[:Metadata])
|
||||
if stream.nil?
|
||||
nil
|
||||
else
|
||||
xml = stream.unfiltered_data
|
||||
xml.force_encoding("utf-8")
|
||||
xml
|
||||
end
|
||||
end
|
||||
|
||||
# To number of pages in this PDF
|
||||
#
|
||||
def page_count
|
||||
pages = @objects.deref_hash(root[:Pages])
|
||||
unless pages.kind_of?(::Hash)
|
||||
raise MalformedPDFError, "Pages structure is missing #{pages.class}"
|
||||
end
|
||||
@page_count ||= @objects.deref_integer(pages[:Count]) || 0
|
||||
end
|
||||
|
||||
# The PDF version this file uses
|
||||
#
|
||||
def pdf_version
|
||||
@objects.pdf_version
|
||||
end
|
||||
|
||||
# syntactic sugar for opening a PDF file and the most common approach. Accepts the
|
||||
# same arguments as new().
|
||||
#
|
||||
# PDF::Reader.open("somefile.pdf") do |reader|
|
||||
# puts reader.pdf_version
|
||||
# end
|
||||
#
|
||||
# or
|
||||
#
|
||||
# PDF::Reader.open("somefile.pdf", :password => "apples") do |reader|
|
||||
# puts reader.pdf_version
|
||||
# end
|
||||
#
|
||||
def self.open(input, opts = {}, &block)
|
||||
yield PDF::Reader.new(input, opts)
|
||||
end
|
||||
|
||||
# returns an array of PDF::Reader::Page objects, one for each
|
||||
# page in the source PDF.
|
||||
#
|
||||
# reader = PDF::Reader.new("somefile.pdf")
|
||||
#
|
||||
# reader.pages.each do |page|
|
||||
# puts page.fonts
|
||||
# puts page.rectangles
|
||||
# puts page.text
|
||||
# end
|
||||
#
|
||||
# See the docs for PDF::Reader::Page to read more about the
|
||||
# methods available on each page
|
||||
#
|
||||
def pages
|
||||
return [] if page_count <= 0
|
||||
|
||||
(1..self.page_count).map do |num|
|
||||
begin
|
||||
PDF::Reader::Page.new(@objects, num, :cache => @cache)
|
||||
rescue InvalidPageError
|
||||
raise MalformedPDFError, "Missing data for page: #{num}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# returns a single PDF::Reader::Page for the specified page.
|
||||
# Use this instead of pages method when you need to access just a single
|
||||
# page
|
||||
#
|
||||
# reader = PDF::Reader.new("somefile.pdf")
|
||||
# page = reader.page(10)
|
||||
#
|
||||
# puts page.text
|
||||
#
|
||||
# See the docs for PDF::Reader::Page to read more about the
|
||||
# methods available on each page
|
||||
#
|
||||
def page(num)
|
||||
num = num.to_i
|
||||
if num < 1 || num > self.page_count
|
||||
raise InvalidPageError, "Valid pages are 1 .. #{self.page_count}"
|
||||
end
|
||||
PDF::Reader::Page.new(@objects, num, :cache => @cache)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# recursively convert strings from outside a content stream into UTF-8
|
||||
#
|
||||
def doc_strings_to_utf8(obj)
|
||||
case obj
|
||||
when ::Hash then
|
||||
{}.tap { |new_hash|
|
||||
obj.each do |key, value|
|
||||
new_hash[key] = doc_strings_to_utf8(value)
|
||||
end
|
||||
}
|
||||
when Array then
|
||||
obj.map { |item| doc_strings_to_utf8(item) }
|
||||
when String then
|
||||
if has_utf16_bom?(obj)
|
||||
utf16_to_utf8(obj)
|
||||
else
|
||||
pdfdoc_to_utf8(obj)
|
||||
end
|
||||
else
|
||||
obj
|
||||
end
|
||||
end
|
||||
|
||||
def has_utf16_bom?(str)
|
||||
first_bytes = str[0,2]
|
||||
|
||||
return false if first_bytes.nil?
|
||||
|
||||
first_bytes.unpack("C*") == [254, 255]
|
||||
end
|
||||
|
||||
# TODO find a PDF I can use to spec this behaviour
|
||||
#
|
||||
def pdfdoc_to_utf8(obj)
|
||||
obj.force_encoding("utf-8")
|
||||
obj
|
||||
end
|
||||
|
||||
# one day we'll all run on a 1.9 compatible VM and I can just do this with
|
||||
# String#encode
|
||||
#
|
||||
def utf16_to_utf8(obj)
|
||||
str = obj[2, obj.size].to_s
|
||||
str = str.unpack("n*").pack("U*")
|
||||
str.force_encoding("utf-8")
|
||||
str
|
||||
end
|
||||
|
||||
def root
|
||||
@root ||= @objects.deref_hash(@objects.trailer[:Root]) || {}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
################################################################################
|
||||
|
||||
require 'pdf/reader/resources'
|
||||
require 'pdf/reader/advanced_text_run_filter'
|
||||
require 'pdf/reader/buffer'
|
||||
require 'pdf/reader/bounding_rectangle_runs_filter'
|
||||
require 'pdf/reader/cid_widths'
|
||||
require 'pdf/reader/cmap'
|
||||
require 'pdf/reader/encoding'
|
||||
require 'pdf/reader/error'
|
||||
require 'pdf/reader/filter'
|
||||
require 'pdf/reader/filter/ascii85'
|
||||
require 'pdf/reader/filter/ascii_hex'
|
||||
require 'pdf/reader/filter/depredict'
|
||||
require 'pdf/reader/filter/flate'
|
||||
require 'pdf/reader/filter/lzw'
|
||||
require 'pdf/reader/filter/null'
|
||||
require 'pdf/reader/filter/run_length'
|
||||
require 'pdf/reader/font'
|
||||
require 'pdf/reader/font_descriptor'
|
||||
require 'pdf/reader/form_xobject'
|
||||
require 'pdf/reader/glyph_hash'
|
||||
require 'pdf/reader/lzw'
|
||||
require 'pdf/reader/object_cache'
|
||||
require 'pdf/reader/object_hash'
|
||||
require 'pdf/reader/object_stream'
|
||||
require 'pdf/reader/pages_strategy'
|
||||
require 'pdf/reader/parser'
|
||||
require 'pdf/reader/point'
|
||||
require 'pdf/reader/print_receiver'
|
||||
require 'pdf/reader/rectangle'
|
||||
require 'pdf/reader/reference'
|
||||
require 'pdf/reader/register_receiver'
|
||||
require 'pdf/reader/no_text_filter'
|
||||
require 'pdf/reader/null_security_handler'
|
||||
require 'pdf/reader/security_handler_factory'
|
||||
require 'pdf/reader/standard_key_builder'
|
||||
require 'pdf/reader/key_builder_v5'
|
||||
require 'pdf/reader/aes_v2_security_handler'
|
||||
require 'pdf/reader/aes_v3_security_handler'
|
||||
require 'pdf/reader/rc4_security_handler'
|
||||
require 'pdf/reader/unimplemented_security_handler'
|
||||
require 'pdf/reader/stream'
|
||||
require 'pdf/reader/text_run'
|
||||
require 'pdf/reader/type_check'
|
||||
require 'pdf/reader/page_state'
|
||||
require 'pdf/reader/page_text_receiver'
|
||||
require 'pdf/reader/token'
|
||||
require 'pdf/reader/xref'
|
||||
require 'pdf/reader/page'
|
||||
require 'pdf/reader/validating_receiver'
|
||||
@@ -0,0 +1,137 @@
|
||||
# coding: utf-8
|
||||
# frozen_string_literal: true
|
||||
# typed: strict
|
||||
|
||||
class PDF::Reader
|
||||
# Filter a collection of TextRun objects based on a set of conditions.
|
||||
# It can be used to filter text runs based on their attributes.
|
||||
# The filter can return the text runs that matches the conditions (only) or
|
||||
# the text runs that do not match the conditions (exclude).
|
||||
#
|
||||
# You can filter the text runs based on all its attributes with the operators
|
||||
# mentioned in VALID_OPERATORS.
|
||||
# The filter can be nested with 'or' and 'and' conditions.
|
||||
#
|
||||
# Examples:
|
||||
# 1. Single condition
|
||||
# AdvancedTextRunFilter.exclude(text_runs, text: { include: 'sample' })
|
||||
#
|
||||
# 2. Multiple conditions (and)
|
||||
# AdvancedTextRunFilter.exclude(text_runs, {
|
||||
# font_size: { greater_than: 10, less_than: 15 }
|
||||
# })
|
||||
#
|
||||
# 3. Multiple possible values (or)
|
||||
# AdvancedTextRunFilter.exclude(text_runs, {
|
||||
# font_size: { equal: [10, 12] }
|
||||
# })
|
||||
#
|
||||
# 4. Complex AND/OR filter
|
||||
# AdvancedTextRunFilter.exclude(text_runs, {
|
||||
# and: [
|
||||
# { font_size: { greater_than: 10 } },
|
||||
# { or: [
|
||||
# { text: { include: "sample" } },
|
||||
# { width: { greater_than: 100 } }
|
||||
# ]}
|
||||
# ]
|
||||
# })
|
||||
class AdvancedTextRunFilter
|
||||
VALID_OPERATORS = %i[
|
||||
equal
|
||||
not_equal
|
||||
greater_than
|
||||
less_than
|
||||
greater_than_or_equal
|
||||
less_than_or_equal
|
||||
include
|
||||
exclude
|
||||
]
|
||||
|
||||
def self.only(text_runs, filter_hash)
|
||||
new(text_runs, filter_hash).only
|
||||
end
|
||||
|
||||
def self.exclude(text_runs, filter_hash)
|
||||
new(text_runs, filter_hash).exclude
|
||||
end
|
||||
|
||||
attr_reader :text_runs, :filter_hash
|
||||
|
||||
def initialize(text_runs, filter_hash)
|
||||
@text_runs = text_runs
|
||||
@filter_hash = filter_hash
|
||||
end
|
||||
|
||||
def only
|
||||
return text_runs if filter_hash.empty?
|
||||
text_runs.select { |text_run| evaluate_filter(text_run) }
|
||||
end
|
||||
|
||||
def exclude
|
||||
return text_runs if filter_hash.empty?
|
||||
text_runs.reject { |text_run| evaluate_filter(text_run) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def evaluate_filter(text_run)
|
||||
if filter_hash[:or]
|
||||
evaluate_or_filters(text_run, filter_hash[:or])
|
||||
elsif filter_hash[:and]
|
||||
evaluate_and_filters(text_run, filter_hash[:and])
|
||||
else
|
||||
evaluate_filters(text_run, filter_hash)
|
||||
end
|
||||
end
|
||||
|
||||
def evaluate_or_filters(text_run, conditions)
|
||||
conditions.any? do |condition|
|
||||
evaluate_filters(text_run, condition)
|
||||
end
|
||||
end
|
||||
|
||||
def evaluate_and_filters(text_run, conditions)
|
||||
conditions.all? do |condition|
|
||||
evaluate_filters(text_run, condition)
|
||||
end
|
||||
end
|
||||
|
||||
def evaluate_filters(text_run, filter_hash)
|
||||
filter_hash.all? do |attribute, conditions|
|
||||
evaluate_attribute_conditions(text_run, attribute, conditions)
|
||||
end
|
||||
end
|
||||
|
||||
def evaluate_attribute_conditions(text_run, attribute, conditions)
|
||||
conditions.all? do |operator, value|
|
||||
unless VALID_OPERATORS.include?(operator)
|
||||
raise ArgumentError, "Invalid operator: #{operator}"
|
||||
end
|
||||
|
||||
apply_operator(text_run.send(attribute), operator, value)
|
||||
end
|
||||
end
|
||||
|
||||
def apply_operator(attribute_value, operator, filter_value)
|
||||
case operator
|
||||
when :equal
|
||||
Array(filter_value).include?(attribute_value)
|
||||
when :not_equal
|
||||
!Array(filter_value).include?(attribute_value)
|
||||
when :greater_than
|
||||
attribute_value > filter_value
|
||||
when :less_than
|
||||
attribute_value < filter_value
|
||||
when :greater_than_or_equal
|
||||
attribute_value >= filter_value
|
||||
when :less_than_or_equal
|
||||
attribute_value <= filter_value
|
||||
when :include
|
||||
Array(filter_value).any? { |v| attribute_value.to_s.include?(v.to_s) }
|
||||
when :exclude
|
||||
Array(filter_value).none? { |v| attribute_value.to_s.include?(v.to_s) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'digest/md5'
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# Decrypts data using the AESV2 algorithim defined in the PDF spec. Requires
|
||||
# a decryption key, which is usually generated by PDF::Reader::StandardKeyBuilder
|
||||
#
|
||||
class AesV2SecurityHandler
|
||||
|
||||
def initialize(key)
|
||||
@encrypt_key = key
|
||||
end
|
||||
|
||||
##7.6.2 General Encryption Algorithm
|
||||
#
|
||||
# Algorithm 1: Encryption of data using the AES-128-CBC algorithm
|
||||
#
|
||||
# version == 4 and CFM == AESV2
|
||||
#
|
||||
# buf - a string to decrypt
|
||||
# ref - a PDF::Reader::Reference for the object to decrypt
|
||||
#
|
||||
def decrypt( buf, ref )
|
||||
objKey = @encrypt_key.dup
|
||||
(0..2).each { |e| objKey << (ref.id >> e*8 & 0xFF ) }
|
||||
(0..1).each { |e| objKey << (ref.gen >> e*8 & 0xFF ) }
|
||||
objKey << 'sAlT' # Algorithm 1, b)
|
||||
length = objKey.length < 16 ? objKey.length : 16
|
||||
cipher = OpenSSL::Cipher.new("AES-#{length << 3}-CBC")
|
||||
cipher.decrypt
|
||||
cipher.key = Digest::MD5.digest(objKey)[0,length]
|
||||
cipher.iv = buf[0..15]
|
||||
cipher.update(buf[16..-1]) + cipher.final
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'digest'
|
||||
require 'openssl'
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# Decrypts data using the AESV3 algorithim defined in the PDF 1.7, Extension Level 3 spec.
|
||||
# Requires a decryption key, which is usually generated by PDF::Reader::KeyBuilderV5
|
||||
#
|
||||
class AesV3SecurityHandler
|
||||
|
||||
def initialize(key)
|
||||
@encrypt_key = key
|
||||
@cipher = "AES-256-CBC"
|
||||
end
|
||||
|
||||
##7.6.2 General Encryption Algorithm
|
||||
#
|
||||
# Algorithm 1: Encryption of data using the RC4 or AES algorithms
|
||||
#
|
||||
# used to decrypt RC4/AES encrypted PDF streams (buf)
|
||||
#
|
||||
# buf - a string to decrypt
|
||||
# ref - a PDF::Reader::Reference for the object to decrypt
|
||||
#
|
||||
def decrypt( buf, ref )
|
||||
cipher = OpenSSL::Cipher.new(@cipher)
|
||||
cipher.decrypt
|
||||
cipher.key = @encrypt_key.dup
|
||||
cipher.iv = buf[0..15]
|
||||
cipher.update(buf[16..-1]) + cipher.final
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,342 @@
|
||||
StartFontMetrics 4.1
|
||||
Comment Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
|
||||
Comment Creation Date: Mon Jun 23 16:28:00 1997
|
||||
Comment UniqueID 43048
|
||||
Comment VMusage 41139 52164
|
||||
FontName Courier-Bold
|
||||
FullName Courier Bold
|
||||
FamilyName Courier
|
||||
Weight Bold
|
||||
ItalicAngle 0
|
||||
IsFixedPitch true
|
||||
CharacterSet ExtendedRoman
|
||||
FontBBox -113 -250 749 801
|
||||
UnderlinePosition -100
|
||||
UnderlineThickness 50
|
||||
Version 003.000
|
||||
Notice Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
|
||||
EncodingScheme AdobeStandardEncoding
|
||||
CapHeight 562
|
||||
XHeight 439
|
||||
Ascender 629
|
||||
Descender -157
|
||||
StdHW 84
|
||||
StdVW 106
|
||||
StartCharMetrics 315
|
||||
C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
|
||||
C 33 ; WX 600 ; N exclam ; B 202 -15 398 572 ;
|
||||
C 34 ; WX 600 ; N quotedbl ; B 135 277 465 562 ;
|
||||
C 35 ; WX 600 ; N numbersign ; B 56 -45 544 651 ;
|
||||
C 36 ; WX 600 ; N dollar ; B 82 -126 519 666 ;
|
||||
C 37 ; WX 600 ; N percent ; B 5 -15 595 616 ;
|
||||
C 38 ; WX 600 ; N ampersand ; B 36 -15 546 543 ;
|
||||
C 39 ; WX 600 ; N quoteright ; B 171 277 423 562 ;
|
||||
C 40 ; WX 600 ; N parenleft ; B 219 -102 461 616 ;
|
||||
C 41 ; WX 600 ; N parenright ; B 139 -102 381 616 ;
|
||||
C 42 ; WX 600 ; N asterisk ; B 91 219 509 601 ;
|
||||
C 43 ; WX 600 ; N plus ; B 71 39 529 478 ;
|
||||
C 44 ; WX 600 ; N comma ; B 123 -111 393 174 ;
|
||||
C 45 ; WX 600 ; N hyphen ; B 100 203 500 313 ;
|
||||
C 46 ; WX 600 ; N period ; B 192 -15 408 171 ;
|
||||
C 47 ; WX 600 ; N slash ; B 98 -77 502 626 ;
|
||||
C 48 ; WX 600 ; N zero ; B 87 -15 513 616 ;
|
||||
C 49 ; WX 600 ; N one ; B 81 0 539 616 ;
|
||||
C 50 ; WX 600 ; N two ; B 61 0 499 616 ;
|
||||
C 51 ; WX 600 ; N three ; B 63 -15 501 616 ;
|
||||
C 52 ; WX 600 ; N four ; B 53 0 507 616 ;
|
||||
C 53 ; WX 600 ; N five ; B 70 -15 521 601 ;
|
||||
C 54 ; WX 600 ; N six ; B 90 -15 521 616 ;
|
||||
C 55 ; WX 600 ; N seven ; B 55 0 494 601 ;
|
||||
C 56 ; WX 600 ; N eight ; B 83 -15 517 616 ;
|
||||
C 57 ; WX 600 ; N nine ; B 79 -15 510 616 ;
|
||||
C 58 ; WX 600 ; N colon ; B 191 -15 407 425 ;
|
||||
C 59 ; WX 600 ; N semicolon ; B 123 -111 408 425 ;
|
||||
C 60 ; WX 600 ; N less ; B 66 15 523 501 ;
|
||||
C 61 ; WX 600 ; N equal ; B 71 118 529 398 ;
|
||||
C 62 ; WX 600 ; N greater ; B 77 15 534 501 ;
|
||||
C 63 ; WX 600 ; N question ; B 98 -14 501 580 ;
|
||||
C 64 ; WX 600 ; N at ; B 16 -15 584 616 ;
|
||||
C 65 ; WX 600 ; N A ; B -9 0 609 562 ;
|
||||
C 66 ; WX 600 ; N B ; B 30 0 573 562 ;
|
||||
C 67 ; WX 600 ; N C ; B 22 -18 560 580 ;
|
||||
C 68 ; WX 600 ; N D ; B 30 0 594 562 ;
|
||||
C 69 ; WX 600 ; N E ; B 25 0 560 562 ;
|
||||
C 70 ; WX 600 ; N F ; B 39 0 570 562 ;
|
||||
C 71 ; WX 600 ; N G ; B 22 -18 594 580 ;
|
||||
C 72 ; WX 600 ; N H ; B 20 0 580 562 ;
|
||||
C 73 ; WX 600 ; N I ; B 77 0 523 562 ;
|
||||
C 74 ; WX 600 ; N J ; B 37 -18 601 562 ;
|
||||
C 75 ; WX 600 ; N K ; B 21 0 599 562 ;
|
||||
C 76 ; WX 600 ; N L ; B 39 0 578 562 ;
|
||||
C 77 ; WX 600 ; N M ; B -2 0 602 562 ;
|
||||
C 78 ; WX 600 ; N N ; B 8 -12 610 562 ;
|
||||
C 79 ; WX 600 ; N O ; B 22 -18 578 580 ;
|
||||
C 80 ; WX 600 ; N P ; B 48 0 559 562 ;
|
||||
C 81 ; WX 600 ; N Q ; B 32 -138 578 580 ;
|
||||
C 82 ; WX 600 ; N R ; B 24 0 599 562 ;
|
||||
C 83 ; WX 600 ; N S ; B 47 -22 553 582 ;
|
||||
C 84 ; WX 600 ; N T ; B 21 0 579 562 ;
|
||||
C 85 ; WX 600 ; N U ; B 4 -18 596 562 ;
|
||||
C 86 ; WX 600 ; N V ; B -13 0 613 562 ;
|
||||
C 87 ; WX 600 ; N W ; B -18 0 618 562 ;
|
||||
C 88 ; WX 600 ; N X ; B 12 0 588 562 ;
|
||||
C 89 ; WX 600 ; N Y ; B 12 0 589 562 ;
|
||||
C 90 ; WX 600 ; N Z ; B 62 0 539 562 ;
|
||||
C 91 ; WX 600 ; N bracketleft ; B 245 -102 475 616 ;
|
||||
C 92 ; WX 600 ; N backslash ; B 99 -77 503 626 ;
|
||||
C 93 ; WX 600 ; N bracketright ; B 125 -102 355 616 ;
|
||||
C 94 ; WX 600 ; N asciicircum ; B 108 250 492 616 ;
|
||||
C 95 ; WX 600 ; N underscore ; B 0 -125 600 -75 ;
|
||||
C 96 ; WX 600 ; N quoteleft ; B 178 277 428 562 ;
|
||||
C 97 ; WX 600 ; N a ; B 35 -15 570 454 ;
|
||||
C 98 ; WX 600 ; N b ; B 0 -15 584 626 ;
|
||||
C 99 ; WX 600 ; N c ; B 40 -15 545 459 ;
|
||||
C 100 ; WX 600 ; N d ; B 20 -15 591 626 ;
|
||||
C 101 ; WX 600 ; N e ; B 40 -15 563 454 ;
|
||||
C 102 ; WX 600 ; N f ; B 83 0 547 626 ; L i fi ; L l fl ;
|
||||
C 103 ; WX 600 ; N g ; B 30 -146 580 454 ;
|
||||
C 104 ; WX 600 ; N h ; B 5 0 592 626 ;
|
||||
C 105 ; WX 600 ; N i ; B 77 0 523 658 ;
|
||||
C 106 ; WX 600 ; N j ; B 63 -146 440 658 ;
|
||||
C 107 ; WX 600 ; N k ; B 20 0 585 626 ;
|
||||
C 108 ; WX 600 ; N l ; B 77 0 523 626 ;
|
||||
C 109 ; WX 600 ; N m ; B -22 0 626 454 ;
|
||||
C 110 ; WX 600 ; N n ; B 18 0 592 454 ;
|
||||
C 111 ; WX 600 ; N o ; B 30 -15 570 454 ;
|
||||
C 112 ; WX 600 ; N p ; B -1 -142 570 454 ;
|
||||
C 113 ; WX 600 ; N q ; B 20 -142 591 454 ;
|
||||
C 114 ; WX 600 ; N r ; B 47 0 580 454 ;
|
||||
C 115 ; WX 600 ; N s ; B 68 -17 535 459 ;
|
||||
C 116 ; WX 600 ; N t ; B 47 -15 532 562 ;
|
||||
C 117 ; WX 600 ; N u ; B -1 -15 569 439 ;
|
||||
C 118 ; WX 600 ; N v ; B -1 0 601 439 ;
|
||||
C 119 ; WX 600 ; N w ; B -18 0 618 439 ;
|
||||
C 120 ; WX 600 ; N x ; B 6 0 594 439 ;
|
||||
C 121 ; WX 600 ; N y ; B -4 -142 601 439 ;
|
||||
C 122 ; WX 600 ; N z ; B 81 0 520 439 ;
|
||||
C 123 ; WX 600 ; N braceleft ; B 160 -102 464 616 ;
|
||||
C 124 ; WX 600 ; N bar ; B 255 -250 345 750 ;
|
||||
C 125 ; WX 600 ; N braceright ; B 136 -102 440 616 ;
|
||||
C 126 ; WX 600 ; N asciitilde ; B 71 153 530 356 ;
|
||||
C 161 ; WX 600 ; N exclamdown ; B 202 -146 398 449 ;
|
||||
C 162 ; WX 600 ; N cent ; B 66 -49 518 614 ;
|
||||
C 163 ; WX 600 ; N sterling ; B 72 -28 558 611 ;
|
||||
C 164 ; WX 600 ; N fraction ; B 25 -60 576 661 ;
|
||||
C 165 ; WX 600 ; N yen ; B 10 0 590 562 ;
|
||||
C 166 ; WX 600 ; N florin ; B -30 -131 572 616 ;
|
||||
C 167 ; WX 600 ; N section ; B 83 -70 517 580 ;
|
||||
C 168 ; WX 600 ; N currency ; B 54 49 546 517 ;
|
||||
C 169 ; WX 600 ; N quotesingle ; B 227 277 373 562 ;
|
||||
C 170 ; WX 600 ; N quotedblleft ; B 71 277 535 562 ;
|
||||
C 171 ; WX 600 ; N guillemotleft ; B 8 70 553 446 ;
|
||||
C 172 ; WX 600 ; N guilsinglleft ; B 141 70 459 446 ;
|
||||
C 173 ; WX 600 ; N guilsinglright ; B 141 70 459 446 ;
|
||||
C 174 ; WX 600 ; N fi ; B 12 0 593 626 ;
|
||||
C 175 ; WX 600 ; N fl ; B 12 0 593 626 ;
|
||||
C 177 ; WX 600 ; N endash ; B 65 203 535 313 ;
|
||||
C 178 ; WX 600 ; N dagger ; B 106 -70 494 580 ;
|
||||
C 179 ; WX 600 ; N daggerdbl ; B 106 -70 494 580 ;
|
||||
C 180 ; WX 600 ; N periodcentered ; B 196 165 404 351 ;
|
||||
C 182 ; WX 600 ; N paragraph ; B 6 -70 576 580 ;
|
||||
C 183 ; WX 600 ; N bullet ; B 140 132 460 430 ;
|
||||
C 184 ; WX 600 ; N quotesinglbase ; B 175 -142 427 143 ;
|
||||
C 185 ; WX 600 ; N quotedblbase ; B 65 -142 529 143 ;
|
||||
C 186 ; WX 600 ; N quotedblright ; B 61 277 525 562 ;
|
||||
C 187 ; WX 600 ; N guillemotright ; B 47 70 592 446 ;
|
||||
C 188 ; WX 600 ; N ellipsis ; B 26 -15 574 116 ;
|
||||
C 189 ; WX 600 ; N perthousand ; B -113 -15 713 616 ;
|
||||
C 191 ; WX 600 ; N questiondown ; B 99 -146 502 449 ;
|
||||
C 193 ; WX 600 ; N grave ; B 132 508 395 661 ;
|
||||
C 194 ; WX 600 ; N acute ; B 205 508 468 661 ;
|
||||
C 195 ; WX 600 ; N circumflex ; B 103 483 497 657 ;
|
||||
C 196 ; WX 600 ; N tilde ; B 89 493 512 636 ;
|
||||
C 197 ; WX 600 ; N macron ; B 88 505 512 585 ;
|
||||
C 198 ; WX 600 ; N breve ; B 83 468 517 631 ;
|
||||
C 199 ; WX 600 ; N dotaccent ; B 230 498 370 638 ;
|
||||
C 200 ; WX 600 ; N dieresis ; B 128 498 472 638 ;
|
||||
C 202 ; WX 600 ; N ring ; B 198 481 402 678 ;
|
||||
C 203 ; WX 600 ; N cedilla ; B 205 -206 387 0 ;
|
||||
C 205 ; WX 600 ; N hungarumlaut ; B 68 488 588 661 ;
|
||||
C 206 ; WX 600 ; N ogonek ; B 169 -199 400 0 ;
|
||||
C 207 ; WX 600 ; N caron ; B 103 493 497 667 ;
|
||||
C 208 ; WX 600 ; N emdash ; B -10 203 610 313 ;
|
||||
C 225 ; WX 600 ; N AE ; B -29 0 602 562 ;
|
||||
C 227 ; WX 600 ; N ordfeminine ; B 147 196 453 580 ;
|
||||
C 232 ; WX 600 ; N Lslash ; B 39 0 578 562 ;
|
||||
C 233 ; WX 600 ; N Oslash ; B 22 -22 578 584 ;
|
||||
C 234 ; WX 600 ; N OE ; B -25 0 595 562 ;
|
||||
C 235 ; WX 600 ; N ordmasculine ; B 147 196 453 580 ;
|
||||
C 241 ; WX 600 ; N ae ; B -4 -15 601 454 ;
|
||||
C 245 ; WX 600 ; N dotlessi ; B 77 0 523 439 ;
|
||||
C 248 ; WX 600 ; N lslash ; B 77 0 523 626 ;
|
||||
C 249 ; WX 600 ; N oslash ; B 30 -24 570 463 ;
|
||||
C 250 ; WX 600 ; N oe ; B -18 -15 611 454 ;
|
||||
C 251 ; WX 600 ; N germandbls ; B 22 -15 596 626 ;
|
||||
C -1 ; WX 600 ; N Idieresis ; B 77 0 523 761 ;
|
||||
C -1 ; WX 600 ; N eacute ; B 40 -15 563 661 ;
|
||||
C -1 ; WX 600 ; N abreve ; B 35 -15 570 661 ;
|
||||
C -1 ; WX 600 ; N uhungarumlaut ; B -1 -15 628 661 ;
|
||||
C -1 ; WX 600 ; N ecaron ; B 40 -15 563 667 ;
|
||||
C -1 ; WX 600 ; N Ydieresis ; B 12 0 589 761 ;
|
||||
C -1 ; WX 600 ; N divide ; B 71 16 529 500 ;
|
||||
C -1 ; WX 600 ; N Yacute ; B 12 0 589 784 ;
|
||||
C -1 ; WX 600 ; N Acircumflex ; B -9 0 609 780 ;
|
||||
C -1 ; WX 600 ; N aacute ; B 35 -15 570 661 ;
|
||||
C -1 ; WX 600 ; N Ucircumflex ; B 4 -18 596 780 ;
|
||||
C -1 ; WX 600 ; N yacute ; B -4 -142 601 661 ;
|
||||
C -1 ; WX 600 ; N scommaaccent ; B 68 -250 535 459 ;
|
||||
C -1 ; WX 600 ; N ecircumflex ; B 40 -15 563 657 ;
|
||||
C -1 ; WX 600 ; N Uring ; B 4 -18 596 801 ;
|
||||
C -1 ; WX 600 ; N Udieresis ; B 4 -18 596 761 ;
|
||||
C -1 ; WX 600 ; N aogonek ; B 35 -199 586 454 ;
|
||||
C -1 ; WX 600 ; N Uacute ; B 4 -18 596 784 ;
|
||||
C -1 ; WX 600 ; N uogonek ; B -1 -199 585 439 ;
|
||||
C -1 ; WX 600 ; N Edieresis ; B 25 0 560 761 ;
|
||||
C -1 ; WX 600 ; N Dcroat ; B 30 0 594 562 ;
|
||||
C -1 ; WX 600 ; N commaaccent ; B 205 -250 397 -57 ;
|
||||
C -1 ; WX 600 ; N copyright ; B 0 -18 600 580 ;
|
||||
C -1 ; WX 600 ; N Emacron ; B 25 0 560 708 ;
|
||||
C -1 ; WX 600 ; N ccaron ; B 40 -15 545 667 ;
|
||||
C -1 ; WX 600 ; N aring ; B 35 -15 570 678 ;
|
||||
C -1 ; WX 600 ; N Ncommaaccent ; B 8 -250 610 562 ;
|
||||
C -1 ; WX 600 ; N lacute ; B 77 0 523 801 ;
|
||||
C -1 ; WX 600 ; N agrave ; B 35 -15 570 661 ;
|
||||
C -1 ; WX 600 ; N Tcommaaccent ; B 21 -250 579 562 ;
|
||||
C -1 ; WX 600 ; N Cacute ; B 22 -18 560 784 ;
|
||||
C -1 ; WX 600 ; N atilde ; B 35 -15 570 636 ;
|
||||
C -1 ; WX 600 ; N Edotaccent ; B 25 0 560 761 ;
|
||||
C -1 ; WX 600 ; N scaron ; B 68 -17 535 667 ;
|
||||
C -1 ; WX 600 ; N scedilla ; B 68 -206 535 459 ;
|
||||
C -1 ; WX 600 ; N iacute ; B 77 0 523 661 ;
|
||||
C -1 ; WX 600 ; N lozenge ; B 66 0 534 740 ;
|
||||
C -1 ; WX 600 ; N Rcaron ; B 24 0 599 790 ;
|
||||
C -1 ; WX 600 ; N Gcommaaccent ; B 22 -250 594 580 ;
|
||||
C -1 ; WX 600 ; N ucircumflex ; B -1 -15 569 657 ;
|
||||
C -1 ; WX 600 ; N acircumflex ; B 35 -15 570 657 ;
|
||||
C -1 ; WX 600 ; N Amacron ; B -9 0 609 708 ;
|
||||
C -1 ; WX 600 ; N rcaron ; B 47 0 580 667 ;
|
||||
C -1 ; WX 600 ; N ccedilla ; B 40 -206 545 459 ;
|
||||
C -1 ; WX 600 ; N Zdotaccent ; B 62 0 539 761 ;
|
||||
C -1 ; WX 600 ; N Thorn ; B 48 0 557 562 ;
|
||||
C -1 ; WX 600 ; N Omacron ; B 22 -18 578 708 ;
|
||||
C -1 ; WX 600 ; N Racute ; B 24 0 599 784 ;
|
||||
C -1 ; WX 600 ; N Sacute ; B 47 -22 553 784 ;
|
||||
C -1 ; WX 600 ; N dcaron ; B 20 -15 727 626 ;
|
||||
C -1 ; WX 600 ; N Umacron ; B 4 -18 596 708 ;
|
||||
C -1 ; WX 600 ; N uring ; B -1 -15 569 678 ;
|
||||
C -1 ; WX 600 ; N threesuperior ; B 138 222 433 616 ;
|
||||
C -1 ; WX 600 ; N Ograve ; B 22 -18 578 784 ;
|
||||
C -1 ; WX 600 ; N Agrave ; B -9 0 609 784 ;
|
||||
C -1 ; WX 600 ; N Abreve ; B -9 0 609 784 ;
|
||||
C -1 ; WX 600 ; N multiply ; B 81 39 520 478 ;
|
||||
C -1 ; WX 600 ; N uacute ; B -1 -15 569 661 ;
|
||||
C -1 ; WX 600 ; N Tcaron ; B 21 0 579 790 ;
|
||||
C -1 ; WX 600 ; N partialdiff ; B 63 -38 537 728 ;
|
||||
C -1 ; WX 600 ; N ydieresis ; B -4 -142 601 638 ;
|
||||
C -1 ; WX 600 ; N Nacute ; B 8 -12 610 784 ;
|
||||
C -1 ; WX 600 ; N icircumflex ; B 73 0 523 657 ;
|
||||
C -1 ; WX 600 ; N Ecircumflex ; B 25 0 560 780 ;
|
||||
C -1 ; WX 600 ; N adieresis ; B 35 -15 570 638 ;
|
||||
C -1 ; WX 600 ; N edieresis ; B 40 -15 563 638 ;
|
||||
C -1 ; WX 600 ; N cacute ; B 40 -15 545 661 ;
|
||||
C -1 ; WX 600 ; N nacute ; B 18 0 592 661 ;
|
||||
C -1 ; WX 600 ; N umacron ; B -1 -15 569 585 ;
|
||||
C -1 ; WX 600 ; N Ncaron ; B 8 -12 610 790 ;
|
||||
C -1 ; WX 600 ; N Iacute ; B 77 0 523 784 ;
|
||||
C -1 ; WX 600 ; N plusminus ; B 71 24 529 515 ;
|
||||
C -1 ; WX 600 ; N brokenbar ; B 255 -175 345 675 ;
|
||||
C -1 ; WX 600 ; N registered ; B 0 -18 600 580 ;
|
||||
C -1 ; WX 600 ; N Gbreve ; B 22 -18 594 784 ;
|
||||
C -1 ; WX 600 ; N Idotaccent ; B 77 0 523 761 ;
|
||||
C -1 ; WX 600 ; N summation ; B 15 -10 586 706 ;
|
||||
C -1 ; WX 600 ; N Egrave ; B 25 0 560 784 ;
|
||||
C -1 ; WX 600 ; N racute ; B 47 0 580 661 ;
|
||||
C -1 ; WX 600 ; N omacron ; B 30 -15 570 585 ;
|
||||
C -1 ; WX 600 ; N Zacute ; B 62 0 539 784 ;
|
||||
C -1 ; WX 600 ; N Zcaron ; B 62 0 539 790 ;
|
||||
C -1 ; WX 600 ; N greaterequal ; B 26 0 523 696 ;
|
||||
C -1 ; WX 600 ; N Eth ; B 30 0 594 562 ;
|
||||
C -1 ; WX 600 ; N Ccedilla ; B 22 -206 560 580 ;
|
||||
C -1 ; WX 600 ; N lcommaaccent ; B 77 -250 523 626 ;
|
||||
C -1 ; WX 600 ; N tcaron ; B 47 -15 532 703 ;
|
||||
C -1 ; WX 600 ; N eogonek ; B 40 -199 563 454 ;
|
||||
C -1 ; WX 600 ; N Uogonek ; B 4 -199 596 562 ;
|
||||
C -1 ; WX 600 ; N Aacute ; B -9 0 609 784 ;
|
||||
C -1 ; WX 600 ; N Adieresis ; B -9 0 609 761 ;
|
||||
C -1 ; WX 600 ; N egrave ; B 40 -15 563 661 ;
|
||||
C -1 ; WX 600 ; N zacute ; B 81 0 520 661 ;
|
||||
C -1 ; WX 600 ; N iogonek ; B 77 -199 523 658 ;
|
||||
C -1 ; WX 600 ; N Oacute ; B 22 -18 578 784 ;
|
||||
C -1 ; WX 600 ; N oacute ; B 30 -15 570 661 ;
|
||||
C -1 ; WX 600 ; N amacron ; B 35 -15 570 585 ;
|
||||
C -1 ; WX 600 ; N sacute ; B 68 -17 535 661 ;
|
||||
C -1 ; WX 600 ; N idieresis ; B 77 0 523 618 ;
|
||||
C -1 ; WX 600 ; N Ocircumflex ; B 22 -18 578 780 ;
|
||||
C -1 ; WX 600 ; N Ugrave ; B 4 -18 596 784 ;
|
||||
C -1 ; WX 600 ; N Delta ; B 6 0 594 688 ;
|
||||
C -1 ; WX 600 ; N thorn ; B -14 -142 570 626 ;
|
||||
C -1 ; WX 600 ; N twosuperior ; B 143 230 436 616 ;
|
||||
C -1 ; WX 600 ; N Odieresis ; B 22 -18 578 761 ;
|
||||
C -1 ; WX 600 ; N mu ; B -1 -142 569 439 ;
|
||||
C -1 ; WX 600 ; N igrave ; B 77 0 523 661 ;
|
||||
C -1 ; WX 600 ; N ohungarumlaut ; B 30 -15 668 661 ;
|
||||
C -1 ; WX 600 ; N Eogonek ; B 25 -199 576 562 ;
|
||||
C -1 ; WX 600 ; N dcroat ; B 20 -15 591 626 ;
|
||||
C -1 ; WX 600 ; N threequarters ; B -47 -60 648 661 ;
|
||||
C -1 ; WX 600 ; N Scedilla ; B 47 -206 553 582 ;
|
||||
C -1 ; WX 600 ; N lcaron ; B 77 0 597 626 ;
|
||||
C -1 ; WX 600 ; N Kcommaaccent ; B 21 -250 599 562 ;
|
||||
C -1 ; WX 600 ; N Lacute ; B 39 0 578 784 ;
|
||||
C -1 ; WX 600 ; N trademark ; B -9 230 749 562 ;
|
||||
C -1 ; WX 600 ; N edotaccent ; B 40 -15 563 638 ;
|
||||
C -1 ; WX 600 ; N Igrave ; B 77 0 523 784 ;
|
||||
C -1 ; WX 600 ; N Imacron ; B 77 0 523 708 ;
|
||||
C -1 ; WX 600 ; N Lcaron ; B 39 0 637 562 ;
|
||||
C -1 ; WX 600 ; N onehalf ; B -47 -60 648 661 ;
|
||||
C -1 ; WX 600 ; N lessequal ; B 26 0 523 696 ;
|
||||
C -1 ; WX 600 ; N ocircumflex ; B 30 -15 570 657 ;
|
||||
C -1 ; WX 600 ; N ntilde ; B 18 0 592 636 ;
|
||||
C -1 ; WX 600 ; N Uhungarumlaut ; B 4 -18 638 784 ;
|
||||
C -1 ; WX 600 ; N Eacute ; B 25 0 560 784 ;
|
||||
C -1 ; WX 600 ; N emacron ; B 40 -15 563 585 ;
|
||||
C -1 ; WX 600 ; N gbreve ; B 30 -146 580 661 ;
|
||||
C -1 ; WX 600 ; N onequarter ; B -56 -60 656 661 ;
|
||||
C -1 ; WX 600 ; N Scaron ; B 47 -22 553 790 ;
|
||||
C -1 ; WX 600 ; N Scommaaccent ; B 47 -250 553 582 ;
|
||||
C -1 ; WX 600 ; N Ohungarumlaut ; B 22 -18 628 784 ;
|
||||
C -1 ; WX 600 ; N degree ; B 86 243 474 616 ;
|
||||
C -1 ; WX 600 ; N ograve ; B 30 -15 570 661 ;
|
||||
C -1 ; WX 600 ; N Ccaron ; B 22 -18 560 790 ;
|
||||
C -1 ; WX 600 ; N ugrave ; B -1 -15 569 661 ;
|
||||
C -1 ; WX 600 ; N radical ; B -19 -104 473 778 ;
|
||||
C -1 ; WX 600 ; N Dcaron ; B 30 0 594 790 ;
|
||||
C -1 ; WX 600 ; N rcommaaccent ; B 47 -250 580 454 ;
|
||||
C -1 ; WX 600 ; N Ntilde ; B 8 -12 610 759 ;
|
||||
C -1 ; WX 600 ; N otilde ; B 30 -15 570 636 ;
|
||||
C -1 ; WX 600 ; N Rcommaaccent ; B 24 -250 599 562 ;
|
||||
C -1 ; WX 600 ; N Lcommaaccent ; B 39 -250 578 562 ;
|
||||
C -1 ; WX 600 ; N Atilde ; B -9 0 609 759 ;
|
||||
C -1 ; WX 600 ; N Aogonek ; B -9 -199 625 562 ;
|
||||
C -1 ; WX 600 ; N Aring ; B -9 0 609 801 ;
|
||||
C -1 ; WX 600 ; N Otilde ; B 22 -18 578 759 ;
|
||||
C -1 ; WX 600 ; N zdotaccent ; B 81 0 520 638 ;
|
||||
C -1 ; WX 600 ; N Ecaron ; B 25 0 560 790 ;
|
||||
C -1 ; WX 600 ; N Iogonek ; B 77 -199 523 562 ;
|
||||
C -1 ; WX 600 ; N kcommaaccent ; B 20 -250 585 626 ;
|
||||
C -1 ; WX 600 ; N minus ; B 71 203 529 313 ;
|
||||
C -1 ; WX 600 ; N Icircumflex ; B 77 0 523 780 ;
|
||||
C -1 ; WX 600 ; N ncaron ; B 18 0 592 667 ;
|
||||
C -1 ; WX 600 ; N tcommaaccent ; B 47 -250 532 562 ;
|
||||
C -1 ; WX 600 ; N logicalnot ; B 71 103 529 413 ;
|
||||
C -1 ; WX 600 ; N odieresis ; B 30 -15 570 638 ;
|
||||
C -1 ; WX 600 ; N udieresis ; B -1 -15 569 638 ;
|
||||
C -1 ; WX 600 ; N notequal ; B 12 -47 537 563 ;
|
||||
C -1 ; WX 600 ; N gcommaaccent ; B 30 -146 580 714 ;
|
||||
C -1 ; WX 600 ; N eth ; B 58 -27 543 626 ;
|
||||
C -1 ; WX 600 ; N zcaron ; B 81 0 520 667 ;
|
||||
C -1 ; WX 600 ; N ncommaaccent ; B 18 -250 592 454 ;
|
||||
C -1 ; WX 600 ; N onesuperior ; B 153 230 447 616 ;
|
||||
C -1 ; WX 600 ; N imacron ; B 77 0 523 585 ;
|
||||
C -1 ; WX 600 ; N Euro ; B 0 0 0 0 ;
|
||||
EndCharMetrics
|
||||
EndFontMetrics
|
||||
@@ -0,0 +1,342 @@
|
||||
StartFontMetrics 4.1
|
||||
Comment Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
|
||||
Comment Creation Date: Mon Jun 23 16:28:46 1997
|
||||
Comment UniqueID 43049
|
||||
Comment VMusage 17529 79244
|
||||
FontName Courier-BoldOblique
|
||||
FullName Courier Bold Oblique
|
||||
FamilyName Courier
|
||||
Weight Bold
|
||||
ItalicAngle -12
|
||||
IsFixedPitch true
|
||||
CharacterSet ExtendedRoman
|
||||
FontBBox -57 -250 869 801
|
||||
UnderlinePosition -100
|
||||
UnderlineThickness 50
|
||||
Version 003.000
|
||||
Notice Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
|
||||
EncodingScheme AdobeStandardEncoding
|
||||
CapHeight 562
|
||||
XHeight 439
|
||||
Ascender 629
|
||||
Descender -157
|
||||
StdHW 84
|
||||
StdVW 106
|
||||
StartCharMetrics 315
|
||||
C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
|
||||
C 33 ; WX 600 ; N exclam ; B 215 -15 495 572 ;
|
||||
C 34 ; WX 600 ; N quotedbl ; B 211 277 585 562 ;
|
||||
C 35 ; WX 600 ; N numbersign ; B 88 -45 641 651 ;
|
||||
C 36 ; WX 600 ; N dollar ; B 87 -126 630 666 ;
|
||||
C 37 ; WX 600 ; N percent ; B 101 -15 625 616 ;
|
||||
C 38 ; WX 600 ; N ampersand ; B 61 -15 595 543 ;
|
||||
C 39 ; WX 600 ; N quoteright ; B 229 277 543 562 ;
|
||||
C 40 ; WX 600 ; N parenleft ; B 265 -102 592 616 ;
|
||||
C 41 ; WX 600 ; N parenright ; B 117 -102 444 616 ;
|
||||
C 42 ; WX 600 ; N asterisk ; B 179 219 598 601 ;
|
||||
C 43 ; WX 600 ; N plus ; B 114 39 596 478 ;
|
||||
C 44 ; WX 600 ; N comma ; B 99 -111 430 174 ;
|
||||
C 45 ; WX 600 ; N hyphen ; B 143 203 567 313 ;
|
||||
C 46 ; WX 600 ; N period ; B 206 -15 427 171 ;
|
||||
C 47 ; WX 600 ; N slash ; B 90 -77 626 626 ;
|
||||
C 48 ; WX 600 ; N zero ; B 135 -15 593 616 ;
|
||||
C 49 ; WX 600 ; N one ; B 93 0 562 616 ;
|
||||
C 50 ; WX 600 ; N two ; B 61 0 594 616 ;
|
||||
C 51 ; WX 600 ; N three ; B 71 -15 571 616 ;
|
||||
C 52 ; WX 600 ; N four ; B 81 0 559 616 ;
|
||||
C 53 ; WX 600 ; N five ; B 77 -15 621 601 ;
|
||||
C 54 ; WX 600 ; N six ; B 135 -15 652 616 ;
|
||||
C 55 ; WX 600 ; N seven ; B 147 0 622 601 ;
|
||||
C 56 ; WX 600 ; N eight ; B 115 -15 604 616 ;
|
||||
C 57 ; WX 600 ; N nine ; B 75 -15 592 616 ;
|
||||
C 58 ; WX 600 ; N colon ; B 205 -15 480 425 ;
|
||||
C 59 ; WX 600 ; N semicolon ; B 99 -111 481 425 ;
|
||||
C 60 ; WX 600 ; N less ; B 120 15 613 501 ;
|
||||
C 61 ; WX 600 ; N equal ; B 96 118 614 398 ;
|
||||
C 62 ; WX 600 ; N greater ; B 97 15 589 501 ;
|
||||
C 63 ; WX 600 ; N question ; B 183 -14 592 580 ;
|
||||
C 64 ; WX 600 ; N at ; B 65 -15 642 616 ;
|
||||
C 65 ; WX 600 ; N A ; B -9 0 632 562 ;
|
||||
C 66 ; WX 600 ; N B ; B 30 0 630 562 ;
|
||||
C 67 ; WX 600 ; N C ; B 74 -18 675 580 ;
|
||||
C 68 ; WX 600 ; N D ; B 30 0 664 562 ;
|
||||
C 69 ; WX 600 ; N E ; B 25 0 670 562 ;
|
||||
C 70 ; WX 600 ; N F ; B 39 0 684 562 ;
|
||||
C 71 ; WX 600 ; N G ; B 74 -18 675 580 ;
|
||||
C 72 ; WX 600 ; N H ; B 20 0 700 562 ;
|
||||
C 73 ; WX 600 ; N I ; B 77 0 643 562 ;
|
||||
C 74 ; WX 600 ; N J ; B 58 -18 721 562 ;
|
||||
C 75 ; WX 600 ; N K ; B 21 0 692 562 ;
|
||||
C 76 ; WX 600 ; N L ; B 39 0 636 562 ;
|
||||
C 77 ; WX 600 ; N M ; B -2 0 722 562 ;
|
||||
C 78 ; WX 600 ; N N ; B 8 -12 730 562 ;
|
||||
C 79 ; WX 600 ; N O ; B 74 -18 645 580 ;
|
||||
C 80 ; WX 600 ; N P ; B 48 0 643 562 ;
|
||||
C 81 ; WX 600 ; N Q ; B 83 -138 636 580 ;
|
||||
C 82 ; WX 600 ; N R ; B 24 0 617 562 ;
|
||||
C 83 ; WX 600 ; N S ; B 54 -22 673 582 ;
|
||||
C 84 ; WX 600 ; N T ; B 86 0 679 562 ;
|
||||
C 85 ; WX 600 ; N U ; B 101 -18 716 562 ;
|
||||
C 86 ; WX 600 ; N V ; B 84 0 733 562 ;
|
||||
C 87 ; WX 600 ; N W ; B 79 0 738 562 ;
|
||||
C 88 ; WX 600 ; N X ; B 12 0 690 562 ;
|
||||
C 89 ; WX 600 ; N Y ; B 109 0 709 562 ;
|
||||
C 90 ; WX 600 ; N Z ; B 62 0 637 562 ;
|
||||
C 91 ; WX 600 ; N bracketleft ; B 223 -102 606 616 ;
|
||||
C 92 ; WX 600 ; N backslash ; B 222 -77 496 626 ;
|
||||
C 93 ; WX 600 ; N bracketright ; B 103 -102 486 616 ;
|
||||
C 94 ; WX 600 ; N asciicircum ; B 171 250 556 616 ;
|
||||
C 95 ; WX 600 ; N underscore ; B -27 -125 585 -75 ;
|
||||
C 96 ; WX 600 ; N quoteleft ; B 297 277 487 562 ;
|
||||
C 97 ; WX 600 ; N a ; B 61 -15 593 454 ;
|
||||
C 98 ; WX 600 ; N b ; B 13 -15 636 626 ;
|
||||
C 99 ; WX 600 ; N c ; B 81 -15 631 459 ;
|
||||
C 100 ; WX 600 ; N d ; B 60 -15 645 626 ;
|
||||
C 101 ; WX 600 ; N e ; B 81 -15 605 454 ;
|
||||
C 102 ; WX 600 ; N f ; B 83 0 677 626 ; L i fi ; L l fl ;
|
||||
C 103 ; WX 600 ; N g ; B 40 -146 674 454 ;
|
||||
C 104 ; WX 600 ; N h ; B 18 0 615 626 ;
|
||||
C 105 ; WX 600 ; N i ; B 77 0 546 658 ;
|
||||
C 106 ; WX 600 ; N j ; B 36 -146 580 658 ;
|
||||
C 107 ; WX 600 ; N k ; B 33 0 643 626 ;
|
||||
C 108 ; WX 600 ; N l ; B 77 0 546 626 ;
|
||||
C 109 ; WX 600 ; N m ; B -22 0 649 454 ;
|
||||
C 110 ; WX 600 ; N n ; B 18 0 615 454 ;
|
||||
C 111 ; WX 600 ; N o ; B 71 -15 622 454 ;
|
||||
C 112 ; WX 600 ; N p ; B -32 -142 622 454 ;
|
||||
C 113 ; WX 600 ; N q ; B 60 -142 685 454 ;
|
||||
C 114 ; WX 600 ; N r ; B 47 0 655 454 ;
|
||||
C 115 ; WX 600 ; N s ; B 66 -17 608 459 ;
|
||||
C 116 ; WX 600 ; N t ; B 118 -15 567 562 ;
|
||||
C 117 ; WX 600 ; N u ; B 70 -15 592 439 ;
|
||||
C 118 ; WX 600 ; N v ; B 70 0 695 439 ;
|
||||
C 119 ; WX 600 ; N w ; B 53 0 712 439 ;
|
||||
C 120 ; WX 600 ; N x ; B 6 0 671 439 ;
|
||||
C 121 ; WX 600 ; N y ; B -21 -142 695 439 ;
|
||||
C 122 ; WX 600 ; N z ; B 81 0 614 439 ;
|
||||
C 123 ; WX 600 ; N braceleft ; B 203 -102 595 616 ;
|
||||
C 124 ; WX 600 ; N bar ; B 201 -250 505 750 ;
|
||||
C 125 ; WX 600 ; N braceright ; B 114 -102 506 616 ;
|
||||
C 126 ; WX 600 ; N asciitilde ; B 120 153 590 356 ;
|
||||
C 161 ; WX 600 ; N exclamdown ; B 196 -146 477 449 ;
|
||||
C 162 ; WX 600 ; N cent ; B 121 -49 605 614 ;
|
||||
C 163 ; WX 600 ; N sterling ; B 106 -28 650 611 ;
|
||||
C 164 ; WX 600 ; N fraction ; B 22 -60 708 661 ;
|
||||
C 165 ; WX 600 ; N yen ; B 98 0 710 562 ;
|
||||
C 166 ; WX 600 ; N florin ; B -57 -131 702 616 ;
|
||||
C 167 ; WX 600 ; N section ; B 74 -70 620 580 ;
|
||||
C 168 ; WX 600 ; N currency ; B 77 49 644 517 ;
|
||||
C 169 ; WX 600 ; N quotesingle ; B 303 277 493 562 ;
|
||||
C 170 ; WX 600 ; N quotedblleft ; B 190 277 594 562 ;
|
||||
C 171 ; WX 600 ; N guillemotleft ; B 62 70 639 446 ;
|
||||
C 172 ; WX 600 ; N guilsinglleft ; B 195 70 545 446 ;
|
||||
C 173 ; WX 600 ; N guilsinglright ; B 165 70 514 446 ;
|
||||
C 174 ; WX 600 ; N fi ; B 12 0 644 626 ;
|
||||
C 175 ; WX 600 ; N fl ; B 12 0 644 626 ;
|
||||
C 177 ; WX 600 ; N endash ; B 108 203 602 313 ;
|
||||
C 178 ; WX 600 ; N dagger ; B 175 -70 586 580 ;
|
||||
C 179 ; WX 600 ; N daggerdbl ; B 121 -70 587 580 ;
|
||||
C 180 ; WX 600 ; N periodcentered ; B 248 165 461 351 ;
|
||||
C 182 ; WX 600 ; N paragraph ; B 61 -70 700 580 ;
|
||||
C 183 ; WX 600 ; N bullet ; B 196 132 523 430 ;
|
||||
C 184 ; WX 600 ; N quotesinglbase ; B 144 -142 458 143 ;
|
||||
C 185 ; WX 600 ; N quotedblbase ; B 34 -142 560 143 ;
|
||||
C 186 ; WX 600 ; N quotedblright ; B 119 277 645 562 ;
|
||||
C 187 ; WX 600 ; N guillemotright ; B 71 70 647 446 ;
|
||||
C 188 ; WX 600 ; N ellipsis ; B 35 -15 587 116 ;
|
||||
C 189 ; WX 600 ; N perthousand ; B -45 -15 743 616 ;
|
||||
C 191 ; WX 600 ; N questiondown ; B 100 -146 509 449 ;
|
||||
C 193 ; WX 600 ; N grave ; B 272 508 503 661 ;
|
||||
C 194 ; WX 600 ; N acute ; B 312 508 609 661 ;
|
||||
C 195 ; WX 600 ; N circumflex ; B 212 483 607 657 ;
|
||||
C 196 ; WX 600 ; N tilde ; B 199 493 643 636 ;
|
||||
C 197 ; WX 600 ; N macron ; B 195 505 637 585 ;
|
||||
C 198 ; WX 600 ; N breve ; B 217 468 652 631 ;
|
||||
C 199 ; WX 600 ; N dotaccent ; B 348 498 493 638 ;
|
||||
C 200 ; WX 600 ; N dieresis ; B 246 498 595 638 ;
|
||||
C 202 ; WX 600 ; N ring ; B 319 481 528 678 ;
|
||||
C 203 ; WX 600 ; N cedilla ; B 168 -206 368 0 ;
|
||||
C 205 ; WX 600 ; N hungarumlaut ; B 171 488 729 661 ;
|
||||
C 206 ; WX 600 ; N ogonek ; B 143 -199 367 0 ;
|
||||
C 207 ; WX 600 ; N caron ; B 238 493 633 667 ;
|
||||
C 208 ; WX 600 ; N emdash ; B 33 203 677 313 ;
|
||||
C 225 ; WX 600 ; N AE ; B -29 0 708 562 ;
|
||||
C 227 ; WX 600 ; N ordfeminine ; B 188 196 526 580 ;
|
||||
C 232 ; WX 600 ; N Lslash ; B 39 0 636 562 ;
|
||||
C 233 ; WX 600 ; N Oslash ; B 48 -22 673 584 ;
|
||||
C 234 ; WX 600 ; N OE ; B 26 0 701 562 ;
|
||||
C 235 ; WX 600 ; N ordmasculine ; B 188 196 543 580 ;
|
||||
C 241 ; WX 600 ; N ae ; B 21 -15 652 454 ;
|
||||
C 245 ; WX 600 ; N dotlessi ; B 77 0 546 439 ;
|
||||
C 248 ; WX 600 ; N lslash ; B 77 0 587 626 ;
|
||||
C 249 ; WX 600 ; N oslash ; B 54 -24 638 463 ;
|
||||
C 250 ; WX 600 ; N oe ; B 18 -15 662 454 ;
|
||||
C 251 ; WX 600 ; N germandbls ; B 22 -15 629 626 ;
|
||||
C -1 ; WX 600 ; N Idieresis ; B 77 0 643 761 ;
|
||||
C -1 ; WX 600 ; N eacute ; B 81 -15 609 661 ;
|
||||
C -1 ; WX 600 ; N abreve ; B 61 -15 658 661 ;
|
||||
C -1 ; WX 600 ; N uhungarumlaut ; B 70 -15 769 661 ;
|
||||
C -1 ; WX 600 ; N ecaron ; B 81 -15 633 667 ;
|
||||
C -1 ; WX 600 ; N Ydieresis ; B 109 0 709 761 ;
|
||||
C -1 ; WX 600 ; N divide ; B 114 16 596 500 ;
|
||||
C -1 ; WX 600 ; N Yacute ; B 109 0 709 784 ;
|
||||
C -1 ; WX 600 ; N Acircumflex ; B -9 0 632 780 ;
|
||||
C -1 ; WX 600 ; N aacute ; B 61 -15 609 661 ;
|
||||
C -1 ; WX 600 ; N Ucircumflex ; B 101 -18 716 780 ;
|
||||
C -1 ; WX 600 ; N yacute ; B -21 -142 695 661 ;
|
||||
C -1 ; WX 600 ; N scommaaccent ; B 66 -250 608 459 ;
|
||||
C -1 ; WX 600 ; N ecircumflex ; B 81 -15 607 657 ;
|
||||
C -1 ; WX 600 ; N Uring ; B 101 -18 716 801 ;
|
||||
C -1 ; WX 600 ; N Udieresis ; B 101 -18 716 761 ;
|
||||
C -1 ; WX 600 ; N aogonek ; B 61 -199 593 454 ;
|
||||
C -1 ; WX 600 ; N Uacute ; B 101 -18 716 784 ;
|
||||
C -1 ; WX 600 ; N uogonek ; B 70 -199 592 439 ;
|
||||
C -1 ; WX 600 ; N Edieresis ; B 25 0 670 761 ;
|
||||
C -1 ; WX 600 ; N Dcroat ; B 30 0 664 562 ;
|
||||
C -1 ; WX 600 ; N commaaccent ; B 151 -250 385 -57 ;
|
||||
C -1 ; WX 600 ; N copyright ; B 53 -18 667 580 ;
|
||||
C -1 ; WX 600 ; N Emacron ; B 25 0 670 708 ;
|
||||
C -1 ; WX 600 ; N ccaron ; B 81 -15 633 667 ;
|
||||
C -1 ; WX 600 ; N aring ; B 61 -15 593 678 ;
|
||||
C -1 ; WX 600 ; N Ncommaaccent ; B 8 -250 730 562 ;
|
||||
C -1 ; WX 600 ; N lacute ; B 77 0 639 801 ;
|
||||
C -1 ; WX 600 ; N agrave ; B 61 -15 593 661 ;
|
||||
C -1 ; WX 600 ; N Tcommaaccent ; B 86 -250 679 562 ;
|
||||
C -1 ; WX 600 ; N Cacute ; B 74 -18 675 784 ;
|
||||
C -1 ; WX 600 ; N atilde ; B 61 -15 643 636 ;
|
||||
C -1 ; WX 600 ; N Edotaccent ; B 25 0 670 761 ;
|
||||
C -1 ; WX 600 ; N scaron ; B 66 -17 633 667 ;
|
||||
C -1 ; WX 600 ; N scedilla ; B 66 -206 608 459 ;
|
||||
C -1 ; WX 600 ; N iacute ; B 77 0 609 661 ;
|
||||
C -1 ; WX 600 ; N lozenge ; B 145 0 614 740 ;
|
||||
C -1 ; WX 600 ; N Rcaron ; B 24 0 659 790 ;
|
||||
C -1 ; WX 600 ; N Gcommaaccent ; B 74 -250 675 580 ;
|
||||
C -1 ; WX 600 ; N ucircumflex ; B 70 -15 597 657 ;
|
||||
C -1 ; WX 600 ; N acircumflex ; B 61 -15 607 657 ;
|
||||
C -1 ; WX 600 ; N Amacron ; B -9 0 633 708 ;
|
||||
C -1 ; WX 600 ; N rcaron ; B 47 0 655 667 ;
|
||||
C -1 ; WX 600 ; N ccedilla ; B 81 -206 631 459 ;
|
||||
C -1 ; WX 600 ; N Zdotaccent ; B 62 0 637 761 ;
|
||||
C -1 ; WX 600 ; N Thorn ; B 48 0 620 562 ;
|
||||
C -1 ; WX 600 ; N Omacron ; B 74 -18 663 708 ;
|
||||
C -1 ; WX 600 ; N Racute ; B 24 0 665 784 ;
|
||||
C -1 ; WX 600 ; N Sacute ; B 54 -22 673 784 ;
|
||||
C -1 ; WX 600 ; N dcaron ; B 60 -15 861 626 ;
|
||||
C -1 ; WX 600 ; N Umacron ; B 101 -18 716 708 ;
|
||||
C -1 ; WX 600 ; N uring ; B 70 -15 592 678 ;
|
||||
C -1 ; WX 600 ; N threesuperior ; B 193 222 526 616 ;
|
||||
C -1 ; WX 600 ; N Ograve ; B 74 -18 645 784 ;
|
||||
C -1 ; WX 600 ; N Agrave ; B -9 0 632 784 ;
|
||||
C -1 ; WX 600 ; N Abreve ; B -9 0 684 784 ;
|
||||
C -1 ; WX 600 ; N multiply ; B 104 39 606 478 ;
|
||||
C -1 ; WX 600 ; N uacute ; B 70 -15 599 661 ;
|
||||
C -1 ; WX 600 ; N Tcaron ; B 86 0 679 790 ;
|
||||
C -1 ; WX 600 ; N partialdiff ; B 91 -38 627 728 ;
|
||||
C -1 ; WX 600 ; N ydieresis ; B -21 -142 695 638 ;
|
||||
C -1 ; WX 600 ; N Nacute ; B 8 -12 730 784 ;
|
||||
C -1 ; WX 600 ; N icircumflex ; B 77 0 577 657 ;
|
||||
C -1 ; WX 600 ; N Ecircumflex ; B 25 0 670 780 ;
|
||||
C -1 ; WX 600 ; N adieresis ; B 61 -15 595 638 ;
|
||||
C -1 ; WX 600 ; N edieresis ; B 81 -15 605 638 ;
|
||||
C -1 ; WX 600 ; N cacute ; B 81 -15 649 661 ;
|
||||
C -1 ; WX 600 ; N nacute ; B 18 0 639 661 ;
|
||||
C -1 ; WX 600 ; N umacron ; B 70 -15 637 585 ;
|
||||
C -1 ; WX 600 ; N Ncaron ; B 8 -12 730 790 ;
|
||||
C -1 ; WX 600 ; N Iacute ; B 77 0 643 784 ;
|
||||
C -1 ; WX 600 ; N plusminus ; B 76 24 614 515 ;
|
||||
C -1 ; WX 600 ; N brokenbar ; B 217 -175 489 675 ;
|
||||
C -1 ; WX 600 ; N registered ; B 53 -18 667 580 ;
|
||||
C -1 ; WX 600 ; N Gbreve ; B 74 -18 684 784 ;
|
||||
C -1 ; WX 600 ; N Idotaccent ; B 77 0 643 761 ;
|
||||
C -1 ; WX 600 ; N summation ; B 15 -10 672 706 ;
|
||||
C -1 ; WX 600 ; N Egrave ; B 25 0 670 784 ;
|
||||
C -1 ; WX 600 ; N racute ; B 47 0 655 661 ;
|
||||
C -1 ; WX 600 ; N omacron ; B 71 -15 637 585 ;
|
||||
C -1 ; WX 600 ; N Zacute ; B 62 0 665 784 ;
|
||||
C -1 ; WX 600 ; N Zcaron ; B 62 0 659 790 ;
|
||||
C -1 ; WX 600 ; N greaterequal ; B 26 0 627 696 ;
|
||||
C -1 ; WX 600 ; N Eth ; B 30 0 664 562 ;
|
||||
C -1 ; WX 600 ; N Ccedilla ; B 74 -206 675 580 ;
|
||||
C -1 ; WX 600 ; N lcommaaccent ; B 77 -250 546 626 ;
|
||||
C -1 ; WX 600 ; N tcaron ; B 118 -15 627 703 ;
|
||||
C -1 ; WX 600 ; N eogonek ; B 81 -199 605 454 ;
|
||||
C -1 ; WX 600 ; N Uogonek ; B 101 -199 716 562 ;
|
||||
C -1 ; WX 600 ; N Aacute ; B -9 0 655 784 ;
|
||||
C -1 ; WX 600 ; N Adieresis ; B -9 0 632 761 ;
|
||||
C -1 ; WX 600 ; N egrave ; B 81 -15 605 661 ;
|
||||
C -1 ; WX 600 ; N zacute ; B 81 0 614 661 ;
|
||||
C -1 ; WX 600 ; N iogonek ; B 77 -199 546 658 ;
|
||||
C -1 ; WX 600 ; N Oacute ; B 74 -18 645 784 ;
|
||||
C -1 ; WX 600 ; N oacute ; B 71 -15 649 661 ;
|
||||
C -1 ; WX 600 ; N amacron ; B 61 -15 637 585 ;
|
||||
C -1 ; WX 600 ; N sacute ; B 66 -17 609 661 ;
|
||||
C -1 ; WX 600 ; N idieresis ; B 77 0 561 618 ;
|
||||
C -1 ; WX 600 ; N Ocircumflex ; B 74 -18 645 780 ;
|
||||
C -1 ; WX 600 ; N Ugrave ; B 101 -18 716 784 ;
|
||||
C -1 ; WX 600 ; N Delta ; B 6 0 594 688 ;
|
||||
C -1 ; WX 600 ; N thorn ; B -32 -142 622 626 ;
|
||||
C -1 ; WX 600 ; N twosuperior ; B 191 230 542 616 ;
|
||||
C -1 ; WX 600 ; N Odieresis ; B 74 -18 645 761 ;
|
||||
C -1 ; WX 600 ; N mu ; B 49 -142 592 439 ;
|
||||
C -1 ; WX 600 ; N igrave ; B 77 0 546 661 ;
|
||||
C -1 ; WX 600 ; N ohungarumlaut ; B 71 -15 809 661 ;
|
||||
C -1 ; WX 600 ; N Eogonek ; B 25 -199 670 562 ;
|
||||
C -1 ; WX 600 ; N dcroat ; B 60 -15 712 626 ;
|
||||
C -1 ; WX 600 ; N threequarters ; B 8 -60 699 661 ;
|
||||
C -1 ; WX 600 ; N Scedilla ; B 54 -206 673 582 ;
|
||||
C -1 ; WX 600 ; N lcaron ; B 77 0 731 626 ;
|
||||
C -1 ; WX 600 ; N Kcommaaccent ; B 21 -250 692 562 ;
|
||||
C -1 ; WX 600 ; N Lacute ; B 39 0 636 784 ;
|
||||
C -1 ; WX 600 ; N trademark ; B 86 230 869 562 ;
|
||||
C -1 ; WX 600 ; N edotaccent ; B 81 -15 605 638 ;
|
||||
C -1 ; WX 600 ; N Igrave ; B 77 0 643 784 ;
|
||||
C -1 ; WX 600 ; N Imacron ; B 77 0 663 708 ;
|
||||
C -1 ; WX 600 ; N Lcaron ; B 39 0 757 562 ;
|
||||
C -1 ; WX 600 ; N onehalf ; B 22 -60 716 661 ;
|
||||
C -1 ; WX 600 ; N lessequal ; B 26 0 671 696 ;
|
||||
C -1 ; WX 600 ; N ocircumflex ; B 71 -15 622 657 ;
|
||||
C -1 ; WX 600 ; N ntilde ; B 18 0 643 636 ;
|
||||
C -1 ; WX 600 ; N Uhungarumlaut ; B 101 -18 805 784 ;
|
||||
C -1 ; WX 600 ; N Eacute ; B 25 0 670 784 ;
|
||||
C -1 ; WX 600 ; N emacron ; B 81 -15 637 585 ;
|
||||
C -1 ; WX 600 ; N gbreve ; B 40 -146 674 661 ;
|
||||
C -1 ; WX 600 ; N onequarter ; B 13 -60 707 661 ;
|
||||
C -1 ; WX 600 ; N Scaron ; B 54 -22 689 790 ;
|
||||
C -1 ; WX 600 ; N Scommaaccent ; B 54 -250 673 582 ;
|
||||
C -1 ; WX 600 ; N Ohungarumlaut ; B 74 -18 795 784 ;
|
||||
C -1 ; WX 600 ; N degree ; B 173 243 570 616 ;
|
||||
C -1 ; WX 600 ; N ograve ; B 71 -15 622 661 ;
|
||||
C -1 ; WX 600 ; N Ccaron ; B 74 -18 689 790 ;
|
||||
C -1 ; WX 600 ; N ugrave ; B 70 -15 592 661 ;
|
||||
C -1 ; WX 600 ; N radical ; B 67 -104 635 778 ;
|
||||
C -1 ; WX 600 ; N Dcaron ; B 30 0 664 790 ;
|
||||
C -1 ; WX 600 ; N rcommaaccent ; B 47 -250 655 454 ;
|
||||
C -1 ; WX 600 ; N Ntilde ; B 8 -12 730 759 ;
|
||||
C -1 ; WX 600 ; N otilde ; B 71 -15 643 636 ;
|
||||
C -1 ; WX 600 ; N Rcommaaccent ; B 24 -250 617 562 ;
|
||||
C -1 ; WX 600 ; N Lcommaaccent ; B 39 -250 636 562 ;
|
||||
C -1 ; WX 600 ; N Atilde ; B -9 0 669 759 ;
|
||||
C -1 ; WX 600 ; N Aogonek ; B -9 -199 632 562 ;
|
||||
C -1 ; WX 600 ; N Aring ; B -9 0 632 801 ;
|
||||
C -1 ; WX 600 ; N Otilde ; B 74 -18 669 759 ;
|
||||
C -1 ; WX 600 ; N zdotaccent ; B 81 0 614 638 ;
|
||||
C -1 ; WX 600 ; N Ecaron ; B 25 0 670 790 ;
|
||||
C -1 ; WX 600 ; N Iogonek ; B 77 -199 643 562 ;
|
||||
C -1 ; WX 600 ; N kcommaaccent ; B 33 -250 643 626 ;
|
||||
C -1 ; WX 600 ; N minus ; B 114 203 596 313 ;
|
||||
C -1 ; WX 600 ; N Icircumflex ; B 77 0 643 780 ;
|
||||
C -1 ; WX 600 ; N ncaron ; B 18 0 633 667 ;
|
||||
C -1 ; WX 600 ; N tcommaaccent ; B 118 -250 567 562 ;
|
||||
C -1 ; WX 600 ; N logicalnot ; B 135 103 617 413 ;
|
||||
C -1 ; WX 600 ; N odieresis ; B 71 -15 622 638 ;
|
||||
C -1 ; WX 600 ; N udieresis ; B 70 -15 595 638 ;
|
||||
C -1 ; WX 600 ; N notequal ; B 30 -47 626 563 ;
|
||||
C -1 ; WX 600 ; N gcommaaccent ; B 40 -146 674 714 ;
|
||||
C -1 ; WX 600 ; N eth ; B 93 -27 661 626 ;
|
||||
C -1 ; WX 600 ; N zcaron ; B 81 0 643 667 ;
|
||||
C -1 ; WX 600 ; N ncommaaccent ; B 18 -250 615 454 ;
|
||||
C -1 ; WX 600 ; N onesuperior ; B 212 230 514 616 ;
|
||||
C -1 ; WX 600 ; N imacron ; B 77 0 575 585 ;
|
||||
C -1 ; WX 600 ; N Euro ; B 0 0 0 0 ;
|
||||
EndCharMetrics
|
||||
EndFontMetrics
|
||||
@@ -0,0 +1,342 @@
|
||||
StartFontMetrics 4.1
|
||||
Comment Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
|
||||
Comment Creation Date: Thu May 1 17:37:52 1997
|
||||
Comment UniqueID 43051
|
||||
Comment VMusage 16248 75829
|
||||
FontName Courier-Oblique
|
||||
FullName Courier Oblique
|
||||
FamilyName Courier
|
||||
Weight Medium
|
||||
ItalicAngle -12
|
||||
IsFixedPitch true
|
||||
CharacterSet ExtendedRoman
|
||||
FontBBox -27 -250 849 805
|
||||
UnderlinePosition -100
|
||||
UnderlineThickness 50
|
||||
Version 003.000
|
||||
Notice Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
|
||||
EncodingScheme AdobeStandardEncoding
|
||||
CapHeight 562
|
||||
XHeight 426
|
||||
Ascender 629
|
||||
Descender -157
|
||||
StdHW 51
|
||||
StdVW 51
|
||||
StartCharMetrics 315
|
||||
C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
|
||||
C 33 ; WX 600 ; N exclam ; B 243 -15 464 572 ;
|
||||
C 34 ; WX 600 ; N quotedbl ; B 273 328 532 562 ;
|
||||
C 35 ; WX 600 ; N numbersign ; B 133 -32 596 639 ;
|
||||
C 36 ; WX 600 ; N dollar ; B 108 -126 596 662 ;
|
||||
C 37 ; WX 600 ; N percent ; B 134 -15 599 622 ;
|
||||
C 38 ; WX 600 ; N ampersand ; B 87 -15 580 543 ;
|
||||
C 39 ; WX 600 ; N quoteright ; B 283 328 495 562 ;
|
||||
C 40 ; WX 600 ; N parenleft ; B 313 -108 572 622 ;
|
||||
C 41 ; WX 600 ; N parenright ; B 137 -108 396 622 ;
|
||||
C 42 ; WX 600 ; N asterisk ; B 212 257 580 607 ;
|
||||
C 43 ; WX 600 ; N plus ; B 129 44 580 470 ;
|
||||
C 44 ; WX 600 ; N comma ; B 157 -112 370 122 ;
|
||||
C 45 ; WX 600 ; N hyphen ; B 152 231 558 285 ;
|
||||
C 46 ; WX 600 ; N period ; B 238 -15 382 109 ;
|
||||
C 47 ; WX 600 ; N slash ; B 112 -80 604 629 ;
|
||||
C 48 ; WX 600 ; N zero ; B 154 -15 575 622 ;
|
||||
C 49 ; WX 600 ; N one ; B 98 0 515 622 ;
|
||||
C 50 ; WX 600 ; N two ; B 70 0 568 622 ;
|
||||
C 51 ; WX 600 ; N three ; B 82 -15 538 622 ;
|
||||
C 52 ; WX 600 ; N four ; B 108 0 541 622 ;
|
||||
C 53 ; WX 600 ; N five ; B 99 -15 589 607 ;
|
||||
C 54 ; WX 600 ; N six ; B 155 -15 629 622 ;
|
||||
C 55 ; WX 600 ; N seven ; B 182 0 612 607 ;
|
||||
C 56 ; WX 600 ; N eight ; B 132 -15 588 622 ;
|
||||
C 57 ; WX 600 ; N nine ; B 93 -15 574 622 ;
|
||||
C 58 ; WX 600 ; N colon ; B 238 -15 441 385 ;
|
||||
C 59 ; WX 600 ; N semicolon ; B 157 -112 441 385 ;
|
||||
C 60 ; WX 600 ; N less ; B 96 42 610 472 ;
|
||||
C 61 ; WX 600 ; N equal ; B 109 138 600 376 ;
|
||||
C 62 ; WX 600 ; N greater ; B 85 42 599 472 ;
|
||||
C 63 ; WX 600 ; N question ; B 222 -15 583 572 ;
|
||||
C 64 ; WX 600 ; N at ; B 127 -15 582 622 ;
|
||||
C 65 ; WX 600 ; N A ; B 3 0 607 562 ;
|
||||
C 66 ; WX 600 ; N B ; B 43 0 616 562 ;
|
||||
C 67 ; WX 600 ; N C ; B 93 -18 655 580 ;
|
||||
C 68 ; WX 600 ; N D ; B 43 0 645 562 ;
|
||||
C 69 ; WX 600 ; N E ; B 53 0 660 562 ;
|
||||
C 70 ; WX 600 ; N F ; B 53 0 660 562 ;
|
||||
C 71 ; WX 600 ; N G ; B 83 -18 645 580 ;
|
||||
C 72 ; WX 600 ; N H ; B 32 0 687 562 ;
|
||||
C 73 ; WX 600 ; N I ; B 96 0 623 562 ;
|
||||
C 74 ; WX 600 ; N J ; B 52 -18 685 562 ;
|
||||
C 75 ; WX 600 ; N K ; B 38 0 671 562 ;
|
||||
C 76 ; WX 600 ; N L ; B 47 0 607 562 ;
|
||||
C 77 ; WX 600 ; N M ; B 4 0 715 562 ;
|
||||
C 78 ; WX 600 ; N N ; B 7 -13 712 562 ;
|
||||
C 79 ; WX 600 ; N O ; B 94 -18 625 580 ;
|
||||
C 80 ; WX 600 ; N P ; B 79 0 644 562 ;
|
||||
C 81 ; WX 600 ; N Q ; B 95 -138 625 580 ;
|
||||
C 82 ; WX 600 ; N R ; B 38 0 598 562 ;
|
||||
C 83 ; WX 600 ; N S ; B 76 -20 650 580 ;
|
||||
C 84 ; WX 600 ; N T ; B 108 0 665 562 ;
|
||||
C 85 ; WX 600 ; N U ; B 125 -18 702 562 ;
|
||||
C 86 ; WX 600 ; N V ; B 105 -13 723 562 ;
|
||||
C 87 ; WX 600 ; N W ; B 106 -13 722 562 ;
|
||||
C 88 ; WX 600 ; N X ; B 23 0 675 562 ;
|
||||
C 89 ; WX 600 ; N Y ; B 133 0 695 562 ;
|
||||
C 90 ; WX 600 ; N Z ; B 86 0 610 562 ;
|
||||
C 91 ; WX 600 ; N bracketleft ; B 246 -108 574 622 ;
|
||||
C 92 ; WX 600 ; N backslash ; B 249 -80 468 629 ;
|
||||
C 93 ; WX 600 ; N bracketright ; B 135 -108 463 622 ;
|
||||
C 94 ; WX 600 ; N asciicircum ; B 175 354 587 622 ;
|
||||
C 95 ; WX 600 ; N underscore ; B -27 -125 584 -75 ;
|
||||
C 96 ; WX 600 ; N quoteleft ; B 343 328 457 562 ;
|
||||
C 97 ; WX 600 ; N a ; B 76 -15 569 441 ;
|
||||
C 98 ; WX 600 ; N b ; B 29 -15 625 629 ;
|
||||
C 99 ; WX 600 ; N c ; B 106 -15 608 441 ;
|
||||
C 100 ; WX 600 ; N d ; B 85 -15 640 629 ;
|
||||
C 101 ; WX 600 ; N e ; B 106 -15 598 441 ;
|
||||
C 102 ; WX 600 ; N f ; B 114 0 662 629 ; L i fi ; L l fl ;
|
||||
C 103 ; WX 600 ; N g ; B 61 -157 657 441 ;
|
||||
C 104 ; WX 600 ; N h ; B 33 0 592 629 ;
|
||||
C 105 ; WX 600 ; N i ; B 95 0 515 657 ;
|
||||
C 106 ; WX 600 ; N j ; B 52 -157 550 657 ;
|
||||
C 107 ; WX 600 ; N k ; B 58 0 633 629 ;
|
||||
C 108 ; WX 600 ; N l ; B 95 0 515 629 ;
|
||||
C 109 ; WX 600 ; N m ; B -5 0 615 441 ;
|
||||
C 110 ; WX 600 ; N n ; B 26 0 585 441 ;
|
||||
C 111 ; WX 600 ; N o ; B 102 -15 588 441 ;
|
||||
C 112 ; WX 600 ; N p ; B -24 -157 605 441 ;
|
||||
C 113 ; WX 600 ; N q ; B 85 -157 682 441 ;
|
||||
C 114 ; WX 600 ; N r ; B 60 0 636 441 ;
|
||||
C 115 ; WX 600 ; N s ; B 78 -15 584 441 ;
|
||||
C 116 ; WX 600 ; N t ; B 167 -15 561 561 ;
|
||||
C 117 ; WX 600 ; N u ; B 101 -15 572 426 ;
|
||||
C 118 ; WX 600 ; N v ; B 90 -10 681 426 ;
|
||||
C 119 ; WX 600 ; N w ; B 76 -10 695 426 ;
|
||||
C 120 ; WX 600 ; N x ; B 20 0 655 426 ;
|
||||
C 121 ; WX 600 ; N y ; B -4 -157 683 426 ;
|
||||
C 122 ; WX 600 ; N z ; B 99 0 593 426 ;
|
||||
C 123 ; WX 600 ; N braceleft ; B 233 -108 569 622 ;
|
||||
C 124 ; WX 600 ; N bar ; B 222 -250 485 750 ;
|
||||
C 125 ; WX 600 ; N braceright ; B 140 -108 477 622 ;
|
||||
C 126 ; WX 600 ; N asciitilde ; B 116 197 600 320 ;
|
||||
C 161 ; WX 600 ; N exclamdown ; B 225 -157 445 430 ;
|
||||
C 162 ; WX 600 ; N cent ; B 151 -49 588 614 ;
|
||||
C 163 ; WX 600 ; N sterling ; B 124 -21 621 611 ;
|
||||
C 164 ; WX 600 ; N fraction ; B 84 -57 646 665 ;
|
||||
C 165 ; WX 600 ; N yen ; B 120 0 693 562 ;
|
||||
C 166 ; WX 600 ; N florin ; B -26 -143 671 622 ;
|
||||
C 167 ; WX 600 ; N section ; B 104 -78 590 580 ;
|
||||
C 168 ; WX 600 ; N currency ; B 94 58 628 506 ;
|
||||
C 169 ; WX 600 ; N quotesingle ; B 345 328 460 562 ;
|
||||
C 170 ; WX 600 ; N quotedblleft ; B 262 328 541 562 ;
|
||||
C 171 ; WX 600 ; N guillemotleft ; B 92 70 652 446 ;
|
||||
C 172 ; WX 600 ; N guilsinglleft ; B 204 70 540 446 ;
|
||||
C 173 ; WX 600 ; N guilsinglright ; B 170 70 506 446 ;
|
||||
C 174 ; WX 600 ; N fi ; B 3 0 619 629 ;
|
||||
C 175 ; WX 600 ; N fl ; B 3 0 619 629 ;
|
||||
C 177 ; WX 600 ; N endash ; B 124 231 586 285 ;
|
||||
C 178 ; WX 600 ; N dagger ; B 217 -78 546 580 ;
|
||||
C 179 ; WX 600 ; N daggerdbl ; B 163 -78 546 580 ;
|
||||
C 180 ; WX 600 ; N periodcentered ; B 275 189 434 327 ;
|
||||
C 182 ; WX 600 ; N paragraph ; B 100 -78 630 562 ;
|
||||
C 183 ; WX 600 ; N bullet ; B 224 130 485 383 ;
|
||||
C 184 ; WX 600 ; N quotesinglbase ; B 185 -134 397 100 ;
|
||||
C 185 ; WX 600 ; N quotedblbase ; B 115 -134 478 100 ;
|
||||
C 186 ; WX 600 ; N quotedblright ; B 213 328 576 562 ;
|
||||
C 187 ; WX 600 ; N guillemotright ; B 58 70 618 446 ;
|
||||
C 188 ; WX 600 ; N ellipsis ; B 46 -15 575 111 ;
|
||||
C 189 ; WX 600 ; N perthousand ; B 59 -15 627 622 ;
|
||||
C 191 ; WX 600 ; N questiondown ; B 105 -157 466 430 ;
|
||||
C 193 ; WX 600 ; N grave ; B 294 497 484 672 ;
|
||||
C 194 ; WX 600 ; N acute ; B 348 497 612 672 ;
|
||||
C 195 ; WX 600 ; N circumflex ; B 229 477 581 654 ;
|
||||
C 196 ; WX 600 ; N tilde ; B 212 489 629 606 ;
|
||||
C 197 ; WX 600 ; N macron ; B 232 525 600 565 ;
|
||||
C 198 ; WX 600 ; N breve ; B 279 501 576 609 ;
|
||||
C 199 ; WX 600 ; N dotaccent ; B 373 537 478 640 ;
|
||||
C 200 ; WX 600 ; N dieresis ; B 272 537 579 640 ;
|
||||
C 202 ; WX 600 ; N ring ; B 332 463 500 627 ;
|
||||
C 203 ; WX 600 ; N cedilla ; B 197 -151 344 10 ;
|
||||
C 205 ; WX 600 ; N hungarumlaut ; B 239 497 683 672 ;
|
||||
C 206 ; WX 600 ; N ogonek ; B 189 -172 377 4 ;
|
||||
C 207 ; WX 600 ; N caron ; B 262 492 614 669 ;
|
||||
C 208 ; WX 600 ; N emdash ; B 49 231 661 285 ;
|
||||
C 225 ; WX 600 ; N AE ; B 3 0 655 562 ;
|
||||
C 227 ; WX 600 ; N ordfeminine ; B 209 249 512 580 ;
|
||||
C 232 ; WX 600 ; N Lslash ; B 47 0 607 562 ;
|
||||
C 233 ; WX 600 ; N Oslash ; B 94 -80 625 629 ;
|
||||
C 234 ; WX 600 ; N OE ; B 59 0 672 562 ;
|
||||
C 235 ; WX 600 ; N ordmasculine ; B 210 249 535 580 ;
|
||||
C 241 ; WX 600 ; N ae ; B 41 -15 626 441 ;
|
||||
C 245 ; WX 600 ; N dotlessi ; B 95 0 515 426 ;
|
||||
C 248 ; WX 600 ; N lslash ; B 95 0 587 629 ;
|
||||
C 249 ; WX 600 ; N oslash ; B 102 -80 588 506 ;
|
||||
C 250 ; WX 600 ; N oe ; B 54 -15 615 441 ;
|
||||
C 251 ; WX 600 ; N germandbls ; B 48 -15 617 629 ;
|
||||
C -1 ; WX 600 ; N Idieresis ; B 96 0 623 753 ;
|
||||
C -1 ; WX 600 ; N eacute ; B 106 -15 612 672 ;
|
||||
C -1 ; WX 600 ; N abreve ; B 76 -15 576 609 ;
|
||||
C -1 ; WX 600 ; N uhungarumlaut ; B 101 -15 723 672 ;
|
||||
C -1 ; WX 600 ; N ecaron ; B 106 -15 614 669 ;
|
||||
C -1 ; WX 600 ; N Ydieresis ; B 133 0 695 753 ;
|
||||
C -1 ; WX 600 ; N divide ; B 136 48 573 467 ;
|
||||
C -1 ; WX 600 ; N Yacute ; B 133 0 695 805 ;
|
||||
C -1 ; WX 600 ; N Acircumflex ; B 3 0 607 787 ;
|
||||
C -1 ; WX 600 ; N aacute ; B 76 -15 612 672 ;
|
||||
C -1 ; WX 600 ; N Ucircumflex ; B 125 -18 702 787 ;
|
||||
C -1 ; WX 600 ; N yacute ; B -4 -157 683 672 ;
|
||||
C -1 ; WX 600 ; N scommaaccent ; B 78 -250 584 441 ;
|
||||
C -1 ; WX 600 ; N ecircumflex ; B 106 -15 598 654 ;
|
||||
C -1 ; WX 600 ; N Uring ; B 125 -18 702 760 ;
|
||||
C -1 ; WX 600 ; N Udieresis ; B 125 -18 702 753 ;
|
||||
C -1 ; WX 600 ; N aogonek ; B 76 -172 569 441 ;
|
||||
C -1 ; WX 600 ; N Uacute ; B 125 -18 702 805 ;
|
||||
C -1 ; WX 600 ; N uogonek ; B 101 -172 572 426 ;
|
||||
C -1 ; WX 600 ; N Edieresis ; B 53 0 660 753 ;
|
||||
C -1 ; WX 600 ; N Dcroat ; B 43 0 645 562 ;
|
||||
C -1 ; WX 600 ; N commaaccent ; B 145 -250 323 -58 ;
|
||||
C -1 ; WX 600 ; N copyright ; B 53 -18 667 580 ;
|
||||
C -1 ; WX 600 ; N Emacron ; B 53 0 660 698 ;
|
||||
C -1 ; WX 600 ; N ccaron ; B 106 -15 614 669 ;
|
||||
C -1 ; WX 600 ; N aring ; B 76 -15 569 627 ;
|
||||
C -1 ; WX 600 ; N Ncommaaccent ; B 7 -250 712 562 ;
|
||||
C -1 ; WX 600 ; N lacute ; B 95 0 640 805 ;
|
||||
C -1 ; WX 600 ; N agrave ; B 76 -15 569 672 ;
|
||||
C -1 ; WX 600 ; N Tcommaaccent ; B 108 -250 665 562 ;
|
||||
C -1 ; WX 600 ; N Cacute ; B 93 -18 655 805 ;
|
||||
C -1 ; WX 600 ; N atilde ; B 76 -15 629 606 ;
|
||||
C -1 ; WX 600 ; N Edotaccent ; B 53 0 660 753 ;
|
||||
C -1 ; WX 600 ; N scaron ; B 78 -15 614 669 ;
|
||||
C -1 ; WX 600 ; N scedilla ; B 78 -151 584 441 ;
|
||||
C -1 ; WX 600 ; N iacute ; B 95 0 612 672 ;
|
||||
C -1 ; WX 600 ; N lozenge ; B 94 0 519 706 ;
|
||||
C -1 ; WX 600 ; N Rcaron ; B 38 0 642 802 ;
|
||||
C -1 ; WX 600 ; N Gcommaaccent ; B 83 -250 645 580 ;
|
||||
C -1 ; WX 600 ; N ucircumflex ; B 101 -15 572 654 ;
|
||||
C -1 ; WX 600 ; N acircumflex ; B 76 -15 581 654 ;
|
||||
C -1 ; WX 600 ; N Amacron ; B 3 0 607 698 ;
|
||||
C -1 ; WX 600 ; N rcaron ; B 60 0 636 669 ;
|
||||
C -1 ; WX 600 ; N ccedilla ; B 106 -151 614 441 ;
|
||||
C -1 ; WX 600 ; N Zdotaccent ; B 86 0 610 753 ;
|
||||
C -1 ; WX 600 ; N Thorn ; B 79 0 606 562 ;
|
||||
C -1 ; WX 600 ; N Omacron ; B 94 -18 628 698 ;
|
||||
C -1 ; WX 600 ; N Racute ; B 38 0 670 805 ;
|
||||
C -1 ; WX 600 ; N Sacute ; B 76 -20 650 805 ;
|
||||
C -1 ; WX 600 ; N dcaron ; B 85 -15 849 629 ;
|
||||
C -1 ; WX 600 ; N Umacron ; B 125 -18 702 698 ;
|
||||
C -1 ; WX 600 ; N uring ; B 101 -15 572 627 ;
|
||||
C -1 ; WX 600 ; N threesuperior ; B 213 240 501 622 ;
|
||||
C -1 ; WX 600 ; N Ograve ; B 94 -18 625 805 ;
|
||||
C -1 ; WX 600 ; N Agrave ; B 3 0 607 805 ;
|
||||
C -1 ; WX 600 ; N Abreve ; B 3 0 607 732 ;
|
||||
C -1 ; WX 600 ; N multiply ; B 103 43 607 470 ;
|
||||
C -1 ; WX 600 ; N uacute ; B 101 -15 602 672 ;
|
||||
C -1 ; WX 600 ; N Tcaron ; B 108 0 665 802 ;
|
||||
C -1 ; WX 600 ; N partialdiff ; B 45 -38 546 710 ;
|
||||
C -1 ; WX 600 ; N ydieresis ; B -4 -157 683 620 ;
|
||||
C -1 ; WX 600 ; N Nacute ; B 7 -13 712 805 ;
|
||||
C -1 ; WX 600 ; N icircumflex ; B 95 0 551 654 ;
|
||||
C -1 ; WX 600 ; N Ecircumflex ; B 53 0 660 787 ;
|
||||
C -1 ; WX 600 ; N adieresis ; B 76 -15 575 620 ;
|
||||
C -1 ; WX 600 ; N edieresis ; B 106 -15 598 620 ;
|
||||
C -1 ; WX 600 ; N cacute ; B 106 -15 612 672 ;
|
||||
C -1 ; WX 600 ; N nacute ; B 26 0 602 672 ;
|
||||
C -1 ; WX 600 ; N umacron ; B 101 -15 600 565 ;
|
||||
C -1 ; WX 600 ; N Ncaron ; B 7 -13 712 802 ;
|
||||
C -1 ; WX 600 ; N Iacute ; B 96 0 640 805 ;
|
||||
C -1 ; WX 600 ; N plusminus ; B 96 44 594 558 ;
|
||||
C -1 ; WX 600 ; N brokenbar ; B 238 -175 469 675 ;
|
||||
C -1 ; WX 600 ; N registered ; B 53 -18 667 580 ;
|
||||
C -1 ; WX 600 ; N Gbreve ; B 83 -18 645 732 ;
|
||||
C -1 ; WX 600 ; N Idotaccent ; B 96 0 623 753 ;
|
||||
C -1 ; WX 600 ; N summation ; B 15 -10 670 706 ;
|
||||
C -1 ; WX 600 ; N Egrave ; B 53 0 660 805 ;
|
||||
C -1 ; WX 600 ; N racute ; B 60 0 636 672 ;
|
||||
C -1 ; WX 600 ; N omacron ; B 102 -15 600 565 ;
|
||||
C -1 ; WX 600 ; N Zacute ; B 86 0 670 805 ;
|
||||
C -1 ; WX 600 ; N Zcaron ; B 86 0 642 802 ;
|
||||
C -1 ; WX 600 ; N greaterequal ; B 98 0 594 710 ;
|
||||
C -1 ; WX 600 ; N Eth ; B 43 0 645 562 ;
|
||||
C -1 ; WX 600 ; N Ccedilla ; B 93 -151 658 580 ;
|
||||
C -1 ; WX 600 ; N lcommaaccent ; B 95 -250 515 629 ;
|
||||
C -1 ; WX 600 ; N tcaron ; B 167 -15 587 717 ;
|
||||
C -1 ; WX 600 ; N eogonek ; B 106 -172 598 441 ;
|
||||
C -1 ; WX 600 ; N Uogonek ; B 124 -172 702 562 ;
|
||||
C -1 ; WX 600 ; N Aacute ; B 3 0 660 805 ;
|
||||
C -1 ; WX 600 ; N Adieresis ; B 3 0 607 753 ;
|
||||
C -1 ; WX 600 ; N egrave ; B 106 -15 598 672 ;
|
||||
C -1 ; WX 600 ; N zacute ; B 99 0 612 672 ;
|
||||
C -1 ; WX 600 ; N iogonek ; B 95 -172 515 657 ;
|
||||
C -1 ; WX 600 ; N Oacute ; B 94 -18 640 805 ;
|
||||
C -1 ; WX 600 ; N oacute ; B 102 -15 612 672 ;
|
||||
C -1 ; WX 600 ; N amacron ; B 76 -15 600 565 ;
|
||||
C -1 ; WX 600 ; N sacute ; B 78 -15 612 672 ;
|
||||
C -1 ; WX 600 ; N idieresis ; B 95 0 545 620 ;
|
||||
C -1 ; WX 600 ; N Ocircumflex ; B 94 -18 625 787 ;
|
||||
C -1 ; WX 600 ; N Ugrave ; B 125 -18 702 805 ;
|
||||
C -1 ; WX 600 ; N Delta ; B 6 0 598 688 ;
|
||||
C -1 ; WX 600 ; N thorn ; B -24 -157 605 629 ;
|
||||
C -1 ; WX 600 ; N twosuperior ; B 230 249 535 622 ;
|
||||
C -1 ; WX 600 ; N Odieresis ; B 94 -18 625 753 ;
|
||||
C -1 ; WX 600 ; N mu ; B 72 -157 572 426 ;
|
||||
C -1 ; WX 600 ; N igrave ; B 95 0 515 672 ;
|
||||
C -1 ; WX 600 ; N ohungarumlaut ; B 102 -15 723 672 ;
|
||||
C -1 ; WX 600 ; N Eogonek ; B 53 -172 660 562 ;
|
||||
C -1 ; WX 600 ; N dcroat ; B 85 -15 704 629 ;
|
||||
C -1 ; WX 600 ; N threequarters ; B 73 -56 659 666 ;
|
||||
C -1 ; WX 600 ; N Scedilla ; B 76 -151 650 580 ;
|
||||
C -1 ; WX 600 ; N lcaron ; B 95 0 667 629 ;
|
||||
C -1 ; WX 600 ; N Kcommaaccent ; B 38 -250 671 562 ;
|
||||
C -1 ; WX 600 ; N Lacute ; B 47 0 607 805 ;
|
||||
C -1 ; WX 600 ; N trademark ; B 75 263 742 562 ;
|
||||
C -1 ; WX 600 ; N edotaccent ; B 106 -15 598 620 ;
|
||||
C -1 ; WX 600 ; N Igrave ; B 96 0 623 805 ;
|
||||
C -1 ; WX 600 ; N Imacron ; B 96 0 628 698 ;
|
||||
C -1 ; WX 600 ; N Lcaron ; B 47 0 632 562 ;
|
||||
C -1 ; WX 600 ; N onehalf ; B 65 -57 669 665 ;
|
||||
C -1 ; WX 600 ; N lessequal ; B 98 0 645 710 ;
|
||||
C -1 ; WX 600 ; N ocircumflex ; B 102 -15 588 654 ;
|
||||
C -1 ; WX 600 ; N ntilde ; B 26 0 629 606 ;
|
||||
C -1 ; WX 600 ; N Uhungarumlaut ; B 125 -18 761 805 ;
|
||||
C -1 ; WX 600 ; N Eacute ; B 53 0 670 805 ;
|
||||
C -1 ; WX 600 ; N emacron ; B 106 -15 600 565 ;
|
||||
C -1 ; WX 600 ; N gbreve ; B 61 -157 657 609 ;
|
||||
C -1 ; WX 600 ; N onequarter ; B 65 -57 674 665 ;
|
||||
C -1 ; WX 600 ; N Scaron ; B 76 -20 672 802 ;
|
||||
C -1 ; WX 600 ; N Scommaaccent ; B 76 -250 650 580 ;
|
||||
C -1 ; WX 600 ; N Ohungarumlaut ; B 94 -18 751 805 ;
|
||||
C -1 ; WX 600 ; N degree ; B 214 269 576 622 ;
|
||||
C -1 ; WX 600 ; N ograve ; B 102 -15 588 672 ;
|
||||
C -1 ; WX 600 ; N Ccaron ; B 93 -18 672 802 ;
|
||||
C -1 ; WX 600 ; N ugrave ; B 101 -15 572 672 ;
|
||||
C -1 ; WX 600 ; N radical ; B 85 -15 765 792 ;
|
||||
C -1 ; WX 600 ; N Dcaron ; B 43 0 645 802 ;
|
||||
C -1 ; WX 600 ; N rcommaaccent ; B 60 -250 636 441 ;
|
||||
C -1 ; WX 600 ; N Ntilde ; B 7 -13 712 729 ;
|
||||
C -1 ; WX 600 ; N otilde ; B 102 -15 629 606 ;
|
||||
C -1 ; WX 600 ; N Rcommaaccent ; B 38 -250 598 562 ;
|
||||
C -1 ; WX 600 ; N Lcommaaccent ; B 47 -250 607 562 ;
|
||||
C -1 ; WX 600 ; N Atilde ; B 3 0 655 729 ;
|
||||
C -1 ; WX 600 ; N Aogonek ; B 3 -172 607 562 ;
|
||||
C -1 ; WX 600 ; N Aring ; B 3 0 607 750 ;
|
||||
C -1 ; WX 600 ; N Otilde ; B 94 -18 655 729 ;
|
||||
C -1 ; WX 600 ; N zdotaccent ; B 99 0 593 620 ;
|
||||
C -1 ; WX 600 ; N Ecaron ; B 53 0 660 802 ;
|
||||
C -1 ; WX 600 ; N Iogonek ; B 96 -172 623 562 ;
|
||||
C -1 ; WX 600 ; N kcommaaccent ; B 58 -250 633 629 ;
|
||||
C -1 ; WX 600 ; N minus ; B 129 232 580 283 ;
|
||||
C -1 ; WX 600 ; N Icircumflex ; B 96 0 623 787 ;
|
||||
C -1 ; WX 600 ; N ncaron ; B 26 0 614 669 ;
|
||||
C -1 ; WX 600 ; N tcommaaccent ; B 165 -250 561 561 ;
|
||||
C -1 ; WX 600 ; N logicalnot ; B 155 108 591 369 ;
|
||||
C -1 ; WX 600 ; N odieresis ; B 102 -15 588 620 ;
|
||||
C -1 ; WX 600 ; N udieresis ; B 101 -15 575 620 ;
|
||||
C -1 ; WX 600 ; N notequal ; B 43 -16 621 529 ;
|
||||
C -1 ; WX 600 ; N gcommaaccent ; B 61 -157 657 708 ;
|
||||
C -1 ; WX 600 ; N eth ; B 102 -15 639 629 ;
|
||||
C -1 ; WX 600 ; N zcaron ; B 99 0 624 669 ;
|
||||
C -1 ; WX 600 ; N ncommaaccent ; B 26 -250 585 441 ;
|
||||
C -1 ; WX 600 ; N onesuperior ; B 231 249 491 622 ;
|
||||
C -1 ; WX 600 ; N imacron ; B 95 0 543 565 ;
|
||||
C -1 ; WX 600 ; N Euro ; B 0 0 0 0 ;
|
||||
EndCharMetrics
|
||||
EndFontMetrics
|
||||
@@ -0,0 +1,342 @@
|
||||
StartFontMetrics 4.1
|
||||
Comment Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
|
||||
Comment Creation Date: Thu May 1 17:27:09 1997
|
||||
Comment UniqueID 43050
|
||||
Comment VMusage 39754 50779
|
||||
FontName Courier
|
||||
FullName Courier
|
||||
FamilyName Courier
|
||||
Weight Medium
|
||||
ItalicAngle 0
|
||||
IsFixedPitch true
|
||||
CharacterSet ExtendedRoman
|
||||
FontBBox -23 -250 715 805
|
||||
UnderlinePosition -100
|
||||
UnderlineThickness 50
|
||||
Version 003.000
|
||||
Notice Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
|
||||
EncodingScheme AdobeStandardEncoding
|
||||
CapHeight 562
|
||||
XHeight 426
|
||||
Ascender 629
|
||||
Descender -157
|
||||
StdHW 51
|
||||
StdVW 51
|
||||
StartCharMetrics 315
|
||||
C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
|
||||
C 33 ; WX 600 ; N exclam ; B 236 -15 364 572 ;
|
||||
C 34 ; WX 600 ; N quotedbl ; B 187 328 413 562 ;
|
||||
C 35 ; WX 600 ; N numbersign ; B 93 -32 507 639 ;
|
||||
C 36 ; WX 600 ; N dollar ; B 105 -126 496 662 ;
|
||||
C 37 ; WX 600 ; N percent ; B 81 -15 518 622 ;
|
||||
C 38 ; WX 600 ; N ampersand ; B 63 -15 538 543 ;
|
||||
C 39 ; WX 600 ; N quoteright ; B 213 328 376 562 ;
|
||||
C 40 ; WX 600 ; N parenleft ; B 269 -108 440 622 ;
|
||||
C 41 ; WX 600 ; N parenright ; B 160 -108 331 622 ;
|
||||
C 42 ; WX 600 ; N asterisk ; B 116 257 484 607 ;
|
||||
C 43 ; WX 600 ; N plus ; B 80 44 520 470 ;
|
||||
C 44 ; WX 600 ; N comma ; B 181 -112 344 122 ;
|
||||
C 45 ; WX 600 ; N hyphen ; B 103 231 497 285 ;
|
||||
C 46 ; WX 600 ; N period ; B 229 -15 371 109 ;
|
||||
C 47 ; WX 600 ; N slash ; B 125 -80 475 629 ;
|
||||
C 48 ; WX 600 ; N zero ; B 106 -15 494 622 ;
|
||||
C 49 ; WX 600 ; N one ; B 96 0 505 622 ;
|
||||
C 50 ; WX 600 ; N two ; B 70 0 471 622 ;
|
||||
C 51 ; WX 600 ; N three ; B 75 -15 466 622 ;
|
||||
C 52 ; WX 600 ; N four ; B 78 0 500 622 ;
|
||||
C 53 ; WX 600 ; N five ; B 92 -15 497 607 ;
|
||||
C 54 ; WX 600 ; N six ; B 111 -15 497 622 ;
|
||||
C 55 ; WX 600 ; N seven ; B 82 0 483 607 ;
|
||||
C 56 ; WX 600 ; N eight ; B 102 -15 498 622 ;
|
||||
C 57 ; WX 600 ; N nine ; B 96 -15 489 622 ;
|
||||
C 58 ; WX 600 ; N colon ; B 229 -15 371 385 ;
|
||||
C 59 ; WX 600 ; N semicolon ; B 181 -112 371 385 ;
|
||||
C 60 ; WX 600 ; N less ; B 41 42 519 472 ;
|
||||
C 61 ; WX 600 ; N equal ; B 80 138 520 376 ;
|
||||
C 62 ; WX 600 ; N greater ; B 66 42 544 472 ;
|
||||
C 63 ; WX 600 ; N question ; B 129 -15 492 572 ;
|
||||
C 64 ; WX 600 ; N at ; B 77 -15 533 622 ;
|
||||
C 65 ; WX 600 ; N A ; B 3 0 597 562 ;
|
||||
C 66 ; WX 600 ; N B ; B 43 0 559 562 ;
|
||||
C 67 ; WX 600 ; N C ; B 41 -18 540 580 ;
|
||||
C 68 ; WX 600 ; N D ; B 43 0 574 562 ;
|
||||
C 69 ; WX 600 ; N E ; B 53 0 550 562 ;
|
||||
C 70 ; WX 600 ; N F ; B 53 0 545 562 ;
|
||||
C 71 ; WX 600 ; N G ; B 31 -18 575 580 ;
|
||||
C 72 ; WX 600 ; N H ; B 32 0 568 562 ;
|
||||
C 73 ; WX 600 ; N I ; B 96 0 504 562 ;
|
||||
C 74 ; WX 600 ; N J ; B 34 -18 566 562 ;
|
||||
C 75 ; WX 600 ; N K ; B 38 0 582 562 ;
|
||||
C 76 ; WX 600 ; N L ; B 47 0 554 562 ;
|
||||
C 77 ; WX 600 ; N M ; B 4 0 596 562 ;
|
||||
C 78 ; WX 600 ; N N ; B 7 -13 593 562 ;
|
||||
C 79 ; WX 600 ; N O ; B 43 -18 557 580 ;
|
||||
C 80 ; WX 600 ; N P ; B 79 0 558 562 ;
|
||||
C 81 ; WX 600 ; N Q ; B 43 -138 557 580 ;
|
||||
C 82 ; WX 600 ; N R ; B 38 0 588 562 ;
|
||||
C 83 ; WX 600 ; N S ; B 72 -20 529 580 ;
|
||||
C 84 ; WX 600 ; N T ; B 38 0 563 562 ;
|
||||
C 85 ; WX 600 ; N U ; B 17 -18 583 562 ;
|
||||
C 86 ; WX 600 ; N V ; B -4 -13 604 562 ;
|
||||
C 87 ; WX 600 ; N W ; B -3 -13 603 562 ;
|
||||
C 88 ; WX 600 ; N X ; B 23 0 577 562 ;
|
||||
C 89 ; WX 600 ; N Y ; B 24 0 576 562 ;
|
||||
C 90 ; WX 600 ; N Z ; B 86 0 514 562 ;
|
||||
C 91 ; WX 600 ; N bracketleft ; B 269 -108 442 622 ;
|
||||
C 92 ; WX 600 ; N backslash ; B 118 -80 482 629 ;
|
||||
C 93 ; WX 600 ; N bracketright ; B 158 -108 331 622 ;
|
||||
C 94 ; WX 600 ; N asciicircum ; B 94 354 506 622 ;
|
||||
C 95 ; WX 600 ; N underscore ; B 0 -125 600 -75 ;
|
||||
C 96 ; WX 600 ; N quoteleft ; B 224 328 387 562 ;
|
||||
C 97 ; WX 600 ; N a ; B 53 -15 559 441 ;
|
||||
C 98 ; WX 600 ; N b ; B 14 -15 575 629 ;
|
||||
C 99 ; WX 600 ; N c ; B 66 -15 529 441 ;
|
||||
C 100 ; WX 600 ; N d ; B 45 -15 591 629 ;
|
||||
C 101 ; WX 600 ; N e ; B 66 -15 548 441 ;
|
||||
C 102 ; WX 600 ; N f ; B 114 0 531 629 ; L i fi ; L l fl ;
|
||||
C 103 ; WX 600 ; N g ; B 45 -157 566 441 ;
|
||||
C 104 ; WX 600 ; N h ; B 18 0 582 629 ;
|
||||
C 105 ; WX 600 ; N i ; B 95 0 505 657 ;
|
||||
C 106 ; WX 600 ; N j ; B 82 -157 410 657 ;
|
||||
C 107 ; WX 600 ; N k ; B 43 0 580 629 ;
|
||||
C 108 ; WX 600 ; N l ; B 95 0 505 629 ;
|
||||
C 109 ; WX 600 ; N m ; B -5 0 605 441 ;
|
||||
C 110 ; WX 600 ; N n ; B 26 0 575 441 ;
|
||||
C 111 ; WX 600 ; N o ; B 62 -15 538 441 ;
|
||||
C 112 ; WX 600 ; N p ; B 9 -157 555 441 ;
|
||||
C 113 ; WX 600 ; N q ; B 45 -157 591 441 ;
|
||||
C 114 ; WX 600 ; N r ; B 60 0 559 441 ;
|
||||
C 115 ; WX 600 ; N s ; B 80 -15 513 441 ;
|
||||
C 116 ; WX 600 ; N t ; B 87 -15 530 561 ;
|
||||
C 117 ; WX 600 ; N u ; B 21 -15 562 426 ;
|
||||
C 118 ; WX 600 ; N v ; B 10 -10 590 426 ;
|
||||
C 119 ; WX 600 ; N w ; B -4 -10 604 426 ;
|
||||
C 120 ; WX 600 ; N x ; B 20 0 580 426 ;
|
||||
C 121 ; WX 600 ; N y ; B 7 -157 592 426 ;
|
||||
C 122 ; WX 600 ; N z ; B 99 0 502 426 ;
|
||||
C 123 ; WX 600 ; N braceleft ; B 182 -108 437 622 ;
|
||||
C 124 ; WX 600 ; N bar ; B 275 -250 326 750 ;
|
||||
C 125 ; WX 600 ; N braceright ; B 163 -108 418 622 ;
|
||||
C 126 ; WX 600 ; N asciitilde ; B 63 197 540 320 ;
|
||||
C 161 ; WX 600 ; N exclamdown ; B 236 -157 364 430 ;
|
||||
C 162 ; WX 600 ; N cent ; B 96 -49 500 614 ;
|
||||
C 163 ; WX 600 ; N sterling ; B 84 -21 521 611 ;
|
||||
C 164 ; WX 600 ; N fraction ; B 92 -57 509 665 ;
|
||||
C 165 ; WX 600 ; N yen ; B 26 0 574 562 ;
|
||||
C 166 ; WX 600 ; N florin ; B 4 -143 539 622 ;
|
||||
C 167 ; WX 600 ; N section ; B 113 -78 488 580 ;
|
||||
C 168 ; WX 600 ; N currency ; B 73 58 527 506 ;
|
||||
C 169 ; WX 600 ; N quotesingle ; B 259 328 341 562 ;
|
||||
C 170 ; WX 600 ; N quotedblleft ; B 143 328 471 562 ;
|
||||
C 171 ; WX 600 ; N guillemotleft ; B 37 70 563 446 ;
|
||||
C 172 ; WX 600 ; N guilsinglleft ; B 149 70 451 446 ;
|
||||
C 173 ; WX 600 ; N guilsinglright ; B 149 70 451 446 ;
|
||||
C 174 ; WX 600 ; N fi ; B 3 0 597 629 ;
|
||||
C 175 ; WX 600 ; N fl ; B 3 0 597 629 ;
|
||||
C 177 ; WX 600 ; N endash ; B 75 231 525 285 ;
|
||||
C 178 ; WX 600 ; N dagger ; B 141 -78 459 580 ;
|
||||
C 179 ; WX 600 ; N daggerdbl ; B 141 -78 459 580 ;
|
||||
C 180 ; WX 600 ; N periodcentered ; B 222 189 378 327 ;
|
||||
C 182 ; WX 600 ; N paragraph ; B 50 -78 511 562 ;
|
||||
C 183 ; WX 600 ; N bullet ; B 172 130 428 383 ;
|
||||
C 184 ; WX 600 ; N quotesinglbase ; B 213 -134 376 100 ;
|
||||
C 185 ; WX 600 ; N quotedblbase ; B 143 -134 457 100 ;
|
||||
C 186 ; WX 600 ; N quotedblright ; B 143 328 457 562 ;
|
||||
C 187 ; WX 600 ; N guillemotright ; B 37 70 563 446 ;
|
||||
C 188 ; WX 600 ; N ellipsis ; B 37 -15 563 111 ;
|
||||
C 189 ; WX 600 ; N perthousand ; B 3 -15 600 622 ;
|
||||
C 191 ; WX 600 ; N questiondown ; B 108 -157 471 430 ;
|
||||
C 193 ; WX 600 ; N grave ; B 151 497 378 672 ;
|
||||
C 194 ; WX 600 ; N acute ; B 242 497 469 672 ;
|
||||
C 195 ; WX 600 ; N circumflex ; B 124 477 476 654 ;
|
||||
C 196 ; WX 600 ; N tilde ; B 105 489 503 606 ;
|
||||
C 197 ; WX 600 ; N macron ; B 120 525 480 565 ;
|
||||
C 198 ; WX 600 ; N breve ; B 153 501 447 609 ;
|
||||
C 199 ; WX 600 ; N dotaccent ; B 249 537 352 640 ;
|
||||
C 200 ; WX 600 ; N dieresis ; B 148 537 453 640 ;
|
||||
C 202 ; WX 600 ; N ring ; B 218 463 382 627 ;
|
||||
C 203 ; WX 600 ; N cedilla ; B 224 -151 362 10 ;
|
||||
C 205 ; WX 600 ; N hungarumlaut ; B 133 497 540 672 ;
|
||||
C 206 ; WX 600 ; N ogonek ; B 211 -172 407 4 ;
|
||||
C 207 ; WX 600 ; N caron ; B 124 492 476 669 ;
|
||||
C 208 ; WX 600 ; N emdash ; B 0 231 600 285 ;
|
||||
C 225 ; WX 600 ; N AE ; B 3 0 550 562 ;
|
||||
C 227 ; WX 600 ; N ordfeminine ; B 156 249 442 580 ;
|
||||
C 232 ; WX 600 ; N Lslash ; B 47 0 554 562 ;
|
||||
C 233 ; WX 600 ; N Oslash ; B 43 -80 557 629 ;
|
||||
C 234 ; WX 600 ; N OE ; B 7 0 567 562 ;
|
||||
C 235 ; WX 600 ; N ordmasculine ; B 157 249 443 580 ;
|
||||
C 241 ; WX 600 ; N ae ; B 19 -15 570 441 ;
|
||||
C 245 ; WX 600 ; N dotlessi ; B 95 0 505 426 ;
|
||||
C 248 ; WX 600 ; N lslash ; B 95 0 505 629 ;
|
||||
C 249 ; WX 600 ; N oslash ; B 62 -80 538 506 ;
|
||||
C 250 ; WX 600 ; N oe ; B 19 -15 559 441 ;
|
||||
C 251 ; WX 600 ; N germandbls ; B 48 -15 588 629 ;
|
||||
C -1 ; WX 600 ; N Idieresis ; B 96 0 504 753 ;
|
||||
C -1 ; WX 600 ; N eacute ; B 66 -15 548 672 ;
|
||||
C -1 ; WX 600 ; N abreve ; B 53 -15 559 609 ;
|
||||
C -1 ; WX 600 ; N uhungarumlaut ; B 21 -15 580 672 ;
|
||||
C -1 ; WX 600 ; N ecaron ; B 66 -15 548 669 ;
|
||||
C -1 ; WX 600 ; N Ydieresis ; B 24 0 576 753 ;
|
||||
C -1 ; WX 600 ; N divide ; B 87 48 513 467 ;
|
||||
C -1 ; WX 600 ; N Yacute ; B 24 0 576 805 ;
|
||||
C -1 ; WX 600 ; N Acircumflex ; B 3 0 597 787 ;
|
||||
C -1 ; WX 600 ; N aacute ; B 53 -15 559 672 ;
|
||||
C -1 ; WX 600 ; N Ucircumflex ; B 17 -18 583 787 ;
|
||||
C -1 ; WX 600 ; N yacute ; B 7 -157 592 672 ;
|
||||
C -1 ; WX 600 ; N scommaaccent ; B 80 -250 513 441 ;
|
||||
C -1 ; WX 600 ; N ecircumflex ; B 66 -15 548 654 ;
|
||||
C -1 ; WX 600 ; N Uring ; B 17 -18 583 760 ;
|
||||
C -1 ; WX 600 ; N Udieresis ; B 17 -18 583 753 ;
|
||||
C -1 ; WX 600 ; N aogonek ; B 53 -172 587 441 ;
|
||||
C -1 ; WX 600 ; N Uacute ; B 17 -18 583 805 ;
|
||||
C -1 ; WX 600 ; N uogonek ; B 21 -172 590 426 ;
|
||||
C -1 ; WX 600 ; N Edieresis ; B 53 0 550 753 ;
|
||||
C -1 ; WX 600 ; N Dcroat ; B 30 0 574 562 ;
|
||||
C -1 ; WX 600 ; N commaaccent ; B 198 -250 335 -58 ;
|
||||
C -1 ; WX 600 ; N copyright ; B 0 -18 600 580 ;
|
||||
C -1 ; WX 600 ; N Emacron ; B 53 0 550 698 ;
|
||||
C -1 ; WX 600 ; N ccaron ; B 66 -15 529 669 ;
|
||||
C -1 ; WX 600 ; N aring ; B 53 -15 559 627 ;
|
||||
C -1 ; WX 600 ; N Ncommaaccent ; B 7 -250 593 562 ;
|
||||
C -1 ; WX 600 ; N lacute ; B 95 0 505 805 ;
|
||||
C -1 ; WX 600 ; N agrave ; B 53 -15 559 672 ;
|
||||
C -1 ; WX 600 ; N Tcommaaccent ; B 38 -250 563 562 ;
|
||||
C -1 ; WX 600 ; N Cacute ; B 41 -18 540 805 ;
|
||||
C -1 ; WX 600 ; N atilde ; B 53 -15 559 606 ;
|
||||
C -1 ; WX 600 ; N Edotaccent ; B 53 0 550 753 ;
|
||||
C -1 ; WX 600 ; N scaron ; B 80 -15 513 669 ;
|
||||
C -1 ; WX 600 ; N scedilla ; B 80 -151 513 441 ;
|
||||
C -1 ; WX 600 ; N iacute ; B 95 0 505 672 ;
|
||||
C -1 ; WX 600 ; N lozenge ; B 18 0 443 706 ;
|
||||
C -1 ; WX 600 ; N Rcaron ; B 38 0 588 802 ;
|
||||
C -1 ; WX 600 ; N Gcommaaccent ; B 31 -250 575 580 ;
|
||||
C -1 ; WX 600 ; N ucircumflex ; B 21 -15 562 654 ;
|
||||
C -1 ; WX 600 ; N acircumflex ; B 53 -15 559 654 ;
|
||||
C -1 ; WX 600 ; N Amacron ; B 3 0 597 698 ;
|
||||
C -1 ; WX 600 ; N rcaron ; B 60 0 559 669 ;
|
||||
C -1 ; WX 600 ; N ccedilla ; B 66 -151 529 441 ;
|
||||
C -1 ; WX 600 ; N Zdotaccent ; B 86 0 514 753 ;
|
||||
C -1 ; WX 600 ; N Thorn ; B 79 0 538 562 ;
|
||||
C -1 ; WX 600 ; N Omacron ; B 43 -18 557 698 ;
|
||||
C -1 ; WX 600 ; N Racute ; B 38 0 588 805 ;
|
||||
C -1 ; WX 600 ; N Sacute ; B 72 -20 529 805 ;
|
||||
C -1 ; WX 600 ; N dcaron ; B 45 -15 715 629 ;
|
||||
C -1 ; WX 600 ; N Umacron ; B 17 -18 583 698 ;
|
||||
C -1 ; WX 600 ; N uring ; B 21 -15 562 627 ;
|
||||
C -1 ; WX 600 ; N threesuperior ; B 155 240 406 622 ;
|
||||
C -1 ; WX 600 ; N Ograve ; B 43 -18 557 805 ;
|
||||
C -1 ; WX 600 ; N Agrave ; B 3 0 597 805 ;
|
||||
C -1 ; WX 600 ; N Abreve ; B 3 0 597 732 ;
|
||||
C -1 ; WX 600 ; N multiply ; B 87 43 515 470 ;
|
||||
C -1 ; WX 600 ; N uacute ; B 21 -15 562 672 ;
|
||||
C -1 ; WX 600 ; N Tcaron ; B 38 0 563 802 ;
|
||||
C -1 ; WX 600 ; N partialdiff ; B 17 -38 459 710 ;
|
||||
C -1 ; WX 600 ; N ydieresis ; B 7 -157 592 620 ;
|
||||
C -1 ; WX 600 ; N Nacute ; B 7 -13 593 805 ;
|
||||
C -1 ; WX 600 ; N icircumflex ; B 94 0 505 654 ;
|
||||
C -1 ; WX 600 ; N Ecircumflex ; B 53 0 550 787 ;
|
||||
C -1 ; WX 600 ; N adieresis ; B 53 -15 559 620 ;
|
||||
C -1 ; WX 600 ; N edieresis ; B 66 -15 548 620 ;
|
||||
C -1 ; WX 600 ; N cacute ; B 66 -15 529 672 ;
|
||||
C -1 ; WX 600 ; N nacute ; B 26 0 575 672 ;
|
||||
C -1 ; WX 600 ; N umacron ; B 21 -15 562 565 ;
|
||||
C -1 ; WX 600 ; N Ncaron ; B 7 -13 593 802 ;
|
||||
C -1 ; WX 600 ; N Iacute ; B 96 0 504 805 ;
|
||||
C -1 ; WX 600 ; N plusminus ; B 87 44 513 558 ;
|
||||
C -1 ; WX 600 ; N brokenbar ; B 275 -175 326 675 ;
|
||||
C -1 ; WX 600 ; N registered ; B 0 -18 600 580 ;
|
||||
C -1 ; WX 600 ; N Gbreve ; B 31 -18 575 732 ;
|
||||
C -1 ; WX 600 ; N Idotaccent ; B 96 0 504 753 ;
|
||||
C -1 ; WX 600 ; N summation ; B 15 -10 585 706 ;
|
||||
C -1 ; WX 600 ; N Egrave ; B 53 0 550 805 ;
|
||||
C -1 ; WX 600 ; N racute ; B 60 0 559 672 ;
|
||||
C -1 ; WX 600 ; N omacron ; B 62 -15 538 565 ;
|
||||
C -1 ; WX 600 ; N Zacute ; B 86 0 514 805 ;
|
||||
C -1 ; WX 600 ; N Zcaron ; B 86 0 514 802 ;
|
||||
C -1 ; WX 600 ; N greaterequal ; B 98 0 502 710 ;
|
||||
C -1 ; WX 600 ; N Eth ; B 30 0 574 562 ;
|
||||
C -1 ; WX 600 ; N Ccedilla ; B 41 -151 540 580 ;
|
||||
C -1 ; WX 600 ; N lcommaaccent ; B 95 -250 505 629 ;
|
||||
C -1 ; WX 600 ; N tcaron ; B 87 -15 530 717 ;
|
||||
C -1 ; WX 600 ; N eogonek ; B 66 -172 548 441 ;
|
||||
C -1 ; WX 600 ; N Uogonek ; B 17 -172 583 562 ;
|
||||
C -1 ; WX 600 ; N Aacute ; B 3 0 597 805 ;
|
||||
C -1 ; WX 600 ; N Adieresis ; B 3 0 597 753 ;
|
||||
C -1 ; WX 600 ; N egrave ; B 66 -15 548 672 ;
|
||||
C -1 ; WX 600 ; N zacute ; B 99 0 502 672 ;
|
||||
C -1 ; WX 600 ; N iogonek ; B 95 -172 505 657 ;
|
||||
C -1 ; WX 600 ; N Oacute ; B 43 -18 557 805 ;
|
||||
C -1 ; WX 600 ; N oacute ; B 62 -15 538 672 ;
|
||||
C -1 ; WX 600 ; N amacron ; B 53 -15 559 565 ;
|
||||
C -1 ; WX 600 ; N sacute ; B 80 -15 513 672 ;
|
||||
C -1 ; WX 600 ; N idieresis ; B 95 0 505 620 ;
|
||||
C -1 ; WX 600 ; N Ocircumflex ; B 43 -18 557 787 ;
|
||||
C -1 ; WX 600 ; N Ugrave ; B 17 -18 583 805 ;
|
||||
C -1 ; WX 600 ; N Delta ; B 6 0 598 688 ;
|
||||
C -1 ; WX 600 ; N thorn ; B -6 -157 555 629 ;
|
||||
C -1 ; WX 600 ; N twosuperior ; B 177 249 424 622 ;
|
||||
C -1 ; WX 600 ; N Odieresis ; B 43 -18 557 753 ;
|
||||
C -1 ; WX 600 ; N mu ; B 21 -157 562 426 ;
|
||||
C -1 ; WX 600 ; N igrave ; B 95 0 505 672 ;
|
||||
C -1 ; WX 600 ; N ohungarumlaut ; B 62 -15 580 672 ;
|
||||
C -1 ; WX 600 ; N Eogonek ; B 53 -172 561 562 ;
|
||||
C -1 ; WX 600 ; N dcroat ; B 45 -15 591 629 ;
|
||||
C -1 ; WX 600 ; N threequarters ; B 8 -56 593 666 ;
|
||||
C -1 ; WX 600 ; N Scedilla ; B 72 -151 529 580 ;
|
||||
C -1 ; WX 600 ; N lcaron ; B 95 0 533 629 ;
|
||||
C -1 ; WX 600 ; N Kcommaaccent ; B 38 -250 582 562 ;
|
||||
C -1 ; WX 600 ; N Lacute ; B 47 0 554 805 ;
|
||||
C -1 ; WX 600 ; N trademark ; B -23 263 623 562 ;
|
||||
C -1 ; WX 600 ; N edotaccent ; B 66 -15 548 620 ;
|
||||
C -1 ; WX 600 ; N Igrave ; B 96 0 504 805 ;
|
||||
C -1 ; WX 600 ; N Imacron ; B 96 0 504 698 ;
|
||||
C -1 ; WX 600 ; N Lcaron ; B 47 0 554 562 ;
|
||||
C -1 ; WX 600 ; N onehalf ; B 0 -57 611 665 ;
|
||||
C -1 ; WX 600 ; N lessequal ; B 98 0 502 710 ;
|
||||
C -1 ; WX 600 ; N ocircumflex ; B 62 -15 538 654 ;
|
||||
C -1 ; WX 600 ; N ntilde ; B 26 0 575 606 ;
|
||||
C -1 ; WX 600 ; N Uhungarumlaut ; B 17 -18 590 805 ;
|
||||
C -1 ; WX 600 ; N Eacute ; B 53 0 550 805 ;
|
||||
C -1 ; WX 600 ; N emacron ; B 66 -15 548 565 ;
|
||||
C -1 ; WX 600 ; N gbreve ; B 45 -157 566 609 ;
|
||||
C -1 ; WX 600 ; N onequarter ; B 0 -57 600 665 ;
|
||||
C -1 ; WX 600 ; N Scaron ; B 72 -20 529 802 ;
|
||||
C -1 ; WX 600 ; N Scommaaccent ; B 72 -250 529 580 ;
|
||||
C -1 ; WX 600 ; N Ohungarumlaut ; B 43 -18 580 805 ;
|
||||
C -1 ; WX 600 ; N degree ; B 123 269 477 622 ;
|
||||
C -1 ; WX 600 ; N ograve ; B 62 -15 538 672 ;
|
||||
C -1 ; WX 600 ; N Ccaron ; B 41 -18 540 802 ;
|
||||
C -1 ; WX 600 ; N ugrave ; B 21 -15 562 672 ;
|
||||
C -1 ; WX 600 ; N radical ; B 3 -15 597 792 ;
|
||||
C -1 ; WX 600 ; N Dcaron ; B 43 0 574 802 ;
|
||||
C -1 ; WX 600 ; N rcommaaccent ; B 60 -250 559 441 ;
|
||||
C -1 ; WX 600 ; N Ntilde ; B 7 -13 593 729 ;
|
||||
C -1 ; WX 600 ; N otilde ; B 62 -15 538 606 ;
|
||||
C -1 ; WX 600 ; N Rcommaaccent ; B 38 -250 588 562 ;
|
||||
C -1 ; WX 600 ; N Lcommaaccent ; B 47 -250 554 562 ;
|
||||
C -1 ; WX 600 ; N Atilde ; B 3 0 597 729 ;
|
||||
C -1 ; WX 600 ; N Aogonek ; B 3 -172 608 562 ;
|
||||
C -1 ; WX 600 ; N Aring ; B 3 0 597 750 ;
|
||||
C -1 ; WX 600 ; N Otilde ; B 43 -18 557 729 ;
|
||||
C -1 ; WX 600 ; N zdotaccent ; B 99 0 502 620 ;
|
||||
C -1 ; WX 600 ; N Ecaron ; B 53 0 550 802 ;
|
||||
C -1 ; WX 600 ; N Iogonek ; B 96 -172 504 562 ;
|
||||
C -1 ; WX 600 ; N kcommaaccent ; B 43 -250 580 629 ;
|
||||
C -1 ; WX 600 ; N minus ; B 80 232 520 283 ;
|
||||
C -1 ; WX 600 ; N Icircumflex ; B 96 0 504 787 ;
|
||||
C -1 ; WX 600 ; N ncaron ; B 26 0 575 669 ;
|
||||
C -1 ; WX 600 ; N tcommaaccent ; B 87 -250 530 561 ;
|
||||
C -1 ; WX 600 ; N logicalnot ; B 87 108 513 369 ;
|
||||
C -1 ; WX 600 ; N odieresis ; B 62 -15 538 620 ;
|
||||
C -1 ; WX 600 ; N udieresis ; B 21 -15 562 620 ;
|
||||
C -1 ; WX 600 ; N notequal ; B 15 -16 540 529 ;
|
||||
C -1 ; WX 600 ; N gcommaaccent ; B 45 -157 566 708 ;
|
||||
C -1 ; WX 600 ; N eth ; B 62 -15 538 629 ;
|
||||
C -1 ; WX 600 ; N zcaron ; B 99 0 502 669 ;
|
||||
C -1 ; WX 600 ; N ncommaaccent ; B 26 -250 575 441 ;
|
||||
C -1 ; WX 600 ; N onesuperior ; B 172 249 428 622 ;
|
||||
C -1 ; WX 600 ; N imacron ; B 95 0 505 565 ;
|
||||
C -1 ; WX 600 ; N Euro ; B 0 0 0 0 ;
|
||||
EndCharMetrics
|
||||
EndFontMetrics
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=iso-8859-1">
|
||||
<meta name="generator" content="Adobe GoLive 4">
|
||||
<title>Core 14 AFM Files - ReadMe</title>
|
||||
</head>
|
||||
|
||||
<body bgcolor="white">
|
||||
<font color="white">or</font>
|
||||
<table border="0" cellpadding="0" cellspacing="2">
|
||||
<tr>
|
||||
<td width="40"></td>
|
||||
<td width="300">This file and the 14 PostScript(R) AFM files it accompanies may be used, copied, and distributed for any purpose and without charge, with or without modification, provided that all copyright notices are retained; that the AFM files are not distributed without this file; that all modifications to this file or any of the AFM files are prominently noted in the modified file(s); and that this paragraph is not modified. Adobe Systems has no responsibility or obligation to support the use of the AFM files. <font color="white">Col</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,213 @@
|
||||
StartFontMetrics 4.1
|
||||
Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All rights reserved.
|
||||
Comment Creation Date: Thu May 1 15:12:25 1997
|
||||
Comment UniqueID 43064
|
||||
Comment VMusage 30820 39997
|
||||
FontName Symbol
|
||||
FullName Symbol
|
||||
FamilyName Symbol
|
||||
Weight Medium
|
||||
ItalicAngle 0
|
||||
IsFixedPitch false
|
||||
CharacterSet Special
|
||||
FontBBox -180 -293 1090 1010
|
||||
UnderlinePosition -100
|
||||
UnderlineThickness 50
|
||||
Version 001.008
|
||||
Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All rights reserved.
|
||||
EncodingScheme FontSpecific
|
||||
StdHW 92
|
||||
StdVW 85
|
||||
StartCharMetrics 190
|
||||
C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
|
||||
C 33 ; WX 333 ; N exclam ; B 128 -17 240 672 ;
|
||||
C 34 ; WX 713 ; N universal ; B 31 0 681 705 ;
|
||||
C 35 ; WX 500 ; N numbersign ; B 20 -16 481 673 ;
|
||||
C 36 ; WX 549 ; N existential ; B 25 0 478 707 ;
|
||||
C 37 ; WX 833 ; N percent ; B 63 -36 771 655 ;
|
||||
C 38 ; WX 778 ; N ampersand ; B 41 -18 750 661 ;
|
||||
C 39 ; WX 439 ; N suchthat ; B 48 -17 414 500 ;
|
||||
C 40 ; WX 333 ; N parenleft ; B 53 -191 300 673 ;
|
||||
C 41 ; WX 333 ; N parenright ; B 30 -191 277 673 ;
|
||||
C 42 ; WX 500 ; N asteriskmath ; B 65 134 427 551 ;
|
||||
C 43 ; WX 549 ; N plus ; B 10 0 539 533 ;
|
||||
C 44 ; WX 250 ; N comma ; B 56 -152 194 104 ;
|
||||
C 45 ; WX 549 ; N minus ; B 11 233 535 288 ;
|
||||
C 46 ; WX 250 ; N period ; B 69 -17 181 95 ;
|
||||
C 47 ; WX 278 ; N slash ; B 0 -18 254 646 ;
|
||||
C 48 ; WX 500 ; N zero ; B 24 -14 476 685 ;
|
||||
C 49 ; WX 500 ; N one ; B 117 0 390 673 ;
|
||||
C 50 ; WX 500 ; N two ; B 25 0 475 685 ;
|
||||
C 51 ; WX 500 ; N three ; B 43 -14 435 685 ;
|
||||
C 52 ; WX 500 ; N four ; B 15 0 469 685 ;
|
||||
C 53 ; WX 500 ; N five ; B 32 -14 445 690 ;
|
||||
C 54 ; WX 500 ; N six ; B 34 -14 468 685 ;
|
||||
C 55 ; WX 500 ; N seven ; B 24 -16 448 673 ;
|
||||
C 56 ; WX 500 ; N eight ; B 56 -14 445 685 ;
|
||||
C 57 ; WX 500 ; N nine ; B 30 -18 459 685 ;
|
||||
C 58 ; WX 278 ; N colon ; B 81 -17 193 460 ;
|
||||
C 59 ; WX 278 ; N semicolon ; B 83 -152 221 460 ;
|
||||
C 60 ; WX 549 ; N less ; B 26 0 523 522 ;
|
||||
C 61 ; WX 549 ; N equal ; B 11 141 537 390 ;
|
||||
C 62 ; WX 549 ; N greater ; B 26 0 523 522 ;
|
||||
C 63 ; WX 444 ; N question ; B 70 -17 412 686 ;
|
||||
C 64 ; WX 549 ; N congruent ; B 11 0 537 475 ;
|
||||
C 65 ; WX 722 ; N Alpha ; B 4 0 684 673 ;
|
||||
C 66 ; WX 667 ; N Beta ; B 29 0 592 673 ;
|
||||
C 67 ; WX 722 ; N Chi ; B -9 0 704 673 ;
|
||||
C 68 ; WX 612 ; N Delta ; B 6 0 608 688 ;
|
||||
C 69 ; WX 611 ; N Epsilon ; B 32 0 617 673 ;
|
||||
C 70 ; WX 763 ; N Phi ; B 26 0 741 673 ;
|
||||
C 71 ; WX 603 ; N Gamma ; B 24 0 609 673 ;
|
||||
C 72 ; WX 722 ; N Eta ; B 39 0 729 673 ;
|
||||
C 73 ; WX 333 ; N Iota ; B 32 0 316 673 ;
|
||||
C 74 ; WX 631 ; N theta1 ; B 18 -18 623 689 ;
|
||||
C 75 ; WX 722 ; N Kappa ; B 35 0 722 673 ;
|
||||
C 76 ; WX 686 ; N Lambda ; B 6 0 680 688 ;
|
||||
C 77 ; WX 889 ; N Mu ; B 28 0 887 673 ;
|
||||
C 78 ; WX 722 ; N Nu ; B 29 -8 720 673 ;
|
||||
C 79 ; WX 722 ; N Omicron ; B 41 -17 715 685 ;
|
||||
C 80 ; WX 768 ; N Pi ; B 25 0 745 673 ;
|
||||
C 81 ; WX 741 ; N Theta ; B 41 -17 715 685 ;
|
||||
C 82 ; WX 556 ; N Rho ; B 28 0 563 673 ;
|
||||
C 83 ; WX 592 ; N Sigma ; B 5 0 589 673 ;
|
||||
C 84 ; WX 611 ; N Tau ; B 33 0 607 673 ;
|
||||
C 85 ; WX 690 ; N Upsilon ; B -8 0 694 673 ;
|
||||
C 86 ; WX 439 ; N sigma1 ; B 40 -233 436 500 ;
|
||||
C 87 ; WX 768 ; N Omega ; B 34 0 736 688 ;
|
||||
C 88 ; WX 645 ; N Xi ; B 40 0 599 673 ;
|
||||
C 89 ; WX 795 ; N Psi ; B 15 0 781 684 ;
|
||||
C 90 ; WX 611 ; N Zeta ; B 44 0 636 673 ;
|
||||
C 91 ; WX 333 ; N bracketleft ; B 86 -155 299 674 ;
|
||||
C 92 ; WX 863 ; N therefore ; B 163 0 701 487 ;
|
||||
C 93 ; WX 333 ; N bracketright ; B 33 -155 246 674 ;
|
||||
C 94 ; WX 658 ; N perpendicular ; B 15 0 652 674 ;
|
||||
C 95 ; WX 500 ; N underscore ; B -2 -125 502 -75 ;
|
||||
C 96 ; WX 500 ; N radicalex ; B 480 881 1090 917 ;
|
||||
C 97 ; WX 631 ; N alpha ; B 41 -18 622 500 ;
|
||||
C 98 ; WX 549 ; N beta ; B 61 -223 515 741 ;
|
||||
C 99 ; WX 549 ; N chi ; B 12 -231 522 499 ;
|
||||
C 100 ; WX 494 ; N delta ; B 40 -19 481 740 ;
|
||||
C 101 ; WX 439 ; N epsilon ; B 22 -19 427 502 ;
|
||||
C 102 ; WX 521 ; N phi ; B 28 -224 492 673 ;
|
||||
C 103 ; WX 411 ; N gamma ; B 5 -225 484 499 ;
|
||||
C 104 ; WX 603 ; N eta ; B 0 -202 527 514 ;
|
||||
C 105 ; WX 329 ; N iota ; B 0 -17 301 503 ;
|
||||
C 106 ; WX 603 ; N phi1 ; B 36 -224 587 499 ;
|
||||
C 107 ; WX 549 ; N kappa ; B 33 0 558 501 ;
|
||||
C 108 ; WX 549 ; N lambda ; B 24 -17 548 739 ;
|
||||
C 109 ; WX 576 ; N mu ; B 33 -223 567 500 ;
|
||||
C 110 ; WX 521 ; N nu ; B -9 -16 475 507 ;
|
||||
C 111 ; WX 549 ; N omicron ; B 35 -19 501 499 ;
|
||||
C 112 ; WX 549 ; N pi ; B 10 -19 530 487 ;
|
||||
C 113 ; WX 521 ; N theta ; B 43 -17 485 690 ;
|
||||
C 114 ; WX 549 ; N rho ; B 50 -230 490 499 ;
|
||||
C 115 ; WX 603 ; N sigma ; B 30 -21 588 500 ;
|
||||
C 116 ; WX 439 ; N tau ; B 10 -19 418 500 ;
|
||||
C 117 ; WX 576 ; N upsilon ; B 7 -18 535 507 ;
|
||||
C 118 ; WX 713 ; N omega1 ; B 12 -18 671 583 ;
|
||||
C 119 ; WX 686 ; N omega ; B 42 -17 684 500 ;
|
||||
C 120 ; WX 493 ; N xi ; B 27 -224 469 766 ;
|
||||
C 121 ; WX 686 ; N psi ; B 12 -228 701 500 ;
|
||||
C 122 ; WX 494 ; N zeta ; B 60 -225 467 756 ;
|
||||
C 123 ; WX 480 ; N braceleft ; B 58 -183 397 673 ;
|
||||
C 124 ; WX 200 ; N bar ; B 65 -293 135 707 ;
|
||||
C 125 ; WX 480 ; N braceright ; B 79 -183 418 673 ;
|
||||
C 126 ; WX 549 ; N similar ; B 17 203 529 307 ;
|
||||
C 160 ; WX 750 ; N Euro ; B 20 -12 714 685 ;
|
||||
C 161 ; WX 620 ; N Upsilon1 ; B -2 0 610 685 ;
|
||||
C 162 ; WX 247 ; N minute ; B 27 459 228 735 ;
|
||||
C 163 ; WX 549 ; N lessequal ; B 29 0 526 639 ;
|
||||
C 164 ; WX 167 ; N fraction ; B -180 -12 340 677 ;
|
||||
C 165 ; WX 713 ; N infinity ; B 26 124 688 404 ;
|
||||
C 166 ; WX 500 ; N florin ; B 2 -193 494 686 ;
|
||||
C 167 ; WX 753 ; N club ; B 86 -26 660 533 ;
|
||||
C 168 ; WX 753 ; N diamond ; B 142 -36 600 550 ;
|
||||
C 169 ; WX 753 ; N heart ; B 117 -33 631 532 ;
|
||||
C 170 ; WX 753 ; N spade ; B 113 -36 629 548 ;
|
||||
C 171 ; WX 1042 ; N arrowboth ; B 24 -15 1024 511 ;
|
||||
C 172 ; WX 987 ; N arrowleft ; B 32 -15 942 511 ;
|
||||
C 173 ; WX 603 ; N arrowup ; B 45 0 571 910 ;
|
||||
C 174 ; WX 987 ; N arrowright ; B 49 -15 959 511 ;
|
||||
C 175 ; WX 603 ; N arrowdown ; B 45 -22 571 888 ;
|
||||
C 176 ; WX 400 ; N degree ; B 50 385 350 685 ;
|
||||
C 177 ; WX 549 ; N plusminus ; B 10 0 539 645 ;
|
||||
C 178 ; WX 411 ; N second ; B 20 459 413 737 ;
|
||||
C 179 ; WX 549 ; N greaterequal ; B 29 0 526 639 ;
|
||||
C 180 ; WX 549 ; N multiply ; B 17 8 533 524 ;
|
||||
C 181 ; WX 713 ; N proportional ; B 27 123 639 404 ;
|
||||
C 182 ; WX 494 ; N partialdiff ; B 26 -20 462 746 ;
|
||||
C 183 ; WX 460 ; N bullet ; B 50 113 410 473 ;
|
||||
C 184 ; WX 549 ; N divide ; B 10 71 536 456 ;
|
||||
C 185 ; WX 549 ; N notequal ; B 15 -25 540 549 ;
|
||||
C 186 ; WX 549 ; N equivalence ; B 14 82 538 443 ;
|
||||
C 187 ; WX 549 ; N approxequal ; B 14 135 527 394 ;
|
||||
C 188 ; WX 1000 ; N ellipsis ; B 111 -17 889 95 ;
|
||||
C 189 ; WX 603 ; N arrowvertex ; B 280 -120 336 1010 ;
|
||||
C 190 ; WX 1000 ; N arrowhorizex ; B -60 220 1050 276 ;
|
||||
C 191 ; WX 658 ; N carriagereturn ; B 15 -16 602 629 ;
|
||||
C 192 ; WX 823 ; N aleph ; B 175 -18 661 658 ;
|
||||
C 193 ; WX 686 ; N Ifraktur ; B 10 -53 578 740 ;
|
||||
C 194 ; WX 795 ; N Rfraktur ; B 26 -15 759 734 ;
|
||||
C 195 ; WX 987 ; N weierstrass ; B 159 -211 870 573 ;
|
||||
C 196 ; WX 768 ; N circlemultiply ; B 43 -17 733 673 ;
|
||||
C 197 ; WX 768 ; N circleplus ; B 43 -15 733 675 ;
|
||||
C 198 ; WX 823 ; N emptyset ; B 39 -24 781 719 ;
|
||||
C 199 ; WX 768 ; N intersection ; B 40 0 732 509 ;
|
||||
C 200 ; WX 768 ; N union ; B 40 -17 732 492 ;
|
||||
C 201 ; WX 713 ; N propersuperset ; B 20 0 673 470 ;
|
||||
C 202 ; WX 713 ; N reflexsuperset ; B 20 -125 673 470 ;
|
||||
C 203 ; WX 713 ; N notsubset ; B 36 -70 690 540 ;
|
||||
C 204 ; WX 713 ; N propersubset ; B 37 0 690 470 ;
|
||||
C 205 ; WX 713 ; N reflexsubset ; B 37 -125 690 470 ;
|
||||
C 206 ; WX 713 ; N element ; B 45 0 505 468 ;
|
||||
C 207 ; WX 713 ; N notelement ; B 45 -58 505 555 ;
|
||||
C 208 ; WX 768 ; N angle ; B 26 0 738 673 ;
|
||||
C 209 ; WX 713 ; N gradient ; B 36 -19 681 718 ;
|
||||
C 210 ; WX 790 ; N registerserif ; B 50 -17 740 673 ;
|
||||
C 211 ; WX 790 ; N copyrightserif ; B 51 -15 741 675 ;
|
||||
C 212 ; WX 890 ; N trademarkserif ; B 18 293 855 673 ;
|
||||
C 213 ; WX 823 ; N product ; B 25 -101 803 751 ;
|
||||
C 214 ; WX 549 ; N radical ; B 10 -38 515 917 ;
|
||||
C 215 ; WX 250 ; N dotmath ; B 69 210 169 310 ;
|
||||
C 216 ; WX 713 ; N logicalnot ; B 15 0 680 288 ;
|
||||
C 217 ; WX 603 ; N logicaland ; B 23 0 583 454 ;
|
||||
C 218 ; WX 603 ; N logicalor ; B 30 0 578 477 ;
|
||||
C 219 ; WX 1042 ; N arrowdblboth ; B 27 -20 1023 510 ;
|
||||
C 220 ; WX 987 ; N arrowdblleft ; B 30 -15 939 513 ;
|
||||
C 221 ; WX 603 ; N arrowdblup ; B 39 2 567 911 ;
|
||||
C 222 ; WX 987 ; N arrowdblright ; B 45 -20 954 508 ;
|
||||
C 223 ; WX 603 ; N arrowdbldown ; B 44 -19 572 890 ;
|
||||
C 224 ; WX 494 ; N lozenge ; B 18 0 466 745 ;
|
||||
C 225 ; WX 329 ; N angleleft ; B 25 -198 306 746 ;
|
||||
C 226 ; WX 790 ; N registersans ; B 50 -20 740 670 ;
|
||||
C 227 ; WX 790 ; N copyrightsans ; B 49 -15 739 675 ;
|
||||
C 228 ; WX 786 ; N trademarksans ; B 5 293 725 673 ;
|
||||
C 229 ; WX 713 ; N summation ; B 14 -108 695 752 ;
|
||||
C 230 ; WX 384 ; N parenlefttp ; B 24 -293 436 926 ;
|
||||
C 231 ; WX 384 ; N parenleftex ; B 24 -85 108 925 ;
|
||||
C 232 ; WX 384 ; N parenleftbt ; B 24 -293 436 926 ;
|
||||
C 233 ; WX 384 ; N bracketlefttp ; B 0 -80 349 926 ;
|
||||
C 234 ; WX 384 ; N bracketleftex ; B 0 -79 77 925 ;
|
||||
C 235 ; WX 384 ; N bracketleftbt ; B 0 -80 349 926 ;
|
||||
C 236 ; WX 494 ; N bracelefttp ; B 209 -85 445 925 ;
|
||||
C 237 ; WX 494 ; N braceleftmid ; B 20 -85 284 935 ;
|
||||
C 238 ; WX 494 ; N braceleftbt ; B 209 -75 445 935 ;
|
||||
C 239 ; WX 494 ; N braceex ; B 209 -85 284 935 ;
|
||||
C 241 ; WX 329 ; N angleright ; B 21 -198 302 746 ;
|
||||
C 242 ; WX 274 ; N integral ; B 2 -107 291 916 ;
|
||||
C 243 ; WX 686 ; N integraltp ; B 308 -88 675 920 ;
|
||||
C 244 ; WX 686 ; N integralex ; B 308 -88 378 975 ;
|
||||
C 245 ; WX 686 ; N integralbt ; B 11 -87 378 921 ;
|
||||
C 246 ; WX 384 ; N parenrighttp ; B 54 -293 466 926 ;
|
||||
C 247 ; WX 384 ; N parenrightex ; B 382 -85 466 925 ;
|
||||
C 248 ; WX 384 ; N parenrightbt ; B 54 -293 466 926 ;
|
||||
C 249 ; WX 384 ; N bracketrighttp ; B 22 -80 371 926 ;
|
||||
C 250 ; WX 384 ; N bracketrightex ; B 294 -79 371 925 ;
|
||||
C 251 ; WX 384 ; N bracketrightbt ; B 22 -80 371 926 ;
|
||||
C 252 ; WX 494 ; N bracerighttp ; B 48 -85 284 925 ;
|
||||
C 253 ; WX 494 ; N bracerightmid ; B 209 -85 473 935 ;
|
||||
C 254 ; WX 494 ; N bracerightbt ; B 48 -75 284 935 ;
|
||||
C -1 ; WX 790 ; N apple ; B 56 -3 733 808 ;
|
||||
EndCharMetrics
|
||||
EndFontMetrics
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,225 @@
|
||||
StartFontMetrics 4.1
|
||||
Comment Copyright (c) 1985, 1987, 1988, 1989, 1997 Adobe Systems Incorporated. All Rights Reserved.
|
||||
Comment Creation Date: Thu May 1 15:14:13 1997
|
||||
Comment UniqueID 43082
|
||||
Comment VMusage 45775 55535
|
||||
FontName ZapfDingbats
|
||||
FullName ITC Zapf Dingbats
|
||||
FamilyName ZapfDingbats
|
||||
Weight Medium
|
||||
ItalicAngle 0
|
||||
IsFixedPitch false
|
||||
CharacterSet Special
|
||||
FontBBox -1 -143 981 820
|
||||
UnderlinePosition -100
|
||||
UnderlineThickness 50
|
||||
Version 002.000
|
||||
Notice Copyright (c) 1985, 1987, 1988, 1989, 1997 Adobe Systems Incorporated. All Rights Reserved.ITC Zapf Dingbats is a registered trademark of International Typeface Corporation.
|
||||
EncodingScheme FontSpecific
|
||||
StdHW 28
|
||||
StdVW 90
|
||||
StartCharMetrics 202
|
||||
C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
|
||||
C 33 ; WX 974 ; N a1 ; B 35 72 939 621 ;
|
||||
C 34 ; WX 961 ; N a2 ; B 35 81 927 611 ;
|
||||
C 35 ; WX 974 ; N a202 ; B 35 72 939 621 ;
|
||||
C 36 ; WX 980 ; N a3 ; B 35 0 945 692 ;
|
||||
C 37 ; WX 719 ; N a4 ; B 34 139 685 566 ;
|
||||
C 38 ; WX 789 ; N a5 ; B 35 -14 755 705 ;
|
||||
C 39 ; WX 790 ; N a119 ; B 35 -14 755 705 ;
|
||||
C 40 ; WX 791 ; N a118 ; B 35 -13 761 705 ;
|
||||
C 41 ; WX 690 ; N a117 ; B 34 138 655 553 ;
|
||||
C 42 ; WX 960 ; N a11 ; B 35 123 925 568 ;
|
||||
C 43 ; WX 939 ; N a12 ; B 35 134 904 559 ;
|
||||
C 44 ; WX 549 ; N a13 ; B 29 -11 516 705 ;
|
||||
C 45 ; WX 855 ; N a14 ; B 34 59 820 632 ;
|
||||
C 46 ; WX 911 ; N a15 ; B 35 50 876 642 ;
|
||||
C 47 ; WX 933 ; N a16 ; B 35 139 899 550 ;
|
||||
C 48 ; WX 911 ; N a105 ; B 35 50 876 642 ;
|
||||
C 49 ; WX 945 ; N a17 ; B 35 139 909 553 ;
|
||||
C 50 ; WX 974 ; N a18 ; B 35 104 938 587 ;
|
||||
C 51 ; WX 755 ; N a19 ; B 34 -13 721 705 ;
|
||||
C 52 ; WX 846 ; N a20 ; B 36 -14 811 705 ;
|
||||
C 53 ; WX 762 ; N a21 ; B 35 0 727 692 ;
|
||||
C 54 ; WX 761 ; N a22 ; B 35 0 727 692 ;
|
||||
C 55 ; WX 571 ; N a23 ; B -1 -68 571 661 ;
|
||||
C 56 ; WX 677 ; N a24 ; B 36 -13 642 705 ;
|
||||
C 57 ; WX 763 ; N a25 ; B 35 0 728 692 ;
|
||||
C 58 ; WX 760 ; N a26 ; B 35 0 726 692 ;
|
||||
C 59 ; WX 759 ; N a27 ; B 35 0 725 692 ;
|
||||
C 60 ; WX 754 ; N a28 ; B 35 0 720 692 ;
|
||||
C 61 ; WX 494 ; N a6 ; B 35 0 460 692 ;
|
||||
C 62 ; WX 552 ; N a7 ; B 35 0 517 692 ;
|
||||
C 63 ; WX 537 ; N a8 ; B 35 0 503 692 ;
|
||||
C 64 ; WX 577 ; N a9 ; B 35 96 542 596 ;
|
||||
C 65 ; WX 692 ; N a10 ; B 35 -14 657 705 ;
|
||||
C 66 ; WX 786 ; N a29 ; B 35 -14 751 705 ;
|
||||
C 67 ; WX 788 ; N a30 ; B 35 -14 752 705 ;
|
||||
C 68 ; WX 788 ; N a31 ; B 35 -14 753 705 ;
|
||||
C 69 ; WX 790 ; N a32 ; B 35 -14 756 705 ;
|
||||
C 70 ; WX 793 ; N a33 ; B 35 -13 759 705 ;
|
||||
C 71 ; WX 794 ; N a34 ; B 35 -13 759 705 ;
|
||||
C 72 ; WX 816 ; N a35 ; B 35 -14 782 705 ;
|
||||
C 73 ; WX 823 ; N a36 ; B 35 -14 787 705 ;
|
||||
C 74 ; WX 789 ; N a37 ; B 35 -14 754 705 ;
|
||||
C 75 ; WX 841 ; N a38 ; B 35 -14 807 705 ;
|
||||
C 76 ; WX 823 ; N a39 ; B 35 -14 789 705 ;
|
||||
C 77 ; WX 833 ; N a40 ; B 35 -14 798 705 ;
|
||||
C 78 ; WX 816 ; N a41 ; B 35 -13 782 705 ;
|
||||
C 79 ; WX 831 ; N a42 ; B 35 -14 796 705 ;
|
||||
C 80 ; WX 923 ; N a43 ; B 35 -14 888 705 ;
|
||||
C 81 ; WX 744 ; N a44 ; B 35 0 710 692 ;
|
||||
C 82 ; WX 723 ; N a45 ; B 35 0 688 692 ;
|
||||
C 83 ; WX 749 ; N a46 ; B 35 0 714 692 ;
|
||||
C 84 ; WX 790 ; N a47 ; B 34 -14 756 705 ;
|
||||
C 85 ; WX 792 ; N a48 ; B 35 -14 758 705 ;
|
||||
C 86 ; WX 695 ; N a49 ; B 35 -14 661 706 ;
|
||||
C 87 ; WX 776 ; N a50 ; B 35 -6 741 699 ;
|
||||
C 88 ; WX 768 ; N a51 ; B 35 -7 734 699 ;
|
||||
C 89 ; WX 792 ; N a52 ; B 35 -14 757 705 ;
|
||||
C 90 ; WX 759 ; N a53 ; B 35 0 725 692 ;
|
||||
C 91 ; WX 707 ; N a54 ; B 35 -13 672 704 ;
|
||||
C 92 ; WX 708 ; N a55 ; B 35 -14 672 705 ;
|
||||
C 93 ; WX 682 ; N a56 ; B 35 -14 647 705 ;
|
||||
C 94 ; WX 701 ; N a57 ; B 35 -14 666 705 ;
|
||||
C 95 ; WX 826 ; N a58 ; B 35 -14 791 705 ;
|
||||
C 96 ; WX 815 ; N a59 ; B 35 -14 780 705 ;
|
||||
C 97 ; WX 789 ; N a60 ; B 35 -14 754 705 ;
|
||||
C 98 ; WX 789 ; N a61 ; B 35 -14 754 705 ;
|
||||
C 99 ; WX 707 ; N a62 ; B 34 -14 673 705 ;
|
||||
C 100 ; WX 687 ; N a63 ; B 36 0 651 692 ;
|
||||
C 101 ; WX 696 ; N a64 ; B 35 0 661 691 ;
|
||||
C 102 ; WX 689 ; N a65 ; B 35 0 655 692 ;
|
||||
C 103 ; WX 786 ; N a66 ; B 34 -14 751 705 ;
|
||||
C 104 ; WX 787 ; N a67 ; B 35 -14 752 705 ;
|
||||
C 105 ; WX 713 ; N a68 ; B 35 -14 678 705 ;
|
||||
C 106 ; WX 791 ; N a69 ; B 35 -14 756 705 ;
|
||||
C 107 ; WX 785 ; N a70 ; B 36 -14 751 705 ;
|
||||
C 108 ; WX 791 ; N a71 ; B 35 -14 757 705 ;
|
||||
C 109 ; WX 873 ; N a72 ; B 35 -14 838 705 ;
|
||||
C 110 ; WX 761 ; N a73 ; B 35 0 726 692 ;
|
||||
C 111 ; WX 762 ; N a74 ; B 35 0 727 692 ;
|
||||
C 112 ; WX 762 ; N a203 ; B 35 0 727 692 ;
|
||||
C 113 ; WX 759 ; N a75 ; B 35 0 725 692 ;
|
||||
C 114 ; WX 759 ; N a204 ; B 35 0 725 692 ;
|
||||
C 115 ; WX 892 ; N a76 ; B 35 0 858 705 ;
|
||||
C 116 ; WX 892 ; N a77 ; B 35 -14 858 692 ;
|
||||
C 117 ; WX 788 ; N a78 ; B 35 -14 754 705 ;
|
||||
C 118 ; WX 784 ; N a79 ; B 35 -14 749 705 ;
|
||||
C 119 ; WX 438 ; N a81 ; B 35 -14 403 705 ;
|
||||
C 120 ; WX 138 ; N a82 ; B 35 0 104 692 ;
|
||||
C 121 ; WX 277 ; N a83 ; B 35 0 242 692 ;
|
||||
C 122 ; WX 415 ; N a84 ; B 35 0 380 692 ;
|
||||
C 123 ; WX 392 ; N a97 ; B 35 263 357 705 ;
|
||||
C 124 ; WX 392 ; N a98 ; B 34 263 357 705 ;
|
||||
C 125 ; WX 668 ; N a99 ; B 35 263 633 705 ;
|
||||
C 126 ; WX 668 ; N a100 ; B 36 263 634 705 ;
|
||||
C 128 ; WX 390 ; N a89 ; B 35 -14 356 705 ;
|
||||
C 129 ; WX 390 ; N a90 ; B 35 -14 355 705 ;
|
||||
C 130 ; WX 317 ; N a93 ; B 35 0 283 692 ;
|
||||
C 131 ; WX 317 ; N a94 ; B 35 0 283 692 ;
|
||||
C 132 ; WX 276 ; N a91 ; B 35 0 242 692 ;
|
||||
C 133 ; WX 276 ; N a92 ; B 35 0 242 692 ;
|
||||
C 134 ; WX 509 ; N a205 ; B 35 0 475 692 ;
|
||||
C 135 ; WX 509 ; N a85 ; B 35 0 475 692 ;
|
||||
C 136 ; WX 410 ; N a206 ; B 35 0 375 692 ;
|
||||
C 137 ; WX 410 ; N a86 ; B 35 0 375 692 ;
|
||||
C 138 ; WX 234 ; N a87 ; B 35 -14 199 705 ;
|
||||
C 139 ; WX 234 ; N a88 ; B 35 -14 199 705 ;
|
||||
C 140 ; WX 334 ; N a95 ; B 35 0 299 692 ;
|
||||
C 141 ; WX 334 ; N a96 ; B 35 0 299 692 ;
|
||||
C 161 ; WX 732 ; N a101 ; B 35 -143 697 806 ;
|
||||
C 162 ; WX 544 ; N a102 ; B 56 -14 488 706 ;
|
||||
C 163 ; WX 544 ; N a103 ; B 34 -14 508 705 ;
|
||||
C 164 ; WX 910 ; N a104 ; B 35 40 875 651 ;
|
||||
C 165 ; WX 667 ; N a106 ; B 35 -14 633 705 ;
|
||||
C 166 ; WX 760 ; N a107 ; B 35 -14 726 705 ;
|
||||
C 167 ; WX 760 ; N a108 ; B 0 121 758 569 ;
|
||||
C 168 ; WX 776 ; N a112 ; B 35 0 741 705 ;
|
||||
C 169 ; WX 595 ; N a111 ; B 34 -14 560 705 ;
|
||||
C 170 ; WX 694 ; N a110 ; B 35 -14 659 705 ;
|
||||
C 171 ; WX 626 ; N a109 ; B 34 0 591 705 ;
|
||||
C 172 ; WX 788 ; N a120 ; B 35 -14 754 705 ;
|
||||
C 173 ; WX 788 ; N a121 ; B 35 -14 754 705 ;
|
||||
C 174 ; WX 788 ; N a122 ; B 35 -14 754 705 ;
|
||||
C 175 ; WX 788 ; N a123 ; B 35 -14 754 705 ;
|
||||
C 176 ; WX 788 ; N a124 ; B 35 -14 754 705 ;
|
||||
C 177 ; WX 788 ; N a125 ; B 35 -14 754 705 ;
|
||||
C 178 ; WX 788 ; N a126 ; B 35 -14 754 705 ;
|
||||
C 179 ; WX 788 ; N a127 ; B 35 -14 754 705 ;
|
||||
C 180 ; WX 788 ; N a128 ; B 35 -14 754 705 ;
|
||||
C 181 ; WX 788 ; N a129 ; B 35 -14 754 705 ;
|
||||
C 182 ; WX 788 ; N a130 ; B 35 -14 754 705 ;
|
||||
C 183 ; WX 788 ; N a131 ; B 35 -14 754 705 ;
|
||||
C 184 ; WX 788 ; N a132 ; B 35 -14 754 705 ;
|
||||
C 185 ; WX 788 ; N a133 ; B 35 -14 754 705 ;
|
||||
C 186 ; WX 788 ; N a134 ; B 35 -14 754 705 ;
|
||||
C 187 ; WX 788 ; N a135 ; B 35 -14 754 705 ;
|
||||
C 188 ; WX 788 ; N a136 ; B 35 -14 754 705 ;
|
||||
C 189 ; WX 788 ; N a137 ; B 35 -14 754 705 ;
|
||||
C 190 ; WX 788 ; N a138 ; B 35 -14 754 705 ;
|
||||
C 191 ; WX 788 ; N a139 ; B 35 -14 754 705 ;
|
||||
C 192 ; WX 788 ; N a140 ; B 35 -14 754 705 ;
|
||||
C 193 ; WX 788 ; N a141 ; B 35 -14 754 705 ;
|
||||
C 194 ; WX 788 ; N a142 ; B 35 -14 754 705 ;
|
||||
C 195 ; WX 788 ; N a143 ; B 35 -14 754 705 ;
|
||||
C 196 ; WX 788 ; N a144 ; B 35 -14 754 705 ;
|
||||
C 197 ; WX 788 ; N a145 ; B 35 -14 754 705 ;
|
||||
C 198 ; WX 788 ; N a146 ; B 35 -14 754 705 ;
|
||||
C 199 ; WX 788 ; N a147 ; B 35 -14 754 705 ;
|
||||
C 200 ; WX 788 ; N a148 ; B 35 -14 754 705 ;
|
||||
C 201 ; WX 788 ; N a149 ; B 35 -14 754 705 ;
|
||||
C 202 ; WX 788 ; N a150 ; B 35 -14 754 705 ;
|
||||
C 203 ; WX 788 ; N a151 ; B 35 -14 754 705 ;
|
||||
C 204 ; WX 788 ; N a152 ; B 35 -14 754 705 ;
|
||||
C 205 ; WX 788 ; N a153 ; B 35 -14 754 705 ;
|
||||
C 206 ; WX 788 ; N a154 ; B 35 -14 754 705 ;
|
||||
C 207 ; WX 788 ; N a155 ; B 35 -14 754 705 ;
|
||||
C 208 ; WX 788 ; N a156 ; B 35 -14 754 705 ;
|
||||
C 209 ; WX 788 ; N a157 ; B 35 -14 754 705 ;
|
||||
C 210 ; WX 788 ; N a158 ; B 35 -14 754 705 ;
|
||||
C 211 ; WX 788 ; N a159 ; B 35 -14 754 705 ;
|
||||
C 212 ; WX 894 ; N a160 ; B 35 58 860 634 ;
|
||||
C 213 ; WX 838 ; N a161 ; B 35 152 803 540 ;
|
||||
C 214 ; WX 1016 ; N a163 ; B 34 152 981 540 ;
|
||||
C 215 ; WX 458 ; N a164 ; B 35 -127 422 820 ;
|
||||
C 216 ; WX 748 ; N a196 ; B 35 94 698 597 ;
|
||||
C 217 ; WX 924 ; N a165 ; B 35 140 890 552 ;
|
||||
C 218 ; WX 748 ; N a192 ; B 35 94 698 597 ;
|
||||
C 219 ; WX 918 ; N a166 ; B 35 166 884 526 ;
|
||||
C 220 ; WX 927 ; N a167 ; B 35 32 892 660 ;
|
||||
C 221 ; WX 928 ; N a168 ; B 35 129 891 562 ;
|
||||
C 222 ; WX 928 ; N a169 ; B 35 128 893 563 ;
|
||||
C 223 ; WX 834 ; N a170 ; B 35 155 799 537 ;
|
||||
C 224 ; WX 873 ; N a171 ; B 35 93 838 599 ;
|
||||
C 225 ; WX 828 ; N a172 ; B 35 104 791 588 ;
|
||||
C 226 ; WX 924 ; N a173 ; B 35 98 889 594 ;
|
||||
C 227 ; WX 924 ; N a162 ; B 35 98 889 594 ;
|
||||
C 228 ; WX 917 ; N a174 ; B 35 0 882 692 ;
|
||||
C 229 ; WX 930 ; N a175 ; B 35 84 896 608 ;
|
||||
C 230 ; WX 931 ; N a176 ; B 35 84 896 608 ;
|
||||
C 231 ; WX 463 ; N a177 ; B 35 -99 429 791 ;
|
||||
C 232 ; WX 883 ; N a178 ; B 35 71 848 623 ;
|
||||
C 233 ; WX 836 ; N a179 ; B 35 44 802 648 ;
|
||||
C 234 ; WX 836 ; N a193 ; B 35 44 802 648 ;
|
||||
C 235 ; WX 867 ; N a180 ; B 35 101 832 591 ;
|
||||
C 236 ; WX 867 ; N a199 ; B 35 101 832 591 ;
|
||||
C 237 ; WX 696 ; N a181 ; B 35 44 661 648 ;
|
||||
C 238 ; WX 696 ; N a200 ; B 35 44 661 648 ;
|
||||
C 239 ; WX 874 ; N a182 ; B 35 77 840 619 ;
|
||||
C 241 ; WX 874 ; N a201 ; B 35 73 840 615 ;
|
||||
C 242 ; WX 760 ; N a183 ; B 35 0 725 692 ;
|
||||
C 243 ; WX 946 ; N a184 ; B 35 160 911 533 ;
|
||||
C 244 ; WX 771 ; N a197 ; B 34 37 736 655 ;
|
||||
C 245 ; WX 865 ; N a185 ; B 35 207 830 481 ;
|
||||
C 246 ; WX 771 ; N a194 ; B 34 37 736 655 ;
|
||||
C 247 ; WX 888 ; N a198 ; B 34 -19 853 712 ;
|
||||
C 248 ; WX 967 ; N a186 ; B 35 124 932 568 ;
|
||||
C 249 ; WX 888 ; N a195 ; B 34 -19 853 712 ;
|
||||
C 250 ; WX 831 ; N a187 ; B 35 113 796 579 ;
|
||||
C 251 ; WX 873 ; N a188 ; B 36 118 838 578 ;
|
||||
C 252 ; WX 927 ; N a189 ; B 35 150 891 542 ;
|
||||
C 253 ; WX 970 ; N a190 ; B 35 76 931 616 ;
|
||||
C 254 ; WX 918 ; N a191 ; B 34 99 884 593 ;
|
||||
EndCharMetrics
|
||||
EndFontMetrics
|
||||
@@ -0,0 +1,16 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# Filter our text/characters that are positioned outside a rectangle. Usually the page
|
||||
# MediaBox or CropBox, but could be a user specified rectangle too
|
||||
class BoundingRectangleRunsFilter
|
||||
|
||||
def self.runs_within_rect(runs, rect)
|
||||
runs.select { |run| rect.contains?(run.origin) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
# coding: ASCII-8BIT
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2010 James Healy (jimmy@deefa.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# A string tokeniser that recognises PDF grammar. When passed an IO stream or a
|
||||
# string, repeated calls to token() will return the next token from the source.
|
||||
#
|
||||
# This is very low level, and getting the raw tokens is not very useful in itself.
|
||||
#
|
||||
# This will usually be used in conjunction with PDF:Reader::Parser, which converts
|
||||
# the raw tokens into objects we can work with (strings, ints, arrays, etc)
|
||||
#
|
||||
class Buffer
|
||||
TOKEN_WHITESPACE=[0x00, 0x09, 0x0A, 0x0C, 0x0D, 0x20]
|
||||
TOKEN_DELIMITER=[0x25, 0x3C, 0x3E, 0x28, 0x5B, 0x7B, 0x29, 0x5D, 0x7D, 0x2F]
|
||||
|
||||
# some strings for comparissons. Declaring them here avoids creating new
|
||||
# strings that need GC over and over
|
||||
LEFT_PAREN = "("
|
||||
LESS_THAN = "<"
|
||||
STREAM = "stream"
|
||||
ID = "ID"
|
||||
FWD_SLASH = "/"
|
||||
NULL_BYTE = "\x00"
|
||||
CR = "\r"
|
||||
LF = "\n"
|
||||
CRLF = "\r\n"
|
||||
WHITE_SPACE = ["\n", "\r", ' ']
|
||||
|
||||
# Quite a few PDFs have trailing junk.
|
||||
# This can be several k of nuls in some cases
|
||||
# Allow for this here
|
||||
TRAILING_BYTECOUNT = 5000
|
||||
|
||||
# must match whole tokens
|
||||
DIGITS_ONLY = %r{\A\d+\z}
|
||||
|
||||
attr_reader :pos
|
||||
|
||||
# Creates a new buffer.
|
||||
#
|
||||
# Params:
|
||||
#
|
||||
# io - an IO stream (usually a StringIO) with the raw data to tokenise
|
||||
#
|
||||
# options:
|
||||
#
|
||||
# :seek - a byte offset to seek to before starting to tokenise
|
||||
# :content_stream - set to true if buffer will be tokenising a
|
||||
# content stream. Defaults to false
|
||||
#
|
||||
def initialize(io, opts = {})
|
||||
@io = io
|
||||
@tokens = []
|
||||
@in_content_stream = opts[:content_stream]
|
||||
|
||||
@io.seek(opts[:seek]) if opts[:seek]
|
||||
@pos = @io.pos
|
||||
end
|
||||
|
||||
# return true if there are no more tokens left
|
||||
#
|
||||
def empty?
|
||||
prepare_tokens if @tokens.size < 3
|
||||
|
||||
@tokens.empty?
|
||||
end
|
||||
|
||||
# return raw bytes from the underlying IO stream.
|
||||
#
|
||||
# bytes - the number of bytes to read
|
||||
#
|
||||
# options:
|
||||
#
|
||||
# :skip_eol - if true, the IO stream is advanced past a CRLF, CR or LF
|
||||
# that is sitting under the io cursor.
|
||||
# Note:
|
||||
# Skipping a bare CR is not spec-compliant.
|
||||
# This is because the data may start with LF.
|
||||
# However we check for CRLF first, so the ambiguity is avoided.
|
||||
def read(bytes, opts = {})
|
||||
reset_pos
|
||||
|
||||
if opts[:skip_eol]
|
||||
@io.seek(-1, IO::SEEK_CUR)
|
||||
str = @io.read(2)
|
||||
if str.nil?
|
||||
return nil
|
||||
elsif str == CRLF # This MUST be done before checking for CR alone
|
||||
# do nothing
|
||||
elsif str[0, 1] == LF || str[0, 1] == CR # LF or CR alone
|
||||
@io.seek(-1, IO::SEEK_CUR)
|
||||
else
|
||||
@io.seek(-2, IO::SEEK_CUR)
|
||||
end
|
||||
end
|
||||
|
||||
bytes = @io.read(bytes)
|
||||
save_pos
|
||||
bytes
|
||||
end
|
||||
|
||||
# return the next token from the source. Returns a string if a token
|
||||
# is found, nil if there are no tokens left.
|
||||
#
|
||||
def token
|
||||
reset_pos
|
||||
prepare_tokens if @tokens.size < 3
|
||||
merge_indirect_reference
|
||||
prepare_tokens if @tokens.size < 3
|
||||
|
||||
@tokens.shift
|
||||
end
|
||||
|
||||
# return the byte offset where the first XRef table in th source can be found.
|
||||
#
|
||||
def find_first_xref_offset
|
||||
check_size_is_non_zero
|
||||
@io.seek(-TRAILING_BYTECOUNT, IO::SEEK_END) rescue @io.seek(0)
|
||||
data = @io.read(TRAILING_BYTECOUNT)
|
||||
|
||||
raise MalformedPDFError, "PDF does not contain EOF marker" if data.nil?
|
||||
|
||||
# the PDF 1.7 spec (section #3.4) says that EOL markers can be either \r, \n, or both.
|
||||
lines = data.split(/[\n\r]+/).reverse
|
||||
eof_index = lines.index { |l| l.strip[/^%%EOF/] }
|
||||
|
||||
raise MalformedPDFError, "PDF does not contain EOF marker" if eof_index.nil?
|
||||
raise MalformedPDFError, "PDF EOF marker does not follow offset" if eof_index >= lines.size-1
|
||||
offset = lines[eof_index+1].to_i
|
||||
|
||||
# a byte offset < 0 doesn't make much sense. This is unlikely to happen, but in theory some
|
||||
# corrupted PDFs might have a line that looks like a negative int preceding the `%%EOF`
|
||||
raise MalformedPDFError, "invalid xref offset" if offset < 0
|
||||
offset
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_size_is_non_zero
|
||||
@io.seek(-1, IO::SEEK_END)
|
||||
@io.seek(0)
|
||||
rescue Errno::EINVAL
|
||||
raise MalformedPDFError, "PDF file is empty"
|
||||
end
|
||||
|
||||
# Returns true if this buffer is parsing a content stream
|
||||
#
|
||||
def in_content_stream?
|
||||
@in_content_stream ? true : false
|
||||
end
|
||||
|
||||
# Some bastard moved our IO stream cursor. Restore it.
|
||||
#
|
||||
def reset_pos
|
||||
@io.seek(@pos) if @io.pos != @pos
|
||||
end
|
||||
|
||||
# save the current position of the source IO stream. If someone else (like another buffer)
|
||||
# moves the cursor, we can then restore it.
|
||||
#
|
||||
def save_pos
|
||||
@pos = @io.pos
|
||||
end
|
||||
|
||||
# attempt to prime the buffer with the next few tokens.
|
||||
#
|
||||
def prepare_tokens
|
||||
10.times do
|
||||
case state
|
||||
when :literal_string then prepare_literal_token
|
||||
when :hex_string then prepare_hex_token
|
||||
when :regular then prepare_regular_token
|
||||
when :inline then prepare_inline_token
|
||||
end
|
||||
end
|
||||
|
||||
save_pos
|
||||
end
|
||||
|
||||
# tokenising behaves slightly differently based on the current context.
|
||||
# Determine the current context/state by examining the last token we found
|
||||
#
|
||||
def state
|
||||
case @tokens.last
|
||||
when LEFT_PAREN then :literal_string
|
||||
when LESS_THAN then :hex_string
|
||||
when STREAM then :stream
|
||||
when ID
|
||||
if in_content_stream? && @tokens[-2] != FWD_SLASH
|
||||
:inline
|
||||
else
|
||||
:regular
|
||||
end
|
||||
else
|
||||
:regular
|
||||
end
|
||||
end
|
||||
|
||||
# detect a series of 3 tokens that make up an indirect object. If we find
|
||||
# them, replace the tokens with a PDF::Reader::Reference instance.
|
||||
#
|
||||
# Merging them into a single string was another option, but that would mean
|
||||
# code further up the stack would need to check every token to see if it looks
|
||||
# like an indirect object. For optimisation reasons, I'd rather avoid
|
||||
# that extra check.
|
||||
#
|
||||
# It's incredibly likely that the next 3 tokens in the buffer are NOT an
|
||||
# indirect reference, so test for that case first and avoid the relatively
|
||||
# expensive regexp checks if possible.
|
||||
#
|
||||
def merge_indirect_reference
|
||||
return if @tokens.size < 3
|
||||
return if @tokens[2] != "R"
|
||||
|
||||
token_one = @tokens[0]
|
||||
token_two = @tokens[1]
|
||||
if token_one.is_a?(String) && token_two.is_a?(String) && token_one.match(DIGITS_ONLY) && token_two.match(DIGITS_ONLY)
|
||||
@tokens[0] = PDF::Reader::Reference.new(token_one.to_i, token_two.to_i)
|
||||
@tokens.delete_at(2)
|
||||
@tokens.delete_at(1)
|
||||
end
|
||||
end
|
||||
|
||||
# Extract data between ID and EI
|
||||
# If the EI follows white-space the space is dropped from the data
|
||||
# The EI must followed by white-space or end of buffer
|
||||
# This is to reduce the chance of accidentally matching an embedded EI
|
||||
def prepare_inline_token
|
||||
idstart = @io.pos
|
||||
prevchr = ''
|
||||
eisize = 0 # how many chars in the end marker
|
||||
seeking = 'E' # what are we looking for now?
|
||||
loop do
|
||||
chr = @io.read(1)
|
||||
break if chr.nil?
|
||||
case seeking
|
||||
when 'E'
|
||||
if chr == 'E'
|
||||
seeking = 'I'
|
||||
if WHITE_SPACE.include? prevchr
|
||||
eisize = 3 # include whitespace in delimiter, i.e. drop from data
|
||||
else # assume the EI immediately follows the data
|
||||
eisize = 2 # leave prevchr in data
|
||||
end
|
||||
end
|
||||
when 'I'
|
||||
if chr == 'I'
|
||||
seeking = ''
|
||||
else
|
||||
seeking = 'E'
|
||||
end
|
||||
when ''
|
||||
if WHITE_SPACE.include? chr
|
||||
eisize += 1 # Drop trailer
|
||||
break
|
||||
else
|
||||
seeking = 'E'
|
||||
end
|
||||
end
|
||||
prevchr = chr.is_a?(String) ? chr : ''
|
||||
end
|
||||
unless seeking == ''
|
||||
raise MalformedPDFError, "EI terminator not found"
|
||||
end
|
||||
eiend = @io.pos
|
||||
@io.seek(idstart, IO::SEEK_SET)
|
||||
str = @io.read(eiend - eisize - idstart) # get the ID content
|
||||
@tokens << str.freeze if str
|
||||
end
|
||||
|
||||
# if we're currently inside a hex string, read hex nibbles until
|
||||
# we find a closing >
|
||||
#
|
||||
def prepare_hex_token
|
||||
str = "".dup
|
||||
|
||||
loop do
|
||||
byte = @io.getbyte
|
||||
if byte.nil?
|
||||
break
|
||||
elsif (48..57).include?(byte) || (65..90).include?(byte) || (97..122).include?(byte)
|
||||
str << byte
|
||||
elsif byte <= 32
|
||||
# ignore it
|
||||
else
|
||||
@tokens << str if str.size > 0
|
||||
@tokens << ">" if byte != 0x3E # '>'
|
||||
@tokens << byte.chr
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# if we're currently inside a literal string we more or less just read bytes until
|
||||
# we find the closing ) delimiter. Lots of bytes that would otherwise indicate the
|
||||
# start of a new token in regular mode are left untouched when inside a literal
|
||||
# string.
|
||||
#
|
||||
# The entire literal string will be returned as a single token. It will need further
|
||||
# processing to fix things like escaped new lines, but that's someone else's
|
||||
# problem.
|
||||
#
|
||||
def prepare_literal_token
|
||||
str = "".dup
|
||||
count = 1
|
||||
|
||||
while count > 0
|
||||
byte = @io.getbyte
|
||||
if byte.nil?
|
||||
count = 0 # unbalanced params
|
||||
elsif byte == 0x5C
|
||||
str << byte << @io.getbyte
|
||||
elsif byte == 0x28 # "("
|
||||
str << "("
|
||||
count += 1
|
||||
elsif byte == 0x29 # ")"
|
||||
count -= 1
|
||||
str << ")" unless count == 0
|
||||
else
|
||||
str << byte unless count == 0
|
||||
end
|
||||
end
|
||||
|
||||
@tokens << str if str.size > 0
|
||||
@tokens << ")"
|
||||
end
|
||||
|
||||
# Extract the next regular token and stock it in our buffer, ready to be returned.
|
||||
#
|
||||
# What each byte means is complex, check out section "3.1.1 Character Set" of the 1.7 spec
|
||||
# to read up on it.
|
||||
#
|
||||
def prepare_regular_token
|
||||
tok = "".dup
|
||||
|
||||
loop do
|
||||
byte = @io.getbyte
|
||||
|
||||
case byte
|
||||
when nil
|
||||
break
|
||||
when 0x25
|
||||
# comment, ignore everything until the next EOL char
|
||||
loop do
|
||||
commentbyte = @io.getbyte
|
||||
break if commentbyte.nil? || commentbyte == 0x0A || commentbyte == 0x0D
|
||||
end
|
||||
when *TOKEN_WHITESPACE
|
||||
# white space, token finished
|
||||
@tokens << tok if tok.size > 0
|
||||
|
||||
#If the token was empty, chomp the rest of the whitespace too
|
||||
while TOKEN_WHITESPACE.include?(peek_byte) && tok.size == 0
|
||||
@io.getbyte
|
||||
end
|
||||
tok = "".dup
|
||||
break
|
||||
when 0x3C
|
||||
# opening delimiter '<', start of new token
|
||||
@tokens << tok if tok.size > 0
|
||||
if peek_byte == 0x3C # check if token is actually '<<'
|
||||
@io.getbyte
|
||||
@tokens << "<<"
|
||||
else
|
||||
@tokens << "<"
|
||||
end
|
||||
tok = "".dup
|
||||
break
|
||||
when 0x3E
|
||||
# closing delimiter '>', start of new token
|
||||
@tokens << tok if tok.size > 0
|
||||
if peek_byte == 0x3E # check if token is actually '>>'
|
||||
@io.getbyte
|
||||
@tokens << ">>"
|
||||
else
|
||||
@tokens << ">"
|
||||
end
|
||||
tok = "".dup
|
||||
break
|
||||
when 0x28, 0x5B, 0x7B
|
||||
# opening delimiter, start of new token
|
||||
@tokens << tok if tok.size > 0
|
||||
@tokens << byte.chr
|
||||
tok = "".dup
|
||||
break
|
||||
when 0x29, 0x5D, 0x7D
|
||||
# closing delimiter
|
||||
@tokens << tok if tok.size > 0
|
||||
@tokens << byte.chr
|
||||
tok = "".dup
|
||||
break
|
||||
when 0x2F
|
||||
# PDF name, start of new token
|
||||
@tokens << tok if tok.size > 0
|
||||
@tokens << byte.chr
|
||||
@tokens << "" if byte == 0x2F && ([nil, 0x20, 0x0A] + TOKEN_DELIMITER).include?(peek_byte)
|
||||
tok = "".dup
|
||||
break
|
||||
else
|
||||
tok << byte
|
||||
end
|
||||
end
|
||||
|
||||
@tokens << tok if tok.size > 0
|
||||
end
|
||||
|
||||
# peek at the next character in the io stream, leaving the stream position
|
||||
# untouched
|
||||
#
|
||||
def peek_byte
|
||||
byte = @io.getbyte
|
||||
@io.seek(-1, IO::SEEK_CUR) if byte
|
||||
byte
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,66 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'forwardable'
|
||||
|
||||
class PDF::Reader
|
||||
# A Hash-like object that wraps the array of glyph widths in a CID font
|
||||
# and gives us a nice way to query it for specific widths.
|
||||
#
|
||||
# there are two ways to calculate a cidfont_glyph_width, that are defined
|
||||
# in Section 9.7.4.3 PDF 32000-1:2008 pp 271, the differences are remarked
|
||||
# on below. because of these difference that may be contained within the
|
||||
# same array, it is a bit difficult to parse this array.
|
||||
class CidWidths
|
||||
extend Forwardable
|
||||
|
||||
# Graphics State Operators
|
||||
def_delegators :@widths, :[], :fetch
|
||||
|
||||
def initialize(default, array)
|
||||
@widths = parse_array(default, array.dup)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_array(default, array)
|
||||
widths = Hash.new(default)
|
||||
params = []
|
||||
while array.size > 0
|
||||
params << array.shift
|
||||
|
||||
if params.size == 2 && params.last.is_a?(Array)
|
||||
widths.merge! parse_first_form(params.first.to_i, Array(params.last))
|
||||
params = []
|
||||
elsif params.size == 3
|
||||
widths.merge! parse_second_form(params[0].to_i, params[1].to_i, params[2].to_i)
|
||||
params = []
|
||||
end
|
||||
end
|
||||
widths
|
||||
end
|
||||
|
||||
# this is the form 10 [234 63 234 346 47 234] where width of index 10 is
|
||||
# 234, index 11 is 63, etc
|
||||
def parse_first_form(first, widths)
|
||||
widths.inject({}) { |accum, glyph_width|
|
||||
accum[first + accum.size] = glyph_width
|
||||
accum
|
||||
}
|
||||
end
|
||||
|
||||
# this is the form 10 20 123 where all index between 10 and 20 have width 123
|
||||
def parse_second_form(first, final, width)
|
||||
if first > final
|
||||
raise MalformedPDFError, "CidWidths: #{first} must be less than #{final}"
|
||||
end
|
||||
|
||||
(first..final).inject({}) { |accum, index|
|
||||
accum[index] = width
|
||||
accum
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,186 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2008 James Healy (jimmy@deefa.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# wraps a string containing a PDF CMap and provides convenience methods for
|
||||
# extracting various useful information.
|
||||
#
|
||||
class CMap # :nodoc:
|
||||
|
||||
CMAP_KEYWORDS = {
|
||||
"begincodespacerange" => :noop,
|
||||
"endcodespacerange" => :noop,
|
||||
"beginbfchar" => :noop,
|
||||
"endbfchar" => :noop,
|
||||
"beginbfrange" => :noop,
|
||||
"endbfrange" => :noop,
|
||||
"begin" => :noop,
|
||||
"begincmap" => :noop,
|
||||
"def" => :noop
|
||||
}
|
||||
|
||||
attr_reader :map
|
||||
|
||||
def initialize(data)
|
||||
@map = {}
|
||||
process_data(data)
|
||||
end
|
||||
|
||||
def size
|
||||
@map.size
|
||||
end
|
||||
|
||||
# Convert a glyph code into one or more Codepoints.
|
||||
#
|
||||
# Returns an array of Integers.
|
||||
#
|
||||
def decode(c)
|
||||
@map.fetch(c, [])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_data(data, initial_mode = :none)
|
||||
parser = build_parser(data)
|
||||
mode = initial_mode
|
||||
instructions = []
|
||||
|
||||
while token = parser.parse_token(CMAP_KEYWORDS)
|
||||
if token.is_a?(String) || token.is_a?(Array)
|
||||
if token == "beginbfchar"
|
||||
mode = :char
|
||||
elsif token == "endbfchar"
|
||||
process_bfchar_instructions(instructions)
|
||||
instructions = []
|
||||
mode = :none
|
||||
elsif token == "beginbfrange"
|
||||
mode = :range
|
||||
elsif token == "endbfrange"
|
||||
process_bfrange_instructions(instructions)
|
||||
instructions = []
|
||||
mode = :none
|
||||
elsif mode == :char
|
||||
instructions << token.to_s
|
||||
elsif mode == :range
|
||||
instructions << token
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def build_parser(instructions)
|
||||
buffer = Buffer.new(StringIO.new(instructions))
|
||||
Parser.new(buffer)
|
||||
end
|
||||
|
||||
# The following includes some manual decoding of UTF-16BE strings into unicode codepoints. In
|
||||
# theory we could replace all the UTF-16 code with something based on Ruby's encoding support:
|
||||
#
|
||||
# str.dup.force_encoding("utf-16be").encode!("utf-8").unpack("U*")
|
||||
#
|
||||
# However, some cmaps contain broken surrogate pairs and the ruby encoding support raises an
|
||||
# exception when we try converting broken UTF-16 to UTF-8
|
||||
#
|
||||
def str_to_int(str)
|
||||
unpacked_string = if str.bytesize == 1 # UTF-8
|
||||
str.unpack("C*")
|
||||
else # UTF-16
|
||||
str.unpack("n*")
|
||||
end
|
||||
result = []
|
||||
while unpacked_string.any? do
|
||||
if unpacked_string.size >= 2 &&
|
||||
unpacked_string.first.to_i >= 0xD800 &&
|
||||
unpacked_string.first.to_i <= 0xDBFF
|
||||
# this is a Unicode UTF-16 "Surrogate Pair" see Unicode Spec. Chapter 3.7
|
||||
# lets convert to a UTF-32. (the high bit is between 0xD800-0xDBFF, the
|
||||
# low bit is between 0xDC00-0xDFFF) for example: U+1D44E (U+D835 U+DC4E)
|
||||
point_one = unpacked_string.shift.to_i
|
||||
point_two = unpacked_string.shift.to_i
|
||||
result << (point_one - 0xD800) * 0x400 + (point_two - 0xDC00) + 0x10000
|
||||
else
|
||||
result << unpacked_string.shift
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def process_bfchar_instructions(instructions)
|
||||
instructions.each_slice(2) do |one, two|
|
||||
find = str_to_int(one.to_s)
|
||||
replace = str_to_int(two.to_s)
|
||||
if find.any? && replace.any?
|
||||
@map[find.first.to_i] = replace
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def process_bfrange_instructions(instructions)
|
||||
instructions.each_slice(3) do |start, finish, to|
|
||||
if start.kind_of?(String) && finish.kind_of?(String) && to.kind_of?(String)
|
||||
bfrange_type_one(start, finish, to)
|
||||
elsif start.kind_of?(String) && finish.kind_of?(String) && to.kind_of?(Array)
|
||||
bfrange_type_two(start, finish, to)
|
||||
else
|
||||
raise MalformedPDFError, "invalid bfrange section"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def bfrange_type_one(start_code, end_code, dst)
|
||||
start_code = str_to_int(start_code).first
|
||||
end_code = str_to_int(end_code).first
|
||||
dst = str_to_int(dst)
|
||||
|
||||
return if start_code.nil? || end_code.nil?
|
||||
|
||||
# add all values in the range to our mapping
|
||||
(start_code..end_code).each_with_index do |val, idx|
|
||||
@map[val] = dst.length == 1 ? [dst[0].to_i + idx] : [dst[0].to_i, dst[1].to_i + 1]
|
||||
end
|
||||
end
|
||||
|
||||
def bfrange_type_two(start_code, end_code, dst)
|
||||
start_code = str_to_int(start_code).first
|
||||
end_code = str_to_int(end_code).first
|
||||
|
||||
return if start_code.nil? || end_code.nil?
|
||||
|
||||
from_range = (start_code..end_code)
|
||||
|
||||
# add all values in the range to our mapping
|
||||
from_range.each_with_index do |val, idx|
|
||||
dst_char = dst[idx]
|
||||
@map[val.to_i] = str_to_int(dst_char) if dst_char
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,218 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2008 James Healy (jimmy@deefa.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
# Util class for working with string encodings in PDF files. Mostly used to
|
||||
# convert strings of various PDF-dialect encodings into UTF-8.
|
||||
class Encoding # :nodoc:
|
||||
CONTROL_CHARS = [0,1,2,3,4,5,6,7,8,11,12,14,15,16,17,18,19,20,21,22,23,
|
||||
24,25,26,27,28,29,30,31]
|
||||
UNKNOWN_CHAR = 0x25AF # ▯
|
||||
|
||||
attr_reader :unpack
|
||||
|
||||
def initialize(enc)
|
||||
@mapping = default_mapping # maps from character codes to Unicode codepoints
|
||||
@string_cache = {} # maps from character codes to UTF-8 strings.
|
||||
|
||||
@enc_name = if enc.kind_of?(Hash)
|
||||
enc[:Encoding] || enc[:BaseEncoding]
|
||||
elsif enc && enc.respond_to?(:to_sym)
|
||||
enc.to_sym
|
||||
else
|
||||
:StandardEncoding
|
||||
end
|
||||
|
||||
@unpack = get_unpack(@enc_name)
|
||||
@map_file = get_mapping_file(@enc_name)
|
||||
|
||||
load_mapping(@map_file) if @map_file
|
||||
|
||||
if enc.is_a?(Hash) && enc[:Differences]
|
||||
self.differences = enc[:Differences]
|
||||
end
|
||||
end
|
||||
|
||||
# set the differences table for this encoding. should be an array in the following format:
|
||||
#
|
||||
# [25, :A, 26, :B]
|
||||
#
|
||||
# The array alternates between a decimal byte number and a glyph name to map to that byte
|
||||
#
|
||||
# To save space the following array is also valid and equivalent to the previous one
|
||||
#
|
||||
# [25, :A, :B]
|
||||
def differences=(diff)
|
||||
PDF::Reader::Error.validate_type(diff, "diff", Array)
|
||||
|
||||
@differences = {}
|
||||
byte = 0
|
||||
diff.each do |val|
|
||||
if val.kind_of?(Numeric)
|
||||
byte = val.to_i
|
||||
elsif codepoint = glyphlist.name_to_unicode(val)
|
||||
@differences[byte] = val
|
||||
@mapping[byte] = codepoint
|
||||
byte += 1
|
||||
end
|
||||
end
|
||||
@differences
|
||||
end
|
||||
|
||||
def differences
|
||||
# this method is only used by the spec tests
|
||||
@differences ||= {}
|
||||
end
|
||||
|
||||
# convert the specified string to utf8
|
||||
#
|
||||
# * unpack raw bytes into codepoints
|
||||
# * replace any that have entries in the differences table with a glyph name
|
||||
# * convert codepoints from source encoding to Unicode codepoints
|
||||
# * convert any glyph names to Unicode codepoints
|
||||
# * replace characters that didn't convert to Unicode nicely with something
|
||||
# valid
|
||||
# * pack the final array of Unicode codepoints into a utf-8 string
|
||||
# * mark the string as utf-8 if we're running on a M17N aware VM
|
||||
#
|
||||
def to_utf8(str)
|
||||
if utf8_conversion_impossible?
|
||||
little_boxes(str.unpack(unpack).size)
|
||||
else
|
||||
convert_to_utf8(str)
|
||||
end
|
||||
end
|
||||
|
||||
def int_to_utf8_string(glyph_code)
|
||||
@string_cache[glyph_code] ||= internal_int_to_utf8_string(glyph_code)
|
||||
end
|
||||
|
||||
# convert an integer glyph code into an Adobe glyph name.
|
||||
#
|
||||
# int_to_name(65)
|
||||
# => [:A]
|
||||
#
|
||||
def int_to_name(glyph_code)
|
||||
if @enc_name == :"Identity-H" || @enc_name == :"Identity-V"
|
||||
[]
|
||||
elsif differences[glyph_code]
|
||||
[differences[glyph_code]]
|
||||
elsif @mapping[glyph_code]
|
||||
glyphlist.unicode_to_name(@mapping[glyph_code])
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# returns a hash that:
|
||||
# - maps control chars and nil to the unicode "unknown character"
|
||||
# - leaves all other bytes <= 255 unchaged
|
||||
#
|
||||
# Each specific encoding will change this default as required for their glyphs
|
||||
def default_mapping
|
||||
all_bytes = (0..255).to_a
|
||||
tuples = all_bytes.map {|i|
|
||||
CONTROL_CHARS.include?(i) ? [i, UNKNOWN_CHAR] : [i,i]
|
||||
}
|
||||
mapping = Hash[tuples]
|
||||
mapping
|
||||
end
|
||||
|
||||
def internal_int_to_utf8_string(glyph_code)
|
||||
ret = [
|
||||
@mapping[glyph_code.to_i] || glyph_code.to_i
|
||||
].pack("U*")
|
||||
ret.force_encoding("UTF-8")
|
||||
ret
|
||||
end
|
||||
|
||||
def utf8_conversion_impossible?
|
||||
@enc_name == :"Identity-H" || @enc_name == :"Identity-V"
|
||||
end
|
||||
|
||||
def little_boxes(times)
|
||||
codepoints = [ PDF::Reader::Encoding::UNKNOWN_CHAR ] * times
|
||||
ret = codepoints.pack("U*")
|
||||
ret.force_encoding("UTF-8")
|
||||
ret
|
||||
end
|
||||
|
||||
def convert_to_utf8(str)
|
||||
ret = str.unpack(unpack).map! { |c| @mapping[c.to_i] || c }.pack("U*")
|
||||
ret.force_encoding("UTF-8")
|
||||
ret
|
||||
end
|
||||
|
||||
def get_unpack(enc)
|
||||
case enc
|
||||
when :"Identity-H", :"Identity-V", :UTF16Encoding
|
||||
"n*"
|
||||
else
|
||||
"C*"
|
||||
end
|
||||
end
|
||||
|
||||
def get_mapping_file(enc)
|
||||
case enc
|
||||
when :"Identity-H", :"Identity-V", :UTF16Encoding then
|
||||
nil
|
||||
when :MacRomanEncoding then
|
||||
File.dirname(__FILE__) + "/encodings/mac_roman.txt"
|
||||
when :MacExpertEncoding then
|
||||
File.dirname(__FILE__) + "/encodings/mac_expert.txt"
|
||||
when :PDFDocEncoding then
|
||||
File.dirname(__FILE__) + "/encodings/pdf_doc.txt"
|
||||
when :SymbolEncoding then
|
||||
File.dirname(__FILE__) + "/encodings/symbol.txt"
|
||||
when :WinAnsiEncoding then
|
||||
File.dirname(__FILE__) + "/encodings/win_ansi.txt"
|
||||
when :ZapfDingbatsEncoding then
|
||||
File.dirname(__FILE__) + "/encodings/zapf_dingbats.txt"
|
||||
else
|
||||
File.dirname(__FILE__) + "/encodings/standard.txt"
|
||||
end
|
||||
end
|
||||
|
||||
def glyphlist
|
||||
@glyphlist ||= PDF::Reader::GlyphHash.new
|
||||
end
|
||||
|
||||
def load_mapping(file)
|
||||
File.open(file, "r:BINARY") do |f|
|
||||
f.each do |l|
|
||||
_m, single_byte, unicode = *l.match(/\A([0-9A-Za-z]+);([0-9A-F]{4})/)
|
||||
@mapping["0x#{single_byte}".hex] = "0x#{unicode}".hex if single_byte
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,159 @@
|
||||
21;F721
|
||||
22;F6F8 # Hungarumlautsmall
|
||||
23;F7A2
|
||||
24;F724
|
||||
25;F6E4
|
||||
26;F726
|
||||
27;F7B4
|
||||
28;207D
|
||||
29;F07E
|
||||
2A;2025
|
||||
2B;2024
|
||||
2F;2044
|
||||
30;F730
|
||||
31;F731
|
||||
32;F732
|
||||
33;F733
|
||||
34;F734
|
||||
35;F735
|
||||
36;F736
|
||||
37;F737
|
||||
38;F738
|
||||
39;F739
|
||||
3D;F6DE
|
||||
3F;F73F
|
||||
44;F7F0
|
||||
47;00BC
|
||||
48;00BD
|
||||
49;00BE
|
||||
4A;215B
|
||||
4B;215C
|
||||
4C;215D
|
||||
4D;215E
|
||||
4E;2153
|
||||
4F;2154
|
||||
56;FB00
|
||||
57;FB01
|
||||
58;FB02
|
||||
59;FB03
|
||||
5A;FB04
|
||||
5B;208D
|
||||
5D;208E
|
||||
5E;F6F6
|
||||
5F;F6E5
|
||||
60;F760
|
||||
61;F761
|
||||
62;F762
|
||||
63;F763
|
||||
64;F764
|
||||
65;F765
|
||||
66;F766
|
||||
67;F767
|
||||
68;F768
|
||||
69;F769
|
||||
6A;F76A
|
||||
6B;F76B
|
||||
6C;F76C
|
||||
6D;F76D
|
||||
6E;F76E
|
||||
6F;F76F
|
||||
70;F770
|
||||
71;F771
|
||||
72;F772
|
||||
73;F773
|
||||
74;F774
|
||||
75;F775
|
||||
76;F776
|
||||
77;F777
|
||||
78;F778
|
||||
79;F779
|
||||
7A;F77A
|
||||
7B;20A1
|
||||
7C;F6DC
|
||||
7D;F6DD
|
||||
7E;F6FE
|
||||
81;F6E9
|
||||
82;F6E0
|
||||
87;F7E1 # Acircumflexsmall
|
||||
88;F7E0
|
||||
89;F7E2 # Acutesmall
|
||||
8A;F7E4
|
||||
8B;F7E3
|
||||
8C;F7E5
|
||||
8D;F7E7
|
||||
8E;F7E9
|
||||
8F;F7E8
|
||||
90;F7E4
|
||||
91;F7EB
|
||||
92;F7ED
|
||||
93;F7EC
|
||||
94;F7EE
|
||||
95;F7EF
|
||||
96;F7F1
|
||||
97;F7F3
|
||||
98;F7F2
|
||||
99;F7F4
|
||||
9A;F7F6
|
||||
9B;F7F5
|
||||
9C;F7FA
|
||||
9D;F7F9
|
||||
9E;F7FB
|
||||
9F;F7FC
|
||||
A1;2078
|
||||
A2;2084
|
||||
A3;2083
|
||||
A4;2086
|
||||
A5;2088
|
||||
A6;2087
|
||||
A7;F6FD
|
||||
A9;F6DF
|
||||
AA;2082
|
||||
AC;F7A8
|
||||
AE;F6F5
|
||||
AF;F6F0
|
||||
B0;2085
|
||||
B2;F6E1
|
||||
B3;F6E7
|
||||
B4;F7FD
|
||||
B6;F6E3
|
||||
B9;F7FE
|
||||
BB;2089
|
||||
BC;2080
|
||||
BD;F6FF
|
||||
BE;F7E6 # AEsmall
|
||||
BF;F7F8
|
||||
C0;F7BF
|
||||
C1;2081
|
||||
C2;F6F9
|
||||
C9;F7B8
|
||||
CF;F6FA
|
||||
D0;2012
|
||||
D1;F6E6
|
||||
D6;F7A1
|
||||
D8;F7FF
|
||||
DA;00B9
|
||||
DB;00B2
|
||||
DC;00B3
|
||||
DD;2074
|
||||
DE;2075
|
||||
DF;2076
|
||||
E0;2077
|
||||
E1;2079
|
||||
E2;2070
|
||||
E4;F6EC
|
||||
E5;F6F1
|
||||
E6;F6F3
|
||||
E9;F6ED
|
||||
EA;F6F2
|
||||
EB;F6EB
|
||||
F1;F6EE
|
||||
F2;F6FB
|
||||
F3;F6F4
|
||||
F4;F7AF
|
||||
F5;F6EF
|
||||
F6;207F
|
||||
F7;F6EF
|
||||
F8;F6E2
|
||||
F9;F6E8
|
||||
FA;F6F7
|
||||
FB;F6FC
|
||||
@@ -0,0 +1,128 @@
|
||||
80;00C4
|
||||
81;00C5
|
||||
82;00C7
|
||||
83;00C9
|
||||
84;00D1
|
||||
85;00D6
|
||||
86;00DC
|
||||
87;00E1
|
||||
88;00E0
|
||||
89;00E2
|
||||
8A;00E4
|
||||
8B;00E3
|
||||
8C;00E5
|
||||
8D;00E7
|
||||
8E;00E9
|
||||
8F;00E8
|
||||
90;00EA
|
||||
91;00EB
|
||||
92;00ED
|
||||
93;00EC
|
||||
94;00EE
|
||||
95;00EF
|
||||
96;00F1
|
||||
97;00F3
|
||||
98;00F2
|
||||
99;00F4
|
||||
9A;00F6
|
||||
9B;00F5
|
||||
9C;00FA
|
||||
9D;00F9
|
||||
9E;00FB
|
||||
9F;00FC
|
||||
A0;2020
|
||||
A1;00B0
|
||||
A2;00A2
|
||||
A3;00A3
|
||||
A4;00A7
|
||||
A5;2022
|
||||
A6;00B6
|
||||
A7;00DF
|
||||
A8;00AE
|
||||
A9;00A9
|
||||
AA;2122
|
||||
AB;00B4
|
||||
AC;00A8
|
||||
AD;2260
|
||||
AE;00C6
|
||||
AF;00D8
|
||||
B0;221E
|
||||
B1;00B1
|
||||
B2;2264
|
||||
B3;2265
|
||||
B4;00A5
|
||||
B5;00B5
|
||||
B6;2202
|
||||
B7;2211
|
||||
B8;220F
|
||||
B9;03C0
|
||||
BA;222B
|
||||
BB;00AA
|
||||
BC;00BA
|
||||
BD;03A9
|
||||
BE;00E6
|
||||
BF;00F8
|
||||
C0;00BF
|
||||
C1;00A1
|
||||
C2;00AC
|
||||
C3;221A
|
||||
C4;0192
|
||||
C5;2248
|
||||
C6;2206
|
||||
C7;00AB
|
||||
C8;00BB
|
||||
C9;2026
|
||||
CA;00A0
|
||||
CB;00C0
|
||||
CC;00C3
|
||||
CD;00D5
|
||||
CE;0152
|
||||
CF;0153
|
||||
D0;2013
|
||||
D1;2014
|
||||
D2;201C
|
||||
D3;201D
|
||||
D4;2018
|
||||
D5;2019
|
||||
D6;00F7
|
||||
D7;25CA
|
||||
D8;00FF
|
||||
D9;0178
|
||||
DA;2044
|
||||
DB;20AC
|
||||
DC;2039
|
||||
DD;203A
|
||||
DE;FB01
|
||||
DF;FB02
|
||||
E0;2021
|
||||
E1;00B7
|
||||
E2;201A
|
||||
E3;201E
|
||||
E4;2030
|
||||
E5;00C2
|
||||
E6;00CA
|
||||
E7;00C1
|
||||
E8;00CB
|
||||
E9;00C8
|
||||
EA;00CD
|
||||
EB;00CE
|
||||
EC;00CF
|
||||
ED;00CC
|
||||
EE;00D3
|
||||
EF;00D4
|
||||
F0;F8FF
|
||||
F1;00D2
|
||||
F2;00DA
|
||||
F3;00D8
|
||||
F4;00D9
|
||||
F5;0131
|
||||
F6;02C6
|
||||
F7;02DC
|
||||
F8;00AF
|
||||
F9;02D8
|
||||
FA;02D9
|
||||
FB;02DA
|
||||
FC;00B8
|
||||
FD;02DD
|
||||
FE;02DB
|
||||
FF;02C7
|
||||
@@ -0,0 +1,40 @@
|
||||
18;02D8
|
||||
19;02C7
|
||||
1A;02C6
|
||||
1B;02D9
|
||||
1C;02DD
|
||||
1D;02DB
|
||||
1E;02DA
|
||||
1F;02DC
|
||||
80;2022
|
||||
81;2020
|
||||
82;2021
|
||||
83;2026
|
||||
84;2014
|
||||
85;2013
|
||||
86;0192
|
||||
87;2044
|
||||
88;2039
|
||||
89;203A
|
||||
8A;2212
|
||||
8B;2030
|
||||
8C;201E
|
||||
8D;201C
|
||||
8E;201D
|
||||
8F;2018
|
||||
90;2019
|
||||
91;201A
|
||||
92;2122
|
||||
93;FB01
|
||||
94;FB02
|
||||
95;0141
|
||||
96;0152
|
||||
97;0160
|
||||
98;0178
|
||||
99;017D
|
||||
9A;0131
|
||||
9B;0142
|
||||
9C;0153
|
||||
9D;0161
|
||||
9E;017E
|
||||
A0;20AC
|
||||
@@ -0,0 +1,47 @@
|
||||
27;2019
|
||||
60;2018
|
||||
A4;2044
|
||||
A6;0192
|
||||
A8;00A4
|
||||
A9;0027
|
||||
AA;201C
|
||||
AC;2039
|
||||
AD;203A
|
||||
AE;FB01
|
||||
AF;FB02
|
||||
B1;2013
|
||||
B2;2020
|
||||
B3;2021
|
||||
B4;00B7
|
||||
B7;2022
|
||||
B8;201A
|
||||
B9;201E
|
||||
BA;201D
|
||||
BC;2026
|
||||
BD;2030
|
||||
C1;0060
|
||||
C2;00B4
|
||||
C3;02C6
|
||||
C4;02DC
|
||||
C5;00AF
|
||||
C6;02D8
|
||||
C7;02D9
|
||||
C8;00A8
|
||||
CA;02DA
|
||||
CB;00B8
|
||||
CD;02DD
|
||||
CE;02DB
|
||||
CF;02C7
|
||||
D0;2014
|
||||
E1;00C6
|
||||
E3;00AA
|
||||
E8;0141
|
||||
E9;00D8
|
||||
EA;0152
|
||||
EB;00BA
|
||||
F1;00E6
|
||||
F5;0131
|
||||
F8;0142
|
||||
F9;00F8
|
||||
FA;0153
|
||||
FB;00DF
|
||||
@@ -0,0 +1,154 @@
|
||||
22;2200
|
||||
24;2203
|
||||
27;220B
|
||||
2A;2217
|
||||
2D;2212
|
||||
40;2245
|
||||
41;0391
|
||||
42;0392
|
||||
43;03A7
|
||||
44;0394
|
||||
45;0395
|
||||
46;03A6
|
||||
47;0393
|
||||
48;0397
|
||||
49;0399
|
||||
4A;03D1
|
||||
4B;039A
|
||||
4C;039B
|
||||
4D;039C
|
||||
4E;039D
|
||||
4F;039F
|
||||
50;03A0
|
||||
51;0398
|
||||
52;03A1
|
||||
53;03A3
|
||||
54;03A4
|
||||
55;03A5
|
||||
56;03C2
|
||||
57;03A9
|
||||
58;039E
|
||||
59;03A8
|
||||
5A;0396
|
||||
5C;2234
|
||||
5E;22A5
|
||||
60;F8E5
|
||||
61;03B1
|
||||
62;03B2
|
||||
63;03C7
|
||||
64;03B4
|
||||
65;03B5
|
||||
66;03C6
|
||||
67;03B3
|
||||
68;03B7
|
||||
69;03B9
|
||||
6A;03D5
|
||||
6B;03BA
|
||||
6C;03BB
|
||||
6D;03BC
|
||||
6E;03BD
|
||||
6F;03BF
|
||||
70;03C0
|
||||
71;03B8
|
||||
72;03C1
|
||||
73;03C3
|
||||
74;03C4
|
||||
75;03C5
|
||||
76;03D6
|
||||
77;03C9
|
||||
78;03BE
|
||||
79;03C8
|
||||
7A;03B6
|
||||
7E;223C
|
||||
A0;20AC
|
||||
A1;03D2
|
||||
A2;2032
|
||||
A3;2264
|
||||
A4;2215
|
||||
A5;221E
|
||||
A6;0192
|
||||
A7;2663
|
||||
A8;2666
|
||||
A9;2665
|
||||
AA;2660
|
||||
AB;2194
|
||||
AC;2190
|
||||
AD;2191
|
||||
AE;2192
|
||||
AF;2193
|
||||
B2;2033
|
||||
B3;2265
|
||||
B4;00D7
|
||||
B5;221D
|
||||
B6;2202
|
||||
B7;2022
|
||||
B8;00F7
|
||||
B9;2260
|
||||
BA;2261
|
||||
BB;2248
|
||||
BC;2026
|
||||
BD;F8E6
|
||||
BE;F8E7
|
||||
BF;21B5
|
||||
C0;2135
|
||||
C1;2111
|
||||
C2;211C
|
||||
C3;2118
|
||||
C4;2297
|
||||
C5;2295
|
||||
C6;2205
|
||||
C7;2229
|
||||
C8;222A
|
||||
C9;2283
|
||||
CA;2287
|
||||
CB;2284
|
||||
CC;2282
|
||||
CD;2286
|
||||
CE;2208
|
||||
CF;2209
|
||||
D0;2220
|
||||
D1;2207
|
||||
D2;F6DA
|
||||
D3;F6D9
|
||||
D4;F6DB
|
||||
D5;220F
|
||||
D6;221A
|
||||
D7;22C5
|
||||
D8;00AC
|
||||
D9;2227
|
||||
DA;2228
|
||||
DB;21D4
|
||||
DC;21D0
|
||||
DD;21D1
|
||||
DE;21D2
|
||||
DF;21D3
|
||||
E0;25CA
|
||||
E1;2329
|
||||
E2;F8E8
|
||||
E3;F8E9
|
||||
E4;F8EA
|
||||
E5;2211
|
||||
E6;F8EB
|
||||
E7;F8EC
|
||||
E8;F8ED
|
||||
E9;F8EE
|
||||
EA;F8EF
|
||||
EB;F8F0
|
||||
EC;F8F1
|
||||
ED;F8F2
|
||||
EE;F8F3
|
||||
EF;F8F4
|
||||
F1;232A
|
||||
F2;222B
|
||||
F3;2320
|
||||
F4;F8F5
|
||||
F5;2321
|
||||
F6;F8F6
|
||||
F7;F8F7
|
||||
F8;F8F8
|
||||
F9;F8F9
|
||||
FA;F8FA
|
||||
FB;F8FB
|
||||
FC;F8FC
|
||||
FD;F8FD
|
||||
FE;F8FE
|
||||
@@ -0,0 +1,29 @@
|
||||
# A mapping of WinAnsi (win-1252) characters to unicode. Anything
|
||||
# not specified is left unchanged
|
||||
80;20AC
|
||||
82;201A
|
||||
83;0192
|
||||
84;201E
|
||||
85;2026
|
||||
86;2020
|
||||
87;2021
|
||||
88;02C6
|
||||
89;2030
|
||||
8A;0160
|
||||
8B;2039
|
||||
8C;0152
|
||||
8E;017D
|
||||
91;2018
|
||||
92;2019
|
||||
93;201C
|
||||
94;201D
|
||||
95;2022
|
||||
96;2013
|
||||
97;2014
|
||||
98;02DC
|
||||
99;2122
|
||||
9A;0161
|
||||
9B;203A
|
||||
9C;0152
|
||||
9E;017E
|
||||
9F;0178
|
||||
@@ -0,0 +1,201 @@
|
||||
21;2701
|
||||
22;2702
|
||||
23;2703
|
||||
24;2704
|
||||
25;260E
|
||||
26;2706
|
||||
27;2707
|
||||
28;2708
|
||||
29;2709
|
||||
2A;261B
|
||||
2B;261E
|
||||
2C;270C
|
||||
2D;270D
|
||||
2E;270E
|
||||
2F;270F
|
||||
30;2710
|
||||
31;2711
|
||||
32;2712
|
||||
33;2713
|
||||
34;2714
|
||||
35;2715
|
||||
36;2716
|
||||
37;2717
|
||||
38;2718
|
||||
39;2719
|
||||
3A;271A
|
||||
3B;271B
|
||||
3C;271C
|
||||
3D;271D
|
||||
3E;271E
|
||||
3F;271E
|
||||
40;2720
|
||||
41;2721
|
||||
42;2722
|
||||
43;2723
|
||||
44;2724
|
||||
45;2725
|
||||
46;2726
|
||||
47;2727
|
||||
48;2605
|
||||
49;2729
|
||||
4A;272A
|
||||
4B;272B
|
||||
4C;272C
|
||||
4D;272D
|
||||
4E;272E
|
||||
4F;272F
|
||||
50;2730
|
||||
51;2731
|
||||
52;2732
|
||||
53;2733
|
||||
54;2734
|
||||
55;2735
|
||||
56;2736
|
||||
57;2737
|
||||
58;2738
|
||||
59;2739
|
||||
5A;273A
|
||||
5B;273B
|
||||
5C;273C
|
||||
5D;273D
|
||||
5E;273E
|
||||
5F;273F
|
||||
60;2740
|
||||
61;2741
|
||||
62;2742
|
||||
63;2743
|
||||
64;2744
|
||||
65;2745
|
||||
66;2746
|
||||
67;2747
|
||||
68;2748
|
||||
69;2749
|
||||
6A;274A
|
||||
6B;274B
|
||||
6C;25CF
|
||||
6D;274D
|
||||
6E;25A0
|
||||
6F;274F
|
||||
70;2750
|
||||
71;2751
|
||||
72;2752
|
||||
73;2753
|
||||
74;2754
|
||||
75;2755
|
||||
76;2756
|
||||
77;2757
|
||||
78;2758
|
||||
79;2759
|
||||
7A;275A
|
||||
7B;275B
|
||||
7C;275C
|
||||
7D;275D
|
||||
7E;275E
|
||||
80;F8D7
|
||||
81;F8D8
|
||||
82;F8D9
|
||||
83;F8DA
|
||||
84;F8DB
|
||||
85;F8DC
|
||||
86;F8DD
|
||||
87;F8DE
|
||||
88;F8DF
|
||||
89;F8E0
|
||||
8A;F8E1
|
||||
8B;F8E2
|
||||
8C;F8E3
|
||||
8D;F8E4
|
||||
A1;2761
|
||||
A2;2762
|
||||
A3;2763
|
||||
A4;2764
|
||||
A5;2765
|
||||
A6;2766
|
||||
A7;2767
|
||||
A8;2663
|
||||
A9;2666
|
||||
AA;2665
|
||||
AB;2660
|
||||
AC;2460
|
||||
AD;2461
|
||||
AE;2462
|
||||
AF;2463
|
||||
B0;2464
|
||||
B1;2465
|
||||
B2;2466
|
||||
B3;2467
|
||||
B4;2468
|
||||
B5;2469
|
||||
B6;2776
|
||||
B7;2777
|
||||
B8;2778
|
||||
B9;2779
|
||||
BA;277A
|
||||
BB;277B
|
||||
BC;277C
|
||||
BD;277D
|
||||
BE;277E
|
||||
BF;277F
|
||||
C0;2780
|
||||
C1;2781
|
||||
C2;2782
|
||||
C3;2783
|
||||
C4;2784
|
||||
C5;2785
|
||||
C6;2786
|
||||
C7;2787
|
||||
C8;2788
|
||||
C9;2789
|
||||
CA;278A
|
||||
CB;278B
|
||||
CC;278C
|
||||
CD;278D
|
||||
CE;278E
|
||||
CF;278F
|
||||
D0;2790
|
||||
D1;2791
|
||||
D2;2792
|
||||
D3;2793
|
||||
D4;2794
|
||||
D5;2795
|
||||
D6;2796
|
||||
D7;2797
|
||||
D8;2798
|
||||
D9;2799
|
||||
DA;279A
|
||||
DB;279B
|
||||
DC;279C
|
||||
DD;279D
|
||||
DE;279E
|
||||
DF;279F
|
||||
E0;27A0
|
||||
E1;27A1
|
||||
E2;27A2
|
||||
E3;27A3
|
||||
E4;27A4
|
||||
E5;27A5
|
||||
E6;27A6
|
||||
E7;27A7
|
||||
E8;27A8
|
||||
E9;27A9
|
||||
EA;27AA
|
||||
EB;27AB
|
||||
EC;27AC
|
||||
ED;27AD
|
||||
EE;27AE
|
||||
EF;27AF
|
||||
F1;27B1
|
||||
F2;27B2
|
||||
F3;27B3
|
||||
F4;27B4
|
||||
F5;27B5
|
||||
F6;27B6
|
||||
F7;27B7
|
||||
F8;27B8
|
||||
F9;27B9
|
||||
FA;27BA
|
||||
FB;27BB
|
||||
FC;27BC
|
||||
FD;27BD
|
||||
FE;27BE
|
||||
@@ -0,0 +1,86 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
class PDF::Reader
|
||||
################################################################################
|
||||
# An internal PDF::Reader class that helps to verify various parts of the PDF file
|
||||
# are valid
|
||||
class Error # :nodoc:
|
||||
################################################################################
|
||||
def self.str_assert(lvalue, rvalue, chars=nil)
|
||||
raise MalformedPDFError, "PDF malformed, expected string but found #{lvalue.class} instead" if chars and !lvalue.kind_of?(String)
|
||||
lvalue = lvalue[0,chars] if chars
|
||||
raise MalformedPDFError, "PDF malformed, expected '#{rvalue}' but found '#{lvalue}' instead" if lvalue != rvalue
|
||||
end
|
||||
################################################################################
|
||||
def self.str_assert_not(lvalue, rvalue, chars=nil)
|
||||
raise MalformedPDFError, "PDF malformed, expected string but found #{lvalue.class} instead" if chars and !lvalue.kind_of?(String)
|
||||
lvalue = lvalue[0,chars] if chars
|
||||
raise MalformedPDFError, "PDF malformed, expected '#{rvalue}' but found '#{lvalue}' instead" if lvalue == rvalue
|
||||
end
|
||||
################################################################################
|
||||
def self.assert_equal(lvalue, rvalue)
|
||||
raise MalformedPDFError, "PDF malformed, expected '#{rvalue}' but found '#{lvalue}' instead" if lvalue != rvalue
|
||||
end
|
||||
################################################################################
|
||||
def self.validate_type(object, name, klass)
|
||||
raise ArgumentError, "#{name} (#{object}) must be a #{klass}" unless object.is_a?(klass)
|
||||
end
|
||||
################################################################################
|
||||
def self.validate_type_as_malformed(object, name, klass)
|
||||
raise MalformedPDFError, "#{name} (#{object}) must be a #{klass}" unless object.is_a?(klass)
|
||||
end
|
||||
################################################################################
|
||||
def self.validate_not_nil(object, name)
|
||||
raise ArgumentError, "#{object} must not be nil" if object.nil?
|
||||
end
|
||||
end
|
||||
|
||||
################################################################################
|
||||
# an exception that is raised when we believe the current PDF is not following
|
||||
# the PDF spec and cannot be recovered
|
||||
class MalformedPDFError < RuntimeError; end
|
||||
|
||||
################################################################################
|
||||
# an exception that is raised when an invalid page number is used
|
||||
class InvalidPageError < ArgumentError; end
|
||||
|
||||
################################################################################
|
||||
# an exception that is raised when a PDF object appears to be invalid
|
||||
class InvalidObjectError < MalformedPDFError; end
|
||||
|
||||
################################################################################
|
||||
# an exception that is raised when a PDF follows the specs but uses a feature
|
||||
# that we don't support just yet
|
||||
class UnsupportedFeatureError < RuntimeError; end
|
||||
|
||||
################################################################################
|
||||
# an exception that is raised when a PDF is encrypted and we don't have the
|
||||
# necessary data to decrypt it
|
||||
class EncryptedPDFError < UnsupportedFeatureError; end
|
||||
end
|
||||
################################################################################
|
||||
@@ -0,0 +1,60 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
################################################################################
|
||||
# Various parts of a PDF file can be passed through a filter before being stored to provide
|
||||
# support for features like compression and encryption. This class is for decoding that
|
||||
# content.
|
||||
#
|
||||
module Filter # :nodoc:
|
||||
|
||||
################################################################################
|
||||
# creates a new filter for decoding content.
|
||||
#
|
||||
# Filters that are only used to encode image data are accepted, but the data is
|
||||
# returned untouched. At this stage PDF::Reader has no need to decode images.
|
||||
#
|
||||
def self.with(name, options = {})
|
||||
case name
|
||||
when :ASCII85Decode, :A85 then PDF::Reader::Filter::Ascii85.new(options)
|
||||
when :ASCIIHexDecode, :AHx then PDF::Reader::Filter::AsciiHex.new(options)
|
||||
when :CCITTFaxDecode, :CCF then PDF::Reader::Filter::Null.new(options)
|
||||
when :DCTDecode, :DCT then PDF::Reader::Filter::Null.new(options)
|
||||
when :FlateDecode, :Fl then PDF::Reader::Filter::Flate.new(options)
|
||||
when :JBIG2Decode then PDF::Reader::Filter::Null.new(options)
|
||||
when :JPXDecode then PDF::Reader::Filter::Null.new(options)
|
||||
when :LZWDecode, :LZW then PDF::Reader::Filter::Lzw.new(options)
|
||||
when :RunLengthDecode, :RL then PDF::Reader::Filter::RunLength.new(options)
|
||||
else
|
||||
raise UnsupportedFeatureError, "Unknown filter: #{name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'ascii85'
|
||||
|
||||
class PDF::Reader
|
||||
module Filter # :nodoc:
|
||||
# implementation of the Ascii85 filter
|
||||
class Ascii85
|
||||
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
end
|
||||
|
||||
################################################################################
|
||||
# Decode the specified data using the Ascii85 algorithm. Relies on the AScii85
|
||||
# rubygem.
|
||||
#
|
||||
def filter(data)
|
||||
data = "<~#{data}" unless data.to_s[0,2] == "<~"
|
||||
if defined?(::Ascii85Native)
|
||||
::Ascii85Native::decode(data)
|
||||
else
|
||||
::Ascii85::decode(data)
|
||||
end
|
||||
rescue Exception => e
|
||||
# Oops, there was a problem decoding the stream
|
||||
raise MalformedPDFError,
|
||||
"Error occured while decoding an ASCII85 stream (#{e.class.to_s}: #{e.to_s})"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
class PDF::Reader
|
||||
module Filter # :nodoc:
|
||||
# implementation of the AsciiHex stream filter
|
||||
class AsciiHex
|
||||
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
end
|
||||
|
||||
################################################################################
|
||||
# Decode the specified data using the AsciiHex algorithm.
|
||||
#
|
||||
def filter(data)
|
||||
data.chop! if data[-1,1] == ">"
|
||||
data = data[1,data.size] if data[0,1] == "<"
|
||||
|
||||
return "" if data.nil?
|
||||
|
||||
data.gsub!(/[^A-Fa-f0-9]/,"")
|
||||
data << "0" if data.size % 2 == 1
|
||||
data.scan(/.{2}/).flatten.map { |s| s.hex.chr }.join("")
|
||||
rescue Exception => e
|
||||
# Oops, there was a problem decoding the stream
|
||||
raise MalformedPDFError,
|
||||
"Error occured while decoding an ASCIIHex stream (#{e.class.to_s}: #{e.to_s})"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
module Filter # :nodoc:
|
||||
# some filter implementations support preprocessing of the data to
|
||||
# improve compression
|
||||
class Depredict
|
||||
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
end
|
||||
|
||||
################################################################################
|
||||
# Streams can be preprocessed to improve compression. This reverses the
|
||||
# preprocessing
|
||||
#
|
||||
def filter(data)
|
||||
predictor = @options[:Predictor].to_i
|
||||
|
||||
case predictor
|
||||
when 0, 1 then
|
||||
data
|
||||
when 2 then
|
||||
tiff_depredict(data)
|
||||
when 10, 11, 12, 13, 14, 15 then
|
||||
png_depredict(data)
|
||||
else
|
||||
raise MalformedPDFError, "Unrecognised predictor value (#{predictor})"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
################################################################################
|
||||
def tiff_depredict(data)
|
||||
data = data.unpack("C*")
|
||||
unfiltered = ''
|
||||
bpc = @options[:BitsPerComponent] || 8
|
||||
pixel_bits = bpc * @options[:Colors]
|
||||
pixel_bytes = pixel_bits / 8
|
||||
line_len = (pixel_bytes * @options[:Columns])
|
||||
pos = 0
|
||||
|
||||
if bpc != 8
|
||||
raise UnsupportedFeatureError, "TIFF predictor onlys supports 8 Bits Per Component"
|
||||
end
|
||||
|
||||
until pos > data.size
|
||||
row_data = data[pos, line_len]
|
||||
row_data.each_with_index do |byte, index|
|
||||
left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
|
||||
row_data[index] = (byte + left) % 256
|
||||
end
|
||||
unfiltered += row_data.pack("C*")
|
||||
pos += line_len
|
||||
end
|
||||
|
||||
unfiltered
|
||||
end
|
||||
################################################################################
|
||||
def png_depredict(data)
|
||||
return data if @options[:Predictor].to_i < 10
|
||||
|
||||
data = data.unpack("C*")
|
||||
|
||||
pixel_bytes = @options[:Colors] || 1
|
||||
scanline_length = (pixel_bytes * @options[:Columns]) + 1
|
||||
row = 0
|
||||
pixels = []
|
||||
paeth, pa, pb, pc = 0, 0, 0, 0
|
||||
until data.empty? do
|
||||
row_data = data.slice! 0, scanline_length
|
||||
filter = row_data.shift
|
||||
case filter
|
||||
when 0 # None
|
||||
when 1 # Sub
|
||||
row_data.each_with_index do |byte, index|
|
||||
left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
|
||||
row_data[index] = (byte + left) % 256
|
||||
#p [byte, left, row_data[index]]
|
||||
end
|
||||
when 2 # Up
|
||||
row_data.each_with_index do |byte, index|
|
||||
col = index / pixel_bytes
|
||||
upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_bytes]
|
||||
row_data[index] = (upper + byte) % 256
|
||||
end
|
||||
when 3 # Average
|
||||
row_data.each_with_index do |byte, index|
|
||||
col = index / pixel_bytes
|
||||
upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_bytes]
|
||||
left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
|
||||
|
||||
row_data[index] = (byte + ((left + upper)/2).floor) % 256
|
||||
end
|
||||
when 4 # Paeth
|
||||
left = upper = upper_left = 0
|
||||
row_data.each_with_index do |byte, index|
|
||||
col = index / pixel_bytes
|
||||
|
||||
left = index < pixel_bytes ? 0 : Integer(row_data[index - pixel_bytes])
|
||||
if row.zero?
|
||||
upper = upper_left = 0
|
||||
else
|
||||
upper = Integer(pixels[row-1][col][index % pixel_bytes])
|
||||
upper_left = col.zero? ? 0 :
|
||||
Integer(pixels[row-1][col-1][index % pixel_bytes])
|
||||
end
|
||||
|
||||
p = left + upper - upper_left
|
||||
pa = (p - left).abs
|
||||
pb = (p - upper).abs
|
||||
pc = (p - upper_left).abs
|
||||
|
||||
paeth = if pa <= pb && pa <= pc
|
||||
left
|
||||
elsif pb <= pc
|
||||
upper
|
||||
else
|
||||
upper_left
|
||||
end
|
||||
|
||||
row_data[index] = (byte + paeth) % 256
|
||||
end
|
||||
else
|
||||
raise MalformedPDFError, "Invalid filter algorithm #{filter}"
|
||||
end
|
||||
|
||||
s = []
|
||||
row_data.each_slice pixel_bytes do |slice|
|
||||
s << slice
|
||||
end
|
||||
pixels << s
|
||||
row += 1
|
||||
end
|
||||
|
||||
pixels.map { |bytes| bytes.flatten.pack("C*") }.join("")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
|
||||
require 'zlib'
|
||||
|
||||
class PDF::Reader
|
||||
module Filter # :nodoc:
|
||||
# implementation of the Flate (zlib) stream filter
|
||||
class Flate
|
||||
|
||||
ZLIB_AUTO_DETECT_ZLIB_OR_GZIP = 47 # Zlib::MAX_WBITS + 32
|
||||
ZLIB_RAW_DEFLATE = -15 # Zlib::MAX_WBITS * -1
|
||||
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
end
|
||||
|
||||
################################################################################
|
||||
# Decode the specified data with the Zlib compression algorithm
|
||||
def filter(data)
|
||||
deflated = zlib_inflate(data) || zlib_inflate(data[0, data.bytesize-1])
|
||||
|
||||
if deflated.nil?
|
||||
raise MalformedPDFError,
|
||||
"Error while inflating a compressed stream (no suitable inflation algorithm found)"
|
||||
end
|
||||
Depredict.new(@options).filter(deflated)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def zlib_inflate(data)
|
||||
begin
|
||||
return Zlib::Inflate.new(ZLIB_AUTO_DETECT_ZLIB_OR_GZIP).inflate(data)
|
||||
rescue Zlib::Error
|
||||
# by default, Ruby's Zlib assumes the data it's inflating
|
||||
# is RFC1951 deflated data, wrapped in a RFC1950 zlib container. If that
|
||||
# fails, swallow the exception and attempt to inflate the data as a raw
|
||||
# RFC1951 stream.
|
||||
end
|
||||
|
||||
begin
|
||||
return Zlib::Inflate.new(ZLIB_RAW_DEFLATE).inflate(data)
|
||||
rescue Zlib::Error
|
||||
# swallow this one too, so we can try some other fallback options
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
class PDF::Reader
|
||||
module Filter # :nodoc:
|
||||
# implementation of the LZW stream filter
|
||||
class Lzw
|
||||
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
end
|
||||
|
||||
################################################################################
|
||||
# Decode the specified data with the LZW compression algorithm
|
||||
def filter(data)
|
||||
data = PDF::Reader::LZW.decode(data)
|
||||
Depredict.new(@options).filter(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
module Filter # :nodoc:
|
||||
# implementation of the null stream filter
|
||||
class Null
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
end
|
||||
|
||||
def filter(data)
|
||||
data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
class PDF::Reader # :nodoc:
|
||||
module Filter # :nodoc:
|
||||
# implementation of the run length stream filter
|
||||
class RunLength
|
||||
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
end
|
||||
|
||||
################################################################################
|
||||
# Decode the specified data with the RunLengthDecode compression algorithm
|
||||
def filter(data)
|
||||
pos = 0
|
||||
out = "".dup
|
||||
|
||||
while pos < data.length
|
||||
length = data.getbyte(pos)
|
||||
pos += 1
|
||||
|
||||
unless length.nil?
|
||||
case
|
||||
# nothing
|
||||
when length == 128
|
||||
break
|
||||
when length < 128
|
||||
# When the length is < 128, we copy the following length+1 bytes
|
||||
# literally.
|
||||
out << data[pos, length + 1]
|
||||
pos += length
|
||||
else
|
||||
# When the length is > 128, we copy the next byte (257 - length)
|
||||
# times; i.e., "\xFA\x00" ([250, 0]) will expand to
|
||||
# "\x00\x00\x00\x00\x00\x00\x00".
|
||||
previous_byte = data[pos, 1] || ""
|
||||
out << previous_byte * (257 - length)
|
||||
end
|
||||
end
|
||||
|
||||
pos += 1
|
||||
end
|
||||
|
||||
Depredict.new(@options).filter(out)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,252 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2008 James Healy (jimmy@deefa.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
require 'pdf/reader/width_calculator'
|
||||
|
||||
class PDF::Reader
|
||||
# Represents a single font PDF object and provides some useful methods
|
||||
# for extracting info. Mainly used for converting text to UTF-8.
|
||||
#
|
||||
class Font
|
||||
attr_accessor :subtype, :encoding, :descendantfonts, :tounicode
|
||||
attr_reader :widths, :first_char, :last_char, :basefont, :font_descriptor,
|
||||
:cid_widths, :cid_default_width
|
||||
|
||||
def initialize(ohash, obj)
|
||||
@ohash = ohash
|
||||
@tounicode = nil
|
||||
|
||||
extract_base_info(obj)
|
||||
extract_type3_info(obj)
|
||||
extract_descriptor(obj)
|
||||
extract_descendants(obj)
|
||||
@width_calc = build_width_calculator
|
||||
|
||||
@encoding ||= PDF::Reader::Encoding.new(:StandardEncoding)
|
||||
end
|
||||
|
||||
def to_utf8(params)
|
||||
if @tounicode
|
||||
to_utf8_via_cmap(params)
|
||||
else
|
||||
to_utf8_via_encoding(params)
|
||||
end
|
||||
end
|
||||
|
||||
def unpack(data)
|
||||
data.unpack(encoding.unpack)
|
||||
end
|
||||
|
||||
# looks up the specified codepoint and returns a value that is in (pdf)
|
||||
# glyph space, which is 1000 glyph units = 1 text space unit
|
||||
def glyph_width(code_point)
|
||||
if code_point.is_a?(String)
|
||||
code_point = code_point.unpack(encoding.unpack).first
|
||||
end
|
||||
|
||||
@cached_widths ||= {}
|
||||
@cached_widths[code_point] ||= @width_calc.glyph_width(code_point)
|
||||
end
|
||||
|
||||
# In most cases glyph width is converted into text space with a simple divide by 1000.
|
||||
#
|
||||
# However, Type3 fonts provide their own FontMatrix that's used for the transformation.
|
||||
#
|
||||
def glyph_width_in_text_space(code_point)
|
||||
glyph_width_in_glyph_space = glyph_width(code_point)
|
||||
|
||||
if @subtype == :Type3
|
||||
x1, _y1 = font_matrix_transform(0,0)
|
||||
x2, _y2 = font_matrix_transform(glyph_width_in_glyph_space, 0)
|
||||
(x2 - x1).abs.round(2)
|
||||
else
|
||||
glyph_width_in_glyph_space / 1000.0
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Only valid for Type3 fonts
|
||||
def font_matrix_transform(x, y)
|
||||
return x, y if @font_matrix.nil?
|
||||
|
||||
matrix = TransformationMatrix.new(
|
||||
@font_matrix[0], @font_matrix[1],
|
||||
@font_matrix[2], @font_matrix[3],
|
||||
@font_matrix[4], @font_matrix[5],
|
||||
)
|
||||
|
||||
if x == 0 && y == 0
|
||||
[matrix.e, matrix.f]
|
||||
else
|
||||
[
|
||||
(matrix.a * x) + (matrix.c * y) + (matrix.e),
|
||||
(matrix.b * x) + (matrix.d * y) + (matrix.f)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def default_encoding(font_name)
|
||||
case font_name.to_s
|
||||
when "Symbol" then
|
||||
PDF::Reader::Encoding.new(:SymbolEncoding)
|
||||
when "ZapfDingbats" then
|
||||
PDF::Reader::Encoding.new(:ZapfDingbatsEncoding)
|
||||
else
|
||||
PDF::Reader::Encoding.new(:StandardEncoding)
|
||||
end
|
||||
end
|
||||
|
||||
def build_width_calculator
|
||||
if @subtype == :Type0
|
||||
PDF::Reader::WidthCalculator::TypeZero.new(self)
|
||||
elsif @subtype == :Type1
|
||||
if @font_descriptor.nil?
|
||||
PDF::Reader::WidthCalculator::BuiltIn.new(self)
|
||||
else
|
||||
PDF::Reader::WidthCalculator::TypeOneOrThree .new(self)
|
||||
end
|
||||
elsif @subtype == :Type3
|
||||
PDF::Reader::WidthCalculator::TypeOneOrThree.new(self)
|
||||
elsif @subtype == :TrueType
|
||||
if @font_descriptor
|
||||
PDF::Reader::WidthCalculator::TrueType.new(self)
|
||||
else
|
||||
# A TrueType font that isn't embedded. Most readers look for a version on the
|
||||
# local system and fallback to a substitute. For now, we go straight to a substitute
|
||||
PDF::Reader::WidthCalculator::BuiltIn.new(self)
|
||||
end
|
||||
elsif @subtype == :CIDFontType0 || @subtype == :CIDFontType2
|
||||
PDF::Reader::WidthCalculator::Composite.new(self)
|
||||
else
|
||||
PDF::Reader::WidthCalculator::TypeOneOrThree.new(self)
|
||||
end
|
||||
end
|
||||
|
||||
def build_encoding(obj)
|
||||
if obj[:Encoding].is_a?(Symbol)
|
||||
# one of the standard encodings, referenced by name
|
||||
# TODO pass in a standard shape, always a Hash
|
||||
PDF::Reader::Encoding.new(obj[:Encoding])
|
||||
elsif obj[:Encoding].is_a?(Hash) || obj[:Encoding].is_a?(PDF::Reader::Stream)
|
||||
PDF::Reader::Encoding.new(obj[:Encoding])
|
||||
elsif obj[:Encoding].nil?
|
||||
default_encoding(@basefont)
|
||||
else
|
||||
raise MalformedPDFError, "Unexpected type for Encoding (#{obj[:Encoding].class})"
|
||||
end
|
||||
end
|
||||
|
||||
def extract_base_info(obj)
|
||||
@subtype = @ohash.deref_name(obj[:Subtype])
|
||||
@basefont = @ohash.deref_name(obj[:BaseFont])
|
||||
@encoding = build_encoding(obj)
|
||||
@widths = @ohash.deref_array_of_numbers(obj[:Widths]) || []
|
||||
@first_char = @ohash.deref_integer(obj[:FirstChar])
|
||||
@last_char = @ohash.deref_integer(obj[:LastChar])
|
||||
|
||||
# CID Fonts are not required to have a W or DW entry, if they don't exist,
|
||||
# the default cid width = 1000, see Section 9.7.4.1 PDF 32000-1:2008 pp 269
|
||||
@cid_widths = @ohash.deref_array(obj[:W]) || []
|
||||
@cid_default_width = @ohash.deref_number(obj[:DW]) || 1000
|
||||
|
||||
if obj[:ToUnicode]
|
||||
# ToUnicode is optional for Type1 and Type3
|
||||
stream = @ohash.deref_stream(obj[:ToUnicode])
|
||||
if stream
|
||||
@tounicode = PDF::Reader::CMap.new(stream.unfiltered_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def extract_type3_info(obj)
|
||||
if @subtype == :Type3
|
||||
@font_matrix = @ohash.deref_array_of_numbers(obj[:FontMatrix]) || [
|
||||
0.001, 0, 0, 0.001, 0, 0
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def extract_descriptor(obj)
|
||||
if obj[:FontDescriptor]
|
||||
# create a font descriptor object if we can, in other words, unless this is
|
||||
# a CID Font
|
||||
fd = @ohash.deref_hash(obj[:FontDescriptor])
|
||||
@font_descriptor = PDF::Reader::FontDescriptor.new(@ohash, fd)
|
||||
else
|
||||
@font_descriptor = nil
|
||||
end
|
||||
end
|
||||
|
||||
def extract_descendants(obj)
|
||||
# per PDF 32000-1:2008 pp. 280 :DescendentFonts is:
|
||||
# A one-element array specifying the CIDFont dictionary that is the
|
||||
# descendant of this Type 0 font.
|
||||
if obj[:DescendantFonts]
|
||||
descendants = @ohash.deref_array(obj[:DescendantFonts])
|
||||
@descendantfonts = descendants.map { |desc|
|
||||
PDF::Reader::Font.new(@ohash, @ohash.deref_hash(desc))
|
||||
}
|
||||
else
|
||||
@descendantfonts = []
|
||||
end
|
||||
end
|
||||
|
||||
def to_utf8_via_cmap(params)
|
||||
case params
|
||||
when Integer
|
||||
[
|
||||
@tounicode.decode(params) || PDF::Reader::Encoding::UNKNOWN_CHAR
|
||||
].flatten.pack("U*")
|
||||
when String
|
||||
params.unpack(encoding.unpack).map { |c|
|
||||
@tounicode.decode(c) || PDF::Reader::Encoding::UNKNOWN_CHAR
|
||||
}.flatten.pack("U*")
|
||||
when Array
|
||||
params.collect { |param| to_utf8_via_cmap(param) }.join("")
|
||||
end
|
||||
end
|
||||
|
||||
def to_utf8_via_encoding(params)
|
||||
if encoding.kind_of?(String)
|
||||
raise UnsupportedFeatureError, "font encoding '#{encoding}' currently unsupported"
|
||||
end
|
||||
|
||||
case params
|
||||
when Integer
|
||||
encoding.int_to_utf8_string(params)
|
||||
when String
|
||||
encoding.to_utf8(params)
|
||||
when Array
|
||||
params.collect { |param| to_utf8_via_encoding(param) }.join("")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'ttfunk'
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# Font descriptors are outlined in Section 9.8, PDF 32000-1:2008, pp 281-288
|
||||
class FontDescriptor
|
||||
|
||||
attr_reader :font_name, :font_family, :font_stretch, :font_weight,
|
||||
:font_bounding_box, :cap_height, :ascent, :descent, :leading,
|
||||
:avg_width, :max_width, :missing_width, :italic_angle, :stem_v,
|
||||
:x_height, :font_flags
|
||||
|
||||
def initialize(ohash, fd_hash)
|
||||
# TODO change these to typed derefs
|
||||
@ascent = ohash.deref_number(fd_hash[:Ascent]) || 0
|
||||
@descent = ohash.deref_number(fd_hash[:Descent]) || 0
|
||||
@missing_width = ohash.deref_number(fd_hash[:MissingWidth]) || 0
|
||||
@font_bounding_box = ohash.deref_array_of_numbers(fd_hash[:FontBBox]) || [0,0,0,0]
|
||||
@avg_width = ohash.deref_number(fd_hash[:AvgWidth]) || 0
|
||||
@cap_height = ohash.deref_number(fd_hash[:CapHeight]) || 0
|
||||
@font_flags = ohash.deref_integer(fd_hash[:Flags]) || 0
|
||||
@italic_angle = ohash.deref_number(fd_hash[:ItalicAngle])
|
||||
@font_name = ohash.deref_name(fd_hash[:FontName]).to_s
|
||||
@leading = ohash.deref_number(fd_hash[:Leading]) || 0
|
||||
@max_width = ohash.deref_number(fd_hash[:MaxWidth]) || 0
|
||||
@stem_v = ohash.deref_number(fd_hash[:StemV])
|
||||
@x_height = ohash.deref_number(fd_hash[:XHeight])
|
||||
@font_stretch = ohash.deref_name(fd_hash[:FontStretch]) || :Normal
|
||||
@font_weight = ohash.deref_number(fd_hash[:FontWeight]) || 400
|
||||
@font_family = ohash.deref_string(fd_hash[:FontFamily])
|
||||
|
||||
# A FontDescriptor may have an embedded font program in FontFile
|
||||
# (Type 1 Font Program), FontFile2 (TrueType font program), or
|
||||
# FontFile3 (Other font program as defined by Subtype entry)
|
||||
# Subtype entries:
|
||||
# 1) Type1C: Type 1 Font Program in Compact Font Format
|
||||
# 2) CIDFontType0C: Type 0 Font Program in Compact Font Format
|
||||
# 3) OpenType: OpenType Font Program
|
||||
# see Section 9.9, PDF 32000-1:2008, pp 288-292
|
||||
@font_program_stream = ohash.deref_stream(fd_hash[:FontFile2])
|
||||
#TODO handle FontFile and FontFile3
|
||||
|
||||
@is_ttf = true if @font_program_stream
|
||||
end
|
||||
|
||||
def glyph_width(char_code)
|
||||
if @is_ttf
|
||||
if ttf_program_stream.cmap.unicode.length > 0
|
||||
glyph_id = ttf_program_stream.cmap.unicode.first[char_code]
|
||||
else
|
||||
glyph_id = char_code
|
||||
end
|
||||
char_metric = ttf_program_stream.horizontal_metrics.metrics[glyph_id]
|
||||
if char_metric
|
||||
char_metric.advance_width
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PDF states that a glyph is 1000 units wide, true type doesn't enforce
|
||||
# any behavior, but uses units/em to define how wide the 'M' is (the widest letter)
|
||||
def glyph_to_pdf_scale_factor
|
||||
if @is_ttf
|
||||
@glyph_to_pdf_sf ||= (1.0 / ttf_program_stream.header.units_per_em) * 1000.0
|
||||
else
|
||||
@glyph_to_pdf_sf ||= 1.0
|
||||
end
|
||||
@glyph_to_pdf_sf
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ttf_program_stream
|
||||
@ttf_program_stream ||= TTFunk::File.new(@font_program_stream.unfiltered_data)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,121 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'digest/md5'
|
||||
|
||||
module PDF
|
||||
class Reader
|
||||
|
||||
# High level representation of a single PDF form xobject. Form xobjects
|
||||
# are contained pieces of content that can be inserted onto multiple
|
||||
# pages. They're generally used as a space efficient way to store
|
||||
# repetative content (like logos, header, footers, etc).
|
||||
#
|
||||
# This behaves and looks much like a limited PDF::Reader::Page class.
|
||||
#
|
||||
class FormXObject
|
||||
extend Forwardable
|
||||
|
||||
attr_reader :xobject
|
||||
|
||||
def_delegators :resources, :color_spaces
|
||||
def_delegators :resources, :fonts
|
||||
def_delegators :resources, :graphic_states
|
||||
def_delegators :resources, :patterns
|
||||
def_delegators :resources, :procedure_sets
|
||||
def_delegators :resources, :properties
|
||||
def_delegators :resources, :shadings
|
||||
def_delegators :resources, :xobjects
|
||||
|
||||
def initialize(page, xobject, options = {})
|
||||
@page = page
|
||||
@objects = page.objects
|
||||
@cache = options[:cache] || {}
|
||||
@xobject = @objects.deref_stream(xobject)
|
||||
end
|
||||
|
||||
# return a hash of fonts used on this form.
|
||||
#
|
||||
# The keys are the font labels used within the form content stream.
|
||||
#
|
||||
# The values are a PDF::Reader::Font instances that provide access
|
||||
# to most available metrics for each font.
|
||||
#
|
||||
def font_objects
|
||||
raw_fonts = @objects.deref_hash(fonts)
|
||||
::Hash[raw_fonts.map { |label, font|
|
||||
[label, PDF::Reader::Font.new(@objects, @objects.deref_hash(font) || {})]
|
||||
}]
|
||||
end
|
||||
|
||||
# processes the raw content stream for this form in sequential order and
|
||||
# passes callbacks to the receiver objects.
|
||||
#
|
||||
# See the comments on PDF::Reader::Page#walk for more detail.
|
||||
#
|
||||
def walk(*receivers)
|
||||
receivers = receivers.map { |receiver|
|
||||
ValidatingReceiver.new(receiver)
|
||||
}
|
||||
content_stream(receivers, raw_content)
|
||||
end
|
||||
|
||||
# returns the raw content stream for this page. This is plumbing, nothing to
|
||||
# see here unless you're a PDF nerd like me.
|
||||
#
|
||||
def raw_content
|
||||
@xobject.unfiltered_data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns the resources that accompany this form.
|
||||
#
|
||||
def resources
|
||||
@resources ||= Resources.new(@objects, @objects.deref_hash(@xobject.hash[:Resources]) || {})
|
||||
end
|
||||
|
||||
def callback(receivers, name, params=[])
|
||||
receivers.each do |receiver|
|
||||
receiver.send(name, *params) if receiver.respond_to?(name)
|
||||
end
|
||||
end
|
||||
|
||||
def content_stream_md5
|
||||
@content_stream_md5 ||= Digest::MD5.hexdigest(raw_content)
|
||||
end
|
||||
|
||||
def cached_tokens_key
|
||||
@cached_tokens_key ||= "tokens-#{content_stream_md5}"
|
||||
end
|
||||
|
||||
def tokens
|
||||
@cache[cached_tokens_key] ||= begin
|
||||
buffer = Buffer.new(StringIO.new(raw_content), :content_stream => true)
|
||||
parser = Parser.new(buffer, @objects)
|
||||
result = []
|
||||
while (token = parser.parse_token(PagesStrategy::OPERATORS))
|
||||
result << token
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def content_stream(receivers, instructions)
|
||||
params = []
|
||||
|
||||
tokens.each do |token|
|
||||
if token.kind_of?(Token) and PagesStrategy::OPERATORS.has_key?(token)
|
||||
callback(receivers, PagesStrategy::OPERATORS[token], params)
|
||||
params.clear
|
||||
else
|
||||
params << token
|
||||
end
|
||||
end
|
||||
rescue EOFError
|
||||
raise MalformedPDFError, "End Of File while processing a content stream"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,142 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2011 James Healy (jimmy@deefa.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
# A Hash-like object that can convert glyph names into a unicode codepoint.
|
||||
# The mapping is read from a data file on disk the first time it's needed.
|
||||
#
|
||||
class GlyphHash # :nodoc:
|
||||
def initialize
|
||||
@@by_codepoint_cache ||= nil
|
||||
@@by_name_cache ||= nil
|
||||
|
||||
# only parse the glyph list once, and cache the results (for performance)
|
||||
if @@by_codepoint_cache != nil && @@by_name_cache != nil
|
||||
@by_name = @@by_name_cache
|
||||
@by_codepoint = @@by_codepoint_cache
|
||||
else
|
||||
by_name, by_codepoint = load_adobe_glyph_mapping
|
||||
@by_name = @@by_name_cache ||= by_name
|
||||
@by_codepoint = @@by_codepoint_cache ||= by_codepoint
|
||||
end
|
||||
end
|
||||
|
||||
# attempt to convert a PDF Name to a unicode codepoint. Returns nil
|
||||
# if no conversion is possible.
|
||||
#
|
||||
# h = GlyphHash.new
|
||||
#
|
||||
# h.name_to_unicode(:A)
|
||||
# => 65
|
||||
#
|
||||
# h.name_to_unicode(:Euro)
|
||||
# => 8364
|
||||
#
|
||||
# h.name_to_unicode(:X4A)
|
||||
# => 74
|
||||
#
|
||||
# h.name_to_unicode(:G30)
|
||||
# => 48
|
||||
#
|
||||
# h.name_to_unicode(:34)
|
||||
# => 34
|
||||
#
|
||||
def name_to_unicode(name)
|
||||
return nil unless name.is_a?(Symbol)
|
||||
|
||||
name = name.to_s.gsub('_', '').intern
|
||||
str = name.to_s
|
||||
|
||||
if @by_name.has_key?(name)
|
||||
@by_name[name]
|
||||
elsif str.match(/\AX[0-9a-fA-F]{2,4}\Z/)
|
||||
"0x#{str[1,4]}".hex
|
||||
elsif str.match(/\Auni[A-F\d]{4}\Z/)
|
||||
"0x#{str[3,4]}".hex
|
||||
elsif str.match(/\Au[A-F\d]{4,6}\Z/)
|
||||
"0x#{str[1,6]}".hex
|
||||
elsif str.match(/\A[A-Za-z]\d{1,5}\Z/)
|
||||
str[1,5].to_i
|
||||
elsif str.match(/\A[A-Za-z]{2}\d{2,5}\Z/)
|
||||
str[2,5].to_i
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# attempt to convert a Unicode code point to the equivilant PDF Name. Returns nil
|
||||
# if no conversion is possible.
|
||||
#
|
||||
# h = GlyphHash.new
|
||||
#
|
||||
# h.unicode_to_name(65)
|
||||
# => [:A]
|
||||
#
|
||||
# h.unicode_to_name(8364)
|
||||
# => [:Euro]
|
||||
#
|
||||
# h.unicode_to_name(34)
|
||||
# => [:34]
|
||||
#
|
||||
def unicode_to_name(codepoint)
|
||||
@by_codepoint[codepoint.to_i] || []
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# returns a hash that maps glyph names to unicode codepoints. The mapping is based on
|
||||
# a text file supplied by Adobe at:
|
||||
# https://github.com/adobe-type-tools/agl-aglfn
|
||||
def load_adobe_glyph_mapping
|
||||
keyed_by_name = {}
|
||||
keyed_by_codepoint = {}
|
||||
|
||||
paths = [
|
||||
File.dirname(__FILE__) + "/glyphlist.txt",
|
||||
File.dirname(__FILE__) + "/glyphlist-zapfdingbats.txt",
|
||||
]
|
||||
paths.each do |path|
|
||||
File.open(path, "r:BINARY") do |f|
|
||||
f.each do |l|
|
||||
_m, name, code = *l.match(/([0-9A-Za-z]+);([0-9A-F]{4})/)
|
||||
if name && code
|
||||
cp = "0x#{code}".hex
|
||||
keyed_by_name[name.to_sym] = cp
|
||||
keyed_by_codepoint[cp] ||= []
|
||||
keyed_by_codepoint[cp] << name.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return keyed_by_name.freeze, keyed_by_codepoint.freeze
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,245 @@
|
||||
# -----------------------------------------------------------
|
||||
# Copyright 2002-2019 Adobe (http://www.adobe.com/).
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that the
|
||||
# following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials
|
||||
# provided with the distribution.
|
||||
#
|
||||
# Neither the name of Adobe nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this
|
||||
# software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
# -----------------------------------------------------------
|
||||
# Name: ITC Zapf Dingbats Glyph List
|
||||
# Table version: 2.0
|
||||
# Date: September 20, 2002
|
||||
# URL: https://github.com/adobe-type-tools/agl-aglfn
|
||||
#
|
||||
# Format: two semicolon-delimited fields:
|
||||
# (1) glyph name--upper/lowercase letters and digits
|
||||
# (2) Unicode scalar value--four uppercase hexadecimal digits
|
||||
#
|
||||
a100;275E
|
||||
a101;2761
|
||||
a102;2762
|
||||
a103;2763
|
||||
a104;2764
|
||||
a105;2710
|
||||
a106;2765
|
||||
a107;2766
|
||||
a108;2767
|
||||
a109;2660
|
||||
a10;2721
|
||||
a110;2665
|
||||
a111;2666
|
||||
a112;2663
|
||||
a117;2709
|
||||
a118;2708
|
||||
a119;2707
|
||||
a11;261B
|
||||
a120;2460
|
||||
a121;2461
|
||||
a122;2462
|
||||
a123;2463
|
||||
a124;2464
|
||||
a125;2465
|
||||
a126;2466
|
||||
a127;2467
|
||||
a128;2468
|
||||
a129;2469
|
||||
a12;261E
|
||||
a130;2776
|
||||
a131;2777
|
||||
a132;2778
|
||||
a133;2779
|
||||
a134;277A
|
||||
a135;277B
|
||||
a136;277C
|
||||
a137;277D
|
||||
a138;277E
|
||||
a139;277F
|
||||
a13;270C
|
||||
a140;2780
|
||||
a141;2781
|
||||
a142;2782
|
||||
a143;2783
|
||||
a144;2784
|
||||
a145;2785
|
||||
a146;2786
|
||||
a147;2787
|
||||
a148;2788
|
||||
a149;2789
|
||||
a14;270D
|
||||
a150;278A
|
||||
a151;278B
|
||||
a152;278C
|
||||
a153;278D
|
||||
a154;278E
|
||||
a155;278F
|
||||
a156;2790
|
||||
a157;2791
|
||||
a158;2792
|
||||
a159;2793
|
||||
a15;270E
|
||||
a160;2794
|
||||
a161;2192
|
||||
a162;27A3
|
||||
a163;2194
|
||||
a164;2195
|
||||
a165;2799
|
||||
a166;279B
|
||||
a167;279C
|
||||
a168;279D
|
||||
a169;279E
|
||||
a16;270F
|
||||
a170;279F
|
||||
a171;27A0
|
||||
a172;27A1
|
||||
a173;27A2
|
||||
a174;27A4
|
||||
a175;27A5
|
||||
a176;27A6
|
||||
a177;27A7
|
||||
a178;27A8
|
||||
a179;27A9
|
||||
a17;2711
|
||||
a180;27AB
|
||||
a181;27AD
|
||||
a182;27AF
|
||||
a183;27B2
|
||||
a184;27B3
|
||||
a185;27B5
|
||||
a186;27B8
|
||||
a187;27BA
|
||||
a188;27BB
|
||||
a189;27BC
|
||||
a18;2712
|
||||
a190;27BD
|
||||
a191;27BE
|
||||
a192;279A
|
||||
a193;27AA
|
||||
a194;27B6
|
||||
a195;27B9
|
||||
a196;2798
|
||||
a197;27B4
|
||||
a198;27B7
|
||||
a199;27AC
|
||||
a19;2713
|
||||
a1;2701
|
||||
a200;27AE
|
||||
a201;27B1
|
||||
a202;2703
|
||||
a203;2750
|
||||
a204;2752
|
||||
a205;276E
|
||||
a206;2770
|
||||
a20;2714
|
||||
a21;2715
|
||||
a22;2716
|
||||
a23;2717
|
||||
a24;2718
|
||||
a25;2719
|
||||
a26;271A
|
||||
a27;271B
|
||||
a28;271C
|
||||
a29;2722
|
||||
a2;2702
|
||||
a30;2723
|
||||
a31;2724
|
||||
a32;2725
|
||||
a33;2726
|
||||
a34;2727
|
||||
a35;2605
|
||||
a36;2729
|
||||
a37;272A
|
||||
a38;272B
|
||||
a39;272C
|
||||
a3;2704
|
||||
a40;272D
|
||||
a41;272E
|
||||
a42;272F
|
||||
a43;2730
|
||||
a44;2731
|
||||
a45;2732
|
||||
a46;2733
|
||||
a47;2734
|
||||
a48;2735
|
||||
a49;2736
|
||||
a4;260E
|
||||
a50;2737
|
||||
a51;2738
|
||||
a52;2739
|
||||
a53;273A
|
||||
a54;273B
|
||||
a55;273C
|
||||
a56;273D
|
||||
a57;273E
|
||||
a58;273F
|
||||
a59;2740
|
||||
a5;2706
|
||||
a60;2741
|
||||
a61;2742
|
||||
a62;2743
|
||||
a63;2744
|
||||
a64;2745
|
||||
a65;2746
|
||||
a66;2747
|
||||
a67;2748
|
||||
a68;2749
|
||||
a69;274A
|
||||
a6;271D
|
||||
a70;274B
|
||||
a71;25CF
|
||||
a72;274D
|
||||
a73;25A0
|
||||
a74;274F
|
||||
a75;2751
|
||||
a76;25B2
|
||||
a77;25BC
|
||||
a78;25C6
|
||||
a79;2756
|
||||
a7;271E
|
||||
a81;25D7
|
||||
a82;2758
|
||||
a83;2759
|
||||
a84;275A
|
||||
a85;276F
|
||||
a86;2771
|
||||
a87;2772
|
||||
a88;2773
|
||||
a89;2768
|
||||
a8;271F
|
||||
a90;2769
|
||||
a91;276C
|
||||
a92;276D
|
||||
a93;276A
|
||||
a94;276B
|
||||
a95;2774
|
||||
a96;2775
|
||||
a97;275B
|
||||
a98;275C
|
||||
a99;275D
|
||||
a9;2720
|
||||
# END
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,138 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'digest/md5'
|
||||
require 'rc4'
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# Processes the Encrypt dict from an encrypted PDF and a user provided
|
||||
# password and returns a key that can decrypt the file.
|
||||
#
|
||||
# This can generate a decryption key compatible with the following standard encryption algorithms:
|
||||
#
|
||||
# * Version 5 (AESV3)
|
||||
#
|
||||
class KeyBuilderV5
|
||||
|
||||
def initialize(opts = {})
|
||||
@key_length = 256
|
||||
|
||||
# hash(32B) + validation salt(8B) + key salt(8B)
|
||||
@owner_key = opts[:owner_key] || ""
|
||||
|
||||
# hash(32B) + validation salt(8B) + key salt(8B)
|
||||
@user_key = opts[:user_key] || ""
|
||||
|
||||
# decryption key, encrypted w/ owner password
|
||||
@owner_encryption_key = opts[:owner_encryption_key] || ""
|
||||
|
||||
# decryption key, encrypted w/ user password
|
||||
@user_encryption_key = opts[:user_encryption_key] || ""
|
||||
end
|
||||
|
||||
# Takes a string containing a user provided password.
|
||||
#
|
||||
# If the password matches the file, then a string containing a key suitable for
|
||||
# decrypting the file will be returned. If the password doesn't match the file,
|
||||
# and exception will be raised.
|
||||
#
|
||||
def key(pass)
|
||||
pass = pass.byteslice(0...127).to_s # UTF-8 encoded password. first 127 bytes
|
||||
|
||||
encrypt_key = auth_owner_pass(pass)
|
||||
encrypt_key ||= auth_user_pass(pass)
|
||||
encrypt_key ||= auth_owner_pass_r6(pass)
|
||||
encrypt_key ||= auth_user_pass_r6(pass)
|
||||
|
||||
raise PDF::Reader::EncryptedPDFError, "Invalid password (#{pass})" if encrypt_key.nil?
|
||||
encrypt_key
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Algorithm 3.2a - Computing an encryption key
|
||||
#
|
||||
# Defined in PDF 1.7 Extension Level 3
|
||||
#
|
||||
# if the string is a valid user/owner password, this will return the decryption key
|
||||
#
|
||||
def auth_owner_pass(password)
|
||||
if Digest::SHA256.digest(password + @owner_key[32..39] + @user_key) == @owner_key[0..31]
|
||||
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
||||
cipher.decrypt
|
||||
cipher.key = Digest::SHA256.digest(password + @owner_key[40..-1] + @user_key)
|
||||
cipher.iv = "\x00" * 16
|
||||
cipher.padding = 0
|
||||
cipher.update(@owner_encryption_key) + cipher.final
|
||||
end
|
||||
end
|
||||
|
||||
def auth_user_pass(password)
|
||||
if Digest::SHA256.digest(password + @user_key[32..39]) == @user_key[0..31]
|
||||
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
||||
cipher.decrypt
|
||||
cipher.key = Digest::SHA256.digest(password + @user_key[40..-1])
|
||||
cipher.iv = "\x00" * 16
|
||||
cipher.padding = 0
|
||||
cipher.update(@user_encryption_key) + cipher.final
|
||||
end
|
||||
end
|
||||
|
||||
def auth_owner_pass_r6(password)
|
||||
if r6_digest(password, @owner_key[32..39].to_s, @user_key[0,48].to_s) == @owner_key[0..31]
|
||||
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
||||
cipher.decrypt
|
||||
cipher.key = r6_digest(password, @owner_key[40,8].to_s, @user_key[0, 48].to_s)
|
||||
cipher.iv = "\x00" * 16
|
||||
cipher.padding = 0
|
||||
cipher.update(@owner_encryption_key) + cipher.final
|
||||
end
|
||||
end
|
||||
|
||||
def auth_user_pass_r6(password)
|
||||
if r6_digest(password, @user_key[32..39].to_s) == @user_key[0..31]
|
||||
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
||||
cipher.decrypt
|
||||
cipher.key = r6_digest(password, @user_key[40,8].to_s)
|
||||
cipher.iv = "\x00" * 16
|
||||
cipher.padding = 0
|
||||
cipher.update(@user_encryption_key) + cipher.final
|
||||
end
|
||||
end
|
||||
|
||||
# PDF 2.0 spec, 7.6.4.3.4
|
||||
# Algorithm 2.B: Computing a hash (revision 6 and later)
|
||||
def r6_digest(password, salt, user_key = '')
|
||||
k = Digest::SHA256.digest(password + salt + user_key)
|
||||
e = ''
|
||||
|
||||
i = 0
|
||||
while i < 64 or e.getbyte(-1).to_i > i - 32
|
||||
k1 = (password + k + user_key) * 64
|
||||
|
||||
aes = OpenSSL::Cipher.new("aes-128-cbc").encrypt
|
||||
aes.key = k[0, 16].to_s
|
||||
aes.iv = k[16, 16].to_s
|
||||
aes.padding = 0
|
||||
e = String.new(aes.update(k1))
|
||||
k = case unpack_128bit_bigendian_int(e) % 3
|
||||
when 0 then Digest::SHA256.digest(e)
|
||||
when 1 then Digest::SHA384.digest(e)
|
||||
when 2 then Digest::SHA512.digest(e)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
k[0, 32].to_s
|
||||
end
|
||||
|
||||
def unpack_128bit_bigendian_int(str)
|
||||
ints = str[0,16].to_s.unpack("N*")
|
||||
(ints[0].to_i << 96) + (ints[1].to_i << 64) + (ints[2].to_i << 32) + ints[3].to_i
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module PDF
|
||||
|
||||
class Reader
|
||||
|
||||
# A general class for decoding LZW compressed data. LZW can be
|
||||
# used in PDF files to compresses streams, usually for image data sourced
|
||||
# from a TIFF file.
|
||||
#
|
||||
# See the following links for more information:
|
||||
#
|
||||
# ref http://www.fileformat.info/format/tiff/corion-lzw.htm
|
||||
# ref http://marknelson.us/1989/10/01/lzw-data-compression/
|
||||
#
|
||||
# The PDF spec also has some data on the algorithm.
|
||||
#
|
||||
class LZW # :nodoc:
|
||||
|
||||
# Wraps an LZW encoded string
|
||||
class BitStream # :nodoc:
|
||||
|
||||
def initialize(data, bits_in_chunk)
|
||||
@data = data
|
||||
@data.force_encoding("BINARY")
|
||||
set_bits_in_chunk(bits_in_chunk)
|
||||
@current_pos = 0
|
||||
@bits_left_in_byte = 8
|
||||
end
|
||||
|
||||
def set_bits_in_chunk(bits_in_chunk)
|
||||
raise MalformedPDFError, "invalid LZW bits" if bits_in_chunk < 9 || bits_in_chunk > 12
|
||||
|
||||
@bits_in_chunk = bits_in_chunk
|
||||
end
|
||||
|
||||
def read
|
||||
bits_left_in_chunk = @bits_in_chunk
|
||||
chunk = -1
|
||||
while bits_left_in_chunk > 0 and @current_pos < @data.size
|
||||
chunk = 0 if chunk < 0
|
||||
codepoint = @data[@current_pos, 1].to_s.unpack("C*")[0].to_i
|
||||
current_byte = codepoint & (2**@bits_left_in_byte - 1).to_i #clear consumed bits
|
||||
dif = bits_left_in_chunk - @bits_left_in_byte
|
||||
if dif > 0 then current_byte <<= dif
|
||||
elsif dif < 0 then current_byte >>= dif.abs
|
||||
end
|
||||
chunk |= current_byte #add bits to result
|
||||
bits_left_in_chunk = if dif >= 0 then dif else 0 end
|
||||
@bits_left_in_byte = if dif < 0 then dif.abs else 0 end
|
||||
if @bits_left_in_byte.zero? #next byte
|
||||
@current_pos += 1
|
||||
@bits_left_in_byte = 8
|
||||
end
|
||||
end
|
||||
chunk
|
||||
end
|
||||
end
|
||||
|
||||
CODE_EOD = 257 #end of data
|
||||
CODE_CLEAR_TABLE = 256 #clear table
|
||||
|
||||
# stores de pairs code => string
|
||||
class StringTable
|
||||
attr_reader :string_table_pos
|
||||
|
||||
def initialize
|
||||
@data = Hash.new
|
||||
@string_table_pos = 258 #initial code
|
||||
end
|
||||
|
||||
#if code less than 258 return fixed string
|
||||
def [](key)
|
||||
if key > 257
|
||||
@data[key]
|
||||
else
|
||||
key.chr
|
||||
end
|
||||
end
|
||||
|
||||
def add(string)
|
||||
@data.store(@string_table_pos, string)
|
||||
@string_table_pos += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Decompresses a LZW compressed string.
|
||||
#
|
||||
def self.decode(data)
|
||||
stream = BitStream.new(data.to_s, 9) # size of codes between 9 and 12 bits
|
||||
string_table = StringTable.new
|
||||
result = "".dup
|
||||
until (code = stream.read) == CODE_EOD
|
||||
if code == CODE_CLEAR_TABLE
|
||||
stream.set_bits_in_chunk(9)
|
||||
string_table = StringTable.new
|
||||
code = stream.read
|
||||
break if code == CODE_EOD
|
||||
result << string_table[code]
|
||||
old_code = code
|
||||
else
|
||||
string = string_table[code]
|
||||
if string
|
||||
result << string
|
||||
string_table.add create_new_string(string_table, old_code, code)
|
||||
old_code = code
|
||||
else
|
||||
new_string = create_new_string(string_table, old_code, old_code)
|
||||
result << new_string
|
||||
string_table.add new_string
|
||||
old_code = code
|
||||
end
|
||||
#increase de size of the codes when limit reached
|
||||
if string_table.string_table_pos == 511
|
||||
stream.set_bits_in_chunk(10)
|
||||
elsif string_table.string_table_pos == 1023
|
||||
stream.set_bits_in_chunk(11)
|
||||
elsif string_table.string_table_pos == 2047
|
||||
stream.set_bits_in_chunk(12)
|
||||
end
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def self.create_new_string(string_table, some_code, other_code)
|
||||
raise MalformedPDFError, "invalid LZW data" if some_code.nil? || other_code.nil?
|
||||
|
||||
item_one = string_table[some_code]
|
||||
item_two = string_table[other_code]
|
||||
|
||||
if item_one && item_two
|
||||
item_one + item_two.chr
|
||||
else
|
||||
raise MalformedPDFError, "invalid LZW data"
|
||||
end
|
||||
end
|
||||
private_class_method :create_new_string
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
# There's no point rendering zero-width characters
|
||||
class NoTextFilter
|
||||
|
||||
def self.exclude_empty_strings(runs)
|
||||
runs.reject { |run| run.text.to_s.size == 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# A null object security handler. Used when a PDF is unencrypted.
|
||||
class NullSecurityHandler
|
||||
|
||||
def decrypt(buf, _ref)
|
||||
buf
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,110 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'hashery/lru_hash'
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# A Hash-like object for caching commonly used objects from a PDF file.
|
||||
#
|
||||
# This is an internal class, no promises about a stable API.
|
||||
#
|
||||
class ObjectCache # nodoc
|
||||
|
||||
# These object types use little memory and are accessed a heap of times as
|
||||
# part of random page access, so we'll cache the unmarshalled objects and
|
||||
# avoid lots of repetitive (and expensive) tokenising
|
||||
CACHEABLE_TYPES = [:Catalog, :Page, :Pages]
|
||||
|
||||
attr_reader :hits, :misses
|
||||
|
||||
def initialize(lru_size = 1000)
|
||||
@objects = {}
|
||||
@lru_cache = Hashery::LRUHash.new(lru_size.to_i)
|
||||
@hits = 0
|
||||
@misses = 0
|
||||
end
|
||||
|
||||
def [](key)
|
||||
update_stats(key)
|
||||
@objects[key] || @lru_cache[key]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
if cacheable?(value)
|
||||
@objects[key] = value
|
||||
else
|
||||
@lru_cache[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(key, local_default = nil)
|
||||
update_stats(key)
|
||||
@objects[key] || @lru_cache.fetch(key, local_default)
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
@objects.each(&block)
|
||||
@lru_cache.each(&block)
|
||||
end
|
||||
alias :each_pair :each
|
||||
|
||||
def each_key(&block)
|
||||
@objects.each_key(&block)
|
||||
@lru_cache.each_key(&block)
|
||||
end
|
||||
|
||||
def each_value(&block)
|
||||
@objects.each_value(&block)
|
||||
@lru_cache.each_value(&block)
|
||||
end
|
||||
|
||||
def size
|
||||
@objects.size + @lru_cache.size
|
||||
end
|
||||
alias :length :size
|
||||
|
||||
def empty?
|
||||
@objects.empty? && @lru_cache.empty?
|
||||
end
|
||||
|
||||
def include?(key)
|
||||
@objects.include?(key) || @lru_cache.include?(key)
|
||||
end
|
||||
alias :has_key? :include?
|
||||
alias :key? :include?
|
||||
alias :member? :include?
|
||||
|
||||
def has_value?(value)
|
||||
@objects.has_value?(value) || @lru_cache.has_value?(value)
|
||||
end
|
||||
|
||||
def to_s
|
||||
"<PDF::Reader::ObjectCache size: #{self.size}>"
|
||||
end
|
||||
|
||||
def keys
|
||||
@objects.keys + @lru_cache.keys
|
||||
end
|
||||
|
||||
def values
|
||||
@objects.values + @lru_cache.values
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_stats(key)
|
||||
if has_key?(key)
|
||||
@hits += 1
|
||||
else
|
||||
@misses += 1
|
||||
end
|
||||
end
|
||||
|
||||
def cacheable?(obj)
|
||||
obj.is_a?(Hash) && CACHEABLE_TYPES.include?(obj[:Type])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,620 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'tempfile'
|
||||
|
||||
class PDF::Reader
|
||||
# Provides low level access to the objects in a PDF file via a hash-like
|
||||
# object.
|
||||
#
|
||||
# A PDF file can be viewed as a large hash map. It is a series of objects
|
||||
# stored at precise byte offsets, and a table that maps object IDs to byte
|
||||
# offsets. Given an object ID, looking up an object is an O(1) operation.
|
||||
#
|
||||
# Each PDF object can be mapped to a ruby object, so by passing an object
|
||||
# ID to the [] method, a ruby representation of that object will be
|
||||
# retrieved.
|
||||
#
|
||||
# The class behaves much like a standard Ruby hash, including the use of
|
||||
# the Enumerable mixin. The key difference is no []= method - the hash
|
||||
# is read only.
|
||||
#
|
||||
# == Basic Usage
|
||||
#
|
||||
# h = PDF::Reader::ObjectHash.new("somefile.pdf")
|
||||
# h[1]
|
||||
# => 3469
|
||||
#
|
||||
# h[PDF::Reader::Reference.new(1,0)]
|
||||
# => 3469
|
||||
#
|
||||
class ObjectHash
|
||||
include Enumerable
|
||||
|
||||
attr_accessor :default
|
||||
attr_reader :trailer, :pdf_version
|
||||
attr_reader :sec_handler
|
||||
|
||||
# Creates a new ObjectHash object. Input can be a string with a valid filename
|
||||
# or an IO-like object.
|
||||
#
|
||||
# Valid options:
|
||||
#
|
||||
# :password - the user password to decrypt the source PDF
|
||||
#
|
||||
def initialize(input, opts = {})
|
||||
@io = extract_io_from(input)
|
||||
@xref = PDF::Reader::XRef.new(@io)
|
||||
@pdf_version = read_version
|
||||
@trailer = @xref.trailer
|
||||
@cache = opts[:cache] || PDF::Reader::ObjectCache.new
|
||||
@sec_handler = NullSecurityHandler.new
|
||||
@sec_handler = SecurityHandlerFactory.build(
|
||||
deref(trailer[:Encrypt]),
|
||||
deref(trailer[:ID]),
|
||||
opts[:password]
|
||||
)
|
||||
end
|
||||
|
||||
# returns the type of object a ref points to
|
||||
def obj_type(ref)
|
||||
self[ref].class.to_s.to_sym
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
# returns true if the supplied references points to an object with a stream
|
||||
def stream?(ref)
|
||||
self.has_key?(ref) && self[ref].is_a?(PDF::Reader::Stream)
|
||||
end
|
||||
|
||||
# Access an object from the PDF. key can be an int or a PDF::Reader::Reference
|
||||
# object.
|
||||
#
|
||||
# If an int is used, the object with that ID and a generation number of 0 will
|
||||
# be returned.
|
||||
#
|
||||
# If a PDF::Reader::Reference object is used the exact ID and generation number
|
||||
# can be specified.
|
||||
#
|
||||
def [](key)
|
||||
return default if key.to_i <= 0
|
||||
|
||||
unless key.is_a?(PDF::Reader::Reference)
|
||||
key = PDF::Reader::Reference.new(key.to_i, 0)
|
||||
end
|
||||
|
||||
@cache[key] ||= fetch_object(key) || fetch_object_stream(key)
|
||||
rescue InvalidObjectError
|
||||
return default
|
||||
end
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
def object(key)
|
||||
key.is_a?(PDF::Reader::Reference) ? self[key] : key
|
||||
end
|
||||
alias :deref :object
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
# Guaranteed to only return an Array or nil. If the dereference results in
|
||||
# any other type then a MalformedPDFError exception will raise. Useful when
|
||||
# expecting an Array and no other type will do.
|
||||
def deref_array(key)
|
||||
obj = deref(key)
|
||||
|
||||
return obj if obj.nil?
|
||||
|
||||
obj.tap { |obj|
|
||||
raise MalformedPDFError, "expected object to be an Array or nil" if !obj.is_a?(Array)
|
||||
}
|
||||
end
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
# Guaranteed to only return an Array of Numerics or nil. If the dereference results in
|
||||
# any other type then a MalformedPDFError exception will raise. Useful when
|
||||
# expecting an Array and no other type will do.
|
||||
#
|
||||
# Some effort to cast array elements to a number is made for any non-numeric elements.
|
||||
def deref_array_of_numbers(key)
|
||||
arr = deref(key)
|
||||
|
||||
return arr if arr.nil?
|
||||
|
||||
raise MalformedPDFError, "expected object to be an Array" unless arr.is_a?(Array)
|
||||
|
||||
arr.map { |item|
|
||||
if item.is_a?(Numeric)
|
||||
item
|
||||
elsif item.respond_to?(:to_f)
|
||||
item.to_f
|
||||
elsif item.respond_to?(:to_i)
|
||||
item.to_i
|
||||
else
|
||||
raise MalformedPDFError, "expected object to be a number"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
# Guaranteed to only return a Hash or nil. If the dereference results in
|
||||
# any other type then a MalformedPDFError exception will raise. Useful when
|
||||
# expecting an Array and no other type will do.
|
||||
def deref_hash(key)
|
||||
obj = deref(key)
|
||||
|
||||
return obj if obj.nil?
|
||||
|
||||
obj.tap { |obj|
|
||||
raise MalformedPDFError, "expected object to be a Hash or nil" if !obj.is_a?(Hash)
|
||||
}
|
||||
end
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
# Guaranteed to only return a PDF name (Symbol) or nil. If the dereference results in
|
||||
# any other type then a MalformedPDFError exception will raise. Useful when
|
||||
# expecting an Array and no other type will do.
|
||||
#
|
||||
# Some effort to cast to a symbol is made when the reference points to a non-symbol.
|
||||
def deref_name(key)
|
||||
obj = deref(key)
|
||||
|
||||
return obj if obj.nil?
|
||||
|
||||
if !obj.is_a?(Symbol)
|
||||
if obj.respond_to?(:to_sym)
|
||||
obj = obj.to_sym
|
||||
else
|
||||
raise MalformedPDFError, "expected object to be a Name"
|
||||
end
|
||||
end
|
||||
|
||||
obj
|
||||
end
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
# Guaranteed to only return an Integer or nil. If the dereference results in
|
||||
# any other type then a MalformedPDFError exception will raise. Useful when
|
||||
# expecting an Array and no other type will do.
|
||||
#
|
||||
# Some effort to cast to an int is made when the reference points to a non-integer.
|
||||
def deref_integer(key)
|
||||
obj = deref(key)
|
||||
|
||||
return obj if obj.nil?
|
||||
|
||||
if !obj.is_a?(Integer)
|
||||
if obj.respond_to?(:to_i)
|
||||
obj = obj.to_i
|
||||
else
|
||||
raise MalformedPDFError, "expected object to be an Integer"
|
||||
end
|
||||
end
|
||||
|
||||
obj
|
||||
end
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
# Guaranteed to only return a Numeric or nil. If the dereference results in
|
||||
# any other type then a MalformedPDFError exception will raise. Useful when
|
||||
# expecting an Array and no other type will do.
|
||||
#
|
||||
# Some effort to cast to a number is made when the reference points to a non-number.
|
||||
def deref_number(key)
|
||||
obj = deref(key)
|
||||
|
||||
return obj if obj.nil?
|
||||
|
||||
if !obj.is_a?(Numeric)
|
||||
if obj.respond_to?(:to_f)
|
||||
obj = obj.to_f
|
||||
elsif obj.respond_to?(:to_i)
|
||||
obj.to_i
|
||||
else
|
||||
raise MalformedPDFError, "expected object to be a number"
|
||||
end
|
||||
end
|
||||
|
||||
obj
|
||||
end
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
# Guaranteed to only return a PDF::Reader::Stream or nil. If the dereference results in
|
||||
# any other type then a MalformedPDFError exception will raise. Useful when
|
||||
# expecting a stream and no other type will do.
|
||||
def deref_stream(key)
|
||||
obj = deref(key)
|
||||
|
||||
return obj if obj.nil?
|
||||
|
||||
obj.tap { |obj|
|
||||
if !obj.is_a?(PDF::Reader::Stream)
|
||||
raise MalformedPDFError, "expected object to be a Stream or nil"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
# Guaranteed to only return a String or nil. If the dereference results in
|
||||
# any other type then a MalformedPDFError exception will raise. Useful when
|
||||
# expecting a string and no other type will do.
|
||||
#
|
||||
# Some effort to cast to a string is made when the reference points to a non-string.
|
||||
def deref_string(key)
|
||||
obj = deref(key)
|
||||
|
||||
return obj if obj.nil?
|
||||
|
||||
if !obj.is_a?(String)
|
||||
if obj.respond_to?(:to_s)
|
||||
obj = obj.to_s
|
||||
else
|
||||
raise MalformedPDFError, "expected object to be a string"
|
||||
end
|
||||
end
|
||||
|
||||
obj
|
||||
end
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
# Guaranteed to only return a PDF Name (symbol), Array or nil. If the dereference results in
|
||||
# any other type then a MalformedPDFError exception will raise. Useful when
|
||||
# expecting a Name or Array and no other type will do.
|
||||
def deref_name_or_array(key)
|
||||
obj = deref(key)
|
||||
|
||||
return obj if obj.nil?
|
||||
|
||||
obj.tap { |obj|
|
||||
if !obj.is_a?(Symbol) && !obj.is_a?(Array)
|
||||
raise MalformedPDFError, "expected object to be an Array or Name"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# If key is a PDF::Reader::Reference object, lookup the corresponding
|
||||
# object in the PDF and return it. Otherwise return key untouched.
|
||||
#
|
||||
# Guaranteed to only return a PDF::Reader::Stream, Array or nil. If the dereference results in
|
||||
# any other type then a MalformedPDFError exception will raise. Useful when
|
||||
# expecting a stream or Array and no other type will do.
|
||||
def deref_stream_or_array(key)
|
||||
obj = deref(key)
|
||||
|
||||
return obj if obj.nil?
|
||||
|
||||
obj.tap { |obj|
|
||||
if !obj.is_a?(PDF::Reader::Stream) && !obj.is_a?(Array)
|
||||
raise MalformedPDFError, "expected object to be an Array or Stream"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Recursively dereferences the object refered to be +key+. If +key+ is not
|
||||
# a PDF::Reader::Reference, the key is returned unchanged.
|
||||
#
|
||||
def deref!(key)
|
||||
deref_internal!(key, {})
|
||||
end
|
||||
|
||||
def deref_array!(key)
|
||||
deref!(key).tap { |obj|
|
||||
if !obj.nil? && !obj.is_a?(Array)
|
||||
raise MalformedPDFError, "expected object (#{obj.inspect}) to be an Array or nil"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def deref_hash!(key)
|
||||
deref!(key).tap { |obj|
|
||||
if !obj.nil? && !obj.is_a?(Hash)
|
||||
raise MalformedPDFError, "expected object (#{obj.inspect}) to be a Hash or nil"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Access an object from the PDF. key can be an int or a PDF::Reader::Reference
|
||||
# object.
|
||||
#
|
||||
# If an int is used, the object with that ID and a generation number of 0 will
|
||||
# be returned.
|
||||
#
|
||||
# If a PDF::Reader::Reference object is used the exact ID and generation number
|
||||
# can be specified.
|
||||
#
|
||||
# local_default is the object that will be returned if the requested key doesn't
|
||||
# exist.
|
||||
#
|
||||
def fetch(key, local_default = nil)
|
||||
obj = self[key]
|
||||
if obj
|
||||
return obj
|
||||
elsif local_default
|
||||
return local_default
|
||||
else
|
||||
raise IndexError, "#{key} is invalid" if key.to_i <= 0
|
||||
end
|
||||
end
|
||||
|
||||
# iterate over each key, value. Just like a ruby hash.
|
||||
#
|
||||
def each(&block)
|
||||
@xref.each do |ref|
|
||||
yield ref, self[ref]
|
||||
end
|
||||
end
|
||||
alias :each_pair :each
|
||||
|
||||
# iterate over each key. Just like a ruby hash.
|
||||
#
|
||||
def each_key(&block)
|
||||
each do |id, obj|
|
||||
yield id
|
||||
end
|
||||
end
|
||||
|
||||
# iterate over each value. Just like a ruby hash.
|
||||
#
|
||||
def each_value(&block)
|
||||
each do |id, obj|
|
||||
yield obj
|
||||
end
|
||||
end
|
||||
|
||||
# return the number of objects in the file. An object with multiple generations
|
||||
# is counted once.
|
||||
def size
|
||||
xref.size
|
||||
end
|
||||
alias :length :size
|
||||
|
||||
# return true if there are no objects in this file
|
||||
#
|
||||
def empty?
|
||||
size == 0 ? true : false
|
||||
end
|
||||
|
||||
# return true if the specified key exists in the file. key
|
||||
# can be an int or a PDF::Reader::Reference
|
||||
#
|
||||
def has_key?(check_key)
|
||||
# TODO update from O(n) to O(1)
|
||||
each_key do |key|
|
||||
if check_key.kind_of?(PDF::Reader::Reference)
|
||||
return true if check_key == key
|
||||
else
|
||||
return true if check_key.to_i == key.id
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
alias :include? :has_key?
|
||||
alias :key? :has_key?
|
||||
alias :member? :has_key?
|
||||
|
||||
# return true if the specifiedvalue exists in the file
|
||||
#
|
||||
def has_value?(value)
|
||||
# TODO update from O(n) to O(1)
|
||||
each_value do |obj|
|
||||
return true if obj == value
|
||||
end
|
||||
return false
|
||||
end
|
||||
alias :value? :has_key?
|
||||
|
||||
def to_s
|
||||
"<PDF::Reader::ObjectHash size: #{self.size}>"
|
||||
end
|
||||
|
||||
# return an array of all keys in the file
|
||||
#
|
||||
def keys
|
||||
ret = []
|
||||
each_key { |k| ret << k }
|
||||
ret
|
||||
end
|
||||
|
||||
# return an array of all values in the file
|
||||
#
|
||||
def values
|
||||
ret = []
|
||||
each_value { |v| ret << v }
|
||||
ret
|
||||
end
|
||||
|
||||
# return an array of all values from the specified keys
|
||||
#
|
||||
def values_at(*ids)
|
||||
ids.map { |id| self[id] }
|
||||
end
|
||||
|
||||
# return an array of arrays. Each sub array contains a key/value pair.
|
||||
#
|
||||
def to_a
|
||||
ret = []
|
||||
each do |id, obj|
|
||||
ret << [id, obj]
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# returns an array of PDF::Reader::References. Each reference in the
|
||||
# array points a Page object, one for each page in the PDF. The first
|
||||
# reference is page 1, second reference is page 2, etc.
|
||||
#
|
||||
# Useful for apps that want to extract data from specific pages.
|
||||
#
|
||||
def page_references
|
||||
root = fetch(trailer[:Root])
|
||||
@page_references ||= begin
|
||||
pages_root = deref_hash(root[:Pages]) || {}
|
||||
get_page_objects(pages_root)
|
||||
end
|
||||
end
|
||||
|
||||
def encrypted?
|
||||
trailer.has_key?(:Encrypt)
|
||||
end
|
||||
|
||||
def sec_handler?
|
||||
!!sec_handler
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# parse a traditional object from the PDF, starting from the byte offset indicated
|
||||
# in the xref table
|
||||
#
|
||||
def fetch_object(key)
|
||||
if xref[key].is_a?(Integer)
|
||||
buf = new_buffer(xref[key])
|
||||
decrypt(key, Parser.new(buf, self).object(key.id, key.gen))
|
||||
end
|
||||
end
|
||||
|
||||
# parse a object that's embedded in an object stream in the PDF
|
||||
#
|
||||
def fetch_object_stream(key)
|
||||
if xref[key].is_a?(PDF::Reader::Reference)
|
||||
container_key = xref[key]
|
||||
stream = deref_stream(container_key)
|
||||
raise MalformedPDFError, "Object Stream cannot be nil" if stream.nil?
|
||||
object_streams[container_key] ||= PDF::Reader::ObjectStream.new(stream)
|
||||
object_streams[container_key][key.id]
|
||||
end
|
||||
end
|
||||
|
||||
# Private implementation of deref!, which exists to ensure the `seen` argument
|
||||
# isn't publicly available. It's used to avoid endless loops in the recursion, and
|
||||
# doesn't need to be part of the public API.
|
||||
#
|
||||
def deref_internal!(key, seen)
|
||||
seen_key = key.is_a?(PDF::Reader::Reference) ? key : key.object_id
|
||||
|
||||
return seen[seen_key] if seen.key?(seen_key)
|
||||
|
||||
case object = deref(key)
|
||||
when Hash
|
||||
seen[seen_key] ||= {}
|
||||
object.each do |k, value|
|
||||
seen[seen_key][k] = deref_internal!(value, seen)
|
||||
end
|
||||
seen[seen_key]
|
||||
when PDF::Reader::Stream
|
||||
seen[seen_key] ||= PDF::Reader::Stream.new({}, object.data)
|
||||
object.hash.each do |k,value|
|
||||
seen[seen_key].hash[k] = deref_internal!(value, seen)
|
||||
end
|
||||
seen[seen_key]
|
||||
when Array
|
||||
seen[seen_key] ||= []
|
||||
object.each do |value|
|
||||
seen[seen_key] << deref_internal!(value, seen)
|
||||
end
|
||||
seen[seen_key]
|
||||
else
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
def decrypt(ref, obj)
|
||||
case obj
|
||||
when PDF::Reader::Stream then
|
||||
# PDF 32000-1:2008 7.5.8.2: "The cross-reference stream shall not be encrypted [...]."
|
||||
# Therefore we shouldn't try to decrypt it.
|
||||
obj.data = sec_handler.decrypt(obj.data, ref) unless obj.hash[:Type] == :XRef
|
||||
obj
|
||||
when Hash then
|
||||
arr = obj.map { |key,val| [key, decrypt(ref, val)] }
|
||||
arr.each_with_object({}) { |(k,v), accum|
|
||||
accum[k] = v
|
||||
}
|
||||
when Array then
|
||||
obj.collect { |item| decrypt(ref, item) }
|
||||
when String
|
||||
sec_handler.decrypt(obj, ref)
|
||||
else
|
||||
obj
|
||||
end
|
||||
end
|
||||
|
||||
def new_buffer(offset = 0)
|
||||
PDF::Reader::Buffer.new(@io, :seek => offset)
|
||||
end
|
||||
|
||||
def xref
|
||||
@xref
|
||||
end
|
||||
|
||||
def object_streams
|
||||
@object_streams ||= {}
|
||||
end
|
||||
|
||||
# returns an array of object references for all pages in this object store. The ordering of
|
||||
# the Array is significant and matches the page ordering of the document
|
||||
#
|
||||
def get_page_objects(obj)
|
||||
derefed_obj = deref_hash(obj)
|
||||
|
||||
if derefed_obj.nil?
|
||||
raise MalformedPDFError, "Expected Page or Pages object, got nil"
|
||||
elsif derefed_obj[:Type] == :Page
|
||||
[obj]
|
||||
elsif derefed_obj[:Kids]
|
||||
kids = deref_array(derefed_obj[:Kids]) || []
|
||||
kids.map { |kid|
|
||||
get_page_objects(kid)
|
||||
}.flatten
|
||||
else
|
||||
raise MalformedPDFError, "Expected Page or Pages object"
|
||||
end
|
||||
end
|
||||
|
||||
def read_version
|
||||
@io.seek(0)
|
||||
_m, version = *@io.read(10).to_s.match(/PDF-(\d.\d)/)
|
||||
@io.seek(0)
|
||||
version.to_f
|
||||
end
|
||||
|
||||
def extract_io_from(input)
|
||||
if input.is_a?(IO) || input.is_a?(StringIO) || input.is_a?(Tempfile)
|
||||
input
|
||||
elsif File.file?(input.to_s)
|
||||
StringIO.new read_as_binary(input.to_s)
|
||||
else
|
||||
raise ArgumentError, "input must be an IO-like object or a filename (#{input.class})"
|
||||
end
|
||||
end
|
||||
|
||||
def read_as_binary(input)
|
||||
if File.respond_to?(:binread)
|
||||
File.binread(input.to_s)
|
||||
else
|
||||
File.open(input.to_s,"rb") { |f| f.read }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# provides a wrapper around a PDF stream object that contains other objects in it.
|
||||
# This is done for added compression and is described as an "Object Stream" in the spec.
|
||||
#
|
||||
class ObjectStream # :nodoc:
|
||||
def initialize(stream)
|
||||
@dict = stream.hash
|
||||
@data = stream.unfiltered_data
|
||||
end
|
||||
|
||||
def [](objid)
|
||||
if offsets[objid].nil?
|
||||
nil
|
||||
else
|
||||
buf = PDF::Reader::Buffer.new(StringIO.new(@data), :seek => offsets[objid])
|
||||
parser = PDF::Reader::Parser.new(buf)
|
||||
parser.parse_token
|
||||
end
|
||||
end
|
||||
|
||||
def size
|
||||
TypeCheck.cast_to_int!(@dict[:N])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def offsets
|
||||
@offsets ||= {}
|
||||
return @offsets if @offsets.keys.size > 0
|
||||
|
||||
size.times do
|
||||
@offsets[buffer.token.to_i] = first + buffer.token.to_i
|
||||
end
|
||||
@offsets
|
||||
end
|
||||
|
||||
def first
|
||||
TypeCheck.cast_to_int!(@dict[:First])
|
||||
end
|
||||
|
||||
def buffer
|
||||
@buffer ||= PDF::Reader::Buffer.new(StringIO.new(@data))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# coding: utf-8
|
||||
# frozen_string_literal: true
|
||||
# typed: strict
|
||||
|
||||
class PDF::Reader
|
||||
# remove duplicates from a collection of TextRun objects. This can be helpful when a PDF
|
||||
# uses slightly offset overlapping characters to achieve a fake 'bold' effect.
|
||||
class OverlappingRunsFilter
|
||||
|
||||
# This should be between 0 and 1. If TextRun B obscures this much of TextRun A (and they
|
||||
# have identical characters) then one will be discarded
|
||||
OVERLAPPING_THRESHOLD = 0.5
|
||||
|
||||
def self.exclude_redundant_runs(runs)
|
||||
sweep_line_status = Array.new
|
||||
event_point_schedule = Array.new
|
||||
to_exclude = []
|
||||
|
||||
runs.each do |run|
|
||||
event_point_schedule << EventPoint.new(run.x, run)
|
||||
event_point_schedule << EventPoint.new(run.endx, run)
|
||||
end
|
||||
|
||||
event_point_schedule.sort! { |a,b| a.x <=> b.x }
|
||||
|
||||
event_point_schedule.each do |event_point|
|
||||
run = event_point.run
|
||||
|
||||
if event_point.start?
|
||||
if detect_intersection(sweep_line_status, event_point)
|
||||
to_exclude << run
|
||||
end
|
||||
sweep_line_status.push(run)
|
||||
else
|
||||
sweep_line_status.delete(run)
|
||||
end
|
||||
end
|
||||
runs - to_exclude
|
||||
end
|
||||
|
||||
def self.detect_intersection(sweep_line_status, event_point)
|
||||
sweep_line_status.each do |open_text_run|
|
||||
if open_text_run.text == event_point.run.text &&
|
||||
event_point.x >= open_text_run.x &&
|
||||
event_point.x <= open_text_run.endx &&
|
||||
open_text_run.intersection_area_percent(event_point.run) >= OVERLAPPING_THRESHOLD
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Utility class used to avoid modifying the underlying TextRun objects while we're
|
||||
# looking for duplicates
|
||||
class EventPoint
|
||||
|
||||
attr_reader :x
|
||||
|
||||
attr_reader :run
|
||||
|
||||
def initialize(x, run)
|
||||
@x = x
|
||||
@run = run
|
||||
end
|
||||
|
||||
def start?
|
||||
@x == @run.x
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,316 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module PDF
|
||||
class Reader
|
||||
|
||||
# high level representation of a single PDF page. Ties together the various
|
||||
# low level classes in PDF::Reader and provides access to the various
|
||||
# components of the page (text, images, fonts, etc) in convenient formats.
|
||||
#
|
||||
# If you require access to the raw PDF objects for this page, you can access
|
||||
# the Page dictionary via the page_object accessor. You will need to use the
|
||||
# objects accessor to help walk the page dictionary in any useful way.
|
||||
#
|
||||
class Page
|
||||
extend Forwardable
|
||||
|
||||
# lowlevel hash-like access to all objects in the underlying PDF
|
||||
attr_reader :objects
|
||||
|
||||
# the raw PDF object that defines this page
|
||||
attr_reader :page_object
|
||||
|
||||
# a Hash-like object for storing cached data. Generally this is scoped to
|
||||
# the current document and is used to avoid repeating expensive
|
||||
# operations
|
||||
attr_reader :cache
|
||||
|
||||
def_delegators :resources, :color_spaces
|
||||
def_delegators :resources, :fonts
|
||||
def_delegators :resources, :graphic_states
|
||||
def_delegators :resources, :patterns
|
||||
def_delegators :resources, :procedure_sets
|
||||
def_delegators :resources, :properties
|
||||
def_delegators :resources, :shadings
|
||||
def_delegators :resources, :xobjects
|
||||
|
||||
# creates a new page wrapper.
|
||||
#
|
||||
# * objects - an ObjectHash instance that wraps a PDF file
|
||||
# * pagenum - an int specifying the page number to expose. 1 indexed.
|
||||
#
|
||||
def initialize(objects, pagenum, options = {})
|
||||
@objects, @pagenum = objects, pagenum
|
||||
@page_object = objects.deref_hash(objects.page_references[pagenum - 1]) || {}
|
||||
@cache = options[:cache] || {}
|
||||
|
||||
if @page_object.empty?
|
||||
raise InvalidPageError, "Invalid page: #{pagenum}"
|
||||
end
|
||||
end
|
||||
|
||||
# return the number of this page within the full document
|
||||
#
|
||||
def number
|
||||
@pagenum
|
||||
end
|
||||
|
||||
# return a friendly string representation of this page
|
||||
#
|
||||
def inspect
|
||||
"<PDF::Reader::Page page: #{@pagenum}>"
|
||||
end
|
||||
|
||||
# Returns the attributes that accompany this page, including
|
||||
# attributes inherited from parents.
|
||||
#
|
||||
def attributes
|
||||
@attributes ||= {}.tap { |hash|
|
||||
page_with_ancestors.reverse.each do |obj|
|
||||
hash.merge!(@objects.deref_hash(obj) || {})
|
||||
end
|
||||
}
|
||||
# This shouldn't be necesary, but some non compliant PDFs leave MediaBox
|
||||
# out. Assuming 8.5" x 11" is what Acobat does, so we do it too.
|
||||
@attributes[:MediaBox] ||= [0,0,612,792]
|
||||
@attributes
|
||||
end
|
||||
|
||||
def height
|
||||
rect = Rectangle.new(*attributes[:MediaBox])
|
||||
rect.apply_rotation(rotate) if rotate > 0
|
||||
rect.height
|
||||
end
|
||||
|
||||
def width
|
||||
rect = Rectangle.new(*attributes[:MediaBox])
|
||||
rect.apply_rotation(rotate) if rotate > 0
|
||||
rect.width
|
||||
end
|
||||
|
||||
def origin
|
||||
rect = Rectangle.new(*attributes[:MediaBox])
|
||||
rect.apply_rotation(rotate) if rotate > 0
|
||||
|
||||
rect.bottom_left
|
||||
end
|
||||
|
||||
# Convenience method to identify the page's orientation.
|
||||
#
|
||||
def orientation
|
||||
if height > width
|
||||
"portrait"
|
||||
else
|
||||
"landscape"
|
||||
end
|
||||
end
|
||||
|
||||
# returns the plain text content of this page encoded as UTF-8. Any
|
||||
# characters that can't be translated will be returned as a ▯
|
||||
#
|
||||
def text(opts = {})
|
||||
receiver = PageTextReceiver.new
|
||||
walk(receiver)
|
||||
runs = receiver.runs(opts)
|
||||
|
||||
# rectangles[:MediaBox] can never be nil, but I have no easy way to tell sorbet that atm
|
||||
mediabox = rectangles[:MediaBox] || Rectangle.new(0, 0, 0, 0)
|
||||
|
||||
PageLayout.new(runs, mediabox).to_s
|
||||
end
|
||||
alias :to_s :text
|
||||
|
||||
def runs(opts = {})
|
||||
receiver = PageTextReceiver.new
|
||||
walk(receiver)
|
||||
receiver.runs(opts)
|
||||
end
|
||||
|
||||
# processes the raw content stream for this page in sequential order and
|
||||
# passes callbacks to the receiver objects.
|
||||
#
|
||||
# This is mostly low level and you can probably ignore it unless you need
|
||||
# access to something like the raw encoded text. For an example of how
|
||||
# this can be used as a basis for higher level functionality, see the
|
||||
# text() method
|
||||
#
|
||||
# If someone was motivated enough, this method is intended to provide all
|
||||
# the data required to faithfully render the entire page. If you find
|
||||
# some required data isn't available it's a bug - let me know.
|
||||
#
|
||||
# Many operators that generate callbacks will reference resources stored
|
||||
# in the page header - think images, fonts, etc. To facilitate these
|
||||
# operators, the first available callback is page=. If your receiver
|
||||
# accepts that callback it will be passed the current
|
||||
# PDF::Reader::Page object. Use the Page#resources method to grab any
|
||||
# required resources.
|
||||
#
|
||||
# It may help to think of each page as a self contained program made up of
|
||||
# a set of instructions and associated resources. Calling walk() executes
|
||||
# the program in the correct order and calls out to your implementation.
|
||||
#
|
||||
def walk(*receivers)
|
||||
receivers = receivers.map { |receiver|
|
||||
ValidatingReceiver.new(receiver)
|
||||
}
|
||||
callback(receivers, :page=, [self])
|
||||
content_stream(receivers, raw_content)
|
||||
end
|
||||
|
||||
# returns the raw content stream for this page. This is plumbing, nothing to
|
||||
# see here unless you're a PDF nerd like me.
|
||||
#
|
||||
def raw_content
|
||||
contents = objects.deref_stream_or_array(@page_object[:Contents])
|
||||
[contents].flatten.compact.map { |obj|
|
||||
objects.deref_stream(obj)
|
||||
}.compact.map { |obj|
|
||||
obj.unfiltered_data
|
||||
}.join(" ")
|
||||
end
|
||||
|
||||
# returns the angle to rotate the page clockwise. Always 0, 90, 180 or 270
|
||||
#
|
||||
def rotate
|
||||
value = attributes[:Rotate].to_i
|
||||
case value
|
||||
when 0, 90, 180, 270
|
||||
value
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
# returns the "boxes" that define the page object.
|
||||
# values are defaulted according to section 7.7.3.3 of the PDF Spec 1.7
|
||||
#
|
||||
# DEPRECATED. Recommend using Page#rectangles instead
|
||||
#
|
||||
def boxes
|
||||
# In ruby 2.4+ we could use Hash#transform_values
|
||||
Hash[rectangles.map{ |k,rect| [k,rect.to_a] } ]
|
||||
end
|
||||
|
||||
# returns the "boxes" that define the page object.
|
||||
# values are defaulted according to section 7.7.3.3 of the PDF Spec 1.7
|
||||
#
|
||||
def rectangles
|
||||
# attributes[:MediaBox] can never be nil, but I have no easy way to tell sorbet that atm
|
||||
mediabox = objects.deref_array_of_numbers(attributes[:MediaBox]) || []
|
||||
cropbox = objects.deref_array_of_numbers(attributes[:CropBox]) || mediabox
|
||||
bleedbox = objects.deref_array_of_numbers(attributes[:BleedBox]) || cropbox
|
||||
trimbox = objects.deref_array_of_numbers(attributes[:TrimBox]) || cropbox
|
||||
artbox = objects.deref_array_of_numbers(attributes[:ArtBox]) || cropbox
|
||||
|
||||
begin
|
||||
mediarect = Rectangle.from_array(mediabox)
|
||||
croprect = Rectangle.from_array(cropbox)
|
||||
bleedrect = Rectangle.from_array(bleedbox)
|
||||
trimrect = Rectangle.from_array(trimbox)
|
||||
artrect = Rectangle.from_array(artbox)
|
||||
rescue ArgumentError => e
|
||||
raise MalformedPDFError, e.message
|
||||
end
|
||||
|
||||
if rotate > 0
|
||||
mediarect.apply_rotation(rotate)
|
||||
croprect.apply_rotation(rotate)
|
||||
bleedrect.apply_rotation(rotate)
|
||||
trimrect.apply_rotation(rotate)
|
||||
artrect.apply_rotation(rotate)
|
||||
end
|
||||
|
||||
{
|
||||
MediaBox: mediarect,
|
||||
CropBox: croprect,
|
||||
BleedBox: bleedrect,
|
||||
TrimBox: trimrect,
|
||||
ArtBox: artrect,
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def root
|
||||
@root ||= objects.deref_hash(@objects.trailer[:Root]) || {}
|
||||
end
|
||||
|
||||
# Returns the resources that accompany this page. Includes
|
||||
# resources inherited from parents.
|
||||
#
|
||||
def resources
|
||||
@resources ||= Resources.new(@objects, @objects.deref_hash(attributes[:Resources]) || {})
|
||||
end
|
||||
|
||||
def content_stream(receivers, instructions)
|
||||
buffer = Buffer.new(StringIO.new(instructions), :content_stream => true)
|
||||
parser = Parser.new(buffer, @objects)
|
||||
params = []
|
||||
|
||||
while (token = parser.parse_token(PagesStrategy::OPERATORS))
|
||||
if token.kind_of?(Token) && method_name = PagesStrategy::OPERATORS[token]
|
||||
callback(receivers, method_name, params)
|
||||
params.clear
|
||||
else
|
||||
params << token
|
||||
end
|
||||
end
|
||||
rescue EOFError
|
||||
raise MalformedPDFError, "End Of File while processing a content stream"
|
||||
end
|
||||
|
||||
# calls the name callback method on each receiver object with params as the arguments
|
||||
#
|
||||
# The silly style here is because sorbet won't let me use splat arguments
|
||||
#
|
||||
def callback(receivers, name, params=[])
|
||||
receivers.each do |receiver|
|
||||
if receiver.respond_to?(name)
|
||||
case params.size
|
||||
when 0 then receiver.send(name)
|
||||
when 1 then receiver.send(name, params[0])
|
||||
when 2 then receiver.send(name, params[0], params[1])
|
||||
when 3 then receiver.send(name, params[0], params[1], params[2])
|
||||
when 4 then receiver.send(name, params[0], params[1], params[2], params[3])
|
||||
when 5 then receiver.send(name, params[0], params[1], params[2], params[3], params[4])
|
||||
when 6 then receiver.send(name, params[0], params[1], params[2], params[3], params[4], params[5])
|
||||
when 7 then receiver.send(name, params[0], params[1], params[2], params[3], params[4], params[5], params[6])
|
||||
when 8 then receiver.send(name, params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7])
|
||||
when 9 then receiver.send(name, params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8])
|
||||
else
|
||||
receiver.send(name, params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8], params[9])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def page_with_ancestors
|
||||
[ @page_object ] + ancestors
|
||||
end
|
||||
|
||||
def ancestors(origin = @page_object[:Parent])
|
||||
if origin.nil?
|
||||
[]
|
||||
else
|
||||
obj = objects.deref_hash(origin)
|
||||
if obj.nil?
|
||||
raise MalformedPDFError, "parent mus not be nil"
|
||||
end
|
||||
[ select_inheritable(obj) ] + ancestors(obj[:Parent])
|
||||
end
|
||||
end
|
||||
|
||||
# select the elements from a Pages dictionary that can be inherited by
|
||||
# child Page dictionaries.
|
||||
#
|
||||
def select_inheritable(obj)
|
||||
::Hash[obj.select { |key, value|
|
||||
[:Resources, :MediaBox, :CropBox, :Rotate, :Parent].include?(key)
|
||||
}]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,124 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pdf/reader/overlapping_runs_filter'
|
||||
require 'pdf/reader/zero_width_runs_filter'
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# Takes a collection of TextRun objects and renders them into a single
|
||||
# string that best approximates the way they'd appear on a render PDF page.
|
||||
#
|
||||
# media box should be a 4 number array that describes the dimensions of the
|
||||
# page to be rendered as described by the page's MediaBox attribute
|
||||
class PageLayout
|
||||
|
||||
DEFAULT_FONT_SIZE = 12
|
||||
|
||||
def initialize(runs, mediabox)
|
||||
# mediabox is a 4-element array for now, but it'd be nice to switch to a
|
||||
# PDF::Reader::Rectangle at some point
|
||||
PDF::Reader::Error.validate_not_nil(mediabox, "mediabox")
|
||||
|
||||
@mediabox = process_mediabox(mediabox)
|
||||
@runs = runs
|
||||
@mean_font_size = mean(@runs.map(&:font_size)) || DEFAULT_FONT_SIZE
|
||||
@mean_font_size = DEFAULT_FONT_SIZE if @mean_font_size == 0
|
||||
@median_glyph_width = median(@runs.map(&:mean_character_width)) || 0
|
||||
@x_offset = @runs.map(&:x).sort.first || 0
|
||||
lowest_y = @runs.map(&:y).sort.first || 0
|
||||
@y_offset = lowest_y > 0 ? 0 : lowest_y
|
||||
end
|
||||
|
||||
def to_s
|
||||
return "" if @runs.empty?
|
||||
return "" if row_count == 0
|
||||
|
||||
page = row_count.times.map { |i| " " * col_count }
|
||||
@runs.each do |run|
|
||||
x_pos = ((run.x - @x_offset) / col_multiplier).round
|
||||
y_pos = row_count - ((run.y - @y_offset) / row_multiplier).round
|
||||
if y_pos <= row_count && y_pos >= 0 && x_pos <= col_count && x_pos >= 0
|
||||
local_string_insert(page[y_pos-1], run.text, x_pos)
|
||||
end
|
||||
end
|
||||
interesting_rows(page).map(&:rstrip).join("\n")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def page_width
|
||||
@mediabox.width
|
||||
end
|
||||
|
||||
def page_height
|
||||
@mediabox.height
|
||||
end
|
||||
|
||||
# given an array of strings, return a new array with empty rows from the
|
||||
# beginning and end removed.
|
||||
#
|
||||
# interesting_rows([ "", "one", "two", "" ])
|
||||
# => [ "one", "two" ]
|
||||
#
|
||||
def interesting_rows(rows)
|
||||
line_lengths = rows.map { |l| l.strip.length }
|
||||
|
||||
return [] if line_lengths.all?(&:zero?)
|
||||
|
||||
first_line_with_text = line_lengths.index { |l| l > 0 }
|
||||
last_line_with_text = line_lengths.size - line_lengths.reverse.index { |l| l > 0 }
|
||||
interesting_line_count = last_line_with_text - first_line_with_text
|
||||
rows[first_line_with_text, interesting_line_count].map
|
||||
end
|
||||
|
||||
def row_count
|
||||
@row_count ||= (page_height / @mean_font_size).floor
|
||||
end
|
||||
|
||||
def col_count
|
||||
@col_count ||= ((page_width / @median_glyph_width) * 1.05).floor
|
||||
end
|
||||
|
||||
def row_multiplier
|
||||
@row_multiplier ||= page_height.to_f / row_count.to_f
|
||||
end
|
||||
|
||||
def col_multiplier
|
||||
@col_multiplier ||= page_width.to_f / col_count.to_f
|
||||
end
|
||||
|
||||
def mean(collection)
|
||||
if collection.size == 0
|
||||
0
|
||||
else
|
||||
collection.inject(0) { |accum, v| accum + v} / collection.size.to_f
|
||||
end
|
||||
end
|
||||
|
||||
def median(collection)
|
||||
if collection.size == 0
|
||||
0
|
||||
else
|
||||
collection.sort[(collection.size * 0.5).floor]
|
||||
end
|
||||
end
|
||||
|
||||
def local_string_insert(haystack, needle, index)
|
||||
haystack[Range.new(index, index + needle.length - 1)] = String.new(needle)
|
||||
end
|
||||
|
||||
def process_mediabox(mediabox)
|
||||
if mediabox.is_a?(Array)
|
||||
msg = "Passing the mediabox to PageLayout as an Array is deprecated," +
|
||||
" please use a Rectangle instead"
|
||||
$stderr.puts msg
|
||||
PDF::Reader::Rectangle.from_array(mediabox)
|
||||
else
|
||||
mediabox
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,417 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pdf/reader/transformation_matrix'
|
||||
|
||||
class PDF::Reader
|
||||
# encapsulates logic for tracking graphics state as the instructions for
|
||||
# a single page are processed. Most of the public methods correspond
|
||||
# directly to PDF operators.
|
||||
class PageState
|
||||
|
||||
DEFAULT_GRAPHICS_STATE = {
|
||||
:char_spacing => 0,
|
||||
:word_spacing => 0,
|
||||
:h_scaling => 1.0,
|
||||
:text_leading => 0,
|
||||
:text_font => nil,
|
||||
:text_font_size => 0,
|
||||
:text_mode => 0,
|
||||
:text_rise => 0,
|
||||
:text_knockout => 0
|
||||
}
|
||||
|
||||
# starting a new page
|
||||
def initialize(page)
|
||||
@page = page
|
||||
@cache = page.cache
|
||||
@objects = page.objects
|
||||
@font_stack = [build_fonts(page.fonts)]
|
||||
@xobject_stack = [page.xobjects]
|
||||
@cs_stack = [page.color_spaces]
|
||||
@stack = [DEFAULT_GRAPHICS_STATE.dup]
|
||||
state[:ctm] = identity_matrix
|
||||
|
||||
# These are only valid when inside a `BT` block and we re-initialize them on each
|
||||
# `BT`. However, we need the instance variables set so PDFs with the text operators
|
||||
# out order don't trigger NoMethodError when these are nil
|
||||
@text_matrix = identity_matrix
|
||||
@text_line_matrix = identity_matrix
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Graphics State Operators
|
||||
#####################################################
|
||||
|
||||
# Clones the current graphics state and push it onto the top of the stack.
|
||||
# Any changes that are subsequently made to the state can then by reversed
|
||||
# by calling restore_graphics_state.
|
||||
#
|
||||
def save_graphics_state
|
||||
@stack.push clone_state
|
||||
end
|
||||
|
||||
# Restore the state to the previous value on the stack.
|
||||
#
|
||||
def restore_graphics_state
|
||||
@stack.pop
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Matrix Operators
|
||||
#####################################################
|
||||
|
||||
# update the current transformation matrix.
|
||||
#
|
||||
# If the CTM is currently undefined, just store the new values.
|
||||
#
|
||||
# If there's an existing CTM, then multiply the existing matrix
|
||||
# with the new matrix to form the updated matrix.
|
||||
#
|
||||
def concatenate_matrix(a, b, c, d, e, f)
|
||||
if state[:ctm]
|
||||
ctm = state[:ctm]
|
||||
state[:ctm] = TransformationMatrix.new(a,b,c,d,e,f).multiply!(
|
||||
ctm.a, ctm.b,
|
||||
ctm.c, ctm.d,
|
||||
ctm.e, ctm.f
|
||||
)
|
||||
else
|
||||
state[:ctm] = TransformationMatrix.new(a,b,c,d,e,f)
|
||||
end
|
||||
@text_rendering_matrix = nil # invalidate cached value
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text Object Operators
|
||||
#####################################################
|
||||
|
||||
def begin_text_object
|
||||
@text_matrix = identity_matrix
|
||||
@text_line_matrix = identity_matrix
|
||||
@font_size = nil
|
||||
end
|
||||
|
||||
def end_text_object
|
||||
# don't need to do anything
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text State Operators
|
||||
#####################################################
|
||||
|
||||
def set_character_spacing(char_spacing)
|
||||
state[:char_spacing] = char_spacing
|
||||
end
|
||||
|
||||
def set_horizontal_text_scaling(h_scaling)
|
||||
state[:h_scaling] = h_scaling / 100.0
|
||||
end
|
||||
|
||||
def set_text_font_and_size(label, size)
|
||||
state[:text_font] = label
|
||||
state[:text_font_size] = size
|
||||
end
|
||||
|
||||
def font_size
|
||||
@font_size ||= begin
|
||||
_, zero = trm_transform(0,0)
|
||||
_, one = trm_transform(1,1)
|
||||
(zero - one).abs
|
||||
end
|
||||
end
|
||||
|
||||
def set_text_leading(leading)
|
||||
state[:text_leading] = leading
|
||||
end
|
||||
|
||||
def set_text_rendering_mode(mode)
|
||||
state[:text_mode] = mode
|
||||
end
|
||||
|
||||
def set_text_rise(rise)
|
||||
state[:text_rise] = rise
|
||||
end
|
||||
|
||||
def set_word_spacing(word_spacing)
|
||||
state[:word_spacing] = word_spacing
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text Positioning Operators
|
||||
#####################################################
|
||||
|
||||
def move_text_position(x, y) # Td
|
||||
temp = TransformationMatrix.new(1, 0,
|
||||
0, 1,
|
||||
x, y)
|
||||
@text_line_matrix = temp.multiply!(
|
||||
@text_line_matrix.a, @text_line_matrix.b,
|
||||
@text_line_matrix.c, @text_line_matrix.d,
|
||||
@text_line_matrix.e, @text_line_matrix.f
|
||||
)
|
||||
@text_matrix = @text_line_matrix.dup
|
||||
@font_size = @text_rendering_matrix = nil # invalidate cached value
|
||||
end
|
||||
|
||||
def move_text_position_and_set_leading(x, y) # TD
|
||||
set_text_leading(-1 * y)
|
||||
move_text_position(x, y)
|
||||
end
|
||||
|
||||
def set_text_matrix_and_text_line_matrix(a, b, c, d, e, f) # Tm
|
||||
@text_matrix = TransformationMatrix.new(
|
||||
a, b,
|
||||
c, d,
|
||||
e, f
|
||||
)
|
||||
@text_line_matrix = @text_matrix.dup
|
||||
@font_size = @text_rendering_matrix = nil # invalidate cached value
|
||||
end
|
||||
|
||||
def move_to_start_of_next_line # T*
|
||||
move_text_position(0, -state[:text_leading])
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text Showing Operators
|
||||
#####################################################
|
||||
|
||||
def show_text_with_positioning(params) # TJ
|
||||
# TODO record position changes in state here
|
||||
end
|
||||
|
||||
def move_to_next_line_and_show_text(str) # '
|
||||
move_to_start_of_next_line
|
||||
end
|
||||
|
||||
def set_spacing_next_line_show_text(aw, ac, string) # "
|
||||
set_word_spacing(aw)
|
||||
set_character_spacing(ac)
|
||||
move_to_next_line_and_show_text(string)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# XObjects
|
||||
#####################################################
|
||||
def invoke_xobject(label)
|
||||
save_graphics_state
|
||||
xobject = find_xobject(label)
|
||||
|
||||
raise MalformedPDFError, "XObject #{label} not found" if xobject.nil?
|
||||
matrix = xobject.hash[:Matrix]
|
||||
concatenate_matrix(*matrix) if matrix
|
||||
|
||||
if xobject.hash[:Subtype] == :Form
|
||||
form = PDF::Reader::FormXObject.new(@page, xobject, :cache => @cache)
|
||||
@font_stack.unshift(form.font_objects)
|
||||
@xobject_stack.unshift(form.xobjects)
|
||||
yield form if block_given?
|
||||
@font_stack.shift
|
||||
@xobject_stack.shift
|
||||
else
|
||||
yield xobject if block_given?
|
||||
end
|
||||
|
||||
restore_graphics_state
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Public Visible State
|
||||
#####################################################
|
||||
|
||||
# transform x and y co-ordinates from the current user space to the
|
||||
# underlying device space.
|
||||
#
|
||||
def ctm_transform(x, y)
|
||||
[
|
||||
(ctm.a * x) + (ctm.c * y) + (ctm.e),
|
||||
(ctm.b * x) + (ctm.d * y) + (ctm.f)
|
||||
]
|
||||
end
|
||||
|
||||
# transform x and y co-ordinates from the current text space to the
|
||||
# underlying device space.
|
||||
#
|
||||
# transforming (0,0) is a really common case, so optimise for it to
|
||||
# avoid unnecessary object allocations
|
||||
#
|
||||
def trm_transform(x, y)
|
||||
trm = text_rendering_matrix
|
||||
if x == 0 && y == 0
|
||||
[trm.e, trm.f]
|
||||
else
|
||||
[
|
||||
(trm.a * x) + (trm.c * y) + (trm.e),
|
||||
(trm.b * x) + (trm.d * y) + (trm.f)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def current_font
|
||||
find_font(state[:text_font])
|
||||
end
|
||||
|
||||
def find_font(label)
|
||||
dict = @font_stack.detect { |fonts|
|
||||
fonts.has_key?(label)
|
||||
}
|
||||
dict ? dict[label] : nil
|
||||
end
|
||||
|
||||
def find_color_space(label)
|
||||
dict = @cs_stack.detect { |colorspaces|
|
||||
colorspaces.has_key?(label)
|
||||
}
|
||||
dict ? dict[label] : nil
|
||||
end
|
||||
|
||||
def find_xobject(label)
|
||||
dict = @xobject_stack.detect { |xobjects|
|
||||
xobjects.has_key?(label)
|
||||
}
|
||||
dict ? dict[label] : nil
|
||||
end
|
||||
|
||||
# when save_graphics_state is called, we need to push a new copy of the
|
||||
# current state onto the stack. That way any modifications to the state
|
||||
# will be undone once restore_graphics_state is called.
|
||||
#
|
||||
def stack_depth
|
||||
@stack.size
|
||||
end
|
||||
|
||||
# This returns a deep clone of the current state, ensuring changes are
|
||||
# keep separate from earlier states.
|
||||
#
|
||||
# Marshal is used to round-trip the state through a string to easily
|
||||
# perform the deep clone. Kinda hacky, but effective.
|
||||
#
|
||||
def clone_state
|
||||
if @stack.empty?
|
||||
{}
|
||||
else
|
||||
Marshal.load Marshal.dump(@stack.last)
|
||||
end
|
||||
end
|
||||
|
||||
# after each glyph is painted onto the page the text matrix must be
|
||||
# modified. There's no defined operator for this, but depending on
|
||||
# the use case some receivers may need to mutate the state with this
|
||||
# while walking a page.
|
||||
#
|
||||
# NOTE: some of the variable names in this method are obscure because
|
||||
# they mirror variable names from the PDF spec
|
||||
#
|
||||
# NOTE: see Section 9.4.4, PDF 32000-1:2008, pp 252
|
||||
#
|
||||
# Arguments:
|
||||
#
|
||||
# w0 - the glyph width in *text space*. This generally means the width
|
||||
# in glyph space should be divded by 1000 before being passed to
|
||||
# this function
|
||||
# tj - any kerning that should be applied to the text matrix before the
|
||||
# following glyph is painted. This is usually the numeric arguments
|
||||
# in the array passed to a TJ operator
|
||||
# word_boundary - a boolean indicating if a word boundary was just
|
||||
# reached. Depending on the current state extra space
|
||||
# may need to be added
|
||||
#
|
||||
def process_glyph_displacement(w0, tj, word_boundary)
|
||||
fs = state[:text_font_size]
|
||||
tc = state[:char_spacing]
|
||||
if word_boundary
|
||||
tw = state[:word_spacing]
|
||||
else
|
||||
tw = 0
|
||||
end
|
||||
th = state[:h_scaling]
|
||||
# optimise the common path to reduce Float allocations
|
||||
if th == 1 && tj == 0 && tc == 0 && tw == 0
|
||||
tx = w0 * fs
|
||||
elsif tj != 0
|
||||
# don't apply spacing to TJ displacement
|
||||
tx = (w0 - (tj/1000.0)) * fs * th
|
||||
else
|
||||
# apply horizontal scaling to spacing values but not font size
|
||||
tx = ((w0 * fs) + tc + tw) * th
|
||||
end
|
||||
# TODO: support ty > 0
|
||||
ty = 0
|
||||
temp = TransformationMatrix.new(1, 0,
|
||||
0, 1,
|
||||
tx, ty)
|
||||
@text_matrix = temp.multiply!(
|
||||
@text_matrix.a, @text_matrix.b,
|
||||
@text_matrix.c, @text_matrix.d,
|
||||
@text_matrix.e, @text_matrix.f
|
||||
)
|
||||
@font_size = @text_rendering_matrix = nil # invalidate cached value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# used for many and varied text positioning calculations. We potentially
|
||||
# need to access the results of this method many times when working with
|
||||
# text, so memoize it
|
||||
#
|
||||
def text_rendering_matrix
|
||||
@text_rendering_matrix ||= begin
|
||||
state_matrix = TransformationMatrix.new(
|
||||
state[:text_font_size] * state[:h_scaling], 0,
|
||||
0, state[:text_font_size],
|
||||
0, state[:text_rise]
|
||||
)
|
||||
state_matrix.multiply!(
|
||||
@text_matrix.a, @text_matrix.b,
|
||||
@text_matrix.c, @text_matrix.d,
|
||||
@text_matrix.e, @text_matrix.f
|
||||
)
|
||||
state_matrix.multiply!(
|
||||
ctm.a, ctm.b,
|
||||
ctm.c, ctm.d,
|
||||
ctm.e, ctm.f
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# return the current transformation matrix
|
||||
#
|
||||
def ctm
|
||||
state[:ctm]
|
||||
end
|
||||
|
||||
def state
|
||||
@stack.last
|
||||
end
|
||||
|
||||
# wrap the raw PDF Font objects in handy ruby Font objects.
|
||||
#
|
||||
def build_fonts(raw_fonts)
|
||||
wrapped_fonts = raw_fonts.map { |label, font|
|
||||
[label, PDF::Reader::Font.new(@objects, @objects.deref_hash(font) || {})]
|
||||
}
|
||||
|
||||
::Hash[wrapped_fonts]
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Low-level Matrix Operations
|
||||
#####################################################
|
||||
|
||||
# This class uses 3x3 matrices to represent geometric transformations
|
||||
# These matrices are represented by arrays with 9 elements
|
||||
# The array [a,b,c,d,e,f,g,h,i] would represent a matrix like:
|
||||
# a b c
|
||||
# d e f
|
||||
# g h i
|
||||
|
||||
def identity_matrix
|
||||
TransformationMatrix.new(1, 0,
|
||||
0, 1,
|
||||
0, 0)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,198 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'forwardable'
|
||||
require 'pdf/reader/page_layout'
|
||||
|
||||
module PDF
|
||||
class Reader
|
||||
|
||||
# Builds a UTF-8 string of all the text on a single page by processing all
|
||||
# the operaters in a content stream.
|
||||
#
|
||||
class PageTextReceiver
|
||||
extend Forwardable
|
||||
|
||||
SPACE = " "
|
||||
|
||||
attr_reader :state, :options
|
||||
|
||||
########## BEGIN FORWARDERS ##########
|
||||
# Graphics State Operators
|
||||
def_delegators :@state, :save_graphics_state, :restore_graphics_state
|
||||
|
||||
# Matrix Operators
|
||||
def_delegators :@state, :concatenate_matrix
|
||||
|
||||
# Text Object Operators
|
||||
def_delegators :@state, :begin_text_object, :end_text_object
|
||||
|
||||
# Text State Operators
|
||||
def_delegators :@state, :set_character_spacing, :set_horizontal_text_scaling
|
||||
def_delegators :@state, :set_text_font_and_size, :font_size
|
||||
def_delegators :@state, :set_text_leading, :set_text_rendering_mode
|
||||
def_delegators :@state, :set_text_rise, :set_word_spacing
|
||||
|
||||
# Text Positioning Operators
|
||||
def_delegators :@state, :move_text_position, :move_text_position_and_set_leading
|
||||
def_delegators :@state, :set_text_matrix_and_text_line_matrix, :move_to_start_of_next_line
|
||||
########## END FORWARDERS ##########
|
||||
|
||||
# starting a new page
|
||||
def page=(page)
|
||||
@state = PageState.new(page)
|
||||
@page = page
|
||||
@content = []
|
||||
@characters = []
|
||||
end
|
||||
|
||||
def runs(opts = {})
|
||||
runs = @characters
|
||||
|
||||
if rect = opts.fetch(:rect, @page.rectangles[:CropBox])
|
||||
runs = BoundingRectangleRunsFilter.runs_within_rect(runs, rect)
|
||||
end
|
||||
|
||||
if opts.fetch(:skip_zero_width, true)
|
||||
runs = ZeroWidthRunsFilter.exclude_zero_width_runs(runs)
|
||||
end
|
||||
|
||||
if opts.fetch(:skip_overlapping, true)
|
||||
runs = OverlappingRunsFilter.exclude_redundant_runs(runs)
|
||||
end
|
||||
|
||||
runs = NoTextFilter.exclude_empty_strings(runs)
|
||||
|
||||
if opts.fetch(:merge, true)
|
||||
runs = merge_runs(runs)
|
||||
end
|
||||
|
||||
if (only_filter = opts.fetch(:only, nil))
|
||||
runs = AdvancedTextRunFilter.only(runs, only_filter)
|
||||
end
|
||||
|
||||
if (exclude_filter = opts.fetch(:exclude, nil))
|
||||
runs = AdvancedTextRunFilter.exclude(runs, exclude_filter)
|
||||
end
|
||||
|
||||
runs
|
||||
end
|
||||
|
||||
# deprecated
|
||||
def content
|
||||
mediabox = @page.rectangles[:MediaBox]
|
||||
PageLayout.new(runs, mediabox).to_s
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text Showing Operators
|
||||
#####################################################
|
||||
# record text that is drawn on the page
|
||||
def show_text(string) # Tj (AWAY)
|
||||
internal_show_text(string)
|
||||
end
|
||||
|
||||
def show_text_with_positioning(params) # TJ [(A) 120 (WA) 20 (Y)]
|
||||
params.each do |arg|
|
||||
if arg.is_a?(String)
|
||||
internal_show_text(arg)
|
||||
elsif arg.is_a?(Numeric)
|
||||
@state.process_glyph_displacement(0, arg, false)
|
||||
else
|
||||
# skip it
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def move_to_next_line_and_show_text(str) # '
|
||||
@state.move_to_start_of_next_line
|
||||
show_text(str)
|
||||
end
|
||||
|
||||
def set_spacing_next_line_show_text(aw, ac, string) # "
|
||||
@state.set_word_spacing(aw)
|
||||
@state.set_character_spacing(ac)
|
||||
move_to_next_line_and_show_text(string)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# XObjects
|
||||
#####################################################
|
||||
def invoke_xobject(label)
|
||||
@state.invoke_xobject(label) do |xobj|
|
||||
case xobj
|
||||
when PDF::Reader::FormXObject then
|
||||
xobj.walk(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def internal_show_text(string)
|
||||
PDF::Reader::Error.validate_type_as_malformed(string, "string", String)
|
||||
if @state.current_font.nil?
|
||||
raise PDF::Reader::MalformedPDFError, "current font is invalid"
|
||||
end
|
||||
glyphs = @state.current_font.unpack(string)
|
||||
glyphs.each_with_index do |glyph_code, index|
|
||||
# paint the current glyph
|
||||
newx, newy = @state.trm_transform(0,0)
|
||||
newx, newy = apply_rotation(newx, newy)
|
||||
|
||||
utf8_chars = @state.current_font.to_utf8(glyph_code)
|
||||
|
||||
# apply to glyph displacment for the current glyph so the next
|
||||
# glyph will appear in the correct position
|
||||
glyph_width = @state.current_font.glyph_width_in_text_space(glyph_code)
|
||||
th = 1
|
||||
scaled_glyph_width = glyph_width * @state.font_size * th
|
||||
unless utf8_chars == SPACE
|
||||
@characters << TextRun.new(newx, newy, scaled_glyph_width, @state.font_size, utf8_chars)
|
||||
end
|
||||
@state.process_glyph_displacement(glyph_width, 0, utf8_chars == SPACE)
|
||||
end
|
||||
end
|
||||
|
||||
def apply_rotation(x, y)
|
||||
if @page.rotate == 90
|
||||
tmp = x
|
||||
x = y
|
||||
y = tmp * -1
|
||||
elsif @page.rotate == 180
|
||||
y *= -1
|
||||
x *= -1
|
||||
elsif @page.rotate == 270
|
||||
tmp = y
|
||||
y = x
|
||||
x = tmp * -1
|
||||
end
|
||||
return x, y
|
||||
end
|
||||
|
||||
# take a collection of TextRun objects and merge any that are in close
|
||||
# proximity
|
||||
def merge_runs(runs)
|
||||
runs.group_by { |char|
|
||||
char.y.to_i
|
||||
}.map { |y, chars|
|
||||
group_chars_into_runs(chars.sort)
|
||||
}.flatten.sort
|
||||
end
|
||||
|
||||
def group_chars_into_runs(chars)
|
||||
chars.each_with_object([]) do |char, runs|
|
||||
if runs.empty?
|
||||
runs << char
|
||||
elsif runs.last.mergable?(char)
|
||||
runs[-1] = runs.last + char
|
||||
else
|
||||
runs << char
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,187 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
################################################################################
|
||||
# == Text Callbacks
|
||||
#
|
||||
# - end_text_object
|
||||
# - move_to_start_of_next_line
|
||||
# - set_character_spacing
|
||||
# - move_text_position
|
||||
# - move_text_position_and_set_leading
|
||||
# - set_text_font_and_size
|
||||
# - show_text
|
||||
# - show_text_with_positioning
|
||||
# - set_text_leading
|
||||
# - set_text_matrix_and_text_line_matrix
|
||||
# - set_text_rendering_mode
|
||||
# - set_text_rise
|
||||
# - set_word_spacing
|
||||
# - set_horizontal_text_scaling
|
||||
# - move_to_next_line_and_show_text
|
||||
# - set_spacing_next_line_show_text
|
||||
#
|
||||
# == Graphics Callbacks
|
||||
# - close_fill_stroke
|
||||
# - fill_stroke
|
||||
# - close_fill_stroke_with_even_odd
|
||||
# - fill_stroke_with_even_odd
|
||||
# - begin_marked_content_with_pl
|
||||
# - begin_inline_image
|
||||
# - begin_marked_content
|
||||
# - begin_text_object
|
||||
# - append_curved_segment
|
||||
# - concatenate_matrix
|
||||
# - set_stroke_color_space
|
||||
# - set_nonstroke_color_space
|
||||
# - set_line_dash
|
||||
# - set_glyph_width
|
||||
# - set_glyph_width_and_bounding_box
|
||||
# - invoke_xobject
|
||||
# - define_marked_content_with_pl
|
||||
# - end_inline_image
|
||||
# - end_marked_content
|
||||
# - fill_path_with_nonzero
|
||||
# - fill_path_with_nonzero
|
||||
# - fill_path_with_even_odd
|
||||
# - set_gray_for_stroking
|
||||
# - set_gray_for_nonstroking
|
||||
# - set_graphics_state_parameters
|
||||
# - close_subpath
|
||||
# - set_flatness_tolerance
|
||||
# - begin_inline_image_data
|
||||
# - set_line_join_style
|
||||
# - set_line_cap_style
|
||||
# - set_cmyk_color_for_stroking,
|
||||
# - set_cmyk_color_for_nonstroking
|
||||
# - append_line
|
||||
# - begin_new_subpath
|
||||
# - set_miter_limit
|
||||
# - define_marked_content_point
|
||||
# - end_path
|
||||
# - save_graphics_state
|
||||
# - restore_graphics_state
|
||||
# - append_rectangle
|
||||
# - set_rgb_color_for_stroking
|
||||
# - set_rgb_color_for_nonstroking
|
||||
# - set_color_rendering_intent
|
||||
# - close_and_stroke_path
|
||||
# - stroke_path
|
||||
# - set_color_for_stroking
|
||||
# - set_color_for_nonstroking
|
||||
# - set_color_for_stroking_and_special
|
||||
# - set_color_for_nonstroking_and_special
|
||||
# - paint_area_with_shading_pattern
|
||||
# - append_curved_segment_initial_point_replicated
|
||||
# - set_line_width
|
||||
# - set_clipping_path_with_nonzero
|
||||
# - set_clipping_path_with_even_odd
|
||||
# - append_curved_segment_final_point_replicated
|
||||
#
|
||||
class PagesStrategy # :nodoc:
|
||||
OPERATORS = {
|
||||
'b' => :close_fill_stroke,
|
||||
'B' => :fill_stroke,
|
||||
'b*' => :close_fill_stroke_with_even_odd,
|
||||
'B*' => :fill_stroke_with_even_odd,
|
||||
'BDC' => :begin_marked_content_with_pl,
|
||||
'BI' => :begin_inline_image,
|
||||
'BMC' => :begin_marked_content,
|
||||
'BT' => :begin_text_object,
|
||||
'BX' => :begin_compatibility_section,
|
||||
'c' => :append_curved_segment,
|
||||
'cm' => :concatenate_matrix,
|
||||
'CS' => :set_stroke_color_space,
|
||||
'cs' => :set_nonstroke_color_space,
|
||||
'd' => :set_line_dash,
|
||||
'd0' => :set_glyph_width,
|
||||
'd1' => :set_glyph_width_and_bounding_box,
|
||||
'Do' => :invoke_xobject,
|
||||
'DP' => :define_marked_content_with_pl,
|
||||
'EI' => :end_inline_image,
|
||||
'EMC' => :end_marked_content,
|
||||
'ET' => :end_text_object,
|
||||
'EX' => :end_compatibility_section,
|
||||
'f' => :fill_path_with_nonzero,
|
||||
'F' => :fill_path_with_nonzero,
|
||||
'f*' => :fill_path_with_even_odd,
|
||||
'G' => :set_gray_for_stroking,
|
||||
'g' => :set_gray_for_nonstroking,
|
||||
'gs' => :set_graphics_state_parameters,
|
||||
'h' => :close_subpath,
|
||||
'i' => :set_flatness_tolerance,
|
||||
'ID' => :begin_inline_image_data,
|
||||
'j' => :set_line_join_style,
|
||||
'J' => :set_line_cap_style,
|
||||
'K' => :set_cmyk_color_for_stroking,
|
||||
'k' => :set_cmyk_color_for_nonstroking,
|
||||
'l' => :append_line,
|
||||
'm' => :begin_new_subpath,
|
||||
'M' => :set_miter_limit,
|
||||
'MP' => :define_marked_content_point,
|
||||
'n' => :end_path,
|
||||
'q' => :save_graphics_state,
|
||||
'Q' => :restore_graphics_state,
|
||||
're' => :append_rectangle,
|
||||
'RG' => :set_rgb_color_for_stroking,
|
||||
'rg' => :set_rgb_color_for_nonstroking,
|
||||
'ri' => :set_color_rendering_intent,
|
||||
's' => :close_and_stroke_path,
|
||||
'S' => :stroke_path,
|
||||
'SC' => :set_color_for_stroking,
|
||||
'sc' => :set_color_for_nonstroking,
|
||||
'SCN' => :set_color_for_stroking_and_special,
|
||||
'scn' => :set_color_for_nonstroking_and_special,
|
||||
'sh' => :paint_area_with_shading_pattern,
|
||||
'T*' => :move_to_start_of_next_line,
|
||||
'Tc' => :set_character_spacing,
|
||||
'Td' => :move_text_position,
|
||||
'TD' => :move_text_position_and_set_leading,
|
||||
'Tf' => :set_text_font_and_size,
|
||||
'Tj' => :show_text,
|
||||
'TJ' => :show_text_with_positioning,
|
||||
'TL' => :set_text_leading,
|
||||
'Tm' => :set_text_matrix_and_text_line_matrix,
|
||||
'Tr' => :set_text_rendering_mode,
|
||||
'Ts' => :set_text_rise,
|
||||
'Tw' => :set_word_spacing,
|
||||
'Tz' => :set_horizontal_text_scaling,
|
||||
'v' => :append_curved_segment_initial_point_replicated,
|
||||
'w' => :set_line_width,
|
||||
'W' => :set_clipping_path_with_nonzero,
|
||||
'W*' => :set_clipping_path_with_even_odd,
|
||||
'y' => :append_curved_segment_final_point_replicated,
|
||||
'\'' => :move_to_next_line_and_show_text,
|
||||
'"' => :set_spacing_next_line_show_text,
|
||||
}
|
||||
end
|
||||
################################################################################
|
||||
end
|
||||
################################################################################
|
||||
@@ -0,0 +1,240 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
################################################################################
|
||||
# An internal PDF::Reader class that reads objects from the PDF file and converts
|
||||
# them into useable ruby objects (hash's, arrays, true, false, etc)
|
||||
class Parser
|
||||
|
||||
TOKEN_STRATEGY = proc { |parser, token| Token.new(token) }
|
||||
|
||||
STRATEGIES = {
|
||||
"/" => proc { |parser, token| parser.send(:pdf_name) },
|
||||
"<<" => proc { |parser, token| parser.send(:dictionary) },
|
||||
"[" => proc { |parser, token| parser.send(:array) },
|
||||
"(" => proc { |parser, token| parser.send(:string) },
|
||||
"<" => proc { |parser, token| parser.send(:hex_string) },
|
||||
|
||||
nil => proc { nil },
|
||||
"true" => proc { true },
|
||||
"false" => proc { false },
|
||||
"null" => proc { nil },
|
||||
|
||||
"obj" => TOKEN_STRATEGY,
|
||||
"endobj" => TOKEN_STRATEGY,
|
||||
"stream" => TOKEN_STRATEGY,
|
||||
"endstream" => TOKEN_STRATEGY,
|
||||
">>" => TOKEN_STRATEGY,
|
||||
"]" => TOKEN_STRATEGY,
|
||||
">" => TOKEN_STRATEGY,
|
||||
")" => TOKEN_STRATEGY
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Create a new parser around a PDF::Reader::Buffer object
|
||||
#
|
||||
# buffer - a PDF::Reader::Buffer object that contains PDF data
|
||||
# objects - a PDF::Reader::ObjectHash object that can return objects from the PDF file
|
||||
def initialize(buffer, objects=nil)
|
||||
@buffer = buffer
|
||||
@objects = objects
|
||||
end
|
||||
################################################################################
|
||||
# Reads the next token from the underlying buffer and convets it to an appropriate
|
||||
# object
|
||||
#
|
||||
# operators - a hash of supported operators to read from the underlying buffer.
|
||||
def parse_token(operators={})
|
||||
token = @buffer.token
|
||||
|
||||
if STRATEGIES.has_key? token
|
||||
STRATEGIES[token].call(self, token)
|
||||
elsif token.is_a? PDF::Reader::Reference
|
||||
token
|
||||
elsif operators.has_key? token
|
||||
Token.new(token)
|
||||
elsif token.frozen?
|
||||
token
|
||||
elsif token =~ /\d*\.\d/
|
||||
token.to_f
|
||||
else
|
||||
token.to_i
|
||||
end
|
||||
end
|
||||
################################################################################
|
||||
# Reads an entire PDF object from the buffer and returns it as a Ruby String.
|
||||
# If the object is a content stream, returns both the stream and the dictionary
|
||||
# that describes it
|
||||
#
|
||||
# id - the object ID to return
|
||||
# gen - the object revision number to return
|
||||
def object(id, gen)
|
||||
idCheck = parse_token
|
||||
|
||||
# Sometimes the xref table is corrupt and points to an offset slightly too early in the file.
|
||||
# check the next token, maybe we can find the start of the object we're looking for
|
||||
if idCheck != id
|
||||
Error.assert_equal(parse_token, id)
|
||||
end
|
||||
Error.assert_equal(parse_token, gen)
|
||||
Error.str_assert(parse_token, "obj")
|
||||
|
||||
obj = parse_token
|
||||
post_obj = parse_token
|
||||
|
||||
if obj.is_a?(Hash) && post_obj == "stream"
|
||||
stream(obj)
|
||||
else
|
||||
obj
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
################################################################################
|
||||
# reads a PDF dict from the buffer and converts it to a Ruby Hash.
|
||||
def dictionary
|
||||
dict = {}
|
||||
|
||||
loop do
|
||||
key = parse_token
|
||||
break if key.kind_of?(Token) and key == ">>"
|
||||
raise MalformedPDFError, "unterminated dict" if @buffer.empty?
|
||||
PDF::Reader::Error.validate_type_as_malformed(key, "Dictionary key", Symbol)
|
||||
|
||||
value = parse_token
|
||||
value.kind_of?(Token) and Error.str_assert_not(value, ">>")
|
||||
dict[key] = value
|
||||
end
|
||||
|
||||
dict
|
||||
end
|
||||
################################################################################
|
||||
# reads a PDF name from the buffer and converts it to a Ruby Symbol
|
||||
def pdf_name
|
||||
tok = @buffer.token
|
||||
tok = tok.dup.gsub(/#([A-Fa-f0-9]{2})/) do |match|
|
||||
match[1, 2].hex.chr
|
||||
end
|
||||
tok.to_sym
|
||||
end
|
||||
################################################################################
|
||||
# reads a PDF array from the buffer and converts it to a Ruby Array.
|
||||
def array
|
||||
a = []
|
||||
|
||||
loop do
|
||||
item = parse_token
|
||||
break if item.kind_of?(Token) and item == "]"
|
||||
raise MalformedPDFError, "unterminated array" if @buffer.empty?
|
||||
a << item
|
||||
end
|
||||
|
||||
a
|
||||
end
|
||||
################################################################################
|
||||
# Reads a PDF hex string from the buffer and converts it to a Ruby String
|
||||
def hex_string
|
||||
str = "".dup
|
||||
|
||||
loop do
|
||||
token = @buffer.token
|
||||
break if token == ">"
|
||||
raise MalformedPDFError, "unterminated hex string" if @buffer.empty?
|
||||
str << token
|
||||
end
|
||||
|
||||
# add a missing digit if required, as required by the spec
|
||||
str << "0" unless str.size % 2 == 0
|
||||
[str].pack('H*')
|
||||
end
|
||||
################################################################################
|
||||
# Reads a PDF String from the buffer and converts it to a Ruby String
|
||||
def string
|
||||
str = @buffer.token
|
||||
return "".dup.force_encoding("binary") if str == ")"
|
||||
Error.assert_equal(parse_token, ")")
|
||||
|
||||
str.gsub!(/\\(\r\n|[nrtbf()\\\n\r]|([0-7]{1,3}))?|\r\n?/m) do |match|
|
||||
if $2.nil? # not octal digits
|
||||
MAPPING[match] || "".dup
|
||||
else # must be octal digits
|
||||
($2.oct & 0xff).chr # ignore high level overflow
|
||||
end
|
||||
end
|
||||
str.force_encoding("binary")
|
||||
end
|
||||
|
||||
MAPPING = {
|
||||
"\r" => "\n",
|
||||
"\r\n" => "\n",
|
||||
"\\n" => "\n",
|
||||
"\\r" => "\r",
|
||||
"\\t" => "\t",
|
||||
"\\b" => "\b",
|
||||
"\\f" => "\f",
|
||||
"\\(" => "(",
|
||||
"\\)" => ")",
|
||||
"\\\\" => "\\",
|
||||
"\\\n" => "",
|
||||
"\\\r" => "",
|
||||
"\\\r\n" => "",
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Decodes the contents of a PDF Stream and returns it as a Ruby String.
|
||||
def stream(dict)
|
||||
raise MalformedPDFError, "PDF malformed, missing stream length" unless dict.has_key?(:Length)
|
||||
if @objects
|
||||
length = @objects.deref_integer(dict[:Length])
|
||||
if dict[:Filter]
|
||||
dict[:Filter] = @objects.deref_name_or_array(dict[:Filter])
|
||||
end
|
||||
else
|
||||
length = dict[:Length] || 0
|
||||
end
|
||||
|
||||
PDF::Reader::Error.validate_type_as_malformed(length, "length", Numeric)
|
||||
|
||||
data = @buffer.read(length, :skip_eol => true)
|
||||
|
||||
Error.str_assert(parse_token, "endstream")
|
||||
|
||||
# We used to assert that the stream had the correct closing token, but it doesn't *really*
|
||||
# matter if it's missing, and other readers seems to handle its absence just fine
|
||||
# Error.str_assert(parse_token, "endobj")
|
||||
|
||||
PDF::Reader::Stream.new(dict, data)
|
||||
end
|
||||
################################################################################
|
||||
end
|
||||
################################################################################
|
||||
end
|
||||
################################################################################
|
||||
@@ -0,0 +1,25 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module PDF
|
||||
class Reader
|
||||
|
||||
# PDFs are all about positioning content on a page, so there's lots of need to
|
||||
# work with a set of X,Y coordinates.
|
||||
#
|
||||
class Point
|
||||
|
||||
attr_reader :x, :y
|
||||
|
||||
def initialize(x, y)
|
||||
@x, @y = x, y
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other.respond_to?(:x) && other.respond_to?(:y) && x == other.x && y == other.y
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
# A simple receiver that prints all operaters and parameters in the content
|
||||
# stream of a single page.
|
||||
#
|
||||
class PrintReceiver
|
||||
|
||||
attr_accessor :callbacks
|
||||
|
||||
def initialize
|
||||
@callbacks = []
|
||||
end
|
||||
|
||||
def respond_to?(meth)
|
||||
true
|
||||
end
|
||||
|
||||
def method_missing(methodname, *args)
|
||||
puts "#{methodname} => #{args.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'digest/md5'
|
||||
require 'rc4'
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# Decrypts data using the RC4 algorithim defined in the PDF spec. Requires
|
||||
# a decryption key, which is usually generated by PDF::Reader::StandardKeyBuilder
|
||||
#
|
||||
class Rc4SecurityHandler
|
||||
|
||||
def initialize(key)
|
||||
@encrypt_key = key
|
||||
end
|
||||
|
||||
##7.6.2 General Encryption Algorithm
|
||||
#
|
||||
# Algorithm 1: Encryption of data using the RC4 algorithm
|
||||
#
|
||||
# version <=3 or (version == 4 and CFM == V2)
|
||||
#
|
||||
# buf - a string to decrypt
|
||||
# ref - a PDF::Reader::Reference for the object to decrypt
|
||||
#
|
||||
def decrypt( buf, ref )
|
||||
objKey = @encrypt_key.dup
|
||||
(0..2).each { |e| objKey << (ref.id >> e*8 & 0xFF ) }
|
||||
(0..1).each { |e| objKey << (ref.gen >> e*8 & 0xFF ) }
|
||||
length = objKey.length < 16 ? objKey.length : 16
|
||||
rc4 = RC4.new( Digest::MD5.digest(objKey)[0,length] )
|
||||
rc4.decrypt(buf)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,113 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module PDF
|
||||
class Reader
|
||||
|
||||
# PDFs represent rectangles all over the place. They're 4 element arrays, like this:
|
||||
#
|
||||
# [A, B, C, D]
|
||||
#
|
||||
# Four element arrays are yucky to work with though, so here's a class that's better.
|
||||
# Initialize it with the 4 elements, and get utility functions (width, height, etc)
|
||||
# for free.
|
||||
#
|
||||
# By convention the first two elements are x1, y1, the co-ords for the bottom left corner
|
||||
# of the rectangle. The third and fourth elements are x2, y2, the co-ords for the top left
|
||||
# corner of the rectangle. It's valid for the alternative corners to be used though, so
|
||||
# we don't assume which is which.
|
||||
#
|
||||
class Rectangle
|
||||
|
||||
attr_reader :bottom_left, :bottom_right, :top_left, :top_right
|
||||
|
||||
def initialize(x1, y1, x2, y2)
|
||||
set_corners(x1, y1, x2, y2)
|
||||
end
|
||||
|
||||
def self.from_array(arr)
|
||||
if arr.size != 4
|
||||
raise ArgumentError, "Only 4-element Arrays can be converted to a Rectangle"
|
||||
end
|
||||
|
||||
PDF::Reader::Rectangle.new(
|
||||
arr[0].to_f,
|
||||
arr[1].to_f,
|
||||
arr[2].to_f,
|
||||
arr[3].to_f,
|
||||
)
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
to_a == other.to_a
|
||||
end
|
||||
|
||||
def height
|
||||
top_right.y - bottom_right.y
|
||||
end
|
||||
|
||||
def width
|
||||
bottom_right.x - bottom_left.x
|
||||
end
|
||||
|
||||
def contains?(point)
|
||||
point.x >= bottom_left.x && point.x <= top_right.x &&
|
||||
point.y >= bottom_left.y && point.y <= top_right.y
|
||||
end
|
||||
|
||||
# A pdf-style 4-number array
|
||||
def to_a
|
||||
[
|
||||
bottom_left.x,
|
||||
bottom_left.y,
|
||||
top_right.x,
|
||||
top_right.y,
|
||||
]
|
||||
end
|
||||
|
||||
def apply_rotation(degrees)
|
||||
return if degrees != 90 && degrees != 180 && degrees != 270
|
||||
|
||||
if degrees == 90
|
||||
new_x1 = bottom_left.x
|
||||
new_y1 = bottom_left.y - width
|
||||
new_x2 = bottom_left.x + height
|
||||
new_y2 = bottom_left.y
|
||||
elsif degrees == 180
|
||||
new_x1 = bottom_left.x - width
|
||||
new_y1 = bottom_left.y - height
|
||||
new_x2 = bottom_left.x
|
||||
new_y2 = bottom_left.y
|
||||
elsif degrees == 270
|
||||
new_x1 = bottom_left.x - height
|
||||
new_y1 = bottom_left.y
|
||||
new_x2 = bottom_left.x
|
||||
new_y2 = bottom_left.y + width
|
||||
end
|
||||
set_corners(new_x1 || 0, new_y1 || 0, new_x2 || 0, new_y2 || 0)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_corners(x1, y1, x2, y2)
|
||||
@bottom_left = PDF::Reader::Point.new(
|
||||
[x1, x2].min,
|
||||
[y1, y2].min,
|
||||
)
|
||||
@bottom_right = PDF::Reader::Point.new(
|
||||
[x1, x2].max,
|
||||
[y1, y2].min,
|
||||
)
|
||||
@top_left = PDF::Reader::Point.new(
|
||||
[x1, x2].min,
|
||||
[y1, y2].max,
|
||||
)
|
||||
@top_right = PDF::Reader::Point.new(
|
||||
[x1, x2].max,
|
||||
[y1, y2].max,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,71 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
################################################################################
|
||||
# An internal PDF::Reader class that represents an indirect reference to a PDF Object
|
||||
class Reference
|
||||
attr_reader :id
|
||||
attr_reader :gen
|
||||
################################################################################
|
||||
# Create a new Reference to an object with the specified id and revision number
|
||||
def initialize(id, gen)
|
||||
@id, @gen = id, gen
|
||||
end
|
||||
################################################################################
|
||||
# returns the current Reference object in an array with a single element
|
||||
def to_a
|
||||
[self]
|
||||
end
|
||||
################################################################################
|
||||
# returns the ID of this reference. Use with caution, ignores the generation id
|
||||
def to_i
|
||||
self.id
|
||||
end
|
||||
################################################################################
|
||||
# returns true if the provided object points to the same PDF Object as the
|
||||
# current object
|
||||
def ==(obj)
|
||||
return false unless obj.kind_of?(PDF::Reader::Reference)
|
||||
|
||||
self.hash == obj.hash
|
||||
end
|
||||
alias :eql? :==
|
||||
################################################################################
|
||||
# returns a hash based on the PDF::Reference this object points to. Two
|
||||
# different Reference objects that point to the same PDF Object will
|
||||
# return an identical hash
|
||||
def hash
|
||||
"#{self.id}:#{self.gen}".hash
|
||||
end
|
||||
################################################################################
|
||||
end
|
||||
################################################################################
|
||||
end
|
||||
################################################################################
|
||||
@@ -0,0 +1,82 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Copyright (C) 2010 James Healy (jimmy@deefa.com)
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# An example receiver that just records all callbacks generated by parsing
|
||||
# a PDF file.
|
||||
#
|
||||
# Useful for testing the contents of a file in an rspec/test-unit suite.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# PDF::Reader.open("somefile.pdf") do |reader|
|
||||
# receiver = PDF::Reader::RegisterReceiver.new
|
||||
# reader.page(1).walk(receiver)
|
||||
# callback = receiver.first_occurance_of(:show_text)
|
||||
# callback[:args].first.should == "Hellow World"
|
||||
# end
|
||||
#
|
||||
class RegisterReceiver
|
||||
|
||||
attr_accessor :callbacks
|
||||
|
||||
def initialize
|
||||
@callbacks = []
|
||||
end
|
||||
|
||||
def respond_to?(meth)
|
||||
true
|
||||
end
|
||||
|
||||
def method_missing(methodname, *args)
|
||||
callbacks << {:name => methodname.to_sym, :args => args}
|
||||
end
|
||||
|
||||
# count the number of times a callback fired
|
||||
def count(methodname)
|
||||
callbacks.count { |cb| cb[:name] == methodname}
|
||||
end
|
||||
|
||||
# return the details for every time the specified callback was fired
|
||||
def all(methodname)
|
||||
callbacks.select { |cb| cb[:name] == methodname }
|
||||
end
|
||||
|
||||
def all_args(methodname)
|
||||
all(methodname).map { |cb| cb[:args] }
|
||||
end
|
||||
|
||||
# return the details for the first time the specified callback was fired
|
||||
def first_occurance_of(methodname)
|
||||
callbacks.find { |cb| cb[:name] == methodname }
|
||||
end
|
||||
|
||||
# return the details for the final time the specified callback was fired
|
||||
def final_occurance_of(methodname)
|
||||
all(methodname).last
|
||||
end
|
||||
|
||||
# return the first occurance of a particular series of callbacks
|
||||
def series(*methods)
|
||||
return nil if methods.empty?
|
||||
|
||||
indexes = (0..(callbacks.size-1))
|
||||
method_indexes = (0..(methods.size-1))
|
||||
|
||||
indexes.each do |idx|
|
||||
count = methods.size
|
||||
method_indexes.each do |midx|
|
||||
count -= 1 if callbacks[idx+midx] && callbacks[idx+midx][:name] == methods[midx]
|
||||
end
|
||||
if count == 0
|
||||
return callbacks[idx, methods.size]
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,101 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module PDF
|
||||
class Reader
|
||||
|
||||
# mixin for common methods in Page and FormXobjects
|
||||
#
|
||||
class Resources
|
||||
|
||||
def initialize(objects, resources)
|
||||
@objects = objects
|
||||
@resources = resources
|
||||
end
|
||||
|
||||
# Returns a Hash of color spaces that are available to this page
|
||||
#
|
||||
# NOTE: this method de-serialise objects from the underlying PDF
|
||||
# with no caching. You will want to cache the results instead
|
||||
# of calling it over and over.
|
||||
#
|
||||
def color_spaces
|
||||
@objects.deref_hash!(@resources[:ColorSpace]) || {}
|
||||
end
|
||||
|
||||
# Returns a Hash of fonts that are available to this page
|
||||
#
|
||||
# NOTE: this method de-serialise objects from the underlying PDF
|
||||
# with no caching. You will want to cache the results instead
|
||||
# of calling it over and over.
|
||||
#
|
||||
def fonts
|
||||
@objects.deref_hash!(@resources[:Font]) || {}
|
||||
end
|
||||
|
||||
# Returns a Hash of external graphic states that are available to this
|
||||
# page
|
||||
#
|
||||
# NOTE: this method de-serialise objects from the underlying PDF
|
||||
# with no caching. You will want to cache the results instead
|
||||
# of calling it over and over.
|
||||
#
|
||||
def graphic_states
|
||||
@objects.deref_hash!(@resources[:ExtGState]) || {}
|
||||
end
|
||||
|
||||
# Returns a Hash of patterns that are available to this page
|
||||
#
|
||||
# NOTE: this method de-serialise objects from the underlying PDF
|
||||
# with no caching. You will want to cache the results instead
|
||||
# of calling it over and over.
|
||||
#
|
||||
def patterns
|
||||
@objects.deref_hash!(@resources[:Pattern]) || {}
|
||||
end
|
||||
|
||||
# Returns an Array of procedure sets that are available to this page
|
||||
#
|
||||
# NOTE: this method de-serialise objects from the underlying PDF
|
||||
# with no caching. You will want to cache the results instead
|
||||
# of calling it over and over.
|
||||
#
|
||||
def procedure_sets
|
||||
@objects.deref_array!(@resources[:ProcSet]) || []
|
||||
end
|
||||
|
||||
# Returns a Hash of properties sets that are available to this page
|
||||
#
|
||||
# NOTE: this method de-serialise objects from the underlying PDF
|
||||
# with no caching. You will want to cache the results instead
|
||||
# of calling it over and over.
|
||||
#
|
||||
def properties
|
||||
@objects.deref_hash!(@resources[:Properties]) || {}
|
||||
end
|
||||
|
||||
# Returns a Hash of shadings that are available to this page
|
||||
#
|
||||
# NOTE: this method de-serialise objects from the underlying PDF
|
||||
# with no caching. You will want to cache the results instead
|
||||
# of calling it over and over.
|
||||
#
|
||||
def shadings
|
||||
@objects.deref_hash!(@resources[:Shading]) || {}
|
||||
end
|
||||
|
||||
# Returns a Hash of XObjects that are available to this page
|
||||
#
|
||||
# NOTE: this method de-serialise objects from the underlying PDF
|
||||
# with no caching. You will want to cache the results instead
|
||||
# of calling it over and over.
|
||||
#
|
||||
def xobjects
|
||||
dict = @objects.deref_hash!(@resources[:XObject]) || {}
|
||||
TypeCheck.cast_to_pdf_dict_with_stream_values!(dict)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
# Examines the Encrypt entry of a PDF trailer (if any) and returns an object that's
|
||||
# able to decrypt the file.
|
||||
class SecurityHandlerFactory
|
||||
|
||||
def self.build(encrypt, doc_id, password)
|
||||
doc_id ||= []
|
||||
password ||= ""
|
||||
|
||||
if encrypt.nil?
|
||||
NullSecurityHandler.new
|
||||
elsif standard?(encrypt)
|
||||
build_standard_handler(encrypt, doc_id, password)
|
||||
elsif standard_v5?(encrypt)
|
||||
build_v5_handler(encrypt, doc_id, password)
|
||||
else
|
||||
UnimplementedSecurityHandler.new
|
||||
end
|
||||
end
|
||||
|
||||
def self.build_standard_handler(encrypt, doc_id, password)
|
||||
encmeta = !encrypt.has_key?(:EncryptMetadata) || encrypt[:EncryptMetadata].to_s == "true"
|
||||
key_builder = StandardKeyBuilder.new(
|
||||
key_length: (encrypt[:Length] || 40).to_i,
|
||||
revision: encrypt[:R],
|
||||
owner_key: encrypt[:O],
|
||||
user_key: encrypt[:U],
|
||||
permissions: encrypt[:P].to_i,
|
||||
encrypted_metadata: encmeta,
|
||||
file_id: doc_id.first,
|
||||
)
|
||||
cfm = encrypt.fetch(:CF, {}).fetch(encrypt[:StmF], {}).fetch(:CFM, nil)
|
||||
if cfm == :AESV2
|
||||
AesV2SecurityHandler.new(key_builder.key(password))
|
||||
else
|
||||
Rc4SecurityHandler.new(key_builder.key(password))
|
||||
end
|
||||
end
|
||||
|
||||
def self.build_v5_handler(encrypt, doc_id, password)
|
||||
key_builder = KeyBuilderV5.new(
|
||||
owner_key: encrypt[:O],
|
||||
user_key: encrypt[:U],
|
||||
owner_encryption_key: encrypt[:OE],
|
||||
user_encryption_key: encrypt[:UE],
|
||||
)
|
||||
AesV3SecurityHandler.new(key_builder.key(password))
|
||||
end
|
||||
|
||||
# This handler supports all encryption that follows upto PDF 1.5 spec (revision 4)
|
||||
def self.standard?(encrypt)
|
||||
return false if encrypt.nil?
|
||||
|
||||
filter = encrypt.fetch(:Filter, :Standard)
|
||||
version = encrypt.fetch(:V, 0)
|
||||
algorithm = encrypt.fetch(:CF, {}).fetch(encrypt[:StmF], {}).fetch(:CFM, nil)
|
||||
(filter == :Standard) && (encrypt[:StmF] == encrypt[:StrF]) &&
|
||||
(version <= 3 || (version == 4 && ((algorithm == :V2) || (algorithm == :AESV2))))
|
||||
end
|
||||
|
||||
# This handler supports both
|
||||
# - AES-256 encryption defined in PDF 1.7 Extension Level 3 ('revision 5')
|
||||
# - AES-256 encryption defined in PDF 2.0 ('revision 6')
|
||||
def self.standard_v5?(encrypt)
|
||||
return false if encrypt.nil?
|
||||
|
||||
filter = encrypt.fetch(:Filter, :Standard)
|
||||
version = encrypt.fetch(:V, 0)
|
||||
revision = encrypt.fetch(:R, 0)
|
||||
algorithm = encrypt.fetch(:CF, {}).fetch(encrypt[:StmF], {}).fetch(:CFM, nil)
|
||||
(filter == :Standard) && (encrypt[:StmF] == encrypt[:StrF]) &&
|
||||
((version == 5) && (revision == 5 || revision == 6) && (algorithm == :AESV3))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,157 @@
|
||||
# coding: utf-8
|
||||
|
||||
require 'digest/md5'
|
||||
require 'rc4'
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# Processes the Encrypt dict from an encrypted PDF and a user provided
|
||||
# password and returns a key that can decrypt the file.
|
||||
#
|
||||
# This can generate a key compatible with the following standard encryption algorithms:
|
||||
#
|
||||
# * Version 1-3, all variants
|
||||
# * Version 4, V2 (RC4) and AESV2
|
||||
#
|
||||
class StandardKeyBuilder
|
||||
|
||||
## 7.6.3.3 Encryption Key Algorithm (pp61)
|
||||
#
|
||||
# needs a document's user password to build a key for decrypting an
|
||||
# encrypted PDF document
|
||||
#
|
||||
PassPadBytes = [ 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41,
|
||||
0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08,
|
||||
0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80,
|
||||
0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a ]
|
||||
|
||||
def initialize(opts = {})
|
||||
@key_length = opts[:key_length].to_i/8
|
||||
@revision = opts[:revision].to_i
|
||||
@owner_key = opts[:owner_key]
|
||||
@user_key = opts[:user_key]
|
||||
@permissions = opts[:permissions].to_i
|
||||
@encryptMeta = opts.fetch(:encrypted_metadata, true)
|
||||
@file_id = opts[:file_id] || ""
|
||||
|
||||
if @key_length != 5 && @key_length != 16
|
||||
msg = "StandardKeyBuilder only supports 40 and 128 bit\
|
||||
encryption (#{@key_length * 8}bit)"
|
||||
raise UnsupportedFeatureError, msg
|
||||
end
|
||||
end
|
||||
|
||||
# Takes a string containing a user provided password.
|
||||
#
|
||||
# If the password matches the file, then a string containing a key suitable for
|
||||
# decrypting the file will be returned. If the password doesn't match the file,
|
||||
# and exception will be raised.
|
||||
#
|
||||
def key(pass)
|
||||
pass ||= ""
|
||||
encrypt_key = auth_owner_pass(pass)
|
||||
encrypt_key ||= auth_user_pass(pass)
|
||||
|
||||
raise PDF::Reader::EncryptedPDFError, "Invalid password (#{pass})" if encrypt_key.nil?
|
||||
encrypt_key
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Pads supplied password to 32bytes using PassPadBytes as specified on
|
||||
# pp61 of spec
|
||||
def pad_pass(p="")
|
||||
if p.nil? || p.empty?
|
||||
PassPadBytes.pack('C*')
|
||||
else
|
||||
p[0, 32] + PassPadBytes[0, 32-p.length].pack('C*')
|
||||
end
|
||||
end
|
||||
|
||||
def xor_each_byte(buf, int)
|
||||
buf.each_byte.map{ |b| b^int}.pack("C*")
|
||||
end
|
||||
|
||||
## 7.6.3.4 Password Algorithms
|
||||
#
|
||||
# Algorithm 7 - Authenticating the Owner Password
|
||||
#
|
||||
# Used to test Owner passwords
|
||||
#
|
||||
# if the string is a valid owner password this will return the user
|
||||
# password that should be used to decrypt the document.
|
||||
#
|
||||
# if the supplied password is not a valid owner password for this document
|
||||
# then it returns nil
|
||||
#
|
||||
def auth_owner_pass(pass)
|
||||
md5 = Digest::MD5.digest(pad_pass(pass))
|
||||
if @revision > 2 then
|
||||
50.times { md5 = Digest::MD5.digest(md5) }
|
||||
keyBegins = md5[0, @key_length]
|
||||
#first iteration decrypt owner_key
|
||||
out = @owner_key
|
||||
#RC4 keyed with (keyBegins XOR with iteration #) to decrypt previous out
|
||||
19.downto(0).each { |i| out=RC4.new(xor_each_byte(keyBegins,i)).decrypt(out) }
|
||||
else
|
||||
out = RC4.new( md5[0, 5] ).decrypt( @owner_key )
|
||||
end
|
||||
# c) check output as user password
|
||||
auth_user_pass( out )
|
||||
end
|
||||
|
||||
# Algorithm 6 - Authenticating the User Password
|
||||
#
|
||||
# Used to test User passwords
|
||||
#
|
||||
# if the string is a valid user password this will return the user
|
||||
# password that should be used to decrypt the document.
|
||||
#
|
||||
# if the supplied password is not a valid user password for this document
|
||||
# then it returns nil
|
||||
#
|
||||
def auth_user_pass(pass)
|
||||
keyBegins = make_file_key(pass)
|
||||
if @revision >= 3
|
||||
#initialize out for first iteration
|
||||
out = Digest::MD5.digest(PassPadBytes.pack("C*") + @file_id)
|
||||
#zero doesn't matter -> so from 0-19
|
||||
20.times{ |i| out=RC4.new(xor_each_byte(keyBegins, i)).encrypt(out) }
|
||||
pass = @user_key[0, 16] == out
|
||||
else
|
||||
pass = RC4.new(keyBegins).encrypt(PassPadBytes.pack("C*")) == @user_key
|
||||
end
|
||||
pass ? keyBegins : nil
|
||||
end
|
||||
|
||||
def make_file_key( user_pass )
|
||||
# a) if there's a password, pad it to 32 bytes, else, just use the padding.
|
||||
@buf = pad_pass(user_pass)
|
||||
# c) add owner key
|
||||
@buf << @owner_key
|
||||
# d) add permissions 1 byte at a time, in little-endian order
|
||||
(0..24).step(8){|e| @buf << (@permissions >> e & 0xFF)}
|
||||
# e) add the file ID
|
||||
@buf << @file_id
|
||||
# f) if revision >= 4 and metadata not encrypted then add 4 bytes of 0xFF
|
||||
if @revision >= 4 && !@encryptMeta
|
||||
@buf << [0xFF,0xFF,0xFF,0xFF].pack('C*')
|
||||
end
|
||||
# b) init MD5 digest + g) finish the hash
|
||||
md5 = Digest::MD5.digest(@buf)
|
||||
# h) spin hash 50 times
|
||||
if @revision >= 3
|
||||
50.times {
|
||||
md5 = Digest::MD5.digest(md5[0, @key_length])
|
||||
}
|
||||
end
|
||||
# i) n = key_length revision >= 3, n = 5 revision == 2
|
||||
if @revision < 3
|
||||
md5[0, 5]
|
||||
else
|
||||
md5[0, @key_length]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,73 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
################################################################################
|
||||
# An internal PDF::Reader class that represents a stream object from a PDF. Stream
|
||||
# objects have 2 components, a dictionary that describes the content (size,
|
||||
# compression, etc) and a stream of bytes.
|
||||
#
|
||||
class Stream
|
||||
attr_accessor :hash, :data
|
||||
|
||||
################################################################################
|
||||
# Creates a new stream with the specified dictionary and data. The dictionary
|
||||
# should be a standard ruby hash, the data should be a standard ruby string.
|
||||
def initialize(hash, data)
|
||||
@hash = TypeCheck.cast_to_pdf_dict!(hash)
|
||||
@data = data
|
||||
@udata = nil
|
||||
end
|
||||
################################################################################
|
||||
# apply this streams filters to its data and return the result.
|
||||
def unfiltered_data
|
||||
return @udata if @udata
|
||||
@udata = data.dup
|
||||
|
||||
if hash.has_key?(:Filter)
|
||||
options = []
|
||||
|
||||
if hash.has_key?(:DecodeParms)
|
||||
if hash[:DecodeParms].is_a?(Hash)
|
||||
options = [hash[:DecodeParms]]
|
||||
else
|
||||
options = hash[:DecodeParms]
|
||||
end
|
||||
end
|
||||
|
||||
Array(hash[:Filter]).each_with_index do |filter, index|
|
||||
@udata = Filter.with(filter, options[index] || {}).filter(@udata)
|
||||
end
|
||||
end
|
||||
@udata
|
||||
end
|
||||
end
|
||||
################################################################################
|
||||
end
|
||||
################################################################################
|
||||
@@ -0,0 +1,34 @@
|
||||
# encoding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
# utilities.rb : General-purpose utility classes which don't fit anywhere else
|
||||
#
|
||||
# Copyright August 2012, Alex Dowad. All Rights Reserved.
|
||||
#
|
||||
# This is free software. Please see the LICENSE and COPYING files for details.
|
||||
#
|
||||
# This was originally written for the prawn gem.
|
||||
|
||||
require 'thread'
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# Throughout the pdf-reader codebase, repeated calculations which can benefit
|
||||
# from caching are made In some cases, caching and reusing results can not
|
||||
# only save CPU cycles but also greatly reduce memory requirements But at the
|
||||
# same time, we don't want to throw away thread safety We have two
|
||||
# interchangeable thread-safe cache implementations:
|
||||
class SynchronizedCache
|
||||
def initialize
|
||||
@cache = {}
|
||||
@mutex = Mutex.new
|
||||
end
|
||||
def [](key)
|
||||
@mutex.synchronize { @cache[key] }
|
||||
end
|
||||
def []=(key,value)
|
||||
@mutex.synchronize { @cache[key] = value }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,110 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
# A value object that represents one or more consecutive characters on a page.
|
||||
class TextRun
|
||||
include Comparable
|
||||
|
||||
attr_reader :origin
|
||||
attr_reader :width
|
||||
attr_reader :font_size
|
||||
attr_reader :text
|
||||
|
||||
alias :to_s :text
|
||||
|
||||
def initialize(x, y, width, font_size, text)
|
||||
@origin = PDF::Reader::Point.new(x, y)
|
||||
@width = width
|
||||
@font_size = font_size
|
||||
@text = text
|
||||
end
|
||||
|
||||
# Allows collections of TextRun objects to be sorted. They will be sorted
|
||||
# in order of their position on a cartesian plain - Top Left to Bottom Right
|
||||
def <=>(other)
|
||||
if x == other.x && y == other.y
|
||||
0
|
||||
elsif y < other.y
|
||||
1
|
||||
elsif y > other.y
|
||||
-1
|
||||
elsif x < other.x
|
||||
-1
|
||||
elsif x > other.x
|
||||
1
|
||||
end
|
||||
end
|
||||
|
||||
def x
|
||||
@origin.x
|
||||
end
|
||||
|
||||
def y
|
||||
@origin.y
|
||||
end
|
||||
|
||||
def endx
|
||||
@endx ||= @origin.x + width
|
||||
end
|
||||
|
||||
def endy
|
||||
@endy ||= @origin.y + font_size
|
||||
end
|
||||
|
||||
def mean_character_width
|
||||
@width / character_count
|
||||
end
|
||||
|
||||
def mergable?(other)
|
||||
y.to_i == other.y.to_i && font_size == other.font_size && mergable_range.include?(other.x)
|
||||
end
|
||||
|
||||
def +(other)
|
||||
raise ArgumentError, "#{other} cannot be merged with this run" unless mergable?(other)
|
||||
|
||||
if (other.x - endx) <( font_size * 0.2)
|
||||
TextRun.new(x, y, other.endx - x, font_size, text + other.text)
|
||||
else
|
||||
TextRun.new(x, y, other.endx - x, font_size, "#{text} #{other.text}")
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#{text} w:#{width} f:#{font_size} @#{x},#{y}"
|
||||
end
|
||||
|
||||
def intersect?(other_run)
|
||||
x <= other_run.endx && endx >= other_run.x &&
|
||||
endy >= other_run.y && y <= other_run.endy
|
||||
end
|
||||
|
||||
# return what percentage of this text run is overlapped by another run
|
||||
def intersection_area_percent(other_run)
|
||||
return 0 unless intersect?(other_run)
|
||||
|
||||
dx = [endx, other_run.endx].min - [x, other_run.x].max
|
||||
dy = [endy, other_run.endy].min - [y, other_run.y].max
|
||||
intersection_area = dx*dy
|
||||
|
||||
intersection_area.to_f / area
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def area
|
||||
(endx - x) * (endy - y)
|
||||
end
|
||||
|
||||
def mergable_range
|
||||
@mergable_range ||= Range.new(endx - 3, endx + font_size)
|
||||
end
|
||||
|
||||
# Assume string encoding is marked correctly and we can trust String#size to return a
|
||||
# character count
|
||||
def character_count
|
||||
@text.size.to_f
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
################################################################################
|
||||
# An internal PDF::Reader class that represents a single token from a PDF file.
|
||||
#
|
||||
# Behaves exactly like a Ruby String - it basically exists for convenience.
|
||||
class Token < String # :nodoc:
|
||||
################################################################################
|
||||
# Creates a new token with the specified value
|
||||
def initialize(val)
|
||||
super
|
||||
end
|
||||
################################################################################
|
||||
end
|
||||
################################################################################
|
||||
end
|
||||
################################################################################
|
||||
@@ -0,0 +1,196 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
# co-ordinate systems in PDF files are specified using a 3x3 matrix that looks
|
||||
# something like this:
|
||||
#
|
||||
# [ a b 0 ]
|
||||
# [ c d 0 ]
|
||||
# [ e f 1 ]
|
||||
#
|
||||
# Because the final column never changes, we can represent each matrix using
|
||||
# only 6 numbers. This is important to save CPU time, memory and GC pressure
|
||||
# caused by allocating too many unnecessary objects.
|
||||
class TransformationMatrix
|
||||
attr_reader :a, :b, :c, :d, :e, :f
|
||||
|
||||
def initialize(a, b, c, d, e, f)
|
||||
@a, @b, @c, @d, @e, @f = a, b, c, d, e, f
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#{a}, #{b}, 0,\n#{c}, #{d}, #{0},\n#{e}, #{f}, 1"
|
||||
end
|
||||
|
||||
def to_a
|
||||
[@a,@b,0,
|
||||
@c,@d,0,
|
||||
@e,@f,1]
|
||||
end
|
||||
|
||||
# multiply this matrix with another.
|
||||
#
|
||||
# the second matrix is represented by the 6 scalar values that are changeable
|
||||
# in a PDF transformation matrix.
|
||||
#
|
||||
# WARNING: This mutates the current matrix to avoid allocating memory when
|
||||
# we don't need too. Matrices are multiplied ALL THE FREAKING TIME
|
||||
# so this is a worthwhile optimisation
|
||||
#
|
||||
# NOTE: When multiplying matrices, ordering matters. Double check
|
||||
# the PDF spec to ensure you're multiplying things correctly.
|
||||
#
|
||||
# NOTE: see Section 8.3.3, PDF 32000-1:2008, pp 119
|
||||
#
|
||||
# NOTE: The if statements in this method are ordered to prefer optimisations
|
||||
# that allocate fewer objects
|
||||
#
|
||||
# TODO: it might be worth adding an optimised path for vertical
|
||||
# displacement to speed up processing documents that use vertical
|
||||
# writing systems
|
||||
#
|
||||
def multiply!(a,b,c, d,e,f)
|
||||
if a == 1 && b == 0 && c == 0 && d == 1 && e == 0 && f == 0
|
||||
# the identity matrix, no effect
|
||||
self
|
||||
elsif @a == 1 && @b == 0 && @c == 0 && @d == 1 && @e == 0 && @f == 0
|
||||
# I'm the identity matrix, so just copy values across
|
||||
@a = a
|
||||
@b = b
|
||||
@c = c
|
||||
@d = d
|
||||
@e = e
|
||||
@f = f
|
||||
elsif a == 1 && b == 0 && c == 0 && d == 1 && f == 0
|
||||
# the other matrix is a horizontal displacement
|
||||
horizontal_displacement_multiply!(e)
|
||||
elsif @a == 1 && @b == 0 && @c == 0 && @d == 1 && @f == 0
|
||||
# I'm a horizontal displacement
|
||||
horizontal_displacement_multiply_reversed!(a,b,c,d,e,f)
|
||||
elsif @a != 1 && @b == 0 && @c == 0 && @d != 1 && @e == 0 && @f == 0
|
||||
# I'm a xy scale
|
||||
xy_scaling_multiply_reversed!(a,b,c,d,e,f)
|
||||
elsif a != 1 && b == 0 && c == 0 && d != 1 && e == 0 && f == 0
|
||||
# the other matrix is an xy scale
|
||||
xy_scaling_multiply!(a,b,c,d,e,f)
|
||||
else
|
||||
faster_multiply!(a,b,c, d,e,f)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# Optimised method for when the second matrix in the calculation is
|
||||
# a simple horizontal displacement.
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ 1 2 0 ] [ 1 0 0 ]
|
||||
# [ 3 4 0 ] x [ 0 1 0 ]
|
||||
# [ 5 6 1 ] [ e2 0 1 ]
|
||||
#
|
||||
def horizontal_displacement_multiply!(e2)
|
||||
@e = @e + e2
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Optimised method for when the first matrix in the calculation is
|
||||
# a simple horizontal displacement.
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ 1 0 0 ] [ 1 2 0 ]
|
||||
# [ 0 1 0 ] x [ 3 4 0 ]
|
||||
# [ 5 0 1 ] [ 5 6 1 ]
|
||||
#
|
||||
def horizontal_displacement_multiply_reversed!(a2,b2,c2,d2,e2,f2)
|
||||
newa = a2
|
||||
newb = b2
|
||||
newc = c2
|
||||
newd = d2
|
||||
newe = (@e * a2) + e2
|
||||
newf = (@e * b2) + f2
|
||||
@a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
|
||||
end
|
||||
|
||||
# Optimised method for when the second matrix in the calculation is
|
||||
# an X and Y scale
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ 1 2 0 ] [ 5 0 0 ]
|
||||
# [ 3 4 0 ] x [ 0 5 0 ]
|
||||
# [ 5 6 1 ] [ 0 0 1 ]
|
||||
#
|
||||
def xy_scaling_multiply!(a2,b2,c2,d2,e2,f2)
|
||||
newa = @a * a2
|
||||
newb = @b * d2
|
||||
newc = @c * a2
|
||||
newd = @d * d2
|
||||
newe = @e * a2
|
||||
newf = @f * d2
|
||||
@a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
|
||||
end
|
||||
|
||||
# Optimised method for when the first matrix in the calculation is
|
||||
# an X and Y scale
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ 5 0 0 ] [ 1 2 0 ]
|
||||
# [ 0 5 0 ] x [ 3 4 0 ]
|
||||
# [ 0 0 1 ] [ 5 6 1 ]
|
||||
#
|
||||
def xy_scaling_multiply_reversed!(a2,b2,c2,d2,e2,f2)
|
||||
newa = @a * a2
|
||||
newb = @a * b2
|
||||
newc = @d * c2
|
||||
newd = @d * d2
|
||||
newe = e2
|
||||
newf = f2
|
||||
@a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
|
||||
end
|
||||
|
||||
# A general solution to multiplying two 3x3 matrixes. This is correct in all cases,
|
||||
# but slower due to excessive object allocations. It's not actually used in any
|
||||
# active code paths, but is here for reference. Use faster_multiply instead.
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ a b 0 ] [ a b 0 ]
|
||||
# [ c d 0 ] x [ c d 0 ]
|
||||
# [ e f 1 ] [ e f 1 ]
|
||||
#
|
||||
def regular_multiply!(a2,b2,c2,d2,e2,f2)
|
||||
newa = (@a * a2) + (@b * c2) + (e2 * 0)
|
||||
newb = (@a * b2) + (@b * d2) + (f2 * 0)
|
||||
newc = (@c * a2) + (@d * c2) + (e2 * 0)
|
||||
newd = (@c * b2) + (@d * d2) + (f2 * 0)
|
||||
newe = (@e * a2) + (@f * c2) + (e2 * 1)
|
||||
newf = (@e * b2) + (@f * d2) + (f2 * 1)
|
||||
@a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
|
||||
end
|
||||
|
||||
# A general solution for multiplying two matrices when we know all values
|
||||
# in the final column are fixed. This is the fallback method for when none
|
||||
# of the optimised methods are applicable.
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ a b 0 ] [ a b 0 ]
|
||||
# [ c d 0 ] x [ c d 0 ]
|
||||
# [ e f 1 ] [ e f 1 ]
|
||||
#
|
||||
def faster_multiply!(a2,b2,c2, d2,e2,f2)
|
||||
newa = (@a * a2) + (@b * c2)
|
||||
newb = (@a * b2) + (@b * d2)
|
||||
newc = (@c * a2) + (@d * c2)
|
||||
newd = (@c * b2) + (@d * d2)
|
||||
newe = (@e * a2) + (@f * c2) + e2
|
||||
newf = (@e * b2) + (@f * d2) + f2
|
||||
@a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,98 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module PDF
|
||||
class Reader
|
||||
|
||||
# Cast untrusted input (usually parsed out of a PDF file) to a known type
|
||||
#
|
||||
class TypeCheck
|
||||
|
||||
def self.cast_to_int!(obj)
|
||||
if obj.is_a?(Integer)
|
||||
obj
|
||||
elsif obj.nil?
|
||||
0
|
||||
elsif obj.respond_to?(:to_i)
|
||||
obj.to_i
|
||||
else
|
||||
raise MalformedPDFError, "Unable to cast to integer"
|
||||
end
|
||||
end
|
||||
|
||||
def self.cast_to_numeric!(obj)
|
||||
if obj.is_a?(Numeric)
|
||||
obj
|
||||
elsif obj.nil?
|
||||
0
|
||||
elsif obj.respond_to?(:to_f)
|
||||
obj.to_f
|
||||
elsif obj.respond_to?(:to_i)
|
||||
obj.to_i
|
||||
else
|
||||
raise MalformedPDFError, "Unable to cast to numeric"
|
||||
end
|
||||
end
|
||||
|
||||
def self.cast_to_string!(string)
|
||||
if string.is_a?(String)
|
||||
string
|
||||
elsif string.nil?
|
||||
""
|
||||
elsif string.respond_to?(:to_s)
|
||||
string.to_s
|
||||
else
|
||||
raise MalformedPDFError, "Unable to cast to string"
|
||||
end
|
||||
end
|
||||
|
||||
def self.cast_to_symbol(obj)
|
||||
if obj.is_a?(Symbol)
|
||||
obj
|
||||
elsif obj.nil?
|
||||
nil
|
||||
elsif obj.respond_to?(:to_sym)
|
||||
obj.to_sym
|
||||
else
|
||||
raise MalformedPDFError, "Unable to cast to symbol"
|
||||
end
|
||||
end
|
||||
|
||||
def self.cast_to_symbol!(obj)
|
||||
res = cast_to_symbol(obj)
|
||||
if res
|
||||
res
|
||||
else
|
||||
raise MalformedPDFError, "Unable to cast to symbol"
|
||||
end
|
||||
end
|
||||
|
||||
def self.cast_to_pdf_dict!(obj)
|
||||
if obj.is_a?(Hash)
|
||||
obj
|
||||
elsif obj.respond_to?(:to_h)
|
||||
obj.to_h
|
||||
else
|
||||
raise MalformedPDFError, "Unable to cast to hash"
|
||||
end
|
||||
end
|
||||
|
||||
def self.cast_to_pdf_dict_with_stream_values!(obj)
|
||||
if obj.is_a?(Hash)
|
||||
result = Hash.new
|
||||
obj.each do |k, v|
|
||||
raise MalformedPDFError, "Expected a stream" unless v.is_a?(PDF::Reader::Stream)
|
||||
result[cast_to_symbol!(k)] = v
|
||||
end
|
||||
result
|
||||
elsif obj.respond_to?(:to_h)
|
||||
cast_to_pdf_dict_with_stream_values!(obj.to_h)
|
||||
else
|
||||
raise MalformedPDFError, "Unable to cast to hash"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
|
||||
# Security handler for when we don't support the flavour of encryption
|
||||
# used in a PDF.
|
||||
class UnimplementedSecurityHandler
|
||||
def self.supports?(encrypt)
|
||||
true
|
||||
end
|
||||
|
||||
def decrypt(buf, ref)
|
||||
raise PDF::Reader::EncryptedPDFError, "Unsupported encryption style"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,262 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module PDF
|
||||
class Reader
|
||||
|
||||
# Page#walk will execute the content stream of a page, calling methods on a receiver class
|
||||
# provided by the user. Each operator has a specific set of parameters it expects, and we
|
||||
# wrap the users receiver class in this one to verify the PDF uses valid parameters.
|
||||
#
|
||||
# Without these checks, users can't be confident about the number of parameters they'll receive
|
||||
# for an operator, or what the type of those parameters will be. Everyone ends up building their
|
||||
# own type safety guard clauses and it's tedious.
|
||||
#
|
||||
# Not all operators have type safety implemented yet, but we can expand the number over time.
|
||||
class ValidatingReceiver
|
||||
|
||||
def initialize(wrapped)
|
||||
@wrapped = wrapped
|
||||
end
|
||||
|
||||
def page=(page)
|
||||
call_wrapped(:page=, page)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Graphics State Operators
|
||||
#####################################################
|
||||
def save_graphics_state(*args)
|
||||
call_wrapped(:save_graphics_state)
|
||||
end
|
||||
|
||||
def restore_graphics_state(*args)
|
||||
call_wrapped(:restore_graphics_state)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Matrix Operators
|
||||
#####################################################
|
||||
|
||||
def concatenate_matrix(*args)
|
||||
a, b, c, d, e, f = *args
|
||||
call_wrapped(
|
||||
:concatenate_matrix,
|
||||
TypeCheck.cast_to_numeric!(a),
|
||||
TypeCheck.cast_to_numeric!(b),
|
||||
TypeCheck.cast_to_numeric!(c),
|
||||
TypeCheck.cast_to_numeric!(d),
|
||||
TypeCheck.cast_to_numeric!(e),
|
||||
TypeCheck.cast_to_numeric!(f),
|
||||
)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text Object Operators
|
||||
#####################################################
|
||||
|
||||
def begin_text_object(*args)
|
||||
call_wrapped(:begin_text_object)
|
||||
end
|
||||
|
||||
def end_text_object(*args)
|
||||
call_wrapped(:end_text_object)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text State Operators
|
||||
#####################################################
|
||||
def set_character_spacing(*args)
|
||||
char_spacing, _ = *args
|
||||
call_wrapped(
|
||||
:set_character_spacing,
|
||||
TypeCheck.cast_to_numeric!(char_spacing)
|
||||
)
|
||||
end
|
||||
|
||||
def set_horizontal_text_scaling(*args)
|
||||
h_scaling, _ = *args
|
||||
call_wrapped(
|
||||
:set_horizontal_text_scaling,
|
||||
TypeCheck.cast_to_numeric!(h_scaling)
|
||||
)
|
||||
end
|
||||
|
||||
def set_text_font_and_size(*args)
|
||||
label, size, _ = *args
|
||||
call_wrapped(
|
||||
:set_text_font_and_size,
|
||||
TypeCheck.cast_to_symbol(label),
|
||||
TypeCheck.cast_to_numeric!(size)
|
||||
)
|
||||
end
|
||||
|
||||
def set_text_leading(*args)
|
||||
leading, _ = *args
|
||||
call_wrapped(
|
||||
:set_text_leading,
|
||||
TypeCheck.cast_to_numeric!(leading)
|
||||
)
|
||||
end
|
||||
|
||||
def set_text_rendering_mode(*args)
|
||||
mode, _ = *args
|
||||
call_wrapped(
|
||||
:set_text_rendering_mode,
|
||||
TypeCheck.cast_to_numeric!(mode)
|
||||
)
|
||||
end
|
||||
|
||||
def set_text_rise(*args)
|
||||
rise, _ = *args
|
||||
call_wrapped(
|
||||
:set_text_rise,
|
||||
TypeCheck.cast_to_numeric!(rise)
|
||||
)
|
||||
end
|
||||
|
||||
def set_word_spacing(*args)
|
||||
word_spacing, _ = *args
|
||||
call_wrapped(
|
||||
:set_word_spacing,
|
||||
TypeCheck.cast_to_numeric!(word_spacing)
|
||||
)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text Positioning Operators
|
||||
#####################################################
|
||||
|
||||
def move_text_position(*args) # Td
|
||||
x, y, _ = *args
|
||||
call_wrapped(
|
||||
:move_text_position,
|
||||
TypeCheck.cast_to_numeric!(x),
|
||||
TypeCheck.cast_to_numeric!(y)
|
||||
)
|
||||
end
|
||||
|
||||
def move_text_position_and_set_leading(*args) # TD
|
||||
x, y, _ = *args
|
||||
call_wrapped(
|
||||
:move_text_position_and_set_leading,
|
||||
TypeCheck.cast_to_numeric!(x),
|
||||
TypeCheck.cast_to_numeric!(y)
|
||||
)
|
||||
end
|
||||
|
||||
def set_text_matrix_and_text_line_matrix(*args) # Tm
|
||||
a, b, c, d, e, f = *args
|
||||
call_wrapped(
|
||||
:set_text_matrix_and_text_line_matrix,
|
||||
TypeCheck.cast_to_numeric!(a),
|
||||
TypeCheck.cast_to_numeric!(b),
|
||||
TypeCheck.cast_to_numeric!(c),
|
||||
TypeCheck.cast_to_numeric!(d),
|
||||
TypeCheck.cast_to_numeric!(e),
|
||||
TypeCheck.cast_to_numeric!(f),
|
||||
)
|
||||
end
|
||||
|
||||
def move_to_start_of_next_line(*args) # T*
|
||||
call_wrapped(:move_to_start_of_next_line)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text Showing Operators
|
||||
#####################################################
|
||||
def show_text(*args) # Tj (AWAY)
|
||||
string, _ = *args
|
||||
call_wrapped(
|
||||
:show_text,
|
||||
TypeCheck.cast_to_string!(string)
|
||||
)
|
||||
end
|
||||
|
||||
def show_text_with_positioning(*args) # TJ [(A) 120 (WA) 20 (Y)]
|
||||
params, _ = *args
|
||||
unless params.is_a?(Array)
|
||||
raise MalformedPDFError, "TJ operator expects a single Array argument"
|
||||
end
|
||||
|
||||
call_wrapped(
|
||||
:show_text_with_positioning,
|
||||
params
|
||||
)
|
||||
end
|
||||
|
||||
def move_to_next_line_and_show_text(*args) # '
|
||||
string, _ = *args
|
||||
call_wrapped(
|
||||
:move_to_next_line_and_show_text,
|
||||
TypeCheck.cast_to_string!(string)
|
||||
)
|
||||
end
|
||||
|
||||
def set_spacing_next_line_show_text(*args) # "
|
||||
aw, ac, string = *args
|
||||
call_wrapped(
|
||||
:set_spacing_next_line_show_text,
|
||||
TypeCheck.cast_to_numeric!(aw),
|
||||
TypeCheck.cast_to_numeric!(ac),
|
||||
TypeCheck.cast_to_string!(string)
|
||||
)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Form XObject Operators
|
||||
#####################################################
|
||||
|
||||
def invoke_xobject(*args)
|
||||
label, _ = *args
|
||||
|
||||
call_wrapped(
|
||||
:invoke_xobject,
|
||||
TypeCheck.cast_to_symbol(label)
|
||||
)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Inline Image Operators
|
||||
#####################################################
|
||||
|
||||
def begin_inline_image(*args)
|
||||
call_wrapped(:begin_inline_image)
|
||||
end
|
||||
|
||||
def begin_inline_image_data(*args)
|
||||
# We can't use call_wrapped() here because sorbet won't allow splat args with a dynamic
|
||||
# number of elements
|
||||
@wrapped.begin_inline_image_data(*args) if @wrapped.respond_to?(:begin_inline_image_data)
|
||||
end
|
||||
|
||||
def end_inline_image(*args)
|
||||
data, _ = *args
|
||||
|
||||
call_wrapped(
|
||||
:end_inline_image,
|
||||
TypeCheck.cast_to_string!(data)
|
||||
)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Final safety net for any operators that don't have type checking enabled yet
|
||||
#####################################################
|
||||
|
||||
def respond_to?(meth)
|
||||
@wrapped.respond_to?(meth)
|
||||
end
|
||||
|
||||
def method_missing(methodname, *args)
|
||||
@wrapped.send(methodname, *args)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def call_wrapped(methodname, *args)
|
||||
@wrapped.send(methodname, *args) if @wrapped.respond_to?(methodname)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
# PDF files may define fonts in a number of ways. Each approach means we must
|
||||
# calculate glyph widths differently, so this set of classes conform to an
|
||||
# interface that will perform the appropriate calculations.
|
||||
|
||||
require 'pdf/reader/width_calculator/built_in'
|
||||
require 'pdf/reader/width_calculator/composite'
|
||||
require 'pdf/reader/width_calculator/true_type'
|
||||
require 'pdf/reader/width_calculator/type_zero'
|
||||
require 'pdf/reader/width_calculator/type_one_or_three'
|
||||
@@ -0,0 +1,69 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'afm'
|
||||
require 'pdf/reader/synchronized_cache'
|
||||
|
||||
class PDF::Reader
|
||||
module WidthCalculator
|
||||
|
||||
# Type1 fonts can be one of 14 "built in" standard fonts. In these cases,
|
||||
# the reader is expected to have it's own copy of the font metrics.
|
||||
# see Section 9.6.2.2, PDF 32000-1:2008, pp 256
|
||||
class BuiltIn
|
||||
|
||||
BUILTINS = [
|
||||
:Courier, :"Courier-Bold", :"Courier-BoldOblique", :"Courier-Oblique",
|
||||
:Helvetica, :"Helvetica-Bold", :"Helvetica-BoldOblique", :"Helvetica-Oblique",
|
||||
:Symbol,
|
||||
:"Times-Roman", :"Times-Bold", :"Times-BoldItalic", :"Times-Italic",
|
||||
:ZapfDingbats
|
||||
]
|
||||
|
||||
def initialize(font)
|
||||
@font = font
|
||||
@@all_metrics ||= PDF::Reader::SynchronizedCache.new
|
||||
|
||||
basefont = extract_basefont(font.basefont)
|
||||
metrics_path = File.join(File.dirname(__FILE__), "..","afm","#{basefont}.afm")
|
||||
|
||||
if File.file?(metrics_path)
|
||||
@metrics = @@all_metrics[metrics_path] ||= AFM::Font.new(metrics_path)
|
||||
else
|
||||
raise ArgumentError, "No built-in metrics for #{font.basefont}"
|
||||
end
|
||||
end
|
||||
|
||||
def glyph_width(code_point)
|
||||
return 0 if code_point.nil? || code_point < 0
|
||||
|
||||
names = @font.encoding.int_to_name(code_point)
|
||||
metrics = names.map { |name|
|
||||
@metrics.char_metrics[name.to_s]
|
||||
}.compact.first
|
||||
|
||||
if metrics
|
||||
metrics[:wx]
|
||||
else
|
||||
@font.widths[code_point - 1] || 0
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def control_character?(code_point)
|
||||
match = @font.encoding.int_to_name(code_point).first.to_s[/\Acontrol..\Z/]
|
||||
match ? true : false
|
||||
end
|
||||
|
||||
def extract_basefont(font_name)
|
||||
if BUILTINS.include?(font_name)
|
||||
font_name.to_s
|
||||
else
|
||||
"Times-Roman"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
module WidthCalculator
|
||||
# CIDFontType0 or CIDFontType2 use DW (integer) and W (array) to determine
|
||||
# codepoint widths, note that CIDFontType2 will contain a true type font
|
||||
# program which could be used to calculate width, however, a conforming writer
|
||||
# is supposed to convert the widths for the codepoints used into the W array
|
||||
# so that it can be used.
|
||||
# see Section 9.7.4.1, PDF 32000-1:2008, pp 269-270
|
||||
class Composite
|
||||
|
||||
def initialize(font)
|
||||
@font = font
|
||||
@widths = PDF::Reader::CidWidths.new(@font.cid_default_width, @font.cid_widths)
|
||||
end
|
||||
|
||||
def glyph_width(code_point)
|
||||
return 0 if code_point.nil? || code_point < 0
|
||||
|
||||
w = @widths[code_point]
|
||||
# 0 is a valid width
|
||||
if w
|
||||
w.to_f
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
module WidthCalculator
|
||||
# Calculates the width of a glyph in a TrueType font
|
||||
class TrueType
|
||||
|
||||
def initialize(font)
|
||||
@font = font
|
||||
|
||||
if fd = @font.font_descriptor
|
||||
@missing_width = fd.missing_width
|
||||
else
|
||||
@missing_width = 0
|
||||
end
|
||||
end
|
||||
|
||||
def glyph_width(code_point)
|
||||
return 0 if code_point.nil? || code_point < 0
|
||||
glyph_width_from_font(code_point) || glyph_width_from_descriptor(code_point) || 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
#TODO convert Type3 units 1000 units => 1 text space unit
|
||||
def glyph_width_from_font(code_point)
|
||||
return if @font.widths.nil? || @font.widths.count == 0
|
||||
|
||||
# in ruby a negative index is valid, and will go from the end of the array
|
||||
# which is undesireable in this case.
|
||||
first_char = @font.first_char
|
||||
if first_char && first_char <= code_point
|
||||
@font.widths.fetch(code_point - first_char, @missing_width.to_i).to_f
|
||||
else
|
||||
@missing_width.to_f
|
||||
end
|
||||
end
|
||||
|
||||
def glyph_width_from_descriptor(code_point)
|
||||
# true type fonts will have most of their information contained
|
||||
# with-in a program inside the font descriptor, however the widths
|
||||
# may not be in standard PDF glyph widths (1000 units => 1 text space unit)
|
||||
# so this width will need to be scaled
|
||||
if fd = @font.font_descriptor
|
||||
if w = fd.glyph_width(code_point)
|
||||
w.to_f * fd.glyph_to_pdf_scale_factor.to_f
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
module WidthCalculator
|
||||
# Calculates the width of a glyph in a Type One or Type Three
|
||||
class TypeOneOrThree
|
||||
|
||||
def initialize(font)
|
||||
@font = font
|
||||
|
||||
if fd = @font.font_descriptor
|
||||
@missing_width = fd.missing_width
|
||||
else
|
||||
@missing_width = 0
|
||||
end
|
||||
end
|
||||
|
||||
def glyph_width(code_point)
|
||||
return 0 if code_point.nil? || code_point < 0
|
||||
return 0 if @font.widths.nil? || @font.widths.count == 0
|
||||
|
||||
# in ruby a negative index is valid, and will go from the end of the array
|
||||
# which is undesireable in this case.
|
||||
first_char = @font.first_char
|
||||
if first_char && first_char <= code_point
|
||||
@font.widths.fetch(code_point - first_char, @missing_width.to_i).to_f
|
||||
else
|
||||
@missing_width.to_f
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
module WidthCalculator
|
||||
# Type0 (or Composite) fonts are a "root font" that rely on a "descendant font"
|
||||
# to do the heavy lifting. The "descendant font" is a CID-Keyed font.
|
||||
# see Section 9.7.1, PDF 32000-1:2008, pp 267
|
||||
# so if we are calculating a Type0 font width, we just pass off to
|
||||
# the descendant font
|
||||
class TypeZero
|
||||
|
||||
def initialize(font)
|
||||
@font = font
|
||||
end
|
||||
|
||||
def glyph_width(code_point)
|
||||
return 0 if code_point.nil? || code_point < 0
|
||||
|
||||
if descendant_font = @font.descendantfonts.first
|
||||
descendant_font.glyph_width(code_point).to_f
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,275 @@
|
||||
# coding: utf-8
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class PDF::Reader
|
||||
################################################################################
|
||||
# An internal PDF::Reader class that represents the XRef table in a PDF file as a
|
||||
# hash-like object.
|
||||
#
|
||||
# An Xref table is a map of object identifiers and byte offsets. Any time a particular
|
||||
# object needs to be found, the Xref table is used to find where it is stored in the
|
||||
# file.
|
||||
#
|
||||
# Hash keys are object ids, values are either:
|
||||
#
|
||||
# * a byte offset where the object starts (regular PDF objects)
|
||||
# * a PDF::Reader::Reference instance that points to a stream that contains the
|
||||
# desired object (PDF objects embedded in an object stream)
|
||||
#
|
||||
# The class behaves much like a standard Ruby hash, including the use of
|
||||
# the Enumerable mixin. The key difference is no []= method - the hash
|
||||
# is read only.
|
||||
#
|
||||
class XRef
|
||||
include Enumerable
|
||||
attr_reader :trailer
|
||||
|
||||
################################################################################
|
||||
# create a new Xref table based on the contents of the supplied io object
|
||||
#
|
||||
# io - must be an IO object, generally either a file or a StringIO
|
||||
#
|
||||
def initialize(io)
|
||||
@io = io
|
||||
@junk_offset = calc_junk_offset(io) || 0
|
||||
@xref = {}
|
||||
@trailer = load_offsets
|
||||
end
|
||||
|
||||
################################################################################
|
||||
# return the number of objects in this file. Objects with multiple generations are
|
||||
# only counter once.
|
||||
def size
|
||||
@xref.size
|
||||
end
|
||||
################################################################################
|
||||
# returns the byte offset for the specified PDF object.
|
||||
#
|
||||
# ref - a PDF::Reader::Reference object containing an object ID and revision number
|
||||
def [](ref)
|
||||
@xref.fetch(ref.id, {}).fetch(ref.gen)
|
||||
rescue
|
||||
raise InvalidObjectError, "Object #{ref.id}, Generation #{ref.gen} is invalid"
|
||||
end
|
||||
################################################################################
|
||||
# iterate over each object in the xref table
|
||||
def each(&block)
|
||||
ids = @xref.keys.sort
|
||||
ids.each do |id|
|
||||
gen = @xref.fetch(id, {}).keys.sort[-1]
|
||||
yield PDF::Reader::Reference.new(id, gen.to_i)
|
||||
end
|
||||
end
|
||||
################################################################################
|
||||
private
|
||||
################################################################################
|
||||
# Read a xref table from the underlying buffer.
|
||||
#
|
||||
# If offset is specified the table will be loaded from there, otherwise the
|
||||
# default offset will be located and used.
|
||||
#
|
||||
# After seeking to the offset, processing is handed of to either load_xref_table()
|
||||
# or load_xref_stream() based on what we find there.
|
||||
#
|
||||
def load_offsets(offset = nil)
|
||||
offset ||= new_buffer.find_first_xref_offset
|
||||
offset += @junk_offset
|
||||
|
||||
buf = new_buffer(offset)
|
||||
tok_one = buf.token
|
||||
|
||||
# we have a traditional xref table
|
||||
return load_xref_table(buf) if tok_one == "xref" || tok_one == "ref"
|
||||
|
||||
tok_two = buf.token
|
||||
tok_three = buf.token
|
||||
|
||||
# we have an XRef stream
|
||||
if tok_one.to_i >= 0 && tok_two.to_i >= 0 && tok_three == "obj"
|
||||
buf = new_buffer(offset)
|
||||
# Maybe we should be parsing the ObjectHash second argument to the Parser here,
|
||||
# to handle the case where an XRef Stream has the Length specified via an
|
||||
# indirect object
|
||||
stream = PDF::Reader::Parser.new(buf).object(tok_one.to_i, tok_two.to_i)
|
||||
return load_xref_stream(stream)
|
||||
end
|
||||
|
||||
raise PDF::Reader::MalformedPDFError,
|
||||
"xref table not found at offset #{offset} (#{tok_one} != xref)"
|
||||
end
|
||||
################################################################################
|
||||
# Assumes the underlying buffer is positioned at the start of a traditional
|
||||
# Xref table and processes it into memory.
|
||||
def load_xref_table(buf)
|
||||
params = []
|
||||
|
||||
while !params.include?("trailer") && !params.include?(nil)
|
||||
if params.size == 2
|
||||
unless params[0].to_s.match(/\A\d+\z/)
|
||||
raise MalformedPDFError, "invalid xref table, expected object ID"
|
||||
end
|
||||
|
||||
objid, count = params[0].to_i, params[1].to_i
|
||||
count.times do
|
||||
offset = buf.token.to_i
|
||||
generation = buf.token.to_i
|
||||
state = buf.token
|
||||
|
||||
# Some PDF writers start numbering at 1 instead of 0. Fix up the number.
|
||||
# TODO should this fix be logged?
|
||||
objid = 0 if objid == 1 and offset == 0 and generation == 65535 and state == 'f'
|
||||
store(objid, generation, offset + @junk_offset) if state == "n" && offset > 0
|
||||
objid += 1
|
||||
params.clear
|
||||
end
|
||||
end
|
||||
params << buf.token
|
||||
end
|
||||
|
||||
trailer = Parser.new(buf).parse_token
|
||||
|
||||
unless trailer.kind_of?(Hash)
|
||||
raise MalformedPDFError, "PDF malformed, trailer should be a dictionary"
|
||||
end
|
||||
|
||||
load_offsets(trailer[:XRefStm]) if trailer.has_key?(:XRefStm)
|
||||
# Some PDF creators seem to use '/Prev 0' in trailer if there is no previous xref
|
||||
# It's not possible for an xref to appear at offset 0, so can safely skip the ref
|
||||
load_offsets(trailer[:Prev].to_i) if trailer.has_key?(:Prev) and trailer[:Prev].to_i != 0
|
||||
|
||||
trailer
|
||||
end
|
||||
|
||||
################################################################################
|
||||
# Read an XRef stream from the underlying buffer instead of a traditional xref table.
|
||||
#
|
||||
def load_xref_stream(stream)
|
||||
unless stream.is_a?(PDF::Reader::Stream) && stream.hash[:Type] == :XRef
|
||||
raise PDF::Reader::MalformedPDFError, "xref stream not found when expected"
|
||||
end
|
||||
trailer = Hash[stream.hash.select { |key, value|
|
||||
[:Size, :Prev, :Root, :Encrypt, :Info, :ID].include?(key)
|
||||
}]
|
||||
|
||||
widths = stream.hash[:W]
|
||||
|
||||
PDF::Reader::Error.validate_type_as_malformed(widths, "xref stream widths", Array)
|
||||
|
||||
entry_length = widths.inject(0) { |s, w|
|
||||
unless w.is_a?(Integer)
|
||||
w = 0
|
||||
end
|
||||
s + w
|
||||
}
|
||||
raw_data = StringIO.new(stream.unfiltered_data)
|
||||
if stream.hash[:Index]
|
||||
index = stream.hash[:Index]
|
||||
else
|
||||
index = [0, stream.hash[:Size]]
|
||||
end
|
||||
index.each_slice(2) do |start_id, size|
|
||||
obj_ids = (start_id..(start_id+(size-1)))
|
||||
obj_ids.each do |objid|
|
||||
entry = raw_data.read(entry_length) || ""
|
||||
f1 = unpack_bytes(entry[0,widths[0]])
|
||||
f2 = unpack_bytes(entry[widths[0],widths[1]])
|
||||
f3 = unpack_bytes(entry[widths[0]+widths[1],widths[2]])
|
||||
if f1 == 1 && f2 > 0
|
||||
store(objid, f3, f2 + @junk_offset)
|
||||
elsif f1 == 2 && f2 > 0
|
||||
store(objid, 0, PDF::Reader::Reference.new(f2, 0))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
load_offsets(trailer[:Prev].to_i) if trailer.has_key?(:Prev)
|
||||
|
||||
trailer
|
||||
end
|
||||
################################################################################
|
||||
# XRef streams pack info into integers 1-N bytes wide. Depending on the number of
|
||||
# bytes they need to be converted to an int in different ways.
|
||||
#
|
||||
def unpack_bytes(bytes)
|
||||
if bytes.to_s.size == 0
|
||||
0
|
||||
elsif bytes.size == 1
|
||||
bytes.unpack("C")[0]
|
||||
elsif bytes.size == 2
|
||||
bytes.unpack("n")[0]
|
||||
elsif bytes.size == 3
|
||||
("\x00" + bytes).unpack("N")[0]
|
||||
elsif bytes.size == 4
|
||||
bytes.unpack("N")[0]
|
||||
elsif bytes.size == 8
|
||||
bytes.unpack("Q>")[0]
|
||||
else
|
||||
raise UnsupportedFeatureError, "Unable to unpack xref stream entries of #{bytes.size} bytes"
|
||||
end
|
||||
end
|
||||
################################################################################
|
||||
# Wrap the io stream we're working with in a buffer that can tokenise it for us.
|
||||
#
|
||||
# We create multiple buffers so we can be tokenising multiple sections of the file
|
||||
# at the same time without worrying about clearing the buffers contents.
|
||||
#
|
||||
def new_buffer(offset = 0)
|
||||
PDF::Reader::Buffer.new(@io, :seek => offset)
|
||||
end
|
||||
################################################################################
|
||||
# Stores an offset value for a particular PDF object ID and revision number
|
||||
#
|
||||
def store(id, gen, offset)
|
||||
(@xref[id] ||= {})[gen] ||= offset
|
||||
end
|
||||
################################################################################
|
||||
# Returns the offset of the PDF document in the +stream+. In theory this
|
||||
# should always be 0, but all sort of crazy junk is prefixed to PDF files
|
||||
# in the real world.
|
||||
#
|
||||
# Checks up to 1024 chars into the file,
|
||||
# returns nil if no PDF data detected.
|
||||
# Adobe PDF 1.4 spec (3.4.1) 12. Acrobat viewers require only that the
|
||||
# header appear somewhere within the first 1024 bytes of the file
|
||||
#
|
||||
def calc_junk_offset(io)
|
||||
io.rewind
|
||||
offset = io.pos
|
||||
until (c = io.readchar) == '%' || c == 37 || offset > 1024
|
||||
offset += 1
|
||||
end
|
||||
io.rewind
|
||||
offset < 1024 ? offset : nil
|
||||
rescue EOFError
|
||||
nil
|
||||
end
|
||||
end
|
||||
################################################################################
|
||||
end
|
||||
################################################################################
|
||||
@@ -0,0 +1,13 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
# There's no point rendering zero-width characters
|
||||
class ZeroWidthRunsFilter
|
||||
|
||||
def self.exclude_zero_width_runs(runs)
|
||||
runs.reject { |run| run.width == 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user