Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
@@ -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